clearMessage & update on modifyChat & support for browser credentials

This commit is contained in:
Adhiraj Singh
2020-07-09 13:00:36 +05:30
parent 583c2adf28
commit 7a2a5e03a8
7 changed files with 112 additions and 29 deletions

View File

@@ -39,25 +39,34 @@ export default class WhatsAppWebMessages extends WhatsAppWebBase {
/**
* Modify a given chat (archive, pin etc.)
* @param jid the ID of the person/group you are modifiying
* @param options.stamp the timestamp of pinning/muting the chat. Is required when unpinning/unmuting
*/
async modifyChat (jid: string, type: ChatModification, options: {stamp: Date} = {stamp: new Date()}) {
async modifyChat (jid: string, type: ChatModification, options: {stamp: Date | string} = {stamp: new Date()}) {
let chatAttrs: Record<string, string> = {jid: jid}
if ((type === ChatModification.unpin || type === ChatModification.unmute) && !options?.stamp) {
throw 'options.stamp must be set to the timestamp of the time of pinning/unpinning of the chat'
}
const strStamp = options.stamp &&
(typeof options.stamp === 'string' ? options.stamp : Math.round(options.stamp.getTime ()/1000).toString ())
switch (type) {
case ChatModification.pin:
case ChatModification.mute:
chatAttrs.type = type
chatAttrs[type] = Math.round(options.stamp.getTime ()/1000).toString ()
chatAttrs[type] = strStamp
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 ()
chatAttrs.previous = strStamp
break
default:
chatAttrs.type = type
break
}
return this.setQuery ([['chat', chatAttrs, null]])
console.log (chatAttrs)
let response = await this.setQuery ([['chat', chatAttrs, null]]) as any
response.stamp = strStamp
return response as {status: number, stamp: string}
}
/**
* Search WhatsApp messages with a given text string
@@ -82,7 +91,22 @@ export default class WhatsAppWebMessages extends WhatsAppWebBase {
return { last: response[1]['last'] === 'true', messages: messages as WAMessage[] }
}
/**
* Delete a message in a chat
* Delete a message in a chat for yourself
* @param messageKey key of the message you want to delete
*/
async clearMessage (messageKey: WAMessageKey) {
const tag = Math.round(Math.random ()*1000000)
const attrs: WANode = [
'chat',
{ jid: messageKey.remoteJid, modify_tag: tag.toString(), type: 'clear' },
[
['item', {owner: `${messageKey.fromMe}`, index: messageKey.id}, null]
]
]
return this.setQuery ([attrs])
}
/**
* Delete a message in a chat for everyone
* @param id the person or group where you're trying to delete the message
* @param messageKey key of the message you want to delete
*/

View File

@@ -62,10 +62,14 @@ WAClientTest('Messages', (client) => {
})
it('should send a text message & delete it', async () => {
const message = await sendAndRetreiveMessage(client, 'hello fren', MessageType.text)
assert.strictEqual(message.message.conversation, 'hello fren')
await createTimeout (2000)
await client.deleteMessage (testJid, message.key)
})
it('should clear the most recent message', async () => {
const messages = await client.loadConversation (testJid, 1)
await createTimeout (2000)
await client.clearMessage (messages[0].key)
})
})
describe('Validate WhatsApp IDs', () => {
@@ -118,10 +122,9 @@ WAClientTest('Misc', (client) => {
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})
const response = await client.modifyChat (testJid, ChatModification.pin)
await createTimeout (2000)
await client.modifyChat (testJid, ChatModification.unpin, {stamp: pindate})
await client.modifyChat (testJid, ChatModification.unpin, {stamp: response.stamp})
})
it('should mute & unmute a chat', async () => {
const mutedate = new Date (new Date().getTime() + 8*60*60*1000) // 8 hours in the future
@@ -129,9 +132,6 @@ WAClientTest('Misc', (client) => {
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)
assert.ok (response.messages)

View File

@@ -4,7 +4,7 @@ import WS from 'ws'
import * as Utils from './Utils'
import Encoder from '../Binary/Encoder'
import Decoder from '../Binary/Decoder'
import { AuthenticationCredentials, UserMetaData, WANode, AuthenticationCredentialsBase64, WATag, MessageLogLevel } from './Constants'
import { AuthenticationCredentials, UserMetaData, WANode, AuthenticationCredentialsBase64, WATag, MessageLogLevel, AuthenticationCredentialsBrowser } from './Constants'
/** Generate a QR code from the ref & the curve public key. This is scanned by the phone */
@@ -82,6 +82,27 @@ export default class WAConnectionBase {
macKey: Buffer.from(authInfo.macKey, 'base64'), // decode from base64
}
}
/**
* Load in the authentication credentials
* @param authInfo the authentication credentials or path to browser credentials JSON
*/
loadAuthInfoFromBrowser(authInfo: AuthenticationCredentialsBrowser | string) {
if (!authInfo) {
throw 'given authInfo is null'
}
if (typeof authInfo === 'string') {
this.log(`loading authentication credentials from ${authInfo}`)
const file = fs.readFileSync(authInfo, { encoding: 'utf-8' }) // load a closed session back if it exists
authInfo = JSON.parse(file) as AuthenticationCredentialsBrowser
}
this.authInfo = {
clientID: authInfo.WABrowserId.replace (/\"/g, ''),
serverToken: authInfo.WAToken2.replace (/\"/g, ''),
clientToken: authInfo.WAToken1.replace (/\"/g, ''),
encKey: Buffer.from(authInfo.WASecretBundle.encKey, 'base64'), // decode from base64
macKey: Buffer.from(authInfo.WASecretBundle.macKey, 'base64'), // decode from base64
}
}
/**
* Register for a callback for a certain function, will cancel automatically after one execution
* @param {[string, object, string] | string} parameters name of the function along with some optional specific parameters

View File

@@ -20,6 +20,12 @@ export interface AuthenticationCredentialsBase64 {
encKey: string
macKey: string
}
export interface AuthenticationCredentialsBrowser {
WABrowserId: string
WASecretBundle: {encKey: string, macKey: string}
WAToken1: string
WAToken2: string
}
export interface UserMetaData {
id: string
name: string
@@ -58,6 +64,8 @@ export interface WAChat {
count: number
archive?: 'true' | 'false'
read_only?: 'true' | 'false'
mute?: string
pin?: string
spam: 'false' | 'true'
jid: string
modify_tag: string

View File

@@ -16,7 +16,6 @@ export default class WAConnectionValidator extends WAConnectionBase {
macKey: null,
}
}
const data = ['admin', 'init', this.version, this.browserDescription, this.authInfo.clientID, true]
return this.query(data)
.then((json) => {
@@ -42,15 +41,17 @@ export default class WAConnectionValidator extends WAConnectionBase {
}
})
.then((json) => {
switch (json.status) {
case 401: // if the phone was unpaired
throw [json.status, 'unpaired from phone', json]
case 429: // request to login was denied, don't know why it happens
throw [json.status, 'request denied, try reconnecting', json]
case 304: // request to generate a new key for a QR code was denied
throw [json.status, 'request for new key denied', json]
default:
break
if ('status' in json) {
switch (json.status) {
case 401: // if the phone was unpaired
throw [json.status, 'unpaired from phone', json]
case 429: // request to login was denied, don't know why it happens
throw [json.status, 'request denied, try reconnecting', json]
case 304: // request to generate a new key for a QR code was denied
throw [json.status, 'request for new key denied', json]
default:
throw [json.status, 'unknown error status', json]
}
}
if (json[1] && json[1].challenge) {
// if its a challenge request (we get it when logging in)