beatrun-courses-server/routes/api.js
2024-08-29 09:48:27 +05:00

379 lines
15 KiB
JavaScript

const express = require("express"),
router = express.Router(),
fs = require("fs"),
openGraphScraper = require("open-graph-scraper"),
lzma = require("lzma");
const { isAdmin, isUser, isUserGame, generateCode, isCourseFileValid, log } = require("../utils/functions"),
{ formidable } = require("formidable");
router.post("/", isUser, async (req, res) => {
res.send("Hello World!");
});
router.get("/", isUser, async (req, res) => {
res.send("Hello World!");
});
router.get("/download", isUserGame, async (req, res) => {
const { headers } = req;
if (!headers.code) return res.status(401).json({ res: res.statusCode, message: "No code provided. Please provide a valid course code." });
if (!headers.map) return res.status(401).json({ res: res.statusCode, message: "No map provided. Please provide a valid map." });
const ip = headers["cf-connecting-ip"] || "Unknown";
const key = headers.authorization;
const keys = await req.app.locals.db.getData("/keys");
const steamIds = Object.fromEntries(Object.entries(keys).map(([k, v]) => [v, k]));
const steamid = steamIds[key];
if (ip !== "Unknown" && (await req.app.locals.isRatelimited(ip))) return res.status(401).json({ res: res.statusCode, message: "Too many requests. Please try again later." });
if (ip !== "Unknown" && (await req.app.locals.isMultiAccount(ip, steamid))) return res.status(401).json({ res: res.statusCode, message: "Your account was detected as multiaccount. Please open a ticket on our Discord server." });
let courseData;
try {
courseData = await req.app.locals.db.getData(`/courses/${headers.code.toUpperCase()}`);
} catch (e) {
return res.status(401).json({ res: res.statusCode, message: "Invalid course code provided." });
}
if (courseData.map !== headers.map) return res.status(401).json({ res: res.statusCode, message: "Invalid map. Please start a map that require by course you provided." });
const file = fs.readFileSync(`public/${courseData.path}`, "utf-8");
await log(
`[DOWNLOAD] Served a course for user (Course: ${headers.code.toUpperCase()}, SteamID: ${steamid}, Key ${key}).`,
`[DOWNLOAD] Served a course for user (Course: \`${headers.code.toUpperCase()}\`, SteamID: \`${steamid}\`, Key \`${key}\`).`,
);
if (!courseData.plays) courseData.plays = 1;
else courseData.plays++;
await req.app.locals.db.push(`/courses/${headers.code.toUpperCase()}`, courseData);
res.send({ res: res.statusCode, file: file });
});
router.post("/upload", isUserGame, async (req, res) => {
const { headers } = req;
if (!headers.course) return res.status(401).json({ res: res.statusCode, message: "No course provided. Please provide a valid course." });
if (!headers.map) return res.status(401).json({ res: res.statusCode, message: "No map provided. Please provide a valid map." });
if (headers.mapid === null || headers.mapid === undefined) return res.status(401).json({ res: res.statusCode, message: "No map id provided. Please provide a valid map id." });
const ip = headers["cf-connecting-ip"] || "Unknown";
const key = headers.authorization;
const keys = await req.app.locals.db.getData("/keys");
const steamIds = Object.fromEntries(Object.entries(keys).map(([k, v]) => [v, k]));
const steamid = steamIds[key];
if (ip !== "Unknown" && (await req.app.locals.isRatelimited(ip))) return res.status(401).json({ res: res.statusCode, message: "Too many requests. Please try again later." });
if (ip !== "Unknown" && (await req.app.locals.isMultiAccount(ip, steamid))) return res.status(401).json({ res: res.statusCode, message: "Your account was detected as multiaccount. Please open a ticket on our Discord server." });
let course = "";
try {
course = lzma.decompress(Buffer.from(headers.course, "base64"));
} catch (e) {
course = Buffer.from(headers.course, "base64").toString("utf-8");
}
if (!isCourseFileValid(JSON.parse(course))) return res.status(401).json({ res: res.statusCode, message: "Invalid course file. Please provide a valid course." });
let code = generateCode();
let file = `public/courses/${code}.txt`;
do {
code = generateCode();
file = `public/courses/${code}.txt`;
} while (fs.existsSync(file));
fs.writeFileSync(file, course, "utf-8");
const mapImage = headers.mapid === "0" || headers.mapid === "no_map_id" ? "" : await openGraphScraper({ url: `https://steamcommunity.com/sharedfiles/filedetails/?id=${headers.mapid}` }).then(data => data.result.ogImage[0].url);
await req.app.locals.db.push("/courses", {
[code]: {
map: headers.map,
uploader: {
authkey: key,
userid: steamIds[key],
},
time: Date.now(),
path: `courses/${code}.txt`,
mapid: headers.mapid === "0" || headers.mapid === "no_map_id" ? "" : headers.mapid,
mapimg: mapImage,
plays: 0,
},
}, false);
await log(
`[UPLOAD] User uploaded a course from the game (Course: ${code}, SteamID: ${steamIds[key]}, Key ${key}).`,
`[UPLOAD] User uploaded a course from the game (Course: \`${code}\`, SteamID: \`${steamIds[key]}\`, Key \`${key}\`).`,
);
res.send({ res: res.statusCode, code: code });
});
router.post("/upload_site", isUser, async (req, res) => {
const { headers, user } = req;
const ip = headers["cf-connecting-ip"] || "Unknown";
if (ip !== "Unknown" && (await req.app.locals.isRatelimited(ip))) return res.status(401).json({ res: res.statusCode, message: "Too many requests. Please try again later." });
if (ip !== "Unknown" && (await req.app.locals.isMultiAccount(ip, user.steamid))) return res.status(401).json({ res: res.statusCode, message: "Your account was detected as multiaccount. Please open a ticket on our Discord server." });
const form = formidable({ maxFileSize: 10 * 1024 * 1024 });
form.parse(req, async (err, fields, files) => {
if (err) return res.send("Error while uploading file. Please contact the administrator.");
const uploaded = fs.readFileSync(files.file.filepath);
let course = "";
try {
course = lzma.decompress(uploaded);
} catch (e) {
course = uploaded;
}
if (!isCourseFileValid(JSON.parse(course))) return res.status(401).json({ res: res.statusCode, message: "Invalid course file. Please provide a valid course." });
let code = generateCode();
let file = `public/courses/${code}.txt`;
do {
code = generateCode();
file = `public/courses/${code}.txt`;
} while (fs.existsSync(file));
fs.writeFileSync(file, course);
const mapid = fields.link.match(/id=(\d+)/)[1];
const mapImage = await openGraphScraper({ url: `https://steamcommunity.com/sharedfiles/filedetails/?id=${mapid}` }).then(data => data.result.ogImage[0].url);
await req.app.locals.db.push("/courses", {
[code]: {
map: fields.map,
uploader: {
authkey: user.authKey,
userid: user.steamid,
},
time: Date.now(),
path: `courses/${code}.txt`,
mapid: mapid,
mapimg: mapImage,
plays: 0,
},
}, false);
await log(
`[UPLOAD] User uploaded a course from the site (Course: ${code}, SteamID: ${user.steamid}, Key ${user.authKey}).`,
`[UPLOAD] User uploaded a course from the site (Course: \`${code}\`, SteamID: \`${user.steamid}\`, Key \`${user.authKey}\`).`,
);
res.send({ res: res.statusCode, code: code });
});
});
router.post("/update", isUserGame, async (req, res) => {
const { headers } = req;
if (!headers.course) return res.status(401).json({ res: res.statusCode, message: "No course provided. Please provide a valid course." });
if (!headers.map) return res.status(401).json({ res: res.statusCode, message: "No map provided. Please provide a valid map." });
if (!headers.code) return res.status(401).json({ res: res.statusCode, message: "No code provided. Please provide a valid course code." });
const ip = headers["cf-connecting-ip"] || "Unknown";
const key = headers.authorization;
const keys = await req.app.locals.db.getData("/keys");
const steamIds = Object.fromEntries(Object.entries(keys).map(([k, v]) => [v, k]));
const steamid = steamIds[key];
if (ip !== "Unknown" && (await req.app.locals.isRatelimited(ip))) return res.status(401).json({ message: "Too many requests. Please try again later." });
if (ip !== "Unknown" && (await req.app.locals.isMultiAccount(ip, steamid))) return res.status(401).json({ message: "Your account was detected as multiaccount. Please open a ticket on our Discord server." });
const courseData = await req.app.locals.db.getData(`/courses/${headers.code.toUpperCase()}`);
if (courseData.map !== headers.map) return res.status(401).json({ res: res.statusCode, message: "Invalid map. You should provide the same map as before." });
if (courseData.uploader.userid !== steamIds[key]) return res.status(401).json({ res: res.statusCode, message: "Invalid key. You are not the uploader of this course. Only the uploader can update their course." });
let course = "";
try {
course = lzma.decompress(Buffer.from(headers.course, "base64"));
} catch (e) {
course = Buffer.from(headers.course, "base64").toString("utf-8");
}
if (!isCourseFileValid(JSON.parse(course))) return res.status(401).json({ res: res.statusCode, message: "Invalid course file. Please provide a valid course." });
fs.writeFileSync(`public/courses/${headers.code.toUpperCase()}.txt`, course, "utf-8");
courseData.time = Date.now();
await req.app.locals.db.push(`/courses/${headers.code.toUpperCase()}`, courseData);
await log(
`[UPDATE] User updated a course (Course: ${headers.code.toUpperCase()}, SteamID: ${steamIds[key]}, Key ${key}).`,
`[UPDATE] User updated a course (Course: \`${headers.code.toUpperCase()}\`, SteamID: \`${steamIds[key]}\`, Key \`${key}\`).`,
);
res.send({ res: res.statusCode, code: headers.code.toUpperCase() });
});
router.post("/rate", isUser, async (req, res) => {
const { action, code, steamid } = req.body;
if (action === "like") {
const ratings = await req.app.locals.db.getData("/rating");
ratings[code][steamid] = true;
await req.app.locals.db.push("/rating", ratings);
res.send({ success: true, code: code, likes: Object.values(ratings[code]).filter(x => x === true).length });
} else if (action === "dislike") {
const ratings = await req.app.locals.db.getData("/rating");
ratings[code][steamid] = false;
await req.app.locals.db.push("/rating", ratings);
res.send({ success: true, code: code, dislikes: Object.values(ratings[code]).filter(x => x === false).length });
} else return res.status(401).json({ res: res.statusCode, message: "Invalid action provided." });
});
router.post("/admin", isAdmin, async (req, res) => {
const { user } = req,
{ action, target } = req.body.args;
if (action === "addKey") {
if (!target) return res.send({ success: false, message: "Target not provided. Please provide a target." });
const key = await req.app.locals.getKey(target);
if (!key) return res.send({ success: false, message: "Internal error. Contact the developer." });
await log(
`[ADMIN] Added new user (Admin: ${user.steamid}, SteamID: ${target}, Key ${key}).`,
`[ADMIN] Added new user (Admin: \`${user.steamid}\`, SteamID: \`${target}\`, Key \`${key}\`).`,
);
res.send({ success: true, message: `Key added successfully.\n${key}` });
} else if (action === "removeKey") {
if (!target) return res.send({ success: false, message: "Target not provided. Please provide a target." });
const keys = await req.app.locals.db.getData("/keys");
if (!keys[target]) return res.send({ success: false, message: "Invalid SteamID provided." });
delete keys[target];
await req.app.locals.db.push("/keys", keys);
await log(
`[ADMIN] Removed a user (Admin: ${user.steamid}, SteamID: ${target}).`,
`[ADMIN] Removed a user (Admin: \`${user.steamid}\`, SteamID: \`${target}\`).`,
);
res.send({ success: true, message: `Key removed successfully.\n${target.toUpperCase()}` });
} else if (action === "lockUser") {
if (!target) return res.send({ success: false, message: "Target not provided. Please provide a target." });
const locked = await req.app.locals.db.getData("/locked");
if (locked[target]) return res.send({ success: false, message: "User is already locked." });
locked[target] = true;
await req.app.locals.db.push("/locked", locked);
await log(
`[ADMIN] Locked a user (Admin: ${user.steamid}, SteamID: ${target}).`,
`[ADMIN] Locked a user (Admin: \`${user.steamid}\`, SteamID: \`${target}\`).`,
);
res.send({ success: true, message: `User is now locked.\n${target}` });
} else if (action === "unlockUser") {
if (!target) return res.send({ success: false, message: "Target not provided. Please provide a target." });
const locked = await req.app.locals.db.getData("/locked");
if (!locked[target]) return res.send({ success: false, message: "User is not locked." });
delete locked[target];
await req.app.locals.db.push("/locked", locked);
await log(
`[ADMIN] Unlocked a user (Admin: ${user.steamid}, SteamID: ${target}).`,
`[ADMIN] Unlocked a user (Admin: \`${user.steamid}\`, SteamID: \`${target}\`).`,
);
res.send({ success: true, message: `User is now unlocked.\n${target}` });
} else if (action === "removeCourse") {
if (!target) return res.send({ success: false, message: "Target not provided. Please provide a target." });
const courses = await req.app.locals.db.getData("/courses");
if (!courses[target.toUpperCase()]) return res.send({ success: false, message: "Invalid course code provided." });
delete courses[target.toUpperCase()];
fs.unlinkSync(`public/courses/${target.toUpperCase()}.txt`);
await req.app.locals.db.push("/courses", courses);
await log(
`[ADMIN] Remove a course (Admin: ${user.steamid}, Course: ${target.toUpperCase()}).`,
`[ADMIN] Remove a course (Admin: \`${user.steamid}\`, Course: \`${target.toUpperCase()}\`).`,
);
res.send({ success: true, message: `Course removed successfully.\n${target.toUpperCase()}` });
} else if (action === "showLogs") {
const file = fs.readFileSync("data/logs.log", "utf-8");
if (!file) return res.send({ success: false, message: "No logs found." });
res.send({ success: true, message: file });
} else if (action === "showRecords") {
const records = await req.app.locals.db.getData("/records");
res.send({ success: true, message: JSON.stringify(records) });
} else if (action === "showLocks") {
const locked = await req.app.locals.db.getData("/locked");
const message = Object.keys(locked).length === 0
? JSON.stringify(locked)
: Object.keys(locked).map(x => `${x} - ${locked[x]}`).join("\n");
res.send({ success: true, message: message });
} else return res.send({ success: false, message: "Invalid action provided." });
});
router.get("/admin", isAdmin, async (req, res) => {
res.send("Hello get");
});
router.get("/info", async (req, res) => {
const courses = await req.app.locals.db.getData("/courses");
const usernames = await req.app.locals.db.getData("/usernames");
// eslint-disable-next-line no-unused-vars
for (const [code, data] of Object.entries(courses)) {
delete data.uploader.authkey;
data.uploader.name = usernames[data.uploader.userid];
}
res.send({ res: res.statusCode, data: courses });
});
router.get("/info/:code", async (req, res) => {
let course;
try {
course = await req.app.locals.db.getData(`/courses/${req.params.code.toUpperCase()}`);
} catch (e) {
return res.status(401).json({ res: res.statusCode, message: "Invalid course code provided." });
}
const usernames = await req.app.locals.db.getData("/usernames");
delete course.uploader.authkey;
course.uploader.name = usernames[course.uploader.userid];
res.send({ res: res.statusCode, data: course });
});
module.exports = router;