we're up to date with the terrible

This commit is contained in:
2026-02-24 09:52:39 +01:00
parent 098b6ba4e9
commit 0ae484529d
17 changed files with 673 additions and 459 deletions
+5
View File
@@ -28,6 +28,7 @@ var dataToSend = {
rt_flag: '',
ims: 0,
eq: 0,
agc: 0,
ant: 0,
txInfo: {
tx: '',
@@ -128,6 +129,10 @@ function handleData(wss, receivedData, rdsWss) {
initialData.ant = receivedLine.substring(1);
rdsReset();
break;
case receivedLine.startsWith('A'): // AGC
dataToSend.agc = receivedLine.substring(1);
initialData.agc = receivedLine.substring(1);
break;
case receivedLine.startsWith('G'): // EQ / iMS (RF+/IF+)
const mapping = filterMappings[receivedLine];
if (mapping) {
+45 -27
View File
@@ -11,7 +11,8 @@ const { parseAudioDevice } = require('./stream/parser');
const { configName, serverConfig, configUpdate, configSave, configExists, configPath } = require('./server_config');
const helpers = require('./helpers');
const storage = require('./storage');
const { logInfo, logDebug, logWarn, logError, logFfmpeg, logs } = require('./console');
const tunerProfiles = require('./tuner_profiles');
const { logInfo, logs } = require('./console');
const dataHandler = require('./datahandler');
const fmdxList = require('./fmdx_list');
const { allPluginConfigs } = require('./plugins');
@@ -48,7 +49,13 @@ router.get('/', (req, res) => {
isAdminAuthenticated: true,
videoDevices: result.audioDevices,
audioDevices: result.videoDevices,
serialPorts: serialPorts
serialPorts: serialPorts,
serialPorts: serialPorts,
tunerProfiles: tunerProfiles.map((profile) => ({
id: profile.id,
label: profile.label,
detailsHtml: helpers.parseMarkdown(profile.details || '')
}))
});
});
});
@@ -68,6 +75,8 @@ router.get('/', (req, res) => {
tuningUpperLimit: serverConfig.webserver.tuningUpperLimit,
chatEnabled: serverConfig.webserver.chatEnabled,
device: serverConfig.device,
tunerProfiles,
si47xxAgcControl: !!serverConfig.si47xx?.agcControl,
noPlugins,
plugins: serverConfig.plugins,
fmlist_integration: serverConfig.extras.fmlistIntegration,
@@ -101,7 +110,12 @@ router.get('/wizard', (req, res) => {
isAdminAuthenticated: req.session.isAdminAuthenticated,
videoDevices: result.audioDevices,
audioDevices: result.videoDevices,
serialPorts: serialPorts
serialPorts: serialPorts,
tunerProfiles: tunerProfiles.map((profile) => ({
id: profile.id,
label: profile.label,
detailsHtml: helpers.parseMarkdown(profile.details || '')
}))
});
});
})
@@ -135,20 +149,25 @@ router.get('/wizard', (req, res) => {
const updatedConfig = loadConfig(); // Reload the config every time
res.render('setup', {
isAdminAuthenticated: req.session.isAdminAuthenticated,
videoDevices: result.audioDevices,
audioDevices: result.videoDevices,
serialPorts: serialPorts,
memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
memoryHeap: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(1) + ' MB',
processUptime: formattedProcessUptime,
consoleOutput: logs,
plugins: allPluginConfigs,
enabledPlugins: updatedConfig.plugins,
onlineUsers: dataHandler.dataToSend.users,
connectedUsers: storage.connectedUsers,
device: serverConfig.device,
banlist: updatedConfig.webserver.banlist // Updated banlist from the latest config
isAdminAuthenticated: req.session.isAdminAuthenticated,
videoDevices: result.audioDevices,
audioDevices: result.videoDevices,
serialPorts: serialPorts,
memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
memoryHeap: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(1) + ' MB',
processUptime: formattedProcessUptime,
consoleOutput: logs,
plugins: allPluginConfigs,
enabledPlugins: updatedConfig.plugins,
onlineUsers: dataHandler.dataToSend.users,
connectedUsers: storage.connectedUsers,
device: serverConfig.device,
banlist: updatedConfig.webserver.banlist, // Updated banlist from the latest config
tunerProfiles: tunerProfiles.map((profile) => ({
id: profile.id,
label: profile.label,
detailsHtml: helpers.parseMarkdown(profile.details || '')
}))
});
});
})
@@ -164,12 +183,13 @@ router.get('/rdsspy', (req, res) => {
});
router.get('/api', (req, res) => {
const { ps_errors, rt0_errors, rt1_errors, ims, eq, ant, st_forced, previousFreq, txInfo, ...dataToSend } = dataHandler.dataToSend;
const { ps_errors, rt0_errors, rt1_errors, ims, eq, ant, st_forced, previousFreq, txInfo, rdsMode, ...dataToSend } = dataHandler.dataToSend;
res.json({
...dataToSend,
txInfo: txInfo,
ps_errors: ps_errors,
ant: ant
ant: ant,
rbds: serverConfig.webserver.rdsMode
});
});
@@ -241,6 +261,7 @@ router.get('/kick', (req, res) => {
});
router.get('/addToBanlist', (req, res) => {
if (!req.session.isAdminAuthenticated) return;
const ipAddress = req.query.ip;
const location = 'Unknown';
const date = Date.now();
@@ -252,17 +273,14 @@ router.get('/addToBanlist', (req, res) => {
serverConfig.webserver.banlist = [];
}
if (req.session.isAdminAuthenticated) {
serverConfig.webserver.banlist.push(userBanData);
configSave();
res.json({ success: true, message: 'IP address added to banlist.' });
helpers.kickClient(ipAddress);
} else {
res.status(403).json({ success: false, message: 'Unauthorized access.' });
}
serverConfig.webserver.banlist.push(userBanData);
configSave();
res.json({ success: true, message: 'IP address added to banlist.' });
helpers.kickClient(ipAddress);
});
router.get('/removeFromBanlist', (req, res) => {
if (!req.session.isAdminAuthenticated) return;
const ipAddress = req.query.ip;
if (typeof serverConfig.webserver.banlist !== 'object') {
+17 -21
View File
@@ -27,6 +27,7 @@ const fmdxList = require('./fmdx_list');
const { logError, logInfo, logWarn } = require('./console');
const storage = require('./storage');
const { serverConfig, configExists } = require('./server_config');
const pluginsApi = require('./plugins_api');
const pjson = require('../package.json');
// Function to find server files based on the plugins listed in config
@@ -155,8 +156,9 @@ function connectToSerial() {
return;
}
logInfo('Using COM device: ' + serverConfig.xdrd.comPort);
logInfo('Using serial port: ' + serverConfig.xdrd.comPort);
dataHandler.state.isSerialportAlive = true;
pluginsApi.setOutput(serialport);
setTimeout(() => {
serialport.write('x\n');
}, 3000);
@@ -175,7 +177,7 @@ function connectToSerial() {
} else serialport.write('T87500\n');
dataHandler.state.isSerialportRetrying = false;
serialport.write('A0\n');
if (serverConfig.device === 'si47xx') serialport.write('A0\n');
serialport.write('F-1\n');
serialport.write('W0\n');
serverConfig.webserver.rdsMode ? serialport.write('D1\n') : serialport.write('D0\n');
@@ -202,6 +204,7 @@ function connectToSerial() {
// Handle port closure
serialport.on('close', () => {
pluginsApi.setOutput(null);
logWarn('Disconnected from ' + serverConfig.xdrd.comPort + '. Attempting to reconnect.');
setTimeout(() => {
dataHandler.state.isSerialportRetrying = true;
@@ -220,6 +223,7 @@ function connectToXdrd() {
if (xdrd.wirelessConnection && configExists()) {
client.connect(xdrd.xdrdPort, xdrd.xdrdIp, () => {
logInfo('Connection to xdrd established successfully.');
pluginsApi.setOutput(client);
authFlags = {
authMsg: false,
@@ -277,7 +281,7 @@ client.on('data', (data) => {
client.write(serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? 'T' + Math.round(serverConfig.defaultFreq * 1000) + '\n' : 'T87500\n');
dataHandler.initialData.freq = serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? Number(serverConfig.defaultFreq).toFixed(3) : (87.5).toFixed(3);
dataHandler.dataToSend.freq = serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? Number(serverConfig.defaultFreq).toFixed(3) : (87.5).toFixed(3);
client.write('A0\n');
if (serverConfig.device === 'si47xx') serialport.write('A0\n');
client.write(serverConfig.audio.startupVolume ? 'Y' + (serverConfig.audio.startupVolume * 100).toFixed(0) + '\n' : 'Y100\n');
serverConfig.webserver.rdsMode ? client.write('D1\n') : client.write('D0\n');
return;
@@ -287,6 +291,7 @@ client.on('data', (data) => {
});
client.on('close', () => {
pluginsApi.setOutput(null);
if(serverConfig.autoShutdown === false) {
logWarn('Disconnected from xdrd. Attempting to reconnect.');
setTimeout(function () {
@@ -572,7 +577,12 @@ pluginsWss.on('connection', (ws, request) => {
});
// Websocket register for /text, /audio and /chat paths
httpServer.on('upgrade', (request, socket, head) => {
httpServer.on('upgrade', (request, socket, head) => {
const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
if (serverConfig.webserver.banlist?.includes(clientIp)) {
socket.destroy();
return;
}
if (request.url === '/text') {
sessionMiddleware(request, {}, () => {
wss.handleUpgrade(request, socket, head, (ws) => {
@@ -595,23 +605,6 @@ httpServer.on('upgrade', (request, socket, head) => {
sessionMiddleware(request, {}, () => {
rdsWss.handleUpgrade(request, socket, head, (ws) => {
rdsWss.emit('connection', ws, request);
const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
const userCommandHistory = {};
if (serverConfig.webserver.banlist?.includes(clientIp)) {
ws.close(1008, 'Banned IP');
return;
}
// Anti-spam tracking for each client
const userCommands = {};
let lastWarn = { time: 0 };
ws.on('message', function incoming(message) {
// Anti-spam
const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '5', 'rds');
});
});
});
} else if (request.url === '/data_plugins') {
@@ -648,3 +641,6 @@ helpers.checkIPv6Support((isIPv6Supported) => {
startServer(ipv6Address, true); // Start on IPv6
} else startServer(ipv4Address, false); // Start only on IPv4
});
pluginsApi.registerServerContext({ wss, pluginsWss, httpServer, serverConfig });
module.exports = { wss, pluginsWss, httpServer, serverConfig };
+137
View File
@@ -0,0 +1,137 @@
// plugins_api.js
// Shared API for server plugins:
// - Provides privileged/admin command access
// - Exposes server-side hooks for inter-plugin communication
// - Optionally broadcasts events to connected plugin WebSocket clients
const { EventEmitter } = require('events');
const { logInfo, logWarn, logError } = require('./console');
let output = null;
let wss = null;
let pluginsWss = null;
let httpServer = null;
let serverConfig = null;
// ---- internal plugin event bus ----
const pluginEvents = new EventEmitter();
// prevent accidental memory leak warnings
pluginEvents.setMaxListeners(50);
// ---- registration server side ----
function registerServerContext(ctx) {
if (ctx.wss) wss = ctx.wss;
if (ctx.pluginsWss) pluginsWss = ctx.pluginsWss;
if (ctx.httpServer) httpServer = ctx.httpServer;
if (ctx.serverConfig) serverConfig = ctx.serverConfig;
}
function setOutput(newOutput) {
output = newOutput;
}
function clearOutput() {
output = null;
}
// ---- accessors plugin side ----
function getWss() {
return wss;
}
function getPluginsWss() {
return pluginsWss;
}
function getHttpServer() {
return httpServer;
}
function getServerConfig() {
return serverConfig;
}
// ---- privileged command path ----
async function sendPrivilegedCommand(command, isPluginInternal = false) {
const maxWait = 10000;
const interval = 500;
let waited = 0;
while (!output && waited < maxWait) {
await new Promise(resolve => setTimeout(resolve, interval));
waited += interval;
}
if (!output) {
logError(`[Privileged Send] Timeout waiting for output (${command})`);
return false;
}
if (isPluginInternal) {
output.write(`${command}\n`);
//logInfo(`[Privileged Plugin] Command sent: ${command}`); // Debug
return true;
}
logWarn(`[Privileged Send] Rejected (not internal): ${command.slice(0, 64)}`);
return false;
}
// ---- plugin hook API ----
function emitPluginEvent(event, payload, opts = {}) {
pluginEvents.emit(event, payload);
// Stop here unless option to broadcast to clients if true
if (opts.broadcast === false) return;
// Broadcast to connected plugin WebSocket clients if available
if (pluginsWss) {
const message = JSON.stringify({ type: event, value: payload });
pluginsWss.clients.forEach((client) => {
if (client.readyState === client.OPEN) {
try {
// Send event to client
client.send(message);
} catch (err) {
logWarn(`[plugins_api] Failed to send ${event} to client: ${err.message}`);
}
}
});
}
}
function onPluginEvent(event, handler) {
pluginEvents.on(event, handler);
}
function offPluginEvent(event, handler) {
pluginEvents.off(event, handler);
}
// ---- exports ----
module.exports = {
// server registration
registerServerContext,
setOutput,
clearOutput,
// server context access
getWss,
getPluginsWss,
getHttpServer,
getServerConfig,
// privileged control
sendPrivilegedCommand,
// inter-plugin hooks
emitPluginEvent,
onPluginEvent,
offPluginEvent
};
+3
View File
@@ -90,6 +90,9 @@ let serverConfig = {
fmlistAdminOnly: false,
fmlistOmid: "",
},
si47xx: {
agcControl: false
},
tunnel: {
enabled: false,
username: "",
+84
View File
@@ -0,0 +1,84 @@
const tunerProfiles = [
{
id: 'tef',
label: 'TEF668x',
fmBandwidths: [
{ value: 0, label: 'Auto' },
{ value: 56000, label: '56 kHz' },
{ value: 64000, label: '64 kHz' },
{ value: 72000, label: '72 kHz' },
{ value: 84000, label: '84 kHz' },
{ value: 97000, label: '97 kHz' },
{ value: 114000, label: '114 kHz' },
{ value: 133000, label: '133 kHz' },
{ value: 151000, label: '151 kHz' },
{ value: 184000, label: '184 kHz' },
{ value: 200000, label: '200 kHz' },
{ value: 217000, label: '217 kHz' },
{ value: 236000, label: '236 kHz' },
{ value: 254000, label: '254 kHz' },
{ value: 287000, label: '287 kHz' },
{ value: 311000, label: '311 kHz' }
],
details: ''
},
{
id: 'xdr',
label: 'XDR (F1HD / S10HDiP)',
fmBandwidths: [
{ value: 0, value2: -1, label: 'Auto' },
{ value: 55000, value2: 0, label: '55 kHz' },
{ value: 73000, value2: 1, label: '73 kHz' },
{ value: 90000, value2: 2, label: '90 kHz' },
{ value: 108000, value2: 3, label: '108 kHz' },
{ value: 125000, value2: 4, label: '125 kHz' },
{ value: 142000, value2: 5, label: '142 kHz' },
{ value: 159000, value2: 6, label: '159 kHz' },
{ value: 177000, value2: 7, label: '177 kHz' },
{ value: 194000, value2: 8, label: '194 kHz' },
{ value: 211000, value2: 9, label: '211 kHz' },
{ value: 229000, value2: 10, label: '229 kHz' },
{ value: 246000, value2: 11, label: '246 kHz' },
{ value: 263000, value2: 12, label: '263 kHz' },
{ value: 281000, value2: 13, label: '281 kHz' },
{ value: 298000, value2: 14, label: '298 kHz' },
{ value: 309000, value2: 15, label: '309 kHz' }
],
details: ''
},
{
id: 'sdr',
label: 'SDR (RTL-SDR / AirSpy)',
fmBandwidths: [
{ value: 0, label: 'Auto' },
{ value: 4000, label: '4 kHz' },
{ value: 8000, label: '8 kHz' },
{ value: 10000, label: '10 kHz' },
{ value: 20000, label: '20 kHz' },
{ value: 30000, label: '30 kHz' },
{ value: 50000, label: '50 kHz' },
{ value: 75000, label: '75 kHz' },
{ value: 100000, label: '100 kHz' },
{ value: 125000, label: '125 kHz' },
{ value: 150000, label: '150 kHz' },
{ value: 175000, label: '175 kHz' },
{ value: 200000, label: '200 kHz' },
{ value: 225000, label: '225 kHz' }
],
details: ''
},
{
id: 'si47xx',
label: 'Si47XX (Si4735 / Si4732)',
fmBandwidths: [
{ value: 0, label: 'Auto' },
{ value: 40000, label: '40 kHz' },
{ value: 60000, label: '60 kHz' },
{ value: 84000, label: '84 kHz' },
{ value: 110000, label: '110 kHz' }
],
details: ''
}
];
module.exports = tunerProfiles;