mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
chore: Merge builders/1.x into v14 (#11260)
* chore: merge builders (and formatters) * chore: match cliff.toml * chore: update README.mds * build: discord-api-types 0.38.32
This commit is contained in:
@@ -2,6 +2,52 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
# [@discordjs/builders@1.13.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.12.2...@discordjs/builders@1.13.0) - (2025-10-24)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- V1 builders file uploads support (#11196) ([1417c49](https://github.com/discordjs/discord.js/commit/1417c498a40b843d772ecf88dfff5f87a1665042))
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- Fix type error ([f780c6a](https://github.com/discordjs/discord.js/commit/f780c6a5500f7ea5c7a1ea7cd6720f6159d9d36e))
|
||||||
|
|
||||||
|
# [@discordjs/builders@1.12.2](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.12.1...@discordjs/builders@1.12.2) - (2025-10-09)
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
- **Assertions:** Literal default values ([43362c9](https://github.com/discordjs/discord.js/commit/43362c93525f98d72b894eb0fc6b358d30ec45b9))
|
||||||
|
|
||||||
|
# [@discordjs/builders@1.12.1](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.12.0...@discordjs/builders@1.12.1) - (2025-10-08)
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
- **builders:** Text display component support for modals (#11155) ([99b8436](https://github.com/discordjs/discord.js/commit/99b8436117bc12654278337abc4a23f5bdf4ba46))
|
||||||
|
|
||||||
|
# [@discordjs/builders@1.12.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.11.3...@discordjs/builders@1.12.0) - (2025-10-08)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **builders:** Modal select menus in builders v1 (#11138) ([ac683b9](https://github.com/discordjs/discord.js/commit/ac683b9d040635de8514c80a9d433d9c6d63701b))
|
||||||
|
|
||||||
|
# [@discordjs/builders@1.11.3](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.11.2...@discordjs/builders@1.11.3) - (2025-08-10)
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
- **contextMenuCommands:** Remove regular expression validation (#10996) ([4906aae](https://github.com/discordjs/discord.js/commit/4906aaea4c0e6e868fa658d3359026eb662fbcb8))
|
||||||
|
|
||||||
|
# [@discordjs/builders@1.11.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.10.1...@discordjs/builders@1.11.0) - (2025-04-25)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Components v2 in builders v1 (#10787) ([118e682](https://github.com/discordjs/discord.js/commit/118e6826821b3b90f5923e40f167747e0658cfd1))
|
||||||
|
|
||||||
|
# [@discordjs/builders@1.10.1](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.10.0...@discordjs/builders@1.10.1) - (2025-02-10)
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
- **EmbedBuilder:** Allow empty `name` and `value` on fields (#10747) ([49ef3a8](https://github.com/discordjs/discord.js/commit/49ef3a833eab23d426d5c667e28aa493ddc9cb6c))
|
||||||
|
|
||||||
# [@discordjs/builders@1.9.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.8.2...@discordjs/builders@1.9.0) - (2024-09-01)
|
# [@discordjs/builders@1.9.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.8.2...@discordjs/builders@1.9.0) - (2024-09-01)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
**Node.js 18 or newer is required.**
|
**Node.js 16.11.0 or newer is required.**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm install @discordjs/builders
|
npm install @discordjs/builders
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
ButtonStyle,
|
ButtonStyle,
|
||||||
ComponentType,
|
ComponentType,
|
||||||
type APIActionRowComponent,
|
type APIActionRowComponent,
|
||||||
type APIMessageActionRowComponent,
|
type APIComponentInMessageActionRow,
|
||||||
} from 'discord-api-types/v10';
|
} from 'discord-api-types/v10';
|
||||||
import { describe, test, expect } from 'vitest';
|
import { describe, test, expect } from 'vitest';
|
||||||
import {
|
import {
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
StringSelectMenuOptionBuilder,
|
StringSelectMenuOptionBuilder,
|
||||||
} from '../../src/index.js';
|
} from '../../src/index.js';
|
||||||
|
|
||||||
const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
|
const rowWithButtonData: APIActionRowComponent<APIComponentInMessageActionRow> = {
|
||||||
type: ComponentType.ActionRow,
|
type: ComponentType.ActionRow,
|
||||||
components: [
|
components: [
|
||||||
{
|
{
|
||||||
@@ -25,7 +25,7 @@ const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const rowWithSelectMenuData: APIActionRowComponent<APIMessageActionRowComponent> = {
|
const rowWithSelectMenuData: APIActionRowComponent<APIComponentInMessageActionRow> = {
|
||||||
type: ComponentType.ActionRow,
|
type: ComponentType.ActionRow,
|
||||||
components: [
|
components: [
|
||||||
{
|
{
|
||||||
@@ -57,7 +57,7 @@ describe('Action Row Components', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
|
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
|
||||||
const actionRowData: APIActionRowComponent<APIMessageActionRowComponent> = {
|
const actionRowData: APIActionRowComponent<APIComponentInMessageActionRow> = {
|
||||||
type: ComponentType.ActionRow,
|
type: ComponentType.ActionRow,
|
||||||
components: [
|
components: [
|
||||||
{
|
{
|
||||||
@@ -92,7 +92,7 @@ describe('Action Row Components', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('GIVEN valid builder options THEN valid JSON output is given', () => {
|
test('GIVEN valid builder options THEN valid JSON output is given', () => {
|
||||||
const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
|
const rowWithButtonData: APIActionRowComponent<APIComponentInMessageActionRow> = {
|
||||||
type: ComponentType.ActionRow,
|
type: ComponentType.ActionRow,
|
||||||
components: [
|
components: [
|
||||||
{
|
{
|
||||||
@@ -104,7 +104,7 @@ describe('Action Row Components', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const rowWithSelectMenuData: APIActionRowComponent<APIMessageActionRowComponent> = {
|
const rowWithSelectMenuData: APIActionRowComponent<APIComponentInMessageActionRow> = {
|
||||||
type: ComponentType.ActionRow,
|
type: ComponentType.ActionRow,
|
||||||
components: [
|
components: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
ComponentType,
|
ComponentType,
|
||||||
TextInputStyle,
|
TextInputStyle,
|
||||||
type APIButtonComponent,
|
type APIButtonComponent,
|
||||||
type APIMessageActionRowComponent,
|
type APIComponentInMessageActionRow,
|
||||||
type APISelectMenuComponent,
|
type APISelectMenuComponent,
|
||||||
type APITextInputComponent,
|
type APITextInputComponent,
|
||||||
type APIActionRowComponent,
|
type APIActionRowComponent,
|
||||||
@@ -27,7 +27,7 @@ describe('createComponentBuilder', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
test('GIVEN an action row component THEN returns a ActionRowBuilder', () => {
|
test('GIVEN an action row component THEN returns a ActionRowBuilder', () => {
|
||||||
const actionRow: APIActionRowComponent<APIMessageActionRowComponent> = {
|
const actionRow: APIActionRowComponent<APIComponentInMessageActionRow> = {
|
||||||
components: [],
|
components: [],
|
||||||
type: ComponentType.ActionRow,
|
type: ComponentType.ActionRow,
|
||||||
};
|
};
|
||||||
|
|||||||
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -100,11 +100,11 @@ describe('Text Input Components', () => {
|
|||||||
.setPlaceholder('hello')
|
.setPlaceholder('hello')
|
||||||
.setStyle(TextInputStyle.Paragraph)
|
.setStyle(TextInputStyle.Paragraph)
|
||||||
.toJSON();
|
.toJSON();
|
||||||
}).toThrowError();
|
}).not.toThrowError();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GIVEN valid input THEN valid JSON outputs are given', () => {
|
test('GIVEN valid input THEN valid JSON outputs are given', () => {
|
||||||
const textInputData: APITextInputComponent = {
|
const textInputData = {
|
||||||
type: ComponentType.TextInput,
|
type: ComponentType.TextInput,
|
||||||
label: 'label',
|
label: 'label',
|
||||||
custom_id: 'custom id',
|
custom_id: 'custom id',
|
||||||
@@ -114,7 +114,7 @@ describe('Text Input Components', () => {
|
|||||||
value: 'value',
|
value: 'value',
|
||||||
required: false,
|
required: false,
|
||||||
style: TextInputStyle.Paragraph,
|
style: TextInputStyle.Paragraph,
|
||||||
};
|
} satisfies APITextInputComponent;
|
||||||
|
|
||||||
expect(new TextInputBuilder(textInputData).toJSON()).toEqual(textInputData);
|
expect(new TextInputBuilder(textInputData).toJSON()).toEqual(textInputData);
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
248
packages/builders/__tests__/components/v2/container.test.ts
Normal file
248
packages/builders/__tests__/components/v2/container.test.ts
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
import { type APIContainerComponent, ComponentType, SeparatorSpacingSize } from 'discord-api-types/v10';
|
||||||
|
import { describe, test, expect } from 'vitest';
|
||||||
|
import { ActionRowBuilder } from '../../../src/components/ActionRow.js';
|
||||||
|
import { createComponentBuilder } from '../../../src/components/Components.js';
|
||||||
|
import { ButtonBuilder } from '../../../src/components/button/Button.js';
|
||||||
|
import { ContainerBuilder } from '../../../src/components/v2/Container.js';
|
||||||
|
import { FileBuilder } from '../../../src/components/v2/File.js';
|
||||||
|
import { MediaGalleryBuilder } from '../../../src/components/v2/MediaGallery.js';
|
||||||
|
import { SectionBuilder } from '../../../src/components/v2/Section.js';
|
||||||
|
import { SeparatorBuilder } from '../../../src/components/v2/Separator.js';
|
||||||
|
import { TextDisplayBuilder } from '../../../src/components/v2/TextDisplay.js';
|
||||||
|
|
||||||
|
const containerWithTextDisplay: APIContainerComponent = {
|
||||||
|
type: ComponentType.Container,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
id: 123,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const containerWithSeparatorData: APIContainerComponent = {
|
||||||
|
type: ComponentType.Container,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.Separator,
|
||||||
|
id: 1_234,
|
||||||
|
spacing: SeparatorSpacingSize.Small,
|
||||||
|
divider: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
accent_color: 0x00ff00,
|
||||||
|
};
|
||||||
|
|
||||||
|
const containerWithSeparatorDataNoColor: APIContainerComponent = {
|
||||||
|
type: ComponentType.Container,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.Separator,
|
||||||
|
id: 1_234,
|
||||||
|
spacing: SeparatorSpacingSize.Small,
|
||||||
|
divider: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Container Components', () => {
|
||||||
|
describe('Assertion Tests', () => {
|
||||||
|
test('GIVEN valid components THEN do not throw', () => {
|
||||||
|
expect(() =>
|
||||||
|
new ContainerBuilder().addActionRowComponents(
|
||||||
|
new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder()),
|
||||||
|
),
|
||||||
|
).not.toThrowError();
|
||||||
|
expect(() => new ContainerBuilder().addFileComponents(new FileBuilder())).not.toThrowError();
|
||||||
|
expect(() => new ContainerBuilder().addMediaGalleryComponents(new MediaGalleryBuilder())).not.toThrowError();
|
||||||
|
expect(() => new ContainerBuilder().addSectionComponents(new SectionBuilder())).not.toThrowError();
|
||||||
|
expect(() => new ContainerBuilder().addSeparatorComponents(new SeparatorBuilder())).not.toThrowError();
|
||||||
|
expect(() => new ContainerBuilder().addTextDisplayComponents(new TextDisplayBuilder())).not.toThrowError();
|
||||||
|
expect(() => new ContainerBuilder().spliceComponents(0, 0, new SeparatorBuilder())).not.toThrowError();
|
||||||
|
expect(() => new ContainerBuilder().addSeparatorComponents([new SeparatorBuilder()])).not.toThrowError();
|
||||||
|
expect(() =>
|
||||||
|
new ContainerBuilder().spliceComponents(0, 0, [{ type: ComponentType.Separator }]),
|
||||||
|
).not.toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
|
||||||
|
const containerData: APIContainerComponent = {
|
||||||
|
type: ComponentType.Container,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
id: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: ComponentType.Separator,
|
||||||
|
spacing: SeparatorSpacingSize.Large,
|
||||||
|
divider: true,
|
||||||
|
id: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: ComponentType.File,
|
||||||
|
file: {
|
||||||
|
url: 'attachment://file.png',
|
||||||
|
},
|
||||||
|
spoiler: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
accent_color: 0xff00ff,
|
||||||
|
spoiler: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(new ContainerBuilder(containerData).toJSON()).toEqual(containerData);
|
||||||
|
expect(() => createComponentBuilder({ type: ComponentType.Container, components: [] })).not.toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid builder options THEN valid JSON output is given', () => {
|
||||||
|
const containerWithTextDisplay: APIContainerComponent = {
|
||||||
|
type: ComponentType.Container,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
id: 123,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const containerWithSeparatorData: APIContainerComponent = {
|
||||||
|
type: ComponentType.Container,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.Separator,
|
||||||
|
id: 1_234,
|
||||||
|
spacing: SeparatorSpacingSize.Small,
|
||||||
|
divider: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
accent_color: 0x00ff00,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(new ContainerBuilder(containerWithTextDisplay).toJSON()).toEqual(containerWithTextDisplay);
|
||||||
|
expect(new ContainerBuilder(containerWithSeparatorData).toJSON()).toEqual(containerWithSeparatorData);
|
||||||
|
expect(() => createComponentBuilder({ type: ComponentType.Container, components: [] })).not.toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid builder options THEN valid JSON output is given 2', () => {
|
||||||
|
const textDisplay = new TextDisplayBuilder().setContent('test').setId(123);
|
||||||
|
const separator = new SeparatorBuilder().setId(1_234).setSpacing(SeparatorSpacingSize.Small).setDivider(false);
|
||||||
|
|
||||||
|
expect(new ContainerBuilder().addTextDisplayComponents(textDisplay).toJSON()).toEqual(containerWithTextDisplay);
|
||||||
|
expect(new ContainerBuilder().addSeparatorComponents(separator).toJSON()).toEqual(
|
||||||
|
containerWithSeparatorDataNoColor,
|
||||||
|
);
|
||||||
|
expect(new ContainerBuilder().addTextDisplayComponents([textDisplay]).toJSON()).toEqual(containerWithTextDisplay);
|
||||||
|
expect(new ContainerBuilder().addSeparatorComponents([separator]).toJSON()).toEqual(
|
||||||
|
containerWithSeparatorDataNoColor,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid accent color THEN valid JSON output is given', () => {
|
||||||
|
expect(
|
||||||
|
new ContainerBuilder({
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.setAccentColor([255, 0, 255])
|
||||||
|
.toJSON(),
|
||||||
|
).toEqual({
|
||||||
|
type: ComponentType.Container,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
accent_color: 0xff00ff,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
new ContainerBuilder({
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.setAccentColor(0xff00ff)
|
||||||
|
.toJSON(),
|
||||||
|
).toEqual({
|
||||||
|
type: ComponentType.Container,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
accent_color: 0xff00ff,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
new ContainerBuilder({
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.setAccentColor([255, 0, 255])
|
||||||
|
.clearAccentColor()
|
||||||
|
.toJSON(),
|
||||||
|
).toEqual({
|
||||||
|
type: ComponentType.Container,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(new ContainerBuilder(containerWithSeparatorData).clearAccentColor().toJSON()).toEqual(
|
||||||
|
containerWithSeparatorDataNoColor,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid method parameters THEN valid JSON is given', () => {
|
||||||
|
expect(
|
||||||
|
new ContainerBuilder()
|
||||||
|
.addTextDisplayComponents(new TextDisplayBuilder().setId(3).clearId().setContent('test'))
|
||||||
|
.setSpoiler()
|
||||||
|
.toJSON(),
|
||||||
|
).toEqual({
|
||||||
|
type: ComponentType.Container,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
spoiler: true,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
new ContainerBuilder()
|
||||||
|
.addTextDisplayComponents({ type: ComponentType.TextDisplay, content: 'test' })
|
||||||
|
.setSpoiler(false)
|
||||||
|
.setId(5)
|
||||||
|
.toJSON(),
|
||||||
|
).toEqual({
|
||||||
|
type: ComponentType.Container,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
spoiler: false,
|
||||||
|
id: 5,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
44
packages/builders/__tests__/components/v2/file.test.ts
Normal file
44
packages/builders/__tests__/components/v2/file.test.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { ComponentType } from 'discord-api-types/v10';
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
import { FileBuilder } from '../../../src/components/v2/File';
|
||||||
|
|
||||||
|
const dummy = {
|
||||||
|
type: ComponentType.File as const,
|
||||||
|
file: { url: 'attachment://owo.png' },
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('File', () => {
|
||||||
|
describe('File url', () => {
|
||||||
|
test('GIVEN a file with a pre-defined url THEN return valid toJSON data', () => {
|
||||||
|
const file = new FileBuilder({ file: { url: 'attachment://owo.png' } });
|
||||||
|
expect(file.toJSON()).toEqual({ ...dummy, file: { url: 'attachment://owo.png' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN a file using File#setURL THEN return valid toJSON data', () => {
|
||||||
|
const file = new FileBuilder();
|
||||||
|
file.setURL('attachment://uwu.png');
|
||||||
|
|
||||||
|
expect(file.toJSON()).toEqual({ ...dummy, file: { url: 'attachment://uwu.png' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN a file with an invalid url THEN throws error', () => {
|
||||||
|
const file = new FileBuilder();
|
||||||
|
|
||||||
|
expect(() => file.setURL('https://google.com')).toThrowError();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('File spoiler', () => {
|
||||||
|
test('GIVEN a file with a pre-defined spoiler status THEN return valid toJSON data', () => {
|
||||||
|
const file = new FileBuilder({ ...dummy, spoiler: true });
|
||||||
|
expect(file.toJSON()).toEqual({ ...dummy, spoiler: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN a file using File#setSpoiler THEN return valid toJSON data', () => {
|
||||||
|
const file = new FileBuilder({ ...dummy });
|
||||||
|
file.setSpoiler(false);
|
||||||
|
|
||||||
|
expect(file.toJSON()).toEqual({ ...dummy, spoiler: false });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
150
packages/builders/__tests__/components/v2/mediagallery.test.ts
Normal file
150
packages/builders/__tests__/components/v2/mediagallery.test.ts
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import { type APIMediaGalleryItem, type APIMediaGalleryComponent, ComponentType } from 'discord-api-types/v10';
|
||||||
|
import { describe, test, expect } from 'vitest';
|
||||||
|
import { createComponentBuilder } from '../../../src/components/Components.js';
|
||||||
|
import { MediaGalleryBuilder } from '../../../src/components/v2/MediaGallery.js';
|
||||||
|
import { MediaGalleryItemBuilder } from '../../../src/components/v2/MediaGalleryItem.js';
|
||||||
|
|
||||||
|
const galleryHttpsDisplay: APIMediaGalleryComponent = {
|
||||||
|
type: ComponentType.MediaGallery,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
description: 'test',
|
||||||
|
spoiler: false,
|
||||||
|
media: { url: 'https://discord.com/logo.png' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const galleryAttachmentData: APIMediaGalleryComponent = {
|
||||||
|
type: ComponentType.MediaGallery,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
media: { url: 'attachment://file.png' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
id: 123,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Media Gallery Components', () => {
|
||||||
|
describe('Assertion Tests', () => {
|
||||||
|
test('GIVEN an empty media gallery THEN throws error', () => {
|
||||||
|
const gallery = new MediaGalleryBuilder();
|
||||||
|
expect(() => gallery.toJSON()).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid items THEN do not throw', () => {
|
||||||
|
expect(() => new MediaGalleryBuilder().addItems(new MediaGalleryItemBuilder())).not.toThrowError();
|
||||||
|
expect(() => new MediaGalleryBuilder().spliceItems(0, 0, new MediaGalleryItemBuilder())).not.toThrowError();
|
||||||
|
expect(() => new MediaGalleryBuilder().addItems([new MediaGalleryItemBuilder()])).not.toThrowError();
|
||||||
|
expect(() => new MediaGalleryBuilder().spliceItems(0, 0, [new MediaGalleryItemBuilder()])).not.toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
|
||||||
|
const mediaGalleryData: APIMediaGalleryComponent = {
|
||||||
|
type: ComponentType.MediaGallery,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
media: { url: 'attachment://file.png' },
|
||||||
|
description: 'test',
|
||||||
|
spoiler: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
media: { url: 'https://discord.js.org/logo.jpg' },
|
||||||
|
spoiler: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
id: 1_234,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(new MediaGalleryBuilder(mediaGalleryData).toJSON()).toEqual(mediaGalleryData);
|
||||||
|
expect(() => createComponentBuilder({ type: ComponentType.MediaGallery, items: [] })).not.toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid builder options THEN valid JSON output is given', () => {
|
||||||
|
const galleryHttpsDisplay: APIMediaGalleryComponent = {
|
||||||
|
type: ComponentType.MediaGallery,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
description: 'test',
|
||||||
|
spoiler: false,
|
||||||
|
media: { url: 'https://discord.com/logo.png' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const galleryAttachmentData: APIMediaGalleryComponent = {
|
||||||
|
type: ComponentType.MediaGallery,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
media: { url: 'attachment://file.png' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
id: 123,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(new MediaGalleryBuilder(galleryHttpsDisplay).toJSON()).toEqual(galleryHttpsDisplay);
|
||||||
|
expect(new MediaGalleryBuilder(galleryAttachmentData).toJSON()).toEqual(galleryAttachmentData);
|
||||||
|
expect(() => createComponentBuilder({ type: ComponentType.MediaGallery, items: [] })).not.toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid builder options THEN valid JSON output is given 2', () => {
|
||||||
|
const item1 = new MediaGalleryItemBuilder()
|
||||||
|
.setDescription('test')
|
||||||
|
.setSpoiler(false)
|
||||||
|
.setURL('https://discord.com/logo.png');
|
||||||
|
const item2 = new MediaGalleryItemBuilder().setURL('attachment://file.png');
|
||||||
|
|
||||||
|
expect(new MediaGalleryBuilder().addItems(item1).toJSON()).toEqual(galleryHttpsDisplay);
|
||||||
|
expect(new MediaGalleryBuilder().addItems(item2).setId(123).toJSON()).toEqual(galleryAttachmentData);
|
||||||
|
expect(new MediaGalleryBuilder().addItems([item1]).toJSON()).toEqual(galleryHttpsDisplay);
|
||||||
|
expect(new MediaGalleryBuilder().addItems([item2]).setId(123).toJSON()).toEqual(galleryAttachmentData);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid JSON options THEN valid JSON output is given 2', () => {
|
||||||
|
const item1: APIMediaGalleryItem = {
|
||||||
|
description: 'test',
|
||||||
|
spoiler: false,
|
||||||
|
media: { url: 'https://discord.com/logo.png' },
|
||||||
|
};
|
||||||
|
const item2 = {
|
||||||
|
media: { url: 'attachment://file.png' },
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(new MediaGalleryBuilder().addItems(item1).toJSON()).toEqual(galleryHttpsDisplay);
|
||||||
|
expect(new MediaGalleryBuilder().addItems(item2).setId(123).toJSON()).toEqual(galleryAttachmentData);
|
||||||
|
expect(new MediaGalleryBuilder().addItems([item1]).toJSON()).toEqual(galleryHttpsDisplay);
|
||||||
|
expect(new MediaGalleryBuilder().addItems([item2]).setId(123).toJSON()).toEqual(galleryAttachmentData);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid builder callback THEN valid JSON output is given', () => {
|
||||||
|
const item1 = new MediaGalleryItemBuilder()
|
||||||
|
.setDescription('test')
|
||||||
|
.setSpoiler(false)
|
||||||
|
.setURL('https://discord.com/logo.png');
|
||||||
|
const item2 = new MediaGalleryItemBuilder().setURL('attachment://file.png');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
new MediaGalleryBuilder()
|
||||||
|
.addItems((item) => item.setDescription('test').setSpoiler(false).setURL('https://discord.com/logo.png'))
|
||||||
|
.toJSON(),
|
||||||
|
).toEqual(galleryHttpsDisplay);
|
||||||
|
expect(
|
||||||
|
new MediaGalleryBuilder()
|
||||||
|
.spliceItems(0, 0, (item) => item.setURL('attachment://file.png'))
|
||||||
|
.setId(123)
|
||||||
|
.toJSON(),
|
||||||
|
).toEqual(galleryAttachmentData);
|
||||||
|
expect(
|
||||||
|
new MediaGalleryBuilder()
|
||||||
|
.addItems([(item) => item.setDescription('test').setSpoiler(false).setURL('https://discord.com/logo.png')])
|
||||||
|
.toJSON(),
|
||||||
|
).toEqual(galleryHttpsDisplay);
|
||||||
|
expect(
|
||||||
|
new MediaGalleryBuilder()
|
||||||
|
.spliceItems(0, 0, [(item) => item.setDescription('test').clearDescription().setURL('attachment://file.png')])
|
||||||
|
.setId(123)
|
||||||
|
.toJSON(),
|
||||||
|
).toEqual(galleryAttachmentData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
191
packages/builders/__tests__/components/v2/section.test.ts
Normal file
191
packages/builders/__tests__/components/v2/section.test.ts
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import { type APISectionComponent, ButtonStyle, ComponentType } from 'discord-api-types/v10';
|
||||||
|
import { describe, test, expect } from 'vitest';
|
||||||
|
import { createComponentBuilder } from '../../../src/components/Components.js';
|
||||||
|
import { ButtonBuilder } from '../../../src/components/button/Button.js';
|
||||||
|
import { SectionBuilder } from '../../../src/components/v2/Section.js';
|
||||||
|
import { TextDisplayBuilder } from '../../../src/components/v2/TextDisplay.js';
|
||||||
|
import { ThumbnailBuilder } from '../../../src/components/v2/Thumbnail.js';
|
||||||
|
|
||||||
|
const sectionWithButtonData: APISectionComponent = {
|
||||||
|
type: ComponentType.Section,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
accessory: {
|
||||||
|
type: ComponentType.Button,
|
||||||
|
label: 'test',
|
||||||
|
custom_id: '123',
|
||||||
|
style: ButtonStyle.Primary,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const sectionWithThumbnailData: APISectionComponent = {
|
||||||
|
type: ComponentType.Section,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
accessory: {
|
||||||
|
type: ComponentType.Thumbnail,
|
||||||
|
media: { url: 'attachment://file.png' },
|
||||||
|
spoiler: true,
|
||||||
|
description: 'test',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Section Components', () => {
|
||||||
|
describe('Assertion Tests', () => {
|
||||||
|
test('GIVEN valid components THEN do not throw', () => {
|
||||||
|
expect(() => new SectionBuilder().addTextDisplayComponents(new TextDisplayBuilder())).not.toThrowError();
|
||||||
|
expect(() => new SectionBuilder().spliceTextDisplayComponents(0, 0, new TextDisplayBuilder())).not.toThrowError();
|
||||||
|
expect(() => new SectionBuilder().addTextDisplayComponents([new TextDisplayBuilder()])).not.toThrowError();
|
||||||
|
expect(() =>
|
||||||
|
new SectionBuilder().spliceTextDisplayComponents(0, 0, [new TextDisplayBuilder()]),
|
||||||
|
).not.toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
|
||||||
|
const sectionData: APISectionComponent = {
|
||||||
|
type: ComponentType.Section,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
id: 123,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
accessory: {
|
||||||
|
type: ComponentType.Thumbnail,
|
||||||
|
media: { url: 'attachment://file.png' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(new SectionBuilder(sectionData).toJSON()).toEqual(sectionData);
|
||||||
|
expect(() =>
|
||||||
|
createComponentBuilder({
|
||||||
|
type: ComponentType.Section,
|
||||||
|
components: [],
|
||||||
|
accessory: { type: ComponentType.Thumbnail, media: { url: 'https://discord.com/logo.png' } },
|
||||||
|
}),
|
||||||
|
).not.toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid builder options THEN valid JSON output is given', () => {
|
||||||
|
const sectionWithButtonData: APISectionComponent = {
|
||||||
|
type: ComponentType.Section,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
accessory: {
|
||||||
|
type: ComponentType.Button,
|
||||||
|
label: 'test',
|
||||||
|
custom_id: '123',
|
||||||
|
style: ButtonStyle.Primary,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const sectionWithThumbnailData: APISectionComponent = {
|
||||||
|
type: ComponentType.Section,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
content: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
accessory: {
|
||||||
|
type: ComponentType.Thumbnail,
|
||||||
|
media: { url: 'attachment://file.png' },
|
||||||
|
spoiler: true,
|
||||||
|
description: 'test',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(new SectionBuilder(sectionWithButtonData).toJSON()).toEqual(sectionWithButtonData);
|
||||||
|
expect(new SectionBuilder(sectionWithThumbnailData).toJSON()).toEqual(sectionWithThumbnailData);
|
||||||
|
expect(() =>
|
||||||
|
createComponentBuilder({
|
||||||
|
type: ComponentType.Section,
|
||||||
|
components: [],
|
||||||
|
accessory: {
|
||||||
|
type: ComponentType.Button,
|
||||||
|
label: 'test',
|
||||||
|
custom_id: '123',
|
||||||
|
style: ButtonStyle.Primary,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).not.toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid builder options THEN valid JSON output is given 2', () => {
|
||||||
|
const button = new ButtonBuilder().setLabel('test').setStyle(ButtonStyle.Primary).setCustomId('123');
|
||||||
|
const thumbnail = new ThumbnailBuilder().setDescription('test').setSpoiler().setURL('attachment://file.png');
|
||||||
|
const textDisplay = new TextDisplayBuilder().setContent('test');
|
||||||
|
|
||||||
|
expect(new SectionBuilder().addTextDisplayComponents(textDisplay).setButtonAccessory(button).toJSON()).toEqual(
|
||||||
|
sectionWithButtonData,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
new SectionBuilder().addTextDisplayComponents(textDisplay).setThumbnailAccessory(thumbnail).toJSON(),
|
||||||
|
).toEqual(sectionWithThumbnailData);
|
||||||
|
expect(
|
||||||
|
new SectionBuilder()
|
||||||
|
.addTextDisplayComponents([textDisplay])
|
||||||
|
.setButtonAccessory((button) => button.setLabel('test').setStyle(ButtonStyle.Primary).setCustomId('123'))
|
||||||
|
.toJSON(),
|
||||||
|
).toEqual(sectionWithButtonData);
|
||||||
|
expect(
|
||||||
|
new SectionBuilder()
|
||||||
|
.addTextDisplayComponents([textDisplay])
|
||||||
|
.setThumbnailAccessory((thumbnail) =>
|
||||||
|
thumbnail.setDescription('test').setSpoiler().setURL('attachment://file.png'),
|
||||||
|
)
|
||||||
|
.toJSON(),
|
||||||
|
).toEqual(sectionWithThumbnailData);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid builder callback THEN valid JSON output is given', () => {
|
||||||
|
const button = new ButtonBuilder().setLabel('test').setStyle(ButtonStyle.Primary).setCustomId('123');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
new SectionBuilder()
|
||||||
|
.addTextDisplayComponents((textDisplay) => textDisplay.setContent('test'))
|
||||||
|
.setButtonAccessory(button)
|
||||||
|
.toJSON(),
|
||||||
|
).toEqual(sectionWithButtonData);
|
||||||
|
expect(
|
||||||
|
new SectionBuilder()
|
||||||
|
.spliceTextDisplayComponents(0, 0, (textDisplay) => textDisplay.setContent('test'))
|
||||||
|
.setButtonAccessory(button)
|
||||||
|
.toJSON(),
|
||||||
|
).toEqual(sectionWithButtonData);
|
||||||
|
expect(
|
||||||
|
new SectionBuilder()
|
||||||
|
.addTextDisplayComponents([(textDisplay) => textDisplay.setContent('test')])
|
||||||
|
.setButtonAccessory(button)
|
||||||
|
.toJSON(),
|
||||||
|
).toEqual(sectionWithButtonData);
|
||||||
|
expect(
|
||||||
|
new SectionBuilder()
|
||||||
|
.spliceTextDisplayComponents(0, 0, [(textDisplay) => textDisplay.setContent('test')])
|
||||||
|
.setButtonAccessory(button)
|
||||||
|
.toJSON(),
|
||||||
|
).toEqual(sectionWithButtonData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
35
packages/builders/__tests__/components/v2/separator.test.ts
Normal file
35
packages/builders/__tests__/components/v2/separator.test.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { ComponentType, SeparatorSpacingSize } from 'discord-api-types/v10';
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
import { SeparatorBuilder } from '../../../src/components/v2/Separator';
|
||||||
|
|
||||||
|
describe('Separator', () => {
|
||||||
|
describe('Divider', () => {
|
||||||
|
test('GIVEN a separator with a pre-defined divider THEN return valid toJSON data', () => {
|
||||||
|
const separator = new SeparatorBuilder({ divider: true });
|
||||||
|
expect(separator.toJSON()).toEqual({ type: ComponentType.Separator, divider: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN a separator with a set divider THEN return valid toJSON data', () => {
|
||||||
|
const separator = new SeparatorBuilder().setDivider(false);
|
||||||
|
expect(separator.toJSON()).toEqual({ type: ComponentType.Separator, divider: false });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Spacing', () => {
|
||||||
|
test('GIVEN a separator with a pre-defined spacing THEN return valid toJSON data', () => {
|
||||||
|
const separator = new SeparatorBuilder({ spacing: SeparatorSpacingSize.Small });
|
||||||
|
expect(separator.toJSON()).toEqual({ type: ComponentType.Separator, spacing: SeparatorSpacingSize.Small });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN a separator with a set spacing THEN return valid toJSON data', () => {
|
||||||
|
const separator = new SeparatorBuilder().setSpacing(SeparatorSpacingSize.Large);
|
||||||
|
expect(separator.toJSON()).toEqual({ type: ComponentType.Separator, spacing: SeparatorSpacingSize.Large });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN a separator with a set spacing THEN clear spacing THEN return valid toJSON data', () => {
|
||||||
|
const separator = new SeparatorBuilder({ spacing: SeparatorSpacingSize.Small });
|
||||||
|
separator.clearSpacing();
|
||||||
|
expect(separator.toJSON()).toEqual({ type: ComponentType.Separator });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentType } from 'discord-api-types/v10';
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
import { TextDisplayBuilder } from '../../../src/components/v2/TextDisplay';
|
||||||
|
|
||||||
|
describe('TextDisplay', () => {
|
||||||
|
describe('TextDisplay content', () => {
|
||||||
|
test('GIVEN a text display with a pre-defined content THEN return valid toJSON data', () => {
|
||||||
|
const textDisplay = new TextDisplayBuilder({ content: 'foo' });
|
||||||
|
expect(textDisplay.toJSON()).toEqual({ type: ComponentType.TextDisplay, content: 'foo' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN a text display with a set content THEN return valid toJSON data', () => {
|
||||||
|
const textDisplay = new TextDisplayBuilder().setContent('foo');
|
||||||
|
expect(textDisplay.toJSON()).toEqual({ type: ComponentType.TextDisplay, content: 'foo' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN a text display with a pre-defined content THEN overwritten content THEN return valid toJSON data', () => {
|
||||||
|
const textDisplay = new TextDisplayBuilder({ content: 'foo' });
|
||||||
|
textDisplay.setContent('bar');
|
||||||
|
expect(textDisplay.toJSON()).toEqual({ type: ComponentType.TextDisplay, content: 'bar' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
69
packages/builders/__tests__/components/v2/thumbnail.test.ts
Normal file
69
packages/builders/__tests__/components/v2/thumbnail.test.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { ComponentType } from 'discord-api-types/v10';
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
import { ThumbnailBuilder } from '../../../src/components/v2/Thumbnail';
|
||||||
|
|
||||||
|
const dummy = {
|
||||||
|
type: ComponentType.Thumbnail as const,
|
||||||
|
media: { url: 'https://google.com' },
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Thumbnail', () => {
|
||||||
|
describe('Thumbnail url', () => {
|
||||||
|
test('GIVEN a thumbnail with a pre-defined url THEN return valid toJSON data', () => {
|
||||||
|
const thumbnail = new ThumbnailBuilder({ media: { url: 'https://google.com' } });
|
||||||
|
expect(thumbnail.toJSON()).toEqual({ type: ComponentType.Thumbnail, media: { url: 'https://google.com' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN a thumbnail with a set url THEN return valid toJSON data', () => {
|
||||||
|
const thumbnail = new ThumbnailBuilder().setURL('https://google.com');
|
||||||
|
expect(thumbnail.toJSON()).toEqual({ type: ComponentType.Thumbnail, media: { url: 'https://google.com' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each(['owo', 'discord://user'])('GIVEN a thumbnail with an invalid URL (%s) THEN throws error', (input) => {
|
||||||
|
const thumbnail = new ThumbnailBuilder();
|
||||||
|
|
||||||
|
expect(() => thumbnail.setURL(input)).toThrowError();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Thumbnail description', () => {
|
||||||
|
test('GIVEN a thumbnail with a pre-defined description THEN return valid toJSON data', () => {
|
||||||
|
const thumbnail = new ThumbnailBuilder({ ...dummy, description: 'foo' });
|
||||||
|
expect(thumbnail.toJSON()).toEqual({ ...dummy, description: 'foo' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN a thumbnail with a set description THEN return valid toJSON data', () => {
|
||||||
|
const thumbnail = new ThumbnailBuilder({ ...dummy });
|
||||||
|
thumbnail.setDescription('foo');
|
||||||
|
|
||||||
|
expect(thumbnail.toJSON()).toEqual({ ...dummy, description: 'foo' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN a thumbnail with a pre-defined description THEN unset description THEN return valid toJSON data', () => {
|
||||||
|
const thumbnail = new ThumbnailBuilder({ description: 'foo', ...dummy });
|
||||||
|
thumbnail.clearDescription();
|
||||||
|
|
||||||
|
expect(thumbnail.toJSON()).toEqual({ ...dummy });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN a thumbnail with an invalid description THEN throws error', () => {
|
||||||
|
const thumbnail = new ThumbnailBuilder();
|
||||||
|
|
||||||
|
expect(() => thumbnail.setDescription('a'.repeat(1_025))).toThrowError();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Thumbnail spoiler', () => {
|
||||||
|
test('GIVEN a thumbnail with a pre-defined spoiler status THEN return valid toJSON data', () => {
|
||||||
|
const thumbnail = new ThumbnailBuilder({ ...dummy, spoiler: true });
|
||||||
|
expect(thumbnail.toJSON()).toEqual({ ...dummy, spoiler: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN a thumbnail with a set spoiler status THEN return valid toJSON data', () => {
|
||||||
|
const thumbnail = new ThumbnailBuilder({ ...dummy });
|
||||||
|
thumbnail.setSpoiler(false);
|
||||||
|
|
||||||
|
expect(thumbnail.toJSON()).toEqual({ ...dummy, spoiler: false });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -16,8 +16,8 @@ describe('Context Menu Commands', () => {
|
|||||||
// Too short of a name
|
// Too short of a name
|
||||||
expect(() => ContextMenuCommandAssertions.validateName('')).toThrowError();
|
expect(() => ContextMenuCommandAssertions.validateName('')).toThrowError();
|
||||||
|
|
||||||
// Invalid characters used
|
// This should be fine, even with trailing and leading spaces (API trims it).
|
||||||
expect(() => ContextMenuCommandAssertions.validateName('ABC123$%^&')).toThrowError();
|
expect(() => ContextMenuCommandAssertions.validateName(' 🩵 ABC 123 $%^& ')).not.toThrowError();
|
||||||
|
|
||||||
// Too long of a name
|
// Too long of a name
|
||||||
expect(() =>
|
expect(() =>
|
||||||
@@ -60,8 +60,6 @@ describe('Context Menu Commands', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('GIVEN invalid name THEN throw error', () => {
|
test('GIVEN invalid name THEN throw error', () => {
|
||||||
expect(() => getBuilder().setName('$$$')).toThrowError();
|
|
||||||
|
|
||||||
expect(() => getBuilder().setName(' ')).toThrowError();
|
expect(() => getBuilder().setName(' ')).toThrowError();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -166,7 +164,7 @@ describe('Context Menu Commands', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('integration types', () => {
|
describe('integration types', () => {
|
||||||
test('GIVEN a builder with valid integration types THEN does not throw an error', () => {
|
test('GIVEN a builder with valid integraton types THEN does not throw an error', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
getBuilder().setIntegrationTypes([
|
getBuilder().setIntegrationTypes([
|
||||||
ApplicationIntegrationType.GuildInstall,
|
ApplicationIntegrationType.GuildInstall,
|
||||||
|
|||||||
@@ -565,7 +565,7 @@ describe('Slash Commands', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('integration types', () => {
|
describe('integration types', () => {
|
||||||
test('GIVEN a builder with valid integration types THEN does not throw an error', () => {
|
test('GIVEN a builder with valid integraton types THEN does not throw an error', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
getBuilder().setIntegrationTypes([
|
getBuilder().setIntegrationTypes([
|
||||||
ApplicationIntegrationType.GuildInstall,
|
ApplicationIntegrationType.GuildInstall,
|
||||||
|
|||||||
@@ -324,12 +324,16 @@ describe('Embed', () => {
|
|||||||
test('GIVEN an embed using Embed#addFields THEN returns valid toJSON data', () => {
|
test('GIVEN an embed using Embed#addFields THEN returns valid toJSON data', () => {
|
||||||
const embed = new EmbedBuilder();
|
const embed = new EmbedBuilder();
|
||||||
embed.addFields({ name: 'foo', value: 'bar' });
|
embed.addFields({ name: 'foo', value: 'bar' });
|
||||||
embed.addFields([{ name: 'foo', value: 'bar' }]);
|
embed.addFields([
|
||||||
|
{ name: 'foo', value: 'bar' },
|
||||||
|
{ name: '', value: '' },
|
||||||
|
]);
|
||||||
|
|
||||||
expect(embed.toJSON()).toStrictEqual({
|
expect(embed.toJSON()).toStrictEqual({
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'foo', value: 'bar' },
|
{ name: 'foo', value: 'bar' },
|
||||||
{ name: 'foo', value: 'bar' },
|
{ name: 'foo', value: 'bar' },
|
||||||
|
{ name: '', value: '' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -381,38 +385,24 @@ describe('Embed', () => {
|
|||||||
expect(() => embed.setFields(Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' })))).toThrowError();
|
expect(() => embed.setFields(Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' })))).toThrowError();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GIVEN invalid field amount THEN throws error', () => {
|
test('GIVEN invalid field amount THEN throws error', () => {
|
||||||
test('1', () => {
|
|
||||||
const embed = new EmbedBuilder();
|
const embed = new EmbedBuilder();
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
embed.addFields(...Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' }))),
|
embed.addFields(...Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' }))),
|
||||||
).toThrowError();
|
).toThrowError();
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('GIVEN invalid field name THEN throws error', () => {
|
test('GIVEN invalid field name length THEN throws error', () => {
|
||||||
test('2', () => {
|
|
||||||
const embed = new EmbedBuilder();
|
|
||||||
|
|
||||||
expect(() => embed.addFields({ name: '', value: 'bar' })).toThrowError();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GIVEN invalid field name length THEN throws error', () => {
|
|
||||||
test('3', () => {
|
|
||||||
const embed = new EmbedBuilder();
|
const embed = new EmbedBuilder();
|
||||||
|
|
||||||
expect(() => embed.addFields({ name: 'a'.repeat(257), value: 'bar' })).toThrowError();
|
expect(() => embed.addFields({ name: 'a'.repeat(257), value: 'bar' })).toThrowError();
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('GIVEN invalid field value length THEN throws error', () => {
|
test('GIVEN invalid field value length THEN throws error', () => {
|
||||||
test('4', () => {
|
|
||||||
const embed = new EmbedBuilder();
|
const embed = new EmbedBuilder();
|
||||||
|
|
||||||
expect(() => embed.addFields({ name: '', value: 'a'.repeat(1_025) })).toThrowError();
|
expect(() => embed.addFields({ name: '', value: 'a'.repeat(1_025) })).toThrowError();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@discordjs/builders",
|
"name": "@discordjs/builders",
|
||||||
"version": "1.9.0",
|
"version": "1.13.0",
|
||||||
"description": "A set of builders that you can use when creating your bot",
|
"description": "A set of builders that you can use when creating your bot",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
@@ -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.37.119",
|
"discord-api-types": "^0.38.32",
|
||||||
"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"
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
"vitest": "^2.0.5"
|
"vitest": "^2.0.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=16.11.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
import {
|
import {
|
||||||
type APIActionRowComponent,
|
type APIActionRowComponent,
|
||||||
ComponentType,
|
ComponentType,
|
||||||
type APIMessageActionRowComponent,
|
type APIComponentInMessageActionRow,
|
||||||
type APIModalActionRowComponent,
|
type APIComponentInModalActionRow,
|
||||||
type APIActionRowComponentTypes,
|
type APIComponentInActionRow,
|
||||||
} from 'discord-api-types/v10';
|
} from 'discord-api-types/v10';
|
||||||
import { normalizeArray, type RestOrArray } from '../util/normalizeArray.js';
|
import { normalizeArray, type RestOrArray } from '../util/normalizeArray.js';
|
||||||
import { ComponentBuilder } from './Component.js';
|
import { ComponentBuilder } from './Component.js';
|
||||||
@@ -18,13 +18,6 @@ import type { StringSelectMenuBuilder } from './selectMenu/StringSelectMenu.js';
|
|||||||
import type { UserSelectMenuBuilder } from './selectMenu/UserSelectMenu.js';
|
import type { UserSelectMenuBuilder } from './selectMenu/UserSelectMenu.js';
|
||||||
import type { TextInputBuilder } from './textInput/TextInput.js';
|
import type { TextInputBuilder } from './textInput/TextInput.js';
|
||||||
|
|
||||||
/**
|
|
||||||
* The builders that may be used for messages.
|
|
||||||
*/
|
|
||||||
export type MessageComponentBuilder =
|
|
||||||
| ActionRowBuilder<MessageActionRowComponentBuilder>
|
|
||||||
| MessageActionRowComponentBuilder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The builders that may be used for modals.
|
* The builders that may be used for modals.
|
||||||
*/
|
*/
|
||||||
@@ -57,7 +50,7 @@ export type AnyComponentBuilder = MessageActionRowComponentBuilder | ModalAction
|
|||||||
* @typeParam ComponentType - The types of components this action row holds
|
* @typeParam ComponentType - The types of components this action row holds
|
||||||
*/
|
*/
|
||||||
export class ActionRowBuilder<ComponentType extends AnyComponentBuilder> extends ComponentBuilder<
|
export class ActionRowBuilder<ComponentType extends AnyComponentBuilder> extends ComponentBuilder<
|
||||||
APIActionRowComponent<APIMessageActionRowComponent | APIModalActionRowComponent>
|
APIActionRowComponent<APIComponentInMessageActionRow | APIComponentInModalActionRow>
|
||||||
> {
|
> {
|
||||||
/**
|
/**
|
||||||
* The components within this action row.
|
* The components within this action row.
|
||||||
@@ -98,7 +91,7 @@ export class ActionRowBuilder<ComponentType extends AnyComponentBuilder> extends
|
|||||||
* .addComponents(button2, button3);
|
* .addComponents(button2, button3);
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
public constructor({ components, ...data }: Partial<APIActionRowComponent<APIActionRowComponentTypes>> = {}) {
|
public constructor({ components, ...data }: Partial<APIActionRowComponent<APIComponentInActionRow>> = {}) {
|
||||||
super({ type: ComponentType.ActionRow, ...data });
|
super({ type: ComponentType.ActionRow, ...data });
|
||||||
this.components = (components?.map((component) => createComponentBuilder(component)) ?? []) as ComponentType[];
|
this.components = (components?.map((component) => createComponentBuilder(component)) ?? []) as ComponentType[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,13 @@ import { ButtonStyle, ChannelType, type APIMessageComponentEmoji } from 'discord
|
|||||||
import { isValidationEnabled } from '../util/validation.js';
|
import { isValidationEnabled } from '../util/validation.js';
|
||||||
import { StringSelectMenuOptionBuilder } from './selectMenu/StringSelectMenuOption.js';
|
import { StringSelectMenuOptionBuilder } from './selectMenu/StringSelectMenuOption.js';
|
||||||
|
|
||||||
|
export const idValidator = s
|
||||||
|
.number()
|
||||||
|
.safeInt()
|
||||||
|
.greaterThanOrEqual(1)
|
||||||
|
.lessThan(4_294_967_296) // 2^32 - 1
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|
||||||
export const customIdValidator = s
|
export const customIdValidator = s
|
||||||
.string()
|
.string()
|
||||||
.lengthGreaterThanOrEqual(1)
|
.lengthGreaterThanOrEqual(1)
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
import type { JSONEncodable } from '@discordjs/util';
|
import type { JSONEncodable } from '@discordjs/util';
|
||||||
import type {
|
import type {
|
||||||
APIActionRowComponent,
|
APIActionRowComponent,
|
||||||
APIActionRowComponentTypes,
|
APIComponentInActionRow,
|
||||||
APIBaseComponent,
|
APIBaseComponent,
|
||||||
ComponentType,
|
ComponentType,
|
||||||
|
APIMessageComponent,
|
||||||
|
APIModalComponent,
|
||||||
} from 'discord-api-types/v10';
|
} from 'discord-api-types/v10';
|
||||||
|
import { idValidator } from './Assertions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any action row component data represented as an object.
|
* Any action row component data represented as an object.
|
||||||
*/
|
*/
|
||||||
export type AnyAPIActionRowComponent = APIActionRowComponent<APIActionRowComponentTypes> | APIActionRowComponentTypes;
|
export type AnyAPIActionRowComponent =
|
||||||
|
| APIActionRowComponent<APIComponentInActionRow>
|
||||||
|
| APIComponentInActionRow
|
||||||
|
| APIMessageComponent
|
||||||
|
| APIModalComponent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base component builder that contains common symbols for all sorts of components.
|
* The base component builder that contains common symbols for all sorts of components.
|
||||||
@@ -42,4 +49,22 @@ export abstract class ComponentBuilder<
|
|||||||
public constructor(data: Partial<DataType>) {
|
public constructor(data: Partial<DataType>) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the id (not the custom id) for this component.
|
||||||
|
*
|
||||||
|
* @param id - The id for this component
|
||||||
|
*/
|
||||||
|
public setId(id: number) {
|
||||||
|
this.data.id = idValidator.parse(id);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the id of this component, defaulting to a default incremented id.
|
||||||
|
*/
|
||||||
|
public clearId() {
|
||||||
|
this.data.id = undefined;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,42 @@
|
|||||||
|
import type { JSONEncodable } from '@discordjs/util';
|
||||||
import { ComponentType, type APIMessageComponent, type APIModalComponent } from 'discord-api-types/v10';
|
import { ComponentType, type APIMessageComponent, type APIModalComponent } from 'discord-api-types/v10';
|
||||||
import {
|
import {
|
||||||
ActionRowBuilder,
|
ActionRowBuilder,
|
||||||
|
type MessageActionRowComponentBuilder,
|
||||||
type AnyComponentBuilder,
|
type AnyComponentBuilder,
|
||||||
type MessageComponentBuilder,
|
|
||||||
type ModalComponentBuilder,
|
type ModalComponentBuilder,
|
||||||
} 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 { 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';
|
||||||
import { StringSelectMenuBuilder } from './selectMenu/StringSelectMenu.js';
|
import { StringSelectMenuBuilder } from './selectMenu/StringSelectMenu.js';
|
||||||
import { UserSelectMenuBuilder } from './selectMenu/UserSelectMenu.js';
|
import { UserSelectMenuBuilder } from './selectMenu/UserSelectMenu.js';
|
||||||
import { TextInputBuilder } from './textInput/TextInput.js';
|
import { TextInputBuilder } from './textInput/TextInput.js';
|
||||||
|
import { ContainerBuilder } from './v2/Container.js';
|
||||||
|
import { FileBuilder } from './v2/File.js';
|
||||||
|
import { MediaGalleryBuilder } from './v2/MediaGallery.js';
|
||||||
|
import { SectionBuilder } from './v2/Section.js';
|
||||||
|
import { SeparatorBuilder } from './v2/Separator.js';
|
||||||
|
import { TextDisplayBuilder } from './v2/TextDisplay.js';
|
||||||
|
import { ThumbnailBuilder } from './v2/Thumbnail.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The builders that may be used for messages.
|
||||||
|
*/
|
||||||
|
export type MessageComponentBuilder =
|
||||||
|
| ActionRowBuilder<MessageActionRowComponentBuilder>
|
||||||
|
| ContainerBuilder
|
||||||
|
| FileBuilder
|
||||||
|
| MediaGalleryBuilder
|
||||||
|
| MessageActionRowComponentBuilder
|
||||||
|
| SectionBuilder
|
||||||
|
| SeparatorBuilder
|
||||||
|
| TextDisplayBuilder
|
||||||
|
| ThumbnailBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Components here are mapped to their respective builder.
|
* Components here are mapped to their respective builder.
|
||||||
@@ -50,6 +74,42 @@ export interface MappedComponentTypes {
|
|||||||
* The channel select component type is associated with a {@link ChannelSelectMenuBuilder}.
|
* The channel select component type is associated with a {@link ChannelSelectMenuBuilder}.
|
||||||
*/
|
*/
|
||||||
[ComponentType.ChannelSelect]: ChannelSelectMenuBuilder;
|
[ComponentType.ChannelSelect]: ChannelSelectMenuBuilder;
|
||||||
|
/**
|
||||||
|
* The file component type is associated with a {@link FileBuilder}.
|
||||||
|
*/
|
||||||
|
[ComponentType.File]: FileBuilder;
|
||||||
|
/**
|
||||||
|
* The separator component type is associated with a {@link SeparatorBuilder}.
|
||||||
|
*/
|
||||||
|
[ComponentType.Separator]: SeparatorBuilder;
|
||||||
|
/**
|
||||||
|
* The container component type is associated with a {@link ContainerBuilder}.
|
||||||
|
*/
|
||||||
|
[ComponentType.Container]: ContainerBuilder;
|
||||||
|
/**
|
||||||
|
* The text display component type is associated with a {@link TextDisplayBuilder}.
|
||||||
|
*/
|
||||||
|
[ComponentType.TextDisplay]: TextDisplayBuilder;
|
||||||
|
/**
|
||||||
|
* The thumbnail component type is associated with a {@link ThumbnailBuilder}.
|
||||||
|
*/
|
||||||
|
[ComponentType.Thumbnail]: ThumbnailBuilder;
|
||||||
|
/**
|
||||||
|
* The section component type is associated with a {@link SectionBuilder}.
|
||||||
|
*/
|
||||||
|
[ComponentType.Section]: SectionBuilder;
|
||||||
|
/**
|
||||||
|
* The media gallery component type is associated with a {@link MediaGalleryBuilder}.
|
||||||
|
*/
|
||||||
|
[ComponentType.MediaGallery]: MediaGalleryBuilder;
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,8 +157,48 @@ export function createComponentBuilder(
|
|||||||
return new MentionableSelectMenuBuilder(data);
|
return new MentionableSelectMenuBuilder(data);
|
||||||
case ComponentType.ChannelSelect:
|
case ComponentType.ChannelSelect:
|
||||||
return new ChannelSelectMenuBuilder(data);
|
return new ChannelSelectMenuBuilder(data);
|
||||||
|
case ComponentType.File:
|
||||||
|
return new FileBuilder(data);
|
||||||
|
case ComponentType.Container:
|
||||||
|
return new ContainerBuilder(data);
|
||||||
|
case ComponentType.Section:
|
||||||
|
return new SectionBuilder(data);
|
||||||
|
case ComponentType.Separator:
|
||||||
|
return new SeparatorBuilder(data);
|
||||||
|
case ComponentType.TextDisplay:
|
||||||
|
return new TextDisplayBuilder(data);
|
||||||
|
case ComponentType.Thumbnail:
|
||||||
|
return new ThumbnailBuilder(data);
|
||||||
|
case ComponentType.MediaGallery:
|
||||||
|
return new MediaGalleryBuilder(data);
|
||||||
|
case ComponentType.Label:
|
||||||
|
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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isBuilder<Builder extends JSONEncodable<any>>(
|
||||||
|
builder: unknown,
|
||||||
|
Constructor: new () => Builder,
|
||||||
|
): builder is Builder {
|
||||||
|
return builder instanceof Constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveBuilder<ComponentType extends Record<PropertyKey, any>, Builder extends JSONEncodable<any>>(
|
||||||
|
builder: Builder | ComponentType | ((builder: Builder) => Builder),
|
||||||
|
Constructor: new (data?: ComponentType) => Builder,
|
||||||
|
) {
|
||||||
|
if (isBuilder(builder, Constructor)) {
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof builder === 'function') {
|
||||||
|
return builder(new Constructor());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Constructor(builder);
|
||||||
|
}
|
||||||
|
|||||||
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 can 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
packages/builders/src/components/label/Assertions.ts
Normal file
31
packages/builders/src/components/label/Assertions.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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,
|
||||||
|
selectMenuRolePredicate,
|
||||||
|
selectMenuStringPredicate,
|
||||||
|
selectMenuUserPredicate,
|
||||||
|
} from '../selectMenu/Assertions.js';
|
||||||
|
import { textInputPredicate } from '../textInput/Assertions.js';
|
||||||
|
|
||||||
|
export const labelPredicate = s
|
||||||
|
.object({
|
||||||
|
id: idValidator.optional(),
|
||||||
|
type: s.literal(ComponentType.Label),
|
||||||
|
label: s.string().lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(45),
|
||||||
|
description: s.string().lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100).optional(),
|
||||||
|
component: s.union([
|
||||||
|
textInputPredicate,
|
||||||
|
selectMenuUserPredicate,
|
||||||
|
selectMenuRolePredicate,
|
||||||
|
selectMenuMentionablePredicate,
|
||||||
|
selectMenuChannelPredicate,
|
||||||
|
selectMenuStringPredicate,
|
||||||
|
fileUploadPredicate,
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
213
packages/builders/src/components/label/Label.ts
Normal file
213
packages/builders/src/components/label/Label.ts
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import type {
|
||||||
|
APIChannelSelectComponent,
|
||||||
|
APIFileUploadComponent,
|
||||||
|
APILabelComponent,
|
||||||
|
APIMentionableSelectComponent,
|
||||||
|
APIRoleSelectComponent,
|
||||||
|
APIStringSelectComponent,
|
||||||
|
APITextInputComponent,
|
||||||
|
APIUserSelectComponent,
|
||||||
|
} from 'discord-api-types/v10';
|
||||||
|
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';
|
||||||
|
import { StringSelectMenuBuilder } from '../selectMenu/StringSelectMenu.js';
|
||||||
|
import { UserSelectMenuBuilder } from '../selectMenu/UserSelectMenu.js';
|
||||||
|
import { TextInputBuilder } from '../textInput/TextInput.js';
|
||||||
|
import { labelPredicate } from './Assertions.js';
|
||||||
|
|
||||||
|
export interface LabelBuilderData extends Partial<Omit<APILabelComponent, 'component'>> {
|
||||||
|
component?:
|
||||||
|
| ChannelSelectMenuBuilder
|
||||||
|
| FileUploadBuilder
|
||||||
|
| MentionableSelectMenuBuilder
|
||||||
|
| RoleSelectMenuBuilder
|
||||||
|
| StringSelectMenuBuilder
|
||||||
|
| TextInputBuilder
|
||||||
|
| UserSelectMenuBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder that creates API-compatible JSON data for labels.
|
||||||
|
*/
|
||||||
|
export class LabelBuilder extends ComponentBuilder<LabelBuilderData> {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public override readonly data: LabelBuilderData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new label.
|
||||||
|
*
|
||||||
|
* @param data - The API data to create this label with
|
||||||
|
* @example
|
||||||
|
* Creating a label from an API data object:
|
||||||
|
* ```ts
|
||||||
|
* const label = new LabelBuilder({
|
||||||
|
* label: "label",
|
||||||
|
* component,
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* Creating a label using setters and API data:
|
||||||
|
* ```ts
|
||||||
|
* const label = new LabelBuilder({
|
||||||
|
* label: 'label',
|
||||||
|
* component,
|
||||||
|
* }).setLabel('new text');
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public constructor(data: Partial<APILabelComponent> = {}) {
|
||||||
|
super({ type: ComponentType.Label });
|
||||||
|
|
||||||
|
const { component, ...rest } = data;
|
||||||
|
|
||||||
|
this.data = {
|
||||||
|
...rest,
|
||||||
|
component: component ? createComponentBuilder(component) : undefined,
|
||||||
|
type: ComponentType.Label,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the label for this label.
|
||||||
|
*
|
||||||
|
* @param label - The label to use
|
||||||
|
*/
|
||||||
|
public setLabel(label: string) {
|
||||||
|
this.data.label = label;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the description for this label.
|
||||||
|
*
|
||||||
|
* @param description - The description to use
|
||||||
|
*/
|
||||||
|
public setDescription(description: string) {
|
||||||
|
this.data.description = description;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the description for this label.
|
||||||
|
*/
|
||||||
|
public clearDescription() {
|
||||||
|
this.data.description = undefined;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a string select menu component to this label.
|
||||||
|
*
|
||||||
|
* @param input - A function that returns a component builder or an already built builder
|
||||||
|
*/
|
||||||
|
public setStringSelectMenuComponent(
|
||||||
|
input:
|
||||||
|
| APIStringSelectComponent
|
||||||
|
| StringSelectMenuBuilder
|
||||||
|
| ((builder: StringSelectMenuBuilder) => StringSelectMenuBuilder),
|
||||||
|
): this {
|
||||||
|
this.data.component = resolveBuilder(input, StringSelectMenuBuilder);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a user select menu component to this label.
|
||||||
|
*
|
||||||
|
* @param input - A function that returns a component builder or an already built builder
|
||||||
|
*/
|
||||||
|
public setUserSelectMenuComponent(
|
||||||
|
input: APIUserSelectComponent | UserSelectMenuBuilder | ((builder: UserSelectMenuBuilder) => UserSelectMenuBuilder),
|
||||||
|
): this {
|
||||||
|
this.data.component = resolveBuilder(input, UserSelectMenuBuilder);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a role select menu component to this label.
|
||||||
|
*
|
||||||
|
* @param input - A function that returns a component builder or an already built builder
|
||||||
|
*/
|
||||||
|
public setRoleSelectMenuComponent(
|
||||||
|
input: APIRoleSelectComponent | RoleSelectMenuBuilder | ((builder: RoleSelectMenuBuilder) => RoleSelectMenuBuilder),
|
||||||
|
): this {
|
||||||
|
this.data.component = resolveBuilder(input, RoleSelectMenuBuilder);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a mentionable select menu component to this label.
|
||||||
|
*
|
||||||
|
* @param input - A function that returns a component builder or an already built builder
|
||||||
|
*/
|
||||||
|
public setMentionableSelectMenuComponent(
|
||||||
|
input:
|
||||||
|
| APIMentionableSelectComponent
|
||||||
|
| MentionableSelectMenuBuilder
|
||||||
|
| ((builder: MentionableSelectMenuBuilder) => MentionableSelectMenuBuilder),
|
||||||
|
): this {
|
||||||
|
this.data.component = resolveBuilder(input, MentionableSelectMenuBuilder);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a channel select menu component to this label.
|
||||||
|
*
|
||||||
|
* @param input - A function that returns a component builder or an already built builder
|
||||||
|
*/
|
||||||
|
public setChannelSelectMenuComponent(
|
||||||
|
input:
|
||||||
|
| APIChannelSelectComponent
|
||||||
|
| ChannelSelectMenuBuilder
|
||||||
|
| ((builder: ChannelSelectMenuBuilder) => ChannelSelectMenuBuilder),
|
||||||
|
): this {
|
||||||
|
this.data.component = resolveBuilder(input, ChannelSelectMenuBuilder);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a text input component to this label.
|
||||||
|
*
|
||||||
|
* @param input - A function that returns a component builder or an already built builder
|
||||||
|
*/
|
||||||
|
public setTextInputComponent(
|
||||||
|
input: APITextInputComponent | TextInputBuilder | ((builder: TextInputBuilder) => TextInputBuilder),
|
||||||
|
): this {
|
||||||
|
this.data.component = resolveBuilder(input, TextInputBuilder);
|
||||||
|
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}
|
||||||
|
*/
|
||||||
|
public override toJSON(): APILabelComponent {
|
||||||
|
const { component, ...rest } = this.data;
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
...rest,
|
||||||
|
// The label predicate validates the component.
|
||||||
|
component: component?.toJSON(),
|
||||||
|
};
|
||||||
|
|
||||||
|
labelPredicate.parse(data);
|
||||||
|
|
||||||
|
return data as APILabelComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
92
packages/builders/src/components/selectMenu/Assertions.ts
Normal file
92
packages/builders/src/components/selectMenu/Assertions.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { Result, s } from '@sapphire/shapeshift';
|
||||||
|
import { ChannelType, ComponentType, SelectMenuDefaultValueType } from 'discord-api-types/v10';
|
||||||
|
import { isValidationEnabled } from '../../util/validation.js';
|
||||||
|
import { customIdValidator, emojiValidator, idValidator } from '../Assertions.js';
|
||||||
|
import { labelValidator } from '../textInput/Assertions.js';
|
||||||
|
|
||||||
|
const selectMenuBasePredicate = s.object({
|
||||||
|
id: idValidator.optional(),
|
||||||
|
placeholder: s.string().lengthLessThanOrEqual(150).optional(),
|
||||||
|
min_values: s.number().greaterThanOrEqual(0).lessThanOrEqual(25).optional(),
|
||||||
|
max_values: s.number().greaterThanOrEqual(0).lessThanOrEqual(25).optional(),
|
||||||
|
custom_id: customIdValidator,
|
||||||
|
disabled: s.boolean().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const selectMenuChannelPredicate = selectMenuBasePredicate
|
||||||
|
.extend({
|
||||||
|
type: s.literal(ComponentType.ChannelSelect),
|
||||||
|
channel_types: s.nativeEnum(ChannelType).array().optional(),
|
||||||
|
default_values: s
|
||||||
|
.object({ id: s.string(), type: s.literal(SelectMenuDefaultValueType.Channel) })
|
||||||
|
.array()
|
||||||
|
.lengthLessThanOrEqual(25)
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|
||||||
|
export const selectMenuMentionablePredicate = selectMenuBasePredicate
|
||||||
|
.extend({
|
||||||
|
type: s.literal(ComponentType.MentionableSelect),
|
||||||
|
default_values: s
|
||||||
|
.object({
|
||||||
|
id: s.string(),
|
||||||
|
type: s.union([s.literal(SelectMenuDefaultValueType.Role), s.literal(SelectMenuDefaultValueType.User)]),
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.lengthLessThanOrEqual(25)
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|
||||||
|
export const selectMenuRolePredicate = selectMenuBasePredicate
|
||||||
|
.extend({
|
||||||
|
type: s.literal(ComponentType.RoleSelect),
|
||||||
|
default_values: s
|
||||||
|
.object({ id: s.string(), type: s.literal(SelectMenuDefaultValueType.Role) })
|
||||||
|
.array()
|
||||||
|
.lengthLessThanOrEqual(25)
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|
||||||
|
export const selectMenuUserPredicate = selectMenuBasePredicate
|
||||||
|
.extend({
|
||||||
|
type: s.literal(ComponentType.UserSelect),
|
||||||
|
default_values: s
|
||||||
|
.object({ id: s.string(), type: s.literal(SelectMenuDefaultValueType.User) })
|
||||||
|
.array()
|
||||||
|
.lengthLessThanOrEqual(25)
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|
||||||
|
export const selectMenuStringOptionPredicate = s
|
||||||
|
.object({
|
||||||
|
label: labelValidator,
|
||||||
|
value: s.string().lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100),
|
||||||
|
description: s.string().lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100).optional(),
|
||||||
|
emoji: emojiValidator.optional(),
|
||||||
|
default: s.boolean().optional(),
|
||||||
|
})
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|
||||||
|
export const selectMenuStringPredicate = selectMenuBasePredicate
|
||||||
|
.extend({
|
||||||
|
type: s.literal(ComponentType.StringSelect),
|
||||||
|
options: selectMenuStringOptionPredicate.array().lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(25),
|
||||||
|
})
|
||||||
|
.reshape((value) => {
|
||||||
|
if (value.min_values !== undefined && value.options.length < value.min_values) {
|
||||||
|
return Result.err(new RangeError(`The number of options must be greater than or equal to min_values`));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.min_values !== undefined && value.max_values !== undefined && value.min_values > value.max_values) {
|
||||||
|
return Result.err(
|
||||||
|
new RangeError(`The maximum amount of options must be greater than or equal to the minimum amount of options`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(value);
|
||||||
|
})
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { APISelectMenuComponent } from 'discord-api-types/v10';
|
import type { APISelectMenuComponent } from 'discord-api-types/v10';
|
||||||
import { customIdValidator, disabledValidator, minMaxValidator, placeholderValidator } from '../Assertions.js';
|
import { customIdValidator, disabledValidator, minMaxValidator, placeholderValidator } from '../Assertions.js';
|
||||||
import { ComponentBuilder } from '../Component.js';
|
import { ComponentBuilder } from '../Component.js';
|
||||||
|
import { requiredValidator } from '../textInput/Assertions.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base select menu builder that contains common symbols for select menu builders.
|
* The base select menu builder that contains common symbols for select menu builders.
|
||||||
@@ -31,9 +32,9 @@ export abstract class BaseSelectMenuBuilder<
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the maximum values that must be selected in the select menu.
|
* Sets the maximum values that can be selected in the select menu.
|
||||||
*
|
*
|
||||||
* @param maxValues - The maximum values that must be selected
|
* @param maxValues - The maximum values that can be selected
|
||||||
*/
|
*/
|
||||||
public setMaxValues(maxValues: number) {
|
public setMaxValues(maxValues: number) {
|
||||||
this.data.max_values = minMaxValidator.parse(maxValues);
|
this.data.max_values = minMaxValidator.parse(maxValues);
|
||||||
@@ -60,6 +61,17 @@ export abstract class BaseSelectMenuBuilder<
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether this select menu is required.
|
||||||
|
*
|
||||||
|
* @remarks Only for use in modals.
|
||||||
|
* @param required - Whether this select menu is required
|
||||||
|
*/
|
||||||
|
public setRequired(required = true) {
|
||||||
|
this.data.required = requiredValidator.parse(required);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc ComponentBuilder.toJSON}
|
* {@inheritDoc ComponentBuilder.toJSON}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export class StringSelectMenuBuilder extends BaseSelectMenuBuilder<APIStringSele
|
|||||||
*
|
*
|
||||||
* @remarks
|
* @remarks
|
||||||
* This method behaves similarly
|
* This method behaves similarly
|
||||||
* to {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/slice | Array.prototype.splice()}.
|
* to {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice | Array.prototype.splice()}.
|
||||||
* It's useful for modifying and adjusting the order of existing options.
|
* It's useful for modifying and adjusting the order of existing options.
|
||||||
* @example
|
* @example
|
||||||
* Remove the first option:
|
* Remove the first option:
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { s } from '@sapphire/shapeshift';
|
import { s } from '@sapphire/shapeshift';
|
||||||
import { TextInputStyle } from 'discord-api-types/v10';
|
import { ComponentType, TextInputStyle } from 'discord-api-types/v10';
|
||||||
import { isValidationEnabled } from '../../util/validation.js';
|
import { isValidationEnabled } from '../../util/validation.js';
|
||||||
import { customIdValidator } from '../Assertions.js';
|
import { customIdValidator, idValidator } from '../Assertions.js';
|
||||||
|
|
||||||
export const textInputStyleValidator = s.nativeEnum(TextInputStyle);
|
export const textInputStyleValidator = s.nativeEnum(TextInputStyle).setValidationEnabled(isValidationEnabled);
|
||||||
export const minLengthValidator = s
|
export const minLengthValidator = s
|
||||||
.number()
|
.number()
|
||||||
.int()
|
.int()
|
||||||
@@ -16,7 +16,7 @@ export const maxLengthValidator = s
|
|||||||
.greaterThanOrEqual(1)
|
.greaterThanOrEqual(1)
|
||||||
.lessThanOrEqual(4_000)
|
.lessThanOrEqual(4_000)
|
||||||
.setValidationEnabled(isValidationEnabled);
|
.setValidationEnabled(isValidationEnabled);
|
||||||
export const requiredValidator = s.boolean();
|
export const requiredValidator = s.boolean().setValidationEnabled(isValidationEnabled);
|
||||||
export const valueValidator = s.string().lengthLessThanOrEqual(4_000).setValidationEnabled(isValidationEnabled);
|
export const valueValidator = s.string().lengthLessThanOrEqual(4_000).setValidationEnabled(isValidationEnabled);
|
||||||
export const placeholderValidator = s.string().lengthLessThanOrEqual(100).setValidationEnabled(isValidationEnabled);
|
export const placeholderValidator = s.string().lengthLessThanOrEqual(100).setValidationEnabled(isValidationEnabled);
|
||||||
export const labelValidator = s
|
export const labelValidator = s
|
||||||
@@ -25,8 +25,21 @@ export const labelValidator = s
|
|||||||
.lengthLessThanOrEqual(45)
|
.lengthLessThanOrEqual(45)
|
||||||
.setValidationEnabled(isValidationEnabled);
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|
||||||
export function validateRequiredParameters(customId?: string, style?: TextInputStyle, label?: string) {
|
export const textInputPredicate = s
|
||||||
|
.object({
|
||||||
|
type: s.literal(ComponentType.TextInput),
|
||||||
|
custom_id: customIdValidator,
|
||||||
|
style: textInputStyleValidator,
|
||||||
|
id: idValidator.optional(),
|
||||||
|
min_length: minLengthValidator.optional(),
|
||||||
|
max_length: maxLengthValidator.optional(),
|
||||||
|
placeholder: placeholderValidator.optional(),
|
||||||
|
value: valueValidator.optional(),
|
||||||
|
required: requiredValidator.optional(),
|
||||||
|
})
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|
||||||
|
export function validateRequiredParameters(customId?: string, style?: TextInputStyle) {
|
||||||
customIdValidator.parse(customId);
|
customIdValidator.parse(customId);
|
||||||
textInputStyleValidator.parse(style);
|
textInputStyleValidator.parse(style);
|
||||||
labelValidator.parse(label);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class TextInputBuilder
|
|||||||
* ```ts
|
* ```ts
|
||||||
* const textInput = new TextInputBuilder({
|
* const textInput = new TextInputBuilder({
|
||||||
* custom_id: 'a cool text input',
|
* custom_id: 'a cool text input',
|
||||||
* label: 'Type something',
|
* placeholder: 'Type something',
|
||||||
* style: TextInputStyle.Short,
|
* style: TextInputStyle.Short,
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
@@ -38,7 +38,7 @@ export class TextInputBuilder
|
|||||||
* Creating a text input using setters and API data:
|
* Creating a text input using setters and API data:
|
||||||
* ```ts
|
* ```ts
|
||||||
* const textInput = new TextInputBuilder({
|
* const textInput = new TextInputBuilder({
|
||||||
* label: 'Type something else',
|
* placeholder: 'Type something else',
|
||||||
* })
|
* })
|
||||||
* .setCustomId('woah')
|
* .setCustomId('woah')
|
||||||
* .setStyle(TextInputStyle.Paragraph);
|
* .setStyle(TextInputStyle.Paragraph);
|
||||||
@@ -62,6 +62,7 @@ export class TextInputBuilder
|
|||||||
* Sets the label for this text input.
|
* Sets the label for this text input.
|
||||||
*
|
*
|
||||||
* @param label - The label to use
|
* @param label - The label to use
|
||||||
|
* @deprecated Use a label builder to create a label (and optionally a description) instead.
|
||||||
*/
|
*/
|
||||||
public setLabel(label: string) {
|
public setLabel(label: string) {
|
||||||
this.data.label = labelValidator.parse(label);
|
this.data.label = labelValidator.parse(label);
|
||||||
@@ -132,7 +133,7 @@ export class TextInputBuilder
|
|||||||
* {@inheritDoc ComponentBuilder.toJSON}
|
* {@inheritDoc ComponentBuilder.toJSON}
|
||||||
*/
|
*/
|
||||||
public toJSON(): APITextInputComponent {
|
public toJSON(): APITextInputComponent {
|
||||||
validateRequiredParameters(this.data.custom_id, this.data.style, this.data.label);
|
validateRequiredParameters(this.data.custom_id, this.data.style);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...this.data,
|
...this.data,
|
||||||
|
|||||||
72
packages/builders/src/components/v2/Assertions.ts
Normal file
72
packages/builders/src/components/v2/Assertions.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { s } from '@sapphire/shapeshift';
|
||||||
|
import { SeparatorSpacingSize } from 'discord-api-types/v10';
|
||||||
|
import { colorPredicate } from '../../messages/embed/Assertions';
|
||||||
|
import { isValidationEnabled } from '../../util/validation';
|
||||||
|
import { ComponentBuilder } from '../Component';
|
||||||
|
import { ButtonBuilder } from '../button/Button';
|
||||||
|
import type { ContainerComponentBuilder } from './Container';
|
||||||
|
import type { MediaGalleryItemBuilder } from './MediaGalleryItem';
|
||||||
|
import type { TextDisplayBuilder } from './TextDisplay';
|
||||||
|
import { ThumbnailBuilder } from './Thumbnail';
|
||||||
|
|
||||||
|
export const unfurledMediaItemPredicate = s
|
||||||
|
.object({
|
||||||
|
url: s
|
||||||
|
.string()
|
||||||
|
.url(
|
||||||
|
{ allowedProtocols: ['http:', 'https:', 'attachment:'] },
|
||||||
|
{ message: 'Invalid protocol for media URL. Must be http:, https:, or attachment:' },
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|
||||||
|
export const descriptionPredicate = s
|
||||||
|
.string()
|
||||||
|
.lengthGreaterThanOrEqual(1)
|
||||||
|
.lengthLessThanOrEqual(1_024)
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|
||||||
|
export const filePredicate = s
|
||||||
|
.object({
|
||||||
|
url: s
|
||||||
|
.string()
|
||||||
|
.url({ allowedProtocols: ['attachment:'] }, { message: 'Invalid protocol for file URL. Must be attachment:' }),
|
||||||
|
})
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|
||||||
|
export const spoilerPredicate = s.boolean();
|
||||||
|
|
||||||
|
export const dividerPredicate = s.boolean();
|
||||||
|
|
||||||
|
export const spacingPredicate = s.nativeEnum(SeparatorSpacingSize);
|
||||||
|
|
||||||
|
export const textDisplayContentPredicate = s
|
||||||
|
.string()
|
||||||
|
.lengthGreaterThanOrEqual(1)
|
||||||
|
.lengthLessThanOrEqual(4_000)
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|
||||||
|
export const accessoryPredicate = s
|
||||||
|
.instance(ButtonBuilder)
|
||||||
|
.or(s.instance(ThumbnailBuilder))
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|
||||||
|
export const containerColorPredicate = colorPredicate.nullish();
|
||||||
|
|
||||||
|
export function assertReturnOfBuilder<ReturnType extends MediaGalleryItemBuilder | TextDisplayBuilder>(
|
||||||
|
input: unknown,
|
||||||
|
ExpectedInstanceOf: new () => ReturnType,
|
||||||
|
): asserts input is ReturnType {
|
||||||
|
s.instance(ExpectedInstanceOf).setValidationEnabled(isValidationEnabled).parse(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateComponentArray<
|
||||||
|
ReturnType extends ContainerComponentBuilder | MediaGalleryItemBuilder = ContainerComponentBuilder,
|
||||||
|
>(input: unknown, min: number, max: number, ExpectedInstanceOf?: new () => ReturnType): asserts input is ReturnType[] {
|
||||||
|
(ExpectedInstanceOf ? s.instance(ExpectedInstanceOf) : s.instance(ComponentBuilder))
|
||||||
|
.array()
|
||||||
|
.lengthGreaterThanOrEqual(min)
|
||||||
|
.lengthLessThanOrEqual(max)
|
||||||
|
.setValidationEnabled(isValidationEnabled)
|
||||||
|
.parse(input);
|
||||||
|
}
|
||||||
239
packages/builders/src/components/v2/Container.ts
Normal file
239
packages/builders/src/components/v2/Container.ts
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
/* eslint-disable jsdoc/check-param-names */
|
||||||
|
|
||||||
|
import type {
|
||||||
|
APIActionRowComponent,
|
||||||
|
APIComponentInContainer,
|
||||||
|
APIComponentInMessageActionRow,
|
||||||
|
APIContainerComponent,
|
||||||
|
APIFileComponent,
|
||||||
|
APIMediaGalleryComponent,
|
||||||
|
APISectionComponent,
|
||||||
|
APISeparatorComponent,
|
||||||
|
APITextDisplayComponent,
|
||||||
|
} from 'discord-api-types/v10';
|
||||||
|
import { ComponentType } from 'discord-api-types/v10';
|
||||||
|
import type { RGBTuple } from '../../index.js';
|
||||||
|
import { MediaGalleryBuilder, SectionBuilder } from '../../index.js';
|
||||||
|
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
|
||||||
|
import type { AnyComponentBuilder, MessageActionRowComponentBuilder } from '../ActionRow.js';
|
||||||
|
import { ActionRowBuilder } from '../ActionRow.js';
|
||||||
|
import { ComponentBuilder } from '../Component.js';
|
||||||
|
import { createComponentBuilder, resolveBuilder } from '../Components.js';
|
||||||
|
import { containerColorPredicate, spoilerPredicate } from './Assertions.js';
|
||||||
|
import { FileBuilder } from './File.js';
|
||||||
|
import { SeparatorBuilder } from './Separator.js';
|
||||||
|
import { TextDisplayBuilder } from './TextDisplay.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The builders that may be used within a container.
|
||||||
|
*/
|
||||||
|
export type ContainerComponentBuilder =
|
||||||
|
| ActionRowBuilder<AnyComponentBuilder>
|
||||||
|
| FileBuilder
|
||||||
|
| MediaGalleryBuilder
|
||||||
|
| SectionBuilder
|
||||||
|
| SeparatorBuilder
|
||||||
|
| TextDisplayBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder that creates API-compatible JSON data for a container.
|
||||||
|
*/
|
||||||
|
export class ContainerBuilder extends ComponentBuilder<APIContainerComponent> {
|
||||||
|
/**
|
||||||
|
* The components within this container.
|
||||||
|
*/
|
||||||
|
public readonly components: ContainerComponentBuilder[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new container from API data.
|
||||||
|
*
|
||||||
|
* @param data - The API data to create this container with
|
||||||
|
* @example
|
||||||
|
* Creating a container from an API data object:
|
||||||
|
* ```ts
|
||||||
|
* const container = new ContainerBuilder({
|
||||||
|
* components: [
|
||||||
|
* {
|
||||||
|
* content: "Some text here",
|
||||||
|
* type: ComponentType.TextDisplay,
|
||||||
|
* },
|
||||||
|
* ],
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* Creating a container using setters and API data:
|
||||||
|
* ```ts
|
||||||
|
* const container = new ContainerBuilder({
|
||||||
|
* components: [
|
||||||
|
* {
|
||||||
|
* content: "# Heading",
|
||||||
|
* type: ComponentType.TextDisplay,
|
||||||
|
* },
|
||||||
|
* ],
|
||||||
|
* })
|
||||||
|
* .addComponents(separator, section);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public constructor({ components, ...data }: Partial<APIContainerComponent> = {}) {
|
||||||
|
super({ type: ComponentType.Container, ...data });
|
||||||
|
this.components = (components?.map((component) => createComponentBuilder(component)) ??
|
||||||
|
[]) as ContainerComponentBuilder[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the accent color of this container.
|
||||||
|
*
|
||||||
|
* @param color - The color to use
|
||||||
|
*/
|
||||||
|
public setAccentColor(color?: RGBTuple | number): this {
|
||||||
|
// Data assertions
|
||||||
|
containerColorPredicate.parse(color);
|
||||||
|
|
||||||
|
if (Array.isArray(color)) {
|
||||||
|
const [red, green, blue] = color;
|
||||||
|
this.data.accent_color = (red << 16) + (green << 8) + blue;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data.accent_color = color;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the accent color of this container.
|
||||||
|
*/
|
||||||
|
public clearAccentColor() {
|
||||||
|
this.data.accent_color = undefined;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds action row components to this container.
|
||||||
|
*
|
||||||
|
* @param components - The action row components to add
|
||||||
|
*/
|
||||||
|
public addActionRowComponents<ComponentType extends MessageActionRowComponentBuilder>(
|
||||||
|
...components: RestOrArray<
|
||||||
|
| ActionRowBuilder<ComponentType>
|
||||||
|
| APIActionRowComponent<APIComponentInMessageActionRow>
|
||||||
|
| ((builder: ActionRowBuilder<ComponentType>) => ActionRowBuilder<ComponentType>)
|
||||||
|
>
|
||||||
|
) {
|
||||||
|
this.components.push(
|
||||||
|
...normalizeArray(components).map((component) => resolveBuilder(component, ActionRowBuilder<ComponentType>)),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds file components to this container.
|
||||||
|
*
|
||||||
|
* @param components - The file components to add
|
||||||
|
*/
|
||||||
|
public addFileComponents(
|
||||||
|
...components: RestOrArray<APIFileComponent | FileBuilder | ((builder: FileBuilder) => FileBuilder)>
|
||||||
|
) {
|
||||||
|
this.components.push(...normalizeArray(components).map((component) => resolveBuilder(component, FileBuilder)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds media gallery components to this container.
|
||||||
|
*
|
||||||
|
* @param components - The media gallery components to add
|
||||||
|
*/
|
||||||
|
public addMediaGalleryComponents(
|
||||||
|
...components: RestOrArray<
|
||||||
|
APIMediaGalleryComponent | MediaGalleryBuilder | ((builder: MediaGalleryBuilder) => MediaGalleryBuilder)
|
||||||
|
>
|
||||||
|
) {
|
||||||
|
this.components.push(
|
||||||
|
...normalizeArray(components).map((component) => resolveBuilder(component, MediaGalleryBuilder)),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds section components to this container.
|
||||||
|
*
|
||||||
|
* @param components - The section components to add
|
||||||
|
*/
|
||||||
|
public addSectionComponents(
|
||||||
|
...components: RestOrArray<APISectionComponent | SectionBuilder | ((builder: SectionBuilder) => SectionBuilder)>
|
||||||
|
) {
|
||||||
|
this.components.push(...normalizeArray(components).map((component) => resolveBuilder(component, SectionBuilder)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds separator components to this container.
|
||||||
|
*
|
||||||
|
* @param components - The separator components to add
|
||||||
|
*/
|
||||||
|
public addSeparatorComponents(
|
||||||
|
...components: RestOrArray<
|
||||||
|
APISeparatorComponent | SeparatorBuilder | ((builder: SeparatorBuilder) => SeparatorBuilder)
|
||||||
|
>
|
||||||
|
) {
|
||||||
|
this.components.push(...normalizeArray(components).map((component) => resolveBuilder(component, SeparatorBuilder)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds text display components to this container.
|
||||||
|
*
|
||||||
|
* @param components - The text display components to add
|
||||||
|
*/
|
||||||
|
public addTextDisplayComponents(
|
||||||
|
...components: RestOrArray<
|
||||||
|
APITextDisplayComponent | TextDisplayBuilder | ((builder: TextDisplayBuilder) => TextDisplayBuilder)
|
||||||
|
>
|
||||||
|
) {
|
||||||
|
this.components.push(
|
||||||
|
...normalizeArray(components).map((component) => resolveBuilder(component, TextDisplayBuilder)),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes, replaces, or inserts components for this container.
|
||||||
|
*
|
||||||
|
* @param index - The index to start removing, replacing or inserting components
|
||||||
|
* @param deleteCount - The amount of components to remove
|
||||||
|
* @param components - The components to set
|
||||||
|
*/
|
||||||
|
public spliceComponents(
|
||||||
|
index: number,
|
||||||
|
deleteCount: number,
|
||||||
|
...components: RestOrArray<APIComponentInContainer | ContainerComponentBuilder>
|
||||||
|
) {
|
||||||
|
this.components.splice(
|
||||||
|
index,
|
||||||
|
deleteCount,
|
||||||
|
...normalizeArray(components).map((component) =>
|
||||||
|
component instanceof ComponentBuilder ? component : createComponentBuilder(component),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the spoiler status of this container.
|
||||||
|
*
|
||||||
|
* @param spoiler - The spoiler status to use
|
||||||
|
*/
|
||||||
|
public setSpoiler(spoiler = true) {
|
||||||
|
this.data.spoiler = spoilerPredicate.parse(spoiler);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc ComponentBuilder.toJSON}
|
||||||
|
*/
|
||||||
|
public toJSON(): APIContainerComponent {
|
||||||
|
return {
|
||||||
|
...this.data,
|
||||||
|
components: this.components.map((component) => component.toJSON()),
|
||||||
|
} as APIContainerComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
packages/builders/src/components/v2/File.ts
Normal file
63
packages/builders/src/components/v2/File.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { ComponentType, type APIFileComponent } from 'discord-api-types/v10';
|
||||||
|
import { ComponentBuilder } from '../Component';
|
||||||
|
import { filePredicate, spoilerPredicate } from './Assertions';
|
||||||
|
|
||||||
|
export class FileBuilder extends ComponentBuilder<APIFileComponent> {
|
||||||
|
/**
|
||||||
|
* Creates a new file from API data.
|
||||||
|
*
|
||||||
|
* @param data - The API data to create this file with
|
||||||
|
* @example
|
||||||
|
* Creating a file from an API data object:
|
||||||
|
* ```ts
|
||||||
|
* const file = new FileBuilder({
|
||||||
|
* spoiler: true,
|
||||||
|
* file: {
|
||||||
|
* url: 'attachment://file.png',
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* Creating a file using setters and API data:
|
||||||
|
* ```ts
|
||||||
|
* const file = new FileBuilder({
|
||||||
|
* file: {
|
||||||
|
* url: 'attachment://image.jpg',
|
||||||
|
* },
|
||||||
|
* })
|
||||||
|
* .setSpoiler(false);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public constructor(data: Partial<APIFileComponent> = {}) {
|
||||||
|
super({ type: ComponentType.File, ...data, file: data.file ? { url: data.file.url } : undefined });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the spoiler status of this file.
|
||||||
|
*
|
||||||
|
* @param spoiler - The spoiler status to use
|
||||||
|
*/
|
||||||
|
public setSpoiler(spoiler = true) {
|
||||||
|
this.data.spoiler = spoilerPredicate.parse(spoiler);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the media URL of this file.
|
||||||
|
*
|
||||||
|
* @param url - The URL to use
|
||||||
|
*/
|
||||||
|
public setURL(url: string) {
|
||||||
|
this.data.file = filePredicate.parse({ url });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc ComponentBuilder.toJSON}
|
||||||
|
*/
|
||||||
|
public override toJSON(): APIFileComponent {
|
||||||
|
filePredicate.parse(this.data.file);
|
||||||
|
|
||||||
|
return { ...this.data, file: { ...this.data.file } } as APIFileComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
117
packages/builders/src/components/v2/MediaGallery.ts
Normal file
117
packages/builders/src/components/v2/MediaGallery.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
/* eslint-disable jsdoc/check-param-names */
|
||||||
|
|
||||||
|
import type { APIMediaGalleryComponent, APIMediaGalleryItem } from 'discord-api-types/v10';
|
||||||
|
import { ComponentType } from 'discord-api-types/v10';
|
||||||
|
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
|
||||||
|
import { ComponentBuilder } from '../Component.js';
|
||||||
|
import { resolveBuilder } from '../Components.js';
|
||||||
|
import { assertReturnOfBuilder, validateComponentArray } from './Assertions.js';
|
||||||
|
import { MediaGalleryItemBuilder } from './MediaGalleryItem.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder that creates API-compatible JSON data for a container.
|
||||||
|
*/
|
||||||
|
export class MediaGalleryBuilder extends ComponentBuilder<APIMediaGalleryComponent> {
|
||||||
|
/**
|
||||||
|
* The components within this container.
|
||||||
|
*/
|
||||||
|
public readonly items: MediaGalleryItemBuilder[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new media gallery from API data.
|
||||||
|
*
|
||||||
|
* @param data - The API data to create this media gallery with
|
||||||
|
* @example
|
||||||
|
* Creating a media gallery from an API data object:
|
||||||
|
* ```ts
|
||||||
|
* const mediaGallery = new MediaGalleryBuilder({
|
||||||
|
* items: [
|
||||||
|
* {
|
||||||
|
* description: "Some text here",
|
||||||
|
* media: {
|
||||||
|
* url: 'https://cdn.discordapp.com/embed/avatars/2.png',
|
||||||
|
* },
|
||||||
|
* },
|
||||||
|
* ],
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* Creating a media gallery using setters and API data:
|
||||||
|
* ```ts
|
||||||
|
* const mediaGallery = new MediaGalleryBuilder({
|
||||||
|
* items: [
|
||||||
|
* {
|
||||||
|
* description: "alt text",
|
||||||
|
* media: {
|
||||||
|
* url: 'https://cdn.discordapp.com/embed/avatars/5.png',
|
||||||
|
* },
|
||||||
|
* },
|
||||||
|
* ],
|
||||||
|
* })
|
||||||
|
* .addItems(item2, item3);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public constructor({ items, ...data }: Partial<APIMediaGalleryComponent> = {}) {
|
||||||
|
super({ type: ComponentType.MediaGallery, ...data });
|
||||||
|
this.items = items?.map((item) => new MediaGalleryItemBuilder(item)) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds items to this media gallery.
|
||||||
|
*
|
||||||
|
* @param items - The items to add
|
||||||
|
*/
|
||||||
|
public addItems(
|
||||||
|
...items: RestOrArray<
|
||||||
|
APIMediaGalleryItem | MediaGalleryItemBuilder | ((builder: MediaGalleryItemBuilder) => MediaGalleryItemBuilder)
|
||||||
|
>
|
||||||
|
) {
|
||||||
|
this.items.push(
|
||||||
|
...normalizeArray(items).map((input) => {
|
||||||
|
const result = resolveBuilder(input, MediaGalleryItemBuilder);
|
||||||
|
|
||||||
|
assertReturnOfBuilder(result, MediaGalleryItemBuilder);
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes, replaces, or inserts media gallery items for this media gallery.
|
||||||
|
*
|
||||||
|
* @param index - The index to start removing, replacing or inserting items
|
||||||
|
* @param deleteCount - The amount of items to remove
|
||||||
|
* @param items - The items to insert
|
||||||
|
*/
|
||||||
|
public spliceItems(
|
||||||
|
index: number,
|
||||||
|
deleteCount: number,
|
||||||
|
...items: RestOrArray<
|
||||||
|
APIMediaGalleryItem | MediaGalleryItemBuilder | ((builder: MediaGalleryItemBuilder) => MediaGalleryItemBuilder)
|
||||||
|
>
|
||||||
|
) {
|
||||||
|
this.items.splice(
|
||||||
|
index,
|
||||||
|
deleteCount,
|
||||||
|
...normalizeArray(items).map((input) => {
|
||||||
|
const result = resolveBuilder(input, MediaGalleryItemBuilder);
|
||||||
|
|
||||||
|
assertReturnOfBuilder(result, MediaGalleryItemBuilder);
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc ComponentBuilder.toJSON}
|
||||||
|
*/
|
||||||
|
public toJSON(): APIMediaGalleryComponent {
|
||||||
|
validateComponentArray(this.items, 1, 10, MediaGalleryItemBuilder);
|
||||||
|
return {
|
||||||
|
...this.data,
|
||||||
|
items: this.items.map((item) => item.toJSON()),
|
||||||
|
} as APIMediaGalleryComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
90
packages/builders/src/components/v2/MediaGalleryItem.ts
Normal file
90
packages/builders/src/components/v2/MediaGalleryItem.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import type { JSONEncodable } from '@discordjs/util';
|
||||||
|
import type { APIMediaGalleryItem } from 'discord-api-types/v10';
|
||||||
|
import { descriptionPredicate, spoilerPredicate, unfurledMediaItemPredicate } from './Assertions';
|
||||||
|
|
||||||
|
export class MediaGalleryItemBuilder implements JSONEncodable<APIMediaGalleryItem> {
|
||||||
|
/**
|
||||||
|
* The API data associated with this media gallery item.
|
||||||
|
*/
|
||||||
|
public readonly data: Partial<APIMediaGalleryItem>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new media gallery item from API data.
|
||||||
|
*
|
||||||
|
* @param data - The API data to create this media gallery item with
|
||||||
|
* @example
|
||||||
|
* Creating a media gallery item from an API data object:
|
||||||
|
* ```ts
|
||||||
|
* const item = new MediaGalleryItemBuilder({
|
||||||
|
* description: "Some text here",
|
||||||
|
* media: {
|
||||||
|
* url: 'https://cdn.discordapp.com/embed/avatars/2.png',
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* Creating a media gallery item using setters and API data:
|
||||||
|
* ```ts
|
||||||
|
* const item = new MediaGalleryItemBuilder({
|
||||||
|
* media: {
|
||||||
|
* url: 'https://cdn.discordapp.com/embed/avatars/5.png',
|
||||||
|
* },
|
||||||
|
* })
|
||||||
|
* .setDescription("alt text");
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public constructor(data: Partial<APIMediaGalleryItem> = {}) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the description of this media gallery item.
|
||||||
|
*
|
||||||
|
* @param description - The description to use
|
||||||
|
*/
|
||||||
|
public setDescription(description: string) {
|
||||||
|
this.data.description = descriptionPredicate.parse(description);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the description of this media gallery item.
|
||||||
|
*/
|
||||||
|
public clearDescription() {
|
||||||
|
this.data.description = undefined;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the spoiler status of this media gallery item.
|
||||||
|
*
|
||||||
|
* @param spoiler - The spoiler status to use
|
||||||
|
*/
|
||||||
|
public setSpoiler(spoiler = true) {
|
||||||
|
this.data.spoiler = spoilerPredicate.parse(spoiler);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the media URL of this media gallery item.
|
||||||
|
*
|
||||||
|
* @param url - The URL to use
|
||||||
|
*/
|
||||||
|
public setURL(url: string) {
|
||||||
|
this.data.media = unfurledMediaItemPredicate.parse({ url });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes this builder to API-compatible JSON data.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* This method runs validations on the data before serializing it.
|
||||||
|
* As such, it may throw an error if the data is invalid.
|
||||||
|
*/
|
||||||
|
public toJSON(): APIMediaGalleryItem {
|
||||||
|
unfurledMediaItemPredicate.parse(this.data.media);
|
||||||
|
|
||||||
|
return { ...this.data } as APIMediaGalleryItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
153
packages/builders/src/components/v2/Section.ts
Normal file
153
packages/builders/src/components/v2/Section.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
/* eslint-disable jsdoc/check-param-names */
|
||||||
|
|
||||||
|
import type {
|
||||||
|
APIButtonComponent,
|
||||||
|
APISectionComponent,
|
||||||
|
APITextDisplayComponent,
|
||||||
|
APIThumbnailComponent,
|
||||||
|
} from 'discord-api-types/v10';
|
||||||
|
import { ComponentType } from 'discord-api-types/v10';
|
||||||
|
import { ButtonBuilder, ThumbnailBuilder } from '../../index.js';
|
||||||
|
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
|
||||||
|
import { ComponentBuilder } from '../Component.js';
|
||||||
|
import { createComponentBuilder, resolveBuilder } from '../Components.js';
|
||||||
|
import { accessoryPredicate, assertReturnOfBuilder, validateComponentArray } from './Assertions.js';
|
||||||
|
import { TextDisplayBuilder } from './TextDisplay.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder that creates API-compatible JSON data for a section.
|
||||||
|
*/
|
||||||
|
export class SectionBuilder extends ComponentBuilder<APISectionComponent> {
|
||||||
|
/**
|
||||||
|
* The components within this section.
|
||||||
|
*/
|
||||||
|
public readonly components: ComponentBuilder[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The accessory of this section.
|
||||||
|
*/
|
||||||
|
public readonly accessory?: ButtonBuilder | ThumbnailBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new section from API data.
|
||||||
|
*
|
||||||
|
* @param data - The API data to create this section with
|
||||||
|
* @example
|
||||||
|
* Creating a section from an API data object:
|
||||||
|
* ```ts
|
||||||
|
* const section = new SectionBuilder({
|
||||||
|
* components: [
|
||||||
|
* {
|
||||||
|
* content: "Some text here",
|
||||||
|
* type: ComponentType.TextDisplay,
|
||||||
|
* },
|
||||||
|
* ],
|
||||||
|
* accessory: {
|
||||||
|
* media: {
|
||||||
|
* url: 'https://cdn.discordapp.com/embed/avatars/3.png',
|
||||||
|
* },
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* Creating a section using setters and API data:
|
||||||
|
* ```ts
|
||||||
|
* const section = new SectionBuilder({
|
||||||
|
* components: [
|
||||||
|
* {
|
||||||
|
* content: "# Heading",
|
||||||
|
* type: ComponentType.TextDisplay,
|
||||||
|
* },
|
||||||
|
* ],
|
||||||
|
* })
|
||||||
|
* .setPrimaryButtonAccessory(button);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public constructor({ components, accessory, ...data }: Partial<APISectionComponent> = {}) {
|
||||||
|
super({ type: ComponentType.Section, ...data });
|
||||||
|
this.components = (components?.map((component) => createComponentBuilder(component)) ?? []) as ComponentBuilder[];
|
||||||
|
this.accessory = accessory ? createComponentBuilder(accessory) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the accessory of this section to a button.
|
||||||
|
*
|
||||||
|
* @param accessory - The accessory to use
|
||||||
|
*/
|
||||||
|
public setButtonAccessory(
|
||||||
|
accessory: APIButtonComponent | ButtonBuilder | ((builder: ButtonBuilder) => ButtonBuilder),
|
||||||
|
): this {
|
||||||
|
Reflect.set(this, 'accessory', accessoryPredicate.parse(resolveBuilder(accessory, ButtonBuilder)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the accessory of this section to a thumbnail.
|
||||||
|
*
|
||||||
|
* @param accessory - The accessory to use
|
||||||
|
*/
|
||||||
|
public setThumbnailAccessory(
|
||||||
|
accessory: APIThumbnailComponent | ThumbnailBuilder | ((builder: ThumbnailBuilder) => ThumbnailBuilder),
|
||||||
|
): this {
|
||||||
|
Reflect.set(this, 'accessory', accessoryPredicate.parse(resolveBuilder(accessory, ThumbnailBuilder)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds text display components to this section.
|
||||||
|
*
|
||||||
|
* @param components - The text display components to add
|
||||||
|
*/
|
||||||
|
public addTextDisplayComponents(
|
||||||
|
...components: RestOrArray<TextDisplayBuilder | ((builder: TextDisplayBuilder) => TextDisplayBuilder)>
|
||||||
|
) {
|
||||||
|
this.components.push(
|
||||||
|
...normalizeArray(components).map((input) => {
|
||||||
|
const result = resolveBuilder(input, TextDisplayBuilder);
|
||||||
|
|
||||||
|
assertReturnOfBuilder(result, TextDisplayBuilder);
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes, replaces, or inserts text display components for this section.
|
||||||
|
*
|
||||||
|
* @param index - The index to start removing, replacing or inserting text display components
|
||||||
|
* @param deleteCount - The amount of text display components to remove
|
||||||
|
* @param components - The text display components to insert
|
||||||
|
*/
|
||||||
|
public spliceTextDisplayComponents(
|
||||||
|
index: number,
|
||||||
|
deleteCount: number,
|
||||||
|
...components: RestOrArray<
|
||||||
|
APITextDisplayComponent | TextDisplayBuilder | ((builder: TextDisplayBuilder) => TextDisplayBuilder)
|
||||||
|
>
|
||||||
|
) {
|
||||||
|
this.components.splice(
|
||||||
|
index,
|
||||||
|
deleteCount,
|
||||||
|
...normalizeArray(components).map((input) => {
|
||||||
|
const result = resolveBuilder(input, TextDisplayBuilder);
|
||||||
|
|
||||||
|
assertReturnOfBuilder(result, TextDisplayBuilder);
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc ComponentBuilder.toJSON}
|
||||||
|
*/
|
||||||
|
public toJSON(): APISectionComponent {
|
||||||
|
validateComponentArray(this.components, 1, 3, TextDisplayBuilder);
|
||||||
|
return {
|
||||||
|
...this.data,
|
||||||
|
components: this.components.map((component) => component.toJSON()),
|
||||||
|
accessory: accessoryPredicate.parse(this.accessory).toJSON(),
|
||||||
|
} as APISectionComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
69
packages/builders/src/components/v2/Separator.ts
Normal file
69
packages/builders/src/components/v2/Separator.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import type { SeparatorSpacingSize, APISeparatorComponent } from 'discord-api-types/v10';
|
||||||
|
import { ComponentType } from 'discord-api-types/v10';
|
||||||
|
import { ComponentBuilder } from '../Component';
|
||||||
|
import { dividerPredicate, spacingPredicate } from './Assertions';
|
||||||
|
|
||||||
|
export class SeparatorBuilder extends ComponentBuilder<APISeparatorComponent> {
|
||||||
|
/**
|
||||||
|
* Creates a new separator from API data.
|
||||||
|
*
|
||||||
|
* @param data - The API data to create this separator with
|
||||||
|
* @example
|
||||||
|
* Creating a separator from an API data object:
|
||||||
|
* ```ts
|
||||||
|
* const separator = new SeparatorBuilder({
|
||||||
|
* spacing: SeparatorSpacingSize.Small,
|
||||||
|
* divider: true,
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* Creating a separator using setters and API data:
|
||||||
|
* ```ts
|
||||||
|
* const separator = new SeparatorBuilder({
|
||||||
|
* spacing: SeparatorSpacingSize.Large,
|
||||||
|
* })
|
||||||
|
* .setDivider(false);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public constructor(data: Partial<APISeparatorComponent> = {}) {
|
||||||
|
super({
|
||||||
|
type: ComponentType.Separator,
|
||||||
|
...data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether this separator should show a divider line.
|
||||||
|
*
|
||||||
|
* @param divider - Whether to show a divider line
|
||||||
|
*/
|
||||||
|
public setDivider(divider = true) {
|
||||||
|
this.data.divider = dividerPredicate.parse(divider);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the spacing of this separator.
|
||||||
|
*
|
||||||
|
* @param spacing - The spacing to use
|
||||||
|
*/
|
||||||
|
public setSpacing(spacing: SeparatorSpacingSize) {
|
||||||
|
this.data.spacing = spacingPredicate.parse(spacing);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the spacing of this separator.
|
||||||
|
*/
|
||||||
|
public clearSpacing() {
|
||||||
|
this.data.spacing = undefined;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc ComponentBuilder.toJSON}
|
||||||
|
*/
|
||||||
|
public override toJSON(): APISeparatorComponent {
|
||||||
|
return { ...this.data } as APISeparatorComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
packages/builders/src/components/v2/TextDisplay.ts
Normal file
52
packages/builders/src/components/v2/TextDisplay.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import type { APITextDisplayComponent } from 'discord-api-types/v10';
|
||||||
|
import { ComponentType } from 'discord-api-types/v10';
|
||||||
|
import { ComponentBuilder } from '../Component';
|
||||||
|
import { textDisplayContentPredicate } from './Assertions';
|
||||||
|
|
||||||
|
export class TextDisplayBuilder extends ComponentBuilder<APITextDisplayComponent> {
|
||||||
|
/**
|
||||||
|
* Creates a new text display from API data.
|
||||||
|
*
|
||||||
|
* @param data - The API data to create this text display with
|
||||||
|
* @example
|
||||||
|
* Creating a text display from an API data object:
|
||||||
|
* ```ts
|
||||||
|
* const textDisplay = new TextDisplayBuilder({
|
||||||
|
* content: 'some text',
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* Creating a text display using setters and API data:
|
||||||
|
* ```ts
|
||||||
|
* const textDisplay = new TextDisplayBuilder({
|
||||||
|
* content: 'old text',
|
||||||
|
* })
|
||||||
|
* .setContent('new text');
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public constructor(data: Partial<APITextDisplayComponent> = {}) {
|
||||||
|
super({
|
||||||
|
type: ComponentType.TextDisplay,
|
||||||
|
...data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the text of this text display.
|
||||||
|
*
|
||||||
|
* @param content - The text to use
|
||||||
|
*/
|
||||||
|
public setContent(content: string) {
|
||||||
|
this.data.content = textDisplayContentPredicate.parse(content);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc ComponentBuilder.toJSON}
|
||||||
|
*/
|
||||||
|
public override toJSON(): APITextDisplayComponent {
|
||||||
|
textDisplayContentPredicate.parse(this.data.content);
|
||||||
|
|
||||||
|
return { ...this.data } as APITextDisplayComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
86
packages/builders/src/components/v2/Thumbnail.ts
Normal file
86
packages/builders/src/components/v2/Thumbnail.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import type { APIThumbnailComponent } from 'discord-api-types/v10';
|
||||||
|
import { ComponentType } from 'discord-api-types/v10';
|
||||||
|
import { ComponentBuilder } from '../Component';
|
||||||
|
import { descriptionPredicate, spoilerPredicate, unfurledMediaItemPredicate } from './Assertions';
|
||||||
|
|
||||||
|
export class ThumbnailBuilder extends ComponentBuilder<APIThumbnailComponent> {
|
||||||
|
/**
|
||||||
|
* Creates a new thumbnail from API data.
|
||||||
|
*
|
||||||
|
* @param data - The API data to create this thumbnail with
|
||||||
|
* @example
|
||||||
|
* Creating a thumbnail from an API data object:
|
||||||
|
* ```ts
|
||||||
|
* const thumbnail = new ThumbnailBuilder({
|
||||||
|
* description: 'some text',
|
||||||
|
* media: {
|
||||||
|
* url: 'https://cdn.discordapp.com/embed/avatars/4.png',
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* Creating a thumbnail using setters and API data:
|
||||||
|
* ```ts
|
||||||
|
* const thumbnail = new ThumbnailBuilder({
|
||||||
|
* media: {
|
||||||
|
* url: 'attachment://image.png',
|
||||||
|
* },
|
||||||
|
* })
|
||||||
|
* .setDescription('alt text');
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public constructor(data: Partial<APIThumbnailComponent> = {}) {
|
||||||
|
super({
|
||||||
|
type: ComponentType.Thumbnail,
|
||||||
|
...data,
|
||||||
|
media: data.media ? { url: data.media.url } : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the description of this thumbnail.
|
||||||
|
*
|
||||||
|
* @param description - The description to use
|
||||||
|
*/
|
||||||
|
public setDescription(description: string) {
|
||||||
|
this.data.description = descriptionPredicate.parse(description);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the description of this thumbnail.
|
||||||
|
*/
|
||||||
|
public clearDescription() {
|
||||||
|
this.data.description = undefined;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the spoiler status of this thumbnail.
|
||||||
|
*
|
||||||
|
* @param spoiler - The spoiler status to use
|
||||||
|
*/
|
||||||
|
public setSpoiler(spoiler = true) {
|
||||||
|
this.data.spoiler = spoilerPredicate.parse(spoiler);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the media URL of this thumbnail.
|
||||||
|
*
|
||||||
|
* @param url - The URL to use
|
||||||
|
*/
|
||||||
|
public setURL(url: string) {
|
||||||
|
this.data.media = unfurledMediaItemPredicate.parse({ url });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc ComponentBuilder.toJSON}
|
||||||
|
*/
|
||||||
|
public override toJSON(): APIThumbnailComponent {
|
||||||
|
unfurledMediaItemPredicate.parse(this.data.media);
|
||||||
|
|
||||||
|
return { ...this.data } as APIThumbnailComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,6 +34,22 @@ 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 * as LabelAssertions from './components/label/Assertions.js';
|
||||||
|
|
||||||
|
export * as ComponentsV2Assertions from './components/v2/Assertions.js';
|
||||||
|
export * from './components/v2/Container.js';
|
||||||
|
export * from './components/v2/File.js';
|
||||||
|
export * from './components/v2/MediaGallery.js';
|
||||||
|
export * from './components/v2/MediaGalleryItem.js';
|
||||||
|
export * from './components/v2/Section.js';
|
||||||
|
export * from './components/v2/Separator.js';
|
||||||
|
export * from './components/v2/TextDisplay.js';
|
||||||
|
export * from './components/v2/Thumbnail.js';
|
||||||
|
|
||||||
export * as SlashCommandAssertions from './interactions/slashCommands/Assertions.js';
|
export * as SlashCommandAssertions from './interactions/slashCommands/Assertions.js';
|
||||||
export * from './interactions/slashCommands/SlashCommandBuilder.js';
|
export * from './interactions/slashCommands/SlashCommandBuilder.js';
|
||||||
export * from './interactions/slashCommands/SlashCommandSubcommands.js';
|
export * from './interactions/slashCommands/SlashCommandSubcommands.js';
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ const namePredicate = s
|
|||||||
.string()
|
.string()
|
||||||
.lengthGreaterThanOrEqual(1)
|
.lengthGreaterThanOrEqual(1)
|
||||||
.lengthLessThanOrEqual(32)
|
.lengthLessThanOrEqual(32)
|
||||||
// eslint-disable-next-line prefer-named-capture-group
|
.regex(/\S/)
|
||||||
.regex(/^( *[\p{P}\p{L}\p{N}\p{sc=Devanagari}\p{sc=Thai}]+ *)+$/u)
|
|
||||||
.setValidationEnabled(isValidationEnabled);
|
.setValidationEnabled(isValidationEnabled);
|
||||||
const typePredicate = s
|
const typePredicate = s
|
||||||
.union([s.literal(ApplicationCommandType.User), s.literal(ApplicationCommandType.Message)])
|
.union([s.literal(ApplicationCommandType.User), s.literal(ApplicationCommandType.Message)])
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { s } from '@sapphire/shapeshift';
|
import { s } from '@sapphire/shapeshift';
|
||||||
import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../../components/ActionRow.js';
|
import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../../components/ActionRow.js';
|
||||||
import { customIdValidator } from '../../components/Assertions.js';
|
import { customIdValidator } from '../../components/Assertions.js';
|
||||||
|
import { LabelBuilder } from '../../components/label/Label.js';
|
||||||
|
import { TextDisplayBuilder } from '../../components/v2/TextDisplay.js';
|
||||||
import { isValidationEnabled } from '../../util/validation.js';
|
import { isValidationEnabled } from '../../util/validation.js';
|
||||||
|
|
||||||
export const titleValidator = s
|
export const titleValidator = s
|
||||||
@@ -9,7 +11,7 @@ export const titleValidator = s
|
|||||||
.lengthLessThanOrEqual(45)
|
.lengthLessThanOrEqual(45)
|
||||||
.setValidationEnabled(isValidationEnabled);
|
.setValidationEnabled(isValidationEnabled);
|
||||||
export const componentsValidator = s
|
export const componentsValidator = s
|
||||||
.instance(ActionRowBuilder)
|
.union([s.instance(ActionRowBuilder), s.instance(LabelBuilder), s.instance(TextDisplayBuilder)])
|
||||||
.array()
|
.array()
|
||||||
.lengthGreaterThanOrEqual(1)
|
.lengthGreaterThanOrEqual(1)
|
||||||
.setValidationEnabled(isValidationEnabled);
|
.setValidationEnabled(isValidationEnabled);
|
||||||
@@ -17,7 +19,7 @@ export const componentsValidator = s
|
|||||||
export function validateRequiredParameters(
|
export function validateRequiredParameters(
|
||||||
customId?: string,
|
customId?: string,
|
||||||
title?: string,
|
title?: string,
|
||||||
components?: ActionRowBuilder<ModalActionRowComponentBuilder>[],
|
components?: (ActionRowBuilder<ModalActionRowComponentBuilder> | LabelBuilder | TextDisplayBuilder)[],
|
||||||
) {
|
) {
|
||||||
customIdValidator.parse(customId);
|
customIdValidator.parse(customId);
|
||||||
titleValidator.parse(title);
|
titleValidator.parse(title);
|
||||||
|
|||||||
@@ -2,13 +2,20 @@
|
|||||||
|
|
||||||
import type { JSONEncodable } from '@discordjs/util';
|
import type { JSONEncodable } from '@discordjs/util';
|
||||||
import type {
|
import type {
|
||||||
|
APITextInputComponent,
|
||||||
APIActionRowComponent,
|
APIActionRowComponent,
|
||||||
APIModalActionRowComponent,
|
APIComponentInModalActionRow,
|
||||||
|
APILabelComponent,
|
||||||
APIModalInteractionResponseCallbackData,
|
APIModalInteractionResponseCallbackData,
|
||||||
|
APITextDisplayComponent,
|
||||||
} from 'discord-api-types/v10';
|
} from 'discord-api-types/v10';
|
||||||
|
import { ComponentType } from 'discord-api-types/v10';
|
||||||
import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../../components/ActionRow.js';
|
import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../../components/ActionRow.js';
|
||||||
import { customIdValidator } from '../../components/Assertions.js';
|
import { customIdValidator } from '../../components/Assertions.js';
|
||||||
import { createComponentBuilder } from '../../components/Components.js';
|
import { createComponentBuilder, resolveBuilder } from '../../components/Components.js';
|
||||||
|
import { LabelBuilder } from '../../components/label/Label.js';
|
||||||
|
import { TextInputBuilder } from '../../components/textInput/TextInput.js';
|
||||||
|
import { TextDisplayBuilder } from '../../components/v2/TextDisplay.js';
|
||||||
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
|
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
|
||||||
import { titleValidator, validateRequiredParameters } from './Assertions.js';
|
import { titleValidator, validateRequiredParameters } from './Assertions.js';
|
||||||
|
|
||||||
@@ -24,7 +31,8 @@ export class ModalBuilder implements JSONEncodable<APIModalInteractionResponseCa
|
|||||||
/**
|
/**
|
||||||
* The components within this modal.
|
* The components within this modal.
|
||||||
*/
|
*/
|
||||||
public readonly components: ActionRowBuilder<ModalActionRowComponentBuilder>[] = [];
|
public readonly components: (ActionRowBuilder<ModalActionRowComponentBuilder> | LabelBuilder | TextDisplayBuilder)[] =
|
||||||
|
[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new modal from API data.
|
* Creates a new modal from API data.
|
||||||
@@ -33,8 +41,10 @@ export class ModalBuilder implements JSONEncodable<APIModalInteractionResponseCa
|
|||||||
*/
|
*/
|
||||||
public constructor({ components, ...data }: Partial<APIModalInteractionResponseCallbackData> = {}) {
|
public constructor({ components, ...data }: Partial<APIModalInteractionResponseCallbackData> = {}) {
|
||||||
this.data = { ...data };
|
this.data = { ...data };
|
||||||
this.components = (components?.map((component) => createComponentBuilder(component)) ??
|
this.components = (components?.map((component) => createComponentBuilder(component)) ?? []) as (
|
||||||
[]) as ActionRowBuilder<ModalActionRowComponentBuilder>[];
|
| ActionRowBuilder<ModalActionRowComponentBuilder>
|
||||||
|
| LabelBuilder
|
||||||
|
)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,19 +71,170 @@ export class ModalBuilder implements JSONEncodable<APIModalInteractionResponseCa
|
|||||||
* Adds components to this modal.
|
* Adds components to this modal.
|
||||||
*
|
*
|
||||||
* @param components - The components to add
|
* @param components - The components to add
|
||||||
|
* @deprecated Use {@link ModalBuilder.addLabelComponents} or {@link ModalBuilder.addTextDisplayComponents} instead
|
||||||
*/
|
*/
|
||||||
public addComponents(
|
public addComponents(
|
||||||
...components: RestOrArray<
|
...components: RestOrArray<
|
||||||
ActionRowBuilder<ModalActionRowComponentBuilder> | APIActionRowComponent<APIModalActionRowComponent>
|
| ActionRowBuilder<ModalActionRowComponentBuilder>
|
||||||
|
| APIActionRowComponent<APIComponentInModalActionRow>
|
||||||
|
| APILabelComponent
|
||||||
|
| APITextDisplayComponent
|
||||||
|
| APITextInputComponent
|
||||||
|
| LabelBuilder
|
||||||
|
| TextDisplayBuilder
|
||||||
|
| TextInputBuilder
|
||||||
>
|
>
|
||||||
) {
|
) {
|
||||||
this.components.push(
|
this.components.push(
|
||||||
...normalizeArray(components).map((component) =>
|
...normalizeArray(components).map((component, idx) => {
|
||||||
component instanceof ActionRowBuilder
|
if (
|
||||||
? component
|
component instanceof ActionRowBuilder ||
|
||||||
: new ActionRowBuilder<ModalActionRowComponentBuilder>(component),
|
component instanceof LabelBuilder ||
|
||||||
),
|
component instanceof TextDisplayBuilder
|
||||||
|
) {
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated support
|
||||||
|
if (component instanceof TextInputBuilder) {
|
||||||
|
return new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('type' in component) {
|
||||||
|
if (component.type === ComponentType.ActionRow) {
|
||||||
|
return new ActionRowBuilder<ModalActionRowComponentBuilder>(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.type === ComponentType.Label) {
|
||||||
|
return new LabelBuilder(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.type === ComponentType.TextDisplay) {
|
||||||
|
return new TextDisplayBuilder(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated, should go in a label component
|
||||||
|
if (component.type === ComponentType.TextInput) {
|
||||||
|
return new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(
|
||||||
|
new TextInputBuilder(component),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TypeError(`Invalid component passed in ModalBuilder.addComponents at index ${idx}!`);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds label components to this modal.
|
||||||
|
*
|
||||||
|
* @param components - The components to add
|
||||||
|
*/
|
||||||
|
public addLabelComponents(
|
||||||
|
...components: RestOrArray<APILabelComponent | LabelBuilder | ((builder: LabelBuilder) => LabelBuilder)>
|
||||||
|
) {
|
||||||
|
const normalized = normalizeArray(components);
|
||||||
|
const resolved = normalized.map((label) => resolveBuilder(label, LabelBuilder));
|
||||||
|
|
||||||
|
this.components.push(...resolved);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds text display components to this modal.
|
||||||
|
*
|
||||||
|
* @param components - The components to add
|
||||||
|
*/
|
||||||
|
public addTextDisplayComponents(
|
||||||
|
...components: RestOrArray<
|
||||||
|
APITextDisplayComponent | TextDisplayBuilder | ((builder: TextDisplayBuilder) => TextDisplayBuilder)
|
||||||
|
>
|
||||||
|
) {
|
||||||
|
const normalized = normalizeArray(components);
|
||||||
|
const resolved = normalized.map((row) => resolveBuilder(row, TextDisplayBuilder));
|
||||||
|
|
||||||
|
this.components.push(...resolved);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds action rows to this modal.
|
||||||
|
*
|
||||||
|
* @param components - The components to add
|
||||||
|
* @deprecated Use {@link ModalBuilder.addLabelComponents} instead
|
||||||
|
*/
|
||||||
|
public addActionRowComponents(
|
||||||
|
...components: RestOrArray<
|
||||||
|
| ActionRowBuilder<ModalActionRowComponentBuilder>
|
||||||
|
| APIActionRowComponent<APIComponentInModalActionRow>
|
||||||
|
| ((
|
||||||
|
builder: ActionRowBuilder<ModalActionRowComponentBuilder>,
|
||||||
|
) => ActionRowBuilder<ModalActionRowComponentBuilder>)
|
||||||
|
>
|
||||||
|
) {
|
||||||
|
const normalized = normalizeArray(components);
|
||||||
|
const resolved = normalized.map((row) => resolveBuilder(row, ActionRowBuilder<ModalActionRowComponentBuilder>));
|
||||||
|
|
||||||
|
this.components.push(...resolved);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the labels for this modal.
|
||||||
|
*
|
||||||
|
* @param components - The components to set
|
||||||
|
*/
|
||||||
|
public setLabelComponents(
|
||||||
|
...components: RestOrArray<APILabelComponent | LabelBuilder | ((builder: LabelBuilder) => LabelBuilder)>
|
||||||
|
) {
|
||||||
|
const normalized = normalizeArray(components);
|
||||||
|
this.spliceLabelComponents(0, this.components.length, ...normalized);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes, replaces, or inserts labels for this modal.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* This method behaves similarly
|
||||||
|
* to {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/splice | Array.prototype.splice()}.
|
||||||
|
* The maximum amount of labels that can be added is 5.
|
||||||
|
*
|
||||||
|
* It's useful for modifying and adjusting order of the already-existing labels of a modal.
|
||||||
|
* @example
|
||||||
|
* Remove the first label:
|
||||||
|
* ```ts
|
||||||
|
* modal.spliceLabelComponents(0, 1);
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* Remove the first n labels:
|
||||||
|
* ```ts
|
||||||
|
* const n = 4;
|
||||||
|
* modal.spliceLabelComponents(0, n);
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* Remove the last label:
|
||||||
|
* ```ts
|
||||||
|
* modal.spliceLabelComponents(-1, 1);
|
||||||
|
* ```
|
||||||
|
* @param index - The index to start at
|
||||||
|
* @param deleteCount - The number of labels to remove
|
||||||
|
* @param labels - The replacing label objects
|
||||||
|
*/
|
||||||
|
public spliceLabelComponents(
|
||||||
|
index: number,
|
||||||
|
deleteCount: number,
|
||||||
|
...labels: (APILabelComponent | LabelBuilder | ((builder: LabelBuilder) => LabelBuilder))[]
|
||||||
|
): this {
|
||||||
|
const resolved = labels.map((label) => resolveBuilder(label, LabelBuilder));
|
||||||
|
this.components.splice(index, deleteCount, ...resolved);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,8 +242,11 @@ export class ModalBuilder implements JSONEncodable<APIModalInteractionResponseCa
|
|||||||
* Sets components for this modal.
|
* Sets components for this modal.
|
||||||
*
|
*
|
||||||
* @param components - The components to set
|
* @param components - The components to set
|
||||||
|
* @deprecated Use {@link ModalBuilder.setLabelComponents} instead
|
||||||
*/
|
*/
|
||||||
public setComponents(...components: RestOrArray<ActionRowBuilder<ModalActionRowComponentBuilder>>) {
|
public setComponents(
|
||||||
|
...components: RestOrArray<ActionRowBuilder<ModalActionRowComponentBuilder> | LabelBuilder | TextDisplayBuilder>
|
||||||
|
) {
|
||||||
this.components.splice(0, this.components.length, ...normalizeArray(components));
|
this.components.splice(0, this.components.length, ...normalizeArray(components));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,9 @@ import { s } from '@sapphire/shapeshift';
|
|||||||
import type { APIEmbedField } from 'discord-api-types/v10';
|
import type { APIEmbedField } from 'discord-api-types/v10';
|
||||||
import { isValidationEnabled } from '../../util/validation.js';
|
import { isValidationEnabled } from '../../util/validation.js';
|
||||||
|
|
||||||
export const fieldNamePredicate = s
|
export const fieldNamePredicate = s.string().lengthLessThanOrEqual(256).setValidationEnabled(isValidationEnabled);
|
||||||
.string()
|
|
||||||
.lengthGreaterThanOrEqual(1)
|
|
||||||
.lengthLessThanOrEqual(256)
|
|
||||||
.setValidationEnabled(isValidationEnabled);
|
|
||||||
|
|
||||||
export const fieldValuePredicate = s
|
export const fieldValuePredicate = s.string().lengthLessThanOrEqual(1_024).setValidationEnabled(isValidationEnabled);
|
||||||
.string()
|
|
||||||
.lengthGreaterThanOrEqual(1)
|
|
||||||
.lengthLessThanOrEqual(1_024)
|
|
||||||
.setValidationEnabled(isValidationEnabled);
|
|
||||||
|
|
||||||
export const fieldInlinePredicate = s.boolean().optional();
|
export const fieldInlinePredicate = s.boolean().optional();
|
||||||
|
|
||||||
@@ -32,7 +24,10 @@ export function validateFieldLength(amountAdding: number, fields?: APIEmbedField
|
|||||||
fieldLengthPredicate.parse((fields?.length ?? 0) + amountAdding);
|
fieldLengthPredicate.parse((fields?.length ?? 0) + amountAdding);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const authorNamePredicate = fieldNamePredicate.nullable().setValidationEnabled(isValidationEnabled);
|
export const authorNamePredicate = fieldNamePredicate
|
||||||
|
.lengthGreaterThanOrEqual(1)
|
||||||
|
.nullable()
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|
||||||
export const imageURLPredicate = s
|
export const imageURLPredicate = s
|
||||||
.string()
|
.string()
|
||||||
@@ -96,4 +91,7 @@ export const embedFooterPredicate = s
|
|||||||
|
|
||||||
export const timestampPredicate = s.union([s.number(), s.date()]).nullable().setValidationEnabled(isValidationEnabled);
|
export const timestampPredicate = s.union([s.number(), s.date()]).nullable().setValidationEnabled(isValidationEnabled);
|
||||||
|
|
||||||
export const titlePredicate = fieldNamePredicate.nullable().setValidationEnabled(isValidationEnabled);
|
export const titlePredicate = fieldNamePredicate
|
||||||
|
.lengthGreaterThanOrEqual(1)
|
||||||
|
.nullable()
|
||||||
|
.setValidationEnabled(isValidationEnabled);
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
"@discordjs/ws": "workspace:^",
|
"@discordjs/ws": "workspace:^",
|
||||||
"@sapphire/snowflake": "^3.5.3",
|
"@sapphire/snowflake": "^3.5.3",
|
||||||
"@vladfrangu/async_event_emitter": "^2.4.6",
|
"@vladfrangu/async_event_emitter": "^2.4.6",
|
||||||
"discord-api-types": "^0.38.31"
|
"discord-api-types": "^0.38.32"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@discordjs/api-extractor": "workspace:^",
|
"@discordjs/api-extractor": "workspace:^",
|
||||||
|
|||||||
@@ -66,14 +66,14 @@
|
|||||||
"homepage": "https://discord.js.org",
|
"homepage": "https://discord.js.org",
|
||||||
"funding": "https://github.com/discordjs/discord.js?sponsor",
|
"funding": "https://github.com/discordjs/discord.js?sponsor",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/builders": "^1.13.0",
|
"@discordjs/builders": "workspace:^",
|
||||||
"@discordjs/collection": "1.5.3",
|
"@discordjs/collection": "1.5.3",
|
||||||
"@discordjs/formatters": "^0.6.1",
|
"@discordjs/formatters": "workspace:^",
|
||||||
"@discordjs/rest": "workspace:^",
|
"@discordjs/rest": "workspace:^",
|
||||||
"@discordjs/util": "workspace:^",
|
"@discordjs/util": "workspace:^",
|
||||||
"@discordjs/ws": "^1.2.3",
|
"@discordjs/ws": "^1.2.3",
|
||||||
"@sapphire/snowflake": "3.5.3",
|
"@sapphire/snowflake": "3.5.3",
|
||||||
"discord-api-types": "^0.38.31",
|
"discord-api-types": "^0.38.32",
|
||||||
"fast-deep-equal": "3.1.3",
|
"fast-deep-equal": "3.1.3",
|
||||||
"lodash.snakecase": "4.1.1",
|
"lodash.snakecase": "4.1.1",
|
||||||
"magic-bytes.js": "^1.10.0",
|
"magic-bytes.js": "^1.10.0",
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
**Node.js 18 or newer is required.**
|
**Node.js 16.11.0 or newer is required.**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm install @discordjs/formatters
|
npm install @discordjs/formatters
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@discordjs/formatters",
|
"name": "@discordjs/formatters",
|
||||||
"version": "0.5.0",
|
"version": "0.6.1",
|
||||||
"description": "A set of functions to format strings for Discord.",
|
"description": "A set of functions to format strings for Discord.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
"homepage": "https://discord.js.org",
|
"homepage": "https://discord.js.org",
|
||||||
"funding": "https://github.com/discordjs/discord.js?sponsor",
|
"funding": "https://github.com/discordjs/discord.js?sponsor",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"discord-api-types": "^0.38.24"
|
"discord-api-types": "^0.38.32"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@discordjs/api-extractor": "workspace:^",
|
"@discordjs/api-extractor": "workspace:^",
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
"vitest": "^2.0.5"
|
"vitest": "^2.0.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=16.11.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
"@discordjs/rest": "workspace:^",
|
"@discordjs/rest": "workspace:^",
|
||||||
"@discordjs/util": "workspace:^",
|
"@discordjs/util": "workspace:^",
|
||||||
"@discordjs/ws": "workspace:^",
|
"@discordjs/ws": "workspace:^",
|
||||||
"discord-api-types": "^0.38.24"
|
"discord-api-types": "^0.38.32"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@discordjs/api-extractor": "workspace:^",
|
"@discordjs/api-extractor": "workspace:^",
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
"@sapphire/async-queue": "^1.5.3",
|
"@sapphire/async-queue": "^1.5.3",
|
||||||
"@sapphire/snowflake": "^3.5.3",
|
"@sapphire/snowflake": "^3.5.3",
|
||||||
"@vladfrangu/async_event_emitter": "^2.4.6",
|
"@vladfrangu/async_event_emitter": "^2.4.6",
|
||||||
"discord-api-types": "^0.38.24",
|
"discord-api-types": "^0.38.32",
|
||||||
"magic-bytes.js": "^1.10.0",
|
"magic-bytes.js": "^1.10.0",
|
||||||
"tslib": "^2.6.3",
|
"tslib": "^2.6.3",
|
||||||
"undici": "6.21.3"
|
"undici": "6.21.3"
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
"funding": "https://github.com/discordjs/discord.js?sponsor",
|
"funding": "https://github.com/discordjs/discord.js?sponsor",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/ws": "^8.5.12",
|
"@types/ws": "^8.5.12",
|
||||||
"discord-api-types": "^0.38.24",
|
"discord-api-types": "^0.38.32",
|
||||||
"prism-media": "^1.3.5",
|
"prism-media": "^1.3.5",
|
||||||
"tslib": "^2.6.3",
|
"tslib": "^2.6.3",
|
||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0"
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
"@sapphire/async-queue": "^1.5.3",
|
"@sapphire/async-queue": "^1.5.3",
|
||||||
"@types/ws": "^8.5.12",
|
"@types/ws": "^8.5.12",
|
||||||
"@vladfrangu/async_event_emitter": "^2.4.6",
|
"@vladfrangu/async_event_emitter": "^2.4.6",
|
||||||
"discord-api-types": "^0.38.24",
|
"discord-api-types": "^0.38.32",
|
||||||
"tslib": "^2.6.3",
|
"tslib": "^2.6.3",
|
||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0"
|
||||||
},
|
},
|
||||||
|
|||||||
82
pnpm-lock.yaml
generated
82
pnpm-lock.yaml
generated
@@ -692,8 +692,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.37.119
|
specifier: ^0.38.32
|
||||||
version: 0.37.119
|
version: 0.38.32
|
||||||
fast-deep-equal:
|
fast-deep-equal:
|
||||||
specifier: ^3.1.3
|
specifier: ^3.1.3
|
||||||
version: 3.1.3
|
version: 3.1.3
|
||||||
@@ -816,8 +816,8 @@ importers:
|
|||||||
specifier: ^2.4.6
|
specifier: ^2.4.6
|
||||||
version: 2.4.6
|
version: 2.4.6
|
||||||
discord-api-types:
|
discord-api-types:
|
||||||
specifier: ^0.38.31
|
specifier: ^0.38.32
|
||||||
version: 0.38.31
|
version: 0.38.32
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@discordjs/api-extractor':
|
'@discordjs/api-extractor':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
@@ -932,14 +932,14 @@ importers:
|
|||||||
packages/discord.js:
|
packages/discord.js:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@discordjs/builders':
|
'@discordjs/builders':
|
||||||
specifier: ^1.13.0
|
specifier: workspace:^
|
||||||
version: 1.13.0
|
version: link:../builders
|
||||||
'@discordjs/collection':
|
'@discordjs/collection':
|
||||||
specifier: 1.5.3
|
specifier: 1.5.3
|
||||||
version: 1.5.3
|
version: 1.5.3
|
||||||
'@discordjs/formatters':
|
'@discordjs/formatters':
|
||||||
specifier: ^0.6.1
|
specifier: workspace:^
|
||||||
version: 0.6.1
|
version: link:../formatters
|
||||||
'@discordjs/rest':
|
'@discordjs/rest':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../rest
|
version: link:../rest
|
||||||
@@ -953,8 +953,8 @@ importers:
|
|||||||
specifier: 3.5.3
|
specifier: 3.5.3
|
||||||
version: 3.5.3
|
version: 3.5.3
|
||||||
discord-api-types:
|
discord-api-types:
|
||||||
specifier: ^0.38.31
|
specifier: ^0.38.32
|
||||||
version: 0.38.31
|
version: 0.38.32
|
||||||
fast-deep-equal:
|
fast-deep-equal:
|
||||||
specifier: 3.1.3
|
specifier: 3.1.3
|
||||||
version: 3.1.3
|
version: 3.1.3
|
||||||
@@ -1075,8 +1075,8 @@ importers:
|
|||||||
packages/formatters:
|
packages/formatters:
|
||||||
dependencies:
|
dependencies:
|
||||||
discord-api-types:
|
discord-api-types:
|
||||||
specifier: ^0.38.24
|
specifier: ^0.38.32
|
||||||
version: 0.38.24
|
version: 0.38.32
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@discordjs/api-extractor':
|
'@discordjs/api-extractor':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
@@ -1148,8 +1148,8 @@ importers:
|
|||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../ws
|
version: link:../ws
|
||||||
discord-api-types:
|
discord-api-types:
|
||||||
specifier: ^0.38.24
|
specifier: ^0.38.32
|
||||||
version: 0.38.24
|
version: 0.38.32
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@discordjs/api-extractor':
|
'@discordjs/api-extractor':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
@@ -1322,8 +1322,8 @@ importers:
|
|||||||
specifier: ^2.4.6
|
specifier: ^2.4.6
|
||||||
version: 2.4.6
|
version: 2.4.6
|
||||||
discord-api-types:
|
discord-api-types:
|
||||||
specifier: ^0.38.24
|
specifier: ^0.38.32
|
||||||
version: 0.38.24
|
version: 0.38.32
|
||||||
magic-bytes.js:
|
magic-bytes.js:
|
||||||
specifier: ^1.10.0
|
specifier: ^1.10.0
|
||||||
version: 1.10.0
|
version: 1.10.0
|
||||||
@@ -1619,8 +1619,8 @@ importers:
|
|||||||
specifier: ^8.5.12
|
specifier: ^8.5.12
|
||||||
version: 8.5.12
|
version: 8.5.12
|
||||||
discord-api-types:
|
discord-api-types:
|
||||||
specifier: ^0.38.24
|
specifier: ^0.38.32
|
||||||
version: 0.38.24
|
version: 0.38.32
|
||||||
prism-media:
|
prism-media:
|
||||||
specifier: ^1.3.5
|
specifier: ^1.3.5
|
||||||
version: 1.3.5
|
version: 1.3.5
|
||||||
@@ -1716,8 +1716,8 @@ importers:
|
|||||||
specifier: ^2.4.6
|
specifier: ^2.4.6
|
||||||
version: 2.4.6
|
version: 2.4.6
|
||||||
discord-api-types:
|
discord-api-types:
|
||||||
specifier: ^0.38.24
|
specifier: ^0.38.32
|
||||||
version: 0.38.24
|
version: 0.38.32
|
||||||
tslib:
|
tslib:
|
||||||
specifier: ^2.6.3
|
specifier: ^2.6.3
|
||||||
version: 2.6.3
|
version: 2.6.3
|
||||||
@@ -2789,10 +2789,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-4JINx4Rttha29f50PBsJo48xZXx/He5yaIWJRwVarhYAN947+S84YciHl+AIhQNRPAFkg8+5qFngEGtKxQDWXA==}
|
resolution: {integrity: sha512-4JINx4Rttha29f50PBsJo48xZXx/He5yaIWJRwVarhYAN947+S84YciHl+AIhQNRPAFkg8+5qFngEGtKxQDWXA==}
|
||||||
engines: {node: '>=18.18.0'}
|
engines: {node: '>=18.18.0'}
|
||||||
|
|
||||||
'@discordjs/builders@1.13.0':
|
|
||||||
resolution: {integrity: sha512-COK0uU6ZaJI+LA67H/rp8IbEkYwlZf3mAoBI5wtPh5G5cbEQGNhVpzINg2f/6+q/YipnNIKy6fJDg6kMUKUw4Q==}
|
|
||||||
engines: {node: '>=16.11.0'}
|
|
||||||
|
|
||||||
'@discordjs/collection@1.5.3':
|
'@discordjs/collection@1.5.3':
|
||||||
resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==}
|
resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==}
|
||||||
engines: {node: '>=16.11.0'}
|
engines: {node: '>=16.11.0'}
|
||||||
@@ -2801,10 +2797,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==}
|
resolution: {integrity: sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
'@discordjs/formatters@0.6.1':
|
|
||||||
resolution: {integrity: sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg==}
|
|
||||||
engines: {node: '>=16.11.0'}
|
|
||||||
|
|
||||||
'@discordjs/rest@2.5.1':
|
'@discordjs/rest@2.5.1':
|
||||||
resolution: {integrity: sha512-Tg9840IneBcbrAjcGaQzHUJWFNq1MMWZjTdjJ0WS/89IffaNKc++iOvffucPxQTF/gviO9+9r8kEPea1X5J2Dw==}
|
resolution: {integrity: sha512-Tg9840IneBcbrAjcGaQzHUJWFNq1MMWZjTdjJ0WS/89IffaNKc++iOvffucPxQTF/gviO9+9r8kEPea1X5J2Dw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -8354,14 +8346,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
|
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
discord-api-types@0.37.119:
|
discord-api-types@0.38.32:
|
||||||
resolution: {integrity: sha512-WasbGFXEB+VQWXlo6IpW3oUv73Yuau1Ig4AZF/m13tXcTKnMpc/mHjpztIlz4+BM9FG9BHQkEXiPto3bKduQUg==}
|
resolution: {integrity: sha512-UhIqkFuUVwBzejLPPWF18qixYPucMf718RnGh1NxZYNS7czXUmcUsWWkzWR7lRWj5pjfj4LwrnN9McvpfLvGqQ==}
|
||||||
|
|
||||||
discord-api-types@0.38.24:
|
|
||||||
resolution: {integrity: sha512-P7/DkcFIiIoaBogStnhhcGRX7KR+gIFp0SpmwsZUIM0bgDkYMEUx+8l+t3quYc/KSgg92wvE9w/4mabO57EMug==}
|
|
||||||
|
|
||||||
discord-api-types@0.38.31:
|
|
||||||
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==}
|
||||||
@@ -16321,24 +16307,10 @@ snapshots:
|
|||||||
tar-stream: 3.1.7
|
tar-stream: 3.1.7
|
||||||
which: 4.0.0
|
which: 4.0.0
|
||||||
|
|
||||||
'@discordjs/builders@1.13.0':
|
|
||||||
dependencies:
|
|
||||||
'@discordjs/formatters': 0.6.1
|
|
||||||
'@discordjs/util': 1.1.1
|
|
||||||
'@sapphire/shapeshift': 4.0.0
|
|
||||||
discord-api-types: 0.38.31
|
|
||||||
fast-deep-equal: 3.1.3
|
|
||||||
ts-mixer: 6.0.4
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
'@discordjs/collection@1.5.3': {}
|
'@discordjs/collection@1.5.3': {}
|
||||||
|
|
||||||
'@discordjs/collection@2.1.1': {}
|
'@discordjs/collection@2.1.1': {}
|
||||||
|
|
||||||
'@discordjs/formatters@0.6.1':
|
|
||||||
dependencies:
|
|
||||||
discord-api-types: 0.38.31
|
|
||||||
|
|
||||||
'@discordjs/rest@2.5.1':
|
'@discordjs/rest@2.5.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@discordjs/collection': 2.1.1
|
'@discordjs/collection': 2.1.1
|
||||||
@@ -16346,7 +16318,7 @@ snapshots:
|
|||||||
'@sapphire/async-queue': 1.5.3
|
'@sapphire/async-queue': 1.5.3
|
||||||
'@sapphire/snowflake': 3.5.3
|
'@sapphire/snowflake': 3.5.3
|
||||||
'@vladfrangu/async_event_emitter': 2.4.6
|
'@vladfrangu/async_event_emitter': 2.4.6
|
||||||
discord-api-types: 0.38.31
|
discord-api-types: 0.38.32
|
||||||
magic-bytes.js: 1.10.0
|
magic-bytes.js: 1.10.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
undici: 6.21.3
|
undici: 6.21.3
|
||||||
@@ -16361,7 +16333,7 @@ snapshots:
|
|||||||
'@sapphire/async-queue': 1.5.3
|
'@sapphire/async-queue': 1.5.3
|
||||||
'@types/ws': 8.5.12
|
'@types/ws': 8.5.12
|
||||||
'@vladfrangu/async_event_emitter': 2.4.6
|
'@vladfrangu/async_event_emitter': 2.4.6
|
||||||
discord-api-types: 0.38.31
|
discord-api-types: 0.38.32
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
|
ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -23712,11 +23684,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
path-type: 4.0.0
|
path-type: 4.0.0
|
||||||
|
|
||||||
discord-api-types@0.37.119: {}
|
discord-api-types@0.38.32: {}
|
||||||
|
|
||||||
discord-api-types@0.38.24: {}
|
|
||||||
|
|
||||||
discord-api-types@0.38.31: {}
|
|
||||||
|
|
||||||
dlv@1.1.3: {}
|
dlv@1.1.3: {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user