@whanext/core 0.7.0 → 0.10.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/CHANGELOG.md CHANGED
@@ -2,6 +2,54 @@
2
2
 
3
3
  Todas as mudanças relevantes do projeto serão registradas neste arquivo.
4
4
 
5
+ ## 0.10.0
6
+
7
+ ### Adicionado
8
+
9
+ - `CommandContext`, uma interação enriquecida que continua compatível com o modelo `Message` legado.
10
+ - `ctx.reply()`, `ctx.defer()`, `ctx.edit()`, `ctx.react()`, `ctx.unreact()`, `ctx.delete()` e respostas com exclusão programada.
11
+ - `option.string`, `number`, `boolean`, `enum`, `user` e `duration`, com inferência de tipos, obrigatoriedade e validações.
12
+ - `defineCommandGroup()` e `defineSubcommand()` para comandos como `&grupo abrir` e `&grupo fechar`.
13
+ - Guards reutilizáveis para grupo, privado, admin, bot admin e regras customizadas.
14
+ - Middleware global e por comando, além de hooks `beforeExecute`, `afterExecute` e `onError`.
15
+ - Cooldown por usuário, chat, usuário+chat ou global, com limpeza automática de entradas expiradas.
16
+ - Controle de concorrência com estratégias `parallel`, `reject`, `queue` e `replace`.
17
+ - `app.commands`, catálogo consultável, categorias, busca e help gerado por metadados.
18
+ - Localizações de nome, aliases e descrição sem duplicar definições.
19
+ - Códigos de erro `COMMAND_COOLDOWN` e `COMMAND_BUSY`.
20
+
21
+ ### Compatibilidade
22
+
23
+ - `app.router()` continua retornando o mesmo router disponível em `app.commands`.
24
+ - `onlyGroup`, `onlyPrivate`, `onlyAdmin` e `botMustBeAdmin` continuam suportados.
25
+ - Comandos legados com `execute(message, args)` continuam funcionando sem alteração.
26
+ - `loadCommands()` agora também reconhece grupos de comandos.
27
+
28
+ ## 0.9.0
29
+
30
+ ### Alterado
31
+
32
+ - Baileys atualizado de `7.0.0-rc13` para `7.0.0-rc14`.
33
+ - O provider agora fornece `cachedGroupMetadata` ao Baileys, evitando consultas redundantes ao WhatsApp no fan-out de mensagens em grupos.
34
+ - O cache interno usado pelo envio possui TTL, LRU limitado, deduplicação de buscas concorrentes e proteção contra a reinserção de resultados invalidados durante uma busca.
35
+ - `MemoryCache` agora promove corretamente chaves sobrescritas na ordem LRU.
36
+
37
+ ### Adicionado
38
+
39
+ - `MemoryCache.stats()` para observar hits, misses, sets, evictions e expirations.
40
+ - `MemoryCache.prune()` para remover entradas expiradas de forma explícita.
41
+
42
+ ### Compatibilidade
43
+
44
+ - Nenhuma alteração é necessária nos comandos ou na API pública existente.
45
+ - Sessões criadas pela v0.8 continuam compatíveis; o auth state multifile do Baileys 7 já persiste as chaves de LID, device list e TC token exigidas pela migração.
46
+
47
+ ## 0.8.0
48
+
49
+ ### Adicionado
50
+
51
+ - `app.media.sticker(chatId, { sticker })` para enviar stickers WebP estáticos ou animados a partir de bytes, URL ou arquivo local.
52
+
5
53
  ## 0.7.0
6
54
 
7
55
  ### Adicionado
package/README.md CHANGED
@@ -290,6 +290,10 @@ await app.media.audio(chatId, {
290
290
  voice: true,
291
291
  });
292
292
 
293
+ await app.media.sticker(chatId, {
294
+ sticker: { path: './sticker.webp' },
295
+ });
296
+
293
297
  const downloaded = await app.media.download(message);
294
298
 
295
299
  await writeFile(`./downloads/${downloaded.fileName ?? message.id}`, downloaded.data);
