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.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
1
4
|
// src/decorators/Stoat.ts
|
|
2
5
|
import "reflect-metadata";
|
|
3
6
|
|
|
@@ -11,6 +14,9 @@ var METADATA_KEYS = {
|
|
|
11
14
|
|
|
12
15
|
// src/decorators/store.ts
|
|
13
16
|
var DecoratorStore = class _DecoratorStore {
|
|
17
|
+
static {
|
|
18
|
+
__name(this, "DecoratorStore");
|
|
19
|
+
}
|
|
14
20
|
static instance;
|
|
15
21
|
/** Stoat classes with their SimpleCommand methods */
|
|
16
22
|
stoatClasses = /* @__PURE__ */ new Map();
|
|
@@ -27,8 +33,8 @@ var DecoratorStore = class _DecoratorStore {
|
|
|
27
33
|
return _DecoratorStore.instance;
|
|
28
34
|
}
|
|
29
35
|
/**
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
* Register a @Stoat decorated class
|
|
37
|
+
*/
|
|
32
38
|
registerStoatClass(classConstructor) {
|
|
33
39
|
if (!this.stoatClasses.has(classConstructor)) {
|
|
34
40
|
const instance = new classConstructor();
|
|
@@ -36,40 +42,40 @@ var DecoratorStore = class _DecoratorStore {
|
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
/**
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
* Get all registered Stoat classes with their instances
|
|
46
|
+
*/
|
|
41
47
|
getStoatClasses() {
|
|
42
48
|
return this.stoatClasses;
|
|
43
49
|
}
|
|
44
50
|
/**
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
* Add a registered command
|
|
52
|
+
*/
|
|
47
53
|
addCommand(command) {
|
|
48
54
|
this.commands.push(command);
|
|
49
55
|
}
|
|
50
56
|
/**
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
* Get all registered commands
|
|
58
|
+
*/
|
|
53
59
|
getCommands() {
|
|
54
60
|
return this.commands;
|
|
55
61
|
}
|
|
56
62
|
/**
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
* Clear all registered classes (useful for testing)
|
|
64
|
+
*/
|
|
59
65
|
clear() {
|
|
60
66
|
this.stoatClasses.clear();
|
|
61
67
|
this.commands = [];
|
|
62
68
|
this.initialized = false;
|
|
63
69
|
}
|
|
64
70
|
/**
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
* Mark as initialized
|
|
72
|
+
*/
|
|
67
73
|
markInitialized() {
|
|
68
74
|
this.initialized = true;
|
|
69
75
|
}
|
|
70
76
|
/**
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
* Check if initialized
|
|
78
|
+
*/
|
|
73
79
|
isInitialized() {
|
|
74
80
|
return this.initialized;
|
|
75
81
|
}
|
|
@@ -83,9 +89,11 @@ function Stoat() {
|
|
|
83
89
|
decoratorStore.registerStoatClass(target);
|
|
84
90
|
};
|
|
85
91
|
}
|
|
92
|
+
__name(Stoat, "Stoat");
|
|
86
93
|
function isStoatClass(target) {
|
|
87
94
|
return Reflect.getMetadata(METADATA_KEYS.IS_STOAT_CLASS, target) === true;
|
|
88
95
|
}
|
|
96
|
+
__name(isStoatClass, "isStoatClass");
|
|
89
97
|
|
|
90
98
|
// src/decorators/SimpleCommand.ts
|
|
91
99
|
import "reflect-metadata";
|
|
@@ -101,9 +109,11 @@ function SimpleCommand(options = {}) {
|
|
|
101
109
|
return descriptor;
|
|
102
110
|
};
|
|
103
111
|
}
|
|
112
|
+
__name(SimpleCommand, "SimpleCommand");
|
|
104
113
|
function getSimpleCommands(target) {
|
|
105
114
|
return Reflect.getMetadata(METADATA_KEYS.SIMPLE_COMMANDS, target) || [];
|
|
106
115
|
}
|
|
116
|
+
__name(getSimpleCommands, "getSimpleCommands");
|
|
107
117
|
|
|
108
118
|
// src/decorators/Guard.ts
|
|
109
119
|
import "reflect-metadata";
|
|
@@ -114,9 +124,11 @@ function Guard(guardClass) {
|
|
|
114
124
|
Reflect.defineMetadata(METADATA_KEYS.GUARDS, existingGuards, target);
|
|
115
125
|
};
|
|
116
126
|
}
|
|
127
|
+
__name(Guard, "Guard");
|
|
117
128
|
function getGuards(target) {
|
|
118
129
|
return Reflect.getMetadata(METADATA_KEYS.GUARDS, target) || [];
|
|
119
130
|
}
|
|
131
|
+
__name(getGuards, "getGuards");
|
|
120
132
|
|
|
121
133
|
// src/decorators/Events.ts
|
|
122
134
|
import "reflect-metadata";
|
|
@@ -133,15 +145,19 @@ function createEventDecorator(event, type) {
|
|
|
133
145
|
return descriptor;
|
|
134
146
|
};
|
|
135
147
|
}
|
|
148
|
+
__name(createEventDecorator, "createEventDecorator");
|
|
136
149
|
function On(event) {
|
|
137
150
|
return createEventDecorator(event, "on");
|
|
138
151
|
}
|
|
152
|
+
__name(On, "On");
|
|
139
153
|
function Once(event) {
|
|
140
154
|
return createEventDecorator(event, "once");
|
|
141
155
|
}
|
|
156
|
+
__name(Once, "Once");
|
|
142
157
|
function getEventsMetadata(target) {
|
|
143
158
|
return Reflect.getMetadata(METADATA_KEYS.EVENTS, target) || [];
|
|
144
159
|
}
|
|
160
|
+
__name(getEventsMetadata, "getEventsMetadata");
|
|
145
161
|
|
|
146
162
|
// src/decorators/utils.ts
|
|
147
163
|
function buildSimpleCommandMetadata(options, methodName, category) {
|
|
@@ -152,11 +168,16 @@ function buildSimpleCommandMetadata(options, methodName, category) {
|
|
|
152
168
|
permissions: options.permissions ?? [],
|
|
153
169
|
category: options.category ?? category ?? "uncategorized",
|
|
154
170
|
cooldown: options.cooldown ?? 0,
|
|
155
|
-
...options.cooldownStorage !== void 0 ? {
|
|
171
|
+
...options.cooldownStorage !== void 0 ? {
|
|
172
|
+
cooldownStorage: options.cooldownStorage
|
|
173
|
+
} : {},
|
|
156
174
|
nsfw: options.nsfw ?? false,
|
|
157
|
-
ownerOnly: options.ownerOnly ?? false
|
|
175
|
+
ownerOnly: options.ownerOnly ?? false,
|
|
176
|
+
options: options.options ?? [],
|
|
177
|
+
args: options.args ?? []
|
|
158
178
|
};
|
|
159
179
|
}
|
|
180
|
+
__name(buildSimpleCommandMetadata, "buildSimpleCommandMetadata");
|
|
160
181
|
|
|
161
182
|
// src/registry.ts
|
|
162
183
|
import * as path from "path";
|
|
@@ -164,6 +185,9 @@ import * as fs from "fs/promises";
|
|
|
164
185
|
import { pathToFileURL } from "url";
|
|
165
186
|
import { glob } from "tinyglobby";
|
|
166
187
|
var CommandRegistry = class _CommandRegistry {
|
|
188
|
+
static {
|
|
189
|
+
__name(this, "CommandRegistry");
|
|
190
|
+
}
|
|
167
191
|
static DEFAULT_AUTO_DISCOVERY_IGNORES = [
|
|
168
192
|
"**/node_modules/**",
|
|
169
193
|
"**/.git/**",
|
|
@@ -176,23 +200,31 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
176
200
|
registeredEvents = [];
|
|
177
201
|
extensions;
|
|
178
202
|
processedStoatClasses = /* @__PURE__ */ new Set();
|
|
179
|
-
constructor(extensions = [
|
|
203
|
+
constructor(extensions = [
|
|
204
|
+
".js",
|
|
205
|
+
".mjs",
|
|
206
|
+
".cjs"
|
|
207
|
+
]) {
|
|
180
208
|
this.extensions = extensions;
|
|
181
209
|
}
|
|
182
210
|
/**
|
|
183
|
-
|
|
184
|
-
|
|
211
|
+
* Get the number of registered commands
|
|
212
|
+
*/
|
|
185
213
|
get size() {
|
|
186
214
|
return this.commands.size;
|
|
187
215
|
}
|
|
188
216
|
/**
|
|
189
|
-
|
|
190
|
-
|
|
217
|
+
* Load commands from a directory using glob pattern matching
|
|
218
|
+
*/
|
|
191
219
|
async loadFromDirectory(directory) {
|
|
192
220
|
const patterns = this.extensions.map((ext) => path.join(directory, "**", `*${ext}`).replace(/\\/g, "/"));
|
|
193
221
|
for (const pattern of patterns) {
|
|
194
222
|
const files = await glob(pattern, {
|
|
195
|
-
ignore: [
|
|
223
|
+
ignore: [
|
|
224
|
+
"**/*.d.ts",
|
|
225
|
+
"**/*.test.ts",
|
|
226
|
+
"**/*.spec.ts"
|
|
227
|
+
],
|
|
196
228
|
absolute: true
|
|
197
229
|
});
|
|
198
230
|
for (const file of files) {
|
|
@@ -202,19 +234,24 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
202
234
|
console.log(`[Stoatx] Loaded ${this.commands.size} command(s) and ${this.registeredEvents.length} event(s)`);
|
|
203
235
|
}
|
|
204
236
|
/**
|
|
205
|
-
|
|
206
|
-
|
|
237
|
+
* Auto-discover command files across one or more roots.
|
|
238
|
+
*/
|
|
207
239
|
async autoDiscover(options = {}) {
|
|
208
|
-
const roots = options.roots?.length ? options.roots : [
|
|
240
|
+
const roots = options.roots?.length ? options.roots : [
|
|
241
|
+
process.cwd()
|
|
242
|
+
];
|
|
209
243
|
const includePatterns = options.include?.length ? options.include : this.getDefaultAutoDiscoveryPatterns();
|
|
210
|
-
const patterns = roots.flatMap(
|
|
211
|
-
(root) => includePatterns.map((pattern) => path.join(root, pattern).replace(/\\/g, "/"))
|
|
212
|
-
);
|
|
244
|
+
const patterns = roots.flatMap((root) => includePatterns.map((pattern) => path.join(root, pattern).replace(/\\/g, "/")));
|
|
213
245
|
const files = await glob(patterns, {
|
|
214
|
-
ignore: [
|
|
246
|
+
ignore: [
|
|
247
|
+
..._CommandRegistry.DEFAULT_AUTO_DISCOVERY_IGNORES,
|
|
248
|
+
...options.ignore ?? []
|
|
249
|
+
],
|
|
215
250
|
absolute: true
|
|
216
251
|
});
|
|
217
|
-
const uniqueFiles = [
|
|
252
|
+
const uniqueFiles = [
|
|
253
|
+
...new Set(files)
|
|
254
|
+
];
|
|
218
255
|
let candidateFiles = 0;
|
|
219
256
|
for (const file of uniqueFiles) {
|
|
220
257
|
if (!await this.isLikelyCommandModule(file)) {
|
|
@@ -241,8 +278,8 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
241
278
|
}
|
|
242
279
|
}
|
|
243
280
|
/**
|
|
244
|
-
|
|
245
|
-
|
|
281
|
+
* Register a command instance
|
|
282
|
+
*/
|
|
246
283
|
register(instance, metadata, classConstructor, methodName) {
|
|
247
284
|
const name = metadata.name.toLowerCase();
|
|
248
285
|
if (this.commands.has(name)) {
|
|
@@ -250,7 +287,12 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
250
287
|
return;
|
|
251
288
|
}
|
|
252
289
|
this.validateGuards(classConstructor, metadata.name);
|
|
253
|
-
this.commands.set(name, {
|
|
290
|
+
this.commands.set(name, {
|
|
291
|
+
instance,
|
|
292
|
+
metadata,
|
|
293
|
+
methodName,
|
|
294
|
+
classConstructor
|
|
295
|
+
});
|
|
254
296
|
for (const alias of metadata.aliases) {
|
|
255
297
|
const aliasLower = alias.toLowerCase();
|
|
256
298
|
if (this.aliases.has(aliasLower) || this.commands.has(aliasLower)) {
|
|
@@ -261,41 +303,41 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
261
303
|
}
|
|
262
304
|
}
|
|
263
305
|
/**
|
|
264
|
-
|
|
265
|
-
|
|
306
|
+
* Get a command by name or alias
|
|
307
|
+
*/
|
|
266
308
|
get(name) {
|
|
267
309
|
const lowerName = name.toLowerCase();
|
|
268
310
|
const resolvedName = this.aliases.get(lowerName) ?? lowerName;
|
|
269
311
|
return this.commands.get(resolvedName);
|
|
270
312
|
}
|
|
271
313
|
/**
|
|
272
|
-
|
|
273
|
-
|
|
314
|
+
* Check if a command exists
|
|
315
|
+
*/
|
|
274
316
|
has(name) {
|
|
275
317
|
const lowerName = name.toLowerCase();
|
|
276
318
|
return this.commands.has(lowerName) || this.aliases.has(lowerName);
|
|
277
319
|
}
|
|
278
320
|
/**
|
|
279
|
-
|
|
280
|
-
|
|
321
|
+
* Get all registered commands
|
|
322
|
+
*/
|
|
281
323
|
getAll() {
|
|
282
324
|
return Array.from(this.commands.values());
|
|
283
325
|
}
|
|
284
326
|
/**
|
|
285
|
-
|
|
286
|
-
|
|
327
|
+
* Get all command metadata
|
|
328
|
+
*/
|
|
287
329
|
getAllMetadata() {
|
|
288
330
|
return this.getAll().map((c) => c.metadata);
|
|
289
331
|
}
|
|
290
332
|
/**
|
|
291
|
-
|
|
292
|
-
|
|
333
|
+
* Get all registered events
|
|
334
|
+
*/
|
|
293
335
|
getEvents() {
|
|
294
336
|
return this.registeredEvents;
|
|
295
337
|
}
|
|
296
338
|
/**
|
|
297
|
-
|
|
298
|
-
|
|
339
|
+
* Get commands grouped by category
|
|
340
|
+
*/
|
|
299
341
|
getByCategory() {
|
|
300
342
|
const categories = /* @__PURE__ */ new Map();
|
|
301
343
|
for (const cmd of this.commands.values()) {
|
|
@@ -307,8 +349,8 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
307
349
|
return categories;
|
|
308
350
|
}
|
|
309
351
|
/**
|
|
310
|
-
|
|
311
|
-
|
|
352
|
+
* Clear all commands
|
|
353
|
+
*/
|
|
312
354
|
clear() {
|
|
313
355
|
this.commands.clear();
|
|
314
356
|
this.aliases.clear();
|
|
@@ -316,51 +358,47 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
316
358
|
this.processedStoatClasses.clear();
|
|
317
359
|
}
|
|
318
360
|
/**
|
|
319
|
-
|
|
320
|
-
|
|
361
|
+
* Iterate over commands
|
|
362
|
+
*/
|
|
321
363
|
[Symbol.iterator]() {
|
|
322
364
|
return this.commands.entries();
|
|
323
365
|
}
|
|
324
366
|
/**
|
|
325
|
-
|
|
326
|
-
|
|
367
|
+
* Iterate over command values
|
|
368
|
+
*/
|
|
327
369
|
values() {
|
|
328
370
|
return this.commands.values();
|
|
329
371
|
}
|
|
330
372
|
/**
|
|
331
|
-
|
|
332
|
-
|
|
373
|
+
* Iterate over command names
|
|
374
|
+
*/
|
|
333
375
|
keys() {
|
|
334
376
|
return this.commands.keys();
|
|
335
377
|
}
|
|
336
378
|
/**
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
379
|
+
* Validate that all guards on a command implement the required methods
|
|
380
|
+
* @param commandClass
|
|
381
|
+
* @param commandName
|
|
382
|
+
* @private
|
|
383
|
+
*/
|
|
342
384
|
validateGuards(commandClass, commandName) {
|
|
343
385
|
const guards = Reflect.getMetadata("stoatx:command:guards", commandClass) || [];
|
|
344
386
|
for (const GuardClass of guards) {
|
|
345
387
|
const guardInstance = new GuardClass();
|
|
346
388
|
if (typeof guardInstance.run !== "function") {
|
|
347
|
-
console.error(
|
|
348
|
-
`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`
|
|
349
|
-
);
|
|
389
|
+
console.error(`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`);
|
|
350
390
|
process.exit(1);
|
|
351
391
|
}
|
|
352
392
|
if (typeof guardInstance.guardFail !== "function") {
|
|
353
|
-
console.error(
|
|
354
|
-
`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`
|
|
355
|
-
);
|
|
393
|
+
console.error(`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`);
|
|
356
394
|
console.error(`[Stoatx] All guards must implement guardFail() to handle failed checks.`);
|
|
357
395
|
process.exit(1);
|
|
358
396
|
}
|
|
359
397
|
}
|
|
360
398
|
}
|
|
361
399
|
/**
|
|
362
|
-
|
|
363
|
-
|
|
400
|
+
* Load commands from a single file
|
|
401
|
+
*/
|
|
364
402
|
async loadFile(filePath, baseDir) {
|
|
365
403
|
try {
|
|
366
404
|
const knownStoatClasses = new Set(decoratorStore.getStoatClasses().keys());
|
|
@@ -382,9 +420,7 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
382
420
|
const events = getEventsMetadata(stoatClass);
|
|
383
421
|
const category = this.getCategoryFromPath(filePath, baseDir);
|
|
384
422
|
if (simpleCommands.length === 0 && events.length === 0) {
|
|
385
|
-
console.warn(
|
|
386
|
-
`[Stoatx] Class ${stoatClass.name} is decorated with @Stoat but has no @SimpleCommand, @On or @Once methods. Skipping...`
|
|
387
|
-
);
|
|
423
|
+
console.warn(`[Stoatx] Class ${stoatClass.name} is decorated with @Stoat but has no @SimpleCommand, @On or @Once methods. Skipping...`);
|
|
388
424
|
this.processedStoatClasses.add(stoatClass);
|
|
389
425
|
return;
|
|
390
426
|
}
|
|
@@ -413,8 +449,8 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
413
449
|
this.processedStoatClasses.add(stoatClass);
|
|
414
450
|
}
|
|
415
451
|
/**
|
|
416
|
-
|
|
417
|
-
|
|
452
|
+
* Derive category from file path relative to base directory
|
|
453
|
+
*/
|
|
418
454
|
getCategoryFromPath(filePath, baseDir) {
|
|
419
455
|
const relative2 = path.relative(baseDir, filePath);
|
|
420
456
|
const parts = relative2.split(path.sep);
|
|
@@ -427,7 +463,24 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
427
463
|
|
|
428
464
|
// src/handler.ts
|
|
429
465
|
import "reflect-metadata";
|
|
466
|
+
|
|
467
|
+
// src/error.ts
|
|
468
|
+
var CommandValidationError = class extends Error {
|
|
469
|
+
static {
|
|
470
|
+
__name(this, "CommandValidationError");
|
|
471
|
+
}
|
|
472
|
+
optionName;
|
|
473
|
+
constructor(optionName, message) {
|
|
474
|
+
super(message), this.optionName = optionName;
|
|
475
|
+
this.name = "CommandValidationError";
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
// src/handler.ts
|
|
430
480
|
var DefaultCooldownManager = class {
|
|
481
|
+
static {
|
|
482
|
+
__name(this, "DefaultCooldownManager");
|
|
483
|
+
}
|
|
431
484
|
cooldowns = /* @__PURE__ */ new Map();
|
|
432
485
|
check(ctx, metadata) {
|
|
433
486
|
if (metadata.cooldown <= 0) return true;
|
|
@@ -460,6 +513,9 @@ var DefaultCooldownManager = class {
|
|
|
460
513
|
}
|
|
461
514
|
};
|
|
462
515
|
var StoatxHandler = class {
|
|
516
|
+
static {
|
|
517
|
+
__name(this, "StoatxHandler");
|
|
518
|
+
}
|
|
463
519
|
commandsDir;
|
|
464
520
|
discoveryOptions;
|
|
465
521
|
prefixResolver;
|
|
@@ -468,6 +524,7 @@ var StoatxHandler = class {
|
|
|
468
524
|
cooldownManager;
|
|
469
525
|
disableMentionPrefix;
|
|
470
526
|
client;
|
|
527
|
+
flagPrefix;
|
|
471
528
|
constructor(options) {
|
|
472
529
|
this.client = options.client;
|
|
473
530
|
this.commandsDir = options.commandsDir;
|
|
@@ -477,10 +534,11 @@ var StoatxHandler = class {
|
|
|
477
534
|
this.registry = new CommandRegistry(options.extensions);
|
|
478
535
|
this.disableMentionPrefix = options.disableMentionPrefix ?? false;
|
|
479
536
|
this.cooldownManager = options.cooldownManager ?? new DefaultCooldownManager();
|
|
537
|
+
this.flagPrefix = options.flagPrefix || "-";
|
|
480
538
|
}
|
|
481
539
|
/**
|
|
482
|
-
|
|
483
|
-
|
|
540
|
+
* Initialize the handler - load all commands
|
|
541
|
+
*/
|
|
484
542
|
async init() {
|
|
485
543
|
if (this.commandsDir) {
|
|
486
544
|
await this.registry.loadFromDirectory(this.commandsDir);
|
|
@@ -490,21 +548,18 @@ var StoatxHandler = class {
|
|
|
490
548
|
this.attachEvents();
|
|
491
549
|
}
|
|
492
550
|
/**
|
|
493
|
-
|
|
494
|
-
|
|
551
|
+
* Attach registered events to the client
|
|
552
|
+
*/
|
|
495
553
|
attachEvents() {
|
|
496
554
|
const events = this.registry.getEvents();
|
|
497
555
|
for (const eventDef of events) {
|
|
498
|
-
const handler = async (...args) => {
|
|
556
|
+
const handler = /* @__PURE__ */ __name(async (...args) => {
|
|
499
557
|
try {
|
|
500
558
|
await eventDef.instance[eventDef.methodName](...args, this.client);
|
|
501
559
|
} catch (error) {
|
|
502
|
-
console.error(
|
|
503
|
-
`[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`,
|
|
504
|
-
error
|
|
505
|
-
);
|
|
560
|
+
console.error(`[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`, error);
|
|
506
561
|
}
|
|
507
|
-
};
|
|
562
|
+
}, "handler");
|
|
508
563
|
const eventName = eventDef.event;
|
|
509
564
|
if (eventDef.type === "once") {
|
|
510
565
|
this.client.once(eventName, handler);
|
|
@@ -514,8 +569,38 @@ var StoatxHandler = class {
|
|
|
514
569
|
}
|
|
515
570
|
}
|
|
516
571
|
/**
|
|
517
|
-
|
|
518
|
-
|
|
572
|
+
* Parses raw string arguments into positional args and key-value options
|
|
573
|
+
*/
|
|
574
|
+
parseCommandOptions(rawArgs) {
|
|
575
|
+
const args = [];
|
|
576
|
+
const options = {};
|
|
577
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
578
|
+
const arg = rawArgs[i];
|
|
579
|
+
if (arg === void 0) continue;
|
|
580
|
+
if (arg.startsWith(this.flagPrefix)) {
|
|
581
|
+
let key = arg;
|
|
582
|
+
while (key.startsWith(this.flagPrefix)) {
|
|
583
|
+
key = key.slice(this.flagPrefix.length);
|
|
584
|
+
}
|
|
585
|
+
const nextArg = rawArgs[i + 1];
|
|
586
|
+
if (nextArg !== void 0 && !nextArg.startsWith(this.flagPrefix)) {
|
|
587
|
+
options[key] = nextArg;
|
|
588
|
+
i++;
|
|
589
|
+
} else {
|
|
590
|
+
options[key] = true;
|
|
591
|
+
}
|
|
592
|
+
} else {
|
|
593
|
+
args.push(arg);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
args,
|
|
598
|
+
options
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Parse a raw message into command context
|
|
603
|
+
*/
|
|
519
604
|
async parseMessage(rawContent, message, meta) {
|
|
520
605
|
const prefix = await this.resolvePrefix(meta.serverId);
|
|
521
606
|
let usedPrefix = prefix;
|
|
@@ -538,10 +623,11 @@ var StoatxHandler = class {
|
|
|
538
623
|
if (!withoutPrefix) {
|
|
539
624
|
return null;
|
|
540
625
|
}
|
|
541
|
-
const [commandName, ...
|
|
626
|
+
const [commandName, ...rawArgs] = withoutPrefix.split(/\s+/);
|
|
542
627
|
if (!commandName) {
|
|
543
628
|
return null;
|
|
544
629
|
}
|
|
630
|
+
const { args, options } = this.parseCommandOptions(rawArgs);
|
|
545
631
|
return {
|
|
546
632
|
client: this.client,
|
|
547
633
|
content: rawContent,
|
|
@@ -549,6 +635,7 @@ var StoatxHandler = class {
|
|
|
549
635
|
channelId: meta.channelId,
|
|
550
636
|
serverId: meta.serverId,
|
|
551
637
|
args,
|
|
638
|
+
options,
|
|
552
639
|
prefix: usedPrefix,
|
|
553
640
|
commandName: commandName.toLowerCase(),
|
|
554
641
|
reply: meta.reply,
|
|
@@ -556,16 +643,16 @@ var StoatxHandler = class {
|
|
|
556
643
|
};
|
|
557
644
|
}
|
|
558
645
|
/**
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
+
*/
|
|
569
656
|
async handle(message) {
|
|
570
657
|
if (!message.channel || !message.author || !message.content) {
|
|
571
658
|
return false;
|
|
@@ -577,9 +664,9 @@ var StoatxHandler = class {
|
|
|
577
664
|
const authorId = message.author.id;
|
|
578
665
|
const channelId = message.channel.id;
|
|
579
666
|
const serverId = message.server?.id;
|
|
580
|
-
const reply = async (content) => {
|
|
667
|
+
const reply = /* @__PURE__ */ __name(async (content) => {
|
|
581
668
|
return await message.channel.send(content);
|
|
582
|
-
};
|
|
669
|
+
}, "reply");
|
|
583
670
|
await this.handleMessage(rawContent, message, {
|
|
584
671
|
authorId,
|
|
585
672
|
channelId,
|
|
@@ -589,21 +676,21 @@ var StoatxHandler = class {
|
|
|
589
676
|
return true;
|
|
590
677
|
}
|
|
591
678
|
/**
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
+
*/
|
|
607
694
|
async handleMessage(rawContent, message, meta) {
|
|
608
695
|
const ctx = await this.parseMessage(rawContent, message, meta);
|
|
609
696
|
if (!ctx) {
|
|
@@ -612,14 +699,15 @@ var StoatxHandler = class {
|
|
|
612
699
|
await this.execute(ctx);
|
|
613
700
|
}
|
|
614
701
|
/**
|
|
615
|
-
|
|
616
|
-
|
|
702
|
+
* Execute a command with the given context
|
|
703
|
+
*/
|
|
617
704
|
async execute(ctx) {
|
|
618
705
|
const registered = this.registry.get(ctx.commandName);
|
|
619
706
|
if (!registered) {
|
|
620
707
|
return false;
|
|
621
708
|
}
|
|
622
709
|
const { instance, metadata, methodName, classConstructor } = registered;
|
|
710
|
+
console.log(`[Debug] Metadata options for ${ctx.commandName}:`, metadata.options);
|
|
623
711
|
if (metadata.ownerOnly && !this.owners.has(ctx.authorId)) {
|
|
624
712
|
await ctx.reply("This command is owner-only.");
|
|
625
713
|
return false;
|
|
@@ -652,6 +740,190 @@ var StoatxHandler = class {
|
|
|
652
740
|
}
|
|
653
741
|
}
|
|
654
742
|
}
|
|
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
|
+
}
|
|
655
927
|
if (!await this.cooldownManager.check(ctx, metadata)) {
|
|
656
928
|
const remaining = await this.cooldownManager.getRemaining(ctx, metadata);
|
|
657
929
|
if (typeof instance.onCooldown === "function") {
|
|
@@ -677,26 +949,26 @@ var StoatxHandler = class {
|
|
|
677
949
|
}
|
|
678
950
|
}
|
|
679
951
|
/**
|
|
680
|
-
|
|
681
|
-
|
|
952
|
+
* Get the command registry
|
|
953
|
+
*/
|
|
682
954
|
getRegistry() {
|
|
683
955
|
return this.registry;
|
|
684
956
|
}
|
|
685
957
|
/**
|
|
686
|
-
|
|
687
|
-
|
|
958
|
+
* Get a command by name or alias
|
|
959
|
+
*/
|
|
688
960
|
getCommand(name) {
|
|
689
961
|
return this.registry.get(name);
|
|
690
962
|
}
|
|
691
963
|
/**
|
|
692
|
-
|
|
693
|
-
|
|
964
|
+
* Get all commands
|
|
965
|
+
*/
|
|
694
966
|
getCommands() {
|
|
695
967
|
return this.registry.getAll();
|
|
696
968
|
}
|
|
697
969
|
/**
|
|
698
|
-
|
|
699
|
-
|
|
970
|
+
* Reload all commands
|
|
971
|
+
*/
|
|
700
972
|
async reload() {
|
|
701
973
|
this.registry.clear();
|
|
702
974
|
if (this.cooldownManager.clear) {
|
|
@@ -709,29 +981,31 @@ var StoatxHandler = class {
|
|
|
709
981
|
await this.registry.autoDiscover(this.discoveryOptions);
|
|
710
982
|
}
|
|
711
983
|
/**
|
|
712
|
-
|
|
713
|
-
|
|
984
|
+
* Check if a user is an owner
|
|
985
|
+
*/
|
|
714
986
|
isOwner(userId) {
|
|
715
987
|
return this.owners.has(userId);
|
|
716
988
|
}
|
|
717
989
|
/**
|
|
718
|
-
|
|
719
|
-
|
|
990
|
+
* Add an owner
|
|
991
|
+
*/
|
|
720
992
|
addOwner(userId) {
|
|
721
993
|
this.owners.add(userId);
|
|
722
994
|
}
|
|
723
995
|
/**
|
|
724
|
-
|
|
725
|
-
|
|
996
|
+
* Remove an owner
|
|
997
|
+
*/
|
|
726
998
|
removeOwner(userId) {
|
|
727
999
|
this.owners.delete(userId);
|
|
728
1000
|
}
|
|
729
1001
|
/**
|
|
730
|
-
|
|
731
|
-
|
|
1002
|
+
* Resolve the prefix for a context
|
|
1003
|
+
*/
|
|
732
1004
|
async resolvePrefix(serverId) {
|
|
733
1005
|
if (typeof this.prefixResolver === "function") {
|
|
734
|
-
return this.prefixResolver({
|
|
1006
|
+
return this.prefixResolver({
|
|
1007
|
+
serverId
|
|
1008
|
+
});
|
|
735
1009
|
}
|
|
736
1010
|
return this.prefixResolver;
|
|
737
1011
|
}
|
|
@@ -740,16 +1014,23 @@ var StoatxHandler = class {
|
|
|
740
1014
|
// src/client.ts
|
|
741
1015
|
import { Client as StoatClient } from "@stoatx/client";
|
|
742
1016
|
var Client = class extends StoatClient {
|
|
1017
|
+
static {
|
|
1018
|
+
__name(this, "Client");
|
|
1019
|
+
}
|
|
743
1020
|
handler;
|
|
744
1021
|
constructor(options) {
|
|
745
|
-
super();
|
|
746
|
-
this.handler = new StoatxHandler({
|
|
747
|
-
|
|
748
|
-
|
|
1022
|
+
super(options);
|
|
1023
|
+
this.handler = new StoatxHandler({
|
|
1024
|
+
...options,
|
|
1025
|
+
client: this
|
|
749
1026
|
});
|
|
750
1027
|
}
|
|
751
|
-
async
|
|
1028
|
+
async login(token) {
|
|
752
1029
|
await this.handler.init();
|
|
1030
|
+
return super.login(token);
|
|
1031
|
+
}
|
|
1032
|
+
async executeCommand(message) {
|
|
1033
|
+
await this.handler.handle(message);
|
|
753
1034
|
}
|
|
754
1035
|
};
|
|
755
1036
|
|
|
@@ -758,6 +1039,7 @@ export * from "@stoatx/client";
|
|
|
758
1039
|
export {
|
|
759
1040
|
Client,
|
|
760
1041
|
CommandRegistry,
|
|
1042
|
+
CommandValidationError,
|
|
761
1043
|
DefaultCooldownManager,
|
|
762
1044
|
Guard,
|
|
763
1045
|
METADATA_KEYS,
|