Skip to content

Commit

Permalink
feat: PermissionOverwriteManager (#5318)
Browse files Browse the repository at this point in the history
Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>
  • Loading branch information
Ishmaam Khan and vladfrangu committed Jul 3, 2021
1 parent a11a105 commit e7ad2fe
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 146 deletions.
1 change: 1 addition & 0 deletions src/index.js
Expand Up @@ -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'),
Expand Down
147 changes: 147 additions & 0 deletions 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<Snowflake, PermissionOverwrites>}
* @name PermissionOverwriteManager#cache
*/

add(data, cache) {
return super.add(data, cache, { extras: [this.channel] });
}

/**
* Replaces the permission overwrites in this channel.
* @param {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} overwrites
* Permission overwrites the channel gets updated with
* @param {string} [reason] Reason for updating the channel overwrites
* @returns {Promise<GuildChannel>}
* @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<GuildChannel>}
* @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<GuildChannel>}
* @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<GuildChannel>}
* @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;
119 changes: 12 additions & 107 deletions src/structures/GuildChannel.js
Expand Up @@ -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');

Expand All @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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<Snowflake, PermissionOverwrites>}
*/
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);
}
}
}
Expand Down Expand Up @@ -220,102 +221,6 @@ class GuildChannel extends Channel {
.freeze();
}

/**
* Replaces the permission overwrites in this channel.
* @param {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} overwrites
* Permission overwrites the channel gets updated with
* @param {string} [reason] Reason for updating the channel overwrites
* @returns {Promise<GuildChannel>}
* @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<GuildChannel>}
* @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<GuildChannel>}
* @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<GuildChannel>}
Expand Down
33 changes: 12 additions & 21 deletions src/structures/PermissionOverwrites.js
@@ -1,22 +1,26 @@
'use strict';

const Base = require('./Base');
const Role = require('./Role');
const { TypeError } = require('../errors');
const { OverwriteTypes } = require('../util/Constants');
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);
}
Expand Down Expand Up @@ -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<PermissionOverwrites>}
* @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;
}

Expand All @@ -84,7 +75,7 @@ class PermissionOverwrites {
* @returns {Promise<PermissionOverwrites>}
*/
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;
}

Expand Down

0 comments on commit e7ad2fe

Please sign in to comment.