diff --git a/app/[character]/[page]/opengraph-image.tsx b/app/[character]/[page]/opengraph-image.tsx new file mode 100644 index 0000000..4f8609f --- /dev/null +++ b/app/[character]/[page]/opengraph-image.tsx @@ -0,0 +1,96 @@ +import { connection } from "next/server"; +import { getCachedPublicGuide } from "@/lib/cache/public"; +import { publicSections } from "@/components/public/structured-guide"; +import { + generateOgImage, + ogImageContentType, + ogImageSize, +} from "@/lib/og-image"; + +export const alt = "Buzz Guide section"; +export const size = ogImageSize; +export const contentType = ogImageContentType; + +function sectionTitle(slug: string, fallback: string): string { + if (slug === "weapon") return "อาวุธแนะนำ"; + if (slug === "artifact") return "อาร์ติแฟกต์และค่าสถานะ"; + if (slug === "constellations") return "กลุ่มดาว"; + if (slug === "team") return "ทีมแนะนำ"; + return fallback; +} + +function sectionContent( + guide: Awaited>, + slug: string, +) { + if (!guide) return { description: "คู่มือเกนชินอิมแพกต์", highlights: [] }; + + if (slug === "weapon") { + return { + description: "อาวุธที่เหมาะกับตัวละคร พร้อมอันดับและเปอร์เซ็นต์การใช้งาน", + highlights: guide.weapons.slice(0, 3).map((item, index) => { + const percent = item.overallPercent ? ` · ${item.overallPercent}%` : ""; + return `${index + 1}. ${item.weapon.name}${percent}`; + }), + }; + } + + if (slug === "artifact") { + const artifactHighlights = guide.artifactGroups.flatMap((group) => + group.rows.slice(0, 2).map((row) => + `${group.name}: ${row.first.name}${row.second ? ` + ${row.second.name}` : ""}`, + ), + ); + const targetHighlights = guide.artifactProfile?.targets + .slice(0, 2) + .map((target) => `${target.stat}: ${target.value}`) ?? []; + return { + description: "เซ็ตอาร์ติแฟกต์ ค่าสถานะหลัก และเป้าหมายที่ควรจัด", + highlights: [...artifactHighlights, ...targetHighlights], + }; + } + + if (slug === "team") { + return { + description: "ทีมแนะนำพร้อมสมาชิกและเวลาหมุนคอมโบ", + highlights: guide.teams + .slice(0, 3) + .map((team) => `${team.title} · ${team.members.length} ตัวละคร`), + }; + } + + if (slug === "constellations") { + return { + description: "รายละเอียดกลุ่มดาวและพลังที่เพิ่มขึ้นในแต่ละระดับ", + highlights: guide.constellations + .slice(0, 3) + .map((constellation) => `C${constellation.level} ${constellation.name}`), + }; + } + + const extra = guide.extras.find((item) => item.slug === slug); + return { + description: extra?.title ?? "รายละเอียดเพิ่มเติมของคู่มือ", + highlights: extra?.rows.slice(0, 3).map((row) => row.heading || row.body) ?? [], + }; +} + +export default async function Image({ + params, +}: { + params: Promise<{ character: string; page: string }>; +}) { + await connection(); + const { character, page } = await params; + const guide = await getCachedPublicGuide(character); + const section = guide + ? publicSections(guide).find((item) => item.slug === page) + : null; + if (!guide || !section) return new Response("Not found", { status: 404 }); + + const content = sectionContent(guide, page); + return generateOgImage({ + title: guide.name, + sub: `${sectionTitle(page, section.title)} · ${content.highlights.join(" · ") || content.description}`, + }); +} diff --git a/app/[character]/opengraph-image.tsx b/app/[character]/opengraph-image.tsx new file mode 100644 index 0000000..39fb2c4 --- /dev/null +++ b/app/[character]/opengraph-image.tsx @@ -0,0 +1,22 @@ +import { connection } from "next/server"; +import { getCachedPublicGuide } from "@/lib/cache/public"; +import { + generateOgImage, + ogImageContentType, + ogImageSize, +} from "@/lib/og-image"; + +export const alt = "Buzz Guide character guide"; +export const size = ogImageSize; +export const contentType = ogImageContentType; + +export default async function Image({ + params, +}: { + params: Promise<{ character: string }>; +}) { + await connection(); + const guide = await getCachedPublicGuide((await params).character); + if (!guide) return new Response("Not found", { status: 404 }); + return generateOgImage({ title: guide.name, sub: guide.overview }); +} diff --git a/app/opengraph-image.tsx b/app/opengraph-image.tsx new file mode 100644 index 0000000..dac1e55 --- /dev/null +++ b/app/opengraph-image.tsx @@ -0,0 +1,16 @@ +import { + generateOgImage, + ogImageContentType, + ogImageSize, +} from "@/lib/og-image"; + +export const alt = "Buzz Guide — คู่มือเกนชินสาธารณะที่เตรียมไว้ให้คุณ"; +export const size = ogImageSize; +export const contentType = ogImageContentType; + +export default function Image() { + return generateOgImage({ + title: "ไกด์ตัวละคร", + sub: "คู่มือเกนชินสาธารณะที่เตรียมไว้ให้คุณ", + }); +} diff --git a/lib/og-image.tsx b/lib/og-image.tsx new file mode 100644 index 0000000..96e0ef6 --- /dev/null +++ b/lib/og-image.tsx @@ -0,0 +1,83 @@ +/* eslint-disable @next/next/no-img-element */ + +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; + +import { ImageResponse } from "next/og"; + +export const ogImageSize = { width: 1222, height: 560 } as const; +export const ogImageContentType = "image/png"; + +let bannerDataPromise: Promise | undefined; +let fontDataPromise: Promise | undefined; + +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 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; +} + +export async function generateOgImage({ + title, + sub, +}: { + title: string; + sub: string; +}) { + const [bannerSrc, kanit] = await Promise.all([getBannerData(), getFontData()]); + + return new ImageResponse( +
+ Banner +
+
{shorten(title, 24)}
+
+ {shorten(sub, 92)} +
+
+
, + { + ...ogImageSize, + fonts: [{ name: "Kanit", data: kanit, style: "normal", weight: 600 }], + }, + ); +} diff --git a/public/Kanit-SemiBold.ttf b/public/Kanit-SemiBold.ttf new file mode 100755 index 0000000..028bc7a Binary files /dev/null and b/public/Kanit-SemiBold.ttf differ diff --git a/public/web_banner.png b/public/web_banner.png new file mode 100644 index 0000000..ba318c2 Binary files /dev/null and b/public/web_banner.png differ