2024-11-10 01:55:07 -06:00
import { API } from './api' ;
2024-11-15 13:49:33 -06:00
import { Gateway } from './connectors/gateway' ;
2024-11-10 01:55:07 -06:00
import { Processor } from './processor' ;
2024-11-15 13:49:33 -06:00
import { Expectation , fromDiscord , Intent , type ClientConfig } from '../types/common' ;
2024-11-10 01:55:07 -06:00
import { exists , isNil } from '../utils/index' ;
import type { Handler } from '../types/hander' ;
2024-11-16 11:55:10 -06:00
import { type DiscordMessage , Message } from '../types/message' ;
2024-11-15 13:49:33 -06:00
import { Guilds } from './caches/guilds' ;
import { Messages } from './caches/messages' ;
import { Users } from './caches/users' ;
import { hydrate , hydrator , type Hydrateable , type Hydrated } from './hydrator' ;
2024-11-10 01:55:07 -06:00
export class Client {
readonly token : string ;
readonly application_id : string ;
readonly intents : Intent [ ] = [ Intent . DEFAULT ] ;
prefix : string ;
// Internal services
2024-11-12 00:38:08 -06:00
protected api : API ;
protected processor : Processor ;
protected gateway : Gateway ;
2024-11-10 01:55:07 -06:00
guilds : Guilds ;
users : Users ;
2024-11-12 00:38:08 -06:00
messages : Messages
2024-11-10 01:55:07 -06:00
constructor ( config : ClientConfig ) {
// Validate the configuration
Client . validateConfig ( config ) ;
// Save the configuration
this . token = config . token ;
this . application_id = config . application_id ;
this . intents = config . intents ? ? this . intents ;
this . prefix = config . prefix ? ? '!' ;
// Initialize internal services
2024-11-12 00:38:08 -06:00
this . api = new API ( this ) ;
this . processor = new Processor ( this , this . api ) ;
this . gateway = new Gateway ( this , this . processor , this . api ) ;
2024-11-10 01:55:07 -06:00
this . guilds = new Guilds ( this . api , this . processor ) ;
this . users = new Users ( this . api , this . processor ) ;
2024-11-12 00:38:08 -06:00
this . messages = new Messages ( this , this . api , this . processor ) ;
2024-11-10 01:55:07 -06:00
}
private static validateConfig ( config : ClientConfig ) {
if ( isNil ( config . token ) ) {
throw new Error ( 'Token is required' ) ;
}
if ( isNil ( config . application_id ) ) {
throw new Error ( 'Application ID is required' ) ;
}
if ( exists ( config . prefix ) && typeof config . prefix !== 'string' ) {
throw new Error ( 'Prefix must be a string' ) ;
}
if ( exists ( config . intents ) ) {
if ( ! Array . isArray ( config . intents ) ) {
throw new Error ( 'Intents must be an array' ) ;
}
for ( const intent of config . intents ) {
if ( ! Intent [ intent ] ) {
throw new Error ( ` Invalid intent: ${ intent } ` ) ;
}
}
}
}
2024-11-15 13:49:33 -06:00
/**
* Hydrates a given data object based on the provided expectations.
*
* @template T - The type of the data object to be hydrated. Must extend `Hydrateable`.
* @template K - An array of `Expectation` types that specify what properties should be hydrated.
*
* @param data - The data object to be hydrated. Can be of type `Context` or `Message`.
* @param expectations - An array of expectations that specify what properties should be hydrated.
*
* @returns A promise that resolves to the hydrated data object.
*
* @throws Will throw an error if an expectation cannot be resolved.
*/
hydrate = async < T extends Hydrateable | Hydrated < Hydrateable , Array < Expectation > > , K extends Array < Expectation > > ( data : T , expectations : K ) = > {
return hydrate < T , K > ( data , expectations , this , this . api ) ;
}
/**
* A wrapper function that attempts to hydrate a given data object and returns a type guard function.
*
* @template T - The type of the data object to be hydrated. Must extend `Hydrateable`.
* @template K - An array of `Expectation` types that specify what properties should be hydrated.
*
* @param data - The data object to be hydrated. Can be of type `Context` or `Message`.
* @param expectations - An array of expectations that specify what properties should be hydrated.
*
* @returns A promise that resolves to a type guard function. The type guard function returns `true` if the data object was successfully hydrated, otherwise `false`.
*/
2024-11-13 13:34:43 -06:00
hydrator = async < T extends Hydrateable , K extends Expectation [ ] > ( data : T , expectations : K ) = > {
return hydrator < T , K > ( data , expectations , this , this . api ) ;
}
2024-11-10 01:55:07 -06:00
sendMessage = async ( channel_id : string , message : Message | string ) = > {
if ( typeof message === 'string' ) {
message = new Message ( ) . setContent ( message ) ;
}
2024-11-16 11:55:10 -06:00
return Message [ fromDiscord ] ( await this . api . post < DiscordMessage > ( ` /channels/ ${ channel_id } /messages ` , message . toJSON ( ) ) ) ;
2024-11-10 01:55:07 -06:00
}
chatCommands = {
2024-11-12 00:38:08 -06:00
register : ( name : string | string [ ] , handler : Handler ) = > this . processor . chat_commands . register ( name , handler ) ,
unregister : ( name : string | string [ ] ) = > this . processor . chat_commands . unregister ( name ) ,
has : ( name : string ) = > this . processor . chat_commands . has ( name )
}
slashCommands = {
register : ( name : string , handler : Handler ) = > this . processor . slash_commands . register ( name , handler ) ,
unregister : ( name : string ) = > this . processor . slash_commands . unregister ( name ) ,
has : ( name : string ) = > this . processor . slash_commands . has ( name )
2024-11-10 01:55:07 -06:00
}
connect = ( ) = > this . gateway . connect ( ) ;
}