diff --git a/packages/discord.js/src/structures/GuildMember.js b/packages/discord.js/src/structures/GuildMember.js
index 3e71824f3f12..8806b508b555 100644
--- a/packages/discord.js/src/structures/GuildMember.js
+++ b/packages/discord.js/src/structures/GuildMember.js
@@ -238,12 +238,12 @@ class GuildMember extends Base {
}
/**
- * The nickname of this member, or their username if they don't have one
+ * The nickname of this member, or their user display name if they don't have one
* @type {?string}
* @readonly
*/
get displayName() {
- return this.nickname ?? this.user.username;
+ return this.nickname ?? this.user.displayName;
}
/**
diff --git a/packages/discord.js/src/structures/User.js b/packages/discord.js/src/structures/User.js
index c784e4e95eb4..092cc3c6ffcc 100644
--- a/packages/discord.js/src/structures/User.js
+++ b/packages/discord.js/src/structures/User.js
@@ -1,11 +1,15 @@
'use strict';
+const process = require('node:process');
const { userMention } = require('@discordjs/builders');
+const { calculateUserDefaultAvatarIndex } = require('@discordjs/rest');
const { DiscordSnowflake } = require('@sapphire/snowflake');
const Base = require('./Base');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const UserFlagsBitField = require('../util/UserFlagsBitField');
+let tagDeprecationEmitted = false;
+
/**
* Represents a user on Discord.
* @implements {TextBasedChannel}
@@ -41,6 +45,16 @@ class User extends Base {
this.username ??= null;
}
+ if ('global_name' in data) {
+ /**
+ * The global name of this user
+ * @type {?string}
+ */
+ this.globalName = data.global_name;
+ } else {
+ this.globalName ??= null;
+ }
+
if ('bot' in data) {
/**
* Whether or not the user is a bot
@@ -53,7 +67,8 @@ class User extends Base {
if ('discriminator' in data) {
/**
- * A discriminator based on username for the user
+ * The discriminator of this user
+ * `'0'`, or a 4-digit stringified number if they're using the legacy username system
* @type {?string}
*/
this.discriminator = data.discriminator;
@@ -154,7 +169,8 @@ class User extends Base {
* @readonly
*/
get defaultAvatarURL() {
- return this.client.rest.cdn.defaultAvatar(this.discriminator % 5);
+ const index = this.discriminator === '0' ? calculateUserDefaultAvatarIndex(this.id) : this.discriminator % 5;
+ return this.client.rest.cdn.defaultAvatar(index);
}
/**
@@ -188,12 +204,33 @@ class User extends Base {
}
/**
- * The Discord "tag" (e.g. `hydrabolt#0001`) for this user
+ * The tag of this user
+ * This user's username, or their legacy tag (e.g. `hydrabolt#0001`)
+ * if they're using the legacy username system
* @type {?string}
* @readonly
+ * @deprecated Use {@link User#username} instead.
*/
get tag() {
- return typeof this.username === 'string' ? `${this.username}#${this.discriminator}` : null;
+ if (!tagDeprecationEmitted) {
+ process.emitWarning('User#tag is deprecated. Use User#username instead.', 'DeprecationWarning');
+ tagDeprecationEmitted = true;
+ }
+
+ return typeof this.username === 'string'
+ ? this.discriminator === '0'
+ ? this.username
+ : `${this.username}#${this.discriminator}`
+ : null;
+ }
+
+ /**
+ * The global name of this user, or their username if they don't have one
+ * @type {?string}
+ * @readonly
+ */
+ get displayName() {
+ return this.globalName ?? this.username;
}
/**
diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts
index c96a54f37db8..c8e8e4b13e8f 100644
--- a/packages/discord.js/typings/index.d.ts
+++ b/packages/discord.js/typings/index.d.ts
@@ -3060,13 +3060,16 @@ export class User extends PartialTextBasedChannel(Base) {
public get createdAt(): Date;
public get createdTimestamp(): number;
public discriminator: string;
+ public get displayName(): string;
public get defaultAvatarURL(): string;
public get dmChannel(): DMChannel | null;
public flags: Readonly | null;
+ public globalName: string | null;
public get hexAccentColor(): HexColorString | null | undefined;
public id: Snowflake;
public get partial(): false;
public system: boolean;
+ /** @deprecated Use {@link User#username} instead. */
public get tag(): string;
public username: string;
public avatarURL(options?: ImageURLOptions): string | null;
diff --git a/packages/rest/src/index.ts b/packages/rest/src/index.ts
index 586580e61fc4..b6af7b7ef7df 100644
--- a/packages/rest/src/index.ts
+++ b/packages/rest/src/index.ts
@@ -5,7 +5,7 @@ export * from './lib/errors/RateLimitError.js';
export * from './lib/RequestManager.js';
export * from './lib/REST.js';
export * from './lib/utils/constants.js';
-export { makeURLSearchParams, parseResponse } from './lib/utils/utils.js';
+export { calculateUserDefaultAvatarIndex, makeURLSearchParams, parseResponse } from './lib/utils/utils.js';
/**
* The {@link https://github.com/discordjs/discord.js/blob/main/packages/rest/#readme | @discordjs/rest} version
diff --git a/packages/rest/src/lib/CDN.ts b/packages/rest/src/lib/CDN.ts
index eddde427e469..a42966bd8829 100644
--- a/packages/rest/src/lib/CDN.ts
+++ b/packages/rest/src/lib/CDN.ts
@@ -119,12 +119,15 @@ export class CDN {
}
/**
- * Generates the default avatar URL for a discriminator.
+ * Generates a default avatar URL
*
- * @param discriminator - The discriminator modulo 5
+ * @param index - The default avatar index
+ * @remarks
+ * To calculate the index for a user do `(userId >> 22) % 6`,
+ * or `discriminator % 5` if they're using the legacy username system.
*/
- public defaultAvatar(discriminator: number): string {
- return this.makeURL(`/embed/avatars/${discriminator}`, { extension: 'png' });
+ public defaultAvatar(index: number): string {
+ return this.makeURL(`/embed/avatars/${index}`, { extension: 'png' });
}
/**
diff --git a/packages/rest/src/lib/utils/utils.ts b/packages/rest/src/lib/utils/utils.ts
index 0489b02d289d..07d63e542d86 100644
--- a/packages/rest/src/lib/utils/utils.ts
+++ b/packages/rest/src/lib/utils/utils.ts
@@ -1,5 +1,5 @@
import { URLSearchParams } from 'node:url';
-import type { RESTPatchAPIChannelJSONBody } from 'discord-api-types/v10';
+import type { RESTPatchAPIChannelJSONBody, Snowflake } from 'discord-api-types/v10';
import type { RateLimitData, ResponseLike } from '../REST.js';
import { type RequestManager, RequestMethod } from '../RequestManager.js';
import { RateLimitError } from '../errors/RateLimitError.js';
@@ -112,3 +112,12 @@ export async function onRateLimit(manager: RequestManager, rateLimitData: RateLi
throw new RateLimitError(rateLimitData);
}
}
+
+/**
+ * Calculates the default avatar index for a given user id.
+ *
+ * @param userId - The user id to calculate the default avatar index for
+ */
+export function calculateUserDefaultAvatarIndex(userId: Snowflake) {
+ return Number(BigInt(userId) >> 22n) % 6;
+}