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

@systemzero/baileys

v1.1.2

Published

System-zero baileys bot

Readme

@systemzero/baileys

v1.1.2 · Fork avançado do Baileys com suporte nativo a todos os tipos de mensagens do WhatsApp

v1.1.2: corrigida a desconexão da sessão.

NPM License Author Node


Instalação

npm i @systemzero/baileys

Dependências opcionais:

npm i sharp          # conversão de imagem para sticker pack
npm i @napi-rs/image # alternativa ao sharp

Requer ffmpeg instalado no sistema (com suporte a libopus) para conversão automática de áudio PTT. Confirme com ffmpeg -encoders | grep opus.


Índice

  1. Conexão
  2. Mensagens de mídia
  3. Mensagens especiais
  4. Botões e interativos
  5. Sticker Pack nativo
  6. Canais Newsletter
  7. Enquetes com decrypt
  8. AI Rich
  9. LID / JID — Sistema avançado
  10. Username (@usuario)
  11. MessageBuilder — Button, ButtonV2, Carousel, AIRich
  12. Botões estendidos
  13. Grupos
  14. Perfil e privacidade
  15. Eventos do socket
  16. Utilitários
  17. Fixar / Desfixar mensagens
  18. Resolução de nomes (getName)
  19. PTT real com qualquer formato
  20. Guard de pagamento stealth
  21. Bad MAC Handler
  22. Resolução LID → telefone via grupo
  23. Versão do WhatsApp sempre atualizada
  24. WhatsApp Flows — Formulários
  25. Mensagens agendadas
  26. Status em grupo (GroupStatusMessageV2)
  27. Figurinha de Avatar (isAvatar)
  28. Álbum (múltiplas mídias agrupadas)

1. Conexão

Básico

const {
    default: makeWASocket,
    useMultiFileAuthState,
    DisconnectReason,
    Browsers,
    getBestWaVersion
} = require('@systemzero/baileys')

const { state, saveCreds } = await useMultiFileAuthState('./session')
const { version, isLatest, source } = await getBestWaVersion()

const sock = makeWASocket({
    version,
    auth:    state,
    browser: Browsers.ubuntu('Chrome'),
    printQRInTerminal: false,
    logger:  require('pino')({ level: 'silent' }),
    getMessage: async (key) => {
        const msg = await store.loadMessage(key.remoteJid, key.id)
        return msg?.message || undefined
    }
})

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

Use getBestWaVersion() em vez de fetchLatestWaWebVersion() — ela tenta as duas fontes e só aceita resultado se buscou fresco de verdade, sem cair silenciosamente num fallback desatualizado que pode causar erros 403.

Opções recomendadas para produção

const sock = makeWASocket({
    version,
    auth:    state,
    browser: Browsers.ubuntu('Chrome'),
    printQRInTerminal: false,
    logger:  require('pino')({ level: 'silent' }),
    connectTimeoutMs:        60000,
    defaultQueryTimeoutMs:   60000,
    keepAliveIntervalMs:     25000,   // ping periódico pra manter o socket vivo
    syncFullHistory:         false,   // não baixa histórico inteiro (mais leve e rápido)
    shouldSyncHistoryMessage: () => false,
    generateHighQualityLinkPreview: false,
    emitOwnEvents:           false,   // não reprocessa mensagens do próprio bot
    getMessage: async (key) => {
        // IMPORTANTE: retorne msg.message (WAMessage), não o WebMessageInfo inteiro —
        // retornar o objeto completo causa falha silenciosa no processamento de eventos
        const msg = await store.loadMessage(key.remoteJid, key.id)
        return msg?.message || undefined
    }
})

Reconexão robusta (com anti-ban do 405)

O tratamento correto de cada motivo de desconexão é o que evita loops de reconexão e, principalmente, ban por rate limit (405). Reconectar rápido demais ao receber 405 é o principal precursor de ban.

sock.ev.on('connection.update', async (update) => {
    const { connection, lastDisconnect } = update

    if (connection === 'open') {
        console.log('Conectado ✔')
        return
    }

    if (connection === 'close') {
        const code = lastDisconnect?.error?.output?.statusCode

        if (code === DisconnectReason.loggedOut) {
            // sessão encerrada de vez — apague a pasta session e re-pareie
            console.log('Deslogado. Apague a sessão e re-pareie.')
            process.exit()

        } else if (code === DisconnectReason.forbidden || code === 403) {
            // conta revogada pelo WhatsApp
            console.log('Sessão revogada (403). Apague a sessão e re-pareie.')
            process.exit()

        } else if (code === DisconnectReason.connectionReplaced) {
            // outra sessão abriu no mesmo número — encerre pra não entrar em loop
            console.log('Conflito: outra sessão ativa no mesmo número.')
            process.exit()

        } else if (code === 405) {
            // ANTI-BAN: 405 = rate limit. Reconectar rápido leva a BAN.
            // Espere bastante (60s) antes de tentar de novo.
            console.log('[ANTI-BAN] 405 (rate limit). Aguardando 60s...')
            await new Promise(r => setTimeout(r, 60000))
            startBot()

        } else if (code === DisconnectReason.restartRequired) {
            // 515 é normal logo após o pareamento — reconecta imediato
            startBot()

        } else {
            // qualquer outro motivo (conexão perdida, timeout) — espera 5s
            console.log(`Reconectando (motivo ${code})...`)
            await new Promise(r => setTimeout(r, 5000))
            startBot()
        }
    }
})

Watchdog — detecta socket "zumbi"

Às vezes o socket para de receber mensagens mas o connection.update não dispara close (fica num estado morto silencioso). Um watchdog checa o readyState do WebSocket e reinicia se necessário:

let lastMsgTime = Date.now()
sock.ev.on('messages.upsert', () => { lastMsgTime = Date.now() })

