diff --git a/README.md b/README.md index 4b5d29a..f430b82 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,23 @@ A Discord.js based framework that makes creating Discord bots with support for slash commands easy and fast. -# Table of contents +## Features + +- Support for slash commands +- Intuitive usage +- Good customization +- Permission system +- Built-in help command +- Built-in information messages +- Automatic error catching +- Large documentation in README and using JSDoc +- Written in TypeScript + +## Table of contents - [CommandBot](#commandbot) -- [Table of contents](#table-of-contents) + - [Features](#features) + - [Table of contents](#table-of-contents) - [Instalation](#installation) - [System requirements](#system-requirements) - [Creating a project](#creating-a-project) @@ -298,26 +311,34 @@ There are 4 information messages: - **PERMISSION** - "Insufficient permissions" message (sent when the caller doesn't have enough permissions) - **NOT_FOUND** - "Command not found" message (sent when someone tries to call a command that does not exist) -Each of these messages can be customized with these properties: +Their configuration is stored in _messages.system_ property. -- **enabled** - _boolean_ - enables or disables the message -- **title** - _string_ - title of the message +Each of these messages can be customized with these properties (\* - required): + +- **enabled\*** - _boolean_ - enables or disables the message +- **title\*** - _string_ - title of the message - **bottomText** - _string_ - text below the title (also known as description) - **accentColor** - _ColorResolvable_ - embed color +- **displayDetails** - _boolean_ - whether to display additional informations - **showTimestamp** - _boolean_ - whether to show date and time in the footer - **footer** - _string_ - message footer +- **deleteTimeout** - _number_ - time (ms) after the message gets deleted + +There is also a global _deleteTimeout_ property (_messages.system.deleteTimeout_) ## Help message -You can customize the help message with these properties: +Its configuration is stored in _messages.help_ property. -- **enabled** - _boolean_ - enables or disables the message -- **title** - _string_ - title of the message -- **bottomText** - _string_ - text below the title (also known as description) -- **accentColor** - _ColorResolvable_ - embed color -- **description** - _string_ - description shown in the message itself -- **usage** - _string_ - usage displayed in the message itself -- **visible** - _boolean_ - whether to show the help command in the list +You can customize the help message with these properties (\* - required): + +- **enabled\*** - _boolean_ - enables or disables the message +- **title\*** - _string_ - title of the message +- **bottomText\*** - _string_ - text below the title (also known as description) +- **accentColor\*** - _ColorResolvable_ - embed color +- **description\*** - _string_ - description shown in the message itself +- **usage\*** - _string_ - usage displayed in the message itself +- **visible\*** - _boolean_ - whether to show the help command in the list # Issues diff --git a/index.ts b/index.ts index 2c0dbf2..28b8bd5 100644 --- a/index.ts +++ b/index.ts @@ -1,8 +1,34 @@ import { Bot } from "./src/Bot.js"; import { Command } from "./src/Command.js"; -import { ParameterResolvable, ParameterType } from "./src/types.js"; +import { InitOptions, CommandBuilder, CommandMessageStructure, HelpMessageParams, ParameterResolvable, ParameterType } from "./src/types.js"; +import { ObjectID, InputParameter, ParameterSchema, StringParameter, BooleanParameter, NumberParameter, ObjectParameter, Parameter } from "./src/Parameter.js"; +import { SystemMessageAppearance, SystemMessageData } from "./src/SystemMessage.js"; +import { PermissionsError, ParameterTypeError, MissingParameterError, OperationSuccess } from "./src/errors.js"; import { MessageEmbed } from "discord.js"; -import { ObjectID, InputParameter } from "./src/Parameter.js"; export default Bot; -export { Bot, Command, MessageEmbed, ParameterResolvable, ParameterType, ObjectID, InputParameter }; +export { + Bot, + Command, + MessageEmbed, + InitOptions, + CommandBuilder, + CommandMessageStructure, + HelpMessageParams, + ParameterResolvable, + ParameterType, + ObjectID, + SystemMessageAppearance, + SystemMessageData, + ParameterSchema, + Parameter, + InputParameter, + StringParameter, + BooleanParameter, + NumberParameter, + ObjectParameter, + PermissionsError, + ParameterTypeError, + MissingParameterError, + OperationSuccess, +}; diff --git a/package.json b/package.json index 29f2acd..58fd036 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "commandbot", - "version": "3.0.0", + "version": "3.1.0", "description": "A framework that helps you create your own Discord bot easier.", "main": "dist/cjs/index.js", "module": "dist/mjs/index.js", diff --git a/src/Bot.ts b/src/Bot.ts index 7c7608c..b08c061 100644 --- a/src/Bot.ts +++ b/src/Bot.ts @@ -16,27 +16,57 @@ export declare interface Bot { on(event: "ERROR", listener: (e: any) => void): this; } +/** + * @class Class that represents your bot instance + * @extends {EventEmitter} + * @exports + */ export class Bot extends EventEmitter { + /** + * Bot name + * @type {string} + */ name: string; + /** + * Discord.js {@link Client} instance + * @type {Client} + */ client: Client; + /** + * Instance command manager + * @type {CommandManager} + */ commands: CommandManager; + /** + * Discord bot token + * @type {string} + */ token: string; + /** + * Discord API application ID + * @type {string} + */ applicationId: string; + /** + * Built-in messages configuration + * @type {Object} + */ messages: { + /** + * Help message configuration + * @type {HelpMessageParams} + */ help: HelpMessageParams; + /** + * {@link SystemMessageManager} storing messages' configuration + * @type {SystemMessageManager} + */ system: SystemMessageManager; }; /** - * Bot instance initializer * @constructor - * @param {InitOptions} options - instance properties - * @param {string} options.name - name of your bot - * @param {string} options.prefix - prefix used to call commands - * @param {string} [options.argumentSeparator=','] - used to get parameters from message - * @param {ClientOptions} [options.clientOptions] - client options from Discord.js - * @param {string} options.token - bot token from Discord Developer Portal - * @param {string} options.applicationId - Discord application ID used to register slash commands + * @param {InitOptions} options - instance properties ({@link InitOptions}) */ constructor(options: InitOptions) { super(); @@ -80,10 +110,11 @@ export class Bot extends EventEmitter { } /** + * @method * Starts your Discord bot * @param {number} [port] - if specified, the app will create a http server that will be listening on the specified port - * @param {boolean} [register] - if true, the bot will register all slash commands in Discord API - * @returns *Promise* + * @param {boolean} [register=true] - if *true* or *undefined*, the bot will register all slash commands in Discord API + * @returns {Promise} whether this operation has been completed successfully */ async start(port?: number, register?: boolean): Promise { try { @@ -208,8 +239,9 @@ export class Bot extends EventEmitter { } /** - * Registers commands in Discord API - * @returns *Promise* + * @method + * Registers commands from {@link CommandManager} in Discord API + * @returns {Promise} */ async register(): Promise { process.stdout.write("Registering commands... "); diff --git a/src/Command.ts b/src/Command.ts index ccd114d..83b5fac 100644 --- a/src/Command.ts +++ b/src/Command.ts @@ -5,18 +5,74 @@ import { OperationSuccess, PermissionsError } from "./errors.js"; import { DefaultParameter, InputParameter, Parameter } from "./Parameter.js"; //CLASSES +/** + * @class Class that represents a command instance + * @exports + */ export class Command { + /** + * Command name + * @type {string} + */ name: string; + /** + * List of parameters that can passed to this command + * @type {Array} {@link Parameter} + */ parameters: Parameter[]; + /** + * List of different names that can be used to invoke a command (when using prefix interactions) + * @type {Array} *string* + */ aliases?: string[]; + /** + * Command description displayed in the help message (Default description: "No description") + * @type {string} + */ description: string; + /** + * Command usage displayed in the help message + * @type {string} + */ usage?: string; + /** + * Whether to check if a caller has all defined in *permissions* property permissions or at least one of them (doesn't apply to custom function permissions) + * @type {PermissionCheckTypes} "ALL" | "ANY" + */ permissionCheck: PermissionCheckTypes; + /** + * Command permissions (if *undefined*, no permissions check will be performed) + * @type {PermissionResolvable} + * @type {Function} should return boolean value + */ permissions?: Permissions | ((m?: Message | CommandInteraction) => boolean); + /** + * List of Discord guild (server) IDs in which this command can be used + * @type {Array} *string* + */ guilds?: string[]; + /** + * Whether this command is visible in the help message (default: true) + * @type {boolean} + */ visible: boolean; + /** + * Whether this command should be registered as a slash command (default: true) + * @type {boolean} + */ slash: boolean; + /** + * Whether to send a SUCCESS message if no other response is defined (default: true) + * @type {boolean} + */ announceSuccess: boolean; + /** + * Command execution function (triggered when someone invokes the command) + * @type {Function} + * @param {Function} p - function to fetch input parameters' values + * @param {Message | CommandInteraction} i - interaction object + * @private + */ private function: ( params: (query: string, returnType?: "value" | "object") => ParameterResolvable | InputParameter | null, interaction?: Message | CommandInteraction @@ -25,17 +81,7 @@ export class Command { /** * Command constructor * @constructor - * @param {CommandBuilder} options - all command properties - * @param {string} options.name - command name (used to trigger the command) - * @param {ParameterSchema} options.parameters - all parameters that can be passed with the command - * @param {string | string[]} [options.aliases] - other words that can trigger the command with prefix (not used in slash commands) - * @param {string} [options.description="No description"] - command description shown in the help message - * @param {string} [options.usage] - command usage description shown in the help message (usage message can be automatically generated using parameters) - * @param {PermissionCheckTypes} [options.permissionCheck='ANY'] - specifies if the caller has to have all of the specified permissions or any of that - * @param {PermissionResolvable} [options.permissions] - permissions needed to run the command - * @param {boolean} [options.visible=true] - show command in the help message (visible as a slash command) - * @param {boolean} [options.slash=true] - whether the command should be available as a slash command - * @param {Function} options.function - function that will trigger when the commands gets called + * @param {CommandBuilder} options - {@link CommandBuilder} */ constructor(options: CommandBuilder) { this.name = options.name.split(" ").join("_"); @@ -65,10 +111,10 @@ export class Command { } /** - * Starts the command - * @param {Message | CommandInteraction} [interaction] - a *Message* or *CommandInteraction* object used to check caller's permissions. It will get passed to the execution function (specified in *function* property of command's constructor) - * @param {InputParameter[]} [cmdParams] - list of processed parameters passed in a Discord message - * @returns *Promise* + * Invokes the command + * @param {Message | CommandInteraction} [interaction] - Used to check caller's permissions. It will get passed to the execution function (specified in *function* property of command's constructor) + * @param {InputParameter[]} [cmdParams] - list of processed parameters passed in a Discord interaction + * @returns {Promise} */ async start(interaction?: Message | CommandInteraction, cmdParams?: InputParameter[]): Promise { const paramFindFn = function (query: string, returnType?: "value" | "object") { @@ -124,7 +170,7 @@ export class Command { } /** - * Returns object that is ready to be registered in the Discord API + * Converts {@link Command} instance to object that is recognized by the Discord API * @returns {Object} object */ toCommandObject() { @@ -163,10 +209,6 @@ export class Command { }; } - /** - * @param {string|string[]} phrase? - * @returns string - */ private static processPhrase(phrase?: string | string[]): string[] | undefined { if (Array.isArray(phrase)) { const buff = phrase.map((p) => { @@ -187,9 +229,6 @@ export class Command { } } - /** - * @returns string - */ private generateUsageFromArguments(): string { let usageTemplate: string = ""; this.parameters && diff --git a/src/CommandManager.ts b/src/CommandManager.ts index 05a18fc..8c9ad3f 100644 --- a/src/CommandManager.ts +++ b/src/CommandManager.ts @@ -4,14 +4,30 @@ import { BooleanParameter, InputParameter, NumberParameter, ObjectParameter, Str import { Command } from "./Command.js"; import { CommandMessageStructure, PhraseOccurrenceData } from "./types.js"; +/** + * @class Command manager + */ export class CommandManager { + /** + * List of commands registered in the manager + * @type {Array} {@link Command} + */ list: Command[]; + /** + * Prefix used as a way to trigger the bot using messages + * @type {string} + */ prefix?: string; + /** + * Separator used to split user input to a list of {@link InputParameter}s (applies to prefix interactions) + * @type {string} + */ parameterSeparator: string; /** - * @param {string} prefix? - * @param {string} parameterSeparator? + * @constructor + * @param {string} [prefix] - prefix used as a way to trigger the bot using messages + * @param {string} parameterSeparator - used to split user input to a list of {@link InputParameter}s (applies to prefix interactions) */ constructor(prefix?: string, parameterSeparator?: string) { this.list = []; @@ -22,7 +38,7 @@ export class CommandManager { /** * Retrieves the command by name, alias or keyword * @param {string} phrase - command name, alias or keyword - * @returns *Command* | *null* + * @returns {Command | null} Retrieved {@link Command} object from the manager or *null* */ get(phrase: string): Command | null { let command: Command | null = null; @@ -41,9 +57,9 @@ export class CommandManager { } /** - * Adds the given command to the instance manager - * @param {Command} command - *Command* or *HelpMessage* object - * @returns *boolean* + * Adds the given {@link Command} to the instance manager with initial processing + * @param {Command} command - {@link Command} instance object + * @returns {boolean} Whether this command has been added successfully */ add(command: Command): boolean { try { @@ -79,9 +95,9 @@ export class CommandManager { } /** - * Fetches command and parameters from the given *Message* object - * @param {Message} message - *Message* object - * @returns *CommandMessagesStructure* | *null* + * Fetches {@link Command} and list of {@link InputParameter}s from the given {@link Message} object + * @param {Message} message - {@link Message} object + * @returns {CommandMessagesStructure | null} {@link CommandMessageStructure} or *null* */ fetchFromMessage(message: Message): CommandMessageStructure | null { if (!this.prefix) return null; @@ -138,9 +154,9 @@ export class CommandManager { } /** - * Fetches command and parameters from the given *CommandInteraction* object - * @param {CommandInteraction} interaction - interaction object - * @returns *CommandMessagesStructure* | *null* + * Fetches command and parameters from the given {@link CommandInteraction} object + * @param {CommandInteraction} interaction - {@link CommandInteraction} object + * @returns {CommandMessagesStructure | null} {@link CommandMessageStructure} or *null* */ fetchFromInteraction(interaction: CommandInteraction): CommandMessageStructure | null { const cmd = this.get(interaction.commandName); @@ -187,10 +203,6 @@ export class CommandManager { return null; } - /** - * @param {string} phrase? - * @returns PhraseOccurrenceData - */ private findPhraseOccurrence(phrase?: string): PhraseOccurrenceData | null { let returnValue: PhraseOccurrenceData | null = null; this.list.map((c) => { diff --git a/src/Help.ts b/src/Help.ts index 6211fa7..b3c666f 100644 --- a/src/Help.ts +++ b/src/Help.ts @@ -19,15 +19,18 @@ export class HelpMessage extends Command { type: "string", }, ], - function: (param, _) => { + function: (p, i) => { const helpMsg = new MessageEmbed(); helpMsg.setColor(params.color); helpMsg.setTimestamp(); helpMsg.setFooter(botName || ""); if (helpMsg != null) { - if (param("command_name")) { - const cmd: Command | null = cmdManager.get(param("command_name")?.toString() || ""); + if (p("command_name")) { + const cmd: Command | null = cmdManager.get(p("command_name")?.toString() || ""); if (cmd) { + if (Array.isArray(cmd.guilds) && cmd.guilds.length > 0 && !cmd.guilds.find((g) => i?.guild?.id === g)) { + throw new ReferenceError(`Command "${cmd.name}" is not available`); + } helpMsg.setTitle(`${cmd.name} ${cmd.visible ? "" : "[HIDDEN]"}`); helpMsg.setDescription(cmd.description); if (cmd.usage) helpMsg.addField("Usage:", `${cmdManager.prefix || "/"}${cmd.name} ${cmd.usage}`, false); @@ -49,15 +52,20 @@ export class HelpMessage extends Command { }); helpMsg.addField("Aliases:", aList, false); } + helpMsg.addField("Slash command:", cmd.slash ? "ENABLED" : "DISABLED", false); + helpMsg.addField("Command scope:", Array.isArray(cmd.guilds) && cmd.guilds.length > 0 ? "CUSTOM" : "GLOBAL", false); } else { - throw new ReferenceError(`Command "${param("command_name")}" does not exist`); + throw new ReferenceError(`Command "${p("command_name")}" does not exist`); } } else { helpMsg.setTitle(params.title); helpMsg.setDescription(params.bottomText); cmdManager.list.map((c) => { - if (c.visible) { + if ( + c.visible && + ((Array.isArray(c.guilds) && c.guilds.length > 0 && c.guilds.find((g) => i?.guild?.id === g)) || !Array.isArray(c.guilds) || c.guilds.length === 0) + ) { helpMsg.addField(`${cmdManager.prefix || "/"}${c.name} ${c.usage}`, c.description, false); } }); diff --git a/src/Parameter.ts b/src/Parameter.ts index 0bdecd5..f024b61 100644 --- a/src/Parameter.ts +++ b/src/Parameter.ts @@ -1,17 +1,61 @@ import { CategoryChannel, Guild, GuildMember, NewsChannel, Role, StageChannel, StoreChannel, TextChannel, VoiceChannel } from "discord.js"; import { ParameterType, ParameterResolvable } from "./types.js"; +/** + * @interface + * Properties required to build a {@link Parameter} object + * @exports + */ export interface ParameterSchema { + /** + * Parameter name + * @type {string} + */ name: string; + /** + * Parameter description + * @type {string} + */ description?: string; + /** + * Whether this parameter is optional + * @type {boolean} + */ optional: boolean; + type: ParameterType; + /** + * List of value choices (available only when type is set to "STRING") + */ choices?: string[]; } +/** + * @class Representation of command parameter + * @exports + */ export class Parameter { + /** + * Parameter name + * @type {string} + */ name: string; + /** + * Parameter description + * @type {string} + */ description: string; + /** + * Whether this parameter is optional + * @type {boolean} + */ optional: boolean; + /** + * Parameter input type + * @type {ParameterType} + */ type: ParameterType; + /** + * List of value choices (available only when type is set to "STRING") + */ choices?: string[]; constructor(options: ParameterSchema) { @@ -39,7 +83,16 @@ export class DefaultParameter extends Parameter { }); } } +/** + * @class Representation of input parameter (argument) coming from a message or an interaction + * @extends Parameter + * @exports + */ export class InputParameter extends Parameter { + /** + * Input value + * @type {ParameterResolvable} + */ value: ParameterResolvable; constructor(parameter: Parameter, value: ParameterResolvable) { diff --git a/src/SystemMessage.ts b/src/SystemMessage.ts index ec2d6de..6e2a0c4 100644 --- a/src/SystemMessage.ts +++ b/src/SystemMessage.ts @@ -2,27 +2,108 @@ import { ColorResolvable, CommandInteraction, GuildMember, Message, MessageEmbed import { Command } from "./Command.js"; import { PermissionsError } from "./errors.js"; +/** + * @type Type of message + */ export type MessageType = "PERMISSION" | "ERROR" | "NOT_FOUND" | "SUCCESS"; +/** + * @interface + * Configuration of system message + */ export interface SystemMessageAppearance { + /** + * Whether this type of message is enabled + * @type {boolean} + */ enabled: boolean; + /** + * Title field + * @type {string} + */ title: string; + /** + * Text below the title + * @type {string} + */ bottomText?: string; + /** + * Color of a message + * @type {ColorResolvable} + */ accentColor?: ColorResolvable; + /** + * Whether to display detailed informations in the message + * @type {boolean} + */ + displayDetails?: boolean; + /** + * Whether to show current time and date in a footer + * @type {boolean} + */ showTimestamp?: boolean; + /** + * Footer text + * @type {string} + */ footer?: string; + /** + * Time (in ms) after a message of this type gets deleted (set to *Infinity* to not delete the message) + * @type {number} + */ + deleteTimeout?: number; } -interface SystemMessageData { +/** + * @interface + * System message data definition + */ +export interface SystemMessageData { + /** + * A {@link Command} instance + * @type {Command} + */ command?: Command; + /** + * Phrase received from a Discord channel + * @type {string} + */ phrase?: string; + /** + * User who used the bot + * @type {GuildMember | User} + */ user?: GuildMember | User; + /** + * Error object + * @type {Error | PermissionsError | string} + */ error?: Error | PermissionsError | string; } export class SystemMessageManager { + /** + * "Insufficient permissions" message + * @type {SystemMessageAppearance} + */ PERMISSION: SystemMessageAppearance; + /** + * Error message + * @type {SystemMessageAppearance} + */ ERROR: SystemMessageAppearance; + /** + * "Command not found" message + * @type {SystemMessageAppearance} + */ NOT_FOUND: SystemMessageAppearance; + /** + * "Task completed successfully" message + * @type {SystemMessageAppearance} + */ SUCCESS: SystemMessageAppearance; + /** + * Global time (in ms) after a message gets deleted + * @type {number} + */ deleteTimeout: number; constructor(botName?: string) { @@ -31,6 +112,7 @@ export class SystemMessageManager { title: "👮‍♂️ Insufficient permissions", bottomText: "You don't have enough permissions to run this command", accentColor: "#1d1dc4", + displayDetails: true, showTimestamp: true, footer: botName, }; @@ -39,6 +121,7 @@ export class SystemMessageManager { title: "❌ An error occurred", bottomText: "Something went wrong while processing your request.", accentColor: "#ff0000", + displayDetails: true, showTimestamp: true, footer: botName, }; @@ -46,6 +129,7 @@ export class SystemMessageManager { enabled: true, title: "🔍 Command not found", accentColor: "#ff5500", + displayDetails: true, showTimestamp: true, footer: botName, }; @@ -53,18 +137,20 @@ export class SystemMessageManager { enabled: true, title: "✅ Task completed successfully", accentColor: "#00ff00", + displayDetails: true, showTimestamp: true, footer: botName, + deleteTimeout: Infinity, }; this.deleteTimeout = Infinity; } /** * Generates and sends a system message - * @param {MessageType} type - 'ERROR' | 'PERMISSION' | 'NOT_FOUND' + * @param {MessageType} type - "ERROR" | "PERMISSION" | "NOT_FOUND" | "SUCCESS" * @param {SystemMessageData} [data] - additional data to include in the message * @param {Message | CommandInteraction} [interaction] - if specified, the generated message will be sent in this channel - * @returns *Promise* + * @returns {Promise} A message that got sent or *void* */ async send(type: MessageType, data?: SystemMessageData, interaction?: Message | CommandInteraction): Promise { if (this[type]) { @@ -77,7 +163,7 @@ export class SystemMessageManager { embed.setColor(this[type].accentColor || "#000"); if (this[type].showTimestamp) embed.setTimestamp(); if (this[type].footer) embed.setFooter(this[type].footer || ""); - if (data) { + if (data && this[type].displayDetails) { switch (type) { case "ERROR": if (data.command) { @@ -124,14 +210,29 @@ export class SystemMessageManager { } if (interaction && !(interaction instanceof CommandInteraction)) { const message = await interaction.reply({ embeds: [embed] }); - if (this.deleteTimeout != Infinity && message.deletable) { - setTimeout(async () => { - await message.delete(); - }, this.deleteTimeout || 0); + if (message.deletable) { + if (Number.isFinite(this[type].deleteTimeout)) { + setTimeout(async () => { + await message.delete().catch(); + }, this[type].deleteTimeout); + } else if (this[type].deleteTimeout === undefined && Number.isFinite(this.deleteTimeout)) { + setTimeout(async () => { + await message.delete().catch(); + }, this.deleteTimeout); + } } return message; } else if (interaction && interaction instanceof CommandInteraction) { interaction.replied || interaction.deferred ? await interaction.editReply({ embeds: [embed] }) : await interaction.reply({ embeds: [embed] }); + if (Number.isFinite(this[type].deleteTimeout)) { + setTimeout(async () => { + await interaction.deleteReply().catch(); + }, this[type].deleteTimeout); + } else if (this[type].deleteTimeout === undefined && Number.isFinite(this.deleteTimeout)) { + setTimeout(async () => { + await interaction.deleteReply().catch(); + }, this.deleteTimeout); + } } else { return embed; } diff --git a/src/errors.ts b/src/errors.ts index 2e4911f..c5b5e66 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -3,6 +3,11 @@ import { GuildMember } from "discord.js"; import { Parameter } from "./Parameter.js"; import { ParameterType } from "./types.js"; +/** + * @class Error indicating that a caller doesn't have enough permissions to execute a command + * @extends Error + * @exports + */ export class PermissionsError extends Error { private command: Command; private user: GuildMember | null; @@ -16,6 +21,11 @@ export class PermissionsError extends Error { } } +/** + * @class Error indicating that an input value cannot be converted to a expected type + * @extends TypeError + * @exports + */ export class ParameterTypeError extends TypeError { private stringContent: string; private type: ParameterType; @@ -29,6 +39,11 @@ export class ParameterTypeError extends TypeError { } } +/** + * @class Error indicating that a required parameter is missing in the request + * @extends ReferenceError + * @exports + */ export class MissingParameterError extends ReferenceError { private argument: Parameter; constructor(a: Parameter) { @@ -40,6 +55,10 @@ export class MissingParameterError extends ReferenceError { } } +/** + * @class Entity indicating command execution success + * @exports + */ export class OperationSuccess { command: Command; constructor(c: Command) { diff --git a/src/types.ts b/src/types.ts index 7701236..39775e5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,28 +3,111 @@ import { InputParameter, ObjectID, ParameterSchema } from "./Parameter.js"; import { PermissionResolvable, Message, MessageEmbed } from "discord.js"; import type { Command } from "./Command.js"; +/** + * @interface + * Main object initialization options + */ export interface InitOptions { + /** + * Bot name + * @type {string} + */ name: string; + /** + * Prefix used as a way to trigger the bot using messages + * @type {string} + */ prefix?: string; + /** + * Separator used to split user input to a list of {@link InputParameter}s (applies to prefix interactions) + * @type {string} + */ parameterSeparator?: string; + /** + * Additional {@link ClientOptions} for Discord.js {@link Client} object + * @type {ClientOptions} + */ clientOptions?: ClientOptions; + /** + * Discord bot token + * @type {string} + */ token: string; + /** + * Discord API application ID + * @type {string} + */ applicationId: string; } export type GetMode = "ALL" | "PREFIX" | "NO_PREFIX"; export type PermissionCheckTypes = "ALL" | "ANY"; +/** + * @interface + * Options for building a {@link Command} object + */ export interface CommandBuilder { + /** + * Command name + * @type {string} + */ name: string; + /** + * List of parameters that can passed to this command + * @type {Array | string} List of {@link Parameter}s or template ("simple" - one string input, "no_input") + */ parameters?: ParameterSchema[] | "simple" | "no_input"; + /** + * List of different names that can be used to invoke a command (when using prefix interactions) + * @type {Array} *string* + */ aliases?: string[] | string; + /** + * Command description displayed in the help message (Default description: "No description") + * @type {string} + */ description?: string; + /** + * Command usage displayed in the help message + * @type {string} + */ usage?: string; + /** + * Whether to check if a caller has all defined in *permissions* property permissions or at least one of them (doesn't apply to custom function permissions) + * @type {PermissionCheckTypes} "ALL" | "ANY" + */ permissionCheck?: PermissionCheckTypes; + /** + * Command permissions (if *undefined*, no permissions check will be performed) + * @type {PermissionResolvable} + * @type {Function} should return boolean value + */ permissions?: PermissionResolvable | ((m?: Message | CommandInteraction) => boolean); + /** + * List of Discord guild (server) IDs in which this command can be used + * @type {Array} *string* + */ guilds?: string[]; + /** + * Whether this command is visible in the help message (default: true) + * @type {boolean} + */ visible?: boolean; + /** + * Whether this command should be registered as a slash command (default: true) + * @type {boolean} + */ slash?: boolean; + /** + * Whether to send a SUCCESS message if no other response is defined (default: true) + * @type {boolean} + */ announceSuccess?: boolean; + /** + * Command execution function (triggered when someone invokes the command) + * @type {Function} + * @param {Function} p - function to fetch input parameters' values + * @param {Message | CommandInteraction} i - interaction object + */ function: ( params: (query: string, returnType?: "value" | "object") => ParameterResolvable | InputParameter | null, interaction?: Message | CommandInteraction @@ -38,13 +121,45 @@ export interface CommandMessageStructure { command: Command; parameters: InputParameter[]; } +/** + * @interface + * Help message properties + */ export interface HelpMessageParams { + /** + * Whether help message is enabled + * @type {boolean} + */ enabled: boolean; + /** + * Title field + * @type {string} + */ title: string; + /** + * Text below the title + * @type {string} + */ bottomText: string; + /** + * Color of a message + * @type {ColorResolvable} + */ color: ColorResolvable; + /** + * Description of the "help" command + * @type {string} + */ description: string; + /** + * Usage of the "help" command + * @type {string} + */ usage: string; + /** + * Whether the "help" command should be visible in the help message + * @type {boolean} + */ visible: boolean; } export type ParameterType = "string" | "boolean" | "number" | "user" | "role" | "channel" | "mentionable"; diff --git a/tsconfig.json b/tsconfig.json index b172d1f..cb2e03d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ "allowSyntheticDefaultImports": true, "baseUrl": "src", "pretty": true, - "lib": ["esnext"] + "lib": ["esnext"], + "removeComments": false }, "exclude": ["node_modules", "dist", "test"] }