feat: message structures (#10982)

* feat: message structures

* fix: docs

* chore: components and more

* feat: embed and more

* feat: more substructures and code review suggestions

* chore: tests and date conversions

* chore: jsdoc strings

* fix: tests

* fix: tests

* feat: hexColor getters

* chore: remove getters for nested data

* chore: apply suggestions from code review

* fix: burst_colors in toJSON

* docs: rephrase SectionBuilder remark

* chore: add LabelComponent

* fix: add name and size to file component

* chore: move resolved interaction data to interactions dir

* fix: code review

* chore: bump discord-api-types

* chore: apply code review suggestions

* fix: lockfile

* chore: update remark

* fix: missing export

* chore: code review and tests

* build: fix file

* fix: typo

* fix: missing toJSON

* fix: remove redundant patch overrides

* chore: missing component suffix

* chore: better name

* chore: add comment explaining timestamp conversion

---------

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
This commit is contained in:
Qjuh
2025-11-28 17:52:42 +01:00
committed by GitHub
parent aeb8986aa1
commit 19253f6b7b
79 changed files with 3281 additions and 93 deletions

View File

@@ -38,7 +38,7 @@ import {
TextChannel,
ThreadMetadata,
VoiceChannel,
} from '../src/index.js';
} from '../src/channels/index.js';
import { kData } from '../src/utils/symbols.js';
describe('text channel', () => {

View File

@@ -1,7 +1,8 @@
import type { APIExtendedInvite, APIInvite } from 'discord-api-types/v10';
import { InviteTargetType, InviteType } from 'discord-api-types/v10';
import { describe, expect, test } from 'vitest';
import { Invite } from '../src/index.js';
import { Invite } from '../src/invites/Invite.js';
import { dateToDiscordISOTimestamp } from '../src/utils/optimization.js';
import { kPatch } from '../src/utils/symbols.js';
describe('Invite', () => {
@@ -22,7 +23,7 @@ describe('Invite', () => {
const dataExtended: Omit<APIExtendedInvite, 'expires_at'> = {
...data,
created_at: '2020-10-10T13:50:17.209Z',
created_at: '2020-10-10T13:50:17.209000+00:00',
max_age: 12,
max_uses: 34,
temporary: false,
@@ -54,7 +55,7 @@ describe('Invite', () => {
const instance = new Invite(dataExtended);
expect(instance.type).toBe(data.type);
expect(instance.code).toBe(dataExtended.code);
expect(instance.createdAt?.toISOString()).toBe(dataExtended.created_at);
expect(dateToDiscordISOTimestamp(instance.createdAt!)).toBe(dataExtended.created_at);
expect(instance.createdTimestamp).toBe(Date.parse(dataExtended.created_at));
expect(instance.maxAge).toBe(dataExtended.max_age);
expect(instance.maxUses).toBe(dataExtended.max_uses);
@@ -63,10 +64,10 @@ describe('Invite', () => {
expect(instance.targetType).toBe(dataExtended.target_type);
expect(instance.temporary).toBe(dataExtended.temporary);
expect(instance.uses).toBe(dataExtended.uses);
expect(instance.expiresTimestamp).toStrictEqual(Date.parse('2020-10-10T13:50:29.209Z'));
expect(instance.expiresAt).toStrictEqual(new Date('2020-10-10T13:50:29.209Z'));
expect(instance.expiresTimestamp).toStrictEqual(Date.parse('2020-10-10T13:50:29.209000+00:00'));
expect(instance.expiresAt).toStrictEqual(new Date('2020-10-10T13:50:29.209000+00:00'));
expect(instance.url).toBe('https://discord.gg/123');
expect(instance.toJSON()).toEqual({ ...dataExtended, expires_at: '2020-10-10T13:50:29.209Z' });
expect(instance.toJSON()).toEqual({ ...dataExtended, expires_at: '2020-10-10T13:50:29.209000+00:00' });
});
test('Invite with omitted properties', () => {
@@ -79,8 +80,8 @@ describe('Invite', () => {
});
test('Invite with expiration', () => {
const instance = new Invite({ ...dataExtended, expires_at: '2020-10-10T13:50:29.209Z' });
expect(instance.toJSON()).toEqual({ ...dataExtended, expires_at: '2020-10-10T13:50:29.209Z' });
const instance = new Invite({ ...dataExtended, expires_at: '2020-10-10T13:50:29.209000+00:00' });
expect(instance.toJSON()).toEqual({ ...dataExtended, expires_at: '2020-10-10T13:50:29.209000+00:00' });
});
test('Patching Invite works in place', () => {

View File

@@ -0,0 +1,478 @@
import { DiscordSnowflake } from '@sapphire/snowflake';
import type {
APIActionRowComponent,
APIButtonComponent,
APIChannelSelectComponent,
APIContainerComponent,
APIFileComponent,
APIMediaGalleryComponent,
APIMentionableSelectComponent,
APIMessage,
APIRoleSelectComponent,
APISectionComponent,
APISeparatorComponent,
APIStringSelectComponent,
APIUser,
APIUserSelectComponent,
} from 'discord-api-types/v10';
import {
MessageReferenceType,
MessageType,
MessageFlags,
ComponentType,
ButtonStyle,
SeparatorSpacingSize,
ChannelType,
SelectMenuDefaultValueType,
} from 'discord-api-types/v10';
import { describe, expect, test } from 'vitest';
import { Attachment } from '../src/messages/Attachment.js';
import { Message } from '../src/messages/Message.js';
import { ContainerComponent } from '../src/messages/components/ContainerComponent.js';
import { Embed } from '../src/messages/embeds/Embed.js';
import { User } from '../src/users/User.js';
import { dateToDiscordISOTimestamp } from '../src/utils/optimization.js';
const user: APIUser = {
username: 'user',
avatar: 'abcd123',
global_name: 'User',
discriminator: '0',
id: '3',
};
describe('message with embeds and attachments', () => {
const timestamp = '2025-10-09T17:48:20.192000+00:00';
const data: APIMessage = {
id: DiscordSnowflake.generate({ timestamp: Date.parse(timestamp) }).toString(),
type: MessageType.Default,
position: 10,
channel_id: '2',
author: user,
attachments: [
{
filename: 'file.txt',
description: 'describe attachment',
id: '0',
proxy_url: 'https://media.example.com/attachment/123.txt',
size: 5,
url: 'https://example.com/attachment/123.txt',
},
],
content: 'something <&5> <&6>',
edited_timestamp: '2025-10-09T17:50:20.292000+00:00',
embeds: [
{
author: {
name: 'embed author',
icon_url: 'https://discord.js.org/static/logo.svg',
},
color: 255,
description: 'describe me',
fields: [
{
name: 'field name',
value: 'field value',
inline: false,
},
],
footer: {
text: 'footer',
},
image: {
url: 'https://discord.js.org/static/logo.svg',
},
thumbnail: {
url: 'https://discord.js.org/static/logo.svg',
},
title: 'Title',
timestamp: '2025-10-19T21:39:40.193000+00:00',
},
],
mention_everyone: false,
mention_roles: ['5', '6'],
mentions: [user],
pinned: false,
timestamp,
tts: false,
flags: MessageFlags.SuppressNotifications,
};
test('Message has all properties', () => {
const instance = new Message(data);
expect(instance.id).toBe(data.id);
expect(instance.channelId).toBe(data.channel_id);
expect(instance.position).toBe(data.position);
expect(instance.content).toBe(data.content);
expect(instance.createdTimestamp).toBe(Date.parse(data.timestamp));
expect(dateToDiscordISOTimestamp(instance.createdAt!)).toBe(data.timestamp);
expect(instance.flags?.toJSON()).toBe(data.flags);
expect(instance.editedTimestamp).toBe(Date.parse(data.edited_timestamp!));
expect(dateToDiscordISOTimestamp(instance.editedAt!)).toBe(data.edited_timestamp);
expect(instance.nonce).toBe(data.nonce);
expect(instance.pinned).toBe(data.pinned);
expect(instance.tts).toBe(data.tts);
expect(instance.webhookId).toBe(data.webhook_id);
expect(instance.type).toBe(MessageType.Default);
expect(instance.toJSON()).toEqual(data);
});
test('Attachment sub-structure', () => {
const instances = data.attachments?.map((attachment) => new Attachment(attachment));
expect(instances?.map((attachment) => attachment.toJSON())).toEqual(data.attachments);
expect(instances?.[0]?.description).toBe(data.attachments?.[0]?.description);
expect(instances?.[0]?.filename).toBe(data.attachments?.[0]?.filename);
expect(instances?.[0]?.id).toBe(data.attachments?.[0]?.id);
expect(instances?.[0]?.size).toBe(data.attachments?.[0]?.size);
expect(instances?.[0]?.url).toBe(data.attachments?.[0]?.url);
expect(instances?.[0]?.proxyURL).toBe(data.attachments?.[0]?.proxy_url);
});
test('Embed sub-structure', () => {
const instances = data.embeds?.map((embed) => new Embed(embed));
expect(instances?.map((embed) => embed.toJSON())).toEqual(data.embeds);
expect(instances?.[0]?.description).toBe(data.embeds?.[0]?.description);
expect(instances?.[0]?.color).toBe(data.embeds?.[0]?.color);
expect(instances?.[0]?.timestamp).toBe(Date.parse(data.embeds![0]!.timestamp!));
expect(instances?.[0]?.title).toBe(data.embeds?.[0]?.title);
expect(instances?.[0]?.url).toBe(data.embeds?.[0]?.url);
expect(instances?.[0]?.type).toBe(data.embeds?.[0]?.type);
});
test('User sub-structure', () => {
const instance = new User(data.author);
const instances = data.mentions.map((user) => new User(user));
expect(instance.toJSON()).toEqual(data.author);
expect(instances.map((user) => user.toJSON())).toEqual(data.mentions);
expect(instance.avatar).toBe(data.author.avatar);
expect(instance.discriminator).toBe(data.author.discriminator);
expect(instance.displayName).toBe(data.author.global_name);
expect(instance.globalName).toBe(data.author.global_name);
expect(instance.id).toBe(data.author.id);
expect(instance.username).toBe(data.author.username);
});
});
describe('message with components', () => {
const timestamp = '2025-10-10T15:48:20.192000+00:00';
const buttonRow: APIActionRowComponent<APIButtonComponent> = {
type: ComponentType.ActionRow,
id: 5,
components: [
{
type: ComponentType.Button,
style: ButtonStyle.Danger,
custom_id: 'danger',
disabled: false,
emoji: {
animated: false,
id: '12345',
name: 'emoji',
},
id: 6,
label: 'Danger button',
},
{
type: ComponentType.Button,
style: ButtonStyle.Link,
url: 'https://discord.js.org/',
disabled: false,
id: 7,
label: 'DJS',
},
{
type: ComponentType.Button,
style: ButtonStyle.Premium,
sku_id: '9876',
disabled: false,
id: 8,
},
],
};
const file: APIFileComponent = {
type: ComponentType.File,
file: {
url: 'attachment://file.txt',
attachment_id: '0',
content_type: 'text/plain',
flags: 0,
},
id: 9,
spoiler: true,
};
const mediaGallery: APIMediaGalleryComponent = {
type: ComponentType.MediaGallery,
items: [
{
media: {
url: 'https://discord.js.org/static/logo.svg',
content_type: 'image/svg+xml',
height: 50,
width: 50,
},
description: 'Logo',
spoiler: false,
},
],
id: 10,
};
const section: APISectionComponent = {
type: ComponentType.Section,
accessory: {
type: ComponentType.Thumbnail,
media: {
url: 'https://discord.js.org/static/logo.svg',
},
description: 'Logo thumbnail',
id: 13,
spoiler: false,
},
components: [
{
type: ComponentType.TextDisplay,
content: 'Text',
id: 14,
},
],
id: 12,
};
const separator: APISeparatorComponent = {
type: ComponentType.Separator,
divider: true,
id: 15,
spacing: SeparatorSpacingSize.Large,
};
const channelRow: APIActionRowComponent<APIChannelSelectComponent> = {
type: ComponentType.ActionRow,
id: 16,
components: [
{
type: ComponentType.ChannelSelect,
custom_id: 'channel',
channel_types: [ChannelType.GuildCategory, ChannelType.GuildText],
default_values: [
{
id: '123456789012345678',
type: SelectMenuDefaultValueType.Channel,
},
{
id: '123456789012345679',
type: SelectMenuDefaultValueType.Channel,
},
],
disabled: false,
id: 17,
max_values: 2,
min_values: 0,
placeholder: '(none)',
required: false,
},
],
};
const mentionRow: APIActionRowComponent<APIMentionableSelectComponent> = {
type: ComponentType.ActionRow,
id: 18,
components: [
{
type: ComponentType.MentionableSelect,
custom_id: 'mention',
default_values: [
{
id: '123456789012345678',
type: SelectMenuDefaultValueType.User,
},
{
id: '123456789012345679',
type: SelectMenuDefaultValueType.Role,
},
],
disabled: false,
id: 19,
max_values: 2,
min_values: 0,
placeholder: '(none)',
required: false,
},
],
};
const roleRow: APIActionRowComponent<APIRoleSelectComponent> = {
type: ComponentType.ActionRow,
id: 20,
components: [
{
type: ComponentType.RoleSelect,
custom_id: 'role',
default_values: [
{
id: '123456789012345678',
type: SelectMenuDefaultValueType.Role,
},
{
id: '123456789012345679',
type: SelectMenuDefaultValueType.Role,
},
],
disabled: false,
id: 21,
max_values: 2,
min_values: 0,
placeholder: '(none)',
required: false,
},
],
};
const userRow: APIActionRowComponent<APIUserSelectComponent> = {
type: ComponentType.ActionRow,
id: 22,
components: [
{
type: ComponentType.UserSelect,
custom_id: 'user',
default_values: [
{
id: '123456789012345678',
type: SelectMenuDefaultValueType.User,
},
{
id: '123456789012345679',
type: SelectMenuDefaultValueType.User,
},
],
disabled: false,
id: 23,
max_values: 2,
min_values: 0,
placeholder: '(none)',
required: false,
},
],
};
const stringRow: APIActionRowComponent<APIStringSelectComponent> = {
type: ComponentType.ActionRow,
id: 24,
components: [
{
type: ComponentType.StringSelect,
custom_id: 'string',
options: [
{
label: 'one',
value: '1',
default: true,
},
{
label: 'two',
value: '2',
default: false,
},
{
label: 'three',
value: '3',
description: 'third',
emoji: {
id: '3333333333333333333',
name: '3',
animated: false,
},
},
],
disabled: false,
id: 25,
max_values: 2,
min_values: 0,
placeholder: '(none)',
required: false,
},
],
};
const container: APIContainerComponent = {
type: ComponentType.Container,
accent_color: 255,
id: 4,
components: [
buttonRow,
file,
mediaGallery,
section,
separator,
channelRow,
mentionRow,
roleRow,
userRow,
stringRow,
],
spoiler: true,
};
const data: APIMessage = {
id: DiscordSnowflake.generate({ timestamp: Date.parse(timestamp) }).toString(),
type: MessageType.Reply,
position: 15,
channel_id: '2',
author: user,
attachments: [
{
filename: 'file.txt',
description: 'describe attachment',
id: '0',
proxy_url: 'https://media.example.com/attachment/123.txt',
size: 5,
url: 'https://example.com/attachment/123.txt',
},
],
content: '',
edited_timestamp: '2025-10-10T15:50:20.292000+00:00',
embeds: [],
components: [container],
message_reference: {
channel_id: '505050505050505050',
message_id: '606060606060606060',
guild_id: '707070707070707070',
type: MessageReferenceType.Default,
},
mention_everyone: false,
mention_roles: ['5', '6'],
mentions: [user],
pinned: false,
timestamp,
tts: false,
flags: MessageFlags.IsComponentsV2 | MessageFlags.Ephemeral,
};
test('Message has all properties', () => {
const instance = new Message(data);
expect(instance.id).toBe(data.id);
expect(instance.channelId).toBe(data.channel_id);
expect(instance.position).toBe(data.position);
expect(instance.content).toBe(data.content);
expect(instance.createdTimestamp).toBe(Date.parse(data.timestamp));
expect(dateToDiscordISOTimestamp(instance.createdAt!)).toBe(data.timestamp);
expect(instance.flags?.toJSON()).toBe(data.flags);
expect(instance.editedTimestamp).toBe(Date.parse(data.edited_timestamp!));
expect(dateToDiscordISOTimestamp(instance.editedAt!)).toBe(data.edited_timestamp);
expect(instance.nonce).toBe(data.nonce);
expect(instance.pinned).toBe(data.pinned);
expect(instance.tts).toBe(data.tts);
expect(instance.webhookId).toBe(data.webhook_id);
expect(instance.type).toBe(MessageType.Reply);
expect(instance.toJSON()).toEqual(data);
});
test('Attachment sub-structure', () => {
const instances = data.attachments?.map((attachment) => new Attachment(attachment));
expect(instances?.map((attachment) => attachment.toJSON())).toEqual(data.attachments);
expect(instances?.[0]?.description).toBe(data.attachments?.[0]?.description);
expect(instances?.[0]?.filename).toBe(data.attachments?.[0]?.filename);
expect(instances?.[0]?.id).toBe(data.attachments?.[0]?.id);
expect(instances?.[0]?.size).toBe(data.attachments?.[0]?.size);
expect(instances?.[0]?.url).toBe(data.attachments?.[0]?.url);
expect(instances?.[0]?.proxyURL).toBe(data.attachments?.[0]?.proxy_url);
});
test('Component sub-structures', () => {
const containerInstance = new ContainerComponent(data.components?.[0] as APIContainerComponent);
expect(containerInstance.toJSON()).toEqual(container);
expect(containerInstance.type).toBe(container.type);
expect(containerInstance.id).toBe(container.id);
expect(containerInstance.spoiler).toBe(container.spoiler);
});
});

View File

@@ -1,4 +1,3 @@
import { expectNotType, expectType } from 'tsd';
import { expectTypeOf } from 'vitest';
import type { MixinTypes } from '../../src/MixinTypes.d.ts';
import type { kMixinConstruct } from '../../src/utils/symbols.js';
@@ -16,22 +15,26 @@ declare const extendsBothOmitBoth: Omit<
keyof Base | typeof kMixinConstruct
>;
expectType<MixinTypes<Base, [MixinProperty1]>>(extendsNoOmit);
expectType<MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>]>>(extendsOmitProperty1);
expectNotType<MixinTypes<Base, [MixinProperty1]>>(extendsOmitProperty1);
expectNotType<MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>]>>(extendsNoOmit);
expectTypeOf(extendsNoOmit).toEqualTypeOf<MixinTypes<Base, [MixinProperty1]>>();
expectTypeOf(extendsOmitProperty1).toEqualTypeOf<MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>]>>();
expectTypeOf(extendsOmitProperty1).not.toEqualTypeOf<MixinTypes<Base, [MixinProperty1]>>();
expectTypeOf(extendsNoOmit).not.toEqualTypeOf<MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>]>>();
expectType<MixinTypes<Base, [MixinProperty1, MixinProperty2]>>(extendsBothNoOmit);
expectTypeOf(extendsBothNoOmit).toEqualTypeOf<MixinTypes<Base, [MixinProperty1, MixinProperty2]>>();
// Since MixinProperty2 doesn't utilize the type of property1 in kData, this works and is ok
expectType<MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>, MixinProperty2]>>(extendsBothOmitProperty1);
expectNotType<MixinTypes<Base, [MixinProperty1, MixinProperty2]>>(extendsBothOmitProperty1);
expectTypeOf(extendsBothOmitProperty1).toEqualTypeOf<
MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>, MixinProperty2]>
>();
expectTypeOf(extendsBothOmitProperty1).not.toEqualTypeOf<MixinTypes<Base, [MixinProperty1, MixinProperty2]>>();
// Since MixinProperty2 doesn't utilize the type of property1 in kData, this works and is ok
expectNotType<MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>, MixinProperty2]>>(extendsBothNoOmit);
expectTypeOf(extendsBothNoOmit).not.toEqualTypeOf<
MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>, MixinProperty2]>
>();
// Earlier mixins in the list must specify all properties because of the way merging works
expectType<
expectTypeOf(extendsBothOmitBoth).toEqualTypeOf<
MixinTypes<Base<'property1' | 'property2'>, [MixinProperty1<'property1' | 'property2'>, MixinProperty2<'property2'>]>
>(extendsBothOmitBoth);
>();
expectTypeOf<MixinTypes<Base<'property1'>, [MixinProperty1]>>().toBeNever();
// @ts-expect-error Shouldn't be able to assign non identical omits

View File

@@ -1,79 +1,81 @@
import type { ChannelType, GuildChannelType, GuildTextChannelType, ThreadChannelType } from 'discord-api-types/v10';
import { expectNever, expectType } from 'tsd';
import type { Channel } from '../../src/index.js';
import { expectTypeOf } from 'vitest';
import type { Channel } from '../../src/channels/Channel.js';
declare const channel: Channel;
if (channel.isGuildBased()) {
expectType<string>(channel.guildId);
expectType<GuildChannelType>(channel.type);
expectTypeOf(channel.guildId).toBeString();
expectTypeOf(channel.type).toEqualTypeOf<GuildChannelType>();
if (channel.isDMBased()) {
expectNever(channel);
expectTypeOf(channel).toBeNever();
}
if (channel.isPermissionCapable()) {
expectType<Exclude<GuildChannelType, ChannelType.GuildDirectory | ThreadChannelType>>(channel.type);
expectTypeOf(channel.type).toEqualTypeOf<
Exclude<GuildChannelType, ChannelType.GuildDirectory | ThreadChannelType>
>();
}
if (channel.isTextBased()) {
expectType<GuildTextChannelType>(channel.type);
expectTypeOf(channel.type).toEqualTypeOf<GuildTextChannelType>();
}
if (channel.isWebhookCapable()) {
expectType<ChannelType.GuildForum | ChannelType.GuildMedia | Exclude<GuildTextChannelType, ThreadChannelType>>(
channel.type,
);
expectTypeOf(channel.type).toEqualTypeOf<
ChannelType.GuildForum | ChannelType.GuildMedia | Exclude<GuildTextChannelType, ThreadChannelType>
>();
}
if (channel.isThread()) {
expectType<ThreadChannelType>(channel.type);
expectTypeOf(channel.type).toEqualTypeOf<ThreadChannelType>();
}
if (channel.isThreadOnly()) {
expectType<ChannelType.GuildForum | ChannelType.GuildMedia>(channel.type);
expectTypeOf(channel.type).toEqualTypeOf<ChannelType.GuildForum | ChannelType.GuildMedia>();
}
if (channel.isVoiceBased()) {
expectType<ChannelType.GuildStageVoice | ChannelType.GuildVoice>(channel.type);
expectTypeOf(channel.type).toEqualTypeOf<ChannelType.GuildStageVoice | ChannelType.GuildVoice>();
if (!channel.isTextBased()) {
expectNever(channel);
expectTypeOf(channel).toBeNever();
}
if (!channel.isWebhookCapable()) {
expectNever(channel);
expectTypeOf(channel).toBeNever();
}
}
}
if (channel.isDMBased()) {
expectType<ChannelType.DM | ChannelType.GroupDM>(channel.type);
expectTypeOf(channel.type).toEqualTypeOf<ChannelType.DM | ChannelType.GroupDM>();
if (channel.isGuildBased()) {
expectNever(channel);
expectTypeOf(channel).toBeNever();
}
if (channel.isPermissionCapable()) {
expectNever(channel);
expectTypeOf(channel).toBeNever();
}
if (channel.isWebhookCapable()) {
expectNever(channel);
expectTypeOf(channel).toBeNever();
}
if (channel.isVoiceBased()) {
expectNever(channel);
expectTypeOf(channel).toBeNever();
}
if (channel.isThread()) {
expectNever(channel);
expectTypeOf(channel).toBeNever();
}
if (channel.isThreadOnly()) {
expectNever(channel);
expectTypeOf(channel).toBeNever();
}
if (channel.isTextBased()) {
expectType<ChannelType.DM | ChannelType.GroupDM>(channel.type);
expectTypeOf(channel.type).toEqualTypeOf<ChannelType.DM | ChannelType.GroupDM>();
}
}

View File

@@ -79,7 +79,6 @@
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.6.2",
"tsd": "^0.33.0",
"tsup": "^8.5.0",
"turbo": "^2.5.8",
"typescript": "~5.9.3",
@@ -91,8 +90,5 @@
"publishConfig": {
"access": "public",
"provenance": true
},
"tsd": {
"directory": "__tests__/types"
}
}

View File

@@ -0,0 +1,16 @@
import { AttachmentFlags } from 'discord-api-types/v10';
import { BitField } from './BitField.js';
/**
* Data structure that makes it easy to interact with a {@link Attachment#flags} bitfield.
*/
export class AttachmentFlagsBitField extends BitField<keyof AttachmentFlags> {
/**
* Numeric attachment flags.
*/
public static override readonly Flags = AttachmentFlags;
public override toJSON() {
return super.toJSON(true);
}
}

View File

@@ -0,0 +1,16 @@
import { MessageFlags } from 'discord-api-types/v10';
import { BitField } from './BitField.js';
/**
* Data structure that makes it easy to interact with a {@link Message#flags} bitfield.
*/
export class MessageFlagsBitField extends BitField<keyof MessageFlags> {
/**
* Numeric message flags.
*/
public static override readonly Flags = MessageFlags;
public override toJSON() {
return super.toJSON(true);
}
}

View File

@@ -1,4 +1,6 @@
export * from './BitField.js';
export * from './AttachmentFlagsBitField.js';
export * from './ChannelFlagsBitField.js';
export * from './MessageFlagsBitField.js';
export * from './PermissionsBitField.js';

View File

@@ -2,7 +2,7 @@ import { DiscordSnowflake } from '@sapphire/snowflake';
import type { APIChannel, APIPartialChannel, ChannelType, ChannelFlags } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { ChannelFlagsBitField } from '../bitfields/ChannelFlagsBitField.js';
import { kData, kPatch } from '../utils/symbols.js';
import { kData } from '../utils/symbols.js';
import { isIdSet } from '../utils/type-guards.js';
import type { Partialize } from '../utils/types.js';
import type { ChannelPermissionMixin } from './mixins/ChannelPermissionMixin.js';
@@ -50,15 +50,6 @@ export class Channel<
super(data as ChannelDataType<Type>);
}
/**
* {@inheritDoc Structure.[kPatch]}
*
* @internal
*/
public override [kPatch](data: Partial<ChannelDataType<Type>>) {
return super[kPatch](data);
}
/**
* The id of the channel
*/

View File

@@ -1,6 +1,10 @@
export * from './bitfields/index.js';
export * from './channels/index.js';
export * from './interactions/index.js';
export * from './invites/index.js';
export * from './messages/index.js';
export * from './polls/index.js';
export * from './stickers/index.js';
export * from './users/index.js';
export * from './Structure.js';
export * from './Mixin.js';

View File

@@ -0,0 +1,20 @@
import type { APIInteractionDataResolved } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import type { Partialize } from '../utils/types.js';
/**
* Represents data for users, members, channels, and roles in the message's auto-populated select menus.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has substructures `User`, `Channel`, `Role`, `Message`, `GuildMember`, `Attachment`, which need to be instantiated and stored by an extending class using it
*/
export abstract class ResolvedInteractionData<
Omitted extends keyof APIInteractionDataResolved | '' = '',
> extends Structure<APIInteractionDataResolved, Omitted> {
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<APIInteractionDataResolved, Omitted>) {
super(data);
}
}

View File

@@ -0,0 +1 @@
export * from './ResolvedInteractionData.js';

View File

@@ -1,6 +1,7 @@
import { type APIInvite, type APIExtendedInvite, RouteBases } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData, kExpiresTimestamp, kCreatedTimestamp, kPatch } from '../utils/symbols.js';
import { dateToDiscordISOTimestamp } from '../utils/optimization.js';
import { kData, kExpiresTimestamp, kCreatedTimestamp } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
export interface APIActualInvite extends APIInvite, Partial<Omit<APIExtendedInvite, keyof APIInvite>> {}
@@ -47,16 +48,6 @@ export class Invite<Omitted extends keyof APIActualInvite | '' = 'created_at' |
this.optimizeData(data);
}
/**
* {@inheritDoc Structure.[kPatch]}
*
* @internal
*/
public override [kPatch](data: Partial<APIActualInvite>) {
super[kPatch](data);
return this;
}
/**
* {@inheritDoc Structure.optimizeData}
*
@@ -201,11 +192,11 @@ export class Invite<Omitted extends keyof APIActualInvite | '' = 'created_at' |
public override toJSON() {
const clone = super.toJSON();
if (this[kExpiresTimestamp]) {
clone.expires_at = new Date(this[kExpiresTimestamp]).toISOString();
clone.expires_at = dateToDiscordISOTimestamp(new Date(this[kExpiresTimestamp]));
}
if (this[kCreatedTimestamp]) {
clone.created_at = new Date(this[kCreatedTimestamp]).toISOString();
clone.created_at = dateToDiscordISOTimestamp(new Date(this[kCreatedTimestamp]));
}
return clone;

View File

@@ -0,0 +1,33 @@
import type { APIApplicationCommandInteractionMetadata, InteractionType } from 'discord-api-types/v10';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
import { InteractionMetadata } from './InteractionMetadata.js';
/**
* Represents metadata about the application command interaction causing a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has a substructure `User` which needs to be instantiated and stored by an extending class using it
*/
export class ApplicationCommandInteractionMetadata<
Omitted extends keyof APIApplicationCommandInteractionMetadata | '' = '',
> extends InteractionMetadata<InteractionType.ApplicationCommand, Omitted> {
/**
* The template used for removing data from the raw data stored for each ApplicationCommandInteractionMetadata.
*/
public static override readonly DataTemplate: Partial<APIApplicationCommandInteractionMetadata> = {};
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<APIApplicationCommandInteractionMetadata, Omitted>) {
super(data);
}
/**
* The id of the message the command was run on
*/
public get targetMessageId() {
return this[kData].target_message_id;
}
}