const watchdog = setInterval(async () => {
    const inativoSeg = Math.floor((Date.now() - lastMsgTime) / 1000)
    if (inativoSeg < 600) return // menos de 10 min sem msg — ok

    // 1 = OPEN. Qualquer outro estado = socket morto
    if (sock.ws?.readyState !== 1) {
        console.log('[WATCHDOG] Socket morto. Reiniciando...')
        clearInterval(watchdog)
        startBot()
        return
    }

    // socket diz que está aberto — confirma com um ping real
    try {
        await sock.sendPresenceUpdate('available')
        lastMsgTime = Date.now()
    } catch {
        console.log('[WATCHDOG] Ping falhou. Reiniciando...')
        clearInterval(watchdog)
        startBot()
    }
}, 120000) // checa a cada 2 min

Pairing Code

const code = await sock.requestPairingCode('5511999999999')
const code = await sock.requestPairingCode('5511999999999', 'MYBOT001') // personalizado — 8 chars

Peça o pairing code alguns segundos após criar o socket (ex: dentro de um setTimeout de 3s), e só quando !sock.authState.creds.registered.

Store em memória

const store = {
    messages: {},
    bind: (ev) => {
        ev.on('messages.upsert', ({ messages }) => {
            for (const msg of messages) {
                if (!msg.key?.remoteJid) continue
                store.messages[msg.key.remoteJid] ??= {}
                store.messages[msg.key.remoteJid][msg.key.id] = msg
            }
        })
    },
    loadMessage: async (jid, id) => store.messages[jid]?.[id] || null
}
store.bind(sock.ev)

2. Mensagens de mídia

Todos os campos de mídia aceitam Buffer, { url: 'https://...' } ou { stream }.

await sock.sendMessage(jid, { text: 'Olá!' })
await sock.sendMessage(jid, { text: '@5511...!', mentions: ['[email protected]'] })
await sock.sendMessage(jid, { image: { url: 'https://...' }, caption: 'Legenda' })
await sock.sendMessage(jid, { video: buffer, caption: 'Legenda', gifPlayback: false })
await sock.sendMessage(jid, { audio: buffer, mimetype: 'audio/mpeg', ptt: false })
await sock.sendMessage(jid, { audio: buffer, ptt: true }) // PTT — converte automaticamente
await sock.sendMessage(jid, { document: buffer, mimetype: 'application/pdf', fileName: 'doc.pdf' })
await sock.sendMessage(jid, { sticker: buffer })
await sock.sendMessage(jid, {
    album: [{ image: buffer1 }, { image: buffer2 }, { video: bufferVideo }]
}) // veja a seção 28 para o guia completo de álbum
await sock.sendMessage(jid, {
    contacts: {
        displayName: 'João',
        contacts: [{ vcard: 'BEGIN:VCARD\nVERSION:3.0\nFN:João\nTEL:+5511999999999\nEND:VCARD' }]
    }
})
await sock.sendMessage(jid, {
    location: { degreesLatitude: -23.55, degreesLongitude: -46.63, name: 'São Paulo' }
})

3. Mensagens especiais

await sock.sendMessage(jid, { react: { text: '❤️', key: message.key } })
await sock.sendMessage(jid, { delete: message.key })
await sock.sendMessage(jid, { edit: message.key, text: 'Texto editado' })
await sock.sendMessage(jid, { image: buffer, viewOnce: true })
await sock.sendMessage(jid, { image: buffer, viewOnceV2: true })
await sock.sendMessage(jid, { image: buffer, caption: '!', spoiler: true })
await sock.sendMessage(jid, { image: buffer, groupStatus: true })
await sock.sendMessage(jid, { image: buffer, ephemeral: true })
await sock.sendMessage(jid, { sticker: buffer, isLottie: true })
await sock.sendMessage(jid, { text: '@all', mentionAll: true })
await sock.sendMessage(jid, {
    text: 'Confira!',
    externalAdReply: {
        title: 'Título', body: 'Descrição',
        thumbnail: bufferImg, mediaType: 1,
        sourceUrl: 'https://systemzone.store',
        renderLargerThumbnail: true
    }
})
await sock.sendMessage(jid, {
    poll: { name: 'Pergunta?', values: ['A', 'B', 'C'], selectableCount: 1 }
})
const { generateForwardMessageContent, generateWAMessageFromContent } = require('@systemzero/baileys')
const fwd = generateForwardMessageContent(message, false)
const msg = generateWAMessageFromContent(jid, fwd, { quoted: m })
await sock.relayMessage(jid, msg.message, { messageId: msg.key.id })

4. Botões e interativos

const { generateWAMessageFromContent, proto } = require('@systemzero/baileys')

const msg = generateWAMessageFromContent(jid, {
    viewOnceMessage: {
        message: {
            interactiveMessage: proto.Message.InteractiveMessage.create({
                header: { title: 'Título', hasMediaAttachment: false },
                body:   { text: 'Texto' },
                footer: { text: 'Rodapé' },
                nativeFlowMessage: { buttons: [ /* tipos abaixo */ ] }
            })
        }
    }
}, { quoted: m })

await sock.relayMessage(jid, msg.message, { messageId: msg.key.id })
{ name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'OK', id: 'ok' }) }
{ name: 'cta_url',    buttonParamsJson: JSON.stringify({ display_text: 'Abrir', url: 'https://systemzone.store' }) }
{ name: 'cta_copy',   buttonParamsJson: JSON.stringify({ display_text: 'Copiar', copy_code: 'CODIGO' }) }
{ name: 'cta_call',   buttonParamsJson: JSON.stringify({ display_text: 'Ligar', phone_number: '5511999999999' }) }
{
    name: 'single_select',
    buttonParamsJson: JSON.stringify({
        title: 'Escolher',
        sections: [{ title: 'Cat', rows: [{ header: 'Op1', title: 'Opção 1', description: 'Desc', id: 'op1' }] }]
    })
}

Header com imagem + nativeFlow

// Use `caption`, não `text` — `text` faz a lib pular o bloco do header
await sock.sendMessage(jid, {
    image:      { url: 'https://...' },
    caption:    'Texto do card',
    nativeFlow: [{ text: 'Botão', id: 'btn1' }]
})

Carousel

