mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-07-31 17:29:19 +02:00
Compare commits
6
Commits
1d04719580
...
c0d1fee257
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0d1fee257
|
||
|
|
5d524eba56
|
||
|
|
648ef00bed
|
||
|
|
8a53bf1027
|
||
|
|
722277c41f
|
||
|
|
ee25214160
|
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
/*.json
|
/*.json
|
||||||
/serverlog.txt
|
/serverlog*.txt
|
||||||
/web/js/plugins/
|
/web/js/plugins/
|
||||||
/libraries/
|
/libraries/
|
||||||
/plugins/*
|
/plugins/*
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ require('./server/index.js');
|
|||||||
/**
|
/**
|
||||||
* FM-DX Webserver
|
* FM-DX Webserver
|
||||||
*
|
*
|
||||||
* Github repo: https://github.com/NoobishSVK/fm-dx-webserver
|
* Github repo: https://github.com/KubaPro010/fm-dx-webserver
|
||||||
* Server files: /server
|
* Server files: /server
|
||||||
* Client files (web): /web
|
* Client files (web): /web
|
||||||
* Plugin files: /plugins
|
* Plugin files: /plugins
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fm-dx-webserver",
|
"name": "fm-dx-webserver",
|
||||||
"version": "1.4.0",
|
"version": "1.4.0a",
|
||||||
"description": "FM DX Webserver",
|
"description": "FM DX Webserver",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+35
-14
@@ -3,12 +3,19 @@ const { serverConfig } = require('./server_config');
|
|||||||
const { logChat } = require('./console');
|
const { logChat } = require('./console');
|
||||||
const helpers = require('./helpers');
|
const helpers = require('./helpers');
|
||||||
|
|
||||||
|
function heartbeat() { // WebSocket heartbeat helper
|
||||||
|
this.isAlive = true;
|
||||||
|
}
|
||||||
|
|
||||||
function createChatServer(storage) {
|
function createChatServer(storage) {
|
||||||
if (!serverConfig.webserver.chatEnabled) return null;
|
if (!serverConfig.webserver.chatEnabled) return null;
|
||||||
|
|
||||||
const chatWss = new WebSocket.Server({ noServer: true });
|
const chatWss = new WebSocket.Server({ noServer: true });
|
||||||
|
|
||||||
chatWss.on('connection', (ws, request) => {
|
chatWss.on('connection', (ws, request) => {
|
||||||
|
ws.isAlive = true;
|
||||||
|
ws.on('pong', heartbeat);
|
||||||
|
|
||||||
const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
|
const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
|
||||||
const userCommandHistory = {};
|
const userCommandHistory = {};
|
||||||
|
|
||||||
@@ -25,19 +32,18 @@ function createChatServer(storage) {
|
|||||||
ws.send(JSON.stringify(historyMessage));
|
ws.send(JSON.stringify(historyMessage));
|
||||||
});
|
});
|
||||||
|
|
||||||
const ipMessage = {
|
ws.send(JSON.stringify({
|
||||||
type: 'clientIp',
|
type: 'clientIp',
|
||||||
ip: clientIp,
|
ip: clientIp,
|
||||||
admin: request.session?.isAdminAuthenticated
|
admin: request.session?.isAdminAuthenticated
|
||||||
};
|
}));
|
||||||
|
|
||||||
ws.send(JSON.stringify(ipMessage));
|
|
||||||
|
|
||||||
const userCommands = {};
|
const userCommands = {};
|
||||||
let lastWarn = { time: 0 };
|
let lastWarn = { time: 0 };
|
||||||
|
|
||||||
ws.on('message', (message) => {
|
ws.on('message', (message) => {
|
||||||
helpers.antispamProtection(
|
message = helpers.antispamProtection(
|
||||||
message,
|
message,
|
||||||
clientIp,
|
clientIp,
|
||||||
ws,
|
ws,
|
||||||
@@ -45,9 +51,12 @@ function createChatServer(storage) {
|
|||||||
lastWarn,
|
lastWarn,
|
||||||
userCommandHistory,
|
userCommandHistory,
|
||||||
'5',
|
'5',
|
||||||
'chat'
|
'chat',
|
||||||
|
512
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if(!message) return;
|
||||||
|
|
||||||
let messageData;
|
let messageData;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -57,23 +66,16 @@ function createChatServer(storage) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Chat message:", messageData);
|
|
||||||
|
|
||||||
delete messageData.admin;
|
delete messageData.admin;
|
||||||
delete messageData.ip;
|
delete messageData.ip;
|
||||||
delete messageData.time;
|
delete messageData.time;
|
||||||
|
|
||||||
if (messageData.nickname != null) {
|
if (messageData.nickname != null) messageData.nickname = helpers.escapeHtml(String(messageData.nickname));
|
||||||
messageData.nickname = helpers.escapeHtml(String(messageData.nickname));
|
|
||||||
}
|
|
||||||
|
|
||||||
messageData.ip = clientIp;
|
messageData.ip = clientIp;
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
messageData.time =
|
messageData.time = String(now.getHours()).padStart(2, '0') + ":" + String(now.getMinutes()).padStart(2, '0');
|
||||||
String(now.getHours()).padStart(2, '0') +
|
|
||||||
":" +
|
|
||||||
String(now.getMinutes()).padStart(2, '0');
|
|
||||||
|
|
||||||
if (serverConfig.webserver.banlist?.includes(clientIp)) return;
|
if (serverConfig.webserver.banlist?.includes(clientIp)) return;
|
||||||
|
|
||||||
@@ -95,6 +97,25 @@ function createChatServer(storage) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ws.on('close', () => {
|
||||||
|
ws.isAlive = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We will not always be receiving data, so some proxies may terminate the connection, this prevents it.
|
||||||
|
*/
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
chatWss.clients.forEach((ws) => {
|
||||||
|
if (ws.isAlive === false) return ws.terminate();
|
||||||
|
ws.isAlive = false;
|
||||||
|
ws.ping();
|
||||||
|
});
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
chatWss.on('close', () => {
|
||||||
|
clearInterval(interval);
|
||||||
});
|
});
|
||||||
|
|
||||||
return chatWss;
|
return chatWss;
|
||||||
|
|||||||
+3
-1
@@ -3,7 +3,9 @@ const fs = require('fs').promises;
|
|||||||
const verboseMode = process.argv.includes('--debug');
|
const verboseMode = process.argv.includes('--debug');
|
||||||
const verboseModeFfmpeg = process.argv.includes('--ffmpegdebug');
|
const verboseModeFfmpeg = process.argv.includes('--ffmpegdebug');
|
||||||
|
|
||||||
const LOG_FILE = 'serverlog.txt';
|
const LOG_FILE = process.argv.includes('--config') && process.argv[process.argv.indexOf('--config') + 1]
|
||||||
|
? `serverlog_${process.argv[process.argv.indexOf('--config') + 1]}.txt`
|
||||||
|
: 'serverlog.txt';
|
||||||
const ANSI_ESCAPE_CODE_PATTERN = /\x1b\[[0-9;]*m/g;
|
const ANSI_ESCAPE_CODE_PATTERN = /\x1b\[[0-9;]*m/g;
|
||||||
const MAX_LOG_LINES = 5000;
|
const MAX_LOG_LINES = 5000;
|
||||||
const FLUSH_INTERVAL = 60000;
|
const FLUSH_INTERVAL = 60000;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
const RDSDecoder = require("./rds.js");
|
const RDSDecoder = require("./rds.js");
|
||||||
const { serverConfig } = require('./server_config');
|
const { serverConfig } = require('./server_config');
|
||||||
|
|
||||||
const { fetchTx } = require('./tx_search.js');
|
const fetchTx = require('./tx_search.js');
|
||||||
const updateInterval = 75;
|
const updateInterval = 75;
|
||||||
|
|
||||||
// Initialize the data object
|
// Initialize the data object
|
||||||
@@ -193,8 +193,7 @@ function handleData(wss, receivedData, rdsWss) {
|
|||||||
data += (((errors & 0x03) == 0) ? modifiedData.slice(12, 16) : '----');
|
data += (((errors & 0x03) == 0) ? modifiedData.slice(12, 16) : '----');
|
||||||
|
|
||||||
const newDataString = "G:\r\n" + data + "\r\n\r\n";
|
const newDataString = "G:\r\n" + data + "\r\n\r\n";
|
||||||
const finalBuffer = Buffer.from(newDataString, 'utf-8');
|
client.send(newDataString);
|
||||||
client.send(finalBuffer);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
rdsdec.decodeGroup(parseInt(modifiedData.slice(0, 4), 16), parseInt(modifiedData.slice(4, 8), 16), parseInt(modifiedData.slice(8, 12), 16), parseInt(modifiedData.slice(12, 16), 16));
|
rdsdec.decodeGroup(parseInt(modifiedData.slice(0, 4), 16), parseInt(modifiedData.slice(4, 8), 16), parseInt(modifiedData.slice(8, 12), 16), parseInt(modifiedData.slice(12, 16), 16));
|
||||||
|
|||||||
+3
-2
@@ -15,7 +15,7 @@ const tunerProfiles = require('./tuner_profiles');
|
|||||||
const { logInfo, logs } = require('./console');
|
const { logInfo, logs } = require('./console');
|
||||||
const dataHandler = require('./datahandler');
|
const dataHandler = require('./datahandler');
|
||||||
const fmdxList = require('./fmdx_list');
|
const fmdxList = require('./fmdx_list');
|
||||||
const { allPluginConfigs } = require('./plugins');
|
const allPluginConfigs = require('./plugins');
|
||||||
|
|
||||||
// Endpoints
|
// Endpoints
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
@@ -87,7 +87,8 @@ router.get('/', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get('/403', (req, res) => {
|
router.get('/403', (req, res) => {
|
||||||
res.render('403');
|
const reason = req.query.reason || null;
|
||||||
|
res.render('403', { reason });
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/wizard', (req, res) => {
|
router.get('/wizard', (req, res) => {
|
||||||
|
|||||||
+54
-22
@@ -1,3 +1,5 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const net = require('net');
|
const net = require('net');
|
||||||
@@ -5,7 +7,7 @@ const crypto = require('crypto');
|
|||||||
const dataHandler = require('./datahandler');
|
const dataHandler = require('./datahandler');
|
||||||
const storage = require('./storage');
|
const storage = require('./storage');
|
||||||
const consoleCmd = require('./console');
|
const consoleCmd = require('./console');
|
||||||
const { serverConfig, configExists, configSave } = require('./server_config');
|
const { serverConfig, configSave } = require('./server_config');
|
||||||
|
|
||||||
function parseMarkdown(parsed) {
|
function parseMarkdown(parsed) {
|
||||||
parsed = parsed.replace(/<\/?[^>]+(>|$)/g, '');
|
parsed = parsed.replace(/<\/?[^>]+(>|$)/g, '');
|
||||||
@@ -93,9 +95,7 @@ let bannedASCache = { data: null, timestamp: 0 };
|
|||||||
|
|
||||||
function fetchBannedAS(callback) {
|
function fetchBannedAS(callback) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (bannedASCache.data && now - bannedASCache.timestamp < 10 * 60 * 1000) {
|
if (bannedASCache.data && now - bannedASCache.timestamp < 10 * 60 * 1000) return callback(null, bannedASCache.data);
|
||||||
return callback(null, bannedASCache.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
const req = https.get("https://fmdx.org/banned_as.json", { family: 4 }, (banResponse) => {
|
const req = https.get("https://fmdx.org/banned_as.json", { family: 4 }, (banResponse) => {
|
||||||
let banData = "";
|
let banData = "";
|
||||||
@@ -152,9 +152,7 @@ function processConnection(clientIp, locationInfo, currentUsers, ws, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const userLocation =
|
const userLocation =
|
||||||
locationInfo.country === undefined
|
locationInfo.country === undefined ? "Unknown" : `${locationInfo.city}, ${locationInfo.regionName}, ${locationInfo.countryCode}`;
|
||||||
? "Unknown"
|
|
||||||
: `${locationInfo.city}, ${locationInfo.regionName}, ${locationInfo.countryCode}`;
|
|
||||||
|
|
||||||
storage.connectedUsers.push({
|
storage.connectedUsers.push({
|
||||||
ip: clientIp,
|
ip: clientIp,
|
||||||
@@ -252,12 +250,18 @@ function checkLatency(host, port = 80, timeout = 2000) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, lengthCommands, endpointName) {
|
function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, lengthCommands, endpointName, maxPayloadSize = 1024 * 1024) {
|
||||||
const command = message.toString();
|
const rawCommand = message.toString();
|
||||||
|
const command = rawCommand.replace(/[\r\n]+/g, '');
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const normalizedClientIp = clientIp?.replace(/^::ffff:/, '');
|
const normalizedClientIp = clientIp?.replace(/^::ffff:/, '');
|
||||||
if (endpointName === 'text') consoleCmd.logDebug(`Command received from \x1b[90m${clientIp}\x1b[0m: ${command}`);
|
if (endpointName === 'text') consoleCmd.logDebug(`Command received from \x1b[90m${clientIp}\x1b[0m: ${command}`);
|
||||||
|
|
||||||
|
if (command.length > maxPayloadSize) {
|
||||||
|
consoleCmd.logWarn(`Command from \x1b[90m${normalizedClientIp}\x1b[0m on \x1b[90m/${endpointName}\x1b[0m exceeded maximum payload size (${parseInt(command.length / 1024)} KB / ${parseInt(maxPayloadSize / 1024)} KB).`);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize user command history if not present
|
// Initialize user command history if not present
|
||||||
if (!userCommandHistory[clientIp]) userCommandHistory[clientIp] = [];
|
if (!userCommandHistory[clientIp]) userCommandHistory[clientIp] = [];
|
||||||
|
|
||||||
@@ -269,7 +273,7 @@ function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userC
|
|||||||
|
|
||||||
// Check if there are 8 or more commands in the last 20 ms
|
// Check if there are 8 or more commands in the last 20 ms
|
||||||
if (userCommandHistory[clientIp].length >= 8) {
|
if (userCommandHistory[clientIp].length >= 8) {
|
||||||
consoleCmd.logWarn(`User \x1b[90m${clientIp}\x1b[0m is spamming with rapid commands. Connection will be terminated and user will be banned.`);
|
consoleCmd.logWarn(`User \x1b[90m${clientIp}\x1b[0m is spamming with rapid commands. Connection will be terminated and user will be banned.`);
|
||||||
|
|
||||||
// Check if the normalized IP is already in the banlist
|
// Check if the normalized IP is already in the banlist
|
||||||
const isAlreadyBanned = serverConfig.webserver.banlist.some(banEntry => banEntry[0] === normalizedClientIp);
|
const isAlreadyBanned = serverConfig.webserver.banlist.some(banEntry => banEntry[0] === normalizedClientIp);
|
||||||
@@ -281,17 +285,15 @@ function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userC
|
|||||||
configSave();
|
configSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
ws.close(1008, 'Bot-like behavior detected');
|
ws.close(1008, 'Bot-like behavior detected');
|
||||||
return command; // Return command value before closing connection
|
return command; // Return command value before closing connection
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the last message time for general spam detection
|
// Update the last message time for general spam detection
|
||||||
lastMessageTime = now;
|
lastMessageTime = now;
|
||||||
|
|
||||||
// Initialize command history for rate-limiting checks
|
// Initialize command history for rate-limiting checks
|
||||||
if (!userCommands[command]) {
|
if (!userCommands[command]) userCommands[command] = [];
|
||||||
userCommands[command] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record the current timestamp for this command
|
// Record the current timestamp for this command
|
||||||
userCommands[command].push(now);
|
userCommands[command].push(now);
|
||||||
@@ -313,15 +315,45 @@ function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userC
|
|||||||
}
|
}
|
||||||
|
|
||||||
const escapeHtml = (unsafe) => {
|
const escapeHtml = (unsafe) => {
|
||||||
return unsafe
|
return unsafe.replace(/&/g, "&")
|
||||||
.replace(/&/g, "&")
|
.replace(/</g, "<").replace(/>/g, ">")
|
||||||
.replace(/</g, "<")
|
.replace(/"/g, """).replace(/'/g, "'");
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Start plugins with delay
|
||||||
|
function startPluginsWithDelay(plugins, delay) {
|
||||||
|
plugins.forEach((pluginPath, index) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const pluginName = path.basename(pluginPath, '.js'); // Extract plugin name from path
|
||||||
|
consoleCmd.logInfo(`-----------------------------------------------------------------`);
|
||||||
|
consoleCmd.logInfo(`Plugin ${pluginName} loaded successfully!`);
|
||||||
|
require(pluginPath);
|
||||||
|
}, delay * index);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add final log line after all plugins are loaded
|
||||||
|
setTimeout(() => {
|
||||||
|
consoleCmd.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 = {
|
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
|
||||||
}
|
}
|
||||||
+11
-53
@@ -1,4 +1,3 @@
|
|||||||
// Library imports
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const endpoints = require('./endpoints');
|
const endpoints = require('./endpoints');
|
||||||
const session = require('express-session');
|
const session = require('express-session');
|
||||||
@@ -8,21 +7,16 @@ const readline = require('readline');
|
|||||||
const app = express();
|
const app = express();
|
||||||
const httpServer = http.createServer(app);
|
const httpServer = http.createServer(app);
|
||||||
const WebSocket = require('ws');
|
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 path = require('path');
|
||||||
const net = require('net');
|
const net = require('net');
|
||||||
const client = new net.Socket();
|
|
||||||
const { SerialPort } = require('serialport');
|
const { SerialPort } = require('serialport');
|
||||||
const tunnel = require('./tunnel');
|
const tunnel = require('./tunnel');
|
||||||
const { createChatServer } = require('./chat');
|
const { createChatServer } = require('./chat');
|
||||||
const { createAudioServer } = require('./stream/ws.js');
|
|
||||||
const figlet = require('figlet');
|
const figlet = require('figlet');
|
||||||
|
|
||||||
// File imports
|
|
||||||
const helpers = require('./helpers');
|
const helpers = require('./helpers');
|
||||||
|
const { findServerFiles, startPluginsWithDelay } = helpers;
|
||||||
|
|
||||||
const dataHandler = require('./datahandler');
|
const dataHandler = require('./datahandler');
|
||||||
const fmdxList = require('./fmdx_list');
|
const fmdxList = require('./fmdx_list');
|
||||||
const { logError, logInfo, logWarn } = require('./console');
|
const { logError, logInfo, logWarn } = require('./console');
|
||||||
@@ -31,35 +25,10 @@ const { serverConfig, configExists } = require('./server_config');
|
|||||||
const pluginsApi = require('./plugins_api');
|
const pluginsApi = require('./plugins_api');
|
||||||
const pjson = require('../package.json');
|
const pjson = require('../package.json');
|
||||||
|
|
||||||
// Function to find server files based on the plugins listed in config
|
const client = new net.Socket();
|
||||||
function findServerFiles(plugins) {
|
const wss = new WebSocket.Server({ noServer: true, perMessageDeflate: true });
|
||||||
let results = [];
|
const rdsWss = new WebSocket.Server({ noServer: true });
|
||||||
plugins.forEach(plugin => {
|
const pluginsWss = new WebSocket.Server({ noServer: true, perMessageDeflate: true });
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all plugins from config and find corresponding server files
|
// Get all plugins from config and find corresponding server files
|
||||||
const plugins = findServerFiles(serverConfig.plugins);
|
const plugins = findServerFiles(serverConfig.plugins);
|
||||||
@@ -76,22 +45,12 @@ const terminalWidth = readline.createInterface({
|
|||||||
output: process.stdout
|
output: process.stdout
|
||||||
}).output.columns;
|
}).output.columns;
|
||||||
|
|
||||||
|
console.log('\x1b[32m' + figlet.textSync("FM-DX Webserver"));
|
||||||
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\x1b[2mby Noobish @ \x1b[4mFMDX.org\x1b[0m');
|
console.log('\x1b[32m\x1b[2mby Noobish @ \x1b[4mFMDX.org\x1b[0m');
|
||||||
console.log("v" + pjson.version)
|
console.log("v" + pjson.version)
|
||||||
console.log('\x1b[90m' + '─'.repeat(terminalWidth - 1) + '\x1b[0m');
|
console.log('\x1b[90m' + '─'.repeat(terminalWidth - 1) + '\x1b[0m');
|
||||||
|
|
||||||
const chatWss = createChatServer(storage);
|
const audioWss = require('./stream/ws.js');
|
||||||
const audioWss = createAudioServer();
|
|
||||||
// Start ffmpeg
|
|
||||||
require('./stream/index');
|
require('./stream/index');
|
||||||
require('./plugins');
|
require('./plugins');
|
||||||
|
|
||||||
@@ -107,6 +66,7 @@ const sessionMiddleware = session({
|
|||||||
});
|
});
|
||||||
app.use(sessionMiddleware);
|
app.use(sessionMiddleware);
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
|
const chatWss = createChatServer(storage);
|
||||||
|
|
||||||
connectToXdrd();
|
connectToXdrd();
|
||||||
connectToSerial();
|
connectToSerial();
|
||||||
@@ -237,9 +197,7 @@ client.on('data', (data) => {
|
|||||||
const { xdrd } = serverConfig;
|
const { xdrd } = serverConfig;
|
||||||
|
|
||||||
helpers.resolveDataBuffer(data, wss, rdsWss);
|
helpers.resolveDataBuffer(data, wss, rdsWss);
|
||||||
if (authFlags.authMsg == true && authFlags.messageCount > 1) {
|
if (authFlags.authMsg == true && authFlags.messageCount > 1) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
authFlags.messageCount++;
|
authFlags.messageCount++;
|
||||||
const receivedData = data.toString();
|
const receivedData = data.toString();
|
||||||
@@ -398,7 +356,7 @@ wss.on('connection', (ws, request) => {
|
|||||||
let lastWarn = { time: 0 };
|
let lastWarn = { time: 0 };
|
||||||
|
|
||||||
ws.on('message', (message) => {
|
ws.on('message', (message) => {
|
||||||
const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '18', 'text');
|
const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '18', 'text', 16 * 1024);
|
||||||
|
|
||||||
if (!clientIp.includes("127.0.0.1")) {
|
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) ||
|
||||||
|
|||||||
+1
-3
@@ -93,6 +93,4 @@ function createLinks() {
|
|||||||
const allPluginConfigs = collectPluginConfigs();
|
const allPluginConfigs = collectPluginConfigs();
|
||||||
createLinks();
|
createLinks();
|
||||||
|
|
||||||
module.exports = {
|
module.exports = allPluginConfigs;
|
||||||
allPluginConfigs
|
|
||||||
};
|
|
||||||
|
|||||||
+10
-10
@@ -93,8 +93,8 @@ class RDSDecoder {
|
|||||||
|
|
||||||
this.ps[idx * 2] = String.fromCharCode(blockD >> 8);
|
this.ps[idx * 2] = String.fromCharCode(blockD >> 8);
|
||||||
this.ps[idx * 2 + 1] = String.fromCharCode(blockD & 0xFF);
|
this.ps[idx * 2 + 1] = String.fromCharCode(blockD & 0xFF);
|
||||||
this.ps_errors[idx * 2] = error;
|
this.ps_errors[idx * 2] = Math.ceil(d_error * (10/3));
|
||||||
this.ps_errors[idx * 2 + 1] = error;
|
this.ps_errors[idx * 2 + 1] = Math.ceil(d_error * (10/3));
|
||||||
|
|
||||||
this.data.ps = this.ps.join('');
|
this.data.ps = this.ps.join('');
|
||||||
this.data.ps_errors = this.ps_errors.join(',');
|
this.data.ps_errors = this.ps_errors.join(',');
|
||||||
@@ -124,15 +124,15 @@ class RDSDecoder {
|
|||||||
if(c_error < 2 && multiplier !== 2) {
|
if(c_error < 2 && multiplier !== 2) {
|
||||||
this.rt1[idx * multiplier] = String.fromCharCode(blockC >> 8);
|
this.rt1[idx * multiplier] = String.fromCharCode(blockC >> 8);
|
||||||
this.rt1[idx * multiplier + 1] = String.fromCharCode(blockC & 0xFF);
|
this.rt1[idx * multiplier + 1] = String.fromCharCode(blockC & 0xFF);
|
||||||
this.rt1_errors[idx * multiplier] = error;
|
this.rt1_errors[idx * multiplier] = Math.ceil(c_error * (10/3));
|
||||||
this.rt1_errors[idx * multiplier + 1] = error;
|
this.rt1_errors[idx * multiplier + 1] = Math.ceil(c_error * (10/3));
|
||||||
}
|
}
|
||||||
if(d_error < 2) {
|
if(d_error < 2) {
|
||||||
var offset = (multiplier == 2) ? 0 : 2;
|
var offset = (multiplier == 2) ? 0 : 2;
|
||||||
this.rt1[idx * multiplier + offset] = String.fromCharCode(blockD >> 8);
|
this.rt1[idx * multiplier + offset] = String.fromCharCode(blockD >> 8);
|
||||||
this.rt1[idx * multiplier + offset + 1] = String.fromCharCode(blockD & 0xFF);
|
this.rt1[idx * multiplier + offset + 1] = String.fromCharCode(blockD & 0xFF);
|
||||||
this.rt1_errors[idx * multiplier + offset] = error;
|
this.rt1_errors[idx * multiplier + offset] = Math.ceil(d_error * (10/3));
|
||||||
this.rt1_errors[idx * multiplier + offset + 1] = error;
|
this.rt1_errors[idx * multiplier + offset + 1] = Math.ceil(d_error * (10/3));
|
||||||
}
|
}
|
||||||
|
|
||||||
var i = this.rt1.indexOf("\r")
|
var i = this.rt1.indexOf("\r")
|
||||||
@@ -155,15 +155,15 @@ class RDSDecoder {
|
|||||||
if(c_error !== 3 && multiplier !== 2) {
|
if(c_error !== 3 && multiplier !== 2) {
|
||||||
this.rt0[idx * multiplier] = String.fromCharCode(blockC >> 8);
|
this.rt0[idx * multiplier] = String.fromCharCode(blockC >> 8);
|
||||||
this.rt0[idx * multiplier + 1] = String.fromCharCode(blockC & 0xFF);
|
this.rt0[idx * multiplier + 1] = String.fromCharCode(blockC & 0xFF);
|
||||||
this.rt0_errors[idx * multiplier] = error;
|
this.rt0_errors[idx * multiplier] = Math.ceil(c_error * (10/3));
|
||||||
this.rt0_errors[idx * multiplier + 1] = error;
|
this.rt0_errors[idx * multiplier + 1] = Math.ceil(c_error * (10/3));
|
||||||
}
|
}
|
||||||
if(d_error !== 3) {
|
if(d_error !== 3) {
|
||||||
var offset = (multiplier == 2) ? 0 : 2;
|
var offset = (multiplier == 2) ? 0 : 2;
|
||||||
this.rt0[idx * multiplier + offset] = String.fromCharCode(blockD >> 8);
|
this.rt0[idx * multiplier + offset] = String.fromCharCode(blockD >> 8);
|
||||||
this.rt0[idx * multiplier + offset + 1] = String.fromCharCode(blockD & 0xFF);
|
this.rt0[idx * multiplier + offset + 1] = String.fromCharCode(blockD & 0xFF);
|
||||||
this.rt0_errors[idx * multiplier + offset] = error;
|
this.rt0_errors[idx * multiplier + offset] = Math.ceil(d_error * (10/3));
|
||||||
this.rt0_errors[idx * multiplier + offset + 1] = error;
|
this.rt0_errors[idx * multiplier + offset + 1] = Math.ceil(d_error * (10/3));
|
||||||
}
|
}
|
||||||
|
|
||||||
var i = this.rt0.indexOf("\r");
|
var i = this.rt0.indexOf("\r");
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ checkFFmpeg().then((ffmpegPath) => {
|
|||||||
logInfo(`${consoleLogTitle} Using ${ffmpegPath === 'ffmpeg' ? 'system-installed FFmpeg' : 'ffmpeg-static'}`);
|
logInfo(`${consoleLogTitle} Using ${ffmpegPath === 'ffmpeg' ? 'system-installed FFmpeg' : 'ffmpeg-static'}`);
|
||||||
logInfo(`${consoleLogTitle} Starting audio stream on device: \x1b[35m${serverConfig.audio.audioDevice}\x1b[0m`);
|
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);
|
const channels = Number(serverConfig.audio.audioChannels || 2);
|
||||||
|
|
||||||
@@ -139,4 +139,4 @@ checkFFmpeg().then((ffmpegPath) => {
|
|||||||
logError(`${consoleLogTitle} Error: ${err.message}`);
|
logError(`${consoleLogTitle} Error: ${err.message}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports.audio_pipe = audio_pipe;
|
module.exports = audio_pipe;
|
||||||
+19
-23
@@ -1,32 +1,28 @@
|
|||||||
const WebSocket = require('ws');
|
const WebSocket = require('ws');
|
||||||
const { serverConfig } = require('../server_config');
|
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, skipUTF8Validation: true });
|
||||||
const audioWss = new WebSocket.Server({ noServer: true });
|
|
||||||
|
|
||||||
audioWss.on('connection', (ws, request) => {
|
audioWss.on('connection', (ws, request) => {
|
||||||
const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
|
const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
|
||||||
|
|
||||||
if (serverConfig.webserver.banlist?.includes(clientIp)) {
|
if (serverConfig.webserver.banlist?.includes(clientIp)) {
|
||||||
ws.close(1008, 'Banned IP');
|
ws.close(1008, 'Banned IP');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
audio_pipe.on('data', (chunk) => {
|
||||||
|
audioWss.clients.forEach((client) => {
|
||||||
|
if (client.readyState === WebSocket.OPEN) client.send(chunk, {binary: true, compress: false});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
audio_pipe.on('data', (chunk) => {
|
audio_pipe.on('end', () => {
|
||||||
audioWss.clients.forEach((client) => {
|
audioWss.clients.forEach((client) => {
|
||||||
if (client.readyState === WebSocket.OPEN) client.send(chunk, {binary: true, compress: false });
|
client.close(1001, "Audio stream ended");
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
audio_pipe.on('end', () => {
|
module.exports = audioWss;
|
||||||
audioWss.clients.forEach((client) => {
|
|
||||||
client.close(1001, "Audio stream ended");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return audioWss;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { createAudioServer };
|
|
||||||
+6
-18
@@ -80,9 +80,7 @@ async function buildTxDatabase() {
|
|||||||
consoleCmd.logInfo('Fetching transmitter database...');
|
consoleCmd.logInfo('Fetching transmitter database...');
|
||||||
const response = await fetch(`https://maps.fmdx.org/api?qth=${serverConfig.identification.lat},${serverConfig.identification.lon}`, {
|
const response = await fetch(`https://maps.fmdx.org/api?qth=${serverConfig.identification.lat},${serverConfig.identification.lon}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {'Accept': 'application/json'}
|
||||||
'Accept': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
localDb = await response.json();
|
localDb = await response.json();
|
||||||
@@ -169,9 +167,7 @@ function getStateForCoordinates(lat, lon) {
|
|||||||
|
|
||||||
for (const feature of usStatesGeoJson.features) {
|
for (const feature of usStatesGeoJson.features) {
|
||||||
const boundingBox = getStateBoundingBox(feature.geometry.coordinates);
|
const boundingBox = getStateBoundingBox(feature.geometry.coordinates);
|
||||||
if (isCityInState(lat, lon, boundingBox)) {
|
if (isCityInState(lat, lon, boundingBox)) return feature.properties.name; // Return the state's name if city is inside bounding box
|
||||||
return feature.properties.name; // Return the state's name if city is inside bounding box
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -208,22 +204,16 @@ function validPsCompare(rdsPs, stationPs) {
|
|||||||
for (let i = 0; i < standardizedRdsPs.length; i++) {
|
for (let i = 0; i < standardizedRdsPs.length; i++) {
|
||||||
// Skip this position if the character in standardizedRdsPs is an underscore.
|
// Skip this position if the character in standardizedRdsPs is an underscore.
|
||||||
if (standardizedRdsPs[i] === '_') continue;
|
if (standardizedRdsPs[i] === '_') continue;
|
||||||
if (token[i] === standardizedRdsPs[i]) {
|
if (token[i] === standardizedRdsPs[i]) matchCount++;
|
||||||
matchCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (matchCount >= minMatchLen) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
if (matchCount >= minMatchLen) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function evaluateStation(station, esMode) {
|
function evaluateStation(station, esMode) {
|
||||||
let weightDistance = station.distanceKm;
|
let weightDistance = station.distanceKm;
|
||||||
if (esMode && station.distanceKm > 700) {
|
if (esMode && station.distanceKm > 700) weightDistance = Math.abs(station.distanceKm - 1500) + 200;
|
||||||
weightDistance = Math.abs(station.distanceKm - 1500) + 200;
|
|
||||||
}
|
|
||||||
let erp = station.erp && station.erp > 0 ? station.erp : 1;
|
let erp = station.erp && station.erp > 0 ? station.erp : 1;
|
||||||
let extraWeight = erp > weightedErp && station.distanceKm <= weightDistance ? 0.3 : 0;
|
let extraWeight = erp > weightedErp && station.distanceKm <= weightDistance ? 0.3 : 0;
|
||||||
let score = 0;
|
let score = 0;
|
||||||
@@ -394,6 +384,4 @@ function deg2rad(deg) {
|
|||||||
return deg * (Math.PI / 180);
|
return deg * (Math.PI / 180);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = fetchTx;
|
||||||
fetchTx
|
|
||||||
};
|
|
||||||
+9
-2
@@ -3,8 +3,11 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Unauthorized - FM-DX Webserver</title>
|
<title>Unauthorized - FM-DX Webserver</title>
|
||||||
<link href="css/entry.css" type="text/css" rel="stylesheet">
|
<link href="css/entry.css" type="text/css" rel="stylesheet">
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" type="text/css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" type="text/css"
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
rel="stylesheet">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"
|
||||||
|
integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g=="
|
||||||
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
<link rel="icon" type="image/png" href="favicon2.png" />
|
<link rel="icon" type="image/png" href="favicon2.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
</head>
|
</head>
|
||||||
@@ -20,6 +23,10 @@
|
|||||||
<p>
|
<p>
|
||||||
There's a possibility you were kicked by the system.<br>
|
There's a possibility you were kicked by the system.<br>
|
||||||
Please try again later.</p>
|
Please try again later.</p>
|
||||||
|
|
||||||
|
<% if (reason) { %>
|
||||||
|
<p><strong>Reason:</strong> too dig of a bick</p>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1,24 @@
|
|||||||
|
<svg width="128" height="128" viewBox="0 0 128 128"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
||||||
|
<!-- Outer hollow circle -->
|
||||||
|
<circle
|
||||||
|
cx="64"
|
||||||
|
cy="64"
|
||||||
|
r="54"
|
||||||
|
fill="none"
|
||||||
|
stroke="#A7A88B"
|
||||||
|
stroke-width="20"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Inner hollow circle -->
|
||||||
|
<circle
|
||||||
|
cx="64"
|
||||||
|
cy="64"
|
||||||
|
r="22"
|
||||||
|
fill="none"
|
||||||
|
stroke="#FFFFFF"
|
||||||
|
stroke-width="18"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 382 B |
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
<script src="js/libs/chartjs-adapter-luxon.umd.min.js"></script>
|
<script src="js/libs/chartjs-adapter-luxon.umd.min.js"></script>
|
||||||
<script src="js/libs/chartjs-plugin-streaming.min.js"></script>
|
<script src="js/libs/chartjs-plugin-streaming.min.js"></script>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/svg+xml" href="favicon.svg" id="favicon" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<meta property="og:title" content="FM-DX WebServer [<%= tunerName %>]">
|
<meta property="og:title" content="FM-DX WebServer [<%= tunerName %>]">
|
||||||
|
|||||||
+5
-14
@@ -27,11 +27,8 @@ function destroyStream() {
|
|||||||
|
|
||||||
function OnConnectivityCallback(isConnected) {
|
function OnConnectivityCallback(isConnected) {
|
||||||
console.log("Connectivity changed:", isConnected);
|
console.log("Connectivity changed:", isConnected);
|
||||||
if (Stream) {
|
if (Stream) Stream.Volume = $('#volumeSlider').val();
|
||||||
Stream.Volume = $('#volumeSlider').val();
|
else console.warn("Stream is not initialized.");
|
||||||
} else {
|
|
||||||
console.warn("Stream is not initialized.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -44,18 +41,14 @@ function OnPlayButtonClick(_ev) {
|
|||||||
shouldReconnect = false;
|
shouldReconnect = false;
|
||||||
destroyStream();
|
destroyStream();
|
||||||
$playbutton.find('.fa-solid').toggleClass('fa-stop fa-play');
|
$playbutton.find('.fa-solid').toggleClass('fa-stop fa-play');
|
||||||
if (isAppleiOS && 'audioSession' in navigator) {
|
if (isAppleiOS && 'audioSession' in navigator) navigator.audioSession.type = "none";
|
||||||
navigator.audioSession.type = "none";
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
console.log("Starting stream...");
|
console.log("Starting stream...");
|
||||||
shouldReconnect = true;
|
shouldReconnect = true;
|
||||||
createStream();
|
createStream();
|
||||||
Stream.Start();
|
Stream.Start();
|
||||||
$playbutton.find('.fa-solid').toggleClass('fa-play fa-stop');
|
$playbutton.find('.fa-solid').toggleClass('fa-play fa-stop');
|
||||||
if (isAppleiOS && 'audioSession' in navigator) {
|
if (isAppleiOS && 'audioSession' in navigator) navigator.audioSession.type = "playback";
|
||||||
navigator.audioSession.type = "playback";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$playbutton.addClass('bg-gray').prop('disabled', true);
|
$playbutton.addClass('bg-gray').prop('disabled', true);
|
||||||
@@ -70,9 +63,7 @@ function updateVolume() {
|
|||||||
newVolumeGlobal = newVolume;
|
newVolumeGlobal = newVolume;
|
||||||
console.log("Volume updated to:", newVolume);
|
console.log("Volume updated to:", newVolume);
|
||||||
Stream.Volume = newVolume;
|
Stream.Volume = newVolume;
|
||||||
} else {
|
} else console.warn("Stream is not initialized.");
|
||||||
console.warn("Stream is not initialized.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(Init);
|
$(document).ready(Init);
|
||||||
|
|||||||
+11
-21
@@ -3,18 +3,13 @@ function tuneUp() {
|
|||||||
if (socket.readyState === WebSocket.OPEN) {
|
if (socket.readyState === WebSocket.OPEN) {
|
||||||
getCurrentFreq();
|
getCurrentFreq();
|
||||||
let addVal = 0;
|
let addVal = 0;
|
||||||
if (currentFreq < 0.52) {
|
if (currentFreq < 0.52) addVal = 9 - (Math.round(currentFreq*1000) % 9);
|
||||||
addVal = 9 - (Math.round(currentFreq*1000) % 9);
|
else if (currentFreq < 1.71) {
|
||||||
} else if (currentFreq < 1.71) {
|
|
||||||
// TODO: Rework to replace 9 with 9 or 10 based on regionalisation setting
|
// TODO: Rework to replace 9 with 9 or 10 based on regionalisation setting
|
||||||
addVal = 9 - (Math.round(currentFreq*1000) % 9);
|
addVal = 9 - (Math.round(currentFreq*1000) % 9);
|
||||||
} else if (currentFreq < 29.6) {
|
} else if (currentFreq < 29.6) addVal = 5 - (Math.round(currentFreq*1000) % 5);
|
||||||
addVal = 5 - (Math.round(currentFreq*1000) % 5);
|
else if (currentFreq >= 65.9 && currentFreq < 74) addVal = 30 - ((Math.round(currentFreq*1000) - 65900) % 30);
|
||||||
} else if (currentFreq >= 65.9 && currentFreq < 74) {
|
else addVal = 100 - (Math.round(currentFreq*1000) % 100);
|
||||||
addVal = 30 - ((Math.round(currentFreq*1000) - 65900) % 30);
|
|
||||||
} else {
|
|
||||||
addVal = 100 - (Math.round(currentFreq*1000) % 100);
|
|
||||||
}
|
|
||||||
socket.send("T" + (Math.round(currentFreq*1000) + addVal));
|
socket.send("T" + (Math.round(currentFreq*1000) + addVal));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,18 +18,13 @@ function tuneDown() {
|
|||||||
if (socket.readyState === WebSocket.OPEN) {
|
if (socket.readyState === WebSocket.OPEN) {
|
||||||
getCurrentFreq();
|
getCurrentFreq();
|
||||||
let subVal = 0;
|
let subVal = 0;
|
||||||
if (currentFreq < 0.52) {
|
if (currentFreq < 0.52) subVal = (Math.round(currentFreq*1000) % 9 == 0) ? 9 : (Math.round(currentFreq*1000) % 9);
|
||||||
|
else if (currentFreq < 1.71) {
|
||||||
|
// TODO: Rework to replace 9 with 9 or 10 based on regionalisation setting (Americans use 10, because of dumbfuckinstan)
|
||||||
subVal = (Math.round(currentFreq*1000) % 9 == 0) ? 9 : (Math.round(currentFreq*1000) % 9);
|
subVal = (Math.round(currentFreq*1000) % 9 == 0) ? 9 : (Math.round(currentFreq*1000) % 9);
|
||||||
} else if (currentFreq < 1.71) {
|
} else if (currentFreq < 29.6) subVal = (Math.round(currentFreq*1000) % 5 == 0) ? 5 : (Math.round(currentFreq*1000) % 5);
|
||||||
// TODO: Rework to replace 9 with 9 or 10 based on regionalisation setting
|
else if (currentFreq > 65.9 && currentFreq <= 74) subVal = ((Math.round(currentFreq*1000) - 65900) % 30 == 0) ? 30 : ((Math.round(currentFreq*1000) - 65900) % 30);
|
||||||
subVal = (Math.round(currentFreq*1000) % 9 == 0) ? 9 : (Math.round(currentFreq*1000) % 9);
|
else subVal = (Math.round(currentFreq*1000) % 100 == 0) ? 100 : (Math.round(currentFreq*1000) % 100);
|
||||||
} else if (currentFreq < 29.6) {
|
|
||||||
subVal = (Math.round(currentFreq*1000) % 5 == 0) ? 5 : (Math.round(currentFreq*1000) % 5);
|
|
||||||
} else if (currentFreq > 65.9 && currentFreq <= 74) {
|
|
||||||
subVal = ((Math.round(currentFreq*1000) - 65900) % 30 == 0) ? 30 : ((Math.round(currentFreq*1000) - 65900) % 30);
|
|
||||||
} else {
|
|
||||||
subVal = (Math.round(currentFreq*1000) % 100 == 0) ? 100 : (Math.round(currentFreq*1000) % 100);
|
|
||||||
}
|
|
||||||
socket.send("T" + (Math.round(currentFreq*1000) - subVal));
|
socket.send("T" + (Math.round(currentFreq*1000) - subVal));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-6
@@ -19,9 +19,7 @@ $(document).ready(function() {
|
|||||||
function generateRandomString(length) {
|
function generateRandomString(length) {
|
||||||
const characters = 'ABCDEFGHJKMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz0123456789';
|
const characters = 'ABCDEFGHJKMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz0123456789';
|
||||||
let result = '';
|
let result = '';
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,9 +85,7 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
chatSendInput.keypress(function(event) {
|
chatSendInput.keypress(function(event) {
|
||||||
if (event.which === 13) {
|
if (event.which === 13) sendMessage();
|
||||||
sendMessage();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function sendMessage() {
|
function sendMessage() {
|
||||||
|
|||||||
+9
-25
@@ -38,9 +38,7 @@ function fetchConfig() {
|
|||||||
|
|
||||||
function populateFields(data, prefix = "") {
|
function populateFields(data, prefix = "") {
|
||||||
$.each(data, (key, value) => {
|
$.each(data, (key, value) => {
|
||||||
if (value === null) {
|
if (value === null) value = ""; // Convert null to an empty string
|
||||||
value = ""; // Convert null to an empty string
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = `${prefix}${prefix ? "-" : ""}${key}`;
|
let id = `${prefix}${prefix ? "-" : ""}${key}`;
|
||||||
const $element = $(`#${id}`);
|
const $element = $(`#${id}`);
|
||||||
@@ -50,11 +48,8 @@ function populateFields(data, prefix = "") {
|
|||||||
$element.find('option').each(function() {
|
$element.find('option').each(function() {
|
||||||
const $option = $(this);
|
const $option = $(this);
|
||||||
const dataName = $option.data('name');
|
const dataName = $option.data('name');
|
||||||
if (value.includes(dataName)) {
|
if (value.includes(dataName)) $option.prop('selected', true);
|
||||||
$option.prop('selected', true);
|
else $option.prop('selected', false);
|
||||||
} else {
|
|
||||||
$option.prop('selected', false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$element.trigger('change');
|
$element.trigger('change');
|
||||||
@@ -68,11 +63,8 @@ function populateFields(data, prefix = "") {
|
|||||||
const arrayId = `${id}-${index + 1}`;
|
const arrayId = `${id}-${index + 1}`;
|
||||||
const $arrayElement = $(`#${arrayId}`);
|
const $arrayElement = $(`#${arrayId}`);
|
||||||
|
|
||||||
if ($arrayElement.length) {
|
if ($arrayElement.length) $arrayElement.val(item);
|
||||||
$arrayElement.val(item);
|
else console.log(`Element with id ${arrayId} not found`);
|
||||||
} else {
|
|
||||||
console.log(`Element with id ${arrayId} not found`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -92,9 +84,7 @@ function populateFields(data, prefix = "") {
|
|||||||
const $dropdownOption = $element.siblings('ul.options').find(`li[data-value="${value}"]`);
|
const $dropdownOption = $element.siblings('ul.options').find(`li[data-value="${value}"]`);
|
||||||
$element.val($dropdownOption.length ? $dropdownOption.text() : value);
|
$element.val($dropdownOption.length ? $dropdownOption.text() : value);
|
||||||
$element.attr('data-value', value);
|
$element.attr('data-value', value);
|
||||||
} else {
|
} else $element.val(value);
|
||||||
$element.val(value);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,9 +101,7 @@ function updateConfigData(data, prefix = "") {
|
|||||||
if ($presetElement.length) {
|
if ($presetElement.length) {
|
||||||
data[key].push($presetElement.val() || null); // Allow null if necessary
|
data[key].push($presetElement.val() || null); // Allow null if necessary
|
||||||
index++;
|
index++;
|
||||||
} else {
|
} else break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -123,16 +111,12 @@ function updateConfigData(data, prefix = "") {
|
|||||||
const $selectedOptions = $element.find('option:selected');
|
const $selectedOptions = $element.find('option:selected');
|
||||||
$selectedOptions.each(function() {
|
$selectedOptions.each(function() {
|
||||||
const dataName = $(this).attr('data-name');
|
const dataName = $(this).attr('data-name');
|
||||||
if (dataName) {
|
if (dataName) data[key].push(dataName);
|
||||||
data[key].push(dataName);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) return updateConfigData(value, id);
|
||||||
return updateConfigData(value, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($element.length) {
|
if ($element.length) {
|
||||||
const newValue = $element.attr("data-value") ?? $element.val() ?? null;
|
const newValue = $element.attr("data-value") ?? $element.val() ?? null;
|
||||||
|
|||||||
+42
-44
@@ -23,38 +23,38 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
switch($currentDropdown.attr('id')) {
|
switch($currentDropdown.attr('id')) {
|
||||||
case 'data-ant':
|
case 'data-ant':
|
||||||
socket.send("Z" + $(event.currentTarget).attr('data-value'));
|
socket.send("Z" + $(event.currentTarget).attr('data-value'));
|
||||||
resetRDS(getCurrentFreq()); // Reset RDS when change antenna input
|
resetRDS(getCurrentFreq()); // Reset RDS when change antenna input
|
||||||
break;
|
break;
|
||||||
case 'data-ant-phone':
|
case 'data-ant-phone':
|
||||||
socket.send("Z" + $(event.currentTarget).attr('data-value'));
|
socket.send("Z" + $(event.currentTarget).attr('data-value'));
|
||||||
resetRDS(getCurrentFreq()); // Reset RDS when change antenna input
|
resetRDS(getCurrentFreq()); // Reset RDS when change antenna input
|
||||||
break;
|
break;
|
||||||
case 'data-bw':
|
case 'data-bw':
|
||||||
legacyBwValue = $(event.currentTarget).attr('data-value2') || "";
|
legacyBwValue = $(event.currentTarget).attr('data-value2') || "";
|
||||||
socket.send("F" + legacyBwValue);
|
socket.send("F" + legacyBwValue);
|
||||||
socket.send("W" + $(event.currentTarget).attr('data-value'));
|
socket.send("W" + $(event.currentTarget).attr('data-value'));
|
||||||
$currentDropdown.find('input').val($(event.currentTarget).text());
|
$currentDropdown.find('input').val($(event.currentTarget).text());
|
||||||
break;
|
break;
|
||||||
case 'data-bw-phone':
|
case 'data-bw-phone':
|
||||||
legacyBwValue = $(event.currentTarget).attr('data-value2') || "";
|
legacyBwValue = $(event.currentTarget).attr('data-value2') || "";
|
||||||
socket.send("F" + legacyBwValue);
|
socket.send("F" + legacyBwValue);
|
||||||
socket.send("W" + $(event.currentTarget).attr('data-value'));
|
socket.send("W" + $(event.currentTarget).attr('data-value'));
|
||||||
$currentDropdown.find('input').val($(event.currentTarget).text());
|
$currentDropdown.find('input').val($(event.currentTarget).text());
|
||||||
break;
|
break;
|
||||||
case 'data-agc':
|
case 'data-agc':
|
||||||
socket.send("A" + $(event.currentTarget).attr('data-value'));
|
socket.send("A" + $(event.currentTarget).attr('data-value'));
|
||||||
$currentDropdown.find('input').val($(event.currentTarget).text());
|
$currentDropdown.find('input').val($(event.currentTarget).text());
|
||||||
break;
|
break;
|
||||||
case 'data-agc-phone':
|
case 'data-agc-phone':
|
||||||
socket.send("A" + $(event.currentTarget).attr('data-value'));
|
socket.send("A" + $(event.currentTarget).attr('data-value'));
|
||||||
$currentDropdown.find('input').val($(event.currentTarget).text());
|
$currentDropdown.find('input').val($(event.currentTarget).text());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$currentDropdown.find('input')
|
$currentDropdown.find('input')
|
||||||
.val($(event.currentTarget).text())
|
.val($(event.currentTarget).text())
|
||||||
.attr('data-value', $(event.currentTarget).data('value'));
|
.attr('data-value', $(event.currentTarget).data('value'));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use setTimeout to delay class removal
|
// Use setTimeout to delay class removal
|
||||||
@@ -80,24 +80,24 @@ $(document).ready(function() {
|
|||||||
const $options = currentDropdown.find('.option');
|
const $options = currentDropdown.find('.option');
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'ArrowDown':
|
case 'ArrowDown':
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
currentIndex = (currentIndex + 1) % $options.length;
|
currentIndex = (currentIndex + 1) % $options.length;
|
||||||
$options.eq(currentIndex).focus();
|
$options.eq(currentIndex).focus();
|
||||||
break;
|
break;
|
||||||
case 'ArrowUp':
|
case 'ArrowUp':
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
currentIndex = (currentIndex - 1 + $options.length) % $options.length;
|
currentIndex = (currentIndex - 1 + $options.length) % $options.length;
|
||||||
$options.eq(currentIndex).focus();
|
$options.eq(currentIndex).focus();
|
||||||
break;
|
break;
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
$options.eq(currentIndex).click();
|
$options.eq(currentIndex).click();
|
||||||
break;
|
break;
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
currentDropdown.removeClass('opened');
|
currentDropdown.removeClass('opened');
|
||||||
currentDropdown = null;
|
currentDropdown = null;
|
||||||
currentIndex = -1;
|
currentIndex = -1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -106,9 +106,7 @@ $(document).ready(function() {
|
|||||||
$listOfOptions.on('click', selectOption);
|
$listOfOptions.on('click', selectOption);
|
||||||
$dropdowns.on('click', 'input', toggleDropdown);
|
$dropdowns.on('click', 'input', toggleDropdown);
|
||||||
$dropdowns.on('keydown', 'input', function(event) {
|
$dropdowns.on('keydown', 'input', function(event) {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') toggleDropdown(event);
|
||||||
toggleDropdown(event);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
$dropdowns.on('keydown', '.option', navigateOptions);
|
$dropdowns.on('keydown', '.option', navigateOptions);
|
||||||
|
|
||||||
|
|||||||
+3
-10
@@ -1028,15 +1028,10 @@ const updateDataElements = throttle(function(parsedData) {
|
|||||||
updateHtmlIfChanged($('#alternative-txes'), altTxInfo);
|
updateHtmlIfChanged($('#alternative-txes'), altTxInfo);
|
||||||
updateTextIfChanged($('#data-station-distance'), txDistance);
|
updateTextIfChanged($('#data-station-distance'), txDistance);
|
||||||
$dataStationContainer.css('display', 'block');
|
$dataStationContainer.css('display', 'block');
|
||||||
} else {
|
} else $dataStationContainer.removeAttr('style');
|
||||||
$dataStationContainer.removeAttr('style');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(parsedData.txInfo.tx.length > 1 && parsedData.txInfo.dist > 150 && parsedData.txInfo.dist < 4000) {
|
if(parsedData.txInfo.tx.length > 1 && parsedData.txInfo.dist > 150 && parsedData.txInfo.dist < 4000) $('.log-fmlist').removeAttr('disabled').removeClass('btn-disabled cursor-disabled');
|
||||||
$('.log-fmlist').removeAttr('disabled').removeClass('btn-disabled cursor-disabled');
|
else $('.log-fmlist').attr('disabled', 'true').addClass('btn-disabled cursor-disabled');
|
||||||
} else {
|
|
||||||
$('.log-fmlist').attr('disabled', 'true').addClass('btn-disabled cursor-disabled');
|
|
||||||
}
|
|
||||||
updateHtmlIfChanged($('#data-regular-pi'), parsedData.txInfo.reg === true ? parsedData.txInfo.pi : ' ');
|
updateHtmlIfChanged($('#data-regular-pi'), parsedData.txInfo.reg === true ? parsedData.txInfo.pi : ' ');
|
||||||
|
|
||||||
if (updateCounter % 8 === 0) {
|
if (updateCounter % 8 === 0) {
|
||||||
@@ -1084,7 +1079,6 @@ function updatePanels(parsedData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (updateCounter % 3 === 0) {
|
if (updateCounter % 3 === 0) {
|
||||||
|
|
||||||
updateButtonState("data-eq", parsedData.eq);
|
updateButtonState("data-eq", parsedData.eq);
|
||||||
updateButtonState("data-ims", parsedData.ims);
|
updateButtonState("data-ims", parsedData.ims);
|
||||||
|
|
||||||
@@ -1288,4 +1282,3 @@ function initTooltips(target = null) {
|
|||||||
$('#preset1').parent().hide();
|
$('#preset1').parent().hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+4
-9
@@ -31,15 +31,11 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
|
|
||||||
$(document).on("click", function(event) { // Close the modal when clicking outside of it
|
$(document).on("click", function(event) { // Close the modal when clicking outside of it
|
||||||
if ($(event.target).is(modal)) {
|
if ($(event.target).is(modal)) closeModal();
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on("keydown", function(event) { // Close the modal when pressing ESC key
|
$(document).on("keydown", function(event) { // Close the modal when pressing ESC key
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") closeModal();
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".tuner-mobile-settings").on("click", function () {
|
$(".tuner-mobile-settings").on("click", function () {
|
||||||
@@ -69,9 +65,8 @@ function initPopups() {
|
|||||||
function togglePopup(targetSelector) {
|
function togglePopup(targetSelector) {
|
||||||
const $target = $(targetSelector);
|
const $target = $(targetSelector);
|
||||||
|
|
||||||
if ($target.is(":visible")) {
|
if ($target.is(":visible")) $target.fadeOut(200);
|
||||||
$target.fadeOut(200);
|
else {
|
||||||
} else {
|
|
||||||
$(".popup-window").fadeOut(200);
|
$(".popup-window").fadeOut(200);
|
||||||
$target.fadeIn(200);
|
$target.fadeIn(200);
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-3
@@ -75,6 +75,27 @@ function getQueryParameter(name) {
|
|||||||
return urlParams.get(name);
|
return urlParams.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateFavicon(color) {
|
||||||
|
function rgbToHex(rgb) {
|
||||||
|
const result = rgb.match(/\d+/g);
|
||||||
|
return "#" + result.slice(0, 3).map(x =>(+x).toString(16).padStart(2, "0")).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
const hex = rgbToHex(color);
|
||||||
|
|
||||||
|
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||||
|
<circle cx="64" cy="64" r="54" fill="none" stroke="${hex}" stroke-width="20"/>
|
||||||
|
<circle cx="64" cy="64" r="22" fill="none" stroke="white" stroke-width="18"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
|
const base64 = btoa(svg);
|
||||||
|
|
||||||
|
$('#favicon').attr(
|
||||||
|
'href',
|
||||||
|
`data:image/svg+xml;base64,${base64}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function setTheme(themeName) {
|
function setTheme(themeName) {
|
||||||
const themeColors = themes[themeName];
|
const themeColors = themes[themeName];
|
||||||
if (themeColors) {
|
if (themeColors) {
|
||||||
@@ -94,6 +115,7 @@ function setTheme(themeName) {
|
|||||||
$(':root').css('--color-text', themeColors[2]);
|
$(':root').css('--color-text', themeColors[2]);
|
||||||
$(':root').css('--color-text-2', textColor2);
|
$(':root').css('--color-text-2', textColor2);
|
||||||
$('.wrapper-outer').css('background-color', backgroundColorWithOpacity);
|
$('.wrapper-outer').css('background-color', backgroundColorWithOpacity);
|
||||||
|
updateFavicon(themeColors[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,9 +189,7 @@ function loadInitialSettings() {
|
|||||||
if(signalParameter && !localStorage.getItem('signalUnit')) {
|
if(signalParameter && !localStorage.getItem('signalUnit')) {
|
||||||
signalSelector.find('input').val(signalSelector.find('.option[data-value="' + signalParameter + '"]').text());
|
signalSelector.find('input').val(signalSelector.find('.option[data-value="' + signalParameter + '"]').text());
|
||||||
localStorage.setItem('signalUnit', signalParameter);
|
localStorage.setItem('signalUnit', signalParameter);
|
||||||
} else {
|
} else signalSelector.find('input').val(signalSelector.find('.option[data-value="' + savedUnit + '"]').text());
|
||||||
signalSelector.find('input').val(signalSelector.find('.option[data-value="' + savedUnit + '"]').text());
|
|
||||||
}
|
|
||||||
|
|
||||||
signalSelector.on('click', '.option', (event) => {
|
signalSelector.on('click', '.option', (event) => {
|
||||||
const selectedSignalUnit = $(event.target).data('value');
|
const selectedSignalUnit = $(event.target).data('value');
|
||||||
|
|||||||
+2
-6
@@ -27,9 +27,7 @@ function mapCreate() {
|
|||||||
zoom: 3,
|
zoom: 3,
|
||||||
worldCopyJump: true
|
worldCopyJump: true
|
||||||
});
|
});
|
||||||
} else {
|
} else map.setZoom(3).panTo([40, 0]);
|
||||||
map.setZoom(3).panTo([40, 0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
L.tileLayer(tilesURL, {
|
L.tileLayer(tilesURL, {
|
||||||
attribution: mapAttrib,
|
attribution: mapAttrib,
|
||||||
@@ -272,9 +270,7 @@ function checkTunnelServers() {
|
|||||||
|
|
||||||
// If this li is the currently selected one, update input text too
|
// If this li is the currently selected one, update input text too
|
||||||
// Note: input.val() holds the label, so match by label is safer
|
// Note: input.val() holds the label, so match by label is safer
|
||||||
if ($li.text() === selectedValue || server.value === selectedValue) {
|
if ($li.text() === selectedValue || server.value === selectedValue) $input.val(server.label);
|
||||||
$input.val(server.label);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-2
@@ -1,2 +1,2 @@
|
|||||||
const versionDate = new Date('Feb 24, 2026 01:00:00');
|
const versionDate = new Date('Feb 24, 2026 15:00:00');
|
||||||
const currentVersion = `v1.4.0 [${versionDate.getDate()}/${versionDate.getMonth() + 1}/${versionDate.getFullYear()}]`;
|
const currentVersion = `v1.4.0a [${versionDate.getDate()}/${versionDate.getMonth() + 1}/${versionDate.getFullYear()}]`;
|
||||||
+1
-3
@@ -27,8 +27,6 @@ function navigateStep(isNext) {
|
|||||||
currentStep.hide();
|
currentStep.hide();
|
||||||
targetStep.show();
|
targetStep.show();
|
||||||
updateProgressBar(targetStep);
|
updateProgressBar(targetStep);
|
||||||
} else if (isNext) {
|
} else if (isNext) submitConfig();
|
||||||
submitConfig();
|
|
||||||
}
|
|
||||||
updateWizardContent();
|
updateWizardContent();
|
||||||
}
|
}
|
||||||
+2
-2
@@ -8,7 +8,7 @@
|
|||||||
<script src="js/libs/jquery.min.js"></script>
|
<script src="js/libs/jquery.min.js"></script>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" type="text/css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" type="text/css" rel="stylesheet">
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/svg+xml" href="favicon.svg" id="favicon" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<div class="wrapper-outer wrapper-full">
|
<div class="wrapper-outer wrapper-full">
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div class="panel-100 no-bg">
|
<div class="panel-100 no-bg">
|
||||||
<img class="top-25" src="favicon.png" height="64px">
|
<img class="top-25" src="favicon.svg" height="64px">
|
||||||
<p>You are currently not logged in as an administrator and therefore can't change the settings.</p>
|
<p>You are currently not logged in as an administrator and therefore can't change the settings.</p>
|
||||||
<p>Please login below.</p>
|
<p>Please login below.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
<script src="js/libs/jquery.min.js"></script>
|
<script src="js/libs/jquery.min.js"></script>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" type="text/css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" type="text/css" rel="stylesheet">
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/svg+xml" href="favicon.svg" id="favicon" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
<script src="js/libs/jquery.min.js"></script>
|
<script src="js/libs/jquery.min.js"></script>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" type="text/css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" type="text/css" rel="stylesheet">
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/svg+xml" href="favicon.svg" id="favicon" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Reference in New Issue
Block a user