View File

@@ -0,0 +1,118 @@
import type { APIAttachment, AttachmentFlags } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { AttachmentFlagsBitField } from '../bitfields/AttachmentFlagsBitField.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
export class Attachment<Omitted extends keyof APIAttachment | '' = ''> extends Structure<APIAttachment, Omitted> {
/**
* The template used for removing data from the raw data stored for each Attachment.
*/
public static override readonly DataTemplate: Partial<APIAttachment> = {};
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<APIAttachment, Omitted>) {
super(data);
}
/**
* The id of the attachment
*/
public get id() {
return this[kData].id;
}
/**
* The name of the attached file
*/
public get filename() {
return this[kData].filename;
}
/**
* The title of the file
*/
public get title() {
return this[kData].title;
}
/**
* The description for the file
*/
public get description() {
return this[kData].description;
}
/**
* The attachment's media type
*/
public get contentType() {
return this[kData].content_type;
}
/**
* The size of the file in bytes
*/
public get size() {
return this[kData].size;
}
/**
* The source URL of the file
*/
public get url() {
return this[kData].url;
}
/**
* A proxied URL of the file
*/
public get proxyURL() {
return this[kData].proxy_url;
}
/**
* The height of the file (if image)
*/
public get height() {
return this[kData].height;
}
/**
* The width of the file (if image)
*/
public get width() {
return this[kData].width;
}
/**
* Whether this attachment is ephemeral
*/
public get ephemeral() {
return this[kData].ephemeral;
}
/**
* The duration of the audio file
*/
public get durationSecs() {
return this[kData].duration_secs;
}
/**
* Base64 encoded bytearray representing a sampled waveform
*/
public get waveform() {
return this[kData].waveform;
}
/**
* Attachment flags combined as a bitfield
*/
public get flags() {
const flags = this[kData].flags;
return flags ? new AttachmentFlagsBitField(this[kData].flags as AttachmentFlags) : null;
}
}

