From c121d17c12039e8e34f06bdd0fe7af9164ecf43f Mon Sep 17 00:00:00 2001 From: Adhiraj Date: Sun, 2 Aug 2020 13:45:27 +0530 Subject: [PATCH] Set Status + Set Group Description + Better Message Tags --- src/WAClient/Base.ts | 12 +++++++++++- src/WAClient/Groups.ts | 18 +++++++++++++++++- src/WAClient/Messages.ts | 13 +++++++------ src/WAClient/Tests.ts | 31 +++++++++++++++++++++++++++++-- src/WAConnection/Base.ts | 2 +- src/WAConnection/Constants.ts | 1 + src/WAConnection/Utils.ts | 16 ++++++++++++---- 7 files changed, 78 insertions(+), 15 deletions(-) diff --git a/src/WAClient/Base.ts b/src/WAClient/Base.ts index fde661f..5898f81 100644 --- a/src/WAClient/Base.ts +++ b/src/WAClient/Base.ts @@ -74,6 +74,15 @@ export default class WhatsAppWebBase extends WAConnection { async getStatus (jid?: string) { return this.query(['query', 'Status', jid || this.userMetaData.id]) as Promise<{ status: string }> } + async setStatus (status: string) { + return this.setQuery ([ + [ + 'status', + null, + Buffer.from (status, 'utf-8') + ] + ]) + } /** Get the URL to download the profile picture of a person/group */ async getProfilePicture(jid: string | null) { const response = await this.queryExpecting200(['query', 'ProfilePicThumb', jid || this.userMetaData.id]) @@ -204,6 +213,7 @@ export default class WhatsAppWebBase extends WAConnection { /** Generic function for action, set queries */ async setQuery (nodes: WANode[], binaryTags: WATag = [WAMetric.group, WAFlag.ignore], tag?: string) { const json = ['action', {epoch: this.msgCount.toString(), type: 'set'}, nodes] - return this.queryExpecting200(json, binaryTags, null, tag) as Promise<{status: number}> + const result = await this.queryExpecting200(json, binaryTags, null, tag) as Promise<{status: number}> + return result } } diff --git a/src/WAClient/Groups.ts b/src/WAClient/Groups.ts index bbc8c05..54cbfcc 100644 --- a/src/WAClient/Groups.ts +++ b/src/WAClient/Groups.ts @@ -1,6 +1,7 @@ import WhatsAppWebBase from './Base' import { WAMessage, WAMetric, WAFlag, WANode, WAGroupMetadata, WAGroupCreateResponse, WAGroupModification } from '../WAConnection/Constants' import { GroupSettingChange } from './Constants' +import { generateMessageID } from '../WAConnection/Utils' export default class WhatsAppWebGroups extends WhatsAppWebBase { /** Generic function for group queries */ @@ -17,7 +18,8 @@ export default class WhatsAppWebGroups extends WhatsAppWebBase { }, participants ? participants.map(str => ['participant', { jid: str }, null]) : additionalNodes, ] - return this.setQuery ([json], [WAMetric.group, WAFlag.ignore], tag) + const result = await this.setQuery ([json], [WAMetric.group, WAFlag.ignore], tag) + return result } /** Get the metadata of the group */ groupMetadata = (jid: string) => this.queryExpecting200(['query', 'GroupMetadata', jid]) as Promise @@ -58,6 +60,20 @@ export default class WhatsAppWebGroups extends WhatsAppWebBase { */ groupUpdateSubject = (jid: string, title: string) => this.groupQuery('subject', jid, title) as Promise<{ status: number }> + /** + * Update the group description + * @param {string} jid the ID of the group + * @param {string} title the new title of the group + */ + groupUpdateDescription = async (jid: string, description: string) => { + const metadata = await this.groupMetadata (jid) + const node: WANode = [ + 'description', + {id: generateMessageID(), prev: metadata?.descId}, + Buffer.from (description, 'utf-8') + ] + return this.groupQuery ('description', jid, null, null, [node]) + } /** * Add somebody to the group * @param jid the ID of the group diff --git a/src/WAClient/Messages.ts b/src/WAClient/Messages.ts index 95c4e79..dac2e23 100644 --- a/src/WAClient/Messages.ts +++ b/src/WAClient/Messages.ts @@ -254,7 +254,7 @@ export default class WhatsAppWebMessages extends WhatsAppWebGroups { return this.sendGenericMessage(id, m, options) } /** Prepare a media message for sending */ - protected async prepareMediaMessage(buffer: Buffer, mediaType: MessageType, options: MessageOptions = {}) { + async prepareMediaMessage(buffer: Buffer, mediaType: MessageType, options: MessageOptions = {}) { if (mediaType === MessageType.document && !options.mimetype) { throw new Error('mimetype required to send a document') } @@ -278,10 +278,10 @@ export default class WhatsAppWebMessages extends WhatsAppWebGroups { const fileSha256 = sha256(buffer) // url safe Base64 encode the SHA256 hash of the body const fileEncSha256B64 = sha256(body) - .toString('base64') - .replace(/\+/g, '-') - .replace(/\//g, '_') - .replace(/\=+$/, '') + .toString('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/\=+$/, '') await generateThumbnail(buffer, mediaType, options) // send a query JSON to obtain the url & auth token to upload our media @@ -311,6 +311,7 @@ export default class WhatsAppWebMessages extends WhatsAppWebGroups { fileLength: buffer.length, fileName: options.filename || 'file', gifPlayback: isGIF || null, + caption: options.caption } return message as WAMessageContent } @@ -332,13 +333,13 @@ export default class WhatsAppWebMessages extends WhatsAppWebGroups { message[key].contextInfo.participant = participant message[key].contextInfo.stanzaId = quoted.key.id message[key].contextInfo.quotedMessage = quoted.message + // if a participant is quoted, then it must be a group // hence, remoteJid of group must also be entered if (quoted.key.participant) { message[key].contextInfo.remoteJid = quoted.key.remoteJid } } - message[key].caption = options?.caption if (!message[key].jpegThumbnail) message[key].jpegThumbnail = options?.thumbnail const messageJSON = { diff --git a/src/WAClient/Tests.ts b/src/WAClient/Tests.ts index 667278c..b75a7f7 100644 --- a/src/WAClient/Tests.ts +++ b/src/WAClient/Tests.ts @@ -5,7 +5,8 @@ import * as assert from 'assert' import fetch from 'node-fetch' import { decodeMediaMessage, validateJIDForSending } from './Utils' -import { promiseTimeout, createTimeout, Browsers } from '../WAConnection/Utils' +import { promiseTimeout, createTimeout, Browsers, generateMessageTag } from '../WAConnection/Utils' +import { MessageLogLevel } 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 @@ -19,6 +20,8 @@ async function sendAndRetreiveMessage(client: WAClient, content, type: MessageTy function WAClientTest(name: string, func: (client: WAClient) => void) { describe(name, () => { const client = new WAClient() + client.logLevel = MessageLogLevel.info + before(async () => { const file = './auth_info.json' await client.connectSlim(file) @@ -121,9 +124,24 @@ WAClientTest('Misc', (client) => { }) it('should return the status', async () => { const response = await client.getStatus(testJid) - assert.ok(response.status) assert.strictEqual(typeof response.status, 'string') }) + it('should update status', async () => { + const newStatus = 'v cool status' + + const response = await client.getStatus() + assert.strictEqual(typeof response.status, 'string') + + await createTimeout (1000) + + await client.setStatus (newStatus) + const response2 = await client.getStatus() + assert.equal (response2.status, newStatus) + + await createTimeout (1000) + + await client.setStatus (response.status) // update back + }) it('should return the stories', async () => { await client.getStories() }) @@ -195,6 +213,15 @@ WAClientTest('Groups', (client) => { assert.strictEqual(metadata.id, gid) assert.strictEqual(metadata.participants.filter((obj) => obj.id.split('@')[0] === testJid.split('@')[0]).length, 1) }) + it('should update the group description', async () => { + const newDesc = 'Wow this was set from Baileys' + + await client.groupUpdateDescription (gid, newDesc) + await createTimeout (1000) + + const metadata = await client.groupMetadata(gid) + assert.strictEqual(metadata.desc, newDesc) + }) it('should send a message on the group', async () => { await client.sendMessage(gid, 'hello', MessageType.text) }) diff --git a/src/WAConnection/Base.ts b/src/WAConnection/Base.ts index bda21a8..d7dd5f8 100644 --- a/src/WAConnection/Base.ts +++ b/src/WAConnection/Base.ts @@ -326,7 +326,7 @@ export default class WAConnectionBase { } } generateMessageTag () { - return `${this.referenceDate.getTime()/1000}.--${this.msgCount}` + return `${Math.round(this.referenceDate.getTime())/1000}.--${this.msgCount}` } protected log(text, level: MessageLogLevel) { if (this.logLevel >= level) diff --git a/src/WAConnection/Constants.ts b/src/WAConnection/Constants.ts index 1b2d33c..9bc3745 100644 --- a/src/WAConnection/Constants.ts +++ b/src/WAConnection/Constants.ts @@ -61,6 +61,7 @@ export interface WAGroupMetadata { creation: number desc?: string descOwner?: string + descId?: string participants: [{ id: string; isAdmin: boolean; isSuperAdmin: boolean }] } export interface WAGroupModification { diff --git a/src/WAConnection/Utils.ts b/src/WAConnection/Utils.ts index 6887dec..7cefa3a 100644 --- a/src/WAConnection/Utils.ts +++ b/src/WAConnection/Utils.ts @@ -65,15 +65,23 @@ export function randomBytes(length) { return Crypto.randomBytes(length) } export const createTimeout = (timeout) => new Promise(resolve => setTimeout(resolve, timeout)) -export function promiseTimeout(ms: number, promise: Promise) { +export async function promiseTimeout(ms: number, promise: Promise) { if (!ms) return promise // Create a promise that rejects in milliseconds - const timeout = createTimeout (ms).then (() => { throw new BaileysError ('Timed out', promise) }) - return Promise.race([promise, timeout]) as Promise + let timeoutI + const timeout = new Promise( + (_, reject) => timeoutI = setTimeout(() => reject(new BaileysError ('Timed out', promise)), ms) + ) + try { + const content = await Promise.race([promise, timeout]) + return content as T + } finally { + clearTimeout (timeoutI) + } } // whatsapp requires a message tag for every message, we just use the timestamp as one export function generateMessageTag(epoch?: number) { - let tag = new Date().getTime().toString() + let tag = Math.round(new Date().getTime()/1000).toString() if (epoch) tag += '.--' + epoch // attach epoch if provided return tag }