Files
clube67_newwhats.local/clube67/newwhats.local/BAILEYS_ENGINE_SWAP.md
T
VPS 4 Deploy Agent 0dc5eefa06
continuous-integration/webhook Falha no deploy de clube67_newwhats.local (VPS 4)
chore(ops): restore missing root files in newwhats.clube67.com
2026-05-18 03:27:25 +02:00

11 KiB
Raw Blame History

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 (<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)
  • ListMessagenão exibe na conversa do destinatário (issue #669)
  • CarouselMessagenão existe (exclusivo da Cloud API)
  • Migração seria ~23 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: ~510 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 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:

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.ts caso 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 ~45 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