feat: character list
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
"@takumi-rs/image-response": "^0.71.7",
|
"@takumi-rs/image-response": "^0.71.7",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"elysia": "latest",
|
"elysia": "latest",
|
||||||
"enka.ts": "^1.1.9",
|
"enka.ts": "^1.1.10",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
},
|
},
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
|
|
||||||
"elysia": ["[email protected]", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-2UlmNEjPJVA/WZVPYKy+KdsrfFwwNlqSBW1lHz6i2AHc75k7gV4Rhm01kFeotH7PDiHIX2G8X3KnRPc33SGVIg=="],
|
"elysia": ["[email protected]", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-2UlmNEjPJVA/WZVPYKy+KdsrfFwwNlqSBW1lHz6i2AHc75k7gV4Rhm01kFeotH7PDiHIX2G8X3KnRPc33SGVIg=="],
|
||||||
|
|
||||||
"enka.ts": ["[email protected].9", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-6dBNMIQ/LQ2y6RtcxJCGBjTPOyGcF93OdGbxJ5NW/p9KZ+0Jaqj4dZfrVvYpynbBMGj1NNuvIW3f+g6m5SGrlg=="],
|
"enka.ts": ["[email protected].10", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lf6nEc2kPiycqS4Is4WxJI8iGMWcOYOphEX42TTy1zn6dizTDjoF8ViD2L2pAjf98gSXz5DzIA6uK0/1AfRVEg=="],
|
||||||
|
|
||||||
"exact-mirror": ["[email protected]", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg=="],
|
"exact-mirror": ["[email protected]", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg=="],
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
"@takumi-rs/image-response": "^0.71.7",
|
"@takumi-rs/image-response": "^0.71.7",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"elysia": "latest",
|
"elysia": "latest",
|
||||||
"enka.ts": "^1.1.9",
|
"enka.ts": "^1.1.10",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4"
|
"react-dom": "^19.2.4"
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-1
@@ -37,10 +37,11 @@ export class AssetManager {
|
|||||||
...Object.values(a.Skills),
|
...Object.values(a.Skills),
|
||||||
...Object.values(a.Costumes ?? {}).flatMap((c) => [c.Art, c.SideIcon]),
|
...Object.values(a.Costumes ?? {}).flatMap((c) => [c.Art, c.SideIcon]),
|
||||||
a.SideIconName.replace("AvatarIcon_Side", "Gacha_AvatarImg"),
|
a.SideIconName.replace("AvatarIcon_Side", "Gacha_AvatarImg"),
|
||||||
|
a.SideIconName.replace("_Side", ""),
|
||||||
]),
|
]),
|
||||||
...Object.values(weapons).flatMap((w) => [w.Icon, w.AwakenIcon]),
|
...Object.values(weapons).flatMap((w) => [w.Icon, w.AwakenIcon]),
|
||||||
...Object.values(relics.Items).map((r) => r.Icon),
|
...Object.values(relics.Items).map((r) => r.Icon),
|
||||||
...Object.values(pfps).map((p) => p.IconPath),
|
...Object.values(pfps).map((p) => p.IconPath.replace("_Circle", "")),
|
||||||
...Object.values(namecards).map((p) => p.Icon),
|
...Object.values(namecards).map((p) => p.Icon),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,3 +50,5 @@ export function createLogger(label?: string) {
|
|||||||
console.write(normalized);
|
console.write(normalized);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Logger = ReturnType<typeof createLogger>;
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
// GENERATED BY util/genNamecardRelation
|
||||||
|
|
||||||
|
export const CharacterNamecardMap: Record<string, number> = {
|
||||||
|
"10000002": 210081,
|
||||||
|
"10000003": 210008,
|
||||||
|
"10000006": 210011,
|
||||||
|
"10000014": 210009,
|
||||||
|
"10000015": 210010,
|
||||||
|
"10000016": 210005,
|
||||||
|
"10000020": 210006,
|
||||||
|
"10000021": 210003,
|
||||||
|
"10000022": 210007,
|
||||||
|
"10000023": 210017,
|
||||||
|
"10000024": 210039,
|
||||||
|
"10000025": 210018,
|
||||||
|
"10000026": 210058,
|
||||||
|
"10000027": 210038,
|
||||||
|
"10000029": 210004,
|
||||||
|
"10000030": 210044,
|
||||||
|
"10000031": 210013,
|
||||||
|
"10000032": 210016,
|
||||||
|
"10000033": 210046,
|
||||||
|
"10000034": 210014,
|
||||||
|
"10000035": 210019,
|
||||||
|
"10000036": 210040,
|
||||||
|
"10000037": 210053,
|
||||||
|
"10000038": 210054,
|
||||||
|
"10000039": 210043,
|
||||||
|
"10000041": 210015,
|
||||||
|
"10000042": 210020,
|
||||||
|
"10000043": 210012,
|
||||||
|
"10000044": 210045,
|
||||||
|
"10000045": 210063,
|
||||||
|
"10000046": 210059,
|
||||||
|
"10000047": 210073,
|
||||||
|
"10000048": 210069,
|
||||||
|
"10000049": 210082,
|
||||||
|
"10000050": 210101,
|
||||||
|
"10000051": 210070,
|
||||||
|
"10000052": 210092,
|
||||||
|
"10000053": 210083,
|
||||||
|
"10000054": 210093,
|
||||||
|
"10000055": 210104,
|
||||||
|
"10000056": 210094,
|
||||||
|
"10000057": 210105,
|
||||||
|
"10000058": 210112,
|
||||||
|
"10000059": 210122,
|
||||||
|
"10000060": 210119,
|
||||||
|
"10000061": 210162,
|
||||||
|
"10000062": 210095,
|
||||||
|
"10000063": 210107,
|
||||||
|
"10000064": 210108,
|
||||||
|
"10000065": 210120,
|
||||||
|
"10000066": 210114,
|
||||||
|
"10000067": 210125,
|
||||||
|
"10000068": 210126,
|
||||||
|
"10000069": 210124,
|
||||||
|
"10000070": 210136,
|
||||||
|
"10000071": 210134,
|
||||||
|
"10000072": 210135,
|
||||||
|
"10000073": 210140,
|
||||||
|
"10000074": 210141,
|
||||||
|
"10000075": 210143,
|
||||||
|
"10000076": 210144,
|
||||||
|
"10000077": 210148,
|
||||||
|
"10000078": 210147,
|
||||||
|
"10000079": 210152,
|
||||||
|
"10000080": 210153,
|
||||||
|
"10000081": 210156,
|
||||||
|
"10000082": 210155,
|
||||||
|
"10000083": 210166,
|
||||||
|
"10000084": 210165,
|
||||||
|
"10000085": 210167,
|
||||||
|
"10000086": 210175,
|
||||||
|
"10000087": 210174,
|
||||||
|
"10000088": 210181,
|
||||||
|
"10000089": 210180,
|
||||||
|
"10000090": 210185,
|
||||||
|
"10000091": 210184,
|
||||||
|
"10000092": 210188,
|
||||||
|
"10000093": 210187,
|
||||||
|
"10000094": 210193,
|
||||||
|
"10000095": 210201,
|
||||||
|
"10000096": 210195,
|
||||||
|
"10000097": 210202,
|
||||||
|
"10000098": 210200,
|
||||||
|
"10000099": 210205,
|
||||||
|
"10000100": 210210,
|
||||||
|
"10000101": 210208,
|
||||||
|
"10000102": 210209,
|
||||||
|
"10000103": 210219,
|
||||||
|
"10000104": 210221,
|
||||||
|
"10000105": 210222,
|
||||||
|
"10000106": 210226,
|
||||||
|
"10000107": 210227,
|
||||||
|
"10000108": 210228,
|
||||||
|
"10000109": 210233,
|
||||||
|
"10000110": 210237,
|
||||||
|
"10000111": 210236,
|
||||||
|
"10000112": 210241,
|
||||||
|
"10000113": 210242,
|
||||||
|
"10000114": 210244,
|
||||||
|
"10000115": 210245,
|
||||||
|
"10000116": 121279,
|
||||||
|
"10000119": 210253,
|
||||||
|
"10000120": 210254,
|
||||||
|
"10000121": 121279,
|
||||||
|
"10000122": 210261,
|
||||||
|
"10000123": 210263,
|
||||||
|
"10000124": 210264,
|
||||||
|
"10000125": 210267,
|
||||||
|
"10000126": 121462,
|
||||||
|
"10000127": 210269,
|
||||||
|
"10000128": 210273,
|
||||||
|
};
|
||||||
+6
-1
@@ -107,6 +107,7 @@ export function card(app: App) {
|
|||||||
.get(
|
.get(
|
||||||
"/genshin/:uid",
|
"/genshin/:uid",
|
||||||
async ({ params: { uid }, query: { debug }, log }) => {
|
async ({ params: { uid }, query: { debug }, log }) => {
|
||||||
|
// #region Character List
|
||||||
const [[avatarStore, locs, pfps, namecards], data] =
|
const [[avatarStore, locs, pfps, namecards], data] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
deduper.acquire("stores", () =>
|
deduper.acquire("stores", () =>
|
||||||
@@ -128,7 +129,10 @@ export function card(app: App) {
|
|||||||
pfps[
|
pfps[
|
||||||
`${data.playerInfo.profilePicture.id ?? 1}`
|
`${data.playerInfo.profilePicture.id ?? 1}`
|
||||||
].IconPath.replace("_Circle", ""),
|
].IconPath.replace("_Circle", ""),
|
||||||
namecards[`${data.playerInfo.namecardId ?? 210001}`].Icon,
|
namecards[`${data.playerInfo.nameCardId ?? 210001}`].Icon,
|
||||||
|
namecards,
|
||||||
|
avatarStore,
|
||||||
|
log,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -163,6 +167,7 @@ export function card(app: App) {
|
|||||||
description:
|
description:
|
||||||
"Generate a Genshin Impact character list card image from a UID.",
|
"Generate a Genshin Impact character list card image from a UID.",
|
||||||
},
|
},
|
||||||
|
afterResponse: ({ log }) => log`Rendering completed`,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.get(
|
.get(
|
||||||
|
|||||||
+125
-1
@@ -1,16 +1,27 @@
|
|||||||
import { ResolvedRoot } from "enka.ts";
|
import { ResolvedRoot } from "enka.ts";
|
||||||
import { assets } from "@/lib/assets";
|
import { assets } from "@/lib/assets";
|
||||||
|
import { Logger } from "@/lib/logger";
|
||||||
|
import { CharacterNamecardMap } from "@/lib/namecards";
|
||||||
|
import { AvatarStore, EnkaNamecards } from "@/type/enka";
|
||||||
import { CardResponse } from "./response";
|
import { CardResponse } from "./response";
|
||||||
|
|
||||||
export async function ListPage(
|
export async function ListPage(
|
||||||
{ playerInfo: p, uid }: ResolvedRoot,
|
{ playerInfo: p, uid }: ResolvedRoot,
|
||||||
pfp: string,
|
pfp: string,
|
||||||
namecard: string,
|
namecard: string,
|
||||||
|
namecards: EnkaNamecards,
|
||||||
|
avatars: AvatarStore,
|
||||||
|
log: Logger,
|
||||||
) {
|
) {
|
||||||
return CardResponse(
|
return CardResponse(
|
||||||
<div tw="flex font-[Open_Sans]">
|
<div tw="flex font-[Open_Sans]">
|
||||||
{/* left bar */}
|
{/* left bar */}
|
||||||
<div tw="flex flex-col justify-between p-[1em] w-[37.5%]">
|
<div
|
||||||
|
tw="flex flex-col justify-between p-[1em] w-[37.5%]"
|
||||||
|
style={{
|
||||||
|
textShadow: "0px .08em .2em black",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{/* profile */}
|
{/* profile */}
|
||||||
<div tw="flex gap-[1em] relative">
|
<div tw="flex gap-[1em] relative">
|
||||||
<div tw="absolute -top-1 -left-1 -bottom-1 -right-1 overflow-hidden rounded-[.3em] opacity-50">
|
<div tw="absolute -top-1 -left-1 -bottom-1 -right-1 overflow-hidden rounded-[.3em] opacity-50">
|
||||||
@@ -45,11 +56,124 @@ export async function ListPage(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div tw="pt-[.2em]">
|
||||||
|
<div tw="opacity-50">UID</div>
|
||||||
|
{`: ${uid}`}
|
||||||
|
</div>
|
||||||
|
<div tw="text-[1.2em] p-[.7em]">
|
||||||
|
<div tw="grid grid-cols-[2em_1fr_1.25fr] gap-y-[1em]">
|
||||||
|
<img tw="aspect-square h-[2em]" src="/i/achi" alt="achievements" />
|
||||||
|
<div tw="flex items-center font-[ShinShin]">
|
||||||
|
{p.finishAchievementNum}
|
||||||
|
</div>
|
||||||
|
<div tw="text-[.9em] flex items-center justify-end opacity-50 pb-2">
|
||||||
|
Achievements
|
||||||
|
</div>
|
||||||
|
{p.towerFloorIndex && (
|
||||||
|
<>
|
||||||
|
<img tw="aspect-square h-[2em]" src="/i/abyss" alt="abyss" />
|
||||||
|
<div tw="flex items-center font-[ShinShin]">
|
||||||
|
{p.towerFloorIndex}-{p.towerLevelIndex}
|
||||||
|
{p.towerStarIndex ? ` | ${p.towerStarIndex}★` : ""}
|
||||||
|
</div>
|
||||||
|
<div tw="text-[.9em] flex items-center justify-end opacity-50 pb-2">
|
||||||
|
Spiral Abyss
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{p.theaterActIndex && (
|
||||||
|
<>
|
||||||
|
<img
|
||||||
|
tw="aspect-square h-[2em]"
|
||||||
|
src="/i/theater"
|
||||||
|
alt="theater"
|
||||||
|
/>
|
||||||
|
<div tw="flex items-center font-[ShinShin]">{`${p.theaterActIndex} | ${p.theaterStarIndex}`}</div>
|
||||||
|
<div tw="text-[.9em] flex items-center justify-end opacity-50 pb-2">
|
||||||
|
Theater
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{p.stygianSeconds && (
|
||||||
|
<>
|
||||||
|
<img
|
||||||
|
tw="aspect-square h-[2em]"
|
||||||
|
src="/i/stygian"
|
||||||
|
alt="stygian"
|
||||||
|
/>
|
||||||
|
<div tw="flex items-center font-[ShinShin]">
|
||||||
|
{p.stygianSeconds}s
|
||||||
|
</div>
|
||||||
|
<div tw="text-[.9em] flex items-center justify-end opacity-50 pb-2">
|
||||||
|
Stygian Onslaught
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* character list (for some reason ________ absolute hates me) */}
|
||||||
|
<div tw="absolute top-0 right-0 w-[62.5%] h-[117%]">
|
||||||
|
<div tw="flex relative flex-wrap gap-x-[1.25%] gap-y-[0.75%] p-[.5em] w-full">
|
||||||
|
{p.showAvatarInfoList.map((a) => (
|
||||||
|
<div tw="w-[49.625%] bg-black/50 relative overflow-hidden rounded-lg flex">
|
||||||
|
<img
|
||||||
|
src={`/namecard/${a.avatar.avatarId}`}
|
||||||
|
alt="namecard"
|
||||||
|
tw="absolute top-0 left-0 right-0 bottom-0 object-cover brightness-50"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={`/pfp/${a.avatar.avatarId}`}
|
||||||
|
alt="pfp"
|
||||||
|
tw="absolute top-0 left-0 bottom-0 aspect-square brightness-0 scale-120 opacity-20"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={`/pfp/${a.avatar.avatarId}`}
|
||||||
|
alt="pfp"
|
||||||
|
tw="absolute top-0 left-0 bottom-0 aspect-square"
|
||||||
|
/>
|
||||||
|
<div tw="ml-[20%] p-[.25em] font-[ShinShin] flex flex-col">
|
||||||
|
<div>{a.avatar.name}</div>
|
||||||
|
<div>Lv. {a.level}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
[
|
[
|
||||||
{ src: "/pfp", data: assets.load(pfp) },
|
{ src: "/pfp", data: assets.load(pfp) },
|
||||||
{ src: "/namecard", data: assets.load(namecard) },
|
{ src: "/namecard", data: assets.load(namecard) },
|
||||||
|
{ src: "/i/achi", data: assets.load("/img/achievements.png") },
|
||||||
|
{ src: "/i/abyss", data: assets.load("/img/abyss.png") },
|
||||||
|
{ src: "/i/theater", data: assets.load("/ui/UI_Icon_Tower_Star1.png") },
|
||||||
|
{
|
||||||
|
src: "/i/stygian",
|
||||||
|
data: assets.load(
|
||||||
|
`/ui/UI_LeyLineChallenge_Medal_${p.stygianIndex ?? 1}.png`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
...(p.showAvatarInfoList
|
||||||
|
.flatMap((a) => {
|
||||||
|
const card =
|
||||||
|
namecards[`${CharacterNamecardMap[`${a.avatar.avatarId}`]}`];
|
||||||
|
return [
|
||||||
|
card
|
||||||
|
? {
|
||||||
|
src: `/namecard/${a.avatar.avatarId}`,
|
||||||
|
data: assets.load(card ? card.Icon : namecard),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
{
|
||||||
|
src: `/pfp/${a.avatar.avatarId}`,
|
||||||
|
data: assets.load(
|
||||||
|
avatars[a.avatar.avatarId].SideIconName.replace("_Side", ""),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
})
|
||||||
|
.filter(Boolean) as { src: string; data: Promise<ArrayBuffer> }[]),
|
||||||
],
|
],
|
||||||
|
log,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-5
@@ -1,7 +1,9 @@
|
|||||||
|
import { feature } from "bun:bundle";
|
||||||
import ImageResponse from "@takumi-rs/image-response";
|
import ImageResponse from "@takumi-rs/image-response";
|
||||||
import { MaybePromise } from "bun";
|
import { MaybePromise } from "bun";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { font, iconFont, sansFont } from "@/assets/fonts";
|
import { font, iconFont, sansFont } from "@/assets/fonts";
|
||||||
|
import { createLogger } from "@/lib/logger";
|
||||||
|
|
||||||
export async function CardResponse(
|
export async function CardResponse(
|
||||||
children: ReactNode,
|
children: ReactNode,
|
||||||
@@ -9,6 +11,7 @@ export async function CardResponse(
|
|||||||
src: string;
|
src: string;
|
||||||
data: MaybePromise<ArrayBuffer | Buffer>;
|
data: MaybePromise<ArrayBuffer | Buffer>;
|
||||||
}>[] = [],
|
}>[] = [],
|
||||||
|
log = createLogger("RES"),
|
||||||
) {
|
) {
|
||||||
return new ImageResponse(
|
return new ImageResponse(
|
||||||
<div
|
<div
|
||||||
@@ -41,11 +44,21 @@ export async function CardResponse(
|
|||||||
format: "webp",
|
format: "webp",
|
||||||
// same ratio as card
|
// same ratio as card
|
||||||
width: 1228,
|
width: 1228,
|
||||||
fetchedResources: await Promise.all(assets).then((list) =>
|
fetchedResources: await Promise.all(assets)
|
||||||
Promise.all(
|
.then((list) =>
|
||||||
list.map(async (a) => ({ src: a.src, data: await a.data })),
|
Promise.all(
|
||||||
),
|
list.map(async (a) => ({ src: a.src, data: await a.data })),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
.then((l) => {
|
||||||
|
const res = l.filter((r) => {
|
||||||
|
if (r.data.byteLength) return true;
|
||||||
|
if (!feature("API")) console.warn(`[DEV] ${r.src} is blank`);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
log`Image resources loaded\n${res.map((r) => `- ${r.src} ${(r.data.byteLength / 1000).toFixed(2)}Kb`).join("\n")}`;
|
||||||
|
return res;
|
||||||
|
}),
|
||||||
height: 512,
|
height: 512,
|
||||||
// drawDebugBorder: true,
|
// drawDebugBorder: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { join } from "node:path";
|
||||||
|
import { $, MaybePromise, write } from "bun";
|
||||||
|
import { createLogger } from "@/lib/logger";
|
||||||
|
|
||||||
|
const log = createLogger();
|
||||||
|
fetch("https://gi.yatta.moe/api/v2/en/avatar")
|
||||||
|
.then((a) => a.json())
|
||||||
|
.then(async (a) => {
|
||||||
|
const avatars = Object.keys(a.data.items);
|
||||||
|
log`fetch complete, ${avatars.length} avatars`;
|
||||||
|
const map = Object.fromEntries(
|
||||||
|
(
|
||||||
|
await asyncMap(avatars, (a) =>
|
||||||
|
fetch(`https://gi.yatta.moe/api/v2/en/avatar/${a}`)
|
||||||
|
.then((c) => c.text())
|
||||||
|
.then((t) => {
|
||||||
|
try {
|
||||||
|
const c = JSON.parse(t);
|
||||||
|
log`${c.data.route}`;
|
||||||
|
return [a, c.data.other?.nameCard?.id] as const;
|
||||||
|
} catch {
|
||||||
|
log`${a}: ${t}`;
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
).filter((e) => e[1]),
|
||||||
|
);
|
||||||
|
log`crawling complete`;
|
||||||
|
const target = join(__dirname, "../src/lib/namecards.ts");
|
||||||
|
await write(
|
||||||
|
target,
|
||||||
|
`// GENERATED BY util/genNamecardRelation\n\nexport const CharacterNamecardMap: Record<string, number> = ${JSON.stringify(map)}`,
|
||||||
|
);
|
||||||
|
await $`bun biome format ${target} --fix`;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function asyncMap<S, R>(arr: S[], fn: (a: S) => MaybePromise<R>) {
|
||||||
|
const res: R[] = [];
|
||||||
|
for (const ent of arr) res.push(await fn(ent));
|
||||||
|
return res;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user