@whanext/core 0.13.1 → 0.14.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.14.1
4
+
5
+ ### Corrigido
6
+
7
+ - A normalização agora preserva corretamente o envelope de visualização única (`viewOnceMessage`, `viewOnceMessageV2` e `viewOnceMessageV2Extension`) antes de desembrulhar a mídia.
8
+ - `message.isViewOnce` passa a ser verdadeiro mesmo quando o Baileys representa a visualização única apenas pelo wrapper externo.
9
+ - Mensagens citadas agora expõem `quoted.isViewOnce`, `quoted.contentKind` e `quoted.media`, permitindo distinguir mídia normal de visualização única sem heurísticas na aplicação.
10
+ - `app.media.download()` aceita diretamente uma `QuotedMessage`, permitindo `app.media.download(message.quoted)` quando houver mídia citada.
11
+ - `app.account.jid`, `app.account.lid`, `app.account.phoneNumber` e `app.account.selfChatId` expõem identidades canônicas da própria conta. `selfChatId` prioriza PN JID sem sufixo de dispositivo antes de usar LID.
12
+
13
+ ### Compatibilidade
14
+
15
+ - Os novos campos de `QuotedMessage` são opcionais no contrato público para não quebrar providers customizados ou objetos criados manualmente. O provider Baileys oficial os preenche nas mensagens normalizadas.
16
+ - `app.media.download(message)` e `app.media.download(message.keys)` continuam funcionando sem alteração.
17
+
18
+ ## 0.14.0
19
+
20
+ ### Adicionado
21
+
22
+ - `guards.owner()` para restringir comandos à própria conta conectada ao WhatsApp.
23
+ - `onlyOwner: true` para manter o mesmo recurso disponível na API legada de comandos.
24
+ - `ctx.isOwner` e `ctx.account` no contexto moderno; em multi-account, `ctx.account.id` identifica qual conta executou o comando.
25
+ - `app.account` com os identificadores atuais da conta e `isOwner(message)` sem exigir JID, LID ou número manual.
26
+ - `createMulti()` e `WhaNextMultiApp` para manter várias contas independentes no mesmo processo.
27
+ - `multi.commands` para registrar comandos, middleware, handlers de erro e autoload em todas as contas da instância.
28
+ - `multi.login()`, `multi.disconnect()`, `multi.health()`, `multi.get()`, `multi.ids()` e `multi.isReady` para operar as contas em conjunto.
29
+ - `multi.on()` para observar eventos de todas as contas com `accountId`, aplicação e payload de origem.
30
+ - Sessões automáticas isoladas em `./sessions/<id>` quando `auth` não é informado.
31
+ - Banco de mute automático separado por conta (`./data/whanext-<id>.sqlite`) quando o mute é habilitado sem banco/store explícito.
32
+
33
+ ### Segurança e compatibilidade
34
+
35
+ - IDs de contas multi-account são validados para impedir caminhos de sessão inseguros.
36
+ - Diretórios `auth` duplicados são rejeitados entre contas que usam o provider padrão.
37
+ - `create()` e toda a API de uma única conta continuam compatíveis.
38
+ - Cada conta mantém provider, socket, sessão, cache, reconexão e identidade próprios; nenhuma sessão ativa é compartilhada entre contas.
39
+
3
40
  ## 0.13.1
4
41
 
5
42
  - Corrigido download de mídias para mensagens visualização única citadas, através do cache do payload citado carregado em `contextInfo`.
