npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mr-supun-fernando/supunmd-bail

v3.0.3

Published

WhatsApp API Modification By Supun Fernando

Readme

🧑‍💻 supunmd-bail 🇱🇰

Open-source WhatsApp automation library — no browser required. Built on WebSocket for speed, stability, and full multi-device support.

Installation • Features • Stability • Website • WhatsApp Channel • YouTube


✨ What is supunmd-bail?

supunmd-bail (@mr-supun-fernando/supunmd-bail) is a powerful, open-source WhatsApp Web library built on top of the Baileys protocol stack — extended with features not found in any other public fork. It connects directly to WhatsApp's multi-device WebSocket protocol. No Selenium, no Puppeteer, no browser overhead.

⚡ Node.js ≥ 20 required.


🚀 Getting Started

npm install @mr-supun-fernando/supunmd-bail
import { makeWASocket, useMultiFileAuthState } from '@mr-supun-fernando/supunmd-bail'

const { state, saveCreds } = await useMultiFileAuthState('auth_info')
const sock = makeWASocket({
    auth: state,
    syncFullHistory: false,  // skip history sync — bot works immediately
    aiLabel: true,           // stamp messages with AI bot marker (default: true)
})

sock.ev.on('creds.update', saveCreds)

🧩 Main Features

| Feature | Description | |---|---| | 💬 Rich Response (GenAI Bubble) | Send Meta AI-style messages: markdown, code blocks, tables, LaTeX, maps, inline images, HTML — rendered natively as GenAI bubble in WA client | | 🚫 4-Factor Ban Checker | Detect ban status via 4 independent signals: live registry, public send-page, identity-key probe, device-count signature | | 🆔 Username Socket (w:mex) | Full WA username API: check availability, set, delete, pin, find by username, fetch recommendations — via WhatsApp's internal Pando/MEX GraphQL protocol | | 🏘️ Communities Socket | Full community management: create, link/unlink groups, manage participants, invite codes, ephemeral toggle, approval mode | | 🔕 noSelfSync | Skip syncing outgoing messages to your own other devices — reduces noise and bandwidth. Includes silent-drop guard (throws 421 instead of pretending the send succeeded) | | ⚙️ Rust-powered Crypto | md5, hkdf, LT-Hash anti-tampering offloaded to whatsapp-rust-bridge native module for raw speed | | 🗂️ LID Mapping Store | Persistent LID ↔ phone-number bi-directional cache with LRU eviction and in-flight dedup — prevents duplicate USync lookups | | 🔐 Pre-Key Manager | Concurrency-safe Signal pre-key operations via per-key-type PQueue — no race conditions on key updates/deletions | | 🔄 Classify Disconnect | Maps every WA disconnect code to { category, shouldReconnect, backoffMs } — properly handles code 515 (restartRequired) as recoverable, not fatal | | ⏱️ Rate Limiter | Anti-spam pacing calculator: enforces per-minute/hour/day caps, burst allowance, new-chat delays, identical-message dedup | | 🖼️ Album Message | Send multiple images/videos as a single WA album/grid, with per-item or top-level caption and gifPlayback support | | 🤖 AI Label Config | aiLabel: true/false in socket config — controls whether outgoing messages carry the biz_bot attribute that WA uses to render the AI icon | | 🔎 USync Username Protocol | Username resolution baked into USync queries — resolve WA usernames alongside contacts in a single round-trip | | 📥 Offline Node Processor | Batch-processes pending stanzas received while offline, preventing message loss on reconnect | | 🔑 Identity Change Handler | Dedicated handler for Signal identity key changes — prevents session corruption when a contact re-registers |


💬 Rich Response (GenAI Bubble)

Send messages that render as Meta AI-style bubbles inside WhatsApp. Supports multiple primitives in a single message:

// Markdown text
await sock.sendMessage(jid, {
    richResponse: {
        text: '**Hello** from *supunmd-bail*',
        responseId: 'optional-uuid'
    }
})

// Code block
await sock.sendMessage(jid, {
    richResponse: {
        code: 'console.log("hello")',
        language: 'javascript'
    }
})

// Table
await sock.sendMessage(jid, {
    richResponse: {
        table: {
            rows: [
                ['Name', 'Age'],
                ['Alice', '25'],
                ['Bob', '30']
            ]
        }
    }
})

// HTML (raw HTML rendered in client)
await sock.sendMessage(jid, {
    richResponse: {
        text: 'fallback',
        html: '<b>bold</b> <a href="https://example.com">link</a>'
    }
})

// LaTeX
await sock.sendMessage(jid, {
    richResponse: {
        latex: 'E = mc^2'
    }
})

