diff --git a/package.json b/package.json
index 96f4946..7395618 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"@creations.works/logger": "^1.0.3",
"ejs": "^3.1.10",
"isomorphic-dompurify": "^2.23.0",
+ "linkedom": "^0.18.9",
"marked": "^15.0.7"
}
}
diff --git a/public/js/index.js b/public/js/index.js
index 27541b5..c930518 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -312,6 +312,28 @@ async function loadBadges(userId, options = {}) {
}
}
+async function populateReadme(data) {
+ const readmeSection = document.querySelector(".readme");
+
+ if (readmeSection && data.kv?.readme) {
+ const url = data.kv.readme;
+ try {
+ const res = await fetch(`/api/readme?url=${encodeURIComponent(url)}`);
+ if (!res.ok) throw new Error("Failed to fetch readme");
+
+ const text = await res.text();
+
+ readmeSection.innerHTML = `
${text}
`;
+ readmeSection.classList.remove("hidden");
+ } catch (err) {
+ console.error("Failed to load README", err);
+ readmeSection.classList.add("hidden");
+ }
+ } else if (readmeSection) {
+ readmeSection.classList.add("hidden");
+ }
+}
+
async function updatePresence(data) {
const cssLink = data.kv?.css;
if (cssLink) {
@@ -431,7 +453,7 @@ async function updatePresence(data) {
}
if (!badgesLoaded) {
- await loadBadges(userId, {
+ loadBadges(userId, {
services: [],
seperated: true,
cache: true,
@@ -444,25 +466,7 @@ async function updatePresence(data) {
const custom = data.activities?.find((a) => a.type === 4);
updateCustomStatus(custom);
- const readmeSection = document.querySelector(".readme");
-
- if (readmeSection && data.kv?.readme) {
- const url = data.kv.readme;
- try {
- const res = await fetch(`/api/readme?url=${encodeURIComponent(url)}`);
- if (!res.ok) throw new Error("Failed to fetch readme");
-
- const text = await res.text();
-
- readmeSection.innerHTML = `${text}
`;
- readmeSection.classList.remove("hidden");
- } catch (err) {
- console.error("Failed to load README", err);
- readmeSection.classList.add("hidden");
- }
- } else if (readmeSection) {
- readmeSection.classList.add("hidden");
- }
+ populateReadme(data);
const filtered = data.activities
?.filter((a) => a.type !== 4)
diff --git a/src/routes/api/art[game].ts b/src/routes/api/art[game].ts
index 006337d..c91b7b4 100644
--- a/src/routes/api/art[game].ts
+++ b/src/routes/api/art[game].ts
@@ -5,6 +5,7 @@ const routeDef: RouteDef = {
method: "GET",
accepts: "*/*",
returns: "application/json",
+ log: false,
};
async function fetchSteamGridIcon(gameName: string): Promise {
@@ -53,7 +54,10 @@ async function fetchSteamGridIcon(gameName: string): Promise {
async function handler(request: ExtendedRequest): Promise {
if (!steamGridDbKey) {
return Response.json(
- { status: 503, error: "Route disabled due to missing SteamGridDB key" },
+ {
+ status: 503,
+ error: "Route disabled due to missing SteamGridDB key",
+ },
{ status: 503 },
);
}
diff --git a/src/routes/api/css.ts b/src/routes/api/css.ts
index 20e359e..92c4f37 100644
--- a/src/routes/api/css.ts
+++ b/src/routes/api/css.ts
@@ -1,4 +1,6 @@
+import { redisTtl } from "@config/environment";
import { fetch } from "bun";
+import { redis } from "bun";
const routeDef: RouteDef = {
method: "GET",
@@ -7,6 +9,37 @@ const routeDef: RouteDef = {
log: false,
};
+async function fetchAndCacheCss(url: string): Promise {
+ const cacheKey = `css:${url}`;
+ const cached = await redis.get(cacheKey);
+ if (cached) return cached;
+
+ const res = await fetch(url, {
+ headers: {
+ Accept: "text/css",
+ },
+ });
+
+ if (!res.ok) return null;
+
+ if (res.headers.has("content-length")) {
+ const size = Number.parseInt(res.headers.get("content-length") || "0", 10);
+ if (size > 1024 * 50) return null;
+ }
+
+ const text = await res.text();
+ if (!text || text.length < 5) return null;
+
+ const sanitized = text
+ .replace(/