commit
603bf9385d
|
@ -1,2 +0,0 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
|
@ -1 +0,0 @@
|
|||
*.json
|
|
@ -0,0 +1,34 @@
|
|||
module.exports = Object.seal({
|
||||
chat: {
|
||||
lobby: {
|
||||
amount: 4,
|
||||
time: 4000
|
||||
},
|
||||
normal: {
|
||||
amount: 4,
|
||||
time: 4000
|
||||
},
|
||||
insane: {
|
||||
amount: 10,
|
||||
time: 4000
|
||||
}
|
||||
},
|
||||
chown: {
|
||||
amount: 10,
|
||||
time: 5000
|
||||
},
|
||||
userset: {
|
||||
amount: 30,
|
||||
time: 30 * 60000
|
||||
},
|
||||
room: {
|
||||
time: 500
|
||||
},
|
||||
cursor: {
|
||||
time: 16
|
||||
},
|
||||
kickban: {
|
||||
amount: 2,
|
||||
time: 1000
|
||||
}
|
||||
})
|
|
@ -0,0 +1,3 @@
|
|||
[
|
||||
|
||||
]
|
|
@ -0,0 +1,10 @@
|
|||
module.exports = Object.seal({
|
||||
port: "8080",
|
||||
motd: "You agree to read this message.",
|
||||
_id_PrivateKey: "boppity",
|
||||
defaultUsername: "Anonymous",
|
||||
defaultRoomColor: "#3b5054",
|
||||
defaultLobbyColor: "#19b4b9",
|
||||
defaultLobbyColor2: "#801014",
|
||||
adminpass: "adminpass"
|
||||
})
|
2
index.js
2
index.js
|
@ -16,7 +16,7 @@ global.isObj = function(a){
|
|||
}
|
||||
|
||||
let Server = require("./src/Server.js");
|
||||
let config = require('./src/db/config.json');
|
||||
let config = require('./config');
|
||||
global.SERVER = new Server(config);
|
||||
let console = process.platform == 'win32' ? new AsyncConsole("", input => {
|
||||
try {
|
||||
|
|
|
@ -23,10 +23,10 @@
|
|||
"homepage": "https://github.com/BopItFreak/mpp-server#readme",
|
||||
"dependencies": {
|
||||
"asyncconsole": "^1.3.9",
|
||||
"events": "^3.0.0",
|
||||
"keccak": "^2.0.0",
|
||||
"events": "^3.1.0",
|
||||
"keccak": "^2.1.0",
|
||||
"node-json-color-stringify": "^1.1.0",
|
||||
"ws": "^7.1.2"
|
||||
"ws": "^7.2.3"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
const Room = require("./Room.js");
|
||||
const Quota = require ("./Quota.js");
|
||||
const quotas = require('../Quotas');
|
||||
const RateLimit = require('./RateLimit.js').RateLimit;
|
||||
const RateLimitChain = require('./RateLimit.js').RateLimitChain;
|
||||
require('node-json-color-stringify');
|
||||
class Client extends EventEmitter {
|
||||
constructor(ws, req, server) {
|
||||
|
@ -9,6 +13,10 @@ class Client extends EventEmitter {
|
|||
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:", "");
|
||||
|
@ -62,6 +70,24 @@ class Client extends EventEmitter {
|
|||
this.ws.send(JSON.stringify(arr));
|
||||
}
|
||||
}
|
||||
initParticipantQuotas() {
|
||||
this.quotas = {
|
||||
//"chat": new Quota(Quota.PARAMS_A_NORMAL),
|
||||
chat: {
|
||||
lobby: new RateLimitChain(quotas.chat.lobby.amount, quotas.chat.lobby.time),
|
||||
normal: new RateLimitChain(quotas.chat.normal.amount, quotas.chat.normal.time),
|
||||
insane: new RateLimitChain(quotas.chat.insane.amount, quotas.chat.insane.time)
|
||||
},
|
||||
cursor: new RateLimit(quotas.cursor.time),
|
||||
chown: new RateLimitChain(quotas.chown.amount, quotas.chown.time),
|
||||
userset: new RateLimitChain(quotas.userset.amount, quotas.userset.time),
|
||||
kickban: new RateLimitChain(quotas.kickban.amount, quotas.kickban.time),
|
||||
note: new Quota(Quota.PARAMS_LOBBY),
|
||||
chset: new Quota(Quota.PARAMS_USED_A_LOT),
|
||||
"+ls": new Quota(Quota.PARAMS_USED_A_LOT),
|
||||
"-ls": new Quota(Quota.PARAMS_USED_A_LOT)
|
||||
}
|
||||
}
|
||||
destroy() {
|
||||
this.ws.close();
|
||||
if (this.channel) {
|
||||
|
@ -102,4 +128,4 @@ class Client extends EventEmitter {
|
|||
});
|
||||
}
|
||||
}
|
||||
module.exports = Client;
|
||||
module.exports = Client;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
const Quota = require('./Quota');
|
||||
const User = require("./User.js");
|
||||
const Room = require("./Room.js");
|
||||
module.exports = (cl) => {
|
||||
|
@ -26,10 +27,28 @@ module.exports = (cl) => {
|
|||
if (!msg.hasOwnProperty("set") || !msg.set) 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;
|
||||
param.m = "nq";
|
||||
cl.sendArray([param])
|
||||
} else {
|
||||
if (!(cl.user._id == cl.channel.crown.userId)) {
|
||||
param = Quota.N_PARAMS_NORMAL;
|
||||
param.m = "nq";
|
||||
cl.sendArray([param])
|
||||
} else {
|
||||
param = Quota.N_PARAMS_RIDICULOUS;
|
||||
param.m = "nq";
|
||||
cl.sendArray([param])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
cl.on("m", msg => {
|
||||
cl.on("m", (msg, admin) => {
|
||||
if (!cl.quotas.cursor.attempt() && !admin) return;
|
||||
if (!(cl.channel && cl.participantId)) return;
|
||||
if (!msg.hasOwnProperty("x")) msg.x = null;
|
||||
if (!msg.hasOwnProperty("y")) msg.y = null;
|
||||
|
@ -38,7 +57,8 @@ module.exports = (cl) => {
|
|||
cl.channel.emit("m", cl, msg.x, msg.y)
|
||||
|
||||
})
|
||||
cl.on("chown", msg => {
|
||||
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));
|
||||
|
@ -47,9 +67,17 @@ module.exports = (cl) => {
|
|||
// console.log(cl.channel.crown)
|
||||
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 {
|
||||
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])
|
||||
}
|
||||
})
|
||||
cl.on("chset", msg => {
|
||||
|
@ -59,10 +87,19 @@ module.exports = (cl) => {
|
|||
cl.channel.settings = msg.set;
|
||||
cl.channel.updateCh();
|
||||
})
|
||||
cl.on("a", msg => {
|
||||
cl.on("a", (msg, admin) => {
|
||||
if (!(cl.channel && cl.participantId)) return;
|
||||
if (!msg.hasOwnProperty('message')) return;
|
||||
if (cl.channel.settings.chat) {
|
||||
if (cl.channel.isLobby(cl.channel._id)) {
|
||||
if (!cl.quotas.chat.lobby.attempt() && !admin) return;
|
||||
} else {
|
||||
if (!(cl.user._id == cl.channel.crown.userId)) {
|
||||
if (!cl.quotas.chat.normal.attempt() && !admin) return;
|
||||
} else {
|
||||
if (!cl.quotas.chat.insane.attempt() && !admin) return;
|
||||
}
|
||||
}
|
||||
cl.channel.emit('a', cl, msg);
|
||||
}
|
||||
})
|
||||
|
@ -104,6 +141,7 @@ module.exports = (cl) => {
|
|||
if (!msg.hasOwnProperty("set") || !msg.set) msg.set = {};
|
||||
if (msg.set.hasOwnProperty('name') && typeof msg.set.name == "string") {
|
||||
if (msg.set.name.length > 40) return;
|
||||
if (!cl.quotas.name.attempt()) return;
|
||||
cl.user.name = msg.set.name;
|
||||
let user = new User(cl);
|
||||
user.getUserData().then((usr) => {
|
||||
|
@ -124,6 +162,7 @@ module.exports = (cl) => {
|
|||
if (!(cl.channel && cl.participantId)) return;
|
||||
if (!(cl.user._id == cl.channel.crown.userId)) return;
|
||||
if (msg.hasOwnProperty('_id') && typeof msg._id == "string") {
|
||||
if (!cl.quotas.kickban.attempt() && !admin) return;
|
||||
let _id = msg._id;
|
||||
let ms = msg.ms || 0;
|
||||
cl.channel.kickban(_id, ms);
|
||||
|
@ -153,7 +192,7 @@ module.exports = (cl) => {
|
|||
let dbentry = user.userdb.get(uSr._id);
|
||||
if (!dbentry) return;
|
||||
dbentry.color = msg.color;
|
||||
//user.updatedb();
|
||||
user.updatedb();
|
||||
cl.server.rooms.forEach((room) => {
|
||||
room.updateParticipant(usr.participantId, {
|
||||
color: msg.color
|
||||
|
@ -165,4 +204,4 @@ module.exports = (cl) => {
|
|||
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
|
109
src/Quota.js
109
src/Quota.js
|
@ -1,30 +1,96 @@
|
|||
//Adaptation of https://gist.github.com/brandon-lockaby/7339587 into modern javascript.
|
||||
/*
|
||||
class RateLimit {
|
||||
constructor(interval_ms) {
|
||||
this._interval_ms = interval_ms || 0; // (0 means no limit)
|
||||
this._after = 0;
|
||||
}
|
||||
attempt(time) {
|
||||
var time = time || Date.now();
|
||||
if(time < this._after) return false;
|
||||
this._after = time + this._interval_ms;
|
||||
return true;
|
||||
};
|
||||
|
||||
interval(interval_ms) {
|
||||
this._after += interval_ms - this._interval_ms;
|
||||
this._interval_ms = interval_ms;
|
||||
};
|
||||
}
|
||||
|
||||
class RateLimitChain(num, interval_ms) {
|
||||
constructor(num, interval_ms) {
|
||||
this.setNumAndInterval(num, interval_ms);
|
||||
}
|
||||
|
||||
attempt(time) {
|
||||
var time = time || Date.now();
|
||||
for(var i = 0; i < this._chain.length; i++) {
|
||||
if(this._chain[i].attempt(time)) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
setNumAndInterval(num, interval_ms) {
|
||||
this._chain = [];
|
||||
for(var i = 0; i < num; i++) {
|
||||
this._chain.push(new RateLimit(interval_ms));
|
||||
}
|
||||
};
|
||||
}*/
|
||||
|
||||
class Quota {
|
||||
constructor(cb) {
|
||||
constructor(params, cb) {
|
||||
this.cb = cb;
|
||||
this.setParams();
|
||||
this.setParams(params);
|
||||
this.resetPoints();
|
||||
this.interval;
|
||||
};
|
||||
static NQ_PARAMS_LOBBY = {
|
||||
static N_PARAMS_LOBBY = {
|
||||
allowance: 200,
|
||||
max: 600
|
||||
max: 600,
|
||||
interval: 2000
|
||||
};
|
||||
static NQ_PARAMS_NORMAL = {
|
||||
static N_PARAMS_NORMAL = {
|
||||
allowance: 400,
|
||||
max: 1200
|
||||
max: 1200,
|
||||
interval: 2000
|
||||
};
|
||||
static NQ_PARAMS_RIDICULOUS = {
|
||||
static N_PARAMS_RIDICULOUS = {
|
||||
allowance: 600,
|
||||
max: 1800
|
||||
max: 1800,
|
||||
interval: 2000
|
||||
};
|
||||
static NQ_PARAMS_OFFLINE = {
|
||||
static PARAMS_OFFLINE = {
|
||||
allowance: 8000,
|
||||
max: 24000,
|
||||
maxHistLen: 3
|
||||
maxHistLen: 3,
|
||||
interval: 2000
|
||||
};
|
||||
static CH_PARAMS = {
|
||||
allowance: 8000,
|
||||
max: 24000,
|
||||
maxHistLen: 3
|
||||
static PARAMS_A_NORMAL = {
|
||||
allowance: 4,
|
||||
max: 4,
|
||||
interval: 6000
|
||||
};
|
||||
static PARAMS_A_CROWNED = {
|
||||
allowance:10,
|
||||
max:10,
|
||||
interval: 2000
|
||||
}
|
||||
static PARAMS_CH = {
|
||||
allowance: 1,
|
||||
max: 2,
|
||||
interval: 1000
|
||||
}
|
||||
static PARAMS_USED_A_LOT = {
|
||||
allowance:1,
|
||||
max:1,
|
||||
interval: 2000
|
||||
}
|
||||
static PARAMS_M = {
|
||||
allowance:15000,
|
||||
max:500000,
|
||||
interval: 2000
|
||||
}
|
||||
getParams() {
|
||||
return {
|
||||
|
@ -35,10 +101,14 @@ class Quota {
|
|||
};
|
||||
};
|
||||
setParams(params) {
|
||||
params = params || NoteQuota.PARAMS_OFFLINE;
|
||||
var allowance = params.allowance || this.allowance || NoteQuota.PARAMS_OFFLINE.allowance;
|
||||
var max = params.max || this.max || NoteQuota.PARAMS_OFFLINE.max;
|
||||
var maxHistLen = params.maxHistLen || this.maxHistLen || NoteQuota.PARAMS_OFFLINE.maxHistLen;
|
||||
params = params || Quota.PARAMS_OFFLINE;
|
||||
var allowance = params.allowance || this.allowance || Quota.PARAMS_OFFLINE.allowance;
|
||||
var max = params.max || this.max || Quota.PARAMS_OFFLINE.max;
|
||||
var maxHistLen = params.maxHistLen || this.maxHistLen || Quota.PARAMS_OFFLINE.maxHistLen;
|
||||
let interval = params.interval || 0
|
||||
this.inverval = setInterval(() => {
|
||||
this.tick();
|
||||
}, params.interval)
|
||||
if (allowance !== this.allowance || max !== this.max || maxHistLen !== this.maxHistLen) {
|
||||
this.allowance = allowance;
|
||||
this.max = max;
|
||||
|
@ -84,4 +154,5 @@ class Quota {
|
|||
}
|
||||
};
|
||||
}
|
||||
module.exports = Quota;
|
||||
|
||||
module.exports = Quota
|
|
@ -0,0 +1,40 @@
|
|||
|
||||
var RateLimit = function(interval_ms) {
|
||||
this._interval_ms = interval_ms || 0; // (0 means no limit)
|
||||
this._after = 0;
|
||||
};
|
||||
|
||||
RateLimit.prototype.attempt = function(time) {
|
||||
var time = time || Date.now();
|
||||
if(time < this._after) return false;
|
||||
this._after = time + this._interval_ms;
|
||||
return true;
|
||||
};
|
||||
|
||||
RateLimit.prototype.setInterval = function(interval_ms) {
|
||||
this._after += interval_ms - this._interval_ms;
|
||||
this._interval_ms = interval_ms;
|
||||
};
|
||||
|
||||
var RateLimitChain = function(num, interval_ms) {
|
||||
this.setNumAndInterval(num, interval_ms);
|
||||
};
|
||||
|
||||
RateLimitChain.prototype.attempt = function(time) {
|
||||
var time = time || Date.now();
|
||||
for(var i = 0; i < this._chain.length; i++) {
|
||||
if(this._chain[i].attempt(time)) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
RateLimitChain.prototype.setNumAndInterval = function(num, interval_ms) {
|
||||
this._chain = [];
|
||||
for(var i = 0; i < num; i++) {
|
||||
this._chain.push(new RateLimit(interval_ms));
|
||||
}
|
||||
};
|
||||
|
||||
var exports = typeof module !== "undefined" ? module.exports : this;
|
||||
exports.RateLimit = RateLimit;
|
||||
exports.RateLimitChain = RateLimitChain;
|
38
src/Room.js
38
src/Room.js
|
@ -2,7 +2,7 @@
|
|||
//room class
|
||||
//room deleter
|
||||
//databases in Map
|
||||
|
||||
const Quota = require("./Quota.js");
|
||||
class Room extends EventEmitter {
|
||||
constructor(server, _id, settings) {
|
||||
super();
|
||||
|
@ -11,7 +11,9 @@ class Room extends EventEmitter {
|
|||
this.server = server;
|
||||
this.crown = null;
|
||||
this.crowndropped = false;
|
||||
this.settings = this.verifySet(this._id,{set:settings});
|
||||
this.settings = this.verifySet(this._id, {
|
||||
set: settings
|
||||
});
|
||||
this.chatmsgs = [];
|
||||
this.ppl = new Map();
|
||||
this.connections = [];
|
||||
|
@ -25,7 +27,9 @@ class Room extends EventEmitter {
|
|||
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.
|
||||
//cl.quotas.a.setParams(Quota.PARAMS_A_CROWNED);
|
||||
this.crown = {
|
||||
participantId: cl.participantId,
|
||||
userId: cl.user._id,
|
||||
|
@ -40,8 +44,11 @@ class Room extends EventEmitter {
|
|||
}
|
||||
}
|
||||
this.crowndropped = false;
|
||||
} else {
|
||||
//cl.quotas.a.setParams(Quota.PARAMS_A_NORMAL);
|
||||
}
|
||||
this.ppl.set(participantId, cl);
|
||||
|
||||
this.connections.push(cl);
|
||||
this.sendArray([{
|
||||
color: this.ppl.get(cl.participantId).user.color,
|
||||
|
@ -60,6 +67,7 @@ class Room extends EventEmitter {
|
|||
} else {
|
||||
cl.user.id = otheruser.participantId;
|
||||
cl.participantId = otheruser.participantId;
|
||||
cl.quotas = otheruser.quotas;
|
||||
this.connections.push(cl);
|
||||
cl.sendArray([{
|
||||
m: "c",
|
||||
|
@ -169,12 +177,12 @@ class Room extends EventEmitter {
|
|||
}
|
||||
return data;
|
||||
}
|
||||
verifyColor(strColor){
|
||||
verifyColor(strColor) {
|
||||
var test2 = /^#[0-9A-F]{6}$/i.test(strColor);
|
||||
if(test2 == true){
|
||||
return strColor;
|
||||
} else{
|
||||
return false;
|
||||
if (test2 == true) {
|
||||
return strColor;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
isLobby(_id) {
|
||||
|
@ -182,16 +190,16 @@ class Room extends EventEmitter {
|
|||
let lobbynum = _id.split("lobby")[1];
|
||||
if (_id == "lobby") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!(parseInt(lobbynum).toString() == lobbynum)) return false;
|
||||
for (let i in lobbynum) {
|
||||
if (parseInt(lobbynum[i]) >= 0) {
|
||||
if (parseInt(i) + 1 == lobbynum.length) return true;
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
for (let i in lobbynum) {
|
||||
if (parseInt(lobbynum[i]) >= 0) {
|
||||
if (parseInt(i) + 1 == lobbynum.length) return true;
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (_id.startsWith("test/")) {
|
||||
if (_id == "test/") {
|
||||
return false;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const Client = require("./Client.js")
|
||||
const Client = require("./Client.js");
|
||||
const banned = require('../banned.json');
|
||||
class Server extends EventEmitter {
|
||||
constructor(config) {
|
||||
super();
|
||||
|
@ -6,8 +7,9 @@ class Server extends EventEmitter {
|
|||
this.wss = new WebSocket.Server({
|
||||
port: config.port,
|
||||
backlog: 100,
|
||||
verifyClient: function (info, done) {
|
||||
done(true)
|
||||
verifyClient: (info) => {
|
||||
if (banned.includes((info.req.connection.remoteAddress).replace("::ffff:", ""))) return false;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
this.connectionid = 0;
|
||||
|
|
|
@ -50,4 +50,4 @@ class User {
|
|||
return [...strMap.entries()].reduce((obj, [key, value]) => (obj[key] = value, obj), {});
|
||||
}
|
||||
}
|
||||
module.exports = User;
|
||||
module.exports = User;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{}
|
Loading…
Reference in New Issue