pbx-status/index.js
2025-03-17 23:11:07 -06:00

178 lines
6 KiB
JavaScript

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)
}