buddy

node MVC discord bot
Log | Files | Refs | README

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;