From 5e5174df5da14a0db716efe236d96a70fc1c594a Mon Sep 17 00:00:00 2001 From: wont-stream <143244075+wont-stream@users.noreply.github.com> Date: Sat, 5 Apr 2025 02:29:23 -0400 Subject: [PATCH 1/3] Add image processing route with color extraction using node-vibrant --- .gitignore | 1 + package.json | 3 ++- src/routes/img.ts | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/routes/img.ts diff --git a/.gitignore b/.gitignore index d23d9c1..d0ef245 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /node_modules bun.lock .env +logs/ \ No newline at end of file diff --git a/package.json b/package.json index 8240d4f..b92f1d3 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "typescript": "^5.8.2" }, "dependencies": { - "ejs": "^3.1.10" + "ejs": "^3.1.10", + "node-vibrant": "^4.0.3" } } diff --git a/src/routes/img.ts b/src/routes/img.ts new file mode 100644 index 0000000..18de14a --- /dev/null +++ b/src/routes/img.ts @@ -0,0 +1,37 @@ +import { Vibrant } from "node-vibrant/node"; + +const routeDef: RouteDef = { + method: "GET", + accepts: "*/*", + returns: "application/json", +}; + +async function handler(request: ExtendedRequest): Promise { + const req = await fetch(request.query.url); + + if (!req.ok) { + return Response.json({ error: "Failed to fetch image" }, { status: 500 }); + } + + const type = req.headers.get("content-type") + if (!type?.includes("image/")) { + return Response.json({ error: "Not an image" }, { status: 400 }); + } + + const imageBuffer = await req.arrayBuffer() + + const colors = await Vibrant.from(Buffer.from(imageBuffer)).getPalette(); + + return new Response(Bun.gzipSync(JSON.stringify({ + img: `data:${type};base64,${Buffer.from(imageBuffer).toString("base64")}`, + colors, + })), { + headers: { + "Content-Type": "application/json", + "Content-Encoding": "gzip", + "Cache-Control": "public, max-age=31536000, immutable", + } + }) +} + +export { handler, routeDef }; From ab0a5c6c09653c71a635c4e960ed207f4c0182c6 Mon Sep 17 00:00:00 2001 From: wont-stream <143244075+wont-stream@users.noreply.github.com> Date: Sat, 5 Apr 2025 21:49:27 -0400 Subject: [PATCH 2/3] update devDependencies to latest versions --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index e92d7f1..7d18b41 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,13 @@ "cleanup": "rm -rf logs node_modules bun.lockdb" }, "devDependencies": { - "@eslint/js": "^9.23.0", - "@types/bun": "^1.2.6", + "@eslint/js": "^9.24.0", + "@types/bun": "^1.2.8", "@types/ejs": "^3.1.5", - "@typescript-eslint/eslint-plugin": "^8.28.0", - "@typescript-eslint/parser": "^8.28.0", - "eslint": "^9.23.0", - "eslint-plugin-prettier": "^5.2.5", + "@typescript-eslint/eslint-plugin": "^8.29.0", + "@typescript-eslint/parser": "^8.29.0", + "eslint": "^9.24.0", + "eslint-plugin-prettier": "^5.2.6", "eslint-plugin-promise": "^7.2.1", "eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-unicorn": "^56.0.1", @@ -25,7 +25,7 @@ "prettier": "^3.5.3" }, "peerDependencies": { - "typescript": "^5.8.2" + "typescript": "^5.8.3" }, "dependencies": { "ejs": "^3.1.10", From 2c91a8dcc9eb34ea4aa6422e5cd3663b5b869bd4 Mon Sep 17 00:00:00 2001 From: creations Date: Sun, 6 Apr 2025 18:27:22 -0400 Subject: [PATCH 3/3] merge from https://git.creations.works/creations/profilePage/pulls/1 change route and change a couple things --- src/routes/api/colors.ts | 69 ++++++++++++++++++++++++++++++++++++++++ src/routes/img.ts | 37 --------------------- 2 files changed, 69 insertions(+), 37 deletions(-) create mode 100644 src/routes/api/colors.ts delete mode 100644 src/routes/img.ts diff --git a/src/routes/api/colors.ts b/src/routes/api/colors.ts new file mode 100644 index 0000000..d8817ca --- /dev/null +++ b/src/routes/api/colors.ts @@ -0,0 +1,69 @@ +import { fetch } from "bun"; +import { Vibrant } from "node-vibrant/node"; + +type Palette = Awaited>; + +const routeDef: RouteDef = { + method: "GET", + accepts: "*/*", + returns: "application/json", +}; + +async function handler(request: ExtendedRequest): Promise { + const { url } = request.query; + + if (!url) { + return Response.json({ error: "URL is required" }, { status: 400 }); + } + + if (typeof url !== "string" || !url.startsWith("http")) { + return Response.json({ error: "Invalid URL" }, { status: 400 }); + } + + let res: Response; + try { + res = await fetch(url); + } catch { + return Response.json( + { error: "Failed to fetch image" }, + { status: 500 }, + ); + } + + if (!res.ok) { + return Response.json( + { error: "Image fetch returned error" }, + { status: res.status }, + ); + } + + const type: string | null = res.headers.get("content-type"); + if (!type?.startsWith("image/")) { + return Response.json({ error: "Not an image" }, { status: 400 }); + } + + const buffer: Buffer = Buffer.from(await res.arrayBuffer()); + const base64: string = buffer.toString("base64"); + const colors: Palette = await Vibrant.from(buffer).getPalette(); + + const payload: { + img: string; + colors: Palette; + } = { + img: `data:${type};base64,${base64}`, + colors, + }; + + const compressed: Uint8Array = Bun.gzipSync(JSON.stringify(payload)); + + return new Response(compressed, { + headers: { + "Content-Type": "application/json", + "Content-Encoding": "gzip", + "Cache-Control": "public, max-age=31536000, immutable", + "Access-Control-Allow-Origin": "*", + }, + }); +} + +export { handler, routeDef }; diff --git a/src/routes/img.ts b/src/routes/img.ts deleted file mode 100644 index 18de14a..0000000 --- a/src/routes/img.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Vibrant } from "node-vibrant/node"; - -const routeDef: RouteDef = { - method: "GET", - accepts: "*/*", - returns: "application/json", -}; - -async function handler(request: ExtendedRequest): Promise { - const req = await fetch(request.query.url); - - if (!req.ok) { - return Response.json({ error: "Failed to fetch image" }, { status: 500 }); - } - - const type = req.headers.get("content-type") - if (!type?.includes("image/")) { - return Response.json({ error: "Not an image" }, { status: 400 }); - } - - const imageBuffer = await req.arrayBuffer() - - const colors = await Vibrant.from(Buffer.from(imageBuffer)).getPalette(); - - return new Response(Bun.gzipSync(JSON.stringify({ - img: `data:${type};base64,${Buffer.from(imageBuffer).toString("base64")}`, - colors, - })), { - headers: { - "Content-Type": "application/json", - "Content-Encoding": "gzip", - "Cache-Control": "public, max-age=31536000, immutable", - } - }) -} - -export { handler, routeDef };