finalize multi-device

This commit is contained in:
Adhiraj Singh
2021-09-15 13:40:02 +05:30
parent 9cba28e891
commit f267f27ada
82 changed files with 35228 additions and 10644 deletions

558
README.md
View File

@@ -1,8 +1,10 @@
# Baileys - Typescript/Javascript WhatsApp Web API
# Baileys MD - Typescript/Javascript WhatsApp Web API
Early Multi-Device Edition. Breaks completely from master.
Baileys does not require Selenium or any other browser to be interface with WhatsApp Web, it does so directly using a **WebSocket**. Not running Selenium or Chromimum saves you like **half a gig** of ram :/
Thank you to [@Sigalor](https://github.com/sigalor/whatsapp-web-reveng) for writing his observations on the workings of WhatsApp Web and thanks to [@Rhymen](https://github.com/Rhymen/go-whatsapp/) for the __go__ implementation.
Thank you to [@pokearaujo](https://github.com/pokearaujo/multidevice) for writing his observations on the workings of WhatsApp Multi-Device.
Baileys is type-safe, extensible and simple to use. If you require more functionality than provided, it'll super easy for you to write an extension. More on this [here](#WritingCustomFunctionality).
@@ -12,107 +14,102 @@
**Join the Discord [here](https://discord.gg/7FYURJyqng)**
## Example
Do check out & run [example.ts](Example/example.ts) to see example usage of the library.
The script covers most common use cases.
To run the example script, download or clone the repo and then type the following in terminal:
1. ``` cd path/to/Baileys ```
2. ``` npm install ```
3. ``` npm run example ```
2. ``` yarn```
3. ``` yarn example ```
## Install
Create and cd to your NPM project directory and then in terminal, write:
1. stable: `npm install @adiwajshing/baileys`
2. stabl-ish w quicker fixes & latest features: `npm install github:adiwajshing/baileys`
Do note, the library will likely vary if you're using the NPM package, read that [here](https://www.npmjs.com/package/@adiwajshing/baileys)
Right now, the multi-device branch is only available from GitHub, install using:
```
yarn add github:adiwajshing/baileys#multi-device
```
Then import in your code using:
``` ts
import { WAConnection } from '@adiwajshing/baileys'
import makeWASocket from '@adiwajshing/baileys'
```
## Unit Tests
Baileys also comes with a unit test suite. Simply cd into the Baileys directory & run `npm test`.
You will require a phone with WhatsApp to test, and a second WhatsApp number to send messages to.
Set the phone number you can randomly send messages to in a `.env` file with `TEST_JID=1234@s.whatsapp.net`
TODO
## Connecting
``` ts
import { WAConnection } from '@adiwajshing/baileys'
import makeWASocket from '@adiwajshing/baileys'
async function connectToWhatsApp () {
const conn = new WAConnection()
// called when WA sends chats
// this can take up to a few minutes if you have thousands of chats!
conn.on('chats-received', async ({ hasNewChats }) => {
console.log(`you have ${conn.chats.length} chats, new chats available: ${hasNewChats}`)
const unread = await conn.loadAllUnreadMessages ()
console.log ("you have " + unread.length + " unread messages")
const conn = makeWASocket({
// can provide additional config here
printQRInTerminal: true
})
// called when WA sends chats
// this can take up to a few minutes if you have thousands of contacts!
conn.on('contacts-received', () => {
console.log('you have ' + Object.keys(conn.contacts).length + ' contacts')
sock.ev.on('connection.update', (update) => {
const { connection, lastDisconnect } = update
if(connection === 'close') {
const shouldReconnect = (lastDisconnect.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut
console.log('connection closed due to ', lastDisconnect.error, ', reconnecting ', shouldReconnect)
// reconnect if not logged out
if(shouldReconnect) {
sock = startSock()
}
} else if(connection === 'open') {
console.log('opened connection')
}
})
sock.ev.on('messages.upsert', m => {
console.log(JSON.stringify(m, undefined, 2))
await conn.connect ()
conn.on('chat-update', chatUpdate => {
// `chatUpdate` is a partial object, containing the updated properties of the chat
// received a new message
if (chatUpdate.messages && chatUpdate.count) {
const message = chatUpdate.messages.all()[0]
console.log (message)
} else console.log (chatUpdate) // see updates (can be archived, pinned etc.)
console.log('replying to', m.messages[0].key.remoteJid)
sendMessageWTyping({ text: 'Hello there!' }, m.messages[0].key.remoteJid!)
})
}
// run in main file
connectToWhatsApp ()
.catch (err => console.log("unexpected error: " + err) ) // catch any errors
connectToWhatsApp()
```
If the connection is successful, you will see a QR code printed on your terminal screen, scan it with WhatsApp on your phone and you'll be logged in!
Do note, the `conn.chats` object is a [KeyedDB](https://github.com/adiwajshing/keyed-db). This is done for the following reasons:
- Most applications require chats to be ordered in descending order of time. (`KeyedDB` does this in `log(N)` time)
- Most applications require pagination of chats (Use `chats.paginated()`)
- Most applications require **O(1)** access to chats via the chat ID. (Use `chats.get(jid)` with `KeyedDB`)
**Note:** install `qrcode-terminal` using `yarn add qrcode-terminal` to auto-print the QR to the terminal.
## Notable Differences Between Baileys Web & MD
1. Baileys no longer maintains an internal state of chats/contacts/messages. You must take this on your own, simply because your state in MD is its own source of truth & there is no one-size-fits-all way to handle the storage for this.
2. A baileys "socket" is meant to be a temporary & disposable object -- this is done to maintain simplicity & prevent bugs. I felt the entire Baileys object became too bloated as it supported too many configurations.
3. Moreover, Baileys does not offer an inbuilt reconnect mechanism anymore (though it's super easy to set one up on your own with your own rules, check the example script)
## Configuring the Connection
You can configure the connection via the `connectOptions` property. You can even specify an HTTPS proxy. For example:
You can configure the connection by passing a `SocketConfig` object.
The entire `SocketConfig` structure is mentioned here with default values:
``` ts
import { WAConnection, ProxyAgent } from '@adiwajshing/baileys'
const conn = new WAConnecion ()
conn.connectOptions.agent = ProxyAgent ('http://some-host:1234')
await conn.connect ()
console.log ("oh hello " + conn.user.name + "! You connected via a proxy")
```
The entire `WAConnectOptions` struct is mentioned here with default values:
``` ts
conn.connectOptions = {
/** fails the connection if no data is received for X seconds */
maxIdleTimeMs?: 60_000,
/** maximum attempts to connect */
maxRetries?: 10,
/** max time for the phone to respond to a connectivity test */
phoneResponseTime?: 15_000,
/** minimum time between new connections */
connectCooldownMs?: 4000,
/** agent used for WS connections (could be a proxy agent) */
agent?: Agent = undefined,
/** agent used for fetch requests -- uploading/downloading media */
fetchAgent?: Agent = undefined,
/** always uses takeover for connecting */
alwaysUseTakeover: true
/** log QR to terminal */
logQR: true
} as WAConnectOptions
type SocketConfig = {
/** provide an auth state object to maintain the auth state */
auth?: AuthenticationState
/** the WS url to connect to WA */
waWebSocketUrl: string | URL
/** Fails the connection if the connection times out in this time interval or no data is received */
connectTimeoutMs: number
/** ping-pong interval for WS connection */
keepAliveIntervalMs: number
/** proxy agent */
agent?: Agent
/** pino logger */
logger: Logger
/** version to connect with */
version: WAVersion
/** override browser config */
browser: WABrowserDescription
/** agent used for fetch requests -- uploading/downloading media */
fetchAgent?: Agent
/** should the QR be printed in the terminal */
printQRInTerminal: boolean
}
```
## Saving & Restoring Sessions
@@ -121,92 +118,126 @@ You obviously don't want to keep scanning the QR code every time you want to con
So, you can save the credentials to log back in via:
``` ts
import makeWASocket, { BufferJSON } from '@adiwajshing/baileys'
import * as fs from 'fs'
const conn = new WAConnection()
// will initialize a default in-memory auth session
const conn = makeSocket()
// this will be called as soon as the credentials are updated
conn.on ('open', () => {
conn.ev.on ('auth-state.update', () => {
// save credentials whenever updated
console.log (`credentials updated!`)
const authInfo = conn.base64EncodedAuthInfo() // get all the auth info we need to restore this session
fs.writeFileSync('./auth_info.json', JSON.stringify(authInfo, null, '\t')) // save this info to a file
const authInfo = conn.authState // get all the auth info we need to restore this session
// save this info to a file
fs.writeFileSync(
'./auth_info.json',
JSON.stringify(authInfo, BufferJSON.replacer, 2)
)
})
await conn.connect() // connect
```
Then, to restore a session:
``` ts
const conn = new WAConnection()
conn.loadAuthInfo ('./auth_info.json') // will load JSON credentials from file
await conn.connect()
// yay connected without scanning QR
/*
Optionally, you can load the credentials yourself from somewhere
& pass in the JSON object to loadAuthInfo () as well.
*/
```
import makeWASocket, { BufferJSON, initInMemoryKeyStore } from '@adiwajshing/baileys'
import * as fs from 'fs'
If you're considering switching from a Chromium/Puppeteer based library, you can use WhatsApp Web's Browser credentials to restore sessions too:
``` ts
conn.loadAuthInfo ('./auth_info_browser.json')
await conn.connect() // works the same
```
See the browser credentials type in the docs.
**Note**: Upon every successive connection, WA can update part of the stored credentials. Whenever that happens, the credentials are uploaded, and you should probably update your saved credentials upon receiving the `open` event. Not doing so *may* lead WA to log you out after a few weeks with a 419 error code.
## QR Callback
If you want to do some custom processing with the QR code used to authenticate, you can register for the following event:
``` ts
conn.on('qr', qr => {
// Now, use the 'qr' string to display in QR UI or send somewhere
const authJSON = JSON.parse(
fs.readFileSync(
'./auth_info.json',
{ encoding: 'utf-8' }
),
BufferJSON.reviver
)
const auth = {
creds: authJSON.creds,
// stores pre-keys, session & other keys in a JSON object
// we deserialize it here
keys: initInMemoryKeyStore(authJSON.keys)
}
await conn.connect ()
const conn = makeWASocket(auth)
// yay will connect without scanning QR
```
**Note**: Upon every successive connection, the auth state can update part of the stored credentials. It will also update when a message is received/sent due to signal sessions needing updating. Whenever that happens, the `auth-state.update` event is fired uploaded, and you must update your saved credentials upon receiving the event. Not doing so will prevent your messages from reaching the recipient & other unexpected consequences.
## Listening to Connection Updates
Baileys now fires the `connection.update` event to let you know something has updated in the connection. This data has the following structure:
``` ts
type ConnectionState = {
/** connection is now open, connecting or closed */
connection: WAConnectionState
/** the error that caused the connection to close */
lastDisconnect?: {
error: Error
date: Date
}
/** is this a new login */
isNewLogin?: boolean
/** the current QR code */
qr?: string
/** has the device received all pending notifications while it was offline */
receivedPendingNotifications?: boolean
}
```
Note: this also offers any updates to the QR
## Handling Events
Baileys now uses the EventEmitter syntax for events.
Baileys uses the EventEmitter syntax for events.
They're all nicely typed up, so you shouldn't have any issues with an Intellisense editor like VS Code.
Also, these events are fired regardless of whether they are initiated by the Baileys client or are relayed from your phone.
The events are typed up in a type map, as mentioned here:
``` ts
/** when the connection has opened successfully */
on (event: 'open', listener: (result: WAOpenResult) => void): this
/** when the connection is opening */
on (event: 'connecting', listener: () => void): this
/** when the connection has closed */
on (event: 'close', listener: (err: {reason?: DisconnectReason | string, isReconnecting: boolean}) => void): this
/** when the socket is closed */
on (event: 'ws-close', listener: (err: {reason?: DisconnectReason | string}) => void): this
/** when a new QR is generated, ready for scanning */
on (event: 'qr', listener: (qr: string) => void): this
/** when the connection to the phone changes */
on (event: 'connection-phone-change', listener: (state: {connected: boolean}) => void): this
/** when a contact is updated */
on (event: 'contact-update', listener: (update: WAContactUpdate) => void): this
/** when a new chat is added */
on (event: 'chat-new', listener: (chat: WAChat) => void): this
/** when contacts are sent by WA */
on (event: 'contacts-received', listener: (u: { updatedContacts: Partial<WAContact>[] }) => void): this
/** when chats are sent by WA, and when all messages are received */
on (event: 'chats-received', listener: (update: {hasNewChats?: boolean}) => void): this
/** when all initial messages are received from WA */
on (event: 'initial-data-received', listener: (update: {chatsWithMissingMessages: { jid: string, count: number }[] }) => void): this
/** when multiple chats are updated (new message, updated message, deleted, pinned, etc) */
on (event: 'chats-update', listener: (chats: WAChatUpdate[]) => void): this
/** when a chat is updated (new message, updated message, read message, deleted, pinned, presence updated etc) */
on (event: 'chat-update', listener: (chat: WAChatUpdate) => void): this
/** when participants are added to a group */
on (event: 'group-participants-update', listener: (update: {jid: string, participants: string[], actor?: string, action: WAParticipantAction}) => void): this
/** when the group is updated */
on (event: 'group-update', listener: (update: Partial<WAGroupMetadata> & {jid: string, actor?: string}) => void): this
/** when WA sends back a pong */
on (event: 'received-pong', listener: () => void): this
/** when a user is blocked or unblockd */
on (event: 'blocklist-update', listener: (update: BlocklistUpdate) => void): this
export type BaileysEventMap = {
/** connection state has been updated -- WS closed, opened, connecting etc. */
'connection.update': Partial<ConnectionState>
/** auth state updated -- some pre keys, or identity keys etc. */
'auth-state.update': AuthenticationState
/** set chats (history sync), messages are reverse chronologically sorted */
'chats.set': { chats: Chat[], messages: WAMessage[] }
/** update/insert chats */
'chats.upsert': Chat[]
/** update the given chats */
'chats.update': Partial<Chat>[]
/** delete chats with given ID */
'chats.delete': string[]
/** presence of contact in a chat updated */
'presence.update': { id: string, presences: { [participant: string]: PresenceData } }
'contacts.upsert': Contact[]
'contacts.update': Partial<Contact>[]
'messages.delete': { jid: string, ids: string[] } | { jid: string, all: true }
'messages.update': WAMessageUpdate[]
/**
* add/update the given messages. If they were received while the connection was online,
* the update will have type: "notify"
* */
'messages.upsert': { messages: WAMessage[], type: MessageUpdateType }
'message-info.update': MessageInfoUpdate[]
'groups.update': Partial<GroupMetadata>[]
/** apply an action to participants in a group */
'group-participants.update': { id: string, participants: string[], action: ParticipantAction }
'blocklist.set': { blocklist: string[] }
'blocklist.update': { blocklist: string[], type: 'add' | 'remove' }
}
```
You can listen to these events like this:
``` ts
const sock = makeWASocket()
sock.ev.on('messages.upsert', ({ messages }) => {
console.log('got messages', messages)
})
```
## Sending Messages
@@ -220,9 +251,12 @@ import { MessageType, MessageOptions, Mimetype } from '@adiwajshing/baileys'
const id = 'abcd@s.whatsapp.net' // the WhatsApp ID
// send a simple text!
const sentMsg = await conn.sendMessage (id, 'oh hello there', MessageType.text)
const sentMsg = await conn.sendMessage(id, { text: 'oh hello there' })
// send a location!
const sentMsg = await conn.sendMessage(id, {degreesLatitude: 24.121231, degreesLongitude: 55.1121221}, MessageType.location)
const sentMsg = await conn.sendMessage(
id,
{ location: { degreesLatitude: 24.121231, degreesLongitude: 55.1121221 } }
)
// send a contact!
const vcard = 'BEGIN:VCARD\n' // metadata of the contact card
+ 'VERSION:3.0\n'
@@ -230,7 +264,15 @@ const vcard = 'BEGIN:VCARD\n' // metadata of the contact card
+ 'ORG:Ashoka Uni;\n' // the organization of the contact
+ 'TEL;type=CELL;type=VOICE;waid=911234567890:+91 12345 67890\n' // WhatsApp ID + phone number
+ 'END:VCARD'
const sentMsg = await conn.sendMessage(id, {displayname: "Jeff", vcard: vcard}, MessageType.contact)
const sentMsg = await conn.sendMessage(
id,
{
contacts: {
displayName: 'Jeff',
contacts: [{ vcard }]
}
}
)
```
### Media Messages
@@ -244,34 +286,38 @@ import { MessageType, MessageOptions, Mimetype } from '@adiwajshing/baileys'
// Sending gifs
await conn.sendMessage(
id,
fs.readFileSync("Media/ma_gif.mp4"), // load a gif and send it
MessageType.video,
{ mimetype: Mimetype.gif, caption: "hello!" }
{
video: fs.readFileSync("Media/ma_gif.mp4"),
caption: "hello!",
gifPlayback: true
}
)
await conn.sendMessage(
id,
{ url: 'Media/ma_gif.mp4' }, // send directly from local file
MessageType.video,
{ mimetype: Mimetype.gif, caption: "hello!" }
{
video: "./Media/ma_gif.mp4",
caption: "hello!",
gifPlayback: true
}
)
await conn.sendMessage(
id,
{ url: 'https://giphy.com/gifs/11JTxkrmq4bGE0/html5' }, // send directly from remote url!
MessageType.video,
{ mimetype: Mimetype.gif, caption: "hello!" }
{
video: "./Media/ma_gif.mp4",
caption: "hello!",
gifPlayback: true
}
)
// send an audio file
await conn.sendMessage(
id,
{ audio: { url: "./Media/audio.mp3" }, mimetype: 'audio/mp4' }
{ url: "Media/audio.mp3" }, // can send mp3, mp4, & ogg
MessageType.audio,
{ mimetype: Mimetype.mp4Audio } // some metadata (can't have caption in audio)
)
```
### Notes
- `id` is the WhatsApp ID of the person or group you're sending the message to.
@@ -279,7 +325,7 @@ await conn.sendMessage(
- For broadcast lists it's `[timestamp of creation]@broadcast`.
- For stories, the ID is `status@broadcast`.
- For media messages, the thumbnail can be generated automatically for images & stickers. Thumbnails for videos can also be generated automatically, though, you need to have `ffmpeg` installed on your system.
- **MessageOptions**: some extra info about the message. It can have the following __optional__ values:
- **MiscGenerationOptions**: some extra info about the message. It can have the following __optional__ values:
``` ts
const info: MessageOptions = {
quoted: quotedMessage, // the message you want to quote
@@ -313,12 +359,16 @@ await conn.forwardMessage ('455@s.whatsapp.net', message) // WA forward the mess
## Reading Messages
A set of message IDs must be explicitly marked read now.
Cannot mark an entire "chat" read as it were with Baileys Web.
This does mean you have to keep track of unread messages.
``` ts
const id = '1234-123@g.us'
const messageID = 'AHASHH123123AHGA' // id of the message you want to read
const participant = '912121232@s.whatsapp.net' // the ID of the user that sent the message (undefined for individual chats)
await conn.chatRead (id) // mark all messages in chat as read (equivalent of opening a chat in WA)
await conn.chatRead (id, 'unread') // mark the chat as unread
await conn.sendReadReceipt(id, participant, [messageID])
```
The message ID is the unique identifier of the message that you are marking as read. On a `WAMessage`, the `messageID` can be accessed using ```messageID = message.key.id```.
@@ -326,18 +376,12 @@ The message ID is the unique identifier of the message that you are marking as r
## Update Presence
``` ts
import { Presence } from '@adiwajshing/baileys'
await conn.updatePresence(id, Presence.available)
await conn.updatePresence(id, 'available')
```
This lets the person/group with ``` id ``` know whether you're online, offline, typing etc. where ``` presence ``` can be one of the following:
``` ts
export enum Presence {
available = 'available', // "online"
composing = 'composing', // "typing..."
recording = 'recording', // "recording..."
paused = 'paused' // stopped typing, back to "online"
}
type WAPresence = 'unavailable' | 'available' | 'composing' | 'recording' | 'paused'
```
The presence expires after about 10 seconds.
@@ -364,108 +408,74 @@ conn.on ('message-new', async m => {
``` ts
const jid = '1234@s.whatsapp.net' // can also be a group
const response = await conn.sendMessage (jid, 'hello!', MessageType.text) // send a message
await conn.deleteMessage (jid, {id: response.messageID, remoteJid: jid, fromMe: true}) // will delete the sent message for everyone!
await conn.clearMessage (jid, {id: response.messageID, remoteJid: jid, fromMe: true}) // will delete the sent message for only you!
const response = await conn.sendMessage(jid, { text: 'hello!' }) // send a message
// sends a message to delete the given message
// this deletes the message for everyone
await conn.sendMessage(jid, { delete: response.key })
```
Note: deleting for oneself is not supported yet
## Modifying Chats
``` ts
const jid = '1234@s.whatsapp.net' // can also be a group
await conn.modifyChat (jid, ChatModification.archive) // archive chat
await conn.modifyChat (jid, ChatModification.unarchive) // unarchive chat
const response = await conn.modifyChat (jid, ChatModification.pin) // pin the chat
await conn.modifyChat (jid, ChatModification.unpin) // unpin it
await conn.modifyChat (jid, ChatModification.mute, 8*60*60*1000) // mute for 8 hours
setTimeout (() => {
conn.modifyChat (jid, ChatModification.unmute)
}, 5000) // unmute after 5 seconds
await conn.modifyChat (jid, ChatModification.delete) // will delete the chat (can be a group or broadcast list as well)
```
**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.
TODO: haven't figured this bit out yet. Can receive chat modifications tho.
## Disappearing Messages
``` ts
const jid = '1234@s.whatsapp.net' // can also be a group
// turn on disappearing messages
await conn.toggleDisappearingMessages(
await conn.sendMessage(
jid,
WA_DEFAULT_EPHEMERAL // this is 1 week in seconds -- how long you want messages to appear for
)
// will automatically send as a disappearing message
await conn.sendMessage(jid, 'Hello poof!', MessageType.text)
// this is 1 week in seconds -- how long you want messages to appear for
{ disappearingMessagesInChat: WA_DEFAULT_EPHEMERAL }
)
// will send as a disappearing message
await conn.sendMessage(jid, { text: 'hello' }, { ephemeralExpiration: WA_DEFAULT_EPHEMERAL })
// turn off disappearing messages
await conn.toggleDisappearingMessages(jid, 0)
await conn.sendMessage(
jid,
{ disappearingMessagesInChat: false }
)
```
## Misc
- To load chats in a paginated manner
``` ts
const {chats, cursor} = await conn.loadChats (25)
if (cursor) {
const moreChats = await conn.loadChats (25, cursor) // load the next 25 chats
}
```
- To check if a given ID is on WhatsApp
Note: this method falls back to using `https://wa.me` to determine whether a number is on WhatsApp in case the WebSocket connection is not open yet.
``` ts
const id = '123456'
const exists = await conn.isOnWhatsApp (id)
if (exists) console.log (`${id} exists on WhatsApp, as jid: ${exists.jid}`)
const [result] = await conn.onWhatsApp(id)
if (result.exists) console.log (`${id} exists on WhatsApp, as jid: ${result.jid}`)
```
- To query chat history on a group or with someone
``` ts
// query the last 25 messages (replace 25 with the number of messages you want to query)
const messages = await conn.loadMessages ("xyz-abc@g.us", 25)
console.log("got back " + messages.length + " messages")
```
You can also load the entire conversation history if you want
``` ts
await conn.loadAllMessages ("xyz@c.us", message => console.log("Loaded message with ID: " + message.key.id))
console.log("queried all messages") // promise resolves once all messages are retrieved
```
TODO, if possible
- To get the status of some person
``` ts
const status = await conn.getStatus ("xyz@c.us") // leave empty to get your own status
const status = await conn.fetchStatus("xyz@s.whatsapp.net")
console.log("status: " + status)
```
- To get the display picture of some person/group
``` ts
const ppUrl = await conn.getProfilePicture ("xyz@g.us") // leave empty to get your own
const ppUrl = await conn.profilePictureUrl("xyz@g.us")
console.log("download profile picture from: " + ppUrl)
```
- To change your display picture or a group's
``` ts
const jid = '111234567890-1594482450@g.us' // can be your own too
const img = fs.readFileSync ('new-profile-picture.jpeg') // can be PNG also
await conn.updateProfilePicture (jid, img)
await conn.updateProfilePicture(jid, { url: './new-profile-picture.jpeg' })
```
- To get someone's presence (if they're typing, online)
``` ts
// the presence update is fetched and called here
conn.on ('CB:Presence', json => console.log(json.id + " presence is " + json.type))
await conn.requestPresenceUpdate ("xyz@c.us") // request the update
```
- To search through messages
``` ts
const response = await conn.searchMessages ('so cool', null, 25, 1) // search in all chats
console.log (`got ${response.messages.length} messages in search`)
const response2 = await conn.searchMessages ('so cool', '1234@c.us', 25, 1) // search in given chat
conn.ev.on('presence-update', json => console.log(json))
// request updates for a chat
await conn.presenceSubscribe("xyz@s.whatsapp.net")
```
- To block or unblock user
``` ts
await conn.blockUser ("xyz@c.us", "add") // Block user
await conn.blockUser ("xyz@c.us", "remove") // Unblock user
await conn.updateBlockStatus("xyz@s.whatsapp.net", "block") // Block user
await conn.updateBlockStatus("xyz@s.whatsapp.net", "unblock") // Unblock user
```
Of course, replace ``` xyz ``` with an actual ID.
@@ -473,67 +483,52 @@ Of course, replace ``` xyz ``` with an actual ID.
- To create a group
``` ts
// title & participants
const group = await conn.groupCreate ("My Fab Group", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"])
const group = await conn.groupCreate("My Fab Group", ["1234@s.whatsapp.net", "4564@s.whatsapp.net"])
console.log ("created group with id: " + group.gid)
conn.sendMessage(group.gid, "hello everyone", MessageType.extendedText) // say hello to everyone on the group
conn.sendMessage(group.id, { text: 'hello there' }) // say hello to everyone on the group
```
- To add people to a group
- To add/remove people to a group or demote/promote people
``` ts
// id & people to add to the group (will throw error if it fails)
const response = await conn.groupAdd ("abcd-xyz@g.us", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"])
```
- To make/demote admins on a group
``` ts
// id & people to make admin (will throw error if it fails)
await conn.groupMakeAdmin ("abcd-xyz@g.us", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"])
await conn.groupDemoteAdmin ("abcd-xyz@g.us", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"]) // demote admins
const response = await conn.groupParticipantsUpdate(
"abcd-xyz@g.us",
["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"],
"add" // replace this parameter with "remove", "demote" or "promote"
)
```
- To change the group's subject
``` ts
await conn.groupUpdateSubject("abcd-xyz@g.us", "New Subject!")
```
- To change the group's description
``` ts
await conn.groupUpdateDescription("abcd-xyz@g.us", "This group has a new description")
```
- To change group settings
``` ts
import { GroupSettingChange } from '@adiwajshing/baileys'
// only allow admins to send messages
await conn.groupSettingChange ("abcd-xyz@g.us", GroupSettingChange.messageSend, true)
await conn.groupSettingUpdate("abcd-xyz@g.us", 'announcement')
// allow everyone to modify the group's settings -- like display picture etc.
await conn.groupSettingChange ("abcd-xyz@g.us", GroupSettingChange.settingChange, false)
await conn.groupSettingUpdate("abcd-xyz@g.us", 'unlocked')
// only allow admins to modify the group's settings
await conn.groupSettingChange ("abcd-xyz@g.us", GroupSettingChange.settingChange, true)
await conn.groupSettingUpdate("abcd-xyz@g.us", 'locked')
```
- To leave a group
``` ts
await conn.groupLeave ("abcd-xyz@g.us") // (will throw error if it fails)
await conn.groupLeave("abcd-xyz@g.us") // (will throw error if it fails)
```
- To get the invite code for a group
``` ts
const code = await conn.groupInviteCode ("abcd-xyz@g.us")
const code = await conn.groupInviteCode("abcd-xyz@g.us")
console.log("group code: " + code)
```
- To query the metadata of a group
``` ts
const metadata = await conn.groupMetadata ("abcd-xyz@g.us")
const metadata = await conn.groupMetadata("abcd-xyz@g.us")
console.log(json.id + ", title: " + json.subject + ", description: " + json.desc)
// Or if you've left the group -- call this
const metadata2 = await conn.groupMetadataMinimal ("abcd-xyz@g.us")
```
- To join the group using the invitation code
``` ts
const response = await conn.acceptInvite ("xxx")
const response = await conn.acceptInvite("xxx")
console.log("joined to: " + response.gid)
```
Of course, replace ``` xxx ``` with invitation code.
- To revokes the current invite link of a group
``` ts
const response = await conn.revokeInvite ("abcd-xyz@g.us")
console.log("new group code: " + response.code)
```
## Broadcast Lists & Stories
@@ -542,7 +537,7 @@ Of course, replace ``` xyz ``` with an actual ID.
- Broadcast IDs are in the format `12345678@broadcast`
- To query a broadcast list's recipients & name:
``` ts
const bList = await conn.getBroadcastListInfo ("1234@broadcast")
const bList = await conn.getBroadcastListInfo("1234@broadcast")
console.log (`list name: ${bList.name}, recps: ${bList.recipients}`)
```
@@ -551,53 +546,34 @@ Baileys is written, keeping in mind, that you may require other custom functiona
First, enable the logging of unhandled messages from WhatsApp by setting
``` ts
conn.logger.level = 'debug'
const sock = makeWASocket({
logger: P({ level: 'debug' }),
})
```
This will enable you to see all sorts of messages WhatsApp sends in the console. Some examples:
1. Functionality to track of the battery percentage of your phone.
You enable logging and you'll see a message about your battery pop up in the console:
```s22, ["action",null,[["battery",{"live":"false","value":"52"},null]]] ```
```{"level":10,"fromMe":false,"frame":{"tag":"ib","attrs":{"from":"@s.whatsapp.net"},"content":[{"tag":"edge_routing","attrs":{},"content":[{"tag":"routing_info","attrs":{},"content":{"type":"Buffer","data":[8,2,8,5]}}]}]},"msg":"communication"} ```
You now know what a battery update looks like. It'll have the following characteristics.
- Given ```const bMessage = ["action",null,[["battery",{"live":"false","value":"52"},null]]]```
- ```bMessage[0]``` is always ``` "action" ```
- ```bMessage[1]``` is always ``` null ```
- ```bMessage[2][0][0]``` is always ``` "battery" ```
The "frame" is what the message received is, it has three components:
- `tag` -- what this frame is about (eg. message will have "message")
- `attrs` -- a string key-value pair with some metadata (contains ID of the message usually)
- `content` -- the actual data (eg. a message node will have the actual message content in it)
- read more about this format [here](/src/WABinary/readme.md)
Hence, you can register a callback for an event using the following:
``` ts
conn.on (`CB:action,,battery`, json => {
const batteryLevelStr = json[2][0][1].value
const batterylevel = parseInt (batteryLevelStr)
console.log ("battery level: " + batterylevel + "%")
})
// for any message with tag 'edge_routing'
conn.ws.on(`CB:edge_routing`, (node: BinaryNode) => { })
// for any message with tag 'edge_routing' and id attribute = abcd
conn.ws.on(`CB:edge_routing,id:abcd`, (node: BinaryNode) => { })
// for any message with tag 'edge_routing', id attribute = abcd & first content node routing_info
conn.ws.on(`CB:edge_routing,id:abcd,routing_info`, (node: BinaryNode) => { })
```
This callback will be fired any time a message is received matching the following criteria:
``` message [0] === "action" && message [1] === null && message[2][0][0] === "battery" ```
2. Functionality to keep track of the pushname changes on your phone.
You enable logging and you'll see an unhandled message about your pushanme pop up like this:
```s24, ["Conn",{"pushname":"adiwajshing"}]```
You now know what a pushname update looks like. It'll have the following characteristics.
- Given ```const pMessage = ["Conn",{"pushname":"adiwajshing"}] ```
- ```pMessage[0]``` is always ``` "Conn" ```
- ```pMessage[1]``` always has the key ``` "pushname" ```
- ```pMessage[2]``` is always ``` undefined ```
Following this, one can implement the following callback:
``` ts
conn.on ('CB:Conn,pushname', json => {
const pushname = json[1].pushname
conn.user.name = pushname // update on client too
console.log ("Name updated: " + pushname)
})
```
This callback will be fired any time a message is received matching the following criteria:
``` message [0] === "Conn" && message [1].pushname ```
A little more testing will reveal that almost all WhatsApp messages are in the format illustrated above.
Note: except for the first parameter (in the above cases, ```"action"``` or ```"Conn"```), all the other parameters are optional.
### Note
This library was originally a project for **CS-2362 at Ashoka University** and is in no way affiliated with WhatsApp. Use at your own discretion. Do not spam people with this.
Also, this repo is now licenced under GPL 3 since it uses [libsignal-node](https://git.questbook.io/backend/service-coderunner/-/merge_requests/1)