x
This commit is contained in:
@@ -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<ReturnType<typeof getCachedPublicGuide>>,
|
||||
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}`,
|
||||
});
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -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: "คู่มือเกนชินสาธารณะที่เตรียมไว้ให้คุณ",
|
||||
});
|
||||
}
|
||||
@@ -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<string> | undefined;
|
||||
let fontDataPromise: Promise<Buffer> | 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<string> {
|
||||
bannerDataPromise ??= readFile(
|
||||
join(process.cwd(), "public/web_banner.png"),
|
||||
"base64",
|
||||
).then((data) => `data:image/png;base64,${data}`);
|
||||
return bannerDataPromise;
|
||||
}
|
||||
|
||||
function getFontData(): Promise<Buffer> {
|
||||
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(
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
width: "1222px",
|
||||
height: "560px",
|
||||
fontFamily: "Kanit",
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
<img src={bannerSrc} alt="Banner" width={1222} height={560} />
|
||||
<div
|
||||
style={{
|
||||
fontSize: 64,
|
||||
position: "absolute",
|
||||
top: "160px",
|
||||
left: "60px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
right: "450px",
|
||||
bottom: "100px",
|
||||
}}
|
||||
>
|
||||
<div>{shorten(title, 24)}</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 24,
|
||||
marginTop: "-20px",
|
||||
color: "#aaa",
|
||||
}}
|
||||
>
|
||||
{shorten(sub, 92)}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
{
|
||||
...ogImageSize,
|
||||
fonts: [{ name: "Kanit", data: kanit, style: "normal", weight: 600 }],
|
||||
},
|
||||
);
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 507 KiB |
Reference in New Issue
Block a user