mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
Got rid of WAClient, deprecated code. Prep for V3
Layered classes based on hierarchy as well.
This commit is contained in:
@@ -1,40 +1,39 @@
|
|||||||
import {
|
import {
|
||||||
WAClient,
|
WAConnection,
|
||||||
MessageType,
|
MessageType,
|
||||||
decodeMediaMessage,
|
|
||||||
Presence,
|
Presence,
|
||||||
MessageOptions,
|
MessageOptions,
|
||||||
Mimetype,
|
Mimetype,
|
||||||
WALocationMessage,
|
WALocationMessage,
|
||||||
MessageLogLevel,
|
MessageLogLevel,
|
||||||
WAMessageType,
|
WAMessageType,
|
||||||
} from '../src/WAClient/WAClient'
|
} from '../src/WAConnection/WAConnection'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
|
|
||||||
async function example() {
|
async function example() {
|
||||||
const client = new WAClient() // instantiate
|
const conn = new WAConnection() // instantiate
|
||||||
client.autoReconnect = true // auto reconnect on disconnect
|
conn.autoReconnect = true // auto reconnect on disconnect
|
||||||
client.logLevel = MessageLogLevel.info // set to unhandled to see what kind of stuff you can implement
|
conn.logLevel = MessageLogLevel.info // set to unhandled to see what kind of stuff you can implement
|
||||||
|
|
||||||
// connect or timeout in 20 seconds (loads the auth file credentials if present)
|
// connect or timeout in 20 seconds (loads the auth file credentials if present)
|
||||||
const [user, chats, contacts] = await client.connect('./auth_info.json', 20 * 1000)
|
const [user, chats, contacts] = await conn.connect('./auth_info.json', 20 * 1000)
|
||||||
const unread = chats.all().flatMap (chat => chat.messages.slice(chat.messages.length-chat.count))
|
const unread = chats.all().flatMap (chat => chat.messages.slice(chat.messages.length-chat.count))
|
||||||
|
|
||||||
console.log('oh hello ' + user.name + ' (' + user.id + ')')
|
console.log('oh hello ' + user.name + ' (' + user.id + ')')
|
||||||
console.log('you have ' + chats.all().length + ' chats & ' + contacts.length + ' contacts')
|
console.log('you have ' + chats.all().length + ' chats & ' + contacts.length + ' contacts')
|
||||||
console.log ('you have ' + unread.length + ' unread messages')
|
console.log ('you have ' + unread.length + ' unread messages')
|
||||||
|
|
||||||
const authInfo = client.base64EncodedAuthInfo() // get all the auth info we need to restore this session
|
const authInfo = conn.base64EncodedAuthInfo() // get all the auth info we need to restore this session
|
||||||
fs.writeFileSync('./auth_info.json', JSON.stringify(authInfo, null, '\t')) // save this info to a file
|
fs.writeFileSync('./auth_info.json', JSON.stringify(authInfo, null, '\t')) // save this info to a file
|
||||||
/* Note: one can take this auth_info.json file and login again from any computer without having to scan the QR code,
|
/* Note: one can take this auth_info.json file and login again from any computer without having to scan the QR code,
|
||||||
and get full access to one's WhatsApp. Despite the convenience, be careful with this file */
|
and get full access to one's WhatsApp. Despite the convenience, be careful with this file */
|
||||||
client.setOnPresenceUpdate(json => console.log(json.id + ' presence is ' + json.type))
|
conn.setOnPresenceUpdate(json => console.log(json.id + ' presence is ' + json.type))
|
||||||
client.setOnMessageStatusChange(json => {
|
conn.setOnMessageStatusChange(json => {
|
||||||
const participant = json.participant ? ' (' + json.participant + ')' : '' // participant exists when the message is from a group
|
const participant = json.participant ? ' (' + json.participant + ')' : '' // participant exists when the message is from a group
|
||||||
console.log(`${json.to}${participant} acknlowledged message(s) ${json.ids} as ${json.type}`)
|
console.log(`${json.to}${participant} acknlowledged message(s) ${json.ids} as ${json.type}`)
|
||||||
})
|
})
|
||||||
// set to false to NOT relay your own sent messages
|
// set to false to NOT relay your own sent messages
|
||||||
client.setOnUnreadMessage(true, async (m) => {
|
conn.setOnUnreadMessage(true, async (m) => {
|
||||||
const messageStubType = WAMessageType[m.messageStubType] || 'MESSAGE'
|
const messageStubType = WAMessageType[m.messageStubType] || 'MESSAGE'
|
||||||
console.log('got notification of type: ' + messageStubType)
|
console.log('got notification of type: ' + messageStubType)
|
||||||
|
|
||||||
@@ -65,7 +64,7 @@ async function example() {
|
|||||||
const locMessage = m.message[messageType] as WALocationMessage
|
const locMessage = m.message[messageType] as WALocationMessage
|
||||||
console.log(`${sender} sent location (lat: ${locMessage.degreesLatitude}, long: ${locMessage.degreesLongitude})`)
|
console.log(`${sender} sent location (lat: ${locMessage.degreesLatitude}, long: ${locMessage.degreesLongitude})`)
|
||||||
|
|
||||||
decodeMediaMessage(m.message, './Media/media_loc_thumb_in_' + m.key.id) // save location thumbnail
|
await conn.downloadAndSaveMediaMessage(m, './Media/media_loc_thumb_in_' + m.key.id) // save location thumbnail
|
||||||
|
|
||||||
if (messageType === MessageType.liveLocation) {
|
if (messageType === MessageType.liveLocation) {
|
||||||
console.log(`${sender} sent live location for duration: ${m.duration/60}`)
|
console.log(`${sender} sent live location for duration: ${m.duration/60}`)
|
||||||
@@ -75,7 +74,7 @@ async function example() {
|
|||||||
// decode, decrypt & save the media.
|
// decode, decrypt & save the media.
|
||||||
// The extension to the is applied automatically based on the media type
|
// The extension to the is applied automatically based on the media type
|
||||||
try {
|
try {
|
||||||
const savedFile = await decodeMediaMessage(m.message, './Media/media_in_' + m.key.id)
|
const savedFile = await conn.downloadAndSaveMediaMessage(m, './Media/media_in_' + m.key.id)
|
||||||
console.log(sender + ' sent media, saved at: ' + savedFile)
|
console.log(sender + ' sent media, saved at: ' + savedFile)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('error in decoding message: ' + err)
|
console.log('error in decoding message: ' + err)
|
||||||
@@ -83,20 +82,18 @@ async function example() {
|
|||||||
}
|
}
|
||||||
// send a reply after 3 seconds
|
// send a reply after 3 seconds
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
await client.sendReadReceipt(m.key.remoteJid, m.key.id) // send read receipt
|
await conn.sendReadReceipt(m.key.remoteJid, m.key.id) // send read receipt
|
||||||
await client.updatePresence(m.key.remoteJid, Presence.available) // tell them we're available
|
await conn.updatePresence(m.key.remoteJid, Presence.available) // tell them we're available
|
||||||
await client.updatePresence(m.key.remoteJid, Presence.composing) // tell them we're composing
|
await conn.updatePresence(m.key.remoteJid, Presence.composing) // tell them we're composing
|
||||||
|
|
||||||
const options: MessageOptions = { quoted: m }
|
const options: MessageOptions = { quoted: m }
|
||||||
let content
|
let content
|
||||||
let type: MessageType
|
let type: MessageType
|
||||||
const rand = Math.random()
|
const rand = Math.random()
|
||||||
if (rand > 0.66) {
|
if (rand > 0.66) { // choose at random
|
||||||
// choose at random
|
|
||||||
content = 'hello!' // send a "hello!" & quote the message recieved
|
content = 'hello!' // send a "hello!" & quote the message recieved
|
||||||
type = MessageType.text
|
type = MessageType.text
|
||||||
} else if (rand > 0.33) {
|
} else if (rand > 0.33) { // choose at random
|
||||||
// choose at random
|
|
||||||
content = { degreesLatitude: 32.123123, degreesLongitude: 12.12123123 }
|
content = { degreesLatitude: 32.123123, degreesLongitude: 12.12123123 }
|
||||||
type = MessageType.location
|
type = MessageType.location
|
||||||
} else {
|
} else {
|
||||||
@@ -104,21 +101,21 @@ async function example() {
|
|||||||
options.mimetype = Mimetype.gif
|
options.mimetype = Mimetype.gif
|
||||||
type = MessageType.video
|
type = MessageType.video
|
||||||
}
|
}
|
||||||
const response = await client.sendMessage(m.key.remoteJid, content, type, options)
|
const response = await conn.sendMessage(m.key.remoteJid, content, type, options)
|
||||||
console.log("sent message with ID '" + response.messageID + "' successfully: " + (response.status === 200))
|
console.log("sent message with ID '" + response.key.id + "' successfully: " + (response.status === 200))
|
||||||
}, 3 * 1000)
|
}, 3 * 1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
/* example of custom functionality for tracking battery */
|
/* example of custom functionality for tracking battery */
|
||||||
client.registerCallback(['action', null, 'battery'], json => {
|
conn.registerCallback(['action', null, 'battery'], json => {
|
||||||
const batteryLevelStr = json[2][0][1].value
|
const batteryLevelStr = json[2][0][1].value
|
||||||
const batterylevel = parseInt(batteryLevelStr)
|
const batterylevel = parseInt(batteryLevelStr)
|
||||||
console.log('battery level: ' + batterylevel)
|
console.log('battery level: ' + batterylevel)
|
||||||
})
|
})
|
||||||
client.setOnUnexpectedDisconnect(reason => {
|
conn.setOnUnexpectedDisconnect(reason => {
|
||||||
if (reason === 'replaced') {
|
if (reason === 'replaced') {
|
||||||
// uncomment to reconnect whenever the connection gets taken over from somewhere else
|
// uncomment to reconnect whenever the connection gets taken over from somewhere else
|
||||||
// await client.connect ()
|
// await conn.connect ()
|
||||||
} else {
|
} else {
|
||||||
console.log ('oh no got disconnected: ' + reason)
|
console.log ('oh no got disconnected: ' + reason)
|
||||||
}
|
}
|
||||||
|
|||||||
150
README.md
150
README.md
@@ -21,11 +21,11 @@ To run the example script, download or clone the repo and then type the followin
|
|||||||
## Install
|
## Install
|
||||||
Create and cd to your NPM project directory and then in terminal, write:
|
Create and cd to your NPM project directory and then in terminal, write:
|
||||||
1. stable: `npm install @adiwajshing/baileys`
|
1. stable: `npm install @adiwajshing/baileys`
|
||||||
2. stabl-ish w quicker fixes & latest features: `npm install github:adiwajshing/baileys`
|
2. stabl-ish w quicker fixes & latest features: `npm install github:adiwajshing/baileys` (major changes incoming right now)
|
||||||
|
|
||||||
Then import in your code using:
|
Then import in your code using:
|
||||||
``` ts
|
``` ts
|
||||||
import { WAClient } from '@adiwajshing/baileys'
|
import { WAConnection } from '@adiwajshing/baileys'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Unit Tests
|
## Unit Tests
|
||||||
@@ -36,11 +36,11 @@ Set the phone number you can randomly send messages to in a `.env` file with `TE
|
|||||||
|
|
||||||
## Connecting
|
## Connecting
|
||||||
``` ts
|
``` ts
|
||||||
import { WAClient } from '@adiwajshing/baileys'
|
import { WAConnection } from '@adiwajshing/baileys'
|
||||||
|
|
||||||
async function connectToWhatsApp () {
|
async function connectToWhatsApp () {
|
||||||
const client = new WAClient()
|
const conn = new WAConnection()
|
||||||
const [user, chats, contacts] = await client.connect ()
|
const [user, chats, contacts] = await conn.connect ()
|
||||||
console.log ("oh hello " + user.name + " (" + user.id + ")")
|
console.log ("oh hello " + user.name + " (" + user.id + ")")
|
||||||
console.log ("you have " + chats.length + " chats")
|
console.log ("you have " + chats.length + " chats")
|
||||||
|
|
||||||
@@ -61,14 +61,14 @@ connectToWhatsApp ()
|
|||||||
If the connection is successful, you will see a QR code printed on your terminal screen, scan it with WhatsApp on your phone and you'll be logged in!
|
If the connection is successful, you will see a QR code printed on your terminal screen, scan it with WhatsApp on your phone and you'll be logged in!
|
||||||
If you don't want to wait for WhatsApp to send all your chats while connecting, you can use the following function:
|
If you don't want to wait for WhatsApp to send all your chats while connecting, you can use the following function:
|
||||||
``` ts
|
``` ts
|
||||||
import { WAClient } from '@adiwajshing/baileys'
|
import { WAConnection } from '@adiwajshing/baileys'
|
||||||
|
|
||||||
async function connectToWhatsApp () {
|
async function connectToWhatsApp () {
|
||||||
const client = new WAClient()
|
const conn = new WAConnection()
|
||||||
const user = await client.connectSlim ()
|
const user = await conn.connectSlim ()
|
||||||
console.log ("oh hello " + user.name + " (" + user.id + ")")
|
console.log ("oh hello " + user.name + " (" + user.id + ")")
|
||||||
|
|
||||||
client.receiveChatsAndContacts () // wait for chats & contacts in the background
|
conn.receiveChatsAndContacts () // wait for chats & contacts in the background
|
||||||
.then (([chats, contacts]) => {
|
.then (([chats, contacts]) => {
|
||||||
console.log ("you have " + chats.all().length + " chats and " + contacts.length + " contacts")
|
console.log ("you have " + chats.all().length + " chats and " + contacts.length + " contacts")
|
||||||
})
|
})
|
||||||
@@ -91,18 +91,18 @@ So, do the following the first time you connect:
|
|||||||
``` ts
|
``` ts
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
|
|
||||||
const client = new WAClient()
|
const conn = new WAConnection()
|
||||||
client.connectSlim() // connect first
|
conn.connectSlim() // connect first
|
||||||
.then (user => {
|
.then (user => {
|
||||||
const creds = client.base64EncodedAuthInfo () // contains all the keys you need to restore a session
|
const creds = conn.base64EncodedAuthInfo () // contains all the keys you need to restore a session
|
||||||
fs.writeFileSync('./auth_info.json', JSON.stringify(creds, null, '\t')) // save JSON to file
|
fs.writeFileSync('./auth_info.json', JSON.stringify(creds, null, '\t')) // save JSON to file
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, to restore a session:
|
Then, to restore a session:
|
||||||
``` ts
|
``` ts
|
||||||
const client = new WAClient()
|
const conn = new WAConnection()
|
||||||
client.connectSlim('./auth_info.json') // will load JSON credentials from file
|
conn.connectSlim('./auth_info.json') // will load JSON credentials from file
|
||||||
.then (user => {
|
.then (user => {
|
||||||
// yay connected without scanning QR
|
// yay connected without scanning QR
|
||||||
})
|
})
|
||||||
@@ -115,8 +115,8 @@ client.connectSlim('./auth_info.json') // will load JSON credentials from file
|
|||||||
|
|
||||||
If you're considering switching from a Chromium/Puppeteer based library, you can use WhatsApp Web's Browser credentials to restore sessions too:
|
If you're considering switching from a Chromium/Puppeteer based library, you can use WhatsApp Web's Browser credentials to restore sessions too:
|
||||||
``` ts
|
``` ts
|
||||||
client.loadAuthInfoFromBrowser ('./auth_info_browser.json')
|
conn.loadAuthInfoFromBrowser ('./auth_info_browser.json')
|
||||||
client.connectSlim(null, 20*1000) // use loaded credentials & timeout in 20s
|
conn.connectSlim(null, 20*1000) // use loaded credentials & timeout in 20s
|
||||||
.then (user => {
|
.then (user => {
|
||||||
// yay! connected using browser keys & without scanning QR
|
// yay! connected using browser keys & without scanning QR
|
||||||
})
|
})
|
||||||
@@ -127,25 +127,25 @@ See the browser credentials type [here](/src/WAConnection/Constants.ts).
|
|||||||
|
|
||||||
If you want to do some custom processing with the QR code used to authenticate, you can override the following method:
|
If you want to do some custom processing with the QR code used to authenticate, you can override the following method:
|
||||||
``` ts
|
``` ts
|
||||||
client.onReadyForPhoneAuthentication = ([ref, publicKey, clientID]) => {
|
conn.onReadyForPhoneAuthentication = ([ref, publicKey, clientID]) => {
|
||||||
const str = ref + ',' + publicKey + ',' + clientID // the QR string
|
const str = ref + ',' + publicKey + ',' + clientID // the QR string
|
||||||
// Now, use 'str' to display in QR UI or send somewhere
|
// Now, use 'str' to display in QR UI or send somewhere
|
||||||
}
|
}
|
||||||
const user = await client.connect ()
|
const user = await conn.connect ()
|
||||||
```
|
```
|
||||||
|
|
||||||
If you need to regenerate the QR, you can also do so using:
|
If you need to regenerate the QR, you can also do so using:
|
||||||
``` ts
|
``` ts
|
||||||
let generateQR: async () => void // call generateQR on some timeout or error
|
let generateQR: async () => void // call generateQR on some timeout or error
|
||||||
client.onReadyForPhoneAuthentication = ([ref, publicKey, clientID]) => {
|
conn.onReadyForPhoneAuthentication = ([ref, publicKey, clientID]) => {
|
||||||
generateQR = async () => {
|
generateQR = async () => {
|
||||||
ref = await client.generateNewQRCode () // returns a new ref code to use for QR generation
|
ref = await conn.generateNewQRCode () // returns a new ref code to use for QR generation
|
||||||
const str = ref + ',' + publicKey + ',' + clientID // the QR string
|
const str = ref + ',' + publicKey + ',' + clientID // the QR string
|
||||||
// re-print str as QR or update in UI or send somewhere
|
// re-print str as QR or update in UI or send somewhere
|
||||||
//QR.generate(str, { small: true })
|
//QR.generate(str, { small: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const user = await client.connect ()
|
const user = await conn.connect ()
|
||||||
```
|
```
|
||||||
## Handling Events
|
## Handling Events
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ Implement the following callbacks in your code:
|
|||||||
``` ts
|
``` ts
|
||||||
import { getNotificationType } from '@adiwajshing/baileys'
|
import { getNotificationType } from '@adiwajshing/baileys'
|
||||||
// set first param to `true` if you want to receive outgoing messages that may be sent from your phone
|
// set first param to `true` if you want to receive outgoing messages that may be sent from your phone
|
||||||
client.setOnUnreadMessage (false, (m: WAMessage) => {
|
conn.setOnUnreadMessage (false, (m: WAMessage) => {
|
||||||
// get what type of notification it is -- message, group add notification etc.
|
// get what type of notification it is -- message, group add notification etc.
|
||||||
const [notificationType, messageType] = getNotificationType(m)
|
const [notificationType, messageType] = getNotificationType(m)
|
||||||
|
|
||||||
@@ -165,11 +165,11 @@ Implement the following callbacks in your code:
|
|||||||
```
|
```
|
||||||
- Called when you recieve an update on someone's presence, they went offline or online
|
- Called when you recieve an update on someone's presence, they went offline or online
|
||||||
``` ts
|
``` ts
|
||||||
client.setOnPresenceUpdate ((json: PresenceUpdate) => console.log(json.id + " presence is " + json.type))
|
conn.setOnPresenceUpdate ((json: PresenceUpdate) => console.log(json.id + " presence is " + json.type))
|
||||||
```
|
```
|
||||||
- Called when your message gets delivered or read
|
- Called when your message gets delivered or read
|
||||||
``` ts
|
``` ts
|
||||||
client.setOnMessageStatusChange ((json: MessageStatusUpdate) => {
|
conn.setOnMessageStatusChange ((json: MessageStatusUpdate) => {
|
||||||
let sent = json.to
|
let sent = json.to
|
||||||
if (json.participant) // participant exists when the message is from a group
|
if (json.participant) // participant exists when the message is from a group
|
||||||
sent += " ("+json.participant+")" // mention as the one sent to
|
sent += " ("+json.participant+")" // mention as the one sent to
|
||||||
@@ -179,7 +179,7 @@ Implement the following callbacks in your code:
|
|||||||
```
|
```
|
||||||
- Called when the connection gets disconnected (either the server loses internet, the phone gets unpaired, or the connection is taken over from somewhere)
|
- Called when the connection gets disconnected (either the server loses internet, the phone gets unpaired, or the connection is taken over from somewhere)
|
||||||
``` ts
|
``` ts
|
||||||
client.setOnUnexpectedDisconnect (reason => console.log ("disconnected unexpectedly: " + reason) )
|
conn.setOnUnexpectedDisconnect (reason => console.log ("disconnected unexpectedly: " + reason) )
|
||||||
```
|
```
|
||||||
## Sending Messages
|
## Sending Messages
|
||||||
|
|
||||||
@@ -189,9 +189,9 @@ import { MessageType, MessageOptions, Mimetype } from '@adiwajshing/baileys'
|
|||||||
|
|
||||||
const id = 'abcd@s.whatsapp.net' // the WhatsApp ID
|
const id = 'abcd@s.whatsapp.net' // the WhatsApp ID
|
||||||
// send a simple text!
|
// send a simple text!
|
||||||
client.sendMessage (id, 'oh hello there', MessageType.text)
|
conn.sendMessage (id, 'oh hello there', MessageType.text)
|
||||||
// send a location!
|
// send a location!
|
||||||
client.sendMessage(id, {degreeslatitude: 24.121231, degreesLongitude: 55.1121221}, MessageType.location)
|
conn.sendMessage(id, {degreeslatitude: 24.121231, degreesLongitude: 55.1121221}, MessageType.location)
|
||||||
// send a contact!
|
// send a contact!
|
||||||
const vcard = 'BEGIN:VCARD\n' // metadata of the contact card
|
const vcard = 'BEGIN:VCARD\n' // metadata of the contact card
|
||||||
+ 'VERSION:3.0\n'
|
+ 'VERSION:3.0\n'
|
||||||
@@ -199,11 +199,11 @@ const vcard = 'BEGIN:VCARD\n' // metadata of the contact card
|
|||||||
+ 'ORG:Ashoka Uni;\n' // the organization of the contact
|
+ 'ORG:Ashoka Uni;\n' // the organization of the contact
|
||||||
+ 'TEL;type=CELL;type=VOICE;waid=911234567890:+91 12345 67890\n' // WhatsApp ID + phone number
|
+ 'TEL;type=CELL;type=VOICE;waid=911234567890:+91 12345 67890\n' // WhatsApp ID + phone number
|
||||||
+ 'END:VCARD'
|
+ 'END:VCARD'
|
||||||
client.sendMessage(id, {displayname: "Jeff", vcard: vcard}, MessageType.contact)
|
conn.sendMessage(id, {displayname: "Jeff", vcard: vcard}, MessageType.contact)
|
||||||
// send a gif
|
// send a gif
|
||||||
const buffer = fs.readFileSync("Media/ma_gif.mp4") // load some gif
|
const buffer = fs.readFileSync("Media/ma_gif.mp4") // load some gif
|
||||||
const options: MessageOptions = {mimetype: Mimetype.gif, caption: "hello!"} // some metadata & caption
|
const options: MessageOptions = {mimetype: Mimetype.gif, caption: "hello!"} // some metadata & caption
|
||||||
client.sendMessage(id, buffer, MessageType.video, options)
|
conn.sendMessage(id, buffer, MessageType.video, options)
|
||||||
```
|
```
|
||||||
To note:
|
To note:
|
||||||
- `id` is the WhatsApp ID of the person or group you're sending the message to.
|
- `id` is the WhatsApp ID of the person or group you're sending the message to.
|
||||||
@@ -233,9 +233,9 @@ To note:
|
|||||||
## Forwarding Messages
|
## Forwarding Messages
|
||||||
|
|
||||||
``` ts
|
``` ts
|
||||||
const messages = await client.loadConversation ('1234@s.whatsapp.net', 1)
|
const messages = await conn.loadConversation ('1234@s.whatsapp.net', 1)
|
||||||
const message = messages[0] // get the last message from this conversation
|
const message = messages[0] // get the last message from this conversation
|
||||||
await client.forwardMessage ('455@s.whatsapp.net', message) // WA forward the message!
|
await conn.forwardMessage ('455@s.whatsapp.net', message) // WA forward the message!
|
||||||
```
|
```
|
||||||
|
|
||||||
## Reading Messages
|
## Reading Messages
|
||||||
@@ -243,10 +243,10 @@ await client.forwardMessage ('455@s.whatsapp.net', message) // WA forward the me
|
|||||||
const id = '1234-123@g.us'
|
const id = '1234-123@g.us'
|
||||||
const messageID = 'AHASHH123123AHGA' // id of the message you want to read
|
const messageID = 'AHASHH123123AHGA' // id of the message you want to read
|
||||||
|
|
||||||
await client.sendReadReceipt(id, messageID) // mark as read
|
await conn.sendReadReceipt(id, messageID) // mark as read
|
||||||
await client.sendReadReceipt (id) // mark all messages in chat as read
|
await conn.sendReadReceipt (id) // mark all messages in chat as read
|
||||||
|
|
||||||
await client.sendReadReceipt(id, null, 'unread') // mark the chat as unread
|
await conn.sendReadReceipt(id, null, 'unread') // mark the chat as unread
|
||||||
```
|
```
|
||||||
|
|
||||||
- `id` is in the same format as mentioned earlier.
|
- `id` is in the same format as mentioned earlier.
|
||||||
@@ -258,7 +258,7 @@ await client.sendReadReceipt(id, null, 'unread') // mark the chat as unread
|
|||||||
``` ts
|
``` ts
|
||||||
import { Presence } from '@adiwajshing/baileys'
|
import { Presence } from '@adiwajshing/baileys'
|
||||||
|
|
||||||
client.updatePresence(id, Presence.available)
|
conn.updatePresence(id, Presence.available)
|
||||||
```
|
```
|
||||||
This lets the person/group with ``` id ``` know whether you're online, offline, typing etc. where ``` presence ``` can be one of the following:
|
This lets the person/group with ``` id ``` know whether you're online, offline, typing etc. where ``` presence ``` can be one of the following:
|
||||||
``` ts
|
``` ts
|
||||||
@@ -275,15 +275,15 @@ export enum Presence {
|
|||||||
If you want to save the media you received
|
If you want to save the media you received
|
||||||
``` ts
|
``` ts
|
||||||
import { MessageType, extensionForMediaMessage } from '@adiwajshing/baileys'
|
import { MessageType, extensionForMediaMessage } from '@adiwajshing/baileys'
|
||||||
client.setOnUnreadMessage (false, async m => {
|
conn.setOnUnreadMessage (false, async m => {
|
||||||
if (!m.message) return // if there is no text or media message
|
if (!m.message) return // if there is no text or media message
|
||||||
|
|
||||||
const messageType = Object.keys (m.message)[0]// get what type of message it is -- text, image, video
|
const messageType = Object.keys (m.message)[0]// get what type of message it is -- text, image, video
|
||||||
// if the message is not a text message
|
// if the message is not a text message
|
||||||
if (messageType !== MessageType.text && messageType !== MessageType.extendedText) {
|
if (messageType !== MessageType.text && messageType !== MessageType.extendedText) {
|
||||||
const buffer = await client.downloadMediaMessage(m) // to decrypt & use as a buffer
|
const buffer = await conn.downloadMediaMessage(m) // to decrypt & use as a buffer
|
||||||
|
|
||||||
const savedFilename = await client.downloadAndSaveMediaMessage (m) // to decrypt & save to file
|
const savedFilename = await conn.downloadAndSaveMediaMessage (m) // to decrypt & save to file
|
||||||
console.log(m.key.remoteJid + " sent media, saved at: " + savedFilename)
|
console.log(m.key.remoteJid + " sent media, saved at: " + savedFilename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,29 +293,29 @@ client.setOnUnreadMessage (false, async m => {
|
|||||||
|
|
||||||
``` ts
|
``` ts
|
||||||
const jid = '1234@s.whatsapp.net' // can also be a group
|
const jid = '1234@s.whatsapp.net' // can also be a group
|
||||||
const response = await client.sendMessage (jid, 'hello!', MessageType.text) // send a message
|
const response = await conn.sendMessage (jid, 'hello!', MessageType.text) // send a message
|
||||||
|
|
||||||
await client.deleteMessage (jid, {id: response.messageID, remoteJid: jid, fromMe: true}) // will delete the sent message for everyone!
|
await conn.deleteMessage (jid, {id: response.messageID, remoteJid: jid, fromMe: true}) // will delete the sent message for everyone!
|
||||||
await client.clearMessage (jid, {id: response.messageID, remoteJid: jid, fromMe: true}) // will delete the sent message for only you!
|
await conn.clearMessage (jid, {id: response.messageID, remoteJid: jid, fromMe: true}) // will delete the sent message for only you!
|
||||||
```
|
```
|
||||||
|
|
||||||
## Modifying Chats
|
## Modifying Chats
|
||||||
|
|
||||||
``` ts
|
``` ts
|
||||||
const jid = '1234@s.whatsapp.net' // can also be a group
|
const jid = '1234@s.whatsapp.net' // can also be a group
|
||||||
await client.modifyChat (jid, ChatModification.archive) // archive chat
|
await conn.modifyChat (jid, ChatModification.archive) // archive chat
|
||||||
await client.modifyChat (jid, ChatModification.unarchive) // unarchive chat
|
await conn.modifyChat (jid, ChatModification.unarchive) // unarchive chat
|
||||||
|
|
||||||
const response = await client.modifyChat (jid, ChatModification.pin) // pin the chat
|
const response = await conn.modifyChat (jid, ChatModification.pin) // pin the chat
|
||||||
await client.modifyChat (jid, ChatModification.unpin, {stamp: response.stamp})
|
await conn.modifyChat (jid, ChatModification.unpin, {stamp: response.stamp})
|
||||||
|
|
||||||
const mutedate = new Date (new Date().getTime() + 8*60*60*1000) // mute for 8 hours in the future
|
const mutedate = new Date (new Date().getTime() + 8*60*60*1000) // mute for 8 hours in the future
|
||||||
await client.modifyChat (jid, ChatModification.mute, {stamp: mutedate}) // mute
|
await conn.modifyChat (jid, ChatModification.mute, {stamp: mutedate}) // mute
|
||||||
setTimeout (() => {
|
setTimeout (() => {
|
||||||
client.modifyChat (jid, ChatModification.unmute, {stamp: mutedate})
|
conn.modifyChat (jid, ChatModification.unmute, {stamp: mutedate})
|
||||||
}, 5000) // unmute after 5 seconds
|
}, 5000) // unmute after 5 seconds
|
||||||
|
|
||||||
await client.deleteChat (jid) // will delete the chat (can be a group or broadcast list)
|
await conn.deleteChat (jid) // will delete the chat (can be a group or broadcast list)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** to unmute or unpin a chat, one must pass the timestamp of the pinning or muting. This is returned by the pin & mute functions. This is also available in the `WAChat` objects of the respective chats, as a `mute` or `pin` property.
|
**Note:** to unmute or unpin a chat, one must pass the timestamp of the pinning or muting. This is returned by the pin & mute functions. This is also available in the `WAChat` objects of the respective chats, as a `mute` or `pin` property.
|
||||||
@@ -325,48 +325,48 @@ await client.deleteChat (jid) // will delete the chat (can be a group or broadca
|
|||||||
- To check if a given ID is on WhatsApp
|
- To check if a given ID is on WhatsApp
|
||||||
``` ts
|
``` ts
|
||||||
const id = 'xyz@s.whatsapp.net'
|
const id = 'xyz@s.whatsapp.net'
|
||||||
const exists = await client.isOnWhatsApp (id)
|
const exists = await conn.isOnWhatsApp (id)
|
||||||
console.log (`${id} ${exists ? " exists " : " does not exist"} on WhatsApp`)
|
console.log (`${id} ${exists ? " exists " : " does not exist"} on WhatsApp`)
|
||||||
```
|
```
|
||||||
- To query chat history on a group or with someone
|
- To query chat history on a group or with someone
|
||||||
``` ts
|
``` ts
|
||||||
// query the last 25 messages (replace 25 with the number of messages you want to query)
|
// query the last 25 messages (replace 25 with the number of messages you want to query)
|
||||||
const messages = await client.loadConversation ("xyz-abc@g.us", 25)
|
const messages = await conn.loadConversation ("xyz-abc@g.us", 25)
|
||||||
console.log("got back " + messages.length + " messages")
|
console.log("got back " + messages.length + " messages")
|
||||||
```
|
```
|
||||||
You can also load the entire conversation history if you want
|
You can also load the entire conversation history if you want
|
||||||
``` ts
|
``` ts
|
||||||
await client.loadEntireConversation ("xyz@c.us", message => console.log("Loaded message with ID: " + message.key.id))
|
await conn.loadEntireConversation ("xyz@c.us", message => console.log("Loaded message with ID: " + message.key.id))
|
||||||
console.log("queried all messages") // promise resolves once all messages are retreived
|
console.log("queried all messages") // promise resolves once all messages are retreived
|
||||||
```
|
```
|
||||||
- To get the status of some person
|
- To get the status of some person
|
||||||
``` ts
|
``` ts
|
||||||
const status = await client.getStatus ("xyz@c.us") // leave empty to get your own status
|
const status = await conn.getStatus ("xyz@c.us") // leave empty to get your own status
|
||||||
console.log("status: " + status)
|
console.log("status: " + status)
|
||||||
```
|
```
|
||||||
- To get the display picture of some person/group
|
- To get the display picture of some person/group
|
||||||
``` ts
|
``` ts
|
||||||
const ppUrl = await client.getProfilePicture ("xyz@g.us") // leave empty to get your own
|
const ppUrl = await conn.getProfilePicture ("xyz@g.us") // leave empty to get your own
|
||||||
console.log("download profile picture from: " + ppUrl)
|
console.log("download profile picture from: " + ppUrl)
|
||||||
```
|
```
|
||||||
- To change your display picture or a group's
|
- To change your display picture or a group's
|
||||||
``` ts
|
``` ts
|
||||||
const jid = '111234567890-1594482450@g.us' // can be your own too
|
const jid = '111234567890-1594482450@g.us' // can be your own too
|
||||||
const img = fs.readFileSync ('new-profile-picture.jpeg') // can be PNG also
|
const img = fs.readFileSync ('new-profile-picture.jpeg') // can be PNG also
|
||||||
await client.updateProfilePicture (jid, newPP)
|
await conn.updateProfilePicture (jid, newPP)
|
||||||
```
|
```
|
||||||
- To get someone's presence (if they're typing, online)
|
- To get someone's presence (if they're typing, online)
|
||||||
``` ts
|
``` ts
|
||||||
// the presence update is fetched and called here
|
// the presence update is fetched and called here
|
||||||
client.setOnPresenceUpdate (json => console.log(json.id + " presence is " + json.type))
|
conn.setOnPresenceUpdate (json => console.log(json.id + " presence is " + json.type))
|
||||||
await client.requestPresenceUpdate ("xyz@c.us") // request the update
|
await conn.requestPresenceUpdate ("xyz@c.us") // request the update
|
||||||
```
|
```
|
||||||
- To search through messages
|
- To search through messages
|
||||||
``` ts
|
``` ts
|
||||||
const response = await client.searchMessages ('so cool', null, 25, 1) // search in all chats
|
const response = await conn.searchMessages ('so cool', null, 25, 1) // search in all chats
|
||||||
console.log (`got ${response.messages.length} messages in search`)
|
console.log (`got ${response.messages.length} messages in search`)
|
||||||
|
|
||||||
const response2 = await client.searchMessages ('so cool', '1234@c.us', 25, 1) // search in given chat
|
const response2 = await conn.searchMessages ('so cool', '1234@c.us', 25, 1) // search in given chat
|
||||||
```
|
```
|
||||||
Of course, replace ``` xyz ``` with an actual ID.
|
Of course, replace ``` xyz ``` with an actual ID.
|
||||||
Append ``` @s.whatsapp.net ``` for individuals & ``` @g.us ``` for groups.
|
Append ``` @s.whatsapp.net ``` for individuals & ``` @g.us ``` for groups.
|
||||||
@@ -375,47 +375,47 @@ Append ``` @s.whatsapp.net ``` for individuals & ``` @g.us ``` for groups.
|
|||||||
- To create a group
|
- To create a group
|
||||||
``` ts
|
``` ts
|
||||||
// title & participants
|
// title & participants
|
||||||
const group = await client.groupCreate ("My Fab Group", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"])
|
const group = await conn.groupCreate ("My Fab Group", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"])
|
||||||
console.log ("created group with id: " + group.gid)
|
console.log ("created group with id: " + group.gid)
|
||||||
client.sendTextMessage(group.gid, "hello everyone") // say hello to everyone on the group
|
conn.sendMessage(group.gid, "hello everyone", MessageType.extendedText) // say hello to everyone on the group
|
||||||
```
|
```
|
||||||
- To add people to a group
|
- To add people to a group
|
||||||
``` ts
|
``` ts
|
||||||
// id & people to add to the group (will throw error if it fails)
|
// id & people to add to the group (will throw error if it fails)
|
||||||
const response = await client.groupAdd ("abcd-xyz@g.us", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"])
|
const response = await conn.groupAdd ("abcd-xyz@g.us", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"])
|
||||||
```
|
```
|
||||||
- To make/demote admins on a group
|
- To make/demote admins on a group
|
||||||
``` ts
|
``` ts
|
||||||
// id & people to make admin (will throw error if it fails)
|
// id & people to make admin (will throw error if it fails)
|
||||||
await client.groupMakeAdmin ("abcd-xyz@g.us", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"])
|
await conn.groupMakeAdmin ("abcd-xyz@g.us", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"])
|
||||||
await client.groupDemoteAdmin ("abcd-xyz@g.us", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"]) // demote admins
|
await conn.groupDemoteAdmin ("abcd-xyz@g.us", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"]) // demote admins
|
||||||
```
|
```
|
||||||
- To change group settings
|
- To change group settings
|
||||||
``` ts
|
``` ts
|
||||||
import { GroupSettingChange } from '@adiwajshing/baileys'
|
import { GroupSettingChange } from '@adiwajshing/baileys'
|
||||||
// only allow admins to send messages
|
// only allow admins to send messages
|
||||||
await client.groupSettingChange ("abcd-xyz@g.us", GroupSettingChange.messageSend, true)
|
await conn.groupSettingChange ("abcd-xyz@g.us", GroupSettingChange.messageSend, true)
|
||||||
// allow everyone to modify the group's settings -- like display picture etc.
|
// allow everyone to modify the group's settings -- like display picture etc.
|
||||||
await client.groupSettingChange ("abcd-xyz@g.us", GroupSettingChange.settingChange, false)
|
await conn.groupSettingChange ("abcd-xyz@g.us", GroupSettingChange.settingChange, false)
|
||||||
// only allow admins to modify the group's settings
|
// only allow admins to modify the group's settings
|
||||||
await client.groupSettingChange ("abcd-xyz@g.us", GroupSettingChange.settingChange, true)
|
await conn.groupSettingChange ("abcd-xyz@g.us", GroupSettingChange.settingChange, true)
|
||||||
```
|
```
|
||||||
- To leave a group
|
- To leave a group
|
||||||
``` ts
|
``` ts
|
||||||
await client.groupLeave ("abcd-xyz@g.us") // (will throw error if it fails)
|
await conn.groupLeave ("abcd-xyz@g.us") // (will throw error if it fails)
|
||||||
```
|
```
|
||||||
- To get the invite code for a group
|
- To get the invite code for a group
|
||||||
``` ts
|
``` ts
|
||||||
const code = await client.groupInviteCode ("abcd-xyz@g.us")
|
const code = await conn.groupInviteCode ("abcd-xyz@g.us")
|
||||||
console.log("group code: " + code)
|
console.log("group code: " + code)
|
||||||
```
|
```
|
||||||
- To query the metadata of a group
|
- To query the metadata of a group
|
||||||
``` ts
|
``` ts
|
||||||
const metadata = await client.groupMetadata ("abcd-xyz@g.us")
|
const metadata = await conn.groupMetadata ("abcd-xyz@g.us")
|
||||||
console.log(json.id + ", title: " + json.subject + ", description: " + json.desc)
|
console.log(json.id + ", title: " + json.subject + ", description: " + json.desc)
|
||||||
|
|
||||||
// Or if you've left the group -- call this
|
// Or if you've left the group -- call this
|
||||||
const metadata2 = await client.groupMetadataMinimal ("abcd-xyz@g.us")
|
const metadata2 = await conn.groupMetadataMinimal ("abcd-xyz@g.us")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Broadcast Lists & Stories
|
## Broadcast Lists & Stories
|
||||||
@@ -425,7 +425,7 @@ Append ``` @s.whatsapp.net ``` for individuals & ``` @g.us ``` for groups.
|
|||||||
- Broadcast IDs are in the format `12345678@broadcast`
|
- Broadcast IDs are in the format `12345678@broadcast`
|
||||||
- To query a broadcast list's recipients & name:
|
- To query a broadcast list's recipients & name:
|
||||||
``` ts
|
``` ts
|
||||||
const bList = await client.getBroadcastListInfo ("1234@broadcast")
|
const bList = await conn.getBroadcastListInfo ("1234@broadcast")
|
||||||
console.log (`list name: ${bList.name}, recps: ${bList.recipients}`)
|
console.log (`list name: ${bList.name}, recps: ${bList.recipients}`)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -434,7 +434,7 @@ Baileys is written, keeping in mind, that you may require other custom functiona
|
|||||||
|
|
||||||
First, enable the logging of unhandled messages from WhatsApp by setting
|
First, enable the logging of unhandled messages from WhatsApp by setting
|
||||||
``` ts
|
``` ts
|
||||||
client.logLevel = MessageLogLevel.unhandled // set to MessageLogLevel.all to see all messages received
|
conn.logLevel = MessageLogLevel.unhandled // set to MessageLogLevel.all to see all messages received
|
||||||
```
|
```
|
||||||
This will enable you to see all sorts of messages WhatsApp sends in the console. Some examples:
|
This will enable you to see all sorts of messages WhatsApp sends in the console. Some examples:
|
||||||
|
|
||||||
@@ -450,7 +450,7 @@ This will enable you to see all sorts of messages WhatsApp sends in the console.
|
|||||||
|
|
||||||
Hence, you can register a callback for an event using the following:
|
Hence, you can register a callback for an event using the following:
|
||||||
``` ts
|
``` ts
|
||||||
client.registerCallback (["action", null, "battery"], json => {
|
conn.registerCallback (["action", null, "battery"], json => {
|
||||||
const batteryLevelStr = json[2][0][1].value
|
const batteryLevelStr = json[2][0][1].value
|
||||||
const batterylevel = parseInt (batteryLevelStr)
|
const batterylevel = parseInt (batteryLevelStr)
|
||||||
console.log ("battery level: " + batterylevel + "%")
|
console.log ("battery level: " + batterylevel + "%")
|
||||||
@@ -470,9 +470,9 @@ This will enable you to see all sorts of messages WhatsApp sends in the console.
|
|||||||
|
|
||||||
Following this, one can implement the following callback:
|
Following this, one can implement the following callback:
|
||||||
``` ts
|
``` ts
|
||||||
client.registerCallback (["Conn", "pushname"], json => {
|
conn.registerCallback (["Conn", "pushname"], json => {
|
||||||
const pushname = json[1].pushname
|
const pushname = json[1].pushname
|
||||||
client.userMetaData.name = pushname // update on client too
|
conn.userMetaData.name = pushname // update on client too
|
||||||
console.log ("Name updated: " + pushname)
|
console.log ("Name updated: " + pushname)
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@adiwajshing/baileys",
|
"name": "@adiwajshing/baileys",
|
||||||
"version": "2.3.1",
|
"version": "3.0.0",
|
||||||
"description": "WhatsApp Web API",
|
"description": "WhatsApp Web API",
|
||||||
"homepage": "https://github.com/adiwajshing/Baileys",
|
"homepage": "https://github.com/adiwajshing/Baileys",
|
||||||
"main": "lib/WAClient/WAClient.js",
|
"main": "lib/WAConnection/WAConnection.js",
|
||||||
"types": "lib/WAClient/WAClient.d.ts",
|
"types": "lib/WAConnection/WAConnection.d.ts",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"whatsapp",
|
"whatsapp",
|
||||||
"js-whatsapp",
|
"js-whatsapp",
|
||||||
@@ -18,12 +18,12 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "npm run build",
|
"prepare": "npm run build",
|
||||||
"test": "mocha --timeout 60000 -r ts-node/register src/*/Tests.ts",
|
"test": "mocha --timeout 60000 -r ts-node/register src/Tests/Tests.*.ts",
|
||||||
"lint": "eslint '*/*.ts' --quiet --fix",
|
"lint": "eslint '*/*.ts' --quiet --fix",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"build:docs": "typedoc",
|
"build:docs": "typedoc",
|
||||||
"example": "npx ts-node Example/example.ts",
|
"example": "npx ts-node Example/example.ts",
|
||||||
"browser-decode": "npx ts-node src/WAConnection/BrowserMessageDecoding.ts"
|
"browser-decode": "npx ts-node src/BrowserMessageDecoding.ts"
|
||||||
},
|
},
|
||||||
"author": "Adhiraj Singh",
|
"author": "Adhiraj Singh",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { decryptWA } from './Utils'
|
import { decryptWA } from './WAConnection/WAConnection'
|
||||||
import Decoder from '../Binary/Decoder'
|
import Decoder from './Binary/Decoder'
|
||||||
|
|
||||||
interface BrowserMessagesInfo {
|
interface BrowserMessagesInfo {
|
||||||
encKey: string,
|
encKey: string,
|
||||||
28
src/Tests/Common.ts
Normal file
28
src/Tests/Common.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { WAConnection, MessageLogLevel, MessageOptions, MessageType } from '../WAConnection/WAConnection'
|
||||||
|
import * as assert from 'assert'
|
||||||
|
import fs from 'fs/promises'
|
||||||
|
|
||||||
|
require ('dotenv').config () // dotenv to load test jid
|
||||||
|
export const testJid = process.env.TEST_JID || '1234@s.whatsapp.net' // set TEST_JID=xyz@s.whatsapp.net in a .env file in the root directory
|
||||||
|
|
||||||
|
export async function sendAndRetreiveMessage(conn: WAConnection, content, type: MessageType, options: MessageOptions = {}) {
|
||||||
|
const response = await conn.sendMessage(testJid, content, type, options)
|
||||||
|
const messages = await conn.loadConversation(testJid, 10, null, true)
|
||||||
|
const message = messages.find (m => m.key.id === response.key.id)
|
||||||
|
assert.ok(message)
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
export function WAConnectionTest(name: string, func: (conn: WAConnection) => void) {
|
||||||
|
describe(name, () => {
|
||||||
|
const conn = new WAConnection()
|
||||||
|
conn.logLevel = MessageLogLevel.info
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
const file = './auth_info.json'
|
||||||
|
await conn.connectSlim(file)
|
||||||
|
await fs.writeFile(file, JSON.stringify(conn.base64EncodedAuthInfo(), null, '\t'))
|
||||||
|
})
|
||||||
|
after(() => conn.close())
|
||||||
|
func(conn)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { strict as assert } from 'assert'
|
import { strict as assert } from 'assert'
|
||||||
import Encoder from './Encoder'
|
import Encoder from '../Binary/Encoder'
|
||||||
import Decoder from './Decoder'
|
import Decoder from '../Binary/Decoder'
|
||||||
|
|
||||||
describe('Binary Coding Tests', () => {
|
describe('Binary Coding Tests', () => {
|
||||||
const testVectors: [string, Object][] = [
|
const testVectors: [string, Object][] = [
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import * as assert from 'assert'
|
import * as assert from 'assert'
|
||||||
import * as QR from 'qrcode-terminal'
|
import * as QR from 'qrcode-terminal'
|
||||||
import WAConnection from './WAConnection'
|
import {WAConnection} from '../WAConnection/WAConnection'
|
||||||
import { AuthenticationCredentialsBase64 } from './Constants'
|
import { AuthenticationCredentialsBase64 } from '../WAConnection/Constants'
|
||||||
import { createTimeout } from './Utils'
|
import { createTimeout } from '../WAConnection/Utils'
|
||||||
|
|
||||||
describe('QR generation', () => {
|
describe('QR Generation', () => {
|
||||||
it('should generate QR', async () => {
|
it('should generate QR', async () => {
|
||||||
const conn = new WAConnection()
|
const conn = new WAConnection()
|
||||||
let calledQR = false
|
let calledQR = false
|
||||||
59
src/Tests/Tests.Groups.ts
Normal file
59
src/Tests/Tests.Groups.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { MessageType, GroupSettingChange, createTimeout, ChatModification } from '../WAConnection/WAConnection'
|
||||||
|
import * as assert from 'assert'
|
||||||
|
import { WAConnectionTest, testJid } from './Common'
|
||||||
|
|
||||||
|
WAConnectionTest('Groups', (conn) => {
|
||||||
|
let gid: string
|
||||||
|
it('should create a group', async () => {
|
||||||
|
const response = await conn.groupCreate('Cool Test Group', [testJid])
|
||||||
|
gid = response.gid
|
||||||
|
console.log('created group: ' + JSON.stringify(response))
|
||||||
|
})
|
||||||
|
it('should retreive group invite code', async () => {
|
||||||
|
const code = await conn.groupInviteCode(gid)
|
||||||
|
assert.ok(code)
|
||||||
|
assert.strictEqual(typeof code, 'string')
|
||||||
|
})
|
||||||
|
it('should retreive group metadata', async () => {
|
||||||
|
const metadata = await conn.groupMetadata(gid)
|
||||||
|
assert.strictEqual(metadata.id, gid)
|
||||||
|
assert.strictEqual(metadata.participants.filter((obj) => obj.id.split('@')[0] === testJid.split('@')[0]).length, 1)
|
||||||
|
})
|
||||||
|
it('should update the group description', async () => {
|
||||||
|
const newDesc = 'Wow this was set from Baileys'
|
||||||
|
|
||||||
|
await conn.groupUpdateDescription (gid, newDesc)
|
||||||
|
await createTimeout (1000)
|
||||||
|
|
||||||
|
const metadata = await conn.groupMetadata(gid)
|
||||||
|
assert.strictEqual(metadata.desc, newDesc)
|
||||||
|
})
|
||||||
|
it('should send a message on the group', async () => {
|
||||||
|
await conn.sendMessage(gid, 'hello', MessageType.text)
|
||||||
|
})
|
||||||
|
it('should update the subject', async () => {
|
||||||
|
const subject = 'V Cool Title'
|
||||||
|
await conn.groupUpdateSubject(gid, subject)
|
||||||
|
|
||||||
|
const metadata = await conn.groupMetadata(gid)
|
||||||
|
assert.strictEqual(metadata.subject, subject)
|
||||||
|
})
|
||||||
|
it('should update the group settings', async () => {
|
||||||
|
await conn.groupSettingChange (gid, GroupSettingChange.messageSend, true)
|
||||||
|
await createTimeout (5000)
|
||||||
|
await conn.groupSettingChange (gid, GroupSettingChange.settingsChange, true)
|
||||||
|
})
|
||||||
|
it('should remove someone from a group', async () => {
|
||||||
|
await conn.groupRemove(gid, [testJid])
|
||||||
|
})
|
||||||
|
it('should leave the group', async () => {
|
||||||
|
await conn.groupLeave(gid)
|
||||||
|
await conn.groupMetadataMinimal (gid)
|
||||||
|
})
|
||||||
|
it('should archive the group', async () => {
|
||||||
|
await conn.modifyChat(gid, ChatModification.archive)
|
||||||
|
})
|
||||||
|
it('should delete the group', async () => {
|
||||||
|
await conn.deleteChat(gid)
|
||||||
|
})
|
||||||
|
})
|
||||||
68
src/Tests/Tests.Messages.ts
Normal file
68
src/Tests/Tests.Messages.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { MessageType, Mimetype, createTimeout } from '../WAConnection/WAConnection'
|
||||||
|
import fs from 'fs/promises'
|
||||||
|
import * as assert from 'assert'
|
||||||
|
import { WAConnectionTest, testJid, sendAndRetreiveMessage } from './Common'
|
||||||
|
|
||||||
|
WAConnectionTest('Messages', (conn) => {
|
||||||
|
it('should send a text message', async () => {
|
||||||
|
const message = await sendAndRetreiveMessage(conn, 'hello fren', MessageType.text)
|
||||||
|
assert.strictEqual(message.message.conversation, 'hello fren')
|
||||||
|
})
|
||||||
|
it('should forward a message', async () => {
|
||||||
|
let messages = await conn.loadConversation (testJid, 1)
|
||||||
|
await conn.forwardMessage (testJid, messages[0], true)
|
||||||
|
|
||||||
|
messages = await conn.loadConversation (testJid, 1)
|
||||||
|
const message = messages[0]
|
||||||
|
const content = message.message[ Object.keys(message.message)[0] ]
|
||||||
|
assert.equal (content?.contextInfo?.isForwarded, true)
|
||||||
|
})
|
||||||
|
it('should send a link preview', async () => {
|
||||||
|
const content = await conn.generateLinkPreview ('hello this is from https://www.github.com/adiwajshing/Baileys')
|
||||||
|
const message = await sendAndRetreiveMessage(conn, content, MessageType.text)
|
||||||
|
const received = message.message.extendedTextMessage
|
||||||
|
|
||||||
|
assert.strictEqual(received.text, content.text)
|
||||||
|
assert.ok (received.canonicalUrl)
|
||||||
|
assert.ok (received.title)
|
||||||
|
assert.ok (received.jpegThumbnail)
|
||||||
|
})
|
||||||
|
it('should quote a message', async () => {
|
||||||
|
const messages = await conn.loadConversation(testJid, 2)
|
||||||
|
const message = await sendAndRetreiveMessage(conn, 'hello fren 2', MessageType.extendedText, {
|
||||||
|
quoted: messages[0],
|
||||||
|
})
|
||||||
|
assert.strictEqual(message.message.extendedTextMessage.contextInfo.stanzaId, messages[0].key.id)
|
||||||
|
})
|
||||||
|
it('should send a gif', async () => {
|
||||||
|
const content = await fs.readFile('./Media/ma_gif.mp4')
|
||||||
|
const message = await sendAndRetreiveMessage(conn, content, MessageType.video, { mimetype: Mimetype.gif })
|
||||||
|
|
||||||
|
await conn.downloadAndSaveMediaMessage(message,'./Media/received_vid')
|
||||||
|
})
|
||||||
|
it('should send an image', async () => {
|
||||||
|
const content = await fs.readFile('./Media/meme.jpeg')
|
||||||
|
const message = await sendAndRetreiveMessage(conn, content, MessageType.image)
|
||||||
|
|
||||||
|
await conn.downloadMediaMessage(message)
|
||||||
|
//const message2 = await sendAndRetreiveMessage (conn, 'this is a quote', MessageType.extendedText)
|
||||||
|
})
|
||||||
|
it('should send an image & quote', async () => {
|
||||||
|
const messages = await conn.loadConversation(testJid, 1)
|
||||||
|
const content = await fs.readFile('./Media/meme.jpeg')
|
||||||
|
const message = await sendAndRetreiveMessage(conn, content, MessageType.image, { quoted: messages[0] })
|
||||||
|
|
||||||
|
await conn.downloadMediaMessage(message) // check for successful decoding
|
||||||
|
assert.strictEqual(message.message.imageMessage.contextInfo.stanzaId, messages[0].key.id)
|
||||||
|
})
|
||||||
|
it('should send a text message & delete it', async () => {
|
||||||
|
const message = await sendAndRetreiveMessage(conn, 'hello fren', MessageType.text)
|
||||||
|
await createTimeout (2000)
|
||||||
|
await conn.deleteMessage (testJid, message.key)
|
||||||
|
})
|
||||||
|
it('should clear the most recent message', async () => {
|
||||||
|
const messages = await conn.loadConversation (testJid, 1)
|
||||||
|
await createTimeout (2000)
|
||||||
|
await conn.clearMessage (messages[0].key)
|
||||||
|
})
|
||||||
|
})
|
||||||
113
src/Tests/Tests.Misc.ts
Normal file
113
src/Tests/Tests.Misc.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { MessageType, Presence, ChatModification, promiseTimeout, createTimeout } from '../WAConnection/WAConnection'
|
||||||
|
import fs from 'fs/promises'
|
||||||
|
import * as assert from 'assert'
|
||||||
|
import fetch from 'node-fetch'
|
||||||
|
import { WAConnectionTest, testJid } from './Common'
|
||||||
|
|
||||||
|
WAConnectionTest('Presence', (conn) => {
|
||||||
|
it('should update presence', async () => {
|
||||||
|
const presences = Object.values(Presence)
|
||||||
|
for (const i in presences) {
|
||||||
|
const response = await conn.updatePresence(testJid, presences[i])
|
||||||
|
assert.strictEqual(response.status, 200)
|
||||||
|
|
||||||
|
await createTimeout(1500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
WAConnectionTest('Misc', (conn) => {
|
||||||
|
it('should tell if someone has an account on WhatsApp', async () => {
|
||||||
|
const response = await conn.isOnWhatsApp(testJid)
|
||||||
|
assert.strictEqual(response, true)
|
||||||
|
|
||||||
|
const responseFail = await conn.isOnWhatsApp('abcd@s.whatsapp.net')
|
||||||
|
assert.strictEqual(responseFail, false)
|
||||||
|
})
|
||||||
|
it('should return the status', async () => {
|
||||||
|
const response = await conn.getStatus(testJid)
|
||||||
|
assert.strictEqual(typeof response.status, 'string')
|
||||||
|
})
|
||||||
|
it('should update status', async () => {
|
||||||
|
const newStatus = 'v cool status'
|
||||||
|
|
||||||
|
const response = await conn.getStatus()
|
||||||
|
assert.strictEqual(typeof response.status, 'string')
|
||||||
|
|
||||||
|
await createTimeout (1000)
|
||||||
|
|
||||||
|
await conn.setStatus (newStatus)
|
||||||
|
const response2 = await conn.getStatus()
|
||||||
|
assert.equal (response2.status, newStatus)
|
||||||
|
|
||||||
|
await createTimeout (1000)
|
||||||
|
|
||||||
|
await conn.setStatus (response.status) // update back
|
||||||
|
})
|
||||||
|
it('should return the stories', async () => {
|
||||||
|
await conn.getStories()
|
||||||
|
})
|
||||||
|
it('should change the profile picture', async () => {
|
||||||
|
await createTimeout (5000)
|
||||||
|
|
||||||
|
const ppUrl = await conn.getProfilePicture(conn.userMetaData.id)
|
||||||
|
const fetched = await fetch(ppUrl, { headers: { Origin: 'https://web.whatsapp.com' } })
|
||||||
|
const buff = await fetched.buffer ()
|
||||||
|
|
||||||
|
const newPP = await fs.readFile ('./Media/cat.jpeg')
|
||||||
|
const response = await conn.updateProfilePicture (conn.userMetaData.id, newPP)
|
||||||
|
|
||||||
|
await createTimeout (10000)
|
||||||
|
|
||||||
|
await conn.updateProfilePicture (conn.userMetaData.id, buff) // revert back
|
||||||
|
})
|
||||||
|
it('should return the profile picture', async () => {
|
||||||
|
const response = await conn.getProfilePicture(testJid)
|
||||||
|
assert.ok(response)
|
||||||
|
assert.rejects(conn.getProfilePicture('abcd@s.whatsapp.net'))
|
||||||
|
})
|
||||||
|
it('should send typing indicator', async () => {
|
||||||
|
const response = await conn.updatePresence(testJid, Presence.composing)
|
||||||
|
assert.ok(response)
|
||||||
|
})
|
||||||
|
it('should mark a chat unread', async () => {
|
||||||
|
await conn.sendReadReceipt(testJid, null, 'unread')
|
||||||
|
})
|
||||||
|
it('should archive & unarchive', async () => {
|
||||||
|
await conn.modifyChat (testJid, ChatModification.archive)
|
||||||
|
await createTimeout (2000)
|
||||||
|
await conn.modifyChat (testJid, ChatModification.unarchive)
|
||||||
|
})
|
||||||
|
it('should pin & unpin a chat', async () => {
|
||||||
|
const response = await conn.modifyChat (testJid, ChatModification.pin)
|
||||||
|
await createTimeout (2000)
|
||||||
|
await conn.modifyChat (testJid, ChatModification.unpin, {stamp: response.stamp})
|
||||||
|
})
|
||||||
|
it('should mute & unmute a chat', async () => {
|
||||||
|
const mutedate = new Date (new Date().getTime() + 8*60*60*1000) // 8 hours in the future
|
||||||
|
await conn.modifyChat (testJid, ChatModification.mute, {stamp: mutedate})
|
||||||
|
await createTimeout (2000)
|
||||||
|
await conn.modifyChat (testJid, ChatModification.unmute, {stamp: mutedate})
|
||||||
|
})
|
||||||
|
it('should return search results', async () => {
|
||||||
|
const jids = [null, testJid]
|
||||||
|
for (let i in jids) {
|
||||||
|
const response = await conn.searchMessages('Hello', jids[i], 25, 1)
|
||||||
|
assert.ok (response.messages)
|
||||||
|
assert.ok (response.messages.length >= 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
WAConnectionTest('Events', (conn) => {
|
||||||
|
it('should deliver a message', async () => {
|
||||||
|
const waitForUpdate = () =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
conn.setOnMessageStatusChange((update) => {
|
||||||
|
if (update.ids.includes(response.key.id)) {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const response = await conn.sendMessage(testJid, 'My Name Jeff', MessageType.text)
|
||||||
|
await promiseTimeout(15000, waitForUpdate())
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
import { WAMessage } from '../WAConnection/Constants'
|
|
||||||
import { proto } from '../../WAMessage/WAMessage'
|
|
||||||
/**
|
|
||||||
* set of statuses visible to other people; see updatePresence() in WhatsAppWeb.Send
|
|
||||||
*/
|
|
||||||
export enum Presence {
|
|
||||||
available = 'available', // "online"
|
|
||||||
unavailable = 'unavailable', // "offline"
|
|
||||||
composing = 'composing', // "typing..."
|
|
||||||
recording = 'recording', // "recording..."
|
|
||||||
paused = 'paused', // I have no clue
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Status of a message sent or received
|
|
||||||
*/
|
|
||||||
export enum MessageStatus {
|
|
||||||
sent = 'sent',
|
|
||||||
received = 'received',
|
|
||||||
read = 'read',
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* set of message types that are supported by the library
|
|
||||||
*/
|
|
||||||
export enum MessageType {
|
|
||||||
text = 'conversation',
|
|
||||||
extendedText = 'extendedTextMessage',
|
|
||||||
contact = 'contactMessage',
|
|
||||||
location = 'locationMessage',
|
|
||||||
liveLocation = 'liveLocationMessage',
|
|
||||||
|
|
||||||
image = 'imageMessage',
|
|
||||||
video = 'videoMessage',
|
|
||||||
sticker = 'stickerMessage',
|
|
||||||
document = 'documentMessage',
|
|
||||||
audio = 'audioMessage',
|
|
||||||
product = 'productMessage'
|
|
||||||
}
|
|
||||||
export enum ChatModification {
|
|
||||||
archive='archive',
|
|
||||||
unarchive='unarchive',
|
|
||||||
pin='pin',
|
|
||||||
unpin='unpin',
|
|
||||||
mute='mute',
|
|
||||||
unmute='unmute'
|
|
||||||
}
|
|
||||||
export const HKDFInfoKeys = {
|
|
||||||
[MessageType.image]: 'WhatsApp Image Keys',
|
|
||||||
[MessageType.audio]: 'WhatsApp Audio Keys',
|
|
||||||
[MessageType.video]: 'WhatsApp Video Keys',
|
|
||||||
[MessageType.document]: 'WhatsApp Document Keys',
|
|
||||||
[MessageType.sticker]: 'WhatsApp Image Keys'
|
|
||||||
}
|
|
||||||
export enum Mimetype {
|
|
||||||
jpeg = 'image/jpeg',
|
|
||||||
png = 'image/png',
|
|
||||||
mp4 = 'video/mp4',
|
|
||||||
gif = 'video/gif',
|
|
||||||
pdf = 'application/pdf',
|
|
||||||
ogg = 'audio/ogg; codecs=opus',
|
|
||||||
/** for stickers */
|
|
||||||
webp = 'image/webp',
|
|
||||||
}
|
|
||||||
export interface MessageOptions {
|
|
||||||
quoted?: WAMessage
|
|
||||||
contextInfo?: WAContextInfo
|
|
||||||
timestamp?: Date
|
|
||||||
caption?: string
|
|
||||||
thumbnail?: string
|
|
||||||
mimetype?: Mimetype | string
|
|
||||||
validateID?: boolean,
|
|
||||||
filename?: string
|
|
||||||
}
|
|
||||||
export interface WABroadcastListInfo {
|
|
||||||
status: number
|
|
||||||
name: string
|
|
||||||
recipients?: {id: string}[]
|
|
||||||
}
|
|
||||||
export interface WAUrlInfo {
|
|
||||||
'canonical-url': string
|
|
||||||
'matched-text': string
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
jpegThumbnail?: Buffer
|
|
||||||
}
|
|
||||||
export interface WAProfilePictureChange {
|
|
||||||
status: number
|
|
||||||
tag: string
|
|
||||||
eurl: string
|
|
||||||
}
|
|
||||||
export interface MessageInfo {
|
|
||||||
reads: {jid: string, t: string}[]
|
|
||||||
deliveries: {jid: string, t: string}[]
|
|
||||||
}
|
|
||||||
export interface MessageStatusUpdate {
|
|
||||||
from: string
|
|
||||||
to: string
|
|
||||||
/** Which participant caused the update (only for groups) */
|
|
||||||
participant?: string
|
|
||||||
timestamp: Date
|
|
||||||
/** Message IDs read/delivered */
|
|
||||||
ids: string[]
|
|
||||||
/** Status of the Message IDs */
|
|
||||||
type: WA_MESSAGE_STATUS_TYPE
|
|
||||||
}
|
|
||||||
export enum GroupSettingChange {
|
|
||||||
messageSend = 'announcement',
|
|
||||||
settingsChange = 'locked',
|
|
||||||
}
|
|
||||||
export interface PresenceUpdate {
|
|
||||||
id: string
|
|
||||||
participant?: string
|
|
||||||
t?: string
|
|
||||||
type?: Presence
|
|
||||||
deny?: boolean
|
|
||||||
}
|
|
||||||
// path to upload the media
|
|
||||||
export const MediaPathMap = {
|
|
||||||
imageMessage: '/mms/image',
|
|
||||||
videoMessage: '/mms/video',
|
|
||||||
documentMessage: '/mms/document',
|
|
||||||
audioMessage: '/mms/audio',
|
|
||||||
stickerMessage: '/mms/image',
|
|
||||||
}
|
|
||||||
// gives WhatsApp info to process the media
|
|
||||||
export const MimetypeMap = {
|
|
||||||
imageMessage: Mimetype.jpeg,
|
|
||||||
videoMessage: Mimetype.mp4,
|
|
||||||
documentMessage: Mimetype.pdf,
|
|
||||||
audioMessage: Mimetype.ogg,
|
|
||||||
stickerMessage: Mimetype.webp,
|
|
||||||
}
|
|
||||||
export interface WASendMessageResponse {
|
|
||||||
status: number
|
|
||||||
messageID: string
|
|
||||||
message: WAMessage
|
|
||||||
}
|
|
||||||
export interface WALocationMessage {
|
|
||||||
degreesLatitude: number
|
|
||||||
degreesLongitude: number
|
|
||||||
address?: string
|
|
||||||
}
|
|
||||||
export import WA_MESSAGE_STUB_TYPE = proto.WebMessageInfo.WEB_MESSAGE_INFO_STUBTYPE
|
|
||||||
export import WA_MESSAGE_STATUS_TYPE = proto.WebMessageInfo.WEB_MESSAGE_INFO_STATUS
|
|
||||||
|
|
||||||
/** Reverse stub type dictionary */
|
|
||||||
export const WAMessageType = function () {
|
|
||||||
const types = WA_MESSAGE_STUB_TYPE
|
|
||||||
const dict: Record<number, string> = {}
|
|
||||||
Object.keys(types).forEach(element => dict[ types[element] ] = element)
|
|
||||||
return dict
|
|
||||||
}()
|
|
||||||
export type WAContactMessage = proto.ContactMessage
|
|
||||||
export type WAMessageKey = proto.IMessageKey
|
|
||||||
export type WATextMessage = proto.ExtendedTextMessage
|
|
||||||
export type WAContextInfo = proto.IContextInfo
|
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
import { WAClient } from './WAClient'
|
|
||||||
import { MessageType, MessageOptions, Mimetype, Presence, ChatModification, GroupSettingChange } from './Constants'
|
|
||||||
import * as fs from 'fs'
|
|
||||||
import * as assert from 'assert'
|
|
||||||
import fetch from 'node-fetch'
|
|
||||||
|
|
||||||
import { decodeMediaMessage, validateJIDForSending } from './Utils'
|
|
||||||
import { promiseTimeout, createTimeout, Browsers, generateMessageTag } from '../WAConnection/Utils'
|
|
||||||
import { MessageLogLevel } from '../WAConnection/Constants'
|
|
||||||
|
|
||||||
require ('dotenv').config () // dotenv to load test jid
|
|
||||||
const testJid = process.env.TEST_JID || '1234@s.whatsapp.net' // set TEST_JID=xyz@s.whatsapp.net in a .env file in the root directory
|
|
||||||
|
|
||||||
async function sendAndRetreiveMessage(client: WAClient, content, type: MessageType, options: MessageOptions = {}) {
|
|
||||||
const response = await client.sendMessage(testJid, content, type, options)
|
|
||||||
const messages = await client.loadConversation(testJid, 1, null, true)
|
|
||||||
assert.strictEqual(messages[0].key.id, response.messageID)
|
|
||||||
return messages[0]
|
|
||||||
}
|
|
||||||
function WAClientTest(name: string, func: (client: WAClient) => void) {
|
|
||||||
describe(name, () => {
|
|
||||||
const client = new WAClient()
|
|
||||||
client.logLevel = MessageLogLevel.info
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
const file = './auth_info.json'
|
|
||||||
await client.connectSlim(file)
|
|
||||||
fs.writeFileSync(file, JSON.stringify(client.base64EncodedAuthInfo(), null, '\t'))
|
|
||||||
})
|
|
||||||
after(() => client.close())
|
|
||||||
func(client)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
WAClientTest('Messages', (client) => {
|
|
||||||
it('should send a text message', async () => {
|
|
||||||
const message = await sendAndRetreiveMessage(client, 'hello fren', MessageType.text)
|
|
||||||
assert.strictEqual(message.message.conversation, 'hello fren')
|
|
||||||
})
|
|
||||||
it('should forward a message', async () => {
|
|
||||||
let messages = await client.loadConversation (testJid, 1)
|
|
||||||
await client.forwardMessage (testJid, messages[0])
|
|
||||||
|
|
||||||
messages = await client.loadConversation (testJid, 1)
|
|
||||||
const message = messages[0]
|
|
||||||
const content = message.message[ Object.keys(message.message)[0] ]
|
|
||||||
assert.equal (content?.contextInfo?.isForwarded, true)
|
|
||||||
})
|
|
||||||
it('should send a link preview', async () => {
|
|
||||||
const content = await client.generateLinkPreview ('hello this is from https://www.github.com/adiwajshing/Baileys')
|
|
||||||
const message = await sendAndRetreiveMessage(client, content, MessageType.text)
|
|
||||||
const received = message.message.extendedTextMessage
|
|
||||||
assert.strictEqual(received.text, content.text)
|
|
||||||
|
|
||||||
fs.writeFileSync ('Media/received-thumb.jpeg', content.jpegThumbnail)
|
|
||||||
})
|
|
||||||
it('should quote a message', async () => {
|
|
||||||
const messages = await client.loadConversation(testJid, 2)
|
|
||||||
const message = await sendAndRetreiveMessage(client, 'hello fren 2', MessageType.extendedText, {
|
|
||||||
quoted: messages[0],
|
|
||||||
})
|
|
||||||
assert.strictEqual(message.message.extendedTextMessage.contextInfo.stanzaId, messages[0].key.id)
|
|
||||||
})
|
|
||||||
it('should send a gif', async () => {
|
|
||||||
const content = fs.readFileSync('./Media/ma_gif.mp4')
|
|
||||||
const message = await sendAndRetreiveMessage(client, content, MessageType.video, { mimetype: Mimetype.gif })
|
|
||||||
|
|
||||||
await client.downloadAndSaveMediaMessage(message,'./Media/received_vid')
|
|
||||||
})
|
|
||||||
it('should send an image', async () => {
|
|
||||||
const content = fs.readFileSync('./Media/meme.jpeg')
|
|
||||||
const message = await sendAndRetreiveMessage(client, content, MessageType.image)
|
|
||||||
const file = await decodeMediaMessage(message.message, './Media/received_img')
|
|
||||||
//const message2 = await sendAndRetreiveMessage (client, 'this is a quote', MessageType.extendedText)
|
|
||||||
})
|
|
||||||
it('should send an image & quote', async () => {
|
|
||||||
const messages = await client.loadConversation(testJid, 1)
|
|
||||||
const content = fs.readFileSync('./Media/meme.jpeg')
|
|
||||||
const message = await sendAndRetreiveMessage(client, content, MessageType.image, { quoted: messages[0] })
|
|
||||||
const file = await decodeMediaMessage(message.message, './Media/received_img')
|
|
||||||
assert.strictEqual(message.message.imageMessage.contextInfo.stanzaId, messages[0].key.id)
|
|
||||||
})
|
|
||||||
it('should send a text message & delete it', async () => {
|
|
||||||
const message = await sendAndRetreiveMessage(client, 'hello fren', MessageType.text)
|
|
||||||
await createTimeout (2000)
|
|
||||||
await client.deleteMessage (testJid, message.key)
|
|
||||||
})
|
|
||||||
it('should clear the most recent message', async () => {
|
|
||||||
const messages = await client.loadConversation (testJid, 1)
|
|
||||||
await createTimeout (2000)
|
|
||||||
await client.clearMessage (messages[0].key)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Validate WhatsApp IDs', () => {
|
|
||||||
it ('should correctly validate', () => {
|
|
||||||
assert.doesNotThrow (() => validateJIDForSending ('12345@s.whatsapp.net'))
|
|
||||||
assert.doesNotThrow (() => validateJIDForSending ('919999999999@s.whatsapp.net'))
|
|
||||||
assert.doesNotThrow (() => validateJIDForSending ('10203040506@s.whatsapp.net'))
|
|
||||||
assert.doesNotThrow (() => validateJIDForSending ('12345-3478@g.us'))
|
|
||||||
assert.doesNotThrow (() => validateJIDForSending ('1234567890-34712121238@g.us'))
|
|
||||||
assert.throws (() => validateJIDForSending ('123454677@c.us'))
|
|
||||||
assert.throws (() => validateJIDForSending ('+123454677@s.whatsapp.net'))
|
|
||||||
assert.throws (() => validateJIDForSending ('+12345-3478@g.us'))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
WAClientTest('Presence', (client) => {
|
|
||||||
it('should update presence', async () => {
|
|
||||||
const presences = Object.values(Presence)
|
|
||||||
for (const i in presences) {
|
|
||||||
const response = await client.updatePresence(testJid, presences[i])
|
|
||||||
assert.strictEqual(response.status, 200)
|
|
||||||
|
|
||||||
await createTimeout(1500)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
WAClientTest('Misc', (client) => {
|
|
||||||
it('should tell if someone has an account on WhatsApp', async () => {
|
|
||||||
const response = await client.isOnWhatsApp(testJid)
|
|
||||||
assert.strictEqual(response, true)
|
|
||||||
|
|
||||||
const responseFail = await client.isOnWhatsApp('abcd@s.whatsapp.net')
|
|
||||||
assert.strictEqual(responseFail, false)
|
|
||||||
})
|
|
||||||
it('should return the status', async () => {
|
|
||||||
const response = await client.getStatus(testJid)
|
|
||||||
assert.strictEqual(typeof response.status, 'string')
|
|
||||||
})
|
|
||||||
it('should update status', async () => {
|
|
||||||
const newStatus = 'v cool status'
|
|
||||||
|
|
||||||
const response = await client.getStatus()
|
|
||||||
assert.strictEqual(typeof response.status, 'string')
|
|
||||||
|
|
||||||
await createTimeout (1000)
|
|
||||||
|
|
||||||
await client.setStatus (newStatus)
|
|
||||||
const response2 = await client.getStatus()
|
|
||||||
assert.equal (response2.status, newStatus)
|
|
||||||
|
|
||||||
await createTimeout (1000)
|
|
||||||
|
|
||||||
await client.setStatus (response.status) // update back
|
|
||||||
})
|
|
||||||
it('should return the stories', async () => {
|
|
||||||
await client.getStories()
|
|
||||||
})
|
|
||||||
it('should change the profile picture', async () => {
|
|
||||||
await createTimeout (5000)
|
|
||||||
|
|
||||||
const ppUrl = await client.getProfilePicture(client.userMetaData.id)
|
|
||||||
const fetched = await fetch(ppUrl, { headers: { Origin: 'https://web.whatsapp.com' } })
|
|
||||||
const buff = await fetched.buffer ()
|
|
||||||
|
|
||||||
const newPP = fs.readFileSync ('./Media/cat.jpeg')
|
|
||||||
const response = await client.updateProfilePicture (client.userMetaData.id, newPP)
|
|
||||||
|
|
||||||
await createTimeout (10000)
|
|
||||||
|
|
||||||
await client.updateProfilePicture (client.userMetaData.id, buff) // revert back
|
|
||||||
})
|
|
||||||
it('should return the profile picture', async () => {
|
|
||||||
const response = await client.getProfilePicture(testJid)
|
|
||||||
assert.ok(response)
|
|
||||||
assert.rejects(client.getProfilePicture('abcd@s.whatsapp.net'))
|
|
||||||
})
|
|
||||||
it('should send typing indicator', async () => {
|
|
||||||
const response = await client.updatePresence(testJid, Presence.composing)
|
|
||||||
assert.ok(response)
|
|
||||||
})
|
|
||||||
it('should mark a chat unread', async () => {
|
|
||||||
await client.sendReadReceipt(testJid, null, 'unread')
|
|
||||||
})
|
|
||||||
it('should archive & unarchive', async () => {
|
|
||||||
await client.modifyChat (testJid, ChatModification.archive)
|
|
||||||
await createTimeout (2000)
|
|
||||||
await client.modifyChat (testJid, ChatModification.unarchive)
|
|
||||||
})
|
|
||||||
it('should pin & unpin a chat', async () => {
|
|
||||||
const response = await client.modifyChat (testJid, ChatModification.pin)
|
|
||||||
await createTimeout (2000)
|
|
||||||
await client.modifyChat (testJid, ChatModification.unpin, {stamp: response.stamp})
|
|
||||||
})
|
|
||||||
it('should mute & unmute a chat', async () => {
|
|
||||||
const mutedate = new Date (new Date().getTime() + 8*60*60*1000) // 8 hours in the future
|
|
||||||
await client.modifyChat (testJid, ChatModification.mute, {stamp: mutedate})
|
|
||||||
await createTimeout (2000)
|
|
||||||
await client.modifyChat (testJid, ChatModification.unmute, {stamp: mutedate})
|
|
||||||
})
|
|
||||||
it('should return search results', async () => {
|
|
||||||
const jids = [null, testJid]
|
|
||||||
for (let i in jids) {
|
|
||||||
const response = await client.searchMessages('Hello', jids[i], 25, 1)
|
|
||||||
assert.ok (response.messages)
|
|
||||||
assert.ok (response.messages.length >= 0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
WAClientTest('Groups', (client) => {
|
|
||||||
let gid: string
|
|
||||||
it('should create a group', async () => {
|
|
||||||
const response = await client.groupCreate('Cool Test Group', [testJid])
|
|
||||||
gid = response.gid
|
|
||||||
console.log('created group: ' + JSON.stringify(response))
|
|
||||||
})
|
|
||||||
it('should retreive group invite code', async () => {
|
|
||||||
const code = await client.groupInviteCode(gid)
|
|
||||||
assert.ok(code)
|
|
||||||
assert.strictEqual(typeof code, 'string')
|
|
||||||
})
|
|
||||||
it('should retreive group metadata', async () => {
|
|
||||||
const metadata = await client.groupMetadata(gid)
|
|
||||||
assert.strictEqual(metadata.id, gid)
|
|
||||||
assert.strictEqual(metadata.participants.filter((obj) => obj.id.split('@')[0] === testJid.split('@')[0]).length, 1)
|
|
||||||
})
|
|
||||||
it('should update the group description', async () => {
|
|
||||||
const newDesc = 'Wow this was set from Baileys'
|
|
||||||
|
|
||||||
await client.groupUpdateDescription (gid, newDesc)
|
|
||||||
await createTimeout (1000)
|
|
||||||
|
|
||||||
const metadata = await client.groupMetadata(gid)
|
|
||||||
assert.strictEqual(metadata.desc, newDesc)
|
|
||||||
})
|
|
||||||
it('should send a message on the group', async () => {
|
|
||||||
await client.sendMessage(gid, 'hello', MessageType.text)
|
|
||||||
})
|
|
||||||
it('should update the subject', async () => {
|
|
||||||
const subject = 'V Cool Title'
|
|
||||||
await client.groupUpdateSubject(gid, subject)
|
|
||||||
|
|
||||||
const metadata = await client.groupMetadata(gid)
|
|
||||||
assert.strictEqual(metadata.subject, subject)
|
|
||||||
})
|
|
||||||
it('should update the group settings', async () => {
|
|
||||||
await client.groupSettingChange (gid, GroupSettingChange.messageSend, true)
|
|
||||||
await createTimeout (5000)
|
|
||||||
await client.groupSettingChange (gid, GroupSettingChange.settingsChange, true)
|
|
||||||
})
|
|
||||||
it('should remove someone from a group', async () => {
|
|
||||||
await client.groupRemove(gid, [testJid])
|
|
||||||
})
|
|
||||||
it('should leave the group', async () => {
|
|
||||||
await client.groupLeave(gid)
|
|
||||||
await client.groupMetadataMinimal (gid)
|
|
||||||
})
|
|
||||||
it('should archive the group', async () => {
|
|
||||||
await client.archiveChat(gid)
|
|
||||||
})
|
|
||||||
it('should delete the group', async () => {
|
|
||||||
await client.deleteChat(gid)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
WAClientTest('Events', (client) => {
|
|
||||||
it('should deliver a message', async () => {
|
|
||||||
const waitForUpdate = () =>
|
|
||||||
new Promise((resolve) => {
|
|
||||||
client.setOnMessageStatusChange((update) => {
|
|
||||||
if (update.ids.includes(response.messageID)) {
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
const response = await client.sendMessage(testJid, 'My Name Jeff', MessageType.text)
|
|
||||||
await promiseTimeout(10000, waitForUpdate())
|
|
||||||
})
|
|
||||||
/*it('should retreive all conversations', async () => {
|
|
||||||
const [chats] = await client.receiveChatsAndContacts (10000)
|
|
||||||
for (let chat of chats.all()) {
|
|
||||||
console.log ('receiving ' + chat.jid)
|
|
||||||
const convo = await client.loadConversation (chat.jid.replace('@s.whatsapp.net', '@c.us'), 25)
|
|
||||||
await createTimeout (200)
|
|
||||||
}
|
|
||||||
})*/
|
|
||||||
})
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
import { MessageType, HKDFInfoKeys, MessageOptions, WAMessageType } from './Constants'
|
|
||||||
import Jimp from 'jimp'
|
|
||||||
import * as fs from 'fs'
|
|
||||||
import fetch from 'node-fetch'
|
|
||||||
import { WAMessage, WAMessageContent, BaileysError } from '../WAConnection/Constants'
|
|
||||||
import { hmacSign, aesDecryptWithIV, hkdf } from '../WAConnection/Utils'
|
|
||||||
import { proto } from '../../WAMessage/WAMessage'
|
|
||||||
import { randomBytes } from 'crypto'
|
|
||||||
import { exec } from 'child_process'
|
|
||||||
|
|
||||||
export function validateJIDForSending (jid: string) {
|
|
||||||
const regexp = /^[0-9]{1,20}(-[0-9]{1,20}@g.us|@s.whatsapp.net)$/
|
|
||||||
if (!regexp.test (jid)) {
|
|
||||||
throw new Error (
|
|
||||||
`Invalid WhatsApp id: ${jid}
|
|
||||||
1. Please ensure you suffix '@s.whatsapp.net' for individual numbers & '@g.us' for groups
|
|
||||||
2. Please do not put any alphabets or special characters like a '+' in the number. A '-' symbol in groups is fine`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Type of notification
|
|
||||||
* @deprecated use WA_MESSAGE_STUB_TYPE instead
|
|
||||||
* */
|
|
||||||
export function getNotificationType(message: WAMessage): [string, MessageType?] {
|
|
||||||
if (message.message) {
|
|
||||||
return ['message', Object.keys(message.message)[0] as MessageType]
|
|
||||||
} else if (message.messageStubType) {
|
|
||||||
return [WAMessageType[message.messageStubType], null]
|
|
||||||
} else {
|
|
||||||
return ['unknown', null]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** generates all the keys required to encrypt/decrypt & sign a media message */
|
|
||||||
export function getMediaKeys(buffer, mediaType: MessageType) {
|
|
||||||
if (typeof buffer === 'string') {
|
|
||||||
buffer = Buffer.from (buffer.replace('data:;base64,', ''), 'base64')
|
|
||||||
}
|
|
||||||
// expand using HKDF to 112 bytes, also pass in the relevant app info
|
|
||||||
const expandedMediaKey = hkdf(buffer, 112, HKDFInfoKeys[mediaType])
|
|
||||||
return {
|
|
||||||
iv: expandedMediaKey.slice(0, 16),
|
|
||||||
cipherKey: expandedMediaKey.slice(16, 48),
|
|
||||||
macKey: expandedMediaKey.slice(48, 80),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** Extracts video thumb using FFMPEG */
|
|
||||||
const extractVideoThumb = async (
|
|
||||||
path: string,
|
|
||||||
destPath: string,
|
|
||||||
time: string,
|
|
||||||
size: { width: number; height: number },
|
|
||||||
) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
const cmd = `ffmpeg -ss ${time} -i ${path} -y -s ${size.width}x${size.height} -vframes 1 -f image2 ${destPath}`
|
|
||||||
exec(cmd, (err) => {
|
|
||||||
if (err) reject(err)
|
|
||||||
else resolve()
|
|
||||||
})
|
|
||||||
}) as Promise<void>
|
|
||||||
|
|
||||||
export const compressImage = async (buffer: Buffer) => {
|
|
||||||
const jimp = await Jimp.read (buffer)
|
|
||||||
return jimp.resize(48, 48).getBufferAsync (Jimp.MIME_JPEG)
|
|
||||||
}
|
|
||||||
export const generateProfilePicture = async (buffer: Buffer) => {
|
|
||||||
const jimp = await Jimp.read (buffer)
|
|
||||||
const min = Math.min(jimp.getWidth (), jimp.getHeight ())
|
|
||||||
const cropped = jimp.crop (0, 0, min, min)
|
|
||||||
return {
|
|
||||||
img: await cropped.resize(640, 640).getBufferAsync (Jimp.MIME_JPEG),
|
|
||||||
preview: await cropped.resize(96, 96).getBufferAsync (Jimp.MIME_JPEG)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** generates a thumbnail for a given media, if required */
|
|
||||||
export async function generateThumbnail(buffer: Buffer, mediaType: MessageType, info: MessageOptions) {
|
|
||||||
if (info.thumbnail === null || info.thumbnail) {
|
|
||||||
// don't do anything if the thumbnail is already provided, or is null
|
|
||||||
if (mediaType === MessageType.audio) {
|
|
||||||
throw new Error('audio messages cannot have thumbnails')
|
|
||||||
}
|
|
||||||
} else if (mediaType === MessageType.image || mediaType === MessageType.sticker) {
|
|
||||||
const buff = await compressImage (buffer)
|
|
||||||
info.thumbnail = buff.toString('base64')
|
|
||||||
} else if (mediaType === MessageType.video) {
|
|
||||||
const filename = './' + randomBytes(5).toString('hex') + '.mp4'
|
|
||||||
const imgFilename = filename + '.jpg'
|
|
||||||
fs.writeFileSync(filename, buffer)
|
|
||||||
try {
|
|
||||||
await extractVideoThumb(filename, imgFilename, '00:00:00', { width: 48, height: 48 })
|
|
||||||
const buff = fs.readFileSync(imgFilename)
|
|
||||||
info.thumbnail = buff.toString('base64')
|
|
||||||
fs.unlinkSync(imgFilename)
|
|
||||||
} catch (err) {
|
|
||||||
console.log('could not generate video thumb: ' + err)
|
|
||||||
}
|
|
||||||
fs.unlinkSync(filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Decode a media message (video, image, document, audio) & return decrypted buffer
|
|
||||||
* @param message the media message you want to decode
|
|
||||||
*/
|
|
||||||
export async function decodeMediaMessageBuffer(message: WAMessageContent, fetchHeaders: {[k: string]: string} = {}) {
|
|
||||||
/*
|
|
||||||
One can infer media type from the key in the message
|
|
||||||
it is usually written as [mediaType]Message. Eg. imageMessage, audioMessage etc.
|
|
||||||
*/
|
|
||||||
const type = Object.keys(message)[0] as MessageType
|
|
||||||
if (!type) {
|
|
||||||
throw new BaileysError('unknown message type', message)
|
|
||||||
}
|
|
||||||
if (type === MessageType.text || type === MessageType.extendedText) {
|
|
||||||
throw new BaileysError('cannot decode text message', message)
|
|
||||||
}
|
|
||||||
if (type === MessageType.location || type === MessageType.liveLocation) {
|
|
||||||
return new Buffer(message[type].jpegThumbnail)
|
|
||||||
}
|
|
||||||
let messageContent: proto.IVideoMessage | proto.IImageMessage | proto.IAudioMessage | proto.IDocumentMessage
|
|
||||||
if (message.productMessage) {
|
|
||||||
const product = message.productMessage.product?.productImage
|
|
||||||
if (!product) throw new BaileysError ('product has no image', message)
|
|
||||||
messageContent = product
|
|
||||||
} else {
|
|
||||||
messageContent = message[type]
|
|
||||||
}
|
|
||||||
|
|
||||||
// download the message
|
|
||||||
const headers = { Origin: 'https://web.whatsapp.com' }
|
|
||||||
const fetched = await fetch(messageContent.url, { headers })
|
|
||||||
const buffer = await fetched.buffer()
|
|
||||||
|
|
||||||
if (buffer.length <= 10) {
|
|
||||||
throw new BaileysError ('Empty buffer returned. File has possibly been deleted from WA servers. Run `client.updateMediaMessage()` to refresh the url', {status: 404})
|
|
||||||
}
|
|
||||||
|
|
||||||
const decryptedMedia = (type: MessageType) => {
|
|
||||||
// get the keys to decrypt the message
|
|
||||||
const mediaKeys = getMediaKeys(messageContent.mediaKey, type) //getMediaKeys(Buffer.from(messageContent.mediaKey, 'base64'), type)
|
|
||||||
// first part is actual file
|
|
||||||
const file = buffer.slice(0, buffer.length - 10)
|
|
||||||
// last 10 bytes is HMAC sign of file
|
|
||||||
const mac = buffer.slice(buffer.length - 10, buffer.length)
|
|
||||||
// sign IV+file & check for match with mac
|
|
||||||
const testBuff = Buffer.concat([mediaKeys.iv, file])
|
|
||||||
const sign = hmacSign(testBuff, mediaKeys.macKey).slice(0, 10)
|
|
||||||
// our sign should equal the mac
|
|
||||||
if (!sign.equals(mac)) throw new Error()
|
|
||||||
|
|
||||||
return aesDecryptWithIV(file, mediaKeys.cipherKey, mediaKeys.iv) // decrypt media
|
|
||||||
}
|
|
||||||
const allTypes = [type, ...Object.keys(HKDFInfoKeys)]
|
|
||||||
for (let i = 0; i < allTypes.length;i++) {
|
|
||||||
try {
|
|
||||||
const decrypted = decryptedMedia (allTypes[i] as MessageType)
|
|
||||||
|
|
||||||
if (i > 0) { console.log (`decryption of ${type} media with HKDF key of ${allTypes[i]}`) }
|
|
||||||
return decrypted
|
|
||||||
} catch {
|
|
||||||
if (i === 0) { console.log (`decryption of ${type} media with original HKDF key failed`) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new BaileysError('Decryption failed, HMAC sign does not match', {status: 400})
|
|
||||||
}
|
|
||||||
export function extensionForMediaMessage(message: WAMessageContent) {
|
|
||||||
const getExtension = (mimetype: string) => mimetype.split(';')[0].split('/')[1]
|
|
||||||
const type = Object.keys(message)[0] as MessageType
|
|
||||||
let extension: string
|
|
||||||
if (type === MessageType.location || type === MessageType.liveLocation || type === MessageType.product) {
|
|
||||||
extension = '.jpeg'
|
|
||||||
} else {
|
|
||||||
const messageContent = message[type] as
|
|
||||||
| proto.VideoMessage
|
|
||||||
| proto.ImageMessage
|
|
||||||
| proto.AudioMessage
|
|
||||||
| proto.DocumentMessage
|
|
||||||
extension = getExtension (messageContent.mimetype)
|
|
||||||
}
|
|
||||||
return extension
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode a media message (video, image, document, audio) & save it to the given file
|
|
||||||
* @deprecated use `client.downloadAndSaveMediaMessage`
|
|
||||||
*/
|
|
||||||
export async function decodeMediaMessage(message: WAMessageContent, filename: string, attachExtension: boolean=true) {
|
|
||||||
const buffer = await decodeMediaMessageBuffer (message, {})
|
|
||||||
const extension = extensionForMediaMessage (message)
|
|
||||||
const trueFileName = attachExtension ? (filename + '.' + extension) : filename
|
|
||||||
fs.writeFileSync(trueFileName, buffer)
|
|
||||||
return trueFileName
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import WhatsAppWebMessages from './Messages'
|
|
||||||
|
|
||||||
export { WhatsAppWebMessages as WAClient }
|
|
||||||
export * from './Constants'
|
|
||||||
export * from './Utils'
|
|
||||||
export * from '../WAConnection/Constants'
|
|
||||||
export { Browsers } from '../WAConnection/Utils'
|
|
||||||
@@ -14,6 +14,11 @@ import {
|
|||||||
AuthenticationCredentialsBrowser,
|
AuthenticationCredentialsBrowser,
|
||||||
BaileysError,
|
BaileysError,
|
||||||
WAConnectionMode,
|
WAConnectionMode,
|
||||||
|
WAMessage,
|
||||||
|
PresenceUpdate,
|
||||||
|
MessageStatusUpdate,
|
||||||
|
WAMetric,
|
||||||
|
WAFlag,
|
||||||
} from './Constants'
|
} from './Constants'
|
||||||
|
|
||||||
/** Generate a QR code from the ref & the curve public key. This is scanned by the phone */
|
/** Generate a QR code from the ref & the curve public key. This is scanned by the phone */
|
||||||
@@ -22,7 +27,7 @@ const generateQRCode = function ([ref, publicKey, clientID]) {
|
|||||||
QR.generate(str, { small: true })
|
QR.generate(str, { small: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class WAConnectionBase {
|
export class WAConnection {
|
||||||
/** The version of WhatsApp Web we're telling the servers we are */
|
/** The version of WhatsApp Web we're telling the servers we are */
|
||||||
version: [number, number, number] = [2, 2027, 10]
|
version: [number, number, number] = [2, 2027, 10]
|
||||||
/** The Browser we're telling the WhatsApp Web servers we are */
|
/** The Browser we're telling the WhatsApp Web servers we are */
|
||||||
@@ -61,9 +66,7 @@ export default class WAConnectionBase {
|
|||||||
protected pendingRequests: (() => void)[] = []
|
protected pendingRequests: (() => void)[] = []
|
||||||
protected reconnectLoop: () => Promise<void>
|
protected reconnectLoop: () => Promise<void>
|
||||||
protected referenceDate = new Date () // used for generating tags
|
protected referenceDate = new Date () // used for generating tags
|
||||||
protected userAgentString: string
|
|
||||||
constructor () {
|
constructor () {
|
||||||
this.userAgentString = Utils.userAgentString (this.browserDescription[1])
|
|
||||||
this.registerCallback (['Cmd', 'type:disconnect'], json => this.unexpectedDisconnect(json[1].kind))
|
this.registerCallback (['Cmd', 'type:disconnect'], json => this.unexpectedDisconnect(json[1].kind))
|
||||||
}
|
}
|
||||||
async unexpectedDisconnect (error: string) {
|
async unexpectedDisconnect (error: string) {
|
||||||
@@ -74,6 +77,46 @@ export default class WAConnectionBase {
|
|||||||
this.unexpectedDisconnectCallback (error)
|
this.unexpectedDisconnectCallback (error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/** Set the callback for message status updates (when a message is delivered, read etc.) */
|
||||||
|
setOnMessageStatusChange(callback: (update: MessageStatusUpdate) => void) {
|
||||||
|
const func = json => {
|
||||||
|
json = json[1]
|
||||||
|
let ids = json.id
|
||||||
|
if (json.cmd === 'ack') {
|
||||||
|
ids = [json.id]
|
||||||
|
}
|
||||||
|
const data: MessageStatusUpdate = {
|
||||||
|
from: json.from,
|
||||||
|
to: json.to,
|
||||||
|
participant: json.participant,
|
||||||
|
timestamp: new Date(json.t * 1000),
|
||||||
|
ids: ids,
|
||||||
|
type: (+json.ack)+1,
|
||||||
|
}
|
||||||
|
callback(data)
|
||||||
|
}
|
||||||
|
this.registerCallback('Msg', func)
|
||||||
|
this.registerCallback('MsgInfo', func)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Set the callback for new/unread messages; if someone sends you a message, this callback will be fired
|
||||||
|
* @param callbackOnMyMessages - should the callback be fired on a message you sent from the phone
|
||||||
|
*/
|
||||||
|
setOnUnreadMessage(callbackOnMyMessages = false, callback: (m: WAMessage) => void) {
|
||||||
|
this.registerCallback(['action', 'add:relay', 'message'], (json) => {
|
||||||
|
const message = json[2][0][2]
|
||||||
|
if (!message.key.fromMe || callbackOnMyMessages) {
|
||||||
|
// if this message was sent to us, notify
|
||||||
|
callback(message as WAMessage)
|
||||||
|
} else {
|
||||||
|
this.log(`[Unhandled] message - ${JSON.stringify(message)}`, MessageLogLevel.unhandled)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/** Set the callback for presence updates; if someone goes offline/online, this callback will be fired */
|
||||||
|
setOnPresenceUpdate(callback: (p: PresenceUpdate) => void) {
|
||||||
|
this.registerCallback('Presence', json => callback(json[1]))
|
||||||
|
}
|
||||||
/** Set the callback for unexpected disconnects including take over events, log out events etc. */
|
/** Set the callback for unexpected disconnects including take over events, log out events etc. */
|
||||||
setOnUnexpectedDisconnect(callback: (error: string) => void) {
|
setOnUnexpectedDisconnect(callback: (error: string) => void) {
|
||||||
this.unexpectedDisconnectCallback = callback
|
this.unexpectedDisconnectCallback = callback
|
||||||
@@ -243,6 +286,12 @@ export default class WAConnectionBase {
|
|||||||
|
|
||||||
return this.waitForMessage(tag, json, timeoutMs)
|
return this.waitForMessage(tag, json, timeoutMs)
|
||||||
}
|
}
|
||||||
|
/** Generic function for action, set queries */
|
||||||
|
async setQuery (nodes: WANode[], binaryTags: WATag = [WAMetric.group, WAFlag.ignore], tag?: string) {
|
||||||
|
const json = ['action', {epoch: this.msgCount.toString(), type: 'set'}, nodes]
|
||||||
|
const result = await this.queryExpecting200(json, binaryTags, null, tag) as Promise<{status: number}>
|
||||||
|
return result
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Send a binary encoded message
|
* Send a binary encoded message
|
||||||
* @param json the message to encode & send
|
* @param json the message to encode & send
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import * as Curve from 'curve25519-js'
|
import * as Curve from 'curve25519-js'
|
||||||
import * as Utils from './Utils'
|
import * as Utils from './Utils'
|
||||||
import WAConnectionBase from './Base'
|
import {WAConnection as Base} from './0.Base'
|
||||||
import { MessageLogLevel, WAMetric, WAFlag, BaileysError } from './Constants'
|
import { MessageLogLevel, WAMetric, WAFlag, BaileysError, Presence } from './Constants'
|
||||||
import { Presence } from '../WAClient/WAClient'
|
|
||||||
|
|
||||||
export default class WAConnectionValidator extends WAConnectionBase {
|
export class WAConnection extends Base {
|
||||||
|
|
||||||
/** Authenticate the connection */
|
/** Authenticate the connection */
|
||||||
protected async authenticate() {
|
protected async authenticate() {
|
||||||
if (!this.authInfo.clientID) {
|
if (!this.authInfo.clientID) {
|
||||||
@@ -21,6 +21,7 @@ export default class WAConnectionValidator extends WAConnectionBase {
|
|||||||
|
|
||||||
this.referenceDate = new Date () // refresh reference date
|
this.referenceDate = new Date () // refresh reference date
|
||||||
const data = ['admin', 'init', this.version, this.browserDescription, this.authInfo.clientID, true]
|
const data = ['admin', 'init', this.version, this.browserDescription, this.authInfo.clientID, true]
|
||||||
|
|
||||||
return this.queryExpecting200(data)
|
return this.queryExpecting200(data)
|
||||||
.then(json => {
|
.then(json => {
|
||||||
// we're trying to establish a new connection or are trying to log in
|
// we're trying to establish a new connection or are trying to log in
|
||||||
@@ -2,10 +2,10 @@ import WS from 'ws'
|
|||||||
import KeyedDB from '@adiwajshing/keyed-db'
|
import KeyedDB from '@adiwajshing/keyed-db'
|
||||||
import * as Utils from './Utils'
|
import * as Utils from './Utils'
|
||||||
import { AuthenticationCredentialsBase64, UserMetaData, WAMessage, WAChat, WAContact, MessageLogLevel, WANode, WAConnectionMode } from './Constants'
|
import { AuthenticationCredentialsBase64, UserMetaData, WAMessage, WAChat, WAContact, MessageLogLevel, WANode, WAConnectionMode } from './Constants'
|
||||||
import WAConnectionValidator from './Validation'
|
import {WAConnection as Base} from './1.Validation'
|
||||||
import Decoder from '../Binary/Decoder'
|
import Decoder from '../Binary/Decoder'
|
||||||
|
|
||||||
export default class WAConnectionConnector extends WAConnectionValidator {
|
export class WAConnection extends Base {
|
||||||
/**
|
/**
|
||||||
* Connect to WhatsAppWeb
|
* Connect to WhatsAppWeb
|
||||||
* @param [authInfo] credentials or path to credentials to log back in
|
* @param [authInfo] credentials or path to credentials to log back in
|
||||||
@@ -1,58 +1,16 @@
|
|||||||
import WAConnection from '../WAConnection/WAConnection'
|
import {WAConnection as Base} from './3.Connect'
|
||||||
import { MessageStatusUpdate, PresenceUpdate, Presence, WABroadcastListInfo, WAProfilePictureChange } from './Constants'
|
import { Presence, WABroadcastListInfo, WAProfilePictureChange } from './Constants'
|
||||||
import {
|
import {
|
||||||
WAMessage,
|
WAMessage,
|
||||||
WANode,
|
WANode,
|
||||||
WAMetric,
|
WAMetric,
|
||||||
WAFlag,
|
WAFlag,
|
||||||
MessageLogLevel,
|
|
||||||
WATag,
|
|
||||||
} from '../WAConnection/Constants'
|
} from '../WAConnection/Constants'
|
||||||
import { generateProfilePicture } from '../WAClient/Utils'
|
import { generateProfilePicture } from './Utils'
|
||||||
|
|
||||||
|
// All user related functions -- get profile picture, set status etc.
|
||||||
|
|
||||||
export default class WhatsAppWebBase extends WAConnection {
|
export class WAConnection extends Base {
|
||||||
|
|
||||||
/** Set the callback for message status updates (when a message is delivered, read etc.) */
|
|
||||||
setOnMessageStatusChange(callback: (update: MessageStatusUpdate) => void) {
|
|
||||||
const func = json => {
|
|
||||||
json = json[1]
|
|
||||||
let ids = json.id
|
|
||||||
if (json.cmd === 'ack') {
|
|
||||||
ids = [json.id]
|
|
||||||
}
|
|
||||||
const data: MessageStatusUpdate = {
|
|
||||||
from: json.from,
|
|
||||||
to: json.to,
|
|
||||||
participant: json.participant,
|
|
||||||
timestamp: new Date(json.t * 1000),
|
|
||||||
ids: ids,
|
|
||||||
type: (+json.ack)+1,
|
|
||||||
}
|
|
||||||
callback(data)
|
|
||||||
}
|
|
||||||
this.registerCallback('Msg', func)
|
|
||||||
this.registerCallback('MsgInfo', func)
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Set the callback for new/unread messages; if someone sends you a message, this callback will be fired
|
|
||||||
* @param callbackOnMyMessages - should the callback be fired on a message you sent from the phone
|
|
||||||
*/
|
|
||||||
setOnUnreadMessage(callbackOnMyMessages = false, callback: (m: WAMessage) => void) {
|
|
||||||
this.registerCallback(['action', 'add:relay', 'message'], (json) => {
|
|
||||||
const message = json[2][0][2]
|
|
||||||
if (!message.key.fromMe || callbackOnMyMessages) {
|
|
||||||
// if this message was sent to us, notify
|
|
||||||
callback(message as WAMessage)
|
|
||||||
} else {
|
|
||||||
this.log(`[Unhandled] message - ${JSON.stringify(message)}`, MessageLogLevel.unhandled)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/** Set the callback for presence updates; if someone goes offline/online, this callback will be fired */
|
|
||||||
setOnPresenceUpdate(callback: (p: PresenceUpdate) => void) {
|
|
||||||
this.registerCallback('Presence', json => callback(json[1]))
|
|
||||||
}
|
|
||||||
/** Query whether a given number is registered on WhatsApp */
|
/** Query whether a given number is registered on WhatsApp */
|
||||||
isOnWhatsApp = (jid: string) => this.query(['query', 'exist', jid]).then((m) => m.status === 200)
|
isOnWhatsApp = (jid: string) => this.query(['query', 'exist', jid]).then((m) => m.status === 200)
|
||||||
/**
|
/**
|
||||||
@@ -75,13 +33,15 @@ export default class WhatsAppWebBase extends WAConnection {
|
|||||||
return this.query(['query', 'Status', jid || this.userMetaData.id]) as Promise<{ status: string }>
|
return this.query(['query', 'Status', jid || this.userMetaData.id]) as Promise<{ status: string }>
|
||||||
}
|
}
|
||||||
async setStatus (status: string) {
|
async setStatus (status: string) {
|
||||||
return this.setQuery ([
|
return this.setQuery (
|
||||||
[
|
[
|
||||||
'status',
|
[
|
||||||
null,
|
'status',
|
||||||
Buffer.from (status, 'utf-8')
|
null,
|
||||||
|
Buffer.from (status, 'utf-8')
|
||||||
|
]
|
||||||
]
|
]
|
||||||
])
|
)
|
||||||
}
|
}
|
||||||
/** Get the URL to download the profile picture of a person/group */
|
/** Get the URL to download the profile picture of a person/group */
|
||||||
async getProfilePicture(jid: string | null) {
|
async getProfilePicture(jid: string | null) {
|
||||||
@@ -157,10 +117,7 @@ export default class WhatsAppWebBase extends WAConnection {
|
|||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
]
|
]
|
||||||
const response = await this.query(json, [WAMetric.queryMessages, WAFlag.ignore])
|
const response = await this.queryExpecting200(json, [WAMetric.queryMessages, WAFlag.ignore])
|
||||||
|
|
||||||
if (response.status) throw new Error(`error in query, got status: ${response.status}`)
|
|
||||||
|
|
||||||
return response[2] ? (response[2] as WANode[]).map((item) => item[2] as WAMessage) : []
|
return response[2] ? (response[2] as WANode[]).map((item) => item[2] as WAMessage) : []
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -210,10 +167,4 @@ export default class WhatsAppWebBase extends WAConnection {
|
|||||||
]
|
]
|
||||||
return this.setQuery ([query], [WAMetric.picture, 136], tag) as Promise<WAProfilePictureChange>
|
return this.setQuery ([query], [WAMetric.picture, 136], tag) as Promise<WAProfilePictureChange>
|
||||||
}
|
}
|
||||||
/** Generic function for action, set queries */
|
|
||||||
async setQuery (nodes: WANode[], binaryTags: WATag = [WAMetric.group, WAFlag.ignore], tag?: string) {
|
|
||||||
const json = ['action', {epoch: this.msgCount.toString(), type: 'set'}, nodes]
|
|
||||||
const result = await this.queryExpecting200(json, binaryTags, null, tag) as Promise<{status: number}>
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import WhatsAppWebGroups from './Groups'
|
import {WAConnection as Base} from './4.User'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
import { promises as fs } from 'fs'
|
import fs from 'fs/promises'
|
||||||
import {
|
import {
|
||||||
MessageOptions,
|
MessageOptions,
|
||||||
MessageType,
|
MessageType,
|
||||||
@@ -15,13 +15,11 @@ import {
|
|||||||
MessageInfo,
|
MessageInfo,
|
||||||
WATextMessage,
|
WATextMessage,
|
||||||
WAUrlInfo,
|
WAUrlInfo,
|
||||||
|
WAMessageContent, WAMetric, WAFlag, WANode, WAMessage, WAMessageProto, BaileysError, MessageLogLevel, WA_MESSAGE_STATUS_TYPE
|
||||||
} from './Constants'
|
} from './Constants'
|
||||||
import { generateMessageID, sha256, hmacSign, aesEncrypWithIV, randomBytes } from '../WAConnection/Utils'
|
import { generateMessageID, sha256, hmacSign, aesEncrypWithIV, randomBytes, generateThumbnail, getMediaKeys, decodeMediaMessageBuffer, extensionForMediaMessage } from './Utils'
|
||||||
import { WAMessageContent, WAMetric, WAFlag, WANode, WAMessage, WAMessageProto, BaileysError, MessageLogLevel } from '../WAConnection/Constants'
|
|
||||||
import { validateJIDForSending, generateThumbnail, getMediaKeys, decodeMediaMessageBuffer, extensionForMediaMessage } from './Utils'
|
|
||||||
import { proto } from '../../WAMessage/WAMessage'
|
|
||||||
|
|
||||||
export default class WhatsAppWebMessages extends WhatsAppWebGroups {
|
export class WAConnection extends Base {
|
||||||
/** Get the message info, who has read it, who its been delivered to */
|
/** Get the message info, who has read it, who its been delivered to */
|
||||||
async messageInfo (jid: string, messageID: string) {
|
async messageInfo (jid: string, messageID: string) {
|
||||||
const query = ['query', {type: 'message_info', index: messageID, jid: jid, epoch: this.msgCount.toString()}, null]
|
const query = ['query', {type: 'message_info', index: messageID, jid: jid, epoch: this.msgCount.toString()}, null]
|
||||||
@@ -56,16 +54,6 @@ export default class WhatsAppWebMessages extends WhatsAppWebGroups {
|
|||||||
}
|
}
|
||||||
return this.setQuery ([['read', attributes, null]])
|
return this.setQuery ([['read', attributes, null]])
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Mark a given chat as unread
|
|
||||||
* @deprecated since 2.0.0, use `sendReadReceipt (jid, null, 'unread')` instead
|
|
||||||
*/
|
|
||||||
async markChatUnread (jid: string) { return this.sendReadReceipt (jid, null, 'unread') }
|
|
||||||
/**
|
|
||||||
* Archive a chat
|
|
||||||
* @deprecated since 2.0.0, use `modifyChat (jid, ChatModification.archive)` instead
|
|
||||||
*/
|
|
||||||
async archiveChat (jid: string) { return this.modifyChat (jid, ChatModification.archive) }
|
|
||||||
/**
|
/**
|
||||||
* Modify a given chat (archive, pin etc.)
|
* Modify a given chat (archive, pin etc.)
|
||||||
* @param jid the ID of the person/group you are modifiying
|
* @param jid the ID of the person/group you are modifiying
|
||||||
@@ -190,24 +178,27 @@ export default class WhatsAppWebMessages extends WhatsAppWebGroups {
|
|||||||
const json: WAMessageContent = {
|
const json: WAMessageContent = {
|
||||||
protocolMessage: {
|
protocolMessage: {
|
||||||
key: messageKey,
|
key: messageKey,
|
||||||
type: proto.ProtocolMessage.PROTOCOL_MESSAGE_TYPE.REVOKE
|
type: WAMessageProto.ProtocolMessage.PROTOCOL_MESSAGE_TYPE.REVOKE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.sendMessageContent (id, json, {})
|
const waMessage = this.generateWAMessage (id, json, {})
|
||||||
|
await this.relayWAMessage (waMessage)
|
||||||
|
return waMessage
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Forward a message like WA does
|
* Forward a message like WA does
|
||||||
* @param id the id to forward the message to
|
* @param id the id to forward the message to
|
||||||
* @param message the message to forward
|
* @param message the message to forward
|
||||||
|
* @param forceForward will show the message as forwarded even if it is from you
|
||||||
*/
|
*/
|
||||||
async forwardMessage(id: string, message: WAMessage) {
|
async forwardMessage(id: string, message: WAMessage, forceForward: boolean=false) {
|
||||||
const content = message.message
|
const content = message.message
|
||||||
if (!content) throw new Error ('no content in message')
|
if (!content) throw new Error ('no content in message')
|
||||||
|
|
||||||
let key = Object.keys(content)[0]
|
let key = Object.keys(content)[0]
|
||||||
|
|
||||||
let score = content[key].contextInfo?.forwardingScore || 0
|
let score = content[key].contextInfo?.forwardingScore || 0
|
||||||
score += message.key.fromMe ? 0 : 1
|
score += message.key.fromMe && !forceForward ? 0 : 1
|
||||||
if (key === MessageType.text) {
|
if (key === MessageType.text) {
|
||||||
content[MessageType.extendedText] = { text: content[key] }
|
content[MessageType.extendedText] = { text: content[key] }
|
||||||
delete content[MessageType.text]
|
delete content[MessageType.text]
|
||||||
@@ -216,18 +207,35 @@ export default class WhatsAppWebMessages extends WhatsAppWebGroups {
|
|||||||
}
|
}
|
||||||
if (score > 0) content[key].contextInfo = { forwardingScore: score, isForwarded: true }
|
if (score > 0) content[key].contextInfo = { forwardingScore: score, isForwarded: true }
|
||||||
else content[key].contextInfo = {}
|
else content[key].contextInfo = {}
|
||||||
|
|
||||||
return this.sendMessageContent (id, content, {})
|
const waMessage = this.generateWAMessage (id, content, {})
|
||||||
|
await this.relayWAMessage (waMessage)
|
||||||
|
return waMessage
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Send a message to the given ID (can be group, single, or broadcast)
|
||||||
|
* @param id
|
||||||
|
* @param message
|
||||||
|
* @param type
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
async sendMessage(
|
async sendMessage(
|
||||||
id: string,
|
id: string,
|
||||||
message: string | WATextMessage | WALocationMessage | WAContactMessage | Buffer,
|
message: string | WATextMessage | WALocationMessage | WAContactMessage | Buffer,
|
||||||
type: MessageType,
|
type: MessageType,
|
||||||
options: MessageOptions = {},
|
options: MessageOptions = {},
|
||||||
) {
|
) {
|
||||||
if (options.validateID === true || !('validateID' in options)) {
|
const waMessage = await this.prepareMessage (id, message, type, options)
|
||||||
validateJIDForSending (id)
|
await this.relayWAMessage (waMessage)
|
||||||
}
|
return waMessage
|
||||||
|
}
|
||||||
|
/** Prepares a message for sending via sendWAMessage () */
|
||||||
|
async prepareMessage(
|
||||||
|
id: string,
|
||||||
|
message: string | WATextMessage | WALocationMessage | WAContactMessage | Buffer,
|
||||||
|
type: MessageType,
|
||||||
|
options: MessageOptions = {},
|
||||||
|
) {
|
||||||
let m: WAMessageContent = {}
|
let m: WAMessageContent = {}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MessageType.text:
|
case MessageType.text:
|
||||||
@@ -251,7 +259,7 @@ export default class WhatsAppWebMessages extends WhatsAppWebGroups {
|
|||||||
m = await this.prepareMediaMessage(message as Buffer, type, options)
|
m = await this.prepareMediaMessage(message as Buffer, type, options)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return this.sendMessageContent(id, m, options)
|
return this.generateWAMessage(id, m, options)
|
||||||
}
|
}
|
||||||
/** Prepare a media message for sending */
|
/** Prepare a media message for sending */
|
||||||
async prepareMediaMessage(buffer: Buffer, mediaType: MessageType, options: MessageOptions = {}) {
|
async prepareMediaMessage(buffer: Buffer, mediaType: MessageType, options: MessageOptions = {}) {
|
||||||
@@ -315,15 +323,13 @@ export default class WhatsAppWebMessages extends WhatsAppWebGroups {
|
|||||||
}
|
}
|
||||||
return message as WAMessageContent
|
return message as WAMessageContent
|
||||||
}
|
}
|
||||||
/** Send message content */
|
|
||||||
async sendMessageContent(id: string, message: WAMessageContent, options: MessageOptions) {
|
|
||||||
const messageJSON = this.generateWAMessage (id, message, options)
|
|
||||||
return this.sendWAMessage (messageJSON)
|
|
||||||
}
|
|
||||||
/** generates a WAMessage from the given content & options */
|
/** generates a WAMessage from the given content & options */
|
||||||
generateWAMessage(id: string, message: WAMessageContent, options: MessageOptions) {
|
generateWAMessage(id: string, message: WAMessageContent, options: MessageOptions) {
|
||||||
if (!options.timestamp) options.timestamp = new Date() // set timestamp to now
|
if (!options.timestamp) options.timestamp = new Date() // set timestamp to now
|
||||||
|
|
||||||
|
// prevent an annoying bug (WA doesn't accept sending messages with '@c.us')
|
||||||
|
id = id.replace ('@c.us', '@s.whatsapp')
|
||||||
|
|
||||||
const key = Object.keys(message)[0]
|
const key = Object.keys(message)[0]
|
||||||
const timestamp = options.timestamp.getTime()/1000
|
const timestamp = options.timestamp.getTime()/1000
|
||||||
const quoted = options.quoted
|
const quoted = options.quoted
|
||||||
@@ -356,29 +362,22 @@ export default class WhatsAppWebMessages extends WhatsAppWebGroups {
|
|||||||
messageTimestamp: timestamp,
|
messageTimestamp: timestamp,
|
||||||
messageStubParameters: [],
|
messageStubParameters: [],
|
||||||
participant: id.includes('@g.us') ? this.userMetaData.id : null,
|
participant: id.includes('@g.us') ? this.userMetaData.id : null,
|
||||||
status: WAMessageProto.proto.WebMessageInfo.WEB_MESSAGE_INFO_STATUS.PENDING
|
status: WA_MESSAGE_STATUS_TYPE.PENDING
|
||||||
}
|
}
|
||||||
return messageJSON as WAMessage
|
return messageJSON as WAMessage
|
||||||
}
|
}
|
||||||
/**
|
/** Relay (send) a WAMessage; more advanced functionality to send a built WA Message, you may want to stick with sendMessage() */
|
||||||
* Send a WAMessage; more advanced functionality, you may want to stick with sendMessage()
|
async relayWAMessage(message: WAMessage) {
|
||||||
* */
|
|
||||||
async sendWAMessage(message: WAMessage) {
|
|
||||||
const json = ['action', {epoch: this.msgCount.toString(), type: 'relay'}, [['message', null, message]]]
|
const json = ['action', {epoch: this.msgCount.toString(), type: 'relay'}, [['message', null, message]]]
|
||||||
const flag = message.key.remoteJid === this.userMetaData.id ? WAFlag.acknowledge : WAFlag.ignore // acknowledge when sending message to oneself
|
const flag = message.key.remoteJid === this.userMetaData.id ? WAFlag.acknowledge : WAFlag.ignore // acknowledge when sending message to oneself
|
||||||
const response = await this.queryExpecting200(json, [WAMetric.message, flag], null, message.key.id)
|
await this.queryExpecting200(json, [WAMetric.message, flag], null, message.key.id)
|
||||||
return {
|
|
||||||
status: response.status as number,
|
|
||||||
messageID: message.key.id,
|
|
||||||
message: message as WAMessage
|
|
||||||
} as WASendMessageResponse
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Securely downloads the media from the message.
|
* Securely downloads the media from the message.
|
||||||
* Renews the download url automatically, if necessary.
|
* Renews the download url automatically, if necessary.
|
||||||
*/
|
*/
|
||||||
async downloadMediaMessage (message: WAMessage) {
|
async downloadMediaMessage (message: WAMessage) {
|
||||||
const fetchHeaders = { 'User-Agent': this.userAgentString }
|
const fetchHeaders = { }
|
||||||
try {
|
try {
|
||||||
const buff = await decodeMediaMessageBuffer (message.message, fetchHeaders)
|
const buff = await decodeMediaMessageBuffer (message.message, fetchHeaders)
|
||||||
return buff
|
return buff
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import WhatsAppWebBase from './Base'
|
import {WAConnection as Base} from './5.Messages'
|
||||||
import { WAMessage, WAMetric, WAFlag, WANode, WAGroupMetadata, WAGroupCreateResponse, WAGroupModification } from '../WAConnection/Constants'
|
import { WAMetric, WAFlag, WANode, WAGroupMetadata, WAGroupCreateResponse, WAGroupModification } from '../WAConnection/Constants'
|
||||||
import { GroupSettingChange } from './Constants'
|
import { GroupSettingChange } from './Constants'
|
||||||
import { generateMessageID } from '../WAConnection/Utils'
|
import { generateMessageID } from '../WAConnection/Utils'
|
||||||
|
|
||||||
export default class WhatsAppWebGroups extends WhatsAppWebBase {
|
export class WAConnection extends Base {
|
||||||
/** Generic function for group queries */
|
/** Generic function for group queries */
|
||||||
async groupQuery(type: string, jid?: string, subject?: string, participants?: string[], additionalNodes?: WANode[]) {
|
async groupQuery(type: string, jid?: string, subject?: string, participants?: string[], additionalNodes?: WANode[]) {
|
||||||
const tag = this.generateMessageTag()
|
const tag = this.generateMessageTag()
|
||||||
@@ -133,6 +133,152 @@ export enum WAFlag {
|
|||||||
}
|
}
|
||||||
/** Tag used with binary queries */
|
/** Tag used with binary queries */
|
||||||
export type WATag = [WAMetric, WAFlag]
|
export type WATag = [WAMetric, WAFlag]
|
||||||
export * as WAMessageProto from '../../WAMessage/WAMessage'
|
// export the WAMessage Prototype as well
|
||||||
|
export { proto as WAMessageProto } from '../../WAMessage/WAMessage'
|
||||||
|
|
||||||
|
/** set of statuses visible to other people; see updatePresence() in WhatsAppWeb.Send */
|
||||||
|
export enum Presence {
|
||||||
|
available = 'available', // "online"
|
||||||
|
unavailable = 'unavailable', // "offline"
|
||||||
|
composing = 'composing', // "typing..."
|
||||||
|
recording = 'recording', // "recording..."
|
||||||
|
paused = 'paused', // I have no clue
|
||||||
|
}
|
||||||
|
/** Status of a message sent or received */
|
||||||
|
export enum MessageStatus {
|
||||||
|
sent = 'sent',
|
||||||
|
received = 'received',
|
||||||
|
read = 'read',
|
||||||
|
}
|
||||||
|
/** Set of message types that are supported by the library */
|
||||||
|
export enum MessageType {
|
||||||
|
text = 'conversation',
|
||||||
|
extendedText = 'extendedTextMessage',
|
||||||
|
contact = 'contactMessage',
|
||||||
|
location = 'locationMessage',
|
||||||
|
liveLocation = 'liveLocationMessage',
|
||||||
|
|
||||||
|
image = 'imageMessage',
|
||||||
|
video = 'videoMessage',
|
||||||
|
sticker = 'stickerMessage',
|
||||||
|
document = 'documentMessage',
|
||||||
|
audio = 'audioMessage',
|
||||||
|
product = 'productMessage'
|
||||||
|
}
|
||||||
|
export enum ChatModification {
|
||||||
|
archive='archive',
|
||||||
|
unarchive='unarchive',
|
||||||
|
pin='pin',
|
||||||
|
unpin='unpin',
|
||||||
|
mute='mute',
|
||||||
|
unmute='unmute'
|
||||||
|
}
|
||||||
|
export const HKDFInfoKeys = {
|
||||||
|
[MessageType.image]: 'WhatsApp Image Keys',
|
||||||
|
[MessageType.audio]: 'WhatsApp Audio Keys',
|
||||||
|
[MessageType.video]: 'WhatsApp Video Keys',
|
||||||
|
[MessageType.document]: 'WhatsApp Document Keys',
|
||||||
|
[MessageType.sticker]: 'WhatsApp Image Keys'
|
||||||
|
}
|
||||||
|
export enum Mimetype {
|
||||||
|
jpeg = 'image/jpeg',
|
||||||
|
png = 'image/png',
|
||||||
|
mp4 = 'video/mp4',
|
||||||
|
gif = 'video/gif',
|
||||||
|
pdf = 'application/pdf',
|
||||||
|
ogg = 'audio/ogg; codecs=opus',
|
||||||
|
/** for stickers */
|
||||||
|
webp = 'image/webp',
|
||||||
|
}
|
||||||
|
export interface MessageOptions {
|
||||||
|
quoted?: WAMessage
|
||||||
|
contextInfo?: WAContextInfo
|
||||||
|
timestamp?: Date
|
||||||
|
caption?: string
|
||||||
|
thumbnail?: string
|
||||||
|
mimetype?: Mimetype | string
|
||||||
|
filename?: string
|
||||||
|
}
|
||||||
|
export interface WABroadcastListInfo {
|
||||||
|
status: number
|
||||||
|
name: string
|
||||||
|
recipients?: {id: string}[]
|
||||||
|
}
|
||||||
|
export interface WAUrlInfo {
|
||||||
|
'canonical-url': string
|
||||||
|
'matched-text': string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
jpegThumbnail?: Buffer
|
||||||
|
}
|
||||||
|
export interface WAProfilePictureChange {
|
||||||
|
status: number
|
||||||
|
tag: string
|
||||||
|
eurl: string
|
||||||
|
}
|
||||||
|
export interface MessageInfo {
|
||||||
|
reads: {jid: string, t: string}[]
|
||||||
|
deliveries: {jid: string, t: string}[]
|
||||||
|
}
|
||||||
|
export interface MessageStatusUpdate {
|
||||||
|
from: string
|
||||||
|
to: string
|
||||||
|
/** Which participant caused the update (only for groups) */
|
||||||
|
participant?: string
|
||||||
|
timestamp: Date
|
||||||
|
/** Message IDs read/delivered */
|
||||||
|
ids: string[]
|
||||||
|
/** Status of the Message IDs */
|
||||||
|
type: WA_MESSAGE_STATUS_TYPE
|
||||||
|
}
|
||||||
|
export enum GroupSettingChange {
|
||||||
|
messageSend = 'announcement',
|
||||||
|
settingsChange = 'locked',
|
||||||
|
}
|
||||||
|
export interface PresenceUpdate {
|
||||||
|
id: string
|
||||||
|
participant?: string
|
||||||
|
t?: string
|
||||||
|
type?: Presence
|
||||||
|
deny?: boolean
|
||||||
|
}
|
||||||
|
// path to upload the media
|
||||||
|
export const MediaPathMap = {
|
||||||
|
imageMessage: '/mms/image',
|
||||||
|
videoMessage: '/mms/video',
|
||||||
|
documentMessage: '/mms/document',
|
||||||
|
audioMessage: '/mms/audio',
|
||||||
|
stickerMessage: '/mms/image',
|
||||||
|
}
|
||||||
|
// gives WhatsApp info to process the media
|
||||||
|
export const MimetypeMap = {
|
||||||
|
imageMessage: Mimetype.jpeg,
|
||||||
|
videoMessage: Mimetype.mp4,
|
||||||
|
documentMessage: Mimetype.pdf,
|
||||||
|
audioMessage: Mimetype.ogg,
|
||||||
|
stickerMessage: Mimetype.webp,
|
||||||
|
}
|
||||||
|
export interface WASendMessageResponse {
|
||||||
|
status: number
|
||||||
|
messageID: string
|
||||||
|
message: WAMessage
|
||||||
|
}
|
||||||
|
export interface WALocationMessage {
|
||||||
|
degreesLatitude: number
|
||||||
|
degreesLongitude: number
|
||||||
|
address?: string
|
||||||
|
}
|
||||||
|
export import WA_MESSAGE_STUB_TYPE = proto.WebMessageInfo.WEB_MESSAGE_INFO_STUBTYPE
|
||||||
|
export import WA_MESSAGE_STATUS_TYPE = proto.WebMessageInfo.WEB_MESSAGE_INFO_STATUS
|
||||||
|
|
||||||
|
/** Reverse stub type dictionary */
|
||||||
|
export const WAMessageType = function () {
|
||||||
|
const types = WA_MESSAGE_STUB_TYPE
|
||||||
|
const dict: Record<number, string> = {}
|
||||||
|
Object.keys(types).forEach(element => dict[ types[element] ] = element)
|
||||||
|
return dict
|
||||||
|
}()
|
||||||
|
export type WAContactMessage = proto.ContactMessage
|
||||||
|
export type WAMessageKey = proto.IMessageKey
|
||||||
|
export type WATextMessage = proto.ExtendedTextMessage
|
||||||
|
export type WAContextInfo = proto.IContextInfo
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import * as Crypto from 'crypto'
|
import * as Crypto from 'crypto'
|
||||||
import HKDF from 'futoin-hkdf'
|
import HKDF from 'futoin-hkdf'
|
||||||
import Decoder from '../Binary/Decoder'
|
import Jimp from 'jimp'
|
||||||
|
import fs from 'fs/promises'
|
||||||
|
import fetch from 'node-fetch'
|
||||||
|
import { exec } from 'child_process'
|
||||||
import {platform, release} from 'os'
|
import {platform, release} from 'os'
|
||||||
import { BaileysError, WAChat } from './Constants'
|
|
||||||
import UserAgent from 'user-agents'
|
import Decoder from '../Binary/Decoder'
|
||||||
|
import { MessageType, HKDFInfoKeys, MessageOptions, WAChat, WAMessageType, WAMessage, WAMessageContent, BaileysError, WAMessageProto } from './Constants'
|
||||||
|
|
||||||
const platformMap = {
|
const platformMap = {
|
||||||
'aix': 'AIX',
|
'aix': 'AIX',
|
||||||
@@ -25,10 +29,10 @@ function hashCode(s: string) {
|
|||||||
}
|
}
|
||||||
export const waChatUniqueKey = (c: WAChat) => ((+c.t*100000) + (hashCode(c.jid)%100000))*-1 // -1 to sort descending
|
export const waChatUniqueKey = (c: WAChat) => ((+c.t*100000) + (hashCode(c.jid)%100000))*-1 // -1 to sort descending
|
||||||
|
|
||||||
export function userAgentString (browser) {
|
/*export function userAgentString (browser) {
|
||||||
const agent = new UserAgent (new RegExp(browser))
|
const agent = new UserAgent (new RegExp(browser))
|
||||||
return agent.toString ()
|
return agent.toString ()
|
||||||
}
|
}*/
|
||||||
/** decrypt AES 256 CBC; where the IV is prefixed to the buffer */
|
/** decrypt AES 256 CBC; where the IV is prefixed to the buffer */
|
||||||
export function aesDecrypt(buffer: Buffer, key: Buffer) {
|
export function aesDecrypt(buffer: Buffer, key: Buffer) {
|
||||||
return aesDecryptWithIV(buffer.slice(16, buffer.length), key, buffer.slice(0, 16))
|
return aesDecryptWithIV(buffer.slice(16, buffer.length), key, buffer.slice(0, 16))
|
||||||
@@ -65,6 +69,7 @@ export function randomBytes(length) {
|
|||||||
return Crypto.randomBytes(length)
|
return Crypto.randomBytes(length)
|
||||||
}
|
}
|
||||||
export const createTimeout = (timeout) => new Promise(resolve => setTimeout(resolve, timeout))
|
export const createTimeout = (timeout) => new Promise(resolve => setTimeout(resolve, timeout))
|
||||||
|
|
||||||
export async function promiseTimeout<T>(ms: number, promise: Promise<T>) {
|
export async function promiseTimeout<T>(ms: number, promise: Promise<T>) {
|
||||||
if (!ms) return promise
|
if (!ms) return promise
|
||||||
// Create a promise that rejects in <ms> milliseconds
|
// Create a promise that rejects in <ms> milliseconds
|
||||||
@@ -139,4 +144,151 @@ export function decryptWA (message: string | Buffer, macKey: Buffer, encKey: Buf
|
|||||||
json = decoder.read(decrypted) // decode the binary message into a JSON array
|
json = decoder.read(decrypted) // decode the binary message into a JSON array
|
||||||
}
|
}
|
||||||
return [messageTag, json, tags]
|
return [messageTag, json, tags]
|
||||||
|
}
|
||||||
|
/** generates all the keys required to encrypt/decrypt & sign a media message */
|
||||||
|
export function getMediaKeys(buffer, mediaType: MessageType) {
|
||||||
|
if (typeof buffer === 'string') {
|
||||||
|
buffer = Buffer.from (buffer.replace('data:;base64,', ''), 'base64')
|
||||||
|
}
|
||||||
|
// expand using HKDF to 112 bytes, also pass in the relevant app info
|
||||||
|
const expandedMediaKey = hkdf(buffer, 112, HKDFInfoKeys[mediaType])
|
||||||
|
return {
|
||||||
|
iv: expandedMediaKey.slice(0, 16),
|
||||||
|
cipherKey: expandedMediaKey.slice(16, 48),
|
||||||
|
macKey: expandedMediaKey.slice(48, 80),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Extracts video thumb using FFMPEG */
|
||||||
|
const extractVideoThumb = async (
|
||||||
|
path: string,
|
||||||
|
destPath: string,
|
||||||
|
time: string,
|
||||||
|
size: { width: number; height: number },
|
||||||
|
) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const cmd = `ffmpeg -ss ${time} -i ${path} -y -s ${size.width}x${size.height} -vframes 1 -f image2 ${destPath}`
|
||||||
|
exec(cmd, (err) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
else resolve()
|
||||||
|
})
|
||||||
|
}) as Promise<void>
|
||||||
|
|
||||||
|
export const compressImage = async (buffer: Buffer) => {
|
||||||
|
const jimp = await Jimp.read (buffer)
|
||||||
|
return jimp.resize(48, 48).getBufferAsync (Jimp.MIME_JPEG)
|
||||||
|
}
|
||||||
|
export const generateProfilePicture = async (buffer: Buffer) => {
|
||||||
|
const jimp = await Jimp.read (buffer)
|
||||||
|
const min = Math.min(jimp.getWidth (), jimp.getHeight ())
|
||||||
|
const cropped = jimp.crop (0, 0, min, min)
|
||||||
|
return {
|
||||||
|
img: await cropped.resize(640, 640).getBufferAsync (Jimp.MIME_JPEG),
|
||||||
|
preview: await cropped.resize(96, 96).getBufferAsync (Jimp.MIME_JPEG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** generates a thumbnail for a given media, if required */
|
||||||
|
export async function generateThumbnail(buffer: Buffer, mediaType: MessageType, info: MessageOptions) {
|
||||||
|
if (info.thumbnail === null || info.thumbnail) {
|
||||||
|
// don't do anything if the thumbnail is already provided, or is null
|
||||||
|
if (mediaType === MessageType.audio) {
|
||||||
|
throw new Error('audio messages cannot have thumbnails')
|
||||||
|
}
|
||||||
|
} else if (mediaType === MessageType.image || mediaType === MessageType.sticker) {
|
||||||
|
const buff = await compressImage (buffer)
|
||||||
|
info.thumbnail = buff.toString('base64')
|
||||||
|
} else if (mediaType === MessageType.video) {
|
||||||
|
const filename = './' + randomBytes(5).toString('hex') + '.mp4'
|
||||||
|
const imgFilename = filename + '.jpg'
|
||||||
|
await fs.writeFile(filename, buffer)
|
||||||
|
try {
|
||||||
|
await extractVideoThumb(filename, imgFilename, '00:00:00', { width: 48, height: 48 })
|
||||||
|
const buff = await fs.readFile(imgFilename)
|
||||||
|
info.thumbnail = buff.toString('base64')
|
||||||
|
await fs.unlink(imgFilename)
|
||||||
|
} catch (err) {
|
||||||
|
console.log('could not generate video thumb: ' + err)
|
||||||
|
}
|
||||||
|
await fs.unlink(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode a media message (video, image, document, audio) & return decrypted buffer
|
||||||
|
* @param message the media message you want to decode
|
||||||
|
*/
|
||||||
|
export async function decodeMediaMessageBuffer(message: WAMessageContent, fetchHeaders: {[k: string]: string} = {}) {
|
||||||
|
/*
|
||||||
|
One can infer media type from the key in the message
|
||||||
|
it is usually written as [mediaType]Message. Eg. imageMessage, audioMessage etc.
|
||||||
|
*/
|
||||||
|
const type = Object.keys(message)[0] as MessageType
|
||||||
|
if (!type) {
|
||||||
|
throw new BaileysError('unknown message type', message)
|
||||||
|
}
|
||||||
|
if (type === MessageType.text || type === MessageType.extendedText) {
|
||||||
|
throw new BaileysError('cannot decode text message', message)
|
||||||
|
}
|
||||||
|
if (type === MessageType.location || type === MessageType.liveLocation) {
|
||||||
|
return new Buffer(message[type].jpegThumbnail)
|
||||||
|
}
|
||||||
|
let messageContent: WAMessageProto.IVideoMessage | WAMessageProto.IImageMessage | WAMessageProto.IAudioMessage | WAMessageProto.IDocumentMessage
|
||||||
|
if (message.productMessage) {
|
||||||
|
const product = message.productMessage.product?.productImage
|
||||||
|
if (!product) throw new BaileysError ('product has no image', message)
|
||||||
|
messageContent = product
|
||||||
|
} else {
|
||||||
|
messageContent = message[type]
|
||||||
|
}
|
||||||
|
|
||||||
|
// download the message
|
||||||
|
const headers = { Origin: 'https://web.whatsapp.com' }
|
||||||
|
const fetched = await fetch(messageContent.url, { headers })
|
||||||
|
const buffer = await fetched.buffer()
|
||||||
|
|
||||||
|
if (buffer.length <= 10) {
|
||||||
|
throw new BaileysError ('Empty buffer returned. File has possibly been deleted from WA servers. Run `client.updateMediaMessage()` to refresh the url', {status: 404})
|
||||||
|
}
|
||||||
|
|
||||||
|
const decryptedMedia = (type: MessageType) => {
|
||||||
|
// get the keys to decrypt the message
|
||||||
|
const mediaKeys = getMediaKeys(messageContent.mediaKey, type) //getMediaKeys(Buffer.from(messageContent.mediaKey, 'base64'), type)
|
||||||
|
// first part is actual file
|
||||||
|
const file = buffer.slice(0, buffer.length - 10)
|
||||||
|
// last 10 bytes is HMAC sign of file
|
||||||
|
const mac = buffer.slice(buffer.length - 10, buffer.length)
|
||||||
|
// sign IV+file & check for match with mac
|
||||||
|
const testBuff = Buffer.concat([mediaKeys.iv, file])
|
||||||
|
const sign = hmacSign(testBuff, mediaKeys.macKey).slice(0, 10)
|
||||||
|
// our sign should equal the mac
|
||||||
|
if (!sign.equals(mac)) throw new Error()
|
||||||
|
|
||||||
|
return aesDecryptWithIV(file, mediaKeys.cipherKey, mediaKeys.iv) // decrypt media
|
||||||
|
}
|
||||||
|
const allTypes = [type, ...Object.keys(HKDFInfoKeys)]
|
||||||
|
for (let i = 0; i < allTypes.length;i++) {
|
||||||
|
try {
|
||||||
|
const decrypted = decryptedMedia (allTypes[i] as MessageType)
|
||||||
|
|
||||||
|
if (i > 0) { console.log (`decryption of ${type} media with HKDF key of ${allTypes[i]}`) }
|
||||||
|
return decrypted
|
||||||
|
} catch {
|
||||||
|
if (i === 0) { console.log (`decryption of ${type} media with original HKDF key failed`) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new BaileysError('Decryption failed, HMAC sign does not match', {status: 400})
|
||||||
|
}
|
||||||
|
export function extensionForMediaMessage(message: WAMessageContent) {
|
||||||
|
const getExtension = (mimetype: string) => mimetype.split(';')[0].split('/')[1]
|
||||||
|
const type = Object.keys(message)[0] as MessageType
|
||||||
|
let extension: string
|
||||||
|
if (type === MessageType.location || type === MessageType.liveLocation || type === MessageType.product) {
|
||||||
|
extension = '.jpeg'
|
||||||
|
} else {
|
||||||
|
const messageContent = message[type] as
|
||||||
|
| WAMessageProto.VideoMessage
|
||||||
|
| WAMessageProto.ImageMessage
|
||||||
|
| WAMessageProto.AudioMessage
|
||||||
|
| WAMessageProto.DocumentMessage
|
||||||
|
extension = getExtension (messageContent.mimetype)
|
||||||
|
}
|
||||||
|
return extension
|
||||||
}
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
import WAConnection from './Connect'
|
export * from './6.Groups'
|
||||||
export default WAConnection
|
export * from './Utils'
|
||||||
|
export * from './Constants'
|
||||||
Reference in New Issue
Block a user