aboutsummaryrefslogtreecommitdiff
path: root/src/lib/extensions/BushCommandHandler.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/extensions/BushCommandHandler.ts')
-rw-r--r--src/lib/extensions/BushCommandHandler.ts274
1 files changed, 269 insertions, 5 deletions
diff --git a/src/lib/extensions/BushCommandHandler.ts b/src/lib/extensions/BushCommandHandler.ts
index 6ef44d7..670125d 100644
--- a/src/lib/extensions/BushCommandHandler.ts
+++ b/src/lib/extensions/BushCommandHandler.ts
@@ -1,15 +1,279 @@
-import { CommandHandler, CommandHandlerOptions } from 'discord-akairo';
-import { Collection } from 'discord.js';
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { AkairoMessage, Category, CommandHandler, CommandHandlerOptions, CommandUtil, Util } from 'discord-akairo';
+import { Collection, CommandInteraction, GuildMember, Interaction } from 'discord.js';
import { BushClient } from './BushClient';
import { BushCommand } from './BushCommand';
+import { BushMessage } from './BushMessage';
-export interface BushCommandHandlerOptions extends CommandHandlerOptions {}
+export const ArgumentMatches = {
+ PHRASE: 'phrase',
+ FLAG: 'flag',
+ OPTION: 'option',
+ REST: 'rest',
+ SEPARATE: 'separate',
+ TEXT: 'text',
+ CONTENT: 'content',
+ REST_CONTENT: 'restContent',
+ NONE: 'none'
+};
+export const ArgumentTypes = {
+ STRING: 'string',
+ LOWERCASE: 'lowercase',
+ UPPERCASE: 'uppercase',
+ CHAR_CODES: 'charCodes',
+ NUMBER: 'number',
+ INTEGER: 'integer',
+ BIGINT: 'bigint',
+ EMOJINT: 'emojint',
+ URL: 'url',
+ DATE: 'date',
+ COLOR: 'color',
+ USER: 'user',
+ USERS: 'users',
+ MEMBER: 'member',
+ MEMBERS: 'members',
+ RELEVANT: 'relevant',
+ RELEVANTS: 'relevants',
+ CHANNEL: 'channel',
+ CHANNELS: 'channels',
+ TEXT_CHANNEL: 'textChannel',
+ TEXT_CHANNELS: 'textChannels',
+ VOICE_CHANNEL: 'voiceChannel',
+ VOICE_CHANNELS: 'voiceChannels',
+ CATEGORY_CHANNEL: 'categoryChannel',
+ CATEGORY_CHANNELS: 'categoryChannels',
+ NEWS_CHANNEL: 'newsChannel',
+ NEWS_CHANNELS: 'newsChannels',
+ STORE_CHANNEL: 'storeChannel',
+ STORE_CHANNELS: 'storeChannels',
+ ROLE: 'role',
+ ROLES: 'roles',
+ EMOJI: 'emoji',
+ EMOJIS: 'emojis',
+ GUILD: 'guild',
+ GUILDS: 'guilds',
+ MESSAGE: 'message',
+ GUILD_MESSAGE: 'guildMessage',
+ RELEVANT_MESSAGE: 'relevantMessage',
+ INVITE: 'invite',
+ MEMBER_MENTION: 'memberMention',
+ CHANNEL_MENTION: 'channelMention',
+ ROLE_MENTION: 'roleMention',
+ EMOJI_MENTION: 'emojiMention',
+ COMMAND_ALIAS: 'commandAlias',
+ COMMAND: 'command',
+ INHIBITOR: 'inhibitor',
+ LISTENER: 'listener'
+};
+
+export const blockedReasons = {
+ DM: 'dm',
+ BOT: 'bot',
+ GUILD: 'guild',
+ OWNER: 'owner',
+ CLIENT: 'client',
+ DISABLED: 'disabled',
+ SUPERUSER: 'superuser',
+ ROLE_BLACKLIST: 'roleBlacklist',
+ USER_BLACKLIST: 'userBlacklist',
+ RESTRICTED_GUILD: 'restrictedGuild',
+ CHANNEL_BLACKLIST: 'channelBlacklist',
+ RESTRICTED_CHANNEL: 'restrictedChannel'
+};
+
+export const CommandHandlerEvents = {
+ MESSAGE_BLOCKED: 'messageBlocked',
+ MESSAGE_INVALID: 'messageInvalid',
+ COMMAND_BLOCKED: 'commandBlocked',
+ COMMAND_STARTED: 'commandStarted',
+ COMMAND_FINISHED: 'commandFinished',
+ COMMAND_CANCELLED: 'commandCancelled',
+ COMMAND_LOCKED: 'commandLocked',
+ COMMAND_INVALID: 'commandInvalid',
+ COMMAND_LOCKED_NSFW: 'commandLockedNsfw',
+ MISSING_PERMISSIONS: 'missingPermissions',
+ COOLDOWN: 'cooldown',
+ IN_PROMPT: 'inPrompt',
+ ERROR: 'error',
+ SLASH_COMMAND_BLOCKED: 'slashCommandBlocked'
+};
+
+// A large amount of this code is copied from Akairo so that I can add custom checks to it.
export class BushCommandHandler extends CommandHandler {
- public constructor(client: BushClient, options: BushCommandHandlerOptions) {
+ public declare client: BushClient;
+ public declare modules: Collection<string, BushCommand>;
+ public declare categories: Collection<string, Category<string, BushCommand>>;
+ public constructor(client: BushClient, options: CommandHandlerOptions) {
super(client, options);
this.client = client;
}
- declare modules: Collection<string, BushCommand>;
+ async handleSlash(interaction: Interaction): Promise<boolean> {
+ if (!interaction.isCommand()) return false;
+
+ if (await this.runAllTypeInhibitors(interaction)) {
+ return false;
+ }
+
+ if (!interaction.guildID) {
+ this.emit('slashGuildOnly', interaction);
+ return false;
+ }
+ const command = this.findCommand(interaction.commandName) as BushCommand;
+ const before = command.before(interaction);
+ if (Util.isPromise(before)) await before;
+
+ if (!command) {
+ this.emit('slashNotFound', interaction);
+ return false;
+ }
+
+ if (command.ownerOnly && !this.client.isOwner(interaction.user)) {
+ this.emit('slashBlocked', interaction, command, 'owner');
+ return false;
+ }
+ if (command.superUserOnly && !this.client.isSuperUser(interaction.user)) {
+ this.emit('slashBlocked', interaction, command, 'superuser');
+ return false;
+ }
+
+ if (interaction.channel.type !== 'dm') {
+ const userPermissions = interaction.channel.permissionsFor(interaction.member as GuildMember).toArray();
+
+ if (command.userPermissions) {
+ const userMissingPermissions =
+ typeof command.userPermissions === 'object'
+ ? command.userPermissions.filter((p) => !userPermissions.includes(p))
+ : '';
+ if (command.userPermissions && command.userPermissions.length > 0 && userMissingPermissions.length > 0) {
+ this.emit('slashMissingPermissions', interaction, command, 'user', userMissingPermissions);
+ return false;
+ }
+ }
+
+ const clientPermissions = interaction.channel.permissionsFor(interaction.guild.me).toArray();
+
+ if (command.clientPermissions) {
+ const clientMissingPermissions = command.clientPermissions.filter((p) => !clientPermissions.includes(p));
+ if (command.clientPermissions && command.clientPermissions.length > 0 && clientMissingPermissions.length > 0) {
+ this.emit('slashMissingPermissions', interaction, command, 'client', clientMissingPermissions);
+ return false;
+ }
+ }
+ }
+
+ //@ts-ignore: Typings are wrong
+ if (this.runCooldowns(interaction, command)) {
+ return true;
+ }
+ const message = new AkairoMessage(this.client, interaction, {
+ slash: true,
+ replied: this.autoDefer || command.slashEphemeral
+ });
+
+ if (this.commandUtil) {
+ if (this.commandUtils.has(message.id)) {
+ message.util = this.commandUtils.get(message.id);
+ } else {
+ message.util = new CommandUtil(this, message);
+ this.commandUtils.set(message.id, message.util);
+ }
+ }
+
+ let parsed = await this.parseCommand(message);
+ if (!parsed.command) {
+ const overParsed = await this.parseCommandOverwrittenPrefixes(message);
+ if (overParsed.command || (parsed.prefix == null && overParsed.prefix != null)) {
+ parsed = overParsed;
+ }
+ }
+
+ if (this.commandUtil) {
+ message.util.parsed = parsed;
+ }
+
+ try {
+ if (this.autoDefer || command.slashEphemeral) {
+ await interaction.defer(command.slashEphemeral);
+ }
+ const convertedOptions = {};
+ for (const option of interaction.options.values()) {
+ if (option.member) convertedOptions[option.name] = option.member;
+ else if (option.channel) convertedOptions[option.name] = option.channel;
+ else if (option.role) convertedOptions[option.name] = option.role;
+ else convertedOptions[option.name] = option.value;
+ }
+ this.emit('slashStarted', interaction, command);
+
+ if (command.execSlash || this.execSlash) await command.execSlash(message, convertedOptions);
+ else await command.exec(message, convertedOptions);
+
+ return true;
+ } catch (err) {
+ this.emit('slashError', err, message, command);
+ return false;
+ }
+ }
+ public async runPostTypeInhibitors(message: BushMessage, command: BushCommand): Promise<boolean> {
+ if (command.ownerOnly && !message.client.isOwner(message.author)) {
+ super.emit(CommandHandlerEvents.COMMAND_BLOCKED, message, command, blockedReasons.OWNER);
+ return true;
+ }
+ if (command.superUserOnly && !(this.client.isSuperUser(message.author) || this.client.isOwner(message.author))) {
+ super.emit(CommandHandlerEvents.COMMAND_BLOCKED, message, command, blockedReasons.SUPERUSER);
+ return true;
+ }
+ if (command.channel === 'guild' && !message.guild) {
+ this.emit(CommandHandlerEvents.COMMAND_BLOCKED, message, command, blockedReasons.GUILD);
+ return true;
+ }
+ if (command.channel === 'dm' && message.guild) {
+ this.emit(CommandHandlerEvents.COMMAND_BLOCKED, message, command, blockedReasons.DM);
+ return true;
+ }
+ if (command.restrictedChannels?.length && message.channel) {
+ if (!command.restrictedChannels.includes(message.channel.id)) {
+ this.emit(CommandHandlerEvents.COMMAND_BLOCKED, message, command, blockedReasons.RESTRICTED_CHANNEL);
+ return true;
+ }
+ }
+ if (command.restrictedGuilds?.length && message.guild) {
+ if (!command.restrictedGuilds.includes(message.guild.id)) {
+ this.emit(CommandHandlerEvents.COMMAND_BLOCKED, message, command, blockedReasons.RESTRICTED_GUILD);
+ return true;
+ }
+ }
+ if (await this.runPermissionChecks(message, command)) {
+ return true;
+ }
+ const reason = this.inhibitorHandler ? await this.inhibitorHandler.test('post', message, command) : null;
+ if (reason != null) {
+ this.emit(CommandHandlerEvents.COMMAND_BLOCKED, message, command, reason);
+ return true;
+ }
+ if (this.runCooldowns(message, command)) {
+ return true;
+ }
+ return false;
+ }
+ public async runCommand(message: BushMessage, command: BushCommand, args: unknown): Promise<void> {
+ if (command.typing) {
+ message.channel.startTyping();
+ }
+ try {
+ this.emit(CommandHandlerEvents.COMMAND_STARTED, message, command, args);
+ const ret = await command.exec(message, args);
+ this.emit(CommandHandlerEvents.COMMAND_FINISHED, message, command, args, ret);
+ } finally {
+ if (command.typing) {
+ message.channel.stopTyping();
+ }
+ }
+ }
+ public runAllTypeInhibitors(message: BushMessage): any;
+ public runAllTypeInhibitors(message: BushMessage | CommandInteraction): any;
+ public runAllTypeInhibitors(message): any {
+ super.runAllTypeInhibitors(message);
+ }
}