fix: typing, recording-audio & reactions silently dropped (canonical JID)#73
fix: typing, recording-audio & reactions silently dropped (canonical JID)#73joaoporth wants to merge 4 commits into
Conversation
…SendChatPresence - React: remove `ID: msgId` from SendRequestExtra so whatsmeow generates a fresh unique message ID for the reaction envelope. Passing the original message ID as the send ID caused WhatsApp to silently deduplicate and drop the reaction. The msgId still lives correctly inside MessageKey (the reference to the message being reacted to). - ChatPresence: call client.SubscribePresence before SendChatPresence. WhatsApp requires an active presence subscription on the recipient JID before it forwards chatstate (typing/recording) events. Without it the call succeeds locally but the indicator never appears on the recipient side. SubscribePresence errors are logged as warnings and are non-fatal. Fixes evolution-foundation#63 (typing indicator) and the silent reaction drop issue.
…uestExtra Passing ID: msgId in SendRequestExtra caused WhatsApp to use the original message ID as the reaction envelope ID. Since that ID is already known to the server, it silently deduplicates and drops the reaction. The msgId belongs only inside MessageKey (reference to the message being reacted to). Let whatsmeow generate a fresh ID for the reaction envelope. Also update messageInfo.ID to use response.ID (the real generated ID) instead of the original msgId. ChatPresence is reverted to its original correct form. The SubscribePresence call added previously was wrong: it is for receiving presence updates from a contact, not for sending chatstate (typing) events. SendChatPresence sends a chatstate node directly and does not require SubscribePresence.
Reviewer's guide (collapsed on small PRs)Reviewer's GuideFixes WhatsApp reaction handling so that reactions are sent with a fresh envelope ID while still targeting the original message, preventing WhatsApp from silently deduplicating and dropping reactions, and ensures the stored message record uses the actual reaction envelope ID returned by the server. Sequence diagram for updated WhatsApp reaction sending in ReactsequenceDiagram
participant Caller as Caller
participant MessageService as messageService
participant WhatsmeowClient as whatsmeowClient
participant WhatsAppServer as WhatsApp
participant DB as MessageStore
Caller->>MessageService: React(data, instance)
MessageService->>MessageService: Build MessageKey using msgId
MessageService->>MessageService: Build waE2E.Message with ReactionMessage
MessageService->>WhatsmeowClient: SendMessage(ctx, recipient, msg)
WhatsmeowClient->>WhatsAppServer: Send reaction with new envelope ID
WhatsAppServer-->>WhatsmeowClient: SendMessage response(ID, ServerID)
WhatsmeowClient-->>MessageService: response
MessageService->>DB: Store message with ID = response.ID
MessageService-->>Caller: ReactResponse
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Updates reaction sending to avoid ID reuse so WhatsApp doesn’t deduplicate/drop reactions.
Changes:
- Clarified that the
MessageKeyshould reference the original message ID, not the reaction envelope ID. - Stopped passing
SendRequestExtra{ID: msgId}sowhatsmeowgenerates a fresh envelope ID. - Persisted the returned envelope ID (
response.ID) instead of the original message ID (msgId).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| IsGroup: isGroup, | ||
| }, | ||
| ID: msgId, | ||
| ID: response.ID, |
| // GroupingKey: proto.String(reaction), | ||
| Key: messageKey, | ||
| Text: proto.String(reaction), | ||
| SenderTimestampMS: proto.Int64(time.Now().UnixMilli()), |
| }, | ||
| ID: msgId, | ||
| ID: response.ID, | ||
| Timestamp: time.Now(), |
|
Fechando este PR. Após testes mais aprofundados, as correções não se sustentaram: a mudança de presence (SendPresence available + chatstate) envia os nós corretamente no protocolo, mas o indicador de "digitando..." não aparece de fato para o destinatário em nossos testes, e a correção de React não pôde ser validada (usync timeout impediu o teste). Vou revisar e reabrir/abrir um novo PR quando estiver realmente validado. Obrigado! |
The /message/presence chatstate (typing & recording-audio) was silently dropped: ParseJID/CreateJID prefixes phone numbers with '+' (e.g. +5541...@s.whatsapp.net). Message sending tolerates this because whatsmeow normalizes the JID during usync/device resolution, but RAW nodes (chatstate, read receipts) are sent without usync, so the malformed '+JID' reaches the server and the node is never routed to the recipient. - Add utils.CanonicalJID to strip the leading '+' for RAW-node targets, with unit test (TestCanonicalJID). - Apply it in ChatPresence (typing/recording) and MarkRead (read receipts). - Send SendPresence(available) before the chatstate so the server forwards the indicator (it only relays chatstate while the sender is online). - Add optional 'delay' (ms) to /message/presence: keep the indicator alive for the duration (re-sending every 5s, capped at 60s) then send 'paused', instead of a single ephemeral fire. Tested end-to-end: 'digitando...' and 'gravando audio...' both confirmed appearing on the recipient device. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Reabrindo: a causa-raiz foi identificada e corrigida. O |
The reaction (/message/react) was failing for the same root cause as the presence indicators: ParseJID/CreateJID prefix the number with '+'. In React that malformed JID is used in two places: - as the SendMessage target (usync/device resolution), which could stall the send (usync timeout); and - as the MessageKey RemoteJID that references the reacted message's chat — a '+'-prefixed chat JID does not match the real chat, so the reaction never attaches to the original message. Apply utils.CanonicalJID to the recipient (covers both the SendMessage target and the MessageKey RemoteJID) and to the optional group participant. Tested end-to-end: reactions (including multi-codepoint emoji like ❤️) confirmed attaching to the correct message on the recipient device, with no usync timeout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Atualização: além dos indicadores de presença (digitando/gravando áudio), este PR agora também corrige as reações ( |
Closes #63
What
Fix three WhatsApp features that returned
200but never reached the recipient:digitando...) and recording-audio (gravando áudio...) onPOST /message/presencePOST /message/reactPOST /message/markread…plus a
delayoption to sustain the typing/recording indicator.Root cause
utils.CreateJID/ParseJIDintentionally prefix phone numbers with+(e.g.+5541...@s.whatsapp.net) for the IsOnWhatsApp/display convention. Message sending tolerates this because whatsmeow normalizes the JID during usync/device resolution.But several features send RAW nodes without usync, where the malformed
+JIDsurvives:to="+5541...@s.whatsapp.net", which WhatsApp does not route, so the indicator never appeared. Verified at the wire level.+JIDis used both as theSendMessagetarget (can stall device resolution → usync timeout) and as theMessageKey.RemoteJIDthat references the reacted message's chat; a+-prefixed chat doesn't match the real chat, so the reaction never attaches to the message.Changes
utils.CanonicalJID— strips the leading+so RAW-node targets are canonical, digits-only WhatsApp JIDs. Covered byTestCanonicalJID.ChatPresence(typing/recording),React(recipient + group participant) andMarkRead(read receipts).SendPresence(available)before the chatstate — WhatsApp only forwards chatstate while the sender is marked online; background presence handling can leave the clientUnavailable, dropping the indicator.delay(ms) on/message/presence— keeps the indicator alive for the requested duration (re-sending every 5s, capped at 60s) then sendspaused, instead of a single ephemeral fire.Testing
End-to-end on a live instance, confirmed on the recipient device:
digitando...andgravando áudio...appear (and persist fordelay).❤️), with no usync timeout.go build,go vet,go test ./pkg/utils/all green.Examples
isAudio: true→gravando áudio....delayomitted/0 → legacy single-fire (unchanged).🤖 Generated with Claude Code