Repository navigation
feat(webhooks): lid<->pn addressing (fromAlt) and unified reaction delivery - #19
Conversation
…malized message With the LID migration, `from`/`participant` can arrive as `@lid`. Both engines already resolve the alternate addressing on the message key, so surface it as an optional `fromAlt` on the normalized message event — the phone-number jid when `from` is a lid, or vice-versa. - types: add optional `fromAlt` to messageEventSchema. - engine-baileys/engine-zapo: map `fromAlt` from the key (`participantAlt ?? remoteJidAlt`), mirroring `from = participant ?? chat`. Omitted when the engine doesn't provide it. - openapi: document `fromAlt` on the WebhookDelivery message branch, the webhook description, and a lid-addressed group example. - tests: fromAlt present for lid group/direct, absent otherwise, on both engines. Closes #17.
Reactions were only normalized when they arrived as a `reactionMessage` inside `messages.upsert`. That left a real gap on zapo (where reactions are delivered as decrypted `message_addon` events, not `message`) and made the baileys path fragile (it would miss encrypted reactions while a future decrypt would also duplicate plain ones via `messages.reaction`). Unify both engines onto a single, dedicated reaction source, producing the exact same normalized payload the upsert path produced: - engine-zapo: subscribe to `message_addon` and map `kind: 'reaction'` to the normalized reaction message event (other addon kinds are ignored for now). This is the actual fix — reactions were dropped on zapo before. - engine-baileys: map the `messages.reaction` event (envelope = who reacted, `entry.key` = target) and skip `reactionMessage`/`encReactionMessage` in the upsert path so there is exactly one source — no duplication, and encrypted reactions are covered as soon as the engine decrypts them. - both reuse `fromAlt` (lid<->pn) and the existing `content.type: 'reaction'`. - openapi: add a reaction delivery example. - tests: reaction mappers (group emoji, removal, envelope/target, null for non-reaction addons) and the upsert reaction-skip guard. Closes #18.
📝 WalkthroughWalkthroughThis PR implements message reaction support and introduces a ChangesMessage Reactions and Alternate Sender Addressing
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/api/src/http/openapi.ts (1)
313-333:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix the inconsistent
fromfield in the message reply example.The example sets
participantto a lid ('199999999999999@lid') butfromto a phone-number JID ('5511888888888@s.whatsapp.net'). According to both engine implementations,fromis derived asparticipant ?? chat, so whenparticipantis present in a group message,fromshould match it.📝 Proposed fix
value: { type: 'message', id: '3EB0...', chat: '120363000000000000@g.us', - from: '5511888888888@s.whatsapp.net', + from: '199999999999999@lid', fromMe: false, isGroup: true, participant: '199999999999999@lid', fromAlt: '5511888888888@s.whatsapp.net',🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/http/openapi.ts` around lines 313 - 333, The example object message_reply_mention_group has an inconsistent from field: when participant is present in a group message, from must equal participant (engine uses participant ?? chat). Update the value under message_reply_mention_group so from is '199999999999999@lid' (matching participant) instead of the phone-number JID; ensure any similar group examples follow the same rule (participant wins over chat for from).
🧹 Nitpick comments (1)
packages/engine-zapo/src/events.test.ts (1)
243-265: ⚡ Quick winConsider adding a test case for
remoteJidAltin direct messages.The Baileys test suite includes a
lidDirecttest case that verifiesfromAltderivation fromremoteJidAlt(lines 251-255 in packages/engine-baileys/src/events.test.ts). The Zapo tests only coverparticipantAltfor group messages but don't testremoteJidAltfor direct messages.If Zapo's event structure supports
remoteJidAlt, consider adding a similar test case for consistency and completeness.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/engine-zapo/src/events.test.ts` around lines 243 - 265, Add a test that mirrors the Baileys "lidDirect" case: create a direct-message event using mapZapoMessageEvent + base where key includes remoteJidAlt (e.g., remoteJid: '123@s.whatsapp.net', remoteJidAlt: '55@lid', fromMe: false, isGroup: false) and assert that the resulting object from mapZapoMessageEvent exposes fromAlt equal to the remoteJidAlt; also include a plain message case (no remoteJidAlt) asserting fromAlt is undefined. Reference mapZapoMessageEvent and base to locate where to add the new assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/api/src/http/openapi.ts`:
- Around line 313-333: The example object message_reply_mention_group has an
inconsistent from field: when participant is present in a group message, from
must equal participant (engine uses participant ?? chat). Update the value under
message_reply_mention_group so from is '199999999999999@lid' (matching
participant) instead of the phone-number JID; ensure any similar group examples
follow the same rule (participant wins over chat for from).
---
Nitpick comments:
In `@packages/engine-zapo/src/events.test.ts`:
- Around line 243-265: Add a test that mirrors the Baileys "lidDirect" case:
create a direct-message event using mapZapoMessageEvent + base where key
includes remoteJidAlt (e.g., remoteJid: '123@s.whatsapp.net', remoteJidAlt:
'55@lid', fromMe: false, isGroup: false) and assert that the resulting object
from mapZapoMessageEvent exposes fromAlt equal to the remoteJidAlt; also include
a plain message case (no remoteJidAlt) asserting fromAlt is undefined. Reference
mapZapoMessageEvent and base to locate where to add the new assertion.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7a658c14-e0fc-4d2c-bba0-cc8d4288df13
📒 Files selected for processing (8)
apps/api/src/http/openapi.tspackages/engine-baileys/src/engine.tspackages/engine-baileys/src/events.test.tspackages/engine-baileys/src/events.tspackages/engine-zapo/src/engine.tspackages/engine-zapo/src/events.test.tspackages/engine-zapo/src/events.tspackages/types/src/engine.ts
…o direct fromAlt Review follow-up on #19: - openapi: in the lid-addressed group example, `from` now equals `participant` (`@lid`), matching the engine rule `from = participant ?? chat`; the phone number stays in `fromAlt`. - engine-zapo test: add the direct-chat case (remoteJidAlt, no participant) so `fromAlt` coverage mirrors the baileys lidDirect test.
Docstrings generation was requested by @jobasfernandes. * #19 (comment) The following files were modified: * `packages/engine-baileys/src/events.ts` * `packages/engine-zapo/src/events.ts`
|
Note Docstrings generation - SUCCESS |
Closes #17. Closes #18.
Two related additions to the normalized message/webhook contract, kept neutral and identical across the zapo and baileys engines. Split into two atomic commits.
1.
fromAlt— alternate lid↔pn addressing (#17)With the LID migration,
from/participantcan arrive as@lid. Both engines already resolve the alternate addressing on the message key (participantAlt/remoteJidAlt), so the message event now carries an optionalfromAlt— the phone-number jid whenfromis a lid, or vice-versa, mirroringfrom = participant ?? chat. Omitted when the engine doesn't provide it.fromAltonmessageEventSchema.fromAlt = participantAlt ?? remoteJidAltfrom the key.WebhookDeliverymessage branch + description + a lid-addressed group example.2. Unified reaction delivery (#18)
Reactions were only normalized when they arrived as a
reactionMessageinsidemessages.upsert. Investigation showed this was wrong in different ways per engine:addons.autoDecryptis on by default, so reactions are delivered as decryptedmessage_addonevents (kind: 'reaction'), not asmessage. The engine never subscribed to that event, so reactions were silently dropped. This is the real bug.reactionMessagealready arrives viamessages.upsertand firesmessages.reaction— listening to both would duplicate. Encrypted reactions (encReactionMessage) would also slip through the upsert path asunknown.Both engines now use a single dedicated reaction source that produces the exact same normalized payload the upsert produced:
message_addon, mapkind: 'reaction'→content.type: 'reaction'(other addon kinds ignored for now — see feat(webhooks): evento de mensagem editada #8 for edits).messages.reaction(envelope = who reacted,entry.key= target) and skipreactionMessage/encReactionMessagein the upsert path — exactly one source, no duplication, and encrypted reactions are covered as soon as the engine decrypts them.fromAltand the existinginboundReactionContentSchema.Tests
fromAltpresent for lid group/direct, absent otherwise, on both engines.mapBaileysReaction(group emoji, removal → null emoji, envelope-vs-target mapping,fromAlt);isBaileysReactionUpsert(plain + encrypted true, other content false).mapZapoReaction(reaction addon → normalized event; non-reaction addon → null).lint,typecheck,build,test:coverage(branches ≥ 70%) all pass.Notes
messages.reactioninstead of leaving the upsert path? Long-term it gives one canonical reaction source per engine with explicit who-reacted/target semantics, keeps the two engines symmetric, and is future-proof: when baileys starts decryptingencReactionMessage, it already flows throughmessages.reaction(the upsert would only surface it asunknown). Risk is contained by reproducing the prior payload field-for-field and skipping reactions in the upsert so there is never a duplicate.timestampon baileys is derived fromreaction.senderTimestampMs(ms → s); on zapo the addon carries no reliable timestamp, so it's omitted.emoji: null(a reaction removal), matching the existingreactionMessagemapping.ack/presencelid↔pn resolution can follow separately if wanted.Summary by CodeRabbit
New Features
fromAlt) in message eventsDocumentation
fromAltbehavior alongside existing fields