chore(ops): cleanly purge old clube67 folder references
continuous-integration/webhook Falha no deploy de clube67_newwhats.local (VPS 4)
continuous-integration/webhook Falha no deploy de clube67_newwhats.local (VPS 4)
This commit is contained in:
@@ -1,319 +0,0 @@
|
||||
# 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 ~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<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** | **~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
|
||||
```
|
||||
Reference in New Issue
Block a user