diff --git a/src/WAConnection/0.Base.ts b/src/WAConnection/0.Base.ts index c7ecd36..e77123c 100644 --- a/src/WAConnection/0.Base.ts +++ b/src/WAConnection/0.Base.ts @@ -26,10 +26,11 @@ import { } from './Constants' import { EventEmitter } from 'events' import KeyedDB from '@adiwajshing/keyed-db' +import { STATUS_CODES } from 'http' export class WAConnection extends EventEmitter { /** The version of WhatsApp Web we're telling the servers we are */ - version: [number, number, number] = [2, 2033, 7] + version: [number, number, number] = [2, 2035, 14] /** The Browser we're telling the WhatsApp Web servers we are */ browserDescription: [string, string, string] = Utils.Browsers.baileys ('Chrome') /** Metadata like WhatsApp id, name set on WhatsApp etc. */ @@ -224,12 +225,12 @@ export class WAConnection extends EventEmitter { * @param tag the tag to attach to the message * recieved JSON */ - async query({json, binaryTags, tag, timeoutMs, expect200, waitForOpen}: WAQuery) { + async query({json, binaryTags, tag, timeoutMs, expect200, waitForOpen, longTag}: WAQuery) { waitForOpen = typeof waitForOpen === 'undefined' ? true : waitForOpen if (waitForOpen) await this.waitForConnection () - if (binaryTags) tag = await this.sendBinary(json as WANode, binaryTags, tag) - else tag = await this.sendJSON(json, tag) + if (binaryTags) tag = await this.sendBinary(json as WANode, binaryTags, tag, longTag) + else tag = await this.sendJSON(json, tag, longTag) const response = await this.waitForMessage(tag, json, timeoutMs) if (expect200 && response.status && Math.floor(+response.status / 100) !== 2) { @@ -239,7 +240,11 @@ export class WAConnection extends EventEmitter { const response = await this.query ({json, binaryTags, tag, timeoutMs, expect200, waitForOpen}) return response } - throw new BaileysError(`Unexpected status code in '${json[0] || 'generic query'}': ${response.status}`, {query: json, status: response.status}) + const message = STATUS_CODES[response.status] || 'unknown' + throw new BaileysError( + `Unexpected status in '${json[0] || 'generic query'}': ${STATUS_CODES[response.status]}(${response.status})`, + {query: json, message, status: response.status} + ) } return response } @@ -250,12 +255,12 @@ export class WAConnection extends EventEmitter { * @param tag the tag to attach to the message * @return the message tag */ - protected sendBinary(json: WANode, tags: WATag, tag: string = null) { + protected sendBinary(json: WANode, tags: WATag, tag: string = null, longTag: boolean = false) { 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 const sign = Utils.hmacSign(buff, this.authInfo.macKey) // sign the message using HMAC and our macKey - tag = tag || this.generateMessageTag() + tag = tag || this.generateMessageTag(longTag) buff = Buffer.concat([ Buffer.from(tag + ','), // generate & prefix the message tag Buffer.from(tags), // prefix some bytes that tell whatsapp what the message is about @@ -271,8 +276,8 @@ export class WAConnection extends EventEmitter { * @param tag the tag to attach to the message * @return the message tag */ - protected sendJSON(json: any[] | WANode, tag: string = null) { - tag = tag || this.generateMessageTag() + protected sendJSON(json: any[] | WANode, tag: string = null, longTag: boolean = false) { + tag = tag || this.generateMessageTag(longTag) this.send(`${tag},${JSON.stringify(json)}`) return tag } @@ -353,8 +358,9 @@ export class WAConnection extends EventEmitter { agent: this.connectOptions.agent }) ) - generateMessageTag () { - return `${Utils.unixTimestampSeconds(this.referenceDate)}.--${this.msgCount}` + generateMessageTag (longTag: boolean = false) { + const seconds = Utils.unixTimestampSeconds(this.referenceDate) + return `${longTag ? seconds : (seconds%1000)}.--${this.msgCount}` } protected log(text, level: MessageLogLevel) { (this.logLevel >= level) && console.log(`[Baileys][${new Date().toLocaleString()}] ${text}`) diff --git a/src/WAConnection/1.Validation.ts b/src/WAConnection/1.Validation.ts index 52a4ade..d2e7f28 100644 --- a/src/WAConnection/1.Validation.ts +++ b/src/WAConnection/1.Validation.ts @@ -12,15 +12,24 @@ export class WAConnection extends Base { if (!this.authInfo?.clientID) { this.authInfo = { clientID: Utils.generateClientID() } as any } - - this.referenceDate = new Date () // refresh reference date - const json = ['admin', 'init', this.version, this.browserDescription, this.authInfo?.clientID, true] - return this.query({json, expect200: true, waitForOpen: false}) - .then(json => { - // we're trying to establish a new connection or are trying to log in - if (this.authInfo?.encKey && this.authInfo?.macKey) { - // if we have the info to restore a closed session + const canLogin = this.authInfo?.encKey && this.authInfo?.macKey + this.referenceDate = new Date () // refresh reference date + + const initQueries = [ + (async () => { + const json = ['admin', 'init', this.version, this.browserDescription, this.authInfo?.clientID, true] + const ref = await this.query({json, expect200: true, waitForOpen: false, longTag: true}) + if (!canLogin) { + const result = await this.generateKeysForAuth (ref) + return result + } + })() + ] + if (canLogin) { + // if we have the info to restore a closed session + initQueries.push ( + (async () => { const json = [ 'admin', 'login', @@ -30,38 +39,30 @@ export class WAConnection extends Base { ] if (reconnect) json.push(...['reconnect', reconnect.replace('@s.whatsapp.net', '@c.us')]) else json.push ('takeover') - return this.query({ json, tag: 's1', waitForOpen: false }) // wait for response with tag "s1" - } - return this.generateKeysForAuth(json.ref) // generate keys which will in turn be the QR - }) - .then(async json => { - if ('status' in json) { - switch (json.status) { - case 401: // if the phone was unpaired - throw new BaileysError ('unpaired from phone', json) - case 429: // request to login was denied, don't know why it happens - throw new BaileysError ('request denied', json) - default: - throw new BaileysError ('unexpected status ' + json.status, json) + + let response = await this.query({ json, tag: 's1', waitForOpen: false, expect200: true, longTag: true }) // wait for response with tag "s1" + // if its a challenge request (we get it when logging in) + if (response[1]?.challenge) { + await this.respondToChallenge(response[1].challenge) + response = await this.waitForMessage('s2', []) } - } - // if its a challenge request (we get it when logging in) - if (json[1]?.challenge) { - await this.respondToChallenge(json[1].challenge) - return this.waitForMessage('s2', []) - } - // otherwise just chain the promise further - return json - }) - .then(json => { - this.user = this.validateNewConnection(json[1]) // validate the connection - this.log('validated connection successfully', MessageLogLevel.info) + return response + })() + ) + } - this.sendPostConnectQueries () - }) - // load profile picture - .then (() => this.query({ json: ['query', 'ProfilePicThumb', this.user.jid], waitForOpen: false, expect200: false })) - .then (response => this.user.imgUrl = response?.eurl || '') + const validationJSON = (await Promise.all (initQueries)).slice(-1)[0] // get the last result + + this.user = await this.validateNewConnection(validationJSON[1]) // validate the connection + + this.log('validated connection successfully', MessageLogLevel.info) + + const response = await this.query({ json: ['query', 'ProfilePicThumb', this.user.jid], waitForOpen: false, expect200: false }) + this.user.imgUrl = response?.eurl || '' + + this.sendPostConnectQueries () + + this.log('sent init queries', MessageLogLevel.info) } /** * Send the same queries WA Web sends after connect @@ -80,7 +81,7 @@ export class WAConnection extends Base { * @returns the new ref */ async generateNewQRCodeRef() { - const response = await this.query({json: ['admin', 'Conn', 'reref'], expect200: true, waitForOpen: false}) + const response = await this.query({json: ['admin', 'Conn', 'reref'], expect200: true, waitForOpen: false, longTag: true}) return response.ref as string } /** diff --git a/src/WAConnection/3.Connect.ts b/src/WAConnection/3.Connect.ts index f136d99..9a9866e 100644 --- a/src/WAConnection/3.Connect.ts +++ b/src/WAConnection/3.Connect.ts @@ -234,7 +234,7 @@ export class WAConnection extends Base { const oldChats = this.chats const updatedChats: { [k: string]: Partial } = {} - for (let chat of chats.all()) { + chats.all().forEach (chat => { const respectiveContact = contacts[chat.jid] chat.name = respectiveContact?.name || respectiveContact?.notify || chat.name @@ -246,8 +246,8 @@ export class WAConnection extends Base { delete changes.messages updatedChats[chat.jid] = changes } - } - + }) + this.chats = chats this.contacts = contacts diff --git a/src/WAConnection/Constants.ts b/src/WAConnection/Constants.ts index b6d89a2..e28d744 100644 --- a/src/WAConnection/Constants.ts +++ b/src/WAConnection/Constants.ts @@ -55,6 +55,7 @@ export interface WAQuery { tag?: string expect200?: boolean waitForOpen?: boolean + longTag?: boolean } export enum ReconnectMode { /** does not reconnect */