Skip to content
49 changes: 48 additions & 1 deletion packages/discord.js/src/structures/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,30 @@ class User extends Base {
} else {
this.avatarDecorationData = 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 (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;
}
}

/**
Expand Down Expand Up @@ -246,6 +270,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
* <info>This user's username, or their legacy tag (e.g. `hydrabolt#0001`)
Expand Down Expand Up @@ -338,7 +374,11 @@ class User extends Base {
this.banner === user.banner &&
this.accentColor === user.accentColor &&
this.avatarDecorationData?.asset === user.avatarDecorationData?.asset &&
this.avatarDecorationData?.skuId === user.avatarDecorationData?.skuId
this.avatarDecorationData?.skuId === user.avatarDecorationData?.skuId &&
this.primaryGuild?.identityGuildId === user.primaryGuild?.identityGuildId &&
this.primaryGuild?.identityEnabled === user.primaryGuild?.identityEnabled &&
this.primaryGuild?.tag === user.primaryGuild?.tag &&
this.primaryGuild?.badge === user.primaryGuild?.badge
);
}

Expand All @@ -363,6 +403,12 @@ class User extends Base {
('avatar_decoration_data' in user
? this.avatarDecorationData?.asset === user.avatar_decoration_data?.asset &&
this.avatarDecorationData?.skuId === user.avatar_decoration_data?.sku_id
: 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)
);
}
Expand Down Expand Up @@ -402,6 +448,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;
}
}
Expand Down
12 changes: 9 additions & 3 deletions packages/discord.js/typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ import {
APIComponentInModalActionRow,
APIContainerComponent,
APIEmbed,
APIEmbedAuthor,
APIEmbedField,
APIEmbedFooter,
APIEmbedImage,
APIEmbedProvider,
APIEmoji,
APIEntitlement,
Expand Down Expand Up @@ -3497,6 +3494,13 @@ export interface AvatarDecorationData {
skuId: Snowflake;
}

export interface UserPrimaryGuild {
badge: string | null;
identityEnabled: boolean | null;
identityGuildId: Snowflake | null;
tag: string | null;
}

export interface UnfurledMediaItemData {
url: string;
}
Expand Down Expand Up @@ -3528,12 +3532,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<DMChannel>;
public deleteDM(): Promise<DMChannel>;
public displayAvatarURL(options?: ImageURLOptions): string;
Expand Down
5 changes: 4 additions & 1 deletion packages/rest/__tests__/CDN.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});

Expand Down
11 changes: 11 additions & 0 deletions packages/rest/src/lib/CDN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,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<BaseImageURLOptions>): 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`.
*
Expand Down