Files
EpicNext-Cms/src/lib/services/clone-sources.ts
T
openhands 2fba923a3a
CI / check (push) Successful in 32s
CI / release (push) Skipped
CI / deploy (push) Successful in 1m18s
feat: browser headers on audit fetches + verified clone furnidata sources
2026-08-03 19:00:45 +02:00

87 lines
2.4 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;
}
const KEY = "clone_sources";
/**
* Verified working sources (checked Aug 2026). Used as the default list and
* as the fallback when the `clone_sources` site setting is empty.
*/
export const DEFAULT_SOURCES: CloneSource[] = [
{
id: "default-habbo",
name: "Habbo",
furnidataUrl:
"https://raw.githubusercontent.com/Izzxt/habbo-resource/master/gamedata/furnidata.json",
nitroBaseUrl: "",
iconBaseUrl: "",
},
{
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",
},
{
id: "default-hubba",
name: "Hubba.cc",
furnidataUrl: "https://nitro.hubba.cc/gamedata/FurnitureData.json",
nitroBaseUrl: "https://nitro.hubba.cc/bundled/furniture",
iconBaseUrl: "",
},
];
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(/\/+$/, ""),
};
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;
}