Skip to content

Commit 2bb2e12

Browse files
authored
feat: add UserPermissions precondition (#252)
1 parent a48b076 commit 2bb2e12

File tree

8 files changed

+128
-23
lines changed

8 files changed

+128
-23
lines changed

src/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@ export * from './lib/utils/logger/Logger';
4242
export * from './lib/utils/preconditions/conditions/IPreconditionCondition';
4343
export * from './lib/utils/preconditions/conditions/PreconditionConditionAnd';
4444
export * from './lib/utils/preconditions/conditions/PreconditionConditionOr';
45-
export * from './lib/utils/preconditions/containers/PermissionsPrecondition';
45+
export * from './lib/utils/preconditions/containers/ClientPermissionsPrecondition';
46+
export * from './lib/utils/preconditions/containers/UserPermissionsPrecondition';
4647
export * from './lib/utils/preconditions/IPreconditionContainer';
4748
export * from './lib/utils/preconditions/PreconditionContainerArray';
4849
export * from './lib/utils/preconditions/PreconditionContainerSingle';
4950
export type { CooldownContext } from './preconditions/Cooldown';
51+
export { CorePrecondition as ClientPermissionsCorePrecondition } from './preconditions/ClientPermissions';
5052

5153
export const version = '[VI]{version}[/VI]';

src/lib/errors/Identifiers.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const enum Identifiers {
4141
PreconditionGuildPublicThreadOnly = 'preconditionGuildPublicThreadOnly',
4242
PreconditionGuildTextOnly = 'preconditionGuildTextOnly',
4343
PreconditionNSFW = 'preconditionNsfw',
44-
PreconditionPermissions = 'preconditionPermissions',
44+
PreconditionClientPermissions = 'preconditionClientPermissions',
45+
PreconditionUserPermissions = 'preconditionUserPermissions',
4546
PreconditionThreadOnly = 'preconditionThreadOnly'
4647
}

src/lib/structures/Command.ts

+24-3
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export abstract class Command<T = Args> extends AliasPiece {
112112
this.parseConstructorPreConditionsRunIn(options);
113113
this.parseConstructorPreConditionsNsfw(options);
114114
this.parseConstructorPreConditionsRequiredClientPermissions(options);
115+
this.parseConstructorPreConditionsRequiredUserPermissions(options);
115116
this.parseConstructorPreConditionsCooldown(options);
116117
}
117118

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

137138
/**
138-
* Appends the `Permissions` precondition when {@link CommandOptions.requiredClientPermissions} resolves to a
139+
* Appends the `ClientPermissions` precondition when {@link CommandOptions.requiredClientPermissions} resolves to a
139140
* non-zero bitfield.
140141
* @param options The command options given from the constructor.
141142
*/
142143
protected parseConstructorPreConditionsRequiredClientPermissions(options: CommandOptions) {
143144
const permissions = new Permissions(options.requiredClientPermissions);
144145
if (permissions.bitfield !== 0n) {
145-
this.preconditions.append({ name: CommandPreConditions.Permissions, context: { permissions } });
146+
this.preconditions.append({ name: CommandPreConditions.ClientPermissions, context: { permissions } });
147+
}
148+
}
149+
150+
/**
151+
* Appends the `UserPermissions` precondition when {@link CommandOptions.requiredUserPermissions} resolves to a
152+
* non-zero bitfield.
153+
* @param options The command options given from the constructor.
154+
*/
155+
protected parseConstructorPreConditionsRequiredUserPermissions(options: CommandOptions) {
156+
const permissions = new Permissions(options.requiredUserPermissions);
157+
if (permissions.bitfield !== 0n) {
158+
this.preconditions.append({ name: CommandPreConditions.UserPermissions, context: { permissions } });
146159
}
147160
}
148161

@@ -269,7 +282,8 @@ export const enum CommandPreConditions {
269282
GuildTextOnly = 'GuildTextOnly',
270283
GuildThreadOnly = 'GuildThreadOnly',
271284
NotSafeForWork = 'NSFW',
272-
Permissions = 'Permissions'
285+
ClientPermissions = 'ClientPermissions',
286+
UserPermissions = 'UserPermissions'
273287
}
274288

275289
/**
@@ -361,6 +375,13 @@ export interface CommandOptions extends AliasPieceOptions, FlagStrategyOptions {
361375
*/
362376
requiredClientPermissions?: PermissionResolvable;
363377

378+
/**
379+
* The required permissions for the user.
380+
* @since 2.0.0
381+
* @default 0
382+
*/
383+
requiredUserPermissions?: PermissionResolvable;
384+
364385
/**
365386
* 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).
366387
* @since 2.0.0

src/lib/structures/Precondition.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,10 @@ export interface Preconditions {
9292
GuildTextOnly: never;
9393
GuildThreadOnly: never;
9494
NSFW: never;
95-
Permissions: {
95+
ClientPermissions: {
96+
permissions: Permissions;
97+
};
98+
UserPermissions: {
9699
permissions: Permissions;
97100
};
98101
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { PermissionResolvable, Permissions } from 'discord.js';
2+
import type { PreconditionSingleResolvableDetails } from '../PreconditionContainerSingle';
3+
4+
/**
5+
* Constructs a contextful permissions precondition requirement.
6+
* @since 1.0.0
7+
* @example
8+
* ```typescript
9+
* export class CoreCommand extends Command {
10+
* public constructor(context: PieceContext) {
11+
* super(context, {
12+
* preconditions: [
13+
* 'GuildOnly',
14+
* new ClientPermissionsPrecondition('ADD_REACTIONS')
15+
* ]
16+
* });
17+
* }
18+
*
19+
* public run(message: Message, args: Args) {
20+
* // ...
21+
* }
22+
* }
23+
* ```
24+
*/
25+
export class ClientPermissionsPrecondition implements PreconditionSingleResolvableDetails<'ClientPermissions'> {
26+
public name: 'ClientPermissions';
27+
public context: { permissions: Permissions };
28+
29+
/**
30+
* Constructs a precondition container entry.
31+
* @param permissions The permissions that will be required by this command.
32+
*/
33+
public constructor(permissions: PermissionResolvable) {
34+
this.name = 'ClientPermissions';
35+
this.context = {
36+
permissions: new Permissions(permissions)
37+
};
38+
}
39+
}

src/lib/utils/preconditions/containers/PermissionsPrecondition.ts renamed to src/lib/utils/preconditions/containers/UserPermissionsPrecondition.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type { PreconditionSingleResolvableDetails } from '../PreconditionContain
1111
* super(context, {
1212
* preconditions: [
1313
* 'GuildOnly',
14-
* new PermissionsPrecondition('ADD_REACTIONS')
14+
* new UserPermissionsPrecondition('ADD_REACTIONS')
1515
* ]
1616
* });
1717
* }
@@ -22,16 +22,16 @@ import type { PreconditionSingleResolvableDetails } from '../PreconditionContain
2222
* }
2323
* ```
2424
*/
25-
export class PermissionsPrecondition implements PreconditionSingleResolvableDetails<'Permissions'> {
26-
public name: 'Permissions';
25+
export class UserPermissionsPrecondition implements PreconditionSingleResolvableDetails<'UserPermissions'> {
26+
public name: 'UserPermissions';
2727
public context: { permissions: Permissions };
2828

2929
/**
3030
* Constructs a precondition container entry.
3131
* @param permissions The permissions that will be required by this command.
3232
*/
3333
public constructor(permissions: PermissionResolvable) {
34-
this.name = 'Permissions';
34+
this.name = 'UserPermissions';
3535
this.context = {
3636
permissions: new Permissions(permissions)
3737
};

src/preconditions/Permissions.ts renamed to src/preconditions/ClientPermissions.ts

+14-13
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@ import type { Command } from '../lib/structures/Command';
44
import { Precondition, PreconditionContext, PreconditionResult } from '../lib/structures/Precondition';
55

66
export class CorePrecondition extends Precondition {
7-
private readonly dmChannelPermissions = new Permissions([
8-
Permissions.FLAGS.VIEW_CHANNEL,
9-
Permissions.FLAGS.SEND_MESSAGES,
10-
Permissions.FLAGS.SEND_TTS_MESSAGES,
11-
Permissions.FLAGS.EMBED_LINKS,
12-
Permissions.FLAGS.ATTACH_FILES,
13-
Permissions.FLAGS.READ_MESSAGE_HISTORY,
14-
Permissions.FLAGS.MENTION_EVERYONE,
15-
Permissions.FLAGS.USE_EXTERNAL_EMOJIS,
16-
Permissions.FLAGS.ADD_REACTIONS
17-
]).freeze();
7+
private readonly dmChannelPermissions = new Permissions(
8+
~new Permissions([
9+
//
10+
'ADD_REACTIONS',
11+
'ATTACH_FILES',
12+
'EMBED_LINKS',
13+
'READ_MESSAGE_HISTORY',
14+
'SEND_MESSAGES',
15+
'USE_EXTERNAL_EMOJIS',
16+
'VIEW_CHANNEL'
17+
]).bitfield & Permissions.ALL
18+
).freeze();
1819

1920
public run(message: Message, _command: Command, context: PreconditionContext): PreconditionResult {
2021
const required = (context.permissions as Permissions) ?? new Permissions();
@@ -25,15 +26,15 @@ export class CorePrecondition extends Precondition {
2526
return missing.length === 0
2627
? this.ok()
2728
: this.error({
28-
identifier: Identifiers.PreconditionPermissions,
29+
identifier: Identifiers.PreconditionClientPermissions,
2930
message: `I am missing the following permissions to run this command: ${missing
3031
.map((perm) => CorePrecondition.readablePermissions[perm])
3132
.join(', ')}`,
3233
context: { missing }
3334
});
3435
}
3536

36-
protected static readonly readablePermissions = {
37+
public static readonly readablePermissions = {
3738
ADD_REACTIONS: 'Add Reactions',
3839
ADMINISTRATOR: 'Administrator',
3940
ATTACH_FILES: 'Attach Files',

src/preconditions/UserPermissions.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Message, NewsChannel, Permissions, TextChannel } from 'discord.js';
2+
import { Identifiers } from '../lib/errors/Identifiers';
3+
import type { Command } from '../lib/structures/Command';
4+
import { Precondition, PreconditionContext, PreconditionResult } from '../lib/structures/Precondition';
5+
import { CorePrecondition as ClientPermissionsPrecondition } from './ClientPermissions';
6+
7+
export class CorePrecondition extends Precondition {
8+
private readonly dmChannelPermissions = new Permissions(
9+
~new Permissions([
10+
'ADD_REACTIONS',
11+
'ATTACH_FILES',
12+
'EMBED_LINKS',
13+
'READ_MESSAGE_HISTORY',
14+
'SEND_MESSAGES',
15+
'USE_EXTERNAL_EMOJIS',
16+
'VIEW_CHANNEL',
17+
'USE_EXTERNAL_STICKERS',
18+
'MENTION_EVERYONE'
19+
]).bitfield & Permissions.ALL
20+
).freeze();
21+
22+
public run(message: Message, _command: Command, context: PreconditionContext): PreconditionResult {
23+
const required = (context.permissions as Permissions) ?? new Permissions();
24+
const channel = message.channel as TextChannel | NewsChannel;
25+
26+
const permissions = message.guild ? channel.permissionsFor(message.client.id!)! : this.dmChannelPermissions;
27+
const missing = permissions.missing(required);
28+
return missing.length === 0
29+
? this.ok()
30+
: this.error({
31+
identifier: Identifiers.PreconditionUserPermissions,
32+
message: `You are missing the following permissions to run this command: ${missing
33+
.map((perm) => ClientPermissionsPrecondition.readablePermissions[perm])
34+
.join(', ')}`,
35+
context: { missing }
36+
});
37+
}
38+
}

0 commit comments

Comments
 (0)