Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
92 changes: 85 additions & 7 deletions src/services/client_baileys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { t } from '../i18n'
import { ClientForward } from './client_forward'
import { SendError } from './send_error'
import audioConverter from '../utils/audio_converter'
import { convertToWebpSticker } from '../utils/sticker_convert'

const attempts = 3

Expand Down Expand Up @@ -465,9 +466,60 @@ export class ClientBaileys implements Client {
throw new Error(`Unknow message status ${status}`)
}
} else if (type) {
if (['text', 'image', 'audio', 'sticker', 'document', 'video', 'template', 'interactive', 'contacts'].includes(type)) {
if (['text', 'image', 'audio', 'sticker', 'document', 'video', 'template', 'interactive', 'contacts', 'reaction'].includes(type)) {
let content
if ('template' === type) {
let targetTo = to
const extraSendOptions: any = {}
if ('reaction' === type) {
const reaction = payload?.reaction || {}
const messageId =
reaction?.message_id ||
reaction?.messageId ||
payload?.message_id ||
payload?.context?.message_id ||
payload?.context?.id
if (!messageId) {
throw new SendError(400, 'invalid_reaction_payload: missing message_id')
}
const dataStore = this.store?.dataStore
let key = await dataStore?.loadKey(messageId)
if (!key) {
const unoId = await dataStore?.loadUnoId(messageId)
if (unoId) {
key = await dataStore?.loadKey(unoId)
}
}
if (!key || !key.id || !key.remoteJid) {
throw new SendError(404, `reaction_message_not_found: ${messageId}`)
}
const emojiRaw = typeof reaction?.emoji !== 'undefined'
? reaction.emoji
: (typeof reaction?.text !== 'undefined' ? reaction.text : reaction?.value)
const emoji = `${emojiRaw ?? ''}`
let reactionKey = key
try {
const original = await dataStore?.loadMessage?.(reactionKey.remoteJid, reactionKey.id)
if (original?.key) {
reactionKey = { ...original.key, id: reactionKey.id }
if (typeof reactionKey.participant === 'string' && reactionKey.participant.trim() === '') {
delete (reactionKey as any).participant
}
}
} catch {}
try {
logger.info(
'REACTION send: msgId=%s key.id=%s key.remoteJid=%s key.participant=%s',
messageId,
reactionKey?.id || '<none>',
reactionKey?.remoteJid || '<none>',
(reactionKey as any)?.participant || '<none>',
)
} catch {}
content = { react: { text: emoji, key: reactionKey } }
targetTo = reactionKey.remoteJid
extraSendOptions.forceRemoteJid = reactionKey.remoteJid
extraSendOptions.skipBrSendOrder = true
} else if ('template' === type) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const template = new Template(this.getConfig)
content = await template.bind(this.phone, payload.template.name, payload.template.components)
} else {
Expand All @@ -481,6 +533,30 @@ export class ClientBaileys implements Client {
}
}
content = toBaileysMessageContent(payload, this.config.customMessageCharactersFunction)
if (type === 'sticker') {
try {
const stickerPayload: any = payload?.sticker || {}
const stickerLink = stickerPayload?.link || (content as any)?.sticker?.url
const cleanLink = `${stickerLink || ''}`.split('?')[0].split('#')[0]
const stickerMimeRaw = `${stickerPayload?.mime_type || stickerPayload?.mimetype || (content as any)?.mimetype || ''}`.toLowerCase()
const isWebp = stickerMimeRaw.includes('webp') || cleanLink.toLowerCase().endsWith('.webp')
if (stickerLink && !isWebp && typeof (content as any)?.sticker === 'object' && (content as any)?.sticker?.url) {
const resp = await fetch(stickerLink, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), method: 'GET' })
if (!resp?.ok) {
throw new Error(`sticker_download_failed: ${resp?.status || 0}`)
}
const contentType = `${resp.headers.get('content-type') || ''}`.toLowerCase()
const isAnimated = contentType.includes('gif') || cleanLink.toLowerCase().endsWith('.gif')
const buf = Buffer.from(await resp.arrayBuffer())
const webp = await convertToWebpSticker(buf, { animated: isAnimated })
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
;(content as any).sticker = webp
;(content as any).mimetype = 'image/webp'
logger.debug('Sticker converted to webp for %s', stickerLink)
}
} catch (err) {
logger.warn(err, 'Ignore error converting sticker to webp sending original')
}
}
}
let quoted: WAMessage | undefined = undefined
let disappearingMessagesInChat: boolean | number = false
Expand All @@ -490,7 +566,7 @@ export class ClientBaileys implements Client {
const key = await this.store?.dataStore?.loadKey(messageId)
logger.debug('Quoted message key %s!', key?.id)
if (key?.id) {
const remoteJid = phoneNumberToJid(to)
const remoteJid = phoneNumberToJid(targetTo)
quoted = await this.store?.dataStore.loadMessage(remoteJid, key?.id)
if (!quoted) {
const unoId = await this.store?.dataStore?.loadUnoId(key?.id)
Expand Down Expand Up @@ -527,12 +603,12 @@ export class ClientBaileys implements Client {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const sockDelays = delays.get(this.phone) || (delays.set(this.phone, new Map<string, Delay>()) && delays.get(this.phone)!)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const toDelay = sockDelays.get(to) || (async (_phone: string, to) => sockDelays.set(to, this.delayBeforeSecondMessage))
await toDelay(this.phone, to)
const toDelay = sockDelays.get(targetTo) || (async (_phone: string, to) => sockDelays.set(to, this.delayBeforeSecondMessage))
await toDelay(this.phone, targetTo)
let response
if (content?.listMessage) {
response = await this.sendMessage(
to,
targetTo,
{
forward: {
key: {
Expand All @@ -548,14 +624,16 @@ export class ClientBaileys implements Client {
composing: this.config.composingMessage,
quoted,
disappearingMessagesInChat,
...extraSendOptions,
...options,
},
)
} else {
response = await this.sendMessage(to, content, {
response = await this.sendMessage(targetTo, content, {
composing: this.config.composingMessage,
quoted,
disappearingMessagesInChat,
...extraSendOptions,
...options,
})
}
Expand Down
13 changes: 13 additions & 0 deletions src/utils/sticker_convert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import sharp from 'sharp'

type StickerConvertOptions = {
animated?: boolean
}

export const convertToWebpSticker = async (input: Buffer, opts: StickerConvertOptions = {}) => {
const image = sharp(input, { animated: !!opts.animated })
return image
.resize(512, 512, { fit: 'inside', withoutEnlargement: true })
.webp({ lossless: !opts.animated, quality: 80, effort: 4 })
.toBuffer()
}