mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-07-29 16:29:19 +02:00
some changes
This commit is contained in:
+15
-66
@@ -6,8 +6,6 @@ const { SerialPort } = require('serialport')
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
|
||||
// File Imports
|
||||
const parseAudioDevice = require('./stream/parser');
|
||||
const { configName, serverConfig, configUpdate, configSave, configExists, configPath } = require('./server_config');
|
||||
const helpers = require('./helpers');
|
||||
const storage = require('./storage');
|
||||
@@ -70,7 +68,11 @@ router.get('/403', (req, res) => {
|
||||
router.get('/audioonly', (req, res) => res.render('audioonly'))
|
||||
|
||||
router.get('/setup', (req, res) => {
|
||||
let serialPorts;
|
||||
if(!helpers.isAdmin(req)) {
|
||||
res.render('login');
|
||||
return;
|
||||
}
|
||||
|
||||
function loadConfig() {
|
||||
if (fs.existsSync(configPath)) {
|
||||
const configFileContents = fs.readFileSync(configPath, 'utf8');
|
||||
@@ -79,45 +81,17 @@ router.get('/setup', (req, res) => {
|
||||
return serverConfig;
|
||||
}
|
||||
|
||||
if(!helpers.isAdmin(req)) {
|
||||
res.render('login');
|
||||
return;
|
||||
}
|
||||
const processUptimeInSeconds = Math.floor(process.uptime());
|
||||
const formattedProcessUptime = helpers.formatUptime(processUptimeInSeconds);
|
||||
|
||||
SerialPort.list().then((deviceList) => {
|
||||
serialPorts = deviceList.map(port => ({
|
||||
path: port.path,
|
||||
friendlyName: port.friendlyName,
|
||||
}));
|
||||
|
||||
parseAudioDevice((result) => {
|
||||
const processUptimeInSeconds = Math.floor(process.uptime());
|
||||
const formattedProcessUptime = helpers.formatUptime(processUptimeInSeconds);
|
||||
|
||||
const updatedConfig = loadConfig(); // Reload the config every time
|
||||
res.render('setup', {
|
||||
isAdminAuthenticated: helpers.isAdmin(req),
|
||||
videoDevices: result.audioDevices,
|
||||
audioDevices: result.videoDevices,
|
||||
serialPorts: serialPorts,
|
||||
memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
|
||||
memoryHeap: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(1) + ' MB',
|
||||
processUptime: formattedProcessUptime,
|
||||
consoleOutput: logs,
|
||||
plugins: require('./plugins'),
|
||||
enabledPlugins: updatedConfig.plugins,
|
||||
onlineUsers: dataHandler.dataToSend.users,
|
||||
connectedUsers: storage.connectedUsers,
|
||||
device: serverConfig.device,
|
||||
banlist: updatedConfig.webserver.banlist, // Updated banlist from the latest config
|
||||
tunerProfiles: tunerProfiles.map((profile) => ({
|
||||
id: profile.id,
|
||||
label: profile.label,
|
||||
detailsHtml: helpers.parseMarkdown(profile.details || '')
|
||||
}))
|
||||
});
|
||||
});
|
||||
})
|
||||
const updatedConfig = loadConfig(); // Reload the config every time
|
||||
res.render('setup', {
|
||||
isAdminAuthenticated: true,
|
||||
memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
|
||||
memoryHeap: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(1) + ' MB',
|
||||
processUptime: formattedProcessUptime,
|
||||
consoleOutput: logs,
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/api', (req, res) => {
|
||||
@@ -254,11 +228,6 @@ router.get('/getData', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/getDevices', (req, res) => {
|
||||
if (helpers.isAdmin(req) || !fs.existsSync(configName + '.json')) parseAudioDevice(res.json);
|
||||
else res.status(403).json({ error: 'Unauthorized' });
|
||||
});
|
||||
|
||||
/* Static data are being sent through here on connection - these don't change when the server is running */
|
||||
router.get('/static_data', (req, res) => {
|
||||
res.json({
|
||||
@@ -382,24 +351,4 @@ router.get('/log_fmlist', (req, res) => {
|
||||
request.end();
|
||||
});
|
||||
|
||||
router.get('/tunnelservers', async (req, res) => {
|
||||
const servers = [
|
||||
{ value: "eu", host: "eu.fmtuner.org", label: "Europe" },
|
||||
{ value: "us", host: "us.fmtuner.org", label: "Americas" },
|
||||
{ value: "sg", host: "sg.fmtuner.org", label: "Asia & Oceania" }
|
||||
];
|
||||
|
||||
const results = await Promise.all(
|
||||
servers.map(async s => {
|
||||
const latency = await helpers.checkLatency(s.host);
|
||||
return {
|
||||
value: s.value,
|
||||
label: `${s.label} (${latency ? latency + ' ms' : 'offline'})` // From my tests, the latency via HTTP ping is roughly 2x higher than regular ping
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
res.json(results);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
+1
-5
@@ -1,7 +1,3 @@
|
||||
const tunnel = require('./tunnel');
|
||||
tunnel.download();
|
||||
|
||||
require('./stream/index');
|
||||
require("./device");
|
||||
|
||||
const { serverConfig } = require('./server_config');
|
||||
@@ -12,7 +8,7 @@ const { serverConfig } = require('./server_config');
|
||||
if (plugins.length > 0) setTimeout(helpers.startPluginsWithDelay, 3000, plugins, 3000);
|
||||
}
|
||||
|
||||
require('./stream/index');
|
||||
require("./web")(serverConfig.webserver.webserverIp);
|
||||
|
||||
tunnel.connect();
|
||||
require('./server_list')();
|
||||
+24
-47
@@ -1,52 +1,29 @@
|
||||
var countries = [
|
||||
"Albania",
|
||||
"Estonia",
|
||||
"Algeria",
|
||||
"Ethiopia",
|
||||
"Andorra",
|
||||
"Angola",
|
||||
"Finland",
|
||||
"Armenia",
|
||||
"France",
|
||||
"Ascension Island",
|
||||
"Gabon",
|
||||
"Austria",
|
||||
"Gambia",
|
||||
"Azerbaijan",
|
||||
"Georgia",
|
||||
"Germany",
|
||||
"Bahrein",
|
||||
"Ghana",
|
||||
"Belarus",
|
||||
"Gibraltar",
|
||||
"Belgium",
|
||||
"Greece",
|
||||
"Benin",
|
||||
"Guinea",
|
||||
"Bosnia Herzegovina",
|
||||
"Guinea-Bissau",
|
||||
"Botswana",
|
||||
"Hungary",
|
||||
"Bulgaria",
|
||||
"Iceland",
|
||||
"Burkina Faso",
|
||||
"Iraq",
|
||||
"Burundi",
|
||||
"Ireland",
|
||||
"Cabinda",
|
||||
"Israel",
|
||||
"Cameroon",
|
||||
"Italy",
|
||||
"Jordan",
|
||||
"Cape Verde",
|
||||
"Albania", "Estonia",
|
||||
"Algeria", "Ethiopia",
|
||||
"Andorra", "Angola",
|
||||
"Finland", "Armenia",
|
||||
"France", "Ascension Island",
|
||||
"Gabon", "Austria",
|
||||
"Gambia", "Azerbaijan",
|
||||
"Georgia", "Germany",
|
||||
"Bahrein", "Ghana",
|
||||
"Belarus", "Gibraltar",
|
||||
"Belgium", "Greece",
|
||||
"Benin", "Guinea",
|
||||
"Bosnia Herzegovina", "Guinea-Bissau",
|
||||
"Botswana", "Hungary",
|
||||
"Bulgaria", "Iceland",
|
||||
"Burkina Faso", "Iraq",
|
||||
"Burundi", "Ireland",
|
||||
"Cabinda", "-",
|
||||
"Cameroon", "Italy",
|
||||
"Jordan", "Cape Verde",
|
||||
"Kazakhstan",
|
||||
"Central African Republic",
|
||||
"Kenya",
|
||||
"Chad",
|
||||
"Kosovo",
|
||||
"Comoros",
|
||||
"Kuwait",
|
||||
"DR Congo",
|
||||
"Kenya", "Chad",
|
||||
"Kosovo", "Comoros",
|
||||
"Kuwait", "DR Congo",
|
||||
"Kyrgyzstan",
|
||||
"Republic of Congo",
|
||||
"Latvia",
|
||||
@@ -402,7 +379,7 @@ const rdsEccD0D4Lut = [
|
||||
const rdsEccE0E5Lut = [
|
||||
// E0
|
||||
[
|
||||
"Germany", "Algeria", "Andorra", "Israel", "Italy",
|
||||
"Germany", "Algeria", "Andorra", "-", "Italy",
|
||||
"Belgium", "Russia", "Palestine", "Albania", "Austria",
|
||||
"Hungary", "Malta", "Germany", "", "Egypt"
|
||||
],
|
||||
|
||||
@@ -89,20 +89,6 @@ let serverConfig = {
|
||||
si47xx: {
|
||||
agcControl: false
|
||||
},
|
||||
tunnel: {
|
||||
enabled: false,
|
||||
username: "",
|
||||
token: "",
|
||||
region: "eu",
|
||||
lowLatencyMode: false,
|
||||
subdomain: "",
|
||||
httpName: "",
|
||||
httpPassword: "",
|
||||
community: {
|
||||
enabled: false,
|
||||
host: ""
|
||||
}
|
||||
},
|
||||
plugins: [],
|
||||
device: 'tef',
|
||||
defaultFreq: 87.5,
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
module.exports = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const checkFFmpegProcess = spawn('ffmpeg', ['-version'], {
|
||||
stdio: ['ignore', 'ignore', 'ignore'],
|
||||
});
|
||||
|
||||
checkFFmpegProcess.on('error', () => {
|
||||
resolve(require('ffmpeg-static'));
|
||||
});
|
||||
|
||||
checkFFmpegProcess.on('exit', (code) => {
|
||||
if (code === 0) resolve('ffmpeg');
|
||||
else resolve(require('ffmpeg-static'));
|
||||
});
|
||||
});
|
||||
}
|
||||
+18
-6
@@ -7,7 +7,23 @@ if (!configExists() || !serverConfig.audio.audioDevice) return;
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const { logDebug, logError, logInfo, logWarn } = require('../console');
|
||||
const checkFFmpeg = require('./checkFFmpeg');
|
||||
|
||||
const checkFFmpeg = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const checkFFmpegProcess = spawn('ffmpeg', ['-version'], {
|
||||
stdio: ['ignore', 'ignore', 'ignore'],
|
||||
});
|
||||
|
||||
checkFFmpegProcess.on('error', () => {
|
||||
resolve(require('ffmpeg-static'));
|
||||
});
|
||||
|
||||
checkFFmpegProcess.on('exit', (code) => {
|
||||
if (code === 0) resolve('ffmpeg');
|
||||
else resolve(require('ffmpeg-static'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const consoleLogTitle = '[Audio Stream]';
|
||||
|
||||
@@ -24,8 +40,7 @@ checkFFmpeg().then((ffmpegPath) => {
|
||||
logInfo(`${consoleLogTitle} Using ${ffmpegPath === 'ffmpeg' ? 'system-installed FFmpeg' : 'ffmpeg-static'}`);
|
||||
logInfo(`${consoleLogTitle} Starting audio stream on device: \x1b[35m${serverConfig.audio.audioDevice}\x1b[0m`);
|
||||
|
||||
const sampleRate = Number(serverConfig.audio.sampleRate || 44100); // Maybe even do 32 khz, we do not need higher than 15 khz precision
|
||||
|
||||
const sampleRate = Number(serverConfig.audio.sampleRate || 32000);
|
||||
const channels = Number(serverConfig.audio.audioChannels || 2);
|
||||
|
||||
let ffmpeg = null;
|
||||
@@ -80,8 +95,6 @@ checkFFmpeg().then((ffmpegPath) => {
|
||||
|
||||
ffmpeg.stdout.pipe(audio_pipe, { end: false });
|
||||
|
||||
connectMessage(`${consoleLogTitle} Connected FFmpeg → MP3 → audioWss`);
|
||||
|
||||
ffmpeg.stderr.on('data', (data) => {
|
||||
const msg = data.toString();
|
||||
|
||||
@@ -136,7 +149,6 @@ checkFFmpeg().then((ffmpegPath) => {
|
||||
});
|
||||
|
||||
launchFFmpeg();
|
||||
|
||||
}).catch((err) => {
|
||||
logError(`${consoleLogTitle} Error: ${err.message}`);
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const exec = require('child_process').exec;
|
||||
const fs = require('fs').promises; // Use the Promise-based fs API
|
||||
const ffmpeg = require('ffmpeg-static');
|
||||
const filePath = '/proc/asound/cards';
|
||||
const platform = process.platform;
|
||||
|
||||
function parseAudioDevice(options, callback) {
|
||||
let videoDevices = [];
|
||||
let audioDevices = [];
|
||||
let isVideo = true;
|
||||
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
options = options || {};
|
||||
const ffmpegPath = `"${ffmpeg.replace(/\\/g, '\\\\')}"`;
|
||||
const callbackExists = typeof callback === 'function';
|
||||
|
||||
const execute = async (fulfill, reject) => {
|
||||
try {
|
||||
if (platform === 'linux') {
|
||||
try {
|
||||
const data = await fs.readFile(filePath, 'utf8');
|
||||
const regex = /\[([^\]]+)\]/g;
|
||||
const matches = (data.match(regex) || []).map(match => 'hw:' + match.replace(/\s+/g, '').slice(1, -1));
|
||||
|
||||
matches.forEach(match => {
|
||||
if (typeof match === 'string') audioDevices.push({ name: match });
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Error reading file: ${err.message}`);
|
||||
}
|
||||
|
||||
// Linux doesn't support the `-list_devices` ffmpeg command like macOS/Windows,
|
||||
// so skip the ffmpeg exec for Linux
|
||||
const result = { videoDevices: [], audioDevices };
|
||||
if (callbackExists) return callback(result);
|
||||
return fulfill(result);
|
||||
}
|
||||
|
||||
let inputDevice, prefix, audioSeparator, alternativeName, deviceParams;
|
||||
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
inputDevice = 'dshow';
|
||||
prefix = /\[dshow/;
|
||||
audioSeparator = /DirectShow\saudio\sdevices/;
|
||||
alternativeName = /Alternative\sname\s*?"(.*?)"/;
|
||||
deviceParams = /"(.*?)"/;
|
||||
break;
|
||||
case 'darwin':
|
||||
inputDevice = 'avfoundation';
|
||||
prefix = /^\[AVFoundation/;
|
||||
audioSeparator = /AVFoundation\saudio\sdevices/;
|
||||
deviceParams = /^\[AVFoundation.*?]\s\[(\d+)]\s(.*)$/;
|
||||
break;
|
||||
}
|
||||
|
||||
exec(`${ffmpegPath} -f ${inputDevice} -list_devices true -i ""`, (err, stdout, stderr) => {
|
||||
stderr.split("\n")
|
||||
.filter(line => line.search(prefix) > -1)
|
||||
.forEach(line => {
|
||||
const deviceList = isVideo ? videoDevices : audioDevices;
|
||||
if (line.search(audioSeparator) > -1) {
|
||||
isVideo = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (platform === 'win32' && line.search(/Alternative\sname/) > -1) {
|
||||
const lastDevice = deviceList[deviceList.length - 1];
|
||||
const alt = line.match(alternativeName);
|
||||
if (lastDevice && alt) lastDevice.alternativeName = alt[1];
|
||||
return;
|
||||
}
|
||||
|
||||
const params = line.match(deviceParams);
|
||||
if (params) {
|
||||
let device;
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
device = { name: params[1] };
|
||||
break;
|
||||
case 'darwin':
|
||||
device = { id: parseInt(params[1]), name: params[2] };
|
||||
break;
|
||||
}
|
||||
deviceList.push(device);
|
||||
}
|
||||
});
|
||||
|
||||
audioDevices = audioDevices.filter(device => device.name !== undefined);
|
||||
const result = { videoDevices, audioDevices };
|
||||
if (callbackExists) return callback(result);
|
||||
return fulfill(result);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Unexpected error:', err);
|
||||
if (callbackExists) callback({ videoDevices: [], audioDevices: [] });
|
||||
else reject(err);
|
||||
}
|
||||
};
|
||||
|
||||
if (callbackExists) execute();
|
||||
else return new Promise(execute);
|
||||
}
|
||||
|
||||
module.exports = parseAudioDevice;
|
||||
@@ -1,101 +0,0 @@
|
||||
const { logDebug, logError, logInfo, logWarn } = require('./console');
|
||||
const { serverConfig } = require('./server_config');
|
||||
const { Readable } = require('stream');
|
||||
const { finished } = require('stream/promises');
|
||||
const fs = require('fs/promises');
|
||||
const fs2 = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const ejs = require('ejs');
|
||||
const { spawn } = require('child_process');
|
||||
const readline = require('readline');
|
||||
|
||||
const fileExists = path => new Promise(resolve => fs.access(path, fs.constants.F_OK).then(() => resolve(true)).catch(() => resolve(false)));
|
||||
|
||||
async function download() {
|
||||
if (serverConfig.tunnel?.enabled === true) {
|
||||
const librariesDir = path.resolve(__dirname, '../libraries');
|
||||
if (!await fileExists(librariesDir)) await fs.mkdir(librariesDir);
|
||||
const frpcPath = path.resolve(librariesDir, 'frpc' + (os.platform() === 'win32' ? '.exe' : ''));
|
||||
if (!await fileExists(frpcPath)) {
|
||||
logInfo('frpc binary, required for tunnel is not available. Downloading now...');
|
||||
const frpcFileName = `frpc_${os.platform}_${os.arch}` + (os.platform() === 'win32' ? '.exe' : '');
|
||||
|
||||
try {
|
||||
const res = await fetch('https://fmtuner.org/binaries/' + frpcFileName);
|
||||
if (res.status === 404) throw new Error('404 error');
|
||||
const stream = fs2.createWriteStream(frpcPath);
|
||||
await finished(Readable.fromWeb(res.body).pipe(stream));
|
||||
} catch (err) {
|
||||
logError('Failed to download frpc, reason: ' + err);
|
||||
return;
|
||||
}
|
||||
logInfo('Downloading of frpc is completed.')
|
||||
if (os.platform() === 'linux' || os.platform() === 'darwin') await fs.chmod(frpcPath, 0o770);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function connect() {
|
||||
if (serverConfig.tunnel?.enabled === true) {
|
||||
const librariesDir = path.resolve(__dirname, '../libraries');
|
||||
const frpcPath = path.resolve(librariesDir, 'frpc' + (os.platform() === 'win32' ? '.exe' : ''));
|
||||
|
||||
const cfg = ejs.render(frpcConfigTemplate, {
|
||||
cfg: serverConfig.tunnel,
|
||||
host: serverConfig.tunnel.community.enabled ? serverConfig.tunnel.community.host : (serverConfig.tunnel.region + ".fmtuner.org"),
|
||||
server: {
|
||||
port: serverConfig.webserver.webserverPort
|
||||
}
|
||||
});
|
||||
const cfgPath = path.resolve(librariesDir, 'frpc.toml');
|
||||
await fs.writeFile(cfgPath, cfg);
|
||||
const child = spawn(frpcPath, ['-c', cfgPath]);
|
||||
process.on('exit', () => child.kill());
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: child.stdout,
|
||||
terminal: false
|
||||
});
|
||||
|
||||
rl.on('line', (line) => {
|
||||
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);
|
||||
});
|
||||
|
||||
child.on('error', (err) => {
|
||||
logError('Failed to start tunnel process:', err);
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
logInfo(`Tunnel process exited with code ${code}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const frpcConfigTemplate = `
|
||||
serverAddr = "<%= host %>"
|
||||
serverPort = 7000
|
||||
loginFailExit = false
|
||||
log.disablePrintColor = true
|
||||
user = "<%= cfg.username %>"
|
||||
metadatas.token = "<%= cfg.token %>"
|
||||
<% if (cfg.lowLatencyMode) { %>
|
||||
transport.protocol = "kcp"
|
||||
<% } %>
|
||||
|
||||
[[proxies]]
|
||||
name = "web"
|
||||
type = "http"
|
||||
localPort = <%= server.port %>
|
||||
subdomain = "<%= cfg.subdomain %>"
|
||||
<% if (cfg.httpName != "") { %>
|
||||
httpUser = "<%= cfg.httpName %>"
|
||||
httpPassword = "<%= cfg.httpPassword %>"
|
||||
<% } %>
|
||||
`;
|
||||
|
||||
module.exports = { connect, download };
|
||||
@@ -83,7 +83,6 @@ wss.on('connection', (ws, request) => {
|
||||
}
|
||||
|
||||
if(currentUsers >= MAX_USERS) {
|
||||
// TODO: give a nice fucking unwelcome message on the ui (we kindly ask you to FUCK OFF?)
|
||||
ws.send("a0"); // This means not authenticated in XDR
|
||||
ws.close(1008, 'Too many users using this server at the moment.');
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user