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:
Asad Humayun
2026-08-29 06:39:46 +00:00
committed by GitHub
parent a258a488b9
commit a81ed8a306
36 changed files with 1077 additions and 64 deletions
+11 -10
View File
@@ -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);
+15 -10
View File
@@ -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';
+1 -1
View File
@@ -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;
}
}
+1 -1
View File
@@ -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;
+1
View File
@@ -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';
+2 -2
View File
@@ -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;
}
+10 -7
View File
@@ -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;
}
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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;
}