/* eslint-disable @next/next/no-img-element */ import { ImageResponse } from "next/og"; import sharp from "sharp"; import { getPublicTeamExport } from "@/lib/guides/queries"; const anuphanFonts = Promise.all( [ { subset: "latin", weight: 400 as const }, { subset: "thai", weight: 400 as const }, { subset: "latin", weight: 700 as const }, { subset: "thai", weight: 700 as const }, ].map(async ({ subset, weight }) => { const response = await fetch( `https://unpkg.com/@fontsource/anuphan@5.3.0/files/anuphan-${subset}-${weight}-normal.woff`, ); if (!response.ok) throw new Error(`Unable to load Anuphan ${subset} ${weight}`); return { name: "Anuphan", data: await response.arrayBuffer(), weight, style: "normal" as const, }; }), ).catch(() => []); function amount(value: number): string { return new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(value); } function contentDisposition(teamName: string): string { const displayName = teamName.trim() || "team"; const fallback = displayName .normalize("NFKD") .replace(/[^a-z0-9]+/giu, "-") .replace(/^-|-$/gu, "") || "team"; const encoded = encodeURIComponent(`${displayName}.png`).replace( /['()*]/gu, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`, ); return `attachment; filename="${fallback}.png"; filename*=UTF-8''${encoded}`; } async function pngDataUrl(url: string): Promise { try { const response = await fetch(url); if (!response.ok) return null; const png = await sharp(Buffer.from(await response.arrayBuffer())) .resize(156, 156, { fit: "cover" }) .png() .toBuffer(); return `data:image/png;base64,${png.toString("base64")}`; } catch { return null; } } export async function GET( _request: Request, context: RouteContext<"/api/teams/[teamId]/image">, ) { const { teamId } = await context.params; const team = await getPublicTeamExport(teamId); if (!team) return new Response("Team not found", { status: 404 }); const total = team.members.reduce( (sum, member) => sum + Number(member.damage), 0, ); const seconds = Number(team.rotationSeconds); const dps = seconds > 0 ? total / seconds : 0; const fonts = await anuphanFonts; const memberImages = await Promise.all( team.members.map((member) => pngDataUrl(member.character.imageUrl)), ); return new ImageResponse(
{team.guideName}
{team.title} · ระยะเวลาโรเทชั่น {seconds.toLocaleString(undefined, { maximumFractionDigits: 2 })} วิ
{team.members.map((member, index) => { const damage = Number(member.damage); const percent = total > 0 ? (damage / total) * 100 : 0; return (
{memberImages[index] ? ( ) : (
{member.character.name.slice(0, 1)}
)}
{member.character.name}
{amount(damage)}
{percent.toFixed(1)}%
); })}
DPS: {amount(dps)}
{team.combo}
By YT เกนชินไม่ใช่เกมมือถือ
, { width: 550, height: 776, fonts: fonts.length ? fonts : undefined, headers: { "Cache-Control": "no-store", "Content-Disposition": contentDisposition(team.title), "Content-Type": "image/png", }, }, ); }