"use strict"; const Base = require("./Base"); const Endpoints = require("../rest/Endpoints"); const User = require("./User"); const VoiceState = require("./VoiceState"); /** * Represents a server member * @prop {Number?} accentColor The user's banner color, or null if no banner color (REST only) * @prop {Array?} activities The member's current activities * @prop {String?} avatar The hash of the member's guild avatar, or null if no guild avatar * @prop {String} avatarURL The URL of the user's avatar which can be either a JPG or GIF * @prop {String?} banner The hash of the user's banner, or null if no banner (REST only) * @prop {String?} bannerURL The URL of the user's banner * @prop {Boolean} bot Whether the user is an OAuth bot or not * @prop {Object?} clientStatus The member's per-client status * @prop {String} clientStatus.web The member's status on web. Either "online", "idle", "dnd", or "offline". Will be "online" for bots * @prop {String} clientStatus.desktop The member's status on desktop. Either "online", "idle", "dnd", or "offline". Will be "offline" for bots * @prop {String} clientStatus.mobile The member's status on mobile. Either "online", "idle", "dnd", or "offline". Will be "offline" for bots * @prop {Number?} communicationDisabledUntil Timestamp of timeout expiry. If `null`, the member is not timed out * @prop {Number} createdAt Timestamp of user creation * @prop {String} defaultAvatar The hash for the default avatar of a user if there is no avatar set * @prop {String} defaultAvatarURL The URL of the user's default avatar * @prop {String} discriminator The discriminator of the user * @prop {Object?} game The active game the member is playing * @prop {String} game.name The name of the active game * @prop {Number} game.type The type of the active game (0 is default, 1 is Twitch, 2 is YouTube) * @prop {String?} game.url The url of the active game * @prop {Guild} guild The guild the member is in * @prop {String} id The ID of the member * @prop {Number?} joinedAt Timestamp of when the member joined the guild * @prop {String} mention A string that mentions the member * @prop {String?} nick The server nickname of the member * @prop {Boolean?} pending Whether the member has passed the guild's Membership Screening requirements * @prop {Permission} permission [DEPRECATED] The guild-wide permissions of the member. Use Member#permissions instead * @prop {Permission} permissions The guild-wide permissions of the member * @prop {Number?} premiumSince Timestamp of when the member boosted the guild * @prop {Array} roles An array of role IDs this member is a part of * @prop {String} staticAvatarURL The URL of the user's avatar (always a JPG) * @prop {String} status The member's status. Either "online", "idle", "dnd", or "offline" * @prop {User} user The user object of the member * @prop {String} username The username of the user * @prop {VoiceState} voiceState The voice state of the member */ class Member extends Base { constructor(data, guild, client) { super(data.id || data.user.id); if(!data.id && data.user) { data.id = data.user.id; } if((this.guild = guild)) { this.user = guild.shard.client.users.get(data.id); if(!this.user && data.user) { this.user = guild.shard.client.users.add(data.user, guild.shard.client); } if(!this.user) { throw new Error("User associated with Member not found: " + data.id); } } else if(data.user) { if(!client) { this.user = new User(data.user); } else { this.user = client.users.update(data.user, client); } } else { this.user = null; } this.nick = null; this.roles = []; this.update(data); } update(data) { if(data.status !== undefined) { this.status = data.status; } if(data.joined_at !== undefined) { this.joinedAt = data.joined_at ? Date.parse(data.joined_at) : null; } if(data.client_status !== undefined) { this.clientStatus = Object.assign({web: "offline", desktop: "offline", mobile: "offline"}, data.client_status); } if(data.activities !== undefined) { this.activities = data.activities; } if(data.premium_since !== undefined) { this.premiumSince = data.premium_since === null ? null : Date.parse(data.premium_since); } if(data.hasOwnProperty("mute") && this.guild) { const state = this.guild.voiceStates.get(this.id); if(data.channel_id === null && !data.mute && !data.deaf && !data.suppress) { this.guild.voiceStates.delete(this.id); } else if(state) { state.update(data); } else if(data.channel_id || data.mute || data.deaf || data.suppress) { this.guild.voiceStates.update(data); } } if(data.nick !== undefined) { this.nick = data.nick; } if(data.roles !== undefined) { this.roles = data.roles; } if(data.pending !== undefined) { this.pending = data.pending; } if(data.avatar !== undefined) { this.avatar = data.avatar; } if(data.communication_disabled_until !== undefined) { if(data.communication_disabled_until !== null) { this.communicationDisabledUntil = Date.parse(data.communication_disabled_until); } else { this.communicationDisabledUntil = data.communication_disabled_until; } } } get accentColor() { return this.user.accentColor; } get avatarURL() { return this.avatar ? this.guild.shard.client._formatImage(Endpoints.GUILD_AVATAR(this.guild.id, this.id, this.avatar)) : this.user.avatarURL; } get banner() { return this.user.banner; } get bannerURL() { return this.user.bannerURL; } get bot() { return this.user.bot; } get createdAt() { return this.user.createdAt; } get defaultAvatar() { return this.user.defaultAvatar; } get defaultAvatarURL() { return this.user.defaultAvatarURL; } get discriminator() { return this.user.discriminator; } get mention() { return `<@!${this.id}>`; } get permission() { this.guild.shard.client.emit("warn", "[DEPRECATED] Member#permission is deprecated. Use Member#permissions instead"); return this.permissions; } get permissions() { return this.guild.permissionsOf(this); } get staticAvatarURL(){ return this.user.staticAvatarURL; } get username() { return this.user.username; } get voiceState() { if(this.guild && this.guild.voiceStates.has(this.id)) { return this.guild.voiceStates.get(this.id); } else { return new VoiceState({ id: this.id }); } } get game() { return this.activities && this.activities.length > 0 ? this.activities[0] : null; } /** * Add a role to the guild member * @arg {String} roleID The ID of the role * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ addRole(roleID, reason) { return this.guild.shard.client.addGuildMemberRole.call(this.guild.shard.client, this.guild.id, this.id, roleID, reason); } /** * Ban the user from the guild * @arg {Number} [deleteMessageDays=0] Number of days to delete messages for, between 0-7 inclusive * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ ban(deleteMessageDays, reason) { return this.guild.shard.client.banGuildMember.call(this.guild.shard.client, this.guild.id, this.id, deleteMessageDays, reason); } /** * Get the member's avatar with the given format and size * @arg {String} [format] The filetype of the avatar ("jpg", "jpeg", "png", "gif", or "webp") * @arg {Number} [size] The size of the avatar (any power of two between 16 and 4096) * @returns {String} */ dynamicAvatarURL(format, size) { if(!this.avatar) { return this.user.dynamicAvatarURL(format, size); } return this.guild.shard.client._formatImage(Endpoints.GUILD_AVATAR(this.guild.id, this.id, this.avatar), format, size); } /** * Edit the guild member * @arg {Object} options The properties to edit * @arg {String?} [options.channelID] The ID of the voice channel to move the member to (must be in voice). Set to `null` to disconnect the member * @arg {Date?} [options.communicationDisabledUntil] When the user's timeout should expire. Set to `null` to instantly remove timeout * @arg {Boolean} [options.deaf] Server deafen the user * @arg {Boolean} [options.mute] Server mute the user * @arg {String} [options.nick] Set the user's server nickname, "" to remove * @arg {Array} [options.roles] The array of role IDs the user should have * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ edit(options, reason) { return this.guild.shard.client.editGuildMember.call(this.guild.shard.client, this.guild.id, this.id, options, reason); } /** * Kick the member from the guild * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ kick(reason) { return this.guild.shard.client.kickGuildMember.call(this.guild.shard.client, this.guild.id, this.id, reason); } /** * Remove a role from the guild member * @arg {String} roleID The ID of the role * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ removeRole(roleID, reason) { return this.guild.shard.client.removeGuildMemberRole.call(this.guild.shard.client, this.guild.id, this.id, roleID, reason); } /** * Unban the user from the guild * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ unban(reason) { return this.guild.shard.client.unbanGuildMember.call(this.guild.shard.client, this.guild.id, this.id, reason); } toJSON(props = []) { return super.toJSON([ "activities", "communicationDisabledUntil", "joinedAt", "nick", "pending", "premiumSince", "roles", "status", "user", "voiceState", ...props ]); } } module.exports = Member;