feat(CommandInteraction): add CommandInteractionOptionResolver (#6107)

Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com>
This commit is contained in:
Shino
2021-07-18 14:16:32 -04:00
committed by GitHub
parent 4d53d0fd11
commit f293132345
4 changed files with 263 additions and 21 deletions

View File

@@ -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,12 @@ class CommandInteraction extends Interaction {
/**
* The options passed to the command.
* @type {Collection<string, CommandInteractionOption>}
* @type {CommandInteractionOptionResolver}
*/
this.options = this._createOptionsCollection(data.data.options, data.data.resolved);
this.options = new CommandInteractionOptionResolver(
this.client,
data.data.options?.map(option => this.transformOption(option, data.data.resolved)),
);
/**
* Whether this interaction has already been replied to
@@ -108,7 +111,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 = option.options.map(opt => this.transformOption(opt, resolved));
if (resolved) {
const user = resolved.users?.[option.value];
@@ -127,22 +130,6 @@ class CommandInteraction extends Interaction {
return result;
}
/**
* Creates a collection of options from the received options array.
* @param {APIApplicationCommandOption[]} options The received options
* @param {APIApplicationCommandOptionResolved} resolved The resolved interaction data
* @returns {Collection<string, CommandInteractionOption>}
* @private
*/
_createOptionsCollection(options, resolved) {
const optionsCollection = new Collection();
if (typeof options === 'undefined') return optionsCollection;
for (const option of options) {
optionsCollection.set(option.name, this.transformOption(option, resolved));
}
return optionsCollection;
}
// These are here only for documentation purposes - they are implemented by InteractionResponses
/* eslint-disable no-empty-function */
defer() {}

View File

@@ -0,0 +1,200 @@
'use strict';
const { TypeError } = require('../errors');
/**
* A resolver for command interaction options.
*/
class CommandInteractionOptionResolver {
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 ?? [];
/**
* 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 ?? [];
}
}
/**
* Gets an option by its name.
* @param {string} name The name of the option.
* @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) {
if (required) {
throw new TypeError('COMMAND_INTERACTION_OPTION_NOT_FOUND', name);
}
return null;
}
return option;
}
/**
* 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 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
*/
_getTypedOption(name, types, properties, required) {
const option = this.get(name, required);
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;
}
/**
* Gets the selected sub-command.
* @returns {string} The name of the selected sub-command.
*/
getSubCommand() {
if (!this._subCommand) {
throw new TypeError('COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND');
}
return this._subCommand;
}
/**
* Gets the selected sub-command group.
* @returns {string} The name of the selected sub-command group.
*/
getSubCommandGroup() {
if (!this._group) {
throw new TypeError('COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND_GROUP');
}
return this._group;
}
/**
* Gets a boolean 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 {?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);
return option?.value ?? null;
}
/**
* Gets a channel 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 {?(GuildChannel|APIInteractionDataResolvedChannel)}
* 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=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) {
const option = this._getTypedOption(name, ['STRING'], ['value'], required);
return option?.value ?? null;
}
/**
* Gets an integer 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 {?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);
return option?.value ?? null;
}
/**
* Gets a user 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} 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=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.
*/
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=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) {
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=false] Whether to throw an error if the option is not found.
* @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);
return option?.member ?? option?.user ?? option?.role ?? null;
}
}
module.exports = CommandInteractionOptionResolver;