View File

@@ -0,0 +1,54 @@
import type { APIChannelMention } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
/**
* Represents the mention of a channel on a message on Discord.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class ChannelMention<Omitted extends keyof APIChannelMention | '' = ''> extends Structure<
APIChannelMention,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each ChannelMention.
*/
public static override DataTemplate: Partial<APIChannelMention> = {};
/**
* @param data - The raw data received from the API for the channel mention
*/
public constructor(data: Partialize<APIChannelMention, Omitted>) {
super(data);
}
/**
* The type of the mentioned channel
*/
public get type() {
return this[kData].type;
}
/**
* The name of the mentioned channel
*/
public get name() {
return this[kData].name;
}
/**
* The id of the mentioned channel
*/
public get id() {
return this[kData].id;
}
/**
* The id of the guild the mentioned channel is in
*/
public get guildId() {
return this[kData].guild_id;
}
}

View File

@@ -0,0 +1,48 @@
import type { APIMessageInteractionMetadata, InteractionType } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
export type InteractionMetadataType<Type extends InteractionType> = Extract<
APIMessageInteractionMetadata,
{ type: Type }
>;
/**
* Represents metadata about the interaction causing a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has a substructure `User` which needs to be instantiated and stored by an extending class using it
*/
export abstract class InteractionMetadata<
Type extends InteractionType,
Omitted extends keyof InteractionMetadataType<Type> | '' = '',
> extends Structure<InteractionMetadataType<Type>, Omitted> {
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<InteractionMetadataType<Type>, Omitted>) {
super(data as InteractionMetadataType<Type>);
}
/**
* The id of the interaction
*/
public get id() {
return this[kData].id;
}
/**
* The id of the original response message, present only on follow-up messages
*/
public get originalResponseMessageId() {
return this[kData].original_response_message_id;
}
/**
* The type of interaction
*/
public get type() {
return this[kData].type;
}
}

View File

