GuildMember.js (11474B)
1 'use strict'; 2 3 const Base = require('./Base'); 4 const { Presence } = require('./Presence'); 5 const Role = require('./Role'); 6 const VoiceState = require('./VoiceState'); 7 const TextBasedChannel = require('./interfaces/TextBasedChannel'); 8 const { Error } = require('../errors'); 9 const GuildMemberRoleManager = require('../managers/GuildMemberRoleManager'); 10 const Permissions = require('../util/Permissions'); 11 12 /** 13 * Represents a member of a guild on Discord. 14 * @implements {TextBasedChannel} 15 * @extends {Base} 16 */ 17 class GuildMember extends Base { 18 /** 19 * @param {Client} client The instantiating client 20 * @param {Object} data The data for the guild member 21 * @param {Guild} guild The guild the member is part of 22 */ 23 constructor(client, data, guild) { 24 super(client); 25 26 /** 27 * The guild that this member is part of 28 * @type {Guild} 29 */ 30 this.guild = guild; 31 32 /** 33 * The user that this guild member instance represents 34 * @type {User} 35 * @name GuildMember#user 36 */ 37 if (data.user) this.user = client.users.add(data.user, true); 38 39 /** 40 * The timestamp the member joined the guild at 41 * @type {?number} 42 */ 43 this.joinedTimestamp = null; 44 45 /** 46 * The ID of the last message sent by the member in their guild, if one was sent 47 * @type {?Snowflake} 48 */ 49 this.lastMessageID = null; 50 51 /** 52 * The ID of the channel for the last message sent by the member in their guild, if one was sent 53 * @type {?Snowflake} 54 */ 55 this.lastMessageChannelID = null; 56 57 /** 58 * The timestamp of when the member used their Nitro boost on the guild, if it was used 59 * @type {?number} 60 */ 61 this.premiumSinceTimestamp = null; 62 63 /** 64 * Whether the member has been removed from the guild 65 * @type {boolean} 66 */ 67 this.deleted = false; 68 69 this._roles = []; 70 if (data) this._patch(data); 71 } 72 73 _patch(data) { 74 /** 75 * The nickname of this member, if they have one 76 * @type {?string} 77 * @name GuildMember#nickname 78 */ 79 if (typeof data.nick !== 'undefined') this.nickname = data.nick; 80 81 if (data.joined_at) this.joinedTimestamp = new Date(data.joined_at).getTime(); 82 if (data.premium_since) this.premiumSinceTimestamp = new Date(data.premium_since).getTime(); 83 84 if (data.user) this.user = this.guild.client.users.add(data.user); 85 if (data.roles) this._roles = data.roles; 86 } 87 88 _clone() { 89 const clone = super._clone(); 90 clone._roles = this._roles.slice(); 91 return clone; 92 } 93 94 /** 95 * Whether this GuildMember is a partial 96 * @type {boolean} 97 * @readonly 98 */ 99 get partial() { 100 return !this.joinedTimestamp; 101 } 102 103 /** 104 * A manager for the roles belonging to this member 105 * @type {GuildMemberRoleManager} 106 * @readonly 107 */ 108 get roles() { 109 return new GuildMemberRoleManager(this); 110 } 111 112 /** 113 * The Message object of the last message sent by the member in their guild, if one was sent 114 * @type {?Message} 115 * @readonly 116 */ 117 get lastMessage() { 118 const channel = this.guild.channels.cache.get(this.lastMessageChannelID); 119 return (channel && channel.messages.cache.get(this.lastMessageID)) || null; 120 } 121 122 /** 123 * The voice state of this member 124 * @type {VoiceState} 125 * @readonly 126 */ 127 get voice() { 128 return this.guild.voiceStates.cache.get(this.id) || new VoiceState(this.guild, { user_id: this.id }); 129 } 130 131 /** 132 * The time this member joined the guild 133 * @type {?Date} 134 * @readonly 135 */ 136 get joinedAt() { 137 return this.joinedTimestamp ? new Date(this.joinedTimestamp) : null; 138 } 139 140 /** 141 * The time of when the member used their Nitro boost on the guild, if it was used 142 * @type {?Date} 143 * @readonly 144 */ 145 get premiumSince() { 146 return this.premiumSinceTimestamp ? new Date(this.premiumSinceTimestamp) : null; 147 } 148 149 /** 150 * The presence of this guild member 151 * @type {Presence} 152 * @readonly 153 */ 154 get presence() { 155 return ( 156 this.guild.presences.cache.get(this.id) || 157 new Presence(this.client, { 158 user: { 159 id: this.id, 160 }, 161 guild: this.guild, 162 }) 163 ); 164 } 165 166 /** 167 * The displayed color of this member in base 10 168 * @type {number} 169 * @readonly 170 */ 171 get displayColor() { 172 const role = this.roles.color; 173 return (role && role.color) || 0; 174 } 175 176 /** 177 * The displayed color of this member in hexadecimal 178 * @type {string} 179 * @readonly 180 */ 181 get displayHexColor() { 182 const role = this.roles.color; 183 return (role && role.hexColor) || '#000000'; 184 } 185 186 /** 187 * The ID of this member 188 * @type {Snowflake} 189 * @readonly 190 */ 191 get id() { 192 return this.user.id; 193 } 194 195 /** 196 * The nickname of this member, or their username if they don't have one 197 * @type {string} 198 * @readonly 199 */ 200 get displayName() { 201 return this.nickname || this.user.username; 202 } 203 204 /** 205 * The overall set of permissions for this member, taking only roles into account 206 * @type {Readonly<Permissions>} 207 * @readonly 208 */ 209 get permissions() { 210 if (this.user.id === this.guild.ownerID) return new Permissions(Permissions.ALL).freeze(); 211 return new Permissions(this.roles.cache.map(role => role.permissions)).freeze(); 212 } 213 214 /** 215 * Whether the client user is above this user in the hierarchy, according to role position and guild ownership. 216 * This is a prerequisite for many moderative actions. 217 * @type {boolean} 218 * @readonly 219 */ 220 get manageable() { 221 if (this.user.id === this.guild.ownerID) return false; 222 if (this.user.id === this.client.user.id) return false; 223 if (this.client.user.id === this.guild.ownerID) return true; 224 if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME'); 225 return this.guild.me.roles.highest.comparePositionTo(this.roles.highest) > 0; 226 } 227 228 /** 229 * Whether this member is kickable by the client user 230 * @type {boolean} 231 * @readonly 232 */ 233 get kickable() { 234 return this.manageable && this.guild.me.permissions.has(Permissions.FLAGS.KICK_MEMBERS); 235 } 236 237 /** 238 * Whether this member is bannable by the client user 239 * @type {boolean} 240 * @readonly 241 */ 242 get bannable() { 243 return this.manageable && this.guild.me.permissions.has(Permissions.FLAGS.BAN_MEMBERS); 244 } 245 246 /** 247 * Returns `channel.permissionsFor(guildMember)`. Returns permissions for a member in a guild channel, 248 * taking into account roles and permission overwrites. 249 * @param {ChannelResolvable} channel The guild channel to use as context 250 * @returns {Readonly<Permissions>} 251 */ 252 permissionsIn(channel) { 253 channel = this.guild.channels.resolve(channel); 254 if (!channel) throw new Error('GUILD_CHANNEL_RESOLVE'); 255 return channel.memberPermissions(this); 256 } 257 258 /** 259 * Checks if any of this member's roles have a permission. 260 * @param {PermissionResolvable} permission Permission(s) to check for 261 * @param {Object} [options] Options 262 * @param {boolean} [options.checkAdmin=true] Whether to allow the administrator permission to override 263 * @param {boolean} [options.checkOwner=true] Whether to allow being the guild's owner to override 264 * @returns {boolean} 265 */ 266 hasPermission(permission, { checkAdmin = true, checkOwner = true } = {}) { 267 if (checkOwner && this.user.id === this.guild.ownerID) return true; 268 return this.roles.cache.some(r => r.permissions.has(permission, checkAdmin)); 269 } 270 271 /** 272 * The data for editing a guild member. 273 * @typedef {Object} GuildMemberEditData 274 * @property {string} [nick] The nickname to set for the member 275 * @property {Collection<Snowflake, Role>|RoleResolvable[]} [roles] The roles or role IDs to apply 276 * @property {boolean} [mute] Whether or not the member should be muted 277 * @property {boolean} [deaf] Whether or not the member should be deafened 278 * @property {ChannelResolvable|null} [channel] Channel to move member to (if they are connected to voice), or `null` 279 * if you want to kick them from voice 280 */ 281 282 /** 283 * Edits this member. 284 * @param {GuildMemberEditData} data The data to edit the member with 285 * @param {string} [reason] Reason for editing this user 286 * @returns {Promise<GuildMember>} 287 */ 288 async edit(data, reason) { 289 if (data.channel) { 290 data.channel = this.guild.channels.resolve(data.channel); 291 if (!data.channel || data.channel.type !== 'voice') { 292 throw new Error('GUILD_VOICE_CHANNEL_RESOLVE'); 293 } 294 data.channel_id = data.channel.id; 295 data.channel = undefined; 296 } else if (data.channel === null) { 297 data.channel_id = null; 298 data.channel = undefined; 299 } 300 if (data.roles) data.roles = data.roles.map(role => (role instanceof Role ? role.id : role)); 301 let endpoint = this.client.api.guilds(this.guild.id); 302 if (this.user.id === this.client.user.id) { 303 const keys = Object.keys(data); 304 if (keys.length === 1 && keys[0] === 'nick') endpoint = endpoint.members('@me').nick; 305 else endpoint = endpoint.members(this.id); 306 } else { 307 endpoint = endpoint.members(this.id); 308 } 309 await endpoint.patch({ data, reason }); 310 311 const clone = this._clone(); 312 data.user = this.user; 313 clone._patch(data); 314 return clone; 315 } 316 317 /** 318 * Sets the nickname for this member. 319 * @param {string} nick The nickname for the guild member 320 * @param {string} [reason] Reason for setting the nickname 321 * @returns {Promise<GuildMember>} 322 */ 323 setNickname(nick, reason) { 324 return this.edit({ nick }, reason); 325 } 326 327 /** 328 * Creates a DM channel between the client and this member. 329 * @returns {Promise<DMChannel>} 330 */ 331 createDM() { 332 return this.user.createDM(); 333 } 334 335 /** 336 * Deletes any DMs with this member. 337 * @returns {Promise<DMChannel>} 338 */ 339 deleteDM() { 340 return this.user.deleteDM(); 341 } 342 343 /** 344 * Kicks this member from the guild. 345 * @param {string} [reason] Reason for kicking user 346 * @returns {Promise<GuildMember>} 347 */ 348 kick(reason) { 349 return this.client.api 350 .guilds(this.guild.id) 351 .members(this.user.id) 352 .delete({ reason }) 353 .then(() => this); 354 } 355 356 /** 357 * Bans this guild member. 358 * @param {Object} [options] Options for the ban 359 * @param {number} [options.days=0] Number of days of messages to delete 360 * @param {string} [options.reason] Reason for banning 361 * @returns {Promise<GuildMember>} 362 * @example 363 * // ban a guild member 364 * guildMember.ban({ days: 7, reason: 'They deserved it' }) 365 * .then(console.log) 366 * .catch(console.error); 367 */ 368 ban(options) { 369 return this.guild.members.ban(this, options); 370 } 371 372 /** 373 * Fetches this GuildMember. 374 * @returns {Promise<GuildMember>} 375 */ 376 fetch() { 377 return this.guild.members.fetch(this.id, true); 378 } 379 380 /** 381 * When concatenated with a string, this automatically returns the user's mention instead of the GuildMember object. 382 * @returns {string} 383 * @example 384 * // Logs: Hello from <@123456789012345678>! 385 * console.log(`Hello from ${member}!`); 386 */ 387 toString() { 388 return `<@${this.nickname ? '!' : ''}${this.user.id}>`; 389 } 390 391 toJSON() { 392 return super.toJSON({ 393 guild: 'guildID', 394 user: 'userID', 395 displayName: true, 396 speaking: false, 397 lastMessage: false, 398 lastMessageID: false, 399 roles: true, 400 }); 401 } 402 403 // These are here only for documentation purposes - they are implemented by TextBasedChannel 404 /* eslint-disable no-empty-function */ 405 send() {} 406 } 407 408 TextBasedChannel.applyToClass(GuildMember); 409 410 module.exports = GuildMember;