mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
Typescript update
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ output.csv
|
|||||||
package-lock.json
|
package-lock.json
|
||||||
*/.DS_Store
|
*/.DS_Store
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.env
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default class Encoder {
|
|||||||
this.pushBytes([(value >> 16) & 0x0f, (value >> 8) & 0xff, value & 0xff])
|
this.pushBytes([(value >> 16) & 0x0f, (value >> 8) & 0xff, value & 0xff])
|
||||||
}
|
}
|
||||||
pushBytes(bytes: Uint8Array | Array<number>) {
|
pushBytes(bytes: Uint8Array | Array<number>) {
|
||||||
this.data.push(...bytes)
|
this.data.push.apply(this.data, bytes)
|
||||||
}
|
}
|
||||||
pushString(str: string) {
|
pushString(str: string) {
|
||||||
const bytes = new TextEncoder().encode(str)
|
const bytes = new TextEncoder().encode(str)
|
||||||
|
|||||||
@@ -1,53 +1,40 @@
|
|||||||
import {
|
import {
|
||||||
WAClient,
|
WAClient,
|
||||||
AuthenticationCredentialsBase64,
|
|
||||||
getNotificationType,
|
getNotificationType,
|
||||||
MessageType,
|
MessageType,
|
||||||
decodeMediaMessage,
|
decodeMediaMessage,
|
||||||
Presence,
|
Presence,
|
||||||
MessageOptions,
|
MessageOptions,
|
||||||
Mimetype,
|
Mimetype,
|
||||||
|
WALocationMessage,
|
||||||
|
MessageLogLevel,
|
||||||
} from '../WAClient/WAClient'
|
} from '../WAClient/WAClient'
|
||||||
import fs from 'fs'
|
import * as fs from 'fs'
|
||||||
|
|
||||||
async function example() {
|
async function example() {
|
||||||
const client = new WAClient() // instantiate
|
const client = new WAClient() // instantiate
|
||||||
client.autoReconnect = true // auto reconnect on disconnect
|
client.autoReconnect = true // auto reconnect on disconnect
|
||||||
client.logUnhandledMessages = false // set to true to see what kind of stuff you can implement
|
client.logLevel = MessageLogLevel.none // set to unhandled to see what kind of stuff you can implement
|
||||||
|
|
||||||
let authInfo: AuthenticationCredentialsBase64 = null
|
// connect or timeout in 20 seconds (loads the auth file credentials if present)
|
||||||
try {
|
const [user, chats, contacts, unread] = await client.connect('./auth_info.json', 20 * 1000)
|
||||||
const file = fs.readFileSync('auth_info.json') // load a closed session back if it exists
|
|
||||||
authInfo = JSON.parse(file)
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
// connect or timeout in 20 seconds
|
|
||||||
const [user, chats, contacts, unread] = await client.connect(authInfo, 20 * 1000)
|
|
||||||
|
|
||||||
console.log('oh hello ' + user.name + ' (' + user.id + ')')
|
console.log('oh hello ' + user.name + ' (' + user.id + ')')
|
||||||
console.log('you have ' + unread.length + ' unread messages')
|
console.log('you have ' + unread.length + ' unread messages')
|
||||||
console.log('you have ' + chats.length + ' chats & ' + contacts.length + ' contacts')
|
console.log('you have ' + chats.length + ' chats & ' + contacts.length + ' contacts')
|
||||||
|
|
||||||
authInfo = client.base64EncodedAuthInfo() // get all the auth info we need to restore this session
|
const authInfo = client.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))
|
client.setOnPresenceUpdate(json => console.log(json.id + ' presence is ' + json.type))
|
||||||
client.setOnMessageStatusChange((json) => {
|
client.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(
|
console.log(`${json.to}${participant} acknlowledged message(s) ${json.ids} as ${json.type}`)
|
||||||
json.to +
|
|
||||||
participant +
|
|
||||||
' acknowledged message(s) ' +
|
|
||||||
json.ids +
|
|
||||||
' as ' +
|
|
||||||
json.type +
|
|
||||||
' at ' +
|
|
||||||
json.timestamp,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
client.setOnUnreadMessage(async (m) => {
|
// set to false to NOT relay your own sent messages
|
||||||
|
client.setOnUnreadMessage(true, async (m) => {
|
||||||
const [notificationType, messageType] = getNotificationType(m) // get what type of notification it is -- message, group add notification etc.
|
const [notificationType, messageType] = getNotificationType(m) // get what type of notification it is -- message, group add notification etc.
|
||||||
console.log('got notification of type: ' + notificationType)
|
console.log('got notification of type: ' + notificationType)
|
||||||
|
|
||||||
@@ -74,32 +61,20 @@ async function example() {
|
|||||||
const contact = m.message.contactMessage
|
const contact = m.message.contactMessage
|
||||||
console.log(sender + ' sent contact (' + contact.displayName + '): ' + contact.vcard)
|
console.log(sender + ' sent contact (' + contact.displayName + '): ' + contact.vcard)
|
||||||
} else if (messageType === MessageType.location || messageType === MessageType.liveLocation) {
|
} else if (messageType === MessageType.location || messageType === MessageType.liveLocation) {
|
||||||
const locMessage = m.message[messageType]
|
const locMessage = m.message[messageType] as WALocationMessage
|
||||||
console.log(
|
console.log(`${sender} sent location (lat: ${locMessage.degreesLatitude}, long: ${locMessage.degreesLongitude})`)
|
||||||
sender +
|
|
||||||
' sent location (lat: ' +
|
decodeMediaMessage(m.message, './Media/loc_thumb_in_' + m.key.id) // save location thumbnail
|
||||||
locMessage.degreesLatitude +
|
|
||||||
', long: ' +
|
|
||||||
locMessage.degreesLongitude +
|
|
||||||
'), saving thumbnail...',
|
|
||||||
)
|
|
||||||
decodeMediaMessage(m.message, 'loc_thumb_in_' + m.key.id)
|
|
||||||
|
|
||||||
if (messageType === MessageType.liveLocation) {
|
if (messageType === MessageType.liveLocation) {
|
||||||
console.log(
|
console.log(`${sender} sent live location for duration: ${m.duration/60}`)
|
||||||
sender +
|
|
||||||
' sent live location for duration: ' +
|
|
||||||
m.duration / 60 +
|
|
||||||
' minutes, seq number: ' +
|
|
||||||
locMessage.sequenceNumber,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if it is a media (audio, image, video) message
|
// if it is a media (audio, image, video) message
|
||||||
// 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_in_' + m.key.id)
|
const savedFile = await decodeMediaMessage(m.message, './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)
|
||||||
@@ -124,21 +99,22 @@ async function example() {
|
|||||||
content = { degreesLatitude: 32.123123, degreesLongitude: 12.12123123 }
|
content = { degreesLatitude: 32.123123, degreesLongitude: 12.12123123 }
|
||||||
type = MessageType.location
|
type = MessageType.location
|
||||||
} else {
|
} else {
|
||||||
content = fs.readFileSync('example/ma_gif.mp4') // load the gif
|
content = fs.readFileSync('./Media/ma_gif.mp4') // load the gif
|
||||||
options.mimetype = Mimetype.gif
|
options.mimetype = Mimetype.gif
|
||||||
|
type = MessageType.video
|
||||||
}
|
}
|
||||||
const response = await client.sendMessage(m.key.remoteJid, content, type, options)
|
const response = await client.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.messageID + "' successfully: " + (response.status === 200))
|
||||||
}, 3 * 1000)
|
}, 3 * 1000)
|
||||||
}, true) // set to false to not relay your own sent messages
|
})
|
||||||
|
|
||||||
/* example of custom functionality for tracking battery */
|
/* example of custom functionality for tracking battery */
|
||||||
client.registerCallback(['action', null, 'battery'], (json) => {
|
client.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((err) => console.log('disconnected unexpectedly: ' + err))
|
client.setOnUnexpectedDisconnect(err => console.log('disconnected unexpectedly: ' + err))
|
||||||
}
|
}
|
||||||
|
|
||||||
example().catch((err) => console.log(`encountered error: ${err}`))
|
example().catch((err) => console.log(`encountered error: ${err}`))
|
||||||
|
|||||||
2
Media/.gitkeep
Normal file
2
Media/.gitkeep
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ma_gif.mp4
|
||||||
|
meme.jpeg
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
WAGroupCreateResponse,
|
WAGroupCreateResponse,
|
||||||
WAGroupMetadata,
|
WAGroupMetadata,
|
||||||
WAGroupModification,
|
WAGroupModification,
|
||||||
|
MessageLogLevel,
|
||||||
} from '../WAConnection/Constants'
|
} from '../WAConnection/Constants'
|
||||||
import { generateMessageTag } from '../WAConnection/Utils'
|
import { generateMessageTag } from '../WAConnection/Utils'
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ export default class WhatsAppWebBase extends WAConnection {
|
|||||||
if (!message.key.fromMe || callbackOnMyMessages) {
|
if (!message.key.fromMe || callbackOnMyMessages) {
|
||||||
// if this message was sent to us, notify
|
// if this message was sent to us, notify
|
||||||
callback(message as WAMessage)
|
callback(message as WAMessage)
|
||||||
} else if (this.logUnhandledMessages) {
|
} else if (this.logLevel >= MessageLogLevel.unhandled) {
|
||||||
this.log(`[Unhandled] message - ${JSON.stringify(message)}`)
|
this.log(`[Unhandled] message - ${JSON.stringify(message)}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
import { WAClient } from './WAClient'
|
import { WAClient } from './WAClient'
|
||||||
import { MessageType, MessageOptions, Mimetype, Presence } from './Constants'
|
import { MessageType, MessageOptions, Mimetype, Presence } from './Constants'
|
||||||
import fs from 'fs'
|
import * as fs from 'fs'
|
||||||
|
import * as assert from 'assert'
|
||||||
|
|
||||||
import assert from 'assert'
|
|
||||||
import { decodeMediaMessage } from './Utils'
|
import { decodeMediaMessage } from './Utils'
|
||||||
import { promiseTimeout } from '../WAConnection/Utils'
|
import { promiseTimeout } from '../WAConnection/Utils'
|
||||||
|
|
||||||
const testJid = '919646328797@s.whatsapp.net'
|
require ('dotenv').config () // dotenv to load test jid
|
||||||
const createTimeout = (timeout) => new Promise((resolve) => setTimeout(resolve, timeout))
|
const testJid = process.env.TEST_JID // set TEST_JID=xyz@s.whatsapp.net in a .env file in the root directory
|
||||||
|
|
||||||
|
const createTimeout = (timeout) => new Promise(resolve => setTimeout(resolve, timeout))
|
||||||
|
|
||||||
async function sendAndRetreiveMessage(client: WAClient, content, type: MessageType, options: MessageOptions = {}) {
|
async function sendAndRetreiveMessage(client: WAClient, content, type: MessageType, options: MessageOptions = {}) {
|
||||||
const response = await client.sendMessage(testJid, content, type, options)
|
const response = await client.sendMessage(testJid, content, type, options)
|
||||||
assert.equal(response.status, 200)
|
assert.strictEqual(response.status, 200)
|
||||||
const messages = await client.loadConversation(testJid, 1, null, true)
|
const messages = await client.loadConversation(testJid, 1, null, true)
|
||||||
assert.equal(messages[0].key.id, response.messageID)
|
assert.strictEqual(messages[0].key.id, response.messageID)
|
||||||
return messages[0]
|
return messages[0]
|
||||||
}
|
}
|
||||||
function WAClientTest(name: string, func: (client: WAClient) => void) {
|
function WAClientTest(name: string, func: (client: WAClient) => void) {
|
||||||
describe(name, () => {
|
describe(name, () => {
|
||||||
const client = new WAClient()
|
const client = new WAClient()
|
||||||
|
console.log (`test jid: ${testJid}`)
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const file = './auth_info.json'
|
const file = './auth_info.json'
|
||||||
await client.connectSlim(file)
|
await client.connectSlim(file)
|
||||||
@@ -31,14 +34,14 @@ function WAClientTest(name: string, func: (client: WAClient) => void) {
|
|||||||
WAClientTest('Messages', (client) => {
|
WAClientTest('Messages', (client) => {
|
||||||
it('should send a text message', async () => {
|
it('should send a text message', async () => {
|
||||||
const message = await sendAndRetreiveMessage(client, 'hello fren', MessageType.text)
|
const message = await sendAndRetreiveMessage(client, 'hello fren', MessageType.text)
|
||||||
assert.equal(message.message.conversation, 'hello fren')
|
assert.strictEqual(message.message.conversation, 'hello fren')
|
||||||
})
|
})
|
||||||
it('should quote a message', async () => {
|
it('should quote a message', async () => {
|
||||||
const messages = await client.loadConversation(testJid, 2)
|
const messages = await client.loadConversation(testJid, 2)
|
||||||
const message = await sendAndRetreiveMessage(client, 'hello fren 2', MessageType.extendedText, {
|
const message = await sendAndRetreiveMessage(client, 'hello fren 2', MessageType.extendedText, {
|
||||||
quoted: messages[0],
|
quoted: messages[0],
|
||||||
})
|
})
|
||||||
assert.equal(message.message.extendedTextMessage.contextInfo.stanzaId, messages[0].key.id)
|
assert.strictEqual(message.message.extendedTextMessage.contextInfo.stanzaId, messages[0].key.id)
|
||||||
})
|
})
|
||||||
it('should send a gif', async () => {
|
it('should send a gif', async () => {
|
||||||
const content = fs.readFileSync('./Media/ma_gif.mp4')
|
const content = fs.readFileSync('./Media/ma_gif.mp4')
|
||||||
@@ -56,7 +59,7 @@ WAClientTest('Messages', (client) => {
|
|||||||
const content = fs.readFileSync('./Media/meme.jpeg')
|
const content = fs.readFileSync('./Media/meme.jpeg')
|
||||||
const message = await sendAndRetreiveMessage(client, content, MessageType.image, { quoted: messages[0] })
|
const message = await sendAndRetreiveMessage(client, content, MessageType.image, { quoted: messages[0] })
|
||||||
const file = await decodeMediaMessage(message.message, './Media/received_img')
|
const file = await decodeMediaMessage(message.message, './Media/received_img')
|
||||||
assert.equal(message.message.imageMessage.contextInfo.stanzaId, messages[0].key.id)
|
assert.strictEqual(message.message.imageMessage.contextInfo.stanzaId, messages[0].key.id)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
WAClientTest('Presence', (client) => {
|
WAClientTest('Presence', (client) => {
|
||||||
@@ -64,7 +67,7 @@ WAClientTest('Presence', (client) => {
|
|||||||
const presences = Object.values(Presence)
|
const presences = Object.values(Presence)
|
||||||
for (const i in presences) {
|
for (const i in presences) {
|
||||||
const response = await client.updatePresence(testJid, presences[i])
|
const response = await client.updatePresence(testJid, presences[i])
|
||||||
assert.equal(response.status, 200)
|
assert.strictEqual(response.status, 200)
|
||||||
|
|
||||||
await createTimeout(1500)
|
await createTimeout(1500)
|
||||||
}
|
}
|
||||||
@@ -73,15 +76,15 @@ WAClientTest('Presence', (client) => {
|
|||||||
WAClientTest('Misc', (client) => {
|
WAClientTest('Misc', (client) => {
|
||||||
it('should tell if someone has an account on WhatsApp', async () => {
|
it('should tell if someone has an account on WhatsApp', async () => {
|
||||||
const response = await client.isOnWhatsApp(testJid)
|
const response = await client.isOnWhatsApp(testJid)
|
||||||
assert.equal(response, true)
|
assert.strictEqual(response, true)
|
||||||
|
|
||||||
const responseFail = await client.isOnWhatsApp('abcd@s.whatsapp.net')
|
const responseFail = await client.isOnWhatsApp('abcd@s.whatsapp.net')
|
||||||
assert.equal(responseFail, false)
|
assert.strictEqual(responseFail, false)
|
||||||
})
|
})
|
||||||
it('should return the status', async () => {
|
it('should return the status', async () => {
|
||||||
const response = await client.getStatus(testJid)
|
const response = await client.getStatus(testJid)
|
||||||
assert.ok(response.status)
|
assert.ok(response.status)
|
||||||
assert.equal(typeof response.status, 'string')
|
assert.strictEqual(typeof response.status, 'string')
|
||||||
})
|
})
|
||||||
it('should return the profile picture', async () => {
|
it('should return the profile picture', async () => {
|
||||||
const response = await client.getProfilePicture(testJid)
|
const response = await client.getProfilePicture(testJid)
|
||||||
@@ -93,38 +96,38 @@ WAClientTest('Groups', (client) => {
|
|||||||
let gid: string
|
let gid: string
|
||||||
it('should create a group', async () => {
|
it('should create a group', async () => {
|
||||||
const response = await client.groupCreate('Cool Test Group', [testJid])
|
const response = await client.groupCreate('Cool Test Group', [testJid])
|
||||||
assert.equal(response.status, 200)
|
assert.strictEqual(response.status, 200)
|
||||||
gid = response.gid
|
gid = response.gid
|
||||||
console.log('created group: ' + gid)
|
console.log('created group: ' + gid)
|
||||||
})
|
})
|
||||||
it('should retreive group invite code', async () => {
|
it('should retreive group invite code', async () => {
|
||||||
const code = await client.groupInviteCode(gid)
|
const code = await client.groupInviteCode(gid)
|
||||||
assert.ok(code)
|
assert.ok(code)
|
||||||
assert.equal(typeof code, 'string')
|
assert.strictEqual(typeof code, 'string')
|
||||||
})
|
})
|
||||||
it('should retreive group metadata', async () => {
|
it('should retreive group metadata', async () => {
|
||||||
const metadata = await client.groupMetadata(gid)
|
const metadata = await client.groupMetadata(gid)
|
||||||
assert.equal(metadata.id, gid)
|
assert.strictEqual(metadata.id, gid)
|
||||||
assert.equal(metadata.participants.filter((obj) => obj.id.split('@')[0] === testJid.split('@')[0]).length, 1)
|
assert.strictEqual(metadata.participants.filter((obj) => obj.id.split('@')[0] === testJid.split('@')[0]).length, 1)
|
||||||
})
|
})
|
||||||
it('should send a message on the group', async () => {
|
it('should send a message on the group', async () => {
|
||||||
const r = await client.sendMessage(gid, 'hello', MessageType.text)
|
const r = await client.sendMessage(gid, 'hello', MessageType.text)
|
||||||
assert.equal(r.status, 200)
|
assert.strictEqual(r.status, 200)
|
||||||
})
|
})
|
||||||
it('should update the subject', async () => {
|
it('should update the subject', async () => {
|
||||||
const subject = 'V Cool Title'
|
const subject = 'V Cool Title'
|
||||||
const r = await client.groupUpdateSubject(gid, subject)
|
const r = await client.groupUpdateSubject(gid, subject)
|
||||||
assert.equal(r.status, 200)
|
assert.strictEqual(r.status, 200)
|
||||||
|
|
||||||
const metadata = await client.groupMetadata(gid)
|
const metadata = await client.groupMetadata(gid)
|
||||||
assert.equal(metadata.subject, subject)
|
assert.strictEqual(metadata.subject, subject)
|
||||||
})
|
})
|
||||||
it('should remove someone from a group', async () => {
|
it('should remove someone from a group', async () => {
|
||||||
await client.groupRemove(gid, [testJid])
|
await client.groupRemove(gid, [testJid])
|
||||||
})
|
})
|
||||||
it('should leave the group', async () => {
|
it('should leave the group', async () => {
|
||||||
const response = await client.groupLeave(gid)
|
const response = await client.groupLeave(gid)
|
||||||
assert.equal(response.status, 200)
|
assert.strictEqual(response.status, 200)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
WAClientTest('Events', (client) => {
|
WAClientTest('Events', (client) => {
|
||||||
@@ -146,7 +149,12 @@ WAClientTest('Events', (client) => {
|
|||||||
console.log (presence)
|
console.log (presence)
|
||||||
})
|
})
|
||||||
const response = await client.requestPresenceUpdate (client.userMetaData)
|
const response = await client.requestPresenceUpdate (client.userMetaData)
|
||||||
assert.equal (response.status, 200)
|
assert.strictEqual (response.status, 200)
|
||||||
await createTimeout (25000)
|
await createTimeout (25000)
|
||||||
})*/
|
})*/
|
||||||
})
|
})
|
||||||
|
/*WAClientTest ('Testz', client => {
|
||||||
|
it ('should work', async () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
})*/
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { MessageType, HKDFInfoKeys, MessageOptions, MessageStubTypes } from './Constants'
|
import { MessageType, HKDFInfoKeys, MessageOptions, MessageStubTypes } from './Constants'
|
||||||
import sharp from 'sharp'
|
import * as sharp from 'sharp'
|
||||||
import fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
import { WAMessage, WAMessageContent } from '../WAConnection/Constants'
|
import { WAMessage, WAMessageContent } from '../WAConnection/Constants'
|
||||||
import { hmacSign, aesDecryptWithIV, hkdf } from '../WAConnection/Utils'
|
import { hmacSign, aesDecryptWithIV, hkdf } from '../WAConnection/Utils'
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import WS from 'ws'
|
import * as QR from 'qrcode-terminal'
|
||||||
import QR from 'qrcode-terminal'
|
import * as fs from 'fs'
|
||||||
import fs from 'fs'
|
import * as WS from 'ws'
|
||||||
import * as Utils from './Utils'
|
import * as Utils from './Utils'
|
||||||
import Encoder from '../Binary/Encoder'
|
import Encoder from '../Binary/Encoder'
|
||||||
import Decoder from '../Binary/Decoder'
|
import Decoder from '../Binary/Decoder'
|
||||||
import { AuthenticationCredentials, UserMetaData, WANode, AuthenticationCredentialsBase64, WATag } from './Constants'
|
import { AuthenticationCredentials, UserMetaData, WANode, AuthenticationCredentialsBase64, WATag, MessageLogLevel } 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 */
|
||||||
const generateQRCode = function ([ref, publicKey, clientID]) {
|
const generateQRCode = function ([ref, publicKey, clientID]) {
|
||||||
@@ -23,7 +24,7 @@ export default class WAConnectionBase {
|
|||||||
autoReconnect = true
|
autoReconnect = true
|
||||||
lastSeen: Date = null
|
lastSeen: Date = null
|
||||||
/** Log messages that are not handled, so you can debug & see what custom stuff you can implement */
|
/** Log messages that are not handled, so you can debug & see what custom stuff you can implement */
|
||||||
logUnhandledMessages = false
|
logLevel: MessageLogLevel = MessageLogLevel.none
|
||||||
/** Data structure of tokens & IDs used to establish one's identiy to WhatsApp Web */
|
/** Data structure of tokens & IDs used to establish one's identiy to WhatsApp Web */
|
||||||
protected authInfo: AuthenticationCredentials = {
|
protected authInfo: AuthenticationCredentials = {
|
||||||
clientID: null,
|
clientID: null,
|
||||||
@@ -259,6 +260,6 @@ export default class WAConnectionBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected log(text) {
|
protected log(text) {
|
||||||
console.log(`[Baileys] ${text}`)
|
console.log(`[Baileys][${new Date().toLocaleString()}] ${text}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import WS from 'ws'
|
import * as WS from 'ws'
|
||||||
|
|
||||||
import * as Utils from './Utils'
|
import * as Utils from './Utils'
|
||||||
import { AuthenticationCredentialsBase64, UserMetaData, WAMessage, WAChat, WAContact } from './Constants'
|
import { AuthenticationCredentialsBase64, UserMetaData, WAMessage, WAChat, WAContact, MessageLogLevel } from './Constants'
|
||||||
import WAConnectionValidator from './Validation'
|
import WAConnectionValidator from './Validation'
|
||||||
|
|
||||||
export default class WAConnectionConnector extends WAConnectionValidator {
|
export default class WAConnectionConnector extends WAConnectionValidator {
|
||||||
@@ -13,7 +12,7 @@ export default class WAConnectionConnector extends WAConnectionValidator {
|
|||||||
*/
|
*/
|
||||||
async connect(authInfo: AuthenticationCredentialsBase64 | string = null, timeoutMs: number = null) {
|
async connect(authInfo: AuthenticationCredentialsBase64 | string = null, timeoutMs: number = null) {
|
||||||
const userInfo = await this.connectSlim(authInfo, timeoutMs)
|
const userInfo = await this.connectSlim(authInfo, timeoutMs)
|
||||||
const chats = await this.receiveChatsAndContacts()
|
const chats = await this.receiveChatsAndContacts(timeoutMs)
|
||||||
return [userInfo, ...chats] as [UserMetaData, WAChat[], WAContact[], WAMessage[]]
|
return [userInfo, ...chats] as [UserMetaData, WAChat[], WAContact[], WAMessage[]]
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -52,8 +51,8 @@ export default class WAConnectionConnector extends WAConnectionValidator {
|
|||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
promise = timeoutMs ? Utils.promiseTimeout(timeoutMs, promise) : promise
|
promise = Utils.promiseTimeout(timeoutMs, promise)
|
||||||
return promise.catch((err) => {
|
return promise.catch(err => {
|
||||||
this.close()
|
this.close()
|
||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
@@ -61,25 +60,31 @@ export default class WAConnectionConnector extends WAConnectionValidator {
|
|||||||
/**
|
/**
|
||||||
* Sets up callbacks to receive chats, contacts & unread messages.
|
* Sets up callbacks to receive chats, contacts & unread messages.
|
||||||
* Must be called immediately after connect
|
* Must be called immediately after connect
|
||||||
* [chats, contacts, unreadMessages]
|
* @returns [chats, contacts, unreadMessages]
|
||||||
*/
|
*/
|
||||||
receiveChatsAndContacts() {
|
async receiveChatsAndContacts(timeoutMs: number = null) {
|
||||||
const chats: Array<WAChat> = []
|
let chats: Array<WAChat> = []
|
||||||
let contacts: Array<WAContact> = []
|
let contacts: Array<WAContact> = []
|
||||||
const unreadMessages: Array<WAMessage> = []
|
let unreadMessages: Array<WAMessage> = []
|
||||||
const unreadMap = {}
|
let unreadMap: Record<string, number> = {}
|
||||||
|
|
||||||
let encounteredAddBefore = false
|
let receivedContacts = false
|
||||||
|
let receivedMessages = false
|
||||||
let convoResolve
|
let convoResolve
|
||||||
|
|
||||||
this.log('waiting for chats & contacts') // wait for the message with chats
|
this.log('waiting for chats & contacts') // wait for the message with chats
|
||||||
const waitForConvos = () =>
|
const waitForConvos = () =>
|
||||||
new Promise((resolve, _) => {
|
new Promise(resolve => {
|
||||||
convoResolve = resolve
|
convoResolve = () => {
|
||||||
const chatUpdate = (json) => {
|
// de-register the callbacks, so that they don't get called again
|
||||||
|
this.deregisterCallback(['action', 'add:last'])
|
||||||
|
this.deregisterCallback(['action', 'add:before'])
|
||||||
|
this.deregisterCallback(['action', 'add:unread'])
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
const chatUpdate = json => {
|
||||||
|
receivedMessages = true
|
||||||
const isLast = json[1].last
|
const isLast = json[1].last
|
||||||
encounteredAddBefore = json[1].add === 'before' ? true : encounteredAddBefore
|
|
||||||
|
|
||||||
json = json[2]
|
json = json[2]
|
||||||
if (json) {
|
if (json) {
|
||||||
for (let k = json.length - 1; k >= 0; k--) {
|
for (let k = json.length - 1; k >= 0; k--) {
|
||||||
@@ -92,12 +97,8 @@ export default class WAConnectionConnector extends WAConnectionValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isLast) {
|
if (isLast && receivedContacts) { // if received contacts before messages
|
||||||
// de-register the callbacks, so that they don't get called again
|
convoResolve ()
|
||||||
this.deregisterCallback(['action', 'add:last'])
|
|
||||||
this.deregisterCallback(['action', 'add:before'])
|
|
||||||
this.deregisterCallback(['action', 'add:unread'])
|
|
||||||
resolve()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// wait for actual messages to load, "last" is the most recent message, "before" contains prior messages
|
// wait for actual messages to load, "last" is the most recent message, "before" contains prior messages
|
||||||
@@ -105,22 +106,27 @@ export default class WAConnectionConnector extends WAConnectionValidator {
|
|||||||
this.registerCallback(['action', 'add:before'], chatUpdate)
|
this.registerCallback(['action', 'add:before'], chatUpdate)
|
||||||
this.registerCallback(['action', 'add:unread'], chatUpdate)
|
this.registerCallback(['action', 'add:unread'], chatUpdate)
|
||||||
})
|
})
|
||||||
const waitForChats = this.registerCallbackOneTime(['response', 'type:chat']).then((json) => {
|
const waitForChats = async () => {
|
||||||
json[2].forEach((chat) => {
|
const json = await this.registerCallbackOneTime(['response', 'type:chat'])
|
||||||
|
json[2].forEach(chat => {
|
||||||
chats.push(chat[1]) // chats data (log json to see what it looks like)
|
chats.push(chat[1]) // chats data (log json to see what it looks like)
|
||||||
// store the number of unread messages for each sender
|
// store the number of unread messages for each sender
|
||||||
unreadMap[chat[1].jid] = chat[1].count
|
unreadMap[chat[1].jid] = chat[1].count
|
||||||
})
|
})
|
||||||
if (chats && chats.length > 0) return waitForConvos()
|
if (chats.length > 0) return waitForConvos()
|
||||||
})
|
}
|
||||||
const waitForContacts = this.registerCallbackOneTime(['response', 'type:contacts']).then((json) => {
|
const waitForContacts = async () => {
|
||||||
contacts = json[2].map((item) => item[1])
|
const json = await this.registerCallbackOneTime(['response', 'type:contacts'])
|
||||||
// if no add:before messages are sent, and you receive contacts
|
contacts = json[2].map(item => item[1])
|
||||||
|
receivedContacts = true
|
||||||
|
// if you receive contacts after messages
|
||||||
// should probably resolve the promise
|
// should probably resolve the promise
|
||||||
if (!encounteredAddBefore && convoResolve) convoResolve()
|
if (receivedMessages) convoResolve()
|
||||||
})
|
}
|
||||||
// wait for the chats & contacts to load
|
// wait for the chats & contacts to load
|
||||||
return Promise.all([waitForChats, waitForContacts]).then(() => [chats, contacts, unreadMessages])
|
const promise = Promise.all([waitForChats(), waitForContacts()])
|
||||||
|
await Utils.promiseTimeout (timeoutMs, promise)
|
||||||
|
return [chats, contacts, unreadMessages] as [WAChat[], WAContact[], WAMessage[]]
|
||||||
}
|
}
|
||||||
private onMessageRecieved(message) {
|
private onMessageRecieved(message) {
|
||||||
if (message[0] === '!') {
|
if (message[0] === '!') {
|
||||||
@@ -170,6 +176,9 @@ export default class WAConnectionConnector extends WAConnectionValidator {
|
|||||||
// if we recieved a message that was encrypted but we don't have the keys, then there must be an error
|
// if we recieved a message that was encrypted but we don't have the keys, then there must be an error
|
||||||
throw [3, 'recieved encrypted message when auth creds not available', message]
|
throw [3, 'recieved encrypted message when auth creds not available', message]
|
||||||
}
|
}
|
||||||
|
if (this.logLevel === MessageLogLevel.all) {
|
||||||
|
this.log(messageTag + ', ' + JSON.stringify(json))
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
Check if this is a response to a message we sent
|
Check if this is a response to a message we sent
|
||||||
*/
|
*/
|
||||||
@@ -214,7 +223,7 @@ export default class WAConnectionConnector extends WAConnectionValidator {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.logUnhandledMessages) {
|
if (this.logLevel === MessageLogLevel.unhandled) {
|
||||||
this.log('[Unhandled] ' + messageTag + ', ' + JSON.stringify(json))
|
this.log('[Unhandled] ' + messageTag + ', ' + JSON.stringify(json))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { WA } from '../Binary/Constants'
|
import { WA } from '../Binary/Constants'
|
||||||
import { proto } from '../Binary/WAMessage'
|
import { proto } from '../Binary/WAMessage'
|
||||||
|
|
||||||
|
export enum MessageLogLevel {
|
||||||
|
none=0,
|
||||||
|
unhandled=1,
|
||||||
|
all=2
|
||||||
|
}
|
||||||
export interface AuthenticationCredentials {
|
export interface AuthenticationCredentials {
|
||||||
clientID: string
|
clientID: string
|
||||||
serverToken: string
|
serverToken: string
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import assert from 'assert'
|
import * as assert from 'assert'
|
||||||
import WAConnection from './WAConnection'
|
import WAConnection from './WAConnection'
|
||||||
import { AuthenticationCredentialsBase64 } from './Constants'
|
import { AuthenticationCredentialsBase64 } from './Constants'
|
||||||
|
|
||||||
@@ -22,17 +22,16 @@ describe('Test Connect', () => {
|
|||||||
it('should connect', async () => {
|
it('should connect', async () => {
|
||||||
console.log('please be ready to scan with your phone')
|
console.log('please be ready to scan with your phone')
|
||||||
const conn = new WAConnection()
|
const conn = new WAConnection()
|
||||||
await assert.doesNotReject(async () => conn.connectSlim(null), 'initial connection failed')
|
const user = await conn.connectSlim(null)
|
||||||
assert.ok(conn.userMetaData)
|
assert.ok(user)
|
||||||
assert.ok(conn.userMetaData.id)
|
assert.ok(user.id)
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
auth = conn.base64EncodedAuthInfo()
|
auth = conn.base64EncodedAuthInfo()
|
||||||
})
|
})
|
||||||
it('should reconnect', async () => {
|
it('should reconnect', async () => {
|
||||||
const conn = new WAConnection()
|
const conn = new WAConnection()
|
||||||
|
const [user, chats, contacts, unread] = await conn.connect(auth, 20*1000)
|
||||||
const [user, chats, contacts, unread] = await conn.connect('./auth_info.json', 20 * 1000)
|
|
||||||
|
|
||||||
assert.ok(user)
|
assert.ok(user)
|
||||||
assert.ok(user.id)
|
assert.ok(user.id)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Crypto from 'crypto'
|
import * as Crypto from 'crypto'
|
||||||
import HKDF from 'futoin-hkdf'
|
import * as HKDF from 'futoin-hkdf'
|
||||||
|
|
||||||
/** decrypt AES 256 CBC; where the IV is prefixed to the buffer */
|
/** decrypt AES 256 CBC; where the IV is prefixed to the buffer */
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ export function randomBytes(length) {
|
|||||||
return Crypto.randomBytes(length)
|
return Crypto.randomBytes(length)
|
||||||
}
|
}
|
||||||
export function promiseTimeout<T>(ms: number, promise: Promise<T>) {
|
export function promiseTimeout<T>(ms: number, promise: Promise<T>) {
|
||||||
|
if (!ms) { return promise }
|
||||||
// Create a promise that rejects in <ms> milliseconds
|
// Create a promise that rejects in <ms> milliseconds
|
||||||
const timeout = new Promise((_, reject) => {
|
const timeout = new Promise((_, reject) => {
|
||||||
const id = setTimeout(() => {
|
const id = setTimeout(() => {
|
||||||
|
|||||||
15
package-lock.json
generated
15
package-lock.json
generated
@@ -140,6 +140,15 @@
|
|||||||
"integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==",
|
"integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/ws": {
|
||||||
|
"version": "7.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.6.tgz",
|
||||||
|
"integrity": "sha512-Q07IrQUSNpr+cXU4E4LtkSIBPie5GLZyyMC1QtQYRLWz701+XcoVygGUZgvLqElq1nU4ICldMYPnexlBsg3dqQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@typescript-eslint/eslint-plugin": {
|
"@typescript-eslint/eslint-plugin": {
|
||||||
"version": "3.5.0",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.5.0.tgz",
|
||||||
@@ -722,6 +731,12 @@
|
|||||||
"esutils": "^2.0.2"
|
"esutils": "^2.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dotenv": {
|
||||||
|
"version": "8.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
|
||||||
|
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"dynamic-dedupe": {
|
"dynamic-dedupe": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"lint": "eslint '*/*.ts' --quiet --fix",
|
"lint": "eslint '*/*.ts' --quiet --fix",
|
||||||
"tsc": "tsc",
|
"tsc": "tsc",
|
||||||
"dev": "ts-node-dev --respawn --transpileOnly ./app/app.ts",
|
"dev": "ts-node-dev --respawn --transpileOnly ./app/app.ts",
|
||||||
"prod": "tsc && node ./build/app.js"
|
"example": "npx ts-node Example/example.ts"
|
||||||
},
|
},
|
||||||
"author": "Adhiraj Singh",
|
"author": "Adhiraj Singh",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -40,9 +40,11 @@
|
|||||||
"@types/mocha": "^7.0.2",
|
"@types/mocha": "^7.0.2",
|
||||||
"@types/node": "^14.0.14",
|
"@types/node": "^14.0.14",
|
||||||
"@types/sharp": "^0.25.0",
|
"@types/sharp": "^0.25.0",
|
||||||
|
"@types/ws": "^7.2.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^3.5.0",
|
"@typescript-eslint/eslint-plugin": "^3.5.0",
|
||||||
"@typescript-eslint/parser": "^3.5.0",
|
"@typescript-eslint/parser": "^3.5.0",
|
||||||
"assert": "^2.0.0",
|
"assert": "^2.0.0",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
"eslint": "^7.3.1",
|
"eslint": "^7.3.1",
|
||||||
"eslint-config-prettier": "^6.11.0",
|
"eslint-config-prettier": "^6.11.0",
|
||||||
"eslint-plugin-prettier": "^3.1.4",
|
"eslint-plugin-prettier": "^3.1.4",
|
||||||
|
|||||||
Reference in New Issue
Block a user