mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
Better media conn handling
This commit is contained in:
@@ -25,30 +25,16 @@ entries.forEach ((e, i) => {
|
|||||||
wsMessages.push (...e['_webSocketMessages'])
|
wsMessages.push (...e['_webSocketMessages'])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const decrypt = buffer => {
|
const decrypt = (buffer, fromMe) => decryptWA (buffer, macKey, encKey, new Decoder(), fromMe)
|
||||||
try {
|
|
||||||
return decryptWA (buffer, macKey, encKey, new Decoder())
|
|
||||||
} catch {
|
|
||||||
return decryptWA (buffer, macKey, encKey, new Decoder(), true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log ('parsing ' + wsMessages.length + ' messages')
|
console.log ('parsing ' + wsMessages.length + ' messages')
|
||||||
const list = wsMessages.map ((item, i) => {
|
const list = wsMessages.map ((item, i) => {
|
||||||
const buffer = Buffer.from (item.data, 'base64')
|
const buffer = item.data.includes(',') ? item.data : Buffer.from (item.data, 'base64')
|
||||||
try {
|
try {
|
||||||
|
const [tag, json, binaryTags] = decrypt (buffer, item.type === 'send')
|
||||||
const [tag, json, binaryTags] = decrypt (buffer)
|
return {tag, json: json && JSON.stringify(json), binaryTags}
|
||||||
return {tag, json: JSON.stringify(json), binaryTags}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
try {
|
return { error: error.message, data: buffer.toString('utf-8') }
|
||||||
const [tag, json, binaryTags] = decrypt (item.data)
|
|
||||||
return {tag, json: JSON.stringify(json), binaryTags}
|
|
||||||
} catch (error) {
|
|
||||||
console.log ('error in decoding: ' + item.data + ': ' + error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const str = JSON.stringify (list, null, '\t')
|
const str = JSON.stringify (list, null, '\t')
|
||||||
|
|||||||
@@ -83,6 +83,17 @@ WAConnectionTest('Messages', conn => {
|
|||||||
await delay (2000)
|
await delay (2000)
|
||||||
await conn.clearMessage (messages[0].key)
|
await conn.clearMessage (messages[0].key)
|
||||||
})
|
})
|
||||||
|
it('should send media after close', async () => {
|
||||||
|
const content = await fs.readFile('./Media/octopus.webp')
|
||||||
|
await sendAndRetreiveMessage(conn, content, MessageType.sticker)
|
||||||
|
|
||||||
|
conn.close ()
|
||||||
|
|
||||||
|
await conn.connect ()
|
||||||
|
|
||||||
|
const content2 = await fs.readFile('./Media/cat.jpeg')
|
||||||
|
await sendAndRetreiveMessage(conn, content2, MessageType.image)
|
||||||
|
})
|
||||||
it('should fail to send a text message', done => {
|
it('should fail to send a text message', done => {
|
||||||
const JID = '1234-1234@g.us'
|
const JID = '1234-1234@g.us'
|
||||||
conn.sendMessage(JID, 'hello', MessageType.text)
|
conn.sendMessage(JID, 'hello', MessageType.text)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
WAQuery,
|
WAQuery,
|
||||||
ReconnectMode,
|
ReconnectMode,
|
||||||
WAConnectOptions,
|
WAConnectOptions,
|
||||||
|
MediaConnInfo,
|
||||||
} from './Constants'
|
} from './Constants'
|
||||||
import { EventEmitter } from 'events'
|
import { EventEmitter } from 'events'
|
||||||
import KeyedDB from '@adiwajshing/keyed-db'
|
import KeyedDB from '@adiwajshing/keyed-db'
|
||||||
@@ -74,6 +75,8 @@ export class WAConnection extends EventEmitter {
|
|||||||
protected lastDisconnectTime: Date = null
|
protected lastDisconnectTime: Date = null
|
||||||
protected lastDisconnectReason: DisconnectReason
|
protected lastDisconnectReason: DisconnectReason
|
||||||
|
|
||||||
|
protected mediaConn: MediaConnInfo
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
super ()
|
super ()
|
||||||
this.registerCallback (['Cmd', 'type:disconnect'], json => (
|
this.registerCallback (['Cmd', 'type:disconnect'], json => (
|
||||||
|
|||||||
@@ -266,10 +266,8 @@ export class WAConnection extends Base {
|
|||||||
this.lastSeen = new Date(parseInt(timestamp))
|
this.lastSeen = new Date(parseInt(timestamp))
|
||||||
this.emit ('received-pong')
|
this.emit ('received-pong')
|
||||||
} else {
|
} else {
|
||||||
const decrypted = Utils.decryptWA (message, this.authInfo?.macKey, this.authInfo?.encKey, new Decoder())
|
const [messageTag, json] = Utils.decryptWA (message, this.authInfo?.macKey, this.authInfo?.encKey, new Decoder())
|
||||||
if (!decrypted) return
|
if (!json) return
|
||||||
|
|
||||||
const [messageTag, json] = decrypted
|
|
||||||
|
|
||||||
if (this.logLevel === MessageLogLevel.all) {
|
if (this.logLevel === MessageLogLevel.all) {
|
||||||
this.log(messageTag + ', ' + JSON.stringify(json), MessageLogLevel.all)
|
this.log(messageTag + ', ' + JSON.stringify(json), MessageLogLevel.all)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
WALocationMessage,
|
WALocationMessage,
|
||||||
WAContactMessage,
|
WAContactMessage,
|
||||||
WATextMessage,
|
WATextMessage,
|
||||||
WAMessageContent, WAMetric, WAFlag, WAMessage, BaileysError, MessageLogLevel, WA_MESSAGE_STATUS_TYPE, WAMessageProto
|
WAMessageContent, WAMetric, WAFlag, WAMessage, BaileysError, MessageLogLevel, WA_MESSAGE_STATUS_TYPE, WAMessageProto, MediaConnInfo
|
||||||
} from './Constants'
|
} from './Constants'
|
||||||
import { generateMessageID, sha256, hmacSign, aesEncrypWithIV, randomBytes, generateThumbnail, getMediaKeys, decodeMediaMessageBuffer, extensionForMediaMessage, whatsappID, unixTimestampSeconds } from './Utils'
|
import { generateMessageID, sha256, hmacSign, aesEncrypWithIV, randomBytes, generateThumbnail, getMediaKeys, decodeMediaMessageBuffer, extensionForMediaMessage, whatsappID, unixTimestampSeconds } from './Utils'
|
||||||
|
|
||||||
@@ -105,14 +105,12 @@ export class WAConnection extends Base {
|
|||||||
.replace(/\=+$/, '')
|
.replace(/\=+$/, '')
|
||||||
|
|
||||||
await generateThumbnail(buffer, mediaType, options)
|
await generateThumbnail(buffer, mediaType, options)
|
||||||
// send a query JSON to obtain the url & auth token to upload our media
|
|
||||||
const json = (await this.query({json: ['query', 'mediaConn']})).media_conn
|
|
||||||
const auth = json.auth // the auth token
|
|
||||||
let hostname = 'https://' + json.hosts[0].hostname // first hostname available
|
|
||||||
hostname += MediaPathMap[mediaType] + '/' + fileEncSha256B64 // append path
|
|
||||||
hostname += '?auth=' + auth // add auth token
|
|
||||||
hostname += '&token=' + fileEncSha256B64 // file hash
|
|
||||||
|
|
||||||
|
// send a query JSON to obtain the url & auth token to upload our media
|
||||||
|
const json = await this.refreshMediaConn ()
|
||||||
|
const auth = json.auth // the auth token
|
||||||
|
const hostname = `https://${json.hosts[0].hostname}${MediaPathMap[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
|
||||||
|
|
||||||
const urlFetch = await fetch(hostname, {
|
const urlFetch = await fetch(hostname, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: body,
|
body: body,
|
||||||
@@ -232,4 +230,13 @@ export class WAConnection extends Base {
|
|||||||
await fs.writeFile (trueFileName, buffer)
|
await fs.writeFile (trueFileName, buffer)
|
||||||
return trueFileName
|
return trueFileName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async refreshMediaConn () {
|
||||||
|
if (!this.mediaConn || (new Date().getTime()-this.mediaConn.fetchDate.getTime()) > this.mediaConn.ttl*1000) {
|
||||||
|
const result = await this.query({json: ['query', 'mediaConn']})
|
||||||
|
this.mediaConn = result.media_conn
|
||||||
|
this.mediaConn.fetchDate = new Date()
|
||||||
|
}
|
||||||
|
return this.mediaConn
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,14 @@ export enum MessageLogLevel {
|
|||||||
unhandled=2,
|
unhandled=2,
|
||||||
all=3
|
all=3
|
||||||
}
|
}
|
||||||
|
export interface MediaConnInfo {
|
||||||
|
auth: string
|
||||||
|
ttl: number
|
||||||
|
hosts: {
|
||||||
|
hostname: string
|
||||||
|
}[]
|
||||||
|
fetchDate: Date
|
||||||
|
}
|
||||||
export interface AuthenticationCredentials {
|
export interface AuthenticationCredentials {
|
||||||
clientID: string
|
clientID: string
|
||||||
serverToken: string
|
serverToken: string
|
||||||
|
|||||||
@@ -130,53 +130,52 @@ export function generateMessageID() {
|
|||||||
}
|
}
|
||||||
export function decryptWA (message: string | Buffer, macKey: Buffer, encKey: Buffer, decoder: Decoder, fromMe: boolean=false): [string, Object, [number, number]?] {
|
export function decryptWA (message: string | Buffer, macKey: Buffer, encKey: Buffer, decoder: Decoder, fromMe: boolean=false): [string, Object, [number, number]?] {
|
||||||
let commaIndex = message.indexOf(',') // all whatsapp messages have a tag and a comma, followed by the actual message
|
let commaIndex = message.indexOf(',') // all whatsapp messages have a tag and a comma, followed by the actual message
|
||||||
|
|
||||||
if (commaIndex < 0) throw Error ('invalid message: ' + message) // if there was no comma, then this message must be not be valid
|
if (commaIndex < 0) throw Error ('invalid message: ' + message) // if there was no comma, then this message must be not be valid
|
||||||
|
|
||||||
if (message[commaIndex+1] === ',') commaIndex += 1
|
if (message[commaIndex+1] === ',') commaIndex += 1
|
||||||
let data = message.slice(commaIndex+1, message.length)
|
let data = message.slice(commaIndex+1, message.length)
|
||||||
|
|
||||||
// get the message tag.
|
// get the message tag.
|
||||||
// If a query was done, the server will respond with the same message tag we sent the query with
|
// If a query was done, the server will respond with the same message tag we sent the query with
|
||||||
const messageTag: string = message.slice(0, commaIndex).toString()
|
const messageTag: string = message.slice(0, commaIndex).toString()
|
||||||
if (data.length === 0) {
|
|
||||||
// got an empty message, usually get one after sending a query with the 128 tag
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let json
|
let json
|
||||||
let tags = null
|
let tags
|
||||||
if (typeof data === 'string') {
|
if (data.length > 0) {
|
||||||
json = JSON.parse(data) // parse the JSON
|
if (typeof data === 'string') {
|
||||||
} else {
|
json = JSON.parse(data) // parse the JSON
|
||||||
if (!macKey || !encKey) {
|
} else {
|
||||||
console.warn ('recieved encrypted buffer when auth creds unavailable: ' + message)
|
if (!macKey || !encKey) {
|
||||||
return
|
console.warn ('recieved encrypted buffer when auth creds unavailable: ' + message)
|
||||||
}
|
return
|
||||||
/*
|
}
|
||||||
If the data recieved was not a JSON, then it must be an encrypted message.
|
/*
|
||||||
Such a message can only be decrypted if we're connected successfully to the servers & have encryption keys
|
If the data recieved was not a JSON, then it must be an encrypted message.
|
||||||
*/
|
Such a message can only be decrypted if we're connected successfully to the servers & have encryption keys
|
||||||
if (fromMe) {
|
*/
|
||||||
tags = [data[0], data[1]]
|
if (fromMe) {
|
||||||
data = data.slice(2, data.length)
|
tags = [data[0], data[1]]
|
||||||
}
|
data = data.slice(2, data.length)
|
||||||
|
}
|
||||||
const checksum = data.slice(0, 32) // the first 32 bytes of the buffer are the HMAC sign of the message
|
|
||||||
data = data.slice(32, data.length) // the actual message
|
const checksum = data.slice(0, 32) // the first 32 bytes of the buffer are the HMAC sign of the message
|
||||||
const computedChecksum = hmacSign(data, macKey) // compute the sign of the message we recieved using our macKey
|
data = data.slice(32, data.length) // the actual message
|
||||||
|
const computedChecksum = hmacSign(data, macKey) // compute the sign of the message we recieved using our macKey
|
||||||
if (!checksum.equals(computedChecksum)) {
|
|
||||||
console.error (`
|
if (checksum.equals(computedChecksum)) {
|
||||||
Checksums don't match:
|
// the checksum the server sent, must match the one we computed for the message to be valid
|
||||||
og: ${checksum.toString('hex')}
|
const decrypted = aesDecrypt(data, encKey) // decrypt using AES
|
||||||
computed: ${computedChecksum.toString('hex')}
|
json = decoder.read(decrypted) // decode the binary message into a JSON array
|
||||||
message: ${message.slice(0, 80).toString()}
|
} else {
|
||||||
`)
|
console.error (`
|
||||||
return
|
Checksums don't match:
|
||||||
}
|
og: ${checksum.toString('hex')}
|
||||||
// the checksum the server sent, must match the one we computed for the message to be valid
|
computed: ${computedChecksum.toString('hex')}
|
||||||
const decrypted = aesDecrypt(data, encKey) // decrypt using AES
|
data: ${data.slice(0, 80).toString()}
|
||||||
json = decoder.read(decrypted) // decode the binary message into a JSON array
|
tag: ${messageTag}
|
||||||
|
message: ${message.slice(0, 80).toString()}
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return [messageTag, json, tags]
|
return [messageTag, json, tags]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user