@whanext/core 0.19.13 → 0.19.15

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,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.19.15
4
+
5
+ ### Added
6
+ - Added provider-safe payload classification through `message.protocolKinds` and `message.payloadKinds`, also available on quoted messages.
7
+ - Added `payment` to `MessageContentKind` for WhatsApp payment payloads and native-flow payment cards.
8
+ - Added Zapo 1.7.1 payment coverage for `sendPaymentMessage`, `requestPaymentMessage`, `paymentInviteMessage`, `cancelPaymentRequestMessage`, `declinePaymentRequestMessage`, `invoiceMessage`, `paymentReminderMessage`, `splitPaymentMessage`, and `splitPaymentUpdateMessage`.
9
+ - Added group-status wrapper coverage for `groupStatusMessage`, `groupStatusMessageV2`, `groupStatusMentionMessage`, and `groupMentionedMessage` while preserving the nested content classification.
10
+ - Added `catalog_message` classification for Zapo `productMessage` and `orderMessage` payloads.
11
+ - Added native-flow payment detection for the documented `payment_info` and `review_and_pay` flows.
12
+ - Added defensive `malformed_payload` and `native_flow_crash` signals for invalid or structurally unsafe native-flow JSON without exposing raw provider payloads to consumers.
13
+
14
+ ### Compatibility
15
+ - Existing `contentKind` consumers remain compatible; the new protocol/payload arrays are optional.
16
+ - `groupStatus*` and `groupMentionedMessage` are unwrapped as Zapo `FutureProofMessage` containers so text/media inside them continues through the regular normalizer.
17
+
18
+ ## 0.19.14
19
+
20
+ - Removed the temporary end-to-end AntiEdit diagnostic instrumentation after confirming the group-author identity fix in production.
21
+ - Normal edit handling remains quiet apart from the existing standard error and recovery logs.
22
+
3
23
  ## 0.19.13
4
24
 
5
25
  - Group edits now preserve the original message author's participant identities instead of replacing them with identities carried by the edit-addon envelope.
package/README.md CHANGED
@@ -356,7 +356,26 @@ app.on('message', async (message) => {
356
356
  });
357
357
  ```
358
358
 
359
- 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`.
359
+ Os valores disponíveis são `text`, `image`, `video`, `audio`, `document`, `sticker`, `location`, `contact`, `poll`, `catalog`, `payment` e `unknown`. Para mídia baixável, continue usando `message.media.kind`.
360
+
361
+ Para moderação de payloads especiais, o provider Zapo também expõe duas classificações opcionais:
362
+
363
+ - `message.protocolKinds`: nomes reais do protocolo reconhecidos no `Proto.IMessage`, sem expor o objeto protobuf bruto.
364
+ - `message.payloadKinds`: categorias estáveis do WhaNext: `catalog_message`, `payment_payload`, `group_status_payload`, `payment_info_embedded`, `native_flow_crash` e `malformed_payload`.
365
+
366
+ Um filtro de pagamentos pode cobrir tanto mensagens de pagamento do protocolo quanto os cards PIX/cobrança em native-flow:
367
+
368
+ ```ts
369
+ app.on('message', async (message) => {
370
+ if (message.contentKind === 'payment' && message.isGroup) {
371
+ await app.message.delete(message);
372
+ }
373
+ });
374
+ ```
375
+
376
+ Os wrappers `groupStatusMessage`, `groupStatusMessageV2`, `groupStatusMentionMessage` e `groupMentionedMessage` são desembrulhados antes da classificação normal. Por exemplo, uma imagem dentro de `groupStatusMessageV2` continua com `contentKind === 'image'`, enquanto `payloadKinds` inclui `group_status_payload` e `protocolKinds` preserva o wrapper detectado.
377
+
378
+ `catalog_message` normaliza `productMessage` e `orderMessage`. `payment_info_embedded` cobre os flows `payment_info` e `review_and_pay`. `native_flow_crash` e `malformed_payload` são sinais defensivos do WhaNext, não nomes de campos do protocolo Zapo.
360
379
 
