@dnuzi/baileys

中文文档
0.1.2发布于 2026/10/26MITES Module近 30 天下载 2796

WhatsApp Web API Library Modification By DanuZz.

原始使用文档
文档目录37 节
  1. ✨ Highlights
  2. 📥 Install
  3. 🚀 Quick Start
  4. ⚙️ Socket options
  5. ⚡ Performance Optimization & Latency Tuning
  6. 🚀 Performance Modes
  7. 🛠️ Core Optimization Architecture
  8. 📊 Real-World Benchmarks
  9. 🗄️ Session storage
  10. 🔌 Disconnect reasons
  11. 💬 Messages
  12. Text, mentions, reactions, contacts
  13. Media
  14. Stickers & sticker packs
  15. Location & events
  16. Polls
  17. Edit, delete, pin, keep
  18. Status updates
  19. 🎛️ Interactive Messages
  20. Native flow buttons
  21. Carousel
  22. Widgets (A2UI)
  23. Legacy template buttons
  24. Simulated replies
  25. 🤖 AI Rich Messages
  26. 🛍️ Commerce & Payments
  27. Extras
  28. 📞 Calls
  29. Start a call
  30. Media, video & playlists
  31. In-call controls
  32. Call events
  33. Incoming calls
  34. Multi-call
  35. 🙏 Credits
  36. 📄 License
  37. 📞 Contact
@dnuzi/baileys

@dnuzi/baileys

A fast, lightweight WhatsApp Web API for Node.js.

npm version downloads node license

Install · Quick Start · Performance · Messages · Interactive · AI Rich · Calls · Credits

✨ Highlights

Multi-session, LID / PN / Username, Voice and video calls, Zero dependencies

🆔 IdentityLID, phone number and username support
⚡ PerformanceBuilt for many sessions at once, low CPU and memory use
📦 DependenciesZero runtime dependencies, no native or WebRTC builds
📞 CallsAudio and video calls, playlists, multi-call
🧩 MessagesButtons, carousels, widgets, polls, events, payments, AI rich
🗄️ SessionsMulti-file, single-file, SQLite or MongoDB auth storage

Made by @DanuZz · Package: npmjs.com/package/@dnuzi/baileys

v0.1.2 Update: Optimized CPU & RAM consumption across all internal caches (adaptive sweep intervals, bounded cache limits, cache-first signal store mutex bypass, and proper timer cleanup). Full CommonJS require() support without ERR_REQUIRE_ASYNC_MODULE.

v0.1.1 Update: Fixed VoIP video call rendering on answered calls. Resolved an issue where answering video calls played audio but showed no video by properly packetizing H.264 NALUs (SPS, PPS, and fragmented IDR keyframes) per RFC 6184, and synchronized video SRTP sessions on call acceptance.

📥 Install

Requires Node.js 18+ and FFmpeg. Calls also need ffprobe available in PATH.

npm install @dnuzi/baileys
{
  "dependencies": {
    "@dnuzi/baileys": "v0.1.1"
  }
}

🚀 Quick Start

A complete bot that links by QR or pairing code and answers !ping.

import makeWASocket, {
  useMultiFileAuthState,
  DisconnectReason,
  extractMessageContent
} from '@dnuzi/baileys'

const PHONE_NUMBER = '' // e.g. '948123456789' to use a pairing code
const PAIR_CODE = ''    // optional custom 8-character code

const start = async () => {
  const { state, saveCreds } = await useMultiFileAuthState('./session')

  const sock = makeWASocket({
    auth: state,
    printQRInTerminal: !PHONE_NUMBER,
    syncFullHistory: false
  })

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

  let pairingRequested = false
  sock.ev.on('connection.update', async ({ connection, lastDisconnect, qr }) => {
    if (qr && PHONE_NUMBER && !pairingRequested) {
      pairingRequested = true
      try {
        console.log('Pairing code:', await sock.requestPairingCode(PHONE_NUMBER, PAIR_CODE || undefined))
      } catch (err) {
        console.error('Could not request a pairing code:', err.message)
      }
    }

    if (connection === 'open') console.log('Connected as', sock.user?.id)

    if (connection === 'close') {
      const code = lastDisconnect?.error?.output?.statusCode
      if (code === DisconnectReason.loggedOut) {
        return console.log('Logged out. Delete ./session and run again to link a new device.')
      }
      if (code === DisconnectReason.connectionReplaced) {
        return console.log('This session was opened somewhere else. Not reconnecting.')
      }
      console.log(`Connection closed (${code}), reconnecting...`)
      setTimeout(start, 1000)
    }
  })

  sock.ev.on('messages.upsert', async ({ messages, type }) => {
    if (type !== 'notify') return
    for (const m of messages) {
      if (m.key.fromMe || !m.message) continue
      const content = extractMessageContent(m.message)
      const text = content?.conversation || content?.extendedTextMessage?.text
      if (text === '!ping') {
        await sock.sendMessage(m.key.remoteJid, { text: 'pong' }, { quoted: m }).catch(console.error)
      }
    }
  })
}

