diff --git a/src/WAClient/Base.ts b/src/WAClient/Base.ts index 519953a..ae8671c 100644 --- a/src/WAClient/Base.ts +++ b/src/WAClient/Base.ts @@ -1,5 +1,5 @@ import WAConnection from '../WAConnection/WAConnection' -import { MessageStatus, MessageStatusUpdate, PresenceUpdate, Presence } from './Constants' +import { MessageStatus, MessageStatusUpdate, PresenceUpdate, Presence, ChatModification } from './Constants' import { WAMessage, WANode, @@ -99,18 +99,6 @@ export default class WhatsAppWebBase extends WAConnection { const json = ['query', { epoch: this.msgCount.toString(), type: 'chat' }, null] return this.query(json, [WAMetric.group, WAFlag.ignore]) // this has to be an encrypted query } - /** - * Archive a given chat - * @param jid the ID of the person/group you are archiving - */ - async archiveChat(jid: string) { - const json = [ - 'action', - { epoch: this.msgCount.toString(), type: 'set' }, - [['chat', { type: 'archive', jid: jid }, null]], - ] - return this.queryExpecting200(json, [WAMetric.group, WAFlag.acknowledge]) as Promise<{ status: number }> - } /** * Check if your phone is connected * @param timeoutMs max time for the phone to respond @@ -189,8 +177,13 @@ export default class WhatsAppWebBase extends WAConnection { } return loadMessage() as Promise } + /** Generic function for action, set queries */ + async setQuery (nodes: WANode[]) { + const json = ['action', {epoch: this.msgCount.toString(), type: 'set'}, nodes] + return this.queryExpecting200(json, [WAMetric.group, WAFlag.ignore]) as Promise<{status: number}> + } /** Generic function for group queries */ - groupQuery(type: string, jid?: string, subject?: string, participants?: string[]) { + async groupQuery(type: string, jid?: string, subject?: string, participants?: string[]) { const json: WANode = [ 'group', { diff --git a/src/WAClient/Constants.ts b/src/WAClient/Constants.ts index 82423b3..c58a023 100644 --- a/src/WAClient/Constants.ts +++ b/src/WAClient/Constants.ts @@ -34,6 +34,14 @@ export enum MessageType { document = 'documentMessage', audio = 'audioMessage', } +export enum ChatModification { + archive='archive', + unarchive='unarchive', + pin='pin', + unpin='unpin', + mute='mute', + unmute='unmute' +} export const WAMessageType = function () { const types = proto.WebMessageInfo.WEB_MESSAGE_INFO_STUBTYPE const dict: Record = {} diff --git a/src/WAClient/Messages.ts b/src/WAClient/Messages.ts index f4cadbc..c90d56e 100644 --- a/src/WAClient/Messages.ts +++ b/src/WAClient/Messages.ts @@ -10,6 +10,7 @@ import { WAContactMessage, WASendMessageResponse, WAMessageKey, + ChatModification, } from './Constants' import { generateMessageID, sha256, hmacSign, aesEncrypWithIV, randomBytes } from '../WAConnection/Utils' import { WAMessageContent, WAMetric, WAFlag, WANode, WAMessage } from '../WAConnection/Constants' @@ -20,15 +21,43 @@ export default class WhatsAppWebMessages extends WhatsAppWebBase { /** * Send a read receipt to the given ID for a certain message * @param {string} jid the ID of the person/group whose message you want to mark read - * @param {string} messageID the message ID + * @param {string} [messageID] optionally, the message ID */ - sendReadReceipt(jid: string, messageID: string) { - const json = [ - 'action', - { epoch: this.msgCount.toString(), type: 'set' }, - [['read', { count: '1', index: messageID, jid: jid, owner: 'false' }, null]], - ] - return this.queryExpecting200(json, [WAMetric.group, WAFlag.ignore]) // encrypt and send off + async sendReadReceipt(jid: string, messageID?: string, type: 'read' | 'unread' = 'read') { + const attributes = { + jid: jid, + count: messageID ? '1' : null, + index: messageID, + owner: 'false', + type: type==='unread' && 'false' + } + return this.setQuery ([['read', attributes, null]]) + } + /** Mark a given chat as unread */ + async markChatUnread (jid: string) { return this.sendReadReceipt (jid, null, 'unread') } + async archiveChat (jid: string) { return this.modifyChat (jid, ChatModification.archive) } + /** + * Modify a given chat (archive, pin etc.) + * @param jid the ID of the person/group you are modifiying + */ + async modifyChat (jid: string, type: ChatModification, options: {stamp: Date} = {stamp: new Date()}) { + let chatAttrs: Record = {jid: jid} + switch (type) { + case ChatModification.pin: + case ChatModification.mute: + chatAttrs.type = type + chatAttrs[type] = Math.round(options.stamp.getTime ()/1000).toString () + break + case ChatModification.unpin: + case ChatModification.unmute: + chatAttrs.type = type.replace ('un', '') // replace 'unpin' with 'pin' + chatAttrs.previous = Math.round(options.stamp.getTime ()/1000).toString () + break + default: + chatAttrs.type = type + break + } + return this.setQuery ([['chat', chatAttrs, null]]) } /** * Search WhatsApp messages with a given text string @@ -52,18 +81,6 @@ export default class WhatsAppWebMessages extends WhatsAppWebBase { const messages = response[2] ? response[2].map (row => row[2]) : [] return { last: response[1]['last'] === 'true', messages: messages as WAMessage[] } } - /** - * Mark a given chat as unread - * @param jid - */ - async markChatUnread (jid: string) { - const json = [ - 'action', - { epoch: this.msgCount.toString(), type: 'set' }, - [['read', {jid: jid, type: 'false', count: '1'}, null]] - ] - return this.queryExpecting200(json, [WAMetric.group, WAFlag.ignore]) as Promise<{ status: number }> - } /** * Delete a message in a chat * @param id the person or group where you're trying to delete the message diff --git a/src/WAClient/Tests.ts b/src/WAClient/Tests.ts index 2f7df87..98e8714 100644 --- a/src/WAClient/Tests.ts +++ b/src/WAClient/Tests.ts @@ -1,5 +1,5 @@ import { WAClient } from './WAClient' -import { MessageType, MessageOptions, Mimetype, Presence } from './Constants' +import { MessageType, MessageOptions, Mimetype, Presence, ChatModification } from './Constants' import * as fs from 'fs' import * as assert from 'assert' @@ -110,8 +110,27 @@ WAClientTest('Misc', (client) => { assert.rejects(client.getProfilePicture('abcd@s.whatsapp.net')) }) it('should mark a chat unread', async () => { - const response = await client.markChatUnread(testJid) - assert.ok(response) + await client.sendReadReceipt(testJid, null, 'unread') + }) + it('should archive & unarchive', async () => { + await client.modifyChat (testJid, ChatModification.archive) + await createTimeout (2000) + await client.modifyChat (testJid, ChatModification.unarchive) + }) + it('should pin & unpin a chat', async () => { + const pindate = new Date() + await client.modifyChat (testJid, ChatModification.pin, {stamp: pindate}) + await createTimeout (2000) + await client.modifyChat (testJid, ChatModification.unpin, {stamp: pindate}) + }) + it('should mute & unmute a chat', async () => { + const mutedate = new Date (new Date().getTime() + 8*60*60*1000) // 8 hours in the future + await client.modifyChat (testJid, ChatModification.mute, {stamp: mutedate}) + await createTimeout (2000) + await client.modifyChat (testJid, ChatModification.unmute, {stamp: mutedate}) + }) + it('should unpin a chat', async () => { + await client.modifyChat (testJid, ChatModification.unpin) }) it('should return search results', async () => { const response = await client.searchMessages('Adh', 25, 0)