@whanext/core 0.10.0 → 0.12.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
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+
4
+ ## 0.12.0 - Message content classification
5
+
6
+ ### Adicionado
7
+
8
+ - `Message.contentKind` para classificar o payload recebido sem expor tipos do Baileys.
9
+ - Novo tipo público `MessageContentKind`.
10
+ - Classificação nativa para texto, imagem, vídeo, áudio, documento, sticker, localização, contato, enquete e catálogo/produto.
11
+ - `unknown` como fallback para formatos ainda não normalizados pela biblioteca.
12
+
13
+ ### Compatibilidade
14
+
15
+ - `contentKind` é opcional no contrato público para manter compatibilidade com providers customizados e objetos `Message` criados por aplicações existentes.
16
+ - O `BaileysProvider` oficial sempre preenche `contentKind` nas mensagens recebidas.
17
+ - `message.media.kind` continua sendo a API indicada para mídia baixável; `contentKind` complementa essa API com tipos não-mídia.
18
+
19
+ ## 0.11.0 - Command discovery
20
+
21
+ - `CommandRouter.load()` carrega diretórios de comandos diretamente pelo router.
22
+ - `loadCommands()` agora aceita `URL` além de caminhos em string.
23
+ - Autoload reconhece `.ts`, `.mts` e `.cts` por padrão, além de JavaScript.
24
+ - `CommandContext` agora expõe `ctx.commands` (catálogo somente-leitura) e `ctx.prefix`.
25
+ - Menus e comandos de ajuda podem consultar o catálogo sem factory ou referência global ao app.
26
+ - Mantém compatibilidade com `defineCommand`, `defineCommands`, `defineSubcommand` e `defineCommandGroup`.
27
+
3
28
  Todas as mudanças relevantes do projeto serão registradas neste arquivo.
4
29
 
5
30
  ## 0.10.0