@@ -0,0 +1,178 @@
import { DiscordSnowflake } from '@sapphire/snowflake';
import type { APIMessage, MessageFlags } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { MessageFlagsBitField } from '../bitfields/MessageFlagsBitField.js';
import { dateToDiscordISOTimestamp } from '../utils/optimization.js';
import { kData, kEditedTimestamp } from '../utils/symbols.js';
import { isIdSet } from '../utils/type-guards.js';
import type { Partialize } from '../utils/types.js';
// TODO: missing substructures: application
/**
* Represents a message on Discord.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has substructures `Message`, `Channel`, `MessageActivity`, `MessageCall`, `MessageReference`, `Attachment`, `Application`, `ChannelMention`, `Reaction`, `Poll`, `ResolvedInteractionData`, `RoleSubscriptionData`, `Sticker`, all the different `Component`s, ... which need to be instantiated and stored by an extending class using it
*/
export class Message<Omitted extends keyof APIMessage | '' = 'edited_timestamp' | 'timestamp'> extends Structure<
APIMessage,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each Message
*/
public static override DataTemplate: Partial<APIMessage> = {
set timestamp(_: string) {},
set edited_timestamp(_: string) {},
};
protected [kEditedTimestamp]: number | null = null;
/**
* @param data - The raw data received from the API for the message
*/
public constructor(data: Partialize<APIMessage, Omitted>) {
super(data);
this.optimizeData(data);
}
/**
* {@inheritDoc Structure.optimizeData}
*
* @internal
*/
protected override optimizeData(data: Partial<APIMessage>) {
if (data.edited_timestamp) {
this[kEditedTimestamp] = Date.parse(data.edited_timestamp);
}
}
/**
* The message's id
*/
public get id() {
return this[kData].id;
}
/**
* The id of the interaction's application, if this message is a reply to an interaction
*/
public get applicationId() {
return this[kData].application_id;
}
/**
* The channel's id this message was sent in
*/
public get channelId() {
return this[kData].channel_id;
}
/**
* The timestamp this message was created at
*/
public get createdTimestamp() {
return isIdSet(this.id) ? DiscordSnowflake.timestampFrom(this.id) : null;
}
/**
* The time the message was created at
*/
public get createdAt() {
const createdTimestamp = this.createdTimestamp;
return createdTimestamp ? new Date(createdTimestamp) : null;
}
/**
* The content of the message
*/
public get content() {
return this[kData].content;
}
/**
* The timestamp this message was last edited at, or `null` if it never was edited
*/
public get editedTimestamp() {
return this[kEditedTimestamp];
}
/**
* The time the message was last edited at, or `null` if it never was edited
*/
public get editedAt() {
const editedTimestamp = this.editedTimestamp;
return editedTimestamp ? new Date(editedTimestamp) : null;
}
/**
* The flags of this message as a bit field
*/
public get flags() {
const flags = this[kData].flags;
return flags ? new MessageFlagsBitField(this[kData].flags as MessageFlags) : null;
}
/**
* The nonce used when sending this message.
*
* @remarks This is only present in MESSAGE_CREATE event, if a nonce was provided when sending
*/
public get nonce() {
return this[kData].nonce;
}
/**
* Whether this message is pinned in its channel
*/
public get pinned() {
return this[kData].pinned;
}
/**
* A generally increasing integer (there may be gaps or duplicates) that represents the approximate position of the message in a thread
* It can be used to estimate the relative position of the message in a thread in company with `totalMessageSent` on parent thread
*/
public get position() {
return this[kData].position;
}
/**
* Whether this message was a TTS message
*/
public get tts() {
return this[kData].tts;
}
/**
* The type of message
*/
public get type() {
return this[kData].type;
}
/**
* If the message is generated by a webhook, this is the webhook's id
*/
public get webhookId() {
return this[kData].webhook_id;
}
/**
* {@inheritDoc Structure.toJSON}
*/
public override toJSON() {
const clone = super.toJSON();
if (this[kEditedTimestamp]) {
clone.edited_timestamp = dateToDiscordISOTimestamp(new Date(this[kEditedTimestamp]));
}
const createdAt = this.createdAt;
if (createdAt) {
clone.timestamp = dateToDiscordISOTimestamp(createdAt);
}
return clone;
}
}

View File

@@ -0,0 +1,35 @@
import type { APIMessageActivity } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
export class MessageActivity<Omitted extends keyof APIMessageActivity | '' = ''> extends Structure<
APIMessageActivity,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each MessageActivity.
*/
public static override readonly DataTemplate: Partial<APIMessageActivity> = {};
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<APIMessageActivity, Omitted>) {
super(data);
}
/**
* The party id from a Rich Presence event
*/
public get partyId() {
return this[kData].party_id;
}
/**
* The type of message activity
*/
public get type() {
return this[kData].type;
}
}

View File

@@ -0,0 +1,65 @@
import type { APIMessageCall } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { dateToDiscordISOTimestamp } from '../utils/optimization.js';
import { kEndedTimestamp } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
export class MessageCall<Omitted extends keyof APIMessageCall | '' = 'ended_timestamp'> extends Structure<
APIMessageCall,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each MessageCall
*/
public static override DataTemplate: Partial<APIMessageCall> = {
set ended_timestamp(_: string) {},
};
protected [kEndedTimestamp]: number | null = null;
/**
* @param data - The raw data received from the API for the message call
*/
public constructor(data: Partialize<APIMessageCall, Omitted>) {
super(data);
this.optimizeData(data);
}
/**
* {@inheritDoc Structure.optimizeData}
*
* @internal
*/
protected override optimizeData(data: Partial<APIMessageCall>) {
if (data.ended_timestamp) {
this[kEndedTimestamp] = Date.parse(data.ended_timestamp);
}
}
/**
* The timestamp this call ended at, or `null` if it didn't end yet
*/
public get endedTimestamp() {
return this[kEndedTimestamp];
}
/**
* The time the call ended at, or `null` if it didn't end yet
*/
public get endedAt() {
const endedTimestamp = this.endedTimestamp;
return endedTimestamp ? new Date(endedTimestamp) : null;
}
/**
* {@inheritDoc Structure.toJSON}
*/
public override toJSON() {
const clone = super.toJSON();
if (this[kEndedTimestamp]) {
clone.ended_timestamp = dateToDiscordISOTimestamp(new Date(this[kEndedTimestamp]));
}
return clone;
}
}

View File

@@ -0,0 +1,32 @@
import type { APIMessageComponentInteractionMetadata, InteractionType } from 'discord-api-types/v10';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
import { InteractionMetadata } from './InteractionMetadata.js';
/**
* Represents metadata about the message component interaction causing a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class MessageComponentInteractionMetadata<
Omitted extends keyof APIMessageComponentInteractionMetadata | '' = '',
> extends InteractionMetadata<InteractionType.MessageComponent, Omitted> {
/**
* The template used for removing data from the raw data stored for each MessageComponentInteractionMetadata.
*/
public static override readonly DataTemplate: Partial<APIMessageComponentInteractionMetadata> = {};
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<APIMessageComponentInteractionMetadata, Omitted>) {
super(data);
}
/**
* The id of the message that contained the interactive component
*/
public get interactedMessageId() {
return this[kData].interacted_message_id;
}
}

View File

@@ -0,0 +1,54 @@
import { MessageReferenceType, type APIMessageReference } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
/**
* Represents the reference to another message on a message on Discord.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class MessageReference<Omitted extends keyof APIMessageReference | '' = ''> extends Structure<
APIMessageReference,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each MessageReference.
*/
public static override DataTemplate: Partial<APIMessageReference> = {};
/**
* @param data - The raw data received from the API for the message reference
*/
public constructor(data: Partialize<APIMessageReference, Omitted>) {
super(data);
}
/**
* The type of this reference
*/
public get type() {
return 'type' in this[kData] ? (this[kData].type as MessageReferenceType) : MessageReferenceType.Default;
}
/**
* The id of the referenced message
*/
public get messageId() {
return this[kData].message_id;
}
/**
* The id of the channel the referenced message was sent in
*/
public get channelId() {
return this[kData].channel_id;
}
/**
* The id of the guild the referenced message was sent in
*/
public get guildId() {
return this[kData].guild_id;
}
}

View File

@@ -0,0 +1,25 @@
import type { APIModalSubmitInteractionMetadata, InteractionType } from 'discord-api-types/v10';
import type { Partialize } from '../utils/types.js';
import { InteractionMetadata } from './InteractionMetadata.js';
/**
* Represents metadata about the modal submit interaction causing a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has a substructure `InteractionMetadata` which needs to be instantiated and stored by an extending class using it
*/
export class ModalSubmitInteractionMetadata<
Omitted extends keyof APIModalSubmitInteractionMetadata | '' = '',
> extends InteractionMetadata<InteractionType.ModalSubmit, Omitted> {
/**
* The template used for removing data from the raw data stored for each ModalSubmitInteractionMetadata.
*/
public static override readonly DataTemplate: Partial<APIModalSubmitInteractionMetadata> = {};
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<APIModalSubmitInteractionMetadata, Omitted>) {
super(data);
}
}

View File

@@ -0,0 +1,80 @@
import type { APIReaction } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kBurstColors, kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
/**
* Represents a reaction on a message on Discord.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has substructures `Emoji`, `ReactionCountDetails` which need to be instantiated and stored by an extending class using it
*/
export class Reaction<Omitted extends keyof APIReaction | '' = ''> extends Structure<APIReaction, Omitted> {
/**
* The template used for removing data from the raw data stored for each Reaction.
*/
public static override DataTemplate: Partial<APIReaction> = {
set burst_colors(_: string[]) {},
};
protected [kBurstColors]: number[] | null = null;
/**
* @param data - The raw data received from the API for the reaction
*/
public constructor(data: Partialize<APIReaction, Omitted>) {
super(data);
this.optimizeData(data);
}
/**
* {@inheritDoc Structure.optimizeData}
*
* @internal
*/
protected override optimizeData(data: Partial<APIReaction>) {
if (data.burst_colors) {
this[kBurstColors] = data.burst_colors.map((color) => Number.parseInt(color, 16));
}
}
/**
* The amount how often this emoji has been used to react (including super reacts)
*/
public get count() {
return this[kData].count;
}
/**
* Whether the current user has reacted using this emoji
*/
public get me() {
return this[kData].me;
}
/**
* Whether the current user has super-reacted using this emoji
*/
public get meBurst() {
return this[kData].me_burst;
}
/**
* The colors used for super reaction
*/
public get burstColors() {
return this[kBurstColors];
}
/**
* {@inheritDoc Structure.toJSON}
*/
public override toJSON() {
const clone = super.toJSON();
if (this[kBurstColors]) {
clone.burst_colors = this[kBurstColors].map((color) => `#${color.toString(16).padStart(6, '0')}`);
}
return clone;
}
}

View File

@@ -0,0 +1,40 @@
import type { APIReactionCountDetails } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
/**
* Represents the usage count of a reaction on a message on Discord.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class ReactionCountDetails<Omitted extends keyof APIReactionCountDetails | '' = ''> extends Structure<
APIReactionCountDetails,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each ReactionCountDetails.
*/
public static override DataTemplate: Partial<APIReactionCountDetails> = {};
/**
* @param data - The raw data received from the API for the reaction count details
*/
public constructor(data: Partialize<APIReactionCountDetails, Omitted>) {
super(data);
}
/**
* The amount how often this emoji has been used to react (excluding super reacts)
*/
public get normal() {
return this[kData].normal;
}
/**
* The amount how often this emoji has been used to super-react
*/
public get burst() {
return this[kData].burst;
}
}

View File

@@ -0,0 +1,48 @@
import type { APIMessageRoleSubscriptionData } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
/**
* Represents metadata about the role subscription causing a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export abstract class RoleSubscriptionData<
Omitted extends keyof APIMessageRoleSubscriptionData | '' = '',
> extends Structure<APIMessageRoleSubscriptionData, Omitted> {
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<APIMessageRoleSubscriptionData, Omitted>) {
super(data);
}
/**
* The id of the SKU and listing the user is subscribed to
*/
public get roleSubscriptionListingId() {
return this[kData].role_subscription_listing_id;
}
/**
* The name of the tier the user is subscribed to
*/
public get tierName() {
return this[kData].tier_name;
}
/**
* The number of months the user has been subscribed for
*/
public get totalMonthsSubscribed() {
return this[kData].total_months_subscribed;
}
/**
* Whether this notification is for a renewal
*/
public get isRenewal() {
return this[kData].is_renewal;
}
}

