mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
finalize multi-device
This commit is contained in:
558
README.md
558
README.md
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user