diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index 0bc84414f5ba..d2ac4c24ec37 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -83,6 +83,15 @@ class GuildMember extends Base { } if ('nick' in data) this.nickname = data.nick; + if ('avatar' in data) { + /** + * The ID of the member's guild avatar + * @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 === null ? null : new Date(data.premium_since).getTime(); @@ -136,6 +145,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} @@ -361,6 +390,7 @@ class GuildMember extends Base { this.lastMessageID === member.lastMessageID && this.lastMessageChannelID === member.lastMessageChannelID && 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]))) @@ -379,7 +409,7 @@ class GuildMember extends Base { } toJSON() { - return super.toJSON({ + const json = super.toJSON({ guild: 'guildID', user: 'userID', displayName: true, @@ -387,6 +417,9 @@ class GuildMember extends Base { lastMessageID: false, 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 e4e158bc0ea7..fe8df2bd4734 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -159,6 +159,10 @@ exports.Endpoints = { if (dynamic) format = hash.startsWith('a_') ? 'gif' : format; return makeImageUrl(`${root}/avatars/${userID}/${hash}`, { format, size }); }, + GuildMemberAvatar: (guildID, memberID, hash, format = 'webp', size, dynamic = false) => { + if (dynamic) format = hash.startsWith('a_') ? 'gif' : format; + return makeImageUrl(`${root}/guilds/${guildID}/users/${memberID}/avatars/${hash}`, { format, size }); + }, Banner: (guildID, hash, format = 'webp', size) => makeImageUrl(`${root}/banners/${guildID}/${hash}`, { format, size }), Icon: (guildID, hash, format = 'webp', size, dynamic = false) => { diff --git a/typings/index.d.ts b/typings/index.d.ts index ec8f6f7e0617..af2d6b986289 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -548,6 +548,7 @@ declare module 'discord.js' { } type AllowedImageFormat = 'webp' | 'png' | 'jpg' | 'jpeg' | 'gif'; + type AllowedImageSize = 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096; export const Constants: { Package: { @@ -581,27 +582,59 @@ declare module 'discord.js' { Avatar: ( userID: Snowflake | number, hash: string, - format: 'default' | AllowedImageFormat, - size: number, + format: AllowedImageFormat, + size: AllowedImageSize, + ) => string; + GuildMemberAvatar: ( + guildId: Snowflake | number, + userId: Snowflake | number, + hash: string, + format: AllowedImageFormat, + size: AllowedImageSize, + ) => string; + Banner: ( + guildID: Snowflake | number, + hash: string, + format: AllowedImageFormat, + size: AllowedImageSize, ) => string; - Banner: (guildID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; - Icon: ( + Icon: (userID: Snowflake | number, hash: string, format: AllowedImageFormat, size: AllowedImageSize) => string; + AppIcon: ( userID: Snowflake | number, hash: string, - format: 'default' | AllowedImageFormat, - size: number, + format: AllowedImageFormat, + size: AllowedImageSize, + ) => string; + AppAsset: ( + userID: Snowflake | number, + hash: string, + format: AllowedImageFormat, + size: AllowedImageSize, + ) => string; + GDMIcon: ( + userID: Snowflake | number, + hash: string, + format: AllowedImageFormat, + size: AllowedImageSize, + ) => string; + Splash: ( + guildID: Snowflake | number, + hash: string, + format: AllowedImageFormat, + size: AllowedImageSize, ) => string; - AppIcon: (userID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; - AppAsset: (userID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; - GDMIcon: (userID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; - Splash: (guildID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; DiscoverySplash: ( guildID: Snowflake | number, hash: string, format: AllowedImageFormat, - size: number, + size: AllowedImageSize, + ) => string; + TeamIcon: ( + teamID: Snowflake | number, + hash: string, + format: AllowedImageFormat, + size: AllowedImageSize, ) => string; - TeamIcon: (teamID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; }; }; WSCodes: { @@ -1037,6 +1070,7 @@ declare module 'discord.js' { constructor(client: Client, data: unknown, guild: Guild); public readonly bannable: boolean; public deleted: boolean; + public avatar: string | null; public readonly displayColor: number; public readonly displayHexColor: string; public readonly displayName: string; @@ -1057,6 +1091,8 @@ declare module 'discord.js' { public readonly roles: GuildMemberRoleManager; public user: User; public readonly voice: VoiceState; + public avatarURL(options?: ImageURLOptions): string | null; + public displayAvatarURL(options?: ImageURLOptions): string; public ban(options?: BanOptions): Promise; public fetch(force?: boolean): Promise; public createDM(force?: boolean): Promise;