View File

@@ -0,0 +1,27 @@
import type { APIActionRowComponent, APIComponentInActionRow, ComponentType } from 'discord-api-types/v10';
import type { Partialize } from '../../utils/types.js';
import type { ComponentDataType } from './Component.js';
import { Component } from './Component.js';
/**
* Represents an action row component on a message or modal.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has `Component`s as substructures which need to be instantiated and stored by an extending class using it
*/
export class ActionRowComponent<
Type extends APIComponentInActionRow,
Omitted extends keyof APIActionRowComponent<Type> | '' = '',
> extends Component<ComponentDataType<ComponentType.ActionRow>, Omitted> {
/**
* The template used for removing data from the raw data stored for each ActionRowComponent.
*/
public static override readonly DataTemplate: Partial<ComponentDataType<ComponentType.ActionRow>> = {};
/**
* @param data - The raw data received from the API for the action row
*/
public constructor(data: Partialize<ComponentDataType<ComponentType.ActionRow>, Omitted>) {
super(data);
}
}

View File

@@ -0,0 +1,41 @@
import type { APIButtonComponent, APIButtonComponentWithCustomId, ButtonStyle } from 'discord-api-types/v10';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
import { Component } from './Component.js';
/**
* The data stored by a {@link ButtonComponent} structure based on its {@link (ButtonComponent:class)."style"} property.
*/
export type ButtonDataType<Style extends ButtonStyle> = Style extends
| ButtonStyle.Danger
| ButtonStyle.Primary
| ButtonStyle.Secondary
| ButtonStyle.Success
? APIButtonComponentWithCustomId
: Extract<APIButtonComponent, { style: Style }>;
export abstract class ButtonComponent<
Style extends ButtonStyle,
Omitted extends keyof ButtonDataType<Style> | '' = '',
> extends Component<ButtonDataType<Style>, Omitted> {
/**
* @param data - The raw data received from the API for the button
*/
public constructor(data: Partialize<ButtonDataType<Style>, Omitted>) {
super(data);
}
/**
* The style of the button
*/
public get style() {
return this[kData].style;
}
/**
* The status of the button
*/
public get disabled() {
return typeof this[kData].disabled === 'boolean' ? this[kData].disabled : null;
}
}

View File

@@ -0,0 +1,33 @@
import type { APIChannelSelectComponent, ChannelType } from 'discord-api-types/v10';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
import { SelectMenuComponent } from './SelectMenuComponent.js';
/**
* Represents a channel select menu component.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has `SelectMenuDefaultValue`s as substructures which need to be instantiated and stored by an extending class using it
*/
export class ChannelSelectMenuComponent<
Omitted extends keyof APIChannelSelectComponent | '' = '',
> extends SelectMenuComponent<APIChannelSelectComponent, Omitted> {
/**
* The template used for removing data from the raw data stored for each ChannelSelectMenuComponent.
*/
public static override readonly DataTemplate: Partial<APIChannelSelectComponent> = {};
/**
* @param data - The raw data received from the API for the channel select menu
*/
public constructor(data: Partialize<APIChannelSelectComponent, Omitted>) {
super(data);
}
/**
* The list of channel types to include in the channel select component
*/
public get channelTypes() {
return Array.isArray(this[kData].channel_types) ? (this[kData].channel_types as readonly ChannelType[]) : null;
}
}

View File

@@ -0,0 +1,36 @@
import type { APIBaseComponent, APIMessageComponent, APIModalComponent, ComponentType } from 'discord-api-types/v10';
import { Structure } from '../../Structure.js';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
/**
* The data stored by a {@link Component} structure based on its {@link (Component:class)."type"} property.
*/
export type ComponentDataType<Type extends ComponentType | 'unknown'> = Type extends ComponentType
? Extract<APIMessageComponent | APIModalComponent, { type: Type }>
: APIBaseComponent<ComponentType>;
export abstract class Component<
Type extends APIMessageComponent | APIModalComponent,
Omitted extends keyof Type | '' = '',
> extends Structure<Type, Omitted> {
/**
* @param data - The raw data received from the API for the component
*/
public constructor(data: Partialize<Type, Omitted>) {
super(data as Type);
}
/**
* 32 bit integer used as an optional identifier for component
*/
public get id() {
return this[kData].id;
}
/**
* The type of the component
*/
public get type() {
return this[kData].type;
}
}

View File

@@ -0,0 +1,42 @@
import type { APIMessageComponentEmoji } from 'discord-api-types/v10';
import { Structure } from '../../Structure.js';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
export class ComponentEmoji<Omitted extends keyof APIMessageComponentEmoji | '' = ''> extends Structure<
APIMessageComponentEmoji,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each ComponentEmoji.
*/
public static override readonly DataTemplate: Partial<APIMessageComponentEmoji> = {};
/**
* @param data - The raw data received from the API for the component emoji
*/
public constructor(data: Partialize<APIMessageComponentEmoji, Omitted>) {
super(data);
}
/**
* The id of the emoji
*/
public get id() {
return this[kData].id;
}
/**
* The name of the emoji
*/
public get name() {
return this[kData].name;
}
/**
* Whether this emoji is animated
*/
public get animated() {
return this[kData].animated;
}
}

View File

@@ -0,0 +1,50 @@
import type { APIContainerComponent } from 'discord-api-types/v10';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
import { Component } from './Component.js';
/**
* Represents a container component on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has `Component`s as substructures which need to be instantiated and stored by an extending class using it
*/
export class ContainerComponent<Omitted extends keyof APIContainerComponent | '' = ''> extends Component<
APIContainerComponent,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each ContainerComponent.
*/
public static override readonly DataTemplate: Partial<APIContainerComponent> = {};
/**
* @param data - The raw data received from the API for the container
*/
public constructor(data: Partialize<APIContainerComponent, Omitted>) {
super(data);
}
/**
* Color for the accent on the container as RGB
*/
public get accentColor() {
return this[kData].accent_color;
}
/**
* The hexadecimal version of the accent color, with a leading hash
*/
public get hexAccentColor() {
const accentColor = this.accentColor;
if (typeof accentColor !== 'number') return accentColor;
return `#${accentColor.toString(16).padStart(6, '0')}`;
}
/**
* Whether the container should be a spoiler (or blurred out)
*/
public get spoiler() {
return this[kData].spoiler;
}
}

View File

@@ -0,0 +1,48 @@
import type { APIFileComponent } from 'discord-api-types/v10';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
import { Component } from './Component.js';
/**
* Represents a file component on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has a substructure `UnfurledMediaItem` which needs to be instantiated and stored by an extending class using it
*/
export class FileComponent<Omitted extends keyof APIFileComponent | '' = ''> extends Component<
APIFileComponent,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each FileComponent.
*/
public static override readonly DataTemplate: Partial<APIFileComponent> = {};
/**
* @param data - The raw data received from the API for the file component
*/
public constructor(data: Partialize<APIFileComponent, Omitted>) {
super(data);
}
/**
* Whether the media should be a spoiler (or blurred out)
*/
public get spoiler() {
return this[kData].spoiler;
}
/**
* The name of the file
*/
public get name() {
return this[kData].name;
}
/**
* The size of the file in bytes
*/
public get size() {
return this[kData].size;
}
}

View File

@@ -0,0 +1,54 @@
import type { APIFileUploadComponent } from 'discord-api-types/v10';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
import { Component } from './Component.js';
/**
* Represents a file upload component on a modal.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class FileUploadComponent<Omitted extends keyof APIFileUploadComponent | '' = ''> extends Component<
APIFileUploadComponent,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each FileUploadComponent.
*/
public static override readonly DataTemplate: Partial<APIFileUploadComponent> = {};
/**
* @param data - The raw data received from the API for the file upload component
*/
public constructor(data: Partialize<APIFileUploadComponent, Omitted>) {
super(data);
}
/**
* The custom id to be sent in the interaction when the modal gets submitted
*/
public get customId() {
return this[kData].custom_id;
}
/**
* The maximum number of items that can be uploaded
*/
public get maxValues() {
return this[kData].max_values;
}
/**
* The minimum number of items that must be uploaded
*/
public get minValues() {
return this[kData].min_values;
}
/**
* Whether the file upload requires files to be uploaded before submitting the modal
*/
public get required() {
return this[kData].required;
}
}

View File

@@ -0,0 +1,34 @@
import type { APIButtonComponentWithCustomId, ButtonStyle } from 'discord-api-types/v10';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
import type { ButtonDataType } from './ButtonComponent.js';
import { LabeledButtonComponent } from './LabeledButtonComponent.js';
/**
* Represents a button causing a message component interaction on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class InteractiveButtonComponent<
Style extends ButtonStyle.Danger | ButtonStyle.Primary | ButtonStyle.Secondary | ButtonStyle.Success,
Omitted extends keyof APIButtonComponentWithCustomId | '' = '',
> extends LabeledButtonComponent<Style, Omitted> {
/**
* The template used for removing data from the raw data stored for each InteractiveButtonComponent.
*/
public static override readonly DataTemplate: Partial<APIButtonComponentWithCustomId> = {};
/**
* @param data - The raw data received from the API for the interactive button
*/
public constructor(data: Partialize<APIButtonComponentWithCustomId, Omitted>) {
super(data as ButtonDataType<Style>);
}
/**
* The custom id to be sent in the interaction when clicked
*/
public get customId() {
return this[kData].custom_id;
}
}

View File

@@ -0,0 +1,42 @@
import type { APILabelComponent, ComponentType } from 'discord-api-types/v10';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
import type { ComponentDataType } from './Component.js';
import { Component } from './Component.js';
/**
* Represents a label component on a modal.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has `Component`s as substructures which need to be instantiated and stored by an extending class using it
*/
export class LabelComponent<Omitted extends keyof APILabelComponent | '' = ''> extends Component<
ComponentDataType<ComponentType.Label>,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each LabelComponent.
*/
public static override readonly DataTemplate: Partial<ComponentDataType<ComponentType.Label>> = {};
/**
* @param data - The raw data received from the API for the label
*/
public constructor(data: Partialize<ComponentDataType<ComponentType.Label>, Omitted>) {
super(data);
}
/**
* The label text
*/
public get label() {
return this[kData].label;
}
/**
* An optional description text for the label
*/
public get description() {
return this[kData].description;
}
}

View File

@@ -0,0 +1,30 @@
import type { ButtonStyle } from 'discord-api-types/v10';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
import type { ButtonDataType } from './ButtonComponent.js';
import { ButtonComponent } from './ButtonComponent.js';
/**
* Base class for all buttons that can have a label on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has a substructure `ComponentEmoji` which needs to be instantiated and stored by an extending class using it
*/
export abstract class LabeledButtonComponent<
Style extends Exclude<ButtonStyle, ButtonStyle.Premium>,
Omitted extends keyof ButtonDataType<Style> | '' = '',
> extends ButtonComponent<Style, Omitted> {
/**
* @param data - The raw data received from the API for the button
*/
public constructor(data: Partialize<ButtonDataType<Style>, Omitted>) {
super(data);
}
/**
* The label to be displayed on the button
*/
public get label() {
return this[kData].label;
}
}

View File

