diff --git a/packages/structures/src/entitlements/Entitlement.ts b/packages/structures/src/entitlements/Entitlement.ts new file mode 100644 index 000000000..a1dab3619 --- /dev/null +++ b/packages/structures/src/entitlements/Entitlement.ts @@ -0,0 +1,176 @@ +import { DiscordSnowflake } from '@sapphire/snowflake'; +import type { APIEntitlement } from 'discord-api-types/v10'; +import { Structure } from '../Structure.js'; +import { dateToDiscordISOTimestamp } from '../utils/optimization.js'; +import { kData, kStartsTimestamp, kEndsTimestamp } from '../utils/symbols.js'; +import { isIdSet } from '../utils/type-guards.js'; +import type { Partialize } from '../utils/types.js'; + +/** + * Represents any entitlement 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 Entitlement extends Structure< + APIEntitlement, + Omitted +> { + /** + * The template used for removing data from the raw data stored for each entitlement + * + * @remarks This template has defaults, if you want to remove additional data and keep the defaults, + * use `Object.defineProperties`. To override the defaults, set this value directly. + */ + public static override readonly DataTemplate: Partial = { + set starts_at(_: string) {}, + set ends_at(_: string) {}, + }; + + protected [kStartsTimestamp]: number | null = null; + + protected [kEndsTimestamp]: number | null = null; + + /** + * @param data - The raw data received from the API for the entitlement + */ + public constructor(data: Partialize) { + super(data); + this.optimizeData(data); + } + + /** + * {@inheritDoc Structure.optimizeData} + */ + protected override optimizeData(data: Partial) { + if (data.starts_at) { + this[kStartsTimestamp] = Date.parse(data.starts_at); + } + + if (data.ends_at) { + this[kEndsTimestamp] = Date.parse(data.ends_at); + } + } + + /** + * The id of the entitlement + */ + public get id() { + return this[kData].id; + } + + /** + * The id of the SKU + */ + public get skuId() { + return this[kData].sku_id; + } + + /** + * The id of the parent application + */ + public get applicationId() { + return this[kData].application_id; + } + + /** + * The id of the user that is granted access to the entitlement's SKU + */ + public get userId() { + return this[kData].user_id; + } + + /** + * Type of entitlement + * + * @see {@link https://discord.com/developers/docs/resources/entitlement#entitlement-object-entitlement-types} + */ + public get type() { + return this[kData].type; + } + + /** + * Whether the entitlement was deleted + */ + public get deleted() { + return this[kData].deleted; + } + + /** + * Start date at which the entitlement is valid + */ + public get startsAt() { + const timestamp = this.startsTimestamp; + return timestamp ? new Date(timestamp) : null; + } + + /** + * Timestamp of the start date at which the entitlement is valid + */ + public get startsTimestamp() { + return this[kStartsTimestamp]; + } + + /** + * End date at which the entitlement is valid + */ + public get endsAt() { + const timestamp = this.endsTimestamp; + return timestamp ? new Date(timestamp) : null; + } + + /** + * Timestamp of the end date at which the entitlement is valid + */ + public get endsTimestamp() { + return this[kEndsTimestamp]; + } + + /** + * Id of the guild that is granted access to the entitlement's SKU + */ + public get guildId() { + return this[kData].guild_id; + } + + /** + * For consumable items, whether the entitlement has been consumed + */ + public get consumed() { + return this[kData].consumed; + } + + /** + * The timestamp the entitlement was created at + */ + public get createdTimestamp() { + return isIdSet(this.id) ? DiscordSnowflake.timestampFrom(this.id) : null; + } + + /** + * The time the entitlement was created at + */ + public get createdAt() { + const createdTimestamp = this.createdTimestamp; + return createdTimestamp ? new Date(createdTimestamp) : null; + } + + /** + * {@inheritDoc Structure.toJSON} + */ + public override toJSON() { + const clone = super.toJSON(); + + const startsAtTimestamp = this[kStartsTimestamp]; + const endsAtTimestamp = this[kEndsTimestamp]; + + if (startsAtTimestamp) { + clone.starts_at = dateToDiscordISOTimestamp(new Date(startsAtTimestamp)); + } + + if (endsAtTimestamp) { + clone.ends_at = dateToDiscordISOTimestamp(new Date(endsAtTimestamp)); + } + + return clone; + } +} diff --git a/packages/structures/src/entitlements/index.ts b/packages/structures/src/entitlements/index.ts new file mode 100644 index 000000000..0c06ae8a9 --- /dev/null +++ b/packages/structures/src/entitlements/index.ts @@ -0,0 +1 @@ +export * from './Entitlement.js'; diff --git a/packages/structures/src/index.ts b/packages/structures/src/index.ts index 06a7611db..97ad34d05 100644 --- a/packages/structures/src/index.ts +++ b/packages/structures/src/index.ts @@ -1,6 +1,7 @@ export * from './bitfields/index.js'; export * from './channels/index.js'; export * from './emojis/index.js'; +export * from './entitlements/index.js'; export * from './interactions/index.js'; export * from './invites/index.js'; export * from './messages/index.js'; diff --git a/packages/structures/src/utils/symbols.ts b/packages/structures/src/utils/symbols.ts index 67c2707ec..c9c9d72dc 100644 --- a/packages/structures/src/utils/symbols.ts +++ b/packages/structures/src/utils/symbols.ts @@ -7,6 +7,9 @@ export const kCreatedTimestamp = Symbol.for('djs.structures.createdTimestamp'); export const kEditedTimestamp = Symbol.for('djs.structures.editedTimestamp'); export const kArchiveTimestamp = Symbol.for('djs.structures.archiveTimestamp'); +export const kStartsTimestamp = Symbol.for('djs.structures.startsTimestamp'); +export const kEndsTimestamp = Symbol.for('djs.structures.endsTimestamp'); + export const kAllow = Symbol.for('djs.structures.allow'); export const kDeny = Symbol.for('djs.structures.deny');