@systemzero/baileys
v1.0.6
Published
System-zero baileys bot
Maintainers
Readme
@systemzero/baileys
v1.0.6 · Fork avançado do Baileys com suporte nativo a todos os tipos de mensagens do WhatsApp
Instalação
npm i @systemzero/baileysDependências opcionais:
npm i sharp # conversão de imagem para sticker pack
npm i @napi-rs/image # alternativa ao sharpRequer 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
- Conexão
- Mensagens de mídia
- Mensagens especiais
- Botões e interativos
- Sticker Pack nativo
- Canais Newsletter
- Enquetes com decrypt
- AI Rich
- LID / JID — Sistema avançado
- Username (@usuario)
- MessageBuilder — Button, ButtonV2, Carousel, AIRich
- Botões estendidos (v1.0.6)
- Grupos
- Perfil e privacidade
- Eventos do socket
- Utilitários
- Fixar / Desfixar mensagens
- Resolução de nomes (getName)
- PTT real com qualquer formato de entrada
- Guard de pagamento stealth (anti-fraude)
- Bad MAC Handler — sessões Signal
- Resolução LID → telefone via grupo
- Versão do WhatsApp sempre atualizada
- 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 (
linkPreviewmanual) 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 — oaudioFooternão mostrou o player de áudio, e olinkPreviewmanual 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ãotext, quando quiser header com imagem junto denativeFlow— usartextfaz 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
coverquanto cada item destickers[].dataesperam 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 comonumero:N@dominio(ex:558892659041:[email protected]). Pra comparar com a lista departicipants(que não tem sufixo), remova só o:N, preservando o domínio:jid.replace(/:\d+(?=@)/, '')— não usejid.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'
getDevicesó funciona em cima de ummessage.key.idreal — 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 → verifiedName → pushName/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: true só etiquetava 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
ffmpegcomlibopusnoPATHdo 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_idaprovado pela Meta pra sua conta/app específico. O exemplo abaixo é o que está em uso real no System Zero — funciona proflow_idconfigurado nessa conta, mas não é garantido que o mesmoflow_idfuncione 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 real —
ptt: trueagora transcodifica viaffmpegquando 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.lidcom sufixo de dispositivo (:N) agora preserva o domínio - Guard de pagamento stealth —
bindPaymentGuard, eventomessages.decrypt-failed - Bad MAC Handler —
BadMacHandler,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
@username—isUsername,resolveUsername
v1.0.5
- Sticker Pack nativo com suporte a animadas e PNG
- Fix canal newsletter (
extraAttrsnoplaintext) - 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
