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