package/README.md CHANGED
@@ -220,6 +220,22 @@ if (app.isReady) {
220
220
 
221
221
  Toda mensagem possui `message.sender: User`. Menções ficam em `message.mentionedUsers`, e o remetente de um reply em `message.quoted?.sender`.
222
222
 
223
+ O provider Baileys também classifica o payload em `message.contentKind`, sem exigir acesso aos tipos internos do Baileys ou download da mídia:
224
+
225
+ ```ts
226
+ app.on('message', async (message) => {
227
+ if (message.contentKind === 'location') {
228
+ console.log('Localização recebida');
229
+ }
230
+
231
+ if (message.contentKind === 'poll') {
232
+ console.log('Enquete recebida');
233
+ }
234
+ });
235
+ ```
236
+
237
+ Os valores disponíveis são `text`, `image`, `video`, `audio`, `document`, `sticker`, `location`, `contact`, `poll`, `catalog` e `unknown`. Para mídia baixável, continue usando `message.media.kind`.
238
+
223
239
  ```ts
224
240
  app.on('message', async (message) => {
225
241
  console.log(message.sender.id);
@@ -566,81 +582,83 @@ Restrições disponíveis:
566
582
 
567
583
  `ArgsParser` possui `string`, `number`, `boolean`, `enum`, `user`, `duration`, `peek`, `skip` e `rest`. `args.user()` retorna `User`, nunca uma string crua.
568
584
 
569
- ### Carregando comandos de uma pasta
585
+ ### Descoberta automática de comandos
570
586
 
571
- `loadCommands` importa e registra todos os comandos de um diretório, sem precisar listar cada arquivo manualmente. Um arquivo pode conter um ou vários comandos:
587
+ A forma recomendada agora é deixar cada arquivo de comando autossuficiente e pedir ao próprio router para descobrir a árvore inteira. Não é necessário manter `index.ts` por pasta nem um arquivo central de imports:
572
588
 
573
589
  ```ts
574
- import { loadCommands } from '@whanext/core';
590
+ const app = await create({ prefix: '&' });
575
591
 
576
- await loadCommands(app.router(), './commands');
592
+ await app.commands.load(new URL('./commands/', import.meta.url));
577
593
  ```
578
594
 
579
- Para um comando por arquivo, continue usando `defineCommand` normalmente:
595
+ O mesmo código funciona em desenvolvimento TypeScript (com um runtime/loader como `tsx`) e depois do build: o loader reconhece `.ts`, `.mts`, `.cts`, `.js`, `.mjs` e `.cjs` por padrão. O diretório é percorrido recursivamente.
580
596
 
581
- ```ts
582
- // commands/ping.js
583
- export default defineCommand({
584
- name: 'ping',
585
- description: 'Responde pong.',
586
- async execute(message) {
587
- await app.message.reply(message, 'pong');
588
- },
589
- });
597
+ ```text
598
+ src/commands/
599
+ ├── admin/
600
+ │ ├── ban.ts
601
+ │ ├── mute.ts
602
+ │ └── warn.ts
603
+ ├── group/
604
+ │ ├── access.ts
605
+ │ └── pin.ts
606
+ └── general/
607
+ ├── menu.ts
608
+ └── profile.ts
590
609
  ```
591
610
 
592
- Para manter comandos relacionados juntos, use `defineCommands`. Esse é o formato recomendado para módulos com vários comandos:
611
+ Um arquivo pode exportar um comando:
593
612
 
594
613
  ```ts
595
- // commands/moderation.ts
596
- import { defineCommand, defineCommands } from '@whanext/core';
597
-
598
- const mute = defineCommand({
599
- name: 'mute',
600
- description: 'Silencia um membro.',
601
- async execute(message, args) {
602
- // ...
603
- },
604
- });
605
-
606
- const unmute = defineCommand({
607
- name: 'unmute',
608
- description: 'Remove o silêncio de um membro.',
609
- async execute(message, args) {
610
- // ...
614
+ export default defineCommand({
615
+ name: 'ping',
616
+ description: 'Responde pong.',
617
+ async execute(ctx) {
618
+ await ctx.reply('pong');
611
619
  },
612
620
  });
613
-
614
- export default defineCommands(mute, unmute);
615
621
  ```
616
622
 
617
- Vários exports nomeados também são descobertos automaticamente:
623
+ Ou vários comandos relacionados no mesmo módulo:
618
624
 
619
625
  ```ts
620
626
  export const mute = defineCommand({ /* ... */ });
621
627
  export const unmute = defineCommand({ /* ... */ });
622
628
  ```
623
629
 
624
- Exports auxiliares, como constantes e metadados, são ignorados quando o módulo contém ao menos um comando válido. Se o mesmo objeto de comando aparecer em uma coleção e em um export nomeado, ele será registrado apenas uma vez.
630
+ Também é possível exportar uma coleção com `defineCommands(...)`. Exports auxiliares são ignorados quando o módulo contém pelo menos um comando válido, e o mesmo objeto não é registrado duas vezes.
625
631
 
626
- Por padrão, `loadCommands` procura arquivos `.js`, `.mjs` e `.cjs`, incluindo subpastas. Extensões como `.ts` não são carregadas por padrão, já que o `import()` dinâmico depende de um loader do TypeScript estar ativo no processo (`tsx`, `ts-node` ou similar); habilite explicitamente quando esse loader existir:
632
+ Para menus dinâmicos, `CommandContext` expõe uma visão somente-leitura do catálogo e o prefixo atual:
627
633
 
628
634
  ```ts
629
- await loadCommands(app.router(), './commands', { extensions: ['.ts'] });
635
+ export default defineCommand({
636
+ name: 'menu',
637
+ description: 'Mostra os comandos.',
638
+ async execute(ctx) {
639
+ const commands = ctx.commands.catalog({ category: 'administração' });
640
+ const lines = commands.map((command) =>
641
+ `${ctx.prefix}${command.path.join(' ')} — ${command.definition.description}`,
642
+ );
643
+
644
+ await ctx.reply(lines.join('\n'));
645
+ },
646
+ });
630
647
  ```
631
648
 
632
- `loadCommands` também aceita qualquer objeto com um método `command()`, não apenas o router retornado por `app.router()`.
649
+ `ctx.commands` fornece `catalog()`, `categories()`, `find()`, `has()`, `size` e `prefix`; ele não expõe detalhes internos do provider.
633
650
 
634
- O retorno informa os arquivos carregados e ignorados, além de cada comando registrado:
651
+ Se for necessário controlar extensões ou recursão:
635
652
 
636
653
  ```ts
637
- const result = await loadCommands(app.router(), './commands');
638
-
639
- console.log(result.loaded); // arquivos importados
640
- console.log(result.skipped); // extensões não habilitadas
641
- console.log(result.commands); // { name, filePath }[]
654
+ await app.commands.load(new URL('./commands/', import.meta.url), {
655
+ recursive: true,
656
+ extensions: ['.ts'],
657
+ });
642
658
  ```
643
659
 
660
+ `loadCommands(registrar, directory, options)` continua disponível como API de baixo nível para registradores customizados. O retorno contém arquivos carregados/ignorados e os comandos descobertos.
661
+
644
662
  ## Cache externo
645
663
 
646
664
  ```ts
package/dist/index.d.ts CHANGED
@@ -65,6 +65,14 @@ interface MessageKey {
65
65
  participantId?: string;
66
66
  }
67
67
  type MediaKind = 'image' | 'video' | 'audio' | 'document' | 'sticker';
68
+ /**
69
+ * High-level classification of the received WhatsApp message payload.
70
+ *
71
+ * `media.kind` remains the source of truth for downloadable media.
72
+ * `contentKind` additionally exposes non-media payloads such as locations,
73
+ * contacts, polls and catalog/product messages without leaking Baileys types.
74
+ */
75
+ type MessageContentKind = 'text' | 'image' | 'video' | 'audio' | 'document' | 'sticker' | 'location' | 'contact' | 'poll' | 'catalog' | 'unknown';
68
76
  interface MessageMedia {
69
77
  kind: MediaKind;
70
78
  mimetype?: string;
@@ -99,6 +107,7 @@ interface Message {
99
107
  isReply: boolean;
100
108
  isViewOnce: boolean;
101
109
  hasMedia: boolean;
110
+ contentKind?: MessageContentKind;
102
111
  media?: MessageMedia;
103
112
  quoted?: QuotedMessage;
104
113
  }
@@ -438,6 +447,17 @@ declare class MessageService {
438
447
  text(chatId: string, text: string, mentions?: MentionTarget[]): Promise<SentMessage>;
439
448
  }
440
449
 
450
+ interface CommandCatalogView {
451
+ readonly prefix: string;
452
+ readonly size: number;
453
+ catalog(options?: {
454
+ category?: string;
455
+ includeHidden?: boolean;
456
+ }): readonly RegisteredCommand[];
457
+ categories(): readonly string[];
458
+ has(path: string): boolean;
459
+ find(path: string): RegisteredCommand | undefined;
460
+ }
441
461
  interface CommandRuntimeServices {
442
462
  messages: MessageService;
443
463
  media: MediaService;
@@ -466,6 +486,8 @@ interface CommandContext<Schema extends CommandOptionSchema = CommandOptionSchem
466
486
  readonly chat: CommandChatContext;
467
487
  readonly group: CommandGroupContext | undefined;
468
488
  readonly command: RegisteredCommand;
489
+ readonly commands: CommandCatalogView;
490
+ readonly prefix: string;
469
491
  readonly options: ParsedCommandOptions<Schema>;
470
492
  readonly args: ArgsParser;
471
493
  readonly locale: string | undefined;
@@ -584,6 +606,24 @@ declare function defineCommandGroup<const Group extends CommandGroupDefinition>(
584
606
  declare function defineCommands<const Commands extends readonly CommandDefinition[]>(...commands: Commands): Commands;
585
607
  declare function isCommandGroup(definition: CommandDefinition): definition is CommandGroupDefinition;
586
608
 
609
+ interface CommandRegistrar {
610
+ command(definition: CommandDefinition): unknown;
611
+ }
612
+ interface LoadCommandsOptions {
613
+ extensions?: readonly string[];
614
+ recursive?: boolean;
615
+ }
616
+ interface LoadCommandsResult {
617
+ loaded: readonly string[];
618
+ skipped: readonly string[];
619
+ commands: readonly LoadedCommand[];
620
+ }
621
+ interface LoadedCommand {
622
+ name: string;
623
+ filePath: string;
624
+ }
625
+ declare function loadCommands(registrar: CommandRegistrar, dirPath: string | URL, options?: LoadCommandsOptions): Promise<LoadCommandsResult>;
626
+
587
627
  interface RouterOptions {
588
628
  prefix?: string;
589
629
  onError?: (error: WhaNextError, message: Message) => void | Promise<void>;
@@ -606,6 +646,7 @@ declare class CommandRouter {
606
646
  get prefix(): string;
607
647
  get size(): number;
608
648
  command(definition: CommandDefinition): this;
649
+ load(dirPath: string | URL, options?: LoadCommandsOptions): Promise<LoadCommandsResult>;
609
650
  use(middleware: CommandMiddleware): this;
610
651
  onError(handler: CommandErrorHandler): () => void;
611
652
  catalog(options?: CommandCatalogOptions): readonly RegisteredCommand[];
@@ -749,24 +790,6 @@ declare class MemoryCache implements CacheStore {
749
790
  stats(): Readonly<MemoryCacheStats>;
750
791
  }
751
792
 
752
- interface CommandRegistrar {
753
- command(definition: CommandDefinition): unknown;
754
- }
755
- interface LoadCommandsOptions {
756
- extensions?: readonly string[];
757
- recursive?: boolean;
758
- }
759
- interface LoadCommandsResult {
760
- loaded: readonly string[];
761
- skipped: readonly string[];
762
- commands: readonly LoadedCommand[];
763
- }
764
- interface LoadedCommand {
765
- name: string;
766
- filePath: string;
767
- }
768
- declare function loadCommands(registrar: CommandRegistrar, dirPath: string, options?: LoadCommandsOptions): Promise<LoadCommandsResult>;
769
-
770
793
  declare class SqliteMuteStore implements MuteStore {
771
794
  #private;
772
795
  constructor(path?: string);
@@ -777,4 +800,4 @@ declare class SqliteMuteStore implements MuteStore {
777
800
  close(): void;
778
801
  }
779
802
 
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 };
803
+ 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 CommandCatalogView, 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 MessageContentKind, 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 };
package/dist/index.js CHANGED
@@ -395,6 +395,8 @@ var CommandContextImplementation = class {
395
395
  chat;
396
396
  group;
397
397
  command;
398
+ commands;
399
+ prefix;
398
400
  options;
399
401
  args;
400
402
  locale;
@@ -413,6 +415,8 @@ var CommandContextImplementation = class {
413
415
  this.user = options.message.sender;
414
416
  this.chat = { id: options.message.chatId, isGroup: options.message.isGroup };
415
417
  this.command = options.command;
418
+ this.commands = options.commands;
419
+ this.prefix = options.commands.prefix;
416
420
  this.options = options.options;
417
421
  this.args = options.args;
418
422
  this.locale = options.locale;
@@ -510,6 +514,85 @@ function normalizeContent(content) {
510
514
  return typeof content === "string" ? { text: content } : content;
511
515
  }
512
516
 
517
+ // src/commands/load-commands.ts
518
+ import { readdir } from "fs/promises";
519
+ import path from "path";
520
+ import { fileURLToPath, pathToFileURL } from "url";
521
+ var DEFAULT_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"];
522
+ async function loadCommands(registrar, dirPath, options = {}) {
523
+ const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
524
+ const recursive = options.recursive ?? true;
525
+ const resolvedDirPath = dirPath instanceof URL ? fileURLToPath(dirPath) : dirPath;
526
+ const entries = await readEntries(resolvedDirPath, recursive);
527
+ const loaded = [];
528
+ const skipped = [];
529
+ const commands = [];
530
+ for (const filePath of entries) {
531
+ if (isDeclarationFile(filePath) || !extensions.includes(path.extname(filePath))) {
532
+ skipped.push(filePath);
533
+ continue;
534
+ }
535
+ const definitions = await importCommands(filePath);
536
+ for (const definition of definitions) {
537
+ registrar.command(definition);
538
+ commands.push({ name: definition.name, filePath });
539
+ }
540
+ loaded.push(filePath);
541
+ }
542
+ return { loaded, skipped, commands };
543
+ }
544
+ async function readEntries(dirPath, recursive) {
545
+ let dirents;
546
+ try {
547
+ dirents = await readdir(dirPath, { recursive, withFileTypes: true });
548
+ } catch (error) {
549
+ throw new WhaNextError("COMMAND_LOAD_FAILED", `Could not read the commands directory "${dirPath}".`, {
550
+ cause: error,
551
+ context: { dirPath }
552
+ });
553
+ }
554
+ return dirents.filter((dirent) => dirent.isFile()).map((dirent) => path.join(dirent.parentPath, dirent.name)).sort((left, right) => left.localeCompare(right));
555
+ }
556
+ async function importCommands(filePath) {
557
+ let module;
558
+ try {
559
+ module = await import(pathToFileURL(filePath).href);
560
+ } catch (error) {
561
+ throw new WhaNextError("COMMAND_LOAD_FAILED", `Could not import the command file "${filePath}".`, {
562
+ cause: error,
563
+ context: { filePath }
564
+ });
565
+ }
566
+ const definitions = [];
567
+ const seen = /* @__PURE__ */ new Set();
568
+ const exports = [
569
+ module.default,
570
+ ...Object.entries(module).filter(([name]) => name !== "default").map(([, value]) => value)
571
+ ];
572
+ for (const value of exports) {
573
+ const candidates = Array.isArray(value) ? value : [value];
574
+ for (const candidate of candidates) {
575
+ if (isCommandDefinition(candidate) && !seen.has(candidate)) {
576
+ seen.add(candidate);
577
+ definitions.push(candidate);
578
+ }
579
+ }
580
+ }
581
+ if (definitions.length === 0) {
582
+ throw new WhaNextError("COMMAND_LOAD_FAILED", `The file "${filePath}" does not export any valid commands.`, {
583
+ context: { filePath }
584
+ });
585
+ }
586
+ return definitions;
587
+ }
588
+ function isCommandDefinition(value) {
589
+ const candidate = value;
590
+ return typeof value === "object" && value !== null && typeof candidate.name === "string" && (typeof candidate.execute === "function" || Array.isArray(candidate.subcommands));
591
+ }
592
+ function isDeclarationFile(filePath) {
593
+ return /\.d\.(?:ts|mts|cts)$/.test(filePath);
594
+ }
595
+
513
596
  // src/commands/options.ts
514
597
  var option = {
515
598
  string(definition) {
@@ -705,6 +788,9 @@ var CommandRouter = class {
705
788
  this.#definitions.add(definition);
706
789
  return this;
707
790
  }
791
+ load(dirPath, options = {}) {
792
+ return loadCommands(this, dirPath, options);
793
+ }
708
794
  use(middleware) {
709
795
  this.#globalMiddleware.push(middleware);
710
796
  return this;
@@ -778,6 +864,7 @@ _Nenhum comando dispon\xEDvel._`;
778
864
  options: new ParsedCommandOptions({}),
779
865
  args: new ArgsParser(tokens),
780
866
  services: this.#services,
867
+ commands: this,
781
868
  signal: new AbortController().signal,
782
869
  ...root.locale ? { locale: root.locale } : {}
783
870
  });
