require("dotenv").config(); const fs = require('fs') const { execSync } = require('child_process'); const jsondb = require("node-json-db"); // Init json db var db = new jsondb.JsonDB(new jsondb.Config("known-hosts", true, false, '/', true)); const ejs = require("ejs") const Discord = require("discord.js"); const { userInfo } = require("os"); const client = new Discord.Client({intents: "DirectMessages"}); function extractTextFromRawText(rawText) { // Strip everything before the line with "[default]". and everything before two newlines, and strip [default] const startIndex = rawText.indexOf("[default]"); const endIndex = rawText.indexOf("\n\n", startIndex); const extractedText = rawText.slice(startIndex, endIndex).replace("[default]\n", ""); return extractedText; } // Split data by newlines, then make key value pairs split by = and make into object function parseVoicemailData(data) { const lines = data.split("\n"); const voicemailData = {}; //console.log(lines) lines.forEach((line) => { var [key, value] = line.split("="); if (!value) return; value = value.split(","); voicemailData[key] = value; }); return voicemailData; } function getUsers() { var vmData = fs.readFileSync(process.env.DEV ? "./dev/voicemail.conf" : "/etc/asterisk/voicemail.conf").toString() voicemailData = parseVoicemailData(extractTextFromRawText(vmData)); return voicemailData; } function getEndpoints() { if (!process.env.DEV) { const currentEndpoint = execSync('asterisk -x "pjsip show endpoints"').toString(); const endpoints = currentEndpoint.split("\n"); return endpoints; } else { return fs.readFileSync("./dev/endpoints.txt").toString().split("\n"); } } function parseEndpointData(data) { const endpointRegex = /Endpoint:\s+(\d+\/\d+)\s+([A-Za-z\s]+)\s+(\d+)/g; const contactRegex = /Contact:\s*(\d+\/\S+)\s*;?.*\s*(\S+)\s+(\S+)\s+(\d+\.\d+)/g; let endpointObject = {}; // Match endpoint details const endpointMatch = endpointRegex.exec(data); if (endpointMatch) { endpointObject.endpoint = endpointMatch[1].split("/")[1].trim(); // e.g. 1000/1000 endpointObject.state = endpointMatch[2].trim(); // e.g. "Not in use" endpointObject.channelCount = parseInt(endpointMatch[3], 10); // e.g. 0 } if (!endpointObject.endpoint) return undefined; endpointObject.contacts = []; // Match contact details let contactMatch; while ((contactMatch = contactRegex.exec(data)) !== null) { endpointObject.contacts.push({ host: contactMatch[1].split('@')[1]?.split(";")[0] || "0.0.0.0:0000", // Extract host from the format "sip:1000@ip:port" status: contactMatch[3], // "Avail" ping: parseFloat(contactMatch[4]), // ping value }); } return endpointObject; } function parseEndpoints(endpoints) { // Ignore first 12 lines endpoints = endpoints.slice(12); // Endpoints split by double newline endpoints = endpoints.join("\n").split("\n\n"); const users = getUsers(); const parsedEndpoints = endpoints.map(endpoint => { const parsedEndpoint = parseEndpointData(endpoint); if (parsedEndpoint && users[parsedEndpoint.endpoint]) { parsedEndpoint.discordId = users[parsedEndpoint.endpoint][2]; parsedEndpoint.name = users[parsedEndpoint.endpoint][1]; } return parsedEndpoint; }).filter(endpoint => endpoint !== undefined); return parsedEndpoints } async function checkForNewEndpoints() { const parsed = parseEndpoints(getEndpoints()); let newContacts = []; for (const endpoint of parsed) { const seen = await db.getObjectDefault(`/seen/${endpoint.endpoint}`, []); const newEndpointContacts = endpoint.contacts.filter(contact => !seen.includes(contact.host.split(":")[0])); const uniqueHosts = [...new Set(newEndpointContacts.map(contact => contact.host.split(":")[0]))]; uniqueHosts.forEach(host => { db.push(`/seen/${endpoint.endpoint}[]`, host); }); resp = newEndpointContacts.length > 0 ? { endpoint, newContacts: newEndpointContacts, } : []; newContacts = newContacts.concat(resp); } return newContacts; }; function notifyNew() { console.log("Checking...") checkForNewEndpoints().then(newContacts => { if (newContacts.length > 0) { newContacts.forEach(async newContact => { const { endpoint, newContacts } = newContact; const user = await client.users.fetch(endpoint.discordId).catch(() => null); if (user) { const contactList = newContacts.map(contact => `${contact.host} - ${contact.status} - ${contact.ping}`).join("\n"); user.send(`New registrations for ${endpoint.endpoint} (${endpoint.name}) from unknown IPs:\n${contactList}`); console.log(`Notified ${endpoint.endpoint} (${endpoint.name}) of new contacts`); } }); } }); } //console.log(Object.fromEntries(Object.entries(getUsers()).map(([key, value]) => [key, value[1]]))) function renderStatusPage() { ejs.renderFile("status.ejs", {endpoints: parseEndpoints(getEndpoints()), userInfo: Object.fromEntries(Object.entries(getUsers()).map(([key, value]) => [key, value[1]]))}, (err, str) => { if (err) { console.error(err); } else { fs.writeFileSync(process.env.DEV ? "./dev/index.html" : "/var/www/html/status/index.html", str); } }); const jsonOutput = { endpoints: parseEndpoints(getEndpoints()), userInfo: Object.fromEntries(Object.entries(getUsers()).map(([key, value]) => [key, value[1]])) }; jsonOutput.endpoints.forEach(endpoint => { const avgPing = endpoint.contacts.reduce((sum, contact) => sum + contact.ping, 0) / endpoint.contacts.length; endpoint.contacts = [{ ping: isNaN(avgPing) ? 'No Data' : avgPing.toFixed(2) }]; }); fs.writeFileSync(process.env.DEV ? "./dev/status.json" : "/var/www/html/status/status.json", JSON.stringify(jsonOutput, null, 2)); } client.on("ready", () => { console.log(`Logged in as ${client.user.displayName}!`); notifyNew(); renderStatusPage(); setInterval(notifyNew, 10*1000); setInterval(renderStatusPage, 5*1000); }); if (process.env.DEV) { console.log("DEV") renderStatusPage(); notifyNew(); } else { console.log("PROD") client.login(process.env.DISCORD_TOKEN) }