diff --git a/packages/discord.js/src/structures/User.js b/packages/discord.js/src/structures/User.js index e2fa4cab4fde..58dfa6d6e982 100644 --- a/packages/discord.js/src/structures/User.js +++ b/packages/discord.js/src/structures/User.js @@ -28,6 +28,8 @@ class User extends Base { this.flags = null; + this.guildtag = null; + this._patch(data); } @@ -151,6 +153,17 @@ class User extends Base { } else { this.avatarDecorationData = null; } + + if (data.primary_guild?.tag) { + /** + * The guild tag name of this user + * + * @type {?string} + */ + this.guildtag = data.primary_guild?.tag; + } else { + this.guildtag ??= null; + } } /** @@ -338,7 +351,8 @@ 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.guildtag === user.guildtag ); } @@ -363,7 +377,8 @@ 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) + : true) && + this.guildtag === user.primary_guild?.tag ); } @@ -396,6 +411,7 @@ class User extends Base { defaultAvatarURL: true, hexAccentColor: true, tag: true, + guildtag: true, }, ...props, ); diff --git a/packages/discord.js/test/user-guildtag.test.js b/packages/discord.js/test/user-guildtag.test.js new file mode 100644 index 000000000000..c322e1258505 --- /dev/null +++ b/packages/discord.js/test/user-guildtag.test.js @@ -0,0 +1,150 @@ +'use strict'; + +const { Client, GatewayIntentBits } = require('../src/index.js'); + +// Mock data based on actual API response (personal information anonymized) +const mockUserDataWithGuildtag = { + id: '1234567890123456789', + username: 'testuser', + avatar: null, + discriminator: '0', + public_flags: 128, + flags: 128, + banner: null, + accent_color: null, + global_name: 'Test User', + avatar_decoration_data: null, + collectibles: null, + banner_color: null, + clan: { + identity_guild_id: '9876543210987654321', + identity_enabled: true, + tag: 'TEST', + badge: 'abcdef1234567890abcdef1234567890', + }, + primary_guild: { + identity_guild_id: '9876543210987654321', + identity_enabled: true, + tag: 'TEST', + badge: 'abcdef1234567890abcdef1234567890', + }, +}; + +const mockUserDataWithoutGuildtag = { + id: '123456789012345678', + username: 'testuser', + discriminator: '1234', + avatar: 'test_avatar_hash', + bot: false, + system: false, + public_flags: 0, + // primary_guild is not present +}; + +describe('User guildtag property tests', () => { + let client; + let user; + + beforeEach(() => { + client = new Client({ + intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.DirectMessages], + }); + + const { User } = require('../src/structures/User.js'); + user = new User(client, mockUserDataWithGuildtag); + }); + + afterEach(() => { + if (client) { + client.destroy(); + } + }); + + test('should have guildtag property when provided in data', () => { + expect(user.guildtag).toBe('TEST'); + }); + + test('should set guildtag to null when not provided in data', () => { + const userWithoutGuildtag = new (require('../src/structures/User.js').User)(client, mockUserDataWithoutGuildtag); + expect(userWithoutGuildtag.guildtag).toBeNull(); + }); + + test('should update guildtag when _patch is called with new data', () => { + const newData = { + ...mockUserDataWithGuildtag, + primary_guild: { ...mockUserDataWithGuildtag.primary_guild, tag: 'NewGuildTag' }, + }; + user._patch(newData); + expect(user.guildtag).toBe('NewGuildTag'); + }); + + test('should handle guildtag in equals method', () => { + const { User } = require('../src/structures/User.js'); + const user1 = new User(client, mockUserDataWithGuildtag); + const user2 = new User(client, { + ...mockUserDataWithGuildtag, + primary_guild: { ...mockUserDataWithGuildtag.primary_guild, tag: 'DifferentGuildTag' }, + }); + + expect(user1.equals(user2)).toBe(false); + + const user3 = new User(client, mockUserDataWithGuildtag); + expect(user1.equals(user3)).toBe(true); + }); + + test('should handle guildtag in _equals method', () => { + const apiUserData = { + id: '1234567890123456789', + username: 'testuser', + avatar: null, + discriminator: '0', + public_flags: 128, + flags: 128, + banner: null, + accent_color: null, + global_name: 'Test User', + avatar_decoration_data: null, + collectibles: null, + banner_color: null, + clan: { + identity_guild_id: '9876543210987654321', + identity_enabled: true, + tag: 'TEST', + badge: 'abcdef1234567890abcdef1234567890', + }, + primary_guild: { + identity_guild_id: '9876543210987654321', + identity_enabled: true, + tag: 'TEST', + badge: 'abcdef1234567890abcdef1234567890', + }, + }; + + expect(user._equals(apiUserData)).toBe(true); + + const differentApiData = { + ...apiUserData, + primary_guild: { ...apiUserData.primary_guild, tag: 'DifferentGuildTag' }, + }; + expect(user._equals(differentApiData)).toBe(false); + }); + + test('should include guildtag in toJSON output', () => { + const json = user.toJSON(); + expect(json.guildtag).toBe('TEST'); + }); + + test('should handle guildtag in cache operations', () => { + client.users.cache.set(user.id, user); + const cachedUser = client.users.cache.get(user.id); + expect(cachedUser.guildtag).toBe('TEST'); + }); + + test('should handle partial user data', () => { + const partialData = { id: '123456789012345678' }; + const partialUser = new (require('../src/structures/User.js').User)(client, partialData); + + expect(partialUser.partial).toBe(true); + expect(partialUser.guildtag).toBeNull(); + }); +}); diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index ed2da0646c0f..7043997a717d 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -3519,6 +3519,7 @@ export class User extends Base { public avatarDecorationData: AvatarDecorationData | null; public banner: string | null | undefined; public bot: boolean; + public guildtag: string | null; public get createdAt(): Date; public get createdTimestamp(): number; public discriminator: string;