UNPKG

discordx

Version:

Create a discord bot with TypeScript and Decorators!

1,617 lines (1,606 loc) • 97.9 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { ApplicationCommandMixin: () => ApplicationCommandMixin, Bot: () => Bot, ButtonComponent: () => ButtonComponent, Client: () => Client, ComponentType: () => ComponentType, ContextMenu: () => ContextMenu, DApplicationCommand: () => DApplicationCommand, DApplicationCommandGroup: () => DApplicationCommandGroup, DApplicationCommandOption: () => DApplicationCommandOption, DApplicationCommandOptionChoice: () => DApplicationCommandOptionChoice, DComponent: () => DComponent, DDiscord: () => DDiscord, DGuard: () => DGuard, DOn: () => DOn, DReaction: () => DReaction, DSimpleCommand: () => DSimpleCommand, DSimpleCommandOption: () => DSimpleCommandOption, Discord: () => Discord, Guard: () => Guard, Guild: () => Guild, MetadataStorage: () => MetadataStorage, ModalComponent: () => ModalComponent, On: () => On, Once: () => Once, Reaction: () => Reaction, RecursivelyMatchField: () => RecursivelyMatchField, SelectMenuComponent: () => SelectMenuComponent, SimpleCommand: () => SimpleCommand, SimpleCommandMessage: () => SimpleCommandMessage, SimpleCommandOption: () => SimpleCommandOption, SimpleCommandOptionType: () => SimpleCommandOptionType, SimpleCommandParseType: () => SimpleCommandParseType, Slash: () => Slash, SlashChoice: () => SlashChoice, SlashGroup: () => SlashGroup, SlashNameValidator: () => SlashNameValidator, SlashOption: () => SlashOption, SpecialCharactersList: () => SpecialCharactersList, isApplicationCommandEqual: () => isApplicationCommandEqual, resolveIGuilds: () => resolveIGuilds, toStringArray: () => toStringArray }); module.exports = __toCommonJS(index_exports); // src/classes/Mixin/ApplicationCommandMixin.ts var ApplicationCommandMixin = class { constructor(command, instance) { this.command = command; this.instance = instance; } get name() { return this.command.name; } get description() { return this.command.description; } }; // src/classes/SimpleCommandMessage.ts var import_crypto = __toESM(require("crypto")); var import_discord = require("discord.js"); var SimpleCommandMessage = class { constructor(prefix, argString, message, info, splitter) { this.prefix = prefix; this.argString = argString; this.message = message; this.info = info; this.splitter = splitter; } options = []; get name() { return this.info.name; } get description() { return this.info.description; } /** * Resolve options */ resolveOptions() { return this.info.parseParamsEx(this); } /** * Verify that all options are valid * * @returns */ isValid() { return !this.options.includes(null); } /** * Get related commands * * @returns */ getRelatedCommands() { const commandName = this.info.name.split(" ")[0]; if (!commandName) { return []; } return MetadataStorage.instance.simpleCommands.filter( (cmd) => cmd.name.startsWith(commandName) && cmd.name !== this.info.name ); } /** * Send usage syntax for command * * @returns */ sendUsageSyntax() { const maxLength = !this.info.options.length ? 0 : this.info.options.reduce( (a, b) => a.name.length > b.name.length ? a : b ).name.length; const embed = new import_discord.EmbedBuilder(); embed.setColor(import_crypto.default.randomInt(654321)); embed.setTitle("Command Info"); embed.addFields({ name: "Name", value: this.info.name }); embed.addFields({ name: "Description", value: this.info.description }); if (this.info.aliases.length) { embed.addFields({ name: "Aliases", value: this.info.aliases.join(", ") }); } embed.addFields({ name: "Command Usage", value: `\`\`\`${this.prefix.toString()}${this.name} ${this.info.options.map((op) => `{${op.name}: ${SimpleCommandOptionType[op.type]}}`).join(" ")}\`\`\`` }); if (this.info.options.length) { embed.addFields({ name: "Options", value: `\`\`\`${this.info.options.map((op) => `${op.name.padEnd(maxLength + 2)}: ${op.description}`).join("\n")}\`\`\`` }); } return this.message.reply({ embeds: [embed] }); } }; // src/Client.ts var import_di = require("@discordx/di"); var import_discord2 = require("discord.js"); var import_escapeRegExp = __toESM(require("lodash/escapeRegExp.js")); var Client = class _Client extends import_discord2.Client { _botId; _isBuilt = false; _prefix; _simpleCommandConfig; _silent; _listeners = /* @__PURE__ */ new Map(); _botGuilds = []; _guards = []; logger; // static getters static get applicationCommandSlashesFlat() { return MetadataStorage.instance.applicationCommandSlashesFlat; } static get applicationCommandSlashes() { return MetadataStorage.instance.applicationCommandSlashes; } static get applicationCommandUsers() { return MetadataStorage.instance.applicationCommandUsers; } static get applicationCommandMessages() { return MetadataStorage.instance.applicationCommandMessages; } static get applicationCommandSlashOptions() { return MetadataStorage.instance.applicationCommandSlashOptions; } static get applicationCommands() { return MetadataStorage.instance.applicationCommands; } static get applicationCommandSlashGroups() { return MetadataStorage.instance.applicationCommandSlashGroups; } static get applicationCommandSlashSubGroups() { return MetadataStorage.instance.applicationCommandSlashSubGroups; } static get buttonComponents() { return MetadataStorage.instance.buttonComponents; } static get discords() { return MetadataStorage.instance.discords; } static get events() { return MetadataStorage.instance.events; } static get instance() { return MetadataStorage.instance; } static get modalComponents() { return MetadataStorage.instance.modalComponents; } static get reactions() { return MetadataStorage.instance.reactions; } static get selectMenuComponents() { return MetadataStorage.instance.selectMenuComponents; } static get simpleCommandsByName() { return MetadataStorage.instance.simpleCommandsByName; } static get simpleCommandMappedPrefix() { return MetadataStorage.instance.simpleCommandMappedPrefix; } static get simpleCommands() { return MetadataStorage.instance.simpleCommands; } // map static getters get applicationCommandSlashes() { return _Client.applicationCommandSlashes; } get applicationCommandSlashesFlat() { return _Client.applicationCommandSlashesFlat; } get applicationCommandSlashOptions() { return _Client.applicationCommandSlashOptions; } get applicationCommandSlashGroups() { return _Client.applicationCommandSlashGroups; } get applicationCommandSlashSubGroups() { return _Client.applicationCommandSlashSubGroups; } get applicationCommandUsers() { return _Client.applicationCommandUsers; } get applicationCommandMessages() { return _Client.applicationCommandMessages; } get applicationCommands() { return _Client.applicationCommands; } get buttonComponents() { return _Client.buttonComponents; } get discords() { return _Client.discords; } get events() { return _Client.events; } get instance() { return _Client.instance; } get modalComponents() { return _Client.modalComponents; } get reactions() { return _Client.reactions; } get selectMenuComponents() { return _Client.selectMenuComponents; } get simpleCommandsByName() { return _Client.simpleCommandsByName; } get simpleCommandMappedPrefix() { return _Client.simpleCommandMappedPrefix; } get simpleCommands() { return _Client.simpleCommands; } // client getters get botResolvedGuilds() { return resolveIGuilds(this, void 0, this._botGuilds); } get botGuilds() { return this._botGuilds; } set botGuilds(value) { this._botGuilds = value; } get botId() { return this._botId; } set botId(value) { this._botId = value; } get guards() { return this._guards; } set guards(value) { this._guards = value; } get prefix() { return this._prefix; } set prefix(value) { this._prefix = value; } get simpleCommandConfig() { return this._simpleCommandConfig; } set simpleCommandConfig(value) { this._simpleCommandConfig = value; } get silent() { return this._silent; } set silent(value) { this._silent = value; } /** * Extend original client class of discord.js * * @param options - Client options * ___ * * [View Documentation](https://discordx.js.org/docs/discordx/basics/client) */ constructor(options) { super(options); this._silent = options.silent ?? true; this.guards = options.guards ?? []; this.botGuilds = options.botGuilds ?? []; this._botId = options.botId ?? "bot"; this._prefix = options.simpleCommand?.prefix ?? ["!"]; this._simpleCommandConfig = options.simpleCommand; this.logger = options.logger ?? console; } /** * Start bot * * @param token - Bot token */ async login(token) { await this.build(); if (!this.silent) { this.logger.log( `${this.user?.username ?? this.botId} >> connecting discord... ` ); } return super.login(token); } /** * Print information about all events and commands to your console */ printDebug() { if (!this.instance.isBuilt) { this.logger.error( "Build the app before running this method with client.build()" ); return; } this.logger.log("client >> Events"); if (this.events.length) { this.events.forEach((event) => { const eventName = event.event; const className = event.classRef.name; const key = event.key; this.logger.log(`>> ${eventName} (${className}.${key})`); }); } else { this.logger.log(" No event detected"); } this.logger.log(""); this.logger.log("client >> buttons"); if (this.buttonComponents.length) { this.buttonComponents.forEach((btn) => { const className = btn.classRef.name; const key = btn.key; this.logger.log(`>> ${btn.id.toString()} (${className}.${key})`); }); } else { this.logger.log(" No buttons detected"); } this.logger.log(""); this.logger.log("client >> select menu's"); if (this.selectMenuComponents.length) { this.selectMenuComponents.forEach((menu) => { const className = menu.classRef.name; const key = menu.key; this.logger.log(`>> ${menu.id.toString()} (${className}.${key})`); }); } else { this.logger.log(" No select menu detected"); } this.logger.log(""); this.logger.log("client >> modals"); if (this.modalComponents.length) { this.modalComponents.forEach((menu) => { const className = menu.classRef.name; const key = menu.key; this.logger.log(`>> ${menu.id.toString()} (${className}.${key})`); }); } else { this.logger.log(" No modal detected"); } this.logger.log(""); this.logger.log("client >> reactions"); if (this.reactions.length) { this.reactions.forEach((menu) => { const className = menu.classRef.name; const key = menu.key; this.logger.log(`>> ${menu.emoji} (${className}.${key})`); }); } else { this.logger.log(" No reaction detected"); } this.logger.log(""); this.logger.log("client >> context menu's"); const contexts = [ ...this.applicationCommandUsers, ...this.applicationCommandMessages ]; if (contexts.length) { contexts.forEach((menu) => { const type = menu.type.toString(); const className = menu.classRef.name; const key = menu.key; this.logger.log(`>> ${menu.name} (${type}) (${className}.${key})`); }); } else { this.logger.log(" No context menu detected"); } this.logger.log(""); this.logger.log("client >> application commands"); if (this.applicationCommands.length) { this.applicationCommands.forEach((DCommand, index) => { if (DCommand.botIds.length && !DCommand.botIds.includes(this.botId)) { return; } const line = index !== 0 ? "\n" : ""; const className = DCommand.classRef.name; const key = DCommand.key; this.logger.log(`${line} >> ${DCommand.name} (${className}.${key})`); const printOptions = (options, depth) => { const tab = Array(depth).join(" "); options.forEach((option, optionIndex) => { const className2 = option.classRef.name; const key2 = option.key; this.logger.log( `${(option.type === import_discord2.ApplicationCommandOptionType.Subcommand || option.type === import_discord2.ApplicationCommandOptionType.SubcommandGroup) && optionIndex !== 0 ? "\n" : ""}${tab}>> ${option.type === import_discord2.ApplicationCommandOptionType.Subcommand || option.type === import_discord2.ApplicationCommandOptionType.SubcommandGroup ? option.name : option.name}: ${import_discord2.ApplicationCommandOptionType[option.type].toLowerCase()} (${className2}.${key2})` ); printOptions(option.options, depth + 1); }); }; printOptions(DCommand.options, 2); }); } else { this.logger.log(" No application command detected"); } this.logger.log(""); this.logger.log("client >> simple commands"); if (this.simpleCommands.length) { this.simpleCommands.forEach((cmd) => { const className = cmd.classRef.name; const key = cmd.key; this.logger.log(` >> ${cmd.name} (${className}.${key})`); if (cmd.aliases.length) { this.logger.log(` aliases:`, cmd.aliases.join(", ")); } const printOptions = (options, depth) => { const tab = Array(depth).join(" "); options.forEach((option) => { const type = SimpleCommandOptionType[option.type]; const className2 = option.classRef.name; const key2 = option.key; this.logger.log( `${tab}${option.name}: ${type} (${className2}.${key2})` ); }); }; printOptions(cmd.options, 2); this.logger.log(""); }); } else { this.logger.log(" No simple command detected"); } this.logger.log("\n"); } /** * Get commands mapped by guild id (in case of multi bot, commands are filtered for this client only) * @returns */ async CommandByGuild() { const botResolvedGuilds = await this.botResolvedGuilds; const guildDCommandStore = /* @__PURE__ */ new Map(); const allGuildDCommands = this.applicationCommands.filter((DCommand) => { const guilds = [...botResolvedGuilds, ...DCommand.guilds]; return DCommand.isBotAllowed(this.botId) && guilds.length; }); await Promise.all( allGuildDCommands.map(async (DCommand) => { const guilds = await resolveIGuilds(this, DCommand, [ ...botResolvedGuilds, ...DCommand.guilds ]); guilds.forEach((guild) => { const commands = guildDCommandStore.get(guild) ?? []; return guildDCommandStore.set(guild, [...commands, DCommand]); }); }) ); return guildDCommandStore; } /** * Initialize application commands */ async initApplicationCommands(retainDeleted = false) { const allGuildPromises = []; const guildDCommandStore = await this.CommandByGuild(); guildDCommandStore.forEach((DCommands, guildId) => { const guild = this.guilds.cache.get(guildId); if (!guild) { return; } allGuildPromises.push( this.initGuildApplicationCommands(guildId, DCommands, retainDeleted) ); }); await Promise.all([ Promise.all(allGuildPromises), this.initGlobalApplicationCommands(retainDeleted) ]); } /** * Init application commands for guild */ async initGuildApplicationCommands(guildId, DCommands, retainDeleted = false) { const botResolvedGuilds = await this.botResolvedGuilds; const guild = this.guilds.cache.get(guildId); if (!guild) { this.logger.warn( `${this.user?.username ?? this.botId} >> initGuildApplicationCommands: skipped (Reason: guild ${guildId} unavailable)` ); return; } const discordCommands = await guild.commands.fetch({ withLocalizations: true }); const commandsToAdd = DCommands.filter((DCommand) => { const match = (cmd) => { return cmd.name === DCommand.name && cmd.type === DCommand.type; }; return !discordCommands.find(match); }); const commandsToUpdate = []; const commandsToSkip = []; DCommands.forEach((DCommand) => { const match = (cmd) => { return cmd.name === DCommand.name && cmd.type === DCommand.type; }; const findCommand = discordCommands.find(match); if (!findCommand) { return; } const mixinCommand = new ApplicationCommandMixin(findCommand, DCommand); if (!isApplicationCommandEqual(findCommand, DCommand, true)) { commandsToUpdate.push(mixinCommand); } else { commandsToSkip.push(mixinCommand); } }); const commandsToDelete = []; await Promise.all( discordCommands.map(async (cmd) => { const match = (DCommand) => { return cmd.name === DCommand.name && cmd.type === DCommand.type; }; const DCommandFind = DCommands.find(match); if (!DCommandFind) { commandsToDelete.push(cmd); return; } const guilds = await resolveIGuilds(this, DCommandFind, [ ...botResolvedGuilds, ...DCommandFind.guilds ]); if (!cmd.guildId || !guilds.includes(cmd.guildId)) { commandsToDelete.push(cmd); } }) ); if (!this.silent) { let str = `${this.user?.username ?? "Bot"} >> commands >> guild: #${guild.toString()}`; const commandsToAddNames = commandsToAdd.map((DCommand) => DCommand.name).join(", "); const commandsToDeleteNames = commandsToDelete.map((DCommand) => DCommand.name).join(", "); const commandsToSkipNames = commandsToSkip.map((DCommand) => DCommand.name).join(", "); const commandsToUpdateNames = commandsToUpdate.map((DCommand) => DCommand.name).join(", "); const deleteOrRetain = retainDeleted ? "retaining" : "deleting"; str += ` >> adding ${String(commandsToAdd.length)} [${commandsToAddNames}]`; str += ` >> ${deleteOrRetain} ${String(commandsToDelete.length)} [${commandsToDeleteNames}]`; str += ` >> skipping ${String(commandsToSkip.length)} [${commandsToSkipNames}]`; str += ` >> updating ${String(commandsToUpdate.length)} [${commandsToUpdateNames}]`; str += "\n"; this.logger.log(str); } const bulkUpdate = []; commandsToSkip.forEach((cmd) => bulkUpdate.push(cmd.instance.toJSON())); commandsToAdd.forEach((DCommand) => bulkUpdate.push(DCommand.toJSON())); commandsToUpdate.forEach((cmd) => bulkUpdate.push(cmd.instance.toJSON())); if (retainDeleted) { commandsToDelete.forEach((cmd) => { bulkUpdate.push(cmd.toJSON()); }); } if (bulkUpdate.length === 0) { return; } await guild.commands.set(bulkUpdate); } /** * Init global application commands */ async initGlobalApplicationCommands(retainDeleted = false) { const botResolvedGuilds = await this.botResolvedGuilds; if (!this.application) { throw Error( "The client is not yet ready, connect to discord before fetching commands" ); } const allDiscordCommands = await this.application.commands.fetch(); const discordCommands = allDiscordCommands.filter((cmd) => !cmd.guild); const DCommands = this.applicationCommands.filter((DCommand) => { if (botResolvedGuilds.length || DCommand.guilds.length) { return false; } if (DCommand.botIds.length && !DCommand.botIds.includes(this.botId)) { return false; } return true; }); const commandsToAdd = DCommands.filter((DCommand) => { const match = (cmd) => { return cmd.name === DCommand.name && cmd.type === DCommand.type; }; return !discordCommands.find(match); }); const commandsToUpdate = []; const commandsToSkip = []; DCommands.forEach((DCommand) => { const match = (cmd) => { return cmd.name === DCommand.name && cmd.type === DCommand.type; }; const discordCommand = discordCommands.find(match); if (!discordCommand) { return; } const mixinCommand = new ApplicationCommandMixin( discordCommand, DCommand ); if (!isApplicationCommandEqual(discordCommand, DCommand)) { commandsToUpdate.push(mixinCommand); } else { commandsToSkip.push(mixinCommand); } }); const commandsToDelete = discordCommands.filter((cmd) => { const match = (DCommand) => { return DCommand.name !== cmd.name || DCommand.type !== cmd.type; }; return DCommands.every(match); }); if (!this.silent) { let str = `${this.user?.username ?? this.botId} >> commands >> global`; const commandsToAddNames = commandsToAdd.map((DCommand) => DCommand.name).join(", "); const commandsToDeleteNames = commandsToDelete.map((DCommand) => DCommand.name).join(", "); const commandsToSkipNames = commandsToSkip.map((DCommand) => DCommand.name).join(", "); const commandsToUpdateNames = commandsToUpdate.map((DCommand) => DCommand.name).join(", "); const deleteOrRetain = retainDeleted ? "retaining" : "deleting"; str += ` >> adding ${String(commandsToAdd.length)} [${commandsToAddNames}]`; str += ` >> ${deleteOrRetain} ${String(commandsToDelete.size)} [${commandsToDeleteNames}]`; str += ` >> skipping ${String(commandsToSkip.length)} [${commandsToSkipNames}]`; str += ` >> updating ${String(commandsToUpdate.length)} [${commandsToUpdateNames}]`; str += "\n"; this.logger.log(str); } const bulkUpdate = []; commandsToSkip.forEach((cmd) => bulkUpdate.push(cmd.instance.toJSON())); commandsToAdd.forEach((instance) => bulkUpdate.push(instance.toJSON())); commandsToUpdate.forEach((cmd) => bulkUpdate.push(cmd.instance.toJSON())); if (retainDeleted) { commandsToDelete.forEach((cmd) => { bulkUpdate.push(cmd.toJSON()); }); } if (bulkUpdate.length === 0) { return; } await this.application.commands.set(bulkUpdate); } /** * Clear the application commands globally or for some guilds * * @param guilds - The guild Ids (empty -> globally) */ async clearApplicationCommands(...guilds) { if (guilds.length) { await Promise.all( // Select and delete the commands of each guild guilds.map((guild) => this.guilds.cache.get(guild)?.commands.set([])) ); } else { await this.application?.commands.set([]); } } /** * Get the group tree of an slash interaction * /hello => ["hello"] * /test hello => ["test", "hello"] * /test hello me => ["test", "hello", "me"] * * @param interaction - The targeted slash interaction * * @returns */ getApplicationCommandGroupTree(interaction) { const tree = []; const getOptionsTree = (option) => { if (!option) { return; } if (!option.type || option.type === import_discord2.ApplicationCommandOptionType.SubcommandGroup || option.type === import_discord2.ApplicationCommandOptionType.Subcommand) { if (option.name) { tree.push(option.name); } getOptionsTree(Array.from(option.options?.values() ?? [])[0]); } }; getOptionsTree({ name: interaction.commandName, options: Array.from(interaction.options.data.values()), type: void 0 }); return tree; } /** * Return the corresponding @Slash from a tree * * @param tree - Array of string * * @returns */ getApplicationCommandFromTree(tree) { return this.applicationCommandSlashesFlat.find((slash) => { switch (tree.length) { case 1: return slash.group === void 0 && slash.subgroup === void 0 && slash.name === tree[0] && slash.type === import_discord2.ApplicationCommandType.ChatInput; case 2: return slash.group === tree[0] && slash.subgroup === void 0 && slash.name === tree[1] && slash.type === import_discord2.ApplicationCommandType.ChatInput; case 3: return slash.group === tree[0] && slash.subgroup === tree[1] && slash.name === tree[2] && slash.type === import_discord2.ApplicationCommandType.ChatInput; default: return false; } }); } /** * Execute all types of interaction * * @param interaction - Interaction * * @returns */ executeInteraction(interaction) { if (interaction.isButton()) { return this.executeComponent(this.buttonComponents, interaction); } if (interaction.type === import_discord2.InteractionType.ModalSubmit) { return this.executeComponent(this.modalComponents, interaction); } if (interaction.isAnySelectMenu()) { return this.executeComponent(this.selectMenuComponents, interaction); } if (interaction.isContextMenuCommand()) { return this.executeContextMenu(interaction); } return this.executeCommandInteraction(interaction); } /** * Execute command interaction * * @param interaction - Interaction instance * * @returns */ async executeCommandInteraction(interaction) { const tree = this.getApplicationCommandGroupTree(interaction); const applicationCommand = this.getApplicationCommandFromTree(tree); if (!applicationCommand?.isBotAllowed(this.botId)) { if (!this.silent) { this.logger.warn( `${this.user?.username ?? this.botId} >> interaction not found, commandName: ${interaction.commandName}` ); } return null; } if (interaction.type === import_discord2.InteractionType.ApplicationCommandAutocomplete) { const focusOption = interaction.options.getFocused(true); const option = applicationCommand.options.find( (op) => op.name === focusOption.name ); if (option && typeof option.autocomplete === "function") { await option.autocomplete.call( import_di.DIService.engine.getService(option.from), interaction, applicationCommand ); return null; } } return applicationCommand.execute(this.guards, interaction, this); } /** * Execute component interaction * * @param interaction - Interaction instance * * @returns */ async executeComponent(components, interaction) { const executes = components.filter((component) => { return component.isId(interaction.customId) && component.isBotAllowed(this.botId); }); if (!executes.length) { if (!this.silent) { this.logger.warn( `${this.user?.username ?? this.botId} >> ${interaction.isButton() ? "button" : interaction.isAnySelectMenu() ? "select menu" : "modal"} component handler not found, interactionId: ${interaction.id} | customId: ${interaction.customId}` ); } return null; } const results = await Promise.all( executes.map(async (component) => { if (!await component.isGuildAllowed(this, interaction.guildId)) { return null; } return component.execute(this.guards, interaction, this); }) ); return results; } /** * Execute context menu interaction * * @param interaction - Interaction instance * * @returns */ executeContextMenu(interaction) { const applicationCommand = interaction.isUserContextMenuCommand() ? this.applicationCommandUsers.find( (cmd) => cmd.name === interaction.commandName ) : this.applicationCommandMessages.find( (cmd) => cmd.name === interaction.commandName ); if (!applicationCommand?.isBotAllowed(this.botId)) { if (!this.silent) { this.logger.warn( `${this.user?.username ?? this.botId} >> context interaction not found, name: ${interaction.commandName}` ); } return null; } return applicationCommand.execute(this.guards, interaction, this); } /** * Fetch prefix for message * * @param message - Message instance * * @returns */ async getMessagePrefix(message) { if (typeof this.prefix !== "function") { return toStringArray(this.prefix); } const prefix = await this.prefix(message); return toStringArray(prefix); } /** * Parse command message * * @param prefix - Command prefix * @param message - Original message * @param caseSensitive - Execute case-sensitively * * @returns */ async parseCommand(message, caseSensitive = false) { const prefix = await this.getMessagePrefix(message); const prefixRegex = RegExp( `^(${toStringArray(prefix, Array.from(this.simpleCommandMappedPrefix)).map((pfx) => (0, import_escapeRegExp.default)(pfx)).join("|")})` ); const isCommand = prefixRegex.test(message.content); if (!isCommand) { return 0 /* notCommand */; } const matchedPrefix = prefixRegex.exec(message.content)?.at(1) ?? "unknown"; const contentWithoutPrefix = `${message.content.replace(prefixRegex, "").trim()} `; const commandRaw = this.simpleCommandsByName.find((cmd) => { if (caseSensitive) { return contentWithoutPrefix.startsWith(`${cmd.name} `); } return contentWithoutPrefix.toLowerCase().startsWith(`${cmd.name.toLowerCase()} `); }); if (!commandRaw) { return 1 /* notFound */; } const commandArgs = contentWithoutPrefix.replace(new RegExp(commandRaw.name, "i"), "").trim(); const command = new SimpleCommandMessage( matchedPrefix, commandArgs, message, commandRaw.command, this.simpleCommandConfig?.argSplitter ); command.options = await command.resolveOptions(); return command; } /** * Execute the corresponding @SimpleCommand based on an message instance * * @param message - Message instance * @param options - Options * * @returns */ async executeCommand(message, caseSensitive) { const command = await this.parseCommand(message, caseSensitive ?? false); if (command === 0 /* notCommand */) { return null; } if (command === 1 /* notFound */) { const handleNotFound = this.simpleCommandConfig?.responses?.notFound; if (handleNotFound) { if (typeof handleNotFound === "string") { await message.reply(handleNotFound); } else { await handleNotFound(message); } } return null; } if (!command.info.isBotAllowed(this.botId)) { return null; } if (!await command.info.isGuildAllowed(this, command, message.guildId)) { return null; } if (!command.info.directMessage && !message.guild) { return null; } return command.info.execute(this.guards, command, this); } /** * Parse reaction * * @param message - Original reaction * * @returns */ parseReaction(message) { const reaction = this.reactions.find((react) => { const validNames = [react.emoji, ...react.aliases]; const { emoji } = message; return (emoji.id ? validNames.includes(emoji.id) : false) || (emoji.name ? validNames.includes(emoji.name) : false); }); return reaction; } /** * Execute the corresponding @Reaction based on an message reaction instance * * @param reaction - MessageReaction instance * @param options - Options * * @returns */ async executeReaction(reaction, user) { const action = this.parseReaction(reaction); if (!action) { return null; } if (!action.isBotAllowed(this.botId)) { return null; } if (!await action.isGuildAllowed(this, reaction.message.guildId)) { return null; } if (!action.directMessage && !reaction.message.guild) { return null; } if (!action.partial && reaction.partial) { reaction = await reaction.fetch(); } if (!action.partial && user.partial) { user = await user.fetch(); } if (action.remove) { await reaction.users.remove(user.id); } return action.execute(this.guards, reaction, user, this); } /** * Trigger an event manually (used for testing) * * @param options - Event data * @param params - Params to inject * * @returns */ trigger(options, params) { return this.instance.trigger(options)(params); } /** * Bind discordx events to client */ initEvents() { for (const { event, once, rest } of this.instance.usedEvents) { const trigger = this.instance.trigger({ client: this, event, guards: this.guards, once, rest }); if (!this._listeners.has(event)) { this._listeners.set(event, []); } this._listeners.get(event)?.push({ once, rest, trigger }); const method = once ? "once" : "on"; if (rest) { this.rest[method](event, trigger); } else { this[method](event, trigger); } } } /** * Unbind all discordx events initialized by the initEvents method. */ removeEvents() { this._listeners.forEach((listenerDetails, event) => { listenerDetails.forEach(({ rest, trigger }) => { if (rest) { this.rest.off(event, trigger); } else { this.off(event, trigger); } }); }); this._listeners.clear(); } /** * Manually build client */ async build() { if (this._isBuilt) { return; } this._isBuilt = true; await this.instance.build(); this.initEvents(); if (!this.silent) { this.printDebug(); } } }; // src/decorators/classes/DApplicationCommand.ts var import_discord3 = require("discord.js"); var import_discord4 = require("discord.js"); // src/decorators/classes/Method.ts var import_internal = require("@discordx/internal"); var Method = class extends import_internal.Decorator { _discord; _guards = []; get discord() { return this._discord; } set discord(value) { this._discord = value; } /** * Compiled methods executes all the guards and the main method * ```ts * compiledMethod = async (params: ArgsOf<any>, client: Client) => { * guard1(params, client) * guard2(params, client) * guard3(params, client) * main(params, client) * } * ``` * @returns */ get execute() { return (guards, ...params) => { const globalGuards = guards.map( (guard) => DGuard.create(guard.bind(void 0)) ); return this.getGuardFunction(globalGuards)(...params); }; } /** * Returns all the guards of the application * The guards that are defined globally with Client * The guards that decorate @Discord * The guards that decorate the method (this) */ get guards() { return [ ...this.discord.guards, ...this._guards, DGuard.create(this._method?.bind(this._discord.instance)) ]; } set guards(value) { this._guards = value; } /** * Execute a guard with params */ getGuardFunction(globalGuards) { const next = async (params, index, paramsToNext) => { const nextFn = () => next(params, index + 1, paramsToNext); const guardToExecute = [...globalGuards, ...this.guards][index]; let res; if (index >= [...globalGuards, ...this.guards].length - 1) { const parsedParams = await this.parseParams(...params); res = await (guardToExecute?.fn)( ...parsedParams, ...params, paramsToNext ); } else { res = await (guardToExecute?.fn)( ...params, nextFn, paramsToNext ); } if (res) { return res; } return paramsToNext; }; return (...params) => next(params, 0, {}); } }; // src/decorators/classes/DApplicationCommand.ts var DApplicationCommand = class _DApplicationCommand extends Method { _botIds; _contexts; _defaultMemberPermissions; _description; _descriptionLocalizations; _dmPermission; _group; _guilds; _integrationTypes; _name; _nameLocalizations; _nsfw; _options = []; _subgroup; _type; get botIds() { return this._botIds; } set botIds(value) { this._botIds = value; } get description() { return this._description; } set description(value) { this._description = value; } get defaultMemberPermissions() { return this._defaultMemberPermissions; } set defaultMemberPermissions(value) { this._defaultMemberPermissions = value; } get dmPermission() { return this._dmPermission; } set dmPermission(value) { this._dmPermission = value; } get contexts() { return this._contexts; } set contexts(value) { this._contexts = value; } get integrationTypes() { return this._integrationTypes; } set integrationTypes(value) { this._integrationTypes = value; } get descriptionLocalizations() { return this._descriptionLocalizations; } set descriptionLocalizations(value) { this._descriptionLocalizations = value; } get group() { return this._group; } set group(value) { this._group = value; } get guilds() { return this._guilds; } set guilds(value) { this._guilds = value; } get name() { return this._name; } set name(value) { this._name = value; } get nameLocalizations() { return this._nameLocalizations; } set nameLocalizations(value) { this._nameLocalizations = value; } get nsfw() { return this._nsfw; } set nsfw(value) { this._nsfw = value; } get options() { return this._options; } set options(value) { this._options = value; } get subgroup() { return this._subgroup; } set subgroup(value) { this._subgroup = value; } get type() { return this._type; } set type(value) { this._type = value; } constructor(data) { super(); this._botIds = data.botIds ?? []; this._contexts = data.contexts ?? null; this._defaultMemberPermissions = data.defaultMemberPermissions ?? null; this._description = data.description; this._descriptionLocalizations = data.descriptionLocalizations ?? null; this._dmPermission = data.dmPermission ?? true; this._guilds = data.guilds ?? []; this._integrationTypes = data.integrationTypes ?? [ import_discord3.ApplicationIntegrationType.GuildInstall ]; this._name = data.name; this._nameLocalizations = data.nameLocalizations ?? null; this._nsfw = data.nsfw ?? false; this._type = data.type; } static create(data) { return new _DApplicationCommand(data); } isBotAllowed(botId) { if (!this.botIds.length) { return true; } return this.botIds.includes(botId); } async getGuilds(client) { const guilds = await resolveIGuilds(client, this, [ ...client.botGuilds, ...this.guilds ]); return guilds; } async isGuildAllowed(client, guildId) { if (!guildId) { return true; } const guilds = await this.getGuilds(client); if (!guilds.length) { return true; } return guilds.includes(guildId); } toSubCommand() { const option = DApplicationCommandOption.create({ description: this.description, descriptionLocalizations: this.descriptionLocalizations, name: this.name, nameLocalizations: this.nameLocalizations, type: import_discord4.ApplicationCommandOptionType.Subcommand }).decorate(this.classRef, this.key, this.method, this.from, this.index); option.options = this.options; return option; } toJSON() { const options = [...this.options].reverse().sort((a, b) => { if ((a.type === import_discord4.ApplicationCommandOptionType.Subcommand || a.type === import_discord4.ApplicationCommandOptionType.SubcommandGroup) && (b.type === import_discord4.ApplicationCommandOptionType.Subcommand || b.type === import_discord4.ApplicationCommandOptionType.SubcommandGroup)) { return a.name < b.name ? -1 : 1; } return 0; }).map((option) => option.toJSON()); const data = { contexts: this.contexts, defaultMemberPermissions: this.defaultMemberPermissions, description: this.description, descriptionLocalizations: this.descriptionLocalizations, dmPermission: this.dmPermission, integrationTypes: this.integrationTypes, name: this.name, nameLocalizations: this.nameLocalizations, nsfw: this.nsfw, options, type: this.type }; return data; } parseParams(interaction) { return Promise.all( [...this.options].reverse().map((op) => op.parse(interaction)) ); } }; // src/decorators/classes/DApplicationCommandGroup.ts var import_internal2 = require("@discordx/internal"); var DApplicationCommandGroup = class _DApplicationCommandGroup extends import_internal2.Decorator { name; root; payload; constructor(options) { super(); this.name = options.name; this.root = options.root; this.payload = options.payload; } static create(options) { return new _DApplicationCommandGroup(options); } }; // src/decorators/classes/DApplicationCommandOption.ts var import_internal3 = require("@discordx/internal"); var import_discord5 = require("discord.js"); var DApplicationCommandOption = class _DApplicationCommandOption extends import_internal3.Decorator { _autocomplete; _channelTypes = void 0; _choices = []; _description; _descriptionLocalizations; _name; _nameLocalizations; _maxValue; _minValue; _maxLength; _minLength; _options = []; _required = true; _type; _transformer; get autocomplete() { return this._autocomplete; } set autocomplete(value) { this._autocomplete = value; } get channelTypes() { return this._channelTypes; } set channelTypes(value) { this._channelTypes = value; } get choices() { return this._choices; } set choices(value) { this._choices = value; } get description() { return this._description; } set description(value) { this._description = value; } get descriptionLocalizations() { return this._descriptionLocalizations; } set descriptionLocalizations(value) { this._descriptionLocalizations = value; } get isNode() { return this.type === import_discord5.ApplicationCommandOptionType.Subcommand || this.type === import_discord5.ApplicationCommandOptionType.SubcommandGroup; } get maxValue() { return this._maxValue; } set maxValue(value) { this._maxValue = value; } get minValue() { return this._minValue; } set minValue(value) { this._minValue = value; } get maxLength() { return this._maxLength; } set maxLength(value) { this._maxLength = value; } get minLength() { return this._minLength; } set minLength(value) { this._minLength = value; } get name() { return this._name; } set name(value) { this._name = value; } get nameLocalizations() { return this._nameLocalizations; } set nameLocalizations(value) { this._nameLocalizations = value; } get options() { return this._options; } set options(value) { this._options = value; } get required() { return this._required; } set required(value) { this._required = value; } get type() { return this._type; } set type(value) { this._type = value; } constructor(data) { super(); this._name = data.name; this._autocomplete = data.autocomplete; this._channelTypes = data.channelType?.sort(); this._choices = data.choices ?? []; this._description = data.description; this._index = data.index; this._maxValue = data.maxValue; this._minValue = data.minValue; this._maxLength = data.maxLength; this._minLength = data.minLength; this._required = data.required ?? false; this._type = data.type; this._descriptionLocalizations = data.descriptionLocalizations ?? null; this._nameLocalizations = data.nameLocalizations ?? null; this._transformer = data.transformer; } static create(data) { return new _DApplicationCommandOption(data); } toJSON() { const options = [...this.options].reverse().map((option) => option.toJSON()); const data = { autocomplete: this.autocomplete ? true : void 0, channelTypes: this.channelTypes, choices: this.isNode ? void 0 : this.choices.length === 0 ? void 0 : this.choices.map((choice) => choice.toJSON()), description: this.description, descriptionLocalizations: this.descriptionLocalizations, maxLength: this.maxLength, maxValue: this.maxValue, minLength: this.minLength, minValue: this.minValue, name: this.name, nameLocalizations: this.nameLocalizations, options: options.length === 0 ? void 0 : options, required: this.isNode ? void 0 : this.required, type: this.type }; return data; } parseType(interaction) { switch (this.type) { case import_discord5.ApplicationCommandOptionType.Attachment: return interaction.options.getAttachment(this.name) ?? void 0; case import_discord5.ApplicationCommandOptionType.String: return interaction.options.getString(this.name) ?? void 0; case import_discord5.ApplicationCommandOptionType.Boolean: return interaction.options.getBoolean(this.name) ?? void 0; case import_discord5.ApplicationCommandOptionType.Number: return interaction.options.getNumber(this.name) ?? void 0; case import_discord5.ApplicationCommandOptionType.Integer: return interaction.options.getInteger(this.name) ?? void 0; case import_discord5.ApplicationCommandOptionType.Role: return interaction.options.getRole(this.name) ?? void 0; case import_discord5.ApplicationCommandOptionType.Channel: return interaction.options.getChannel(this.name) ?? void 0; case import_discord5.ApplicationCommandOptionType.Mentionable: return interaction.options.getMentionable(this.name) ?? void 0; case import_discord5.ApplicationCommandOptionType.User: return interaction.options.getMember(this.name) ?? interaction.options.getUser(this.name) ?? void 0; default: return interaction.options.getString(this.name) ?? void 0; } } parse(interaction) { if (this._transformer !== void 0) { return this._transformer(this.parseType(interaction), interaction); } return this.parseType(interaction); } }; // src/decorators/classes/DApplicationCommandOptionChoice.ts var import_internal4 = require("@discordx/internal"); var DApplicationCommandOptionChoice = class _DApplicationCommandOptionChoice extends import_internal4.Decorator { _name; _nameLocalizations; _value; get name() { return this._name; } set name(value) { this._name = value; } get nameLocalizations() { return this._nameLocalizations; } set nameLocalizations(value) { this._nameLocalizations = value; } get value() { return this._value; } set value(value) { this._value = value; } constructor(data) { super(); this._name = data.name; this._nameLocalizations = data.nameLocalizations ?? null; this._value = data.value ?? data.name; } static create(data) { return new _DApplicationCommandOptionChoice(data); } toJSON() { return { name: this.name, nameLocalizations: this.nameLocalizations, value: this.value }; } }; // src/decorators/classes/DComponent.ts var DComponent = class _DComponent extends Method { _type; _id; _guilds; _botIds; get type() { return this._type