From 4473e7f404e912b385a380f83a200abd989ce79c Mon Sep 17 00:00:00 2001 From: GRZ4NA Date: Tue, 31 Aug 2021 18:21:52 +0200 Subject: [PATCH 1/9] improved code safety by adding readonly keywords --- src/Bot.ts | 12 ++++++------ src/Command.ts | 24 ++++++++++++------------ src/CommandManager.ts | 6 +++--- src/Parameter.ts | 14 +++++++------- src/SystemMessage.ts | 10 +++++----- src/errors.ts | 12 ++++++------ 6 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/Bot.ts b/src/Bot.ts index b08c061..2153a89 100644 --- a/src/Bot.ts +++ b/src/Bot.ts @@ -26,32 +26,32 @@ export class Bot extends EventEmitter { * Bot name * @type {string} */ - name: string; + public readonly name: string; /** * Discord.js {@link Client} instance * @type {Client} */ - client: Client; + public readonly client: Client; /** * Instance command manager * @type {CommandManager} */ - commands: CommandManager; + public readonly commands: CommandManager; /** * Discord bot token * @type {string} */ - token: string; + public readonly token: string; /** * Discord API application ID * @type {string} */ - applicationId: string; + public readonly applicationId: string; /** * Built-in messages configuration * @type {Object} */ - messages: { + public readonly messages: { /** * Help message configuration * @type {HelpMessageParams} diff --git a/src/Command.ts b/src/Command.ts index 83b5fac..eedc174 100644 --- a/src/Command.ts +++ b/src/Command.ts @@ -14,58 +14,58 @@ export class Command { * Command name * @type {string} */ - name: string; + public readonly name: string; /** * List of parameters that can passed to this command * @type {Array} {@link Parameter} */ - parameters: Parameter[]; + public readonly parameters: Parameter[]; /** * List of different names that can be used to invoke a command (when using prefix interactions) * @type {Array} *string* */ - aliases?: string[]; + public readonly aliases?: string[]; /** * Command description displayed in the help message (Default description: "No description") * @type {string} */ - description: string; + public readonly description: string; /** * Command usage displayed in the help message * @type {string} */ - usage?: string; + public readonly 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; + public readonly 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); + public readonly permissions?: Permissions | ((m?: Message | CommandInteraction) => boolean); /** * List of Discord guild (server) IDs in which this command can be used * @type {Array} *string* */ - guilds?: string[]; + public readonly guilds?: string[]; /** * Whether this command is visible in the help message (default: true) * @type {boolean} */ - visible: boolean; + public readonly visible: boolean; /** * Whether this command should be registered as a slash command (default: true) * @type {boolean} */ - slash: boolean; + public readonly slash: boolean; /** * Whether to send a SUCCESS message if no other response is defined (default: true) * @type {boolean} */ - announceSuccess: boolean; + public readonly announceSuccess: boolean; /** * Command execution function (triggered when someone invokes the command) * @type {Function} @@ -73,7 +73,7 @@ export class Command { * @param {Message | CommandInteraction} i - interaction object * @private */ - private function: ( + private readonly function: ( params: (query: string, returnType?: "value" | "object") => ParameterResolvable | InputParameter | null, interaction?: Message | CommandInteraction ) => void | string | MessageEmbed | ReplyMessageOptions | Promise; diff --git a/src/CommandManager.ts b/src/CommandManager.ts index 8c9ad3f..685e6ec 100644 --- a/src/CommandManager.ts +++ b/src/CommandManager.ts @@ -12,17 +12,17 @@ export class CommandManager { * List of commands registered in the manager * @type {Array} {@link Command} */ - list: Command[]; + public readonly list: Command[]; /** * Prefix used as a way to trigger the bot using messages * @type {string} */ - prefix?: string; + public readonly prefix?: string; /** * Separator used to split user input to a list of {@link InputParameter}s (applies to prefix interactions) * @type {string} */ - parameterSeparator: string; + public readonly parameterSeparator: string; /** * @constructor diff --git a/src/Parameter.ts b/src/Parameter.ts index f024b61..85ddc35 100644 --- a/src/Parameter.ts +++ b/src/Parameter.ts @@ -37,26 +37,26 @@ export class Parameter { * Parameter name * @type {string} */ - name: string; + public readonly name: string; /** * Parameter description * @type {string} */ - description: string; + public readonly description: string; /** * Whether this parameter is optional * @type {boolean} */ - optional: boolean; + public readonly optional: boolean; /** * Parameter input type * @type {ParameterType} */ - type: ParameterType; + public readonly type: ParameterType; /** * List of value choices (available only when type is set to "STRING") */ - choices?: string[]; + public readonly choices?: string[]; constructor(options: ParameterSchema) { this.name = options.name; @@ -93,7 +93,7 @@ export class InputParameter extends Parameter { * Input value * @type {ParameterResolvable} */ - value: ParameterResolvable; + public readonly value: ParameterResolvable; constructor(parameter: Parameter, value: ParameterResolvable) { super(parameter); @@ -148,7 +148,7 @@ export class ObjectParameter extends InputParameter { } } export class ObjectID { - id: string; + public readonly id: string; constructor(id: string) { this.id = id.replace(">", "").replace("<@!", "").replace("<#!", ""); } diff --git a/src/SystemMessage.ts b/src/SystemMessage.ts index 6e2a0c4..9006f2b 100644 --- a/src/SystemMessage.ts +++ b/src/SystemMessage.ts @@ -84,27 +84,27 @@ export class SystemMessageManager { * "Insufficient permissions" message * @type {SystemMessageAppearance} */ - PERMISSION: SystemMessageAppearance; + public PERMISSION: SystemMessageAppearance; /** * Error message * @type {SystemMessageAppearance} */ - ERROR: SystemMessageAppearance; + public ERROR: SystemMessageAppearance; /** * "Command not found" message * @type {SystemMessageAppearance} */ - NOT_FOUND: SystemMessageAppearance; + public NOT_FOUND: SystemMessageAppearance; /** * "Task completed successfully" message * @type {SystemMessageAppearance} */ - SUCCESS: SystemMessageAppearance; + public SUCCESS: SystemMessageAppearance; /** * Global time (in ms) after a message gets deleted * @type {number} */ - deleteTimeout: number; + public deleteTimeout: number; constructor(botName?: string) { this.PERMISSION = { diff --git a/src/errors.ts b/src/errors.ts index c5b5e66..8b7d1d0 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -9,8 +9,8 @@ import { ParameterType } from "./types.js"; * @exports */ export class PermissionsError extends Error { - private command: Command; - private user: GuildMember | null; + private readonly command: Command; + private readonly user: GuildMember | null; constructor(command: Command, user?: GuildMember | null) { super(); this.command = command; @@ -27,8 +27,8 @@ export class PermissionsError extends Error { * @exports */ export class ParameterTypeError extends TypeError { - private stringContent: string; - private type: ParameterType; + private readonly stringContent: string; + private readonly type: ParameterType; constructor(s: string, type: ParameterType) { super(); this.stringContent = s; @@ -45,7 +45,7 @@ export class ParameterTypeError extends TypeError { * @exports */ export class MissingParameterError extends ReferenceError { - private argument: Parameter; + private readonly argument: Parameter; constructor(a: Parameter) { super(); this.argument = a; @@ -60,7 +60,7 @@ export class MissingParameterError extends ReferenceError { * @exports */ export class OperationSuccess { - command: Command; + public readonly command: Command; constructor(c: Command) { this.command = c; } From f18f40a1ff0550c232efd070e30fd0c4e7304180 Mon Sep 17 00:00:00 2001 From: GRZ4NA Date: Tue, 31 Aug 2021 18:36:17 +0200 Subject: [PATCH 2/9] added command warning --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f430b82..657e94e 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,8 @@ bot.start( ); ``` +> **WARNING!** All commands have to be added to the instance **before starting the bot**. Adding commands while the bot is running is not possible and can cause issues. + # Commands ## Creating and registering a command From 6c687d5a2e6d81ff702b65975142e3db11bb0041 Mon Sep 17 00:00:00 2001 From: GRZ4NA Date: Tue, 31 Aug 2021 18:40:35 +0200 Subject: [PATCH 3/9] improving safety - freezing command object --- src/Bot.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Bot.ts b/src/Bot.ts index 2153a89..ecfbfc0 100644 --- a/src/Bot.ts +++ b/src/Bot.ts @@ -132,6 +132,7 @@ export class Bot extends EventEmitter { const helpMsg: Command = new HelpMessage(this.commands, this.messages.help, this.name); this.commands.add(helpMsg); } + Object.freeze(this.commands); process.stdout.write("Connecting to Discord... "); this.client.login(this.token); this.client.on("ready", async () => { From 40eb4b35da6261888770e8c5f60f09448696b9f2 Mon Sep 17 00:00:00 2001 From: GRZ4NA Date: Tue, 31 Aug 2021 18:47:57 +0200 Subject: [PATCH 4/9] tweaked to only freeze the list --- src/Bot.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bot.ts b/src/Bot.ts index ecfbfc0..fcd6565 100644 --- a/src/Bot.ts +++ b/src/Bot.ts @@ -132,7 +132,7 @@ export class Bot extends EventEmitter { const helpMsg: Command = new HelpMessage(this.commands, this.messages.help, this.name); this.commands.add(helpMsg); } - Object.freeze(this.commands); + Object.freeze(this.commands.list); process.stdout.write("Connecting to Discord... "); this.client.login(this.token); this.client.on("ready", async () => { From bf375336a9d5fa40acc1b9cab58a9247a63c8d8a Mon Sep 17 00:00:00 2001 From: GRZ4NA Date: Tue, 31 Aug 2021 18:54:33 +0200 Subject: [PATCH 5/9] added warning about messages customizations --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 657e94e..1e50e49 100644 --- a/README.md +++ b/README.md @@ -342,6 +342,8 @@ You can customize the help message with these properties (\* - required): - **usage\*** - _string_ - usage displayed in the message itself - **visible\*** - _boolean_ - whether to show the help command in the list +> **WARNING!** You can't customize these messages after starting the bot. Changing these properties while the bot is running will have no effect. + # Issues Since this package is created by only 1 person it may contain some bugs or behave weirdly. If you notice any problem, typo etc, please create an issue in the _Issues_ tab on GitHub. From 695f66843f9ada67df7b473b8a131ebf9c7a5e6b Mon Sep 17 00:00:00 2001 From: GRZ4NA Date: Tue, 31 Aug 2021 19:34:49 +0200 Subject: [PATCH 6/9] 3.1.1 npm --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 28225fb..1fa4715 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "commandbot", - "version": "3.1.0", + "version": "3.1.1", "description": "A framework that helps you create your own Discord bot easier.", "main": "dist/cjs/index.js", "module": "dist/mjs/index.js", From 00753477dec31c5c05bf4ba06d1897a0bfa1ea7f Mon Sep 17 00:00:00 2001 From: GRZ4NA Date: Tue, 31 Aug 2021 19:53:55 +0200 Subject: [PATCH 7/9] restricted access to CommandManager's list --- src/Bot.ts | 9 ++++++--- src/CommandManager.ts | 11 ++++++++++- src/Help.ts | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Bot.ts b/src/Bot.ts index fcd6565..f8eae3b 100644 --- a/src/Bot.ts +++ b/src/Bot.ts @@ -132,7 +132,6 @@ export class Bot extends EventEmitter { const helpMsg: Command = new HelpMessage(this.commands, this.messages.help, this.name); this.commands.add(helpMsg); } - Object.freeze(this.commands.list); process.stdout.write("Connecting to Discord... "); this.client.login(this.token); this.client.on("ready", async () => { @@ -248,11 +247,15 @@ export class Bot extends EventEmitter { process.stdout.write("Registering commands... "); await axios.put( `https://discord.com/api/v8/applications/${this.applicationId}/commands`, - this.commands.list.filter((c) => !Array.isArray(c.guilds) && c.slash).map((c) => c.toCommandObject()), + this.commands + .getList() + .filter((c) => !Array.isArray(c.guilds) && c.slash) + .map((c) => c.toCommandObject()), { headers: { Authorization: `Bot ${this.token}` } } ); const guilds: any = {}; - await this.commands.list + await this.commands + .getList() .filter((c) => Array.isArray(c.guilds) && c.guilds.length > 0) .map((c) => { c.guilds?.map(async (g) => { diff --git a/src/CommandManager.ts b/src/CommandManager.ts index 685e6ec..cf4268e 100644 --- a/src/CommandManager.ts +++ b/src/CommandManager.ts @@ -12,7 +12,7 @@ export class CommandManager { * List of commands registered in the manager * @type {Array} {@link Command} */ - public readonly list: Command[]; + private readonly list: Command[]; /** * Prefix used as a way to trigger the bot using messages * @type {string} @@ -56,6 +56,15 @@ export class CommandManager { return command; } + /** + * Get all commands registered in a manager + * @returns {Command[]} List of registered commands in a manager + */ + getList(): Command[] { + const list = this.list; + return list; + } + /** * Adds the given {@link Command} to the instance manager with initial processing * @param {Command} command - {@link Command} instance object diff --git a/src/Help.ts b/src/Help.ts index b3c666f..47faaf6 100644 --- a/src/Help.ts +++ b/src/Help.ts @@ -61,7 +61,7 @@ export class HelpMessage extends Command { helpMsg.setTitle(params.title); helpMsg.setDescription(params.bottomText); - cmdManager.list.map((c) => { + cmdManager.getList().map((c) => { 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) From 6bf657c3d284a9b3ef5df6d07c7303f93c777bda Mon Sep 17 00:00:00 2001 From: GRZ4NA Date: Tue, 31 Aug 2021 19:59:52 +0200 Subject: [PATCH 8/9] Revert "restricted access to CommandManager's list" This reverts commit 00753477dec31c5c05bf4ba06d1897a0bfa1ea7f. --- src/Bot.ts | 9 +++------ src/CommandManager.ts | 11 +---------- src/Help.ts | 2 +- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/Bot.ts b/src/Bot.ts index f8eae3b..fcd6565 100644 --- a/src/Bot.ts +++ b/src/Bot.ts @@ -132,6 +132,7 @@ export class Bot extends EventEmitter { const helpMsg: Command = new HelpMessage(this.commands, this.messages.help, this.name); this.commands.add(helpMsg); } + Object.freeze(this.commands.list); process.stdout.write("Connecting to Discord... "); this.client.login(this.token); this.client.on("ready", async () => { @@ -247,15 +248,11 @@ export class Bot extends EventEmitter { process.stdout.write("Registering commands... "); await axios.put( `https://discord.com/api/v8/applications/${this.applicationId}/commands`, - this.commands - .getList() - .filter((c) => !Array.isArray(c.guilds) && c.slash) - .map((c) => c.toCommandObject()), + this.commands.list.filter((c) => !Array.isArray(c.guilds) && c.slash).map((c) => c.toCommandObject()), { headers: { Authorization: `Bot ${this.token}` } } ); const guilds: any = {}; - await this.commands - .getList() + await this.commands.list .filter((c) => Array.isArray(c.guilds) && c.guilds.length > 0) .map((c) => { c.guilds?.map(async (g) => { diff --git a/src/CommandManager.ts b/src/CommandManager.ts index cf4268e..685e6ec 100644 --- a/src/CommandManager.ts +++ b/src/CommandManager.ts @@ -12,7 +12,7 @@ export class CommandManager { * List of commands registered in the manager * @type {Array} {@link Command} */ - private readonly list: Command[]; + public readonly list: Command[]; /** * Prefix used as a way to trigger the bot using messages * @type {string} @@ -56,15 +56,6 @@ export class CommandManager { return command; } - /** - * Get all commands registered in a manager - * @returns {Command[]} List of registered commands in a manager - */ - getList(): Command[] { - const list = this.list; - return list; - } - /** * Adds the given {@link Command} to the instance manager with initial processing * @param {Command} command - {@link Command} instance object diff --git a/src/Help.ts b/src/Help.ts index 47faaf6..b3c666f 100644 --- a/src/Help.ts +++ b/src/Help.ts @@ -61,7 +61,7 @@ export class HelpMessage extends Command { helpMsg.setTitle(params.title); helpMsg.setDescription(params.bottomText); - cmdManager.getList().map((c) => { + cmdManager.list.map((c) => { 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) From e0c5ea38201107ec46d3f1eafd8ff4cf46e78d2f Mon Sep 17 00:00:00 2001 From: GRZ4NA Date: Tue, 31 Aug 2021 20:23:14 +0200 Subject: [PATCH 9/9] Restricted access to CommandManager's list added getList() method instead of list property, added object freezing when bot is starting up --- src/Bot.ts | 10 +++++++--- src/CommandManager.ts | 18 +++++++++++++++++- src/Help.ts | 2 +- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Bot.ts b/src/Bot.ts index fcd6565..be937a1 100644 --- a/src/Bot.ts +++ b/src/Bot.ts @@ -132,7 +132,7 @@ export class Bot extends EventEmitter { const helpMsg: Command = new HelpMessage(this.commands, this.messages.help, this.name); this.commands.add(helpMsg); } - Object.freeze(this.commands.list); + this.commands.freezeList(); process.stdout.write("Connecting to Discord... "); this.client.login(this.token); this.client.on("ready", async () => { @@ -248,11 +248,15 @@ export class Bot extends EventEmitter { process.stdout.write("Registering commands... "); await axios.put( `https://discord.com/api/v8/applications/${this.applicationId}/commands`, - this.commands.list.filter((c) => !Array.isArray(c.guilds) && c.slash).map((c) => c.toCommandObject()), + this.commands + .getList() + .filter((c) => !Array.isArray(c.guilds) && c.slash) + .map((c) => c.toCommandObject()), { headers: { Authorization: `Bot ${this.token}` } } ); const guilds: any = {}; - await this.commands.list + await this.commands + .getList() .filter((c) => Array.isArray(c.guilds) && c.guilds.length > 0) .map((c) => { c.guilds?.map(async (g) => { diff --git a/src/CommandManager.ts b/src/CommandManager.ts index 685e6ec..4728e9d 100644 --- a/src/CommandManager.ts +++ b/src/CommandManager.ts @@ -12,7 +12,7 @@ export class CommandManager { * List of commands registered in the manager * @type {Array} {@link Command} */ - public readonly list: Command[]; + private readonly list: Command[]; /** * Prefix used as a way to trigger the bot using messages * @type {string} @@ -56,6 +56,15 @@ export class CommandManager { return command; } + /** + * Get list (array) of all {@link Command}s registered in a manager + * @returns {Command[]} Array of {@link Command}s registered in a manager + */ + getList(): Command[] { + const list = [...this.list]; + return list; + } + /** * Adds the given {@link Command} to the instance manager with initial processing * @param {Command} command - {@link Command} instance object @@ -203,6 +212,13 @@ export class CommandManager { return null; } + freezeList(): void { + if (!Object.isFrozen(this.list)) { + Object.freeze(this.list); + } + return; + } + 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 b3c666f..47faaf6 100644 --- a/src/Help.ts +++ b/src/Help.ts @@ -61,7 +61,7 @@ export class HelpMessage extends Command { helpMsg.setTitle(params.title); helpMsg.setDescription(params.bottomText); - cmdManager.list.map((c) => { + cmdManager.getList().map((c) => { 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)