Initial Commit
This commit is contained in:
+197
@@ -0,0 +1,197 @@
|
||||
import { ImageResponse } from "@takumi-rs/image-response";
|
||||
import { file } from "bun";
|
||||
import { NotFoundError, status, t } from "elysia";
|
||||
import {
|
||||
fetchEnka,
|
||||
InternalError,
|
||||
InvalidFormatError,
|
||||
MaintenanceError,
|
||||
RatelimitError,
|
||||
} from "enka.ts";
|
||||
import { twj } from "tw-to-css";
|
||||
import type { App } from "@";
|
||||
import { ConstsSvg } from "@/assets/consts";
|
||||
import { mockData } from "@/lib/mock";
|
||||
|
||||
const font = file("src/assets/shinshin.woff2");
|
||||
const shade = file("src/assets/shade.svg");
|
||||
|
||||
export function card(app: App) {
|
||||
return app
|
||||
.group("/card", (app) =>
|
||||
app
|
||||
// .onBeforeHandle(({ key }) => {
|
||||
// if (!key.valid) throw status(401);
|
||||
// if (!key.has("card")) throw status(403);
|
||||
// })
|
||||
.get(
|
||||
"/genshin/:uid/:char",
|
||||
async ({ params: { uid, char: charId } }) => {
|
||||
try {
|
||||
// const data = await fetchEnka(uid, false);
|
||||
const data = mockData;
|
||||
if (typeof data.avatarInfoList === "undefined")
|
||||
return status(400, "The showcase for this UID is private");
|
||||
const char = data.avatarInfoList.find(
|
||||
(c) => c.avatar.avatarId === Number(charId),
|
||||
);
|
||||
if (!char) return status(400, "Character not found in showcase");
|
||||
|
||||
return new ImageResponse(
|
||||
<div style={twj("relative text-[14.8667px] text-white")}>
|
||||
<div style={twj("aspect-[1218/508] h-full")}></div>
|
||||
<div
|
||||
style={{
|
||||
...twj(
|
||||
"absolute top-0 bottom-0 left-0 w-[27.4em] p-[1.4em] bg-red-500/50",
|
||||
"flex flex-col",
|
||||
),
|
||||
textShadow:
|
||||
"0 .08em .1em black,0 .1em .3em rgba(0,0,0,.4)",
|
||||
}}
|
||||
>
|
||||
<div style={twj("absolute top-0 left-0 bottom-0 right-0")}>
|
||||
<img src="shade" alt="shade" style={twj("opacity-40")} />
|
||||
</div>
|
||||
<div
|
||||
style={twj(
|
||||
"text-[130%] pb-[.5%] flex items-center gap-[.5em]",
|
||||
)}
|
||||
>
|
||||
{char.avatar.name}
|
||||
<div style={twj("text-[11.596px] opacity-80")}>
|
||||
▴ {data.playerInfo.nickname}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Lv. 80 /<span style={twj("opacity-60 pl-1")}>80</span>
|
||||
</div>
|
||||
<div style={twj("flex gap-1 items-end")}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={twj("h-[1em] mb-1")}
|
||||
viewBox="0 0 14 14"
|
||||
>
|
||||
<title>fren</title>
|
||||
<path
|
||||
fill="white"
|
||||
d="M6.922 12.375c0-.134-.97-1.102-1.669-1.663l-.697-.56.728-.045c.426-.027.931-.11 1.218-.203l.49-.157.457.155a4.4 4.4 0 0 0 1.2.202l.744.048-.77.618c-.825.663-1.555 1.4-1.555 1.571 0 .058-.033.105-.073.105a.07.07 0 0 1-.073-.071M4.163 9.43q-1.678-.451-2.552-2.236C1.225 6.407.679 5.763.39 5.756c-.082-.002-.032-.055.135-.143.348-.184.749-.66.979-1.162.373-.814.44-.915.94-1.426.553-.566 1.156-.909 1.86-1.06.449-.096 1.677-.051 1.99.073l.21.083-.468.428c-.257.236-.558.56-.668.718-.177.255-.253.3-.63.375a2.094 2.094 0 0 0-1.487 3c.798 1.591 3.044 1.548 3.83-.072.137-.28.163-.45.155-1.006-.009-.664-.007-.67.247-.967.285-.333.755-.606.983-.572.367.056.632 1.656.422 2.545-.323 1.37-1.443 2.51-2.818 2.868-.519.135-1.39.132-1.907-.007Zm3.65-.017-.384-.123.48-.427c.264-.235.562-.553.663-.706.154-.232.246-.29.56-.358.526-.112.95-.332 1.21-.628.965-1.1.677-2.697-.607-3.36-.306-.158-.471-.193-.92-.193-.626 0-1.013.156-1.48.6-.477.454-.59.748-.602 1.557-.009.651-.026.73-.207.968-.25.327-.687.62-.957.639-.19.014-.223-.023-.388-.422-.255-.62-.264-1.744-.018-2.459C5.713 2.906 7.125 1.9 8.816 1.9c1.538 0 2.764.79 3.47 2.236.41.84.74 1.274 1.09 1.44l.273.13-.262.135c-.37.19-.722.651-1.067 1.4-.497 1.08-1.461 1.905-2.567 2.2-.525.139-1.462.126-1.94-.027z"
|
||||
></path>
|
||||
</svg>
|
||||
8
|
||||
</div>
|
||||
|
||||
<div style={twj("grow")} />
|
||||
<div
|
||||
style={twj("flex align-start flex-col text-[1.12em]")}
|
||||
className={`${char.avatar.element} locked`}
|
||||
>
|
||||
{Array(6)
|
||||
.fill(0)
|
||||
.map((_, i) => (
|
||||
<div>
|
||||
<ConstsSvg
|
||||
locked={i >= (char.talentIdList?.length || 0)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
style={twj(
|
||||
"mb-[-1em] ml-[-.3em] mt-[.3em] text-[.9em] opacity-80",
|
||||
)}
|
||||
>
|
||||
{data.uid}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={twj(
|
||||
"absolute top-0 bottom-0 right-[23.1em] w-[21.5em] p-[1.4em_.7em_.7em] bg-green-500/50",
|
||||
)}
|
||||
></div>
|
||||
<div
|
||||
style={twj(
|
||||
"absolute top-0 bottom-0 right-0 w-[23.1em] p-[.7em] bg-blue-500/50",
|
||||
)}
|
||||
></div>
|
||||
</div>,
|
||||
{
|
||||
fonts: [await font.arrayBuffer()],
|
||||
height: 508,
|
||||
persistentImages: [
|
||||
{ data: await shade.arrayBuffer(), src: "shade" },
|
||||
],
|
||||
// drawDebugBorder: true,
|
||||
stylesheets: [
|
||||
`
|
||||
.Rock { color: #ffc64a; }
|
||||
.Wind { color: #71fdec; }
|
||||
.Ice { color: #98efff; }
|
||||
.Water { color: #56a2ff; }
|
||||
.Electric { color: #ffa1ff; }
|
||||
.Fire { color: #f58453; }
|
||||
.Grass { color: #a5ffab; }
|
||||
`,
|
||||
],
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
if (err instanceof InvalidFormatError)
|
||||
return status(400, "Invalid UID format");
|
||||
else if (err instanceof NotFoundError)
|
||||
return status(404, "UID not found on enka.network");
|
||||
else if (err instanceof MaintenanceError)
|
||||
return status(424, "enka.network is under maintenance");
|
||||
else if (err instanceof RatelimitError)
|
||||
return status(
|
||||
429,
|
||||
"Too many requests to enka.network, please try again later",
|
||||
);
|
||||
else if (err instanceof InternalError)
|
||||
return status(500, "Internal server error");
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
{
|
||||
params: t.Object({
|
||||
uid: t.String(),
|
||||
char: t.Number(),
|
||||
}),
|
||||
detail: {
|
||||
tags: ["Card"],
|
||||
description:
|
||||
"Generate a Genshin Impact enka.network card image from a UID.",
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
.get(
|
||||
"/health/enka",
|
||||
async () => {
|
||||
try {
|
||||
fetchEnka("888888888");
|
||||
return "OK";
|
||||
} catch (err) {
|
||||
if (err instanceof InvalidFormatError)
|
||||
return status(200, "enka.network API is reachable");
|
||||
else if (err instanceof NotFoundError)
|
||||
return status(200, "enka.network API is reachable");
|
||||
else if (err instanceof MaintenanceError)
|
||||
return status(503, "enka.network is under maintenance");
|
||||
else if (err instanceof RatelimitError)
|
||||
return status(
|
||||
503,
|
||||
"Too many requests to enka.network, please try again later",
|
||||
);
|
||||
else if (err instanceof InternalError)
|
||||
return status(503, "enka.network API is unreachable");
|
||||
}
|
||||
},
|
||||
{
|
||||
detail: {
|
||||
tags: ["Card"],
|
||||
description: "Check if enka.network API is reachable.",
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { App } from "@";
|
||||
import { card } from "./card";
|
||||
|
||||
export function v1routes(app: App) {
|
||||
app.group("/v1", (app) => app.use(card));
|
||||
}
|
||||
Reference in New Issue
Block a user