topsapp: estruturar suporte

Signed-off-by: Rodrigo B. de Sousa Martins <rodrigo.sousa.577@gmail.com>
This commit is contained in:
rodrigo committed 2025-08-08 21:48:26 -03:00
1 parent 723ad8c268
commit 8dc6c594c9
7 files changed
+1685 -102

No files matched your search

+39
View File
@@ -9,7 +9,9 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"axios": "^1.6.0",
"dotenv": "^16.3.1",
"form-data": "^4.0.0",
"node-telegram-bot-api": "^0.64.0",
"ssh2": "^1.15.0"
},
@@ -227,6 +229,17 @@
"integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -781,6 +794,26 @@
"node": ">=8"
}
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@@ -1819,6 +1852,12 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/psl": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
+930 -52
View File
File diff suppressed because it is too large. Load diff
+27 -3
View File
@@ -169,9 +169,9 @@ class NLPProcessor {
};
}
// Detectar comandos TopSapp - bloquear cliente
if (/bloquear|suspender|cortar/i.test(action) && /cliente|user/i.test(fullMatch)) {
let clienteId = match[3] || match[2]; // ID do cliente
// Detectar comandos TopSapp - bloquear cliente/serviço
if (/bloquear|suspender|cortar/i.test(action)) {
let clienteId = match[3] || match[2]; // ID/CPF/Nome do cliente
return {
action: 'bloquear_cliente',
@@ -181,6 +181,30 @@ class NLPProcessor {
};
}
// Detectar comandos TopSapp - listar serviços
if (/listar|ver|mostrar/i.test(action) && /serviços?|produtos?|planos?/i.test(fullMatch)) {
let clienteId = match[3] || match[2]; // ID/CPF/Nome do cliente
return {
action: 'listar_servicos',
target: clienteId ? clienteId.trim() : null,
confidence: 0.95,
type: 'topsapp_services_pattern'
};
}
// Detectar comandos TopSapp - liberar cliente/serviço
if (/liberar|ativar|religar|reconectar/i.test(action)) {
let clienteId = match[3] || match[2]; // ID/CPF/Nome do cliente
return {
action: 'liberar_cliente',
target: clienteId ? clienteId.trim() : null,
confidence: 0.95,
type: 'topsapp_unblock_pattern'
};
}
// Detectar comandos TopSapp - liberar cliente
if (/liberar|desbloquear|reconectar/i.test(action) && /cliente|user/i.test(fullMatch)) {
let clienteId = match[3] || match[2]; // ID do cliente
+152 -28
View File
@@ -1,5 +1,6 @@
const axios = require('axios');
const https = require('https');
const logger = require('../utils/logger');
class TopSappAPI {
constructor() {
@@ -23,10 +24,8 @@ class TopSappAPI {
httpsAgent: new https.Agent({
rejectUnauthorized: false
}),
timeout: 30000,
headers: {
'Content-Type': 'multipart/form-data'
}
timeout: 30000
// Não definir Content-Type, deixar o FormData fazer isso automaticamente
};
console.log('🔗 TopSappAPI inicializado');
@@ -53,31 +52,45 @@ class TopSappAPI {
async login(instance, credentials) {
try {
console.log(`🔐 Fazendo login na instância: ${instance.name}`);
console.log(`📍 URL: ${instance.baseURL}/Login`);
console.log(`👤 Usuario: ${credentials.usuario}`);
const formData = new FormData();
formData.append('usuario', credentials.usuario);
formData.append('senha', credentials.senha);
formData.append('identificador', credentials.identificador || 'BotTelegram');
formData.append('idUsuario', credentials.idUsuario || '1');
const response = await axios.post(
`${instance.baseURL}/Login`,
formData,
this.axiosConfig
{
...this.axiosConfig,
timeout: 10000
}
);
if (response.data && response.data.status === 'success') {
console.log(`📊 Status da resposta: ${response.status}`);
console.log(`📋 Dados da resposta:`, response.data);
if (response.data && response.data.resultado === true) {
const sessionId = response.data.sessao;
const idUsuario = response.data.id_usuario;
this.sessions.set(instance.baseURL, {
sessionId,
idUsuario,
timestamp: Date.now(),
instance
});
console.log(`✅ Login realizado com sucesso na instância: ${instance.name}`);
return { success: true, sessionId, instance };
console.log(`🎫 Sessão: ${sessionId}`);
console.log(`👤 ID do usuário: ${idUsuario}`);
return { success: true, sessionId, idUsuario, instance };
} else {
throw new Error(response.data?.message || 'Login falhou');
const errorMsg = response.data?.msg || 'Login falhou';
console.log(`❌ Erro no login: ${errorMsg}`);
throw new Error(errorMsg);
}
} catch (error) {
@@ -127,15 +140,19 @@ class TopSappAPI {
const formData = new FormData();
formData.append('sessao', session.sessionId);
formData.append('identificador', credentials.identificador || 'BotTelegram');
// Adicionar parâmetros extras
Object.keys(data).forEach(key => {
if (data[key] !== undefined && data[key] !== null) {
formData.append(key, data[key]);
// Converter idCliente para inteiro se necessário
const value = key === 'idCliente' ? parseInt(data[key], 10) : data[key];
formData.append(key, value.toString());
}
});
console.log(`📡 Fazendo requisição para: ${instance.baseURL}/${endpoint}`);
console.log(`📊 Parâmetros:`, data);
const response = await axios.post(
`${instance.baseURL}/${endpoint}`,
@@ -143,14 +160,19 @@ class TopSappAPI {
this.axiosConfig
);
if (response.data && response.data.status === 'success') {
console.log(`📊 Status da resposta: ${response.status}`);
console.log(`📋 Dados da resposta:`, response.data);
if (response.data && response.data.resultado === true) {
return {
success: true,
data: response.data,
instance: instance.name
};
} else {
throw new Error(response.data?.message || 'Requisição falhou');
const errorMsg = response.data?.msg || 'Requisição falhou';
console.log(`❌ Erro na API: ${errorMsg}`);
throw new Error(errorMsg);
}
} catch (error) {
@@ -170,19 +192,26 @@ class TopSappAPI {
const instance = this.getInstanceByLocation(location);
try {
// Obter sessão válida para pegar o idUsuario
const session = await this.getValidSession(instance, credentials);
// Tentar buscar por diferentes critérios
const searchParams = {};
const searchParams = {
idUsuario: session.idUsuario || 1 // Usar ID do usuário da sessão
};
// Se é número, pode ser CPF ou ID
if (/^\d+$/.test(query)) {
if (query.length === 11) {
searchParams.cpf = query;
if (query.length === 11 || query.length === 14) {
// CPF (11 dígitos) ou CNPJ (14 dígitos)
searchParams.cpfCnpj = query;
} else {
// ID do cliente
searchParams.idCliente = query;
}
} else {
// Buscar por nome
searchParams.nome = query;
// CPF formatado ou nome - usar o endpoint de busca geral
searchParams.cpfCnpj = query.replace(/[^\d]/g, ''); // Remove formatação
}
const result = await this.makeRequest('ObterClientes', searchParams, instance, credentials);
@@ -190,7 +219,7 @@ class TopSappAPI {
if (result.success) {
return {
success: true,
clientes: result.data.clientes || [],
clientes: result.data.dados || [], // Usar 'dados' em vez de 'clientes'
instance: result.instance
};
}
@@ -221,7 +250,85 @@ class TopSappAPI {
async obterServicosCliente(idCliente, location = '', credentials) {
const instance = this.getInstanceByLocation(location);
return await this.makeRequest('ObterClientesServicos', { idCliente }, instance, credentials);
// Obter o idUsuario da sessão atual
const session = await this.getValidSession(instance, credentials);
console.log(`🔍 Obtendo serviços para cliente: ${idCliente} (tipo: ${typeof idCliente})`);
const clienteIdInt = parseInt(idCliente, 10);
// Tentar diferentes abordagens para obter serviços do cliente
let response = null;
let tentativas = [
// Tentativa 1: Endpoint padrão com idCliente
{ endpoint: 'ObterClientesServicos', data: { idCliente: clienteIdInt, idUsuario: session.idUsuario || 1 } },
// Tentativa 2: Endpoint alternativo (se existir)
{ endpoint: 'ObterServicosCliente', data: { idCliente: clienteIdInt, idUsuario: session.idUsuario || 1 } },
// Tentativa 3: Com parâmetros alternativos
{ endpoint: 'ObterClientesServicos', data: { cliente_id: clienteIdInt, idUsuario: session.idUsuario || 1 } }
];
logger.debug('Iniciando tentativas de busca de serviços', {
clienteIdInt,
sessionId: session.idUsuario,
tentativasCount: tentativas.length
});
for (let i = 0; i < tentativas.length; i++) {
const tentativa = tentativas[i];
try {
console.log(`📋 Tentativa ${i + 1} - Endpoint: ${tentativa.endpoint}, Dados:`, tentativa.data);
logger.debug(`Tentativa ${i + 1}`, {
endpoint: tentativa.endpoint,
data: tentativa.data
});
response = await this.makeRequest(tentativa.endpoint, tentativa.data, instance, credentials);
logger.apiCall(tentativa.endpoint, tentativa.data, response);
if (response.success && response.data) {
console.log(`✅ Sucesso na tentativa ${i + 1} - Total de itens: ${response.data.dados?.length || response.data.length || 0}`);
logger.debug(`Sucesso na tentativa ${i + 1}`, {
totalItems: response.data.dados?.length || response.data.length || 0,
hasData: !!response.data
});
// Verificar se os dados são relevantes para o cliente
const dados = response.data.dados || response.data;
if (Array.isArray(dados) && dados.length > 0) {
// Verificar quantos itens realmente pertencem ao cliente
const servicosDoCliente = dados.filter(item => {
const itemClienteId = parseInt(item.idCliente || item.cliente_id || item.id_cliente || 0, 10);
return itemClienteId === clienteIdInt;
});
console.log(`🎯 Serviços filtrados para cliente ${clienteIdInt}: ${servicosDoCliente.length} de ${dados.length} total`);
logger.debug('Filtragem de serviços', {
clienteIdBuscado: clienteIdInt,
servicosEncontrados: servicosDoCliente.length,
totalServicos: dados.length,
primeiroItem: dados[0] ? {
id_cliente: dados[0].id_cliente,
idCliente: dados[0].idCliente,
cliente_id: dados[0].cliente_id
} : null
});
// Se encontrou serviços específicos do cliente, usar essa resposta
if (servicosDoCliente.length > 0 || i === tentativas.length - 1) {
break;
}
}
}
} catch (error) {
console.log(`❌ Falha na tentativa ${i + 1}: ${error.message}`);
if (i === tentativas.length - 1) {
throw error;
}
}
}
return response;
}
/**
@@ -230,19 +337,31 @@ class TopSappAPI {
async obterFaturas(idCliente, location = '', credentials) {
const instance = this.getInstanceByLocation(location);
// Obter o idUsuario da sessão atual
const session = await this.getValidSession(instance, credentials);
return await this.makeRequest('ObterDocReceber', {
idCliente,
situacao: 1 // Faturas em aberto
idUsuario: session.idUsuario || 1, // Usar ID do usuário da sessão
status: 0, // 0 = Faturas abertas
situacaoFatura: 0 // 0 = Faturas ativas
}, instance, credentials);
}
/**
* Obter dados para pagamento da fatura (linha digitável)
*/
async obterDadosFatura(idFatura, location = '', credentials) {
async obterDadosFatura(idFatura, idCliente, location = '', credentials) {
const instance = this.getInstanceByLocation(location);
return await this.makeRequest('ObterDocReceberDadosFatura', { idFatura }, instance, credentials);
// Obter o idUsuario da sessão atual
const session = await this.getValidSession(instance, credentials);
return await this.makeRequest('ObterDocReceberDadosFatura', {
idFatura,
idCliente,
idUsuario: session.idUsuario || 1
}, instance, credentials);
}
/**
@@ -257,28 +376,33 @@ class TopSappAPI {
/**
* Alterar status do serviço (bloquear/liberar)
*/
async alterarStatusServico(idServico, status, location = '', credentials) {
async alterarStatusServico(idServico, idCliente, status, location = '', credentials) {
const instance = this.getInstanceByLocation(location);
// Obter o idUsuario da sessão atual
const session = await this.getValidSession(instance, credentials);
// Status: 1 = Ativo, 2 = Bloqueado, 3 = Desativado
return await this.makeRequest('AlterarServicoStatus', {
idServico,
status
idCliente,
idUsuario: session.idUsuario || 1,
idStatus: status
}, instance, credentials);
}
/**
* Bloquear serviço do cliente
*/
async bloquearServico(idServico, location = '', credentials) {
return await this.alterarStatusServico(idServico, 2, location, credentials);
async bloquearServico(idServico, idCliente, location = '', credentials) {
return await this.alterarStatusServico(idServico, idCliente, 2, location, credentials);
}
/**
* Liberar serviço do cliente
*/
async liberarServico(idServico, location = '', credentials) {
return await this.alterarStatusServico(idServico, 1, location, credentials);
async liberarServico(idServico, idCliente, location = '', credentials) {
return await this.alterarStatusServico(idServico, idCliente, 1, location, credentials);
}
/**
+4 -4
View File
@@ -11,10 +11,10 @@ class TopSappConfig {
idUsuario: process.env.TOPSAPP_GUARAI_USERID || '1'
},
pedro_afonso: {
usuario: process.env.TOPSAPP_PEDRO_USER || '',
senha: process.env.TOPSAPP_PEDRO_PASS || '',
identificador: process.env.TOPSAPP_PEDRO_ID || 'BotTelegram',
idUsuario: process.env.TOPSAPP_PEDRO_USERID || '1'
usuario: process.env.TOPSAPP_PEDRO_AFONSO_USER || '',
senha: process.env.TOPSAPP_PEDRO_AFONSO_PASS || '',
identificador: process.env.TOPSAPP_PEDRO_AFONSO_ID || 'BotTelegram',
idUsuario: process.env.TOPSAPP_PEDRO_AFONSO_USERID || '1'
}
};
+360 -15
View File
@@ -1,5 +1,6 @@
const TopSappAPI = require('./api');
const TopSappConfig = require('./config');
const logger = require('../utils/logger');
const fs = require('fs').promises;
const path = require('path');
@@ -8,6 +9,8 @@ class TopSappManager {
this.api = new TopSappAPI();
this.config = new TopSappConfig();
logger.info('TopSappManager inicializado');
// Limpar sessões expiradas a cada 10 minutos
setInterval(() => {
this.api.clearExpiredSessions();
@@ -149,21 +152,29 @@ class TopSappManager {
const boletos = [];
for (const fatura of faturas.data.faturas || []) {
for (const fatura of faturas.data.dados || []) {
// Obter dados da fatura (linha digitável)
const dadosFatura = await this.api.obterDadosFatura(fatura.id, location, credentials);
const dadosFatura = await this.api.obterDadosFatura(fatura.id, fatura.codigo_cliente, location, credentials);
const boleto = {
id: fatura.id,
valor: fatura.valor,
vencimento: fatura.vencimento,
status: fatura.status,
descricao: fatura.descricao,
linhaDigitavel: null
status: fatura.pago === 'SIM' ? 'Pago' : 'Pendente',
descricao: fatura.historico,
periodo: this.formatarPeriodo(fatura.periodo_inicio, fatura.periodo_fim),
linhaDigitavel: null,
pixCopiaCola: null
};
if (dadosFatura.success && dadosFatura.data.linhaDigitavel) {
boleto.linhaDigitavel = dadosFatura.data.linhaDigitavel;
// Adicionar linha digitável e PIX copia e cola se obtida com sucesso
if (dadosFatura.success && dadosFatura.data.fatura) {
if (dadosFatura.data.fatura.linha_digitavel) {
boleto.linhaDigitavel = dadosFatura.data.fatura.linha_digitavel;
}
if (dadosFatura.data.fatura.qrcode) {
boleto.pixCopiaCola = dadosFatura.data.fatura.qrcode;
}
}
boletos.push(boleto);
@@ -285,12 +296,12 @@ class TopSappManager {
const resultados = [];
// Bloquear todos os serviços ativos
for (const servico of servicos.data.servicos || []) {
if (servico.status === 1) { // Apenas serviços ativos
const resultado = await this.api.bloquearServico(servico.id, location, credentials);
for (const servico of servicos.data.dados || []) {
if (servico.status === 1 || servico.estado === 'liberado') { // Apenas serviços ativos
const resultado = await this.api.bloquearServico(servico.id, idCliente, location, credentials);
resultados.push({
servicoId: servico.id,
descricao: servico.descricao,
descricao: servico.descricao || servico.nome,
resultado
});
}
@@ -335,12 +346,12 @@ class TopSappManager {
const resultados = [];
// Liberar todos os serviços bloqueados
for (const servico of servicos.data.servicos || []) {
if (servico.status === 2) { // Apenas serviços bloqueados
const resultado = await this.api.liberarServico(servico.id, location, credentials);
for (const servico of servicos.data.dados || []) {
if (servico.status === 2 || servico.estado === 'bloqueado') { // Apenas serviços bloqueados
const resultado = await this.api.liberarServico(servico.id, idCliente, location, credentials);
resultados.push({
servicoId: servico.id,
descricao: servico.descricao,
descricao: servico.descricao || servico.nome,
resultado
});
}
@@ -361,12 +372,346 @@ class TopSappManager {
}
}
/**
* Obter lista de serviços do cliente
*/
async obterServicosCliente(idCliente, location = '') {
logger.debug('obterServicosCliente chamado', {
idCliente,
location,
tipoIdCliente: typeof idCliente
});
if (!this.isReady()) {
logger.warn('TopSapp não está configurado');
return {
success: false,
error: 'TopSapp não está configurado'
};
}
try {
const credentials = this.config.getCredentialsByLocation(location);
logger.debug('Credenciais obtidas', { location, hasCredentials: !!credentials });
const servicos = await this.api.obterServicosCliente(idCliente, location, credentials);
logger.debug('Resposta da API obterServicosCliente', {
success: servicos.success,
dataLength: servicos.data?.dados?.length,
error: servicos.error
});
if (!servicos.success) {
logger.warn('API retornou erro', servicos.error);
return servicos;
}
// Converter idCliente para número para comparação
console.log(`🔍 Recebido idCliente:`, idCliente, `(tipo: ${typeof idCliente})`);
logger.debug('Processando idCliente', {
originalValue: idCliente,
originalType: typeof idCliente
});
const clienteId = parseInt(idCliente, 10);
logger.debug('idCliente convertido', {
clienteId,
isValid: !isNaN(clienteId)
});
console.log(`🔍 Convertido para clienteId:`, clienteId, `(tipo: ${typeof clienteId})`);
// Verificar se a conversão foi bem-sucedida
if (isNaN(clienteId)) {
console.log(`❌ ERRO: idCliente inválido. Valor original:`, idCliente);
return {
success: false,
error: `ID do cliente inválido: ${idCliente}`
};
}
console.log(`🔍 Manager - Filtrando serviços para cliente ID: ${clienteId}`);
// Acessar os dados da resposta
let dadosServicos = servicos.data?.dados || servicos.data || [];
// Se não é array, tentar converter
if (!Array.isArray(dadosServicos)) {
console.log(`⚠️ Dados retornados não são um array:`, typeof dadosServicos);
dadosServicos = [];
}
console.log(`📊 Total de registros retornados pela API: ${dadosServicos.length}`);
if (dadosServicos.length > 100) {
console.log(`⚠️ ALERTA: API retornou ${dadosServicos.length} registros - muito mais que o esperado para um cliente`);
// Log dos primeiros registros para debug
const amostra = dadosServicos.slice(0, 5).map(item => ({
id: item.id,
idCliente: item.idCliente || item.cliente_id || item.id_cliente,
nome: item.nome || item.descricao
}));
console.log(`🔍 Amostra dos primeiros registros:`, amostra);
}
// Filtrar RIGOROSAMENTE apenas os serviços do cliente específico
const servicosDoCliente = dadosServicos.filter(servico => {
// Tentar diferentes campos para o ID do cliente
const possiveisIds = [
servico.idCliente,
servico.cliente_id,
servico.id_cliente,
servico.clienteId,
servico.cod_cliente,
servico.codigo_cliente
];
// Verificar se algum dos IDs corresponde ao cliente
for (const id of possiveisIds) {
if (id !== undefined && id !== null) {
const servicoClienteId = parseInt(id, 10);
if (servicoClienteId === clienteId) {
return true;
}
}
}
return false;
});
console.log(`🎯 Serviços filtrados para o cliente ${clienteId}: ${servicosDoCliente.length}`);
// Se ainda assim temos muitos resultados, algo está errado
if (servicosDoCliente.length > 50) {
console.log(`❌ ERRO: Ainda temos ${servicosDoCliente.length} serviços após filtro - possível problema na API`);
// Log detalhado para investigação
const idsEncontrados = servicosDoCliente.map(s => ({
id: s.id,
idCliente: s.idCliente || s.cliente_id || s.id_cliente,
nome: s.nome || s.descricao
})).slice(0, 10);
console.log(`🔍 IDs encontrados (primeiros 10):`, idsEncontrados);
}
const servicosFormatados = servicosDoCliente.map(servico => ({
id: servico.id || servico.id_servico,
nome: servico.nome || servico.descricao || 'Serviço sem nome',
status: servico.estado || (servico.status === 1 ? 'liberado' : 'bloqueado'),
statusNumerico: servico.status,
valorMensal: servico.valor_mensal || '0.00',
tipoServico: servico.tipo_servico || 'Não informado',
dataAtivacao: servico.data_ativacao,
observacoes: servico.observacoes,
clienteId: servico.idCliente || servico.cliente_id || servico.id_cliente
}));
return {
success: true,
servicos: servicosFormatados,
total: servicosFormatados.length,
instancia: servicos.instance,
debug: {
totalRetornadoAPI: dadosServicos.length,
totalFiltrado: servicosFormatados.length,
clienteId: clienteId
}
};
} catch (error) {
console.error(`❌ Erro ao obter serviços do cliente ${idCliente}:`, error);
return {
success: false,
error: error.message
};
}
}
/**
* Bloquear serviço específico
*/
async bloquearServicoEspecifico(idCliente, idServico, location = '') {
if (!this.isReady()) {
return {
success: false,
error: 'TopSapp não está configurado'
};
}
try {
const credentials = this.config.getCredentialsByLocation(location);
const resultado = await this.api.bloquearServico(idServico, idCliente, location, credentials);
return {
...resultado,
instancia: location
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
/**
* Liberar serviço específico
*/
async liberarServicoEspecifico(idCliente, idServico, location = '') {
if (!this.isReady()) {
return {
success: false,
error: 'TopSapp não está configurado'
};
}
try {
const credentials = this.config.getCredentialsByLocation(location);
const resultado = await this.api.liberarServico(idServico, idCliente, location, credentials);
return {
...resultado,
instancia: location
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
/**
* Alterar status de um serviço específico
*/
async alterarStatusServico(idServico, acao, location = '') {
if (!this.isReady()) {
return {
success: false,
error: 'TopSapp não está configurado'
};
}
try {
const credentials = this.config.getCredentialsByLocation(location);
// Determinar o status numérico baseado na ação
let status;
if (acao === 'bloquear') {
status = 2; // Bloqueado
} else if (acao === 'liberar') {
status = 1; // Ativo
} else {
return {
success: false,
error: 'Ação inválida. Use "bloquear" ou "liberar"'
};
}
// Primeiro, obter informações do serviço para saber o idCliente
const servicos = await this.api.obterServicosCliente(null, location, credentials);
if (!servicos.success) {
return {
success: false,
error: 'Erro ao obter informações do serviço'
};
}
// Procurar o serviço específico
const servicoEncontrado = (servicos.data.dados || []).find(s =>
s.id_servico == idServico || s.id == idServico
);
if (!servicoEncontrado) {
return {
success: false,
error: 'Serviço não encontrado'
};
}
const idCliente = servicoEncontrado.id_cliente;
logger.debug('Alterando status do serviço', {
idServico,
idCliente,
status,
acao
});
const resultado = await this.api.alterarStatusServico(idServico, idCliente, status, location, credentials);
return {
...resultado,
instancia: location
};
} catch (error) {
logger.error('Erro ao alterar status do serviço', error);
return {
success: false,
error: error.message
};
}
}
/**
* Obter instâncias disponíveis
*/
getInstances() {
return this.api.getInstances();
}
/**
* Formatar período de datas para DD/MM/YYYY
*/
formatarPeriodo(dataInicio, dataFim) {
const formatarData = (data) => {
if (!data) return '';
// Se já está no formato DD/MM/YYYY, retornar como está
if (data.match(/^\d{2}\/\d{2}\/\d{4}$/)) {
return data;
}
// Se está no formato YYYY-MM-DD, converter
if (data.match(/^\d{4}-\d{2}-\d{2}$/)) {
const [ano, mes, dia] = data.split('-');
return `${dia}/${mes}/${ano}`;
}
// Tentar converter outros formatos
try {
const dataObj = new Date(data);
if (!isNaN(dataObj.getTime())) {
const dia = String(dataObj.getDate()).padStart(2, '0');
const mes = String(dataObj.getMonth() + 1).padStart(2, '0');
const ano = dataObj.getFullYear();
return `${dia}/${mes}/${ano}`;
}
} catch (error) {
console.warn('Erro ao formatar data:', data, error);
}
return data;
};
const inicio = formatarData(dataInicio);
const fim = formatarData(dataFim);
if (inicio && fim) {
return `${inicio} a ${fim}`;
} else if (inicio) {
return inicio;
} else if (fim) {
return fim;
} else {
return 'Período não informado';
}
}
}
module.exports = TopSappManager;
+173
View File
@@ -0,0 +1,173 @@
const fs = require('fs');
const path = require('path');
class Logger {
constructor() {
this.logDir = path.join(__dirname, '../../logs');
this.logFile = path.join(this.logDir, 'bot-debug.log');
this.maxLogSize = 10 * 1024 * 1024; // 10MB
this.maxBackups = 5;
// Criar diretório de logs se não existir
this.ensureLogDir();
// Só ativa logs se estiver em modo debug
this.isDebug = process.env.NODE_ENV === 'development' || process.env.DEBUG === 'true';
}
ensureLogDir() {
if (!fs.existsSync(this.logDir)) {
fs.mkdirSync(this.logDir, { recursive: true });
}
}
formatMessage(level, message, data = null) {
const timestamp = new Date().toISOString();
let logLine = `[${timestamp}] [${level}] ${message}`;
if (data) {
if (typeof data === 'object') {
logLine += '\n' + JSON.stringify(data, null, 2);
} else {
logLine += ` - ${data}`;
}
}
return logLine + '\n';
}
rotateLogIfNeeded() {
if (!fs.existsSync(this.logFile)) {
return;
}
const stats = fs.statSync(this.logFile);
if (stats.size > this.maxLogSize) {
// Fazer backup dos logs existentes
for (let i = this.maxBackups - 1; i >= 0; i--) {
const oldFile = i === 0 ? this.logFile : `${this.logFile}.${i}`;
const newFile = `${this.logFile}.${i + 1}`;
if (fs.existsSync(oldFile)) {
if (i === this.maxBackups - 1) {
fs.unlinkSync(oldFile); // Deletar o mais antigo
} else {
fs.renameSync(oldFile, newFile);
}
}
}
}
}
writeLog(level, message, data = null) {
if (!this.isDebug) {
return; // Só loga em modo debug
}
try {
this.rotateLogIfNeeded();
const logMessage = this.formatMessage(level, message, data);
// Escrever no arquivo de log
fs.appendFileSync(this.logFile, logMessage);
// Também mostrar no console se for erro ou warning
if (level === 'ERROR' || level === 'WARN') {
console.log(logMessage.trim());
}
} catch (error) {
console.error('Erro ao escrever log:', error);
}
}
debug(message, data = null) {
this.writeLog('DEBUG', message, data);
}
info(message, data = null) {
this.writeLog('INFO', message, data);
}
warn(message, data = null) {
this.writeLog('WARN', message, data);
}
error(message, data = null) {
this.writeLog('ERROR', message, data);
}
// Métodos específicos para o bot
botCommand(userId, command, data = null) {
this.debug(`Comando recebido - User: ${userId}, Command: ${command}`, data);
}
apiCall(endpoint, params = null, response = null) {
this.debug(`API Call - Endpoint: ${endpoint}`, {
params,
responseStatus: response?.status,
responseDataLength: response?.data?.length
});
}
botError(userId, error, context = null) {
this.error(`Bot Error - User: ${userId}`, {
error: error.message,
stack: error.stack,
context
});
}
topsappOperation(operation, clientId, result = null) {
this.debug(`TopSapp Operation - ${operation} - Cliente: ${clientId}`, {
success: result?.success,
dataCount: result?.data?.length || result?.servicos?.length || result?.clientes?.length,
error: result?.error
});
}
// Limpar logs antigos (método utilitário)
clearLogs() {
if (!this.isDebug) {
return;
}
try {
if (fs.existsSync(this.logFile)) {
fs.unlinkSync(this.logFile);
}
// Limpar backups também
for (let i = 1; i <= this.maxBackups; i++) {
const backupFile = `${this.logFile}.${i}`;
if (fs.existsSync(backupFile)) {
fs.unlinkSync(backupFile);
}
}
this.info('Logs limpos com sucesso');
} catch (error) {
console.error('Erro ao limpar logs:', error);
}
}
// Obter últimas linhas do log
getRecentLogs(lines = 100) {
if (!this.isDebug || !fs.existsSync(this.logFile)) {
return [];
}
try {
const content = fs.readFileSync(this.logFile, 'utf8');
const allLines = content.split('\n').filter(line => line.trim());
return allLines.slice(-lines);
} catch (error) {
console.error('Erro ao ler logs:', error);
return [];
}
}
}
// Singleton instance
const logger = new Logger();
module.exports = logger;