await sock.sendMessage(jid, {
    cards: [
        { image: buffer1, caption: 'Card 1', nativeFlow: [{ text: 'Ver', id: 'c1' }] },
        { image: buffer2, caption: 'Card 2', nativeFlow: [{ text: 'Ver', id: 'c2' }] },
    ],
    footer: 'Rodapé'
})

Template Buttons / Sections

await sock.sendMessage(jid, {
    text: 'Escolha:',
    templateButtons: [
        { text: 'Resposta', id: 'r1' },
        { text: 'Site', url: 'https://systemzone.store' },
        { text: 'Ligar', call: '5511999999999' },
    ]
})

await sock.sendMessage(jid, {
    text: 'Selecione', buttonText: 'Ver opções',
    sections: [{ title: 'Cat', rows: [{ title: 'Item 1', description: 'Desc', id: 'i1' }] }]
})

5. Sticker Pack nativo

await sock.sendMessage(jid, {
    cover:    bufferWebP,
    stickers: [
        { data: bufferWebP,  emojis: ['😂'] },
        { data: bufferAnima, emojis: ['🔥'] },
        { data: bufferPng,   emojis: ['✨'] },
    ],
    name:      'Nome do Pack',
    publisher: 'Autor',
})

cover e cada stickers[].data esperam Buffer. Máximo 60 figurinhas, até 1MB cada. Requer sharp ou @napi-rs/image para PNG.


6. Canais Newsletter

await sock.sendMessage('120363...@newsletter', { text: 'Novidade!' })

const { generateWAMessage, generateMessageIDV2 } = require('@systemzero/baileys')
const fullMsg = await generateWAMessage(canalJid, { image: buffer, caption: 'Legenda' }, {
    upload: sock.waUploadToServer, userJid: sock.user.id,
    messageId: generateMessageIDV2(sock.user.id),
})
await sock.relayMessage(canalJid, fullMsg.message, { messageId: fullMsg.key.id })

const canal = await sock.newsletterCreate('Nome', 'Descrição')
await sock.newsletterFollow(jid)
await sock.newsletterUnfollow(jid)
await sock.newsletterMute(jid)
await sock.newsletterUnmute(jid)
const meta = await sock.newsletterMetadata('jid', jid)
await sock.newsletterUpdateName(jid, 'Novo Nome')
await sock.newsletterUpdateDescription(jid, 'Nova descrição')
await sock.newsletterUpdatePicture(jid, buffer)
await sock.newsletterRemovePicture(jid)
await sock.newsletterReactMessage(jid, serverId, '❤️')
const msgs = await sock.newsletterFetchMessages(jid, 30, 0, 0)
await sock.newsletterDelete(jid)
const { subscribers } = await sock.newsletterSubscribers(jid)

Reencaminhar qualquer mídia marcada para um canal

Baixa a mídia de uma mensagem citada (texto, imagem, vídeo, áudio, sticker ou documento) e reenvia para o canal. Use relayMessage — é o único método que passa pelo encodeNewsletterMessage com os extraAttrs corretos (o mediatype no plaintext); sendMessage direto não faz isso para mídia em canal.

const {
    downloadContentFromMessage,
    generateWAMessage,
    generateMessageIDV2,
} = require('@systemzero/baileys')

const canalJid = '120363...@newsletter'
const q        = quotedMsg          // a mensagem citada
const raw      = q.msg || {}
const type     = q.mtype || ''
const mime     = raw.mimetype || ''
const msgText  = q.text || raw.caption || ''

// ── TEXTO ────────────────────────────────────────────────────────────────
if (type === 'conversation' || type === 'extendedTextMessage' || (!mime && !raw.mediaKey && msgText)) {
    await sock.sendMessage(canalJid, { text: msgText || '.' })
    return
}

// ── mapeia o tipo de mídia ───────────────────────────────────────────────
const MEDIA_TYPE_MAP = {
    stickerMessage:  'sticker',
    imageMessage:    'image',
    videoMessage:    'video',
    audioMessage:    'audio',
    documentMessage: 'document',
}
const mediaType = MEDIA_TYPE_MAP[type] || (
    mime.startsWith('image/') ? 'image' :
    mime.startsWith('video/') ? 'video' :
    mime.startsWith('audio/') ? 'audio' : 'document'
)

// ── baixa a mídia ────────────────────────────────────────────────────────
const stream = await downloadContentFromMessage(
    { mediaKey: raw.mediaKey, directPath: raw.directPath, url: raw.url },
    mediaType
)
const chunks = []
for await (const chunk of stream) chunks.push(chunk)
const buffer = Buffer.concat(chunks)

// ── monta o content conforme o tipo ──────────────────────────────────────
let content
if (mediaType === 'sticker')  content = { sticker: buffer }
else if (mediaType === 'image') content = { image: buffer, caption: msgText }
else if (mediaType === 'video') content = { video: buffer, caption: msgText, gifPlayback: raw.gifPlayback || false }
else if (mediaType === 'audio') content = { audio: buffer, mimetype: mime || 'audio/mp4', ptt: raw.ptt || false }
else content = { document: buffer, mimetype: mime || 'application/octet-stream', fileName: raw.fileName || 'arquivo', caption: msgText }

// ── gera a mensagem (faz upload) e envia via relayMessage ────────────────
const fullMsg = await generateWAMessage(canalJid, content, {
    upload:    sock.waUploadToServer,
    userJid:   sock.user.id,
    messageId: generateMessageIDV2(sock.user.id),
})

await sock.relayMessage(canalJid, fullMsg.message, { messageId: fullMsg.key.id })

7. Enquetes com decrypt

await sock.sendMessage(jid, {
    poll: { name: 'Pergunta?', values: ['A', 'B', 'C'], selectableCount: 1 }
})

