Repository navigation
feat(baileys): add sendPollVote and sendEventResponse endpoints - #2777
Open
timohissink wants to merge 2 commits into
Open
timohissink wants to merge 2 commits into
timohissink wants to merge 2 commits into
Conversation
Allow voting on existing polls and responding to event invitations by encrypting the vote/response with the stored messageSecret (inverse of Baileys decryptPollVote and decryptEventResponse). Uses the LID domain for creator/voter when the chat uses LID addressing or the message was sent by this account; jidMode can override (auto|lid|pn). Unsupported on the Business API and Evolution channels.
Contributor
Reviewer's GuideAdds validated POST endpoints for casting encrypted poll votes and submitting encrypted event RSVPs, with Baileys-specific message-secret encryption, PN/LID JID resolution, protocol-compatible relay metadata, and explicit unsupported responses on other channel types. Sequence diagram for encrypted poll vote submissionsequenceDiagram
actor Client
participant Router
participant Controller
participant Baileys
participant Database
participant WhatsApp
Client->>Router: POST sendPollVote
Router->>Router: pollVoteMessageSchema validation
Router->>Controller: sendPollVote
Controller->>Baileys: pollVoteMessage(data)
Baileys->>Database: findFirst message by instanceId and key.id
Database-->>Baileys: poll creation and messageSecret
Baileys->>Baileys: getMessageSecretContext
Baileys->>Baileys: encryptMessageSecretPayload
Baileys->>WhatsApp: relayMessage with poll metadata
WhatsApp-->>Baileys: voteMessageId
Baileys-->>Controller: SENT response
Controller-->>Client: 201 voteMessageId and crypto JIDs
Sequence diagram for encrypted event RSVP submissionsequenceDiagram
actor Client
participant Router
participant Controller
participant Baileys
participant Database
participant WhatsApp
Client->>Router: POST sendEventResponse
Router->>Router: eventResponseMessageSchema validation
Router->>Controller: sendEventResponse
Controller->>Baileys: eventResponseMessage(data)
Baileys->>Database: findFirst event by instanceId and key.id
Database-->>Baileys: event message and messageSecret
Baileys->>Baileys: getMessageSecretContext
Baileys->>Baileys: encryptMessageSecretPayload
Baileys->>WhatsApp: relayMessage with event metadata
WhatsApp-->>Baileys: responseMessageId
Baileys-->>Controller: SENT response
Controller-->>Client: 201 responseMessageId and crypto JIDs
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Contributor
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts" line_range="3983" />
<code_context>
+ });
+
+ if (!stored) {
+ throw new NotFoundException('Message not found');
+ }
+
</code_context>
<issue_to_address>
**New errors use English**
When a request reaches one of the new error paths, the new exceptions and validation errors return English messages instead of Portuguese, so callers receive errors that do not follow the repository's user-facing language requirement.
Return Portuguese messages for the newly added user-facing errors.
Also at `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:3994`, `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:3999`, `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:4012`, `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:4091`, `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:4099`, `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:4142`, `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:4193`, `src/api/integrations/channel/meta/whatsapp.business.service.ts:1764`, `src/api/integrations/channel/meta/whatsapp.business.service.ts:1767`, `src/api/integrations/channel/evolution/evolution.channel.service.ts:785`, `src/api/integrations/channel/evolution/evolution.channel.service.ts:788`.
</issue_to_address>Sourcery assessment
Needs a human reviewer. If the message-secret key or JID derivation is wrong, the endpoint could relay a poll vote or event response that does not match the caller’s intended action. That response is sent outside the service and may remain visible after a revert; reverting only removes the endpoints.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📋 Description
This PR adds two Baileys endpoints that send real, encrypted responses to existing messages:
POST /message/sendPollVote/{instance}casts a vote on a poll (pollUpdateMessage). It is not an emoji reaction. WhatsApp clients count it in the poll results.POST /message/sendEventResponse/{instance}sends an RSVP to an event message (encEventResponseMessage: going / maybe / not_going, plus an optionalextraGuestCount).Evolution can already create polls (
sendPoll) and decrypt received votes (getPollVote, #2297), but it had no way to cast a vote or answer an event.Implementation (follows the existing
sendPoll/sendReactionpattern; 7 files, +325/−1):src/api/dto/sendMessage.dto.tsSendPollVoteDto,SendEventResponseDtosrc/validate/message.schema.tspollVoteMessageSchema,eventResponseMessageSchema(JSONSchema7; max 12 options;jidModeenum)src/api/controllers/sendMessage.controller.tssendPollVote,sendEventResponse→waMonitordelegationsrc/api/routes/sendMessage.router.tsPOST sendPollVote,POST sendEventResponsesrc/api/integrations/channel/whatsapp/whatsapp.baileys.service.tspollVoteMessage,eventResponseMessage, plus private helpersgetMessageSecretContext/encryptMessageSecretPayloadsrc/api/integrations/channel/meta/whatsapp.business.service.tsBadRequestException('Método não disponível na WhatsApp Business API')src/api/integrations/channel/evolution/evolution.channel.service.tsBadRequestException('Método não disponível no Evolution Channel')How it works
Message, scoped byinstanceId. The request'sremoteJidmust match the storedremoteJidorremoteJidAlt. The message type must bepollCreationMessage(V2/V3)oreventMessage. A 32-bytemessageSecretmust be present. Each failure returns a clear 400 or 404.selectableOptionsCount. Unknown options return the list of available ones.decryptPollVoteand whatsmeowencryptMsgSecret: HMAC-SHA256 key derivation overmsgId + creatorJid + voterJid + "Poll Vote" | "Event Response", then AES-256-GCM with AADmsgId\0voterJid. It uses only Nodecrypto, with no new dependency.jidMode(auto|lid|pn, defaultauto): in auto mode the LID domain is used when the chat or author uses LID addressing, and the own LID is used forfromMemessages, including the self-chat.lidandpnforce one domain for edge cases.pollCreationMessageKey.remoteJidand the relay target are the LID chat JID, matching what the primary device sends.type="poll"and<meta polltype="vote" content_type="add_on">, the same markers a phone puts on its own votes (see Baileys PR #2870: without them the server acks but does not deliver the vote). The response returnsvoteMessageIdand the JIDs used (crypto.creator,crypto.voter,crypto.keyRemoteJid).No database migration (PostgreSQL and MySQL are unaffected). No new environment variables. No breaking change.
🔗 Related Issue
Closes #2776
🧪 Type of Change
🧪 Testing
Verified so far (Evolution 2.3.7 + this patch, self-chat test poll):
Votes cast on the phone for the same poll decrypt with Baileys
decryptPollVoteusing creator = voter = own LID; the endpoint derives the same JIDs (auto).Field-by-field match with the phone's own votes: chat JID
@lid,pollCreationMessageKey { id, fromMe: true, remoteJid: <own LID> }without participant,metadata: {},senderTimestampMsin ms, 12-byteencIv, nomessageContextInfoon the vote.Round trip: a payload built with the same derivation decrypts to the selected option hash with Baileys
decryptPollVote.Baileys debug log: the stanza is relayed with
type="poll"+<meta polltype="vote">to the primary device (sending message to 1 devices); no retry receipt.Error paths: unknown option → 400 with
availablelist; unknown message id → 404; nothing is sent in those cases.Baileys does not set
decrypt-fail="hide"on theencnode for poll updates/event responses and this is not configurable viarelayMessage(onlypinInChatMessagegets it). The test deployment adds it with a one-line patch tomessages-send.js(as proposed upstream in Baileys PR #2870); this PR does not patch Baileys. Maintainers may prefer apatches/entry or waiting for the upstream fix.Manual testing completed (self-chat poll vote on a real device; other scenarios pending)
Functionality verified in development environment
No breaking changes introduced
Tested with different connection types (if applicable): Baileys only. Business API and Evolution return "Método não disponível".
Checks run locally on this branch (rebased on current
develop):npm ci✅ ·npm run db:generate✅ ·npm run lint:check✅ (0 warnings) ·prettier --checkon changed files ✅ ·tsc --noEmit✅ ·npm run build✅ · commitlint ✅Planned scenarios: group poll from another user (PN and LID), 1:1 poll, own poll in the self-chat, multi-select poll, unknown option, event Going / Maybe / Not going with guests, and a message without
messageSecret.📸 Screenshots (if applicable)
To be added after on-device verification.
✅ Checklist
📝 Additional Notes
Evolution-API/message.yaml), outside this repo. I'm happy to send the two endpoints there if you point me to the docs repo.sendPollVoteunder/chat, next togetPollVote, if you prefer.Made-with: Cursor
Summary by Sourcery
Enable sending encrypted poll votes and event RSVPs through the messaging API.
New Features:
Enhancements: