diff --git a/package-lock.json b/package-lock.json index 0d851c4550d7..17b73ff6718b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,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" @@ -4300,9 +4300,9 @@ } }, "node_modules/discord-api-types": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.27.1.tgz", - "integrity": "sha512-NhOrRs3TDx/p/e7+VCzcvtVz/Wkqa/olS82HJb2aM/oI0CLcnB+lJMXWa8wjn57XviFBcMMR0poqUMXx0IqTkQ==" + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.30.0.tgz", + "integrity": "sha512-wYst0jrT8EJs2tVlwUTQ2xT0oWMjUrRMpFTkNY3NMleWyQNHgWaKhqFfxdLPdC2im9IuR5EsxcEgjhf/npeftw==" }, "node_modules/dmd": { "version": "4.0.6", @@ -16064,9 +16064,9 @@ } }, "discord-api-types": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.27.1.tgz", - "integrity": "sha512-NhOrRs3TDx/p/e7+VCzcvtVz/Wkqa/olS82HJb2aM/oI0CLcnB+lJMXWa8wjn57XviFBcMMR0poqUMXx0IqTkQ==" + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.30.0.tgz", + "integrity": "sha512-wYst0jrT8EJs2tVlwUTQ2xT0oWMjUrRMpFTkNY3NMleWyQNHgWaKhqFfxdLPdC2im9IuR5EsxcEgjhf/npeftw==" }, "dmd": { "version": "4.0.6", diff --git a/package.json b/package.json index cb64a6a480ba..5411ae5ee2d7 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/managers/ApplicationCommandManager.js b/src/managers/ApplicationCommandManager.js index 5456dd578f4e..a163fb497a40 100644 --- a/src/managers/ApplicationCommandManager.js +++ b/src/managers/ApplicationCommandManager.js @@ -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 */ /** @@ -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); @@ -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()); } @@ -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, diff --git a/src/structures/ApplicationCommand.js b/src/structures/ApplicationCommand.js index ea4558efc71c..03e02ffbaf94 100644 --- a/src/structures/ApplicationCommand.js +++ b/src/structures/ApplicationCommand.js @@ -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} + */ + this.nameLocalizations = data.name_localizations; + } else { + this.nameLocalizations ??= null; + } + + if ('name_localized' in data) { + /** + * The localized name for this command + * @type {?Object} + */ + this.nameLocalized = data.name_localized; + } else { + this.nameLocalized ??= null; + } + if ('description' in data) { /** * The description of this command @@ -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 @@ -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} [nameLocalizations] The localizations for the command name * @property {string} description The description of the command + * @property {Object} [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 @@ -143,10 +185,12 @@ 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} [nameLocalizations] The name localizations for the option * @property {string} description The description of the option + * @property {Object} [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 @@ -154,6 +198,18 @@ class ApplicationCommand extends Base { * @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} [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 @@ -179,6 +235,23 @@ class ApplicationCommand extends Base { return this.edit({ name }); } + /** + * Edits the localized names of this ApplicationCommand + * @param {Object} nameLocalizations The new localized names for the command + * @returns {Promise} + * @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 @@ -188,6 +261,23 @@ class ApplicationCommand extends Base { return this.edit({ description }); } + /** + * Edits the localized descriptions of this ApplicationCommand + * @param {Object} descriptionLocalizations The new localized descriptions for the command + * @returns {Promise} + * @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 @@ -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} [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} [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 @@ -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} [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 @@ -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]) diff --git a/src/structures/AutocompleteInteraction.js b/src/structures/AutocompleteInteraction.js index e942a6dc4558..3f1a2e3013e9 100644 --- a/src/structures/AutocompleteInteraction.js +++ b/src/structures/AutocompleteInteraction.js @@ -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} * @example * // respond to autocomplete interaction diff --git a/typings/index.d.ts b/typings/index.d.ts index fbb85d02eb16..cac235ab232e 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -52,6 +52,8 @@ import { GatewayVoiceStateUpdateDispatchData, RESTPostAPIApplicationCommandsJSONBody, Snowflake, + LocalizationMap, + LocaleString, } from 'discord-api-types/v9'; import { ChildProcess } from 'node:child_process'; import { EventEmitter } from 'node:events'; @@ -223,12 +225,16 @@ export class ApplicationCommand extends Base { public readonly createdTimestamp: number; public defaultPermission: boolean; public description: string; + public descriptionLocalizations: LocalizationMap | null; + public descriptionLocalized: string | null; public guild: Guild | null; public guildId: Snowflake | null; public readonly manager: ApplicationCommandManager; public id: Snowflake; public name: string; - public options: ApplicationCommandOption[]; + public nameLocalizations: LocalizationMap | null; + public nameLocalized: string | null; + public options: (ApplicationCommandOption & { nameLocalized?: string; descriptionLocalized?: string })[]; public permissions: ApplicationCommandPermissionsManager< PermissionsFetchType, PermissionsFetchType, @@ -241,7 +247,11 @@ export class ApplicationCommand extends Base { public delete(): Promise>; public edit(data: ApplicationCommandData): Promise>; public setName(name: string): Promise>; + public setNameLocalizations(nameLocalizations: LocalizationMap): Promise>; public setDescription(description: string): Promise>; + public setDescriptionLocalizations( + descriptionLocalizations: LocalizationMap, + ): Promise>; public setDefaultPermission(defaultPermission?: boolean): Promise>; public setOptions(options: ApplicationCommandOptionData[]): Promise>; public equals( @@ -773,7 +783,7 @@ export class AutocompleteInteraction exten public inCachedGuild(): this is AutocompleteInteraction<'cached'>; public inRawGuild(): this is AutocompleteInteraction<'raw'>; private transformOption(option: APIApplicationCommandOption): CommandInteractionOption; - public respond(options: ApplicationCommandOptionChoice[]): Promise; + public respond(options: ApplicationCommandOptionChoiceData[]): Promise; } export class CommandInteractionOptionResolver { @@ -830,7 +840,7 @@ export class CommandInteractionOptionResolver['member' | 'role' | 'user']> | null; public getMessage(name: string, required: true): NonNullable['message']>; public getMessage(name: string, required?: boolean): NonNullable['message']> | null; - public getFocused(getFull: true): ApplicationCommandOptionChoice; + public getFocused(getFull: true): ApplicationCommandOptionChoiceData; public getFocused(getFull?: boolean): string | number; public getAttachment(name: string, required: true): NonNullable['attachment']>; public getAttachment( @@ -3136,6 +3146,8 @@ export class ChannelManager extends CachedManager; } +export type FetchGuildApplicationCommandFetchOptions = Omit; + export class GuildApplicationCommandManager extends ApplicationCommandManager { private constructor(guild: Guild, iterable?: Iterable); public guild: Guild; @@ -3145,9 +3157,12 @@ export class GuildApplicationCommandManager extends ApplicationCommandManager; - public fetch(id: Snowflake, options?: BaseFetchOptions): Promise; - public fetch(options: BaseFetchOptions): Promise>; - public fetch(id?: undefined, options?: BaseFetchOptions): Promise>; + public fetch(id: Snowflake, options?: FetchGuildApplicationCommandFetchOptions): Promise; + public fetch(options: FetchGuildApplicationCommandFetchOptions): Promise>; + public fetch( + id?: undefined, + options?: FetchGuildApplicationCommandFetchOptions, + ): Promise>; public set(commands: ApplicationCommandDataResolvable[]): Promise>; } @@ -3738,6 +3753,7 @@ export interface ApplicationAsset { export interface BaseApplicationCommandData { name: string; + nameLocalizations?: LocalizationMap; defaultPermission?: boolean; } @@ -3769,7 +3785,9 @@ export type CommandOptionNonChoiceResolvableType = Exclude< export interface BaseApplicationCommandOptionsData { name: string; + nameLocalizations?: LocalizationMap; description: string; + descriptionLocalizations?: LocalizationMap; required?: boolean; autocomplete?: never; } @@ -3784,6 +3802,7 @@ export interface MessageApplicationCommandData extends BaseApplicationCommandDat export interface ChatInputApplicationCommandData extends BaseApplicationCommandData { description: string; + descriptionLocalizations?: LocalizationMap; type?: 'CHAT_INPUT' | ApplicationCommandTypes.CHAT_INPUT; options?: ApplicationCommandOptionData[]; } @@ -3817,13 +3836,13 @@ export interface ApplicationCommandAutocompleteOption extends Omit { type: CommandOptionChoiceResolvableType; - choices?: ApplicationCommandOptionChoice[]; + choices?: ApplicationCommandOptionChoiceData[]; autocomplete?: false; } export interface ApplicationCommandChoicesOption extends Omit { type: Exclude; - choices?: ApplicationCommandOptionChoice[]; + choices?: ApplicationCommandOptionChoiceData[]; autocomplete?: false; } @@ -3892,11 +3911,16 @@ export type ApplicationCommandOption = | ApplicationCommandNumericOption | ApplicationCommandSubCommand; -export interface ApplicationCommandOptionChoice { +export interface ApplicationCommandOptionChoiceData { name: string; + nameLocalizations?: LocalizationMap; value: string | number; } +export interface ApplicationCommandOptionChoice extends ApplicationCommandOptionChoiceData { + nameLocalized?: string; +} + export type ApplicationCommandType = keyof typeof ApplicationCommandTypes; export type ApplicationCommandOptionType = keyof typeof ApplicationCommandOptionTypes; @@ -4558,6 +4582,8 @@ export type ExplicitContentFilterLevel = keyof typeof ExplicitContentFilterLevel export interface FetchApplicationCommandOptions extends BaseFetchOptions { guildId?: Snowflake; + locale?: LocaleString; + withLocalizations?: boolean; } export interface FetchArchivedThreadOptions {