@whanext/core 0.14.3 → 0.16.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,6 +1,42 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## 0.16.0
5
+
6
+ ### Adicionado
7
+
8
+ - Novo evento público `messageDeleted` em `app.on()` e `multi.on()`.
9
+ - Novo tipo `MessageDeleted`, com chave da mensagem, payload original quando disponível, autor da revogação e horário da exclusão.
10
+ - O provider Baileys passa a observar `messages.update` e reconhecer revogações sem expor tipos do Baileys à aplicação.
11
+ - Mensagens revogadas permanecem no cache recente, permitindo `app.message.repost()` e `app.media.download()` enquanto a entrada estiver disponível.
12
+
13
+ ### Compatibilidade
14
+
15
+ - A mudança é aditiva para consumidores da API pública.
16
+ - Providers customizados passam a incluir `messageDeleted` em `ProviderEvents`.
17
+ - Quando a mensagem original já saiu do cache, o evento continua sendo emitido sem o campo `message`.
18
+
19
+ ## 0.15.0
20
+
21
+ ### Adicionado
22
+
23
+ - Suporte de alto nível a mensagens com botões interativos Native Flow.
24
+ - Botão `copy` para copiar códigos/textos usando `cta_copy`.
25
+ - Botão `link` para abrir URLs usando `cta_url`.
26
+ - Novo `ButtonsContent` no `MessageContent`, com `title`, `text`, `footer`, `mentions` e uma lista tipada de botões.
27
+ - Novo atalho `app.message.buttons(chatId, content)`; `ctx.reply({...})` e `app.message.reply(...)` também aceitam o novo conteúdo automaticamente.
28
+ - Exportação pública dos tipos `ButtonsContent`, `MessageButton`, `CopyCodeButton` e `LinkButton`.
29
+
30
+ ### Provider Baileys
31
+
32
+ - Mensagens interativas são geradas como `InteractiveMessage`/`NativeFlowMessage` e enviadas por `relayMessage`, sem expor protobufs ao consumidor.
33
+ - Replies e menções são preservados no envio de botões.
34
+ - O relay inclui os nós de compatibilidade necessários para Native Flow em chats privados e grupos.
35
+
36
+ ### Compatibilidade
37
+
38
+ - Nenhum contrato existente foi removido. Texto, mídia, reações, edição, delete e repost continuam usando as APIs anteriores.
39
+
4
40
  ## 0.14.3
5
41
 
6
42
  - Corrige uma rejeição órfã durante o login quando a solicitação de código de pareamento falha ao mesmo tempo em que o socket é fechado.
