From 8d5aaf4eebfd08edbd0b9fe24259c26cd41ca7e3 Mon Sep 17 00:00:00 2001 From: Elijah R Date: Sun, 23 Jun 2024 19:33:38 -0400 Subject: [PATCH] typescriptify it all --- .gitignore | 2 + package.json | 9 ++- client.js => src/client.ts | 83 ++++++++++++--------- src/config.ts | 14 ++++ index.js => src/index.ts | 145 +++++++++++++++++++++---------------- tsconfig.json | 108 +++++++++++++++++++++++++++ 6 files changed, 261 insertions(+), 100 deletions(-) rename client.js => src/client.ts (74%) create mode 100644 src/config.ts rename index.js => src/index.ts (83%) create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 00103ef..35b34ee 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules/ package-lock.json yarn.lock config.js +config.json +dist/ \ No newline at end of file diff --git a/package.json b/package.json index 45ba980..d15fede 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,14 @@ }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e", "scripts": { - "format": "prettier *.js -w" + "format": "prettier src/*.ts -w", + "build": "tsc", + "serve": "node dist/index.js" }, "devDependencies": { - "prettier": "^3.3.2" + "@types/node": "^20.14.8", + "@types/ws": "^8.5.10", + "prettier": "^3.3.2", + "typescript": "^5.5.2" } } diff --git a/client.js b/src/client.ts similarity index 74% rename from client.js rename to src/client.ts index fadbbfb..3a1f767 100644 --- a/client.js +++ b/src/client.ts @@ -2,9 +2,9 @@ import * as ws from "ws"; const guacutils = { - parse: (string) => { + parse: (string: string) => { let pos = -1; - let sections = []; + let sections: string[] = []; for (;;) { let len = string.indexOf(".", pos + 1); @@ -29,7 +29,7 @@ const guacutils = { return sections; }, - encode: (cypher) => { + encode: (cypher: string[]) => { let command = ""; for (var i = 0; i < cypher.length; i++) { @@ -41,22 +41,22 @@ const guacutils = { }, }; -const ConnectionState = Object.freeze({ - CLOSED: 0, - CONNECTING: 1, - CONNECTED: 2, -}); +enum ConnectionState { + CLOSED = 0, + CONNECTING = 1, + CONNECTED = 2, +} // System chat messages have a nil username. -function IsSystemChatInstruction(inst) { +function IsSystemChatInstruction(inst: string[]) { return inst[1] == ""; } class UserData { - #_name; - #_rank; + private _name: string; + private _rank: number; - constructor(name, rank) { + constructor(name: string, rank: number) { this._name = name; this._rank = rank; } @@ -68,12 +68,25 @@ class UserData { return this._rank; } - UpdateRank(new_rank) { + UpdateRank(new_rank: number) { this._rank = new_rank; } } -export default class CollabVMClient { +export default abstract class CollabVMClient { + abstract OnOpen(): void; + abstract OnClose(): void; + abstract OnChat(username: string, message: string): void; + abstract OnGuacamoleMessage(msg: string[]): void; + abstract OnAddUser_Bot(count: number): void; + abstract OnRemUser_Bot(count: number): void; + + private _state: ConnectionState; + private _users: UserData[]; + private _ws: ws.WebSocket | undefined; + private _username: string | undefined; + private _vm: string | undefined; + constructor() { this._state = ConnectionState.CLOSED; this._users = []; @@ -83,14 +96,16 @@ export default class CollabVMClient { return this._state; } - Connect(uri) { + Connect(uri: string) { this._ws = new ws.WebSocket(uri, "guacamole", { origin: "https://computernewb.com", }); - this._ws.onopen = this.OnWebSocketOpen.bind(this); - this._ws.onclose = this.OnWebSocketClose.bind(this); - this._ws.onerror = this.OnWebSocketError.bind(this); - this._ws.onmessage = this.OnWebSocketMessage.bind(this); + this._ws.on("open", () => this.OnWebSocketOpen()); + this._ws.on("close", () => this.OnWebSocketClose()); + this._ws.on("error", () => this.OnWebSocketError()); + this._ws.on("message", (msg, isBinary) => + this.OnWebSocketMessage(msg, isBinary), + ); this._state = ConnectionState.CONNECTING; } @@ -101,25 +116,25 @@ export default class CollabVMClient { OnWebSocketClose() { this._state = ConnectionState.CLOSED; - this.OnClose(arguments); + this.OnClose(); } OnWebSocketError() { // fire the close handler this._state = ConnectionState.CLOSED; - this.OnClose(arguments); + this.OnClose(); } Close() { this._state = ConnectionState.CLOSED; - this._ws.close(); + this._ws?.close(); } - OnWebSocketMessage(ev) { + OnWebSocketMessage(msg: Buffer | ArrayBuffer | Buffer[], isBinary: boolean) { // cvm server should never send binary data - if (typeof ev.data !== "string") return; + if (isBinary) return; - let message = guacutils.parse(ev.data); + let message = guacutils.parse(msg.toString("utf-8")); if (message.length === 0) return; // Hardcoded, we need to keep this to be alive @@ -165,11 +180,11 @@ export default class CollabVMClient { this.OnGuacamoleMessage(message); } - OnAddUser(users, count) { + OnAddUser(users: string[], count: number) { //console.log(users); for (var i = 0; i < count * 2; i += 2) { let name = users[i]; - let rank = users[i + 1]; + let rank = parseInt(users[i + 1]); //console.log(`[${this.GetVM()}] user ${name} rank ${rank}`) @@ -188,7 +203,7 @@ export default class CollabVMClient { this.OnAddUser_Bot(this.GetUserCount()); } - OnRemUser(users, count) { + OnRemUser(users: string[], count: number) { for (var i = 0; i < count; i++) { let saveUserTemp = this.GetUser(users[i]); this._users = this._users.filter((user) => user.GetName() != users[i]); @@ -212,7 +227,7 @@ export default class CollabVMClient { // subtract known bots for (var i = len - 1; i != 0; --i) { // ? - if (this._users[i] === undefined) return; + if (this._users[i] === undefined) break; var name = this._users[i].GetName(); if (KnownBots.find((elem) => name == elem) !== undefined) { @@ -232,7 +247,7 @@ export default class CollabVMClient { this._users; } - GetUser(username) { + GetUser(username: string) { let existingUser = this._users.find((elem) => elem.GetName() == username); // Apparently this can fail somehow..? @@ -245,7 +260,7 @@ export default class CollabVMClient { return this._username; } - Rename(name) { + Rename(name: string) { this._username = name; this.SendGuacamoleMessage("rename", name); } @@ -254,14 +269,14 @@ export default class CollabVMClient { return this._vm; } - ConnectToVM(vm) { + ConnectToVM(vm: string) { this._vm = vm; this.SendGuacamoleMessage("connect", vm); } - SendGuacamoleMessage() { + SendGuacamoleMessage(...args: string[]) { if (this._state !== ConnectionState.CONNECTED) return; - this._ws.send(guacutils.encode(Array.prototype.slice.call(arguments))); + this._ws?.send(guacutils.encode(args)); } } diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..c7b2d28 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,14 @@ +export default interface Config { + BANNED_ISO: RegExp[]; + ISO_DIRECTORIES: { [key: string]: string }; + INSTALLBOT_VMS: { + uri: string; + id: string; + usesIde2: boolean; + hasFloppy: boolean; + }[]; + BOT_PREFIX: string; + ADMIN_TOKEN: string; + kGeneralLimitBaseSeconds: number; + kRebootLimitBaseSeconds: number; +} diff --git a/index.js b/src/index.ts similarity index 83% rename from index.js rename to src/index.ts index 1212142..eb00f9a 100644 --- a/index.js +++ b/src/index.ts @@ -1,28 +1,39 @@ +import { readFileSync } from "fs"; import CollabVMClient from "./client.js"; -import { - BANNED_ISO, - ISO_DIRECTORIES, - INSTALLBOT_VMS, - BOT_PREFIX, - ADMIN_TOKEN, - kGeneralLimitBaseSeconds, - kRebootLimitBaseSeconds, -} from "./config.js"; +import Config from "./config.js"; -function Log() { +let config: Config = JSON.parse(readFileSync("config.json", "utf-8")); + +function Log(...args: string[]) { // console.log(`[AnyOSBot] [${new Date()}]`, [...arguments].join(' ')) - console.log("[AnyOSBot]", [...arguments].join(" ")); + console.log("[AnyOSBot]", args.join(" ")); +} + +interface HelpCommand { + command: string; + help: string; + usesFloppy?: boolean | undefined; } // you people SUCK man (dynamic edition, with less bugs!) // and it actually probably works without hanging or negative seconds this time. class RateLimit { - constructor(time, factor, ident) { + private _ident: string; + private _timeBase: number; + private _factor: number; + private _msUntil: number; + private _n: number; + private _limited: boolean; + private _siHandle: NodeJS.Timeout | null; + + constructor(time: number, factor: number, ident: string) { this._ident = ident; this._timeBase = time; this._msUntil = 0; this._n = 1; this._factor = factor; + this._limited = false; + this._siHandle = null; } GetTime() { @@ -46,7 +57,7 @@ class RateLimit { return ret; } - SetUserCount(count) { + SetUserCount(count: number) { if (count == 0) count = 1; this._n = count; @@ -57,19 +68,18 @@ class RateLimit { // TODO: this might work for the dyna ratelimit? if (this._limited) return; - let self = this; - self._msUntil = this.GetMs(); + this._msUntil = this.GetMs(); Log( - `Ratelimit \"${this._ident}\" started, will be done in ${self._msUntil} ms (${self._msUntil / 1000} seconds)`, + `Ratelimit \"${this._ident}\" started, will be done in ${this._msUntil} ms (${this._msUntil / 1000} seconds)`, ); - self._limited = true; + this._limited = true; this._siHandle = setInterval(() => { - self._msUntil -= 1000; - if (self._msUntil <= 0) { - Log(`Ratelimit \"${self._ident}\" is done.`); - clearInterval(self._siHandle); - self._limited = false; + this._msUntil -= 1000; + if (this._msUntil <= 0) { + Log(`Ratelimit \"${this._ident}\" is done.`); + if (this._siHandle !== null) clearInterval(this._siHandle); + this._limited = false; return; } }, 1000); @@ -86,7 +96,14 @@ class RateLimit { } class HelperBot extends CollabVMClient { - constructor(wsUri, vmId, ide2, floppy) { + private _wsUri: string; + private _vmId: string; + private _ide2: boolean; + private _hasFloppy: boolean; + private GeneralCmdLimit: RateLimit | undefined; + private RebootLimit: RateLimit | undefined; + + constructor(wsUri: string, vmId: string, ide2: boolean, floppy: boolean) { super(); this._wsUri = wsUri; @@ -109,17 +126,17 @@ class HelperBot extends CollabVMClient { this.ConnectToVM(this._vmId); } - OnClose(ev) { + OnClose() { //console.log(arguments) // reconnect lol /* the right way doesnt work thanks to something Log(`[${this._vmId}]`, `Connection closed, reconnecting in 5 seconds`); - let self = this; + let this = this; setTimeout(() => { Log(`[${this._vmId}]`, `Reconnecting now`); - self.DoConn(); + this.DoConn(); }, 1000 * 5) */ @@ -129,21 +146,21 @@ class HelperBot extends CollabVMClient { // The bot should probably give up after some attempts } - Chat(message) { + Chat(message: string) { this.SendGuacamoleMessage("chat", message); } - OnAddUser_Bot(count) { - this.GeneralCmdLimit.SetUserCount(count); - this.RebootLimit.SetUserCount(count); + OnAddUser_Bot(count: number) { + this.GeneralCmdLimit!.SetUserCount(count); + this.RebootLimit!.SetUserCount(count); } - OnRemUser_Bot(count) { - this.GeneralCmdLimit.SetUserCount(count); - this.RebootLimit.SetUserCount(count); + OnRemUser_Bot(count: number) { + this.GeneralCmdLimit!.SetUserCount(count); + this.RebootLimit!.SetUserCount(count); } - UserCanBypass(username) { + UserCanBypass(username: string) { let existingUser = this.GetUser(username); // Apparently this can fail somehow..? @@ -153,12 +170,12 @@ class HelperBot extends CollabVMClient { return rank == 2 || rank == 3; } - SendMonitorCommand(cmd) { - this.SendGuacamoleMessage("admin", "5", this.GetVM(), cmd); + SendMonitorCommand(cmd: string) { + this.SendGuacamoleMessage("admin", "5", this.GetVM()!, cmd); } - ConcatPath(isodir, otherPath) { - let isopath = ISO_DIRECTORIES[isodir]; + ConcatPath(isodir: string, otherPath: string) { + let isopath = config.ISO_DIRECTORIES[isodir]; if (isopath === undefined) { // imo crashing and being restarted by systemd is better than not knowing why shit was coming out as undefined // gotta love javascript @@ -169,11 +186,11 @@ class HelperBot extends CollabVMClient { // QEMU Abstractions - QemuEjectDevice(devname) { + QemuEjectDevice(devname: string) { this.SendMonitorCommand(`eject ${devname}`); } - QemuChangeDevice(devname, source, opts) { + QemuChangeDevice(devname: string, source: string, opts: string) { this.SendMonitorCommand(`change ${devname} "${source}" ${opts}`); } @@ -186,27 +203,27 @@ class HelperBot extends CollabVMClient { if (this._hasFloppy) this.QemuEjectDevice("vm.floppy"); } - QemuChangeCd(source, opts) { + QemuChangeCd(source: string, opts: string) { if (this._ide2) this.QemuChangeDevice("ide2-cd0", source, opts); else this.QemuChangeDevice("vm.cd", source, opts); } - QemuChangeFloppy(source) { + QemuChangeFloppy(source: string) { if (this._hasFloppy) this.QemuChangeDevice("vm.floppy", source, "raw read-only"); } - OnChat(username, message) { + OnChat(username: string, message: string) { //console.log(`${username}> ${message}`); if (username == this.GetUsername()) return; - if (message[0] === BOT_PREFIX) { + if (message[0] === config.BOT_PREFIX) { this.HandleCommands(username, message); } } - HandleCommands(username, message) { + HandleCommands(username: string, message: string) { { let user = this.GetUser(username); if (user == null) { @@ -224,7 +241,7 @@ class HelperBot extends CollabVMClient { // Little code fragment to make rate limiting // more portable. - const DoLimit = (limit) => { + const DoLimit = (limit: RateLimit) => { //console.log(`[AnyOSBot] [${this._vmId}] ${this.GetUserCount()} users online (${this.GetUserCountFull()} actual)`) //if(this._vmId !== 'vm0b0t') { @@ -249,18 +266,18 @@ class HelperBot extends CollabVMClient { }; // generate a help HTML string for the help - const generateHelp = (arr) => { + const generateHelp = (arr: HelpCommand[]) => { let str = "

AnyOSInstallBot Help:

"; return str; }; - const generateList = (title, arr) => { + const generateList = (title: string, arr: string[]) => { let str = `

${title}