mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-18 12:33:30 +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/formatters": "workspace:^",
|
||||||
"@discordjs/util": "workspace:^",
|
"@discordjs/util": "workspace:^",
|
||||||
"@sapphire/shapeshift": "^4.0.0",
|
"@sapphire/shapeshift": "^4.0.0",
|
||||||
"discord-api-types": "^0.38.26",
|
"discord-api-types": "^0.38.31",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"ts-mixer": "^6.0.4",
|
"ts-mixer": "^6.0.4",
|
||||||
"tslib": "^2.6.3"
|
"tslib": "^2.6.3"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
} from './ActionRow.js';
|
} from './ActionRow.js';
|
||||||
import { ComponentBuilder } from './Component.js';
|
import { ComponentBuilder } from './Component.js';
|
||||||
import { ButtonBuilder } from './button/Button.js';
|
import { ButtonBuilder } from './button/Button.js';
|
||||||
|
import { FileUploadBuilder } from './fileUpload/FileUpload.js';
|
||||||
import { LabelBuilder } from './label/Label.js';
|
import { LabelBuilder } from './label/Label.js';
|
||||||
import { ChannelSelectMenuBuilder } from './selectMenu/ChannelSelectMenu.js';
|
import { ChannelSelectMenuBuilder } from './selectMenu/ChannelSelectMenu.js';
|
||||||
import { MentionableSelectMenuBuilder } from './selectMenu/MentionableSelectMenu.js';
|
import { MentionableSelectMenuBuilder } from './selectMenu/MentionableSelectMenu.js';
|
||||||
@@ -105,6 +106,10 @@ export interface MappedComponentTypes {
|
|||||||
* The label component type is associated with a {@link LabelBuilder}.
|
* The label component type is associated with a {@link LabelBuilder}.
|
||||||
*/
|
*/
|
||||||
[ComponentType.Label]: 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);
|
return new MediaGalleryBuilder(data);
|
||||||
case ComponentType.Label:
|
case ComponentType.Label:
|
||||||
return new LabelBuilder(data);
|
return new LabelBuilder(data);
|
||||||
|
case ComponentType.FileUpload:
|
||||||
|
return new FileUploadBuilder(data);
|
||||||
default:
|
default:
|
||||||
// @ts-expect-error This case can still occur if we get a newer unsupported component type
|
// @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}`);
|
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 { ComponentType } from 'discord-api-types/v10';
|
||||||
import { isValidationEnabled } from '../../util/validation.js';
|
import { isValidationEnabled } from '../../util/validation.js';
|
||||||
import { idValidator } from '../Assertions.js';
|
import { idValidator } from '../Assertions.js';
|
||||||
|
import { fileUploadPredicate } from '../fileUpload/Assertions.js';
|
||||||
import {
|
import {
|
||||||
selectMenuChannelPredicate,
|
selectMenuChannelPredicate,
|
||||||
selectMenuMentionablePredicate,
|
selectMenuMentionablePredicate,
|
||||||
@@ -24,6 +25,7 @@ export const labelPredicate = s
|
|||||||
selectMenuMentionablePredicate,
|
selectMenuMentionablePredicate,
|
||||||
selectMenuChannelPredicate,
|
selectMenuChannelPredicate,
|
||||||
selectMenuStringPredicate,
|
selectMenuStringPredicate,
|
||||||
|
fileUploadPredicate,
|
||||||
]),
|
]),
|
||||||
})
|
})
|
||||||
.setValidationEnabled(isValidationEnabled);
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type {
|
import type {
|
||||||
APIChannelSelectComponent,
|
APIChannelSelectComponent,
|
||||||
|
APIFileUploadComponent,
|
||||||
APILabelComponent,
|
APILabelComponent,
|
||||||
APIMentionableSelectComponent,
|
APIMentionableSelectComponent,
|
||||||
APIRoleSelectComponent,
|
APIRoleSelectComponent,
|
||||||
@@ -10,6 +11,7 @@ import type {
|
|||||||
import { ComponentType } from 'discord-api-types/v10';
|
import { ComponentType } from 'discord-api-types/v10';
|
||||||
import { ComponentBuilder } from '../Component.js';
|
import { ComponentBuilder } from '../Component.js';
|
||||||
import { createComponentBuilder, resolveBuilder } from '../Components.js';
|
import { createComponentBuilder, resolveBuilder } from '../Components.js';
|
||||||
|
import { FileUploadBuilder } from '../fileUpload/FileUpload.js';
|
||||||
import { ChannelSelectMenuBuilder } from '../selectMenu/ChannelSelectMenu.js';
|
import { ChannelSelectMenuBuilder } from '../selectMenu/ChannelSelectMenu.js';
|
||||||
import { MentionableSelectMenuBuilder } from '../selectMenu/MentionableSelectMenu.js';
|
import { MentionableSelectMenuBuilder } from '../selectMenu/MentionableSelectMenu.js';
|
||||||
import { RoleSelectMenuBuilder } from '../selectMenu/RoleSelectMenu.js';
|
import { RoleSelectMenuBuilder } from '../selectMenu/RoleSelectMenu.js';
|
||||||
@@ -21,6 +23,7 @@ import { labelPredicate } from './Assertions.js';
|
|||||||
export interface LabelBuilderData extends Partial<Omit<APILabelComponent, 'component'>> {
|
export interface LabelBuilderData extends Partial<Omit<APILabelComponent, 'component'>> {
|
||||||
component?:
|
component?:
|
||||||
| ChannelSelectMenuBuilder
|
| ChannelSelectMenuBuilder
|
||||||
|
| FileUploadBuilder
|
||||||
| MentionableSelectMenuBuilder
|
| MentionableSelectMenuBuilder
|
||||||
| RoleSelectMenuBuilder
|
| RoleSelectMenuBuilder
|
||||||
| StringSelectMenuBuilder
|
| StringSelectMenuBuilder
|
||||||
@@ -179,6 +182,18 @@ export class LabelBuilder extends ComponentBuilder<LabelBuilderData> {
|
|||||||
return this;
|
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}
|
* {@inheritDoc ComponentBuilder.toJSON}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ export {
|
|||||||
export * from './components/selectMenu/StringSelectMenuOption.js';
|
export * from './components/selectMenu/StringSelectMenuOption.js';
|
||||||
export * from './components/selectMenu/UserSelectMenu.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 * from './components/label/Label.js';
|
||||||
export * as LabelAssertions from './components/label/Assertions.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
|
specifier: ^4.0.0
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
discord-api-types:
|
discord-api-types:
|
||||||
specifier: ^0.38.26
|
specifier: ^0.38.31
|
||||||
version: 0.38.26
|
version: 0.38.31
|
||||||
fast-deep-equal:
|
fast-deep-equal:
|
||||||
specifier: ^3.1.3
|
specifier: ^3.1.3
|
||||||
version: 3.1.3
|
version: 3.1.3
|
||||||
@@ -7654,8 +7654,8 @@ packages:
|
|||||||
discord-api-types@0.38.16:
|
discord-api-types@0.38.16:
|
||||||
resolution: {integrity: sha512-Cz42dC5WqJD17Yk0bRy7YLTJmh3NKo4FGpxZuA8MHqT0RPxKSrll5YhlODZ2z5DiEV/gpHMeTSrTFTWpSXjT1Q==}
|
resolution: {integrity: sha512-Cz42dC5WqJD17Yk0bRy7YLTJmh3NKo4FGpxZuA8MHqT0RPxKSrll5YhlODZ2z5DiEV/gpHMeTSrTFTWpSXjT1Q==}
|
||||||
|
|
||||||
discord-api-types@0.38.26:
|
discord-api-types@0.38.31:
|
||||||
resolution: {integrity: sha512-xpmPviHjIJ6dFu1eNwNDIGQ3N6qmPUUYFVAx/YZ64h7ZgPkTcKjnciD8bZe8Vbeji7yS5uYljyciunpq0J5NSw==}
|
resolution: {integrity: sha512-kC94ANsk8ackj8ENTuO8joTNEL0KtymVhHy9dyEC/s4QAZ7GCx40dYEzQaadyo8w+oP0X8QydE/nzAWRylTGtQ==}
|
||||||
|
|
||||||
dlv@1.1.3:
|
dlv@1.1.3:
|
||||||
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
||||||
@@ -21553,7 +21553,7 @@ snapshots:
|
|||||||
|
|
||||||
discord-api-types@0.38.16: {}
|
discord-api-types@0.38.16: {}
|
||||||
|
|
||||||
discord-api-types@0.38.26: {}
|
discord-api-types@0.38.31: {}
|
||||||
|
|
||||||
dlv@1.1.3: {}
|
dlv@1.1.3: {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user