New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(Interactions): member objects for uncached guilds #10195
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,328 @@ | ||||||||
'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 ID of the guild that this member is part of | ||||||||
* @type {string} | ||||||||
*/ | ||||||||
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; | ||||||||
|
||||||||
/** | ||||||||
* The guild member's avatar hash | ||||||||
* @type {?string} | ||||||||
*/ | ||||||||
this.avatar = data.avatar; | ||||||||
|
||||||||
/** | ||||||||
* 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 | ||||||||
Comment on lines
+71
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This comment was marked as spam.
Sorry, something went wrong. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is what I had originally, but it fails prettier due to the line length, so I split it up. |
||||||||
*/ | ||||||||
this.premium_since = data.premium_since; | ||||||||
|
||||||||
/** | ||||||||
* 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; | ||||||||
|
||||||||
/** | ||||||||
* The flags of this member | ||||||||
* @type {Readonly<GuildMemberFlagsBitField>} | ||||||||
*/ | ||||||||
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<PermissionsBitField>} | ||||||||
*/ | ||||||||
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; | ||||||||
} | ||||||||
vladfrangu marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
/** | ||||||||
* 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} | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would they not always have a display name, or is this something to do with partials? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hm yeah, why is this nullable in GuildMember? also user is nullable in GuildMember but id (which just returns this.user.id) isn't There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it would seem so There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the user being nullable related to the
note that is under the Guild Member object table in the DAPI docs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this sounds like something we need to investigate.... possibly seperately re posted because email replies dont work properly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In those two events the
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so in conclusion: the docs are wrong |
||||||||
* @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; | ||||||||
} | ||||||||
Jiralite marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
/** | ||||||||
* 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<DMChannel>} | ||||||||
*/ | ||||||||
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<DMChannel>} | ||||||||
*/ | ||||||||
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 && | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. .flags is deprecated so I didn't want to use it; I want to rename parsedFlags to flags in v15 |
||||||||
(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({ | ||||||||
guildId: true, | ||||||||
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<Message>} | ||||||||
* @example | ||||||||
* // Send a direct message | ||||||||
* UncachedGuildMember.send('Hello!') | ||||||||
* .then(message => console.log(`Sent message: ${message.content} to ${guildMember.displayName}`)) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Feels a bit silly but eh |
||||||||
* .catch(console.error); | ||||||||
*/ | ||||||||
|
||||||||
TextBasedChannel.applyToClass(UncachedGuildMember); | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This won’t work without additional changes in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah thanks, will look into that |
||||||||
|
||||||||
module.exports = UncachedGuildMember; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn’t this a breaking change?
APIInteractionGuildMember#user
isAPIUser
and thus has snakecase properties. Now it doesn’t anymore.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that is a breaking change indeed... @advaith1 can you solve that too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ugh, good catch. so I think the options here are
global_name
,accent_color
, andpublic_flags
to Userfor simplicity I'd prefer to do the former tbh... what do people think