diff --git a/src/WAClient/Constants.ts b/src/WAClient/Constants.ts index 6d4b5d5..a8b2cd2 100644 --- a/src/WAClient/Constants.ts +++ b/src/WAClient/Constants.ts @@ -33,6 +33,7 @@ export enum MessageType { sticker = 'stickerMessage', document = 'documentMessage', audio = 'audioMessage', + product = 'productMessage' } export enum ChatModification { archive='archive', diff --git a/src/WAClient/Messages.ts b/src/WAClient/Messages.ts index c95f5bd..6a0f090 100644 --- a/src/WAClient/Messages.ts +++ b/src/WAClient/Messages.ts @@ -18,6 +18,11 @@ import { validateJIDForSending, generateThumbnail, getMediaKeys } from './Utils' import { proto } from '../../WAMessage/WAMessage' export default class WhatsAppWebMessages extends WhatsAppWebBase { + /** Get the message info, who has read it, who its been delivered to */ + async messageInfo (jid: string, messageID: string) { + const query = ['query', {type: 'message_info', index: messageID, jid: jid, epoch: this.msgCount.toString()}, null] + return this.queryExpecting200 (query, [22, WAFlag.ignore]) + } /** * Send a read receipt to the given ID for a certain message * @param jid the ID of the person/group whose message you want to mark read @@ -67,6 +72,15 @@ export default class WhatsAppWebMessages extends WhatsAppWebBase { response.stamp = strStamp return response as {status: number, stamp: string} } + async loadMessage (jid: string, messageID: string) { + const messages = await this.loadConversation (jid, 1, {id: messageID, fromMe: false}, false) + var index = null + if (messages.length > 0) { + index = {id: messages[0].key.id, fromMe: false} + } + const actual = await this.loadConversation (jid, 1, index) + return actual[0] + } /** * Search WhatsApp messages with a given text string * @param txt the search string diff --git a/src/WAClient/Tests.ts b/src/WAClient/Tests.ts index cc7ca49..4060baa 100644 --- a/src/WAClient/Tests.ts +++ b/src/WAClient/Tests.ts @@ -171,8 +171,8 @@ WAClientTest('Groups', (client) => { await client.groupRemove(gid, [testJid]) }) it('should leave the group', async () => { - // await client.groupLeave(gid) - await client.groupCreatorAndParticipants ('919324993767-1593506879@g.us') + 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 9e1cc7a..57fa40c 100644 --- a/src/WAClient/Utils.ts +++ b/src/WAClient/Utils.ts @@ -87,13 +87,10 @@ export async function generateThumbnail(buffer: Buffer, mediaType: MessageType, } } /** - * Decode a media message (video, image, document, audio) & save it to the given file + * Decode a media message (video, image, document, audio) & return decrypted buffer * @param message the media message you want to decode - * @param filename the name of the file where the media will be saved - * @param attachExtension should the correct extension be applied automatically to the file */ -export async function decodeMediaMessage(message: WAMessageContent, filename: string, attachExtension: boolean=true) { - const getExtension = (mimetype) => mimetype.split(';')[0].split('/')[1] +export async function decodeMediaMessageBuffer(message: WAMessageContent) { /* One can infer media type from the key in the message it is usually written as [mediaType]Message. Eg. imageMessage, audioMessage etc. @@ -106,15 +103,17 @@ export async function decodeMediaMessage(message: WAMessageContent, filename: st throw new Error('cannot decode text message') } if (type === MessageType.location || type === MessageType.liveLocation) { - fs.writeFileSync(filename + '.jpeg', message[type].jpegThumbnail) - return { filename: filename + '.jpeg' } + return new Buffer(message[type].jpegThumbnail) } - const messageContent = message[type] as - | proto.VideoMessage - | proto.ImageMessage - | proto.AudioMessage - | proto.DocumentMessage - + let messageContent: proto.IVideoMessage | proto.IImageMessage | proto.IAudioMessage | proto.IDocumentMessage + if (message.productMessage) { + const product = message.productMessage.product?.productImage + if (!product) throw new Error ('product has no image') + messageContent = product + } else { + messageContent = message[type] + } + // download the message const fetched = await fetch(messageContent.url, { headers: { Origin: 'https://web.whatsapp.com' } }) const buffer = await fetched.buffer() @@ -146,13 +145,38 @@ export async function decodeMediaMessage(message: WAMessageContent, filename: st const decrypted = decryptedMedia (allTypes[i] as MessageType) if (i > 0) { console.log (`decryption of ${type} media with HKDF key of ${allTypes[i]}`) } - - const trueFileName = attachExtension ? (filename + '.' + getExtension(messageContent.mimetype)) : filename - fs.writeFileSync(trueFileName, decrypted) - return trueFileName + return decrypted } catch { if (i === 0) { console.log (`decryption of ${type} media with original HKDF key failed`) } } } throw new Error('HMAC sign does not match for ' + buffer.length) } +/** + * Decode a media message (video, image, document, audio) & save it to the given file + * @param message the media message you want to decode + * @param filename the name of the file where the media will be saved + * @param attachExtension should the correct extension be applied automatically to the file + */ +export async function decodeMediaMessage(message: WAMessageContent, filename: string, attachExtension: boolean=true) { + const getExtension = (mimetype) => mimetype.split(';')[0].split('/')[1] + + const buffer = await decodeMediaMessageBuffer (message) + const type = Object.keys(message)[0] as MessageType + let extension + if (type === MessageType.location || type === MessageType.liveLocation) { + extension = '.jpeg' + } else { + const messageContent = message[type] as + | proto.VideoMessage + | proto.ImageMessage + | proto.AudioMessage + | proto.DocumentMessage + extension = getExtension (messageContent.mimetype) + } + + const trueFileName = attachExtension ? (filename + '.' + extension) : filename + fs.writeFileSync(trueFileName, buffer) + return trueFileName +} + diff --git a/src/WAConnection/BrowserMessageDecoding.ts b/src/WAConnection/BrowserMessageDecoding.ts index ce3bc87..cb00ddc 100644 --- a/src/WAConnection/BrowserMessageDecoding.ts +++ b/src/WAConnection/BrowserMessageDecoding.ts @@ -40,8 +40,14 @@ const list = wsMessages.map ((item, i) => { const [tag, json, binaryTags] = decrypt (buffer) return {tag, json: JSON.stringify(json), binaryTags} } catch (error) { - console.error (`received error in decoding ${i}: ${error}`) - return null + try { + const [tag, json, binaryTags] = decrypt (item.data) + return {tag, json: JSON.stringify(json), binaryTags} + } catch (error) { + console.log ('error in decoding: ' + error) + return null + } + } }) const str = JSON.stringify (list, null, '\t')