perf: enable source-specific asset downloads and make nitro/icon URLs optional
Import improvements:
- importSingleFurni now accepts sourceSwfBaseUrl, nitroBaseUrl, iconBaseUrl
params to try source-specific asset downloads before falling back to
the official Habbo CDN (images.habbo.com)
- Source-specific URL cascade:
1. Try sourceSwfBaseUrl/hof_furni/{rev}/{name}.swf
2. Try sourceNitroBaseUrl/{name}.nitro (pre-made nitro bundles)
3. Fallback to images.habbo.com/dcr/hof_furni/{rev}/{name}.swf
- Same fallback chain for icon downloads
- Clone sources can now have empty nitroBaseUrl/iconBaseUrl (retro
hotels without nitro support)
- Added VirtualCity source (verified SWF downloads via virtualc.nl/dcr)
- Added sourceSwfBaseUrl field to CloneSource interface
- Both batch and single import routes pass source params through
- Clone POST route accepts sourceSwfBaseUrl, makes nitro/icon optional
- Nitro fallback download tries .nitro files before SWF conversion
This commit is contained in:
1 parent
4c93dc38c6
commit
a1775fbf1e
5 files changed
+156
-40
No files matched your search
@@ -6,6 +6,7 @@ import { apiError, apiOk } from "@/lib/api-response";
|
||||
import { CatalogPages, db, ItemsBase } from "@/lib/db";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { logAudit } from "@/lib/services/audit";
|
||||
import { getSource } from "@/lib/services/clone-sources";
|
||||
import { getFurniAssetDirs } from "@/lib/services/furni-asset-dirs";
|
||||
import {
|
||||
autoPriceFurni,
|
||||
@@ -260,6 +261,10 @@ export const POST = withAdmin(
|
||||
|
||||
// ── Batch import ─────────────────────────────────────────────────
|
||||
if (Array.isArray(body.items)) {
|
||||
const { sourceId } = body as { sourceId?: string };
|
||||
let source: Awaited<ReturnType<typeof getSource>> | null = null;
|
||||
if (sourceId) source = await getSource(sourceId);
|
||||
|
||||
const results: Array<{
|
||||
classname: string;
|
||||
ok: boolean;
|
||||
@@ -291,6 +296,9 @@ export const POST = withAdmin(
|
||||
type: item.type ?? "flooritem",
|
||||
revision: item.revision ?? 0,
|
||||
category: item.category ?? "unknown",
|
||||
sourceSwfBaseUrl: source?.sourceSwfBaseUrl,
|
||||
nitroBaseUrl: source?.nitroBaseUrl || undefined,
|
||||
iconBaseUrl: source?.iconBaseUrl || undefined,
|
||||
});
|
||||
|
||||
results.push({ classname: item.classname, ...result });
|
||||
@@ -341,10 +349,22 @@ export const POST = withAdmin(
|
||||
}
|
||||
|
||||
// ── Single import ────────────────────────────────────────────────
|
||||
const { id, classname, name, description, type, revision, category } = body;
|
||||
const {
|
||||
id,
|
||||
classname,
|
||||
name,
|
||||
description,
|
||||
type,
|
||||
revision,
|
||||
category,
|
||||
sourceId,
|
||||
} = body;
|
||||
|
||||
if (!classname || !name) return apiError("Missing classname or name", 400);
|
||||
|
||||
let source: Awaited<ReturnType<typeof getSource>> | null = null;
|
||||
if (sourceId) source = await getSource(sourceId);
|
||||
|
||||
const result = await importSingleFurni({
|
||||
id: id ?? 0,
|
||||
classname,
|
||||
@@ -353,6 +373,9 @@ export const POST = withAdmin(
|
||||
type: type ?? "flooritem",
|
||||
revision: revision ?? 0,
|
||||
category: category ?? "unknown",
|
||||
sourceSwfBaseUrl: source?.sourceSwfBaseUrl,
|
||||
nitroBaseUrl: source?.nitroBaseUrl || undefined,
|
||||
iconBaseUrl: source?.iconBaseUrl || undefined,
|
||||
});
|
||||
|
||||
if (!result.ok) return apiError(result.error ?? "Import failed", 409);
|
||||
|
||||
Reference in new issue
Block a user