feat(clone): add hotel field to clone source presets
- CloneSource interface now has a hotel field (maps to habbo_gamedata_hotel) - DEFAULT_SOURCES presets include their associated hotel (it/com) - When cloning from a source with a hotel configured, habbo_gamedata_hotel is set automatically so official furnidata enrichment uses the correct locale - Clone source form UI now has a hotel select dropdown - Source list shows the hotel label when configured - Clone API route passes hotel through to upsertSource
This commit is contained in:
1 parent
79b82e0a31
commit
936053cca6
5 files changed
+53
-1
No files matched your search
@@ -18,7 +18,15 @@ import { toast } from "sonner";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { adminFetch } from "@/lib/admin-fetch";
|
||||
import { HABBO_GAMEDATA_HOTELS } from "@/lib/habbo-gamedata-hotel";
|
||||
import { runSseImport } from "@/lib/sse-client";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -30,6 +38,7 @@ interface CloneSource {
|
||||
furnidataUrl: string;
|
||||
nitroBaseUrl: string;
|
||||
iconBaseUrl: string;
|
||||
hotel: string;
|
||||
}
|
||||
|
||||
interface FurniItem {
|
||||
@@ -70,6 +79,7 @@ function SourceForm({ initial, onSave, onCancel, saving }: SourceFormProps) {
|
||||
const [furnidataUrl, setFurnidataUrl] = useState(initial?.furnidataUrl ?? "");
|
||||
const [nitroBaseUrl, setNitroBaseUrl] = useState(initial?.nitroBaseUrl ?? "");
|
||||
const [iconBaseUrl, setIconBaseUrl] = useState(initial?.iconBaseUrl ?? "");
|
||||
const [hotel, setHotel] = useState(initial?.hotel ?? "");
|
||||
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
@@ -79,6 +89,7 @@ function SourceForm({ initial, onSave, onCancel, saving }: SourceFormProps) {
|
||||
furnidataUrl,
|
||||
nitroBaseUrl,
|
||||
iconBaseUrl,
|
||||
hotel,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -115,6 +126,19 @@ function SourceForm({ initial, onSave, onCancel, saving }: SourceFormProps) {
|
||||
onChange={(e) => setIconBaseUrl(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<Select value={hotel} onValueChange={setHotel}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Hotel (optional)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">None</SelectItem>
|
||||
{HABBO_GAMEDATA_HOTELS.map((h) => (
|
||||
<SelectItem key={h.value} value={h.value}>
|
||||
{h.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button type="submit" size="sm" disabled={saving}>
|
||||
@@ -261,6 +285,12 @@ function SourcesManager({
|
||||
<div className="text-xs text-muted-foreground truncate">
|
||||
{src.furnidataUrl}
|
||||
</div>
|
||||
{src.hotel ? (
|
||||
<div className="text-[10px] text-muted-foreground/70">
|
||||
{HABBO_GAMEDATA_HOTELS.find((h) => h.value === src.hotel)
|
||||
?.label ?? `habbo.${src.hotel}`}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</button>
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
|
||||
@@ -76,7 +76,7 @@ export const POST = withAdmin(
|
||||
{ permission: PERMS.ASSETS_IMPORT },
|
||||
async (request, ctx) => {
|
||||
const body = await request.json();
|
||||
const { name, furnidataUrl, nitroBaseUrl, iconBaseUrl, id } =
|
||||
const { name, furnidataUrl, nitroBaseUrl, iconBaseUrl, hotel, id } =
|
||||
body as Record<string, string>;
|
||||
|
||||
const isHttps = (u: string) => {
|
||||
@@ -104,6 +104,7 @@ export const POST = withAdmin(
|
||||
furnidataUrl,
|
||||
nitroBaseUrl,
|
||||
iconBaseUrl,
|
||||
hotel: hotel ?? "",
|
||||
});
|
||||
|
||||
logAudit({
|
||||
|
||||
@@ -85,6 +85,7 @@ const SOURCE = {
|
||||
furnidataUrl: "https://x/fd.json",
|
||||
nitroBaseUrl: "https://x/furni",
|
||||
iconBaseUrl: "https://x/icons",
|
||||
hotel: "",
|
||||
};
|
||||
const ENTRY = {
|
||||
id: 5,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { eq, inArray, sql } from "drizzle-orm";
|
||||
import { db, ItemsBase } from "@/lib/db";
|
||||
import { HABBO_GAMEDATA_HOTEL_SETTING_KEY } from "@/lib/habbo-gamedata-hotel";
|
||||
import { logger } from "@/lib/logger";
|
||||
import { extractFurniIconPng } from "@/lib/services/clone-icon";
|
||||
import type { CloneSource } from "@/lib/services/clone-sources";
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
} from "@/lib/services/furni-import";
|
||||
import { browserHeaders } from "@/lib/services/import/core/browser-headers";
|
||||
import { downloadFile } from "@/lib/services/import/core/download";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
import { parseNitroBundle } from "@/lib/services/swf/nitro-builder";
|
||||
|
||||
export interface SourceFurni {
|
||||
@@ -221,6 +223,15 @@ export async function cloneSingleFurni(params: {
|
||||
const { classname, itemType } = entry;
|
||||
const warnings: string[] = [];
|
||||
|
||||
// Set the active Habbo hotel for official furnidata enrichment.
|
||||
if (source.hotel) {
|
||||
try {
|
||||
await siteSettings.update(HABBO_GAMEDATA_HOTEL_SETTING_KEY, source.hotel);
|
||||
} catch {
|
||||
/* non-blocking */
|
||||
}
|
||||
}
|
||||
|
||||
// Path-traversal guard: classname comes from remote furnidata and is used to
|
||||
// build file paths — reject anything that doesn't look like a safe furni name.
|
||||
if (!/^[\w\-.*]+$/.test(classname)) {
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface CloneSource {
|
||||
furnidataUrl: string;
|
||||
nitroBaseUrl: string;
|
||||
iconBaseUrl: string;
|
||||
hotel: string;
|
||||
}
|
||||
|
||||
const KEY = "clone_sources";
|
||||
@@ -23,6 +24,7 @@ export const DEFAULT_SOURCES: CloneSource[] = [
|
||||
"https://raw.githubusercontent.com/Izzxt/habbo-resource/master/gamedata/furnidata.json",
|
||||
nitroBaseUrl: "",
|
||||
iconBaseUrl: "",
|
||||
hotel: "it",
|
||||
},
|
||||
{
|
||||
id: "default-wibbo",
|
||||
@@ -30,6 +32,7 @@ export const DEFAULT_SOURCES: CloneSource[] = [
|
||||
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",
|
||||
@@ -37,6 +40,7 @@ export const DEFAULT_SOURCES: CloneSource[] = [
|
||||
furnidataUrl: "https://nitro.hubba.cc/gamedata/FurnitureData.json",
|
||||
nitroBaseUrl: "https://nitro.hubba.cc/bundled/furniture",
|
||||
iconBaseUrl: "",
|
||||
hotel: "com",
|
||||
},
|
||||
{
|
||||
id: "default-sodaho",
|
||||
@@ -44,6 +48,7 @@ export const DEFAULT_SOURCES: CloneSource[] = [
|
||||
furnidataUrl: "https://assets.sodaho.tel/gamedata/FurnitureData.json",
|
||||
nitroBaseUrl: "",
|
||||
iconBaseUrl: "",
|
||||
hotel: "com",
|
||||
},
|
||||
{
|
||||
id: "default-leet",
|
||||
@@ -51,6 +56,7 @@ export const DEFAULT_SOURCES: CloneSource[] = [
|
||||
furnidataUrl: "https://www.leet.ws/gamedata/furnidata.json",
|
||||
nitroBaseUrl: "https://www.leet.ws/nitro/bundled/furniture",
|
||||
iconBaseUrl: "https://www.leet.ws/nitro/icons",
|
||||
hotel: "com",
|
||||
},
|
||||
{
|
||||
id: "default-hubbly",
|
||||
@@ -58,6 +64,7 @@ export const DEFAULT_SOURCES: CloneSource[] = [
|
||||
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-habbletcity",
|
||||
@@ -65,6 +72,7 @@ export const DEFAULT_SOURCES: CloneSource[] = [
|
||||
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",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -96,6 +104,7 @@ export async function upsertSource(
|
||||
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;
|
||||
|
||||
Reference in new issue
Block a user