496 lines
16 KiB
JavaScript
Executable File
496 lines
16 KiB
JavaScript
Executable File
const io = require('@pm2/io')
|
|
import { log } from "./log.js";
|
|
import bodyParser from "body-parser";
|
|
import express from "express";
|
|
import cookieParser from "cookie-parser";
|
|
import fs from "node:fs/promises";
|
|
import { fileURLToPath } from "url";
|
|
import path, { dirname } from "node:path";
|
|
import mime from "mime-types";
|
|
import compression from "compression";
|
|
import { accs, infdb, polytrack } from "./database.js";
|
|
import { } from "./accounts/friend.js";
|
|
import { banUser, removeAccount, verifyCookie, getUserFromCookie, createAccount, resetPassword, loginAccount, addBadge } from "./accounts/manage.js";
|
|
import { } from "./accounts/misc.js";
|
|
import { getRawData, generateAccountPage, editProfile, saveData, getUsers, isAdmin, retrieveData } from "./accounts/profile.js";
|
|
// import { infiniteCraft, chatBot } from "./ai.js";
|
|
import os from "node:os";
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
|
|
const port = process.env.PORT || 3000;
|
|
|
|
const app = express();
|
|
app.use(compression());
|
|
app.use(cookieParser());
|
|
app.use(express.json({ limit: "10mb" }));
|
|
app.use(express.urlencoded({ extended: false }));
|
|
app.use(express.text());
|
|
let requests = 0;
|
|
const requestsPerSec = io.meter({
|
|
name: 'req/sec',
|
|
id: 'app/requests/sec'
|
|
});
|
|
const requestsPerMin = io.meter({
|
|
name: 'req/min',
|
|
id: 'app/requests/min'
|
|
});
|
|
app.use("/", (req, res, next) => {
|
|
requestsPerSec.mark();
|
|
requestsPerMin.mark();
|
|
next();
|
|
});
|
|
const sockets = io.metric({
|
|
name: 'Open Websockets',
|
|
id: 'app/requests/sockets',
|
|
});
|
|
// setInterval(()=>{
|
|
|
|
// }, 1000)
|
|
import WebSocket, { WebSocketServer } from "ws";
|
|
import { request } from "node:http";
|
|
const wss = new WebSocketServer({ noServer: true });
|
|
let openSockets = 0;
|
|
wss.on("connection", function connection(ws, req, res) {
|
|
openSockets++;
|
|
sockets.set(openSockets);
|
|
setInterval(() => {
|
|
ws.send("ping");
|
|
}, 30000);
|
|
|
|
ws.on("error", console.error);
|
|
|
|
ws.on("message", async function message(data, isBinary) {
|
|
let message = Buffer.from(data).toString();
|
|
if (message.startsWith(process.env.ANNOUNCEMENT_KEY)) {
|
|
wss.clients.forEach(function each(client) {
|
|
if (client.readyState === WebSocket.OPEN) {
|
|
client.send(message.replace(process.env.ANNOUNCEMENT_KEY, "announce."));
|
|
}
|
|
});
|
|
} else if (message.startsWith("token") && (await verifyCookie(message.substring(6)))) {
|
|
ws.id = await getUserFromCookie(message.substring(6));
|
|
ws.send(ws.id);
|
|
const updateAccount = accs.query(`UPDATE accounts SET last_login = $login WHERE username = $user`)
|
|
updateAccount.get({ $login: new Date().toUTCString(), $user: ws.id });
|
|
} else if (message.startsWith("pong")) {
|
|
if (ws.id) {
|
|
const updateAccount = accs.query(`UPDATE accounts SET last_login = $login WHERE username = $user`)
|
|
updateAccount.get({ $login: new Date().toUTCString(), $user: ws.id });
|
|
if (message.substring(4)) {
|
|
const existingAccount = accs.query(`SELECT * FROM accounts WHERE username LIKE $1`)
|
|
let userData = existingAccount.get({ $1: ws.id });
|
|
if (userData == null) {
|
|
return { success: false, reason: "The account doesn't exist." };
|
|
}
|
|
let games;
|
|
if (userData.playedgames) {
|
|
games = JSON.parse(userData.playedgames);
|
|
} else {
|
|
games = {};
|
|
}
|
|
if (games[message.substring(4)]) {
|
|
games[message.substring(4)] += 30;
|
|
} else {
|
|
games[message.substring(4)] = 30;
|
|
}
|
|
const updateAccount = accs.query(`UPDATE accounts SET playedgames = $playedgames WHERE username = $user`)
|
|
updateAccount.get({ $playedgames: JSON.stringify(games), $user: ws.id });
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
ws.on("close", () => {openSockets--;
|
|
sockets.set(openSockets);});
|
|
});
|
|
app.post(
|
|
"/api/event",
|
|
(req, res) => {
|
|
fetch("https://analytics.skysthelimit.dev/api/event", {
|
|
method: "post",
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json'
|
|
},
|
|
|
|
body: req.body
|
|
}).then(async (response) => {
|
|
res.send(response.status);
|
|
})
|
|
}
|
|
);
|
|
// app.use("*.json", async (req, res, next) => {
|
|
// optimize json
|
|
// console.log("got data");
|
|
// next()
|
|
// });
|
|
app.post("/register", async (req, res) => {
|
|
let status = await createAccount(req.body.username, req.body.password, req.body["h-captcha-response"]);
|
|
if (status["success"]) {
|
|
res.status(200).send(status);
|
|
} else {
|
|
res.status(400).send(status);
|
|
}
|
|
});
|
|
|
|
app.post("/login", async (req, res) => {
|
|
let status = await loginAccount(req.body.username, req.body.password, req.body["h-captcha-response"]);
|
|
if (status["success"]) {
|
|
res.status(200).send(status);
|
|
} else {
|
|
res.status(400).send(status);
|
|
}
|
|
});
|
|
app.use(["/register", "/login"], async (req, res, next) => {
|
|
if (req.cookies.token && (await verifyCookie(req.cookies.token))) {
|
|
res.redirect("/u/");
|
|
} else {
|
|
res
|
|
.type("text/html")
|
|
.status(200)
|
|
.send(await fs.readFile(`./html${req.baseUrl}.html`));
|
|
}
|
|
});
|
|
app.use("/users", async (req, res, next) => {
|
|
res
|
|
.type("text/html")
|
|
.status(200)
|
|
.send(await fs.readFile(`./html/users.html`));
|
|
});
|
|
app.use("/reset", async (req, res, next) => {
|
|
res
|
|
.type("text/html")
|
|
.status(200)
|
|
.send(await fs.readFile(`./html/reset.html`));
|
|
});
|
|
app.post("/api/account/upload", async (req, res, next) => {
|
|
if (req.cookies.token && (await verifyCookie(req.cookies.token))) {
|
|
let status = await saveData(req.cookies.token, req.body.data);
|
|
if (status["success"]) {
|
|
res.status(200).send(status);
|
|
} else {
|
|
res.status(400).send(status);
|
|
}
|
|
} else {
|
|
return "KILL YOURSELF";
|
|
}
|
|
});
|
|
|
|
// ai endpoints
|
|
app.post("/api/ai/createChat", async (req, res) => {
|
|
// create chat in database and store some metadata about it
|
|
// ie: last model, time created, etc etc
|
|
})
|
|
app.post("/api/ai/sendMessage", async (req, res) => {
|
|
// take in chat id and message
|
|
// stream back reply
|
|
})
|
|
app.get("/api/ai/messages", async (req, res) => {
|
|
// take in chat id
|
|
// use unique uuid to store every chat
|
|
// return messages
|
|
})
|
|
// friends endpoints
|
|
app.get("/api/friends/list", async (req, res) => {
|
|
// use auth token to get user
|
|
// just return array of user infos
|
|
})
|
|
app.get("/api/friends/requests", async (req, res) => {
|
|
// view friend requests
|
|
})
|
|
app.post("/api/friends/sendRequest", async (req, res) => {
|
|
// takes in user
|
|
// send friend request
|
|
})
|
|
app.post("/api/friends/reply", async (req, res) => {
|
|
// accepted? true or false
|
|
})
|
|
// chat endpoints
|
|
app.post("/api/chat/create", async (req, res) => {
|
|
// create a new chat
|
|
// create one for new friends maybe?
|
|
// generates metadata
|
|
// time created, last message sent, people in the chat, id, owner
|
|
})
|
|
app.post("/api/chat/send", async (req, res) => {
|
|
// send chat message
|
|
// id, auth, message
|
|
})
|
|
app.post("/api/chat/addUser", async (req, res) => {
|
|
// add user to chat
|
|
})
|
|
app.post("/api/chat/removeUser", async (req, res) => {
|
|
// remove user from chat
|
|
})
|
|
app.get("/api/chat/recent", async (req, res) => {
|
|
// get last 50 recent messages
|
|
// offset by a page param
|
|
})
|
|
app.get("/api/infinite/get", async (req, res, next) => {
|
|
if (req.query[1] && req.query[2]) {
|
|
let success = false;
|
|
let data;
|
|
try {
|
|
let search1Query = infdb.query(`SELECT * FROM caches WHERE 1 = $one AND 2 = $two`)
|
|
let search1 = await search1Query.get({ $one: req.query[1], $two: req.query[2] });
|
|
console.log(search1);
|
|
if (search1 && search1.length > 0) {
|
|
data = { item: search1[0].result_item, emoji: search1[0].result_emoji, new: false };
|
|
success = true;
|
|
} else {
|
|
let search2Query = infdb.query(`SELECT * FROM caches WHERE 1 = $two AND 2 = $one`)
|
|
let search2 = await search2Query.get({ $one: req.query[1], $two: req.query[2] });
|
|
console.log(search2);
|
|
if (search2 && search2.length > 0) {
|
|
data = { item: search2[0].result_item, emoji: search2[0].result_emoji, new: false };
|
|
success = true;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
if (success) {
|
|
console.log("success");
|
|
res.send(data);
|
|
return;
|
|
}
|
|
data = await infiniteCraft(req.query[1], req.query[2]);
|
|
try {
|
|
let parse = JSON.parse(data);
|
|
let keys = Object.keys(parse);
|
|
if (keys.indexOf("item") > -1 && keys.indexOf("emoji") > -1) {
|
|
parse.new = true;
|
|
data = parse;
|
|
const createCached = infdb.query(`INSERT INTO caches (1, 2, result_item, result_emoji) VALUES ($one, $two, $item, $emoji)`)
|
|
createCached.run({ $one: req.query[1], $two: req.query[2], $item: data.item, $emoji: data.emoji });
|
|
res.send(data);
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
data = { item: "N/A", emoji: "N/A" };
|
|
res.send(data);
|
|
}
|
|
}
|
|
});
|
|
app.use("/semag/polytrack/data/", async (req, res, next) => {
|
|
let path = req.path.substring(1, req.path.length);
|
|
if(path == "user") {
|
|
res.sendStatus(200);
|
|
} else if(path == "leaderboard") {
|
|
let data = {};
|
|
|
|
if(req.method == "POST") {
|
|
req.body.split("&").forEach((item) => {
|
|
data[item.split("=")[0]] = item.split("=")[1]
|
|
});
|
|
console.log(data);
|
|
const getExistingRuns = polytrack.query(`SELECT * FROM polytrack WHERE userid = $usrid AND trackid = $trackid`);
|
|
let existingRuns = getExistingRuns.all({ $usrid: data["userToken"], $trackid: data["trackId"] });
|
|
let saveRun = true;
|
|
if(existingRuns !== null) {
|
|
existingRuns.forEach((item) => {
|
|
if(saveRun) {
|
|
if(data.frames > item.frames) {
|
|
saveRun = false;
|
|
} else {
|
|
let deleteRun = polytrack.query(`DELETE FROM polytrack WHERE id = $id`);
|
|
deleteRun.run({ $id: item.id })
|
|
}
|
|
}
|
|
})
|
|
}
|
|
if(saveRun) {
|
|
const addRun = polytrack.query(`INSERT INTO polytrack (trackid, username, colors, recording, frames, userid) VALUES ($id, $usr, $clr, $record, $frames, $usrid)`)
|
|
let runData = addRun.run({ $id: data["trackId"], $usr: data["name"], $clr: data["carColors"], $record: data["recording"], $usrid: data["userToken"], $frames: data["frames"] });
|
|
console.log("run", runData);
|
|
res.send(runData.lastInsertRowid);
|
|
}
|
|
} else {
|
|
let leaderboard = polytrack.query(`SELECT * FROM polytrack WHERE trackid = $id LIMIT $limit OFFSET $offset`).all({ $id: req.query.trackId, $limit: req.query.amount, $offset: req.query.skip })
|
|
console.log(leaderboard);
|
|
let returnValue = {"total": leaderboard.length, "entries":[]}
|
|
for(let i = 0; i<leaderboard.length;i++) {
|
|
returnValue["entries"][i] = {};
|
|
returnValue["entries"][i]["id"] = leaderboard[i]["id"];
|
|
returnValue["entries"][i]["name"] = decodeURIComponent(leaderboard[i]["username"]);
|
|
returnValue["entries"][i]["carColors"] = leaderboard[i]["colors"];
|
|
returnValue["entries"][i]["frames"] = leaderboard[i]["frames"];
|
|
returnValue["entries"][i]["verifiedState"] = true;
|
|
returnValue["entries"][i]["isSelf"] = false;
|
|
}
|
|
res.send(returnValue);
|
|
}
|
|
} else if(path == "recording") {
|
|
let recordingQuery = polytrack.query(`SELECT * FROM polytrack WHERE id = $id`).get({ $id: req.query.recordingId });
|
|
res.send({
|
|
"recording": recordingQuery.recording,
|
|
"frames": recordingQuery.frames,
|
|
"verifiedState": true,
|
|
"carColors": recordingQuery.colors
|
|
});
|
|
}
|
|
})
|
|
app.use("/api/account/load", async (req, res, next) => {
|
|
if (req.cookies.token && (await verifyCookie(req.cookies.token))) {
|
|
let status = await retrieveData(req.cookies.token);
|
|
if (status["success"]) {
|
|
res.status(200).send(status);
|
|
} else {
|
|
res.status(400).send(status);
|
|
}
|
|
} else {
|
|
res.status(200).send("No token");
|
|
}
|
|
});
|
|
|
|
app.use("/api/getUsers", async (req, res, next) => {
|
|
console.log(req.query)
|
|
let status = await getUsers(req.query.page, req.query.query);
|
|
res.status(200).send(status);
|
|
});
|
|
|
|
app.use("/admin", async (req, res, next) => {
|
|
if ((await isAdmin(req.cookies.token)) && (await verifyCookie(req.cookies.token))) {
|
|
res
|
|
.type("text/html")
|
|
.status(200)
|
|
.send(await fs.readFile(`./html/admin.html`));
|
|
} else {
|
|
next();
|
|
}
|
|
});
|
|
app.use("/api/stats", async (req, res, next) => {
|
|
if ((await isAdmin(req.cookies.token)) && (await verifyCookie(req.cookies.token))) {
|
|
res
|
|
.type("text/json")
|
|
.status(200)
|
|
.send({
|
|
"users": accs.query(`SELECT COUNT(*) FROM accounts`).get()["COUNT(*)"],
|
|
// "cpu": os.cpus(),
|
|
"ram": `${(os.totalmem()-os.freemem())/1000000000}GB / ${os.totalmem()/1000000000}GB`,
|
|
"cpuUsage": os.loadavg(),
|
|
"openWebSockets": openSockets,
|
|
"uptime": `${os.uptime()}s`
|
|
});
|
|
} else {
|
|
next();
|
|
}
|
|
});
|
|
app.use("/ai", async (req, res, next) => {
|
|
if (await verifyCookie(req.cookies.token) && isAdmin(req.cookies.token)) {
|
|
res
|
|
.type("text/html")
|
|
.status(200)
|
|
.send(await fs.readFile(`./html/ai.html`));
|
|
} else {
|
|
next();
|
|
}
|
|
});
|
|
app.use("/", express.static("./selenite", { extensions: ["html"] }));
|
|
app.use("/data/:id/:file", async (req, res) => {
|
|
const id = path.basename(req.params.id);
|
|
const file = path.basename(req.params.file);
|
|
|
|
const filePath = path.join(process.env.DATA_PATH, "data", id, file);
|
|
try {
|
|
await fs.access(filePath);
|
|
|
|
const image = await fs.readFile(filePath);
|
|
if (mime.lookup(filePath) == "image/webp") {
|
|
res.type("image/webp");
|
|
res.status(200).send(image);
|
|
} else {
|
|
res.status(404).send("File not found");
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.status(404).send("File not found");
|
|
}
|
|
});
|
|
|
|
app.use("/u/raw", async (req, res) => {
|
|
if (req.cookies.token && (await verifyCookie(req.cookies.token))) {
|
|
res.send(await getRawData(req.cookies.token));
|
|
} else {
|
|
res.redirect("/register");
|
|
}
|
|
});
|
|
app.use("/u/:username/edit", async (req, res, next) => {
|
|
if (await isAdmin(req.cookies.token)) {
|
|
res.send(await generateAccountPage(req.params.username, req.cookies.token, true));
|
|
return;
|
|
}
|
|
next();
|
|
});
|
|
app.use("/u/:username", async (req, res) => {
|
|
if (["skysthelimit.dev", "selenite.cc", "selenite", "owner"].includes(req.params.username)) {
|
|
res.redirect("/u/sky");
|
|
return;
|
|
}
|
|
res.send(await generateAccountPage(req.params.username, req.cookies.token));
|
|
});
|
|
|
|
app.use("/u/", async (req, res) => {
|
|
if (req.cookies.token && (await verifyCookie(req.cookies.token))) {
|
|
res.send(await generateAccountPage(req.params.username, req.cookies.token));
|
|
} else {
|
|
res.redirect("/register");
|
|
}
|
|
});
|
|
|
|
app.post("/api/account/reset", async (req, res) => {
|
|
let status = await resetPassword(req.body.username, req.body.key, req.body.password, req.body["h-captcha-response"]);
|
|
if (status["success"]) {
|
|
res.status(200).send(status);
|
|
} else {
|
|
res.status(400).send(status);
|
|
}
|
|
});
|
|
|
|
app.post("/api/profile/edit", async (req, res) => {
|
|
let status = await editProfile(req.body, req.cookies.token, false);
|
|
if (status["success"]) {
|
|
res.status(200).send(status);
|
|
} else {
|
|
res.status(400).send(status);
|
|
}
|
|
});
|
|
|
|
app.post("/api/admin/badge", async (req, res) => {
|
|
let status = await addBadge(req.body.username, req.body.badge, req.cookies.token);
|
|
if (status["success"]) {
|
|
res.status(200).send(status);
|
|
} else {
|
|
res.status(400).send(status);
|
|
}
|
|
});
|
|
app.post("/api/admin/removeAcc", async (req, res) => {
|
|
let status = await removeAccount(req.body.username, req.cookies.token);
|
|
res.status(200).send(status);
|
|
});
|
|
app.post("/api/admin/removeAcc", async (req, res) => {
|
|
let status = await editProfile(req.body, req.cookies.token, true);
|
|
res.status(200).send(status);
|
|
});
|
|
app.post("/api/admin/ban", async (req, res) => {
|
|
let status = await banUser(req.body.name, req.body.reason, req.cookies.token);
|
|
res.status(200).send(status);
|
|
});
|
|
const server = app.listen(port, () => {
|
|
console.log(log.success("Express is online."));
|
|
console.log("- " + log.info("http://localhost:" + port));
|
|
});
|
|
server.on("upgrade", (request, socket, head) => {
|
|
wss.handleUpgrade(request, socket, head, (socket) => {
|
|
wss.emit("connection", socket, request);
|
|
});
|
|
});
|
|
|
|
app.use(async (req, res) => {
|
|
res
|
|
.type("text/html")
|
|
.send(await fs.readFile(`./selenite/404.html`))
|
|
.status(404);
|
|
});
|