mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
Better tag generation + better traffic emulation
This commit is contained in:
@@ -9,7 +9,6 @@ import {
|
|||||||
WATag,
|
WATag,
|
||||||
} from '../WAConnection/Constants'
|
} from '../WAConnection/Constants'
|
||||||
import { generateProfilePicture } from '../WAClient/Utils'
|
import { generateProfilePicture } from '../WAClient/Utils'
|
||||||
import { generateMessageTag } from '../WAConnection/Utils'
|
|
||||||
|
|
||||||
|
|
||||||
export default class WhatsAppWebBase extends WAConnection {
|
export default class WhatsAppWebBase extends WAConnection {
|
||||||
@@ -190,7 +189,7 @@ export default class WhatsAppWebBase extends WAConnection {
|
|||||||
}
|
}
|
||||||
async updateProfilePicture (jid: string, img: Buffer) {
|
async updateProfilePicture (jid: string, img: Buffer) {
|
||||||
const data = await generateProfilePicture (img)
|
const data = await generateProfilePicture (img)
|
||||||
const tag = generateMessageTag (this.msgCount)
|
const tag = this.generateMessageTag ()
|
||||||
const query: WANode = [
|
const query: WANode = [
|
||||||
'picture',
|
'picture',
|
||||||
{ jid: jid, id: tag, type: 'set' },
|
{ jid: jid, id: tag, type: 'set' },
|
||||||
@@ -199,7 +198,7 @@ export default class WhatsAppWebBase extends WAConnection {
|
|||||||
['preview', null, data.preview]
|
['preview', null, data.preview]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
return this.setQuery ([query], [14, 136], tag) as Promise<WAProfilePictureChange>
|
return this.setQuery ([query], [WAMetric.picture, 136], tag) as Promise<WAProfilePictureChange>
|
||||||
}
|
}
|
||||||
/** Generic function for action, set queries */
|
/** Generic function for action, set queries */
|
||||||
async setQuery (nodes: WANode[], binaryTags: WATag = [WAMetric.group, WAFlag.ignore], tag?: string) {
|
async setQuery (nodes: WANode[], binaryTags: WATag = [WAMetric.group, WAFlag.ignore], tag?: string) {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import WhatsAppWebBase from './Base'
|
import WhatsAppWebBase from './Base'
|
||||||
import { WAMessage, WAMetric, WAFlag, WANode, WAGroupMetadata, WAGroupCreateResponse, WAGroupModification } from '../WAConnection/Constants'
|
import { WAMessage, WAMetric, WAFlag, WANode, WAGroupMetadata, WAGroupCreateResponse, WAGroupModification } from '../WAConnection/Constants'
|
||||||
import { GroupSettingChange } from './Constants'
|
import { GroupSettingChange } from './Constants'
|
||||||
import { generateMessageTag } from '../WAConnection/Utils'
|
|
||||||
|
|
||||||
export default class WhatsAppWebGroups extends WhatsAppWebBase {
|
export default class WhatsAppWebGroups extends WhatsAppWebBase {
|
||||||
/** 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 = generateMessageTag(this.msgCount)
|
const tag = this.generateMessageTag()
|
||||||
const json: WANode = [
|
const json: WANode = [
|
||||||
'group',
|
'group',
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export default class WAConnectionBase {
|
|||||||
protected decoder = new Decoder()
|
protected decoder = new Decoder()
|
||||||
protected pendingRequests: (() => void)[] = []
|
protected pendingRequests: (() => void)[] = []
|
||||||
protected reconnectLoop: () => Promise<void>
|
protected reconnectLoop: () => Promise<void>
|
||||||
|
protected referenceDate = new Date () // used for generating tags
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
this.registerCallback (['Cmd', 'type:disconnect'], json => this.unexpectedDisconnect(json[1].kind))
|
this.registerCallback (['Cmd', 'type:disconnect'], json => this.unexpectedDisconnect(json[1].kind))
|
||||||
@@ -235,12 +236,12 @@ export default class WAConnectionBase {
|
|||||||
* @param tag the tag to attach to the message
|
* @param tag the tag to attach to the message
|
||||||
* @return the message tag
|
* @return the message tag
|
||||||
*/
|
*/
|
||||||
private async sendBinary(json: WANode, tags: WATag, tag: string) {
|
protected async sendBinary(json: WANode, tags: WATag, tag?: string) {
|
||||||
const binary = this.encoder.write(json) // encode the JSON to the WhatsApp binary format
|
const binary = this.encoder.write(json) // encode the JSON to the WhatsApp binary format
|
||||||
|
|
||||||
let buff = Utils.aesEncrypt(binary, this.authInfo.encKey) // encrypt it using AES and our encKey
|
let buff = Utils.aesEncrypt(binary, this.authInfo.encKey) // encrypt it using AES and our encKey
|
||||||
const sign = Utils.hmacSign(buff, this.authInfo.macKey) // sign the message using HMAC and our macKey
|
const sign = Utils.hmacSign(buff, this.authInfo.macKey) // sign the message using HMAC and our macKey
|
||||||
tag = tag || Utils.generateMessageTag(this.msgCount)
|
tag = tag || this.generateMessageTag()
|
||||||
buff = Buffer.concat([
|
buff = Buffer.concat([
|
||||||
Buffer.from(tag + ','), // generate & prefix the message tag
|
Buffer.from(tag + ','), // generate & prefix the message tag
|
||||||
Buffer.from(tags), // prefix some bytes that tell whatsapp what the message is about
|
Buffer.from(tags), // prefix some bytes that tell whatsapp what the message is about
|
||||||
@@ -256,8 +257,8 @@ export default class WAConnectionBase {
|
|||||||
* @param tag the tag to attach to the message
|
* @param tag the tag to attach to the message
|
||||||
* @return the message tag
|
* @return the message tag
|
||||||
*/
|
*/
|
||||||
private async sendJSON(json: any[] | WANode, tag: string = null) {
|
protected async sendJSON(json: any[] | WANode, tag: string = null) {
|
||||||
tag = tag || Utils.generateMessageTag(this.msgCount)
|
tag = tag || this.generateMessageTag()
|
||||||
await this.send(tag + ',' + JSON.stringify(json))
|
await this.send(tag + ',' + JSON.stringify(json))
|
||||||
return tag
|
return tag
|
||||||
}
|
}
|
||||||
@@ -282,12 +283,8 @@ export default class WAConnectionBase {
|
|||||||
async logout() {
|
async logout() {
|
||||||
if (!this.conn) throw new Error("You're not even connected, you can't log out")
|
if (!this.conn) throw new Error("You're not even connected, you can't log out")
|
||||||
|
|
||||||
await new Promise(resolve => {
|
await new Promise(resolve => this.conn.send('goodbye,["admin","Conn","disconnect"]', null, resolve))
|
||||||
this.conn.send('goodbye,["admin","Conn","disconnect"]', null, () => {
|
this.authInfo = null
|
||||||
this.authInfo = null
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
this.close()
|
this.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +297,7 @@ export default class WAConnectionBase {
|
|||||||
this.conn = null
|
this.conn = null
|
||||||
}
|
}
|
||||||
const keys = Object.keys(this.callbacks)
|
const keys = Object.keys(this.callbacks)
|
||||||
keys.forEach((key) => {
|
keys.forEach(key => {
|
||||||
if (!key.includes('function:')) {
|
if (!key.includes('function:')) {
|
||||||
this.callbacks[key].errCallback('connection closed')
|
this.callbacks[key].errCallback('connection closed')
|
||||||
delete this.callbacks[key]
|
delete this.callbacks[key]
|
||||||
@@ -310,6 +307,9 @@ export default class WAConnectionBase {
|
|||||||
clearInterval(this.keepAliveReq)
|
clearInterval(this.keepAliveReq)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
generateMessageTag () {
|
||||||
|
return `${this.referenceDate.getTime()/1000}.--${this.msgCount}`
|
||||||
|
}
|
||||||
protected log(text, level: MessageLogLevel) {
|
protected log(text, level: MessageLogLevel) {
|
||||||
if (this.logLevel >= level)
|
if (this.logLevel >= level)
|
||||||
console.log(`[Baileys][${new Date().toLocaleString()}] ${text}`)
|
console.log(`[Baileys][${new Date().toLocaleString()}] ${text}`)
|
||||||
|
|||||||
@@ -75,12 +75,33 @@ export interface WAChat {
|
|||||||
messages: WAMessage[]
|
messages: WAMessage[]
|
||||||
}
|
}
|
||||||
export enum WAMetric {
|
export enum WAMetric {
|
||||||
|
debugLog = 1,
|
||||||
|
queryResume = 2,
|
||||||
liveLocation = 3,
|
liveLocation = 3,
|
||||||
queryMedia = 4,
|
queryMedia = 4,
|
||||||
|
queryChat = 5,
|
||||||
|
queryContact = 6,
|
||||||
queryMessages = 7,
|
queryMessages = 7,
|
||||||
|
presence = 8,
|
||||||
|
presenceSubscribe = 9,
|
||||||
group = 10,
|
group = 10,
|
||||||
|
read = 11,
|
||||||
|
chat = 12,
|
||||||
|
received = 13,
|
||||||
|
picture = 14,
|
||||||
|
status = 15,
|
||||||
message = 16,
|
message = 16,
|
||||||
|
queryActions = 17,
|
||||||
|
block = 18,
|
||||||
|
queryGroup = 19,
|
||||||
|
queryPreview = 20,
|
||||||
|
queryEmoji = 21,
|
||||||
|
queryVCard = 29,
|
||||||
|
queryStatus = 30,
|
||||||
|
queryStatusUpdate = 31,
|
||||||
queryLiveLocation = 33,
|
queryLiveLocation = 33,
|
||||||
|
queryLabel = 36,
|
||||||
|
queryQuickReply = 39
|
||||||
}
|
}
|
||||||
export enum WAFlag {
|
export enum WAFlag {
|
||||||
ignore = 1 << 7,
|
ignore = 1 << 7,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
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 WAConnectionBase from './Base'
|
||||||
import { MessageLogLevel } from './Constants'
|
import { MessageLogLevel, WAMetric, WAFlag } from './Constants'
|
||||||
|
import { Presence } from '../WAClient/WAClient'
|
||||||
|
|
||||||
const StatusError = (message: any, description: string='unknown error') => new Error (`unexpected status: ${message.status} on JSON: ${JSON.stringify(message)}`)
|
const StatusError = (message: any, description: string='unknown error') => new Error (`unexpected status: ${message.status} on JSON: ${JSON.stringify(message)}`)
|
||||||
|
|
||||||
@@ -19,32 +20,28 @@ export default class WAConnectionValidator extends WAConnectionBase {
|
|||||||
macKey: null,
|
macKey: null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.query(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
|
||||||
switch (json.status) {
|
if (this.authInfo.encKey && this.authInfo.macKey) {
|
||||||
case 200: // all good and we can procede to generate a QR code for new connection, or can now login given present auth info
|
// if we have the info to restore a closed session
|
||||||
if (this.authInfo.encKey && this.authInfo.macKey) {
|
const data = [
|
||||||
// if we have the info to restore a closed session
|
'admin',
|
||||||
const data = [
|
'login',
|
||||||
'admin',
|
this.authInfo.clientToken,
|
||||||
'login',
|
this.authInfo.serverToken,
|
||||||
this.authInfo.clientToken,
|
this.authInfo.clientID,
|
||||||
this.authInfo.serverToken,
|
'takeover',
|
||||||
this.authInfo.clientID,
|
]
|
||||||
'takeover',
|
return this.query(data, null, null, 's1') // wait for response with tag "s1"
|
||||||
]
|
|
||||||
return this.query(data, null, null, 's1') // wait for response with tag "s1"
|
|
||||||
} else {
|
|
||||||
return this.generateKeysForAuth(json.ref)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw StatusError (json)
|
|
||||||
}
|
}
|
||||||
|
return this.generateKeysForAuth(json.ref) // generate keys which will in turn be the QR
|
||||||
})
|
})
|
||||||
.then((json) => {
|
.then(json => {
|
||||||
if ('status' in json) {
|
if ('status' in json) {
|
||||||
switch (json.status) {
|
switch (json.status) {
|
||||||
case 401: // if the phone was unpaired
|
case 401: // if the phone was unpaired
|
||||||
throw StatusError (json, 'unpaired from phone')
|
throw StatusError (json, 'unpaired from phone')
|
||||||
@@ -54,28 +51,36 @@ export default class WAConnectionValidator extends WAConnectionBase {
|
|||||||
throw StatusError (json)
|
throw StatusError (json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (json[1] && json[1].challenge) {
|
// if its a challenge request (we get it when logging in)
|
||||||
// if its a challenge request (we get it when logging in)
|
if (json[1]?.challenge) {
|
||||||
return this.respondToChallenge(json[1].challenge)
|
return this.respondToChallenge(json[1].challenge)
|
||||||
.then((json) => {
|
.then (() => this.waitForMessage('s2', []))
|
||||||
if (json.status !== 200) {
|
|
||||||
// throw an error if the challenge failed
|
|
||||||
throw StatusError (json)
|
|
||||||
}
|
|
||||||
return this.waitForMessage('s2', []) // otherwise wait for the validation message
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// otherwise just chain the promise further
|
|
||||||
return json
|
|
||||||
}
|
}
|
||||||
|
// otherwise just chain the promise further
|
||||||
|
return json
|
||||||
})
|
})
|
||||||
.then((json) => {
|
.then(async json => {
|
||||||
this.validateNewConnection(json[1]) // validate the connection
|
this.validateNewConnection(json[1]) // validate the connection
|
||||||
this.log('validated connection successfully', MessageLogLevel.info)
|
this.log('validated connection successfully', MessageLogLevel.info)
|
||||||
|
|
||||||
|
await this.sendPostConnectQueries ()
|
||||||
|
|
||||||
this.lastSeen = new Date() // set last seen to right now
|
this.lastSeen = new Date() // set last seen to right now
|
||||||
return this.userMetaData
|
return this.userMetaData
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Send the same queries WA Web sends after connect
|
||||||
|
*/
|
||||||
|
async sendPostConnectQueries () {
|
||||||
|
await this.sendBinary (['query', {type: 'contacts', epoch: '1'}, null], [ WAMetric.queryContact, WAFlag.ignore ])
|
||||||
|
await this.sendBinary (['query', {type: 'chat', epoch: '1'}, null], [ WAMetric.queryChat, WAFlag.ignore ])
|
||||||
|
await this.sendBinary (['query', {type: 'status', epoch: '1'}, null], [ WAMetric.queryStatus, WAFlag.ignore ])
|
||||||
|
await this.sendBinary (['query', {type: 'quick_reply', epoch: '1'}, null], [ WAMetric.queryQuickReply, WAFlag.ignore ])
|
||||||
|
await this.sendBinary (['query', {type: 'label', epoch: '1'}, null], [ WAMetric.queryLabel, WAFlag.ignore ])
|
||||||
|
await this.sendBinary (['query', {type: 'emoji', epoch: '1'}, null], [ WAMetric.queryEmoji, WAFlag.ignore ])
|
||||||
|
await this.sendBinary (['action', {type: 'set', epoch: '1'}, [['presence', {type: Presence.available}, null]] ], [ WAMetric.presence, 160 ])
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Refresh QR Code
|
* Refresh QR Code
|
||||||
* @returns the new ref
|
* @returns the new ref
|
||||||
@@ -158,7 +163,7 @@ export default class WAConnectionValidator extends WAConnectionBase {
|
|||||||
const signed = Utils.hmacSign(bytes, this.authInfo.macKey).toString('base64') // sign the challenge string with our macKey
|
const signed = Utils.hmacSign(bytes, this.authInfo.macKey).toString('base64') // sign the challenge string with our macKey
|
||||||
const data = ['admin', 'challenge', signed, this.authInfo.serverToken, this.authInfo.clientID] // prepare to send this signed string with the serverToken & clientID
|
const data = ['admin', 'challenge', signed, this.authInfo.serverToken, this.authInfo.clientID] // prepare to send this signed string with the serverToken & clientID
|
||||||
this.log('resolving login challenge', MessageLogLevel.info)
|
this.log('resolving login challenge', MessageLogLevel.info)
|
||||||
return this.query(data)
|
return this.queryExpecting200(data)
|
||||||
}
|
}
|
||||||
/** When starting a new session, generate a QR code by generating a private/public key pair & the keys the server sends */
|
/** When starting a new session, generate a QR code by generating a private/public key pair & the keys the server sends */
|
||||||
protected async generateKeysForAuth(ref: string) {
|
protected async generateKeysForAuth(ref: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user