From 0362c8145d2cd20f7722527b0679a7045571176f Mon Sep 17 00:00:00 2001 From: Shino Date: Mon, 12 Jul 2021 20:07:10 -0400 Subject: [PATCH 01/18] refactor(CommandInteraction): create CommandInteractionOptionResolver --- src/structures/CommandInteraction.js | 24 +-- .../CommandInteractionOptionResolver.js | 160 ++++++++++++++++++ typings/index.d.ts | 64 ++++++- 3 files changed, 236 insertions(+), 12 deletions(-) create mode 100644 src/structures/CommandInteractionOptionResolver.js diff --git a/src/structures/CommandInteraction.js b/src/structures/CommandInteraction.js index f2504a2959b8..ad36707f5e49 100644 --- a/src/structures/CommandInteraction.js +++ b/src/structures/CommandInteraction.js @@ -1,9 +1,9 @@ 'use strict'; +const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver'); const Interaction = require('./Interaction'); const InteractionWebhook = require('./InteractionWebhook'); const InteractionResponses = require('./interfaces/InteractionResponses'); -const Collection = require('../util/Collection'); const { ApplicationCommandOptionTypes } = require('../util/Constants'); /** @@ -48,9 +48,11 @@ class CommandInteraction extends Interaction { /** * The options passed to the command. - * @type {Collection} + * @type {CommandInteractionOptionResolver} */ - this.options = this._createOptionsCollection(data.data.options, data.data.resolved); + this.options = new CommandInteractionOptionResolver( + this._createOptionsArray(data.data.options, data.data.resolved), + ); /** * Whether this interaction has already been replied to @@ -108,7 +110,7 @@ class CommandInteraction extends Interaction { }; if ('value' in option) result.value = option.value; - if ('options' in option) result.options = this._createOptionsCollection(option.options, resolved); + if ('options' in option) result.options = this._createOptionsArray(option.options, resolved); if (resolved) { const user = resolved.users?.[option.value]; @@ -128,19 +130,19 @@ class CommandInteraction extends Interaction { } /** - * Creates a collection of options from the received options array. + * Creates an array of options from the received API options array. * @param {APIApplicationCommandOption[]} options The received options * @param {APIApplicationCommandOptionResolved} resolved The resolved interaction data - * @returns {Collection} + * @returns {CommandInteractionOption[]} * @private */ - _createOptionsCollection(options, resolved) { - const optionsCollection = new Collection(); - if (typeof options === 'undefined') return optionsCollection; + _createOptionsArray(options, resolved) { + const optionsArray = []; + if (typeof options === 'undefined') return optionsArray; for (const option of options) { - optionsCollection.set(option.name, this.transformOption(option, resolved)); + optionsArray.push(this.transformOption(option, resolved)); } - return optionsCollection; + return optionsArray; } // These are here only for documentation purposes - they are implemented by InteractionResponses diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js new file mode 100644 index 000000000000..f8115ef4784b --- /dev/null +++ b/src/structures/CommandInteractionOptionResolver.js @@ -0,0 +1,160 @@ +'use strict'; + +/** + * A resolver for command interaction options. + */ +class CommandInteractionOptionResolver { + constructor(options) { + /** + * The raw interaction options array. + * @type {CommandInteractionOption[]} + * @private + */ + this.options = options ?? []; + } + + /** + * Gets an option by its name. + * @param {string} name The name of the option. + * @param {boolean} required Whether to throw an error if the option is not found. + * @returns {?CommandInteractionOption} The option, if found. + */ + get(name, required = false) { + const option = this.options.find(opt => opt.name === name); + if (option) { + return option; + } else if (required) { + throw new Error(`Missing required option "${name}"`); + } else { + return null; + } + } + + /** + * Gets an option by name and property and checks its type. + * @param {string} name The name of the option. + * @param {ApplicationCommandOptionType[]} types The type of the option. + * @param {string[]} properties The property to check for for `required`. + * @param {boolean} required Whether to throw an error if the option is not found. + * @returns {?|null} The value of `property`. + * @private + */ + _getTypedOption(name, types, properties, required) { + const option = this.get(name, required); + if (option) { + if (!types.includes(option.type)) { + throw new Error(`Option "${name}" is of type "${option.type}" but expected: ${types.join('|')}.`); + } else if (required && properties.every(prop => option[prop] === null)) { + throw new Error(`Option "${name}" of type "${option.type}" is marked as required, but is null.`); + } else { + return option; + } + } else if (required) { + throw new Error(`Missing required option "${name}"`); + } else { + return option; + } + } + + /** + * Gets a sub-command or sub-command group. + * @param {string} name The name of the sub-command or sub-command group. + * @returns {CommandInteractionOptionResolver|null} + */ + getSubCommand(name) { + const option = this._getTypedOption(name, ['SUB_COMMAND', 'SUB_COMMAND_GROUP'], ['options'], false); + return option ? new CommandInteractionOptionResolver(option.options) : null; + } + + /** + * Gets a boolean option. + * @param {string} name The name of the option. + * @param {boolean} required Whether to throw an error if the option is not found. + * @returns {boolean|null} The value of the option, or null if not set and not required. + */ + getBoolean(name, required = false) { + const option = this._getTypedOption(name, ['BOOLEAN'], ['value'], required); + return option?.value ?? null; + } + + /** + * Gets a channel option. + * @param {string} name The name of the option. + * @param {boolean} required Whether to throw an error if the option is not found. + * @returns {GuildChannel|APIInteractionDataResolvedChannel|null} + * The value of the option, or null if not set and not required. + */ + getChannel(name, required = false) { + const option = this._getTypedOption(name, ['CHANNEL'], ['channel'], required); + return option?.channel ?? null; + } + + /** + * Gets a string option. + * @param {string} name The name of the option. + * @param {boolean} required Whether to throw an error if the option is not found. + * @returns {string|null} The value of the option, or null if not set and not required. + */ + getString(name, required = false) { + const option = this._getTypedOption(name, ['STRING'], ['value'], required); + return option?.value ?? null; + } + + /** + * Gets a integer option. + * @param {string} name The name of the option. + * @param {boolean} required Whether to throw an error if the option is not found. + * @returns {number|null} The value of the option, or null if not set and not required. + */ + getInteger(name, required = false) { + const option = this._getTypedOption(name, ['INTEGER'], ['value'], required); + return option?.value ?? null; + } + + /** + * Gets a user option. + * @param {string} name The name of the option. + * @param {boolean} required Whether to throw an error if the option is not found. + * @returns {User|null} The value of the option, or null if not set and not required. + */ + getUser(name, required = false) { + const option = this._getTypedOption(name, ['USER'], ['user'], required); + return option?.user ?? null; + } + + /** + * Gets a member option. + * @param {string} name The name of the option. + * @param {boolean} required Whether to throw an error if the option is not found. + * @returns {GuildMember|APIInteractionDataResolvedGuildMember|null} + * The value of the option, or null if not set and not required. + */ + getMember(name, required = false) { + const option = this._getTypedOption(name, ['MEMBER'], ['member'], required); + return option?.member ?? null; + } + + /** + * Gets a role option. + * @param {string} name The name of the option. + * @param {boolean} required Whether to throw an error if the option is not found. + * @returns {Role|APIRole|null} The value of the option, or null if not set and not required. + */ + getRole(name, required = false) { + const option = this._getTypedOption(name, ['ROLE'], ['role'], required); + return option?.role ?? null; + } + + /** + * Gets a mentionable option. + * @param {string} name The name of the option. + * @param {boolean} required Whether to throw an error if the option is not found. + * @returns {User|GuildMember|Role|null} The value of the option, or null if not set and not required. + */ + getMentionable(name, required = false) { + const option = this._getTypedOption(name, ['MENTIONABLE'], ['user', 'member', 'role'], required); + return option?.user ?? option?.member ?? option?.role ?? null; + } +} + +module.exports = CommandInteractionOptionResolver; diff --git a/typings/index.d.ts b/typings/index.d.ts index 4f78a786d27a..0854d1adf64d 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -416,7 +416,7 @@ export class CommandInteraction extends Interaction { public commandName: string; public deferred: boolean; public ephemeral: boolean | null; - public options: Collection; + public options: CommandInteractionOptionResolver; public replied: boolean; public webhook: InteractionWebhook; public defer(options?: InteractionDeferOptions & { fetchReply: true }): Promise; @@ -431,6 +431,68 @@ export class CommandInteraction extends Interaction { private _createOptionsCollection(options: unknown, resolved: unknown): Collection; } +export class CommandInteractionOptionResolver { + private options: CommandInteractionOption[]; + + constructor(options: CommandInteractionOption[]); + + public get(name: string, required: true): CommandInteractionOption; + public get(name: string, required: boolean): CommandInteractionOption | null; + + private _getTypedOption( + name: string, + types: string[], + properties: string[], + required: true, + ): CommandInteractionOption; + private _getTypedOption( + name: string, + types: string[], + properties: string[], + required: boolean, + ): CommandInteractionOption | null; + + public getSubCommand(name: string): CommandInteractionOptionResolver | null; + + public getBoolean(name: string, required: true): boolean; + public getBoolean(name: string, required?: boolean): boolean | null; + + public getChannel(name: string, required: true): NonNullable; + public getChannel(name: string, required?: boolean): NonNullable | null; + + public getString(name: string, required: true): string; + public getString(name: string, required?: boolean): string | null; + + public getInteger(name: string, required: true): number; + public getInteger(name: string, required?: boolean): number | null; + + public getUser(name: string, required: true): NonNullable; + public getUser(name: string, required?: boolean): NonNullable | null; + + public getMember(name: string, required: true): NonNullable; + public getMember(name: string, required?: boolean): NonNullable | null; + + public getRole(name: string, required: true): NonNullable; + public getRole(name: string, required?: boolean): NonNullable | null; + + public getMentionable( + name: string, + required: true, + ): + | NonNullable + | NonNullable + | NonNullable; + + public getMentionable( + name: string, + required?: boolean, + ): + | NonNullable + | NonNullable + | NonNullable + | null; +} + export class DataResolver extends null { private constructor(); public static resolveBase64(data: Base64Resolvable): string; From 6335e8a3412d7a837968bf3fc1a6218cabbf8cb1 Mon Sep 17 00:00:00 2001 From: Shino Date: Mon, 12 Jul 2021 23:53:41 -0400 Subject: [PATCH 02/18] fix: fix a typo and logic error --- src/structures/CommandInteractionOptionResolver.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js index f8115ef4784b..4a10c9a12a6c 100644 --- a/src/structures/CommandInteractionOptionResolver.js +++ b/src/structures/CommandInteractionOptionResolver.js @@ -6,7 +6,7 @@ class CommandInteractionOptionResolver { constructor(options) { /** - * The raw interaction options array. + * The interaction options array. * @type {CommandInteractionOption[]} * @private */ @@ -36,7 +36,7 @@ class CommandInteractionOptionResolver { * @param {ApplicationCommandOptionType[]} types The type of the option. * @param {string[]} properties The property to check for for `required`. * @param {boolean} required Whether to throw an error if the option is not found. - * @returns {?|null} The value of `property`. + * @returns {*|null} The value of `property`. * @private */ _getTypedOption(name, types, properties, required) { @@ -44,7 +44,7 @@ class CommandInteractionOptionResolver { if (option) { if (!types.includes(option.type)) { throw new Error(`Option "${name}" is of type "${option.type}" but expected: ${types.join('|')}.`); - } else if (required && properties.every(prop => option[prop] === null)) { + } else if (required && properties.every(prop => option[prop] === null || typeof option[prop] === 'undefined')) { throw new Error(`Option "${name}" of type "${option.type}" is marked as required, but is null.`); } else { return option; @@ -101,7 +101,7 @@ class CommandInteractionOptionResolver { } /** - * Gets a integer option. + * Gets an integer option. * @param {string} name The name of the option. * @param {boolean} required Whether to throw an error if the option is not found. * @returns {number|null} The value of the option, or null if not set and not required. From 5bb6b2be39fad3cd7ac621840b853b306b278e72 Mon Sep 17 00:00:00 2001 From: Shino Date: Mon, 12 Jul 2021 23:55:44 -0400 Subject: [PATCH 03/18] fix: remove duplicated code and fix error --- src/structures/CommandInteractionOptionResolver.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js index 4a10c9a12a6c..892fe25feaf0 100644 --- a/src/structures/CommandInteractionOptionResolver.js +++ b/src/structures/CommandInteractionOptionResolver.js @@ -43,14 +43,12 @@ class CommandInteractionOptionResolver { const option = this.get(name, required); if (option) { if (!types.includes(option.type)) { - throw new Error(`Option "${name}" is of type "${option.type}" but expected: ${types.join('|')}.`); + throw new Error(`Option "${name}" is of type "${option.type}"; expected ${types.join('|')}.`); } else if (required && properties.every(prop => option[prop] === null || typeof option[prop] === 'undefined')) { - throw new Error(`Option "${name}" of type "${option.type}" is marked as required, but is null.`); + throw new Error(`Option "${name}" of type "${option.type}" is required, but is empty.`); } else { return option; } - } else if (required) { - throw new Error(`Missing required option "${name}"`); } else { return option; } From a08751aa7271bd9407ff301018bc8de1fc6f2333 Mon Sep 17 00:00:00 2001 From: Shino Date: Mon, 12 Jul 2021 23:59:12 -0400 Subject: [PATCH 04/18] types: add private method arguments --- typings/index.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/typings/index.d.ts b/typings/index.d.ts index 0854d1adf64d..192cbe010aa4 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -441,14 +441,14 @@ export class CommandInteractionOptionResolver { private _getTypedOption( name: string, - types: string[], - properties: string[], + types: ApplicationCommandOptionType[], + properties: (keyof ApplicationCommandOption)[], required: true, ): CommandInteractionOption; private _getTypedOption( name: string, - types: string[], - properties: string[], + types: ApplicationCommandOptionType[], + properties: (keyof ApplicationCommandOption)[], required: boolean, ): CommandInteractionOption | null; From 804a35866b2d043b6ae6121bd378e19e709ddacf Mon Sep 17 00:00:00 2001 From: Shino Date: Tue, 13 Jul 2021 00:04:51 -0400 Subject: [PATCH 05/18] docs: add jsdoc for getSubCommand return value --- src/structures/CommandInteractionOptionResolver.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js index 892fe25feaf0..d442523981f8 100644 --- a/src/structures/CommandInteractionOptionResolver.js +++ b/src/structures/CommandInteractionOptionResolver.js @@ -58,6 +58,7 @@ class CommandInteractionOptionResolver { * Gets a sub-command or sub-command group. * @param {string} name The name of the sub-command or sub-command group. * @returns {CommandInteractionOptionResolver|null} + * A new resolver for the sub-command/group's options, or null if empty */ getSubCommand(name) { const option = this._getTypedOption(name, ['SUB_COMMAND', 'SUB_COMMAND_GROUP'], ['options'], false); From 4f73d71a5a0105fa2c445b829d0ce69c6b73dcc9 Mon Sep 17 00:00:00 2001 From: Shino Date: Tue, 13 Jul 2021 04:12:43 -0400 Subject: [PATCH 06/18] style: fix typings whitespace --- typings/index.d.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/typings/index.d.ts b/typings/index.d.ts index 192cbe010aa4..516cdf8a90fa 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -432,13 +432,11 @@ export class CommandInteraction extends Interaction { } export class CommandInteractionOptionResolver { - private options: CommandInteractionOption[]; - constructor(options: CommandInteractionOption[]); + private options: CommandInteractionOption[]; public get(name: string, required: true): CommandInteractionOption; public get(name: string, required: boolean): CommandInteractionOption | null; - private _getTypedOption( name: string, types: ApplicationCommandOptionType[], @@ -453,28 +451,20 @@ export class CommandInteractionOptionResolver { ): CommandInteractionOption | null; public getSubCommand(name: string): CommandInteractionOptionResolver | null; - public getBoolean(name: string, required: true): boolean; public getBoolean(name: string, required?: boolean): boolean | null; - public getChannel(name: string, required: true): NonNullable; public getChannel(name: string, required?: boolean): NonNullable | null; - public getString(name: string, required: true): string; public getString(name: string, required?: boolean): string | null; - public getInteger(name: string, required: true): number; public getInteger(name: string, required?: boolean): number | null; - public getUser(name: string, required: true): NonNullable; public getUser(name: string, required?: boolean): NonNullable | null; - public getMember(name: string, required: true): NonNullable; public getMember(name: string, required?: boolean): NonNullable | null; - public getRole(name: string, required: true): NonNullable; public getRole(name: string, required?: boolean): NonNullable | null; - public getMentionable( name: string, required: true, @@ -482,7 +472,6 @@ export class CommandInteractionOptionResolver { | NonNullable | NonNullable | NonNullable; - public getMentionable( name: string, required?: boolean, From c811bccf52c5c883009b7bc5dcd6cab39ccade2a Mon Sep 17 00:00:00 2001 From: Shino Date: Tue, 13 Jul 2021 04:16:06 -0400 Subject: [PATCH 07/18] style: fix typings whitespace (again) --- typings/index.d.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/typings/index.d.ts b/typings/index.d.ts index 516cdf8a90fa..f71eac2847f6 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -434,9 +434,6 @@ export class CommandInteraction extends Interaction { export class CommandInteractionOptionResolver { constructor(options: CommandInteractionOption[]); private options: CommandInteractionOption[]; - - public get(name: string, required: true): CommandInteractionOption; - public get(name: string, required: boolean): CommandInteractionOption | null; private _getTypedOption( name: string, types: ApplicationCommandOptionType[], @@ -450,6 +447,8 @@ export class CommandInteractionOptionResolver { required: boolean, ): CommandInteractionOption | null; + public get(name: string, required: true): CommandInteractionOption; + public get(name: string, required: boolean): CommandInteractionOption | null; public getSubCommand(name: string): CommandInteractionOptionResolver | null; public getBoolean(name: string, required: true): boolean; public getBoolean(name: string, required?: boolean): boolean | null; From 76a1c3804f2b782df422a15341cf1c73536b553d Mon Sep 17 00:00:00 2001 From: Shino Date: Tue, 13 Jul 2021 04:22:02 -0400 Subject: [PATCH 08/18] fix: mark parameter as optional in typings --- typings/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings/index.d.ts b/typings/index.d.ts index f71eac2847f6..327794d1b9bf 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -448,7 +448,7 @@ export class CommandInteractionOptionResolver { ): CommandInteractionOption | null; public get(name: string, required: true): CommandInteractionOption; - public get(name: string, required: boolean): CommandInteractionOption | null; + public get(name: string, required?: boolean): CommandInteractionOption | null; public getSubCommand(name: string): CommandInteractionOptionResolver | null; public getBoolean(name: string, required: true): boolean; public getBoolean(name: string, required?: boolean): boolean | null; From 903c340a6484975a763db8e2a01ad165a863b163 Mon Sep 17 00:00:00 2001 From: Shino Date: Tue, 13 Jul 2021 14:16:27 -0400 Subject: [PATCH 09/18] fix: cleanup some issues re. vaporox review --- src/errors/Messages.js | 6 +++ src/structures/CommandInteraction.js | 21 ++------- .../CommandInteractionOptionResolver.js | 46 +++++++++++-------- typings/index.d.ts | 16 ++----- 4 files changed, 42 insertions(+), 47 deletions(-) diff --git a/src/errors/Messages.js b/src/errors/Messages.js index f082ceaed1fe..61c8e1a9e463 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -133,6 +133,12 @@ const Messages = { INTERACTION_EPHEMERAL_REPLIED: 'Ephemeral responses cannot be fetched or deleted.', INTERACTION_FETCH_EPHEMERAL: 'Ephemeral responses cannot be fetched.', + COMMAND_INTERACTION_OPTION_NOT_FOUND: name => `Required option "${name}" not found.`, + COMMAND_INTERACTION_OPTION_TYPE: (name, type, expected) => + `Option "${name}" is of type: ${type}; expected one of: ${expected.join(', ')}`, + COMMAND_INTERACTION_OPTION_EMPTY: (name, type) => + `Required option "${name}" is of type: ${type}; expected a non-empty value.`, + INVITE_MISSING_SCOPES: 'At least one valid scope must be provided for the invite', NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`, diff --git a/src/structures/CommandInteraction.js b/src/structures/CommandInteraction.js index ad36707f5e49..e8d59f3d5243 100644 --- a/src/structures/CommandInteraction.js +++ b/src/structures/CommandInteraction.js @@ -51,7 +51,8 @@ class CommandInteraction extends Interaction { * @type {CommandInteractionOptionResolver} */ this.options = new CommandInteractionOptionResolver( - this._createOptionsArray(data.data.options, data.data.resolved), + this.client, + data.data.options?.map(option => this.transformOption(option, data.data.resolved)), ); /** @@ -110,7 +111,7 @@ class CommandInteraction extends Interaction { }; if ('value' in option) result.value = option.value; - if ('options' in option) result.options = this._createOptionsArray(option.options, resolved); + if ('options' in option) result.options = option.options.map(opt => this.transformOption(opt, resolved)); if (resolved) { const user = resolved.users?.[option.value]; @@ -129,22 +130,6 @@ class CommandInteraction extends Interaction { return result; } - /** - * Creates an array of options from the received API options array. - * @param {APIApplicationCommandOption[]} options The received options - * @param {APIApplicationCommandOptionResolved} resolved The resolved interaction data - * @returns {CommandInteractionOption[]} - * @private - */ - _createOptionsArray(options, resolved) { - const optionsArray = []; - if (typeof options === 'undefined') return optionsArray; - for (const option of options) { - optionsArray.push(this.transformOption(option, resolved)); - } - return optionsArray; - } - // These are here only for documentation purposes - they are implemented by InteractionResponses /* eslint-disable no-empty-function */ defer() {} diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js index d442523981f8..c9dd1610712f 100644 --- a/src/structures/CommandInteractionOptionResolver.js +++ b/src/structures/CommandInteractionOptionResolver.js @@ -1,16 +1,26 @@ 'use strict'; +const { TypeError } = require('../errors'); + /** * A resolver for command interaction options. */ class CommandInteractionOptionResolver { - constructor(options) { + constructor(client, options) { + /** + * The client that instantiated this. + * @name CommandInteractionOptionResolver#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + /** * The interaction options array. * @type {CommandInteractionOption[]} * @private */ - this.options = options ?? []; + this._options = options ?? []; } /** @@ -20,11 +30,11 @@ class CommandInteractionOptionResolver { * @returns {?CommandInteractionOption} The option, if found. */ get(name, required = false) { - const option = this.options.find(opt => opt.name === name); + const option = this._options.find(opt => opt.name === name); if (option) { return option; } else if (required) { - throw new Error(`Missing required option "${name}"`); + throw new TypeError('COMMAND_INTERACTION_OPTION_NOT_FOUND', name); } else { return null; } @@ -36,16 +46,16 @@ class CommandInteractionOptionResolver { * @param {ApplicationCommandOptionType[]} types The type of the option. * @param {string[]} properties The property to check for for `required`. * @param {boolean} required Whether to throw an error if the option is not found. - * @returns {*|null} The value of `property`. + * @returns {?CommandInteractionOption} The option, if found. * @private */ _getTypedOption(name, types, properties, required) { const option = this.get(name, required); if (option) { if (!types.includes(option.type)) { - throw new Error(`Option "${name}" is of type "${option.type}"; expected ${types.join('|')}.`); + throw new TypeError('COMMAND_INTERACTION_OPTION_TYPE', name, option.type, types); } else if (required && properties.every(prop => option[prop] === null || typeof option[prop] === 'undefined')) { - throw new Error(`Option "${name}" of type "${option.type}" is required, but is empty.`); + throw new TypeError('COMMAND_INTERACTION_OPTION_EMPTY', name, option.type); } else { return option; } @@ -57,19 +67,19 @@ class CommandInteractionOptionResolver { /** * Gets a sub-command or sub-command group. * @param {string} name The name of the sub-command or sub-command group. - * @returns {CommandInteractionOptionResolver|null} + * @returns {?CommandInteractionOptionResolver} * A new resolver for the sub-command/group's options, or null if empty */ getSubCommand(name) { const option = this._getTypedOption(name, ['SUB_COMMAND', 'SUB_COMMAND_GROUP'], ['options'], false); - return option ? new CommandInteractionOptionResolver(option.options) : null; + return option ? new CommandInteractionOptionResolver(this.client, option.options) : null; } /** * Gets a boolean option. * @param {string} name The name of the option. * @param {boolean} required Whether to throw an error if the option is not found. - * @returns {boolean|null} The value of the option, or null if not set and not required. + * @returns {?boolean} The value of the option, or null if not set and not required. */ getBoolean(name, required = false) { const option = this._getTypedOption(name, ['BOOLEAN'], ['value'], required); @@ -80,7 +90,7 @@ class CommandInteractionOptionResolver { * Gets a channel option. * @param {string} name The name of the option. * @param {boolean} required Whether to throw an error if the option is not found. - * @returns {GuildChannel|APIInteractionDataResolvedChannel|null} + * @returns {?GuildChannel|APIInteractionDataResolvedChannel} * The value of the option, or null if not set and not required. */ getChannel(name, required = false) { @@ -92,7 +102,7 @@ class CommandInteractionOptionResolver { * Gets a string option. * @param {string} name The name of the option. * @param {boolean} required Whether to throw an error if the option is not found. - * @returns {string|null} The value of the option, or null if not set and not required. + * @returns {?string} The value of the option, or null if not set and not required. */ getString(name, required = false) { const option = this._getTypedOption(name, ['STRING'], ['value'], required); @@ -103,7 +113,7 @@ class CommandInteractionOptionResolver { * Gets an integer option. * @param {string} name The name of the option. * @param {boolean} required Whether to throw an error if the option is not found. - * @returns {number|null} The value of the option, or null if not set and not required. + * @returns {?number} The value of the option, or null if not set and not required. */ getInteger(name, required = false) { const option = this._getTypedOption(name, ['INTEGER'], ['value'], required); @@ -114,7 +124,7 @@ class CommandInteractionOptionResolver { * Gets a user option. * @param {string} name The name of the option. * @param {boolean} required Whether to throw an error if the option is not found. - * @returns {User|null} The value of the option, or null if not set and not required. + * @returns {?User} The value of the option, or null if not set and not required. */ getUser(name, required = false) { const option = this._getTypedOption(name, ['USER'], ['user'], required); @@ -125,7 +135,7 @@ class CommandInteractionOptionResolver { * Gets a member option. * @param {string} name The name of the option. * @param {boolean} required Whether to throw an error if the option is not found. - * @returns {GuildMember|APIInteractionDataResolvedGuildMember|null} + * @returns {?GuildMember|APIInteractionDataResolvedGuildMember} * The value of the option, or null if not set and not required. */ getMember(name, required = false) { @@ -137,7 +147,7 @@ class CommandInteractionOptionResolver { * Gets a role option. * @param {string} name The name of the option. * @param {boolean} required Whether to throw an error if the option is not found. - * @returns {Role|APIRole|null} The value of the option, or null if not set and not required. + * @returns {?Role|APIRole} The value of the option, or null if not set and not required. */ getRole(name, required = false) { const option = this._getTypedOption(name, ['ROLE'], ['role'], required); @@ -148,11 +158,11 @@ class CommandInteractionOptionResolver { * Gets a mentionable option. * @param {string} name The name of the option. * @param {boolean} required Whether to throw an error if the option is not found. - * @returns {User|GuildMember|Role|null} The value of the option, or null if not set and not required. + * @returns {?User|GuildMember|Role} The value of the option, or null if not set and not required. */ getMentionable(name, required = false) { const option = this._getTypedOption(name, ['MENTIONABLE'], ['user', 'member', 'role'], required); - return option?.user ?? option?.member ?? option?.role ?? null; + return option?.member ?? option?.user ?? option?.role ?? null; } } diff --git a/typings/index.d.ts b/typings/index.d.ts index 327794d1b9bf..b041fca23855 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -432,8 +432,9 @@ export class CommandInteraction extends Interaction { } export class CommandInteractionOptionResolver { - constructor(options: CommandInteractionOption[]); - private options: CommandInteractionOption[]; + public constructor(client: Client, options: CommandInteractionOption[]); + public readonly client: Client; + private _options: CommandInteractionOption[]; private _getTypedOption( name: string, types: ApplicationCommandOptionType[], @@ -467,18 +468,11 @@ export class CommandInteractionOptionResolver { public getMentionable( name: string, required: true, - ): - | NonNullable - | NonNullable - | NonNullable; + ): NonNullable; public getMentionable( name: string, required?: boolean, - ): - | NonNullable - | NonNullable - | NonNullable - | null; + ): NonNullable | null; } export class DataResolver extends null { From fe7c38a13e04a4665cf5d175103d3679a1db470f Mon Sep 17 00:00:00 2001 From: Shino Date: Tue, 13 Jul 2021 18:42:10 -0400 Subject: [PATCH 10/18] docs: group nullables in jsdoc types --- src/structures/CommandInteractionOptionResolver.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js index c9dd1610712f..69461b3b6403 100644 --- a/src/structures/CommandInteractionOptionResolver.js +++ b/src/structures/CommandInteractionOptionResolver.js @@ -90,7 +90,7 @@ class CommandInteractionOptionResolver { * Gets a channel option. * @param {string} name The name of the option. * @param {boolean} required Whether to throw an error if the option is not found. - * @returns {?GuildChannel|APIInteractionDataResolvedChannel} + * @returns {?(GuildChannel|APIInteractionDataResolvedChannel)} * The value of the option, or null if not set and not required. */ getChannel(name, required = false) { @@ -135,7 +135,7 @@ class CommandInteractionOptionResolver { * Gets a member option. * @param {string} name The name of the option. * @param {boolean} required Whether to throw an error if the option is not found. - * @returns {?GuildMember|APIInteractionDataResolvedGuildMember} + * @returns {?(GuildMember|APIInteractionDataResolvedGuildMember)} * The value of the option, or null if not set and not required. */ getMember(name, required = false) { @@ -147,7 +147,7 @@ class CommandInteractionOptionResolver { * Gets a role option. * @param {string} name The name of the option. * @param {boolean} required Whether to throw an error if the option is not found. - * @returns {?Role|APIRole} The value of the option, or null if not set and not required. + * @returns {?(Role|APIRole)} The value of the option, or null if not set and not required. */ getRole(name, required = false) { const option = this._getTypedOption(name, ['ROLE'], ['role'], required); @@ -158,7 +158,7 @@ class CommandInteractionOptionResolver { * Gets a mentionable option. * @param {string} name The name of the option. * @param {boolean} required Whether to throw an error if the option is not found. - * @returns {?User|GuildMember|Role} The value of the option, or null if not set and not required. + * @returns {?(User|GuildMember|Role)} The value of the option, or null if not set and not required. */ getMentionable(name, required = false) { const option = this._getTypedOption(name, ['MENTIONABLE'], ['user', 'member', 'role'], required); From d5e2f84bbf4a3ef71415540e04d991a63c675a52 Mon Sep 17 00:00:00 2001 From: Shino Date: Tue, 13 Jul 2021 18:47:41 -0400 Subject: [PATCH 11/18] refactor: various code flow fixes from review --- .../CommandInteractionOptionResolver.js | 47 +++++++++---------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js index 69461b3b6403..541187c4a590 100644 --- a/src/structures/CommandInteractionOptionResolver.js +++ b/src/structures/CommandInteractionOptionResolver.js @@ -26,18 +26,18 @@ class CommandInteractionOptionResolver { /** * Gets an option by its name. * @param {string} name The name of the option. - * @param {boolean} required Whether to throw an error if the option is not found. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. * @returns {?CommandInteractionOption} The option, if found. */ get(name, required = false) { const option = this._options.find(opt => opt.name === name); - if (option) { - return option; - } else if (required) { - throw new TypeError('COMMAND_INTERACTION_OPTION_NOT_FOUND', name); - } else { + if (!option) { + if (required) { + throw new TypeError('COMMAND_INTERACTION_OPTION_NOT_FOUND', name); + } return null; } + return option; } /** @@ -51,17 +51,14 @@ class CommandInteractionOptionResolver { */ _getTypedOption(name, types, properties, required) { const option = this.get(name, required); - if (option) { - if (!types.includes(option.type)) { - throw new TypeError('COMMAND_INTERACTION_OPTION_TYPE', name, option.type, types); - } else if (required && properties.every(prop => option[prop] === null || typeof option[prop] === 'undefined')) { - throw new TypeError('COMMAND_INTERACTION_OPTION_EMPTY', name, option.type); - } else { - return option; - } - } else { - return option; + if (!option) { + return null; + } else if (!types.includes(option.type)) { + throw new TypeError('COMMAND_INTERACTION_OPTION_TYPE', name, option.type, types); + } else if (required && properties.every(prop => option[prop] === null || typeof option[prop] === 'undefined')) { + throw new TypeError('COMMAND_INTERACTION_OPTION_EMPTY', name, option.type); } + return option; } /** @@ -72,13 +69,13 @@ class CommandInteractionOptionResolver { */ getSubCommand(name) { const option = this._getTypedOption(name, ['SUB_COMMAND', 'SUB_COMMAND_GROUP'], ['options'], false); - return option ? new CommandInteractionOptionResolver(this.client, option.options) : null; + return option && new CommandInteractionOptionResolver(this.client, option.options); } /** * Gets a boolean option. * @param {string} name The name of the option. - * @param {boolean} required Whether to throw an error if the option is not found. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. * @returns {?boolean} The value of the option, or null if not set and not required. */ getBoolean(name, required = false) { @@ -89,7 +86,7 @@ class CommandInteractionOptionResolver { /** * Gets a channel option. * @param {string} name The name of the option. - * @param {boolean} required Whether to throw an error if the option is not found. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. * @returns {?(GuildChannel|APIInteractionDataResolvedChannel)} * The value of the option, or null if not set and not required. */ @@ -101,7 +98,7 @@ class CommandInteractionOptionResolver { /** * Gets a string option. * @param {string} name The name of the option. - * @param {boolean} required Whether to throw an error if the option is not found. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. * @returns {?string} The value of the option, or null if not set and not required. */ getString(name, required = false) { @@ -112,7 +109,7 @@ class CommandInteractionOptionResolver { /** * Gets an integer option. * @param {string} name The name of the option. - * @param {boolean} required Whether to throw an error if the option is not found. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. * @returns {?number} The value of the option, or null if not set and not required. */ getInteger(name, required = false) { @@ -123,7 +120,7 @@ class CommandInteractionOptionResolver { /** * Gets a user option. * @param {string} name The name of the option. - * @param {boolean} required Whether to throw an error if the option is not found. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. * @returns {?User} The value of the option, or null if not set and not required. */ getUser(name, required = false) { @@ -134,7 +131,7 @@ class CommandInteractionOptionResolver { /** * Gets a member option. * @param {string} name The name of the option. - * @param {boolean} required Whether to throw an error if the option is not found. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. * @returns {?(GuildMember|APIInteractionDataResolvedGuildMember)} * The value of the option, or null if not set and not required. */ @@ -146,7 +143,7 @@ class CommandInteractionOptionResolver { /** * Gets a role option. * @param {string} name The name of the option. - * @param {boolean} required Whether to throw an error if the option is not found. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. * @returns {?(Role|APIRole)} The value of the option, or null if not set and not required. */ getRole(name, required = false) { @@ -157,7 +154,7 @@ class CommandInteractionOptionResolver { /** * Gets a mentionable option. * @param {string} name The name of the option. - * @param {boolean} required Whether to throw an error if the option is not found. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. * @returns {?(User|GuildMember|Role)} The value of the option, or null if not set and not required. */ getMentionable(name, required = false) { From 8a6c879b673406ba53f33b34061b561ba1a324fa Mon Sep 17 00:00:00 2001 From: Shino Date: Tue, 13 Jul 2021 22:48:22 -0400 Subject: [PATCH 12/18] fix: fix small typo --- src/structures/CommandInteractionOptionResolver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js index 541187c4a590..f4af13bafe8d 100644 --- a/src/structures/CommandInteractionOptionResolver.js +++ b/src/structures/CommandInteractionOptionResolver.js @@ -44,7 +44,7 @@ class CommandInteractionOptionResolver { * Gets an option by name and property and checks its type. * @param {string} name The name of the option. * @param {ApplicationCommandOptionType[]} types The type of the option. - * @param {string[]} properties The property to check for for `required`. + * @param {string[]} properties The properties to check for for `required`. * @param {boolean} required Whether to throw an error if the option is not found. * @returns {?CommandInteractionOption} The option, if found. * @private From 6d4f5ad11bc8c59321c946e87af8f74ec5beca90 Mon Sep 17 00:00:00 2001 From: Shino Date: Wed, 14 Jul 2021 07:54:08 -0400 Subject: [PATCH 13/18] refactor: flatten sub-command/group logic --- src/errors/Messages.js | 2 + .../CommandInteractionOptionResolver.js | 47 ++++++++++++++++--- typings/index.d.ts | 6 ++- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/errors/Messages.js b/src/errors/Messages.js index 61c8e1a9e463..404658a5505f 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -138,6 +138,8 @@ const Messages = { `Option "${name}" is of type: ${type}; expected one of: ${expected.join(', ')}`, COMMAND_INTERACTION_OPTION_EMPTY: (name, type) => `Required option "${name}" is of type: ${type}; expected a non-empty value.`, + COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND: 'No sub-command specified for interaction.', + COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND_GROUP: 'No sub-command group specified for interaction.', INVITE_MISSING_SCOPES: 'At least one valid scope must be provided for the invite', diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js index f4af13bafe8d..eb37a9dac6b9 100644 --- a/src/structures/CommandInteractionOptionResolver.js +++ b/src/structures/CommandInteractionOptionResolver.js @@ -21,6 +21,27 @@ class CommandInteractionOptionResolver { * @private */ this._options = options ?? []; + + /** + * The name of the sub-command group. + * @type {?string} + * @private + */ + this._group = null; + /** + * The name of the sub-command. + * @type {?string} + * @private + */ + this._subCommand = null; + if (this._options[0]?.type === 'SUB_COMMAND_GROUP') { + this._group = this._options[0].name; + this._options = this._options[0].options ?? []; + } + if (this._options[0]?.type === 'SUB_COMMAND') { + this._subCommand = this._options[0].name; + this._options = this._options[0].options ?? []; + } } /** @@ -62,14 +83,26 @@ class CommandInteractionOptionResolver { } /** - * Gets a sub-command or sub-command group. - * @param {string} name The name of the sub-command or sub-command group. - * @returns {?CommandInteractionOptionResolver} - * A new resolver for the sub-command/group's options, or null if empty + * Gets the selected sub-command. + * @returns {string} The name of the selected sub-command. */ - getSubCommand(name) { - const option = this._getTypedOption(name, ['SUB_COMMAND', 'SUB_COMMAND_GROUP'], ['options'], false); - return option && new CommandInteractionOptionResolver(this.client, option.options); + getSubCommand() { + if (!this._subCommand) { + throw new TypeError('COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND'); + } + return this._subCommand; + } + + /** + * Gets the selected sub-command group. + * @param {boolean} [required=false] Whether to throw an error if there is no sub-command group. + * @returns {?string} The name of the selected sub-command group, or null if not set and not required. + */ + getSubCommandGroup(required = false) { + if (required && !this._group) { + throw new TypeError('COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND_GROUP'); + } + return this._group; } /** diff --git a/typings/index.d.ts b/typings/index.d.ts index b041fca23855..fd166a1872c8 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -435,6 +435,8 @@ export class CommandInteractionOptionResolver { public constructor(client: Client, options: CommandInteractionOption[]); public readonly client: Client; private _options: CommandInteractionOption[]; + private _group: string | null; + private _subCommand: string | null; private _getTypedOption( name: string, types: ApplicationCommandOptionType[], @@ -450,7 +452,9 @@ export class CommandInteractionOptionResolver { public get(name: string, required: true): CommandInteractionOption; public get(name: string, required?: boolean): CommandInteractionOption | null; - public getSubCommand(name: string): CommandInteractionOptionResolver | null; + public getSubCommand(): string; + public getSubCommandGroup(required: true): string; + public getSubCommandGroup(required?: boolean): string | null; public getBoolean(name: string, required: true): boolean; public getBoolean(name: string, required?: boolean): boolean | null; public getChannel(name: string, required: true): NonNullable; From 458a1df6fadd97b817e1b902f6e07888ff43aebe Mon Sep 17 00:00:00 2001 From: Shino Date: Thu, 15 Jul 2021 16:12:09 -0400 Subject: [PATCH 14/18] style: add newline Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com> --- src/structures/CommandInteractionOptionResolver.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js index eb37a9dac6b9..0ee1d604899a 100644 --- a/src/structures/CommandInteractionOptionResolver.js +++ b/src/structures/CommandInteractionOptionResolver.js @@ -28,6 +28,7 @@ class CommandInteractionOptionResolver { * @private */ this._group = null; + /** * The name of the sub-command. * @type {?string} From f5516eec15427845e79a83e7ccd44f7a90cf76bb Mon Sep 17 00:00:00 2001 From: Shino Date: Thu, 15 Jul 2021 16:22:15 -0400 Subject: [PATCH 15/18] style: remove leading spaces --- src/structures/CommandInteractionOptionResolver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js index 0ee1d604899a..7f800a0db29f 100644 --- a/src/structures/CommandInteractionOptionResolver.js +++ b/src/structures/CommandInteractionOptionResolver.js @@ -28,7 +28,7 @@ class CommandInteractionOptionResolver { * @private */ this._group = null; - + /** * The name of the sub-command. * @type {?string} From d86a77b4326d094b79beb5fe06a58b875a752844 Mon Sep 17 00:00:00 2001 From: Shino Date: Sat, 17 Jul 2021 16:40:21 -0400 Subject: [PATCH 16/18] refactor: make getSubCommandGroup() assert group --- src/structures/CommandInteractionOptionResolver.js | 7 +++---- typings/index.d.ts | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js index 7f800a0db29f..0317751e6afb 100644 --- a/src/structures/CommandInteractionOptionResolver.js +++ b/src/structures/CommandInteractionOptionResolver.js @@ -96,11 +96,10 @@ class CommandInteractionOptionResolver { /** * Gets the selected sub-command group. - * @param {boolean} [required=false] Whether to throw an error if there is no sub-command group. - * @returns {?string} The name of the selected sub-command group, or null if not set and not required. + * @returns {string} The name of the selected sub-command group, or null if not set and not required. */ - getSubCommandGroup(required = false) { - if (required && !this._group) { + getSubCommandGroup() { + if (!this._group) { throw new TypeError('COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND_GROUP'); } return this._group; diff --git a/typings/index.d.ts b/typings/index.d.ts index fd166a1872c8..4f9f2449d04b 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -453,8 +453,7 @@ export class CommandInteractionOptionResolver { public get(name: string, required: true): CommandInteractionOption; public get(name: string, required?: boolean): CommandInteractionOption | null; public getSubCommand(): string; - public getSubCommandGroup(required: true): string; - public getSubCommandGroup(required?: boolean): string | null; + public getSubCommandGroup(): string; public getBoolean(name: string, required: true): boolean; public getBoolean(name: string, required?: boolean): boolean | null; public getChannel(name: string, required: true): NonNullable; From 29c6536cf36bd99b41e0927dcbc9ed782053b96e Mon Sep 17 00:00:00 2001 From: Shino Date: Sat, 17 Jul 2021 16:44:08 -0400 Subject: [PATCH 17/18] docs: update outdated jsdoc --- src/structures/CommandInteractionOptionResolver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js index 0317751e6afb..d56dd1368d1c 100644 --- a/src/structures/CommandInteractionOptionResolver.js +++ b/src/structures/CommandInteractionOptionResolver.js @@ -96,7 +96,7 @@ class CommandInteractionOptionResolver { /** * Gets the selected sub-command group. - * @returns {string} The name of the selected sub-command group, or null if not set and not required. + * @returns {string} The name of the selected sub-command group. */ getSubCommandGroup() { if (!this._group) { From 00b4d8cfa09e51472608e92a3507579f4313b7e0 Mon Sep 17 00:00:00 2001 From: Shino Date: Sat, 17 Jul 2021 23:16:15 -0400 Subject: [PATCH 18/18] docs: change jsdoc return type of getMentionable() --- src/structures/CommandInteractionOptionResolver.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js index d56dd1368d1c..f8bc6ee29cf3 100644 --- a/src/structures/CommandInteractionOptionResolver.js +++ b/src/structures/CommandInteractionOptionResolver.js @@ -188,7 +188,8 @@ class CommandInteractionOptionResolver { * Gets a mentionable option. * @param {string} name The name of the option. * @param {boolean} [required=false] Whether to throw an error if the option is not found. - * @returns {?(User|GuildMember|Role)} The value of the option, or null if not set and not required. + * @returns {?(User|GuildMember|APIInteractionDataResolvedGuildMember|Role|APIRole)} + * The value of the option, or null if not set and not required. */ getMentionable(name, required = false) { const option = this._getTypedOption(name, ['MENTIONABLE'], ['user', 'member', 'role'], required);