Baileys - Typescript/Javascript WhatsApp Web API
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 for writing his observations on the workings of WhatsApp Web and thanks to @Rhymen for the go implementation.
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.
If you're interested in building a WhatsApp bot, you may wanna check out WhatsAppInfoBot and an actual bot built with it, Messcat.
Read the docs here
Join the Discord here
Multi-Device Support
Baileys now supports the latest multi-device beta. However, as it's a completely different implementation from WA Web & will break a lot of things, it's on its own branch which will be merged into master once mutli-device hits production. You may find the MD repo here.
The master branch only supports WA Web at the moment.
Example
Do check out & run 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:
cd path/to/Baileys
npm install
npm run example
Install
Create and cd to your NPM project directory and then in terminal, write:
- stable:
npm install @adiwajshing/baileys
- 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
Then import in your code using:
import { WAConnection } 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
Connecting
import { WAConnection } from '@adiwajshing/baileys'
async function connectToWhatsApp () {
const conn = new WAConnection()
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")
})
conn.on('contacts-received', () => {
console.log('you have ' + Object.keys(conn.contacts).length + ' contacts')
})
await conn.connect ()
conn.on('chat-update', chatUpdate => {
if (chatUpdate.messages && chatUpdate.count) {
const message = chatUpdate.messages.all()[0]
console.log (message)
} else console.log (chatUpdate)
})
}
connectToWhatsApp ()
.catch (err => console.log("unexpected error: " + err) )
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. 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
)
Configuring the Connection
You can configure the connection via the connectOptions
property. You can even specify an HTTPS proxy. For example:
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:
conn.connectOptions = {
maxIdleTimeMs?: 60_000,
maxRetries?: 10,
phoneResponseTime?: 15_000,
connectCooldownMs?: 4000,
agent?: Agent = undefined,
fetchAgent?: Agent = undefined,
alwaysUseTakeover: true
logQR: true
} as WAConnectOptions
Saving & Restoring Sessions
You obviously don't want to keep scanning the QR code every time you want to connect.
So, you can save the credentials to log back in via:
import * as fs from 'fs'
const conn = new WAConnection()
conn.on ('open', () => {
console.log (`credentials updated!`)
const authInfo = conn.base64EncodedAuthInfo()
fs.writeFileSync('./auth_info.json', JSON.stringify(authInfo, null, '\t'))
})
await conn.connect()
Then, to restore a session:
const conn = new WAConnection()
conn.loadAuthInfo ('./auth_info.json')
await conn.connect()
If you're considering switching from a Chromium/Puppeteer based library, you can use WhatsApp Web's Browser credentials to restore sessions too:
conn.loadAuthInfo ('./auth_info_browser.json')
await conn.connect()
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:
conn.on('qr', qr => {
}
await conn.connect ()
Handling Events
Baileys now 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.
on (event: 'open', listener: (result: WAOpenResult) => void): this
on (event: 'connecting', listener: () => void): this
on (event: 'close', listener: (err: {reason?: DisconnectReason | string, isReconnecting: boolean}) => void): this
on (event: 'ws-close', listener: (err: {reason?: DisconnectReason | string}) => void): this
on (event: 'qr', listener: (qr: string) => void): this
on (event: 'connection-phone-change', listener: (state: {connected: boolean}) => void): this
on (event: 'contact-update', listener: (update: WAContactUpdate) => void): this
on (event: 'chat-new', listener: (chat: WAChat) => void): this
on (event: 'contacts-received', listener: (u: { updatedContacts: Partial<WAContact>[] }) => void): this
on (event: 'chats-received', listener: (update: {hasNewChats?: boolean}) => void): this
on (event: 'initial-data-received', listener: (update: {chatsWithMissingMessages: { jid: string, count: number }[] }) => void): this
on (event: 'chats-update', listener: (chats: WAChatUpdate[]) => void): this
on (event: 'chat-update', listener: (chat: WAChatUpdate) => void): this
on (event: 'group-participants-update', listener: (update: {jid: string, participants: string[], actor?: string, action: WAParticipantAction}) => void): this
on (event: 'group-update', listener: (update: Partial<WAGroupMetadata> & {jid: string, actor?: string}) => void): this
on (event: 'received-pong', listener: () => void): this
on (event: 'blocklist-update', listener: (update: BlocklistUpdate) => void): this
Sending Messages
Send all types of messages with a single function:
Non-Media Messages
import { MessageType, MessageOptions, Mimetype } from '@adiwajshing/baileys'
const id = 'abcd@s.whatsapp.net'
const sentMsg = await conn.sendMessage (id, 'oh hello there', MessageType.text)
const sentMsg = await conn.sendMessage(id, {degreesLatitude: 24.121231, degreesLongitude: 55.1121221}, MessageType.location)
const vcard = 'BEGIN:VCARD\n'
+ 'VERSION:3.0\n'
+ 'FN:Jeff Singh\n'
+ 'ORG:Ashoka Uni;\n'
+ 'TEL;type=CELL;type=VOICE;waid=911234567890:+91 12345 67890\n'
+ 'END:VCARD'
const sentMsg = await conn.sendMessage(id, {displayname: "Jeff", vcard: vcard}, MessageType.contact)
const rows = [
{title: 'Row 1', description: "Hello it's description 1", rowId:"rowid1"},
{title: 'Row 2', description: "Hello it's description 2", rowId:"rowid2"}
]
const sections = [{title: "Section 1", rows: rows}]
const button = {
buttonText: 'Click Me!',
description: "Hello it's list message",
sections: sections,
listType: 1
}
const sendMsg = await conn.sendMessage(id, button, MessageType.listMessage)
const buttons = [
{buttonId: 'id1', buttonText: {displayText: 'Button 1'}, type: 1},
{buttonId: 'id2', buttonText: {displayText: 'Button 2'}, type: 1}
]
const buttonMessage = {
contentText: "Hi it's button message",
footerText: 'Hello World',
buttons: buttons,
headerType: 1
}
const sendMsg = await conn.sendMessage(id, buttonMessage, MessageType.buttonsMessage)
Media Messages
Sending media (video, stickers, images) is easier & more efficient than ever.
- You can specify a buffer, a local url or even a remote url.
- When specifying a media url, Baileys never loads the entire buffer into memory, it even encrypts the media as a readable stream.
import { MessageType, MessageOptions, Mimetype } from '@adiwajshing/baileys'
await conn.sendMessage(
id,
fs.readFileSync("Media/ma_gif.mp4"),
MessageType.video,
{ mimetype: Mimetype.gif, caption: "hello!" }
)
await conn.sendMessage(
id,
{ url: 'Media/ma_gif.mp4' },
MessageType.video,
{ mimetype: Mimetype.gif, caption: "hello!" }
)
await conn.sendMessage(
id,
{ url: 'https://giphy.com/gifs/11JTxkrmq4bGE0/html5' },
MessageType.video,
{ mimetype: Mimetype.gif, caption: "hello!" }
)
await conn.sendMessage(
id,
{ url: "Media/audio.mp3" },
MessageType.audio,
{ mimetype: Mimetype.mp4Audio }
)
Notes
id
is the WhatsApp ID of the person or group you're sending the message to.
- It must be in the format
[country code without +][phone number]@s.whatsapp.net
, for example 19999999999@s.whatsapp.net
for people. For groups, it must be in the format 123456789-123345@g.us
. - 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:
const info: MessageOptions = {
quoted: quotedMessage,
contextInfo: { forwardingScore: 2, isForwarded: true },
timestamp: Date(),
caption: "hello there!",
thumbnail: "23GD#4/==",
mimetype: Mimetype.pdf,
filename: 'somefile.pdf',
ptt: true,
detectLinks: true,
sendEphemeral: 'chat'
}
Forwarding Messages
const messages = await conn.loadConversation ('1234@s.whatsapp.net', 1)
const message = messages[0]
await conn.forwardMessage ('455@s.whatsapp.net', message)
Reading Messages
const id = '1234-123@g.us'
const messageID = 'AHASHH123123AHGA'
await conn.chatRead (id)
await conn.chatRead (id, 'unread')
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
.
Update Presence
import { Presence } from '@adiwajshing/baileys'
await conn.updatePresence(id, Presence.available)
This lets the person/group with id
know whether you're online, offline, typing etc. where presence
can be one of the following:
export enum Presence {
available = 'available',
composing = 'composing',
recording = 'recording',
paused = 'paused'
}
The presence expires after about 10 seconds.
Downloading Media Messages
If you want to save the media you received
import { MessageType } from '@adiwajshing/baileys'
conn.on ('message-new', async m => {
if (!m.message) return
const messageType = Object.keys (m.message)[0]
if (messageType !== MessageType.text && messageType !== MessageType.extendedText) {
const buffer = await conn.downloadMediaMessage(m)
const savedFilename = await conn.downloadAndSaveMediaMessage (m)
console.log(m.key.remoteJid + " sent media, saved at: " + savedFilename)
}
}
Deleting Messages
const jid = '1234@s.whatsapp.net'
const response = await conn.sendMessage (jid, 'hello!', MessageType.text)
await conn.deleteMessage (jid, {id: response.messageID, remoteJid: jid, fromMe: true})
await conn.clearMessage (jid, {id: response.messageID, remoteJid: jid, fromMe: true})
Modifying Chats
const jid = '1234@s.whatsapp.net'
await conn.modifyChat (jid, ChatModification.archive)
await conn.modifyChat (jid, ChatModification.unarchive)
const response = await conn.modifyChat (jid, ChatModification.pin)
await conn.modifyChat (jid, ChatModification.unpin)
await conn.modifyChat (jid, ChatModification.mute, 8*60*60*1000)
setTimeout (() => {
conn.modifyChat (jid, ChatModification.unmute)
}, 5000)
await conn.modifyChat (jid, ChatModification.delete)
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.
Disappearing Messages
const jid = '1234@s.whatsapp.net'
await conn.toggleDisappearingMessages(
jid,
WA_DEFAULT_EPHEMERAL
)
await conn.sendMessage(jid, 'Hello poof!', MessageType.text)
await conn.toggleDisappearingMessages(jid, 0)
Misc
- To load chats in a paginated manner
const {chats, cursor} = await conn.loadChats (25)
if (cursor) {
const moreChats = await conn.loadChats (25, cursor)
}
- 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.
const id = '123456'
const exists = await conn.isOnWhatsApp (id)
if (exists) console.log (`${id} exists on WhatsApp, as jid: ${exists.jid}`)
- To query chat history on a group or with someone
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
await conn.loadAllMessages ("xyz@c.us", message => console.log("Loaded message with ID: " + message.key.id))
console.log("queried all messages")
- To get the status of some person
const status = await conn.getStatus ("xyz@c.us")
console.log("status: " + status)
- To get the display picture of some person/group
const ppUrl = await conn.getProfilePicture ("xyz@g.us")
console.log("download profile picture from: " + ppUrl)
- To change your display picture or a group's
const jid = '111234567890-1594482450@g.us'
const img = fs.readFileSync ('new-profile-picture.jpeg')
await conn.updateProfilePicture (jid, img)
- To get someone's presence (if they're typing, online)
conn.on ('CB:Presence', json => console.log(json.id + " presence is " + json.type))
await conn.requestPresenceUpdate ("xyz@c.us")
- To search through messages
const response = await conn.searchMessages ('so cool', null, 25, 1)
console.log (`got ${response.messages.length} messages in search`)
const response2 = await conn.searchMessages ('so cool', '1234@c.us', 25, 1)
- To block or unblock user
await conn.blockUser ("xyz@c.us", "add")
await conn.blockUser ("xyz@c.us", "remove")
Of course, replace xyz
with an actual ID.
Groups
- To create a group
const group = await conn.groupCreate ("My Fab Group", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"])
console.log ("created group with id: " + group.gid)
conn.sendMessage(group.gid, "hello everyone", MessageType.extendedText)
- To add people to a group
const response = await conn.groupAdd ("abcd-xyz@g.us", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"])
- To make/demote admins on a group
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"])
- To change the group's subject
await conn.groupUpdateSubject("abcd-xyz@g.us", "New Subject!")
- To change the group's description
await conn.groupUpdateDescription("abcd-xyz@g.us", "This group has a new description")
- To change group settings
import { GroupSettingChange } from '@adiwajshing/baileys'
await conn.groupSettingChange ("abcd-xyz@g.us", GroupSettingChange.messageSend, true)
await conn.groupSettingChange ("abcd-xyz@g.us", GroupSettingChange.settingsChange, false)
await conn.groupSettingChange ("abcd-xyz@g.us", GroupSettingChange.settingsChange, true)
- To leave a group
await conn.groupLeave ("abcd-xyz@g.us")
- To get the invite code for a group
const code = await conn.groupInviteCode ("abcd-xyz@g.us")
console.log("group code: " + code)
- To query the metadata of a group
const metadata = await conn.groupMetadata ("abcd-xyz@g.us")
console.log(json.id + ", title: " + json.subject + ", description: " + json.desc)
const metadata2 = await conn.groupMetadataMinimal ("abcd-xyz@g.us")
- To join the group using the invitation code
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
const response = await conn.revokeInvite ("abcd-xyz@g.us")
console.log("new group code: " + response.code)
Broadcast Lists & Stories
Writing Custom Functionality
Baileys is written, keeping in mind, that you may require other custom functionality. Hence, instead of having to fork the project & re-write the internals, you can simply write extensions in your own code.
First, enable the logging of unhandled messages from WhatsApp by setting
conn.logger.level = 'debug'
This will enable you to see all sorts of messages WhatsApp sends in the console. Some examples:
-
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]]]
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"
Hence, you can register a callback for an event using the following:
conn.on (`CB:action,,battery`, json => {
const batteryLevelStr = json[2][0][1].value
const batterylevel = parseInt (batteryLevelStr)
console.log ("battery level: " + batterylevel + "%")
})
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"
-
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:
conn.on ('CB:Conn,pushname', json => {
const pushname = json[1].pushname
conn.user.name = pushname
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.