Files
backend/index.js
2025-06-22 11:41:21 -04:00

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);
});