feat : use cover image guide instead of shit image generater
This commit is contained in:
@@ -1,28 +0,0 @@
|
|||||||
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,
|
|
||||||
characterCard: true,
|
|
||||||
backgroundUrl: guide.character.nameCardImageUrl,
|
|
||||||
characterUrl: guide.character.splashImageUrl ?? guide.character.imageUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,14 @@ export const instant = false;
|
|||||||
export async function generateMetadata({ params }: { params: Params }): Promise<Metadata> {
|
export async function generateMetadata({ params }: { params: Params }): Promise<Metadata> {
|
||||||
const { character } = await params;
|
const { character } = await params;
|
||||||
const guide = await getCachedPublicGuide(character);
|
const guide = await getCachedPublicGuide(character);
|
||||||
return guide ? { title: `${guide.name}`, description: guide.overview } : { title: "ไม่พบไกด์" };
|
return guide
|
||||||
|
? {
|
||||||
|
title: `${guide.name}`,
|
||||||
|
description: guide.overview,
|
||||||
|
openGraph: { images: [guide.coverUrl] },
|
||||||
|
twitter: { images: [guide.coverUrl] },
|
||||||
|
}
|
||||||
|
: { title: "ไม่พบไกด์" };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function GuideSectionPage({ params }: { params: Params }) {
|
export default async function GuideSectionPage({ params }: { params: Params }) {
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
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,
|
|
||||||
characterCard: true,
|
|
||||||
backgroundUrl: guide.character.nameCardImageUrl,
|
|
||||||
characterUrl: guide.character.splashImageUrl ?? guide.character.imageUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,12 @@ export async function generateMetadata({
|
|||||||
}): Promise<Metadata> {
|
}): Promise<Metadata> {
|
||||||
const guide = await getCachedPublicGuide((await params).character);
|
const guide = await getCachedPublicGuide((await params).character);
|
||||||
return guide
|
return guide
|
||||||
? { title: `${guide.name}`, description: guide.overview }
|
? {
|
||||||
|
title: `${guide.name}`,
|
||||||
|
description: guide.overview,
|
||||||
|
openGraph: { images: [guide.coverUrl] },
|
||||||
|
twitter: { images: [guide.coverUrl] },
|
||||||
|
}
|
||||||
: { title: "ไม่พบไกด์" };
|
: { title: "ไม่พบไกด์" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,174 +0,0 @@
|
|||||||
/* 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";
|
|
||||||
|
|
||||||
export const ogImageSize = { width: 1222, height: 560 } as const;
|
|
||||||
export const ogImageContentType = "image/png";
|
|
||||||
|
|
||||||
let bannerDataPromise: Promise<string> | undefined;
|
|
||||||
let fontDataPromise: Promise<Buffer> | undefined;
|
|
||||||
let characterFontDataPromise: Promise<Buffer> | undefined;
|
|
||||||
const imageDataPromises = new Map<string, Promise<string | null>>();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCharacterFontData(): Promise<Buffer> {
|
|
||||||
characterFontDataPromise ??= readFile(join(process.cwd(), "public/zh-cn.ttf"));
|
|
||||||
return characterFontDataPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getImageData(url: string): Promise<string | null> {
|
|
||||||
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,
|
|
||||||
}: {
|
|
||||||
title: string;
|
|
||||||
sub: string;
|
|
||||||
characterCard?: boolean;
|
|
||||||
backgroundUrl?: string | null;
|
|
||||||
characterUrl?: string | null;
|
|
||||||
}) {
|
|
||||||
const [
|
|
||||||
bannerSrc,
|
|
||||||
kanit,
|
|
||||||
characterFont,
|
|
||||||
characterBackgroundSrc,
|
|
||||||
characterArtSrc,
|
|
||||||
] = await Promise.all([
|
|
||||||
characterCard ? null : getBannerData(),
|
|
||||||
getFontData(),
|
|
||||||
characterCard ? getCharacterFontData() : null,
|
|
||||||
characterCard && backgroundUrl ? getImageData(backgroundUrl) : null,
|
|
||||||
characterCard && characterUrl ? getImageData(characterUrl) : null,
|
|
||||||
]);
|
|
||||||
const backgroundSrc = characterCard ? characterBackgroundSrc : bannerSrc;
|
|
||||||
|
|
||||||
return new ImageResponse(
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
width: "1222px",
|
|
||||||
height: "560px",
|
|
||||||
fontFamily: "Kanit",
|
|
||||||
color: "#fff",
|
|
||||||
textShadow: "0 2px 8px rgba(0, 0, 0, 0.8)",
|
|
||||||
position: "relative",
|
|
||||||
overflow: "hidden",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{backgroundSrc ? (
|
|
||||||
<img src={backgroundSrc} alt="" width={1222} height={560} style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover" }} />
|
|
||||||
) : (
|
|
||||||
<div style={{ position: "absolute", inset: 0, background: "#111827" }} />
|
|
||||||
)}
|
|
||||||
{characterCard && characterArtSrc ? (
|
|
||||||
<img
|
|
||||||
src={characterArtSrc}
|
|
||||||
alt=""
|
|
||||||
width={480}
|
|
||||||
height={360}
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
right: 0,
|
|
||||||
width: "58%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "cover",
|
|
||||||
objectPosition: "center",
|
|
||||||
maskImage: "linear-gradient(90deg, transparent 0%, black 28%, black 100%)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{characterCard ? (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
inset: 0,
|
|
||||||
background: "linear-gradient(90deg, rgba(3, 8, 18, 0.88) 0%, rgba(3, 8, 18, 0.62) 45%, rgba(3, 8, 18, 0.08) 100%)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
left: "60px",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
right: characterCard ? "560px" : "450px",
|
|
||||||
...(characterCard
|
|
||||||
? { top: 0, bottom: 0, justifyContent: "center" }
|
|
||||||
: { top: "160px", bottom: "100px" }),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{characterCard ? (
|
|
||||||
<div style={{ display: "flex", color: "#d8f4ee", fontSize: 28 }}>
|
|
||||||
ไกด์เกนชินไม่ใช่เกมมือถือ
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div style={{ display: "flex", fontFamily: characterCard ? "Genshin" : "Kanit", fontSize: 64, marginTop: characterCard ? "8px" : 0 }}>
|
|
||||||
{shorten(title, 24)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
fontSize: 24,
|
|
||||||
marginTop: characterCard ? "8px" : "-20px",
|
|
||||||
color: "#fff",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{shorten(sub, 92)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
{
|
|
||||||
...ogImageSize,
|
|
||||||
fonts: [
|
|
||||||
{ name: "Kanit", data: kanit, style: "normal", weight: 600 },
|
|
||||||
...(characterFont
|
|
||||||
? [{ name: "Genshin", data: characterFont, style: "normal" as const, weight: 400 as const }]
|
|
||||||
: []),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user