From cadd34bc0eb1680c312063af8f7454019d2829db Mon Sep 17 00:00:00 2001 From: Adhiraj Date: Mon, 31 Aug 2020 16:02:09 +0530 Subject: [PATCH] protobuf uniformity + messages bug fix --- src/Tests/Tests.Messages.ts | 33 +++++++---- src/WAConnection/4.Events.ts | 95 +++++++++++++++++------------- src/WAConnection/5.User.ts | 6 +- src/WAConnection/6.MessagesSend.ts | 8 +-- 4 files changed, 81 insertions(+), 61 deletions(-) diff --git a/src/Tests/Tests.Messages.ts b/src/Tests/Tests.Messages.ts index 6f3db94..3c0c008 100644 --- a/src/Tests/Tests.Messages.ts +++ b/src/Tests/Tests.Messages.ts @@ -1,19 +1,19 @@ -import { MessageType, Mimetype, delay, promiseTimeout, WAMessage, WA_MESSAGE_STATUS_TYPE, WAMessageStatusUpdate } from '../WAConnection/WAConnection' +import { MessageType, Mimetype, delay, promiseTimeout, WAMessage, WA_MESSAGE_STATUS_TYPE, WAMessageStatusUpdate, MessageLogLevel } from '../WAConnection/WAConnection' import {promises as fs} from 'fs' import * as assert from 'assert' import { WAConnectionTest, testJid, sendAndRetreiveMessage } from './Common' -WAConnectionTest('Messages', (conn) => { +WAConnectionTest('Messages', conn => { it('should send a text message', async () => { - //const message = await sendAndRetreiveMessage(conn, 'hello fren', MessageType.text) - //assert.strictEqual(message.message.conversation || message.message.extendedTextMessage?.text, 'hello fren') + const message = await sendAndRetreiveMessage(conn, 'hello fren', MessageType.text) + assert.strictEqual(message.message.conversation || message.message.extendedTextMessage?.text, 'hello fren') }) it('should forward a message', async () => { - let messages = await conn.loadMessages (testJid, 1) + let {messages} = await conn.loadMessages (testJid, 1) await conn.forwardMessage (testJid, messages[0], true) - messages = await conn.loadMessages (testJid, 1) - const message = messages[0] + messages = (await conn.loadMessages (testJid, 1)).messages + const message = messages.slice (-1)[0] const content = message.message[ Object.keys(message.message)[0] ] assert.equal (content?.contextInfo?.isForwarded, true) }) @@ -25,7 +25,7 @@ WAConnectionTest('Messages', (conn) => { assert.strictEqual(received.text, content.text) assert.ok (received.canonicalUrl) assert.ok (received.title) - assert.ok (received.jpegThumbnail) + assert.ok (received.description) }) it('should quote a message', async () => { const {messages} = await conn.loadMessages(testJid, 2) @@ -52,7 +52,7 @@ WAConnectionTest('Messages', (conn) => { //const message2 = await sendAndRetreiveMessage (conn, 'this is a quote', MessageType.extendedText) }) it('should send an image & quote', async () => { - const messages = await conn.loadMessages(testJid, 1) + const {messages} = await conn.loadMessages(testJid, 1) const content = await fs.readFile('./Media/meme.jpeg') const message = await sendAndRetreiveMessage(conn, content, MessageType.image, { quoted: messages[0] }) @@ -65,12 +65,21 @@ WAConnectionTest('Messages', (conn) => { await conn.deleteMessage (testJid, message.key) }) it('should clear the most recent message', async () => { - const messages = await conn.loadMessages (testJid, 1) + const {messages} = await conn.loadMessages (testJid, 1) await delay (2000) await conn.clearMessage (messages[0].key) }) -}) -WAConnectionTest('Message Events', (conn) => { + it('should fail to send a text message', done => { + const JID = '1234-1234@g.us' + conn.sendMessage(JID, 'hello', MessageType.text) + + conn.on ('message-status-update', update => { + if (update.to === JID) { + assert.equal (update.type, WA_MESSAGE_STATUS_TYPE.ERROR) + done () + } + }) + }) it('should deliver a message', async () => { const waitForUpdate = promiseTimeout(15000, resolve => { diff --git a/src/WAConnection/4.Events.ts b/src/WAConnection/4.Events.ts index ed17cd8..44eee7f 100644 --- a/src/WAConnection/4.Events.ts +++ b/src/WAConnection/4.Events.ts @@ -7,11 +7,16 @@ export class WAConnection extends Base { constructor () { super () - - this.registerOnMessageStatusChange () - this.registerOnUnreadMessage () - this.registerOnPresenceUpdate () + // new messages + this.registerCallback(['action', 'add:relay', 'message'], json => { + const message = json[2][0][2] as WAMessage + this.chatAddMessageAppropriate (message) + }) + // presence updates + this.registerCallback('Presence', json => ( + this.emit('user-presence-update', json[1] as PresenceUpdate) + )) // If a message has been updated (usually called when a video message gets its upload url) this.registerCallback (['action', 'add:update', 'message'], json => { const message: WAMessage = json[2][0][2] @@ -105,6 +110,40 @@ export class WAConnection extends Base { this.emit ('chat-update', { jid: chat.jid, count: chat.count }) }) + this.registerCallback (['action', 'add:relay', 'received'], json => { + json = json[2][0][1] + if (json.type === 'error') { + const update: WAMessageStatusUpdate = { + from: this.user.jid, + to: json.jid, + participant: this.user.jid, + timestamp: new Date(), + ids: [ json.index ], + type: WA_MESSAGE_STATUS_TYPE.ERROR, + } + this.forwardStatusUpdate (update) + } + + }) + + const func = json => { + json = json[1] + let ids = json.id + + if (json.cmd === 'ack') ids = [json.id] + + const update: WAMessageStatusUpdate = { + from: json.from, + to: json.to, + participant: json.participant, + timestamp: new Date(json.t * 1000), + ids: ids, + type: (+json.ack)+1, + } + this.forwardStatusUpdate (update) + } + this.registerCallback('Msg', func) + this.registerCallback('MsgInfo', func) /*// genetic chat action this.registerCallback (['Chat', 'cmd:action'], json => { const data = json[1].data as WANode @@ -121,44 +160,15 @@ export class WAConnection extends Base { } /** Get the URL to download the profile picture of a person/group */ async getProfilePicture(jid: string | null) { - const response = await this.query({ json: ['query', 'ProfilePicThumb', jid || this.user.jid] }) + const response = await this.query({ json: ['query', 'ProfilePicThumb', jid || this.user.jid], expect200: true }) return response.eurl as string } - /** Set the callback for message status updates (when a message is delivered, read etc.) */ - protected registerOnMessageStatusChange() { - const func = json => { - json = json[1] - let ids = json.id - - if (json.cmd === 'ack') ids = [json.id] - - const update: WAMessageStatusUpdate = { - from: json.from, - to: json.to, - participant: json.participant, - timestamp: new Date(json.t * 1000), - ids: ids, - type: (+json.ack)+1, - } - - const chat = this.chats.get( whatsappID(update.to) ) - if (!chat) return - - this.emit ('message-status-update', update) - this.chatUpdatedMessage (update.ids, update.type as number, chat) - } - this.registerCallback('Msg', func) - this.registerCallback('MsgInfo', func) - } - protected registerOnUnreadMessage() { - this.registerCallback(['action', 'add:relay', 'message'], json => { - const message = json[2][0][2] as WAMessage - this.chatAddMessageAppropriate (message) - }) - } - /** Set the callback for presence updates; if someone goes offline/online, this callback will be fired */ - protected registerOnPresenceUpdate() { - this.registerCallback('Presence', json => this.emit('user-presence-update', json[1] as PresenceUpdate)) + protected forwardStatusUpdate (update: WAMessageStatusUpdate) { + const chat = this.chats.get( whatsappID(update.to) ) + if (!chat) return + + this.emit ('message-status-update', update) + this.chatUpdatedMessage (update.ids, update.type as number, chat) } /** inserts an empty chat into the DB */ protected async chatAdd (jid: string, name?: string) { @@ -265,10 +275,11 @@ export class WAConnection extends Base { } } } - protected chatUpdatedMessage (messageIDs: string[], status: number, chat: WAChat) { + protected chatUpdatedMessage (messageIDs: string[], status: WA_MESSAGE_STATUS_TYPE, chat: WAChat) { for (let msg of chat.messages) { if (messageIDs.includes(msg.key.id)) { - if (isGroupID(chat.jid)) msg.status = WA_MESSAGE_STATUS_TYPE.SERVER_ACK + if (status <= WA_MESSAGE_STATUS_TYPE.PENDING) msg.status = status + else if (isGroupID(chat.jid)) msg.status = status-1 else msg.status = status } } diff --git a/src/WAConnection/5.User.ts b/src/WAConnection/5.User.ts index 2562168..1bc91ca 100644 --- a/src/WAConnection/5.User.ts +++ b/src/WAConnection/5.User.ts @@ -34,7 +34,7 @@ export class WAConnection extends Base { requestPresenceUpdate = async (jid: string) => this.query({json: ['action', 'presence', 'subscribe', jid]}) /** Query the status of the person (see groupMetadata() for groups) */ async getStatus (jid?: string) { - const status: { status: string } = await this.query({json: ['query', 'Status', jid || this.user.jid]}) + const status: { status: string } = await this.query({json: ['query', 'Status', jid || this.user.jid], expect200: true}) return status } async setStatus (status: string) { @@ -53,7 +53,7 @@ export class WAConnection extends Base { /** Get your contacts */ async getContacts() { const json = ['query', { epoch: this.msgCount.toString(), type: 'contacts' }, null] - const response = await this.query({ json, binaryTags: [6, WAFlag.ignore] }) // this has to be an encrypted query + const response = await this.query({ json, binaryTags: [6, WAFlag.ignore], expect200: true }) // this has to be an encrypted query return response } /** Get the stories of your contacts */ @@ -74,7 +74,7 @@ export class WAConnection extends Base { /** Fetch your chats */ async getChats() { const json = ['query', { epoch: this.msgCount.toString(), type: 'chat' }, null] - return this.query({ json, binaryTags: [5, WAFlag.ignore]}) // this has to be an encrypted query + return this.query({ json, binaryTags: [5, WAFlag.ignore], expect200: true }) // this has to be an encrypted query } /** Query broadcast list info */ async getBroadcastListInfo(jid: string) { return this.query({json: ['query', 'contact', jid], expect200: true}) as Promise } diff --git a/src/WAConnection/6.MessagesSend.ts b/src/WAConnection/6.MessagesSend.ts index 27d9308..5970e9d 100644 --- a/src/WAConnection/6.MessagesSend.ts +++ b/src/WAConnection/6.MessagesSend.ts @@ -10,7 +10,7 @@ import { WALocationMessage, WAContactMessage, WATextMessage, - WAMessageContent, WAMetric, WAFlag, WAMessage, BaileysError, MessageLogLevel, WA_MESSAGE_STATUS_TYPE + WAMessageContent, WAMetric, WAFlag, WAMessage, BaileysError, MessageLogLevel, WA_MESSAGE_STATUS_TYPE, WAMessageProto } from './Constants' import { generateMessageID, sha256, hmacSign, aesEncrypWithIV, randomBytes, generateThumbnail, getMediaKeys, decodeMediaMessageBuffer, extensionForMediaMessage, whatsappID, unixTimestampSeconds } from './Utils' @@ -72,7 +72,7 @@ export class WAConnection extends Base { m = await this.prepareMessageMedia(message as Buffer, type, options) break } - return m + return WAMessageProto.Message.create (m) } /** Prepare a media message for sending */ async prepareMessageMedia(buffer: Buffer, mediaType: MessageType, options: MessageOptions = {}) { @@ -177,13 +177,13 @@ export class WAConnection extends Base { participant: id.includes('@g.us') ? this.user.jid : null, status: WA_MESSAGE_STATUS_TYPE.PENDING } - return messageJSON as WAMessage + return WAMessageProto.WebMessageInfo.create (messageJSON) } /** Relay (send) a WAMessage; more advanced functionality to send a built WA Message, you may want to stick with sendMessage() */ async relayWAMessage(message: WAMessage) { const json = ['action', {epoch: this.msgCount.toString(), type: 'relay'}, [['message', null, message]]] const flag = message.key.remoteJid === this.user.jid ? WAFlag.acknowledge : WAFlag.ignore // acknowledge when sending message to oneself - await this.query({json, binaryTags: [WAMetric.message, flag], tag: message.key.id}) + await this.query({json, binaryTags: [WAMetric.message, flag], tag: message.key.id, expect200: true}) await this.chatAddMessageAppropriate (message) } /**