start()

How to link

MethodSteps
QR codeLeave PHONE_NUMBER empty, then scan from Linked devices → Link a device
Pairing codeSet PHONE_NUMBER, choose Link with phone number instead, enter the code
Custom codeSet PAIR_CODE to any 8 characters (empty = random)

Your login is stored in ./session. Keep it private and out of git.

⚙️ Socket options

OptionDefaultWhat it does
auth–Required. The state from an auth helper
performanceMode'balanced'Performance preset: 'balanced' (safe defaults), 'high' (aggressive caching & lowest latency), or 'low-memory' (RAM-constrained environments)
dedupTTL30000 (ms)Deduplication window for incoming message stanzas
dedupMax2000Maximum messages tracked in deduplication buffer
groupMetadataCacheTTL300000 (ms)In-memory group metadata cache TTL
browserBrowsers.macOS('Chrome')Device name in Linked devices (also .ubuntu(), .windows(), .appropriate())
printQRInTerminalfalsePrint each QR in the terminal (qr is always emitted on connection.update)
qrTimeout60000, then 20000How long each QR stays valid
syncFullHistorytrueSet false for lighter bots
markOnlineOnConnecttrueSet false to keep notifications on your phone
connectTimeoutMs20000Opening handshake timeout
keepAliveIntervalMs30000Ping interval
defaultQueryTimeoutMs60000Server request timeout
getMessageundefinedReturn a stored message by key so retries and poll votes can be decrypted
disableStickyRoutingfalsetrue stops sending ED in the URL and the sticky_routing cookie
waWebSocketUrlwss://web.whatsapp.com/ws/chatCustom endpoint only
loggerbuilt-inAny pino-style logger

⚡ Performance Optimization & Latency Tuning

@dnuzi/baileys includes an internal performance engine engineered to minimize bot response latency and eliminate redundant network operations without breaking any existing Baileys APIs.

🚀 Performance Modes

You can select a performance mode when initializing your socket:

const sock = makeWASocket({
  auth: state,
  performanceMode: 'high' // 'balanced' | 'high' | 'low-memory'
})
ModeTargetDescription
'balanced' (default)General BotsSafe, backward-compatible defaults. In-memory group metadata caching (5 min), message deduplication (30s window), request coalescing, and safe event isolation.
'high'Ultra-fast Response BotsMaximum throughput and lowest latency. Extended caching (15 min group cache, 60s dedup), aggressive request coalescing, enlarged signal cache, and zero-overhead lazy logging.
'low-memory'VPS / Embedded (≤512MB RAM)Compact cache bounds (100 groups, 500 messages, 2 min TTL) with aggressive garbage collection.

🛠️ Core Optimization Architecture

  1. Fast Message Pipeline & Deduplication

    • Incoming message stanzas are checked against a high-speed, bounded circular cache (MessageDeduplicator) before expensive cryptographic decryption.
    • Duplicate message delivery stanzas are acknowledged and dropped instantly (~116,000+ msgs/sec throughput).
    • If decryption fails (e.g. key missing/resend needed), the entry is automatically forgotten so retries can proceed seamlessly.
  2. Intelligent Request Coalescing

    • Prevents duplicate concurrent lookups (e.g. 50 incoming messages requesting group metadata at the same millisecond).
    • Reuses the in-flight Promise so 50 concurrent requests trigger exactly 1 network query, reducing server round-trips by up to 98%.
  3. In-Memory Group Metadata & LID Caching

    • sock.groupMetadata(jid) caches results with automatic invalidation on group changes (subject, description, participants, settings).
    • Pre-populates the cache during initial sync (groupFetchAllParticipating) so your bot answers commands without cold-start delay.
    • Access the cache directly via sock.groupMetadataCache.
  4. Event Emitter Fault Isolation

    • Broken or crashing listener functions registered on sock.ev.on('messages.upsert', ...) are safely isolated.
    • One plugin/handler error will never crash other listeners or break the WhatsApp connection pipeline.
  5. Optimized Serialization & Sender Keys

    • In-memory sender key record caching avoids constant JSON serialization and file/database re-reading during rapid group chat traffic.
  6. Zero-Overhead Lazy Logging

    • Uses logger.isLevelEnabled(level) and lazy argument evaluation so debug information is never formatted when running in production.

