js-discord-modularcommand 2.4.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,69 +1,286 @@
1
- # JS Discord ModularCommand
1
+ # Discord.js Modular Command
2
2
 
3
- A module to create and manage modular commands in a simple way for Discord.js bots.
3
+ [![npm version](https://img.shields.io/npm/v/js-discord-modularcommand.svg?style=flat-square)](https://www.npmjs.com/package/js-discord-modularcommand)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
4
5
 
5
- ## What is it for?
6
+ A powerful and elegant library for creating modular, feature-rich slash commands for your [Discord.js](https://discord.js.org/) bot.
6
7
 
7
- This library simplifies the creation and management of slash commands for [Discord.js](https://discord.js.org/). It allows you to structure your commands in a modular way, making it easier to handle logic, permissions, cooldowns, localizations, and interactive components like buttons and modals.
8
+ `js-discord-modularcommand` simplifies command management by providing a clean, chainable structure for defining commands, handling interactions, and managing localizations. Move away from boilerplate code and focus on what truly matters: your bot's logic.
8
9
 
9
- ## How to use it?
10
+ ## Features
10
11
 
11
- First, install the package in your project:
12
+ - **Modular by Design:** Structure each command in its own file for a clean and scalable project architecture.
13
+ - **Effortless Localization:** Built-in support for multiple languages for command descriptions, options, and in-command responses.
14
+ - **Interactive Components Made Easy:** Fluent builders for creating and managing Buttons, Modals, and Select Menus with their own handlers, all within the command's context.
15
+ - **Chainable Configuration:** Use a fluent, chainable API to configure every aspect of your command, from descriptions and options to permissions and cooldowns.
16
+ - **Simplified Handlers:** The library abstracts away the complexity of handling different interaction types. You just provide the logic.
17
+
18
+ ## Installation
19
+
20
+ Install the package using npm or your favorite package manager:
12
21
 
13
22
  ```sh
14
- npm install js-discord-modularcommand@latest
23
+ npm install js-discord-modularcommand
15
24
  ```
16
25
 
17
- Then, you can create your commands in a modular fashion. Here is a basic example of a `ping` command:
26
+ ## Getting Started
27
+
28
+ The core of the library is the `ModularCommand` class. You create an instance of it for each command and chain methods to configure it.
29
+
30
+ Here is a basic example of a `ping` command:
18
31
 
19
32
  ```javascript
20
- // filepath: commands/ping.js
33
+ // filepath: /commands/ping.js
21
34
  const { ModularCommand, RegisterCommand } = require('js-discord-modularcommand');
22
- const { PermissionFlagsBits, Locale } = require('discord.js');
35
+ const { Locale } = require('discord.js');
36
+
37
+ // 1. Create a new command instance
38
+ const pingCommand = new ModularCommand('ping');
23
39
 
24
- // Create a new command instance
25
- const PingCommand = new ModularCommand('ping');
40
+ // 2. Configure the command using chainable methods
41
+ pingCommand.setDescription('Replies with Pong!')
26
42
 
27
- // Set the description
28
- PingCommand.setDescription('Sends a ping message!');
43
+ // Optional: Set a 5-second cooldown
44
+ pingCommand.setCooldown(5)
45
+
46
+ // Optional: Add localized descriptions for the command itself
47
+ pingCommand.setLocalizationDescription({
48
+ [Locale.EnglishUS]: 'Replies with Pong!',
49
+ [Locale.SpanishLATAM]: '¡Responde con Pong!',
50
+ })
51
+
52
+ // Optional: Define response phrases for different languages
53
+ pingCommand.setLocalizationPhrases({
54
+ [Locale.EnglishUS]: {
55
+ 'pong_reply': 'Pong! 🏓',
56
+ },
57
+ [Locale.SpanishLATAM]: {
58
+ 'pong_reply': '¡Pong! 🏓',
59
+ }
60
+ })
29
61
 
30
- // Optional: Add a permission check
31
- PingCommand.setPermissionCheck(async ({ interaction }) => {
32
- return interaction.member.permissions.has(PermissionFlagsBits.Administrator);
62
+ // 3. Define the execution logic
63
+ // The 'locale' object contains the phrases for the user's language
64
+ pingCommand.setExecute(async ({ interaction, locale }) => {
65
+ await interaction.reply({
66
+ content: locale['pong_reply']
67
+ });
33
68
  });
34
69
 
35
- // Optional: Localization to use with 'locale'
36
- PingCommand.setLocalizationPhrases({
70
+ // 4. Export the command for the handler
71
+ module.exports = RegisterCommand(pingCommand);
72
+ ```
73
+
74
+ ## Advanced Usage: Interactive Components
75
+
76
+ Easily add interactive components to your commands.
77
+
78
+ ### Buttons
79
+
80
+ Create buttons and attach specific logic to each one.
81
+
82
+ ```javascript
83
+ // filepath: /commands/vote.js
84
+ const { ModularCommand, RegisterCommand } = require('js-discord-modularcommand');
85
+ const { ButtonStyle, Locale, MessageFlags } = require('discord.js');
86
+ const { ActionRowBuilder } = require('@discordjs/builders');
87
+
88
+ const voteCommand = new ModularCommand('votecommand');
89
+
90
+ voteCommand.setDescription('Starts a simple poll.');
91
+
92
+ voteCommand.setLocalizationPhrases({
37
93
  [Locale.EnglishUS]: {
38
- response: 'Replies with Pong!',
94
+ 'poll_question': 'What is your favorite color?',
95
+ 'votecommand.yes': 'Green',
96
+ 'votecommand.no': 'Blue',
97
+ 'reply.yes': 'You voted for Green!',
98
+ 'reply.no': 'You voted for Blue!',
39
99
  },
40
100
  [Locale.SpanishLATAM]: {
41
- response: 'Responde con Pong!',
101
+ 'poll_question': '¿Cuál es tu color favorito?',
102
+ 'votecommand.yes': 'Verde',
103
+ 'votecommand.no': 'Azul',
104
+ 'reply.yes': '¡Has votado por el Verde!',
105
+ 'reply.no': '¡Has votado por el Azul!',
42
106
  }
43
107
  });
44
108
 
45
- // Optional: Set a cooldown (in seconds) by default is 3 seconds
46
- PingCommand.setCooldown(5);
109
+ // Create and handle the "Yes" button
110
+ const yesButton = voteCommand.addButton('yes', async ({ interaction, locale }) => {
111
+ await interaction.reply({
112
+ content: locale['reply.yes'],
113
+ flags: MessageFlags.Ephemeral
114
+ });
115
+ });
116
+
117
+ // Create and handle the "No" button
118
+ const noButton = voteCommand.addButton('no', async ({ interaction, locale }) => {
119
+ await interaction.reply({
120
+ content: locale['reply.no'],
121
+ flags: MessageFlags.Ephemeral
122
+ });
123
+ });
47
124
 
48
- // Set the command's description
49
- PingCommand.setDescription('Replies with Pong!');
125
+ // Customize the underlying discord.js button
126
+ yesButton.getButton().setStyle(ButtonStyle.Success);
127
+ noButton.getButton().setStyle(ButtonStyle.Primary);
50
128
 
51
- // Optional: Add more localization descriptions for the command itself
52
- PingCommand.setLocalizationDescription({
53
- [Locale.EnglishUS]: 'Replies with Pong!',
54
- [Locale.SpanishLATAM]: 'Responde con Pong!',
129
+ // Main command execution: sends the message with the buttons
130
+ voteCommand.setExecute(async ({ interaction, locale }) => {
131
+ const row = new ActionRowBuilder();
132
+
133
+ row.addComponents(
134
+ yesButton.build(locale), // .build(locale) applies the correct localization
135
+ noButton.build(locale)
136
+ );
137
+
138
+ await interaction.reply({
139
+ content: locale['poll_question'],
140
+ components: [row]
141
+ });
142
+ });
143
+
144
+ module.exports = RegisterCommand(voteCommand);
145
+ ```
146
+
147
+ ### Select Menus
148
+
149
+ Build and handle string select menus seamlessly.
150
+
151
+ ```javascript
152
+ // filepath: /commands/starter.js
153
+ const { ModularCommand, RegisterCommand } = require('js-discord-modularcommand');
154
+ const { ActionRowBuilder } = require('@discordjs/builders');
155
+ const { Locale, MessageFlags } = require('discord.js');
156
+
157
+ const starterCommand = new ModularCommand('starter');
158
+
159
+ starterCommand.setDescription('Choose your starter Pokémon.')
160
+
161
+ starterCommand.setLocalizationPhrases({
162
+ [Locale.EnglishUS]: {
163
+ 'select_prompt': 'Please select your starter:',
164
+ 'menuselection.placeholder': 'Make a selection!',
165
+ 'menuselection.bulbasaur.label': 'Bulbasaur',
166
+ 'menuselection.bulbasaur.description': 'The Seed Pokémon.',
167
+ 'menuselection.charmander.label': 'Charmander',
168
+ 'menuselection.charmander.description': 'The Lizard Pokémon.',
169
+ 'menuselection.squirtle.label': 'Squirtle',
170
+ 'menuselection.squirtle.description': 'The Tiny Turtle Pokémon.',
171
+ 'response': 'You chose {selection}!',
172
+ },
173
+ [Locale.SpanishLATAM]: {
174
+ 'select_prompt': 'Por favor, elige tu inicial:',
175
+ 'menuselection.placeholder': '¡Haz una selección!',
176
+ 'menuselection.bulbasaur.label': 'Bulbasaur',
177
+ 'menuselection.bulbasaur.description': 'El Pokémon Semilla.',
178
+ 'menuselection.charmander.label': 'Charmander',
179
+ 'menuselection.charmander.description': 'El Pokémon Lagartija.',
180
+ 'menuselection.squirtle.label': 'Squirtle',
181
+ 'menuselection.squirtle.description': 'El Pokémon Agua.',
182
+ 'response': '¡Elegiste a {selection}!',
183
+ }
184
+ });
185
+
186
+ const starterMenu = starterCommand.addSelectMenu('menuselection');
187
+
188
+ // Value must match the key in localization phrases
189
+ starterMenu.addOption('bulbasaur')
190
+ starterMenu.addOption('charmander')
191
+ starterMenu.addOption('squirtle')
192
+
193
+ starterMenu.setExecute(async ({ interaction, selected, locale }) => {
194
+ // 'selected' directly gives you the value of the chosen option
195
+ const selectionLabel = locale[`menuselection.${selected}.label`];
196
+ await interaction.update({
197
+ content: locale['response'].replace('{selection}', selectionLabel),
198
+ components: [] // Remove menu after selection
199
+ });
55
200
  });
56
201
 
57
- // Set the executor function
58
- PingCommand.setExecute(async ({ interaction, locale }) => {
59
- await interaction.reply(locale['response']);
202
+ starterCommand.setExecute(async ({ interaction, locale }) => {
203
+ const row = new ActionRowBuilder()
204
+ row.addComponents(starterMenu.build(locale));
205
+
206
+ await interaction.reply({
207
+ content: locale['select_prompt'],
208
+ components: [row],
209
+ flags: MessageFlags.Ephemeral
210
+ });
60
211
  });
61
212
 
62
- module.exports = RegisterCommand(PingCommand)
213
+ module.exports = RegisterCommand(starterCommand);
63
214
  ```
64
215
 
65
- In your main file, you can load the commands and register their executors with your Discord client.
216
+ ### Modals (Pop-up Forms)
217
+
218
+ Display pop-up forms to collect detailed user input.
219
+
220
+ ```javascript
221
+ // filepath: /commands/feedback.js
222
+ const { ModularCommand, RegisterCommand } = require('js-discord-modularcommand');
223
+ const { TextInputStyle, Locale, MessageFlags } = require('discord.js');
224
+
225
+ const feedbackCommand = new ModularCommand('feedback');
226
+
227
+ feedbackCommand.setDescription('Submit feedback about the bot.')
228
+
229
+ feedbackCommand.setLocalizationPhrases({
230
+ [Locale.EnglishUS]: {
231
+ 'form.title': 'Feedback Form',
232
+ 'form.subject.label': 'Subject',
233
+ 'form.subject.placeholder': 'e.g., Feature Request',
234
+ 'form.message.label': 'Message',
235
+ 'form.message.placeholder': 'Your detailed feedback here...',
236
+ 'success_reply': 'Thank you for your feedback!',
237
+ },
238
+ [Locale.SpanishLATAM]: {
239
+ 'form.title': 'Formulario de Comentarios',
240
+ 'form.subject.label': 'Asunto',
241
+ 'form.subject.placeholder': 'Ej: Solicitud de función',
242
+ 'form.message.label': 'Mensaje',
243
+ 'form.message.placeholder': 'Tus comentarios detallados aquí...',
244
+ 'success_reply': '¡Gracias por tus comentarios!',
245
+ }
246
+ });
247
+
248
+ const feedbackModal = feedbackCommand.addModal('form');
249
+
250
+ // Define text inputs
251
+ const subjectInput = feedbackModal.newTextInput('subject')
252
+ .setStyle(TextInputStyle.Short)
253
+ .setRequired(true)
254
+ .data
255
+ .custom_id;
256
+
257
+ const messageInput = feedbackModal.newTextInput('message')
258
+ .setStyle(TextInputStyle.Paragraph)
259
+ .setRequired(true)
260
+ .data
261
+ .custom_id;
262
+
263
+ // This function runs when the user submits the modal
264
+ feedbackModal.setExecute(async ({ interaction, args, locale }) => {
265
+ const subject = args[subjectInput];
266
+ const message = args[messageInput];
267
+
268
+ // Process the data
269
+ console.log(`New Feedback: ${subject} - ${message}`);
270
+ await interaction.reply({
271
+ content: locale['success_reply'],
272
+ flags: MessageFlags.Ephemeral
273
+ });
274
+ });
275
+
276
+ // This function runs when the /feedback command is used, showing the modal
277
+ feedbackCommand.setExecute(async ({ interaction, locale }) => {
278
+ await interaction.showModal(feedbackModal.build(locale));
279
+ });
280
+
281
+ module.exports = RegisterCommand(feedbackCommand);
282
+ ```
66
283
 
67
284
  ## License
68
285
 
69
- This project is under the MIT License. See the [LICENSE](LICENSE) file
286
+ This project is licensed under the MIT License. See the `LICENSE` file for details.
@@ -23,7 +23,7 @@ type InteractionHandler = (args: InteractionHandlerArgs) => Promise<boolean | un
23
23
  * @description Creates a modular command handler function for the Discord client.
24
24
  * @param {ClientWithCommands} client The Discord client instance with a commands collection.
25
25
  * @param {InteractionHandler} customHandler A custom function to handle interactions before the default logic.
26
- * @returns {(interaction: BaseInteraction) = Promise<void>} The main interaction handler function.
26
+ * @returns {(interaction: BaseInteraction) => Promise<void>} The main interaction handler function.
27
27
  */
28
28
  export default function ModularCommandHandler(client: ClientWithCommands, customHandler: InteractionHandler): (interaction: BaseInteraction) => Promise<void>;
29
29
  export {};
@@ -15,7 +15,7 @@ const locales_1 = require("./locales");
15
15
  * @description Creates a modular command handler function for the Discord client.
16
16
  * @param {ClientWithCommands} client The Discord client instance with a commands collection.
17
17
  * @param {InteractionHandler} customHandler A custom function to handle interactions before the default logic.
18
- * @returns {(interaction: BaseInteraction) = Promise<void>} The main interaction handler function.
18
+ * @returns {(interaction: BaseInteraction) => Promise<void>} The main interaction handler function.
19
19
  */
20
20
  function ModularCommandHandler(client, customHandler) {
21
21
  if (!client.commands) {
@@ -41,12 +41,15 @@ function ModularCommandHandler(client, customHandler) {
41
41
  commandName = interaction.customId.split('_')[0];
42
42
  }
43
43
  else {
44
- const errorMessage = locales_1.LOCALE_ERROR[interaction.locale];
44
+ const errorMessage = locales_1.LOCALE_ERROR[interaction.locale] || 'An unexpected error occurred.';
45
45
  if (interaction.replied || interaction.deferred) {
46
46
  await interaction.followUp({ content: errorMessage, flags: discord_js_1.MessageFlags.Ephemeral });
47
47
  }
48
48
  else {
49
- await interaction.reply({ content: errorMessage, flags: discord_js_1.MessageFlags.Ephemeral });
49
+ // Type guard to ensure reply method exists
50
+ if ('reply' in interaction) {
51
+ await interaction.reply({ content: errorMessage, flags: discord_js_1.MessageFlags.Ephemeral });
52
+ }
50
53
  }
51
54
  console.error(`Interaction does not have a commandName or customId: ${interaction.id}`);
52
55
  return;
@@ -63,6 +66,9 @@ function ModularCommandHandler(client, customHandler) {
63
66
  else if (interaction.isButton() && command.buttonExecute) {
64
67
  await command.buttonExecute(interaction);
65
68
  }
69
+ else if (interaction.isStringSelectMenu() && command.selectMenuExecute) {
70
+ await command.selectMenuExecute(interaction);
71
+ }
66
72
  else if (interaction.isMessageComponent() && command.componentExecute) {
67
73
  await command.componentExecute(interaction);
68
74
  }
@@ -71,12 +77,14 @@ function ModularCommandHandler(client, customHandler) {
71
77
  }
72
78
  }
73
79
  catch (error) {
74
- const errorMessage = locales_1.LOCALE_ERROR[interaction.locale];
80
+ const errorMessage = locales_1.LOCALE_ERROR[interaction.locale] || 'An unexpected error occurred.';
75
81
  if (interaction.replied || interaction.deferred) {
76
82
  await interaction.followUp({ content: errorMessage, flags: discord_js_1.MessageFlags.Ephemeral });
77
83
  }
78
84
  else {
79
- await interaction.reply({ content: errorMessage, flags: discord_js_1.MessageFlags.Ephemeral });
85
+ if ('reply' in interaction) {
86
+ await interaction.reply({ content: errorMessage, flags: discord_js_1.MessageFlags.Ephemeral });
87
+ }
80
88
  }
81
89
  console.error(`Error handling interaction: ${interaction.id}`, error);
82
90
  }
@@ -6,7 +6,8 @@
6
6
  import { LocalizationMap } from 'discord.js';
7
7
  import ModularModal from './modularmodal.js';
8
8
  import ModularButton from './modularbutton.js';
9
- import { ButtonExecuteFunction, CommandExecuteFunction, CommandOption, ComponentExecuteFunction, ModalExecuteFunction, PermissionCheckFunction } from './types.js';
9
+ import ModularSelectMenu from './modularselectmenu.js';
10
+ import { ButtonExecuteFunction, CommandExecuteFunction, CommandOption, ComponentExecuteFunction, ModalExecuteFunction, PermissionCheckFunction, SelectMenuExecuteFunction } from './types.js';
10
11
  /**
11
12
  * @description Represents a modular command that can be registered with Discord.js.
12
13
  * It allows for dynamic command creation and execution in a simple way.
@@ -47,7 +48,7 @@ export default class ModularCommand {
47
48
  /** (Optional) The function to handle modal submissions. */
48
49
  modalExecute?: ModalExecuteFunction;
49
50
  /** A record of handlers for specific component custom IDs. */
50
- customIdHandlers: Record<string, CommandExecuteFunction | ButtonExecuteFunction | ModalExecuteFunction>;
51
+ customIdHandlers: Record<string, CommandExecuteFunction | ButtonExecuteFunction | ModalExecuteFunction | SelectMenuExecuteFunction>;
51
52
  /** The command's cooldown time in seconds. */
52
53
  cooldown: number;
53
54
  /** Whether the command is marked as Not Safe For Work (NSFW). */
@@ -64,6 +65,10 @@ export default class ModularCommand {
64
65
  buttons: Map<string, ModularButton>;
65
66
  /** An array containing all ModularButton instances for easy access. */
66
67
  buttonsArray: ModularButton[];
68
+ /** A map of select menus associated with this command, keyed by the select menu's custom ID. */
69
+ selectMenus: Map<string, ModularSelectMenu>;
70
+ /** An array containing all ModularSelectMenu instances for easy access. */
71
+ selectMenusArray: ModularSelectMenu[];
67
72
  constructor(name: string);
68
73
  /**
69
74
  * Sets the description of the command.
@@ -92,7 +97,6 @@ export default class ModularCommand {
92
97
  setLocalizationOptions(localizations: LocalizationMap): this;
93
98
  /**
94
99
  * Sets the localization phrases for the command.
95
- * Accepts a partial record, so not all locales need to be provided.
96
100
  * @param {LocalizationMap} localizationPhrases The localization phrases.
97
101
  * @returns {ModularCommand} The command instance for chaining.
98
102
  */
@@ -136,10 +140,10 @@ export default class ModularCommand {
136
140
  /**
137
141
  * Adds a custom ID handler for the command.
138
142
  * @param {string} customId The custom ID to match.
139
- * @param {CommandExecuteFunction | ButtonExecuteFunction | ModalExecuteFunction} handlerFunction The function to execute when the custom ID matches.
143
+ * @param {CommandExecuteFunction | ButtonExecuteFunction | ModalExecuteFunction | SelectMenuExecuteFunction} handlerFunction The function to execute when the custom ID matches.
140
144
  * @returns {ModularCommand} The command instance for chaining.
141
145
  */
142
- addCustomIDHandler(customId: string, handlerFunction: CommandExecuteFunction | ButtonExecuteFunction | ModalExecuteFunction): this;
146
+ addCustomIDHandler(customId: string, handlerFunction: CommandExecuteFunction | ButtonExecuteFunction | ModalExecuteFunction | SelectMenuExecuteFunction): this;
143
147
  /**
144
148
  * Creates a new modal for the command.
145
149
  * @param {string} modalId The ID for the modal.
@@ -153,4 +157,10 @@ export default class ModularCommand {
153
157
  * @return {ModularButton} The created button instance.
154
158
  */
155
159
  addButton(customId: string, execute: ButtonExecuteFunction): ModularButton;
160
+ /**
161
+ * Creates a new select menu for the command.
162
+ * @param {string} selectMenuId The ID for the select menu.
163
+ * @returns {ModularSelectMenu} The created select menu instance.
164
+ */
165
+ addSelectMenu(selectMenuId: string): ModularSelectMenu;
156
166
  }
@@ -14,6 +14,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
14
14
  const discord_js_1 = require("discord.js");
15
15
  const modularmodal_js_1 = __importDefault(require("./modularmodal.js"));
16
16
  const modularbutton_js_1 = __importDefault(require("./modularbutton.js"));
17
+ const modularselectmenu_js_1 = __importDefault(require("./modularselectmenu.js")); // <- ADD THIS
17
18
  // =================================================================================================
18
19
  // Class: ModularCommand
19
20
  // =================================================================================================
@@ -50,7 +51,9 @@ class ModularCommand {
50
51
  this.cooldown = 3;
51
52
  this.modals = new Map();
52
53
  this.buttons = new Map();
54
+ this.selectMenus = new Map(); // <- ADD THIS
53
55
  this.buttonsArray = [];
56
+ this.selectMenusArray = []; // <- ADD THIS
54
57
  this.isNSFW = false;
55
58
  }
56
59
  /**
@@ -98,7 +101,6 @@ class ModularCommand {
98
101
  }
99
102
  /**
100
103
  * Sets the localization phrases for the command.
101
- * Accepts a partial record, so not all locales need to be provided.
102
104
  * @param {LocalizationMap} localizationPhrases The localization phrases.
103
105
  * @returns {ModularCommand} The command instance for chaining.
104
106
  */
@@ -166,7 +168,7 @@ class ModularCommand {
166
168
  /**
167
169
  * Adds a custom ID handler for the command.
168
170
  * @param {string} customId The custom ID to match.
169
- * @param {CommandExecuteFunction | ButtonExecuteFunction | ModalExecuteFunction} handlerFunction The function to execute when the custom ID matches.
171
+ * @param {CommandExecuteFunction | ButtonExecuteFunction | ModalExecuteFunction | SelectMenuExecuteFunction} handlerFunction The function to execute when the custom ID matches.
170
172
  * @returns {ModularCommand} The command instance for chaining.
171
173
  */
172
174
  addCustomIDHandler(customId, handlerFunction) {
@@ -196,5 +198,16 @@ class ModularCommand {
196
198
  this.buttonsArray.push(button);
197
199
  return button;
198
200
  }
201
+ /**
202
+ * Creates a new select menu for the command.
203
+ * @param {string} selectMenuId The ID for the select menu.
204
+ * @returns {ModularSelectMenu} The created select menu instance.
205
+ */
206
+ addSelectMenu(selectMenuId) {
207
+ const menu = new modularselectmenu_js_1.default(selectMenuId, this);
208
+ this.selectMenus.set(selectMenuId, menu);
209
+ this.selectMenusArray.push(menu);
210
+ return menu;
211
+ }
199
212
  }
200
213
  exports.default = ModularCommand;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @file Contains the structure and logic for creating modular select menus.
3
+ * @author vicentefelipechile
4
+ * @license MIT
5
+ */
6
+ import { StringSelectMenuBuilder, StringSelectMenuOptionBuilder } from "discord.js";
7
+ import { LocaleKey, SelectMenuExecuteFunction } from "./types";
8
+ import ModularCommand from "./modularcommand";
9
+ /**
10
+ * @class ModularSelectMenu
11
+ * @description Represents a modular select menu that can be dynamically created and managed.
12
+ */
13
+ export default class ModularSelectMenu {
14
+ /** The Discord.js StringSelectMenuBuilder instance. */
15
+ selectMenuObject: StringSelectMenuBuilder;
16
+ /** The unique custom ID for the select menu, formatted as `${command.name}_${selectMenuId}`. */
17
+ customId: string;
18
+ /** The base ID for the select menu, used for localization. */
19
+ selectMenuId: string;
20
+ /** A map to store the option components of the select menu. */
21
+ options: Map<string, StringSelectMenuOptionBuilder>;
22
+ /** The command instance to which this select menu belongs. */
23
+ command: ModularCommand;
24
+ /** The function to execute when the select menu is interacted with. */
25
+ execute: SelectMenuExecuteFunction;
26
+ /**
27
+ * @description Creates a new ModularSelectMenu instance.
28
+ * @param {string} selectMenuId The base ID for the select menu.
29
+ * @param {ModularCommand} command The command that this select menu is associated with.
30
+ */
31
+ constructor(selectMenuId: string, command: ModularCommand);
32
+ /**
33
+ * @description Retrieves the underlying StringSelectMenuBuilder instance.
34
+ * @returns {StringSelectMenuBuilder} The StringSelectMenuBuilder instance.
35
+ */
36
+ getSelectMenu(): StringSelectMenuBuilder;
37
+ /**
38
+ * @description Retrieves the custom ID of the select menu.
39
+ * @returns {string} The custom ID of the select menu.
40
+ */
41
+ getCustomId(): string;
42
+ /**
43
+ * @description Sets the execution function for the select menu's submission event.
44
+ * @param {SelectMenuExecuteFunction} executeFunction The function to run when the select menu is used.
45
+ * @returns {this} The current ModularSelectMenu instance for method chaining.
46
+ */
47
+ setExecute(executeFunction: SelectMenuExecuteFunction): this;
48
+ /**
49
+ * @description Creates a new option for the select menu.
50
+ * The label and description should be set in your localization files.
51
+ * @param {string} value The unique value for this option.
52
+ * @returns {StringSelectMenuOptionBuilder} The created option instance for further configuration (e.g., `setDefault`).
53
+ */
54
+ addOption(value: string): StringSelectMenuOptionBuilder;
55
+ /**
56
+ * @description Builds the final select menu object, applying localized placeholder, labels, and descriptions.
57
+ * @param {LocaleKey} locale The localization object containing translated texts.
58
+ * @returns {StringSelectMenuBuilder} The fully constructed select menu object ready to be sent to a user.
59
+ */
60
+ build(locale: LocaleKey): StringSelectMenuBuilder;
61
+ }
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ /**
3
+ * @file Contains the structure and logic for creating modular select menus.
4
+ * @author vicentefelipechile
5
+ * @license MIT
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ const discord_js_1 = require("discord.js");
9
+ /**
10
+ * @class ModularSelectMenu
11
+ * @description Represents a modular select menu that can be dynamically created and managed.
12
+ */
13
+ class ModularSelectMenu {
14
+ /**
15
+ * @description Creates a new ModularSelectMenu instance.
16
+ * @param {string} selectMenuId The base ID for the select menu.
17
+ * @param {ModularCommand} command The command that this select menu is associated with.
18
+ */
19
+ constructor(selectMenuId, command) {
20
+ /** The function to execute when the select menu is interacted with. */
21
+ this.execute = async () => { };
22
+ this.selectMenuId = selectMenuId;
23
+ this.command = command;
24
+ this.customId = `${command.name}_${selectMenuId}`;
25
+ this.selectMenuObject = new discord_js_1.StringSelectMenuBuilder().setCustomId(this.customId);
26
+ this.options = new Map();
27
+ }
28
+ /**
29
+ * @description Retrieves the underlying StringSelectMenuBuilder instance.
30
+ * @returns {StringSelectMenuBuilder} The StringSelectMenuBuilder instance.
31
+ */
32
+ getSelectMenu() {
33
+ return this.selectMenuObject;
34
+ }
35
+ /**
36
+ * @description Retrieves the custom ID of the select menu.
37
+ * @returns {string} The custom ID of the select menu.
38
+ */
39
+ getCustomId() {
40
+ return this.customId;
41
+ }
42
+ /**
43
+ * @description Sets the execution function for the select menu's submission event.
44
+ * @param {SelectMenuExecuteFunction} executeFunction The function to run when the select menu is used.
45
+ * @returns {this} The current ModularSelectMenu instance for method chaining.
46
+ */
47
+ setExecute(executeFunction) {
48
+ this.execute = executeFunction;
49
+ return this;
50
+ }
51
+ /**
52
+ * @description Creates a new option for the select menu.
53
+ * The label and description should be set in your localization files.
54
+ * @param {string} value The unique value for this option.
55
+ * @returns {StringSelectMenuOptionBuilder} The created option instance for further configuration (e.g., `setDefault`).
56
+ */
57
+ addOption(value) {
58
+ const option = new discord_js_1.StringSelectMenuOptionBuilder().setValue(value);
59
+ this.options.set(value, option);
60
+ return option;
61
+ }
62
+ /**
63
+ * @description Builds the final select menu object, applying localized placeholder, labels, and descriptions.
64
+ * @param {LocaleKey} locale The localization object containing translated texts.
65
+ * @returns {StringSelectMenuBuilder} The fully constructed select menu object ready to be sent to a user.
66
+ */
67
+ build(locale) {
68
+ const placeholderKey = `${this.command.name}.${this.selectMenuId}.placeholder`;
69
+ if (locale[placeholderKey]) {
70
+ this.selectMenuObject.setPlaceholder(locale[placeholderKey]);
71
+ }
72
+ const builtOptions = [];
73
+ this.options.forEach((optionBuilder, value) => {
74
+ const labelKey = `${this.command.name}.${this.selectMenuId}.${value}.label`;
75
+ const descriptionKey = `${this.command.name}.${this.selectMenuId}.${value}.description`;
76
+ // Set label from locale, fallback to the value if not found
77
+ optionBuilder.setLabel(locale[labelKey] || value);
78
+ if (locale[descriptionKey]) {
79
+ optionBuilder.setDescription(locale[descriptionKey]);
80
+ }
81
+ builtOptions.push(optionBuilder);
82
+ });
83
+ if (builtOptions.length > 0) {
84
+ this.selectMenuObject.setOptions(builtOptions);
85
+ }
86
+ return this.selectMenuObject;
87
+ }
88
+ }
89
+ exports.default = ModularSelectMenu;
@@ -8,7 +8,7 @@ import ModularCommand from "./modularcommand";
8
8
  /**
9
9
  * @description Registers an array of modular commands, building their final `CommandData` objects.
10
10
  * This function processes the command definitions, sets up command builders, and assigns the execution logic.
11
- * @param {ModularCommand[]} commands An array of ModularCommand instances.
11
+ * @param {ModularCommand[] | ModularCommand} commands An array of or a single ModularCommand instance.
12
12
  * @returns {CommandData[]} An array of command data objects ready for the Discord.js client.
13
13
  */
14
14
  export default function RegisterCommand(commands: ModularCommand[] | ModularCommand): CommandData[];
@@ -181,13 +181,35 @@ function createButtonExecutor(command) {
181
181
  });
182
182
  };
183
183
  }
184
+ /**
185
+ * @description Creates the execution function for select menu interactions.
186
+ * @param {ModularCommand} command The command to create the executor for.
187
+ * @returns {Function|undefined} The async function or undefined if not needed.
188
+ */
189
+ function createSelectMenuExecutor(command) {
190
+ if (command.selectMenus.size === 0)
191
+ return undefined;
192
+ return async (interaction) => {
193
+ const menuObject = command.selectMenus.get(interaction.customId.split('_')[1]);
194
+ if (!menuObject)
195
+ return;
196
+ // The user's selected option value is abstracted into `selected`.
197
+ await menuObject.execute({
198
+ interaction,
199
+ command,
200
+ locale: getCommandLocale(command, interaction),
201
+ message: interaction.message,
202
+ selected: interaction.values[0],
203
+ });
204
+ };
205
+ }
184
206
  // =================================================================================================
185
207
  // Main Registration Function
186
208
  // =================================================================================================
187
209
  /**
188
210
  * @description Registers an array of modular commands, building their final `CommandData` objects.
189
211
  * This function processes the command definitions, sets up command builders, and assigns the execution logic.
190
- * @param {ModularCommand[]} commands An array of ModularCommand instances.
212
+ * @param {ModularCommand[] | ModularCommand} commands An array of or a single ModularCommand instance.
191
213
  * @returns {CommandData[]} An array of command data objects ready for the Discord.js client.
192
214
  */
193
215
  function RegisterCommand(commands) {
@@ -249,6 +271,7 @@ function RegisterCommand(commands) {
249
271
  componentExecute: createComponentExecutor(command),
250
272
  modalExecute: createModalExecutor(command),
251
273
  buttonExecute: createButtonExecutor(command),
274
+ selectMenuExecute: createSelectMenuExecutor(command),
252
275
  cooldown: command.cooldown,
253
276
  };
254
277
  });
package/dist/types.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * @author vicentefelipechile
4
4
  * @license MIT
5
5
  */
6
- import { ApplicationCommandOptionType as OptionType, APIApplicationCommandOptionChoice, ChatInputCommandInteraction, MessageComponentInteraction, ModalSubmitInteraction, CommandInteraction, ButtonInteraction, SlashCommandBuilder, PartialDMChannel, ThreadChannel, GuildChannel, Collection, Message, Locale, Client, User } from "discord.js";
6
+ import { ApplicationCommandOptionType as OptionType, APIApplicationCommandOptionChoice, ChatInputCommandInteraction, MessageComponentInteraction, ModalSubmitInteraction, StringSelectMenuInteraction, CommandInteraction, ButtonInteraction, SlashCommandBuilder, PartialDMChannel, ThreadChannel, GuildChannel, Collection, Message, Locale, Client, User } from "discord.js";
7
7
  import ModularCommand from "./modularcommand";
8
8
  /**
9
9
  * @interface CommandOption
@@ -36,6 +36,8 @@ export interface CommandData {
36
36
  modalExecute?: (interaction: ModalSubmitInteraction) => Promise<void>;
37
37
  /** (Optional) Function to handle button interactions. */
38
38
  buttonExecute?: (interaction: ButtonInteraction) => Promise<void>;
39
+ /** (Optional) Function to handle string select menu interactions. */
40
+ selectMenuExecute?: (interaction: StringSelectMenuInteraction) => Promise<void>;
39
41
  /** The command's cooldown time in seconds. */
40
42
  cooldown: number;
41
43
  }
@@ -103,6 +105,16 @@ export type ModalExecuteParams = BaseExecuteParams<ModalSubmitInteraction> & {
103
105
  /** An object with the values of the fields submitted in the modal. */
104
106
  args: Record<string, string>;
105
107
  };
108
+ /**
109
+ * @type SelectMenuExecuteParams
110
+ * @description Parameters for the execution function of a select menu.
111
+ */
112
+ export type SelectMenuExecuteParams = BaseExecuteParams<StringSelectMenuInteraction> & {
113
+ /** The message to which the select menu is attached. */
114
+ message: Message;
115
+ /** The value selected by the user. */
116
+ selected: string;
117
+ };
106
118
  /**
107
119
  * @type CommandExecuteFunction
108
120
  * @description Defines the signature for the main execution function of a command.
@@ -123,6 +135,11 @@ export type ButtonExecuteFunction = (params: ButtonExecuteParams) => Promise<voi
123
135
  * @description Defines the signature for the function that handles a modal submission.
124
136
  */
125
137
  export type ModalExecuteFunction = (params: ModalExecuteParams) => Promise<void>;
138
+ /**
139
+ * @type SelectMenuExecuteFunction
140
+ * @description Defines the signature for the function that handles select menu interactions.
141
+ */
142
+ export type SelectMenuExecuteFunction = (params: SelectMenuExecuteParams) => Promise<void>;
126
143
  /**
127
144
  * @type PermissionCheckFunction
128
145
  * @description Defines the signature for a function that checks a user's permissions to execute a command.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-discord-modularcommand",
3
- "version": "2.4.0",
3
+ "version": "2.5.1",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "discord",