feat(decorators): add support for @event and @command decorators
This commit is contained in:
		
							parent
							
								
									f6c307844f
								
							
						
					
					
						commit
						895b13a9b1
					
				
					 6 changed files with 164 additions and 11 deletions
				
			
		|  | @ -70,6 +70,7 @@ export class Client extends EventEmitter { | |||
|   canary: boolean = false | ||||
|   /** Client's presence. Startup one if set before connecting */ | ||||
|   presence: ClientPresence = new ClientPresence() | ||||
|   _decoratedEvents?: { [name: string]: (...args: any[]) => any } | ||||
| 
 | ||||
|   private readonly _untypedOn = this.on | ||||
| 
 | ||||
|  | @ -101,6 +102,16 @@ export class Client extends EventEmitter { | |||
|       this.reactionCacheLifetime = options.reactionCacheLifetime | ||||
|     if (options.fetchUncachedReactions === true) | ||||
|       this.fetchUncachedReactions = true | ||||
| 
 | ||||
|     if ( | ||||
|       this._decoratedEvents !== undefined && | ||||
|       Object.keys(this._decoratedEvents).length !== 0 | ||||
|     ) { | ||||
|       Object.entries(this._decoratedEvents).forEach((entry) => { | ||||
|         this.on(entry[0], entry[1]) | ||||
|       }) | ||||
|       this._decoratedEvents = undefined | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | @ -127,6 +138,21 @@ export class Client extends EventEmitter { | |||
|     this.emit('debug', `[${tag}] ${msg}`) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * EXPERIMENTAL Decorators support for listening to events. | ||||
|    * @param event Event name to listen for | ||||
|    */ | ||||
|   event(event: string): CallableFunction { | ||||
|     // eslint-disable-next-line @typescript-eslint/no-this-alias
 | ||||
|     const parent = this | ||||
|     return function ( | ||||
|       target: { [name: string]: CallableFunction }, | ||||
|       prop: string | ||||
|     ) { | ||||
|       parent.addListener(event, target[prop] as (...args: any[]) => any) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // TODO(DjDeveloperr): Implement this
 | ||||
|   // fetchApplication(): Promise<Application>
 | ||||
| 
 | ||||
|  | @ -153,3 +179,13 @@ export class Client extends EventEmitter { | |||
|     this.gateway = new Gateway(this, token, intents) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function event(name?: string) { | ||||
|   return function (client: Client, prop: string) { | ||||
|     const listener = ((client as unknown) as { | ||||
|       [name: string]: (...args: any[]) => any | ||||
|     })[prop] | ||||
|     if (client._decoratedEvents === undefined) client._decoratedEvents = {} | ||||
|     client._decoratedEvents[name === undefined ? prop : name] = listener | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import { User } from '../structures/user.ts' | |||
| import { Collection } from '../utils/collection.ts' | ||||
| import { CommandClient } from './commandClient.ts' | ||||
| import { Extension } from './extensions.ts' | ||||
| import { parse } from 'https://deno.land/x/mutil@0.1.2/mod.ts' | ||||
| 
 | ||||
| export interface CommandContext { | ||||
|   /** The Client object */ | ||||
|  | @ -29,9 +30,9 @@ export interface CommandContext { | |||
|   guild?: Guild | ||||
| } | ||||
| 
 | ||||
| export class Command { | ||||
| export interface CommandOptions { | ||||
|   /** Name of the Command */ | ||||
|   name: string = '' | ||||
|   name?: string | ||||
|   /** Description of the Command */ | ||||
|   description?: string | ||||
|   /** Category of the Command */ | ||||
|  | @ -66,6 +67,27 @@ export class Command { | |||
|   dmOnly?: boolean | ||||
|   /** Whether the Command can only be used by Bot Owners */ | ||||
|   ownerOnly?: boolean | ||||
| } | ||||
| 
 | ||||
| export class Command implements CommandOptions { | ||||
|   name: string = '' | ||||
|   description?: string | ||||
|   category?: string | ||||
|   aliases?: string | string[] | ||||
|   extension?: Extension | ||||
|   usage?: string | string[] | ||||
|   examples?: string | string[] | ||||
|   args?: number | boolean | string[] | ||||
|   permissions?: string | string[] | ||||
|   userPermissions?: string | string[] | ||||
|   botPermissions?: string | string[] | ||||
|   roles?: string | string[] | ||||
|   whitelistedGuilds?: string | string[] | ||||
|   whitelistedChannels?: string | string[] | ||||
|   whitelistedUsers?: string | string[] | ||||
|   guildOnly?: boolean | ||||
|   dmOnly?: boolean | ||||
|   ownerOnly?: boolean | ||||
| 
 | ||||
|   /** Method executed before executing actual command. Returns bool value - whether to continue or not (optional) */ | ||||
|   beforeExecute(ctx: CommandContext): boolean | Promise<boolean> { | ||||
|  | @ -418,7 +440,8 @@ export const parseCommand = ( | |||
| ): ParsedCommand => { | ||||
|   let content = msg.content.slice(prefix.length) | ||||
|   if (client.spacesAfterPrefix === true) content = content.trim() | ||||
|   const args = content.split(client.betterArgs === true ? /[\S\s]*/ : / +/) | ||||
|   const args = parse(content)._.map((e) => e.toString()) | ||||
| 
 | ||||
|   const name = args.shift() as string | ||||
|   const argString = content.slice(name.length).trim() | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,11 +3,13 @@ import { awaitSync } from '../utils/mixedPromise.ts' | |||
| import { Client, ClientOptions } from './client.ts' | ||||
| import { | ||||
|   CategoriesManager, | ||||
|   Command, | ||||
|   CommandContext, | ||||
|   CommandOptions, | ||||
|   CommandsManager, | ||||
|   parseCommand | ||||
| } from './command.ts' | ||||
| import { ExtensionsManager } from './extensions.ts' | ||||
| import { Extension, ExtensionsManager } from './extensions.ts' | ||||
| 
 | ||||
| type PrefixReturnType = string | string[] | Promise<string | string[]> | ||||
| 
 | ||||
|  | @ -29,8 +31,6 @@ export interface CommandClientOptions extends ClientOptions { | |||
|   isChannelBlacklisted?: (guildID: string) => boolean | Promise<boolean> | ||||
|   /** Allow spaces after prefix? Recommended with Mention Prefix ON. */ | ||||
|   spacesAfterPrefix?: boolean | ||||
|   /** Better Arguments regex to split at every whitespace. */ | ||||
|   betterArgs?: boolean | ||||
|   /** List of Bot's Owner IDs whom can access `ownerOnly` commands. */ | ||||
|   owners?: string[] | ||||
|   /** Whether to allow Bots to use Commands or not, not allowed by default. */ | ||||
|  | @ -50,7 +50,6 @@ export class CommandClient extends Client implements CommandClientOptions { | |||
|   isUserBlacklisted: (guildID: string) => boolean | Promise<boolean> | ||||
|   isChannelBlacklisted: (guildID: string) => boolean | Promise<boolean> | ||||
|   spacesAfterPrefix: boolean | ||||
|   betterArgs: boolean | ||||
|   owners: string[] | ||||
|   allowBots: boolean | ||||
|   allowDMs: boolean | ||||
|  | @ -58,6 +57,7 @@ export class CommandClient extends Client implements CommandClientOptions { | |||
|   extensions: ExtensionsManager = new ExtensionsManager(this) | ||||
|   commands: CommandsManager = new CommandsManager(this) | ||||
|   categories: CategoriesManager = new CategoriesManager(this) | ||||
|   _decoratedCommands?: { [name: string]: Command } | ||||
| 
 | ||||
|   constructor(options: CommandClientOptions) { | ||||
|     super(options) | ||||
|  | @ -88,14 +88,18 @@ export class CommandClient extends Client implements CommandClientOptions { | |||
|       options.spacesAfterPrefix === undefined | ||||
|         ? false | ||||
|         : options.spacesAfterPrefix | ||||
|     this.betterArgs = | ||||
|       options.betterArgs === undefined ? false : options.betterArgs | ||||
|     this.owners = options.owners === undefined ? [] : options.owners | ||||
|     this.allowBots = options.allowBots === undefined ? false : options.allowBots | ||||
|     this.allowDMs = options.allowDMs === undefined ? true : options.allowDMs | ||||
|     this.caseSensitive = | ||||
|       options.caseSensitive === undefined ? false : options.caseSensitive | ||||
| 
 | ||||
|     if (this._decoratedCommands !== undefined) { | ||||
|       Object.values(this._decoratedCommands).forEach((entry) => { | ||||
|         this.commands.add(entry) | ||||
|       }) | ||||
|     } | ||||
| 
 | ||||
|     this.on( | ||||
|       'messageCreate', | ||||
|       async (msg: Message) => await this.processMessage(msg) | ||||
|  | @ -345,3 +349,25 @@ export class CommandClient extends Client implements CommandClientOptions { | |||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function command(options?: CommandOptions) { | ||||
|   return function (target: CommandClient | Extension, name: string) { | ||||
|     const command = new Command() | ||||
| 
 | ||||
|     command.name = name | ||||
|     command.execute = ((target as unknown) as { | ||||
|       [name: string]: (ctx: CommandContext) => any | ||||
|     })[name] | ||||
| 
 | ||||
|     if (options !== undefined) Object.assign(command, options) | ||||
| 
 | ||||
|     if (target instanceof CommandClient) { | ||||
|       if (target._decoratedCommands === undefined) | ||||
|         target._decoratedCommands = {} | ||||
|       target._decoratedCommands[command.name] = command | ||||
|     } else { | ||||
|       if (target._decorated === undefined) target._decorated = {} | ||||
|       target._decorated[command.name] = command | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -69,9 +69,15 @@ export class Extension { | |||
|   commands: ExtensionCommands = new ExtensionCommands(this) | ||||
|   /** Events registered by this Extension */ | ||||
|   events: { [name: string]: (...args: any[]) => {} } = {} | ||||
|   _decorated?: { [name: string]: Command } | ||||
| 
 | ||||
|   constructor(client: CommandClient) { | ||||
|     this.client = client | ||||
|     if (this._decorated !== undefined) { | ||||
|       Object.entries(this._decorated).forEach((entry) => { | ||||
|         this.commands.add(entry[1]) | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** Listen for an Event through Extension. */ | ||||
|  |  | |||
							
								
								
									
										62
									
								
								src/test/class.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/test/class.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,62 @@ | |||
| import { | ||||
|   CommandClient, | ||||
|   event, | ||||
|   Intents, | ||||
|   command, | ||||
|   CommandContext, | ||||
|   Extension | ||||
| } from '../../mod.ts' | ||||
| import { TOKEN } from './config.ts' | ||||
| 
 | ||||
| class MyClient extends CommandClient { | ||||
|   constructor() { | ||||
|     super({ | ||||
|       prefix: '!', | ||||
|       caseSensitive: false | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   @event() | ||||
|   ready(): void { | ||||
|     console.log(`Logged in as ${this.user?.tag}!`) | ||||
|   } | ||||
| 
 | ||||
|   @command({ | ||||
|     aliases: 'pong' | ||||
|   }) | ||||
|   Ping(ctx: CommandContext): void { | ||||
|     ctx.message.reply('Pong!') | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class VCExtension extends Extension { | ||||
|   @command() | ||||
|   async join(ctx: CommandContext): Promise<void> { | ||||
|     const userVS = await ctx.guild?.voiceStates.get(ctx.author.id) | ||||
|     if (userVS === undefined) { | ||||
|       ctx.message.reply("You're not in VC.") | ||||
|       return | ||||
|     } | ||||
|     await userVS.channel?.join() | ||||
|     ctx.message.reply(`Joined VC channel - ${userVS.channel?.name}!`) | ||||
|   } | ||||
| 
 | ||||
|   @command() | ||||
|   async leave(ctx: CommandContext): Promise<void> { | ||||
|     const userVS = await ctx.guild?.voiceStates.get( | ||||
|       (ctx.client.user?.id as unknown) as string | ||||
|     ) | ||||
|     if (userVS === undefined) { | ||||
|       ctx.message.reply("I'm not in VC.") | ||||
|       return | ||||
|     } | ||||
|     userVS.channel?.leave() | ||||
|     ctx.message.reply(`Left VC channel - ${userVS.channel?.name}!`) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const client = new MyClient() | ||||
| 
 | ||||
| client.extensions.load(VCExtension) | ||||
| 
 | ||||
| client.connect(TOKEN, Intents.All) | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue