1
0
mirror of https://github.com/KubaPro010/fm-dx-webserver.git synced 2026-02-26 14:11:59 +01:00

some changes again

This commit is contained in:
2026-02-24 14:15:52 +01:00
parent 1d04719580
commit ee25214160
11 changed files with 99 additions and 139 deletions

View File

@@ -3,7 +3,7 @@ require('./server/index.js');
/**
* FM-DX Webserver
*
* Github repo: https://github.com/NoobishSVK/fm-dx-webserver
* Github repo: https://github.com/KubaPro010/fm-dx-webserver
* Server files: /server
* Client files (web): /web
* Plugin files: /plugins

View File

@@ -63,17 +63,12 @@ function createChatServer(storage) {
delete messageData.ip;
delete messageData.time;
if (messageData.nickname != null) {
messageData.nickname = helpers.escapeHtml(String(messageData.nickname));
}
if (messageData.nickname != null) messageData.nickname = helpers.escapeHtml(String(messageData.nickname));
messageData.ip = clientIp;
const now = new Date();
messageData.time =
String(now.getHours()).padStart(2, '0') +
":" +
String(now.getMinutes()).padStart(2, '0');
messageData.time = String(now.getHours()).padStart(2, '0') + ":" + String(now.getMinutes()).padStart(2, '0');
if (serverConfig.webserver.banlist?.includes(clientIp)) return;

View File

@@ -2,7 +2,7 @@
const RDSDecoder = require("./rds.js");
const { serverConfig } = require('./server_config');
const { fetchTx } = require('./tx_search.js');
const fetchTx = require('./tx_search.js');
const updateInterval = 75;
// Initialize the data object

View File

@@ -15,7 +15,7 @@ const tunerProfiles = require('./tuner_profiles');
const { logInfo, logs } = require('./console');
const dataHandler = require('./datahandler');
const fmdxList = require('./fmdx_list');
const { allPluginConfigs } = require('./plugins');
const allPluginConfigs = require('./plugins');
// Endpoints
router.get('/', (req, res) => {

View File

@@ -1,3 +1,4 @@
const fs = require('fs');
const http = require('http');
const https = require('https');
const net = require('net');
@@ -5,7 +6,7 @@ const crypto = require('crypto');
const dataHandler = require('./datahandler');
const storage = require('./storage');
const consoleCmd = require('./console');
const { serverConfig, configExists, configSave } = require('./server_config');
const { serverConfig, configSave } = require('./server_config');
function parseMarkdown(parsed) {
parsed = parsed.replace(/<\/?[^>]+(>|$)/g, '');
@@ -93,9 +94,7 @@ let bannedASCache = { data: null, timestamp: 0 };
function fetchBannedAS(callback) {
const now = Date.now();
if (bannedASCache.data && now - bannedASCache.timestamp < 10 * 60 * 1000) {
return callback(null, bannedASCache.data);
}
if (bannedASCache.data && now - bannedASCache.timestamp < 10 * 60 * 1000) return callback(null, bannedASCache.data);
const req = https.get("https://fmdx.org/banned_as.json", { family: 4 }, (banResponse) => {
let banData = "";
@@ -152,9 +151,7 @@ function processConnection(clientIp, locationInfo, currentUsers, ws, callback) {
}
const userLocation =
locationInfo.country === undefined
? "Unknown"
: `${locationInfo.city}, ${locationInfo.regionName}, ${locationInfo.countryCode}`;
locationInfo.country === undefined ? "Unknown" : `${locationInfo.city}, ${locationInfo.regionName}, ${locationInfo.countryCode}`;
storage.connectedUsers.push({
ip: clientIp,
@@ -289,9 +286,7 @@ function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userC
lastMessageTime = now;
// Initialize command history for rate-limiting checks
if (!userCommands[command]) {
userCommands[command] = [];
}
if (!userCommands[command]) userCommands[command] = [];
// Record the current timestamp for this command
userCommands[command].push(now);
@@ -313,15 +308,45 @@ function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userC
}
const escapeHtml = (unsafe) => {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
return unsafe.replace(/&/g, "&amp;")
.replace(/</g, "&lt;").replace(/>/g, "&gt;")
.replace(/"/g, "&quot;").replace(/'/g, "&#039;");
};
// Start plugins with delay
function startPluginsWithDelay(plugins, delay) {
plugins.forEach((pluginPath, index) => {
setTimeout(() => {
const pluginName = path.basename(pluginPath, '.js'); // Extract plugin name from path
logInfo(`-----------------------------------------------------------------`);
logInfo(`Plugin ${pluginName} loaded successfully!`);
require(pluginPath);
}, delay * index);
});
// Add final log line after all plugins are loaded
setTimeout(() => {
logInfo(`-----------------------------------------------------------------`);
}, delay * plugins.length);
}
// Function to find server files based on the plugins listed in config
function findServerFiles(plugins) {
let results = [];
plugins.forEach(plugin => {
// Remove .js extension if present
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);
});
return results;
}
module.exports = {
authenticateWithXdrd, parseMarkdown, handleConnect, removeMarkdown, formatUptime, resolveDataBuffer, kickClient, checkIPv6Support, checkLatency, antispamProtection, escapeHtml
authenticateWithXdrd, parseMarkdown, handleConnect,
removeMarkdown, formatUptime, resolveDataBuffer,
kickClient, checkIPv6Support, checkLatency,
antispamProtection, escapeHtml, findServerFiles,
startPluginsWithDelay
}

View File

@@ -1,4 +1,3 @@
// Library imports
const express = require('express');
const endpoints = require('./endpoints');
const session = require('express-session');
@@ -8,21 +7,16 @@ const readline = require('readline');
const app = express();
const httpServer = http.createServer(app);
const WebSocket = require('ws');
const wss = new WebSocket.Server({ noServer: true, perMessageDeflate: true });
const rdsWss = new WebSocket.Server({ noServer: true });
const pluginsWss = new WebSocket.Server({ noServer: true, perMessageDeflate: true });
const fs = require('fs');
const path = require('path');
const net = require('net');
const client = new net.Socket();
const { SerialPort } = require('serialport');
const tunnel = require('./tunnel');
const { createChatServer } = require('./chat');
const { createAudioServer } = require('./stream/ws.js');
const figlet = require('figlet');
// File imports
const helpers = require('./helpers');
const { findServerFiles, startPluginsWithDelay } = helpers;
const dataHandler = require('./datahandler');
const fmdxList = require('./fmdx_list');
const { logError, logInfo, logWarn } = require('./console');
@@ -31,35 +25,10 @@ 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
function findServerFiles(plugins) {
let results = [];
plugins.forEach(plugin => {
// Remove .js extension if present
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);
});
return results;
}
// Start plugins with delay
function startPluginsWithDelay(plugins, delay) {
plugins.forEach((pluginPath, index) => {
setTimeout(() => {
const pluginName = path.basename(pluginPath, '.js'); // Extract plugin name from path
logInfo(`-----------------------------------------------------------------`);
logInfo(`Plugin ${pluginName} loaded successfully!`);
require(pluginPath);
}, delay * index);
});
// Add final log line after all plugins are loaded
setTimeout(() => {
logInfo(`-----------------------------------------------------------------`);
}, delay * plugins.length);
}
const client = new net.Socket();
const wss = new WebSocket.Server({ noServer: true, perMessageDeflate: true });
const rdsWss = new WebSocket.Server({ noServer: true });
const pluginsWss = new WebSocket.Server({ noServer: true, perMessageDeflate: true });
// Get all plugins from config and find corresponding server files
const plugins = findServerFiles(serverConfig.plugins);
@@ -76,22 +45,12 @@ const terminalWidth = readline.createInterface({
output: process.stdout
}).output.columns;
figlet("FM-DX Webserver", function (err, data) {
if (err) {
console.log("Something went wrong...");
console.dir(err);
return;
}
console.log('\x1b[32m' + data);
});
console.log('\x1b[32m' + figlet.textSync("FM-DX Webserver"));
console.log('\x1b[32m\x1b[2mby Noobish @ \x1b[4mFMDX.org\x1b[0m');
console.log("v" + pjson.version)
console.log('\x1b[90m' + '─'.repeat(terminalWidth - 1) + '\x1b[0m');
const chatWss = createChatServer(storage);
const audioWss = createAudioServer();
// Start ffmpeg
const audioWss = require('./stream/ws.js');
require('./stream/index');
require('./plugins');
@@ -107,6 +66,7 @@ const sessionMiddleware = session({
});
app.use(sessionMiddleware);
app.use(bodyParser.json());
const chatWss = createChatServer(storage);
connectToXdrd();
connectToSerial();
@@ -237,9 +197,7 @@ client.on('data', (data) => {
const { xdrd } = serverConfig;
helpers.resolveDataBuffer(data, wss, rdsWss);
if (authFlags.authMsg == true && authFlags.messageCount > 1) {
return;
}
if (authFlags.authMsg == true && authFlags.messageCount > 1) return;
authFlags.messageCount++;
const receivedData = data.toString();

View File

@@ -93,6 +93,4 @@ function createLinks() {
const allPluginConfigs = collectPluginConfigs();
createLinks();
module.exports = {
allPluginConfigs
};
module.exports = allPluginConfigs;

View File

@@ -93,8 +93,8 @@ class RDSDecoder {
this.ps[idx * 2] = String.fromCharCode(blockD >> 8);
this.ps[idx * 2 + 1] = String.fromCharCode(blockD & 0xFF);
this.ps_errors[idx * 2] = error;
this.ps_errors[idx * 2 + 1] = error;
this.ps_errors[idx * 2] = Math.ceil(d_error * (10/3));
this.ps_errors[idx * 2 + 1] = Math.ceil(d_error * (10/3));
this.data.ps = this.ps.join('');
this.data.ps_errors = this.ps_errors.join(',');
@@ -124,15 +124,15 @@ class RDSDecoder {
if(c_error < 2 && multiplier !== 2) {
this.rt1[idx * multiplier] = String.fromCharCode(blockC >> 8);
this.rt1[idx * multiplier + 1] = String.fromCharCode(blockC & 0xFF);
this.rt1_errors[idx * multiplier] = error;
this.rt1_errors[idx * multiplier + 1] = error;
this.rt1_errors[idx * multiplier] = Math.ceil(c_error * (10/3));
this.rt1_errors[idx * multiplier + 1] = Math.ceil(c_error * (10/3));
}
if(d_error < 2) {
var offset = (multiplier == 2) ? 0 : 2;
this.rt1[idx * multiplier + offset] = String.fromCharCode(blockD >> 8);
this.rt1[idx * multiplier + offset + 1] = String.fromCharCode(blockD & 0xFF);
this.rt1_errors[idx * multiplier + offset] = error;
this.rt1_errors[idx * multiplier + offset + 1] = error;
this.rt1_errors[idx * multiplier + offset] = Math.ceil(d_error * (10/3));
this.rt1_errors[idx * multiplier + offset + 1] = Math.ceil(d_error * (10/3));
}
var i = this.rt1.indexOf("\r")
@@ -155,15 +155,15 @@ class RDSDecoder {
if(c_error !== 3 && multiplier !== 2) {
this.rt0[idx * multiplier] = String.fromCharCode(blockC >> 8);
this.rt0[idx * multiplier + 1] = String.fromCharCode(blockC & 0xFF);
this.rt0_errors[idx * multiplier] = error;
this.rt0_errors[idx * multiplier + 1] = error;
this.rt0_errors[idx * multiplier] = Math.ceil(c_error * (10/3));
this.rt0_errors[idx * multiplier + 1] = Math.ceil(c_error * (10/3));
}
if(d_error !== 3) {
var offset = (multiplier == 2) ? 0 : 2;
this.rt0[idx * multiplier + offset] = String.fromCharCode(blockD >> 8);
this.rt0[idx * multiplier + offset + 1] = String.fromCharCode(blockD & 0xFF);
this.rt0_errors[idx * multiplier + offset] = error;
this.rt0_errors[idx * multiplier + offset + 1] = error;
this.rt0_errors[idx * multiplier + offset] = Math.ceil(d_error * (10/3));
this.rt0_errors[idx * multiplier + offset + 1] = Math.ceil(d_error * (10/3));
}
var i = this.rt0.indexOf("\r");

View File

@@ -21,7 +21,7 @@ checkFFmpeg().then((ffmpegPath) => {
logInfo(`${consoleLogTitle} Using ${ffmpegPath === 'ffmpeg' ? 'system-installed FFmpeg' : 'ffmpeg-static'}`);
logInfo(`${consoleLogTitle} Starting audio stream on device: \x1b[35m${serverConfig.audio.audioDevice}\x1b[0m`);
const sampleRate = Number(serverConfig.audio.sampleRate || 44100) + Number(serverConfig.audio.samplerateOffset || 0);
const sampleRate = Number(serverConfig.audio.sampleRate || 44100) + Number(serverConfig.audio.samplerateOffset || 0); // Maybe even do 32 khz, we do not need higher than 15 khz precision
const channels = Number(serverConfig.audio.audioChannels || 2);
@@ -139,4 +139,4 @@ checkFFmpeg().then((ffmpegPath) => {
logError(`${consoleLogTitle} Error: ${err.message}`);
});
module.exports.audio_pipe = audio_pipe;
module.exports = audio_pipe;

View File

@@ -1,32 +1,28 @@
const WebSocket = require('ws');
const { serverConfig } = require('../server_config');
const { audio_pipe } = require('./index.js');
const audio_pipe = require('./index.js');
function createAudioServer() {
const audioWss = new WebSocket.Server({ noServer: true });
const audioWss = new WebSocket.Server({ noServer: true, skipUTF8Validation: true });
audioWss.on('connection', (ws, request) => {
audioWss.on('connection', (ws, request) => {
const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
if (serverConfig.webserver.banlist?.includes(clientIp)) {
ws.close(1008, 'Banned IP');
return;
}
});
});
audio_pipe.on('data', (chunk) => {
audio_pipe.on('data', (chunk) => {
audioWss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) client.send(chunk, {binary: true, compress: false });
});
if (client.readyState === WebSocket.OPEN) client.send(chunk, {binary: true, compress: false});
});
});
audio_pipe.on('end', () => {
audio_pipe.on('end', () => {
audioWss.clients.forEach((client) => {
client.close(1001, "Audio stream ended");
});
});
});
return audioWss;
}
module.exports = { createAudioServer };
module.exports = audioWss;

View File

@@ -80,9 +80,7 @@ async function buildTxDatabase() {
consoleCmd.logInfo('Fetching transmitter database...');
const response = await fetch(`https://maps.fmdx.org/api?qth=${serverConfig.identification.lat},${serverConfig.identification.lon}`, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
headers: {'Accept': 'application/json'}
});
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
localDb = await response.json();
@@ -169,9 +167,7 @@ function getStateForCoordinates(lat, lon) {
for (const feature of usStatesGeoJson.features) {
const boundingBox = getStateBoundingBox(feature.geometry.coordinates);
if (isCityInState(lat, lon, boundingBox)) {
return feature.properties.name; // Return the state's name if city is inside bounding box
}
if (isCityInState(lat, lon, boundingBox)) return feature.properties.name; // Return the state's name if city is inside bounding box
}
return null;
}
@@ -208,22 +204,16 @@ function validPsCompare(rdsPs, stationPs) {
for (let i = 0; i < standardizedRdsPs.length; i++) {
// Skip this position if the character in standardizedRdsPs is an underscore.
if (standardizedRdsPs[i] === '_') continue;
if (token[i] === standardizedRdsPs[i]) {
matchCount++;
}
}
if (matchCount >= minMatchLen) {
return true;
if (token[i] === standardizedRdsPs[i]) matchCount++;
}
if (matchCount >= minMatchLen) return true;
}
return false;
}
function evaluateStation(station, esMode) {
let weightDistance = station.distanceKm;
if (esMode && station.distanceKm > 700) {
weightDistance = Math.abs(station.distanceKm - 1500) + 200;
}
if (esMode && station.distanceKm > 700) weightDistance = Math.abs(station.distanceKm - 1500) + 200;
let erp = station.erp && station.erp > 0 ? station.erp : 1;
let extraWeight = erp > weightedErp && station.distanceKm <= weightDistance ? 0.3 : 0;
let score = 0;
@@ -394,6 +384,4 @@ function deg2rad(deg) {
return deg * (Math.PI / 180);
}
module.exports = {
fetchTx
};
module.exports = fetchTx;