stoatx 0.2.0 → 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.
@@ -0,0 +1,545 @@
1
+ import { Client as Client$1, Message } from 'stoat.js';
2
+
3
+ /**
4
+ * Permission types for commands
5
+ */
6
+ type Permission = "SendMessages" | "ManageMessages" | "ManageChannels" | "ManageServer" | "KickMembers" | "BanMembers" | "Administrator" | (string & {});
7
+ /**
8
+ * Simple command options passed to @SimpleCommand decorator
9
+ * Used with @Stoat() decorated classes for method-based commands
10
+ */
11
+ interface SimpleCommandOptions {
12
+ /** Command name (defaults to method name) */
13
+ name?: string;
14
+ /** Command description */
15
+ description?: string;
16
+ /** Command aliases */
17
+ aliases?: string[];
18
+ /** Required permissions to run the command */
19
+ permissions?: Permission[];
20
+ /** Command category (auto-detected from directory if not provided) */
21
+ category?: string;
22
+ /** Cooldown in milliseconds */
23
+ cooldown?: number;
24
+ /** Whether the command is NSFW only */
25
+ nsfw?: boolean;
26
+ /** Whether the command is owner only */
27
+ ownerOnly?: boolean;
28
+ }
29
+ /**
30
+ * Resolved command metadata with required fields
31
+ */
32
+ interface CommandMetadata {
33
+ name: string;
34
+ description: string;
35
+ aliases: string[];
36
+ permissions: Permission[];
37
+ category: string;
38
+ cooldown: number;
39
+ nsfw: boolean;
40
+ ownerOnly: boolean;
41
+ }
42
+ /**
43
+ * Command execution context
44
+ */
45
+ interface CommandContext {
46
+ /** The client instance */
47
+ client: Client$1;
48
+ /** The raw message content */
49
+ content: string;
50
+ /** The author ID */
51
+ authorId: string;
52
+ /** The channel ID */
53
+ channelId: string;
54
+ /** The server/guild ID (if applicable) */
55
+ serverId?: string;
56
+ /** Parsed command arguments */
57
+ args: string[];
58
+ /** The prefix used */
59
+ prefix: string;
60
+ /** The command name used (could be an alias) */
61
+ commandName: string;
62
+ /** Reply to the message */
63
+ reply: (content: string) => Promise<void>;
64
+ /** The original message object (platform-specific) */
65
+ message: Message;
66
+ }
67
+ /**
68
+ * Optional lifecycle hooks for @Stoat() class instances
69
+ */
70
+ interface StoatLifecycle {
71
+ /** Optional: Called when an error occurs during command execution */
72
+ onError?(ctx: CommandContext, error: Error): Promise<void>;
73
+ /** Optional: Called when a cooldown is active */
74
+ onCooldown?(ctx: CommandContext, remaining: number): Promise<void>;
75
+ }
76
+ interface StoatxGuard {
77
+ run(ctx: CommandContext): Promise<boolean> | boolean;
78
+ guardFail?(ctx: CommandContext): Promise<void> | void;
79
+ }
80
+ /**
81
+ * Discovery options for automatic command module loading
82
+ */
83
+ interface StoatxDiscoveryOptions {
84
+ /** Root directories to scan (default: [process.cwd()]) */
85
+ roots?: string[];
86
+ /** Glob patterns relative to each root */
87
+ include?: string[];
88
+ /** Additional ignore patterns */
89
+ ignore?: string[];
90
+ }
91
+ /**
92
+ * Handler options
93
+ */
94
+ interface StoatxHandlerOptions {
95
+ /** The client instance */
96
+ client: Client$1;
97
+ /** Directory to scan for command modules (absolute path) */
98
+ commandsDir?: string;
99
+ /** Auto-discovery options used when commandsDir is not provided */
100
+ discovery?: StoatxDiscoveryOptions;
101
+ /** Command prefix or prefix resolver function */
102
+ prefix: string | ((ctx: {
103
+ serverId?: string;
104
+ }) => string | Promise<string>);
105
+ /** Owner IDs for owner-only commands */
106
+ owners?: string[];
107
+ /** File extensions to load (default: ['.js', '.mjs', '.cjs']) */
108
+ extensions?: string[];
109
+ /** Disable mention prefix support (default: false) */
110
+ disableMentionPrefix?: boolean;
111
+ }
112
+
113
+ /**
114
+ * @Stoat
115
+ * Marks a class as a Stoat command container.
116
+ * Use this decorator on classes that contain @SimpleCommand methods.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * import { Stoat, SimpleCommand, CommandContext } from 'stoatx';
121
+ *
122
+ * @Stoat()
123
+ * class ModerationCommands {
124
+ * @SimpleCommand({ name: 'ban', description: 'Ban a user' })
125
+ * async ban(ctx: CommandContext) {
126
+ * await ctx.reply('User banned!');
127
+ * }
128
+ *
129
+ * @SimpleCommand({ name: 'kick', description: 'Kick a user' })
130
+ * async kick(ctx: CommandContext) {
131
+ * await ctx.reply('User kicked!');
132
+ * }
133
+ * }
134
+ * ```
135
+ */
136
+ declare function Stoat(): ClassDecorator;
137
+ /**
138
+ * Check if a class is decorated with @Stoat
139
+ */
140
+ declare function isStoatClass(target: Function): boolean;
141
+
142
+ /**
143
+ * Stored simple command metadata from method decorator
144
+ */
145
+ interface SimpleCommandDefinition {
146
+ methodName: string;
147
+ options: SimpleCommandOptions;
148
+ }
149
+ /**
150
+ * @SimpleCommand
151
+ * Marks a method as a simple command within a @Stoat() decorated class.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * @Stoat()
156
+ * class Example {
157
+ * @SimpleCommand({ name: 'ping', description: 'Replies with Pong!' })
158
+ * async ping(ctx: CommandContext) {
159
+ * await ctx.reply('Pong!');
160
+ * }
161
+ *
162
+ * @SimpleCommand({ aliases: ['perm'], name: 'permission' })
163
+ * async permission(ctx: CommandContext) {
164
+ * await ctx.reply('Access granted');
165
+ * }
166
+ * }
167
+ * ```
168
+ */
169
+ declare function SimpleCommand(options?: SimpleCommandOptions): MethodDecorator;
170
+ /**
171
+ * Get all simple command definitions from a @Stoat class
172
+ */
173
+ declare function getSimpleCommands(target: Function): SimpleCommandDefinition[];
174
+
175
+ /**
176
+ * @Guard
177
+ * Runs before a command to check if it should execute.
178
+ * Should return true to allow execution, false to block.
179
+ * Applied on @Stoat classes to guard all contained @SimpleCommand methods.
180
+ *
181
+ * @example
182
+ * ```ts
183
+ * import { Guard, Stoat, SimpleCommand, CommandContext } from 'stoatx';
184
+ *
185
+ * // Define a guard
186
+ * class NotBot implements StoatxGuard {
187
+ * run(ctx: CommandContext): boolean {
188
+ * return !ctx.message.author.bot;
189
+ * }
190
+ *
191
+ * guardFail(ctx: CommandContext): void {
192
+ * ctx.reply("Bots cannot use this command!");
193
+ * }
194
+ * }
195
+ *
196
+ * @Stoat()
197
+ * @Guard(NotBot)
198
+ * class AdminCommands {
199
+ * @SimpleCommand({ name: 'admin', description: 'Admin only command' })
200
+ * async admin(ctx: CommandContext) {
201
+ * ctx.reply("You passed the guard check!");
202
+ * }
203
+ * }
204
+ * ```
205
+ */
206
+ declare function Guard(guardClass: Function): ClassDecorator;
207
+ /**
208
+ * Get all guards from a decorated class
209
+ */
210
+ declare function getGuards(target: Function): Function[];
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
+
266
+ /**
267
+ * Build CommandMetadata from SimpleCommandOptions
268
+ */
269
+ declare function buildSimpleCommandMetadata(options: SimpleCommandOptions, methodName: string, category?: string): CommandMetadata;
270
+
271
+ /**
272
+ * Metadata keys used by decorators
273
+ */
274
+ declare const METADATA_KEYS: {
275
+ readonly IS_STOAT_CLASS: symbol;
276
+ readonly SIMPLE_COMMANDS: symbol;
277
+ readonly GUARDS: "stoatx:command:guards";
278
+ readonly EVENTS: symbol;
279
+ };
280
+
281
+ interface AutoDiscoveryOptions {
282
+ roots?: string[];
283
+ include?: string[];
284
+ ignore?: string[];
285
+ }
286
+ /**
287
+ * Stored command entry from @Stoat/@SimpleCommand registration.
288
+ */
289
+ interface RegisteredCommand {
290
+ /** Instance of the @Stoat class */
291
+ instance: object;
292
+ /** Command metadata */
293
+ metadata: CommandMetadata;
294
+ /** Method name to call */
295
+ methodName: string;
296
+ /** The original class constructor (for guard validation) */
297
+ classConstructor: Function;
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
+ }
308
+ /**
309
+ * CommandRegistry - Scans directories and stores commands in a Map
310
+ *
311
+ * @example
312
+ * ```ts
313
+ * const registry = new CommandRegistry();
314
+ * await registry.loadFromDirectory('./src/commands');
315
+ *
316
+ * const ping = registry.get('ping');
317
+ * const allCommands = registry.getAll();
318
+ * ```
319
+ */
320
+ declare class CommandRegistry {
321
+ private static readonly DEFAULT_AUTO_DISCOVERY_IGNORES;
322
+ private readonly commands;
323
+ private readonly aliases;
324
+ private readonly registeredEvents;
325
+ private readonly extensions;
326
+ private readonly processedStoatClasses;
327
+ constructor(extensions?: string[]);
328
+ /**
329
+ * Get the number of registered commands
330
+ */
331
+ get size(): number;
332
+ /**
333
+ * Load commands from a directory using glob pattern matching
334
+ */
335
+ loadFromDirectory(directory: string): Promise<void>;
336
+ /**
337
+ * Auto-discover command files across one or more roots.
338
+ */
339
+ autoDiscover(options?: AutoDiscoveryOptions): Promise<void>;
340
+ private getDefaultAutoDiscoveryPatterns;
341
+ private isLikelyCommandModule;
342
+ /**
343
+ * Register a command instance
344
+ */
345
+ register(instance: object, metadata: CommandMetadata, classConstructor: Function, methodName: string): void;
346
+ /**
347
+ * Get a command by name or alias
348
+ */
349
+ get(name: string): RegisteredCommand | undefined;
350
+ /**
351
+ * Check if a command exists
352
+ */
353
+ has(name: string): boolean;
354
+ /**
355
+ * Get all registered commands
356
+ */
357
+ getAll(): RegisteredCommand[];
358
+ /**
359
+ * Get all command metadata
360
+ */
361
+ getAllMetadata(): CommandMetadata[];
362
+ /**
363
+ * Get all registered events
364
+ */
365
+ getEvents(): RegisteredEvent[];
366
+ /**
367
+ * Get commands grouped by category
368
+ */
369
+ getByCategory(): Map<string, RegisteredCommand[]>;
370
+ /**
371
+ * Clear all commands
372
+ */
373
+ clear(): void;
374
+ /**
375
+ * Iterate over commands
376
+ */
377
+ [Symbol.iterator](): IterableIterator<[string, RegisteredCommand]>;
378
+ /**
379
+ * Iterate over command values
380
+ */
381
+ values(): IterableIterator<RegisteredCommand>;
382
+ /**
383
+ * Iterate over command names
384
+ */
385
+ keys(): IterableIterator<string>;
386
+ /**
387
+ * Validate that all guards on a command implement the required methods
388
+ * @param commandClass
389
+ * @param commandName
390
+ * @private
391
+ */
392
+ private validateGuards;
393
+ /**
394
+ * Load commands from a single file
395
+ */
396
+ private loadFile;
397
+ private registerStoatClassCommands;
398
+ /**
399
+ * Derive category from file path relative to base directory
400
+ */
401
+ private getCategoryFromPath;
402
+ }
403
+
404
+ /**
405
+ * Client - An extended Client that integrates StoatxHandler directly
406
+ *
407
+ * @example
408
+ * ```ts
409
+ * import { Client } from 'stoatx';
410
+ *
411
+ * const client = new Client({
412
+ * prefix: '!',
413
+ * owners: ['owner-user-id'],
414
+ * });
415
+ *
416
+ * await client.initCommands();
417
+ * ```
418
+ */
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;
437
+ private readonly prefixResolver;
438
+ private readonly owners;
439
+ private readonly registry;
440
+ private readonly cooldowns;
441
+ private readonly disableMentionPrefix;
442
+ private readonly client;
443
+ constructor(options: StoatxHandlerOptions);
444
+ /**
445
+ * Initialize the handler - load all commands
446
+ */
447
+ init(): Promise<void>;
448
+ /**
449
+ * Attach registered events to the client
450
+ */
451
+ private attachEvents;
452
+ /**
453
+ * Parse a raw message into command context
454
+ */
455
+ parseMessage(rawContent: string, message: Message, meta: {
456
+ authorId: string;
457
+ channelId: string;
458
+ serverId?: string;
459
+ reply: (content: string) => Promise<void>;
460
+ }): Promise<CommandContext | null>;
461
+ /**
462
+ * Handle a message object using the configured message adapter
463
+ *
464
+ * @example
465
+ * ```ts
466
+ * // With message adapter configured
467
+ * client.on('messageCreate', (message) => {
468
+ * handler.handle(message);
469
+ * });
470
+ * ```
471
+ */
472
+ handle(message: any): Promise<boolean>;
473
+ /**
474
+ * Handle a raw message string with metadata
475
+ *
476
+ * @example
477
+ * ```ts
478
+ * // Manual usage without message adapter
479
+ * client.on('messageCreate', (message) => {
480
+ * handler.handleMessage(message.content, message, {
481
+ * authorId: message.author.id,
482
+ * channelId: message.channel.id,
483
+ * serverId: message.server?.id,
484
+ * reply: (content) => message.channel.sendMessage(content),
485
+ * });
486
+ * });
487
+ * ```
488
+ */
489
+ handleMessage(rawContent: string, message: Message, meta: {
490
+ authorId: string;
491
+ channelId: string;
492
+ serverId?: string;
493
+ reply: (content: string) => Promise<void>;
494
+ }): Promise<boolean>;
495
+ /**
496
+ * Execute a command with the given context
497
+ */
498
+ execute(ctx: CommandContext): Promise<boolean>;
499
+ /**
500
+ * Get the command registry
501
+ */
502
+ getRegistry(): CommandRegistry;
503
+ /**
504
+ * Get a command by name or alias
505
+ */
506
+ getCommand(name: string): RegisteredCommand | undefined;
507
+ /**
508
+ * Get all commands
509
+ */
510
+ getCommands(): RegisteredCommand[];
511
+ /**
512
+ * Reload all commands
513
+ */
514
+ reload(): Promise<void>;
515
+ /**
516
+ * Check if a user is an owner
517
+ */
518
+ isOwner(userId: string): boolean;
519
+ /**
520
+ * Add an owner
521
+ */
522
+ addOwner(userId: string): void;
523
+ /**
524
+ * Remove an owner
525
+ */
526
+ removeOwner(userId: string): void;
527
+ /**
528
+ * Resolve the prefix for a context
529
+ */
530
+ private resolvePrefix;
531
+ /**
532
+ * Check if user is on cooldown
533
+ */
534
+ private checkCooldown;
535
+ /**
536
+ * Get remaining cooldown time in ms
537
+ */
538
+ private getRemainingCooldown;
539
+ /**
540
+ * Set cooldown for a user
541
+ */
542
+ private setCooldown;
543
+ }
544
+
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 };