380 lines
15 KiB
JavaScript
380 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;
|