Compare commits

..

98 Commits

Author SHA1 Message Date
Renato Alcara 104d2c47f5 chore: update WhatsApp Web version to v2.3000.1038147544 (#380)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-25 06:17:10 -03:00
Renato Alcara 924c54f1f8 chore: update WhatsApp Web version to v2.3000.1038063777 (#378)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-24 06:31:31 -03:00
github-actions[bot] e34ef5479d chore: update proto/version to v2.3000.1038024963 (#377)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-24 01:03:36 -03:00
Renato Alcara 5e8d30d709 chore: update WhatsApp Web version to v2.3000.1037968143 (#376)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-23 06:31:41 -03:00
github-actions[bot] ac93080595 chore: update proto/version to v2.3000.1037953056 (#375)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-23 01:01:23 -03:00
Renato Alcara 37d1bdfb76 chore: update WhatsApp Web version to v2.3000.1037878879 (#374)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-22 06:29:59 -03:00
github-actions[bot] 76b2007f27 chore: update proto/version to v2.3000.1037847281 (#373)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-22 01:00:13 -03:00
Renato Alcara 676912c295 Fix/remove vo raw debug logs (#372)
* chore: remove temporary [VO_RAW] debug logs from handleMessage

* chore: remove temporary VO_TRACE debug logger.info from handleMessage
2026-04-21 15:09:00 -03:00
Renato Alcara 06d9f35ca9 chore: remove temporary [VO_RAW] debug logs from handleMessage (#371)
chore: remove temporary [VO_RAW] debug logs from handleMessage (#371)
2026-04-21 15:00:00 -03:00
Renato Alcara 4cadcd1c57 chore: update WhatsApp Web version to v2.3000.1037786138 (#370)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-21 06:30:57 -03:00
github-actions[bot] 937b26edcd chore: update proto/version to v2.3000.1037753511 (#368)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-21 01:01:09 -03:00
Renato Alcara acad0c06dc chore: update WhatsApp Web version to v2.3000.1037679412 (#369)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-20 06:33:26 -03:00
Renato Alcara 98995aae89 chore: update WhatsApp Web version to v2.3000.1037659536 (#367)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-19 06:15:22 -03:00
github-actions[bot] 7a7d92e3f8 chore: update proto/version to v2.3000.1037654574 (#366)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-19 01:01:54 -03:00
Renato Alcara 1fbaa284f9 chore: update WhatsApp Web version to v2.3000.1037639827 (#365)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-18 06:15:03 -03:00
github-actions[bot] 9c7dd0460c chore: update proto/version to v2.3000.1037622359 (#364)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-18 00:55:26 -03:00
Renato Alcara 579277cb29 chore: update WhatsApp Web version to v2.3000.1037554587 (#363)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-17 06:29:43 -03:00
github-actions[bot] bb0cb90260 chore: update proto/version to v2.3000.1037539916 (#358)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-17 01:01:19 -03:00
Renato Alcara 56955e12f5 chore: update WhatsApp Web version to v2.3000.1037471135 (#362)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-16 06:29:30 -03:00
Renato Alcara b7d9f2b866 chore: update WhatsApp Web version to v2.3000.1037376462 (#361)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-15 06:30:13 -03:00
Renato Alcara daaf0bc813 chore: update WhatsApp Web version to v2.3000.1037280436 (#360)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-14 06:29:53 -03:00
Renato Alcara bf95867089 Merge branch 'master' of https://github.com/rsalcara/InfiniteAPI 2026-04-13 07:20:06 -03:00
Renato Alcara 9fac156474 feat: add dual protocol support (web/native) via BAILEYS_PROTOCOL env
Adds experimental native protocol mode (WAM\x05) as alternative to
web protocol (WA\x06\x03). Configurable via BAILEYS_PROTOCOL env var:
- web (default): standard WhatsApp Web protocol
- native: Android native protocol (may enable view-once media on companions)

When native mode is enabled:
- NOISE_WA_HEADER changes from WA\x06\x03 to WAM\x05
- DICT_VERSION changes from 3 to 5
- WebInfo is omitted from ClientPayload

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:19:55 -03:00
Renato Alcara 230e08037b chore: update WhatsApp Web version to v2.3000.1037186195 (#359)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-13 06:33:07 -03:00
Renato Alcara 77ffe59911 Revert "feat: add WebdPayload with E2E media support and features bitmask"
This reverts commit 313f304ddf.
2026-04-13 00:01:32 -03:00
Renato Alcara 313f304ddf feat: add WebdPayload with E2E media support and features bitmask
Add webdPayload to WebInfo during connection with:
- supportsE2EImage/Video/Audio/Document = true
- supportsMediaRetry = true
- features bitmask with view-once capability flag

This may enable the WA server to deliver view-once media content
to Baileys companions instead of just <unavailable> placeholders.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:43:17 -03:00
Renato Alcara a74fbb08a6 fix: request view-once content via PDO from primary phone
When receiving <unavailable type='view_once'/>, attempt to request
the actual media content from the primary phone via PDO
(Peer Data Operation / PLACEHOLDER_MESSAGE_RESEND).

If the phone responds, the full media (url, mediaKey, directPath)
arrives via messages.upsert, allowing consumers to display the image.

Falls back to placeholder if PDO fails or phone doesn't respond.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:21:02 -03:00
Renato Alcara 0e6bad72bb fix: emit view-once placeholder from unavailable handler
The early unavailable handler at line ~2261 was intercepting
view_once stanzas and silently discarding them (sendMessageAck + return)
before the view_once_unavailable_fanout handler could emit them.

Now emits a viewOnceMessage placeholder with isViewOnce=true so
consumers receive the event in messages.upsert.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:14:13 -03:00
Renato Alcara 08c19e2951 merge: resolve conflict in messages-recv.ts (keep viewOnceMessage placeholder) 2026-04-12 11:55:12 -03:00
Renato Alcara 90061dd6a1 fix: allow msmsg decryption and emit view-once unavailable fanout (#355)
Two issues found during view-once testing on linked devices:

1. msmsg block removal:
   The "temporary fix" that discarded all msmsg-type encrypted messages
   was also blocking legitimate view-once messages. On linked devices,
   view-once arrives as msmsg encryption type. The block was originally
   added to prevent crashes from missing messageSecret, but the existing
   try/catch in the decrypt path already handles those errors gracefully
   by marking the message as a CIPHERTEXT stub.

2. view_once_unavailable_fanout emission:
   When a linked device receives a view-once, the WA server sends only
   <unavailable type="view_once"/> (no <enc> with actual media content).
   Previously this was silently acknowledged without emitting any event,
   so consumers (zpro, astra-api, etc.) never knew a view-once arrived.
   Now it emits the message as a CIPHERTEXT stub with key.isViewOnce=true
   so consumers can display "view-once message received" in their UI.

Co-Authored-By: Renato Alcara
2026-04-12 11:46:42 -03:00
Renato Alcara d014a2496e fix: allow msmsg decryption and emit view-once unavailable fanout (#355)
Two issues found during view-once testing on linked devices:

1. msmsg block removal:
   The "temporary fix" that discarded all msmsg-type encrypted messages
   was also blocking legitimate view-once messages. On linked devices,
   view-once arrives as msmsg encryption type. The block was originally
   added to prevent crashes from missing messageSecret, but the existing
   try/catch in the decrypt path already handles those errors gracefully
   by marking the message as a CIPHERTEXT stub.

2. view_once_unavailable_fanout emission:
   When a linked device receives a view-once, the WA server sends only
   <unavailable type="view_once"/> (no <enc> with actual media content).
   Previously this was silently acknowledged without emitting any event,
   so consumers (zpro, astra-api, etc.) never knew a view-once arrived.
   Now it emits the message as a CIPHERTEXT stub with key.isViewOnce=true
   so consumers can display "view-once message received" in their UI.

Co-Authored-By: Renato Alcara
2026-04-12 10:02:30 -03:00
Renato Alcara 47b0940428 chore: update WhatsApp Web version to v2.3000.1037166441 (#357)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-12 06:15:16 -03:00
github-actions[bot] 90937af374 chore: update proto/version to v2.3000.1037162330 (#356)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-12 01:00:42 -03:00
Renato Alcara 703b5da554 chore: update WhatsApp Web version to v2.3000.1037137890 (#354)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-11 06:13:10 -03:00
github-actions[bot] 8c61ce5f8e chore: update proto/version to v2.3000.1037082622 (#353)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-11 00:48:54 -03:00
Renato Alcara 000932accd chore: update WhatsApp Web version to v2.3000.1037061899 (#352)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-10 06:25:40 -03:00
github-actions[bot] 8df39c320e chore: update proto/version to v2.3000.1037038379 (#351)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-10 01:00:21 -03:00
Renato Alcara 22e758b1e6 chore: update WhatsApp Web version to v2.3000.1036978211 (#350)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-09 06:26:10 -03:00
github-actions[bot] f473484852 chore: update proto/version to v2.3000.1036961647 (#349)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-09 00:55:22 -03:00
Renato Alcara 4dde4c5500 chore: update WhatsApp Web version to v2.3000.1036893687 (#348)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-08 06:22:46 -03:00
github-actions[bot] e3cf104b59 chore: update proto/version to v2.3000.1036861941 (#347)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-08 00:56:53 -03:00
Renato Alcara aa0a3c2b71 chore: update WhatsApp Web version to v2.3000.1036798793 (#346)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-07 06:23:04 -03:00
github-actions[bot] bdbad50074 chore: update proto/version to v2.3000.1036772203 (#345)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-07 00:56:17 -03:00
Renato Alcara 45b322abac chore: update WhatsApp Web version to v2.3000.1036709653 (#344)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-06 06:25:21 -03:00
github-actions[bot] 3c3cf32027 chore: update proto/version to v2.3000.1036704126 (#343)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-06 00:59:26 -03:00
Renato Alcara 99d85498fb chore: update WhatsApp Web version to v2.3000.1036691526 (#342)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-05 06:12:28 -03:00
github-actions[bot] d6414f203b chore: update proto/version to v2.3000.1036688876 (#340)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-05 00:58:23 -03:00
Renato Alcara b98df92d8c chore: update WhatsApp Web version to v2.3000.1036673976 (#341)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-04 06:11:52 -03:00
Renato Alcara 00a8113d47 chore: update WhatsApp Web version to v2.3000.1036606219 (#339)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-03 06:17:29 -03:00
github-actions[bot] 670ac582f9 chore: update proto/version to v2.3000.1036588306 (#338)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-03 00:54:16 -03:00
Renato Alcara 08910c61e1 chore: update WhatsApp Web version to v2.3000.1036512893 (#337)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-02 06:20:16 -03:00
github-actions[bot] cb5e42c46e chore: update proto/version to v2.3000.1036469328 (#336)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-02 00:53:53 -03:00
Renato Alcara 30e986ee67 chore: update WhatsApp Web version to v2.3000.1036420555 (#335)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-01 06:24:15 -03:00
github-actions[bot] 60302d73d5 chore: update proto/version to v2.3000.1036375660 (#334)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-01 01:01:11 -03:00
Renato Alcara 3baa494fad chore: update WhatsApp Web version to v2.3000.1036326359 (#333)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-31 06:54:53 -03:00
github-actions[bot] 59cad85544 chore: update proto/version to v2.3000.1036244875 (#332)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-31 02:02:42 -03:00
Renato Alcara ad53bc0982 chore: update WhatsApp Web version to v2.3000.1036229535 (#331)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-30 07:06:44 -03:00
github-actions[bot] fb8f51be52 chore: update proto/version to v2.3000.1036224280 (#330)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-30 02:15:18 -03:00
Renato Alcara 9a7d43d4ba chore: update WhatsApp Web version to v2.3000.1036208776 (#329)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-29 06:27:46 -03:00
github-actions[bot] 578efe945f chore: update proto/version to v2.3000.1036204984 (#328)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-29 02:03:52 -03:00
Renato Alcara e999fc1aa1 chore: update WhatsApp Web version to v2.3000.1036185592 (#327)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-28 06:27:30 -03:00
github-actions[bot] 9fd88b533d chore: update proto/version to v2.3000.1036173230 (#326)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-28 01:46:40 -03:00
Renato Alcara 43f3525437 chore: update WhatsApp Web version to v2.3000.1036094556 (#325)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-27 06:44:22 -03:00
github-actions[bot] fdcbd3fc1d chore: update proto/version to v2.3000.1036065881 (#324)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-27 02:00:33 -03:00
Renato Alcara cdc89316f0 chore: update WhatsApp Web version to v2.3000.1036016053 (#323)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-26 06:53:02 -03:00
github-actions[bot] 3050c329c5 chore: update proto/version to v2.3000.1036000318 (#319)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-26 01:59:23 -03:00
Renato Alcara 3ef7474842 fix: respect caller-provided waveform for PTT audio messages (#322)
* fix: respect caller-provided waveform for PTT audio messages

When sending PTT audio with an explicit waveform, Baileys unconditionally
overwrites it with getAudioWaveform() which returns undefined when
audio-decode is not installed — resulting in a flat line on WhatsApp.

Now only invokes getAudioWaveform() when no waveform is already provided,
matching the existing pattern used for jpegThumbnail (line 241).

Validated via CDP + Frida interception: WhatsApp waveform format is
64 bytes Uint8Array, values 0-99, byte-for-byte identical between
Android (audio_data table) and WA Web (model-storage IDB).

Ref: WhiskeySockets/Baileys#2443

Co-Authored-By: Renato Alcara
2026-03-25 15:51:52 -03:00
Renato Alcara 9dfd31d109 chore: update WhatsApp Web version to v2.3000.1035913242 (#321)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-25 06:42:47 -03:00
Renato Alcara e686c5a2e1 chore: update WhatsApp Web version to v2.3000.1035819858 (#320)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-24 06:44:53 -03:00
Renato Alcara 466c14ff82 chore: update WhatsApp Web version to v2.3000.1035718650 (#318)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-23 06:53:55 -03:00
Renato Alcara 03bc03b318 Merge branch 'master' of https://github.com/rsalcara/InfiniteAPI 2026-03-23 01:59:15 -03:00
Renato Alcara d83971d15e fix: remove duplicate isViewOnceMsg/viewOnceMeRecipients declarations
Merge duplicou o bloco — TS2451 Cannot redeclare block-scoped variable.
Mantida a versão correta (verifica viewOnceInner?.imageMessage?.viewOnce).

Co-Authored-By: Renato Alcara
2026-03-23 01:59:08 -03:00
github-actions[bot] 14f0321d7c chore: update proto/version to v2.3000.1035715252 (#317)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-23 01:56:01 -03:00
Renato Alcara b65a8a334b chore: resolve merge conflict in messages-send.ts (comment-only)
Conflict was between Portuguese and English comment for phash/DSM block.
Kept HEAD (our) version.

Co-Authored-By: Renato Alcara
2026-03-23 01:54:46 -03:00
Renato Alcara 28e9e5b912 feat: implement view-once message sending and receiving
- Add viewonce-image/video/audio to MEDIA_PATH_MAP and MEDIA_HKDF_KEY_MAPPING
- messages-send: use viewOnceMessageV2 wrapper, omit companion devices for DSM
  (server auto-generates <unavailable type="view_once"/> for linked devices)
- messages-send: fix mediatype detection by unwrapping viewOnce before getMediaType
- messages-recv: handle <unavailable type="view_once"/> sync from primary device
- messages-recv: note — do NOT send view_once_read from linked device

Co-Authored-By: Renato Alcara
2026-03-23 01:53:55 -03:00
Renato Alcara 962e7de0bf chore: mark upstream commits as reviewed (3841abca35 ad5ea817f7 d077902695 c4e5d1262a 802d5f6efa)
- 3841abca35 / ad5ea817f7: WA version updates — applied separately via our own version bumps
- d077902695: WA version update — same as above
- c4e5d1262a: Fix ack handling — high-risk 2718-line diff in messages-recv.ts, intentionally skipped
- 802d5f6efa: Fix mentions regression — already covered by existing rsalcara contextInfo logic
- 6afde71691: feat groups mentionAll — applied in previous commit
2026-03-23 01:53:00 -03:00
Renato Alcara c6cb29fd80 feat: groups mention all + fix view-once upload path and wrapper
- Add mentionAll?: boolean to Mentionable type (upstream 6afde71691)
- Set contextInfo.nonJidMentions=1 for @everyone group mentions
- Use viewOnceMessageV2 (field 55) wrapper instead of legacy viewOnceMessage (field 37)
- Set viewOnce=true on inner imageMessage/videoMessage/audioMessage
- Route view-once media uploads to /o1/mms/* CDN via mediaTypeOverride

Co-Authored-By: Renato Alcará
2026-03-23 01:52:43 -03:00
Renato Alcara a283d858c6 docs: rewrite INTERACTIVE_MESSAGES.md — complete API guide
Full rewrite covering all 8 interactive message types:
1. Menu Texto
2. Botões Quick Reply (tested up to 16 buttons)
3. Botões CTA — URL, Copy, Call
4. Lista Dropdown (10 sections × 3 rows = 30 items max)
5. Enquete/Poll
6. Apenas Botões Reply sem CTA
7. Apenas CTAs sem Quick Reply
8. Carrossel com Imagens (up to 10 cards)

Each type includes:
- Complete curl example with realistic data
- All parameters documented with types and limits
- Webhook payload structure received on click/select
- JavaScript helpers to parse each response type

Also includes:
- Platform compatibility table (Android / iOS / WA Web)
- Universal webhook router for all interactive response types
- Complete limits table per message type
- Account restrictions (hosted vs non-hosted)

Co-Authored-By: Renato Alará
2026-03-23 01:27:45 -03:00
Renato Alcara 9c626801b0 docs: add hosted account restriction warning to VIEW_ONCE.md
Accounts registered as hosted (Meta Business Cloud API) have view-once
blocked by the WA server for all clients (Android, iOS, Web).
This is a server-side policy with no code workaround.

Added prominent warning block in section 1 and dedicated row in the
limitations table.

Co-Authored-By: Renato Alcará
2026-03-23 01:17:27 -03:00
Renato Alcara 4ca1878705 docs: add VIEW_ONCE.md — view-once implementation guide
Complete guide for the view-once (visualização única) feature:
- Send examples (curl) for image, video and audio
- Webhook payload structure with key.isViewOnce detection
- JavaScript helpers to identify and access view-once media
- Frontend/CRM rendering guide with placeholder after open
- Implemented code with inline explanations
- Full send/receive flow diagram
- Known limitations and validation checklist

Co-Authored-By: Renato Alcara
2026-03-23 01:12:36 -03:00
Renato Alcara 452df5d409 Feat/view once receive (#316)
* fix: detect view-once media (image/video/audio) on stanza 1 for linked devices
2026-03-23 00:38:05 -03:00
Renato Alcara e633d62a3b chore: update WhatsApp Web version to v2.3000.1035697879 (#315)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-22 06:22:56 -03:00
github-actions[bot] 2a321b881e chore: update proto/version to v2.3000.1035693752 (#314)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-22 01:46:05 -03:00
Rajeh Taher 802d5f6efa messages: Fix regression where missing contextInfo caused no mentions 2026-03-22 02:43:03 +02:00
Renato Alcara e0038bde36 chore: update WhatsApp Web version to v2.3000.1035675348 (#313)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-21 06:22:45 -03:00
github-actions[bot] 1cbf1b18b7 chore: update proto/version to v2.3000.1035661795 (#312)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-21 01:22:14 -03:00
Renato Alcara c3a021fcde fix: use newsletter-specific upload paths for channel media (#311)
fix: use newsletter-specific upload paths for channel media (#311)
2026-03-20 12:02:36 -03:00
Renato Alcara e6ea7e4563 Feat/view once receive (#310)
* fix: unwrap viewOnceMessage in getMediaType so enc node gets mediatype attribute

Without this fix, view-once media (image/video/audio) was sent without
mediatype="image/video/audio" on the enc node because getMediaType only
checked the top-level message fields. The viewOnceMessage wrapper hides
the inner imageMessage, causing mediatype to be empty and WA servers to
silently drop the message on the recipient side.
2026-03-20 10:49:25 -03:00
Renato Alcara 3ab578aadf fix: detect view-once media (image/video/audio) (#309)
* fix: detect view-once media (image/video/audio) on stanza 1 for linked devices

When a view-once arrives at a linked device (InfiniteAPI), the server sends:
- Stanza 1: enc payload with full media metadata (mediaKey, directPath) wrapped in
  viewOnceMessage > imageMessage/videoMessage/audioMessage with viewOnce: true
- Stanza 2: unavailable view_once fanout placeholder (already handled)

Previously only stanza 2 set key.isViewOnce = true. Stanza 1 was emitted as
plain media with no view-once indicator, making it indistinguishable from regular
media on the consumer side (messages.upsert).

This fix inspects the decrypted proto for viewOnceMessage (v1/v2/v2Ext) wrappers
containing a media message with viewOnce=true and propagates key.isViewOnce=true.

The viewOnceMessage wrapper is also used for interactive messages (carousel,
buttons, lists) but those carry interactiveMessage/listMessage inside -- never
imageMessage.viewOnce / videoMessage.viewOnce / audioMessage.viewOnce.
The distinction is unambiguous and does not affect interactive message handling.

Verified via WA Desktop
2026-03-20 10:11:49 -03:00
Renato Alcara 5926da9493 Merge branch 'master' of https://github.com/rsalcara/InfiniteAPI 2026-03-19 12:22:07 -03:00
Renato Alcara 2b2c3f9155 Merge branch 'master' of https://github.com/rsalcara/InfiniteAPI 2026-03-19 11:24:01 -03:00
github-actions[bot] d077902695 chore: update WhatsApp Web version (#2420)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-15 22:49:39 +02:00
João Lucas c4e5d1262a Fix ack handling (#2373)
* fix: ack handling to match wa web

* fix: prevent duplicate ack on message errors
2026-03-13 21:45:49 +02:00
Matheus Filype 6afde71691 feat: groups mention all (#2396)
* fix: improve message resend logic by adding checks for message IDs

* Revert "fix: improve message resend logic by adding checks for message IDs"

This reverts commit c03f9d8e6fc6cbfbb9d1f8f67c169700e704213d.

* feat: add mentionAll support to message context for group mentions

* fix: improve readability of condition for mentions and mentionAll in generateWAMessageContent

* Apply suggestions from code review

Co-authored-by: Rajeh Taher <rajeh@reforward.dev>

---------

Co-authored-by: Rajeh Taher <rajeh@reforward.dev>
2026-03-13 19:49:16 +02:00
Renato Alcara 6060293793 fix: jimp detection for thumbnail generation
Fix typeof check: Jimp constructor is 'function' not 'object' in
jimp v1.6+. Without this fix, jpegThumbnail is never generated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:41:54 -03:00
Renato Alcara afce764ce9 Revert "fix: carousel renders on WhatsApp Web without F5"
This reverts commit f2c14f9ad6.
2026-03-07 00:41:38 -03:00
ShellTear ad5ea817f7 Merge pull request #2360 from WhiskeySockets/update-version/stable 2026-02-25 01:33:33 +09:00
github-actions[bot] 3841abca35 chore: update WhatsApp Web version 2026-02-22 00:43:34 +00:00
13 changed files with 3608 additions and 491 deletions
+92 -2
View File
@@ -1,7 +1,7 @@
syntax = "proto3"; syntax = "proto3";
package proto; package proto;
/// WhatsApp Version: 2.3000.1035577069 /// WhatsApp Version: 2.3000.1038024963
message ADVDeviceIdentity { message ADVDeviceIdentity {
optional uint32 rawId = 1; optional uint32 rawId = 1;
@@ -421,6 +421,12 @@ message BotCapabilityMetadata {
} }
} }
message BotCommandMetadata {
optional string commandName = 1;
optional string commandDescription = 2;
optional string commandPrompt = 3;
}
message BotDocumentMessageMetadata { message BotDocumentMessageMetadata {
optional DocumentPluginType pluginType = 1; optional DocumentPluginType pluginType = 1;
enum DocumentPluginType { enum DocumentPluginType {
@@ -652,6 +658,7 @@ message BotMetadata {
optional BotRenderingConfigMetadata botRenderingConfigMetadata = 36; optional BotRenderingConfigMetadata botRenderingConfigMetadata = 36;
optional BotInfrastructureDiagnostics botInfrastructureDiagnostics = 37; optional BotInfrastructureDiagnostics botInfrastructureDiagnostics = 37;
optional AIMediaCollectionMetadata aiMediaCollectionMetadata = 38; optional AIMediaCollectionMetadata aiMediaCollectionMetadata = 38;
optional BotCommandMetadata commandMetadata = 39;
optional bytes internalMetadata = 999; optional bytes internalMetadata = 999;
} }
@@ -703,6 +710,7 @@ enum BotMetricsEntryPoint {
WEB_NAVIGATION_BAR = 47; WEB_NAVIGATION_BAR = 47;
GROUP_MEMBER = 54; GROUP_MEMBER = 54;
CHATLIST_SEARCH = 55; CHATLIST_SEARCH = 55;
NEW_CHAT_LIST = 56;
} }
message BotMetricsMetadata { message BotMetricsMetadata {
optional string destinationId = 1; optional string destinationId = 1;
@@ -1467,6 +1475,10 @@ message ContextInfo {
optional string wtwaWebsiteUrl = 26; optional string wtwaWebsiteUrl = 26;
optional string adPreviewUrl = 27; optional string adPreviewUrl = 27;
optional bool containsCtwaFlowsAutoReply = 28; optional bool containsCtwaFlowsAutoReply = 28;
optional int32 agmThumbnailStrategy = 29;
optional int32 agmTitleStrategy = 30;
optional int32 agmSubtitleStrategy = 31;
optional int32 agmHeaderInteractionStrategy = 32;
enum AdType { enum AdType {
CTWA = 0; CTWA = 0;
CAWC = 1; CAWC = 1;
@@ -1652,6 +1664,7 @@ message DeviceCapabilities {
optional bool companionSupportEnabled = 2; optional bool companionSupportEnabled = 2;
optional bool campaignSyncEnabled = 3; optional bool campaignSyncEnabled = 3;
optional bool insightsSyncEnabled = 4; optional bool insightsSyncEnabled = 4;
optional int32 recipientLimit = 5;
} }
enum ChatLockSupportLevel { enum ChatLockSupportLevel {
@@ -1727,6 +1740,8 @@ message DeviceProps {
optional uint32 initialSyncMaxMessagesPerChat = 20; optional uint32 initialSyncMaxMessagesPerChat = 20;
optional bool supportManusHistory = 21; optional bool supportManusHistory = 21;
optional bool supportHatchHistory = 22; optional bool supportHatchHistory = 22;
repeated string supportedBotChannelFbids = 23;
optional bool supportInlineContacts = 24;
} }
enum PlatformType { enum PlatformType {
@@ -1913,6 +1928,16 @@ message GroupParticipant {
} }
} }
message GroupRootKeyShare {
repeated GroupRootKeyShareEntry keys = 1;
}
message GroupRootKeyShareEntry {
optional bytes groupRootKey = 1;
optional string keyId = 2;
optional int64 expiryTimestampMs = 3;
}
message HandshakeMessage { message HandshakeMessage {
optional ClientHello clientHello = 2; optional ClientHello clientHello = 2;
optional ServerHello serverHello = 3; optional ServerHello serverHello = 3;
@@ -1935,6 +1960,7 @@ message HandshakeMessage {
optional bool sendServerHelloPaddedBytes = 7; optional bool sendServerHelloPaddedBytes = 7;
optional bool simulateXxkemFs = 8; optional bool simulateXxkemFs = 8;
optional HandshakeMessage.HandshakePqMode pqMode = 9; optional HandshakeMessage.HandshakePqMode pqMode = 9;
optional bytes extendedEphemeral = 10;
} }
enum HandshakePqMode { enum HandshakePqMode {
@@ -1954,6 +1980,7 @@ message HandshakeMessage {
optional bytes payload = 3; optional bytes payload = 3;
optional bytes extendedStatic = 4; optional bytes extendedStatic = 4;
optional bytes paddingBytes = 5; optional bytes paddingBytes = 5;
optional bytes extendedCiphertext = 6;
} }
} }
@@ -1977,6 +2004,8 @@ message HistorySync {
optional bytes shareableChatIdentifierEncryptionKey = 17; optional bytes shareableChatIdentifierEncryptionKey = 17;
repeated Account accounts = 18; repeated Account accounts = 18;
optional bytes nctSalt = 19; optional bytes nctSalt = 19;
repeated InlineContact inlineContacts = 20;
optional bool inlineContactsProvided = 21;
enum BotAIWaitListState { enum BotAIWaitListState {
IN_WAITLIST = 0; IN_WAITLIST = 0;
AI_AVAILABLE = 1; AI_AVAILABLE = 1;
@@ -2071,6 +2100,14 @@ message InThreadSurveyMetadata {
} }
message InlineContact {
optional string pnJid = 1;
optional string lidJid = 2;
optional string fullName = 3;
optional string firstName = 4;
optional string username = 5;
}
message InteractiveAnnotation { message InteractiveAnnotation {
repeated Point polygonVertices = 1; repeated Point polygonVertices = 1;
optional bool shouldSkipConfirmation = 4; optional bool shouldSkipConfirmation = 4;
@@ -2310,6 +2347,7 @@ message Message {
optional ConditionalRevealMessage conditionalRevealMessage = 120; optional ConditionalRevealMessage conditionalRevealMessage = 120;
optional PollAddOptionMessage pollAddOptionMessage = 121; optional PollAddOptionMessage pollAddOptionMessage = 121;
optional EventInviteMessage eventInviteMessage = 122; optional EventInviteMessage eventInviteMessage = 122;
optional GroupRootKeyShare groupRootKeyShare = 123;
message AlbumMessage { message AlbumMessage {
optional uint32 expectedImageCount = 2; optional uint32 expectedImageCount = 2;
optional uint32 expectedVideoCount = 3; optional uint32 expectedVideoCount = 3;
@@ -2501,6 +2539,7 @@ message Message {
UNKNOWN = 0; UNKNOWN = 0;
CONTROL_PASSED = 1; CONTROL_PASSED = 1;
CONTROL_TAKEN = 2; CONTROL_TAKEN = 2;
INFO = 3;
} }
message CloudAPIThreadControlNotificationContent { message CloudAPIThreadControlNotificationContent {
optional string handoffNotificationText = 1; optional string handoffNotificationText = 1;
@@ -3282,6 +3321,7 @@ message Message {
optional int32 onDemandMsgCount = 4; optional int32 onDemandMsgCount = 4;
optional int64 oldestMsgTimestampMs = 5; optional int64 oldestMsgTimestampMs = 5;
optional string accountLid = 6; optional string accountLid = 6;
optional bool supportInlineResponse = 7;
} }
message PlaceholderMessageResendRequest { message PlaceholderMessageResendRequest {
@@ -3368,6 +3408,7 @@ message Message {
ERROR_REQUEST_ON_NON_SMB_PRIMARY = 4; ERROR_REQUEST_ON_NON_SMB_PRIMARY = 4;
ERROR_HOSTED_DEVICE_NOT_CONNECTED = 5; ERROR_HOSTED_DEVICE_NOT_CONNECTED = 5;
ERROR_HOSTED_DEVICE_LOGIN_TIME_NOT_SET = 6; ERROR_HOSTED_DEVICE_LOGIN_TIME_NOT_SET = 6;
ERROR_MULTI_PROVIDER_NOT_CONFIGURED = 7;
} }
message HistorySyncChunkRetryResponse { message HistorySyncChunkRetryResponse {
optional Message.HistorySyncType syncType = 1; optional Message.HistorySyncType syncType = 1;
@@ -3616,7 +3657,6 @@ message Message {
GROUP_MEMBER_LABEL_CHANGE = 30; GROUP_MEMBER_LABEL_CHANGE = 30;
AI_MEDIA_COLLECTION_MESSAGE = 31; AI_MEDIA_COLLECTION_MESSAGE = 31;
MESSAGE_UNSCHEDULE = 32; MESSAGE_UNSCHEDULE = 32;
BOT_UNLINK_MESSAGE = 33;
} }
} }
@@ -3767,6 +3807,7 @@ message Message {
optional bool isLottie = 21; optional bool isLottie = 21;
optional string accessibilityLabel = 22; optional string accessibilityLabel = 22;
optional int32 premium = 24; optional int32 premium = 24;
optional string emojis = 25;
} }
message StickerPackMessage { message StickerPackMessage {
@@ -3987,6 +4028,7 @@ message MessageContextInfo {
optional LimitSharing limitSharingV2 = 14; optional LimitSharing limitSharingV2 = 14;
repeated ThreadID threadId = 15; repeated ThreadID threadId = 15;
optional WebLinkRenderConfig weblinkRenderConfig = 16; optional WebLinkRenderConfig weblinkRenderConfig = 16;
optional bytes teeBotMetadata = 17;
enum MessageAddonExpiryType { enum MessageAddonExpiryType {
STATIC = 1; STATIC = 1;
DEPENDENT_ON_PARENT = 2; DEPENDENT_ON_PARENT = 2;
@@ -4179,6 +4221,8 @@ enum MutationProps {
CUSTOMER_DATA_ACTION = 83; CUSTOMER_DATA_ACTION = 83;
SUBSCRIPTIONS_SYNC_V2_ACTION = 84; SUBSCRIPTIONS_SYNC_V2_ACTION = 84;
THREAD_PIN_ACTION = 85; THREAD_PIN_ACTION = 85;
AUTO_ORGANIZE_BUSINESS_CHAT_SETTING = 86;
BIZ_AI_SETTINGS_NUDGE_ACTION = 87;
SHARE_OWN_PN = 10001; SHARE_OWN_PN = 10001;
BUSINESS_BROADCAST_ACTION = 10002; BUSINESS_BROADCAST_ACTION = 10002;
AI_THREAD_DELETE_ACTION = 10003; AI_THREAD_DELETE_ACTION = 10003;
@@ -4492,6 +4536,12 @@ message ReportingTokenInfo {
optional bytes reportingTag = 1; optional bytes reportingTag = 1;
} }
message ScheduledMessageMetadata {
optional string revealKeyId = 1;
optional bytes revealKey = 2;
optional uint64 scheduledTime = 3;
}
message SenderKeyDistributionMessage { message SenderKeyDistributionMessage {
optional uint32 id = 1; optional uint32 id = 1;
optional uint32 iteration = 2; optional uint32 iteration = 2;
@@ -4818,6 +4868,8 @@ message SyncActionValue {
optional CustomerDataAction customerDataAction = 83; optional CustomerDataAction customerDataAction = 83;
optional SubscriptionsSyncV2Action subscriptionsSyncV2Action = 84; optional SubscriptionsSyncV2Action subscriptionsSyncV2Action = 84;
optional ThreadPinAction threadPinAction = 85; optional ThreadPinAction threadPinAction = 85;
optional AutoOrganizeBusinessChatSetting autoOrganizeBusinessChatSetting = 86;
optional BizAISettingsNudgeAction bizAiSettingsNudgeAction = 87;
message AgentAction { message AgentAction {
optional string name = 1; optional string name = 1;
optional int32 deviceID = 2; optional int32 deviceID = 2;
@@ -4837,6 +4889,10 @@ message SyncActionValue {
optional SyncActionValue.SyncActionMessageRange messageRange = 2; optional SyncActionValue.SyncActionMessageRange messageRange = 2;
} }
message AutoOrganizeBusinessChatSetting {
optional bool autoOrganize = 1;
}
message AvatarUpdatedAction { message AvatarUpdatedAction {
optional AvatarEventType eventType = 1; optional AvatarEventType eventType = 1;
repeated SyncActionValue.StickerAction recentAvatarStickers = 2; repeated SyncActionValue.StickerAction recentAvatarStickers = 2;
@@ -4847,6 +4903,20 @@ message SyncActionValue {
} }
} }
message BizAISettingsNudgeAction {
optional BizAISettingsCategory category = 1;
optional int64 version = 2;
optional int64 updatedAtMs = 3;
enum BizAISettingsCategory {
UNKNOWN = 0;
INSTRUCTIONS = 1;
RESPONSE_SETTINGS = 2;
EXAMPLE_RESPONSES = 3;
KNOWLEDGE = 4;
LEAD_GEN = 5;
}
}
message BotWelcomeRequestAction { message BotWelcomeRequestAction {
optional bool isSent = 1; optional bool isSent = 1;
} }
@@ -5022,6 +5092,7 @@ message SyncActionValue {
DRAFTED = 8; DRAFTED = 8;
AI_HANDOFF = 9; AI_HANDOFF = 9;
CHANNELS = 10; CHANNELS = 10;
AI_RESPONDING = 11;
} }
} }
@@ -5237,6 +5308,8 @@ message SyncActionValue {
optional bool isStatusNotificationEnabled = 29; optional bool isStatusNotificationEnabled = 29;
optional int32 statusNotificationToneId = 30; optional int32 statusNotificationToneId = 30;
optional bool shouldPlaySoundForCallNotification = 31; optional bool shouldPlaySoundForCallNotification = 31;
optional string chatThemeId = 32;
optional string colorSchemeId = 33;
enum DisplayMode { enum DisplayMode {
DISPLAY_MODE_UNKNOWN = 0; DISPLAY_MODE_UNKNOWN = 0;
ALWAYS = 1; ALWAYS = 1;
@@ -5281,6 +5354,8 @@ message SyncActionValue {
IS_STATUS_NOTIFICATION_ENABLED = 29; IS_STATUS_NOTIFICATION_ENABLED = 29;
STATUS_NOTIFICATION_TONE_ID = 30; STATUS_NOTIFICATION_TONE_ID = 30;
SHOULD_PLAY_SOUND_FOR_CALL_NOTIFICATION = 31; SHOULD_PLAY_SOUND_FOR_CALL_NOTIFICATION = 31;
CHAT_THEME_ID = 32;
COLOR_SCHEME_ID = 33;
} }
enum SettingPlatform { enum SettingPlatform {
PLATFORM_UNKNOWN = 0; PLATFORM_UNKNOWN = 0;
@@ -5304,11 +5379,22 @@ message SyncActionValue {
repeated string userJid = 2; repeated string userJid = 2;
optional bool shareToFB = 3; optional bool shareToFB = 3;
optional bool shareToIG = 4; optional bool shareToIG = 4;
repeated CustomList customLists = 5;
repeated StatusDistributionMode modes = 6;
message CustomList {
optional string listId = 1;
optional string name = 2;
optional string emoji = 3;
optional bool isSelected = 4;
repeated string userJid = 5;
}
enum StatusDistributionMode { enum StatusDistributionMode {
ALLOW_LIST = 0; ALLOW_LIST = 0;
DENY_LIST = 1; DENY_LIST = 1;
CONTACTS = 2; CONTACTS = 2;
CLOSE_FRIENDS = 3; CLOSE_FRIENDS = 3;
CUSTOM_LIST = 4;
} }
} }
@@ -5695,6 +5781,8 @@ message WebMessageInfo {
optional QuarantinedMessage quarantinedMessage = 77; optional QuarantinedMessage quarantinedMessage = 77;
optional uint32 nonJidMentions = 78; optional uint32 nonJidMentions = 78;
optional string hsmTag = 79; optional string hsmTag = 79;
optional uint64 ephemeralExpirationTimestamp = 80;
optional ScheduledMessageMetadata scheduledMessageMetadata = 81;
enum BizPrivacyStatus { enum BizPrivacyStatus {
E2EE = 0; E2EE = 0;
FB = 2; FB = 2;
@@ -5934,6 +6022,8 @@ message WebMessageInfo {
GROUP_MEMBER_SHARE_GROUP_HISTORY_MODE = 221; GROUP_MEMBER_SHARE_GROUP_HISTORY_MODE = 221;
GROUP_OPEN_BOT_ADDED = 222; GROUP_OPEN_BOT_ADDED = 222;
GROUP_TEE_BOT_ADDED = 223; GROUP_TEE_BOT_ADDED = 223;
CONTACT_INFO = 224;
SCHEDULED_MESSAGE_CREATED = 225;
} }
} }
+239 -9
View File
@@ -1097,6 +1097,26 @@ export namespace proto {
} }
} }
interface IBotCommandMetadata {
commandName?: (string|null);
commandDescription?: (string|null);
commandPrompt?: (string|null);
}
class BotCommandMetadata implements IBotCommandMetadata {
constructor(p?: proto.IBotCommandMetadata);
public commandName?: (string|null);
public commandDescription?: (string|null);
public commandPrompt?: (string|null);
public static create(properties?: proto.IBotCommandMetadata): proto.BotCommandMetadata;
public static encode(m: proto.IBotCommandMetadata, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.BotCommandMetadata;
public static fromObject(d: { [k: string]: any }): proto.BotCommandMetadata;
public static toObject(m: proto.BotCommandMetadata, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface IBotDocumentMessageMetadata { interface IBotDocumentMessageMetadata {
pluginType?: (proto.BotDocumentMessageMetadata.DocumentPluginType|null); pluginType?: (proto.BotDocumentMessageMetadata.DocumentPluginType|null);
} }
@@ -1681,6 +1701,7 @@ export namespace proto {
botRenderingConfigMetadata?: (proto.IBotRenderingConfigMetadata|null); botRenderingConfigMetadata?: (proto.IBotRenderingConfigMetadata|null);
botInfrastructureDiagnostics?: (proto.IBotInfrastructureDiagnostics|null); botInfrastructureDiagnostics?: (proto.IBotInfrastructureDiagnostics|null);
aiMediaCollectionMetadata?: (proto.IAIMediaCollectionMetadata|null); aiMediaCollectionMetadata?: (proto.IAIMediaCollectionMetadata|null);
commandMetadata?: (proto.IBotCommandMetadata|null);
internalMetadata?: (Uint8Array|null); internalMetadata?: (Uint8Array|null);
} }
@@ -1723,6 +1744,7 @@ export namespace proto {
public botRenderingConfigMetadata?: (proto.IBotRenderingConfigMetadata|null); public botRenderingConfigMetadata?: (proto.IBotRenderingConfigMetadata|null);
public botInfrastructureDiagnostics?: (proto.IBotInfrastructureDiagnostics|null); public botInfrastructureDiagnostics?: (proto.IBotInfrastructureDiagnostics|null);
public aiMediaCollectionMetadata?: (proto.IAIMediaCollectionMetadata|null); public aiMediaCollectionMetadata?: (proto.IAIMediaCollectionMetadata|null);
public commandMetadata?: (proto.IBotCommandMetadata|null);
public internalMetadata?: (Uint8Array|null); public internalMetadata?: (Uint8Array|null);
public static create(properties?: proto.IBotMetadata): proto.BotMetadata; public static create(properties?: proto.IBotMetadata): proto.BotMetadata;
public static encode(m: proto.IBotMetadata, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.IBotMetadata, w?: $protobuf.Writer): $protobuf.Writer;
@@ -1780,7 +1802,8 @@ export namespace proto {
WEB_INTRO_PANEL = 46, WEB_INTRO_PANEL = 46,
WEB_NAVIGATION_BAR = 47, WEB_NAVIGATION_BAR = 47,
GROUP_MEMBER = 54, GROUP_MEMBER = 54,
CHATLIST_SEARCH = 55 CHATLIST_SEARCH = 55,
NEW_CHAT_LIST = 56
} }
interface IBotMetricsMetadata { interface IBotMetricsMetadata {
@@ -3592,6 +3615,10 @@ export namespace proto {
wtwaWebsiteUrl?: (string|null); wtwaWebsiteUrl?: (string|null);
adPreviewUrl?: (string|null); adPreviewUrl?: (string|null);
containsCtwaFlowsAutoReply?: (boolean|null); containsCtwaFlowsAutoReply?: (boolean|null);
agmThumbnailStrategy?: (number|null);
agmTitleStrategy?: (number|null);
agmSubtitleStrategy?: (number|null);
agmHeaderInteractionStrategy?: (number|null);
} }
class ExternalAdReplyInfo implements IExternalAdReplyInfo { class ExternalAdReplyInfo implements IExternalAdReplyInfo {
@@ -3624,6 +3651,10 @@ export namespace proto {
public wtwaWebsiteUrl?: (string|null); public wtwaWebsiteUrl?: (string|null);
public adPreviewUrl?: (string|null); public adPreviewUrl?: (string|null);
public containsCtwaFlowsAutoReply?: (boolean|null); public containsCtwaFlowsAutoReply?: (boolean|null);
public agmThumbnailStrategy?: (number|null);
public agmTitleStrategy?: (number|null);
public agmSubtitleStrategy?: (number|null);
public agmHeaderInteractionStrategy?: (number|null);
public static create(properties?: proto.ContextInfo.IExternalAdReplyInfo): proto.ContextInfo.ExternalAdReplyInfo; public static create(properties?: proto.ContextInfo.IExternalAdReplyInfo): proto.ContextInfo.ExternalAdReplyInfo;
public static encode(m: proto.ContextInfo.IExternalAdReplyInfo, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.ContextInfo.IExternalAdReplyInfo, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.ContextInfo.ExternalAdReplyInfo; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.ContextInfo.ExternalAdReplyInfo;
@@ -4028,6 +4059,7 @@ export namespace proto {
companionSupportEnabled?: (boolean|null); companionSupportEnabled?: (boolean|null);
campaignSyncEnabled?: (boolean|null); campaignSyncEnabled?: (boolean|null);
insightsSyncEnabled?: (boolean|null); insightsSyncEnabled?: (boolean|null);
recipientLimit?: (number|null);
} }
class BusinessBroadcast implements IBusinessBroadcast { class BusinessBroadcast implements IBusinessBroadcast {
@@ -4036,6 +4068,7 @@ export namespace proto {
public companionSupportEnabled?: (boolean|null); public companionSupportEnabled?: (boolean|null);
public campaignSyncEnabled?: (boolean|null); public campaignSyncEnabled?: (boolean|null);
public insightsSyncEnabled?: (boolean|null); public insightsSyncEnabled?: (boolean|null);
public recipientLimit?: (number|null);
public static create(properties?: proto.DeviceCapabilities.IBusinessBroadcast): proto.DeviceCapabilities.BusinessBroadcast; public static create(properties?: proto.DeviceCapabilities.IBusinessBroadcast): proto.DeviceCapabilities.BusinessBroadcast;
public static encode(m: proto.DeviceCapabilities.IBusinessBroadcast, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.DeviceCapabilities.IBusinessBroadcast, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.DeviceCapabilities.BusinessBroadcast; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.DeviceCapabilities.BusinessBroadcast;
@@ -4211,6 +4244,8 @@ export namespace proto {
initialSyncMaxMessagesPerChat?: (number|null); initialSyncMaxMessagesPerChat?: (number|null);
supportManusHistory?: (boolean|null); supportManusHistory?: (boolean|null);
supportHatchHistory?: (boolean|null); supportHatchHistory?: (boolean|null);
supportedBotChannelFbids?: (string[]|null);
supportInlineContacts?: (boolean|null);
} }
class HistorySyncConfig implements IHistorySyncConfig { class HistorySyncConfig implements IHistorySyncConfig {
@@ -4237,6 +4272,8 @@ export namespace proto {
public initialSyncMaxMessagesPerChat?: (number|null); public initialSyncMaxMessagesPerChat?: (number|null);
public supportManusHistory?: (boolean|null); public supportManusHistory?: (boolean|null);
public supportHatchHistory?: (boolean|null); public supportHatchHistory?: (boolean|null);
public supportedBotChannelFbids: string[];
public supportInlineContacts?: (boolean|null);
public static create(properties?: proto.DeviceProps.IHistorySyncConfig): proto.DeviceProps.HistorySyncConfig; public static create(properties?: proto.DeviceProps.IHistorySyncConfig): proto.DeviceProps.HistorySyncConfig;
public static encode(m: proto.DeviceProps.IHistorySyncConfig, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.DeviceProps.IHistorySyncConfig, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.DeviceProps.HistorySyncConfig; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.DeviceProps.HistorySyncConfig;
@@ -4705,6 +4742,42 @@ export namespace proto {
} }
} }
interface IGroupRootKeyShare {
keys?: (proto.IGroupRootKeyShareEntry[]|null);
}
class GroupRootKeyShare implements IGroupRootKeyShare {
constructor(p?: proto.IGroupRootKeyShare);
public keys: proto.IGroupRootKeyShareEntry[];
public static create(properties?: proto.IGroupRootKeyShare): proto.GroupRootKeyShare;
public static encode(m: proto.IGroupRootKeyShare, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.GroupRootKeyShare;
public static fromObject(d: { [k: string]: any }): proto.GroupRootKeyShare;
public static toObject(m: proto.GroupRootKeyShare, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface IGroupRootKeyShareEntry {
groupRootKey?: (Uint8Array|null);
keyId?: (string|null);
expiryTimestampMs?: (number|Long|null);
}
class GroupRootKeyShareEntry implements IGroupRootKeyShareEntry {
constructor(p?: proto.IGroupRootKeyShareEntry);
public groupRootKey?: (Uint8Array|null);
public keyId?: (string|null);
public expiryTimestampMs?: (number|Long|null);
public static create(properties?: proto.IGroupRootKeyShareEntry): proto.GroupRootKeyShareEntry;
public static encode(m: proto.IGroupRootKeyShareEntry, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.GroupRootKeyShareEntry;
public static fromObject(d: { [k: string]: any }): proto.GroupRootKeyShareEntry;
public static toObject(m: proto.GroupRootKeyShareEntry, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface IHandshakeMessage { interface IHandshakeMessage {
clientHello?: (proto.HandshakeMessage.IClientHello|null); clientHello?: (proto.HandshakeMessage.IClientHello|null);
serverHello?: (proto.HandshakeMessage.IServerHello|null); serverHello?: (proto.HandshakeMessage.IServerHello|null);
@@ -4761,6 +4834,7 @@ export namespace proto {
sendServerHelloPaddedBytes?: (boolean|null); sendServerHelloPaddedBytes?: (boolean|null);
simulateXxkemFs?: (boolean|null); simulateXxkemFs?: (boolean|null);
pqMode?: (proto.HandshakeMessage.HandshakePqMode|null); pqMode?: (proto.HandshakeMessage.HandshakePqMode|null);
extendedEphemeral?: (Uint8Array|null);
} }
class ClientHello implements IClientHello { class ClientHello implements IClientHello {
@@ -4774,6 +4848,7 @@ export namespace proto {
public sendServerHelloPaddedBytes?: (boolean|null); public sendServerHelloPaddedBytes?: (boolean|null);
public simulateXxkemFs?: (boolean|null); public simulateXxkemFs?: (boolean|null);
public pqMode?: (proto.HandshakeMessage.HandshakePqMode|null); public pqMode?: (proto.HandshakeMessage.HandshakePqMode|null);
public extendedEphemeral?: (Uint8Array|null);
public static create(properties?: proto.HandshakeMessage.IClientHello): proto.HandshakeMessage.ClientHello; public static create(properties?: proto.HandshakeMessage.IClientHello): proto.HandshakeMessage.ClientHello;
public static encode(m: proto.HandshakeMessage.IClientHello, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.HandshakeMessage.IClientHello, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.HandshakeMessage.ClientHello; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.HandshakeMessage.ClientHello;
@@ -4801,6 +4876,7 @@ export namespace proto {
payload?: (Uint8Array|null); payload?: (Uint8Array|null);
extendedStatic?: (Uint8Array|null); extendedStatic?: (Uint8Array|null);
paddingBytes?: (Uint8Array|null); paddingBytes?: (Uint8Array|null);
extendedCiphertext?: (Uint8Array|null);
} }
class ServerHello implements IServerHello { class ServerHello implements IServerHello {
@@ -4810,6 +4886,7 @@ export namespace proto {
public payload?: (Uint8Array|null); public payload?: (Uint8Array|null);
public extendedStatic?: (Uint8Array|null); public extendedStatic?: (Uint8Array|null);
public paddingBytes?: (Uint8Array|null); public paddingBytes?: (Uint8Array|null);
public extendedCiphertext?: (Uint8Array|null);
public static create(properties?: proto.HandshakeMessage.IServerHello): proto.HandshakeMessage.ServerHello; public static create(properties?: proto.HandshakeMessage.IServerHello): proto.HandshakeMessage.ServerHello;
public static encode(m: proto.HandshakeMessage.IServerHello, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.HandshakeMessage.IServerHello, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.HandshakeMessage.ServerHello; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.HandshakeMessage.ServerHello;
@@ -4839,6 +4916,8 @@ export namespace proto {
shareableChatIdentifierEncryptionKey?: (Uint8Array|null); shareableChatIdentifierEncryptionKey?: (Uint8Array|null);
accounts?: (proto.IAccount[]|null); accounts?: (proto.IAccount[]|null);
nctSalt?: (Uint8Array|null); nctSalt?: (Uint8Array|null);
inlineContacts?: (proto.IInlineContact[]|null);
inlineContactsProvided?: (boolean|null);
} }
class HistorySync implements IHistorySync { class HistorySync implements IHistorySync {
@@ -4861,6 +4940,8 @@ export namespace proto {
public shareableChatIdentifierEncryptionKey?: (Uint8Array|null); public shareableChatIdentifierEncryptionKey?: (Uint8Array|null);
public accounts: proto.IAccount[]; public accounts: proto.IAccount[];
public nctSalt?: (Uint8Array|null); public nctSalt?: (Uint8Array|null);
public inlineContacts: proto.IInlineContact[];
public inlineContactsProvided?: (boolean|null);
public static create(properties?: proto.IHistorySync): proto.HistorySync; public static create(properties?: proto.IHistorySync): proto.HistorySync;
public static encode(m: proto.IHistorySync, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.IHistorySync, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.HistorySync; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.HistorySync;
@@ -5128,6 +5209,30 @@ export namespace proto {
} }
} }
interface IInlineContact {
pnJid?: (string|null);
lidJid?: (string|null);
fullName?: (string|null);
firstName?: (string|null);
username?: (string|null);
}
class InlineContact implements IInlineContact {
constructor(p?: proto.IInlineContact);
public pnJid?: (string|null);
public lidJid?: (string|null);
public fullName?: (string|null);
public firstName?: (string|null);
public username?: (string|null);
public static create(properties?: proto.IInlineContact): proto.InlineContact;
public static encode(m: proto.IInlineContact, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.InlineContact;
public static fromObject(d: { [k: string]: any }): proto.InlineContact;
public static toObject(m: proto.InlineContact, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface IInteractiveAnnotation { interface IInteractiveAnnotation {
polygonVertices?: (proto.IPoint[]|null); polygonVertices?: (proto.IPoint[]|null);
shouldSkipConfirmation?: (boolean|null); shouldSkipConfirmation?: (boolean|null);
@@ -5619,6 +5724,7 @@ export namespace proto {
conditionalRevealMessage?: (proto.Message.IConditionalRevealMessage|null); conditionalRevealMessage?: (proto.Message.IConditionalRevealMessage|null);
pollAddOptionMessage?: (proto.Message.IPollAddOptionMessage|null); pollAddOptionMessage?: (proto.Message.IPollAddOptionMessage|null);
eventInviteMessage?: (proto.Message.IEventInviteMessage|null); eventInviteMessage?: (proto.Message.IEventInviteMessage|null);
groupRootKeyShare?: (proto.IGroupRootKeyShare|null);
} }
class Message implements IMessage { class Message implements IMessage {
@@ -5725,6 +5831,7 @@ export namespace proto {
public conditionalRevealMessage?: (proto.Message.IConditionalRevealMessage|null); public conditionalRevealMessage?: (proto.Message.IConditionalRevealMessage|null);
public pollAddOptionMessage?: (proto.Message.IPollAddOptionMessage|null); public pollAddOptionMessage?: (proto.Message.IPollAddOptionMessage|null);
public eventInviteMessage?: (proto.Message.IEventInviteMessage|null); public eventInviteMessage?: (proto.Message.IEventInviteMessage|null);
public groupRootKeyShare?: (proto.IGroupRootKeyShare|null);
public static create(properties?: proto.IMessage): proto.Message; public static create(properties?: proto.IMessage): proto.Message;
public static encode(m: proto.IMessage, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.IMessage, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message;
@@ -6264,7 +6371,8 @@ export namespace proto {
enum CloudAPIThreadControl { enum CloudAPIThreadControl {
UNKNOWN = 0, UNKNOWN = 0,
CONTROL_PASSED = 1, CONTROL_PASSED = 1,
CONTROL_TAKEN = 2 CONTROL_TAKEN = 2,
INFO = 3
} }
interface ICloudAPIThreadControlNotificationContent { interface ICloudAPIThreadControlNotificationContent {
@@ -8372,6 +8480,7 @@ export namespace proto {
onDemandMsgCount?: (number|null); onDemandMsgCount?: (number|null);
oldestMsgTimestampMs?: (number|Long|null); oldestMsgTimestampMs?: (number|Long|null);
accountLid?: (string|null); accountLid?: (string|null);
supportInlineResponse?: (boolean|null);
} }
class HistorySyncOnDemandRequest implements IHistorySyncOnDemandRequest { class HistorySyncOnDemandRequest implements IHistorySyncOnDemandRequest {
@@ -8382,6 +8491,7 @@ export namespace proto {
public onDemandMsgCount?: (number|null); public onDemandMsgCount?: (number|null);
public oldestMsgTimestampMs?: (number|Long|null); public oldestMsgTimestampMs?: (number|Long|null);
public accountLid?: (string|null); public accountLid?: (string|null);
public supportInlineResponse?: (boolean|null);
public static create(properties?: proto.Message.PeerDataOperationRequestMessage.IHistorySyncOnDemandRequest): proto.Message.PeerDataOperationRequestMessage.HistorySyncOnDemandRequest; public static create(properties?: proto.Message.PeerDataOperationRequestMessage.IHistorySyncOnDemandRequest): proto.Message.PeerDataOperationRequestMessage.HistorySyncOnDemandRequest;
public static encode(m: proto.Message.PeerDataOperationRequestMessage.IHistorySyncOnDemandRequest, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.Message.PeerDataOperationRequestMessage.IHistorySyncOnDemandRequest, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.PeerDataOperationRequestMessage.HistorySyncOnDemandRequest; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.PeerDataOperationRequestMessage.HistorySyncOnDemandRequest;
@@ -8655,7 +8765,8 @@ export namespace proto {
GENERIC_ERROR = 3, GENERIC_ERROR = 3,
ERROR_REQUEST_ON_NON_SMB_PRIMARY = 4, ERROR_REQUEST_ON_NON_SMB_PRIMARY = 4,
ERROR_HOSTED_DEVICE_NOT_CONNECTED = 5, ERROR_HOSTED_DEVICE_NOT_CONNECTED = 5,
ERROR_HOSTED_DEVICE_LOGIN_TIME_NOT_SET = 6 ERROR_HOSTED_DEVICE_LOGIN_TIME_NOT_SET = 6,
ERROR_MULTI_PROVIDER_NOT_CONFIGURED = 7
} }
interface IHistorySyncChunkRetryResponse { interface IHistorySyncChunkRetryResponse {
@@ -9281,8 +9392,7 @@ export namespace proto {
AI_QUERY_FANOUT = 29, AI_QUERY_FANOUT = 29,
GROUP_MEMBER_LABEL_CHANGE = 30, GROUP_MEMBER_LABEL_CHANGE = 30,
AI_MEDIA_COLLECTION_MESSAGE = 31, AI_MEDIA_COLLECTION_MESSAGE = 31,
MESSAGE_UNSCHEDULE = 32, MESSAGE_UNSCHEDULE = 32
BOT_UNLINK_MESSAGE = 33
} }
} }
@@ -9661,6 +9771,7 @@ export namespace proto {
isLottie?: (boolean|null); isLottie?: (boolean|null);
accessibilityLabel?: (string|null); accessibilityLabel?: (string|null);
premium?: (number|null); premium?: (number|null);
emojis?: (string|null);
} }
class StickerMessage implements IStickerMessage { class StickerMessage implements IStickerMessage {
@@ -9686,6 +9797,7 @@ export namespace proto {
public isLottie?: (boolean|null); public isLottie?: (boolean|null);
public accessibilityLabel?: (string|null); public accessibilityLabel?: (string|null);
public premium?: (number|null); public premium?: (number|null);
public emojis?: (string|null);
public static create(properties?: proto.Message.IStickerMessage): proto.Message.StickerMessage; public static create(properties?: proto.Message.IStickerMessage): proto.Message.StickerMessage;
public static encode(m: proto.Message.IStickerMessage, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.Message.IStickerMessage, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.StickerMessage; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.StickerMessage;
@@ -10181,6 +10293,7 @@ export namespace proto {
limitSharingV2?: (proto.ILimitSharing|null); limitSharingV2?: (proto.ILimitSharing|null);
threadId?: (proto.IThreadID[]|null); threadId?: (proto.IThreadID[]|null);
weblinkRenderConfig?: (proto.WebLinkRenderConfig|null); weblinkRenderConfig?: (proto.WebLinkRenderConfig|null);
teeBotMetadata?: (Uint8Array|null);
} }
class MessageContextInfo implements IMessageContextInfo { class MessageContextInfo implements IMessageContextInfo {
@@ -10201,6 +10314,7 @@ export namespace proto {
public limitSharingV2?: (proto.ILimitSharing|null); public limitSharingV2?: (proto.ILimitSharing|null);
public threadId: proto.IThreadID[]; public threadId: proto.IThreadID[];
public weblinkRenderConfig?: (proto.WebLinkRenderConfig|null); public weblinkRenderConfig?: (proto.WebLinkRenderConfig|null);
public teeBotMetadata?: (Uint8Array|null);
public static create(properties?: proto.IMessageContextInfo): proto.MessageContextInfo; public static create(properties?: proto.IMessageContextInfo): proto.MessageContextInfo;
public static encode(m: proto.IMessageContextInfo, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.IMessageContextInfo, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.MessageContextInfo; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.MessageContextInfo;
@@ -10578,6 +10692,8 @@ export namespace proto {
CUSTOMER_DATA_ACTION = 83, CUSTOMER_DATA_ACTION = 83,
SUBSCRIPTIONS_SYNC_V2_ACTION = 84, SUBSCRIPTIONS_SYNC_V2_ACTION = 84,
THREAD_PIN_ACTION = 85, THREAD_PIN_ACTION = 85,
AUTO_ORGANIZE_BUSINESS_CHAT_SETTING = 86,
BIZ_AI_SETTINGS_NUDGE_ACTION = 87,
SHARE_OWN_PN = 10001, SHARE_OWN_PN = 10001,
BUSINESS_BROADCAST_ACTION = 10002, BUSINESS_BROADCAST_ACTION = 10002,
AI_THREAD_DELETE_ACTION = 10003 AI_THREAD_DELETE_ACTION = 10003
@@ -11389,6 +11505,26 @@ export namespace proto {
public static getTypeUrl(typeUrlPrefix?: string): string; public static getTypeUrl(typeUrlPrefix?: string): string;
} }
interface IScheduledMessageMetadata {
revealKeyId?: (string|null);
revealKey?: (Uint8Array|null);
scheduledTime?: (number|Long|null);
}
class ScheduledMessageMetadata implements IScheduledMessageMetadata {
constructor(p?: proto.IScheduledMessageMetadata);
public revealKeyId?: (string|null);
public revealKey?: (Uint8Array|null);
public scheduledTime?: (number|Long|null);
public static create(properties?: proto.IScheduledMessageMetadata): proto.ScheduledMessageMetadata;
public static encode(m: proto.IScheduledMessageMetadata, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.ScheduledMessageMetadata;
public static fromObject(d: { [k: string]: any }): proto.ScheduledMessageMetadata;
public static toObject(m: proto.ScheduledMessageMetadata, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface ISenderKeyDistributionMessage { interface ISenderKeyDistributionMessage {
id?: (number|null); id?: (number|null);
iteration?: (number|null); iteration?: (number|null);
@@ -12175,6 +12311,8 @@ export namespace proto {
customerDataAction?: (proto.SyncActionValue.ICustomerDataAction|null); customerDataAction?: (proto.SyncActionValue.ICustomerDataAction|null);
subscriptionsSyncV2Action?: (proto.SyncActionValue.ISubscriptionsSyncV2Action|null); subscriptionsSyncV2Action?: (proto.SyncActionValue.ISubscriptionsSyncV2Action|null);
threadPinAction?: (proto.SyncActionValue.IThreadPinAction|null); threadPinAction?: (proto.SyncActionValue.IThreadPinAction|null);
autoOrganizeBusinessChatSetting?: (proto.SyncActionValue.IAutoOrganizeBusinessChatSetting|null);
bizAiSettingsNudgeAction?: (proto.SyncActionValue.IBizAISettingsNudgeAction|null);
} }
class SyncActionValue implements ISyncActionValue { class SyncActionValue implements ISyncActionValue {
@@ -12255,6 +12393,8 @@ export namespace proto {
public customerDataAction?: (proto.SyncActionValue.ICustomerDataAction|null); public customerDataAction?: (proto.SyncActionValue.ICustomerDataAction|null);
public subscriptionsSyncV2Action?: (proto.SyncActionValue.ISubscriptionsSyncV2Action|null); public subscriptionsSyncV2Action?: (proto.SyncActionValue.ISubscriptionsSyncV2Action|null);
public threadPinAction?: (proto.SyncActionValue.IThreadPinAction|null); public threadPinAction?: (proto.SyncActionValue.IThreadPinAction|null);
public autoOrganizeBusinessChatSetting?: (proto.SyncActionValue.IAutoOrganizeBusinessChatSetting|null);
public bizAiSettingsNudgeAction?: (proto.SyncActionValue.IBizAISettingsNudgeAction|null);
public static create(properties?: proto.ISyncActionValue): proto.SyncActionValue; public static create(properties?: proto.ISyncActionValue): proto.SyncActionValue;
public static encode(m: proto.ISyncActionValue, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.ISyncActionValue, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue;
@@ -12336,6 +12476,22 @@ export namespace proto {
public static getTypeUrl(typeUrlPrefix?: string): string; public static getTypeUrl(typeUrlPrefix?: string): string;
} }
interface IAutoOrganizeBusinessChatSetting {
autoOrganize?: (boolean|null);
}
class AutoOrganizeBusinessChatSetting implements IAutoOrganizeBusinessChatSetting {
constructor(p?: proto.SyncActionValue.IAutoOrganizeBusinessChatSetting);
public autoOrganize?: (boolean|null);
public static create(properties?: proto.SyncActionValue.IAutoOrganizeBusinessChatSetting): proto.SyncActionValue.AutoOrganizeBusinessChatSetting;
public static encode(m: proto.SyncActionValue.IAutoOrganizeBusinessChatSetting, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue.AutoOrganizeBusinessChatSetting;
public static fromObject(d: { [k: string]: any }): proto.SyncActionValue.AutoOrganizeBusinessChatSetting;
public static toObject(m: proto.SyncActionValue.AutoOrganizeBusinessChatSetting, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface IAvatarUpdatedAction { interface IAvatarUpdatedAction {
eventType?: (proto.SyncActionValue.AvatarUpdatedAction.AvatarEventType|null); eventType?: (proto.SyncActionValue.AvatarUpdatedAction.AvatarEventType|null);
recentAvatarStickers?: (proto.SyncActionValue.IStickerAction[]|null); recentAvatarStickers?: (proto.SyncActionValue.IStickerAction[]|null);
@@ -12363,6 +12519,38 @@ export namespace proto {
} }
} }
interface IBizAISettingsNudgeAction {
category?: (proto.SyncActionValue.BizAISettingsNudgeAction.BizAISettingsCategory|null);
version?: (number|Long|null);
updatedAtMs?: (number|Long|null);
}
class BizAISettingsNudgeAction implements IBizAISettingsNudgeAction {
constructor(p?: proto.SyncActionValue.IBizAISettingsNudgeAction);
public category?: (proto.SyncActionValue.BizAISettingsNudgeAction.BizAISettingsCategory|null);
public version?: (number|Long|null);
public updatedAtMs?: (number|Long|null);
public static create(properties?: proto.SyncActionValue.IBizAISettingsNudgeAction): proto.SyncActionValue.BizAISettingsNudgeAction;
public static encode(m: proto.SyncActionValue.IBizAISettingsNudgeAction, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue.BizAISettingsNudgeAction;
public static fromObject(d: { [k: string]: any }): proto.SyncActionValue.BizAISettingsNudgeAction;
public static toObject(m: proto.SyncActionValue.BizAISettingsNudgeAction, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
namespace BizAISettingsNudgeAction {
enum BizAISettingsCategory {
UNKNOWN = 0,
INSTRUCTIONS = 1,
RESPONSE_SETTINGS = 2,
EXAMPLE_RESPONSES = 3,
KNOWLEDGE = 4,
LEAD_GEN = 5
}
}
interface IBotWelcomeRequestAction { interface IBotWelcomeRequestAction {
isSent?: (boolean|null); isSent?: (boolean|null);
} }
@@ -12920,7 +13108,8 @@ export namespace proto {
SERVER_ASSIGNED = 7, SERVER_ASSIGNED = 7,
DRAFTED = 8, DRAFTED = 8,
AI_HANDOFF = 9, AI_HANDOFF = 9,
CHANNELS = 10 CHANNELS = 10,
AI_RESPONDING = 11
} }
} }
@@ -13562,6 +13751,8 @@ export namespace proto {
isStatusNotificationEnabled?: (boolean|null); isStatusNotificationEnabled?: (boolean|null);
statusNotificationToneId?: (number|null); statusNotificationToneId?: (number|null);
shouldPlaySoundForCallNotification?: (boolean|null); shouldPlaySoundForCallNotification?: (boolean|null);
chatThemeId?: (string|null);
colorSchemeId?: (string|null);
} }
class SettingsSyncAction implements ISettingsSyncAction { class SettingsSyncAction implements ISettingsSyncAction {
@@ -13597,6 +13788,8 @@ export namespace proto {
public isStatusNotificationEnabled?: (boolean|null); public isStatusNotificationEnabled?: (boolean|null);
public statusNotificationToneId?: (number|null); public statusNotificationToneId?: (number|null);
public shouldPlaySoundForCallNotification?: (boolean|null); public shouldPlaySoundForCallNotification?: (boolean|null);
public chatThemeId?: (string|null);
public colorSchemeId?: (string|null);
public static create(properties?: proto.SyncActionValue.ISettingsSyncAction): proto.SyncActionValue.SettingsSyncAction; public static create(properties?: proto.SyncActionValue.ISettingsSyncAction): proto.SyncActionValue.SettingsSyncAction;
public static encode(m: proto.SyncActionValue.ISettingsSyncAction, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.SyncActionValue.ISettingsSyncAction, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue.SettingsSyncAction; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue.SettingsSyncAction;
@@ -13653,7 +13846,9 @@ export namespace proto {
IS_GROUP_REACTIONS_NOTIFICATION_ENABLED = 28, IS_GROUP_REACTIONS_NOTIFICATION_ENABLED = 28,
IS_STATUS_NOTIFICATION_ENABLED = 29, IS_STATUS_NOTIFICATION_ENABLED = 29,
STATUS_NOTIFICATION_TONE_ID = 30, STATUS_NOTIFICATION_TONE_ID = 30,
SHOULD_PLAY_SOUND_FOR_CALL_NOTIFICATION = 31 SHOULD_PLAY_SOUND_FOR_CALL_NOTIFICATION = 31,
CHAT_THEME_ID = 32,
COLOR_SCHEME_ID = 33
} }
enum SettingPlatform { enum SettingPlatform {
@@ -13702,6 +13897,8 @@ export namespace proto {
userJid?: (string[]|null); userJid?: (string[]|null);
shareToFB?: (boolean|null); shareToFB?: (boolean|null);
shareToIG?: (boolean|null); shareToIG?: (boolean|null);
customLists?: (proto.SyncActionValue.StatusPrivacyAction.ICustomList[]|null);
modes?: (proto.SyncActionValue.StatusPrivacyAction.StatusDistributionMode[]|null);
} }
class StatusPrivacyAction implements IStatusPrivacyAction { class StatusPrivacyAction implements IStatusPrivacyAction {
@@ -13710,6 +13907,8 @@ export namespace proto {
public userJid: string[]; public userJid: string[];
public shareToFB?: (boolean|null); public shareToFB?: (boolean|null);
public shareToIG?: (boolean|null); public shareToIG?: (boolean|null);
public customLists: proto.SyncActionValue.StatusPrivacyAction.ICustomList[];
public modes: proto.SyncActionValue.StatusPrivacyAction.StatusDistributionMode[];
public static create(properties?: proto.SyncActionValue.IStatusPrivacyAction): proto.SyncActionValue.StatusPrivacyAction; public static create(properties?: proto.SyncActionValue.IStatusPrivacyAction): proto.SyncActionValue.StatusPrivacyAction;
public static encode(m: proto.SyncActionValue.IStatusPrivacyAction, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.SyncActionValue.IStatusPrivacyAction, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue.StatusPrivacyAction; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue.StatusPrivacyAction;
@@ -13721,11 +13920,36 @@ export namespace proto {
namespace StatusPrivacyAction { namespace StatusPrivacyAction {
interface ICustomList {
listId?: (string|null);
name?: (string|null);
emoji?: (string|null);
isSelected?: (boolean|null);
userJid?: (string[]|null);
}
class CustomList implements ICustomList {
constructor(p?: proto.SyncActionValue.StatusPrivacyAction.ICustomList);
public listId?: (string|null);
public name?: (string|null);
public emoji?: (string|null);
public isSelected?: (boolean|null);
public userJid: string[];
public static create(properties?: proto.SyncActionValue.StatusPrivacyAction.ICustomList): proto.SyncActionValue.StatusPrivacyAction.CustomList;
public static encode(m: proto.SyncActionValue.StatusPrivacyAction.ICustomList, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue.StatusPrivacyAction.CustomList;
public static fromObject(d: { [k: string]: any }): proto.SyncActionValue.StatusPrivacyAction.CustomList;
public static toObject(m: proto.SyncActionValue.StatusPrivacyAction.CustomList, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
enum StatusDistributionMode { enum StatusDistributionMode {
ALLOW_LIST = 0, ALLOW_LIST = 0,
DENY_LIST = 1, DENY_LIST = 1,
CONTACTS = 2, CONTACTS = 2,
CLOSE_FRIENDS = 3 CLOSE_FRIENDS = 3,
CUSTOM_LIST = 4
} }
} }
@@ -14733,6 +14957,8 @@ export namespace proto {
quarantinedMessage?: (proto.IQuarantinedMessage|null); quarantinedMessage?: (proto.IQuarantinedMessage|null);
nonJidMentions?: (number|null); nonJidMentions?: (number|null);
hsmTag?: (string|null); hsmTag?: (string|null);
ephemeralExpirationTimestamp?: (number|Long|null);
scheduledMessageMetadata?: (proto.IScheduledMessageMetadata|null);
} }
class WebMessageInfo implements IWebMessageInfo { class WebMessageInfo implements IWebMessageInfo {
@@ -14805,6 +15031,8 @@ export namespace proto {
public quarantinedMessage?: (proto.IQuarantinedMessage|null); public quarantinedMessage?: (proto.IQuarantinedMessage|null);
public nonJidMentions?: (number|null); public nonJidMentions?: (number|null);
public hsmTag?: (string|null); public hsmTag?: (string|null);
public ephemeralExpirationTimestamp?: (number|Long|null);
public scheduledMessageMetadata?: (proto.IScheduledMessageMetadata|null);
public static create(properties?: proto.IWebMessageInfo): proto.WebMessageInfo; public static create(properties?: proto.IWebMessageInfo): proto.WebMessageInfo;
public static encode(m: proto.IWebMessageInfo, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.IWebMessageInfo, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.WebMessageInfo; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.WebMessageInfo;
@@ -15056,7 +15284,9 @@ export namespace proto {
QUARANTINED_MESSAGE = 220, QUARANTINED_MESSAGE = 220,
GROUP_MEMBER_SHARE_GROUP_HISTORY_MODE = 221, GROUP_MEMBER_SHARE_GROUP_HISTORY_MODE = 221,
GROUP_OPEN_BOT_ADDED = 222, GROUP_OPEN_BOT_ADDED = 222,
GROUP_TEE_BOT_ADDED = 223 GROUP_TEE_BOT_ADDED = 223,
CONTACT_INFO = 224,
SCHEDULED_MESSAGE_CREATED = 225
} }
} }
+1781 -5
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+531
View File
@@ -0,0 +1,531 @@
# Mensagens View-Once (Visualização Única) — Guia de Implementação
**Data:** 2026-03-23
**Versão:** 1.0
---
## 1. O que é View-Once
Mensagem de visualização única é um tipo especial de mídia (imagem, vídeo ou áudio) que o destinatário pode abrir apenas uma vez. Após abrir, o conteúdo some permanentemente.
---
> ## ⚠️ RESTRIÇÃO CRÍTICA — Contas Hosted (Meta Business Cloud API)
>
> **Contas registradas como "hosted" na Meta Business Cloud API têm view-once bloqueado pelo servidor do WhatsApp.**
>
> Essa restrição se aplica a **todos os clientes** (Android, iOS, WA Web) e **não há como contornar por código** — é uma política imposta diretamente pelo servidor WA para contas hosted.
>
> **Como identificar se sua conta é hosted:**
> - A conta foi criada via Meta Business Cloud API (WABA)
> - Aparece como "Meta" ou "Business Cloud" nas configurações do WhatsApp Business Manager
>
> **Contas não-hosted** (registradas diretamente via QR code ou pair code) **funcionam normalmente** com view-once.
---
**Comportamento em cada dispositivo após envio via API (contas não-hosted):**
| Dispositivo | O que aparece |
|---|---|
| Destinatário (Android/iOS) | Recebe a mídia com ícone de olhinho — pode abrir uma vez |
| Android principal do remetente | Mostra na conversa que a mensagem foi enviada (ícone de olhinho) |
| WA Web do remetente | "Aguardando mensagem. Abra o WhatsApp no seu celular." |
> **Nota:** A mensagem "Aguardando mensagem..." no WA Web é o comportamento correto e esperado quando o envio vem de um companion (API). É uma limitação do protocolo WhatsApp — o servidor só permite que o celular principal notifique companions com o conteúdo da view-once.
---
## 2. Como Enviar via API
### 2.1 Imagem
```bash
curl -X POST https://sua-api.com/v1/messages/send_image \
-H "Content-Type: application/json" \
-H "x-api-key: SEU_API_KEY" \
-d '{
"instance": "nome-da-instancia",
"to": "5511999999999",
"caption": "Olha só isso!",
"imageUrl": "https://exemplo.com/foto.jpg",
"viewOnce": true
}'
```
**Resposta de sucesso:**
```json
{ "ok": true }
```
### 2.2 Vídeo
```bash
curl -X POST https://sua-api.com/v1/messages/send_video \
-H "Content-Type: application/json" \
-H "x-api-key: SEU_API_KEY" \
-d '{
"instance": "nome-da-instancia",
"to": "5511999999999",
"caption": "Assista uma vez",
"videoUrl": "https://exemplo.com/video.mp4",
"viewOnce": true
}'
```
### 2.3 Áudio
```bash
curl -X POST https://sua-api.com/v1/messages/send_audio \
-H "Content-Type: application/json" \
-H "x-api-key: SEU_API_KEY" \
-d '{
"instance": "nome-da-instancia",
"to": "5511999999999",
"audioUrl": "https://exemplo.com/audio.ogg",
"ptt": false,
"viewOnce": true
}'
```
### 2.4 Usando Base64 (sem URL pública)
```bash
curl -X POST https://sua-api.com/v1/messages/send_image \
-H "Content-Type: application/json" \
-H "x-api-key: SEU_API_KEY" \
-d '{
"instance": "nome-da-instancia",
"to": "5511999999999",
"imageBase64": "data:image/jpeg;base64,/9j/4AAQSkZJRgAB...",
"viewOnce": true
}'
```
---
## 3. Como Receber via Webhook
Quando um contato envia uma mensagem de visualização única para o número conectado na API, o webhook recebe o evento com `key.isViewOnce = true`.
### 3.1 Estrutura do evento recebido
```json
{
"event": "messages.upsert",
"instance": "nome-da-instancia",
"data": {
"messages": [
{
"key": {
"remoteJid": "5511999999999@s.whatsapp.net",
"fromMe": false,
"id": "3EB0ABCDEF123456",
"isViewOnce": true
},
"message": {
"viewOnceMessageV2": {
"message": {
"imageMessage": {
"url": "https://mmg.whatsapp.net/...",
"mimetype": "image/jpeg",
"mediaKey": "base64...",
"fileEncSha256": "base64...",
"directPath": "/v/...",
"viewOnce": true
}
}
}
},
"messageTimestamp": 1742700000,
"pushName": "João Silva"
}
],
"type": "notify"
}
}
```
### 3.2 Como identificar que é view-once
O campo principal para identificar é `key.isViewOnce = true`. Existem dois caminhos para confirmar:
```javascript
function isViewOnce(msg) {
// Caminho 1: flag direta (mais simples)
if (msg.key?.isViewOnce) return true
// Caminho 2: verificar o wrapper da mensagem
const inner =
msg.message?.viewOnceMessageV2?.message ||
msg.message?.viewOnceMessage?.message ||
msg.message?.viewOnceMessageV2Extension?.message
return !!(
inner?.imageMessage?.viewOnce ||
inner?.videoMessage?.viewOnce ||
inner?.audioMessage?.viewOnce
)
}
```
### 3.3 Como acessar a mídia
```javascript
function getViewOnceMedia(msg) {
const inner =
msg.message?.viewOnceMessageV2?.message ||
msg.message?.viewOnceMessage?.message ||
msg.message?.viewOnceMessageV2Extension?.message
if (!inner) return null
if (inner.imageMessage) return { type: 'image', media: inner.imageMessage }
if (inner.videoMessage) return { type: 'video', media: inner.videoMessage }
if (inner.audioMessage) return { type: 'audio', media: inner.audioMessage }
return null
}
// Uso:
const media = getViewOnceMedia(msg)
if (media) {
console.log(`View-once ${media.type}`)
console.log('URL:', media.media.url)
console.log('DirectPath:', media.media.directPath)
console.log('MediaKey:', media.media.mediaKey) // necessário para download
}
```
---
## 4. Como Exibir numa Ferramenta (Frontend/CRM)
### 4.1 Lógica de detecção e exibição
```javascript
function renderMessage(msg) {
if (!isViewOnce(msg)) {
// Renderizar mensagem normal
return renderNormalMessage(msg)
}
const media = getViewOnceMedia(msg)
if (!media) return
// Verificar se já foi aberta (controle local da ferramenta)
const alreadyOpened = localStorage.getItem(`vo_${msg.key.id}`)
if (alreadyOpened) {
// Mostrar placeholder de "já visualizado"
return renderViewOncePlaceholder(media.type, msg.key.fromMe)
}
if (msg.key.fromMe) {
// Mensagem enviada por mim — nunca mostrar conteúdo
return renderSentViewOnce(media.type)
}
// Mensagem recebida — mostrar botão para "abrir uma vez"
return renderViewOnceButton(media.type, msg)
}
```
### 4.2 Componente de exibição — mensagem recebida
```javascript
function renderViewOnceButton(type, msg) {
const icons = { image: '📷', video: '🎥', audio: '🎵' }
const labels = { image: 'Foto', video: 'Vídeo', áudio: 'Áudio' }
return `
<div class="view-once-bubble received">
<span class="view-once-icon">${icons[type]}</span>
<span class="view-once-label">${labels[type]} de visualização única</span>
<button onclick="openViewOnce('${msg.key.id}', '${type}')">
Abrir
</button>
</div>
`
}
async function openViewOnce(msgId, type) {
// 1. Marcar como aberta ANTES de mostrar (só uma chance)
localStorage.setItem(`vo_${msgId}`, '1')
// 2. Baixar e mostrar a mídia
const mediaUrl = await downloadViewOnceMedia(msgId)
showMedia(type, mediaUrl)
// 3. Após fechar/visualizar, substituir pelo placeholder
onMediaClosed(() => {
replaceWithPlaceholder(msgId, type)
})
}
```
### 4.3 Componente de exibição — mensagem enviada
```javascript
function renderSentViewOnce(type) {
const labels = { image: 'Foto', video: 'Vídeo', audio: 'Áudio' }
return `
<div class="view-once-bubble sent">
<span class="view-once-icon">👁️</span>
<span>${labels[type]} de visualização única</span>
<span class="view-once-status">Enviada</span>
</div>
`
}
```
### 4.4 Placeholder após visualização
```javascript
function renderViewOncePlaceholder(type, fromMe) {
const labels = { image: 'foto', video: 'vídeo', audio: 'áudio' }
const text = fromMe
? `Você enviou uma ${labels[type]} de visualização única`
: `${labels[type]} de visualização única aberta`
return `
<div class="view-once-bubble placeholder">
<span class="view-once-icon">🚫</span>
<span>${text}</span>
</div>
`
}
```
### 4.5 CSS sugerido
```css
.view-once-bubble {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
border-radius: 12px;
max-width: 280px;
}
.view-once-bubble.received {
background: #f0f0f0;
color: #333;
}
.view-once-bubble.sent {
background: #dcf8c6;
color: #333;
align-self: flex-end;
}
.view-once-bubble.placeholder {
background: #e8e8e8;
color: #999;
font-style: italic;
}
.view-once-bubble button {
background: #25d366;
color: white;
border: none;
border-radius: 8px;
padding: 6px 12px;
cursor: pointer;
font-size: 13px;
}
```
---
## 5. Código Implementado na API
### 5.1 Geração da mensagem — `src/Utils/messages.ts`
Quando `viewOnce: true` é passado, a mídia é encapsulada no wrapper moderno `viewOnceMessageV2` (campo 55 do proto) e o flag `viewOnce=true` é setado na mídia interna:
```typescript
if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
// Seta viewOnce=true na mensagem de mídia interna
if (m.imageMessage) m.imageMessage.viewOnce = true
else if (m.videoMessage) m.videoMessage.viewOnce = true
else if (m.audioMessage) m.audioMessage.viewOnce = true
// Usa viewOnceMessageV2 (campo 55) — formato atual do WA
m = { viewOnceMessageV2: { message: m } }
}
```
### 5.2 Envio — `src/Socket/messages-send.ts`
**Detecção de view-once real** (não confunde com botões/listas que também usam o wrapper `viewOnceMessage`):
```typescript
// Detecta view-once real pela flag viewOnce na mídia interna.
// viewOnceMessage* wrappers são também usados por botões/listas/carrosséis
// (que carregam interactiveMessage dentro) — esses NÃO têm viewOnce=true na mídia.
const viewOnceInner =
message.viewOnceMessageV2?.message ||
message.viewOnceMessage?.message ||
message.viewOnceMessageV2Extension?.message
const isViewOnceMsg = !!(
viewOnceInner?.imageMessage?.viewOnce ||
viewOnceInner?.videoMessage?.viewOnce ||
viewOnceInner?.audioMessage?.viewOnce
)
// Para view-once: DSM enviado apenas para o celular principal (device=0).
// Companions (device>0) são omitidos — o servidor WA gera
// <unavailable type="view_once"/> automaticamente para eles.
const viewOnceMeRecipients = isViewOnceMsg
? meRecipients.filter(jid => !jidDecode(jid)?.device)
: meRecipients
// assertSessions apenas para quem vai realmente receber
await assertSessions([...viewOnceMeRecipients, ...otherRecipients])
const [meNodes, otherNodes] = await Promise.all([
createParticipantNodes(viewOnceMeRecipients, meMsg || message, extraAttrs),
createParticipantNodes(otherRecipients, message, extraAttrs)
])
participants.push(...meNodes)
participants.push(...otherNodes)
// phash recalculado com os participantes reais
const phashRecipients = isViewOnceMsg
? [...viewOnceMeRecipients, ...otherRecipients]
: [...meRecipients, ...otherRecipients]
if (phashRecipients.length > 0) {
extraAttrs['phash'] = generateParticipantHashV2(phashRecipients)
}
```
**`getMediaType()` — garante que vídeo e áudio view-once sejam entregues:**
```typescript
const getMediaType = (message: proto.IMessage) => {
// Desencapsula wrapper view-once antes de verificar o tipo de mídia.
// Sem isso, message.videoMessage e message.audioMessage são undefined
// (estão dentro do wrapper) e o atributo mediatype ficaria ausente no
// enc node — o servidor WA descarta silenciosamente vídeo/áudio view-once.
const inner =
message.viewOnceMessage?.message ||
message.viewOnceMessageV2?.message ||
message.viewOnceMessageV2Extension?.message
if (inner) {
return getMediaType(inner)
}
if (message.imageMessage) return 'image'
else if (message.videoMessage) return 'video'
// ...
}
```
### 5.3 Recepção — `src/Utils/decode-wa-message.ts`
Quando a API recebe uma view-once como companion (linked device), o servidor envia dois stanzas:
- **Stanza 1** (`enc`): conteúdo completo com metadados da mídia
- **Stanza 2** (`unavailable type="view_once"`): sinal de sync
O stanza 1 é detectado e marcado corretamente:
```typescript
// Detecta view-once na stanza 1 recebida por linked device.
// O wrapper viewOnceMessage também é usado por mensagens interativas
// (interactiveMessage, listMessage) — esses NÃO têm viewOnce=true na mídia.
const viewOnceInner =
msg.viewOnceMessage?.message ||
msg.viewOnceMessageV2?.message ||
msg.viewOnceMessageV2Extension?.message
if (
viewOnceInner?.imageMessage?.viewOnce ||
viewOnceInner?.videoMessage?.viewOnce ||
viewOnceInner?.audioMessage?.viewOnce
) {
fullMessage.key.isViewOnce = true
}
```
---
## 6. Fluxo Completo
```
API envia view-once para destinatário
├── Protobuf gerado:
│ viewOnceMessageV2 {
│ message {
│ imageMessage { viewOnce: true, url, mediaKey, ... }
│ }
│ }
├── Stanza enviado ao servidor WA:
│ <message to="destinatario@s.whatsapp.net" type="media">
│ <enc type="pkmsg">...viewOnceMessageV2 cifrado...</enc> ← para o destinatário
│ <participants>
│ <to jid="meu-numero:0@lid"> ← celular principal
│ <enc type="msg">...DSM{viewOnceMessageV2}...</enc>
│ </to>
│ <!-- device>0 omitidos — servidor gera unavailable -->
│ </participants>
│ </message>
└── Distribuição pelo servidor:
├── Destinatário → recebe enc → abre uma vez ✅
├── Celular principal → recebe DSM → mostra "você enviou" ✅
└── WA Web → recebe <unavailable> → "Aguardando..." ✅
Destinatário envia view-once de volta para a API
├── Stanza recebido pela API (stanza 1 — enc com conteúdo):
│ <message from="destinatario@s.whatsapp.net" type="media">
│ <enc type="msg">...viewOnceMessageV2 cifrado...</enc>
│ </message>
├── decode-wa-message.ts:
│ └── Descriptografa → detecta viewOnce=true → seta key.isViewOnce=true
├── Stanza recebido (stanza 2 — unavailable, ignorado):
│ <message from="...">
│ <unavailable type="view_once"/>
│ </message>
└── Webhook emitido:
messages.upsert → msg.key.isViewOnce = true ✅
```
---
## 7. Limitações
| Situação | Comportamento | Motivo |
|---|---|---|
| **Conta hosted (Meta Business Cloud API)** | **View-once bloqueado para todos os clientes** | **Política do servidor WA para contas hosted — sem workaround** |
| WA Web do remetente | "Aguardando mensagem. Abra no celular." | Servidor WA só aceita `<unavailable>` vindo do celular principal, não de companions |
| View-once de documento/sticker | Não suportado | Limitação do protocolo WA — só imagem, vídeo e áudio |
| `error_479` nos logs | Normal, não é erro | TcToken é atualizado após o envio de view-once |
| Companion não consegue abrir | Por design | O conteúdo não é entregue para linked devices |
---
## 8. Checklist de Validação
**Envio:**
- [ ] Destinatário recebe com ícone de olhinho
- [ ] Destinatário consegue abrir a mídia
- [ ] Após abrir, a mídia some
- [ ] Celular principal do remetente mostra "você enviou" com ícone de olhinho
- [ ] WA Web do remetente mostra "Aguardando mensagem..."
- [ ] Funciona para imagem, vídeo e áudio
- [ ] Logs mostram `📤 Message sent` (error_479 é normal)
**Recepção:**
- [ ] Webhook recebe `key.isViewOnce = true`
- [ ] `viewOnceMessageV2.message.imageMessage` (ou video/audio) está presente
- [ ] API não crasha ao receber view-once de outro dispositivo
- [ ] Mensagem interativa (botão/lista) enviada após view-once não é afetada
+1 -1
View File
@@ -1 +1 @@
{"version":[2,3000,1035595667]} {"version":[2,3000,1038147544]}
+38 -4
View File
@@ -31,9 +31,26 @@ export const STATUS_EXPIRY_SECONDS = 24 * 60 * 60
export const PLACEHOLDER_MAX_AGE_SECONDS = 7 * 24 * 60 * 60 export const PLACEHOLDER_MAX_AGE_SECONDS = 7 * 24 * 60 * 60
export const NOISE_MODE = 'Noise_XX_25519_AESGCM_SHA256\0\0\0\0' export const NOISE_MODE = 'Noise_XX_25519_AESGCM_SHA256\0\0\0\0'
export const DICT_VERSION = 3
/**
* Protocol mode: 'web' (default) or 'native' (experimental)
* - web: WA\x06\x03 — standard WhatsApp Web protocol
* - native: WAM\x05 — native Android protocol (may enable view-once media on companions)
*
* Set via BAILEYS_PROTOCOL env var:
* BAILEYS_PROTOCOL=native
*/
const PROTOCOL_MODE = process.env.BAILEYS_PROTOCOL?.trim().toLowerCase() === 'native' ? 'native' : 'web'
export const DICT_VERSION = PROTOCOL_MODE === 'native' ? 5 : 3
export const KEY_BUNDLE_TYPE = Buffer.from([5]) export const KEY_BUNDLE_TYPE = Buffer.from([5])
export const NOISE_WA_HEADER = Buffer.from([87, 65, 6, DICT_VERSION]) // last is "DICT_VERSION"
// WA\x06\x03 = web protocol, WAM\x05 = native Android protocol
export const NOISE_WA_HEADER = PROTOCOL_MODE === 'native'
? Buffer.from([87, 65, 77, DICT_VERSION]) // WAM\x05
: Buffer.from([87, 65, 6, DICT_VERSION]) // WA\x06\x03
export { PROTOCOL_MODE }
/** from: https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url */ /** from: https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url */
export const URL_REGEX = /https:\/\/(?![^:@\/\s]+:[^:@\/\s]+@)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:\d+)?(\/[^\s]*)?/g export const URL_REGEX = /https:\/\/(?![^:@\/\s]+:[^:@\/\s]+@)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:\d+)?(\/[^\s]*)?/g
@@ -154,7 +171,20 @@ export const MEDIA_PATH_MAP: { [T in MediaType]?: string } = {
'md-msg-hist': '/mms/md-app-state', 'md-msg-hist': '/mms/md-app-state',
'biz-cover-photo': '/pps/biz-cover-photo', 'biz-cover-photo': '/pps/biz-cover-photo',
'sticker-pack': '/mms/sticker-pack', 'sticker-pack': '/mms/sticker-pack',
'thumbnail-sticker-pack': '/mms/thumbnail-sticker-pack' 'thumbnail-sticker-pack': '/mms/thumbnail-sticker-pack',
// View-once types — same upload path as regular media (server CDN routing TBD)
'viewonce-image': '/mms/image',
'viewonce-video': '/mms/video',
'viewonce-audio': '/mms/audio'
}
export const NEWSLETTER_MEDIA_PATH_MAP: { [T in MediaType]?: string } = {
image: '/newsletter/newsletter-image',
video: '/newsletter/newsletter-video',
document: '/newsletter/newsletter-document',
audio: '/newsletter/newsletter-audio',
sticker: '/newsletter/newsletter-image',
'thumbnail-link': '/newsletter/newsletter-image'
} }
export const MEDIA_HKDF_KEY_MAPPING = { export const MEDIA_HKDF_KEY_MAPPING = {
@@ -178,7 +208,11 @@ export const MEDIA_HKDF_KEY_MAPPING = {
ptv: 'Video', ptv: 'Video',
'biz-cover-photo': 'Image', 'biz-cover-photo': 'Image',
'sticker-pack': 'Sticker Pack', 'sticker-pack': 'Sticker Pack',
'thumbnail-sticker-pack': 'Sticker Pack Thumbnail' 'thumbnail-sticker-pack': 'Sticker Pack Thumbnail',
// View-once types — same HKDF keys as regular types (recipients decrypt with normal keys)
'viewonce-image': 'Image',
'viewonce-video': 'Video',
'viewonce-audio': 'Audio'
} }
export type MediaType = keyof typeof MEDIA_HKDF_KEY_MAPPING export type MediaType = keyof typeof MEDIA_HKDF_KEY_MAPPING
+71 -7
View File
@@ -2243,18 +2243,60 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
const handleMessage = async (node: BinaryNode) => { const handleMessage = async (node: BinaryNode) => {
if (shouldIgnoreJid(node.attrs.from!) && node.attrs.from !== S_WHATSAPP_NET) { if (shouldIgnoreJid(node.attrs.from!) && node.attrs.from !== S_WHATSAPP_NET) {
logger.trace({ from: node.attrs.from }, 'ignored message') logger.trace({ from: node.attrs.from }, 'ignored message')
// Send a clean ACK (no error code) so the server considers the
// message delivered. Using error 500 (UnhandledError) previously
// caused the server to retry delivery, generating duplicate traffic.
await sendMessageAck(node) await sendMessageAck(node)
return return
} }
const encNode = getBinaryNodeChild(node, 'enc') const encNode = getBinaryNodeChild(node, 'enc')
// TODO: temporary fix for crashes and issues resulting of failed msmsg decryption const unavailableNode = getBinaryNodeChild(node, 'unavailable')
if (encNode?.attrs.type === 'msmsg') {
logger.debug({ key: node.attrs.key }, 'ignored msmsg') // msmsg block removed — allow msmsg decryption (required for view-once on linked devices)
await sendMessageAck(node, NACK_REASONS.MissingMessageSecret) if (unavailableNode?.attrs?.type === 'view_once') {
const { fullMessage: voMsg } = decryptMessageNode(
node,
authState.creds.me!.id,
authState.creds.me!.lid || '',
signalRepository,
logger,
)
voMsg.key.isViewOnce = true
// Try to request the actual view-once content from the primary phone via PDO
// The phone may resend the full media (url, mediaKey, directPath, etc.)
console.log(`[VO_PDO] Requesting view-once content via PDO for ${voMsg.key.id} from ${voMsg.key.remoteJid}`)
try {
const requestId = await requestPlaceholderResend(voMsg.key, {
key: { ...voMsg.key },
pushName: voMsg.pushName,
messageTimestamp: voMsg.messageTimestamp,
participant: voMsg.key.participant,
participantAlt: voMsg.key.participantAlt
})
if (requestId && requestId !== 'RESOLVED') {
console.log(`[VO_PDO] PDO request sent! requestId=${requestId} — waiting for phone to resend`)
} else if (requestId === 'RESOLVED') {
console.log(`[VO_PDO] Message arrived during PDO delay!`)
} else {
console.log(`[VO_PDO] Already requested or no response`)
}
} catch (err: any) {
console.log(`[VO_PDO] PDO request failed: ${err.message} — falling back to placeholder`)
}
// Emit placeholder immediately so consumers see the notification
// If PDO succeeds, the real message will arrive via messages.upsert later
voMsg.message = {
viewOnceMessage: {
message: {
imageMessage: {
viewOnce: true,
}
}
}
}
const upsertType = node.attrs.offline ? 'append' : 'notify'
await upsertMessage(voMsg, upsertType)
await sendMessageAck(node)
return return
} }
@@ -2371,6 +2413,23 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
{ msgId: msg.key?.id, messageType }, { msgId: msg.key?.id, messageType },
'CTWA: Skipping placeholder resend for unavailable fanout type' 'CTWA: Skipping placeholder resend for unavailable fanout type'
) )
// For view-once: emit the message with a viewOnceMessage placeholder
// so consumers (zpro, etc.) can detect and display it.
// Include a message wrapper so isValidMsg accepts it.
if (messageType === 'view_once_unavailable_fanout') {
msg.key.isViewOnce = true
msg.message = {
viewOnceMessage: {
message: {
imageMessage: {
viewOnce: true,
}
}
}
}
const upsertType = node.attrs.offline ? 'append' : 'notify'
await upsertMessage(msg, upsertType)
}
metrics.ctwaRecoveryFailures.inc({ reason: 'unavailable_fanout' }) metrics.ctwaRecoveryFailures.inc({ reason: 'unavailable_fanout' })
return sendMessageAck(node) return sendMessageAck(node)
} }
@@ -2552,6 +2611,11 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
await sendReceipt(msg.key.remoteJid!, participant!, [msg.key.id!], type) await sendReceipt(msg.key.remoteJid!, participant!, [msg.key.id!], type)
// NOTE: do NOT send view_once_read from a linked device (API).
// WA shows view-once as view_once_unavailable_fanout on linked devices — only the
// primary phone sends view_once_read when the user actually opens it.
// Sending it from a linked device causes WA to flag the account and disables view-once.
// send ack for history message // send ack for history message
const isAnyHistoryMsg = getHistoryMsg(msg.message!) const isAnyHistoryMsg = getHistoryMsg(msg.message!)
if (isAnyHistoryMsg) { if (isAnyHistoryMsg) {
+6 -3
View File
@@ -1041,7 +1041,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
} }
await authState.keys.transaction(async () => { await authState.keys.transaction(async () => {
const mediaType = getMediaType(message) // normalizeMessageContent unwraps viewOnceMessage/viewOnceMessageV2 so getMediaType works correctly
const mediaType = getMediaType(normalizeMessageContent(message) || message)
if (mediaType) { if (mediaType) {
extraAttrs['mediatype'] = mediaType extraAttrs['mediatype'] = mediaType
} }
@@ -1051,7 +1052,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const bytes = encodeNewsletterMessage(patched as proto.IMessage) const bytes = encodeNewsletterMessage(patched as proto.IMessage)
binaryNodeContent.push({ binaryNodeContent.push({
tag: 'plaintext', tag: 'plaintext',
attrs: {}, attrs: mediaType ? { mediatype: mediaType } : {},
content: bytes content: bytes
}) })
const stanza: BinaryNode = { const stanza: BinaryNode = {
@@ -1073,6 +1074,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
extraAttrs['decrypt-fail'] = 'hide' // todo: expand for reactions and other types extraAttrs['decrypt-fail'] = 'hide' // todo: expand for reactions and other types
} }
// view_once_write disabled — causes server to drop message (validation fails for linked device sessions)
if (isGroupOrStatus && !isRetryResend) { if (isGroupOrStatus && !isRetryResend) {
const [groupData, senderKeyMap] = await Promise.all([ const [groupData, senderKeyMap] = await Promise.all([
(async () => { (async () => {
@@ -1307,13 +1310,13 @@ export const makeMessagesSocket = (config: SocketConfig) => {
{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: meNodes, shouldIncludeDeviceIdentity: s1 },
{ nodes: otherNodes, shouldIncludeDeviceIdentity: s2 } { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }
] = await Promise.all([ ] = await Promise.all([
// For own devices: use DSM (deviceSentMessage) wrapper
createParticipantNodes(viewOnceMeRecipients, meMsg || message, extraAttrs), createParticipantNodes(viewOnceMeRecipients, meMsg || message, extraAttrs),
createParticipantNodes(effectiveOtherRecipients, message, extraAttrs) createParticipantNodes(effectiveOtherRecipients, message, extraAttrs)
]) ])
participants.push(...meNodes) participants.push(...meNodes)
participants.push(...otherNodes) participants.push(...otherNodes)
// phash: para view-once inclui só celular principal + destinatários
const phashRecipients = isViewOnceMsg const phashRecipients = isViewOnceMsg
? [...viewOnceMeRecipients, ...effectiveOtherRecipients] ? [...viewOnceMeRecipients, ...effectiveOtherRecipients]
: effectiveAllRecipients : effectiveAllRecipients
+13 -2
View File
@@ -155,6 +155,7 @@ export type MessageReceiptType =
| 'sender' | 'sender'
| 'inactive' | 'inactive'
| 'played' | 'played'
| 'view_once_read'
| undefined | undefined
export type MediaConnInfo = { export type MediaConnInfo = {
@@ -178,6 +179,8 @@ export interface WAUrlInfo {
type Mentionable = { type Mentionable = {
/** list of jids that are mentioned in the accompanying text */ /** list of jids that are mentioned in the accompanying text */
mentions?: string[] mentions?: string[]
/** mention all participants in the group */
mentionAll?: boolean
} }
type Contextable = { type Contextable = {
/** add contextInfo to the message */ /** add contextInfo to the message */
@@ -990,8 +993,16 @@ export type MessageGenerationOptionsFromContent = MiscMessageGenerationOptions &
export type WAMediaUploadFunction = ( export type WAMediaUploadFunction = (
encFilePath: string, encFilePath: string,
opts: { fileEncSha256B64: string; mediaType: MediaType; timeoutMs?: number } opts: { fileEncSha256B64: string; mediaType: MediaType; timeoutMs?: number; newsletter?: boolean }
) => Promise<{ mediaUrl: string; directPath: string; meta_hmac?: string; ts?: number; fbid?: number }> ) => Promise<{
mediaUrl: string | undefined
directPath: string
meta_hmac?: string
ts?: number
fbid?: number
thumbnailDirectPath?: string
thumbnailSha256?: string
}>
export type MediaGenerationOptions = { export type MediaGenerationOptions = {
logger?: ILogger logger?: ILogger
+32 -6
View File
@@ -10,7 +10,13 @@ import { join } from 'path'
import { Readable, Transform } from 'stream' import { Readable, Transform } from 'stream'
import { URL } from 'url' import { URL } from 'url'
import { proto } from '../../WAProto/index.js' import { proto } from '../../WAProto/index.js'
import { DEFAULT_ORIGIN, MEDIA_HKDF_KEY_MAPPING, MEDIA_PATH_MAP, type MediaType } from '../Defaults' import {
DEFAULT_ORIGIN,
MEDIA_HKDF_KEY_MAPPING,
MEDIA_PATH_MAP,
type MediaType,
NEWSLETTER_MEDIA_PATH_MAP
} from '../Defaults'
import type { import type {
BaileysEventMap, BaileysEventMap,
DownloadableMessage, DownloadableMessage,
@@ -681,6 +687,10 @@ type MediaUploadResult = {
meta_hmac?: string meta_hmac?: string
ts?: number ts?: number
fbid?: number fbid?: number
thumbnail_info?: {
thumbnail_sha256?: string
thumbnail_direct_path?: string
}
} }
export type UploadParams = { export type UploadParams = {
@@ -825,11 +835,21 @@ export const getWAUploadToServer = (
{ customUploadHosts, fetchAgent, logger, options }: SocketConfig, { customUploadHosts, fetchAgent, logger, options }: SocketConfig,
refreshMediaConn: (force: boolean) => Promise<MediaConnInfo> refreshMediaConn: (force: boolean) => Promise<MediaConnInfo>
): WAMediaUploadFunction => { ): WAMediaUploadFunction => {
return async (filePath, { mediaType, fileEncSha256B64, timeoutMs }) => { return async (filePath, { mediaType, fileEncSha256B64, timeoutMs, newsletter }) => {
// send a query JSON to obtain the url & auth token to upload our media // send a query JSON to obtain the url & auth token to upload our media
let uploadInfo = await refreshMediaConn(false) let uploadInfo = await refreshMediaConn(false)
let urls: { mediaUrl: string; directPath: string; meta_hmac?: string; ts?: number; fbid?: number } | undefined let urls:
| {
mediaUrl: string
directPath: string
meta_hmac?: string
ts?: number
fbid?: number
thumbnailDirectPath?: string
thumbnailSha256?: string
}
| undefined
const hosts = [...customUploadHosts, ...uploadInfo.hosts] const hosts = [...customUploadHosts, ...uploadInfo.hosts]
fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64) fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64)
@@ -851,7 +871,11 @@ export const getWAUploadToServer = (
logger.debug(`uploading to "${hostname}"`) logger.debug(`uploading to "${hostname}"`)
const auth = encodeURIComponent(uploadInfo.auth) const auth = encodeURIComponent(uploadInfo.auth)
const url = `https://${hostname}${MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}` const mediaPath = (newsletter ? NEWSLETTER_MEDIA_PATH_MAP[mediaType] : undefined) || MEDIA_PATH_MAP[mediaType]
let url = `https://${hostname}${mediaPath}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
if (newsletter) {
url += '&server_thumb_gen=1'
}
let result: MediaUploadResult | undefined let result: MediaUploadResult | undefined
try { try {
@@ -868,11 +892,13 @@ export const getWAUploadToServer = (
if (result?.url || result?.direct_path) { if (result?.url || result?.direct_path) {
urls = { urls = {
mediaUrl: result.url!, mediaUrl: result.url || result.direct_path!,
directPath: result.direct_path!, directPath: result.direct_path!,
meta_hmac: result.meta_hmac, meta_hmac: result.meta_hmac,
fbid: result.fbid, fbid: result.fbid,
ts: result.ts ts: result.ts,
thumbnailDirectPath: result.thumbnail_info?.thumbnail_direct_path,
thumbnailSha256: result.thumbnail_info?.thumbnail_sha256
} }
break break
} else { } else {
+46 -14
View File
@@ -196,10 +196,11 @@ export const prepareWAMessageMedia = async (
) )
const fileSha256B64 = fileSha256.toString('base64') const fileSha256B64 = fileSha256.toString('base64')
const { mediaUrl, directPath } = await options.upload(filePath, { const { directPath, thumbnailDirectPath, thumbnailSha256 } = await options.upload(filePath, {
fileEncSha256B64: fileSha256B64, fileEncSha256B64: fileSha256B64,
mediaType: mediaType, mediaType: mediaType,
timeoutMs: options.mediaUploadTimeoutMs timeoutMs: options.mediaUploadTimeoutMs,
newsletter: true
}) })
await fs.unlink(filePath) await fs.unlink(filePath)
@@ -207,10 +208,12 @@ export const prepareWAMessageMedia = async (
const obj = WAProto.Message.fromObject({ const obj = WAProto.Message.fromObject({
// todo: add more support here // todo: add more support here
[`${mediaType}Message`]: (MessageTypeProto as any)[mediaType].fromObject({ [`${mediaType}Message`]: (MessageTypeProto as any)[mediaType].fromObject({
url: mediaUrl, // url intentionally omitted — newsletters use directPath only
directPath, directPath,
fileSha256, fileSha256,
fileLength, fileLength,
thumbnailDirectPath,
thumbnailSha256: thumbnailSha256 ? Buffer.from(thumbnailSha256, 'base64') : undefined,
...uploadData, ...uploadData,
media: undefined media: undefined
}) })
@@ -236,7 +239,7 @@ export const prepareWAMessageMedia = async (
const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined' const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined'
const requiresThumbnailComputation = const requiresThumbnailComputation =
(mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined' (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined'
const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined'
const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation
const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream( const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(
@@ -254,7 +257,9 @@ export const prepareWAMessageMedia = async (
(async () => { (async () => {
const result = await options.upload(encFilePath, { const result = await options.upload(encFilePath, {
fileEncSha256B64, fileEncSha256B64,
mediaType, // Use mediaTypeOverride for upload path (e.g. 'viewonce-image' → /o1/mms/image)
// Encryption already used the override via encryptedStream above
mediaType: options.mediaTypeOverride || mediaType,
timeoutMs: options.mediaUploadTimeoutMs timeoutMs: options.mediaUploadTimeoutMs
}) })
logger?.debug({ mediaType, cacheableKey }, 'uploaded media') logger?.debug({ mediaType, cacheableKey }, 'uploaded media')
@@ -292,7 +297,7 @@ export const prepareWAMessageMedia = async (
logger?.debug('computed audio duration') logger?.debug('computed audio duration')
} }
if (requiresWaveformProcessing) { if (requiresWaveformProcessing && !uploadData.waveform) {
uploadData.waveform = await getAudioWaveform(filePath, logger) uploadData.waveform = await getAudioWaveform(filePath, logger)
logger?.debug('processed waveform') logger?.debug('processed waveform')
} }
@@ -1747,22 +1752,49 @@ export const generateWAMessageContent = async (
} }
} }
} else { } else {
m = await prepareWAMessageMedia(message, options) // For view-once media, upload to the one-time CDN (/o1/mms/*) instead of regular CDN
// WA Web view-once messages always have directPath: /o1/v/t24/... (not /v/t62.7118-24/...)
// Using the wrong CDN causes WA server to flag the account and breaks view-once capability
const isViewOnce = hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce
const viewOnceTypeMap: Record<string, string> = {
image: 'viewonce-image',
video: 'viewonce-video',
audio: 'viewonce-audio'
}
const viewOnceOverride = isViewOnce
? viewOnceTypeMap[Object.keys(message).find(k => k in viewOnceTypeMap) || '']
: undefined
m = await prepareWAMessageMedia(message, {
...options,
mediaTypeOverride: (viewOnceOverride as any) || options.mediaTypeOverride
})
} }
if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) { if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
m = { viewOnceMessage: { message: m } } // Also set viewOnce=true on the inner media message (required by WA app to render as view-once)
if (m.imageMessage) m.imageMessage.viewOnce = true
else if (m.videoMessage) m.videoMessage.viewOnce = true
else if (m.audioMessage) m.audioMessage.viewOnce = true
// Use viewOnceMessageV2 (field 55) — modern WA clients use this wrapper
// viewOnceMessage (field 37) is the old format which WA server no longer accepts with view_once_write
m = { viewOnceMessageV2: { message: m } }
} }
if (hasOptionalProperty(message, 'mentions') && message.mentions?.length) { if (
(hasOptionalProperty(message, 'mentions') && message.mentions?.length) ||
(hasOptionalProperty(message, 'mentionAll') && message.mentionAll)
) {
const messageType = Object.keys(m)[0] as Extract<keyof proto.IMessage, MessageWithContextInfo> const messageType = Object.keys(m)[0] as Extract<keyof proto.IMessage, MessageWithContextInfo>
if (messageType) { if (messageType) {
const key = m[messageType] const key = m[messageType]
if (key && 'contextInfo' in key && !!key.contextInfo) { if (key && 'contextInfo' in key) {
key.contextInfo.mentionedJid = message.mentions key.contextInfo = key.contextInfo || {}
} else if (key) { if (message.mentions?.length) {
key.contextInfo = { key.contextInfo.mentionedJid = message.mentions
mentionedJid: message.mentions }
if (message.mentionAll) {
key.contextInfo.nonJidMentions = 1
} }
} }
} }
+5 -1
View File
@@ -3,6 +3,7 @@ import { createHash } from 'crypto'
import { proto } from '../../WAProto/index.js' import { proto } from '../../WAProto/index.js'
import { import {
KEY_BUNDLE_TYPE, KEY_BUNDLE_TYPE,
PROTOCOL_MODE,
WA_ADV_ACCOUNT_SIG_PREFIX, WA_ADV_ACCOUNT_SIG_PREFIX,
WA_ADV_DEVICE_SIG_PREFIX, WA_ADV_DEVICE_SIG_PREFIX,
WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX
@@ -58,7 +59,10 @@ const getClientPayload = (config: SocketConfig) => {
userAgent: getUserAgent(config) userAgent: getUserAgent(config)
} }
payload.webInfo = getWebInfo(config) // Native protocol (WAM\x05) does not use WebInfo — only web protocol does
if (PROTOCOL_MODE !== 'native') {
payload.webInfo = getWebInfo(config)
}
return payload return payload
} }