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 { download } from "./music.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("./public/resources/games.json").toString()); let appsJSON = JSON.parse(fs.readFileSync("./public/resources/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/${userData.id}/`; let uuid = crypto.randomUUID(); let path = `${process.env.DATA_PATH}/data/${userData.id}/${uuid}.webp`; url = `/data/${userData.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}/${userData.pfp_url}`, () => {}); const updateAccount = accs.query(`UPDATE accounts SET pfp_url = $url WHERE username = $user`) updateAccount.get({ $url: url, $user: user }); } } if (body.artist) { let checkStatus = new Request({ url: process.env.PROCESSING_SERVER + "/status" }); let checkStatusRequest = await fetch(checkStatus); if(checkStatusRequest.status != 200) { return { success: false, err: "processing server is down, try again later"}; } let path = await download(body.url); console.log("exit download"); let file = Bun.file(path); let request = new Request({ url: process.env.PROCESSING_SERVER + "/process", method: "POST", body: await file.arrayBuffer(), headers: { "X-Authentication": process.env.PROCESSING_SERVER_SECRET } }); console.log("created request"); let oggFile = await fetch(request); console.log("finished request"); let filePath = `/data/${userData.id}/${crypto.randomUUID()}.ogg`; await Bun.write(process.env.DATA_PATH + filePath, oggFile); const updateAccount = accs.query(`UPDATE accounts SET music = $music WHERE username = $user`) updateAccount.get({ $music: JSON.stringify({ path: filePath, name: body.title, artist: body.artist }), $user: user }); console.log("database"); } 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; let songData = JSON.parse(userData.music) || false; 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("{{ username }}", sanitizeHtml(userData.username, allowNone)); if(songData) { modifiedHTML = modifiedHTML.replaceAll("{{ song_title }}", sanitizeHtml(songData.name, allowNone)); modifiedHTML = modifiedHTML.replaceAll("{{ song_artist }}", sanitizeHtml(songData.artist, allowNone)); modifiedHTML = modifiedHTML.replaceAll("{{ song_url }}", sanitizeHtml(songData.path, allowNone)); modifiedHTML = modifiedHTML.replaceAll("{{ is_music }}", "true"); } else { modifiedHTML = modifiedHTML.replaceAll("{{ is_music }}", "false"); } let badges_html = ""; if (userData.badges !== null) { let badges = JSON.parse(userData.badges); for (let i = 0; i < badges.length; i++) { badges_html += `${badge[badges[i]]}`; } } 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; let songData = JSON.parse(userData.music) || false; 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 ? '' : ""); if(songData) { modifiedHTML = modifiedHTML.replaceAll("{{ song_title }}", sanitizeHtml(songData.name, allowNone)); modifiedHTML = modifiedHTML.replaceAll("{{ song_artist }}", sanitizeHtml(songData.artist, allowNone)); modifiedHTML = modifiedHTML.replaceAll("{{ song_url }}", sanitizeHtml(songData.path, allowNone)); modifiedHTML = modifiedHTML.replaceAll("{{ is_music }}", "true"); } else { modifiedHTML = modifiedHTML.replaceAll("{{ is_music }}", "false"); } let badges_html = ""; if (userData.badges !== null) { let badges = JSON.parse(userData.badges); for (let i = 0; i < badges.length; i++) { badges_html += `${badge[badges[i]]}`; } } modifiedHTML = modifiedHTML.replaceAll("{{ badges }}", badges_html); return modifiedHTML; } } async function getUsers(page, search) { let amount = 12; if (!page) { page = 0; } const getUsersSQL = accs.query(`SELECT * FROM accounts WHERE username LIKE $search AND banned IS NULL LIMIT $limit OFFSET $offset`); let data = getUsersSQL.all({ $search: `%${search}%`, $limit: amount, $offset: page * amount }); let countUsers = accs.query(`SELECT COUNT(*) FROM accounts`); let finalData = { users: {}, count: countUsers.get()["COUNT(*)"] }; for (let i = 0; i < data.length; i++) { finalData.users[i] = { username: sanitizeHtml(data[i].username, sanitizeConfig), name: sanitizeHtml(data[i].name, sanitizeConfig), about: (data[i].about + "").length > 50 ? `${(sanitizeHtml(data[i].about, sanitizeConfigNoLink) + "").substring(0, 50)}...` : sanitizeHtml(data[i].about, sanitizeConfigNoLink), badges: data[i].badges, pfp_url: data[i].pfp_url || "/img/user.svg", }; } return finalData; } export { getRawData, generateAccountPage, editProfile, saveData, getUsers, isAdmin, retrieveData };