/
Precondition.ts
190 lines (170 loc) · 6.37 KB
/
Precondition.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
import { Piece } from '@sapphire/pieces';
import { Result } from '@sapphire/result';
import type { Awaitable } from '@sapphire/utilities';
import type {
ChannelType,
ChatInputCommandInteraction,
CommandInteraction,
ContextMenuCommandInteraction,
Message,
PermissionsBitField,
TextBasedChannel
} from 'discord.js';
import type { CooldownPreconditionContext } from '../../preconditions/Cooldown';
import { PreconditionError } from '../errors/PreconditionError';
import type { UserError } from '../errors/UserError';
import type { ChatInputCommand, ContextMenuCommand, MessageCommand } from '../types/CommandTypes';
export type PreconditionResult = Awaitable<Result<unknown, UserError>>;
export type AsyncPreconditionResult = Promise<Result<unknown, UserError>>;
export class Precondition<Options extends Precondition.Options = Precondition.Options> extends Piece<Options, 'preconditions'> {
public readonly position: number | null;
public constructor(context: Precondition.LoaderContext, options: Options = {} as Options) {
super(context, options);
this.position = options.position ?? null;
}
public messageRun?(message: Message, command: MessageCommand, context: Precondition.Context): Precondition.Result;
public chatInputRun?(interaction: ChatInputCommandInteraction, command: ChatInputCommand, context: Precondition.Context): Precondition.Result;
public contextMenuRun?(
interaction: ContextMenuCommandInteraction,
command: ContextMenuCommand,
context: Precondition.Context
): Precondition.Result;
public ok(): Precondition.Result {
return Result.ok();
}
/**
* Constructs a {@link PreconditionError} with the precondition parameter set to `this`.
* @param options The information.
*/
public error(options: Omit<PreconditionError.Options, 'precondition'> = {}): Precondition.Result {
return Result.err(new PreconditionError({ precondition: this, ...options }));
}
protected async fetchChannelFromInteraction(interaction: CommandInteraction): Promise<TextBasedChannel> {
const channel = (await interaction.client.channels.fetch(interaction.channelId, {
cache: false,
allowUnknownGuild: true
})) as TextBasedChannel;
return channel;
}
}
export abstract class AllFlowsPrecondition extends Precondition {
public abstract override messageRun(message: Message, command: MessageCommand, context: Precondition.Context): Precondition.Result;
public abstract override chatInputRun(
interaction: ChatInputCommandInteraction,
command: ChatInputCommand,
context: Precondition.Context
): Precondition.Result;
public abstract override contextMenuRun(
interaction: ContextMenuCommandInteraction,
command: ContextMenuCommand,
context: Precondition.Context
): Precondition.Result;
}
/**
* The registered preconditions and their contexts, if any. When registering new ones, it is recommended to use
* [module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) so
* custom ones are registered.
*
* When a key's value is `never`, it means that it does not take any context, which allows you to pass its identifier as
* a bare string (e.g. `preconditions: ['NSFW']`), however, if context is required, a non-`never` type should be passed,
* which will type {@link PreconditionContainerArray#append} and require an object with the name and a `context` with
* the defined type.
*
* @example
* ```typescript
* declare module '@sapphire/framework' {
* interface Preconditions {
* // A precondition named `Moderator` which does not read `context`:
* Moderator: never;
*
* // A precondition named `ChannelPermissions` which does read `context`:
* ChannelPermissions: {
* permissions: Permissions;
* };
* }
* }
*
* // [✔] These are valid:
* preconditions.append('Moderator');
* preconditions.append({ name: 'Moderator' });
* preconditions.append({
* name: 'ChannelPermissions',
* context: { permissions: new Permissions(8) }
* });
*
* // [X] These are invalid:
* preconditions.append({ name: 'Moderator', context: {} });
* // ➡ `never` keys do not accept `context`.
*
* preconditions.append('ChannelPermissions');
* // ➡ non-`never` keys always require `context`, a string cannot be used.
*
* preconditions.append({
* name: 'ChannelPermissions',
* context: { unknownProperty: 1 }
* });
* // ➡ mismatching `context` properties, `{ unknownProperty: number }` is not
* // assignable to `{ permissions: Permissions }`.
* ```
*/
export interface Preconditions {
Cooldown: CooldownPreconditionContext;
DMOnly: never;
Enabled: never;
GuildNewsOnly: never;
GuildNewsThreadOnly: never;
GuildOnly: never;
GuildPrivateThreadOnly: never;
GuildPublicThreadOnly: never;
GuildTextOnly: never;
GuildVoiceOnly: never;
GuildThreadOnly: never;
NSFW: never;
RunIn: {
types: readonly ChannelType[] | RunInPreconditionCommandSpecificData;
};
ClientPermissions: {
permissions: PermissionsBitField;
};
UserPermissions: {
permissions: PermissionsBitField;
};
}
/**
* The specific data for the precondition types for the `RunIn` precondition, when the command
* specified the types for specific command types.
*/
export interface RunInPreconditionCommandSpecificData {
messageRun: readonly ChannelType[];
chatInputRun: readonly ChannelType[];
contextMenuRun: readonly ChannelType[];
}
export type PreconditionKeys = keyof Preconditions;
export type SimplePreconditionKeys = {
[K in PreconditionKeys]: Preconditions[K] extends never ? K : never;
}[PreconditionKeys];
export interface PreconditionOptions extends Piece.Options {
/**
* The position for the precondition to be set at in the global precondition list. If set to `null`, this
* precondition will not be set as a global one.
* @default null
*/
position?: number | null;
}
export interface PreconditionContext extends Record<PropertyKey, unknown> {
external?: boolean;
}
export namespace Precondition {
export type Options = PreconditionOptions;
export type LoaderContext = Piece.LoaderContext<'preconditions'>;
export type Context = PreconditionContext;
export type Result = PreconditionResult;
export type AsyncResult = AsyncPreconditionResult;
}
export namespace AllFlowsPrecondition {
export type Options = PreconditionOptions;
export type LoaderContext = Piece.LoaderContext<'preconditions'>;
export type Context = PreconditionContext;
export type Result = PreconditionResult;
export type AsyncResult = AsyncPreconditionResult;
}