import fs from "bun:fs";
import crypto from "node:crypto";
import sanitizeHtml from "sanitize-html";
import sharp from "sharp";
import { accs } from "../database.js";
import { getUserFromCookie, isBanned, decryptCookie, verifyCookie } from "./manage.js";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime.js";
dayjs.extend(relativeTime);
const sanitizeConfig = {
allowedTags: ["b", "i", "em", "strong", "a"],
allowedAttributes: {
a: ["href"],
},
disallowedTagsMode: "escape",
};
const sanitizeConfigNoLink = {
allowedTags: ["b", "i", "em", "strong"],
disallowedTagsMode: "escape",
};
const allowNone = {
allowedTags: [],
allowedAttributes: {},
disallowedTagsMode: "discard",
};
let badge = {
dev: "An official Selenite developer.",
donate: "A Selenite donator.",
mod: "An official Selenite moderator.",
owner: "The owner of Selenite (/u/sky)",
};
let rawProfileHTML = fs.readFileSync("./html/profile.html").toString();
let rawEditProfileHTML = fs.readFileSync("./html/profile_edit.html").toString();
let profile404 = fs.readFileSync("./html/profile_404.html").toString();
let profileBan = fs.readFileSync("./html/profile_ban.html").toString();
let gamesJSON = JSON.parse(fs.readFileSync("./selenite/data/games.json").toString());
let appsJSON = JSON.parse(fs.readFileSync("./selenite/data/apps.json").toString());
let profileReadyJSON = {};
for (let i = 0; i < gamesJSON.length; i++) {
profileReadyJSON[gamesJSON[i].directory] = { name: gamesJSON[i].name, image: gamesJSON[i].image };
}
for (let i = 0; i < appsJSON.length; i++) {
profileReadyJSON[appsJSON[i].directory] = { name: appsJSON[i].name, image: appsJSON[i].image };
}
let gamesExceptions = { win11: "11", gba: "gba", turbowarp: "turbowarp", scratch1: "scratch1", emulatorjs: "emu" };
async function getRawData(token) {
let name = await getUserFromCookie(token);
const existingAccount = accs.query(`SELECT * FROM accounts WHERE username LIKE $1`)
let userData = existingAccount.get({ $1: name });
if (userData == null) {
return { success: false, reason: "Does not exist" };
}
return {
username: userData.username,
name: userData.name,
about: userData.about,
badges: userData.badges,
pfp: userData.pfp_url,
css: userData.custom_css,
game_time: userData.playedgames,
};
}
async function retrieveData(token) {
let user = JSON.parse(await decryptCookie(token))["n"];
const existingAccount = accs.query(`SELECT * FROM accounts WHERE username LIKE $1`)
let userData = existingAccount.get({ $1: user });
if (userData == null) {
return { success: false, reason: "Does not exist" };
}
let path = `${process.env.DATA_PATH}/data/${userData.id}/save.dat`;
let file = Bun.file(path)
if(!(await file.exists())) {
return { success: false, reason: "No data was found." }
}
try {
let data = await file.text();
const iv = Buffer.from(data.split(".")[0], "base64");
data = data.split(".")[1];
let cipher = crypto.createDecipheriv("aes-256-cbc", process.env.AUTH_KEY, iv);
let decrypted = cipher.update(data, "base64", "utf8");
decrypted += cipher.final("utf8");
return { success: true, data: decrypted };
} catch (err) {
console.error(err);
shitHitTheFan("Failure retrieving data, either database is messed up or something else.");
shitHitTheFan("User info: " + user + ", " + path);
}
return { success: false, reason: "Anonymous error" };
}
async function saveData(token, data) {
if (data["cookies"] && data["localStorage"]) {
let user = JSON.parse(await decryptCookie(token))["n"];
const existingAccount = accs.query(`SELECT * FROM accounts WHERE username LIKE $1`)
let userData = existingAccount.get({ $1: user });
if (userData == null) {
return { success: false, reason: "Does not exist" };
}
let path = `${process.env.DATA_PATH}/data/${userData.id}/save.dat`;
let dir = `${process.env.DATA_PATH}/data/${userData.id}/`;
fs.mkdirSync(dir, { recursive: true });
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv("aes-256-cbc", process.env.AUTH_KEY, iv);
let encrypted = cipher.update(JSON.stringify(data), "utf8", "base64");
encrypted += cipher.final("base64");
try {
Bun.write(path, (iv.toString("base64") + "." + encrypted).replaceAll("=", ""));
return { success: true };
} catch (err) {
console.error(err);
}
}
return { success: false, reason: "Anonymous error" };
}
async function isAdmin(token) {
if (token) {
let user = JSON.parse(await decryptCookie(token))["n"];
let existingAccount = accs.query(`SELECT * FROM accounts WHERE username LIKE $1`)
let userData = existingAccount.get({ $1: user });
if (userData == null) {
return false;
}
return userData.type == "admin";
}
return false;
}
async function editProfile(body, token, admin) {
let userIsAdmin = false;
if(admin) {
userIsAdmin = await isAdmin(token);
}
if (await verifyCookie(token) || userIsAdmin) {
let user = userIsAdmin ? await getUserFromCookie(token) : body.username;
const existingAccount = accs.query(`SELECT * FROM accounts WHERE username LIKE $1`);
let userData = existingAccount.get({ $1: user });
if (userData == null) {
return { success: false, reason: "The account doesn't exists. (if you see this we fucked LMFAOOOO)" };
}
if (body.name) {
if (body.name.length > 20) {
return { success: false, reason: "Length too long." };
}
const updateAccount = accs.query(`UPDATE accounts SET name = $name WHERE username = $user`)
updateAccount.get({ $name: body.name, $user: user });
}
if (body.about) {
if (body.about.length > 200) {
return { success: false, reason: "Length too long." };
}
const updateAccount = accs.query(`UPDATE accounts SET about = $about WHERE username = $user`)
updateAccount.get({ $about: body.about, $user: user });
}
if (body.custom) {
const updateAccount = accs.query(`UPDATE accounts SET custom_css = $css WHERE username = $user`)
updateAccount.get({ $css: body.custom, $user: user });
}
if (body.pfp) {
if(body.pfp == "del") {
const updateAccount = accs.query(`UPDATE accounts SET pfp_url = null WHERE username = $user`)
updateAccount.get({ $name: body.name, $user: user });
return { success: true };
}
const { fileTypeFromBuffer } = await import("file-type");
let base64Data = body.pfp.split(";base64,").pop();
let pfp = Buffer.from(base64Data, "base64");
let fileType = (await fileTypeFromBuffer(pfp))["ext"];
if (["png", "jpg", "gif", "avif", "webp", "tiff"].includes(fileType)) {
let url;
let dir = `${process.env.DATA_PATH}/data/${existingAccount.id}/`;
let uuid = crypto.randomUUID();
let path = `${process.env.DATA_PATH}/data/${existingAccount.id}/${uuid}.webp`;
url = `/data/${existingAccount.id}/${uuid}.webp`;
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path, "");
await sharp(pfp, { animated: fileType == "gif" })
.resize({ width: 300, withoutEnlargement: true })
.webp({ quality: 70, effort: 4 })
.toFile(path);
await fs.unlink(`${__dirname}/${existingAccount.pfp_url}`, () => {});
const updateAccount = accs.query(`UPDATE accounts SET pfp_url = $url WHERE username = $user`)
updateAccount.get({ $url: url, $user: user });
}
}
return { success: true };
}
return { success: false };
}
async function generateAccountPage(name, cookie, admin) {
let userIsAdmin = false;
if(admin) {
userIsAdmin = await isAdmin(cookie);
}
if (name && !admin) {
const existingAccount = accs.query(`SELECT * FROM accounts WHERE username LIKE $1`)
let userData = existingAccount.get({ $1: name });
if (userData == null || (await isBanned(name.toLowerCase()))) {
return profile404;
}
let modifiedHTML = rawProfileHTML;
modifiedHTML = modifiedHTML.replaceAll("{{ name }}", sanitizeHtml(userData.name, allowNone));
modifiedHTML = modifiedHTML.replaceAll("{{ join_date }}", dayjs(userData.createdAt).fromNow());
modifiedHTML = modifiedHTML.replaceAll("{{ about }}", sanitizeHtml(userData.about, sanitizeConfig) || "No about me available..");
modifiedHTML = modifiedHTML.replaceAll("{{ about_none }}", sanitizeHtml(userData.about, allowNone) || "");
modifiedHTML = modifiedHTML.replaceAll("{{ user_pfp }}", userData.pfp_url || "/img/user.svg");
modifiedHTML = modifiedHTML.replaceAll("{{ custom_css }}", userData.custom_css || "");
modifiedHTML = modifiedHTML.replaceAll("{{ online_time }}", dayjs(userData.last_login).fromNow());
modifiedHTML = modifiedHTML.replaceAll("{{ played_games }}", buildGameHTML(userData));
let badges_html = "";
if (userData.badges !== null) {
let badges = JSON.parse(userData.badges);
for (let i = 0; i < badges.length; i++) {
badges_html += ``;
}
}
modifiedHTML = modifiedHTML.replaceAll("{{ badges }}", badges_html);
return modifiedHTML;
} else if (cookie || userIsAdmin) {
name = userIsAdmin ? name : await getUserFromCookie(cookie);
const existingAccount = accs.query(`SELECT * FROM accounts WHERE username LIKE $1`)
let userData = existingAccount.get({ $1: name });
if (existingAccount.get() == null) {
return profile404;
}
if (await isBanned(name.toLowerCase())) {
let modified_ban = profileBan;
modified_ban = modified_ban.replaceAll("{{ reason }}", userData.banned);
return modified_ban;
}
let modifiedHTML = rawEditProfileHTML;
modifiedHTML = modifiedHTML.replaceAll("{{ name }}", sanitizeHtml(userData.name, sanitizeConfig));
modifiedHTML = modifiedHTML.replaceAll("{{ username }}", userData.username);
modifiedHTML = modifiedHTML.replaceAll("{{ join_date }}", dayjs(userData.createdAt).fromNow());
modifiedHTML = modifiedHTML.replaceAll("{{ about }}", sanitizeHtml(userData.about, sanitizeConfig) || "No about me available..");
modifiedHTML = modifiedHTML.replaceAll("{{ user_pfp }}", userData.pfp_url || "/img/user.svg");
modifiedHTML = modifiedHTML.replaceAll("{{ custom_css }}", userData.custom_css || "");
modifiedHTML = modifiedHTML.replaceAll("{{ url_gen }}", `https://selenite.cc/u/${userData.username}`);
modifiedHTML = modifiedHTML.replaceAll("{{ online_time }}", dayjs(userData.last_login).fromNow());
modifiedHTML = modifiedHTML.replaceAll("{{ css_edit }}", (userData.badges ? userData.badges.length : 0) > 0 ? '
' : "");
modifiedHTML = modifiedHTML.replaceAll("{{ played_games }}", buildGameHTML(userData));
let badges_html = "";
if (userData.badges !== null) {
let badges = JSON.parse(userData.badges);
for (let i = 0; i < badges.length; i++) {
badges_html += `
`;
}
}
modifiedHTML = modifiedHTML.replaceAll("{{ badges }}", badges_html);
return modifiedHTML;
}
}
function buildGameHTML(existingAccount) {
if (existingAccount.playedgames) {
let games = JSON.parse(existingAccount.playedgames);
let sortedGames = Object.keys(games).sort((a, b) => games[b] - games[a]);
let return_data = [];
if (Object.keys(games).length < 10) {
for (let i = 0; i < sortedGames.length; i++) {
try {
let origin = gamesExceptions[sortedGames[i]] ? "sppa" : "semag";
sortedGames[i] = gamesExceptions[sortedGames[i]] ? gamesExceptions[sortedGames[i]] : sortedGames[i];
return_data[i] = { name: profileReadyJSON[sortedGames[i]].name, image: profileReadyJSON[sortedGames[i]].image, path: sortedGames[i], origin: origin, valid: true };
} catch (e) {
return_data[i] = { valid: false };
}
}
} else {
for (let i = 0; i < 10; i++) {
try {
let origin = gamesExceptions[sortedGames[i]] ? "sppa" : "semag";
sortedGames[i] = gamesExceptions[sortedGames[i]] ? gamesExceptions[sortedGames[i]] : sortedGames[i];
return_data[i] = { name: profileReadyJSON[sortedGames[i]].name, image: profileReadyJSON[sortedGames[i]].image, path: sortedGames[i], origin: origin, valid: true };
} catch (e) {
return_data[i] = { valid: false };
}
}
}
let return_html = "";
for (let i = 0; i < Object.keys(return_data).length; i++) {
if (return_data[i].valid) {
return_html += `
${return_data[i].name}