cgnat: add função

Signed-off-by: Rodrigo Martins <rodrigo@martinsevieira.adv.br>
This commit is contained in:
Rodrigo Martins committed 2026-04-09 20:37:26 -03:00
1 parent f20c586f94
commit 6a5742ad7c
6 files changed
+10330 -2

No files matched your search

+208 -1
View File
@@ -7,6 +7,7 @@ const { GenieACSManager } = require('../genieacs');
const config = require('../config/config');
const logger = require('../utils/logger');
const DateProcessor = require('../utils/dateProcessor');
const cgnatParser = require('../cgn_list/parser');
class BotHandler {
constructor(bot) {
@@ -287,6 +288,11 @@ class BotHandler {
console.log(`🔄 Executando handleReiniciarOnu com target: ${target}`);
await this.handleReiniciarOnu(chatId, target);
break;
case 'ver_cgnat':
console.log(`🌐 Executando handleVerCgnat com target: ${target}`);
await this.handleVerCgnat(chatId, userId, target);
break;
case 'conectar':
await this.handleConnect(chatId, target);
@@ -1800,11 +1806,15 @@ Digite /help para ver todos os comandos disponíveis!
await this.executeCustomCommand(chatId, session.connectionId, text);
this.userSessions.delete(userId);
return;
case 'waiting_username_disconnect':
await this.handleDisconnectUser(chatId, text.trim());
this.userSessions.delete(userId);
return;
case 'cgnat_aguardando_datetime':
await this.handleCgnatDatetimeInput(chatId, userId, text.trim(), session);
return;
}
}
@@ -2142,6 +2152,11 @@ Digite /help para ver todos os comandos disponíveis!
await this._configurarRoteadorPorServico(chatId, userId, servico, location);
}
}
} else if (data.startsWith('buscar_cgnat_user_')) {
console.log(`🔍 Condição buscar_cgnat_user_ atendida`);
await this.bot.answerCallbackQuery(callbackQuery.id);
const login = data.replace('buscar_cgnat_user_', '');
await this.handleBuscarCliente(chatId, login);
} else if (data.startsWith('diag_cliente_')) {
console.log(`🔍 Condição diag_cliente_ atendida`);
await this.handleDiagClienteCallback(callbackQuery, data);
@@ -8858,6 +8873,198 @@ Digite /help para ver todos os comandos disponíveis!
}
}
/**
* Consulta CGNAT: dado IP público + porta opcional, informa o IP privado
* e opcionalmente busca no TopSapp quem estava usando aquele IP.
*/
async handleVerCgnat(chatId, userId, target) {
if (!target) {
await this.bot.sendMessage(chatId,
'🌐 *Consulta CGNAT*\n\n' +
'Informe o IP público e, opcionalmente, a porta:\n\n' +
'• `cgnat 131.0.193.0 1500` — busca por IP + porta\n' +
'• `cgnat 131.0.193.0` — lista todos os IPs privados do IP público',
{ parse_mode: 'Markdown' }
);
return;
}
// Parsear target: "IP" ou "IP PORTA"
const parts = target.trim().split(/\s+/);
const publicIp = parts[0];
const porta = parts[1] ? parseInt(parts[1], 10) : null;
// Validar IP
if (!/^\d+\.\d+\.\d+\.\d+$/.test(publicIp)) {
await this.bot.sendMessage(chatId, `❌ IP inválido: \`${publicIp}\``, { parse_mode: 'Markdown' });
return;
}
if (!cgnatParser.hasPublicIp(publicIp)) {
await this.bot.sendMessage(chatId, `❌ IP \`${publicIp}\` não encontrado na tabela CGNAT.`, { parse_mode: 'Markdown' });
return;
}
if (porta !== null) {
// Busca por IP + porta
const entry = cgnatParser.lookupByPort(publicIp, porta);
if (!entry) {
await this.bot.sendMessage(chatId,
`❌ Porta *${porta}* não encontrada nos ranges do IP \`${publicIp}\`.`,
{ parse_mode: 'Markdown' }
);
return;
}
await this.bot.sendMessage(chatId,
`🌐 *Resultado CGNAT*\n\n` +
`📡 IP Público: \`${publicIp}\`\n` +
`🔌 Porta: \`${porta}\`\n` +
`🖥️ IP Privado: \`${entry.privateIP}\`\n` +
`📊 Range de portas: \`${entry.portStart} – ${entry.portEnd}\`\n\n` +
`🔍 Deseja consultar quem usava este IP no TopSapp?\n` +
`Digite a data e hora do acesso no formato:\n` +
`\`DD/MM/AAAA HH:MM\`\n\n` +
`Ou envie *cancelar* para encerrar.`,
{ parse_mode: 'Markdown' }
);
this.userSessions.set(userId, {
type: 'cgnat_aguardando_datetime',
privateIP: entry.privateIP,
publicIP: publicIp,
porta
});
} else {
// Listar todos os IPs privados
const entries = cgnatParser.lookupAll(publicIp);
if (!entries || entries.length === 0) {
await this.bot.sendMessage(chatId, `❌ Nenhum IP privado encontrado para \`${publicIp}\`.`, { parse_mode: 'Markdown' });
return;
}
let msg = `🌐 *IPs privados do IP público \`${publicIp}\`*\n\n`;
for (const e of entries) {
msg += `• \`${e.privateIP}\` — portas \`${e.portStart}\` a \`${e.portEnd}\`\n`;
}
msg += `\n💡 Para identificar o cliente, informe também a porta:\n\`cgnat ${publicIp} PORTA\``;
await this.bot.sendMessage(chatId, msg, { parse_mode: 'Markdown' });
}
}
/**
* Recebe a data/hora e consulta o TopSapp por acessos do IP privado
*/
async handleCgnatDatetimeInput(chatId, userId, text, session) {
if (/^cancelar$/i.test(text)) {
this.userSessions.delete(userId);
await this.bot.sendMessage(chatId, '❌ Consulta cancelada.');
return;
}
// Aceitar formatos: "DD/MM/AAAA HH:MM" ou "DD/MM/AAAA"
const dtMatch = text.match(/^(\d{2}\/\d{2}\/\d{4})(?:\s+(\d{2}:\d{2}))?$/);
if (!dtMatch) {
await this.bot.sendMessage(chatId,
'❌ Formato inválido. Use `DD/MM/AAAA HH:MM` ou `DD/MM/AAAA`.\n' +
'Ou envie *cancelar* para encerrar.',
{ parse_mode: 'Markdown' }
);
return;
}
this.userSessions.delete(userId);
const dataStr = dtMatch[1]; // DD/MM/AAAA
const horaStr = dtMatch[2]; // HH:MM ou undefined
await this.bot.sendMessage(chatId,
`🔍 Consultando acessos de \`${session.privateIP}\` em ${dataStr}${horaStr ? ' ' + horaStr : ''}...`,
{ parse_mode: 'Markdown' }
);
const result = await this.topsapp.obterAcessosPorEstacao(
session.privateIP,
dataStr,
dataStr
);
if (!result.success) {
await this.bot.sendMessage(chatId,
`❌ Erro ao consultar TopSapp: ${result.error}`,
{ parse_mode: 'Markdown' }
);
return;
}
const acessos = result.data?.dados || [];
if (acessos.length === 0) {
await this.bot.sendMessage(chatId,
`ℹ️ Nenhum acesso encontrado para o IP \`${session.privateIP}\` em ${dataStr}.`,
{ parse_mode: 'Markdown' }
);
return;
}
// Filtrar por hora se informada
let filtrados = acessos;
if (horaStr) {
const [hh, mm] = horaStr.split(':').map(Number);
const refMs = hh * 3600000 + mm * 60000;
const janela = 3600000; // ±1 hora
filtrados = acessos.filter(a => {
const inicioMatch = a.inicio ? a.inicio.match(/(\d{2}):(\d{2})/) : null;
if (!inicioMatch) return true;
const aMs = parseInt(inicioMatch[1]) * 3600000 + parseInt(inicioMatch[2]) * 60000;
return Math.abs(aMs - refMs) <= janela;
});
}
if (filtrados.length === 0) {
await this.bot.sendMessage(chatId,
`ℹ️ Nenhum acesso encontrado para \`${session.privateIP}\` próximo às ${horaStr} em ${dataStr}.`,
{ parse_mode: 'Markdown' }
);
return;
}
let msg = `🌐 *Acessos CGNAT — \`${session.privateIP}\`*\n`;
msg += `📅 Data: ${dataStr}${horaStr ? ' ± 1h de ' + horaStr : ''}\n\n`;
for (const a of filtrados.slice(0, 10)) {
const inicio = a.inicio ? a.inicio.replace(/\.\d+.*$/, '').replace('T', ' ') : '—';
const termino = a.termino ? a.termino.replace(/\.\d+.*$/, '').replace('T', ' ') : 'ativo';
msg += `👤 *${a.usuario || '—'}*\n`;
msg += ` 🕐 ${inicio} → ${termino}\n`;
if (a.ip) msg += ` 🖥️ IP: \`${a.ip}\`\n`;
if (a.estacao) msg += ` 📡 Estação: ${a.estacao}\n`;
msg += '\n';
}
if (filtrados.length > 10) {
msg += `_... e mais ${filtrados.length - 10} registro(s)._\n`;
}
await this.bot.sendMessage(chatId, msg, { parse_mode: 'Markdown' });
// Se encontrou usuário(s), oferecer busca de nome do cliente
const logins = [...new Set(filtrados.slice(0, 10).map(a => a.usuario).filter(Boolean))];
if (logins.length === 1) {
const keyboard = {
inline_keyboard: [[
{ text: `🔍 Ver dados de ${logins[0]}`, callback_data: `buscar_cgnat_user_${logins[0]}` },
{ text: '❌ Fechar', callback_data: 'cancel_action' }
]]
};
await this.bot.sendMessage(chatId, '💡 Deseja ver os dados cadastrais do cliente?', {
reply_markup: keyboard
});
}
}
/**
* Reiniciar ONU Datacom — busca em todas as OLTs
*/