From 5d743343744288c6635c807303f23aab0828281b Mon Sep 17 00:00:00 2001
From: Elysia <71698422+aiko-chan-ai@users.noreply.github.com>
Date: Sat, 10 Jun 2023 14:33:16 +0700
Subject: [PATCH 1/8] feat: support new username system (v13)
---
src/structures/GuildMember.js | 4 ++--
src/structures/User.js | 44 +++++++++++++++++++++++++++++++----
src/util/Constants.js | 2 +-
src/util/Util.js | 10 ++++++++
typings/index.d.ts | 5 +++-
5 files changed, 57 insertions(+), 8 deletions(-)
diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js
index 2fff24990d37..7cbd748307f4 100644
--- a/src/structures/GuildMember.js
+++ b/src/structures/GuildMember.js
@@ -259,12 +259,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/src/structures/User.js b/src/structures/User.js
index dd1880edec3d..49223a10f988 100644
--- a/src/structures/User.js
+++ b/src/structures/User.js
@@ -1,10 +1,14 @@
'use strict';
+const process = require('node:process');
const Base = require('./Base');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const { Error } = require('../errors');
const SnowflakeUtil = require('../util/SnowflakeUtil');
const UserFlags = require('../util/UserFlags');
+const Util = require('../util/Util');
+
+let tagDeprecationEmitted = false;
/**
* Represents a user on Discord.
@@ -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;
@@ -155,7 +170,8 @@ class User extends Base {
* @readonly
*/
get defaultAvatarURL() {
- return this.client.rest.cdn.DefaultAvatar(this.discriminator % 5);
+ const index = this.discriminator === '0' ? Util.calculateUserDefaultAvatarIndex(this.id) : this.discriminator % 5;
+ return this.client.rest.cdn.DefaultAvatar(index);
}
/**
@@ -193,12 +209,32 @@ 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}
+ * @deprecated Use {@link User#username} instead.
* @readonly
*/
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/src/util/Constants.js b/src/util/Constants.js
index 63cd4d3e2011..e3a86cf058b6 100644
--- a/src/util/Constants.js
+++ b/src/util/Constants.js
@@ -68,7 +68,7 @@ exports.Endpoints = {
return {
Emoji: (emojiId, format = 'webp') => `${root}/emojis/${emojiId}.${format}`,
Asset: name => `${root}/assets/${name}`,
- DefaultAvatar: discriminator => `${root}/embed/avatars/${discriminator}.png`,
+ DefaultAvatar: index => `${root}/embed/avatars/${index}.png`,
Avatar: (userId, hash, format, size, dynamic = false) => {
if (dynamic && hash.startsWith('a_')) format = 'gif';
return makeImageUrl(`${root}/avatars/${userId}/${hash}`, { format, size });
diff --git a/src/util/Util.js b/src/util/Util.js
index 205e9a024bd1..6f6ac687d2ca 100644
--- a/src/util/Util.js
+++ b/src/util/Util.js
@@ -741,6 +741,16 @@ class Util extends null {
emoji_name: defaultReaction.name,
};
}
+
+ /**
+ * Calculates the default avatar index for a given user id.
+ * @param {Snowflake} userId - The user id to calculate the default avatar index for
+ * @returns {number}
+ * @ignore
+ */
+ static calculateUserDefaultAvatarIndex(userId) {
+ return Number(BigInt(userId) >> 22n) % 6;
+ }
}
module.exports = Util;
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 116d4fbb7779..e54b53065044 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -2703,13 +2703,16 @@ export class User extends PartialTextBasedChannel(Base) {
public readonly createdAt: Date;
public readonly createdTimestamp: number;
public discriminator: string;
+ public get displayName(): string;
public readonly defaultAvatarURL: string;
public readonly dmChannel: DMChannel | null;
public flags: Readonly | null;
+ public globalName: string | null;
public readonly hexAccentColor: HexColorString | null | undefined;
public id: Snowflake;
public readonly partial: false;
public system: boolean;
+ /** @deprecated Use {@link User#username} instead. */
public readonly tag: string;
public username: string;
public avatarURL(options?: ImageURLOptions): string | null;
@@ -3093,7 +3096,7 @@ export const Constants: {
dynamic: boolean,
): string;
Banner(id: Snowflake, hash: string, format: DynamicImageFormat, size: AllowedImageSize, dynamic: boolean): string;
- DefaultAvatar(discriminator: number): string;
+ DefaultAvatar(index: number): string;
DiscoverySplash(guildId: Snowflake, hash: string, format: AllowedImageFormat, size: AllowedImageSize): string;
Emoji(emojiId: Snowflake, format: DynamicImageFormat): string;
GDMIcon(channelId: Snowflake, hash: string, format: AllowedImageFormat, size: AllowedImageSize): string;
From 5b2212dd43369c53e9645ecc000b9ad1fdd334ae Mon Sep 17 00:00:00 2001
From: Elysia <71698422+aiko-chan-ai@users.noreply.github.com>
Date: Sat, 10 Jun 2023 14:34:33 +0700
Subject: [PATCH 2/8] fix(User): check global name in equals
---
src/structures/User.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/structures/User.js b/src/structures/User.js
index 49223a10f988..1a8a037b20e4 100644
--- a/src/structures/User.js
+++ b/src/structures/User.js
@@ -276,6 +276,7 @@ class User extends Base {
this.id === user.id &&
this.username === user.username &&
this.discriminator === user.discriminator &&
+ this.globalName === user.globalName &&
this.avatar === user.avatar &&
this.flags?.bitfield === user.flags?.bitfield &&
this.banner === user.banner &&
@@ -295,6 +296,7 @@ class User extends Base {
this.id === user.id &&
this.username === user.username &&
this.discriminator === user.discriminator &&
+ this.globalName === user.global_name &&
this.avatar === user.avatar &&
this.flags?.bitfield === user.public_flags &&
('banner' in user ? this.banner === user.banner : true) &&
From d73a28b91e42f241d6091c82fdfc82871b1af0bb Mon Sep 17 00:00:00 2001
From: Elysia <71698422+aiko-chan-ai@users.noreply.github.com>
Date: Sat, 10 Jun 2023 18:43:03 +0700
Subject: [PATCH 3/8] Update typings/index.d.ts
Co-authored-by: Jaw0r3k
---
typings/index.d.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/typings/index.d.ts b/typings/index.d.ts
index e54b53065044..a99082f97c5c 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -2703,7 +2703,7 @@ export class User extends PartialTextBasedChannel(Base) {
public readonly createdAt: Date;
public readonly createdTimestamp: number;
public discriminator: string;
- public get displayName(): string;
+ public readonly displayName: string;
public readonly defaultAvatarURL: string;
public readonly dmChannel: DMChannel | null;
public flags: Readonly | null;
From 0aed6c63aa09e3cb623cf5d06758ee861e1c3390 Mon Sep 17 00:00:00 2001
From: Elysia <71698422+aiko-chan-ai@users.noreply.github.com>
Date: Sat, 10 Jun 2023 20:11:59 +0700
Subject: [PATCH 4/8] Update src/util/Util.js
Co-authored-by: Jaw0r3k
---
src/util/Util.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/util/Util.js b/src/util/Util.js
index 6f6ac687d2ca..34e3e24fe7ea 100644
--- a/src/util/Util.js
+++ b/src/util/Util.js
@@ -746,7 +746,6 @@ class Util extends null {
* Calculates the default avatar index for a given user id.
* @param {Snowflake} userId - The user id to calculate the default avatar index for
* @returns {number}
- * @ignore
*/
static calculateUserDefaultAvatarIndex(userId) {
return Number(BigInt(userId) >> 22n) % 6;
From 63d47b764ec5a8d869ea76eb9201ec261c548c7c Mon Sep 17 00:00:00 2001
From: Elysia <71698422+aiko-chan-ai@users.noreply.github.com>
Date: Sun, 11 Jun 2023 12:00:45 +0700
Subject: [PATCH 5/8] typing
---
typings/index.d.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/typings/index.d.ts b/typings/index.d.ts
index a99082f97c5c..926aa412c1a5 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -2788,6 +2788,7 @@ export class Util extends null {
public static splitMessage(text: string, options?: SplitOptions): string[];
/** @deprecated This will be removed in the next major version. */
public static resolveAutoArchiveMaxLimit(guild: Guild): Exclude;
+ public static calculateUserDefaultAvatarIndex(userId: Snowflake): number;
}
export class Formatters extends null {
From 9c75ab8febb92efb795f56151bc92f11bf8e21c8 Mon Sep 17 00:00:00 2001
From: Elysia <71698422+aiko-chan-ai@users.noreply.github.com>
Date: Sat, 8 Jul 2023 08:00:53 +0700
Subject: [PATCH 6/8] Update User.js
---
src/structures/User.js | 7 -------
1 file changed, 7 deletions(-)
diff --git a/src/structures/User.js b/src/structures/User.js
index 1a8a037b20e4..3eb980c95354 100644
--- a/src/structures/User.js
+++ b/src/structures/User.js
@@ -1,6 +1,5 @@
'use strict';
-const process = require('node:process');
const Base = require('./Base');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const { Error } = require('../errors');
@@ -8,8 +7,6 @@ const SnowflakeUtil = require('../util/SnowflakeUtil');
const UserFlags = require('../util/UserFlags');
const Util = require('../util/Util');
-let tagDeprecationEmitted = false;
-
/**
* Represents a user on Discord.
* @implements {TextBasedChannel}
@@ -217,10 +214,6 @@ class User extends Base {
* @readonly
*/
get tag() {
- 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
From 941c9b6102f29ae51aa08a80a6751b15c2303d53 Mon Sep 17 00:00:00 2001
From: Elysia <71698422+aiko-chan-ai@users.noreply.github.com>
Date: Sat, 8 Jul 2023 08:05:45 +0700
Subject: [PATCH 7/8] Update index.d.ts
---
typings/index.d.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 926aa412c1a5..b53d3e6676c1 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -2712,7 +2712,6 @@ export class User extends PartialTextBasedChannel(Base) {
public id: Snowflake;
public readonly partial: false;
public system: boolean;
- /** @deprecated Use {@link User#username} instead. */
public readonly tag: string;
public username: string;
public avatarURL(options?: ImageURLOptions): string | null;
From c522a91f625c3956be4e4bfd25120c64035c3f08 Mon Sep 17 00:00:00 2001
From: Elysia <71698422+aiko-chan-ai@users.noreply.github.com>
Date: Sat, 8 Jul 2023 22:18:17 +0700
Subject: [PATCH 8/8] Update User.js
---
src/structures/User.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/structures/User.js b/src/structures/User.js
index 3eb980c95354..a991a80c5109 100644
--- a/src/structures/User.js
+++ b/src/structures/User.js
@@ -210,7 +210,6 @@ class User extends Base {
* This user's username, or their legacy tag (e.g. `hydrabolt#0001`)
* if they're using the legacy username system
* @type {?string}
- * @deprecated Use {@link User#username} instead.
* @readonly
*/
get tag() {