const { getAggregateVotesInPollMessage } = require('@systemzero/baileys/lib/Utils/messages.js')
sock.ev.on('messages.update', async (updates) => {
    for (const { key, update } of updates) {
        if (!update.pollUpdates) continue
        const pollMsg = await store.loadMessage(key.remoteJid, key.id)
        if (!pollMsg?.message) continue
        const result = getAggregateVotesInPollMessage({
            message: pollMsg.message, pollUpdates: update.pollUpdates
        })
        // [{ name: 'A', voters: ['[email protected]'] }]
    }
})

8. AI Rich

await sock.sendRich(jid, [
    sock.makeText('Acesse [nosso site](https://systemzone.store).'),
    sock.makeCode('bash', 'npm i @systemzero/baileys'),
    sock.makeTable([['Nome', 'Status'], ['Botões', '✅']]),
    sock.makeList(['Item 1', 'Item 2']),
], quotedMsg, ['RICH_RESPONSE_CODE', 'RICH_RESPONSE_TABLE'])

await sock.sendRichText(jid, 'Texto com [link](https://systemzone.store)', quotedMsg)
await sock.sendRichCode(jid, 'Título', 'javascript', 'const x = 1', quotedMsg)
await sock.sendRichList(jid, 'Lista', ['A', 'B'], quotedMsg)

9. LID / JID — Sistema avançado

O WhatsApp usa dois tipos de identificador:

  • JID (@s.whatsapp.net) — baseado em número de telefone
  • LID (@lid) — identificador opaco sem número

A partir da v1.0.6, a lib resolve automaticamente LID↔JID via sharedLidPhoneCache, inclusive ao buscar metadata de grupo (groupMetadata).

const {
    lidToJid, resolveJid, resolveAll, normalizeJid, validateJid,
    getSenderInfo, sharedLidPhoneCache, isLidUser, isPnUser,
    getBotJid, setBotMap
} = require('@systemzero/baileys')

const jid = lidToJid('123456@lid')
const { jid, lid } = resolveAll('[email protected]')
const { jid, lid, isGroup } = getSenderInfo(message)

normalizeJid('5511999999999')  // → '[email protected]'
normalizeJid('123456@lid')     // → resolve via cache

sharedLidPhoneCache.set('123456@lid', '[email protected]')
const jid = sharedLidPhoneCache.getPhoneForLid('123456@lid')
const lid = sharedLidPhoneCache.getLidForPhone('[email protected]')
console.log('Cache size:', sharedLidPhoneCache.size)

⚠️ Identidades do próprio bot (sock.user.id/.lid) vêm com sufixo de dispositivo (numero:N@dominio). Pra comparar com participants, remova só o :N preservando o domínio: jid.replace(/:\d+(?=@)/, '')não use split(':')[0].


10. Username (@usuario)

const { resolveUsername, isUsername } = require('@systemzero/baileys')

isUsername('@josue')   // true
const jid = await resolveUsername('@josue', sock.onWhatsApp.bind(sock))
if (jid) await sock.sendMessage(jid, { text: 'Olá!' })

11. MessageBuilder — Button, ButtonV2, Carousel, AIRich

const { Button, ButtonV2, Carousel, AIRich } = require('@systemzero/baileys/lib/MB.cjs')
// O socket vai sempre no construtor: new ButtonV2(sock)

ButtonV2

const msg = new ButtonV2(sock)
msg.setTitle('Título'); msg.setBody('Corpo'); msg.setFooter('Rodapé')
msg.setThumbnail('https://exemplo.com/imagem.jpg')
msg.addButton('✅ Opção 1', 'opcao_1')
msg.addButton('❌ Opção 2', 'opcao_2')
await msg.send(jid, { quoted: m })
// clique chega como m.body === 'opcao_1'

Button (native flow)

const msg = new Button(sock)
msg.addReply('✅ Confirmar', 'confirmar')
msg.addUrl('🔗 Abrir', 'https://systemzone.store')
msg.addCopy('📋 Copiar', 'PROMO2025')
msg.addCall('📞 Ligar', '5511999999999')
await msg.send(jid, { quoted: m })

Carousel

const msg = new Carousel(sock)
msg.setBody('Escolha:')
msg.card(c => c.image('https://...').title('Card 1').text('Desc').button('Ver', 'c1'))
msg.card(c => c.image('https://...').title('Card 2').text('Desc').button('Ver', 'c2'))
await msg.send(jid, { quoted: m })

AIRich

const msg = new AIRich(sock)
msg.addText('Acesse [System Zero](https://systemzone.store).')
msg.addCode('javascript', `require('@systemzero/baileys')`)
msg.addTable([['Comando', 'Desc'], ['!menu', 'Menu principal']])
await msg.send(jid, { quoted: m })

12. Botões estendidos

await sock.sendMessage(jid, { text: 'Mensagem', nativeFlow: [ /* botões */ ] })

{ text: '⏰ Me lembre', reminder: 'lembrete_id' }
{ text: '🔕 Cancelar', cancelReminder: 'lembrete_id' }
{ text: '📍 Endereço', address: true }
{ text: '📡 Localização', location: true }
{ text: '🛍️ Catálogo', catalog: '[email protected]' }
{ text: 'Copiar código', otp: '123456' }
{ text: '📞 Ligar', phoneNumber: '5511999999999' }
{ text: '🗑️ Limpar chat', clearChat: true }
{ text: '🔗 Abrir', urlBtn: 'https://systemzone.store' }

isSystemNotification

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

13. Grupos

const meta  = await sock.groupMetadata(jid)
const grupo = await sock.groupCreate('Nome', ['[email protected]'])
await sock.groupParticipantsUpdate(jid, ['[email protected]'], 'add')
await sock.groupParticipantsUpdate(jid, ['[email protected]'], 'remove')
await sock.groupParticipantsUpdate(jid, ['[email protected]'], 'promote')
await sock.groupParticipantsUpdate(jid, ['[email protected]'], 'demote')
await sock.groupLeave(jid)
await sock.groupUpdateSubject(jid, 'Novo Nome')
await sock.groupUpdateDescription(jid, 'Nova descrição')
const code = await sock.groupInviteCode(jid)
await sock.groupRevokeInvite(jid)
await sock.groupAcceptInvite('CODIGO')
await sock.groupToggleEphemeral(jid, 86400)
const grupos = await sock.groupFetchAllParticipating()

