feat: components v2 in builders v1 (#10787)

* feat(builders): components v2 in builders v1

* feat: implemented the first components

* fix: tests

* fix: tests

* fix: export the new stuff

* feat: add rest of components

* feat: add callback syntax

* feat: callback syntax for section

* fix: missing implements

* fix: accessory property

* fix: apply suggestions from v2 PR

* chore: bring in line with builders v2

* fix: add missing type

* chore: split accessory methods

* chore: backport changes from v2 PR

* fix: accent_color is nullish

* fix: allow passing raw json to MediaGallery methods

* fix: add test

* chore: add Container#addXComponents

* fix: docs

* chore: bump discord-api-types

* Update packages/builders/src/components/Assertions.ts

Co-authored-by: Denis-Adrian Cristea <didinele.dev@gmail.com>

---------

Co-authored-by: Denis-Adrian Cristea <didinele.dev@gmail.com>
This commit is contained in:
Qjuh
2025-04-24 21:59:58 +02:00
committed by GitHub
parent 53dbc96194
commit 118e682682
31 changed files with 1955 additions and 120 deletions

View File

@@ -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: [
{ {

View File

@@ -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,
}; };

View File

@@ -0,0 +1,248 @@
import { type APIContainerComponent, ComponentType, SeparatorSpacingSize } from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import { ButtonBuilder } from '../../../dist/index.mjs';
import { ActionRowBuilder } from '../../../src/components/ActionRow.js';
import { createComponentBuilder } from '../../../src/components/Components.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,
});
});
});
});

View 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 });
});
});
});

View 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);
});
});
});

View 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);
});
});
});

View 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 });
});
});
});

View File

@@ -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' });
});
});
});

View 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 });
});
});
});

View File

@@ -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.1",
"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"

View File

@@ -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[];
} }

View File

@@ -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)

View File

