stoatx 0.8.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +199 -211
- package/dist/index.d.ts +199 -211
- package/dist/index.js +513 -395
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +497 -392
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
|
+
// src/types.ts
|
|
5
|
+
import { User, BaseChannel, Role } from "@stoatx/client";
|
|
6
|
+
var PARAM_TYPE_MAP = /* @__PURE__ */ new Map([
|
|
7
|
+
[
|
|
8
|
+
String,
|
|
9
|
+
"string"
|
|
10
|
+
],
|
|
11
|
+
[
|
|
12
|
+
Number,
|
|
13
|
+
"number"
|
|
14
|
+
],
|
|
15
|
+
[
|
|
16
|
+
Boolean,
|
|
17
|
+
"boolean"
|
|
18
|
+
],
|
|
19
|
+
[
|
|
20
|
+
User,
|
|
21
|
+
"user"
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
BaseChannel,
|
|
25
|
+
"channel"
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
Role,
|
|
29
|
+
"role"
|
|
30
|
+
]
|
|
31
|
+
]);
|
|
32
|
+
|
|
4
33
|
// src/decorators/Stoat.ts
|
|
5
34
|
import "reflect-metadata";
|
|
6
35
|
|
|
@@ -9,7 +38,12 @@ var METADATA_KEYS = {
|
|
|
9
38
|
IS_STOAT_CLASS: /* @__PURE__ */ Symbol("stoatx:stoat:isClass"),
|
|
10
39
|
SIMPLE_COMMANDS: /* @__PURE__ */ Symbol("stoatx:stoat:simpleCommands"),
|
|
11
40
|
GUARDS: "stoatx:command:guards",
|
|
12
|
-
EVENTS: /* @__PURE__ */ Symbol("stoatx:stoat:events")
|
|
41
|
+
EVENTS: /* @__PURE__ */ Symbol("stoatx:stoat:events"),
|
|
42
|
+
ARGS: /* @__PURE__ */ Symbol("stoatx:param:args"),
|
|
43
|
+
OPTIONS: /* @__PURE__ */ Symbol("stoatx:param:options"),
|
|
44
|
+
INJECTABLE: /* @__PURE__ */ Symbol("stoatx:injectable"),
|
|
45
|
+
SUBCOMMAND: /* @__PURE__ */ Symbol("stoatx:subcommand"),
|
|
46
|
+
COMMAND_GROUP: /* @__PURE__ */ Symbol("stoatx:commandGroup")
|
|
13
47
|
};
|
|
14
48
|
|
|
15
49
|
// src/decorators/store.ts
|
|
@@ -19,7 +53,7 @@ var DecoratorStore = class _DecoratorStore {
|
|
|
19
53
|
}
|
|
20
54
|
static instance;
|
|
21
55
|
/** Stoat classes with their SimpleCommand methods */
|
|
22
|
-
stoatClasses = /* @__PURE__ */ new
|
|
56
|
+
stoatClasses = /* @__PURE__ */ new Set();
|
|
23
57
|
/** Registered commands from @Stoat/@SimpleCommand decorators */
|
|
24
58
|
commands = [];
|
|
25
59
|
/** Whether the store has been initialized */
|
|
@@ -36,10 +70,7 @@ var DecoratorStore = class _DecoratorStore {
|
|
|
36
70
|
* Register a @Stoat decorated class
|
|
37
71
|
*/
|
|
38
72
|
registerStoatClass(classConstructor) {
|
|
39
|
-
|
|
40
|
-
const instance = new classConstructor();
|
|
41
|
-
this.stoatClasses.set(classConstructor, instance);
|
|
42
|
-
}
|
|
73
|
+
this.stoatClasses.add(classConstructor);
|
|
43
74
|
}
|
|
44
75
|
/**
|
|
45
76
|
* Get all registered Stoat classes with their instances
|
|
@@ -118,14 +149,23 @@ __name(getSimpleCommands, "getSimpleCommands");
|
|
|
118
149
|
// src/decorators/Guard.ts
|
|
119
150
|
import "reflect-metadata";
|
|
120
151
|
function Guard(guardClass) {
|
|
121
|
-
return (target) => {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
152
|
+
return (target, propertyKey) => {
|
|
153
|
+
if (propertyKey) {
|
|
154
|
+
const existingGuards = Reflect.getMetadata(METADATA_KEYS.GUARDS, target, propertyKey) || [];
|
|
155
|
+
existingGuards.push(guardClass);
|
|
156
|
+
Reflect.defineMetadata(METADATA_KEYS.GUARDS, existingGuards, target, propertyKey);
|
|
157
|
+
} else {
|
|
158
|
+
const existingGuards = Reflect.getMetadata(METADATA_KEYS.GUARDS, target) || [];
|
|
159
|
+
existingGuards.push(guardClass);
|
|
160
|
+
Reflect.defineMetadata(METADATA_KEYS.GUARDS, existingGuards, target);
|
|
161
|
+
}
|
|
125
162
|
};
|
|
126
163
|
}
|
|
127
164
|
__name(Guard, "Guard");
|
|
128
|
-
function getGuards(target) {
|
|
165
|
+
function getGuards(target, propertyKey) {
|
|
166
|
+
if (propertyKey) {
|
|
167
|
+
return Reflect.getMetadata(METADATA_KEYS.GUARDS, target.prototype, propertyKey) || [];
|
|
168
|
+
}
|
|
129
169
|
return Reflect.getMetadata(METADATA_KEYS.GUARDS, target) || [];
|
|
130
170
|
}
|
|
131
171
|
__name(getGuards, "getGuards");
|
|
@@ -159,8 +199,74 @@ function getEventsMetadata(target) {
|
|
|
159
199
|
}
|
|
160
200
|
__name(getEventsMetadata, "getEventsMetadata");
|
|
161
201
|
|
|
202
|
+
// src/decorators/Arg.ts
|
|
203
|
+
import "reflect-metadata";
|
|
204
|
+
function Arg(options = {}) {
|
|
205
|
+
return (target, propertyKey, parameterIndex) => {
|
|
206
|
+
const existing = Reflect.getMetadata(METADATA_KEYS.ARGS, target, propertyKey) || [];
|
|
207
|
+
existing.push({
|
|
208
|
+
...options,
|
|
209
|
+
index: parameterIndex
|
|
210
|
+
});
|
|
211
|
+
Reflect.defineMetadata(METADATA_KEYS.ARGS, existing, target, propertyKey);
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
__name(Arg, "Arg");
|
|
215
|
+
function getArgs(target, propertyKey) {
|
|
216
|
+
return Reflect.getMetadata(METADATA_KEYS.ARGS, target, propertyKey) || [];
|
|
217
|
+
}
|
|
218
|
+
__name(getArgs, "getArgs");
|
|
219
|
+
|
|
220
|
+
// src/decorators/Option.ts
|
|
221
|
+
import "reflect-metadata";
|
|
222
|
+
function Option(options) {
|
|
223
|
+
return (target, propertyKey, parameterIndex) => {
|
|
224
|
+
const existing = Reflect.getMetadata(METADATA_KEYS.OPTIONS, target, propertyKey) || [];
|
|
225
|
+
existing.push({
|
|
226
|
+
...options,
|
|
227
|
+
index: parameterIndex
|
|
228
|
+
});
|
|
229
|
+
Reflect.defineMetadata(METADATA_KEYS.OPTIONS, existing, target, propertyKey);
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
__name(Option, "Option");
|
|
233
|
+
function getOptions(target, propertyKey) {
|
|
234
|
+
return Reflect.getMetadata(METADATA_KEYS.OPTIONS, target, propertyKey) || [];
|
|
235
|
+
}
|
|
236
|
+
__name(getOptions, "getOptions");
|
|
237
|
+
|
|
238
|
+
// src/decorators/Injectable.ts
|
|
239
|
+
import "reflect-metadata";
|
|
240
|
+
function Injectable() {
|
|
241
|
+
return (target) => {
|
|
242
|
+
Reflect.defineMetadata(METADATA_KEYS.INJECTABLE, true, target);
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
__name(Injectable, "Injectable");
|
|
246
|
+
|
|
247
|
+
// src/decorators/CommandGroup.ts
|
|
248
|
+
import "reflect-metadata";
|
|
249
|
+
function CommandGroup(name) {
|
|
250
|
+
return (target) => {
|
|
251
|
+
Reflect.defineMetadata(METADATA_KEYS.COMMAND_GROUP, name, target);
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
__name(CommandGroup, "CommandGroup");
|
|
255
|
+
|
|
256
|
+
// src/decorators/SubCommand.ts
|
|
257
|
+
import "reflect-metadata";
|
|
258
|
+
function SubCommand(options) {
|
|
259
|
+
const opts = typeof options === "string" ? {
|
|
260
|
+
name: options
|
|
261
|
+
} : options;
|
|
262
|
+
return (target, propertyKey) => {
|
|
263
|
+
Reflect.defineMetadata(METADATA_KEYS.SUBCOMMAND, opts, target, propertyKey);
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
__name(SubCommand, "SubCommand");
|
|
267
|
+
|
|
162
268
|
// src/decorators/utils.ts
|
|
163
|
-
function buildSimpleCommandMetadata(options, methodName, category) {
|
|
269
|
+
function buildSimpleCommandMetadata(options, methodName, category, params) {
|
|
164
270
|
return {
|
|
165
271
|
name: options.name ?? methodName.toLowerCase(),
|
|
166
272
|
description: options.description ?? "No description provided",
|
|
@@ -173,11 +279,25 @@ function buildSimpleCommandMetadata(options, methodName, category) {
|
|
|
173
279
|
} : {},
|
|
174
280
|
nsfw: options.nsfw ?? false,
|
|
175
281
|
ownerOnly: options.ownerOnly ?? false,
|
|
176
|
-
|
|
177
|
-
args: options.args ?? []
|
|
282
|
+
params
|
|
178
283
|
};
|
|
179
284
|
}
|
|
180
285
|
__name(buildSimpleCommandMetadata, "buildSimpleCommandMetadata");
|
|
286
|
+
function getSubCommands(target) {
|
|
287
|
+
const methods = Object.getOwnPropertyNames(target.prototype);
|
|
288
|
+
const subCommands = [];
|
|
289
|
+
for (const method of methods) {
|
|
290
|
+
const options = Reflect.getMetadata(METADATA_KEYS.SUBCOMMAND, target.prototype, method);
|
|
291
|
+
if (options) {
|
|
292
|
+
subCommands.push({
|
|
293
|
+
methodName: method,
|
|
294
|
+
options
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return subCommands;
|
|
299
|
+
}
|
|
300
|
+
__name(getSubCommands, "getSubCommands");
|
|
181
301
|
|
|
182
302
|
// src/registry.ts
|
|
183
303
|
import * as path from "path";
|
|
@@ -200,22 +320,18 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
200
320
|
registeredEvents = [];
|
|
201
321
|
extensions;
|
|
202
322
|
processedStoatClasses = /* @__PURE__ */ new Set();
|
|
203
|
-
|
|
323
|
+
container;
|
|
324
|
+
constructor(container, extensions = [
|
|
204
325
|
".js",
|
|
205
326
|
".mjs",
|
|
206
327
|
".cjs"
|
|
207
328
|
]) {
|
|
329
|
+
this.container = container;
|
|
208
330
|
this.extensions = extensions;
|
|
209
331
|
}
|
|
210
|
-
/**
|
|
211
|
-
* Get the number of registered commands
|
|
212
|
-
*/
|
|
213
332
|
get size() {
|
|
214
333
|
return this.commands.size;
|
|
215
334
|
}
|
|
216
|
-
/**
|
|
217
|
-
* Load commands from a directory using glob pattern matching
|
|
218
|
-
*/
|
|
219
335
|
async loadFromDirectory(directory) {
|
|
220
336
|
const patterns = this.extensions.map((ext) => path.join(directory, "**", `*${ext}`).replace(/\\/g, "/"));
|
|
221
337
|
for (const pattern of patterns) {
|
|
@@ -233,9 +349,6 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
233
349
|
}
|
|
234
350
|
console.log(`[Stoatx] Loaded ${this.commands.size} command(s) and ${this.registeredEvents.length} event(s)`);
|
|
235
351
|
}
|
|
236
|
-
/**
|
|
237
|
-
* Auto-discover command files across one or more roots.
|
|
238
|
-
*/
|
|
239
352
|
async autoDiscover(options = {}) {
|
|
240
353
|
const roots = options.roots?.length ? options.roots : [
|
|
241
354
|
process.cwd()
|
|
@@ -252,12 +365,10 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
252
365
|
const uniqueFiles = [
|
|
253
366
|
...new Set(files)
|
|
254
367
|
];
|
|
255
|
-
let candidateFiles = 0;
|
|
256
368
|
for (const file of uniqueFiles) {
|
|
257
369
|
if (!await this.isLikelyCommandModule(file)) {
|
|
258
370
|
continue;
|
|
259
371
|
}
|
|
260
|
-
candidateFiles++;
|
|
261
372
|
const baseDir = roots.find((root) => {
|
|
262
373
|
const relative2 = path.relative(root, file);
|
|
263
374
|
return relative2 && !relative2.startsWith("..") && !path.isAbsolute(relative2);
|
|
@@ -277,11 +388,11 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
277
388
|
return true;
|
|
278
389
|
}
|
|
279
390
|
}
|
|
280
|
-
/**
|
|
281
|
-
* Register a command instance
|
|
282
|
-
*/
|
|
283
391
|
register(instance, metadata, classConstructor, methodName) {
|
|
284
|
-
const
|
|
392
|
+
const groupOptions = Reflect.getMetadata(METADATA_KEYS.COMMAND_GROUP, classConstructor);
|
|
393
|
+
const subCommandOptions = Reflect.getMetadata(METADATA_KEYS.SUBCOMMAND, instance, methodName);
|
|
394
|
+
const subCommandName = subCommandOptions?.name;
|
|
395
|
+
const name = groupOptions && subCommandName ? `${groupOptions.name}:${subCommandName}`.toLowerCase() : metadata.name.toLowerCase();
|
|
285
396
|
if (this.commands.has(name)) {
|
|
286
397
|
console.warn(`[Stoatx] Duplicate command name: ${name}. Skipping...`);
|
|
287
398
|
return;
|
|
@@ -302,42 +413,24 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
302
413
|
this.aliases.set(aliasLower, name);
|
|
303
414
|
}
|
|
304
415
|
}
|
|
305
|
-
/**
|
|
306
|
-
* Get a command by name or alias
|
|
307
|
-
*/
|
|
308
416
|
get(name) {
|
|
309
417
|
const lowerName = name.toLowerCase();
|
|
310
418
|
const resolvedName = this.aliases.get(lowerName) ?? lowerName;
|
|
311
419
|
return this.commands.get(resolvedName);
|
|
312
420
|
}
|
|
313
|
-
/**
|
|
314
|
-
* Check if a command exists
|
|
315
|
-
*/
|
|
316
421
|
has(name) {
|
|
317
422
|
const lowerName = name.toLowerCase();
|
|
318
423
|
return this.commands.has(lowerName) || this.aliases.has(lowerName);
|
|
319
424
|
}
|
|
320
|
-
/**
|
|
321
|
-
* Get all registered commands
|
|
322
|
-
*/
|
|
323
425
|
getAll() {
|
|
324
426
|
return Array.from(this.commands.values());
|
|
325
427
|
}
|
|
326
|
-
/**
|
|
327
|
-
* Get all command metadata
|
|
328
|
-
*/
|
|
329
428
|
getAllMetadata() {
|
|
330
429
|
return this.getAll().map((c) => c.metadata);
|
|
331
430
|
}
|
|
332
|
-
/**
|
|
333
|
-
* Get all registered events
|
|
334
|
-
*/
|
|
335
431
|
getEvents() {
|
|
336
432
|
return this.registeredEvents;
|
|
337
433
|
}
|
|
338
|
-
/**
|
|
339
|
-
* Get commands grouped by category
|
|
340
|
-
*/
|
|
341
434
|
getByCategory() {
|
|
342
435
|
const categories = /* @__PURE__ */ new Map();
|
|
343
436
|
for (const cmd of this.commands.values()) {
|
|
@@ -348,89 +441,70 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
348
441
|
}
|
|
349
442
|
return categories;
|
|
350
443
|
}
|
|
351
|
-
/**
|
|
352
|
-
* Clear all commands
|
|
353
|
-
*/
|
|
354
444
|
clear() {
|
|
355
445
|
this.commands.clear();
|
|
356
446
|
this.aliases.clear();
|
|
357
447
|
this.registeredEvents.length = 0;
|
|
358
448
|
this.processedStoatClasses.clear();
|
|
359
449
|
}
|
|
360
|
-
/**
|
|
361
|
-
* Iterate over commands
|
|
362
|
-
*/
|
|
363
450
|
[Symbol.iterator]() {
|
|
364
451
|
return this.commands.entries();
|
|
365
452
|
}
|
|
366
|
-
/**
|
|
367
|
-
* Iterate over command values
|
|
368
|
-
*/
|
|
369
453
|
values() {
|
|
370
454
|
return this.commands.values();
|
|
371
455
|
}
|
|
372
|
-
/**
|
|
373
|
-
* Iterate over command names
|
|
374
|
-
*/
|
|
375
456
|
keys() {
|
|
376
457
|
return this.commands.keys();
|
|
377
458
|
}
|
|
378
|
-
/**
|
|
379
|
-
* Validate that all guards on a command implement the required methods
|
|
380
|
-
* @param commandClass
|
|
381
|
-
* @param commandName
|
|
382
|
-
* @private
|
|
383
|
-
*/
|
|
384
459
|
validateGuards(commandClass, commandName) {
|
|
385
|
-
const guards = Reflect.getMetadata(
|
|
460
|
+
const guards = Reflect.getMetadata(METADATA_KEYS.GUARDS, commandClass) || [];
|
|
386
461
|
for (const GuardClass of guards) {
|
|
387
|
-
const guardInstance =
|
|
462
|
+
const guardInstance = this.container.resolve(GuardClass);
|
|
388
463
|
if (typeof guardInstance.run !== "function") {
|
|
389
464
|
console.error(`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`);
|
|
390
465
|
process.exit(1);
|
|
391
466
|
}
|
|
392
467
|
if (typeof guardInstance.guardFail !== "function") {
|
|
393
468
|
console.error(`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`);
|
|
394
|
-
console.error(`[Stoatx] All guards must implement guardFail() to handle failed checks.`);
|
|
395
469
|
process.exit(1);
|
|
396
470
|
}
|
|
397
471
|
}
|
|
398
472
|
}
|
|
399
|
-
/**
|
|
400
|
-
* Load commands from a single file
|
|
401
|
-
*/
|
|
402
473
|
async loadFile(filePath, baseDir) {
|
|
403
474
|
try {
|
|
404
475
|
const knownStoatClasses = new Set(decoratorStore.getStoatClasses().keys());
|
|
405
476
|
const fileUrl = pathToFileURL(filePath).href;
|
|
406
477
|
await import(fileUrl);
|
|
407
478
|
const allStoatClasses = decoratorStore.getStoatClasses();
|
|
408
|
-
for (const [stoatClass
|
|
479
|
+
for (const [stoatClass] of allStoatClasses.entries()) {
|
|
409
480
|
if (knownStoatClasses.has(stoatClass) || this.processedStoatClasses.has(stoatClass)) {
|
|
410
481
|
continue;
|
|
411
482
|
}
|
|
412
|
-
this.registerStoatClassCommands(stoatClass,
|
|
483
|
+
this.registerStoatClassCommands(stoatClass, filePath, baseDir);
|
|
413
484
|
}
|
|
414
485
|
} catch (error) {
|
|
415
486
|
console.error(`[Stoatx] Failed to load command file: ${filePath}`, error);
|
|
416
487
|
}
|
|
417
488
|
}
|
|
418
|
-
registerStoatClassCommands(stoatClass,
|
|
489
|
+
registerStoatClassCommands(stoatClass, filePath, baseDir) {
|
|
490
|
+
const instance = this.container.resolve(stoatClass);
|
|
419
491
|
const simpleCommands = getSimpleCommands(stoatClass);
|
|
492
|
+
const subCommands = getSubCommands(stoatClass);
|
|
420
493
|
const events = getEventsMetadata(stoatClass);
|
|
421
494
|
const category = this.getCategoryFromPath(filePath, baseDir);
|
|
422
|
-
|
|
423
|
-
|
|
495
|
+
const allCommands = [
|
|
496
|
+
...simpleCommands,
|
|
497
|
+
...subCommands
|
|
498
|
+
];
|
|
499
|
+
if (allCommands.length === 0 && events.length === 0) {
|
|
424
500
|
this.processedStoatClasses.add(stoatClass);
|
|
425
501
|
return;
|
|
426
502
|
}
|
|
427
|
-
for (const cmdDef of
|
|
503
|
+
for (const cmdDef of allCommands) {
|
|
428
504
|
const method = instance[cmdDef.methodName];
|
|
429
|
-
if (typeof method !== "function")
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
const metadata = buildSimpleCommandMetadata(cmdDef.options, cmdDef.methodName, category);
|
|
505
|
+
if (typeof method !== "function") continue;
|
|
506
|
+
const params = this.buildParamSchema(stoatClass.prototype, cmdDef.methodName);
|
|
507
|
+
const metadata = buildSimpleCommandMetadata(cmdDef.options, cmdDef.methodName, category, params);
|
|
434
508
|
this.register(instance, metadata, stoatClass, cmdDef.methodName);
|
|
435
509
|
}
|
|
436
510
|
for (const eventDef of events) {
|
|
@@ -449,15 +523,60 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
449
523
|
this.processedStoatClasses.add(stoatClass);
|
|
450
524
|
}
|
|
451
525
|
/**
|
|
452
|
-
*
|
|
526
|
+
* Build the parameter schema for a command method by combining
|
|
527
|
+
* reflect-metadata param types with @Arg/@Option decorator metadata.
|
|
453
528
|
*/
|
|
529
|
+
buildParamSchema(prototype, methodName) {
|
|
530
|
+
const paramTypes = Reflect.getMetadata("design:paramtypes", prototype, methodName) ?? [];
|
|
531
|
+
const argDefs = getArgs(prototype, methodName);
|
|
532
|
+
const optionDefs = getOptions(prototype, methodName);
|
|
533
|
+
const argByIndex = new Map(argDefs.map((a) => [
|
|
534
|
+
a.index,
|
|
535
|
+
a
|
|
536
|
+
]));
|
|
537
|
+
const optionByIndex = new Map(optionDefs.map((o) => [
|
|
538
|
+
o.index,
|
|
539
|
+
o
|
|
540
|
+
]));
|
|
541
|
+
const params = [];
|
|
542
|
+
for (let i = 0; i < paramTypes.length; i++) {
|
|
543
|
+
const reflectedType = paramTypes[i];
|
|
544
|
+
if (optionByIndex.has(i)) {
|
|
545
|
+
const optDef = optionByIndex.get(i);
|
|
546
|
+
const resolvedType = reflectedType ? PARAM_TYPE_MAP.get(reflectedType) ?? "string" : "string";
|
|
547
|
+
params.push({
|
|
548
|
+
index: i,
|
|
549
|
+
kind: "option",
|
|
550
|
+
resolvedType,
|
|
551
|
+
name: optDef.name,
|
|
552
|
+
required: optDef.required
|
|
553
|
+
});
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if (argByIndex.has(i)) {
|
|
557
|
+
const argDef = argByIndex.get(i);
|
|
558
|
+
const resolvedType = reflectedType ? PARAM_TYPE_MAP.get(reflectedType) ?? "string" : "string";
|
|
559
|
+
params.push({
|
|
560
|
+
index: i,
|
|
561
|
+
kind: "arg",
|
|
562
|
+
resolvedType,
|
|
563
|
+
required: argDef.required,
|
|
564
|
+
fetch: argDef.fetch
|
|
565
|
+
});
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
params.push({
|
|
569
|
+
index: i,
|
|
570
|
+
kind: "ctx",
|
|
571
|
+
resolvedType: "ctx"
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
return params;
|
|
575
|
+
}
|
|
454
576
|
getCategoryFromPath(filePath, baseDir) {
|
|
455
577
|
const relative2 = path.relative(baseDir, filePath);
|
|
456
578
|
const parts = relative2.split(path.sep);
|
|
457
|
-
|
|
458
|
-
return parts[0];
|
|
459
|
-
}
|
|
460
|
-
return void 0;
|
|
579
|
+
return parts.length > 1 ? parts[0] : void 0;
|
|
461
580
|
}
|
|
462
581
|
};
|
|
463
582
|
|
|
@@ -465,16 +584,113 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
465
584
|
import "reflect-metadata";
|
|
466
585
|
|
|
467
586
|
// src/error.ts
|
|
468
|
-
var
|
|
587
|
+
var StoatxError = class extends Error {
|
|
588
|
+
static {
|
|
589
|
+
__name(this, "StoatxError");
|
|
590
|
+
}
|
|
591
|
+
constructor(message) {
|
|
592
|
+
super(message);
|
|
593
|
+
this.name = "StoatxError";
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
var CommandValidationError = class extends StoatxError {
|
|
469
597
|
static {
|
|
470
598
|
__name(this, "CommandValidationError");
|
|
471
599
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
600
|
+
paramName;
|
|
601
|
+
paramKind;
|
|
602
|
+
constructor(paramName, paramKind, message) {
|
|
603
|
+
super(message), this.paramName = paramName, this.paramKind = paramKind;
|
|
475
604
|
this.name = "CommandValidationError";
|
|
476
605
|
}
|
|
477
606
|
};
|
|
607
|
+
var MissingArgumentError = class extends CommandValidationError {
|
|
608
|
+
static {
|
|
609
|
+
__name(this, "MissingArgumentError");
|
|
610
|
+
}
|
|
611
|
+
constructor(paramName) {
|
|
612
|
+
super(paramName, "arg", `Missing required argument: \`<${paramName}>\``);
|
|
613
|
+
this.name = "MissingArgumentError";
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
var MissingOptionError = class extends CommandValidationError {
|
|
617
|
+
static {
|
|
618
|
+
__name(this, "MissingOptionError");
|
|
619
|
+
}
|
|
620
|
+
constructor(paramName, flagPrefix) {
|
|
621
|
+
super(paramName, "option", `Missing required option: \`${flagPrefix.repeat(2)}${paramName}\``);
|
|
622
|
+
this.name = "MissingOptionError";
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
var InvalidTypeError = class extends CommandValidationError {
|
|
626
|
+
static {
|
|
627
|
+
__name(this, "InvalidTypeError");
|
|
628
|
+
}
|
|
629
|
+
expected;
|
|
630
|
+
received;
|
|
631
|
+
constructor(paramName, paramKind, expected, received) {
|
|
632
|
+
super(paramName, paramKind, `Invalid value for \`${paramName}\`. Expected ${expected}, got \`${received}\`.`), this.expected = expected, this.received = received;
|
|
633
|
+
this.name = "InvalidTypeError";
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
var InvalidMentionError = class extends CommandValidationError {
|
|
637
|
+
static {
|
|
638
|
+
__name(this, "InvalidMentionError");
|
|
639
|
+
}
|
|
640
|
+
mentionKind;
|
|
641
|
+
rawValue;
|
|
642
|
+
constructor(paramName, paramKind, mentionKind, rawValue) {
|
|
643
|
+
super(paramName, paramKind, `Invalid ${mentionKind} mention for \`${paramName}\`.`), this.mentionKind = mentionKind, this.rawValue = rawValue;
|
|
644
|
+
this.name = "InvalidMentionError";
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
var FetchFailedError = class extends CommandValidationError {
|
|
648
|
+
static {
|
|
649
|
+
__name(this, "FetchFailedError");
|
|
650
|
+
}
|
|
651
|
+
mentionKind;
|
|
652
|
+
resolvedId;
|
|
653
|
+
constructor(paramName, paramKind, mentionKind, resolvedId) {
|
|
654
|
+
super(paramName, paramKind, `Could not fetch ${mentionKind} \`${resolvedId}\` for \`${paramName}\`.`), this.mentionKind = mentionKind, this.resolvedId = resolvedId;
|
|
655
|
+
this.name = "FetchFailedError";
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
var NoServerContextError = class extends CommandValidationError {
|
|
659
|
+
static {
|
|
660
|
+
__name(this, "NoServerContextError");
|
|
661
|
+
}
|
|
662
|
+
constructor(paramName, paramKind) {
|
|
663
|
+
super(paramName, paramKind, `Cannot fetch role for \`${paramName}\` outside of a server.`);
|
|
664
|
+
this.name = "NoServerContextError";
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
// src/di/Container.ts
|
|
669
|
+
import "reflect-metadata";
|
|
670
|
+
var StoatxContainer = class {
|
|
671
|
+
static {
|
|
672
|
+
__name(this, "StoatxContainer");
|
|
673
|
+
}
|
|
674
|
+
instances = /* @__PURE__ */ new Map();
|
|
675
|
+
/**
|
|
676
|
+
* Resolves a class instance, injecting any required dependencies.
|
|
677
|
+
*/
|
|
678
|
+
resolve(target) {
|
|
679
|
+
if (this.instances.has(target)) {
|
|
680
|
+
return this.instances.get(target);
|
|
681
|
+
}
|
|
682
|
+
const paramTypes = Reflect.getMetadata("design:paramtypes", target) || [];
|
|
683
|
+
const injections = paramTypes.map((param) => {
|
|
684
|
+
if (!param) {
|
|
685
|
+
throw new Error(`[Stoatx DI] Cannot resolve dependency for ${target.name}. Ensure all injected services are decorated with @Injectable().`);
|
|
686
|
+
}
|
|
687
|
+
return this.resolve(param);
|
|
688
|
+
});
|
|
689
|
+
const instance = new target(...injections);
|
|
690
|
+
this.instances.set(target, instance);
|
|
691
|
+
return instance;
|
|
692
|
+
}
|
|
693
|
+
};
|
|
478
694
|
|
|
479
695
|
// src/handler.ts
|
|
480
696
|
var DefaultCooldownManager = class {
|
|
@@ -518,6 +734,7 @@ var StoatxHandler = class {
|
|
|
518
734
|
}
|
|
519
735
|
commandsDir;
|
|
520
736
|
discoveryOptions;
|
|
737
|
+
// After
|
|
521
738
|
prefixResolver;
|
|
522
739
|
owners;
|
|
523
740
|
registry;
|
|
@@ -525,20 +742,20 @@ var StoatxHandler = class {
|
|
|
525
742
|
disableMentionPrefix;
|
|
526
743
|
client;
|
|
527
744
|
flagPrefix;
|
|
745
|
+
globalGuards;
|
|
746
|
+
container = new StoatxContainer();
|
|
528
747
|
constructor(options) {
|
|
529
748
|
this.client = options.client;
|
|
530
749
|
this.commandsDir = options.commandsDir;
|
|
531
750
|
this.discoveryOptions = options.discovery;
|
|
532
751
|
this.prefixResolver = options.prefix;
|
|
533
752
|
this.owners = new Set(options.owners ?? []);
|
|
534
|
-
this.registry = new CommandRegistry(options.extensions);
|
|
753
|
+
this.registry = new CommandRegistry(this.container, options.extensions);
|
|
535
754
|
this.disableMentionPrefix = options.disableMentionPrefix ?? false;
|
|
536
755
|
this.cooldownManager = options.cooldownManager ?? new DefaultCooldownManager();
|
|
537
756
|
this.flagPrefix = options.flagPrefix || "-";
|
|
757
|
+
this.globalGuards = options.globalGuards ?? [];
|
|
538
758
|
}
|
|
539
|
-
/**
|
|
540
|
-
* Initialize the handler - load all commands
|
|
541
|
-
*/
|
|
542
759
|
async init() {
|
|
543
760
|
if (this.commandsDir) {
|
|
544
761
|
await this.registry.loadFromDirectory(this.commandsDir);
|
|
@@ -547,9 +764,6 @@ var StoatxHandler = class {
|
|
|
547
764
|
}
|
|
548
765
|
this.attachEvents();
|
|
549
766
|
}
|
|
550
|
-
/**
|
|
551
|
-
* Attach registered events to the client
|
|
552
|
-
*/
|
|
553
767
|
attachEvents() {
|
|
554
768
|
const events = this.registry.getEvents();
|
|
555
769
|
for (const eventDef of events) {
|
|
@@ -568,12 +782,9 @@ var StoatxHandler = class {
|
|
|
568
782
|
}
|
|
569
783
|
}
|
|
570
784
|
}
|
|
571
|
-
|
|
572
|
-
* Parses raw string arguments into positional args and key-value options
|
|
573
|
-
*/
|
|
574
|
-
parseCommandOptions(rawArgs) {
|
|
785
|
+
parseRawInput(rawArgs) {
|
|
575
786
|
const args = [];
|
|
576
|
-
const
|
|
787
|
+
const flags = {};
|
|
577
788
|
for (let i = 0; i < rawArgs.length; i++) {
|
|
578
789
|
const arg = rawArgs[i];
|
|
579
790
|
if (arg === void 0) continue;
|
|
@@ -584,10 +795,10 @@ var StoatxHandler = class {
|
|
|
584
795
|
}
|
|
585
796
|
const nextArg = rawArgs[i + 1];
|
|
586
797
|
if (nextArg !== void 0 && !nextArg.startsWith(this.flagPrefix)) {
|
|
587
|
-
|
|
798
|
+
flags[key] = nextArg;
|
|
588
799
|
i++;
|
|
589
800
|
} else {
|
|
590
|
-
|
|
801
|
+
flags[key] = true;
|
|
591
802
|
}
|
|
592
803
|
} else {
|
|
593
804
|
args.push(arg);
|
|
@@ -595,78 +806,62 @@ var StoatxHandler = class {
|
|
|
595
806
|
}
|
|
596
807
|
return {
|
|
597
808
|
args,
|
|
598
|
-
|
|
809
|
+
flags
|
|
599
810
|
};
|
|
600
811
|
}
|
|
601
|
-
/**
|
|
602
|
-
* Parse a raw message into command context
|
|
603
|
-
*/
|
|
604
812
|
async parseMessage(rawContent, message, meta) {
|
|
605
|
-
const
|
|
606
|
-
let usedPrefix =
|
|
813
|
+
const prefixes = await this.resolvePrefix(meta.serverId);
|
|
814
|
+
let usedPrefix = "";
|
|
607
815
|
let withoutPrefix = "";
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
usedPrefix =
|
|
611
|
-
|
|
612
|
-
|
|
816
|
+
const matchedPrefix = prefixes.find((p) => rawContent.startsWith(p));
|
|
817
|
+
if (matchedPrefix !== void 0) {
|
|
818
|
+
usedPrefix = matchedPrefix;
|
|
819
|
+
withoutPrefix = rawContent.slice(matchedPrefix.length).trim();
|
|
820
|
+
} else if (!this.disableMentionPrefix && rawContent.match(/^<@!?\w+>/)) {
|
|
821
|
+
const mentionMatch = rawContent.match(/^<@!?(\w+)>\s*/);
|
|
613
822
|
if (mentionMatch) {
|
|
614
823
|
const mentionedId = mentionMatch[1];
|
|
615
824
|
const botId = this.client.user?.id;
|
|
616
825
|
if (botId && mentionedId === botId) {
|
|
617
826
|
usedPrefix = mentionMatch[0];
|
|
618
827
|
withoutPrefix = rawContent.slice(mentionMatch[0].length).trim();
|
|
619
|
-
} else {
|
|
620
828
|
}
|
|
621
829
|
}
|
|
622
830
|
}
|
|
623
|
-
if (!withoutPrefix)
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const
|
|
627
|
-
if (!
|
|
628
|
-
|
|
831
|
+
if (!withoutPrefix) return null;
|
|
832
|
+
const parts = withoutPrefix.split(/\s+/);
|
|
833
|
+
const part1 = parts[0];
|
|
834
|
+
const part2 = parts[1];
|
|
835
|
+
if (!part1) return null;
|
|
836
|
+
let commandKey = part1.toLowerCase();
|
|
837
|
+
let remainingParts = parts.slice(1);
|
|
838
|
+
if (part2 && this.registry.has(`${part1}:${part2}`)) {
|
|
839
|
+
commandKey = `${part1}:${part2}`.toLowerCase();
|
|
840
|
+
remainingParts = parts.slice(2);
|
|
629
841
|
}
|
|
630
|
-
const { args,
|
|
842
|
+
const { args, flags } = this.parseRawInput(remainingParts);
|
|
631
843
|
return {
|
|
632
844
|
client: this.client,
|
|
633
845
|
content: rawContent,
|
|
634
846
|
authorId: meta.authorId,
|
|
635
847
|
channelId: meta.channelId,
|
|
636
848
|
serverId: meta.serverId,
|
|
637
|
-
args,
|
|
638
|
-
options,
|
|
639
849
|
prefix: usedPrefix,
|
|
640
|
-
commandName:
|
|
850
|
+
commandName: commandKey,
|
|
641
851
|
reply: meta.reply,
|
|
642
|
-
message
|
|
852
|
+
message,
|
|
853
|
+
_rawArgs: args,
|
|
854
|
+
_rawFlags: flags
|
|
643
855
|
};
|
|
644
856
|
}
|
|
645
|
-
/**
|
|
646
|
-
* Handle a message object using the configured message adapter
|
|
647
|
-
*
|
|
648
|
-
* @example
|
|
649
|
-
* ```ts
|
|
650
|
-
* // With message adapter configured
|
|
651
|
-
* client.on('messageCreate', (message) => {
|
|
652
|
-
* handler.handle(message);
|
|
653
|
-
* });
|
|
654
|
-
* ```
|
|
655
|
-
*/
|
|
656
857
|
async handle(message) {
|
|
657
|
-
if (!message.channel || !message.author || !message.content)
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
if (message.author.bot) {
|
|
661
|
-
return false;
|
|
662
|
-
}
|
|
858
|
+
if (!message.channel || !message.author || !message.content) return false;
|
|
859
|
+
if (message.author.bot) return false;
|
|
663
860
|
const rawContent = message.content;
|
|
664
861
|
const authorId = message.author.id;
|
|
665
862
|
const channelId = message.channel.id;
|
|
666
863
|
const serverId = message.server?.id;
|
|
667
|
-
const reply = /* @__PURE__ */ __name(async (content) =>
|
|
668
|
-
return await message.channel.send(content);
|
|
669
|
-
}, "reply");
|
|
864
|
+
const reply = /* @__PURE__ */ __name(async (content) => await message.channel.send(content), "reply");
|
|
670
865
|
await this.handleMessage(rawContent, message, {
|
|
671
866
|
authorId,
|
|
672
867
|
channelId,
|
|
@@ -675,44 +870,20 @@ var StoatxHandler = class {
|
|
|
675
870
|
});
|
|
676
871
|
return true;
|
|
677
872
|
}
|
|
678
|
-
/**
|
|
679
|
-
* Handle a raw message string with metadata
|
|
680
|
-
*
|
|
681
|
-
* @example
|
|
682
|
-
* ```ts
|
|
683
|
-
* // Manual usage without message adapter
|
|
684
|
-
* client.on('messageCreate', (message) => {
|
|
685
|
-
* handler.handleMessage(message.content, message, {
|
|
686
|
-
* authorId: message.author.id,
|
|
687
|
-
* channelId: message.channel.id,
|
|
688
|
-
* serverId: message.server?.id,
|
|
689
|
-
* reply: (content) => message.channel.sendMessage(content),
|
|
690
|
-
* });
|
|
691
|
-
* });
|
|
692
|
-
* ```
|
|
693
|
-
*/
|
|
694
873
|
async handleMessage(rawContent, message, meta) {
|
|
695
874
|
const ctx = await this.parseMessage(rawContent, message, meta);
|
|
696
|
-
if (!ctx)
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
875
|
+
if (!ctx) return;
|
|
699
876
|
await this.execute(ctx);
|
|
700
877
|
}
|
|
701
|
-
/**
|
|
702
|
-
* Execute a command with the given context
|
|
703
|
-
*/
|
|
704
878
|
async execute(ctx) {
|
|
705
879
|
const registered = this.registry.get(ctx.commandName);
|
|
706
|
-
if (!registered)
|
|
707
|
-
return false;
|
|
708
|
-
}
|
|
880
|
+
if (!registered) return false;
|
|
709
881
|
const { instance, metadata, methodName, classConstructor } = registered;
|
|
710
|
-
console.log(`[Debug] Metadata options for ${ctx.commandName}:`, metadata.options);
|
|
711
882
|
if (metadata.ownerOnly && !this.owners.has(ctx.authorId)) {
|
|
712
883
|
await ctx.reply("This command is owner-only.");
|
|
713
884
|
return false;
|
|
714
885
|
}
|
|
715
|
-
if (metadata.permissions) {
|
|
886
|
+
if (metadata.permissions.length > 0) {
|
|
716
887
|
const server = ctx.message.server;
|
|
717
888
|
const member = server ? await server.members.fetch(ctx.authorId) : null;
|
|
718
889
|
if (!member || !member.permissions.has(metadata.permissions)) {
|
|
@@ -725,9 +896,16 @@ var StoatxHandler = class {
|
|
|
725
896
|
return false;
|
|
726
897
|
}
|
|
727
898
|
}
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
899
|
+
const globalGuards = this.globalGuards;
|
|
900
|
+
const classGuards = Reflect.getMetadata(METADATA_KEYS.GUARDS, classConstructor) || [];
|
|
901
|
+
const methodGuards = Reflect.getMetadata(METADATA_KEYS.GUARDS, instance, methodName) || [];
|
|
902
|
+
const allGuards = [
|
|
903
|
+
...globalGuards,
|
|
904
|
+
...classGuards,
|
|
905
|
+
...methodGuards
|
|
906
|
+
];
|
|
907
|
+
for (const guardClass of allGuards) {
|
|
908
|
+
const guardInstance = this.container.resolve(guardClass);
|
|
731
909
|
if (typeof guardInstance.run === "function") {
|
|
732
910
|
const guardResult = await guardInstance.run(ctx);
|
|
733
911
|
if (!guardResult) {
|
|
@@ -740,190 +918,6 @@ var StoatxHandler = class {
|
|
|
740
918
|
}
|
|
741
919
|
}
|
|
742
920
|
}
|
|
743
|
-
if (metadata.args) {
|
|
744
|
-
const finalArgs = [];
|
|
745
|
-
for (let i = 0; i < metadata.args.length; i++) {
|
|
746
|
-
const def = metadata.args[i];
|
|
747
|
-
const rawValue = ctx.args[i];
|
|
748
|
-
if (def === void 0) continue;
|
|
749
|
-
if (rawValue === void 0) {
|
|
750
|
-
if (def.required) {
|
|
751
|
-
try {
|
|
752
|
-
throw new CommandValidationError(def.name, `Missing required argument: \`<${def.name}>\``);
|
|
753
|
-
} catch (error) {
|
|
754
|
-
if (typeof instance.onError === "function") {
|
|
755
|
-
await instance.onError(ctx, error);
|
|
756
|
-
} else {
|
|
757
|
-
await ctx.reply(`Missing required argument: \`<${def.name}>\``);
|
|
758
|
-
}
|
|
759
|
-
return false;
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
break;
|
|
763
|
-
}
|
|
764
|
-
if (def.type === "number") {
|
|
765
|
-
const numValue = Number(rawValue);
|
|
766
|
-
if (isNaN(numValue)) {
|
|
767
|
-
try {
|
|
768
|
-
throw new CommandValidationError(def.name, `Invalid value for \`<${def.name}>\`. Expected a number.`);
|
|
769
|
-
} catch (error) {
|
|
770
|
-
if (typeof instance.onError === "function") {
|
|
771
|
-
await instance.onError(ctx, error);
|
|
772
|
-
} else {
|
|
773
|
-
await ctx.reply(`Invalid value for \`<${def.name}>\`. Expected a number.`);
|
|
774
|
-
}
|
|
775
|
-
return false;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
finalArgs[i] = numValue;
|
|
779
|
-
} else if (def.type === "boolean") {
|
|
780
|
-
finalArgs[i] = rawValue === "false" ? false : Boolean(rawValue);
|
|
781
|
-
} else if (def.type === "user") {
|
|
782
|
-
const match = String(rawValue).match(/^(?:<@)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
|
|
783
|
-
if (!match) {
|
|
784
|
-
try {
|
|
785
|
-
throw new CommandValidationError(def.name, `Invalid user mention for \`<${def.name}>\`.`);
|
|
786
|
-
} catch (error) {
|
|
787
|
-
if (typeof instance.onError === "function") {
|
|
788
|
-
await instance.onError(ctx, error);
|
|
789
|
-
} else {
|
|
790
|
-
await ctx.reply(`Invalid user mention for \`<${def.name}>\`.`);
|
|
791
|
-
}
|
|
792
|
-
return false;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
finalArgs[i] = match[1];
|
|
796
|
-
} else if (def.type === "channel") {
|
|
797
|
-
const match = String(rawValue).match(/^(?:<#)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
|
|
798
|
-
if (!match) {
|
|
799
|
-
try {
|
|
800
|
-
throw new CommandValidationError(def.name, `Invalid channel mention for \`<${def.name}>\`.`);
|
|
801
|
-
} catch (error) {
|
|
802
|
-
if (typeof instance.onError === "function") {
|
|
803
|
-
await instance.onError(ctx, error);
|
|
804
|
-
} else {
|
|
805
|
-
await ctx.reply(`Invalid channel mention for \`<${def.name}>\`.`);
|
|
806
|
-
}
|
|
807
|
-
return false;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
finalArgs[i] = match[1];
|
|
811
|
-
} else if (def.type === "role") {
|
|
812
|
-
const match = String(rawValue).match(/^(?:<%)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
|
|
813
|
-
if (!match) {
|
|
814
|
-
try {
|
|
815
|
-
throw new CommandValidationError(def.name, `Invalid role mention for \`<${def.name}>\`.`);
|
|
816
|
-
} catch (error) {
|
|
817
|
-
if (typeof instance.onError === "function") {
|
|
818
|
-
await instance.onError(ctx, error);
|
|
819
|
-
} else {
|
|
820
|
-
await ctx.reply(`Invalid role mention for \`<${def.name}>\`.`);
|
|
821
|
-
}
|
|
822
|
-
return false;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
finalArgs[i] = match[1];
|
|
826
|
-
} else {
|
|
827
|
-
finalArgs[i] = String(rawValue);
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
if (ctx.args.length > metadata.args.length) {
|
|
831
|
-
for (let i = metadata.args.length; i < ctx.args.length; i++) {
|
|
832
|
-
if (ctx.args[i] !== void 0) {
|
|
833
|
-
finalArgs.push(ctx.args[i]);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
ctx.args = finalArgs;
|
|
838
|
-
}
|
|
839
|
-
const finalOptions = {};
|
|
840
|
-
const currentOptions = ctx.options || {};
|
|
841
|
-
if (metadata.options) {
|
|
842
|
-
for (const def of metadata.options) {
|
|
843
|
-
const rawValue = currentOptions[def.name];
|
|
844
|
-
if (rawValue === void 0) {
|
|
845
|
-
if (def.required) {
|
|
846
|
-
try {
|
|
847
|
-
throw new CommandValidationError(def.name, `Missing required option: \`--${def.name}\``);
|
|
848
|
-
} catch (error) {
|
|
849
|
-
if (typeof instance.onError === "function") {
|
|
850
|
-
await instance.onError(ctx, error);
|
|
851
|
-
} else {
|
|
852
|
-
await ctx.reply(`Missing required option: \`--${def.name}\``);
|
|
853
|
-
}
|
|
854
|
-
return false;
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
continue;
|
|
858
|
-
}
|
|
859
|
-
if (def.type === "number") {
|
|
860
|
-
const numValue = Number(rawValue);
|
|
861
|
-
if (isNaN(numValue)) {
|
|
862
|
-
try {
|
|
863
|
-
throw new CommandValidationError(def.name, `Invalid value for \`--${def.name}\`. Expected a number.`);
|
|
864
|
-
} catch (error) {
|
|
865
|
-
if (typeof instance.onError === "function") {
|
|
866
|
-
await instance.onError(ctx, error);
|
|
867
|
-
} else {
|
|
868
|
-
await ctx.reply(`Invalid value for \`--${def.name}\`. Expected a number.`);
|
|
869
|
-
}
|
|
870
|
-
return false;
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
finalOptions[def.name] = numValue;
|
|
874
|
-
} else if (def.type === "boolean") {
|
|
875
|
-
finalOptions[def.name] = rawValue === "false" ? false : Boolean(rawValue);
|
|
876
|
-
} else if (def.type === "user") {
|
|
877
|
-
const match = String(rawValue).match(/^(?:<@)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
|
|
878
|
-
if (!match) {
|
|
879
|
-
try {
|
|
880
|
-
throw new CommandValidationError(def.name, `Invalid user mention for \`--${def.name}\`.`);
|
|
881
|
-
} catch (error) {
|
|
882
|
-
if (typeof instance.onError === "function") {
|
|
883
|
-
await instance.onError(ctx, error);
|
|
884
|
-
} else {
|
|
885
|
-
await ctx.reply(`Invalid user mention for \`--${def.name}\`.`);
|
|
886
|
-
}
|
|
887
|
-
return false;
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
finalOptions[def.name] = match[1];
|
|
891
|
-
} else if (def.type === "channel") {
|
|
892
|
-
const match = String(rawValue).match(/^(?:<#)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
|
|
893
|
-
if (!match) {
|
|
894
|
-
try {
|
|
895
|
-
throw new CommandValidationError(def.name, `Invalid channel mention for \`--${def.name}\`.`);
|
|
896
|
-
} catch (error) {
|
|
897
|
-
if (typeof instance.onError === "function") {
|
|
898
|
-
await instance.onError(ctx, error);
|
|
899
|
-
} else {
|
|
900
|
-
await ctx.reply(`Invalid channel mention for \`--${def.name}\`.`);
|
|
901
|
-
}
|
|
902
|
-
return false;
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
finalOptions[def.name] = match[1];
|
|
906
|
-
} else if (def.type === "role") {
|
|
907
|
-
const match = String(rawValue).match(/^(?:<%)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
|
|
908
|
-
if (!match) {
|
|
909
|
-
try {
|
|
910
|
-
throw new CommandValidationError(def.name, `Invalid role mention for \`--${def.name}\`.`);
|
|
911
|
-
} catch (error) {
|
|
912
|
-
if (typeof instance.onError === "function") {
|
|
913
|
-
await instance.onError(ctx, error);
|
|
914
|
-
} else {
|
|
915
|
-
await ctx.reply(`Invalid role mention for \`--${def.name}\`.`);
|
|
916
|
-
}
|
|
917
|
-
return false;
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
finalOptions[def.name] = match[1];
|
|
921
|
-
} else {
|
|
922
|
-
finalOptions[def.name] = String(rawValue);
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
ctx.options = finalOptions;
|
|
926
|
-
}
|
|
927
921
|
if (!await this.cooldownManager.check(ctx, metadata)) {
|
|
928
922
|
const remaining = await this.cooldownManager.getRemaining(ctx, metadata);
|
|
929
923
|
if (typeof instance.onCooldown === "function") {
|
|
@@ -933,42 +927,152 @@ var StoatxHandler = class {
|
|
|
933
927
|
}
|
|
934
928
|
return false;
|
|
935
929
|
}
|
|
930
|
+
const resolvedParams = await this.resolveParams(metadata.params, ctx, instance);
|
|
931
|
+
if (resolvedParams === null) return false;
|
|
936
932
|
try {
|
|
937
933
|
if (metadata.cooldown > 0) {
|
|
938
934
|
await this.cooldownManager.set(ctx, metadata);
|
|
939
935
|
}
|
|
940
|
-
await instance[methodName](
|
|
936
|
+
await instance[methodName](...resolvedParams);
|
|
941
937
|
return true;
|
|
942
938
|
} catch (error) {
|
|
943
939
|
if (typeof instance.onError === "function") {
|
|
944
940
|
await instance.onError(ctx, error);
|
|
945
941
|
} else {
|
|
946
942
|
console.error(`[Stoatx] Error in command ${metadata.name}:`, error);
|
|
943
|
+
await ctx.reply("Something went wrong. Please try again later.");
|
|
947
944
|
}
|
|
948
945
|
return false;
|
|
949
946
|
}
|
|
950
947
|
}
|
|
951
948
|
/**
|
|
952
|
-
*
|
|
949
|
+
* Report a validation error to the instance via onValidationError → onError → default reply
|
|
953
950
|
*/
|
|
951
|
+
async reportValidationError(instance, ctx, error) {
|
|
952
|
+
if (typeof instance.onValidationError === "function") {
|
|
953
|
+
await instance.onValidationError(ctx, error);
|
|
954
|
+
} else if (typeof instance.onError === "function") {
|
|
955
|
+
await instance.onError(ctx, error);
|
|
956
|
+
} else {
|
|
957
|
+
await ctx.reply(error.message);
|
|
958
|
+
}
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
async resolveParams(params, ctx, instance) {
|
|
962
|
+
const resolved = new Array(params.length);
|
|
963
|
+
let argCursor = 0;
|
|
964
|
+
for (const param of params) {
|
|
965
|
+
if (param.kind === "ctx") {
|
|
966
|
+
resolved[param.index] = ctx;
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
if (param.kind === "arg") {
|
|
970
|
+
const rawValue = ctx._rawArgs[argCursor++];
|
|
971
|
+
if (rawValue === void 0) {
|
|
972
|
+
if (param.required) {
|
|
973
|
+
const paramName = param.name ?? `arg[${param.index}]`;
|
|
974
|
+
return this.reportValidationError(instance, ctx, new MissingArgumentError(paramName));
|
|
975
|
+
}
|
|
976
|
+
resolved[param.index] = void 0;
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
const value = await this.resolveValue(rawValue, param, ctx, instance, "arg");
|
|
980
|
+
if (value === null) return null;
|
|
981
|
+
resolved[param.index] = value;
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
if (param.kind === "option") {
|
|
985
|
+
const rawValue = ctx._rawFlags[param.name];
|
|
986
|
+
if (rawValue === void 0) {
|
|
987
|
+
if (param.required) {
|
|
988
|
+
return this.reportValidationError(instance, ctx, new MissingOptionError(param.name, this.flagPrefix));
|
|
989
|
+
}
|
|
990
|
+
resolved[param.index] = void 0;
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
const value = await this.resolveValue(String(rawValue), param, ctx, instance, "option");
|
|
994
|
+
if (value === null) return null;
|
|
995
|
+
resolved[param.index] = value;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return resolved;
|
|
999
|
+
}
|
|
1000
|
+
async resolveValue(rawValue, param, ctx, instance, kind) {
|
|
1001
|
+
const paramName = kind === "arg" ? param.name ?? `arg[${param.index}]` : param.name;
|
|
1002
|
+
switch (param.resolvedType) {
|
|
1003
|
+
case "string":
|
|
1004
|
+
return String(rawValue);
|
|
1005
|
+
case "number": {
|
|
1006
|
+
const num = Number(rawValue);
|
|
1007
|
+
if (isNaN(num)) {
|
|
1008
|
+
return this.reportValidationError(instance, ctx, new InvalidTypeError(paramName, kind, "a number", rawValue));
|
|
1009
|
+
}
|
|
1010
|
+
return num;
|
|
1011
|
+
}
|
|
1012
|
+
case "boolean":
|
|
1013
|
+
return rawValue === "false" ? false : Boolean(rawValue);
|
|
1014
|
+
case "user": {
|
|
1015
|
+
const match = rawValue.match(/^(?:<@!?)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
|
|
1016
|
+
if (!match) {
|
|
1017
|
+
return this.reportValidationError(instance, ctx, new InvalidMentionError(paramName, kind, "user", rawValue));
|
|
1018
|
+
}
|
|
1019
|
+
const userId = match[1];
|
|
1020
|
+
if (param.fetch) {
|
|
1021
|
+
try {
|
|
1022
|
+
return await this.client.users.fetch(userId);
|
|
1023
|
+
} catch {
|
|
1024
|
+
return this.reportValidationError(instance, ctx, new FetchFailedError(paramName, kind, "user", userId));
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return this.client.users.cache.get(userId) ?? userId;
|
|
1028
|
+
}
|
|
1029
|
+
case "channel": {
|
|
1030
|
+
const match = rawValue.match(/^(?:<#)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
|
|
1031
|
+
if (!match) {
|
|
1032
|
+
return this.reportValidationError(instance, ctx, new InvalidMentionError(paramName, kind, "channel", rawValue));
|
|
1033
|
+
}
|
|
1034
|
+
const channelId = match[1];
|
|
1035
|
+
if (param.fetch) {
|
|
1036
|
+
try {
|
|
1037
|
+
return await this.client.channels.fetch(channelId);
|
|
1038
|
+
} catch {
|
|
1039
|
+
return this.reportValidationError(instance, ctx, new FetchFailedError(paramName, kind, "channel", channelId));
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
return this.client.channels.cache.get(channelId) ?? channelId;
|
|
1043
|
+
}
|
|
1044
|
+
case "role": {
|
|
1045
|
+
const match = rawValue.match(/^(?:<@&)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
|
|
1046
|
+
if (!match) {
|
|
1047
|
+
return this.reportValidationError(instance, ctx, new InvalidMentionError(paramName, kind, "role", rawValue));
|
|
1048
|
+
}
|
|
1049
|
+
const roleId = match[1];
|
|
1050
|
+
if (param.fetch) {
|
|
1051
|
+
const server = ctx.message.server;
|
|
1052
|
+
if (!server) {
|
|
1053
|
+
return this.reportValidationError(instance, ctx, new NoServerContextError(paramName, kind));
|
|
1054
|
+
}
|
|
1055
|
+
try {
|
|
1056
|
+
return await server.roles.fetch(roleId);
|
|
1057
|
+
} catch {
|
|
1058
|
+
return this.reportValidationError(instance, ctx, new FetchFailedError(paramName, kind, "role", roleId));
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
return ctx.message.server?.roles.cache.get(roleId) ?? roleId;
|
|
1062
|
+
}
|
|
1063
|
+
default:
|
|
1064
|
+
return rawValue;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
954
1067
|
getRegistry() {
|
|
955
1068
|
return this.registry;
|
|
956
1069
|
}
|
|
957
|
-
/**
|
|
958
|
-
* Get a command by name or alias
|
|
959
|
-
*/
|
|
960
1070
|
getCommand(name) {
|
|
961
1071
|
return this.registry.get(name);
|
|
962
1072
|
}
|
|
963
|
-
/**
|
|
964
|
-
* Get all commands
|
|
965
|
-
*/
|
|
966
1073
|
getCommands() {
|
|
967
1074
|
return this.registry.getAll();
|
|
968
1075
|
}
|
|
969
|
-
/**
|
|
970
|
-
* Reload all commands
|
|
971
|
-
*/
|
|
972
1076
|
async reload() {
|
|
973
1077
|
this.registry.clear();
|
|
974
1078
|
if (this.cooldownManager.clear) {
|
|
@@ -980,34 +1084,22 @@ var StoatxHandler = class {
|
|
|
980
1084
|
}
|
|
981
1085
|
await this.registry.autoDiscover(this.discoveryOptions);
|
|
982
1086
|
}
|
|
983
|
-
/**
|
|
984
|
-
* Check if a user is an owner
|
|
985
|
-
*/
|
|
986
1087
|
isOwner(userId) {
|
|
987
1088
|
return this.owners.has(userId);
|
|
988
1089
|
}
|
|
989
|
-
/**
|
|
990
|
-
* Add an owner
|
|
991
|
-
*/
|
|
992
1090
|
addOwner(userId) {
|
|
993
1091
|
this.owners.add(userId);
|
|
994
1092
|
}
|
|
995
|
-
/**
|
|
996
|
-
* Remove an owner
|
|
997
|
-
*/
|
|
998
1093
|
removeOwner(userId) {
|
|
999
1094
|
this.owners.delete(userId);
|
|
1000
1095
|
}
|
|
1001
|
-
/**
|
|
1002
|
-
* Resolve the prefix for a context
|
|
1003
|
-
*/
|
|
1004
1096
|
async resolvePrefix(serverId) {
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1097
|
+
const result = typeof this.prefixResolver === "function" ? await this.prefixResolver({
|
|
1098
|
+
serverId
|
|
1099
|
+
}) : this.prefixResolver;
|
|
1100
|
+
return Array.isArray(result) ? result : [
|
|
1101
|
+
result
|
|
1102
|
+
];
|
|
1011
1103
|
}
|
|
1012
1104
|
};
|
|
1013
1105
|
|
|
@@ -1037,16 +1129,29 @@ var Client = class extends StoatClient {
|
|
|
1037
1129
|
// src/index.ts
|
|
1038
1130
|
export * from "@stoatx/client";
|
|
1039
1131
|
export {
|
|
1132
|
+
Arg,
|
|
1040
1133
|
Client,
|
|
1134
|
+
CommandGroup,
|
|
1041
1135
|
CommandRegistry,
|
|
1042
1136
|
CommandValidationError,
|
|
1043
1137
|
DefaultCooldownManager,
|
|
1138
|
+
FetchFailedError,
|
|
1044
1139
|
Guard,
|
|
1140
|
+
Injectable,
|
|
1141
|
+
InvalidMentionError,
|
|
1142
|
+
InvalidTypeError,
|
|
1045
1143
|
METADATA_KEYS,
|
|
1144
|
+
MissingArgumentError,
|
|
1145
|
+
MissingOptionError,
|
|
1146
|
+
NoServerContextError,
|
|
1046
1147
|
On,
|
|
1047
1148
|
Once,
|
|
1149
|
+
Option,
|
|
1150
|
+
PARAM_TYPE_MAP,
|
|
1048
1151
|
SimpleCommand,
|
|
1049
1152
|
Stoat,
|
|
1153
|
+
StoatxError,
|
|
1154
|
+
SubCommand,
|
|
1050
1155
|
buildSimpleCommandMetadata,
|
|
1051
1156
|
getEventsMetadata,
|
|
1052
1157
|
getGuards,
|