mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-10 16:43:31 +01:00
feat: v1 builders file uploads support (#11196)
* feat: support file uploads * test: add tests * style: sort imports * feat(Label): add method
This commit is contained in:
46
packages/builders/__tests__/components/fileUpload.test.ts
Normal file
46
packages/builders/__tests__/components/fileUpload.test.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import type { APIFileUploadComponent } 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';
|
||||
|
||||
describe('File Upload Components', () => {
|
||||
test('Valid builder does not throw.', () => {
|
||||
expect(() => new FileUploadBuilder().setCustomId('file_upload').toJSON()).not.toThrowError();
|
||||
expect(() => new FileUploadBuilder().setCustomId('file_upload').setId(5).toJSON()).not.toThrowError();
|
||||
|
||||
expect(() =>
|
||||
new FileUploadBuilder().setCustomId('file_upload').setMaxValues(5).setMinValues(2).toJSON(),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() => new FileUploadBuilder().setCustomId('file_upload').setRequired(false).toJSON()).not.toThrowError();
|
||||
});
|
||||
|
||||
test('Invalid builder does throw.', () => {
|
||||
expect(() => new FileUploadBuilder().toJSON()).toThrowError();
|
||||
expect(() => new FileUploadBuilder().setCustomId('file_upload').setId(-3).toJSON()).toThrowError();
|
||||
expect(() => new FileUploadBuilder().setMaxValues(5).setMinValues(2).setId(10).toJSON()).toThrowError();
|
||||
expect(() => new FileUploadBuilder().setCustomId('file_upload').setMaxValues(500).toJSON()).toThrowError();
|
||||
|
||||
expect(() =>
|
||||
new FileUploadBuilder().setCustomId('file_upload').setMinValues(500).setMaxValues(501).toJSON(),
|
||||
).toThrowError();
|
||||
|
||||
expect(() => new FileUploadBuilder().setRequired(false).toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('API data equals toJSON().', () => {
|
||||
const fileUploadData = {
|
||||
type: ComponentType.FileUpload,
|
||||
custom_id: 'file_upload',
|
||||
min_values: 4,
|
||||
max_values: 9,
|
||||
required: false,
|
||||
} satisfies APIFileUploadComponent;
|
||||
|
||||
expect(new FileUploadBuilder(fileUploadData).toJSON()).toEqual(fileUploadData);
|
||||
|
||||
expect(
|
||||
new FileUploadBuilder().setCustomId('file_upload').setMinValues(4).setMaxValues(9).setRequired(false).toJSON(),
|
||||
).toEqual(fileUploadData);
|
||||
});
|
||||
});
|
||||
@@ -68,7 +68,7 @@
|
||||
"@discordjs/formatters": "workspace:^",
|
||||
"@discordjs/util": "workspace:^",
|
||||
"@sapphire/shapeshift": "^4.0.0",
|
||||
"discord-api-types": "^0.38.26",
|
||||
"discord-api-types": "^0.38.31",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"ts-mixer": "^6.0.4",
|
||||
"tslib": "^2.6.3"
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from './ActionRow.js';
|
||||
import { ComponentBuilder } from './Component.js';
|
||||
import { ButtonBuilder } from './button/Button.js';
|
||||
import { FileUploadBuilder } from './fileUpload/FileUpload.js';
|
||||
import { LabelBuilder } from './label/Label.js';
|
||||
import { ChannelSelectMenuBuilder } from './selectMenu/ChannelSelectMenu.js';
|
||||
import { MentionableSelectMenuBuilder } from './selectMenu/MentionableSelectMenu.js';
|
||||
@@ -105,6 +106,10 @@ export interface MappedComponentTypes {
|
||||
* The label component type is associated with a {@link LabelBuilder}.
|
||||
*/
|
||||
[ComponentType.Label]: LabelBuilder;
|
||||
/**
|
||||
* The file upload component type is associated with a {@link FileUploadBuilder}.
|
||||
*/
|
||||
[ComponentType.FileUpload]: FileUploadBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,6 +173,8 @@ export function createComponentBuilder(
|
||||
return new MediaGalleryBuilder(data);
|
||||
case ComponentType.Label:
|
||||
return new LabelBuilder(data);
|
||||
case ComponentType.FileUpload:
|
||||
return new FileUploadBuilder(data);
|
||||
default:
|
||||
// @ts-expect-error This case can still occur if we get a newer unsupported component type
|
||||
throw new Error(`Cannot properly serialize component type: ${data.type}`);
|
||||
|
||||
12
packages/builders/src/components/fileUpload/Assertions.ts
Normal file
12
packages/builders/src/components/fileUpload/Assertions.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { s } from '@sapphire/shapeshift';
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { customIdValidator, idValidator } from '../Assertions.js';
|
||||
|
||||
export const fileUploadPredicate = s.object({
|
||||
type: s.literal(ComponentType.FileUpload),
|
||||
id: idValidator.optional(),
|
||||
custom_id: customIdValidator,
|
||||
min_values: s.number().greaterThanOrEqual(0).lessThanOrEqual(10).optional(),
|
||||
max_values: s.number().greaterThanOrEqual(1).lessThanOrEqual(10).optional(),
|
||||
required: s.boolean().optional(),
|
||||
});
|
||||
99
packages/builders/src/components/fileUpload/FileUpload.ts
Normal file
99
packages/builders/src/components/fileUpload/FileUpload.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { type APIFileUploadComponent, ComponentType } from 'discord-api-types/v10';
|
||||
import { ComponentBuilder } from '../Component.js';
|
||||
import { fileUploadPredicate } from './Assertions.js';
|
||||
|
||||
/**
|
||||
* A builder that creates API-compatible JSON data for file uploads.
|
||||
*/
|
||||
export class FileUploadBuilder extends ComponentBuilder<APIFileUploadComponent> {
|
||||
/**
|
||||
* Creates a new file upload.
|
||||
*
|
||||
* @param data - The API data to create this file upload with
|
||||
* @example
|
||||
* Creating a file upload from an API data object:
|
||||
* ```ts
|
||||
* const fileUpload = new FileUploadBuilder({
|
||||
* custom_id: "file_upload",
|
||||
* min_values: 2,
|
||||
* max_values: 5,
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a file upload using setters and API data:
|
||||
* ```ts
|
||||
* const fileUpload = new FileUploadBuilder({
|
||||
* custom_id: "file_upload",
|
||||
* min_values: 2,
|
||||
* max_values: 5,
|
||||
* }).setRequired();
|
||||
* ```
|
||||
*/
|
||||
public constructor(data: Partial<APIFileUploadComponent> = {}) {
|
||||
super({ type: ComponentType.FileUpload, ...data });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom id for this file upload.
|
||||
*
|
||||
* @param customId - The custom id to use
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
this.data.custom_id = customId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of file uploads required.
|
||||
*
|
||||
* @param minValues - The minimum values that must be uploaded
|
||||
*/
|
||||
public setMinValues(minValues: number) {
|
||||
this.data.min_values = minValues;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the minimum values.
|
||||
*/
|
||||
public clearMinValues() {
|
||||
this.data.min_values = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of file uploads required.
|
||||
*
|
||||
* @param maxValues - The maximum values that must be uploaded
|
||||
*/
|
||||
public setMaxValues(maxValues: number) {
|
||||
this.data.max_values = maxValues;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the maximum values.
|
||||
*/
|
||||
public clearMaxValues() {
|
||||
this.data.max_values = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this file upload is required.
|
||||
*
|
||||
* @param required - Whether this file upload is required
|
||||
*/
|
||||
public setRequired(required = true) {
|
||||
this.data.required = required;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ComponentBuilder.toJSON}
|
||||
*/
|
||||
public toJSON(): APIFileUploadComponent {
|
||||
fileUploadPredicate.parse(this.data);
|
||||
return this.data as APIFileUploadComponent;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { s } from '@sapphire/shapeshift';
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { isValidationEnabled } from '../../util/validation.js';
|
||||
import { idValidator } from '../Assertions.js';
|
||||
import { fileUploadPredicate } from '../fileUpload/Assertions.js';
|
||||
import {
|
||||
selectMenuChannelPredicate,
|
||||
selectMenuMentionablePredicate,
|
||||
@@ -24,6 +25,7 @@ export const labelPredicate = s
|
||||
selectMenuMentionablePredicate,
|
||||
selectMenuChannelPredicate,
|
||||
selectMenuStringPredicate,
|
||||
fileUploadPredicate,
|
||||
]),
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type {
|
||||
APIChannelSelectComponent,
|
||||
APIFileUploadComponent,
|
||||
APILabelComponent,
|
||||
APIMentionableSelectComponent,
|
||||
APIRoleSelectComponent,
|
||||
@@ -10,6 +11,7 @@ import type {
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { ComponentBuilder } from '../Component.js';
|
||||
import { createComponentBuilder, resolveBuilder } from '../Components.js';
|
||||
import { FileUploadBuilder } from '../fileUpload/FileUpload.js';
|
||||
import { ChannelSelectMenuBuilder } from '../selectMenu/ChannelSelectMenu.js';
|
||||
import { MentionableSelectMenuBuilder } from '../selectMenu/MentionableSelectMenu.js';
|
||||
import { RoleSelectMenuBuilder } from '../selectMenu/RoleSelectMenu.js';
|
||||
@@ -21,6 +23,7 @@ import { labelPredicate } from './Assertions.js';
|
||||
export interface LabelBuilderData extends Partial<Omit<APILabelComponent, 'component'>> {
|
||||
component?:
|
||||
| ChannelSelectMenuBuilder
|
||||
| FileUploadBuilder
|
||||
| MentionableSelectMenuBuilder
|
||||
| RoleSelectMenuBuilder
|
||||
| StringSelectMenuBuilder
|
||||
@@ -179,6 +182,18 @@ export class LabelBuilder extends ComponentBuilder<LabelBuilderData> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a file upload component to this label.
|
||||
*
|
||||
* @param input - A function that returns a component builder or an already built builder
|
||||
*/
|
||||
public setFileUploadComponent(
|
||||
input: APIFileUploadComponent | FileUploadBuilder | ((builder: FileUploadBuilder) => FileUploadBuilder),
|
||||
): this {
|
||||
this.data.component = resolveBuilder(input, FileUploadBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ComponentBuilder.toJSON}
|
||||
*/
|
||||
|
||||
@@ -34,6 +34,9 @@ export {
|
||||
export * from './components/selectMenu/StringSelectMenuOption.js';
|
||||
export * from './components/selectMenu/UserSelectMenu.js';
|
||||
|
||||
export * from './components/fileUpload/FileUpload.js';
|
||||
export * as FileUploadAssertions from './components/fileUpload/Assertions.js';
|
||||
|
||||
export * from './components/label/Label.js';
|
||||
export * as LabelAssertions from './components/label/Assertions.js';
|
||||
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -680,8 +680,8 @@ importers:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
discord-api-types:
|
||||
specifier: ^0.38.26
|
||||
version: 0.38.26
|
||||
specifier: ^0.38.31
|
||||
version: 0.38.31
|
||||
fast-deep-equal:
|
||||
specifier: ^3.1.3
|
||||
version: 3.1.3
|
||||
@@ -7654,8 +7654,8 @@ packages:
|
||||
discord-api-types@0.38.16:
|
||||
resolution: {integrity: sha512-Cz42dC5WqJD17Yk0bRy7YLTJmh3NKo4FGpxZuA8MHqT0RPxKSrll5YhlODZ2z5DiEV/gpHMeTSrTFTWpSXjT1Q==}
|
||||
|
||||
discord-api-types@0.38.26:
|
||||
resolution: {integrity: sha512-xpmPviHjIJ6dFu1eNwNDIGQ3N6qmPUUYFVAx/YZ64h7ZgPkTcKjnciD8bZe8Vbeji7yS5uYljyciunpq0J5NSw==}
|
||||
discord-api-types@0.38.31:
|
||||
resolution: {integrity: sha512-kC94ANsk8ackj8ENTuO8joTNEL0KtymVhHy9dyEC/s4QAZ7GCx40dYEzQaadyo8w+oP0X8QydE/nzAWRylTGtQ==}
|
||||
|
||||
dlv@1.1.3:
|
||||
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
||||
@@ -21553,7 +21553,7 @@ snapshots:
|
||||
|
||||
discord-api-types@0.38.16: {}
|
||||
|
||||
discord-api-types@0.38.26: {}
|
||||
discord-api-types@0.38.31: {}
|
||||
|
||||
dlv@1.1.3: {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user