diff options
author | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2021-12-26 17:16:32 -0500 |
---|---|---|
committer | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2021-12-26 17:16:32 -0500 |
commit | fc390ffc300334c396d9d06b0feaf8fbc6ed2814 (patch) | |
tree | a6282a74cf99033291ac7bc9de123ae273d528d2 /src/lib/extensions/discord-akairo | |
parent | 062435590980b87f5b054418ed88604e26358ae9 (diff) | |
download | tanzanite-fc390ffc300334c396d9d06b0feaf8fbc6ed2814.tar.gz tanzanite-fc390ffc300334c396d9d06b0feaf8fbc6ed2814.tar.bz2 tanzanite-fc390ffc300334c396d9d06b0feaf8fbc6ed2814.zip |
documentation, bug fixes etc
Diffstat (limited to 'src/lib/extensions/discord-akairo')
-rw-r--r-- | src/lib/extensions/discord-akairo/BushClient.ts | 195 | ||||
-rw-r--r-- | src/lib/extensions/discord-akairo/BushClientUtil.ts | 102 | ||||
-rw-r--r-- | src/lib/extensions/discord-akairo/BushCommand.ts | 188 | ||||
-rw-r--r-- | src/lib/extensions/discord-akairo/BushInhibitor.ts | 7 | ||||
-rw-r--r-- | src/lib/extensions/discord-akairo/BushSlashMessage.ts | 2 |
5 files changed, 340 insertions, 154 deletions
diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts index a9e172a..d7c8b60 100644 --- a/src/lib/extensions/discord-akairo/BushClient.ts +++ b/src/lib/extensions/discord-akairo/BushClient.ts @@ -1,26 +1,25 @@ import type { BushApplicationCommand, BushBaseGuildEmojiManager, - BushChannel, BushChannelManager, BushClientEvents, BushClientUser, BushGuildManager, BushReactionEmoji, + BushStageChannel, BushUserManager, Config } from '#lib'; -import { patch, type PatchedElements } from '@notenoughupdates/events-intercept'; +import { patch, PatchedElements } from '@notenoughupdates/events-intercept'; import * as Sentry from '@sentry/node'; import { AkairoClient, ContextMenuCommandHandler, version as akairoVersion } from 'discord-akairo'; import { + Awaitable, Intents, Options, Structures, version as discordJsVersion, - type Awaitable, type Collection, - type DMChannel, type InteractionReplyOptions, type Message, type MessageEditOptions, @@ -31,6 +30,7 @@ import { type Snowflake, type WebhookEditMessageOptions } from 'discord.js'; +import EventEmitter from 'events'; import path from 'path'; import readline from 'readline'; import type { Sequelize as SequelizeType } from 'sequelize'; @@ -100,9 +100,28 @@ export type BushEmojiIdentifierResolvable = string | BushEmojiResolvable; export type BushThreadChannelResolvable = BushThreadChannel | Snowflake; export type BushApplicationCommandResolvable = BushApplicationCommand | Snowflake; export type BushGuildTextChannelResolvable = BushTextChannel | BushNewsChannel | Snowflake; -export type BushChannelResolvable = BushChannel | Snowflake; -export type BushTextBasedChannels = PartialDMChannel | BushDMChannel | BushTextChannel | BushNewsChannel | BushThreadChannel; -export type BushGuildTextBasedChannel = Exclude<BushTextBasedChannels, PartialDMChannel | BushDMChannel | DMChannel>; +export type BushChannelResolvable = BushAnyChannel | Snowflake; +export type BushGuildChannelResolvable = Snowflake | BushGuildBasedChannel; +export type BushAnyChannel = + | BushCategoryChannel + | BushDMChannel + | PartialDMChannel + | BushNewsChannel + | BushStageChannel + // eslint-disable-next-line deprecation/deprecation + | BushStoreChannel + | BushTextChannel + | BushThreadChannel + | BushVoiceChannel; +export type BushTextBasedChannel = PartialDMChannel | BushThreadChannel | BushDMChannel | BushNewsChannel | BushTextChannel; +export type BushTextBasedChannelTypes = BushTextBasedChannel['type']; +export type BushVoiceBasedChannel = Extract<BushAnyChannel, { bitrate: number }>; +export type BushGuildBasedChannel = Extract<BushAnyChannel, { guild: BushGuild }>; +export type BushNonThreadGuildBasedChannel = Exclude<BushGuildBasedChannel, BushThreadChannel>; +export type BushGuildTextBasedChannel = Extract<BushGuildBasedChannel, BushTextBasedChannel>; +export type BushTextChannelResolvable = Snowflake | BushTextChannel; +export type BushGuildVoiceChannelResolvable = BushVoiceBasedChannel | Snowflake; + export interface BushFetchedThreads { threads: Collection<Snowflake, BushThreadChannel>; hasMore?: boolean; @@ -118,29 +137,86 @@ type If<T extends boolean, A, B = null> = T extends true ? A : T extends false ? const __dirname = path.dirname(fileURLToPath(import.meta.url)); +/** + * The main hub for interacting with the Discord API. + */ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Ready> { public declare channels: BushChannelManager; public declare readonly emojis: BushBaseGuildEmojiManager; public declare guilds: BushGuildManager; public declare user: If<Ready, BushClientUser>; public declare users: BushUserManager; + public declare util: BushClientUtil; + public declare ownerID: Snowflake[]; + /** + * Whether or not the client is ready. + */ public customReady = false; - public stats: { cpu: number | undefined; commandsUsed: bigint } = { cpu: undefined, commandsUsed: 0n }; + + /** + * Stats for the client. + */ + public stats: BushStats = { cpu: undefined, commandsUsed: 0n }; + + /** + * The configuration for the client. + */ public config: Config; + + /** + * The handler for the bot's listeners. + */ public listenerHandler: BushListenerHandler; + + /** + * The handler for the bot's command inhibitors. + */ public inhibitorHandler: BushInhibitorHandler; + + /** + * The handler for the bot's commands. + */ public commandHandler: BushCommandHandler; + + /** + * The handler for the bot's tasks. + */ public taskHandler: BushTaskHandler; + + /** + * The handler for the bot's context menu commands. + */ public contextMenuCommandHandler: ContextMenuCommandHandler; - public declare util: BushClientUtil; - public declare ownerID: Snowflake[]; + + /** + * The database connection for the bot. + */ public db: SequelizeType; + + /** + * A custom logging system for the bot. + */ public logger = BushLogger; + + /** + * Constants for the bot. + */ public constants = BushConstants; + + /** + * Cached global and guild database data. + */ public cache = new BushCache(); + + /** + * Sentry error reporting for the bot. + */ public sentry!: typeof Sentry; + /** + * @param config The configuration for the bot. + */ public constructor(config: Config) { super({ ownerID: config.owners, @@ -163,25 +239,18 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re this.token = config.token as If<Ready, string, string | null>; this.config = config; - // Create listener handler this.listenerHandler = new BushListenerHandler(this, { directory: path.join(__dirname, '..', '..', '..', 'listeners'), automateCategories: true }); - - // Create inhibitor handler this.inhibitorHandler = new BushInhibitorHandler(this, { directory: path.join(__dirname, '..', '..', '..', 'inhibitors'), automateCategories: true }); - - // Create task handler this.taskHandler = new BushTaskHandler(this, { directory: path.join(__dirname, '..', '..', '..', 'tasks'), automateCategories: true }); - - // Create command handler this.commandHandler = new BushCommandHandler(this, { directory: path.join(__dirname, '..', '..', '..', 'commands'), prefix: async ({ guild }: Message) => { @@ -215,12 +284,10 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re useSlashPermissions: true, aliasReplacement: /-/g }); - this.contextMenuCommandHandler = new ContextMenuCommandHandler(this, { directory: path.join(__dirname, '..', '..', '..', 'context-menu-commands'), automateCategories: true }); - this.util = new BushClientUtil(this); this.db = new Sequelize({ database: this.config.isDevelopment ? 'bushbot-dev' : this.config.isBeta ? 'bushbot-beta' : 'bushbot', @@ -234,15 +301,24 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re }); } - get console(): typeof BushLogger { + /** + * A custom logging system for the bot. + */ + public get console(): typeof BushLogger { return this.logger; } - get consts(): typeof BushConstants { + /** + * Constants for the bot. + */ + public get consts(): typeof BushConstants { return this.constants; } - public static init(): void { + /** + * Extends discord.js structures before the client is instantiated. + */ + public static extendStructures(): void { Structures.extend('GuildEmoji', () => BushGuildEmoji); Structures.extend('DMChannel', () => BushDMChannel); Structures.extend('TextChannel', () => BushTextChannel); @@ -265,18 +341,28 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re Structures.extend('SelectMenuInteraction', () => BushSelectMenuInteraction); } - // Initialize everything - async #init() { - this.commandHandler.useListenerHandler(this.listenerHandler); + /** + * Initializes the bot. + */ + async init() { + if (!process.version.startsWith('v17.')) { + void (await this.console.error('version', `Please use node <<v17.x.x>>, not <<${process.version}>>.`, false)); + process.exit(2); + } + this.commandHandler.useInhibitorHandler(this.inhibitorHandler); + this.commandHandler.useListenerHandler(this.listenerHandler); + this.commandHandler.useTaskHandler(this.taskHandler); + this.commandHandler.useContextMenuCommandHandler(this.contextMenuCommandHandler); this.commandHandler.ignorePermissions = this.config.owners; this.commandHandler.ignoreCooldown = [...new Set([...this.config.owners, ...this.cache.global.superUsers])]; this.listenerHandler.setEmitters({ client: this, commandHandler: this.commandHandler, - listenerHandler: this.listenerHandler, inhibitorHandler: this.inhibitorHandler, + listenerHandler: this.listenerHandler, taskHandler: this.taskHandler, + contextMenuCommandHandler: this.contextMenuCommandHandler, process, stdin: rl, gateway: this.ws @@ -301,28 +387,31 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re void this.logger.success('startup', `Successfully connected to <<Sentry>>.`, false); // loads all the handlers - const loaders = { + const handlers = { commands: this.commandHandler, - contextMenuCommand: this.contextMenuCommandHandler, + contextMenuCommands: this.contextMenuCommandHandler, listeners: this.listenerHandler, inhibitors: this.inhibitorHandler, tasks: this.taskHandler }; - for (const loader in loaders) { - try { - await loaders[loader as keyof typeof loaders].loadAll(); - void this.logger.success('startup', `Successfully loaded <<${loader}>>.`, false); - } catch (e) { - void this.logger.error('startup', `Unable to load loader <<${loader}>> with error:\n${e?.stack || e}`, false); - } - } - await this.dbPreInit(); - await UpdateCacheTask.init(this); - void this.console.success('startup', `Successfully created <<cache>>.`, false); - this.stats.commandsUsed = await UpdateStatsTask.init(); + const handlerPromises = Object.entries(handlers).map(([handlerName, handler]) => + handler + .loadAll() + .then(() => { + void this.logger.success('startup', `Successfully loaded <<${handlerName}>>.`, false); + }) + .catch((e) => { + void this.logger.error('startup', `Unable to load loader <<${handlerName}>> with error:\n${e?.stack || e}`, false); + if (process.argv.includes('dry')) process.exit(1); + }) + ); + await Promise.allSettled(handlerPromises); } - public async dbPreInit() { + /** + * Connects to the database, initializes models, and creates tables if they do not exist. + */ + private async dbPreInit() { try { await this.db.authenticate(); Global.initModel(this.db); @@ -348,10 +437,6 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re * Starts the bot */ public async start() { - if (!process.version.startsWith('v17.')) { - void (await this.console.error('version', `Please use node <<v17.x.x>>, not <<${process.version}>>.`, false)); - process.exit(2); - } this.intercept('ready', async (arg, done) => { await this.guilds.fetch(); const promises = this.guilds.cache.map((guild) => { @@ -368,7 +453,10 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re global.util = this.util; try { - await this.#init(); + await this.dbPreInit(); + await UpdateCacheTask.init(this); + void this.console.success('startup', `Successfully created <<cache>>.`, false); + this.stats.commandsUsed = await UpdateStatsTask.init(); await this.login(this.token!); } catch (e) { await this.console.error('start', util.inspect(e, { colors: true, depth: 1 }), false); @@ -389,13 +477,14 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re public override isOwner(user: BushUserResolvable): boolean { return this.config.owners.includes(this.users.resolveId(user!)!); } + public override isSuperUser(user: BushUserResolvable): boolean { const userID = this.users.resolveId(user)!; return !!client.cache?.global?.superUsers?.includes(userID) || this.config.owners.includes(userID); } } -export interface BushClient extends PatchedElements { +export interface BushClient extends EventEmitter, PatchedElements { on<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this; on<S extends string | symbol>(event: Exclude<S, keyof BushClientEvents>, listener: (...args: any[]) => Awaitable<void>): this; @@ -411,3 +500,15 @@ export interface BushClient extends PatchedElements { removeAllListeners<K extends keyof BushClientEvents>(event?: K): this; removeAllListeners<S extends string | symbol>(event?: Exclude<S, keyof BushClientEvents>): this; } + +export interface BushStats { + /** + * The average cpu usage of the bot from the past 60 seconds. + */ + cpu: number | undefined; + + /** + * The total number of times any command has been used. + */ + commandsUsed: bigint; +} diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index ab1f3ed..5ae2ac0 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -2,6 +2,7 @@ import { Arg, BushConstants, Global, + GlobalCache, type BushClient, type BushInspectOptions, type BushMessage, @@ -438,6 +439,12 @@ export class BushClientUtil extends ClientUtil { return array.join(', '); } + public getGlobal(): GlobalCache; + public getGlobal<K extends keyof GlobalCache>(key: K): GlobalCache[K]; + public getGlobal(key?: keyof GlobalCache) { + return key ? client.cache.global[key] : client.cache.global; + } + /** * Add or remove an element from an array stored in the Globals database. * @param action Either `add` or `remove` an element. @@ -610,11 +617,11 @@ export class BushClientUtil extends ClientUtil { /** * Wait an amount in seconds. - * @param s The number of seconds to wait + * @param seconds The number of seconds to wait * @returns A promise that resolves after the specified amount of seconds */ - public async sleep(s: number) { - return new Promise((resolve) => setTimeout(resolve, s * 1000)); + public async sleep(seconds: number) { + return new Promise((resolve) => setTimeout(resolve, seconds * 1000)); } /** @@ -629,8 +636,13 @@ export class BushClientUtil extends ClientUtil { }); } + /** + * Fetches a user from discord. + * @param user The user to fetch + * @returns Undefined if the user is not found, otherwise the user. + */ public async resolveNonCachedUser(user: UserResolvable | undefined | null): Promise<BushUser | undefined> { - if (!user) return undefined; + if (user == null) return undefined; const id = user instanceof User || user instanceof GuildMember || user instanceof ThreadMember ? user.id @@ -643,6 +655,11 @@ export class BushClientUtil extends ClientUtil { else return await client.users.fetch(id).catch(() => undefined); } + /** + * Get the pronouns of a discord user from pronoundb.org + * @param user The user to retrieve the promises of. + * @returns The human readable pronouns of the user, or undefined if they do not have any. + */ public async getPronounsOf(user: User | Snowflake): Promise<Pronoun | undefined> { const _user = await this.resolveNonCachedUser(user); if (!_user) throw new Error(`Cannot find user ${user}`); @@ -657,6 +674,11 @@ export class BushClientUtil extends ClientUtil { return client.constants.pronounMapping[apiRes.pronouns!]!; } + /** + * List the methods of an object. + * @param obj The object to get the methods of. + * @returns A string with each method on a new line. + */ public getMethods(obj: Record<string, any>): string { // modified from https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class // answer by Bruno Grieder @@ -700,13 +722,17 @@ export class BushClientUtil extends ClientUtil { return props.join('\n'); } + /** + * Uploads an image to imgur. + * @param image The image to upload. + * @returns The url of the imgur. + */ public async uploadImageToImgur(image: string) { const clientId = this.client.config.credentials.imgurClientId; const resp = (await got .post('https://api.imgur.com/3/upload', { headers: { - // Authorization: `Bearer ${token}`, Authorization: `Client-ID ${clientId}`, Accept: 'application/json' }, @@ -721,18 +747,38 @@ export class BushClientUtil extends ClientUtil { return resp.data.link; } + /** + * Checks if a user has a certain guild permission (doesn't check channel permissions). + * @param message The message to check the user from. + * @param permissions The permissions to check for. + * @returns The missing permissions or null if none are missing. + */ public userGuildPermCheck(message: BushMessage | BushSlashMessage, permissions: PermissionResolvable) { const missing = message.member?.permissions.missing(permissions) ?? []; return missing.length ? missing : null; } + /** + * Check if the client has certain permissions in the guild (doesn't check channel permissions). + * @param message The message to check the client user from. + * @param permissions The permissions to check for. + * @returns The missing permissions or null if none are missing. + */ public clientGuildPermCheck(message: BushMessage | BushSlashMessage, permissions: PermissionResolvable) { const missing = message.guild?.me?.permissions.missing(permissions) ?? []; return missing.length ? missing : null; } + /** + * Check if the client has permission to send messages in the channel as well as check if they have other permissions + * in the guild (or the channel if `checkChannel` is `true`). + * @param message The message to check the client user from. + * @param permissions The permissions to check for. + * @param checkChannel Whether to check the channel permissions instead of the guild permissions. + * @returns The missing permissions or null if none are missing. + */ public clientSendAndPermCheck( message: BushMessage | BushSlashMessage, permissions: PermissionResolvable = [], @@ -752,6 +798,11 @@ export class BushClientUtil extends ClientUtil { return missing.length ? missing : null; } + /** + * Gets the prefix based off of the message. + * @param message The message to get the prefix from. + * @returns The prefix. + */ public prefix(message: BushMessage | BushSlashMessage): string { return message.util.isSlash ? '/' @@ -760,14 +811,55 @@ export class BushClientUtil extends ClientUtil { : message.util.parsed?.prefix ?? client.config.prefix; } + /** + * Recursively apply provided options operations on object + * and all of the object properties that are either object or function. + * + * By default freezes object. + * + * @param obj - The object to which will be applied `freeze`, `seal` or `preventExtensions` + * @param options default `{ action: 'freeze' }` + * @param options.action + * ``` + * | action | Add | Modify | Delete | Reconfigure | + * | ----------------- | --- | ------ | ------ | ----------- | + * | preventExtensions | - | + | + | + | + * | seal | - | + | - | - | + * | freeze | - | - | - | - | + * ``` + * + * @returns Initial object with applied options action + */ public get deepFreeze() { return deepLock; } + /** + * Recursively apply provided options operations on object + * and all of the object properties that are either object or function. + * + * By default freezes object. + * + * @param obj - The object to which will be applied `freeze`, `seal` or `preventExtensions` + * @param options default `{ action: 'freeze' }` + * @param options.action + * ``` + * | action | Add | Modify | Delete | Reconfigure | + * | ----------------- | --- | ------ | ------ | ----------- | + * | preventExtensions | - | + | + | + | + * | seal | - | + | - | - | + * | freeze | - | - | - | - | + * ``` + * + * @returns Initial object with applied options action + */ public static get deepFreeze() { return deepLock; } + /** + * A wrapper for the Argument class that adds custom typings. + */ public get arg() { return Arg; } diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts index ae3dcb2..6b54e20 100644 --- a/src/lib/extensions/discord-akairo/BushCommand.ts +++ b/src/lib/extensions/discord-akairo/BushCommand.ts @@ -174,7 +174,7 @@ export interface CustomBushArgumentOptions extends BaseBushArgumentOptions { export type BushMissingPermissionSupplier = (message: BushMessage | BushSlashMessage) => Promise<any> | any; -export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermissions' | 'clientPermissions' | 'args'> { +interface ExtendedCommandOptions { /** * Whether the command is hidden from the help command. */ @@ -191,11 +191,6 @@ export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermis restrictedGuilds?: Snowflake[]; /** - * The description of the command. - */ - description: string; - - /** * Show how to use the command. */ usage: string[]; @@ -206,13 +201,6 @@ export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermis examples: string[]; /** - * The arguments for the command. - */ - args?: BushArgumentOptions[] & CustomBushArgumentOptions[]; - - category: string; - - /** * A fake command, completely hidden from the help command. */ pseudo?: boolean; @@ -223,6 +211,27 @@ export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermis bypassChannelBlacklist?: boolean; /** + * Use instead of {@link BaseBushCommandOptions.args} when using argument generators or custom slashOptions + */ + helpArgs?: BushArgumentOptions[]; +} + +export interface BaseBushCommandOptions + extends Omit<CommandOptions, 'userPermissions' | 'clientPermissions' | 'args'>, + ExtendedCommandOptions { + /** + * The description of the command. + */ + description: string; + + /** + * The arguments for the command. + */ + args?: BushArgumentOptions[] & CustomBushArgumentOptions[]; + + category: string; + + /** * Permissions required by the client to run this command. */ clientPermissions: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier; @@ -241,11 +250,6 @@ export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermis * Restrict this argument to super users. */ superUserOnly?: boolean; - - /** - * Use instead of {@link BaseBushCommandOptions.args} when using argument generators or custom slashOptions - */ - helpArgs?: BushArgumentOptions[]; } export type BushCommandOptions = Omit<BaseBushCommandOptions, 'helpArgs'> | Omit<BaseBushCommandOptions, 'args'>; @@ -329,90 +333,67 @@ export class BushCommand extends Command { }); } - const newOptions: CommandOptions = {}; - if ('aliases' in options_) newOptions.aliases = options_.aliases; - if ('args' in options_ && typeof options_.args === 'object') { - const newTextArgs: ArgumentOptions[] = []; - const newSlashArgs: SlashOption[] = []; - for (const arg of options_.args) { - if (arg.only !== 'slash' && !options_.slashOnly) { - const newArg: ArgumentOptions = {}; - if ('default' in arg) newArg.default = arg.default; - if ('description' in arg) newArg.description = arg.description; - if ('flag' in arg) newArg.flag = arg.flag; - if ('id' in arg) newArg.id = arg.id; - if ('index' in arg) newArg.index = arg.index; - if ('limit' in arg) newArg.limit = arg.limit; - if ('match' in arg) newArg.match = arg.match; - if ('modifyOtherwise' in arg) newArg.modifyOtherwise = arg.modifyOtherwise; - if ('multipleFlags' in arg) newArg.multipleFlags = arg.multipleFlags; - if ('otherwise' in arg) newArg.otherwise = arg.otherwise; - if ('prompt' in arg || 'retry' in arg || 'optional' in arg) { - newArg.prompt = {}; - if ('prompt' in arg) newArg.prompt.start = arg.prompt; - if ('retry' in arg) newArg.prompt.retry = arg.retry; - if ('optional' in arg) newArg.prompt.optional = arg.optional; + const newOptions: Partial<CommandOptions & ExtendedCommandOptions> = {}; + for (const _key in options_) { + const key = _key as keyof typeof options_; // you got to love typescript + if (key === 'args' && 'args' in options_ && typeof options_.args === 'object') { + const newTextArgs: ArgumentOptions[] = []; + const newSlashArgs: SlashOption[] = []; + for (const arg of options_.args) { + if (arg.only !== 'slash' && !options_.slashOnly) { + const newArg: ArgumentOptions = {}; + if ('default' in arg) newArg.default = arg.default; + if ('description' in arg) newArg.description = arg.description; + if ('flag' in arg) newArg.flag = arg.flag; + if ('id' in arg) newArg.id = arg.id; + if ('index' in arg) newArg.index = arg.index; + if ('limit' in arg) newArg.limit = arg.limit; + if ('match' in arg) newArg.match = arg.match; + if ('modifyOtherwise' in arg) newArg.modifyOtherwise = arg.modifyOtherwise; + if ('multipleFlags' in arg) newArg.multipleFlags = arg.multipleFlags; + if ('otherwise' in arg) newArg.otherwise = arg.otherwise; + if ('prompt' in arg || 'retry' in arg || 'optional' in arg) { + newArg.prompt = {}; + if ('prompt' in arg) newArg.prompt.start = arg.prompt; + if ('retry' in arg) newArg.prompt.retry = arg.retry; + if ('optional' in arg) newArg.prompt.optional = arg.optional; + } + if ('type' in arg) newArg.type = arg.type as ArgumentType | ArgumentTypeCaster; + if ('unordered' in arg) newArg.unordered = arg.unordered; + newTextArgs.push(newArg); + } + if ( + arg.only !== 'text' && + !('slashOptions' in options_) && + (options_.slash || options_.slashOnly) && + arg.slashType !== false + ) { + const newArg: { + [key in SlashOptionKeys]?: any; + } = { + name: arg.id, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + description: arg.prompt || arg.description || 'No description provided.', + type: arg.slashType + }; + if ('slashResolve' in arg) newArg.resolve = arg.slashResolve; + if ('autocomplete' in arg) newArg.autocomplete = arg.autocomplete; + if ('channelTypes' in arg) newArg.channelTypes = arg.channelTypes; + if ('choices' in arg) newArg.choices = arg.choices; + if ('minValue' in arg) newArg.minValue = arg.minValue; + if ('maxValue' in arg) newArg.maxValue = arg.maxValue; + newArg.required = 'optional' in arg ? !arg.optional : true; + newSlashArgs.push(newArg as SlashOption); } - if ('type' in arg) newArg.type = arg.type as ArgumentType | ArgumentTypeCaster; - if ('unordered' in arg) newArg.unordered = arg.unordered; - newTextArgs.push(newArg); - } - if ( - arg.only !== 'text' && - !('slashOptions' in options_) && - (options_.slash || options_.slashOnly) && - arg.slashType !== false - ) { - const newArg: { - [key in SlashOptionKeys]?: any; - } = { - name: arg.id, - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - description: arg.prompt || arg.description || 'No description provided.', - type: arg.slashType - }; - if ('slashResolve' in arg) newArg.resolve = arg.slashResolve; - if ('autocomplete' in arg) newArg.autocomplete = arg.autocomplete; - if ('channelTypes' in arg) newArg.channelTypes = arg.channelTypes; - if ('choices' in arg) newArg.choices = arg.choices; - if ('minValue' in arg) newArg.minValue = arg.minValue; - if ('maxValue' in arg) newArg.maxValue = arg.maxValue; - newArg.required = 'optional' in arg ? !arg.optional : true; - newSlashArgs.push(newArg as SlashOption); } + if (newTextArgs.length > 0) newOptions.args = newTextArgs; + if (newSlashArgs.length > 0) newOptions.slashOptions = options_.slashOptions ?? newSlashArgs; + } else if (key === 'clientPermissions' || key === 'userPermissions') { + newOptions[key] = options_[key] as PermissionResolvable | PermissionResolvable[] | MissingPermissionSupplier; + } else { + newOptions[key] = options_[key]; } - if (newTextArgs.length > 0) newOptions.args = newTextArgs; - if (newSlashArgs.length > 0) newOptions.slashOptions = options_.slashOptions ?? newSlashArgs; } - type perm = PermissionResolvable | PermissionResolvable[] | MissingPermissionSupplier; - - if ('argumentDefaults' in options_) newOptions.argumentDefaults = options_.argumentDefaults; - if ('before' in options_) newOptions.before = options_.before; - if ('channel' in options_) newOptions.channel = options_.channel; - if ('clientPermissions' in options_) newOptions.clientPermissions = options_.clientPermissions as perm; - if ('condition' in options_) newOptions.condition = options_.condition; - if ('cooldown' in options_) newOptions.cooldown = options_.cooldown; - if ('description' in options_) newOptions.description = options_.description; - if ('editable' in options_) newOptions.editable = options_.editable; - if ('flags' in options_) newOptions.flags = options_.flags; - if ('ignoreCooldown' in options_) newOptions.ignoreCooldown = options_.ignoreCooldown; - if ('ignorePermissions' in options_) newOptions.ignorePermissions = options_.ignorePermissions; - if ('lock' in options_) newOptions.lock = options_.lock; - if ('onlyNsfw' in options_) newOptions.onlyNsfw = options_.onlyNsfw; - if ('optionFlags' in options_) newOptions.optionFlags = options_.optionFlags; - if ('ownerOnly' in options_) newOptions.ownerOnly = options_.ownerOnly; - if ('prefix' in options_) newOptions.prefix = options_.prefix; - if ('quoted' in options_) newOptions.quoted = options_.quoted; - if ('ratelimit' in options_) newOptions.ratelimit = options_.ratelimit; - if ('regex' in options_) newOptions.regex = options_.regex; - if ('separator' in options_) newOptions.separator = options_.separator; - if ('slash' in options_) newOptions.slash = options_.slash; - if ('slashEphemeral' in options_) newOptions.slashEphemeral = options_.slashEphemeral; - if ('slashGuilds' in options_) newOptions.slashGuilds = options_.slashGuilds; - if ('slashOptions' in options_) newOptions.slashOptions = options_.slashOptions; - if ('superUserOnly' in options_) newOptions.superUserOnly = options_.superUserOnly; - if ('typing' in options_) newOptions.typing = options_.typing; - if ('userPermissions' in options_) newOptions.userPermissions = options_.userPermissions as perm; super(id, newOptions); @@ -447,9 +428,14 @@ export class BushCommand extends Command { } } -export interface BushCommand { - exec(message: BushMessage, args: any): any; - exec(message: BushMessage | BushSlashMessage, args: any): any; +export interface BushCommand extends Command { + /** + * Executes the command. + * @param message - Message that triggered the command. + * @param args - Evaluated arguments. + */ + exec<R, A>(message: BushMessage, args: A): R; + exec<R, A>(message: BushMessage | BushSlashMessage, args: A): R; } type SlashOptionKeys = diff --git a/src/lib/extensions/discord-akairo/BushInhibitor.ts b/src/lib/extensions/discord-akairo/BushInhibitor.ts index c3f9994..7f13594 100644 --- a/src/lib/extensions/discord-akairo/BushInhibitor.ts +++ b/src/lib/extensions/discord-akairo/BushInhibitor.ts @@ -6,6 +6,13 @@ export class BushInhibitor extends Inhibitor { } export interface BushInhibitor { + /** + * Checks if message should be blocked. + * A return value of true will block the message. + * If returning a Promise, a resolved value of true will block the message. + * @param message - Message being handled. + * @param command - Command to check. + */ exec(message: BushMessage, command: BushCommand): any; exec(message: BushMessage | BushSlashMessage, command: BushCommand): any; } diff --git a/src/lib/extensions/discord-akairo/BushSlashMessage.ts b/src/lib/extensions/discord-akairo/BushSlashMessage.ts index c0a4a60..448963b 100644 --- a/src/lib/extensions/discord-akairo/BushSlashMessage.ts +++ b/src/lib/extensions/discord-akairo/BushSlashMessage.ts @@ -12,6 +12,6 @@ export class BushSlashMessage extends AkairoMessage { } } -export interface BushSlashMessage { +export interface BushSlashMessage extends AkairoMessage { get guild(): BushGuild | null; } |