UNPKG

commandbot

Version:

A framework that helps you create your own Discord bot easier.

256 lines (255 loc) 12.3 kB
import { DMChannel, Interaction, Message, MessageEmbed, TextChannel } from "discord.js"; /** * Stores configuration and generates system messages * * System messages - predefined messages sent by the bot in special cases (for example after an error, or when a command doesn't exist) * * @remarks You can't customize messages after starting the bot. Changing these properties while the bot is running will have no effect. * * @class */ export class SystemMessageManager { /** * Client parent attached to this manager * @type {Bot} * @public * @readonly */ client; /** * Sent whenever caller's permissions are not sufficient to run a command * @type {SystemMessageAppearance} * @public */ PERMISSION; /** * Sent when an error occurs during the execution of a command * @type {SystemMessageAppearance} * @public */ ERROR; /** * Sent when someone tries to run a command that does not exist (mainly by using prefix interactions) * @type {SystemMessageAppearance} * @public */ NOT_FOUND; /** * Sent when a command function returns _void_ without throwing an error * @type {SystemMessageAppearance} * @public * @remarks An _announceSuccess_ property must be set to _true_ (default) in order to send this message */ SUCCESS; /** * Global time (in ms) after a message gets deleted * @type {number} * @public * @remarks This time applies to all message types but it can be overwritten using local properties with the same name (for example ERROR.deleteTimeout) */ deleteTimeout; constructor(client) { this.client = client; this.PERMISSION = { enabled: true, title: "👮‍♂️ Insufficient permissions", bottomText: "You don't have enough permissions to run this command", accentColor: "#1d1dc4", displayDetails: false, showTimestamp: true, footer: this.client.name, }; this.ERROR = { enabled: true, title: "❌ An error occurred", bottomText: "Something went wrong while processing your request.", accentColor: "#ff0000", displayDetails: false, showTimestamp: true, footer: this.client.name, }; this.NOT_FOUND = { enabled: false, title: "🔍 Command not found", accentColor: "#ff5500", displayDetails: false, showTimestamp: true, footer: this.client.name, }; this.SUCCESS = { enabled: true, title: "✅ Task completed successfully", accentColor: "#00ff00", displayDetails: false, showTimestamp: true, footer: this.client.name, deleteTimeout: Infinity, }; this.deleteTimeout = Infinity; } /** * Generates and sends a system message * @param {MessageType} type - "ERROR" | "PERMISSION" | "NOT_FOUND" | "SUCCESS" * @param {?SystemMessageData} [data] - additional data to include in the message * @param {?Message | Interaction | TextChannel | DMChannel} [interaction] - if specified, the generated message will be sent in this channel * @returns {Promise<MessageEmbed | Message | void>} A message that got sent or *void* * @public * @async */ async send(type, data, interaction) { if (this[type]) { if (this[type].enabled === false) return; const embed = new MessageEmbed(); embed.setTitle(this[type].title); if (this[type].description || this[type].bottomText) embed.setDescription(this[type].description ?? this[type].bottomText ?? ""); embed.setColor(this[type].accentColor || "#000000"); if (this[type].showTimestamp) embed.setTimestamp(); if (data) { switch (type) { case "ERROR": if (this[type].displayDetails) { if (data.command) { embed.addField("Command name:", data.command.name, false); } if (data.user) { embed.addField("User:", data.user.toString(), false); } } if (data.error) { if (data.error instanceof Error) { embed.setFooter(data.error.message); } else if (typeof data.error == "string") { embed.setFooter(data.error); } } break; case "NOT_FOUND": if (data.phrase) { embed.setFooter(data.phrase); } break; case "PERMISSION": if (this[type].displayDetails) { if (data.user) { embed.addField("User:", data.user.toString(), false); } if (data.command) { embed.addField("Command name:", data.command.name, false); if (data.command.isBaseCommandType("PERMISSION")) { if (data.command.permissions.isCustom) { embed.addField("Required permissions:", "CUSTOM", false); } else { let permList = ""; data.command.permissions.permissions.toArray(false).map((p) => { permList += `${p}\n`; }); permList && embed.addField("Required permissions:", permList, false); } } } } break; case "SUCCESS": break; } } if (interaction instanceof TextChannel || interaction instanceof DMChannel) { const message = data?.command?.ephemeral === "FULL" ? await data.user?.send({ embeds: [embed] }).catch((e) => console.error(e)) : await interaction.send({ embeds: [embed] }).catch((e) => console.error(e)); if (message?.deletable) { if (Number.isFinite(this[type].deleteTimeout)) { setTimeout(async () => { await message.delete().catch((e) => console.error(e)); }, this[type].deleteTimeout); } else if (this[type].deleteTimeout === undefined && Number.isFinite(this.deleteTimeout)) { setTimeout(async () => { await message.delete().catch((e) => console.error(e)); }, this.deleteTimeout); } } return message ?? embed; } else if (interaction instanceof Message) { const message = data?.command?.ephemeral === "FULL" ? await interaction.member?.send({ embeds: [embed] }).catch((e) => console.error(e)) : await interaction.reply({ embeds: [embed] }).catch(async () => { return data?.command?.ephemeral === "FULL" ? await interaction.member?.send({ embeds: [embed] }).catch((e) => console.error(e)) : await interaction.channel.send({ embeds: [embed] }).catch((e) => console.error(e)); }); if (message?.deletable) { if (Number.isFinite(this[type].deleteTimeout)) { setTimeout(async () => { await message.delete().catch((e) => console.error(e)); }, this[type].deleteTimeout); } else if (this[type].deleteTimeout === undefined && Number.isFinite(this.deleteTimeout)) { setTimeout(async () => { await message.delete().catch((e) => console.error(e)); }, this.deleteTimeout); } } return message ?? embed; } else if (interaction instanceof Interaction && (interaction.isCommand() || interaction.isContextMenu() || interaction.isButton() || interaction.isSelectMenu() || interaction.isSelectMenu())) { const message = interaction.replied || interaction.deferred ? await interaction.editReply({ embeds: [embed] }).catch(async () => { return await interaction.channel?.send({ embeds: [embed] }).catch((e) => console.error(e)); }) : await interaction.reply({ embeds: [embed], ephemeral: data?.command?.ephemeral !== "NONE" ?? false }).catch(async () => { return data?.command?.ephemeral !== "NONE" ? await interaction.user.send({ embeds: [embed] }).catch((e) => console.error(e)) : await interaction.channel?.send({ embeds: [embed] }).catch((e) => console.error(e)); }); if (Number.isFinite(this[type].deleteTimeout)) { setTimeout(async () => { await interaction.deleteReply().catch(async () => { message instanceof Message && message?.deletable && (await message.delete().catch((e) => console.error(e))); }); }, this[type].deleteTimeout); } else if (this[type].deleteTimeout === undefined && Number.isFinite(this.deleteTimeout)) { setTimeout(async () => { await interaction.deleteReply().catch(async () => { message instanceof Message && message?.deletable && (await message.delete().catch((e) => console.error(e))); }); }, this.deleteTimeout); } return message instanceof Message ? message : embed; } else if (interaction?.channel) { const message = data?.command?.ephemeral === "FULL" ? await interaction.user.send({ embeds: [embed] }).catch((e) => console.error(e)) : await interaction.channel.send({ embeds: [embed] }).catch((e) => console.error(e)); if (message?.deletable) { if (Number.isFinite(this[type].deleteTimeout)) { setTimeout(async () => { await message.delete().catch((e) => console.error(e)); }, this[type].deleteTimeout); } else if (this[type].deleteTimeout === undefined && Number.isFinite(this.deleteTimeout)) { setTimeout(async () => { await message.delete().catch((e) => console.error(e)); }, this.deleteTimeout); } } return message ?? embed; } else { return embed; } } else { console.error("[❌ ERROR] System message: Incorrect parameter"); return; } } }