14. Perfil e privacidade

const url = await sock.profilePictureUrl(jid, 'image')
await sock.updateProfilePicture(jid, buffer)
await sock.updateProfileStatus('🤖 Bot ativo')
const [result] = await sock.onWhatsApp('5511999999999')
await sock.updateBlockStatus(jid, 'block')
await sock.updateBlockStatus(jid, 'unblock')
await sock.sendPresenceUpdate('composing', jid)
await sock.sendPresenceUpdate('available', jid)
await sock.presenceSubscribe(jid)
await sock.readMessages([message.key])
await sock.chatModify({ archive: true }, jid)
await sock.chatModify({ pin: true }, jid)
await sock.chatModify({ mute: 8 * 60 * 60 * 1000 }, jid)
await sock.logout()

15. Eventos do socket

sock.ev.on('messages.upsert',          ({ messages, type }) => {})
sock.ev.on('messages.update',           (updates) => {})
sock.ev.on('messages.delete',           (item) => {})
sock.ev.on('messages.reaction',         (reactions) => {})
sock.ev.on('messages.decrypt-failed',   (failInfo) => {}) // ver seção 20
sock.ev.on('presence.update',           ({ id, presences }) => {})
sock.ev.on('groups.update',             (updates) => {})
sock.ev.on('group-participants.update', ({ id, participants, action }) => {})
sock.ev.on('contacts.update',           (contacts) => {})
sock.ev.on('connection.update',         ({ connection, lastDisconnect, qr }) => {})
sock.ev.on('creds.update',              saveCreds)
sock.ev.on('call', (calls) => {
    for (const call of calls) {
        if (call.status === 'offer') sock.rejectCall(call.id, call.from)
    }
})

16. Utilitários

const { downloadMediaMessage } = require('@systemzero/baileys')
const buffer = await downloadMediaMessage(message, 'buffer', {})

const { generateWAMessage, generateWAMessageFromContent, generateMessageIDV2 } = require('@systemzero/baileys')
const { jidNormalizedUser, isJidGroup, isJidNewsletter, isLidUser, isPnUser } = require('@systemzero/baileys')

// Detectar dispositivo — analisa o ID DA MENSAGEM, não o JID
const { getDevice } = require('@systemzero/baileys')
const device = getDevice(message.key.id) // 'android' | 'ios' | 'web' | 'unknown'

17. Fixar / Desfixar mensagens

await sock.sendMessage(jid, { pin: quotedMsg.key, type: 1 }) // PIN_FOR_ALL
await sock.sendMessage(jid, { pin: quotedMsg.key, type: 2 }) // UNPIN_FOR_ALL

18. Resolução de nomes (getName)

const { getName } = require('@systemzero/baileys')
const nome = getName(msg, contactStore)
// Prioridade: contato salvo → verifiedName → pushName → @lid → telefone → "Usuário desconhecido"

19. PTT real com qualquer formato

// Qualquer formato de entrada — a lib converte via ffmpeg automaticamente
await sock.sendMessage(jid, { audio: { url: 'https://musica.mp3' }, ptt: true })

A lib detecta a extensão real da fonte (não o mimetype declarado) e converte pra mono/16kHz opus quando necessário. Requer ffmpeg com libopus.


20. Guard de pagamento stealth (anti-fraude)

const { bindPaymentGuard } = require('@systemzero/baileys')

bindPaymentGuard(sock, {
    isPaymentMessage: (webMessage) => { /* sua lógica */ },
    recordEnvelope:  (webMessage, isPayment) => { /* seu registro */ },
    treatDecryptFailureAsSuspicious: true,
    onDetect: (detection) => {
        // detection.type: 'direct' | 'edited' | 'undecryptable'
    }
})

| Caminho | Evento | Garantia | |---|---|---| | Mensagem direta | messages.upsert | Roda detector no conteúdo | | Mensagem editada | messages.update | Extrai editedMessage e roda detector | | Falha de decrypt | messages.decrypt-failed | Nunca ignora silenciosamente |


21. Bad MAC Handler — sessões Signal

const { badMacHandler } = require('@systemzero/baileys')

if (badMacHandler.isBadMacError(error)) {
    badMacHandler.handleError(error, 'contexto')
}
badMacHandler.clearProblematicSessionFiles() // preserva creds.json

22. Resolução LID → telefone via grupo

const { resolveLidPhoneFromGroup } = require('@systemzero/baileys')
const telefone = await resolveLidPhoneFromGroup(sock, groupJid, lid)

Força resolução de @lid → telefone via groupMetadata — útil quando o cache ainda não tem aquele par.


23. Versão do WhatsApp sempre atualizada

const { getBestWaVersion } = require('@systemzero/baileys')
const { version, isLatest, source } = await getBestWaVersion()
// tenta web.whatsapp.com → GitHub → fallback fixo (avisando explicitamente)

24. WhatsApp Flows — Formulários interativos

Requer flow_id aprovado no Meta Business Manager.

const { generateWAMessageFromContent } = require('@systemzero/baileys')

const formMsg = generateWAMessageFromContent(jid, {
    viewOnceMessage: {
        message: {
            messageContextInfo: { deviceListMetadata: {}, deviceListMetadataVersion: 2 },
            interactiveMessage: {
                body: { text: 'Preencha seus dados.' },
                nativeFlowMessage: {
                    buttons: [{
                        name: 'galaxy_message',
                        buttonParamsJson: JSON.stringify({
                            flow_message_version: '4',
                            flow_id: 'SEU_FLOW_ID',
                            flow_action_payload: {
                                screen: 'contact_details',
                                data: { full_name_visible: true, email_visible: true }
                            },
                            flow_cta: '__localize:FLOWS_SIGN_UP_BUTTON_TITLE',
                            flow_action: 'navigate',
                            flow_token: 'T0ZGRVJfU0lHTlVQ'
                        })
                    }],
                    messageParamsJson: '{}'
                }
            }
        }
    }
}, { userJid: sock.user.id })