📊 Real-World Benchmarks

Run the benchmark suite on your own machine:

npm run benchmark

Results on standard hardware (Node.js 20+):

  • Message Deduplicator: 115,000+ msgs/sec throughput
  • Request Coalescer: 50 concurrent callers → 1 network operation (100% deduplicated in 27ms)
  • Isolated Event Dispatch: 19,000+ events/sec with 100% fault-isolation
  • LRU-TTL Cache: 165,000+ operations/sec

🗄️ Session storage

All helpers return { state, saveCreds }, so you can swap them freely.

import {
  useMultiFileAuthState,
  useSingleFileAuthState,
  useSqliteAuthState,
  useMongoDBAuthState
} from '@dnuzi/baileys'

const multi  = await useMultiFileAuthState('./session')              // one file per key
const single = await useSingleFileAuthState('./session.json')        // one file, flushed in batches
const sqlite = await useSqliteAuthState({ dbPath: './session.db' })  // SQLite (file-based, zero setup)
const mongo  = await useMongoDBAuthState({ url: 'mongodb://localhost:27017' }) // MongoDB (cloud & Docker)

SQLite Auth Storage

Stores session credentials and Signal cryptographic keys in an SQLite database file. Built-in WAL (Write-Ahead Logging) mode ensures safe concurrent transactions with ultra-low memory overhead.

[!TIP] Zero extra dependencies required! Baileys includes a built-in lightweight pure-JS SQLite engine, or you can plug in better-sqlite3 for native C++ performance.

1. Basic setup (Built-in engine)

import makeWASocket, { useSqliteAuthState } from '@dnuzi/baileys'

const { state, saveCreds } = await useSqliteAuthState({
  dbPath: './session.db'
})

const sock = makeWASocket({
  auth: state,
  printQRInTerminal: true
})

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

2. Using with better-sqlite3 (Optional native driver)

import Database from 'better-sqlite3'
import makeWASocket, { useSqliteAuthState } from '@dnuzi/baileys'

const db = new Database('./session.db')

const { state, saveCreds } = await useSqliteAuthState({
  database: db
})

const sock = makeWASocket({
  auth: state
})

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

Options for useSqliteAuthState

OptionTypeDescription
dbPathstringFile path to the SQLite database (e.g. './session.db'). Uses the built-in pure-JS database engine
databaseobjectAn existing database instance (e.g. from better-sqlite3 or sqlite3)

MongoDB Auth Storage

Store session credentials and encryption keys directly in MongoDB (local or MongoDB Atlas). Ideal for cloud deployments (Railway, Render, Heroku, AWS, Docker) where local files are ephemeral, or for managing multiple accounts in a single database.

[!NOTE] When connecting via MongoDB URI (url / uri), ensure the mongodb package is installed in your project:

npm install mongodb

1. Connect via MongoDB URI

import makeWASocket, { useMongoDBAuthState } from '@dnuzi/baileys'

const { state, saveCreds } = await useMongoDBAuthState({
  url: 'mongodb+srv://user:[email protected]/?retryWrites=true&w=majority',
  dbName: 'whatsapp_bot',     // optional (default: 'baileys')
  collectionName: 'sessions', // optional (default: 'auth_state')
  sessionId: 'bot-1'          // optional: isolate multiple accounts in one collection
})

const sock = makeWASocket({
  auth: state,
  printQRInTerminal: true
})

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

2. Use an existing MongoDB collection or client

If your app already connects to MongoDB, pass the collection directly — no extra driver connection needed:

import { MongoClient } from 'mongodb'
import { useMongoDBAuthState } from '@dnuzi/baileys'

const client = new MongoClient('mongodb://localhost:27017')
await client.connect()

const collection = client.db('whatsapp_bot').collection('sessions')

// Pass collection directly (with optional sessionId as 2nd argument)
const { state, saveCreds } = await useMongoDBAuthState(collection, 'main-session')

