aboutsummaryrefslogtreecommitdiff
path: root/src/lib/extensions/discord-akairo
diff options
context:
space:
mode:
authorIRONM00N <64110067+IRONM00N@users.noreply.github.com>2021-12-26 17:16:32 -0500
committerIRONM00N <64110067+IRONM00N@users.noreply.github.com>2021-12-26 17:16:32 -0500
commitfc390ffc300334c396d9d06b0feaf8fbc6ed2814 (patch)
treea6282a74cf99033291ac7bc9de123ae273d528d2 /src/lib/extensions/discord-akairo
parent062435590980b87f5b054418ed88604e26358ae9 (diff)
downloadtanzanite-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.ts195
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts102
-rw-r--r--src/lib/extensions/discord-akairo/BushCommand.ts188
-rw-r--r--src/lib/extensions/discord-akairo/BushInhibitor.ts7
-rw-r--r--src/lib/extensions/discord-akairo/BushSlashMessage.ts2
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;
}