11 KiB
Baileys Engine Swap — Plano de Implementação
Data: 2026-04-29
Contexto: Mensagens ricas (botões, lista, carrossel) não renderizam no WhatsApp Web com @whiskeysockets/baileys padrão. A solução é usar o fork InfiniteAPI que implementa esses tipos nativamente dentro do sendMessage().
1. Problema Raiz
O @whiskeysockets/baileys v6.7.18 (oficial) não suporta os campos:
nativeButtonsnativeListnativeCarousel
O WhatsApp Web exige nós binários adicionais (<biz><interactive>, <bot biz_bot="1"/>) injetados no stanza via relayMessage(additionalNodes). O rich-message.ts atual tenta replicar isso manualmente, mas ainda falha no WhatsApp Web.
Erro observado: "Não foi possível carregar a mensagem. Use seu celular para acessá-la."
Por que o InfiniteAPI funciona
O fork github:rsalcara/InfiniteAPI (v7.0.0-rc.9, package name: baileys) implementa toda a lógica de additionalNodes internamente no generateWAMessageContent() / relayMessage(). O dev só chama sock.sendMessage(jid, { nativeButtons: [...] }) e o fork faz o resto.
Por que whatsmeow (Go) NÃO resolve
Pesquisa confirmada (abril 2026):
InteractiveMessage+NativeFlowMessage→ struct existe, botões não aparecem (issue #711, sem solução)ListMessage→ não exibe na conversa do destinatário (issue #669)CarouselMessage→ não existe (exclusivo da Cloud API)- Migração seria ~2–3 meses de reescrita para o mesmo resultado quebrado
2. Solução Escolhida: Engine Swap com Fallback
Instalar os dois packages (nomes diferentes, convivem no mesmo node_modules) e criar uma camada de abstração engine/ que permite trocar via variável de ambiente + restart automático.
Downtime por troca: ~5–10 segundos (restart + reconexão automática, sem novo QR — auth state no disco é compatível entre os dois, InfiniteAPI é fork direto).
3. Estrutura de Arquivos
backend/
src/
modules/whatsapp/
engine/
official.ts ← re-exporta @whiskeysockets/baileys
infinite.ts ← re-exporta baileys (InfiniteAPI)
index.ts ← exporta conforme process.env.BAILEYS_ENGINE
connection/
WhatsAppConnectionManager.ts ← muda import para '../engine'
handlers/
MessageHandler.ts ← muda import para '../../engine' (ou relativo)
ContactHandler.ts ← idem
rich-message.ts ← simplificado + fallback comentado
modules/chatbot/
chatbot.service.ts ← muda import
shared/utils/
whatsapp.ts ← muda import
4. Implementação Passo a Passo
4.1 Instalar os dois packages
cd /home/deploy/projetos/newwhats.local/backend
# Manter o oficial
# @whiskeysockets/baileys já está em package.json
# Adicionar InfiniteAPI com alias para não conflitar
npm install baileys@github:rsalcara/InfiniteAPI
Ambos convivem no node_modules porque têm nomes diferentes:
node_modules/@whiskeysockets/baileys/node_modules/baileys/
4.2 Criar engine/official.ts
// Re-exporta tudo do Baileys oficial
export * from '@whiskeysockets/baileys'
export { proto } from '@whiskeysockets/baileys'
4.3 Criar engine/infinite.ts
// Re-exporta tudo do InfiniteAPI
export * from 'baileys'
export { proto } from 'baileys'
4.4 Criar engine/index.ts
// Seleciona engine conforme BAILEYS_ENGINE (padrão: infinite)
// IMPORTANTE: process.env lido em tempo de inicialização do módulo.
// Para trocar a engine: alterar BAILEYS_ENGINE + reiniciar o processo.
const engine = process.env.BAILEYS_ENGINE ?? 'infinite'
if (engine === 'infinite') {
module.exports = require('./infinite')
} else {
module.exports = require('./official')
}
Nota TypeScript: usar
require()condicional ou umexport *com re-export dinâmico. Alternativa mais simples: dois arquivos de entrada separados e otsconfigaponta para um deles via path alias.
4.5 Trocar imports nos 6 arquivos
| Arquivo | Import atual | Novo import |
|---|---|---|
WhatsAppConnectionManager.ts |
@whiskeysockets/baileys |
../engine |
handlers/MessageHandler.ts |
@whiskeysockets/baileys |
../../engine |
handlers/ContactHandler.ts |
@whiskeysockets/baileys |
../../engine |
rich-message.ts |
@whiskeysockets/baileys (3 imports) |
./engine |
modules/chatbot/chatbot.service.ts |
@whiskeysockets/baileys |
../whatsapp/engine |
shared/utils/whatsapp.ts |
(verificar) | path relativo para engine |
4.6 Simplificar rich-message.ts para InfiniteAPI
Com InfiniteAPI, o sendRichMessage() fica:
export async function sendRichMessage(
sock: WASocket,
jid: string,
payload: RichPayload,
quoted?: { key: proto.IMessageKey; message: proto.IMessage },
): Promise<string> {
const { text, footer, nativeButtons, nativeList, nativeCarousel, poll } = payload
if (poll) {
const sent = await sock.sendMessage(jid, {
poll: { name: poll.name, values: poll.values, selectableCount: poll.selectableCount ?? 1 },
}, quoted ? { quoted } : undefined)
return sent?.key?.id ?? `poll-${Date.now()}`
}
if (nativeButtons) {
const sent = await sock.sendMessage(jid, {
text: text ?? '',
footer,
nativeButtons, // InfiniteAPI trata tudo internamente
} as any, quoted ? { quoted } : undefined)
return sent?.key?.id ?? `btn-${Date.now()}`
}
if (nativeList) {
const sent = await sock.sendMessage(jid, {
text: text ?? '',
footer,
nativeList,
} as any, quoted ? { quoted } : undefined)
return sent?.key?.id ?? `list-${Date.now()}`
}
if (nativeCarousel) {
const sent = await sock.sendMessage(jid, {
text: text ?? '',
footer,
nativeCarousel,
} as any, quoted ? { quoted } : undefined)
return sent?.key?.id ?? `carousel-${Date.now()}`
}
// Texto simples
const sent = await sock.sendMessage(jid, { text: text ?? '' }, quoted ? { quoted } : undefined)
return sent?.key?.id ?? `text-${Date.now()}`
}
Fallback: Manter o código atual do proto manual em
rich-message.official.tscaso precise debugar com a engine oficial.
4.7 Adicionar BAILEYS_ENGINE ao .env
# /home/deploy/projetos/newwhats.local/backend/.env
BAILEYS_ENGINE=infinite # ou: official
4.8 Endpoint de toggle no painel admin
Backend — novo endpoint em whatsapp.routes.ts ou admin.routes.ts:
// POST /api/admin/engine
// Body: { engine: 'infinite' | 'official' }
router.post('/engine', adminOnly, async (req, res) => {
const { engine } = req.body
if (!['infinite', 'official'].includes(engine)) return res.status(400).json({ error: 'invalid engine' })
// 1. Persiste no banco (tabela config ou settings)
await prisma.config.upsert({
where: { key: 'BAILEYS_ENGINE' },
update: { value: engine },
create: { key: 'BAILEYS_ENGINE', value: engine },
})
// 2. Reinicia o processo (pm2 ou systemctl)
// Usar exec() com o comando correto para o ambiente
exec('pm2 restart newwhats-backend', (err) => {
if (err) return res.status(500).json({ error: 'restart failed' })
res.json({ ok: true, engine })
})
})
// GET /api/admin/engine — retorna engine ativa
router.get('/engine', adminOnly, async (req, res) => {
const config = await prisma.config.findUnique({ where: { key: 'BAILEYS_ENGINE' } })
res.json({ engine: config?.value ?? process.env.BAILEYS_ENGINE ?? 'infinite' })
})
Frontend — componente simples na página de administração:
// Toggle na página /admin/settings ou /admin/system
<div>
<span>Engine WhatsApp</span>
<select value={engine} onChange={handleSwitch}>
<option value="infinite">InfiniteAPI (botões/lista/carrossel)</option>
<option value="official">Baileys Oficial (estável, sem botões)</option>
</select>
<span className="text-yellow-500">⚠ Troca reinicia o servidor (~10s)</span>
</div>
5. Tipos Suportados por Engine
| Tipo de Mensagem | Baileys Oficial | InfiniteAPI |
|---|---|---|
| Texto simples | ✅ | ✅ |
| Reply / Quote | ✅ | ✅ |
| Menções (@) | ✅ | ✅ |
| Mídia (foto/vídeo/doc/áudio) | ✅ | ✅ |
| Enquete (poll) | ✅ | ✅ |
Botões (nativeButtons) — reply, url, copy, call |
❌ (quebrado no Web) | ✅ |
Lista (nativeList) |
❌ (quebrado no Web) | ✅ |
Carrossel (nativeCarousel) |
❌ (quebrado no Web) | ✅ |
| Botão com header imagem/vídeo | ❌ | ✅ (headerImage, headerVideo) |
Tipos de botão suportados pelo InfiniteAPI
type NativeButton =
| { type: 'reply'; id: string; text: string } // Quick reply
| { type: 'url'; text: string; url: string } // Abrir URL
| { type: 'copy'; text: string; copyText: string } // Copiar texto
| { type: 'call'; text: string; phoneNumber: string } // Ligar
6. Riscos e Mitigações
| Risco | Mitigação |
|---|---|
| InfiniteAPI é RC (não estável) | Engine swap — volta pro oficial em 10s |
Repo github:rsalcara/InfiniteAPI pode sair do ar |
Fazer fork próprio no GitHub interno |
| Breaking changes na API do fork | Wrapper engine/ isola o impacto |
| Auth state incompatível após troca | Não é problema — InfiniteAPI usa o mesmo formato useMultiFileAuthState |
| TypeScript: tipos diferentes entre engines | Usar as any nos campos extras do InfiniteAPI |
7. Estimativa de Esforço
| Tarefa | Tempo |
|---|---|
Instalar packages + criar engine/ |
30 min |
| Trocar imports nos 6 arquivos | 30 min |
Simplificar rich-message.ts |
30 min |
| Endpoint admin + frontend toggle | 2 horas |
| Teste real (celular + Web) | 1 hora |
| Total | ~4–5 horas |
8. Arquivos-Chave para Referência
| Arquivo | Descrição |
|---|---|
backend/src/modules/whatsapp/rich-message.ts |
Lógica atual de envio rico (proto manual) |
backend/src/modules/whatsapp/connection/WhatsAppConnectionManager.ts |
Core da conexão Baileys (1265 linhas) |
backend/src/modules/whatsapp/handlers/MessageHandler.ts |
Processa mensagens recebidas (865 linhas) |
backend/src/modules/whatsapp/whatsapp.send.routes.ts |
Rotas de envio (757 linhas) |
/home/deploy/projetos/whatsbotoes/baileys_interactive/ |
Projeto de referência usando InfiniteAPI |
/home/deploy/projetos/whatsbotoes/tipos_de_disparo.md |
Documentação dos tipos de disparo |
node_modules/baileys/lib/Types/Message.d.ts |
Tipos do InfiniteAPI (após instalar) |
9. Comandos Úteis
# Compilar backend
cd /home/deploy/projetos/newwhats.local/backend
npx tsc --skipLibCheck
# Compilar só rich-message
npx tsc src/modules/whatsapp/rich-message.ts --outDir dist/modules/whatsapp --skipLibCheck --esModuleInterop --module commonjs --moduleResolution node --target es2020
# Reiniciar backend
pm2 restart newwhats-backend # ou o nome do processo
# Ver engine ativa
grep BAILEYS_ENGINE .env