diff --git a/packages/structures/src/Structure.ts b/packages/structures/src/Structure.ts new file mode 100644 index 0000000000000..fcee826469690 --- /dev/null +++ b/packages/structures/src/Structure.ts @@ -0,0 +1,26 @@ +import { data as kData } from './utils/symbols'; + +export abstract class Structure { + protected [kData]: Readonly>; + + protected constructor(data: Readonly>) { + // Do not shallow clone data here as subclasses should do it (also allows them to set the constructor to public) + this[kData] = data; + } + + /** + * Update this structure with new data + * @param data - A payload with updated data for this structure + * @returns New structure with patched data + */ + public patch(data: Readonly>): this { + // @ts-expect-error TS cannot identify the type of this.constructor properly because subclasses + return new this.constructor({ ...this.toJSON(), ...data }) as this; + } + + public toJSON(): DataType { + // This will be DataType provided nothing is omitted, when omits occur, subclass needs to overwrite this. + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return { ...this[kData] } as DataType; + } +} diff --git a/packages/structures/src/users/Connection.ts b/packages/structures/src/users/Connection.ts index 703d0c736d815..3bc29243c49da 100644 --- a/packages/structures/src/users/Connection.ts +++ b/packages/structures/src/users/Connection.ts @@ -1,76 +1,80 @@ import type { APIConnection } from 'discord-api-types/v10'; +import { Structure } from '../Structure'; +import { data as kData } from '../utils/symbols'; /** * Represents a user's connection on Discord. */ -export class Connection { +export class Connection extends Structure { public constructor( /** * The raw data received from the API for the connection */ - protected raw: APIConnection, - ) {} + data: APIConnection, + ) { + super({ ...data }); + } /** * The id of the connection account */ public get id() { - return this.raw.id; + return this[kData].id; } /** * The username of the connection account */ public get name() { - return this.raw.name; + return this[kData].name; } /** * The type of service this connection is for */ public get type() { - return this.raw.type; + return this[kData].type; } /** * Whether the connection is revoked */ public get revoked() { - return this.raw.revoked ?? false; + return this[kData].revoked ?? false; } /** * Any integrations associated with this connection */ public get integrations() { - return this.raw.integrations ?? null; + return this[kData].integrations ?? null; } /** * Whether the connection is verified */ public get verified() { - return this.raw.verified; + return this[kData].verified; } /** * Whether friend sync is enabled for this connection */ public get friendSync() { - return this.raw.friend_sync; + return this[kData].friend_sync; } /** * Whether activities related to this connection are shown in the users presence */ public get showActivity() { - return this.raw.show_activity; + return this[kData].show_activity; } /** * The visibilty state for this connection */ public get visibility() { - return this.raw.visibility; + return this[kData].visibility; } } diff --git a/packages/structures/src/users/User.ts b/packages/structures/src/users/User.ts index eb0bad767ba6b..50bd74c78c9e7 100644 --- a/packages/structures/src/users/User.ts +++ b/packages/structures/src/users/User.ts @@ -1,57 +1,62 @@ import { DiscordSnowflake } from '@sapphire/snowflake'; import type { APIUser, Snowflake } from 'discord-api-types/v10'; +import { Structure } from '../Structure'; + +import { data as kData } from '../utils/symbols'; /** * Represents any user on Discord. */ -export class User { +export class User extends Structure { public constructor( /** * The raw data received from the API for the user */ - protected raw: APIUser, - ) {} + data: APIUser, + ) { + super({ ...data }); + } /** * The user's id */ public get id(): Snowflake { - return this.raw.id; + return this[kData].id; } /** * The username of the user */ public get username() { - return this.raw.username; + return this[kData].username; } /** * The user's 4 digit tag, in combination with the username can uniquely identify the user */ public get discriminator() { - return this.raw.discriminator; + return this[kData].discriminator; } /** * The user avatar's hash */ public get avatar() { - return this.raw.avatar; + return this[kData].avatar; } /** * Whether the user is a bot */ public get bot() { - return this.raw.bot ?? false; + return this[kData].bot ?? false; } /** * Whether the user is an Official Discord System user */ public get system() { - return this.raw.system ?? false; + return this[kData].system ?? false; } /** @@ -59,7 +64,7 @@ export class User { * This property is only set when the user was fetched with an OAuth2 token and the `identify` scope */ public get mfaEnabled() { - return this.raw.mfa_enabled; + return this[kData].mfa_enabled; } /** @@ -67,7 +72,7 @@ export class User { * This property is only set when the user was manually fetched */ public get banner() { - return this.raw.banner; + return this[kData].banner; } /** @@ -75,7 +80,7 @@ export class User { * This property is only set when the user was manually fetched */ public get accentColor() { - return this.raw.accent_color; + return this[kData].accent_color; } /** @@ -83,7 +88,7 @@ export class User { * This property is only set when the user was fetched with an Oauth2 token and the `identify` scope */ public get locale() { - return this.raw.locale; + return this[kData].locale; } /** @@ -91,7 +96,7 @@ export class User { * This property is only set when the user was fetched with an OAuth2 token and the `email` scope */ public get verified() { - return this.raw.verified; + return this[kData].verified; } /** @@ -99,7 +104,7 @@ export class User { * This property is only set when the user was fetched with an OAuth2 token and the `email` scope */ public get email() { - return this.raw.email; + return this[kData].email; } /** @@ -107,14 +112,14 @@ export class User { * This property is only set when the user was fetched with an OAuth2 token and the `identify` scope */ public get premiumType() { - return this.raw.premium_type; + return this[kData].premium_type; } /** * The flags for the user */ public get flags() { - return this.raw.public_flags; + return this[kData].public_flags; } /** diff --git a/packages/structures/src/utils/symbols.ts b/packages/structures/src/utils/symbols.ts new file mode 100644 index 0000000000000..29edb1d295cac --- /dev/null +++ b/packages/structures/src/utils/symbols.ts @@ -0,0 +1 @@ +export const data = Symbol('structure:data');