mirror of
https://github.com/discordjs/discord.js.git
synced 2026-09-17 08:47:27 +00:00
feat(structures): add Presences structures (#11411)
* feat(structures): presence structures * feat(structures): add GatewayPresenceActivityTimestamps substructure * feat(structures): add GatewayPresenceActivitySecrets * feat(structures): add GatewayPresenceActivityParty * feat(structures): add GatewayPresenceActivityEmoji * feat(structures): add GatewayPresenceActivityButton * feat(structures): add GatewayPresenceActivityAssets * feat(structures): add ActivityFlagBitFields * feat(structures): add GatewayPresenceActivity structure * docs(structures): update class remark on GatewayPresenceActivityButton * feat(structures): add GatewayPresenceClientStatus * feat(structures): add GatewayPresenceUpdate * docs(structures): update `GatewayPresenceActivity#flags` for clarity * fix(structures): return correct value for getter on ActivitySecrets * test(structures): add tests & use correct type on ActivityTimestamps * docs: rm todo comment * fix: correct incorrect implementation of party#id * chore: rename structures * feat(structures): update tests, comments, date getters * feat: split `size` into 2 different getters * test: update tests for new getters * refactor(structures)!: use xDate convention in favour of xAt * fix(structures): add `inviteCoverImage` getter + update tests * test(structures): apply review feedback * chore: use `dateToDiscordISOTimestamp` and cleanup tests * feat: isArrayFieldSet typeguard & tidy up * feat(structures): add URL getters, typeguards, update tests * test(presence): review feedback * test(presence): use realistic values This uses values from the Discord API docs. * test: rename tests to Presence instead of GatewayPresence * test(structures): review feedback * docs: use new discord docs links in jsdoc * fix: address feedback for tests Make some changes to all tests; - use a standard beforeEach block to use a new instance on each test for robustness; - check that the patched result contains the expected patched values as opposed to testing for any change across the structure, for accuracy.
This commit is contained in:
@@ -39,6 +39,7 @@ import {
|
||||
ThreadMetadata,
|
||||
VoiceChannel,
|
||||
} from '../src/channels/index.js';
|
||||
import { dateToDiscordISOTimestamp } from '../src/index.js';
|
||||
import { kData } from '../src/utils/symbols.js';
|
||||
|
||||
describe('text channel', () => {
|
||||
@@ -49,7 +50,7 @@ describe('text channel', () => {
|
||||
position: 0,
|
||||
guild_id: '2',
|
||||
last_message_id: '3',
|
||||
last_pin_timestamp: '2020-10-10T13:50:17.209Z',
|
||||
last_pin_timestamp: '2020-10-10T13:50:17.209000+00:00',
|
||||
nsfw: true,
|
||||
parent_id: '4',
|
||||
permission_overwrites: [
|
||||
@@ -77,7 +78,7 @@ describe('text channel', () => {
|
||||
expect(instance.guildId).toBe(data.guild_id);
|
||||
expect(instance.lastMessageId).toBe(data.last_message_id);
|
||||
expect(instance.lastPinTimestamp).toBe(Date.parse(data.last_pin_timestamp!));
|
||||
expect(instance.lastPinAt?.toISOString()).toBe(data.last_pin_timestamp);
|
||||
expect(dateToDiscordISOTimestamp(instance.lastPinDate!)).toBe(data.last_pin_timestamp);
|
||||
expect(instance.nsfw).toBe(data.nsfw);
|
||||
expect(instance.parentId).toBe(data.parent_id);
|
||||
expect(instance[kData].permission_overwrites).toEqual(data.permission_overwrites);
|
||||
@@ -137,8 +138,8 @@ describe('announcement channel', () => {
|
||||
expect(instance.flags?.toJSON()).toBe(data.flags);
|
||||
expect(instance.guildId).toBe(data.guild_id);
|
||||
expect(instance.lastMessageId).toBe(data.last_message_id);
|
||||
expect(instance.lastPinTimestamp).toBe(null);
|
||||
expect(instance.lastPinAt).toBe(data.last_pin_timestamp);
|
||||
expect(instance.lastPinTimestamp).toBeNull();
|
||||
expect(instance.lastPinDate).toBeNull();
|
||||
expect(instance.nsfw).toBe(data.nsfw);
|
||||
expect(instance.parentId).toBe(data.parent_id);
|
||||
expect(instance[kData].permission_overwrites).toEqual(data.permission_overwrites);
|
||||
@@ -210,7 +211,7 @@ describe('DM channel', () => {
|
||||
id: '1',
|
||||
type: ChannelType.DM,
|
||||
last_message_id: '3',
|
||||
last_pin_timestamp: '2020-10-10T13:50:17.209Z',
|
||||
last_pin_timestamp: '2020-10-10T13:50:17.209000+00:00',
|
||||
name: null,
|
||||
};
|
||||
|
||||
@@ -234,7 +235,7 @@ describe('DM channel', () => {
|
||||
expect(instance.flags?.toJSON()).toBe(data.flags);
|
||||
expect(instance.lastMessageId).toBe(data.last_message_id);
|
||||
expect(instance.lastPinTimestamp).toBe(Date.parse(data.last_pin_timestamp!));
|
||||
expect(instance.lastPinAt?.toISOString()).toBe(data.last_pin_timestamp);
|
||||
expect(dateToDiscordISOTimestamp(instance.lastPinDate!)).toBe(data.last_pin_timestamp);
|
||||
expect(instance[kData].recipients).toEqual(data.recipients);
|
||||
expect(instance.type).toBe(ChannelType.DM);
|
||||
expect(instance.url).toBe('https://discord.com/channels/@me/1');
|
||||
@@ -614,7 +615,7 @@ describe('thread channels', () => {
|
||||
const dataAnnounce: APIAnnouncementThreadChannel = {
|
||||
...dataPublic,
|
||||
thread_metadata: {
|
||||
archive_timestamp: '2024-09-08T12:01:02.345Z',
|
||||
archive_timestamp: '2024-09-08T12:01:02.345000+00:00',
|
||||
archived: false,
|
||||
auto_archive_duration: ThreadAutoArchiveDuration.ThreeDays,
|
||||
locked: true,
|
||||
@@ -627,7 +628,7 @@ describe('thread channels', () => {
|
||||
...dataPublic,
|
||||
thread_metadata: {
|
||||
...dataAnnounce.thread_metadata!,
|
||||
create_timestamp: '2023-01-02T15:13:11.987Z',
|
||||
create_timestamp: '2023-01-02T15:13:11.987000+00:00',
|
||||
invitable: true,
|
||||
},
|
||||
type: ChannelType.PrivateThread,
|
||||
@@ -730,9 +731,9 @@ describe('thread channels', () => {
|
||||
const instance = new ThreadMetadata(dataPrivate.thread_metadata!);
|
||||
expect(instance.toJSON()).toEqual(dataPrivate.thread_metadata);
|
||||
expect(instance.archived).toBe(dataPrivate.thread_metadata?.archived);
|
||||
expect(instance.archivedAt?.toISOString()).toBe(dataPrivate.thread_metadata?.archive_timestamp);
|
||||
expect(dateToDiscordISOTimestamp(instance.archivedDate!)).toBe(dataPrivate.thread_metadata?.archive_timestamp);
|
||||
expect(instance.archivedTimestamp).toBe(Date.parse(dataPrivate.thread_metadata!.archive_timestamp));
|
||||
expect(instance.createdAt?.toISOString()).toBe(dataPrivate.thread_metadata?.create_timestamp);
|
||||
expect(dateToDiscordISOTimestamp(instance.createdDate!)).toBe(dataPrivate.thread_metadata?.create_timestamp);
|
||||
expect(instance.createdTimestamp).toBe(Date.parse(dataPrivate.thread_metadata!.create_timestamp!));
|
||||
expect(instance.autoArchiveDuration).toBe(dataPrivate.thread_metadata?.auto_archive_duration);
|
||||
expect(instance.invitable).toBe(dataPrivate.thread_metadata?.invitable);
|
||||
|
||||
@@ -34,17 +34,17 @@ describe('Invite', () => {
|
||||
const instance = new Invite(data);
|
||||
expect(instance.type).toBe(data.type);
|
||||
expect(instance.code).toBe(data.code);
|
||||
expect(instance.createdAt).toBe(null);
|
||||
expect(instance.createdTimestamp).toBe(null);
|
||||
expect(instance.maxAge).toBe(undefined);
|
||||
expect(instance.maxUses).toBe(undefined);
|
||||
expect(instance.createdDate).toBeNull();
|
||||
expect(instance.createdTimestamp).toBeNull();
|
||||
expect(instance.maxAge).toBeUndefined();
|
||||
expect(instance.maxUses).toBeUndefined();
|
||||
expect(instance.approximateMemberCount).toBe(data.approximate_member_count);
|
||||
expect(instance.approximatePresenceCount).toBe(data.approximate_presence_count);
|
||||
expect(instance.targetType).toBe(data.target_type);
|
||||
expect(instance.temporary).toBe(undefined);
|
||||
expect(instance.uses).toBe(undefined);
|
||||
expect(instance.expiresTimestamp).toBe(null);
|
||||
expect(instance.expiresAt).toBe(null);
|
||||
expect(instance.temporary).toBeUndefined();
|
||||
expect(instance.uses).toBeUndefined();
|
||||
expect(instance.expiresTimestamp).toBeNull();
|
||||
expect(instance.expiresDate).toBeNull();
|
||||
expect(instance.url).toBe('https://discord.gg/123');
|
||||
expect(instance.toJSON()).toEqual(data);
|
||||
expect(`${instance}`).toBe('https://discord.gg/123');
|
||||
@@ -53,9 +53,10 @@ describe('Invite', () => {
|
||||
|
||||
test('extended Invite has all properties', () => {
|
||||
const instance = new Invite(dataExtended);
|
||||
|
||||
expect(instance.type).toBe(data.type);
|
||||
expect(instance.code).toBe(dataExtended.code);
|
||||
expect(dateToDiscordISOTimestamp(instance.createdAt!)).toBe(dataExtended.created_at);
|
||||
expect(dateToDiscordISOTimestamp(instance.createdDate!)).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);
|
||||
@@ -64,10 +65,14 @@ describe('Invite', () => {
|
||||
expect(instance.targetType).toBe(dataExtended.target_type);
|
||||
expect(instance.temporary).toBe(dataExtended.temporary);
|
||||
expect(instance.uses).toBe(dataExtended.uses);
|
||||
expect(instance.createdTimestamp).toBe(Date.parse(dataExtended.created_at));
|
||||
expect(dateToDiscordISOTimestamp(instance.createdDate!)).toEqual(dataExtended.created_at);
|
||||
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.expiresDate).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.209000+00:00' });
|
||||
expect(instance.toString()).toEqual('https://discord.gg/123');
|
||||
expect(instance.valueOf()).toEqual('123');
|
||||
});
|
||||
|
||||
test('Invite with omitted properties', () => {
|
||||
|
||||
@@ -107,10 +107,10 @@ describe('message with embeds and attachments', () => {
|
||||
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(dateToDiscordISOTimestamp(instance.createdDate!)).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(dateToDiscordISOTimestamp(instance.editedDate!)).toBe(data.edited_timestamp);
|
||||
expect(instance.nonce).toBe(data.nonce);
|
||||
expect(instance.pinned).toBe(data.pinned);
|
||||
expect(instance.tts).toBe(data.tts);
|
||||
@@ -449,10 +449,10 @@ describe('message with components', () => {
|
||||
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(dateToDiscordISOTimestamp(instance.createdDate!)).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(dateToDiscordISOTimestamp(instance.editedDate!)).toBe(data.edited_timestamp);
|
||||
expect(instance.nonce).toBe(data.nonce);
|
||||
expect(instance.pinned).toBe(data.pinned);
|
||||
expect(instance.tts).toBe(data.tts);
|
||||
|
||||
@@ -0,0 +1,438 @@
|
||||
import {
|
||||
type APIUser,
|
||||
type GatewayActivity,
|
||||
type GatewayActivityTimestamps,
|
||||
type GatewayActivityAssets,
|
||||
type GatewayActivityButton,
|
||||
type GatewayActivityParty,
|
||||
type GatewayActivitySecrets,
|
||||
type GatewayActivityEmoji,
|
||||
type GatewayGuildMembersChunkPresence,
|
||||
type GatewayPresenceClientStatus as GatewayPresenceClientStatusTypedef,
|
||||
ActivityType,
|
||||
StatusDisplayType,
|
||||
ActivityFlags,
|
||||
PresenceUpdateStatus,
|
||||
ImageFormat,
|
||||
RouteBases,
|
||||
CDNRoutes,
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, test, expect, beforeEach } from 'vitest';
|
||||
import {
|
||||
Activity,
|
||||
ActivityAssets,
|
||||
ActivityButton,
|
||||
ActivityParty,
|
||||
ActivitySecrets,
|
||||
ActivityTimestamps,
|
||||
ClientStatus,
|
||||
Presence,
|
||||
} from '../src/index.js';
|
||||
import { kPatch } from '../src/utils/symbols.js';
|
||||
|
||||
const user: APIUser = {
|
||||
username: 'username',
|
||||
avatar: '54a38112404a550eab14e01fb7f77c9c',
|
||||
global_name: 'User',
|
||||
discriminator: '0000',
|
||||
id: '1091287989151342622',
|
||||
};
|
||||
|
||||
const gatewayPresenceActivityTimestampsData: GatewayActivityTimestamps = {
|
||||
start: 1_771_670_132,
|
||||
};
|
||||
|
||||
const gatewayPresenceActivitySecretsData: GatewayActivitySecrets = {
|
||||
join: '025ed05c71f639de8bfaa0d679d7c94b2fdce12f',
|
||||
match: '4b2fdce12f639de8bfa7e3591b71a0d679d7c93f',
|
||||
};
|
||||
|
||||
const gatewayPresenceActivityPartyData: GatewayActivityParty = {
|
||||
id: '676544578976545764',
|
||||
size: [11, 40],
|
||||
};
|
||||
|
||||
const gatewayPresenceActivityEmojiData: GatewayActivityEmoji = {
|
||||
name: 'emoji_name',
|
||||
animated: false,
|
||||
};
|
||||
|
||||
const gatewayPresenceActivityButtonData: GatewayActivityButton = {
|
||||
label: 'label',
|
||||
url: 'https://github.com//discordjs/discord.js',
|
||||
};
|
||||
|
||||
const gatewayPresenceActivityAssetsData: GatewayActivityAssets = {
|
||||
large_image: '123456789012345678',
|
||||
large_text: 'large-text',
|
||||
large_url: 'https://discord.js.org',
|
||||
small_image: '123456789012345678',
|
||||
small_text: 'activity-asset/smallText',
|
||||
invite_cover_image: '123456789012345670',
|
||||
};
|
||||
|
||||
const gatewayPresenceActivityData: GatewayActivity = {
|
||||
id: '1006883369382064179',
|
||||
name: 'activity-name',
|
||||
type: ActivityType.Playing,
|
||||
url: 'https://github.com/discordjs/discord.js',
|
||||
created_at: 1_540_381_143_572,
|
||||
timestamps: gatewayPresenceActivityTimestampsData,
|
||||
application_id: '222078108977594368',
|
||||
status_display_type: StatusDisplayType.Details,
|
||||
details: 'activity-details',
|
||||
details_url: 'https://github.com/discordjs/discord.js',
|
||||
state: 'activity-state',
|
||||
state_url: 'https://github.com/discordjs/discord.js',
|
||||
emoji: gatewayPresenceActivityEmojiData,
|
||||
party: gatewayPresenceActivityPartyData,
|
||||
assets: gatewayPresenceActivityAssetsData,
|
||||
secrets: gatewayPresenceActivitySecretsData,
|
||||
instance: true,
|
||||
flags: ActivityFlags.Instance,
|
||||
buttons: [gatewayPresenceActivityButtonData],
|
||||
};
|
||||
|
||||
const gatewayPresenceUpdateData: GatewayGuildMembersChunkPresence = {
|
||||
user,
|
||||
activities: [gatewayPresenceActivityData],
|
||||
status: PresenceUpdateStatus.DoNotDisturb,
|
||||
};
|
||||
|
||||
const gatewayPresenceClientStatusData: GatewayPresenceClientStatusTypedef = {
|
||||
desktop: PresenceUpdateStatus.DoNotDisturb,
|
||||
mobile: PresenceUpdateStatus.Online,
|
||||
};
|
||||
|
||||
describe('Presences structures', () => {
|
||||
describe('PresenceClientStatus sub-structure', () => {
|
||||
const data = gatewayPresenceClientStatusData;
|
||||
let instance: ClientStatus;
|
||||
|
||||
beforeEach(() => {
|
||||
instance = new ClientStatus(data);
|
||||
});
|
||||
|
||||
test('correct value for all getters', () => {
|
||||
expect(instance.desktop).toBe(data.desktop);
|
||||
expect(instance.mobile).toBe(data.mobile);
|
||||
|
||||
expect(instance.web).toBeUndefined();
|
||||
});
|
||||
|
||||
test('toJSON() returns expected values', () => {
|
||||
expect(instance.toJSON()).toStrictEqual(data);
|
||||
});
|
||||
|
||||
test('patching the structure works in-place', () => {
|
||||
const web = PresenceUpdateStatus.DoNotDisturb;
|
||||
const mobile = PresenceUpdateStatus.Idle;
|
||||
|
||||
const patched = instance[kPatch]({
|
||||
web,
|
||||
mobile,
|
||||
});
|
||||
|
||||
expect(instance.web).toEqual(web);
|
||||
expect(instance.mobile).toEqual(mobile);
|
||||
|
||||
expect(patched.toJSON()).toEqual({
|
||||
...data,
|
||||
web,
|
||||
mobile,
|
||||
});
|
||||
|
||||
expect(patched).toBe(instance);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PresenceUpdate sub-structure', () => {
|
||||
const data = gatewayPresenceUpdateData;
|
||||
let instance: Presence;
|
||||
|
||||
beforeEach(() => {
|
||||
instance = new Presence(data);
|
||||
});
|
||||
|
||||
test('correct value for all getters', () => {
|
||||
expect(instance.status).toBe(data.status);
|
||||
});
|
||||
|
||||
test('toJSON() returns expected values', () => {
|
||||
expect(instance.toJSON()).toStrictEqual(data);
|
||||
});
|
||||
|
||||
test('patching the structure works in-place', () => {
|
||||
const status = PresenceUpdateStatus.Online;
|
||||
|
||||
const patched = instance[kPatch]({
|
||||
status,
|
||||
});
|
||||
|
||||
expect(patched.status).toEqual(PresenceUpdateStatus.Online);
|
||||
|
||||
expect(patched.toJSON()).toEqual({
|
||||
...data,
|
||||
status,
|
||||
});
|
||||
|
||||
expect(patched).toBe(instance);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Presences sub-structures', () => {
|
||||
describe('PresenceActivity sub-structure', () => {
|
||||
const data = gatewayPresenceActivityData;
|
||||
let instance: Activity;
|
||||
|
||||
beforeEach(() => {
|
||||
instance = new Activity(data);
|
||||
});
|
||||
|
||||
test('correct value for all getters and helper method [createdDate]', () => {
|
||||
expect(instance.name).toBe(data.name);
|
||||
expect(instance.type).toBe(data.type);
|
||||
expect(instance.url).toBe(data.url);
|
||||
expect(instance.applicationId).toBe(data.application_id);
|
||||
expect(instance.statusDisplayType).toBe(data.status_display_type);
|
||||
expect(instance.details).toBe(data.details);
|
||||
expect(instance.detailsURL).toBe(data.details_url);
|
||||
expect(instance.state).toBe(data.state);
|
||||
expect(instance.stateURL).toBe(data.state_url);
|
||||
expect(instance.instance).toBe(data.instance);
|
||||
expect(instance.flags!.bitField).toBe(BigInt(data.flags!));
|
||||
expect(instance.createdTimestamp).toBe(data.created_at);
|
||||
expect(instance.createdDate!.valueOf()).toEqual(data.created_at);
|
||||
});
|
||||
|
||||
test('toJSON() returns expected values', () => {
|
||||
expect(instance.toJSON()).toStrictEqual(data);
|
||||
});
|
||||
|
||||
test('patching the structure works in-place', () => {
|
||||
const status_display_type = StatusDisplayType.Name;
|
||||
const state = '[PATCHED]-activity-state';
|
||||
const state_url = null;
|
||||
const type = ActivityType.Custom;
|
||||
|
||||
const patched = instance[kPatch]({
|
||||
status_display_type,
|
||||
state,
|
||||
state_url,
|
||||
type,
|
||||
});
|
||||
|
||||
expect(patched.toJSON()).toEqual({
|
||||
...data,
|
||||
status_display_type,
|
||||
state,
|
||||
state_url,
|
||||
type,
|
||||
});
|
||||
|
||||
expect(patched).toBe(instance);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PresenceActivityAssets sub-structure', () => {
|
||||
const data = gatewayPresenceActivityAssetsData;
|
||||
const applicationId = '23498573429574598';
|
||||
let instance: ActivityAssets;
|
||||
|
||||
beforeEach(() => {
|
||||
instance = new ActivityAssets(data);
|
||||
});
|
||||
|
||||
test('correct value for all getters and helper methods [largeImageURL, smallImageURL]', () => {
|
||||
expect(instance.largeImage).toBe(data.large_image);
|
||||
expect(instance.largeText).toBe(data.large_text);
|
||||
expect(instance.largeURL).toBe(data.large_url);
|
||||
expect(instance.smallImage).toBe(data.small_image);
|
||||
expect(instance.smallText).toBe(data.small_text);
|
||||
expect(instance.inviteCoverImage).toBe(data.invite_cover_image);
|
||||
|
||||
expect(instance.largeImageURL(applicationId, ImageFormat.JPEG)).toEqual(
|
||||
`${RouteBases.cdn}${CDNRoutes.applicationAsset(applicationId, data.large_image!, ImageFormat.JPEG)}`,
|
||||
);
|
||||
expect(instance.smallImageURL(applicationId, ImageFormat.JPEG)).toEqual(
|
||||
`${RouteBases.cdn}${CDNRoutes.applicationAsset(applicationId, data.small_image!, ImageFormat.JPEG)}`,
|
||||
);
|
||||
|
||||
expect(instance.smallURL).toBeUndefined();
|
||||
});
|
||||
|
||||
test('toJSON() returns expected values', () => {
|
||||
expect(instance.toJSON()).toStrictEqual(data);
|
||||
});
|
||||
|
||||
test('patching the structure works in-place', () => {
|
||||
const large_text = 'large_text_patched_str';
|
||||
const small_url = 'https://discord.js.org/docs/packages/structures/main';
|
||||
|
||||
const patched = instance[kPatch]({
|
||||
large_text,
|
||||
small_url,
|
||||
});
|
||||
|
||||
expect(patched.largeText).toEqual(large_text);
|
||||
expect(patched.smallURL).toEqual(small_url);
|
||||
|
||||
expect(patched).toBe(instance);
|
||||
expect(patched.toJSON()).toEqual({
|
||||
...data,
|
||||
large_text,
|
||||
small_url,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PresenceActivityButton sub-structure', () => {
|
||||
const data = gatewayPresenceActivityButtonData;
|
||||
let instance: ActivityButton;
|
||||
|
||||
beforeEach(() => {
|
||||
instance = new ActivityButton(data);
|
||||
});
|
||||
|
||||
test('correct value for all getters', () => {
|
||||
expect(instance.label).toBe(data.label);
|
||||
expect(instance.url).toBe(data.url);
|
||||
});
|
||||
|
||||
test('toJSON() returns expected values', () => {
|
||||
expect(instance.toJSON()).toStrictEqual(data);
|
||||
});
|
||||
|
||||
test('patching the structure works in-place', () => {
|
||||
const label = '[PATCHED]-button-label';
|
||||
|
||||
const patched = instance[kPatch]({
|
||||
label,
|
||||
});
|
||||
|
||||
expect(patched.label).toEqual(label);
|
||||
|
||||
expect(patched).toEqual(instance);
|
||||
|
||||
expect(patched.toJSON()).toEqual({
|
||||
...data,
|
||||
label,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PresenceActivityParty sub-structure', () => {
|
||||
const data = gatewayPresenceActivityPartyData;
|
||||
let instance: ActivityParty;
|
||||
|
||||
beforeEach(() => {
|
||||
instance = new ActivityParty(data);
|
||||
});
|
||||
|
||||
test('correct value for all getters and helper methods [createdTimestamp, createdDate]', () => {
|
||||
expect(instance.id).toBe(data.id);
|
||||
expect(instance.currentSize).toBe(data.size![0]);
|
||||
expect(instance.maximumSize).toEqual(data.size![1]);
|
||||
});
|
||||
|
||||
test('toJSON() returns expected values', () => {
|
||||
expect(instance.toJSON()).toStrictEqual(data);
|
||||
});
|
||||
|
||||
test('patching the structure works in-place', () => {
|
||||
const size: [current_size: number, max_size: number] = [1, 999];
|
||||
|
||||
const patched = instance[kPatch]({
|
||||
size,
|
||||
});
|
||||
|
||||
expect(instance.currentSize).toBe(size[0]);
|
||||
expect(instance.maximumSize).toBe(size[1]);
|
||||
|
||||
expect(patched).toBe(instance);
|
||||
|
||||
expect(patched.toJSON()).toEqual({
|
||||
...data,
|
||||
size,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PresenceActivitySecrets sub-structure', () => {
|
||||
const data = gatewayPresenceActivitySecretsData;
|
||||
let instance: ActivitySecrets;
|
||||
|
||||
beforeEach(() => {
|
||||
instance = new ActivitySecrets(data);
|
||||
});
|
||||
|
||||
test('correct value for all getters', () => {
|
||||
expect(instance.join).toBe(data.join);
|
||||
expect(instance.match).toBe(data.match);
|
||||
|
||||
expect(instance.spectate).toBeUndefined();
|
||||
});
|
||||
|
||||
test('toJSON() returns expected values', () => {
|
||||
expect(instance.toJSON()).toStrictEqual(data);
|
||||
});
|
||||
|
||||
test('patching the structure works in-place', () => {
|
||||
const spectate = 'e7eb30d2ee025ed05c71ea495f770b76454ee4e0';
|
||||
const match = '025ed05c71f639de8bfaa0d679d7c94b2fdce12f-new-match';
|
||||
|
||||
const patched = instance[kPatch]({
|
||||
match,
|
||||
spectate,
|
||||
});
|
||||
|
||||
expect(patched.match).toEqual(match);
|
||||
expect(patched.spectate).toEqual(spectate);
|
||||
|
||||
expect(patched).toBe(instance);
|
||||
expect(patched.toJSON()).toEqual({
|
||||
...data,
|
||||
match,
|
||||
spectate,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PresenceActivityTimestamps sub-structure', () => {
|
||||
const data = gatewayPresenceActivityTimestampsData;
|
||||
let instance: ActivityTimestamps;
|
||||
|
||||
beforeEach(() => {
|
||||
instance = new ActivityTimestamps(data);
|
||||
});
|
||||
|
||||
test('correct value for all getters', () => {
|
||||
expect(instance.startTimestamp).toBe(data.start);
|
||||
expect(instance.startDate?.valueOf()).toStrictEqual(data.start);
|
||||
|
||||
expect(instance.endTimestamp).toBeUndefined();
|
||||
expect(instance.endDate).toBeNull();
|
||||
});
|
||||
|
||||
test('toJSON() returns expected values', () => {
|
||||
expect(instance.toJSON()).toStrictEqual(data);
|
||||
});
|
||||
|
||||
test('patching the structure works in-place', () => {
|
||||
const end = 1_771_670_132;
|
||||
const patched = instance[kPatch]({
|
||||
end,
|
||||
});
|
||||
|
||||
expect(patched.endDate?.valueOf()).toStrictEqual(end);
|
||||
|
||||
expect(patched).toEqual(instance);
|
||||
|
||||
expect(patched.toJSON()).toEqual({
|
||||
...data,
|
||||
end,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ActivityFlags } from 'discord-api-types/v10';
|
||||
import { BitField } from './BitField.js';
|
||||
|
||||
/**
|
||||
* Data structure that makes it easy to interact with an {@link ActivityFlags} bitfield.
|
||||
*/
|
||||
export class ActivityFlagsBitField extends BitField<keyof typeof ActivityFlags> {
|
||||
/**
|
||||
* Numeric gateway presence activity flags.
|
||||
*/
|
||||
public static override readonly Flags = ActivityFlags;
|
||||
|
||||
public override toJSON() {
|
||||
return super.toJSON(true);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './BitField.js';
|
||||
|
||||
export * from './ActivityFlagsBitField.js';
|
||||
export * from './AttachmentFlagsBitField.js';
|
||||
export * from './ChannelFlagsBitField.js';
|
||||
export * from './MessageFlagsBitField.js';
|
||||
|
||||
@@ -97,7 +97,7 @@ export class Channel<
|
||||
/**
|
||||
* The time the channel was created at
|
||||
*/
|
||||
public get createdAt() {
|
||||
public get createdDate() {
|
||||
const createdTimestamp = this.createdTimestamp;
|
||||
return createdTimestamp ? new Date(createdTimestamp) : null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { APIThreadMetadata } from 'discord-api-types/v10';
|
||||
import { Structure } from '../Structure.js';
|
||||
import { dateToDiscordISOTimestamp } from '../utils/optimization.js';
|
||||
import { kArchiveTimestamp, kCreatedTimestamp, kData } from '../utils/symbols.js';
|
||||
import type { Partialize } from '../utils/types.js';
|
||||
|
||||
@@ -89,7 +90,7 @@ export class ThreadMetadata<
|
||||
/**
|
||||
* The time the thread was archived at
|
||||
*/
|
||||
public get archivedAt() {
|
||||
public get archivedDate() {
|
||||
const archivedTimestamp = this.archivedTimestamp;
|
||||
return archivedTimestamp ? new Date(archivedTimestamp) : null;
|
||||
}
|
||||
@@ -97,7 +98,7 @@ export class ThreadMetadata<
|
||||
/**
|
||||
* The time the thread was created at
|
||||
*/
|
||||
public get createdAt() {
|
||||
public get createdDate() {
|
||||
const createdTimestamp = this.createdTimestamp;
|
||||
return createdTimestamp ? new Date(createdTimestamp) : null;
|
||||
}
|
||||
@@ -108,11 +109,11 @@ export class ThreadMetadata<
|
||||
public override toJSON() {
|
||||
const data = super.toJSON();
|
||||
if (this[kArchiveTimestamp]) {
|
||||
data.archive_timestamp = new Date(this[kArchiveTimestamp]).toISOString();
|
||||
data.archive_timestamp = dateToDiscordISOTimestamp(new Date(this[kArchiveTimestamp]));
|
||||
}
|
||||
|
||||
if (this[kCreatedTimestamp]) {
|
||||
data.create_timestamp = new Date(this[kCreatedTimestamp]).toISOString();
|
||||
data.create_timestamp = dateToDiscordISOTimestamp(new Date(this[kCreatedTimestamp]));
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ChannelType, ThreadChannelType } from 'discord-api-types/v10';
|
||||
import { dateToDiscordISOTimestamp } from '../../utils/optimization.js';
|
||||
import { kLastPinTimestamp, kMixinConstruct, kMixinToJSON } from '../../utils/symbols.js';
|
||||
import type { Channel, ChannelDataType } from '../Channel.js';
|
||||
|
||||
@@ -46,7 +47,7 @@ export class ChannelPinMixin<
|
||||
/**
|
||||
* The Date of when the last pin in the channel happened
|
||||
*/
|
||||
public get lastPinAt() {
|
||||
public get lastPinDate() {
|
||||
const lastPinTimestamp = this.lastPinTimestamp;
|
||||
return lastPinTimestamp ? new Date(lastPinTimestamp) : null;
|
||||
}
|
||||
@@ -57,6 +58,8 @@ export class ChannelPinMixin<
|
||||
* @param data - the result of {@link (Structure:class).toJSON}
|
||||
*/
|
||||
protected [kMixinToJSON](data: Partial<ChannelDataType<Type>>) {
|
||||
data.last_pin_timestamp = this[kLastPinTimestamp] ? new Date(this[kLastPinTimestamp]).toISOString() : null;
|
||||
data.last_pin_timestamp = this[kLastPinTimestamp]
|
||||
? dateToDiscordISOTimestamp(new Date(this[kLastPinTimestamp]))
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export class Emoji<Omitted extends keyof APIEmoji | '' = ''> extends Structure<A
|
||||
/**
|
||||
* The time the emoji was created at
|
||||
*/
|
||||
public get createdAt() {
|
||||
public get createdDate() {
|
||||
const createdTimestamp = this.createdTimestamp;
|
||||
return createdTimestamp ? new Date(createdTimestamp) : null;
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ export class Entitlement<Omitted extends keyof APIEntitlement | '' = 'ends_at' |
|
||||
/**
|
||||
* Start date at which the entitlement is valid
|
||||
*/
|
||||
public get startsAt() {
|
||||
public get startsDate() {
|
||||
const timestamp = this.startsTimestamp;
|
||||
return timestamp ? new Date(timestamp) : null;
|
||||
}
|
||||
@@ -113,7 +113,7 @@ export class Entitlement<Omitted extends keyof APIEntitlement | '' = 'ends_at' |
|
||||
/**
|
||||
* End date at which the entitlement is valid
|
||||
*/
|
||||
public get endsAt() {
|
||||
public get endsDate() {
|
||||
const timestamp = this.endsTimestamp;
|
||||
return timestamp ? new Date(timestamp) : null;
|
||||
}
|
||||
@@ -149,7 +149,7 @@ export class Entitlement<Omitted extends keyof APIEntitlement | '' = 'ends_at' |
|
||||
/**
|
||||
* The time the entitlement was created at
|
||||
*/
|
||||
public get createdAt() {
|
||||
public get createdDate() {
|
||||
const createdTimestamp = this.createdTimestamp;
|
||||
return createdTimestamp ? new Date(createdTimestamp) : null;
|
||||
}
|
||||
@@ -160,15 +160,15 @@ export class Entitlement<Omitted extends keyof APIEntitlement | '' = 'ends_at' |
|
||||
public override toJSON() {
|
||||
const clone = super.toJSON();
|
||||
|
||||
const startsAtTimestamp = this[kStartsTimestamp];
|
||||
const endsAtTimestamp = this[kEndsTimestamp];
|
||||
const startsDateTimestamp = this[kStartsTimestamp];
|
||||
const endsDateTimestamp = this[kEndsTimestamp];
|
||||
|
||||
if (startsAtTimestamp) {
|
||||
clone.starts_at = dateToDiscordISOTimestamp(new Date(startsAtTimestamp));
|
||||
if (startsDateTimestamp) {
|
||||
clone.starts_at = dateToDiscordISOTimestamp(new Date(startsDateTimestamp));
|
||||
}
|
||||
|
||||
if (endsAtTimestamp) {
|
||||
clone.ends_at = dateToDiscordISOTimestamp(new Date(endsAtTimestamp));
|
||||
if (endsDateTimestamp) {
|
||||
clone.ends_at = dateToDiscordISOTimestamp(new Date(endsDateTimestamp));
|
||||
}
|
||||
|
||||
return clone;
|
||||
|
||||
@@ -7,6 +7,7 @@ export * from './interactions/index.js';
|
||||
export * from './invites/index.js';
|
||||
export * from './messages/index.js';
|
||||
export * from './polls/index.js';
|
||||
export * from './presences/index.js';
|
||||
export * from './skus/index.js';
|
||||
export * from './soundboards/index.js';
|
||||
export * from './stageInstances/index.js';
|
||||
|
||||
@@ -122,7 +122,7 @@ export class Invite<Omitted extends keyof APIActualInvite | '' = 'created_at' |
|
||||
/**
|
||||
* The time the invite will expire at
|
||||
*/
|
||||
public get expiresAt() {
|
||||
public get expiresDate() {
|
||||
const expiresTimestamp = this.expiresTimestamp;
|
||||
return expiresTimestamp ? new Date(expiresTimestamp) : null;
|
||||
}
|
||||
@@ -165,7 +165,7 @@ export class Invite<Omitted extends keyof APIActualInvite | '' = 'created_at' |
|
||||
/**
|
||||
* The time the invite was created at
|
||||
*/
|
||||
public get createdAt() {
|
||||
public get createdDate() {
|
||||
const createdTimestamp = this.createdTimestamp;
|
||||
return createdTimestamp ? new Date(createdTimestamp) : null;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export class Message<Omitted extends keyof APIMessage | '' = 'edited_timestamp'
|
||||
/**
|
||||
* The time the message was created at
|
||||
*/
|
||||
public get createdAt() {
|
||||
public get createdDate() {
|
||||
const createdTimestamp = this.createdTimestamp;
|
||||
return createdTimestamp ? new Date(createdTimestamp) : null;
|
||||
}
|
||||
@@ -101,7 +101,7 @@ export class Message<Omitted extends keyof APIMessage | '' = 'edited_timestamp'
|
||||
/**
|
||||
* The time the message was last edited at, or `null` if it never was edited
|
||||
*/
|
||||
public get editedAt() {
|
||||
public get editedDate() {
|
||||
const editedTimestamp = this.editedTimestamp;
|
||||
return editedTimestamp ? new Date(editedTimestamp) : null;
|
||||
}
|
||||
@@ -165,13 +165,16 @@ export class Message<Omitted extends keyof APIMessage | '' = 'edited_timestamp'
|
||||
*/
|
||||
public override toJSON() {
|
||||
const clone = super.toJSON();
|
||||
if (this[kEditedTimestamp]) {
|
||||
clone.edited_timestamp = dateToDiscordISOTimestamp(new Date(this[kEditedTimestamp]));
|
||||
|
||||
const editedTimestamp = this[kEditedTimestamp];
|
||||
const createdDate = this.createdDate;
|
||||
|
||||
if (editedTimestamp) {
|
||||
clone.edited_timestamp = dateToDiscordISOTimestamp(new Date(editedTimestamp));
|
||||
}
|
||||
|
||||
const createdAt = this.createdAt;
|
||||
if (createdAt) {
|
||||
clone.timestamp = dateToDiscordISOTimestamp(createdAt);
|
||||
if (createdDate) {
|
||||
clone.timestamp = dateToDiscordISOTimestamp(createdDate);
|
||||
}
|
||||
|
||||
return clone;
|
||||
|
||||
@@ -46,7 +46,7 @@ export class MessageCall<Omitted extends keyof APIMessageCall | '' = 'ended_time
|
||||
/**
|
||||
* The time the call ended at, or `null` if it didn't end yet
|
||||
*/
|
||||
public get endedAt() {
|
||||
public get endedDate() {
|
||||
const endedTimestamp = this.endedTimestamp;
|
||||
return endedTimestamp ? new Date(endedTimestamp) : null;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ export class Poll<Omitted extends keyof APIPoll | '' = ''> extends Structure<API
|
||||
/**
|
||||
* The time the poll will expire at
|
||||
*/
|
||||
public get expiresAt() {
|
||||
public get expiresDate() {
|
||||
const expiresTimestamp = this.expiresTimestamp;
|
||||
return expiresTimestamp ? new Date(expiresTimestamp) : null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { GatewayPresenceClientStatus } from 'discord-api-types/v10';
|
||||
import { Structure } from '../Structure.js';
|
||||
import { kData } from '../utils/symbols.js';
|
||||
import type { Partialize } from '../utils/types.js';
|
||||
|
||||
/**
|
||||
* Represents any client status 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 ClientStatus<Omitted extends keyof GatewayPresenceClientStatus | '' = ''> extends Structure<
|
||||
GatewayPresenceClientStatus,
|
||||
Omitted
|
||||
> {
|
||||
/**
|
||||
* The template used for removing data from the raw data stored for each client status
|
||||
*/
|
||||
public static override readonly DataTemplate: Partial<GatewayPresenceClientStatus> = {};
|
||||
|
||||
/**
|
||||
* @param data - The raw data received from the API for the client status
|
||||
*/
|
||||
public constructor(data: Partialize<GatewayPresenceClientStatus, Omitted>) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* The user's status set for an active desktop (Windows, Linux, Mac) application session
|
||||
*/
|
||||
public get desktop() {
|
||||
return this[kData].desktop;
|
||||
}
|
||||
|
||||
/**
|
||||
* The user's status set for an active mobile (iOS, Android) application session
|
||||
*/
|
||||
public get mobile() {
|
||||
return this[kData].mobile;
|
||||
}
|
||||
|
||||
/**
|
||||
* The user's status set for an active web (browser, bot user) application session
|
||||
*/
|
||||
public get web() {
|
||||
return this[kData].web;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { GatewayGuildMembersChunkPresence } from 'discord-api-types/v10';
|
||||
import { Structure } from '../Structure.js';
|
||||
import { kData } from '../utils/symbols.js';
|
||||
import type { Partialize } from '../utils/types.js';
|
||||
|
||||
/**
|
||||
* Represents any presence update 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 `Activity`, `ClientStatus` which need to be instantiated and stored by an extending class using it
|
||||
*/
|
||||
export class Presence<Omitted extends keyof GatewayGuildMembersChunkPresence | '' = ''> extends Structure<
|
||||
GatewayGuildMembersChunkPresence,
|
||||
Omitted
|
||||
> {
|
||||
/**
|
||||
* The template used for removing data from the raw data stored for each presence update
|
||||
*/
|
||||
public static override readonly DataTemplate: Partial<GatewayGuildMembersChunkPresence> = {};
|
||||
|
||||
/**
|
||||
* @param data - The raw data received from the API for the presence update
|
||||
*/
|
||||
public constructor(data: Partialize<GatewayGuildMembersChunkPresence, Omitted>) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* The user's new status
|
||||
*/
|
||||
public get status() {
|
||||
return this[kData].status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import type { ActivityFlags, GatewayActivity, ActivityType } from 'discord-api-types/v10';
|
||||
import { Structure } from '../../Structure.js';
|
||||
import { ActivityFlagsBitField } from '../../bitfields/ActivityFlagsBitField.js';
|
||||
import { kData } from '../../utils/symbols.js';
|
||||
import { isFieldSet } from '../../utils/type-guards.js';
|
||||
import type { Partialize } from '../../utils/types.js';
|
||||
|
||||
/**
|
||||
* Represents any activity 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 `ActivityTimestamps`, `ActivityParty`,`ActivityAssets`, `ActivitySecrets` which need to be instantiated
|
||||
* and stored by an extending class using it.
|
||||
* @remarks intentionally does not export `buttons` so that extending classes can resolve `string[]` to `ActivityButton[]`
|
||||
*/
|
||||
export class Activity<Omitted extends keyof GatewayActivity | '' = ''> extends Structure<GatewayActivity, Omitted> {
|
||||
/**
|
||||
* The template used for removing data from the raw data stored for each activity
|
||||
*/
|
||||
public static override readonly DataTemplate: Partial<GatewayActivity> = {};
|
||||
|
||||
/**
|
||||
* @param data - The raw data received from the API for the activity
|
||||
*/
|
||||
public constructor(data: Partialize<GatewayActivity, Omitted>) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the activity
|
||||
*/
|
||||
public get name() {
|
||||
return this[kData].name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of this activity
|
||||
*
|
||||
* @see {@link https://docs.discord.com/developers/events/gateway-events#activity-object-activity-types}
|
||||
*/
|
||||
public get type() {
|
||||
return this[kData].type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream URL, is validated when type is {@link ActivityType.Streaming}
|
||||
*/
|
||||
public get url() {
|
||||
return this[kData].url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time of when the activity was added to the user's session
|
||||
*/
|
||||
public get createdDate() {
|
||||
return isFieldSet(this[kData], 'created_at', 'number') ? new Date(this[kData].created_at) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unix timestamp (in milliseconds) of when the activity was added to the user's session
|
||||
*/
|
||||
public get createdTimestamp() {
|
||||
return this[kData].created_at;
|
||||
}
|
||||
|
||||
/**
|
||||
* Application id for the game
|
||||
*/
|
||||
public get applicationId() {
|
||||
return this[kData].application_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls which field is displayed in the user's status text in the member list
|
||||
*
|
||||
* @see {@link https://docs.discord.com/developers/events/gateway-events#activity-object-status-display-types}
|
||||
*/
|
||||
public get statusDisplayType() {
|
||||
return this[kData].status_display_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* What the player is currently doing
|
||||
*/
|
||||
public get details() {
|
||||
return this[kData].details;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL that is linked when clicking on the details text
|
||||
*/
|
||||
public get detailsURL() {
|
||||
return this[kData].details_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* User's current party status, or text used for a custom status
|
||||
*/
|
||||
public get state() {
|
||||
return this[kData].state;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL that is linked when clicking on the state text
|
||||
*/
|
||||
public get stateURL() {
|
||||
return this[kData].state_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the activity is an instanced game session
|
||||
*/
|
||||
public get instance() {
|
||||
return this[kData].instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity flags `OR`d together, describes what the payload includes
|
||||
*
|
||||
* @see {@link https://docs.discord.com/developers/events/gateway-events#activity-object-activity-flags}
|
||||
*/
|
||||
public get flags() {
|
||||
return isFieldSet(this[kData], 'flags', 'number')
|
||||
? new ActivityFlagsBitField(this[kData].flags as ActivityFlags)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import {
|
||||
CDNRoutes,
|
||||
ImageFormat,
|
||||
RouteBases,
|
||||
type GatewayActivityAssets,
|
||||
type ApplicationAssetFormat,
|
||||
} from 'discord-api-types/v10';
|
||||
import { Structure } from '../../Structure.js';
|
||||
import { kData } from '../../utils/symbols.js';
|
||||
import { isFieldSet } from '../../utils/type-guards.js';
|
||||
import type { Partialize } from '../../utils/types.js';
|
||||
|
||||
/**
|
||||
* Represents any activity assets 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 ActivityAssets<Omitted extends keyof GatewayActivityAssets | '' = ''> extends Structure<
|
||||
GatewayActivityAssets,
|
||||
Omitted
|
||||
> {
|
||||
/**
|
||||
* The template used for removing data from the raw data stored for each activity assets
|
||||
*/
|
||||
public static override readonly DataTemplate: Partial<GatewayActivityAssets> = {};
|
||||
|
||||
/**
|
||||
* @param data - The raw data received from the API for the activity assets
|
||||
*/
|
||||
public constructor(data: Partialize<GatewayActivityAssets, Omitted>) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link https://docs.discord.com/developers/events/gateway-events#activity-object-activity-asset-image}
|
||||
*/
|
||||
public get largeImage() {
|
||||
return this[kData].large_image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the large image URL of this `largeImage`
|
||||
*
|
||||
* @param applicationId - The id of the application to whom this asset belongs to
|
||||
* @param imageFormat - The desired format of the returned image URL
|
||||
*/
|
||||
public largeImageURL(applicationId: string, imageFormat: ApplicationAssetFormat = ImageFormat.PNG) {
|
||||
return isFieldSet(this[kData], 'large_image', 'string')
|
||||
? `${RouteBases.cdn}${CDNRoutes.applicationAsset(applicationId, this[kData].large_image, imageFormat)}`
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Text displayed when hovering over the large image of the activity
|
||||
*/
|
||||
public get largeText() {
|
||||
return this[kData].large_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL that is opened when clicking on the large image
|
||||
*/
|
||||
public get largeURL() {
|
||||
return this[kData].large_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link https://docs.discord.com/developers/events/gateway-events#activity-object-activity-asset-image}
|
||||
*/
|
||||
public get smallImage() {
|
||||
return this[kData].small_image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the small image URL of this `smallImage`
|
||||
*
|
||||
* @param applicationId - The id of the application to whom this asset belongs to
|
||||
* @param imageFormat - The desired format of the returned image URL
|
||||
*/
|
||||
public smallImageURL(applicationId: string, imageFormat: ApplicationAssetFormat = ImageFormat.PNG) {
|
||||
return isFieldSet(this[kData], 'small_image', 'string')
|
||||
? `${RouteBases.cdn}${CDNRoutes.applicationAsset(applicationId, this[kData].small_image, imageFormat)}`
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Text displayed when hovering over the small image of the activity
|
||||
*/
|
||||
public get smallText() {
|
||||
return this[kData].small_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL that is opened when clicking on the small image
|
||||
*/
|
||||
public get smallURL() {
|
||||
return this[kData].small_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL of the image which is displayed as a banner on a Game Invite
|
||||
*/
|
||||
public get inviteCoverImage() {
|
||||
return this[kData].invite_cover_image;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { GatewayActivityButton } from 'discord-api-types/v10';
|
||||
import { Structure } from '../../Structure.js';
|
||||
import { kData } from '../../utils/symbols.js';
|
||||
import type { Partialize } from '../../utils/types.js';
|
||||
|
||||
/**
|
||||
* Represents any activity button on Discord.
|
||||
*
|
||||
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`.
|
||||
* @remarks When received over the gateway, the `buttons` field is an array of strings, which are the `button` labels. Bots cannot
|
||||
* access a user's activity button URLs. When sending, the buttons field must be an array of this structure.
|
||||
*/
|
||||
export class ActivityButton<Omitted extends keyof GatewayActivityButton | '' = ''> extends Structure<
|
||||
GatewayActivityButton,
|
||||
Omitted
|
||||
> {
|
||||
/**
|
||||
* The template used for removing data from the raw data stored for each activity button
|
||||
*/
|
||||
public static override readonly DataTemplate: Partial<GatewayActivityButton> = {};
|
||||
|
||||
/**
|
||||
* @param data - The raw data received from the API for the activity button
|
||||
*/
|
||||
public constructor(data: Partialize<GatewayActivityButton, Omitted>) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Text shown on the button (1-32 characters)
|
||||
*/
|
||||
public get label() {
|
||||
return this[kData].label;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL opened when clicking the button (1-512 characters)
|
||||
*/
|
||||
public get url() {
|
||||
return this[kData].url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { GatewayActivityParty } from 'discord-api-types/v10';
|
||||
import { Structure } from '../../Structure.js';
|
||||
import { kData } from '../../utils/symbols.js';
|
||||
import { isArrayFieldSet } from '../../utils/type-guards.js';
|
||||
import type { Partialize } from '../../utils/types.js';
|
||||
|
||||
/**
|
||||
* Represents any activity party 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 ActivityParty<Omitted extends keyof GatewayActivityParty | '' = ''> extends Structure<
|
||||
GatewayActivityParty,
|
||||
Omitted
|
||||
> {
|
||||
/**
|
||||
* The template used for removing data from the raw data stored for each activity party.
|
||||
*/
|
||||
public static override readonly DataTemplate: Partial<GatewayActivityParty> = {};
|
||||
|
||||
/**
|
||||
* @param data - The raw data received from the API for the activity party.
|
||||
*/
|
||||
public constructor(data: Partialize<GatewayActivityParty, Omitted>) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* The id of the party.
|
||||
*
|
||||
* @remarks This is an application-defined id and is not a Discord snowflake.
|
||||
*/
|
||||
public get id() {
|
||||
return this[kData].id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current size of the party
|
||||
*/
|
||||
public get currentSize() {
|
||||
const array = this[kData].size;
|
||||
|
||||
return array ? (isArrayFieldSet(array, 0, 'number') ? array[0] : null) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum size of the party
|
||||
*/
|
||||
public get maximumSize() {
|
||||
const array = this[kData].size;
|
||||
|
||||
return array ? (isArrayFieldSet(array, 1, 'number') ? array[1] : null) : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { GatewayActivitySecrets } from 'discord-api-types/v10';
|
||||
import { Structure } from '../../Structure.js';
|
||||
import { kData } from '../../utils/symbols.js';
|
||||
import type { Partialize } from '../../utils/types.js';
|
||||
|
||||
/**
|
||||
* Represents any activity secrets 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 ActivitySecrets<Omitted extends keyof GatewayActivitySecrets | '' = ''> extends Structure<
|
||||
GatewayActivitySecrets,
|
||||
Omitted
|
||||
> {
|
||||
/**
|
||||
* The template used for removing data from the raw data stored for each activity's secrets.
|
||||
*/
|
||||
public static override readonly DataTemplate: Partial<GatewayActivitySecrets> = {};
|
||||
|
||||
/**
|
||||
* @param data - The raw data received from the API for the activity's secrets.
|
||||
*/
|
||||
public constructor(data: Partialize<GatewayActivitySecrets, Omitted>) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Secret for joining a party.
|
||||
*/
|
||||
public get join() {
|
||||
return this[kData].join;
|
||||
}
|
||||
|
||||
/**
|
||||
* Secret for spectating a game.
|
||||
*/
|
||||
public get spectate() {
|
||||
return this[kData].spectate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Secret for a specific instanced match
|
||||
*/
|
||||
public get match() {
|
||||
return this[kData].match;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { GatewayActivityTimestamps } from 'discord-api-types/v10';
|
||||
import { Structure } from '../../Structure.js';
|
||||
import { kData } from '../../utils/symbols.js';
|
||||
import { isFieldSet } from '../../utils/type-guards.js';
|
||||
import type { Partialize } from '../../utils/types.js';
|
||||
|
||||
/**
|
||||
* Represents any activity timestamp 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 ActivityTimestamps<Omitted extends keyof GatewayActivityTimestamps | '' = ''> extends Structure<
|
||||
GatewayActivityTimestamps,
|
||||
Omitted
|
||||
> {
|
||||
/**
|
||||
* The template used for removing data from the raw data stored for each activity timestamp
|
||||
*/
|
||||
public static override readonly DataTemplate: Partial<GatewayActivityTimestamps> = {};
|
||||
|
||||
/**
|
||||
* @param data - The raw data received from the API for the activity timestamp
|
||||
*/
|
||||
public constructor(data: Partialize<GatewayActivityTimestamps, Omitted>) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unix time (in milliseconds) of when the activity started.
|
||||
*/
|
||||
public get startTimestamp() {
|
||||
return this[kData].start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time of when the activity started.
|
||||
*/
|
||||
public get startDate() {
|
||||
return isFieldSet(this[kData], 'start', 'number') ? new Date(this[kData].start) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unix time (in milliseconds) of when the activity ends.
|
||||
*/
|
||||
public get endTimestamp() {
|
||||
return this[kData].end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time of when the activity ended.
|
||||
*/
|
||||
public get endDate() {
|
||||
return isFieldSet(this[kData], 'end', 'number') ? new Date(this[kData].end) : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export * from './Activity.js';
|
||||
export * from './ActivityAssets.js';
|
||||
export * from './ActivityButton.js';
|
||||
export * from './ActivityParty.js';
|
||||
export * from './ActivitySecrets.js';
|
||||
export * from './ActivityTimestamps.js';
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './activities/index.js';
|
||||
|
||||
export * from './ClientStatus.js';
|
||||
export * from './Presence.js';
|
||||
@@ -92,9 +92,8 @@ export class SoundboardSound<Omitted extends keyof APISoundboardSound | '' = ''>
|
||||
*
|
||||
* @remarks only available for guild soundboard sounds
|
||||
*/
|
||||
public get createdAt() {
|
||||
public get createdDate() {
|
||||
const createdTimestamp = this.createdTimestamp;
|
||||
|
||||
return createdTimestamp ? new Date(createdTimestamp) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export class StageInstance<Omitted extends keyof APIStageInstance | '' = ''> ext
|
||||
/**
|
||||
* The time the stage instance was created at
|
||||
*/
|
||||
public get createdAt() {
|
||||
public get createdDate() {
|
||||
const createdTimestamp = this.createdTimestamp;
|
||||
return createdTimestamp ? new Date(createdTimestamp) : null;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ export class Sticker<Omitted extends keyof APISticker | '' = ''> extends Structu
|
||||
/**
|
||||
* The time the sticker was created at
|
||||
*/
|
||||
public get createdAt() {
|
||||
public get createdDate() {
|
||||
const createdTimestamp = this.createdTimestamp;
|
||||
return createdTimestamp ? new Date(createdTimestamp) : null;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export class StickerPack<Omitted extends keyof APIStickerPack | '' = ''> extends
|
||||
/**
|
||||
* The time the sticker pack was created at
|
||||
*/
|
||||
public get createdAt() {
|
||||
public get createdDate() {
|
||||
const createdTimestamp = this.createdTimestamp;
|
||||
return createdTimestamp ? new Date(createdTimestamp) : null;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ export class Subscription<
|
||||
/**
|
||||
* The time at which the current subscription period will start
|
||||
*/
|
||||
public get currentPeriodStartAt() {
|
||||
public get currentPeriodStartDate() {
|
||||
const startTimestamp = this.currentPeriodStartTimestamp;
|
||||
return startTimestamp ? new Date(startTimestamp) : null;
|
||||
}
|
||||
@@ -125,7 +125,7 @@ export class Subscription<
|
||||
/**
|
||||
* The time at which the current subscription period will end
|
||||
*/
|
||||
public get currentPeriodEndsAt() {
|
||||
public get currentPeriodEndDate() {
|
||||
const endTimestamp = this.currentPeriodEndTimestamp;
|
||||
return endTimestamp ? new Date(endTimestamp) : null;
|
||||
}
|
||||
@@ -149,7 +149,7 @@ export class Subscription<
|
||||
*
|
||||
* @remarks This is populated when the {@link Subscription.status} transitions to {@link discord-api-types/v10#SubscriptionStatus.Ending}.
|
||||
*/
|
||||
public get canceledAt() {
|
||||
public get canceledDate() {
|
||||
const canceledTimestamp = this.canceledTimestamp;
|
||||
return canceledTimestamp ? new Date(canceledTimestamp) : null;
|
||||
}
|
||||
@@ -171,7 +171,7 @@ export class Subscription<
|
||||
/**
|
||||
* The time the subscription was created at
|
||||
*/
|
||||
public get createdAt() {
|
||||
public get createdDate() {
|
||||
const createdTimestamp = this.createdTimestamp;
|
||||
return createdTimestamp ? new Date(createdTimestamp) : null;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export class Team<Omitted extends keyof APITeam | '' = ''> extends Structure<API
|
||||
/**
|
||||
* The time the team was created at
|
||||
*/
|
||||
public get createdAt() {
|
||||
public get createdDate() {
|
||||
const createdTimestamp = this.createdTimestamp;
|
||||
return createdTimestamp ? new Date(createdTimestamp) : null;
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ export class User<Omitted extends keyof APIUser | '' = ''> extends Structure<API
|
||||
/**
|
||||
* The time the user was created at
|
||||
*/
|
||||
public get createdAt() {
|
||||
public get createdDate() {
|
||||
const createdTimestamp = this.createdTimestamp;
|
||||
return createdTimestamp ? new Date(createdTimestamp) : null;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export function extendTemplate<SuperTemplate extends Record<string, unknown>>(
|
||||
* Turns a JavaScript Date object into the timestamp format used by Discord in payloads.
|
||||
* E.g. `2025-11-16T14:09:25.239000+00:00`
|
||||
*
|
||||
* @internal
|
||||
* @param date - a Date instance
|
||||
* @returns an ISO8601 timestamp with microseconds precision and explicit +00:00 timezone
|
||||
*/
|
||||
|
||||
@@ -30,3 +30,25 @@ export function isFieldSet<Value extends object, Key extends string, Type extend
|
||||
// eslint-disable-next-line valid-typeof
|
||||
return hasProperty(data, fieldName) && typeof data[fieldName] === type;
|
||||
}
|
||||
|
||||
/**
|
||||
* This typeguard will determine whether a specified array field is the specified
|
||||
* type.
|
||||
*
|
||||
* @internal
|
||||
* @param array - The target array from which the target index will be narrowed
|
||||
* @param targetIndex - The target index of the element whose type to narrow
|
||||
* @param type - The type to compare against
|
||||
*/
|
||||
export function isArrayFieldSet<Type extends TypeofType, Index extends number>(
|
||||
array: unknown,
|
||||
targetIndex: Index,
|
||||
type: Type,
|
||||
): array is Record<Index, Type> & unknown[] {
|
||||
return Array.isArray(array)
|
||||
? array.length > targetIndex
|
||||
? // eslint-disable-next-line valid-typeof
|
||||
typeof array[targetIndex] === type
|
||||
: false
|
||||
: false;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ export class Webhook<Omitted extends keyof APIWebhook | '' = ''> extends Structu
|
||||
/**
|
||||
* The time the webhook was created at
|
||||
*/
|
||||
public get createdAt() {
|
||||
public get createdDate() {
|
||||
const createdTimestamp = this.createdTimestamp;
|
||||
return createdTimestamp ? new Date(createdTimestamp) : null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user