// Map / location
await sock.sendMessage(jid, {
    richResponse: {
        map: {
            latitude: -6.2,
            longitude: 106.8,
            zoom: 15,
            title: 'Jakarta',
            annotations: []
        }
    }
})

// Inline image
await sock.sendMessage(jid, {
    richResponse: {
        imageUrl: 'https://example.com/photo.jpg'
    }
})

Shortcut methods (all accept quoted and options):

await sock.sendTable(jid, 'Title', ['H1','H2'], [['A','B']], quoted)
await sock.sendList(jid, 'Title', ['item1','item2'], quoted)
await sock.sendCodeBlock(jid, 'print("hello")', quoted, { language: 'python' })
await sock.sendLatex(jid, quoted, { latex: 'x^2 + y^2 = z^2' })
await sock.sendRichMessage(jid, submessages, quoted)

🖼️ Album Message

Send multiple images/videos grouped into a single WA album:

await sock.sendMessage(jid, {
    album: [
        { image: { url: 'https://example.com/a.jpg' } },
        { image: { url: 'https://example.com/b.jpg' }, caption: 'caption foto kedua' },
        { video: { url: 'https://example.com/c.mp4' }, gifPlayback: false }
    ],
    caption: 'caption ini otomatis ke item pertama'
})

🚫 Ban Checker

4-factor ban detection against WhatsApp's own servers — no third-party API:

const result = await sock.checkBanStatus('628123456789')
// or: sock.checkBanStatus('[email protected]')

console.log(result)
// {
//   status: 'ACTIVE' | 'PROFILE_HIDDEN' | 'LIKELY_ACTIVE' | 'BANNED' | 'OFF_WHATSAPP' | 'UNKNOWN',
//   emoji: '🟢' | '🟡' | '🔴' | '❓',
//   confidence: 0..1,
//   deviceCount: number | null,
//   registryExists: boolean | null,
//   pageVisible: boolean | null,
//   profileName: string | null
// }

Four factors checked:

  • Factor A — Live registry (sock.onWhatsApp)
  • Factor B — Public send-page title/image probe (api.whatsapp.com)
  • Factor C — Identity-key device probe via USync (strongest: banned numbers have their keys destroyed)
  • Factor D — Device-count signature (2+ = ACTIVE, 1 generic = likely BANNED, 0 = BANNED/OFF_WA)

🆔 Username Socket (w:mex)

Full WA @username API via WhatsApp's internal Pando/MEX GraphQL protocol, query IDs sourced from Java decompile of WA 2.26.17.2:

// Check availability
const res = await sock.checkUsername('myusername')
// { available: true, username } or { available: false, suggestions: [...] }

// Set username
await sock.setUsername('myusername')

// Delete username
await sock.deleteUsername()

// Get your own username
const mine = await sock.getMyUsername()

// Pin/unpin username
await sock.setUsernamePin(true)

// Find user by username
const user = await sock.findUserByUsername('someuser', pin)

// Fetch usernames of your contacts in batch
const usernames = await sock.fetchContactUsernames('[email protected]', '[email protected]')

// Get username suggestions
const suggestions = await sock.getUsernameRecommendations()

🏘️ Communities Socket

// Create community
const community = await sock.communityCreate('Community Name', 'Description')

// Create group inside community
await sock.communityCreateGroup('Group Name', [jid1, jid2], communityJid)

// Link/unlink existing group
await sock.communityLinkGroup(groupJid, communityJid)
await sock.communityUnlinkGroup(groupJid, communityJid)

// Fetch linked groups
const groups = await sock.communityFetchLinkedGroups(communityJid)

// Manage participants
await sock.communityParticipantsUpdate(communityJid, [jid1], 'add')   // 'add' | 'remove' | 'promote' | 'demote'

// Invite code
const code = await sock.communityInviteCode(communityJid)
await sock.communityRevokeInvite(communityJid)
await sock.communityAcceptInvite(code)

// Settings
await sock.communityToggleEphemeral(communityJid, 86400)   // seconds
await sock.communityMemberAddMode(communityJid, 'admin_add')
await sock.communityJoinApprovalMode(communityJid, 'on')
await sock.communityUpdateSubject(communityJid, 'New Name')
await sock.communityUpdateDescription(communityJid, 'New Desc')
await sock.communityLeave(communityJid)

🔕 noSelfSync

Skip syncing outgoing messages to your own linked devices. Useful for high-volume bots where sync traffic is unnecessary:

await sock.sendMessage(jid, { text: 'hello' }, { noSelfSync: true })

// Also supported in all shortcut methods:
await sock.sendTable(jid, title, headers, rows, quoted, { noSelfSync: true })
await sock.sendCodeBlock(jid, code, quoted, { noSelfSync: true })

