stoatx 0.7.6 → 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 +367 -335
- package/dist/index.d.ts +367 -335
- package/dist/index.js +427 -147
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +428 -147
- 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,8 +463,24 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
427
463
|
|
|
428
464
|
// src/handler.ts
|
|
429
465
|
import "reflect-metadata";
|
|
430
|
-
|
|
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
|
|
431
480
|
var DefaultCooldownManager = class {
|
|
481
|
+
static {
|
|
482
|
+
__name(this, "DefaultCooldownManager");
|
|
483
|
+
}
|
|
432
484
|
cooldowns = /* @__PURE__ */ new Map();
|
|
433
485
|
check(ctx, metadata) {
|
|
434
486
|
if (metadata.cooldown <= 0) return true;
|
|
@@ -460,23 +512,10 @@ var DefaultCooldownManager = class {
|
|
|
460
512
|
this.cooldowns.clear();
|
|
461
513
|
}
|
|
462
514
|
};
|
|
463
|
-
var Client = class extends StoatClient {
|
|
464
|
-
handler;
|
|
465
|
-
constructor(options) {
|
|
466
|
-
super();
|
|
467
|
-
this.handler = new StoatxHandler({ ...options, client: this });
|
|
468
|
-
this.on("messageCreate", async (message) => {
|
|
469
|
-
await this.handler.handle(message);
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
/**
|
|
473
|
-
* Initialize the StoatxHandler commands
|
|
474
|
-
*/
|
|
475
|
-
async initCommands() {
|
|
476
|
-
await this.handler.init();
|
|
477
|
-
}
|
|
478
|
-
};
|
|
479
515
|
var StoatxHandler = class {
|
|
516
|
+
static {
|
|
517
|
+
__name(this, "StoatxHandler");
|
|
518
|
+
}
|
|
480
519
|
commandsDir;
|
|
481
520
|
discoveryOptions;
|
|
482
521
|
prefixResolver;
|
|
@@ -485,6 +524,7 @@ var StoatxHandler = class {
|
|
|
485
524
|
cooldownManager;
|
|
486
525
|
disableMentionPrefix;
|
|
487
526
|
client;
|
|
527
|
+
flagPrefix;
|
|
488
528
|
constructor(options) {
|
|
489
529
|
this.client = options.client;
|
|
490
530
|
this.commandsDir = options.commandsDir;
|
|
@@ -494,10 +534,11 @@ var StoatxHandler = class {
|
|
|
494
534
|
this.registry = new CommandRegistry(options.extensions);
|
|
495
535
|
this.disableMentionPrefix = options.disableMentionPrefix ?? false;
|
|
496
536
|
this.cooldownManager = options.cooldownManager ?? new DefaultCooldownManager();
|
|
537
|
+
this.flagPrefix = options.flagPrefix || "-";
|
|
497
538
|
}
|
|
498
539
|
/**
|
|
499
|
-
|
|
500
|
-
|
|
540
|
+
* Initialize the handler - load all commands
|
|
541
|
+
*/
|
|
501
542
|
async init() {
|
|
502
543
|
if (this.commandsDir) {
|
|
503
544
|
await this.registry.loadFromDirectory(this.commandsDir);
|
|
@@ -507,21 +548,18 @@ var StoatxHandler = class {
|
|
|
507
548
|
this.attachEvents();
|
|
508
549
|
}
|
|
509
550
|
/**
|
|
510
|
-
|
|
511
|
-
|
|
551
|
+
* Attach registered events to the client
|
|
552
|
+
*/
|
|
512
553
|
attachEvents() {
|
|
513
554
|
const events = this.registry.getEvents();
|
|
514
555
|
for (const eventDef of events) {
|
|
515
|
-
const handler = async (...args) => {
|
|
556
|
+
const handler = /* @__PURE__ */ __name(async (...args) => {
|
|
516
557
|
try {
|
|
517
558
|
await eventDef.instance[eventDef.methodName](...args, this.client);
|
|
518
559
|
} catch (error) {
|
|
519
|
-
console.error(
|
|
520
|
-
`[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`,
|
|
521
|
-
error
|
|
522
|
-
);
|
|
560
|
+
console.error(`[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`, error);
|
|
523
561
|
}
|
|
524
|
-
};
|
|
562
|
+
}, "handler");
|
|
525
563
|
const eventName = eventDef.event;
|
|
526
564
|
if (eventDef.type === "once") {
|
|
527
565
|
this.client.once(eventName, handler);
|
|
@@ -531,8 +569,38 @@ var StoatxHandler = class {
|
|
|
531
569
|
}
|
|
532
570
|
}
|
|
533
571
|
/**
|
|
534
|
-
|
|
535
|
-
|
|
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
|
+
*/
|
|
536
604
|
async parseMessage(rawContent, message, meta) {
|
|
537
605
|
const prefix = await this.resolvePrefix(meta.serverId);
|
|
538
606
|
let usedPrefix = prefix;
|
|
@@ -555,10 +623,11 @@ var StoatxHandler = class {
|
|
|
555
623
|
if (!withoutPrefix) {
|
|
556
624
|
return null;
|
|
557
625
|
}
|
|
558
|
-
const [commandName, ...
|
|
626
|
+
const [commandName, ...rawArgs] = withoutPrefix.split(/\s+/);
|
|
559
627
|
if (!commandName) {
|
|
560
628
|
return null;
|
|
561
629
|
}
|
|
630
|
+
const { args, options } = this.parseCommandOptions(rawArgs);
|
|
562
631
|
return {
|
|
563
632
|
client: this.client,
|
|
564
633
|
content: rawContent,
|
|
@@ -566,6 +635,7 @@ var StoatxHandler = class {
|
|
|
566
635
|
channelId: meta.channelId,
|
|
567
636
|
serverId: meta.serverId,
|
|
568
637
|
args,
|
|
638
|
+
options,
|
|
569
639
|
prefix: usedPrefix,
|
|
570
640
|
commandName: commandName.toLowerCase(),
|
|
571
641
|
reply: meta.reply,
|
|
@@ -573,16 +643,16 @@ var StoatxHandler = class {
|
|
|
573
643
|
};
|
|
574
644
|
}
|
|
575
645
|
/**
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
+
*/
|
|
586
656
|
async handle(message) {
|
|
587
657
|
if (!message.channel || !message.author || !message.content) {
|
|
588
658
|
return false;
|
|
@@ -594,9 +664,9 @@ var StoatxHandler = class {
|
|
|
594
664
|
const authorId = message.author.id;
|
|
595
665
|
const channelId = message.channel.id;
|
|
596
666
|
const serverId = message.server?.id;
|
|
597
|
-
const reply = async (content) => {
|
|
667
|
+
const reply = /* @__PURE__ */ __name(async (content) => {
|
|
598
668
|
return await message.channel.send(content);
|
|
599
|
-
};
|
|
669
|
+
}, "reply");
|
|
600
670
|
await this.handleMessage(rawContent, message, {
|
|
601
671
|
authorId,
|
|
602
672
|
channelId,
|
|
@@ -606,21 +676,21 @@ var StoatxHandler = class {
|
|
|
606
676
|
return true;
|
|
607
677
|
}
|
|
608
678
|
/**
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
+
*/
|
|
624
694
|
async handleMessage(rawContent, message, meta) {
|
|
625
695
|
const ctx = await this.parseMessage(rawContent, message, meta);
|
|
626
696
|
if (!ctx) {
|
|
@@ -629,14 +699,15 @@ var StoatxHandler = class {
|
|
|
629
699
|
await this.execute(ctx);
|
|
630
700
|
}
|
|
631
701
|
/**
|
|
632
|
-
|
|
633
|
-
|
|
702
|
+
* Execute a command with the given context
|
|
703
|
+
*/
|
|
634
704
|
async execute(ctx) {
|
|
635
705
|
const registered = this.registry.get(ctx.commandName);
|
|
636
706
|
if (!registered) {
|
|
637
707
|
return false;
|
|
638
708
|
}
|
|
639
709
|
const { instance, metadata, methodName, classConstructor } = registered;
|
|
710
|
+
console.log(`[Debug] Metadata options for ${ctx.commandName}:`, metadata.options);
|
|
640
711
|
if (metadata.ownerOnly && !this.owners.has(ctx.authorId)) {
|
|
641
712
|
await ctx.reply("This command is owner-only.");
|
|
642
713
|
return false;
|
|
@@ -669,6 +740,190 @@ var StoatxHandler = class {
|
|
|
669
740
|
}
|
|
670
741
|
}
|
|
671
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
|
+
}
|
|
672
927
|
if (!await this.cooldownManager.check(ctx, metadata)) {
|
|
673
928
|
const remaining = await this.cooldownManager.getRemaining(ctx, metadata);
|
|
674
929
|
if (typeof instance.onCooldown === "function") {
|
|
@@ -694,26 +949,26 @@ var StoatxHandler = class {
|
|
|
694
949
|
}
|
|
695
950
|
}
|
|
696
951
|
/**
|
|
697
|
-
|
|
698
|
-
|
|
952
|
+
* Get the command registry
|
|
953
|
+
*/
|
|
699
954
|
getRegistry() {
|
|
700
955
|
return this.registry;
|
|
701
956
|
}
|
|
702
957
|
/**
|
|
703
|
-
|
|
704
|
-
|
|
958
|
+
* Get a command by name or alias
|
|
959
|
+
*/
|
|
705
960
|
getCommand(name) {
|
|
706
961
|
return this.registry.get(name);
|
|
707
962
|
}
|
|
708
963
|
/**
|
|
709
|
-
|
|
710
|
-
|
|
964
|
+
* Get all commands
|
|
965
|
+
*/
|
|
711
966
|
getCommands() {
|
|
712
967
|
return this.registry.getAll();
|
|
713
968
|
}
|
|
714
969
|
/**
|
|
715
|
-
|
|
716
|
-
|
|
970
|
+
* Reload all commands
|
|
971
|
+
*/
|
|
717
972
|
async reload() {
|
|
718
973
|
this.registry.clear();
|
|
719
974
|
if (this.cooldownManager.clear) {
|
|
@@ -726,39 +981,65 @@ var StoatxHandler = class {
|
|
|
726
981
|
await this.registry.autoDiscover(this.discoveryOptions);
|
|
727
982
|
}
|
|
728
983
|
/**
|
|
729
|
-
|
|
730
|
-
|
|
984
|
+
* Check if a user is an owner
|
|
985
|
+
*/
|
|
731
986
|
isOwner(userId) {
|
|
732
987
|
return this.owners.has(userId);
|
|
733
988
|
}
|
|
734
989
|
/**
|
|
735
|
-
|
|
736
|
-
|
|
990
|
+
* Add an owner
|
|
991
|
+
*/
|
|
737
992
|
addOwner(userId) {
|
|
738
993
|
this.owners.add(userId);
|
|
739
994
|
}
|
|
740
995
|
/**
|
|
741
|
-
|
|
742
|
-
|
|
996
|
+
* Remove an owner
|
|
997
|
+
*/
|
|
743
998
|
removeOwner(userId) {
|
|
744
999
|
this.owners.delete(userId);
|
|
745
1000
|
}
|
|
746
1001
|
/**
|
|
747
|
-
|
|
748
|
-
|
|
1002
|
+
* Resolve the prefix for a context
|
|
1003
|
+
*/
|
|
749
1004
|
async resolvePrefix(serverId) {
|
|
750
1005
|
if (typeof this.prefixResolver === "function") {
|
|
751
|
-
return this.prefixResolver({
|
|
1006
|
+
return this.prefixResolver({
|
|
1007
|
+
serverId
|
|
1008
|
+
});
|
|
752
1009
|
}
|
|
753
1010
|
return this.prefixResolver;
|
|
754
1011
|
}
|
|
755
1012
|
};
|
|
756
1013
|
|
|
1014
|
+
// src/client.ts
|
|
1015
|
+
import { Client as StoatClient } from "@stoatx/client";
|
|
1016
|
+
var Client = class extends StoatClient {
|
|
1017
|
+
static {
|
|
1018
|
+
__name(this, "Client");
|
|
1019
|
+
}
|
|
1020
|
+
handler;
|
|
1021
|
+
constructor(options) {
|
|
1022
|
+
super(options);
|
|
1023
|
+
this.handler = new StoatxHandler({
|
|
1024
|
+
...options,
|
|
1025
|
+
client: this
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
async login(token) {
|
|
1029
|
+
await this.handler.init();
|
|
1030
|
+
return super.login(token);
|
|
1031
|
+
}
|
|
1032
|
+
async executeCommand(message) {
|
|
1033
|
+
await this.handler.handle(message);
|
|
1034
|
+
}
|
|
1035
|
+
};
|
|
1036
|
+
|
|
757
1037
|
// src/index.ts
|
|
758
1038
|
export * from "@stoatx/client";
|
|
759
1039
|
export {
|
|
760
1040
|
Client,
|
|
761
1041
|
CommandRegistry,
|
|
1042
|
+
CommandValidationError,
|
|
762
1043
|
DefaultCooldownManager,
|
|
763
1044
|
Guard,
|
|
764
1045
|
METADATA_KEYS,
|