From 5867d11415b8d8fff5971757b7cff81ebfc14f09 Mon Sep 17 00:00:00 2001 From: Fleny Date: Tue, 28 Oct 2025 20:26:07 +0100 Subject: [PATCH] feat(components): File upload component for modals (#4468) * feat(components): File upload component for modals * Update comments upstream has been merged into the file uploads pr * reverse transform, comment typo fix --- packages/bot/src/transformers/component.ts | 25 ++++++++++ .../bot/src/transformers/reverse/component.ts | 14 ++++++ packages/types/src/discord/components.ts | 49 ++++++++++++++++++- packages/types/src/discordeno/components.ts | 29 +++++++++++ 4 files changed, 116 insertions(+), 1 deletion(-) diff --git a/packages/bot/src/transformers/component.ts b/packages/bot/src/transformers/component.ts index 2a31fa7be..aeb4b74a4 100644 --- a/packages/bot/src/transformers/component.ts +++ b/packages/bot/src/transformers/component.ts @@ -3,6 +3,8 @@ import { type DiscordButtonComponent, type DiscordContainerComponent, type DiscordFileComponent, + type DiscordFileUploadComponent, + type DiscordFileUploadInteractionResponse, type DiscordLabelComponent, type DiscordLabelInteractionResponse, type DiscordMediaGalleryComponent, @@ -69,6 +71,9 @@ export function transformComponent(bot: Bot, payload: DiscordMessageComponent | case MessageComponentTypes.Label: component = transformLabelComponent(bot, payload) break + case MessageComponentTypes.FileUpload: + component = transformFileUploadComponent(bot, payload) + break } return bot.transformers.customizers.component(bot, payload, component) @@ -294,3 +299,23 @@ function transformLabelComponent(bot: Bot, payload: DiscordLabelComponent | Disc return label } + +function transformFileUploadComponent(bot: Bot, payload: DiscordFileUploadComponent | DiscordFileUploadInteractionResponse): Component { + const props = bot.transformers.desiredProperties.component + const fileUpload = {} as Component + + if (props.type && payload.type) fileUpload.type = payload.type + if (props.id && payload.id) fileUpload.id = payload.id + if (props.customId && payload.custom_id) fileUpload.customId = payload.custom_id + + // Check that this is a response + if ('values' in payload) { + if (props.values && payload.values) fileUpload.values = payload.values + } else { + if (props.minValues && payload.min_values) fileUpload.minValues = payload.min_values + if (props.maxValues && payload.max_values) fileUpload.maxValues = payload.max_values + if (props.required && payload.required) fileUpload.required = payload.required + } + + return fileUpload +} diff --git a/packages/bot/src/transformers/reverse/component.ts b/packages/bot/src/transformers/reverse/component.ts index be7ccbe21..995b464ec 100644 --- a/packages/bot/src/transformers/reverse/component.ts +++ b/packages/bot/src/transformers/reverse/component.ts @@ -4,6 +4,7 @@ import { type DiscordButtonComponent, type DiscordContainerComponent, type DiscordFileComponent, + type DiscordFileUploadComponent, type DiscordLabelComponent, type DiscordMediaGalleryComponent, type DiscordMediaGalleryItem, @@ -53,6 +54,8 @@ export function transformComponentToDiscordComponent( return transformThumbnailComponent(bot, payload) case MessageComponentTypes.Label: return transformLabelComponent(bot, payload) + case MessageComponentTypes.FileUpload: + return transformFileUploadComponent(bot, payload) case MessageComponentTypes.Separator: case MessageComponentTypes.TextDisplay: // As of now they are compatible @@ -223,3 +226,14 @@ function transformLabelComponent(bot: Bot, payload: Component): DiscordLabelComp component: bot.transformers.reverse.component(bot, payload.component!) as DiscordLabelComponent['component'], } } + +function transformFileUploadComponent(bot: Bot, payload: Component): DiscordFileUploadComponent { + return { + type: MessageComponentTypes.FileUpload, + id: payload.id, + custom_id: payload.customId!, + max_values: payload.maxValues, + min_values: payload.minValues, + required: payload.required, + } +} diff --git a/packages/types/src/discord/components.ts b/packages/types/src/discord/components.ts index 736964367..80c988239 100644 --- a/packages/types/src/discord/components.ts +++ b/packages/types/src/discord/components.ts @@ -39,6 +39,8 @@ export enum MessageComponentTypes { Container = 17, /** Container associating a label and description with a component */ Label, + /** Component for uploading files */ + FileUpload, } export type DiscordMessageComponents = DiscordMessageComponent[] @@ -55,6 +57,7 @@ export type DiscordMessageComponent = | DiscordContainerComponent | DiscordFileComponent | DiscordLabelComponent + | DiscordFileUploadComponent export type DiscordMessageComponentFromModalInteractionResponse = | DiscordTextInputInteractionResponse @@ -547,7 +550,7 @@ export interface DiscordLabelComponent extends DiscordBaseComponent { */ description?: string /** The component within the label */ - component: DiscordTextInputComponent | DiscordSelectMenuComponent + component: DiscordTextInputComponent | DiscordSelectMenuComponent | DiscordFileUploadComponent } /** https://discord.com/developers/docs/components/reference#label-label-interaction-response-structure */ @@ -563,6 +566,50 @@ export interface DiscordLabelInteractionResponse { | DiscordRoleSelectInteractionResponseFromModal | DiscordMentionableSelectInteractionResponseFromModal | DiscordChannelSelectInteractionResponseFromModal + | DiscordFileUploadInteractionResponse +} + +/** https://discord.com/developers/docs/components/reference#file-upload-file-upload-structure */ +export interface DiscordFileUploadComponent extends DiscordBaseComponent { + type: MessageComponentTypes.FileUpload + + /** The custom id for the file upload */ + custom_id: string + /** + * The minimum number of files that must be uploaded + * + * @remarks + * Between 0-10 + * + * @default 1 + */ + min_values?: number + /** The maximum number of files that can be uploaded + * + * @remarks + * Between 1-10 + * + * @default 1 + */ + max_values?: number + /** + * Whether this component is required to be filled + * + * @default true + */ + required?: boolean +} + +/** https://discord.com/developers/docs/components/reference#file-upload-file-upload-interaction-response-structure */ +export interface DiscordFileUploadInteractionResponse { + type: MessageComponentTypes.FileUpload + + /** Unique identifier for the component */ + id: number + /** The custom id for the file upload */ + custom_id: string + /** IDs of the uploaded files found in the resolved data */ + values: string[] } /** https://discord.com/developers/docs/components/reference#unfurled-media-item-structure */ diff --git a/packages/types/src/discordeno/components.ts b/packages/types/src/discordeno/components.ts index cdd362ef5..db9853d02 100644 --- a/packages/types/src/discordeno/components.ts +++ b/packages/types/src/discordeno/components.ts @@ -29,6 +29,7 @@ export type MessageComponent = | ContainerComponent | FileComponent | LabelComponent + | FileUploadComponent /** https://discord.com/developers/docs/components/reference#anatomy-of-a-component */ export interface BaseComponent { @@ -439,4 +440,32 @@ export interface LabelComponent extends BaseComponent { | RoleSelectComponent | MentionableSelectComponent | ChannelSelectComponent + | FileUploadComponent +} + +/** https://discord.com/developers/docs/components/reference#file-upload-file-upload-structure */ +export interface FileUploadComponent extends BaseComponent { + type: MessageComponentTypes.FileUpload + + /** The custom id for the file upload */ + customId: string + /** + * The minimum number of files that must be uploaded + * + * @remarks + * Between 0-10 + */ + minValues?: number + /** The maximum number of files that can be uploaded + * + * @remarks + * Between 1-10 + */ + maxValues?: number + /** + * Whether this component is required to be filled + * + * @default true + */ + required?: boolean }