Skip to content

Commit

Permalink
feat: add support for localized slash commands (v13 backport) (#7766)
Browse files Browse the repository at this point in the history
  • Loading branch information
xHyroM committed Apr 14, 2022
1 parent 9e4a900 commit 022e138
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 24 deletions.
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

0 comments on commit 022e138

Please sign in to comment.