cvsa/packages/next/app/[locale]/song/[id]/info/page.tsx

177 lines
6.0 KiB
TypeScript

import { format } from "date-fns";
import { zhCN } from "date-fns/locale";
import { getAllSnapshots } from "@/lib/db/snapshots/getAllSnapshots";
import { getAidFromBV } from "@/lib/db/bilibili_metadata/getAidFromBV";
import { getVideoMetadata } from "@/lib/db/bilibili_metadata/getVideoMetadata";
import { aidExists as idExists } from "@/lib/db/bilibili_metadata/aidExists";
import { notFound } from "next/navigation";
import { BiliVideoMetadataType, VideoSnapshotType } from "@cvsa/core";
import { Metadata } from "next";
const MetadataRow = ({ title, desc }: { title: string; desc: string | number | undefined | null }) => {
if (!desc) return <></>;
return (
<tr>
<td className="max-w-14 min-w-14 md:max-w-24 md:min-w-24 border dark:border-zinc-500 px-2 md:px-3 py-2 font-semibold">
{title}
</td>
<td className="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{desc}</td>
</tr>
);
};
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
const backendURL = process.env.BACKEND_URL;
const { id } = await params;
const res = await fetch(`${backendURL}/video/${id}/info`);
if (!res.ok) {
return {
title: "页面未找到 - 中 V 档案馆"
};
}
const data = await res.json();
return {
title: `${data.title} - 歌曲信息 - 中 V 档案馆`
};
}
export default async function VideoInfoPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
let videoInfo: BiliVideoMetadataType | null = null;
let snapshots: VideoSnapshotType[] = [];
async function getVideoAid(videoId: string | string[] | undefined) {
if (!videoId) return null;
const videoIdStr = Array.isArray(videoId) ? videoId[0] : videoId;
if (videoIdStr?.startsWith("av")) {
return parseInt(videoIdStr.slice(2));
} else if (videoIdStr?.startsWith("BV")) {
return getAidFromBV(videoIdStr);
}
return parseInt(videoIdStr);
}
const aid = await getVideoAid(id);
if (!aid) {
return notFound();
}
const exists = await idExists(aid);
if (!exists) {
return notFound();
}
try {
const videoData = await getVideoMetadata(aid);
const snapshotsData = await getAllSnapshots(aid);
videoInfo = videoData;
if (snapshotsData) {
snapshots = snapshotsData;
}
} catch (e) {
console.error(e);
}
if (!videoInfo) {
return notFound();
}
return (
<main className="flex flex-col items-center min-h-screen gap-8 mt-10 md:mt-6 relative z-0 overflow-x-auto pb-8">
<div className="w-full lg:max-w-4xl lg:mx-auto lg:p-6">
<h1 className="text-2xl font-medium ml-2 mb-4">
:{" "}
<a href={`https://www.bilibili.com/video/av${videoInfo.aid}`} className="underline">
av{videoInfo.aid}
</a>
</h1>
<div className="mb-6">
<h2 className="px-2 mb-2 text-xl font-medium"></h2>
<div className="overflow-x-auto max-w-full px-2">
<table className="table-fixed">
<tbody>
<MetadataRow title="ID" desc={videoInfo.id} />
<MetadataRow title="av 号" desc={videoInfo.aid} />
<MetadataRow title="BV 号" desc={videoInfo.bvid} />
<MetadataRow title="标题" desc={videoInfo.title} />
<MetadataRow title="描述" desc={videoInfo.description} />
<MetadataRow title="UID" desc={videoInfo.uid} />
<MetadataRow title="标签" desc={videoInfo.tags} />
<MetadataRow
title="发布时间"
desc={
videoInfo.published_at
? format(new Date(videoInfo.published_at), "yyyy-MM-dd HH:mm:ss", {
locale: zhCN
})
: null
}
/>
<MetadataRow title="时长 (秒)" desc={videoInfo.duration} />
<MetadataRow
title="创建时间"
desc={format(new Date(videoInfo.created_at), "yyyy-MM-dd HH:mm:ss", {
locale: zhCN
})}
/>
<MetadataRow title="封面" desc={videoInfo?.cover_url} />
</tbody>
</table>
</div>
</div>
<div>
<h2 className="px-2 mb-2 text-xl font-medium"></h2>
{snapshots && snapshots.length > 0 ? (
<div className="overflow-x-auto px-2">
<table className="table-auto w-full">
<thead>
<tr>
<th className="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th className="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th className="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th className="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th className="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th className="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th className="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th className="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
</tr>
</thead>
<tbody>
{snapshots.map((snapshot) => (
<tr key={snapshot.created_at}>
<td className="border dark:border-zinc-500 px-4 py-2">
{format(new Date(snapshot.created_at), "yyyy-MM-dd HH:mm:ss", {
locale: zhCN
})}
</td>
<td className="border dark:border-zinc-500 px-4 py-2">{snapshot.views}</td>
<td className="border dark:border-zinc-500 px-4 py-2">{snapshot.coins}</td>
<td className="border dark:border-zinc-500 px-4 py-2">{snapshot.likes}</td>
<td className="border dark:border-zinc-500 px-4 py-2">
{snapshot.favorites}
</td>
<td className="border dark:border-zinc-500 px-4 py-2">{snapshot.shares}</td>
<td className="border dark:border-zinc-500 px-4 py-2">
{snapshot.danmakus}
</td>
<td className="border dark:border-zinc-500 px-4 py-2">
{snapshot.replies}
</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<p></p>
)}
</div>
</div>
</main>
);
}