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;