/** * 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 }