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.
194 lines
5.2 KiB
TypeScript
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;
|
|
}
|