GuildChannel.js (21333B)
1 'use strict'; 2 3 const Channel = require('./Channel'); 4 const Invite = require('./Invite'); 5 const PermissionOverwrites = require('./PermissionOverwrites'); 6 const Role = require('./Role'); 7 const { Error, TypeError } = require('../errors'); 8 const Collection = require('../util/Collection'); 9 const Permissions = require('../util/Permissions'); 10 const Util = require('../util/Util'); 11 12 /** 13 * Represents a guild channel from any of the following: 14 * - {@link TextChannel} 15 * - {@link VoiceChannel} 16 * - {@link CategoryChannel} 17 * - {@link NewsChannel} 18 * - {@link StoreChannel} 19 * @extends {Channel} 20 */ 21 class GuildChannel extends Channel { 22 /** 23 * @param {Guild} guild The guild the guild channel is part of 24 * @param {Object} data The data for the guild channel 25 */ 26 constructor(guild, data) { 27 super(guild.client, data); 28 29 /** 30 * The guild the channel is in 31 * @type {Guild} 32 */ 33 this.guild = guild; 34 } 35 36 _patch(data) { 37 super._patch(data); 38 39 /** 40 * The name of the guild channel 41 * @type {string} 42 */ 43 this.name = data.name; 44 45 /** 46 * The raw position of the channel from discord 47 * @type {number} 48 */ 49 this.rawPosition = data.position; 50 51 /** 52 * The ID of the category parent of this channel 53 * @type {?Snowflake} 54 */ 55 this.parentID = data.parent_id; 56 57 /** 58 * A map of permission overwrites in this channel for roles and users 59 * @type {Collection<Snowflake, PermissionOverwrites>} 60 */ 61 this.permissionOverwrites = new Collection(); 62 if (data.permission_overwrites) { 63 for (const overwrite of data.permission_overwrites) { 64 this.permissionOverwrites.set(overwrite.id, new PermissionOverwrites(this, overwrite)); 65 } 66 } 67 } 68 69 /** 70 * The category parent of this channel 71 * @type {?CategoryChannel} 72 * @readonly 73 */ 74 get parent() { 75 return this.guild.channels.cache.get(this.parentID) || null; 76 } 77 78 /** 79 * If the permissionOverwrites match the parent channel, null if no parent 80 * @type {?boolean} 81 * @readonly 82 */ 83 get permissionsLocked() { 84 if (!this.parent) return null; 85 if (this.permissionOverwrites.size !== this.parent.permissionOverwrites.size) return false; 86 return this.permissionOverwrites.every((value, key) => { 87 const testVal = this.parent.permissionOverwrites.get(key); 88 return ( 89 testVal !== undefined && 90 testVal.deny.bitfield === value.deny.bitfield && 91 testVal.allow.bitfield === value.allow.bitfield 92 ); 93 }); 94 } 95 96 /** 97 * The position of the channel 98 * @type {number} 99 * @readonly 100 */ 101 get position() { 102 const sorted = this.guild._sortedChannels(this); 103 return sorted.array().indexOf(sorted.get(this.id)); 104 } 105 106 /** 107 * Gets the overall set of permissions for a member or role in this channel, taking into account channel overwrites. 108 * @param {GuildMemberResolvable|RoleResolvable} memberOrRole The member or role to obtain the overall permissions for 109 * @returns {?Readonly<Permissions>} 110 */ 111 permissionsFor(memberOrRole) { 112 const member = this.guild.members.resolve(memberOrRole); 113 if (member) return this.memberPermissions(member); 114 const role = this.guild.roles.resolve(memberOrRole); 115 if (role) return this.rolePermissions(role); 116 return null; 117 } 118 119 overwritesFor(member, verified = false, roles = null) { 120 if (!verified) member = this.guild.members.resolve(member); 121 if (!member) return []; 122 123 roles = roles || member.roles.cache; 124 const roleOverwrites = []; 125 let memberOverwrites; 126 let everyoneOverwrites; 127 128 for (const overwrite of this.permissionOverwrites.values()) { 129 if (overwrite.id === this.guild.id) { 130 everyoneOverwrites = overwrite; 131 } else if (roles.has(overwrite.id)) { 132 roleOverwrites.push(overwrite); 133 } else if (overwrite.id === member.id) { 134 memberOverwrites = overwrite; 135 } 136 } 137 138 return { 139 everyone: everyoneOverwrites, 140 roles: roleOverwrites, 141 member: memberOverwrites, 142 }; 143 } 144 145 /** 146 * Gets the overall set of permissions for a member in this channel, taking into account channel overwrites. 147 * @param {GuildMember} member The member to obtain the overall permissions for 148 * @returns {Readonly<Permissions>} 149 * @private 150 */ 151 memberPermissions(member) { 152 if (member.id === this.guild.ownerID) return new Permissions(Permissions.ALL).freeze(); 153 154 const roles = member.roles.cache; 155 const permissions = new Permissions(roles.map(role => role.permissions)); 156 157 if (permissions.has(Permissions.FLAGS.ADMINISTRATOR)) return new Permissions(Permissions.ALL).freeze(); 158 159 const overwrites = this.overwritesFor(member, true, roles); 160 161 return permissions 162 .remove(overwrites.everyone ? overwrites.everyone.deny : 0) 163 .add(overwrites.everyone ? overwrites.everyone.allow : 0) 164 .remove(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.deny) : 0) 165 .add(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.allow) : 0) 166 .remove(overwrites.member ? overwrites.member.deny : 0) 167 .add(overwrites.member ? overwrites.member.allow : 0) 168 .freeze(); 169 } 170 171 /** 172 * Gets the overall set of permissions for a role in this channel, taking into account channel overwrites. 173 * @param {Role} role The role to obtain the overall permissions for 174 * @returns {Readonly<Permissions>} 175 * @private 176 */ 177 rolePermissions(role) { 178 if (role.permissions.has(Permissions.FLAGS.ADMINISTRATOR)) return new Permissions(Permissions.ALL).freeze(); 179 180 const everyoneOverwrites = this.permissionOverwrites.get(this.guild.id); 181 const roleOverwrites = this.permissionOverwrites.get(role.id); 182 183 return role.permissions 184 .remove(everyoneOverwrites ? everyoneOverwrites.deny : 0) 185 .add(everyoneOverwrites ? everyoneOverwrites.allow : 0) 186 .remove(roleOverwrites ? roleOverwrites.deny : 0) 187 .add(roleOverwrites ? roleOverwrites.allow : 0) 188 .freeze(); 189 } 190 191 /** 192 * Replaces the permission overwrites in this channel. 193 * @param {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} overwrites 194 * Permission overwrites the channel gets updated with 195 * @param {string} [reason] Reason for updating the channel overwrites 196 * @returns {Promise<GuildChannel>} 197 * @example 198 * channel.overwritePermissions([ 199 * { 200 * id: message.author.id, 201 * deny: ['VIEW_CHANNEL'], 202 * }, 203 * ], 'Needed to change permissions'); 204 */ 205 overwritePermissions(overwrites, reason) { 206 if (!Array.isArray(overwrites) && !(overwrites instanceof Collection)) { 207 return Promise.reject( 208 new TypeError('INVALID_TYPE', 'overwrites', 'Array or Collection of Permission Overwrites', true), 209 ); 210 } 211 return this.edit({ permissionOverwrites: overwrites, reason }).then(() => this); 212 } 213 214 /** 215 * Updates Overwrites for a user or role in this channel. (creates if non-existent) 216 * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update 217 * @param {PermissionOverwriteOptions} options The options for the update 218 * @param {string} [reason] Reason for creating/editing this overwrite 219 * @returns {Promise<GuildChannel>} 220 * @example 221 * // Update or Create permission overwrites for a message author 222 * message.channel.updateOverwrite(message.author, { 223 * SEND_MESSAGES: false 224 * }) 225 * .then(channel => console.log(channel.permissionOverwrites.get(message.author.id))) 226 * .catch(console.error); 227 */ 228 updateOverwrite(userOrRole, options, reason) { 229 userOrRole = this.guild.roles.resolve(userOrRole) || this.client.users.resolve(userOrRole); 230 if (!userOrRole) return Promise.reject(new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role', true)); 231 232 const existing = this.permissionOverwrites.get(userOrRole.id); 233 if (existing) return existing.update(options, reason).then(() => this); 234 return this.createOverwrite(userOrRole, options, reason); 235 } 236 237 /** 238 * Overwrites the permissions for a user or role in this channel. (replaces if existent) 239 * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update 240 * @param {PermissionOverwriteOptions} options The options for the update 241 * @param {string} [reason] Reason for creating/editing this overwrite 242 * @returns {Promise<GuildChannel>} 243 * @example 244 * // Create or Replace permissions overwrites for a message author 245 * message.channel.createOverwrite(message.author, { 246 * SEND_MESSAGES: false 247 * }) 248 * .then(channel => console.log(channel.permissionOverwrites.get(message.author.id))) 249 * .catch(console.error); 250 */ 251 createOverwrite(userOrRole, options, reason) { 252 userOrRole = this.guild.roles.resolve(userOrRole) || this.client.users.resolve(userOrRole); 253 if (!userOrRole) return Promise.reject(new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role', true)); 254 255 const type = userOrRole instanceof Role ? 'role' : 'member'; 256 const { allow, deny } = PermissionOverwrites.resolveOverwriteOptions(options); 257 258 return this.client.api 259 .channels(this.id) 260 .permissions[userOrRole.id].put({ 261 data: { id: userOrRole.id, type, allow: allow.bitfield, deny: deny.bitfield }, 262 reason, 263 }) 264 .then(() => this); 265 } 266 267 /** 268 * Locks in the permission overwrites from the parent channel. 269 * @returns {Promise<GuildChannel>} 270 */ 271 lockPermissions() { 272 if (!this.parent) return Promise.reject(new Error('GUILD_CHANNEL_ORPHAN')); 273 const permissionOverwrites = this.parent.permissionOverwrites.map(overwrite => overwrite.toJSON()); 274 return this.edit({ permissionOverwrites }); 275 } 276 277 /** 278 * A collection of members that can see this channel, mapped by their ID 279 * @type {Collection<Snowflake, GuildMember>} 280 * @readonly 281 */ 282 get members() { 283 const members = new Collection(); 284 for (const member of this.guild.members.cache.values()) { 285 if (this.permissionsFor(member).has('VIEW_CHANNEL', false)) { 286 members.set(member.id, member); 287 } 288 } 289 return members; 290 } 291 292 /** 293 * The data for a guild channel. 294 * @typedef {Object} ChannelData 295 * @property {string} [name] The name of the channel 296 * @property {number} [position] The position of the channel 297 * @property {string} [topic] The topic of the text channel 298 * @property {boolean} [nsfw] Whether the channel is NSFW 299 * @property {number} [bitrate] The bitrate of the voice channel 300 * @property {number} [userLimit] The user limit of the voice channel 301 * @property {Snowflake} [parentID] The parent ID of the channel 302 * @property {boolean} [lockPermissions] 303 * Lock the permissions of the channel to what the parent's permissions are 304 * @property {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [permissionOverwrites] 305 * Permission overwrites for the channel 306 * @property {number} [rateLimitPerUser] The ratelimit per user for the channel in seconds 307 */ 308 309 /** 310 * Edits the channel. 311 * @param {ChannelData} data The new data for the channel 312 * @param {string} [reason] Reason for editing this channel 313 * @returns {Promise<GuildChannel>} 314 * @example 315 * // Edit a channel 316 * channel.edit({ name: 'new-channel' }) 317 * .then(console.log) 318 * .catch(console.error); 319 */ 320 async edit(data, reason) { 321 if (typeof data.position !== 'undefined') { 322 await Util.setPosition( 323 this, 324 data.position, 325 false, 326 this.guild._sortedChannels(this), 327 this.client.api.guilds(this.guild.id).channels, 328 reason, 329 ).then(updatedChannels => { 330 this.client.actions.GuildChannelsPositionUpdate.handle({ 331 guild_id: this.guild.id, 332 channels: updatedChannels, 333 }); 334 }); 335 } 336 337 const permission_overwrites = 338 data.permissionOverwrites && data.permissionOverwrites.map(o => PermissionOverwrites.resolve(o, this.guild)); 339 340 const newData = await this.client.api.channels(this.id).patch({ 341 data: { 342 name: (data.name || this.name).trim(), 343 topic: data.topic, 344 nsfw: data.nsfw, 345 bitrate: data.bitrate || this.bitrate, 346 user_limit: typeof data.userLimit !== 'undefined' ? data.userLimit : this.userLimit, 347 parent_id: data.parentID, 348 lock_permissions: data.lockPermissions, 349 rate_limit_per_user: data.rateLimitPerUser, 350 permission_overwrites, 351 }, 352 reason, 353 }); 354 355 const clone = this._clone(); 356 clone._patch(newData); 357 return clone; 358 } 359 360 /** 361 * Sets a new name for the guild channel. 362 * @param {string} name The new name for the guild channel 363 * @param {string} [reason] Reason for changing the guild channel's name 364 * @returns {Promise<GuildChannel>} 365 * @example 366 * // Set a new channel name 367 * channel.setName('not_general') 368 * .then(newChannel => console.log(`Channel's new name is ${newChannel.name}`)) 369 * .catch(console.error); 370 */ 371 setName(name, reason) { 372 return this.edit({ name }, reason); 373 } 374 375 /** 376 * Sets the category parent of this channel. 377 * @param {?CategoryChannel|Snowflake} channel Parent channel 378 * @param {Object} [options={}] Options to pass 379 * @param {boolean} [options.lockPermissions=true] Lock the permissions to what the parent's permissions are 380 * @param {string} [options.reason] Reason for modifying the parent of this channel 381 * @returns {Promise<GuildChannel>} 382 * @example 383 * // Add a parent to a channel 384 * message.channel.setParent('355908108431917066', { lockPermissions: false }) 385 * .then(channel => console.log(`New parent of ${message.channel.name}: ${channel.name}`)) 386 * .catch(console.error); 387 */ 388 setParent(channel, { lockPermissions = true, reason } = {}) { 389 return this.edit( 390 { 391 // eslint-disable-next-line no-prototype-builtins 392 parentID: channel !== null ? (channel.hasOwnProperty('id') ? channel.id : channel) : null, 393 lockPermissions, 394 }, 395 reason, 396 ); 397 } 398 399 /** 400 * Sets a new topic for the guild channel. 401 * @param {string} topic The new topic for the guild channel 402 * @param {string} [reason] Reason for changing the guild channel's topic 403 * @returns {Promise<GuildChannel>} 404 * @example 405 * // Set a new channel topic 406 * channel.setTopic('needs more rate limiting') 407 * .then(newChannel => console.log(`Channel's new topic is ${newChannel.topic}`)) 408 * .catch(console.error); 409 */ 410 setTopic(topic, reason) { 411 return this.edit({ topic }, reason); 412 } 413 414 /** 415 * Sets a new position for the guild channel. 416 * @param {number} position The new position for the guild channel 417 * @param {Object} [options] Options for setting position 418 * @param {boolean} [options.relative=false] Change the position relative to its current value 419 * @param {string} [options.reason] Reason for changing the position 420 * @returns {Promise<GuildChannel>} 421 * @example 422 * // Set a new channel position 423 * channel.setPosition(2) 424 * .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`)) 425 * .catch(console.error); 426 */ 427 setPosition(position, { relative, reason } = {}) { 428 return Util.setPosition( 429 this, 430 position, 431 relative, 432 this.guild._sortedChannels(this), 433 this.client.api.guilds(this.guild.id).channels, 434 reason, 435 ).then(updatedChannels => { 436 this.client.actions.GuildChannelsPositionUpdate.handle({ 437 guild_id: this.guild.id, 438 channels: updatedChannels, 439 }); 440 return this; 441 }); 442 } 443 444 /** 445 * Creates an invite to this guild channel. 446 * @param {Object} [options={}] Options for the invite 447 * @param {boolean} [options.temporary=false] Whether members that joined via the invite should be automatically 448 * kicked after 24 hours if they have not yet received a role 449 * @param {number} [options.maxAge=86400] How long the invite should last (in seconds, 0 for forever) 450 * @param {number} [options.maxUses=0] Maximum number of uses 451 * @param {boolean} [options.unique=false] Create a unique invite, or use an existing one with similar settings 452 * @param {string} [options.reason] Reason for creating this 453 * @returns {Promise<Invite>} 454 * @example 455 * // Create an invite to a channel 456 * channel.createInvite() 457 * .then(invite => console.log(`Created an invite with a code of ${invite.code}`)) 458 * .catch(console.error); 459 */ 460 createInvite({ temporary = false, maxAge = 86400, maxUses = 0, unique, reason } = {}) { 461 return this.client.api 462 .channels(this.id) 463 .invites.post({ 464 data: { 465 temporary, 466 max_age: maxAge, 467 max_uses: maxUses, 468 unique, 469 }, 470 reason, 471 }) 472 .then(invite => new Invite(this.client, invite)); 473 } 474 475 /** 476 * Fetches a collection of invites to this guild channel. 477 * Resolves with a collection mapping invites by their codes. 478 * @returns {Promise<Collection<string, Invite>>} 479 */ 480 async fetchInvites() { 481 const inviteItems = await this.client.api.channels(this.id).invites.get(); 482 const invites = new Collection(); 483 for (const inviteItem of inviteItems) { 484 const invite = new Invite(this.client, inviteItem); 485 invites.set(invite.code, invite); 486 } 487 return invites; 488 } 489 490 /* eslint-disable max-len */ 491 /** 492 * Clones this channel. 493 * @param {Object} [options] The options 494 * @param {string} [options.name=this.name] Name of the new channel 495 * @param {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [options.permissionOverwrites=this.permissionOverwrites] 496 * Permission overwrites of the new channel 497 * @param {string} [options.type=this.type] Type of the new channel 498 * @param {string} [options.topic=this.topic] Topic of the new channel (only text) 499 * @param {boolean} [options.nsfw=this.nsfw] Whether the new channel is nsfw (only text) 500 * @param {number} [options.bitrate=this.bitrate] Bitrate of the new channel in bits (only voice) 501 * @param {number} [options.userLimit=this.userLimit] Maximum amount of users allowed in the new channel (only voice) 502 * @param {number} [options.rateLimitPerUser=ThisType.rateLimitPerUser] Ratelimit per user for the new channel (only text) 503 * @param {ChannelResolvable} [options.parent=this.parent] Parent of the new channel 504 * @param {string} [options.reason] Reason for cloning this channel 505 * @returns {Promise<GuildChannel>} 506 */ 507 clone(options = {}) { 508 Util.mergeDefault( 509 { 510 name: this.name, 511 permissionOverwrites: this.permissionOverwrites, 512 topic: this.topic, 513 type: this.type, 514 nsfw: this.nsfw, 515 parent: this.parent, 516 bitrate: this.bitrate, 517 userLimit: this.userLimit, 518 rateLimitPerUser: this.rateLimitPerUser, 519 reason: null, 520 }, 521 options, 522 ); 523 return this.guild.channels.create(options.name, options); 524 } 525 /* eslint-enable max-len */ 526 527 /** 528 * Checks if this channel has the same type, topic, position, name, overwrites and ID as another channel. 529 * In most cases, a simple `channel.id === channel2.id` will do, and is much faster too. 530 * @param {GuildChannel} channel Channel to compare with 531 * @returns {boolean} 532 */ 533 equals(channel) { 534 let equal = 535 channel && 536 this.id === channel.id && 537 this.type === channel.type && 538 this.topic === channel.topic && 539 this.position === channel.position && 540 this.name === channel.name; 541 542 if (equal) { 543 if (this.permissionOverwrites && channel.permissionOverwrites) { 544 equal = this.permissionOverwrites.equals(channel.permissionOverwrites); 545 } else { 546 equal = !this.permissionOverwrites && !channel.permissionOverwrites; 547 } 548 } 549 550 return equal; 551 } 552 553 /** 554 * Whether the channel is deletable by the client user 555 * @type {boolean} 556 * @readonly 557 */ 558 get deletable() { 559 return this.permissionsFor(this.client.user).has(Permissions.FLAGS.MANAGE_CHANNELS, false); 560 } 561 562 /** 563 * Whether the channel is manageable by the client user 564 * @type {boolean} 565 * @readonly 566 */ 567 get manageable() { 568 if (this.client.user.id === this.guild.ownerID) return true; 569 if (this.type === 'voice') { 570 if (!this.permissionsFor(this.client.user).has(Permissions.FLAGS.CONNECT, false)) { 571 return false; 572 } 573 } else if (!this.viewable) { 574 return false; 575 } 576 return this.permissionsFor(this.client.user).has(Permissions.FLAGS.MANAGE_CHANNELS, false); 577 } 578 579 /** 580 * Whether the channel is viewable by the client user 581 * @type {boolean} 582 * @readonly 583 */ 584 get viewable() { 585 if (this.client.user.id === this.guild.ownerID) return true; 586 const permissions = this.permissionsFor(this.client.user); 587 if (!permissions) return false; 588 return permissions.has(Permissions.FLAGS.VIEW_CHANNEL, false); 589 } 590 591 /** 592 * Deletes this channel. 593 * @param {string} [reason] Reason for deleting this channel 594 * @returns {Promise<GuildChannel>} 595 * @example 596 * // Delete the channel 597 * channel.delete('making room for new channels') 598 * .then(console.log) 599 * .catch(console.error); 600 */ 601 delete(reason) { 602 return this.client.api 603 .channels(this.id) 604 .delete({ reason }) 605 .then(() => this); 606 } 607 } 608 609 module.exports = GuildChannel;