mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
clearMessage & update on modifyChat & support for browser credentials
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,4 +5,5 @@ package-lock.json
|
|||||||
*/.DS_Store
|
*/.DS_Store
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env
|
.env
|
||||||
lib
|
lib
|
||||||
|
auth_info_browser.json
|
||||||
|
|||||||
38
README.md
38
README.md
@@ -19,7 +19,7 @@ To run the example script, download or clone the repo and then type the followin
|
|||||||
## Install
|
## Install
|
||||||
Create and cd to your NPM project directory and then in terminal, write:
|
Create and cd to your NPM project directory and then in terminal, write:
|
||||||
1. stable: `npm install @adiwajshing/baileys`
|
1. stable: `npm install @adiwajshing/baileys`
|
||||||
2. stabl-ish (but quicker fixes): `npm install github:adiwajshing/baileys`
|
2. stabl-ish (but quicker fixes & latest features): `npm install github:adiwajshing/baileys`
|
||||||
|
|
||||||
Then import in your code using:
|
Then import in your code using:
|
||||||
``` ts
|
``` ts
|
||||||
@@ -86,9 +86,22 @@ client.connectSlim('./auth_info.json') // will load JSON credentials from file
|
|||||||
.then (user => {
|
.then (user => {
|
||||||
// yay connected without scanning QR
|
// yay connected without scanning QR
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
Optionally, you can load the credentials yourself from somewhere
|
||||||
|
& pass in the JSON object to connectSlim () as well.
|
||||||
|
*/
|
||||||
```
|
```
|
||||||
|
|
||||||
Optionally, you can load the credentials yourself from somewhere & pass in the JSON object as well.
|
If you're considering switching from a Chromium/Puppeteer based library, you can use WhatsApp Web's Browser credentials to restore sessions too:
|
||||||
|
``` ts
|
||||||
|
client.loadAuthInfoFromBrowser ('./auth_info_browser.json')
|
||||||
|
client.connectSlim(null, 20*1000) // use loaded credentials & timeout in 20s
|
||||||
|
.then (user => {
|
||||||
|
// yay! connected using browser keys & without scanning QR
|
||||||
|
})
|
||||||
|
```
|
||||||
|
See the browser credentials type [here](/src/WAConnection/Constants.ts).
|
||||||
|
|
||||||
## Handling Events
|
## Handling Events
|
||||||
Implement the following callbacks in your code:
|
Implement the following callbacks in your code:
|
||||||
@@ -217,11 +230,26 @@ client.setOnUnreadMessage (false, async m => {
|
|||||||
const jid = '1234@s.whatsapp.net' // can also be a group
|
const jid = '1234@s.whatsapp.net' // can also be a group
|
||||||
const response = await client.sendMessage (jid, 'hello!', MessageType.text) // send a message
|
const response = await client.sendMessage (jid, 'hello!', MessageType.text) // send a message
|
||||||
await client.deleteMessage (jid, {id: response.messageID, remoteJid: jid, fromMe: true}) // will delete the sent message!
|
await client.deleteMessage (jid, {id: response.messageID, remoteJid: jid, fromMe: true}) // will delete the sent message!
|
||||||
|
|
||||||
// You can also archive a chat using:
|
|
||||||
await client.archiveChat(jid)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Modifying Chats
|
||||||
|
|
||||||
|
``` ts
|
||||||
|
const jid = '1234@s.whatsapp.net' // can also be a group
|
||||||
|
await client.modifyChat (jid, ChatModification.archive) // archive chat
|
||||||
|
await client.modifyChat (jid, ChatModification.unarchive) // unarchive chat
|
||||||
|
|
||||||
|
const response = await client.modifyChat (jid, ChatModification.pin) // pin the chat
|
||||||
|
await client.modifyChat (jid, ChatModification.unarchive, {stamp: response.stamp})
|
||||||
|
|
||||||
|
const mutedate = new Date (new Date().getTime() + 8*60*60*1000) // mute for 8 hours in the future
|
||||||
|
await client.modifyChat (jid, ChatModification.mute, {stamp: mutedate}) // mute
|
||||||
|
setTimeout (() => {
|
||||||
|
client.modifyChat (jid, ChatModification.unmute, {stamp: mutedate})
|
||||||
|
}, 5000) // unmute after 5 seconds
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** to unmute or unpin a chat, one must pass the timestamp of the pinning or muting. This is returned by the pin & mute functions. This is also available in the `WAChat` objects of the respective chats, as a `mute` or `pin` property.
|
||||||
## Querying
|
## Querying
|
||||||
- To check if a given ID is on WhatsApp
|
- To check if a given ID is on WhatsApp
|
||||||
``` ts
|
``` ts
|
||||||
|
|||||||
@@ -39,25 +39,34 @@ export default class WhatsAppWebMessages extends WhatsAppWebBase {
|
|||||||
/**
|
/**
|
||||||
* Modify a given chat (archive, pin etc.)
|
* Modify a given chat (archive, pin etc.)
|
||||||
* @param jid the ID of the person/group you are modifiying
|
* @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}
|
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) {
|
switch (type) {
|
||||||
case ChatModification.pin:
|
case ChatModification.pin:
|
||||||
case ChatModification.mute:
|
case ChatModification.mute:
|
||||||
chatAttrs.type = type
|
chatAttrs.type = type
|
||||||
chatAttrs[type] = Math.round(options.stamp.getTime ()/1000).toString ()
|
chatAttrs[type] = strStamp
|
||||||
break
|
break
|
||||||
case ChatModification.unpin:
|
case ChatModification.unpin:
|
||||||
case ChatModification.unmute:
|
case ChatModification.unmute:
|
||||||
chatAttrs.type = type.replace ('un', '') // replace 'unpin' with 'pin'
|
chatAttrs.type = type.replace ('un', '') // replace 'unpin' with 'pin'
|
||||||
chatAttrs.previous = Math.round(options.stamp.getTime ()/1000).toString ()
|
chatAttrs.previous = strStamp
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
chatAttrs.type = type
|
chatAttrs.type = type
|
||||||
break
|
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
|
* 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[] }
|
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 id the person or group where you're trying to delete the message
|
||||||
* @param messageKey key of the message you want to delete
|
* @param messageKey key of the message you want to delete
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -62,10 +62,14 @@ WAClientTest('Messages', (client) => {
|
|||||||
})
|
})
|
||||||
it('should send a text message & delete it', async () => {
|
it('should send a text message & delete it', async () => {
|
||||||
const message = await sendAndRetreiveMessage(client, 'hello fren', MessageType.text)
|
const message = await sendAndRetreiveMessage(client, 'hello fren', MessageType.text)
|
||||||
assert.strictEqual(message.message.conversation, 'hello fren')
|
|
||||||
await createTimeout (2000)
|
await createTimeout (2000)
|
||||||
await client.deleteMessage (testJid, message.key)
|
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', () => {
|
describe('Validate WhatsApp IDs', () => {
|
||||||
@@ -118,10 +122,9 @@ WAClientTest('Misc', (client) => {
|
|||||||
await client.modifyChat (testJid, ChatModification.unarchive)
|
await client.modifyChat (testJid, ChatModification.unarchive)
|
||||||
})
|
})
|
||||||
it('should pin & unpin a chat', async () => {
|
it('should pin & unpin a chat', async () => {
|
||||||
const pindate = new Date()
|
const response = await client.modifyChat (testJid, ChatModification.pin)
|
||||||
await client.modifyChat (testJid, ChatModification.pin, {stamp: pindate})
|
|
||||||
await createTimeout (2000)
|
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 () => {
|
it('should mute & unmute a chat', async () => {
|
||||||
const mutedate = new Date (new Date().getTime() + 8*60*60*1000) // 8 hours in the future
|
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 createTimeout (2000)
|
||||||
await client.modifyChat (testJid, ChatModification.unmute, {stamp: mutedate})
|
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 () => {
|
it('should return search results', async () => {
|
||||||
const response = await client.searchMessages('Adh', 25, 0)
|
const response = await client.searchMessages('Adh', 25, 0)
|
||||||
assert.ok (response.messages)
|
assert.ok (response.messages)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import WS from 'ws'
|
|||||||
import * as Utils from './Utils'
|
import * as Utils from './Utils'
|
||||||
import Encoder from '../Binary/Encoder'
|
import Encoder from '../Binary/Encoder'
|
||||||
import Decoder from '../Binary/Decoder'
|
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 */
|
/** 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
|
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
|
* 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
|
* @param {[string, object, string] | string} parameters name of the function along with some optional specific parameters
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ export interface AuthenticationCredentialsBase64 {
|
|||||||
encKey: string
|
encKey: string
|
||||||
macKey: string
|
macKey: string
|
||||||
}
|
}
|
||||||
|
export interface AuthenticationCredentialsBrowser {
|
||||||
|
WABrowserId: string
|
||||||
|
WASecretBundle: {encKey: string, macKey: string}
|
||||||
|
WAToken1: string
|
||||||
|
WAToken2: string
|
||||||
|
}
|
||||||
export interface UserMetaData {
|
export interface UserMetaData {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
@@ -58,6 +64,8 @@ export interface WAChat {
|
|||||||
count: number
|
count: number
|
||||||
archive?: 'true' | 'false'
|
archive?: 'true' | 'false'
|
||||||
read_only?: 'true' | 'false'
|
read_only?: 'true' | 'false'
|
||||||
|
mute?: string
|
||||||
|
pin?: string
|
||||||
spam: 'false' | 'true'
|
spam: 'false' | 'true'
|
||||||
jid: string
|
jid: string
|
||||||
modify_tag: string
|
modify_tag: string
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ export default class WAConnectionValidator extends WAConnectionBase {
|
|||||||
macKey: null,
|
macKey: null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = ['admin', 'init', this.version, this.browserDescription, this.authInfo.clientID, true]
|
const data = ['admin', 'init', this.version, this.browserDescription, this.authInfo.clientID, true]
|
||||||
return this.query(data)
|
return this.query(data)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
@@ -42,15 +41,17 @@ export default class WAConnectionValidator extends WAConnectionBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
switch (json.status) {
|
if ('status' in json) {
|
||||||
case 401: // if the phone was unpaired
|
switch (json.status) {
|
||||||
throw [json.status, 'unpaired from phone', json]
|
case 401: // if the phone was unpaired
|
||||||
case 429: // request to login was denied, don't know why it happens
|
throw [json.status, 'unpaired from phone', json]
|
||||||
throw [json.status, 'request denied, try reconnecting', json]
|
case 429: // request to login was denied, don't know why it happens
|
||||||
case 304: // request to generate a new key for a QR code was denied
|
throw [json.status, 'request denied, try reconnecting', json]
|
||||||
throw [json.status, 'request for new key denied', json]
|
case 304: // request to generate a new key for a QR code was denied
|
||||||
default:
|
throw [json.status, 'request for new key denied', json]
|
||||||
break
|
default:
|
||||||
|
throw [json.status, 'unknown error status', json]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (json[1] && json[1].challenge) {
|
if (json[1] && json[1].challenge) {
|
||||||
// if its a challenge request (we get it when logging in)
|
// if its a challenge request (we get it when logging in)
|
||||||
|
|||||||
Reference in New Issue
Block a user