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.0.6

Published

System-zero baileys bot

Readme

@systemzero/baileys

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

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 e para o sistema anti-fraude de pagamento processar mídia. 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 (v1.0.6)
  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 de entrada
  20. Guard de pagamento stealth (anti-fraude)
  21. Bad MAC Handler — sessões Signal
  22. Resolução LID → telefone via grupo
  23. Versão do WhatsApp sempre atualizada
  24. WhatsApp Flows — Formulários interativos

1. Conexão

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

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

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)

sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
    if (connection === 'close') {
        const code = lastDisconnect?.error?.output?.statusCode
        if (code !== DisconnectReason.loggedOut) startBot()
    }
})

Veja a seção 23 para um jeito mais robusto de buscar a versão, que não falha silenciosamente.

Pairing Code

// Automático
const code = await sock.requestPairingCode('5511999999999')

// Personalizado — exatamente 8 caracteres
const code = await sock.requestPairingCode('5511999999999', 'MYBOT001')

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 }.

// Texto
await sock.sendMessage(jid, { text: 'Olá!' })

// Texto com menção
await sock.sendMessage(jid, {
    text:     '@5511...!',
    mentions: ['[email protected]']
})

// Imagem
await sock.sendMessage(jid, { image: { url: 'https://...' }, caption: 'Legenda' })

// Vídeo
await sock.sendMessage(jid, { video: buffer, caption: 'Legenda', gifPlayback: false })

// Áudio normal (música)
await sock.sendMessage(jid, { audio: buffer, mimetype: 'audio/mpeg', ptt: false })

// Nota de voz (PTT) — ver seção 19 para conversão automática de qualquer formato
await sock.sendMessage(jid, { audio: buffer, ptt: true })

// Documento
await sock.sendMessage(jid, { document: buffer, mimetype: 'application/pdf', fileName: 'doc.pdf' })

// Figurinha
await sock.sendMessage(jid, { sticker: buffer })

// Album (mínimo 2 itens)
await sock.sendMessage(jid, {
    album: [{ image: buffer1 }, { image: buffer2 }, { video: bufferVideo }]
})

// Contato
await sock.sendMessage(jid, {
    contacts: {
        displayName: 'João',
        contacts: [{ vcard: 'BEGIN:VCARD\nVERSION:3.0\nFN:João\nTEL:+5511999999999\nEND:VCARD' }]
    }
})

// Localização
await sock.sendMessage(jid, {
    location: { degreesLatitude: -23.55, degreesLongitude: -46.63, name: 'São Paulo' }
})

3. Mensagens especiais

// Reação
await sock.sendMessage(jid, { react: { text: '❤️', key: message.key } })

// Deletar
await sock.sendMessage(jid, { delete: message.key })

// Editar
await sock.sendMessage(jid, { edit: message.key, text: 'Texto editado' })

// Fixar — ver seção 17 para a versão completa
await sock.sendMessage(jid, { pin: message.key, type: 1 })

// View Once
await sock.sendMessage(jid, { image: buffer, viewOnce: true })
await sock.sendMessage(jid, { image: buffer, viewOnceV2: true })

// Spoiler
await sock.sendMessage(jid, { image: buffer, caption: '!', spoiler: true })

// Status de grupo
await sock.sendMessage(jid, { image: buffer, groupStatus: true })

// Efêmera
await sock.sendMessage(jid, { image: buffer, ephemeral: true })

// Lottie sticker
await sock.sendMessage(jid, { sticker: buffer, isLottie: true })

// Mention All
await sock.sendMessage(jid, { text: '@all', mentionAll: true })

// External Ad Reply
await sock.sendMessage(jid, {
    text: 'Confira!',
    externalAdReply: {
        title: 'Título', body: 'Descrição',
        thumbnail: bufferImg, mediaType: 1,
        sourceUrl: 'https://systemzone.store',
        renderLargerThumbnail: true
    }
})

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

// Encaminhar
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 })

⚠️ Removido desta versão do README: preview de link customizado (linkPreview manual) e áudio embutido em rodapé de card interativo (audioFooter). Os dois existem no proto do WhatsApp, mas em testes reais (Android e iOS) não renderizaram corretamente — o audioFooter não mostrou o player de áudio, e o linkPreview manual caiu num formato de card genérico em vez do esperado. Não são recursos confiáveis hoje.


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 })

Tipos de botão (testados e funcionais)

// Resposta rápida
{ name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'OK', id: 'ok' }) }

// Link
{ name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: 'Abrir', url: 'https://systemzone.store' }) }

// Copiar
{ name: 'cta_copy', buttonParamsJson: JSON.stringify({ display_text: 'Copiar', copy_code: 'CODIGO' }) }