3. Use with Mongoose

import mongoose from 'mongoose'
import { useMongoDBAuthState } from '@dnuzi/baileys'

await mongoose.connect('mongodb://localhost:27017/whatsapp_bot')

const collection = mongoose.connection.collection('sessions')
const { state, saveCreds } = await useMongoDBAuthState(collection)

Options for useMongoDBAuthState

OptionTypeDescription
url / uristringMongoDB connection string (e.g. mongodb://localhost:27017 or Atlas URI)
collectionCollectionExisting MongoDB collection or Mongoose model
db / databaseDbExisting MongoDB database instance (client.db(...))
clientMongoClientExisting MongoClient instance
dbNamestringDatabase name (default: 'baileys' or DB from connection string)
collectionNamestringCollection name (default: 'auth_state')
sessionIdstringSession ID to namespace credentials and keys in a shared collection
mongoOptionsobjectConnection options passed directly to MongoClient

🔌 Disconnect reasons

lastDisconnect.error.output.statusCode maps to DisconnectReason:

CodeReasonDo this
401loggedOutDelete the session and link again
440connectionReplacedDo not reconnect
515restartRequiredNormal after pairing, reconnect
428 / 408connectionClosed / connectionLost / timedOutNetwork drop, reconnect
500badSessionReconnect

When the server closes the socket, lastDisconnect.error.data includes wsCode and wsReason.

💬 Messages

Text, mentions, reactions, contacts

await sock.sendMessage(jid, { text: 'Hello there!' })

await sock.sendMessage(jid, {
  text: 'Hi @948123456789',
  mentions: ['[email protected]']
}, { quoted: m, ephemeralExpiration: 86400 })

await sock.sendMessage(jid, { react: { text: '👍', key: m.key } })

await sock.sendMessage(jid, {
  contacts: {
    contacts: [{
      displayName: 'DanuZz',
      vcard: 'BEGIN:VCARD\nVERSION:3.0\nFN:DanuZz\nTEL;type=CELL:+948123456789\nEND:VCARD'
    }]
  }
})

Media

await sock.sendMessage(jid, { image: { url: './photo.jpg' }, caption: 'Nice view' })
await sock.sendMessage(jid, { video: { url: './clip.mp4' }, caption: 'Clip', gifPlayback: false })
await sock.sendMessage(jid, { audio: { url: './voice.ogg' }, ptt: true })
await sock.sendMessage(jid, { document: { url: './file.pdf' }, mimetype: 'application/pdf', fileName: 'file.pdf' })
await sock.sendMessage(jid, { sticker: { url: './sticker.webp' } })

Wrappers and albums

await sock.sendMessage(jid, { image: { url: './photo.jpg' }, viewOnce: true })                       // view once
await sock.sendMessage(jid, { video: { url: './clip.mp4' }, ptv: true })                             // video note
await sock.sendMessage(jid, { image: { url: './surprise.jpg' }, caption: 'Peekaboo', spoiler: true }) // spoiler

await sock.sendMessage(jid, {
  album: [
    { image: { url: './1.jpg' } },
    { image: { url: './2.jpg' } },
    { video: { url: './clip.mp4' } }
  ]
})

Stickers & sticker packs

Single sticker

await sock.sendMessage(jid, { sticker: { url: './sticker.webp' } })

📦 Sticker Pack

[!IMPORTANT] If sharp or @napi-rs/image is not installed, the cover and stickers must already be in WebP format.

await sock.sendMessage(jid, {
  cover: {
    url: './path/to/image.webp'
  },
  stickers: [
    {
      data: {
        url: './path/to/image.webp'
      }
    },
    {
      data: {
        url: './path/to/image.webp'
      }
    },
    {
      data: {
        url: './path/to/image.webp'
      }
    }
  ],
  name: '📦 My Sticker Pack',
  publisher: 'DanuZz',
  description: 'Optional pack description'
})

Sticker pack options

PropertyTypeDescription
cover{ url: string } | BufferRequired. The pack tray icon / cover image
stickersArray<{ data: { url } | Buffer, emojis?: string[] }>Required. List of stickers (up to 60 stickers)
namestringName of the sticker pack (e.g. '📦 My Sticker Pack')
publisherstringAuthor / publisher name (e.g. 'DanuZz')
descriptionstringOptional description of the pack

Location & events

await sock.sendMessage(jid, {
  location: { degreesLatitude: -6.2088, degreesLongitude: 106.8456, name: 'Monas' }
})

await sock.sendMessage(jid, {
  event: {
    name: 'Team Sync',
    description: 'Discuss the Q1 roadmap',
    startDate: new Date(Date.now() + 3600_000)
  }
})

Polls

await sock.sendMessage(jid, {
  poll: { name: 'Favorite language?', values: ['JavaScript', 'Python', 'Rust'], selectableCount: 1 }
})

// Quiz polls: newsletters only, require correctAnswer
await sock.sendMessage(newsletterJid, {
  poll: { name: 'Capital of Japan?', values: ['Tokyo', 'Osaka'], pollType: 1, correctAnswer: 'Tokyo' }
})

// Final tally of a poll you created
await sock.sendMessage(jid, {
  pollResult: {
    name: 'Favorite language?',
    votes: [{ name: 'JavaScript', voteCount: 12 }, { name: 'Python', voteCount: 9 }]
  }
})

Edit, delete, pin, keep

ActionCode
Editsock.sendMessage(jid, { text: 'Updated text', edit: m.key })
Deletesock.sendMessage(jid, { delete: m.key })
Pin / unpinsock.sendMessage(jid, { pin: m.key, type: 1, time: 86400 }) (type: 0 unpins)
Keep / removesock.sendMessage(jid, { keep: m.key, type: 1 }) (type: 0 removes)
Disappearing (group)sock.sendMessage(groupJid, { disappearingMessagesInChat: 86400 }) (true = 7 days, false = off)

Status updates

// jid can be an array to choose who sees the status
await sock.sendMessage([contactJid1, contactJid2], { text: 'Status update text' })

🎛️ Interactive Messages

Native flow buttons

await sock.sendMessage(jid, {
  text: 'Choose one:',
  footer: 'DanuZz',
  nativeFlow: [
    { text: '👋🏻 Yes', id: 'yes_1' },
    { text: '📞 Call', call: '948123456789' },
    { text: '📋 Copy', copy: 'PROMO123' },
    { text: '🌐 Source', url: 'https://example.com' },
    {
      text: '📋 Select',
      sections: [{ title: '✨ Section 1', rows: [{ title: '🏷️ Coupon', id: 'coupon_code' }] }]
    }
  ]
})

With a media header, use caption instead of text (works with image, video, document, location, product):

await sock.sendMessage(jid, {
  image: { url: './promo.jpg' },
  title: 'Flash Sale',
  caption: 'Up to 50% off today only',
  footer: 'DanuZz',
  nativeFlow: [
    { text: 'Shop Now', url: 'https://shop.example.com' },
    { text: 'Copy Code', copy: 'SALE50' }
  ]
})

Card headers can be an image, a video or a product.

await sock.sendMessage(jid, {
  text: 'Check out our new arrivals:',
  footer: 'DanuZz',
  cards: [
    { image: { url: './item-a.jpg' }, title: 'Item A', caption: 'New in stock', nativeFlow: [{ text: 'View', id: 'view_a' }] },
    { video: { url: './item-b.mp4' }, title: 'Item B', caption: 'Limited edition', nativeFlow: [{ text: 'View', id: 'view_b' }] },
    {
      businessOwnerJid: '[email protected]',
      product: { title: 'Wireless Mouse', productImage: { url: './mouse.png' } },
      title: 'Wireless Mouse',
      caption: '$19.99',
      nativeFlow: [{ text: 'Buy Now', id: 'buy_mouse' }]
    }
  ]
})

Widgets (A2UI)

Combine with buttons, or pass nativeFlow: [] for none.

await sock.sendMessage(jid, {
  text: 'Widget demo',
  footer: 'A2UI Showcase',
  nativeFlow: [{ text: '🌐 Source', url: 'https://example.com' }],
  widget: {
    align: 'center',
    fallback: 'Widget cannot be loaded on this device', // optional
    items: [
      { text: 'Welcome to the Widget Demo', variant: 'title' }, // title | body | caption
      { text: 'This is a longer description.', variant: 'body' },

      { icon: 'info' },
      { icon: 'warning', style: 'symbol' },
      { icon: 'favorite', native: true },

      { image: 'https://example.com/banner.jpg', variant: 'header', fit: 'cover', description: 'Promo banner' },
      { video: 'https://example.com/preview.mp4' },
      { audio: 'https://example.com/audio.mp3', description: 'Listen to this audio' },

      { divider: 'horizontal' }, // or 'vertical'
      { button: 'Open Website', url: 'https://example.com', variant: 'primary' },

      { input: 'name', label: 'Enter your name', value: '', variant: 'shortText' }, // or 'longText'
      { input: 'email', label: 'Email', validationRegexp: '^[^@]+@[^@]+\\.[^@]+$' },
      { checkbox: 'I agree to the terms & conditions', value: false },
      { choice: ['Red', 'Green', 'Blue'], label: 'Favorite color', variant: 'mutuallyExclusive', displayStyle: 'chips', filterable: true },
      { choice: [{ label: 'Option A', value: 'a' }, { label: 'Option B', value: 'b' }], label: 'Multi-select', variant: 'multipleSelection', value: ['a'] },
      { slider: 100, min: 0, value: 50, label: 'Volume' }, // slider = max value
      { datetime: true, label: 'Pick a date', enableDate: true, enableTime: false },

      { row: [{ text: 'Left' }, { text: 'Center' }, { text: 'Right' }], justify: 'space-between', align: 'center' },
      { column: [{ text: 'Row 1' }, { text: 'Row 2' }], justify: 'start', align: 'stretch' },
      { list: [{ text: '• First item' }, { text: '• Second item' }], direction: 'vertical' }
    ]
  }
})

Legacy template buttons

Old format. Prefer nativeFlow.

await sock.sendMessage(jid, {
  text: 'Choose one:',
  footer: 'DanuZz',
  templateButtons: [
    { text: 'Reply', id: 'r1' },
    { text: 'Visit', url: 'https://example.com' },
    { text: 'Call', call: '+948123456789' }
  ]
})

Simulated replies

Send a reply as if a user tapped a button or list row.

await sock.sendMessage(jid, { buttonReply: { id: 'yes_1', displayText: 'Yes' }, type: 'plain' })
await sock.sendMessage(jid, { listReply: { id: 'row1', title: 'Row 1', description: 'desc' } })
await sock.sendMessage(jid, { flowReply: { name: 'single_select', paramsJson: JSON.stringify({ id: 'row1' }) } })

🤖 AI Rich Messages

Build structured rich responses with a chainable builder.

await sock.aiRich()
  .setTitle('Ai Rich Message')
  .addText('[HyperLink](https://example.com)\nCitation [](https://example.com)')
  .addImage('https://example.com/image.png')
  .addCode('javascript', `console.log('Hello World')`)
  .addHtml(['<html>Hello world</html>', 'Tab 1'], ['<html>Hi twin</html>', 'Tab 2'])
  .addTable([
    ['Name', 'DanuZz'],
    ['Bio', 'Im developer'],
    ['Age', '67']
  ])
  .addSource([['https://example.com/favicon.ico', 'https://example.com', 'Source']])
  .addTip('Tip Text')
  .addSuggest(['Continue', 'Cancel'])
  .send(jid, { quoted: m })
// Animated progress indicator
await sock.aiRich().addProcess('Loading...').send(jid)

// Shorthand without the builder
await sock.sendMessage(jid, {
  aiRich: { title: 'Assistant', text: 'Here is what I found:', table: [['Name', 'Score'], ['Alice', '90']] }
})

Bypass download is on by default for both send() and sendMessage(jid, { aiRich }). The message edits itself right after sending so the client renders it without downloading first. Pass bypassDownload: false to turn it off. If the edit fails, the thrown error has error.relayedKey set to the key of the message that was already sent.

Builder method reference
MethodPurpose
setTitle(title)Disclaimer label shown on the message
setFooter(footer)Trailing metadata text block
setContextInfo(obj)Merge extra fields into contextInfo
addText(text, opts?)Markdown text. Extracts [text](url) links, [](url) citations, [text|w|h](<url>) LaTeX
addCode(language, code)Syntax-highlighted code block
addTable(rows, opts?)[[header...], [row...], ...] array of strings
addImage(image)string | Buffer | array, shown as a grid
addVideo(video)string | Buffer | { url, mimeType?, duration? } | array
addSource(sources)[icon, url, text][] source cards
addProduct(data)Product card, or an array for a carousel
addPost(data)Social-post card, or an array for a carousel
addReels(data)Reel card, or an array
addTip(text)Small tip line
addSuggest(suggestion, opts?)Follow-up suggestion pill
addProcess(title)In-progress status indicator
addHtml(...)One inline HTML block, or [html, title] tab pairs
build(opts?)Build the raw content without sending
buildEdit(jid, key, message)Build the edit payload used by bypass download
send(jid, opts?)Send it. opts.bypassDownload (default true) sends the follow-up edit, other options go to sendMessage

🛍️ Commerce & Payments

// Product message
await sock.sendMessage(jid, {
  businessOwnerJid: '[email protected]',
  image: { url: './mouse.png' },
  product: {
    title: 'Wireless Mouse',
    description: 'Ergonomic, 2.4GHz',
    currencyCode: 'USD',
    priceAmount1000: 19990, // $19.99
    retailerId: 'sku-001'
  }
})

// Payments
await sock.sendMessage(jid, { requestPaymentFrom: recipientJid, text: 'Payment for order #123' })
await sock.sendMessage(jid, { paymentInviteServiceType: 1 })
await sock.sendMessage(jid, { orderText: 'Your order summary', thumbnail: fs.readFileSync('./order-thumb.jpg') })
await sock.sendMessage(jid, { document: { url: './invoice.pdf' }, mimetype: 'application/pdf', invoiceNote: 'Invoice #1024' })

Extras

// Sponsored ad card on any message
await sock.sendMessage(jid, {
  text: 'Check this deal!',
  externalAdReply: {
    title: 'Big Sale',
    body: '50% off',
    thumbnail: fs.readFileSync('./thumb.jpg'),
    mediaType: 1,
    url: 'https://example.com'
  }
})

// Phone number requests
await sock.sendMessage(jid, { requestPhoneNumber: true })
await sock.sendMessage(jid, { sharePhoneNumber: true })

// Group invite card
await sock.sendMessage(jid, {
  groupInvite: { jid: groupJid, inviteCode: code, inviteExpiration: Date.now() + 3600_000, subject: 'My Group', text: 'Join us!' }
})

// Disable forwarding
await sock.sendMessage(jid, { limitSharing: true })

📞 Calls

Calls need ffmpeg and ffprobe in PATH. Create one Voip per socket after it opens. After a reconnect you get a new socket, so create a new Voip.

Start a call

import Voip from '@dnuzi/baileys/voip'

const voip = new Voip(sock, {
  ffprobePath: 'ffprobe',
  voipLogLevel: 'warn',   // trace | debug | info | warn | error
  tmpDir: './tmp',        // temp folder for downloads
  maxConcurrentCalls: 1
})

const call = await voip.call('948123456789', './song.mp3')
await call.hangup() // or call.end()

await voip.call('948123456789') // ring only, no media

Media, video & playlists

await voip.call(jid, './song.mp3')                    // local file
await voip.call(jid, 'https://example.com/song.mp3')  // URL, downloaded first
await voip.call(jid, { audio: './voice.ogg' })
await voip.call(jid, './clip.mp4', '720p')            // video call

await voip.call(
  jid,
  ['./intro.mp3', './clip.mp4', 'https://example.com/outro.mp3'],
  '480p',
  {
    loop: false,          // repeat the playlist
    autoEndCall: true,    // hang up when the playlist ends
    autoDowngrade: true   // drop to audio when the peer turns every camera off
  }
)
ItemSupported
Audio formatsmp3 ogg opus wav m4a aac flac weba
Video formatsmp4 mov webm mkv avi m4v 3gp
Download limitsAudio 20 MB, video 50 MB
Resolution'240p' '360p' '480p' '720p' '1080p' or { width, height, frameRate } (default 480p, portrait is rotated automatically)
jidA number or a JID, non-digits are stripped

In-call controls

await call.silent(true)       // silence audio (false = resume, no arg = toggle)
console.log(call.isSilenced)

call.mute(true)               // mic only
call.raiseHand(true)
call.react('👍')              // returns false if not connected yet
await call.shareScreen(true)  // needs video, 1:1 only
await call.shareScreen(false)

const result = await call.upgradeToVideo()
// 'accepted' | 'rejected' | 'rejected_by_timeout' | 'error' | 'timeout' | 'cancelled'

shareScreen only tells the peer a screen is being shared. The picture is whatever you feed to voip.coordinator.feedLiveVideo().

Call events

call.on('ringing', () => {})
call.on('connected', () => {})
call.on('ended', reason => {})          // 'connection_closed' if the socket drops
call.on('error', err => {})

call.on('item', ({ index, kind, source }) => {})
call.on('playlist_looped', () => {})
call.on('playlist_ended', () => {})
call.on('downgraded', () => {})         // video dropped to audio
call.on('silent', state => {})

call.on('peer_mute', muted => {})
call.on('hand_raise', ({ jid, raised }) => {})
call.on('reaction', reaction => {})
call.on('screen_share', share => {})
call.on('peer_video', change => {})
call.on('inbound_audio', pcm => {})     // Float32Array, 16 kHz mono, 60 ms
call.on('inbound_video', frame => {})   // decoded H.264 frame
call.on('audio_finished', () => {})     // current audio item reached its end

Incoming calls

await voip.listen() // register call handlers, safe to call twice

voip.on('call_incoming', async call => {
  console.log('incoming from', call.peerJid, call.callId)
  await voip.acceptCall(call.callId)
})

voip.on('call_inbound_audio', ({ call, pcm }) => {})
voip.on('call_ended', call => console.log('ended', call.callId))

await voip.rejectCall(callId)
await voip.hangup(callId)

Available voip events: call_incoming, call_state, call_ended, call_peer_mute, call_inbound_audio, call_inbound_video, call_hand_raise, call_reaction, call_screen_share, call_peer_video_state, call_outbound_audio_finished, call_error.

voip.coordinator is the raw engine (feedLiveAudio, feedLiveVideo, setExternalAudioMode, ...).

Multi-call

const voip = new Voip(sock, { maxConcurrentCalls: 3 })

const a = await voip.call('948111111111', './song.mp3')
const b = await voip.call('948222222222', './song.mp3')

// Many numbers at once
const results = await voip.callMany(
  ['948111111111', '948222222222', '948333333333'],
  './song.mp3'
)

for (const { jid, call, error } of results) {
  if (error) { console.log(jid, 'failed:', error.message); continue }
  call.on('ended', reason => console.log(jid, 'ended:', reason))
  call.on('error', err => console.error(jid, err))
}

voip.calls                  // active calls
voip.calls[0].callId
voip.calls[0].target        // dialed number
await voip.hangup(callId)   // end one call
await voip.end()            // end all calls
await voip.end(true)        // only release the slots
  • maxConcurrentCalls defaults to 1. Each call gets its own relay, codec and audio.
  • Incoming calls count toward the limit.
  • Going over the limit throws Maximum concurrent calls reached. In callMany() it is returned as error for that number and the rest still go through.
  • callMany() accepts the same media and options as call() and dials duplicate numbers once.
  • A safety timeout emits error if a call never ends (starts at 105 s).

🙏 Credits

This library is built on the original Baileys by @WhiskeySockets.

Thanks to the Baileys maintainers and contributors: purpshell · jlucaso1 · adiwajshing

Protocol Buffer definitions come from wa-proto by WPP Connect.

Additional code and modifications by DanuZz.

📄 License

MIT © DanuZz. See LICENSE.

📞 Contact

MovaNest · @DanuZz

Built with ❤️ for the WhatsApp dev community.

版本与发布记录

@dnuzi/baileys 共发布 13 个版本,首次发布于 2026/04/10;过去 12 个月发布了 13 个版本。

版本发布日期距今
0.1.2latest2026/10/2618 天前
0.1.12026/10/2420 天前
0.1.02026/10/2321 天前
0.1.0-turboturbo2026/10/2221 天前
0.0.92026/10/2123 天前
0.0.82026/10/2123 天前

关于 @dnuzi/baileys 的常见问题

@dnuzi/baileys 是什么?

WhatsApp Web API Library Modification By DanuZz.

如何安装 @dnuzi/baileys?

在项目目录运行 npm install @dnuzi/baileys,或使用 pnpm add / yarn add / bun add。

@dnuzi/baileys 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/dnuzi__baileys 是否存在。

@dnuzi/baileys 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@dnuzi/baileys 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@dnuzi/baileys 可以商用吗?

@dnuzi/baileys 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@dnuzi/baileys 的下载量有多少?

根据 npm 官方下载统计,@dnuzi/baileys 在 2026/10/13 – 2026/11/11 这 30 天内被下载 2,796 次,日均约 93 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@dnuzi/baileys 还在维护吗?

最新版本 0.1.2 发布于 2026/10/26(18 天前);至今共发布 13 个版本,其中过去 12 个月发布了 13 个;首次发布于 2026/04/10。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。