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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user