From b5fb409d1e76f6528bc8e8836e59996435e744a1 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Tue, 22 Jul 2025 18:45:00 -0400 Subject: [PATCH 01/28] deprecated old interaction fn and refine some codes --- docs/builtIn/interactionCreate-event.md | 149 ++++++++++++---------- docs/builtIn/messageCreate-event.md | 19 +-- docs/release-notes.md | 7 +- docs/setup/commands-file.md | 4 +- docs/setup/other-interaction.md | 157 +++++++++++++----------- src/events/dfhInteractionCreate.js | 70 ++--------- src/events/dfhMessageCreate.js | 16 +-- src/handlers/loadCommands.js | 21 +++- src/index.d.ts | 43 ++++++- 9 files changed, 263 insertions(+), 223 deletions(-) diff --git a/docs/builtIn/interactionCreate-event.md b/docs/builtIn/interactionCreate-event.md index 1cdd5e8..e2aabd5 100644 --- a/docs/builtIn/interactionCreate-event.md +++ b/docs/builtIn/interactionCreate-event.md @@ -3,7 +3,7 @@ This event handles the creation execution of the slash commands, using the built-in slash command executing from the slash properties of a command file and also using the built-in `client.commands` Collection object. We will also display the slash command Id, incase you decide to delete a slash command. ```javascript -const { InteractionType, Events } = require("discord.js"); +const { InteractionType, Events } = require("discord.js");const { InteractionType, Events } = require("discord.js"); module.exports = { name: Events.InteractionCreate, async execute(interaction, client) { @@ -15,77 +15,100 @@ module.exports = { guildMember: interaction.member, }); - if (interaction.type === InteractionType.ApplicationCommand) { - const cmd = client.commands.get(commandName); + try { + if (interaction.type === InteractionType.ApplicationCommand) { + const cmd = client.commands.get(commandName); - if (!cmd) { - return console.error("Unable to find slash command:" + cmd); - } - console.log(`[SLASH CMD]`, `[${interaction.user.tag}]`, `${commandName}`); - console.log("[SLASH CMD]", "[ID]", commandId); - - try { - return cmd.interactionReply(interaction, client, level); - } catch (e) { + if (!cmd) { + console.error("Unable to find slash command:" + cmd); + return interaction.reply({ + content: `Command not found: ${commandName}`, + ephemeral: true, + }); + } console.log( - "ApplicationCommand interaction (slash command) execution failed", - e + `[SLASH CMD]`, + `[${interaction.user.tag}]`, + `${commandName}` ); + console.log("[SLASH CMD]", "[ID]", commandId); + + try { + return cmd.interactionReply(interaction, client, level); + } catch (e) { + console.log( + "ApplicationCommand interaction (slash command) execution failed", + e + ); + return interaction.reply({ + content: `An error occurred while processing your command: ${commandName}. Error: + ${e}`, + ephemeral: true, + }); + } + } + // if (interaction.isUserContextMenuCommand()) { + // const cmd = client.commands.get(commandName); + // if (!cmd) { + // return console.error("Unable to find context menu command:" + cmd); + // } + // console.log( + // `[CONTEXT MENU CMD]`, + // `[${interaction.user.tag}]`, + // `${commandName} ID: ${commandId}`, + // commandId + // ); + // cmd.contextMenuInteraction(interaction, client, level); + // } + + const foundCmd = client.commands.find((cmd) => { + if (!cmd.customIds) return false; + + if (Array.isArray(cmd.customIds)) { + return cmd.customIds.includes(customId); + } + + if (typeof cmd.customIds === "object") { + return Object.values(cmd.customIds).includes(customId); + } + + return false; + }); + + if (!foundCmd) { + console.error("Unable to find command with customId: " + customId); + + return interaction.reply({ + content: `Command not found for customId: ${customId}`, + ephemeral: true, + }); } - } else if (interaction.isUserContextMenuCommand()) { - const cmd = client.commands.get(commandName); - if (!cmd) { - return console.error("Unable to find context menu command:" + cmd); + + if ( + typeof foundCmd.customIdInteraction !== "function" || + !foundCmd.customIdInteraction + ) { + console.error( + `Command "${foundCmd.name}" has not implemented customIdInteraction.` + ); + return interaction.reply({ + content: `Command "${foundCmd.name}" has not implemented customIdInteraction.`, + ephemeral: true, + }); } + console.log( - `[CONTEXT MENU CMD]`, + `[CUSTOM ID CMD]`, `[${interaction.user.tag}]`, - `${commandName} ID: ${commandId}`, - commandId + `${foundCmd.name} CustomID: ${customId}` ); - cmd.contextMenuInteraction(interaction, client, level); - } else if ( - interaction.type === InteractionType.ApplicationCommandAutocomplete - ) { - const cmdName = client.commands.find((cmd) => - cmd.customIds?.autoComplete?.includes(customId) - )?.name; - if (!cmdName) return; - - const cmd = client.commands.get(cmdName); - try { - return cmd.autoCompleteInteraction(interaction, client, level); - } catch (e) { - console.log("auto complete interaction execution failed", e); - } - } else if (interaction.type === InteractionType.MessageComponent) { - const cmdName = client.commands.find((cmd) => - cmd.customIds?.messageComponent?.includes(customId) - )?.name; - if (!cmdName) return; - const cmd = client.commands.get(cmdName); - try { - return cmd.componentInteraction(interaction, client, level); - } catch (e) { - console.log("MessageComponent interaction execution failed", e); - } - } else if (interaction.type === InteractionType.ModalSubmit) { - const cmdName = client.commands.find((cmd) => - cmd.customIds?.modal?.includes(customId) - )?.name; - if (!cmdName) - return console.error( - interaction.customId, - "no modal interaction found" - ); - const cmd = client.commands.get(cmdName); - - try { - return cmd.modalInteraction(interaction, client, level); - } catch (e) { - console.log("ModalSubmit interaction execution failed", e); - } + return foundCmd.customIdInteraction(interaction, client, level); + } catch (e) { + console.log("Interaction execution failed", e); + return interaction.reply({ + content: "An error occurred while processing your interaction." + e, + }); } }, }; diff --git a/docs/builtIn/messageCreate-event.md b/docs/builtIn/messageCreate-event.md index bdd91c0..3019e54 100644 --- a/docs/builtIn/messageCreate-event.md +++ b/docs/builtIn/messageCreate-event.md @@ -13,20 +13,21 @@ module.exports = { if (message.author.bot) return; if ( - typeof configPrefix === String && - !message.content.startsWith(configPrefix) - ) - return; - if ( - Array.isArray(configPrefix) && - !configPrefix.some((prefix) => message.content.startsWith(prefix)) - ) + (typeof configPrefix === "string" && + !message.content.startsWith(configPrefix)) || + (Array.isArray(configPrefix) && + !configPrefix.some((prefix) => message.content.startsWith(prefix))) + ) { + // if no prefix found; you can create your own logic here for handling messages without a prefix return; - + } + const prefix = Array.isArray(configPrefix) ? configPrefix.find((prefix) => message.content.startsWith(prefix)) : configPrefix; + if (!prefix) return; + const args = message.content.slice(prefix.length).trim().split(/ +/); const command = args.shift().toLowerCase(); const cmd = diff --git a/docs/release-notes.md b/docs/release-notes.md index 4731fb3..359e2fc 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -15,10 +15,9 @@ hide: - Add new optional options to the DiscordFeaturesHandlerOptions - slashCommandIdsToDelete: Array of strings for deleting specific slash command ids, - onSlashCommandsLoading: Object of boolean for enabling delete of slash commands before loading new ones -- CommandFile Properties update: - - Change `customIds` to Array of Strings of customIds or a Object of key values - - Removed `componentInteraction`, `autoCompleteInteraction` `contextMenuInteraction` `modalInteraction` and replaced with a unviersal one called: `customIdInteraction` that handles interaction based off `customIds` - - Add `customIdInteraction(interaction, client, level)` to handle customIds interactions to ensure no button or other type of customIds are inactive if the bot application or collector run out on time. +- CommandFile Properties update: Change `customIds` to Array of Strings of customIds or a Object of key values +- Removed `componentInteraction`, `autoCompleteInteraction` `contextMenuInteraction` `modalInteraction` from XommandFile peroperties and replaced with a unviersal one called: `customIdInteraction` that handles interaction based off `customIds` +- Add `customIdInteraction(interaction, client, level)` into CommandFile Properties to handle customIds interactions to ensure no button or other type of customIds are inactive if the bot application or collector run out on time. ### Fix - Issue where prefix commands doens't work diff --git a/docs/setup/commands-file.md b/docs/setup/commands-file.md index 9250607..3b4685d 100644 --- a/docs/setup/commands-file.md +++ b/docs/setup/commands-file.md @@ -70,8 +70,8 @@ This is the maximum arguments required to execute the command

- customIds Object
- An object containing interaction name properties that are an Array<string> containing the custom id names for button/autoComplete/userContext/modal interactions + customIds Array<String>
+ An Array of strings containing strings of customIds used in current command file. This can be also a key:value pairs as an object of keys where the values are the string of customIds for easier reference

usage string
diff --git a/docs/setup/other-interaction.md b/docs/setup/other-interaction.md index 1897f6a..f1d2876 100644 --- a/docs/setup/other-interaction.md +++ b/docs/setup/other-interaction.md @@ -5,93 +5,102 @@ Interaction such as buttons, select menu, auto complete, modals and context menu When setting up these interactions you will need to define a customIds property in your command file: ```javascript +import { + ActionRowBuilder, + ActionRowComponent, + ButtonBuilder, + ButtonStyle, + SlashCommandBuilder, +} from "discord.js"; + module.exports = { - name: 'ping', - description: 'Ping Pong Command!', - aliases: ['p'], - guildOnly: true, - permissions: 0, - minArgs: 0, - /** - * customIds for your interaction components - */ - customIds: { - /** - * customIds for select menus and buttons - */ - messageComponent: ['msgComponentId'], - /** - * customIds for auto complete interaction - */ - autoComplete: ['autoCompleteId'], + name: 'ping', + description: 'Ping Pong Command!', + aliases: ['p'], + guildOnly: true, + permissions: 0, + minArgs: 0, + data: new SlashCommandBuilder() + .setName("ping") + .setDescription("Ping Pong Command"), /** - * customIds for modal + * customIds for your interaction components * + * or this can be a Array + * customIds: ["btnComponentId","backBtnId"] */ - modal: ['modalId'], - }, - usage: '', - execute(message, args, client) { // function named execute; define what the command does - return message.channel.send({ content: 'Pong.'}); - }, + customIds: { + /** + * customId for a button component + */ + buttonComponent: 'btnComponentId', + /** + * customId for second button + */ + secondButton: 'secondBtnId', + }, + usage: '', + execute(message, args, client) { + return message.channel.send({ content: 'Pong.'}); + }, + async interactionReply(interaction, client, level){ + await interaction.deferReply({ + ephemeral: true, + }); + + let row = new ActionRowBuilder(); + const btnArray = [{ + id: this.customIds.buttonComponent, + name: "Button1", + color: ButtonStyle.Primary, + },{ + id: this.customIds.secondButton, + name: "Button1", + color: ButtonStyle.Secondary, + }].map((btn) => { + return new ButtonBuilder() + .setCustomId(btn.id) + .setLabel(btn.name) + .setStyle(btn.color) + .setDisabled(disabled); + }); + + let row = new ActionRowBuilder(); + + btnArray.map((btn) => row.addComponents(btn)); + + return await interaction.editReply({ + content: "pong", + components: [row], + }); + + } + /** + * this is the function to used when interacting with customIds + */ + async customIdInteraction(interaction, client, level){ + /** + * if(interaction.customId === this.customIds[0]) + */ + if(interaction.customId === this.customIds.buttonComponent){ + + return interaction.update({ + content: "updated button component", + }) + } + } }; ``` -If you are creating a modal then you will need to add `modalInteraction` method - -

- modalInteraction(interaction, client, level) - Promise<Interaction>
-

- -| Property | Type | Required | Description | -|---------------|-----------------------------------------------------------------------------------------------------------|----------|--------------------------------------------------------------| -| interaction | [ModalSubmitInteraction Class](https://discord.js.org/docs/packages/discord.js/main/ModalSubmitInteraction:Class) | true | This is the command interaction object that represents a slash command interaction on Discord. | -| client | [Discord.Client](https://discord.js.org/docs/packages/discord.js/main/BaseClient:Class) | false | This is the Discord client object. | -| level | Number | false | This is the user's permission level. | - - -If you are creating a message component then you will need to add `componentInteraction` method - -

- componentInteraction(interaction, client, level) - Promise<Interaction>
-

- -!!! info - If you are using `createMessageComponentCollector` then you do not need to define this method to handle the button or select menu interaction - -| Property | Type | Required | Description | -|---------------|-----------------------------------------------------------------------------------------------------------|----------|--------------------------------------------------------------| -| interaction | [MessageComponentInteraction Class](https://discord.js.org/docs/packages/discord.js/main/MessageComponentInteraction:Class) | true | This is the command interaction object that represents a button or select menu interaction on Discord. | -| client | [Discord.Client](https://discord.js.org/docs/packages/discord.js/main/BaseClient:Class) | false | This is the Discord client object. | -| level | Number | false | This is the user's permission level. | - - -If you are creating a auto complete component then you will need to add `autoCompleteInteraction` method - -

- autoCompleteInteraction(interaction, client, level) - Promise<Interaction>
-

- -| Property | Type | Required | Description | -|---------------|-----------------------------------------------------------------------------------------------------------|----------|--------------------------------------------------------------| -| interaction | [AutocompleteInteraction Class](https://discord.js.org/docs/packages/discord.js/main/AutocompleteInteraction:Class) | true | This is the command interaction object that represents a auto complete interaction on Discord. | -| client | [Discord.Client](https://discord.js.org/docs/packages/discord.js/main/BaseClient:Class) | false | This is the Discord client object. | -| level | Number | false | This is the user's permission level. | - - -If you are creating a user context menu component then you will need to add `contextMenuInteraction` method +If you are creating a customId interaction then you will need to add `customIdInteraction` method

- contextMenuInteraction(interaction, client, level) + customIdInteraction(interaction, client, level) Promise<Interaction>

| Property | Type | Required | Description | |---------------|-----------------------------------------------------------------------------------------------------------|----------|--------------------------------------------------------------| -| interaction | [UserContextMenuCommandInteraction Class](https://discord.js.org/docs/packages/discord.js/main/UserContextMenuCommandInteraction:Class) | true | This is the command interaction object that represents a user context menu interaction on Discord. | +| interaction | [CommandInteraction Class](https://discord.js.org/docs/packages/discord.js/main/CommandInteraction:Class) | true | This is the command interaction object that represents a slash command interaction on Discord. | | client | [Discord.Client](https://discord.js.org/docs/packages/discord.js/main/BaseClient:Class) | false | This is the Discord client object. | | level | Number | false | This is the user's permission level. | - diff --git a/src/events/dfhInteractionCreate.js b/src/events/dfhInteractionCreate.js index a06b676..109eea0 100644 --- a/src/events/dfhInteractionCreate.js +++ b/src/events/dfhInteractionCreate.js @@ -42,19 +42,6 @@ module.exports = { }); } } - // if (interaction.isUserContextMenuCommand()) { - // const cmd = client.commands.get(commandName); - // if (!cmd) { - // return console.error("Unable to find context menu command:" + cmd); - // } - // console.log( - // `[CONTEXT MENU CMD]`, - // `[${interaction.user.tag}]`, - // `${commandName} ID: ${commandId}`, - // commandId - // ); - // cmd.contextMenuInteraction(interaction, client, level); - // } const foundCmd = client.commands.find((cmd) => { if (!cmd.customIds) return false; @@ -70,7 +57,7 @@ module.exports = { return false; }); - if (!foundCmd) { + if (!foundCmd || !foundCmd.name) { console.error("Unable to find command with customId: " + customId); return interaction.reply({ @@ -97,55 +84,22 @@ module.exports = { `[${interaction.user.tag}]`, `${foundCmd.name} CustomID: ${customId}` ); - - return foundCmd.customIdInteraction(interaction, client, level); + try { + return foundCmd.customIdInteraction(interaction, client, level); + } catch (e) { + console.error( + `Error executing customIdInteraction for command "${foundCmd.name}":`, + e + ); + return interaction.reply({ + content: `An error occurred while processing your interaction: ${foundCmd.name} and customId: ${customId}. Error: ${e}`, + }); + } } catch (e) { console.log("Interaction execution failed", e); return interaction.reply({ content: "An error occurred while processing your interaction." + e, }); } - - if (interaction.type === InteractionType.ApplicationCommandAutocomplete) { - const cmdName = client.commands.find((cmd) => - cmd.customIds?.autoComplete?.includes(customId) - )?.name; - if (!cmdName) return; - - const cmd = client.commands.get(cmdName); - try { - return cmd.autoCompleteInteraction(interaction, client, level); - } catch (e) { - console.log("auto complete interaction execution failed", e); - } - } else if (interaction.type === InteractionType.MessageComponent) { - const cmdName = client.commands.find((cmd) => - cmd.customIds?.messageComponent?.includes(customId) - )?.name; - if (!cmdName) return; - const cmd = client.commands.get(cmdName); - try { - return cmd.componentInteraction(interaction, client, level); - } catch (e) { - console.log("MessageComponent interaction execution failed", e); - } - } else if (interaction.type === InteractionType.ModalSubmit) { - const cmdName = client.commands.find((cmd) => - cmd.customIds?.modal?.includes(customId) - )?.name; - if (!cmdName) - return console.error( - interaction.customId, - "no modal interaction found" - ); - - const cmd = client.commands.get(cmdName); - - try { - return cmd.modalInteraction(interaction, client, level); - } catch (e) { - console.log("ModalSubmit interaction execution failed", e); - } - } }, }; diff --git a/src/events/dfhMessageCreate.js b/src/events/dfhMessageCreate.js index 342248b..cdce116 100644 --- a/src/events/dfhMessageCreate.js +++ b/src/events/dfhMessageCreate.js @@ -6,20 +6,20 @@ module.exports = { if (message.author.bot) return; if ( - typeof configPrefix === String && - !message.content.startsWith(configPrefix) - ) - return; - if ( - Array.isArray(configPrefix) && - !configPrefix.some((prefix) => message.content.startsWith(prefix)) - ) + (typeof configPrefix === "string" && + !message.content.startsWith(configPrefix)) || + (Array.isArray(configPrefix) && + !configPrefix.some((prefix) => message.content.startsWith(prefix))) + ) { return; + } const prefix = Array.isArray(configPrefix) ? configPrefix.find((prefix) => message.content.startsWith(prefix)) : configPrefix; + if (!prefix) return; + const args = message.content.slice(prefix.length).trim().split(/ +/); const command = args.shift().toLowerCase(); const cmd = diff --git a/src/handlers/loadCommands.js b/src/handlers/loadCommands.js index f1d0359..02c3f30 100644 --- a/src/handlers/loadCommands.js +++ b/src/handlers/loadCommands.js @@ -55,10 +55,16 @@ module.exports = ({ const deleteSlashCommands = async () => { const rest = new REST().setToken(client.config.token); const { clientId, guildId } = client.config; + if (!clientId) { + console.error("[log]", "[Slash CMDs]", "Client ID is not defined."); + return; + } + try { - for (const id of slashCommandIdsToDelete) { + slashCommandIdsToDelete.map(async (id) => { await rest - .delete(Routes.applicationCommand(clientId, id)) + .delete(Routes.applicationCommand(clientId, guildId, id)) + .then(() => console.log( "[log]", @@ -66,8 +72,15 @@ module.exports = ({ `Successfully deleted slash command with ID: ${id}` ) ) - .catch(console.error); - } + .catch((e) => { + console.error( + "[log]", + "[Slash CMDs]", + `Failed to delete slash command with ID: ${id}`, + e.message + ); + }); + }); } catch (err) { console.error( "[log]", diff --git a/src/index.d.ts b/src/index.d.ts index d34ae99..ad84271 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -335,7 +335,7 @@ interface CommandFile { * @param client Client Object * @param level permission level of user * - * This function is used for custom id interactions like buttons, select menus, modals, etc. + * This function is used for custom id interactions like message components, modals, autocomplete interactions, and user context menu commands. */ customIdInteraction?( interaction: @@ -346,6 +346,47 @@ interface CommandFile { client?: Client, level?: number ): Promise; + + /** + * + * @readonly + * @deprecated this function is depecated used customIdInteraction instead + */ + componentInteraction?( + interaction: MessageComponentInteraction | ModalSubmitInteraction, + client?: Client, + level?: number + ): Promise; + + /** + * + * @readonly + * @deprecated this function is depecated used customIdInteraction instead + */ + autoCompleteInteraction?( + interaction: AutocompleteInteraction, + client?: Client, + level?: number + ): Promise; + /** + * + * @readonly + * @deprecated this function is depecated used customIdInteraction instead + */ + modalInteraction?( + interaction: ModalSubmitInteraction, + client?: Client, + level?: number + ): Promise; + /** + * @readonly + * @deprecated this function is depecated used customIdInteraction instead + */ + contextMenuInteraction?( + interaction: UserContextMenuCommandInteraction, + client?: Client, + level?: number + ): Promise; } interface EventFile { From 1662439ef326869820fff3d1d8fd1be766f94c9c Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Sat, 9 Aug 2025 09:12:20 -0400 Subject: [PATCH 02/28] updated .yml to work on pull request --- .github/workflows/deploy-docs.yml | 10 +++++++--- .github/workflows/publish-release.yml | 23 ++++++++++++++++++++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 89fcf83..bc3fc5c 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -2,7 +2,11 @@ name: deploy-docs on: push: branches: - - master + - master + pull_request: + types: [opened, reopened] + branches: + - master permissions: contents: write jobs: @@ -14,7 +18,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: '3.x' + python-version: "3.x" - name: Install dependencies run: | python -m pip install --upgrade pip @@ -22,4 +26,4 @@ jobs: pip install "mkdocs-material[imaging]" - name: Deploy to GitHub Pages run: | - mkdocs gh-deploy --force \ No newline at end of file + mkdocs gh-deploy --force diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 30bd388..cfdffdb 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -3,6 +3,10 @@ on: push: branches: - master + pull_request: + types: [opened, reopened] + branches: + - master jobs: create-release: @@ -14,7 +18,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v3 with: - node-version: '16' + node-version: "16" - name: Get package version id: get_version @@ -31,3 +35,20 @@ jobs: body: ${{ github.event.head_commit.message }} draft: false prerelease: false + publish: + runs-on: ubuntu-latest + needs: create-release + steps: + - name: Checkout repository + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + registry-url: https://registry.npmjs.org + + - name: Publish package + run: npm ci && npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} From d284127c75b9e15248ba71cfc279a137dd69e001 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Sat, 16 Aug 2025 16:12:02 -0400 Subject: [PATCH 03/28] add executePrefix property to cmds and recommend conversion Add jsdoc to recommend use of executePrefix for prefix commands and use execute for slash commands following discord.js guidelines for slash commands --- src/events/dfhInteractionCreate.js | 11 +++++++++- src/events/dfhMessageCreate.js | 27 ++++++++++++++++++++++-- src/handlers/loadCommands.js | 2 +- src/index.d.ts | 34 +++++++++++++++++++++++------- 4 files changed, 62 insertions(+), 12 deletions(-) diff --git a/src/events/dfhInteractionCreate.js b/src/events/dfhInteractionCreate.js index 109eea0..f53c80c 100644 --- a/src/events/dfhInteractionCreate.js +++ b/src/events/dfhInteractionCreate.js @@ -29,7 +29,16 @@ module.exports = { console.log("[SLASH CMD]", "[ID]", commandId); try { - return cmd.interactionReply(interaction, client, level); + if ("execute" in cmd && !"interactionReply" in cmd) { + return cmd.execute(interaction, client, level); + } + return cmd + .interactionReply(interaction, client, level) + .then((reply) => { + console.warn( + "Please use execute property for slash commands as next version of discord-features-handler will use, 'executePrefix' property for prefix commands and deprecate interactionReply." + ); + }); } catch (e) { console.log( "ApplicationCommand interaction (slash command) execution failed", diff --git a/src/events/dfhMessageCreate.js b/src/events/dfhMessageCreate.js index cdce116..4e8e75d 100644 --- a/src/events/dfhMessageCreate.js +++ b/src/events/dfhMessageCreate.js @@ -20,7 +20,10 @@ module.exports = { if (!prefix) return; - const args = message.content.slice(prefix.length).trim().split(/ +/); + const args = (args = message.content + .slice(prefix.length) + .trim() + .split(/\s+/)); const command = args.shift().toLowerCase(); const cmd = client.commands.get(command) || @@ -77,7 +80,27 @@ module.exports = { } try { - cmd.execute(message, args, client, level); + if (cmd.executePrefix) { + return cmd.executePrefix(message, args, client, level); + } else { + return cmd + .execute(message, args, client, level) + .then((reply) => { + console.warn( + "Please use executePrefix property for prefix commands as next version of discord-features-handler will use, 'execute' property for slash commands only." + ); + }) + .catch((e) => { + console.error(e, `Executing CMD: ${cmd.name}`); + console.error( + "[log]", + "[Prefix CMDs]", + `Failed to execute prefix command: ${command}, please update to executePrefix property instead`, + e.message + ); + message.reply("There was an error trying to execute that command!"); + }); + } } catch (e) { console.error(e, `Executing CMD: ${cmd.name}`); message.reply("There was an error trying to execute that command!"); diff --git a/src/handlers/loadCommands.js b/src/handlers/loadCommands.js index 02c3f30..7b4f184 100644 --- a/src/handlers/loadCommands.js +++ b/src/handlers/loadCommands.js @@ -106,7 +106,7 @@ module.exports = ({ const slashCommands = []; client.commands.forEach((cmd) => { - if ("data" in cmd && "interactionReply" in cmd) { + if ("data" in cmd && ("interactionReply" in cmd || "execute" in cmd)) { slashCommands.push(cmd.data.toJSON()); } }); diff --git a/src/index.d.ts b/src/index.d.ts index ad84271..b855951 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -286,43 +286,61 @@ interface CommandFile { /** * customIds for your interaction components * @example + * + * ```ts + * customIds: ["myModalId", "myModalId2"] + * ``` + * * ```ts * customIds: { * modal: "myModalId", * model2: "myModalId2", * } * ``` - * - * ```ts - * customIds: ["myModalId", "myModalId2"] - * ``` */ customIds?: | string[] | { [key: string]: string; }; + /** - * Executing a prefix command call for this command + * @summary Executes a prefix command call for this command. As discord.js shifts towards using interactions over prefix, this function is required to be implemented for prefix commands in next version of discord-features-handler. -- This is still useful outside of slash commands, as you can set up permission-based levels to ensure the command is for admins or bot admins/devs by using the permission levels. + * * @param message Message Object * @param args arguments provided with command call * @param client Client Object * @param level permission level of user */ - execute( + executePrefix?( message: Message, args?: string[], client?: Client, level?: number ): Promise; + /** + * @important this will still work for executing prefix commands, as long as data and interactionReply property are enabled. + * + * Executing a slash interaction command call + * @param interaction Interaction object + * @param client Client Object + * @param level permission level of user + * + * Since discord.js is moving towards using interactions over messages, this function is required to be implemented for slash commands in next version of discord-features-handler. + * @see {@link executePrefix} for prefix command execution + */ + execute( + interaction: CommandInteraction, + client?: Client, + level?: number + ): Promise; /** * Executing a slash interaction command call * @param interaction Interaction object * @param client Client Object * @param level permission level of user * - * @todo - * This function is used for slash commands, context menu commands, and user commands, and is required to be implemented in next version of discord-features-handler, as discord recommends using slash commands over prefix commands; You can still return an empty interaction reply and not implmeent this function and data property. + * @deprecated Please use {@link execute} instead and for prefix commands, use {@link executePrefix} */ interactionReply?( interaction: CommandInteraction, From e36d53e23d1bbf45391b7eea52ea1f142db2d5d9 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Sat, 16 Aug 2025 16:12:02 -0400 Subject: [PATCH 04/28] add executePrefix property to cmds and recommend conversion Add jsdoc to recommend use of executePrefix for prefix commands and use execute for slash commands following discord.js guidelines for slash commands --- mkdocs.yml | 54 ++++++++++++++---------------- src/events/dfhInteractionCreate.js | 11 +++++- src/events/dfhMessageCreate.js | 24 +++++++++++-- src/handlers/loadCommands.js | 2 +- src/index.d.ts | 34 ++++++++++++++----- 5 files changed, 85 insertions(+), 40 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index dab8189..bc2139e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,30 +2,30 @@ site_name: discord-features-handler site_author: Brandon Ng site_description: >- discord-features-handler npm package official documentation website -copyright: Copyright © 2022 - 2024 discord-features-handler +copyright: Copyright © 2022 - 2025 discord-features-handler nav: - Discord-Features-Handler: index.md - - Getting started: - - Installation: getting-started/installation.md - - Environment Variables: getting-started/environment-variables.md - - Folder Structure: getting-started/folder-structure.md - - Bot Script File: getting-started/Bot Script file.md - - TypeScript Support: getting-started/TypeScript Support.md - - Setup: - - DiscordFeaturesHandlerOptions: setup/DiscordFeaturesHandlerOptions.md - - config file: setup/config-file.md - - command files: setup/commands-file.md - - slash command files: setup/slash-commands-file.md - - other interactions: setup/other-interaction.md - - event files: setup/events-file.md - - modules file: setup/modules-files.md + - Getting started: + - Installation: getting-started/installation.md + - Environment Variables: getting-started/environment-variables.md + - Folder Structure: getting-started/folder-structure.md + - Bot Script File: getting-started/Bot Script file.md + - TypeScript Support: getting-started/TypeScript Support.md + - Setup: + - DiscordFeaturesHandlerOptions: setup/DiscordFeaturesHandlerOptions.md + - config file: setup/config-file.md + - command files: setup/commands-file.md + - slash command files: setup/slash-commands-file.md + - other interactions: setup/other-interaction.md + - event files: setup/events-file.md + - modules file: setup/modules-files.md - Built-in Features: - - messageCreate event: builtIn/messageCreate-event.md - - interactionCreate event: builtIn/interactionCreate-event.md - - help command: builtIn/help-command.md - - reload command: builtIn/reload-command.md - - functions: builtIn/functions.md - - disabling built in: builtIn/disabling-built-in-features.md + - messageCreate event: builtIn/messageCreate-event.md + - interactionCreate event: builtIn/interactionCreate-event.md + - help command: builtIn/help-command.md + - reload command: builtIn/reload-command.md + - functions: builtIn/functions.md + - disabling built in: builtIn/disabling-built-in-features.md - Demo: demo.md - About: about.md - Release Notes: release-notes.md @@ -53,11 +53,11 @@ theme: icon: material/weather-sunny name: Switch to dark mode primary: teal - accent: purple - - scheme: slate + accent: purple + - scheme: slate toggle: icon: material/moon-waning-crescent - name: Switch to light mode + name: Switch to light mode primary: teal accent: lime @@ -65,8 +65,6 @@ plugins: - social - search - - extra: analytics: provider: google @@ -96,6 +94,6 @@ markdown_extensions: - tables - attr_list - md_in_html - - pymdownx.emoji: + - pymdownx.emoji: emoji_index: !!python/name:material.extensions.emoji.twemoji - emoji_generator: !!python/name:material.extensions.emoji.to_svg \ No newline at end of file + emoji_generator: !!python/name:material.extensions.emoji.to_svg diff --git a/src/events/dfhInteractionCreate.js b/src/events/dfhInteractionCreate.js index 109eea0..f53c80c 100644 --- a/src/events/dfhInteractionCreate.js +++ b/src/events/dfhInteractionCreate.js @@ -29,7 +29,16 @@ module.exports = { console.log("[SLASH CMD]", "[ID]", commandId); try { - return cmd.interactionReply(interaction, client, level); + if ("execute" in cmd && !"interactionReply" in cmd) { + return cmd.execute(interaction, client, level); + } + return cmd + .interactionReply(interaction, client, level) + .then((reply) => { + console.warn( + "Please use execute property for slash commands as next version of discord-features-handler will use, 'executePrefix' property for prefix commands and deprecate interactionReply." + ); + }); } catch (e) { console.log( "ApplicationCommand interaction (slash command) execution failed", diff --git a/src/events/dfhMessageCreate.js b/src/events/dfhMessageCreate.js index cdce116..7798b4d 100644 --- a/src/events/dfhMessageCreate.js +++ b/src/events/dfhMessageCreate.js @@ -20,7 +20,7 @@ module.exports = { if (!prefix) return; - const args = message.content.slice(prefix.length).trim().split(/ +/); + const args = message.content.slice(prefix.length).trim().split(/\s+/); const command = args.shift().toLowerCase(); const cmd = client.commands.get(command) || @@ -77,7 +77,27 @@ module.exports = { } try { - cmd.execute(message, args, client, level); + if (cmd.executePrefix) { + return cmd.executePrefix(message, args, client, level); + } else { + return cmd + .execute(message, args, client, level) + .then((reply) => { + console.warn( + "Please use executePrefix property for prefix commands as next version of discord-features-handler will use, 'execute' property for slash commands only." + ); + }) + .catch((e) => { + console.error(e, `Executing CMD: ${cmd.name}`); + console.error( + "[log]", + "[Prefix CMDs]", + `Failed to execute prefix command: ${command}, please update to executePrefix property instead`, + e.message + ); + message.reply("There was an error trying to execute that command!"); + }); + } } catch (e) { console.error(e, `Executing CMD: ${cmd.name}`); message.reply("There was an error trying to execute that command!"); diff --git a/src/handlers/loadCommands.js b/src/handlers/loadCommands.js index 02c3f30..7b4f184 100644 --- a/src/handlers/loadCommands.js +++ b/src/handlers/loadCommands.js @@ -106,7 +106,7 @@ module.exports = ({ const slashCommands = []; client.commands.forEach((cmd) => { - if ("data" in cmd && "interactionReply" in cmd) { + if ("data" in cmd && ("interactionReply" in cmd || "execute" in cmd)) { slashCommands.push(cmd.data.toJSON()); } }); diff --git a/src/index.d.ts b/src/index.d.ts index ad84271..b855951 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -286,43 +286,61 @@ interface CommandFile { /** * customIds for your interaction components * @example + * + * ```ts + * customIds: ["myModalId", "myModalId2"] + * ``` + * * ```ts * customIds: { * modal: "myModalId", * model2: "myModalId2", * } * ``` - * - * ```ts - * customIds: ["myModalId", "myModalId2"] - * ``` */ customIds?: | string[] | { [key: string]: string; }; + /** - * Executing a prefix command call for this command + * @summary Executes a prefix command call for this command. As discord.js shifts towards using interactions over prefix, this function is required to be implemented for prefix commands in next version of discord-features-handler. -- This is still useful outside of slash commands, as you can set up permission-based levels to ensure the command is for admins or bot admins/devs by using the permission levels. + * * @param message Message Object * @param args arguments provided with command call * @param client Client Object * @param level permission level of user */ - execute( + executePrefix?( message: Message, args?: string[], client?: Client, level?: number ): Promise; + /** + * @important this will still work for executing prefix commands, as long as data and interactionReply property are enabled. + * + * Executing a slash interaction command call + * @param interaction Interaction object + * @param client Client Object + * @param level permission level of user + * + * Since discord.js is moving towards using interactions over messages, this function is required to be implemented for slash commands in next version of discord-features-handler. + * @see {@link executePrefix} for prefix command execution + */ + execute( + interaction: CommandInteraction, + client?: Client, + level?: number + ): Promise; /** * Executing a slash interaction command call * @param interaction Interaction object * @param client Client Object * @param level permission level of user * - * @todo - * This function is used for slash commands, context menu commands, and user commands, and is required to be implemented in next version of discord-features-handler, as discord recommends using slash commands over prefix commands; You can still return an empty interaction reply and not implmeent this function and data property. + * @deprecated Please use {@link execute} instead and for prefix commands, use {@link executePrefix} */ interactionReply?( interaction: CommandInteraction, From 09ed13d081387fdbafe261ab4ba265c4576e27bb Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Sat, 16 Aug 2025 16:59:03 -0400 Subject: [PATCH 05/28] updated doc for v3.1.0 --- docs/builtIn/messageCreate-event.md | 7 +++++-- src/events/dfhMessageCreate.js | 21 --------------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/docs/builtIn/messageCreate-event.md b/docs/builtIn/messageCreate-event.md index 3019e54..87f9d6e 100644 --- a/docs/builtIn/messageCreate-event.md +++ b/docs/builtIn/messageCreate-event.md @@ -4,6 +4,9 @@ This event file essentially handles your command, check the permission level set This is the built-in MessageCreate event that you can disable in DiscordFeaturesHandlerOptions and then can use to tailor to your bot if desired. +??? note "Using v3.1.0 or later and you decided to disable the built-in MessageCreate" + Please use executePrefix property instead of execute property for running prefix commands to avoid any conflicts in future. + ```javascript const { ChannelType, Events } = require("discord.js"); module.exports = { @@ -28,7 +31,7 @@ module.exports = { if (!prefix) return; - const args = message.content.slice(prefix.length).trim().split(/ +/); + const args = message.content.slice(prefix.length).trim().split(/\s+/); const command = args.shift().toLowerCase(); const cmd = client.commands.get(command) || @@ -84,7 +87,7 @@ module.exports = { } try { - cmd.execute(message, args, client, level); + return cmd.executePrefix(message, args, client, level); } catch (e) { console.error(e, `Executing CMD: ${cmd.name}`); message.reply("There was an error trying to execute that command!"); diff --git a/src/events/dfhMessageCreate.js b/src/events/dfhMessageCreate.js index 1edc3d0..7798b4d 100644 --- a/src/events/dfhMessageCreate.js +++ b/src/events/dfhMessageCreate.js @@ -98,27 +98,6 @@ module.exports = { message.reply("There was an error trying to execute that command!"); }); } - if (cmd.executePrefix) { - return cmd.executePrefix(message, args, client, level); - } else { - return cmd - .execute(message, args, client, level) - .then((reply) => { - console.warn( - "Please use executePrefix property for prefix commands as next version of discord-features-handler will use, 'execute' property for slash commands only." - ); - }) - .catch((e) => { - console.error(e, `Executing CMD: ${cmd.name}`); - console.error( - "[log]", - "[Prefix CMDs]", - `Failed to execute prefix command: ${command}, please update to executePrefix property instead`, - e.message - ); - message.reply("There was an error trying to execute that command!"); - }); - } } catch (e) { console.error(e, `Executing CMD: ${cmd.name}`); message.reply("There was an error trying to execute that command!"); From b3092497c4e17765163987ae5cd8dbd493daa378 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Sat, 16 Aug 2025 17:15:39 -0400 Subject: [PATCH 06/28] update release note for v3.1.0 --- docs/release-notes.md | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index b521e14..99497d5 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,18 +9,34 @@ hide: # Release Notes -## 3.0.0 - Latest Version -### Feature -- Add new optional options to the DiscordFeaturesHandlerOptions - - slashCommandIdsToDelete: Array of strings for deleting specific slash command ids, - - onSlashCommandsLoading: Object of boolean for enabling delete of slash commands before loading new ones -- CommandFile Properties update: Change `customIds` to Array of Strings of customIds or a Object of key values -- Removed `componentInteraction`, `autoCompleteInteraction` `contextMenuInteraction` `modalInteraction` from XommandFile peroperties and replaced with a unviersal one called: `customIdInteraction` that handles interaction based off `customIds` -- Add `customIdInteraction(interaction, client, level)` into CommandFile Properties to handle customIds interactions to ensure no button or other type of customIds are inactive if the bot application or collector run out on time. +## 3.1.0 – Latest Version -### Fix -- Issue where prefix commands doens't work +### Features +- Introduced a new `executePrefix` property for handling prefix commands. +- Enabled usage of `execute` property to run slash commands. +- Added console warnings when prefix commands use `execute` instead of `executePrefix`, recommending migration to the new property. +- Added console warnings when slash commands use `interactionReply` instead of `execute`, following discord.js guidelines. + +- +### Deprecation +- `interactionReply` is now deprecated. It will continue to work in v3.x, but logs a console warning. + Please migrate to `execute`, as `interactionReply` will be removed in v4.0.0. + +## 3.0.0 + +### Features +- Added new optional options to `DiscordFeaturesHandlerOptions`: + - `slashCommandIdsToDelete`: Array of strings for deleting specific slash command IDs. + - `onSlashCommandsLoading`: Object of booleans for enabling the deletion of slash commands before loading new ones. +- Updated `CommandFile` properties: + - Changed `customIds` to accept either an array of strings (`customIds`) or an object of key–value pairs. + - Removed `componentInteraction`, `autoCompleteInteraction`, `contextMenuInteraction`, and `modalInteraction`, replacing them with a single universal property: `customIdInteraction`. + This new property handles interactions based on `customIds`. + - Added `customIdInteraction(interaction, client, level)` to `CommandFile` properties to ensure that no button or other customId-based interaction becomes inactive if the bot application or collector times out. + +### Fixes +- Fixed an issue where prefix commands were not working. ## 2.2.0 From ef8fa1142de294b00d2b0c690f40da6ac0427893 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Sat, 16 Aug 2025 17:18:43 -0400 Subject: [PATCH 07/28] 3.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index efcbd99..6df30d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "discord-features-handler", - "version": "3.0.0", + "version": "3.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "discord-features-handler", - "version": "3.0.0", + "version": "3.1.0", "license": "MIT", "dependencies": { "discord.js": "^14.9.0" diff --git a/package.json b/package.json index fa88c64..2ad3081 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-features-handler", - "version": "3.0.0", + "version": "3.1.0", "description": "An simple discord regular and slash commands, events and modules handler with folder structure", "main": "src/index.js", "types": "src/index.d.ts", From f37386488e05d883bc796f6a9422b7ce89815064 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Sat, 16 Aug 2025 20:08:15 -0400 Subject: [PATCH 08/28] doc changes for v3.1.0 Switching interactionReply property to execute property for slash commands, focus on encourging slash commands as most guide uses .execute for slash --- README.md | 12 +- docs/builtIn/help-command.md | 707 +----------------------- docs/builtIn/interactionCreate-event.md | 40 +- docs/builtIn/messageCreate-event.md | 4 +- docs/builtIn/reload-command.md | 6 +- docs/index.md | 2 +- docs/release-notes.md | 4 +- docs/setup/commands-file.md | 28 +- docs/setup/other-interaction.md | 4 +- docs/setup/slash-commands-file.md | 27 +- mkdocs.yml | 5 +- src/commands/miscellaneous/dfhHelp.js | 4 +- test/commands/miscellaneous/ping.js | 4 +- 13 files changed, 82 insertions(+), 765 deletions(-) diff --git a/README.md b/README.md index 12b7eaa..ff841a6 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ The properties that are **required** to have when creating a command file Show by writing an example of how to execute the command using the command argument(s) in the command call - execute(message, args, client, level) + executePrefix(message, args, client, level) func "" This is a function that is invoked when the command is called to be executed. Parameters are `message` object, `arguments` array, `client` object, and user's permission level to run a command @@ -226,7 +226,7 @@ module.exports = { * @param {Client} client The discord client object * @param {number} level The permission level of the user who made the command call */ - execute(message, args, client, level) { + executePrefix(message, args, client, level) { return message.channel.send('Pong.'); }, }; @@ -249,7 +249,7 @@ The properties that are required when creating a command file for slash commands DiscordJS SlashCommandBuilder - interactionReply(interaction, client, level) + execute(interaction, client, level) func This is a function that is invoked when the slash command is called to be executed Parameters are `interaction`, `client` object, and `user's permission level`. @@ -282,7 +282,7 @@ module.exports = { * @param {Client} client The discord client object * @param {number} level The permission level of the user who made the command call */ - execute(message, args, client, level) { + executePrefix(message, args, client, level) { return message.channel.send({ content: 'Pong.'}); }, /** @@ -290,7 +290,7 @@ module.exports = { * @param {Client} client The discord client object * @param {number} level The permission level of the user who made the command call */ - async interactionReply(interaction, client, level) { + async execute(interaction, client, level) { await interaction.reply({ content: 'Pong!' }); @@ -390,7 +390,7 @@ You can read more about these and other built-in functions in the [official Docu If you found and bug and issues please [report the issue](https://github.com/bng94/discord-features-handler/issues) and provide steps to reproducible bugs/issues. ## Notes -This handler also allows you to follow the Discord.js guide with a few changes, such as we are using a JavaScript file instead of a JSON file for the `config` file, using the property `interactionReply` instead of `execute` property for slash commands, and without creating your own handler for loading commands and events file. Also loading your modules files that contains features of your bot. +This handler also allows you to follow the Discord.js guide with a few changes, such as we are using a JavaScript file instead of a JSON file for the `config` file and without creating your own handler for loading commands and events file. Also loading your modules files that contains features of your bot. ## Support and New Features This package is looking for feedback and ideas to help cover more use cases. If you have any ideas feel free to share them or even contribute to this package! Please first discuss the add-on or change you wish to make, in the repository. If you like this package, and want to see more add-on, please don't forget to give a star to the repository and provide some feedbacks! diff --git a/docs/builtIn/help-command.md b/docs/builtIn/help-command.md index b3941d6..1867805 100644 --- a/docs/builtIn/help-command.md +++ b/docs/builtIn/help-command.md @@ -7,7 +7,7 @@ This is the built-in help command, this will generate an embed with Buttons, and If you want to modify this built-in file you can disable it in DiscordFeaturesHandlerOptions and create the following help command file -```````javascript +```javascript /** * Display all commands based off the user's permission level defined in config.js */ @@ -27,7 +27,7 @@ module.exports = { minArgs: 0, maxArgs: 1, usage: "", - async execute(message, args, client) { + async executePrefix(message, args, client) { // using the built-in functions and get the permission level of user const level = client.getPermissionsLevel({ author: message.author, @@ -85,707 +85,9 @@ module.exports = { } }, /** - * This is can be used for slash help command if you choose to! + * This is can be used for slash help command if you choose to and adding a "data" property */ - async interactionReply(interaction, client, level) { - await interaction.deferReply(); - const { options } = interaction; - const name = options.getString("cmd_name"); - - const commands = await client.commands.filter( - (cmd) => cmd.permissions <= level - ); - const data = getSortedCommandArray(client, commands); - - if (!name) { - const embed = getInitialEmbed(data, client); - const row = getButtonRows(data); - - await interaction.editReply({ - embeds: [embed], - components: [row], - }); - - //display the command info requested from user's call - // Filter, ensures that the user who initial the help cmd call is changing the embed by their request - const filter = (i) => i.user.id === interaction.user.id; - // Create the message component for buttons to show on embeds - const collector = interaction.channel.createMessageComponentCollector({ - filter, - time: filterTime, - }); - //this awaits and collect responses from input of user and handle it. - collector.on("collect", async (i) => { - const newEmbed = getUpdateEmbed(data, i, client); - await i.update({ embeds: [newEmbed], components: [row] }); - }); - // handles after the collection event ended - // disable listening to btn inputs after filterTime expires - return collector.on("end", async (collected) => { - const lastRow = getButtonRows(data, true); - return await interaction.editReply({ components: [lastRow] }); - }); - } else { - //display the command info requested from user's call - const response = await getSingleCmd(commands, name, client); - return await interaction - .editReply(response) - .catch((error) => console.log(error)); - } - }, -}; -/** - * - * @param {Client} client discord client object - * @param {Array} commands all commands based off user's permission lvl - * formatted array of all commands categorized based off sub folder names - * @returns - */ -const getSortedCommandArray = (client, commands) => { - const dataArray = []; - const prefix = Array.isArray(client.config.prefix) - ? client.config.prefix[0] - : client.config.prefix; - const commandNames = commands.map((cmd) => cmd.name); - const longestName = commandNames.reduce(function (a, b) { - return a.length > b.length ? a : b; - }); - const sorted = commands.sort((p, c) => - p.category > c.category - ? 1 - : p.name > c.name && p.category === c.category - ? 1 - : -1 - ); - let category = ""; - let index = -1; - sorted.map((command) => { - let temp = { - category: "", - commands: [], - }; - if (!category || category != command.category) { - category = command.category; - temp = { ...temp, category }; - dataArray.push(temp); - } - - index = dataArray.findIndex((element) => element.category === category); - temp = dataArray[index]; - - temp.commands.push({ - name: `${prefix}${command.name}`, - description: `${command.description}`, - }); - dataArray[index] = temp; - }); - - return dataArray; -}; - -/** - * - * @param {Array} data the data to display on the embed - * @param {Client} client Discord client object - * @returns EmbedBuilder to display - */ -const getInitialEmbed = (data, client) => { - const categories = data.map((cat) => `**${cat.category}**`).join(`\n`); - - const defaultEmbed = new EmbedBuilder().setTitle("Help Menu").setAuthor({ - name: `${client.user.username} Help Menu`, - iconURL: `${client.user.avatarURL()}`, - }).setDescription(`There are ${data.length} categories!\n${categories} -Click the respective buttons to see the commands of the category. You have ${ - filterTime / 60000 - } minutes until buttons are disabled.`); - - return defaultEmbed; -}; - -/** - * - * @param {Array} data the data to display on the embed - * @param {Number} i index of category to show - * @param {Client} client Discord client object - * @returns EmbedBuilder to display - */ -const getUpdateEmbed = (data, i, client) => { - const index = data.findIndex((d) => d.category === i.customId); - const cmds = data[index].commands - .map((cmd) => { - let cmdName = cmd.name - .replace( - Array.isArray(client.config.prefix) - ? client.config.prefix[0] - : client.config.prefix, - "" - ) - .toProperCase(); - return `**${cmdName}**\n${cmd.description}\n`; - }) - .join("\n"); - - return new EmbedBuilder() - .setAuthor({ - name: `${client.user.username} Help Menu`, - iconURL: `${client.user.avatarURL()}`, - }) - .setTitle(`${data[index].category} Category`) - .setDescription(cmds) - .setFields({ - name: `To see a more details about a specific command type following and replace "name" with the command name:`, - value: `/help name or ${ - Array.isArray(client.config.prefix) - ? client.config.prefix[0] - : client.config.prefix - }help name`, - }); -}; - -/** - * - * @param {Array} data of all commands - * @param {Boolean} disabled the button once timer expires - * @returns different colors variations for the component - */ -const getButtonRows = (data, disabled = false) => { - const colorForCategory = [ - { - name: "admin", - color: ButtonStyle.Secondary, - }, - { - name: "commands", - color: ButtonStyle.Primary, - }, - { - name: "miscellaneous", - color: ButtonStyle.Success, - }, - { - name: "system", - color: ButtonStyle.Secondary, - }, - ]; - const defaultColor = ButtonStyle.Primary; - - const btnArray = data.map((res) => { - const catName = res.category; - - const index = colorForCategory.findIndex( - (colors) => colors.name === catName.toLowerCase() - ); - const style = index !== -1 ? colorForCategory[index].color : defaultColor; - - return new ButtonBuilder() - .setCustomId(catName) - .setLabel(catName) - .setStyle(style) - .setDisabled(disabled); - }); - - let row = new ActionRowBuilder(); - - if (btnArray.length > 0) { - btnArray.map((btn) => row.addComponents(btn)); - } - - return row; -}; - -/** - * - * @param {Array} commands listed for the users to see - * @param {string} name of the command to lookup - * @param {client} client Discord client object - * @returns information about the command requested to lookup - */ -const getSingleCmd = async (commands, name, client) => { - const prefix = Array.isArray(client.config.prefix) - ? client.config.prefix[0] - : client.config.prefix; - const command = await commands.find( - (cmd) => cmd.name === name || cmd.aliases === name - ); - - if (!command) { - return { - content: `The command, **${name}** - + does not exist!`, - }; - } - - const fieldObj = []; - const aliases = command.aliases.join(", "); - if (aliases.length !== 0) { - fieldObj.push({ - name: `Aliases:`, - value: `${aliases}`, - inline: true, - }); - } - fieldObj.push({ - name: `Category:`, - value: `${command.category}`, - inline: true, - }); - - if (command.usage.length !== 0) { - fieldObj.push({ - name: `Usage:`, - value: `${prefix}${command.name} ${command.usage}`, - }); - } - fieldObj.push({ - name: `Slash:`, - value: `${command.data ? `True` : `False`}`, - inline: true, - }); - try { - const embed = new EmbedBuilder() - .setAuthor({ - name: `${client.user.tag}`, - iconURL: `${client.user.avatarURL()}`, - }) - .setTitle(`${command.name.toProperCase()} Command`) - .setDescription(command.description) - .setTimestamp() - .setFields(fieldObj); - - return { embeds: [embed] }; - } catch (e) { - console.log(e); - } -}; - -``````javascript -/** - * Display all commands based off the user's permission level defined in config.js - */ -const { - ActionRowBuilder, - ButtonBuilder, - EmbedBuilder, - ButtonStyle, -} = require("discord.js"); -const filterTime = 60000; - -module.exports = { - name: "help", - description: "List all of my commands or info about a specific command.", - aliases: ["commands"], - permissions: 0, - minArgs: 0, - maxArgs: 1, - usage: "", - async execute(message, args, client) { - // using the built-in functions and get the permission level of user - const level = client.getPermissionsLevel({ - author: message.author, - channel: message.channel, - guild: message.guild, - guildMember: message.member, - }); - // filter the commands saved in new collection object - const commands = await client.commands.filter( - (cmd) => cmd.permissions <= level - ); - const data = getSortedCommandArray(client, commands); - - if (!args.length) { - // get embed with data and categorized all the commands displayed - const embed = getInitialEmbed(data, client); - //get rows of buttons based of cmds categories - const row = getButtonRows(data); - - //send initial message and await - message - .reply({ - embeds: [embed], - components: [row], - }) - .then((msg) => { - // after message was sent then listen... - // Filter, ensures that the user who initial the help cmd call is changing the embed by their request - const filter = (i) => i.user.id === message.author.id; - // Create the message component for buttons to show on embeds - const collector = message.channel.createMessageComponentCollector({ - filter, - time: filterTime, - }); - - //this awaits and collect responses from input of user and handle it. - collector.on("collect", async (i) => { - const newEmbed = getUpdateEmbed(data, i, client); - - await i.update({ embeds: [newEmbed], components: [row] }); - }); - - // handles after the collection event ended - // disable listening to btn inputs after filterTime expires - return collector.on("end", async (collected) => { - const lastRow = getButtonRows(data, true); - return await msg.edit({ components: [lastRow] }); - }); - }); - } else { - //display the command info requested from user's call - const name = args[0].toLowerCase(); - const response = await getSingleCmd(commands, name, client); - return message.reply(response).catch((error) => console.log(error)); - } - }, - async interactionReply(interaction, client, level) { - await interaction.deferReply(); - const { options } = interaction; - const name = options.getString("cmd_name"); - - const commands = await client.commands.filter( - (cmd) => cmd.permissions <= level - ); - const data = getSortedCommandArray(client, commands); - - if (!name) { - const embed = getInitialEmbed(data, client); - const row = getButtonRows(data); - - await interaction.editReply({ - embeds: [embed], - components: [row], - }); - - //display the command info requested from user's call - // Filter, ensures that the user who initial the help cmd call is changing the embed by their request - const filter = (i) => i.user.id === interaction.user.id; - // Create the message component for buttons to show on embeds - const collector = interaction.channel.createMessageComponentCollector({ - filter, - time: filterTime, - }); - //this awaits and collect responses from input of user and handle it. - collector.on("collect", async (i) => { - const newEmbed = getUpdateEmbed(data, i, client); - await i.update({ embeds: [newEmbed], components: [row] }); - }); - // handles after the collection event ended - // disable listening to btn inputs after filterTime expires - return collector.on("end", async (collected) => { - const lastRow = getButtonRows(data, true); - return await interaction.editReply({ components: [lastRow] }); - }); - } else { - //display the command info requested from user's call - const response = await getSingleCmd(commands, name, client); - return await interaction - .editReply(response) - .catch((error) => console.log(error)); - } - }, -}; -/** - * - * @param {Client} client discord client object - * @param {Array} commands all commands based off user's permission lvl - * formatted array of all commands categorized based off sub folder names - * @returns - */ -const getSortedCommandArray = (client, commands) => { - const dataArray = []; - const prefix = Array.isArray(client.config.prefix) - ? client.config.prefix[0] - : client.config.prefix; - const commandNames = commands.map((cmd) => cmd.name); - const longestName = commandNames.reduce(function (a, b) { - return a.length > b.length ? a : b; - }); - const sorted = commands.sort((p, c) => - p.category > c.category - ? 1 - : p.name > c.name && p.category === c.category - ? 1 - : -1 - ); - let category = ""; - let index = -1; - sorted.map((command) => { - let temp = { - category: "", - commands: [], - }; - if (!category || category != command.category) { - category = command.category; - temp = { ...temp, category }; - dataArray.push(temp); - } - - index = dataArray.findIndex((element) => element.category === category); - temp = dataArray[index]; - - temp.commands.push({ - name: `${prefix}${command.name}`, - description: `${command.description}`, - }); - dataArray[index] = temp; - }); - - return dataArray; -}; - -/** - * - * @param {Array} data the data to display on the embed - * @param {Client} client Discord client object - * @returns EmbedBuilder to display - */ -const getInitialEmbed = (data, client) => { - const categories = data.map((cat) => `**${cat.category}**`).join(`\n`); - - const defaultEmbed = new EmbedBuilder().setTitle("Help Menu").setAuthor({ - name: `${client.user.username} Help Menu`, - iconURL: `${client.user.avatarURL()}`, - }).setDescription(`There are ${data.length} categories!\n${categories} -Click the respective buttons to see the commands of the category. You have ${ - filterTime / 60000 - } minutes until buttons are disabled.`); - - return defaultEmbed; -}; - -/** - * - * @param {Array} data the data to display on the embed - * @param {Number} i index of category to show - * @param {Client} client Discord client object - * @returns EmbedBuilder to display - */ -const getUpdateEmbed = (data, i, client) => { - const index = data.findIndex((d) => d.category === i.customId); - const cmds = data[index].commands - .map((cmd) => { - let cmdName = cmd.name - .replace( - Array.isArray(client.config.prefix) - ? client.config.prefix[0] - : client.config.prefix, - "" - ) - .toProperCase(); - return `**${cmdName}**\n${cmd.description}\n`; - }) - .join("\n"); - - return new EmbedBuilder() - .setAuthor({ - name: `${client.user.username} Help Menu`, - iconURL: `${client.user.avatarURL()}`, - }) - .setTitle(`${data[index].category} Category`) - .setDescription(cmds) - .setFields({ - name: `To see a more details about a specific command type following and replace "name" with the command name:`, - value: `/help name or ${ - Array.isArray(client.config.prefix) - ? client.config.prefix[0] - : client.config.prefix - }help name`, - }); -}; - -/** - * - * @param {Array} data of all commands - * @param {Boolean} disabled the button once timer expires - * @returns different colors variations for the component - */ -const getButtonRows = (data, disabled = false) => { - const colorForCategory = [ - { - name: "admin", - color: ButtonStyle.Secondary, - }, - { - name: "commands", - color: ButtonStyle.Primary, - }, - { - name: "miscellaneous", - color: ButtonStyle.Success, - }, - { - name: "system", - color: ButtonStyle.Secondary, - }, - ]; - const defaultColor = ButtonStyle.Primary; - - const btnArray = data.map((res) => { - const catName = res.category; - - const index = colorForCategory.findIndex( - (colors) => colors.name === catName.toLowerCase() - ); - const style = index !== -1 ? colorForCategory[index].color : defaultColor; - - return new ButtonBuilder() - .setCustomId(catName) - .setLabel(catName) - .setStyle(style) - .setDisabled(disabled); - }); - - let row = new ActionRowBuilder(); - - if (btnArray.length > 0) { - btnArray.map((btn) => row.addComponents(btn)); - } - - return row; -}; - -/** - * - * @param {Array} commands listed for the users to see - * @param {string} name of the command to lookup - * @param {client} client Discord client object - * @returns information about the command requested to lookup - */ -const getSingleCmd = async (commands, name, client) => { - const prefix = Array.isArray(client.config.prefix) - ? client.config.prefix[0] - : client.config.prefix; - const command = await commands.find( - (cmd) => cmd.name === name || cmd.aliases === name - ); - - if (!command) { - return { - content: `The command, **${name}** - + does not exist!`, - }; - } - - const fieldObj = []; - const aliases = command.aliases.join(", "); - if (aliases.length !== 0) { - fieldObj.push({ - name: `Aliases:`, - value: `${aliases}`, - inline: true, - }); - } - fieldObj.push({ - name: `Category:`, - value: `${command.category}`, - inline: true, - }); - - if (command.usage.length !== 0) { - fieldObj.push({ - name: `Usage:`, - value: `${prefix}${command.name} ${command.usage}`, - }); - } - fieldObj.push({ - name: `Slash:`, - value: `${command.data ? `True` : `False`}`, - inline: true, - }); - try { - const embed = new EmbedBuilder() - .setAuthor({ - name: `${client.user.tag}`, - iconURL: `${client.user.avatarURL()}`, - }) - .setTitle(`${command.name.toProperCase()} Command`) - .setDescription(command.description) - .setTimestamp() - .setFields(fieldObj); - - return { embeds: [embed] }; - } catch (e) { - console.log(e); - } -}; - -``````javascript -/** - * Display all commands based off the user's permission level defined in config.js - */ -const { - ActionRowBuilder, - ButtonBuilder, - EmbedBuilder, - ButtonStyle, -} = require("discord.js"); -const filterTime = 60000; - -module.exports = { - name: "help", - description: "List all of my commands or info about a specific command.", - aliases: ["commands"], - permissions: 0, - minArgs: 0, - maxArgs: 1, - usage: "", - async execute(message, args, client) { - // using the built-in functions and get the permission level of user - const level = client.getPermissionsLevel({ - author: message.author, - channel: message.channel, - guild: message.guild, - guildMember: message.member, - }); - // filter the commands saved in new collection object - const commands = await client.commands.filter( - (cmd) => cmd.permissions <= level - ); - const data = getSortedCommandArray(client, commands); - - if (!args.length) { - // get embed with data and categorized all the commands displayed - const embed = getInitialEmbed(data, client); - //get rows of buttons based of cmds categories - const row = getButtonRows(data); - - //send initial message and await - message - .reply({ - embeds: [embed], - components: [row], - }) - .then((msg) => { - // after message was sent then listen... - // Filter, ensures that the user who initial the help cmd call is changing the embed by their request - const filter = (i) => i.user.id === message.author.id; - // Create the message component for buttons to show on embeds - const collector = message.channel.createMessageComponentCollector({ - filter, - time: filterTime, - }); - - //this awaits and collect responses from input of user and handle it. - collector.on("collect", async (i) => { - const newEmbed = getUpdateEmbed(data, i, client); - - await i.update({ embeds: [newEmbed], components: [row] }); - }); - - // handles after the collection event ended - // disable listening to btn inputs after filterTime expires - return collector.on("end", async (collected) => { - const lastRow = getButtonRows(data, true); - return await msg.edit({ components: [lastRow] }); - }); - }); - } else { - //display the command info requested from user's call - const name = args[0].toLowerCase(); - const response = await getSingleCmd(commands, name, client); - return message.reply(response).catch((error) => console.log(error)); - } - }, - async interactionReply(interaction, client, level) { + async execute(interaction, client, level) { await interaction.deferReply(); const { options } = interaction; const name = options.getString("cmd_name"); @@ -1058,4 +360,3 @@ const getSingleCmd = async (commands, name, client) => { }; ``` -``````` diff --git a/docs/builtIn/interactionCreate-event.md b/docs/builtIn/interactionCreate-event.md index e2aabd5..5495730 100644 --- a/docs/builtIn/interactionCreate-event.md +++ b/docs/builtIn/interactionCreate-event.md @@ -2,8 +2,12 @@ This event handles the creation execution of the slash commands, using the built-in slash command executing from the slash properties of a command file and also using the built-in `client.commands` Collection object. We will also display the slash command Id, incase you decide to delete a slash command. +!!! note "Using v3.1.0 or later and you decided to disable the built-in IntercationCreate" + Please use `execute` property instead of `interactionReply` property for running slash commands and make sure for the prefix version of the command use `executePrefix` property if you choose to use a prefix version of the command. + + ```javascript -const { InteractionType, Events } = require("discord.js");const { InteractionType, Events } = require("discord.js"); +const { InteractionType, Events } = require("discord.js"); module.exports = { name: Events.InteractionCreate, async execute(interaction, client) { @@ -34,7 +38,7 @@ module.exports = { console.log("[SLASH CMD]", "[ID]", commandId); try { - return cmd.interactionReply(interaction, client, level); + return cmd.execute(interaction, client, level); } catch (e) { console.log( "ApplicationCommand interaction (slash command) execution failed", @@ -47,19 +51,6 @@ module.exports = { }); } } - // if (interaction.isUserContextMenuCommand()) { - // const cmd = client.commands.get(commandName); - // if (!cmd) { - // return console.error("Unable to find context menu command:" + cmd); - // } - // console.log( - // `[CONTEXT MENU CMD]`, - // `[${interaction.user.tag}]`, - // `${commandName} ID: ${commandId}`, - // commandId - // ); - // cmd.contextMenuInteraction(interaction, client, level); - // } const foundCmd = client.commands.find((cmd) => { if (!cmd.customIds) return false; @@ -75,7 +66,7 @@ module.exports = { return false; }); - if (!foundCmd) { + if (!foundCmd || !foundCmd.name) { console.error("Unable to find command with customId: " + customId); return interaction.reply({ @@ -102,8 +93,17 @@ module.exports = { `[${interaction.user.tag}]`, `${foundCmd.name} CustomID: ${customId}` ); - - return foundCmd.customIdInteraction(interaction, client, level); + try { + return foundCmd.customIdInteraction(interaction, client, level); + } catch (e) { + console.error( + `Error executing customIdInteraction for command "${foundCmd.name}":`, + e + ); + return interaction.reply({ + content: `An error occurred while processing your interaction: ${foundCmd.name} and customId: ${customId}. Error: ${e}`, + }); + } } catch (e) { console.log("Interaction execution failed", e); return interaction.reply({ @@ -113,7 +113,5 @@ module.exports = { }, }; -``` - - +``` \ No newline at end of file diff --git a/docs/builtIn/messageCreate-event.md b/docs/builtIn/messageCreate-event.md index 87f9d6e..2ac882d 100644 --- a/docs/builtIn/messageCreate-event.md +++ b/docs/builtIn/messageCreate-event.md @@ -4,8 +4,8 @@ This event file essentially handles your command, check the permission level set This is the built-in MessageCreate event that you can disable in DiscordFeaturesHandlerOptions and then can use to tailor to your bot if desired. -??? note "Using v3.1.0 or later and you decided to disable the built-in MessageCreate" - Please use executePrefix property instead of execute property for running prefix commands to avoid any conflicts in future. +!!! note "Using v3.1.0 or later and you decided to disable the built-in MessageCreate" + Please use `executePrefix` property instead of `execute` property for running prefix commands to avoid any conflicts in future. ```javascript const { ChannelType, Events } = require("discord.js"); diff --git a/docs/builtIn/reload-command.md b/docs/builtIn/reload-command.md index 565682b..4b6b682 100644 --- a/docs/builtIn/reload-command.md +++ b/docs/builtIn/reload-command.md @@ -20,7 +20,7 @@ module.exports = { minArgs: 1, maxArgs: 1, usage: "", - async execute(message, args, client) { + async executePrefix(message, args, client) { const commandName = args[0]; let command; @@ -69,6 +69,4 @@ module.exports = { message.reply(`The command \`${commandName}\` has been reloaded`); }, }; -``` - -Check back later for update! +``` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index bc8fc48..b2ebf61 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,7 +17,7 @@ DiscordFeaturesHandler is a handler for Discord.js commands, slash commands, and
-**discord-features-handler** allows you to follow the Discord.js guide with a few changes, such as using a JavaScript file instead of a JSON file for the `config` file, using the `interactionReply` function instead of `execute` for slash commands, and without creating your own handler for loading commands and events file. This package also supports TypeScript natively, so you can create your bot in JavaScript or TypeScript, based on your preferences. +**discord-features-handler** allows you to follow the Discord.js guide with a few changes, such as using a JavaScript file instead of a JSON file for the `config` file without creating your own handler for loading commands and events file. This package also supports TypeScript natively, so you can create your bot in JavaScript or TypeScript, based on your preferences. Some key Features are: diff --git a/docs/release-notes.md b/docs/release-notes.md index 99497d5..a0dbc01 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -16,9 +16,7 @@ hide: - Introduced a new `executePrefix` property for handling prefix commands. - Enabled usage of `execute` property to run slash commands. - Added console warnings when prefix commands use `execute` instead of `executePrefix`, recommending migration to the new property. -- Added console warnings when slash commands use `interactionReply` instead of `execute`, following discord.js guidelines. - -- +- Added console warnings when slash commands use `interactionReply` instead of `execute`, following discord.js guides and as Discord is encouraging developers to transition to using slash commands over traditional prefix commands for bots. ### Deprecation - `interactionReply` is now deprecated. It will continue to work in v3.x, but logs a console warning. Please migrate to `execute`, as `interactionReply` will be removed in v4.0.0. diff --git a/docs/setup/commands-file.md b/docs/setup/commands-file.md index 3b4685d..7d3aaf1 100644 --- a/docs/setup/commands-file.md +++ b/docs/setup/commands-file.md @@ -4,7 +4,7 @@ Follow the folder structure and create sub folders inside your command folder. N Here is a sample command example with the filename of "ping.js" and it's the command properties: -```javascript +```javascript module.exports = { name: 'ping', //name of command when using ping description: 'Ping Pong Command!', // description of command @@ -21,13 +21,17 @@ module.exports = { permissions: 0, minArgs: 0, // minimum arguments required to execute command usage: '', // example of how to use / call the command - execute(message, args, client) { // function named execute; define what the command does + /** + * Defines what the prefix command does, + * + * note as of v3.1.0, execute property has backward compatibility to run prefix commands until v4.0.0 + */ + executePrefix(message, args, client) { return message.channel.send({ content: 'Pong.'}); }, }; ``` -## Properties

name string
@@ -78,11 +82,21 @@ This is the maximum arguments required to execute the command Show by writing an example of how to execute the command using the command argument(s) in the command call Example: !ping

-

- execute(message, args, client, level) +

+ executePrefix(message, args, client, level) Promise<Message>
- This is a function that is invoked when the command is called to be executed -

+ This is a function that is invoked when the prefix command is called to be executed. + +??? warning "Original `execute` property in v3.1.0 or later" + If both the `interactionReply` and `data` properties are defined for the same command, the `execute` property will still run prefix commands. This behavior is provided for backward compatibility and will be removed in v4.0.0. + + However, if the `data` property is defined and the `interactionReply` property is not, the `execute` property will run as a slash command instead. In this case, `executePrefix` is required for prefix commands. This will be the expected behavior from v4.0.0 and later. + + + +
+ + diff --git a/docs/setup/other-interaction.md b/docs/setup/other-interaction.md index f1d2876..9453c69 100644 --- a/docs/setup/other-interaction.md +++ b/docs/setup/other-interaction.md @@ -40,10 +40,10 @@ module.exports = { secondButton: 'secondBtnId', }, usage: '', - execute(message, args, client) { + executePrefix(message, args, client) { return message.channel.send({ content: 'Pong.'}); }, - async interactionReply(interaction, client, level){ + async execute(interaction, client, level){ await interaction.deferReply({ ephemeral: true, }); diff --git a/docs/setup/slash-commands-file.md b/docs/setup/slash-commands-file.md index ba9d5fa..1eb6dee 100644 --- a/docs/setup/slash-commands-file.md +++ b/docs/setup/slash-commands-file.md @@ -1,11 +1,11 @@ # Setting up Slash Commands -You will need to follow properties to create a regular command and the following data, and interactionReply properties to create a slash command. +You will need to follow properties to create a regular command and the following `data` and `execute` properties to create a slash command. ```javascript data: new SlashCommandBuilder(), -async interactionReply(interaction) {}, +async execute(interaction) {}, ``` Here is a sample slash command created from our previous ping command: @@ -21,13 +21,13 @@ module.exports = { permissions: 0, minArgs: 0, usage: '', - data: new SlashCommandBuilder() + data: new SlashCommandBuilder() .setName("ping") .setDescription("Ping Pong Command"), - execute(message, args, client) { + executePrefix(message, args, client) { return message.channel.send('Pong.'); }, - async interactionReply(interaction) { + async execute(interaction) { await interaction.reply({ content: 'Pong!' }); @@ -40,14 +40,21 @@ module.exports = {

dataSlashCommandBuilder
- This is where you define the properties of the slash command using the SlashCommandBuilder Class. You can follow the official Discord.js guide or builders guide, however, we are using interactionReply and not using execute for the slash command functionality.
+ This is where you define the properties of the slash command using the SlashCommandBuilder Class. You can also follow the official Discord.js guide.

-

- interactionReply(interaction, client, level) +

+ execute(interaction, client, level) Promise<Interaction>
- This is a function that is invoked when the command is called to be executed. -

+ This is a function that is invoked when the slash command is called to be executed. + +??? warning "Original `interactionReply` property in v3.1.0 or later" + If both the `interactionReply` and `data` properties are defined for the same command, the `execute` property will still run prefix commands. This is provided for backward compatibility and will be removed in v4.0.0. + + However, if the `data` property is defined and the `interactionReply` property is not, then it will execute as a slash command as expected in v4.0.0 and later. + +
+ | Property | Type | Required | Description | |---------------|-----------------------------------------------------------------------------------------------------------|----------|--------------------------------------------------------------| diff --git a/mkdocs.yml b/mkdocs.yml index bc2139e..26c8a0d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -64,6 +64,7 @@ theme: plugins: - social - search + - tags extra: analytics: @@ -88,12 +89,12 @@ markdown_extensions: - pymdownx.inlinehilite - pymdownx.snippets - footnotes + - attr_list + - md_in_html - pymdownx.superfences - pymdownx.tabbed: alternate_style: true - tables - - attr_list - - md_in_html - pymdownx.emoji: emoji_index: !!python/name:material.extensions.emoji.twemoji emoji_generator: !!python/name:material.extensions.emoji.to_svg diff --git a/src/commands/miscellaneous/dfhHelp.js b/src/commands/miscellaneous/dfhHelp.js index 44c37e5..80e9221 100644 --- a/src/commands/miscellaneous/dfhHelp.js +++ b/src/commands/miscellaneous/dfhHelp.js @@ -17,7 +17,7 @@ module.exports = { minArgs: 0, maxArgs: 1, usage: "", - async execute(message, args, client) { + async executePrefix(message, args, client) { // using the built-in functions and get the permission level of user const level = client.getPermissionsLevel({ author: message.author, @@ -74,7 +74,7 @@ module.exports = { return message.reply(response).catch((error) => console.log(error)); } }, - async interactionReply(interaction, client, level) { + async execute(interaction, client, level) { await interaction.deferReply(); const { options } = interaction; const name = options.getString("cmd_name"); diff --git a/test/commands/miscellaneous/ping.js b/test/commands/miscellaneous/ping.js index 1c75600..0592f78 100644 --- a/test/commands/miscellaneous/ping.js +++ b/test/commands/miscellaneous/ping.js @@ -6,7 +6,7 @@ module.exports = { permissions: 0, minArgs: 0, usage: "", - execute(message, args, client, level) { - return message.channel.send('Pong!') + executePrefix(message, args, client, level) { + return message.channel.send("Pong!"); }, }; From 665015135acd584237f91a6801f7acb878d76af8 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Sat, 16 Aug 2025 20:12:52 -0400 Subject: [PATCH 09/28] Update help-command.md --- docs/builtIn/help-command.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/builtIn/help-command.md b/docs/builtIn/help-command.md index 1867805..f1196ba 100644 --- a/docs/builtIn/help-command.md +++ b/docs/builtIn/help-command.md @@ -87,7 +87,7 @@ module.exports = { /** * This is can be used for slash help command if you choose to and adding a "data" property */ - async execute(interaction, client, level) { + async executePrefix(interaction, client, level) { await interaction.deferReply(); const { options } = interaction; const name = options.getString("cmd_name"); From 09671de107c9d85815137dc3202f82fcaeaa6350 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Sat, 16 Aug 2025 20:16:17 -0400 Subject: [PATCH 10/28] updated to latest discordjs --- package-lock.json | 268 ++++++++++++++++++++++++++-------------------- package.json | 2 +- 2 files changed, 154 insertions(+), 116 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6df30d1..49f2f7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "3.1.0", "license": "MIT", "dependencies": { - "discord.js": "^14.9.0" + "discord.js": "^14.21.0" }, "devDependencies": { "dotenv": "^16.4.5", @@ -17,20 +17,24 @@ } }, "node_modules/@discordjs/builders": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.7.0.tgz", - "integrity": "sha512-GDtbKMkg433cOZur8Dv6c25EHxduNIBsxeHrsRoIM8+AwmEZ8r0tEpckx/sHwTLwQPOF3e2JWloZh9ofCaMfAw==", + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.11.3.tgz", + "integrity": "sha512-p3kf5eV49CJiRTfhtutUCeivSyQ/l2JlKodW1ZquRwwvlOWmG9+6jFShX6x8rUiYhnP6wKI96rgN/SXMy5e5aw==", + "license": "Apache-2.0", "dependencies": { - "@discordjs/formatters": "^0.3.3", - "@discordjs/util": "^1.0.2", - "@sapphire/shapeshift": "^3.9.3", - "discord-api-types": "0.37.61", + "@discordjs/formatters": "^0.6.1", + "@discordjs/util": "^1.1.1", + "@sapphire/shapeshift": "^4.0.0", + "discord-api-types": "^0.38.16", "fast-deep-equal": "^3.1.3", - "ts-mixer": "^6.0.3", - "tslib": "^2.6.2" + "ts-mixer": "^6.0.4", + "tslib": "^2.6.3" }, "engines": { "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, "node_modules/@discordjs/collection": { @@ -42,99 +46,117 @@ } }, "node_modules/@discordjs/formatters": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.3.tgz", - "integrity": "sha512-wTcI1Q5cps1eSGhl6+6AzzZkBBlVrBdc9IUhJbijRgVjCNIIIZPgqnUj3ntFODsHrdbGU8BEG9XmDQmgEEYn3w==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.1.tgz", + "integrity": "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg==", + "license": "Apache-2.0", "dependencies": { - "discord-api-types": "0.37.61" + "discord-api-types": "^0.38.1" }, "engines": { "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, "node_modules/@discordjs/rest": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.2.0.tgz", - "integrity": "sha512-nXm9wT8oqrYFRMEqTXQx9DUTeEtXUDMmnUKIhZn6O2EeDY9VCdwj23XCPq7fkqMPKdF7ldAfeVKyxxFdbZl59A==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.5.1.tgz", + "integrity": "sha512-Tg9840IneBcbrAjcGaQzHUJWFNq1MMWZjTdjJ0WS/89IffaNKc++iOvffucPxQTF/gviO9+9r8kEPea1X5J2Dw==", + "license": "Apache-2.0", "dependencies": { - "@discordjs/collection": "^2.0.0", - "@discordjs/util": "^1.0.2", - "@sapphire/async-queue": "^1.5.0", - "@sapphire/snowflake": "^3.5.1", - "@vladfrangu/async_event_emitter": "^2.2.2", - "discord-api-types": "0.37.61", - "magic-bytes.js": "^1.5.0", - "tslib": "^2.6.2", - "undici": "5.27.2" + "@discordjs/collection": "^2.1.1", + "@discordjs/util": "^1.1.1", + "@sapphire/async-queue": "^1.5.3", + "@sapphire/snowflake": "^3.5.3", + "@vladfrangu/async_event_emitter": "^2.4.6", + "discord-api-types": "^0.38.1", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.3" }, "engines": { - "node": ">=16.11.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.0.0.tgz", - "integrity": "sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", "engines": { "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, "node_modules/@discordjs/util": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.0.2.tgz", - "integrity": "sha512-IRNbimrmfb75GMNEjyznqM1tkI7HrZOf14njX7tCAAUetyZM1Pr8hX/EK2lxBCOgWDRmigbp24fD1hdMfQK5lw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz", + "integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==", + "license": "Apache-2.0", "engines": { - "node": ">=16.11.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, "node_modules/@discordjs/ws": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.0.2.tgz", - "integrity": "sha512-+XI82Rm2hKnFwAySXEep4A7Kfoowt6weO6381jgW+wVdTpMS/56qCvoXyFRY0slcv7c/U8My2PwIB2/wEaAh7Q==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", + "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", + "license": "Apache-2.0", "dependencies": { - "@discordjs/collection": "^2.0.0", - "@discordjs/rest": "^2.1.0", - "@discordjs/util": "^1.0.2", - "@sapphire/async-queue": "^1.5.0", - "@types/ws": "^8.5.9", - "@vladfrangu/async_event_emitter": "^2.2.2", - "discord-api-types": "0.37.61", + "@discordjs/collection": "^2.1.0", + "@discordjs/rest": "^2.5.1", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@types/ws": "^8.5.10", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "^0.38.1", "tslib": "^2.6.2", - "ws": "^8.14.2" + "ws": "^8.17.0" }, "engines": { "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.0.0.tgz", - "integrity": "sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==", - "engines": { - "node": ">=18" - } - }, - "node_modules/@fastify/busboy": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", "engines": { - "node": ">=14" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, "node_modules/@sapphire/async-queue": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.2.tgz", - "integrity": "sha512-7X7FFAA4DngXUl95+hYbUF19bp1LGiffjJtu7ygrZrbdCSsdDDBaSjB7Akw0ZbOu6k0xpXyljnJ6/RZUvLfRdg==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", + "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", + "license": "MIT", "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" } }, "node_modules/@sapphire/shapeshift": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.7.tgz", - "integrity": "sha512-4It2mxPSr4OGn4HSQWGmhFMsNFGfFVhWeRPCRwbH972Ek2pzfGRZtb0pJ4Ze6oIzcyh2jw7nUDa6qGlWofgd9g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", + "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" @@ -144,66 +166,77 @@ } }, "node_modules/@sapphire/snowflake": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.1.tgz", - "integrity": "sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", + "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "license": "MIT", "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" } }, "node_modules/@types/node": { - "version": "20.12.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", - "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", + "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", + "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~7.10.0" } }, "node_modules/@types/ws": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz", - "integrity": "sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@vladfrangu/async_event_emitter": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.4.tgz", - "integrity": "sha512-ButUPz9E9cXMLgvAW8aLAKKJJsPu1dY1/l/E8xzLFuysowXygs6GBcyunK9rnGC4zTsnIc2mQo71rGw9U+Ykug==", + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz", + "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==", + "license": "MIT", "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" } }, "node_modules/discord-api-types": { - "version": "0.37.61", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz", - "integrity": "sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==" + "version": "0.38.20", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.20.tgz", + "integrity": "sha512-wJSmFFi8eoFL/jIosUQLoXeCv7YK+l7joKmFCsnkx7HWSFt5xScNQdhvILLxC0oU6J5bK0ppR7GZ1d4NJScSNQ==", + "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] }, "node_modules/discord.js": { - "version": "14.14.1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.14.1.tgz", - "integrity": "sha512-/hUVzkIerxKHyRKopJy5xejp4MYKDPTszAnpYxzVVv4qJYf+Tkt+jnT2N29PIPschicaEEpXwF2ARrTYHYwQ5w==", + "version": "14.21.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.21.0.tgz", + "integrity": "sha512-U5w41cEmcnSfwKYlLv5RJjB8Joa+QJyRwIJz5i/eg+v2Qvv6EYpCRhN9I2Rlf0900LuqSDg8edakUATrDZQncQ==", + "license": "Apache-2.0", "dependencies": { - "@discordjs/builders": "^1.7.0", + "@discordjs/builders": "^1.11.2", "@discordjs/collection": "1.5.3", - "@discordjs/formatters": "^0.3.3", - "@discordjs/rest": "^2.1.0", - "@discordjs/util": "^1.0.2", - "@discordjs/ws": "^1.0.2", - "@sapphire/snowflake": "3.5.1", - "@types/ws": "8.5.9", - "discord-api-types": "0.37.61", + "@discordjs/formatters": "^0.6.1", + "@discordjs/rest": "^2.5.1", + "@discordjs/util": "^1.1.1", + "@discordjs/ws": "^1.2.3", + "@sapphire/snowflake": "3.5.3", + "discord-api-types": "^0.38.1", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", - "tslib": "2.6.2", - "undici": "5.27.2", - "ws": "8.14.2" + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.3" }, "engines": { - "node": ">=16.11.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, "node_modules/dotenv": { @@ -221,12 +254,14 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" }, "node_modules/lodash.snakecase": { "version": "4.1.1", @@ -234,19 +269,22 @@ "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==" }, "node_modules/magic-bytes.js": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", - "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==" + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz", + "integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==", + "license": "MIT" }, "node_modules/ts-mixer": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", - "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==" + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "license": "MIT" }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/typescript": { "version": "5.4.5", @@ -262,25 +300,25 @@ } }, "node_modules/undici": { - "version": "5.27.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.27.2.tgz", - "integrity": "sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "license": "MIT", "engines": { - "node": ">=14.0" + "node": ">=18.17" } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "license": "MIT" }, "node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 2ad3081..a61d6ad 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ }, "homepage": "https://bng94.github.io/discord-features-handler/", "dependencies": { - "discord.js": "^14.9.0" + "discord.js": "^14.21.0" }, "devDependencies": { "dotenv": "^16.4.5", From 39ac82391361573c75ee404198858b1be7ea14a8 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Sun, 17 Aug 2025 08:37:53 -0400 Subject: [PATCH 11/28] Make selective Command Properties optional in v3.1 --- docs/release-notes.md | 12 ++++ docs/setup/commands-file.md | 36 ++++------- docs/setup/slash-commands-file.md | 29 +++++---- mkdocs.yml | 24 +++---- src/utils/clientUtils.js | 103 +++++++++++++++++++++--------- 5 files changed, 127 insertions(+), 77 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index a0dbc01..27ebdf2 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -17,6 +17,18 @@ hide: - Enabled usage of `execute` property to run slash commands. - Added console warnings when prefix commands use `execute` instead of `executePrefix`, recommending migration to the new property. - Added console warnings when slash commands use `interactionReply` instead of `execute`, following discord.js guides and as Discord is encouraging developers to transition to using slash commands over traditional prefix commands for bots. +- Following Command Properties are now optional: + - `name` - slash commands only + - `description` - slash commands only + - `aliases` + - `minArgs` + - `permissions` + - `usage` + +- Recommended name changes to Command Properties: + - For executing prefix commands: `execute` → `executePrefix` + - For executing slash commands: `interactionReply` → `execute` + - These changes are not mandatory until v4.0.0 ### Deprecation - `interactionReply` is now deprecated. It will continue to work in v3.x, but logs a console warning. Please migrate to `execute`, as `interactionReply` will be removed in v4.0.0. diff --git a/docs/setup/commands-file.md b/docs/setup/commands-file.md index 7d3aaf1..e0542b8 100644 --- a/docs/setup/commands-file.md +++ b/docs/setup/commands-file.md @@ -1,4 +1,4 @@ -# Setting up Commands +# Setting up Prefix Commands Follow the folder structure and create sub folders inside your command folder. Name these sub-folders as a category name for your command files. In order for commands to run when placed inside their respective sub-folders of the command folder, you need to set the properties for each command. @@ -8,24 +8,12 @@ Here is a sample command example with the filename of "ping.js" and it's the com module.exports = { name: 'ping', //name of command when using ping description: 'Ping Pong Command!', // description of command - aliases: ['p'], // aliases of command - guildOnly: true, // guild command only? - /** - * @property {Number} permissions - * Permission Level of the command: - * 0 = Any User - * 5 = Server Owner - * 10 = Bot Owner - * The permission level can be set and changed by updating the config.js file - **/ - permissions: 0, - minArgs: 0, // minimum arguments required to execute command - usage: '', // example of how to use / call the command - /** - * Defines what the prefix command does, - * - * note as of v3.1.0, execute property has backward compatibility to run prefix commands until v4.0.0 - */ + /** + * Defines what the prefix command does, + * + * note for v3.1.0 or later: + * execute property has backward compatibility to run prefix commands + */ executePrefix(message, args, client) { return message.channel.send({ content: 'Pong.'}); }, @@ -44,7 +32,7 @@ module.exports = {

- aliases Array<String>
+ aliases Array<String>
This is the different abbreviation (aliases) of the command that you can use to call and execute the command

@@ -55,12 +43,12 @@ module.exports = {

- permissions number
+ permissions number = 0
This is the permission level value of who can execute the command. If set to 0, any user can run this command, 5 is the server owner and 10 is only the bot owner can run the command. For more details, please refer to the config file on the permission levels.

- minArgs number
+ minArgs number = 0
This is the minimum arguments required to execute the command

@@ -74,11 +62,11 @@ This is the maximum arguments required to execute the command

- customIds Array<String>
+ customIds Array<String>
An Array of strings containing strings of customIds used in current command file. This can be also a key:value pairs as an object of keys where the values are the string of customIds for easier reference

- usage string
+ usage string
Show by writing an example of how to execute the command using the command argument(s) in the command call Example: !ping

diff --git a/docs/setup/slash-commands-file.md b/docs/setup/slash-commands-file.md index 1eb6dee..0fcdfad 100644 --- a/docs/setup/slash-commands-file.md +++ b/docs/setup/slash-commands-file.md @@ -1,6 +1,8 @@ # Setting up Slash Commands -You will need to follow properties to create a regular command and the following `data` and `execute` properties to create a slash command. +If you have a prefix command execution, it can be created in the same command file. + +All properties required for prefix commands are optional for slash commands. ```javascript @@ -8,27 +10,25 @@ data: new SlashCommandBuilder(), async execute(interaction) {}, ``` -Here is a sample slash command created from our previous ping command: +Here is a sample slash command created from our prefix command, ping: ```javascript const { SlashCommandBuilder } = require("discord.js"); +const name = "ping"; +const description = "Ping Pong Command!"; + module.exports = { - name: 'ping', - description: 'Ping Pong Command!', - aliases: ['p'], - guildOnly: true, - permissions: 0, - minArgs: 0, - usage: '', + name, + description, data: new SlashCommandBuilder() - .setName("ping") - .setDescription("Ping Pong Command"), + .setName(name) + .setDescription(description), executePrefix(message, args, client) { return message.channel.send('Pong.'); }, async execute(interaction) { - await interaction.reply({ + return await interaction.reply({ content: 'Pong!' }); } @@ -43,6 +43,11 @@ module.exports = { This is where you define the properties of the slash command using the SlashCommandBuilder Class. You can also follow the official Discord.js guide.

+

+ permissions number = 0
+ In slash commands, you can use this permission level to add an extra layer or feature based on the level, but it will not prevent execution since slash commands are manageable at the server/guild level. +

+
execute(interaction, client, level) Promise<Interaction>
diff --git a/mkdocs.yml b/mkdocs.yml index 26c8a0d..6895514 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,19 +13,19 @@ nav: - TypeScript Support: getting-started/TypeScript Support.md - Setup: - DiscordFeaturesHandlerOptions: setup/DiscordFeaturesHandlerOptions.md - - config file: setup/config-file.md - - command files: setup/commands-file.md - - slash command files: setup/slash-commands-file.md - - other interactions: setup/other-interaction.md - - event files: setup/events-file.md - - modules file: setup/modules-files.md + - Config file: setup/config-file.md + - Slash command files: setup/slash-commands-file.md + - Prefix command files: setup/commands-file.md + - Other interactions: setup/other-interaction.md + - Event files: setup/events-file.md + - Modules file: setup/modules-files.md - Built-in Features: - - messageCreate event: builtIn/messageCreate-event.md - - interactionCreate event: builtIn/interactionCreate-event.md - - help command: builtIn/help-command.md - - reload command: builtIn/reload-command.md - - functions: builtIn/functions.md - - disabling built in: builtIn/disabling-built-in-features.md + - MessageCreate event: builtIn/messageCreate-event.md + - InteractionCreate event: builtIn/interactionCreate-event.md + - Help command: builtIn/help-command.md + - Reload command: builtIn/reload-command.md + - Functions: builtIn/functions.md + - Disabling built in: builtIn/disabling-built-in-features.md - Demo: demo.md - About: about.md - Release Notes: release-notes.md diff --git a/src/utils/clientUtils.js b/src/utils/clientUtils.js index 7cee5c6..bd1c5b6 100644 --- a/src/utils/clientUtils.js +++ b/src/utils/clientUtils.js @@ -7,25 +7,19 @@ const path = require("path"); */ const checkCommandErrors = (cmd) => { let error = ""; - if (typeof cmd.permissions === "undefined") { - error += `\n- Permission level is Missing!`; - } else if (isNaN(cmd.permissions)) { + if (cmd.permissions !== undefined && typeof cmd.permissions !== "number") { error += `\n- Permission level must be a Number!`; } - - if (typeof cmd.minArgs === "undefined") { - error += `\n- Minimum args is Missing!`; - } else if ( - isNaN(cmd.minArgs) || - (isNaN(cmd.minArgs) === false && cmd.minArgs < 0) - ) { + if (cmd.minArgs !== undefined && typeof cmd.minArgs !== "number") { + error += `\n- MinArgs must be a Number!`; + } + if (typeof cmd.minArgs === "number" && cmd.minArgs < 0) { error += `\n- MinArgs must be a Number equal to 0 or greater!`; } - if (cmd.maxArgs && isNaN(cmd.maxArgs)) { - if (isNaN(cmd.maxArgs)) { - error += `\n- maxArgs must be a number and greater then MinArgs`; - } else if (cmd.maxArgs !== -1 && cmd.maxArgs <= cmd.minArgs) { + if (typeof cmd.maxArgs === "number") { + const minArgs = cmd.minArgs || 0; + if (cmd.maxArgs !== 0 && cmd.maxArgs <= minArgs) { error += `\n- maxArgs must be a number greater then MinArgs`; } } @@ -35,30 +29,65 @@ const checkCommandErrors = (cmd) => { } if (cmd.customIds) { - if (cmd.customIds.modal && !Array.isArray(cmd.customIds.modal)) { - error += `\n- customIds for modal must be a Array of String`; - } if ( - cmd.customIds.messageComponent && - !Array.isArray(cmd.customIds.messageComponent) + !Array.isArray(cmd.customIds) && + (typeof cmd.customIds !== "object" || cmd.customIds === null) ) { - error += `\n- customIds for MessageComponent must be a Array of String`; + error += `\n- customIds must be an Array of String or an object of [k:string]: string`; + } else if (cmd.customIds.length < 1) { + error += `\n- customIds must contain at least one String`; } + } + + if (cmd.usage && typeof cmd.usage !== "string") { + error += `\n- Command Usage is not a String!`; + } + + if (cmd.data && !(cmd.data instanceof SlashCommandBuilder)) { + error += `\n- Command Data is not a SlashCommandBuilder instance!`; + } else if (cmd.data && cmd.data instanceof SlashCommandBuilder) { if ( - cmd.customIds.autoComplete && - !Array.isArray(cmd.customIds.autoComplete) + (!cmd.executePrefix && + (!cmd.interactionReply || + typeof cmd.interactionReply !== "function")) || + (cmd.executePrefix && (!cmd.execute || typeof cmd.execute !== "function")) ) { - error += `\n- customIds for AutoComplete must be a Array of String`; + error += `\n- Command must have either an execute property function for slash commands!`; } } - if (typeof cmd.usage === "undefined") { - error += `\n- Command Usage is Missing!`; + if ( + !cmd.data && + !( + (cmd.execute && typeof cmd.execute === "function") || + (cmd.executePrefix && typeof cmd.executePrefix === "function") + ) + ) { + error += `\n- Command must have either a executePrefix property function for prefix commands!`; } - if (typeof cmd.description === "undefined") { - error += `\n- Description is Missing!`; + if (!cmd.name && !(cmd.data && cmd.data.name)) { + error += `\n- Command Name is Missing!`; } + if ( + cmd.name && + typeof cmd.name !== "string" && + !(cmd.data && cmd.data.name) + ) { + error += `\n- Command Name is not a String!`; + } + + if (!cmd.description && !(cmd.data && cmd.data.description)) { + error += `\n- Command Description is Missing!`; + } + if ( + cmd.description && + typeof cmd.description !== "string" && + !(cmd.data && cmd.data.description) + ) { + error += `\n- Command Description is not a String!`; + } + return error; }; @@ -123,18 +152,34 @@ const configureClient = (client, config, directories) => { const placeHolder = `\nRequired:`; throw placeHolder + error; } + + if (!command.minArgs) { + command.minArgs = 0; + } + if (!command.permissions) { + command.permissions = 0; + } + if (!command.aliases) { + command.aliases = []; + } + if (!command.usage) { + command.usage = ""; + } + + const commandName = command.name || command.data.name; + /** * *in each cmd file, * *defines their category as folderName that contains the cmd file */ command.category = folder.toProperCase(); - client.commands.set(command.name, command); + client.commands.set(commandName, command); //requires a permission level set; best to have an permission level set to prevent unauthorized usage of a command. if (command.aliases) { command.aliases.forEach((alias) => { - client.aliases.set(alias, command.name); + client.aliases.set(alias, commandName); }); } From 7538cdde406fdafac0052a17f7077b6cfb3f3b78 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Sun, 17 Aug 2025 08:37:53 -0400 Subject: [PATCH 12/28] Make selective Command Properties optional in v3.1 --- README.md | 96 ++++++++-------------------- docs/release-notes.md | 12 ++++ docs/setup/commands-file.md | 36 ++++------- docs/setup/slash-commands-file.md | 29 +++++---- mkdocs.yml | 24 +++---- src/utils/clientUtils.js | 103 +++++++++++++++++++++--------- 6 files changed, 152 insertions(+), 148 deletions(-) diff --git a/README.md b/README.md index ff841a6..892b679 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ Here are the some parameters of options Object. For a full list please check out ## Commands Properties -The properties that are **required** to have when creating a command file +The properties that are required to have when creating a command file
@@ -163,42 +163,6 @@ The properties that are **required** to have when creating a command file - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -215,17 +179,12 @@ The properties that are **required** to have when creating a command file module.exports = { name: 'ping', description: 'Ping Pong Command!', - aliases: ['p'], - guildOnly: true, - permissions: 0, - minArgs: 0, - usage: '', - /** - * @param {message} message The discord message object - * @param {Array} args The arguments following the command call - * @param {Client} client The discord client object - * @param {number} level The permission level of the user who made the command call - */ + /** + * @param {message} message The discord message object + * @param {Array} args The arguments following the command call + * @param {Client} client The discord client object + * @param {number} level The permission level of the user who made the command call + */ executePrefix(message, args, client, level) { return message.channel.send('Pong.'); }, @@ -266,35 +225,30 @@ const description = "Ping Pong Command"; module.exports = { name, description, - aliases: ['p'], - guildOnly: true, - permissions: 0, - minArgs: 0, - usage: '', /** * This is required and set as true. Otherwise would not recognize as a slash command */ data: new SlashBuilderCommand().setName(name) .setDescription(description), - /** - * @param {message} message The discord message object - * @param {Array} args The arguments following the command call - * @param {Client} client The discord client object - * @param {number} level The permission level of the user who made the command call - */ + /** + * @param {message} message The discord message object + * @param {Array} args The arguments following the command call + * @param {Client} client The discord client object + * @param {number} level The permission level of the user who made the command call + */ executePrefix(message, args, client, level) { - return message.channel.send({ content: 'Pong.'}); - }, - /** - * @param {interaction} interaction The discord interaction object - * @param {Client} client The discord client object - * @param {number} level The permission level of the user who made the command call - */ - async execute(interaction, client, level) { - await interaction.reply({ - content: 'Pong!' - }); - } + return message.channel.send({ content: 'Pong.'}); + }, + /** + * @param {interaction} interaction The discord interaction object + * @param {Client} client The discord client object + * @param {number} level The permission level of the user who made the command call + */ + async execute(interaction, client, level) { + await interaction.reply({ + content: 'Pong!' + }); + } }; ``` diff --git a/docs/release-notes.md b/docs/release-notes.md index a0dbc01..27ebdf2 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -17,6 +17,18 @@ hide: - Enabled usage of `execute` property to run slash commands. - Added console warnings when prefix commands use `execute` instead of `executePrefix`, recommending migration to the new property. - Added console warnings when slash commands use `interactionReply` instead of `execute`, following discord.js guides and as Discord is encouraging developers to transition to using slash commands over traditional prefix commands for bots. +- Following Command Properties are now optional: + - `name` - slash commands only + - `description` - slash commands only + - `aliases` + - `minArgs` + - `permissions` + - `usage` + +- Recommended name changes to Command Properties: + - For executing prefix commands: `execute` → `executePrefix` + - For executing slash commands: `interactionReply` → `execute` + - These changes are not mandatory until v4.0.0 ### Deprecation - `interactionReply` is now deprecated. It will continue to work in v3.x, but logs a console warning. Please migrate to `execute`, as `interactionReply` will be removed in v4.0.0. diff --git a/docs/setup/commands-file.md b/docs/setup/commands-file.md index 7d3aaf1..e0542b8 100644 --- a/docs/setup/commands-file.md +++ b/docs/setup/commands-file.md @@ -1,4 +1,4 @@ -# Setting up Commands +# Setting up Prefix Commands Follow the folder structure and create sub folders inside your command folder. Name these sub-folders as a category name for your command files. In order for commands to run when placed inside their respective sub-folders of the command folder, you need to set the properties for each command. @@ -8,24 +8,12 @@ Here is a sample command example with the filename of "ping.js" and it's the com module.exports = { name: 'ping', //name of command when using ping description: 'Ping Pong Command!', // description of command - aliases: ['p'], // aliases of command - guildOnly: true, // guild command only? - /** - * @property {Number} permissions - * Permission Level of the command: - * 0 = Any User - * 5 = Server Owner - * 10 = Bot Owner - * The permission level can be set and changed by updating the config.js file - **/ - permissions: 0, - minArgs: 0, // minimum arguments required to execute command - usage: '', // example of how to use / call the command - /** - * Defines what the prefix command does, - * - * note as of v3.1.0, execute property has backward compatibility to run prefix commands until v4.0.0 - */ + /** + * Defines what the prefix command does, + * + * note for v3.1.0 or later: + * execute property has backward compatibility to run prefix commands + */ executePrefix(message, args, client) { return message.channel.send({ content: 'Pong.'}); }, @@ -44,7 +32,7 @@ module.exports = {

- aliases Array<String>
+ aliases Array<String>
This is the different abbreviation (aliases) of the command that you can use to call and execute the command

@@ -55,12 +43,12 @@ module.exports = {

- permissions number
+ permissions number = 0
This is the permission level value of who can execute the command. If set to 0, any user can run this command, 5 is the server owner and 10 is only the bot owner can run the command. For more details, please refer to the config file on the permission levels.

- minArgs number
+ minArgs number = 0
This is the minimum arguments required to execute the command

@@ -74,11 +62,11 @@ This is the maximum arguments required to execute the command

- customIds Array<String>
+ customIds Array<String>
An Array of strings containing strings of customIds used in current command file. This can be also a key:value pairs as an object of keys where the values are the string of customIds for easier reference

- usage string
+ usage string
Show by writing an example of how to execute the command using the command argument(s) in the command call Example: !ping

diff --git a/docs/setup/slash-commands-file.md b/docs/setup/slash-commands-file.md index 1eb6dee..0fcdfad 100644 --- a/docs/setup/slash-commands-file.md +++ b/docs/setup/slash-commands-file.md @@ -1,6 +1,8 @@ # Setting up Slash Commands -You will need to follow properties to create a regular command and the following `data` and `execute` properties to create a slash command. +If you have a prefix command execution, it can be created in the same command file. + +All properties required for prefix commands are optional for slash commands. ```javascript @@ -8,27 +10,25 @@ data: new SlashCommandBuilder(), async execute(interaction) {}, ``` -Here is a sample slash command created from our previous ping command: +Here is a sample slash command created from our prefix command, ping: ```javascript const { SlashCommandBuilder } = require("discord.js"); +const name = "ping"; +const description = "Ping Pong Command!"; + module.exports = { - name: 'ping', - description: 'Ping Pong Command!', - aliases: ['p'], - guildOnly: true, - permissions: 0, - minArgs: 0, - usage: '', + name, + description, data: new SlashCommandBuilder() - .setName("ping") - .setDescription("Ping Pong Command"), + .setName(name) + .setDescription(description), executePrefix(message, args, client) { return message.channel.send('Pong.'); }, async execute(interaction) { - await interaction.reply({ + return await interaction.reply({ content: 'Pong!' }); } @@ -43,6 +43,11 @@ module.exports = { This is where you define the properties of the slash command using the SlashCommandBuilder Class. You can also follow the official Discord.js guide.

+

+ permissions number = 0
+ In slash commands, you can use this permission level to add an extra layer or feature based on the level, but it will not prevent execution since slash commands are manageable at the server/guild level. +

+
execute(interaction, client, level) Promise<Interaction>
diff --git a/mkdocs.yml b/mkdocs.yml index 26c8a0d..6895514 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,19 +13,19 @@ nav: - TypeScript Support: getting-started/TypeScript Support.md - Setup: - DiscordFeaturesHandlerOptions: setup/DiscordFeaturesHandlerOptions.md - - config file: setup/config-file.md - - command files: setup/commands-file.md - - slash command files: setup/slash-commands-file.md - - other interactions: setup/other-interaction.md - - event files: setup/events-file.md - - modules file: setup/modules-files.md + - Config file: setup/config-file.md + - Slash command files: setup/slash-commands-file.md + - Prefix command files: setup/commands-file.md + - Other interactions: setup/other-interaction.md + - Event files: setup/events-file.md + - Modules file: setup/modules-files.md - Built-in Features: - - messageCreate event: builtIn/messageCreate-event.md - - interactionCreate event: builtIn/interactionCreate-event.md - - help command: builtIn/help-command.md - - reload command: builtIn/reload-command.md - - functions: builtIn/functions.md - - disabling built in: builtIn/disabling-built-in-features.md + - MessageCreate event: builtIn/messageCreate-event.md + - InteractionCreate event: builtIn/interactionCreate-event.md + - Help command: builtIn/help-command.md + - Reload command: builtIn/reload-command.md + - Functions: builtIn/functions.md + - Disabling built in: builtIn/disabling-built-in-features.md - Demo: demo.md - About: about.md - Release Notes: release-notes.md diff --git a/src/utils/clientUtils.js b/src/utils/clientUtils.js index 7cee5c6..3a55b6e 100644 --- a/src/utils/clientUtils.js +++ b/src/utils/clientUtils.js @@ -7,25 +7,19 @@ const path = require("path"); */ const checkCommandErrors = (cmd) => { let error = ""; - if (typeof cmd.permissions === "undefined") { - error += `\n- Permission level is Missing!`; - } else if (isNaN(cmd.permissions)) { + if (cmd.permissions !== undefined && typeof cmd.permissions !== "number") { error += `\n- Permission level must be a Number!`; } - - if (typeof cmd.minArgs === "undefined") { - error += `\n- Minimum args is Missing!`; - } else if ( - isNaN(cmd.minArgs) || - (isNaN(cmd.minArgs) === false && cmd.minArgs < 0) - ) { + if (cmd.minArgs !== undefined && typeof cmd.minArgs !== "number") { + error += `\n- MinArgs must be a Number!`; + } + if (typeof cmd.minArgs === "number" && cmd.minArgs < 0) { error += `\n- MinArgs must be a Number equal to 0 or greater!`; } - if (cmd.maxArgs && isNaN(cmd.maxArgs)) { - if (isNaN(cmd.maxArgs)) { - error += `\n- maxArgs must be a number and greater then MinArgs`; - } else if (cmd.maxArgs !== -1 && cmd.maxArgs <= cmd.minArgs) { + if (typeof cmd.maxArgs === "number") { + const minArgs = cmd.minArgs || 0; + if (typeof cmd.maxArgs === "number" && cmd.maxArgs < minArgs) { error += `\n- maxArgs must be a number greater then MinArgs`; } } @@ -35,30 +29,65 @@ const checkCommandErrors = (cmd) => { } if (cmd.customIds) { - if (cmd.customIds.modal && !Array.isArray(cmd.customIds.modal)) { - error += `\n- customIds for modal must be a Array of String`; - } if ( - cmd.customIds.messageComponent && - !Array.isArray(cmd.customIds.messageComponent) + !Array.isArray(cmd.customIds) && + (typeof cmd.customIds !== "object" || cmd.customIds === null) ) { - error += `\n- customIds for MessageComponent must be a Array of String`; + error += `\n- customIds must be an Array of String or an object of [k:string]: string`; + } else if (cmd.customIds.length < 1) { + error += `\n- customIds must contain at least one String`; } + } + + if (cmd.usage && typeof cmd.usage !== "string") { + error += `\n- Command Usage is not a String!`; + } + + if (cmd.data && !(cmd.data instanceof SlashCommandBuilder)) { + error += `\n- Command Data is not a SlashCommandBuilder instance!`; + } else if (cmd.data && cmd.data instanceof SlashCommandBuilder) { if ( - cmd.customIds.autoComplete && - !Array.isArray(cmd.customIds.autoComplete) + (!cmd.executePrefix && + (!cmd.interactionReply || + typeof cmd.interactionReply !== "function")) || + (cmd.executePrefix && (!cmd.execute || typeof cmd.execute !== "function")) ) { - error += `\n- customIds for AutoComplete must be a Array of String`; + error += `\n- Command must have either an execute property function for slash commands!`; } } - if (typeof cmd.usage === "undefined") { - error += `\n- Command Usage is Missing!`; + if ( + !cmd.data && + !( + (cmd.execute && typeof cmd.execute === "function") || + (cmd.executePrefix && typeof cmd.executePrefix === "function") + ) + ) { + error += `\n- Command must have either a executePrefix property function for prefix commands!`; } - if (typeof cmd.description === "undefined") { - error += `\n- Description is Missing!`; + if (!cmd.name && !(cmd.data && cmd.data.name)) { + error += `\n- Command Name is Missing!`; } + if ( + cmd.name && + typeof cmd.name !== "string" && + !(cmd.data && cmd.data.name) + ) { + error += `\n- Command Name is not a String!`; + } + + if (!cmd.description && !(cmd.data && cmd.data.description)) { + error += `\n- Command Description is Missing!`; + } + if ( + cmd.description && + typeof cmd.description !== "string" && + !(cmd.data && cmd.data.description) + ) { + error += `\n- Command Description is not a String!`; + } + return error; }; @@ -123,18 +152,34 @@ const configureClient = (client, config, directories) => { const placeHolder = `\nRequired:`; throw placeHolder + error; } + + if (!command.minArgs) { + command.minArgs = 0; + } + if (!command.permissions) { + command.permissions = 0; + } + if (!command.aliases) { + command.aliases = []; + } + if (!command.usage) { + command.usage = ""; + } + + const commandName = command.name || command.data.name; + /** * *in each cmd file, * *defines their category as folderName that contains the cmd file */ command.category = folder.toProperCase(); - client.commands.set(command.name, command); + client.commands.set(commandName, command); //requires a permission level set; best to have an permission level set to prevent unauthorized usage of a command. if (command.aliases) { command.aliases.forEach((alias) => { - client.aliases.set(alias, command.name); + client.aliases.set(alias, commandName); }); } From 8b8353e0cd891ff570c7df0513c570ffe8d8a88a Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Sun, 17 Aug 2025 09:11:57 -0400 Subject: [PATCH 13/28] Update dfhInteractionCreate.js wrong type check --- src/events/dfhInteractionCreate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/events/dfhInteractionCreate.js b/src/events/dfhInteractionCreate.js index f53c80c..11ee71d 100644 --- a/src/events/dfhInteractionCreate.js +++ b/src/events/dfhInteractionCreate.js @@ -29,7 +29,7 @@ module.exports = { console.log("[SLASH CMD]", "[ID]", commandId); try { - if ("execute" in cmd && !"interactionReply" in cmd) { + if (cmd.execute && !cmd.interactionReply) { return cmd.execute(interaction, client, level); } return cmd From 67a127899aee387d3ce51e6eff80937f9250a74d Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Sun, 17 Aug 2025 09:36:15 -0400 Subject: [PATCH 14/28] Update README.md --- README.md | 51 ++++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 892b679..1af89a4 100644 --- a/README.md +++ b/README.md @@ -179,12 +179,12 @@ The properties that are required to have when creating a command file module.exports = { name: 'ping', description: 'Ping Pong Command!', - /** - * @param {message} message The discord message object - * @param {Array} args The arguments following the command call - * @param {Client} client The discord client object - * @param {number} level The permission level of the user who made the command call - */ + /** + * @param {message} message The discord message object + * @param {Array} args The arguments following the command call + * @param {Client} client The discord client object + * @param {number} level The permission level of the user who made the command call + */ executePrefix(message, args, client, level) { return message.channel.send('Pong.'); }, @@ -228,27 +228,28 @@ module.exports = { /** * This is required and set as true. Otherwise would not recognize as a slash command */ - data: new SlashBuilderCommand().setName(name) + data: new SlashBuilderCommand() + .setName(name) .setDescription(description), - /** - * @param {message} message The discord message object - * @param {Array} args The arguments following the command call - * @param {Client} client The discord client object - * @param {number} level The permission level of the user who made the command call - */ + /** + * @param {message} message The discord message object + * @param {Array} args The arguments following the command call + * @param {Client} client The discord client object + * @param {number} level The permission level of the user who made the command call + */ executePrefix(message, args, client, level) { - return message.channel.send({ content: 'Pong.'}); - }, - /** - * @param {interaction} interaction The discord interaction object - * @param {Client} client The discord client object - * @param {number} level The permission level of the user who made the command call - */ - async execute(interaction, client, level) { - await interaction.reply({ - content: 'Pong!' - }); - } + return message.channel.send({ content: 'Pong.'}); + }, + /** + * @param {interaction} interaction The discord interaction object + * @param {Client} client The discord client object + * @param {number} level The permission level of the user who made the command call + */ + async execute(interaction, client, level) { + await interaction.reply({ + content: 'Pong!' + }); + } }; ``` From 85c52f2917c0917d6b922642093a8b83ab6709cd Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Sun, 17 Aug 2025 17:40:05 -0400 Subject: [PATCH 15/28] updated dfhHelp cmd with customIdInteraction fn also changed execute to executePrefix for dfh commands --- docs/builtIn/help-command.md | 228 ++++++++++++++------------ src/commands/miscellaneous/dfhHelp.js | 218 +++++++++++++----------- src/commands/system/dfhReload.js | 3 +- 3 files changed, 242 insertions(+), 207 deletions(-) diff --git a/docs/builtIn/help-command.md b/docs/builtIn/help-command.md index f1196ba..97b92db 100644 --- a/docs/builtIn/help-command.md +++ b/docs/builtIn/help-command.md @@ -5,7 +5,7 @@ This is the built-in help command, this will generate an embed with Buttons, and !!! info Max category this can handle is 5 categories due to MessageActionRow limit, therefore if you make more than 5 categories in your command folder this command will not work! -If you want to modify this built-in file you can disable it in DiscordFeaturesHandlerOptions and create the following help command file +If you want to modify this built-in file you can disable it in DiscordFeaturesHandlerOptions and create the following help command file and will need to include your own data property if you want it to be a slash command. ```javascript /** @@ -17,14 +17,20 @@ const { EmbedBuilder, ButtonStyle, } = require("discord.js"); -const filterTime = 60000; + +const customIds = [ + "dfh_help_1", + "dfh_help_2", + "dfh_help_3", + "dfh_help_4", + "dfh_help_5", +]; module.exports = { name: "help", description: "List all of my commands or info about a specific command.", aliases: ["commands"], - permissions: 0, - minArgs: 0, + customIds, maxArgs: 1, usage: "", async executePrefix(message, args, client) { @@ -48,35 +54,10 @@ module.exports = { const row = getButtonRows(data); //send initial message and await - message - .reply({ - embeds: [embed], - components: [row], - }) - .then((msg) => { - // after message was sent then listen... - // Filter, ensures that the user who initial the help cmd call is changing the embed by their request - const filter = (i) => i.user.id === message.author.id; - // Create the message component for buttons to show on embeds - const collector = message.channel.createMessageComponentCollector({ - filter, - time: filterTime, - }); - - //this awaits and collect responses from input of user and handle it. - collector.on("collect", async (i) => { - const newEmbed = getUpdateEmbed(data, i, client); - - await i.update({ embeds: [newEmbed], components: [row] }); - }); - - // handles after the collection event ended - // disable listening to btn inputs after filterTime expires - return collector.on("end", async (collected) => { - const lastRow = getButtonRows(data, true); - return await msg.edit({ components: [lastRow] }); - }); - }); + message.reply({ + embeds: [embed], + components: [row], + }); } else { //display the command info requested from user's call const name = args[0].toLowerCase(); @@ -85,10 +66,12 @@ module.exports = { } }, /** - * This is can be used for slash help command if you choose to and adding a "data" property - */ - async executePrefix(interaction, client, level) { - await interaction.deferReply(); + * Missing data property if you plan to turn this into a slash command! + */ + async execute(interaction, client, level) { + await interaction.deferReply({ + ephemeral: true, + }); const { options } = interaction; const name = options.getString("cmd_name"); @@ -105,26 +88,6 @@ module.exports = { embeds: [embed], components: [row], }); - - //display the command info requested from user's call - // Filter, ensures that the user who initial the help cmd call is changing the embed by their request - const filter = (i) => i.user.id === interaction.user.id; - // Create the message component for buttons to show on embeds - const collector = interaction.channel.createMessageComponentCollector({ - filter, - time: filterTime, - }); - //this awaits and collect responses from input of user and handle it. - collector.on("collect", async (i) => { - const newEmbed = getUpdateEmbed(data, i, client); - await i.update({ embeds: [newEmbed], components: [row] }); - }); - // handles after the collection event ended - // disable listening to btn inputs after filterTime expires - return collector.on("end", async (collected) => { - const lastRow = getButtonRows(data, true); - return await interaction.editReply({ components: [lastRow] }); - }); } else { //display the command info requested from user's call const response = await getSingleCmd(commands, name, client); @@ -133,6 +96,15 @@ module.exports = { .catch((error) => console.log(error)); } }, + async customIdInteraction(interaction, client, level) { + const commands = await client.commands.filter( + (cmd) => cmd.permissions <= level + ); + const data = getSortedCommandArray(client, commands); + const newEmbed = getUpdateEmbed(data, interaction.customId, client); + + await interaction.update({ embeds: [newEmbed] }); + }, }; /** * @@ -162,6 +134,10 @@ const getSortedCommandArray = (client, commands) => { sorted.map((command) => { let temp = { category: "", + customId: + dataArray.length < 5 + ? customIds[dataArray.length] + : `dfh_help_${dataArray.length}`, commands: [], }; if (!category || category != command.category) { @@ -196,9 +172,7 @@ const getInitialEmbed = (data, client) => { name: `${client.user.username} Help Menu`, iconURL: `${client.user.avatarURL()}`, }).setDescription(`There are ${data.length} categories!\n${categories} -Click the respective buttons to see the commands of the category. You have ${ - filterTime / 60000 - } minutes until buttons are disabled.`); +Click the respective buttons to see the commands of the category. `); return defaultEmbed; }; @@ -206,12 +180,12 @@ Click the respective buttons to see the commands of the category. You have ${ /** * * @param {Array} data the data to display on the embed - * @param {Number} i index of category to show + * @param {Number} customId index of category to show * @param {Client} client Discord client object * @returns EmbedBuilder to display */ -const getUpdateEmbed = (data, i, client) => { - const index = data.findIndex((d) => d.category === i.customId); +const getUpdateEmbed = (data, customId, client) => { + const index = data.findIndex((d) => d.customId === customId); const cmds = data[index].commands .map((cmd) => { let cmdName = cmd.name @@ -270,7 +244,7 @@ const getButtonRows = (data, disabled = false) => { ]; const defaultColor = ButtonStyle.Primary; - const btnArray = data.map((res) => { + const btnArray = data.map((res, i) => { const catName = res.category; const index = colorForCategory.findIndex( @@ -279,7 +253,7 @@ const getButtonRows = (data, disabled = false) => { const style = index !== -1 ? colorForCategory[index].color : defaultColor; return new ButtonBuilder() - .setCustomId(catName) + .setCustomId(res.customId) .setLabel(catName) .setStyle(style) .setDisabled(disabled); @@ -306,56 +280,100 @@ const getSingleCmd = async (commands, name, client) => { ? client.config.prefix[0] : client.config.prefix; const command = await commands.find( - (cmd) => cmd.name === name || cmd.aliases === name + (cmd) => + cmd.name === name || + (Array.isArray(cmd.aliases) && cmd.aliases.includes(name)) ); - if (!command) { - return { - content: `The command, **${name}** - + does not exist!`, - }; - } + const slashCommand = await commands.find( + (cmd) => cmd.data && cmd.data.name === name + ); - const fieldObj = []; - const aliases = command.aliases.join(", "); - if (aliases.length !== 0) { + if (slashCommand) { + const fieldObj = []; fieldObj.push({ - name: `Aliases:`, - value: `${aliases}`, + name: `Category:`, + value: `${command.category}`, inline: true, }); - } - fieldObj.push({ - name: `Category:`, - value: `${command.category}`, - inline: true, - }); - if (command.usage.length !== 0) { fieldObj.push({ name: `Usage:`, - value: `${prefix}${command.name} ${command.usage}`, + value: `/${command.name}`, + inline: true, }); - } - fieldObj.push({ - name: `Slash:`, - value: `${command.data ? `True` : `False`}`, - inline: true, - }); - try { - const embed = new EmbedBuilder() - .setAuthor({ - name: `${client.user.tag}`, - iconURL: `${client.user.avatarURL()}`, - }) - .setTitle(`${command.name.toProperCase()} Command`) - .setDescription(command.description) - .setTimestamp() - .setFields(fieldObj); - - return { embeds: [embed] }; - } catch (e) { - console.log(e); + if (command.executePrefix) { + fieldObj.push({ + name: `Using prefix:`, + value: `${prefix}${command.name} ${command.usage}${ + command.aliases + ? `, ${prefix}${command.aliases.join(`, ${prefix}`)}` + : "" + }`, + }); + } + try { + const embed = new EmbedBuilder() + .setAuthor({ + name: `${client.user.tag}`, + iconURL: `${client.user.avatarURL()}`, + }) + .setTitle(`${command.data.name.toProperCase()} Command`) + .setDescription(command.data.description) + .setTimestamp() + .setFields(fieldObj); + + return { embeds: [embed] }; + } catch (e) { + console.log(e); + } + } else if (command) { + const fieldObj = []; + const aliases = command.aliases ? command.aliases.join(", ") : null; + if (aliases) { + fieldObj.push({ + name: `Aliases:`, + value: `${aliases}`, + inline: true, + }); + } + fieldObj.push({ + name: `Category:`, + value: `${command.category}`, + inline: true, + }); + + if (command.usage.length !== 0) { + fieldObj.push({ + name: `Usage:`, + value: `${prefix}${command.name} ${command.usage}`, + }); + } + fieldObj.push({ + name: `Slash:`, + value: `${command.data ? `True` : `False`}`, + inline: true, + }); + try { + const embed = new EmbedBuilder() + .setAuthor({ + name: `${client.user.tag}`, + iconURL: `${client.user.avatarURL()}`, + }) + .setTitle(`${command.name.toProperCase()} Command`) + .setDescription(command.description) + .setTimestamp() + .setFields(fieldObj); + + return { embeds: [embed] }; + } catch (e) { + console.log(e); + } + } else { + return { + content: `The command, **${name}** + + does not exist!`, + }; } }; diff --git a/src/commands/miscellaneous/dfhHelp.js b/src/commands/miscellaneous/dfhHelp.js index 80e9221..e907293 100644 --- a/src/commands/miscellaneous/dfhHelp.js +++ b/src/commands/miscellaneous/dfhHelp.js @@ -7,14 +7,20 @@ const { EmbedBuilder, ButtonStyle, } = require("discord.js"); -const filterTime = 60000; + +const customIds = [ + "dfh_help_1", + "dfh_help_2", + "dfh_help_3", + "dfh_help_4", + "dfh_help_5", +]; module.exports = { name: "help", description: "List all of my commands or info about a specific command.", aliases: ["commands"], - permissions: 0, - minArgs: 0, + customIds, maxArgs: 1, usage: "", async executePrefix(message, args, client) { @@ -38,35 +44,10 @@ module.exports = { const row = getButtonRows(data); //send initial message and await - message - .reply({ - embeds: [embed], - components: [row], - }) - .then((msg) => { - // after message was sent then listen... - // Filter, ensures that the user who initial the help cmd call is changing the embed by their request - const filter = (i) => i.user.id === message.author.id; - // Create the message component for buttons to show on embeds - const collector = message.channel.createMessageComponentCollector({ - filter, - time: filterTime, - }); - - //this awaits and collect responses from input of user and handle it. - collector.on("collect", async (i) => { - const newEmbed = getUpdateEmbed(data, i, client); - - await i.update({ embeds: [newEmbed], components: [row] }); - }); - - // handles after the collection event ended - // disable listening to btn inputs after filterTime expires - return collector.on("end", async (collected) => { - const lastRow = getButtonRows(data, true); - return await msg.edit({ components: [lastRow] }); - }); - }); + message.reply({ + embeds: [embed], + components: [row], + }); } else { //display the command info requested from user's call const name = args[0].toLowerCase(); @@ -75,7 +56,9 @@ module.exports = { } }, async execute(interaction, client, level) { - await interaction.deferReply(); + await interaction.deferReply({ + ephemeral: true, + }); const { options } = interaction; const name = options.getString("cmd_name"); @@ -92,26 +75,6 @@ module.exports = { embeds: [embed], components: [row], }); - - //display the command info requested from user's call - // Filter, ensures that the user who initial the help cmd call is changing the embed by their request - const filter = (i) => i.user.id === interaction.user.id; - // Create the message component for buttons to show on embeds - const collector = interaction.channel.createMessageComponentCollector({ - filter, - time: filterTime, - }); - //this awaits and collect responses from input of user and handle it. - collector.on("collect", async (i) => { - const newEmbed = getUpdateEmbed(data, i, client); - await i.update({ embeds: [newEmbed], components: [row] }); - }); - // handles after the collection event ended - // disable listening to btn inputs after filterTime expires - return collector.on("end", async (collected) => { - const lastRow = getButtonRows(data, true); - return await interaction.editReply({ components: [lastRow] }); - }); } else { //display the command info requested from user's call const response = await getSingleCmd(commands, name, client); @@ -120,6 +83,15 @@ module.exports = { .catch((error) => console.log(error)); } }, + async customIdInteraction(interaction, client, level) { + const commands = await client.commands.filter( + (cmd) => cmd.permissions <= level + ); + const data = getSortedCommandArray(client, commands); + const newEmbed = getUpdateEmbed(data, interaction.customId, client); + + await interaction.update({ embeds: [newEmbed] }); + }, }; /** * @@ -149,6 +121,10 @@ const getSortedCommandArray = (client, commands) => { sorted.map((command) => { let temp = { category: "", + customId: + dataArray.length < 5 + ? customIds[dataArray.length] + : `dfh_help_${dataArray.length}`, commands: [], }; if (!category || category != command.category) { @@ -183,9 +159,7 @@ const getInitialEmbed = (data, client) => { name: `${client.user.username} Help Menu`, iconURL: `${client.user.avatarURL()}`, }).setDescription(`There are ${data.length} categories!\n${categories} -Click the respective buttons to see the commands of the category. You have ${ - filterTime / 60000 - } minutes until buttons are disabled.`); +Click the respective buttons to see the commands of the category. `); return defaultEmbed; }; @@ -193,12 +167,12 @@ Click the respective buttons to see the commands of the category. You have ${ /** * * @param {Array} data the data to display on the embed - * @param {Number} i index of category to show + * @param {Number} customId index of category to show * @param {Client} client Discord client object * @returns EmbedBuilder to display */ -const getUpdateEmbed = (data, i, client) => { - const index = data.findIndex((d) => d.category === i.customId); +const getUpdateEmbed = (data, customId, client) => { + const index = data.findIndex((d) => d.customId === customId); const cmds = data[index].commands .map((cmd) => { let cmdName = cmd.name @@ -257,7 +231,7 @@ const getButtonRows = (data, disabled = false) => { ]; const defaultColor = ButtonStyle.Primary; - const btnArray = data.map((res) => { + const btnArray = data.map((res, i) => { const catName = res.category; const index = colorForCategory.findIndex( @@ -266,7 +240,7 @@ const getButtonRows = (data, disabled = false) => { const style = index !== -1 ? colorForCategory[index].color : defaultColor; return new ButtonBuilder() - .setCustomId(catName) + .setCustomId(res.customId) .setLabel(catName) .setStyle(style) .setDisabled(disabled); @@ -293,55 +267,99 @@ const getSingleCmd = async (commands, name, client) => { ? client.config.prefix[0] : client.config.prefix; const command = await commands.find( - (cmd) => cmd.name === name || (cmd.aliases && cmd.aliases === name) + (cmd) => + cmd.name === name || + (Array.isArray(cmd.aliases) && cmd.aliases.includes(name)) ); - if (!command) { - return { - content: `The command, **${name}** - + does not exist!`, - }; - } + const slashCommand = await commands.find( + (cmd) => cmd.data && cmd.data.name === name + ); - const fieldObj = []; - const aliases = command.aliases ? command.aliases.join(", ") : null; - if (aliases) { + if (slashCommand) { + const fieldObj = []; fieldObj.push({ - name: `Aliases:`, - value: `${aliases}`, + name: `Category:`, + value: `${command.category}`, inline: true, }); - } - fieldObj.push({ - name: `Category:`, - value: `${command.category}`, - inline: true, - }); - if (command.usage.length !== 0) { fieldObj.push({ name: `Usage:`, - value: `${prefix}${command.name} ${command.usage}`, + value: `/${command.name}`, + inline: true, + }); + if (command.executePrefix) { + fieldObj.push({ + name: `Using prefix:`, + value: `${prefix}${command.name} ${command.usage}${ + command.aliases + ? `, ${prefix}${command.aliases.join(`, ${prefix}`)}` + : "" + }`, + }); + } + try { + const embed = new EmbedBuilder() + .setAuthor({ + name: `${client.user.tag}`, + iconURL: `${client.user.avatarURL()}`, + }) + .setTitle(`${command.data.name.toProperCase()} Command`) + .setDescription(command.data.description) + .setTimestamp() + .setFields(fieldObj); + + return { embeds: [embed] }; + } catch (e) { + console.log(e); + } + } else if (command) { + const fieldObj = []; + const aliases = command.aliases ? command.aliases.join(", ") : null; + if (aliases) { + fieldObj.push({ + name: `Aliases:`, + value: `${aliases}`, + inline: true, + }); + } + fieldObj.push({ + name: `Category:`, + value: `${command.category}`, + inline: true, }); - } - fieldObj.push({ - name: `Slash:`, - value: `${command.data ? `True` : `False`}`, - inline: true, - }); - try { - const embed = new EmbedBuilder() - .setAuthor({ - name: `${client.user.tag}`, - iconURL: `${client.user.avatarURL()}`, - }) - .setTitle(`${command.name.toProperCase()} Command`) - .setDescription(command.description) - .setTimestamp() - .setFields(fieldObj); - return { embeds: [embed] }; - } catch (e) { - console.log(e); + if (command.usage.length !== 0) { + fieldObj.push({ + name: `Usage:`, + value: `${prefix}${command.name} ${command.usage}`, + }); + } + fieldObj.push({ + name: `Slash:`, + value: `${command.data ? `True` : `False`}`, + inline: true, + }); + try { + const embed = new EmbedBuilder() + .setAuthor({ + name: `${client.user.tag}`, + iconURL: `${client.user.avatarURL()}`, + }) + .setTitle(`${command.name.toProperCase()} Command`) + .setDescription(command.description) + .setTimestamp() + .setFields(fieldObj); + + return { embeds: [embed] }; + } catch (e) { + console.log(e); + } + } else { + return { + content: `The command, **${name}** + + does not exist!`, + }; } }; diff --git a/src/commands/system/dfhReload.js b/src/commands/system/dfhReload.js index 80a2639..2fe5d1d 100644 --- a/src/commands/system/dfhReload.js +++ b/src/commands/system/dfhReload.js @@ -4,13 +4,12 @@ const absolute = path.resolve(); module.exports = { name: "reload", description: "Reload a command!", - aliases: [""], guildOnly: false, permissions: 8, minArgs: 1, maxArgs: 1, usage: "", - async execute(message, args, client) { + async executePrefix(message, args, client) { const commandName = args[0]; let command; From a063d7c07b1c2474b17a4c3fcc1d934deb33df6b Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Sun, 17 Aug 2025 20:29:23 -0400 Subject: [PATCH 16/28] add try/catch to the dfhhelp customidinteraction fn --- docs/builtIn/help-command.md | 24 +++++++++++++++++++++--- src/commands/miscellaneous/dfhHelp.js | 20 ++++++++++++++++++-- src/index.js | 5 +++-- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/docs/builtIn/help-command.md b/docs/builtIn/help-command.md index 97b92db..5338d45 100644 --- a/docs/builtIn/help-command.md +++ b/docs/builtIn/help-command.md @@ -101,9 +101,27 @@ module.exports = { (cmd) => cmd.permissions <= level ); const data = getSortedCommandArray(client, commands); - const newEmbed = getUpdateEmbed(data, interaction.customId, client); - - await interaction.update({ embeds: [newEmbed] }); + const customId = interaction.customId; + try { + if (customIds.includes(customId)) { + const newEmbed = getUpdateEmbed(data, customId, client); + + await interaction.update({ embeds: [newEmbed] }); + } else { + console.log("Invalid category: " + customId, data); + await interaction.reply({ content: "Invalid category: " + customId }); + } + } catch (error) { + console.error("Error handling customIdInteraction:", error); + await interaction + .reply({ + content: "An error occurred while processing your request.", + ephemeral: true, + }) + .catch((e) => { + + }); + } }, }; /** diff --git a/src/commands/miscellaneous/dfhHelp.js b/src/commands/miscellaneous/dfhHelp.js index e907293..05f63f9 100644 --- a/src/commands/miscellaneous/dfhHelp.js +++ b/src/commands/miscellaneous/dfhHelp.js @@ -88,9 +88,25 @@ module.exports = { (cmd) => cmd.permissions <= level ); const data = getSortedCommandArray(client, commands); - const newEmbed = getUpdateEmbed(data, interaction.customId, client); + const customId = interaction.customId; + try { + if (customIds.includes(customId)) { + const newEmbed = getUpdateEmbed(data, customId, client); - await interaction.update({ embeds: [newEmbed] }); + await interaction.update({ embeds: [newEmbed] }); + } else { + console.log("Invalid category: " + customId, data); + await interaction.reply({ content: "Invalid category: " + customId }); + } + } catch (error) { + console.error("Error handling customIdInteraction:", error); + await interaction + .reply({ + content: "An error occurred while processing your request.", + ephemeral: true, + }) + .catch(() => {}); + } }, }; /** diff --git a/src/index.js b/src/index.js index ebb21cc..0b9af66 100644 --- a/src/index.js +++ b/src/index.js @@ -173,8 +173,9 @@ const DiscordFeaturesHandler = async ( functions(); - console.log(`Thank you for installing DiscordFeaturesHandler!`); - console.log(`Loading your files now...`); + console.log( + `Thank you for installing DiscordFeaturesHandler\nLoading your files...` + ); const configFile = config.endsWith("./defaultConfig.js") ? require(config) From 8660082c133745190618522b386b5d10cb97d07b Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:22:40 -0400 Subject: [PATCH 17/28] change customIds to array and add global property --- docs/builtIn/interactionCreate-event.md | 4 --- docs/release-notes.md | 33 +++++++++++++---------- docs/setup/commands-file.md | 2 +- docs/setup/other-interaction.md | 14 +--------- docs/setup/slash-commands-file.md | 5 ++++ mkdocs.yml | 1 + src/events/dfhInteractionCreate.js | 4 --- src/handlers/loadCommands.js | 19 +++++++++----- src/index.d.ts | 35 ++++++++++++++++--------- src/utils/clientUtils.js | 15 ++++++----- test/commands/system/about.js | 2 +- 11 files changed, 72 insertions(+), 62 deletions(-) diff --git a/docs/builtIn/interactionCreate-event.md b/docs/builtIn/interactionCreate-event.md index 5495730..82f8a4e 100644 --- a/docs/builtIn/interactionCreate-event.md +++ b/docs/builtIn/interactionCreate-event.md @@ -59,10 +59,6 @@ module.exports = { return cmd.customIds.includes(customId); } - if (typeof cmd.customIds === "object") { - return Object.values(cmd.customIds).includes(customId); - } - return false; }); diff --git a/docs/release-notes.md b/docs/release-notes.md index 27ebdf2..33a3267 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -13,30 +13,35 @@ hide: ## 3.1.0 – Latest Version ### Features -- Introduced a new `executePrefix` property for handling prefix commands. +: - Introduced a new `executePrefix` property for handling prefix commands. - Enabled usage of `execute` property to run slash commands. -- Added console warnings when prefix commands use `execute` instead of `executePrefix`, recommending migration to the new property. -- Added console warnings when slash commands use `interactionReply` instead of `execute`, following discord.js guides and as Discord is encouraging developers to transition to using slash commands over traditional prefix commands for bots. -- Following Command Properties are now optional: - - `name` - slash commands only - - `description` - slash commands only - - `aliases` - - `minArgs` - - `permissions` - - `usage` - + * As Discord Recommends slash commands, discord-feature-handler will start migrating to follow discord.js guide to make it easier to create slash commands. +- Following Prefix Command Properties are now optional: + * `aliases` + * `minArgs` + * `permissions` + * `usage` +- Following Slash Command Properties are now optional: + * `name` - slash commands only + * `description` - slash commands only +- New Slash Command Property: + * `global`: boolean; default is false + + Allows creation of global slash command for the specific command file +- `customIds` Property is now changed to be Array of Strings and no longer accepts Objects - Recommended name changes to Command Properties: + - These changes are not mandatory until v4.0.0 - For executing prefix commands: `execute` → `executePrefix` + - Added console warnings when prefix commands use `execute` instead of `executePrefix`. - For executing slash commands: `interactionReply` → `execute` - - These changes are not mandatory until v4.0.0 + - Added console warnings when slash commands use `interactionReply` instead of `execute`. ### Deprecation -- `interactionReply` is now deprecated. It will continue to work in v3.x, but logs a console warning. +: - `interactionReply` is now deprecated. It will continue to work in v3.x, but logs a console warning. Please migrate to `execute`, as `interactionReply` will be removed in v4.0.0. ## 3.0.0 ### Features -- Added new optional options to `DiscordFeaturesHandlerOptions`: +: - Added new optional options to `DiscordFeaturesHandlerOptions`: - `slashCommandIdsToDelete`: Array of strings for deleting specific slash command IDs. - `onSlashCommandsLoading`: Object of booleans for enabling the deletion of slash commands before loading new ones. - Updated `CommandFile` properties: diff --git a/docs/setup/commands-file.md b/docs/setup/commands-file.md index e0542b8..4b5ec73 100644 --- a/docs/setup/commands-file.md +++ b/docs/setup/commands-file.md @@ -63,7 +63,7 @@ This is the maximum arguments required to execute the command

customIds Array<String>
- An Array of strings containing strings of customIds used in current command file. This can be also a key:value pairs as an object of keys where the values are the string of customIds for easier reference + An Array of strings containing strings of customIds used in current command file.

usage string
diff --git a/docs/setup/other-interaction.md b/docs/setup/other-interaction.md index 9453c69..d34ba46 100644 --- a/docs/setup/other-interaction.md +++ b/docs/setup/other-interaction.md @@ -25,20 +25,8 @@ module.exports = { .setDescription("Ping Pong Command"), /** * customIds for your interaction components - * - * or this can be a Array - * customIds: ["btnComponentId","backBtnId"] */ - customIds: { - /** - * customId for a button component - */ - buttonComponent: 'btnComponentId', - /** - * customId for second button - */ - secondButton: 'secondBtnId', - }, + customIds: ["btnComponentId","backBtnId"], usage: '', executePrefix(message, args, client) { return message.channel.send({ content: 'Pong.'}); diff --git a/docs/setup/slash-commands-file.md b/docs/setup/slash-commands-file.md index 0fcdfad..89224fc 100644 --- a/docs/setup/slash-commands-file.md +++ b/docs/setup/slash-commands-file.md @@ -48,6 +48,11 @@ module.exports = { In slash commands, you can use this permission level to add an extra layer or feature based on the level, but it will not prevent execution since slash commands are manageable at the server/guild level.

+

+ global boolean = false
+ Default is always false. This is used to define if you want the slash command to be a global slash command. For guild based slash commands you will need to go to your `.env` file to set environment variable `DEVELOPMENT_GUILD_ID`, then you will have guild based slash commands for that specific guild id. +

+
execute(interaction, client, level) Promise<Interaction>
diff --git a/mkdocs.yml b/mkdocs.yml index 6895514..2675268 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -90,6 +90,7 @@ markdown_extensions: - pymdownx.snippets - footnotes - attr_list + - def_list - md_in_html - pymdownx.superfences - pymdownx.tabbed: diff --git a/src/events/dfhInteractionCreate.js b/src/events/dfhInteractionCreate.js index 11ee71d..fdd4da2 100644 --- a/src/events/dfhInteractionCreate.js +++ b/src/events/dfhInteractionCreate.js @@ -59,10 +59,6 @@ module.exports = { return cmd.customIds.includes(customId); } - if (typeof cmd.customIds === "object") { - return Object.values(cmd.customIds).includes(customId); - } - return false; }); diff --git a/src/handlers/loadCommands.js b/src/handlers/loadCommands.js index 7b4f184..c0af5c2 100644 --- a/src/handlers/loadCommands.js +++ b/src/handlers/loadCommands.js @@ -105,9 +105,15 @@ module.exports = ({ const { clientId, guildId, toDeleteSlashCommand } = client.config; const slashCommands = []; + const globalSlashCommands = []; + client.commands.forEach((cmd) => { if ("data" in cmd && ("interactionReply" in cmd || "execute" in cmd)) { - slashCommands.push(cmd.data.toJSON()); + if ("global" in cmd && cmd.global === true) { + globalSlashCommands.push(cmd.data.toJSON()); + } else { + slashCommands.push(cmd.data.toJSON()); + } } }); @@ -117,7 +123,7 @@ module.exports = ({ } try { - if (guildId) { + if (guildId && slashCommands.length > 0) { if (onSlashCommandsLoading.delete_guild_slash_commands === true) { console.log( "[log]", @@ -154,7 +160,8 @@ module.exports = ({ "[Slash CMDs]", `Successfully Loaded a total of ${data.length} slash commands.` ); - } else { + } + if (globalSlashCommands.length > 0 || !guildId) { if ( onSlashCommandsLoading.delete_global_slash_commands === true || toDeleteSlashCommand @@ -188,10 +195,10 @@ module.exports = ({ console.log( "[log]", "[Slash CMDs]", - `Loading ${slashCommands.length} global slash commands...` + `Loading ${globalSlashCommands.length} global slash commands...` ); - const data = await rest.put(Routes.applicationGuildCommands(clientId), { - body: slashCommands, + const data = await rest.put(Routes.applicationCommands(clientId), { + body: globalSlashCommands, }); console.log( diff --git a/src/index.d.ts b/src/index.d.ts index b855951..023d660 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -242,10 +242,14 @@ interface Permissions { interface CommandFile { /** * Name of the command + * + * @optional for slash commands */ name: string; /** * Description of the command + * + * @optional for slash commands */ description: string; /** @@ -254,29 +258,46 @@ interface CommandFile { category?: string; /** * Array of strings for command aliases + * + * @default [] */ aliases?: string[]; /** * Is this command for guild only to use. DM command is not allowed + * + * @default false */ guildOnly?: boolean; /** * Permission level to use the command + * + * @default 0 */ permissions: number; /** * Minimum Arguments allowed for the command + * + * @default 0 */ minArgs: number; /** * Maximum Arguments allowed for the command * + * */ maxArgs?: number; /** * Describe how to call this command and its argument + * + * @default "" */ usage?: string; + /** + * Whether this command is a global slash command + * + * @default false + */ + global?: boolean; /** * SlashCommandBuilder object for creating this command into a slash command * @@ -290,20 +311,8 @@ interface CommandFile { * ```ts * customIds: ["myModalId", "myModalId2"] * ``` - * - * ```ts - * customIds: { - * modal: "myModalId", - * model2: "myModalId2", - * } - * ``` */ - customIds?: - | string[] - | { - [key: string]: string; - }; - + customIds?: string[]; /** * @summary Executes a prefix command call for this command. As discord.js shifts towards using interactions over prefix, this function is required to be implemented for prefix commands in next version of discord-features-handler. -- This is still useful outside of slash commands, as you can set up permission-based levels to ensure the command is for admins or bot admins/devs by using the permission levels. * diff --git a/src/utils/clientUtils.js b/src/utils/clientUtils.js index 3a55b6e..4e8c8d3 100644 --- a/src/utils/clientUtils.js +++ b/src/utils/clientUtils.js @@ -17,10 +17,14 @@ const checkCommandErrors = (cmd) => { error += `\n- MinArgs must be a Number equal to 0 or greater!`; } + if (cmd.maxArgs !== undefined && typeof cmd.maxArgs !== "number") { + error += `\n- MaxArgs must be a Number!`; + } + if (typeof cmd.maxArgs === "number") { const minArgs = cmd.minArgs || 0; if (typeof cmd.maxArgs === "number" && cmd.maxArgs < minArgs) { - error += `\n- maxArgs must be a number greater then MinArgs`; + error += `\n- MaxArgs must be a number greater then MinArgs`; } } @@ -29,13 +33,12 @@ const checkCommandErrors = (cmd) => { } if (cmd.customIds) { - if ( - !Array.isArray(cmd.customIds) && - (typeof cmd.customIds !== "object" || cmd.customIds === null) - ) { - error += `\n- customIds must be an Array of String or an object of [k:string]: string`; + if (!Array.isArray(cmd.customIds)) { + error += `\n- customIds must be an Array of String`; } else if (cmd.customIds.length < 1) { error += `\n- customIds must contain at least one String`; + } else if (!cmd.customIds.every((id) => typeof id === "string")) { + error += `\n- Every customId must be a String`; } } diff --git a/test/commands/system/about.js b/test/commands/system/about.js index 7c6cb9a..a1f83cc 100644 --- a/test/commands/system/about.js +++ b/test/commands/system/about.js @@ -9,7 +9,7 @@ module.exports = { permissions: 0, minArgs: 0, usage: "", - execute(message, args, client, level) { + executePrefix(message, args, client, level) { const embed = new EmbedBuilder() .setColor(0x800000) .setThumbnail(`${client.user.avatarURL()}`) From 3c06129be6bcb8d1b3ad9ba4115f6b03030975a4 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:50:30 -0400 Subject: [PATCH 18/28] updated slashCommandIdsToDelete to be object property --- docs/release-notes.md | 2 + docs/setup/DiscordFeaturesHandlerOptions.md | 35 +++++++++- src/handlers/loadCommands.js | 71 +++++++++++++++------ src/index.d.ts | 16 ++++- src/index.js | 23 +++++-- 5 files changed, 116 insertions(+), 31 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 33a3267..f70a7ea 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -28,6 +28,8 @@ hide: * `global`: boolean; default is false + Allows creation of global slash command for the specific command file - `customIds` Property is now changed to be Array of Strings and no longer accepts Objects +- Updated `slashCommandIdsToDelete` property to be an object containing two property: `global` and `guild` + * Each of these property are an Array containing the slash commands ids to be deleted - Recommended name changes to Command Properties: - These changes are not mandatory until v4.0.0 - For executing prefix commands: `execute` → `executePrefix` diff --git a/docs/setup/DiscordFeaturesHandlerOptions.md b/docs/setup/DiscordFeaturesHandlerOptions.md index 779bdbd..734b6c5 100644 --- a/docs/setup/DiscordFeaturesHandlerOptions.md +++ b/docs/setup/DiscordFeaturesHandlerOptions.md @@ -32,7 +32,10 @@ DiscordFeaturesHandler(client, { events: false, modules: false, }, - slashCommandIdsToDelete = [] + slashCommandIdsToDelete = { + global: [], + guild: [], + } onSlashCommandsLoading = { delete_global_slash_commands: false, delete_guild_slash_commands: false, @@ -183,11 +186,37 @@ The DiscordFeaturesHandlerOptions object contains properties to configure Discor
"" Description of your command
aliasesArray[""]Aliases of the command. You must set `[]`
guildOnlybooleanfalseIf command is guild only (not a DM command)
permissionnumber""Permission level required to use command
minArgsnumber""Minimum number of arguments required for command execution
maxArgsnumber""Maximum number of arguments required for command execution
usagestring""Show by writing an example of how to execute the command using the command argument(s) in the command call
executePrefix(message, args, client, level) func
-

slashCommandIdsToDelete Array<string> +

slashCommandIdsToDelete object
-An array of slash command IDs that should be deleted. Each string in the array represents the unique identifier of a registered slash command you want to remove from your application. +An object that contains two property that is an array of slash command IDs that should be deleted for global and guild based slash commands. Each string in the array represents the unique identifier of a registered slash command you want to remove from your application.

+ + + + + + + + + + + + + + + + + + + + + + + + +
PropertiesTypeDefaultDescription
globalArray<string>[]Array of strings of global slash commands ids to be deleted
guildArray<string>[]Array of strings of guild slash commands ids to be deleted
+

onSlashCommandsLoading object
diff --git a/src/handlers/loadCommands.js b/src/handlers/loadCommands.js index c0af5c2..ff29956 100644 --- a/src/handlers/loadCommands.js +++ b/src/handlers/loadCommands.js @@ -8,7 +8,10 @@ module.exports = ({ filesToExclude = [""], mainDirectory, logger, - slashCommandIdsToDelete = [], + slashCommandIdsToDelete = { + global: [], + guild: [], + }, onSlashCommandsLoading = { delete_global_slash_commands: false, delete_guild_slash_commands: false, @@ -61,26 +64,49 @@ module.exports = ({ } try { - slashCommandIdsToDelete.map(async (id) => { - await rest - .delete(Routes.applicationCommand(clientId, guildId, id)) - - .then(() => - console.log( - "[log]", - "[Slash CMDs]", - `Successfully deleted slash command with ID: ${id}` + if (slashCommandIdsToDelete.global.length > 0) { + slashCommandIdsToDelete.global.map(async (id) => { + await rest + .delete(Routes.applicationCommand(clientId, id)) + + .then(() => + console.log( + "[log]", + "[Slash CMDs]", + `Successfully deleted slash command with ID: ${id}` + ) ) - ) - .catch((e) => { - console.error( - "[log]", - "[Slash CMDs]", - `Failed to delete slash command with ID: ${id}`, - e.message - ); - }); - }); + .catch((e) => { + console.error( + "[log]", + "[Slash CMDs]", + `Failed to delete slash command with ID: ${id}`, + e.message + ); + }); + }); + } + if (slashCommandIdsToDelete.guild.length > 0) { + slashCommandIdsToDelete.guild.map(async (id) => { + await rest + .delete(Routes.applicationGuildCommand(clientId, guildId, id)) + .then(() => + console.log( + "[log]", + "[Slash CMDs]", + `Successfully deleted slash command with ID: ${id}` + ) + ) + .catch((e) => { + console.error( + "[log]", + "[Slash CMDs]", + `Failed to delete slash command with ID: ${id}`, + e.message + ); + }); + }); + } } catch (err) { console.error( "[log]", @@ -91,7 +117,10 @@ module.exports = ({ } }; (async () => { - if (slashCommandIdsToDelete.length > 0) { + if ( + slashCommandIdsToDelete.global.length > 0 || + slashCommandIdsToDelete.guild.length > 0 + ) { await deleteSlashCommands(); } })(); diff --git a/src/index.d.ts b/src/index.d.ts index 023d660..c849315 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -159,9 +159,21 @@ interface DiscordFeaturesHandlerOptions { onLoad_list_files?: FileLoadedLogger; /** * If you want to delete specific slash commands, you can provide an array of slash command ids to delete - * @example: ["123456789012345678"] + * @example: { + * global: ["123456789012345678"], + * guild: ["876543210987654321"] + * } */ - slashCommandIdsToDelete?: string[] | string; + slashCommandIdsToDelete?: { + /** + * Array of global slash command IDs to delete + */ + global?: string[]; + /** + * Array of guild slash command IDs to delete + */ + guild?: string[]; + }; /** * If you want to change the behavior to deleting all slash commands when the bot starts up, you can provide an object to define which slash commands to delete * diff --git a/src/index.js b/src/index.js index 0b9af66..1fa1f5f 100644 --- a/src/index.js +++ b/src/index.js @@ -59,7 +59,10 @@ const DiscordFeaturesHandler = async ( events: false, modules: false, }, - slashCommandIdsToDelete = [], + slashCommandIdsToDelete = { + global: [], + guild: [], + }, onSlashCommandsLoading = { delete_global_slash_commands: false, delete_guild_slash_commands: false, @@ -142,13 +145,23 @@ const DiscordFeaturesHandler = async ( } if ( - !Array.isArray(slashCommandIdsToDelete) || - !slashCommandIdsToDelete.every((id) => typeof id === "string") + !Array.isArray(slashCommandIdsToDelete.global) || + !slashCommandIdsToDelete.global.every((id) => typeof id === "string") + ) { + console.warn( + "slashCommandIdsToDelete.global should be an array of strings representing global slash command IDs" + ); + slashCommandIdsToDelete.global = []; + } + + if ( + !Array.isArray(slashCommandIdsToDelete.guild) || + !slashCommandIdsToDelete.guild.every((id) => typeof id === "string") ) { console.warn( - "slashCommandIdsToDelete should be an array of strings representing slash command IDs" + "slashCommandIdsToDelete.guild should be an array of strings representing guild slash command IDs" ); - slashCommandIdsToDelete = []; + slashCommandIdsToDelete.guild = []; } if (typeof process.env.DISCORD_TOKEN === "undefined") { From 553aa85e3502307aa323b4690462936b6834125d Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Mon, 18 Aug 2025 13:58:23 -0400 Subject: [PATCH 19/28] Update publish-release.yml --- .github/workflows/publish-release.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index cfdffdb..ccdf95e 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -3,11 +3,6 @@ on: push: branches: - master - pull_request: - types: [opened, reopened] - branches: - - master - jobs: create-release: runs-on: ubuntu-latest From 7655b550b53dbb9a77986cceca79c28f940bbf9d Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Mon, 18 Aug 2025 13:59:35 -0400 Subject: [PATCH 20/28] Update publish-release.yml --- .github/workflows/publish-release.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index ccdf95e..251c9f3 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -3,6 +3,12 @@ on: push: branches: - master + pull_request: + types: [opened, reopened] + branches: + - master +permissions: + contents: write jobs: create-release: runs-on: ubuntu-latest From 25f49ae4eab6f8f1e2f65cbd123b8339bce85c57 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Mon, 18 Aug 2025 14:53:33 -0400 Subject: [PATCH 21/28] Fix issue where showing console warning when slashCommandIdsToDelete is not used --- .github/workflows/publish-release.yml | 20 +++++++++++++++++++- docs/release-notes.md | 8 +++++++- src/handlers/loadCommands.js | 6 ++++-- src/index.js | 16 ++++++++-------- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index b6c3128..0756b25 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -35,7 +35,7 @@ jobs: prerelease: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - publish: + publish-package: runs-on: ubuntu-latest needs: create-release steps: @@ -50,3 +50,21 @@ jobs: run: npm ci && npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} + deploy: + runs-on: ubuntu-latest + needs: publish-package + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.x" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install mkdocs mkdocs-material + pip install "mkdocs-material[imaging]" + - name: Deploy to GitHub Pages + run: | + mkdocs gh-deploy --force diff --git a/docs/release-notes.md b/docs/release-notes.md index f70a7ea..753c03f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,7 +10,13 @@ hide: # Release Notes -## 3.1.0 – Latest Version +## 3.1.1 – Latest Version + +### Patch +- Removed console warning for when slashCommandIdsToDelete is not used. + + +## 3.1.0 ### Features : - Introduced a new `executePrefix` property for handling prefix commands. diff --git a/src/handlers/loadCommands.js b/src/handlers/loadCommands.js index ff29956..103f870 100644 --- a/src/handlers/loadCommands.js +++ b/src/handlers/loadCommands.js @@ -118,8 +118,10 @@ module.exports = ({ }; (async () => { if ( - slashCommandIdsToDelete.global.length > 0 || - slashCommandIdsToDelete.guild.length > 0 + (slashCommandIdsToDelete.global && + slashCommandIdsToDelete.global.length > 0) || + (slashCommandIdsToDelete.guild && + slashCommandIdsToDelete.guild.length > 0) ) { await deleteSlashCommands(); } diff --git a/src/index.js b/src/index.js index 1fa1f5f..8f0f5f1 100644 --- a/src/index.js +++ b/src/index.js @@ -145,23 +145,23 @@ const DiscordFeaturesHandler = async ( } if ( - !Array.isArray(slashCommandIdsToDelete.global) || - !slashCommandIdsToDelete.global.every((id) => typeof id === "string") + slashCommandIdsToDelete.global && + (!Array.isArray(slashCommandIdsToDelete.global) || + !slashCommandIdsToDelete.global.every((id) => typeof id === "string")) ) { - console.warn( + throw new TypeError( "slashCommandIdsToDelete.global should be an array of strings representing global slash command IDs" ); - slashCommandIdsToDelete.global = []; } if ( - !Array.isArray(slashCommandIdsToDelete.guild) || - !slashCommandIdsToDelete.guild.every((id) => typeof id === "string") + slashCommandIdsToDelete.guild && + (!Array.isArray(slashCommandIdsToDelete.guild) || + !slashCommandIdsToDelete.guild.every((id) => typeof id === "string")) ) { - console.warn( + throw new TypeError( "slashCommandIdsToDelete.guild should be an array of strings representing guild slash command IDs" ); - slashCommandIdsToDelete.guild = []; } if (typeof process.env.DISCORD_TOKEN === "undefined") { From 559cf029e4b07d6102b76afb7924824165a58437 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Mon, 18 Aug 2025 14:53:57 -0400 Subject: [PATCH 22/28] 3.1.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 49f2f7c..315c4f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "discord-features-handler", - "version": "3.1.0", + "version": "3.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "discord-features-handler", - "version": "3.1.0", + "version": "3.1.1", "license": "MIT", "dependencies": { "discord.js": "^14.21.0" diff --git a/package.json b/package.json index a61d6ad..621f56e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-features-handler", - "version": "3.1.0", + "version": "3.1.1", "description": "An simple discord regular and slash commands, events and modules handler with folder structure", "main": "src/index.js", "types": "src/index.d.ts", From ee3b79e87f0ca47ce53f7404a2e5dded32b40eb9 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Mon, 18 Aug 2025 14:54:10 -0400 Subject: [PATCH 23/28] Update release-notes.md --- docs/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 753c03f..f84d477 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -13,7 +13,7 @@ hide: ## 3.1.1 – Latest Version ### Patch -- Removed console warning for when slashCommandIdsToDelete is not used. +: - Removed console warning for when slashCommandIdsToDelete is not used. ## 3.1.0 From fa0ef231da2d987f34683e2174a4d5c58e755ed2 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Mon, 18 Aug 2025 14:58:37 -0400 Subject: [PATCH 24/28] Update publish-release.yml --- .github/workflows/publish-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 0756b25..a9a08e7 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -38,6 +38,7 @@ jobs: publish-package: runs-on: ubuntu-latest needs: create-release + environment: npm-publish steps: - name: Checkout repository uses: actions/checkout@v4 From 28884c3f63e6c80e6d62e55218048204524d5785 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Mon, 18 Aug 2025 15:54:24 -0400 Subject: [PATCH 25/28] combined one .yml file --- .github/workflows/deploy-docs.yml | 29 ------------------- .../{publish-release.yml => release.yml} | 12 ++++---- 2 files changed, 6 insertions(+), 35 deletions(-) delete mode 100644 .github/workflows/deploy-docs.yml rename .github/workflows/{publish-release.yml => release.yml} (88%) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml deleted file mode 100644 index bc3fc5c..0000000 --- a/.github/workflows/deploy-docs.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: deploy-docs -on: - push: - branches: - - master - pull_request: - types: [opened, reopened] - branches: - - master -permissions: - contents: write -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: "3.x" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install mkdocs mkdocs-material - pip install "mkdocs-material[imaging]" - - name: Deploy to GitHub Pages - run: | - mkdocs gh-deploy --force diff --git a/.github/workflows/publish-release.yml b/.github/workflows/release.yml similarity index 88% rename from .github/workflows/publish-release.yml rename to .github/workflows/release.yml index 4b73110..70355ee 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Create and Publish Release +name: Release and Deploy on: push: branches: @@ -45,13 +45,13 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: "lts/*" - - name: Configure npm authentication - run: | - echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN_AUTOMATION }}" > ~/.npmrc + node-version: "20" + registry-url: https://registry.npmjs.org - name: Publish package run: npm ci && npm publish - deploy: + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} + deploy-docs: runs-on: ubuntu-latest needs: publish-package steps: From 35bb0384a399535f19bcf2e416534f1d6ed65219 Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Mon, 18 Aug 2025 19:28:18 -0400 Subject: [PATCH 26/28] fixed typo in help cmd --- docs/builtIn/help-command.md | 24 +++++++++--------------- src/commands/miscellaneous/dfhHelp.js | 16 ++++++++-------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/docs/builtIn/help-command.md b/docs/builtIn/help-command.md index 5338d45..d0ef51a 100644 --- a/docs/builtIn/help-command.md +++ b/docs/builtIn/help-command.md @@ -65,9 +65,6 @@ module.exports = { return message.reply(response).catch((error) => console.log(error)); } }, - /** - * Missing data property if you plan to turn this into a slash command! - */ async execute(interaction, client, level) { await interaction.deferReply({ ephemeral: true, @@ -118,9 +115,7 @@ module.exports = { content: "An error occurred while processing your request.", ephemeral: true, }) - .catch((e) => { - - }); + .catch(() => {}); } }, }; @@ -311,21 +306,21 @@ const getSingleCmd = async (commands, name, client) => { const fieldObj = []; fieldObj.push({ name: `Category:`, - value: `${command.category}`, + value: `${slashCommand.category}`, inline: true, }); fieldObj.push({ name: `Usage:`, - value: `/${command.name}`, + value: `/${slashCommand.name}`, inline: true, }); - if (command.executePrefix) { + if (slashCommand.executePrefix) { fieldObj.push({ name: `Using prefix:`, - value: `${prefix}${command.name} ${command.usage}${ - command.aliases - ? `, ${prefix}${command.aliases.join(`, ${prefix}`)}` + value: `${prefix}${slashCommand.name} ${slashCommand.usage}${ + slashCommand.aliases + ? `, ${prefix}${slashCommand.aliases.join(`, ${prefix}`)}` : "" }`, }); @@ -336,8 +331,8 @@ const getSingleCmd = async (commands, name, client) => { name: `${client.user.tag}`, iconURL: `${client.user.avatarURL()}`, }) - .setTitle(`${command.data.name.toProperCase()} Command`) - .setDescription(command.data.description) + .setTitle(`${slashCommand.data.name.toProperCase()} Command`) + .setDescription(slashCommand.data.description) .setTimestamp() .setFields(fieldObj); @@ -394,5 +389,4 @@ const getSingleCmd = async (commands, name, client) => { }; } }; - ``` diff --git a/src/commands/miscellaneous/dfhHelp.js b/src/commands/miscellaneous/dfhHelp.js index 05f63f9..e9e5a87 100644 --- a/src/commands/miscellaneous/dfhHelp.js +++ b/src/commands/miscellaneous/dfhHelp.js @@ -296,21 +296,21 @@ const getSingleCmd = async (commands, name, client) => { const fieldObj = []; fieldObj.push({ name: `Category:`, - value: `${command.category}`, + value: `${slashCommand.category}`, inline: true, }); fieldObj.push({ name: `Usage:`, - value: `/${command.name}`, + value: `/${slashCommand.name}`, inline: true, }); - if (command.executePrefix) { + if (slashCommand.executePrefix) { fieldObj.push({ name: `Using prefix:`, - value: `${prefix}${command.name} ${command.usage}${ - command.aliases - ? `, ${prefix}${command.aliases.join(`, ${prefix}`)}` + value: `${prefix}${slashCommand.name} ${slashCommand.usage}${ + slashCommand.aliases + ? `, ${prefix}${slashCommand.aliases.join(`, ${prefix}`)}` : "" }`, }); @@ -321,8 +321,8 @@ const getSingleCmd = async (commands, name, client) => { name: `${client.user.tag}`, iconURL: `${client.user.avatarURL()}`, }) - .setTitle(`${command.data.name.toProperCase()} Command`) - .setDescription(command.data.description) + .setTitle(`${slashCommand.data.name.toProperCase()} Command`) + .setDescription(slashCommand.data.description) .setTimestamp() .setFields(fieldObj); From b3dac59f7385c4a942e920abd62eb1b82f783b5c Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Mon, 18 Aug 2025 21:23:46 -0400 Subject: [PATCH 27/28] Update loadCommands.js --- src/handlers/loadCommands.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/handlers/loadCommands.js b/src/handlers/loadCommands.js index 103f870..c9af697 100644 --- a/src/handlers/loadCommands.js +++ b/src/handlers/loadCommands.js @@ -140,10 +140,9 @@ module.exports = ({ client.commands.forEach((cmd) => { if ("data" in cmd && ("interactionReply" in cmd || "execute" in cmd)) { + slashCommands.push(cmd.data.toJSON()); if ("global" in cmd && cmd.global === true) { globalSlashCommands.push(cmd.data.toJSON()); - } else { - slashCommands.push(cmd.data.toJSON()); } } }); From c669ca47cabd5246fef7716df25024d7eec15bca Mon Sep 17 00:00:00 2001 From: Brandon <14009481+bng94@users.noreply.github.com> Date: Mon, 18 Aug 2025 21:25:50 -0400 Subject: [PATCH 28/28] 3.1.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 315c4f5..01cc032 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "discord-features-handler", - "version": "3.1.1", + "version": "3.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "discord-features-handler", - "version": "3.1.1", + "version": "3.1.2", "license": "MIT", "dependencies": { "discord.js": "^14.21.0" diff --git a/package.json b/package.json index 621f56e..966540e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-features-handler", - "version": "3.1.1", + "version": "3.1.2", "description": "An simple discord regular and slash commands, events and modules handler with folder structure", "main": "src/index.js", "types": "src/index.d.ts",