Add multi-hotel Habbo gamedata locale in CMS settings.
Local Build and Deploy / deploy (push) Failing after 33s

Furni name suggestions, import enrichment, and badge texts now follow habbo_gamedata_hotel instead of hardcoded habbo.it.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-17 22:28:18 +02:00
1 parent 7bc0845932
commit d1baaf798a
15 files changed
+275 -65

No files matched your search

+30 -23
View File
@@ -1,53 +1,61 @@
/**
* habbo.it furnidata cache — downloads once, keeps in memory.
* Provides instant lookup by classname for enriching imports.
* Official Habbo furnidata cache — hotel chosen in CMS settings
* (`habbo_gamedata_hotel`). Downloads once per hotel, keeps in memory.
*/
export type { HabboItFurniEntry } from "@/types/furni";
import type { HabboItFurniEntry } from "@/types/furni";
import {
getHabboGamedataHotel,
habboFurnidataUrl,
type HabboGamedataHotel,
} from "@/lib/habbo-gamedata-hotel";
const FURNIDATA_URL = "https://www.habbo.it/gamedata/furnidata_json/1";
const CACHE_TTL = 30 * 60 * 1000; // 30 minutes
let cache: Map<string, HabboItFurniEntry> | null = null;
let cacheHotel: HabboGamedataHotel | null = null;
let cacheTimestamp = 0;
let loading: Promise<Map<string, HabboItFurniEntry>> | null = null;
/**
* Fetches and parses the full habbo.it furnidata JSON.
* Results are cached in memory for 30 minutes.
* Fetches and parses the full official Habbo furnidata JSON for the
* configured hotel. Results are cached in memory for 30 minutes.
*/
export async function getHabboItFurnidata(): Promise<
Map<string, HabboItFurniEntry>
> {
const hotel = await getHabboGamedataHotel();
const now = Date.now();
// Return cache if still valid
if (cache && now - cacheTimestamp < CACHE_TTL) {
if (
cache &&
cacheHotel === hotel &&
now - cacheTimestamp < CACHE_TTL
) {
return cache;
}
// Deduplicate concurrent requests
if (loading) return loading;
loading = (async () => {
const url = habboFurnidataUrl(hotel);
try {
console.debug("[habbo-it] Fetching furnidata from habbo.it...");
const res = await fetch(FURNIDATA_URL, {
console.debug(`[habbo-furnidata] Fetching furnidata from ${url}...`);
const res = await fetch(url, {
signal: AbortSignal.timeout(20000),
headers: { Accept: "application/json" },
});
if (!res.ok) {
console.warn(`[habbo-it] Fetch failed: ${res.status}`);
console.warn(`[habbo-furnidata] Fetch failed: ${res.status}`);
return cache || new Map();
}
const data = await res.json();
const map = new Map<string, HabboItFurniEntry>();
// Process room items (floor furniture)
const roomTypes = data?.roomitemtypes?.furnitype;
if (Array.isArray(roomTypes)) {
for (const item of roomTypes) {
@@ -78,7 +86,6 @@ export async function getHabboItFurnidata(): Promise<
}
}
// Process wall items
const wallTypes = data?.wallitemtypes?.furnitype;
if (Array.isArray(wallTypes)) {
for (const item of wallTypes) {
@@ -110,13 +117,17 @@ export async function getHabboItFurnidata(): Promise<
}
console.debug(
`[habbo-it] Cached ${map.size} items (${roomTypes?.length ?? 0} room + ${wallTypes?.length ?? 0} wall)`,
`[habbo-furnidata] Cached ${map.size} items from habbo.${hotel} (${roomTypes?.length ?? 0} room + ${wallTypes?.length ?? 0} wall)`,
);
cache = map;
cacheTimestamp = now;
cacheHotel = hotel;
cacheTimestamp = Date.now();
return map;
} catch (err) {
console.warn("[habbo-it] Fetch error:", (err as Error).message);
console.warn(
"[habbo-furnidata] Fetch error:",
(err as Error).message,
);
return cache || new Map();
} finally {
loading = null;
@@ -127,7 +138,7 @@ export async function getHabboItFurnidata(): Promise<
}
/**
* Lookup a single item by classname from habbo.it furnidata.
* Lookup a single item by classname from official Habbo furnidata.
* Tries exact match first, then base classname (without color variant).
*/
export async function lookupHabboIt(
@@ -135,22 +146,17 @@ export async function lookupHabboIt(
): Promise<HabboItFurniEntry | null> {
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;
@@ -159,8 +165,9 @@ export async function lookupHabboIt(
return null;
}
/** Clear the cache (useful after manual edits) */
/** Clear the cache (e.g. after changing habbo_gamedata_hotel). */
export function clearHabboItCache(): void {
cache = null;
cacheHotel = null;
cacheTimestamp = 0;
}