add all the staff actions and resets and uhh i forgot to commit but everything works now

This commit is contained in:
elijahr2411
2023-02-07 12:29:33 -05:00
parent 42ecfa2375
commit d9f8653f32
6 changed files with 358 additions and 62 deletions

View File

@@ -5,7 +5,6 @@ import internal from 'stream';
import * as Utilities from './Utilities.js';
import { User, Rank } from './User.js';
import * as guacutils from './guacutils.js';
import * as fs from 'fs';
// I hate that you have to do it like this
import CircularBuffer from 'mnemonist/circular-buffer.js';
import Queue from 'mnemonist/queue.js';
@@ -13,7 +12,7 @@ import { createHash } from 'crypto';
import { isIP } from 'net';
import QEMUVM from './QEMUVM.js';
import Framebuffer from './Framebuffer.js';
import sharp, { Sharp } from 'sharp';
import sharp from 'sharp';
export default class WSServer {
private Config : IConfig;
@@ -22,9 +21,22 @@ export default class WSServer {
private clients : User[];
private ChatHistory : CircularBuffer<{user:string,msg:string}>
private TurnQueue : Queue<User>;
// Time remaining on the current turn
private TurnTime : number;
// Interval to keep track of the current turn time
private TurnInterval? : NodeJS.Timer;
// Is the turn interval running?
private TurnIntervalRunning : boolean;
// If a reset vote is in progress
private voteInProgress : boolean;
// Interval to keep track of vote resets
private voteInterval? : NodeJS.Timer;
// How much time is left on the vote
private voteTime : number;
// How much time until another reset vote can be cast
private voteTimeout : number;
// Interval to keep track
private voteTimeoutInterval? : NodeJS.Timer;
private ModPerms : number;
private VM : QEMUVM;
private framebuffer : Framebuffer;
@@ -35,6 +47,9 @@ export default class WSServer {
this.TurnIntervalRunning = false;
this.clients = [];
this.Config = config;
this.voteInProgress = false;
this.voteTime = 0;
this.voteTimeout = 0;
this.ModPerms = Utilities.MakeModPerms(this.Config.collabvm.moderatorPermissions);
this.server = http.createServer();
this.socket = new WebSocketServer({noServer: true});
@@ -156,49 +171,12 @@ export default class WSServer {
var jpg = await sharp(await this.framebuffer.getFb(), {raw: {height: this.framebuffer.size.height, width: this.framebuffer.size.width, channels: 4}}).jpeg().toBuffer();
var jpg64 = jpg.toString("base64");
client.sendMsg(guacutils.encode("png", "0", "0", "0", "0", jpg64));
if (this.voteInProgress) this.sendVoteUpdate(client);
this.sendTurnUpdate(client);
break;
case "rename":
if (!client.RenameRateLimit.request()) return;
// This shouldn't need a ternary but it does for some reason
var hadName : boolean = client.username ? true : false;
var oldname : any;
if (hadName) oldname = client.username;
if (msgArr.length === 1) {
client.assignGuestName(this.getUsernameList());
} else {
var newName = msgArr[1];
if (hadName && newName === oldname) {
//@ts-ignore
client.sendMsg(guacutils.encode("rename", "0", "0", client.username));
return;
}
if (this.getUsernameList().indexOf(newName) !== -1) {
client.sendMsg(guacutils.encode("rename", "0", "1"));
return;
}
if (!/^[a-zA-Z0-9\ \-\_\.]+$/.test(newName)) {
client.sendMsg(guacutils.encode("rename", "0", "2"));
return;
}
if (this.Config.collabvm.usernameblacklist.indexOf(newName) !== -1) {
client.sendMsg(guacutils.encode("rename", "0", "3"));
return;
}
client.username = newName;
}
//@ts-ignore
client.sendMsg(guacutils.encode("rename", "0", "0", client.username));
if (hadName) {
console.log(`[RENAME] ${client.IP} from ${oldname} to ${client.username}`);
this.clients.filter(c => c.username !== client.username).forEach((c) =>
//@ts-ignore
c.sendMsg(guacutils.encode("rename", "1", oldname, client.username)));
} else {
console.log(`[RENAME] ${client.IP} to ${client.username}`);
this.clients.forEach((c) =>
//@ts-ignore
c.sendMsg(guacutils.encode("adduser", "1", client.username, client.rank)));
}
this.renameUser(client, msgArr[1]);
break;
case "chat":
if (!client.username) return;
@@ -247,6 +225,7 @@ export default class WSServer {
break;
case "mouse":
if (this.TurnQueue.peek() !== client && client.rank !== Rank.Admin) return;
if (!this.VM.vncOpen) return;
if (!this.VM.vnc) throw new Error("VNC Client was undefined");
var x = parseInt(msgArr[1]);
var y = parseInt(msgArr[2]);
@@ -256,16 +235,44 @@ export default class WSServer {
break;
case "key":
if (this.TurnQueue.peek() !== client && client.rank !== Rank.Admin) return;
if (!this.VM.vncOpen) return;
if (!this.VM.vnc) throw new Error("VNC Client was undefined");
var keysym = parseInt(msgArr[1]);
var down = parseInt(msgArr[2]);
if (keysym === undefined || (down !== 0 && down !== 1)) return;
this.VM.vnc.keyEvent(keysym, down);
break;
case "vote":
if (!client.connectedToNode) return;
if (msgArr.length !== 2) return;
switch (msgArr[1]) {
case "1":
if (!this.voteInProgress) {
if (this.voteTimeout !== 0) {
client.sendMsg(guacutils.encode("vote", "3", this.voteTimeout.toString()));
return;
}
this.startVote();
this.clients.forEach(c => c.sendMsg(guacutils.encode("chat", "", `${client.username} has started a vote to reset the VM.`)));
}
else if (client.vote !== true)
this.clients.forEach(c => c.sendMsg(guacutils.encode("chat", "", `${client.username} has voted yes.`)));
client.vote = true;
break;
case "0":
if (!this.voteInProgress) return;
if (client.vote !== false)
this.clients.forEach(c => c.sendMsg(guacutils.encode("chat", "", `${client.username} has voted no.`)));
client.vote = false;
break;
}
this.sendVoteUpdate();
break;
case "admin":
if (msgArr.length < 2) return;
switch (msgArr[1]) {
case "2":
// Login
if (!client.LoginRateLimit.request()) return;
if (msgArr.length !== 3) return;
var sha256 = createHash("sha256");
@@ -285,7 +292,124 @@ export default class WSServer {
//@ts-ignore
this.clients.forEach((c) => c.sendMsg(guacutils.encode("adduser", "1", client.username, client.rank)));
break;
case "5":
// QEMU Monitor
if (client.rank !== Rank.Admin) return;
if (msgArr.length !== 4 || msgArr[2] !== this.Config.collabvm.node) return;
var output = await this.VM.qmpClient.runMonitorCmd(msgArr[3]);
client.sendMsg(guacutils.encode("admin", "2", String(output)));
break;
case "8":
// Restore
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.restore)) return;
this.VM.Restore();
break;
case "10":
// Reboot
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.reboot)) return;
if (msgArr.length !== 3 || msgArr[2] !== this.Config.collabvm.node) return;
this.VM.Reboot();
break;
case "12":
// Ban
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.ban)) return;
var user = this.clients.find(c => c.username === msgArr[2]);
if (!user) return;
user.ban();
case "13":
// Force Vote
if (msgArr.length !== 3) return;
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.forcevote)) return;
if (!this.voteInProgress) return;
switch (msgArr[2]) {
case "1":
this.endVote(true);
break;
case "0":
this.endVote(false);
break;
}
break;
case "14":
// Mute
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.mute)) return;
if (msgArr.length !== 4) return;
var user = this.clients.find(c => c.username === msgArr[2]);
if (!user) return;
var permamute;
switch (msgArr[3]) {
case "0":
permamute = false;
break;
case "1":
permamute = true;
break;
default:
return;
}
user.mute(permamute);
break;
case "15":
// Kick
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.kick)) return;
var user = this.clients.find(c => c.username === msgArr[2]);
if (!user) return;
user.kick();
break;
case "16":
// End turn
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.bypassturn)) return;
if (msgArr.length !== 3) return;
var user = this.clients.find(c => c.username === msgArr[2]);
if (!user) return;
this.endTurn(user);
break;
case "17":
// Clear turn queue
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.bypassturn)) return;
if (msgArr.length !== 3 || msgArr[2] !== this.Config.collabvm.node) return;
this.clearTurns();
break;
case "18":
// Rename user
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.rename)) return;
if (msgArr.length !== 4) return;
var user = this.clients.find(c => c.username === msgArr[2]);
if (!user) return;
this.renameUser(user, msgArr[3]);
break;
case "19":
// Get IP
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.grabip)) return;
if (msgArr.length !== 3) return;
var user = this.clients.find(c => c.username === msgArr[2]);
if (!user) return;
client.sendMsg(guacutils.encode("admin", "19", msgArr[2], user.IP));
break;
case "20":
// Steal turn
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.bypassturn)) return;
this.bypassTurn(client);
break;
case "21":
// XSS
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.xss)) return;
if (msgArr.length !== 3) return;
switch (client.rank) {
case Rank.Admin:
//@ts-ignore
this.clients.forEach(c => c.sendMsg(guacutils.encode("chat", client.username, msgArr[2])));
//@ts-ignore
this.ChatHistory.push({user: client.username, msg: msgArr[2]});
break;
case Rank.Moderator:
//@ts-ignore
this.clients.filter(c => c.rank !== Rank.Admin).forEach(c => c.sendMsg(guacutils.encode("chat", client.username, msgArr[2])));
//@ts-ignore
this.clients.filter(c => c.rank === Rank.Admin).forEach(c => c.sendMsg(guacutils.encode("chat", client.username, Utilities.HTMLSanitize(msgArr[2]))));
break;
}
break;
}
break;
@@ -298,6 +422,49 @@ export default class WSServer {
this.clients.filter(c => c.username).forEach((c) => arr.push(c.username));
return arr;
}
renameUser(client : User, newName? : string) {
// This shouldn't need a ternary but it does for some reason
var hadName : boolean = client.username ? true : false;
var oldname : any;
if (hadName) oldname = client.username;
var status = "0";
if (!newName) {
client.assignGuestName(this.getUsernameList());
} else {
if (hadName && newName === oldname) {
//@ts-ignore
client.sendMsg(guacutils.encode("rename", "0", "0", client.username));
return;
}
if (this.getUsernameList().indexOf(newName) !== -1) {
client.assignGuestName(this.getUsernameList());
status = "1";
} else
if (!/^[a-zA-Z0-9\ \-\_\.]+$/.test(newName) || newName.length > 20 || newName.length < 3) {
client.assignGuestName(this.getUsernameList());
status = "2";
} else
if (this.Config.collabvm.usernameblacklist.indexOf(newName) !== -1) {
client.assignGuestName(this.getUsernameList());
status = "3";
} else client.username = newName;
}
//@ts-ignore
client.sendMsg(guacutils.encode("rename", "0", status, client.username));
if (hadName) {
console.log(`[RENAME] ${client.IP} from ${oldname} to ${client.username}`);
this.clients.filter(c => c.username !== client.username).forEach((c) =>
//@ts-ignore
c.sendMsg(guacutils.encode("rename", "1", oldname, client.username)));
} else {
console.log(`[RENAME] ${client.IP} to ${client.username}`);
this.clients.forEach((c) =>
//@ts-ignore
c.sendMsg(guacutils.encode("adduser", "1", client.username, client.rank)));
}
}
getAdduserMsg() : string {
var arr : string[] = ["adduser", this.clients.length.toString()];
//@ts-ignore
@@ -309,12 +476,16 @@ export default class WSServer {
this.ChatHistory.forEach(c => arr.push(c.user, c.msg));
return guacutils.encode(...arr);
}
private sendTurnUpdate() {
private sendTurnUpdate(client? : User) {
var turnQueueArr = this.TurnQueue.toArray();
var arr = ["turn", (this.TurnTime * 1000).toString(), this.TurnQueue.size.toString()];
// @ts-ignore
this.TurnQueue.forEach((c) => arr.push(c.username));
var currentTurningUser = this.TurnQueue.peek();
if (client) {
client.sendMsg(guacutils.encode(...arr));
return;
}
this.clients.filter(c => (c !== currentTurningUser && c.connectedToNode)).forEach((c) => {
if (turnQueueArr.indexOf(c) !== -1) {
var time = ((this.TurnTime * 1000) + ((turnQueueArr.indexOf(c) - 1) * this.Config.collabvm.turnTime * 1000));
@@ -336,6 +507,27 @@ export default class WSServer {
}
this.sendTurnUpdate();
}
clearTurns() {
clearInterval(this.TurnInterval);
this.TurnIntervalRunning = false;
this.TurnQueue.clear();
this.sendTurnUpdate();
}
bypassTurn(client : User) {
var a = this.TurnQueue.toArray().filter(c => c !== client);
this.TurnQueue = Queue.from([client, ...a]);
this.nextTurn();
}
endTurn(client : User) {
var hasTurn = (this.TurnQueue.peek() === client);
this.TurnQueue = Queue.from(this.TurnQueue.toArray().filter(c => c !== client));
if (hasTurn) this.nextTurn();
else this.sendTurnUpdate();
}
private turnInterval() {
this.TurnTime--;
if (this.TurnTime < 1) {
@@ -364,4 +556,58 @@ export default class WSServer {
res(jpg.toString("base64"));
})
}
startVote() {
if (this.voteInProgress) return;
this.voteInProgress = true;
this.clients.forEach(c => c.sendMsg(guacutils.encode("vote", "0")));
this.voteTime = this.Config.collabvm.voteTime;
this.voteInterval = setInterval(() => {
this.voteTime--;
if (this.voteTime < 1) {
this.endVote();
}
}, 1000);
}
endVote(result? : boolean) {
if (!this.voteInProgress) return;
this.voteInProgress = false;
clearInterval(this.voteInterval);
var count = this.getVoteCounts();
this.clients.forEach((c) => c.sendMsg(guacutils.encode("vote", "2")));
if (result === true || (result === undefined && count.yes >= count.no)) {
this.clients.forEach(c => c.sendMsg(guacutils.encode("chat", "", "The vote to reset the VM has won.")));
this.VM.Restore();
} else {
this.clients.forEach(c => c.sendMsg(guacutils.encode("chat", "", "The vote to reset the VM has lost.")));
}
this.clients.forEach(c => c.vote = null);
this.voteTimeout = 180;
this.voteTimeoutInterval = setInterval(() => {
this.voteTimeout--;
if (this.voteTimeout < 1)
clearInterval(this.voteTimeoutInterval);
}, 1000);
}
sendVoteUpdate(client? : User) {
if (!this.voteInProgress) return;
var count = this.getVoteCounts();
var msg = guacutils.encode("vote", "1", (this.voteTime * 1000).toString(), count.yes.toString(), count.no.toString());
if (client)
client.sendMsg(msg);
else
this.clients.forEach((c) => c.sendMsg(msg));
}
getVoteCounts() : {yes:number,no:number} {
var yes = 0;
var no = 0;
this.clients.forEach((c) => {
if (c.vote === true) yes++;
if (c.vote === false) no++;
});
return {yes:yes,no:no};
}
}