Safe by design: if noSelfSync would accidentally drop all recipients (e.g. messaging yourself), the guard kicks in and delivers normally. If the other party has zero resolved devices, it throws Boom 421 instead of silently pretending the message was sent.


🔄 Stability & Disconnect Handling

supunmd-bail ships classifyDisconnect() — maps every WA status code into an actionable result:

import { classifyDisconnect } from '@mr-supun-fernando/supunmd-bail'

sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
    if (connection === 'close') {
        const code = lastDisconnect?.error?.output?.statusCode
        const info = classifyDisconnect(code)

        console.log(info)
        // {
        //   category: 'fatal' | 'recoverable' | 'rate-limited' | 'unknown',
        //   shouldReconnect: boolean,
        //   backoffMs: number,    // ms to wait before reconnecting
        //   message: string,
        //   code: number
        // }

        if (info.shouldReconnect) {
            setTimeout(() => reconnect(), info.backoffMs)
        }
    }
})

Code map:

| Code | Category | Reconnect | Backoff | |---|---|---|---| | 401, 440 | fatal | ❌ | — | | 515 | recoverable | ✅ | 0 ms (immediate) | | 405 | fatal | ❌ | — | | 408 | recoverable | ✅ | 30 s | | 503 | recoverable | ✅ | 5 min | | 429 | rate-limited | ✅ | 60 s | | 500 | recoverable | ✅ | 5 s | | 503 | recoverable | ✅ | 10 s | | Graceful close | recoverable | ✅ | 2 s | | Unknown | unknown | ✅ | 15 s |

Important: Code 515 (restartRequired) is WhatsApp's normal post-pairing signal. supunmd-bail correctly treats it as recoverable with 0 ms backoff — other forks incorrectly mark it as fatal, causing bots to stop after first pair.


⏱️ Rate Limiter

Anti-ban pacing calculator — wire into your send path before each sendMessage:

import { RateLimiter } from '@mr-supun-fernando/supunmd-bail'

const limiter = new RateLimiter({
    maxPerMinute: 8,
    maxPerHour: 200,
    maxPerDay: 1500,
    minDelayMs: 1500,
    maxDelayMs: 5000,
    newChatDelayMs: 3000,
    maxIdenticalMessages: 3,
    burstAllowance: 3
})

async function safeSend(jid, content) {
    const delay = await limiter.getDelay(jid, content)
    if (delay === -1) throw new Error('Rate limit exceeded — blocked')
    if (delay > 0) await new Promise(r => setTimeout(r, delay))
    await sock.sendMessage(jid, content)
    limiter.record(jid, content)
}

📦 Supported Message Types

Every WhatsApp message type is covered:

  • Text, image, video, audio, document, sticker, GIF/video note (PTV)
  • Location & live location
  • Contact / vCard
  • Poll (create, vote, add option, poll result)
  • Button, list, template, interactive (native flow)
  • Album (multiple media in one message)
  • Event & event invite
  • Group invite, group status, group mention
  • Spoiler (blurred) messages
  • Reaction & edited message
  • View-once media
  • Status/story mention & reply
  • Newsletter / channel messages
  • Payment & payment invite
  • All WhatsApp Business types: product, catalog, order, invoice, business profile
await sock.sendMessage(jid, {
    album: [
        { image: { url: 'https://example.com/1.jpg' } },
        { video: { url: 'https://example.com/2.mp4' } }
    ],
    caption: 'Album caption'
})
await sock.sendMessage(jid, {
    poll: {
        name: 'Poll Title',
        values: ['Option A', 'Option B'],
        selectableCount: 1
    }
})
await sock.sendMessage(jid, {
    text: 'Spoiler content',
    spoiler: true
})
await sock.sendMessage(jid, {
    eventMessage: {
        name: 'Meeting',
        description: 'Weekly sync',
        location: { degreesLatitude: -6.2, degreesLongitude: 106.8 },
        startTime: Math.floor(Date.now() / 1000) + 3600
    }
})

🛠️ Additional Methods

// Parse incoming extended/special messages
sock.ev.on('messages.upsert', ({ messages }) => {
    const extended = sock.parseExtendedMessageContent(messages[0].message)
    if (extended) console.log(extended.type, extended.data)
})

// Business profile
const profile = await sock.getBusinessProfile(jid)
await sock.updateBusinessProfile({ description: 'Open 9-5', email: '[email protected]', category: 'Retail' })

// Get newsletter/channel JID from URL
await sock.newsletterId('https://whatsapp.com/channel/...')

// Refresh media URL for expired messages
await sock.updateMediaMessage(message)

🌐 Community & Support

Developer: Supun Fernando


⭐ If this project helped you, consider giving it a star!

Made with ❤️ by Supun Fernando


📄 License

MIT