diff --git a/server/helpers.js b/server/helpers.js index b81bdf6..9e7e9cf 100644 --- a/server/helpers.js +++ b/server/helpers.js @@ -82,7 +82,7 @@ function handleConnect(clientIp, currentUsers, ws) { const userLocation = `${locationInfo.city}, ${locationInfo.regionName}, ${locationInfo.countryCode}`; const userData = { ip: clientIp, location: userLocation, time: connectionTime, instance: ws }; storage.connectedUsers.push(userData); - consoleCmd.logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m Location: ${locationInfo.city}, ${locationInfo.region}, ${locationInfo.country}`); + consoleCmd.logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m Location: ${locationInfo.city}, ${locationInfo.regionName}, ${locationInfo.country}`); } } catch (error) { console.log(error); @@ -90,7 +90,7 @@ function handleConnect(clientIp, currentUsers, ws) { } }); }).on('error', (err) => { - consoleCmd.chunklogInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m`); + consoleCmd.logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m`); }); } @@ -222,7 +222,16 @@ function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userC return command; // Return command value for normal execution } +const escapeHtml = (unsafe) => { + return unsafe + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +}; + module.exports = { - authenticateWithXdrd, parseMarkdown, handleConnect, removeMarkdown, formatUptime, resolveDataBuffer, kickClient, checkIPv6Support, antispamProtection + authenticateWithXdrd, parseMarkdown, handleConnect, removeMarkdown, formatUptime, resolveDataBuffer, kickClient, checkIPv6Support, antispamProtection, escapeHtml } \ No newline at end of file diff --git a/server/index.js b/server/index.js index f61274d..7db7dd7 100644 --- a/server/index.js +++ b/server/index.js @@ -5,6 +5,7 @@ const session = require('express-session'); const bodyParser = require('body-parser'); const http = require('http'); const httpProxy = require('http-proxy'); +const readline = require('readline'); const app = express(); const httpServer = http.createServer(app); const WebSocket = require('ws'); @@ -72,6 +73,10 @@ if (plugins.length > 0) { }, 3000); // Initial delay of 3 seconds for the first plugin } +const terminalWidth = readline.createInterface({ + input: process.stdin, + output: process.stdout +}).output.columns; console.log(`\x1b[32m @@ -81,8 +86,9 @@ console.log(`\x1b[32m | _| | | | |_____| |_| / \\ \\ V V / __/ |_) \\__ \\ __/ | \\ V / __/ | |_| |_| |_| |____/_/\\_\\ \\_/\\_/ \\___|_.__/|___/\\___|_| \\_/ \\___|_| `); -console.log('\x1b[0mFM-DX Webserver', pjson.version); -console.log('\x1b[90m―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――'); +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'); // Start ffmpeg require('./stream/index'); @@ -446,9 +452,9 @@ wss.on('connection', (ws, request) => { const { isAdminAuthenticated, isTuneAuthenticated } = request.session || {}; - if (serverConfig.publicTuner || (serverConfig.lockToAdmin && isAdminAuthenticated) || (!serverConfig.lockToAdmin && isTuneAuthenticated)) { + if ((serverConfig.publicTuner && !serverConfig.lockToAdmin) || isAdminAuthenticated || (!serverConfig.publicTuner && !serverConfig.lockToAdmin && isTuneAuthenticated)) { output.write(`${command}\n`); - } + } }); @@ -528,6 +534,11 @@ chatWss.on('connection', (ws, request) => { return; } + // Escape nickname and other potentially unsafe fields + if (messageData.nickname) { + messageData.nickname = helpers.escapeHtml(messageData.nickname); + } + messageData.ip = clientIp; const currentTime = new Date(); diff --git a/server/server_config.js b/server/server_config.js index 20db40b..ac72500 100644 --- a/server/server_config.js +++ b/server/server_config.js @@ -100,7 +100,7 @@ let serverConfig = { autoShutdown: false, enableDefaultFreq: false, defaultFreq: "87.5", - TestTest: "tesst" + bwSwitch: false }; // Function to add missing fields without overwriting existing values diff --git a/web/css/helpers.css b/web/css/helpers.css index 1b48c71..7993eee 100644 --- a/web/css/helpers.css +++ b/web/css/helpers.css @@ -230,6 +230,19 @@ table .input-text { clear: both; } +.blink { + animation: blinker 1.5s infinite; +} + +@keyframes blinker { + 0% { + background-color: var(--color-4); + } + 100% { + background-color: var(--color-2); + } +} + @media only screen and (max-width: 960px) { .text-medium-big { font-size: 32px; diff --git a/web/css/modal.css b/web/css/modal.css index 0c4be0c..f0a1ea5 100644 --- a/web/css/modal.css +++ b/web/css/modal.css @@ -1,3 +1,12 @@ +body.modal-open { + overflow: hidden; +} + +.modal-panel, .modal-panel-chat { + max-height: 100vh; + overflow-y: auto; +} + .modal { display: none; position: fixed; @@ -162,7 +171,7 @@ width: 100%; } .modal-panel-chat { - height: 500px; + height: 550px; } #chat-chatbox { height: 333px !important; diff --git a/web/js/chat.js b/web/js/chat.js index e351209..3243698 100644 --- a/web/js/chat.js +++ b/web/js/chat.js @@ -30,7 +30,7 @@ $(document).ready(function() { const isAdmin = messageData.admin ? '[ADMIN]' : ''; if (messageData.type === 'clientIp') { - chatIdentityNickname.html(isAdmin + " " + savedNickname); + chatIdentityNickname.html(isAdmin).append(document.createTextNode(" " + savedNickname)); chatIdentityNickname.attr('title', messageData.ip); } else { const chatMessage = ` @@ -49,7 +49,7 @@ $(document).ready(function() { chatMessageCount++; chatMessagesCount.text(chatMessageCount); chatMessagesCount.attr("aria-label", "Chat (" + chatMessageCount + " unread)"); - chatButton.removeClass('bg-color-2').addClass('bg-color-4'); + chatButton.removeClass('bg-color-2').addClass('blink'); } } } diff --git a/web/js/confighandler.js b/web/js/confighandler.js index ded0f79..cb071c8 100644 --- a/web/js/confighandler.js +++ b/web/js/confighandler.js @@ -38,22 +38,14 @@ function fetchConfig() { function populateFields(data, prefix = "") { $.each(data, (key, value) => { - if (key === "presets" && Array.isArray(value)) { - value.forEach((item, index) => { - const presetId = `${prefix}${prefix ? "-" : ""}${key}-${index + 1}`; - const $element = $(`#${presetId}`); - - if ($element.length) { - $element.val(item); - } - }); - return; + if (value === null) { + value = ""; // Convert null to an empty string } const id = `${prefix}${prefix ? "-" : ""}${key}`; const $element = $(`#${id}`); - if (typeof value === "object" && !Array.isArray(value)) { + if (typeof value === "object" && value !== null && !Array.isArray(value)) { populateFields(value, id); return; } @@ -96,7 +88,7 @@ function updateConfigData(data, prefix = "") { while (true) { const $presetElement = $(`#${prefix}${prefix ? "-" : ""}${key}-${index}`); if ($presetElement.length) { - data[key].push($presetElement.val()); + data[key].push($presetElement.val() || null); // Allow null if necessary index++; } else { break; @@ -117,12 +109,13 @@ function updateConfigData(data, prefix = "") { return; } - if (typeof value === "object" && !Array.isArray(value)) { + if (typeof value === "object" && value !== null && !Array.isArray(value)) { return updateConfigData(value, id); } if ($element.length) { - data[key] = typeof value === "boolean" ? $element.is(":checked") : $element.attr("data-value") ?? $element.val(); + const newValue = $element.attr("data-value") ?? $element.val() ?? null; + data[key] = typeof value === "boolean" ? $element.is(":checked") : newValue; } }); } diff --git a/web/js/init.js b/web/js/init.js index 8a62a34..9f4c32f 100644 --- a/web/js/init.js +++ b/web/js/init.js @@ -1,9 +1,9 @@ -var currentDate = new Date('Jan 11, 2025 21:00:00'); +var currentDate = new Date('Jan 16, 2025 21:00:00'); var day = currentDate.getDate(); var month = currentDate.getMonth() + 1; // Months are zero-indexed, so add 1 var year = currentDate.getFullYear(); var formattedDate = day + '/' + month + '/' + year; -var currentVersion = 'v1.3.3 [' + formattedDate + ']'; +var currentVersion = 'v1.3.3.1 [' + formattedDate + ']'; getInitialSettings(); removeUrlParameters(); diff --git a/web/js/modal.js b/web/js/modal.js index 2638635..20bf901 100644 --- a/web/js/modal.js +++ b/web/js/modal.js @@ -10,17 +10,18 @@ $(document).ready(function() { function openModal(panel) { modal.css("display", "block"); panel.css("display", "block"); + $("body").addClass("modal-open"); // Disable body scrolling setTimeout(function() { modal.css("opacity", 1); }, 10); } - - // Function to close the modal + function closeModal() { modal.css("opacity", 0); setTimeout(function() { modal.css("display", "none"); modalPanel.add(chatPanel).css("display", "none"); + $("body").removeClass("modal-open"); // Enable body scrolling }, 300); } diff --git a/web/js/setup.js b/web/js/setup.js index 43d6edb..b0cfba0 100644 --- a/web/js/setup.js +++ b/web/js/setup.js @@ -142,7 +142,7 @@ function toggleNav() { if (isMobile) { // Do nothing to .admin-wrapper on mobile (since we're overlaying) $(".admin-wrapper").css({ - 'margin-left': '0', + 'margin-left': '32px', 'width': '100%' // Reset content to full width on close }); $("#navigation").css('margin-left', 'calc(64px - 100vw)'); @@ -216,6 +216,7 @@ async function loadConsoleLogs() { const logColors = { INFO: "lime", DEBUG: "cyan", + CHAT: "cyan", WARN: "yellow", ERROR: "red" };