From dfd7b403a932b2e779b0b1619ebd701a86f70d00 Mon Sep 17 00:00:00 2001 From: Rodry <38259440+ImRodry@users.noreply.github.com> Date: Tue, 28 Sep 2021 17:53:37 +0100 Subject: [PATCH] feat(GuildMember): add guild avatars (#5696) Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com> Co-authored-by: Shubham Parihar Co-authored-by: GoldenAngel <50855202+GoldenAngel2@users.noreply.github.com> --- src/structures/GuildMember.js | 35 ++++++++++++++++++++++++++++++++++- src/util/Constants.js | 4 ++++ typings/index.d.ts | 11 +++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index 81779a922af2..0b5c24dcfba0 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -71,6 +71,15 @@ class GuildMember extends Base { } if ('nick' in data) this.nickname = data.nick; + if ('avatar' in data) { + /** + * The guild member's avatar hash + * @type {?string} + */ + this.avatar = data.avatar; + } else if (typeof this.avatar !== 'string') { + this.avatar = null; + } if ('joined_at' in data) this.joinedTimestamp = new Date(data.joined_at).getTime(); if ('premium_since' in data) { this.premiumSinceTimestamp = data.premium_since ? new Date(data.premium_since).getTime() : null; @@ -112,6 +121,26 @@ class GuildMember extends Base { return this.guild.voiceStates.cache.get(this.id) ?? new VoiceState(this.guild, { user_id: this.id }); } + /** + * A link to the member's guild avatar. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} + */ + avatarURL({ format, size, dynamic } = {}) { + if (!this.avatar) return null; + return this.client.rest.cdn.GuildMemberAvatar(this.guild.id, this.id, this.avatar, format, size, dynamic); + } + + /** + * A link to the member's guild avatar if they have one. + * Otherwise, a link to their {@link User#displayAvatarURL} will be returned. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {string} + */ + displayAvatarURL(options) { + return this.avatarURL(options) ?? this.user.displayAvatarURL(options); + } + /** * The time this member joined the guild * @type {?Date} @@ -323,6 +352,7 @@ class GuildMember extends Base { this.guild.id === member.guild.id && this.joinedTimestamp === member.joinedTimestamp && this.nickname === member.nickname && + this.avatar === member.avatar && this.pending === member.pending && (this._roles === member._roles || (this._roles.length === member._roles.length && this._roles.every((role, i) => role === member._roles[i]))) @@ -341,12 +371,15 @@ class GuildMember extends Base { } toJSON() { - return super.toJSON({ + const json = super.toJSON({ guild: 'guildId', user: 'userId', displayName: true, roles: true, }); + json.avatarURL = this.avatarURL(); + json.displayAvatarURL = this.displayAvatarURL(); + return json; } // These are here only for documentation purposes - they are implemented by TextBasedChannel diff --git a/src/util/Constants.js b/src/util/Constants.js index 3761c3f2bdb1..a71d64fa1aec 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -49,6 +49,10 @@ exports.Endpoints = { if (dynamic && hash.startsWith('a_')) format = 'gif'; return makeImageUrl(`${root}/avatars/${userId}/${hash}`, { format, size }); }, + GuildMemberAvatar: (guildId, memberId, hash, format = 'webp', size, dynamic = false) => { + if (dynamic && hash.startsWith('a_')) format = 'gif'; + return makeImageUrl(`${root}/guilds/${guildId}/users/${memberId}/avatars/${hash}`, { format, size }); + }, Banner: (id, hash, format, size, dynamic = false) => { if (dynamic && hash.startsWith('a_')) format = 'gif'; return makeImageUrl(`${root}/banners/${id}/${hash}`, { format, size }); diff --git a/typings/index.d.ts b/typings/index.d.ts index b4004e11d332..dc8dd231eb12 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -873,6 +873,7 @@ export class GuildEmoji extends BaseGuildEmoji { export class GuildMember extends PartialTextBasedChannel(Base) { public constructor(client: Client, data: RawGuildMemberData, guild: Guild); + public avatar: string | null; public readonly bannable: boolean; public deleted: boolean; public readonly displayColor: number; @@ -894,10 +895,12 @@ export class GuildMember extends PartialTextBasedChannel(Base) { public readonly roles: GuildMemberRoleManager; public user: User; public readonly voice: VoiceState; + public avatarURL(options?: ImageURLOptions): string | null; public ban(options?: BanOptions): Promise; public fetch(force?: boolean): Promise; public createDM(force?: boolean): Promise; public deleteDM(): Promise; + public displayAvatarURL(options?: ImageURLOptions): string; public edit(data: GuildMemberEditData, reason?: string): Promise; public kick(reason?: string): Promise; public permissionsIn(channel: GuildChannelResolvable): Readonly; @@ -2297,6 +2300,14 @@ export const Constants: { size: AllowedImageSize, dynamic: boolean, ) => string; + GuildMemberAvatar: ( + guildId: Snowflake, + memberId: Snowflake, + hash: string, + format?: DynamicImageFormat, + size?: AllowedImageSize, + dynamic?: boolean, + ) => string; Icon: ( guildId: Snowflake, hash: string,