Skip to content

Commit

Permalink
feat: add UserPermissions precondition (#252)
Browse files Browse the repository at this point in the history
  • Loading branch information
favna authored Aug 23, 2021
1 parent a48b076 commit 2bb2e12
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 23 deletions.
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ export * from './lib/utils/logger/Logger';
export * from './lib/utils/preconditions/conditions/IPreconditionCondition';
export * from './lib/utils/preconditions/conditions/PreconditionConditionAnd';
export * from './lib/utils/preconditions/conditions/PreconditionConditionOr';
export * from './lib/utils/preconditions/containers/PermissionsPrecondition';
export * from './lib/utils/preconditions/containers/ClientPermissionsPrecondition';
export * from './lib/utils/preconditions/containers/UserPermissionsPrecondition';
export * from './lib/utils/preconditions/IPreconditionContainer';
export * from './lib/utils/preconditions/PreconditionContainerArray';
export * from './lib/utils/preconditions/PreconditionContainerSingle';
export type { CooldownContext } from './preconditions/Cooldown';
export { CorePrecondition as ClientPermissionsCorePrecondition } from './preconditions/ClientPermissions';

export const version = '[VI]{version}[/VI]';
3 changes: 2 additions & 1 deletion src/lib/errors/Identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const enum Identifiers {
PreconditionGuildPublicThreadOnly = 'preconditionGuildPublicThreadOnly',
PreconditionGuildTextOnly = 'preconditionGuildTextOnly',
PreconditionNSFW = 'preconditionNsfw',
PreconditionPermissions = 'preconditionPermissions',
PreconditionClientPermissions = 'preconditionClientPermissions',
PreconditionUserPermissions = 'preconditionUserPermissions',
PreconditionThreadOnly = 'preconditionThreadOnly'
}
27 changes: 24 additions & 3 deletions src/lib/structures/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export abstract class Command<T = Args> extends AliasPiece {
this.parseConstructorPreConditionsRunIn(options);
this.parseConstructorPreConditionsNsfw(options);
this.parseConstructorPreConditionsRequiredClientPermissions(options);
this.parseConstructorPreConditionsRequiredUserPermissions(options);
this.parseConstructorPreConditionsCooldown(options);
}

Expand All @@ -135,14 +136,26 @@ export abstract class Command<T = Args> extends AliasPiece {
}

/**
* Appends the `Permissions` precondition when {@link CommandOptions.requiredClientPermissions} resolves to a
* Appends the `ClientPermissions` precondition when {@link CommandOptions.requiredClientPermissions} resolves to a
* non-zero bitfield.
* @param options The command options given from the constructor.
*/
protected parseConstructorPreConditionsRequiredClientPermissions(options: CommandOptions) {
const permissions = new Permissions(options.requiredClientPermissions);
if (permissions.bitfield !== 0n) {
this.preconditions.append({ name: CommandPreConditions.Permissions, context: { permissions } });
this.preconditions.append({ name: CommandPreConditions.ClientPermissions, context: { permissions } });
}
}

/**
* Appends the `UserPermissions` precondition when {@link CommandOptions.requiredUserPermissions} resolves to a
* non-zero bitfield.
* @param options The command options given from the constructor.
*/
protected parseConstructorPreConditionsRequiredUserPermissions(options: CommandOptions) {
const permissions = new Permissions(options.requiredUserPermissions);
if (permissions.bitfield !== 0n) {
this.preconditions.append({ name: CommandPreConditions.UserPermissions, context: { permissions } });
}
}

Expand Down Expand Up @@ -269,7 +282,8 @@ export const enum CommandPreConditions {
GuildTextOnly = 'GuildTextOnly',
GuildThreadOnly = 'GuildThreadOnly',
NotSafeForWork = 'NSFW',
Permissions = 'Permissions'
ClientPermissions = 'ClientPermissions',
UserPermissions = 'UserPermissions'
}

/**
Expand Down Expand Up @@ -361,6 +375,13 @@ export interface CommandOptions extends AliasPieceOptions, FlagStrategyOptions {
*/
requiredClientPermissions?: PermissionResolvable;

/**
* The required permissions for the user.
* @since 2.0.0
* @default 0
*/
requiredUserPermissions?: PermissionResolvable;

/**
* The channels the command should run in. If set to `null`, no precondition entry will be added. Some optimizations are applied when given an array to reduce the amount of preconditions run (e.g. `'text'` and `'news'` becomes `'guild'`, and if both `'dm'` and `'guild'` are defined, then no precondition entry is added as it runs in all channels).
* @since 2.0.0
Expand Down
5 changes: 4 additions & 1 deletion src/lib/structures/Precondition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ export interface Preconditions {
GuildTextOnly: never;
GuildThreadOnly: never;
NSFW: never;
Permissions: {
ClientPermissions: {
permissions: Permissions;
};
UserPermissions: {
permissions: Permissions;
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { PermissionResolvable, Permissions } from 'discord.js';
import type { PreconditionSingleResolvableDetails } from '../PreconditionContainerSingle';

/**
* Constructs a contextful permissions precondition requirement.
* @since 1.0.0
* @example
* ```typescript
* export class CoreCommand extends Command {
* public constructor(context: PieceContext) {
* super(context, {
* preconditions: [
* 'GuildOnly',
* new ClientPermissionsPrecondition('ADD_REACTIONS')
* ]
* });
* }
*
* public run(message: Message, args: Args) {
* // ...
* }
* }
* ```
*/
export class ClientPermissionsPrecondition implements PreconditionSingleResolvableDetails<'ClientPermissions'> {
public name: 'ClientPermissions';
public context: { permissions: Permissions };

/**
* Constructs a precondition container entry.
* @param permissions The permissions that will be required by this command.
*/
public constructor(permissions: PermissionResolvable) {
this.name = 'ClientPermissions';
this.context = {
permissions: new Permissions(permissions)
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { PreconditionSingleResolvableDetails } from '../PreconditionContain
* super(context, {
* preconditions: [
* 'GuildOnly',
* new PermissionsPrecondition('ADD_REACTIONS')
* new UserPermissionsPrecondition('ADD_REACTIONS')
* ]
* });
* }
Expand All @@ -22,16 +22,16 @@ import type { PreconditionSingleResolvableDetails } from '../PreconditionContain
* }
* ```
*/
export class PermissionsPrecondition implements PreconditionSingleResolvableDetails<'Permissions'> {
public name: 'Permissions';
export class UserPermissionsPrecondition implements PreconditionSingleResolvableDetails<'UserPermissions'> {
public name: 'UserPermissions';
public context: { permissions: Permissions };

/**
* Constructs a precondition container entry.
* @param permissions The permissions that will be required by this command.
*/
public constructor(permissions: PermissionResolvable) {
this.name = 'Permissions';
this.name = 'UserPermissions';
this.context = {
permissions: new Permissions(permissions)
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import type { Command } from '../lib/structures/Command';
import { Precondition, PreconditionContext, PreconditionResult } from '../lib/structures/Precondition';

export class CorePrecondition extends Precondition {
private readonly dmChannelPermissions = new Permissions([
Permissions.FLAGS.VIEW_CHANNEL,
Permissions.FLAGS.SEND_MESSAGES,
Permissions.FLAGS.SEND_TTS_MESSAGES,
Permissions.FLAGS.EMBED_LINKS,
Permissions.FLAGS.ATTACH_FILES,
Permissions.FLAGS.READ_MESSAGE_HISTORY,
Permissions.FLAGS.MENTION_EVERYONE,
Permissions.FLAGS.USE_EXTERNAL_EMOJIS,
Permissions.FLAGS.ADD_REACTIONS
]).freeze();
private readonly dmChannelPermissions = new Permissions(
~new Permissions([
//
'ADD_REACTIONS',
'ATTACH_FILES',
'EMBED_LINKS',
'READ_MESSAGE_HISTORY',
'SEND_MESSAGES',
'USE_EXTERNAL_EMOJIS',
'VIEW_CHANNEL'
]).bitfield & Permissions.ALL
).freeze();

public run(message: Message, _command: Command, context: PreconditionContext): PreconditionResult {
const required = (context.permissions as Permissions) ?? new Permissions();
Expand All @@ -25,15 +26,15 @@ export class CorePrecondition extends Precondition {
return missing.length === 0
? this.ok()
: this.error({
identifier: Identifiers.PreconditionPermissions,
identifier: Identifiers.PreconditionClientPermissions,
message: `I am missing the following permissions to run this command: ${missing
.map((perm) => CorePrecondition.readablePermissions[perm])
.join(', ')}`,
context: { missing }
});
}

protected static readonly readablePermissions = {
public static readonly readablePermissions = {
ADD_REACTIONS: 'Add Reactions',
ADMINISTRATOR: 'Administrator',
ATTACH_FILES: 'Attach Files',
Expand Down
38 changes: 38 additions & 0 deletions src/preconditions/UserPermissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Message, NewsChannel, Permissions, TextChannel } from 'discord.js';
import { Identifiers } from '../lib/errors/Identifiers';
import type { Command } from '../lib/structures/Command';
import { Precondition, PreconditionContext, PreconditionResult } from '../lib/structures/Precondition';
import { CorePrecondition as ClientPermissionsPrecondition } from './ClientPermissions';

export class CorePrecondition extends Precondition {
private readonly dmChannelPermissions = new Permissions(
~new Permissions([
'ADD_REACTIONS',
'ATTACH_FILES',
'EMBED_LINKS',
'READ_MESSAGE_HISTORY',
'SEND_MESSAGES',
'USE_EXTERNAL_EMOJIS',
'VIEW_CHANNEL',
'USE_EXTERNAL_STICKERS',
'MENTION_EVERYONE'
]).bitfield & Permissions.ALL
).freeze();

public run(message: Message, _command: Command, context: PreconditionContext): PreconditionResult {
const required = (context.permissions as Permissions) ?? new Permissions();
const channel = message.channel as TextChannel | NewsChannel;

const permissions = message.guild ? channel.permissionsFor(message.client.id!)! : this.dmChannelPermissions;
const missing = permissions.missing(required);
return missing.length === 0
? this.ok()
: this.error({
identifier: Identifiers.PreconditionUserPermissions,
message: `You are missing the following permissions to run this command: ${missing
.map((perm) => ClientPermissionsPrecondition.readablePermissions[perm])
.join(', ')}`,
context: { missing }
});
}
}

0 comments on commit 2bb2e12

Please sign in to comment.