feat : add metrial fetcher

This commit is contained in:
2026-09-06 13:59:32 +07:00 Unverified
parent 2808a27406
commit 37d6d6703f
8 changed files with 3827 additions and 8 deletions
+53 -2
View File
@@ -6,9 +6,11 @@ import { getDb } from "@/db/client";
import {
catalogArtifacts,
catalogCharacters,
catalogMaterials,
catalogWeapons,
} from "@/db/schema";
import {
ascensionSnapshot,
avatarIconName,
characterDetailSchema,
constellationSnapshot,
@@ -17,7 +19,9 @@ import {
talentSnapshot,
} from "@/lib/catalog/lunaris";
import type {
CatalogAscensionGroup,
CatalogConstellation,
CatalogMaterial,
CatalogTalent,
} from "@/lib/catalog/lunaris";
import { customTravelers, isUpstreamTraveler } from "@/lib/catalog/travelers";
@@ -63,6 +67,16 @@ const artifactSchema = z.record(
qualityType: z.string().optional(),
}),
);
const materialSchema = z.record(
z.string(),
z.object({
enName: z.string(),
enDescription: z.string().optional(),
icon: z.string(),
qualityType: z.string().optional(),
type: z.string().optional(),
}),
);
interface SyncedCharacter {
key: string;
@@ -80,6 +94,7 @@ interface SyncedCharacter {
localImage: string | null;
constellations: CatalogConstellation[];
talents: CatalogTalent[];
ascension: CatalogAscensionGroup[];
}
async function getJson(url: string): Promise<unknown> {
@@ -107,6 +122,10 @@ function webpName(value: string): string {
return value.endsWith(".webp") ? value : `${value}.webp`;
}
function pngName(value: string): string {
return value.endsWith(".png") ? value : `${value}.png`;
}
function rarity(value: string | undefined): number | null {
if (!value) return null;
return (
@@ -186,14 +205,26 @@ async function uploadAssets(
}
async function syncCatalogVersion(version: string): Promise<void> {
const [charactersRaw, weaponsRaw, artifactsRaw] = await Promise.all([
const [charactersRaw, weaponsRaw, artifactsRaw, materialsRaw] = await Promise.all([
getJson(`${API_ROOT}/${version}/charlist.json`),
getJson(`${API_ROOT}/${version}/weaponlist.json`),
getJson(`${API_ROOT}/${version}/artifactlist.json`),
getJson(`${API_ROOT}/${version}/materiallist.json`),
]);
const characterData = characterSchema.parse(charactersRaw);
const weaponData = weaponSchema.parse(weaponsRaw);
const artifactData = artifactSchema.parse(artifactsRaw);
const materialData = materialSchema.parse(materialsRaw);
const materials: CatalogMaterial[] = Object.entries(materialData).map(
([key, value]) => ({
key,
name: value.enName,
description: value.enDescription ?? null,
rarity: rarity(value.qualityType),
type: value.type ?? null,
imageKey: `catalog/materials/${pngName(value.icon)}`,
}),
);
const remoteCharacters: SyncedCharacter[] = await mapConcurrent(
Object.entries(characterData).filter(
@@ -207,7 +238,7 @@ async function syncCatalogVersion(version: string): Promise<void> {
const detailRaw = await getOptionalJson(
`${API_ROOT}/${version}/en/char/${encodeURIComponent(key)}.json`,
);
const detail = detailRaw
const detail = detailRaw
? characterDetailSchema.parse(detailRaw)
: null;
const avatarIcon = webpName(avatarIconName(value.CardImg));
@@ -242,6 +273,7 @@ async function syncCatalogVersion(version: string): Promise<void> {
(icon) => `catalog/characters/talents/${webpName(icon)}`,
)
: [],
ascension: detail ? ascensionSnapshot(detail) : [],
};
},
);
@@ -275,6 +307,7 @@ async function syncCatalogVersion(version: string): Promise<void> {
detail,
(icon) => `catalog/characters/talents/${webpName(icon)}`,
),
ascension: ascensionSnapshot(detail),
};
},
);
@@ -339,6 +372,14 @@ async function syncCatalogVersion(version: string): Promise<void> {
source: item.sourceImage,
target: item.imageKey,
});
for (const material of materials) {
const source = materialData[material.key].icon;
uniqueAssets.set(material.imageKey, {
source: `${API_ROOT}/assets/items/${encodeURIComponent(pngName(source))}`,
target: material.imageKey,
type: "image/png",
});
}
for (const character of characters) {
if (character.elementIconKey && character.element)
uniqueAssets.set(character.elementIconKey, {
@@ -414,6 +455,7 @@ async function syncCatalogVersion(version: string): Promise<void> {
nameCardImageKey: item.nameCardImageKey,
constellations: item.constellations,
talents: item.talents,
ascension: item.ascension,
})),
),
{ type: "application/json", acl: "public-read" },
@@ -443,10 +485,16 @@ async function syncCatalogVersion(version: string): Promise<void> {
),
{ type: "application/json", acl: "public-read" },
),
storage.write(
"catalog/materials.json",
JSON.stringify(materials),
{ type: "application/json", acl: "public-read" },
),
]);
await getDb().transaction(async (tx) => {
await tx.delete(catalogCharacters);
await tx.delete(catalogMaterials);
await tx.delete(catalogWeapons);
await tx.delete(catalogArtifacts);
if (characters.length)
@@ -465,8 +513,11 @@ async function syncCatalogVersion(version: string): Promise<void> {
nameCardImageKey: item.nameCardImageKey,
constellations: item.constellations,
talents: item.talents,
ascension: item.ascension,
})),
);
if (materials.length)
await tx.insert(catalogMaterials).values(materials);
if (weapons.length)
await tx
.insert(catalogWeapons)