CommandBot 3.1.0 (merging 3.1 branch)

CommandBot 3.1.0 - server-specific help message, more customization and JSDoc comments
This commit was merged in pull request #2.
This commit is contained in:
GRZANA authored and GitHub committed 2021-08-31 01:27:19 +02:00
commit 87f8511f9e
12 files changed
+509 -82

No files matched your search

+34 -13
View File
@@ -2,10 +2,23 @@
A Discord.js based framework that makes creating Discord bots with support for slash commands easy and fast. 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) - [CommandBot](#commandbot)
- [Table of contents](#table-of-contents) - [Features](#features)
- [Table of contents](#table-of-contents)
- [Instalation](#installation) - [Instalation](#installation)
- [System requirements](#system-requirements) - [System requirements](#system-requirements)
- [Creating a project](#creating-a-project) - [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) - **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) - **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 Each of these messages can be customized with these properties (\* - required):
- **title** - _string_ - title of the message
- **enabled\*** - _boolean_ - enables or disables the message
- **title\*** - _string_ - title of the message
- **bottomText** - _string_ - text below the title (also known as description) - **bottomText** - _string_ - text below the title (also known as description)
- **accentColor** - _ColorResolvable_ - embed color - **accentColor** - _ColorResolvable_ - embed color
- **displayDetails** - _boolean_ - whether to display additional informations
- **showTimestamp** - _boolean_ - whether to show date and time in the footer - **showTimestamp** - _boolean_ - whether to show date and time in the footer
- **footer** - _string_ - message 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 ## 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 You can customize the help message with these properties (\* - required):
- **title** - _string_ - title of the message
- **bottomText** - _string_ - text below the title (also known as description) - **enabled\*** - _boolean_ - enables or disables the message
- **accentColor** - _ColorResolvable_ - embed color - **title\*** - _string_ - title of the message
- **description** - _string_ - description shown in the message itself - **bottomText\*** - _string_ - text below the title (also known as description)
- **usage** - _string_ - usage displayed in the message itself - **accentColor\*** - _ColorResolvable_ - embed color
- **visible** - _boolean_ - whether to show the help command in the list - **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 # Issues
+29 -3
View File
@@ -1,8 +1,34 @@
import { Bot } from "./src/Bot.js"; import { Bot } from "./src/Bot.js";
import { Command } from "./src/Command.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 { MessageEmbed } from "discord.js";
import { ObjectID, InputParameter } from "./src/Parameter.js";
export default Bot; 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,
};
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "commandbot", "name": "commandbot",
"version": "3.0.0", "version": "3.1.0",
"description": "A framework that helps you create your own Discord bot easier.", "description": "A framework that helps you create your own Discord bot easier.",
"main": "dist/cjs/index.js", "main": "dist/cjs/index.js",
"module": "dist/mjs/index.js", "module": "dist/mjs/index.js",
+44 -12
View File
@@ -16,27 +16,57 @@ export declare interface Bot {
on(event: "ERROR", listener: (e: any) => void): this; on(event: "ERROR", listener: (e: any) => void): this;
} }
/**
* @class Class that represents your bot instance
* @extends {EventEmitter}
* @exports
*/
export class Bot extends EventEmitter { export class Bot extends EventEmitter {
/**
* Bot name
* @type {string}
*/
name: string; name: string;
/**
* Discord.js {@link Client} instance
* @type {Client}
*/
client: Client; client: Client;
/**
* Instance command manager
* @type {CommandManager}
*/
commands: CommandManager; commands: CommandManager;
/**
* Discord bot token
* @type {string}
*/
token: string; token: string;
/**
* Discord API application ID
* @type {string}
*/
applicationId: string; applicationId: string;
/**
* Built-in messages configuration
* @type {Object}
*/
messages: { messages: {
/**
* Help message configuration
* @type {HelpMessageParams}
*/
help: HelpMessageParams; help: HelpMessageParams;
/**
* {@link SystemMessageManager} storing messages' configuration
* @type {SystemMessageManager}
*/
system: SystemMessageManager; system: SystemMessageManager;
}; };
/** /**
* Bot instance initializer
* @constructor * @constructor
* @param {InitOptions} options - instance properties * @param {InitOptions} options - instance properties ({@link InitOptions})
* @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
*/ */
constructor(options: InitOptions) { constructor(options: InitOptions) {
super(); super();
@@ -80,10 +110,11 @@ export class Bot extends EventEmitter {
} }
/** /**
* @method
* Starts your Discord bot * 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 {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 * @param {boolean} [register=true] - if *true* or *undefined*, the bot will register all slash commands in Discord API
* @returns *Promise<boolean>* * @returns {Promise<boolean>} whether this operation has been completed successfully
*/ */
async start(port?: number, register?: boolean): Promise<boolean> { async start(port?: number, register?: boolean): Promise<boolean> {
try { try {
@@ -208,8 +239,9 @@ export class Bot extends EventEmitter {
} }
/** /**
* Registers commands in Discord API * @method
* @returns *Promise<void>* * Registers commands from {@link CommandManager} in Discord API
* @returns {Promise<void>}
*/ */
async register(): Promise<void> { async register(): Promise<void> {
process.stdout.write("Registering commands... "); process.stdout.write("Registering commands... ");
+62 -23
View File
@@ -5,18 +5,74 @@ import { OperationSuccess, PermissionsError } from "./errors.js";
import { DefaultParameter, InputParameter, Parameter } from "./Parameter.js"; import { DefaultParameter, InputParameter, Parameter } from "./Parameter.js";
//CLASSES //CLASSES
/**
* @class Class that represents a command instance
* @exports
*/
export class Command { export class Command {
/**
* Command name
* @type {string}
*/
name: string; name: string;
/**
* List of parameters that can passed to this command
* @type {Array} {@link Parameter}
*/
parameters: Parameter[]; parameters: Parameter[];
/**
* List of different names that can be used to invoke a command (when using prefix interactions)
* @type {Array} *string*
*/
aliases?: string[]; aliases?: string[];
/**
* Command description displayed in the help message (Default description: "No description")
* @type {string}
*/
description: string; description: string;
/**
* Command usage displayed in the help message
* @type {string}
*/
usage?: 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; 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); permissions?: Permissions | ((m?: Message | CommandInteraction) => boolean);
/**
* List of Discord guild (server) IDs in which this command can be used
* @type {Array} *string*
*/
guilds?: string[]; guilds?: string[];
/**
* Whether this command is visible in the help message (default: true)
* @type {boolean}
*/
visible: boolean; visible: boolean;
/**
* Whether this command should be registered as a slash command (default: true)
* @type {boolean}
*/
slash: boolean; slash: boolean;
/**
* Whether to send a SUCCESS message if no other response is defined (default: true)
* @type {boolean}
*/
announceSuccess: 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: ( private function: (
params: (query: string, returnType?: "value" | "object") => ParameterResolvable | InputParameter | null, params: (query: string, returnType?: "value" | "object") => ParameterResolvable | InputParameter | null,
interaction?: Message | CommandInteraction interaction?: Message | CommandInteraction
@@ -25,17 +81,7 @@ export class Command {
/** /**
* Command constructor * Command constructor
* @constructor * @constructor
* @param {CommandBuilder} options - all command properties * @param {CommandBuilder} options - {@link CommandBuilder}
* @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
*/ */
constructor(options: CommandBuilder) { constructor(options: CommandBuilder) {
this.name = options.name.split(" ").join("_"); this.name = options.name.split(" ").join("_");
@@ -65,10 +111,10 @@ export class Command {
} }
/** /**
* Starts the command * Invokes 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 {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 message * @param {InputParameter[]} [cmdParams] - list of processed parameters passed in a Discord interaction
* @returns *Promise<void>* * @returns {Promise<void>}
*/ */
async start(interaction?: Message | CommandInteraction, cmdParams?: InputParameter[]): Promise<void> { async start(interaction?: Message | CommandInteraction, cmdParams?: InputParameter[]): Promise<void> {
const paramFindFn = function (query: string, returnType?: "value" | "object") { 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 * @returns {Object} object
*/ */
toCommandObject() { toCommandObject() {
@@ -163,10 +209,6 @@ export class Command {
}; };
} }
/**
* @param {string|string[]} phrase?
* @returns string
*/
private static processPhrase(phrase?: string | string[]): string[] | undefined { private static processPhrase(phrase?: string | string[]): string[] | undefined {
if (Array.isArray(phrase)) { if (Array.isArray(phrase)) {
const buff = phrase.map((p) => { const buff = phrase.map((p) => {
@@ -187,9 +229,6 @@ export class Command {
} }
} }
/**
* @returns string
*/
private generateUsageFromArguments(): string { private generateUsageFromArguments(): string {
let usageTemplate: string = ""; let usageTemplate: string = "";
this.parameters && this.parameters &&
+28 -16
View File
@@ -4,14 +4,30 @@ import { BooleanParameter, InputParameter, NumberParameter, ObjectParameter, Str
import { Command } from "./Command.js"; import { Command } from "./Command.js";
import { CommandMessageStructure, PhraseOccurrenceData } from "./types.js"; import { CommandMessageStructure, PhraseOccurrenceData } from "./types.js";
/**
* @class Command manager
*/
export class CommandManager { export class CommandManager {
/**
* List of commands registered in the manager
* @type {Array} {@link Command}
*/
list: Command[]; list: Command[];
/**
* Prefix used as a way to trigger the bot using messages
* @type {string}
*/
prefix?: string; prefix?: string;
/**
* Separator used to split user input to a list of {@link InputParameter}s (applies to prefix interactions)
* @type {string}
*/
parameterSeparator: string; parameterSeparator: string;
/** /**
* @param {string} prefix? * @constructor
* @param {string} parameterSeparator? * @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) { constructor(prefix?: string, parameterSeparator?: string) {
this.list = []; this.list = [];
@@ -22,7 +38,7 @@ export class CommandManager {
/** /**
* Retrieves the command by name, alias or keyword * Retrieves the command by name, alias or keyword
* @param {string} phrase - command 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 { get(phrase: string): Command | null {
let command: Command | null = null; let command: Command | null = null;
@@ -41,9 +57,9 @@ export class CommandManager {
} }
/** /**
* Adds the given command to the instance manager * Adds the given {@link Command} to the instance manager with initial processing
* @param {Command} command - *Command* or *HelpMessage* object * @param {Command} command - {@link Command} instance object
* @returns *boolean* * @returns {boolean} Whether this command has been added successfully
*/ */
add(command: Command): boolean { add(command: Command): boolean {
try { try {
@@ -79,9 +95,9 @@ export class CommandManager {
} }
/** /**
* Fetches command and parameters from the given *Message* object * Fetches {@link Command} and list of {@link InputParameter}s from the given {@link Message} object
* @param {Message} message - *Message* object * @param {Message} message - {@link Message} object
* @returns *CommandMessagesStructure* | *null* * @returns {CommandMessagesStructure | null} {@link CommandMessageStructure} or *null*
*/ */
fetchFromMessage(message: Message): CommandMessageStructure | null { fetchFromMessage(message: Message): CommandMessageStructure | null {
if (!this.prefix) return null; if (!this.prefix) return null;
@@ -138,9 +154,9 @@ export class CommandManager {
} }
/** /**
* Fetches command and parameters from the given *CommandInteraction* object * Fetches command and parameters from the given {@link CommandInteraction} object
* @param {CommandInteraction} interaction - interaction object * @param {CommandInteraction} interaction - {@link CommandInteraction} object
* @returns *CommandMessagesStructure* | *null* * @returns {CommandMessagesStructure | null} {@link CommandMessageStructure} or *null*
*/ */
fetchFromInteraction(interaction: CommandInteraction): CommandMessageStructure | null { fetchFromInteraction(interaction: CommandInteraction): CommandMessageStructure | null {
const cmd = this.get(interaction.commandName); const cmd = this.get(interaction.commandName);
@@ -187,10 +203,6 @@ export class CommandManager {
return null; return null;
} }
/**
* @param {string} phrase?
* @returns PhraseOccurrenceData
*/
private findPhraseOccurrence(phrase?: string): PhraseOccurrenceData | null { private findPhraseOccurrence(phrase?: string): PhraseOccurrenceData | null {
let returnValue: PhraseOccurrenceData | null = null; let returnValue: PhraseOccurrenceData | null = null;
this.list.map((c) => { this.list.map((c) => {
+13 -5
View File
@@ -19,15 +19,18 @@ export class HelpMessage extends Command {
type: "string", type: "string",
}, },
], ],
function: (param, _) => { function: (p, i) => {
const helpMsg = new MessageEmbed(); const helpMsg = new MessageEmbed();
helpMsg.setColor(params.color); helpMsg.setColor(params.color);
helpMsg.setTimestamp(); helpMsg.setTimestamp();
helpMsg.setFooter(botName || ""); helpMsg.setFooter(botName || "");
if (helpMsg != null) { if (helpMsg != null) {
if (param("command_name")) { if (p("command_name")) {
const cmd: Command | null = cmdManager.get(param("command_name")?.toString() || ""); const cmd: Command | null = cmdManager.get(p("command_name")?.toString() || "");
if (cmd) { 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.setTitle(`${cmd.name} ${cmd.visible ? "" : "[HIDDEN]"}`);
helpMsg.setDescription(cmd.description); helpMsg.setDescription(cmd.description);
if (cmd.usage) helpMsg.addField("Usage:", `${cmdManager.prefix || "/"}${cmd.name} ${cmd.usage}`, false); 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("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 { } else {
throw new ReferenceError(`Command "${param("command_name")}" does not exist`); throw new ReferenceError(`Command "${p("command_name")}" does not exist`);
} }
} else { } else {
helpMsg.setTitle(params.title); helpMsg.setTitle(params.title);
helpMsg.setDescription(params.bottomText); helpMsg.setDescription(params.bottomText);
cmdManager.list.map((c) => { 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); helpMsg.addField(`${cmdManager.prefix || "/"}${c.name} ${c.usage}`, c.description, false);
} }
}); });
+53
View File
@@ -1,17 +1,61 @@
import { CategoryChannel, Guild, GuildMember, NewsChannel, Role, StageChannel, StoreChannel, TextChannel, VoiceChannel } from "discord.js"; import { CategoryChannel, Guild, GuildMember, NewsChannel, Role, StageChannel, StoreChannel, TextChannel, VoiceChannel } from "discord.js";
import { ParameterType, ParameterResolvable } from "./types.js"; import { ParameterType, ParameterResolvable } from "./types.js";
/**
* @interface
* Properties required to build a {@link Parameter} object
* @exports
*/
export interface ParameterSchema { export interface ParameterSchema {
/**
* Parameter name
* @type {string}
*/
name: string; name: string;
/**
* Parameter description
* @type {string}
*/
description?: string; description?: string;
/**
* Whether this parameter is optional
* @type {boolean}
*/
optional: boolean; optional: boolean;
type: ParameterType; type: ParameterType;
/**
* List of value choices (available only when type is set to "STRING")
*/
choices?: string[]; choices?: string[];
} }
/**
* @class Representation of command parameter
* @exports
*/
export class Parameter { export class Parameter {
/**
* Parameter name
* @type {string}
*/
name: string; name: string;
/**
* Parameter description
* @type {string}
*/
description: string; description: string;
/**
* Whether this parameter is optional
* @type {boolean}
*/
optional: boolean; optional: boolean;
/**
* Parameter input type
* @type {ParameterType}
*/
type: ParameterType; type: ParameterType;
/**
* List of value choices (available only when type is set to "STRING")
*/
choices?: string[]; choices?: string[];
constructor(options: ParameterSchema) { 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 { export class InputParameter extends Parameter {
/**
* Input value
* @type {ParameterResolvable}
*/
value: ParameterResolvable; value: ParameterResolvable;
constructor(parameter: Parameter, value: ParameterResolvable) { constructor(parameter: Parameter, value: ParameterResolvable) {
+109 -8
View File
@@ -2,27 +2,108 @@ import { ColorResolvable, CommandInteraction, GuildMember, Message, MessageEmbed
import { Command } from "./Command.js"; import { Command } from "./Command.js";
import { PermissionsError } from "./errors.js"; import { PermissionsError } from "./errors.js";
/**
* @type Type of message
*/
export type MessageType = "PERMISSION" | "ERROR" | "NOT_FOUND" | "SUCCESS"; export type MessageType = "PERMISSION" | "ERROR" | "NOT_FOUND" | "SUCCESS";
/**
* @interface
* Configuration of system message
*/
export interface SystemMessageAppearance { export interface SystemMessageAppearance {
/**
* Whether this type of message is enabled
* @type {boolean}
*/
enabled: boolean; enabled: boolean;
/**
* Title field
* @type {string}
*/
title: string; title: string;
/**
* Text below the title
* @type {string}
*/
bottomText?: string; bottomText?: string;
/**
* Color of a message
* @type {ColorResolvable}
*/
accentColor?: 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; showTimestamp?: boolean;
/**
* Footer text
* @type {string}
*/
footer?: 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; command?: Command;
/**
* Phrase received from a Discord channel
* @type {string}
*/
phrase?: string; phrase?: string;
/**
* User who used the bot
* @type {GuildMember | User}
*/
user?: GuildMember | User; user?: GuildMember | User;
/**
* Error object
* @type {Error | PermissionsError | string}
*/
error?: Error | PermissionsError | string; error?: Error | PermissionsError | string;
} }
export class SystemMessageManager { export class SystemMessageManager {
/**
* "Insufficient permissions" message
* @type {SystemMessageAppearance}
*/
PERMISSION: SystemMessageAppearance; PERMISSION: SystemMessageAppearance;
/**
* Error message
* @type {SystemMessageAppearance}
*/
ERROR: SystemMessageAppearance; ERROR: SystemMessageAppearance;
/**
* "Command not found" message
* @type {SystemMessageAppearance}
*/
NOT_FOUND: SystemMessageAppearance; NOT_FOUND: SystemMessageAppearance;
/**
* "Task completed successfully" message
* @type {SystemMessageAppearance}
*/
SUCCESS: SystemMessageAppearance; SUCCESS: SystemMessageAppearance;
/**
* Global time (in ms) after a message gets deleted
* @type {number}
*/
deleteTimeout: number; deleteTimeout: number;
constructor(botName?: string) { constructor(botName?: string) {
@@ -31,6 +112,7 @@ export class SystemMessageManager {
title: "👮‍♂️ Insufficient permissions", title: "👮‍♂️ Insufficient permissions",
bottomText: "You don't have enough permissions to run this command", bottomText: "You don't have enough permissions to run this command",
accentColor: "#1d1dc4", accentColor: "#1d1dc4",
displayDetails: true,
showTimestamp: true, showTimestamp: true,
footer: botName, footer: botName,
}; };
@@ -39,6 +121,7 @@ export class SystemMessageManager {
title: "❌ An error occurred", title: "❌ An error occurred",
bottomText: "Something went wrong while processing your request.", bottomText: "Something went wrong while processing your request.",
accentColor: "#ff0000", accentColor: "#ff0000",
displayDetails: true,
showTimestamp: true, showTimestamp: true,
footer: botName, footer: botName,
}; };
@@ -46,6 +129,7 @@ export class SystemMessageManager {
enabled: true, enabled: true,
title: "🔍 Command not found", title: "🔍 Command not found",
accentColor: "#ff5500", accentColor: "#ff5500",
displayDetails: true,
showTimestamp: true, showTimestamp: true,
footer: botName, footer: botName,
}; };
@@ -53,18 +137,20 @@ export class SystemMessageManager {
enabled: true, enabled: true,
title: "✅ Task completed successfully", title: "✅ Task completed successfully",
accentColor: "#00ff00", accentColor: "#00ff00",
displayDetails: true,
showTimestamp: true, showTimestamp: true,
footer: botName, footer: botName,
deleteTimeout: Infinity,
}; };
this.deleteTimeout = Infinity; this.deleteTimeout = Infinity;
} }
/** /**
* Generates and sends a system message * 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 {SystemMessageData} [data] - additional data to include in the message
* @param {Message | CommandInteraction} [interaction] - if specified, the generated message will be sent in this channel * @param {Message | CommandInteraction} [interaction] - if specified, the generated message will be sent in this channel
* @returns *Promise<MessageEmbed | Message | void>* * @returns {Promise<MessageEmbed | Message | void>} A message that got sent or *void*
*/ */
async send(type: MessageType, data?: SystemMessageData, interaction?: Message | CommandInteraction): Promise<MessageEmbed | Message | void> { async send(type: MessageType, data?: SystemMessageData, interaction?: Message | CommandInteraction): Promise<MessageEmbed | Message | void> {
if (this[type]) { if (this[type]) {
@@ -77,7 +163,7 @@ export class SystemMessageManager {
embed.setColor(this[type].accentColor || "#000"); embed.setColor(this[type].accentColor || "#000");
if (this[type].showTimestamp) embed.setTimestamp(); if (this[type].showTimestamp) embed.setTimestamp();
if (this[type].footer) embed.setFooter(this[type].footer || ""); if (this[type].footer) embed.setFooter(this[type].footer || "");
if (data) { if (data && this[type].displayDetails) {
switch (type) { switch (type) {
case "ERROR": case "ERROR":
if (data.command) { if (data.command) {
@@ -124,14 +210,29 @@ export class SystemMessageManager {
} }
if (interaction && !(interaction instanceof CommandInteraction)) { if (interaction && !(interaction instanceof CommandInteraction)) {
const message = await interaction.reply({ embeds: [embed] }); const message = await interaction.reply({ embeds: [embed] });
if (this.deleteTimeout != Infinity && message.deletable) { if (message.deletable) {
setTimeout(async () => { if (Number.isFinite(this[type].deleteTimeout)) {
await message.delete(); setTimeout(async () => {
}, this.deleteTimeout || 0); 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; return message;
} else if (interaction && interaction instanceof CommandInteraction) { } else if (interaction && interaction instanceof CommandInteraction) {
interaction.replied || interaction.deferred ? await interaction.editReply({ embeds: [embed] }) : await interaction.reply({ embeds: [embed] }); 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 { } else {
return embed; return embed;
} }
+19
View File
@@ -3,6 +3,11 @@ import { GuildMember } from "discord.js";
import { Parameter } from "./Parameter.js"; import { Parameter } from "./Parameter.js";
import { ParameterType } from "./types.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 { export class PermissionsError extends Error {
private command: Command; private command: Command;
private user: GuildMember | null; 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 { export class ParameterTypeError extends TypeError {
private stringContent: string; private stringContent: string;
private type: ParameterType; 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 { export class MissingParameterError extends ReferenceError {
private argument: Parameter; private argument: Parameter;
constructor(a: Parameter) { constructor(a: Parameter) {
@@ -40,6 +55,10 @@ export class MissingParameterError extends ReferenceError {
} }
} }
/**
* @class Entity indicating command execution success
* @exports
*/
export class OperationSuccess { export class OperationSuccess {
command: Command; command: Command;
constructor(c: Command) { constructor(c: Command) {
+115
View File
@@ -3,28 +3,111 @@ import { InputParameter, ObjectID, ParameterSchema } from "./Parameter.js";
import { PermissionResolvable, Message, MessageEmbed } from "discord.js"; import { PermissionResolvable, Message, MessageEmbed } from "discord.js";
import type { Command } from "./Command.js"; import type { Command } from "./Command.js";
/**
* @interface
* Main object initialization options
*/
export interface InitOptions { export interface InitOptions {
/**
* Bot name
* @type {string}
*/
name: string; name: string;
/**
* Prefix used as a way to trigger the bot using messages
* @type {string}
*/
prefix?: string; prefix?: string;
/**
* Separator used to split user input to a list of {@link InputParameter}s (applies to prefix interactions)
* @type {string}
*/
parameterSeparator?: string; parameterSeparator?: string;
/**
* Additional {@link ClientOptions} for Discord.js {@link Client} object
* @type {ClientOptions}
*/
clientOptions?: ClientOptions; clientOptions?: ClientOptions;
/**
* Discord bot token
* @type {string}
*/
token: string; token: string;
/**
* Discord API application ID
* @type {string}
*/
applicationId: string; applicationId: string;
} }
export type GetMode = "ALL" | "PREFIX" | "NO_PREFIX"; export type GetMode = "ALL" | "PREFIX" | "NO_PREFIX";
export type PermissionCheckTypes = "ALL" | "ANY"; export type PermissionCheckTypes = "ALL" | "ANY";
/**
* @interface
* Options for building a {@link Command} object
*/
export interface CommandBuilder { export interface CommandBuilder {
/**
* Command name
* @type {string}
*/
name: 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"; 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; aliases?: string[] | string;
/**
* Command description displayed in the help message (Default description: "No description")
* @type {string}
*/
description?: string; description?: string;
/**
* Command usage displayed in the help message
* @type {string}
*/
usage?: 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; 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); permissions?: PermissionResolvable | ((m?: Message | CommandInteraction) => boolean);
/**
* List of Discord guild (server) IDs in which this command can be used
* @type {Array} *string*
*/
guilds?: string[]; guilds?: string[];
/**
* Whether this command is visible in the help message (default: true)
* @type {boolean}
*/
visible?: boolean; visible?: boolean;
/**
* Whether this command should be registered as a slash command (default: true)
* @type {boolean}
*/
slash?: boolean; slash?: boolean;
/**
* Whether to send a SUCCESS message if no other response is defined (default: true)
* @type {boolean}
*/
announceSuccess?: 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: ( function: (
params: (query: string, returnType?: "value" | "object") => ParameterResolvable | InputParameter | null, params: (query: string, returnType?: "value" | "object") => ParameterResolvable | InputParameter | null,
interaction?: Message | CommandInteraction interaction?: Message | CommandInteraction
@@ -38,13 +121,45 @@ export interface CommandMessageStructure {
command: Command; command: Command;
parameters: InputParameter[]; parameters: InputParameter[];
} }
/**
* @interface
* Help message properties
*/
export interface HelpMessageParams { export interface HelpMessageParams {
/**
* Whether help message is enabled
* @type {boolean}
*/
enabled: boolean; enabled: boolean;
/**
* Title field
* @type {string}
*/
title: string; title: string;
/**
* Text below the title
* @type {string}
*/
bottomText: string; bottomText: string;
/**
* Color of a message
* @type {ColorResolvable}
*/
color: ColorResolvable; color: ColorResolvable;
/**
* Description of the "help" command
* @type {string}
*/
description: string; description: string;
/**
* Usage of the "help" command
* @type {string}
*/
usage: string; usage: string;
/**
* Whether the "help" command should be visible in the help message
* @type {boolean}
*/
visible: boolean; visible: boolean;
} }
export type ParameterType = "string" | "boolean" | "number" | "user" | "role" | "channel" | "mentionable"; export type ParameterType = "string" | "boolean" | "number" | "user" | "role" | "channel" | "mentionable";
+2 -1
View File
@@ -7,7 +7,8 @@
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"baseUrl": "src", "baseUrl": "src",
"pretty": true, "pretty": true,
"lib": ["esnext"] "lib": ["esnext"],
"removeComments": false
}, },
"exclude": ["node_modules", "dist", "test"] "exclude": ["node_modules", "dist", "test"]
} }