// Ligar
{ name: 'cta_call', buttonParamsJson: JSON.stringify({ display_text: 'Ligar', phone_number: '5511999999999' }) }

// Lista dropdown
{
    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 (funciona — confirmado em teste real)

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

// Forma simplificada e já testada: usar `image` + `caption` direto no sendMessage,
// junto com nativeFlow — a lib monta o header automaticamente.
await sock.sendMessage(jid, {
    image:      { url: 'https://...' },
    caption:    'Texto do card',
    nativeFlow: [{ text: 'Botão', id: 'btn1' }]
})

Use caption, não text, quando quiser header com imagem junto de nativeFlow — usar text faz a lib pular inteiro o bloco que monta o header.

Template Buttons

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

Sections

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: ['🔥'] },  // animada
        { data: bufferPng,   emojis: ['✨'] },   // PNG converte auto
    ],
    name:        'Nome do Pack',
    publisher:   'Autor',
    description: 'Descrição'
})

Tanto cover quanto cada item de stickers[].data esperam Buffer, não { url }. Se você baixou as figurinhas de uma API, baixe o buffer primeiro (axios.get(url, { responseType: 'arraybuffer' })) antes de montar o objeto.

Máximo 60 figurinhas, cada uma até 1MB. Requer sharp ou @napi-rs/image para PNG.


6. Canais Newsletter

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

// Mídia — sempre via generateWAMessage + relayMessage
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 })

// Gerenciar
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)

7. Enquetes com decrypt

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

// Receber votos — no connect.js após saveCreds:
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

// Texto com hyperlink
await sock.sendRich(jid, [
    sock.makeText('Acesse [nosso site](https://systemzone.store).')
], quotedMsg)

// Código
await sock.sendRich(jid, [
    sock.makeCode('bash', 'npm i @systemzero/baileys')
], null, ['RICH_RESPONSE_CODE'])

// Tabela
await sock.sendRich(jid, [
    sock.makeTable([['Nome', 'Status'], ['Botões', '✅'], ['Canais', '✅']])
], null, ['RICH_RESPONSE_TABLE'])

// Lista
await sock.sendRich(jid, [sock.makeList(['Item 1', 'Item 2'])])

// Atalhos
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) — formato baseado em número de telefone
  • LID (@lid) — identificador opaco, não contém o número de telefone

A partir da v1.0.6, a baileys resolve automaticamente LID↔JID em mensagens recebidas e na metadata de grupo (ver seção 22), via sharedLidPhoneCache.

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]')
normalizeJid('5511999999999')         // → '[email protected]'
normalizeJid('123456@lid')            // → resolve via cache
const { isValid, error } = validateJid('[email protected]')
const { jid, lid, isGroup, isValid } = getSenderInfo(message)

Cache bidirecional

O cache é populado automaticamente ao processar mensagens e ao buscar metadata de grupo (groupMetadata) — esse segundo ponto foi corrigido na v1.0.6, antes o par LID↔telefone que vem pronto na lista de participantes do grupo era descartado.

sharedLidPhoneCache.set('123456@lid', '[email protected]') // manual, raro
const jid = sharedLidPhoneCache.getPhoneForLid('123456@lid')
const lid = sharedLidPhoneCache.getLidForPhone('[email protected]')
console.log('Entradas no cache:', sharedLidPhoneCache.size)

⚠️ Atenção com sufixo de dispositivo: identidades do próprio bot (sock.user.id / sock.user.lid) costumam vir como numero:N@dominio (ex: 558892659041:[email protected]). Pra comparar com a lista de participants (que não tem sufixo), remova só o :N, preservando o domínio: jid.replace(/:\d+(?=@)/, '')não use jid.split(':')[0], isso corta o domínio inteiro junto.


10. Username (@usuario)

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

isUsername('@josue')    // true
isUsername('josue')     // false

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 — botões clássicos

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 })

Capturar clique: m.body chega com o id do botão.

Button — native flow avançado

const msg = new Button(sock)
msg.setTitle('Título')
msg.setBody('Corpo')
msg.addReply('✅ Confirmar', 'confirmar')
msg.addUrl('🔗 Abrir site', 'https://systemzone.store')
msg.addCopy('📋 Copiar código', 'PROMO2025')
msg.addCall('📞 Ligar', '5511999999999')
await msg.send(jid, { quoted: m })

Carousel

