@zeppeliorg/wbails
A high-performance WhatsApp Web library built on Baileys, with critical paths accelerated via a Rust WASM bridge.
Index
MEX β WhatsApp's Internal GraphQL
All WhatsApp features (privacy, passkeys, profiles, usernames, integrity checks, β¦) run over MEX β WhatsApp's GraphQL-over-WebSocket protocol. Every method is available directly on your socket:
await sock.setPrivacySetting('LAST_SEEN', 'CONTACTS')
await sock.updateGroupsAddPrivacy('contact_blacklist')
const result = await sock.contactIntegrityQuery(['491234567890@s.whatsapp.net'])
const user = await sock.findUserByUsername('zepysk')
const abouts = await sock.getTextStatusList(['491234567890@s.whatsapp.net'])
try {
await sock.setPrivacySetting('LAST_SEEN', 'NONE')
} catch (err) {
}
See MEX.md for full documentation.
Feature Documentation
| MEX β WhatsApp's internal GraphQL protocol | MEX.md |
| USync β Bulk user data queries (devices, status, picture, LID) | USYNC.md |
| HTTPS GraphQL β Meta AI, Events, Payments | GRAPHQL.md |
| Privacy, Profile & Account | PRIVACY.md |
| Registration, Passkeys & Account Management | REGISTRATION.md |
| Managed Accounts, Payments Passkey & IPLS | MANAGED-ACCOUNT.md |
| Communities & AI Groups | COMMUNITIES.md |
| Interoperability (BirdyChat, Haiket, DMA) | INTEROP.md |
Username (@username) | USERNAME.md |
| Anti-Ban System | ANTIBAN.md |
| Ban & Enforcement System (APK internals) | BAN-SYSTEM.md |
| WhatsApp Protocol Reference (APK namespaces) | PROTOCOLS.md |
| WA-Web Protocol Port (chat-block, call links, group settings, coexistence) | WA-WEB-PORT.md |
What's Different
Performance β Rust WASM
| Binary decode | JS | Rust WASM |
| Noise handshake | JS | Rust WASM |
| AES / HMAC / HKDF | JS (crypto) | Rust WASM |
| Signal protocol | libsignal-node | Rust WASM |
Extra Features
| Meta AI / msmsg decrypt | Full messageSecret-encrypted AI message decryption |
| Meta AI message handling | Receive and process Meta AI bot responses |
| Rich AI composer | Send tables, lists, code blocks, LaTeX via Meta AI format |
| Interactive buttons | List, reply, template, cards, product list, PIX/PAY |
| Interop (FB/IG) | Near-parity with mobile & web for cross-platform JIDs |
| Anti-ban measures | Connection fingerprinting aligned with official clients |
| Album messages | Send multiple media as an album |
| Sticker packs | Sticker pack message support |
| Newsletter messages | Follower invite messages |
| WA-Web protocol port | Chat-block toggle, call-link waiting room, community sub-group ops, group sharing settings, business eligibility, opt-out/push, coexistence events β WA-WEB-PORT.md |
| Top-level call signalling | Emits call for both <call>-wrapped and top-level <offer>/<terminate> stanzas (+ acks them) |
Installation
npm install npm:@zeppeliorg/wbails
yarn add npm:@zeppeliorg/wbails
Requirements: Node.js β₯ 20
Optional peer dependencies:
sharp | Image processing / thumbnails |
jimp | Fallback image processing |
audio-decode | Voice message metadata |
link-preview-js | Link preview generation |
Running on Termux / Android
pkg install nodejs-lts
npm install npm:@zeppeliorg/wbails
whatsapp-rust-bridge compiles to WASM, not a platform-specific native binary, and
ships a prebuilt .wasm artifact β so a normal npm install typically works on Termux
without installing a Rust toolchain at all (WASM bytecode runs the same on ARM, x86,
etc., unlike a native N-API .node addon). It's still marked as an optional
dependency as a safety net: on the rare setup where the prebuilt doesn't load (very old
CPUs lacking WASM SIMD, an unusual libc, etc.), npm install will not fail, and the
package still loads and works:
- MD5, SHA-256, HMAC-SHA256, AES-GCM/CTR/CBC, and HKDF transparently fall back to
Node's built-in
crypto module β no functionality lost, no native module needed.
- WABinary node encoding/decoding fall back to a pure-JS implementation.
- X25519 key exchange and XEdDSA signing (used for identity/pre-key generation
and signing) also fall back to pure JS β X25519 itself via Node's own built-in
crypto (native, audited), and XEdDSA signing via a small hand-written
Edwards-curve implementation validated against the official RFC 8032 Ed25519 test
vectors and hundreds of randomized MontgomeryβEdwards interop checks (see
Utils/curve25519-js.js).
- The Noise handshake and the Signal Double Ratchet session genuinely need the
module (hand-rolling a full protocol session state machine from scratch is a much
larger correctness/security risk than a single curve operation, so there's no JS
fallback for these). Without the native module, connecting a WhatsApp session will
throw a clear error rather than silently failing β everything else (stickers, the
in-memory store, USync helpers, the command loader, session pool, key generation,
etc.) keeps working.
If you ever do need to build it from source (e.g. WHATSAPP_RUST_BRIDGE_SKIP_PREBUILT=1):
pkg install rust binutils
npm install npm:@zeppeliorg/wbails
Note: some published versions of the bridge ship a package.json whose
"exports" map is missing a . entry, which makes a plain require() throw
ERR_PACKAGE_PATH_NOT_EXPORTED even though the module itself works fine. This
package works around that automatically (it locates the installed module's real
entry file and requires it directly) β no action needed on your end.
Other Termux notes:
sharp needs pkg install libvips to build; if that's not available, install
jimp instead (npm install jimp) β it's a pure-JS fallback used automatically.
- Animated stickers (
videoToWebpSticker) need ffmpeg on PATH: pkg install ffmpeg.
Running on Pterodactyl
Works fine on a standard Node.js egg β Pterodactyl containers are normal x86_64 Linux
(Debian/Alpine-based) Docker images, which is the best-supported target for both the
prebuilt WASM bridge and sharp's prebuilt binaries. Checklist:
- Pick a Node.js β₯ 20 egg/variable (the package's
engines field enforces this).
- If you want animated stickers, make sure
ffmpeg is available in the container β
either an egg/image that already bundles it, or add an install script step
(apt-get install -y ffmpeg on Debian-based yolks). Static stickers only need
sharp, which installs normally.
- Persistent storage: point
useMultiFileAuthState/the store's writeToFile at a path
under the server's data volume so sessions survive container restarts/reinstalls.
- If the egg's image is Alpine/musl-based rather than Debian/glibc-based, the
prebuilt WASM/
sharp binaries are less likely to match β everything still installs
(thanks to the fallbacks above), but a live connection needs the native bridge to
work, so prefer a glibc-based Node image if you have the choice.
Connecting Account
QR Code
const { makeWASocket, useMultiFileAuthState, DisconnectReason } = require('@zeppeliorg/wbails')
const { Boom } = require('@hapi/boom')
const { state, saveCreds } = await useMultiFileAuthState('./auth')
const sock = makeWASocket({ auth: state, printQRInTerminal: true })
sock.ev.on('creds.update', saveCreds)
sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
if (connection === 'close') {
const shouldReconnect = new Boom(lastDisconnect?.error)?.output?.statusCode !== DisconnectReason.loggedOut
if (shouldReconnect) connect()
}
})
Pairing Code
const sock = makeWASocket({ auth: state, printQRInTerminal: false })
if (!state.creds.registered) {
const code = await sock.requestPairingCode('49123456789')
console.log('Pairing code:', code)
}
Receive Full History
const sock = makeWASocket({
auth: state,
syncFullHistory: true
})
Socket Config Notes
const sock = makeWASocket({
auth: state,
cachedGroupMetadata: async (jid) => groupCache.get(jid),
getMessage: async (key) => store.getMsg(key),
markOnlineOnConnect: false,
})
Saving & Restoring Sessions
const { useMultiFileAuthState } = require('@zeppeliorg/wbails')
const { state, saveCreds } = await useMultiFileAuthState('./auth')
sock.ev.on('creds.update', saveCreds)
Handling Events
Messages
sock.ev.on('messages.upsert', ({ messages, type }) => { })
Prefer routing by text pattern instead of parsing messages.upsert by hand?
Every socket also comes with Telegraf/node-telegram-bot-api-style helpers β see
onText / hears / command below.
sock.ev.on('messages.update', updates => { })
sock.ev.on('messages.delete', ({ keys }) => { })
sock.ev.on('messages.media-update', updates => { })
sock.ev.on('messages.reaction', reactions => { })
sock.ev.on('message.comment', ({ message, comment }) => { })
sock.ev.on('message.quarantined', ({ message }) => { })
sock.ev.on('poll.add-option', ({ key, senderTimestampMs }) => { })
Text Routing (onText / hears / command)
Every socket returned by makeWASocket() already has these β no wrapping or setup
needed. They're built on top of messages.upsert internally (one shared listener
regardless of how many routes you add), so you don't have to parse
msg.message.conversation / extendedTextMessage.text / captions by hand.
sock.onText(/^ping$/i, (msg, match) => {
sock.sendMessage(msg.key.remoteJid, { text: 'pong' }, { quoted: msg })
})
sock.hears('menu', (msg) => {
sock.sendMessage(msg.key.remoteJid, { text: '1. Foo\n2. Bar' })
})
sock.command('start', (msg, match) => {
sock.sendMessage(msg.key.remoteJid, { text: `Started with: ${match[1] ?? '(no args)'}` })
})
sock.command(['help', 'h'], (msg) => {
sock.sendMessage(msg.key.remoteJid, { text: 'Available commands: ...' })
})
All three (onText, hears, command) return an unsubscribe function:
const stop = sock.onText(/^bye$/i, (msg) => { })
stop()
Chats & Contacts
sock.ev.on('chats.upsert', chats => { })
sock.ev.on('chats.update', chats => { })
sock.ev.on('chats.delete', ids => { })
sock.ev.on('chats.lock', ({ id, locked }) => { })
sock.ev.on('contacts.upsert', contacts => { })
sock.ev.on('contacts.update', contacts => { })
sock.ev.on('blocklist.update', ({ blocklist, type }) => { })
Groups
sock.ev.on('groups.upsert', groups => { })
sock.ev.on('groups.update', updates => { })
sock.ev.on('group-participants.update', ({ id, participants, action }) => { })
sock.ev.on('group.join-request', ({ id, participant, action }) => { })
sock.ev.on('group.member-tag.update', ({ id, participant }) => { })
Newsletters
sock.ev.on('newsletter-settings.update', update => { })
sock.ev.on('newsletter-participants.update', update => { })
sock.ev.on('newsletter.reaction', update => { })
sock.ev.on('newsletter.view', update => { })
sock.ev.on('newsletter.live-update', update => { })
sock.ev.on('newsletter.pin', update => { })
sock.ev.on('newsletter.invite', update => { })
Connection & Auth
sock.ev.on('connection.update', ({ connection, qr, lastDisconnect, isOnline, reachoutTimeLock }) => { })
sock.ev.on('creds.update', saveCreds)
sock.ev.on('security.alert', data => { })
sock.ev.on('identity.update', ({ jid }) => { })
sock.ev.on('server.config', config => { })
Calls
sock.ev.on('call', calls => { })
sock.ev.on('call.scheduled', ({ call }) => { })
sock.ev.on('call.schedule-cancelled', ({ call }) => { })
const token = await sock.createCallLink('audio')
await sock.toggleCallLinkWaitingRoom(token, true, 'audio')
sock.ev.on('coexistence.update', u => { })
sock.ev.on('business.privacy-settings-sync', s => { })
Labels
sock.ev.on('labels.edit', ({ label }) => { })
sock.ev.on('labels.association', ({ association, type }) => { })
sock.ev.on('labels.reorder', ({ labelIds }) => { })
Presence & Devices
sock.ev.on('presence.update', ({ id, presences }) => { })
sock.ev.on('devices.update', ({ id, devices, isSelf }) => { })
Bot / Meta AI
sock.ev.on('bot.feedback', ({ message }) => { })
sock.ev.on('bot.stop-generation', ({ message }) => { })
sock.ev.on('bot.welcome-request', ({ message }) => { })
sock.ev.on('bot.psi-metadata', ({ message }) => { })
sock.ev.on('bot.query-fanout', ({ message }) => { })
sock.ev.on('bot.media-collection', ({ message }) => { })
sock.ev.on('bot.memu-onboarding', ({ message }) => { })
Sync & Settings
sock.ev.on('messaging-history.set', ({ chats, contacts, messages, isLatest }) => { })
sock.ev.on('messaging-history.status', ({ progress, hasMore }) => { })
sock.ev.on('settings.update', ({ setting, value }) => { })
sock.ev.on('lid-mapping.update', ({ lid, pn }) => { })
sock.ev.on('status.psa', ({ message }) => { })
sock.ev.on('status.mention', ({ message }) => { })
sock.ev.on('media.notify', ({ message }) => { })
sock.ev.on('reminder.update', ({ message }) => { })
sock.ev.on('payment.split', ({ message }) => { })
sock.ev.on('payment.reminder', ({ message }) => { })
sock.ev.on('cloud.thread.control', ({ message }) => { })
sock.ev.on('galaxy.flow.completed', ({ message }) => { })
Decrypt Poll Votes
const { getAggregateVotesInPollMessage } = require('@zeppeliorg/wbails')
sock.ev.on('messages.update', async updates => {
for (const { key, update } of updates) {
if (update.pollUpdates) {
const pollCreation = await getMessage(key)
if (pollCreation) {
const votes = getAggregateVotesInPollMessage({ message: pollCreation, pollUpdates: update.pollUpdates })
console.log(votes)
}
}
}
})
Anti-Ban System
Import from @zeppeliorg/wbails/src/antiban.js:
const {
AntiBan, RateLimiter, WarmUp, HealthMonitor,
TimelockGuard, ReplyRatioGuard, ContactGraphWarmer,
PresenceChoreographer, PostReconnectThrottle,
RetryReasonTracker, LidResolver, JidCanonicalizer,
MessageQueue, Scheduler, wrapSocket
} = require('@zeppeliorg/wbails/src/antiban')
RateLimiter β throttle outbound messages
const limiter = new RateLimiter({
maxPerMinute: 8,
maxPerHour: 200,
maxPerDay: 1500,
minDelayMs: 1500,
maxDelayMs: 5000,
newChatDelayMs: 3000
})
const delay = await limiter.getDelay(jid, text)
if (delay === -1) return
if (delay > 0) await sleep(delay)
await sock.sendMessage(jid, { text })
limiter.record(jid, text)
WarmUp β gradual daily limit increase for new numbers
const warmup = new WarmUp({ warmUpDays: 7, day1Limit: 20, growthFactor: 1.8 })
if (!warmup.canSend()) return
await sock.sendMessage(jid, { text })
warmup.record()
console.log(warmup.getStatus())
HealthMonitor β detect ban risk
const health = new HealthMonitor({ autoPauseAt: 'high' })
sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
if (connection === 'close') health.recordDisconnect(lastDisconnect?.error)
if (connection === 'open') health.recordReconnect()
})
const status = health.getStatus()
if (health.isPaused()) return
TimelockGuard β handle WA 463 reachout blocks
const guard = new TimelockGuard()
sock.ev.on('connection.update', ({ reachoutTimeLock }) => {
if (reachoutTimeLock) guard.onTimelockUpdate(reachoutTimeLock)
})
const { allowed, reason } = guard.canSend(jid)
if (!allowed) return console.log(reason)
PresenceChoreographer β human-like typing simulation
const choreo = new PresenceChoreographer({
enabled: true,
typingWPM: 45,
enableCircadianRhythm: true,
timezone: 'Europe/Berlin'
})
const plan = choreo.computeTypingPlan(text.length)
await choreo.executeTypingPlan(sock, jid, plan)
await sock.sendMessage(jid, { text })
wrapSocket β apply all anti-ban layers at once
const { wrapSocket, resolveConfig, PRESETS } = require('@zeppeliorg/wbails/src/antiban')
const wrappedSock = wrapSocket(sock, resolveConfig(PRESETS.SAFE))
Sending Messages
Text & Basic
await sock.sendMessage(jid, { text: 'Hello!' })
await sock.sendMessage(jid, { text: 'Reply' }, { quoted: msg })
await sock.sendMessage(jid, { text: '@49123456789', mentions: ['49123456789@s.whatsapp.net'] })
await sock.sendMessage(jid, { forward: msg })
await sock.sendMessage(jid, { location: { degreesLatitude: 52.5, degreesLongitude: 13.4 } })
await sock.sendMessage(jid, {
liveLocation: { degreesLatitude: 52.5, degreesLongitude: 13.4 },
accuracyInMeters: 10,
speedInMps: 0,
degreesClockwisefromMagneticNorth: 0,
caption: 'Live',
sequenceNumber: 1
})
await sock.sendMessage(jid, { contacts: { displayName: 'Name', contacts: [{ vcard: '...' }] } })
Rich Response (table / code / latex / images)
Renders as WhatsApp's native AI-assistant-style rich card (the same UI Meta AI uses
for search results). table accepts either a plain 2D array (first row = header) or
{ rows: [...] }.
await sock.sendMessage(jid, {
richResponse: {
text: 'XazepysK',
table: [
['header1', 'header2'],
['X1', 'X2']
],
}
}, { })
await sock.sendMessage(jid, { react: { text: 'π', key: msg.key } })
await sock.sendMessage(jid, { pin: { type: 1, time: 86400, key: msg.key } })
await sock.sendMessage(jid, {
poll: { name: 'Vote?', values: ['Yes', 'No'], selectableCount: 1 }
})
await sock.sendMessage(jid, { call: { callId: '...', callType: 'audio' } })
Buttons & Interactive
await sock.sendMessage(jid, {
buttonsMessage: {
text: 'Choose:',
buttons: [
{ buttonId: '1', buttonText: { displayText: 'Option A' } },
{ buttonId: '2', buttonText: { displayText: 'Option B' } }
]
}
})
await sock.sendMessage(jid, {
listMessage: {
title: 'Menu',
description: 'Pick one',
buttonText: 'Open',
listType: 1,
sections: [{
title: 'Section',
rows: [{ title: 'Item 1', rowId: 'item1' }]
}]
}
})
await sock.sendMessage(jid, {
templateMessage: {
hydratedTemplate: {
hydratedContentText: 'Hello',
hydratedButtons: [
{ quickReplyButton: { displayText: 'Yes', id: 'yes' } },
{ urlButton: { displayText: 'Visit', url: 'https://example.com' } }
]
}
}
})
await sock.sendMessage(jid, {
interactiveMessage: {
body: { text: 'Choose' },
footer: { text: 'Footer' },
nativeFlowMessage: {
buttons: [{ name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Yes', id: 'yes' }) }]
}
}
})
Media
await sock.sendMessage(jid, { image: { url: './image.jpg' }, caption: 'Caption' })
await sock.sendMessage(jid, { video: { url: './video.mp4' }, caption: 'Video' })
await sock.sendMessage(jid, { audio: { url: './audio.mp3' }, mimetype: 'audio/mp4' })
await sock.sendMessage(jid, { audio: { url: './audio.ogg' }, mimetype: 'audio/ogg; codecs=opus', ptt: true })
await sock.sendMessage(jid, { video: { url: './anim.mp4' }, gifPlayback: true })
await sock.sendMessage(jid, { video: { url: './clip.mp4' }, ptv: true })
await sock.sendMessage(jid, { image: { url: './secret.jpg' }, viewOnce: true })
await sock.sendAlbumMessage(jid, [
{ image: { url: './1.jpg' } },
{ image: { url: './2.jpg' } },
{ video: { url: './3.mp4' } }
], { caption: 'Album' })
Meta AI / Rich Responses
await sock.sendRichAIResponse(jid, {
table: { headers: ['Name', 'Value'], rows: [['Foo', '1'], ['Bar', '2']] }
})
await sock.sendRichAIResponse(jid, {
list: { items: ['Item 1', 'Item 2', 'Item 3'] }
})
await sock.sendRichAIResponse(jid, {
codeBlock: { language: 'js', code: 'console.log("hello")' }
})
await sock.sendRichAIResponse(jid, {
latex: 'E = mc^2'
})
await sock.captureAndResendUnifiedResponse(jid, metaAiMsg)
Status / Stories
await sock.sendMessage('status@broadcast', {
text: 'Hello @49123',
mentions: ['49123@s.whatsapp.net'],
statusMentionedJids: ['49123@s.whatsapp.net']
})
await sock.sendMessage('status@broadcast', {
stickerInteraction: { sticker: { url: './sticker.webp' }, reactionKey: msg.key }
})
await sock.sendMessage(jid, { text: 'Reply to status' }, { quoted: statusMsg })
Modifying Messages
await sock.sendMessage(jid, { delete: msg.key })
await sock.sendMessage(jid, { edit: msg.key, text: 'Updated text' })
Manipulating Media
const { downloadMediaMessage } = require('@zeppeliorg/wbails')
const buffer = await downloadMediaMessage(msg, 'buffer', {})
const { url } = await sock.waUploadToServer(buffer, { mimetype: 'image/jpeg' })
Groups
const group = await sock.groupCreate('Name', ['49123@s.whatsapp.net'])
await sock.groupParticipantsUpdate(jid, ['49123@s.whatsapp.net'], 'add')
await sock.groupUpdateSubject(jid, 'New Name')
await sock.groupUpdateDescription(jid, 'Description')
await sock.groupSettingUpdate(jid, 'announcement')
await sock.groupLeave(jid)
const code = await sock.groupInviteCode(jid)
await sock.groupRevokeInvite(jid)
await sock.groupAcceptInvite(code)
const meta = await sock.groupMetadata(jid)
const requests = await sock.groupRequestParticipantsList(jid)
await sock.groupRequestParticipantsUpdate(jid, ['49123@s.whatsapp.net'], 'approve')
const all = await sock.groupFetchAllParticipating()
await sock.groupToggleEphemeral(jid, 86400)
await sock.groupAcknowledge(jid)
const linkedParts = await sock.groupGetLinkedParticipants(communityJid)
await sock.groupJoinLinked(communityJid, subGroupJid)
const pics = await sock.getGroupProfilePictures([jid1, jid2], 'preview')
Privacy
await sock.updateBlockStatus(jid, 'block')
const privacy = await sock.fetchPrivacySettings()
const fresh = await sock.fetchPrivacySettings(true)
const list = await sock.fetchBlocklist()
await sock.updateLastSeenPrivacy('contacts')
await sock.updateOnlinePrivacy('all')
await sock.updateProfilePicturePrivacy('contacts')
await sock.updateStatusPrivacy('contacts')
await sock.updateReadReceiptsPrivacy('all')
await sock.updateGroupsAddPrivacy('contacts')
await sock.updateCallPrivacy('all')
await sock.updateDefaultDisappearingMode(86400)
await sock.setPrivacySetting('LAST_SEEN', 'CONTACTS')
await sock.setPrivacySetting('GROUPS', 'CONTACT_BLACKLIST')
await sock.setPrivacySetting('CALLS', 'NONE')
await sock.updatePrivacyContactList('groupadd', 'contact_blacklist', [jid1, jid2])
const current = await sock.getPrivacyContactList('groupadd', 'contact_blacklist')
const blockStatus = await sock.getChatBlockingStatus()
await sock.updateChatBlockingStatus('block')
const notices = await sock.getUserDisclosures()
const optOut = await sock.getOptOutList()
const push = await sock.getPushConfig()
See MEX.md for full MEX usage and error handling.
User Queries
const results = await sock.onWhatsApp('49123456789')
const ppUrl = await sock.profilePictureUrl(jid, 'image')
const status = await sock.fetchStatus(jid)
const abouts = await sock.getTextStatusList([jid])
const biz = await sock.getBusinessProfile(jid)
await sock.subscribePresence(jid)
sock.ev.on('presence.update', ({ id, presences }) => { })
await sock.fetchMessageHistory(50, oldestMsg.key, oldestMsg.messageTimestamp)
const user = await sock.findUserByUsername('someusername')
const integrity = await sock.contactIntegrityQuery([jid])
Change Profile
await sock.updateProfileStatus('My status')
await sock.updateProfileName('New Name')
await sock.updateProfilePicture(jid, { url: './photo.jpg' })
await sock.removeProfilePicture(jid)
Chat Modifiers
await sock.chatModify({ archive: true, lastMessages: [msg] }, jid)
await sock.chatModify({ mute: Date.now() + 8 * 60 * 60 * 1000 }, jid)
await sock.chatModify({ markRead: false, lastMessages: [msg] }, jid)
await sock.chatModify({ clear: { messages: [{ id: msg.key.id, fromMe: msg.key.fromMe }] } }, jid)
await sock.chatModify({ delete: true, lastMessages: [msg] }, jid)
await sock.chatModify({ star: { messages: [{ id: msg.key.id, fromMe: msg.key.fromMe }], star: true } }, jid)
await sock.sendMessage(jid, { disappearingMessagesInChat: 86400 })
Writing Custom Functionality
const sock = makeWASocket({ logger: pino({ level: 'debug' }) })
sock.ws.on('CB:message', node => console.log(node))
sock.ws.on('CB:iq,,result', node => { })
A handful of batteries-included helpers on top of stock Baileys, importable from the
main package (require('@zeppeliorg/wbails') / top-level Utils exports):
Sticker Maker
const { imageToWebpSticker, videoToWebpSticker } = require('@zeppeliorg/wbails')
const stickerBuf = await imageToWebpSticker(imageBuffer, {
packName: 'My Pack',
packPublisher: 'Me'
})
await sock.sendMessage(jid, { sticker: stickerBuf })
const animatedBuf = await videoToWebpSticker(videoBuffer, { packName: 'My Pack', packPublisher: 'Me' })
Auto-Cache View-Once Media
Downloads and saves view-once photos/videos/voice notes to disk the moment they arrive,
before the sender's app can mark them as opened.
const { autoCacheViewOnceMedia } = require('@zeppeliorg/wbails')
const stop = autoCacheViewOnceMedia(sock, { cacheDir: './viewonce-cache' })
Folder-Based Command Loader
const { createCommandHandler } = require('@zeppeliorg/wbails')
createCommandHandler(sock, { commandsDir: './commands', prefix: '.' })
module.exports = {
name: 'ping',
aliases: ['p'],
async execute({ sock, jid }) {
await sock.sendMessage(jid, { text: 'pong' })
}
}
Multi-Account Session Pool
Runs several accounts side by side and auto-reconnects any that drop (except on
logout) with exponential backoff + jitter, instead of hand-rolling a reconnect loop
per project.
const { createSessionPool } = require('@zeppeliorg/wbails')
const pool = createSessionPool({
makeSocket: async sessionId => {
const { state, saveCreds } = await useMultiFileAuthState(`./sessions/${sessionId}`)
const sock = makeWASocket({ auth: state })
sock.ev.on('creds.update', saveCreds)
return sock
},
logger
})
await pool.add('account-1')
await pool.add('account-2')
Store Auto-Save, Message Limits & Encryption
makeInMemoryStore now persists everything (chats, contacts, messages,
labels, groupMetadata, presences, state) instead of a subset, and supports:
const store = makeInMemoryStore({
socket: sock,
maxMessagesPerChat: 500,
encryptionKey: process.env.STORE_KEY
})
store.readFromFile('./store.json')
const stopAutoSave = store.startAutoSave('./store.json', 30_000)
The native module lives at 7ucg/whatsapp-rust-bridge.
Pre-built and bundled β no Rust toolchain needed to use this package.
Functions offloaded to Rust:
decodeNode | WABinary protocol decoding |
NoiseSession | Noise_XX_25519_AESGCM_SHA256 handshake + framing |
hkdf | HKDF key derivation |
hmacSign | HMAC-SHA256 signing |
sha256 | SHA-256 hashing |
aesEncrypt / aesDecrypt | AES-256-CBC |
aesEncryptGCM / aesDecryptGCM | AES-256-GCM |
aesEncryptCTR / aesDecryptCTR | AES-256-CTR |
License
MIT