stoatx 0.7.7 → 0.8.0
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/README.md +3 -264
- package/dist/index.d.mts +42 -9
- package/dist/index.d.ts +42 -9
- package/dist/index.js +416 -135
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +417 -135
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
9
|
var __export = (target, all) => {
|
|
9
10
|
for (var name in all)
|
|
10
11
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -33,6 +34,7 @@ var index_exports = {};
|
|
|
33
34
|
__export(index_exports, {
|
|
34
35
|
Client: () => Client,
|
|
35
36
|
CommandRegistry: () => CommandRegistry,
|
|
37
|
+
CommandValidationError: () => CommandValidationError,
|
|
36
38
|
DefaultCooldownManager: () => DefaultCooldownManager,
|
|
37
39
|
Guard: () => Guard,
|
|
38
40
|
METADATA_KEYS: () => METADATA_KEYS,
|
|
@@ -61,6 +63,9 @@ var METADATA_KEYS = {
|
|
|
61
63
|
|
|
62
64
|
// src/decorators/store.ts
|
|
63
65
|
var DecoratorStore = class _DecoratorStore {
|
|
66
|
+
static {
|
|
67
|
+
__name(this, "DecoratorStore");
|
|
68
|
+
}
|
|
64
69
|
static instance;
|
|
65
70
|
/** Stoat classes with their SimpleCommand methods */
|
|
66
71
|
stoatClasses = /* @__PURE__ */ new Map();
|
|
@@ -77,8 +82,8 @@ var DecoratorStore = class _DecoratorStore {
|
|
|
77
82
|
return _DecoratorStore.instance;
|
|
78
83
|
}
|
|
79
84
|
/**
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
* Register a @Stoat decorated class
|
|
86
|
+
*/
|
|
82
87
|
registerStoatClass(classConstructor) {
|
|
83
88
|
if (!this.stoatClasses.has(classConstructor)) {
|
|
84
89
|
const instance = new classConstructor();
|
|
@@ -86,40 +91,40 @@ var DecoratorStore = class _DecoratorStore {
|
|
|
86
91
|
}
|
|
87
92
|
}
|
|
88
93
|
/**
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
* Get all registered Stoat classes with their instances
|
|
95
|
+
*/
|
|
91
96
|
getStoatClasses() {
|
|
92
97
|
return this.stoatClasses;
|
|
93
98
|
}
|
|
94
99
|
/**
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
* Add a registered command
|
|
101
|
+
*/
|
|
97
102
|
addCommand(command) {
|
|
98
103
|
this.commands.push(command);
|
|
99
104
|
}
|
|
100
105
|
/**
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
* Get all registered commands
|
|
107
|
+
*/
|
|
103
108
|
getCommands() {
|
|
104
109
|
return this.commands;
|
|
105
110
|
}
|
|
106
111
|
/**
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
* Clear all registered classes (useful for testing)
|
|
113
|
+
*/
|
|
109
114
|
clear() {
|
|
110
115
|
this.stoatClasses.clear();
|
|
111
116
|
this.commands = [];
|
|
112
117
|
this.initialized = false;
|
|
113
118
|
}
|
|
114
119
|
/**
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
* Mark as initialized
|
|
121
|
+
*/
|
|
117
122
|
markInitialized() {
|
|
118
123
|
this.initialized = true;
|
|
119
124
|
}
|
|
120
125
|
/**
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
* Check if initialized
|
|
127
|
+
*/
|
|
123
128
|
isInitialized() {
|
|
124
129
|
return this.initialized;
|
|
125
130
|
}
|
|
@@ -133,9 +138,11 @@ function Stoat() {
|
|
|
133
138
|
decoratorStore.registerStoatClass(target);
|
|
134
139
|
};
|
|
135
140
|
}
|
|
141
|
+
__name(Stoat, "Stoat");
|
|
136
142
|
function isStoatClass(target) {
|
|
137
143
|
return Reflect.getMetadata(METADATA_KEYS.IS_STOAT_CLASS, target) === true;
|
|
138
144
|
}
|
|
145
|
+
__name(isStoatClass, "isStoatClass");
|
|
139
146
|
|
|
140
147
|
// src/decorators/SimpleCommand.ts
|
|
141
148
|
var import_reflect_metadata2 = require("reflect-metadata");
|
|
@@ -151,9 +158,11 @@ function SimpleCommand(options = {}) {
|
|
|
151
158
|
return descriptor;
|
|
152
159
|
};
|
|
153
160
|
}
|
|
161
|
+
__name(SimpleCommand, "SimpleCommand");
|
|
154
162
|
function getSimpleCommands(target) {
|
|
155
163
|
return Reflect.getMetadata(METADATA_KEYS.SIMPLE_COMMANDS, target) || [];
|
|
156
164
|
}
|
|
165
|
+
__name(getSimpleCommands, "getSimpleCommands");
|
|
157
166
|
|
|
158
167
|
// src/decorators/Guard.ts
|
|
159
168
|
var import_reflect_metadata3 = require("reflect-metadata");
|
|
@@ -164,9 +173,11 @@ function Guard(guardClass) {
|
|
|
164
173
|
Reflect.defineMetadata(METADATA_KEYS.GUARDS, existingGuards, target);
|
|
165
174
|
};
|
|
166
175
|
}
|
|
176
|
+
__name(Guard, "Guard");
|
|
167
177
|
function getGuards(target) {
|
|
168
178
|
return Reflect.getMetadata(METADATA_KEYS.GUARDS, target) || [];
|
|
169
179
|
}
|
|
180
|
+
__name(getGuards, "getGuards");
|
|
170
181
|
|
|
171
182
|
// src/decorators/Events.ts
|
|
172
183
|
var import_reflect_metadata4 = require("reflect-metadata");
|
|
@@ -183,15 +194,19 @@ function createEventDecorator(event, type) {
|
|
|
183
194
|
return descriptor;
|
|
184
195
|
};
|
|
185
196
|
}
|
|
197
|
+
__name(createEventDecorator, "createEventDecorator");
|
|
186
198
|
function On(event) {
|
|
187
199
|
return createEventDecorator(event, "on");
|
|
188
200
|
}
|
|
201
|
+
__name(On, "On");
|
|
189
202
|
function Once(event) {
|
|
190
203
|
return createEventDecorator(event, "once");
|
|
191
204
|
}
|
|
205
|
+
__name(Once, "Once");
|
|
192
206
|
function getEventsMetadata(target) {
|
|
193
207
|
return Reflect.getMetadata(METADATA_KEYS.EVENTS, target) || [];
|
|
194
208
|
}
|
|
209
|
+
__name(getEventsMetadata, "getEventsMetadata");
|
|
195
210
|
|
|
196
211
|
// src/decorators/utils.ts
|
|
197
212
|
function buildSimpleCommandMetadata(options, methodName, category) {
|
|
@@ -202,11 +217,16 @@ function buildSimpleCommandMetadata(options, methodName, category) {
|
|
|
202
217
|
permissions: options.permissions ?? [],
|
|
203
218
|
category: options.category ?? category ?? "uncategorized",
|
|
204
219
|
cooldown: options.cooldown ?? 0,
|
|
205
|
-
...options.cooldownStorage !== void 0 ? {
|
|
220
|
+
...options.cooldownStorage !== void 0 ? {
|
|
221
|
+
cooldownStorage: options.cooldownStorage
|
|
222
|
+
} : {},
|
|
206
223
|
nsfw: options.nsfw ?? false,
|
|
207
|
-
ownerOnly: options.ownerOnly ?? false
|
|
224
|
+
ownerOnly: options.ownerOnly ?? false,
|
|
225
|
+
options: options.options ?? [],
|
|
226
|
+
args: options.args ?? []
|
|
208
227
|
};
|
|
209
228
|
}
|
|
229
|
+
__name(buildSimpleCommandMetadata, "buildSimpleCommandMetadata");
|
|
210
230
|
|
|
211
231
|
// src/registry.ts
|
|
212
232
|
var path = __toESM(require("path"));
|
|
@@ -214,6 +234,9 @@ var fs = __toESM(require("fs/promises"));
|
|
|
214
234
|
var import_node_url = require("url");
|
|
215
235
|
var import_tinyglobby = require("tinyglobby");
|
|
216
236
|
var CommandRegistry = class _CommandRegistry {
|
|
237
|
+
static {
|
|
238
|
+
__name(this, "CommandRegistry");
|
|
239
|
+
}
|
|
217
240
|
static DEFAULT_AUTO_DISCOVERY_IGNORES = [
|
|
218
241
|
"**/node_modules/**",
|
|
219
242
|
"**/.git/**",
|
|
@@ -226,23 +249,31 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
226
249
|
registeredEvents = [];
|
|
227
250
|
extensions;
|
|
228
251
|
processedStoatClasses = /* @__PURE__ */ new Set();
|
|
229
|
-
constructor(extensions = [
|
|
252
|
+
constructor(extensions = [
|
|
253
|
+
".js",
|
|
254
|
+
".mjs",
|
|
255
|
+
".cjs"
|
|
256
|
+
]) {
|
|
230
257
|
this.extensions = extensions;
|
|
231
258
|
}
|
|
232
259
|
/**
|
|
233
|
-
|
|
234
|
-
|
|
260
|
+
* Get the number of registered commands
|
|
261
|
+
*/
|
|
235
262
|
get size() {
|
|
236
263
|
return this.commands.size;
|
|
237
264
|
}
|
|
238
265
|
/**
|
|
239
|
-
|
|
240
|
-
|
|
266
|
+
* Load commands from a directory using glob pattern matching
|
|
267
|
+
*/
|
|
241
268
|
async loadFromDirectory(directory) {
|
|
242
269
|
const patterns = this.extensions.map((ext) => path.join(directory, "**", `*${ext}`).replace(/\\/g, "/"));
|
|
243
270
|
for (const pattern of patterns) {
|
|
244
271
|
const files = await (0, import_tinyglobby.glob)(pattern, {
|
|
245
|
-
ignore: [
|
|
272
|
+
ignore: [
|
|
273
|
+
"**/*.d.ts",
|
|
274
|
+
"**/*.test.ts",
|
|
275
|
+
"**/*.spec.ts"
|
|
276
|
+
],
|
|
246
277
|
absolute: true
|
|
247
278
|
});
|
|
248
279
|
for (const file of files) {
|
|
@@ -252,19 +283,24 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
252
283
|
console.log(`[Stoatx] Loaded ${this.commands.size} command(s) and ${this.registeredEvents.length} event(s)`);
|
|
253
284
|
}
|
|
254
285
|
/**
|
|
255
|
-
|
|
256
|
-
|
|
286
|
+
* Auto-discover command files across one or more roots.
|
|
287
|
+
*/
|
|
257
288
|
async autoDiscover(options = {}) {
|
|
258
|
-
const roots = options.roots?.length ? options.roots : [
|
|
289
|
+
const roots = options.roots?.length ? options.roots : [
|
|
290
|
+
process.cwd()
|
|
291
|
+
];
|
|
259
292
|
const includePatterns = options.include?.length ? options.include : this.getDefaultAutoDiscoveryPatterns();
|
|
260
|
-
const patterns = roots.flatMap(
|
|
261
|
-
(root) => includePatterns.map((pattern) => path.join(root, pattern).replace(/\\/g, "/"))
|
|
262
|
-
);
|
|
293
|
+
const patterns = roots.flatMap((root) => includePatterns.map((pattern) => path.join(root, pattern).replace(/\\/g, "/")));
|
|
263
294
|
const files = await (0, import_tinyglobby.glob)(patterns, {
|
|
264
|
-
ignore: [
|
|
295
|
+
ignore: [
|
|
296
|
+
..._CommandRegistry.DEFAULT_AUTO_DISCOVERY_IGNORES,
|
|
297
|
+
...options.ignore ?? []
|
|
298
|
+
],
|
|
265
299
|
absolute: true
|
|
266
300
|
});
|
|
267
|
-
const uniqueFiles = [
|
|
301
|
+
const uniqueFiles = [
|
|
302
|
+
...new Set(files)
|
|
303
|
+
];
|
|
268
304
|
let candidateFiles = 0;
|
|
269
305
|
for (const file of uniqueFiles) {
|
|
270
306
|
if (!await this.isLikelyCommandModule(file)) {
|
|
@@ -291,8 +327,8 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
291
327
|
}
|
|
292
328
|
}
|
|
293
329
|
/**
|
|
294
|
-
|
|
295
|
-
|
|
330
|
+
* Register a command instance
|
|
331
|
+
*/
|
|
296
332
|
register(instance, metadata, classConstructor, methodName) {
|
|
297
333
|
const name = metadata.name.toLowerCase();
|
|
298
334
|
if (this.commands.has(name)) {
|
|
@@ -300,7 +336,12 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
300
336
|
return;
|
|
301
337
|
}
|
|
302
338
|
this.validateGuards(classConstructor, metadata.name);
|
|
303
|
-
this.commands.set(name, {
|
|
339
|
+
this.commands.set(name, {
|
|
340
|
+
instance,
|
|
341
|
+
metadata,
|
|
342
|
+
methodName,
|
|
343
|
+
classConstructor
|
|
344
|
+
});
|
|
304
345
|
for (const alias of metadata.aliases) {
|
|
305
346
|
const aliasLower = alias.toLowerCase();
|
|
306
347
|
if (this.aliases.has(aliasLower) || this.commands.has(aliasLower)) {
|
|
@@ -311,41 +352,41 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
311
352
|
}
|
|
312
353
|
}
|
|
313
354
|
/**
|
|
314
|
-
|
|
315
|
-
|
|
355
|
+
* Get a command by name or alias
|
|
356
|
+
*/
|
|
316
357
|
get(name) {
|
|
317
358
|
const lowerName = name.toLowerCase();
|
|
318
359
|
const resolvedName = this.aliases.get(lowerName) ?? lowerName;
|
|
319
360
|
return this.commands.get(resolvedName);
|
|
320
361
|
}
|
|
321
362
|
/**
|
|
322
|
-
|
|
323
|
-
|
|
363
|
+
* Check if a command exists
|
|
364
|
+
*/
|
|
324
365
|
has(name) {
|
|
325
366
|
const lowerName = name.toLowerCase();
|
|
326
367
|
return this.commands.has(lowerName) || this.aliases.has(lowerName);
|
|
327
368
|
}
|
|
328
369
|
/**
|
|
329
|
-
|
|
330
|
-
|
|
370
|
+
* Get all registered commands
|
|
371
|
+
*/
|
|
331
372
|
getAll() {
|
|
332
373
|
return Array.from(this.commands.values());
|
|
333
374
|
}
|
|
334
375
|
/**
|
|
335
|
-
|
|
336
|
-
|
|
376
|
+
* Get all command metadata
|
|
377
|
+
*/
|
|
337
378
|
getAllMetadata() {
|
|
338
379
|
return this.getAll().map((c) => c.metadata);
|
|
339
380
|
}
|
|
340
381
|
/**
|
|
341
|
-
|
|
342
|
-
|
|
382
|
+
* Get all registered events
|
|
383
|
+
*/
|
|
343
384
|
getEvents() {
|
|
344
385
|
return this.registeredEvents;
|
|
345
386
|
}
|
|
346
387
|
/**
|
|
347
|
-
|
|
348
|
-
|
|
388
|
+
* Get commands grouped by category
|
|
389
|
+
*/
|
|
349
390
|
getByCategory() {
|
|
350
391
|
const categories = /* @__PURE__ */ new Map();
|
|
351
392
|
for (const cmd of this.commands.values()) {
|
|
@@ -357,8 +398,8 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
357
398
|
return categories;
|
|
358
399
|
}
|
|
359
400
|
/**
|
|
360
|
-
|
|
361
|
-
|
|
401
|
+
* Clear all commands
|
|
402
|
+
*/
|
|
362
403
|
clear() {
|
|
363
404
|
this.commands.clear();
|
|
364
405
|
this.aliases.clear();
|
|
@@ -366,51 +407,47 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
366
407
|
this.processedStoatClasses.clear();
|
|
367
408
|
}
|
|
368
409
|
/**
|
|
369
|
-
|
|
370
|
-
|
|
410
|
+
* Iterate over commands
|
|
411
|
+
*/
|
|
371
412
|
[Symbol.iterator]() {
|
|
372
413
|
return this.commands.entries();
|
|
373
414
|
}
|
|
374
415
|
/**
|
|
375
|
-
|
|
376
|
-
|
|
416
|
+
* Iterate over command values
|
|
417
|
+
*/
|
|
377
418
|
values() {
|
|
378
419
|
return this.commands.values();
|
|
379
420
|
}
|
|
380
421
|
/**
|
|
381
|
-
|
|
382
|
-
|
|
422
|
+
* Iterate over command names
|
|
423
|
+
*/
|
|
383
424
|
keys() {
|
|
384
425
|
return this.commands.keys();
|
|
385
426
|
}
|
|
386
427
|
/**
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
428
|
+
* Validate that all guards on a command implement the required methods
|
|
429
|
+
* @param commandClass
|
|
430
|
+
* @param commandName
|
|
431
|
+
* @private
|
|
432
|
+
*/
|
|
392
433
|
validateGuards(commandClass, commandName) {
|
|
393
434
|
const guards = Reflect.getMetadata("stoatx:command:guards", commandClass) || [];
|
|
394
435
|
for (const GuardClass of guards) {
|
|
395
436
|
const guardInstance = new GuardClass();
|
|
396
437
|
if (typeof guardInstance.run !== "function") {
|
|
397
|
-
console.error(
|
|
398
|
-
`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`
|
|
399
|
-
);
|
|
438
|
+
console.error(`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`);
|
|
400
439
|
process.exit(1);
|
|
401
440
|
}
|
|
402
441
|
if (typeof guardInstance.guardFail !== "function") {
|
|
403
|
-
console.error(
|
|
404
|
-
`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`
|
|
405
|
-
);
|
|
442
|
+
console.error(`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`);
|
|
406
443
|
console.error(`[Stoatx] All guards must implement guardFail() to handle failed checks.`);
|
|
407
444
|
process.exit(1);
|
|
408
445
|
}
|
|
409
446
|
}
|
|
410
447
|
}
|
|
411
448
|
/**
|
|
412
|
-
|
|
413
|
-
|
|
449
|
+
* Load commands from a single file
|
|
450
|
+
*/
|
|
414
451
|
async loadFile(filePath, baseDir) {
|
|
415
452
|
try {
|
|
416
453
|
const knownStoatClasses = new Set(decoratorStore.getStoatClasses().keys());
|
|
@@ -432,9 +469,7 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
432
469
|
const events = getEventsMetadata(stoatClass);
|
|
433
470
|
const category = this.getCategoryFromPath(filePath, baseDir);
|
|
434
471
|
if (simpleCommands.length === 0 && events.length === 0) {
|
|
435
|
-
console.warn(
|
|
436
|
-
`[Stoatx] Class ${stoatClass.name} is decorated with @Stoat but has no @SimpleCommand, @On or @Once methods. Skipping...`
|
|
437
|
-
);
|
|
472
|
+
console.warn(`[Stoatx] Class ${stoatClass.name} is decorated with @Stoat but has no @SimpleCommand, @On or @Once methods. Skipping...`);
|
|
438
473
|
this.processedStoatClasses.add(stoatClass);
|
|
439
474
|
return;
|
|
440
475
|
}
|
|
@@ -463,8 +498,8 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
463
498
|
this.processedStoatClasses.add(stoatClass);
|
|
464
499
|
}
|
|
465
500
|
/**
|
|
466
|
-
|
|
467
|
-
|
|
501
|
+
* Derive category from file path relative to base directory
|
|
502
|
+
*/
|
|
468
503
|
getCategoryFromPath(filePath, baseDir) {
|
|
469
504
|
const relative2 = path.relative(baseDir, filePath);
|
|
470
505
|
const parts = relative2.split(path.sep);
|
|
@@ -477,7 +512,24 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
477
512
|
|
|
478
513
|
// src/handler.ts
|
|
479
514
|
var import_reflect_metadata5 = require("reflect-metadata");
|
|
515
|
+
|
|
516
|
+
// src/error.ts
|
|
517
|
+
var CommandValidationError = class extends Error {
|
|
518
|
+
static {
|
|
519
|
+
__name(this, "CommandValidationError");
|
|
520
|
+
}
|
|
521
|
+
optionName;
|
|
522
|
+
constructor(optionName, message) {
|
|
523
|
+
super(message), this.optionName = optionName;
|
|
524
|
+
this.name = "CommandValidationError";
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
// src/handler.ts
|
|
480
529
|
var DefaultCooldownManager = class {
|
|
530
|
+
static {
|
|
531
|
+
__name(this, "DefaultCooldownManager");
|
|
532
|
+
}
|
|
481
533
|
cooldowns = /* @__PURE__ */ new Map();
|
|
482
534
|
check(ctx, metadata) {
|
|
483
535
|
if (metadata.cooldown <= 0) return true;
|
|
@@ -510,6 +562,9 @@ var DefaultCooldownManager = class {
|
|
|
510
562
|
}
|
|
511
563
|
};
|
|
512
564
|
var StoatxHandler = class {
|
|
565
|
+
static {
|
|
566
|
+
__name(this, "StoatxHandler");
|
|
567
|
+
}
|
|
513
568
|
commandsDir;
|
|
514
569
|
discoveryOptions;
|
|
515
570
|
prefixResolver;
|
|
@@ -518,6 +573,7 @@ var StoatxHandler = class {
|
|
|
518
573
|
cooldownManager;
|
|
519
574
|
disableMentionPrefix;
|
|
520
575
|
client;
|
|
576
|
+
flagPrefix;
|
|
521
577
|
constructor(options) {
|
|
522
578
|
this.client = options.client;
|
|
523
579
|
this.commandsDir = options.commandsDir;
|
|
@@ -527,10 +583,11 @@ var StoatxHandler = class {
|
|
|
527
583
|
this.registry = new CommandRegistry(options.extensions);
|
|
528
584
|
this.disableMentionPrefix = options.disableMentionPrefix ?? false;
|
|
529
585
|
this.cooldownManager = options.cooldownManager ?? new DefaultCooldownManager();
|
|
586
|
+
this.flagPrefix = options.flagPrefix || "-";
|
|
530
587
|
}
|
|
531
588
|
/**
|
|
532
|
-
|
|
533
|
-
|
|
589
|
+
* Initialize the handler - load all commands
|
|
590
|
+
*/
|
|
534
591
|
async init() {
|
|
535
592
|
if (this.commandsDir) {
|
|
536
593
|
await this.registry.loadFromDirectory(this.commandsDir);
|
|
@@ -540,21 +597,18 @@ var StoatxHandler = class {
|
|
|
540
597
|
this.attachEvents();
|
|
541
598
|
}
|
|
542
599
|
/**
|
|
543
|
-
|
|
544
|
-
|
|
600
|
+
* Attach registered events to the client
|
|
601
|
+
*/
|
|
545
602
|
attachEvents() {
|
|
546
603
|
const events = this.registry.getEvents();
|
|
547
604
|
for (const eventDef of events) {
|
|
548
|
-
const handler = async (...args) => {
|
|
605
|
+
const handler = /* @__PURE__ */ __name(async (...args) => {
|
|
549
606
|
try {
|
|
550
607
|
await eventDef.instance[eventDef.methodName](...args, this.client);
|
|
551
608
|
} catch (error) {
|
|
552
|
-
console.error(
|
|
553
|
-
`[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`,
|
|
554
|
-
error
|
|
555
|
-
);
|
|
609
|
+
console.error(`[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`, error);
|
|
556
610
|
}
|
|
557
|
-
};
|
|
611
|
+
}, "handler");
|
|
558
612
|
const eventName = eventDef.event;
|
|
559
613
|
if (eventDef.type === "once") {
|
|
560
614
|
this.client.once(eventName, handler);
|
|
@@ -564,8 +618,38 @@ var StoatxHandler = class {
|
|
|
564
618
|
}
|
|
565
619
|
}
|
|
566
620
|
/**
|
|
567
|
-
|
|
568
|
-
|
|
621
|
+
* Parses raw string arguments into positional args and key-value options
|
|
622
|
+
*/
|
|
623
|
+
parseCommandOptions(rawArgs) {
|
|
624
|
+
const args = [];
|
|
625
|
+
const options = {};
|
|
626
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
627
|
+
const arg = rawArgs[i];
|
|
628
|
+
if (arg === void 0) continue;
|
|
629
|
+
if (arg.startsWith(this.flagPrefix)) {
|
|
630
|
+
let key = arg;
|
|
631
|
+
while (key.startsWith(this.flagPrefix)) {
|
|
632
|
+
key = key.slice(this.flagPrefix.length);
|
|
633
|
+
}
|
|
634
|
+
const nextArg = rawArgs[i + 1];
|
|
635
|
+
if (nextArg !== void 0 && !nextArg.startsWith(this.flagPrefix)) {
|
|
636
|
+
options[key] = nextArg;
|
|
637
|
+
i++;
|
|
638
|
+
} else {
|
|
639
|
+
options[key] = true;
|
|
640
|
+
}
|
|
641
|
+
} else {
|
|
642
|
+
args.push(arg);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
args,
|
|
647
|
+
options
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Parse a raw message into command context
|
|
652
|
+
*/
|
|
569
653
|
async parseMessage(rawContent, message, meta) {
|
|
570
654
|
const prefix = await this.resolvePrefix(meta.serverId);
|
|
571
655
|
let usedPrefix = prefix;
|
|
@@ -588,10 +672,11 @@ var StoatxHandler = class {
|
|
|
588
672
|
if (!withoutPrefix) {
|
|
589
673
|
return null;
|
|
590
674
|
}
|
|
591
|
-
const [commandName, ...
|
|
675
|
+
const [commandName, ...rawArgs] = withoutPrefix.split(/\s+/);
|
|
592
676
|
if (!commandName) {
|
|
593
677
|
return null;
|
|
594
678
|
}
|
|
679
|
+
const { args, options } = this.parseCommandOptions(rawArgs);
|
|
595
680
|
return {
|
|
596
681
|
client: this.client,
|
|
597
682
|
content: rawContent,
|
|
@@ -599,6 +684,7 @@ var StoatxHandler = class {
|
|
|
599
684
|
channelId: meta.channelId,
|
|
600
685
|
serverId: meta.serverId,
|
|
601
686
|
args,
|
|
687
|
+
options,
|
|
602
688
|
prefix: usedPrefix,
|
|
603
689
|
commandName: commandName.toLowerCase(),
|
|
604
690
|
reply: meta.reply,
|
|
@@ -606,16 +692,16 @@ var StoatxHandler = class {
|
|
|
606
692
|
};
|
|
607
693
|
}
|
|
608
694
|
/**
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
+
*/
|
|
619
705
|
async handle(message) {
|
|
620
706
|
if (!message.channel || !message.author || !message.content) {
|
|
621
707
|
return false;
|
|
@@ -627,9 +713,9 @@ var StoatxHandler = class {
|
|
|
627
713
|
const authorId = message.author.id;
|
|
628
714
|
const channelId = message.channel.id;
|
|
629
715
|
const serverId = message.server?.id;
|
|
630
|
-
const reply = async (content) => {
|
|
716
|
+
const reply = /* @__PURE__ */ __name(async (content) => {
|
|
631
717
|
return await message.channel.send(content);
|
|
632
|
-
};
|
|
718
|
+
}, "reply");
|
|
633
719
|
await this.handleMessage(rawContent, message, {
|
|
634
720
|
authorId,
|
|
635
721
|
channelId,
|
|
@@ -639,21 +725,21 @@ var StoatxHandler = class {
|
|
|
639
725
|
return true;
|
|
640
726
|
}
|
|
641
727
|
/**
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
+
*/
|
|
657
743
|
async handleMessage(rawContent, message, meta) {
|
|
658
744
|
const ctx = await this.parseMessage(rawContent, message, meta);
|
|
659
745
|
if (!ctx) {
|
|
@@ -662,14 +748,15 @@ var StoatxHandler = class {
|
|
|
662
748
|
await this.execute(ctx);
|
|
663
749
|
}
|
|
664
750
|
/**
|
|
665
|
-
|
|
666
|
-
|
|
751
|
+
* Execute a command with the given context
|
|
752
|
+
*/
|
|
667
753
|
async execute(ctx) {
|
|
668
754
|
const registered = this.registry.get(ctx.commandName);
|
|
669
755
|
if (!registered) {
|
|
670
756
|
return false;
|
|
671
757
|
}
|
|
672
758
|
const { instance, metadata, methodName, classConstructor } = registered;
|
|
759
|
+
console.log(`[Debug] Metadata options for ${ctx.commandName}:`, metadata.options);
|
|
673
760
|
if (metadata.ownerOnly && !this.owners.has(ctx.authorId)) {
|
|
674
761
|
await ctx.reply("This command is owner-only.");
|
|
675
762
|
return false;
|
|
@@ -702,6 +789,190 @@ var StoatxHandler = class {
|
|
|
702
789
|
}
|
|
703
790
|
}
|
|
704
791
|
}
|
|
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
|
+
}
|
|
705
976
|
if (!await this.cooldownManager.check(ctx, metadata)) {
|
|
706
977
|
const remaining = await this.cooldownManager.getRemaining(ctx, metadata);
|
|
707
978
|
if (typeof instance.onCooldown === "function") {
|
|
@@ -727,26 +998,26 @@ var StoatxHandler = class {
|
|
|
727
998
|
}
|
|
728
999
|
}
|
|
729
1000
|
/**
|
|
730
|
-
|
|
731
|
-
|
|
1001
|
+
* Get the command registry
|
|
1002
|
+
*/
|
|
732
1003
|
getRegistry() {
|
|
733
1004
|
return this.registry;
|
|
734
1005
|
}
|
|
735
1006
|
/**
|
|
736
|
-
|
|
737
|
-
|
|
1007
|
+
* Get a command by name or alias
|
|
1008
|
+
*/
|
|
738
1009
|
getCommand(name) {
|
|
739
1010
|
return this.registry.get(name);
|
|
740
1011
|
}
|
|
741
1012
|
/**
|
|
742
|
-
|
|
743
|
-
|
|
1013
|
+
* Get all commands
|
|
1014
|
+
*/
|
|
744
1015
|
getCommands() {
|
|
745
1016
|
return this.registry.getAll();
|
|
746
1017
|
}
|
|
747
1018
|
/**
|
|
748
|
-
|
|
749
|
-
|
|
1019
|
+
* Reload all commands
|
|
1020
|
+
*/
|
|
750
1021
|
async reload() {
|
|
751
1022
|
this.registry.clear();
|
|
752
1023
|
if (this.cooldownManager.clear) {
|
|
@@ -759,29 +1030,31 @@ var StoatxHandler = class {
|
|
|
759
1030
|
await this.registry.autoDiscover(this.discoveryOptions);
|
|
760
1031
|
}
|
|
761
1032
|
/**
|
|
762
|
-
|
|
763
|
-
|
|
1033
|
+
* Check if a user is an owner
|
|
1034
|
+
*/
|
|
764
1035
|
isOwner(userId) {
|
|
765
1036
|
return this.owners.has(userId);
|
|
766
1037
|
}
|
|
767
1038
|
/**
|
|
768
|
-
|
|
769
|
-
|
|
1039
|
+
* Add an owner
|
|
1040
|
+
*/
|
|
770
1041
|
addOwner(userId) {
|
|
771
1042
|
this.owners.add(userId);
|
|
772
1043
|
}
|
|
773
1044
|
/**
|
|
774
|
-
|
|
775
|
-
|
|
1045
|
+
* Remove an owner
|
|
1046
|
+
*/
|
|
776
1047
|
removeOwner(userId) {
|
|
777
1048
|
this.owners.delete(userId);
|
|
778
1049
|
}
|
|
779
1050
|
/**
|
|
780
|
-
|
|
781
|
-
|
|
1051
|
+
* Resolve the prefix for a context
|
|
1052
|
+
*/
|
|
782
1053
|
async resolvePrefix(serverId) {
|
|
783
1054
|
if (typeof this.prefixResolver === "function") {
|
|
784
|
-
return this.prefixResolver({
|
|
1055
|
+
return this.prefixResolver({
|
|
1056
|
+
serverId
|
|
1057
|
+
});
|
|
785
1058
|
}
|
|
786
1059
|
return this.prefixResolver;
|
|
787
1060
|
}
|
|
@@ -790,16 +1063,23 @@ var StoatxHandler = class {
|
|
|
790
1063
|
// src/client.ts
|
|
791
1064
|
var import_client = require("@stoatx/client");
|
|
792
1065
|
var Client = class extends import_client.Client {
|
|
1066
|
+
static {
|
|
1067
|
+
__name(this, "Client");
|
|
1068
|
+
}
|
|
793
1069
|
handler;
|
|
794
1070
|
constructor(options) {
|
|
795
|
-
super();
|
|
796
|
-
this.handler = new StoatxHandler({
|
|
797
|
-
|
|
798
|
-
|
|
1071
|
+
super(options);
|
|
1072
|
+
this.handler = new StoatxHandler({
|
|
1073
|
+
...options,
|
|
1074
|
+
client: this
|
|
799
1075
|
});
|
|
800
1076
|
}
|
|
801
|
-
async
|
|
1077
|
+
async login(token) {
|
|
802
1078
|
await this.handler.init();
|
|
1079
|
+
return super.login(token);
|
|
1080
|
+
}
|
|
1081
|
+
async executeCommand(message) {
|
|
1082
|
+
await this.handler.handle(message);
|
|
803
1083
|
}
|
|
804
1084
|
};
|
|
805
1085
|
|
|
@@ -809,6 +1089,7 @@ __reExport(index_exports, require("@stoatx/client"), module.exports);
|
|
|
809
1089
|
0 && (module.exports = {
|
|
810
1090
|
Client,
|
|
811
1091
|
CommandRegistry,
|
|
1092
|
+
CommandValidationError,
|
|
812
1093
|
DefaultCooldownManager,
|
|
813
1094
|
Guard,
|
|
814
1095
|
METADATA_KEYS,
|