feat: locally store assets
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { mkdir, readdir } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { file, hash, write } from "bun";
|
||||
import { TTLCache } from "enka.ts";
|
||||
import { AvatarStore } from "@/type/enka";
|
||||
|
||||
export const cache = new TTLCache();
|
||||
|
||||
export class AssetManager {
|
||||
private cache = new Map<string, ArrayBuffer>();
|
||||
|
||||
async getMissingAssets() {
|
||||
const store = await getEnkaAvatarStore();
|
||||
await mkdir("store").catch(() => {});
|
||||
const existing = await readdir("store");
|
||||
const missing = new Set<string>();
|
||||
Object.values(store)
|
||||
.flatMap((a) => [
|
||||
...a.Consts,
|
||||
...Object.values(a.Skills),
|
||||
a.SideIconName.replace("AvatarIcon_Side", "Gacha_AvatarImg"),
|
||||
])
|
||||
.map((n) => existing.includes(hash(n).toString(36)) || missing.add(n));
|
||||
|
||||
console.log(
|
||||
`${missing.size} assets missing, ${existing.length} assets in store`,
|
||||
);
|
||||
|
||||
for (const name of missing)
|
||||
await fetch(new URL(name, "https://enka.network"))
|
||||
.then((res) => {
|
||||
if (res.status === 404) {
|
||||
console.warn(`Asset not found: ${name}`);
|
||||
return new ArrayBuffer(0);
|
||||
}
|
||||
if (!res.ok)
|
||||
throw new Error(`Failed to fetch ${name}: ${res.statusText}`);
|
||||
return res.arrayBuffer();
|
||||
})
|
||||
.then((d) => write(`store/${hash(name).toString(36)}`, d));
|
||||
|
||||
console.log("All missing assets fetched and stored");
|
||||
}
|
||||
|
||||
async load(path: string) {
|
||||
if (this.cache.has(path)) return this.cache.get(path)!;
|
||||
const local = file(join("store", hash(path).toString(36)));
|
||||
if (await local.exists()) return local.arrayBuffer();
|
||||
|
||||
const data = await fetch(new URL(path, "https://enka.network")).then(
|
||||
(res) => res.arrayBuffer(),
|
||||
);
|
||||
this.cache.set(path, data);
|
||||
queueMicrotask(() => write(local, data));
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getEnkaAvatarStore(): Promise<AvatarStore> {
|
||||
const cached = cache.get("enkaAvatarStore");
|
||||
if (cached) return cached as AvatarStore;
|
||||
|
||||
const response = await fetch(
|
||||
"https://github.com/EnkaNetwork/API-docs/raw/refs/heads/master/store/gi/avatars.json",
|
||||
);
|
||||
const data = await response.json();
|
||||
cache.set("enkaAvatarStore", data, 86400); // 24 hours
|
||||
return data;
|
||||
}
|
||||
Reference in New Issue
Block a user