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

@yemo-dev/yebail

v4.2.12

Published

WhatsApp Web API Library

Readme

@yemo-dev/yebail

npm version npm downloads license

@yemo-dev/yebail is a fast, stable, and modern interactive-feature-focused WhatsApp Web API library built on WebSocket.

This project is not affiliated with WhatsApp. Use it responsibly.

Install

npm install @yemo-dev/yebail

Optional Dependencies

Install only what you need:

# Image processing (one of):
npm install jimp        # ^0.16 || ^0.22 || ^1.x
npm install sharp       # ^0.34 (recommended, faster)

# Audio metadata (ESM-only, loaded via dynamic import)
npm install music-metadata  # ^11

# Audio waveform (ESM-only, loaded via dynamic import)
npm install audio-decode    # ^2 || ^3

# Link preview
npm install link-preview-js # ^3 || ^4

# QR code in terminal
npm install qrcode-terminal # ^0.12

# SQLite auth store
npm install better-sqlite3  # ^11 || ^12

Import

const {
  default: makeWASocket,
  useMultiFileAuthState,
  DisconnectReason,
  fetchLatestWaWebVersion,
  makeInMemoryStore,
  Browsers,
  getContentType,
  downloadMediaMessage,
  getAggregateVotesInPollMessage
} = require('@yemo-dev/yebail')

Index


Connecting Account

QR Code

const { default: makeWASocket, Browsers } = require('@yemo-dev/yebail')

const sock = makeWASocket({
  browser: Browsers.windows('Yebail'),
  printQRInTerminal: true
})

Pairing Code

const sock = makeWASocket({ printQRInTerminal: false })

if (!sock.authState.creds.registered) {
  const code = await sock.requestPairingCode('628xxxxxxxxxx')
  console.log('Pairing code:', code)
}

Full History Sync

const sock = makeWASocket({
  browser: Browsers.windows('Desktop'),
  syncFullHistory: true
})

Auto Browser Detection

const sock = makeWASocket({
  browser: Browsers.appropriate('Yebail')
})

Auto-Reconnect

const { Boom } = require('@hapi/boom')

sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
  if (connection === 'close') {
    const shouldReconnect = (lastDisconnect?.error instanceof Boom)
      ? lastDisconnect.error.output.statusCode !== DisconnectReason.loggedOut
      : true
    if (shouldReconnect) connectToWhatsApp()
  }
})

Socket Config

const NodeCache = require('@cacheable/node-cache')
const groupCache = new NodeCache({ stdTTL: 5 * 60, useClones: false })

const sock = makeWASocket({
  auth: state,
  browser: Browsers.windows('Yebail'),
  countryCode: 'US', // ISO 3166-1 alpha-2 (auto MCC fallback when mcc is not set)
  // mcc: '310', // optional explicit MCC override
  printQRInTerminal: true,
  syncFullHistory: false,
  markOnlineOnConnect: false,
  generateMessageID: () => require('crypto').randomBytes(16).toString('hex').toUpperCase(),
  cachedGroupMetadata: async (jid) => groupCache.get(jid),
  getMessage: async (key) => {
    const msg = await store.loadMessage(key.remoteJid, key.id)
    return msg?.message || undefined
  },
  linkPreviewImageThumbnailWidth: 192,
  generateHighQualityLinkPreview: true,
  enableRecentMessageCache: true,
  maxMsgRetryCount: 5,
  logger: require('pino')({ level: 'silent' })
})

sock.ev.on('groups.update', async ([event]) => {
  const metadata = await sock.groupMetadata(event.id)
  groupCache.set(event.id, metadata)
})
sock.ev.on('group-participants.update', async (event) => {
  const metadata = await sock.groupMetadata(event.id)
  groupCache.set(event.id, metadata)
})

countryCode now automatically resolves the user-agent MCC from the built-in phone-number MCC table when mcc is not explicitly provided.
If you need a specific carrier/region MCC, set mcc manually. If both countryCode and mcc are omitted, the fallback MCC defaults to 000 (with default country behavior using US internally).


Save Auth Info

const { useMultiFileAuthState } = require('@yemo-dev/yebail')

