diff --git a/packages/discord.js/src/structures/User.js b/packages/discord.js/src/structures/User.js index 0ded5cf47b6e..c12248e653af 100644 --- a/packages/discord.js/src/structures/User.js +++ b/packages/discord.js/src/structures/User.js @@ -141,18 +141,22 @@ class User extends Base { * @property {string} asset The avatar decoration hash * @property {Snowflake} skuId The id of the avatar decoration's SKU */ - - if (data.avatar_decoration_data) { - /** - * The user avatar decoration's data - * @type {?AvatarDecorationData} - */ - this.avatarDecorationData = { - asset: data.avatar_decoration_data.asset, - skuId: data.avatar_decoration_data.sku_id, - }; + if ('avatar_decoration_data' in data) { + if (data.avatar_decoration_data) { + /** + * The user avatar decoration's data + * + * @type {?AvatarDecorationData} + */ + this.avatarDecorationData = { + asset: data.avatar_decoration_data.asset, + skuId: data.avatar_decoration_data.sku_id, + }; + } else { + this.avatarDecorationData = null; + } } else { - this.avatarDecorationData = null; + this.avatarDecorationData ??= null; } /** @@ -178,6 +182,34 @@ class User extends Base { } else { this.collectibles = null; } + + /** + * @typedef {Object} UserPrimaryGuild + * @property {?Snowflake} identityGuildId The id of the user's primary guild + * @property {?boolean} identityEnabled Whether the user is displaying the primary guild's tag + * @property {?string} tag The user's guild tag. Limited to 4 characters + * @property {?string} badge The guild tag badge hash + */ + + if ('primary_guild' in data) { + if (data.primary_guild) { + /** + * The primary guild of the user + * + * @type {?UserPrimaryGuild} + */ + this.primaryGuild = { + identityGuildId: data.primary_guild.identity_guild_id, + identityEnabled: data.primary_guild.identity_enabled, + tag: data.primary_guild.tag, + badge: data.primary_guild.badge, + }; + } else { + this.primaryGuild = null; + } + } else { + this.primaryGuild ??= null; + } } /** @@ -269,6 +301,18 @@ class User extends Base { return this.banner && this.client.rest.cdn.banner(this.id, this.banner, options); } + /** + * A link to the user's guild tag badge. + * + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + guildTagBadgeURL(options = {}) { + return this.primaryGuild?.badge + ? this.client.rest.cdn.guildTagBadge(this.primaryGuild.identityGuildId, this.primaryGuild.badge, options) + : null; + } + /** * The tag of this user * This user's username, or their legacy tag (e.g. `hydrabolt#0001`) @@ -343,7 +387,11 @@ class User extends Base { this.collectibles?.nameplate?.skuId === user.collectibles?.nameplate?.skuId && this.collectibles?.nameplate?.asset === user.collectibles?.nameplate?.asset && this.collectibles?.nameplate?.label === user.collectibles?.nameplate?.label && - this.collectibles?.nameplate?.palette === user.collectibles?.nameplate?.palette + this.collectibles?.nameplate?.palette === user.collectibles?.nameplate?.palette && + this.primaryGuild?.identityGuildId === user.primaryGuild?.identityGuildId && + this.primaryGuild?.identityEnabled === user.primaryGuild?.identityEnabled && + this.primaryGuild?.tag === user.primaryGuild?.tag && + this.primaryGuild?.badge === user.primaryGuild?.badge ); } @@ -374,6 +422,12 @@ class User extends Base { this.collectibles?.nameplate?.asset === user.collectibles?.nameplate?.asset && this.collectibles?.nameplate?.label === user.collectibles?.nameplate?.label && this.collectibles?.nameplate?.palette === user.collectibles?.nameplate?.palette + : true) && + ('primary_guild' in user + ? this.primaryGuild?.identityGuildId === user.primary_guild?.identity_guild_id && + this.primaryGuild?.identityEnabled === user.primary_guild?.identity_enabled && + this.primaryGuild?.tag === user.primary_guild?.tag && + this.primaryGuild?.badge === user.primary_guild?.badge : true) ); } @@ -423,6 +477,7 @@ class User extends Base { json.avatarURL = this.avatarURL(); json.displayAvatarURL = this.displayAvatarURL(); json.bannerURL = this.banner ? this.bannerURL() : this.banner; + json.guildTagBadgeURL = this.guildTagBadgeURL(); return json; } } diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 27015a8c6cf5..175106378dfb 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -3803,6 +3803,17 @@ export interface AvatarDecorationData { skuId: Snowflake; } +export interface Collectibles { + nameplate: NameplateData | null; +} + +export interface UserPrimaryGuild { + badge: string | null; + identityEnabled: boolean | null; + identityGuildId: Snowflake | null; + tag: string | null; +} + export interface NameplateData { asset: string; label: string; @@ -3810,10 +3821,6 @@ export interface NameplateData { skuId: Snowflake; } -export interface Collectibles { - nameplate: NameplateData | null; -} - export interface UnfurledMediaItemData { url: string; } @@ -3849,12 +3856,14 @@ export class User extends Base { public get hexAccentColor(): HexColorString | null | undefined; public id: Snowflake; public get partial(): false; + public primaryGuild: UserPrimaryGuild | null; public system: boolean; public get tag(): string; public username: string; public avatarURL(options?: ImageURLOptions): string | null; public avatarDecorationURL(options?: BaseImageURLOptions): string | null; public bannerURL(options?: ImageURLOptions): string | null | undefined; + public guildTagBadgeURL(options?: ImageURLOptions): string | null; public createDM(force?: boolean): Promise; public deleteDM(): Promise; public displayAvatarURL(options?: ImageURLOptions): string; diff --git a/packages/rest/__tests__/CDN.test.ts b/packages/rest/__tests__/CDN.test.ts index 786158efe8e7..4eaf4bca1284 100644 --- a/packages/rest/__tests__/CDN.test.ts +++ b/packages/rest/__tests__/CDN.test.ts @@ -142,6 +142,10 @@ test('soundboardSound', () => { expect(cdn.soundboardSound(id)).toEqual(`${baseCDN}/soundboard-sounds/${id}`); }); +test('guildTagBadge', () => { + expect(cdn.guildTagBadge(id, hash)).toEqual(`${baseCDN}/guild-tag-badges/${id}/${hash}.webp`); +}); + test('makeURL throws on invalid size', () => { expect(() => cdn.avatar(id, animatedHash, { size: 5 })).toThrow(RangeError); }); diff --git a/packages/rest/src/lib/CDN.ts b/packages/rest/src/lib/CDN.ts index 95ceb8a5ae23..668715b6a517 100644 --- a/packages/rest/src/lib/CDN.ts +++ b/packages/rest/src/lib/CDN.ts @@ -374,6 +374,17 @@ export class CDN { return `${this.cdn}${CDNRoutes.soundboardSound(soundId)}`; } + /** + * Generates a URL for a guild tag badge. + * + * @param guildId - The guild id + * @param badgeHash - The hash of the badge + * @param options - Optional options for the badge + */ + public guildTagBadge(guildId: string, badgeHash: string, options?: Readonly): string { + return this.makeURL(`/guild-tag-badges/${guildId}/${badgeHash}`, options); + } + /** * Constructs the URL for the resource, checking whether or not `hash` starts with `a_` if `dynamic` is set to `true`. *