ponytail: remove resolucaoSetup, compressVideo, sendEvolutionMessage, sendEvolution; add migration #24

- delete: src/resolucaoSetup.js (migration paralela, consolidado em #24)
- shrink: chatController.sendEvolutionMessage (140 -> 6 linhas, delega ao evolutionService)
- shrink: triageController.sendEvolution (55 -> 10 linhas, delega ao evolutionService)
- delete: compressVideo em chatController (codigo morto, chamado so pelo old sendEvolutionMessage)
- add: migration #24 consolida DDL que estava espalhada em runtime
- fix: configController.remove garantirEstrutura calls (arquivo removido)
- net: -270 linhas, -1 arquivo
This commit is contained in:
2026-07-01 13:24:06 +00:00
parent c9d2c668ee
commit 9cf5f00243
11 changed files with 152 additions and 296 deletions
+8 -196
View File
@@ -1,63 +1,9 @@
const db = require('../database');
const { garantirEstrutura } = require('../resolucaoSetup');
const ffmpegPath = require('ffmpeg-static');
const { execFile } = require('child_process');
const path = require('path');
const fs = require('fs');
/**
* Comprime vídeo para tamanho aceitável pelo WhatsApp/Evolution API
* Reduz bitrate e resolução se necessário (alvo: ~10MB)
* @param {Buffer} inputBuffer - Buffer do video original
* @returns {Promise<Buffer>} Buffer do video comprimido
*/
function compressVideo(inputBuffer) {
return new Promise(function(resolve, reject) {
var tmpDir = path.join(__dirname, '../../uploads/audio');
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });
var inputPath = path.join(tmpDir, 'video_in_' + Date.now() + '.mp4');
var outputPath = path.join(tmpDir, 'video_out_' + Date.now() + '.mp4');
fs.writeFileSync(inputPath, inputBuffer);
var inputSizeMB = (inputBuffer.length / (1024 * 1024)).toFixed(1);
// Se o video ja tem <= 10MB, nao precisa comprimir muito
var targetBitrate = inputBuffer.length < 10 * 1024 * 1024 ? '1M' : '500k';
console.log('[Video] Comprimindo video de ' + inputSizeMB + 'MB, bitrate alvo: ' + targetBitrate);
execFile(ffmpegPath, [
'-y',
'-i', inputPath,
'-c:v', 'libx264',
'-b:v', targetBitrate,
'-c:a', 'aac',
'-b:a', '64k',
'-vf', 'scale=720:-2',
'-movflags', '+faststart',
'-preset', 'fast',
'-maxrate', '1M',
'-bufsize', '2M',
outputPath
], { timeout: 120000 }, function(err) {
try { fs.unlinkSync(inputPath); } catch(e) {}
if (err) {
try { fs.unlinkSync(outputPath); } catch(e) {}
console.error('[Video] Erro na compressao:', err.message.substring(0, 100));
return reject(err);
}
var outputBuffer = fs.readFileSync(outputPath);
try { fs.unlinkSync(outputPath); } catch(e) {}
var outputSizeMB = (outputBuffer.length / (1024 * 1024)).toFixed(1);
console.log('[Video] Compressao concluida: ' + inputSizeMB + 'MB -> ' + outputSizeMB + 'MB');
resolve(outputBuffer);
});
});
}
/**
* Converte buffer de áudio WebM para Ogg Opus usando ffmpeg
*/
@@ -691,7 +637,6 @@ class ChatController {
return res.status(403).json({ success: false, error: 'Sem permissão.' });
// ===== Fluxo de Resolução: valida motivo/resolução conforme config =====
await garantirEstrutura(alias);
const motivoId = (req.body && req.body.motivoId) ? parseInt(req.body.motivoId, 10) : null;
const resolucao = (req.body && req.body.resolucao != null) ? String(req.body.resolucao).trim() : '';
@@ -743,7 +688,9 @@ class ChatController {
WHERE CON_CODIGO_ID = ?
`, [usuarioNome, equipeNome, etiquetasDesc, motivoId, resolucao || null, id]);
// CSAT
// CSAT — NÃO envia se a conversa for agendada (skipCsat = true)
const skipCsat = req.body && req.body.skipCsat === true;
if (!skipCsat) {
const config = await db.query(alias,
'SELECT CFE_CSAT_ATIVO, CFE_CSAT_MENSAGEM FROM CHATC2_CONFIGURACOES_EMPRESA WHERE CFE_EMPRESA_ID = ?',
[conv[0].CON_EMPRESA_ID]);
@@ -784,6 +731,7 @@ class ChatController {
console.log('[CSAT] NAO enviado - Evolution API pode estar offline');
}
}
} // fecha if (!skipCsat)
// Salva resolução na tabela MENSAGENS_CLIENTES como aviso (se configurado)
if (isS(fl.CFE_RESOLUCAO_SALVAR_MSC) && resolucao && c.CON_CLIENTE_ID) {
@@ -1711,149 +1659,13 @@ class ChatController {
}
}
// Helper: normaliza número para formato WhatsApp (DDI + 9º dígito)
function _normalizarWhatsApp(numero) {
let n = String(numero || '').replace(/\D/g, '');
if (!n) return '';
if (!n.startsWith('55')) n = '55' + n;
const resto = n.slice(2);
if (resto.length === 10) n = '55' + resto.slice(0, 2) + '9' + resto.slice(2);
return n;
}
// Função auxiliar para enviar mensagem via Evolution API
// ponytail: delegado para evolutionService
const evolution = require('../services/evolutionService');
async function sendEvolutionMessage(instancia, numero, texto, tipo, midiaBase64, nomeArquivo) {
const url = ((instancia.INS_URL || '').trim()).replace(/\/$/, '');
const apiKey = (instancia.INS_API_KEY || '').trim();
const instanceName = (instancia.INS_INSTANCE_NAME || '').trim();
const numeroLimpo = _normalizarWhatsApp(numero);
const https = require('https');
const http = require('http');
let payload;
let endpoint;
if (midiaBase64 && (tipo === 'image' || tipo === 'audio' || tipo === 'video' || tipo === 'document')) {
var mediaB64 = midiaBase64;
var fileName = nomeArquivo || 'arquivo';
if (tipo === 'audio') {
// A mídia pode já estar em OGG (convertida antes de salvar no BD)
// Se ainda for WebM, tenta converter agora como fallback
var jaEOgg = (fileName || '').toLowerCase().endsWith('.ogg');
if (!jaEOgg) {
try {
var inputBuffer = Buffer.from(mediaB64, 'base64');
if (inputBuffer.length > 100) {
var oggBuffer = await convertWebmToOgg(inputBuffer);
if (oggBuffer && oggBuffer.length > 100) {
mediaB64 = oggBuffer.toString('base64');
fileName = 'audio.ogg';
console.log('[Audio] Conversão fallback WebM→OGG:', inputBuffer.length, '→', oggBuffer.length);
}
}
} catch(convErr) {
console.error('[Audio] Conversão OGG falhou, mantendo como áudio:', convErr.message.substring(0, 100));
fileName = 'audio.webm';
// NÃO muda para 'document' - mantém como áudio
}
}
// Usa sendWhatsAppAudio para enviar como mensagem de voz (PTT)
endpoint = '/message/sendWhatsAppAudio/' + encodeURIComponent(instanceName);
payload = JSON.stringify({ number: numeroLimpo, audio: mediaB64, encoding: true });
console.log('[Evolution] Enviando via sendWhatsAppAudio, tamanho:', mediaB64.length, 'arquivo:', fileName);
} else if (tipo === 'image') {
endpoint = '/message/sendMedia/' + encodeURIComponent(instanceName);
payload = JSON.stringify({ number: numeroLimpo, mediatype: 'image', media: mediaB64, fileName: fileName });
} else if (tipo === 'video') {
// Comprime video se for muito grande (> 5MB) para evitar rejeicao da Evolution API
try {
var videoBuf = Buffer.from(mediaB64, 'base64');
if (videoBuf.length > 5 * 1024 * 1024) {
console.log('[Video] Video grande detectado (' + (videoBuf.length / (1024 * 1024)).toFixed(1) + 'MB), comprimindo...');
var compressedBuf = await compressVideo(videoBuf);
if (compressedBuf && compressedBuf.length > 1000) {
mediaB64 = compressedBuf.toString('base64');
fileName = (fileName || 'video').replace(/\.\w+$/, '') + '_compressed.mp4';
console.log('[Video] Compressao OK, novo tamanho base64:', mediaB64.length);
}
}
} catch(compressErr) {
console.error('[Video] Compressao falhou, enviando original:', compressErr.message.substring(0, 80));
}
endpoint = '/message/sendMedia/' + encodeURIComponent(instanceName);
payload = JSON.stringify({ number: numeroLimpo, mediatype: 'video', media: mediaB64, fileName: fileName });
} else if (tipo === 'document') {
// Para documentos que sao videos, comprime tambem se for grande
var isVideoDoc = (fileName || '').toLowerCase().match(/\.(mp4|mov|avi|mkv|webm|3gp|m4v)$/);
if (isVideoDoc) {
try {
var docBuf = Buffer.from(mediaB64, 'base64');
if (docBuf.length > 5 * 1024 * 1024) {
console.log('[Document] Video como documento detectado (' + (docBuf.length / (1024 * 1024)).toFixed(1) + 'MB), comprimindo...');
var compressedDocBuf = await compressVideo(docBuf);
if (compressedDocBuf && compressedDocBuf.length > 1000) {
mediaB64 = compressedDocBuf.toString('base64');
fileName = (fileName || 'video').replace(/\.\w+$/, '') + '_compressed.mp4';
console.log('[Document] Compressao OK, novo tamanho base64:', mediaB64.length);
}
}
} catch(compressErr) {
console.error('[Document] Compressao falhou, enviando original:', compressErr.message.substring(0, 80));
}
}
endpoint = '/message/sendMedia/' + encodeURIComponent(instanceName);
payload = JSON.stringify({ number: numeroLimpo, mediatype: 'document', media: mediaB64, fileName: fileName });
} else {
endpoint = '/message/sendMedia/' + encodeURIComponent(instanceName);
payload = JSON.stringify({ number: numeroLimpo, mediatype: 'document', media: mediaB64, fileName: fileName });
}
} else {
endpoint = '/message/sendText/' + encodeURIComponent(instanceName);
payload = JSON.stringify({ number: numeroLimpo, text: texto || '', delay: 0 });
return evolution.sendMedia(instancia, numero, tipo, midiaBase64, nomeArquivo);
}
const parsedUrl = new URL(url + endpoint);
const lib = parsedUrl.protocol === 'https:' ? https : http;
return new Promise((resolve, reject) => {
const options = {
hostname: parsedUrl.hostname,
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
path: parsedUrl.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload),
'apikey': apiKey,
}
};
const req = lib.request(options, (res) => {
let body = '';
res.on('data', c => body += c);
res.on('end', () => {
if (res.statusCode >= 400) {
console.error('[Evolution] Erro ao enviar mídia:', res.statusCode, body.substring(0, 200));
reject(new Error('HTTP ' + res.statusCode + ': ' + body.substring(0, 100)));
} else {
resolve(body);
}
});
});
req.on('error', function(err) {
console.error('[Evolution] Erro na requisição:', err.message);
reject(err);
});
req.write(payload);
req.end();
});
return evolution.sendText(instancia, numero, texto);
}
module.exports = ChatController;