Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for localized slash commands (v13 backport) #7766

Merged
merged 3 commits into from Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -55,7 +55,7 @@
"@sapphire/async-queue": "^1.1.9",
"@types/node-fetch": "^2.5.12",
"@types/ws": "^8.2.2",
"discord-api-types": "^0.27.1",
"discord-api-types": "^0.30.0",
"form-data": "^4.0.0",
"node-fetch": "^2.6.1",
"ws": "^8.4.0"
Expand Down
18 changes: 15 additions & 3 deletions src/managers/ApplicationCommandManager.js
Expand Up @@ -64,6 +64,8 @@ class ApplicationCommandManager extends CachedManager {
* Options used to fetch Application Commands from Discord
* @typedef {BaseFetchOptions} FetchApplicationCommandOptions
* @property {Snowflake} [guildId] The guild's id to fetch commands for, for when the guild is not cached
* @property {LocaleString} [locale] The locale to use when fetching this command
* @property {boolean} [withLocalizations] Whether to fetch all localization data
*/

/**
Expand All @@ -82,9 +84,9 @@ class ApplicationCommandManager extends CachedManager {
* .then(commands => console.log(`Fetched ${commands.size} commands`))
* .catch(console.error);
*/
async fetch(id, { guildId, cache = true, force = false } = {}) {
async fetch(id, { guildId, cache = true, force = false, locale, withLocalizations } = {}) {
if (typeof id === 'object') {
({ guildId, cache = true } = id);
({ guildId, cache = true, locale, withLocalizations } = id);
} else if (id) {
if (!force) {
const existing = this.cache.get(id);
Expand All @@ -94,7 +96,15 @@ class ApplicationCommandManager extends CachedManager {
return this._add(command, cache);
}

const data = await this.commandPath({ guildId }).get();
const data = await this.commandPath({ guildId }).get({
headers: {
'X-Discord-Locale': locale,
},
query:
typeof withLocalizations === 'boolean'
? new URLSearchParams({ with_localizations: withLocalizations })
: undefined,
});
return data.reduce((coll, command) => coll.set(command.id, this._add(command, cache, guildId)), new Collection());
}

Expand Down Expand Up @@ -206,7 +216,9 @@ class ApplicationCommandManager extends CachedManager {
static transformCommand(command) {
return {
name: command.name,
name_localizations: command.nameLocalizations ?? command.name_localizations,
description: command.description,
description_localizations: command.descriptionLocalizations ?? command.description_localizations,
type: typeof command.type === 'number' ? command.type : ApplicationCommandTypes[command.type],
options: command.options?.map(o => ApplicationCommand.transformOption(o)),
default_permission: command.defaultPermission ?? command.default_permission,
Expand Down
115 changes: 112 additions & 3 deletions src/structures/ApplicationCommand.js
Expand Up @@ -62,6 +62,26 @@ class ApplicationCommand extends Base {
this.name = data.name;
}

if ('name_localizations' in data) {
/**
* The name localizations for this command
* @type {?Object<string, string>}
*/
this.nameLocalizations = data.name_localizations;
} else {
this.nameLocalizations ??= null;
}

if ('name_localized' in data) {
/**
* The localized name for this command
* @type {?Object<string, string>}
*/
this.nameLocalized = data.name_localized;
} else {
this.nameLocalized ??= null;
}

if ('description' in data) {
/**
* The description of this command
Expand All @@ -70,6 +90,26 @@ class ApplicationCommand extends Base {
this.description = data.description;
}

if ('description_localizations' in data) {
/**
* The description localizations for this command
* @type {?string}
*/
this.descriptionLocalizations = data.description_localizations;
} else {
this.descriptionLocalizations ??= null;
}

if ('description_localized' in data) {
/**
* The localized description for this command
* @type {?string}
*/
this.descriptionLocalized = data.description_localized;
} else {
this.descriptionLocalized ??= null;
}

if ('options' in data) {
/**
* The options of this command
Expand Down Expand Up @@ -128,7 +168,9 @@ class ApplicationCommand extends Base {
* Data for creating or editing an application command.
* @typedef {Object} ApplicationCommandData
* @property {string} name The name of the command
* @property {Object<string, string>} [nameLocalizations] The localizations for the command name
* @property {string} description The description of the command
* @property {Object<string, string>} [descriptionLocalizations] The localizations for the command description
* @property {ApplicationCommandType} [type] The type of the command
* @property {ApplicationCommandOptionData[]} [options] Options for the command
* @property {boolean} [defaultPermission] Whether the command is enabled by default when the app is added to a guild
Expand All @@ -143,17 +185,31 @@ class ApplicationCommand extends Base {
* @typedef {Object} ApplicationCommandOptionData
* @property {ApplicationCommandOptionType|number} type The type of the option
* @property {string} name The name of the option
* @property {Object<string, string>} [nameLocalizations] The name localizations for the option
* @property {string} description The description of the option
* @property {Object<string, string>} [descriptionLocalizations] The description localizations for the option
* @property {boolean} [autocomplete] Whether the option is an autocomplete option
* @property {boolean} [required] Whether the option is required
* @property {ApplicationCommandOptionChoice[]} [choices] The choices of the option for the user to pick from
* @property {ApplicationCommandOptionChoiceData[]} [choices] The choices of the option for the user to pick from
* @property {ApplicationCommandOptionData[]} [options] Additional options if this option is a subcommand (group)
* @property {ChannelType[]|number[]} [channelTypes] When the option type is channel,
* the allowed types of channels that can be selected
* @property {number} [minValue] The minimum value for an `INTEGER` or `NUMBER` option
* @property {number} [maxValue] The maximum value for an `INTEGER` or `NUMBER` option
*/

/**
* @typedef {Object} ApplicationCommandOptionChoiceData
* @property {string} name The name of the choice
* @property {Object<string, string>} [nameLocalizations] The localized names for this choice
* @property {string|number} value The value of the choice
*/

/**
* @param {ApplicationCommandOptionChoiceData} ApplicationCommandOptionChoice
* @property {string} [nameLocalized] The localized name for this choice
*/

/**
* Edits this application command.
* @param {ApplicationCommandData} data The data to update the command with
Expand All @@ -179,6 +235,23 @@ class ApplicationCommand extends Base {
return this.edit({ name });
}

/**
* Edits the localized names of this ApplicationCommand
* @param {Object<string, string>} nameLocalizations The new localized names for the command
* @returns {Promise<ApplicationCommand>}
* @example
* // Edit the name localizations of this command
* command.setLocalizedNames({
* 'en-GB': 'test',
* 'pt-BR': 'teste',
* })
* .then(console.log)
* .catch(console.error)
*/
setNameLocalizations(nameLocalizations) {
return this.edit({ nameLocalizations });
}

/**
* Edits the description of this ApplicationCommand
* @param {string} description The new description of the command
Expand All @@ -188,6 +261,23 @@ class ApplicationCommand extends Base {
return this.edit({ description });
}

/**
* Edits the localized descriptions of this ApplicationCommand
* @param {Object<string, string>} descriptionLocalizations The new localized descriptions for the command
* @returns {Promise<ApplicationCommand>}
* @example
* // Edit the description localizations of this command
* command.setLocalizedDescriptions({
* 'en-GB': 'A test command',
* 'pt-BR': 'Um comando de teste',
* })
* .then(console.log)
* .catch(console.error)
*/
setDescriptionLocalizations(descriptionLocalizations) {
return this.edit({ descriptionLocalizations });
}

/**
* Edits the default permission of this ApplicationCommand
* @param {boolean} [defaultPermission=true] The default permission for this command
Expand Down Expand Up @@ -344,7 +434,11 @@ class ApplicationCommand extends Base {
* @typedef {Object} ApplicationCommandOption
* @property {ApplicationCommandOptionType} type The type of the option
* @property {string} name The name of the option
* @property {Object<string, string>} [nameLocalizations] The localizations for the option name
* @property {string} [nameLocalized] The localized name for this option
* @property {string} description The description of the option
* @property {Object<string, string>} [descriptionLocalizations] The localizations for the option description
* @property {string} [descriptionLocalized] The localized description for this option
* @property {boolean} [required] Whether the option is required
* @property {boolean} [autocomplete] Whether the option is an autocomplete option
* @property {ApplicationCommandOptionChoice[]} [choices] The choices of the option for the user to pick from
Expand All @@ -359,12 +453,14 @@ class ApplicationCommand extends Base {
* A choice for an application command option.
* @typedef {Object} ApplicationCommandOptionChoice
* @property {string} name The name of the choice
* @property {string} [nameLocalized] The localized name for this choice
* @property {Object<string, string>} [nameLocalizations] The localized names for this choice
* @property {string|number} value The value of the choice
*/

/**
* Transforms an {@link ApplicationCommandOptionData} object into something that can be used with the API.
* @param {ApplicationCommandOptionData} option The option to transform
* @param {ApplicationCommandOptionData|ApplicationCommandOption} option The option to transform
* @param {boolean} [received] Whether this option has been received from Discord
* @returns {APIApplicationCommandOption}
* @private
Expand All @@ -374,14 +470,27 @@ class ApplicationCommand extends Base {
const channelTypesKey = received ? 'channelTypes' : 'channel_types';
const minValueKey = received ? 'minValue' : 'min_value';
const maxValueKey = received ? 'maxValue' : 'max_value';
const nameLocalizationsKey = received ? 'nameLocalizations' : 'name_localizations';
const nameLocalizedKey = received ? 'nameLocalized' : 'name_localized';
const descriptionLocalizationsKey = received ? 'descriptionLocalizations' : 'description_localizations';
const descriptionLocalizedKey = received ? 'descriptionLocalized' : 'description_localized';
return {
type: typeof option.type === 'number' && !received ? option.type : ApplicationCommandOptionTypes[option.type],
name: option.name,
[nameLocalizationsKey]: option.nameLocalizations ?? option.name_localizations,
[nameLocalizedKey]: option.nameLocalized ?? option.name_localized,
description: option.description,
[descriptionLocalizationsKey]: option.descriptionLocalizations ?? option.description_localizations,
[descriptionLocalizedKey]: option.descriptionLocalized ?? option.description_localized,
required:
option.required ?? (stringType === 'SUB_COMMAND' || stringType === 'SUB_COMMAND_GROUP' ? undefined : false),
autocomplete: option.autocomplete,
choices: option.choices,
choices: option.choices?.map(choice => ({
name: choice.name,
[nameLocalizedKey]: choice.nameLocalized ?? choice.name_localized,
[nameLocalizationsKey]: choice.nameLocalizations ?? choice.name_localizations,
value: choice.value,
})),
options: option.options?.map(o => this.transformOption(o, received)),
[channelTypesKey]: received
? option.channel_types?.map(type => ChannelTypes[type])
Expand Down
2 changes: 1 addition & 1 deletion src/structures/AutocompleteInteraction.js
Expand Up @@ -76,7 +76,7 @@ class AutocompleteInteraction extends Interaction {

/**
* Sends results for the autocomplete of this interaction.
* @param {ApplicationCommandOptionChoice[]} options The options for the autocomplete
* @param {ApplicationCommandOptionChoiceData[]} options The options for the autocomplete
* @returns {Promise<void>}
* @example
* // respond to autocomplete interaction
Expand Down