buddy

node MVC discord bot
Log | Files | Refs | README

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;