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;