mirror of
https://github.com/discordjs/discord.js.git
synced 2026-09-17 08:47:27 +00:00
feat: file types for builders
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { APIFileUploadComponent } from 'discord-api-types/v10';
|
||||
import type { APIFileUploadComponent, FileUploadType } from 'discord-api-types/v10';
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { FileUploadBuilder } from '../../src/components/fileUpload/FileUpload.js';
|
||||
@@ -13,6 +13,16 @@ describe('File Upload Components', () => {
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() => new FileUploadBuilder().setCustomId('file_upload').setRequired(false).toJSON()).not.toThrowError();
|
||||
expect(() =>
|
||||
new FileUploadBuilder().setCustomId('file_upload').setFileTypes('audio', 'image', 'video', '.pdf').toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('File types can be added.', () => {
|
||||
expect(
|
||||
new FileUploadBuilder().setCustomId('file_upload').addFileTypes('image').addFileTypes(['.pdf']).toJSON()
|
||||
.file_types,
|
||||
).toEqual(['image', '.pdf']);
|
||||
});
|
||||
|
||||
test('Invalid builder does throw.', () => {
|
||||
@@ -26,6 +36,29 @@ describe('File Upload Components', () => {
|
||||
).toThrowError();
|
||||
|
||||
expect(() => new FileUploadBuilder().setRequired(false).toJSON()).toThrowError();
|
||||
expect(() =>
|
||||
new FileUploadBuilder()
|
||||
.setCustomId('file_upload')
|
||||
.setFileTypes(Array.from({ length: 11 }, () => '.txt' as const))
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
|
||||
for (const invalidFileType of ['document', 'pdf', '.']) {
|
||||
expect(() =>
|
||||
new FileUploadBuilder()
|
||||
.setCustomId('file_upload')
|
||||
.setFileTypes(invalidFileType as FileUploadType)
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
}
|
||||
|
||||
expect(() =>
|
||||
new FileUploadBuilder({
|
||||
type: ComponentType.FileUpload,
|
||||
custom_id: 'file_upload',
|
||||
file_types: ['document' as FileUploadType],
|
||||
}).toJSON(),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('API data equals toJSON().', () => {
|
||||
@@ -34,13 +67,22 @@ describe('File Upload Components', () => {
|
||||
custom_id: 'file_upload',
|
||||
min_values: 4,
|
||||
max_values: 9,
|
||||
file_types: ['image', '.pdf'],
|
||||
required: false,
|
||||
} satisfies APIFileUploadComponent;
|
||||
|
||||
expect(new FileUploadBuilder(fileUploadData).toJSON()).toEqual(fileUploadData);
|
||||
|
||||
expect(
|
||||
new FileUploadBuilder().setCustomId('file_upload').setMinValues(4).setMaxValues(9).setRequired(false).toJSON(),
|
||||
new FileUploadBuilder()
|
||||
.setCustomId('file_upload')
|
||||
.setMinValues(4)
|
||||
.setMaxValues(9)
|
||||
.setFileTypes(fileUploadData.file_types)
|
||||
.setRequired(false)
|
||||
.toJSON(),
|
||||
).toEqual(fileUploadData);
|
||||
|
||||
expect(new FileUploadBuilder(fileUploadData).clearFileTypes().toJSON().file_types).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
type APIApplicationCommandRoleOption,
|
||||
type APIApplicationCommandStringOption,
|
||||
type APIApplicationCommandUserOption,
|
||||
type FileUploadType,
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
@@ -212,11 +213,37 @@ describe('Application Command toJSON() results', () => {
|
||||
});
|
||||
|
||||
test('GIVEN an attachment option THEN calling toJSON should return a valid JSON', () => {
|
||||
expect(getAttachmentOption().toJSON()).toEqual<APIApplicationCommandAttachmentOption>({
|
||||
expect(
|
||||
getAttachmentOption().setFileTypes(['image', '.pdf']).toJSON(),
|
||||
).toEqual<APIApplicationCommandAttachmentOption>({
|
||||
name: 'attachment',
|
||||
description: 'attachment',
|
||||
type: ApplicationCommandOptionType.Attachment,
|
||||
required: true,
|
||||
file_types: ['image', '.pdf'],
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN attachment option file types THEN they can be added', () => {
|
||||
expect(
|
||||
Reflect.get(getAttachmentOption().addFileTypes('image').addFileTypes(['.pdf']).toJSON(), 'file_types'),
|
||||
).toEqual(['image', '.pdf']);
|
||||
});
|
||||
|
||||
test('GIVEN attachment option file types THEN they can be cleared', () => {
|
||||
expect(
|
||||
Reflect.get(getAttachmentOption().setFileTypes('audio', '.ogg').clearFileTypes().toJSON(), 'file_types'),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
test('GIVEN too many attachment option file types THEN the builder should throw', () => {
|
||||
expect(() => getAttachmentOption().setFileTypes(Array.from({ length: 11 }, () => '.txt' as const))).toThrowError();
|
||||
});
|
||||
|
||||
test.each(['document', 'pdf', '.'])(
|
||||
'GIVEN invalid attachment file type %s THEN the builder should throw',
|
||||
(fileType) => {
|
||||
expect(() => getAttachmentOption().setFileTypes(fileType as FileUploadType)).toThrowError();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { s } from '@sapphire/shapeshift';
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { fileUploadTypesValidator } from '../../util/fileUpload.js';
|
||||
import { customIdValidator, idValidator } from '../Assertions.js';
|
||||
|
||||
export const fileUploadPredicate = s.object({
|
||||
@@ -8,5 +9,6 @@ export const fileUploadPredicate = s.object({
|
||||
custom_id: customIdValidator,
|
||||
min_values: s.number().greaterThanOrEqual(0).lessThanOrEqual(10).optional(),
|
||||
max_values: s.number().greaterThanOrEqual(1).lessThanOrEqual(10).optional(),
|
||||
file_types: fileUploadTypesValidator.optional(),
|
||||
required: s.boolean().optional(),
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type APIFileUploadComponent, ComponentType } from 'discord-api-types/v10';
|
||||
import { type APIFileUploadComponent, ComponentType, type FileUploadType } from 'discord-api-types/v10';
|
||||
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
|
||||
import { ComponentBuilder } from '../Component.js';
|
||||
import { fileUploadPredicate } from './Assertions.js';
|
||||
|
||||
@@ -17,6 +18,7 @@ export class FileUploadBuilder extends ComponentBuilder<APIFileUploadComponent>
|
||||
* custom_id: "file_upload",
|
||||
* min_values: 2,
|
||||
* max_values: 5,
|
||||
* file_types: ["image", ".pdf"],
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
@@ -26,7 +28,9 @@ export class FileUploadBuilder extends ComponentBuilder<APIFileUploadComponent>
|
||||
* custom_id: "file_upload",
|
||||
* min_values: 2,
|
||||
* max_values: 5,
|
||||
* }).setRequired();
|
||||
* })
|
||||
* .setFileTypes("image", ".pdf")
|
||||
* .setRequired();
|
||||
* ```
|
||||
*/
|
||||
public constructor(data: Partial<APIFileUploadComponent> = {}) {
|
||||
@@ -79,6 +83,41 @@ export class FileUploadBuilder extends ComponentBuilder<APIFileUploadComponent>
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds file types allowed in this file upload.
|
||||
*
|
||||
* @remarks
|
||||
* When specifying only extensions, include `.jpg` for image uploads and both `.mp4` and `.mov`
|
||||
* for video uploads due to mobile platform limitations.
|
||||
* @param fileTypes - The file groups or dot-prefixed extensions to allow
|
||||
*/
|
||||
public addFileTypes(...fileTypes: RestOrArray<FileUploadType>) {
|
||||
this.data.file_types ??= [];
|
||||
this.data.file_types.push(...normalizeArray(fileTypes));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file types allowed in this file upload.
|
||||
*
|
||||
* @remarks
|
||||
* When specifying only extensions, include `.jpg` for image uploads and both `.mp4` and `.mov`
|
||||
* for video uploads due to mobile platform limitations.
|
||||
* @param fileTypes - The file groups or dot-prefixed extensions to allow
|
||||
*/
|
||||
public setFileTypes(...fileTypes: RestOrArray<FileUploadType>) {
|
||||
this.data.file_types = normalizeArray(fileTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the file types allowed in this file upload.
|
||||
*/
|
||||
public clearFileTypes() {
|
||||
this.data.file_types = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this file upload is required.
|
||||
*
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { ApplicationCommandOptionType, type APIApplicationCommandAttachmentOption } from 'discord-api-types/v10';
|
||||
import {
|
||||
ApplicationCommandOptionType,
|
||||
type APIApplicationCommandAttachmentOption,
|
||||
type FileUploadType,
|
||||
} from 'discord-api-types/v10';
|
||||
import { fileUploadTypesValidator } from '../../../util/fileUpload.js';
|
||||
import { normalizeArray, type RestOrArray } from '../../../util/normalizeArray.js';
|
||||
import { ApplicationCommandOptionBase } from '../mixins/ApplicationCommandOptionBase.js';
|
||||
|
||||
/**
|
||||
@@ -10,6 +16,54 @@ export class SlashCommandAttachmentOption extends ApplicationCommandOptionBase {
|
||||
*/
|
||||
public override readonly type = ApplicationCommandOptionType.Attachment as const;
|
||||
|
||||
/**
|
||||
* The file types allowed for this attachment option.
|
||||
*/
|
||||
public readonly file_types?: FileUploadType[];
|
||||
|
||||
/**
|
||||
* Adds file types allowed for this attachment option.
|
||||
*
|
||||
* @remarks
|
||||
* When specifying only extensions, include `.jpg` for image uploads and both `.mp4` and `.mov`
|
||||
* for video uploads due to mobile platform limitations.
|
||||
* @param fileTypes - The file groups or dot-prefixed extensions to allow
|
||||
*/
|
||||
public addFileTypes(...fileTypes: RestOrArray<FileUploadType>) {
|
||||
const normalizedFileTypes = normalizeArray(fileTypes);
|
||||
fileUploadTypesValidator.parse([...(this.file_types ?? []), ...normalizedFileTypes]);
|
||||
|
||||
if (this.file_types === undefined) {
|
||||
Reflect.set(this, 'file_types', []);
|
||||
}
|
||||
|
||||
this.file_types!.push(...normalizedFileTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file types allowed for this attachment option.
|
||||
*
|
||||
* @remarks
|
||||
* When specifying only extensions, include `.jpg` for image uploads and both `.mp4` and `.mov`
|
||||
* for video uploads due to mobile platform limitations.
|
||||
* @param fileTypes - The file groups or dot-prefixed extensions to allow
|
||||
*/
|
||||
public setFileTypes(...fileTypes: RestOrArray<FileUploadType>) {
|
||||
const normalizedFileTypes = normalizeArray(fileTypes);
|
||||
fileUploadTypesValidator.parse(normalizedFileTypes);
|
||||
Reflect.set(this, 'file_types', normalizedFileTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the file types allowed for this attachment option.
|
||||
*/
|
||||
public clearFileTypes() {
|
||||
Reflect.set(this, 'file_types', undefined);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ApplicationCommandOptionBase.toJSON}
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { s } from '@sapphire/shapeshift';
|
||||
import { isValidationEnabled } from './validation.js';
|
||||
|
||||
export const fileUploadTypesValidator = s
|
||||
.array(
|
||||
s.union([
|
||||
s.literal('audio'),
|
||||
s.literal('image'),
|
||||
s.literal('video'),
|
||||
s.string().lengthGreaterThanOrEqual(2).regex(/^\./),
|
||||
]),
|
||||
)
|
||||
.lengthLessThanOrEqual(10)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
Reference in New Issue
Block a user