discordx
Version:
Create a discord bot with TypeScript and Decorators!
1,730 lines (1,718 loc) • 93.6 kB
JavaScript
// 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
import crypto from "crypto";
import { EmbedBuilder } from "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 EmbedBuilder();
embed.setColor(crypto.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
import { DIService } from "@discordx/di";
import {
ApplicationCommandOptionType,
ApplicationCommandType,
Client as ClientJS,
InteractionType
} from "discord.js";
import escapeRegExp from "lodash/escapeRegExp.js";
var Client = class _Client extends ClientJS {
_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 === ApplicationCommandOptionType.Subcommand || option.type === ApplicationCommandOptionType.SubcommandGroup) && optionIndex !== 0 ? "\n" : ""}${tab}>> ${option.type === ApplicationCommandOptionType.Subcommand || option.type === ApplicationCommandOptionType.SubcommandGroup ? option.name : option.name}: ${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 === ApplicationCommandOptionType.SubcommandGroup || option.type === 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 === ApplicationCommandType.ChatInput;
case 2:
return slash.group === tree[0] && slash.subgroup === void 0 && slash.name === tree[1] && slash.type === ApplicationCommandType.ChatInput;
case 3:
return slash.group === tree[0] && slash.subgroup === tree[1] && slash.name === tree[2] && slash.type === 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 === 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 === 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(
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) => escapeRegExp(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
import { ApplicationIntegrationType } from "discord.js";
import { ApplicationCommandOptionType as ApplicationCommandOptionType2 } from "discord.js";
// src/decorators/classes/Method.ts
import { Decorator } from "@discordx/internal";
var Method = class extends 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 ?? [
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: ApplicationCommandOptionType2.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 === ApplicationCommandOptionType2.Subcommand || a.type === ApplicationCommandOptionType2.SubcommandGroup) && (b.type === ApplicationCommandOptionType2.Subcommand || b.type === ApplicationCommandOptionType2.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
import { Decorator as Decorator2 } from "@discordx/internal";
var DApplicationCommandGroup = class _DApplicationCommandGroup extends Decorator2 {
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
import { Decorator as Decorator3 } from "@discordx/internal";
import { ApplicationCommandOptionType as ApplicationCommandOptionType3 } from "discord.js";
var DApplicationCommandOption = class _DApplicationCommandOption extends Decorator3 {
_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 === ApplicationCommandOptionType3.Subcommand || this.type === ApplicationCommandOptionType3.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 ApplicationCommandOptionType3.Attachment:
return interaction.options.getAttachment(this.name) ?? void 0;
case ApplicationCommandOptionType3.String:
return interaction.options.getString(this.name) ?? void 0;
case ApplicationCommandOptionType3.Boolean:
return interaction.options.getBoolean(this.name) ?? void 0;
case ApplicationCommandOptionType3.Number:
return interaction.options.getNumber(this.name) ?? void 0;
case ApplicationCommandOptionType3.Integer:
return interaction.options.getInteger(this.name) ?? void 0;
case ApplicationCommandOptionType3.Role:
return interaction.options.getRole(this.name) ?? void 0;
case ApplicationCommandOptionType3.Channel:
return interaction.options.getChannel(this.name) ?? void 0;
case ApplicationCommandOptionType3.Mentionable:
return interaction.options.getMentionable(this.name) ?? void 0;
case ApplicationCommandOptionType3.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
import { Decorator as Decorator4 } from "@discordx/internal";
var DApplicationCommandOptionChoice = class _DApplicationCommandOptionChoice extends Decorator4 {
_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;
}
get botIds() {
return this._botIds;
}
set botIds(value) {
this._botIds = value;
}
get id() {
return this._id;
}
set id(value) {
this._id = value;
}
get guilds() {
return this._guilds;
}
set guilds(value) {
this._guilds = value;
}
constructor(data) {
super();
this._type = data.type;
this._id = data.id;
this._guilds = data.guilds ?? [];
this._botIds = data.botIds ?? [];
}
static create(data) {
return new _DComponent(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);
}
isId(text) {
return typeof this.id === "string" ? this.id === text : this.id.test(text);
}
parseParams() {
return [];
}
};
// src/decorators/classes/DDiscord.ts
import { DIService as DIService2 } from "@discordx/di";
import { Decorator as Decorator5 } from "@discordx/internal";
var DDiscord = class _DDiscord extends Decorator5 {
_applicationCommands = [];
_botIds = [];
_buttonComponents = [];
_description;
_events = [];
_guards = [];
_guilds = [];
_modalComponents = [];
_name;
_reactions = [];
_selectMenuComponents = [];
_simpleCommands = [];
get applicationCommands() {
return this._applicationCommands;
}
set applicationCommands(value) {
this._applicationCommands = value;
}
get botIds() {
return this._botIds;
}
set botIds(value) {
this._botIds = value;
}
get buttons() {
return this._buttonComponents;
}
set buttons(value) {
this._buttonComponents = value;
}
get description() {
return this._description;
}
set description(value) {
this._description = value;
}
get events() {
return this._events;
}
set events(value) {
this._events = value;
}
get guards() {
return this._guards;
}
set guards(value) {
this._guards = value;
}
get guilds() {
return this._guilds;
}
set guilds(value) {
this._guilds = value;
}
get instance() {
return DIService2.engine.getService(this.from);
}
get modal() {
return this._modalComponents;
}
set modal(value) {
this._modalComponents = value;
}
get name() {
return this._name;
}
set name(value) {
this._name = value;
}
get reactions() {
return this._reactions;
}
set reactions(value) {
this._reactions = value;
}
get selectMenus() {
return this._selectMenuComponents;
}
set selectMenus(value) {
this._selectMenuComponents = value;
}
get simpleCommands() {
return this._simpleCommands;
}
set simpleCommands(value) {
this._simpleCommands = value;
}
constructor(name, description) {
super();
this._name = name;
this._description = description ?? name;
}
static create(name, description) {
return new _DDiscord(name, description);
}
};
// src/decorators/classes/DGuard.ts
import { Decorator as Decorator6 } from "@discordx/internal";
var DGuard = class _DGuard extends Decorator6 {
_fn;
get fn() {
return this._fn;
}
constructor(fn) {
super();
this._fn = fn;
}
static create(fn) {
return new _DGuard(fn);
}
};
// src/decorators/classes/DOn.ts
var DOn = class _DOn extends Method {
_event;
_once;
_rest;
_priority;
_botIds;
get