@@ -297,7 +301,7 @@ await writeFile(`./downloads/${downloaded.fileName ?? message.id}`, downloaded.d
297
301
 
298
302
  `download()` aceita a `Message` recebida ou a sua `MessageKey` e devolve o buffer junto dos metadados normalizados. A mídia deve ser baixada enquanto a mensagem ainda está no cache da instância; o provider tenta renovar a URL de mídia automaticamente quando necessário.
299
303
 
300
- Texto, imagem e vídeo aceitam `User` diretamente em `mentions`.
304
+ Stickers aceitam `Uint8Array`, URL ou caminho local e devem estar em WebP, inclusive para animações. Texto, imagem e vídeo aceitam `User` diretamente em `mentions`.
301
305
 
302
306
  ## Grupos e membros
303
307
 
@@ -397,6 +401,137 @@ const app = await create({
397
401
 
398
402
  ## Comandos
399
403
 
404
+ O sistema moderno usa um contexto semelhante às interactions do Discord. `ctx` contém a mensagem normalizada, usuário, chat, grupo, services, options, sinal de cancelamento e helpers de resposta.
405
+
406
+ ```ts
407
+ import {
408
+ defineCommand,
409
+ guards,
410
+ option,
411
+ } from '@whanext/core';
412
+
413
+ app.commands.command(
414
+ defineCommand({
415
+ name: 'ban',
416
+ aliases: ['banir'],
417
+ description: 'Remove um membro do grupo.',
418
+ category: 'moderação',
419
+
420
+ guards: [
421
+ guards.group(),
422
+ guards.userAdmin(),
423
+ guards.botAdmin(),
424
+ ],
425
+
426
+ options: {
427
+ user: option.user({
428
+ description: 'Usuário que será removido.',
429
+ required: true,
430
+ }),
431
+ reason: option.string({
432
+ description: 'Motivo da remoção.',
433
+ rest: true,
434
+ }),
435
+ },
436
+
437
+ cooldown: {
438
+ durationMs: 5_000,
439
+ scope: 'user-chat',
440
+ },
441
+
442
+ concurrency: {
443
+ scope: 'chat',
444
+ max: 1,
445
+ strategy: 'queue',
446
+ },
447
+
448
+ async execute(ctx) {
449
+ const user = ctx.options.user('user');
450
+ const reason = ctx.options.string('reason') ?? 'Não informado';
451
+ const deferred = await ctx.defer('⏳ _Processando banimento..._');
452
+ const result = await ctx.members.remove(ctx.chatId, user);
453
+
454
+ await deferred.edit(
455
+ result.changed
456
+ ? `🔨 *Usuário banido*\n\n${user.mention} foi removido.\n• *Motivo:* ${reason}`
457
+ : `⚠️ O usuário não está mais no grupo.`,
458
+ );
459
+ },
460
+ }),
461
+ );
462
+ ```
463
+
464
+ ### Subcomandos
465
+
466
+ ```ts
467
+ import {
468
+ defineCommandGroup,
469
+ defineSubcommand,
470
+ guards,
471
+ } from '@whanext/core';
472
+
473
+ app.commands.command(defineCommandGroup({
474
+ name: 'grupo',
475
+ aliases: ['group'],
476
+ description: 'Gerencia o grupo.',
477
+ category: 'grupos',
478
+ guards: [guards.group(), guards.userAdmin(), guards.botAdmin()],
479
+
480
+ subcommands: [
481
+ defineSubcommand({
482
+ name: 'abrir',
483
+ aliases: ['open'],
484
+ description: 'Abre o grupo.',
485
+ async execute(ctx) {
486
+ await ctx.groups.open(ctx.chatId);
487
+ await ctx.reply('🔓 *Grupo aberto*');
488
+ },
489
+ }),
490
+ defineSubcommand({
491
+ name: 'fechar',
492
+ aliases: ['close'],
493
+ description: 'Fecha o grupo.',
494
+ async execute(ctx) {
495
+ await ctx.groups.close(ctx.chatId);
496
+ await ctx.reply('🔒 *Grupo fechado*');
497
+ },
498
+ }),
499
+ ],
500
+ }));
501
+ ```
502
+
503
+ Isso aceita `&grupo abrir`, `&group open`, `&grupo fechar` e `&group close` sem duplicar lógica.
504
+
505
+ ### Middleware e erros
506
+
507
+ ```ts
508
+ app.commands.use(async (ctx, next) => {
509
+ const startedAt = performance.now();
510
+ await next();
511
+ app.logger.debug('Command completed', {
512
+ command: ctx.command.path.join(' '),
513
+ durationMs: performance.now() - startedAt,
514
+ });
515
+ });
516
+
517
+ app.commands.onError(async (ctx, error) => {
518
+ if (error.code === 'COMMAND_COOLDOWN') {
519
+ await ctx.reply('⏱️ Aguarde um pouco antes de usar novamente.');
520
+ return;
521
+ }
522
+
523
+ await ctx.reply('⚠️ *Não foi possível concluir*');
524
+ });
525
+ ```
526
+
527
+ `app.commands.catalog()`, `categories()`, `find()`, `has()` e `values()` expõem a coleção registrada. `app.commands.help(ctx, { category: 'moderação' })` gera a ajuda usando descrição, usage, opções e visibilidade dos comandos.
528
+
529
+ Detalhes completos estão em [Comandos modernos](./docs/commands-v0.10.md).
530
+
531
+ ### API legada
532
+
533
+ Comandos existentes continuam válidos:
534
+
400
535
  ```ts
401
536
  app.router().command(
402
537
  defineCommand({
@@ -520,12 +655,33 @@ const app = await create({
520
655
 
521
656
  O cache padrão vive na instância do app, usa LRU limitado e elimina entradas expiradas durante as leituras. Consultas simultâneas dos mesmos metadados são agrupadas em uma única chamada ao WhatsApp. Eventos e mutações de grupo invalidam automaticamente entradas relacionadas.
522
657
 
658
+ Os mesmos metadados também alimentam internamente o `cachedGroupMetadata` do Baileys. Em grupos já aquecidos, isso remove a consulta de metadados do caminho de envio; mensagens continuam aguardando somente criptografia, upload quando houver mídia e confirmação da rede.
659
+
660
+ Para observar um `MemoryCache` criado diretamente:
661
+
662
+ ```ts
663
+ import { MemoryCache } from '@whanext/core';
664
+
665
+ const cache = new MemoryCache({ maxEntries: 5_000 });
666
+
667
+ console.log(cache.stats());
668
+ cache.prune();
669
+ ```
670
+
671
+ `stats()` informa `size`, `maxEntries`, `hits`, `misses`, `sets`, `evictions` e `expirations`.
672
+
673
+ Para uma única instância, o cache padrão é suficiente mesmo com muitos grupos, desde que `memoryMaxEntries` seja dimensionado. Em várias instâncias/processos, use um `CacheStore` distribuído para o cache público; cada conexão mantém ainda um L1 local limitado para o caminho criptográfico do Baileys. Não compartilhe uma mesma sessão ativa entre processos.
674
+
523
675
  O cache interno de mensagens mantém até 1.000 mensagens por padrão para replies, reenvios do provider e downloads de mídia. Ajuste quando necessário:
524
676
 
525
677
  ```ts
526
678
  const app = await create({ messageCacheSize: 2_000 });
527
679
  ```
528
680
 
681
+ Não há delay artificial no envio. Presença é enviada diretamente e previews de alta qualidade permanecem desativados no provider; mídias ainda dependem do tempo de leitura, criptografia e upload ao WhatsApp.
682
+
683
+ Para limites, dimensionamento e decisões de produção, consulte [Desempenho e escala](./docs/performance-and-scale.md).
684
+
529
685
  ## Presença
530
686
 
531
687
  ```ts
@@ -567,7 +723,7 @@ Todos os erros públicos usam `WhaNextError` e códigos estáveis. O logger regi
567
723
  | Domínio | Responsabilidade |
568
724
  | --- | --- |
569
725
  | `app.message` | Envio, reply, edição, exclusão, texto e reações |
570
- | `app.media` | Envio e download de mídia |
726
+ | `app.media` | Envio, download e stickers |
571
727
  | `app.group` | Estado, convite, pin e metadados |
572
728
  | `app.member` | Remoção, promoção e rebaixamento |
573
729
  | `app.user` | Criação e resolução de usuários |
package/SECURITY.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  | Versão | Suporte |
6
6
  | --- | --- |
7
- | 0.7.x | Sim |
7
+ | 0.8.x | Sim |
8
8
  | anteriores | Não |
9
9
 
10
10
  ## Reportando uma vulnerabilidade
package/dist/index.d.ts CHANGED
@@ -142,34 +142,10 @@ interface AudioContent {
142
142
  mimetype?: string;
143
143
  voice?: boolean;
144
144
  }
145
- type MessageContent = TextContent | ImageContent | VideoContent | AudioContent;
146
-
147
- interface CommandDefinition {
148
- name: string;
149
- description: string;
150
- aliases?: readonly string[];
151
- onlyGroup?: boolean;
152
- onlyPrivate?: boolean;
153
- onlyAdmin?: boolean;
154
- botMustBeAdmin?: boolean;
155
- execute(message: Message, args: ArgsParser): void | Promise<void>;
145
+ interface StickerContent {
146
+ sticker: MediaSource;
156
147
  }
157
- declare function defineCommand<const Command extends CommandDefinition>(command: Command): Command;
158
- declare function defineCommands<const Commands extends readonly CommandDefinition[]>(...commands: Commands): Commands;
159
-
160
- type WhaNextErrorCode = 'AUTH_INVALID_PHONE' | 'AUTH_EXPIRED' | 'CONNECTION_CLOSED' | 'CONNECTION_FAILED' | 'GROUP_NOT_FOUND' | 'MEMBER_NOT_FOUND' | 'BOT_NOT_ADMIN' | 'MESSAGE_NOT_FOUND' | 'MEDIA_NOT_AVAILABLE' | 'MUTE_DISABLED' | 'STORAGE_ERROR' | 'ARGUMENT_MISSING' | 'ARGUMENT_INVALID' | 'COMMAND_NOT_ALLOWED' | 'COMMAND_LOAD_FAILED' | 'PROVIDER_ERROR' | 'UNKNOWN_ERROR';
161
- interface WhaNextErrorOptions {
162
- cause?: unknown;
163
- context?: Readonly<Record<string, unknown>>;
164
- recoverable?: boolean;
165
- }
166
- declare class WhaNextError extends Error {
167
- readonly code: WhaNextErrorCode;
168
- readonly context: Readonly<Record<string, unknown>>;
169
- readonly recoverable: boolean;
170
- constructor(code: WhaNextErrorCode, message: string, options?: WhaNextErrorOptions);
171
- }
172
- declare function toWhaNextError(error: unknown, context?: Readonly<Record<string, unknown>>): WhaNextError;
148
+ type MessageContent = TextContent | ImageContent | VideoContent | AudioContent | StickerContent;
173
149
 
174
150
  type GroupAccess = 'open' | 'closed';
175
151
  type GroupRole = 'member' | 'admin' | 'owner';
@@ -283,47 +259,75 @@ declare class GroupService {
283
259
  invalidate(groupId: string): Promise<void>;
284
260
  }
285
261
 
286
- interface RouterOptions {
287
- prefix?: string;
288
- onError?: (error: WhaNextError, message: Message) => void | Promise<void>;
289
- }
290
- declare class CommandRouter {
262
+ declare class UserService {
291
263
  #private;
292
- constructor(group: GroupService, options?: RouterOptions);
293
- command(definition: CommandDefinition): this;
294
- dispatch(message: Message): Promise<boolean>;
264
+ constructor(group: GroupService);
265
+ resolve(message: Message, args: ArgsParser): Promise<User>;
266
+ from(identity: string): User;
295
267
  }
296
268
 
297
- type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
298
- type LogFormat = 'pretty' | 'json';
299
- type LogContext = Readonly<Record<string, unknown>>;
300
- interface LogEntry {
301
- timestamp: string;
302
- level: Exclude<LogLevel, 'silent'>;
303
- scope: string;
304
- message: string;
305
- context: LogContext;
269
+ interface BaseOption<TKind extends string> {
270
+ kind: TKind;
271
+ description: string;
272
+ required?: boolean;
306
273
  }
307
- type LogWriter = (entry: LogEntry) => unknown;
308
- interface LoggerOptions {
309
- level?: LogLevel;
310
- format?: LogFormat;
311
- writer?: LogWriter;
312
- scope?: string;
313
- redact?: readonly string[];
274
+ interface StringOption extends BaseOption<'string'> {
275
+ rest?: boolean;
276
+ minLength?: number;
277
+ maxLength?: number;
314
278
  }
315
- type LoggerConfig = LogLevel | LoggerOptions;
316
- declare class Logger {
279
+ interface NumberOption extends BaseOption<'number'> {
280
+ min?: number;
281
+ max?: number;
282
+ }
283
+ interface BooleanOption extends BaseOption<'boolean'> {
284
+ }
285
+ interface UserOption extends BaseOption<'user'> {
286
+ }
287
+ interface DurationOption extends BaseOption<'duration'> {
288
+ }
289
+ interface EnumOption<Values extends readonly string[] = readonly string[]> extends BaseOption<'enum'> {
290
+ values: Values;
291
+ }
292
+ type CommandOptionDefinition = StringOption | NumberOption | BooleanOption | UserOption | DurationOption | EnumOption;
293
+ type CommandOptionSchema = Readonly<Record<string, CommandOptionDefinition>>;
294
+ type RawOptionValue<Definition extends CommandOptionDefinition> = Definition extends StringOption ? string : Definition extends NumberOption ? number : Definition extends BooleanOption ? boolean : Definition extends UserOption ? User : Definition extends DurationOption ? number | undefined : Definition extends EnumOption<infer Values> ? Values[number] : never;
295
+ type CommandOptionValue<Definition extends CommandOptionDefinition> = Definition['required'] extends true ? RawOptionValue<Definition> : RawOptionValue<Definition> | undefined;
296
+ type CommandOptionValues<Schema extends CommandOptionSchema> = {
297
+ readonly [Name in keyof Schema]: CommandOptionValue<Schema[Name]>;
298
+ };
299
+ declare const option: {
300
+ string<const Definition extends Omit<StringOption, "kind">>(definition: Definition): {
301
+ kind: "string";
302
+ } & Definition;
303
+ number<const Definition extends Omit<NumberOption, "kind">>(definition: Definition): {
304
+ kind: "number";
305
+ } & Definition;
306
+ boolean<const Definition extends Omit<BooleanOption, "kind">>(definition: Definition): {
307
+ kind: "boolean";
308
+ } & Definition;
309
+ user<const Definition extends Omit<UserOption, "kind">>(definition: Definition): {
310
+ kind: "user";
311
+ } & Definition;
312
+ duration<const Definition extends Omit<DurationOption, "kind">>(definition: Definition): {
313
+ kind: "duration";
314
+ } & Definition;
315
+ enum<const Values extends readonly string[], const Definition extends Omit<EnumOption<Values>, "kind" | "values">>(values: Values, definition: Definition): {
316
+ kind: "enum";
317
+ values: Values;
318
+ } & Definition;
319
+ };
320
+ declare class ParsedCommandOptions<Schema extends CommandOptionSchema = CommandOptionSchema> {
317
321
  #private;
318
- constructor(config?: LoggerConfig);
319
- get level(): LogLevel;
320
- setLevel(level: LogLevel): this;
321
- isEnabled(level: Exclude<LogLevel, 'silent'>): boolean;
322
- child(scope: string): Logger;
323
- debug(message: string, context?: LogContext): void;
324
- info(message: string, context?: LogContext): void;
325
- warn(message: string, context?: LogContext): void;
326
- error(message: string, context?: LogContext): void;
322
+ constructor(values: CommandOptionValues<Schema>);
323
+ get<Name extends keyof Schema>(name: Name): CommandOptionValue<Schema[Name]>;
324
+ string<Name extends keyof Schema>(name: Name): CommandOptionValue<Schema[Name]>;
325
+ number<Name extends keyof Schema>(name: Name): CommandOptionValue<Schema[Name]>;
326
+ boolean<Name extends keyof Schema>(name: Name): CommandOptionValue<Schema[Name]>;
327
+ user<Name extends keyof Schema>(name: Name): CommandOptionValue<Schema[Name]>;
328
+ duration<Name extends keyof Schema>(name: Name): CommandOptionValue<Schema[Name]>;
329
+ enum<Name extends keyof Schema>(name: Name): CommandOptionValue<Schema[Name]>;
330
+ toJSON(): CommandOptionValues<Schema>;
327
331
  }
328
332
 
329
333
  interface StoredMute {
@@ -410,6 +414,7 @@ declare class MediaService {
410
414
  image(chatId: string, content: ImageContent): Promise<SentMessage>;
411
415
  video(chatId: string, content: VideoContent): Promise<SentMessage>;
412
416
  audio(chatId: string, content: AudioContent): Promise<SentMessage>;
417
+ sticker(chatId: string, content: StickerContent): Promise<SentMessage>;
413
418
  download(message: Message | MessageKey): Promise<DownloadedMedia>;
414
419
  }
415
420
 
@@ -433,11 +438,215 @@ declare class MessageService {
433
438
  text(chatId: string, text: string, mentions?: MentionTarget[]): Promise<SentMessage>;
434
439
  }
435
440
 
436
- declare class UserService {
441
+ interface CommandRuntimeServices {
442
+ messages: MessageService;
443
+ media: MediaService;
444
+ groups: GroupService;
445
+ members: MemberService;
446
+ chats: ChatService;
447
+ users: UserService;
448
+ mute: MuteService;
449
+ }
450
+ interface CommandChatContext {
451
+ id: string;
452
+ isGroup: boolean;
453
+ }
454
+ interface CommandGroupContext {
455
+ id: string;
456
+ metadata(refresh?: boolean): ReturnType<GroupService['metadata']>;
457
+ isUserAdmin(): Promise<boolean>;
458
+ isBotAdmin(): Promise<boolean>;
459
+ }
460
+ interface ReplyOptions {
461
+ deleteAfterMs?: number;
462
+ }
463
+ interface CommandContext<Schema extends CommandOptionSchema = CommandOptionSchema> extends Message {
464
+ readonly message: Message;
465
+ readonly user: User;
466
+ readonly chat: CommandChatContext;
467
+ readonly group: CommandGroupContext | undefined;
468
+ readonly command: RegisteredCommand;
469
+ readonly options: ParsedCommandOptions<Schema>;
470
+ readonly args: ArgsParser;
471
+ readonly locale: string | undefined;
472
+ readonly signal: AbortSignal;
473
+ readonly client: CommandRuntimeServices;
474
+ readonly messages: MessageService;
475
+ readonly mediaService: MediaService;
476
+ readonly groups: GroupService;
477
+ readonly members: MemberService;
478
+ readonly chats: ChatService;
479
+ readonly users: UserService;
480
+ readonly muteService: MuteService;
481
+ reply(content: string | MessageContent, options?: ReplyOptions): Promise<SentMessage>;
482
+ defer(content?: string | MessageContent): Promise<DeferredReply>;
483
+ edit(content: string): Promise<SentMessage>;
484
+ react(emoji: string): Promise<SentMessage>;
485
+ unreact(): Promise<SentMessage>;
486
+ delete(): Promise<void>;
487
+ deleteReply(options?: ReplyOptions): Promise<void>;
488
+ }
489
+ declare class DeferredReply {
437
490
  #private;
438
- constructor(group: GroupService);
439
- resolve(message: Message, args: ArgsParser): Promise<User>;
440
- from(identity: string): User;
491
+ constructor(messages: MessageService, message: SentMessage, onEdit: (message: SentMessage) => void);
492
+ edit(content: string): Promise<SentMessage>;
493
+ delete(): Promise<void>;
494
+ }
495
+
496
+ type WhaNextErrorCode = 'AUTH_INVALID_PHONE' | 'AUTH_EXPIRED' | 'CONNECTION_CLOSED' | 'CONNECTION_FAILED' | 'GROUP_NOT_FOUND' | 'MEMBER_NOT_FOUND' | 'BOT_NOT_ADMIN' | 'MESSAGE_NOT_FOUND' | 'MEDIA_NOT_AVAILABLE' | 'MUTE_DISABLED' | 'STORAGE_ERROR' | 'ARGUMENT_MISSING' | 'ARGUMENT_INVALID' | 'COMMAND_NOT_ALLOWED' | 'COMMAND_COOLDOWN' | 'COMMAND_BUSY' | 'COMMAND_LOAD_FAILED' | 'PROVIDER_ERROR' | 'UNKNOWN_ERROR';
497
+ interface WhaNextErrorOptions {
498
+ cause?: unknown;
499
+ context?: Readonly<Record<string, unknown>>;
500
+ recoverable?: boolean;
501
+ }
502
+ declare class WhaNextError extends Error {
503
+ readonly code: WhaNextErrorCode;
504
+ readonly context: Readonly<Record<string, unknown>>;
505
+ readonly recoverable: boolean;
506
+ constructor(code: WhaNextErrorCode, message: string, options?: WhaNextErrorOptions);
507
+ }
508
+ declare function toWhaNextError(error: unknown, context?: Readonly<Record<string, unknown>>): WhaNextError;
509
+
510
+ interface GuardResult {
511
+ allowed: boolean;
512
+ code?: WhaNextErrorCode;
513
+ message?: string;
514
+ }
515
+ type CommandGuard = (context: CommandContext) => boolean | void | GuardResult | Promise<boolean | void | GuardResult>;
516
+ declare const guards: {
517
+ group(): CommandGuard;
518
+ private(): CommandGuard;
519
+ userAdmin(): CommandGuard;
520
+ botAdmin(): CommandGuard;
521
+ botEnabled(check: (context: CommandContext) => boolean | Promise<boolean>): CommandGuard;
522
+ custom(guard: CommandGuard): CommandGuard;
523
+ };
524
+
525
+ type CommandScope = 'global' | 'user' | 'chat' | 'user-chat' | 'user-group';
526
+ type ConcurrencyStrategy = 'parallel' | 'reject' | 'queue' | 'replace';
527
+ interface CommandCooldown {
528
+ durationMs: number;
529
+ scope?: CommandScope;
530
+ }
531
+ interface CommandConcurrency {
532
+ max?: number;
533
+ scope?: CommandScope;
534
+ strategy?: ConcurrencyStrategy;
535
+ }
536
+ interface CommandLocalization {
537
+ name?: string;
538
+ aliases?: readonly string[];
539
+ description?: string;
540
+ }
541
+ type CommandMiddleware = (context: CommandContext, next: () => Promise<void>) => void | Promise<void>;
542
+ interface CommandHooks {
543
+ beforeExecute?: (context: CommandContext) => void | Promise<void>;
544
+ afterExecute?: (context: CommandContext) => void | Promise<void>;
545
+ onError?: (context: CommandContext, error: Error) => void | Promise<void>;
546
+ }
547
+ interface CommandMetadata {
548
+ name: string;
549
+ description: string;
550
+ aliases?: readonly string[];
551
+ category?: string;
552
+ usage?: string;
553
+ examples?: readonly string[];
554
+ hidden?: boolean;
555
+ localizations?: Readonly<Record<string, CommandLocalization>>;
556
+ guards?: readonly CommandGuard[];
557
+ middleware?: readonly CommandMiddleware[];
558
+ cooldown?: CommandCooldown;
559
+ concurrency?: CommandConcurrency;
560
+ hooks?: CommandHooks;
561
+ onlyGroup?: boolean;
562
+ onlyPrivate?: boolean;
563
+ onlyAdmin?: boolean;
564
+ botMustBeAdmin?: boolean;
565
+ }
566
+ interface ExecutableCommandDefinition<Schema extends CommandOptionSchema = CommandOptionSchema> extends CommandMetadata {
567
+ options?: Schema;
568
+ execute(context: CommandContext<Schema>, args: ArgsParser): void | Promise<void>;
569
+ }
570
+ interface CommandGroupDefinition extends CommandMetadata {
571
+ subcommands: readonly CommandDefinition[];
572
+ }
573
+ type CommandDefinition<Schema extends CommandOptionSchema = CommandOptionSchema> = ExecutableCommandDefinition<Schema> | CommandGroupDefinition;
574
+ interface RegisteredCommand {
575
+ definition: ExecutableCommandDefinition;
576
+ root: CommandDefinition;
577
+ path: readonly string[];
578
+ aliases: readonly string[];
579
+ category: string;
580
+ }
581
+ declare function defineCommand<const Schema extends CommandOptionSchema = CommandOptionSchema>(command: ExecutableCommandDefinition<Schema>): ExecutableCommandDefinition<Schema>;
582
+ declare function defineSubcommand<const Schema extends CommandOptionSchema = CommandOptionSchema>(command: ExecutableCommandDefinition<Schema>): ExecutableCommandDefinition<Schema>;
583
+ declare function defineCommandGroup<const Group extends CommandGroupDefinition>(group: Group): Group;
584
+ declare function defineCommands<const Commands extends readonly CommandDefinition[]>(...commands: Commands): Commands;
585
+ declare function isCommandGroup(definition: CommandDefinition): definition is CommandGroupDefinition;
586
+
587
+ interface RouterOptions {
588
+ prefix?: string;
589
+ onError?: (error: WhaNextError, message: Message) => void | Promise<void>;
590
+ onCommandError?: CommandErrorHandler;
591
+ beforeExecute?: (context: CommandContext) => void | Promise<void>;
592
+ afterExecute?: (context: CommandContext) => void | Promise<void>;
593
+ }
594
+ interface CommandCatalogOptions {
595
+ category?: string;
596
+ includeHidden?: boolean;
597
+ }
598
+ interface CommandHelpOptions extends CommandCatalogOptions {
599
+ title?: string;
600
+ }
601
+ type CommandErrorHandler = (context: CommandContext, error: WhaNextError) => void | Promise<void>;
602
+ declare class CommandRouter {
603
+ #private;
604
+ constructor(services: CommandRuntimeServices, options?: RouterOptions);
605
+ constructor(group: GroupService, options?: RouterOptions);
606
+ get prefix(): string;
607
+ get size(): number;
608
+ command(definition: CommandDefinition): this;
609
+ use(middleware: CommandMiddleware): this;
610
+ onError(handler: CommandErrorHandler): () => void;
611
+ catalog(options?: CommandCatalogOptions): readonly RegisteredCommand[];
612
+ categories(): readonly string[];
613
+ has(path: string): boolean;
614
+ values(): readonly RegisteredCommand[];
615
+ find(path: string): RegisteredCommand | undefined;
616
+ help(context: CommandContext, options?: CommandHelpOptions): Promise<SentMessage>;
617
+ dispatch(message: Message): Promise<boolean>;
618
+ }
619
+
620
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
621
+ type LogFormat = 'pretty' | 'json';
622
+ type LogContext = Readonly<Record<string, unknown>>;
623
+ interface LogEntry {
624
+ timestamp: string;
625
+ level: Exclude<LogLevel, 'silent'>;
626
+ scope: string;
627
+ message: string;
628
+ context: LogContext;
629
+ }
630
+ type LogWriter = (entry: LogEntry) => unknown;
631
+ interface LoggerOptions {
632
+ level?: LogLevel;
633
+ format?: LogFormat;
634
+ writer?: LogWriter;
635
+ scope?: string;
636
+ redact?: readonly string[];
637
+ }
638
+ type LoggerConfig = LogLevel | LoggerOptions;
639
+ declare class Logger {
640
+ #private;
641
+ constructor(config?: LoggerConfig);
642
+ get level(): LogLevel;
643
+ setLevel(level: LogLevel): this;
644
+ isEnabled(level: Exclude<LogLevel, 'silent'>): boolean;
645
+ child(scope: string): Logger;
646
+ debug(message: string, context?: LogContext): void;
647
+ info(message: string, context?: LogContext): void;
648
+ warn(message: string, context?: LogContext): void;
649
+ error(message: string, context?: LogContext): void;
441
650
  }
442
651
 
443
652
  interface AppEvents {
@@ -480,6 +689,7 @@ declare class WhaNextApp {
480
689
  readonly user: UserService;
481
690
  readonly mute: MuteService;
482
691
  readonly logger: Logger;
692
+ readonly commands: CommandRouter;
483
693
  constructor(provider: WhatsAppProvider, options?: WhaNextAppOptions, logger?: Logger);
484
694
  get state(): ConnectionUpdate['state'];
485
695
  get isReady(): boolean;
@@ -517,6 +727,15 @@ interface CreateOptions {
517
727
  }
518
728
  declare function create(options?: CreateOptions): Promise<WhaNextApp>;
519
729
 
730
+ interface MemoryCacheStats {
731
+ size: number;
732
+ maxEntries: number;
733
+ hits: number;
734
+ misses: number;
735
+ sets: number;
736
+ evictions: number;
737
+ expirations: number;
738
+ }
520
739
  declare class MemoryCache implements CacheStore {
521
740
  #private;
522
741
  constructor(options?: {
@@ -526,6 +745,8 @@ declare class MemoryCache implements CacheStore {
526
745
  set<T>(key: string, value: T, ttlMs?: number): Promise<void>;
527
746
  delete(key: string): Promise<void>;
528
747
  clear(): Promise<void>;
748
+ prune(now?: number): number;
749
+ stats(): Readonly<MemoryCacheStats>;
529
750
  }
530
751
 
531
752
  interface CommandRegistrar {
@@ -556,4 +777,4 @@ declare class SqliteMuteStore implements MuteStore {
556
777
  close(): void;
557
778
  }
558
779
 
559
- export { type AddMuteOptions, type AddMuteResult, type AppEvents, type AppHealth, type AppHealthStatus, ArgsParser, type AudioContent, Browser, type CacheOptions, type CacheStore, type CallEvent, type CallStatus, type ChangeResult, type CommandDefinition, type CommandRegistrar, CommandRouter, type ConnectionState, type ConnectionUpdate, type CreateOptions, type DownloadedMedia, type GroupAccess, type GroupAddressingMode, type GroupParticipant, type GroupParticipantAction, type GroupParticipantsChanged, type GroupRole, type GroupSnapshot, type ImageContent, type InviteResult, type LoadCommandsOptions, type LoadCommandsResult, type LoadedCommand, type LogContext, type LogEntry, type LogFormat, type LogLevel, type LogWriter, Logger, type LoggerConfig, type LoggerOptions, type LoginOptions, type MediaKind, type MediaSource, type MemberActionState, MemoryCache, type MentionTarget, type Message, type MessageContent, type MessageKey, type MessageMedia, type MuteChangeResult, type MuteEnforcement, type MuteOptions, type MuteRecord, MuteService, type MuteStore, type ParticipantUpdateResult, type PresenceState, type QuotedMessage, type ReconnectOptions, type RemoveMuteResult, type RouterOptions, type SentMessage, SqliteMuteStore, type StoredMute, type TextContent, User, type UserData, type VideoContent, WhaNextApp, WhaNextError, type WhaNextErrorCode, type WhatsAppProvider, create, defineCommand, defineCommands, loadCommands, toWhaNextError };
780
+ export { type AddMuteOptions, type AddMuteResult, type AppEvents, type AppHealth, type AppHealthStatus, ArgsParser, type AudioContent, type BooleanOption, Browser, type CacheOptions, type CacheStore, type CallEvent, type CallStatus, type ChangeResult, type CommandCatalogOptions, type CommandChatContext, type CommandConcurrency, type CommandContext, type CommandCooldown, type CommandDefinition, type CommandErrorHandler, type CommandGroupContext, type CommandGroupDefinition, type CommandGuard, type CommandHelpOptions, type CommandHooks, type CommandLocalization, type CommandMetadata, type CommandMiddleware, type CommandOptionDefinition, type CommandOptionSchema, type CommandOptionValue, type CommandOptionValues, type CommandRegistrar, CommandRouter, type CommandRuntimeServices, type CommandScope, type ConcurrencyStrategy, type ConnectionState, type ConnectionUpdate, type CreateOptions, DeferredReply, type DownloadedMedia, type DurationOption, type EnumOption, type ExecutableCommandDefinition, type GroupAccess, type GroupAddressingMode, type GroupParticipant, type GroupParticipantAction, type GroupParticipantsChanged, type GroupRole, type GroupSnapshot, type GuardResult, type ImageContent, type InviteResult, type LoadCommandsOptions, type LoadCommandsResult, type LoadedCommand, type LogContext, type LogEntry, type LogFormat, type LogLevel, type LogWriter, Logger, type LoggerConfig, type LoggerOptions, type LoginOptions, type MediaKind, type MediaSource, type MemberActionState, MemoryCache, type MemoryCacheStats, type MentionTarget, type Message, type MessageContent, type MessageKey, type MessageMedia, type MuteChangeResult, type MuteEnforcement, type MuteOptions, type MuteRecord, MuteService, type MuteStore, type NumberOption, ParsedCommandOptions, type ParticipantUpdateResult, type PresenceState, type QuotedMessage, type ReconnectOptions, type RegisteredCommand, type RemoveMuteResult, type ReplyOptions, type RouterOptions, type SentMessage, SqliteMuteStore, type StickerContent, type StoredMute, type StringOption, type TextContent, User, type UserData, type UserOption, type VideoContent, WhaNextApp, WhaNextError, type WhaNextErrorCode, type WhatsAppProvider, create, defineCommand, defineCommandGroup, defineCommands, defineSubcommand, guards, isCommandGroup, loadCommands, option, toWhaNextError };