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