const msg = new Carousel(sock)
msg.setBody('Escolha um item:')
msg.card(c => c.image('https://...').title('Card 1').text('Descrição').button('Ver', 'c1'))
msg.card(c => c.image('https://...').title('Card 2').text('Descrição').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', `const baileys = require('@systemzero/baileys')`)
msg.addTable([['Comando', 'Descrição'], ['!menu', 'Abre o menu']])
await msg.send(jid, { quoted: m })

12. Botões estendidos (v1.0.6)

⚠️ Não verificado nesta versão do README. Os tipos abaixo (catalog, products, otp, orderDetails, flow, etc) exigem conta WhatsApp Business API verificada com catálogo/pagamentos configurados — nenhum deles foi testado numa conta pessoal real. Os tipos mais simples (reminder, address, location, phoneNumber, urlBtn, clearChat) têm chance maior de funcionar em qualquer conta, mas também não foram confirmados em teste real. Trate como documentação do proto, não como garantia de funcionamento. Para formulários reais e testados, veja a seção 24.

{ text: '⏰ Me lembre', reminder: 'lembrete_id' }
{ text: '🔕 Cancelar', cancelReminder: 'lembrete_id' }
{ text: '📍 Enviar endereço', address: true }
{ text: '📡 Localização', location: true }
{ text: '🛍️ Ver catálogo', catalog: '[email protected]' }
{ text: '📦 Produtos', products: ['prod_id_1'], bizJid: '[email protected]' }
{ text: 'Copiar código', otp: '123456' }
{ text: '📞 Ligar', phoneNumber: '5511999999999' }
{ text: '📋 Ver pedido', orderDetails: { order_id: '123', token: 'abc' } }
{ 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 // ignora notificação de sistema
})

13. Grupos

const grupo = await sock.groupCreate('Nome', ['[email protected]'])
const meta  = await sock.groupMetadata(jid)

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, downloadContentFromMessage } = require('@systemzero/baileys')
const buffer = await downloadMediaMessage(message, 'buffer', {})

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

const {
    jidNormalizedUser, isJidGroup, isJidNewsletter,
    isLidUser, isPnUser, isUsername,
    normalizeJid, resolveAll, getSenderInfo,
    jidEncode, jidDecode
} = require('@systemzero/baileys')

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

getDevice só funciona em cima de um message.key.id real — não dá pra usar pra "descobrir o dispositivo" de alguém sem ter uma mensagem recente dela pra inspecionar.


17. Fixar / Desfixar mensagens

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

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

quotedMsg.key precisa conter remoteJid, fromMe, id e (em grupos) participant.


18. Resolução de nomes (getName)

const { getName } = require('@systemzero/baileys')
const nome = getName(msg, contactStore) // contactStore opcional: { [jid]: { name, notify, verifiedName } }

Ordem de prioridade: contato salvo localmente → verifiedNamepushName/notify → resolução @lid via cache → número formatado → "Usuário desconhecido". Nunca retorna vazio.


19. PTT real com qualquer formato de entrada

Antes da correção, ptt: trueetiquetava o mimetype como opus sem converter os bytes de verdade — qualquer entrada que não já fosse opus puro dava "algo errado com o arquivo de áudio" no WhatsApp. Agora a lib transcodifica de verdade via ffmpeg quando necessário.

await sock.sendMessage(jid, {
    audio: { url: 'https://qualquer-formato.mp3' }, // mp3, m4a, wav, o que for
    ptt: true
})

A lib detecta se a fonte já é .opus/.ogg (pela extensão real, não pelo que você declarar em mimetype) e só converte quando necessário, pra mono/16kHz — a especificação que o player de voz do WhatsApp espera.

Requer ffmpeg com libopus no PATH do servidor.


20. Guard de pagamento stealth (anti-fraude)

A lib emite o evento messages.decrypt-failed sempre que uma mensagem de grupo falha na descriptografia (PreKey/CIPHERTEXT) — cobre o golpe de cobrança "invisível": mensagem normal enviada, depois editada pra conteúdo de pagamento.

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

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

| Caminho | Evento | Garantia | |---|---|---| | Mensagem direta | messages.upsert | Roda seu detector no conteúdo normal | | Mensagem editada | messages.update | Extrai editedMessage e roda o mesmo detector | | Falha de decrypt | messages.decrypt-failed | Nunca ignora em silêncio, mesmo sem conteúdo |

Falha de decrypt nunca foi lida pelo bot — não existe forma de confirmar que era pagamento. O guard garante que isso nunca passe sem nenhum aviso, não que vai "adivinhar" o conteúdo.


21. Bad MAC Handler — sessões Signal

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

if (badMacHandler.isBadMacError(error)) {
    badMacHandler.handleError(error, 'algum-contexto')
}

// limpa sessões Signal problemáticas, preservando creds.json
badMacHandler.clearProblematicSessionFiles()

Erros "Bad MAC" são esperados ocasionalmente em qualquer cliente Signal Protocol (sessão desincronizada do outro lado) — esse handler conta, dá reset automático após um intervalo, e remove só arquivos de sessão por par, nunca as credenciais principais.


