stoatx 0.2.1 → 0.4.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 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 'reflect-metadata';
39
- import { Client } from 'stoat.js';
40
- import { MallyHandler } from 'stoatx';
38
+ import "reflect-metadata";
39
+ import { Client } from "stoatx";
41
40
 
42
- const client = new Client();
43
-
44
- const handler = new MallyHandler({
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 handler.init();
51
-
52
- client.on('messageCreate', (message) => {
53
- handler.handleMessage(message);
54
- });
46
+ await client.initCommands();
55
47
 
56
- client.login('your-token');
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 'stoatx';
55
+ import { Stoat, SimpleCommand, Context } from "stoatx";
64
56
 
65
57
  @Stoat()
66
58
  export class GeneralCommands {
67
- @SimpleCommand({ name: 'ping', description: 'Check bot latency' })
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: 'hello', aliases: ['hi', 'hey'] })
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 'stoatx';
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('Administrator') ?? false;
115
+ return ctx.message.member?.hasPermission("Administrator") ?? false;
124
116
  }
125
117
 
126
118
  guardFail(ctx: Context): void {
127
- ctx.reply('You need Administrator permission!');
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: 'shutdown' })
126
+ @SimpleCommand({ name: "shutdown" })
135
127
  async shutdown(ctx: Context) {
136
- await ctx.reply('Shutting down...');
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; // Stoat client instance
148
- message: Message; // Original message
149
- content: string; // Raw message content
150
- authorId: string; // Author's user ID
151
- channelId: string; // Channel ID
152
- serverId?: string; // Server/Guild ID
153
- args: string[]; // Parsed arguments
154
- prefix: string; // Prefix used
155
- commandName: string; // Command name used
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 MallyHandlerOptions {
156
+ interface StoatxHandlerOptions {
165
157
  client: Client;
166
- commandsDir?: string; // Legacy mode: explicitly scan this directory
158
+ commandsDir?: string; // Legacy mode: explicitly scan this directory
167
159
  discovery?: {
168
- roots?: string[]; // Default: [process.cwd()]
169
- include?: string[]; // Glob patterns per root
170
- ignore?: string[]; // Additional ignore globs
160
+ roots?: string[]; // Default: [process.cwd()]
161
+ include?: string[]; // Glob patterns per root
162
+ ignore?: string[]; // Additional ignore globs
171
163
  };
172
- prefix: string | ((ctx: { serverId?: string }) => string | Promise<string>);
173
- owners?: string[]; // Owner user IDs
174
- extensions?: string[]; // File extensions (default: ['.js', '.mjs', '.cjs'])
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 handler = new MallyHandler({
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 scopedHandler = new MallyHandler({
195
- client,
196
- prefix: '!',
185
+ const scopedClient = new Client({
186
+ prefix: "!",
197
187
  discovery: {
198
188
  roots: [process.cwd()],
199
- include: ['apps/bot/dist/commands/**/*.js'],
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 tsRuntimeHandler = new MallyHandler({
205
- client,
206
- prefix: '!',
207
- extensions: ['.ts'],
194
+ const tsRuntimeClient = new Client({
195
+ prefix: "!",
196
+ extensions: [".ts"],
208
197
  discovery: {
209
- include: ['apps/bot/src/commands/**/*.ts'],
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 */
@@ -60,7 +60,7 @@ interface CommandContext {
60
60
  /** The command name used (could be an alias) */
61
61
  commandName: string;
62
62
  /** Reply to the message */
63
- reply: (content: string) => Promise<void>;
63
+ reply: (content: string) => Promise<Message>;
64
64
  /** The original message object (platform-specific) */
65
65
  message: Message;
66
66
  }
@@ -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 MallyGuard {
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 MallyDiscoveryOptions {
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 MallyHandlerOptions {
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?: MallyDiscoveryOptions;
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 MallyGuard {
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: "mally:command: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
- * MallyHandler - The execution engine for commands
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 { MallyHandler } from 'stoatx';
343
- * import { Client } from 'stoat.js';
344
- *
345
- * const client = new Client();
409
+ * import { Client } from 'stoatx';
346
410
  *
347
- * const handler = new MallyHandler({
348
- * client,
411
+ * const client = new Client({
349
412
  * prefix: '!',
350
413
  * owners: ['owner-user-id'],
351
414
  * });
352
415
  *
353
- * await handler.init();
354
- *
355
- * client.on('message', (message) => {
356
- * handler.handleMessage(message);
357
- * });
416
+ * await client.initCommands();
358
417
  * ```
359
418
  */
360
- declare class MallyHandler {
361
- private readonly commandsDir?;
362
- private readonly discoveryOptions?;
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: MallyHandlerOptions);
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
  */
@@ -378,7 +456,7 @@ declare class MallyHandler {
378
456
  authorId: string;
379
457
  channelId: string;
380
458
  serverId?: string;
381
- reply: (content: string) => Promise<void>;
459
+ reply: (content: string) => Promise<Message>;
382
460
  }): Promise<CommandContext | null>;
383
461
  /**
384
462
  * Handle a message object using the configured message adapter
@@ -412,7 +490,7 @@ declare class MallyHandler {
412
490
  authorId: string;
413
491
  channelId: string;
414
492
  serverId?: string;
415
- reply: (content: string) => Promise<void>;
493
+ reply: (content: string) => Promise<Message>;
416
494
  }): Promise<boolean>;
417
495
  /**
418
496
  * Execute a command with the given context
@@ -464,4 +542,4 @@ declare class MallyHandler {
464
542
  private setCooldown;
465
543
  }
466
544
 
467
- export { type CommandContext, type CommandMetadata, CommandRegistry, type CommandContext as Context, Guard, METADATA_KEYS, type MallyDiscoveryOptions, type MallyGuard, MallyHandler, type MallyHandlerOptions, type Permission, type RegisteredCommand, SimpleCommand, type SimpleCommandDefinition, type SimpleCommandOptions, Stoat, type StoatLifecycle, buildSimpleCommandMetadata, getGuards, getSimpleCommands, isStoatClass };
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 };