await sock.relayMessage(jid, formMsg.message, { messageId: formMsg.key.id })

Recebendo a resposta

sock.ev.on('messages.upsert', async ({ messages }) => {
    const m = messages[0]
    const nfr = m.message?.interactiveResponseMessage?.nativeFlowResponseMessage
    if (nfr?.name !== 'galaxy_message' || !nfr?.paramsJson) return

    const parsed   = JSON.parse(nfr.paramsJson)
    if (!parsed.wa_flow_response_params) return

    const flowId  = parsed.wa_flow_response_params.flow_id
    const screens = JSON.parse(parsed.wa_flow_response_params.response_message).screens || []

    const campos = {}
    for (const screen of screens) {
        for (const comp of (screen.components || [])) {
            if (comp.name && comp.value !== undefined && comp.value !== '')
                campos[comp.name] = comp.value
        }
    }

    if (flowId === 'SEU_FLOW_ID') {
        let resposta = 'Formulário recebido!\n\n'
        if (campos.full_name) resposta += `Nome: ${campos.full_name}\n`
        if (campos.email)     resposta += `Email: ${campos.email}\n`
        await sock.sendMessage(m.key.remoteJid, { text: resposta }, { quoted: m })
    }
})

25. Mensagens agendadas

Sistema de agendamento em memória. Auto-inicia o timer quando há mensagens na fila, auto-para quando a fila esvazia.

const { createMessageScheduler } = require('@systemzero/baileys')

const scheduler = createMessageScheduler(
    (jid, content) => sock.sendMessage(jid, content),
    {
        onSent:   (s, msg) => console.log(`Enviado pra ${s.jid} — ID: ${s.id}`),
        onFailed: (s, err) => console.error(`Falhou: ${err.message}`)
    }
)

// Agendar para data/hora específica
const entry = scheduler.schedule(
    '[email protected]',
    { text: 'Feliz Aniversário! 🎂' },
    new Date('2026-12-25T09:00:00')
)
console.log('Agendado com ID:', entry.id)

// Agendar com delay (30 minutos)
scheduler.scheduleDelay(jid, { text: 'Lembrete!' }, 30 * 60 * 1000)

// Agendar qualquer tipo de mensagem
scheduler.schedule(groupJid, {
    image: { url: './promo.jpg' },
    caption: 'Promoção de fim de semana!'
}, new Date('2026-12-20T08:00:00'))

// Cancelar um agendamento
scheduler.cancel(entry.id)

// Cancelar todos de um JID
scheduler.cancelForJid(jid)

// Ver pendentes
const pendentes = scheduler.getPending()

// Parar/retomar
scheduler.stop()
scheduler.start()

// Limpar tudo
scheduler.clearAll()

Objeto retornado:

{
    id:            'sched_1782000000000_a3f2b1',
    jid:           '[email protected]',
    content:       { text: '...' },
    scheduledTime: Date,
    createdAt:     Date,
    status:        'pending' | 'sent' | 'failed' | 'cancelled',
    messageId:     '...',  // preenchido após envio
    error:         '...'   // preenchido em caso de falha
}

26. Status em grupo (GroupStatusMessageV2)

Posta conteúdo como status dentro de um grupo — texto, imagem, vídeo ou áudio. Suporta modo Close Friends (apenas amigos próximos), usando o mesmo mecanismo do status nativo do WhatsApp.

const { generateWAMessageFromContent, prepareWAMessageMedia, downloadMediaMessage } = require('@systemzero/baileys')
const crypto = require('crypto')

// ── Texto para todos do grupo ────────────────────────────────────────────────
const messageSecret = crypto.randomBytes(32)

const msg = generateWAMessageFromContent(jid, {
    messageContextInfo: { messageSecret },
    groupStatusMessageV2: {
        message: {
            extendedTextMessage: {
                text: 'Olá, grupo! 👋',
                contextInfo: { isGroupStatus: true }
            },
            messageContextInfo: { messageSecret }
        }
    }
}, {})

await sock.relayMessage(jid, msg.message, { messageId: msg.key.id })
// ── Texto apenas para Amigos Próximos (Close Friends) ────────────────────────
const messageSecret = crypto.randomBytes(32)

const msg = generateWAMessageFromContent(jid, {
    messageContextInfo: { messageSecret },
    groupStatusMessageV2: {
        message: {
            extendedTextMessage: {
                text: 'Só pra você que é chegado 🤫',
                contextInfo: {
                    isGroupStatus: true,
                    statusAudienceMetadata: { audienceType: 1 } // 1 = Close Friends
                }
            },
            messageContextInfo: { messageSecret }
        }
    }
}, {})

await sock.relayMessage(jid, msg.message, { messageId: msg.key.id })
// ── Imagem para todos do grupo ───────────────────────────────────────────────
const messageSecret = crypto.randomBytes(32)

const prep = await prepareWAMessageMedia(
    { image: buffer },
    { upload: sock.waUploadToServer }
)

const msg = generateWAMessageFromContent(jid, {
    messageContextInfo: { messageSecret },
    groupStatusMessageV2: {
        message: {
            imageMessage: {
                ...prep.imageMessage,
                caption:    'Legenda da imagem',
                contextInfo: { isGroupStatus: true }
            },
            messageContextInfo: { messageSecret }
        }
    }
}, {})

await sock.relayMessage(jid, msg.message, { messageId: msg.key.id })
// ── Imagem apenas para Amigos Próximos ───────────────────────────────────────
const messageSecret = crypto.randomBytes(32)

const prep = await prepareWAMessageMedia(
    { image: buffer },
    { upload: sock.waUploadToServer }
)

const msg = generateWAMessageFromContent(jid, {
    messageContextInfo: { messageSecret },
    groupStatusMessageV2: {
        message: {
            imageMessage: {
                ...prep.imageMessage,
                caption:    'Só pra chegados 🤫',
                contextInfo: {
                    isGroupStatus: true,
                    statusAudienceMetadata: { audienceType: 1 }
                }
            },
            messageContextInfo: { messageSecret }
        }
    }
}, {})

