js-discord-modularcommand 1.1.0 → 2.0.0

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.
@@ -0,0 +1,254 @@
1
+ "use strict";
2
+ /**
3
+ * @file Contains the logic for registering modular commands and building their Discord.js data structures.
4
+ * @author vicentefelipechile
5
+ * @license MIT
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.default = RegisterCommand;
42
+ const discord_js_1 = require("discord.js");
43
+ const cooldown_1 = __importStar(require("./cooldown"));
44
+ const locales_1 = require("./locales");
45
+ /**
46
+ * @description Gets the appropriate localization object for a command based on the interaction's locale.
47
+ * Falls back to EnglishUS if the target locale is not available.
48
+ * @param {ModularCommand} command The command instance.
49
+ * @param {Interaction} interaction The interaction object to get the locale from.
50
+ * @returns {CommandLocale} The resolved locale object.
51
+ * @throws {Error} If the EnglishUS localization is missing.
52
+ */
53
+ function getCommandLocale(command, interaction) {
54
+ const localeTable = command.localizationPhrases;
55
+ if (!localeTable || !localeTable[discord_js_1.Locale.EnglishUS]) {
56
+ throw new Error(`Missing localization for EnglishUS in command ${command.name}`);
57
+ }
58
+ const targetLocale = localeTable[interaction.locale] ? interaction.locale : discord_js_1.Locale.EnglishUS;
59
+ return { [targetLocale]: localeTable[targetLocale] };
60
+ }
61
+ // =================================================================================================
62
+ // Execution Context Constructors
63
+ // =================================================================================================
64
+ /**
65
+ * @description Creates the execution function for chat input (slash) commands.
66
+ * @param {ModularCommand} command The command to create the executor for.
67
+ * @param {Record<string, OptionType>} options The parsed options for argument retrieval.
68
+ * @returns {Function} The async function that will handle the interaction.
69
+ */
70
+ function createChatInputExecutor(command, options) {
71
+ return async (interaction) => {
72
+ // Permission & NSFW Checks
73
+ if (command.permissionCheck && !command.permissionCheck({ interaction })) {
74
+ await interaction.reply({ content: locales_1.LOCALE_FORBIDDEN[interaction.locale], flags: discord_js_1.MessageFlags.Ephemeral });
75
+ return;
76
+ }
77
+ if (command.isNSFW && (!interaction.channel || !('nsfw' in interaction.channel) || !interaction.channel.nsfw)) {
78
+ await interaction.reply({ content: locales_1.LOCALE_NSFW[interaction.locale], flags: discord_js_1.MessageFlags.Ephemeral });
79
+ return;
80
+ }
81
+ // Cooldown Check
82
+ const { inCooldown, waitTime } = (0, cooldown_1.default)(command.name, interaction.user.id);
83
+ if (inCooldown) {
84
+ await interaction.reply({ content: locales_1.LOCALE_DELAY[interaction.locale].formatTime(waitTime), flags: discord_js_1.MessageFlags.Ephemeral });
85
+ return;
86
+ }
87
+ (0, cooldown_1.cooldownSetUser)(command.name, interaction.user.id);
88
+ // Argument Parsing
89
+ const args = {};
90
+ for (const optionName of Object.keys(options)) {
91
+ switch (options[optionName]) {
92
+ case discord_js_1.ApplicationCommandOptionType.String:
93
+ args[optionName] = interaction.options.getString(optionName, false);
94
+ break;
95
+ case discord_js_1.ApplicationCommandOptionType.Boolean:
96
+ args[optionName] = interaction.options.getBoolean(optionName, false);
97
+ break;
98
+ case discord_js_1.ApplicationCommandOptionType.Integer:
99
+ args[optionName] = interaction.options.getInteger(optionName, false);
100
+ break;
101
+ case discord_js_1.ApplicationCommandOptionType.Number:
102
+ args[optionName] = interaction.options.getNumber(optionName, false);
103
+ break;
104
+ case discord_js_1.ApplicationCommandOptionType.User:
105
+ args[optionName] = interaction.options.getUser(optionName, false);
106
+ break;
107
+ default: throw new Error(`Unsupported option type: ${options[optionName]}`);
108
+ }
109
+ }
110
+ const locale = getCommandLocale(command, interaction);
111
+ // Execute Handler
112
+ const { customId } = interaction.isMessageComponent() ? interaction : { customId: null };
113
+ if (customId && command.customIdHandlers[customId]) {
114
+ await command.customIdHandlers[customId]({ interaction, args, command, locale });
115
+ }
116
+ else {
117
+ await command.execute({ interaction, args, command, locale });
118
+ }
119
+ };
120
+ }
121
+ /**
122
+ * @description Creates the execution function for message components.
123
+ * @param {ModularCommand} command The command to create the executor for.
124
+ * @returns {Function|undefined} The async function or undefined if not needed.
125
+ */
126
+ function createComponentExecutor(command) {
127
+ const executor = command.componentExecute;
128
+ if (executor === undefined)
129
+ return undefined;
130
+ return async (interaction) => {
131
+ if (!interaction.customId.startsWith(command.componentId || ''))
132
+ return;
133
+ // Llama a la constante, que TypeScript sabe que está definida.
134
+ await executor({
135
+ interaction,
136
+ command,
137
+ locale: getCommandLocale(command, interaction),
138
+ });
139
+ };
140
+ }
141
+ /**
142
+ * @description Creates the execution function for modal submissions.
143
+ * @param {ModularCommand} command The command to create the executor for.
144
+ * @returns {Function|undefined} The async function or undefined if not needed.
145
+ */
146
+ function createModalExecutor(command) {
147
+ if (command.modals.size === 0)
148
+ return undefined;
149
+ return async (interaction) => {
150
+ const modalObject = command.modals.get(interaction.customId);
151
+ if (!modalObject)
152
+ return;
153
+ const args = {};
154
+ for (const [id] of modalObject.modalInputs.entries()) {
155
+ args[id] = interaction.fields.getTextInputValue(id);
156
+ }
157
+ await modalObject.execute({
158
+ interaction,
159
+ args,
160
+ command,
161
+ locale: getCommandLocale(command, interaction),
162
+ });
163
+ };
164
+ }
165
+ /**
166
+ * @description Creates the execution function for button interactions.
167
+ * @param {ModularCommand} command The command to create the executor for.
168
+ * @returns {Function|undefined} The async function or undefined if not needed.
169
+ */
170
+ function createButtonExecutor(command) {
171
+ if (command.buttons.size === 0)
172
+ return undefined;
173
+ return async (interaction) => {
174
+ const buttonObject = command.buttons.get(interaction.customId);
175
+ if (!buttonObject)
176
+ return;
177
+ await buttonObject.execute({
178
+ interaction,
179
+ command,
180
+ locale: getCommandLocale(command, interaction),
181
+ message: interaction.message,
182
+ });
183
+ };
184
+ }
185
+ // =================================================================================================
186
+ // Main Registration Function
187
+ // =================================================================================================
188
+ /**
189
+ * @description Registers an array of modular commands, building their final `CommandData` objects.
190
+ * This function processes the command definitions, sets up command builders, and assigns the execution logic.
191
+ * @param {ModularCommand[]} commands An array of ModularCommand instances.
192
+ * @returns {CommandData[]} An array of command data objects ready for the Discord.js client.
193
+ */
194
+ function RegisterCommand(commands) {
195
+ return commands.map(command => {
196
+ // --- Build SlashCommand Data ---
197
+ const commandBuilder = new discord_js_1.SlashCommandBuilder()
198
+ .setName(command.name)
199
+ .setDescription(command.description)
200
+ .setDescriptionLocalizations(command.descriptionLocalizations || null);
201
+ (0, cooldown_1.cooldownRegister)(command.name, command.cooldown);
202
+ const options = {};
203
+ command.options.forEach(opt => {
204
+ const description = typeof opt.description === 'string'
205
+ ? opt.description
206
+ : (opt.description[discord_js_1.Locale.EnglishUS] || `The description for ${opt.name} in English.`);
207
+ if (!description) {
208
+ throw new Error(`Option '${opt.name}' is missing a description.`);
209
+ }
210
+ options[opt.name] = opt.type;
211
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
212
+ const optionBuilder = (option) => {
213
+ option.setName(opt.name)
214
+ .setDescription(description)
215
+ .setRequired(opt.required || false)
216
+ .setDescriptionLocalizations(typeof opt.description === 'object' ? opt.description : {});
217
+ if (opt.choices && opt.choices.length > 0) {
218
+ option.addChoices(...opt.choices);
219
+ }
220
+ return option;
221
+ };
222
+ switch (opt.type) {
223
+ case discord_js_1.ApplicationCommandOptionType.String:
224
+ commandBuilder.addStringOption(optionBuilder);
225
+ break;
226
+ case discord_js_1.ApplicationCommandOptionType.Boolean:
227
+ commandBuilder.addBooleanOption(optionBuilder);
228
+ break;
229
+ case discord_js_1.ApplicationCommandOptionType.Integer:
230
+ commandBuilder.addIntegerOption(optionBuilder);
231
+ break;
232
+ case discord_js_1.ApplicationCommandOptionType.Number:
233
+ commandBuilder.addNumberOption(optionBuilder);
234
+ break;
235
+ case discord_js_1.ApplicationCommandOptionType.User:
236
+ commandBuilder.addUserOption(optionBuilder);
237
+ break;
238
+ case discord_js_1.ApplicationCommandOptionType.Channel:
239
+ commandBuilder.addChannelOption(optionBuilder);
240
+ break;
241
+ default: throw new Error(`Unsupported option type: ${opt.type}`);
242
+ }
243
+ });
244
+ // --- Assign Handlers using Constructors ---
245
+ return {
246
+ data: commandBuilder,
247
+ execute: createChatInputExecutor(command, options),
248
+ componentExecute: createComponentExecutor(command),
249
+ modalExecute: createModalExecutor(command),
250
+ buttonExecute: createButtonExecutor(command),
251
+ cooldown: command.cooldown,
252
+ };
253
+ });
254
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @file Contains the type and interface definitions for the Discord bot's modular commands.
3
+ * @author vicentefelipechile
4
+ * @license MIT
5
+ */
6
+ import { ApplicationCommandOptionType as OptionType, APIApplicationCommandOptionChoice, ChatInputCommandInteraction, MessageComponentInteraction, ModalSubmitInteraction, CommandInteraction, ButtonInteraction, SlashCommandBuilder, PartialDMChannel, ThreadChannel, GuildChannel, Collection, Message, Locale, Client, User } from "discord.js";
7
+ import ModularCommand from "./modularcommand";
8
+ /**
9
+ * @interface CommandOption
10
+ * @description Defines the structure of an option for a slash command.
11
+ */
12
+ export interface CommandOption {
13
+ /** The name of the option, must be unique within the command. */
14
+ name: string;
15
+ /** The data type the option expects (e.g., STRING, USER, CHANNEL). */
16
+ type: OptionType;
17
+ /** The description of the option. It can be a string or an object for localization. */
18
+ description: Record<Locale, string> | string;
19
+ /** Defines if the option is required. */
20
+ required?: boolean;
21
+ /** An array of predefined choices the user can select from. */
22
+ choices?: APIApplicationCommandOptionChoice[];
23
+ }
24
+ /**
25
+ * @interface CommandData
26
+ * @description Represents the final structure of a registered command, ready to be used by the Discord client.
27
+ */
28
+ export interface CommandData {
29
+ /** The slash command configuration built with SlashCommandBuilder. */
30
+ data: SlashCommandBuilder;
31
+ /** The main function that executes when the command is invoked. */
32
+ execute: (interaction: ChatInputCommandInteraction) => Promise<void>;
33
+ /** (Optional) Function to handle component interactions (e.g., select menus). */
34
+ componentExecute?: (interaction: MessageComponentInteraction) => Promise<void>;
35
+ /** (Optional) Function to handle modal submissions. */
36
+ modalExecute?: (interaction: ModalSubmitInteraction) => Promise<void>;
37
+ /** (Optional) Function to handle button interactions. */
38
+ buttonExecute?: (interaction: ButtonInteraction) => Promise<void>;
39
+ /** The command's cooldown time in seconds. */
40
+ cooldown: number;
41
+ }
42
+ /**
43
+ * @type LocaleKey
44
+ * @description An object that maps text identifiers to their translations.
45
+ * @example { "GREETING": "Hello!", "FAREWELL": "Goodbye!" }
46
+ */
47
+ export type LocaleKey = Record<string, string>;
48
+ /**
49
+ * @type ClientWithCommands
50
+ * @description Extends the discord.js `Client` type to include a collection of commands.
51
+ */
52
+ export type ClientWithCommands = Client & {
53
+ commands: Collection<string, CommandData>;
54
+ };
55
+ /**
56
+ * @type CommandArgumentValue
57
+ * @description Represents the possible value types that a command argument can have.
58
+ */
59
+ export type CommandArgumentValue = string | boolean | number | User | GuildChannel | ThreadChannel | PartialDMChannel | null;
60
+ /**
61
+ * @type BaseExecuteParams
62
+ * @description Defines the base parameters shared by all execution functions.
63
+ */
64
+ export type BaseExecuteParams<T extends CommandInteraction | MessageComponentInteraction | ModalSubmitInteraction> = {
65
+ /** The interaction received from Discord. */
66
+ interaction: T;
67
+ /** The instance of the modular command being executed. */
68
+ command: ModularCommand;
69
+ /** The localization object to get translated texts. */
70
+ locale: LocaleKey;
71
+ };
72
+ /**
73
+ * @type CommandExecuteParams
74
+ * @description Parameters for the execution function of a chat command.
75
+ */
76
+ export type CommandExecuteParams = BaseExecuteParams<ChatInputCommandInteraction> & {
77
+ /** An object containing the arguments provided by the user. */
78
+ args?: Record<string, CommandArgumentValue>;
79
+ };
80
+ /**
81
+ * @type ButtonExecuteParams
82
+ * @description Parameters for the execution function of a button.
83
+ */
84
+ export type ButtonExecuteParams = BaseExecuteParams<ButtonInteraction> & {
85
+ /** The message to which the button is attached. */
86
+ message: Message;
87
+ };
88
+ /**
89
+ * @type ModalExecuteParams
90
+ * @description Parameters for the execution function of a modal.
91
+ */
92
+ export type ModalExecuteParams = BaseExecuteParams<ModalSubmitInteraction> & {
93
+ /** An object with the values of the fields submitted in the modal. */
94
+ args: Record<string, string>;
95
+ };
96
+ /**
97
+ * @type CommandExecuteFunction
98
+ * @description Defines the signature for the main execution function of a command.
99
+ */
100
+ export type CommandExecuteFunction = (params: CommandExecuteParams) => Promise<void>;
101
+ /**
102
+ * @type ComponentExecuteFunction
103
+ * @description Defines the signature for the function that handles generic component interactions.
104
+ */
105
+ export type ComponentExecuteFunction = (params: BaseExecuteParams<MessageComponentInteraction>) => Promise<void>;
106
+ /**
107
+ * @type ButtonExecuteFunction
108
+ * @description Defines the signature for the function that handles button interactions.
109
+ */
110
+ export type ButtonExecuteFunction = (params: ButtonExecuteParams) => Promise<void>;
111
+ /**
112
+ * @type ModalExecuteFunction
113
+ * @description Defines the signature for the function that handles a modal submission.
114
+ */
115
+ export type ModalExecuteFunction = (params: ModalExecuteParams) => Promise<void>;
116
+ /**
117
+ * @type PermissionCheckFunction
118
+ * @description Defines the signature for a function that checks a user's permissions to execute a command.
119
+ * @returns {boolean | Promise<boolean>} `true` if the user has permission, `false` otherwise.
120
+ */
121
+ export type PermissionCheckFunction = (params: {
122
+ interaction: CommandInteraction;
123
+ }) => boolean | Promise<boolean>;
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * @file Contains the type and interface definitions for the Discord bot's modular commands.
4
+ * @author vicentefelipechile
5
+ * @license MIT
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-discord-modularcommand",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "discord",
@@ -17,13 +17,22 @@
17
17
  "main": "./dist/index.js",
18
18
  "scripts": {
19
19
  "build": "tsc",
20
- "prepublish": "npm run build"
20
+ "prepublish": "npm run build",
21
+ "lint": "eslint . --ext .ts",
22
+ "lint:fix": "eslint . --ext .ts --fix"
21
23
  },
22
24
  "dependencies": {
23
25
  "discord.js": "^14.21.0"
24
26
  },
25
27
  "devDependencies": {
28
+ "@eslint/js": "^9.34.0",
26
29
  "@types/node": "^24.2.1",
27
- "typescript": "^5.9.2"
30
+ "@typescript-eslint/eslint-plugin": "^8.41.0",
31
+ "@typescript-eslint/parser": "^8.41.0",
32
+ "eslint": "^9.34.0",
33
+ "globals": "^16.3.0",
34
+ "jiti": "^2.5.1",
35
+ "typescript": "^5.9.2",
36
+ "typescript-eslint": "^8.41.0"
28
37
  }
29
38
  }