Skip to content

Commit

Permalink
feat: support new username system (#9512)
Browse files Browse the repository at this point in the history
* feat: support new username system

* docs(GuildMember#displayName): update

* fix(User#hasNewUsername): use User#discriminator

* feat(User#tag): add new username

* docs(User#tag): update

* docs(User#hasNewUsername): update

* docs: update

* feat(User#defaultAvatarURL): update

* feat: remove some newly added properties

* docs(User#discriminator): update

* docs(User#globalName): bad Copilot

* feat(User#displayName): return display name

* types(User#displayName): not a partial structure

* feat: add `calculateUserDefaultAvatarId` function

* docs(CND#defaultAvatar): update

* docs(CDN#defaultAvatar): update

* feat: change default avatar id to type

* feat: deprecate `User#tag`

* docs(CDN#defaultAvatar): update

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>

* docs(User): update

Co-authored-by: Rodrigo Leitão <38259440+ImRodry@users.noreply.github.com>

* feat: update

* typing: update

Co-authored-by: Jan <66554238+vaporoxx@users.noreply.github.com>

* feat: change the param type to `Snowflake`

---------

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
Co-authored-by: Rodrigo Leitão <38259440+ImRodry@users.noreply.github.com>
Co-authored-by: Jan <66554238+vaporoxx@users.noreply.github.com>
  • Loading branch information
4 people committed Jun 8, 2023
1 parent df8b6e9 commit 1ab60f9
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 12 deletions.
4 changes: 2 additions & 2 deletions packages/discord.js/src/structures/GuildMember.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
45 changes: 41 additions & 4 deletions packages/discord.js/src/structures/User.js
Original file line number Diff line number Diff line change
@@ -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}
Expand Down Expand Up @@ -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
Expand All @@ -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
* <info>`'0'`, or a 4-digit stringified number if they're using the legacy username system</info>
* @type {?string}
*/
this.discriminator = data.discriminator;
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -188,12 +204,33 @@ class User extends Base {
}

/**
* The Discord "tag" (e.g. `hydrabolt#0001`) for this user
* The tag of this user
* <info>This user's username, or their legacy tag (e.g. `hydrabolt#0001`)
* if they're using the legacy username system</info>
* @type {?string}
* @readonly
* @deprecated Use {@link User#username} instead.
*/
get tag() {
return typeof this.username === 'string' ? `${this.username}#${this.discriminator}` : null;
if (!tagDeprecationEmitted) {

This comment has been minimized.

Copy link
@RedGuy12

RedGuy12 Jun 12, 2023

Contributor

why was tag deprecated but not discriminator?

This comment has been minimized.

Copy link
@jaw0r3k

jaw0r3k Jun 12, 2023

Contributor

beacuse it isnt deprecated in api
and idk why tag is

This comment has been minimized.

Copy link
@FabienBounoir

FabienBounoir Aug 4, 2023

Why make the tag deprecate when bot still has a discriminant?

This comment has been minimized.

Copy link
@Jiralite

Jiralite Aug 4, 2023

Author Member
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;
}

/**
Expand Down
3 changes: 3 additions & 0 deletions packages/discord.js/typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<UserFlagsBitField> | 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;
Expand Down
2 changes: 1 addition & 1 deletion packages/rest/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 7 additions & 4 deletions packages/rest/src/lib/CDN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' });
}

/**
Expand Down
11 changes: 10 additions & 1 deletion packages/rest/src/lib/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
}

0 comments on commit 1ab60f9

Please sign in to comment.