From 9f508184ebae6ee1a636853c1cc487823f9966cc Mon Sep 17 00:00:00 2001 From: Adhiraj Date: Sun, 12 Jul 2020 13:35:21 +0530 Subject: [PATCH] filename to messageoptions + group query + media query --- .gitignore | 1 + README.md | 1 + src/WAClient/Base.ts | 17 +++++++++-- src/WAClient/Constants.ts | 3 +- src/WAClient/Messages.ts | 18 +++++++++++- src/WAClient/Tests.ts | 2 ++ src/WAClient/Utils.ts | 11 +++++--- src/WAConnection/BrowserMessageDecoding.ts | 33 ++++++++++++++-------- src/WAConnection/Constants.ts | 2 ++ 9 files changed, 68 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index de50faf..d21e9c0 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ yarn.lock browser-messages.json package-lock.json package-lock.json +decoded-ws.json diff --git a/README.md b/README.md index 5768dc5..293bf4e 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,7 @@ To note: mimetype: Mimetype.pdf, /* (for media messages) specify the type of media (optional for all media types except documents), import {Mimetype} from '@adiwajshing/baileys' */ + filename: 'somefile.pdf' // (for media messages) file name for the media } ``` diff --git a/src/WAClient/Base.ts b/src/WAClient/Base.ts index 5775b88..067d002 100644 --- a/src/WAClient/Base.ts +++ b/src/WAClient/Base.ts @@ -144,7 +144,7 @@ export default class WhatsAppWebBase extends WAConnection { }, null, ] - const response = await this.query(json, [WAMetric.group, WAFlag.ignore]) + const response = await this.query(json, [WAMetric.queryMessages, WAFlag.ignore]) if (response.status) throw new Error(`error in query, got status: ${response.status}`) @@ -158,7 +158,6 @@ export default class WhatsAppWebBase extends WAConnection { */ loadEntireConversation(jid: string, onMessage: (m: WAMessage) => void, chunkSize = 25, mostRecentFirst = true) { let offsetID = null - const loadMessage = async () => { const json = await this.loadConversation(jid, chunkSize, offsetID, mostRecentFirst) // callback with most recent message first (descending order of date) @@ -208,6 +207,20 @@ export default class WhatsAppWebBase extends WAConnection { } /** Get the metadata of the group */ groupMetadata = (jid: string) => this.queryExpecting200(['query', 'GroupMetadata', jid]) as Promise + /** Get the metadata (works after you've left the group also) */ + groupCreatorAndParticipants = async (jid: string) => { + const response = await this.queryExpecting200(['query', {type: 'group', jid: jid, epoch: '5'}, null], [WAMetric.group, WAFlag.ignore]) + if (!response[2] || !response[2][1]) throw new Error ('Data missing in ' + JSON.stringify(response)) + const json = response[2] + return { + id: jid, + owner: json[1].creator, + creator: json[1].creator, + creation: parseInt(json[1].create), + subject: null, + participants: json[2] ? json[2].map (item => ({ id: item[1].jid, isAdmin: item[1].type==='admin' })) : [] + } as WAGroupMetadata + } /** * Create a group * @param title like, the title of the group diff --git a/src/WAClient/Constants.ts b/src/WAClient/Constants.ts index 8c1847a..6d4b5d5 100644 --- a/src/WAClient/Constants.ts +++ b/src/WAClient/Constants.ts @@ -70,7 +70,8 @@ export interface MessageOptions { caption?: string thumbnail?: string mimetype?: Mimetype - validateID?: boolean + validateID?: boolean, + filename?: string } export interface MessageStatusUpdate { from: string diff --git a/src/WAClient/Messages.ts b/src/WAClient/Messages.ts index bd0f733..80e53ea 100644 --- a/src/WAClient/Messages.ts +++ b/src/WAClient/Messages.ts @@ -106,6 +106,21 @@ export default class WhatsAppWebMessages extends WhatsAppWebBase { ] return this.setQuery ([attrs]) } + /** + * Fetches the latest url & media key for the given message. + * You may need to call this when the message is old & the content is deleted off of the WA servers + * @param message + */ + async updateMediaMessage (message: WAMessage) { + const content = message.message?.audioMessage || message.message?.videoMessage || message.message?.imageMessage || message.message?.stickerMessage || message.message?.documentMessage + if (!content) throw new Error (`given message ${message.key.id} is not a media message`) + + const query = ['query',{type: 'media', index: message.key.id, owner: message.key.fromMe ? 'true' : 'false', jid: message.key.remoteJid, epoch: this.msgCount.toString()},null] + const response = await this.query (query, [WAMetric.queryMedia, WAFlag.ignore]) + if (response[1].code !== 200) throw new Error ('unexpected status ' + response[1].code) + + Object.keys (response[1]).forEach (key => content[key] = response[1][key]) // update message + } /** * Delete a message in a chat for everyone * @param id the person or group where you're trying to delete the message @@ -207,9 +222,10 @@ export default class WhatsAppWebMessages extends WhatsAppWebBase { fileEncSha256: fileEncSha256B64, fileSha256: fileSha256.toString('base64'), fileLength: buffer.length, + fileName: options.filename || 'file', gifPlayback: isGIF || null, } - return message + return message as WAMessageContent } /** Generic send message function */ async sendGenericMessage(id: string, message: WAMessageContent, options: MessageOptions) { diff --git a/src/WAClient/Tests.ts b/src/WAClient/Tests.ts index 771cd19..4060baa 100644 --- a/src/WAClient/Tests.ts +++ b/src/WAClient/Tests.ts @@ -5,6 +5,7 @@ import * as assert from 'assert' import { decodeMediaMessage, validateJIDForSending } from './Utils' import { promiseTimeout, createTimeout } from '../WAConnection/Utils' +import { WAMessageContent, WAMessage } from '../WAConnection/Constants' require ('dotenv').config () // dotenv to load test jid const testJid = process.env.TEST_JID || '1234@s.whatsapp.net' // set TEST_JID=xyz@s.whatsapp.net in a .env file in the root directory @@ -171,6 +172,7 @@ WAClientTest('Groups', (client) => { }) it('should leave the group', async () => { await client.groupLeave(gid) + await client.groupCreatorAndParticipants (gid) }) it('should archive the group', async () => { await client.archiveChat(gid) diff --git a/src/WAClient/Utils.ts b/src/WAClient/Utils.ts index 855562f..9e1cc7a 100644 --- a/src/WAClient/Utils.ts +++ b/src/WAClient/Utils.ts @@ -109,7 +109,6 @@ export async function decodeMediaMessage(message: WAMessageContent, filename: st fs.writeFileSync(filename + '.jpeg', message[type].jpegThumbnail) return { filename: filename + '.jpeg' } } - const messageContent = message[type] as | proto.VideoMessage | proto.ImageMessage @@ -117,9 +116,13 @@ export async function decodeMediaMessage(message: WAMessageContent, filename: st | proto.DocumentMessage // download the message - const fetched = await fetch(messageContent.url, {}) + const fetched = await fetch(messageContent.url, { headers: { Origin: 'https://web.whatsapp.com' } }) const buffer = await fetched.buffer() + if (buffer.length === 0) { + throw new Error ('Empty buffer returned. File has possibly been deleted from WA servers. Run `client.updateMediaMessage()` to refresh the url') + } + const decryptedMedia = (type: MessageType) => { // get the keys to decrypt the message const mediaKeys = getMediaKeys(messageContent.mediaKey, type) //getMediaKeys(Buffer.from(messageContent.mediaKey, 'base64'), type) @@ -134,7 +137,7 @@ export async function decodeMediaMessage(message: WAMessageContent, filename: st if (sign.equals(mac)) { return aesDecryptWithIV(file, mediaKeys.cipherKey, mediaKeys.iv) // decrypt media } else { - throw new Error('') + throw new Error() } } const allTypes = [type, ...Object.keys(HKDFInfoKeys)] @@ -151,5 +154,5 @@ export async function decodeMediaMessage(message: WAMessageContent, filename: st if (i === 0) { console.log (`decryption of ${type} media with original HKDF key failed`) } } } - throw new Error('HMAC sign does not match for: ' + buffer.toString('utf-8')) + throw new Error('HMAC sign does not match for ' + buffer.length) } diff --git a/src/WAConnection/BrowserMessageDecoding.ts b/src/WAConnection/BrowserMessageDecoding.ts index 2a0a953..ce3bc87 100644 --- a/src/WAConnection/BrowserMessageDecoding.ts +++ b/src/WAConnection/BrowserMessageDecoding.ts @@ -5,7 +5,11 @@ import Decoder from '../Binary/Decoder' interface BrowserMessagesInfo { encKey: string, macKey: string, - messages: string[] + harFilePath: string +} +interface WSMessage { + type: 'send' | 'receive', + data: string } const file = fs.readFileSync ('./browser-messages.json', {encoding: 'utf-8'}) const json: BrowserMessagesInfo = JSON.parse (file) @@ -13,6 +17,14 @@ const json: BrowserMessagesInfo = JSON.parse (file) const encKey = Buffer.from (json.encKey, 'base64') const macKey = Buffer.from (json.macKey, 'base64') +const harFile = JSON.parse ( fs.readFileSync( json.harFilePath , {encoding: 'utf-8'})) +const entries = harFile['log']['entries'] +let wsMessages: WSMessage[] = [] +entries.forEach ((e, i) => { + if ('_webSocketMessages' in e) { + wsMessages.push (...e['_webSocketMessages']) + } +}) const decrypt = buffer => { try { return decryptWA (buffer, macKey, encKey, new Decoder()) @@ -21,19 +33,16 @@ const decrypt = buffer => { } } -json.messages.forEach ((str, i) => { - const buffer = Buffer.from (str, 'base64') +console.log ('parsing ' + wsMessages.length + ' messages') +const list = wsMessages.map ((item, i) => { + const buffer = Buffer.from (item.data, 'base64') try { const [tag, json, binaryTags] = decrypt (buffer) - console.log ( - ` - ${i}. - messageTag: ${tag} - output: ${JSON.stringify(json)} - binaryTags: ${binaryTags} - ` - ) + return {tag, json: JSON.stringify(json), binaryTags} } catch (error) { console.error (`received error in decoding ${i}: ${error}`) + return null } -}) \ No newline at end of file +}) +const str = JSON.stringify (list, null, '\t') +fs.writeFileSync ('decoded-ws.json', str) \ No newline at end of file diff --git a/src/WAConnection/Constants.ts b/src/WAConnection/Constants.ts index 8745c86..369f0f7 100644 --- a/src/WAConnection/Constants.ts +++ b/src/WAConnection/Constants.ts @@ -78,6 +78,8 @@ export interface WAChat { } export enum WAMetric { liveLocation = 3, + queryMedia = 4, + queryMessages = 7, group = 10, message = 16, queryLiveLocation = 33,