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
4
mod.ts
4
mod.ts
|
@ -12,13 +12,13 @@ export {
|
|||
CommandsManager,
|
||||
CategoriesManager
|
||||
} from './src/models/command.ts'
|
||||
export type { CommandContext } from './src/models/command.ts'
|
||||
export type { CommandContext, CommandOptions } from './src/models/command.ts'
|
||||
export {
|
||||
Extension,
|
||||
ExtensionCommands,
|
||||
ExtensionsManager
|
||||
} from './src/models/extensions.ts'
|
||||
export { CommandClient } from './src/models/commandClient.ts'
|
||||
export { CommandClient, command } from './src/models/commandClient.ts'
|
||||
export type { CommandClientOptions } from './src/models/commandClient.ts'
|
||||
export { BaseManager } from './src/managers/base.ts'
|
||||
export { BaseChildManager } from './src/managers/baseChild.ts'
|
||||
|
|
|
@ -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…
Reference in a new issue