@@ -820,6 +907,7 @@ _Nenhum comando dispon\xEDvel._`;
820
907
  options: parsedOptions,
821
908
  args: legacyArgs,
822
909
  services: this.#services,
910
+ commands: this,
823
911
  signal,
824
912
  ...resolved.locale ? { locale: resolved.locale } : {}
825
913
  });
@@ -840,6 +928,7 @@ _Nenhum comando dispon\xEDvel._`;
840
928
  options: new ParsedCommandOptions({}),
841
929
  args: legacyArgs,
842
930
  services: this.#services,
931
+ commands: this,
843
932
  signal: new AbortController().signal,
844
933
  ...resolved.locale ? { locale: resolved.locale } : {}
845
934
  });
@@ -2212,6 +2301,7 @@ function normalizeBaileysMessage(input) {
2212
2301
  });
2213
2302
  const mentionedUsers = (context?.mentionedJid ?? []).map((identity) => User.fromIdentities([identity]));
2214
2303
  const media = getMedia(type, node, Boolean(input.key.isViewOnce));
2304
+ const contentKind = getContentKind(type);
2215
2305
  const text = getText(content);
2216
2306
  const caption = getCaption(content);
2217
2307
  const quoted = getQuoted(context, chatId);
@@ -2229,7 +2319,8 @@ function normalizeBaileysMessage(input) {
2229
2319
  isGroup: chatId.endsWith("@g.us"),
2230
2320
  isReply: quoted !== void 0,
2231
2321
  isViewOnce: media?.viewOnce ?? false,
2232
- hasMedia: media !== void 0
2322
+ hasMedia: media !== void 0,
2323
+ contentKind
2233
2324
  };
