From 86764fabb37e168f6b41692b4ac75fb89e3083d6 Mon Sep 17 00:00:00 2001 From: OCbwoy3 Date: Sat, 26 Jul 2025 21:32:54 +0300 Subject: [PATCH] qol fix outfit switcher --- app/api/proxy/route.ts | 26 ++- bun.lock | 8 + components/site/HomeUserHeader.tsx | 39 +++- components/site/QuickTopUI.tsx | 96 +++++++--- lib/robloxEngine/BrickColorIds.ts | 283 +++++++++++++++++++++++++++++ lib/utils.ts | 39 +++- next.config.ts | 9 + package.json | 2 + 8 files changed, 463 insertions(+), 39 deletions(-) create mode 100644 lib/robloxEngine/BrickColorIds.ts diff --git a/app/api/proxy/route.ts b/app/api/proxy/route.ts index 51c4e2a..42ea64b 100644 --- a/app/api/proxy/route.ts +++ b/app/api/proxy/route.ts @@ -1,10 +1,18 @@ +// chatgpt +function rewriteCookieDomain(rawCookie: string): string { + return rawCookie + .replace(/;?\s*Domain=[^;]+/i, '') + .concat(`; Domain=localhost:3000`); +} + +// chatgpt async function proxyRequest(request: Request, method: string) { const { searchParams } = new URL(request.url); const target = searchParams.get("url"); if (!target) { return new Response( - JSON.stringify({ error: "Missing `url` query parameter." }), + JSON.stringify({ error: "missing url param, dumbass" }), { status: 400, headers: { "Content-Type": "application/json" } @@ -16,7 +24,7 @@ async function proxyRequest(request: Request, method: string) { const headers = new Headers(request.headers); headers.delete("host"); - headers.delete("accept-encoding"); // ! important + headers.delete("accept-encoding"); const init: RequestInit = { method, @@ -30,8 +38,20 @@ async function proxyRequest(request: Request, method: string) { const response = await fetch(targetUrl, init); + // Copy all response headers const responseHeaders = new Headers(response.headers); - responseHeaders.delete("content-encoding"); // ! important + responseHeaders.delete("content-encoding"); + + const rawSetCookies = response.headers.getSetCookie?.() ?? []; + + if (rawSetCookies.length > 0) { + responseHeaders.delete("set-cookie"); + + for (const rawCookie of rawSetCookies) { + const rewritten = rewriteCookieDomain(rawCookie); + responseHeaders.append("set-cookie", rewritten); + } + } return new Response(response.body, { status: response.status, diff --git a/bun.lock b/bun.lock index 8b898b2..e89df99 100644 --- a/bun.lock +++ b/bun.lock @@ -6,6 +6,7 @@ "dependencies": { "@catppuccin/tailwindcss": "^0.1.6", "@hookform/resolvers": "^5.1.1", + "@ocbwoy3/libocbwoy3": "^0.0.5", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-aspect-ratio": "^1.1.7", @@ -34,6 +35,7 @@ "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/line-clamp": "^0.4.4", + "@types/bun": "^1.2.19", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -161,6 +163,8 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@ocbwoy3/libocbwoy3": ["@ocbwoy3/libocbwoy3@0.0.5", "", {}, "sha512-zDm11Z5xzmOgsDg/E8Z9UFlMYjKdkUg28YE/Fwb0+NoVx5gKyj3D9eSbvBAOfTISTKkI3X2FTWHg3ehvyLz1Fg=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], @@ -281,6 +285,8 @@ "@tailwindcss/line-clamp": ["@tailwindcss/line-clamp@0.4.4", "", { "peerDependencies": { "tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1" } }, "sha512-5U6SY5z8N42VtrCrKlsTAA35gy2VSyYtHWCsg1H87NU1SXnEfekTVlrga9fzUDrrHcGi2Lb5KenUWb4lRQT5/g=="], + "@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="], + "@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="], "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], @@ -345,6 +351,8 @@ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + "bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="], + "busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="], "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], diff --git a/components/site/HomeUserHeader.tsx b/components/site/HomeUserHeader.tsx index e5b2d3b..aa5392d 100644 --- a/components/site/HomeUserHeader.tsx +++ b/components/site/HomeUserHeader.tsx @@ -12,6 +12,18 @@ import { useCurrentAccount } from "@/hooks/roblox/useCurrentAccount"; import { Skeleton } from "../ui/skeleton"; import { useFriendsPresence } from "@/hooks/roblox/usePresence"; import { useAccountSettings } from "@/hooks/roblox/useAccountSettings"; +import { loadThumbnails } from "@/lib/thumbnailLoader"; +import { toast } from "sonner"; + +// chatgpt + human +function randomGreeting(name: string): string { + const greetings = [ + `Howdy, ${name}` + ]; + + const index = Math.floor(Math.random() * greetings.length); + return greetings[index]; +} export function HomeLoggedInHeader() { const profile = useCurrentAccount(); @@ -52,7 +64,23 @@ export function HomeLoggedInHeader() { return ( <> {/* */} -
+
{ + if (e.button === 2) { + toast("[debug] reloading user pfp"); + console.log("[debug] reloading user pfp"); + loadThumbnails([ + { + type: "AvatarHeadShot", + targetId: profile ? profile.id : 1, + format: "webp", + size: "720x720" + } + ]).catch(() => {}); + } + }} + > {!isLoaded ? ( ) : ( @@ -64,14 +92,7 @@ export function HomeLoggedInHeader() { )}
- {isLoaded ? ( - <> - {!!accountSettings && - accountSettings.IsPremium === true - ? `Howdy, ${profile.displayName}` - : `${profile.displayName}`} - - ) : ( + {isLoaded ? randomGreeting(window.localStorage.UserPreferredName || profile.displayName || "Robloxian!") : ( <> diff --git a/components/site/QuickTopUI.tsx b/components/site/QuickTopUI.tsx index 856bf33..f99f196 100644 --- a/components/site/QuickTopUI.tsx +++ b/components/site/QuickTopUI.tsx @@ -3,22 +3,27 @@ import { useRobuxBalance } from "@/hooks/roblox/useRobuxBalance"; import { RobuxIcon } from "../roblox/RobloxIcons"; import React, { useState } from "react"; -import { Separator } from "../ui/separator"; -import { Bell, SettingsIcon, ShirtIcon } from "lucide-react"; +import { ShirtIcon } from "lucide-react"; import { StupidHoverThing } from "../util/MiscStuff"; -import { toast } from "sonner"; import { OutfitSelector } from "./OutfitQuickChooser"; import { proxyFetch } from "@/lib/utils"; import { loadThumbnails } from "@/lib/thumbnailLoader"; -/** -requires csrf token cuz u cant use noblox.js on the web - -either go to https://roblox.com/my/avataar or the app to change ur fit -*/ -async function updateOutfit(outfit: { id: number }, acc: {id: number}) { +async function updateOutfit(outfit: { id: number }, acc: { id: number }) { try { - const J = (await ( + // ocbwoy3 stupid idiot for using v3 api + const details = (await ( + await proxyFetch( + `https://avatar.roblox.com/v1/outfits/${outfit.id}/details` + ) + ).json()) as { + id: number; + name: string; + bodyColors: Record; + scale: Record; + }; + + const detailsV3 = (await ( await proxyFetch( `https://avatar.roblox.com/v3/outfits/${outfit.id}/details` ) @@ -26,16 +31,50 @@ async function updateOutfit(outfit: { id: number }, acc: {id: number}) { id: number; name: string; assets: any[]; + bodyColors: Record; + scale: Record; + playerAvatarType: "R6" | "R15"; }; + + await proxyFetch( + `https://avatar.roblox.com/v1/avatar/set-body-colors`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(details.bodyColors) + } + ); + + await proxyFetch(`https://avatar.roblox.com/v1/avatar/set-scales`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(details.scale) + }); + + // u cant set avatar item scaling/rotation cuz roblox can't make good web apis await proxyFetch( `https://avatar.roblox.com/v1/avatar/set-wearing-assets`, { method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - assetIds: J.assets.map(a=>a.id).filter(a=>!!a) + assetIds: detailsV3.assets.map((a) => a.id).filter(Boolean) }) } ); + + await proxyFetch( + `https://avatar.roblox.com/v1/avatar/set-player-avatar-type`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + playerAvatarType: detailsV3.playerAvatarType + }) + } + ); + + loadThumbnails([ { type: "AvatarHeadShot", @@ -43,25 +82,28 @@ async function updateOutfit(outfit: { id: number }, acc: {id: number}) { format: "webp", size: "720x720" } - ]).catch((a) => {}); - } catch {} + ]).catch(() => {}); + } catch (err) { + console.error(err); + } } - - export const QuickTopUI = React.memo(function () { const robux = useRobuxBalance(); const [isOutfitSelectorVisible, setIsOutfitSelectorVisible] = useState(false); return ( <> - {/* {isOutfitSelectorVisible ? ( - + {isOutfitSelectorVisible ? ( + ) : ( <> - )} */} + )}
- {/* + - */} +
{robux ? ( -

{robux || "???"}

+

+ {robux ? robux.toLocaleString() : "???"} +

) : ( <> )} @@ -93,7 +141,9 @@ export const QuickTopUILogoPart = React.memo(function () { return (
-

{"not roblox lol"}

+

{"ocbwoy3-chan's roblox"}

+

{process.env.NODE_ENV} {process.env.NEXT_PUBLIC_CWD} {process.env.NEXT_PUBLIC_ARGV0}

+
); }); diff --git a/lib/robloxEngine/BrickColorIds.ts b/lib/robloxEngine/BrickColorIds.ts new file mode 100644 index 0000000..742d552 --- /dev/null +++ b/lib/robloxEngine/BrickColorIds.ts @@ -0,0 +1,283 @@ +export const BrickColors: { + name: string; + id: number; + col: [number, number, number]; +}[] = [ + { name: "White", id: 1, col: [242, 243, 243] }, + { name: "Grey", id: 2, col: [161, 165, 162] }, + { name: "Light yellow", id: 3, col: [249, 233, 153] }, + { name: "Brick yellow", id: 5, col: [215, 197, 154] }, + { name: "Light green (Mint)", id: 6, col: [194, 218, 184] }, + { name: "Light reddish violet", id: 9, col: [232, 186, 200] }, + { name: "Pastel Blue", id: 11, col: [128, 187, 219] }, + { name: "Light orange brown", id: 12, col: [203, 132, 66] }, + { name: "Nougat", id: 18, col: [204, 142, 105] }, + { name: "Bright red", id: 21, col: [196, 40, 28] }, + { name: "Med. reddish violet", id: 22, col: [196, 112, 160] }, + { name: "Bright blue", id: 23, col: [13, 105, 172] }, + { name: "Bright yellow", id: 24, col: [245, 205, 48] }, + { name: "Earth orange", id: 25, col: [98, 71, 50] }, + { name: "Black", id: 26, col: [27, 42, 53] }, + { name: "Dark grey", id: 27, col: [109, 110, 108] }, + { name: "Dark green", id: 28, col: [40, 127, 71] }, + { name: "Medium green", id: 29, col: [161, 196, 140] }, + { name: "Lig. Yellowich orange", id: 36, col: [243, 207, 155] }, + { name: "Bright green", id: 37, col: [75, 151, 75] }, + { name: "Dark orange", id: 38, col: [160, 95, 53] }, + { name: "Light bluish violet", id: 39, col: [193, 202, 222] }, + { name: "Transparent", id: 40, col: [236, 236, 236] }, + { name: "Tr. Red", id: 41, col: [205, 84, 75] }, + { name: "Tr. Lg blue", id: 42, col: [193, 223, 240] }, + { name: "Tr. Blue", id: 43, col: [123, 182, 232] }, + { name: "Tr. Yellow", id: 44, col: [247, 241, 141] }, + { name: "Light blue", id: 45, col: [180, 210, 228] }, + { name: "Tr. Flu. Reddish orange", id: 47, col: [217, 133, 108] }, + { name: "Tr. Green", id: 48, col: [132, 182, 141] }, + { name: "Tr. Flu. Green", id: 49, col: [248, 241, 132] }, + { name: "Phosph. White", id: 50, col: [236, 232, 222] }, + { name: "Light red", id: 100, col: [238, 196, 182] }, + { name: "Medium red", id: 101, col: [218, 134, 122] }, + { name: "Medium blue", id: 102, col: [110, 153, 202] }, + { name: "Light grey", id: 103, col: [199, 193, 183] }, + { name: "Bright violet", id: 104, col: [107, 50, 124] }, + { name: "Br. yellowish orange", id: 105, col: [226, 155, 64] }, + { name: "Bright orange", id: 106, col: [218, 133, 65] }, + { name: "Bright bluish green", id: 107, col: [0, 143, 156] }, + { name: "Earth yellow", id: 108, col: [104, 92, 67] }, + { name: "Bright bluish violet", id: 110, col: [67, 84, 147] }, + { name: "Tr. Brown", id: 111, col: [191, 183, 177] }, + { name: "Medium bluish violet", id: 112, col: [104, 116, 172] }, + { name: "Tr. Medi. reddish violet", id: 113, col: [229, 173, 200] }, + { name: "Med. yellowish green", id: 115, col: [199, 210, 60] }, + { name: "Med. bluish green", id: 116, col: [85, 165, 175] }, + { name: "Light bluish green", id: 118, col: [183, 215, 213] }, + { name: "Br. yellowish green", id: 119, col: [164, 189, 71] }, + { name: "Lig. yellowish green", id: 120, col: [217, 228, 167] }, + { name: "Med. yellowish orange", id: 121, col: [231, 172, 88] }, + { name: "Br. reddish orange", id: 123, col: [211, 111, 76] }, + { name: "Bright reddish violet", id: 124, col: [146, 57, 120] }, + { name: "Light orange", id: 125, col: [234, 184, 146] }, + { name: "Tr. Bright bluish violet", id: 126, col: [165, 165, 203] }, + { name: "Gold", id: 127, col: [220, 188, 129] }, + { name: "Dark nougat", id: 128, col: [174, 122, 89] }, + { name: "Silver", id: 131, col: [156, 163, 168] }, + { name: "Neon orange", id: 133, col: [213, 115, 61] }, + { name: "Neon green", id: 134, col: [216, 221, 86] }, + { name: "Sand blue", id: 135, col: [116, 134, 157] }, + { name: "Sand violet", id: 136, col: [135, 124, 144] }, + { name: "Medium orange", id: 137, col: [224, 152, 100] }, + { name: "Sand yellow", id: 138, col: [149, 138, 115] }, + { name: "Earth blue", id: 140, col: [32, 58, 86] }, + { name: "Earth green", id: 141, col: [39, 70, 45] }, + { name: "Tr. Flu. Blue", id: 143, col: [207, 226, 247] }, + { name: "Sand blue metallic", id: 145, col: [121, 136, 161] }, + { name: "Sand violet metallic", id: 146, col: [149, 142, 163] }, + { name: "Sand yellow metallic", id: 147, col: [147, 135, 103] }, + { name: "Dark grey metallic", id: 148, col: [87, 88, 87] }, + { name: "Black metallic", id: 149, col: [22, 29, 50] }, + { name: "Light grey metallic", id: 150, col: [171, 173, 172] }, + { name: "Sand green", id: 151, col: [120, 144, 130] }, + { name: "Sand red", id: 153, col: [149, 121, 119] }, + { name: "Dark red", id: 154, col: [123, 46, 47] }, + { name: "Tr. Flu. Yellow", id: 157, col: [255, 246, 123] }, + { name: "Tr. Flu. Red", id: 158, col: [225, 164, 194] }, + { name: "Gun metallic", id: 168, col: [117, 108, 98] }, + { name: "Red flip/flop", id: 176, col: [151, 105, 91] }, + { name: "Yellow flip/flop", id: 178, col: [180, 132, 85] }, + { name: "Silver flip/flop", id: 179, col: [137, 135, 136] }, + { name: "Curry", id: 180, col: [215, 169, 75] }, + { name: "Fire Yellow", id: 190, col: [249, 214, 46] }, + { name: "Flame yellowish orange", id: 191, col: [232, 171, 45] }, + { name: "Reddish brown", id: 192, col: [105, 64, 40] }, + { name: "Flame reddish orange", id: 193, col: [207, 96, 36] }, + { name: "Medium stone grey", id: 194, col: [163, 162, 165] }, + { name: "Royal blue", id: 195, col: [70, 103, 164] }, + { name: "Dark Royal blue", id: 196, col: [35, 71, 139] }, + { name: "Bright reddish lilac", id: 198, col: [142, 66, 133] }, + { name: "Dark stone grey", id: 199, col: [99, 95, 98] }, + { name: "Lemon metalic", id: 200, col: [130, 138, 93] }, + { name: "Light stone grey", id: 208, col: [229, 228, 223] }, + { name: "Dark Curry", id: 209, col: [176, 142, 68] }, + { name: "Faded green", id: 210, col: [112, 149, 120] }, + { name: "Turquoise", id: 211, col: [121, 181, 181] }, + { name: "Light Royal blue", id: 212, col: [159, 195, 233] }, + { name: "Medium Royal blue", id: 213, col: [108, 129, 183] }, + { name: "Rust", id: 216, col: [144, 76, 42] }, + { name: "Brown", id: 217, col: [124, 92, 70] }, + { name: "Reddish lilac", id: 218, col: [150, 112, 159] }, + { name: "Lilac", id: 219, col: [107, 98, 155] }, + { name: "Light lilac", id: 220, col: [167, 169, 206] }, + { name: "Bright purple", id: 221, col: [205, 98, 152] }, + { name: "Light purple", id: 222, col: [228, 173, 200] }, + { name: "Light pink", id: 223, col: [220, 144, 149] }, + { name: "Light brick yellow", id: 224, col: [240, 213, 160] }, + { name: "Warm yellowish orange", id: 225, col: [235, 184, 127] }, + { name: "Cool yellow", id: 226, col: [253, 234, 141] }, + { name: "Dove blue", id: 232, col: [125, 187, 221] }, + { name: "Medium lilac", id: 268, col: [52, 43, 117] }, + { name: "Slime green", id: 301, col: [80, 109, 84] }, + { name: "Smoky grey", id: 302, col: [91, 93, 105] }, + { name: "Dark blue", id: 303, col: [0, 16, 176] }, + { name: "Parsley green", id: 304, col: [44, 101, 29] }, + { name: "Steel blue", id: 305, col: [82, 124, 174] }, + { name: "Storm blue", id: 306, col: [51, 88, 130] }, + { name: "Lapis", id: 307, col: [16, 42, 220] }, + { name: "Dark indigo", id: 308, col: [61, 21, 133] }, + { name: "Sea green", id: 309, col: [52, 142, 64] }, + { name: "Shamrock", id: 310, col: [91, 154, 76] }, + { name: "Fossil", id: 311, col: [159, 161, 172] }, + { name: "Mulberry", id: 312, col: [89, 34, 89] }, + { name: "Forest green", id: 313, col: [31, 128, 29] }, + { name: "Cadet blue", id: 314, col: [159, 173, 192] }, + { name: "Electric blue", id: 315, col: [9, 137, 207] }, + { name: "Eggplant", id: 316, col: [123, 0, 123] }, + { name: "Moss", id: 317, col: [124, 156, 107] }, + { name: "Artichoke", id: 318, col: [138, 171, 133] }, + { name: "Sage green", id: 319, col: [185, 196, 177] }, + { name: "Ghost grey", id: 320, col: [202, 203, 209] }, + { name: "Lilac", id: 321, col: [167, 94, 155] }, + { name: "Plum", id: 322, col: [123, 47, 123] }, + { name: "Olivine", id: 323, col: [148, 190, 129] }, + { name: "Laurel green", id: 324, col: [168, 189, 153] }, + { name: "Quill grey", id: 325, col: [223, 223, 222] }, + { name: "Crimson", id: 327, col: [151, 0, 0] }, + { name: "Mint", id: 328, col: [177, 229, 166] }, + { name: "Baby blue", id: 329, col: [152, 194, 219] }, + { name: "Carnation pink", id: 330, col: [255, 152, 220] }, + { name: "Persimmon", id: 331, col: [255, 89, 89] }, + { name: "Maroon", id: 332, col: [117, 0, 0] }, + { name: "Gold", id: 333, col: [239, 184, 56] }, + { name: "Daisy orange", id: 334, col: [248, 217, 109] }, + { name: "Pearl", id: 335, col: [231, 231, 236] }, + { name: "Fog", id: 336, col: [199, 212, 228] }, + { name: "Salmon", id: 337, col: [255, 148, 148] }, + { name: "Terra Cotta", id: 338, col: [190, 104, 98] }, + { name: "Cocoa", id: 339, col: [86, 36, 36] }, + { name: "Wheat", id: 340, col: [241, 231, 199] }, + { name: "Buttermilk", id: 341, col: [254, 243, 187] }, + { name: "Mauve", id: 342, col: [224, 178, 208] }, + { name: "Sunrise", id: 343, col: [212, 144, 189] }, + { name: "Tawny", id: 344, col: [150, 85, 85] }, + { name: "Rust", id: 345, col: [143, 76, 42] }, + { name: "Cashmere", id: 346, col: [211, 190, 150] }, + { name: "Khaki", id: 347, col: [226, 220, 188] }, + { name: "Lily white", id: 348, col: [237, 234, 234] }, + { name: "Seashell", id: 349, col: [233, 218, 218] }, + { name: "Burgundy", id: 350, col: [136, 62, 62] }, + { name: "Cork", id: 351, col: [188, 155, 93] }, + { name: "Burlap", id: 352, col: [199, 172, 120] }, + { name: "Beige", id: 353, col: [202, 191, 163] }, + { name: "Oyster", id: 354, col: [187, 179, 178] }, + { name: "Pine Cone", id: 355, col: [108, 88, 75] }, + { name: "Fawn brown", id: 356, col: [160, 132, 79] }, + { name: "Hurricane grey", id: 357, col: [149, 137, 136] }, + { name: "Cloudy grey", id: 358, col: [171, 168, 158] }, + { name: "Linen", id: 359, col: [175, 148, 131] }, + { name: "Copper", id: 360, col: [150, 103, 102] }, + { name: "Medium brown", id: 361, col: [86, 66, 54] }, + { name: "Bronze", id: 362, col: [126, 104, 63] }, + { name: "Flint", id: 363, col: [105, 102, 92] }, + { name: "Dark taupe", id: 364, col: [90, 76, 66] }, + { name: "Burnt Sienna", id: 365, col: [106, 57, 9] }, + { name: "Institutional white", id: 1001, col: [248, 248, 248] }, + { name: "Mid gray", id: 1002, col: [205, 205, 205] }, + { name: "Really black", id: 1003, col: [17, 17, 17] }, + { name: "Really red", id: 1004, col: [255, 0, 0] }, + { name: "Deep orange", id: 1005, col: [255, 176, 0] }, + { name: "Alder", id: 1006, col: [180, 128, 255] }, + { name: "Dusty Rose", id: 1007, col: [163, 75, 75] }, + { name: "Olive", id: 1008, col: [193, 190, 66] }, + { name: "New Yeller", id: 1009, col: [255, 255, 0] }, + { name: "Really blue", id: 1010, col: [0, 0, 255] }, + { name: "Navy blue", id: 1011, col: [0, 32, 96] }, + { name: "Deep blue", id: 1012, col: [33, 84, 185] }, + { name: "Cyan", id: 1013, col: [4, 175, 236] }, + { name: "CGA brown", id: 1014, col: [170, 85, 0] }, + { name: "Magenta", id: 1015, col: [170, 0, 170] }, + { name: "Pink", id: 1016, col: [255, 102, 204] }, + { name: "Deep orange", id: 1017, col: [255, 175, 0] }, + { name: "Teal", id: 1018, col: [18, 238, 212] }, + { name: "Toothpaste", id: 1019, col: [0, 255, 255] }, + { name: "Lime green", id: 1020, col: [0, 255, 0] }, + { name: "Camo", id: 1021, col: [58, 125, 21] }, + { name: "Grime", id: 1022, col: [127, 142, 100] }, + { name: "Lavender", id: 1023, col: [140, 91, 159] }, + { name: "Pastel light blue", id: 1024, col: [175, 221, 255] }, + { name: "Pastel orange", id: 1025, col: [255, 201, 201] }, + { name: "Pastel violet", id: 1026, col: [177, 167, 255] }, + { name: "Pastel blue-green", id: 1027, col: [159, 243, 233] }, + { name: "Pastel green", id: 1028, col: [204, 255, 204] }, + { name: "Pastel yellow", id: 1029, col: [255, 255, 204] }, + { name: "Pastel brown", id: 1030, col: [255, 204, 153] }, + { name: "Royal purple", id: 1031, col: [98, 37, 209] }, + { name: "Hot pink", id: 1032, col: [255, 0, 191] } +]; + +// chatgpt +export const BrickColorHexMap: { [hex: string]: number } = Object.fromEntries( + BrickColors.map(({ id, col }) => { + const hex = rgbToHex(...col); + return [hex, id]; + }) +); + +// chatgpt +function rgbToHex(r: number, g: number, b: number): string { + return [r, g, b] + .map((v) => { + const hex = Math.round(v).toString(16).padStart(2, "0"); + return hex; + }) + .join("") + .toLowerCase(); +} + +/** + * find the closest roblox brickcolor id of a hex color + * made by chatgpt + */ +export function findClosestBrickColor(hex: string): { + id: number; + name: string; + col: [number, number, number]; +} { + const target = hexToRgb(hex); + console.log(hex,target) + if (!target) throw new Error("Invalid hex"); + + let bestDist = Infinity; + let bestColor = BrickColors[0]; + + for (const brick of BrickColors) { + const [r, g, b] = brick.col; + const dist = euclideanDistance(target, [r, g, b]); + + if (dist < bestDist) { + bestDist = dist; + bestColor = brick; + } + } + + return bestColor; +} + +// chatgpt +function hexToRgb(hex?: string): [number, number, number] | null { + if (typeof hex !== "string") return null; + + const cleaned = hex.replace(/^#/, "").toLowerCase(); + if (!/^[0-9a-f]{6}$/.test(cleaned)) return null; + + const r = parseInt(cleaned.slice(0, 2), 16); + const g = parseInt(cleaned.slice(2, 4), 16); + const b = parseInt(cleaned.slice(4, 6), 16); + return [r, g, b]; +} + +// chatgpt +function euclideanDistance( + a: [number, number, number], + b: [number, number, number] +) { + return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + (a[2] - b[2]) ** 2; +} diff --git a/lib/utils.ts b/lib/utils.ts index dad49e3..698c3c5 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -7,7 +7,7 @@ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } -export async function proxyFetch( +export async function proxyFetchRaw( input: RequestInfo | URL, init?: RequestInit ): Promise { @@ -17,18 +17,49 @@ export async function proxyFetch( : input instanceof Request ? input.url : ""; + const proxyUrl = `/api/proxy?url=${encodeURIComponent(url)}`; - // fix headers + // Fix headers const headers = new Headers(init?.headers || {}); - headers.delete("accept-encoding"); // prevent stupid encoding bug + headers.delete("accept-encoding"); // prevent encoding issues const fetchInit: RequestInit = { ...init, method: init?.method || "GET", headers, - body: init?.body + body: init?.body, }; return window.fetch(proxyUrl, fetchInit); } + +// CSRF-aware proxy fetch +export async function proxyFetch( + input: RequestInfo | URL, + init?: RequestInit +): Promise { + const xsrfRequestMethods = ["POST", "PATCH", "DELETE"]; + const csrfTokenHeader = "x-csrf-token"; + const csrfInvalidResponseCode = 403; + + const method = init?.method?.toUpperCase() || "GET"; + + let response = await proxyFetchRaw(input, init); + + if ( + xsrfRequestMethods.includes(method) && + response.status === csrfInvalidResponseCode && + response.headers.has(csrfTokenHeader) + ) { + const newHeaders = new Headers(init?.headers || {}); + newHeaders.set(csrfTokenHeader, response.headers.get(csrfTokenHeader)!); + + response = await proxyFetchRaw(input, { + ...init, + headers: newHeaders, + }); + } + + return response; +} \ No newline at end of file diff --git a/next.config.ts b/next.config.ts index aa0ebd7..71359bd 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,5 +1,14 @@ import type { NextConfig } from "next"; +if (!process.isBun) { + console.error(`You are running this with node. Rerun the process: bun --bun run dev`) + process.exit(1) +} + +process.env.NEXT_PUBLIC_CWD = __dirname || "~" +process.env.NEXT_PUBLIC_ARGV0 = process.argv0 || "node" + + const nextConfig: NextConfig = { /* config options here */ images: { diff --git a/package.json b/package.json index 8e9ad63..c2d552e 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "@catppuccin/tailwindcss": "^0.1.6", "@hookform/resolvers": "^5.1.1", + "@ocbwoy3/libocbwoy3": "^0.0.5", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-aspect-ratio": "^1.1.7", @@ -39,6 +40,7 @@ "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/line-clamp": "^0.4.4", + "@types/bun": "^1.2.19", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1",