From 3d90d816e775bc0327facd42e8ec60f029fc9c61 Mon Sep 17 00:00:00 2001 From: Fleny Date: Sat, 26 Apr 2025 08:35:06 +0200 Subject: [PATCH] feat(types,bot)!: Components V2 (#4080) * UI * change component names to match Discord's names * add divider prop to separator component * rename thumbnail component image property to media * Rename MessageFlags.IsUiKit to IsComponentsV2 * Rename FileDisplay component to File component * Added container component * add id to container component transformers * add id to all components * SectionComponent.accessory now accepts ButtonComponent as well * Update description for FileComponent.file * fix bad merge * Change Component.accessory to ButtonComponent | ThumbnailComponent * Add section to reverse, fix Component#accessory * remove type cast on section transform * Add docs link for CreateGuildChannel Co-authored-by: LTS (Link) * Update comments & split components * fix type errors * Really fix CI * transform UnfurledMediaItem * Apply suggestions from code review Co-authored-by: LTS (Link) * change DiscordInputTextComponent to DiscordTextInputComponent * fix order in Transformers.customizers * Fix missing transformers * update action row components type --------- Co-authored-by: Awesome Stickz Co-authored-by: LTS (Link) --- packages/bot/src/transformers.ts | 20 + packages/bot/src/transformers/component.ts | 121 +++++- .../bot/src/transformers/reverse/component.ts | 104 +++++- packages/bot/src/transformers/types.ts | 45 +++ packages/types/src/discord/channel.ts | 2 +- packages/types/src/discord/components.ts | 348 ++++++++++++++++++ packages/types/src/discord/interactions.ts | 196 +--------- packages/types/src/discord/message.ts | 5 +- packages/types/src/discordeno.ts | 130 +++++-- packages/types/src/index.ts | 1 + 10 files changed, 746 insertions(+), 226 deletions(-) create mode 100644 packages/types/src/discord/components.ts diff --git a/packages/bot/src/transformers.ts b/packages/bot/src/transformers.ts index 7636ba718..56eabe43b 100644 --- a/packages/bot/src/transformers.ts +++ b/packages/bot/src/transformers.ts @@ -43,6 +43,7 @@ import type { DiscordInviteStageInstance, DiscordLobby, DiscordLobbyMember, + DiscordMediaGalleryItem, DiscordMember, DiscordMessage, DiscordMessageCall, @@ -65,6 +66,7 @@ import type { DiscordTemplate, DiscordThreadMember, DiscordThreadMemberGuildCreate, + DiscordUnfurledMediaItem, DiscordUser, DiscordVoiceRegion, DiscordVoiceState, @@ -120,6 +122,7 @@ import { type InviteStageInstance, type Lobby, type LobbyMember, + type MediaGalleryItem, type Member, type Message, type MessageCall, @@ -141,6 +144,7 @@ import { type Template, type ThreadMember, type ThreadMemberGuildCreate, + type UnfurledMediaItem, type User, type VoiceRegion, type VoiceState, @@ -190,6 +194,7 @@ import { transformInviteStageInstance, transformLobby, transformLobbyMember, + transformMediaGalleryItem, transformMember, transformMemberToDiscordMember, transformMessage, @@ -213,6 +218,7 @@ import { transformTemplate, transformThreadMember, transformThreadMemberGuildCreate, + transformUnfurledMediaItem, transformUser, transformUserToDiscordUser, transformVoiceRegion, @@ -225,6 +231,8 @@ import { import { transformAllowedMentionsToDiscordAllowedMentions, transformCreateApplicationCommandToDiscordCreateApplicationCommand, + transformMediaGalleryItemToDiscordMediaGalleryItem, + transformUnfurledMediaItemToDiscordUnfurledMediaItem, } from './transformers/reverse/index.js' import { bigintToSnowflake, snowflakeToBigint } from './utils.js' @@ -339,6 +347,7 @@ export type Transformers any lobby: (bot: Bot, payload: DiscordLobby, lobby: SetupDesiredProps) => any lobbyMember: (bot: Bot, payload: DiscordLobbyMember, lobbyMember: SetupDesiredProps) => any + mediaGalleryItem: (bot: Bot, payload: DiscordMediaGalleryItem, item: MediaGalleryItem) => any member: (bot: Bot, payload: DiscordMember, member: SetupDesiredProps) => any message: (bot: Bot, payload: DiscordMessage, message: SetupDesiredProps) => any messageCall: (bot: Bot, payload: DiscordMessageCall, call: SetupDesiredProps) => any @@ -388,6 +397,7 @@ export type Transformers any + unfurledMediaItem: (bot: Bot, payload: DiscordUnfurledMediaItem, unfurledMediaItem: UnfurledMediaItem) => any user: (bot: Bot, payload: DiscordUser, user: SetupDesiredProps) => any voiceRegion: (bot: Bot, payload: DiscordVoiceRegion, voiceRegion: VoiceRegion) => any voiceState: (bot: Bot, payload: DiscordVoiceState, voiceState: SetupDesiredProps) => any @@ -408,9 +418,11 @@ export type Transformers, payload: Component) => DiscordMessageComponent createApplicationCommand: (bot: Bot, payload: CreateApplicationCommand) => DiscordCreateApplicationCommand embed: (bot: Bot, payload: Embed) => DiscordEmbed + mediaGalleryItem: (bot: Bot, payload: MediaGalleryItem) => DiscordMediaGalleryItem member: (bot: Bot, payload: SetupDesiredProps) => DiscordMember snowflake: (snowflake: BigString) => string team: (bot: Bot, payload: Team) => DiscordTeam + unfurledMediaItem: (bot: Bot, payload: UnfurledMediaItem) => DiscordUnfurledMediaItem user: (bot: Bot, payload: SetupDesiredProps) => DiscordUser } activity: (bot: Bot, payload: DiscordActivity) => Activity @@ -480,6 +492,7 @@ export type Transformers SetupDesiredProps lobby: (bot: Bot, payload: DiscordLobby) => SetupDesiredProps lobbyMember: (bot: Bot, payload: DiscordLobbyMember) => SetupDesiredProps + mediaGalleryItem: (bot: Bot, payload: DiscordMediaGalleryItem) => MediaGalleryItem member: ( bot: Bot, payload: DiscordMember, @@ -516,6 +529,7 @@ export type Transformers, payload: DiscordTemplate) => Template threadMember: (bot: Bot, payload: DiscordThreadMember) => ThreadMember threadMemberGuildCreate: (bot: Bot, payload: DiscordThreadMemberGuildCreate) => ThreadMemberGuildCreate + unfurledMediaItem: (bot: Bot, payload: DiscordUnfurledMediaItem) => UnfurledMediaItem user: (bot: Bot, payload: DiscordUser) => SetupDesiredProps voiceRegion: (bot: Bot, payload: DiscordVoiceRegion) => VoiceRegion voiceState: ( @@ -572,6 +586,7 @@ export function createTransformers bot.transformers.component(bot, component)), + } +} + +function transformContainerComponent(bot: Bot, payload: DiscordContainerComponent): Component { + return { + type: MessageComponentTypes.Container, + id: payload.id, + accentColor: payload.accent_color ?? undefined, + spoiler: payload.spoiler, components: payload.components.map((component) => bot.transformers.component(bot, component)), } } @@ -44,6 +110,7 @@ function transformActionRow(bot: Bot, payload: DiscordActionRow): Component { function transformButtonComponent(bot: Bot, payload: DiscordButtonComponent): Component { return { type: MessageComponentTypes.Button, + id: payload.id, label: payload.label, customId: payload.custom_id, style: payload.style, @@ -60,9 +127,10 @@ function transformButtonComponent(bot: Bot, payload: DiscordButtonComponent): Co } } -function transformInputTextComponent(_bot: Bot, payload: DiscordInputTextComponent): Component { +function transformInputTextComponent(_bot: Bot, payload: DiscordTextInputComponent): Component { return { type: MessageComponentTypes.InputText, + id: payload.id, style: payload.style, required: payload.required, customId: payload.custom_id, @@ -77,6 +145,7 @@ function transformInputTextComponent(_bot: Bot, payload: DiscordInputTextCompone function transformSelectMenuComponent(bot: Bot, payload: DiscordSelectMenuComponent): Component { return { type: payload.type, + id: payload.id, customId: payload.custom_id, placeholder: payload.placeholder, minValues: payload.min_values, @@ -102,3 +171,43 @@ function transformSelectMenuComponent(bot: Bot, payload: DiscordSelectMenuCompon disabled: payload.disabled, } } + +function transformSectionComponent(bot: Bot, payload: DiscordSectionComponent): Component { + return { + type: MessageComponentTypes.Section, + id: payload.id, + components: payload.components.map((component) => bot.transformers.component(bot, component)), + accessory: bot.transformers.component(bot, payload.accessory), + } +} + +function transformThumbnailComponent(bot: Bot, payload: DiscordThumbnailComponent): Component { + return { + type: MessageComponentTypes.Thumbnail, + id: payload.id, + media: bot.transformers.unfurledMediaItem(bot, payload.media), + description: payload.description, + spoiler: payload.spoiler, + } +} + +function transformMediaGalleryComponent(bot: Bot, payload: DiscordMediaGalleryComponent): Component { + return { + type: MessageComponentTypes.MediaGallery, + id: payload.id, + items: payload.items.map((media) => bot.transformers.mediaGalleryItem(bot, media)), + } +} + +function transformFileComponent(bot: Bot, payload: DiscordFileComponent): Component { + return { + type: MessageComponentTypes.File, + id: payload.id, + file: bot.transformers.unfurledMediaItem(bot, payload.file), + spoiler: payload.spoiler, + } +} + +function keepAsIs(_bot: Bot, payload: DiscordTextDisplayComponent | DiscordSeparatorComponent): Component { + return payload +} diff --git a/packages/bot/src/transformers/reverse/component.ts b/packages/bot/src/transformers/reverse/component.ts index ba6cb31f2..0c6c26b83 100644 --- a/packages/bot/src/transformers/reverse/component.ts +++ b/packages/bot/src/transformers/reverse/component.ts @@ -1,5 +1,22 @@ import { type DiscordButtonComponent, type DiscordMessageComponent, MessageComponentTypes, type TextStyles } from '@discordeno/types' -import type { Bot, ButtonStyles, Component, DiscordActionRow, DiscordInputTextComponent, DiscordSelectMenuComponent } from '../../index.js' +import type { + Bot, + ButtonStyles, + Component, + DiscordActionRow, + DiscordContainerComponent, + DiscordFileComponent, + DiscordMediaGalleryComponent, + DiscordMediaGalleryItem, + DiscordSectionComponent, + DiscordSelectMenuComponent, + DiscordTextDisplayComponent, + DiscordTextInputComponent, + DiscordThumbnailComponent, + DiscordUnfurledMediaItem, + MediaGalleryItem, + UnfurledMediaItem, +} from '../../index.js' export function transformComponentToDiscordComponent(bot: Bot, payload: Component): DiscordMessageComponent { // This switch should include all cases @@ -8,6 +25,8 @@ export function transformComponentToDiscordComponent(bot: Bot, payload: Componen return transformActionRow(bot, payload) case MessageComponentTypes.Button: return transformButtonComponent(bot, payload) + case MessageComponentTypes.Container: + return transformContainerComponent(bot, payload) case MessageComponentTypes.InputText: return transformInputTextComponent(bot, payload) case MessageComponentTypes.SelectMenu: @@ -16,21 +35,64 @@ export function transformComponentToDiscordComponent(bot: Bot, payload: Componen case MessageComponentTypes.SelectMenuUsers: case MessageComponentTypes.SelectMenuUsersAndRoles: return transformSelectMenuComponent(bot, payload) + case MessageComponentTypes.Section: + return transformSectionComponent(bot, payload) + case MessageComponentTypes.File: + return transformFileComponent(bot, payload) + case MessageComponentTypes.MediaGallery: + return transformMediaGalleryComponent(bot, payload) + case MessageComponentTypes.Thumbnail: + return transformThumbnailComponent(bot, payload) + case MessageComponentTypes.Separator: + case MessageComponentTypes.TextDisplay: + // As of now they are compatible + return payload as DiscordMessageComponent + } +} + +export function transformUnfurledMediaItemToDiscordUnfurledMediaItem(_bot: Bot, payload: UnfurledMediaItem): DiscordUnfurledMediaItem { + return { + url: payload.url, + proxy_url: payload.proxyUrl, + height: payload.height, + width: payload.width, + content_type: payload.contentType, + } +} + +export function transformMediaGalleryItemToDiscordMediaGalleryItem(bot: Bot, payload: MediaGalleryItem): DiscordMediaGalleryItem { + return { + media: bot.transformers.reverse.unfurledMediaItem(bot, payload.media), + description: payload.description, + spoiler: payload.spoiler, } } function transformActionRow(bot: Bot, payload: Component): DiscordActionRow { return { type: MessageComponentTypes.ActionRow, + id: payload.id, // The actionRow.components type is kinda annoying, so we need a cast for this components: (payload.components?.map((component) => bot.transformers.reverse.component(bot, component)) ?? []) as DiscordActionRow['components'], } } +function transformContainerComponent(bot: Bot, payload: Component): DiscordContainerComponent { + return { + type: MessageComponentTypes.Container, + id: payload.id, + accent_color: payload.accentColor, + spoiler: payload.spoiler, + components: (payload.components?.map((component) => bot.transformers.reverse.component(bot, component)) ?? + []) as DiscordContainerComponent['components'], + } +} + function transformButtonComponent(bot: Bot, payload: Component): DiscordButtonComponent { // Since Component is a merge of all components, some casts are necessary return { type: MessageComponentTypes.Button, + id: payload.id, style: payload.style as ButtonStyles, custom_id: payload.customId, disabled: payload.disabled, @@ -47,10 +109,11 @@ function transformButtonComponent(bot: Bot, payload: Component): DiscordButtonCo } } -function transformInputTextComponent(_bot: Bot, payload: Component): DiscordInputTextComponent { +function transformInputTextComponent(_bot: Bot, payload: Component): DiscordTextInputComponent { // Since Component is a merge of all components, some casts are necessary return { type: MessageComponentTypes.InputText, + id: payload.id, style: payload.style as TextStyles, custom_id: payload.customId!, label: payload.label!, @@ -65,6 +128,7 @@ function transformInputTextComponent(_bot: Bot, payload: Component): DiscordInpu function transformSelectMenuComponent(bot: Bot, payload: Component): DiscordSelectMenuComponent { return { type: payload.type as DiscordSelectMenuComponent['type'], + id: payload.id, custom_id: payload.customId!, channel_types: payload.channelTypes, default_values: payload.defaultValues?.map((defaultValue) => ({ @@ -90,3 +154,39 @@ function transformSelectMenuComponent(bot: Bot, payload: Component): DiscordSele placeholder: payload.placeholder, } } + +function transformSectionComponent(bot: Bot, payload: Component): DiscordSectionComponent { + return { + type: MessageComponentTypes.Section, + id: payload.id, + components: payload.components?.map((component) => bot.transformers.reverse.component(bot, component)) as DiscordTextDisplayComponent[], + accessory: (payload.accessory ? bot.transformers.reverse.component(bot, payload.accessory) : undefined) as DiscordSectionComponent['accessory'], + } +} + +function transformFileComponent(bot: Bot, payload: Component): DiscordFileComponent { + return { + type: MessageComponentTypes.File, + id: payload.id, + file: bot.transformers.reverse.unfurledMediaItem(bot, payload.file!), + spoiler: payload.spoiler, + } +} + +function transformMediaGalleryComponent(bot: Bot, payload: Component): DiscordMediaGalleryComponent { + return { + type: MessageComponentTypes.MediaGallery, + id: payload.id, + items: payload.items?.map((item) => bot.transformers.reverse.mediaGalleryItem(bot, item)) ?? [], + } +} + +function transformThumbnailComponent(bot: Bot, payload: Component): DiscordThumbnailComponent { + return { + type: MessageComponentTypes.Thumbnail, + id: payload.id, + media: bot.transformers.reverse.unfurledMediaItem(bot, payload.media!), + description: payload.description, + spoiler: payload.spoiler, + } +} diff --git a/packages/bot/src/transformers/types.ts b/packages/bot/src/transformers/types.ts index b6461a76a..db83fe503 100644 --- a/packages/bot/src/transformers/types.ts +++ b/packages/bot/src/transformers/types.ts @@ -58,6 +58,7 @@ import type { ScheduledEventPrivacyLevel, ScheduledEventStatus, SelectOption, + SeparatorSpacingSize, SkuFlags, SortOrderTypes, StickerFormatTypes, @@ -546,6 +547,50 @@ export interface Component { defaultValues?: DiscordComponentDefaultValue[] /** Identifier for a purchasable SKU, only available when using premium-style buttons */ skuId?: bigint + /** Optional identifier for component */ + id?: number + /** A thumbnail or a button component, with a future possibility of adding more compatible components */ + accessory?: Component + /** Text that will be displayed similar to a message */ + content?: string + /** Alt text for the media */ + description?: string + /** Whether the thumbnail should be a spoiler (or blurred out). Defaults to `false` */ + spoiler?: boolean + /** 1 to 10 media gallery items */ + items?: MediaGalleryItem[] + /** Whether a visual divider should be displayed in the component. Defaults to `true` */ + divider?: boolean + /** Size of separator padding — `1` for small padding, `2` for large padding. Defaults to `1` */ + spacing?: SeparatorSpacingSize + /** This unfurled media item is unique in that it only supports attachment references using the attachment:// syntax */ + file?: UnfurledMediaItem + /** This unfurled media item is unique in that it only supports attachment references using the attachment:// syntax */ + media?: UnfurledMediaItem + /** Color for the accent on the container as RGB from 0x000000 to 0xFFFFFF */ + accentColor?: number +} + +export interface UnfurledMediaItem { + /** Supports arbitrary urls and attachment:// references */ + url: string + /** The proxied url of the media item. This field is ignored and provided by the API as part of the response */ + proxyUrl?: string + /** The height of the media item. This field is ignored and provided by the API as part of the response */ + height?: number | null + /** The width of the media item. This field is ignored and provided by the API as part of the response */ + width?: number | null + /** The media type of the content. This field is ignored and provided by the API as part of the response */ + contentType?: string +} + +export interface MediaGalleryItem { + /** A url or attachment */ + media: UnfurledMediaItem + /** Alt text for the media */ + description?: string + /** Whether the media should be a spoiler (or blurred out). Defaults to `false` */ + spoiler?: boolean } export interface DiscordComponentDefaultValue { diff --git a/packages/types/src/discord/channel.ts b/packages/types/src/discord/channel.ts index 96ded3b60..a3361571d 100644 --- a/packages/types/src/discord/channel.ts +++ b/packages/types/src/discord/channel.ts @@ -1,7 +1,7 @@ /** Types for: https://discord.com/developers/docs/resources/channel */ +import type { DiscordMessageComponents } from './components.js' import type { DiscordMember } from './guild.js' -import type { DiscordMessageComponents } from './interactions.js' import type { DiscordAllowedMentions, DiscordAttachment, DiscordEmbed, MessageFlags } from './message.js' import type { DiscordUser } from './user.js' diff --git a/packages/types/src/discord/components.ts b/packages/types/src/discord/components.ts new file mode 100644 index 000000000..92dbdef5d --- /dev/null +++ b/packages/types/src/discord/components.ts @@ -0,0 +1,348 @@ +/** Types for: https://discord.com/developers/docs/components/reference */ + +import type { ChannelTypes } from './channel.js' + +/** https://discord.com/developers/docs/components/reference#component-object-component-types */ +export enum MessageComponentTypes { + /** A container for other components */ + ActionRow = 1, + /** A button object */ + Button, + /** A select menu for picking from choices */ + SelectMenu, + /** A text input object */ + InputText, + /** Select menu for users */ + SelectMenuUsers, + /** Select menu for roles */ + SelectMenuRoles, + /** Select menu for users and roles */ + SelectMenuUsersAndRoles, + /** Select menu for channels */ + SelectMenuChannels, + /** Container to display text alongside an accessory component */ + Section, + /** Markdown text */ + TextDisplay, + /** Small image that can be used as an accessory */ + Thumbnail, + /** Display images and other media */ + MediaGallery, + /** Displays an attached file */ + File, + /** Component to add vertical padding between other components */ + Separator, + /** Container that visually groups a set of components */ + Container = 17, +} + +export type DiscordMessageComponents = DiscordMessageComponent[] +export type DiscordMessageComponent = + | DiscordActionRow + | DiscordSelectMenuComponent + | DiscordButtonComponent + | DiscordTextInputComponent + | DiscordSectionComponent + | DiscordTextDisplayComponent + | DiscordThumbnailComponent + | DiscordMediaGalleryComponent + | DiscordSeparatorComponent + | DiscordFileComponent + | DiscordContainerComponent + +/** https://discord.com/developers/docs/components/reference#anatomy-of-a-component */ +export interface DiscordBaseComponent { + /** The type of the component */ + type: MessageComponentTypes + /** 32 bit integer used as an optional identifier for component */ + id?: number +} + +/** https://discord.com/developers/docs/components/reference#action-row-action-row-structure */ +export interface DiscordActionRow extends DiscordBaseComponent { + type: MessageComponentTypes.ActionRow + + /** + * The components in this row + * + * @remarks + * Up to 5 button components, a single select component or a single text input component + */ + components: (DiscordButtonComponent | DiscordSelectMenuComponent | DiscordTextInputComponent)[] +} + +/** https://discord.com/developers/docs/components/reference#button-button-structure */ +export interface DiscordButtonComponent extends DiscordBaseComponent { + type: MessageComponentTypes.Button + + /** For different styles/colors of the buttons */ + style: ButtonStyles + /** + * Text that appears on the button + * + * @remarks + * A label can have a max of 80 characters + * A button of style {@link ButtonStyles.Premium | Premium} cannot have a label + */ + label?: string + /** + * Emoji object that includes fields of name, id, and animated supporting unicode and custom emojis. + * + * @remarks + * A button of style {@link ButtonStyles.Premium | Premium} cannot have an emoji + */ + emoji?: { + /** Emoji id */ + id?: string + /** Emoji name */ + name?: string + /** Whether this emoji is animated */ + animated?: boolean + } + /** + * A dev-defined unique string sent on click (max 100 characters). + * + * @remarks + * A button of style {@link ButtonStyles.Link | Link} or {@link ButtonStyles.Premium | Premium} cannot have a custom_id + */ + custom_id?: string + /** + * Identifier for a purchasable SKU + * + * @remarks + * Buttons of style {@link ButtonStyles.Premium | Premium} must have a sku_id, any other button with a different style can not have a a sku_id + */ + sku_id?: string + /** + * Url for {@link ButtonStyles.Link | link} buttons that can navigate a user to the web. + * + * @remarks + * Buttons of style {@link ButtonStyles.Link | Link} must have an url, any other button with a different style can not have an url + */ + url?: string + /** Whether or not this button is disabled */ + disabled?: boolean +} + +/** https://discord.com/developers/docs/components/reference#button-button-styles */ +export enum ButtonStyles { + /** A blurple button */ + Primary = 1, + /** A grey button */ + Secondary, + /** A green button */ + Success, + /** A red button */ + Danger, + /** A button that navigates to a URL */ + Link, + /** A blurple button to show a Premium item in the shop */ + Premium, +} + +/** + * https://discord.com/developers/docs/components/reference#string-select + * https://discord.com/developers/docs/components/reference#user-select + * https://discord.com/developers/docs/components/reference#role-select + * https://discord.com/developers/docs/components/reference#mentionable-select + * https://discord.com/developers/docs/components/reference#channel-select + */ +export interface DiscordSelectMenuComponent extends DiscordBaseComponent { + type: + | MessageComponentTypes.SelectMenu + | MessageComponentTypes.SelectMenuUsers + | MessageComponentTypes.SelectMenuRoles + | MessageComponentTypes.SelectMenuUsersAndRoles + | MessageComponentTypes.SelectMenuChannels + + /** A custom identifier for this component. Maximum 100 characters. */ + custom_id: string + /** Specified choices in a select menu; Maximum of 25 items. */ + options?: DiscordSelectOption[] + /** A custom placeholder text if nothing is selected. Maximum 150 characters. */ + placeholder?: string + /** The minimum number of items that must be selected. Default 1. Between 1-25. */ + min_values?: number + /** The maximum number of items that can be selected. Default 1. Between 1-25. */ + max_values?: number + /** + * Whether select menu is disabled + * + * @default false + */ + disabled?: boolean + /** + * List of default values for auto-populated select menu components + * + * @remarks + * The number of default values must be in the range defined by min_values and max_values + */ + default_values?: DiscordSelectMenuDefaultValue[] + /** List of channel types to include in a channel select menu options list */ + channel_types?: ChannelTypes[] +} + +/** https://discord.com/developers/docs/components/reference#string-select-select-option-structure */ +export interface DiscordSelectOption { + /** The user-facing name of the option. Maximum 25 characters. */ + label: string + /** The dev-defined value of the option. Maximum 100 characters. */ + value: string + /** An additional description of the option. Maximum 50 characters. */ + description?: string + /** The id, name, and animated properties of an emoji. */ + emoji?: { + /** Emoji id */ + id?: string + /** Emoji name */ + name?: string + /** Whether this emoji is animated */ + animated?: boolean + } + /** Will render this option as already-selected by default. */ + default?: boolean +} + +/** https://discord.com/developers/docs/components/reference#text-input-text-input-structure */ +export interface DiscordTextInputComponent extends DiscordBaseComponent { + type: MessageComponentTypes.InputText + + /** The customId of the InputText */ + custom_id: string + /** The style of the InputText */ + style: TextStyles + /** The label of the InputText (max 45 characters) */ + label: string + /** The minimum length of the text the user has to provide */ + min_length?: number + /** The maximum length of the text the user has to provide */ + max_length?: number + /** whether this component is required to be filled, default true */ + required?: boolean + /** Pre-filled value for input text. */ + value?: string + /** The placeholder of the InputText */ + placeholder?: string +} + +/** https://discord.com/developers/docs/components/reference#text-input-text-input-styles */ +export enum TextStyles { + /** Intended for short single-line text */ + Short = 1, + /** Intended for much longer inputs */ + Paragraph = 2, +} + +/** https://discord.com/developers/docs/components/reference#user-select-select-default-value-structure */ +export interface DiscordSelectMenuDefaultValue { + /** ID of a user, role, or channel */ + id: string + /** Type of value that id represents. */ + type: 'user' | 'role' | 'channel' +} + +/** https://discord.com/developers/docs/components/reference#section-section-structure */ +export interface DiscordSectionComponent extends DiscordBaseComponent { + type: MessageComponentTypes.Section + + /** One to three text components */ + components: DiscordTextDisplayComponent[] + /** A thumbnail or a button component, with a future possibility of adding more compatible components */ + accessory: DiscordButtonComponent | DiscordThumbnailComponent +} + +/** https://discord.com/developers/docs/components/reference#text-display-text-display-structure */ +export interface DiscordTextDisplayComponent extends DiscordBaseComponent { + type: MessageComponentTypes.TextDisplay + + /** Text that will be displayed similar to a message */ + content: string +} + +export interface DiscordThumbnailComponent extends DiscordBaseComponent { + type: MessageComponentTypes.Thumbnail + + /** A url or attachment */ + media: DiscordUnfurledMediaItem + /** Alt text for the media */ + description?: string + /** Whether the thumbnail should be a spoiler (or blurred out). Defaults to `false` */ + spoiler?: boolean +} + +/** https://discord.com/developers/docs/components/reference#media-gallery-media-gallery-structure */ +export interface DiscordMediaGalleryComponent extends DiscordBaseComponent { + type: MessageComponentTypes.MediaGallery + + /** 1 to 10 media gallery items */ + items: DiscordMediaGalleryItem[] +} + +/** https://discord.com/developers/docs/components/reference#media-gallery-media-gallery-item-structure */ +export interface DiscordMediaGalleryItem { + /** A url or attachment */ + media: DiscordUnfurledMediaItem + /** Alt text for the media */ + description?: string + /** Whether the media should be a spoiler (or blurred out). Defaults to `false` */ + spoiler?: boolean +} + +/** https://discord.com/developers/docs/components/reference#file-file-structure */ +export interface DiscordFileComponent extends DiscordBaseComponent { + type: MessageComponentTypes.File + + /** This unfurled media item is unique in that it only supports attachment references using the attachment:// syntax */ + file: DiscordUnfurledMediaItem + /** Whether the media should be a spoiler (or blurred out). Defaults to `false` */ + spoiler?: boolean +} + +/** https://discord.com/developers/docs/components/reference#separator-separator-structure */ +export interface DiscordSeparatorComponent extends DiscordBaseComponent { + type: MessageComponentTypes.Separator + + /** Whether a visual divider should be displayed in the component. Defaults to `true` */ + divider?: boolean + /** Size of separator padding — `1` for small padding, `2` for large padding. Defaults to `1` */ + spacing?: SeparatorSpacingSize +} + +/** https://discord.com/developers/docs/components/reference#separator-separator-structure, spacing description */ +export enum SeparatorSpacingSize { + Small = 1, + Large = 2, +} + +/** https://discord.com/developers/docs/components/reference#container-container-structure */ +export interface DiscordContainerComponent extends DiscordBaseComponent { + type: MessageComponentTypes.Container + + /** Up to 10 components of the type action row, text display, section, media gallery, separator, or file */ + components: Array< + | DiscordActionRow + | DiscordTextDisplayComponent + | DiscordSectionComponent + | DiscordMediaGalleryComponent + | DiscordSeparatorComponent + | DiscordFileComponent + > + /** Color for the accent on the container as RGB from 0x000000 to 0xFFFFFF */ + accent_color?: number | null + /** Whether the container should be a spoiler (or blurred out). Defaults to `false` */ + spoiler?: boolean +} + +/** https://discord.com/developers/docs/components/reference#unfurled-media-item-structure */ +export interface DiscordUnfurledMediaItem { + /** Supports arbitrary urls and attachment:// references */ + url: string + /** The proxied url of the media item. This field is ignored and provided by the API as part of the response */ + proxy_url?: string + /** The height of the media item. This field is ignored and provided by the API as part of the response */ + height?: number | null + /** The width of the media item. This field is ignored and provided by the API as part of the response */ + width?: number | null + /** The media type of the content. This field is ignored and provided by the API as part of the response */ + content_type?: string +} diff --git a/packages/types/src/discord/interactions.ts b/packages/types/src/discord/interactions.ts index 4fb152307..62d32dc14 100644 --- a/packages/types/src/discord/interactions.ts +++ b/packages/types/src/discord/interactions.ts @@ -2,11 +2,11 @@ * Types for: * - https://discord.com/developers/docs/interactions/receiving-and-responding * - https://discord.com/developers/docs/interactions/application-commands - * - https://discord.com/developers/docs/interactions/message-components */ import type { DiscordApplicationIntegrationType } from './application.js' import type { ChannelTypes, DiscordChannel } from './channel.js' +import type { DiscordMessageComponents, MessageComponentTypes } from './components.js' import type { DiscordEntitlement } from './entitlement.js' import type { DiscordGuild, DiscordMember, DiscordMemberWithUser } from './guild.js' import type { DiscordAttachment, DiscordMessage } from './message.js' @@ -411,200 +411,6 @@ export enum ApplicationCommandPermissionTypes { Channel, } -/** https://discord.com/developers/docs/interactions/message-components#component-object-component-types */ -export enum MessageComponentTypes { - /** A container for other components */ - ActionRow = 1, - /** A button object */ - Button, - /** A select menu for picking from choices */ - SelectMenu, - /** A text input object */ - InputText, - /** Select menu for users */ - SelectMenuUsers, - /** Select menu for roles */ - SelectMenuRoles, - /** Select menu for users and roles */ - SelectMenuUsersAndRoles, - /** Select menu for channels */ - SelectMenuChannels, -} - -export type DiscordMessageComponents = DiscordMessageComponent[] -export type DiscordMessageComponent = DiscordActionRow | DiscordSelectMenuComponent | DiscordButtonComponent | DiscordInputTextComponent - -/** https://discord.com/developers/docs/interactions/message-components#actionrow */ -export interface DiscordActionRow { - /** Action rows are a group of buttons. */ - type: MessageComponentTypes.ActionRow - /** The components in this row */ - components: Exclude[] -} - -/** https://discord.com/developers/docs/interactions/message-components#button-object-button-structure */ -export interface DiscordButtonComponent { - /** All button components have type 2 */ - type: MessageComponentTypes.Button - /** - * Text that appears on the button - * - * @remarks - * A label can have a max of 80 characters - * A button of style {@link ButtonStyles.Premium | Premium} cannot have a label - */ - label?: string - /** - * A dev-defined unique string sent on click (max 100 characters). - * - * @remarks - * A button of style {@link ButtonStyles.Link | Link} or {@link ButtonStyles.Premium | Premium} cannot have a custom_id - */ - custom_id?: string - /** For different styles/colors of the buttons */ - style: ButtonStyles - /** - * Emoji object that includes fields of name, id, and animated supporting unicode and custom emojis. - * - * @remarks - * A button of style {@link ButtonStyles.Premium | Premium} cannot have an emoji - */ - emoji?: { - /** Emoji id */ - id?: string - /** Emoji name */ - name?: string - /** Whether this emoji is animated */ - animated?: boolean - } - /** - * Url for {@link ButtonStyles.Link | link} buttons that can navigate a user to the web. - * - * @remarks - * Buttons of style {@link ButtonStyles.Link | Link} must have an url, any other button with a different style can not have an url - */ - url?: string - /** Whether or not this button is disabled */ - disabled?: boolean - /** - * Identifier for a purchasable SKU - * - * @remarks - * Buttons of style {@link ButtonStyles.Premium | Premium} must have a sku_id, any other button with a different style can not have a a sku_id - */ - sku_id?: string -} - -/** https://discord.com/developers/docs/interactions/message-components#button-object-button-styles */ -export enum ButtonStyles { - /** A blurple button */ - Primary = 1, - /** A grey button */ - Secondary, - /** A green button */ - Success, - /** A red button */ - Danger, - /** A button that navigates to a URL */ - Link, - /** A blurple button to show a Premium item in the shop */ - Premium, -} - -/** https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure */ -export interface DiscordSelectMenuComponent { - type: - | MessageComponentTypes.SelectMenu - | MessageComponentTypes.SelectMenuChannels - | MessageComponentTypes.SelectMenuRoles - | MessageComponentTypes.SelectMenuUsers - | MessageComponentTypes.SelectMenuUsersAndRoles - /** A custom identifier for this component. Maximum 100 characters. */ - custom_id: string - /** A custom placeholder text if nothing is selected. Maximum 150 characters. */ - placeholder?: string - /** The minimum number of items that must be selected. Default 1. Between 1-25. */ - min_values?: number - /** The maximum number of items that can be selected. Default 1. Between 1-25. */ - max_values?: number - /** - * List of default values for auto-populated select menu components - * - * @remarks - * The number of default values must be in the range defined by min_values and max_values - */ - default_values?: DiscordSelectMenuDefaultValue[] - /** List of channel types to include in a channel select menu options list */ - channel_types?: ChannelTypes[] - /** The choices! Maximum of 25 items. */ - options?: DiscordSelectOption[] - /** - * Whether select menu is disabled - * - * @default false - */ - disabled?: boolean -} - -/** https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure */ -export interface DiscordSelectOption { - /** The user-facing name of the option. Maximum 25 characters. */ - label: string - /** The dev-defined value of the option. Maximum 100 characters. */ - value: string - /** An additional description of the option. Maximum 50 characters. */ - description?: string - /** The id, name, and animated properties of an emoji. */ - emoji?: { - /** Emoji id */ - id?: string - /** Emoji name */ - name?: string - /** Whether this emoji is animated */ - animated?: boolean - } - /** Will render this option as already-selected by default. */ - default?: boolean -} - -/** https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-default-value-structure */ -export interface DiscordSelectMenuDefaultValue { - /** ID of a user, role, or channel */ - id: string - /** Type of value that id represents. */ - type: 'user' | 'role' | 'channel' -} - -/** https://discord.com/developers/docs/interactions/message-components#text-input-object-text-input-structure */ -export interface DiscordInputTextComponent { - /** InputText Component is of type 3 */ - type: MessageComponentTypes.InputText - /** The style of the InputText */ - style: TextStyles - /** whether this component is required to be filled, default true */ - required?: boolean - /** The customId of the InputText */ - custom_id: string - /** The label of the InputText (max 45 characters) */ - label: string - /** The placeholder of the InputText */ - placeholder?: string - /** The minimum length of the text the user has to provide */ - min_length?: number - /** The maximum length of the text the user has to provide */ - max_length?: number - /** Pre-filled value for input text. */ - value?: string -} - -/** https://discord.com/developers/docs/interactions/message-components#text-input-object-text-input-styles */ -export enum TextStyles { - /** Intended for short single-line text */ - Short = 1, - /** Intended for much longer inputs */ - Paragraph = 2, -} - /** https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-structure specifcly the member propriety */ export interface DiscordInteractionMember extends DiscordMemberWithUser { /** Total permissions of the member in the channel, including overwrites, returned when in the interaction object */ diff --git a/packages/types/src/discord/message.ts b/packages/types/src/discord/message.ts index 4a6c46502..90de20c2f 100644 --- a/packages/types/src/discord/message.ts +++ b/packages/types/src/discord/message.ts @@ -2,9 +2,10 @@ import type { DiscordApplication } from './application.js' import type { DiscordChannel, DiscordThreadMember } from './channel.js' +import type { DiscordMessageComponents } from './components.js' import type { DiscordEmoji } from './emoji.js' import type { DiscordMember } from './guild.js' -import type { DiscordAuthorizingIntegrationOwners, DiscordMessageComponents, DiscordMessageInteraction, InteractionTypes } from './interactions.js' +import type { DiscordAuthorizingIntegrationOwners, DiscordMessageInteraction, InteractionTypes } from './interactions.js' import type { DiscordPoll } from './poll.js' import type { DiscordSticker, DiscordStickerItem } from './sticker.js' import type { DiscordUser } from './user.js' @@ -193,6 +194,8 @@ export enum MessageFlags { IsVoiceMessage = 1 << 13, /** This message has a snapshot (via Message Forwarding) */ HasSnapshot = 1 << 14, + /** Allows you to create fully component driven messages */ + IsComponentV2 = 1 << 15, } /** https://discord.com/developers/docs/resources/message#message-interaction-metadata-object */ diff --git a/packages/types/src/discordeno.ts b/packages/types/src/discordeno.ts index 768721fee..04054242d 100644 --- a/packages/types/src/discordeno.ts +++ b/packages/types/src/discordeno.ts @@ -21,6 +21,14 @@ import type { SortOrderTypes, VideoQualityModes, } from './discord/channel.js' +import type { + ButtonStyles, + DiscordMediaGalleryItem, + DiscordUnfurledMediaItem, + MessageComponentTypes, + SeparatorSpacingSize, + TextStyles, +} from './discord/components.js' import type { DefaultMessageNotificationLevels, DiscordGuildOnboardingMode, @@ -38,14 +46,11 @@ import type { } from './discord/guildScheduledEvent.js' import type { ApplicationCommandTypes, - ButtonStyles, DiscordApplicationCommandOption, DiscordApplicationCommandOptionChoice, DiscordInteractionContextType, DiscordInteractionEntryPointCommandHandlerType, InteractionResponseTypes, - MessageComponentTypes, - TextStyles, } from './discord/interactions.js' import type { TargetTypes } from './discord/invite.js' import type { @@ -115,11 +120,25 @@ export type MessageComponent = | SelectMenuRolesComponent | SelectMenuUsersComponent | SelectMenuUsersAndRolesComponent + | SectionComponent + | TextDisplayComponent + | ThumbnailComponent + | MediaGalleryComponent + | SeparatorComponent + | FileComponent + +/** https://discord.com/developers/docs/components/reference#anatomy-of-a-component */ +export interface BaseComponent { + /** The type of the component */ + type: MessageComponentTypes + /** 32 bit integer used as an optional identifier for component */ + id?: number +} /** https://discord.com/developers/docs/interactions/message-components#actionrow */ -export interface ActionRow { - /** Action rows are a group of buttons. */ +export interface ActionRow extends BaseComponent { type: MessageComponentTypes.ActionRow + /** The components in this row */ components: | [Exclude] @@ -130,9 +149,9 @@ export interface ActionRow { } /** https://discord.com/developers/docs/interactions/message-components#button-object-button-structure */ -export interface ButtonComponent { - /** All button components have type 2 */ +export interface ButtonComponent extends BaseComponent { type: MessageComponentTypes.Button + /** for what the button says (max 80 characters) */ label?: string /** a dev-defined unique string sent on click (max 100 characters). type 5 Link buttons can not have a custom_id */ @@ -157,9 +176,9 @@ export interface ButtonComponent { } /** https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure */ -export interface SelectMenuComponent { - /** SelectMenu Component is of type 3 */ +export interface SelectMenuComponent extends BaseComponent { type: MessageComponentTypes.SelectMenu + /** A custom identifier for this component. Maximum 100 characters. */ customId: string /** A custom placeholder text if nothing is selected. Maximum 150 characters. */ @@ -174,9 +193,9 @@ export interface SelectMenuComponent { disabled?: boolean } -export interface SelectMenuUsersComponent { - /** SelectMenuChannels Component is of type 5 */ +export interface SelectMenuUsersComponent extends BaseComponent { type: MessageComponentTypes.SelectMenuUsers + /** A custom identifier for this component. Maximum 100 characters. */ customId: string /** A custom placeholder text if nothing is selected. Maximum 150 characters. */ @@ -194,9 +213,9 @@ export interface SelectMenuUsersComponent { disabled?: boolean } -export interface SelectMenuRolesComponent { - /** SelectMenuChannels Component is of type 6 */ +export interface SelectMenuRolesComponent extends BaseComponent { type: MessageComponentTypes.SelectMenuRoles + /** A custom identifier for this component. Maximum 100 characters. */ customId: string /** A custom placeholder text if nothing is selected. Maximum 150 characters. */ @@ -214,9 +233,9 @@ export interface SelectMenuRolesComponent { disabled?: boolean } -export interface SelectMenuUsersAndRolesComponent { - /** SelectMenuChannels Component is of type 7 */ +export interface SelectMenuUsersAndRolesComponent extends BaseComponent { type: MessageComponentTypes.SelectMenuUsersAndRoles + /** A custom identifier for this component. Maximum 100 characters. */ customId: string /** A custom placeholder text if nothing is selected. Maximum 150 characters. */ @@ -234,9 +253,9 @@ export interface SelectMenuUsersAndRolesComponent { disabled?: boolean } -export interface SelectMenuChannelsComponent { - /** SelectMenuChannels Component is of type 8 */ +export interface SelectMenuChannelsComponent extends BaseComponent { type: MessageComponentTypes.SelectMenuChannels + /** A custom identifier for this component. Maximum 100 characters. */ customId: string /** A custom placeholder text if nothing is selected. Maximum 150 characters. */ @@ -284,9 +303,9 @@ export interface SelectMenuDefaultValue { } /** https://discord.com/developers/docs/interactions/message-components#text-inputs-text-input-structure */ -export interface InputTextComponent { - /** InputText Component is of type 4 */ +export interface InputTextComponent extends BaseComponent { type: MessageComponentTypes.InputText + /** The style of the InputText */ style: TextStyles /** The customId of the InputText */ @@ -305,6 +324,76 @@ export interface InputTextComponent { value?: string } +/** https://discord.com/developers/docs/components/reference#section-section-structure */ +export interface SectionComponent extends BaseComponent { + type: MessageComponentTypes.Section + + /** One to three text components */ + components: TextDisplayComponent[] + /** A thumbnail or a button component, with a future possibility of adding more compatible components */ + accessory: ButtonComponent | ThumbnailComponent +} + +/** https://discord.com/developers/docs/components/reference#text-display */ +export interface TextDisplayComponent extends BaseComponent { + type: MessageComponentTypes.TextDisplay + + /** Text that will be displayed similar to a message */ + content: string +} + +/** https://discord.com/developers/docs/components/reference#thumbnail */ +export interface ThumbnailComponent extends BaseComponent { + type: MessageComponentTypes.Thumbnail + + /** A url or attachment */ + media: DiscordUnfurledMediaItem + /** Alt text for the media */ + description?: string + /** Whether the thumbnail should be a spoiler (or blurred out). Defaults to `false` */ + spoiler?: boolean +} + +/** https://discord.com/developers/docs/components/reference#media-gallery */ +export interface MediaGalleryComponent extends BaseComponent { + type: MessageComponentTypes.MediaGallery + + /** 1 to 10 media gallery items */ + items: DiscordMediaGalleryItem[] +} + +/** https://discord.com/developers/docs/components/reference#file */ +export interface FileComponent extends BaseComponent { + type: MessageComponentTypes.File + + /** This unfurled media item is unique in that it only supports attachment references using the attachment:// syntax */ + file: DiscordUnfurledMediaItem + /** Whether the media should be a spoiler (or blurred out). Defaults to `false` */ + spoiler?: boolean +} + +/** https://discord.com/developers/docs/components/reference#separator */ +export interface SeparatorComponent extends BaseComponent { + type: MessageComponentTypes.Separator + + /** Whether a visual divider should be displayed in the component. Defaults to `true` */ + divider?: boolean + /** Size of separator padding — `1` for small padding, `2` for large padding. Defaults to `1` */ + spacing?: SeparatorSpacingSize +} + +/** https://discord.com/developers/docs/components/reference#container */ +export interface ContainerComponent extends BaseComponent { + type: MessageComponentTypes.Container + + /** Up to 10 components of the type action row, text display, section, media gallery, separator, or file */ + components: Array + /** Color for the accent on the container as RGB from 0x000000 to 0xFFFFFF */ + accentColor?: number + /** Whether the container should be a spoiler (or blurred out). Defaults to `false` */ + spoiler?: boolean +} + /** https://discord.com/developers/docs/resources/channel#allowed-mentions-object */ export interface AllowedMentions { /** An array of allowed mention types to parse from the content. */ @@ -629,8 +718,7 @@ export interface RequestGuildMembers { nonce?: string } -/** https://discord.com/developers/docs/topics/gateway#request-guild-members */ - +/** https://discord.com/developers/docs/resources/guild#create-guild-channel */ export interface CreateGuildChannel { /** Channel name (1-100 characters) */ name: string diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index f21b1e48a..72fad63fc 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -3,6 +3,7 @@ export * from './discord/applicationRoleConnectionMetadata.js' export * from './discord/auditLog.js' export * from './discord/autoModeration.js' export * from './discord/channel.js' +export * from './discord/components.js' export * from './discord/emoji.js' export * from './discord/entitlement.js' export * from './discord/gateway.js'