await sock.relayMessage(jid, msg.message, { messageId: msg.key.id })
// ── Vídeo ─────────────────────────────────────────────────────────────────────
const messageSecret = crypto.randomBytes(32)

const prep = await prepareWAMessageMedia(
    { video: buffer },
    { upload: sock.waUploadToServer }
)

const msg = generateWAMessageFromContent(jid, {
    messageContextInfo: { messageSecret },
    groupStatusMessageV2: {
        message: {
            videoMessage: {
                ...prep.videoMessage,
                caption:    'Legenda do vídeo',
                contextInfo: { isGroupStatus: true }
            },
            messageContextInfo: { messageSecret }
        }
    }
}, {})

await sock.relayMessage(jid, msg.message, { messageId: msg.key.id })
// ── Áudio ─────────────────────────────────────────────────────────────────────
const messageSecret = crypto.randomBytes(32)

const prep = await prepareWAMessageMedia(
    { audio: buffer, mimetype: 'audio/mp4' },
    { upload: sock.waUploadToServer }
)

const msg = generateWAMessageFromContent(jid, {
    messageContextInfo: { messageSecret },
    groupStatusMessageV2: {
        message: {
            audioMessage: {
                ...prep.audioMessage,
                contextInfo: { isGroupStatus: true }
            },
            messageContextInfo: { messageSecret }
        }
    }
}, {})

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

Pontos importantes:

  • messageSecret deve ser um Buffer de 32 bytes gerado com crypto.randomBytes(32) — cada postagem precisa de um novo secret
  • audienceType: 1 ativa o modo Close Friends; omitir statusAudienceMetadata posta para todos do grupo
  • Para repostar uma mensagem citada (quoted), baixe a mídia com downloadMediaMessage antes de fazer o prepareWAMessageMedia
  • O groupStatus: true no sendMessage simples (seção 3) existe como atalho, mas para controle de Close Friends e repost de mídias, use o groupStatusMessageV2 direto como mostrado acima

27. Figurinha de Avatar (isAvatar)

A partir da v1.0.8, a lib suporta o envio de figurinhas com metadata de avatar do WhatsApp — o tipo que carrega os campos is-avatar-sticker, avatar-sticker-style, etc. Basta passar isAvatar: true no sendMessage.

O EXIF de avatar é injetado diretamente nos bytes do container WebP (RIFF), sem reprocessar a imagem (a animação é preservada) e sem depender de bibliotecas externas como node-webpmux.

// envio básico — usa o metadata de avatar padrão
await sock.sendMessage(jid, {
    sticker:  buffer,     // Buffer do webp
    isAvatar: true
})

Customizar nome do pack e autor

await sock.sendMessage(jid, {
    sticker:         buffer,
    isAvatar:        true,
    stickerPackName: 'System Zero',   // opcional — default: 'Hzx'
    stickerAuthor:   'Hzx',           // opcional — default: 'Hzx'
    stickerPackId:   'systemzone'     // opcional — default gerado
})

Metadata injetado

Quando isAvatar: true, a lib injeta o EXIF de avatar no webp (os campos de nome/autor/id são substituídos pelos que você passar):

{
  "sticker-pack-name": "Hzx",
  "sticker-pack-publisher": "Hzx",
  "is-avatar-sticker": 1,
  "avatar-sticker-template-id": "whatsapp",
  "is-ai-sticker": 1,
  "is-avatar-country-sticker": 1,
  "is-avatar-instant-sticker": 1,
  "sticker-maker-source-type": 4,
  "is-avatar-social-sticker": 1,
  "avatar-sticker-style": "whatsapp",
  "avatar-sticker-revision-id": "2026",
  "is-from-user-created-pack": 1,
  "origin-pack-id": "whatsapp",
  "is-text-sticker": 1
}

Exemplo real — roubar figurinha e reenviar como avatar

const { downloadContentFromMessage } = require('@systemzero/baileys')

const stream = await downloadContentFromMessage(stickerMsg, 'sticker')
let buffer = Buffer.from([])
for await (const chunk of stream) buffer = Buffer.concat([buffer, chunk])

await sock.sendMessage(jid, {
    sticker:         buffer,
    isAvatar:        true,
    stickerPackName: 'Roubada',
    stickerAuthor:   'Hzx',
}, { quoted: m })

Notas:

  • Se já existir um chunk EXIF no webp original, ele é removido antes de injetar o novo (não duplica).
  • Funciona com figurinhas estáticas e animadas — os frames não são tocados, só o metadata.
  • Diferente de isLottie, o isAvatar envia um stickerMessage webp normal (não um lottieStickerMessage), que é o formato que renderiza corretamente no envio.

28. Álbum (múltiplas mídias agrupadas)

Envia várias imagens e/ou vídeos agrupados num único bloco visual (carrossel de mídia), como o WhatsApp faz quando você seleciona várias fotos de uma vez.

Uso simples com album

A forma recomendada. Passe um array de itens { image } ou { video } — a lib conta os tipos e monta o albumMessage sozinha.

await sock.sendMessage(jid, {
    album: [
        { image: buffer1 },
        { image: { url: 'https://.../foto2.jpg' } },
        { video: bufferVideo },
    ]
}, { quoted: m })

Regras:

  • Mínimo de 2 itens (imagem + imagem, imagem + vídeo, etc). Menos que isso lança erro.
  • Cada item aceita Buffer, { url } ou { stream }, igual às mídias normais.
  • Imagens e vídeos podem ser misturados livremente no mesmo álbum.

Álbum com legenda

O álbum em si não tem campo de legenda. Para acompanhar de um texto, envie a legenda como mensagem separada logo em seguida:

await sock.sendMessage(jid, {
    album: [{ image: img1 }, { image: img2 }]
}, { quoted: m })

await sock.sendMessage(jid, { text: legenda }, { quoted: m })

Álbum a partir de URLs remotas

