refactor: split game renderers into scoped modules
@@ -0,0 +1,143 @@
|
|||||||
|
import { inspect } from "bun";
|
||||||
|
import { status, t } from "elysia";
|
||||||
|
import {
|
||||||
|
fetchEnka,
|
||||||
|
InternalError,
|
||||||
|
InvalidFormatError,
|
||||||
|
MaintenanceError,
|
||||||
|
NotFoundError,
|
||||||
|
RatelimitError,
|
||||||
|
} from "enka.ts";
|
||||||
|
|
||||||
|
import type { App } from "@/index";
|
||||||
|
import { Deduplicator } from "@/lib/dedupe";
|
||||||
|
import { createLogger } from "@/lib/logger";
|
||||||
|
import { ErrorPage } from "@/v1/error";
|
||||||
|
|
||||||
|
import { EndfieldTemplate } from "./render/template";
|
||||||
|
|
||||||
|
const deduper = new Deduplicator();
|
||||||
|
|
||||||
|
const endfieldErrorLocs = {
|
||||||
|
invalidUid: "Invalid UID format",
|
||||||
|
notFound: "UID not found on enka.network",
|
||||||
|
maintenance: "enka.network is under maintenance",
|
||||||
|
ratelimit: "Too many requests to enka.network, please try again later",
|
||||||
|
internal: "Internal server error",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
function normalizeError(errorType: "http" | "card") {
|
||||||
|
return errorType === "card"
|
||||||
|
? (_: number, message: string) => ErrorPage(message)
|
||||||
|
: status;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function endfieldCard(app: App) {
|
||||||
|
return app.group("/card/endfield", (app) =>
|
||||||
|
app
|
||||||
|
.derive(() => ({
|
||||||
|
log: createLogger(),
|
||||||
|
}))
|
||||||
|
.head(
|
||||||
|
"/:uid/preload",
|
||||||
|
async ({ params: { uid }, log }) => {
|
||||||
|
log`Preloading ${uid}`;
|
||||||
|
try {
|
||||||
|
await deduper.acquire(`endfield-${uid}`, () =>
|
||||||
|
fetchEnka(uid, false).finally(() => log`Enka data loaded`),
|
||||||
|
);
|
||||||
|
return "OK";
|
||||||
|
} catch {
|
||||||
|
return status(502, "Data Preload Error");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: t.Object({ uid: t.String() }),
|
||||||
|
detail: {
|
||||||
|
tags: ["Endfield"],
|
||||||
|
description: "Preload data required to render an Endfield template card.",
|
||||||
|
},
|
||||||
|
afterResponse: ({ log }) => log`Data preload complete`,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
"/:uid",
|
||||||
|
async ({
|
||||||
|
params: { uid },
|
||||||
|
query: { debug, error: errorType = "http" },
|
||||||
|
log,
|
||||||
|
}) => {
|
||||||
|
const error = normalizeError(errorType);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await deduper.acquire(`endfield-${uid}`, () =>
|
||||||
|
fetchEnka(uid, false).finally(() => log`Enka data loaded`),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (debug) return { data };
|
||||||
|
|
||||||
|
return EndfieldTemplate({
|
||||||
|
uid,
|
||||||
|
nickname: data.playerInfo.nickname,
|
||||||
|
level: data.playerInfo.level,
|
||||||
|
signature: data.playerInfo.signature,
|
||||||
|
source: "enka.network",
|
||||||
|
log,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof InvalidFormatError)
|
||||||
|
return error(400, endfieldErrorLocs.invalidUid);
|
||||||
|
if (err instanceof NotFoundError)
|
||||||
|
return error(404, endfieldErrorLocs.notFound);
|
||||||
|
if (err instanceof MaintenanceError)
|
||||||
|
return error(424, endfieldErrorLocs.maintenance);
|
||||||
|
if (err instanceof RatelimitError)
|
||||||
|
return error(429, endfieldErrorLocs.ratelimit);
|
||||||
|
if (err instanceof InternalError)
|
||||||
|
return status(502, endfieldErrorLocs.internal);
|
||||||
|
|
||||||
|
console.error(err);
|
||||||
|
try {
|
||||||
|
return error(
|
||||||
|
500,
|
||||||
|
`Internal Error:\n${(err as { message?: string })?.message || inspect(err)}` as "",
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: t.Object({
|
||||||
|
uid: t.String({
|
||||||
|
examples: ["775956514"],
|
||||||
|
description: "Player UID",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
query: t.Partial(
|
||||||
|
t.Object(
|
||||||
|
{
|
||||||
|
error: t.Enum(
|
||||||
|
{ http: "http", card: "card" },
|
||||||
|
{
|
||||||
|
default: "http",
|
||||||
|
description: "Type of error to return when card fails to render.",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
debug: t.Boolean({
|
||||||
|
description: "Return debug information instead of card.",
|
||||||
|
default: false,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{ additionalProperties: true },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
detail: {
|
||||||
|
tags: ["Endfield"],
|
||||||
|
description: "Generate a placeholder Endfield profile card image from a UID.",
|
||||||
|
},
|
||||||
|
afterResponse: ({ log }) => log`Rendering completed`,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import type { Logger } from "@/lib/logger";
|
||||||
|
import { CardResponse } from "@/v1/response";
|
||||||
|
|
||||||
|
type EndfieldTemplateProps = {
|
||||||
|
uid: string;
|
||||||
|
nickname?: string;
|
||||||
|
level?: number;
|
||||||
|
signature?: string;
|
||||||
|
source: string;
|
||||||
|
log: Logger;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function EndfieldTemplate({
|
||||||
|
uid,
|
||||||
|
nickname,
|
||||||
|
level,
|
||||||
|
signature,
|
||||||
|
source,
|
||||||
|
log,
|
||||||
|
}: EndfieldTemplateProps) {
|
||||||
|
return CardResponse(
|
||||||
|
<div tw="flex h-full w-full bg-[#07131c] text-white relative overflow-hidden font-[Open_Sans]">
|
||||||
|
<div tw="absolute inset-0 bg-[radial-gradient(circle_at_top_left,#1d4f6d,transparent_35%),radial-gradient(circle_at_bottom_right,#113047,transparent_30%),linear-gradient(135deg,#081018,#0d1f2e_45%,#091723)]" />
|
||||||
|
<div tw="absolute inset-[2.2em] rounded-[1.4em] border border-white/12 bg-white/6 backdrop-blur-[6px]" />
|
||||||
|
|
||||||
|
<div tw="relative z-10 flex flex-col justify-between w-full p-[2.4em]">
|
||||||
|
<div tw="flex items-start justify-between gap-[2em]">
|
||||||
|
<div tw="flex flex-col gap-[.55em] max-w-[65%]">
|
||||||
|
<div tw="text-[.95em] tracking-[.28em] uppercase text-[#7ed8ff] opacity-90">
|
||||||
|
Endfield Prototype
|
||||||
|
</div>
|
||||||
|
<div tw="text-[2.6em] leading-none font-semibold">
|
||||||
|
{nickname || "Unknown Operator"}
|
||||||
|
</div>
|
||||||
|
<div tw="text-[1.05em] text-white/70">UID {uid}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div tw="flex flex-col items-end gap-[.5em] text-right shrink-0">
|
||||||
|
<div tw="rounded-full border border-[#7ed8ff]/35 bg-[#7ed8ff]/12 px-[1em] py-[.45em] text-[.95em] text-[#b6ecff]">
|
||||||
|
Template Only
|
||||||
|
</div>
|
||||||
|
<div tw="text-[.95em] text-white/65">Data source: {source}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div tw="grid grid-cols-3 gap-[1em] text-[1.02em]">
|
||||||
|
<div tw="rounded-[1em] border border-white/10 bg-black/20 p-[1.2em]">
|
||||||
|
<div tw="text-white/55 uppercase tracking-[.18em] text-[.72em] mb-[.6em]">
|
||||||
|
Progress
|
||||||
|
</div>
|
||||||
|
<div tw="text-[2.1em] leading-none font-semibold">
|
||||||
|
{typeof level === "number" ? level : "--"}
|
||||||
|
</div>
|
||||||
|
<div tw="text-white/60 mt-[.35em]">Level</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div tw="rounded-[1em] border border-white/10 bg-black/20 p-[1.2em]">
|
||||||
|
<div tw="text-white/55 uppercase tracking-[.18em] text-[.72em] mb-[.6em]">
|
||||||
|
Renderer
|
||||||
|
</div>
|
||||||
|
<div tw="text-[1.55em] leading-[1.15] font-semibold">
|
||||||
|
Route + Card Shell
|
||||||
|
</div>
|
||||||
|
<div tw="text-white/60 mt-[.35em]">Ready for game-specific layout work</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div tw="rounded-[1em] border border-white/10 bg-black/20 p-[1.2em]">
|
||||||
|
<div tw="text-white/55 uppercase tracking-[.18em] text-[.72em] mb-[.6em]">
|
||||||
|
Next
|
||||||
|
</div>
|
||||||
|
<div tw="text-[1.55em] leading-[1.15] font-semibold">
|
||||||
|
Character Grid
|
||||||
|
</div>
|
||||||
|
<div tw="text-white/60 mt-[.35em]">Replace placeholder sections with Endfield data</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div tw="rounded-[1em] border border-white/10 bg-black/20 p-[1.2em] min-h-[6.5em]">
|
||||||
|
<div tw="text-white/55 uppercase tracking-[.18em] text-[.72em] mb-[.8em]">
|
||||||
|
Signature
|
||||||
|
</div>
|
||||||
|
<div tw="text-[1.05em] leading-[1.45] text-white/82 whitespace-pre-wrap">
|
||||||
|
{signature?.trim() || "No signature available in the fetched payload yet."}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
[],
|
||||||
|
{ width: 1228, height: 512 },
|
||||||
|
log,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createCanvas, loadImage } from "@napi-rs/canvas";
|
import { createCanvas, loadImage } from "@napi-rs/canvas";
|
||||||
import { file } from "bun";
|
import { file } from "bun";
|
||||||
|
|
||||||
import overlayImage from "@/assets/overlay.jpg";
|
import overlayImage from "@/games/genshin/assets/overlay.jpg";
|
||||||
|
|
||||||
const elementColors: Record<string, string> = {
|
const elementColors: Record<string, string> = {
|
||||||
Rock: "#bb9f4b",
|
Rock: "#bb9f4b",
|
||||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 577 KiB After Width: | Height: | Size: 577 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 840 B After Width: | Height: | Size: 840 B |
@@ -13,13 +13,13 @@ import type {
|
|||||||
EnkaNamecards,
|
EnkaNamecards,
|
||||||
EnkaPfps,
|
EnkaPfps,
|
||||||
RelicStore,
|
RelicStore,
|
||||||
WeaponStore} from "@/type/enka";
|
WeaponStore} from "@/games/genshin/types/enka";
|
||||||
import {
|
import {
|
||||||
ElementList
|
ElementList
|
||||||
} from "@/type/enka";
|
} from "@/games/genshin/types/enka";
|
||||||
|
|
||||||
import { Deduplicator } from "./dedupe";
|
import { Deduplicator } from "@/lib/dedupe";
|
||||||
import { createLogger } from "./logger";
|
import { createLogger } from "@/lib/logger";
|
||||||
|
|
||||||
export const cache = new TTLCache();
|
export const cache = new TTLCache();
|
||||||
const log = createLogger("ASSET");
|
const log = createLogger("ASSET");
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { ResolvedAvatarInfo } from "enka.ts";
|
import type { ResolvedAvatarInfo } from "enka.ts";
|
||||||
import { AvatarElement } from "enka.ts";
|
import { AvatarElement } from "enka.ts";
|
||||||
|
|
||||||
import type { EnkaLocsLanguage } from "@/type/enka";
|
import type { EnkaLocsLanguage } from "@/games/genshin/types/enka";
|
||||||
import { FightPropMapEnum, PercentageFightProp } from "@/type/fightPropMap";
|
import { FightPropMapEnum, PercentageFightProp } from "@/games/genshin/types/fightPropMap";
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
interface DisplayRow {
|
interface DisplayRow {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Affix } from "@/type/enka";
|
import type { Affix } from "@/games/genshin/types/enka";
|
||||||
import { FightPropMapEnum, PercentageFightProp } from "@/type/fightPropMap";
|
import { FightPropMapEnum, PercentageFightProp } from "@/games/genshin/types/fightPropMap";
|
||||||
|
|
||||||
export type RollSummary = {
|
export type RollSummary = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -14,34 +14,34 @@ import {
|
|||||||
} from "enka.ts";
|
} from "enka.ts";
|
||||||
import { ImageResponse } from "takumi-js/response";
|
import { ImageResponse } from "takumi-js/response";
|
||||||
|
|
||||||
import { generateBackground } from "@/assets/background";
|
import { generateBackground } from "@/games/genshin/assets/background";
|
||||||
import { ConstsSvg } from "@/assets/consts";
|
import { ConstsSvg } from "@/games/genshin/assets/consts";
|
||||||
import { featherImage } from "@/assets/feather";
|
import { featherImage } from "@/games/genshin/assets/feather";
|
||||||
import { font, iconFont, sansFont } from "@/assets/fonts";
|
import { font, iconFont, sansFont } from "@/assets/fonts";
|
||||||
import { EnkaIcon } from "@/assets/icons";
|
import { EnkaIcon } from "@/games/genshin/assets/icons";
|
||||||
import type { library } from "@/assets/icons-library";
|
import type { library } from "@/games/genshin/assets/icons-library";
|
||||||
import ListDiffExample from "@/assets/listoriginal.webp" with { type: "file" };
|
import ListDiffExample from "@/games/genshin/assets/listoriginal.webp" with { type: "file" };
|
||||||
import DiffPageExample from "@/assets/ororo.png" with { type: "file" };
|
import DiffPageExample from "@/games/genshin/assets/ororo.png" with { type: "file" };
|
||||||
import shadeSvg from "@/assets/shade.svg" with { type: "file" };
|
import shadeSvg from "@/games/genshin/assets/shade.svg" with { type: "file" };
|
||||||
import { star } from "@/assets/stars";
|
import { star } from "@/games/genshin/assets/stars";
|
||||||
import { TalentSvg } from "@/assets/talent";
|
import { TalentSvg } from "@/games/genshin/assets/talent";
|
||||||
import { WeaponGlazeSvg } from "@/assets/weapon-glaze";
|
import { WeaponGlazeSvg } from "@/games/genshin/assets/weapon-glaze";
|
||||||
|
import { assets, batchStore, getAvatar } from "@/games/genshin/data/assets";
|
||||||
|
import { cardErrorLocs } from "@/games/genshin/data/locs";
|
||||||
|
import { resolveShowcaseChar } from "@/games/genshin/data/matcher";
|
||||||
|
import { mockData } from "@/games/genshin/data/mock";
|
||||||
|
import { buildDisplayRows } from "@/games/genshin/data/rows";
|
||||||
|
import type { RollSummary } from "@/games/genshin/data/substat";
|
||||||
|
import { buildRollSummary } from "@/games/genshin/data/substat";
|
||||||
|
import { Locale, SubstatOrder } from "@/games/genshin/types/enka";
|
||||||
|
import { FightPropMapEnum, PercentageFightProp } from "@/games/genshin/types/fightPropMap";
|
||||||
import type { App } from "@/index";
|
import type { App } from "@/index";
|
||||||
import { assets, batchStore, getAvatar } from "@/lib/assets";
|
|
||||||
import { Deduplicator } from "@/lib/dedupe";
|
import { Deduplicator } from "@/lib/dedupe";
|
||||||
import { cardErrorLocs } from "@/lib/locs";
|
|
||||||
import { createLogger } from "@/lib/logger";
|
import { createLogger } from "@/lib/logger";
|
||||||
import { resolveShowcaseChar } from "@/lib/matcher";
|
|
||||||
import { mockData } from "@/lib/mock";
|
|
||||||
import { buildDisplayRows } from "@/lib/rows";
|
|
||||||
import type { RollSummary } from "@/lib/substat";
|
|
||||||
import { buildRollSummary } from "@/lib/substat";
|
|
||||||
import { Locale, SubstatOrder } from "@/type/enka";
|
|
||||||
import { FightPropMapEnum, PercentageFightProp } from "@/type/fightPropMap";
|
|
||||||
|
|
||||||
import DiffPage from "../../hmr.html";
|
import DiffPage from "../../../hmr.html";
|
||||||
import { ErrorPage } from "./error";
|
import { ErrorPage } from "@/v1/error";
|
||||||
import { ListPage } from "./list";
|
import { ListPage } from "./render/list";
|
||||||
|
|
||||||
const cache = new TTLCache();
|
const cache = new TTLCache();
|
||||||
const deduper = new Deduplicator();
|
const deduper = new Deduplicator();
|
||||||
@@ -8,19 +8,19 @@ import {
|
|||||||
listNcFrame,
|
listNcFrame,
|
||||||
listRtop,
|
listRtop,
|
||||||
smug,
|
smug,
|
||||||
} from "@/assets";
|
} from "@/games/genshin/assets";
|
||||||
import { assets, getAvatar } from "@/lib/assets";
|
import { assets, getAvatar } from "@/games/genshin/data/assets";
|
||||||
import type { Logger } from "@/lib/logger";
|
import { CharacterNamecardMap } from "@/games/genshin/data/namecards";
|
||||||
import { CharacterNamecardMap } from "@/lib/namecards";
|
|
||||||
import { arrRandom } from "@/lib/util";
|
|
||||||
import {
|
import {
|
||||||
ElementList,
|
ElementList,
|
||||||
type AvatarStore,
|
type AvatarStore,
|
||||||
type EnkaLocsLanguage,
|
type EnkaLocsLanguage,
|
||||||
type EnkaNamecards,
|
type EnkaNamecards,
|
||||||
} from "@/type/enka";
|
} from "@/games/genshin/types/enka";
|
||||||
|
import type { Logger } from "@/lib/logger";
|
||||||
|
import { arrRandom } from "@/lib/util";
|
||||||
|
|
||||||
import { CardResponse } from "./response";
|
import { CardResponse } from "@/v1/response";
|
||||||
|
|
||||||
export async function ListPage(
|
export async function ListPage(
|
||||||
{ playerInfo: p, uid, avatarInfoList }: ResolvedRoot,
|
{ playerInfo: p, uid, avatarInfoList }: ResolvedRoot,
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import type { App } from "@";
|
|
||||||
import { status } from "elysia";
|
|
||||||
import { TTLCache, fetchEnka } from "enka.ts";
|
|
||||||
|
|
||||||
import { batchStore } from "@/lib/assets";
|
|
||||||
import { Deduplicator } from "@/lib/dedupe";
|
|
||||||
import { createLogger } from "@/lib/logger";
|
|
||||||
|
|
||||||
const uidRegex = /^\d{10}$/;
|
|
||||||
|
|
||||||
const cache = new TTLCache();
|
|
||||||
const deduper = new Deduplicator();
|
|
||||||
|
|
||||||
export function endfieldCard<A extends App>(app: A) {
|
|
||||||
return app.group("/endfield/:uid", (app) =>
|
|
||||||
app
|
|
||||||
.derive(() => ({
|
|
||||||
log: createLogger(),
|
|
||||||
}))
|
|
||||||
.head("/preload", async ({ params: { uid }, log }) => {
|
|
||||||
log`Preloading ${uid}`;
|
|
||||||
try {
|
|
||||||
await Promise.all([
|
|
||||||
deduper.acquire("stores", () =>
|
|
||||||
batchStore(
|
|
||||||
"avatars",
|
|
||||||
"locs",
|
|
||||||
"relics",
|
|
||||||
"weapons",
|
|
||||||
"pfps",
|
|
||||||
"affixes",
|
|
||||||
).finally(() => log`Stores loaded`),
|
|
||||||
),
|
|
||||||
deduper.acquire(`enka-${uid}`, () =>
|
|
||||||
fetchEnka(uid, false).finally(() => log`Enka data loaded`),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return "OK";
|
|
||||||
} catch {
|
|
||||||
return status(502, "Data Preload Error");
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
import type { App } from "@";
|
import type { App } from "@";
|
||||||
import { t } from "elysia";
|
import { t } from "elysia";
|
||||||
|
|
||||||
import { card } from "./card";
|
// import { endfieldCard } from "@/games/endfield";
|
||||||
|
import { card } from "@/games/genshin";
|
||||||
|
|
||||||
export function v1routes(app: App) {
|
export function v1routes(app: App) {
|
||||||
app.group("/v1", (app) =>
|
app.group("/v1", (app) =>
|
||||||
app.use(card).get(
|
app
|
||||||
|
.use(card)
|
||||||
|
// .use(endfieldCard)
|
||||||
|
.get(
|
||||||
"/status/:status",
|
"/status/:status",
|
||||||
async ({ params: { status }, set }) => {
|
async ({ params: { status }, set }) => {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { join } from "node:path";
|
|||||||
|
|
||||||
import { file, hash } from "bun";
|
import { file, hash } from "bun";
|
||||||
|
|
||||||
import { AssetManager } from "@/lib/assets";
|
import { AssetManager } from "@/games/genshin/data/assets";
|
||||||
|
|
||||||
describe("AssetManager", () => {
|
describe("AssetManager", () => {
|
||||||
let origCwd: string;
|
let origCwd: string;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { join } from "node:path";
|
|||||||
|
|
||||||
import { $, write } from "bun";
|
import { $, write } from "bun";
|
||||||
|
|
||||||
import { libraryRaw } from "@/assets/icons";
|
import { libraryRaw } from "@/games/genshin/assets/icons";
|
||||||
|
|
||||||
function toJsx(
|
function toJsx(
|
||||||
name: string,
|
name: string,
|
||||||
@@ -44,7 +44,7 @@ function toJsx(
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = join(__dirname, "../src/assets/icons-library.tsx");
|
const target = join(__dirname, "../src/games/genshin/assets/icons-library.tsx");
|
||||||
write(
|
write(
|
||||||
target,
|
target,
|
||||||
`import { SVGProps } from "react";
|
`import { SVGProps } from "react";
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ fetch("https://gi.yatta.moe/api/v2/en/avatar")
|
|||||||
).filter((e) => e[1]),
|
).filter((e) => e[1]),
|
||||||
);
|
);
|
||||||
log`crawling complete`;
|
log`crawling complete`;
|
||||||
const target = join(__dirname, "../src/lib/namecards.ts");
|
const target = join(__dirname, "../src/games/genshin/data/namecards.ts");
|
||||||
await write(
|
await write(
|
||||||
target,
|
target,
|
||||||
`// GENERATED BY util/genNamecardRelation\n\nexport const CharacterNamecardMap: Record<string, number> = ${JSON.stringify(map)}`,
|
`// GENERATED BY util/genNamecardRelation\n\nexport const CharacterNamecardMap: Record<string, number> = ${JSON.stringify(map)}`,
|
||||||
|
|||||||