Skip to content
This repository has been archived by the owner on Apr 27, 2024. It is now read-only.

Commit

Permalink
feat(GuildMember): added getters for easier moderation (#212)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
kyranet and bdistin authored May 22, 2020
1 parent 5c0cf63 commit 5a4ff9d
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 16 deletions.
8 changes: 8 additions & 0 deletions src/lib/caching/stores/GuildMemberRoleStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ export class GuildMemberRoleStore extends ProxyCache<string, Role> {
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
Expand Down
8 changes: 8 additions & 0 deletions src/lib/caching/stores/RoleStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ export class RoleStore extends DataStore<Role> {
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
Expand Down
81 changes: 77 additions & 4 deletions src/lib/caching/structures/guilds/GuildMember.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Permissions> {
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.
Expand All @@ -101,11 +176,9 @@ export class GuildMember extends Structure {
* @param channel The channel to check permissions in
*/
public permissionsIn(channel: GuildChannel): Readonly<Permissions> {
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);

Expand Down
2 changes: 1 addition & 1 deletion src/lib/caching/structures/guilds/Role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class Role extends Structure {
public permissionsIn(channel: GuildChannel): Readonly<Permissions> {
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);

Expand Down
11 changes: 0 additions & 11 deletions src/lib/util/bitfields/Permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,6 @@ export type PermissionsResolvable = keyof typeof Permissions.FLAGS | number | Bi
*/
export class Permissions extends BitField<PermissionsResolvable> {

/**
* 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
*/
Expand Down

0 comments on commit 5a4ff9d

Please sign in to comment.