@systemzero/baileys
v1.1.2
Published
System-zero baileys bot
Maintainers
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.
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. 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
- Grupos
- Perfil e privacidade
- Eventos do socket
- Utilitários
- Fixar / Desfixar mensagens
- Resolução de nomes (getName)
- PTT real com qualquer formato
- Guard de pagamento stealth
- Bad MAC Handler
- Resolução LID → telefone via grupo
- Versão do WhatsApp sempre atualizada
- WhatsApp Flows — Formulários
- Mensagens agendadas
- Status em grupo (GroupStatusMessageV2)
- Figurinha de Avatar (isAvatar)
- Á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 defetchLatestWaWebVersion()— 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 minPairing Code
const code = await sock.requestPairingCode('5511999999999')
const code = await sock.requestPairingCode('5511999999999', 'MYBOT001') // personalizado — 8 charsPeça o pairing code alguns segundos após criar o socket (ex: dentro de um
setTimeoutde 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 comparticipants, remova só o:Npreservando o domínio:jid.replace(/:\d+(?=@)/, '')— não usesplit(':')[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_ALL18. 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.json22. 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_idaprovado 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:
messageSecretdeve ser umBufferde 32 bytes gerado comcrypto.randomBytes(32)— cada postagem precisa de um novo secretaudienceType: 1ativa o modo Close Friends; omitirstatusAudienceMetadataposta para todos do grupo- Para repostar uma mensagem citada (quoted), baixe a mídia com
downloadMediaMessageantes de fazer oprepareWAMessageMedia - O
groupStatus: truenosendMessagesimples (seção 3) existe como atalho, mas para controle de Close Friends e repost de mídias, use ogroupStatusMessageV2direto 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, oisAvatarenvia umstickerMessagewebp normal (não umlottieStickerMessage), 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
albumsempre que possível — ele valida o mínimo de 2 itens e preencheexpectedImageCount/expectedVideoCountcorretamente, 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.maxContentLengthno 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_REASONSexportado — novo array (loggedOut,badSession,multideviceMismatch,forbidden) com os motivos deconnection.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 domakeWASocket— é 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 elibsignalvia npm (fim do erro de instalaçãoEALLOWGIT).
v1.0.9
- Fix instalação (
EALLOWGIT) — a dependêncialibsignalera referenciada via git (git+https://github.com/...), o que quebrava onpm installem 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, apaguenode_modulesepackage-lock.jsone 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 denode-webpmux - Fix waveform (spectral) — o waveform (ondinhas) do áudio saía como linha reta porque o
audio-decodenão decodifica opus/ogg; agora extrai os samples PCM viaffmpeg, funcionando em qualquer formato; gerado para todos os áudios, não sóptt - Fix conexão / pareamento —
getCompanionPlatformId(enumCompanionWebClientTypecorreto) no lugar dogetPlatformIdgenérico, que causava rejeição de pareamento;pushNameadicionado ao payload de login - Anti-ban 405 —
DisconnectReason.rateLimited(405) adicionado; handler loga o rate limit para o reconnect esperar antes de tentar de novo
v1.0.7
- Fix PTT real —
ptt: trueconverte viaffmpegantes do upload; corrigido bugCannot use 'in' operator to search for 'stream'(caminho do arquivo transcrito era passado como string crua em vez de{ url }) - Fix 403 / conexão —
lidDbMigratedno login agora reflete o estado real das credenciais; companion version usa a versão real do WA;"forbidden"mapeado corretamente praDisconnectReason.forbidden - Mensagens agendadas —
MessageScheduler/createMessageSchedulerportado de innovatorssoft/baileys - PreKey recovery melhorado — upload de 30 chaves (era 5), delay de 2500ms (era 1000ms), parâmetro
forcepra ignorarMIN_UPLOAD_INTERVALem 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
@username—isUsername,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