2234
2325
  if (senderJid !== void 0) message.senderJid = senderJid;
2235
2326
  if (senderLid !== void 0) {
@@ -2242,6 +2333,42 @@ function normalizeBaileysMessage(input) {
2242
2333
  if (quoted !== void 0) message.quoted = quoted;
2243
2334
  return message;
2244
2335
  }
2336
+ function getContentKind(type) {
2337
+ switch (String(type ?? "")) {
2338
+ case "conversation":
2339
+ case "extendedTextMessage":
2340
+ case "buttonsResponseMessage":
2341
+ case "listResponseMessage":
2342
+ case "templateButtonReplyMessage":
2343
+ return "text";
2344
+ case "imageMessage":
2345
+ return "image";
2346
+ case "videoMessage":
2347
+ return "video";
2348
+ case "audioMessage":
2349
+ return "audio";
2350
+ case "documentMessage":
2351
+ case "documentWithCaptionMessage":
2352
+ return "document";
2353
+ case "stickerMessage":
2354
+ return "sticker";
2355
+ case "locationMessage":
2356
+ case "liveLocationMessage":
2357
+ return "location";
2358
+ case "contactMessage":
2359
+ case "contactsArrayMessage":
2360
+ return "contact";
2361
+ case "pollCreationMessage":
2362
+ case "pollCreationMessageV2":
2363
+ case "pollCreationMessageV3":
2364
+ return "poll";
2365
+ case "productMessage":
2366
+ case "orderMessage":
2367
+ return "catalog";
2368
+ default:
2369
+ return "unknown";
2370
+ }
2371
+ }
2245
2372
  function normalizeKey(key) {
2246
2373
  const normalized = {
2247
2374
  id: key.id ?? "",
@@ -2890,81 +3017,6 @@ var guards = {
2890
3017
  return guard;
2891
3018
  }
2892
3019
  };
2893
-
2894
- // src/commands/load-commands.ts
2895
- import { readdir } from "fs/promises";
2896
- import path from "path";
2897
- import { pathToFileURL } from "url";
2898
- var DEFAULT_EXTENSIONS = [".js", ".mjs", ".cjs"];
2899
- async function loadCommands(registrar, dirPath, options = {}) {
2900
- const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
2901
- const recursive = options.recursive ?? true;
2902
- const entries = await readEntries(dirPath, recursive);
2903
- const loaded = [];
2904
- const skipped = [];
2905
- const commands = [];
2906
- for (const filePath of entries) {
2907
- if (!extensions.includes(path.extname(filePath))) {
2908
- skipped.push(filePath);
2909
- continue;
2910
- }
2911
- const definitions = await importCommands(filePath);
2912
- for (const definition of definitions) {
2913
- registrar.command(definition);
2914
- commands.push({ name: definition.name, filePath });
2915
- }
2916
- loaded.push(filePath);
2917
- }
2918
- return { loaded, skipped, commands };
2919
- }
2920
- async function readEntries(dirPath, recursive) {
2921
- let dirents;
2922
- try {
2923
- dirents = await readdir(dirPath, { recursive, withFileTypes: true });
2924
- } catch (error) {
2925
- throw new WhaNextError("COMMAND_LOAD_FAILED", `Could not read the commands directory "${dirPath}".`, {
2926
- cause: error,
2927
- context: { dirPath }
2928
- });
2929
- }
2930
- return dirents.filter((dirent) => dirent.isFile()).map((dirent) => path.join(dirent.parentPath, dirent.name)).sort((left, right) => left.localeCompare(right));
2931
- }
2932
- async function importCommands(filePath) {
2933
- let module;
2934
- try {
2935
- module = await import(pathToFileURL(filePath).href);
2936
- } catch (error) {
2937
- throw new WhaNextError("COMMAND_LOAD_FAILED", `Could not import the command file "${filePath}".`, {
2938
- cause: error,
2939
- context: { filePath }
2940
- });
2941
- }
2942
- const definitions = [];
2943
- const seen = /* @__PURE__ */ new Set();
2944
- const exports = [
2945
- module.default,
2946
- ...Object.entries(module).filter(([name]) => name !== "default").map(([, value]) => value)
2947
- ];
2948
- for (const value of exports) {
2949
- const candidates = Array.isArray(value) ? value : [value];
2950
- for (const candidate of candidates) {
2951
- if (isCommandDefinition(candidate) && !seen.has(candidate)) {
2952
- seen.add(candidate);
2953
- definitions.push(candidate);
2954
- }
2955
- }
2956
- }
2957
- if (definitions.length === 0) {
2958
- throw new WhaNextError("COMMAND_LOAD_FAILED", `The file "${filePath}" does not export any valid commands.`, {
2959
- context: { filePath }
2960
- });
2961
- }
2962
- return definitions;
2963
- }
2964
- function isCommandDefinition(value) {
2965
- const candidate = value;
2966
- return typeof value === "object" && value !== null && typeof candidate.name === "string" && (typeof candidate.execute === "function" || Array.isArray(candidate.subcommands));
2967
- }
2968
3020
  export {
2969
3021
  ArgsParser,
2970
3022
  Browser,