22. Resolução LID → telefone via grupo

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

Força a resolução de um @lid pro telefone real buscando a metadata do grupo (que já entrega o par pronto por participante) — ú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, depois GitHub, e só aceita um resultado se isLatest === true de verdade. Diferente de fetchLatestWaWebVersion()/fetchLatestBaileysVersion() isoladas, que sempre retornam algo (mesmo em falha, caindo num valor fixo antigo) sem deixar isso óbvio.


24. WhatsApp Flows — Formulários interativos

⚠️ WhatsApp Flows é um recurso de WhatsApp Business API, normalmente exigindo um flow_id aprovado pela Meta pra sua conta/app específico. O exemplo abaixo é o que está em uso real no System Zero — funciona pro flow_id configurado nessa conta, mas não é garantido que o mesmo flow_id funcione pra qualquer outra conta. Pra criar seu próprio Flow, você precisa cadastrar e aprovar ele no Meta Business Manager.

Enviando o formulário

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

const formMsg = generateWAMessageFromContent(jid, {
    viewOnceMessage: {
        message: {
            messageContextInfo: {
                deviceListMetadata: {},
                deviceListMetadataVersion: 2
            },
            interactiveMessage: {
                body: { text: 'Formulário de teste\n\nPreencha seus dados.' },
                nativeFlowMessage: {
                    buttons: [{
                        name: 'galaxy_message',
                        buttonParamsJson: JSON.stringify({
                            flow_message_version: '4',
                            flow_id: 'SEU_FLOW_ID_AQUI',
                            flow_action_payload: {
                                screen: 'contact_details',
                                data: {
                                    full_name_visible:    true,
                                    phone_number_visible: true,
                                    email_visible:        true,
                                    offer_name:           'Olá, seja bem-vindo',
                                    offer_description:    'Sistema de teste'
                                }
                            },
                            well_version: 'V700',
                            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

A resposta do formulário chega como interactiveResponseMessage. O nativeFlowResponseMessage.paramsJson traz os dados preenchidos dentro de wa_flow_response_params.response_message (uma string JSON aninhada — precisa de JSON.parse duplo).

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

        if (nfr?.name === 'galaxy_message' && nfr?.paramsJson) {
            const parsed = JSON.parse(nfr.paramsJson)
            if (!parsed.wa_flow_response_params) return

            const flowParams  = parsed.wa_flow_response_params
            const flowId      = flowParams.flow_id || ''
            const rawResponse = flowParams.response_message || ''

            let screens = []
            try { screens = JSON.parse(rawResponse).screens || [] } catch {}

            // extrai só os campos preenchidos
            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
                    }
                }
            }

            // roteia por flow_id — adicione mais formulários aqui
            if (flowId === 'SEU_FLOW_ID_AQUI') {
                let resposta = 'Formulário recebido!\n\n'
                if (campos.full_name)    resposta += `Nome: ${campos.full_name}\n`
                if (campos.phone_number) resposta += `Telefone: +${campos.phone_number}\n`
                if (campos.email)        resposta += `Email: ${campos.email}\n`
                await sock.sendMessage(m.key.remoteJid, { text: resposta }, { quoted: m })
            }
        }
    }
})

Changelog

v1.0.6

  • Fix PTT realptt: true agora transcodifica via ffmpeg quando necessário, em vez de só etiquetar o mimetype
  • Fix groupMetadata → sharedLidPhoneCache — pares LID↔telefone da lista de participantes de grupo agora são registrados no cache (antes eram descartados)
  • Fix cleanId de identidade própria — comparação de sock.user.id/sock.user.lid com sufixo de dispositivo (:N) agora preserva o domínio
  • Guard de pagamento stealthbindPaymentGuard, evento messages.decrypt-failed
  • Bad MAC HandlerBadMacHandler, badMacHandler
  • getName — resolução de nome em cascata, nunca vazia
  • getBestWaVersion — busca de versão que não falha em silêncio
  • resolveLidPhoneFromGroup — força resolução via metadata de grupo
  • WhatsApp Flows — suporte documentado a formulários interativos (nativeFlowResponseMessage)
  • isSystemNotification — flag em mensagens de notificação de sistema
  • Sistema LID/JID avançado com cache bidirecional (sharedLidPhoneCache)
  • lidToJid, resolveJid, resolveAll, normalizeJid, validateJid, getSenderInfo
  • Suporte a @usernameisUsername, resolveUsername

v1.0.5

  • Sticker Pack nativo com suporte a animadas e PNG
  • Fix canal newsletter (extraAttrs no plaintext)
  • 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.0.6

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