Skip to content
Open
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
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,41 @@ http://localhost:9876/v15.0/5549988290955/messages \
}'
```

To react to a message

```sh
curl -i -X POST \
http://localhost:9876/v15.0/5549988290955/messages \
-H 'Content-Type: application/json' \
-H 'Authorization: 1' \
-d '{
"messaging_product": "whatsapp",
"to": "5549988290955",
"type": "reaction",
"reaction": {
"message_id": "MESSAGE_ID",
"emoji": "👍"
}
}'
```

To send a sticker (PNG/JPG/GIF are auto-converted to WEBP)

```sh
curl -i -X POST \
http://localhost:9876/v15.0/5549988290955/messages \
-H 'Content-Type: application/json' \
-H 'Authorization: 1' \
-d '{
"messaging_product": "whatsapp",
"to": "5549988290955",
"type": "sticker",
"sticker": {
"link": "https://example.com/sticker.png"
}
}'
```

## Media

To test media
Expand Down
80 changes: 80 additions & 0 deletions __tests__/services/reaction_helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { resolveReactionPayload } from '../../src/services/reaction_helper'
import { SendError } from '../../src/services/send_error'
import { toBaileysMessageContent } from '../../src/services/transformer'

describe('resolveReactionPayload', () => {
test('resolves reaction key and target', async () => {
const loadKey = jest.fn(async (id: string) => {
if (id === 'UNO_ID') {
return {
id: 'BAILEYS_ID',
remoteJid: '554988189915@s.whatsapp.net',
fromMe: true,
}
}
return undefined
})
const loadUnoId = jest.fn(async (id: string) => (id === 'MSG_ID' ? 'UNO_ID' : undefined))
const loadMessage = jest.fn(async () => ({ key: { id: 'BAILEYS_ID', remoteJid: '554988189915@s.whatsapp.net', fromMe: true } }))
const dataStore = { loadKey, loadUnoId, loadMessage }
const payload = {
type: 'reaction',
reaction: { message_id: 'MSG_ID', emoji: '👍' },
}
const result = await resolveReactionPayload(payload, dataStore)
expect(result.emoji).toEqual('👍')
expect(result.targetTo).toEqual('554988189915@s.whatsapp.net')
expect(result.reactionKey).toMatchObject({ id: 'BAILEYS_ID', remoteJid: '554988189915@s.whatsapp.net' })
})

test('throws on missing message_id', async () => {
const dataStore = {}
await expect(resolveReactionPayload({ type: 'reaction', reaction: { emoji: 'ok' } }, dataStore)).rejects.toBeInstanceOf(SendError)
})

test('cloud api reaction payload to baileys content', async () => {
const dataStore = {
loadKey: jest.fn(async (id: string) => {
if (id === '3EB0778F74E14FF7B1FCA4') {
return {
id: 'BAILEYS_ID',
remoteJid: '556696269251@s.whatsapp.net',
fromMe: true,
}
}
return undefined
}),
loadUnoId: jest.fn(async () => undefined),
loadMessage: jest.fn(async () => ({ key: { id: 'BAILEYS_ID', remoteJid: '556696269251@s.whatsapp.net', fromMe: true } })),
}
const cloudInput = {
messaging_product: 'whatsapp',
to: '556696269251',
type: 'reaction',
reaction: {
message_id: '3EB0778F74E14FF7B1FCA4',
emoji: '👍',
},
}
const resolved = await resolveReactionPayload(cloudInput, dataStore)
const resolvedPayload = {
...cloudInput,
reaction: {
...(cloudInput as any).reaction,
emoji: resolved.emoji,
key: resolved.reactionKey,
},
}
const result = toBaileysMessageContent(resolvedPayload)
expect(result).toEqual({
react: {
text: '👍',
key: {
id: 'BAILEYS_ID',
remoteJid: '556696269251@s.whatsapp.net',
fromMe: true,
},
},
})
})
})
140 changes: 139 additions & 1 deletion __tests__/services/transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
extractFromPhone,
extractTypeMessage,
} from '../../src/services/transformer'
import { resolveReactionPayload } from '../../src/services/reaction_helper'
const key = { remoteJid: 'XXXX@s.whatsapp.net', id: 'abc' }

const documentMessage: proto.Message.IDocumentMessage = {
Expand Down Expand Up @@ -1652,6 +1653,143 @@ describe('service transformer', () => {
expect(result).toEqual(output)
})

const buildReactionDataStore = () => ({
loadKey: jest.fn(async (id: string) => {
if (id === 'REACTION_MESSAGE_ID') {
return {
id: 'REACTION_KEY_ID',
remoteJid: '554988189915@s.whatsapp.net',
fromMe: true,
}
}
return undefined
}),
loadUnoId: jest.fn(async () => undefined),
loadMessage: jest.fn(async () => ({ key: { id: 'REACTION_KEY_ID', remoteJid: '554988189915@s.whatsapp.net', fromMe: true } })),
})

test('toBaileysMessageContent reaction (cloud input)', async () => {
const cloudInput = {
messaging_product: 'whatsapp',
to: '554988189915',
type: 'reaction',
reaction: {
message_id: 'REACTION_MESSAGE_ID',
emoji: 'ok',
},
}
const resolved = await resolveReactionPayload(cloudInput, buildReactionDataStore())
const resolvedPayload = {
...cloudInput,
reaction: {
...(cloudInput as any).reaction,
emoji: resolved.emoji,
key: resolved.reactionKey,
},
}
const result = toBaileysMessageContent(resolvedPayload)
expect(result).toEqual({
react: {
text: 'ok',
key: {
remoteJid: '554988189915@s.whatsapp.net',
fromMe: true,
id: 'REACTION_KEY_ID',
},
},
})
})

test('toBaileysMessageContent reaction without emoji (cloud input)', async () => {
const cloudInput = {
messaging_product: 'whatsapp',
to: '554988189915',
type: 'reaction',
reaction: {
message_id: 'REACTION_MESSAGE_ID',
},
}
const resolved = await resolveReactionPayload(cloudInput, buildReactionDataStore())
const resolvedPayload = {
...cloudInput,
reaction: {
...(cloudInput as any).reaction,
emoji: resolved.emoji,
key: resolved.reactionKey,
},
}
const result = toBaileysMessageContent(resolvedPayload)
expect(result).toEqual({
react: {
text: '',
key: {
remoteJid: '554988189915@s.whatsapp.net',
fromMe: true,
id: 'REACTION_KEY_ID',
},
},
})
})

test('toBaileysMessageContent reaction with empty emoji (cloud input)', async () => {
const cloudInput = {
messaging_product: 'whatsapp',
to: '554988189915',
type: 'reaction',
reaction: {
message_id: 'REACTION_MESSAGE_ID',
emoji: '',
},
}
const resolved = await resolveReactionPayload(cloudInput, buildReactionDataStore())
const resolvedPayload = {
...cloudInput,
reaction: {
...(cloudInput as any).reaction,
emoji: resolved.emoji,
key: resolved.reactionKey,
},
}
const result = toBaileysMessageContent(resolvedPayload)
expect(result).toEqual({
react: {
text: '',
key: {
remoteJid: '554988189915@s.whatsapp.net',
fromMe: true,
id: 'REACTION_KEY_ID',
},
},
})
})

test('toBaileysMessageContent reaction without key', async () => {
const input = {
type: 'reaction',
reaction: {
emoji: 'ok',
},
}
expect(() => toBaileysMessageContent(input)).toThrow('invalid_reaction_payload: missing key')
})

test('toBaileysMessageContent sticker', async () => {
const input = {
type: 'sticker',
sticker: {
link: 'https://example.com/sticker.png',
},
}
const output = {
mimetype: 'image/png',
sticker: {
url: 'https://example.com/sticker.png',
},
}
const result = toBaileysMessageContent(input)
expect(result).toEqual(output)
})

test('fromBaileysMessageContent participant outside key', async () => {
const phoneNumer = '5549998093075'
const remotePhoneNumber = '11115551212'
Expand Down Expand Up @@ -2467,4 +2605,4 @@ describe('service transformer', () => {
// }
// }
// }
// }
// }
Loading