From 13ae21fcbe8190c49312b69959afbe652bcdaa36 Mon Sep 17 00:00:00 2001 From: Hri7566 Date: Mon, 12 Apr 2021 17:29:02 -0400 Subject: [PATCH] major update --- .gitignore | 1 + config.js | 6 +- index.js | 20 ++++-- nodemon.json | 5 ++ src/Client.js | 37 +++++++--- src/Database.js | 58 ++++++++++++++++ src/Message.js | 117 +++++++++++++++++++++---------- src/Room.js | 133 ++++++++++++++++++++++------------- src/RoomSettings.js | 166 ++++++++++++++++++++++++++++++++++++++++++++ src/Server.js | 25 +++++-- src/User.js | 65 +++++------------ 11 files changed, 476 insertions(+), 157 deletions(-) create mode 100644 nodemon.json create mode 100644 src/Database.js create mode 100644 src/RoomSettings.js diff --git a/.gitignore b/.gitignore index 03d43d5..a42f0a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .env node_modules ssl/ +src/db/users.json diff --git a/config.js b/config.js index b0a108a..dd0b122 100644 --- a/config.js +++ b/config.js @@ -3,10 +3,10 @@ module.exports = Object.seal({ motd: "big th0nk", _id_PrivateKey: process.env.SALT, defaultUsername: "Anonymous", - //defaultRoomColor: "#3b5054", - defaultRoomColor: "#9900ff", + // defaultRoomColor: "#3b5054", + defaultRoomColor: "#000000", // defaultLobbyColor: "#19b4b9", - defaultLobbyColor: "#5e32a8", + defaultLobbyColor: "#9900ff", // defaultLobbyColor2: "#801014", defaultLobbyColor2: "#801014", adminpass: process.env.ADMINPASS, diff --git a/index.js b/index.js index 2363750..3b6db41 100644 --- a/index.js +++ b/index.js @@ -1,26 +1,31 @@ -// dotenv to keep secret variables in (they won't show on github) +// dotenv require('dotenv').config(); -//call new Server +// call new Server global.WebSocket = require('ws'); global.EventEmitter = require('events').EventEmitter; global.fs = require('fs'); -global.createKeccakHash = require('keccak'); -const AsyncConsole = require('asyncconsole') +const AsyncConsole = require('asyncconsole'); -global.isString = function(a){ +global.isString = function(a) { return typeof a === 'string'; } -global.isBool = function(a){ + +global.isBool = function(a) { return typeof a === 'boolean'; } -global.isObj = function(a){ + +global.isObj = function(a) { return typeof a === "object" && !Array.isArray(a) && a !== null; } let Server = require("./src/Server.js"); let config = require('./config'); global.SERVER = new Server(config); + +// below commented because it doesn't work with pm2 + +/* let console = process.platform == 'win32' ? new AsyncConsole("", input => { try { console.log(JSON.stringify(eval(input))); @@ -28,3 +33,4 @@ let console = process.platform == 'win32' ? new AsyncConsole("", input => { console.log(e.toString()); } }) : {}; +*/ diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..8428115 --- /dev/null +++ b/nodemon.json @@ -0,0 +1,5 @@ +{ + "ignore": [ + "**/*.json" + ] +} \ No newline at end of file diff --git a/src/Client.js b/src/Client.js index 377dcaa..754087f 100644 --- a/src/Client.js +++ b/src/Client.js @@ -3,42 +3,55 @@ const Quota = require ("./Quota.js"); const quotas = require('../Quotas'); const RateLimit = require('./Ratelimit.js').RateLimit; const RateLimitChain = require('./Ratelimit.js').RateLimitChain; +const User = require("./User.js"); +const Database = require("./Database.js"); require('node-json-color-stringify'); + class Client extends EventEmitter { constructor(ws, req, server) { super(); EventEmitter.call(this); - this.user; this.connectionid = server.connectionid; this.server = server; this.participantId; this.channel; + this.staticQuotas = { room: new RateLimit(quotas.room.time) }; + this.quotas = {}; this.ws = ws; this.req = req; this.ip = (req.connection.remoteAddress).replace("::ffff:", ""); - this.destroied = false; - this.bindEventListeners(); - require('./Message.js')(this); + + Database.getUserData(this, server).then(data => { + this.user = new User(this, data); + this.destroied = false; + this.bindEventListeners(); + require('./Message.js')(this); + }); } + isConnected() { return this.ws && this.ws.readyState === WebSocket.OPEN; } + isConnecting() { return this.ws && this.ws.readyState === WebSocket.CONNECTING; } + setChannel(_id, settings) { if (this.channel && this.channel._id == _id) return; if (this.server.rooms.get(_id)) { let room = this.server.rooms.get(_id, settings); let userbanned = room.bans.get(this.user._id); + if (userbanned && (Date.now() - userbanned.bannedtime >= userbanned.msbanned)) { room.bans.delete(userbanned.user._id); userbanned = undefined; } + if (userbanned) { room.Notification(this.user._id, "Notice", @@ -51,9 +64,11 @@ class Client extends EventEmitter { this.setChannel("test/awkward", settings); return; } + let channel = this.channel; if (channel) this.channel.emit("bye", this); - if (channel) this.channel.updateCh(); + if (channel) this.channel.updateCh(this); + this.channel = this.server.rooms.get(_id); this.channel.join(this); } else { @@ -61,7 +76,7 @@ class Client extends EventEmitter { this.server.rooms.set(_id, room); if (this.channel) this.channel.emit("bye", this); this.channel = this.server.rooms.get(_id); - this.channel.join(this); + this.channel.join(this, settings); } } sendArray(arr) { @@ -70,6 +85,7 @@ class Client extends EventEmitter { this.ws.send(JSON.stringify(arr)); } } + initParticipantQuotas() { this.quotas = { //"chat": new Quota(Quota.PARAMS_A_NORMAL), @@ -88,6 +104,7 @@ class Client extends EventEmitter { "-ls": new Quota(Quota.PARAMS_USED_A_LOT) } } + destroy() { this.ws.close(); if (this.channel) { @@ -102,6 +119,7 @@ class Client extends EventEmitter { this.destroied = true; console.log(`Removed Connection ${this.connectionid}.`); } + bindEventListeners() { this.ws.on("message", (evt, admin) => { try { @@ -120,14 +138,17 @@ class Client extends EventEmitter { } }); this.ws.on("close", () => { - if (!this.destroied) + if (!this.destroied) { this.destroy(); + } }); this.ws.addEventListener("error", (err) => { console.error(err); - if (!this.destroied) + if (!this.destroied) { this.destroy(); + } }); } } + module.exports = Client; diff --git a/src/Database.js b/src/Database.js new file mode 100644 index 0000000..97474eb --- /dev/null +++ b/src/Database.js @@ -0,0 +1,58 @@ +const fs = require('fs'); +const { promisify } = require('util'); +const createKeccakHash = require('keccak'); +const ColorEncoder = require('./ColorEncoder'); + +class Database { + static userdb; + + static async load() { + const writeFile = promisify(fs.writeFile); + const readdir = promisify(fs.readdir); + + let files = await readdir("src/db/"); + if (!files.includes("users.json")) { + await writeFile('src/db/users.json', JSON.stringify(this.default_db, null, 2)) + this.userdb = new Map(Object.entries(require("./db/users.json"))); + } else { + this.userdb = new Map(Object.entries(require("./db/users.json"))); + } + } + + static async getUserData(cl, server) { + if (!this.userdb || (this.userdb instanceof Map && [...this.userdb.entries()] == [])) { + await this.load(); + } + + let _id = createKeccakHash('keccak256').update((cl.server._id_Private_Key + cl.ip)).digest('hex').substr(0, 24); + let usertofind = this.userdb.get(_id); + + if (!usertofind) { + if (typeof usertofind == 'object' && (usertofind.hasOwnProperty('name') && usertofind.name != this.server.defaultUsername)) return; + + this.userdb.set(_id, { + "color": `#${ColorEncoder.intToRGB(ColorEncoder.hashCode(_id)).toLowerCase()}`, + "name": server.defaultUsername, + "_id": _id, + "ip": cl.ip + }); + + this.update(); + } + + let user = this.userdb.get(_id); + + return user; + } + + static async update() { + const writeFile = promisify(fs.writeFile); + await writeFile('src/db/users.json', JSON.stringify(this.strMapToObj(this.userdb), null, 2)); + } + + static strMapToObj(strMap) { + return [...strMap.entries()].reduce((obj, [key, value]) => (obj[key] = value, obj), {}); + } +} + +module.exports = Database; diff --git a/src/Message.js b/src/Message.js index f687444..eefc277 100644 --- a/src/Message.js +++ b/src/Message.js @@ -1,20 +1,27 @@ const Quota = require('./Quota'); const User = require("./User.js"); const Room = require("./Room.js"); +const RoomSettings = require('./RoomSettings'); + module.exports = (cl) => { - cl.once("hi", () => { - let user = new User(cl); - user.getUserData().then((data) => { - let msg = {}; - msg.m = "hi"; - msg.motd = cl.server.welcome_motd; - msg.t = Date.now(); - msg.u = data; - msg.v = "Beta"; - cl.sendArray([msg]) - cl.user = data; - }) - }) + cl.once("hi", msg => { + let m = {}; + + m.m = "hi"; + m.motd = cl.server.welcome_motd; + m.t = Date.now(); + m.u = { + name: cl.user.name, + _id: cl.user._id, + id: cl.participantId, + color: cl.user.color + }; + + m.v = "https://gitlab.com/hri7566/mpp-server"; + + cl.sendArray([m]); + }); + cl.on("t", msg => { if (msg.hasOwnProperty("e") && !isNaN(msg.e)) cl.sendArray([{ @@ -22,13 +29,17 @@ module.exports = (cl) => { t: Date.now(), e: msg.e }]) - }) + }); + cl.on("ch", msg => { - if (!msg.hasOwnProperty("set") || !msg.set) msg.set = {}; + if (typeof(msg.set) !== 'object') msg.set = {}; + if (msg.hasOwnProperty("_id") && typeof msg._id == "string") { if (msg._id.length > 512) return; if (!cl.staticQuotas.room.attempt()) return; + cl.setChannel(msg._id, msg.set); + let param; if (cl.channel.isLobby(cl.channel._id)) { param = Quota.N_PARAMS_LOBBY; @@ -39,10 +50,12 @@ module.exports = (cl) => { param = Quota.N_PARAMS_RIDICULOUS; } } + param.m = "nq"; - cl.sendArray([param]) + cl.sendArray([param]); } - }) + }); + cl.on("m", (msg, admin) => { // if (!cl.quotas.cursor.attempt() && !admin) return; if (!(cl.channel && cl.participantId)) return; @@ -50,43 +63,63 @@ module.exports = (cl) => { if (!msg.hasOwnProperty("y")) msg.y = null; if (parseInt(msg.x) == NaN) msg.x = null; if (parseInt(msg.y) == NaN) msg.y = null; - cl.channel.emit("m", cl, msg.x, msg.y) + cl.channel.emit("m", cl, msg.x, msg.y); + }); - }) cl.on("chown", (msg, admin) => { if (!cl.quotas.chown.attempt() && !admin) return; if (!(cl.channel && cl.participantId)) return; + //console.log((Date.now() - cl.channel.crown.time)) //console.log(!(cl.channel.crown.userId != cl.user._id), !((Date.now() - cl.channel.crown.time) > 15000)); + if (!(cl.channel.crown.userId == cl.user._id) && !((Date.now() - cl.channel.crown.time) > 15000)) return; + if (msg.hasOwnProperty("id")) { // console.log(cl.channel.crown) - if (cl.user._id == cl.channel.crown.userId || cl.channel.crowndropped) + if (!admin) { + if (cl.user._id == cl.channel.crown.userId || cl.channel.crowndropped) { + cl.channel.chown(msg.id); + if (msg.id == cl.user.id) { + param = Quota.N_PARAMS_RIDICULOUS; + param.m = "nq"; + cl.sendArray([param]) + } + } + } else { cl.channel.chown(msg.id); if (msg.id == cl.user.id) { param = Quota.N_PARAMS_RIDICULOUS; param.m = "nq"; cl.sendArray([param]) } + } } else { - if (cl.user._id == cl.channel.crown.userId || cl.channel.crowndropped) + if (!admin) { + if (cl.user._id == cl.channel.crown.userId || cl.channel.crowndropped) { + cl.channel.chown(); + param = Quota.N_PARAMS_NORMAL; + param.m = "nq"; + cl.sendArray([param]) + } + } else { cl.channel.chown(); param = Quota.N_PARAMS_NORMAL; param.m = "nq"; - cl.sendArray([param]) + cl.sendArray([param]); + } } - }) + }); + cl.on("chset", msg => { if (!(cl.channel && cl.participantId)) return; + if (!cl.channel.crown) return; if (!(cl.user._id == cl.channel.crown.userId)) return; - if (!msg.hasOwnProperty("set") || !msg.set) msg.set = cl.channel.verifySet(cl.channel._id,{}); - Object.keys(cl.channel.settings).forEach(key => { - if (cl.channel.settings[key] !== msg.set[key]) { - cl.channel.settings[key] = msg.set[key]; - } - }); + if (!msg.hasOwnProperty("set") || !msg.set) msg.set = new RoomSettings(cl.channel.settings, 'user'); + cl.channel.settings.changeSettings(msg.set); cl.channel.updateCh(); - }) + }); + cl.on("a", (msg, admin) => { if (!(cl.channel && cl.participantId)) return; if (!msg.hasOwnProperty('message')) return; @@ -102,7 +135,8 @@ module.exports = (cl) => { } cl.channel.emit('a', cl, msg); } - }) + }); + cl.on('n', msg => { if (!(cl.channel && cl.participantId)) return; if (!msg.hasOwnProperty('t') || !msg.hasOwnProperty('n')) return; @@ -114,7 +148,8 @@ module.exports = (cl) => { } else { cl.channel.playNote(cl, msg); } - }) + }); + cl.on('+ls', msg => { if (!(cl.channel && cl.participantId)) return; cl.server.roomlisteners.set(cl.connectionid, cl); @@ -131,11 +166,13 @@ module.exports = (cl) => { "c": true, "u": rooms }]) - }) + }); + cl.on('-ls', msg => { if (!(cl.channel && cl.participantId)) return; cl.server.roomlisteners.delete(cl.connectionid); - }) + }); + cl.on("userset", msg => { if (!(cl.channel && cl.participantId)) return; if (!msg.hasOwnProperty("set") || !msg.set) msg.set = {}; @@ -157,7 +194,8 @@ module.exports = (cl) => { }) } - }) + }); + cl.on('kickban', msg => { if (cl.channel.crown == null) return; if (!(cl.channel && cl.participantId)) return; @@ -169,17 +207,20 @@ module.exports = (cl) => { let ms = msg.ms || 3600000; cl.channel.kickban(_id, ms); } - }) + }); + cl.on("bye", msg => { cl.destroy(); - }) + }); + cl.on("admin message", msg => { if (!(cl.channel && cl.participantId)) return; if (!msg.hasOwnProperty('password') || !msg.hasOwnProperty('msg')) return; if (typeof msg.msg != 'object') return; if (msg.password !== cl.server.adminpass) return; cl.ws.emit("message", JSON.stringify([msg.msg]), true); - }) + }); + //admin only stuff cl.on('color', (msg, admin) => { if (!admin) return; diff --git a/src/Room.js b/src/Room.js index 8cfc1a0..62024ed 100644 --- a/src/Room.js +++ b/src/Room.js @@ -2,7 +2,11 @@ //room class //room deleter //databases in Map + +const createKeccakHash = require('keccak'); const Quota = require("./Quota.js"); +const RoomSettings = require('./RoomSettings.js'); + class Room extends EventEmitter { constructor(server, _id, settings) { super(); @@ -11,9 +15,7 @@ class Room extends EventEmitter { this.server = server; this.crown = null; this.crowndropped = false; - this.settings = this.verifySet(this._id, { - set: settings - }); + this.settings = settings; this.chatmsgs = []; this.ppl = new Map(); this.connections = []; @@ -21,14 +23,15 @@ class Room extends EventEmitter { this.server.rooms.set(_id, this); this.bans = new Map(); } - join(cl) { //this stuff is complicated + + join(cl, set) { //this stuff is complicated let otheruser = this.connections.find((a) => a.user._id == cl.user._id) if (!otheruser) { let participantId = createKeccakHash('keccak256').update((Math.random().toString() + cl.ip)).digest('hex').substr(0, 24); cl.user.id = participantId; cl.participantId = participantId; cl.initParticipantQuotas(); - if (((this.connections.length == 0 && Array.from(this.ppl.values()).length == 0) && !this.isLobby(this._id)) || this.crown && (this.crown.userId == cl.user._id)) { //user that created the room, give them the crown. + if (((this.connections.length == 0 && Array.from(this.ppl.values()).length == 0) && this.isLobby(this._id) == false) || this.crown && (this.crown.userId == cl.user._id)) { //user that created the room, give them the crown. //cl.quotas.a.setParams(Quota.PARAMS_A_CROWNED); this.crown = { participantId: cl.participantId, @@ -43,15 +46,29 @@ class Room extends EventEmitter { y: this.getCrownY() } } + this.crowndropped = false; - this.settings = {visible:true,color:this.server.defaultRoomColor,chat:true,crownsolo:false}; + this.settings = new RoomSettings(set, 'user'); } else { //cl.quotas.a.setParams(Quota.PARAMS_A_NORMAL); - this.settings = {visible:true,color:this.server.defaultRoomColor,chat:true,crownsolo:false,lobby:true}; + console.log(this.isLobby(this._id)); + + if (this.isLobby(this._id)) { + this.settings = new RoomSettings(this.server.lobbySettings, 'user'); + this.settings.visible = true; + this.settings.crownsolo = false; + this.settings.color = this.server.lobbySettings.color; + this.settings.color2 = this.server.lobbySettings.color2; + this.settings.lobby = true; + } else { + this.settings = new RoomSettings(set, 'user'); + } } + this.ppl.set(participantId, cl); this.connections.push(cl); + this.sendArray([{ color: this.ppl.get(cl.participantId).user.color, id: this.ppl.get(cl.participantId).participantId, @@ -79,6 +96,7 @@ class Room extends EventEmitter { } } + remove(p) { //this is complicated too let otheruser = this.connections.filter((a) => a.user._id == p.user._id); if (!(otheruser.length > 1)) { @@ -99,31 +117,40 @@ class Room extends EventEmitter { } } + updateCh(cl) { //update channel for all people in channel if (Array.from(this.ppl.values()).length <= 0) { setTimeout(() => { this.destroy(); }, 10000); } + this.connections.forEach((usr) => { this.server.connections.get(usr.connectionid).sendArray([this.fetchData(usr, cl)]) - }) + }); + this.server.updateRoom(this.fetchData()); } + updateParticipant(pid, options) { - let p = null; + let p; + Array.from(this.ppl).map(rpg => { - if(rpg[1].user._id == pid) p = rpg[1]; + if (rpg[1].user._id == pid) p = rpg[1]; }); - if (p == null) return; + + if (typeof(p) == 'undefined') return; + options.name ? p.user.name = options.name : {}; options._id ? p.user._id = options._id : {}; options.color ? p.user.color = options.color : {}; + this.connections.filter((ofo) => ofo.participantId == p.participantId).forEach((usr) => { options.name ? usr.user.name = options.name : {}; options._id ? usr.user._id = options._id : {}; options.color ? usr.user.color = options.color : {}; - }) + }); + this.sendArray([{ color: p.user.color, id: p.participantId, @@ -132,17 +159,20 @@ class Room extends EventEmitter { x: p.x || 200, y: p.y || 100, _id: p.user._id - }]) + }]); } + destroy() { //destroy room + if (this.ppl.size > 0) return; this._id; console.log(`Deleted room ${this._id}`); - this.settings = {}; + this.settings = undefined; this.ppl; this.connnections; this.chatmsgs; this.server.rooms.delete(this._id); } + sendArray(arr, not, onlythisparticipant) { this.connections.forEach((usr) => { if (!not || (usr.participantId != not.participantId && !onlythisparticipant) || (usr.connectionid != not.connectionid && onlythisparticipant)) { @@ -152,13 +182,16 @@ class Room extends EventEmitter { console.log(e); } } - }) + }); } + fetchData(usr, cl) { let chppl = []; + [...this.ppl.values()].forEach((a) => { chppl.push(a.user); - }) + }); + let data = { m: "ch", p: "ofo", @@ -170,6 +203,7 @@ class Room extends EventEmitter { }, ppl: chppl } + if (cl) { if (usr.connectionid == cl.connectionid) { data.p = cl.participantId; @@ -179,13 +213,16 @@ class Room extends EventEmitter { } else { delete data.p; } + if (data.ch.crown == null) { delete data.ch.crown; } else { } + return data; } + verifyColor(strColor) { var test2 = /^#[0-9A-F]{6}$/i.test(strColor); if (test2 == true) { @@ -194,6 +231,7 @@ class Room extends EventEmitter { return false; } } + isLobby(_id) { if (_id.startsWith("lobby")) { if (_id == "lobby") { @@ -221,12 +259,15 @@ class Room extends EventEmitter { } } + getCrownY() { - return 50 - 30; + return Math.floor(Math.random() * 10000) / 100; } + getCrownX() { - return 50; + return Math.floor(Math.random() * 10000) / 100; } + chown(id) { let prsn = this.ppl.get(id); if (prsn) { @@ -259,9 +300,11 @@ class Room extends EventEmitter { } this.crowndropped = true; } + this.updateCh(); } - setCords(p, x, y) { + + setCoords(p, x, y) { if (p.participantId && this.ppl.get(p.participantId)) { x ? this.ppl.get(p.participantId).x = x : {}; y ? this.ppl.get(p.participantId).y = y : {}; @@ -273,6 +316,7 @@ class Room extends EventEmitter { }], p, false); } } + chat(p, msg) { if (msg.message.length > 512) return; let filter = ["AMIGHTYWIND"]; @@ -309,6 +353,7 @@ class Room extends EventEmitter { this.chatmsgs.push(message); } } + playNote(cl, note) { this.sendArray([{ m: "n", @@ -317,6 +362,7 @@ class Room extends EventEmitter { t: note.t }], cl, true); } + kickban(_id, ms) { ms = parseInt(ms); if (ms >= (1000 * 60 * 60)) return; @@ -365,6 +411,7 @@ class Room extends EventEmitter { }) } + Notification(who, title, text, html, duration, target, klass, id) { let obj = { m: "notification", @@ -410,7 +457,7 @@ class Room extends EventEmitter { }) this.on("m", (participant, x, y) => { - this.setCords(participant, x, y); + this.setCoords(participant, x, y); }) this.on("a", (participant, msg) => { @@ -418,37 +465,27 @@ class Room extends EventEmitter { }) } - verifySet(_id,msg){ - if(!isObj(msg.set)) msg.set = {visible:true,color:this.server.defaultRoomColor,chat:true,crownsolo:false}; - if(isBool(msg.set.lobby)){ - if(!this.isLobby(_id)) delete msg.set.lobby; // keep it nice and clean - }else{ - if(this.isLobby(_id)) msg.set = {visible:true,color:this.server.defaultLobbyColor,color2:this.server.defaultLobbyColor2,chat:true,crownsolo:false,lobby:true}; + verifySet(_id, msg){ + if(typeof(msg.set) !== 'object') { + msg.set = { + visible: true, + color: this.server.defaultSettings.color, chat:true, + crownsolo:false + } } - if(!isBool(msg.set.visible)){ - if(msg.set.visible == undefined) msg.set.visible = (!isObj(this.settings) ? true : this.settings.visible); - else msg.set.visible = true; - }; - if(!isBool(msg.set.chat)){ - if(msg.set.chat == undefined) msg.set.chat = (!isObj(this.settings) ? true : this.settings.chat); - else msg.set.chat = true; - }; - if(!isBool(msg.set.crownsolo)){ - if(msg.set.crownsolo == undefined) msg.set.crownsolo = (!isObj(this.settings) ? false : this.settings.crownsolo); - else msg.set.crownsolo = false; - }; - if(!isString(msg.set.color) || !/^#[0-9a-f]{6}$/i.test(msg.set.color)) msg.set.color = (!isObj(this.settings) ? this.server.defaultRoomColor : this.settings.color); - if(isString(msg.set.color2)){ - if(!/^#[0-9a-f]{6}$/i.test(msg.set.color2)){ - if(this.settings){ - if(this.settings.color2) msg.set.color2 = this.settings.color2; - else delete msg.set.color2; // keep it nice and clean - } else { - delete msg.set.color2; + + msg.set = RoomSettings.changeSettings(msg.set); + + if (typeof(msg.set.lobby) !== 'undefined') { + if (msg.set.lobby == true) { + if (!this.isLobby(_id)) delete msg.set.lobby; // keep it nice and clean + } else { + if (this.isLobby(_id)) { + msg.set = this.server.lobbySettings; } } - }; - return msg.set; + } } } + module.exports = Room; diff --git a/src/RoomSettings.js b/src/RoomSettings.js new file mode 100644 index 0000000..248d707 --- /dev/null +++ b/src/RoomSettings.js @@ -0,0 +1,166 @@ +class RoomSettings { + static allowedProperties = { + color: { + type: 'color', + default: "#9900ff", + allowedChange: true, + required: true + }, + color2: { + type: 'color2', + default: "#5900bf", + allowedChange: true, + required: false + }, + lobby: { + type: 'boolean', + allowedChange: false, + required: false + }, + visible: { + type: 'boolean', + default: true, + allowedChange: true, + required: true + }, + chat: { + type: 'boolean', + default: true, + allowedChange: true, + required: true + }, + owner_id: { + type: 'string', + allowedChange: false, + required: false + }, + crownsolo: { + type: 'boolean', + default: false, + allowedChange: true, + required: true + } + } + + constructor (set, cont) { + Object.keys(RoomSettings.allowedProperties).forEach(key => { + if (typeof(RoomSettings.allowedProperties[key].default) !== 'undefined') { + if (this[key] !== RoomSettings.allowedProperties[key].default) { + this[key] = RoomSettings.allowedProperties[key].default; + } + } + }); + + Object.keys(RoomSettings.allowedProperties).forEach(key => { + if (RoomSettings.allowedProperties[key].required == true) { + if (typeof(this[key]) == 'undefined') { + this[key] = RoomSettings.allowedProperties[key].default; + } + } + }); + + if (typeof(set) !== 'undefined') { + Object.keys(set).forEach(key => { + if (typeof(set[key]) == 'undefined') return; + if (Object.keys(RoomSettings.allowedProperties).indexOf(key) !== -1) { + if (typeof(cont) == 'undefined') { + this[key] = this.verifyPropertyType(key, set[key], RoomSettings.allowedProperties[key].type); + } else { + if (cont == 'user') { + if (RoomSettings.allowedProperties[key].allowedChange) { + this[key] = this.verifyPropertyType(key, set[key], RoomSettings.allowedProperties[key].type); + } + } + } + } + }); + } + } + + verifyPropertyType(key, pr, type) { + let ret; + + if (typeof(RoomSettings.allowedProperties[key]) !== 'object') return; + + switch (type) { + case 'color': + if (/^#[0-9a-f]{6}$/i.test(pr)) { + ret = pr; + } else { + ret = RoomSettings.allowedProperties[key].default; + } + break; + case 'color2': + if (/^#[0-9a-f]{6}$/i.test(pr)) { + ret = pr; + } else { + ret = RoomSettings.allowedProperties[key].default; + } + break; + default: + if (typeof(pr) == type) { + ret = pr; + } else if (typeof(RoomSettings.allowedProperties[key].default) !== 'undefined') { + ret = RoomSettings.allowedProperties[key].default; + } else { + ret = undefined; + } + break; + } + + return ret; + } + + changeSettings(set) { + Object.keys(set).forEach(key => { + if (RoomSettings.allowedProperties[key].allowedChange) { + this[key] = this.verifyPropertyType(key, set[key], RoomSettings.allowedProperties[key].type); + } + }); + } + + static changeSettings(set) { + Object.keys(set).forEach(key => { + if (RoomSettings.allowedProperties[key].allowedChange) { + set[key] = RoomSettings.verifyPropertyType(key, set[key], RoomSettings.allowedProperties[key].type); + } + }); + return set; + } + + static verifyPropertyType(key, pr, type) { + let ret; + + if (typeof(RoomSettings.allowedProperties[key]) !== 'object') return; + + switch (type) { + case 'color': + if (/^#[0-9a-f]{6}$/i.test(pr)) { + ret = pr; + } else { + ret = RoomSettings.allowedProperties[key].default; + } + break; + case 'color2': + if (/^#[0-9a-f]{6}$/i.test(pr)) { + ret = pr; + } else { + ret = RoomSettings.allowedProperties[key].default; + } + break; + default: + if (typeof(pr) == type) { + ret = pr; + } else if (typeof(RoomSettings.allowedProperties[key].default) !== 'undefined') { + ret = RoomSettings.allowedProperties[key].default; + } else { + ret = undefined; + } + break; + } + + return ret; + } +} + +module.exports = RoomSettings; diff --git a/src/Server.js b/src/Server.js index a6d0525..75910e2 100644 --- a/src/Server.js +++ b/src/Server.js @@ -3,11 +3,13 @@ const banned = require('../banned.json'); const https = require("https"); const http = require("http"); const fs = require('fs'); +const RoomSettings = require('./RoomSettings'); class Server extends EventEmitter { constructor(config) { super(); EventEmitter.call(this); + if (config.ssl == true) { this.https_server = https.createServer({ key: fs.readFileSync('ssl/privkey.pem', 'utf8'), @@ -35,31 +37,42 @@ class Server extends EventEmitter { } }); } + + this.defaultUsername = "Anonymous"; + this.defaultRoomSettings = new RoomSettings(config.defaultRoomSettings); + + this.lobbySettings = new RoomSettings(config.defaultRoomSettings); + this.lobbySettings.lobby = true; + this.lobbySettings.color = config.defaultLobbyColor || "#9900ff"; + this.lobbySettings.color2 = config.defaultLobbyColor2 || "#9900ff"; + console.log(`Server started on port ${config.port}`); this.connectionid = 0; this.connections = new Map(); this.roomlisteners = new Map(); this.rooms = new Map(); + this.wss.on('connection', (ws, req) => { this.connections.set(++this.connectionid, new Client(ws, req, this)); }); + this.legit_m = ["a", "bye", "hi", "ch", "+ls", "-ls", "m", "n", "devices", "t", "chset", "userset", "chown", "kickban", "admin message", "color", "eval", "notification"] this.welcome_motd = config.motd || "You agree to read this message."; + this._id_Private_Key = config._id_PrivateKey || "boppity"; - this.defaultUsername = config.defaultUsername || "Anonymous"; - this.defaultRoomColor = config.defaultRoomColor || "#3b5054"; - this.defaultLobbyColor = config.defaultLobbyColor || "#19b4b9"; - this.defaultLobbyColor2 = config.defaultLobbyColor2 || "#801014"; + this.adminpass = config.adminpass || "Bop It"; - }; + } + updateRoom(data) { if (!data.ch.settings.visible) return; + for (let cl of Array.from(this.roomlisteners.values())) { cl.sendArray([{ "m": "ls", "c": false, "u": [data.ch] - }]) + }]); } } diff --git a/src/User.js b/src/User.js index 3dae730..61a8a66 100644 --- a/src/User.js +++ b/src/User.js @@ -1,53 +1,24 @@ -const ColorEncoder = require("./ColorEncoder.js"); -const { promisify } = require('util'); -let userdb; +const Database = require("./Database"); + class User { - constructor(cl) { - this.cl = cl; - this.server = this.cl.server; - this.userdb = userdb; - this.default_db = {}; + constructor(cl, data) { + this.server = cl.server; + this.name = data.name; + this._id = data._id; + this.color = data.color; + this.ip = data.ip; } - async getUserData() { - if (!userdb || (userdb instanceof Map && [...userdb.entries()] == [])) { - await this.setUpDb(); + + static updateUserModel(cl, user) { + let u2 = new User(cl, user); + if (typeof(u2) == 'undefined') return; + + for (let id in Object.keys(u2)) { + if (!user.hasOwnProperty(id)) { + user[id] = u2[id]; + } } - let _id = createKeccakHash('keccak256').update((this.cl.server._id_Private_Key + this.cl.ip)).digest('hex').substr(0, 24); - let usertofind = userdb.get(_id); - if (!usertofind) { - if (typeof usertofind == 'object' && (usertofind.hasOwnProperty('name') && usertofind.name != this.server.defaultUsername)) return; - userdb.set(_id, { - "color": `#${ColorEncoder.intToRGB(ColorEncoder.hashCode(_id)).toLowerCase()}`, - "name": this.server.defaultUsername, - "_id": _id, - "ip": this.cl.ip - }); - this.updatedb(); - } - let user = userdb.get(_id); - return { - "color": user.color, - "name": user.name, - "_id": user._id, - } - } - async updatedb() { - const writeFile = promisify(fs.writeFile); - await writeFile('src/db/users.json', JSON.stringify(User.strMapToObj(userdb), null, 2)); - } - async setUpDb() { - const writeFile = promisify(fs.writeFile); - const readdir = promisify(fs.readdir); - let files = await readdir("src/db/"); - if (!files.includes("users.json")) { - await writeFile('src/db/users.json', JSON.stringify(this.default_db, null, 2)) - userdb = new Map(Object.entries(require("./db/users.json"))); - } else { - userdb = new Map(Object.entries(require("./db/users.json"))); - } - } - static strMapToObj(strMap) { - return [...strMap.entries()].reduce((obj, [key, value]) => (obj[key] = value, obj), {}); } } + module.exports = User;