cvsa/packages/frontend/src/pages/song/[id]/info.astro

212 lines
10 KiB
Plaintext

---
import Layout from "@layouts/Layout.astro";
import TitleBar from "@components/TitleBar.astro";
import pg from "pg";
import { format } from 'date-fns';
import { zhCN } from 'date-fns/locale';
const databaseHost = process.env.DB_HOST
const databaseName = process.env.DB_NAME
const databaseUser = process.env.DB_USER
const databasePassword = process.env.DB_PASSWORD
const databasePort = process.env.DB_PORT
const postgresConfig = {
hostname: databaseHost,
port: parseInt(databasePort!),
database: databaseName,
user: databaseUser,
password: databasePassword,
};
// 路由参数
const { id } = Astro.params;
const { Client } = pg;
const client = new Client(postgresConfig);
await client.connect();
// 数据库查询函数
async function getVideoMetadata(aid: number) {
const res = await client.query("SELECT * FROM bilibili_metadata WHERE aid = $1", [aid]);
if (res.rows.length <= 0) {
return null;
}
const row = res.rows[0];
if (row) {
return row;
}
return {};
}
async function getVideoSnapshots(aid: number) {
const res = await client.query("SELECT * FROM video_snapshot WHERE aid = $1 ORDER BY created_at DESC", [
aid,
]);
if (res.rows.length <= 0) {
return null;
}
return res.rows;
}
async function getAidFromBV(bv: string) {
const res = await client.query("SELECT aid FROM bilibili_metadata WHERE bvid = $1", [bv]);
if (res.rows.length <= 0) {
return null;
}
const row = res.rows[0];
if (row && row.aid) {
return Number(row.aid);
}
return null;
}
async function idExists(aid: number) {
const res = await client.query("SELECT COUNT(*) FROM bilibili_metadata WHERE aid = $1", [aid]);
return res.rows[0].count > 0;
}
async function getVideoAid(id: string) {
if (id.startsWith("av")) {
return parseInt(id.slice(2));
} else if (id.startsWith("BV")) {
return getAidFromBV(id);
}
return parseInt(id);
}
// 获取数据
if (!id) {
Astro.response.status = 404;
client.end();
return new Response(null, { status: 404 });
}
const aid = await getVideoAid(id);
if (!aid || isNaN(aid)) {
Astro.response.status = 404;
client.end();
return new Response(null, { status: 404 });
}
const aidExists = await idExists(aid);
if (!aidExists) {
Astro.response.status = 404;
client.end();
return new Response(null, { status: 404 });
}
const videoInfo = await getVideoMetadata(aid);
const snapshots = await getVideoSnapshots(aid);
client.end();
interface Snapshot {
created_at: Date;
views: number;
danmakus: number;
replies: number;
coins: number;
likes: number;
favorites: number;
shares: number;
id: number;
}
---
<Layout>
<TitleBar />
<main class="flex flex-col items-center min-h-screen gap-8 mt-10 md:mt-6 relative z-0 overflow-x-auto pb-8">
<div class="w-full lg:max-w-4xl lg:mx-auto lg:p-6">
<h1 class="text-2xl font-medium ml-2 mb-4">视频信息: <a href={`https://www.bilibili.com/video/av${aid}`} class="underline ">av{aid}</a></h1>
<div class="mb-6">
<h2 class="px-2 mb-2 text-xl font-medium">基本信息</h2>
<div class="overflow-x-auto max-w-full px-2">
<table class="table-fixed">
<tbody>
<tr>
<td class="max-w-14 min-w-14 border dark:border-zinc-500 px-2 md:px-4 py-2 font-semibold">ID</td>
<td class="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{videoInfo?.id}</td>
</tr>
<tr>
<td class="max-w-14 min-w-14 border dark:border-zinc-500 px-2 md:px-4 py-2 font-semibold">AID</td>
<td class="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{videoInfo?.aid}</td>
</tr>
<tr>
<td class="max-w-14 min-w-14 border dark:border-zinc-500 px-2 md:px-4 py-2 font-semibold">BVID</td>
<td class="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{videoInfo?.bvid}</td>
</tr>
<tr>
<td class="max-w-14 min-w-14 border dark:border-zinc-500 px-2 md:px-4 py-2 font-[470]">标题</td>
<td class="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{videoInfo?.title}</td>
</tr>
<tr>
<td class="max-w-14 min-w-14 border dark:border-zinc-500 px-2 md:px-4 py-2 font-[470]">描述</td>
<td class="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{videoInfo?.description}</td>
</tr>
<tr>
<td class="max-w-14 min-w-14 border dark:border-zinc-500 px-2 md:px-4 py-2 font-semibold">UID</td>
<td class="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{videoInfo?.uid}</td>
</tr>
<tr>
<td class="max-w-14 min-w-14 border dark:border-zinc-500 px-2 md:px-4 py-2 font-[470]">标签</td>
<td class="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{videoInfo?.tags}</td>
</tr>
<tr>
<td class="max-w-14 min-w-14 border dark:border-zinc-500 px-2 md:px-4 py-2 font-[470]">发布时间</td>
<td class="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{videoInfo?.published_at ? format(new Date(videoInfo.published_at), 'yyyy-MM-dd HH:mm:ss', { locale: zhCN }) : '-'}</td>
</tr>
<tr>
<td class="max-w-14 min-w-14 border dark:border-zinc-500 px-2 md:px-4 py-2 font-[470]">时长 (秒)</td>
<td class="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{videoInfo?.duration}</td>
</tr>
<tr>
<td class="max-w-14 min-w-14 border dark:border-zinc-500 px-2 md:px-4 py-2 font-[470]">创建时间</td>
<td class="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{videoInfo?.created_at ? format(new Date(videoInfo.created_at), 'yyyy-MM-dd HH:mm:ss', { locale: zhCN }) : '-'}</td>
</tr>
<tr>
<td class="max-w-14 min-w-14 border dark:border-zinc-500 px-2 md:px-4 py-2 font-[470]">封面</td>
<td class="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{videoInfo?.cover_url ? videoInfo.cover_url : '-'}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div>
<h2 class="px-2 mb-2 text-xl font-medium">播放量历史数据</h2>
{snapshots && snapshots.length > 0 ? (
<div class="overflow-x-auto px-2">
<table class="table-auto w-full">
<thead>
<tr>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium">创建时间</th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium">观看</th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium">硬币</th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium">点赞</th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium">收藏</th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium">分享</th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium">弹幕</th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium">评论</th>
</tr>
</thead>
<tbody>
{snapshots.map((snapshot: Snapshot) => (
<tr>
<td class="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 class="border dark:border-zinc-500 px-4 py-2">{snapshot.views}</td>
<td class="border dark:border-zinc-500 px-4 py-2">{snapshot.coins}</td>
<td class="border dark:border-zinc-500 px-4 py-2">{snapshot.likes}</td>
<td class="border dark:border-zinc-500 px-4 py-2">{snapshot.favorites}</td>
<td class="border dark:border-zinc-500 px-4 py-2">{snapshot.shares}</td>
<td class="border dark:border-zinc-500 px-4 py-2">{snapshot.danmakus}</td>
<td class="border dark:border-zinc-500 px-4 py-2">{snapshot.replies}</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<p>暂无历史数据。</p>
)}
</div>
</div>
</main>
</Layout>