/** * habbo.it furnidata cache — downloads once, keeps in memory. * Provides instant lookup by classname for enriching imports. */ export type { HabboItFurniEntry } from "@/types/furni"; import type { HabboItFurniEntry } from "@/types/furni"; const FURNIDATA_URL = "https://www.habbo.it/gamedata/furnidata_json/1"; const CACHE_TTL = 30 * 60 * 1000; // 30 minutes let cache: Map | null = null; let cacheTimestamp = 0; let loading: Promise> | null = null; /** * Fetches and parses the full habbo.it furnidata JSON. * Results are cached in memory for 30 minutes. */ export async function getHabboItFurnidata(): Promise> { const now = Date.now(); // Return cache if still valid if (cache && now - cacheTimestamp < CACHE_TTL) { return cache; } // Deduplicate concurrent requests if (loading) return loading; loading = (async () => { try { console.debug("[habbo-it] Fetching furnidata from habbo.it..."); const res = await fetch(FURNIDATA_URL, { signal: AbortSignal.timeout(20000), headers: { Accept: "application/json" }, }); if (!res.ok) { console.warn(`[habbo-it] Fetch failed: ${res.status}`); return cache || new Map(); } const data = await res.json(); const map = new Map(); // Process room items (floor furniture) const roomTypes = data?.roomitemtypes?.furnitype; if (Array.isArray(roomTypes)) { for (const item of roomTypes) { if (!item.classname) continue; map.set(item.classname, { id: item.id ?? 0, classname: item.classname, revision: item.revision ?? 0, category: item.category ?? "", defaultdir: item.defaultdir ?? 0, xdim: item.xdim ?? 1, ydim: item.ydim ?? 1, partcolors: item.partcolors ?? { color: [] }, name: item.name ?? "", description: item.description ?? "", specialtype: item.specialtype ?? 0, canstandon: item.canstandon ?? false, cansiton: item.cansiton ?? false, canlayon: item.canlayon ?? false, furniline: item.furniline ?? "", environment: item.environment ?? "", rare: item.rare ?? false, tradeable: item.tradeable, bc: item.bc, customparams: item.customparams, itemType: "s", }); } } // Process wall items const wallTypes = data?.wallitemtypes?.furnitype; if (Array.isArray(wallTypes)) { for (const item of wallTypes) { if (!item.classname) continue; map.set(item.classname, { id: item.id ?? 0, classname: item.classname, revision: item.revision ?? 0, category: item.category ?? "", defaultdir: item.defaultdir ?? 0, xdim: item.xdim ?? 1, ydim: item.ydim ?? 1, partcolors: item.partcolors ?? { color: [] }, name: item.name ?? "", description: item.description ?? "", specialtype: item.specialtype ?? 0, canstandon: item.canstandon ?? false, cansiton: item.cansiton ?? false, canlayon: item.canlayon ?? false, furniline: item.furniline ?? "", environment: item.environment ?? "", rare: item.rare ?? false, tradeable: item.tradeable, bc: item.bc, customparams: item.customparams, itemType: "i", }); } } console.debug( `[habbo-it] Cached ${map.size} items (${roomTypes?.length ?? 0} room + ${wallTypes?.length ?? 0} wall)`, ); cache = map; cacheTimestamp = now; return map; } catch (err) { console.warn("[habbo-it] Fetch error:", (err as Error).message); return cache || new Map(); } finally { loading = null; } })(); return loading; } /** * Lookup a single item by classname from habbo.it furnidata. * Tries exact match first, then base classname (without color variant). */ export async function lookupHabboIt(classname: string): Promise { const map = await getHabboItFurnidata(); // 1. Exact match const exact = map.get(classname); if (exact) return exact; // 2. If classname has color variant (e.g. "chair*4"), try base if (classname.includes("*")) { const base = classname.split("*")[0]; // Try base with *0 first (most common default) const variant0 = map.get(`${base}*0`); if (variant0) return variant0; // Try just the base const baseMatch = map.get(base); if (baseMatch) return baseMatch; } // 3. If no variant in classname, try *0 if (!classname.includes("*")) { const withVariant = map.get(`${classname}*0`); if (withVariant) return withVariant; } return null; } /** Clear the cache (useful after manual edits) */ export function clearHabboItCache(): void { cache = null; cacheTimestamp = 0; }