Files
clube67_newwhats.local/clube67/newwhats.local/BAILEYS_ENGINE_SWAP.md
T

320 lines
11 KiB
Markdown
Raw Blame History

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)
- `ListMessage`**não exibe** na conversa do destinatário (issue #669)
- `CarouselMessage`**nã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
```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<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
```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
<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
```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** | **~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
```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
```