Útil quando a mídia vem de uma API (galeria, slides, carrossel):

const urls = ['https://.../1.jpg', 'https://.../2.jpg', 'https://.../3.jpg']

await sock.sendMessage(jid, {
    album: urls.map(url => ({ image: { url } }))
}, { quoted: m })

Para uma lista que pode conter imagens e vídeos, decida o tipo por item:

const itens = midias.map(m2 => {
    const ehVideo = /\.mp4($|\?)/i.test(m2.url)
    return ehVideo ? { video: { url: m2.url } } : { image: { url: m2.url } }
})

await sock.sendMessage(jid, { album: itens }, { quoted: m })

Controle manual com albumMessage

Se precisar montar o proto manualmente (por exemplo, mídia já preparada com prepareWAMessageMedia), pode passar albumMessage como array diretamente. Nesse modo você controla cada item, mas perde a contagem automática de tipos que o album faz.

await sock.sendMessage(jid, {
    albumMessage: [
        { image: { url: url1 } },
        { image: { url: url2 } },
    ]
}, { quoted: m })

Prefira album sempre que possível — ele valida o mínimo de 2 itens e preenche expectedImageCount / expectedVideoCount corretamente, o que faz o carrossel renderizar direito em todos os clientes.


Changelog

v1.1.1

  • Limite de upload aumentado pra 1GB — envio de documentos/mídia agora suporta arquivos de até 1GB por padrão (igual ao limite de documento do WhatsApp oficial), configurável via options.maxContentLength no socket. Antes, uploads locais (arquivo/buffer) não tinham nenhuma checagem de tamanho consistente; agora a validação vale pra todos os tipos de origem (arquivo, buffer, stream e URL remota), com erro claro (413) em vez de falha silenciosa/travamento em arquivos grandes demais.
  • FATAL_DISCONNECT_REASONS exportado — novo array (loggedOut, badSession, multideviceMismatch, forbidden) com os motivos de connection.update (close) que significam sessão realmente inválida. connectionReplaced (440) fica de fora de propósito: só significa que outro socket assumiu no lugar desse, as credenciais continuam válidas. Quem escreve a própria lógica de reconexão pode usar isso pra decidir quando limpar a sessão salva vs. só tentar reconectar — sem essa distinção é fácil acabar apagando um pareamento válido por causa de um 440 e ter que parear tudo de novo. Não muda nenhum comportamento do makeWASocket — é só a constante, sem reconexão automática embutida.

v1.1.0

  • Consolida os fixes das v1.0.8/v1.0.9 numa release minor estável: figurinha de avatar (isAvatar), waveform (spectral) via ffmpeg em todos os formatos, fixes de pareamento (getCompanionPlatformId + pushName), anti-ban 405 e libsignal via npm (fim do erro de instalação EALLOWGIT).

v1.0.9

  • Fix instalação (EALLOWGIT) — a dependência libsignal era referenciada via git (git+https://github.com/...), o que quebrava o npm install em ambientes que bloqueiam fetch de pacotes git (Pterodactyl, CI, VPS sem git). Agora aponta para a versão publicada no npm (libsignal@^6.0.0). Ao atualizar, apague node_modules e package-lock.json e reinstale.

v1.0.8

  • Figurinha de Avatar (isAvatar) — envio de figurinha com metadata de avatar do WhatsApp; EXIF injetado direto nos bytes do WebP (RIFF), sem reprocessar a imagem e sem depender de node-webpmux
  • Fix waveform (spectral) — o waveform (ondinhas) do áudio saía como linha reta porque o audio-decode não decodifica opus/ogg; agora extrai os samples PCM via ffmpeg, funcionando em qualquer formato; gerado para todos os áudios, não só ptt
  • Fix conexão / pareamentogetCompanionPlatformId (enum CompanionWebClientType correto) no lugar do getPlatformId genérico, que causava rejeição de pareamento; pushName adicionado ao payload de login
  • Anti-ban 405DisconnectReason.rateLimited (405) adicionado; handler loga o rate limit para o reconnect esperar antes de tentar de novo

v1.0.7

  • Fix PTT realptt: true converte via ffmpeg antes do upload; corrigido bug Cannot use 'in' operator to search for 'stream' (caminho do arquivo transcrito era passado como string crua em vez de { url })
  • Fix 403 / conexãolidDbMigrated no login agora reflete o estado real das credenciais; companion version usa a versão real do WA; "forbidden" mapeado corretamente pra DisconnectReason.forbidden
  • Mensagens agendadasMessageScheduler / createMessageScheduler portado de innovatorssoft/baileys
  • PreKey recovery melhorado — upload de 30 chaves (era 5), delay de 2500ms (era 1000ms), parâmetro force pra ignorar MIN_UPLOAD_INTERVAL em recuperação de erro
  • Fix groupMetadata → sharedLidPhoneCache — pares LID↔telefone agora registrados automaticamente ao buscar metadata de grupo
  • Guard de pagamento stealth, Bad MAC Handler, getName, getBestWaVersion, resolveLidPhoneFromGroup, WhatsApp Flows

v1.0.6

  • Sistema LID/JID avançado com cache bidirecional (sharedLidPhoneCache)
  • lidToJid, resolveJid, resolveAll, normalizeJid, validateJid, getSenderInfo
  • Suporte a @usernameisUsername, resolveUsername
  • Botões estendidos (20+ tipos via nativeFlow)
  • Fix canal newsletter, fix groupStatus audio, isSystemNotification

v1.0.5

  • Sticker Pack nativo com animadas e PNG
  • Album, Spoiler, ViewOnce V2, Ephemeral, Lottie, Evento
  • Native Flow, Carousel, Template Buttons
  • AI Rich (makeText, makeCode, makeTable, makeList, sendRich)
  • Poll decrypt com getAggregateVotesInPollMessage
  • MessageBuilder (Button, ButtonV2, Carousel, AIRich)

@systemzero/baileys v1.1.1

Desenvolvido por Josué </> · Canal WhatsApp · systemzone.store