const { CosmosStore } = require("./db.cjs"); const express = require("express"); const app = express(); const path = require("path"); const htmlparser = require("node-html-parser"); const { existsSync, mkdirSync, readFileSync, writeFileSync } = require("fs"); const fs = require("fs/promises"); const crypto = require("crypto"); app.use(express.static(path.join(process.cwd(), "dist"), { index: false })); app.use(express.json({ limit: "50mb" })); app.use(express.raw({ type: "application/octet-stream", limit: "50mb" })); const { pipeline } = require("stream/promises"); const https = require("https"); const sslPath = path.join(process.cwd(), "server/node/ssl/certificate"); const hubURL = "https://sv.risuai.xyz"; let password = ""; const cosmosStore = new CosmosStore( "http://127.0.0.1:8081", "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", "db", "container", ); const savePath = path.join(process.cwd(), "save"); if (!existsSync(savePath)) { mkdirSync(savePath); } const passwordPath = path.join(process.cwd(), "save", "__password"); if (existsSync(passwordPath)) { password = readFileSync(passwordPath, "utf-8"); } const hexRegex = /^[0-9a-fA-F]+$/; function isHex(str) { return hexRegex.test(str.toUpperCase().trim()) || str === "__password"; } app.get("/", async (req, res, next) => { const clientIP = req.headers["x-forwarded-for"] || req.ip || req.socket.remoteAddress || "Unknown IP"; const timestamp = new Date().toISOString(); console.log(`[Server] ${timestamp} | Connection from: ${clientIP}`); try { const mainIndex = await fs.readFile( path.join(process.cwd(), "dist", "index.html"), ); const root = htmlparser.parse(mainIndex); const head = root.querySelector("head"); head.innerHTML = `` + head.innerHTML; res.send(root.toString()); } catch (error) { console.log(error); next(error); } }); const reverseProxyFunc = async (req, res, next) => { const urlParam = req.headers["risu-url"] ? decodeURIComponent(req.headers["risu-url"]) : req.query.url; if (!urlParam) { res.status(400).send({ error: "URL has no param", }); return; } const header = req.headers["risu-header"] ? JSON.parse(decodeURIComponent(req.headers["risu-header"])) : req.headers; if (!header["x-forwarded-for"]) { header["x-forwarded-for"] = req.ip; } let originalResponse; try { // make request to original server originalResponse = await fetch(urlParam, { method: req.method, headers: header, body: JSON.stringify(req.body), }); // get response body as stream const originalBody = originalResponse.body; // get response headers const head = new Headers(originalResponse.headers); head.delete("content-security-policy"); head.delete("content-security-policy-report-only"); head.delete("clear-site-data"); head.delete("Cache-Control"); head.delete("Content-Encoding"); const headObj = {}; for (let [k, v] of head) { headObj[k] = v; } // send response headers to client res.header(headObj); // send response status to client res.status(originalResponse.status); // send response body to client await pipeline(originalResponse.body, res); } catch (err) { next(err); return; } }; const reverseProxyFunc_get = async (req, res, next) => { const urlParam = req.headers["risu-url"] ? decodeURIComponent(req.headers["risu-url"]) : req.query.url; if (!urlParam) { res.status(400).send({ error: "URL has no param", }); return; } const header = req.headers["risu-header"] ? JSON.parse(decodeURIComponent(req.headers["risu-header"])) : req.headers; if (!header["x-forwarded-for"]) { header["x-forwarded-for"] = req.ip; } let originalResponse; try { // make request to original server originalResponse = await fetch(urlParam, { method: "GET", headers: header, }); // get response body as stream const originalBody = originalResponse.body; // get response headers const head = new Headers(originalResponse.headers); head.delete("content-security-policy"); head.delete("content-security-policy-report-only"); head.delete("clear-site-data"); head.delete("Cache-Control"); head.delete("Content-Encoding"); const headObj = {}; for (let [k, v] of head) { headObj[k] = v; } // send response headers to client res.header(headObj); // send response status to client res.status(originalResponse.status); // send response body to client await pipeline(originalResponse.body, res); } catch (err) { next(err); return; } }; async function hubProxyFunc(req, res) { try { const pathAndQuery = req.originalUrl.replace(/^\/hub-proxy/, ""); const externalURL = hubURL + pathAndQuery; const headersToSend = { ...req.headers }; delete headersToSend.host; delete headersToSend.connection; const response = await fetch(externalURL, { method: req.method, headers: headersToSend, body: req.method !== "GET" && req.method !== "HEAD" ? req : undefined, redirect: "manual", duplex: "half", }); for (const [key, value] of response.headers.entries()) { res.setHeader(key, value); } res.status(response.status); if (response.status >= 300 && response.status < 400) { // Redirect handling (due to ‘/redirect/docs/lua’) const redirectUrl = response.headers.get("location"); if (redirectUrl) { if (redirectUrl.startsWith("http")) { if (redirectUrl.startsWith(hubURL)) { const newPath = redirectUrl.replace(hubURL, "/hub-proxy"); res.setHeader("location", newPath); } } else if (redirectUrl.startsWith("/")) { res.setHeader("location", `/hub-proxy${redirectUrl}`); } } return res.end(); } await pipeline(response.body, res); } catch (error) { console.error("[Hub Proxy] Error:", error); if (!res.headersSent) { res.status(502).send({ error: "Proxy request failed: " + error.message }); } else { res.end(); } } } app.get("/proxy", reverseProxyFunc_get); app.get("/proxy2", reverseProxyFunc_get); app.get("/hub-proxy/*", hubProxyFunc); app.post("/proxy", reverseProxyFunc); app.post("/proxy2", reverseProxyFunc); app.post("/hub-proxy/*", hubProxyFunc); app.get("/api/password", async (req, res) => { if (password === "") { res.send({ status: "unset" }); } else if (req.headers["risu-auth"] === password) { res.send({ status: "correct" }); } else { res.send({ status: "incorrect" }); } }); app.post("/api/crypto", async (req, res) => { try { const hash = crypto.createHash("sha256"); hash.update(Buffer.from(req.body.data, "utf-8")); res.send(hash.digest("hex")); } catch (error) { next(error); } }); app.post("/api/set_password", async (req, res) => { if (password === "") { password = req.body.password; writeFileSync(passwordPath, password, "utf-8"); } res.status(400).send("already set"); }); app.get("/api/read", async (req, res, next) => { if (req.headers["risu-auth"].trim() !== password.trim()) { console.log("incorrect"); res.status(400).send({ error: "Password Incorrect", }); return; } const filePath = req.headers["file-path"]; if (!filePath) { console.log("no path"); res.status(400).send({ error: "File path required", }); return; } if (!isHex(filePath)) { res.status(400).send({ error: "Invaild Path", }); return; } try { try { const fileData = await cosmosStore.getData(filePath); res.setHeader("Content-Type", "application/octet-stream"); res.send(fileData); } catch (e) { res.send(); } if (!existsSync(path.join(savePath, filePath))) { } else { } } catch (error) { next(error); } }); app.get("/api/remove", async (req, res, next) => { if (req.headers["risu-auth"].trim() !== password.trim()) { console.log("incorrect"); res.status(400).send({ error: "Password Incorrect", }); return; } const filePath = req.headers["file-path"]; if (!filePath) { res.status(400).send({ error: "File path required", }); return; } if (!isHex(filePath)) { res.status(400).send({ error: "Invaild Path", }); return; } try { await cosmosStore.removeData(filePath); res.send({ success: true, }); } catch (error) { next(error); } }); app.get("/api/list", async (req, res, next) => { if (req.headers["risu-auth"].trim() !== password.trim()) { console.log("incorrect"); res.status(400).send({ error: "Password Incorrect", }); return; } try { const data = (await cosmosStore.listData()).map((v) => { return Buffer.from(v, "hex").toString("utf-8"); }); res.send({ success: true, content: data, }); } catch (error) { next(error); } }); app.post("/api/write", async (req, res, next) => { if (req.headers["risu-auth"].trim() !== password.trim()) { console.log("incorrect"); res.status(400).send({ error: "Password Incorrect", }); return; } const filePath = req.headers["file-path"]; const fileContent = req.body; if (!filePath || !fileContent) { res.status(400).send({ error: "File path required", }); return; } if (!isHex(filePath)) { res.status(400).send({ error: "Invaild Path", }); return; } try { await cosmosStore.createData(filePath, fileContent); res.send({ success: true, }); } catch (error) { next(error); } }); async function getHttpsOptions() { const keyPath = path.join(sslPath, "server.key"); const certPath = path.join(sslPath, "server.crt"); try { await fs.access(keyPath); await fs.access(certPath); const [key, cert] = await Promise.all([ fs.readFile(keyPath), fs.readFile(certPath), ]); return { key, cert }; } catch (error) { console.error("[Server] SSL setup errors:", error.message); console.log("[Server] Start the server with HTTP instead of HTTPS..."); return null; } } async function startServer() { try { const port = process.env.PORT || 6001; const httpsOptions = await getHttpsOptions(); if (httpsOptions) { // HTTPS https.createServer(httpsOptions, app).listen(port, () => { console.log("[Server] HTTPS server is running."); console.log(`[Server] https://localhost:${port}/`); }); } else { // HTTP app.listen(port, () => { console.log("[Server] HTTP server is running."); console.log(`[Server] http://localhost:${port}/`); }); } } catch (error) { console.error("[Server] Failed to start server :", error); process.exit(1); } } (async () => { await startServer(); })();