From 06d9a900573dda97fdddaf3b4d1153c9551a0ea4 Mon Sep 17 00:00:00 2001 From: GRZ4NA Date: Sat, 7 Aug 2021 16:50:53 +0200 Subject: [PATCH] working slash commands --- src/Bot.ts | 40 +++++++++++++++++- src/Command.ts | 97 ++++++++++++++++++++++++++++++------------- src/CommandManager.ts | 21 +++++++++- src/SystemMessage.ts | 14 +++++-- src/types.ts | 15 ++----- 5 files changed, 138 insertions(+), 49 deletions(-) diff --git a/src/Bot.ts b/src/Bot.ts index a6c6a44..da613d4 100644 --- a/src/Bot.ts +++ b/src/Bot.ts @@ -1,4 +1,4 @@ -import { Client, Intents, Message, TextChannel } from "discord.js"; +import { Client, GuildMember, Intents, Message, TextChannel } from "discord.js"; import { Command } from "./Command.js"; import { CommandManager } from "./CommandManager.js"; import { @@ -119,7 +119,7 @@ export class Bot extends EventEmitter { this.emit("READY"); }); this.client.on("message", async (m) => { - const cmdMsg = this.commands.fetch(m); + const cmdMsg = this.commands.fetchFromMessage(m); if (cmdMsg) { this.emit("COMMAND", m, cmdMsg); try { @@ -160,6 +160,42 @@ export class Bot extends EventEmitter { this.emit("MESSAGE", m); } }); + this.client.on("interactionCreate", async (i) => { + if (!i.isCommand()) return; + const cmd = this.commands.fetchFromInteraction(i); + if (cmd) { + this.emit("COMMAND", i, cmd); + try { + await cmd.command.start(i, cmd.parameters); + } catch (e) { + if (e instanceof PermissionsError) { + this.messages.system.send( + "PERMISSION", + { + user: + (i.member as GuildMember) || undefined, + command: cmd.command, + }, + i + ); + } else { + this.messages.system.send( + "ERROR", + { + command: cmd.command, + user: + (i.member as GuildMember) || undefined, + error: e, + }, + i + ); + console.error(e); + } + this.emit("ERROR", e); + return; + } + } + }); return true; } catch (e) { console.log("❌"); diff --git a/src/Command.ts b/src/Command.ts index 8bbdb58..07fc22f 100644 --- a/src/Command.ts +++ b/src/Command.ts @@ -1,5 +1,12 @@ //IMPORTS -import { Permissions, Message, MessageEmbed, Guild } from "discord.js"; +import { + Permissions, + Message, + MessageEmbed, + Guild, + CommandInteraction, + GuildMember, +} from "discord.js"; import { CommandBuilder, PermissionCheckTypes, @@ -21,7 +28,7 @@ export class Command { guilds?: Guild[]; visible: boolean; private function: ( - message?: Message, + interaction?: Message | CommandInteraction, cmdParams?: ParameterResolvable[] ) => void | string | MessageEmbed | Promise; @@ -72,36 +79,66 @@ export class Command { * @param {string[]} [cmdParams] - list of processed parameters passed in a Discord message * @returns *Promise* */ - async start(message?: Message, cmdParams?: string[]): Promise { - const memberPermissions: Readonly = - message?.member?.permissions || new Permissions(); - if ( - !this.permissions || - (this.permissionCheck == "ALL" - ? memberPermissions.has(this.permissions, true) - : memberPermissions.any(this.permissions, true)) - ) { - let inputArguments: ParameterResolvable[] = cmdParams || []; - if (this.parameters) { - this.parameters.map((a, i) => { - if (!inputArguments[i] && !a.optional) { - throw new MissingParameterError(a); - } else if (inputArguments[i]) { - inputArguments[i] = Parameter.process( - inputArguments[i] as string, - a.type - ); + async start( + interaction?: Message | CommandInteraction, + cmdParams?: ParameterResolvable[] + ): Promise { + if (interaction instanceof Message) { + const memberPermissions: Readonly = + interaction?.member?.permissions || new Permissions(); + if ( + !this.permissions || + (this.permissionCheck == "ALL" + ? memberPermissions.has(this.permissions, true) + : memberPermissions.any(this.permissions, true)) + ) { + let inputArguments: ParameterResolvable[] = cmdParams || []; + if (this.parameters) { + this.parameters.map((a, i) => { + if (!inputArguments[i] && !a.optional) { + throw new MissingParameterError(a); + } else if (inputArguments[i]) { + inputArguments[i] = Parameter.process( + inputArguments[i] as string, + a.type + ); + } + }); + } + const fnResult = await this.function(interaction, cmdParams); + if (typeof fnResult == "string") { + await interaction?.reply(fnResult); + } else if (fnResult instanceof MessageEmbed) { + await interaction?.channel.send({ embeds: [fnResult] }); + } + } else { + throw new PermissionsError(this, interaction?.member); + } + } else if (interaction instanceof CommandInteraction) { + const memberPermissions = interaction.member?.permissions; + if (memberPermissions instanceof Permissions) { + if ( + !this.permissions || + (this.permissionCheck === "ALL" + ? memberPermissions.has(this.permissions, true) + : memberPermissions.any(this.permissions, true)) + ) { + const fnResult = await this.function( + interaction, + cmdParams + ); + if (typeof fnResult == "string") { + await interaction.reply(fnResult); + } else if (fnResult instanceof MessageEmbed) { + await interaction.reply({ embeds: [fnResult] }); } - }); + } + } else { + throw new PermissionsError( + this, + interaction.member as GuildMember + ); } - const fnResult = await this.function(message, cmdParams); - if (typeof fnResult == "string") { - await message?.reply(fnResult); - } else if (fnResult instanceof MessageEmbed) { - await message?.channel.send({ embeds: [fnResult] }); - } - } else { - throw new PermissionsError(this, message?.member); } } diff --git a/src/CommandManager.ts b/src/CommandManager.ts index 06a6fa3..62f86cb 100644 --- a/src/CommandManager.ts +++ b/src/CommandManager.ts @@ -1,4 +1,4 @@ -import { Message } from "discord.js"; +import { CommandInteraction, Message } from "discord.js"; import { Command } from "./Command.js"; import { CommandMessageStructure, @@ -121,7 +121,7 @@ export class CommandManager { * @param {Message} message - *Message* object * @returns *CommandMessagesStructure* | *null* */ - fetch(message: Message): CommandMessageStructure | null { + fetchFromMessage(message: Message): CommandMessageStructure | null { if (!message.author.bot) { let prefix = false; if (message.content.startsWith(this.prefix)) { @@ -156,6 +156,23 @@ export class CommandManager { return null; } } + fetchFromInteraction( + interaction: CommandInteraction + ): CommandMessageStructure | null { + const cmd = this.get(interaction.commandName); + if (cmd) { + if (interaction.options?.data) { + const params = interaction.options.data.map((o) => o.value); + return { + command: cmd, + parameters: params, + }; + } + } else { + return null; + } + return null; + } private findPhraseOccurrence(phrase?: string): PhraseOccurrenceData | null { let returnValue: PhraseOccurrenceData | null = null; diff --git a/src/SystemMessage.ts b/src/SystemMessage.ts index 1be25a3..a922454 100644 --- a/src/SystemMessage.ts +++ b/src/SystemMessage.ts @@ -1,5 +1,6 @@ import { ColorResolvable, + CommandInteraction, DMChannel, GuildMember, Message, @@ -64,13 +65,13 @@ export class SystemMessageManager { * Generates and sends a system message * @param {MessageType} type - 'ERROR' | 'PERMISSION' | 'NOT_FOUND' * @param {SystemMessageData} [data] - additional data to include in the message - * @param {TextChannel | DMChannel | NewsChannel} [channel] - if specified, the generated message will be sent in this channel + * @param {TextChannel | DMChannel | NewsChannel} [interaction] - if specified, the generated message will be sent in this channel * @returns *Promise* */ async send( type: MessageType, data?: SystemMessageData, - channel?: TextChannel | DMChannel | NewsChannel + interaction?: TextChannel | DMChannel | NewsChannel | CommandInteraction ): Promise { if (this[type]) { if (this[type].enabled === false) { @@ -152,14 +153,19 @@ export class SystemMessageManager { break; } } - if (channel) { - const message = await channel.send({ embeds: [embed] }); + if (interaction && !(interaction instanceof CommandInteraction)) { + const message = await interaction.send({ embeds: [embed] }); if (this.deleteTimeout != Infinity && message.deletable) { setTimeout(async () => { await message.delete(); }, this.deleteTimeout || 0); } return message; + } else if ( + interaction && + interaction instanceof CommandInteraction + ) { + interaction.reply({ embeds: [embed] }); } else { return embed; } diff --git a/src/types.ts b/src/types.ts index 5633a7d..5d07087 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,7 @@ import { ClientOptions, ColorResolvable, + CommandInteraction, Guild, GuildMember, NewsChannel, @@ -34,7 +35,7 @@ export interface CommandBuilder { guilds?: Guild[]; visible?: boolean; function: ( - message?: Message, + interaction?: Message | CommandInteraction, cmdParams?: ParameterResolvable[] ) => void | string | MessageEmbed | Promise; } @@ -44,7 +45,7 @@ export interface PhraseOccurrenceData { } export interface CommandMessageStructure { command: Command; - parameters: string[]; + parameters: ParameterResolvable[]; } export interface HelpMessageParams { enabled: boolean; @@ -62,15 +63,7 @@ export type ParameterType = | "role" | "channel" | "mentionable"; -export type ParameterResolvable = - | string - | boolean - | number - | GuildMember - | Role - | TextChannel - | VoiceChannel - | NewsChannel; +export type ParameterResolvable = string | boolean | number | undefined; export interface Choice { name: string; value: string;