@@ -0,0 +1,32 @@
import type { APIButtonComponentWithURL, ButtonStyle } from 'discord-api-types/v10';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
import { LabeledButtonComponent } from './LabeledButtonComponent.js';
/**
* Represents a button linking to an URL on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class LinkButtonComponent<
Omitted extends keyof APIButtonComponentWithURL | '' = '',
> extends LabeledButtonComponent<ButtonStyle.Link, Omitted> {
/**
* The template used for removing data from the raw data stored for each LinkButtonComponent.
*/
public static override readonly DataTemplate: Partial<APIButtonComponentWithURL> = {};
/**
* @param data - The raw data received from the API for the link button
*/
public constructor(data: Partialize<APIButtonComponentWithURL, Omitted>) {
super(data);
}
/**
* The URL to direct users to when clicked
*/
public get url() {
return this[kData].url;
}
}

View File

@@ -0,0 +1,26 @@
import type { APIMediaGalleryComponent } from 'discord-api-types/v10';
import type { Partialize } from '../../utils/types.js';
import { Component } from './Component.js';
/**
* Represents a media gallery component on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has `MediaGalleryItem`s as substructures which need to be instantiated and stored by an extending class using it
*/
export class MediaGalleryComponent<Omitted extends keyof APIMediaGalleryComponent | '' = ''> extends Component<
APIMediaGalleryComponent,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each MediaGalleryComponent.
*/
public static override readonly DataTemplate: Partial<APIMediaGalleryComponent> = {};
/**
* @param data - The raw data received from the API for the media gallery
*/
public constructor(data: Partialize<APIMediaGalleryComponent, Omitted>) {
super(data);
}
}

View File

@@ -0,0 +1,41 @@
import type { APIMediaGalleryItem } from 'discord-api-types/v10';
import { Structure } from '../../Structure.js';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
/**
* Represents an item in a media gallery on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has a substructure `UnfurledMediaItem` which needs to be instantiated and stored by an extending class using it
*/
export class MediaGalleryItem<Omitted extends keyof APIMediaGalleryItem | '' = ''> extends Structure<
APIMediaGalleryItem,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each MediaGalleryItem.
*/
public static override readonly DataTemplate: Partial<APIMediaGalleryItem> = {};
/**
* @param data - The raw data received from the API for the media gallery item
*/
public constructor(data: Partialize<APIMediaGalleryItem, Omitted>) {
super(data);
}
/**
* Alt text for the media
*/
public get description() {
return this[kData].description;
}
/**
* Whether the media should be a spoiler (or blurred out)
*/
public get spoiler() {
return this[kData].spoiler;
}
}

View File

@@ -0,0 +1,25 @@
import type { APIMentionableSelectComponent } from 'discord-api-types/v10';
import type { Partialize } from '../../utils/types.js';
import { SelectMenuComponent } from './SelectMenuComponent.js';
/**
* Represents a mentionable select menu component.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has `SelectMenuDefaultValue`s as substructures which need to be instantiated and stored by an extending class using it
*/
export class MentionableSelectMenuComponent<
Omitted extends keyof APIMentionableSelectComponent | '' = '',
> extends SelectMenuComponent<APIMentionableSelectComponent, Omitted> {
/**
* The template used for removing data from the raw data stored for each MentionableSelectMenuComponent.
*/
public static override readonly DataTemplate: Partial<APIMentionableSelectComponent> = {};
/**
* @param data - The raw data received from the API for the mentionable select menu
*/
public constructor(data: Partialize<APIMentionableSelectComponent, Omitted>) {
super(data);
}
}

View File

@@ -0,0 +1,32 @@
import type { APIButtonComponentWithSKUId, ButtonStyle } from 'discord-api-types/v10';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
import { ButtonComponent } from './ButtonComponent.js';
/**
* Represents a button used to buy an SKU from a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class PremiumButtonComponent<
Omitted extends keyof APIButtonComponentWithSKUId | '' = '',
> extends ButtonComponent<ButtonStyle.Premium, Omitted> {
/**
* The template used for removing data from the raw data stored for each PremiumButtonComponent.
*/
public static override readonly DataTemplate: Partial<APIButtonComponentWithSKUId> = {};
/**
* @param data - The raw data received from the API for the premium button
*/
public constructor(data: Partialize<APIButtonComponentWithSKUId, Omitted>) {
super(data);
}
/**
* The id for a purchasable SKU
*/
public get skuId() {
return this[kData].sku_id;
}
}

View File

@@ -0,0 +1,25 @@
import type { APIRoleSelectComponent } from 'discord-api-types/v10';
import type { Partialize } from '../../utils/types.js';
import { SelectMenuComponent } from './SelectMenuComponent.js';
/**
* Represents a role select menu component.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has `SelectMenuDefaultValue`s as substructures which need to be instantiated and stored by an extending class using it
*/
export class RoleSelectMenuComponent<
Omitted extends keyof APIRoleSelectComponent | '' = '',
> extends SelectMenuComponent<APIRoleSelectComponent, Omitted> {
/**
* The template used for removing data from the raw data stored for each RoleSelectMenuComponent.
*/
public static override readonly DataTemplate: Partial<APIRoleSelectComponent> = {};
/**
* @param data - The raw data received from the API for the role select menu
*/
public constructor(data: Partialize<APIRoleSelectComponent, Omitted>) {
super(data);
}
}

View File

@@ -0,0 +1,26 @@
import type { APISectionComponent } from 'discord-api-types/v10';
import type { Partialize } from '../../utils/types.js';
import { Component } from './Component.js';
/**
* Represents a section component on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has `Component`s as substructures which need to be instantiated and stored by an extending class using it
*/
export class SectionComponent<Omitted extends keyof APISectionComponent | '' = ''> extends Component<
APISectionComponent,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each SectionComponent.
*/
public static override readonly DataTemplate: Partial<APISectionComponent> = {};
/**
* @param data - The raw data received from the API for the section
*/
public constructor(data: Partialize<APISectionComponent, Omitted>) {
super(data);
}
}

View File

@@ -0,0 +1,58 @@
import type { APISelectMenuComponent } from 'discord-api-types/v10';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
import { Component } from './Component.js';
export abstract class SelectMenuComponent<
Type extends APISelectMenuComponent,
Omitted extends keyof Type | '' = '',
> extends Component<Type, Omitted> {
/**
* @param data - The raw data received from the API for the select menu
*/
public constructor(data: Partialize<Type, Omitted>) {
super(data);
}
/**
* The customId to be sent in the interaction when a selection is made
*/
public get customId() {
return this[kData].custom_id;
}
/**
* Whether the select menu is disabled
*/
public get disabled() {
return this[kData].disabled;
}
/**
* The maximum number of items that can be chosen
*/
public get maxValues() {
return this[kData].max_values;
}
/**
* The minimum number of items that must be chosen
*/
public get minValues() {
return this[kData].min_values;
}
/**
* Custom placeholder text if nothing is selected
*/
public get placeholder() {
return this[kData].placeholder;
}
/**
* Whether a selection is required
*/
public get required() {
return this[kData].required;
}
}

View File

@@ -0,0 +1,29 @@
import type { APISelectMenuDefaultValue, SelectMenuDefaultValueType } from 'discord-api-types/v10';
import { Structure } from '../../Structure.js';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
export class SelectMenuDefaultValue<
Type extends SelectMenuDefaultValueType,
Omitted extends keyof APISelectMenuDefaultValue<Type> | '' = '',
> extends Structure<APISelectMenuDefaultValue<Type>, Omitted> {
/**
* The template used for removing data from the raw data stored for each SelectMenuDefaultValue.
*/
public static override readonly DataTemplate: Partial<APISelectMenuDefaultValue<SelectMenuDefaultValueType>> = {};
/**
* @param data - The raw data received from the API for the select menu default value
*/
public constructor(data: Partialize<APISelectMenuDefaultValue<Type>, Omitted>) {
super(data);
}
public get id() {
return this[kData].id;
}
public get type() {
return this[kData].type;
}
}

View File

@@ -0,0 +1,40 @@
import type { APISeparatorComponent } from 'discord-api-types/v10';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
import { Component } from './Component.js';
/**
* Represents a separator component on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class SeparatorComponent<Omitted extends keyof APISeparatorComponent | '' = ''> extends Component<
APISeparatorComponent,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each SeparatorComponent.
*/
public static override readonly DataTemplate: Partial<APISeparatorComponent> = {};
/**
* @param data - The raw data received from the API for the separator
*/
public constructor(data: Partialize<APISeparatorComponent, Omitted>) {
super(data);
}
/**
* Whether a visual divider should be displayed in the component
*/
public get divider() {
return this[kData].divider;
}
/**
* The size of the separator padding
*/
public get spacing() {
return this[kData].spacing;
}
}

View File

@@ -0,0 +1,25 @@
import type { APIStringSelectComponent } from 'discord-api-types/v10';
import type { Partialize } from '../../utils/types.js';
import { SelectMenuComponent } from './SelectMenuComponent.js';
/**
* Represents a string select menu component.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has `StringSelectMenuOption`s as substructures which need to be instantiated and stored by an extending class using it
*/
export class StringSelectMenuComponent<
Omitted extends keyof APIStringSelectComponent | '' = '',
> extends SelectMenuComponent<APIStringSelectComponent, Omitted> {
/**
* The template used for removing data from the raw data stored for each StringSelectMenuComponent.
*/
public static override readonly DataTemplate: Partial<APIStringSelectComponent> = {};
/**
* @param data - The raw data received from the API for the string select menu
*/
public constructor(data: Partialize<APIStringSelectComponent, Omitted>) {
super(data);
}
}

View File

@@ -0,0 +1,55 @@
import type { APISelectMenuOption } from 'discord-api-types/v10';
import { Structure } from '../../Structure.js';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
/**
* Represents an option in a string select menu component.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has a substructure `ComponentEmoji` which needs to be instantiated and stored by an extending class using it
*/
export class StringSelectMenuOption<Omitted extends keyof APISelectMenuOption | '' = ''> extends Structure<
APISelectMenuOption,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each StringSelectMenuOption.
*/
public static override readonly DataTemplate: Partial<APISelectMenuOption> = {};
/**
* @param data - The raw data received from the API for the string select menu option
*/
public constructor(data: Partialize<APISelectMenuOption, Omitted>) {
super(data);
}
/**
* Whether this option should be already-selected by default
*/
public get default() {
return this[kData].default;
}
/**
* An additional description of the option
*/
public get description() {
return this[kData].description;
}
/**
* The user-facing name of the option
*/
public get label() {
return this[kData].label;
}
/**
* The dev-defined value of the option
*/
public get value() {
return this[kData].value;
}
}

View File

@@ -0,0 +1,33 @@
import type { APITextDisplayComponent } from 'discord-api-types/v10';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
import { Component } from './Component.js';
/**
* Represents a text display component on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class TextDisplayComponent<Omitted extends keyof APITextDisplayComponent | '' = ''> extends Component<
APITextDisplayComponent,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each TextDisplayComponent.
*/
public static override readonly DataTemplate: Partial<APITextDisplayComponent> = {};
/**
* @param data - The raw data received from the API for the text display
*/
public constructor(data: Partialize<APITextDisplayComponent, Omitted>) {
super(data);
}
/**
* Text that will be displayed similar to a message
*/
public get content() {
return this[kData].content;
}
}

View File

@@ -0,0 +1,82 @@
import type { APITextInputComponent } from 'discord-api-types/v10';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
import { Component } from './Component.js';
/**
* Represents a text input component on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class TextInputComponent<Omitted extends keyof APITextInputComponent | '' = ''> extends Component<
APITextInputComponent,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each TextInputComponent.
*/
public static override readonly DataTemplate: Partial<APITextInputComponent> = {};
/**
* @param data - The raw data received from the API for the text input
*/
public constructor(data: Partialize<APITextInputComponent, Omitted>) {
super(data);
}
/**
* The custom id for the text input
*/
public get customId() {
return this[kData].custom_id;
}
/**
* Text that appears on top of the text input field
*/
public get label() {
return this[kData].label;
}
/**
* The maximal length of text input
*/
public get maxLength() {
return this[kData].max_length;
}
/**
* The minimal length of text input
*/
public get minLength() {
return this[kData].min_length;
}
/**
* The placeholder for the text input
*/
public get placeholder() {
return this[kData].placeholder;
}
/**
* Whether this text input is required
*/
public get required() {
return this[kData].required;
}
/**
* One of text input styles
*/
public get style() {
return this[kData].style;
}
/**
* The pre-filled text in the text input
*/
public get value() {
return this[kData].value;
}
}

