fix(catalog): check source assets and offer reachable import alternatives
This commit is contained in:
1 parent
8c1efae296
commit
159b1f7d1b
10 files changed
+646
-56
No files matched your search
@@ -0,0 +1,98 @@
|
||||
import type {
|
||||
AssetAvailability,
|
||||
SourceAssetCheck,
|
||||
SourceAssetItem,
|
||||
} from "@/lib/furni/source-assets";
|
||||
import { relatedFurniture } from "@/lib/furni/source-assets";
|
||||
import type { CloneSource } from "./clone-sources";
|
||||
import { browserHeaders } from "./import/core/browser-headers";
|
||||
|
||||
const cache = new Map<string, { expires: number; state: AssetAvailability }>();
|
||||
export async function probeAsset(url: string): Promise<AssetAvailability> {
|
||||
const cached = cache.get(url);
|
||||
if (cached && cached.expires > Date.now()) return cached.state;
|
||||
let state: AssetAvailability = "unknown";
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "HEAD",
|
||||
signal: AbortSignal.timeout(6000),
|
||||
headers: browserHeaders(),
|
||||
});
|
||||
if (
|
||||
response.ok &&
|
||||
!response.headers.get("content-type")?.includes("text/html")
|
||||
)
|
||||
state = "available";
|
||||
else if (response.status === 404 || response.status === 410)
|
||||
state = "missing";
|
||||
} catch {
|
||||
/* A network problem is not proof the asset is absent. */
|
||||
}
|
||||
if (cache.size >= 2000) cache.clear();
|
||||
cache.set(url, { expires: Date.now() + 60000, state });
|
||||
return state;
|
||||
}
|
||||
export function furnitureAssetUrls(
|
||||
item: SourceAssetItem,
|
||||
source?: CloneSource | null,
|
||||
) {
|
||||
const base = encodeURIComponent(item.classname.split("*")[0]);
|
||||
const urls: string[] = [];
|
||||
if (source?.nitroBaseUrl)
|
||||
urls.push(`${source.nitroBaseUrl.replace(/\/+$/, "")}/${base}.nitro`);
|
||||
if (source?.sourceSwfBaseUrl) {
|
||||
const root = source.sourceSwfBaseUrl.replace(/\/+$/, "");
|
||||
urls.push(
|
||||
`${root}/hof_furni/${item.revision}/${base}.swf`,
|
||||
`${root}/hof_furni/${base}.swf`,
|
||||
);
|
||||
}
|
||||
urls.push(
|
||||
`https://images.habbo.com/dcr/hof_furni/${item.revision}/${base}.swf`,
|
||||
);
|
||||
return urls;
|
||||
}
|
||||
async function available(
|
||||
item: SourceAssetItem,
|
||||
source?: CloneSource | null,
|
||||
): Promise<AssetAvailability> {
|
||||
const states = await Promise.all(
|
||||
furnitureAssetUrls(item, source).map(probeAsset),
|
||||
);
|
||||
return states.includes("available")
|
||||
? "available"
|
||||
: states.every((state) => state === "missing")
|
||||
? "missing"
|
||||
: "unknown";
|
||||
}
|
||||
export async function inspectSourceAssets(
|
||||
item: SourceAssetItem,
|
||||
official: SourceAssetItem[],
|
||||
source?: CloneSource | null,
|
||||
): Promise<SourceAssetCheck> {
|
||||
const canonical = official.find(
|
||||
(entry) => entry.classname === item.classname,
|
||||
);
|
||||
const effective = {
|
||||
...item,
|
||||
revision: item.revision || canonical?.revision || 0,
|
||||
};
|
||||
const state = await available(effective, source);
|
||||
const alternatives: SourceAssetItem[] = [];
|
||||
if (state === "missing") {
|
||||
for (const candidate of relatedFurniture(
|
||||
{ ...effective, name: canonical?.name || item.name },
|
||||
official,
|
||||
)) {
|
||||
if ((await available(candidate)) === "available")
|
||||
alternatives.push(candidate);
|
||||
if (alternatives.length === 3) break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
classname: item.classname,
|
||||
revision: effective.revision,
|
||||
state,
|
||||
alternatives,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user