async function connectToWhatsApp() {
  const { state, saveCreds } = await useMultiFileAuthState('auth_info')
  const { version, isLatest } = await fetchLatestWaWebVersion()

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

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

Handling Events

sock.ev.on('connection.update', ({ connection, lastDisconnect, qr, isOnline }) => {
  console.log('Connection:', connection, '| Online:', isOnline)
})

sock.ev.on('messages.upsert', async ({ messages, type }) => {
  if (type !== 'notify') return
  for (const msg of messages) {
    console.log('New message from', msg.key.remoteJid)
  }
})

sock.ev.on('messages.update', (updates) => {
  for (const { key, update } of updates) {
    if (update.status) console.log('Status:', update.status)
  }
})

sock.ev.on('messages.delete', (item) => console.log('Deleted:', item))

sock.ev.on('message.reaction', ({ key, reaction }) => {
  console.log('Reaction', reaction.text, 'on', key.id)
})

sock.ev.on('chats.upsert', (chats) => console.log('Upsert', chats.length, 'chats'))
sock.ev.on('chats.update', (updates) => console.log('Chat updates:', updates))
sock.ev.on('chats.delete', (ids) => console.log('Chats deleted:', ids))

sock.ev.on('groups.update', (updates) => {
  for (const u of updates) console.log('Group updated:', u.id, u.subject)
})
sock.ev.on('group-participants.update', ({ id, participants, action }) => {
  console.log(action, 'in', id, ':', participants)
})

sock.ev.on('contacts.upsert', (contacts) => {
  for (const c of contacts) console.log('Contact:', c.id, c.notify)
})

sock.ev.on('presence.update', ({ id, presences }) => {
  for (const [participant, presence] of Object.entries(presences)) {
    console.log(participant, 'is', presence.lastKnownPresence)
  }
})

sock.ev.on('call', (calls) => {
  for (const call of calls) console.log('Call from', call.from, 'status:', call.status)
})

Decrypt Poll Votes

const { getAggregateVotesInPollMessage } = require('@yemo-dev/yebail')

sock.ev.on('messages.update', async (event) => {
  for (const { key, update } of event) {
    if (update.pollUpdates) {
      const pollCreation = await store.loadMessage(key.remoteJid, key.id)
      if (pollCreation) {
        const votes = getAggregateVotesInPollMessage({
          message: pollCreation.message,
          pollUpdates: update.pollUpdates
        })
        console.log('Poll results:', votes)
      }
    }
  }
})

Data Store

const { makeInMemoryStore } = require('@yemo-dev/yebail')

const store = makeInMemoryStore({})
store.readFromFile('./baileys_store.json')
setInterval(() => store.writeToFile('./baileys_store.json'), 10_000)

store.bind(sock.ev)

const msg = await store.loadMessage('[email protected]', 'MESSAGE_ID')
const chats = store.chats.all()

WhatsApp IDs

User JID   : [country][number]@s.whatsapp.net
Group JID  : [creator]-[timestamp]@g.us
Community  : [id]@g.us
Newsletter : [id]@newsletter
LID        : Modern identity-based identifier
const {
  jidDecode,
  jidNormalizedUser,
  jidEncode,
  isJidGroup,
  isJidNewsletter,
  isJidUser,
  areJidsSameUser
} = require('@yemo-dev/yebail')

const { user, server, device } = jidDecode('[email protected]')
const normalized = jidNormalizedUser('628xxx:[email protected]')
const jid = jidEncode('628xxx', 's.whatsapp.net')

isJidGroup('[email protected]')
isJidNewsletter('xxx@newsletter')
isJidUser('[email protected]')
areJidsSameUser('[email protected]', '628xxx:[email protected]')

Utility Functions

const {
  getContentType,
  downloadMediaMessage,
  generateMessageID,
  normalizeMessageContent,
  extractMessageContent
} = require('@yemo-dev/yebail')

const type = getContentType(msg.message)
const buffer = await downloadMediaMessage(msg, 'buffer', {})
const stream = await downloadMediaMessage(msg, 'stream', {})
const id = generateMessageID()
const content = normalizeMessageContent(msg.message)

Account Restriction Check

const restriction = await sock.checkAccountRestriction()
console.log(restriction.isRestricted, restriction.reachoutTimelock, restriction.messageCap)

Audio Transcoding

await sock.sendMessage(jid, {
  audio: { url: 'https://example.com/voice.mp3' },
  mimetype: 'audio/ogg; codecs=opus',
  ptt: true
}, {
  transcodeAudio: true,
  audioBitrate: '64k'
})

Sending Messages

Text

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

await sock.sendMessage(jid, {
  text: '*bold* _italic_ ~strikethrough~ ```monospace```'
})

await sock.sendMessage(jid, {
  text: 'Check https://github.com/yemo-dev/baileys'
})

await sock.sendMessage(jid, { text: 'No preview', linkPreview: null })

Quote / Reply

await sock.sendMessage(jid, { text: 'Reply!' }, { quoted: msg })

Mention Users

await sock.sendMessage(jid, {
  text: 'Hello @628111111111 and @628222222222!',
  mentions: ['[email protected]', '[email protected]']
})

Image

await sock.sendMessage(jid, {
  image: { url: 'https://example.com/photo.jpg' },
  caption: 'Caption'
})

const fs = require('fs')
await sock.sendMessage(jid, {
  image: fs.readFileSync('./photo.jpg'),
  caption: 'From file'
})

await sock.sendMessage(jid, {
  image: Buffer.from('<base64_string>', 'base64'),
  caption: 'Base64'
})

Video

await sock.sendMessage(jid, {
  video: { url: 'https://example.com/video.mp4' },
  caption: 'Video'
})

await sock.sendMessage(jid, {
  video: { url: 'https://example.com/animation.mp4' },
  gifPlayback: true
})

Audio / PTT

await sock.sendMessage(jid, {
  audio: { url: 'https://example.com/audio.mp3' },
  mimetype: 'audio/mp4'
})

await sock.sendMessage(jid, {
  audio: { url: 'https://example.com/voice.ogg' },
  mimetype: 'audio/ogg; codecs=opus',
  ptt: true
})

PTV

await sock.sendMessage(jid, {
  video: { url: 'https://example.com/clip.mp4' },
  ptv: true
})

Document

await sock.sendMessage(jid, {
  document: { url: 'https://example.com/file.pdf' },
  mimetype: 'application/pdf',
  fileName: 'report.pdf',
  caption: 'Monthly report'
})

Sticker

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

await sock.sendMessage(jid, {
  sticker: fs.readFileSync('./sticker.tgs'),
  isLottie: true
})

Sticker Pack

// option 1
await sock.sendMessage(jid, {
  stickerPack: {
    stickerPackId: 'your-pack-id',
    name: 'My Sticker Pack',
    publisher: 'My Brand',
    stickers: [
      { stickerId: 'sticker-1', fileName: 'sticker1.webp', emoticon: '🔥' },
      { stickerId: 'sticker-2', fileName: 'sticker2.webp', emoticon: '✨' }
    ],
    packDescription: 'Sample sticker pack'
  }
})

// option 2 (alias)
await sock.sendMessage(jid, {
  stickerPackMessage: {
    stickerPackId: 'your-pack-id',
    name: 'My Sticker Pack',
    publisher: 'My Brand',
    stickers: [
      { stickerId: 'sticker-1', fileName: 'sticker1.webp', emoticon: '🔥' }
    ],
    packDescription: 'Sample sticker pack'
  }
})

Note: stickerPack and stickerPackMessage are aliases. Use only one in a single message.

Contact Card

await sock.sendMessage(jid, {
  contacts: {
    displayName: 'John Doe',
    contacts: [
      {
        vcard: `BEGIN:VCARD
VERSION:3.0
FN:John Doe
TEL;type=CELL;type=VOICE;waid=628111111111:+62 811-1111-1111
END:VCARD`
      }
    ]
  }
})

await sock.sendMessage(jid, {
  contacts: {
    displayName: '2 Contacts',
    contacts: [
      { vcard: 'BEGIN:VCARD\nVERSION:3.0\nFN:Alice\nTEL;waid=628111111111:+62811\nEND:VCARD' },
      { vcard: 'BEGIN:VCARD\nVERSION:3.0\nFN:Bob\nTEL;waid=628222222222:+62822\nEND:VCARD' }
    ]
  }
})

Location

await sock.sendMessage(jid, {
  location: {
    degreesLatitude: -6.2088,
    degreesLongitude: 106.8456,
    name: 'Jakarta, Indonesia',
    address: 'DKI Jakarta, Indonesia'
  }
})

Live Location

await sock.sendMessage(jid, {
  liveLocation: {
    degreesLatitude: -6.2088,
    degreesLongitude: 106.8456,
    accuracyInMeters: 10,
    speedInMps: 0,
    degreesClockwiseFromMagneticNorth: 0,
    sequenceNumber: BigInt(Date.now()),
    timeSinceLastUpdate: 0
  },
  caption: 'Live location'
})

Poll

await sock.sendMessage(jid, {
  poll: {
    name: 'Favorite color?',
    values: ['Red', 'Green', 'Blue'],
    selectableCount: 1
  }
})

await sock.sendMessage(jid, {
  poll: {
    name: 'Select hobbies:',
    values: ['Gaming', 'Reading', 'Coding'],
    selectableCount: 0
  }
})

Reaction

await sock.sendMessage(jid, { react: { text: 'ok', key: msg.key } })

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

List Message

await sock.sendMessage(jid, {
  title: 'Order Menu',
  text: 'Please select from the options below:',
  footer: 'Powered by Yebail',
  buttonText: 'Open Menu',
  sections: [
    {
      title: 'Main Course',
      rows: [
        { title: 'Pizza', description: 'Classic tomato', rowId: 'pizza'  },
        { title: 'Burger', description: 'Double beef',   rowId: 'burger' }
      ]
    },
    {
      title: 'Drinks',
      rows: [
        { title: 'Cola',   description: '500ml',          rowId: 'cola'  },
        { title: 'Juice',  description: 'Fresh squeezed', rowId: 'juice' }
      ]
    }
  ]
})

await sock.sendMessage(jid, {
  listMessage: {
    title: 'Order Menu',
    description: 'Please select from the options below:',
    footerText: 'Powered by Yebail',
    buttonText: 'Open Menu',
    listType: 1,
    sections: [
      {
        title: 'Food',
        rows: [
          { title: 'Fried Rice', description: 'Tasty', rowId: 'fried_rice' }
        ]
      }
    ]
  }
})

Buttons Message

await sock.sendMessage(jid, {
  text: 'What would you like to do?',
  footer: 'Yebail Bot',
  buttons: [
    { buttonId: 'id1', buttonText: { displayText: 'View Menu'   } },
    { buttonId: 'id2', buttonText: { displayText: 'Place Order' } },
    { buttonId: 'id3', buttonText: { displayText: 'Help'        } }
  ]
})

await sock.sendMessage(jid, {
  image: { url: 'https://example.com/banner.jpg' },
  caption: 'Choose an option:',
  footer: 'Yebail',
  buttons: [
    { buttonId: 'yes', buttonText: { displayText: 'Yes' } },
    { buttonId: 'no',  buttonText: { displayText: 'No'  } }
  ]
})

// gifted-style shortcuts are also supported
await sock.sendMessage(jid, {
  text: 'Choose one',
  buttons: [
    { id: 'a', text: 'Option A' },
    { id: 'b', displayText: 'Option B' },
    { buttonId: 'c', buttonText: 'Option C' }
  ]
})

// mixed: quick_reply + native flow (type 4 + nativeFlowInfo)
await sock.sendMessage(jid, {
  text: 'Hello World!',
  footer: '© Yebail Dev',
  buttons: [
    {
      buttonId: 'ping',
      buttonText: { displayText: 'Ping Bot' },
      type: 1
    },
    {
      buttonId: 'select',
      buttonText: { displayText: 'Open Menu' },
      type: 4,
      nativeFlowInfo: {
        name: 'single_select',
        paramsJson: JSON.stringify({
          title: 'Choose',
          sections: [
            {
              title: 'Options',
              highlight_label: '🔥',
              rows: [
                { header: 'A', title: 'Option A', description: 'First',  id: 'opt_a' },
                { header: 'B', title: 'Option B', description: 'Second', id: 'opt_b' }
              ]
            }
          ]
        })
      }
    }
  ],
  viewOnce: true
}, { quoted: msg })

// buttons array also accepts already-normalized native flow objects
await sock.sendMessage(jid, {
  text: 'Actions',
  footer: 'Yebail',
  buttons: [
    { name: 'quick_reply',   buttonParamsJson: JSON.stringify({ display_text: 'Yes', id: 'yes' }) },
    { name: 'cta_url',       buttonParamsJson: JSON.stringify({ display_text: 'Open', url: 'https://github.com/yemo-dev/baileys', merchant_url: 'https://github.com/yemo-dev/baileys' }) },
    { name: 'cta_copy',      buttonParamsJson: JSON.stringify({ display_text: 'Copy Code', id: 'code', copy_code: 'YEBAIL' }) }
  ]
})

await sock.sendMessage(jid, {
  buttonsMessage: {
    contentText: 'Legacy buttons message',
    footerText: 'Yebail Legacy',
    buttons: [
      { buttonId: 'legacy_1', buttonText: { displayText: 'Legacy 1' }, type: 1 },
      { buttonId: 'legacy_2', buttonText: { displayText: 'Legacy 2' }, type: 1 }
    ],
    headerType: 1
  }
})

Interactive Message

await sock.sendMessage(jid, {
  interactiveMessage: {
    header: { title: 'Quick Question', hasMediaAttachment: false },
    body:   { text: 'Are you enjoying yebail?' },
    footer: { text: 'yebail' },
    nativeFlowMessage: {
      buttons: [
        { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Yes!',    id: 'yes'   }) },
        { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Not yet', id: 'no'    }) },
        { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Maybe',   id: 'maybe' }) }
      ],
      messageParamsJson: ''
    }
  }
})

// PIX button — works on both WhatsApp Web and mobile
await sock.sendMessage(jid, {
  text: '',
  interactiveButtons: [
    {
      name: 'payment_info',
      buttonParamsJson: JSON.stringify({
        payment_settings: [{
          type: 'pix_static_code',
          pix_static_code: {
            merchant_name: 'Your Name',
            key: '[email protected]',
            key_type: 'EMAIL' // PHONE | EMAIL | CPF | EVP
          }
        }]
      })
    }
  ]
})

// PAY button — works on both WhatsApp Web and mobile
await sock.sendMessage(jid, {
  text: '',
  interactiveButtons: [
    {
      name: 'review_and_pay',
      buttonParamsJson: JSON.stringify({
        currency: 'IDR',
        payment_configuration: '',
        payment_type: '',
        total_amount: { value: '10000', offset: '100' },
        reference_id: 'REF-001',
        type: 'physical-goods',
        payment_method: 'confirm',
        payment_status: 'captured',
        payment_timestamp: Math.floor(Date.now() / 1000),
        order: {
          status: 'completed',
          description: '',
          subtotal: { value: '0', offset: '100' },
          order_type: 'PAYMENT_REQUEST',
          items: [{
            retailer_id: 'your_retailer_id',
            name: 'Product Name',
            amount: { value: '10000', offset: '100' },
            quantity: '1'
          }]
        },
        additional_note: 'Thank you',
        native_payment_methods: [],
        share_payment_status: false
      })
    }
  ]
})

await sock.sendMessage(jid, {
  interactiveMessage: {
    header: { title: 'Visit Our Website', hasMediaAttachment: false },
    body:   { text: 'Click the button below.' },
    footer: { text: 'yebail' },
    nativeFlowMessage: {
      buttons: [
        {
          name: 'cta_url',
          buttonParamsJson: JSON.stringify({
            display_text: 'Open Website',
            url: 'https://github.com/yemo-dev/baileys',
            merchant_url: 'https://github.com/yemo-dev/baileys'
          })
        }
      ],
      messageParamsJson: ''
    }
  }
})

await sock.sendMessage(jid, {
  interactiveMessage: {
    header: { title: 'Your Promo Code', hasMediaAttachment: false },
    body:   { text: 'Use the promo code below for 20% off.' },
    footer: { text: 'yebail Shop' },
    nativeFlowMessage: {
      buttons: [
        {
          name: 'cta_copy',
          buttonParamsJson: JSON.stringify({
            display_text: 'Copy Code',
            id: 'promo_code',
            copy_code: 'YEBAIL20'
          })
        }
      ],
      messageParamsJson: ''
    }
  }
})

await sock.sendMessage(jid, {
  interactiveMessage: {
    header: { title: 'Select a Plan', hasMediaAttachment: false },
    body:   { text: 'Choose your subscription plan:' },
    footer: { text: 'yebail Services' },
    nativeFlowMessage: {
      buttons: [
        {
          name: 'single_select',
          buttonParamsJson: JSON.stringify({
            title: 'Available Plans',
            sections: [
              {
                title: 'Plans',
                rows: [
                  { header: 'Free',    title: 'Free Plan',     description: 'Basic features', id: 'free'    },
                  { header: 'Basic',   title: 'Basic - $5',    description: 'More features',  id: 'basic'   },
                  { header: 'Premium', title: 'Premium - $20', description: 'All features',   id: 'premium' }
                ]
              }
            ]
          })
        }
      ],
      messageParamsJson: ''
    }
  }
})

await sock.sendMessage(jid, {
  interactiveMessage: {
    header: { title: 'Special Offer', hasMediaAttachment: false },
    body:   { text: 'Choose an action:' },
    footer: { text: 'yebail Bot' },
    nativeFlowMessage: {
      buttons: [
        {
          name: 'cta_url',
          buttonParamsJson: JSON.stringify({
            display_text: 'Open Website',
            url: 'https://github.com/yemo-dev/baileys',
            merchant_url: 'https://github.com/yemo-dev/baileys'
          })
        },
        {
          name: 'cta_copy',
          buttonParamsJson: JSON.stringify({ display_text: 'Copy Code', id: 'code', copy_code: 'YEBAIL50' })
        },
        {
          name: 'quick_reply',
          buttonParamsJson: JSON.stringify({ display_text: 'Continue', id: 'continue' })
        }
      ],
      messageParamsJson: ''
    }
  }
}, { quoted: msg })

await sock.sendMessage(jid, {
  interactiveMessage: {
    header: {
      title: 'Choose option',
      hasMediaAttachment: true,
      imageMessage: { url: 'https://example.com/banner.jpg', mimetype: 'image/jpeg' }
    },
    body:   { text: 'Choose:' },
    footer: { text: 'yebail' },
    nativeFlowMessage: {
      buttons: [
        { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Yes', id: 'yes' }) },
        { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'No',  id: 'no'  }) }
      ],
      messageParamsJson: ''
    }
  }
})

await sock.sendMessage(jid, {
  interactiveMessage: {
    header: { title: 'Main Menu', hasMediaAttachment: false },
    body:   { text: 'Please choose a menu:' },
    footer: { text: 'yebail' },
    nativeFlowMessage: {
      buttons: [
        {
          name: 'single_select',
          buttonParamsJson: JSON.stringify({
            title: 'Choose Category',
            sections: [
              {
                title: 'Food',
                rows: [
                  { title: 'Fried Rice', description: 'Tasty', id: 'fried_rice' },
                  { title: 'Chicken Noodles', description: 'Large', id: 'chicken_noodles' }
                ]
              }
            ],
            has_multiple_buttons: true
          })
        },
        {
          name: 'quick_reply',
          buttonParamsJson: JSON.stringify({ display_text: 'Close', id: 'close', has_multiple_buttons: true })
        }
      ],
      messageParamsJson: ''
    }
  }
}, { quoted: msg })

const fs = require('fs')
await sock.sendMessage(jid, {
  interactiveMessage: {
    header: 'Important Document',
    title: 'PDF File',
    footer: 'yebail',
    document: fs.readFileSync('./file.pdf'),
    mimetype: 'application/pdf',
    fileName: 'document.pdf',
    jpegThumbnail: fs.readFileSync('./thumb.jpg'),
    contextInfo: {
      mentionedJid: [jid],
      forwardingScore: 777,
      isForwarded: false
    },
    externalAdReply: {
      title: 'yebail Bot',
      body: 'Interactive bot',
      mediaType: 3,
      thumbnailUrl: 'https://example.com/thumb.jpg',
      sourceUrl: 'https://github.com/yemo-dev/baileys',
      showAdAttribution: true,
      renderLargerThumbnail: false
    },
    buttons: [
      {
        name: 'cta_url',
        buttonParamsJson: JSON.stringify({
          display_text: 'Open Link',
          url: 'https://github.com/yemo-dev/baileys',
          merchant_url: 'https://github.com/yemo-dev/baileys'
        })
      }
    ]
  }
}, { quoted: msg })

await sock.sendMessage(jid, {
  interactiveMessage: {
    header: 'Hello World',
    title: 'Hello World',
    footer: 'yebail',
    image: { url: 'https://example.com/image.jpg' },
    nativeFlowMessage: {
      messageParamsJson: JSON.stringify({
        limited_time_offer: {
          text: 'Limited offer',
          url: 'https://github.com/yemo-dev/baileys',
          copy_code: 'YEBAIL',
          expiration_time: Date.now() + 3600000
        },
        bottom_sheet: {
          in_thread_buttons_limit: 2,
          list_title: 'yebail',
          button_title: 'yebail'
        }
      }),
      buttons: [
        {
          name: 'single_select',
          buttonParamsJson: JSON.stringify({
            title: 'Hello World',
            sections: [
              {
                title: 'Options',
                highlight_label: 'label',
                rows: [
                  { title: 'Option 1', description: 'First option', id: 'opt1' }
                ]
              }
            ],
            has_multiple_buttons: true
          })
        },
        {
          name: 'call_permission_request',
          buttonParamsJson: JSON.stringify({ has_multiple_buttons: true })
        },
        {
          name: 'cta_copy',
          buttonParamsJson: JSON.stringify({ display_text: 'Copy Code', id: 'code', copy_code: 'YEBAIL' })
        }
      ]
    }
  }
}, { quoted: msg })

Carousel Message

await sock.sendMessage(jid, {
  interactiveMessage: {
    body: { text: 'Browse our products:' },
    footer: { text: 'Swipe to see more' },
    carouselMessage: {
      cards: [
        {
          header: {
            imageMessage: { url: 'https://example.com/product1.jpg', mimetype: 'image/jpeg' },
            hasMediaAttachment: true
          },
          body:   { text: 'Product 1 – Best seller' },
          footer: { text: 'Rp 99.000' },
          nativeFlowMessage: {
            buttons: [
              { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Buy Now', id: 'buy_1' }) }
            ],
            messageParamsJson: ''
          }
        },
        {
          header: {
            imageMessage: { url: 'https://example.com/product2.jpg', mimetype: 'image/jpeg' },
            hasMediaAttachment: true
          },
          body:   { text: 'Product 2 – New arrival' },
          footer: { text: 'Rp 149.000' },
          nativeFlowMessage: {
            buttons: [
              { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Buy Now', id: 'buy_2' }) }
            ],
            messageParamsJson: ''
          }
        }
      ]
    }
  }
})

Album Message

await sock.sendAlbumMessage(jid, [
  { image: { url: 'https://picsum.photos/800/600?1' }, caption: 'Photo 1' },
  { image: { url: 'https://picsum.photos/800/600?2' }, caption: 'Photo 2' },
  { video: { url: 'https://example.com/clip.mp4' },    caption: 'Video 1' }
], { delay: 300 })

await sock.sendMessage(jid, {
  album: [
    { image: { url: 'https://picsum.photos/800/600?1' } },
    { image: { url: 'https://picsum.photos/800/600?2' } }
  ]
})

Forward Message

await sock.sendMessage(jid, { forward: msg })
await sock.sendMessage(jid, { forward: msg, force: true })

Event Message

await sock.sendMessage(jid, {
  event: {
    isCanceled: false,
    name: 'Team Meeting',
    description: 'Weekly sync',
    location: { degreesLatitude: -6.2088, degreesLongitude: 106.8456, name: 'Jakarta' },
    joinLink: 'https://call.whatsapp.com/video/xxx',
    startTime: String(Math.floor(Date.now() / 1000) + 3600),
    endTime: String(Math.floor(Date.now() / 1000) + 7200),
    extraGuestsAllowed: false
  }
})

Poll Result Message

await sock.sendMessage(jid, {
  pollResult: {
    name: 'Favorite color?',
    values: [
      ['Red',   112],
      ['Green',  45],
      ['Blue',  233]
    ]
  }
})

Group Status Message

// raw object
await sock.sendMessage(jid, {
  groupStatusMessage: { text: 'Hello group!' }
})

// flag wrapper (wraps any message in groupStatusMessage)
await sock.sendMessage(jid, {
  image: { url: './photo.jpg' },
  caption: 'Group status!',
  groupStatus: true
})

View Once Variants

// viewOnce
await sock.sendMessage(jid, {
  image: { url: './photo.jpg' },
  viewOnce: true
})

// viewOnceMessageV2
await sock.sendMessage(jid, {
  image: { url: './photo.jpg' },
  viewOnceV2: true
})

// viewOnceMessageV2Extension
await sock.sendMessage(jid, {
  image: { url: './photo.jpg' },
  viewOnceV2Extension: true
})

Ephemeral Wrapper

await sock.sendMessage(jid, {
  image: { url: './photo.jpg' },
  caption: 'Ephemeral message',
  ephemeral: true
})

Interactive as Template

await sock.sendMessage(jid, {
  text: 'Choose an option',
  buttons: [{ id: 'a', text: 'Option A' }],
  interactiveAsTemplate: true
})

External Ad Reply (all message types)

await sock.sendMessage(jid, {
  text: 'Check this out',
  externalAdReply: {
    title: 'My App',
    body: 'Click to open',
    thumbnail: fs.readFileSync('./thumb.jpg'),
    largeThumbnail: false,
    url: 'https://example.com',
    showAdAttribution: true
  }
})

// snake_case compatibility aliases are supported too
await sock.sendMessage(jid, {
  text: 'Alias compatibility',
  externalAdReply: {
    title: 'My App',
    body: 'Open now',
    media_type: 1,
    thumbnail_url: 'https://example.com/thumb.jpg',
    source_url: 'https://example.com',
    show_ad_attribution: true,
    render_larger_thumbnail: false
  }
})

Secure Meta Service Label

await sock.sendMessage(jid, {
  text: 'Just a label!',
  secureMetaServiceLabel: true
})

Raw Proto (manual)

await sock.sendMessage(jid, {
  extendedTextMessage: {
    text: 'Built from raw proto',
    contextInfo: {
      externalAdReply: {
        title: 'yebail',
        jpegThumbnail: fs.readFileSync('./thumb.jpg'),
        sourceApp: 'whatsapp',
        showAdAttribution: true,
        mediaType: 1
      }
    }
  },
  raw: true
})

Payment Request

⚠️ WA Web only — Payment request messages are only fully functional on WhatsApp Web. Sending via the mobile app may cause unexpected behaviour or force-close.

// simple shorthand - requestFrom is who should pay
await sock.sendMessage(jid, {
  text: 'Payment for subscription',
  requestPaymentFrom: jid    // jid of the person who should pay
})

// full control — amount is in thousandths of the currency unit (auto-rounded if float/string)
await sock.sendMessage(jid, {
  requestPayment: {
    currency: 'IDR',
    amount: 100000 * 1000,   // 100 000 IDR  →  100000000
    from: jid,               // JID of who should pay (not the bot's own JID)
    note: 'Payment for subscription'
  }
})

// string amount also supported
await sock.sendMessage(jid, {
  requestPayment: {
    currency: 'IDR',
    amount: '10000000',      // "10000000" → parsed to 10000000
    from: jid,
    note: 'Hai Guys'
  }
})

// with sticker note (Buffer or { url })
await sock.sendMessage(jid, {
  requestPayment: {
    currency: 'IDR',
    amount: 10000 * 1000,
    from: jid,
    sticker: fs.readFileSync('./note.webp')
  }
})

await sock.sendMessage(jid, {
  requestPaymentMessage: {
    currencyCodeIso4217: 'IDR',
    amount1000: 100000 * 1000,
    requestFrom: jid,
    noteMessage: {
      extendedTextMessage: { text: 'Payment for subscription' }
    }
  }
})

// with background
await sock.sendMessage(jid, {
  requestPayment: {
    currency: 'IDR',
    amount: 50000 * 1000,
    from: jid,
    background: {
      id: '100',
      fileLength: '0',
      width: 1000,
      height: 1000,
      mimetype: 'image/webp',
      placeholderArgb: 0xFF00FFFF,
      textArgb: 0xFFFFFFFF,
      subtextArgb: 0xFFAA00FF
    }
  }
})

Send Payment (respond to a request)

⚠️ WA Web only — Send payment payload rendering depends on WhatsApp Web support.

await sock.sendMessage(jid, {
  sendPayment: {
    requestMessageKey: reqMsg.key, // key dari pesan requestPayment yang mau dibayar
    note: 'Paid, thank you!',
    transactionData: 'opaque-transaction-payload'
  }
})

// raw/proto-compatible form
await sock.sendMessage(jid, {
  sendPaymentMessage: {
    requestMessageKey: reqMsg.key,
    noteMessage: {
      extendedTextMessage: { text: 'Paid via transfer' }
    }
  }
})

Decline / Cancel Payment Request

// decline request from the payer side
await sock.sendMessage(jid, {
  declinePaymentRequest: reqMsg.key
})

// cancel request from the requester side
await sock.sendMessage(jid, {
  cancelPaymentRequest: reqMsg.key
})

Payment Invite

⚠️ WA Web only — Payment invite messages (GPay / PhonePe / Meta Pay) are only rendered on WhatsApp Web.

// serviceType: 1 = GPay, 2 = PhonePe, 3 = Meta Pay
await sock.sendMessage(jid, {
  paymentInviteServiceType: 3,
  paymentInviteExpiry: Math.floor(Date.now() / 1000) + 86400
})

// alias object form
await sock.sendMessage(jid, {
  paymentInvite: {
    type: 3,
    expiry: Math.floor(Date.now() / 1000) + 86400
  }
})

Invoice

await sock.sendMessage(jid, {
  image: { url: './invoice.jpg' },
  invoiceNote: 'Invoice #1234'
})

Order (simple)

await sock.sendMessage(jid, {
  orderText: 'Your order is ready!',
  thumbnail: fs.readFileSync('./product.jpg')
}, { quoted: message })

Order (full)

await sock.sendMessage(jid, {
  order: {
    id: 'ORD-1001',
    thumbnail: fs.readFileSync('./product.jpg'),
    itemCount: 2,
    status: 1,
    surface: 1,
    title: 'Order Confirmation',
    text: 'Thanks for your purchase!',
    seller: '[email protected]',
    token: 'order-token',
    amount: 150000 * 1000,
    currency: 'IDR'
  }
})

Product Message

await sock.sendMessage(jid, {
  product: {
    productImage: { url: 'https://example.com/product.jpg' },
    productId: 'prod-1',
    title: 'Premium Coffee Beans',
    description: 'Roasted arabica',
    currencyCode: 'IDR',
    priceAmount1000: '120000000',
    retailerId: 'sku-001',
    productImageCount: 1
  },
  businessOwnerJid: '[email protected]'
})

Product List Message

await sock.sendMessage(jid, {
  title: 'Catalog',
  text: 'Choose a product',
  footer: 'Yebail Shop',
  buttonText: 'View Products',
  businessOwnerJid: '[email protected]',
  productList: [
    {
      title: 'Best Seller',
      products: [
        { productId: 'prod-1' },
        { productId: 'prod-2' }
      ]
    }
  ]
})

Shop Interactive

await sock.sendMessage(jid, {
  text: 'Open storefront',
  footer: 'Yebail Store',
  shop: {
    id: '[email protected]',
    surface: 1
  }
})

Template Buttons (legacy)

await sock.sendMessage(jid, {
  text: 'Choose action',
  footer: 'Yebail',
  templateButtons: [
    { index: 1, quickReplyButton: { displayText: 'Ping', id: 'ping' } },
    { index: 2, urlButton: { displayText: 'Website', url: 'https://github.com/yemo-dev/baileys' } }
  ]
})

Interactive Buttons (native flow shorthand)

await sock.sendMessage(jid, {
  text: 'Quick options',
  footer: 'Yebail',
  interactiveButtons: [
    { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Option A', id: 'opt_a' }) },
    { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Option B', id: 'opt_b' }) }
  ]
})

List Reply (send simulated response)

await sock.sendMessage(jid, {
  listReply: {
    title: 'Order Menu',
    description: 'Selected by bot',
    singleSelectReply: { selectedRowId: 'pizza' },
    listType: 1
  }
})

Group Invite Message (send)

await sock.sendMessage(jid, {
  groupInvite: {
    inviteCode: 'AbCdEfGhIj',
    inviteExpiration: Math.floor(Date.now() / 1000) + 86400,
    text: 'Join our group',
    jid: '[email protected]',
    subject: 'Yebail Community'
  }
})

Newsletter Admin Invite (send)

await sock.sendMessage(jid, {
  inviteAdmin: {
    inviteExpiration: Math.floor(Date.now() / 1000) + 86400,
    text: 'Please become admin',
    jid: '1203630xxxxxxxx@newsletter',
    subject: 'Yebail Channel',
    thumbnail: fs.readFileSync('./thumb.jpg')
  }
})

Phone Number Request / Share

await sock.sendMessage(jid, { requestPhoneNumber: true })
await sock.sendMessage(jid, { sharePhoneNumber: true })

Scheduled Call Message

await sock.sendMessage(jid, {
  call: {
    title: 'Project Sync Call',
    type: 1,
    time: Date.now() + 10 * 60 * 1000
  }
})

Status / Story

await sock.sendMessage('status@broadcast', {
  text: 'Hello everyone!',
  backgroundColor: '#FF5733',
  font: 3
}, {
  statusJidList: ['[email protected]', '[email protected]']
})

await sock.sendMessage('status@broadcast', {
  image: { url: 'https://example.com/photo.jpg' },
  caption: 'Check out this photo!'
}, {
  statusJidList: ['[email protected]']
})

await sock.sendStatusMentions(
  { text: 'Hey check this out!' },
  ['[email protected]']
)

await sock.sendStatusMentions(
  { image: { url: 'https://example.com/photo.jpg' }, caption: 'Photo!' },
  ['[email protected]']
)

await sock.sendGroupStatus(
  ['[email protected]', '[email protected]'],
  { text: 'Status for group members' }
)

await sock.sendGroupStatus(
  ['[email protected]'],
  {
    image: { url: 'https://example.com/photo.jpg' },
    caption: 'Group status V2 with media'
  },
  {
    relay: { useCachedGroupMetadata: true }
  }
)

// Backward-compatible: if your code relays `groupStatusMessageV2` or `groupStatusMessage` directly to a group JID,
// Baileys will auto-route it via `status@broadcast` and resolve group members as audience.
// Recommended API is still `sendGroupStatus(...)`.

Image Slide / Carousel (Code Only)

const { proto, prepareWAMessageMedia, generateWAMessageFromContent } = require('@yemo-dev/yebail')

const result = []
const imageUrls = [
  'https://example.com/1.jpg',
  'https://example.com/2.jpg',
  'https://example.com/3.jpg'
]

for (let i = 0; i < imageUrls.length; i++) {
  const imageMessage = await prepareWAMessageMedia(
    { image: { url: imageUrls[i] } },
    { upload: sock.waUploadToServer }
  )

  result.push({
    body: proto.Message.InteractiveMessage.Body.fromObject({}),
    footer: proto.Message.InteractiveMessage.Footer.fromObject({}),
    header: proto.Message.InteractiveMessage.Header.fromObject({
      title: `Slide ${i + 1}/${imageUrls.length}`,
      hasMediaAttachment: true,
      ...imageMessage
    }),
    nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.fromObject({
      buttons: []
    })
  })
}

const msg = generateWAMessageFromContent(jid, {
  viewOnceMessage: {
    message: {
      messageContextInfo: {
        deviceListMetadata: {},
        deviceListMetadataVersion: 2
      },
      interactiveMessage: proto.Message.InteractiveMessage.fromObject({
        body: proto.Message.InteractiveMessage.Body.fromObject({
          text: 'Image Slide'
        }),
        header: proto.Message.InteractiveMessage.Header.fromObject({
          hasMediaAttachment: false
        }),
        carouselMessage: proto.Message.InteractiveMessage.CarouselMessage.fromObject({
          cards: result
        })
      })
    }
  }
}, { quoted: m })

await sock.relayMessage(msg.key.remoteJid, msg.message, { messageId: msg.key.id })

Button Reply (send)

await sock.sendMessage(jid, {
  buttonReply: { title: 'Pizza', rowId: 'pizza' },
  type: 'list'
})

await sock.sendMessage(jid, {
  buttonReply: { displayText: 'View Menu', id: 'id1', index: 0 },
  type: 'template'
})

await sock.sendMessage(jid, {
  buttonReply: {
    displayText: 'Yes!',
    nativeFlows: { name: 'quick_reply', paramsJson: JSON.stringify({ id: 'yes' }) }
  },
  type: 'interactive'
})

Receiving Button Responses

When a user taps a quick_reply or single_select button, the bot receives an interactiveResponseMessage.

sock.ev.on('messages.upsert', async ({ messages }) => {
  const msg = messages[0]
  if (!msg.message) return

  const type = getContentType(msg.message)

  if (type === 'interactiveResponseMessage') {
    const response = msg.message.interactiveResponseMessage
    const body = response?.body?.text
    try {
      const params = JSON.parse(response?.nativeFlowResponseMessage?.paramsJson || '{}')
      const buttonId = params.id
      const displayText = params.display_text || body

      console.log('Button pressed:', buttonId, '|', displayText)

      if (buttonId === 'yes') {
        await sock.sendMessage(msg.key.remoteJid, { text: 'You chose Yes!' }, { quoted: msg })
      } else if (buttonId === 'no') {
        await sock.sendMessage(msg.key.remoteJid, { text: 'You chose No!' }, { quoted: msg })
      }
    } catch (e) {
      console.log('Button response body:', body)
    }
    return
  }

  if (type === 'listResponseMessage') {
    const selectedId = msg.message.listResponseMessage?.singleSelectReply?.selectedRowId
    const selectedTitle = msg.message.listResponseMessage?.title
    console.log('List selected:', selectedId, '|', selectedTitle)
    return
  }

  if (type === 'buttonsResponseMessage') {
    const selectedId = msg.message.buttonsResponseMessage?.selectedButtonId
    const displayText = msg.message.buttonsResponseMessage?.selectedDisplayText
    console.log('Button selected:', selectedId, '|', displayText)
    return
  }
})

Modify Messages

const sent = await sock.sendMessage(jid, { text: 'Original text' })

await sock.sendMessage(jid, { text: 'Corrected text', edit: sent.key })

await sock.sendMessage(jid, { delete: msg.key })

await sock.sendMessage(jid, { pin: sent.key, type: 1 })
await sock.sendMessage(jid, { pin: sent.key, type: 2 })

await sock.sendMessage(jid, { keep: msg.key, type: 1 })

Media Handling

const { downloadMediaMessage } = require('@yemo-dev/yebail')
const fs = require('fs')

sock.ev.on('messages.upsert', async ({ messages }) => {
  const msg = messages[0]
  if (!msg.message) return

  const type = getContentType(msg.message)
  const mediaTypes = ['imageMessage', 'videoMessage', 'audioMessage', 'documentMessage', 'stickerMessage']

  if (mediaTypes.includes(type)) {
    const buffer = await downloadMediaMessage(msg, 'buffer', {})
    fs.writeFileSync('./downloads/media', buffer)

    const stream = await downloadMediaMessage(msg, 'stream', {})
    stream.pipe(fs.createWriteStream('./downloads/stream-file'))

    console.log('Downloaded', type, 'size:', buffer.length, 'bytes')
  }
})

Read Receipts

await sock.readMessages([msg.key])

await sock.readMessages([
  { id: 'MSG_ID_1', remoteJid: jid, fromMe: false },
  { id: 'MSG_ID_2', remoteJid: jid, fromMe: false }
])

Reject Call

sock.ev.on('call', async (calls) => {
  for (const call of calls) {
    if (call.status === 'offer') {
      await sock.rejectCall(call.id, call.from)
    }
  }
})

Presence

await sock.sendPresenceUpdate('available')
await sock.sendPresenceUpdate('unavailable')
await sock.sendPresenceUpdate('composing', jid)
await sock.sendPresenceUpdate('paused', jid)
await sock.sendPresenceUpdate('recording', jid)

await sock.presenceSubscribe(jid)

sock.ev.on('presence.update', ({ id, presences }) => {
  for (const [participant, presence] of Object.entries(presences)) {
    console.log(participant, 'is', presence.lastKnownPresence)
    if (presence.lastSeen) console.log('Last seen:', new Date(presence.lastSeen * 1000))
  }
})

Chat Modification

await sock.chatModify(
  { archive: true, lastMessages: [{ key: msg.key, messageTimestamp: msg.messageTimestamp }] },
  jid
)

await sock.chatModify({ pin: true }, jid)
await sock.chatModify({ pin: false }, jid)

await sock.chatModify({ mute: Date.now() + 8 * 60 * 60 * 1000 }, jid)
await sock.chatModify({ mute: null }, jid)

await sock.chatModify(
  { markRead: false, lastMessages: [{ key: msg.key, messageTimestamp: msg.messageTimestamp }] },
  jid
)

await sock.chatModify(
  { delete: true, lastMessages: [{ key: msg.key, messageTimestamp: msg.messageTimestamp }] },
  jid
)

await sock.star(jid, [{ id: msg.key.id, fromMe: !!msg.key.fromMe }], true)
await sock.star(jid, [{ id: msg.key.id, fromMe: !!msg.key.fromMe }], false)

await sock.sendMessage(jid, { disappearingMessagesInChat: true })
await sock.sendMessage(jid, { disappearingMessagesInChat: false })
await sock.sendMessage(jid, { disappearingMessagesInChat: 86400 })

User Queries

const [result] = await sock.onWhatsApp('[email protected]')
console.log(result?.exists, result?.lid)

const results = await sock.onWhatsApp('[email protected]', '[email protected]')
results.forEach(r => console.log(r.jid, r.exists))

const statuses = await sock.fetchStatus(jid)
console.log(statuses?.[0]?.status)

const durations = await sock.fetchDisappearingDuration(jid)

const props = await sock.fetchProps()
console.log('Web props:', props)
// useful for checking account/web capability flags (varies by account)

const previewUrl = await sock.profilePictureUrl(jid, 'preview')
const fullUrl = await sock.profilePictureUrl(jid, 'image')

await sock.addOrEditContact(jid, { notify: 'John Doe' })
await sock.removeContact(jid)

// resolve PN ↔ LID bidirectionally
const ids = await sock.findUserId('[email protected]')
console.log(ids.phoneNumber, ids.lid)

const ids2 = await sock.findUserId('43411111111111@lid')
console.log(ids2.phoneNumber, ids2.lid)
// { phoneNumber: '[email protected]', lid: '434xxx@lid' }
// { phoneNumber: 'id-not-found', lid: '434xxx@lid' }  <- when not resolvable

Profile

const fs = require('fs')

await sock.updateProfileName('Yebail Bot')
await sock.updateProfileStatus('Running on @yemo-dev/yebail')
await sock.updateProfilePicture(sock.authState.creds.me.id, fs.readFileSync('./avatar.jpg'))
await sock.updateProfilePicture(groupJid, fs.readFileSync('./group-icon.jpg'))
await sock.removeProfilePicture(sock.authState.creds.me.id)

Privacy Settings

await sock.updateLastSeenPrivacy('contacts')
await sock.updateOnlinePrivacy('match_last_seen')
await sock.updateProfilePicturePrivacy('contacts')
await sock.updateStatusPrivacy('contacts')
await sock.updateReadReceiptsPrivacy('all')
await sock.updateGroupsAddPrivacy('contacts')
await sock.updateMessagesPrivacy('all')
await sock.updateCallPrivacy('contacts')
await sock.updateDefaultDisappearingMode(604800)
await sock.updateDisableLinkPreviewsPrivacy(true)

Block / Unblock

const blocklist = await sock.fetchBlocklist()
await sock.updateBlockStatus('[email protected]', 'block')
await sock.updateBlockStatus('[email protected]', 'unblock')

Groups

const group = await sock.groupCreate('My Group', [
  '[email protected]',
  '[email protected]'
])
console.log('Group JID:', group.id)

await sock.groupLeave(groupJid)

await sock.groupUpdateSubject(groupJid, 'New Group Name')
await sock.groupUpdateDescription(groupJid, 'New description.')
await sock.groupUpdateDescription(groupJid, null)

await sock.groupParticipantsUpdate(groupJid, ['[email protected]'], 'add')
await sock.groupParticipantsUpdate(groupJid, ['[email protected]'], 'remove')
await sock.groupParticipantsUpdate(groupJid, ['[email protected]'], 'promote')
await sock.groupParticipantsUpdate(groupJid, ['[email protected]'], 'demote')

const code = await sock.groupInviteCode(groupJid)
const newCode = await sock.groupRevokeInvite(groupJid)
const joinedJid = await sock.groupAcceptInvite('INVITE_CODE')
const info = await sock.groupGetInviteInfo('INVITE_CODE')

sock.ev.on('messages.upsert', async ({ messages }) => {
  const msg = messages[0]
  if (msg.message?.groupInviteMessage) {
    await sock.groupAcceptInviteV4(msg.key, msg.message.groupInviteMessage)
  }
})

await sock.groupRevokeInviteV4(groupJid, '[email protected]')

await sock.groupJoinApprovalMode(groupJid, 'on')
const requests = await sock.groupRequestParticipantsList(groupJid)
await sock.groupRequestParticipantsUpdate(groupJid, ['[email protected]'], 'approve')
await sock.groupRequestParticipantsUpdate(groupJid, ['[email protected]'], 'reject')

await sock.groupSettingUpdate(groupJid, 'announcement')
await sock.groupSettingUpdate(groupJid, 'not_announcement')
await sock.groupSettingUpdate(groupJid, 'locked')
await sock.groupSettingUpdate(groupJid, 'unlocked')

await sock.groupMemberAddMode(groupJid, 'all_member_add')

await sock.groupToggleEphemeral(groupJid, 604800)
await sock.groupToggleEphemeral(groupJid, 86400)
await sock.groupToggleEphemeral(groupJid, 0)

const meta = await sock.groupMetadata(groupJid)
console.log(meta.id, meta.subject, meta.desc, meta.participants.length)

const groups = await sock.groupFetchAllParticipating()
for (const [jid, meta] of Object.entries(groups)) {
  console.log(meta.subject, jid)
}

Community

const community = await sock.communityCreate('My Community', 'Welcome!')

const meta = await sock.communityMetadata(communityJid)

await sock.communityUpdateSubject(communityJid, 'New Name')
await sock.communityUpdateDescription(communityJid, 'New description.')

await sock.communityCreateGroup('Study Room', ['[email protected]'], communityJid)

await sock.communityLinkGroup(existingGroupJid, communityJid)
await sock.communityUnlinkGroup(existingGroupJid, communityJid)

const { linkedGroups } = await sock.communityFetchLinkedGroups(communityJid)

await sock.communityParticipantsUpdate(communityJid, ['[email protected]'], 'add')
await sock.communityParticipantsUpdate(communityJid, ['[email protected]'], 'remove')

const cCode = await sock.communityInviteCode(communityJid)
await sock.communityRevokeInvite(communityJid)

const reqs = await sock.communityRequestParticipantsList(communityJid)
await sock.communityRequestParticipantsUpdate(communityJid, ['[email protected]'], 'approve')

await sock.communityLeave(communityJid)

Newsletter / Channel

const fs = require('fs')

const newsletter = await sock.newsletterCreate(
  'My Channel',
  'Latest updates',
  fs.readFileSync('./logo.jpg')
)
console.log('Newsletter JID:', newsletter.id)

await sock.newsletterDelete(newsletter.id)

await sock.newsletterUpdateName(newsletter.id, 'New Channel Name')
await sock.newsletterUpdateDescription(newsletter.id, 'Updated description.')
await sock.newsletterUpdatePicture(newsletter.id, fs.readFileSync('./logo.jpg'))
await sock.newsletterRemovePicture(newsletter.id)

await sock.newsletterFollow(newsletter.id)
await sock.newsletterUnfollow(newsletter.id)
await sock.newsletterMute(newsletter.id)
await sock.newsletterUnmute(newsletter.id)

await sock.subscribeNewsletterUpdates(newsletter.id)

const meta = await sock.newsletterMetadata('JID', newsletter.id)
console.log(meta.name, meta.subscribers, meta.verification)

const count = await sock.newsletterAdminCount(newsletter.id)

await sock.newsletterChangeOwner(newsletter.id, '[email protected]')
await sock.newsletterDemote(newsletter.id, '[email protected]')

await sock.newsletterReactionMode(newsletter.id, 'all')
await sock.newsletterReactionMode(newsletter.id, 'basic')
await sock.newsletterReactionMode(newsletter.id, 'none')

const messages = await sock.newsletterFetchMessages('jid', newsletter.id, 10)
for (const item of messages) {
  console.log('Server ID:', item.server_id, 'Views:', item.views)
}

const updates = await sock.newsletterFetchUpdates(newsletter.id, 10)

await sock.newsletterReactMessage(newsletter.id, 'SERVER_ID', 'x')
await sock.newsletterReactMessage(newsletter.id, 'SERVER_ID', null)

const inviteMeta = await sock.newsletterId('https://whatsapp.com/channel/0029Va9vcYKGgYKQNc8wUd')
console.log('Newsletter ID:', inviteMeta.id, inviteMeta.name)

const subscribed = await sock.newsletterSubscribed()
for (const ch of subscribed) {
  console.log(ch.id, ch.name)
}

await sock.sendMessage('1203630xxxxxxxx@newsletter', {
  video: { url: 'https://a.top4top.io/m_3706zd9k00.mp4' },
  caption: 'jawa banget',
  streamingSidecar: 'QD4XJIMi3ARGTYV8zNWRfNX05nc//e7lxshUO2RH/NuhA7tkg5ew/vPfKOFtIrTt/+E=',
  annotations: [
    {
      embeddedContent: {
        embeddedMusic: {
          musicContentMediaId: '12',
          songId: '11',
          author: 'Shinaru',
          title: 'Oryta Community',
          artistAttribution: 'https://github.com/sh1njs/Katsumi'
        }
      },
      embeddedAction: true
    }
  ]
})

Business Profile

const profile = await sock.getBusinessProfile('[email protected]')
console.log(profile?.address, profile?.email, profile?.description)

await sock.updateBusinessProfile({
  address: '123 Main Street, Jakarta',
  email: '[email protected]',
  description: 'Official WhatsApp Business account.',
  websites: ['https://mybusiness.com'],
  hours: {
    timezone: 'Asia/Jakarta',
    days: [
      { day: 'MON', mode: 'specific_hours', openTimeInMinutes: 540, closeTimeInMinutes: 1080 },
      { day: 'SAT', mode: 'open_24h' },
      { day: 'SUN', mode: 'closed' }
    ]
  }
})

await sock.updateCoverPhoto(fs.readFileSync('./cover.jpg'))
await sock.removeCoverPhoto()

Compatibility: sock.updateBussinesProfile(...) remains available as a legacy alias.


Labels

await sock.addChatLabel(jid, 'LABEL_ID')
await sock.removeChatLabel(jid, 'LABEL_ID')
await sock.addMessageLabel(jid, msg.key.id, 'LABEL_ID')
await sock.removeMessageLabel(jid, msg.key.id, 'LABEL_ID')

await sock.addOrEditQuickReply({
  shortcut: 'hello',
  message: 'Hello! How can I help you?',
  timestamp: Date.now()
})
await sock.removeQuickReply(timestamp)

await sock.updateMemberLabel(groupJid, 'Custom Member Tag')

Bot Features

const bots = await sock.getBotListV2()
console.log(bots)

await sock.sendMessage(jid, {
  text: 'What is the weather today?',
  ai: true
})

Rich AI Response (Bot Forward)

Send a WhatsApp AI-style rich response — the same format used by Meta AI bots — with an optional syntax-highlighted code block.
Uses botForwardedMessagerichResponseMessageunifiedResponse (raw JSON bytes in the data field).

// Text-only
await sock.sendMessage(jid, {
  richResponse: {
    text: 'aku hann universe'
  }
})

// Text + JS code block (auto-tokenized)
await sock.sendMessage(jid, {
  richResponse: {
    text: 'Here is a Hello World example:',
    code: 'console.log("Hello World")',
    language: 'javascript'   // default
  }
})

// Text + code + custom bot JID
await sock.sendMessage(jid, {
  richResponse: {
    text: 'Result:',
    code: 'const x = 42\nconsole.log(x)',
    botJid: '259786046210223@bot'
  }
})

Token types produced by the built-in tokenizer: KEYWORD, STR, NUMBER, METHOD, COMMENT, DEFAULT
(mapped to GenAICodeUXPrimitive.code_blocks inside the unifiedResponse payload).

WAProto types used: AIRichResponseMessage (field 97), AIRichResponseUnifiedResponse, ForwardedAIBotMessageInfo, BotMessageSharingInfo — all present in WAProto.


New Message Types (WA 2.3000+)

These message types were added in WhatsApp Web 2.3000.x. All support both a short-key alias and the full proto field name.

Status Notification

Sent when a status add-yours / reshare / question-answer-reshare event fires.

await sock.sendMessage(jid, {
  statusNotification: {
    responseMessageKey: { remoteJid: jid, id: 'MSG_ID' },
    originalMessageKey:  { remoteJid: jid, id: 'ORIG_ID' },
    type: 1  // 1=STATUS_ADD_YOURS, 2=STATUS_RESHARE, 3=STATUS_QUESTION_ANSWER_RESHARE
  }
})
// full proto key also accepted:
// statusNotificationMessage: { ... }

Status Question Answer

User answered a status question.

await sock.sendMessage(jid, {
  statusQuestionAnswer: {
    key:  { remoteJid: jid, id: 'MSG_ID' },
    text: 'My answer'
  }
})
// full proto key: statusQuestionAnswerMessage

Question Response

Direct response to a question message.

await sock.sendMessage(jid, {
  questionResponse: {
    key:  { remoteJid: jid, id: 'QUESTION_MSG_ID' },
    text: 'My response'
  }
})
// full proto key: questionResponseMessage

Status Quoted Message

Quote a status with a custom type.

await sock.sendMessage(jid, {
  statusQuoted: {
    type: 1,           // 1 = QUESTION_ANSWER
    text: 'Quoted text',
    thumbnail: Buffer, // optional
    originalStatusId: { remoteJid: jid, id: 'STATUS_MSG_ID' }
  }
})
// full proto key: statusQuotedMessage

Status Sticker Interaction

React to a status with a sticker.

await sock.sendMessage(jid, {
  statusStickerInteraction: {
    key:       { remoteJid: jid, id: 'STATUS_MSG_ID' },
    stickerKey: 'sticker-hash-key',
    type: 1    // 1 = REACTION
  }
})
// full proto key: statusStickerInteractionMessage

Newsletter Follower Invite

Invite a user to follow a newsletter.

await sock.sendMessage(jid, {
  newsletterFollowerInvite: {
    newsletterJid:  '120363xxxxxx@newsletter',
    newsletterName: 'My Channel',
    jpegThumbnail:  Buffer, // optional
    caption: 'Join my channel!'
  }
})
// full proto key: newsletterFollowerInviteMessageV2

Message History Notice

Notify about message history metadata.

await sock.sendMessage(jid, {
  messageHistoryNotice: {
    contextInfo: { ... }
    // messageHistoryMetadata is optional
  }
})

WAProto Sync & Auto-Update

WAProto is the bundled protobuf module (WAProto/index.js) auto-generated from WhatsApp Web. Every top-level proto type has its own per-module directory with .js and .proto files.

Available Scripts

# Fetch latest proto from WA Web + regenerate bundle + sync per-module files + update version
yarn update:all

# Same as above but proto only (no version update)
yarn update:proto

# Update WA Web version tracking only (no proto extraction)
yarn update:version

# Re-sync per-module wrapper files from existing WAProto/index.js (no network)
# Useful after a git pull that updated WAProto/index.js
yarn sync:proto

Version Tracking

The current WhatsApp Web version is stored in:

lib/Defaults/yebail-version.json

Format: {"version":[2,3000,XXXXXXXXX]}. Updated automatically by yarn update:version and yarn update:all. The version array is also exported from the library as version and embedded as a /// WhatsApp Version: comment in each .proto file.

Auto-Update CI

The GitHub Actions Auto Update workflow runs every Sunday (0 0 * * 0) and:

  1. Runs yarn update:version — fetches the latest WA Web version, updates lib/Defaults/yebail-version.json and lib/Defaults/index.js
  2. Runs yarn update:proto — re-extracts the proto schema from WA Web, regenerates WAProto/index.js, syncs all per-module .js/.proto files
  3. If proto extraction fails, runs yarn sync:proto as fallback to regenerate per-module wrappers from the existing bundle
  4. Bumps the npm patch version, commits all changes, creates a git tag, pushes to main, and publishes to npm

You can also trigger it manually from the Actions tab → Auto UpdateRun workflow.


Call Link

const token = await sock.createCallLink('video')
console.log('Video call link token:', token)

const audioToken = await sock.createCallLink('audio')

const eventToken = await sock.createCallLink('video', {
  startTime: Math.floor(Date.now() / 1000) + 3600
})

Custom WS Callbacks

const pino = require('pino')
const sock = makeWASocket({
  logger: pino({ level: 'debug' })
})

sock.ws.on('CB:edge_routing', (node) => console.log('Edge routing:', node))
sock.ws.on('CB:iq', (node) => console.log('IQ received:', node.attrs))
sock.ws.on('CB:call', (node) => console.log('Call node:', node))

Maintenance Mode

Yebail includes a built-in maintenance mode feature. When enabled, each makeWASocket() call immediately shows a maintenance message and stops the process — useful when you need to apply updates or fixes without creating a new WhatsApp connection.

Enable / Disable via npm scripts

# Enable maintenance mode
npm run maintenance:on

# Disable maintenance mode
npm run maintenance:off

Enable via code

const { MAINTENANCE_MODE, MAINTENANCE_MESSAGE } = require('@yemo-dev/yebail')

// Check status
console.log('Maintenance active?', MAINTENANCE_MODE)
// Default message: '[YEBAIL] Maintenance mode is currently active. ...'
console.log(MAINTENANCE_MESSAGE)

Note: npm run maintenance:on/off directly modifies lib/Defaults/index.js, so the effect is persistent until changed again. For temporary usage (env-based), set variables in your own code before calling makeWASocket.


Feature Comparison

| Feature | Status | Notes | |---|---|---| | Text Messages | yes | extended with link preview | | Media (Image, Video, Audio, Document) | yes | with compression and thumbnails | | Stickers | yes | regular, Lottie, Avatar | | Reactions | yes | on any message type | | Polls | yes | V1–V5 with vote tracking | | Buttons / Interactive | yes | quick_reply, type-4 nativeFlowInfo, mixed arrays, buttonsMessage (legacy), list, native flow, carousel, pix/pay | | Event Message | yes | | | Poll Result Message | yes | | | Group Status Message | yes | | | Album / Collection | yes | multiple media grouped | | Carousel | yes | multi-card scrollable | | externalAdReply shorthand | yes | folds into contextInfo.externalAdReply | | Payment Request | yes (WA Web only) | with background support | | Group Management | yes | create, manage, settings | | Communities | yes | create, link groups | | Business Features | yes | profile, catalog, products | | Newsletter / Channels | yes | create, manage, analytics | | newsletterId(url) | yes | get newsletter info from invite URL | | newsletterSubscribed() | yes | list all followed newsletters | | findUserId(jid) | yes | bidirectional PN ↔ LID resolution | | Contact Management | yes | lookup, verification | | Profile Features | yes | update, privacy controls | | Privacy Settings | yes | all major categories | | Message Editing | yes | | | Message Deletion | yes | | | Disappearing Messages | yes | | | Status / Stories | yes | including mentions | | Multi-Device | yes | QR and pairing code | | History Sync | yes | | | SQLite Auth State | yes | | | Custom Auth State | yes | Redis, MongoDB, etc. | | LID Support | yes | modern identity system | | Encryption | yes | Signal protocol (vendored internal libsignal-node in lib/Signal/libsignal-node) | | Auto-Updates | yes | yarn update:all / weekly CI schedule → auto-publishes to npm | | WAProto per-module sync | yes | yarn sync:proto re-generates all per-module wrappers from bundle | | WAProto version tracking | yes | lib/Defaults/yebail-version.json stores current WA Web version | | statusNotificationMessage | yes | status add-yours / reshare notification | | statusQuestionAnswerMessage | yes | answer to a status question | | questionResponseMessage | yes | response to a question message | | statusQuotedMessage | yes | quote a status with type annotation | | statusStickerInteractionMessage | yes | sticker reaction to a status | | newsletterFollowerInviteMessageV2 | yes | newsletter follow invite | | messageHistoryNotice | yes | history metadata notice | | viewOnceV2 / viewOnceV2Extension wrappers | yes | flag on sendMessage | | ephemeral wrapper flag | yes | wraps any message in ephemeralMessage | | groupStatus wrapper flag | yes | wraps any message in groupStatusMessage | | interactiveAsTemplate flag | yes | wraps interactiveMessage in templateMessage | | secureMetaServiceLabel flag | yes | adds label to contextInfo | | raw flag | yes | pass raw proto structu