View File

@@ -0,0 +1,41 @@
import type { APIThumbnailComponent } from 'discord-api-types/v10';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
import { Component } from './Component.js';
/**
* Represents a thumbnail component on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has a substructure `UnfurledMediaItem` which needs to be instantiated and stored by an extending class using it
*/
export class ThumbnailComponent<Omitted extends keyof APIThumbnailComponent | '' = ''> extends Component<
APIThumbnailComponent,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each ThumbnailComponent.
*/
public static override readonly DataTemplate: Partial<APIThumbnailComponent> = {};
/**
* @param data - The raw data received from the API for the thumbnail
*/
public constructor(data: Partialize<APIThumbnailComponent, Omitted>) {
super(data);
}
/**
* Alt text for the media
*/
public get description() {
return this[kData].description;
}
/**
* Whether the thumbnail should be a spoiler (or blurred out)
*/
public get spoiler() {
return this[kData].spoiler;
}
}

View File

@@ -0,0 +1,70 @@
import type { APIUnfurledMediaItem } from 'discord-api-types/v10';
import { Structure } from '../../Structure.js';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
// TODO: add `flags` as a BitField class and appropriate getter, once it gets properly documented
/**
* Represents a media item in a component on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class UnfurledMediaItem<Omitted extends keyof APIUnfurledMediaItem | '' = ''> extends Structure<
APIUnfurledMediaItem,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each UnfurledMediaItem.
*/
public static override readonly DataTemplate: Partial<APIUnfurledMediaItem> = {};
/**
* @param data - The raw data received from the API for the unfurled media item
*/
public constructor(data: Partialize<APIUnfurledMediaItem, Omitted>) {
super(data);
}
/**
* The id of the uploaded attachment
*/
public get attachmentId() {
return this[kData].attachment_id;
}
/**
* The media type of the content
*/
public get contentType() {
return this[kData].content_type;
}
/**
* The height of the media item (if image)
*/
public get height() {
return this[kData].height;
}
/**
* The proxied URL of the media item
*/
public get proxyURL() {
return this[kData].proxy_url;
}
/**
* Supports arbitrary URLs and attachment:// references
*/
public get url() {
return this[kData].url;
}
/**
* The width of the media item (if image)
*/
public get width() {
return this[kData].width;
}
}

View File

@@ -0,0 +1,25 @@
import type { APIUserSelectComponent } from 'discord-api-types/v10';
import type { Partialize } from '../../utils/types.js';
import { SelectMenuComponent } from './SelectMenuComponent.js';
/**
* Represents a user select menu component.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has `SelectMenuDefaultValue`s as substructures which need to be instantiated and stored by an extending class using it
*/
export class UserSelectMenuComponent<
Omitted extends keyof APIUserSelectComponent | '' = '',
> extends SelectMenuComponent<APIUserSelectComponent, Omitted> {
/**
* The template used for removing data from the raw data stored for each UserSelectMenuComponent.
*/
public static override readonly DataTemplate: Partial<APIUserSelectComponent> = {};
/**
* @param data - The raw data received from the API for the user select menu
*/
public constructor(data: Partialize<APIUserSelectComponent, Omitted>) {
super(data);
}
}

View File

@@ -0,0 +1,28 @@
export * from './ActionRowComponent.js';
export * from './ButtonComponent.js';
export * from './ChannelSelectMenuComponent.js';
export * from './Component.js';
export * from './ContainerComponent.js';
export * from './FileComponent.js';
export * from './FileUploadComponent.js';
export * from './InteractiveButtonComponent.js';
export * from './LinkButtonComponent.js';
export * from './MediaGalleryComponent.js';
export * from './MentionableSelectMenuComponent.js';
export * from './LabeledButtonComponent.js';
export * from './PremiumButtonComponent.js';
export * from './RoleSelectMenuComponent.js';
export * from './SectionComponent.js';
export * from './SelectMenuComponent.js';
export * from './SeparatorComponent.js';
export * from './StringSelectMenuComponent.js';
export * from './TextDisplayComponent.js';
export * from './TextInputComponent.js';
export * from './ThumbnailComponent.js';
export * from './UserSelectMenuComponent.js';
export * from './ComponentEmoji.js';
export * from './MediaGalleryItem.js';
export * from './SelectMenuDefaultValue.js';
export * from './StringSelectMenuOption.js';
export * from './UnfurledMediaItem.js';

View File

@@ -0,0 +1,104 @@
import type { APIEmbed } from 'discord-api-types/v10';
import { Structure } from '../../Structure.js';
import { dateToDiscordISOTimestamp } from '../../utils/optimization.js';
import { kCreatedTimestamp, kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
/**
* Represents an embed on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has substructures `EmbedAuthor`, `EmbedFooter`, `EmbedField`, `EmbedImage`, `EmbedThumbnail`, `EmbedProvider`, `EmbedVideo` which need to be instantiated and stored by an extending class using it
*/
export class Embed<Omitted extends keyof APIEmbed | '' = ''> extends Structure<APIEmbed, Omitted> {
/**
* The template used for removing data from the raw data stored for each Embed.
*/
public static override readonly DataTemplate: Partial<APIEmbed> = {
set timestamp(_: string) {},
};
protected [kCreatedTimestamp]: number | null = null;
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<APIEmbed, Omitted>) {
super(data);
this.optimizeData(data);
}
/**
* {@inheritDoc Structure.optimizeData}
*
* @internal
*/
protected override optimizeData(data: Partial<APIEmbed>) {
if (data.timestamp) {
this[kCreatedTimestamp] = Date.parse(data.timestamp);
}
}
/**
* The color code of the embed
*/
public get color() {
return this[kData].color;
}
/**
* The hexadecimal version of the embed color, with a leading hash
*/
public get hexColor() {
const color = this.color;
if (typeof color !== 'number') return color;
return `#${color.toString(16).padStart(6, '0')}`;
}
/**
* The description of the embed
*/
public get description() {
return this[kData].description;
}
/**
* THe title of the embed
*/
public get title() {
return this[kData].title;
}
/**
* The timestamp of the embed content
*/
public get timestamp() {
return this[kCreatedTimestamp];
}
/**
* The type of embed (always "rich" for webhook embeds)
*/
public get type() {
return this[kData].type;
}
/**
* The URL of the embed
*/
public get url() {
return this[kData].url;
}
/**
* {@inheritDoc Structure.toJSON}
*/
public override toJSON() {
const clone = super.toJSON();
if (this[kCreatedTimestamp]) {
clone.timestamp = dateToDiscordISOTimestamp(new Date(this[kCreatedTimestamp]));
}
return clone;
}
}

View File

@@ -0,0 +1,46 @@
import type { APIEmbedAuthor } from 'discord-api-types/v10';
import { Structure } from '../../Structure.js';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
/**
* Represents author data in an embed on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class EmbedAuthor<Omitted extends keyof APIEmbedAuthor | '' = ''> extends Structure<APIEmbedAuthor, Omitted> {
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<APIEmbedAuthor, Omitted>) {
super(data);
}
/**
* The name of the author
*/
public get name() {
return this[kData].name;
}
/**
* The URL of author icon
*/
public get iconURL() {
return this[kData].icon_url;
}
/**
* A proxied URL of author icon
*/
public get proxyIconURL() {
return this[kData].proxy_icon_url;
}
/**
* The URL of the author
*/
public get url() {
return this[kData].url;
}
}

View File

@@ -0,0 +1,39 @@
import type { APIEmbedField } from 'discord-api-types/v10';
import { Structure } from '../../Structure.js';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
/**
* Represents a field's data in an embed on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class EmbedField<Omitted extends keyof APIEmbedField | '' = ''> extends Structure<APIEmbedField, Omitted> {
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<APIEmbedField, Omitted>) {
super(data);
}
/**
* The name of the field
*/
public get name() {
return this[kData].name;
}
/**
* The value of the field
*/
public get value() {
return this[kData].value;
}
/**
* Whether this field should display inline
*/
public get inline() {
return this[kData].inline;
}
}

View File

@@ -0,0 +1,39 @@
import type { APIEmbedFooter } from 'discord-api-types/v10';
import { Structure } from '../../Structure.js';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
/**
* Represents footer data in an embed on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class EmbedFooter<Omitted extends keyof APIEmbedFooter | '' = ''> extends Structure<APIEmbedFooter, Omitted> {
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<APIEmbedFooter, Omitted>) {
super(data);
}
/**
* The footer text
*/
public get text() {
return this[kData].text;
}
/**
* The URL of the footer icon
*/
public get iconURL() {
return this[kData].icon_url;
}
/**
* A proxied URL of the footer icon
*/
public get proxyIconURL() {
return this[kData].proxy_icon_url;
}
}

View File

@@ -0,0 +1,46 @@
import type { APIEmbedImage } from 'discord-api-types/v10';
import { Structure } from '../../Structure.js';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
/**
* Represents image data in an embed on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class EmbedImage<Omitted extends keyof APIEmbedImage | '' = ''> extends Structure<APIEmbedImage, Omitted> {
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<APIEmbedImage, Omitted>) {
super(data);
}
/**
* The height of the image
*/
public get height() {
return this[kData].height;
}
/**
* The width of the image
*/
public get width() {
return this[kData].width;
}
/**
* A proxied URL of the image
*/
public get proxyURL() {
return this[kData].proxy_url;
}
/**
* Source URL of the image
*/
public get url() {
return this[kData].url;
}
}

View File

@@ -0,0 +1,35 @@
import type { APIEmbedProvider } from 'discord-api-types/v10';
import { Structure } from '../../Structure.js';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
/**
* Represents provider data in an embed on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class EmbedProvider<Omitted extends keyof APIEmbedProvider | '' = ''> extends Structure<
APIEmbedProvider,
Omitted
> {
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<APIEmbedProvider, Omitted>) {
super(data);
}
/**
* The name of the provider
*/
public get name() {
return this[kData].name;
}
/**
* The URL of the provider
*/
public get url() {
return this[kData].url;
}
}

View File

@@ -0,0 +1,49 @@
import type { APIEmbedThumbnail } from 'discord-api-types/v10';
import { Structure } from '../../Structure.js';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
/**
* Represents thumbnail data in an embed on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class EmbedThumbnail<Omitted extends keyof APIEmbedThumbnail | '' = ''> extends Structure<
APIEmbedThumbnail,
Omitted
> {
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<APIEmbedThumbnail, Omitted>) {
super(data);
}
/**
* The height of the thumbnail
*/
public get height() {
return this[kData].height;
}
/**
* The width of the thumbnail
*/
public get width() {
return this[kData].width;
}
/**
* A proxied URL of the thumbnail
*/
public get proxyURL() {
return this[kData].proxy_url;
}
/**
* The source URL of the thumbnail
*/
public get url() {
return this[kData].url;
}
}

View File