361
380
  ```ts
362
381
  app.on('message', async (message) => {
package/dist/index.d.ts CHANGED
@@ -72,7 +72,9 @@ type MediaKind = 'image' | 'video' | 'audio' | 'document' | 'sticker';
72
72
  * `contentKind` additionally exposes non-media payloads such as locations,
73
73
  * contacts, polls and catalog/product messages without leaking provider-specific protocol types.
74
74
  */
75
- type MessageContentKind = 'text' | 'image' | 'video' | 'audio' | 'document' | 'sticker' | 'location' | 'contact' | 'poll' | 'catalog' | 'unknown';
75
+ type MessageContentKind = 'text' | 'image' | 'video' | 'audio' | 'document' | 'sticker' | 'location' | 'contact' | 'poll' | 'catalog' | 'payment' | 'unknown';
76
+ type MessageProtocolKind = 'groupStatusMessage' | 'groupStatusMessageV2' | 'groupStatusMentionMessage' | 'groupMentionedMessage' | 'productMessage' | 'orderMessage' | 'sendPaymentMessage' | 'requestPaymentMessage' | 'paymentInviteMessage' | 'cancelPaymentRequestMessage' | 'declinePaymentRequestMessage' | 'invoiceMessage' | 'paymentReminderMessage' | 'splitPaymentMessage' | 'splitPaymentUpdateMessage';
77
+ type MessagePayloadKind = 'catalog_message' | 'payment_payload' | 'group_status_payload' | 'payment_info_embedded' | 'native_flow_crash' | 'malformed_payload';
76
78
  interface MessageMedia {
77
79
  kind: MediaKind;
78
80
  mimetype?: string;
@@ -88,6 +90,8 @@ interface QuotedMessage {
88
90
  hasMedia: boolean;
89
91
  isViewOnce?: boolean;
90
92
  contentKind?: MessageContentKind;
93
+ protocolKinds?: MessageProtocolKind[];
94
+ payloadKinds?: MessagePayloadKind[];
91
95
  media?: MessageMedia;
92
96
  }
93
97
  type InteractiveResponseKind = 'button' | 'list';
@@ -132,6 +136,8 @@ interface Message {
132
136
  isViewOnce: boolean;
133
137
  hasMedia: boolean;
134
138
  contentKind?: MessageContentKind;
139
+ protocolKinds?: MessageProtocolKind[];
140
+ payloadKinds?: MessagePayloadKind[];
135
141
  media?: MessageMedia;
136
142
  quoted?: QuotedMessage;
137
143
  interactive?: InteractiveResponse;
@@ -640,8 +646,8 @@ declare const guards: {
640
646
  custom(guard: CommandGuard): CommandGuard;
641
647
  };
642
648
 
643
- type CommandScope = 'global' | 'user' | 'chat' | 'user-chat' | 'user-group';
644
- type ConcurrencyStrategy = 'parallel' | 'reject' | 'queue' | 'replace';
649
+ type CommandScope = "global" | "user" | "chat" | "user-chat" | "user-group";
650
+ type ConcurrencyStrategy = "parallel" | "reject" | "queue" | "replace";
645
651
  interface CommandCooldown {
646
652
  durationMs: number;
647
653
  scope?: CommandScope;
@@ -966,4 +972,4 @@ declare class SqliteMuteStore implements MuteStore {
966
972
  close(): void;
967
973
  }
968
974
 
969
- 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 InteractiveResponse, type InteractiveResponseKind, type InviteResult, type LinkButton, type ListContent, type ListRow, type ListSection, 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 MessageEdited, 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 PollContent, type PresenceState, type QuickReplyButton, 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 };
975
+ 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 InteractiveResponse, type InteractiveResponseKind, type InviteResult, type LinkButton, type ListContent, type ListRow, type ListSection, 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 MessageEdited, type MessageKey, type MessageMedia, type MessagePayloadKind, type MessageProtocolKind, 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 PollContent, type PresenceState, type QuickReplyButton, 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
@@ -2416,9 +2416,37 @@ import {
2416
2416
  } from "zapo-js";
2417
2417
 
2418
2418
  // src/provider/zapo/normalize-message.ts
2419
+ var GROUP_STATUS_PROTOCOL_KINDS = [
2420
+ "groupStatusMessage",
2421
+ "groupStatusMessageV2",
2422
+ "groupStatusMentionMessage",
2423
+ "groupMentionedMessage"
2424
+ ];
2425
+ var PAYMENT_PROTOCOL_KINDS = [
2426
+ "sendPaymentMessage",
2427
+ "requestPaymentMessage",
2428
+ "paymentInviteMessage",
2429
+ "cancelPaymentRequestMessage",
2430
+ "declinePaymentRequestMessage",
2431
+ "invoiceMessage",
2432
+ "paymentReminderMessage",
2433
+ "splitPaymentMessage",
2434
+ "splitPaymentUpdateMessage"
2435
+ ];
2436
+ var CATALOG_PROTOCOL_KINDS = [
2437
+ "productMessage",
2438
+ "orderMessage"
2439
+ ];
2440
+ var NATIVE_FLOW_PAYMENT_NAMES = /* @__PURE__ */ new Set([
2441
+ "payment_info",
2442
+ "review_and_pay"
2443
+ ]);
2444
+ var MAX_NATIVE_FLOW_JSON_BYTES = 128 * 1024;
2445
+ var MAX_NATIVE_FLOW_DEPTH = 32;
2446
+ var MAX_NATIVE_FLOW_NODES = 4096;
2419
2447
  function unwrapZapoMessageContent(input) {
2420
2448
  if (!input) return void 0;
2421
- const nested = input.ephemeralMessage?.message ?? input.viewOnceMessage?.message ?? input.viewOnceMessageV2?.message ?? viewOnceV2ExtensionMessage(input) ?? input.deviceSentMessage?.message ?? input.documentWithCaptionMessage?.message ?? editedWrapperMessage(input);
2449
+ const nested = input.ephemeralMessage?.message ?? input.viewOnceMessage?.message ?? input.viewOnceMessageV2?.message ?? viewOnceV2ExtensionMessage(input) ?? input.deviceSentMessage?.message ?? input.documentWithCaptionMessage?.message ?? editedWrapperMessage(input) ?? futureProofMessage(input, "groupStatusMessage") ?? futureProofMessage(input, "groupStatusMessageV2") ?? futureProofMessage(input, "groupStatusMentionMessage") ?? futureProofMessage(input, "groupMentionedMessage");
2422
2450
  return nested ? unwrapZapoMessageContent(nested) : input;
2423
2451
  }
2424
2452
  function editedWrapperMessage(input) {
@@ -2429,12 +2457,16 @@ function viewOnceV2ExtensionMessage(input) {
2429
2457
  const extension = input.viewOnceMessageV2Extension;
2430
2458
  return extension?.message ?? void 0;
2431
2459
  }
2460
+ function futureProofMessage(input, key) {
2461
+ const wrapper = input[key];
2462
+ return wrapper?.message ?? void 0;
2463
+ }
2432
2464
  function isZapoViewOnceContent(input) {
2433
2465
  if (!input) return false;
2434
2466
  if (input.viewOnceMessage?.message || input.viewOnceMessageV2?.message || viewOnceV2ExtensionMessage(input)) {
2435
2467
  return true;
2436
2468
  }
2437
- const nested = input.ephemeralMessage?.message ?? input.deviceSentMessage?.message ?? input.documentWithCaptionMessage?.message ?? editedWrapperMessage(input);
2469
+ const nested = input.ephemeralMessage?.message ?? input.deviceSentMessage?.message ?? input.documentWithCaptionMessage?.message ?? editedWrapperMessage(input) ?? futureProofMessage(input, "groupStatusMessage") ?? futureProofMessage(input, "groupStatusMessageV2") ?? futureProofMessage(input, "groupStatusMentionMessage") ?? futureProofMessage(input, "groupMentionedMessage");
2438
2470
  return nested ? isZapoViewOnceContent(nested) : false;
2439
2471
  }
2440
2472
  function extractQuotedZapoMessage(input) {
@@ -2462,6 +2494,7 @@ function normalizeZapoMessage(input) {
2462
2494
  if (!chatId || !id || !event.message) {
2463
2495
  return void 0;
2464
2496
  }
2497
+ const payloadInspection = inspectZapoPayload(event.message);
2465
2498
  const content = unwrapZapoMessageContent(event.message);
2466
2499
  if (!content) {
2467
2500
  return void 0;
@@ -2488,7 +2521,7 @@ function normalizeZapoMessage(input) {
2488
2521
  const mentionedUsers = mentionedIds.map((identity) => User.fromIdentities([identity]));
2489
2522
  const viewOnce = isZapoViewOnceContent(event.message);
2490
2523
  const media = getMedia(type, node, viewOnce);
2491
- const contentKind = getContentKind(type);
2524
+ const contentKind = payloadInspection.payloadKinds.includes("payment_payload") || payloadInspection.payloadKinds.includes("payment_info_embedded") ? "payment" : getContentKind(type);
2492
2525
  const text = getText(content);
2493
2526
  const caption = getCaption(content);
2494
2527
  const quoted = getQuoted(context, chatId);
@@ -2510,6 +2543,12 @@ function normalizeZapoMessage(input) {
2510
2543
  hasMedia: media !== void 0,
2511
2544
  contentKind
2512
2545
  };
2546
+ if (payloadInspection.protocolKinds.length > 0) {
2547
+ message.protocolKinds = payloadInspection.protocolKinds;
2548
+ }
2549
+ if (payloadInspection.payloadKinds.length > 0) {
2550
+ message.payloadKinds = payloadInspection.payloadKinds;
2551
+ }
2513
2552
  if (senderJid !== void 0) message.senderJid = senderJid;
2514
2553
  if (senderLid !== void 0) {
2515
2554
  message.lid = senderLid;
@@ -2534,6 +2573,131 @@ function normalizeZapoKey(key) {
2534
2573
  }
2535
2574
  return normalized;
2536
2575
  }
2576
+ function inspectZapoPayload(input) {
2577
+ const protocolKinds = /* @__PURE__ */ new Set();
2578
+ const payloadKinds = /* @__PURE__ */ new Set();
2579
+ inspectMessageLayer(input, protocolKinds, payloadKinds, 0);
2580
+ return {
2581
+ protocolKinds: [...protocolKinds],
2582
+ payloadKinds: [...payloadKinds]
2583
+ };
2584
+ }
2585
+ function inspectMessageLayer(input, protocolKinds, payloadKinds, depth) {
2586
+ if (!input) return;
2587
+ if (depth > 16) {
2588
+ payloadKinds.add("malformed_payload");
2589
+ return;
2590
+ }
2591
+ for (const kind of GROUP_STATUS_PROTOCOL_KINDS) {
2592
+ if (input[kind] !== null && input[kind] !== void 0) {
2593
+ protocolKinds.add(kind);
2594
+ payloadKinds.add("group_status_payload");
2595
+ if (!futureProofMessage(input, kind)) payloadKinds.add("malformed_payload");
2596
+ }
2597
+ }
2598
+ for (const kind of PAYMENT_PROTOCOL_KINDS) {
2599
+ if (input[kind] !== null && input[kind] !== void 0) {
2600
+ protocolKinds.add(kind);
2601
+ payloadKinds.add("payment_payload");
2602
+ }
2603
+ }
2604
+ for (const kind of CATALOG_PROTOCOL_KINDS) {
2605
+ if (input[kind] !== null && input[kind] !== void 0) {
2606
+ protocolKinds.add(kind);
2607
+ payloadKinds.add("catalog_message");
2608
+ }
2609
+ }
2610
+ inspectNativeFlow(input, payloadKinds);
2611
+ for (const nested of wrappedMessages(input)) {
2612
+ inspectMessageLayer(nested, protocolKinds, payloadKinds, depth + 1);
2613
+ }
2614
+ }
2615
+ function wrappedMessages(input) {
2616
+ const messages = [
2617
+ input.ephemeralMessage?.message,
2618
+ input.viewOnceMessage?.message,
2619
+ input.viewOnceMessageV2?.message,
2620
+ viewOnceV2ExtensionMessage(input),
2621
+ input.deviceSentMessage?.message,
2622
+ input.documentWithCaptionMessage?.message,
2623
+ editedWrapperMessage(input),
2624
+ futureProofMessage(input, "groupStatusMessage"),
2625
+ futureProofMessage(input, "groupStatusMessageV2"),
2626
+ futureProofMessage(input, "groupStatusMentionMessage"),
2627
+ futureProofMessage(input, "groupMentionedMessage")
2628
+ ];
2629
+ return messages.filter((message) => message !== null && message !== void 0);
2630
+ }
2631
+ function inspectNativeFlow(input, payloadKinds) {
2632
+ const interactive = input.interactiveMessage;
2633
+ const nativeFlow = interactive?.nativeFlowMessage;
2634
+ if (nativeFlow) {
2635
+ inspectNativeFlowJson(nativeFlow.messageParamsJson, payloadKinds);
2636
+ for (const button of nativeFlow.buttons ?? []) {
2637
+ if (button.name && NATIVE_FLOW_PAYMENT_NAMES.has(button.name)) {
2638
+ payloadKinds.add("payment_info_embedded");
2639
+ }
2640
+ inspectNativeFlowJson(button.buttonParamsJson, payloadKinds);
2641
+ }
2642
+ }
2643
+ const response = input.interactiveResponseMessage;
2644
+ const nativeResponse = response?.nativeFlowResponseMessage;
2645
+ if (nativeResponse?.name && NATIVE_FLOW_PAYMENT_NAMES.has(nativeResponse.name)) {
2646
+ payloadKinds.add("payment_info_embedded");
2647
+ }
2648
+ inspectNativeFlowJson(nativeResponse?.paramsJson, payloadKinds);
2649
+ const buttonsMessage = input.buttonsMessage;
2650
+ for (const button of buttonsMessage?.buttons ?? []) {
2651
+ const flow = button.nativeFlowInfo;
2652
+ if (flow?.name && NATIVE_FLOW_PAYMENT_NAMES.has(flow.name)) {
2653
+ payloadKinds.add("payment_info_embedded");
2654
+ }
2655
+ inspectNativeFlowJson(flow?.paramsJson, payloadKinds);
2656
+ }
2657
+ }
2658
+ function inspectNativeFlowJson(json, payloadKinds) {
2659
+ if (!json) return;
2660
+ if (Buffer.byteLength(json, "utf8") > MAX_NATIVE_FLOW_JSON_BYTES) {
2661
+ payloadKinds.add("native_flow_crash");
2662
+ payloadKinds.add("malformed_payload");
2663
+ return;
2664
+ }
2665
+ let parsed;
2666
+ try {
2667
+ parsed = JSON.parse(json);
2668
+ } catch {
2669
+ payloadKinds.add("malformed_payload");
2670
+ return;
2671
+ }
2672
+ if (!isSafeNativeFlowJson(parsed)) {
2673
+ payloadKinds.add("native_flow_crash");
2674
+ payloadKinds.add("malformed_payload");
2675
+ }
2676
+ }
2677
+ function isSafeNativeFlowJson(root) {
2678
+ const queue = [{ value: root, depth: 0 }];
2679
+ let nodes = 0;
2680
+ while (queue.length > 0) {
2681
+ const current = queue.shift();
2682
+ if (!current) break;
2683
+ nodes += 1;
2684
+ if (nodes > MAX_NATIVE_FLOW_NODES || current.depth > MAX_NATIVE_FLOW_DEPTH) {
2685
+ return false;
2686
+ }
2687
+ if (Array.isArray(current.value)) {
2688
+ for (const value of current.value) {
2689
+ queue.push({ value, depth: current.depth + 1 });
2690
+ }
2691
+ continue;
2692
+ }
2693
+ if (typeof current.value === "object" && current.value !== null) {
2694
+ for (const value of Object.values(current.value)) {
2695
+ queue.push({ value, depth: current.depth + 1 });
2696
+ }
2697
+ }
2698
+ }
2699
+ return true;
2700
+ }
2537
2701
  function contentNode(content) {
2538
2702
  const order = [
2539
2703
  "conversation",
@@ -2556,6 +2720,7 @@ function contentNode(content) {
2556
2720
  "pollCreationMessageV5",
2557
2721
  "productMessage",
2558
2722
  "orderMessage",
2723
+ "interactiveMessage",
2559
2724
  "interactiveResponseMessage"
2560
2725
  ];
2561
2726
  const type = order.find((key) => content[key] !== null && content[key] !== void 0);
@@ -2568,6 +2733,7 @@ function getContentKind(type) {
2568
2733
  case "buttonsResponseMessage":
2569
2734
  case "listResponseMessage":
2570
2735
  case "templateButtonReplyMessage":
2736
+ case "interactiveMessage":
2571
2737
  case "interactiveResponseMessage":
2572
2738
  return "text";
2573
2739
  case "imageMessage":
@@ -2599,7 +2765,11 @@ function getContentKind(type) {
2599
2765
  }
2600
2766
  }
2601
2767
  function getText(content) {
2602
- return content.conversation ?? content.extendedTextMessage?.text ?? content.buttonsResponseMessage?.selectedDisplayText ?? content.listResponseMessage?.title ?? content.templateButtonReplyMessage?.selectedDisplayText ?? content.pollCreationMessage?.name ?? content.pollCreationMessageV2?.name ?? content.pollCreationMessageV3?.name ?? content.pollCreationMessageV5?.name ?? getNativeFlowDisplayText(content) ?? void 0;
2768
+ return content.conversation ?? content.extendedTextMessage?.text ?? content.buttonsResponseMessage?.selectedDisplayText ?? content.listResponseMessage?.title ?? content.templateButtonReplyMessage?.selectedDisplayText ?? content.pollCreationMessage?.name ?? content.pollCreationMessageV2?.name ?? content.pollCreationMessageV3?.name ?? content.pollCreationMessageV5?.name ?? interactiveMessageBodyText(content) ?? getNativeFlowDisplayText(content) ?? void 0;
2769
+ }
2770
+ function interactiveMessageBodyText(content) {
2771
+ const interactive = content.interactiveMessage;
2772
+ return interactive?.body?.text ?? void 0;
2603
2773
  }
2604
2774
  function getInteractiveResponse(content) {
2605
2775
  const buttons = content.buttonsResponseMessage;
@@ -2700,6 +2870,7 @@ function getMedia(type, node, wrapperViewOnce) {
2700
2870
  }
2701
2871
  function getQuoted(context, chatId) {
2702
2872
  if (!context?.stanzaId || !context.quotedMessage) return void 0;
2873
+ const payloadInspection = inspectZapoPayload(context.quotedMessage);
2703
2874
  const content = unwrapZapoMessageContent(context.quotedMessage);
2704
2875
  if (!content) return void 0;
2705
2876
  const { type, node } = contentNode(content);
@@ -2714,8 +2885,14 @@ function getQuoted(context, chatId) {
2714
2885
  },
2715
2886
  hasMedia: media !== void 0,
2716
2887
  isViewOnce: media?.viewOnce ?? false,
2717
- contentKind: getContentKind(type)
2888
+ contentKind: payloadInspection.payloadKinds.includes("payment_payload") || payloadInspection.payloadKinds.includes("payment_info_embedded") ? "payment" : getContentKind(type)
2718
2889
  };
2890
+ if (payloadInspection.protocolKinds.length > 0) {
2891
+ quoted.protocolKinds = payloadInspection.protocolKinds;
2892
+ }
2893
+ if (payloadInspection.payloadKinds.length > 0) {
2894
+ quoted.payloadKinds = payloadInspection.payloadKinds;
2895
+ }
2719
2896
  const text = getText(content) ?? getCaption(content);
2720
2897
  if (text !== void 0) quoted.text = text;
2721
2898
  if (senderId !== void 0) {
@@ -3112,13 +3289,6 @@ var ZapoProvider = class {
3112
3289
  client.on("message", (event) => {
3113
3290
  const protocol = this.#protocolMessage(event);
3114
3291
  if (protocol && this.#isMessageMutationProtocol(protocol.type)) {
3115
- if (protocol.type === proto.Message.ProtocolMessage.Type.MESSAGE_EDIT) {
3116
- this.#logEditDiagnostic("received_message_protocol_payload", {
3117
- eventMessageId: event.key.id ?? void 0,
3118
- targetMessageId: protocol.key?.id ?? void 0,
3119
- offline: event.offline === true
3120
- });
3121
- }
3122
3292
  this.#enqueueProtocolEvent({
3123
3293
  ...event,
3124
3294
  protocolMessage: protocol
@@ -3127,11 +3297,6 @@ var ZapoProvider = class {
3127
3297
  }
3128
3298
  const directEdit = this.#directEditedMessage(event.message);
3129
3299
  if (directEdit && event.key.id) {
3130
- this.#logEditDiagnostic("received_direct_edited_message", {
3131
- eventMessageId: event.key.id,
3132
- targetMessageId: event.key.id,
3133
- offline: event.offline === true
3134
- });
3135
3300
  this.#enqueueProtocolEvent({
3136
3301
  ...event,
3137
3302
  protocolMessage: {
@@ -3142,14 +3307,6 @@ var ZapoProvider = class {
3142
3307
  });
3143
3308
  return;
3144
3309
  }
3145
- const encryptedEdit = this.#encryptedEditInfo(event.message);
3146
- if (encryptedEdit) {
3147
- this.#logEditDiagnostic("received_encrypted_addon", {
3148
- eventMessageId: event.key.id ?? void 0,
3149
- targetMessageId: encryptedEdit.targetMessageId,
3150
- offline: event.offline === true
3151
- });
3152
- }
3153
3310
  this.#handleMessage(event);
3154
3311
  });
3155
3312
  client.on("message_send", (event) => {
@@ -3165,13 +3322,6 @@ var ZapoProvider = class {
3165
3322
  });
3166
3323
  });
3167
3324
  client.on("message_protocol", (event) => {
3168
- if (event.protocolMessage.type === proto.Message.ProtocolMessage.Type.MESSAGE_EDIT) {
3169
- this.#logEditDiagnostic("received_message_protocol_event", {
3170
- eventMessageId: event.key.id ?? void 0,
3171
- targetMessageId: event.protocolMessage.key?.id ?? void 0,
3172
- offline: event.offline === true
3173
- });
3174
- }
3175
3325
  this.#enqueueProtocolEvent(event);
3176
3326
  });
3177
3327
  client.on("message_addon", (event) => {
@@ -3229,29 +3379,10 @@ var ZapoProvider = class {
3229
3379
  const kind = event.kind ?? this.#stringField(decrypted, "kind") ?? this.#stringField(decrypted, "type");
3230
3380
  const isEdit = kind === "message_edit" || protocol?.type === proto.Message.ProtocolMessage.Type.MESSAGE_EDIT;
3231
3381
  if (!isEdit) return;
3232
- this.#logEditDiagnostic("received_decrypted_addon", {
3233
- eventMessageId: event.key.id ?? void 0,
3234
- targetMessageId: event.targetMessageId ?? this.#stringField(decrypted, "targetMessageId") ?? this.#stringField(decrypted, "targetMessageID") ?? protocol?.key?.id ?? void 0,
3235
- addonKind: kind ?? void 0,
3236
- offline: event.offline === true
3237
- });
3238
3382
  const targetMessageId = event.targetMessageId ?? this.#stringField(decrypted, "targetMessageId") ?? this.#stringField(decrypted, "targetMessageID") ?? protocol?.key?.id ?? void 0;
3239
- if (!targetMessageId) {
3240
- this.#logEditDiagnostic("dropped_addon_without_target", {
3241
- eventMessageId: event.key.id ?? void 0,
3242
- addonKind: kind ?? void 0
3243
- }, "warn");
3244
- return;
3245
- }
3383
+ if (!targetMessageId) return;
3246
3384
  const editedMessage = protocol?.editedMessage ?? this.#addonEditedMessage(event.decrypted);
3247
- if (!editedMessage) {
3248
- this.#logEditDiagnostic("dropped_addon_without_edited_content", {
3249
- eventMessageId: event.key.id ?? void 0,
3250
- targetMessageId,
3251
- addonKind: kind ?? void 0
3252
- }, "warn");
3253
- return;
3254
- }
3385
+ if (!editedMessage) return;
3255
3386
  const protocolKey = protocol?.key;
3256
3387
  const target = {
3257
3388
  ...protocolKey ?? {},
@@ -3334,24 +3465,6 @@ var ZapoProvider = class {
3334
3465
  const nested = message.ephemeralMessage?.message ?? message.deviceSentMessage?.message;
3335
3466
  return nested ? this.#directEditedMessage(nested) : void 0;
3336
3467
  }
3337
- #encryptedEditInfo(message) {
3338
- const content = unwrapZapoMessageContent(message);
3339
- const encrypted = content?.secretEncryptedMessage;
3340
- if (!encrypted) return void 0;
3341
- if (encrypted.secretEncType !== proto.Message.SecretEncryptedMessage.SecretEncType.MESSAGE_EDIT) {
3342
- return void 0;
3343
- }
3344
- return {
3345
- ...encrypted.targetMessageKey?.id ? { targetMessageId: encrypted.targetMessageKey.id } : {}
3346
- };
3347
- }
3348
- #logEditDiagnostic(stage, context, level = "info") {
3349
- this.#logger[level]("AntiEdit diagnostic", {
3350
- diagnostic: "antiedit",
3351
- stage,
3352
- ...context
3353
- });
3354
- }
3355
3468
  #protocolMessage(event) {
3356
3469
  if (event.protocolMessage) return event.protocolMessage;
3357
3470
  const content = unwrapZapoMessageContent(event.message);
@@ -3389,15 +3502,10 @@ var ZapoProvider = class {
3389
3502
  if (!protocol || !this.#isMessageMutationProtocol(protocol.type)) return;
3390
3503
  const protocolKey = protocol.key;
3391
3504
  if (!protocolKey?.id) {
3392
- const context = {
3505
+ this.#logger.debug("Ignored Zapo protocol mutation without a target message id.", {
3393
3506
  messageId: event.key.id ?? void 0,
3394
3507
  protocolType: protocol.type ?? void 0
3395
- };
3396
- if (protocol.type === proto.Message.ProtocolMessage.Type.MESSAGE_EDIT) {
3397
- this.#logEditDiagnostic("dropped_protocol_without_target", context, "warn");
3398
- } else {
3399
- this.#logger.debug("Ignored Zapo protocol mutation without a target message id.", context);
3400
- }
3508
+ });
3401
3509
  return;
3402
3510
  }
3403
3511
  const remoteJid = protocolKey.remoteJid ?? event.key.remoteJid;
@@ -3412,16 +3520,7 @@ var ZapoProvider = class {
3412
3520
  const stored = await this.#findStoredMessage(target);
3413
3521
  if (stored) target = this.#targetKeyFromStoredMessage(target, stored.key);
3414
3522
  if (!target.remoteJid && stored?.key.remoteJid) target.remoteJid = stored.key.remoteJid;
3415
- if (!target.remoteJid) {
3416
- if (protocol.type === proto.Message.ProtocolMessage.Type.MESSAGE_EDIT) {
3417
- this.#logEditDiagnostic("dropped_edit_without_chat", {
3418
- eventMessageId: event.key.id ?? void 0,
3419
- targetMessageId: target.id ?? void 0,
3420
- previousRecovered: stored !== void 0
3421
- }, "warn");
3422
- }
3423
- return;
3424
- }
3523
+ if (!target.remoteJid) return;
3425
3524
  const type = protocol.type;
3426
3525
  const mutationKey = this.#protocolDeliveryKey(event, target, type);
3427
3526
  if (mutationKey && this.#handledProtocolStore.has(mutationKey)) {
@@ -3452,13 +3551,7 @@ var ZapoProvider = class {
3452
3551
  return;
3453
3552
  }
3454
3553
  const editedContent = protocol.editedMessage ? this.#unwrapEditedContent(protocol.editedMessage) : void 0;
3455
- if (!editedContent) {
3456
- this.#logEditDiagnostic("dropped_edit_without_content", {
3457
- eventMessageId: event.key.id ?? void 0,
3458
- targetMessageId: target.id ?? void 0
3459
- }, "warn");
3460
- return;
3461
- }
3554
+ if (!editedContent) return;
3462
3555
  const pushName = event.pushName ?? stored?.pushName;
3463
3556
  const edited = {
3464
3557
  ...stored ?? {},
@@ -3471,31 +3564,18 @@ var ZapoProvider = class {
3471
3564
  ...pushName !== void 0 ? { pushName } : {}
3472
3565
  };
3473
3566
  const message = normalizeZapoMessage(edited);
3474
- if (!message) {
3475
- this.#logEditDiagnostic("dropped_edit_normalization_failed", {
3476
- eventMessageId: event.key.id ?? void 0,
3477
- targetMessageId: target.id ?? void 0,
3478
- previousRecovered: stored !== void 0
3479
- }, "warn");
3480
- return;
3481
- }
3567
+ if (!message) return;
3482
3568
  const previous = stored ? normalizeZapoMessage(stored) : void 0;
3483
3569
  if (mutationKey) this.#rememberHandledProtocol(mutationKey);
3484
3570
  this.#remember(edited);
3485
3571
  const editedByMe = event.key.fromMe === true;
3486
3572
  const editedById = event.key.participant ?? event.key.participantAlt ?? (editedByMe ? this.getCurrentUserIds()[0] : event.key.remoteJid ?? void 0);
3487
3573
  if (!previous) {
3488
- this.#logEditDiagnostic("emitting_without_previous_message", {
3574
+ this.#logger.debug("Zapo edit target was not present in the recent message cache.", {
3489
3575
  targetMessageId: target.id ?? void 0,
3490
3576
  chatId: target.remoteJid ?? void 0
3491
- }, "warn");
3577
+ });
3492
3578
  }
3493
- this.#logEditDiagnostic("emitting_message_edited", {
3494
- eventMessageId: event.key.id ?? void 0,
3495
- targetMessageId: target.id ?? void 0,
3496
- previousRecovered: previous !== void 0,
3497
- editedByMe
3498
- });
3499
3579
  void this.#events.emit("messageEdited", {
3500
3580
  key: message.keys,
3501
3581
  ...previous ? { previous } : {},
@@ -4591,14 +4671,6 @@ var WhaNextZapoLogger = class _WhaNextZapoLogger {
4591
4671
  this.#logger.info(message, this.#merge(context));
4592
4672
  }
4593
4673
  warn(message, context) {
4594
- if (message.startsWith("addon parent message secret not found") && context?.kind === "message_edit") {
4595
- this.#logger.warn("AntiEdit diagnostic", {
4596
- diagnostic: "antiedit",
4597
- stage: "decrypt_failed_missing_parent_secret",
4598
- ...this.#merge(context)
4599
- });
4600
- return;
4601
- }
4602
4674
  this.#logger.warn(message, this.#merge(context));
4603
4675
  }
4604
4676
  error(message, context) {