js-discord-modularcommand 1.1.0 → 2.0.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.
@@ -0,0 +1,249 @@
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
+ await command.execute({ interaction, args, command, locale });
113
+ };
114
+ }
115
+ /**
116
+ * @description Creates the execution function for message components.
117
+ * @param {ModularCommand} command The command to create the executor for.
118
+ * @returns {Function|undefined} The async function or undefined if not needed.
119
+ */
120
+ function createComponentExecutor(command) {
121
+ const executor = command.componentExecute;
122
+ if (executor === undefined)
123
+ return undefined;
124
+ return async (interaction) => {
125
+ if (!interaction.customId.startsWith(command.componentId || ''))
126
+ return;
127
+ await executor({
128
+ interaction,
129
+ command,
130
+ locale: getCommandLocale(command, interaction),
131
+ });
132
+ };
133
+ }
134
+ /**
135
+ * @description Creates the execution function for modal submissions.
136
+ * @param {ModularCommand} command The command to create the executor for.
137
+ * @returns {Function|undefined} The async function or undefined if not needed.
138
+ */
139
+ function createModalExecutor(command) {
140
+ if (command.modals.size === 0)
141
+ return undefined;
142
+ return async (interaction) => {
143
+ const modalObject = command.modals.get(interaction.customId);
144
+ if (!modalObject)
145
+ return;
146
+ const args = {};
147
+ for (const [id] of modalObject.modalInputs.entries()) {
148
+ args[id] = interaction.fields.getTextInputValue(id);
149
+ }
150
+ await modalObject.execute({
151
+ interaction,
152
+ args,
153
+ command,
154
+ locale: getCommandLocale(command, interaction),
155
+ });
156
+ };
157
+ }
158
+ /**
159
+ * @description Creates the execution function for button interactions.
160
+ * @param {ModularCommand} command The command to create the executor for.
161
+ * @returns {Function|undefined} The async function or undefined if not needed.
162
+ */
163
+ function createButtonExecutor(command) {
164
+ if (command.buttons.size === 0)
165
+ return undefined;
166
+ return async (interaction) => {
167
+ const buttonObject = command.buttons.get(interaction.customId);
168
+ if (!buttonObject)
169
+ return;
170
+ await buttonObject.execute({
171
+ interaction,
172
+ command,
173
+ locale: getCommandLocale(command, interaction),
174
+ message: interaction.message,
175
+ });
176
+ };
177
+ }
178
+ // =================================================================================================
179
+ // Main Registration Function
180
+ // =================================================================================================
181
+ /**
182
+ * @description Registers an array of modular commands, building their final `CommandData` objects.
183
+ * This function processes the command definitions, sets up command builders, and assigns the execution logic.
184
+ * @param {ModularCommand[]} commands An array of ModularCommand instances.
185
+ * @returns {CommandData[]} An array of command data objects ready for the Discord.js client.
186
+ */
187
+ function RegisterCommand(commands) {
188
+ commands = Array.isArray(commands) ? commands : [commands];
189
+ return commands.map(command => {
190
+ if (command.name === undefined)
191
+ throw new Error("A command is missing a name.");
192
+ if (command.description === undefined)
193
+ throw new Error(`Command "${command.name}" is missing a description.`);
194
+ // Build SlashCommand Data
195
+ const commandBuilder = new discord_js_1.SlashCommandBuilder()
196
+ .setName(command.name)
197
+ .setDescription(command.description)
198
+ .setDescriptionLocalizations(command.descriptionLocalizations || null);
199
+ (0, cooldown_1.cooldownRegister)(command.name, command.cooldown);
200
+ const options = {};
201
+ command.options.forEach(opt => {
202
+ const description = typeof opt.description === 'string'
203
+ ? opt.description
204
+ : (opt.description[discord_js_1.Locale.EnglishUS] || `The description for ${opt.name} in English.`);
205
+ if (!description) {
206
+ throw new Error(`Option '${opt.name}' is missing a description.`);
207
+ }
208
+ options[opt.name] = opt.type;
209
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
210
+ const optionBuilder = (option) => {
211
+ option.setName(opt.name)
212
+ .setDescription(description)
213
+ .setRequired(opt.required || false)
214
+ .setDescriptionLocalizations(typeof opt.description === 'object' ? opt.description : {});
215
+ if (opt.choices && opt.choices.length > 0) {
216
+ option.addChoices(...opt.choices);
217
+ }
218
+ return option;
219
+ };
220
+ switch (opt.type) {
221
+ case discord_js_1.ApplicationCommandOptionType.String:
222
+ commandBuilder.addStringOption(optionBuilder);
223
+ break;
224
+ case discord_js_1.ApplicationCommandOptionType.Boolean:
225
+ commandBuilder.addBooleanOption(optionBuilder);
226
+ break;
227
+ case discord_js_1.ApplicationCommandOptionType.Integer:
228
+ commandBuilder.addIntegerOption(optionBuilder);
229
+ break;
230
+ case discord_js_1.ApplicationCommandOptionType.Number:
231
+ commandBuilder.addNumberOption(optionBuilder);
232
+ break;
233
+ case discord_js_1.ApplicationCommandOptionType.User:
234
+ commandBuilder.addUserOption(optionBuilder);
235
+ break;
236
+ default: throw new Error(`Unsupported option type: ${opt.type}`);
237
+ }
238
+ });
239
+ // Assign Handlers using Constructors
240
+ return {
241
+ data: commandBuilder,
242
+ execute: createChatInputExecutor(command, options),
243
+ componentExecute: createComponentExecutor(command),
244
+ modalExecute: createModalExecutor(command),
245
+ buttonExecute: createButtonExecutor(command),
246
+ cooldown: command.cooldown,
247
+ };
248
+ });
249
+ }
@@ -0,0 +1,133 @@
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: AllowedOptionType;
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
+ * @const ALLOWED_OPTION_TYPE
44
+ * @description A list of allowed option types for slash commands.
45
+ */
46
+ export declare const ALLOWED_OPTION_TYPE: readonly [OptionType.String, OptionType.Boolean, OptionType.Integer, OptionType.Number, OptionType.User];
47
+ /**
48
+ * @type AllowedOptionType
49
+ * @description The union type of allowed option types for slash commands.
50
+ */
51
+ export type AllowedOptionType = typeof ALLOWED_OPTION_TYPE[number];
52
+ /**
53
+ * @type LocaleKey
54
+ * @description An object that maps text identifiers to their translations.
55
+ * @example { "GREETING": "Hello!", "FAREWELL": "Goodbye!" }
56
+ */
57
+ export type LocaleKey = Record<string, string>;
58
+ /**
59
+ * @type ClientWithCommands
60
+ * @description Extends the discord.js `Client` type to include a collection of commands.
61
+ */
62
+ export type ClientWithCommands = Client & {
63
+ commands: Collection<string, CommandData>;
64
+ };
65
+ /**
66
+ * @type CommandArgumentValue
67
+ * @description Represents the possible value types that a command argument can have.
68
+ */
69
+ export type CommandArgumentValue = string | boolean | number | User | GuildChannel | ThreadChannel | PartialDMChannel | null;
70
+ /**
71
+ * @type BaseExecuteParams
72
+ * @description Defines the base parameters shared by all execution functions.
73
+ */
74
+ export type BaseExecuteParams<T extends CommandInteraction | MessageComponentInteraction | ModalSubmitInteraction> = {
75
+ /** The interaction received from Discord. */
76
+ interaction: T;
77
+ /** The instance of the modular command being executed. */
78
+ command: ModularCommand;
79
+ /** The localization object to get translated texts. */
80
+ locale: LocaleKey;
81
+ };
82
+ /**
83
+ * @type CommandExecuteParams
84
+ * @description Parameters for the execution function of a chat command.
85
+ */
86
+ export type CommandExecuteParams = BaseExecuteParams<ChatInputCommandInteraction> & {
87
+ /** An object containing the arguments provided by the user. */
88
+ args?: Record<string, CommandArgumentValue>;
89
+ };
90
+ /**
91
+ * @type ButtonExecuteParams
92
+ * @description Parameters for the execution function of a button.
93
+ */
94
+ export type ButtonExecuteParams = BaseExecuteParams<ButtonInteraction> & {
95
+ /** The message to which the button is attached. */
96
+ message: Message;
97
+ };
98
+ /**
99
+ * @type ModalExecuteParams
100
+ * @description Parameters for the execution function of a modal.
101
+ */
102
+ export type ModalExecuteParams = BaseExecuteParams<ModalSubmitInteraction> & {
103
+ /** An object with the values of the fields submitted in the modal. */
104
+ args: Record<string, string>;
105
+ };
106
+ /**
107
+ * @type CommandExecuteFunction
108
+ * @description Defines the signature for the main execution function of a command.
109
+ */
110
+ export type CommandExecuteFunction = (params: CommandExecuteParams) => Promise<void>;
111
+ /**
112
+ * @type ComponentExecuteFunction
113
+ * @description Defines the signature for the function that handles generic component interactions.
114
+ */
115
+ export type ComponentExecuteFunction = (params: BaseExecuteParams<MessageComponentInteraction>) => Promise<void>;
116
+ /**
117
+ * @type ButtonExecuteFunction
118
+ * @description Defines the signature for the function that handles button interactions.
119
+ */
120
+ export type ButtonExecuteFunction = (params: ButtonExecuteParams) => Promise<void>;
121
+ /**
122
+ * @type ModalExecuteFunction
123
+ * @description Defines the signature for the function that handles a modal submission.
124
+ */
125
+ export type ModalExecuteFunction = (params: ModalExecuteParams) => Promise<void>;
126
+ /**
127
+ * @type PermissionCheckFunction
128
+ * @description Defines the signature for a function that checks a user's permissions to execute a command.
129
+ * @returns {boolean | Promise<boolean>} `true` if the user has permission, `false` otherwise.
130
+ */
131
+ export type PermissionCheckFunction = (params: {
132
+ interaction: CommandInteraction;
133
+ }) => boolean | Promise<boolean>;
package/dist/types.js ADDED
@@ -0,0 +1,23 @@
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 });
8
+ exports.ALLOWED_OPTION_TYPE = void 0;
9
+ const discord_js_1 = require("discord.js");
10
+ // =================================================================================================
11
+ // Generic and Utility Types
12
+ // =================================================================================================
13
+ /**
14
+ * @const ALLOWED_OPTION_TYPE
15
+ * @description A list of allowed option types for slash commands.
16
+ */
17
+ exports.ALLOWED_OPTION_TYPE = [
18
+ discord_js_1.ApplicationCommandOptionType.String,
19
+ discord_js_1.ApplicationCommandOptionType.Boolean,
20
+ discord_js_1.ApplicationCommandOptionType.Integer,
21
+ discord_js_1.ApplicationCommandOptionType.Number,
22
+ discord_js_1.ApplicationCommandOptionType.User,
23
+ ];
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.1",
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
  }