mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-16 03:23:29 +01:00
feat(CommandInteraction): add CommandInteractionOptionResolver (#6107)
Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com>
This commit is contained in:
@@ -131,6 +131,14 @@ const Messages = {
|
|||||||
INTERACTION_EPHEMERAL_REPLIED: 'Ephemeral responses cannot be fetched or deleted.',
|
INTERACTION_EPHEMERAL_REPLIED: 'Ephemeral responses cannot be fetched or deleted.',
|
||||||
INTERACTION_FETCH_EPHEMERAL: 'Ephemeral responses cannot be fetched.',
|
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.`,
|
||||||
|
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',
|
INVITE_MISSING_SCOPES: 'At least one valid scope must be provided for the invite',
|
||||||
|
|
||||||
NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`,
|
NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver');
|
||||||
const Interaction = require('./Interaction');
|
const Interaction = require('./Interaction');
|
||||||
const InteractionWebhook = require('./InteractionWebhook');
|
const InteractionWebhook = require('./InteractionWebhook');
|
||||||
const InteractionResponses = require('./interfaces/InteractionResponses');
|
const InteractionResponses = require('./interfaces/InteractionResponses');
|
||||||
const Collection = require('../util/Collection');
|
|
||||||
const { ApplicationCommandOptionTypes } = require('../util/Constants');
|
const { ApplicationCommandOptionTypes } = require('../util/Constants');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,9 +48,12 @@ class CommandInteraction extends Interaction {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The options passed to the command.
|
* 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
|
* 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 ('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) {
|
if (resolved) {
|
||||||
const user = resolved.users?.[option.value];
|
const user = resolved.users?.[option.value];
|
||||||
@@ -127,22 +130,6 @@ class CommandInteraction extends Interaction {
|
|||||||
return result;
|
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
|
// These are here only for documentation purposes - they are implemented by InteractionResponses
|
||||||
/* eslint-disable no-empty-function */
|
/* eslint-disable no-empty-function */
|
||||||
defer() {}
|
defer() {}
|
||||||
|
|||||||
200
src/structures/CommandInteractionOptionResolver.js
Normal file
200
src/structures/CommandInteractionOptionResolver.js
Normal 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;
|
||||||
49
typings/index.d.ts
vendored
49
typings/index.d.ts
vendored
@@ -410,7 +410,7 @@ export class CommandInteraction extends Interaction {
|
|||||||
public commandName: string;
|
public commandName: string;
|
||||||
public deferred: boolean;
|
public deferred: boolean;
|
||||||
public ephemeral: boolean | null;
|
public ephemeral: boolean | null;
|
||||||
public options: Collection<string, CommandInteractionOption>;
|
public options: CommandInteractionOptionResolver;
|
||||||
public replied: boolean;
|
public replied: boolean;
|
||||||
public webhook: InteractionWebhook;
|
public webhook: InteractionWebhook;
|
||||||
public defer(options: InteractionDeferOptions & { fetchReply: true }): Promise<Message | APIMessage>;
|
public defer(options: InteractionDeferOptions & { fetchReply: true }): Promise<Message | APIMessage>;
|
||||||
@@ -425,6 +425,53 @@ export class CommandInteraction extends Interaction {
|
|||||||
private _createOptionsCollection(options: unknown, resolved: unknown): Collection<string, CommandInteractionOption>;
|
private _createOptionsCollection(options: unknown, resolved: unknown): Collection<string, CommandInteractionOption>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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[],
|
||||||
|
properties: (keyof ApplicationCommandOption)[],
|
||||||
|
required: true,
|
||||||
|
): CommandInteractionOption;
|
||||||
|
private _getTypedOption(
|
||||||
|
name: string,
|
||||||
|
types: ApplicationCommandOptionType[],
|
||||||
|
properties: (keyof ApplicationCommandOption)[],
|
||||||
|
required: boolean,
|
||||||
|
): CommandInteractionOption | null;
|
||||||
|
|
||||||
|
public get(name: string, required: true): CommandInteractionOption;
|
||||||
|
public get(name: string, required?: boolean): CommandInteractionOption | null;
|
||||||
|
public getSubCommand(): string;
|
||||||
|
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<CommandInteractionOption['channel']>;
|
||||||
|
public getChannel(name: string, required?: boolean): NonNullable<CommandInteractionOption['channel']> | 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<CommandInteractionOption['user']>;
|
||||||
|
public getUser(name: string, required?: boolean): NonNullable<CommandInteractionOption['user']> | null;
|
||||||
|
public getMember(name: string, required: true): NonNullable<CommandInteractionOption['member']>;
|
||||||
|
public getMember(name: string, required?: boolean): NonNullable<CommandInteractionOption['member']> | null;
|
||||||
|
public getRole(name: string, required: true): NonNullable<CommandInteractionOption['role']>;
|
||||||
|
public getRole(name: string, required?: boolean): NonNullable<CommandInteractionOption['role']> | null;
|
||||||
|
public getMentionable(
|
||||||
|
name: string,
|
||||||
|
required: true,
|
||||||
|
): NonNullable<CommandInteractionOption['member' | 'role' | 'user']>;
|
||||||
|
public getMentionable(
|
||||||
|
name: string,
|
||||||
|
required?: boolean,
|
||||||
|
): NonNullable<CommandInteractionOption['member' | 'role' | 'user']> | null;
|
||||||
|
}
|
||||||
|
|
||||||
export class DataResolver extends null {
|
export class DataResolver extends null {
|
||||||
private constructor();
|
private constructor();
|
||||||
public static resolveBase64(data: Base64Resolvable): string;
|
public static resolveBase64(data: Base64Resolvable): string;
|
||||||
|
|||||||
Reference in New Issue
Block a user