From 5a4ff9d971f024d237e3ab3273e1b0079d50afdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Rom=C3=A1n?= Date: Fri, 22 May 2020 23:19:58 +0200 Subject: [PATCH] feat(GuildMember): added getters for easier moderation (#212) * feat(GuildMember): added getters for easier moderation * fix(GuildMember): permissions should check for ADMINISTRATOR * Permissions#has no longer needs checkAdmin, clean up permissionsIn * changes kyra can do the docs * fix logic * forgot to save * fix the logic right this time * whoops, had that wrong * docs(GuildMember): updated the docs Co-authored-by: bdistin --- .../caching/stores/GuildMemberRoleStore.ts | 8 ++ src/lib/caching/stores/RoleStore.ts | 8 ++ .../caching/structures/guilds/GuildMember.ts | 81 ++++++++++++++++++- src/lib/caching/structures/guilds/Role.ts | 2 +- src/lib/util/bitfields/Permissions.ts | 11 --- 5 files changed, 94 insertions(+), 16 deletions(-) diff --git a/src/lib/caching/stores/GuildMemberRoleStore.ts b/src/lib/caching/stores/GuildMemberRoleStore.ts index e655b945d..bff954ac1 100644 --- a/src/lib/caching/stores/GuildMemberRoleStore.ts +++ b/src/lib/caching/stores/GuildMemberRoleStore.ts @@ -35,6 +35,14 @@ export class GuildMemberRoleStore extends ProxyCache { this.member = member; } + /** + * Gets the highest role from this store. + * @since 0.0.1 + */ + public highest(): Role | undefined { + return this.reduce((highest, role) => highest.position > role.position ? highest : role, this.firstValue as Role); + } + /** * The {@link Guild guild} this store belongs to. * @since 0.0.1 diff --git a/src/lib/caching/stores/RoleStore.ts b/src/lib/caching/stores/RoleStore.ts index 17da840f7..d42159cfa 100644 --- a/src/lib/caching/stores/RoleStore.ts +++ b/src/lib/caching/stores/RoleStore.ts @@ -30,6 +30,14 @@ export class RoleStore extends DataStore { this.guild = guild; } + /** + * Gets the highest role from this store. + * @since 0.0.1 + */ + public highest(): Role | undefined { + return this.reduce((highest, role) => highest.position > role.position ? highest : role, this.firstValue as Role); + } + /** * Creates a new {@link Role role} for the {@link Guild guild}. * @since 0.0.1 diff --git a/src/lib/caching/structures/guilds/GuildMember.ts b/src/lib/caching/structures/guilds/GuildMember.ts index d6fc3961e..b95af023a 100644 --- a/src/lib/caching/structures/guilds/GuildMember.ts +++ b/src/lib/caching/structures/guilds/GuildMember.ts @@ -85,6 +85,81 @@ export class GuildMember extends Structure { return this.client.users.get(this.id) ?? null; } + /** + * The calculated permissions from the member's {@link GuildMemberRoleStore roles}. + * @since 0.0.1 + */ + public get permissions(): Readonly { + if (this.id === this.guild.ownerID) return new Permissions(Permissions.ALL).freeze(); + + const permissions = new Permissions(this.roles.map(role => role.permissions)); + return (permissions.has(Permissions.FLAGS.ADMINISTRATOR) ? permissions.add(Permissions.ALL) : permissions).freeze(); + } + + /** + * Whether or not the {@link ClientUser client user} can kick this member. + * @since 0.0.1 + * @returns `null` when the {@link ClientUser client user}'s member is not cached (or when {@link Client#user} is null), + * or a boolean specifying whether or not the conditions are met. + */ + public get kickable(): boolean | null { + return (this.id !== this.client.user?.id && this._manageable && (this.guild.me as GuildMember).permissions.has(Permissions.FLAGS.KICK_MEMBERS)) ?? null; + } + + /** + * Whether or not the {@link ClientUser client user} can ban this member. + * @since 0.0.1 + * @returns `null` when the {@link ClientUser client user}'s member is not cached (or when {@link Client#user} is null), + * or a boolean specifying whether or not the conditions are met. + */ + public get bannable(): boolean | null { + return (this.id !== this.client.user?.id && this._manageable && (this.guild.me as GuildMember).permissions.has(Permissions.FLAGS.BAN_MEMBERS)) ?? null; + } + + /** + * Whether or not the {@link ClientUser client user} can manage the member's nickname. + * @since 0.0.1 + * @returns `null` when the {@link ClientUser client user}'s member is not cached (or when {@link Client#user} is null), + * or a boolean specifying whether or not the conditions are met. + */ + public get manageNicknames(): boolean | null { + return (this._manageable && (this.guild.me as GuildMember).permissions.has(Permissions.FLAGS.MANAGE_NICKNAMES)) ?? null; + } + + /** + * Whether or not the {@link ClientUser client user} can manage the member's roles. + * @since 0.0.1 + * @returns `null` when the {@link ClientUser client user}'s member is not cached (or when {@link Client#user} is null), + * or a boolean specifying whether or not the conditions are met. + */ + public get manageRoles(): boolean | null { + return (this._manageable && (this.guild.me as GuildMember).permissions.has(Permissions.FLAGS.MANAGE_ROLES)) ?? null; + } + + /** + * Whether or not the {@link ClientUser client user} can manage this member. This is based on: + * - The member is not the {@link Guild#owner guild owner}. + * - The {@link ClientUser client user} is the owner of the {@link Guild}. + * - The {@link ClientUser client user}'s {@link GuildMemberRoleStore#highest highest role} is higher than the member's. + * @since 0.0.1 + * @returns `true` when any of the conditions are met, `null` when the {@link ClientUser client user}'s member is not + * cached (or when {@link Client#user} is null), or `false` otherwise. + */ + protected get _manageable(): boolean | null { + // If the client user's member instance is not cached, return null. + const { me } = this.guild; + if (!this.client.user || !me) return null; + + // If the client is the owner, then it can manage itself + if (this.guild.ownerID === this.client.user.id) return true; + + // If this is the owner (and we have already determined we are not the owner), then it can't manage + if (this.id === this.guild.ownerID) return false; + + // If the clients highest role is higher than this roles highest role + return me.roles.highest > this.roles.highest; + } + /** * Modifies the settings for the member. * @param data The settings to be set. @@ -101,11 +176,9 @@ export class GuildMember extends Structure { * @param channel The channel to check permissions in */ public permissionsIn(channel: GuildChannel): Readonly { - if (this.id === this.guild.ownerID) return new Permissions(Permissions.ALL).freeze(); - - const permissions = new Permissions(this.roles.map(role => role.permissions)); + const { permissions } = this; - if (permissions.has(Permissions.FLAGS.ADMINISTRATOR)) return new Permissions(Permissions.ALL).freeze(); + if (permissions.equals(Permissions.ALL)) return permissions; const overwrites = channel.permissionOverwrites.for(this); diff --git a/src/lib/caching/structures/guilds/Role.ts b/src/lib/caching/structures/guilds/Role.ts index f2809496f..73c63bf7b 100644 --- a/src/lib/caching/structures/guilds/Role.ts +++ b/src/lib/caching/structures/guilds/Role.ts @@ -87,7 +87,7 @@ export class Role extends Structure { public permissionsIn(channel: GuildChannel): Readonly { const { permissions } = this; - if (this.permissions.has(Permissions.FLAGS.ADMINISTRATOR)) return new Permissions(Permissions.ALL).freeze(); + if (permissions.has(Permissions.FLAGS.ADMINISTRATOR)) return new Permissions(Permissions.ALL).freeze(); const overwrites = channel.permissionOverwrites.for(this); diff --git a/src/lib/util/bitfields/Permissions.ts b/src/lib/util/bitfields/Permissions.ts index d55ecf3f6..ddb564d75 100644 --- a/src/lib/util/bitfields/Permissions.ts +++ b/src/lib/util/bitfields/Permissions.ts @@ -9,17 +9,6 @@ export type PermissionsResolvable = keyof typeof Permissions.FLAGS | number | Bi */ export class Permissions extends BitField { - /** - * Determines if this Permissions includes a permission or permissions - * @param permission The permission/s to check for - * @param checkAdmin Whether this should account for implied Administrator permissions - */ - public has(permission: PermissionsResolvable, checkAdmin = false): boolean { - const constructor = this.constructor as typeof Permissions; - if (checkAdmin && super.has(constructor.FLAGS.ADMINISTRATOR)) return true; - return super.has(permission); - } - /** * The Permissions flags */