diff --git a/package.json b/package.json index 088729a..206395c 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "ffmpeg-static": "5.3.0", "figlet": "^1.10.0", "http": "0.0.1-security", - "koffi": "2.7.2", "net": "1.0.2", "serialport": "13.0.0", "ws": "8.19.0" diff --git a/server/helpers.js b/server/helpers.js index 681686a..f1bd5f4 100644 --- a/server/helpers.js +++ b/server/helpers.js @@ -24,9 +24,7 @@ function parseMarkdown(parsed) { var linkRegex = /\[([^\]]+)]\(([^)]+)\)/g; parsed = parsed.replace(linkRegex, '$1'); - parsed = parsed.replace(/\n/g, '
'); - - return parsed; + return parsed.replace(/\n/g, '
'); } function removeMarkdown(parsed) { @@ -42,9 +40,8 @@ function removeMarkdown(parsed) { parsed = parsed.replace(italicRegex, '$1'); var linkRegex = /\[([^\]]+)]\(([^)]+)\)/g; - parsed = parsed.replace(linkRegex, '$1'); - return parsed; + return parsed.replace(linkRegex, '$1'); } function authenticateWithXdrd(client, salt, password) { @@ -95,7 +92,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(bannedASCache.data); const req = https.get("https://fmdx.org/banned_as.json", { family: 4 }, (banResponse) => { let banData = ""; @@ -108,10 +105,10 @@ function fetchBannedAS(callback) { try { const bannedAS = JSON.parse(banData).banned_as || []; bannedASCache = { data: bannedAS, timestamp: now }; - callback(null, bannedAS); + callback(bannedAS); } catch (error) { console.error("Error parsing banned AS list:", error); - callback(null, []); // Default to allowing user + callback([]); // Default to allowing user } }); }); @@ -120,12 +117,12 @@ function fetchBannedAS(callback) { req.setTimeout(5000, () => { console.error("Error: Request timed out while fetching banned AS list."); req.abort(); - callback(null, []); // Default to allowing user + callback([]); // Default to allowing user }); req.on("error", (err) => { console.error("Error fetching banned AS list:", err); - callback(null, []); // Default to allowing user + callback([]); // Default to allowing user }); } @@ -136,9 +133,7 @@ function processConnection(clientIp, locationInfo, currentUsers, ws, callback) { const connectionTime = new Date().toLocaleString([], options); const normalizedClientIp = clientIp?.replace(/^::ffff:/, ''); - fetchBannedAS((error, bannedAS) => { - if (error) console.error("Error fetching banned AS list:", error); - + fetchBannedAS((bannedAS) => { if (bannedAS.some((as) => locationInfo.as?.includes(as))) { const now = Date.now(); const lastSeen = recentBannedIps.get(normalizedClientIp) || 0; @@ -151,8 +146,7 @@ function processConnection(clientIp, locationInfo, currentUsers, ws, callback) { return callback("User banned"); } - const userLocation = - locationInfo.country === undefined ? "Unknown" : `${locationInfo.city}, ${locationInfo.regionName}, ${locationInfo.countryCode}`; + const userLocation = locationInfo.country === undefined ? "Unknown" : `${locationInfo.city}, ${locationInfo.regionName}, ${locationInfo.countryCode}`; storage.connectedUsers.push({ ip: clientIp, @@ -168,13 +162,12 @@ function processConnection(clientIp, locationInfo, currentUsers, ws, callback) { } function formatUptime(uptimeInSeconds) { - const secondsInMinute = 60; - const secondsInHour = secondsInMinute * 60; + const secondsInHour = 60 ** 2; const secondsInDay = secondsInHour * 24; const days = Math.floor(uptimeInSeconds / secondsInDay); const hours = Math.floor((uptimeInSeconds % secondsInDay) / secondsInHour); - const minutes = Math.floor((uptimeInSeconds % secondsInHour) / secondsInMinute); + const minutes = Math.floor((uptimeInSeconds % secondsInHour) / 60); return `${days}d ${hours}h ${minutes}m`; } diff --git a/server/index.js b/server/index.js index 119e99d..aa667c4 100644 --- a/server/index.js +++ b/server/index.js @@ -10,7 +10,7 @@ const WebSocket = require('ws'); const path = require('path'); const net = require('net'); const { SerialPort } = require('serialport'); -const tunnel = require('./tunnel'); +const tunnel_connect = require('./tunnel'); const { createChatServer } = require('./chat'); const figlet = require('figlet'); @@ -70,7 +70,7 @@ const chatWss = createChatServer(storage); connectToXdrd(); connectToSerial(); -tunnel.connect(); +tunnel_connect(); // Serialport retry code when port is open but communication is lost (additional code in datahandler.js) dataHandler.state.isSerialportRetrying = false; @@ -498,7 +498,7 @@ pluginsWss.on('connection', (ws, request) => { ws.on('message', message => { // Anti-spam - const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '10', 'data_plugins'); + helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '10', 'data_plugins'); let messageData; @@ -531,6 +531,8 @@ httpServer.on('upgrade', (request, socket, head) => { } var upgradeWss = undefined; + + // TODO: make it a map, so plugins can register their url if needed if (request.url === '/text') upgradeWss = wss; else if (request.url === '/audio') upgradeWss = audioWss; else if (request.url === '/chat' && serverConfig.webserver.chatEnabled === true) upgradeWss = chatWss; diff --git a/server/rds.js b/server/rds.js index 0e86c75..f8c1449 100644 --- a/server/rds.js +++ b/server/rds.js @@ -1,5 +1,147 @@ const { rdsEccLookup, iso, countries } = require("./rds_country.js") +function decode_charset(input) { + var character = input; + switch (character) { + case 0x0A: character = ' '; break; + case 0x20: character = ' '; break; + case 0x5E: character = '―'; break; + case 0x5F: character = '_'; break; + case 0x60: character = '`'; break; + case 0x7E: character = '¯'; break; + case 0x7F: character = ' '; break; + case 0x80: character = 'á'; break; + case 0x81: character = 'à'; break; + case 0x82: character = 'é'; break; + case 0x83: character = 'è'; break; + case 0x84: character = 'í'; break; + case 0x85: character = 'ì'; break; + case 0x86: character = 'ó'; break; + case 0x87: character = 'ò'; break; + case 0x88: character = 'ú'; break; + case 0x89: character = 'ù'; break; + case 0x8A: character = 'Ñ'; break; + case 0x8B: character = 'Ç'; break; + case 0x8C: character = 'Ş'; break; + case 0x8D: character = 'β'; break; + case 0x8E: character = '¡'; break; + case 0x8F: character = 'IJ'; break; + case 0x90: character = 'â'; break; + case 0x91: character = 'ä'; break; + case 0x92: character = 'ê'; break; + case 0x93: character = 'ë'; break; + case 0x94: character = 'î'; break; + case 0x95: character = 'ï'; break; + case 0x96: character = 'ô'; break; + case 0x97: character = 'ö'; break; + case 0x98: character = 'û'; break; + case 0x99: character = 'ü'; break; + case 0x9A: character = 'ñ'; break; + case 0x9B: character = 'ç'; break; + case 0x9C: character = 'ş'; break; + case 0x9D: character = 'ǧ'; break; + case 0x9E: character = 'ı'; break; + case 0x9F: character = 'ij'; break; + case 0xA0: character = 'ª'; break; + case 0xA1: character = 'α'; break; + case 0xA2: character = '©'; break; + case 0xA3: character = '‰'; break; + case 0xA4: character = 'Ǧ'; break; + case 0xA5: character = 'ě'; break; + case 0xA6: character = 'ň'; break; + case 0xA7: character = 'ő'; break; + case 0xA8: character = 'π'; break; + case 0xA9: character = '€'; break; + case 0xAA: character = '£'; break; + case 0xAB: character = '$'; break; + case 0xAC: character = '←'; break; + case 0xAD: character = '↑'; break; + case 0xAE: character = '→'; break; + case 0xAF: character = '↓'; break; + case 0xB0: character = 'º'; break; + case 0xB1: character = '¹'; break; + case 0xB2: character = '²'; break; + case 0xB3: character = '³'; break; + case 0xB4: character = '±'; break; + case 0xB5: character = 'İ'; break; + case 0xB6: character = 'ń'; break; + case 0xB7: character = 'ű'; break; + case 0xB8: character = 'µ'; break; + case 0xB9: character = '¿'; break; + case 0xBA: character = '÷'; break; + case 0xBB: character = '°'; break; + case 0xBC: character = '¼'; break; + case 0xBD: character = '½'; break; + case 0xBE: character = '¾'; break; + case 0xBF: character = '§'; break; + case 0xC0: character = 'Á'; break; + case 0xC1: character = 'À'; break; + case 0xC2: character = 'É'; break; + case 0xC3: character = 'È'; break; + case 0xC4: character = 'Í'; break; + case 0xC5: character = 'Ì'; break; + case 0xC6: character = 'Ó'; break; + case 0xC7: character = 'Ò'; break; + case 0xC8: character = 'Ú'; break; + case 0xC9: character = 'Ù'; break; + case 0xCA: character = 'Ř'; break; + case 0xCB: character = 'Č'; break; + case 0xCC: character = 'Š'; break; + case 0xCD: character = 'Ž'; break; + case 0xCE: character = 'Ð'; break; + case 0xCF: character = 'Ŀ'; break; + case 0xD0: character = 'Â'; break; + case 0xD1: character = 'Ä'; break; + case 0xD2: character = 'Ê'; break; + case 0xD3: character = 'Ë'; break; + case 0xD4: character = 'Î'; break; + case 0xD5: character = 'Ï'; break; + case 0xD6: character = 'Ô'; break; + case 0xD7: character = 'Ö'; break; + case 0xD8: character = 'Û'; break; + case 0xD9: character = 'Ü'; break; + case 0xDA: character = 'ř'; break; + case 0xDB: character = 'č'; break; + case 0xDC: character = 'š'; break; + case 0xDD: character = 'ž'; break; + case 0xDE: character = 'đ'; break; + case 0xDF: character = 'ŀ'; break; + case 0xE0: character = 'Ã'; break; + case 0xE1: character = 'Å'; break; + case 0xE2: character = 'Æ'; break; + case 0xE3: character = 'Œ'; break; + case 0xE4: character = 'ŷ'; break; + case 0xE5: character = 'Ý'; break; + case 0xE6: character = 'Õ'; break; + case 0xE7: character = 'Ø'; break; + case 0xE8: character = 'Þ'; break; + case 0xE9: character = 'Ŋ'; break; + case 0xEA: character = 'Ŕ'; break; + case 0xEB: character = 'Ć'; break; + case 0xEC: character = 'Ś'; break; + case 0xED: character = 'Ź'; break; + case 0xEE: character = 'Ŧ'; break; + case 0xEF: character = 'ð'; break; + case 0xF0: character = 'ã'; break; + case 0xF1: character = 'å'; break; + case 0xF2: character = 'æ'; break; + case 0xF3: character = 'œ'; break; + case 0xF4: character = 'ŵ'; break; + case 0xF5: character = 'ý'; break; + case 0xF6: character = 'õ'; break; + case 0xF7: character = 'ø'; break; + case 0xF8: character = 'þ'; break; + case 0xF9: character = 'ŋ'; break; + case 0xFA: character = 'ŕ'; break; + case 0xFB: character = 'ć'; break; + case 0xFC: character = 'ś'; break; + case 0xFD: character = 'ź'; break; + case 0xFE: character = 'ŧ'; break; + case 0xFF: character = ' '; break; + } + return character +} + class RDSDecoder { constructor(data) { this.data = data; @@ -91,8 +233,8 @@ class RDSDecoder { const idx = blockB & 0x3; - this.ps[idx * 2] = String.fromCharCode(blockD >> 8); - this.ps[idx * 2 + 1] = String.fromCharCode(blockD & 0xFF); + this.ps[idx * 2] = String.fromCharCode(decode_charset(blockD >> 8)); + this.ps[idx * 2 + 1] = String.fromCharCode(decode_charset(blockD & 0xFF)); this.ps_errors[idx * 2] = Math.ceil(d_error * (10/3)); this.ps_errors[idx * 2 + 1] = Math.ceil(d_error * (10/3)); @@ -122,15 +264,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[idx * multiplier] = String.fromCharCode(decode_charset(blockC >> 8)); + this.rt1[idx * multiplier + 1] = String.fromCharCode(decode_charset(blockC & 0xFF)); 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[idx * multiplier + offset] = String.fromCharCode(decode_charset(blockD >> 8)); + this.rt1[idx * multiplier + offset + 1] = String.fromCharCode(decode_charset(blockD & 0xFF)); 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)); } diff --git a/server/server_config.js b/server/server_config.js index 7a8f0e2..e1a065a 100644 --- a/server/server_config.js +++ b/server/server_config.js @@ -47,7 +47,7 @@ let serverConfig = { audioBitrate: "128k", audioBoost: false, softwareMode: false, - startupVolume: "0.95", + startupVolume: "1", ffmpeg: false, samplerateOffset: "0" }, diff --git a/server/stream/index.js b/server/stream/index.js index e7c6cb3..4baba10 100644 --- a/server/stream/index.js +++ b/server/stream/index.js @@ -41,14 +41,14 @@ checkFFmpeg().then((ffmpegPath) => { else inputArgs = ["-f", "alsa", "-i", device]; return [ - "-fflags", "+flush_packets", + "-fflags", "+nobuffer", "-flags", "low_delay", - "-rtbufsize", "4096", - "-probesize", "128", + "-rtbufsize", "6144", + "-probesize", "256", ...inputArgs, - "-thread_queue_size", "1536", + "-thread_queue_size", "2048", "-ar", String(sampleRate), "-ac", String(channels), @@ -62,7 +62,7 @@ checkFFmpeg().then((ffmpegPath) => { "-id3v2_version", "0", "-fflags", "+nobuffer", - "-flush_packets", "1", + // "-flush_packets", "1", "pipe:1" ]; diff --git a/server/tunnel.js b/server/tunnel.js index 18009dc..16f76c5 100644 --- a/server/tunnel.js +++ b/server/tunnel.js @@ -53,10 +53,8 @@ async function connect() { }); rl.on('line', (line) => { - if (line.includes('connect to server error')) { - const reason = line.substring(line.indexOf(': ')+2); - logError('Failed to connect to tunnel, reason: ' + reason); - } else if (line.includes('invalid user or token')) logError('Failed to connect to tunnel, reason: invalid user or token'); + if (line.includes('connect to server error')) logError('Failed to connect to tunnel, reason: ' + line.substring(line.indexOf(': ')+2)); + else if (line.includes('invalid user or token')) logError('Failed to connect to tunnel, reason: invalid user or token'); else if (line.includes('start proxy success')) logInfo('Tunnel established successfully'); else if (line.includes('login to server success')) logInfo('Connection to tunnel server was successful'); else logDebug('Tunnel log:', line); @@ -94,6 +92,4 @@ httpPassword = "<%= cfg.httpPassword %>" <% } %> `; -module.exports = { - connect -}; \ No newline at end of file +module.exports = connect; \ No newline at end of file diff --git a/server/tx_search.js b/server/tx_search.js index fffee7c..7a8bdfe 100644 --- a/server/tx_search.js +++ b/server/tx_search.js @@ -294,8 +294,7 @@ async function fetchTx(freq, piCode, rdsPs) { filteredLocations.sort((a, b) => b.score - a.score); match = filteredLocations[0]; // Have a maximum of 10 extra matches and remove any with less than 1/10 of the winning score - multiMatches = filteredLocations - .slice(1, 11) + multiMatches = filteredLocations.slice(1, 11) .filter(obj => obj.score >= (match.score / 10)); } else if (filteredLocations.length === 1) { match = filteredLocations[0]; diff --git a/web/js/chat.js b/web/js/chat.js index 2d32123..793b0bf 100644 --- a/web/js/chat.js +++ b/web/js/chat.js @@ -79,9 +79,7 @@ $(document).ready(function() { }); chatNicknameInput.keypress(function(event) { - if (event.which === 13) { - chatNicknameSave.trigger('click'); - } + if (event.which === 13) chatNicknameSave.trigger('click'); }); chatSendInput.keypress(function(event) {