feat: complete character list
im too sleepless to document the commits
This commit is contained in:
+23
-1
@@ -2,9 +2,10 @@ import { readdirSync } from "node:fs";
|
|||||||
import { mkdir, readdir, rm } from "node:fs/promises";
|
import { mkdir, readdir, rm } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { file, hash, write } from "bun";
|
import { file, hash, write } from "bun";
|
||||||
import { TTLCache } from "enka.ts";
|
import { ResolvedPlayerInfo, TTLCache } from "enka.ts";
|
||||||
import {
|
import {
|
||||||
AvatarStore,
|
AvatarStore,
|
||||||
|
ElementList,
|
||||||
EnkaLocs,
|
EnkaLocs,
|
||||||
EnkaNamecards,
|
EnkaNamecards,
|
||||||
EnkaPfps,
|
EnkaPfps,
|
||||||
@@ -199,3 +200,24 @@ export async function getEnkaStore<K extends keyof StoreTypeMap>(
|
|||||||
cache.set(`store.${key}`, data, 86400); // 24 hours
|
cache.set(`store.${key}`, data, 86400); // 24 hours
|
||||||
return data;
|
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
@@ -22,13 +22,13 @@ import { star } from "@/assets/stars";
|
|||||||
import { TalentSvg } from "@/assets/talent";
|
import { TalentSvg } from "@/assets/talent";
|
||||||
import { WeaponGlazeSvg } from "@/assets/weapon-glaze";
|
import { WeaponGlazeSvg } from "@/assets/weapon-glaze";
|
||||||
import type { App } from "@/index";
|
import type { App } from "@/index";
|
||||||
import { assets, batchStore } from "@/lib/assets";
|
import { assets, batchStore, getAvatar } from "@/lib/assets";
|
||||||
import { Deduplicator } from "@/lib/dedupe";
|
import { Deduplicator } from "@/lib/dedupe";
|
||||||
import { cardErrorLocs } from "@/lib/locs";
|
import { cardErrorLocs } from "@/lib/locs";
|
||||||
import { createLogger } from "@/lib/logger";
|
import { createLogger } from "@/lib/logger";
|
||||||
import { mockData } from "@/lib/mock";
|
import { mockData } from "@/lib/mock";
|
||||||
import { buildDisplayRows } from "@/lib/rows";
|
import { buildDisplayRows } from "@/lib/rows";
|
||||||
import { ElementList, Locale, SubstatOrder } from "@/type/enka";
|
import { Locale, SubstatOrder } from "@/type/enka";
|
||||||
import { PercentageFightProp } from "@/type/fightPropMap";
|
import { PercentageFightProp } from "@/type/fightPropMap";
|
||||||
import { ErrorPage } from "./error";
|
import { ErrorPage } from "./error";
|
||||||
import { ListPage } from "./list";
|
import { ListPage } from "./list";
|
||||||
@@ -106,7 +106,7 @@ export function card(app: App) {
|
|||||||
)
|
)
|
||||||
.get(
|
.get(
|
||||||
"/genshin/:uid",
|
"/genshin/:uid",
|
||||||
async ({ params: { uid }, query: { debug }, log }) => {
|
async ({ params: { uid }, query: { debug, lang }, log }) => {
|
||||||
// #region Character List
|
// #region Character List
|
||||||
const [[avatarStore, locs, pfps, namecards], data] =
|
const [[avatarStore, locs, pfps, namecards], data] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@@ -132,6 +132,7 @@ export function card(app: App) {
|
|||||||
namecards[`${data.playerInfo.nameCardId ?? 210001}`].Icon,
|
namecards[`${data.playerInfo.nameCardId ?? 210001}`].Icon,
|
||||||
namecards,
|
namecards,
|
||||||
avatarStore,
|
avatarStore,
|
||||||
|
locs[lang ?? "en"],
|
||||||
log,
|
log,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -210,19 +211,11 @@ export function card(app: App) {
|
|||||||
);
|
);
|
||||||
if (!char) return error(400, errorLoc.notInShowcase);
|
if (!char) return error(400, errorLoc.notInShowcase);
|
||||||
|
|
||||||
const avatar =
|
const avatar = getAvatar(
|
||||||
Object.entries(avatarStore).find(
|
avatarStore,
|
||||||
([avatarId, avatar]) =>
|
data.playerInfo,
|
||||||
avatarId.split("-")[0] === `${char.avatar.avatarId}` &&
|
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];
|
|
||||||
if (!avatar) return error(400, errorLoc.unsupported);
|
if (!avatar) return error(400, errorLoc.unsupported);
|
||||||
|
|
||||||
if (!char.equipList)
|
if (!char.equipList)
|
||||||
|
|||||||
+34
-20
@@ -1,8 +1,8 @@
|
|||||||
import { ResolvedRoot } from "enka.ts";
|
import { ResolvedRoot } from "enka.ts";
|
||||||
import { assets } from "@/lib/assets";
|
import { assets, getAvatar } from "@/lib/assets";
|
||||||
import { Logger } from "@/lib/logger";
|
import { Logger } from "@/lib/logger";
|
||||||
import { CharacterNamecardMap } from "@/lib/namecards";
|
import { CharacterNamecardMap } from "@/lib/namecards";
|
||||||
import { AvatarStore, EnkaNamecards } from "@/type/enka";
|
import { AvatarStore, EnkaLocsLanguage, EnkaNamecards } from "@/type/enka";
|
||||||
import { CardResponse } from "./response";
|
import { CardResponse } from "./response";
|
||||||
|
|
||||||
export async function ListPage(
|
export async function ListPage(
|
||||||
@@ -11,6 +11,7 @@ export async function ListPage(
|
|||||||
namecard: string,
|
namecard: string,
|
||||||
namecards: EnkaNamecards,
|
namecards: EnkaNamecards,
|
||||||
avatars: AvatarStore,
|
avatars: AvatarStore,
|
||||||
|
locs: EnkaLocsLanguage,
|
||||||
log: Logger,
|
log: Logger,
|
||||||
) {
|
) {
|
||||||
return CardResponse(
|
return CardResponse(
|
||||||
@@ -66,8 +67,8 @@ export async function ListPage(
|
|||||||
<div tw="flex items-center font-[ShinShin]">
|
<div tw="flex items-center font-[ShinShin]">
|
||||||
{p.finishAchievementNum}
|
{p.finishAchievementNum}
|
||||||
</div>
|
</div>
|
||||||
<div tw="text-[.9em] flex items-center justify-end opacity-50 pb-2">
|
<div tw="text-[.75em] flex items-center justify-end opacity-50 pb-2 whitespace-nowrap text-ellipsis">
|
||||||
Achievements
|
{locs.achievements}
|
||||||
</div>
|
</div>
|
||||||
{p.towerFloorIndex && (
|
{p.towerFloorIndex && (
|
||||||
<>
|
<>
|
||||||
@@ -76,8 +77,8 @@ export async function ListPage(
|
|||||||
{p.towerFloorIndex}-{p.towerLevelIndex}
|
{p.towerFloorIndex}-{p.towerLevelIndex}
|
||||||
{p.towerStarIndex ? ` | ${p.towerStarIndex}★` : ""}
|
{p.towerStarIndex ? ` | ${p.towerStarIndex}★` : ""}
|
||||||
</div>
|
</div>
|
||||||
<div tw="text-[.9em] flex items-center justify-end opacity-50 pb-2">
|
<div tw="text-[.75em] flex items-center justify-end opacity-50 pb-2 whitespace-nowrap text-ellipsis">
|
||||||
Spiral Abyss
|
{locs.abyss}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -89,8 +90,8 @@ export async function ListPage(
|
|||||||
alt="theater"
|
alt="theater"
|
||||||
/>
|
/>
|
||||||
<div tw="flex items-center font-[ShinShin]">{`${p.theaterActIndex} | ${p.theaterStarIndex}`}</div>
|
<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">
|
<div tw="text-[.75em] flex items-center justify-end opacity-50 pb-2 whitespace-nowrap text-ellipsis">
|
||||||
Theater
|
{locs.theater}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -104,8 +105,8 @@ export async function ListPage(
|
|||||||
<div tw="flex items-center font-[ShinShin]">
|
<div tw="flex items-center font-[ShinShin]">
|
||||||
{p.stygianSeconds}s
|
{p.stygianSeconds}s
|
||||||
</div>
|
</div>
|
||||||
<div tw="text-[.9em] flex items-center justify-end opacity-50 pb-2">
|
<div tw="text-[.75em] flex items-center justify-end opacity-50 pb-2 whitespace-nowrap text-ellipsis">
|
||||||
Stygian Onslaught
|
{locs.stygian}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -113,14 +114,14 @@ export async function ListPage(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* character list (for some reason ________ absolute hates me) */}
|
{/* character list (for some reason ________ absolute hates me) */}
|
||||||
<div tw="absolute top-0 right-0 w-[62.5%] h-[117%]">
|
<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">
|
<div tw="flex relative flex-wrap gap-x-[1.25%] gap-y-[0.75%] p-[.5em] w-full h-full">
|
||||||
{p.showAvatarInfoList.map((a) => (
|
{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
|
<img
|
||||||
src={`/namecard/${a.avatar.avatarId}`}
|
src={`/namecard/${a.avatar.avatarId}`}
|
||||||
alt="namecard"
|
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
|
<img
|
||||||
src={`/pfp/${a.avatar.avatarId}`}
|
src={`/pfp/${a.avatar.avatarId}`}
|
||||||
@@ -132,9 +133,23 @@ export async function ListPage(
|
|||||||
alt="pfp"
|
alt="pfp"
|
||||||
tw="absolute top-0 left-0 bottom-0 aspect-square"
|
tw="absolute top-0 left-0 bottom-0 aspect-square"
|
||||||
/>
|
/>
|
||||||
<div tw="ml-[20%] p-[.25em] font-[ShinShin] flex flex-col">
|
<div tw="ml-[22.5%] flex">
|
||||||
<div>{a.avatar.name}</div>
|
<hr tw="border-none h-[90%] w-px mt-[5%] bg-white/60" />
|
||||||
<div>Lv. {a.level}</div>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -155,6 +170,7 @@ export async function ListPage(
|
|||||||
},
|
},
|
||||||
...(p.showAvatarInfoList
|
...(p.showAvatarInfoList
|
||||||
.flatMap((a) => {
|
.flatMap((a) => {
|
||||||
|
const avatar = getAvatar(avatars, p, a.avatar.avatarId);
|
||||||
const card =
|
const card =
|
||||||
namecards[`${CharacterNamecardMap[`${a.avatar.avatarId}`]}`];
|
namecards[`${CharacterNamecardMap[`${a.avatar.avatarId}`]}`];
|
||||||
return [
|
return [
|
||||||
@@ -166,9 +182,7 @@ export async function ListPage(
|
|||||||
: null,
|
: null,
|
||||||
{
|
{
|
||||||
src: `/pfp/${a.avatar.avatarId}`,
|
src: `/pfp/${a.avatar.avatarId}`,
|
||||||
data: assets.load(
|
data: assets.load(avatar.SideIconName.replace("_Side", "")),
|
||||||
avatars[a.avatar.avatarId].SideIconName.replace("_Side", ""),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
|
|||||||
+1
-2
@@ -24,8 +24,7 @@ export async function CardResponse(
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<div tw="grow" />
|
<div tw="absolute bottom-0 left-0 ml-[.5em] flex items-center">
|
||||||
<div tw="ml-[.5em] flex items-center">
|
|
||||||
<div tw="text-[2em] font-[shicon] pb-[.1em] -mr-[.3em] text-white/25">
|
<div tw="text-[2em] font-[shicon] pb-[.1em] -mr-[.3em] text-white/25">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user