diff --git a/bot.ts b/bot.ts index 4d2fa5718..d5dbdfc06 100644 --- a/bot.ts +++ b/bot.ts @@ -69,6 +69,7 @@ import { transformStageInstance } from "./transformers/stageInstance.ts"; import { StickerPack, transformSticker, transformStickerPack } from "./transformers/sticker.ts"; import { GetGatewayBot, transformGatewayBot } from "./transformers/gatewayBot.ts"; import { + DiscordApplicationCommandOptionChoice, DiscordEmoji, DiscordGatewayPayload, DiscordInteractionDataOption, @@ -127,6 +128,10 @@ import { VoiceRegions } from "./transformers/voiceRegion.ts"; import { GuildWidget } from "./transformers/widget.ts"; import { StageInstance } from "./transformers/stageInstance.ts"; import { Sticker } from "./transformers/sticker.ts"; +import { + ApplicationCommandOptionChoice, + transformApplicationCommandOptionChoice, +} from "./transformers/applicationCommandOptionChoice.ts"; import { transformEmbedToDiscordEmbed } from "./transformers/reverse/embed.ts"; import { transformComponentToDiscordComponent } from "./transformers/reverse/component.ts"; @@ -421,6 +426,10 @@ export interface Transformers { stageInstance: (bot: Bot, payload: DiscordStageInstance) => StageInstance; sticker: (bot: Bot, payload: DiscordSticker) => Sticker; stickerPack: (bot: Bot, payload: DiscordStickerPack) => StickerPack; + applicationCommandOptionChoice: ( + bot: Bot, + payload: DiscordApplicationCommandOptionChoice, + ) => ApplicationCommandOptionChoice; template: (bot: Bot, payload: DiscordTemplate) => Template; } @@ -467,6 +476,7 @@ export function createTransformers(options: Partial) { sticker: options.sticker || transformSticker, stickerPack: options.stickerPack || transformStickerPack, gatewayBot: options.gatewayBot || transformGatewayBot, + applicationCommandOptionChoice: options.applicationCommandOptionChoice || transformApplicationCommandOptionChoice, template: options.template || transformTemplate, }; } diff --git a/helpers/interactions/commands/createApplicationCommand.ts b/helpers/interactions/commands/createApplicationCommand.ts index 65664bc6c..031b5725e 100644 --- a/helpers/interactions/commands/createApplicationCommand.ts +++ b/helpers/interactions/commands/createApplicationCommand.ts @@ -1,5 +1,5 @@ import type { Bot } from "../../../bot.ts"; -import { ApplicationCommandOption, ApplicationCommandTypes } from "../../../mod.ts"; +import { ApplicationCommandOption, ApplicationCommandTypes, Localization } from "../../../mod.ts"; import { DiscordApplicationCommand, DiscordApplicationCommandOption } from "../../../types/discord.ts"; /** @@ -24,12 +24,16 @@ export async function createApplicationCommand( guildId ? bot.constants.endpoints.COMMANDS_GUILD(bot.applicationId, guildId) : bot.constants.endpoints.COMMANDS(bot.applicationId), - isContextApplicationCommand(options) ? { name: options.name, type: options.type } : { - name: options.name, - description: options.description, - type: options.type, - options: options.options ? makeOptionsForCommand(options.options) : undefined, - }, + isContextApplicationCommand(options) + ? { name: options.name, name_localizations: options.nameLocalizations, type: options.type } + : { + name: options.name, + name_localizations: options.nameLocalizations, + description: options.description, + description_localizations: options.descriptionLocalizations, + type: options.type, + options: options.options ? makeOptionsForCommand(options.options) : undefined, + }, ); return bot.transformers.applicationCommand(bot, result); @@ -39,9 +43,15 @@ export function makeOptionsForCommand(options: ApplicationCommandOption[]): Disc return options.map((option) => ({ type: option.type, name: option.name, + name_localizations: option.nameLocalizations, description: option.description, + description_localizations: option.descriptionLocalizations, required: option.required, - choices: option.choices, + choices: option.choices?.map((choice) => ({ + name: choice.name, + name_localizations: choice.nameLocalizations, + value: choice.value, + })), options: option.options ? makeOptionsForCommand(option.options) : undefined, channel_types: option.channelTypes, autocomplete: option.autocomplete, @@ -50,12 +60,16 @@ export function makeOptionsForCommand(options: ApplicationCommandOption[]): Disc })); } -/** https://discord.com/developers/docs/interactions/slash-commands#create-global-application-command-json-params */ +/** https://discord.com/developers/docs/interactions/application-commands#endpoints-json-params */ export interface CreateApplicationCommand { /** 1-31 character name matching lowercase `^[\w-]{1,32}$` */ name: string; + /** Localization object for the `name` field. Values follow the same restrictions as `name` */ + nameLocalizations?: Localization; /** 1-100 character description */ description: string; + /** Localization object for the `description` field. Values follow the same restrictions as `description` */ + descriptionLocalizations?: Localization; /** The type of the command */ type?: ApplicationCommandTypes; /** The parameters for the command */ @@ -64,10 +78,12 @@ export interface CreateApplicationCommand { defaultPermission?: boolean; } -/** https://discord.com/developers/docs/interactions/slash-commands#create-global-application-command-json-params */ +/** https://discord.com/developers/docs/interactions/application-commands#endpoints-json-params */ export interface CreateContextApplicationCommand { /** 1-31 character name matching lowercase `^[\w-]{1,32}$` */ name: string; + /** Localization object for the `name` field. Values follow the same restrictions as `name` */ + nameLocalizations?: Localization; /** The type of the command */ type: ApplicationCommandTypes.Message | ApplicationCommandTypes.User; } diff --git a/helpers/interactions/commands/getApplicationCommand.ts b/helpers/interactions/commands/getApplicationCommand.ts index 000bdc42a..f50dd5f20 100644 --- a/helpers/interactions/commands/getApplicationCommand.ts +++ b/helpers/interactions/commands/getApplicationCommand.ts @@ -2,14 +2,30 @@ import type { Bot } from "../../../bot.ts"; import { DiscordApplicationCommand } from "../../../types/discord.ts"; /** Fetches the global command for the given Id. If a guildId is provided, the guild command will be fetched. */ -export async function getApplicationCommand(bot: Bot, commandId: bigint, guildId?: bigint) { +export async function getApplicationCommand(bot: Bot, commandId: bigint, options?: GetApplicationCommand) { + let url = `${ + options?.guildId + ? bot.constants.endpoints.COMMANDS_GUILD_ID(bot.applicationId, options.guildId, commandId) + : bot.constants.endpoints.COMMANDS_ID(bot.applicationId, commandId) + }?`; + + if (options?.withLocalizations !== undefined) { + url += `with_localizations=${options.withLocalizations}`; + } + const result = await bot.rest.runMethod( bot.rest, "get", - guildId - ? bot.constants.endpoints.COMMANDS_GUILD_ID(bot.applicationId, guildId, commandId) - : bot.constants.endpoints.COMMANDS_ID(bot.applicationId, commandId), + url, ); return bot.transformers.applicationCommand(bot, result); } + +/** https://discord.com/developers/docs/interactions/application-commands#endpoints-query-string-params */ +export interface GetApplicationCommand { + /** Guild ID of the guild in which the command is available if it is a guild-specific command */ + guildId?: bigint; + /** Whether to include full localization object (`name_localizations` and `description_localizations`) in the returned objects, instead of the `name_localized` and `description_localized` fields. Default false */ + withLocalizations?: boolean; +} diff --git a/transformers/applicationCommand.ts b/transformers/applicationCommand.ts index 0a118c26b..d06f054be 100644 --- a/transformers/applicationCommand.ts +++ b/transformers/applicationCommand.ts @@ -8,7 +8,9 @@ export function transformApplicationCommand(bot: Bot, payload: DiscordApplicatio applicationId: bot.transformers.snowflake(payload.application_id), guildId: payload.guild_id ? bot.transformers.snowflake(payload.guild_id) : undefined, name: payload.name, + nameLocalizations: payload.name_localizations, description: payload.description, + descriptionLocalizations: payload.description_localizations, defaultPermission: payload.default_permission ?? false, type: payload.type, version: payload.version, diff --git a/transformers/applicationCommandOption.ts b/transformers/applicationCommandOption.ts index d1608f74d..c6a0993ff 100644 --- a/transformers/applicationCommandOption.ts +++ b/transformers/applicationCommandOption.ts @@ -1,7 +1,8 @@ import { Bot } from "../bot.ts"; import { DiscordApplicationCommandOption, DiscordApplicationCommandOptionChoice } from "../types/discord.ts"; -import { ApplicationCommandOptionTypes, ChannelTypes } from "../types/shared.ts"; +import { ApplicationCommandOptionTypes, ChannelTypes, Localization } from "../types/shared.ts"; import { Optionalize } from "../types/shared.ts"; +import { ApplicationCommandOptionChoice } from "./applicationCommandOptionChoice.ts"; export function transformApplicationCommandOption( bot: Bot, @@ -10,9 +11,11 @@ export function transformApplicationCommandOption( return { type: payload.type, name: payload.name, + nameLocalizations: payload.name_localizations, description: payload.description, + descriptionLocalizations: payload.description_localizations, required: payload.required ?? false, - choices: payload.choices, + choices: payload.choices?.map((choice) => bot.transformers.applicationCommandOptionChoice(bot, choice)), autocomplete: payload.autocomplete, channelTypes: payload.channel_types, minValue: payload.min_value, @@ -29,12 +32,16 @@ export interface ApplicationCommandOption { type: ApplicationCommandOptionTypes; /** 1-32 character name matching lowercase `^[\w-]{1,32}$` */ name: string; + /** Localization object for the `name` field. Values follow the same restrictions as `name` */ + nameLocalizations?: Localization; /** 1-100 character description */ description: string; + /** Localization object for the `description` field. Values follow the same restrictions as `description` */ + descriptionLocalizations?: Localization; /** If the parameter is required or optional--default `false` */ required: boolean; /** Choices for `string` and `int` types for the user to pick from */ - choices?: DiscordApplicationCommandOptionChoice[]; + choices?: ApplicationCommandOptionChoice[]; /** If the option is a subcommand or subcommand group type, this nested options will be the parameters */ options?: ApplicationCommandOption[]; /** if autocomplete interactions are enabled for this `String`, `Integer`, or `Number` type option */ diff --git a/transformers/applicationCommandOptionChoice.ts b/transformers/applicationCommandOptionChoice.ts new file mode 100644 index 000000000..1da43dae9 --- /dev/null +++ b/transformers/applicationCommandOptionChoice.ts @@ -0,0 +1,15 @@ +import { Bot } from "../bot.ts"; +import { DiscordApplicationCommandOptionChoice } from "../types/discord.ts"; +import { Camelize, Localization, Optionalize } from "../types/shared.ts"; + +export function transformApplicationCommandOptionChoice(bot: Bot, payload: DiscordApplicationCommandOptionChoice) { + const applicationCommandChoice = { + name: payload.name, + nameLocalizations: payload.name_localizations, + value: payload.value, + }; + + return applicationCommandChoice as Optionalize; +} + +export interface ApplicationCommandOptionChoice extends ReturnType {} diff --git a/types/discord.ts b/types/discord.ts index 7aab73b32..730b7d4e7 100644 --- a/types/discord.ts +++ b/types/discord.ts @@ -16,6 +16,8 @@ import { GuildNsfwLevel, IntegrationExpireBehaviors, InteractionTypes, + Locales, + Localization, MessageActivityTypes, MessageComponentTypes, MessageTypes, @@ -1633,7 +1635,7 @@ export interface DiscordInviteStageInstance { topic: string; } -/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommand */ +/** https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure */ export interface DiscordApplicationCommand { /** Unique id of the command */ id: string; @@ -1643,8 +1645,12 @@ export interface DiscordApplicationCommand { guild_id?: string; /** 1-32 character name matching */ name: string; + /** Localization object for the `name` field. Values follow the same restrictions as `name` */ + name_localizations?: Localization; /** 1-100 character description */ - description?: string; + description: string; + /** Localization object for the `description` field. Values follow the same restrictions as `description` */ + description_localizations?: Localization; /** The parameters for the command */ options?: DiscordApplicationCommandOption[]; /** Whether the command is enbaled by default when the app is added to a guild */ @@ -1655,14 +1661,18 @@ export interface DiscordApplicationCommand { version: string; } -/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoption */ +/** https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure */ export interface DiscordApplicationCommandOption { /** Value of Application Command Option Type */ type: ApplicationCommandOptionTypes; /** 1-32 character name matching lowercase `^[\w-]{1,32}$` */ name: string; + /** Localization object for the `name` field. Values follow the same restrictions as `name` */ + name_localizations?: Localization; /** 1-100 character description */ description: string; + /** Localization object for the `description` field. Values follow the same restrictions as `description` */ + description_localizations?: Localization; /** If the parameter is required or optional--default `false` */ required?: boolean; /** Choices for `string` and `int` types for the user to pick from */ @@ -1679,10 +1689,12 @@ export interface DiscordApplicationCommandOption { max_value?: number; } -/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptionchoice */ +/** https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-choice-structure */ export interface DiscordApplicationCommandOptionChoice { /** 1-100 character choice name */ name: string; + /** Localization object for the `name` field. Values follow the same restrictions as `name` */ + name_localizations?: Localization; /** Value of the choice, up to 100 characters if string */ value: string | number; } diff --git a/types/shared.ts b/types/shared.ts index e3c331d56..74df96117 100644 --- a/types/shared.ts +++ b/types/shared.ts @@ -1240,6 +1240,41 @@ export enum Errors { YOU_CAN_NOT_DM_THE_BOT_ITSELF = "YOU_CAN_NOT_DM_THE_BOT_ITSELF", } +export enum Locales { + Danish = "da", + German = "de", + EnglishUk = "en-GB", + EnglishUs = "en-US", + Spanish = "es-ES", + French = "fr", + Croatian = "hr", + Italian = "it", + Lithuanian = "lt", + Hungarian = "hu", + Dutch = "nl", + Norwegian = "no", + Polish = "pl", + PortugueseBrazilian = "pt-BR", + RomanianRomania = "ro", + Finnish = "fi", + Swedish = "sv-SE", + Vietnamese = "vi", + Turkish = "tr", + Czech = "cs", + Greek = "el", + Bulgarian = "bg", + Russian = "ru", + Ukrainian = "uk", + Hindi = "hi", + Thai = "th", + ChineseChina = "zh-CN", + Japanese = "ja", + ChineseTaiwan = "zh-TW", + Korean = "ko", +} + +export type Localization = Partial>; + // UTILS export type AtLeastOne }> = Partial & U[keyof U];