@@ -1,15 +1,20 @@
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,
} 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;
/** /**
* 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 +47,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;
}
} }

View File

@@ -1,8 +1,9 @@
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';
@@ -13,6 +14,27 @@ 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 +72,34 @@ 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;
} }
/** /**
@@ -97,8 +147,44 @@ 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);
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);
}

View File

@@ -0,0 +1,71 @@
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).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)
.parse(input);
}

View File

@@ -0,0 +1,240 @@
/* 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, validateComponentArray } 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 {
validateComponentArray(this.components, 1, 10);
return {
...this.data,
components: this.components.map((component) => component.toJSON()),
} as APIContainerComponent;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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 thumbnaik = 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;
}
}

View File

@@ -34,6 +34,16 @@ 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 * 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';

View File

@@ -3,7 +3,7 @@
import type { JSONEncodable } from '@discordjs/util'; import type { JSONEncodable } from '@discordjs/util';
import type { import type {
APIActionRowComponent, APIActionRowComponent,
APIModalActionRowComponent, APIComponentInModalActionRow,
APIModalInteractionResponseCallbackData, APIModalInteractionResponseCallbackData,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../../components/ActionRow.js'; import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../../components/ActionRow.js';
@@ -64,7 +64,7 @@ export class ModalBuilder implements JSONEncodable<APIModalInteractionResponseCa
*/ */
public addComponents( public addComponents(
...components: RestOrArray< ...components: RestOrArray<
ActionRowBuilder<ModalActionRowComponentBuilder> | APIActionRowComponent<APIModalActionRowComponent> ActionRowBuilder<ModalActionRowComponentBuilder> | APIActionRowComponent<APIComponentInModalActionRow>
> >
) { ) {
this.components.push( this.components.push(

View File

@@ -72,7 +72,7 @@
"@discordjs/util": "workspace:^", "@discordjs/util": "workspace:^",
"@discordjs/ws": "1.1.1", "@discordjs/ws": "1.1.1",
"@sapphire/snowflake": "3.5.3", "@sapphire/snowflake": "3.5.3",
"discord-api-types": "^0.37.119", "discord-api-types": "^0.38.1",
"fast-deep-equal": "3.1.3", "fast-deep-equal": "3.1.3",
"lodash.snakecase": "4.1.1", "lodash.snakecase": "4.1.1",
"tslib": "^2.6.3", "tslib": "^2.6.3",

View File

@@ -111,13 +111,13 @@ import {
AuditLogEvent, AuditLogEvent,
APIMessageComponentEmoji, APIMessageComponentEmoji,
EmbedType, EmbedType,
APIActionRowComponentTypes, APIComponentInActionRow,
APIModalInteractionResponseCallbackData, APIModalInteractionResponseCallbackData,
APIModalSubmitInteraction, APIModalSubmitInteraction,
APIMessageActionRowComponent, APIComponentInMessageActionRow,
TextInputStyle, TextInputStyle,
APITextInputComponent, APITextInputComponent,
APIModalActionRowComponent, APIComponentInModalActionRow,
APIModalComponent, APIModalComponent,
APISelectMenuOption, APISelectMenuOption,
APIEmbedField, APIEmbedField,
@@ -285,7 +285,7 @@ export interface BaseComponentData {
} }
export type MessageActionRowComponentData = export type MessageActionRowComponentData =
| JSONEncodable<APIMessageActionRowComponent> | JSONEncodable<APIComponentInMessageActionRow>
| ButtonComponentData | ButtonComponentData
| StringSelectMenuComponentData | StringSelectMenuComponentData
| UserSelectMenuComponentData | UserSelectMenuComponentData
@@ -293,13 +293,13 @@ export type MessageActionRowComponentData =
| MentionableSelectMenuComponentData | MentionableSelectMenuComponentData
| ChannelSelectMenuComponentData; | ChannelSelectMenuComponentData;
export type ModalActionRowComponentData = JSONEncodable<APIModalActionRowComponent> | TextInputComponentData; export type ModalActionRowComponentData = JSONEncodable<APIComponentInModalActionRow> | TextInputComponentData;
export type ActionRowComponentData = MessageActionRowComponentData | ModalActionRowComponentData; export type ActionRowComponentData = MessageActionRowComponentData | ModalActionRowComponentData;
export type ActionRowComponent = MessageActionRowComponent | ModalActionRowComponent; export type ActionRowComponent = MessageActionRowComponent | ModalActionRowComponent;
export interface ActionRowData<ComponentType extends JSONEncodable<APIActionRowComponentTypes> | ActionRowComponentData> export interface ActionRowData<ComponentType extends JSONEncodable<APIComponentInActionRow> | ActionRowComponentData>
extends BaseComponentData { extends BaseComponentData {
components: readonly ComponentType[]; components: readonly ComponentType[];
} }
@@ -309,8 +309,8 @@ export class ActionRowBuilder<
> extends BuilderActionRow<ComponentType> { > extends BuilderActionRow<ComponentType> {
public constructor( public constructor(
data?: Partial< data?: Partial<
| ActionRowData<ActionRowComponentData | JSONEncodable<APIActionRowComponentTypes>> | ActionRowData<ActionRowComponentData | JSONEncodable<APIComponentInActionRow>>
| APIActionRowComponent<APIMessageActionRowComponent | APIModalActionRowComponent> | APIActionRowComponent<APIComponentInMessageActionRow | APIComponentInModalActionRow>
>, >,
); );
public static from<ComponentType extends AnyComponentBuilder = AnyComponentBuilder>( public static from<ComponentType extends AnyComponentBuilder = AnyComponentBuilder>(
@@ -330,9 +330,9 @@ export type MessageActionRowComponent =
export type ModalActionRowComponent = TextInputComponent; export type ModalActionRowComponent = TextInputComponent;
export class ActionRow<ComponentType extends MessageActionRowComponent | ModalActionRowComponent> extends Component< export class ActionRow<ComponentType extends MessageActionRowComponent | ModalActionRowComponent> extends Component<
APIActionRowComponent<APIMessageActionRowComponent | APIModalActionRowComponent> APIActionRowComponent<APIComponentInMessageActionRow | APIComponentInModalActionRow>
> { > {
private constructor(data: APIActionRowComponent<APIMessageActionRowComponent | APIModalActionRowComponent>); private constructor(data: APIActionRowComponent<APIComponentInMessageActionRow | APIComponentInModalActionRow>);
public readonly components: ComponentType[]; public readonly components: ComponentType[];
public toJSON(): APIActionRowComponent<ReturnType<ComponentType['toJSON']>>; public toJSON(): APIActionRowComponent<ReturnType<ComponentType['toJSON']>>;
} }
@@ -740,7 +740,7 @@ export class ButtonInteraction<Cached extends CacheType = CacheType> extends Mes
export type AnyComponent = export type AnyComponent =
| APIMessageComponent | APIMessageComponent
| APIModalComponent | APIModalComponent
| APIActionRowComponent<APIMessageActionRowComponent | APIModalActionRowComponent>; | APIActionRowComponent<APIComponentInMessageActionRow | APIComponentInModalActionRow>;
export class Component<RawComponentData extends AnyComponent = AnyComponent> { export class Component<RawComponentData extends AnyComponent = AnyComponent> {
public readonly data: Readonly<RawComponentData>; public readonly data: Readonly<RawComponentData>;
@@ -2086,7 +2086,13 @@ export interface MessageCall {
participants: readonly Snowflake[]; participants: readonly Snowflake[];
} }
export type MessageComponentType = Exclude<ComponentType, ComponentType.TextInput | ComponentType.ActionRow>; export type MessageComponentType =
| ComponentType.Button
| ComponentType.ChannelSelect
| ComponentType.MentionableSelect
| ComponentType.RoleSelect
| ComponentType.StringSelect
| ComponentType.UserSelect;
export interface MessageCollectorOptionsParams< export interface MessageCollectorOptionsParams<
ComponentType extends MessageComponentType, ComponentType extends MessageComponentType,
@@ -2278,9 +2284,9 @@ export class MessageComponentInteraction<Cached extends CacheType = CacheType> e
public get component(): CacheTypeReducer< public get component(): CacheTypeReducer<
Cached, Cached,
MessageActionRowComponent, MessageActionRowComponent,
Exclude<APIMessageComponent, APIActionRowComponent<APIMessageActionRowComponent>>, Exclude<APIMessageComponent, APIActionRowComponent<APIComponentInMessageActionRow>>,
MessageActionRowComponent | Exclude<APIMessageComponent, APIActionRowComponent<APIMessageActionRowComponent>>, MessageActionRowComponent | Exclude<APIMessageComponent, APIActionRowComponent<APIComponentInMessageActionRow>>,
MessageActionRowComponent | Exclude<APIMessageComponent, APIActionRowComponent<APIMessageActionRowComponent>> MessageActionRowComponent | Exclude<APIMessageComponent, APIActionRowComponent<APIComponentInMessageActionRow>>
>; >;
public componentType: Exclude<ComponentType, ComponentType.ActionRow | ComponentType.TextInput>; public componentType: Exclude<ComponentType, ComponentType.ActionRow | ComponentType.TextInput>;
public customId: string; public customId: string;
@@ -2457,7 +2463,7 @@ export interface ModalComponentData {
customId: string; customId: string;
title: string; title: string;
components: readonly ( components: readonly (
| JSONEncodable<APIActionRowComponent<APIModalActionRowComponent>> | JSONEncodable<APIActionRowComponent<APIComponentInModalActionRow>>
| ActionRowData<ModalActionRowComponentData> | ActionRowData<ModalActionRowComponentData>
)[]; )[];
} }
@@ -6454,9 +6460,9 @@ export interface BaseMessageOptions {
| AttachmentPayload | AttachmentPayload
)[]; )[];
components?: readonly ( components?: readonly (
| JSONEncodable<APIActionRowComponent<APIMessageActionRowComponent>> | JSONEncodable<APIActionRowComponent<APIComponentInMessageActionRow>>
| ActionRowData<MessageActionRowComponentData | MessageActionRowComponentBuilder> | ActionRowData<MessageActionRowComponentData | MessageActionRowComponentBuilder>
| APIActionRowComponent<APIMessageActionRowComponent> | APIActionRowComponent<APIComponentInMessageActionRow>
)[]; )[];
poll?: PollData; poll?: PollData;
} }

View File

@@ -25,7 +25,7 @@ import {
ApplicationCommandType, ApplicationCommandType,
APIMessage, APIMessage,
APIActionRowComponent, APIActionRowComponent,
APIActionRowComponentTypes, APIComponentInActionRow,
APIStringSelectComponent, APIStringSelectComponent,
APIUserSelectComponent, APIUserSelectComponent,
APIRoleSelectComponent, APIRoleSelectComponent,
@@ -2347,7 +2347,7 @@ EmbedBuilder.from(embedData);
declare const embedComp: Embed; declare const embedComp: Embed;
EmbedBuilder.from(embedComp); EmbedBuilder.from(embedComp);
declare const actionRowData: APIActionRowComponent<APIActionRowComponentTypes>; declare const actionRowData: APIActionRowComponent<APIComponentInActionRow>;
ActionRowBuilder.from(actionRowData); ActionRowBuilder.from(actionRowData);
declare const actionRowComp: ActionRow<ActionRowComponent>; declare const actionRowComp: ActionRow<ActionRowComponent>;
@@ -2359,7 +2359,7 @@ declare const buttonsActionRowComp: ActionRow<ButtonComponent>;
expectType<ActionRowBuilder<ButtonBuilder>>(ActionRowBuilder.from<ButtonBuilder>(buttonsActionRowData)); expectType<ActionRowBuilder<ButtonBuilder>>(ActionRowBuilder.from<ButtonBuilder>(buttonsActionRowData));
expectType<ActionRowBuilder<ButtonBuilder>>(ActionRowBuilder.from<ButtonBuilder>(buttonsActionRowComp)); expectType<ActionRowBuilder<ButtonBuilder>>(ActionRowBuilder.from<ButtonBuilder>(buttonsActionRowComp));
declare const anyComponentsActionRowData: APIActionRowComponent<APIActionRowComponentTypes>; declare const anyComponentsActionRowData: APIActionRowComponent<APIComponentInActionRow>;
declare const anyComponentsActionRowComp: ActionRow<ActionRowComponent>; declare const anyComponentsActionRowComp: ActionRow<ActionRowComponent>;
expectType<ActionRowBuilder>(ActionRowBuilder.from(anyComponentsActionRowData)); expectType<ActionRowBuilder>(ActionRowBuilder.from(anyComponentsActionRowData));

View File

@@ -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.37.119", "discord-api-types": "^0.38.1",
"magic-bytes.js": "^1.10.0", "magic-bytes.js": "^1.10.0",
"tslib": "^2.6.3", "tslib": "^2.6.3",
"undici": "6.21.1" "undici": "6.21.1"

View File

@@ -1,5 +1,5 @@
import { getUserAgentAppendix } from '@discordjs/util'; import { getUserAgentAppendix } from '@discordjs/util';
import { APIVersion } from 'discord-api-types/v10'; import { APIVersion, type ImageSize } from 'discord-api-types/v10';
import { getDefaultStrategy } from '../../environment.js'; import { getDefaultStrategy } from '../../environment.js';
import type { RESTOptions, ResponseLike } from './types.js'; import type { RESTOptions, ResponseLike } from './types.js';
@@ -48,11 +48,10 @@ export enum RESTEvents {
export const ALLOWED_EXTENSIONS = ['webp', 'png', 'jpg', 'jpeg', 'gif'] as const satisfies readonly string[]; export const ALLOWED_EXTENSIONS = ['webp', 'png', 'jpg', 'jpeg', 'gif'] as const satisfies readonly string[];
export const ALLOWED_STICKER_EXTENSIONS = ['png', 'json', 'gif'] as const satisfies readonly string[]; export const ALLOWED_STICKER_EXTENSIONS = ['png', 'json', 'gif'] as const satisfies readonly string[];
export const ALLOWED_SIZES = [16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096] as const satisfies readonly number[]; export const ALLOWED_SIZES: readonly number[] = [16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096] satisfies ImageSize[];
export type ImageExtension = (typeof ALLOWED_EXTENSIONS)[number]; export type ImageExtension = (typeof ALLOWED_EXTENSIONS)[number];
export type StickerExtension = (typeof ALLOWED_STICKER_EXTENSIONS)[number]; export type StickerExtension = (typeof ALLOWED_STICKER_EXTENSIONS)[number];
export type ImageSize = (typeof ALLOWED_SIZES)[number];
export const OverwrittenMimeTypes = { export const OverwrittenMimeTypes = {
// https://github.com/discordjs/discord.js/issues/8557 // https://github.com/discordjs/discord.js/issues/8557
@@ -67,3 +66,5 @@ export const BurstHandlerMajorIdKey = 'burst';
* @internal * @internal
*/ */
export const DEPRECATION_WARNING_PREFIX = 'DeprecationWarning' as const; export const DEPRECATION_WARNING_PREFIX = 'DeprecationWarning' as const;
export { type ImageSize } from 'discord-api-types/v10';

144
pnpm-lock.yaml generated
View File

@@ -680,8 +680,8 @@ importers:
specifier: ^4.0.0 specifier: ^4.0.0
version: 4.0.0 version: 4.0.0
discord-api-types: discord-api-types:
specifier: ^0.37.119 specifier: ^0.38.1
version: 0.37.119 version: 0.38.1
fast-deep-equal: fast-deep-equal:
specifier: ^3.1.3 specifier: ^3.1.3
version: 3.1.3 version: 3.1.3
@@ -941,8 +941,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.37.119 specifier: ^0.38.1
version: 0.37.119 version: 0.38.1
fast-deep-equal: fast-deep-equal:
specifier: 3.1.3 specifier: 3.1.3
version: 3.1.3 version: 3.1.3
@@ -1307,8 +1307,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.37.119 specifier: ^0.38.1
version: 0.37.119 version: 0.38.1
magic-bytes.js: magic-bytes.js:
specifier: ^1.10.0 specifier: ^1.10.0
version: 1.10.0 version: 1.10.0
@@ -2601,12 +2601,12 @@ packages:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'} engines: {node: '>=12'}
'@definitelytyped/header-parser@0.2.16': '@definitelytyped/header-parser@0.2.19':
resolution: {integrity: sha512-UFsgPft5bhZn07UNGz/9ck4AhdKgLFEOmi2DNr7gXcGL89zbe3u5oVafKUT8j1HOtSBjT8ZEQsXHKlbq+wwF/Q==} resolution: {integrity: sha512-zu+RxQpUCgorYUQZoyyrRIn9CljL1CeM4qak3NDeMO1r7tjAkodfpAGnVzx/6JR2OUk0tAgwmZxNMSwd9LVgxw==}
engines: {node: '>=18.18.0'} engines: {node: '>=18.18.0'}
'@definitelytyped/typescript-versions@0.1.6': '@definitelytyped/typescript-versions@0.1.8':
resolution: {integrity: sha512-gQpXFteIKrOw4ldmBZQfBrD3WobaIG1SwOr/3alXWkcYbkOWa2NRxQbiaYQ2IvYTGaZK26miJw0UOAFiuIs4gA==} resolution: {integrity: sha512-iz6q9aTwWW7CzN2g8jFQfZ955D63LA+wdIAKz4+2pCc/7kokmEHie1/jVWSczqLFOlmH+69bWQxIurryBP/sig==}
engines: {node: '>=18.18.0'} engines: {node: '>=18.18.0'}
'@definitelytyped/utils@0.1.8': '@definitelytyped/utils@0.1.8':
@@ -7638,6 +7638,9 @@ packages:
discord-api-types@0.37.83: discord-api-types@0.37.83:
resolution: {integrity: sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==} resolution: {integrity: sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==}
discord-api-types@0.38.1:
resolution: {integrity: sha512-vsjsqjAuxsPhiwbPjTBeGQaDPlizFmSkU0mTzFGMgRxqCDIRBR7iTY74HacpzrDV0QtERHRKQEk1tq7drZUtHg==}
dlv@1.1.3: dlv@1.1.3:
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
@@ -8102,6 +8105,7 @@ packages:
eslint-plugin-i@2.29.1: eslint-plugin-i@2.29.1:
resolution: {integrity: sha512-ORizX37MelIWLbMyqI7hi8VJMf7A0CskMmYkB+lkCX3aF4pkGV7kwx5bSEb4qx7Yce2rAf9s34HqDRPjGRZPNQ==} resolution: {integrity: sha512-ORizX37MelIWLbMyqI7hi8VJMf7A0CskMmYkB+lkCX3aF4pkGV7kwx5bSEb4qx7Yce2rAf9s34HqDRPjGRZPNQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
deprecated: Please migrate to the brand new `eslint-plugin-import-x` instead
peerDependencies: peerDependencies:
eslint: ^7.2.0 || ^8 eslint: ^7.2.0 || ^8
@@ -8226,6 +8230,7 @@ packages:
eslint@8.57.0: eslint@8.57.0:
resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
hasBin: true hasBin: true
espree@10.1.0: espree@10.1.0:
@@ -12313,6 +12318,7 @@ packages:
stream-connect@1.0.2: stream-connect@1.0.2:
resolution: {integrity: sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==} resolution: {integrity: sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
stream-to-array@2.3.0: stream-to-array@2.3.0:
resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==}
@@ -13860,7 +13866,7 @@ snapshots:
'@babel/helper-plugin-utils': 7.24.8 '@babel/helper-plugin-utils': 7.24.8
debug: 4.4.0 debug: 4.4.0
lodash.debounce: 4.0.8 lodash.debounce: 4.0.8
resolve: 1.22.8 resolve: 1.22.10
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -14609,7 +14615,7 @@ snapshots:
'@babel/parser': 7.25.4 '@babel/parser': 7.25.4
'@babel/template': 7.25.0 '@babel/template': 7.25.0
'@babel/types': 7.25.4 '@babel/types': 7.25.4
debug: 4.3.6 debug: 4.4.0
globals: 11.12.0 globals: 11.12.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -14837,7 +14843,7 @@ snapshots:
'@conventional-changelog/git-client@1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.0.0)': '@conventional-changelog/git-client@1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.0.0)':
dependencies: dependencies:
'@types/semver': 7.5.8 '@types/semver': 7.5.8
semver: 7.6.3 semver: 7.5.4
optionalDependencies: optionalDependencies:
conventional-commits-filter: 5.0.0 conventional-commits-filter: 5.0.0
conventional-commits-parser: 6.0.0 conventional-commits-parser: 6.0.0
@@ -14846,13 +14852,13 @@ snapshots:
dependencies: dependencies:
'@jridgewell/trace-mapping': 0.3.9 '@jridgewell/trace-mapping': 0.3.9
'@definitelytyped/header-parser@0.2.16': '@definitelytyped/header-parser@0.2.19':
dependencies: dependencies:
'@definitelytyped/typescript-versions': 0.1.6 '@definitelytyped/typescript-versions': 0.1.8
'@definitelytyped/utils': 0.1.8 '@definitelytyped/utils': 0.1.8
semver: 7.6.3 semver: 7.6.3
'@definitelytyped/typescript-versions@0.1.6': {} '@definitelytyped/typescript-versions@0.1.8': {}
'@definitelytyped/utils@0.1.8': '@definitelytyped/utils@0.1.8':
dependencies: dependencies:
@@ -15309,7 +15315,7 @@ snapshots:
'@antfu/install-pkg': 0.4.0 '@antfu/install-pkg': 0.4.0
'@antfu/utils': 0.7.10 '@antfu/utils': 0.7.10
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
debug: 4.3.6 debug: 4.4.0
kolorist: 1.8.0 kolorist: 1.8.0
local-pkg: 0.5.0 local-pkg: 0.5.0
mlly: 1.7.1 mlly: 1.7.1
@@ -15432,7 +15438,7 @@ snapshots:
'@jest/console@29.7.0': '@jest/console@29.7.0':
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 18.19.45 '@types/node': 18.19.74
chalk: 4.1.2 chalk: 4.1.2
jest-message-util: 29.7.0 jest-message-util: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
@@ -15512,7 +15518,7 @@ snapshots:
dependencies: dependencies:
'@jest/fake-timers': 29.7.0 '@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 18.19.45 '@types/node': 18.19.74
jest-mock: 29.7.0 jest-mock: 29.7.0
'@jest/expect-utils@29.7.0': '@jest/expect-utils@29.7.0':
@@ -15530,7 +15536,7 @@ snapshots:
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@sinonjs/fake-timers': 10.3.0 '@sinonjs/fake-timers': 10.3.0
'@types/node': 18.19.45 '@types/node': 18.19.74
jest-message-util: 29.7.0 jest-message-util: 29.7.0
jest-mock: 29.7.0 jest-mock: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
@@ -15552,7 +15558,7 @@ snapshots:
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@jridgewell/trace-mapping': 0.3.25 '@jridgewell/trace-mapping': 0.3.25
'@types/node': 18.19.45 '@types/node': 18.19.74
chalk: 4.1.2 chalk: 4.1.2
collect-v8-coverage: 1.0.2 collect-v8-coverage: 1.0.2
exit: 0.1.2 exit: 0.1.2
@@ -15814,7 +15820,7 @@ snapshots:
'@rushstack/ts-command-line': 4.19.1(@types/node@16.18.105) '@rushstack/ts-command-line': 4.19.1(@types/node@16.18.105)
lodash: 4.17.21 lodash: 4.17.21
minimatch: 3.0.8 minimatch: 3.0.8
resolve: 1.22.8 resolve: 1.22.10
semver: 7.5.4 semver: 7.5.4
source-map: 0.6.1 source-map: 0.6.1
typescript: 5.4.2 typescript: 5.4.2
@@ -15833,7 +15839,7 @@ snapshots:
'@rushstack/ts-command-line': 4.19.1(@types/node@18.17.9) '@rushstack/ts-command-line': 4.19.1(@types/node@18.17.9)
lodash: 4.17.21 lodash: 4.17.21
minimatch: 3.0.8 minimatch: 3.0.8
resolve: 1.22.8 resolve: 1.22.10
semver: 7.5.4 semver: 7.5.4
source-map: 0.6.1 source-map: 0.6.1
typescript: 5.4.2 typescript: 5.4.2
@@ -15852,7 +15858,7 @@ snapshots:
'@rushstack/ts-command-line': 4.19.1(@types/node@18.19.45) '@rushstack/ts-command-line': 4.19.1(@types/node@18.19.45)
lodash: 4.17.21 lodash: 4.17.21
minimatch: 3.0.8 minimatch: 3.0.8
resolve: 1.22.8 resolve: 1.22.10
semver: 7.5.4 semver: 7.5.4
source-map: 0.6.1 source-map: 0.6.1
typescript: 5.4.2 typescript: 5.4.2
@@ -15870,7 +15876,7 @@ snapshots:
'@rushstack/ts-command-line': 4.19.1(@types/node@20.16.1) '@rushstack/ts-command-line': 4.19.1(@types/node@20.16.1)
lodash: 4.17.21 lodash: 4.17.21
minimatch: 3.0.8 minimatch: 3.0.8
resolve: 1.22.8 resolve: 1.22.10
semver: 7.5.4 semver: 7.5.4
source-map: 0.6.1 source-map: 0.6.1
typescript: 5.4.2 typescript: 5.4.2
@@ -17995,7 +18001,7 @@ snapshots:
fs-extra: 7.0.1 fs-extra: 7.0.1
import-lazy: 4.0.0 import-lazy: 4.0.0
jju: 1.4.0 jju: 1.4.0
resolve: 1.22.8 resolve: 1.22.10
semver: 7.5.4 semver: 7.5.4
z-schema: 5.0.5 z-schema: 5.0.5
optionalDependencies: optionalDependencies:
@@ -18007,7 +18013,7 @@ snapshots:
fs-extra: 7.0.1 fs-extra: 7.0.1
import-lazy: 4.0.0 import-lazy: 4.0.0
jju: 1.4.0 jju: 1.4.0
resolve: 1.22.8 resolve: 1.22.10
semver: 7.5.4 semver: 7.5.4
z-schema: 5.0.5 z-schema: 5.0.5
optionalDependencies: optionalDependencies:
@@ -18019,7 +18025,7 @@ snapshots:
fs-extra: 7.0.1 fs-extra: 7.0.1
import-lazy: 4.0.0 import-lazy: 4.0.0
jju: 1.4.0 jju: 1.4.0
resolve: 1.22.8 resolve: 1.22.10
semver: 7.5.4 semver: 7.5.4
z-schema: 5.0.5 z-schema: 5.0.5
optionalDependencies: optionalDependencies:
@@ -18030,7 +18036,7 @@ snapshots:
fs-extra: 7.0.1 fs-extra: 7.0.1
import-lazy: 4.0.0 import-lazy: 4.0.0
jju: 1.4.0 jju: 1.4.0
resolve: 1.22.8 resolve: 1.22.10
semver: 7.5.4 semver: 7.5.4
z-schema: 5.0.5 z-schema: 5.0.5
optionalDependencies: optionalDependencies:
@@ -18055,7 +18061,7 @@ snapshots:
'@rushstack/rig-package@0.5.2': '@rushstack/rig-package@0.5.2':
dependencies: dependencies:
resolve: 1.22.8 resolve: 1.22.10
strip-json-comments: 3.1.1 strip-json-comments: 3.1.1
'@rushstack/terminal@0.10.0(@types/node@16.18.105)': '@rushstack/terminal@0.10.0(@types/node@16.18.105)':
@@ -18919,25 +18925,25 @@ snapshots:
'@types/body-parser@1.19.5': '@types/body-parser@1.19.5':
dependencies: dependencies:
'@types/connect': 3.4.38 '@types/connect': 3.4.38
'@types/node': 18.19.45 '@types/node': 18.19.74
'@types/concat-stream@2.0.3': '@types/concat-stream@2.0.3':
dependencies: dependencies:
'@types/node': 18.19.45 '@types/node': 18.19.74
'@types/connect@3.4.38': '@types/connect@3.4.38':
dependencies: dependencies:
'@types/node': 18.19.45 '@types/node': 18.19.74
'@types/conventional-commits-parser@5.0.0': '@types/conventional-commits-parser@5.0.0':
dependencies: dependencies:
'@types/node': 18.19.45 '@types/node': 18.19.74
'@types/cookiejar@2.1.5': {} '@types/cookiejar@2.1.5': {}
'@types/cross-spawn@6.0.6': '@types/cross-spawn@6.0.6':
dependencies: dependencies:
'@types/node': 18.19.45 '@types/node': 18.19.74
'@types/debug@4.1.12': '@types/debug@4.1.12':
dependencies: dependencies:
@@ -18969,7 +18975,7 @@ snapshots:
'@types/express-serve-static-core@4.19.5': '@types/express-serve-static-core@4.19.5':
dependencies: dependencies:
'@types/node': 18.19.45 '@types/node': 18.19.74
'@types/qs': 6.9.15 '@types/qs': 6.9.15
'@types/range-parser': 1.2.7 '@types/range-parser': 1.2.7
'@types/send': 0.17.4 '@types/send': 0.17.4
@@ -18986,11 +18992,11 @@ snapshots:
'@types/glob@7.2.0': '@types/glob@7.2.0':
dependencies: dependencies:
'@types/minimatch': 5.1.2 '@types/minimatch': 5.1.2
'@types/node': 18.19.45 '@types/node': 18.19.74
'@types/graceful-fs@4.1.9': '@types/graceful-fs@4.1.9':
dependencies: dependencies:
'@types/node': 18.19.45 '@types/node': 18.19.74
'@types/hast@2.3.10': '@types/hast@2.3.10':
dependencies: dependencies:
@@ -19072,7 +19078,7 @@ snapshots:
'@types/node-fetch@2.6.11': '@types/node-fetch@2.6.11':
dependencies: dependencies:
'@types/node': 18.19.45 '@types/node': 18.19.74
form-data: 4.0.0 form-data: 4.0.0
'@types/node@16.18.105': {} '@types/node@16.18.105': {}
@@ -19099,7 +19105,7 @@ snapshots:
'@types/pg@8.11.6': '@types/pg@8.11.6':
dependencies: dependencies:
'@types/node': 18.19.45 '@types/node': 18.19.74
pg-protocol: 1.6.1 pg-protocol: 1.6.1
pg-types: 4.0.2 pg-types: 4.0.2
@@ -19132,12 +19138,12 @@ snapshots:
'@types/send@0.17.4': '@types/send@0.17.4':
dependencies: dependencies:
'@types/mime': 1.3.5 '@types/mime': 1.3.5
'@types/node': 18.19.45 '@types/node': 18.19.74
'@types/serve-static@1.15.7': '@types/serve-static@1.15.7':
dependencies: dependencies:
'@types/http-errors': 2.0.4 '@types/http-errors': 2.0.4
'@types/node': 18.19.45 '@types/node': 18.19.74
'@types/send': 0.17.4 '@types/send': 0.17.4
'@types/stack-utils@2.0.3': {} '@types/stack-utils@2.0.3': {}
@@ -19158,7 +19164,7 @@ snapshots:
'@types/through@0.0.33': '@types/through@0.0.33':
dependencies: dependencies:
'@types/node': 18.19.45 '@types/node': 18.19.74
'@types/tinycolor2@1.4.6': {} '@types/tinycolor2@1.4.6': {}
@@ -19278,7 +19284,7 @@ snapshots:
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 7.11.0(typescript@5.5.4) '@typescript-eslint/typescript-estree': 7.11.0(typescript@5.5.4)
'@typescript-eslint/utils': 7.11.0(eslint@8.57.0)(typescript@5.5.4) '@typescript-eslint/utils': 7.11.0(eslint@8.57.0)(typescript@5.5.4)
debug: 4.3.6 debug: 4.4.0
eslint: 8.57.0 eslint: 8.57.0
ts-api-utils: 1.3.0(typescript@5.5.4) ts-api-utils: 1.3.0(typescript@5.5.4)
optionalDependencies: optionalDependencies:
@@ -19290,7 +19296,7 @@ snapshots:
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4)
'@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4) '@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4)
debug: 4.3.6 debug: 4.4.0
eslint: 8.57.0 eslint: 8.57.0
ts-api-utils: 1.3.0(typescript@5.5.4) ts-api-utils: 1.3.0(typescript@5.5.4)
optionalDependencies: optionalDependencies:
@@ -19302,7 +19308,7 @@ snapshots:
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 8.2.0(typescript@5.5.4) '@typescript-eslint/typescript-estree': 8.2.0(typescript@5.5.4)
'@typescript-eslint/utils': 8.2.0(eslint@8.57.0)(typescript@5.5.4) '@typescript-eslint/utils': 8.2.0(eslint@8.57.0)(typescript@5.5.4)
debug: 4.3.6 debug: 4.4.0
ts-api-utils: 1.3.0(typescript@5.5.4) ts-api-utils: 1.3.0(typescript@5.5.4)
optionalDependencies: optionalDependencies:
typescript: 5.5.4 typescript: 5.5.4
@@ -19351,7 +19357,7 @@ snapshots:
dependencies: dependencies:
'@typescript-eslint/types': 7.18.0 '@typescript-eslint/types': 7.18.0
'@typescript-eslint/visitor-keys': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0
debug: 4.3.6 debug: 4.4.0
globby: 11.1.0 globby: 11.1.0
is-glob: 4.0.3 is-glob: 4.0.3
minimatch: 9.0.5 minimatch: 9.0.5
@@ -19366,7 +19372,7 @@ snapshots:
dependencies: dependencies:
'@typescript-eslint/types': 8.2.0 '@typescript-eslint/types': 8.2.0
'@typescript-eslint/visitor-keys': 8.2.0 '@typescript-eslint/visitor-keys': 8.2.0
debug: 4.3.6 debug: 4.4.0
globby: 11.1.0 globby: 11.1.0
is-glob: 4.0.3 is-glob: 4.0.3
minimatch: 9.0.5 minimatch: 9.0.5
@@ -21509,6 +21515,8 @@ snapshots:
discord-api-types@0.37.83: {} discord-api-types@0.37.83: {}
discord-api-types@0.38.1: {}
dlv@1.1.3: {} dlv@1.1.3: {}
dmd@6.2.3: dmd@6.2.3:
@@ -21558,7 +21566,7 @@ snapshots:
dts-critic@3.3.11(typescript@5.5.4): dts-critic@3.3.11(typescript@5.5.4):
dependencies: dependencies:
'@definitelytyped/header-parser': 0.2.16 '@definitelytyped/header-parser': 0.2.19
command-exists: 1.2.9 command-exists: 1.2.9
rimraf: 3.0.2 rimraf: 3.0.2
semver: 6.3.1 semver: 6.3.1
@@ -21568,8 +21576,8 @@ snapshots:
dtslint@4.2.1(typescript@5.5.4): dtslint@4.2.1(typescript@5.5.4):
dependencies: dependencies:
'@definitelytyped/header-parser': 0.2.16 '@definitelytyped/header-parser': 0.2.19
'@definitelytyped/typescript-versions': 0.1.6 '@definitelytyped/typescript-versions': 0.1.8
'@definitelytyped/utils': 0.1.8 '@definitelytyped/utils': 0.1.8
dts-critic: 3.3.11(typescript@5.5.4) dts-critic: 3.3.11(typescript@5.5.4)
fs-extra: 6.0.1 fs-extra: 6.0.1
@@ -23913,7 +23921,7 @@ snapshots:
'@jest/expect': 29.7.0 '@jest/expect': 29.7.0
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 18.19.45 '@types/node': 18.19.74
chalk: 4.1.2 chalk: 4.1.2
co: 4.6.0 co: 4.6.0
dedent: 1.5.3 dedent: 1.5.3
@@ -24088,7 +24096,7 @@ snapshots:
'@jest/environment': 29.7.0 '@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0 '@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 18.19.45 '@types/node': 18.19.74
jest-mock: 29.7.0 jest-mock: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
@@ -24098,7 +24106,7 @@ snapshots:
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/graceful-fs': 4.1.9 '@types/graceful-fs': 4.1.9
'@types/node': 18.19.45 '@types/node': 18.19.74
anymatch: 3.1.3 anymatch: 3.1.3
fb-watchman: 2.0.2 fb-watchman: 2.0.2
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@@ -24137,7 +24145,7 @@ snapshots:
jest-mock@29.7.0: jest-mock@29.7.0:
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 18.19.45 '@types/node': 18.19.74
jest-util: 29.7.0 jest-util: 29.7.0
jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
@@ -24161,7 +24169,7 @@ snapshots:
jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0)
jest-util: 29.7.0 jest-util: 29.7.0
jest-validate: 29.7.0 jest-validate: 29.7.0
resolve: 1.22.8 resolve: 1.22.10
resolve.exports: 2.0.2 resolve.exports: 2.0.2
slash: 3.0.0 slash: 3.0.0
@@ -24172,7 +24180,7 @@ snapshots:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 18.19.45 '@types/node': 18.19.74
chalk: 4.1.2 chalk: 4.1.2
emittery: 0.13.1 emittery: 0.13.1
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@@ -24200,7 +24208,7 @@ snapshots:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 18.19.45 '@types/node': 18.19.74
chalk: 4.1.2 chalk: 4.1.2
cjs-module-lexer: 1.3.1 cjs-module-lexer: 1.3.1
collect-v8-coverage: 1.0.2 collect-v8-coverage: 1.0.2
@@ -24246,7 +24254,7 @@ snapshots:
jest-util@29.7.0: jest-util@29.7.0:
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 18.19.45 '@types/node': 18.19.74
chalk: 4.1.2 chalk: 4.1.2
ci-info: 3.9.0 ci-info: 3.9.0
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@@ -24265,7 +24273,7 @@ snapshots:
dependencies: dependencies:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 18.19.45 '@types/node': 18.19.74
ansi-escapes: 4.3.2 ansi-escapes: 4.3.2
chalk: 4.1.2 chalk: 4.1.2
emittery: 0.13.1 emittery: 0.13.1
@@ -24279,7 +24287,7 @@ snapshots:
jest-worker@29.7.0: jest-worker@29.7.0:
dependencies: dependencies:
'@types/node': 18.19.45 '@types/node': 18.19.74
jest-util: 29.7.0 jest-util: 29.7.0
merge-stream: 2.0.0 merge-stream: 2.0.0
supports-color: 8.1.1 supports-color: 8.1.1
@@ -26065,7 +26073,7 @@ snapshots:
normalize-package-data@3.0.3: normalize-package-data@3.0.3:
dependencies: dependencies:
hosted-git-info: 4.1.0 hosted-git-info: 4.1.0
is-core-module: 2.15.1 is-core-module: 2.16.1
semver: 7.5.4 semver: 7.5.4
validate-npm-package-license: 3.0.4 validate-npm-package-license: 3.0.4
@@ -26753,7 +26761,7 @@ snapshots:
'@protobufjs/path': 1.1.2 '@protobufjs/path': 1.1.2
'@protobufjs/pool': 1.1.0 '@protobufjs/pool': 1.1.0
'@protobufjs/utf8': 1.1.0 '@protobufjs/utf8': 1.1.0
'@types/node': 18.19.45 '@types/node': 18.19.74
long: 5.2.3 long: 5.2.3
proxy-addr@2.0.7: proxy-addr@2.0.7:
@@ -26764,7 +26772,7 @@ snapshots:
proxy-agent@6.4.0: proxy-agent@6.4.0:
dependencies: dependencies:
agent-base: 7.1.1 agent-base: 7.1.1
debug: 4.3.6 debug: 4.4.0
http-proxy-agent: 7.0.2 http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.5 https-proxy-agent: 7.0.5
lru-cache: 7.18.3 lru-cache: 7.18.3
@@ -26936,7 +26944,7 @@ snapshots:
'@types/doctrine': 0.0.9 '@types/doctrine': 0.0.9
'@types/resolve': 1.20.6 '@types/resolve': 1.20.6
doctrine: 3.0.0 doctrine: 3.0.0
resolve: 1.22.8 resolve: 1.22.10
strip-indent: 4.0.0 strip-indent: 4.0.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -27435,7 +27443,7 @@ snapshots:
resolve@2.0.0-next.5: resolve@2.0.0-next.5:
dependencies: dependencies:
is-core-module: 2.15.1 is-core-module: 2.16.1
path-parse: 1.0.7 path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0 supports-preserve-symlinks-flag: 1.0.0
@@ -28088,7 +28096,7 @@ snapshots:
dependencies: dependencies:
component-emitter: 1.3.1 component-emitter: 1.3.1
cookiejar: 2.1.4 cookiejar: 2.1.4
debug: 4.3.6 debug: 4.4.0
fast-safe-stringify: 2.1.1 fast-safe-stringify: 2.1.1
form-data: 4.0.0 form-data: 4.0.0
formidable: 3.5.1 formidable: 3.5.1
@@ -28832,7 +28840,7 @@ snapshots:
'@types/node': 20.16.1 '@types/node': 20.16.1
'@types/unist': 3.0.3 '@types/unist': 3.0.3
concat-stream: 2.0.0 concat-stream: 2.0.0
debug: 4.3.6 debug: 4.4.0
extend: 3.0.2 extend: 3.0.2
glob: 10.4.5 glob: 10.4.5
ignore: 5.3.2 ignore: 5.3.2