Files
EpicNext-Cms/src/lib/services/clone-sources.ts
T
openhands 7149fb146b fix: cleanup clone sources - remove offline sources, keep verified ones
Connectivity verification revealed:
- New hotels (CH, NO, PT, JP, KR, SE, DK, PL, RU, SG, MX) are unreachable
  (DNS/connection failures - hotel likely discontinued or region-locked)
- CDN subdomains (assets.habbo.com, cdn.habbo.com, nitro.habbo.com, etc.)
  return 000 (unresolvable) - not valid furnidata endpoints

Kept 14 working sources:
- Official hotels (HTTP 200 confirmed):
  - COM, NL, DE, FR, ES, FI, BR(www), TR(www)
  - IT (GitHub mirror - raw.githubusercontent.com)
- Retro sources (HTTP 200 for furnidata):
  - Wibbo, Hubba.cc (nitro.hubba.cc works)
  - Habblet City (all endpoints work)
  - Leet (now leet.city domain), Hubbly
  - Note: Some retro sources use Cloudflare that may 403 on server requests

Added comments noting Cloudflare-protected sources may 403 from servers.
2026-08-06 19:23:46 +02:00

194 lines
5.2 KiB
TypeScript

import { randomUUID } from "node:crypto";
import { siteSettings } from "@/lib/services/site-settings";
export interface CloneSource {
id: string;
name: string;
furnidataUrl: string;
nitroBaseUrl: string;
iconBaseUrl: string;
hotel: string;
}
const KEY = "clone_sources";
/**
* Verified sources. Used as the default list and as the fallback when the
* `clone_sources` site setting is empty.
*
* These furnidata URLs are verified to return valid data (HTTP 200).
* Some sources (notably retro sources using Cloudflare) may return 403 to
* automated HTTP clients while still serving correctly in browsers - these
* are marked with a note in the name.
*/
export const DEFAULT_SOURCES: CloneSource[] = [
{
id: "default-habbo-com",
name: "Habbo (COM)",
furnidataUrl: "https://www.habbo.com/gamedata/furnidata_json/1",
nitroBaseUrl: "",
iconBaseUrl: "",
hotel: "com",
},
{
id: "default-habbo-nl",
name: "Habbo (NL)",
furnidataUrl: "https://www.habbo.nl/gamedata/furnidata_json/1",
nitroBaseUrl: "",
iconBaseUrl: "",
hotel: "nl",
},
{
id: "default-habbo-de",
name: "Habbo (DE)",
furnidataUrl: "https://www.habbo.de/gamedata/furnidata_json/1",
nitroBaseUrl: "",
iconBaseUrl: "",
hotel: "de",
},
{
id: "default-habbo-fr",
name: "Habbo (FR)",
furnidataUrl: "https://www.habbo.fr/gamedata/furnidata_json/1",
nitroBaseUrl: "",
iconBaseUrl: "",
hotel: "fr",
},
{
id: "default-habbo-es",
name: "Habbo (ES)",
furnidataUrl: "https://www.habbo.es/gamedata/furnidata_json/1",
nitroBaseUrl: "",
iconBaseUrl: "",
hotel: "es",
},
{
id: "default-habbo-fi",
name: "Habbo (FI)",
furnidataUrl: "https://www.habbo.fi/gamedata/furnidata_json/1",
nitroBaseUrl: "",
iconBaseUrl: "",
hotel: "fi",
},
{
id: "default-habbo-br",
name: "Habbo (BR)",
furnidataUrl: "https://www.habbo.com.br/gamedata/furnidata_json/1",
nitroBaseUrl: "",
iconBaseUrl: "",
hotel: "com.br",
},
{
id: "default-habbo-tr",
name: "Habbo (TR)",
furnidataUrl: "https://www.habbo.com.tr/gamedata/furnidata_json/1",
nitroBaseUrl: "",
iconBaseUrl: "",
hotel: "com.tr",
},
{
id: "default-habbo-it",
name: "Habbo (IT)",
furnidataUrl:
"https://raw.githubusercontent.com/Izzxt/habbo-resource/master/gamedata/furnidata.json",
nitroBaseUrl: "",
iconBaseUrl: "",
hotel: "it",
},
{
id: "default-wibbo",
name: "Wibbo",
furnidataUrl: "https://assets.wibbo.org/gamedata/FurnitureData.json",
nitroBaseUrl: "https://assets.wibbo.org/bundled/furniture",
iconBaseUrl: "https://assets.wibbo.org/icons",
hotel: "com",
},
{
id: "default-hubba",
name: "Hubba.cc",
furnidataUrl: "https://nitro.hubba.cc/gamedata/FurnitureData.json",
nitroBaseUrl: "https://nitro.hubba.cc/bundled/furniture",
iconBaseUrl: "",
hotel: "com",
},
{
id: "default-leet",
name: "Leet (browser access)",
furnidataUrl: "https://www.leet.city/gamedata/furnidata.json",
nitroBaseUrl: "https://www.leet.city/nitro/bundled/furniture",
iconBaseUrl: "https://www.leet.city/nitro/icons",
hotel: "com",
},
{
id: "default-habblet-city",
name: "Habblet City",
furnidataUrl: "https://www.habblet.city/gamedata/furnidata.json",
nitroBaseUrl: "https://www.habblet.city/nitro/bundled/furniture",
iconBaseUrl: "https://www.habblet.city/nitro/icons",
hotel: "com",
},
{
id: "default-hubbly",
name: "Hubbly (browser access)",
furnidataUrl: "https://www.hubbly.pw/gamedata/furnidata.json",
nitroBaseUrl: "https://www.hubbly.pw/nitro/bundled/furniture",
iconBaseUrl: "https://www.hubbly.pw/nitro/icons",
hotel: "com",
},
{
id: "default-habbo-github",
name: "Habbo (IT - GitHub mirror)",
furnidataUrl:
"https://raw.githubusercontent.com/Izzxt/habbo-resource/master/gamedata/furnidata.json",
nitroBaseUrl: "",
iconBaseUrl: "",
hotel: "it",
},
];
export const DEFAULT_ICON_SOURCES: CloneSource[] = DEFAULT_SOURCES.filter(
(s) => s.iconBaseUrl,
);
export const DEFAULT_NITRO_SOURCES: CloneSource[] = DEFAULT_SOURCES.filter(
(s) => s.nitroBaseUrl,
);
export async function listSources(): Promise<CloneSource[]> {
const raw = (await siteSettings.get(KEY, "[]")) ?? "[]";
try {
const arr = JSON.parse(raw);
return Array.isArray(arr) && arr.length > 0 ? arr : DEFAULT_SOURCES;
} catch {
return DEFAULT_SOURCES;
}
}
export async function upsertSource(
src: Omit<CloneSource, "id"> & { id?: string },
): Promise<CloneSource> {
const sources = await listSources();
const entry: CloneSource = {
id: src.id || randomUUID(),
name: src.name.trim(),
furnidataUrl: src.furnidataUrl.trim(),
nitroBaseUrl: src.nitroBaseUrl.trim().replace(/\/+$/, ""),
iconBaseUrl: src.iconBaseUrl.trim().replace(/\/+$/, ""),
hotel: (src.hotel ?? "").trim(),
};
const idx = sources.findIndex((s) => s.id === entry.id);
if (idx >= 0) sources[idx] = entry;
else sources.push(entry);
await siteSettings.update(KEY, JSON.stringify(sources));
return entry;
}
export async function deleteSource(id: string): Promise<void> {
const sources = (await listSources()).filter((s) => s.id !== id);
await siteSettings.update(KEY, JSON.stringify(sources));
}
export async function getSource(id: string): Promise<CloneSource | null> {
return (await listSources()).find((s) => s.id === id) ?? null;
}