Added ability to log messages

This commit is contained in:
Adhiraj
2020-09-30 20:44:22 +05:30
parent ab54e70add
commit 8277590d11
8 changed files with 66 additions and 44 deletions

View File

@@ -33,7 +33,6 @@ const list = wsMessages.map ((item, i) => {
try {
const [tag, json, binaryTags] = decrypt (buffer, item.type === 'send')
if (json && json[1] && json[1].add) return
return {tag, json: json && JSON.stringify(json), binaryTags}
} catch (error) {
return { error: error.message, data: buffer.toString('utf-8') }

View File

@@ -20,10 +20,10 @@ export async function sendAndRetreiveMessage(conn: WAConnection, content, type:
export const WAConnectionTest = (name: string, func: (conn: WAConnection) => void) => (
describe(name, () => {
const conn = new WAConnection()
conn.logLevel = MessageLogLevel.info
conn.connectOptions.maxIdleTimeMs = 30_000
conn.logLevel = MessageLogLevel.unhandled
before(async () => {
//conn.logLevel = MessageLogLevel.unhandled
const file = './auth_info.json'
await conn.loadAuthInfo(file).connect()
await fs.writeFile(file, JSON.stringify(conn.base64EncodedAuthInfo(), null, '\t'))

View File

@@ -30,7 +30,7 @@ import { STATUS_CODES, Agent } from 'http'
export class WAConnection extends EventEmitter {
/** The version of WhatsApp Web we're telling the servers we are */
version: [number, number, number] = [2, 2039, 6]
version: [number, number, number] = [2, 2039, 9]
/** 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. */
@@ -57,6 +57,10 @@ export class WAConnection extends EventEmitter {
/** key to use to order chats */
chatOrderingKey = Utils.WA_CHAT_KEY
/** log messages */
shouldLogMessages = false
messageLog: { tag: string, json: string, fromMe: boolean, binaryTags?: any[] }[] = []
maxCachedMessages = 50
chats: KeyedDB<WAChat> = new KeyedDB (Utils.WA_CHAT_KEY, value => value.jid)
@@ -74,6 +78,7 @@ export class WAConnection extends EventEmitter {
protected encoder = new Encoder()
protected decoder = new Decoder()
protected pendingRequests: {resolve: () => void, reject: (error) => void}[] = []
protected referenceDate = new Date () // used for generating tags
protected lastSeen: Date = null // last keep alive received
protected qrTimeout: NodeJS.Timeout
@@ -208,15 +213,15 @@ export class WAConnection extends EventEmitter {
* @param json query that was sent
* @param timeoutMs timeout after which the promise will reject
*/
async waitForMessage(tag: string, json: Object = null, timeoutMs: number = null) {
let promise = Utils.promiseTimeout(timeoutMs,
(resolve, reject) => (this.callbacks[tag] = { queryJSON: json, callback: resolve, errCallback: reject }),
)
.catch((err) => {
async waitForMessage(tag: string, json?: Object, timeoutMs?: number) {
try {
const result = await Utils.promiseTimeout(timeoutMs,
(resolve, reject) => (this.callbacks[tag] = { queryJSON: json, callback: resolve, errCallback: reject }),
)
return result as any
} finally {
delete this.callbacks[tag]
throw err
})
return promise as Promise<any>
}
}
/** Generic function for action, set queries */
async setQuery (nodes: WANode[], binaryTags: WATag = [WAMetric.group, WAFlag.ignore], tag?: string) {
@@ -230,16 +235,18 @@ export class WAConnection extends EventEmitter {
* @param binaryTags the tags to attach if the query is supposed to be sent encoded in binary
* @param timeoutMs timeout after which the query will be failed (set to null to disable a timeout)
* @param tag the tag to attach to the message
* recieved JSON
*/
async query({json, binaryTags, tag, timeoutMs, expect200, waitForOpen, longTag}: WAQuery) {
waitForOpen = typeof waitForOpen === 'undefined' ? true : waitForOpen
if (waitForOpen) await this.waitForConnection ()
waitForOpen = waitForOpen !== false
if (waitForOpen) await this.waitForConnection()
tag = tag || this.generateMessageTag (longTag)
const promise = this.waitForMessage(tag, json, timeoutMs)
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)
const response = await promise
if (expect200 && response.status && Math.floor(+response.status / 100) !== 2) {
// read here: http://getstatuscode.com/599
if (response.status === 599) {
@@ -262,43 +269,48 @@ 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, longTag: boolean = false) {
protected async 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(longTag)
if (this.shouldLogMessages) this.messageLog.push ({ tag, json: JSON.stringify(json), fromMe: true, binaryTags: tags })
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
sign, // the HMAC sign of the message
buff, // the actual encrypted buffer
])
this.send(buff) // send it off
await this.send(buff) // send it off
return tag
}
/**
* Send a plain JSON message to the WhatsApp servers
* @param json the message to send
* @param tag the tag to attach to the message
* @return the message tag
* @returns the message tag
*/
protected sendJSON(json: any[] | WANode, tag: string = null, longTag: boolean = false) {
protected async sendJSON(json: any[] | WANode, tag: string = null, longTag: boolean = false) {
tag = tag || this.generateMessageTag(longTag)
this.send(`${tag},${JSON.stringify(json)}`)
if (this.shouldLogMessages) this.messageLog.push ({ tag, json: JSON.stringify(json), fromMe: true })
await this.send(`${tag},${JSON.stringify(json)}`)
return tag
}
/** Send some message to the WhatsApp servers */
protected send(m) {
protected async send(m) {
this.msgCount += 1 // increment message count, it makes the 'epoch' field when sending binary messages
return this.conn.send(m)
this.conn.send(m)
}
protected async waitForConnection () {
if (this.state === 'open') return
await Utils.promiseTimeout (
this.pendingRequestTimeoutMs,
(resolve, reject) => this.pendingRequests.push({resolve, reject}))
(resolve, reject) => this.pendingRequests.push({resolve, reject})
)
}
/**
* Disconnect from the phone. Your auth credentials become invalid after sending a disconnect request.
@@ -325,7 +337,6 @@ export class WAConnection extends EventEmitter {
this.debounceTimeout && clearTimeout (this.debounceTimeout)
this.state = 'close'
this.msgCount = 0
this.phoneConnected = false
this.lastDisconnectReason = reason
this.lastDisconnectTime = new Date ()
@@ -344,13 +355,14 @@ export class WAConnection extends EventEmitter {
this.conn?.removeAllListeners ('error')
this.conn?.removeAllListeners ('open')
this.conn?.removeAllListeners ('message')
//this.conn?.close ()
this.conn?.terminate()
this.conn = null
this.lastSeen = null
this.msgCount = 0
Object.keys(this.callbacks).forEach(key => {
if (!key.includes('function:')) {
if (!key.startsWith('function:')) {
this.log (`cancelling message wait: ${key}`, MessageLogLevel.info)
this.callbacks[key].errCallback(new Error('close'))
delete this.callbacks[key]

View File

@@ -267,6 +267,7 @@ export class WAConnection extends Base {
resolveTask = resolve
cancelChats = () => reject (CancelledError())
})
console.log ('resolved task')
const oldChats = this.chats
const updatedChats: { [k: string]: Partial<WAChat> } = {}
@@ -307,12 +308,14 @@ export class WAConnection extends Base {
this.emit ('received-pong')
} else {
const [messageTag, json] = Utils.decryptWA (message, this.authInfo?.macKey, this.authInfo?.encKey, new Decoder())
if (!json) return
if (this.shouldLogMessages) this.messageLog.push ({ tag: messageTag, json: JSON.stringify(json), fromMe: false })
if (!json) {return}
if (this.logLevel === MessageLogLevel.all) {
this.log(messageTag + ', ' + JSON.stringify(json), MessageLogLevel.all)
}
if (!this.phoneConnected) {
if (!this.phoneConnected && this.state === 'open') {
this.phoneConnected = true
this.emit ('connection-phone-change', { connected: true })
}

View File

@@ -3,6 +3,7 @@ import { WAConnection as Base } from './3.Connect'
import { WAMessageStatusUpdate, WAMessage, WAContact, WAChat, WAMessageProto, WA_MESSAGE_STUB_TYPE, WA_MESSAGE_STATUS_TYPE, MessageLogLevel, PresenceUpdate, BaileysEvent, DisconnectReason, WANode, WAOpenResult, Presence } from './Constants'
import { whatsappID, unixTimestampSeconds, isGroupID, toNumber, GET_MESSAGE_ID, WA_MESSAGE_ID, WA_MESSAGE_KEY } from './Utils'
import KeyedDB from '@adiwajshing/keyed-db'
import { Mutex } from './Mutex'
export class WAConnection extends Base {
@@ -165,6 +166,7 @@ export class WAConnection extends Base {
this.on ('qr', qr => QR.generate(qr, { small: true }))
}
/** Get the URL to download the profile picture of a person/group */
@Mutex (jid => jid)
async getProfilePicture(jid: string | null) {
const response = await this.query({ json: ['query', 'ProfilePicThumb', jid || this.user.jid], expect200: true })
return response.eurl as string

View File

@@ -1,5 +1,5 @@
import {WAConnection as Base} from './4.Events'
import { Presence, WABroadcastListInfo, WAProfilePictureChange, WAChat, ChatModification } from './Constants'
import { Presence, WABroadcastListInfo, WAProfilePictureChange, WAChat, ChatModification, WALoadChatOptions } from './Constants'
import {
WAMessage,
WANode,
@@ -96,17 +96,19 @@ export class WAConnection extends Base {
* @param searchString optionally search for users
* @returns the chats & the cursor to fetch the next page
*/
async loadChats (count: number, before: number | null, filters?: {searchString?: string, custom?: (c: WAChat) => boolean}) {
const chats = this.chats.paginated (before, count, filters && (chat => (
(typeof filters?.custom !== 'function' || filters?.custom(chat)) &&
(typeof filters?.searchString === 'undefined' || chat.name?.includes (filters.searchString) || chat.jid?.startsWith(filters.searchString))
async loadChats (count: number, before: number | null, options: WALoadChatOptions = {}) {
const chats = this.chats.paginated (before, count, options && (chat => (
(typeof options?.custom !== 'function' || options?.custom(chat)) &&
(typeof options?.searchString === 'undefined' || chat.name?.includes (options.searchString) || chat.jid?.startsWith(options.searchString))
)))
await Promise.all (
chats.map (async chat => (
chat.imgUrl === undefined && await this.setProfilePicture (chat)
))
)
const cursor = (chats[chats.length-1] && chats.length >= count) ? WA_CHAT_KEY (chats[chats.length-1]) : null
if (options.loadProfilePicture !== false) {
await Promise.all (
chats.map (async chat => (
typeof chat.imgUrl === 'undefined' && await this.setProfilePicture (chat)
))
)
}
const cursor = (chats[chats.length-1] && chats.length >= count) && this.chatOrderingKey (chats[chats.length-1])
return { chats, cursor }
}
/**

View File

@@ -93,7 +93,7 @@ export class WAConnection extends Base {
* @param cursor the data for which message to offset the query by
* @param mostRecentFirst retreive the most recent message first or retreive from the converation start
*/
@Mutex ((jid, _, before, mostRecentFirst) => jid + (before?.id || '') + mostRecentFirst)
@Mutex ()
async loadMessages (
jid: string,
count: number,

View File

@@ -65,6 +65,11 @@ export enum ReconnectMode {
/** reconnects on all disconnects, including take overs */
onAllErrors = 2
}
export type WALoadChatOptions = {
searchString?: string
custom?: (c: WAChat) => boolean
loadProfilePicture?: boolean
}
export type WAConnectOptions = {
/** New QR generation interval, set to null if you don't want to regenerate */
regenerateQRIntervalMs?: number
@@ -78,7 +83,6 @@ export type WAConnectOptions = {
waitOnlyForLastMessage?: boolean
/** max time for the phone to respond to a connectivity test */
phoneResponseTime?: number
connectCooldownMs?: number
/** agent which can be used for proxying connections */
agent?: Agent