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() {