1
Clube67 Baileys Swap
ruicesar edited this page 2026-05-17 08:16:08 +02:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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