UNPKG

@backs/bot-base

Version:

The 6th version of back's bot base.

1,427 lines (1,409 loc) 54.7 kB
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 __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, { Bot: () => Bot2, ButtonBuilder: () => ButtonBuilder, ButtonCommand: () => ButtonCommand, ChannelSelectMenuBuilder: () => ChannelSelectMenuBuilder, ContextMenuCommand: () => ContextMenuCommand, Event: () => Event, MentionableSelectMenuBuilder: () => MentionableSelectMenuBuilder, MessageCommand: () => MessageCommand, ModalBuilder: () => ModalBuilder, ModalCommand: () => ModalCommand, MultiPageEmbed: () => MultiPageEmbed, PermissionsMode: () => PermissionsMode, Plugin: () => Plugin, RoleSelectMenuBuilder: () => RoleSelectMenuBuilder, RunIn: () => RunIn, SelectMenuCommand: () => SelectMenuCommand, SlashCommand: () => SlashCommand, StringSelectMenuBuilder: () => StringSelectMenuBuilder, UserSelectMenuBuilder: () => UserSelectMenuBuilder, default: () => createClient }); module.exports = __toCommonJS(index_exports); var import_discord8 = require("discord.js"); var import_utils3 = require("@backs/utils"); // node_modules/colorette/index.js var tty = __toESM(require("tty"), 1); var { env = {}, argv = [], platform = "" } = typeof process === "undefined" ? {} : process; var isDisabled = "NO_COLOR" in env || argv.includes("--no-color"); var isForced = "FORCE_COLOR" in env || argv.includes("--color"); var isWindows = platform === "win32"; var isDumbTerminal = env.TERM === "dumb"; var isCompatibleTerminal = tty && tty.isatty && tty.isatty(1) && env.TERM && !isDumbTerminal; var isCI = "CI" in env && ("GITHUB_ACTIONS" in env || "GITLAB_CI" in env || "CIRCLECI" in env); var isColorSupported = !isDisabled && (isForced || isWindows && !isDumbTerminal || isCompatibleTerminal || isCI); var replaceClose = (index, string, close, replace, head = string.substring(0, index) + replace, tail = string.substring(index + close.length), next = tail.indexOf(close)) => head + (next < 0 ? tail : replaceClose(next, tail, close, replace)); var clearBleed = (index, string, open, close, replace) => index < 0 ? open + string + close : open + replaceClose(index, string, close, replace) + close; var filterEmpty = (open, close, replace = open, at = open.length + 1) => (string) => string || !(string === "" || string === void 0) ? clearBleed( ("" + string).indexOf(close, at), string, open, close, replace ) : ""; var init = (open, close, replace) => filterEmpty(`\x1B[${open}m`, `\x1B[${close}m`, replace); var colors = { reset: init(0, 0), bold: init(1, 22, "\x1B[22m\x1B[1m"), dim: init(2, 22, "\x1B[22m\x1B[2m"), italic: init(3, 23), underline: init(4, 24), inverse: init(7, 27), hidden: init(8, 28), strikethrough: init(9, 29), black: init(30, 39), red: init(31, 39), green: init(32, 39), yellow: init(33, 39), blue: init(34, 39), magenta: init(35, 39), cyan: init(36, 39), white: init(37, 39), gray: init(90, 39), bgBlack: init(40, 49), bgRed: init(41, 49), bgGreen: init(42, 49), bgYellow: init(43, 49), bgBlue: init(44, 49), bgMagenta: init(45, 49), bgCyan: init(46, 49), bgWhite: init(47, 49), blackBright: init(90, 39), redBright: init(91, 39), greenBright: init(92, 39), yellowBright: init(93, 39), blueBright: init(94, 39), magentaBright: init(95, 39), cyanBright: init(96, 39), whiteBright: init(97, 39), bgBlackBright: init(100, 49), bgRedBright: init(101, 49), bgGreenBright: init(102, 49), bgYellowBright: init(103, 49), bgBlueBright: init(104, 49), bgMagentaBright: init(105, 49), bgCyanBright: init(106, 49), bgWhiteBright: init(107, 49) }; var createColors = ({ useColor = isColorSupported } = {}) => useColor ? colors : Object.keys(colors).reduce( (colors2, key) => ({ ...colors2, [key]: String }), {} ); var { reset, bold, dim, italic, underline, inverse, hidden, strikethrough, black, red, green, yellow, blue, magenta, cyan, white, gray, bgBlack, bgRed, bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite, blackBright, redBright, greenBright, yellowBright, blueBright, magentaBright, cyanBright, whiteBright, bgBlackBright, bgRedBright, bgGreenBright, bgYellowBright, bgBlueBright, bgMagentaBright, bgCyanBright, bgWhiteBright } = createColors(); // src/index.ts var import_glob = require("glob"); var import_path = __toESM(require("path")); var import_is_glob = __toESM(require("is-glob")); var import_logger = __toESM(require("@backs/logger")); var import_config_parser = __toESM(require("@backs/config-parser")); // src/managers/commands.ts var import_discord = require("discord.js"); // src/typings/commands.ts var RunIn = /* @__PURE__ */ ((RunIn2) => { RunIn2[RunIn2["DM"] = 0] = "DM"; RunIn2[RunIn2["Guild"] = 1] = "Guild"; RunIn2[RunIn2["Any"] = 2] = "Any"; return RunIn2; })(RunIn || {}); var PermissionsMode = /* @__PURE__ */ ((PermissionsMode2) => { PermissionsMode2[PermissionsMode2["MatchNone"] = 0] = "MatchNone"; PermissionsMode2[PermissionsMode2["MatchOne"] = 1] = "MatchOne"; PermissionsMode2[PermissionsMode2["MatchAll"] = 2] = "MatchAll"; return PermissionsMode2; })(PermissionsMode || {}); // src/managers/commands.ts var import_utils = require("@backs/utils"); var CommandManager = class { client; constructor(client) { this.client = client; } stores = { slash: new import_discord.Collection(), contextMenuUser: new import_discord.Collection(), contextMenuMessage: new import_discord.Collection(), message: new import_discord.Collection(), selectMenu: new import_discord.Collection(), button: new import_discord.Collection(), modal: new import_discord.Collection() }; ratelimits = { user: new import_discord.Collection(), guild: new import_discord.Collection(), channel: new import_discord.Collection(), global: new import_discord.Collection() }; destroy() { for (const store of Object.values(this.stores)) store.clear(); for (const ratelimit of Object.values(this.ratelimits)) ratelimit.clear(); } async run(intOrMsg, { commandName, precommand, postcommand, postprocess, postrun, behavior, translations }) { try { if (await precommand?.(intOrMsg) === false) return; if (intOrMsg instanceof import_discord.Message) { const command = this.stores.message.get(commandName); if (!command) { if (behavior === "preferReply") return await intOrMsg.reply({ embeds: [ new import_discord.EmbedBuilder().setDescription(translations?.unavailable?.[intOrMsg.guild?.preferredLocale ?? "en_US"] ?? "This command is unavailable.").setColor("Red") ] }); else return; } if (await postcommand?.(intOrMsg, command) === false) return; if (command.enabled === false) { if (behavior === "preferReply") return await intOrMsg.reply({ embeds: [ new import_discord.EmbedBuilder().setDescription(translations?.unavailable?.[intOrMsg.guild?.preferredLocale ?? "en_US"] ?? "This command is unavailable.").setColor("Red") ] }); else return; } if (intOrMsg.inGuild() && command.options?.runIn === 0 /* DM */ || !intOrMsg.inGuild() && command.options?.runIn === 1 /* Guild */ || intOrMsg.inGuild() && !this.checkPermissions(command, intOrMsg.member)) { if (behavior !== "preferIgnore") return await intOrMsg.reply({ embeds: [ new import_discord.EmbedBuilder().setDescription(translations?.notallowed?.[intOrMsg.guild?.preferredLocale ?? intOrMsg.guild?.preferredLocale ?? "en_US"] ?? "You cannot use this command.").setColor("Red") ] }); else return; } if (this.ratelimit(command, intOrMsg.author.id)) { if (behavior !== "preferIgnore") return await intOrMsg.reply({ embeds: [ new import_discord.EmbedBuilder().setDescription(translations?.ratelimit?.[intOrMsg.guild?.preferredLocale ?? "en_US"] ?? "You're a bit too quick there.").setColor("Red") ] }); else return; } const args = command.data.arguments ? this.parseArgs(intOrMsg.content.slice(intOrMsg.content.indexOf(commandName) - 1 + commandName.length), command.data.arguments) : {}; if (await postprocess?.(intOrMsg, command) === false) return; const result = await command.run(intOrMsg, args); if (await postrun?.(intOrMsg, command, result) === false) return; } else if (intOrMsg.isCommand()) { let command; intOrMsg.member; if (intOrMsg.isChatInputCommand()) command = this.stores.slash.get(intOrMsg.commandName); else { if (intOrMsg.isUserContextMenuCommand()) command = this.stores.contextMenuUser.get(intOrMsg.commandName); else command = this.stores.contextMenuMessage.get(intOrMsg.commandName); } if (!command) { if (behavior !== "preferIgnore") return await intOrMsg.reply({ embeds: [ new import_discord.EmbedBuilder().setDescription(translations?.unavailable?.[intOrMsg.locale ?? intOrMsg.guild?.preferredLocale ?? "en_US"] ?? "This command is unavailable.").setColor("Red") ], ephemeral: true }); else return; } if (await postcommand?.(intOrMsg, command) === false) return; if (command.enabled === false) { if (behavior !== "preferIgnore") return await intOrMsg.reply({ embeds: [ new import_discord.EmbedBuilder().setDescription(translations?.unavailable?.[intOrMsg.guild?.preferredLocale ?? "en_US"] ?? "This command is unavailable.").setColor("Red") ], ephemeral: true }); else return; } if (intOrMsg.inGuild() && command.options?.runIn === 0 /* DM */ || !intOrMsg.inGuild() && command.options?.runIn === 1 /* Guild */ || intOrMsg.inGuild() && !this.checkPermissions(command, intOrMsg.member)) { if (behavior !== "preferIgnore") return await intOrMsg.reply({ embeds: [ new import_discord.EmbedBuilder().setDescription(translations?.notallowed?.[intOrMsg.locale ?? intOrMsg.guild?.preferredLocale ?? "en_US"] ?? "You cannot use this command.").setColor("Red") ], ephemeral: true }); else return; } if (this.ratelimit(command, intOrMsg.user.id)) { if (behavior !== "preferIgnore") return await intOrMsg.reply({ embeds: [ new import_discord.EmbedBuilder().setDescription(translations?.ratelimit?.[intOrMsg.locale ?? intOrMsg.guild?.preferredLocale ?? "en_US"] ?? "You're a bit too quick there.").setColor("Red") ], ephemeral: true }); else return; } if (await postprocess?.(intOrMsg, command) === false) return; const result = await command.run(intOrMsg); if (await postrun?.(intOrMsg, command, result) === false) return; } else if (intOrMsg.isAutocomplete()) { const command = this.stores.slash.get(intOrMsg.commandName); if (!command || command.enabled === false || !(intOrMsg.options.getFocused(true).name in (command.autocomplete ?? {}))) return await intOrMsg.respond([]); if (await postcommand?.(intOrMsg, command) === false) return; const result = await command.autocomplete[intOrMsg.options.getFocused(true).name](intOrMsg, intOrMsg.options.getFocused()); if (await postrun?.(intOrMsg, command, result) === false) return; } else { let command; const args = this.parseArgs(intOrMsg.customId, command?.arguments ?? []); if (!args || !args.id) { if (behavior !== "preferIgnore" || intOrMsg.isModalSubmit()) return await intOrMsg.reply({ embeds: [ new import_discord.EmbedBuilder().setDescription(translations?.unavailable?.[intOrMsg.locale ?? intOrMsg.guild?.preferredLocale ?? "en_US"] ?? "This command is unavailable.").setColor("Red") ], ephemeral: true }); else return await intOrMsg.update({}); } if (intOrMsg.isButton() && args.id === "multipageembed") return; if (intOrMsg.isButton()) command = this.stores.button.get(args.id); else if (intOrMsg.isAnySelectMenu()) command = this.stores.selectMenu.get(args.id); else command = this.stores.modal.get(args.id); if (!command) { if (behavior !== "preferIgnore") return await intOrMsg.reply({ embeds: [ new import_discord.EmbedBuilder().setDescription(translations?.unavailable?.[intOrMsg.locale ?? intOrMsg.guild?.preferredLocale ?? "en_US"] ?? "This command is unavailable.").setColor("Red") ], ephemeral: true }); else return; } if (await postcommand?.(intOrMsg, command) === false) return; if (command.enabled === false) { if (behavior !== "preferIgnore") return await intOrMsg.reply({ embeds: [ new import_discord.EmbedBuilder().setDescription(translations?.unavailable?.[intOrMsg.guild?.preferredLocale ?? "en_US"] ?? "This command is unavailable.").setColor("Red") ], ephemeral: true }); else return; } if (args.userId && intOrMsg.user.id !== args.userId) { if (behavior !== "preferIgnore") return await intOrMsg.reply({ embeds: [ new import_discord.EmbedBuilder().setDescription(translations?.notallowed?.[intOrMsg.locale ?? intOrMsg.guild?.preferredLocale ?? "en_US"] ?? "You are not allowed to use this command.").setColor("Red") ], ephemeral: true }); else return; } if (await postprocess?.(intOrMsg, command) === false) return; const result = await command.run(intOrMsg, args); if (await postrun?.(intOrMsg, command, result) === false) return; } } catch (err) { this.client.logger.error(err); if (intOrMsg instanceof import_discord.Message) await intOrMsg.reply({ embeds: [ new import_discord.EmbedBuilder().setDescription("An error occurred while executing this command.").setColor(this.client.color("error")) ] }); else { if (intOrMsg.isAutocomplete()) { if (!intOrMsg.responded) await intOrMsg.respond([]).catch(() => { }); } else if (intOrMsg.replied) await intOrMsg.followUp({ embeds: [ new import_discord.EmbedBuilder().setDescription("An error occurred while executing this command.").setColor(this.client.color("error")) ] }).catch(() => { }); else if (intOrMsg.deferred) await intOrMsg.editReply({ embeds: [ new import_discord.EmbedBuilder().setDescription("An error occurred while executing this command.").setColor(this.client.color("error")) ] }).catch(() => { }); else await intOrMsg.reply({ embeds: [ new import_discord.EmbedBuilder().setDescription("An error occurred while executing this command.").setColor(this.client.color("error")) ], ephemeral: true }).catch(() => { }); } } } checkPermissions(command, user) { if (this.client.botOptions.owners?.includes(user.user.id)) return true; const permissions = user instanceof import_discord.GuildMember ? user.permissions : new import_discord.PermissionsBitField(BigInt(user.permissions)); const restricted = command.options?.restricted ?? false; if (!command.options?.permissions) return !restricted; if (command.options.permissions.allow) { const mode = command.options.permissions.allowMode ?? 1 /* MatchOne */; if (mode === 1 /* MatchOne */) { for (const permission of command.options.permissions.allow) if (permissions.has(permission, true)) return true; return false; } else if (mode === 2 /* MatchAll */) { for (const permission of command.options.permissions.allow) if (!permissions.has(permission, true)) return false; return true; } else { for (const permission of command.options.permissions.allow) if (permissions.has(permission, true)) return false; return true; } } if (command.options.permissions.deny) { const mode = command.options.permissions.allowMode ?? 1 /* MatchOne */; if (mode === 1 /* MatchOne */) { for (const permission of command.options.permissions.deny) if (permissions.has(permission, true)) return false; return true; } else if (mode === 2 /* MatchAll */) { for (const permission of command.options.permissions.deny) if (!permissions.has(permission, true)) return true; return false; } else { for (const permission of command.options.permissions.deny) if (permissions.has(permission, true)) return true; return false; } } return !restricted; } ratelimit(command, userId) { const ratelimit = command.options?.ratelimit; if (!ratelimit) return false; for (const [key, value] of Object.entries(ratelimit)) { if (value === 0) continue; const now = Date.now(); const ratelimits = this.ratelimits[key]; if (ratelimits.has(userId)) { const userRatelimits = ratelimits.get(userId); ratelimits.set(userId, userRatelimits.filter((r) => r.expires > now)); const commandRatelimit = userRatelimits.find((r) => r.command === command.data.name && r.type === command.commandType); if (commandRatelimit && commandRatelimit.expires > now) return true; } const expires = now + value; ratelimits.set(userId, [...ratelimits.get(userId) ?? [], { command: command.data.name, type: command.commandType, expires }]); } return false; } parseArgs(args, format) { if (Array.isArray(format)) { const argsArray = args.split("/"); return { id: argsArray.shift(), userId: argsArray.shift(), ...argsArray.reduce((o, v, i) => { o[format[i]] = v; return o; }, {}) }; } else if (typeof format === "string") { const argDefinitions = args.match(/<[^<>\[\]]>|\[[^<>\[\]]\]>/g) ?? [], parsedArgs = {}, content = args.split(/\s+/); const getNext = (required) => { const c = content.shift(); if (required && !c) throw new Error("Missing required argument."); else if (!c) return void 0; else return c; }; for (let i = 0; i < argDefinitions.length; i++) { const arg = argDefinitions[i]; const [name, options] = arg.match(/(?:<|\[)([a-zA-Z0-9_-]+)(:(?:[a-zA-Z0-9_-],? *)+|\((?:.+)\)|\.\.\.)?(?:>|])/) ?? []; const isRequired = arg.startsWith("<") && arg.endsWith(">"); if (!isRequired && (!arg.startsWith("[") || !arg.endsWith("]"))) throw new Error("Invalid format."); if (!name) throw new Error("Invalid format."); if (options) { if (options.endsWith("...")) { if (i !== argDefinitions.length - 1) throw new Error("Rest arguments must be the last argument."); if (content.length === 0) { if (isRequired) throw new Error("Missing required argument."); else continue; } const c = args.match(new RegExp(`\\s(${(0, import_utils.escape)(content[0])}s+)`))[1]; parsedArgs[name] = c; } else if (options.startsWith("(") && options.endsWith(")")) { const regex = new RegExp(options.slice(1, -1)); const c = getNext(isRequired); if (c && !regex.test(c)) { if (isRequired) throw new Error("Invalid required argument."); else { parsedArgs[name] = void 0; continue; } ; } parsedArgs[name] = c; } else if (options.startsWith(":")) { const enums = options.slice(1).split(","); const c = getNext(isRequired); if (c && !enums.includes(c)) { if (isRequired) throw new Error("Invalid required argument."); else { parsedArgs[name] = enums.length === 1 ? false : void 0; continue; } } parsedArgs[name] = enums.length === 1 ? true : c; } else throw new Error("Invalid format."); } else parsedArgs[name] = getNext(isRequired); } return parsedArgs; } else throw new Error("Invalid format."); } }; // src/managers/plugins.ts var import_discord2 = require("discord.js"); var PluginManager = class { client; constructor(client) { this.client = client; } store = new import_discord2.Collection(); events = { prerun: {}, postrun: {} }; commands = { precommand: [], postcommand: [], postprocess: [], postrun: [] }; register(plugin) { this.store.set(plugin.name, plugin); if (!(plugin.enabled ?? true) || !(this.client.botOptions.enablePlugins ?? true) && plugin.enabled !== true) return; if (plugin.events) { for (const [name, options] of Object.entries(plugin.events)) { if (options.prerun) { if (!this.events.prerun[name]) this.events.prerun[name] = []; this.events.prerun[name].push(options.prerun); } if (options.postrun) { if (!this.events.postrun[name]) this.events.postrun[name] = []; this.events.prerun[name].push(options.postrun); } } } if (plugin.commands) { if (plugin.commands.precommand) this.commands.precommand.push(plugin.commands.precommand); if (plugin.commands.postcommand) this.commands.postcommand.push(plugin.commands.postcommand); if (plugin.commands.postprocess) this.commands.postprocess.push(plugin.commands.postprocess); if (plugin.commands.postrun) this.commands.postrun.push(plugin.commands.postrun); } } }; // src/managers/multipageembed.ts var import_discord7 = require("discord.js"); // src/structures/multipageembed.ts var import_discord6 = require("discord.js"); // src/builders/button.ts var import_discord3 = require("discord.js"); var ButtonBuilder = class extends import_discord3.ButtonBuilder { customId; userId; arguments; constructor(data) { super(data); } setCustomId(id) { this.customId = id; const customId = [this.customId, this.userId ?? "", ...this.arguments ?? []].join("/"); return super.setCustomId(customId); } setUserId(id) { this.userId = id; const customId = [this.customId ?? "", this.userId ?? "", ...this.arguments ?? []].join("/"); return super.setCustomId(customId); } setStyle(style) { if (style === "blurple") return super.setStyle(import_discord3.ButtonStyle.Primary); else if (style === "gray") return super.setStyle(import_discord3.ButtonStyle.Secondary); else if (style === "green") return super.setStyle(import_discord3.ButtonStyle.Success); else if (style === "red") return super.setStyle(import_discord3.ButtonStyle.Danger); else if (style === "link") return super.setStyle(import_discord3.ButtonStyle.Link); else return super.setStyle(style); } setArguments(...args) { this.arguments = args; args.unshift(this.userId ?? ""); args.unshift(this.customId ?? ""); return super.setCustomId(args.join("/")); } }; // src/builders/modal.ts var import_discord4 = require("discord.js"); var ModalBuilder = class extends import_discord4.ModalBuilder { customId; userId; arguments; constructor(data) { super(data); } setCustomId(id) { this.customId = id; const customId = [this.customId, this.userId ?? "", ...this.arguments ?? []].join("/"); return super.setCustomId(customId); } setUserId(id) { this.userId = id; const customId = [this.customId ?? "", this.userId ?? "", ...this.arguments ?? []].join("/"); return super.setCustomId(customId); } setArguments(...args) { this.arguments = args; args.unshift(this.userId ?? ""); args.unshift(this.customId ?? ""); return super.setCustomId(args.join("/")); } }; // src/builders/selectmenus.ts var import_discord5 = require("discord.js"); var StringSelectMenuBuilder = class extends import_discord5.StringSelectMenuBuilder { customId; userId; arguments; constructor(data) { super(data); } setCustomId(id) { this.customId = id; const customId = [this.customId, this.userId ?? "", ...this.arguments ?? []].join("/"); return super.setCustomId(customId); } setUserId(id) { this.userId = id; const customId = [this.customId ?? "", this.userId ?? "", ...this.arguments ?? []].join("/"); return super.setCustomId(customId); } setArguments(...args) { this.arguments = args; args.unshift(this.userId ?? ""); args.unshift(this.customId ?? ""); return super.setCustomId(args.join("/")); } }; var UserSelectMenuBuilder = class extends import_discord5.UserSelectMenuBuilder { customId; userId; arguments; constructor(data) { super(data); } setCustomId(id) { this.customId = id; const customId = [this.customId, this.userId ?? "", ...this.arguments ?? []].join("/"); return super.setCustomId(customId); } setUserId(id) { this.userId = id; const customId = [this.customId ?? "", this.userId ?? "", ...this.arguments ?? []].join("/"); return super.setCustomId(customId); } setArguments(...args) { this.arguments = args; args.unshift(this.userId ?? ""); args.unshift(this.customId ?? ""); return super.setCustomId(args.join("/")); } }; var RoleSelectMenuBuilder = class extends import_discord5.RoleSelectMenuBuilder { customId; userId; arguments; constructor(data) { super(data); } setCustomId(id) { this.customId = id; const customId = [this.customId, this.userId ?? "", ...this.arguments ?? []].join("/"); return super.setCustomId(customId); } setUserId(id) { this.userId = id; const customId = [this.customId ?? "", this.userId ?? "", ...this.arguments ?? []].join("/"); return super.setCustomId(customId); } setArguments(...args) { this.arguments = args; args.unshift(this.userId ?? ""); args.unshift(this.customId ?? ""); return super.setCustomId(args.join("/")); } }; var ChannelSelectMenuBuilder = class extends import_discord5.ChannelSelectMenuBuilder { customId; userId; arguments; constructor(data) { super(data); } setCustomId(id) { this.customId = id; const customId = [this.customId, this.userId ?? "", ...this.arguments ?? []].join("/"); return super.setCustomId(customId); } setUserId(id) { this.userId = id; const customId = [this.customId ?? "", this.userId ?? "", ...this.arguments ?? []].join("/"); return super.setCustomId(customId); } setArguments(...args) { this.arguments = args; args.unshift(this.userId ?? ""); args.unshift(this.customId ?? ""); return super.setCustomId(args.join("/")); } }; var MentionableSelectMenuBuilder = class extends import_discord5.MentionableSelectMenuBuilder { customId; userId; arguments; constructor(data) { super(data); } setCustomId(id) { this.customId = id; const customId = [this.customId, this.userId ?? "", ...this.arguments ?? []].join("/"); return super.setCustomId(customId); } setUserId(id) { this.userId = id; const customId = [this.customId ?? "", this.userId ?? "", ...this.arguments ?? []].join("/"); return super.setCustomId(customId); } setArguments(...args) { this.arguments = args; args.unshift(this.userId ?? ""); args.unshift(this.customId ?? ""); return super.setCustomId(args.join("/")); } }; // src/structures/multipageembed.ts var MultiPageEmbed = class { _id; _userId; _expires; _startingPage; _embeds; _canLoop; _jump; get id() { return this._id; } get userId() { return this._userId; } get expires() { return this._expires; } get startingPage() { return this._startingPage; } get embeds() { return this._embeds; } get canLoop() { return this._canLoop; } get jump() { return this._jump; } constructor(data) { this._id = data.id; this._expires = data.expires ?? 12e4; this._startingPage = data.startingPage ?? 0; this._embeds = data.embeds ?? []; this._canLoop = data.canLoop ?? true; this._jump = data.jump ?? false; } get components() { const row = new import_discord6.ActionRowBuilder(); if (this.jump) row.addComponents( new ButtonBuilder().setCustomId("multipageembed").setUserId(this.userId).setArguments("jump-start", this.id).setEmoji("\u23EE\uFE0F").setStyle("gray").setDisabled(this.startingPage === 0) ); row.addComponents( new ButtonBuilder().setCustomId("multipageembed").setUserId(this.userId).setArguments("previous", this.id).setEmoji("\u23EA").setStyle("gray").setDisabled(this.startingPage === 0 && !this.canLoop), new ButtonBuilder().setCustomId("multipageembed").setUserId(this.userId).setArguments("next", this.id).setEmoji("\u23E9").setStyle("gray").setDisabled(this.startingPage === this.embeds.length - 1 && !this.canLoop) ); if (this.jump) row.addComponents( new ButtonBuilder().setCustomId("multipageembed").setUserId(this.userId).setArguments("jump-end", this.id).setEmoji("\u23ED\uFE0F").setStyle("gray").setDisabled(this.startingPage === this.embeds.length - 1) ); return [row]; } get page() { return this.embeds[this.startingPage]; } }; // src/managers/multipageembed.ts var import_utils2 = require("@backs/utils"); var MultiPageEmbedManager = class { client; constructor(client) { this.client = client; this.client.on("interactionCreate", async (interaction) => { if (interaction.isButton()) { const args = this.client.commands.parseArgs(interaction.customId, ["command", "embedId"]); if (!args.command || !args.embedId || args.userId && args.userId !== interaction.user.id) return await interaction.update({}); if (args.id === "multipageembed") { await interaction.deferUpdate(); const shouldUpdate = this.handle(args.embedId, args.command); if (shouldUpdate === true) { const handler = this.handlers.get(args.embedId); const embed = handler.embed.embeds[handler.index]; const row = new import_discord7.ActionRowBuilder(); if (handler.embed.jump) row.addComponents( new ButtonBuilder().setCustomId("multipageembed").setUserId(handler.userId).setArguments("jump-start", args.embedId).setEmoji("\u23EE\uFE0F").setStyle("gray").setDisabled(handler.index === 0) ); row.addComponents( new ButtonBuilder().setCustomId("multipageembed").setUserId(handler.userId).setArguments("previous", args.embedId).setEmoji("\u23EA").setStyle("gray").setDisabled(handler.index === 0 && !handler.embed.canLoop), new ButtonBuilder().setCustomId("multipageembed").setUserId(handler.userId).setArguments("next", args.embedId).setEmoji("\u23E9").setStyle("gray").setDisabled(handler.index === handler.embed.embeds.length - 1 && !handler.embed.canLoop) ); if (handler.embed.jump) row.addComponents( new ButtonBuilder().setCustomId("multipageembed").setUserId(handler.userId).setArguments("jump-end", args.embedId).setEmoji("\u23ED\uFE0F").setStyle("gray").setDisabled(handler.index === handler.embed.embeds.length - 1) ); await interaction.editReply({ embeds: [embed, ...interaction.message.embeds.slice(1)], components: [row, ...interaction.message.components.slice(1)] }); } else if (shouldUpdate === false) { await interaction.editReply({}); } else { await interaction.editReply({ components: [...interaction.message.components.slice(1)] }); } } } }); } embeds = new import_discord7.Collection(); handlers = new import_discord7.Collection(); create(data, createEmbed = true, userId) { const embedId = data.id ?? (0, import_utils2.uuid)(); this.embeds.set(embedId, { ...data, id: void 0 }); const id = (0, import_utils2.uuid)(); if (!createEmbed) return; const embed = new MultiPageEmbed({ ...data, id }); const expires = embed.expires; this.handlers.set(id, { embed, index: data.startingPage ?? 0, expires: Date.now() + expires, userId }); setTimeout(() => this.handlers.delete(id), expires); return embed; } get(embedId, userId) { const embedData = this.embeds.get(embedId); if (!embedData) return; const id = (0, import_utils2.uuid)(); const embed = new MultiPageEmbed({ ...embedData, id }); const expires = embed.expires; this.handlers.set(id, { embed, index: embedData.startingPage ?? 0, expires: Date.now() + expires, userId }); setTimeout(() => this.handlers.delete(id), expires); return embed; } delete(embedId) { this.embeds.delete(embedId); } handle(embedId, command) { const handler = this.handlers.get(embedId); if (!handler) return; switch (command) { case "jump-start": if (!handler.embed.jump) return false; handler.index = 0; break; case "previous": if (handler.index === 0) { if (handler.embed.canLoop) handler.index = handler.embed.embeds.length - 1; } else handler.index--; break; case "next": if (handler.index === handler.embed.embeds.length - 1) { if (handler.embed.canLoop) handler.index = 0; } else handler.index++; break; case "jump-end": if (!handler.embed.jump) return false; handler.index = handler.embed.embeds.length - 1; break; } return true; } }; // src/structures/commands.ts var MessageCommand = class { constructor(options) { Object.assign(this, options); } commandType = "message"; data; run; enabled; options; isIDBased() { return false; } isSlashCommand() { return false; } isContextMenuCommand() { return false; } isMessageCommand() { return true; } }; var SlashCommand = class { constructor(options) { Object.assign(this, options); } commandType = "slash"; data; run; autocomplete; enabled; options; isIDBased() { return false; } isSlashCommand() { return true; } isContextMenuCommand() { return false; } isMessageCommand() { return false; } }; var ContextMenuCommand = class { constructor(options) { Object.assign(this, options); } commandType; data; type; run; enabled; options; isIDBased() { return false; } isSlashCommand() { return false; } isContextMenuCommand() { return true; } isUserContextMenuCommand() { return this.type === "user"; } isMessageContextMenuCommand() { return this.type === "message"; } isMessageCommand() { return false; } }; var SelectMenuCommand = class { constructor(options) { Object.assign(this, options); } commandType = "selectMenu"; run; id; type; user; arguments; enabled; options; isIDBased() { return true; } isSlashCommand() { return false; } isContextMenuCommand() { return false; } isMessageCommand() { return false; } isSelectMenuCommand() { return true; } isButtonCommand() { return false; } isModalCommand() { return false; } isStringSelectMenuCommand() { return this.type === "string"; } isUserSelectMenuCommand() { return this.type === "user"; } isChannelSelectMenuCommand() { return this.type === "channel"; } isRoleSelectMenuCommand() { return this.type === "role"; } isMentionableSelectMenuCommand() { return this.type === "mentionable"; } }; var ButtonCommand = class { constructor(options) { Object.assign(this, options); } commandType = "button"; run; id; user; arguments; enabled; options; isIDBased() { return true; } isSlashCommand() { return false; } isContextMenuCommand() { return false; } isMessageCommand() { return false; } isSelectMenuCommand() { return false; } isButtonCommand() { return true; } isModalCommand() { return false; } }; var ModalCommand = class { constructor(options) { Object.assign(this, options); } commandType = "modal"; run; id; user; arguments; enabled; options; isIDBased() { return true; } isSlashCommand() { return false; } isContextMenuCommand() { return false; } isMessageCommand() { return false; } isSelectMenuCommand() { return false; } isButtonCommand() { return false; } isModalCommand() { return true; } }; // src/structures/events.ts var Event = class { constructor(options) { Object.assign(this, options); } name; readableName; enabled; once; run; }; // src/structures/plugins.ts var Plugin = class { constructor(options) { Object.assign(this, options); } name; description; enabled; version; events; commands; }; // src/index.ts async function createClient(options) { const configParser = new import_config_parser.default({ ...options.configOptions, logging: { debug: process.env.NODE_ENV === "debug" }, start: false }); await configParser.start(); const parsedOptions = parseOptions(configParser.configs, options); return new Bot2(parsedOptions, configParser, options); } var Bot2 = class extends import_discord8.Client { configParser; botOptions; _botOptions; logger; commands; plugins; multiPageEmbed; get configs() { return this.configParser.configs; } get clientEmojis() { const emojis = this._botOptions.emojis; if (typeof emojis === "function") return emojis(this.configs); else return this.botOptions.emojis ?? {}; } get clientColors() { const colors2 = this._botOptions.colors; if (typeof colors2 === "function") return colors2(this.configs); else return this.botOptions.colors ?? {}; } constructor(options, configParser, BotBaseOptions) { options.clientOptions.ws ??= {}; options.clientOptions.ws.properties ??= {}; options.clientOptions.ws.properties.$browser = options.clientOptions.browser; super(options.clientOptions); this.configParser = configParser; this.botOptions = options; this._botOptions = BotBaseOptions; this.botOptions.debug ??= process.env.NODE_ENV === "debug"; this.botOptions.loggerOptions ??= {}; this.botOptions.loggerOptions.log ??= {}; this.botOptions.loggerOptions.log.debug ??= this.botOptions.debug; this.logger = (0, import_logger.default)(this.botOptions.loggerOptions); this.commands = new CommandManager(this); this.plugins = new PluginManager(this); this.multiPageEmbed = new MultiPageEmbedManager(this); process.on("uncaughtException", (err) => { this.logger.error(err); }); process.on("unhandledRejection", (err, promise) => { this.logger.error(err); promise.catch((err2) => this.logger.error(err2)); }); } emoji(name) { return this.clientEmojis[name]; } color(name) { return this.clientColors[name]; } async login(token) { if (!this.configParser.isStarted) await this.configParser.start(); token = token ?? process.env.TOKEN ?? process.env.DISCORD_TOKEN ?? process.env.BOT_TOKEN ?? process.env.DISCORD_BOT_TOKEN ?? this.botOptions.token; await this.loadPlugins(); await this.loadEvents(); await this.loadCommands(); if (this.botOptions.debug) this.on("debug", (info) => this.logger.debug((0, import_utils3.highlight)(info, magenta))); this.once("ready", () => this.logger.log("Client logged in as %bl_i.", `@${this.user.tag}`)); const loggedIn = await super.login(token).catch((err) => { this.logger.error("Failed to login to Discord. %r", "Exiting..."); this.logger.error(err); process.exit(1); }); await this.application?.fetch(); this.logger.debug( `Logged in. %m ${this.guilds.cache.size} %m ${this.ws.shards.size}`, "Guilds:", "Shards:" ); return loggedIn; } async destroy() { this.logger.debug("Stopping config parser..."); this.configParser.stop(); this.logger.debug("Clearing commands..."); this.commands.destroy(); this.logger.debug("Destroying client..."); await super.destroy(); } async load(filePath) { if ((0, import_is_glob.default)(filePath)) { const globbedFiles = await (0, import_glob.glob)(filePath); const toLoad = []; const files = {}; const loaded = []; for (let file of globbedFiles) { if (!import_path.default.isAbsolute(file)) file = import_path.default.join(import_path.default.relative(__dirname, process.cwd()), file); if (import_path.default.extname(file) !== ".ts" && import_utils3.isTypescript || import_path.default.extname(file) !== ".js" && !import_utils3.isTypescript) { files[file] = { status: "skipped", reason: import_utils3.isTypescript ? "JS file in TS environment" : "TS file in JS environment" }; continue; } toLoad.push(file); } for (const file of toLoad) { try { const loadedFiles = require(file).default; loaded.push(loadedFiles); files[file] = { status: "loaded", index: loaded.length - 1 }; } catch (err) { this.logger.error(err); files[file] = { status: "errored", reason: (0, import_utils3.crop)(err.message, 50) }; } } return { loaded, files }; } else { if (!import_path.default.isAbsolute(filePath)) filePath = import_path.default.relative(__dirname, import_path.default.join(process.cwd(), filePath)); if (import_path.default.extname(filePath) !== ".ts" && import_utils3.isTypescript || import_path.default.extname(filePath) !== ".js" && !import_utils3.isTypescript) return { loaded: [], files: { [filePath]: { status: "skipped", reason: import_utils3.isTypescript ? "JS file in TS environment" : "TS file in JS environment" } } }; try { const file = require(filePath).default; return { loaded: [file], files: { [filePath]: { status: "loaded", index: 0 } } }; } catch (err) { this.logger.error(err); return { loaded: [], files: { [filePath]: { status: "errored", reason: err.message } } }; } } } async loadCommands() { const commands = await Promise.all( this.botOptions.commands.map(async (command) => await this.load(command)) ); const loadedCommands = commands.flatMap((e) => e.loaded); const loadedCommandsData = [], commandFiles = commands.flatMap((e) => Object.entries(e.files)); for (let i = 0; i < loadedCommands.length; i++) { try { const command = loadedCommands[i], filePath = commandFiles.find((e) => e[1].index === i)[0]; if (command.isIDBased()) { if (command.isSelectMenuCommand()) this.commands.stores.selectMenu.set(command.id, command); else if (command.isButtonCommand()) this.commands.stores.button.set(command.id, command); else if (command.isModalCommand()) this.commands.stores.modal.set(command.id, command); loadedCommandsData.push({ name: command.id, type: command.isSelectMenuCommand() ? "Select Menu" : command.isButtonCommand() ? "Button" : "Modal", status: command.enabled ? green("Enabled") : red("Disabled"), loadStatus: bgGreen(`${commandFiles[i][1].status} ${commandFiles[i][1].reason ?? ""}`), filePath }); } else { if (command.isSlashCommand()) this.commands.stores.slash.set(command.data.name, command); else if (command.isContextMenuCommand()) { if (command.isUserContextMenuCommand()) this.commands.stores.contextMenuUser.set(command.data.name, command); else this.commands.stores.contextMenuMessage.set(command.data.name, command); } else if (command.isMessageCommand()) { this.commands.stores.message.set(command.data.name, command); for (const name of Object.values(command.data.nameLocalizations ?? {})) this.commands.stores.message.set(name, command); for (const alias of command.data.aliases ?? []) this.commands.stores.message.set(alias, command); } loadedCommandsData.push({ name: command.data.name, type: command.isSlashCommand() ? "Slash" : command.isContextMenuCommand() ? "Context Menu" : "Message", status: command.enabled ?? true ? green("Enabled") : red("Disabled"), loadStatus: bgGreen(`${commandFiles[i][1].status} ${commandFiles[i][1].reason ?? ""}`), filePath }); } } catch (err) { this.logger.error(err); loadedCommandsData.push({ name: import_path.default.basename(commandFiles[i][0], import_path.default.extname(commandFiles[i][0])), type: italic("Unknown"), status: red("errored"), loadStatus: bgRed(`${commandFiles[i][1].status} ${commandFiles[i][1].reason ?? ""}`), filePath: commandFiles[i][0] }); } } for (const [filePath, command] of commandFiles) { if (command.status === "loaded") continue; loadedCommandsData.push({ name: import_path.default.basename(filePath, import_path.default.extname(filePath)), type: italic("Unknown"), status: blackBright("Unknown"), loadStatus: command.status === "skipped" ? bgYellow(`${command.status} ${command.reason ?? ""}`) : bgRed(`${command.status} ${command.reason ?? ""}`), filePath }); } if (this.botOptions.debug) { this.logger.debug(" "); this.logger.table( [ ["Command Name", "Type", "Status", "Load Status", "File Path"].map((h) => bold(h)), ...loadedCommandsData.map((e) => [e.name, e.type, e.status, e.loadStatus, e.filePath]), [ bold("Total"), ...Object.entries( Object.values(commands).reduce( (acc, e) => { Object.values(e.files).forEach((e2) => acc[e2.status]++); return acc; }, { loaded: 0, skipped: 0, errored: 0 } ) ).map(([k, v]) => `${k === "loaded" ? green(k) : k === "skipped" ? yellow(k) : red(k)}: ${v}`), "" ] ], { header: { content: magenta("Loaded Commands") } } ); } } async loadEvents() { const events = await Promise.all( this.botOptions.events.map(async (event) => await this.load(event)) ); const loadedEvents = events.flatMap((e) => e.loaded); for (const event of loadedEvents) { try { if (!(event.enabled ??= true)) continue; if (event.once) this.once(event.name, async (...args) => { for (const prerun of this.plugins.events[event.name]?.prerun ?? []) await prerun(...args); const result = await event.run.bind(event)(...args); for (const postrun of this.plugins.events[event.name]?.postrun ?? []) await postrun(result, ...args); }); else this.on(event.name, async (...args) => { for (const prerun of this.plugins.events[event.name]?.prerun ?? []) await prerun(...args); const