Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **Native WhatsApp polls** via `POST /api/sessions/:sessionId/messages/send-poll`: question, 2–12 options and an optional `allowMultipleAnswers` flag (default single choice), implemented on both engines (whatsapp-web.js `Poll`, Baileys `poll` content with `selectableCount` 1/0). The message history stores the poll question as the body so the log stays readable.
- **Native WhatsApp polls** via `POST /api/sessions/:sessionId/messages/send-poll`: question, 2–12 options and an optional `allowMultipleAnswers` flag (default single choice), implemented on both engines (whatsapp-web.js `Poll`, Baileys `poll` content with `selectableCount` 1/0). The message history stores the poll question as the body so the log stays readable. Polls are a first-class `poll` message type end to end — both engines map incoming poll messages to it, so the websocket/webhook events, persisted rows, and dashboard all report `poll` consistently. Thanks @alejo117.

## [0.8.7] - 2026-07-03

Expand Down
1 change: 1 addition & 0 deletions dashboard/src/components/DashboardCharts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const TYPE_COLORS: Record<string, string> = {
video: '#14b8a6',
sticker: '#ef4444',
location: '#84cc16',
poll: '#6366f1',
revoked: '#f43f5e',
masked: '#8b5cf6',
unknown: '#64748b',
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export const MESSAGE_TYPES = [
'sticker',
'location',
'contact',
'poll',
'call',
'revoked',
'masked',
Expand Down
5 changes: 4 additions & 1 deletion src/engine/adapters/baileys-message-mapper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ describe('mapBaileysMessageType (baileys content-type -> neutral MessageType)',
// its own `masked` type so it is distinguishable from a genuinely unparseable message (#574).
['placeholderMessage', false, 'masked'],
[undefined, false, 'unknown'],
['pollCreationMessage', false, 'unknown'],
// Native polls surface as their own `poll` type (WhatsApp bumps the content key across versions).
['pollCreationMessage', false, 'poll'],
['pollCreationMessageV2', false, 'poll'],
['pollCreationMessageV3', false, 'poll'],
// Regression trap: calls arrive via the `call` socket event, never as a message content type,
// so any call-ish token must stay 'unknown' (no accidental mapping).
['callLogMessage', false, 'unknown'],
Expand Down
5 changes: 5 additions & 0 deletions src/engine/adapters/baileys-message-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ export function mapBaileysMessageType(contentType: string | undefined, isPtt = f
case 'contactMessage':
case 'contactsArrayMessage':
return 'contact';
case 'pollCreationMessage':
case 'pollCreationMessageV2':
case 'pollCreationMessageV3':
// Native polls; WhatsApp bumps the content key across versions, all map to the same neutral type.
return 'poll';
case 'interactiveMessage':
case 'buttonsMessage':
case 'templateMessage':
Expand Down
1 change: 1 addition & 0 deletions src/engine/adapters/message-mapper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ describe('mapWwebjsMessageType (engine type-token -> neutral MessageType boundar
['multi_vcard', 'contact'],
['call_log', 'call'],
['revoked', 'revoked'],
['poll_creation', 'poll'],
['e2e_notification', 'unknown'], // any unmapped wwebjs type
])('maps wwebjs type %s -> %s', (raw, expected) => {
expect(mapWwebjsMessageType(raw)).toBe(expected);
Expand Down
2 changes: 2 additions & 0 deletions src/engine/adapters/message-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export function mapWwebjsMessageType(raw: string): MessageType {
return 'contact';
case 'call_log':
return 'call';
case 'poll_creation':
return 'poll';
case 'revoked':
return 'revoked';
default:
Expand Down
1 change: 1 addition & 0 deletions src/engine/interfaces/whatsapp-engine.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export type MessageType =
| 'sticker'
| 'location'
| 'contact'
| 'poll'
| 'call'
| 'revoked'
// A message WhatsApp deliberately withheld from linked/companion devices (e.g. high-security
Expand Down
4 changes: 4 additions & 0 deletions src/modules/message/message.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,10 @@ describe('MessageService', () => {
options: ['Park', 'Beach'],
allowMultipleAnswers: false,
});
// A poll has no plain-text body, so it is persisted as type 'poll' with the question as the body.
expect(repository.create).toHaveBeenCalledWith(
expect.objectContaining({ type: 'poll', body: '📊 Where should we meet?' }),
);
});

it('should pass allowMultipleAnswers through to the engine', async () => {
Expand Down
Loading