From b10aec93293246930ec3f39f35363abe0bfa94c9 Mon Sep 17 00:00:00 2001 From: Cleboost Date: Mon, 21 Apr 2025 14:11:08 +0200 Subject: [PATCH] feat: ajout du support des menus contextuels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implémentation de la classe ContextMenu pour gérer les interactions de menu contextuel. - Ajout de la gestion des interactions de menu contextuel dans le client. - Mise à jour des gestionnaires pour inclure le nouveau type d'interaction. - Ajout d'un exemple de menu contextuel dans le playground. --- packages/djs-cli/src/index.ts | 3 + packages/djs-core/src/class/BotClient.ts | 2 + .../src/class/interactions/ContextMenu.ts | 44 +++++++++ packages/djs-core/src/handlers/ContextMenu.ts | 99 +++++++++++++++++++ packages/djs-core/src/handlers/events.ts | 5 +- packages/djs-core/src/handlers/loader.ts | 7 +- packages/djs-core/src/index.ts | 2 + playground/src/interactions/context/infos.ts | 18 ++++ 8 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 packages/djs-core/src/class/interactions/ContextMenu.ts create mode 100644 packages/djs-core/src/handlers/ContextMenu.ts create mode 100644 playground/src/interactions/context/infos.ts diff --git a/packages/djs-cli/src/index.ts b/packages/djs-cli/src/index.ts index dcff135..970e073 100644 --- a/packages/djs-cli/src/index.ts +++ b/packages/djs-cli/src/index.ts @@ -20,6 +20,7 @@ import { SubCommand, Button, SelectMenu, + ContextMenu, } from "djs-core"; import dotenv from "dotenv"; import { pathToFileURL } from "url"; @@ -189,6 +190,8 @@ program return bot.handlers.buttons.reloadInteraction(file); if (file instanceof SelectMenu) return bot.handlers.selectMenus.reloadInteraction(file); + if (file instanceof ContextMenu) + return bot.handlers.contextMenu.reloadInteraction(file); console.log(chalk.yellow("⚠️ Unknown file type, skipping hot reload")); }); diff --git a/packages/djs-core/src/class/BotClient.ts b/packages/djs-core/src/class/BotClient.ts index 2550ffb..0f410ea 100644 --- a/packages/djs-core/src/class/BotClient.ts +++ b/packages/djs-core/src/class/BotClient.ts @@ -23,6 +23,7 @@ import ModalHandler from "../handlers/Modal"; import ButtonHandler from "../handlers/Button"; import SelectMenuHandler from "../handlers/SelectMenu"; import EventHandler from "../handlers/Event"; +import ContetxtMenuHandler from "../handlers/ContextMenu"; interface BotClientArgs { dev?: boolean; @@ -44,6 +45,7 @@ export default class BotClient extends Client { buttons: new ButtonHandler(this), selectMenus: new SelectMenuHandler(this), events: new EventHandler(this), + contextMenu: new ContetxtMenuHandler(this), }; cwdPath: string = process.cwd(); devMode: boolean = false; diff --git a/packages/djs-core/src/class/interactions/ContextMenu.ts b/packages/djs-core/src/class/interactions/ContextMenu.ts new file mode 100644 index 0000000..05cc76c --- /dev/null +++ b/packages/djs-core/src/class/interactions/ContextMenu.ts @@ -0,0 +1,44 @@ +import { + ContextMenuCommandBuilder, + ContextMenuCommandInteraction, + MessageFlags, +} from "discord.js"; +import BotClient from "../BotClient"; + +type ContextRunFn = ( + client: BotClient, + interaction: ContextMenuCommandInteraction, +) => unknown; +export default class ContextMenu extends ContextMenuCommandBuilder { + private runFn?: ContextRunFn; + constructor() { + super(); + } + + run(fn: ContextRunFn) { + this.runFn = fn; + return this; + } + + /** + * @private + * DO NOT USE + * Internal method to execute the function + */ + execute(client: BotClient, interaction: ContextMenuCommandInteraction) { + if (!this.runFn) { + client.logger.error( + new Error(`The command ${this.name} has no function to execute`), + ); + return interaction.reply({ + content: `The command ${this.name} has no function to execute!`, + flags: [MessageFlags.Ephemeral], + }); + } + return this.runFn(client, interaction); + } + + getDiscordCommand() { + return this.toJSON(); + } +} diff --git a/packages/djs-core/src/handlers/ContextMenu.ts b/packages/djs-core/src/handlers/ContextMenu.ts new file mode 100644 index 0000000..4b27658 --- /dev/null +++ b/packages/djs-core/src/handlers/ContextMenu.ts @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2025 Cleboost + * External contributor can be found on the GitHub + * Licence: on the GitHub + */ + +import { + Collection, + ContextMenuCommandInteraction, + MessageFlags, +} from "discord.js"; +import { underline } from "chalk"; +import BotClient from "../class/BotClient"; +import { pushToApi } from "./loader"; +import ContextMenu from "../class/interactions/ContextMenu"; + +export default class ContetxtMenuHandler { + private client: BotClient; + private contextMenus: Collection = new Collection(); + + constructor(client: BotClient) { + this.client = client; + } + + addInteraction(command: ContextMenu) { + if (this.contextMenus.has(command.name)) { + this.client.logger.warn( + `The command ${underline(command.name)} is already loaded! Skipping...`, + ); + return; + } + this.contextMenus.set(command.name, command); + return; + } + + removeInteraction(command: ContextMenu) { + if (!this.contextMenus.has(command.name)) { + this.client.logger.warn( + `The command ${underline(command.name)} is not loaded! Skipping...`, + ); + return; + } + this.contextMenus.delete(command.name); + return; + } + + reloadInteraction(command: ContextMenu) { + const existingCommand = this.contextMenus.find( + (cmd) => cmd.name === command.name, + ); + + if (!existingCommand) { + this.client.logger.warn( + `The command ${underline(command.name)} is not loaded! Adding instead...`, + ); + this.addInteraction(command); + pushToApi(this.client); + this.client.logger.info( + `Command ${underline(command.name)} added successfully, and pushed to API. You may reload your Discord client to see the changes.`, + ); + return; + } + + if (existingCommand.name !== command.name) { + this.client.logger.info( + `Command name changed: ${underline(existingCommand.name)} -> ${underline(command.name)}`, + ); + } + + this.removeInteraction(existingCommand); + this.addInteraction(command); + return; + } + + async eventContextMenu(interaction: ContextMenuCommandInteraction) { + const cmd = this.contextMenus.get(interaction.commandName); + if (!cmd) return; + cmd.execute(this.client, interaction); + + if (this.client.config?.logger?.logCmd) + this.client.logger.info( + `ContextMenu ${underline(interaction.commandName)} used by ${interaction.user.username} (${interaction.user.id})`, + ); + + await new Promise((resolve) => setTimeout(resolve, 2000)); + if (interaction.deferred || interaction.replied) return; + + await interaction.reply({ + content: + "ContextMenu took too long to respond or an error occurred during execution (please report this to the bot developer)", + flags: [MessageFlags.Ephemeral], + }); + return; + } + + listCommands(): ContextMenu[] { + return Array.from(this.contextMenus.values()); + } +} diff --git a/packages/djs-core/src/handlers/events.ts b/packages/djs-core/src/handlers/events.ts index 4dc0121..2941506 100644 --- a/packages/djs-core/src/handlers/events.ts +++ b/packages/djs-core/src/handlers/events.ts @@ -3,7 +3,10 @@ import BotClient from "../class/BotClient"; export function eventListener(client: BotClient) { client.on(Events.InteractionCreate, (interaction: Interaction) => { - if (interaction.isContextMenuCommand()) return; + if (interaction.isContextMenuCommand()) { + client.handlers.contextMenu.eventContextMenu(interaction); + return; + } if (interaction.isCommand()) { client.handlers.commands.eventCommand(interaction); client.handlers.subCommands.eventSubCommand(interaction); diff --git a/packages/djs-core/src/handlers/loader.ts b/packages/djs-core/src/handlers/loader.ts index ff97445..2409158 100644 --- a/packages/djs-core/src/handlers/loader.ts +++ b/packages/djs-core/src/handlers/loader.ts @@ -9,6 +9,7 @@ import Modal from "../class/interactions/Modal"; import Button from "../class/interactions/Button"; import SelectMenu from "../class/interactions/SelectMenu"; import EventListner from "../class/interactions/Event"; +import ContextMenu from "../class/interactions/ContextMenu"; export async function loadHandlers(client: BotClient) { if (client.devMode) { @@ -54,6 +55,7 @@ function registerInteraction( | Modal | Button | SelectMenu + | ContextMenu | unknown, ) { if (interaction instanceof Command) { @@ -70,14 +72,17 @@ function registerInteraction( return client.handlers.selectMenus.addInteraction(interaction); } else if (interaction instanceof EventListner) { return client.handlers.events.addEvent(interaction); + } else if (interaction instanceof ContextMenu) { + return client.handlers.contextMenu.addInteraction(interaction); } return; } export function pushToApi(client: BotClient) { - const commandList: Array = [ + const commandList: Array = [ client.handlers.commands.listCommands(), client.handlers.subCommands.listSubCommands(), + client.handlers.contextMenu.listCommands(), ].flat(); client.application?.commands.set(commandList).catch(console.error); } diff --git a/packages/djs-core/src/index.ts b/packages/djs-core/src/index.ts index f9b5eaa..3129ed7 100644 --- a/packages/djs-core/src/index.ts +++ b/packages/djs-core/src/index.ts @@ -17,6 +17,7 @@ import CommandMiddleware from "./class/middlewares/CommandMiddleware"; import ButtonMiddleware from "./class/middlewares/ButtonMiddleware"; import ModalMiddleware from "./class/middlewares/ModalMiddleware"; import SelectMiddleware from "./class/middlewares/SelectMiddleware"; +import ContextMenu from "./class/interactions/ContextMenu"; export { BotClient, @@ -32,4 +33,5 @@ export { ModalMiddleware, SelectMiddleware, Config, + ContextMenu, }; diff --git a/playground/src/interactions/context/infos.ts b/playground/src/interactions/context/infos.ts new file mode 100644 index 0000000..4e4eda0 --- /dev/null +++ b/playground/src/interactions/context/infos.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2025 Cleboost + * External contributor can be found on the GitHub + * Licence: on the GitHub + */ + +import { ApplicationCommandType, MessageFlags } from "discord.js"; +import { ContextMenu } from "djs-core"; + +export default new ContextMenu() + .setName("Test Context Menu") + .setType(ApplicationCommandType.User) + .run((client, interaction) => { + interaction.reply({ + content: "Hello from the context meenu!", + flags: [MessageFlags.Ephemeral], + }); + });