diff --git a/playground/src/middlewares/btn.ts b/playground/src/middlewares/btn.ts new file mode 100644 index 0000000..366f299 --- /dev/null +++ b/playground/src/middlewares/btn.ts @@ -0,0 +1,7 @@ +import { ButtonMiddleware } from "djs-core"; + +export default new ButtonMiddleware().run(async (interaction) => { + console.log("Just a log from the button middleware"); + console.log(interaction.customId); + return true; +}); diff --git a/playground/src/middlewares/modal.ts b/playground/src/middlewares/modal.ts new file mode 100644 index 0000000..8168a72 --- /dev/null +++ b/playground/src/middlewares/modal.ts @@ -0,0 +1,6 @@ +import { ModalMiddleware } from "djs-core"; + +export default new ModalMiddleware().run(async (interaction) => { + console.log(interaction.id); + return true; +}); diff --git a/playground/src/middlewares/select.ts b/playground/src/middlewares/select.ts new file mode 100644 index 0000000..b38fd42 --- /dev/null +++ b/playground/src/middlewares/select.ts @@ -0,0 +1,6 @@ +import { SelectMiddleware } from "djs-core"; + +export default new SelectMiddleware().run(async (interaction) => { + console.log("Select Middleware"); + return true; +}); diff --git a/playground/src/middlewares/test.ts b/playground/src/middlewares/test.ts new file mode 100644 index 0000000..30e9a23 --- /dev/null +++ b/playground/src/middlewares/test.ts @@ -0,0 +1,10 @@ +import { CommandMiddleware } from "djs-core"; + +//Try to do most speed as possible because this function is called for each command +export default new CommandMiddleware().run(async (inteaction) => { + if (inteaction.commandName === "ping") { + await inteaction.reply("Pong! from middleware"); + return false; // To prevent the command from being executed + } + return true; // To allow the command to be executed +}); diff --git a/src/class/BotClient.ts b/src/class/BotClient.ts index 3c65181..a2b8a64 100644 --- a/src/class/BotClient.ts +++ b/src/class/BotClient.ts @@ -19,11 +19,15 @@ import SubCommandHandler from "../handlers/SubCommand"; import SubCommandGroup from "./interactions/SubCommandGroup"; import { Handler } from "../handlers/Handler"; import { pathToFileURL } from "node:url"; +import ModalMiddleware from "./middlewares/ModalMiddleware"; +import SelectMiddleware from "./middlewares/SelectMiddleware"; export default class BotClient extends Client { logger: Logger = new Logger(); config: Config | null = null; - middlewares: Array = []; + middlewares: Array< + ComandMiddleware | ButtonMiddleware | ModalMiddleware | SelectMiddleware + > = []; constructor() { super({ intents: [ @@ -96,7 +100,7 @@ export default class BotClient extends Client { Promise.resolve(require("../handlers/Modal")), Promise.resolve(require("../handlers/Event")), ]; - const middlewaresPath = path.join(process.cwd(), "src", "middlewares"); + const middlewaresPath = path.join(process.cwd(), "middlewares"); if (fs.existsSync(middlewaresPath)) { await fs.promises .readdir(middlewaresPath) @@ -106,11 +110,14 @@ export default class BotClient extends Client { this.logger.warn(`The file ${file} is not a middleware`); return; } - const middleware = (await import(path.join(middlewaresPath, file))) - .default; + const middleware = ( + await import(pathToFileURL(path.join(middlewaresPath, file)).href) + ).default.default; if ( !(middleware instanceof ComandMiddleware) && - !(middleware instanceof ButtonMiddleware) + !(middleware instanceof ButtonMiddleware) && + !(middleware instanceof ModalMiddleware) && + !(middleware instanceof SelectMiddleware) ) { this.logger.error(`The middleware ${file} is not correct!`); return; diff --git a/src/class/middlewares/ButtonMiddleware.ts b/src/class/middlewares/ButtonMiddleware.ts index 145bba0..69c0b1b 100644 --- a/src/class/middlewares/ButtonMiddleware.ts +++ b/src/class/middlewares/ButtonMiddleware.ts @@ -6,16 +6,33 @@ import { ButtonInteraction } from "discord.js"; +/** + * fn + * @type {Function} - Function to execute + * @param {CommandInteraction} interaction - The interaction to check + * @returns {boolean} - Return true if accepted event, false otherwise + * @public + */ + +type fn = (interaction: ButtonInteraction) => Promise; + export default class ButtonMiddleware { - private fn: (interaction: ButtonInteraction) => void = () => {}; + private fn: fn | null = null; constructor() {} - run(fn: (interaction: ButtonInteraction) => void) { + run(fn: fn) { this.fn = fn; return this; } execute(interaction: ButtonInteraction) { - return this.fn(interaction); + if (this.fn) { + return this.fn(interaction); + } + interaction.reply({ + content: "An error occured", + ephemeral: true, + }); + return new Promise((resolve) => resolve(false)); } } diff --git a/src/class/middlewares/CommandMiddleware.ts b/src/class/middlewares/CommandMiddleware.ts index dd5d26a..1cb397c 100644 --- a/src/class/middlewares/CommandMiddleware.ts +++ b/src/class/middlewares/CommandMiddleware.ts @@ -14,7 +14,7 @@ import { ChatInputCommandInteraction } from "discord.js"; * @public */ -type fn = (interaction: ChatInputCommandInteraction) => boolean; +type fn = (interaction: ChatInputCommandInteraction) => Promise; export default class ComandMiddleware { private fn: fn | null = null; @@ -30,10 +30,14 @@ export default class ComandMiddleware { * DO NOT USE * Internal method to execute the function */ - execute(interaction: ChatInputCommandInteraction) { + execute(interaction: ChatInputCommandInteraction): Promise { if (this.fn) { return this.fn(interaction); } - return true; + interaction.reply({ + content: "An error occured", + ephemeral: true, + }); + return new Promise((resolve) => resolve(false)); } } diff --git a/src/class/middlewares/ModalMiddleware.ts b/src/class/middlewares/ModalMiddleware.ts new file mode 100644 index 0000000..30887be --- /dev/null +++ b/src/class/middlewares/ModalMiddleware.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2025 Cleboost + * External contributor can be found on the GitHub + * Licence: on the GitHub + */ + +import { ModalSubmitInteraction } from "discord.js"; + +/** + * fn + * @type {Function} - Function to execute + * @param {ModalSubmitInteraction} interaction - The interaction to check + * @returns {boolean} - Return true if accepted event, false otherwise + * @public + */ + +type fn = (interaction: ModalSubmitInteraction) => Promise; + +export default class ModalMiddleware { + private fn: fn | null = null; + constructor() {} + + run(fn: fn) { + this.fn = fn; + return this; + } + + /** + * @private + * DO NOT USE + * Internal method to execute the function + */ + execute(interaction: ModalSubmitInteraction): Promise { + if (this.fn) { + return this.fn(interaction); + } + interaction.reply({ + content: "An error occured", + ephemeral: true, + }); + return new Promise((resolve) => resolve(false)); + } +} diff --git a/src/class/middlewares/SelectMiddleware.ts b/src/class/middlewares/SelectMiddleware.ts new file mode 100644 index 0000000..d2806b5 --- /dev/null +++ b/src/class/middlewares/SelectMiddleware.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2025 Cleboost + * External contributor can be found on the GitHub + * Licence: on the GitHub + */ + +import { AnySelectMenuInteraction } from "discord.js"; + +/** + * fn + * @type {Function} - Function to execute + * @param {AnySelectMenuInteraction} interaction - The interaction to check + * @returns {boolean} - Return true if accepted event, false otherwise + * @public + */ + +type fn = (interaction: AnySelectMenuInteraction) => Promise; + +export default class ModalMiddleware { + private fn: fn | null = null; + constructor() {} + + run(fn: fn) { + this.fn = fn; + return this; + } + + /** + * @private + * DO NOT USE + * Internal method to execute the function + */ + execute(interaction: AnySelectMenuInteraction): Promise { + if (this.fn) { + return this.fn(interaction); + } + interaction.reply({ + content: "An error occured", + ephemeral: true, + }); + return new Promise((resolve) => resolve(false)); + } +} diff --git a/src/handlers/Button.ts b/src/handlers/Button.ts index 9c88233..eeeee18 100644 --- a/src/handlers/Button.ts +++ b/src/handlers/Button.ts @@ -11,11 +11,14 @@ import { Events, Interaction } from "discord.js"; import Button from "../class/interactions/Button"; import { pathToFileURL } from "node:url"; import { underline } from "chalk"; +import ButtonMiddleware from "../class/middlewares/ButtonMiddleware"; export default class ButtonHandler extends Handler { - // private middleware: Array = []; + private middleware: Array = []; async load() { - // this.middleware = this.client.middlewares.filter((middleware: unknown) => middleware instanceof CommandMiddleware) as Array; + this.middleware = this.client.middlewares.filter( + (middleware: unknown) => middleware instanceof ButtonMiddleware, + ); /* eslint-disable no-async-promise-executor */ return new Promise(async (resolve) => { @@ -89,9 +92,9 @@ export default class ButtonHandler extends Handler { Events.InteractionCreate, async (interaction: Interaction) => { if (!interaction.isButton()) return; - // for (const middleware of this.middleware) { - // if (!middleware.execute(interaction)) return; - // } + for (const middleware of this.middleware) { + if (!(await middleware.execute(interaction))) return; + } const button: Button | undefined = this.collection.get( interaction.customId, ) as Button | undefined; diff --git a/src/handlers/Command.ts b/src/handlers/Command.ts index 6f5db2a..6e06522 100644 --- a/src/handlers/Command.ts +++ b/src/handlers/Command.ts @@ -19,7 +19,7 @@ export default class CommandHandler extends Handler { async load() { this.middleware = this.client.middlewares.filter( (middleware: unknown) => middleware instanceof CommandMiddleware, - ) as Array; + ); /* eslint-disable no-async-promise-executor */ return new Promise(async (resolve) => { @@ -63,7 +63,7 @@ export default class CommandHandler extends Handler { if (interaction.isContextMenuCommand()) return; if (interaction.options.getSubcommand(false)) return; for (const middleware of this.middleware) { - if (!middleware.execute(interaction)) return; + if (!(await middleware.execute(interaction))) return; } const command: Command = this.collection.get( interaction.commandName, diff --git a/src/handlers/Modal.ts b/src/handlers/Modal.ts index 633704f..5f7f66f 100644 --- a/src/handlers/Modal.ts +++ b/src/handlers/Modal.ts @@ -8,15 +8,17 @@ import { Handler } from "./Handler"; import path from "path"; import fs from "node:fs"; import { Events, Interaction, MessageFlags } from "discord.js"; -import CommandMiddleware from "../class/middlewares/CommandMiddleware"; import Modal from "../class/interactions/Modal"; import { pathToFileURL } from "node:url"; import { underline } from "chalk"; +import ModalMiddleware from "../class/middlewares/ModalMiddleware"; export default class ModalHandler extends Handler { - private middleware: Array = []; + private middleware: Array = []; async load() { - // this.middleware = this.client.middlewares.filter((middleware: unknown) => middleware instanceof CommandMiddleware) as Array; + this.middleware = this.client.middlewares.filter( + (middleware: unknown) => middleware instanceof ModalMiddleware, + ); /* eslint-disable no-async-promise-executor */ return new Promise(async (resolve) => { @@ -59,10 +61,9 @@ export default class ModalHandler extends Handler { Events.InteractionCreate, async (interaction: Interaction) => { if (!interaction.isModalSubmit()) return; - // for (const middleware of this.middleware) { - // if (!middleware.execute(interaction)) return; - // } - + for (const middleware of this.middleware) { + if (!(await middleware.execute(interaction))) return; + } const select = this.collection.get(interaction.customId) as | Modal | undefined; @@ -73,7 +74,7 @@ export default class ModalHandler extends Handler { flags: [MessageFlags.Ephemeral], }); select.execute(this.client, interaction); - // if (this.client.config?.logger?.log) this.client.logger.info(`Modal ${underline(interaction.customId)} used by ${interaction.user.username} (${interaction.user.id})`); + // if (this.client.config?.logger?.) this.client.logger.info(`Modal ${underline(interaction.customId)} used by ${interaction.user.username} (${interaction.user.id})`); }, ); } diff --git a/src/handlers/SelectMenu.ts b/src/handlers/SelectMenu.ts index 91be5bf..2e92bd7 100644 --- a/src/handlers/SelectMenu.ts +++ b/src/handlers/SelectMenu.ts @@ -11,11 +11,14 @@ import { Events, Interaction, MessageFlags } from "discord.js"; import SelectMenu from "../class/interactions/SelectMenu"; import { pathToFileURL } from "node:url"; import { underline } from "chalk"; +import { SelectMiddleware } from ".."; export default class SelectMenuHandler extends Handler { - // private middleware: Array = []; + private middleware: Array = []; async load() { - // this.middleware = this.client.middlewares.filter((middleware: unknown) => middleware instanceof CommandMiddleware) as Array; + this.middleware = this.client.middlewares.filter( + (middleware: unknown) => middleware instanceof SelectMiddleware, + ); /* eslint-disable no-async-promise-executor */ return new Promise(async (resolve) => { @@ -64,9 +67,9 @@ export default class SelectMenuHandler extends Handler { Events.InteractionCreate, async (interaction: Interaction) => { if (!interaction.isAnySelectMenu()) return; - // for (const middleware of this.middleware) { - // if (!middleware.execute(interaction)) return; - // } + for (const middleware of this.middleware) { + if (!(await middleware.execute(interaction))) return; + } const select = this.collection.get(interaction.customId) as | SelectMenu | undefined; diff --git a/src/index.ts b/src/index.ts index 85e5472..ecf780c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,10 @@ import Button from "./class/interactions/Button"; import SelectMenu from "./class/interactions/SelectMenu"; import Modal from "./class/interactions/Modal"; import EventListner from "./class/interactions/Event"; +import CommandMiddleware from "./class/middlewares/CommandMiddleware"; +import ButtonMiddleware from "./class/middlewares/ButtonMiddleware"; +import ModalMiddleware from "./class/middlewares/ModalMiddleware"; +import SelectMiddleware from "./class/middlewares/SelectMiddleware"; export { BotClient, @@ -23,5 +27,9 @@ export { SelectMenu, Modal, EventListner, + CommandMiddleware, + ButtonMiddleware, + ModalMiddleware, + SelectMiddleware, }; export type { Config };