1
1
import { AliasPiece , AliasPieceOptions , Awaited , PieceContext } from '@sapphire/pieces' ;
2
+ import { isNullish } from '@sapphire/utilities' ;
2
3
import type { Message } from 'discord.js' ;
3
4
import * as Lexure from 'lexure' ;
4
5
import { Args } from '../parsers/Args' ;
5
6
import type { IPreconditionContainer } from '../utils/preconditions/IPreconditionContainer' ;
6
- import { PreconditionArrayResolvable , PreconditionContainerArray } from '../utils/preconditions/PreconditionContainerArray' ;
7
+ import { PreconditionContainerArray , PreconditionEntryResolvable } from '../utils/preconditions/PreconditionContainerArray' ;
7
8
import { FlagStrategyOptions , FlagUnorderedStrategy } from '../utils/strategies/FlagUnorderedStrategy' ;
8
9
9
10
export abstract class Command < T = Args > extends AliasPiece {
@@ -47,7 +48,6 @@ export abstract class Command<T = Args> extends AliasPiece {
47
48
super ( context , { ...options , name : ( name ?? context . name ) . toLowerCase ( ) } ) ;
48
49
this . description = options . description ?? '' ;
49
50
this . detailedDescription = options . detailedDescription ?? '' ;
50
- this . preconditions = new PreconditionContainerArray ( options . preconditions ) ;
51
51
this . strategy = new FlagUnorderedStrategy ( options . strategyOptions ) ;
52
52
this . lexer . setQuotes (
53
53
options . quotes ?? [
@@ -64,10 +64,12 @@ export abstract class Command<T = Args> extends AliasPiece {
64
64
65
65
this . aliases = [ ...this . aliases , ...dashLessAliases ] ;
66
66
}
67
+
68
+ this . preconditions = new PreconditionContainerArray ( this . resolveConstructorPreConditions ( options ) ) ;
67
69
}
68
70
69
71
/**
70
- * The pre-parse method. This method can be overriden by plugins to define their own argument parser.
72
+ * The pre-parse method. This method can be overridden by plugins to define their own argument parser.
71
73
* @param message The message that triggered the command.
72
74
* @param parameters The raw parameters as a single string.
73
75
* @param context The command-context used in this execution.
@@ -96,6 +98,79 @@ export abstract class Command<T = Args> extends AliasPiece {
96
98
strategy : this . strategy
97
99
} ;
98
100
}
101
+
102
+ protected resolveConstructorPreConditions ( options : CommandOptions ) : readonly PreconditionEntryResolvable [ ] {
103
+ const preconditions = options . preconditions ?. slice ( ) ?? [ ] ;
104
+ if ( options . nsfw ) preconditions . push ( CommandPreConditions . NotSafeForWork ) ;
105
+
106
+ const runIn = this . resolveConstructorPreConditionsRunType ( options . runIn ) ;
107
+ if ( runIn !== null ) preconditions . push ( runIn ) ;
108
+
109
+ const cooldownBucket = options . cooldownBucket ?? 1 ;
110
+ if ( cooldownBucket && options . cooldownDuration ) {
111
+ preconditions . push ( { name : CommandPreConditions . Cooldown , context : { bucket : cooldownBucket , cooldown : options . cooldownDuration } } ) ;
112
+ }
113
+
114
+ return preconditions ;
115
+ }
116
+
117
+ private resolveConstructorPreConditionsRunType ( runIn : CommandOptions [ 'runIn' ] ) : CommandPreConditions [ ] | null {
118
+ if ( isNullish ( runIn ) ) return null ;
119
+ if ( typeof runIn === 'string' ) {
120
+ switch ( runIn ) {
121
+ case 'dm' :
122
+ return [ CommandPreConditions . DirectMessageOnly ] ;
123
+ case 'text' :
124
+ return [ CommandPreConditions . TextOnly ] ;
125
+ case 'news' :
126
+ return [ CommandPreConditions . NewsOnly ] ;
127
+ case 'guild' :
128
+ return [ CommandPreConditions . GuildOnly ] ;
129
+ default :
130
+ return null ;
131
+ }
132
+ }
133
+
134
+ // If there's no channel it can run on, throw an error:
135
+ if ( runIn . length === 0 ) {
136
+ throw new Error ( `${ this . constructor . name } [${ this . name } ]: "runIn" was specified as an empty array.` ) ;
137
+ }
138
+
139
+ const dm = runIn . includes ( 'dm' ) ;
140
+ const text = runIn . includes ( 'text' ) ;
141
+ const news = runIn . includes ( 'news' ) ;
142
+ const guild = text && news ;
143
+
144
+ // If runs everywhere, optimise to null:
145
+ if ( dm && guild ) return null ;
146
+
147
+ const array : CommandPreConditions [ ] = [ ] ;
148
+ if ( dm ) array . push ( CommandPreConditions . DirectMessageOnly ) ;
149
+ if ( guild ) array . push ( CommandPreConditions . GuildOnly ) ;
150
+ else if ( text ) array . push ( CommandPreConditions . TextOnly ) ;
151
+ else if ( news ) array . push ( CommandPreConditions . NewsOnly ) ;
152
+
153
+ return array ;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * The allowed values for [[CommandOptions.runIn]].
159
+ * @since 2.0.0
160
+ */
161
+ export type CommandOptionsRunType = 'dm' | 'text' | 'news' | 'guild' ;
162
+
163
+ /**
164
+ * The available command pre-conditions.
165
+ * @since 2.0.0
166
+ */
167
+ export const enum CommandPreConditions {
168
+ Cooldown = 'Cooldown' ,
169
+ NotSafeForWork = 'NSFW' ,
170
+ DirectMessageOnly = 'DMOnly' ,
171
+ TextOnly = 'TextOnly' ,
172
+ NewsOnly = 'NewsOnly' ,
173
+ GuildOnly = 'GuildOnly'
99
174
}
100
175
101
176
/**
@@ -130,7 +205,7 @@ export interface CommandOptions extends AliasPieceOptions {
130
205
* @since 1.0.0
131
206
* @default []
132
207
*/
133
- preconditions ?: PreconditionArrayResolvable ;
208
+ preconditions ?: readonly PreconditionEntryResolvable [ ] ;
134
209
135
210
/**
136
211
* The options for the lexer strategy.
@@ -150,6 +225,34 @@ export interface CommandOptions extends AliasPieceOptions {
150
225
* ]
151
226
*/
152
227
quotes ?: [ string , string ] [ ] ;
228
+
229
+ /**
230
+ * Sets whether or not the command should be treated as NSFW. If set to true, the `NSFW` precondition will be added to the list.
231
+ * @since 2.0.0
232
+ * @default false
233
+ */
234
+ nsfw ?: boolean ;
235
+
236
+ /**
237
+ * Sets the bucket of the cool-down, if set to a non-zero value alongside {@link CommandOptions.cooldownDuration}, the `Cooldown` precondition will be added to the list.
238
+ * @since 2.0.0
239
+ * @default 1
240
+ */
241
+ cooldownBucket ?: number ;
242
+
243
+ /**
244
+ * Sets the duration of the tickets in the cool-down, if set to a non-zero value alongside {@link CommandOptions.cooldownBucket}, the `Cooldown` precondition will be added to the list.
245
+ * @since 2.0.0
246
+ * @default 0
247
+ */
248
+ cooldownDuration ?: number ;
249
+
250
+ /**
251
+ * 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).
252
+ * @since 2.0.0
253
+ * @default null
254
+ */
255
+ runIn ?: CommandOptionsRunType | readonly CommandOptionsRunType [ ] | null ;
153
256
}
154
257
155
258
export interface CommandContext extends Record < PropertyKey , unknown > {
0 commit comments