diff --git a/packages/discord.js/src/structures/ApplicationCommand.js b/packages/discord.js/src/structures/ApplicationCommand.js index 2f99c1ca9..4170fc7ca 100644 --- a/packages/discord.js/src/structures/ApplicationCommand.js +++ b/packages/discord.js/src/structures/ApplicationCommand.js @@ -276,6 +276,9 @@ class ApplicationCommand extends Base { * @property {ApplicationCommandOptionData[]} [options] Additional options if this option is a subcommand (group) * @property {ChannelType[]} [channelTypes] When the option type is channel, * the allowed types of channels that can be selected + * @property {FileUploadType[]} [fileTypes] When the option type is attachment, + * the allowed types of files that can be uploaded. When only using extensions, include `.jpg` + * for images and both `.mp4` and `.mov` for videos for mobile compatibility * @property {number} [minValue] The minimum value for an {@link ApplicationCommandOptionType.Integer} or * {@link ApplicationCommandOptionType.Number} option * @property {number} [maxValue] The maximum value for an {@link ApplicationCommandOptionType.Integer} or @@ -509,6 +512,7 @@ class ApplicationCommand extends Base { option.choices?.length !== existing.choices?.length || option.options?.length !== existing.options?.length || (option.channelTypes ?? option.channel_types)?.length !== existing.channelTypes?.length || + (option.fileTypes ?? option.file_types)?.length !== existing.fileTypes?.length || (option.minValue ?? option.min_value) !== existing.minValue || (option.maxValue ?? option.max_value) !== existing.maxValue || (option.minLength ?? option.min_length) !== existing.minLength || @@ -554,6 +558,13 @@ class ApplicationCommand extends Base { } } + if (existing.fileTypes) { + const newTypes = option.fileTypes ?? option.file_types; + for (const type of existing.fileTypes) { + if (!newTypes.includes(type)) return false; + } + } + if (existing.options) { return this.optionsEqual(existing.options, option.options, enforceOptionOrder); } @@ -580,6 +591,9 @@ class ApplicationCommand extends Base { * @property {ApplicationCommandOption[]} [options] Additional options if this option is a subcommand (group) * @property {ApplicationCommandOptionAllowedChannelType[]} [channelTypes] When the option type is channel, * the allowed types of channels that can be selected + * @property {FileUploadType[]} [fileTypes] When the option type is attachment, + * the allowed types of files that can be uploaded. When only using extensions, include `.jpg` + * for images and both `.mp4` and `.mov` for videos for mobile compatibility * @property {number} [minValue] The minimum value for an {@link ApplicationCommandOptionType.Integer} or * {@link ApplicationCommandOptionType.Number} option * @property {number} [maxValue] The maximum value for an {@link ApplicationCommandOptionType.Integer} or @@ -610,6 +624,7 @@ class ApplicationCommand extends Base { */ static transformOption(option, received) { const channelTypesKey = received ? 'channelTypes' : 'channel_types'; + const fileTypesKey = received ? 'fileTypes' : 'file_types'; const minValueKey = received ? 'minValue' : 'min_value'; const maxValueKey = received ? 'maxValue' : 'max_value'; const minLengthKey = received ? 'minLength' : 'min_length'; @@ -641,6 +656,7 @@ class ApplicationCommand extends Base { })), options: option.options?.map(opt => this.transformOption(opt, received)), [channelTypesKey]: option.channelTypes ?? option.channel_types, + [fileTypesKey]: option.fileTypes ?? option.file_types, [minValueKey]: option.minValue ?? option.min_value, [maxValueKey]: option.maxValue ?? option.max_value, [minLengthKey]: option.minLength ?? option.min_length, diff --git a/packages/discord.js/src/util/APITypes.js b/packages/discord.js/src/util/APITypes.js index 95091d8e3..43b1c2071 100644 --- a/packages/discord.js/src/util/APITypes.js +++ b/packages/discord.js/src/util/APITypes.js @@ -380,6 +380,11 @@ * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/EntryPointCommandHandlerType} */ +/** + * @external FileUploadType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10#FileUploadType} + */ + /** * @external ForumLayoutType * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ForumLayoutType} diff --git a/packages/discord.js/src/util/Components.js b/packages/discord.js/src/util/Components.js index 52c453ed6..b52743589 100644 --- a/packages/discord.js/src/util/Components.js +++ b/packages/discord.js/src/util/Components.js @@ -78,6 +78,8 @@ const getUserSelectMenuComponent = lazy( * @property {string} customId The custom id of the file upload * @property {number} [minValues] The minimum number of files that must be uploaded (0-10) * @property {number} [maxValues] The maximum number of files that can be uploaded (1-10) + * @property {FileUploadType[]} [fileTypes] The allowed types of files that can be uploaded (maximum of 10). + * When only using extensions, include `.jpg` for images and both `.mp4` and `.mov` for videos for mobile compatibility * @property {boolean} [required] Whether this component is required in modals */ diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 1ca5ebe90..0472b2180 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -127,6 +127,7 @@ import { EmbedType, EntitlementType, EntryPointCommandHandlerType, + FileUploadType, FormattingPatterns, ForumLayoutType, GatewayActivity, @@ -5120,7 +5121,14 @@ export interface ApplicationCommandMentionableOption extends BaseApplicationComm type: ApplicationCommandOptionType.Mentionable; } +export interface ApplicationCommandAttachmentOptionData extends BaseApplicationCommandOptionsData { + fileTypes?: readonly FileUploadType[]; + file_types?: readonly FileUploadType[]; + type: ApplicationCommandOptionType.Attachment; +} + export interface ApplicationCommandAttachmentOption extends BaseApplicationCommandOptionsData { + fileTypes?: readonly FileUploadType[]; type: ApplicationCommandOptionType.Attachment; } @@ -5232,14 +5240,15 @@ export interface ApplicationCommandSubCommand extends CommonBaseApplicationComma } export interface ApplicationCommandNonOptionsData extends BaseApplicationCommandOptionsData { - type: CommandOptionNonChoiceResolvableType; + type: Exclude; } export interface ApplicationCommandNonOptions extends BaseApplicationCommandOptionsData { - type: Exclude; + type: Exclude; } export type ApplicationCommandOptionData = + | ApplicationCommandAttachmentOptionData | ApplicationCommandAutocompleteNumericOptionData | ApplicationCommandAutocompleteStringOptionData | ApplicationCommandBooleanOptionData @@ -6943,6 +6952,7 @@ export interface TextInputComponentData extends BaseComponentData { export interface FileUploadComponentData extends BaseComponentData { customId: string; + fileTypes?: readonly FileUploadType[]; maxValues?: number; minValues?: number; required?: boolean; diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index a3602b2ec..18007c00a 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -62,6 +62,8 @@ import type { AnnouncementChannel, AnyThreadChannel, ApplicationCommand, + ApplicationCommandAttachmentOption, + ApplicationCommandAttachmentOptionData, ApplicationCommandChannelOption, ApplicationCommandChannelOptionData, ApplicationCommandChoicesData, @@ -116,6 +118,7 @@ import type { FetchedThreadsMore, FetchPinnedMessagesResponse, FileComponentData, + FileUploadComponentData, ForumChannel, Guild, GuildApplicationCommandManager, @@ -1668,6 +1671,21 @@ declare const applicationCommandChannelOption: ApplicationCommandChannelOption; applicationCommandChannelOption.channelTypes = [] as const; } +declare const applicationCommandAttachmentOptionData: ApplicationCommandAttachmentOptionData; +declare const applicationCommandAttachmentOption: ApplicationCommandAttachmentOption; +{ + applicationCommandAttachmentOptionData.fileTypes = ['image', '.pdf'] as const; + applicationCommandAttachmentOptionData.file_types = ['video', '.mov'] as const; + applicationCommandAttachmentOption.fileTypes = ['audio', '.flac'] as const; + + expectNotAssignable({ + description: 'Upload a file', + fileTypes: ['pdf'], + name: 'file', + type: ApplicationCommandOptionType.Attachment, + }); +} + declare const applicationNonChoiceOptionData: ApplicationCommandOptionData & { type: CommandOptionNonChoiceResolvableType; }; @@ -2601,6 +2619,15 @@ await chatInputInteraction.showModal({ type: ComponentType.Label, label: 'yo', }, + { + component: { + type: ComponentType.FileUpload, + custom_id: 'upload', + file_types: ['image', '.pdf'], + }, + type: ComponentType.Label, + label: 'upload', + }, ], }); @@ -2642,9 +2669,24 @@ await chatInputInteraction.showModal({ }, label: 'cc', }, + { + type: ComponentType.Label, + component: { + type: ComponentType.FileUpload, + customId: 'upload', + fileTypes: ['video', '.mp4', '.mov'], + }, + label: 'upload', + }, ], }); +expectNotAssignable({ + customId: 'upload', + fileTypes: ['pdf'], + type: ComponentType.FileUpload, +}); + declare const stringSelectMenuComp: StringSelectMenuComponent; new StringSelectMenuBuilder(stringSelectMenuComp.toJSON());