1
0
mirror of https://github.com/KubaPro010/fm-dx-webserver.git synced 2026-02-26 22:13:53 +01:00

Not sure which has better code, firmware, or this? This is okay, but toy lang. Firmware was horrible.

This commit is contained in:
2026-02-23 23:15:15 +01:00
parent d0a26d2346
commit 410c39a6b8
25 changed files with 978 additions and 512 deletions

View File

@@ -37,7 +37,7 @@ function findServerFiles(plugins) {
if (plugin.endsWith('.js')) {
plugin = plugin.slice(0, -3);
}
const pluginPath = path.join(__dirname, '..', 'plugins', `${plugin}_server.js`);
if (fs.existsSync(pluginPath) && fs.statSync(pluginPath).isFile()) {
results.push(pluginPath);
@@ -79,12 +79,13 @@ const terminalWidth = readline.createInterface({
}).output.columns;
// Couldn't get figlet.js or something like that?
console.log(`\x1b[32m
_____ __ __ ______ __ __ __ _
| ___| \\/ | | _ \\ \\/ / \\ \\ / /__| |__ ___ ___ _ ____ _____ _ __
_____ __ __ ______ __ __ __ _
| ___| \\/ | | _ \\ \\/ / \\ \\ / /__| |__ ___ ___ _ ____ _____ _ __
| |_ | |\\/| |_____| | | \\ / \\ \\ /\\ / / _ \\ '_ \\/ __|/ _ \\ '__\\ \\ / / _ \\ '__|
| _| | | | |_____| |_| / \\ \\ V V / __/ |_) \\__ \\ __/ | \\ V / __/ |
|_| |_| |_| |____/_/\\_\\ \\_/\\_/ \\___|_.__/|___/\\___|_| \\_/ \\___|_|
| _| | | | |_____| |_| / \\ \\ V V / __/ |_) \\__ \\ __/ | \\ V / __/ |
|_| |_| |_| |____/_/\\_\\ \\_/\\_/ \\___|_.__/|___/\\___|_| \\_/ \\___|_|
`);
console.log('\x1b[32m\x1b[2mby Noobish @ \x1b[4mFMDX.org\x1b[0m');
console.log("v" + pjson.version)
@@ -124,21 +125,17 @@ setInterval(() => {
logWarn('Communication lost from ' + serverConfig.xdrd.comPort + ', force closing serialport.');
setTimeout(() => {
serialport.close((err) => {
if (err) {
logError('Error closing serialport: ', err.message);
}
if (err) logError('Error closing serialport: ', err.message);
});
}, 1000);
} else {
logWarn('Communication lost from ' + serverConfig.xdrd.comPort + '.');
}
} else logWarn('Communication lost from ' + serverConfig.xdrd.comPort + '.');
}
}, 2000);
// Serial Connection
function connectToSerial() {
if (serverConfig.xdrd.wirelessConnection === false) {
if (serverConfig.xdrd.wirelessConnection === true) return;
// Configure the SerialPort with DTR and RTS options
serialport = new SerialPort({
path: serverConfig.xdrd.comPort,
@@ -157,13 +154,13 @@ if (serverConfig.xdrd.wirelessConnection === false) {
}, 5000);
return;
}
logInfo('Using COM device: ' + serverConfig.xdrd.comPort);
dataHandler.state.isSerialportAlive = true;
setTimeout(() => {
serialport.write('x\n');
}, 3000);
setTimeout(() => {
serialport.write('Q0\n');
serialport.write('M0\n');
@@ -175,9 +172,7 @@ if (serverConfig.xdrd.wirelessConnection === false) {
dataHandler.dataToSend.freq = Number(serverConfig.defaultFreq).toFixed(3);
} else if (dataHandler.state.lastFrequencyAlive && dataHandler.state.isSerialportRetrying) { // Serialport retry code when port is open but communication is lost
serialport.write('T' + (dataHandler.state.lastFrequencyAlive * 1000) + '\n');
} else {
serialport.write('T87500\n');
}
} else serialport.write('T87500\n');
dataHandler.state.isSerialportRetrying = false;
serialport.write('A0\n');
@@ -185,24 +180,17 @@ if (serverConfig.xdrd.wirelessConnection === false) {
serialport.write('W0\n');
serverConfig.webserver.rdsMode ? serialport.write('D1\n') : serialport.write('D0\n');
// cEQ and iMS combinations
if (serverConfig.ceqStartup === "0" && serverConfig.imsStartup === "0") {
serialport.write("G00\n"); // Both Disabled
} else if (serverConfig.ceqStartup === "1" && serverConfig.imsStartup === "0") {
serialport.write(`G10\n`);
} else if (serverConfig.ceqStartup === "0" && serverConfig.imsStartup === "1") {
serialport.write(`G01\n`);
} else if (serverConfig.ceqStartup === "1" && serverConfig.imsStartup === "1") {
serialport.write("G11\n"); // Both Enabled
}
if (serverConfig.ceqStartup === "0" && serverConfig.imsStartup === "0") serialport.write("G00\n"); // Both Disabled
else if (serverConfig.ceqStartup === "1" && serverConfig.imsStartup === "0") serialport.write(`G10\n`);
else if (serverConfig.ceqStartup === "0" && serverConfig.imsStartup === "1") serialport.write(`G01\n`);
else if (serverConfig.ceqStartup === "1" && serverConfig.imsStartup === "1") serialport.write("G11\n"); // Both Enabled
// Handle stereo mode
if (serverConfig.stereoStartup === "1") {
serialport.write("B1\n"); // Mono
}
serverConfig.audio.startupVolume
? serialport.write('Y' + (serverConfig.audio.startupVolume * 100).toFixed(0) + '\n')
if (serverConfig.stereoStartup === "1") serialport.write("B1\n"); // Mono
serverConfig.audio.startupVolume
? serialport.write('Y' + (serverConfig.audio.startupVolume * 100).toFixed(0) + '\n')
: serialport.write('Y100\n');
}, 6000);
serialport.on('data', (data) => {
helpers.resolveDataBuffer(data, wss, rdsWss);
});
@@ -222,7 +210,6 @@ if (serverConfig.xdrd.wirelessConnection === false) {
});
return serialport;
}
}
// xdrd connection
let authFlags = {};
@@ -233,7 +220,7 @@ function connectToXdrd() {
if (xdrd.wirelessConnection && configExists()) {
client.connect(xdrd.xdrdPort, xdrd.xdrdIp, () => {
logInfo('Connection to xdrd established successfully.');
authFlags = {
authMsg: false,
firstClient: false,
@@ -247,7 +234,7 @@ function connectToXdrd() {
client.on('data', (data) => {
const { xdrd } = serverConfig;
helpers.resolveDataBuffer(data, wss, rdsWss);
if (authFlags.authMsg == true && authFlags.messageCount > 1) {
return;
@@ -266,9 +253,8 @@ client.on('data', (data) => {
if (line.startsWith('a')) {
authFlags.authMsg = true;
logWarn('Authentication with xdrd failed. Is your password set correctly?');
} else if (line.startsWith('o1,')) {
authFlags.firstClient = true;
} else if (line.startsWith('T') && line.length <= 7) {
} else if (line.startsWith('o1,')) authFlags.firstClient = true;
else if (line.startsWith('T') && line.length <= 7) {
const freq = line.slice(1) / 1000;
dataHandler.dataToSend.freq = freq.toFixed(3);
} else if (line.startsWith('OK')) {
@@ -306,9 +292,7 @@ client.on('close', () => {
setTimeout(function () {
connectToXdrd();
}, 2000)
} else {
logWarn('Disconnected from xdrd.');
}
} else logWarn('Disconnected from xdrd.');
});
client.on('error', (err) => {
@@ -369,9 +353,7 @@ wss.on('connection', (ws, request) => {
return;
}
if (clientIp && clientIp.includes(',')) {
clientIp = clientIp.split(',')[0].trim();
}
if (clientIp && clientIp.includes(',')) clientIp = clientIp.split(',')[0].trim();
// Per-IP limit connection open
if (clientIp) {
@@ -380,7 +362,7 @@ wss.on('connection', (ws, request) => {
clientIp === '::1' ||
clientIp === '::ffff:127.0.0.1' ||
clientIp.startsWith('192.168.') ||
clientIp.startsWith('10.') ||
clientIp.startsWith('10.') ||
clientIp.startsWith('172.16.')
);
if (!isLocalIp) {
@@ -419,7 +401,7 @@ wss.on('connection', (ws, request) => {
if (currentUsers === 1 && serverConfig.autoShutdown === true && serverConfig.xdrd.wirelessConnection) {
serverConfig.xdrd.wirelessConnection ? connectToXdrd() : serialport.write('x\n');
}
});
});
const userCommands = {};
let lastWarn = { time: 0 };
@@ -428,36 +410,34 @@ wss.on('connection', (ws, request) => {
const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '18', 'text');
if (!clientIp.includes("127.0.0.1")) {
if (((command.startsWith('X') || command.startsWith('Y')) && !request.session.isAdminAuthenticated) ||
if (((command.startsWith('X') || command.startsWith('Y')) && !request.session.isAdminAuthenticated) ||
((command.startsWith('F') || command.startsWith('W')) && serverConfig.bwSwitch === false)) {
logWarn(`User \x1b[90m${clientIp}\x1b[0m attempted to send a potentially dangerous command: ${command.slice(0, 64)}.`);
return;
}
}
if (command.includes("\'")) {
return;
}
if (command.includes("\'")) return;
const { isAdminAuthenticated, isTuneAuthenticated } = request.session || {};
if (command.startsWith('w') && (isAdminAuthenticated || isTuneAuthenticated)) {
switch (command) {
case 'wL1':
if (isAdminAuthenticated) serverConfig.lockToAdmin = true;
case 'wL1':
if (isAdminAuthenticated) serverConfig.lockToAdmin = true;
break;
case 'wL0':
if (isAdminAuthenticated) serverConfig.lockToAdmin = false;
case 'wL0':
if (isAdminAuthenticated) serverConfig.lockToAdmin = false;
break;
case 'wT0':
serverConfig.publicTuner = true;
if(!isAdminAuthenticated) tunerLockTracker.delete(ws);
case 'wT0':
serverConfig.publicTuner = true;
if(!isAdminAuthenticated) tunerLockTracker.delete(ws);
break;
case 'wT1':
case 'wT1':
serverConfig.publicTuner = false;
if(!isAdminAuthenticated) tunerLockTracker.set(ws, true);
break;
default:
default:
break;
}
}
@@ -465,15 +445,11 @@ wss.on('connection', (ws, request) => {
if (command.startsWith('T')) {
const tuneFreq = Number(command.slice(1)) / 1000;
const { tuningLimit, tuningLowerLimit, tuningUpperLimit } = serverConfig.webserver;
if (tuningLimit && (tuneFreq < tuningLowerLimit || tuneFreq > tuningUpperLimit) || isNaN(tuneFreq)) {
return;
}
if (tuningLimit && (tuneFreq < tuningLowerLimit || tuneFreq > tuningUpperLimit) || isNaN(tuneFreq)) return;
}
if ((serverConfig.publicTuner && !serverConfig.lockToAdmin) || isAdminAuthenticated || (!serverConfig.publicTuner && !serverConfig.lockToAdmin && isTuneAuthenticated)) {
output.write(`${command}\n`);
}
if ((serverConfig.publicTuner && !serverConfig.lockToAdmin) || isAdminAuthenticated || (!serverConfig.publicTuner && !serverConfig.lockToAdmin && isTuneAuthenticated)) output.write(`${command}\n`);
});
ws.on('close', (code, reason) => {
@@ -484,7 +460,7 @@ wss.on('connection', (ws, request) => {
clientIp === '::1' ||
clientIp === '::ffff:127.0.0.1' ||
clientIp.startsWith('192.168.') ||
clientIp.startsWith('10.') ||
clientIp.startsWith('10.') ||
clientIp.startsWith('172.16.')
);
if (!isLocalIp) {
@@ -496,94 +472,69 @@ wss.on('connection', (ws, request) => {
if (clientIp !== '::ffff:127.0.0.1' || (request.connection && request.connection.remoteAddress && request.connection.remoteAddress !== '::ffff:127.0.0.1') || (request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) {
currentUsers--;
}
dataHandler.showOnlineUsers(currentUsers);
dataHandler.showOnlineUsers(currentUsers);
const index = storage.connectedUsers.findIndex(user => user.ip === clientIp);
if (index !== -1) {
storage.connectedUsers.splice(index, 1);
}
const index = storage.connectedUsers.findIndex(user => user.ip === clientIp);
if (index !== -1) storage.connectedUsers.splice(index, 1);
if (currentUsers === 0) {
storage.connectedUsers = [];
if (currentUsers === 0) {
storage.connectedUsers = [];
if (serverConfig.bwAutoNoUsers === "1") {
output.write("W0\n"); // Auto BW 'Enabled'
}
if (serverConfig.bwAutoNoUsers === "1") output.write("W0\n"); // Auto BW 'Enabled'
// cEQ and iMS combinations
if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "1") {
output.write("G00\n"); // Both Disabled
} else if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "0") {
output.write(`G0${dataHandler.dataToSend.ims}\n`);
} else if (serverConfig.ceqNoUsers === "0" && serverConfig.imsNoUsers === "1") {
output.write(`G${dataHandler.dataToSend.eq}0\n`);
} else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "0") {
output.write(`G1${dataHandler.dataToSend.ims}\n`);
} else if (serverConfig.ceqNoUsers === "0" && serverConfig.imsNoUsers === "2") {
output.write(`G${dataHandler.dataToSend.eq}1\n`);
} else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "1") {
output.write("G10\n"); // Only cEQ enabled
} else if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "2") {
output.write("G01\n"); // Only iMS enabled
} else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "2") {
output.write("G11\n"); // Both Enabled
}
// cEQ and iMS combinations
if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "1") output.write("G00\n"); // Both Disabled
else if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "0") output.write(`G0${dataHandler.dataToSend.ims}\n`);
else if (serverConfig.ceqNoUsers === "0" && serverConfig.imsNoUsers === "1") output.write(`G${dataHandler.dataToSend.eq}0\n`);
else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "0") output.write(`G1${dataHandler.dataToSend.ims}\n`);
else if (serverConfig.ceqNoUsers === "0" && serverConfig.imsNoUsers === "2") output.write(`G${dataHandler.dataToSend.eq}1\n`);
else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "1") output.write("G10\n"); // Only cEQ enabled
else if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "2") output.write("G01\n"); // Only iMS enabled
else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "2") output.write("G11\n"); // Both Enabled
// Handle stereo mode
if (serverConfig.stereoNoUsers === "1") {
output.write("B0\n");
} else if (serverConfig.stereoNoUsers === "2") {
output.write("B1\n");
}
// Handle stereo mode
if (serverConfig.stereoNoUsers === "1") output.write("B0\n");
else if (serverConfig.stereoNoUsers === "2") output.write("B1\n");
// Handle Antenna selection
if (timeoutAntenna) clearTimeout(timeoutAntenna);
timeoutAntenna = setTimeout(() => {
if (serverConfig.antennaNoUsers === "1") {
output.write("Z0\n");
} else if (serverConfig.antennaNoUsers === "2") {
output.write("Z1\n");
} else if (serverConfig.antennaNoUsers === "3") {
output.write("Z2\n");
} else if (serverConfig.antennaNoUsers === "4") {
output.write("Z3\n");
}
}, serverConfig.antennaNoUsersDelay ? 15000 : 0);
}
// Handle Antenna selection
if (timeoutAntenna) clearTimeout(timeoutAntenna);
timeoutAntenna = setTimeout(() => {
if (serverConfig.antennaNoUsers === "1") output.write("Z0\n");
else if (serverConfig.antennaNoUsers === "2") output.write("Z1\n");
else if (serverConfig.antennaNoUsers === "3") output.write("Z2\n");
else if (serverConfig.antennaNoUsers === "4") output.write("Z3\n");
}, serverConfig.antennaNoUsersDelay ? 15000 : 0);
}
if (tunerLockTracker.has(ws)) {
logInfo(`User who locked the tuner left. Unlocking the tuner.`);
output.write('wT0\n')
tunerLockTracker.delete(ws);
serverConfig.publicTuner = true;
}
if (tunerLockTracker.has(ws)) {
logInfo(`User who locked the tuner left. Unlocking the tuner.`);
output.write('wT0\n')
tunerLockTracker.delete(ws);
serverConfig.publicTuner = true;
}
if (currentUsers === 0 && serverConfig.enableDefaultFreq === true &&
serverConfig.autoShutdown !== true && serverConfig.xdrd.wirelessConnection === true) {
setTimeout(function() {
if (currentUsers === 0) {
output.write('T' + Math.round(serverConfig.defaultFreq * 1000) + '\n');
dataHandler.resetToDefault(dataHandler.dataToSend);
dataHandler.dataToSend.freq = Number(serverConfig.defaultFreq).toFixed(3);
dataHandler.initialData.freq = Number(serverConfig.defaultFreq).toFixed(3);
}
}, 10000);
}
if (currentUsers === 0 && serverConfig.enableDefaultFreq === true &&
serverConfig.autoShutdown !== true && serverConfig.xdrd.wirelessConnection === true) {
setTimeout(function() {
if (currentUsers === 0) {
output.write('T' + Math.round(serverConfig.defaultFreq * 1000) + '\n');
dataHandler.resetToDefault(dataHandler.dataToSend);
dataHandler.dataToSend.freq = Number(serverConfig.defaultFreq).toFixed(3);
dataHandler.initialData.freq = Number(serverConfig.defaultFreq).toFixed(3);
}
}, 10000);
}
if (currentUsers === 0 && serverConfig.autoShutdown === true && serverConfig.xdrd.wirelessConnection === true) {
client.write('X\n');
}
if (currentUsers === 0 && serverConfig.autoShutdown === true && serverConfig.xdrd.wirelessConnection === true) client.write('X\n');
if (code !== 1008) {
logInfo(`Web client \x1b[31mdisconnected\x1b[0m (${normalizedClientIp}) \x1b[90m[${currentUsers}]`);
}
if (code !== 1008) logInfo(`Web client \x1b[31mdisconnected\x1b[0m (${normalizedClientIp}) \x1b[90m[${currentUsers}]`);
});
ws.on('error', console.error);
});
// Additional web socket for using plugins
pluginsWss.on('connection', (ws, request) => {
pluginsWss.on('connection', (ws, request) => {
const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
const userCommandHistory = {};
if (serverConfig.webserver.banlist?.includes(clientIp)) {
@@ -600,7 +551,7 @@ pluginsWss.on('connection', (ws, request) => {
let messageData;
try {
try { // JS Requires the try statement to have braces, unlike the if statement. This extends the huge list of proofs that this is a fucking toy language
messageData = JSON.parse(message); // Attempt to parse the JSON
} catch (error) {
// console.error("Failed to parse message:", error); // Log the error
@@ -611,9 +562,7 @@ pluginsWss.on('connection', (ws, request) => {
// Broadcast the message to all other clients
pluginsWss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(modifiedMessage); // Send the message to all clients
}
if (client.readyState === WebSocket.OPEN) client.send(modifiedMessage); // Send the message to all clients
});
});
@@ -622,7 +571,7 @@ pluginsWss.on('connection', (ws, request) => {
});
});
// Websocket register for /text, /audio and /chat paths
// Websocket register for /text, /audio and /chat paths
httpServer.on('upgrade', (request, socket, head) => {
if (request.url === '/text') {
sessionMiddleware(request, {}, () => {
@@ -671,9 +620,7 @@ httpServer.on('upgrade', (request, socket, head) => {
pluginsWss.emit('connection', ws, request);
});
});
} else {
socket.destroy();
}
} else socket.destroy();
});
app.use(express.static(path.join(__dirname, '../web'))); // Serve the entire web folder to the user
@@ -691,18 +638,13 @@ helpers.checkIPv6Support((isIPv6Supported) => {
const startServer = (address, isIPv6) => {
httpServer.listen(port, address, () => {
if (!isIPv6 && !configExists()) {
logInfo(`Open your browser and proceed to \x1b[34mhttp://${address}:${port}\x1b[0m to continue with setup.`);
} else {
logServerStart(address, isIPv6);
}
if (!isIPv6 && !configExists()) logInfo(`Open your browser and proceed to \x1b[34mhttp://${address}:${port}\x1b[0m to continue with setup.`);
else logServerStart(address, isIPv6);
});
};
if (isIPv6Supported) {
startServer(ipv4Address, false); // Start on IPv4
startServer(ipv6Address, true); // Start on IPv6
} else {
startServer(ipv4Address, false); // Start only on IPv4
}
} else startServer(ipv4Address, false); // Start only on IPv4
});