@@ -0,0 +1,46 @@
import type { APIEmbedVideo } from 'discord-api-types/v10';
import { Structure } from '../../Structure.js';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
/**
* Represents video data in an embed on a message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class EmbedVideo<Omitted extends keyof APIEmbedVideo | '' = ''> extends Structure<APIEmbedVideo, Omitted> {
/**
* @param data - The raw data received from the API for the connection
*/
public constructor(data: Partialize<APIEmbedVideo, Omitted>) {
super(data);
}
/**
* The height of the video
*/
public get height() {
return this[kData].height;
}
/**
* The width of the video
*/
public get width() {
return this[kData].width;
}
/**
* A proxied URL of the video
*/
public get proxyURL() {
return this[kData].proxy_url;
}
/**
* The source URL of the video
*/
public get url() {
return this[kData].url;
}
}

View File

@@ -0,0 +1,8 @@
export * from './Embed.js';
export * from './EmbedAuthor.js';
export * from './EmbedField.js';
export * from './EmbedFooter.js';
export * from './EmbedImage.js';
export * from './EmbedProvider.js';
export * from './EmbedThumbnail.js';
export * from './EmbedVideo.js';

View File

@@ -0,0 +1,16 @@
export * from './components/index.js';
export * from './embeds/index.js';
export * from './ApplicationCommandInteractionMetadata.js';
export * from './Attachment.js';
export * from './ChannelMention.js';
export * from './InteractionMetadata.js';
export * from './Message.js';
export * from './MessageActivity.js';
export * from './MessageCall.js';
export * from './MessageComponentInteractionMetadata.js';
export * from './MessageReference.js';
export * from './ModalSubmitInteractionMetadata.js';
export * from './Reaction.js';
export * from './ReactionCountDetails.js';
export * from './RoleSubscriptionData.js';

View File

@@ -0,0 +1,87 @@
import type { APIPoll } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { dateToDiscordISOTimestamp } from '../utils/optimization.js';
import { kData, kExpiresTimestamp } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
/**
* Represents a poll on a message on Discord.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has substructures `PollMedia`, `PollAnswer`, `PollResults` which need to be instantiated and stored by an extending class using it
*/
export class Poll<Omitted extends keyof APIPoll | '' = ''> extends Structure<APIPoll, Omitted> {
/**
* The template used for removing data from the raw data stored for each Poll.
*/
public static override DataTemplate: Partial<APIPoll> = {
set expiry(_: string) {},
};
/**
* Optimized storage of {@link discord-api-types/v10#(APIPoll:interface).expiry}
*
* @internal
*/
protected [kExpiresTimestamp]: number | null = null;
/**
* @param data - The raw data received from the API for the poll
*/
public constructor(data: Partialize<APIPoll, Omitted>) {
super(data);
this.optimizeData(data);
}
/**
* {@inheritDoc Structure.optimizeData}
*
* @internal
*/
protected override optimizeData(data: Partial<APIPoll>) {
if (data.expiry) {
this[kExpiresTimestamp] = Date.parse(data.expiry);
}
}
/**
* Whether a user can select multiple answers
*/
public get allowMultiselect() {
return this[kData].allow_multiselect;
}
/**
* The layout type of the poll
*/
public get layoutType() {
return this[kData].layout_type;
}
/**
* The timestamp this poll will expire at
*/
public get expiresTimestamp() {
return this[kExpiresTimestamp];
}
/**
* The time the poll will expire at
*/
public get expiresAt() {
const expiresTimestamp = this.expiresTimestamp;
return expiresTimestamp ? new Date(expiresTimestamp) : null;
}
/**
* {@inheritDoc Structure.toJSON}
*/
public override toJSON() {
const clone = super.toJSON();
if (this[kExpiresTimestamp]) {
clone.expiry = dateToDiscordISOTimestamp(new Date(this[kExpiresTimestamp]));
}
return clone;
}
}

View File

@@ -0,0 +1,31 @@
import type { APIPollAnswer } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
/**
* Represents an answer to a poll on a message on Discord.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has a substructure `PollMedia` which need to be instantiated and stored by an extending class using it
*/
export class PollAnswer<Omitted extends keyof APIPollAnswer | '' = ''> extends Structure<APIPollAnswer, Omitted> {
/**
* The template used for removing data from the raw data stored for each PollAnswer.
*/
public static override DataTemplate: Partial<APIPollAnswer> = {};
/**
* @param data - The raw data received from the API for the poll answer
*/
public constructor(data: Partialize<APIPollAnswer, Omitted>) {
super(data);
}
/**
* The id of the poll answer
*/
public get answerId() {
return this[kData].answer_id;
}
}

View File

@@ -0,0 +1,47 @@
import type { APIPollAnswerCount } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
/**
* Represents the counts of answers to a poll on a message on Discord.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class PollAnswerCount<Omitted extends keyof APIPollAnswerCount | '' = ''> extends Structure<
APIPollAnswerCount,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each PollAnswerCount.
*/
public static override DataTemplate: Partial<APIPollAnswerCount> = {};
/**
* @param data - The raw data received from the API for the poll answer count
*/
public constructor(data: Partialize<APIPollAnswerCount, Omitted>) {
super(data);
}
/**
* The id of the poll answer
*/
public get id() {
return this[kData].id;
}
/**
* The number of votes for this answer
*/
public get count() {
return this[kData].count;
}
/**
* Whether the current user voted for this answer
*/
public get meVoted() {
return this[kData].me_voted;
}
}

View File

@@ -0,0 +1,31 @@
import type { APIPollMedia } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
/**
* Represents a field of a poll on a message on Discord.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has a substructure `Emoji` which need to be instantiated and stored by an extending class using it
*/
export class PollMedia<Omitted extends keyof APIPollMedia | '' = ''> extends Structure<APIPollMedia, Omitted> {
/**
* The template used for removing data from the raw data stored for each PollMedia.
*/
public static override DataTemplate: Partial<APIPollMedia> = {};
/**
* @param data - The raw data received from the API for the poll media
*/
public constructor(data: Partialize<APIPollMedia, Omitted>) {
super(data);
}
/**
* The text of the poll field
*/
public get text() {
return this[kData].text;
}
}

View File

@@ -0,0 +1,31 @@
import type { APIPollResults } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
/**
* Represents the results of a poll on a message on Discord.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has a substructure `PollAnswerCount` which need to be instantiated and stored by an extending class using it
*/
export class PollResults<Omitted extends keyof APIPollResults | '' = ''> extends Structure<APIPollResults, Omitted> {
/**
* The template used for removing data from the raw data stored for each PollResults.
*/
public static override DataTemplate: Partial<APIPollResults> = {};
/**
* @param data - The raw data received from the API for the poll results
*/
public constructor(data: Partialize<APIPollResults, Omitted>) {
super(data);
}
/**
* Whether the votes have been precisely counted
*/
public get isFinalized() {
return this[kData].is_finalized;
}
}

View File

@@ -0,0 +1,5 @@
export * from './Poll.js';
export * from './PollAnswer.js';
export * from './PollAnswerCount.js';
export * from './PollMedia.js';
export * from './PollResults.js';

View File

@@ -0,0 +1,72 @@
import type { APISticker } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
/**
* Represents a sticker on Discord.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class Sticker<Omitted extends keyof APISticker | '' = ''> extends Structure<APISticker, Omitted> {
/**
* The template used for removing data from the raw data stored for each SitckerItem.
*/
public static override DataTemplate: Partial<APISticker> = {};
/**
* @param data - The raw data received from the API for the sticker item
*/
public constructor(data: Partialize<APISticker, Omitted>) {
super(data);
}
/**
* The id of the sticker
*/
public get id() {
return this[kData].id;
}
/**
* The name of the sticker
*/
public get name() {
return this[kData].name;
}
/**
* The format type of the sticker
*/
public get formatType() {
return this[kData].format_type;
}
/**
* Whether this guild sticker can be used, may be false due to loss of Server Boosts
*/
public get available() {
return this[kData].available;
}
/**
* The description of the sticker
*/
public get description() {
return this[kData].description;
}
/**
* The autocomplete/suggestion tags for the sticker
*/
public get tags() {
return this[kData].tags;
}
/**
* The type of this sticker
*/
public get type() {
return this[kData].type;
}
}

View File

@@ -0,0 +1 @@
export * from './Sticker.js';

View File

@@ -1,6 +1,6 @@
import type { APIConnection } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData, kPatch } from '../utils/symbols.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
/**
@@ -21,15 +21,6 @@ export class Connection<Omitted extends keyof APIConnection | '' = ''> extends S
super(data);
}
/**
* {@inheritDoc Structure.[kPatch]}
*
* @internal
*/
public override [kPatch](data: Partial<APIConnection>) {
return super[kPatch](data);
}
/**
* The id of the connection account
*/

View File

@@ -1,7 +1,7 @@
import { DiscordSnowflake } from '@sapphire/snowflake';
import type { APIUser } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData, kPatch } from '../utils/symbols.js';
import { kData } from '../utils/symbols.js';
import { isIdSet } from '../utils/type-guards.js';
import type { Partialize } from '../utils/types.js';
@@ -24,15 +24,6 @@ export class User<Omitted extends keyof APIUser | '' = ''> extends Structure<API
super(data);
}
/**
* {@inheritDoc Structure.[kPatch]}
*
* @internal
*/
public override [kPatch](data: Partial<APIUser>) {
return super[kPatch](data);
}
/**
* The user's id
*/

View File

@@ -8,3 +8,15 @@ export function extendTemplate<SuperTemplate extends Record<string, unknown>>(
> &
SuperTemplate;
}
/**
* Turns a JavaScript Date object into the timestamp format used by Discord in payloads.
* E.g. `2025-11-16T14:09:25.239000+00:00`
*
* @private
* @param date a Date instance
* @returns an ISO8601 timestamp with microseconds precision and explicit +00:00 timezone
*/
export function dateToDiscordISOTimestamp(date: Date) {
return `${date.getUTCFullYear()}-${(date.getUTCMonth() + 1).toString().padStart(2, '0')}-${date.getUTCDate().toString().padStart(2, '0')}T${date.getUTCHours().toString().padStart(2, '0')}:${date.getUTCMinutes().toString().padStart(2, '0')}:${date.getUTCSeconds().toString().padStart(2, '0')}.${date.getUTCMilliseconds().toString().padEnd(6, '0')}+00:00`;
}

View File

@@ -2,6 +2,7 @@ export const kData = Symbol.for('djs.structures.data');
export const kClone = Symbol.for('djs.structures.clone');
export const kPatch = Symbol.for('djs.structures.patch');
export const kExpiresTimestamp = Symbol.for('djs.structures.expiresTimestamp');
export const kEndedTimestamp = Symbol.for('djs.structures.endedTimestamp');
export const kCreatedTimestamp = Symbol.for('djs.structures.createdTimestamp');
export const kEditedTimestamp = Symbol.for('djs.structures.editedTimestamp');
export const kArchiveTimestamp = Symbol.for('djs.structures.archiveTimestamp');
@@ -9,6 +10,8 @@ export const kArchiveTimestamp = Symbol.for('djs.structures.archiveTimestamp');
export const kAllow = Symbol.for('djs.structures.allow');
export const kDeny = Symbol.for('djs.structures.deny');
export const kBurstColors = Symbol.for('djs.structures.burstColors');
export const kLastPinTimestamp = Symbol.for('djs.structures.lastPinTimestamp');
export const kMixinConstruct = Symbol.for('djs.structures.mixin.construct');

3
pnpm-lock.yaml generated
View File

@@ -1619,9 +1619,6 @@ importers:
prettier:
specifier: ^3.6.2
version: 3.6.2
tsd:
specifier: ^0.33.0
version: 0.33.0
tsup:
specifier: ^8.5.0
version: 8.5.0(@microsoft/api-extractor@7.53.3(@types/node@22.18.13))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.19.2)(typescript@5.9.3)(yaml@2.8.1)