package/README.md CHANGED
@@ -37,7 +37,7 @@ await app.login({
37
37
  - Prefixo global e comandos declarativos com argumentos tipados.
38
38
  - Comandos exclusivos da conta conectada com `guards.owner()` ou `onlyOwner`.
39
39
  - Usuários normalizados entre JID, LID e PN.
40
- - Mensagens, replies, edição, exclusão, menções, mídias e download normalizado.
40
+ - Mensagens, replies, edição, exclusão, revogações, menções, mídias e download normalizado.
41
41
  - Operações de grupos e membros com resultados idempotentes.
42
42
  - Cache de metadados transparente e substituível.
43
43
  - Mute permanente ou temporário com SQLite ou banco próprio.
@@ -159,6 +159,20 @@ if (principal?.isReady) {
159
159
 
160
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
161
 
162
+ ## Mensagens apagadas
163
+
164
+ Revogações recebidas pelo WhatsApp são expostas pelo evento `messageDeleted`. Quando a mensagem original ainda estiver no cache recente, o payload inclui `message`, que pode ser republicada ou usada para baixar a mídia pelas APIs existentes.
165
+
166
+ ```ts
167
+ app.on('messageDeleted', async ({ message, deletedByMe }) => {
168
+ if (!message || deletedByMe) return;
169
+
170
+ await app.message.repost(message, app.account.selfChatId);
171
+ });
172
+ ```
173
+
174
+ O evento também informa `deletedById` quando o WhatsApp fornece a identidade responsável pela revogação.
175
+
162
176
  ## Logging
163
177
 
164
178
  O nível padrão é `info`. Estão disponíveis `debug`, `info`, `warn`, `error` e `silent`.
@@ -340,6 +354,44 @@ await app.message.react(sent, '👏🏻');
340
354
  await app.message.unreact(sent);
341
355
  ```
342
356
 
357
+ ### Botões interativos
358
+
359
+ Por enquanto, a API pública suporta botões de **copiar código** e **abrir link**:
360
+
361
+ ```ts
362
+ await app.message.buttons(chatId, {
363
+ title: 'Acesso',
364
+ text: 'Escolha uma ação:',
365
+ footer: 'WhaNext',
366
+ buttons: [
367
+ {
368
+ type: 'copy',
369
+ label: 'Copiar código',
370
+ code: 'ABC-123',
371
+ },
372
+ {
373
+ type: 'link',
374
+ label: 'Abrir painel',
375
+ url: 'https://example.com',
376
+ },
377
+ ],
378
+ });
379
+ ```
380
+
381
+ Também funciona diretamente em replies de comandos, sem importar tipos do Baileys:
382
+
383
+ ```ts
384
+ await ctx.reply({
385
+ text: 'Use uma das opções abaixo.',
386
+ buttons: [
387
+ { type: 'copy', label: 'Copiar', code: 'ABC-123' },
388
+ { type: 'link', label: 'Abrir site', url: 'https://example.com' },
389
+ ],
390
+ });
391
+ ```
392
+
393
+ `title`, `footer` e `mentions` são opcionais. Os botões podem ser combinados na mesma mensagem.
394
+
343
395
  `delete()` aceita `Message`, `SentMessage` ou `MessageKey`.
344
396
 
345
397
  `react()` aceita os mesmos tipos e adiciona uma reação à mensagem. `unreact()` remove a reação da conta conectada.
package/dist/index.d.ts CHANGED
@@ -90,6 +90,13 @@ interface QuotedMessage {
90
90
  contentKind?: MessageContentKind;
91
91
  media?: MessageMedia;
92
92
  }
93
+ interface MessageDeleted {
94
+ key: MessageKey;
95
+ message?: Message;
96
+ deletedByMe: boolean;
97
+ deletedById?: string;
98
+ deletedAt: Date;
99
+ }
93
100
  interface Message {
94
101
  id: string;
95
102
  jid: string;
@@ -139,6 +146,24 @@ interface TextContent {
139
146
  text: string;
140
147
  mentions?: MentionTarget[];
141
148
  }
149
+ interface LinkButton {
150
+ type: 'link';
151
+ label: string;
152
+ url: string;
153
+ }
154
+ interface CopyCodeButton {
155
+ type: 'copy';
156
+ label: string;
157
+ code: string;
158
+ }
159
+ type MessageButton = LinkButton | CopyCodeButton;
160
+ interface ButtonsContent {
161
+ text: string;
162
+ buttons: MessageButton[];
163
+ title?: string;
164
+ footer?: string;
165
+ mentions?: MentionTarget[];
166
+ }
142
167
  interface ImageContent {
143
168
  image: MediaSource;
144
169
  caption?: string;
@@ -160,7 +185,7 @@ interface AudioContent {
160
185
  interface StickerContent {
161
186
  sticker: MediaSource;
162
187
  }
163
- type MessageContent = TextContent | ImageContent | VideoContent | AudioContent | StickerContent;
188
+ type MessageContent = TextContent | ButtonsContent | ImageContent | VideoContent | AudioContent | StickerContent;
164
189
 
165
190
  type GroupAccess = 'open' | 'closed';
166
191
  type GroupRole = 'member' | 'admin' | 'owner';
@@ -229,6 +254,7 @@ interface ConnectionUpdate {
229
254
  }
230
255
  interface ProviderEvents {
231
256
  message: Message;
257
+ messageDeleted: MessageDeleted;
232
258
  connection: ConnectionUpdate;
233
259
  groupChanged: {
234
260
  groupId: string;
@@ -458,6 +484,7 @@ declare class MessageService {
458
484
  react(message: Message | SentMessage | MessageKey, emoji: string): Promise<SentMessage>;
459
485
  unreact(message: Message | SentMessage | MessageKey): Promise<SentMessage>;
460
486
  text(chatId: string, text: string, mentions?: MentionTarget[]): Promise<SentMessage>;
487
+ buttons(chatId: string, content: ButtonsContent): Promise<SentMessage>;
461
488
  }
462
489
 
463
490
  interface CommandCatalogView {
@@ -727,6 +754,7 @@ declare class AccountService {
727
754
 
728
755
  interface AppEvents {
729
756
  message: Message;
757
+ messageDeleted: MessageDeleted;
730
758
  connection: ConnectionUpdate;
731
759
  error: WhaNextError;
732
760
  mute: MuteEnforcement;
@@ -886,4 +914,4 @@ declare class SqliteMuteStore implements MuteStore {
886
914
  close(): void;
887
915
  }
888
916
 
889
- 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 };
917
+ export { AccountService, type AddMuteOptions, type AddMuteResult, type AppEvents, type AppHealth, type AppHealthStatus, ArgsParser, type AudioContent, type BooleanOption, Browser, type ButtonsContent, 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 CopyCodeButton, 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 LinkButton, 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 MessageButton, type MessageContent, type MessageContentKind, type MessageDeleted, 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 };
package/dist/index.js CHANGED
@@ -2036,6 +2036,9 @@ var MessageService = class {
2036
2036
  }
2037
2037
  return this.send(chatId, content);
2038
2038
  }
2039
+ buttons(chatId, content) {
2040
+ return this.send(chatId, content);
2041
+ }
2039
2042
  };
2040
2043
 
2041
2044
  // src/app/whanext-app.ts
@@ -2194,6 +2197,15 @@ var WhaNextApp = class {
2194
2197
  });
2195
2198
  await this.#events.emit("call", call);
2196
2199
  });
2200
+ this.#provider.on("messageDeleted", async (deletion) => {
2201
+ this.logger.debug("Message deleted", {
2202
+ chatId: deletion.key.chatId,
2203
+ messageId: deletion.key.id,
2204
+ deletedByMe: deletion.deletedByMe,
2205
+ cached: deletion.message !== void 0
2206
+ });
2207
+ await this.#events.emit("messageDeleted", deletion);
2208
+ });
2197
2209
  this.#provider.on("message", async (message) => {
2198
2210
  try {
2199
2211
  const enforcement = await this.mute.enforce(message);
@@ -2281,6 +2293,8 @@ import {
2281
2293
  Browsers,
2282
2294
  DisconnectReason,
2283
2295
  downloadMediaMessage,
2296
+ generateWAMessageFromContent,
2297
+ isJidGroup,
2284
2298
  makeWASocket,
2285
2299
  proto,
2286
2300
  useMultiFileAuthState
@@ -2685,6 +2699,9 @@ var BaileysProvider = class {
2685
2699
  }
2686
2700
  }
2687
2701
  async sendMessage(chatId, content, replyTo) {
2702
+ if ("buttons" in content) {
2703
+ return this.#sendButtons(chatId, content, replyTo);
2704
+ }
2688
2705
  const socket = this.#requireSocket();
2689
2706
  const options = replyTo ? {
2690
2707
  quoted: {
@@ -2840,6 +2857,23 @@ var BaileysProvider = class {
2840
2857
  if (message) void this.#events.emit("message", message);
2841
2858
  }
2842
2859
  });
2860
+ socket.ev.on("messages.update", (updates) => {
2861
+ for (const { key, update } of updates) {
2862
+ if (update.message !== null || !key.id || !key.remoteJid) continue;
2863
+ const stored = this.#messageStore.get(this.#messageStoreKey(key));
2864
+ const message = stored ? normalizeBaileysMessage(stored) : void 0;
2865
+ const deletionKey = update.key;
2866
+ const deletedByMe = deletionKey?.fromMe === true;
2867
+ const deletedById = deletionKey?.participant ?? deletionKey?.participantAlt ?? (deletedByMe ? this.#socket?.user?.id : deletionKey?.remoteJid ?? void 0);
2868
+ void this.#events.emit("messageDeleted", {
2869
+ key: message?.keys ?? normalizeKey(key),
2870
+ ...message ? { message } : {},
2871
+ deletedByMe,
2872
+ ...deletedById ? { deletedById } : {},
2873
+ deletedAt: /* @__PURE__ */ new Date()
2874
+ });
2875
+ }
2876
+ });
2843
2877
  socket.ev.on("groups.update", (groups) => {
2844
2878
  for (const group of groups) {
2845
2879
  if (group.id) {
@@ -2928,6 +2962,111 @@ var BaileysProvider = class {
2928
2962
  }
2929
2963
  return this.#socket;
2930
2964
  }
2965
+ async #sendButtons(chatId, content, replyTo) {
2966
+ const socket = this.#requireSocket();
2967
+ const userJid = socket.user?.id;
2968
+ if (!userJid) {
2969
+ throw new WhaNextError(
2970
+ "PROVIDER_ERROR",
2971
+ "WhatsApp did not expose the current account identity for the interactive message."
2972
+ );
2973
+ }
2974
+ const interactiveMessage = proto.Message.InteractiveMessage.create({
2975
+ ...content.title !== void 0 ? {
2976
+ header: {
2977
+ title: content.title,
2978
+ hasMediaAttachment: false
2979
+ }
2980
+ } : {},
2981
+ body: { text: content.text },
2982
+ ...content.footer !== void 0 ? { footer: { text: content.footer } } : {},
2983
+ ...content.mentions && content.mentions.length > 0 ? {
2984
+ contextInfo: {
2985
+ mentionedJid: this.#mentions(content.mentions)
2986
+ }
2987
+ } : {},
2988
+ nativeFlowMessage: {
2989
+ buttons: content.buttons.map((button) => {
2990
+ if (button.type === "copy") {
2991
+ return {
2992
+ name: "cta_copy",
2993
+ buttonParamsJson: JSON.stringify({
2994
+ display_text: button.label,
2995
+ copy_code: button.code
2996
+ })
2997
+ };
2998
+ }
2999
+ return {
3000
+ name: "cta_url",
3001
+ buttonParamsJson: JSON.stringify({
3002
+ display_text: button.label,
3003
+ url: button.url,
3004
+ merchant_url: button.url
3005
+ })
3006
+ };
3007
+ }),
3008
+ messageParamsJson: "{}",
3009
+ messageVersion: 1
3010
+ }
3011
+ });
3012
+ const quoted = replyTo ? {
3013
+ key: this.#toWaKey(replyTo),
3014
+ message: { conversation: "" }
3015
+ } : void 0;
3016
+ const generated = generateWAMessageFromContent(
3017
+ chatId,
3018
+ { interactiveMessage },
3019
+ {
3020
+ userJid,
3021
+ ...quoted ? { quoted } : {}
3022
+ }
3023
+ );
3024
+ const messageId = generated.key.id;
3025
+ if (!generated.message || !messageId) {
3026
+ throw new WhaNextError(
3027
+ "PROVIDER_ERROR",
3028
+ "WhatsApp could not generate the interactive message."
3029
+ );
3030
+ }
3031
+ await socket.relayMessage(chatId, generated.message, {
3032
+ messageId,
3033
+ additionalNodes: this.#interactiveRelayNodes(chatId)
3034
+ });
3035
+ return this.#sent(generated);
3036
+ }
3037
+ #interactiveRelayNodes(chatId) {
3038
+ const bizNode = {
3039
+ tag: "biz",
3040
+ attrs: {
3041
+ actual_actors: "2",
3042
+ host_storage: "2",
3043
+ privacy_mode_ts: (Math.floor(Date.now() / 1e3) - 77980457).toString()
3044
+ },
3045
+ content: [
3046
+ {
3047
+ tag: "interactive",
3048
+ attrs: { type: "native_flow", v: "1" },
3049
+ content: [
3050
+ {
3051
+ tag: "native_flow",
3052
+ attrs: { v: "9", name: "mixed" }
3053
+ }
3054
+ ]
3055
+ },
3056
+ {
3057
+ tag: "quality_control",
3058
+ attrs: { source_type: "third_party" }
3059
+ }
3060
+ ]
3061
+ };
3062
+ if (isJidGroup(chatId)) {
3063
+ return [bizNode];
3064
+ }
3065
+ return [
3066
+ { tag: "bot", attrs: { biz_bot: "1" } },
3067
+ bizNode
3068
+ ];
3069
+ }
2931
3070
  #toContent(content) {
2932
3071
  if ("text" in content) {
2933
3072
  return {