diff --git a/packages/solid/src/lib/db/bilibiliMetadata.ts b/packages/solid/src/lib/db/bilibiliMetadata.ts
new file mode 100644
index 0000000..86616a3
--- /dev/null
+++ b/packages/solid/src/lib/db/bilibiliMetadata.ts
@@ -0,0 +1,15 @@
+import { dbMain } from "~/drizzle";
+import { bilibiliMetadata } from "~db/main/schema";
+import { eq } from "drizzle-orm";
+
+export const getVideoAID = async (id: string) => {
+ "use server";
+ if (id.startsWith("av")) {
+ return parseInt(id.slice(2));
+ } else if (id.startsWith("BV")) {
+ const data = await dbMain.select().from(bilibiliMetadata).where(eq(bilibiliMetadata.bvid, id));
+ return data[0].aid;
+ } else {
+ return null;
+ }
+};
\ No newline at end of file
diff --git a/packages/solid/src/lib/db/songs.ts b/packages/solid/src/lib/db/songs.ts
new file mode 100644
index 0000000..2367233
--- /dev/null
+++ b/packages/solid/src/lib/db/songs.ts
@@ -0,0 +1,15 @@
+import { dbMain } from "~/drizzle";
+import { songs } from "~db/main/schema";
+import { eq } from "drizzle-orm";
+
+export const findSongIDFromAID = async (aid: number) => {
+ "use server";
+ const data = await dbMain
+ .select({
+ id: songs.id
+ })
+ .from(songs)
+ .where(eq(songs.aid, aid))
+ .limit(1);
+ return data[0].id;
+};
\ No newline at end of file
diff --git a/packages/solid/src/routes/song/[id]/_info.tsx b/packages/solid/src/routes/song/[id]/_info.tsx
deleted file mode 100644
index e44e7fc..0000000
--- a/packages/solid/src/routes/song/[id]/_info.tsx
+++ /dev/null
@@ -1,208 +0,0 @@
-import { DateTime } from "luxon";
-import { useParams } from "@solidjs/router";
-import { createResource } from "solid-js";
-import { Suspense } from "solid-js";
-import { For } from "solid-js";
-import { useCachedFetch } from "~/lib/dbCache";
-import { dbMain } from "~/drizzle";
-import { bilibiliMetadata, videoSnapshot } from "~db/main/schema";
-import { desc, eq } from "drizzle-orm";
-import { BilibiliMetadataType, VideoSnapshotType } from "~db/outerSchema";
-import { Context, useRequestContext } from "~/components/requestContext";
-import { Layout } from "~/components/layout";
-
-async function getAllSnapshots(aid: number, context: Context) {
- "use server";
- return useCachedFetch(
- async () => {
- return dbMain
- .select()
- .from(videoSnapshot)
- .where(eq(videoSnapshot.aid, aid))
- .orderBy(desc(videoSnapshot.createdAt));
- },
- "all-snapshots",
- context,
- [aid]
- );
-}
-
-async function getVideoMetadata(avORbv: number | string, context: Context) {
- "use server";
- if (typeof avORbv === "number") {
- return useCachedFetch(
- async () => {
- return dbMain.select().from(bilibiliMetadata).where(eq(bilibiliMetadata.aid, avORbv)).limit(1);
- },
- "bili-metadata",
- context,
- [avORbv]
- );
- } else {
- return useCachedFetch(
- async () => {
- return dbMain.select().from(bilibiliMetadata).where(eq(bilibiliMetadata.bvid, avORbv)).limit(1);
- },
- "bili-metadata",
- context,
- [avORbv]
- );
- }
-}
-
-const MetadataRow = ({ title, desc }: { title: string; desc: string | number | undefined | null }) => {
- if (!desc) return <>>;
- return (
-
- |
- {title}
- |
- {desc} |
-
- );
-};
-
-export default function VideoInfoPage() {
- const params = useParams();
- const { id } = params;
- const context = useRequestContext();
- const [data] = createResource(async () => {
- let videoInfo: BilibiliMetadataType | null = null;
- let snapshots: VideoSnapshotType[] = [];
-
- try {
- const videoData = await getVideoMetadata(id, context);
- if (videoData.length === 0) {
- return null;
- }
- const snapshotsData = await getAllSnapshots(videoData[0].aid, context);
- videoInfo = videoData[0];
- if (snapshotsData) {
- snapshots = snapshotsData;
- }
- } catch (e) {
- console.error(e);
- }
-
- if (!videoInfo) {
- return null;
- }
-
- const title = `${videoInfo.title} - 歌曲信息 - 中 V 档案馆`;
-
- return {
- v: videoInfo,
- s: snapshots,
- t: title
- };
- });
-
- return (
-
-
-
- loading
}>
- {data()?.t}
- {data()?.t}
-
-
-
-
基本信息
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
播放量历史数据
-
-
-
-
-
- | 创建时间 |
- 观看 |
- 硬币 |
- 点赞 |
- 收藏 |
- 分享 |
- 弹幕 |
- 评论 |
-
-
-
-
- {(snapshot) => (
-
- |
- {DateTime.fromJSDate(new Date(snapshot.createdAt)).toFormat(
- "yyyy-MM-dd HH:mm:ss"
- )}
- |
-
- {snapshot.views}
- |
-
- {snapshot.coins}
- |
-
- {snapshot.likes}
- |
-
- {snapshot.favorites}
- |
-
- {snapshot.shares}
- |
-
- {snapshot.danmakus}
- |
-
- {snapshot.replies}
- |
-
- )}
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/packages/solid/src/routes/song/[id]/info.tsx b/packages/solid/src/routes/song/[id]/info.tsx
index 35f1a5d..b71c958 100644
--- a/packages/solid/src/routes/song/[id]/info.tsx
+++ b/packages/solid/src/routes/song/[id]/info.tsx
@@ -4,32 +4,10 @@ import { RightSideBar } from "~/components/song/RightSideBar";
import { Content } from "~/components/song/Content";
import { createAsync, query, RouteDefinition, useParams } from "@solidjs/router";
import { dbMain } from "~/drizzle";
-import { bilibiliMetadata, songs } from "~db/main/schema";
+import { songs } from "~db/main/schema";
import { eq } from "drizzle-orm";
-
-const getVideoAID = async (id: string) => {
- "use server";
- if (id.startsWith("av")) {
- return parseInt(id.slice(2));
- } else if (id.startsWith("BV")) {
- const data = await dbMain
- .select()
- .from(bilibiliMetadata)
- .where(eq(bilibiliMetadata.bvid, id));
- return data[0].aid;
- }
- else {
- return null;
- }
-};
-
-const findSongIDFromAID = async (aid: number) => {
- "use server";
- const data = await dbMain.select({
- id: songs.id,
- }).from(songs).where(eq(songs.aid, aid)).limit(1);
- return data[0].id;
-}
+import { getVideoAID } from "~/lib/db/bilibiliMetadata";
+import { findSongIDFromAID } from "~/lib/db/songs";
const getSongInfo = query(async (songID: number) => {
"use server";
@@ -42,19 +20,17 @@ const getSongInfoFromID = query(async (id: string) => {
const aid = await getVideoAID(id);
if (!aid && parseInt(id)) {
return getSongInfo(parseInt(id));
- }
- else if (!aid) {
+ } else if (!aid) {
return null;
}
const songID = await findSongIDFromAID(aid);
return getSongInfo(songID);
-}, "songsRaw")
+}, "songsRaw");
export const route = {
preload: ({ params }) => getSongInfoFromID(params.id)
} satisfies RouteDefinition;
-
export default function Info() {
const params = useParams();
const info = createAsync(() => getSongInfoFromID(params.id));
@@ -69,7 +45,7 @@ export default function Info() {
-
+
diff --git a/src/importSnapshots.ts b/src/importSnapshots.ts
new file mode 100644
index 0000000..817a056
--- /dev/null
+++ b/src/importSnapshots.ts
@@ -0,0 +1,91 @@
+import logger from "@core/log/logger";
+import { sql } from "@core/index";
+import arg from "arg";
+import fs from "fs/promises";
+import path from "path";
+
+const quit = (reason?: string) => {
+ reason && logger.error(reason);
+ process.exit();
+};
+
+interface Record {
+ id: number;
+ added: number;
+ aid: number;
+ view: number;
+ danmaku: number;
+ reply: number;
+ favorite: number;
+ coin: number;
+ share: number;
+ like: number;
+ dislike: number | null;
+ now_rank: number | null;
+ his_rank: number | null;
+ vt: number | null;
+ vv: number | null;
+}
+
+async function fetchData(aid: number): Promise
{
+ const cacheDir = path.resolve("temp/tdd");
+ const cacheFile = path.join(cacheDir, `${aid}.json`);
+ console.log(cacheFile)
+ try {
+ const cached = await fs.readFile(cacheFile, "utf-8");
+ logger.log(`Using cached data for aid ${aid}`);
+ return JSON.parse(cached) as Record[];
+ } catch (e){
+ console.error(e)
+ logger.log(`Fetching data from API for aid ${aid}`);
+ const url = `https://api.bunnyxt.com/tdd/v2/video/${aid}/record`;
+ const res = await fetch(url);
+ if (!res.ok) {
+ throw new Error(`Failed to fetch data: ${res.status} ${res.statusText}`);
+ }
+ const data = (await res.json()) as Record[];
+
+ await fs.mkdir(cacheDir, { recursive: true });
+ await fs.writeFile(cacheFile, JSON.stringify(data, null, 2), "utf-8");
+
+ return data;
+ }
+}
+
+const args = arg({
+ "--aid": Number
+});
+
+const aid = args["--aid"];
+if (!aid) {
+ quit("Missing --aid ");
+}
+
+const pg = sql;
+
+async function importData() {
+ const data = await fetchData(aid!);
+ const length = data.length;
+ logger.log(`Found ${length} snapshots for aid ${aid}`);
+ let i = 0;
+ for (const record of data) {
+ try {
+ const time = new Date(record.added * 1000);
+ const timeString = time.toISOString().replace("T", " ");
+ await pg`
+ INSERT INTO video_snapshot (aid, created_at, views, danmakus, replies, favorites, coins, shares, likes)
+ VALUES (${record.aid}, ${timeString}, ${record.view}, ${record.danmaku}, ${record.reply}, ${record.favorite}, ${record.coin}, ${record.share}, ${record.like})
+ `;
+ } catch (e) {
+ logger.error(e as Error);
+ logger.warn(
+ `Failed to import snapshot for aid ${record.aid} at ${record.added}, id: ${record.id}`
+ );
+ }
+ i++;
+ logger.log(`Importing snapshots for aid ${record.aid} - Progress: ${i}/${length}`);
+ }
+}
+
+await importData();
+quit();