buddy

node MVC discord bot
Log | Files | Refs | README

Role.js (12081B)


      1 'use strict';
      2 
      3 const Base = require('./Base');
      4 const { Error, TypeError } = require('../errors');
      5 const Permissions = require('../util/Permissions');
      6 const Snowflake = require('../util/Snowflake');
      7 const Util = require('../util/Util');
      8 
      9 /**
     10  * Represents a role on Discord.
     11  * @extends {Base}
     12  */
     13 class Role extends Base {
     14   /**
     15    * @param {Client} client The instantiating client
     16    * @param {Object} data The data for the role
     17    * @param {Guild} guild The guild the role is part of
     18    */
     19   constructor(client, data, guild) {
     20     super(client);
     21 
     22     /**
     23      * The guild that the role belongs to
     24      * @type {Guild}
     25      */
     26     this.guild = guild;
     27 
     28     if (data) this._patch(data);
     29   }
     30 
     31   _patch(data) {
     32     /**
     33      * The ID of the role (unique to the guild it is part of)
     34      * @type {Snowflake}
     35      */
     36     this.id = data.id;
     37 
     38     /**
     39      * The name of the role
     40      * @type {string}
     41      */
     42     this.name = data.name;
     43 
     44     /**
     45      * The base 10 color of the role
     46      * @type {number}
     47      */
     48     this.color = data.color;
     49 
     50     /**
     51      * If true, users that are part of this role will appear in a separate category in the users list
     52      * @type {boolean}
     53      */
     54     this.hoist = data.hoist;
     55 
     56     /**
     57      * The raw position of the role from the API
     58      * @type {number}
     59      */
     60     this.rawPosition = data.position;
     61 
     62     /**
     63      * The permissions of the role
     64      * @type {Readonly<Permissions>}
     65      */
     66     this.permissions = new Permissions(data.permissions).freeze();
     67 
     68     /**
     69      * Whether or not the role is managed by an external service
     70      * @type {boolean}
     71      */
     72     this.managed = data.managed;
     73 
     74     /**
     75      * Whether or not the role can be mentioned by anyone
     76      * @type {boolean}
     77      */
     78     this.mentionable = data.mentionable;
     79 
     80     /**
     81      * Whether the role has been deleted
     82      * @type {boolean}
     83      */
     84     this.deleted = false;
     85   }
     86 
     87   /**
     88    * The timestamp the role was created at
     89    * @type {number}
     90    * @readonly
     91    */
     92   get createdTimestamp() {
     93     return Snowflake.deconstruct(this.id).timestamp;
     94   }
     95 
     96   /**
     97    * The time the role was created at
     98    * @type {Date}
     99    * @readonly
    100    */
    101   get createdAt() {
    102     return new Date(this.createdTimestamp);
    103   }
    104 
    105   /**
    106    * The hexadecimal version of the role color, with a leading hashtag
    107    * @type {string}
    108    * @readonly
    109    */
    110   get hexColor() {
    111     return `#${this.color.toString(16).padStart(6, '0')}`;
    112   }
    113 
    114   /**
    115    * The cached guild members that have this role
    116    * @type {Collection<Snowflake, GuildMember>}
    117    * @readonly
    118    */
    119   get members() {
    120     return this.guild.members.cache.filter(m => m.roles.cache.has(this.id));
    121   }
    122 
    123   /**
    124    * Whether the role is editable by the client user
    125    * @type {boolean}
    126    * @readonly
    127    */
    128   get editable() {
    129     if (this.managed) return false;
    130     const clientMember = this.guild.member(this.client.user);
    131     if (!clientMember.permissions.has(Permissions.FLAGS.MANAGE_ROLES)) return false;
    132     return clientMember.roles.highest.comparePositionTo(this) > 0;
    133   }
    134 
    135   /**
    136    * The position of the role in the role manager
    137    * @type {number}
    138    * @readonly
    139    */
    140   get position() {
    141     const sorted = this.guild._sortedRoles();
    142     return sorted.array().indexOf(sorted.get(this.id));
    143   }
    144 
    145   /**
    146    * Compares this role's position to another role's.
    147    * @param {RoleResolvable} role Role to compare to this one
    148    * @returns {number} Negative number if this role's position is lower (other role's is higher),
    149    * positive number if this one is higher (other's is lower), 0 if equal
    150    */
    151   comparePositionTo(role) {
    152     role = this.guild.roles.resolve(role);
    153     if (!role) throw new TypeError('INVALID_TYPE', 'role', 'Role nor a Snowflake');
    154     return this.constructor.comparePositions(this, role);
    155   }
    156 
    157   /**
    158    * The data for a role.
    159    * @typedef {Object} RoleData
    160    * @property {string} [name] The name of the role
    161    * @property {ColorResolvable} [color] The color of the role, either a hex string or a base 10 number
    162    * @property {boolean} [hoist] Whether or not the role should be hoisted
    163    * @property {number} [position] The position of the role
    164    * @property {PermissionResolvable} [permissions] The permissions of the role
    165    * @property {boolean} [mentionable] Whether or not the role should be mentionable
    166    */
    167 
    168   /**
    169    * Edits the role.
    170    * @param {RoleData} data The new data for the role
    171    * @param {string} [reason] Reason for editing this role
    172    * @returns {Promise<Role>}
    173    * @example
    174    * // Edit a role
    175    * role.edit({ name: 'new role' })
    176    *   .then(updated => console.log(`Edited role ${updated.name} name to ${updated.name}`))
    177    *   .catch(console.error);
    178    */
    179   async edit(data, reason) {
    180     if (typeof data.permissions !== 'undefined') data.permissions = Permissions.resolve(data.permissions);
    181     else data.permissions = this.permissions.bitfield;
    182     if (typeof data.position !== 'undefined') {
    183       await Util.setPosition(
    184         this,
    185         data.position,
    186         false,
    187         this.guild._sortedRoles(),
    188         this.client.api.guilds(this.guild.id).roles,
    189         reason,
    190       ).then(updatedRoles => {
    191         this.client.actions.GuildRolesPositionUpdate.handle({
    192           guild_id: this.guild.id,
    193           roles: updatedRoles,
    194         });
    195       });
    196     }
    197     return this.client.api.guilds[this.guild.id].roles[this.id]
    198       .patch({
    199         data: {
    200           name: data.name || this.name,
    201           color: data.color !== null ? Util.resolveColor(data.color || this.color) : null,
    202           hoist: typeof data.hoist !== 'undefined' ? data.hoist : this.hoist,
    203           permissions: data.permissions,
    204           mentionable: typeof data.mentionable !== 'undefined' ? data.mentionable : this.mentionable,
    205         },
    206         reason,
    207       })
    208       .then(role => {
    209         const clone = this._clone();
    210         clone._patch(role);
    211         return clone;
    212       });
    213   }
    214 
    215   /**
    216    * Returns `channel.permissionsFor(role)`. Returns permissions for a role in a guild channel,
    217    * taking into account permission overwrites.
    218    * @param {ChannelResolvable} channel The guild channel to use as context
    219    * @returns {Readonly<Permissions>}
    220    */
    221   permissionsIn(channel) {
    222     channel = this.guild.channels.resolve(channel);
    223     if (!channel) throw new Error('GUILD_CHANNEL_RESOLVE');
    224     return channel.rolePermissions(this);
    225   }
    226 
    227   /**
    228    * Sets a new name for the role.
    229    * @param {string} name The new name of the role
    230    * @param {string} [reason] Reason for changing the role's name
    231    * @returns {Promise<Role>}
    232    * @example
    233    * // Set the name of the role
    234    * role.setName('new role')
    235    *   .then(updated => console.log(`Edited name of role ${role.name} to ${updated.name}`))
    236    *   .catch(console.error);
    237    */
    238   setName(name, reason) {
    239     return this.edit({ name }, reason);
    240   }
    241 
    242   /**
    243    * Sets a new color for the role.
    244    * @param {ColorResolvable} color The color of the role
    245    * @param {string} [reason] Reason for changing the role's color
    246    * @returns {Promise<Role>}
    247    * @example
    248    * // Set the color of a role
    249    * role.setColor('#FF0000')
    250    *   .then(updated => console.log(`Set color of role to ${updated.color}`))
    251    *   .catch(console.error);
    252    */
    253   setColor(color, reason) {
    254     return this.edit({ color }, reason);
    255   }
    256 
    257   /**
    258    * Sets whether or not the role should be hoisted.
    259    * @param {boolean} hoist Whether or not to hoist the role
    260    * @param {string} [reason] Reason for setting whether or not the role should be hoisted
    261    * @returns {Promise<Role>}
    262    * @example
    263    * // Set the hoist of the role
    264    * role.setHoist(true)
    265    *   .then(r => console.log(`Role hoisted: ${r.hoist}`))
    266    *   .catch(console.error);
    267    */
    268   setHoist(hoist, reason) {
    269     return this.edit({ hoist }, reason);
    270   }
    271 
    272   /**
    273    * Sets the permissions of the role.
    274    * @param {PermissionResolvable} permissions The permissions of the role
    275    * @param {string} [reason] Reason for changing the role's permissions
    276    * @returns {Promise<Role>}
    277    * @example
    278    * // Set the permissions of the role
    279    * role.setPermissions(['KICK_MEMBERS', 'BAN_MEMBERS'])
    280    *   .then(updated => console.log(`Updated permissions to ${updated.permissions.bitfield}`))
    281    *   .catch(console.error);
    282    * @example
    283    * // Remove all permissions from a role
    284    * role.setPermissions(0)
    285    *   .then(updated => console.log(`Updated permissions to ${updated.permissions.bitfield}`))
    286    *   .catch(console.error);
    287    */
    288   setPermissions(permissions, reason) {
    289     return this.edit({ permissions }, reason);
    290   }
    291 
    292   /**
    293    * Sets whether this role is mentionable.
    294    * @param {boolean} mentionable Whether this role should be mentionable
    295    * @param {string} [reason] Reason for setting whether or not this role should be mentionable
    296    * @returns {Promise<Role>}
    297    * @example
    298    * // Make the role mentionable
    299    * role.setMentionable(true)
    300    *   .then(updated => console.log(`Role updated ${updated.name}`))
    301    *   .catch(console.error);
    302    */
    303   setMentionable(mentionable, reason) {
    304     return this.edit({ mentionable }, reason);
    305   }
    306 
    307   /**
    308    * Sets the position of the role.
    309    * @param {number} position The position of the role
    310    * @param {Object} [options] Options for setting position
    311    * @param {boolean} [options.relative=false] Change the position relative to its current value
    312    * @param {string} [options.reason] Reason for changing the position
    313    * @returns {Promise<Role>}
    314    * @example
    315    * // Set the position of the role
    316    * role.setPosition(1)
    317    *   .then(updated => console.log(`Role position: ${updated.position}`))
    318    *   .catch(console.error);
    319    */
    320   setPosition(position, { relative, reason } = {}) {
    321     return Util.setPosition(
    322       this,
    323       position,
    324       relative,
    325       this.guild._sortedRoles(),
    326       this.client.api.guilds(this.guild.id).roles,
    327       reason,
    328     ).then(updatedRoles => {
    329       this.client.actions.GuildRolesPositionUpdate.handle({
    330         guild_id: this.guild.id,
    331         roles: updatedRoles,
    332       });
    333       return this;
    334     });
    335   }
    336 
    337   /**
    338    * Deletes the role.
    339    * @param {string} [reason] Reason for deleting this role
    340    * @returns {Promise<Role>}
    341    * @example
    342    * // Delete a role
    343    * role.delete('The role needed to go')
    344    *   .then(deleted => console.log(`Deleted role ${deleted.name}`))
    345    *   .catch(console.error);
    346    */
    347   delete(reason) {
    348     return this.client.api.guilds[this.guild.id].roles[this.id].delete({ reason }).then(() => {
    349       this.client.actions.GuildRoleDelete.handle({ guild_id: this.guild.id, role_id: this.id });
    350       return this;
    351     });
    352   }
    353 
    354   /**
    355    * Whether this role equals another role. It compares all properties, so for most operations
    356    * it is advisable to just compare `role.id === role2.id` as it is much faster and is often
    357    * what most users need.
    358    * @param {Role} role Role to compare with
    359    * @returns {boolean}
    360    */
    361   equals(role) {
    362     return (
    363       role &&
    364       this.id === role.id &&
    365       this.name === role.name &&
    366       this.color === role.color &&
    367       this.hoist === role.hoist &&
    368       this.position === role.position &&
    369       this.permissions.bitfield === role.permissions.bitfield &&
    370       this.managed === role.managed
    371     );
    372   }
    373 
    374   /**
    375    * When concatenated with a string, this automatically returns the role's mention instead of the Role object.
    376    * @returns {string}
    377    * @example
    378    * // Logs: Role: <@&123456789012345678>
    379    * console.log(`Role: ${role}`);
    380    */
    381   toString() {
    382     if (this.id === this.guild.id) return '@everyone';
    383     return `<@&${this.id}>`;
    384   }
    385 
    386   toJSON() {
    387     return super.toJSON({ createdTimestamp: true });
    388   }
    389 
    390   /**
    391    * Compares the positions of two roles.
    392    * @param {Role} role1 First role to compare
    393    * @param {Role} role2 Second role to compare
    394    * @returns {number} Negative number if the first role's position is lower (second role's is higher),
    395    * positive number if the first's is higher (second's is lower), 0 if equal
    396    */
    397   static comparePositions(role1, role2) {
    398     if (role1.position === role2.position) return role2.id - role1.id;
    399     return role1.position - role2.position;
    400   }
    401 }
    402 
    403 module.exports = Role;