package/README.md CHANGED
@@ -33,7 +33,9 @@ await app.login({
33
33
 
34
34
  - API pública sem objetos ou tipos crus do Baileys.
35
35
  - Login por pairing code, sessão persistente e reconexão automática.
36
+ - Uma ou várias contas independentes no mesmo processo com `createMulti()`.
36
37
  - Prefixo global e comandos declarativos com argumentos tipados.
38
+ - Comandos exclusivos da conta conectada com `guards.owner()` ou `onlyOwner`.
37
39
  - Usuários normalizados entre JID, LID e PN.
38
40
  - Mensagens, replies, edição, exclusão, menções, mídias e download normalizado.
39
41
  - Operações de grupos e membros com resultados idempotentes.
@@ -101,6 +103,62 @@ await app.login({
101
103
 
102
104
  O prefixo é definido uma vez. O router identifica o comando, remove o prefixo e cria o `ArgsParser` automaticamente.
103
105
 
106
+ ## Múltiplas contas
107
+
108
+ Para manter duas, três ou mais contas de WhatsApp no mesmo processo, use `createMulti()`. Cada conta possui conexão, sessão, cache, reconexão e identidade próprios. A instância conjunta apenas coordena essas aplicações independentes.
109
+
110
+ ```ts
111
+ import {
112
+ Browser,
113
+ createMulti,
114
+ defineCommand,
115
+ guards,
116
+ } from '@whanext/core';
117
+
118
+ const multi = await createMulti({
119
+ prefix: ';',
120
+ browser: Browser.Windows,
121
+ authRoot: './sessions',
122
+ logger: 'info',
123
+ accounts: [
124
+ { id: 'principal', phone: process.env.PHONE_1 },
125
+ { id: 'secundaria', phone: process.env.PHONE_2 },
126
+ { id: 'terceira', phone: process.env.PHONE_3 },
127
+ ],
128
+ });
129
+
130
+ multi.commands.command(defineCommand({
131
+ name: 'painel',
132
+ description: 'Exibe um painel exclusivo do dono.',
133
+ guards: [guards.owner()],
134
+ async execute(ctx) {
135
+ await ctx.reply(`Conta: ${ctx.account.id}`);
136
+ },
137
+ }));
138
+
139
+ await multi.login({
140
+ onCode(accountId, code) {
141
+ console.log(`[${accountId}] Código de pareamento:`, code);
142
+ },
143
+ });
144
+ ```
145
+
146
+ Quando `auth` não é informado na conta, o WhaNext cria automaticamente caminhos separados como `./sessions/principal`, `./sessions/secundaria` e `./sessions/terceira`. O mesmo diretório de sessão não pode ser usado por duas contas do provider padrão.
147
+
148
+ Uma conta específica continua sendo um `WhaNextApp` completo:
149
+
150
+ ```ts
151
+ const principal = multi.get('principal');
152
+
153
+ if (principal?.isReady) {
154
+ await principal.message.send('5511999999999@s.whatsapp.net', {
155
+ text: 'Olá pela conta principal.',
156
+ });
157
+ }
158
+ ```
159
+
160
+ `multi.commands.command()`, `use()`, `onError()` e `load()` aplicam a mesma configuração de comandos a todas as contas. Dentro de um comando, `ctx.account.id` informa qual conta recebeu e executou aquela interação. Eventos também podem ser observados em conjunto com `multi.on()`.
161
+
104
162
  ## Logging
105
163
 
106
164
  O nível padrão é `info`. Estão disponíveis `debug`, `info`, `warn`, `error` e `silent`.
@@ -315,7 +373,21 @@ const downloaded = await app.media.download(message);
315
373
  await writeFile(`./downloads/${downloaded.fileName ?? message.id}`, downloaded.data);
316
374
  ```
317
375
 
318
- `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.
376
+ `download()` aceita a `Message` recebida, uma `QuotedMessage` ou uma `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.
377
+
378
+ Para visualização única citada, o provider oficial preserva os metadados do envelope sem expor tipos do Baileys:
379
+
380
+ ```ts
381
+ if (message.quoted?.isViewOnce && message.quoted.hasMedia) {
382
+ const media = await app.media.download(message.quoted);
383
+
384
+ console.log(message.quoted.contentKind); // image | video | audio
385
+ console.log(message.quoted.media?.kind);
386
+ console.log(media.mimetype);
387
+ }
388
+ ```
389
+
390
+ Isso também funciona quando a mídia de visualização única aparece dentro de envelopes `viewOnceMessageV2` ou `viewOnceMessageV2Extension`.
319
391
 
320
392
  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`.
321
393
 
@@ -477,6 +549,56 @@ app.commands.command(
477
549
  );
478
550
  ```
479
551
 
552
+ ### Comandos exclusivos do dono
553
+
554
+ O dono é a própria conta conectada naquela aplicação. Não é necessário salvar número, JID ou LID manualmente. O provider usa a marca de mensagem enviada pela própria conta e, quando necessário, compara as identidades normalizadas da sessão.
555
+
556
+ ```ts
557
+ app.commands.command(defineCommand({
558
+ name: 'chay',
559
+ description: 'Comando privado da conta conectada.',
560
+ guards: [guards.owner()],
561
+
562
+ async execute(ctx) {
563
+ await ctx.reply('💣 *Comando autorizado*');
564
+ },
565
+ }));
566
+ ```
567
+
568
+ No contexto moderno também é possível consultar a informação diretamente:
569
+
570
+ ```ts
571
+ if (ctx.isOwner) {
572
+ console.log(ctx.account.ids);
573
+ }
574
+ ```
575
+
576
+ A identidade da própria conta também pode ser consultada sem escolher manualmente entre JID e LID:
577
+
578
+ ```ts
579
+ console.log(app.account.jid); // 5531...@s.whatsapp.net
580
+ console.log(app.account.lid); // ...@lid
581
+ console.log(app.account.phoneNumber); // 5531...
582
+ console.log(app.account.selfChatId); // destino preferencial para falar consigo mesmo
583
+ ```
584
+
585
+ `selfChatId` prioriza o PN JID canônico e remove automaticamente sufixos de dispositivo como `:12@s.whatsapp.net`.
586
+
587
+ Na API legada, use `onlyOwner: true`:
588
+
589
+ ```ts
590
+ app.router().command(defineCommand({
591
+ name: 'interno',
592
+ description: 'Comando exclusivo do dono.',
593
+ onlyOwner: true,
594
+ execute(message) {
595
+ return app.message.reply(message, { text: 'Autorizado.' });
596
+ },
597
+ }));
598
+ ```
599
+
600
+ Em uma instância criada com `createMulti()`, o dono é resolvido separadamente para cada conta. Uma mensagem enviada pela conta `principal` não passa automaticamente como dona da conta `secundaria`.
601
+
480
602
  ### Subcomandos
481
603
 
482
604
  ```ts
@@ -579,6 +701,7 @@ Restrições disponíveis:
579
701
  - `onlyPrivate`
580
702
  - `onlyAdmin`
581
703
  - `botMustBeAdmin`
704
+ - `onlyOwner`
582
705
 
583
706
  `ArgsParser` possui `string`, `number`, `boolean`, `enum`, `user`, `duration`, `peek`, `skip` e `rest`. `args.user()` retorna `User`, nunca uma string crua.
584
707
 
package/SECURITY.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  | Versão | Suporte |
6
6
  | --- | --- |
7
- | 0.8.x | Sim |
7
+ | 0.14.x | Sim |
8
8
  | anteriores | Não |
9
9
 
10
10
  ## Reportando uma vulnerabilidade
package/dist/index.d.ts CHANGED
@@ -86,6 +86,15 @@ interface QuotedMessage {
86
86
  senderId?: string;
87
87
  sender?: User;
88
88
  hasMedia: boolean;
89
+ /**
90
+ * Indica se a mensagem citada foi enviada como visualização única.
91
+ * O provider oficial sempre preenche este campo.
92
+ */
93
+ isViewOnce?: boolean;
94
+ /** Classificação normalizada do conteúdo citado. */
95
+ contentKind?: MessageContentKind;
96
+ /** Metadados da mídia citada, quando houver. */
97
+ media?: MessageMedia;
89
98
  }
90
99
  interface Message {
91
100
  id: string;
@@ -129,9 +138,7 @@ type MediaSource = Uint8Array | {
129
138
  path: string;
130
139
  };
131
140
  type MentionTarget = string | User;
132
- /** Options for reposting a previously received message without quoting it. */
133
141
  interface RepostMessageOptions {
134
- /** Hidden or visible mentions injected into the reposted payload. */
135
142
  mentions?: readonly MentionTarget[];
136
143
  }
137
144
  interface TextContent {
@@ -430,7 +437,7 @@ declare class MediaService {
430
437
  video(chatId: string, content: VideoContent): Promise<SentMessage>;
431
438
  audio(chatId: string, content: AudioContent): Promise<SentMessage>;
432
439
  sticker(chatId: string, content: StickerContent): Promise<SentMessage>;
433
- download(message: Message | MessageKey): Promise<DownloadedMedia>;
440
+ download(message: Message | QuotedMessage | MessageKey): Promise<DownloadedMedia>;
434
441
  }
435
442
 
436
443
  declare class MemberService {
@@ -470,7 +477,13 @@ interface CommandCatalogView {
470
477
  has(path: string): boolean;
471
478
  find(path: string): RegisteredCommand | undefined;
472
479
  }
480
+ interface CommandAccountContext {
481
+ readonly id: string | undefined;
482
+ readonly ids: readonly string[];
483
+ isOwner(message: Pick<Message, 'keys' | 'senderIds'>): boolean;
484
+ }
473
485
  interface CommandRuntimeServices {
486
+ account: CommandAccountContext;
474
487
  messages: MessageService;
475
488
  media: MediaService;
476
489
  groups: GroupService;
@@ -495,6 +508,8 @@ interface ReplyOptions {
495
508
  interface CommandContext<Schema extends CommandOptionSchema = CommandOptionSchema> extends Message {
496
509
  readonly message: Message;
497
510
  readonly user: User;
511
+ readonly account: CommandAccountContext;
512
+ readonly isOwner: boolean;
498
513
  readonly chat: CommandChatContext;
499
514
  readonly group: CommandGroupContext | undefined;
500
515
  readonly command: RegisteredCommand;
@@ -548,6 +563,7 @@ interface GuardResult {
548
563
  }
549
564
  type CommandGuard = (context: CommandContext) => boolean | void | GuardResult | Promise<boolean | void | GuardResult>;
550
565
  declare const guards: {
566
+ owner(): CommandGuard;
551
567
  group(): CommandGuard;
552
568
  private(): CommandGuard;
553
569
  userAdmin(): CommandGuard;
@@ -596,6 +612,7 @@ interface CommandMetadata {
596
612
  onlyPrivate?: boolean;
597
613
  onlyAdmin?: boolean;
598
614
  botMustBeAdmin?: boolean;
615
+ onlyOwner?: boolean;
599
616
  }
600
617
  interface ExecutableCommandDefinition<Schema extends CommandOptionSchema = CommandOptionSchema> extends CommandMetadata {
601
618
  options?: Schema;
@@ -702,6 +719,18 @@ declare class Logger {
702
719
  error(message: string, context?: LogContext): void;
703
720
  }
704
721
 
722
+ declare class AccountService {
723
+ #private;
724
+ readonly id: string | undefined;
725
+ constructor(provider: WhatsAppProvider, id?: string);
726
+ get ids(): readonly string[];
727
+ get jid(): string | undefined;
728
+ get lid(): string | undefined;
729
+ get phoneNumber(): string | undefined;
730
+ get selfChatId(): string | undefined;
731
+ isOwner(message: Pick<Message, 'keys' | 'senderIds'>): boolean;
732
+ }
733
+
705
734
  interface AppEvents {
706
735
  message: Message;
707
736
  connection: ConnectionUpdate;
@@ -731,9 +760,11 @@ interface WhaNextAppOptions {
731
760
  logger?: LoggerConfig;
732
761
  mute?: MuteOptions;
733
762
  router?: Omit<RouterOptions, 'prefix'>;
763
+ accountId?: string;
734
764
  }
735
765
  declare class WhaNextApp {
736
766
  #private;
767
+ readonly account: AccountService;
737
768
  readonly message: MessageService;
738
769
  readonly media: MediaService;
739
770
  readonly group: GroupService;
@@ -777,9 +808,58 @@ interface CreateOptions {
777
808
  reconnect?: ReconnectOptions;
778
809
  messageCacheSize?: number;
779
810
  provider?: WhatsAppProvider;
811
+ accountId?: string;
780
812
  }
781
813
  declare function create(options?: CreateOptions): Promise<WhaNextApp>;
782
814
 
815
+ interface MultiAccountOptions extends Omit<CreateOptions, 'accountId'> {
816
+ id: string;
817
+ }
818
+ interface CreateMultiOptions extends Omit<CreateOptions, 'accountId' | 'auth' | 'phone' | 'provider'> {
819
+ accounts: readonly MultiAccountOptions[];
820
+ authRoot?: string;
821
+ }
822
+ interface MultiLoginOptions extends Omit<LoginOptions, 'onCode'> {
823
+ onCode?: (accountId: string, code: string) => void | Promise<void>;
824
+ }
825
+ interface MultiAppHealth extends AppHealth {
826
+ accountId: string;
827
+ }
828
+ interface MultiAppEvent<Event extends keyof AppEvents> {
829
+ accountId: string;
830
+ app: WhaNextApp;
831
+ payload: AppEvents[Event];
832
+ }
833
+ interface MultiLoadCommandsResult {
834
+ accountId: string;
835
+ result: LoadCommandsResult;
836
+ }
837
+ declare class MultiCommandRouter {
838
+ #private;
839
+ constructor(apps: ReadonlyMap<string, WhaNextApp>);
840
+ command(definition: CommandDefinition): this;
841
+ use(middleware: CommandMiddleware): this;
842
+ onError(handler: CommandErrorHandler): () => void;
843
+ load(dirPath: string | URL, options?: LoadCommandsOptions): Promise<readonly MultiLoadCommandsResult[]>;
844
+ }
845
+ declare class WhaNextMultiApp {
846
+ #private;
847
+ readonly commands: MultiCommandRouter;
848
+ constructor(apps: ReadonlyMap<string, WhaNextApp>);
849
+ get size(): number;
850
+ get isReady(): boolean;
851
+ ids(): readonly string[];
852
+ has(accountId: string): boolean;
853
+ get(accountId: string): WhaNextApp | undefined;
854
+ values(): readonly WhaNextApp[];
855
+ router(): MultiCommandRouter;
856
+ on<Event extends keyof AppEvents>(event: Event, listener: (entry: MultiAppEvent<Event>) => void | Promise<void>): () => void;
857
+ health(): readonly MultiAppHealth[];
858
+ login(options?: MultiLoginOptions): Promise<void>;
859
+ disconnect(): Promise<void>;
860
+ }
861
+ declare function createMulti(options: CreateMultiOptions): Promise<WhaNextMultiApp>;
862
+
783
863
  interface MemoryCacheStats {
784
864
  size: number;
785
865
  maxEntries: number;
@@ -812,4 +892,4 @@ declare class SqliteMuteStore implements MuteStore {
812
892
  close(): void;
813
893
  }
814
894
 
815
- 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 RepostMessageOptions, 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 };
895
+ export { AccountService, 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 CommandAccountContext, 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 CreateMultiOptions, 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 MultiAccountOptions, type MultiAppEvent, type MultiAppHealth, MultiCommandRouter, type MultiLoadCommandsResult, type MultiLoginOptions, 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 RepostMessageOptions, type RouterOptions, type SentMessage, SqliteMuteStore, type StickerContent, type StoredMute, type StringOption, type TextContent, User, type UserData, type UserOption, type VideoContent, WhaNextApp, WhaNextError, type WhaNextErrorCode, WhaNextMultiApp, type WhatsAppProvider, create, createMulti, defineCommand, defineCommandGroup, defineCommands, defineSubcommand, guards, isCommandGroup, loadCommands, option, toWhaNextError };