From 85025d65ccebf2f970ed4aa0e731d2e35bd7fad1 Mon Sep 17 00:00:00 2001 From: advaith Date: Thu, 28 Mar 2024 20:42:03 -0700 Subject: [PATCH 01/10] feat(Interactions): member objects for uncached guilds --- .../src/structures/BaseInteraction.js | 5 +- .../src/structures/CommandInteraction.js | 7 +- .../src/structures/UncachedGuildMember.js | 332 ++++++++++++++++++ packages/discord.js/typings/index.d.ts | 32 ++ 4 files changed, 374 insertions(+), 2 deletions(-) create mode 100644 packages/discord.js/src/structures/UncachedGuildMember.js diff --git a/packages/discord.js/src/structures/BaseInteraction.js b/packages/discord.js/src/structures/BaseInteraction.js index 09e717bc9ead..6a19d78613c2 100644 --- a/packages/discord.js/src/structures/BaseInteraction.js +++ b/packages/discord.js/src/structures/BaseInteraction.js @@ -5,6 +5,7 @@ const { Collection } = require('@discordjs/collection'); const { DiscordSnowflake } = require('@sapphire/snowflake'); const { InteractionType, ApplicationCommandType, ComponentType } = require('discord-api-types/v10'); const Base = require('./Base'); +const { UncachedGuildMember } = require('./UncachedGuildMember'); const { SelectMenuTypes } = require('../util/Constants'); const PermissionsBitField = require('../util/PermissionsBitField'); @@ -65,7 +66,9 @@ class BaseInteraction extends Base { * If this interaction was sent in a guild, the member which sent it * @type {?(GuildMember|APIInteractionGuildMember)} */ - this.member = data.member ? this.guild?.members._add(data.member) ?? data.member : null; + this.member = data.member + ? this.guild?.members._add(data.member) ?? new UncachedGuildMember(this.client, data.member, this.guildId) + : null; /** * The version diff --git a/packages/discord.js/src/structures/CommandInteraction.js b/packages/discord.js/src/structures/CommandInteraction.js index 0d435deeb446..55de55ca75a1 100644 --- a/packages/discord.js/src/structures/CommandInteraction.js +++ b/packages/discord.js/src/structures/CommandInteraction.js @@ -3,6 +3,7 @@ const Attachment = require('./Attachment'); const BaseInteraction = require('./BaseInteraction'); const InteractionWebhook = require('./InteractionWebhook'); +const { UncachedGuildMember } = require('./UncachedGuildMember'); const InteractionResponses = require('./interfaces/InteractionResponses'); /** @@ -129,7 +130,11 @@ class CommandInteraction extends BaseInteraction { if (user) result.user = this.client.users._add(user); const member = resolved.members?.[option.value]; - if (member) result.member = this.guild?.members._add({ user, ...member }) ?? member; + if (member) { + result.member = + this.guild?.members._add({ user, ...member }) ?? + new UncachedGuildMember(this.client, { user, ...member }, this.guildId); +} const channel = resolved.channels?.[option.value]; if (channel) result.channel = this.client.channels._add(channel, this.guild) ?? channel; diff --git a/packages/discord.js/src/structures/UncachedGuildMember.js b/packages/discord.js/src/structures/UncachedGuildMember.js new file mode 100644 index 000000000000..b4262271774b --- /dev/null +++ b/packages/discord.js/src/structures/UncachedGuildMember.js @@ -0,0 +1,332 @@ +'use strict'; + +const Base = require('./Base'); +const TextBasedChannel = require('./interfaces/TextBasedChannel'); +const { GuildMemberFlagsBitField } = require('../util/GuildMemberFlagsBitField'); +const PermissionsBitField = require('../util/PermissionsBitField'); + +/** + * Represents a member of a guild on Discord. Used in interactions from guilds that aren't cached. + * Backwards compatible with {@link APIGuildMember}. + * @implements {TextBasedChannel} + * @extends {Base} + */ +class UncachedGuildMember extends Base { + constructor(client, data, guildId) { + super(client); + + /** + * The guild that this member is part of + * @type {Guild} + */ + this.guildId = guildId; + + if ('user' in data) { + /** + * The user that this guild member instance represents + * @type {?User} + */ + this.user = this.client.users._add(data.user, true); + } + + /** + * The nickname of this member, if they have one + * @type {?string} + */ + this.nickname = data.nick; + + /** + * The guild member's avatar hash + * @type {?string} + */ + this.avatar = data.avatar ?? null; + + /** + * The role ids of the member + * @name UncachedGuildMember#roleIds + * @type {Snowflake[]} + */ + this.roleIds = data.roles; + + /** + * The timestamp the member joined the guild at + * @type {number} + */ + this.joinedTimestamp = Date.parse(data.joined_at); + + /** + * The timestamp the member joined the guild at, as an ISO8601 timestamp + * @type {string} + * @deprecated Use {@link UncachedGuildMember#joinedTimestamp} or {@link UncachedGuildMember#joinedAt} instead + */ + this.joined_at = data.joined_at; + + /** + * The last timestamp this member started boosting the guild + * @type {?number} + */ + this.premiumSinceTimestamp = data.premium_since ? Date.parse(data.premium_since) : null; + + /** + * The last timestamp this member started boosting the guild, as an ISO8601 timestamp + * @type {?string} + * @deprecated Use {@link UncachedGuildMember#premiumSinceTimestamp} + * or {@link UncachedGuildMember#premiumSince} instead + */ + this.premium_since = data.premium_since; + + /** + * Whether the user is deafened in voice channels + * @type {?boolean} + * @deprecated + */ + this.deaf = data.deaf; + + /** + * Whether the user is muted in voice channels + * @type {?boolean} + * @deprecated + */ + this.mute = data.mute; + + /** + * The flags of this member + * @type {Readonly} + */ + this.parsedFlags = new GuildMemberFlagsBitField(data.flags).freeze(); + + /** + * The raw flags of this member + * @type {number} + * @deprecated Use {@link UncachedGuildMember#parsedFlags} instead. + * This field will be replaced with parsedFlags in the future. + */ + this.flags = data.flags; + + /** + * Whether this member has yet to pass the guild's membership gate + * @type {?boolean} + */ + this.pending = data.pending; + + /** + * The total permissions of the member in this channel, including overwrites + * @type {Readonly} + */ + this.parsedPermissions = new PermissionsBitField(data.permissions).freeze(); + + /** + * Raw total permissions of the member in this channel, including overwrites + * @type {?string} + * @deprecated Use {@link UncachedGuildMember#parsedPermissions} instead. + * This field will be replaced with parsedPermissions in the future. + */ + this.permissions = data.permissions; + + /** + * The timestamp this member's timeout will be removed + * @type {?number} + */ + this.communicationDisabledUntilTimestamp = + data.communication_disabled_until && Date.parse(data.communication_disabled_until); + + /** + * The timestamp this member's timeout will be removed, as an ISO8601 timestamp + * @type {?string} + * @deprecated Use {@link UncachedGuildMember#communicationDisabledUntilTimestamp} + * or {@link UncachedGuildMember#communicationDisabledUntil} instead + */ + this.communication_disabled_until = data.communication_disabled_until; + + /** + * Whether this UncachedGuildMember is a partial (always true, as it is a partial GuildMember) + * @type {boolean} + * @readonly + */ + this.partial = true; + } + + /** + * A link to the member's guild avatar. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + avatarURL(options = {}) { + return this.avatar && this.client.rest.cdn.guildMemberAvatar(this.guildId, this.id, this.avatar, options); + } + + /** + * 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} + * @readonly + */ + get joinedAt() { + return this.joinedTimestamp && new Date(this.joinedTimestamp); + } + + /** + * The time this member's timeout will be removed + * @type {?Date} + * @readonly + */ + get communicationDisabledUntil() { + return this.communicationDisabledUntilTimestamp && new Date(this.communicationDisabledUntilTimestamp); + } + + /** + * The last time this member started boosting the guild + * @type {?Date} + * @readonly + */ + get premiumSince() { + return this.premiumSinceTimestamp && new Date(this.premiumSinceTimestamp); + } + + /** + * The member's id + * @type {Snowflake} + * @readonly + */ + get id() { + return this.user.id; + } + + /** + * The DM between the client's user and this member + * @type {?DMChannel} + * @readonly + */ + get dmChannel() { + return this.client.users.dmChannel(this.id); + } + + /** + * 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.displayName; + } + + /** + * The nickname of the member + * @name UncachedGuildMember#nick + * @type {?string} + * @deprecated Use {@link UncachedGuildMember#nickname} instead + */ + get nick() { + return this.nickname; + } + + /** + * The role ids of the member + * @name UncachedGuildMember#roles + * @type {Snowflake[]} + * @deprecated Use {@link UncachedGuildMember#roleIds} instead + */ + get roles() { + return this.roleIds; + } + + /** + * Whether this member is currently timed out + * @returns {boolean} + */ + isCommunicationDisabled() { + return this.communicationDisabledUntilTimestamp > Date.now(); + } + + /** + * Creates a DM channel between the client and this member. + * @param {boolean} [force=false] Whether to skip the cache check and request the API + * @returns {Promise} + */ + createDM(force = false) { + return this.user.createDM(force); + } + + /** + * Deletes any DMs with this member. + * @returns {Promise} + */ + deleteDM() { + return this.user.deleteDM(); + } + + /** + * Whether this guild member equals another guild member. It compares all properties, so for most + * comparison it is advisable to just compare `member.id === member2.id` as it is significantly faster + * and is often what most users need. + * @param {UncachedGuildMember} member The member to compare with + * @returns {boolean} + */ + equals(member) { + return ( + member instanceof this.constructor && + this.id === member.id && + this.partial === member.partial && + this.guildId === member.guildId && + this.joinedTimestamp === member.joinedTimestamp && + this.nickname === member.nickname && + this.avatar === member.avatar && + this.pending === member.pending && + this.communicationDisabledUntilTimestamp === member.communicationDisabledUntilTimestamp && + this.parsedFlags.bitfield === member.parsedFlags.bitfield && + (this.roleIds === member.roleIds || + (this.roleIds.length === member.roleIds.length && this.roleIds.every((role, i) => role === member.roleIds[i]))) + ); + } + + /** + * When concatenated with a string, this automatically returns the user's mention + * instead of the UncachedGuildMember object. + * @returns {string} + * @example + * // Logs: Hello from <@123456789012345678>! + * console.log(`Hello from ${member}!`); + */ + toString() { + return this.user.toString(); + } + + toJSON() { + const json = super.toJSON({ + guild: 'guildId', + user: 'userId', + displayName: true, + roles: true, + }); + json.avatarURL = this.avatarURL(); + json.displayAvatarURL = this.displayAvatarURL(); + return json; + } +} + +/** + * Sends a message to this user. + * @method send + * @memberof UncachedGuildMember + * @instance + * @param {string|MessagePayload|MessageCreateOptions} options The options to provide + * @returns {Promise} + * @example + * // Send a direct message + * UncachedGuildMember.send('Hello!') + * .then(message => console.log(`Sent message: ${message.content} to ${guildMember.displayName}`)) + * .catch(console.error); + */ + +TextBasedChannel.applyToClass(UncachedGuildMember); + +exports.UncachedGuildMember = UncachedGuildMember; diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index aa96d1a9136b..6e026a70fe85 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -1642,6 +1642,38 @@ export class GuildMember extends Base { public valueOf(): string; } +export class UncachedGuildMember extends Base { + private constructor(client: Client, data: RawGuildMemberData, guildId: Snowflake); + public avatar: string | null; + public get dmChannel(): DMChannel | null; + public get displayName(): string; + public get id(): Snowflake; + public pending: boolean; + public get communicationDisabledUntil(): Date | null; + public communicationDisabledUntilTimestamp: number | null; + public parsedFlags: Readonly; + public get joinedAt(): Date | null; + public joinedTimestamp: number | null; + public nickname: string | null; + public partial: true; + public parsedPermissions: Readonly; + public get premiumSince(): Date | null; + public premiumSinceTimestamp: number | null; + public roleIds: Snowflake[]; + public user: User; + public avatarURL(options?: ImageURLOptions): string | null; + public createDM(force?: boolean): Promise; + public deleteDM(): Promise; + public displayAvatarURL(options?: ImageURLOptions): string; + public isCommunicationDisabled(): this is GuildMember & { + communicationDisabledUntilTimestamp: number; + readonly communicationDisabledUntil: Date; + }; + public toJSON(): unknown; + public toString(): UserMention; + public valueOf(): string; +} + export class GuildOnboarding extends Base { private constructor(client: Client, data: RESTGetAPIGuildOnboardingResult); public get guild(): Guild; From e205862b53557e2dc8ed271db4586cdc02654095 Mon Sep 17 00:00:00 2001 From: advaith Date: Tue, 2 Apr 2024 21:34:07 -0700 Subject: [PATCH 02/10] fix: fix review issues Co-Authored-By: Vlad Frangu Co-Authored-By: Qjuh <76154676+Qjuh@users.noreply.github.com> --- .../src/structures/CommandInteraction.js | 4 +-- .../discord.js/src/structures/GuildMember.js | 2 +- .../src/structures/UncachedGuildMember.js | 28 ++++++++----------- .../structures/interfaces/TextBasedChannel.js | 3 +- packages/discord.js/typings/index.d.ts | 10 ++++++- 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/packages/discord.js/src/structures/CommandInteraction.js b/packages/discord.js/src/structures/CommandInteraction.js index 55de55ca75a1..91c04893a84e 100644 --- a/packages/discord.js/src/structures/CommandInteraction.js +++ b/packages/discord.js/src/structures/CommandInteraction.js @@ -131,10 +131,10 @@ class CommandInteraction extends BaseInteraction { const member = resolved.members?.[option.value]; if (member) { - result.member = + result.member = this.guild?.members._add({ user, ...member }) ?? new UncachedGuildMember(this.client, { user, ...member }, this.guildId); -} + } const channel = resolved.channels?.[option.value]; if (channel) result.channel = this.client.channels._add(channel, this.guild) ?? channel; diff --git a/packages/discord.js/src/structures/GuildMember.js b/packages/discord.js/src/structures/GuildMember.js index 2a39f3f59940..0ed966ac6d2c 100644 --- a/packages/discord.js/src/structures/GuildMember.js +++ b/packages/discord.js/src/structures/GuildMember.js @@ -372,7 +372,7 @@ class GuildMember extends Base { } /** - * Deletes any DMs with this member. + * Deletes a DM channel (if one exists) between the client and the member. Resolves with the channel if successful. * @returns {Promise} */ deleteDM() { diff --git a/packages/discord.js/src/structures/UncachedGuildMember.js b/packages/discord.js/src/structures/UncachedGuildMember.js index b4262271774b..367125e346c0 100644 --- a/packages/discord.js/src/structures/UncachedGuildMember.js +++ b/packages/discord.js/src/structures/UncachedGuildMember.js @@ -16,18 +16,16 @@ class UncachedGuildMember extends Base { super(client); /** - * The guild that this member is part of - * @type {Guild} + * The ID of the guild that this member is part of + * @type {string} */ this.guildId = guildId; - if ('user' in data) { - /** - * The user that this guild member instance represents - * @type {?User} - */ - this.user = this.client.users._add(data.user, true); - } + /** + * The user that this guild member instance represents + * @type {User} + */ + this.user = this.client.users._add(data.user, true); /** * The nickname of this member, if they have one @@ -39,7 +37,7 @@ class UncachedGuildMember extends Base { * The guild member's avatar hash * @type {?string} */ - this.avatar = data.avatar ?? null; + this.avatar = data.avatar; /** * The role ids of the member @@ -77,15 +75,13 @@ class UncachedGuildMember extends Base { /** * Whether the user is deafened in voice channels - * @type {?boolean} - * @deprecated + * @type {boolean | undefined} */ this.deaf = data.deaf; /** * Whether the user is muted in voice channels - * @type {?boolean} - * @deprecated + * @type {boolean | undefined} */ this.mute = data.mute; @@ -257,7 +253,7 @@ class UncachedGuildMember extends Base { } /** - * Deletes any DMs with this member. + * Deletes a DM channel (if one exists) between the client and the member. Resolves with the channel if successful. * @returns {Promise} */ deleteDM() { @@ -302,7 +298,7 @@ class UncachedGuildMember extends Base { toJSON() { const json = super.toJSON({ - guild: 'guildId', + guildId: true, user: 'userId', displayName: true, roles: true, diff --git a/packages/discord.js/src/structures/interfaces/TextBasedChannel.js b/packages/discord.js/src/structures/interfaces/TextBasedChannel.js index ea914465520b..b960b1dd05d7 100644 --- a/packages/discord.js/src/structures/interfaces/TextBasedChannel.js +++ b/packages/discord.js/src/structures/interfaces/TextBasedChannel.js @@ -141,8 +141,9 @@ class TextBasedChannel { async send(options) { const User = require('../User'); const { GuildMember } = require('../GuildMember'); + const { UncachedGuildMember } = require('../UncachedGuildMember'); - if (this instanceof User || this instanceof GuildMember) { + if (this instanceof User || this instanceof GuildMember || this instanceof UncachedGuildMember) { const dm = await this.createDM(); return dm.send(options); } diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 6e026a70fe85..02e47a263bf4 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -1642,11 +1642,17 @@ export class GuildMember extends Base { public valueOf(): string; } +export interface UncachedGuildMember extends PartialTextBasedChannelFields {} export class UncachedGuildMember extends Base { - private constructor(client: Client, data: RawGuildMemberData, guildId: Snowflake); + private constructor( + client: Client, + data: APIInteractionGuildMember | APIInteractionDataResolvedGuildMember, + guildId: Snowflake, + ); public avatar: string | null; public get dmChannel(): DMChannel | null; public get displayName(): string; + public guildId: string; public get id(): Snowflake; public pending: boolean; public get communicationDisabledUntil(): Date | null; @@ -1655,6 +1661,8 @@ export class UncachedGuildMember extends Base { public get joinedAt(): Date | null; public joinedTimestamp: number | null; public nickname: string | null; + public mute?: boolean; + public deaf?: boolean; public partial: true; public parsedPermissions: Readonly; public get premiumSince(): Date | null; From e9cb3009b4a187aee97a3a000e80cbe25283b547 Mon Sep 17 00:00:00 2001 From: advaith Date: Thu, 4 Apr 2024 15:59:38 -0700 Subject: [PATCH 03/10] refactor: default export --- packages/discord.js/src/structures/UncachedGuildMember.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/discord.js/src/structures/UncachedGuildMember.js b/packages/discord.js/src/structures/UncachedGuildMember.js index 367125e346c0..06b6311a14f7 100644 --- a/packages/discord.js/src/structures/UncachedGuildMember.js +++ b/packages/discord.js/src/structures/UncachedGuildMember.js @@ -325,4 +325,4 @@ class UncachedGuildMember extends Base { TextBasedChannel.applyToClass(UncachedGuildMember); -exports.UncachedGuildMember = UncachedGuildMember; +module.exports = UncachedGuildMember; From 1c561a11d55a3617af5546140b78a8882751baab Mon Sep 17 00:00:00 2001 From: advaith Date: Tue, 31 Dec 2024 00:49:57 -0800 Subject: [PATCH 04/10] refactor: remove deprecated api object compat fields --- .../src/structures/UncachedGuildMember.js | 66 +------------------ packages/discord.js/typings/index.d.ts | 4 +- 2 files changed, 5 insertions(+), 65 deletions(-) diff --git a/packages/discord.js/src/structures/UncachedGuildMember.js b/packages/discord.js/src/structures/UncachedGuildMember.js index 06b6311a14f7..35aec6428719 100644 --- a/packages/discord.js/src/structures/UncachedGuildMember.js +++ b/packages/discord.js/src/structures/UncachedGuildMember.js @@ -7,7 +7,6 @@ const PermissionsBitField = require('../util/PermissionsBitField'); /** * Represents a member of a guild on Discord. Used in interactions from guilds that aren't cached. - * Backwards compatible with {@link APIGuildMember}. * @implements {TextBasedChannel} * @extends {Base} */ @@ -52,27 +51,12 @@ class UncachedGuildMember extends Base { */ this.joinedTimestamp = Date.parse(data.joined_at); - /** - * The timestamp the member joined the guild at, as an ISO8601 timestamp - * @type {string} - * @deprecated Use {@link UncachedGuildMember#joinedTimestamp} or {@link UncachedGuildMember#joinedAt} instead - */ - this.joined_at = data.joined_at; - /** * The last timestamp this member started boosting the guild * @type {?number} */ this.premiumSinceTimestamp = data.premium_since ? Date.parse(data.premium_since) : null; - /** - * The last timestamp this member started boosting the guild, as an ISO8601 timestamp - * @type {?string} - * @deprecated Use {@link UncachedGuildMember#premiumSinceTimestamp} - * or {@link UncachedGuildMember#premiumSince} instead - */ - this.premium_since = data.premium_since; - /** * Whether the user is deafened in voice channels * @type {boolean | undefined} @@ -89,15 +73,7 @@ class UncachedGuildMember extends Base { * The flags of this member * @type {Readonly} */ - this.parsedFlags = new GuildMemberFlagsBitField(data.flags).freeze(); - - /** - * The raw flags of this member - * @type {number} - * @deprecated Use {@link UncachedGuildMember#parsedFlags} instead. - * This field will be replaced with parsedFlags in the future. - */ - this.flags = data.flags; + this.flags = new GuildMemberFlagsBitField(data.flags).freeze(); /** * Whether this member has yet to pass the guild's membership gate @@ -109,15 +85,7 @@ class UncachedGuildMember extends Base { * The total permissions of the member in this channel, including overwrites * @type {Readonly} */ - this.parsedPermissions = new PermissionsBitField(data.permissions).freeze(); - - /** - * Raw total permissions of the member in this channel, including overwrites - * @type {?string} - * @deprecated Use {@link UncachedGuildMember#parsedPermissions} instead. - * This field will be replaced with parsedPermissions in the future. - */ - this.permissions = data.permissions; + this.permissions = new PermissionsBitField(data.permissions).freeze(); /** * The timestamp this member's timeout will be removed @@ -126,14 +94,6 @@ class UncachedGuildMember extends Base { this.communicationDisabledUntilTimestamp = data.communication_disabled_until && Date.parse(data.communication_disabled_until); - /** - * The timestamp this member's timeout will be removed, as an ISO8601 timestamp - * @type {?string} - * @deprecated Use {@link UncachedGuildMember#communicationDisabledUntilTimestamp} - * or {@link UncachedGuildMember#communicationDisabledUntil} instead - */ - this.communication_disabled_until = data.communication_disabled_until; - /** * Whether this UncachedGuildMember is a partial (always true, as it is a partial GuildMember) * @type {boolean} @@ -215,26 +175,6 @@ class UncachedGuildMember extends Base { return this.nickname ?? this.user.displayName; } - /** - * The nickname of the member - * @name UncachedGuildMember#nick - * @type {?string} - * @deprecated Use {@link UncachedGuildMember#nickname} instead - */ - get nick() { - return this.nickname; - } - - /** - * The role ids of the member - * @name UncachedGuildMember#roles - * @type {Snowflake[]} - * @deprecated Use {@link UncachedGuildMember#roleIds} instead - */ - get roles() { - return this.roleIds; - } - /** * Whether this member is currently timed out * @returns {boolean} @@ -278,7 +218,7 @@ class UncachedGuildMember extends Base { this.avatar === member.avatar && this.pending === member.pending && this.communicationDisabledUntilTimestamp === member.communicationDisabledUntilTimestamp && - this.parsedFlags.bitfield === member.parsedFlags.bitfield && + this.flags.bitfield === member.flags.bitfield && (this.roleIds === member.roleIds || (this.roleIds.length === member.roleIds.length && this.roleIds.every((role, i) => role === member.roleIds[i]))) ); diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 461e87d4303a..cc194c3ac2e0 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -1722,14 +1722,14 @@ export class UncachedGuildMember extends Base { public pending: boolean; public get communicationDisabledUntil(): Date | null; public communicationDisabledUntilTimestamp: number | null; - public parsedFlags: Readonly; + public flags: Readonly; public get joinedAt(): Date | null; public joinedTimestamp: number | null; public nickname: string | null; public mute?: boolean; public deaf?: boolean; public partial: true; - public parsedPermissions: Readonly; + public permissions: Readonly; public get premiumSince(): Date | null; public premiumSinceTimestamp: number | null; public roleIds: Snowflake[]; From e2a0a2d8fcf495001fb556128604fe8866628a2c Mon Sep 17 00:00:00 2001 From: advaith Date: Tue, 31 Dec 2024 00:57:44 -0800 Subject: [PATCH 05/10] refactor: rename to MinimalGuildMember --- .../src/structures/BaseInteraction.js | 4 ++-- .../src/structures/CommandInteraction.js | 4 ++-- ...hedGuildMember.js => MinimalGuildMember.js} | 18 +++++++++--------- .../structures/interfaces/TextBasedChannel.js | 4 ++-- packages/discord.js/typings/index.d.ts | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) rename packages/discord.js/src/structures/{UncachedGuildMember.js => MinimalGuildMember.js} (93%) diff --git a/packages/discord.js/src/structures/BaseInteraction.js b/packages/discord.js/src/structures/BaseInteraction.js index 90e7d389a2b7..618473e9a894 100644 --- a/packages/discord.js/src/structures/BaseInteraction.js +++ b/packages/discord.js/src/structures/BaseInteraction.js @@ -4,7 +4,7 @@ const { Collection } = require('@discordjs/collection'); const { DiscordSnowflake } = require('@sapphire/snowflake'); const { InteractionType, ApplicationCommandType, ComponentType } = require('discord-api-types/v10'); const Base = require('./Base'); -const { UncachedGuildMember } = require('./UncachedGuildMember'); +const MinimalGuildMember = require('./MinimalGuildMember'); const { SelectMenuTypes } = require('../util/Constants'); const PermissionsBitField = require('../util/PermissionsBitField'); @@ -66,7 +66,7 @@ class BaseInteraction extends Base { * @type {?(GuildMember|APIInteractionGuildMember)} */ this.member = data.member - ? (this.guild?.members._add(data.member) ?? new UncachedGuildMember(this.client, data.member, this.guildId)) + ? (this.guild?.members._add(data.member) ?? new MinimalGuildMember(this.client, data.member, this.guildId)) : null; /** diff --git a/packages/discord.js/src/structures/CommandInteraction.js b/packages/discord.js/src/structures/CommandInteraction.js index 7b5b3baab5ec..de78aa832bee 100644 --- a/packages/discord.js/src/structures/CommandInteraction.js +++ b/packages/discord.js/src/structures/CommandInteraction.js @@ -3,7 +3,7 @@ const Attachment = require('./Attachment'); const BaseInteraction = require('./BaseInteraction'); const InteractionWebhook = require('./InteractionWebhook'); -const { UncachedGuildMember } = require('./UncachedGuildMember'); +const MinimalGuildMember = require('./MinimalGuildMember'); const InteractionResponses = require('./interfaces/InteractionResponses'); /** @@ -133,7 +133,7 @@ class CommandInteraction extends BaseInteraction { if (member) { result.member = this.guild?.members._add({ user, ...member }) ?? - new UncachedGuildMember(this.client, { user, ...member }, this.guildId); + new MinimalGuildMember(this.client, { user, ...member }, this.guildId); } const channel = resolved.channels?.[option.value]; diff --git a/packages/discord.js/src/structures/UncachedGuildMember.js b/packages/discord.js/src/structures/MinimalGuildMember.js similarity index 93% rename from packages/discord.js/src/structures/UncachedGuildMember.js rename to packages/discord.js/src/structures/MinimalGuildMember.js index 35aec6428719..7bd3038f05a4 100644 --- a/packages/discord.js/src/structures/UncachedGuildMember.js +++ b/packages/discord.js/src/structures/MinimalGuildMember.js @@ -10,7 +10,7 @@ const PermissionsBitField = require('../util/PermissionsBitField'); * @implements {TextBasedChannel} * @extends {Base} */ -class UncachedGuildMember extends Base { +class MinimalGuildMember extends Base { constructor(client, data, guildId) { super(client); @@ -40,7 +40,7 @@ class UncachedGuildMember extends Base { /** * The role ids of the member - * @name UncachedGuildMember#roleIds + * @name MinimalGuildMember#roleIds * @type {Snowflake[]} */ this.roleIds = data.roles; @@ -95,7 +95,7 @@ class UncachedGuildMember extends Base { data.communication_disabled_until && Date.parse(data.communication_disabled_until); /** - * Whether this UncachedGuildMember is a partial (always true, as it is a partial GuildMember) + * Whether this MinimalGuildMember is a partial (always true, as it is a partial GuildMember) * @type {boolean} * @readonly */ @@ -204,7 +204,7 @@ class UncachedGuildMember extends Base { * Whether this guild member equals another guild member. It compares all properties, so for most * comparison it is advisable to just compare `member.id === member2.id` as it is significantly faster * and is often what most users need. - * @param {UncachedGuildMember} member The member to compare with + * @param {MinimalGuildMember} member The member to compare with * @returns {boolean} */ equals(member) { @@ -226,7 +226,7 @@ class UncachedGuildMember extends Base { /** * When concatenated with a string, this automatically returns the user's mention - * instead of the UncachedGuildMember object. + * instead of the MinimalGuildMember object. * @returns {string} * @example * // Logs: Hello from <@123456789012345678>! @@ -252,17 +252,17 @@ class UncachedGuildMember extends Base { /** * Sends a message to this user. * @method send - * @memberof UncachedGuildMember + * @memberof MinimalGuildMember * @instance * @param {string|MessagePayload|MessageCreateOptions} options The options to provide * @returns {Promise} * @example * // Send a direct message - * UncachedGuildMember.send('Hello!') + * MinimalGuildMember.send('Hello!') * .then(message => console.log(`Sent message: ${message.content} to ${guildMember.displayName}`)) * .catch(console.error); */ -TextBasedChannel.applyToClass(UncachedGuildMember); +TextBasedChannel.applyToClass(MinimalGuildMember); -module.exports = UncachedGuildMember; +module.exports = MinimalGuildMember; diff --git a/packages/discord.js/src/structures/interfaces/TextBasedChannel.js b/packages/discord.js/src/structures/interfaces/TextBasedChannel.js index c40ceda19b4e..7883d8c74a23 100644 --- a/packages/discord.js/src/structures/interfaces/TextBasedChannel.js +++ b/packages/discord.js/src/structures/interfaces/TextBasedChannel.js @@ -164,9 +164,9 @@ class TextBasedChannel { async send(options) { const User = require('../User'); const { GuildMember } = require('../GuildMember'); - const { UncachedGuildMember } = require('../UncachedGuildMember'); + const MinimalGuildMember = require('../MinimalGuildMember'); - if (this instanceof User || this instanceof GuildMember || this instanceof UncachedGuildMember) { + if (this instanceof User || this instanceof GuildMember || this instanceof MinimalGuildMember) { const dm = await this.createDM(); return dm.send(options); } diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index cc194c3ac2e0..157ff4754ecf 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -1707,8 +1707,8 @@ export class GuildMember extends Base { public valueOf(): string; } -export interface UncachedGuildMember extends PartialTextBasedChannelFields {} -export class UncachedGuildMember extends Base { +export interface MinimalGuildMember extends PartialTextBasedChannelFields {} +export class MinimalGuildMember extends Base { private constructor( client: Client, data: APIInteractionGuildMember | APIInteractionDataResolvedGuildMember, From 4bc530088d79315db73759e0cefc0b88db36abfc Mon Sep 17 00:00:00 2001 From: advaith Date: Tue, 31 Dec 2024 02:41:15 -0800 Subject: [PATCH 06/10] refactor: make GuildMember extend MinimalGuildMember --- .../src/managers/GuildMemberRoleManager.js | 8 +- .../discord.js/src/structures/GuildMember.js | 269 +----------------- .../src/structures/MinimalGuildMember.js | 143 ++++++---- packages/discord.js/src/structures/Role.js | 2 +- packages/discord.js/typings/index.d.ts | 41 +-- 5 files changed, 111 insertions(+), 352 deletions(-) diff --git a/packages/discord.js/src/managers/GuildMemberRoleManager.js b/packages/discord.js/src/managers/GuildMemberRoleManager.js index 7d19bf778b64..cefdbc3f0c12 100644 --- a/packages/discord.js/src/managers/GuildMemberRoleManager.js +++ b/packages/discord.js/src/managers/GuildMemberRoleManager.js @@ -34,7 +34,7 @@ class GuildMemberRoleManager extends DataManager { */ get cache() { const everyone = this.guild.roles.everyone; - return this.guild.roles.cache.filter(role => this.member._roles.includes(role.id)).set(everyone.id, everyone); + return this.guild.roles.cache.filter(role => this.member.roleIds.includes(role.id)).set(everyone.id, everyone); } /** @@ -133,7 +133,7 @@ class GuildMemberRoleManager extends DataManager { await this.client.rest.put(Routes.guildMemberRole(this.guild.id, this.member.id, roleOrRoles), { reason }); const clone = this.member._clone(); - clone._roles = [...this.cache.keys(), roleOrRoles]; + clone.roleIds = [...this.cache.keys(), roleOrRoles]; return clone; } } @@ -173,7 +173,7 @@ class GuildMemberRoleManager extends DataManager { const clone = this.member._clone(); const newRoles = this.cache.filter(role => role.id !== roleOrRoles); - clone._roles = [...newRoles.keys()]; + clone.roleIds = [...newRoles.keys()]; return clone; } } @@ -200,7 +200,7 @@ class GuildMemberRoleManager extends DataManager { clone() { const clone = new this.constructor(this.member); - clone.member._roles = [...this.cache.keys()]; + clone.member.roleIds = [...this.cache.keys()]; return clone; } } diff --git a/packages/discord.js/src/structures/GuildMember.js b/packages/discord.js/src/structures/GuildMember.js index f7c4304935f3..6b849827d8de 100644 --- a/packages/discord.js/src/structures/GuildMember.js +++ b/packages/discord.js/src/structures/GuildMember.js @@ -1,133 +1,25 @@ 'use strict'; const { PermissionFlagsBits } = require('discord-api-types/v10'); -const Base = require('./Base'); +const MinimalGuildMember = require('./MinimalGuildMember'); const VoiceState = require('./VoiceState'); -const TextBasedChannel = require('./interfaces/TextBasedChannel'); const { DiscordjsError, ErrorCodes } = require('../errors'); const GuildMemberRoleManager = require('../managers/GuildMemberRoleManager'); -const { GuildMemberFlagsBitField } = require('../util/GuildMemberFlagsBitField'); const PermissionsBitField = require('../util/PermissionsBitField'); /** * Represents a member of a guild on Discord. - * @implements {TextBasedChannel} - * @extends {Base} + * @extends {MinimalGuildMember} */ -class GuildMember extends Base { +class GuildMember extends MinimalGuildMember { constructor(client, data, guild) { - super(client); + super(client, data, guild.id); /** * The guild that this member is part of * @type {Guild} */ this.guild = guild; - - /** - * The timestamp the member joined the guild at - * @type {?number} - */ - this.joinedTimestamp = null; - - /** - * The last timestamp this member started boosting the guild - * @type {?number} - */ - this.premiumSinceTimestamp = null; - - /** - * The nickname of this member, if they have one - * @type {?string} - */ - this.nickname = null; - - /** - * Whether this member has yet to pass the guild's membership gate - * @type {?boolean} - */ - this.pending = null; - - /** - * The timestamp this member's timeout will be removed - * @type {?number} - */ - this.communicationDisabledUntilTimestamp = null; - - /** - * The role ids of the member - * @name GuildMember#_roles - * @type {Snowflake[]} - * @private - */ - Object.defineProperty(this, '_roles', { value: [], writable: true }); - - if (data) this._patch(data); - } - - _patch(data) { - if ('user' in data) { - /** - * The user that this guild member instance represents - * @type {?User} - */ - this.user = this.client.users._add(data.user, true); - } - - 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 ('banner' in data) { - /** - * The guild member's banner hash. - * @type {?string} - */ - this.banner = data.banner; - } else { - this.banner ??= null; - } - - if ('joined_at' in data) this.joinedTimestamp = Date.parse(data.joined_at); - if ('premium_since' in data) { - this.premiumSinceTimestamp = data.premium_since ? Date.parse(data.premium_since) : null; - } - if ('roles' in data) this._roles = data.roles; - - if ('pending' in data) { - this.pending = data.pending; - } else if (!this.partial) { - // See https://github.com/discordjs/discord.js/issues/6546 for more info. - this.pending ??= false; - } - - if ('communication_disabled_until' in data) { - this.communicationDisabledUntilTimestamp = - data.communication_disabled_until && Date.parse(data.communication_disabled_until); - } - - if ('flags' in data) { - /** - * The flags of this member - * @type {Readonly} - */ - this.flags = new GuildMemberFlagsBitField(data.flags).freeze(); - } else { - this.flags ??= new GuildMemberFlagsBitField().freeze(); - } - } - - _clone() { - const clone = super._clone(); - clone._roles = this._roles.slice(); - return clone; } /** @@ -156,35 +48,6 @@ class GuildMember extends Base { get voice() { 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(options = {}) { - return this.avatar && this.client.rest.cdn.guildMemberAvatar(this.guild.id, this.id, this.avatar, options); - } - - /** - * A link to the member's banner. - * @param {ImageURLOptions} [options={}] Options for the banner URL - * @returns {?string} - */ - bannerURL(options = {}) { - return this.banner && this.client.rest.cdn.guildMemberBanner(this.guild.id, this.id, this.banner, options); - } - - /** - * 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); - } - /** * A link to the member's guild banner if they have one. * Otherwise, a link to their {@link User#bannerURL} will be returned. @@ -195,33 +58,6 @@ class GuildMember extends Base { return this.bannerURL(options) ?? this.user.bannerURL(options); } - /** - * The time this member joined the guild - * @type {?Date} - * @readonly - */ - get joinedAt() { - return this.joinedTimestamp && new Date(this.joinedTimestamp); - } - - /** - * The time this member's timeout will be removed - * @type {?Date} - * @readonly - */ - get communicationDisabledUntil() { - return this.communicationDisabledUntilTimestamp && new Date(this.communicationDisabledUntilTimestamp); - } - - /** - * The last time this member started boosting the guild - * @type {?Date} - * @readonly - */ - get premiumSince() { - return this.premiumSinceTimestamp && new Date(this.premiumSinceTimestamp); - } - /** * The presence of this guild member * @type {?Presence} @@ -249,33 +85,6 @@ class GuildMember extends Base { return this.roles.color?.hexColor ?? '#000000'; } - /** - * The member's id - * @type {Snowflake} - * @readonly - */ - get id() { - return this.user.id; - } - - /** - * The DM between the client's user and this member - * @type {?DMChannel} - * @readonly - */ - get dmChannel() { - return this.client.users.dmChannel(this.id); - } - - /** - * 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.displayName; - } - /** * The overall set of permissions for this member, taking only roles and owner status into account * @type {Readonly} @@ -333,14 +142,6 @@ class GuildMember extends Base { ); } - /** - * Whether this member is currently timed out - * @returns {boolean} - */ - isCommunicationDisabled() { - return this.communicationDisabledUntilTimestamp > Date.now(); - } - /** * Returns `channel.permissionsFor(guildMember)`. Returns permissions for a member in a guild channel, * taking into account roles and permission overwrites. @@ -392,23 +193,6 @@ class GuildMember extends Base { return this.edit({ nick, reason }); } - /** - * Creates a DM channel between the client and this member. - * @param {boolean} [force=false] Whether to skip the cache check and request the API - * @returns {Promise} - */ - createDM(force = false) { - return this.user.createDM(force); - } - - /** - * Deletes a DM channel (if one exists) between the client and the member. Resolves with the channel if successful. - * @returns {Promise} - */ - deleteDM() { - return this.user.deleteDM(); - } - /** * Kicks this member from the guild. * @param {string} [reason] Reason for kicking user @@ -498,51 +282,10 @@ class GuildMember extends Base { this.pending === member.pending && this.communicationDisabledUntilTimestamp === member.communicationDisabledUntilTimestamp && this.flags.bitfield === member.flags.bitfield && - (this._roles === member._roles || - (this._roles.length === member._roles.length && this._roles.every((role, i) => role === member._roles[i]))) + (this.roleIds === member.roleIds || + (this.roleIds.length === member.roleIds.length && this.roleIds.every((role, i) => role === member.roleIds[i]))) ); } - - /** - * When concatenated with a string, this automatically returns the user's mention instead of the GuildMember object. - * @returns {string} - * @example - * // Logs: Hello from <@123456789012345678>! - * console.log(`Hello from ${member}!`); - */ - toString() { - return this.user.toString(); - } - - toJSON() { - const json = super.toJSON({ - guild: 'guildId', - user: 'userId', - displayName: true, - roles: true, - }); - json.avatarURL = this.avatarURL(); - json.bannerURL = this.bannerURL(); - json.displayAvatarURL = this.displayAvatarURL(); - json.displayBannerURL = this.displayBannerURL(); - return json; - } } -/** - * Sends a message to this user. - * @method send - * @memberof GuildMember - * @instance - * @param {string|MessagePayload|MessageCreateOptions} options The options to provide - * @returns {Promise} - * @example - * // Send a direct message - * guildMember.send('Hello!') - * .then(message => console.log(`Sent message: ${message.content} to ${guildMember.displayName}`)) - * .catch(console.error); - */ - -TextBasedChannel.applyToClass(GuildMember); - exports.GuildMember = GuildMember; diff --git a/packages/discord.js/src/structures/MinimalGuildMember.js b/packages/discord.js/src/structures/MinimalGuildMember.js index 7bd3038f05a4..6ababf1a3dfd 100644 --- a/packages/discord.js/src/structures/MinimalGuildMember.js +++ b/packages/discord.js/src/structures/MinimalGuildMember.js @@ -3,7 +3,6 @@ const Base = require('./Base'); const TextBasedChannel = require('./interfaces/TextBasedChannel'); const { GuildMemberFlagsBitField } = require('../util/GuildMemberFlagsBitField'); -const PermissionsBitField = require('../util/PermissionsBitField'); /** * Represents a member of a guild on Discord. Used in interactions from guilds that aren't cached. @@ -20,86 +19,112 @@ class MinimalGuildMember extends Base { */ this.guildId = guildId; - /** - * The user that this guild member instance represents - * @type {User} - */ - this.user = this.client.users._add(data.user, true); - /** * The nickname of this member, if they have one * @type {?string} */ - this.nickname = data.nick; + this.nickname = null; /** * The guild member's avatar hash * @type {?string} */ - this.avatar = data.avatar; + this.avatar = null; /** - * The role ids of the member - * @name MinimalGuildMember#roleIds - * @type {Snowflake[]} + * The guild member's banner hash + * @type {?string} */ - this.roleIds = data.roles; + this.banner = null; /** * The timestamp the member joined the guild at - * @type {number} + * @type {?number} */ - this.joinedTimestamp = Date.parse(data.joined_at); + this.joinedTimestamp = null; /** * The last timestamp this member started boosting the guild * @type {?number} */ - this.premiumSinceTimestamp = data.premium_since ? Date.parse(data.premium_since) : null; - - /** - * Whether the user is deafened in voice channels - * @type {boolean | undefined} - */ - this.deaf = data.deaf; - - /** - * Whether the user is muted in voice channels - * @type {boolean | undefined} - */ - this.mute = data.mute; + this.premiumSinceTimestamp = null; /** * The flags of this member * @type {Readonly} */ - this.flags = new GuildMemberFlagsBitField(data.flags).freeze(); + this.flags = new GuildMemberFlagsBitField().freeze(); /** * Whether this member has yet to pass the guild's membership gate * @type {?boolean} */ - this.pending = data.pending; - - /** - * The total permissions of the member in this channel, including overwrites - * @type {Readonly} - */ - this.permissions = new PermissionsBitField(data.permissions).freeze(); + this.pending = null; /** * The timestamp this member's timeout will be removed * @type {?number} */ - this.communicationDisabledUntilTimestamp = - data.communication_disabled_until && Date.parse(data.communication_disabled_until); + this.communicationDisabledUntilTimestamp = null; - /** - * Whether this MinimalGuildMember is a partial (always true, as it is a partial GuildMember) - * @type {boolean} - * @readonly - */ - this.partial = true; + if (data) this._patch(data); + } + + _patch(data) { + if ('user' in data) { + /** + * The user that this guild member instance represents + * @type {User} + */ + this.user = this.client.users._add(data.user, true); + } + + if ('nick' in data) this.nickname = data.nick; + + if ('avatar' in data) this.avatar = data.avatar; + + if ('banner' in data) this.banner = data.banner; + + if ('roles' in data) { + /** + * The role ids of the member + * @type {Snowflake[]} + */ + this.roleIds = data.roles; + } + + if ('joined_at' in data) this.joinedTimestamp = data.joined_at && Date.parse(data.joined_at); + + if ('premium_since' in data) this.premiumSinceTimestamp = data.premium_since && Date.parse(data.premium_since); + + if ('flags' in data) this.flags = new GuildMemberFlagsBitField(data.flags).freeze(); + + if ('pending' in data) { + this.pending = data.pending; + } else if (!this.partial) { + // See https://github.com/discordjs/discord.js/issues/6546 for more info. + this.pending ??= false; + } + + if ('communication_disabled_until' in data) { + this.communicationDisabledUntilTimestamp = + data.communication_disabled_until && Date.parse(data.communication_disabled_until); + } + } + + _clone() { + const clone = super._clone(); + clone.roleIds = this.roleIds.slice(); + return clone; + } + + /** + * Whether this MinimalGuildMember is a partial (always true, as it is a partial GuildMember) + * @type {boolean} + * @readonly + */ + get partial() { + return true; } /** @@ -111,16 +136,35 @@ class MinimalGuildMember extends Base { return this.avatar && this.client.rest.cdn.guildMemberAvatar(this.guildId, this.id, this.avatar, options); } + /** + * A link to the member's banner. + * @param {ImageURLOptions} [options={}] Options for the banner URL + * @returns {?string} + */ + bannerURL(options = {}) { + return this.banner && this.client.rest.cdn.guildMemberBanner(this.guildId, this.id, this.banner, options); + } + /** * 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 + * @param {ImageURLOptions} [options={}] Options for the image URL * @returns {string} */ displayAvatarURL(options) { return this.avatarURL(options) ?? this.user.displayAvatarURL(options); } + /** + * A link to the member's guild banner if they have one. + * Otherwise, a link to their {@link User#bannerURL} will be returned. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + displayBannerURL(options) { + return this.bannerURL(options) ?? this.user.bannerURL(options); + } + /** * The time this member joined the guild * @type {?Date} @@ -168,7 +212,7 @@ class MinimalGuildMember extends Base { /** * The nickname of this member, or their user display name if they don't have one - * @type {?string} + * @type {string} * @readonly */ get displayName() { @@ -225,8 +269,7 @@ class MinimalGuildMember extends Base { } /** - * When concatenated with a string, this automatically returns the user's mention - * instead of the MinimalGuildMember object. + * When concatenated with a string, this automatically returns the user's mention instead of the GuildMember object. * @returns {string} * @example * // Logs: Hello from <@123456789012345678>! @@ -244,7 +287,9 @@ class MinimalGuildMember extends Base { roles: true, }); json.avatarURL = this.avatarURL(); + json.bannerURL = this.bannerURL(); json.displayAvatarURL = this.displayAvatarURL(); + json.displayBannerURL = this.displayBannerURL(); return json; } } @@ -258,7 +303,7 @@ class MinimalGuildMember extends Base { * @returns {Promise} * @example * // Send a direct message - * MinimalGuildMember.send('Hello!') + * guildMember.send('Hello!') * .then(message => console.log(`Sent message: ${message.content} to ${guildMember.displayName}`)) * .catch(console.error); */ diff --git a/packages/discord.js/src/structures/Role.js b/packages/discord.js/src/structures/Role.js index 06cbac60bade..aa62a924ef6f 100644 --- a/packages/discord.js/src/structures/Role.js +++ b/packages/discord.js/src/structures/Role.js @@ -181,7 +181,7 @@ class Role extends Base { get members() { return this.id === this.guild.id ? this.guild.members.cache.clone() - : this.guild.members.cache.filter(member => member._roles.includes(this.id)); + : this.guild.members.cache.filter(member => member.roleIds.includes(this.id)); } /** diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 157ff4754ecf..049bb4b100cf 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -1653,68 +1653,40 @@ export class GuildMemberFlagsBitField extends BitField { } export interface GuildMember extends PartialTextBasedChannelFields {} -export class GuildMember extends Base { +export class GuildMember extends MinimalGuildMember { private constructor(client: Client, data: RawGuildMemberData, guild: Guild); - private _roles: Snowflake[]; - public avatar: string | null; - public banner: string | null; public get bannable(): boolean; - public get dmChannel(): DMChannel | null; public get displayColor(): number; public get displayHexColor(): HexColorString; - public get displayName(): string; public guild: Guild; - public get id(): Snowflake; - public pending: boolean; - public get communicationDisabledUntil(): Date | null; - public communicationDisabledUntilTimestamp: number | null; - public flags: Readonly; - public get joinedAt(): Date | null; - public joinedTimestamp: number | null; public get kickable(): boolean; public get manageable(): boolean; public get moderatable(): boolean; - public nickname: string | null; public get partial(): false; public get permissions(): Readonly; - public get premiumSince(): Date | null; - public premiumSinceTimestamp: number | null; public get presence(): Presence | null; public get roles(): GuildMemberRoleManager; - public user: User; public get voice(): VoiceState; - public avatarURL(options?: ImageURLOptions): string | null; - public bannerURL(options?: ImageURLOptions): string | null; public ban(options?: BanOptions): Promise; public disableCommunicationUntil(timeout: DateResolvable | null, reason?: string): Promise; public timeout(timeout: number | null, reason?: string): Promise; public fetch(force?: boolean): Promise; - public createDM(force?: boolean): Promise; - public deleteDM(): Promise; - public displayAvatarURL(options?: ImageURLOptions): string; - public displayBannerURL(options?: ImageURLOptions): string | null; public edit(options: GuildMemberEditOptions): Promise; - public isCommunicationDisabled(): this is GuildMember & { - communicationDisabledUntilTimestamp: number; - readonly communicationDisabledUntil: Date; - }; public kick(reason?: string): Promise; public permissionsIn(channel: GuildChannelResolvable): Readonly; public setFlags(flags: GuildMemberFlagsResolvable, reason?: string): Promise; public setNickname(nickname: string | null, reason?: string): Promise; - public toJSON(): unknown; - public toString(): UserMention; - public valueOf(): string; } export interface MinimalGuildMember extends PartialTextBasedChannelFields {} export class MinimalGuildMember extends Base { - private constructor( + protected constructor( client: Client, data: APIInteractionGuildMember | APIInteractionDataResolvedGuildMember, guildId: Snowflake, ); public avatar: string | null; + public banner: string | null; public get dmChannel(): DMChannel | null; public get displayName(): string; public guildId: string; @@ -1726,18 +1698,17 @@ export class MinimalGuildMember extends Base { public get joinedAt(): Date | null; public joinedTimestamp: number | null; public nickname: string | null; - public mute?: boolean; - public deaf?: boolean; - public partial: true; - public permissions: Readonly; + public get partial(): boolean; public get premiumSince(): Date | null; public premiumSinceTimestamp: number | null; public roleIds: Snowflake[]; public user: User; public avatarURL(options?: ImageURLOptions): string | null; + public bannerURL(options?: ImageURLOptions): string | null; public createDM(force?: boolean): Promise; public deleteDM(): Promise; public displayAvatarURL(options?: ImageURLOptions): string; + public displayBannerURL(options?: ImageURLOptions): string | null; public isCommunicationDisabled(): this is GuildMember & { communicationDisabledUntilTimestamp: number; readonly communicationDisabledUntil: Date; From 01ea16430b7a8f063839849349ea4882ac00649a Mon Sep 17 00:00:00 2001 From: advaith Date: Tue, 31 Dec 2024 15:10:44 -0800 Subject: [PATCH 07/10] try to fix format --- packages/discord.js/test/monetization.js | 6 ++---- packages/discord.js/test/polls.js | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/discord.js/test/monetization.js b/packages/discord.js/test/monetization.js index 9dd726e8af4e..5628c41cf468 100644 --- a/packages/discord.js/test/monetization.js +++ b/packages/discord.js/test/monetization.js @@ -1,8 +1,8 @@ 'use strict'; -const { ButtonStyle } = require('discord-api-types/v10'); const { token, owner, skuId } = require('./auth.js'); const { Client, Events, codeBlock, GatewayIntentBits, ActionRowBuilder, ButtonBuilder } = require('../src'); +const { ButtonStyle } = require('discord-api-types/v10'); const client = new Client({ intents: GatewayIntentBits.Guilds | GatewayIntentBits.GuildMessages }); @@ -40,9 +40,7 @@ client.on(Events.InteractionCreate, async interaction => { content: ':3:3:3', components: [ new ActionRowBuilder().setComponents( - new ButtonBuilder().setCustomId('test').setLabel('test') -.setStyle(ButtonStyle.Premium) -.setSKUId(skuId), + new ButtonBuilder().setCustomId('test').setLabel('test').setStyle(ButtonStyle.Premium).setSKUId(skuId), ), ], }); diff --git a/packages/discord.js/test/polls.js b/packages/discord.js/test/polls.js index 2773dd01654b..59c808eefda9 100644 --- a/packages/discord.js/test/polls.js +++ b/packages/discord.js/test/polls.js @@ -12,7 +12,7 @@ client.on('raw', console.log); client.on(Events.ClientReady, async () => { const channel = client.channels.cache.get('1220510756286631968'); - // Const message = await channel.messages.fetch('1220680560414818325'); + // const message = await channel.messages.fetch('1220680560414818325'); // console.dir(message.poll, { depth: Infinity }); // const answer = message.poll.answers.first(); From ea2fedce2258b4d757537b418e074d819d7d8072 Mon Sep 17 00:00:00 2001 From: advaith Date: Wed, 1 Jan 2025 03:08:16 -0800 Subject: [PATCH 08/10] feat: update select menus, fix docs and types --- .../src/structures/BaseInteraction.js | 2 +- .../src/structures/CommandInteraction.js | 4 +- .../CommandInteractionOptionResolver.js | 4 +- .../MentionableSelectMenuInteraction.js | 9 +- .../UserContextMenuCommandInteraction.js | 2 +- .../structures/UserSelectMenuInteraction.js | 9 +- packages/discord.js/typings/index.d.ts | 87 ++++++++++--------- packages/discord.js/typings/index.test-d.ts | 25 +++--- 8 files changed, 78 insertions(+), 64 deletions(-) diff --git a/packages/discord.js/src/structures/BaseInteraction.js b/packages/discord.js/src/structures/BaseInteraction.js index 618473e9a894..d184251e97e2 100644 --- a/packages/discord.js/src/structures/BaseInteraction.js +++ b/packages/discord.js/src/structures/BaseInteraction.js @@ -63,7 +63,7 @@ class BaseInteraction extends Base { /** * If this interaction was sent in a guild, the member which sent it - * @type {?(GuildMember|APIInteractionGuildMember)} + * @type {?(GuildMember|MinimalGuildMember)} */ this.member = data.member ? (this.guild?.members._add(data.member) ?? new MinimalGuildMember(this.client, data.member, this.guildId)) diff --git a/packages/discord.js/src/structures/CommandInteraction.js b/packages/discord.js/src/structures/CommandInteraction.js index de78aa832bee..e5714a0da8ea 100644 --- a/packages/discord.js/src/structures/CommandInteraction.js +++ b/packages/discord.js/src/structures/CommandInteraction.js @@ -84,7 +84,7 @@ class CommandInteraction extends BaseInteraction { * Represents the resolved data of a received command interaction. * @typedef {Object} CommandInteractionResolvedData * @property {Collection} [users] The resolved users - * @property {Collection} [members] The resolved guild members + * @property {Collection} [members] The resolved guild members * @property {Collection} [roles] The resolved roles * @property {Collection} [channels] The resolved channels * @property {Collection} [messages] The resolved messages @@ -103,7 +103,7 @@ class CommandInteraction extends BaseInteraction { * @property {CommandInteractionOption[]} [options] Additional options if this option is a * subcommand (group) * @property {User} [user] The resolved user - * @property {GuildMember|APIGuildMember} [member] The resolved member + * @property {GuildMember|MinimalGuildMember} [member] The resolved member * @property {GuildChannel|ThreadChannel|APIChannel} [channel] The resolved channel * @property {Role|APIRole} [role] The resolved role * @property {Attachment} [attachment] The resolved attachment diff --git a/packages/discord.js/src/structures/CommandInteractionOptionResolver.js b/packages/discord.js/src/structures/CommandInteractionOptionResolver.js index b59f6328e40c..dadaaafb9859 100644 --- a/packages/discord.js/src/structures/CommandInteractionOptionResolver.js +++ b/packages/discord.js/src/structures/CommandInteractionOptionResolver.js @@ -214,7 +214,7 @@ class CommandInteractionOptionResolver { /** * Gets a member option. * @param {string} name The name of the option. - * @returns {?(GuildMember|APIGuildMember)} + * @returns {?(GuildMember|MinimalGuildMember)} * The value of the option, or null if the user is not present in the guild or the option is not set. */ getMember(name) { @@ -258,7 +258,7 @@ class CommandInteractionOptionResolver { * Gets a mentionable option. * @param {string} name The name of the option. * @param {boolean} [required=false] Whether to throw an error if the option is not found. - * @returns {?(User|GuildMember|APIGuildMember|Role|APIRole)} + * @returns {?(User|GuildMember|MinimalGuildMember|Role|APIRole)} * The value of the option, or null if not set and not required. */ getMentionable(name, required = false) { diff --git a/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js index 416d5ceb5429..285cb45a7569 100644 --- a/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js +++ b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js @@ -2,6 +2,7 @@ const { Collection } = require('@discordjs/collection'); const MessageComponentInteraction = require('./MessageComponentInteraction'); +const MinimalGuildMember = require('./MinimalGuildMember'); const Events = require('../util/Events'); /** @@ -28,7 +29,7 @@ class MentionableSelectMenuInteraction extends MessageComponentInteraction { /** * Collection of the selected users - * @type {Collection} + * @type {Collection} */ this.members = new Collection(); @@ -50,7 +51,11 @@ class MentionableSelectMenuInteraction extends MessageComponentInteraction { continue; } - this.members.set(id, this.guild?.members._add({ user, ...member }) ?? { user, ...member }); + this.members.set( + id, + this.guild?.members._add({ user, ...member }) ?? + new MinimalGuildMember(this.client, { user, ...member }, this.guildId), + ); } } diff --git a/packages/discord.js/src/structures/UserContextMenuCommandInteraction.js b/packages/discord.js/src/structures/UserContextMenuCommandInteraction.js index 2e9dc7c62877..db5dc65a4bf0 100644 --- a/packages/discord.js/src/structures/UserContextMenuCommandInteraction.js +++ b/packages/discord.js/src/structures/UserContextMenuCommandInteraction.js @@ -18,7 +18,7 @@ class UserContextMenuCommandInteraction extends ContextMenuCommandInteraction { /** * The target member from this interaction - * @type {?(GuildMember|APIGuildMember)} + * @type {?(GuildMember|MinimalGuildMember)} * @readonly */ get targetMember() { diff --git a/packages/discord.js/src/structures/UserSelectMenuInteraction.js b/packages/discord.js/src/structures/UserSelectMenuInteraction.js index 5e232399708e..49c6337ed559 100644 --- a/packages/discord.js/src/structures/UserSelectMenuInteraction.js +++ b/packages/discord.js/src/structures/UserSelectMenuInteraction.js @@ -2,6 +2,7 @@ const { Collection } = require('@discordjs/collection'); const MessageComponentInteraction = require('./MessageComponentInteraction'); +const MinimalGuildMember = require('./MinimalGuildMember'); const Events = require('../util/Events'); /** @@ -27,7 +28,7 @@ class UserSelectMenuInteraction extends MessageComponentInteraction { /** * Collection of the selected members - * @type {Collection} + * @type {Collection} */ this.members = new Collection(); @@ -43,7 +44,11 @@ class UserSelectMenuInteraction extends MessageComponentInteraction { continue; } - this.members.set(id, this.guild?.members._add({ user, ...member }) ?? { user, ...member }); + this.members.set( + id, + this.guild?.members._add({ user, ...member }) ?? + new MinimalGuildMember(this.client, { user, ...member }, this.guildId), + ); } } } diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 049bb4b100cf..0873c7d05c9a 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -30,8 +30,6 @@ import { APIEmbed, APIEmoji, APIInteractionDataResolvedChannel, - APIInteractionDataResolvedGuildMember, - APIInteractionGuildMember, APIMessage, APIMessageComponent, APIOverwrite, @@ -118,7 +116,6 @@ import { APIRoleSelectComponent, APIMentionableSelectComponent, APIChannelSelectComponent, - APIGuildMember, APIMessageRoleSelectInteractionData, APIMessageMentionableSelectInteractionData, APIMessageChannelSelectInteractionData, @@ -1652,39 +1649,9 @@ export class GuildMemberFlagsBitField extends BitField { public static resolve(bit?: BitFieldResolvable): number; } -export interface GuildMember extends PartialTextBasedChannelFields {} -export class GuildMember extends MinimalGuildMember { - private constructor(client: Client, data: RawGuildMemberData, guild: Guild); - public get bannable(): boolean; - public get displayColor(): number; - public get displayHexColor(): HexColorString; - public guild: Guild; - public get kickable(): boolean; - public get manageable(): boolean; - public get moderatable(): boolean; - public get partial(): false; - public get permissions(): Readonly; - public get presence(): Presence | null; - public get roles(): GuildMemberRoleManager; - public get voice(): VoiceState; - public ban(options?: BanOptions): Promise; - public disableCommunicationUntil(timeout: DateResolvable | null, reason?: string): Promise; - public timeout(timeout: number | null, reason?: string): Promise; - public fetch(force?: boolean): Promise; - public edit(options: GuildMemberEditOptions): Promise; - public kick(reason?: string): Promise; - public permissionsIn(channel: GuildChannelResolvable): Readonly; - public setFlags(flags: GuildMemberFlagsResolvable, reason?: string): Promise; - public setNickname(nickname: string | null, reason?: string): Promise; -} - export interface MinimalGuildMember extends PartialTextBasedChannelFields {} export class MinimalGuildMember extends Base { - protected constructor( - client: Client, - data: APIInteractionGuildMember | APIInteractionDataResolvedGuildMember, - guildId: Snowflake, - ); + protected constructor(client: Client, data: RawGuildMemberData, guildId: Snowflake); public avatar: string | null; public banner: string | null; public get dmChannel(): DMChannel | null; @@ -1709,7 +1676,7 @@ export class MinimalGuildMember extends Base { public deleteDM(): Promise; public displayAvatarURL(options?: ImageURLOptions): string; public displayBannerURL(options?: ImageURLOptions): string | null; - public isCommunicationDisabled(): this is GuildMember & { + public isCommunicationDisabled(): this is MinimalGuildMember & { communicationDisabledUntilTimestamp: number; readonly communicationDisabledUntil: Date; }; @@ -1718,6 +1685,32 @@ export class MinimalGuildMember extends Base { public valueOf(): string; } +export interface GuildMember extends PartialTextBasedChannelFields {} +export class GuildMember extends MinimalGuildMember { + private constructor(client: Client, data: RawGuildMemberData, guild: Guild); + public get bannable(): boolean; + public get displayColor(): number; + public get displayHexColor(): HexColorString; + public guild: Guild; + public get kickable(): boolean; + public get manageable(): boolean; + public get moderatable(): boolean; + public get partial(): false; + public get permissions(): Readonly; + public get presence(): Presence | null; + public get roles(): GuildMemberRoleManager; + public get voice(): VoiceState; + public ban(options?: BanOptions): Promise; + public disableCommunicationUntil(timeout: DateResolvable | null, reason?: string): Promise; + public timeout(timeout: number | null, reason?: string): Promise; + public fetch(force?: boolean): Promise; + public edit(options: GuildMemberEditOptions): Promise; + public kick(reason?: string): Promise; + public permissionsIn(channel: GuildChannelResolvable): Readonly; + public setFlags(flags: GuildMemberFlagsResolvable, reason?: string): Promise; + public setNickname(nickname: string | null, reason?: string): Promise; +} + export class GuildOnboarding extends Base { private constructor(client: Client, data: RESTGetAPIGuildOnboardingResult); public get guild(): Guild; @@ -1976,7 +1969,7 @@ export class BaseInteraction extends Base public get guild(): CacheTypeReducer; public guildId: CacheTypeReducer; public id: Snowflake; - public member: CacheTypeReducer; + public member: CacheTypeReducer; public readonly token: string; public type: InteractionType; public user: User; @@ -2903,7 +2896,13 @@ export class UserSelectMenuInteraction< public users: Collection; public members: Collection< Snowflake, - CacheTypeReducer + CacheTypeReducer< + Cached, + GuildMember, + MinimalGuildMember, + GuildMember | MinimalGuildMember, + GuildMember | MinimalGuildMember + > >; public inGuild(): this is UserSelectMenuInteraction<'raw' | 'cached'>; public inCachedGuild(): this is UserSelectMenuInteraction<'cached'>; @@ -2945,7 +2944,13 @@ export class MentionableSelectMenuInteraction< public users: Collection; public members: Collection< Snowflake, - CacheTypeReducer + CacheTypeReducer< + Cached, + GuildMember, + MinimalGuildMember, + GuildMember | MinimalGuildMember, + GuildMember | MinimalGuildMember + > >; public roles: Collection>; public inGuild(): this is MentionableSelectMenuInteraction<'raw' | 'cached'>; @@ -3512,7 +3517,7 @@ export class UserContextMenuCommandInteraction< | 'getSubcommand' >; public get targetUser(): User; - public get targetMember(): CacheTypeReducer | null; + public get targetMember(): CacheTypeReducer | null; public inGuild(): this is UserContextMenuCommandInteraction<'raw' | 'cached'>; public inCachedGuild(): this is UserContextMenuCommandInteraction<'cached'>; public inRawGuild(): this is UserContextMenuCommandInteraction<'raw'>; @@ -5370,7 +5375,7 @@ export interface CommandInteractionOption autocomplete?: boolean; options?: readonly CommandInteractionOption[]; user?: User; - member?: CacheTypeReducer; + member?: CacheTypeReducer; channel?: CacheTypeReducer; role?: CacheTypeReducer; attachment?: Attachment; @@ -5379,7 +5384,7 @@ export interface CommandInteractionOption export interface CommandInteractionResolvedData { users?: ReadonlyCollection; - members?: ReadonlyCollection>; + members?: ReadonlyCollection>; roles?: ReadonlyCollection>; channels?: ReadonlyCollection>; messages?: ReadonlyCollection>; diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index b587e3bf25bd..3b90ad56930f 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -1,10 +1,8 @@ import type { ChildProcess } from 'node:child_process'; import type { Worker } from 'node:worker_threads'; import { - APIInteractionGuildMember, APIPartialChannel, APIPartialGuild, - APIInteractionDataResolvedGuildMember, APIInteractionDataResolvedChannel, APIRole, APIButtonComponent, @@ -209,6 +207,7 @@ import { SendableChannels, PollData, InteractionCallbackResponse, + MinimalGuildMember, } from '.'; import { expectAssignable, expectNotAssignable, expectNotType, expectType } from 'tsd'; import type { ContextMenuCommandBuilder, SlashCommandBuilder } from '@discordjs/builders'; @@ -330,7 +329,7 @@ client.on('interactionCreate', async interaction => { expectType>(interaction.client); expectType(interaction.guildId); expectType(interaction.channelId); - expectType(interaction.member); + expectType(interaction.member); if (interaction.type === InteractionType.MessageComponent) { expectType(interaction.channelId); @@ -1825,13 +1824,13 @@ client.on('interactionCreate', async interaction => { expectAssignable(interaction); expectType(interaction.guildLocale); } else if (interaction.inRawGuild()) { - expectAssignable(interaction.member); + expectAssignable(interaction.member); expectNotAssignable>(interaction); expectType(interaction.guildLocale); } else if (interaction.inGuild()) { expectType(interaction.guildLocale); } else { - expectType(interaction.member); + expectType(interaction.member); expectNotAssignable>(interaction); expectType(interaction.guildId); } @@ -1895,18 +1894,18 @@ client.on('interactionCreate', async interaction => { interaction.commandType === ApplicationCommandType.User ) { expectType(interaction.targetUser); - expectType(interaction.targetMember); + expectType(interaction.targetMember); expectType(interaction.options.getUser('user')); - expectType(interaction.options.getMember('user')); + expectType(interaction.options.getMember('user')); if (interaction.inCachedGuild()) { expectType(interaction.targetMember); expectType(interaction.options.getMember('user')); } else if (interaction.inRawGuild()) { - expectType(interaction.targetMember); - expectType(interaction.options.getMember('user')); + expectType(interaction.targetMember); + expectType(interaction.options.getMember('user')); } else if (interaction.inGuild()) { - expectType(interaction.targetMember); - expectType(interaction.options.getMember('user')); + expectType(interaction.targetMember); + expectType(interaction.options.getMember('user')); } } @@ -1971,7 +1970,7 @@ client.on('interactionCreate', async interaction => { expectNotAssignable>(interaction); expectAssignable(interaction); expectType>(interaction.reply({ withResponse: true })); - expectType(interaction.options.getMember('test')); + expectType(interaction.options.getMember('test')); expectType(interaction.options.getChannel('test', true)); expectType(interaction.options.getRole('test', true)); @@ -2003,7 +2002,7 @@ client.on('interactionCreate', async interaction => { } else { expectType(interaction); expectType>(interaction.reply({ withResponse: true })); - expectType(interaction.options.getMember('test')); + expectType(interaction.options.getMember('test')); expectType(interaction.options.getChannel('test', true)); expectType(interaction.options.getRole('test', true)); From d634c81bf1de450060f9530e5f0943b7835ba645 Mon Sep 17 00:00:00 2001 From: advaith Date: Wed, 1 Jan 2025 05:07:50 -0800 Subject: [PATCH 09/10] fix: initialize roleIds to [] --- .../src/structures/MinimalGuildMember.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/discord.js/src/structures/MinimalGuildMember.js b/packages/discord.js/src/structures/MinimalGuildMember.js index 6ababf1a3dfd..e732fe0a8f00 100644 --- a/packages/discord.js/src/structures/MinimalGuildMember.js +++ b/packages/discord.js/src/structures/MinimalGuildMember.js @@ -37,6 +37,12 @@ class MinimalGuildMember extends Base { */ this.banner = null; + /** + * The role ids of the member + * @type {Snowflake[]} + */ + this.roleIds = []; + /** * The timestamp the member joined the guild at * @type {?number} @@ -85,13 +91,7 @@ class MinimalGuildMember extends Base { if ('banner' in data) this.banner = data.banner; - if ('roles' in data) { - /** - * The role ids of the member - * @type {Snowflake[]} - */ - this.roleIds = data.roles; - } + if ('roles' in data) this.roleIds = data.roles; if ('joined_at' in data) this.joinedTimestamp = data.joined_at && Date.parse(data.joined_at); From f83626d064f1345d736090255cca3ba9a8d51be6 Mon Sep 17 00:00:00 2001 From: advaith Date: Thu, 2 Jan 2025 19:57:58 -0800 Subject: [PATCH 10/10] feat: add isInCachedGuild typeguard --- packages/discord.js/src/structures/GuildMember.js | 4 ++++ .../discord.js/src/structures/MinimalGuildMember.js | 10 +++++++++- packages/discord.js/typings/index.d.ts | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/discord.js/src/structures/GuildMember.js b/packages/discord.js/src/structures/GuildMember.js index 6b849827d8de..be893cb2930a 100644 --- a/packages/discord.js/src/structures/GuildMember.js +++ b/packages/discord.js/src/structures/GuildMember.js @@ -22,6 +22,10 @@ class GuildMember extends MinimalGuildMember { this.guild = guild; } + isInCachedGuild() { + return true; + } + /** * Whether this GuildMember is a partial * @type {boolean} diff --git a/packages/discord.js/src/structures/MinimalGuildMember.js b/packages/discord.js/src/structures/MinimalGuildMember.js index e732fe0a8f00..d31828c7e179 100644 --- a/packages/discord.js/src/structures/MinimalGuildMember.js +++ b/packages/discord.js/src/structures/MinimalGuildMember.js @@ -119,7 +119,15 @@ class MinimalGuildMember extends Base { } /** - * Whether this MinimalGuildMember is a partial (always true, as it is a partial GuildMember) + * Whether this member is in a cached guild (true for GuildMembers, false for MinimalGuildMembers) + * @returns {boolean} + */ + isInCachedGuild() { + return false; + } + + /** + * Whether this member is a partial (always true for MinimalGuildMembers, as they are partial GuildMembers) * @type {boolean} * @readonly */ diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 0873c7d05c9a..b9dade9c5b3a 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -1680,6 +1680,7 @@ export class MinimalGuildMember extends Base { communicationDisabledUntilTimestamp: number; readonly communicationDisabledUntil: Date; }; + public isInCachedGuild(): this is GuildMember; public toJSON(): unknown; public toString(): UserMention; public valueOf(): string;