diff --git a/src/index.js b/src/index.js index 4734ea1b50ac..43c46a4f5bc0 100644 --- a/src/index.js +++ b/src/index.js @@ -47,6 +47,7 @@ module.exports = { ReactionManager: require('./managers/ReactionManager'), ReactionUserManager: require('./managers/ReactionUserManager'), MessageManager: require('./managers/MessageManager'), + PermissionOverwriteManager: require('./managers/PermissionOverwriteManager'), PresenceManager: require('./managers/PresenceManager'), RoleManager: require('./managers/RoleManager'), ThreadManager: require('./managers/ThreadManager'), diff --git a/src/managers/PermissionOverwriteManager.js b/src/managers/PermissionOverwriteManager.js new file mode 100644 index 000000000000..bd619828124d --- /dev/null +++ b/src/managers/PermissionOverwriteManager.js @@ -0,0 +1,147 @@ +'use strict'; + +const BaseManager = require('./BaseManager'); +const { TypeError } = require('../errors'); +const PermissionOverwrites = require('../structures/PermissionOverwrites'); +const Role = require('../structures/Role'); +const Collection = require('../util/Collection'); +const { OverwriteTypes } = require('../util/Constants'); + +/** + * Manages API methods for guild channel permission overwrites and stores their cache. + * @extends {BaseManager} + */ +class PermissionOverwriteManager extends BaseManager { + constructor(channel, iterable) { + super(channel.client, iterable, PermissionOverwrites); + + /** + * The channel of the permission overwrite this manager belongs to + * @type {GuildChannel} + */ + this.channel = channel; + } + + /** + * The cache of this Manager + * @type {Collection} + * @name PermissionOverwriteManager#cache + */ + + add(data, cache) { + return super.add(data, cache, { extras: [this.channel] }); + } + + /** + * Replaces the permission overwrites in this channel. + * @param {OverwriteResolvable[]|Collection} overwrites + * Permission overwrites the channel gets updated with + * @param {string} [reason] Reason for updating the channel overwrites + * @returns {Promise} + * @example + * message.channel.permissionOverwrites.set([ + * { + * id: message.author.id, + * deny: [Permissions.FLAGS.VIEW_CHANNEL], + * }, + * ], 'Needed to change permissions'); + */ + set(overwrites, reason) { + if (!Array.isArray(overwrites) && !(overwrites instanceof Collection)) { + throw new TypeError('INVALID_TYPE', 'overwrites', 'Array or Collection of Permission Overwrites', true); + } + return this.channel.edit({ permissionOverwrites: overwrites, reason }); + } + + /** + * Extra information about the overwrite + * @typedef {Object} GuildChannelOverwriteOptions + * @property {string} [reason] Reason for creating/editing this overwrite + * @property {number} [type] The type of overwrite, either `0` for a role or `1` for a member. Use this to bypass + * automatic resolution of type that results in an error for uncached structure + */ + + /** + * Creates or edits permission overwrites for a user or role in this channel. + * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update + * @param {PermissionOverwriteOptions} options The options for the update + * @param {GuildChannelOverwriteOptions} [overwriteOptions] The extra information for the update + * @param {PermissionOverwrites} [existing] The existing overwrites to merge with this update + * @returns {Promise} + * @private + */ + async upsert(userOrRole, options, overwriteOptions = {}, existing) { + let userOrRoleID = this.channel.guild.roles.resolveID(userOrRole) ?? this.client.users.resolveID(userOrRole); + let { type, reason } = overwriteOptions; + if (typeof type !== 'number') { + userOrRole = this.channel.guild.roles.resolve(userOrRole) ?? this.client.users.resolve(userOrRole); + if (!userOrRole) return Promise.reject(new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role')); + type = userOrRole instanceof Role ? OverwriteTypes.role : OverwriteTypes.member; + } + + const { allow, deny } = PermissionOverwrites.resolveOverwriteOptions(options, existing); + + await this.client.api + .channels(this.channel.id) + .permissions(userOrRoleID) + .put({ + data: { id: userOrRoleID, type, allow, deny }, + reason, + }); + return this.channel; + } + + /** + * Creates permission overwrites for a user or role in this channel, or replaces them if already present. + * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update + * @param {PermissionOverwriteOptions} options The options for the update + * @param {GuildChannelOverwriteOptions} [overwriteOptions] The extra information for the update + * @returns {Promise} + * @example + * // Create or Replace permission overwrites for a message author + * message.channel.permissionOverwrites.create(message.author, { + * SEND_MESSAGES: false + * }) + * .then(channel => console.log(channel.permissionOverwrites.cache.get(message.author.id))) + * .catch(console.error); + */ + create(userOrRole, options, overwriteOptions) { + return this.upsert(userOrRole, options, overwriteOptions); + } + + /** + * Edits permission overwrites for a user or role in this channel, or creates an entry if not already present. + * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update + * @param {PermissionOverwriteOptions} options The options for the update + * @param {GuildChannelOverwriteOptions} [overwriteOptions] The extra information for the update + * @returns {Promise} + * @example + * // Edit or Create permission overwrites for a message author + * message.channel.permissionOverwrites.edit(message.author, { + * SEND_MESSAGES: false + * }) + * .then(channel => console.log(channel.permissionOverwrites.cache.get(message.author.id))) + * .catch(console.error); + */ + edit(userOrRole, options, overwriteOptions) { + userOrRole = this.channel.guild.roles.resolveID(userOrRole) ?? this.client.users.resolveID(userOrRole); + const existing = this.cache.get(userOrRole); + return this.upsert(userOrRole, options, overwriteOptions, existing); + } + + /** + * Deletes permission overwrites for a user or role in this channel. + * @param {UserResolvable|RoleResolvable} userOrRole The user or role to delete + * @param {string} [reason] The reason for deleting the overwrite + * @returns {GuildChannel} + */ + async delete(userOrRole, reason) { + userOrRole = this.channel.guild.roles.resolveID(userOrRole) ?? this.client.users.resolveID(userOrRole); + if (!userOrRole) throw new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role'); + + await this.client.api.channels(this.channel.id).permissions(userOrRole.id).delete({ reason }); + return this.channel; + } +} + +module.exports = PermissionOverwriteManager; diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index abcab5ccbdb9..89445e34cbff 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -3,11 +3,10 @@ const Channel = require('./Channel'); const Invite = require('./Invite'); const PermissionOverwrites = require('./PermissionOverwrites'); -const Role = require('./Role'); -const { Error, TypeError } = require('../errors'); +const { Error } = require('../errors'); +const PermissionOverwriteManager = require('../managers/PermissionOverwriteManager'); const Collection = require('../util/Collection'); const { ChannelTypes } = require('../util/Constants'); -const { OverwriteTypes } = require('../util/Constants'); const Permissions = require('../util/Permissions'); const Util = require('../util/Util'); @@ -28,7 +27,7 @@ class GuildChannel extends Channel { * @param {APIChannel} data The data for the guild channel */ constructor(guild, data) { - super(guild.client, data); + super(guild.client, data, false); /** * The guild the channel is in @@ -37,7 +36,13 @@ class GuildChannel extends Channel { this.guild = guild; this.parentID = this.parentID ?? null; - this.permissionOverwrites = this.permissionOverwrites ?? new Collection(); + /** + * A manager of permission overwrites that belong to this channel + * @type {PermissionOverwriteManager} + */ + this.permissionOverwrites = new PermissionOverwriteManager(this); + + this._patch(data); } _patch(data) { @@ -68,13 +73,9 @@ class GuildChannel extends Channel { } if ('permission_overwrites' in data) { - /** - * A map of permission overwrites in this channel for roles and users - * @type {Collection} - */ - this.permissionOverwrites = new Collection(); + this.permissionOverwrites.cache.clear(); for (const overwrite of data.permission_overwrites) { - this.permissionOverwrites.set(overwrite.id, new PermissionOverwrites(this, overwrite)); + this.permissionOverwrites.add(overwrite); } } } @@ -220,102 +221,6 @@ class GuildChannel extends Channel { .freeze(); } - /** - * Replaces the permission overwrites in this channel. - * @param {OverwriteResolvable[]|Collection} overwrites - * Permission overwrites the channel gets updated with - * @param {string} [reason] Reason for updating the channel overwrites - * @returns {Promise} - * @example - * channel.overwritePermissions([ - * { - * id: message.author.id, - * deny: [Permissions.FLAGS.VIEW_CHANNEL], - * }, - * ], 'Needed to change permissions'); - */ - async overwritePermissions(overwrites, reason) { - if (!Array.isArray(overwrites) && !(overwrites instanceof Collection)) { - throw new TypeError('INVALID_TYPE', 'overwrites', 'Array or Collection of Permission Overwrites', true); - } - await this.edit({ permissionOverwrites: overwrites }, reason); - return this; - } - - /** - * Extra information about the overwrite - * @typedef {Object} GuildChannelOverwriteOptions - * @property {string} [reason] Reason for creating/editing this overwrite - * @property {number} [type] The type of overwrite, either `0` for a role or `1` for a member. Use this to bypass - * automatic resolution of type that results in an error for uncached structure - */ - - /** - * Updates permission overwrites for a user or role in this channel, or creates an entry if not already present. - * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update - * @param {PermissionOverwriteOptions} options The options for the update - * @param {GuildChannelOverwriteOptions} [overwriteOptions] The extra information for the update - * @returns {Promise} - * @example - * // Update or Create permission overwrites for a message author - * message.channel.updateOverwrite(message.author, { - * SEND_MESSAGES: false - * }) - * .then(channel => console.log(channel.permissionOverwrites.get(message.author.id))) - * .catch(console.error); - */ - async updateOverwrite(userOrRole, options, overwriteOptions = {}) { - const userOrRoleID = this.guild.roles.resolveID(userOrRole) ?? this.client.users.resolveID(userOrRole); - const { reason } = overwriteOptions; - const existing = this.permissionOverwrites.get(userOrRoleID); - if (existing) { - await existing.update(options, reason); - } else { - await this.createOverwrite(userOrRole, options, overwriteOptions); - } - return this; - } - - /** - * Creates permission overwrites for a user or role in this channel, or replaces them if already present. - * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update - * @param {PermissionOverwriteOptions} options The options for the update - * @param {GuildChannelOverwriteOptions} [overwriteOptions] The extra information for the update - * @returns {Promise} - * @example - * // Create or Replace permission overwrites for a message author - * message.channel.createOverwrite(message.author, { - * SEND_MESSAGES: false - * }) - * .then(channel => console.log(channel.permissionOverwrites.get(message.author.id))) - * .catch(console.error); - */ - createOverwrite(userOrRole, options, overwriteOptions = {}) { - let userOrRoleID = this.guild.roles.resolveID(userOrRole) ?? this.client.users.resolveID(userOrRole); - let { type, reason } = overwriteOptions; - if (typeof type !== 'number') { - userOrRole = this.guild.roles.resolve(userOrRole) ?? this.client.users.resolve(userOrRole); - if (!userOrRole) return Promise.reject(new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role')); - userOrRoleID = userOrRole.id; - type = userOrRole instanceof Role ? OverwriteTypes.role : OverwriteTypes.member; - } - const { allow, deny } = PermissionOverwrites.resolveOverwriteOptions(options); - - return this.client.api - .channels(this.id) - .permissions(userOrRoleID) - .put({ - data: { - id: userOrRoleID, - type, - allow, - deny, - }, - reason, - }) - .then(() => this); - } - /** * Locks in the permission overwrites from the parent channel. * @returns {Promise} diff --git a/src/structures/PermissionOverwrites.js b/src/structures/PermissionOverwrites.js index 224d20a0f105..c3782745baff 100644 --- a/src/structures/PermissionOverwrites.js +++ b/src/structures/PermissionOverwrites.js @@ -1,5 +1,6 @@ 'use strict'; +const Base = require('./Base'); const Role = require('./Role'); const { TypeError } = require('../errors'); const { OverwriteTypes } = require('../util/Constants'); @@ -7,16 +8,19 @@ const Permissions = require('../util/Permissions'); /** * Represents a permission overwrite for a role or member in a guild channel. + * @extends {Base} */ -class PermissionOverwrites { - constructor(guildChannel, data) { +class PermissionOverwrites extends Base { + constructor(client, data, channel) { + super(client); + /** * The GuildChannel this overwrite is for * @name PermissionOverwrites#channel * @type {GuildChannel} * @readonly */ - Object.defineProperty(this, 'channel', { value: guildChannel }); + Object.defineProperty(this, 'channel', { value: channel }); if (data) this._patch(data); } @@ -48,33 +52,20 @@ class PermissionOverwrites { } /** - * Updates this permissionOverwrites. + * Edits this Permission Overwrite. * @param {PermissionOverwriteOptions} options The options for the update * @param {string} [reason] Reason for creating/editing this overwrite * @returns {Promise} * @example * // Update permission overwrites - * permissionOverwrites.update({ + * permissionOverwrites.edit({ * SEND_MESSAGES: false * }) * .then(channel => console.log(channel.permissionOverwrites.get(message.author.id))) * .catch(console.error); */ - async update(options, reason) { - const { allow, deny } = this.constructor.resolveOverwriteOptions(options, this); - - await this.channel.client.api - .channels(this.channel.id) - .permissions(this.id) - .put({ - data: { - id: this.id, - type: OverwriteTypes[this.type], - allow, - deny, - }, - reason, - }); + async edit(options, reason) { + await this.channel.permissionOverwrites.upsert(this.id, options, { type: OverwriteTypes[this.type], reason }, this); return this; } @@ -84,7 +75,7 @@ class PermissionOverwrites { * @returns {Promise} */ async delete(reason) { - await this.channel.client.api.channels(this.channel.id).permissions(this.id).delete({ reason }); + await this.channel.permissionOverwrites.delete(this.id, reason); return this; } diff --git a/typings/index.d.ts b/typings/index.d.ts index d60c363c4dce..919dc21131c1 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1021,7 +1021,7 @@ declare module 'discord.js' { public name: string; public readonly parent: CategoryChannel | null; public parentID: Snowflake | null; - public permissionOverwrites: Collection; + public permissionOverwrites: PermissionOverwriteManager; public readonly permissionsLocked: boolean | null; public readonly position: number; public rawPosition: number; @@ -1029,30 +1029,16 @@ declare module 'discord.js' { public readonly viewable: boolean; public clone(options?: GuildChannelCloneOptions): Promise; public createInvite(options?: CreateInviteOptions): Promise; - public createOverwrite( - userOrRole: RoleResolvable | UserResolvable, - options: PermissionOverwriteOptions, - overwriteOptions?: GuildChannelOverwriteOptions, - ): Promise; public edit(data: ChannelData, reason?: string): Promise; public equals(channel: GuildChannel): boolean; public fetchInvites(): Promise>; public lockPermissions(): Promise; - public overwritePermissions( - overwrites: readonly OverwriteResolvable[] | Collection, - reason?: string, - ): Promise; public permissionsFor(memberOrRole: GuildMember | Role): Readonly; public permissionsFor(memberOrRole: GuildMemberResolvable | RoleResolvable): Readonly | null; public setName(name: string, reason?: string): Promise; public setParent(channel: CategoryChannel | Snowflake | null, options?: SetParentOptions): Promise; public setPosition(position: number, options?: SetChannelPositionOptions): Promise; public setTopic(topic: string | null, reason?: string): Promise; - public updateOverwrite( - userOrRole: RoleResolvable | UserResolvable, - options: PermissionOverwriteOptions, - overwriteOptions?: GuildChannelOverwriteOptions, - ): Promise; public isText(): this is TextChannel | NewsChannel; } @@ -1633,14 +1619,14 @@ declare module 'discord.js' { public iconURL(options?: StaticImageURLOptions): string | null; } - export class PermissionOverwrites { - constructor(guildChannel: GuildChannel, data?: unknown); + export class PermissionOverwrites extends Base { + constructor(client: Client, data: object, channel: GuildChannel); public allow: Readonly; public readonly channel: GuildChannel; public deny: Readonly; public id: Snowflake; public type: OverwriteType; - public update(options: PermissionOverwriteOptions, reason?: string): Promise; + public edit(options: PermissionOverwriteOptions, reason?: string): Promise; public delete(reason?: string): Promise; public toJSON(): unknown; public static resolveOverwriteOptions( @@ -2609,6 +2595,35 @@ declare module 'discord.js' { public unpin(message: MessageResolvable): Promise; } + export class PermissionOverwriteManager extends BaseManager< + Snowflake, + PermissionOverwrites, + PermissionOverwriteResolvable + > { + constructor(client: Client, iterable?: Iterable); + public set( + overwrites: readonly OverwriteResolvable[] | Collection, + reason?: string, + ): Promise; + private upsert( + userOrRole: RoleResolvable | UserResolvable, + options: PermissionOverwriteOptions, + overwriteOptions?: GuildChannelOverwriteOptions, + existing?: PermissionOverwrites, + ): Promise; + public create( + userOrRole: RoleResolvable | UserResolvable, + options: PermissionOverwriteOptions, + overwriteOptions?: GuildChannelOverwriteOptions, + ): Promise; + public edit( + userOrRole: RoleResolvable | UserResolvable, + options: PermissionOverwriteOptions, + overwriteOptions?: GuildChannelOverwriteOptions, + ): Promise; + public delete(userOrRole: RoleResolvable | UserResolvable, reason?: string): Promise; + } + export class PresenceManager extends BaseManager { constructor(client: Client, iterable?: Iterable); } @@ -3500,6 +3515,11 @@ declare module 'discord.js' { name?: string; } + interface GuildChannelOverwriteOptions { + reason?: string; + type?: number; + } + interface GuildCreateOptions { afkChannelID?: Snowflake | number; afkTimeout?: number; @@ -4007,6 +4027,8 @@ declare module 'discord.js' { type PermissionResolvable = BitFieldResolvable; + type PermissionOverwriteResolvable = UserResolvable | RoleResolvable | PermissionOverwrites; + type PermissionString = | 'CREATE_INSTANT_INVITE' | 'KICK_MEMBERS'