UNPKG

@backs/bot-base

Version:

The 6th version of back's bot base.

1,461 lines (1,442 loc) 52.1 kB
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); // node_modules/tsup/assets/esm_shims.js import { fileURLToPath } from "url"; import path from "path"; var getFilename = () => fileURLToPath(import.meta.url); var getDirname = () => path.dirname(getFilename()); var __dirname = /* @__PURE__ */ getDirname(); // src/index.ts import { Client } from "discord.js"; import { crop, highlight, isTypescript } from "@backs/utils"; // node_modules/colorette/index.js import * as tty from "tty"; 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 import { glob } from "glob"; import path2 from "path"; import isGlob from "is-glob"; import createLogger from "@backs/logger"; import ConfigParser from "@backs/config-parser"; // src/managers/commands.ts import { Collection, EmbedBuilder, GuildMember, Message, PermissionsBitField } from "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 import { escape } from "@backs/utils"; var CommandManager = class { client; constructor(client) { this.client = client; } stores = { slash: new Collection(), contextMenuUser: new Collection(), contextMenuMessage: new Collection(), message: new Collection(), selectMenu: new Collection(), button: new Collection(), modal: new Collection() }; ratelimits = { user: new Collection(), guild: new Collection(), channel: new Collection(), global: new 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 Message) { const command = this.stores.message.get(commandName); if (!command) { if (behavior === "preferReply") return await intOrMsg.reply({ embeds: [ new 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 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 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 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 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 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 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 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 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 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 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 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 Message) await intOrMsg.reply({ embeds: [ new 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 EmbedBuilder().setDescription("An error occurred while executing this command.").setColor(this.client.color("error")) ] }).catch(() => { }); else if (intOrMsg.deferred) await intOrMsg.editReply({ embeds: [ new EmbedBuilder().setDescription("An error occurred while executing this command.").setColor(this.client.color("error")) ] }).catch(() => { }); else await intOrMsg.reply({ embeds: [ new 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 GuildMember ? user.permissions : new 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(${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 import { Collection as Collection2 } from "discord.js"; var PluginManager = class { client; constructor(client) { this.client = client; } store = new Collection2(); 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 import { ActionRowBuilder as ActionRowBuilder2, Collection as Collection3 } from "discord.js"; // src/structures/multipageembed.ts import { ActionRowBuilder } from "discord.js"; // src/builders/button.ts import { ButtonBuilder as DiscordButtonBuilder, ButtonStyle } from "discord.js"; var ButtonBuilder = class extends DiscordButtonBuilder { 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(ButtonStyle.Primary); else if (style === "gray") return super.setStyle(ButtonStyle.Secondary); else if (style === "green") return super.setStyle(ButtonStyle.Success); else if (style === "red") return super.setStyle(ButtonStyle.Danger); else if (style === "link") return super.setStyle(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 import { ModalBuilder as DiscordModalBuilder } from "discord.js"; var ModalBuilder = class extends DiscordModalBuilder { 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 import { StringSelectMenuBuilder as DiscordStringSelectMenuBuilder, UserSelectMenuBuilder as DiscordUserSelectMenuBuilder, RoleSelectMenuBuilder as DiscordRoleSelectMenuBuilder, ChannelSelectMenuBuilder as DiscordChannelSelectMenuBuilder, MentionableSelectMenuBuilder as DiscordMentionableSelectMenuBuilder } from "discord.js"; var StringSelectMenuBuilder = class extends DiscordStringSelectMenuBuilder { 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 DiscordUserSelectMenuBuilder { 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 DiscordRoleSelectMenuBuilder { 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 DiscordChannelSelectMenuBuilder { 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 DiscordMentionableSelectMenuBuilder { 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 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 import { uuid } from "@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 ActionRowBuilder2(); 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 Collection3(); handlers = new Collection3(); create(data, createEmbed = true, userId) { const embedId = data.id ?? uuid(); this.embeds.set(embedId, { ...data, id: void 0 }); const id = 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 = 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 ConfigParser({ ...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 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 = createLogger(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(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 (isGlob(filePath)) { const globbedFiles = await glob(filePath); const toLoad = []; const files = {}; const loaded = []; for (let file of globbedFiles) { if (!path2.isAbsolute(file)) file = path2.join(path2.relative(__dirname, process.cwd()), file); if (path2.extname(file) !== ".ts" && isTypescript || path2.extname(file) !== ".js" && !isTypescript) { files[file] = { status: "skipped", reason: 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: crop(err.message, 50) }; } } return { loaded, files }; } else { if (!path2.isAbsolute(filePath)) filePath = path2.relative(__dirname, path2.join(process.cwd(), filePath)); if (path2.extname(filePath) !== ".ts" && isTypescript || path2.extname(filePath) !== ".js" && !isTypescript) return { loaded: [], files: { [filePath]: { status: "skipped", reason: 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: path2.basename(commandFiles[i][0], path2.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: path2.basename(filePath, path2.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 result = await event.run.bind(event)(...args); for (const postrun of this.plugins.events[event.name]?.postrun ?? []) await postrun(result, ...args); }); } catch (err) { this.logger.error(err); } } if (this.botOptions.debug) { this.logger.debug(" "); this.logger.table( [ ["Event Name", "Event", "Status", "Load Status", "File Path"].map((h) => bold(h)), ...events.flatMap( (e, i) => Object.entries(e.files).map(([file, { status, reason, index }]) => [ events[i].loaded[index ?? -1]?.readableName ?? events[i].loaded[index ?? -1]?.name ?? path2.basename(file, path2.extname(file)), events[i].loaded[index ?? -1]?.name ?? path2.basename(file, path2.extname(file)), events[i].loaded[index ?? -1] ? events[i].loaded[index ?? -1]?.enabled ? green("Enabled") : red("Disabled") : blackBright("Unknown"), status === "loaded" ? bgGreen(`${status} ${reason ?? ""}`) : status === "skipped" ? bgYellow(`${status} ${reason ?? ""}`) : bgRed(`${status} ${reason ?? ""}`), file ]) ), [ bold("Total"), ...Object.entries( Object.values(events).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 Events") } } ); } } async loadPlugins() { this.botOptions.plugins ??= []; const plugins = await Promise.all( this.botOptions.plugins.map(async (plugin) => await this.load(plugin)) ); const loadedPlugins = plugins.flatMap((e) => e.loaded); for (const plugin of loadedPlugins) this.plugins.register(plugin); if (this.botOptions.debug) { this.logger.debug(" "); this.logger.table( [ ["Plugin Name", "Version", "Status", "Load Status", "File Path"].map((h) => bold(h)), ...plugins.flatMap( (e, i) => Object.entries(e.files).map(([file, { status, reason, index }]) => [ plugins[i].loaded[index