/* eslint-disable @next/next/no-img-element */ import { readFile } from "node:fs/promises"; import { join } from "node:path"; import { ImageResponse } from "next/og"; import sharp from "sharp"; import { rarityGradient } from "@/lib/catalog/rarity"; import { artifactTierCount } from "@/lib/guides/artifacts"; export const ogImageSize = { width: 1222, height: 560 } as const; export const ogImageContentType = "image/png"; let bannerDataPromise: Promise | undefined; let fontDataPromise: Promise | undefined; let characterFontDataPromise: Promise | undefined; const imageDataPromises = new Map>(); function shorten(value: string, length: number): string { const normalized = value.replace(/\s+/gu, " ").trim(); return normalized.length > length ? `${normalized.slice(0, length - 1).trimEnd()}…` : normalized; } function metric(value: string): string { return Number(value) === 0 ? "-%" : `${Number(value).toLocaleString("en-US", { maximumFractionDigits: 2 })}%`; } function getBannerData(): Promise { bannerDataPromise ??= readFile( join(process.cwd(), "public/web_banner.png"), "base64", ).then((data) => `data:image/png;base64,${data}`); return bannerDataPromise; } function getFontData(): Promise { fontDataPromise ??= readFile(join(process.cwd(), "public/Kanit-SemiBold.ttf")); return fontDataPromise; } function getCharacterFontData(): Promise { characterFontDataPromise ??= readFile(join(process.cwd(), "public/zh-cn.ttf")); return characterFontDataPromise; } function getImageData(url: string): Promise { const cached = imageDataPromises.get(url); if (cached) return cached; const promise = fetch(url) .then(async (response) => { if (!response.ok) return null; const png = await sharp(Buffer.from(await response.arrayBuffer())) .png() .toBuffer(); return `data:image/png;base64,${png.toString("base64")}`; }) .catch(() => null); imageDataPromises.set(url, promise); return promise; } export async function generateOgImage({ title, sub, characterCard = false, backgroundUrl, characterUrl, recommendedWeapon, recommendedArtifact, }: { title: string; sub: string; characterCard?: boolean; backgroundUrl?: string | null; characterUrl?: string | null; recommendedWeapon?: { name: string; refinement: number; rarity: number | null; imageUrl: string; overallPercent: string | null; personalPercent: string | null; } | null; recommendedArtifact?: { first: { name: string; rarity: number | null; imageUrl: string; }; second: { name: string; rarity: number | null; imageUrl: string; } | null; percent: string; } | null; }) { const [ bannerSrc, kanit, characterFont, characterBackgroundSrc, characterArtSrc, weaponArtSrc, firstArtifactArtSrc, secondArtifactArtSrc, ] = await Promise.all([ characterCard ? null : getBannerData(), getFontData(), characterCard ? getCharacterFontData() : null, characterCard && backgroundUrl ? getImageData(backgroundUrl) : null, characterCard && characterUrl ? getImageData(characterUrl) : null, recommendedWeapon ? getImageData(recommendedWeapon.imageUrl) : null, recommendedArtifact ? getImageData(recommendedArtifact.first.imageUrl) : null, recommendedArtifact?.second ? getImageData(recommendedArtifact.second.imageUrl) : null, ]); const backgroundSrc = characterCard ? characterBackgroundSrc : bannerSrc; return new ImageResponse(
{backgroundSrc ? ( ) : (
)} {characterCard && characterArtSrc ? ( ) : null} {characterCard ? (
) : null}
{characterCard ? (
เกนชินไม่ใช่เกมมือถือไกด์
) : null}
{shorten(title, 24)}
{shorten(sub, 92)}
{recommendedWeapon ? (
{weaponArtSrc ? ( ) : null}
{shorten(recommendedWeapon.name, 19)} R{recommendedWeapon.refinement}
{recommendedWeapon.overallPercent || recommendedWeapon.personalPercent ? (
{recommendedWeapon.overallPercent ? (
{metric(recommendedWeapon.overallPercent)}
ทั้งทีม
) : null} {recommendedWeapon.personalPercent ? (
{metric(recommendedWeapon.personalPercent)}
ส่วนตัว
) : null}
) : null}
) : null} {recommendedArtifact ? (
{firstArtifactArtSrc ? (
) : null} {recommendedArtifact.second && secondArtifactArtSrc ? (
) : null}
{artifactTierCount(recommendedArtifact.second?.name ?? null)} {shorten(recommendedArtifact.first.name, recommendedArtifact.second ? 12 : 21)} {recommendedArtifact.second ? ` + ${shorten(recommendedArtifact.second.name, 12)}` : ""}
{metric(recommendedArtifact.percent)}
) : null}
, { ...ogImageSize, fonts: [ { name: "Kanit", data: kanit, style: "normal", weight: 600 }, ...(characterFont ? [{ name: "Genshin", data: characterFont, style: "normal" as const, weight: 400 as const }] : []), ], }, ); }