stoatx 0.2.1 → 0.3.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/LICENSE +21 -0
- package/README.md +42 -56
- package/dist/index.d.mts +106 -28
- package/dist/index.d.ts +106 -28
- package/dist/index.js +119 -25
- package/dist/index.mjs +114 -23
- package/package.json +8 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stoatx
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -35,41 +35,33 @@ Make sure to enable decorators in your `tsconfig.json`:
|
|
|
35
35
|
|
|
36
36
|
```typescript
|
|
37
37
|
// index.ts
|
|
38
|
-
import
|
|
39
|
-
import { Client } from
|
|
40
|
-
import { MallyHandler } from 'stoatx';
|
|
38
|
+
import "reflect-metadata";
|
|
39
|
+
import { Client } from "stoatx";
|
|
41
40
|
|
|
42
|
-
const client = new Client(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
client,
|
|
46
|
-
prefix: '!',
|
|
47
|
-
owners: ['your-user-id']
|
|
41
|
+
const client = new Client({
|
|
42
|
+
prefix: "!",
|
|
43
|
+
owners: ["your-user-id"],
|
|
48
44
|
});
|
|
49
45
|
|
|
50
|
-
await
|
|
51
|
-
|
|
52
|
-
client.on('messageCreate', (message) => {
|
|
53
|
-
handler.handleMessage(message);
|
|
54
|
-
});
|
|
46
|
+
await client.initCommands();
|
|
55
47
|
|
|
56
|
-
client.login(
|
|
48
|
+
client.login("your-token");
|
|
57
49
|
```
|
|
58
50
|
|
|
59
51
|
### 2. Create commands
|
|
60
52
|
|
|
61
53
|
```typescript
|
|
62
54
|
// commands/general.ts
|
|
63
|
-
import { Stoat, SimpleCommand, Context } from
|
|
55
|
+
import { Stoat, SimpleCommand, Context } from "stoatx";
|
|
64
56
|
|
|
65
57
|
@Stoat()
|
|
66
58
|
export class GeneralCommands {
|
|
67
|
-
@SimpleCommand({ name:
|
|
59
|
+
@SimpleCommand({ name: "ping", description: "Check bot latency" })
|
|
68
60
|
async ping(ctx: Context) {
|
|
69
61
|
await ctx.reply(`Pong! 🏓`);
|
|
70
62
|
}
|
|
71
63
|
|
|
72
|
-
@SimpleCommand({ name:
|
|
64
|
+
@SimpleCommand({ name: "hello", aliases: ["hi", "hey"] })
|
|
73
65
|
async hello(ctx: Context) {
|
|
74
66
|
await ctx.reply(`Hello, <@${ctx.authorId}>!`);
|
|
75
67
|
}
|
|
@@ -115,25 +107,25 @@ async ban(ctx: Context) {
|
|
|
115
107
|
Adds a guard check before command execution.
|
|
116
108
|
|
|
117
109
|
```typescript
|
|
118
|
-
import { Stoat, SimpleCommand, Guard, MallyGuard, Context } from
|
|
110
|
+
import { Stoat, SimpleCommand, Guard, MallyGuard, Context } from "stoatx";
|
|
119
111
|
|
|
120
112
|
// Define a guard
|
|
121
113
|
class IsAdmin implements MallyGuard {
|
|
122
114
|
run(ctx: Context): boolean {
|
|
123
|
-
return ctx.message.member?.hasPermission(
|
|
115
|
+
return ctx.message.member?.hasPermission("Administrator") ?? false;
|
|
124
116
|
}
|
|
125
117
|
|
|
126
118
|
guardFail(ctx: Context): void {
|
|
127
|
-
ctx.reply(
|
|
119
|
+
ctx.reply("You need Administrator permission!");
|
|
128
120
|
}
|
|
129
121
|
}
|
|
130
122
|
|
|
131
123
|
@Stoat()
|
|
132
124
|
@Guard(IsAdmin)
|
|
133
125
|
export class AdminCommands {
|
|
134
|
-
@SimpleCommand({ name:
|
|
126
|
+
@SimpleCommand({ name: "shutdown" })
|
|
135
127
|
async shutdown(ctx: Context) {
|
|
136
|
-
await ctx.reply(
|
|
128
|
+
await ctx.reply("Shutting down...");
|
|
137
129
|
}
|
|
138
130
|
}
|
|
139
131
|
```
|
|
@@ -144,16 +136,16 @@ The `Context` object provides:
|
|
|
144
136
|
|
|
145
137
|
```typescript
|
|
146
138
|
interface Context {
|
|
147
|
-
client: Client;
|
|
148
|
-
message: Message;
|
|
149
|
-
content: string;
|
|
150
|
-
authorId: string;
|
|
151
|
-
channelId: string;
|
|
152
|
-
serverId?: string;
|
|
153
|
-
args: string[];
|
|
154
|
-
prefix: string;
|
|
155
|
-
commandName: string;
|
|
156
|
-
|
|
139
|
+
client: Client; // Stoat client instance
|
|
140
|
+
message: Message; // Original message
|
|
141
|
+
content: string; // Raw message content
|
|
142
|
+
authorId: string; // Author's user ID
|
|
143
|
+
channelId: string; // Channel ID
|
|
144
|
+
serverId?: string; // Server/Guild ID
|
|
145
|
+
args: string[]; // Parsed arguments
|
|
146
|
+
prefix: string; // Prefix used
|
|
147
|
+
commandName: string; // Command name used
|
|
148
|
+
|
|
157
149
|
reply(content: string): Promise<void>;
|
|
158
150
|
}
|
|
159
151
|
```
|
|
@@ -161,17 +153,17 @@ interface Context {
|
|
|
161
153
|
## Handler Options
|
|
162
154
|
|
|
163
155
|
```typescript
|
|
164
|
-
interface
|
|
156
|
+
interface StoatxHandlerOptions {
|
|
165
157
|
client: Client;
|
|
166
|
-
commandsDir?: string;
|
|
158
|
+
commandsDir?: string; // Legacy mode: explicitly scan this directory
|
|
167
159
|
discovery?: {
|
|
168
|
-
roots?: string[];
|
|
169
|
-
include?: string[];
|
|
170
|
-
ignore?: string[];
|
|
160
|
+
roots?: string[]; // Default: [process.cwd()]
|
|
161
|
+
include?: string[]; // Glob patterns per root
|
|
162
|
+
ignore?: string[]; // Additional ignore globs
|
|
171
163
|
};
|
|
172
|
-
prefix: string
|
|
173
|
-
owners?: string[];
|
|
174
|
-
extensions?: string[];
|
|
164
|
+
prefix: string ((ctx: { serverId?: string }) => string Promise<string>);
|
|
165
|
+
owners?: string[]; // Owner user IDs
|
|
166
|
+
extensions?: string[]; // File extensions (default: ['.js', '.mjs', '.cjs'])
|
|
175
167
|
disableMentionPrefix?: boolean; // Disable @bot prefix
|
|
176
168
|
}
|
|
177
169
|
|
|
@@ -182,31 +174,28 @@ interface MallyHandlerOptions {
|
|
|
182
174
|
## Dynamic Prefix
|
|
183
175
|
|
|
184
176
|
```typescript
|
|
185
|
-
const
|
|
186
|
-
client,
|
|
177
|
+
const client = new Client({
|
|
187
178
|
prefix: async ({ serverId }) => {
|
|
188
179
|
// Fetch from database, etc.
|
|
189
|
-
return serverId ? await getServerPrefix(serverId) :
|
|
180
|
+
return serverId ? await getServerPrefix(serverId) : "!";
|
|
190
181
|
},
|
|
191
182
|
});
|
|
192
183
|
|
|
193
184
|
// Optional: constrain auto-discovery to specific roots/patterns
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
prefix: '!',
|
|
185
|
+
const scopedClient = new Client({
|
|
186
|
+
prefix: "!",
|
|
197
187
|
discovery: {
|
|
198
188
|
roots: [process.cwd()],
|
|
199
|
-
include: [
|
|
189
|
+
include: ["apps/bot/dist/commands/**/*.js"],
|
|
200
190
|
},
|
|
201
191
|
});
|
|
202
192
|
|
|
203
193
|
// TypeScript source discovery is opt-in and requires a TS runtime loader (tsx/ts-node)
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
extensions: ['.ts'],
|
|
194
|
+
const tsRuntimeClient = new Client({
|
|
195
|
+
prefix: "!",
|
|
196
|
+
extensions: [".ts"],
|
|
208
197
|
discovery: {
|
|
209
|
-
include: [
|
|
198
|
+
include: ["apps/bot/src/commands/**/*.ts"],
|
|
210
199
|
},
|
|
211
200
|
});
|
|
212
201
|
```
|
|
@@ -216,6 +205,3 @@ All commands are defined through `@Stoat()` classes and `@SimpleCommand()` metho
|
|
|
216
205
|
## License
|
|
217
206
|
|
|
218
207
|
AGPL-3.0-or-later
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Client, Message } from 'stoat.js';
|
|
1
|
+
import { Client as Client$1, Message } from 'stoat.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Permission types for commands
|
|
@@ -44,7 +44,7 @@ interface CommandMetadata {
|
|
|
44
44
|
*/
|
|
45
45
|
interface CommandContext {
|
|
46
46
|
/** The client instance */
|
|
47
|
-
client: Client;
|
|
47
|
+
client: Client$1;
|
|
48
48
|
/** The raw message content */
|
|
49
49
|
content: string;
|
|
50
50
|
/** The author ID */
|
|
@@ -73,14 +73,14 @@ interface StoatLifecycle {
|
|
|
73
73
|
/** Optional: Called when a cooldown is active */
|
|
74
74
|
onCooldown?(ctx: CommandContext, remaining: number): Promise<void>;
|
|
75
75
|
}
|
|
76
|
-
interface
|
|
76
|
+
interface StoatxGuard {
|
|
77
77
|
run(ctx: CommandContext): Promise<boolean> | boolean;
|
|
78
78
|
guardFail?(ctx: CommandContext): Promise<void> | void;
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
81
|
* Discovery options for automatic command module loading
|
|
82
82
|
*/
|
|
83
|
-
interface
|
|
83
|
+
interface StoatxDiscoveryOptions {
|
|
84
84
|
/** Root directories to scan (default: [process.cwd()]) */
|
|
85
85
|
roots?: string[];
|
|
86
86
|
/** Glob patterns relative to each root */
|
|
@@ -91,13 +91,13 @@ interface MallyDiscoveryOptions {
|
|
|
91
91
|
/**
|
|
92
92
|
* Handler options
|
|
93
93
|
*/
|
|
94
|
-
interface
|
|
94
|
+
interface StoatxHandlerOptions {
|
|
95
95
|
/** The client instance */
|
|
96
|
-
client: Client;
|
|
96
|
+
client: Client$1;
|
|
97
97
|
/** Directory to scan for command modules (absolute path) */
|
|
98
98
|
commandsDir?: string;
|
|
99
99
|
/** Auto-discovery options used when commandsDir is not provided */
|
|
100
|
-
discovery?:
|
|
100
|
+
discovery?: StoatxDiscoveryOptions;
|
|
101
101
|
/** Command prefix or prefix resolver function */
|
|
102
102
|
prefix: string | ((ctx: {
|
|
103
103
|
serverId?: string;
|
|
@@ -183,7 +183,7 @@ declare function getSimpleCommands(target: Function): SimpleCommandDefinition[];
|
|
|
183
183
|
* import { Guard, Stoat, SimpleCommand, CommandContext } from 'stoatx';
|
|
184
184
|
*
|
|
185
185
|
* // Define a guard
|
|
186
|
-
* class NotBot implements
|
|
186
|
+
* class NotBot implements StoatxGuard {
|
|
187
187
|
* run(ctx: CommandContext): boolean {
|
|
188
188
|
* return !ctx.message.author.bot;
|
|
189
189
|
* }
|
|
@@ -209,6 +209,60 @@ declare function Guard(guardClass: Function): ClassDecorator;
|
|
|
209
209
|
*/
|
|
210
210
|
declare function getGuards(target: Function): Function[];
|
|
211
211
|
|
|
212
|
+
interface EventDefinition {
|
|
213
|
+
methodName: string;
|
|
214
|
+
event: string;
|
|
215
|
+
type: "on" | "once";
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* @On
|
|
219
|
+
* Triggered on every occurrence of the event.
|
|
220
|
+
* Marks a method to be executed whenever the specified client event is emitted.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* import { Stoat, On } from 'stoatx';
|
|
225
|
+
* import { Message, Client } from 'stoat.js';
|
|
226
|
+
*
|
|
227
|
+
* @Stoat()
|
|
228
|
+
* class BotEvents {
|
|
229
|
+
* @On('messageCreate')
|
|
230
|
+
* async onMessage(message: Message, client: Client) {
|
|
231
|
+
* console.log('New message received:', message.content);
|
|
232
|
+
* }
|
|
233
|
+
* }
|
|
234
|
+
* ```
|
|
235
|
+
*
|
|
236
|
+
* @param event The name of the client event to listen to
|
|
237
|
+
*/
|
|
238
|
+
declare function On(event: string): MethodDecorator;
|
|
239
|
+
/**
|
|
240
|
+
* @Once
|
|
241
|
+
* Triggered only fully once.
|
|
242
|
+
* Marks a method to be executed only the FIRST time the specified client event is emitted.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```ts
|
|
246
|
+
* import { Stoat, Once } from 'stoatx';
|
|
247
|
+
* import { Client } from 'stoat.js';
|
|
248
|
+
*
|
|
249
|
+
* @Stoat()
|
|
250
|
+
* class BotEvents {
|
|
251
|
+
* @Once('ready')
|
|
252
|
+
* async onReady(client: Client) {
|
|
253
|
+
* console.log('Bot successfully started and logged in!');
|
|
254
|
+
* }
|
|
255
|
+
* }
|
|
256
|
+
* ```
|
|
257
|
+
*
|
|
258
|
+
* @param event The name of the client event to listen to
|
|
259
|
+
*/
|
|
260
|
+
declare function Once(event: string): MethodDecorator;
|
|
261
|
+
/**
|
|
262
|
+
* Get all event definitions from a @Stoat class
|
|
263
|
+
*/
|
|
264
|
+
declare function getEventsMetadata(target: Function): EventDefinition[];
|
|
265
|
+
|
|
212
266
|
/**
|
|
213
267
|
* Build CommandMetadata from SimpleCommandOptions
|
|
214
268
|
*/
|
|
@@ -220,7 +274,8 @@ declare function buildSimpleCommandMetadata(options: SimpleCommandOptions, metho
|
|
|
220
274
|
declare const METADATA_KEYS: {
|
|
221
275
|
readonly IS_STOAT_CLASS: symbol;
|
|
222
276
|
readonly SIMPLE_COMMANDS: symbol;
|
|
223
|
-
readonly GUARDS: "
|
|
277
|
+
readonly GUARDS: "stoatx:command:guards";
|
|
278
|
+
readonly EVENTS: symbol;
|
|
224
279
|
};
|
|
225
280
|
|
|
226
281
|
interface AutoDiscoveryOptions {
|
|
@@ -241,6 +296,15 @@ interface RegisteredCommand {
|
|
|
241
296
|
/** The original class constructor (for guard validation) */
|
|
242
297
|
classConstructor: Function;
|
|
243
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Stored event entry from @On/@Once registration.
|
|
301
|
+
*/
|
|
302
|
+
interface RegisteredEvent {
|
|
303
|
+
instance: object;
|
|
304
|
+
methodName: string;
|
|
305
|
+
event: string;
|
|
306
|
+
type: "on" | "once";
|
|
307
|
+
}
|
|
244
308
|
/**
|
|
245
309
|
* CommandRegistry - Scans directories and stores commands in a Map
|
|
246
310
|
*
|
|
@@ -257,6 +321,7 @@ declare class CommandRegistry {
|
|
|
257
321
|
private static readonly DEFAULT_AUTO_DISCOVERY_IGNORES;
|
|
258
322
|
private readonly commands;
|
|
259
323
|
private readonly aliases;
|
|
324
|
+
private readonly registeredEvents;
|
|
260
325
|
private readonly extensions;
|
|
261
326
|
private readonly processedStoatClasses;
|
|
262
327
|
constructor(extensions?: string[]);
|
|
@@ -294,6 +359,10 @@ declare class CommandRegistry {
|
|
|
294
359
|
* Get all command metadata
|
|
295
360
|
*/
|
|
296
361
|
getAllMetadata(): CommandMetadata[];
|
|
362
|
+
/**
|
|
363
|
+
* Get all registered events
|
|
364
|
+
*/
|
|
365
|
+
getEvents(): RegisteredEvent[];
|
|
297
366
|
/**
|
|
298
367
|
* Get commands grouped by category
|
|
299
368
|
*/
|
|
@@ -333,44 +402,53 @@ declare class CommandRegistry {
|
|
|
333
402
|
}
|
|
334
403
|
|
|
335
404
|
/**
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
* Handles message parsing, middleware execution, and command dispatching
|
|
405
|
+
* Client - An extended Client that integrates StoatxHandler directly
|
|
339
406
|
*
|
|
340
407
|
* @example
|
|
341
408
|
* ```ts
|
|
342
|
-
* import {
|
|
343
|
-
* import { Client } from 'stoat.js';
|
|
344
|
-
*
|
|
345
|
-
* const client = new Client();
|
|
409
|
+
* import { Client } from 'stoatx';
|
|
346
410
|
*
|
|
347
|
-
* const
|
|
348
|
-
* client,
|
|
411
|
+
* const client = new Client({
|
|
349
412
|
* prefix: '!',
|
|
350
413
|
* owners: ['owner-user-id'],
|
|
351
414
|
* });
|
|
352
415
|
*
|
|
353
|
-
* await
|
|
354
|
-
*
|
|
355
|
-
* client.on('message', (message) => {
|
|
356
|
-
* handler.handleMessage(message);
|
|
357
|
-
* });
|
|
416
|
+
* await client.initCommands();
|
|
358
417
|
* ```
|
|
359
418
|
*/
|
|
360
|
-
declare class
|
|
361
|
-
|
|
362
|
-
|
|
419
|
+
declare class Client extends Client$1 {
|
|
420
|
+
readonly handler: StoatxHandler;
|
|
421
|
+
constructor(options: Omit<StoatxHandlerOptions, "client">);
|
|
422
|
+
/**
|
|
423
|
+
* Initialize the StoatxHandler commands
|
|
424
|
+
*/
|
|
425
|
+
initCommands(): Promise<void>;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* StoatxHandler - The execution engine for commands
|
|
429
|
+
*
|
|
430
|
+
* Handles message parsing, middleware execution, and command dispatching
|
|
431
|
+
*
|
|
432
|
+
* @internal This class is not intended to be instantiated directly. Use the `Client` from `stoatx` instead.
|
|
433
|
+
*/
|
|
434
|
+
declare class StoatxHandler {
|
|
435
|
+
private readonly commandsDir;
|
|
436
|
+
private readonly discoveryOptions;
|
|
363
437
|
private readonly prefixResolver;
|
|
364
438
|
private readonly owners;
|
|
365
439
|
private readonly registry;
|
|
366
440
|
private readonly cooldowns;
|
|
367
441
|
private readonly disableMentionPrefix;
|
|
368
442
|
private readonly client;
|
|
369
|
-
constructor(options:
|
|
443
|
+
constructor(options: StoatxHandlerOptions);
|
|
370
444
|
/**
|
|
371
445
|
* Initialize the handler - load all commands
|
|
372
446
|
*/
|
|
373
447
|
init(): Promise<void>;
|
|
448
|
+
/**
|
|
449
|
+
* Attach registered events to the client
|
|
450
|
+
*/
|
|
451
|
+
private attachEvents;
|
|
374
452
|
/**
|
|
375
453
|
* Parse a raw message into command context
|
|
376
454
|
*/
|
|
@@ -464,4 +542,4 @@ declare class MallyHandler {
|
|
|
464
542
|
private setCooldown;
|
|
465
543
|
}
|
|
466
544
|
|
|
467
|
-
export { type CommandContext, type CommandMetadata, CommandRegistry, type
|
|
545
|
+
export { Client, type CommandContext, type CommandMetadata, CommandRegistry, type EventDefinition, Guard, METADATA_KEYS, On, Once, type Permission, type RegisteredCommand, type RegisteredEvent, SimpleCommand, type SimpleCommandDefinition, type SimpleCommandOptions, Stoat, type StoatLifecycle, type StoatxDiscoveryOptions, type StoatxGuard, StoatxHandler, type StoatxHandlerOptions, buildSimpleCommandMetadata, getEventsMetadata, getGuards, getSimpleCommands, isStoatClass };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Client, Message } from 'stoat.js';
|
|
1
|
+
import { Client as Client$1, Message } from 'stoat.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Permission types for commands
|
|
@@ -44,7 +44,7 @@ interface CommandMetadata {
|
|
|
44
44
|
*/
|
|
45
45
|
interface CommandContext {
|
|
46
46
|
/** The client instance */
|
|
47
|
-
client: Client;
|
|
47
|
+
client: Client$1;
|
|
48
48
|
/** The raw message content */
|
|
49
49
|
content: string;
|
|
50
50
|
/** The author ID */
|
|
@@ -73,14 +73,14 @@ interface StoatLifecycle {
|
|
|
73
73
|
/** Optional: Called when a cooldown is active */
|
|
74
74
|
onCooldown?(ctx: CommandContext, remaining: number): Promise<void>;
|
|
75
75
|
}
|
|
76
|
-
interface
|
|
76
|
+
interface StoatxGuard {
|
|
77
77
|
run(ctx: CommandContext): Promise<boolean> | boolean;
|
|
78
78
|
guardFail?(ctx: CommandContext): Promise<void> | void;
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
81
|
* Discovery options for automatic command module loading
|
|
82
82
|
*/
|
|
83
|
-
interface
|
|
83
|
+
interface StoatxDiscoveryOptions {
|
|
84
84
|
/** Root directories to scan (default: [process.cwd()]) */
|
|
85
85
|
roots?: string[];
|
|
86
86
|
/** Glob patterns relative to each root */
|
|
@@ -91,13 +91,13 @@ interface MallyDiscoveryOptions {
|
|
|
91
91
|
/**
|
|
92
92
|
* Handler options
|
|
93
93
|
*/
|
|
94
|
-
interface
|
|
94
|
+
interface StoatxHandlerOptions {
|
|
95
95
|
/** The client instance */
|
|
96
|
-
client: Client;
|
|
96
|
+
client: Client$1;
|
|
97
97
|
/** Directory to scan for command modules (absolute path) */
|
|
98
98
|
commandsDir?: string;
|
|
99
99
|
/** Auto-discovery options used when commandsDir is not provided */
|
|
100
|
-
discovery?:
|
|
100
|
+
discovery?: StoatxDiscoveryOptions;
|
|
101
101
|
/** Command prefix or prefix resolver function */
|
|
102
102
|
prefix: string | ((ctx: {
|
|
103
103
|
serverId?: string;
|
|
@@ -183,7 +183,7 @@ declare function getSimpleCommands(target: Function): SimpleCommandDefinition[];
|
|
|
183
183
|
* import { Guard, Stoat, SimpleCommand, CommandContext } from 'stoatx';
|
|
184
184
|
*
|
|
185
185
|
* // Define a guard
|
|
186
|
-
* class NotBot implements
|
|
186
|
+
* class NotBot implements StoatxGuard {
|
|
187
187
|
* run(ctx: CommandContext): boolean {
|
|
188
188
|
* return !ctx.message.author.bot;
|
|
189
189
|
* }
|
|
@@ -209,6 +209,60 @@ declare function Guard(guardClass: Function): ClassDecorator;
|
|
|
209
209
|
*/
|
|
210
210
|
declare function getGuards(target: Function): Function[];
|
|
211
211
|
|
|
212
|
+
interface EventDefinition {
|
|
213
|
+
methodName: string;
|
|
214
|
+
event: string;
|
|
215
|
+
type: "on" | "once";
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* @On
|
|
219
|
+
* Triggered on every occurrence of the event.
|
|
220
|
+
* Marks a method to be executed whenever the specified client event is emitted.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* import { Stoat, On } from 'stoatx';
|
|
225
|
+
* import { Message, Client } from 'stoat.js';
|
|
226
|
+
*
|
|
227
|
+
* @Stoat()
|
|
228
|
+
* class BotEvents {
|
|
229
|
+
* @On('messageCreate')
|
|
230
|
+
* async onMessage(message: Message, client: Client) {
|
|
231
|
+
* console.log('New message received:', message.content);
|
|
232
|
+
* }
|
|
233
|
+
* }
|
|
234
|
+
* ```
|
|
235
|
+
*
|
|
236
|
+
* @param event The name of the client event to listen to
|
|
237
|
+
*/
|
|
238
|
+
declare function On(event: string): MethodDecorator;
|
|
239
|
+
/**
|
|
240
|
+
* @Once
|
|
241
|
+
* Triggered only fully once.
|
|
242
|
+
* Marks a method to be executed only the FIRST time the specified client event is emitted.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```ts
|
|
246
|
+
* import { Stoat, Once } from 'stoatx';
|
|
247
|
+
* import { Client } from 'stoat.js';
|
|
248
|
+
*
|
|
249
|
+
* @Stoat()
|
|
250
|
+
* class BotEvents {
|
|
251
|
+
* @Once('ready')
|
|
252
|
+
* async onReady(client: Client) {
|
|
253
|
+
* console.log('Bot successfully started and logged in!');
|
|
254
|
+
* }
|
|
255
|
+
* }
|
|
256
|
+
* ```
|
|
257
|
+
*
|
|
258
|
+
* @param event The name of the client event to listen to
|
|
259
|
+
*/
|
|
260
|
+
declare function Once(event: string): MethodDecorator;
|
|
261
|
+
/**
|
|
262
|
+
* Get all event definitions from a @Stoat class
|
|
263
|
+
*/
|
|
264
|
+
declare function getEventsMetadata(target: Function): EventDefinition[];
|
|
265
|
+
|
|
212
266
|
/**
|
|
213
267
|
* Build CommandMetadata from SimpleCommandOptions
|
|
214
268
|
*/
|
|
@@ -220,7 +274,8 @@ declare function buildSimpleCommandMetadata(options: SimpleCommandOptions, metho
|
|
|
220
274
|
declare const METADATA_KEYS: {
|
|
221
275
|
readonly IS_STOAT_CLASS: symbol;
|
|
222
276
|
readonly SIMPLE_COMMANDS: symbol;
|
|
223
|
-
readonly GUARDS: "
|
|
277
|
+
readonly GUARDS: "stoatx:command:guards";
|
|
278
|
+
readonly EVENTS: symbol;
|
|
224
279
|
};
|
|
225
280
|
|
|
226
281
|
interface AutoDiscoveryOptions {
|
|
@@ -241,6 +296,15 @@ interface RegisteredCommand {
|
|
|
241
296
|
/** The original class constructor (for guard validation) */
|
|
242
297
|
classConstructor: Function;
|
|
243
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Stored event entry from @On/@Once registration.
|
|
301
|
+
*/
|
|
302
|
+
interface RegisteredEvent {
|
|
303
|
+
instance: object;
|
|
304
|
+
methodName: string;
|
|
305
|
+
event: string;
|
|
306
|
+
type: "on" | "once";
|
|
307
|
+
}
|
|
244
308
|
/**
|
|
245
309
|
* CommandRegistry - Scans directories and stores commands in a Map
|
|
246
310
|
*
|
|
@@ -257,6 +321,7 @@ declare class CommandRegistry {
|
|
|
257
321
|
private static readonly DEFAULT_AUTO_DISCOVERY_IGNORES;
|
|
258
322
|
private readonly commands;
|
|
259
323
|
private readonly aliases;
|
|
324
|
+
private readonly registeredEvents;
|
|
260
325
|
private readonly extensions;
|
|
261
326
|
private readonly processedStoatClasses;
|
|
262
327
|
constructor(extensions?: string[]);
|
|
@@ -294,6 +359,10 @@ declare class CommandRegistry {
|
|
|
294
359
|
* Get all command metadata
|
|
295
360
|
*/
|
|
296
361
|
getAllMetadata(): CommandMetadata[];
|
|
362
|
+
/**
|
|
363
|
+
* Get all registered events
|
|
364
|
+
*/
|
|
365
|
+
getEvents(): RegisteredEvent[];
|
|
297
366
|
/**
|
|
298
367
|
* Get commands grouped by category
|
|
299
368
|
*/
|
|
@@ -333,44 +402,53 @@ declare class CommandRegistry {
|
|
|
333
402
|
}
|
|
334
403
|
|
|
335
404
|
/**
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
* Handles message parsing, middleware execution, and command dispatching
|
|
405
|
+
* Client - An extended Client that integrates StoatxHandler directly
|
|
339
406
|
*
|
|
340
407
|
* @example
|
|
341
408
|
* ```ts
|
|
342
|
-
* import {
|
|
343
|
-
* import { Client } from 'stoat.js';
|
|
344
|
-
*
|
|
345
|
-
* const client = new Client();
|
|
409
|
+
* import { Client } from 'stoatx';
|
|
346
410
|
*
|
|
347
|
-
* const
|
|
348
|
-
* client,
|
|
411
|
+
* const client = new Client({
|
|
349
412
|
* prefix: '!',
|
|
350
413
|
* owners: ['owner-user-id'],
|
|
351
414
|
* });
|
|
352
415
|
*
|
|
353
|
-
* await
|
|
354
|
-
*
|
|
355
|
-
* client.on('message', (message) => {
|
|
356
|
-
* handler.handleMessage(message);
|
|
357
|
-
* });
|
|
416
|
+
* await client.initCommands();
|
|
358
417
|
* ```
|
|
359
418
|
*/
|
|
360
|
-
declare class
|
|
361
|
-
|
|
362
|
-
|
|
419
|
+
declare class Client extends Client$1 {
|
|
420
|
+
readonly handler: StoatxHandler;
|
|
421
|
+
constructor(options: Omit<StoatxHandlerOptions, "client">);
|
|
422
|
+
/**
|
|
423
|
+
* Initialize the StoatxHandler commands
|
|
424
|
+
*/
|
|
425
|
+
initCommands(): Promise<void>;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* StoatxHandler - The execution engine for commands
|
|
429
|
+
*
|
|
430
|
+
* Handles message parsing, middleware execution, and command dispatching
|
|
431
|
+
*
|
|
432
|
+
* @internal This class is not intended to be instantiated directly. Use the `Client` from `stoatx` instead.
|
|
433
|
+
*/
|
|
434
|
+
declare class StoatxHandler {
|
|
435
|
+
private readonly commandsDir;
|
|
436
|
+
private readonly discoveryOptions;
|
|
363
437
|
private readonly prefixResolver;
|
|
364
438
|
private readonly owners;
|
|
365
439
|
private readonly registry;
|
|
366
440
|
private readonly cooldowns;
|
|
367
441
|
private readonly disableMentionPrefix;
|
|
368
442
|
private readonly client;
|
|
369
|
-
constructor(options:
|
|
443
|
+
constructor(options: StoatxHandlerOptions);
|
|
370
444
|
/**
|
|
371
445
|
* Initialize the handler - load all commands
|
|
372
446
|
*/
|
|
373
447
|
init(): Promise<void>;
|
|
448
|
+
/**
|
|
449
|
+
* Attach registered events to the client
|
|
450
|
+
*/
|
|
451
|
+
private attachEvents;
|
|
374
452
|
/**
|
|
375
453
|
* Parse a raw message into command context
|
|
376
454
|
*/
|
|
@@ -464,4 +542,4 @@ declare class MallyHandler {
|
|
|
464
542
|
private setCooldown;
|
|
465
543
|
}
|
|
466
544
|
|
|
467
|
-
export { type CommandContext, type CommandMetadata, CommandRegistry, type
|
|
545
|
+
export { Client, type CommandContext, type CommandMetadata, CommandRegistry, type EventDefinition, Guard, METADATA_KEYS, On, Once, type Permission, type RegisteredCommand, type RegisteredEvent, SimpleCommand, type SimpleCommandDefinition, type SimpleCommandOptions, Stoat, type StoatLifecycle, type StoatxDiscoveryOptions, type StoatxGuard, StoatxHandler, type StoatxHandlerOptions, buildSimpleCommandMetadata, getEventsMetadata, getGuards, getSimpleCommands, isStoatClass };
|
package/dist/index.js
CHANGED
|
@@ -30,13 +30,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
Client: () => Client,
|
|
33
34
|
CommandRegistry: () => CommandRegistry,
|
|
34
35
|
Guard: () => Guard,
|
|
35
36
|
METADATA_KEYS: () => METADATA_KEYS,
|
|
36
|
-
|
|
37
|
+
On: () => On,
|
|
38
|
+
Once: () => Once,
|
|
37
39
|
SimpleCommand: () => SimpleCommand,
|
|
38
40
|
Stoat: () => Stoat,
|
|
39
41
|
buildSimpleCommandMetadata: () => buildSimpleCommandMetadata,
|
|
42
|
+
getEventsMetadata: () => getEventsMetadata,
|
|
40
43
|
getGuards: () => getGuards,
|
|
41
44
|
getSimpleCommands: () => getSimpleCommands,
|
|
42
45
|
isStoatClass: () => isStoatClass
|
|
@@ -48,9 +51,10 @@ var import_reflect_metadata = require("reflect-metadata");
|
|
|
48
51
|
|
|
49
52
|
// src/decorators/keys.ts
|
|
50
53
|
var METADATA_KEYS = {
|
|
51
|
-
IS_STOAT_CLASS: /* @__PURE__ */ Symbol("
|
|
52
|
-
SIMPLE_COMMANDS: /* @__PURE__ */ Symbol("
|
|
53
|
-
GUARDS: "
|
|
54
|
+
IS_STOAT_CLASS: /* @__PURE__ */ Symbol("stoatx:stoat:isClass"),
|
|
55
|
+
SIMPLE_COMMANDS: /* @__PURE__ */ Symbol("stoatx:stoat:simpleCommands"),
|
|
56
|
+
GUARDS: "stoatx:command:guards",
|
|
57
|
+
EVENTS: /* @__PURE__ */ Symbol("stoatx:stoat:events")
|
|
54
58
|
};
|
|
55
59
|
|
|
56
60
|
// src/decorators/store.ts
|
|
@@ -161,6 +165,31 @@ function getGuards(target) {
|
|
|
161
165
|
return Reflect.getMetadata(METADATA_KEYS.GUARDS, target) || [];
|
|
162
166
|
}
|
|
163
167
|
|
|
168
|
+
// src/decorators/Events.ts
|
|
169
|
+
var import_reflect_metadata4 = require("reflect-metadata");
|
|
170
|
+
function createEventDecorator(event, type) {
|
|
171
|
+
return (target, propertyKey, descriptor) => {
|
|
172
|
+
const constructor = target.constructor;
|
|
173
|
+
const existingEvents = Reflect.getMetadata(METADATA_KEYS.EVENTS, constructor) || [];
|
|
174
|
+
existingEvents.push({
|
|
175
|
+
methodName: String(propertyKey),
|
|
176
|
+
event,
|
|
177
|
+
type
|
|
178
|
+
});
|
|
179
|
+
Reflect.defineMetadata(METADATA_KEYS.EVENTS, existingEvents, constructor);
|
|
180
|
+
return descriptor;
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function On(event) {
|
|
184
|
+
return createEventDecorator(event, "on");
|
|
185
|
+
}
|
|
186
|
+
function Once(event) {
|
|
187
|
+
return createEventDecorator(event, "once");
|
|
188
|
+
}
|
|
189
|
+
function getEventsMetadata(target) {
|
|
190
|
+
return Reflect.getMetadata(METADATA_KEYS.EVENTS, target) || [];
|
|
191
|
+
}
|
|
192
|
+
|
|
164
193
|
// src/decorators/utils.ts
|
|
165
194
|
function buildSimpleCommandMetadata(options, methodName, category) {
|
|
166
195
|
return {
|
|
@@ -184,6 +213,7 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
184
213
|
constructor(extensions = [".js", ".mjs", ".cjs"]) {
|
|
185
214
|
this.commands = /* @__PURE__ */ new Map();
|
|
186
215
|
this.aliases = /* @__PURE__ */ new Map();
|
|
216
|
+
this.registeredEvents = [];
|
|
187
217
|
this.processedStoatClasses = /* @__PURE__ */ new Set();
|
|
188
218
|
this.extensions = extensions;
|
|
189
219
|
}
|
|
@@ -207,7 +237,7 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
207
237
|
await this.loadFile(file, directory);
|
|
208
238
|
}
|
|
209
239
|
}
|
|
210
|
-
console.log(`[
|
|
240
|
+
console.log(`[Stoatx] Loaded ${this.commands.size} command(s) and ${this.registeredEvents.length} event(s)`);
|
|
211
241
|
}
|
|
212
242
|
/**
|
|
213
243
|
* Auto-discover command files across one or more roots.
|
|
@@ -235,7 +265,7 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
235
265
|
}) ?? roots[0];
|
|
236
266
|
await this.loadFile(file, baseDir);
|
|
237
267
|
}
|
|
238
|
-
console.log(`[
|
|
268
|
+
console.log(`[Stoatx] Loaded ${this.commands.size} command(s) and ${this.registeredEvents.length} event(s)`);
|
|
239
269
|
}
|
|
240
270
|
getDefaultAutoDiscoveryPatterns() {
|
|
241
271
|
return this.extensions.map((ext) => `**/*${ext}`);
|
|
@@ -243,7 +273,7 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
243
273
|
async isLikelyCommandModule(filePath) {
|
|
244
274
|
try {
|
|
245
275
|
const source = await fs.readFile(filePath, "utf8");
|
|
246
|
-
return source.includes("Stoat") || source.includes("SimpleCommand") || source.includes("Command") || source.includes("
|
|
276
|
+
return source.includes("Stoat") || source.includes("SimpleCommand") || source.includes("Command") || source.includes("stoatx:command");
|
|
247
277
|
} catch {
|
|
248
278
|
return true;
|
|
249
279
|
}
|
|
@@ -254,7 +284,7 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
254
284
|
register(instance, metadata, classConstructor, methodName) {
|
|
255
285
|
const name = metadata.name.toLowerCase();
|
|
256
286
|
if (this.commands.has(name)) {
|
|
257
|
-
console.warn(`[
|
|
287
|
+
console.warn(`[Stoatx] Duplicate command name: ${name}. Skipping...`);
|
|
258
288
|
return;
|
|
259
289
|
}
|
|
260
290
|
this.validateGuards(classConstructor, metadata.name);
|
|
@@ -262,7 +292,7 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
262
292
|
for (const alias of metadata.aliases) {
|
|
263
293
|
const aliasLower = alias.toLowerCase();
|
|
264
294
|
if (this.aliases.has(aliasLower) || this.commands.has(aliasLower)) {
|
|
265
|
-
console.warn(`[
|
|
295
|
+
console.warn(`[Stoatx] Duplicate alias: ${aliasLower}. Skipping...`);
|
|
266
296
|
continue;
|
|
267
297
|
}
|
|
268
298
|
this.aliases.set(aliasLower, name);
|
|
@@ -295,6 +325,12 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
295
325
|
getAllMetadata() {
|
|
296
326
|
return this.getAll().map((c) => c.metadata);
|
|
297
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Get all registered events
|
|
330
|
+
*/
|
|
331
|
+
getEvents() {
|
|
332
|
+
return this.registeredEvents;
|
|
333
|
+
}
|
|
298
334
|
/**
|
|
299
335
|
* Get commands grouped by category
|
|
300
336
|
*/
|
|
@@ -314,6 +350,7 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
314
350
|
clear() {
|
|
315
351
|
this.commands.clear();
|
|
316
352
|
this.aliases.clear();
|
|
353
|
+
this.registeredEvents.length = 0;
|
|
317
354
|
this.processedStoatClasses.clear();
|
|
318
355
|
}
|
|
319
356
|
/**
|
|
@@ -341,20 +378,20 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
341
378
|
* @private
|
|
342
379
|
*/
|
|
343
380
|
validateGuards(commandClass, commandName) {
|
|
344
|
-
const guards = Reflect.getMetadata("
|
|
381
|
+
const guards = Reflect.getMetadata("stoatx:command:guards", commandClass) || [];
|
|
345
382
|
for (const GuardClass of guards) {
|
|
346
383
|
const guardInstance = new GuardClass();
|
|
347
384
|
if (typeof guardInstance.run !== "function") {
|
|
348
385
|
console.error(
|
|
349
|
-
`[
|
|
386
|
+
`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`
|
|
350
387
|
);
|
|
351
388
|
process.exit(1);
|
|
352
389
|
}
|
|
353
390
|
if (typeof guardInstance.guardFail !== "function") {
|
|
354
391
|
console.error(
|
|
355
|
-
`[
|
|
392
|
+
`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`
|
|
356
393
|
);
|
|
357
|
-
console.error(`[
|
|
394
|
+
console.error(`[Stoatx] All guards must implement guardFail() to handle failed checks.`);
|
|
358
395
|
process.exit(1);
|
|
359
396
|
}
|
|
360
397
|
}
|
|
@@ -375,15 +412,16 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
375
412
|
this.registerStoatClassCommands(stoatClass, stoatInstance, filePath, baseDir);
|
|
376
413
|
}
|
|
377
414
|
} catch (error) {
|
|
378
|
-
console.error(`[
|
|
415
|
+
console.error(`[Stoatx] Failed to load command file: ${filePath}`, error);
|
|
379
416
|
}
|
|
380
417
|
}
|
|
381
418
|
registerStoatClassCommands(stoatClass, instance, filePath, baseDir) {
|
|
382
419
|
const simpleCommands = getSimpleCommands(stoatClass);
|
|
420
|
+
const events = getEventsMetadata(stoatClass);
|
|
383
421
|
const category = this.getCategoryFromPath(filePath, baseDir);
|
|
384
|
-
if (simpleCommands.length === 0) {
|
|
422
|
+
if (simpleCommands.length === 0 && events.length === 0) {
|
|
385
423
|
console.warn(
|
|
386
|
-
`[
|
|
424
|
+
`[Stoatx] Class ${stoatClass.name} is decorated with @Stoat but has no @SimpleCommand, @On or @Once methods. Skipping...`
|
|
387
425
|
);
|
|
388
426
|
this.processedStoatClasses.add(stoatClass);
|
|
389
427
|
return;
|
|
@@ -391,12 +429,25 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
391
429
|
for (const cmdDef of simpleCommands) {
|
|
392
430
|
const method = instance[cmdDef.methodName];
|
|
393
431
|
if (typeof method !== "function") {
|
|
394
|
-
console.warn(`[
|
|
432
|
+
console.warn(`[Stoatx] Method ${cmdDef.methodName} not found on ${stoatClass.name}. Skipping...`);
|
|
395
433
|
continue;
|
|
396
434
|
}
|
|
397
435
|
const metadata = buildSimpleCommandMetadata(cmdDef.options, cmdDef.methodName, category);
|
|
398
436
|
this.register(instance, metadata, stoatClass, cmdDef.methodName);
|
|
399
437
|
}
|
|
438
|
+
for (const eventDef of events) {
|
|
439
|
+
const method = instance[eventDef.methodName];
|
|
440
|
+
if (typeof method !== "function") {
|
|
441
|
+
console.warn(`[Stoatx] Method ${eventDef.methodName} not found on ${stoatClass.name}. Skipping...`);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
this.registeredEvents.push({
|
|
445
|
+
instance,
|
|
446
|
+
methodName: eventDef.methodName,
|
|
447
|
+
event: eventDef.event,
|
|
448
|
+
type: eventDef.type
|
|
449
|
+
});
|
|
450
|
+
}
|
|
400
451
|
this.processedStoatClasses.add(stoatClass);
|
|
401
452
|
}
|
|
402
453
|
/**
|
|
@@ -421,8 +472,24 @@ _CommandRegistry.DEFAULT_AUTO_DISCOVERY_IGNORES = [
|
|
|
421
472
|
var CommandRegistry = _CommandRegistry;
|
|
422
473
|
|
|
423
474
|
// src/handler.ts
|
|
424
|
-
var
|
|
425
|
-
var
|
|
475
|
+
var import_reflect_metadata5 = require("reflect-metadata");
|
|
476
|
+
var import_stoat = require("stoat.js");
|
|
477
|
+
var Client = class extends import_stoat.Client {
|
|
478
|
+
constructor(options) {
|
|
479
|
+
super();
|
|
480
|
+
this.handler = new StoatxHandler({ ...options, client: this });
|
|
481
|
+
this.on("messageCreate", async (message) => {
|
|
482
|
+
await this.handler.handle(message);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Initialize the StoatxHandler commands
|
|
487
|
+
*/
|
|
488
|
+
async initCommands() {
|
|
489
|
+
await this.handler.init();
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
var StoatxHandler = class {
|
|
426
493
|
constructor(options) {
|
|
427
494
|
this.cooldowns = /* @__PURE__ */ new Map();
|
|
428
495
|
this.client = options.client;
|
|
@@ -439,9 +506,33 @@ var MallyHandler = class {
|
|
|
439
506
|
async init() {
|
|
440
507
|
if (this.commandsDir) {
|
|
441
508
|
await this.registry.loadFromDirectory(this.commandsDir);
|
|
442
|
-
|
|
509
|
+
} else {
|
|
510
|
+
await this.registry.autoDiscover(this.discoveryOptions);
|
|
511
|
+
}
|
|
512
|
+
this.attachEvents();
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Attach registered events to the client
|
|
516
|
+
*/
|
|
517
|
+
attachEvents() {
|
|
518
|
+
const events = this.registry.getEvents();
|
|
519
|
+
for (const eventDef of events) {
|
|
520
|
+
const handler = async (...args) => {
|
|
521
|
+
try {
|
|
522
|
+
await eventDef.instance[eventDef.methodName](...args, this.client);
|
|
523
|
+
} catch (error) {
|
|
524
|
+
console.error(
|
|
525
|
+
`[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`,
|
|
526
|
+
error
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
if (eventDef.type === "once") {
|
|
531
|
+
this.client.once(eventDef.event, handler);
|
|
532
|
+
} else {
|
|
533
|
+
this.client.on(eventDef.event, handler);
|
|
534
|
+
}
|
|
443
535
|
}
|
|
444
|
-
await this.registry.autoDiscover(this.discoveryOptions);
|
|
445
536
|
}
|
|
446
537
|
/**
|
|
447
538
|
* Parse a raw message into command context
|
|
@@ -553,7 +644,7 @@ var MallyHandler = class {
|
|
|
553
644
|
await ctx.reply("This command is owner-only.");
|
|
554
645
|
return false;
|
|
555
646
|
}
|
|
556
|
-
const guards = Reflect.getMetadata("
|
|
647
|
+
const guards = Reflect.getMetadata("stoatx:command:guards", classConstructor) || [];
|
|
557
648
|
for (const guardClass of guards) {
|
|
558
649
|
const guardInstance = new guardClass();
|
|
559
650
|
if (typeof guardInstance.run === "function") {
|
|
@@ -562,7 +653,7 @@ var MallyHandler = class {
|
|
|
562
653
|
if (typeof guardInstance.guardFail === "function") {
|
|
563
654
|
await guardInstance.guardFail(ctx);
|
|
564
655
|
} else {
|
|
565
|
-
console.error("[
|
|
656
|
+
console.error("[Stoatx] Guard check failed but no guardFail method defined on", guardClass.name);
|
|
566
657
|
}
|
|
567
658
|
return false;
|
|
568
659
|
}
|
|
@@ -587,7 +678,7 @@ var MallyHandler = class {
|
|
|
587
678
|
if (typeof instance.onError === "function") {
|
|
588
679
|
await instance.onError(ctx, error);
|
|
589
680
|
} else {
|
|
590
|
-
console.error(`[
|
|
681
|
+
console.error(`[Stoatx] Error in command ${metadata.name}:`, error);
|
|
591
682
|
await ctx.reply(`An error occurred: ${error.message}`);
|
|
592
683
|
}
|
|
593
684
|
return false;
|
|
@@ -684,13 +775,16 @@ var MallyHandler = class {
|
|
|
684
775
|
};
|
|
685
776
|
// Annotate the CommonJS export names for ESM import in node:
|
|
686
777
|
0 && (module.exports = {
|
|
778
|
+
Client,
|
|
687
779
|
CommandRegistry,
|
|
688
780
|
Guard,
|
|
689
781
|
METADATA_KEYS,
|
|
690
|
-
|
|
782
|
+
On,
|
|
783
|
+
Once,
|
|
691
784
|
SimpleCommand,
|
|
692
785
|
Stoat,
|
|
693
786
|
buildSimpleCommandMetadata,
|
|
787
|
+
getEventsMetadata,
|
|
694
788
|
getGuards,
|
|
695
789
|
getSimpleCommands,
|
|
696
790
|
isStoatClass
|
package/dist/index.mjs
CHANGED
|
@@ -3,9 +3,10 @@ import "reflect-metadata";
|
|
|
3
3
|
|
|
4
4
|
// src/decorators/keys.ts
|
|
5
5
|
var METADATA_KEYS = {
|
|
6
|
-
IS_STOAT_CLASS: /* @__PURE__ */ Symbol("
|
|
7
|
-
SIMPLE_COMMANDS: /* @__PURE__ */ Symbol("
|
|
8
|
-
GUARDS: "
|
|
6
|
+
IS_STOAT_CLASS: /* @__PURE__ */ Symbol("stoatx:stoat:isClass"),
|
|
7
|
+
SIMPLE_COMMANDS: /* @__PURE__ */ Symbol("stoatx:stoat:simpleCommands"),
|
|
8
|
+
GUARDS: "stoatx:command:guards",
|
|
9
|
+
EVENTS: /* @__PURE__ */ Symbol("stoatx:stoat:events")
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
// src/decorators/store.ts
|
|
@@ -116,6 +117,31 @@ function getGuards(target) {
|
|
|
116
117
|
return Reflect.getMetadata(METADATA_KEYS.GUARDS, target) || [];
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
// src/decorators/Events.ts
|
|
121
|
+
import "reflect-metadata";
|
|
122
|
+
function createEventDecorator(event, type) {
|
|
123
|
+
return (target, propertyKey, descriptor) => {
|
|
124
|
+
const constructor = target.constructor;
|
|
125
|
+
const existingEvents = Reflect.getMetadata(METADATA_KEYS.EVENTS, constructor) || [];
|
|
126
|
+
existingEvents.push({
|
|
127
|
+
methodName: String(propertyKey),
|
|
128
|
+
event,
|
|
129
|
+
type
|
|
130
|
+
});
|
|
131
|
+
Reflect.defineMetadata(METADATA_KEYS.EVENTS, existingEvents, constructor);
|
|
132
|
+
return descriptor;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function On(event) {
|
|
136
|
+
return createEventDecorator(event, "on");
|
|
137
|
+
}
|
|
138
|
+
function Once(event) {
|
|
139
|
+
return createEventDecorator(event, "once");
|
|
140
|
+
}
|
|
141
|
+
function getEventsMetadata(target) {
|
|
142
|
+
return Reflect.getMetadata(METADATA_KEYS.EVENTS, target) || [];
|
|
143
|
+
}
|
|
144
|
+
|
|
119
145
|
// src/decorators/utils.ts
|
|
120
146
|
function buildSimpleCommandMetadata(options, methodName, category) {
|
|
121
147
|
return {
|
|
@@ -139,6 +165,7 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
139
165
|
constructor(extensions = [".js", ".mjs", ".cjs"]) {
|
|
140
166
|
this.commands = /* @__PURE__ */ new Map();
|
|
141
167
|
this.aliases = /* @__PURE__ */ new Map();
|
|
168
|
+
this.registeredEvents = [];
|
|
142
169
|
this.processedStoatClasses = /* @__PURE__ */ new Set();
|
|
143
170
|
this.extensions = extensions;
|
|
144
171
|
}
|
|
@@ -162,7 +189,7 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
162
189
|
await this.loadFile(file, directory);
|
|
163
190
|
}
|
|
164
191
|
}
|
|
165
|
-
console.log(`[
|
|
192
|
+
console.log(`[Stoatx] Loaded ${this.commands.size} command(s) and ${this.registeredEvents.length} event(s)`);
|
|
166
193
|
}
|
|
167
194
|
/**
|
|
168
195
|
* Auto-discover command files across one or more roots.
|
|
@@ -190,7 +217,7 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
190
217
|
}) ?? roots[0];
|
|
191
218
|
await this.loadFile(file, baseDir);
|
|
192
219
|
}
|
|
193
|
-
console.log(`[
|
|
220
|
+
console.log(`[Stoatx] Loaded ${this.commands.size} command(s) and ${this.registeredEvents.length} event(s)`);
|
|
194
221
|
}
|
|
195
222
|
getDefaultAutoDiscoveryPatterns() {
|
|
196
223
|
return this.extensions.map((ext) => `**/*${ext}`);
|
|
@@ -198,7 +225,7 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
198
225
|
async isLikelyCommandModule(filePath) {
|
|
199
226
|
try {
|
|
200
227
|
const source = await fs.readFile(filePath, "utf8");
|
|
201
|
-
return source.includes("Stoat") || source.includes("SimpleCommand") || source.includes("Command") || source.includes("
|
|
228
|
+
return source.includes("Stoat") || source.includes("SimpleCommand") || source.includes("Command") || source.includes("stoatx:command");
|
|
202
229
|
} catch {
|
|
203
230
|
return true;
|
|
204
231
|
}
|
|
@@ -209,7 +236,7 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
209
236
|
register(instance, metadata, classConstructor, methodName) {
|
|
210
237
|
const name = metadata.name.toLowerCase();
|
|
211
238
|
if (this.commands.has(name)) {
|
|
212
|
-
console.warn(`[
|
|
239
|
+
console.warn(`[Stoatx] Duplicate command name: ${name}. Skipping...`);
|
|
213
240
|
return;
|
|
214
241
|
}
|
|
215
242
|
this.validateGuards(classConstructor, metadata.name);
|
|
@@ -217,7 +244,7 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
217
244
|
for (const alias of metadata.aliases) {
|
|
218
245
|
const aliasLower = alias.toLowerCase();
|
|
219
246
|
if (this.aliases.has(aliasLower) || this.commands.has(aliasLower)) {
|
|
220
|
-
console.warn(`[
|
|
247
|
+
console.warn(`[Stoatx] Duplicate alias: ${aliasLower}. Skipping...`);
|
|
221
248
|
continue;
|
|
222
249
|
}
|
|
223
250
|
this.aliases.set(aliasLower, name);
|
|
@@ -250,6 +277,12 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
250
277
|
getAllMetadata() {
|
|
251
278
|
return this.getAll().map((c) => c.metadata);
|
|
252
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Get all registered events
|
|
282
|
+
*/
|
|
283
|
+
getEvents() {
|
|
284
|
+
return this.registeredEvents;
|
|
285
|
+
}
|
|
253
286
|
/**
|
|
254
287
|
* Get commands grouped by category
|
|
255
288
|
*/
|
|
@@ -269,6 +302,7 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
269
302
|
clear() {
|
|
270
303
|
this.commands.clear();
|
|
271
304
|
this.aliases.clear();
|
|
305
|
+
this.registeredEvents.length = 0;
|
|
272
306
|
this.processedStoatClasses.clear();
|
|
273
307
|
}
|
|
274
308
|
/**
|
|
@@ -296,20 +330,20 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
296
330
|
* @private
|
|
297
331
|
*/
|
|
298
332
|
validateGuards(commandClass, commandName) {
|
|
299
|
-
const guards = Reflect.getMetadata("
|
|
333
|
+
const guards = Reflect.getMetadata("stoatx:command:guards", commandClass) || [];
|
|
300
334
|
for (const GuardClass of guards) {
|
|
301
335
|
const guardInstance = new GuardClass();
|
|
302
336
|
if (typeof guardInstance.run !== "function") {
|
|
303
337
|
console.error(
|
|
304
|
-
`[
|
|
338
|
+
`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`
|
|
305
339
|
);
|
|
306
340
|
process.exit(1);
|
|
307
341
|
}
|
|
308
342
|
if (typeof guardInstance.guardFail !== "function") {
|
|
309
343
|
console.error(
|
|
310
|
-
`[
|
|
344
|
+
`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`
|
|
311
345
|
);
|
|
312
|
-
console.error(`[
|
|
346
|
+
console.error(`[Stoatx] All guards must implement guardFail() to handle failed checks.`);
|
|
313
347
|
process.exit(1);
|
|
314
348
|
}
|
|
315
349
|
}
|
|
@@ -330,15 +364,16 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
330
364
|
this.registerStoatClassCommands(stoatClass, stoatInstance, filePath, baseDir);
|
|
331
365
|
}
|
|
332
366
|
} catch (error) {
|
|
333
|
-
console.error(`[
|
|
367
|
+
console.error(`[Stoatx] Failed to load command file: ${filePath}`, error);
|
|
334
368
|
}
|
|
335
369
|
}
|
|
336
370
|
registerStoatClassCommands(stoatClass, instance, filePath, baseDir) {
|
|
337
371
|
const simpleCommands = getSimpleCommands(stoatClass);
|
|
372
|
+
const events = getEventsMetadata(stoatClass);
|
|
338
373
|
const category = this.getCategoryFromPath(filePath, baseDir);
|
|
339
|
-
if (simpleCommands.length === 0) {
|
|
374
|
+
if (simpleCommands.length === 0 && events.length === 0) {
|
|
340
375
|
console.warn(
|
|
341
|
-
`[
|
|
376
|
+
`[Stoatx] Class ${stoatClass.name} is decorated with @Stoat but has no @SimpleCommand, @On or @Once methods. Skipping...`
|
|
342
377
|
);
|
|
343
378
|
this.processedStoatClasses.add(stoatClass);
|
|
344
379
|
return;
|
|
@@ -346,12 +381,25 @@ var _CommandRegistry = class _CommandRegistry {
|
|
|
346
381
|
for (const cmdDef of simpleCommands) {
|
|
347
382
|
const method = instance[cmdDef.methodName];
|
|
348
383
|
if (typeof method !== "function") {
|
|
349
|
-
console.warn(`[
|
|
384
|
+
console.warn(`[Stoatx] Method ${cmdDef.methodName} not found on ${stoatClass.name}. Skipping...`);
|
|
350
385
|
continue;
|
|
351
386
|
}
|
|
352
387
|
const metadata = buildSimpleCommandMetadata(cmdDef.options, cmdDef.methodName, category);
|
|
353
388
|
this.register(instance, metadata, stoatClass, cmdDef.methodName);
|
|
354
389
|
}
|
|
390
|
+
for (const eventDef of events) {
|
|
391
|
+
const method = instance[eventDef.methodName];
|
|
392
|
+
if (typeof method !== "function") {
|
|
393
|
+
console.warn(`[Stoatx] Method ${eventDef.methodName} not found on ${stoatClass.name}. Skipping...`);
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
this.registeredEvents.push({
|
|
397
|
+
instance,
|
|
398
|
+
methodName: eventDef.methodName,
|
|
399
|
+
event: eventDef.event,
|
|
400
|
+
type: eventDef.type
|
|
401
|
+
});
|
|
402
|
+
}
|
|
355
403
|
this.processedStoatClasses.add(stoatClass);
|
|
356
404
|
}
|
|
357
405
|
/**
|
|
@@ -377,7 +425,23 @@ var CommandRegistry = _CommandRegistry;
|
|
|
377
425
|
|
|
378
426
|
// src/handler.ts
|
|
379
427
|
import "reflect-metadata";
|
|
380
|
-
|
|
428
|
+
import { Client as StoatClient } from "stoat.js";
|
|
429
|
+
var Client = class extends StoatClient {
|
|
430
|
+
constructor(options) {
|
|
431
|
+
super();
|
|
432
|
+
this.handler = new StoatxHandler({ ...options, client: this });
|
|
433
|
+
this.on("messageCreate", async (message) => {
|
|
434
|
+
await this.handler.handle(message);
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Initialize the StoatxHandler commands
|
|
439
|
+
*/
|
|
440
|
+
async initCommands() {
|
|
441
|
+
await this.handler.init();
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
var StoatxHandler = class {
|
|
381
445
|
constructor(options) {
|
|
382
446
|
this.cooldowns = /* @__PURE__ */ new Map();
|
|
383
447
|
this.client = options.client;
|
|
@@ -394,9 +458,33 @@ var MallyHandler = class {
|
|
|
394
458
|
async init() {
|
|
395
459
|
if (this.commandsDir) {
|
|
396
460
|
await this.registry.loadFromDirectory(this.commandsDir);
|
|
397
|
-
|
|
461
|
+
} else {
|
|
462
|
+
await this.registry.autoDiscover(this.discoveryOptions);
|
|
463
|
+
}
|
|
464
|
+
this.attachEvents();
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Attach registered events to the client
|
|
468
|
+
*/
|
|
469
|
+
attachEvents() {
|
|
470
|
+
const events = this.registry.getEvents();
|
|
471
|
+
for (const eventDef of events) {
|
|
472
|
+
const handler = async (...args) => {
|
|
473
|
+
try {
|
|
474
|
+
await eventDef.instance[eventDef.methodName](...args, this.client);
|
|
475
|
+
} catch (error) {
|
|
476
|
+
console.error(
|
|
477
|
+
`[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`,
|
|
478
|
+
error
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
if (eventDef.type === "once") {
|
|
483
|
+
this.client.once(eventDef.event, handler);
|
|
484
|
+
} else {
|
|
485
|
+
this.client.on(eventDef.event, handler);
|
|
486
|
+
}
|
|
398
487
|
}
|
|
399
|
-
await this.registry.autoDiscover(this.discoveryOptions);
|
|
400
488
|
}
|
|
401
489
|
/**
|
|
402
490
|
* Parse a raw message into command context
|
|
@@ -508,7 +596,7 @@ var MallyHandler = class {
|
|
|
508
596
|
await ctx.reply("This command is owner-only.");
|
|
509
597
|
return false;
|
|
510
598
|
}
|
|
511
|
-
const guards = Reflect.getMetadata("
|
|
599
|
+
const guards = Reflect.getMetadata("stoatx:command:guards", classConstructor) || [];
|
|
512
600
|
for (const guardClass of guards) {
|
|
513
601
|
const guardInstance = new guardClass();
|
|
514
602
|
if (typeof guardInstance.run === "function") {
|
|
@@ -517,7 +605,7 @@ var MallyHandler = class {
|
|
|
517
605
|
if (typeof guardInstance.guardFail === "function") {
|
|
518
606
|
await guardInstance.guardFail(ctx);
|
|
519
607
|
} else {
|
|
520
|
-
console.error("[
|
|
608
|
+
console.error("[Stoatx] Guard check failed but no guardFail method defined on", guardClass.name);
|
|
521
609
|
}
|
|
522
610
|
return false;
|
|
523
611
|
}
|
|
@@ -542,7 +630,7 @@ var MallyHandler = class {
|
|
|
542
630
|
if (typeof instance.onError === "function") {
|
|
543
631
|
await instance.onError(ctx, error);
|
|
544
632
|
} else {
|
|
545
|
-
console.error(`[
|
|
633
|
+
console.error(`[Stoatx] Error in command ${metadata.name}:`, error);
|
|
546
634
|
await ctx.reply(`An error occurred: ${error.message}`);
|
|
547
635
|
}
|
|
548
636
|
return false;
|
|
@@ -638,13 +726,16 @@ var MallyHandler = class {
|
|
|
638
726
|
}
|
|
639
727
|
};
|
|
640
728
|
export {
|
|
729
|
+
Client,
|
|
641
730
|
CommandRegistry,
|
|
642
731
|
Guard,
|
|
643
732
|
METADATA_KEYS,
|
|
644
|
-
|
|
733
|
+
On,
|
|
734
|
+
Once,
|
|
645
735
|
SimpleCommand,
|
|
646
736
|
Stoat,
|
|
647
737
|
buildSimpleCommandMetadata,
|
|
738
|
+
getEventsMetadata,
|
|
648
739
|
getGuards,
|
|
649
740
|
getSimpleCommands,
|
|
650
741
|
isStoatClass
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stoatx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A high-performance, decorator-based command handler for the Stoat ecosystem.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,16 +23,10 @@
|
|
|
23
23
|
"files": [
|
|
24
24
|
"dist"
|
|
25
25
|
],
|
|
26
|
-
"license": "
|
|
26
|
+
"license": "MIT",
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|
|
29
29
|
},
|
|
30
|
-
"scripts": {
|
|
31
|
-
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
32
|
-
"dev": "tsup src/index.ts --format cjs,esm --watch --dts",
|
|
33
|
-
"lint": "eslint src/**/*.ts",
|
|
34
|
-
"format": "prettier --write src/**/*.ts"
|
|
35
|
-
},
|
|
36
30
|
"dependencies": {
|
|
37
31
|
"reflect-metadata": "^0.2.2",
|
|
38
32
|
"stoat.js": "^7.3.6",
|
|
@@ -46,5 +40,10 @@
|
|
|
46
40
|
},
|
|
47
41
|
"peerDependencies": {
|
|
48
42
|
"reflect-metadata": "^0.2.0"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --tsconfig tsconfig.build.json",
|
|
46
|
+
"dev": "tsup src/index.ts --format cjs,esm --watch --dts --tsconfig tsconfig.build.json",
|
|
47
|
+
"lint": "eslint src/**/*.ts"
|
|
49
48
|
}
|
|
50
|
-
}
|
|
49
|
+
}
|