# 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: - `nativeButtons` - `nativeList` - `nativeCarousel` O WhatsApp Web exige nós binários adicionais (``, ``) 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 ```bash 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` ```typescript // Re-exporta tudo do Baileys oficial export * from '@whiskeysockets/baileys' export { proto } from '@whiskeysockets/baileys' ``` ### 4.3 Criar `engine/infinite.ts` ```typescript // Re-exporta tudo do InfiniteAPI export * from 'baileys' export { proto } from 'baileys' ``` ### 4.4 Criar `engine/index.ts` ```typescript // 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 um `export *` com re-export dinâmico. > Alternativa mais simples: dois arquivos de entrada separados e o `tsconfig` aponta 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: ```typescript export async function sendRichMessage( sock: WASocket, jid: string, payload: RichPayload, quoted?: { key: proto.IMessageKey; message: proto.IMessage }, ): Promise { 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.ts` caso precise debugar com a engine oficial. ### 4.7 Adicionar BAILEYS_ENGINE ao .env ```bash # /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`: ```typescript // 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: ```tsx // Toggle na página /admin/settings ou /admin/system
Engine WhatsApp ⚠ Troca reinicia o servidor (~10s)
``` --- ## 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 ```typescript 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 ```bash # 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 ```