const fs = require('fs'); const https = require('https'); const seedrandom = require('seedrandom'); const express = require('express'); const app = express(); const options = { key: fs.readFileSync("ssl/private.key"), cert: fs.readFileSync("ssl/certificate.crt"), ca: [fs.readFileSync('ssl/ca_bundle.crt')] }; const server = https.createServer(options, app); const io = require('socket.io')(server); var port = 443; var admins = {} fs.readFile("config/admins.json", "utf8", (err, data) => { if (!err && data) { admins = JSON.parse(data); } }) var users = []; var bannedIps = []; fs.readFile("config/ban.json", "utf8", (err, data) => { if (!err && data) { bannedIps = JSON.parse(data); } }) var randomColors = [ '#c42020', '#de801e', '#f6c60b', '#19ce19', '#129696', '#2c53e3', '#6f23e3', '#883f88', ]; var historySize = 50; var history = []; fs.readFile("config/config.json", "utf8", (err, data) => { if (!err) { const config = JSON.parse(data); if (config.port) port = config.port; if (config.historySize) historySize = config.historySize; if (config.randomColors) randomColors = config.randomColors; } }) app.use(express.static(__dirname + '/www')); app.use('/node_modules', express.static(__dirname + '/node_modules')); app.get('/img/ava/:username', (req, res) => { let username = req.params.username; //.toLowerCase(); username = username.normalize('NFD'); username = username.replace(/[\u0300-\u036f]/g, ''); username = username.replace(/ß/g, 'ss'); username = username.replace(/[^\x00-\x7F]/g, ''); const filePath = __dirname + '/www/img/ava/' + username + '.png'; var file = ''; if (fs.existsSync(filePath)) { file = filePath; } else { const files = fs.readdirSync(__dirname + '/www/img/ava/random/').filter((f) => f.toLowerCase().endsWith('.png')); const seededRandom = seedrandom(username)(); const randomFile = files[Math.floor(seededRandom * files.length)]; file = __dirname + '/www/img/ava/random/' + randomFile; } res.sendFile(file); }); io.on('connection', (socket) => { const ip = socket.handshake.address; log('- New user joined the server:', ip); for (const bannedItem of bannedIps) { if (bannedItem.ip == ip) { log('- User blacklisted, kicking:', ip) socket.emit('serverKick'); socket.disconnect(); break; } } socket.emit('serverHandshake'); const user = {ip: ip, socket: socket}; users.push(user); socket.on('login', (username, password) => { if (!username) { socket.emit('usernameInvalid'); return; } for (let user of users) { if (user.name == username) { socket.emit('usernameTaken'); return; } } if (admins && admins[username] && admins[username].password) { if (!password) { log('- Attempted login as admin without password.', username, '(' + ip + ')'); socket.emit('passwordRequired'); return; } else if (admins[username].password != password) { log('- Attempted login as admin with wrong password.', username, '(' + ip + ')'); socket.emit('passwordWrong'); return; } log('- Admin "' + username + '" login successful'); user.admin = true; if (admins[username].color) { user.color = admins[username].color; } socket.on('requestKick', (userToBeKicked) => { log('- Admin "' + username + '" requested kick of User "' + userToBeKicked.name + '"'); kickUser(userToBeKicked); }); socket.on('requestLiftBan', (ip) => { log('- Admin "' + username + '" requested to lift ban for ip "' + ip + '"'); liftBan(ip); }); } log('- New user logged in:', username, '(' + ip + ')'); user.name = username; if (!user.color) { user.color = getRandomColor(username); } socket.emit('serverLogin', getCleanUser(user), history); io.emit('userJoined', username); updateUsers(); updateBanned(); socket.on('disconnect', () => { log('- User joined the server:', ip); users = users.filter((listUser) => listUser !== user); io.emit('userLeft', username); updateUsers(); }); socket.on('message', (msg) => { if (msg) { if (!user.admin) { const regex = /( |<([^>]+)>)/ig; msg = msg.replace(regex, ""); } msg = msg.replace(/(? { try { const urlObj = new URL(url); const urlWithoutParams = urlObj.origin + urlObj.pathname; if (urlWithoutParams.toLowerCase().endsWith('.jpg') || urlWithoutParams.toLowerCase().endsWith('.gif') || urlWithoutParams.toLowerCase().endsWith('.png')) { return ''; } } catch (e) {} var faviconUrl; try { faviconUrl = new URL(url).origin + '/favicon.ico'; } catch (e) { log('Error getting favicon for "' + url + '"'); } return '' + url + ''; }); for (const u of users) { while (msg.indexOf('@' + u.name) > -1) { msg = msg.replace('@' + u.name, '' + u.name + ''); } } log(user.name + ':', msg, '(' + user.ip + ')'); if (history.length >= historySize) { history = history.slice(1); } history.push({msg: msg, user: getCleanUser(user), timestamp: Date.now()}); io.emit('message', msg, getCleanUser(user), Date.now()); } }); }); }); function updateUsers() { io.emit('usersUpdated', users.filter((user) => user.name != null).map(getCleanUser)); } function getCleanUser(user) { return { name: user.name, admin: user.admin, color: user.color } } function kickUser(user) { let ip; for (const exUser of users) { if (exUser.name == user.name) { ip = exUser.ip; break; } } bannedIps.push({ip: ip, user: user}); updateBanned(); for (const exUser of users) { if (exUser.ip == ip) { exUser.socket.emit('serverKick'); exUser.socket.disconnect(); } } } function liftBan(ip) { bannedIps = bannedIps.filter((listItem) => listItem.ip != ip); updateBanned(); } function updateBanned() { fs.writeFile('config/ban.json', JSON.stringify(bannedIps), (e) => { }); io.emit('bannedUpdated', bannedIps); } function getRandomColor(seed) { return randomColors[Math.floor(seedrandom(seed)() * randomColors.length)]; } function log(...inputs) { var output = '[' + new Date().toISOString().substr(0, 19).replace('T', ', ') + '] '; for (const i of inputs) { output += i + ' '; } console.log(output); fs.appendFile('log.txt', '' + output + '\n', (e) => { }); } server.listen(port, () => { log('- Server up and running at port ' + port); });