diff --git a/packages/discord.js/src/structures/User.js b/packages/discord.js/src/structures/User.js index 0f0e84e22d07..c87e50587474 100644 --- a/packages/discord.js/src/structures/User.js +++ b/packages/discord.js/src/structures/User.js @@ -139,18 +139,22 @@ class User extends Base { * @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; } /** @@ -176,6 +180,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; + } } /** @@ -271,6 +303,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`) @@ -367,7 +411,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 ); } @@ -398,6 +446,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) ); } @@ -437,6 +491,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 53e83f95278d..3198570d3c0d 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -31,10 +31,7 @@ import { APIComponentInModalActionRow, APIContainerComponent, APIEmbed, - APIEmbedAuthor, APIEmbedField, - APIEmbedFooter, - APIEmbedImage, APIEmbedProvider, APIEmoji, APIEntitlement, @@ -3510,6 +3507,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; @@ -3517,10 +3525,6 @@ export interface NameplateData { skuId: Snowflake; } -export interface Collectibles { - nameplate: NameplateData | null; -} - export interface UnfurledMediaItemData { url: string; } @@ -3553,12 +3557,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(): 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 5b6912259a16..c858a1910c50 100644 --- a/packages/rest/__tests__/CDN.test.ts +++ b/packages/rest/__tests__/CDN.test.ts @@ -134,8 +134,11 @@ 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', () => { - // @ts-expect-error: 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 b08f64442383..92ab23690e54 100644 --- a/packages/rest/src/lib/CDN.ts +++ b/packages/rest/src/lib/CDN.ts @@ -340,6 +340,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`. *