feat: complete character list

im too sleepless to document the commits
This commit is contained in:
2026-03-22 11:12:56 +07:00 Unverified
parent 39d21ef9ba
commit e228e47ceb
4 changed files with 67 additions and 39 deletions
+23 -1
View File
@@ -2,9 +2,10 @@ import { readdirSync } from "node:fs";
import { mkdir, readdir, rm } from "node:fs/promises";
import { join } from "node:path";
import { file, hash, write } from "bun";
import { TTLCache } from "enka.ts";
import { ResolvedPlayerInfo, TTLCache } from "enka.ts";
import {
AvatarStore,
ElementList,
EnkaLocs,
EnkaNamecards,
EnkaPfps,
@@ -199,3 +200,24 @@ export async function getEnkaStore<K extends keyof StoreTypeMap>(
cache.set(`store.${key}`, data, 86400); // 24 hours
return data;
}
export function getAvatar(
store: AvatarStore,
playerInfo: ResolvedPlayerInfo,
id: number,
) {
return (
Object.entries(store).find(
([avatarId, avatar]) =>
avatarId.split("-")[0] === `${id}` &&
avatar.Element ===
ElementList[
(
playerInfo.showAvatarInfoList.find(
(e) => e.avatar.avatarId === id,
) as { energyType: number } | undefined
)?.energyType ?? 0
],
)?.[1] || store[id]
);
}
+9 -16
View File
@@ -22,13 +22,13 @@ import { star } from "@/assets/stars";
import { TalentSvg } from "@/assets/talent";
import { WeaponGlazeSvg } from "@/assets/weapon-glaze";
import type { App } from "@/index";
import { assets, batchStore } from "@/lib/assets";
import { assets, batchStore, getAvatar } from "@/lib/assets";
import { Deduplicator } from "@/lib/dedupe";
import { cardErrorLocs } from "@/lib/locs";
import { createLogger } from "@/lib/logger";
import { mockData } from "@/lib/mock";
import { buildDisplayRows } from "@/lib/rows";
import { ElementList, Locale, SubstatOrder } from "@/type/enka";
import { Locale, SubstatOrder } from "@/type/enka";
import { PercentageFightProp } from "@/type/fightPropMap";
import { ErrorPage } from "./error";
import { ListPage } from "./list";
@@ -106,7 +106,7 @@ export function card(app: App) {
)
.get(
"/genshin/:uid",
async ({ params: { uid }, query: { debug }, log }) => {
async ({ params: { uid }, query: { debug, lang }, log }) => {
// #region Character List
const [[avatarStore, locs, pfps, namecards], data] =
await Promise.all([
@@ -132,6 +132,7 @@ export function card(app: App) {
namecards[`${data.playerInfo.nameCardId ?? 210001}`].Icon,
namecards,
avatarStore,
locs[lang ?? "en"],
log,
);
},
@@ -210,19 +211,11 @@ export function card(app: App) {
);
if (!char) return error(400, errorLoc.notInShowcase);
const avatar =
Object.entries(avatarStore).find(
([avatarId, avatar]) =>
avatarId.split("-")[0] === `${char.avatar.avatarId}` &&
avatar.Element ===
ElementList[
(
data.playerInfo.showAvatarInfoList.find(
(e) => e.avatar.avatarId === char.avatar.avatarId,
) as { energyType: number } | undefined
)?.energyType ?? 0
],
)?.[1] || avatarStore[char.avatar.avatarId];
const avatar = getAvatar(
avatarStore,
data.playerInfo,
char.avatar.avatarId,
);
if (!avatar) return error(400, errorLoc.unsupported);
if (!char.equipList)
+34 -20
View File
@@ -1,8 +1,8 @@
import { ResolvedRoot } from "enka.ts";
import { assets } from "@/lib/assets";
import { assets, getAvatar } from "@/lib/assets";
import { Logger } from "@/lib/logger";
import { CharacterNamecardMap } from "@/lib/namecards";
import { AvatarStore, EnkaNamecards } from "@/type/enka";
import { AvatarStore, EnkaLocsLanguage, EnkaNamecards } from "@/type/enka";
import { CardResponse } from "./response";
export async function ListPage(
@@ -11,6 +11,7 @@ export async function ListPage(
namecard: string,
namecards: EnkaNamecards,
avatars: AvatarStore,
locs: EnkaLocsLanguage,
log: Logger,
) {
return CardResponse(
@@ -66,8 +67,8 @@ export async function ListPage(
<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 tw="text-[.75em] flex items-center justify-end opacity-50 pb-2 whitespace-nowrap text-ellipsis">
{locs.achievements}
</div>
{p.towerFloorIndex && (
<>
@@ -76,8 +77,8 @@ export async function ListPage(
{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 tw="text-[.75em] flex items-center justify-end opacity-50 pb-2 whitespace-nowrap text-ellipsis">
{locs.abyss}
</div>
</>
)}
@@ -89,8 +90,8 @@ export async function ListPage(
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 tw="text-[.75em] flex items-center justify-end opacity-50 pb-2 whitespace-nowrap text-ellipsis">
{locs.theater}
</div>
</>
)}
@@ -104,8 +105,8 @@ export async function ListPage(
<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 tw="text-[.75em] flex items-center justify-end opacity-50 pb-2 whitespace-nowrap text-ellipsis">
{locs.stygian}
</div>
</>
)}
@@ -113,14 +114,14 @@ export async function ListPage(
</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">
<div tw="absolute top-0 right-0 w-[62.5%] h-[97vh]">
<div tw="flex relative flex-wrap gap-x-[1.25%] gap-y-[0.75%] p-[.5em] w-full h-full">
{p.showAvatarInfoList.map((a) => (
<div tw="w-[49.625%] bg-black/50 relative overflow-hidden rounded-lg flex">
<div tw="w-[49.625%] h-[16.04166667%] 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"
tw="absolute top-0 left-0 right-0 bottom-0 object-cover blur-[2px] brightness-50"
/>
<img
src={`/pfp/${a.avatar.avatarId}`}
@@ -132,9 +133,23 @@ export async function ListPage(
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 tw="ml-[22.5%] flex">
<hr tw="border-none h-[90%] w-px mt-[5%] bg-white/60" />
<div tw="p-[.25em] font-[ShinShin] flex flex-col grow">
<div>{a.avatar.name}</div>
<div>
{locs.level} {a.level}
</div>
</div>
</div>
<div tw="absolute bottom-[.2em] right-[.2em] leading-none p-[.2em] rounded-md bg-black/50">
{uid
.slice(-7)
.split("")
.map((c, i) => (
<div style={{ opacity: i / 5 }}>{c}</div>
))}
/{a.avatar.avatarId}
</div>
</div>
))}
@@ -155,6 +170,7 @@ export async function ListPage(
},
...(p.showAvatarInfoList
.flatMap((a) => {
const avatar = getAvatar(avatars, p, a.avatar.avatarId);
const card =
namecards[`${CharacterNamecardMap[`${a.avatar.avatarId}`]}`];
return [
@@ -166,9 +182,7 @@ export async function ListPage(
: null,
{
src: `/pfp/${a.avatar.avatarId}`,
data: assets.load(
avatars[a.avatar.avatarId].SideIconName.replace("_Side", ""),
),
data: assets.load(avatar.SideIconName.replace("_Side", "")),
},
];
})
+1 -2
View File
@@ -24,8 +24,7 @@ export async function CardResponse(
}}
>
{children}
<div tw="grow" />
<div tw="ml-[.5em] flex items-center">
<div tw="absolute bottom-0 left-0 ml-[.5em] flex items-center">
<div tw="text-[2em] font-[shicon] pb-[.1em] -mr-[.3em] text-white/25">
</div>