stoatx 0.2.0 → 0.2.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.
@@ -0,0 +1,467 @@
1
+ import { Client, 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;
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 MallyGuard {
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 MallyDiscoveryOptions {
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 MallyHandlerOptions {
95
+ /** The client instance */
96
+ client: Client;
97
+ /** Directory to scan for command modules (absolute path) */
98
+ commandsDir?: string;
99
+ /** Auto-discovery options used when commandsDir is not provided */
100
+ discovery?: MallyDiscoveryOptions;
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 MallyGuard {
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
+ /**
213
+ * Build CommandMetadata from SimpleCommandOptions
214
+ */
215
+ declare function buildSimpleCommandMetadata(options: SimpleCommandOptions, methodName: string, category?: string): CommandMetadata;
216
+
217
+ /**
218
+ * Metadata keys used by decorators
219
+ */
220
+ declare const METADATA_KEYS: {
221
+ readonly IS_STOAT_CLASS: symbol;
222
+ readonly SIMPLE_COMMANDS: symbol;
223
+ readonly GUARDS: "mally:command:guards";
224
+ };
225
+
226
+ interface AutoDiscoveryOptions {
227
+ roots?: string[];
228
+ include?: string[];
229
+ ignore?: string[];
230
+ }
231
+ /**
232
+ * Stored command entry from @Stoat/@SimpleCommand registration.
233
+ */
234
+ interface RegisteredCommand {
235
+ /** Instance of the @Stoat class */
236
+ instance: object;
237
+ /** Command metadata */
238
+ metadata: CommandMetadata;
239
+ /** Method name to call */
240
+ methodName: string;
241
+ /** The original class constructor (for guard validation) */
242
+ classConstructor: Function;
243
+ }
244
+ /**
245
+ * CommandRegistry - Scans directories and stores commands in a Map
246
+ *
247
+ * @example
248
+ * ```ts
249
+ * const registry = new CommandRegistry();
250
+ * await registry.loadFromDirectory('./src/commands');
251
+ *
252
+ * const ping = registry.get('ping');
253
+ * const allCommands = registry.getAll();
254
+ * ```
255
+ */
256
+ declare class CommandRegistry {
257
+ private static readonly DEFAULT_AUTO_DISCOVERY_IGNORES;
258
+ private readonly commands;
259
+ private readonly aliases;
260
+ private readonly extensions;
261
+ private readonly processedStoatClasses;
262
+ constructor(extensions?: string[]);
263
+ /**
264
+ * Get the number of registered commands
265
+ */
266
+ get size(): number;
267
+ /**
268
+ * Load commands from a directory using glob pattern matching
269
+ */
270
+ loadFromDirectory(directory: string): Promise<void>;
271
+ /**
272
+ * Auto-discover command files across one or more roots.
273
+ */
274
+ autoDiscover(options?: AutoDiscoveryOptions): Promise<void>;
275
+ private getDefaultAutoDiscoveryPatterns;
276
+ private isLikelyCommandModule;
277
+ /**
278
+ * Register a command instance
279
+ */
280
+ register(instance: object, metadata: CommandMetadata, classConstructor: Function, methodName: string): void;
281
+ /**
282
+ * Get a command by name or alias
283
+ */
284
+ get(name: string): RegisteredCommand | undefined;
285
+ /**
286
+ * Check if a command exists
287
+ */
288
+ has(name: string): boolean;
289
+ /**
290
+ * Get all registered commands
291
+ */
292
+ getAll(): RegisteredCommand[];
293
+ /**
294
+ * Get all command metadata
295
+ */
296
+ getAllMetadata(): CommandMetadata[];
297
+ /**
298
+ * Get commands grouped by category
299
+ */
300
+ getByCategory(): Map<string, RegisteredCommand[]>;
301
+ /**
302
+ * Clear all commands
303
+ */
304
+ clear(): void;
305
+ /**
306
+ * Iterate over commands
307
+ */
308
+ [Symbol.iterator](): IterableIterator<[string, RegisteredCommand]>;
309
+ /**
310
+ * Iterate over command values
311
+ */
312
+ values(): IterableIterator<RegisteredCommand>;
313
+ /**
314
+ * Iterate over command names
315
+ */
316
+ keys(): IterableIterator<string>;
317
+ /**
318
+ * Validate that all guards on a command implement the required methods
319
+ * @param commandClass
320
+ * @param commandName
321
+ * @private
322
+ */
323
+ private validateGuards;
324
+ /**
325
+ * Load commands from a single file
326
+ */
327
+ private loadFile;
328
+ private registerStoatClassCommands;
329
+ /**
330
+ * Derive category from file path relative to base directory
331
+ */
332
+ private getCategoryFromPath;
333
+ }
334
+
335
+ /**
336
+ * MallyHandler - The execution engine for commands
337
+ *
338
+ * Handles message parsing, middleware execution, and command dispatching
339
+ *
340
+ * @example
341
+ * ```ts
342
+ * import { MallyHandler } from 'stoatx';
343
+ * import { Client } from 'stoat.js';
344
+ *
345
+ * const client = new Client();
346
+ *
347
+ * const handler = new MallyHandler({
348
+ * client,
349
+ * prefix: '!',
350
+ * owners: ['owner-user-id'],
351
+ * });
352
+ *
353
+ * await handler.init();
354
+ *
355
+ * client.on('message', (message) => {
356
+ * handler.handleMessage(message);
357
+ * });
358
+ * ```
359
+ */
360
+ declare class MallyHandler {
361
+ private readonly commandsDir?;
362
+ private readonly discoveryOptions?;
363
+ private readonly prefixResolver;
364
+ private readonly owners;
365
+ private readonly registry;
366
+ private readonly cooldowns;
367
+ private readonly disableMentionPrefix;
368
+ private readonly client;
369
+ constructor(options: MallyHandlerOptions);
370
+ /**
371
+ * Initialize the handler - load all commands
372
+ */
373
+ init(): Promise<void>;
374
+ /**
375
+ * Parse a raw message into command context
376
+ */
377
+ parseMessage(rawContent: string, message: Message, meta: {
378
+ authorId: string;
379
+ channelId: string;
380
+ serverId?: string;
381
+ reply: (content: string) => Promise<void>;
382
+ }): Promise<CommandContext | null>;
383
+ /**
384
+ * Handle a message object using the configured message adapter
385
+ *
386
+ * @example
387
+ * ```ts
388
+ * // With message adapter configured
389
+ * client.on('messageCreate', (message) => {
390
+ * handler.handle(message);
391
+ * });
392
+ * ```
393
+ */
394
+ handle(message: any): Promise<boolean>;
395
+ /**
396
+ * Handle a raw message string with metadata
397
+ *
398
+ * @example
399
+ * ```ts
400
+ * // Manual usage without message adapter
401
+ * client.on('messageCreate', (message) => {
402
+ * handler.handleMessage(message.content, message, {
403
+ * authorId: message.author.id,
404
+ * channelId: message.channel.id,
405
+ * serverId: message.server?.id,
406
+ * reply: (content) => message.channel.sendMessage(content),
407
+ * });
408
+ * });
409
+ * ```
410
+ */
411
+ handleMessage(rawContent: string, message: Message, meta: {
412
+ authorId: string;
413
+ channelId: string;
414
+ serverId?: string;
415
+ reply: (content: string) => Promise<void>;
416
+ }): Promise<boolean>;
417
+ /**
418
+ * Execute a command with the given context
419
+ */
420
+ execute(ctx: CommandContext): Promise<boolean>;
421
+ /**
422
+ * Get the command registry
423
+ */
424
+ getRegistry(): CommandRegistry;
425
+ /**
426
+ * Get a command by name or alias
427
+ */
428
+ getCommand(name: string): RegisteredCommand | undefined;
429
+ /**
430
+ * Get all commands
431
+ */
432
+ getCommands(): RegisteredCommand[];
433
+ /**
434
+ * Reload all commands
435
+ */
436
+ reload(): Promise<void>;
437
+ /**
438
+ * Check if a user is an owner
439
+ */
440
+ isOwner(userId: string): boolean;
441
+ /**
442
+ * Add an owner
443
+ */
444
+ addOwner(userId: string): void;
445
+ /**
446
+ * Remove an owner
447
+ */
448
+ removeOwner(userId: string): void;
449
+ /**
450
+ * Resolve the prefix for a context
451
+ */
452
+ private resolvePrefix;
453
+ /**
454
+ * Check if user is on cooldown
455
+ */
456
+ private checkCooldown;
457
+ /**
458
+ * Get remaining cooldown time in ms
459
+ */
460
+ private getRemainingCooldown;
461
+ /**
462
+ * Set cooldown for a user
463
+ */
464
+ private setCooldown;
465
+ }
466
+
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 };