kopasdkopsdaokp
This commit is contained in:
parent
4ebf9ee0e6
commit
53a12461ce
16 changed files with 694 additions and 114 deletions
33
Binato.ts
33
Binato.ts
|
@ -1,12 +1,13 @@
|
||||||
import { Application } from "express";
|
import { ChatHistory } from "./server/ChatHistory";
|
||||||
import compression from "compression";
|
import compression from "compression";
|
||||||
|
import config from "./config.json";
|
||||||
import { ConsoleHelper } from "./ConsoleHelper";
|
import { ConsoleHelper } from "./ConsoleHelper";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { readFile } from "fs";
|
import { HandleRequest } from "./server/BanchoServer";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
import { Registry, collectDefaultMetrics } from "prom-client";
|
import { Registry, collectDefaultMetrics } from "prom-client";
|
||||||
|
|
||||||
const binatoApp:Application = express();
|
const binatoApp:express.Application = express();
|
||||||
const config = require("./config.json");
|
|
||||||
|
|
||||||
if (config["prometheus"]["enabled"]) {
|
if (config["prometheus"]["enabled"]) {
|
||||||
const register:Registry = new Registry();
|
const register:Registry = new Registry();
|
||||||
|
@ -14,7 +15,7 @@ if (config["prometheus"]["enabled"]) {
|
||||||
|
|
||||||
collectDefaultMetrics({ register });
|
collectDefaultMetrics({ register });
|
||||||
|
|
||||||
const prometheusApp:Application = express();
|
const prometheusApp:express.Application = express();
|
||||||
prometheusApp.get("/metrics", async (req, res) => {
|
prometheusApp.get("/metrics", async (req, res) => {
|
||||||
res.end(await register.metrics());
|
res.end(await register.metrics());
|
||||||
});
|
});
|
||||||
|
@ -31,6 +32,8 @@ if (config["express"]["compression"]) {
|
||||||
ConsoleHelper.printWarn("Compression is disabled");
|
ConsoleHelper.printWarn("Compression is disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INDEX_PAGE:string = readFileSync("./web/serverPage.html").toString();
|
||||||
|
|
||||||
binatoApp.use((req, res) => {
|
binatoApp.use((req, res) => {
|
||||||
let packet:Buffer = Buffer.alloc(0);
|
let packet:Buffer = Buffer.alloc(0);
|
||||||
req.on("data", (chunk:Buffer) => packet = Buffer.concat([packet, chunk], packet.length + chunk.length));
|
req.on("data", (chunk:Buffer) => packet = Buffer.concat([packet, chunk], packet.length + chunk.length));
|
||||||
|
@ -38,21 +41,9 @@ binatoApp.use((req, res) => {
|
||||||
switch (req.method) {
|
switch (req.method) {
|
||||||
case "GET":
|
case "GET":
|
||||||
if (req.url == "/" || req.url == "/index.html" || req.url == "/index") {
|
if (req.url == "/" || req.url == "/index.html" || req.url == "/index") {
|
||||||
res.sendFile(`${__dirname}/web/serverPage.html`);
|
res.send(INDEX_PAGE);
|
||||||
} else if (req.url == "/chat") {
|
} else if (req.url == "/chat") {
|
||||||
readFile("./web/chatPageTemplate.html", (err, data) => {
|
res.send(ChatHistory.GenerateForWeb());
|
||||||
if (err) throw err;
|
|
||||||
|
|
||||||
let lines = "", flip = false;
|
|
||||||
const limit = global.chatHistory.length < 10 ? 10 : global.chatHistory.length;
|
|
||||||
for (let i = global.chatHistory.length - 10; i < limit; i++) {
|
|
||||||
if (i < 0) i = 0;
|
|
||||||
lines += `<div class="line line${flip ? 1 : 0}">${global.chatHistory[i] == null ? "<hidden>blank</hidden>" : global.chatHistory[i]}</div>`
|
|
||||||
flip = !flip;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.send(data.toString().replace("|content|", lines));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -60,8 +51,8 @@ binatoApp.use((req, res) => {
|
||||||
// Make sure this address should respond to bancho requests
|
// Make sure this address should respond to bancho requests
|
||||||
// Bancho addresses: c, c1, c2, c3, c4, c5, c6, ce
|
// Bancho addresses: c, c1, c2, c3, c4, c5, c6, ce
|
||||||
// Just looking for the first character being "c" *should* be enough
|
// Just looking for the first character being "c" *should* be enough
|
||||||
if (req.headers["host"].split(".")[0][0] == "c")
|
if (req.headers.host != null && req.headers.host.split(".")[0][0] == "c")
|
||||||
serverHandler(req, res);
|
HandleRequest(req, res, packet);
|
||||||
else
|
else
|
||||||
res.status(400).send("400 | Bad Request!<br>Binato only accepts POST requests on Bancho subdomains.<hr>Binato");
|
res.status(400).send("400 | Bad Request!<br>Binato only accepts POST requests on Bancho subdomains.<hr>Binato");
|
||||||
break;
|
break;
|
||||||
|
|
165
package-lock.json
generated
165
package-lock.json
generated
|
@ -9,6 +9,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/compression": "^1.7.2",
|
||||||
"@types/express": "^4.17.14",
|
"@types/express": "^4.17.14",
|
||||||
"@types/node": "^18.11.9",
|
"@types/node": "^18.11.9",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
|
@ -16,7 +17,8 @@
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"mysql2": "^2.3.3",
|
"mysql2": "^2.3.3",
|
||||||
"osu-packet": "^4.1.2",
|
"osu-packet": "^4.1.2",
|
||||||
"prom-client": "^14.1.0"
|
"prom-client": "^14.1.0",
|
||||||
|
"redis": "^4.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^2.0.20",
|
"nodemon": "^2.0.20",
|
||||||
|
@ -60,6 +62,59 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@redis/bloom": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-9QovlxmpRtvxVbN0UBcv8WfdSMudNZZTFqCsnBszcQXqaZb/TVe30ScgGEO7u1EAIacTPAo7/oCYjYAxiHLanQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@redis/client": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@redis/client": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-1gEj1AkyXPlkcC/9/T5xpDcQF8ntERURjLBgEWMTdUZqe181zfI9BY3jc2OzjTLkvZh5GV7VT4ktoJG2fV2ufw==",
|
||||||
|
"dependencies": {
|
||||||
|
"cluster-key-slot": "1.1.1",
|
||||||
|
"generic-pool": "3.9.0",
|
||||||
|
"yallist": "4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@redis/graph": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@redis/client": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@redis/json": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@redis/client": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@redis/search": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-NyFZEVnxIJEybpy+YskjgOJRNsfTYqaPbK/Buv6W2kmFNaRk85JiqjJZA5QkRmWvGbyQYwoO5QfDi2wHskKrQQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@redis/client": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@redis/time-series": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@redis/client": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tsconfig/node10": {
|
"node_modules/@tsconfig/node10": {
|
||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||||
|
@ -93,6 +148,14 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/compression": {
|
||||||
|
"version": "1.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.2.tgz",
|
||||||
|
"integrity": "sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/connect": {
|
"node_modules/@types/connect": {
|
||||||
"version": "3.4.35",
|
"version": "3.4.35",
|
||||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
|
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
|
||||||
|
@ -374,6 +437,14 @@
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cluster-key-slot": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
@ -656,6 +727,14 @@
|
||||||
"is-property": "^1.0.2"
|
"is-property": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/generic-pool": {
|
||||||
|
"version": "3.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
|
||||||
|
"integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
|
||||||
|
@ -1171,6 +1250,19 @@
|
||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redis": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis/-/redis-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-oZGAmOKG+RPnHo0UxM5GGjJ0dBd/Vi4fs3MYwM1p2baDoXC0wpm0yOdpxVS9K+0hM84ycdysp2eHg2xGoQ4FEw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@redis/bloom": "1.1.0",
|
||||||
|
"@redis/client": "1.4.0",
|
||||||
|
"@redis/graph": "1.1.0",
|
||||||
|
"@redis/json": "1.0.4",
|
||||||
|
"@redis/search": "1.1.0",
|
||||||
|
"@redis/time-series": "1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/safe-buffer": {
|
"node_modules/safe-buffer": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
@ -1510,6 +1602,46 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@redis/bloom": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-9QovlxmpRtvxVbN0UBcv8WfdSMudNZZTFqCsnBszcQXqaZb/TVe30ScgGEO7u1EAIacTPAo7/oCYjYAxiHLanQ==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"@redis/client": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-1gEj1AkyXPlkcC/9/T5xpDcQF8ntERURjLBgEWMTdUZqe181zfI9BY3jc2OzjTLkvZh5GV7VT4ktoJG2fV2ufw==",
|
||||||
|
"requires": {
|
||||||
|
"cluster-key-slot": "1.1.1",
|
||||||
|
"generic-pool": "3.9.0",
|
||||||
|
"yallist": "4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@redis/graph": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"@redis/json": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"@redis/search": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-NyFZEVnxIJEybpy+YskjgOJRNsfTYqaPbK/Buv6W2kmFNaRk85JiqjJZA5QkRmWvGbyQYwoO5QfDi2wHskKrQQ==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"@redis/time-series": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"@tsconfig/node10": {
|
"@tsconfig/node10": {
|
||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||||
|
@ -1543,6 +1675,14 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/compression": {
|
||||||
|
"version": "1.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.2.tgz",
|
||||||
|
"integrity": "sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg==",
|
||||||
|
"requires": {
|
||||||
|
"@types/express": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/connect": {
|
"@types/connect": {
|
||||||
"version": "3.4.35",
|
"version": "3.4.35",
|
||||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
|
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
|
||||||
|
@ -1766,6 +1906,11 @@
|
||||||
"readdirp": "~3.6.0"
|
"readdirp": "~3.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cluster-key-slot": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw=="
|
||||||
|
},
|
||||||
"color-convert": {
|
"color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
@ -1988,6 +2133,11 @@
|
||||||
"is-property": "^1.0.2"
|
"is-property": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"generic-pool": {
|
||||||
|
"version": "3.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
|
||||||
|
"integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g=="
|
||||||
|
},
|
||||||
"get-intrinsic": {
|
"get-intrinsic": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
|
||||||
|
@ -2377,6 +2527,19 @@
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"redis": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis/-/redis-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-oZGAmOKG+RPnHo0UxM5GGjJ0dBd/Vi4fs3MYwM1p2baDoXC0wpm0yOdpxVS9K+0hM84ycdysp2eHg2xGoQ4FEw==",
|
||||||
|
"requires": {
|
||||||
|
"@redis/bloom": "1.1.0",
|
||||||
|
"@redis/client": "1.4.0",
|
||||||
|
"@redis/graph": "1.1.0",
|
||||||
|
"@redis/json": "1.0.4",
|
||||||
|
"@redis/search": "1.1.0",
|
||||||
|
"@redis/time-series": "1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/compression": "^1.7.2",
|
||||||
"@types/express": "^4.17.14",
|
"@types/express": "^4.17.14",
|
||||||
"@types/node": "^18.11.9",
|
"@types/node": "^18.11.9",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
|
@ -17,7 +18,8 @@
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"mysql2": "^2.3.3",
|
"mysql2": "^2.3.3",
|
||||||
"osu-packet": "^4.1.2",
|
"osu-packet": "^4.1.2",
|
||||||
"prom-client": "^14.1.0"
|
"prom-client": "^14.1.0",
|
||||||
|
"redis": "^4.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^2.0.20",
|
"nodemon": "^2.0.20",
|
||||||
|
|
|
@ -1,71 +1,76 @@
|
||||||
import * as osu from "osu-packet";
|
import config from "../config.json";
|
||||||
import { ConsoleHelper } from "../ConsoleHelper";
|
import { ConsoleHelper } from "../ConsoleHelper";
|
||||||
|
import { Database } from "./objects/Database";
|
||||||
|
import { UserArray } from "./objects/UserArray";
|
||||||
|
import { LatLng } from "./objects/LatLng";
|
||||||
import { Packets } from "./enums/Packets";
|
import { Packets } from "./enums/Packets";
|
||||||
|
import { RedisClientType, createClient } from "redis";
|
||||||
|
import { replaceAll } from "./Util";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { User } from "./objects/User";
|
||||||
|
import * as osu from "osu-packet";
|
||||||
|
|
||||||
const
|
/*const
|
||||||
loginHandler = require("./loginHandler.js"),
|
loginHandler = require("./loginHandler.js"),
|
||||||
parseUserData = require("./util/parseUserData.js"),
|
parseUserData = require("./util/parseUserData.js"),
|
||||||
User = require("./User.js"),
|
|
||||||
getUserFromToken = require("./util/getUserByToken.js"),
|
getUserFromToken = require("./util/getUserByToken.js"),
|
||||||
getUserById = require("./util/getUserById.js"),
|
getUserById = require("./util/getUserById.js"),
|
||||||
bakedResponses = require("./bakedResponses.js"),
|
bakedResponses = require("./bakedResponses.js"),
|
||||||
Streams = require("./Streams.js"),
|
Streams = require("./Streams.js");*/
|
||||||
DatabaseHelperClass = require("./DatabaseHelper.js"),
|
|
||||||
funkyArray = require("./util/funkyArray.js"),
|
|
||||||
config = require("../config.json");
|
|
||||||
|
|
||||||
// Users funkyArray for session storage
|
const DB:Database = new Database(config.database.address, config.database.port, config.database.username, config.database.password, config.database.name, async () => {
|
||||||
global.users = new funkyArray();
|
|
||||||
|
|
||||||
// Add the bot user
|
|
||||||
global.botUser = global.users.add("bot", new User(3, "SillyBot", "bot"));
|
|
||||||
// Set the bot's position on the map
|
|
||||||
global.botUser.location[0] = 50;
|
|
||||||
global.botUser.location[1] = -32;
|
|
||||||
|
|
||||||
global.DatabaseHelper = new DatabaseHelperClass(config.database.address, config.database.port, config.database.username, config.database.password, config.database.name, async () => {
|
|
||||||
// Close any unclosed db matches on startup
|
// Close any unclosed db matches on startup
|
||||||
global.DatabaseHelper.query("UPDATE mp_matches SET close_time = UNIX_TIMESTAMP() WHERE close_time IS NULL");
|
DB.query("UPDATE mp_matches SET close_time = UNIX_TIMESTAMP() WHERE close_time IS NULL");
|
||||||
global.DatabaseHelper.query("UPDATE osu_info SET value = 0 WHERE name = 'online_now'");
|
DB.query("UPDATE osu_info SET value = 0 WHERE name = 'online_now'");
|
||||||
});
|
});
|
||||||
|
|
||||||
async function subscribeToChannel(channelName = "", callback = function(message = "") {}) {
|
// Users funkyArray for session storage
|
||||||
|
const users = new UserArray();
|
||||||
|
|
||||||
|
// Add the bot user
|
||||||
|
const botUser:User = users.add("bot", new User(3, "SillyBot", "bot", DB));
|
||||||
|
// Set the bot's position on the map
|
||||||
|
botUser.location = new LatLng(50, -32);
|
||||||
|
|
||||||
|
let redisClient:RedisClientType;
|
||||||
|
|
||||||
|
async function subscribeToChannel(channelName:string, callback:(message:string) => void) {
|
||||||
// Dup and connect new client for channel subscription (required)
|
// Dup and connect new client for channel subscription (required)
|
||||||
const subscriptionClient = global.promClient.duplicate();
|
const subscriptionClient:RedisClientType = redisClient.duplicate();
|
||||||
await subscriptionClient.connect();
|
await subscriptionClient.connect();
|
||||||
// Subscribe to channel
|
// Subscribe to channel
|
||||||
await subscriptionClient.subscribe(channelName, callback);
|
await subscriptionClient.subscribe(channelName, callback);
|
||||||
ConsoleHelper.printRedis(`Subscribed to ${channelName} channel`);
|
ConsoleHelper.printRedis(`Subscribed to ${channelName} channel`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do redis if it's enabled
|
|
||||||
if (config.redis.enabled) {
|
if (config.redis.enabled) {
|
||||||
(async () => {
|
(async () => {
|
||||||
const { createClient } = require("redis");
|
redisClient = createClient({
|
||||||
global.promClient = createClient({
|
url: `redis://${replaceAll(config.redis.password, " ", "") == "" ? "" : `${config.redis.password}@`}${config.redis.address}:${config.redis.port}/${config.redis.database}`
|
||||||
url: `redis://${config.redis.password.replaceAll(" ", "") == "" ? "" : `${config.redis.password}@`}${config.redis.address}:${config.redis.port}/${config.redis.database}`
|
|
||||||
});
|
});
|
||||||
|
|
||||||
global.promClient.on('error', e => consoleHelper.printRedis(e));
|
redisClient.on('error', e => ConsoleHelper.printRedis(e));
|
||||||
|
|
||||||
const connectionStartTime = Date.now();
|
const connectionStartTime = Date.now();
|
||||||
await global.promClient.connect();
|
await redisClient.connect();
|
||||||
consoleHelper.printRedis(`Connected to redis server. Took ${Date.now() - connectionStartTime}ms`);
|
ConsoleHelper.printRedis(`Connected to redis server. Took ${Date.now() - connectionStartTime}ms`);
|
||||||
|
|
||||||
// Score submit update channel
|
// Score submit update channel
|
||||||
subscribeToChannel("binato:update_user_stats", (message) => {
|
subscribeToChannel("binato:update_user_stats", (message) => {
|
||||||
const user = getUserById(parseInt(message));
|
if (typeof(message) === "string") {
|
||||||
|
const user = users.getById(parseInt(message));
|
||||||
// Update user info
|
// Update user info
|
||||||
user.updateUserInfo(true);
|
user.updateUserInfo(true);
|
||||||
|
|
||||||
consoleHelper.printRedis(`Score submission stats update request received for ${user.username}`);
|
ConsoleHelper.printRedis(`Score submission stats update request received for ${user.username}`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
} else consoleHelper.printWarn("Redis is disabled!");
|
} else ConsoleHelper.printWarn("Redis is disabled!");
|
||||||
|
|
||||||
// User timeout interval
|
// User timeout interval
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
for (let User of global.users.getIterableItems()) {
|
for (let User of users.getIterableItems()) {
|
||||||
if (User.id == 3) continue; // Ignore the bot
|
if (User.id == 3) continue; // Ignore the bot
|
||||||
// Bot: :(
|
// Bot: :(
|
||||||
|
|
||||||
|
@ -75,36 +80,25 @@ setInterval(() => {
|
||||||
}
|
}
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
// An array containing the last 15 messages in chat
|
|
||||||
global.chatHistory = [];
|
|
||||||
global.addChatMessage = function(msg) {
|
|
||||||
if (global.chatHistory.length == 15) {
|
|
||||||
global.chatHistory.splice(0, 1);
|
|
||||||
global.chatHistory.push(msg);
|
|
||||||
} else {
|
|
||||||
global.chatHistory.push(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init stream class
|
// Init stream class
|
||||||
Streams.init();
|
//Streams.init();
|
||||||
|
|
||||||
// An array containing all chat channels
|
// An array containing all chat channels
|
||||||
global.channels = [
|
/*global.channels = [
|
||||||
{ channelName:"#osu", channelTopic:"The main channel", channelUserCount: 0, locked: false },
|
{ channelName:"#osu", channelTopic:"The main channel", channelUserCount: 0, locked: false },
|
||||||
{ channelName:"#userlog", channelTopic:"Log about stuff doing go on yes very", channelUserCount: 0, locked: false },
|
{ channelName:"#userlog", channelTopic:"Log about stuff doing go on yes very", channelUserCount: 0, locked: false },
|
||||||
{ channelName:"#lobby", channelTopic:"Talk about multiplayer stuff", channelUserCount: 0, locked: false },
|
{ channelName:"#lobby", channelTopic:"Talk about multiplayer stuff", channelUserCount: 0, locked: false },
|
||||||
{ channelName:"#english", channelTopic:"Talk in exclusively English", channelUserCount: 0, locked: false },
|
{ channelName:"#english", channelTopic:"Talk in exclusively English", channelUserCount: 0, locked: false },
|
||||||
{ channelName:"#japanese", channelTopic:"Talk in exclusively Japanese", channelUserCount: 0, locked: false },
|
{ channelName:"#japanese", channelTopic:"Talk in exclusively Japanese", channelUserCount: 0, locked: false },
|
||||||
];
|
];*/
|
||||||
|
|
||||||
// Create a stream for each chat channel
|
// Create a stream for each chat channel
|
||||||
for (let i = 0; i < global.channels.length; i++) {
|
/*for (let i = 0; i < global.channels.length; i++) {
|
||||||
Streams.addStream(global.channels[i].channelName, false);
|
Streams.addStream(global.channels[i].channelName, false);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// Add a stream for the multiplayer lobby
|
// Add a stream for the multiplayer lobby
|
||||||
Streams.addStream("multiplayer_lobby", false);
|
//Streams.addStream("multiplayer_lobby", false);
|
||||||
|
|
||||||
// Include packets
|
// Include packets
|
||||||
const ChangeAction = require("./Packets/ChangeAction.js"),
|
const ChangeAction = require("./Packets/ChangeAction.js"),
|
||||||
|
@ -127,11 +121,11 @@ const ChangeAction = require("./Packets/ChangeAction.js"),
|
||||||
TourneyMatchLeaveChannel = require("./Packets/TourneyLeaveMatchChannel.js");
|
TourneyMatchLeaveChannel = require("./Packets/TourneyLeaveMatchChannel.js");
|
||||||
|
|
||||||
// A class for managing everything multiplayer
|
// A class for managing everything multiplayer
|
||||||
global.MultiplayerManager = new MultiplayerManager();
|
const multiplayerManager:MultiplayerManager = new MultiplayerManager();
|
||||||
|
|
||||||
module.exports = async function(req, res, packet:Buffer) {
|
export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||||
// Get the client's token string and request data
|
// Get the client's token string and request data
|
||||||
const requestTokenString:string = req.header("osu-token"),
|
const requestTokenString:string | undefined = req.header("osu-token"),
|
||||||
requestData:Buffer = packet;
|
requestData:Buffer = packet;
|
||||||
|
|
||||||
// Server's response
|
// Server's response
|
||||||
|
@ -147,7 +141,7 @@ module.exports = async function(req, res, packet:Buffer) {
|
||||||
// Client has a token, let's see what they want.
|
// Client has a token, let's see what they want.
|
||||||
try {
|
try {
|
||||||
// Get the current user
|
// Get the current user
|
||||||
const PacketUser:User = getUserFromToken(requestTokenString);
|
const PacketUser:User = users.getByToken(requestTokenString);
|
||||||
|
|
||||||
// Make sure the client's token isn't invalid
|
// Make sure the client's token isn't invalid
|
||||||
if (PacketUser != null) {
|
if (PacketUser != null) {
|
||||||
|
@ -160,7 +154,7 @@ module.exports = async function(req, res, packet:Buffer) {
|
||||||
const PacketData = osuPacketReader.Parse();
|
const PacketData = osuPacketReader.Parse();
|
||||||
|
|
||||||
// Go through each packet sent by the client
|
// Go through each packet sent by the client
|
||||||
for (CurrentPacket of PacketData) {
|
for (let CurrentPacket of PacketData) {
|
||||||
switch (CurrentPacket.id) {
|
switch (CurrentPacket.id) {
|
||||||
case Packets.Client_ChangeAction:
|
case Packets.Client_ChangeAction:
|
||||||
ChangeAction(PacketUser, CurrentPacket.data);
|
ChangeAction(PacketUser, CurrentPacket.data);
|
||||||
|
@ -190,136 +184,136 @@ module.exports = async function(req, res, packet:Buffer) {
|
||||||
Spectator.stopSpectatingUser(PacketUser);
|
Spectator.stopSpectatingUser(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_sendPrivateMessage:
|
case Packets.Client_sendPrivateMessage:
|
||||||
SendPrivateMessage(PacketUser, CurrentPacket.data);
|
SendPrivateMessage(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_joinLobby:
|
case Packets.Client_joinLobby:
|
||||||
global.MultiplayerManager.userEnterLobby(PacketUser);
|
global.MultiplayerManager.userEnterLobby(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_partLobby:
|
case Packets.Client_partLobby:
|
||||||
global.MultiplayerManager.userLeaveLobby(PacketUser);
|
global.MultiplayerManager.userLeaveLobby(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_createMatch:
|
case Packets.Client_createMatch:
|
||||||
await global.MultiplayerManager.createMultiplayerMatch(PacketUser, CurrentPacket.data);
|
await global.MultiplayerManager.createMultiplayerMatch(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_joinMatch:
|
case Packets.Client_joinMatch:
|
||||||
global.MultiplayerManager.joinMultiplayerMatch(PacketUser, CurrentPacket.data);
|
global.MultiplayerManager.joinMultiplayerMatch(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_matchChangeSlot:
|
case Packets.Client_matchChangeSlot:
|
||||||
PacketUser.currentMatch.moveToSlot(PacketUser, CurrentPacket.data);
|
PacketUser.currentMatch.moveToSlot(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_matchReady:
|
case Packets.Client_matchReady:
|
||||||
PacketUser.currentMatch.setStateReady(PacketUser);
|
PacketUser.currentMatch.setStateReady(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_matchChangeSettings:
|
case Packets.Client_matchChangeSettings:
|
||||||
await PacketUser.currentMatch.updateMatch(PacketUser, CurrentPacket.data);
|
await PacketUser.currentMatch.updateMatch(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_matchNotReady:
|
case Packets.Client_matchNotReady:
|
||||||
PacketUser.currentMatch.setStateNotReady(PacketUser);
|
PacketUser.currentMatch.setStateNotReady(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_partMatch:
|
case Packets.Client_partMatch:
|
||||||
await global.MultiplayerManager.leaveMultiplayerMatch(PacketUser);
|
await global.MultiplayerManager.leaveMultiplayerMatch(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Also handles user kick if the slot has a user
|
// Also handles user kick if the slot has a user
|
||||||
case Packets.client_matchLock:
|
case Packets.Client_matchLock:
|
||||||
PacketUser.currentMatch.lockMatchSlot(PacketUser, CurrentPacket.data);
|
PacketUser.currentMatch.lockMatchSlot(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_matchNoBeatmap:
|
case Packets.Client_matchNoBeatmap:
|
||||||
PacketUser.currentMatch.missingBeatmap(PacketUser);
|
PacketUser.currentMatch.missingBeatmap(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_matchSkipRequest:
|
case Packets.Client_matchSkipRequest:
|
||||||
PacketUser.currentMatch.matchSkip(PacketUser);
|
PacketUser.currentMatch.matchSkip(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_matchHasBeatmap:
|
case Packets.Client_matchHasBeatmap:
|
||||||
PacketUser.currentMatch.notMissingBeatmap(PacketUser);
|
PacketUser.currentMatch.notMissingBeatmap(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_matchTransferHost:
|
case Packets.Client_matchTransferHost:
|
||||||
PacketUser.currentMatch.transferHost(PacketUser, CurrentPacket.data);
|
PacketUser.currentMatch.transferHost(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_matchChangeMods:
|
case Packets.Client_matchChangeMods:
|
||||||
PacketUser.currentMatch.updateMods(PacketUser, CurrentPacket.data);
|
PacketUser.currentMatch.updateMods(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_matchStart:
|
case Packets.Client_matchStart:
|
||||||
PacketUser.currentMatch.startMatch();
|
PacketUser.currentMatch.startMatch();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_matchLoadComplete:
|
case Packets.Client_matchLoadComplete:
|
||||||
PacketUser.currentMatch.matchPlayerLoaded(PacketUser);
|
PacketUser.currentMatch.matchPlayerLoaded(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_matchComplete:
|
case Packets.Client_matchComplete:
|
||||||
await PacketUser.currentMatch.onPlayerFinishMatch(PacketUser);
|
await PacketUser.currentMatch.onPlayerFinishMatch(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_matchScoreUpdate:
|
case Packets.Client_matchScoreUpdate:
|
||||||
PacketUser.currentMatch.updatePlayerScore(PacketUser, CurrentPacket.data);
|
PacketUser.currentMatch.updatePlayerScore(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_matchFailed:
|
case Packets.Client_matchFailed:
|
||||||
PacketUser.currentMatch.matchFailed(PacketUser);
|
PacketUser.currentMatch.matchFailed(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_matchChangeTeam:
|
case Packets.Client_matchChangeTeam:
|
||||||
PacketUser.currentMatch.changeTeam(PacketUser);
|
PacketUser.currentMatch.changeTeam(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_channelJoin:
|
case Packets.Client_channelJoin:
|
||||||
ChannelJoin(PacketUser, CurrentPacket.data);
|
ChannelJoin(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_channelPart:
|
case Packets.Client_channelPart:
|
||||||
ChannelPart(PacketUser, CurrentPacket.data);
|
ChannelPart(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_setAwayMessage:
|
case Packets.Client_setAwayMessage:
|
||||||
SetAwayMessage(PacketUser, CurrentPacket.data);
|
SetAwayMessage(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_friendAdd:
|
case Packets.Client_friendAdd:
|
||||||
AddFriend(PacketUser, CurrentPacket.data);
|
AddFriend(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_friendRemove:
|
case Packets.Client_friendRemove:
|
||||||
RemoveFriend(PacketUser, CurrentPacket.data);
|
RemoveFriend(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_userStatsRequest:
|
case Packets.Client_userStatsRequest:
|
||||||
UserStatsRequest(PacketUser, CurrentPacket.data);
|
UserStatsRequest(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_specialMatchInfoRequest:
|
case Packets.Client_specialMatchInfoRequest:
|
||||||
TourneyMatchSpecialInfo(PacketUser, CurrentPacket.data);
|
TourneyMatchSpecialInfo(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_specialJoinMatchChannel:
|
case Packets.Client_specialJoinMatchChannel:
|
||||||
TourneyMatchJoinChannel(PacketUser, CurrentPacket.data);
|
TourneyMatchJoinChannel(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_specialLeaveMatchChannel:
|
case Packets.Client_specialLeaveMatchChannel:
|
||||||
TourneyMatchLeaveChannel(PacketUser, CurrentPacket.data);
|
TourneyMatchLeaveChannel(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_invite:
|
case Packets.Client_invite:
|
||||||
MultiplayerInvite(PacketUser, CurrentPacket.data);
|
MultiplayerInvite(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.client_userPresenceRequest:
|
case Packets.Client_userPresenceRequest:
|
||||||
UserPresence(PacketUser, PacketUser.id); // Can't really think of a way to generalize this?
|
UserPresence(PacketUser, PacketUser.id); // Can't really think of a way to generalize this?
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
34
server/ChatHistory.ts
Normal file
34
server/ChatHistory.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
|
||||||
|
export abstract class ChatHistory {
|
||||||
|
private static _history:Array<string> = new Array<string>();
|
||||||
|
private static _lastGeneratedPage:string;
|
||||||
|
private static _hasChanged:boolean = true;
|
||||||
|
private static readonly HISTORY_LENGTH = 10;
|
||||||
|
private static readonly PAGE_TEMPLATE = readFileSync("./web/chatPageTemplate.html").toString();
|
||||||
|
|
||||||
|
public static AddMessage(message:string) : void {
|
||||||
|
if (this._history.length === 10) {
|
||||||
|
this._history.splice(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._history.push(message);
|
||||||
|
this._hasChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GenerateForWeb() : string {
|
||||||
|
let lines:string = "", flip:boolean = false;
|
||||||
|
|
||||||
|
for (let i:number = Math.max(this._history.length - this.HISTORY_LENGTH, this.HISTORY_LENGTH); i < this._history.length; i++) {
|
||||||
|
lines += `<div class="line line${flip ? 1 : 0}">${this._history[i] == null ? "<hidden>blank</hidden>" : this._history[i]}</div>`
|
||||||
|
flip = !flip;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._hasChanged) {
|
||||||
|
this._lastGeneratedPage = this.PAGE_TEMPLATE.toString().replace("|content|", lines);
|
||||||
|
this._hasChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._lastGeneratedPage;
|
||||||
|
}
|
||||||
|
}
|
8
server/Util.ts
Normal file
8
server/Util.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
|
||||||
|
function escapeRegExp(string:string) {
|
||||||
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function replaceAll(inputString:string, toReplace:string, toReplaceWith:string) {
|
||||||
|
return inputString.replace(`/:${escapeRegExp(toReplace)}:/g`, toReplaceWith);
|
||||||
|
}
|
|
@ -56,7 +56,7 @@ export enum Packets {
|
||||||
Client_MatchNoBeatmap,
|
Client_MatchNoBeatmap,
|
||||||
Client_MatchNotReady,
|
Client_MatchNotReady,
|
||||||
Client_MatchFailed,
|
Client_MatchFailed,
|
||||||
Server_MatchComplete,
|
Server_MatchComplete = 58,
|
||||||
Client_MatchHasBeatmap,
|
Client_MatchHasBeatmap,
|
||||||
Client_MatchSkipRequest,
|
Client_MatchSkipRequest,
|
||||||
Server_MatchSkip,
|
Server_MatchSkip,
|
||||||
|
@ -109,5 +109,5 @@ export enum Packets {
|
||||||
// NOTE: Tournament client only
|
// NOTE: Tournament client only
|
||||||
Client_SpecialJoinMatchChannel,
|
Client_SpecialJoinMatchChannel,
|
||||||
// NOTE: Tournament client only
|
// NOTE: Tournament client only
|
||||||
Client_SpecialLeaveMatchChanne,
|
Client_SpecialLeaveMatchChannel,
|
||||||
}
|
}
|
5
server/enums/RankingModes.ts
Normal file
5
server/enums/RankingModes.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export enum RankingModes {
|
||||||
|
PP,
|
||||||
|
RANKED_SCORE,
|
||||||
|
AVG_ACCURACY
|
||||||
|
};
|
77
server/objects/Database.ts
Normal file
77
server/objects/Database.ts
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import { ConsoleHelper } from "../../ConsoleHelper";
|
||||||
|
import { createPool, Pool } from "mysql2";
|
||||||
|
|
||||||
|
export class Database {
|
||||||
|
private connectionPool:Pool;
|
||||||
|
private static readonly CONNECTION_LIMIT = 128;
|
||||||
|
|
||||||
|
public constructor(databaseAddress:string, databasePort:number = 3306, databaseUsername:string, databasePassword:string, databaseName:string, connectedCallback:Function) {
|
||||||
|
this.connectionPool = createPool({
|
||||||
|
connectionLimit: Database.CONNECTION_LIMIT,
|
||||||
|
host: databaseAddress,
|
||||||
|
port: databasePort,
|
||||||
|
user: databaseUsername,
|
||||||
|
password: databasePassword,
|
||||||
|
database: databaseName
|
||||||
|
});
|
||||||
|
|
||||||
|
const classCreationTime:number = Date.now();
|
||||||
|
const connectionCheckInterval = setInterval(() => {
|
||||||
|
this.query("SELECT name FROM osu_info LIMIT 1")
|
||||||
|
.then(data => {
|
||||||
|
ConsoleHelper.printBancho(`Connected to database. Took ${Date.now() - classCreationTime}ms`);
|
||||||
|
clearInterval(connectionCheckInterval);
|
||||||
|
|
||||||
|
connectedCallback();
|
||||||
|
})
|
||||||
|
.catch(err => {});
|
||||||
|
}, 17); // Roughly 6 times per sec
|
||||||
|
}
|
||||||
|
|
||||||
|
public query(query = "", data?:Array<any>) {
|
||||||
|
const limited = query.includes("LIMIT 1");
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.connectionPool.getConnection((err, connection) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (e) {
|
||||||
|
ConsoleHelper.printError("Failed to release mysql connection\n" + err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use old query
|
||||||
|
if (data == null) {
|
||||||
|
connection.query(query, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
connection.release();
|
||||||
|
} else {
|
||||||
|
dataReceived(resolve, data, limited);
|
||||||
|
connection.release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Use new prepared statements w/ placeholders
|
||||||
|
else {
|
||||||
|
connection.execute(query, data, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
connection.release();
|
||||||
|
} else {
|
||||||
|
dataReceived(resolve, data, limited);
|
||||||
|
connection.release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dataReceived(resolveCallback:(value:unknown) => void, data:any, limited:boolean = false) : void {
|
||||||
|
if (limited) resolveCallback(data[0]);
|
||||||
|
else resolveCallback(data);
|
||||||
|
}
|
74
server/objects/FunkyArray.ts
Normal file
74
server/objects/FunkyArray.ts
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
export class FunkyArray<T> {
|
||||||
|
private items:any = {};
|
||||||
|
private itemKeys:Array<string> = Object.keys(this.items);
|
||||||
|
private iterableArray:Array<T> = new Array<T>();
|
||||||
|
|
||||||
|
public add(uuid:string, item:T, regenerate:boolean = true) : T {
|
||||||
|
this.items[uuid] = item;
|
||||||
|
|
||||||
|
if (regenerate) {
|
||||||
|
this.itemKeys = Object.keys(this.items);
|
||||||
|
this.regenerateIterableArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.items[uuid];
|
||||||
|
}
|
||||||
|
|
||||||
|
public remove(uuid:string, regenerate:boolean = true) {
|
||||||
|
delete this.items[uuid];
|
||||||
|
if (regenerate) {
|
||||||
|
this.itemKeys = Object.keys(this.items);
|
||||||
|
this.regenerateIterableArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeFirstItem(regenerate:boolean = true) : void {
|
||||||
|
delete this.items[this.itemKeys[0]];
|
||||||
|
this.itemKeys = Object.keys(this.items);
|
||||||
|
if (regenerate) this.regenerateIterableArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public regenerateIterableArray() : void {
|
||||||
|
this.iterableArray = new Array();
|
||||||
|
for (let itemKey of this.itemKeys) {
|
||||||
|
this.iterableArray.push(this.items[itemKey]);
|
||||||
|
}
|
||||||
|
this.itemKeys = Object.keys(this.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFirstItem() : T {
|
||||||
|
return this.items[this.itemKeys[0]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLength() : number {
|
||||||
|
return this.itemKeys.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getKeyById(id:number) : string {
|
||||||
|
return this.itemKeys[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getById(id:number) : T | undefined {
|
||||||
|
return this.items[this.itemKeys[id]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getByKey(key:string) : T | undefined {
|
||||||
|
if (key in this.items) {
|
||||||
|
return this.items[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getKeys() : Array<string> {
|
||||||
|
return this.itemKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getItems() : any {
|
||||||
|
return this.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getIterableItems() : Array<T> {
|
||||||
|
return this.iterableArray;
|
||||||
|
}
|
||||||
|
}
|
9
server/objects/LatLng.ts
Normal file
9
server/objects/LatLng.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export class LatLng {
|
||||||
|
public latitude:number;
|
||||||
|
public longitude:number;
|
||||||
|
|
||||||
|
public constructor(latitude:number, longitude:number) {
|
||||||
|
this.latitude = latitude;
|
||||||
|
this.longitude = longitude;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
import { Database } from "./Database";
|
||||||
|
import { LatLng } from "./LatLng";
|
||||||
|
import { RankingModes } from "../enums/RankingModes";
|
||||||
|
const StatusUpdate = require("./Packets/StatusUpdate.js");
|
||||||
|
|
||||||
|
const rankingModes = [
|
||||||
|
"pp_raw",
|
||||||
|
"ranked_score",
|
||||||
|
"avg_accuracy"
|
||||||
|
];
|
||||||
|
|
||||||
|
export class User {
|
||||||
|
private static readonly EMPTY_BUFFER = Buffer.alloc(0);
|
||||||
|
|
||||||
|
public id:number;
|
||||||
|
public username:string;
|
||||||
|
public uuid:string;
|
||||||
|
public readonly connectTime:number = Date.now();
|
||||||
|
public timeoutTime:number = Date.now() + 30000;
|
||||||
|
public queue:Buffer = User.EMPTY_BUFFER;
|
||||||
|
|
||||||
|
// Binato data
|
||||||
|
public rankingMode:RankingModes = RankingModes.PP;
|
||||||
|
|
||||||
|
// osu! data
|
||||||
|
public playMode:number = 0;
|
||||||
|
public countryID:number = 0;
|
||||||
|
//public spectators:Array; // TODO: Figure out if this was ever needed
|
||||||
|
public spectating:number = -1;
|
||||||
|
public location:LatLng = new LatLng(0, 0);
|
||||||
|
public joinedChannels:Array<string> = new Array<string>();
|
||||||
|
|
||||||
|
// Presence data
|
||||||
|
public actionID:number = 0;
|
||||||
|
public actionText:string = "";
|
||||||
|
public actionMods:number = 0;
|
||||||
|
public beatmapChecksum:string = "";
|
||||||
|
public beatmapID:number = 0;
|
||||||
|
public currentMods:number = 0;
|
||||||
|
|
||||||
|
// Cached db data
|
||||||
|
public rankedScore:number = 0;
|
||||||
|
public accuracy:number = 0;
|
||||||
|
public playCount:number = 0;
|
||||||
|
public totalScore:number = 0;
|
||||||
|
public rank:number = 0;
|
||||||
|
public pp:number = 0;
|
||||||
|
|
||||||
|
// Multiplayer data
|
||||||
|
public currentMatch = null;
|
||||||
|
public matchSlotId:number = -1;
|
||||||
|
public inMatch:boolean = false;
|
||||||
|
|
||||||
|
// Tournament client flag
|
||||||
|
public isTourneyUser:boolean = false;
|
||||||
|
|
||||||
|
public dbConnection:Database;
|
||||||
|
|
||||||
|
public constructor(id:number, username:string, uuid:string, dbConnection:Database) {
|
||||||
|
this.id = id;
|
||||||
|
this.username = username;
|
||||||
|
this.uuid = uuid;
|
||||||
|
|
||||||
|
this.dbConnection = dbConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concats new actions to the user's queue
|
||||||
|
public addActionToQueue(newData:Buffer) {
|
||||||
|
this.queue = Buffer.concat([this.queue, newData], this.queue.length + newData.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearQueue() {
|
||||||
|
this.queue = User.EMPTY_BUFFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates the user's current action
|
||||||
|
updatePresence(action:any) : void {
|
||||||
|
this.actionID = action.status;
|
||||||
|
this.actionText = action.statusText;
|
||||||
|
this.beatmapChecksum = action.beatmapChecksum;
|
||||||
|
this.currentMods = action.currentMods;
|
||||||
|
this.actionMods = action.currentMods;
|
||||||
|
if (action.playMode != this.playMode) {
|
||||||
|
this.updateUserInfo(true);
|
||||||
|
this.playMode = action.playMode;
|
||||||
|
}
|
||||||
|
this.beatmapID = action.beatmapId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the user's score information from the database and caches it
|
||||||
|
async updateUserInfo(forceUpdate:boolean = false) : Promise<void> {
|
||||||
|
const userScoreDB:any = await this.dbConnection.query("SELECT * FROM users_modes_info WHERE user_id = ? AND mode_id = ? LIMIT 1", [this.id, this.playMode]);
|
||||||
|
const mappedRankingMode = rankingModes[this.rankingMode];
|
||||||
|
const userRankDB:any = await this.dbConnection.query(`SELECT user_id, ${mappedRankingMode} FROM users_modes_info WHERE mode_id = ? ORDER BY ${mappedRankingMode} DESC`, [this.playMode]);
|
||||||
|
|
||||||
|
if (userScoreDB == null || userRankDB == null) throw "fuck";
|
||||||
|
|
||||||
|
// Handle "if we should update" checks for each rankingMode
|
||||||
|
let userScoreUpdate = false;
|
||||||
|
switch (this.rankingMode) {
|
||||||
|
case RankingModes.PP:
|
||||||
|
if (this.pp != userScoreDB.pp_raw)
|
||||||
|
userScoreUpdate = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RankingModes.RANKED_SCORE:
|
||||||
|
if (this.rankedScore != userScoreDB.ranked_score)
|
||||||
|
userScoreUpdate = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RankingModes.AVG_ACCURACY:
|
||||||
|
if (this.accuracy != userScoreDB.avg_accuracy)
|
||||||
|
userScoreUpdate = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rankedScore = userScoreDB.ranked_score;
|
||||||
|
this.totalScore = userScoreDB.total_score;
|
||||||
|
this.accuracy = userScoreDB.avg_accuracy;
|
||||||
|
this.playCount = userScoreDB.playcount;
|
||||||
|
|
||||||
|
// Fetch rank
|
||||||
|
for (let i = 0; i < userRankDB.length; i++) {
|
||||||
|
if (userRankDB[i]["user_id"] == this.id) {
|
||||||
|
this.rank = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set PP to none if ranking mode is not PP
|
||||||
|
if (this.rankingMode == 0) this.pp = userScoreDB.pp_raw;
|
||||||
|
else this.pp = 0;
|
||||||
|
|
||||||
|
if (userScoreUpdate || forceUpdate) {
|
||||||
|
StatusUpdate(this, this.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
server/objects/UserArray.ts
Normal file
26
server/objects/UserArray.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { FunkyArray } from "./FunkyArray";
|
||||||
|
import { User } from "./User";
|
||||||
|
|
||||||
|
export class UserArray extends FunkyArray<User> {
|
||||||
|
public getById(id:number) : User | undefined {
|
||||||
|
for (let user of this.getIterableItems()) {
|
||||||
|
if (user.id == id)
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getByUsername(username:string) : User | undefined {
|
||||||
|
for (let user of this.getIterableItems()) {
|
||||||
|
if (user.username === username)
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getByToken(token:string) : User | undefined {
|
||||||
|
return this.getByKey(token);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
"rootDir": "./",
|
"rootDir": "./",
|
||||||
"outDir": "./build",
|
"outDir": "./build",
|
||||||
"strict": true
|
"strict": true
|
||||||
|
|
39
web/chatPageTemplate.html
Normal file
39
web/chatPageTemplate.html
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 482px;
|
||||||
|
height: 165px;
|
||||||
|
}
|
||||||
|
|
||||||
|
hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
padding: 2px;
|
||||||
|
font-size: 8pt;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line0 {
|
||||||
|
background-color: #edebfa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line1 {
|
||||||
|
background-color: #e3e1fa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
|content|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
19
web/serverPage.html
Normal file
19
web/serverPage.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Binato</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<pre style="border-style:double;border-width:4px;width:376px;">
|
||||||
|
|
||||||
|
. o ..
|
||||||
|
o . o o.o
|
||||||
|
...oo
|
||||||
|
__[]__ <b>Binato</b>
|
||||||
|
__|_o_o_o\__ A custom osu!Bancho
|
||||||
|
\""""""""""/
|
||||||
|
\. .. . / <a href="https://binato.eusv.ml">Website</a> | <a href="https://github.com/tgpethan/Binato">Github</a>
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue