feat: monorepo 重构 + 新增 5 个平台适配器

项目从单体结构重构为 pnpm monorepo (shared/backend/frontend),
新增 YouTube、Instagram、Twitter/X、哔哩哔哩、微博 5 个平台适配器,
包含完整的单元测试和 E2E 测试覆盖。

- 完成 T-031~T-044: 5 个适配器实现、注册、配置和测试
- 重构前后端分离: Hono 后端 + Next.js 前端
- 151 个单元测试 + 21 个 Mock E2E + 25 个真实 E2E
- 适配器基于真实 TikHub API 响应结构实现

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
wxs
2026-03-03 15:43:25 +08:00
co-authored by Claude Opus 4.6
parent ce736f197d
commit 6cc703ada2
136 changed files with 16805 additions and 520 deletions
+19
View File
@@ -0,0 +1,19 @@
{
"name": "@muse/shared",
"version": "0.1.0",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
},
"devDependencies": {
"typescript": "^5",
"vitest": "^4.0.18",
"@vitest/coverage-v8": "^4.0.18"
}
}
+8
View File
@@ -0,0 +1,8 @@
export type {
Platform,
ContentItem,
PlatformConfig,
PlatformAdapter,
} from "./types/content";
export { MVP_PLATFORMS, getPlatformConfig } from "./platforms";
+60
View File
@@ -0,0 +1,60 @@
import { describe, it, expect } from "vitest";
import { MVP_PLATFORMS, getPlatformConfig } from "./platforms";
describe("MVP_PLATFORMS", () => {
it("contains exactly 8 platforms", () => {
expect(MVP_PLATFORMS).toHaveLength(8);
});
it("includes all 8 platforms", () => {
const ids = MVP_PLATFORMS.map((p) => p.id);
expect(ids).toContain("douyin");
expect(ids).toContain("tiktok");
expect(ids).toContain("xiaohongshu");
expect(ids).toContain("youtube");
expect(ids).toContain("instagram");
expect(ids).toContain("twitter");
expect(ids).toContain("bilibili");
expect(ids).toContain("weibo");
});
it("all platforms are enabled by default", () => {
MVP_PLATFORMS.forEach((p) => {
expect(p.enabled).toBe(true);
});
});
it("each platform has required fields", () => {
MVP_PLATFORMS.forEach((p) => {
expect(p.id).toBeTruthy();
expect(p.name).toBeTruthy();
expect(p.icon).toBeTruthy();
expect(p.color).toBeTruthy();
expect(p.endpoints.trending).toBeTruthy();
expect(p.endpoints.detail).toBeTruthy();
});
});
});
describe("getPlatformConfig", () => {
it("returns config for known platform", () => {
const config = getPlatformConfig("douyin");
expect(config).toBeDefined();
expect(config!.id).toBe("douyin");
expect(config!.name).toBe("抖音");
});
it("returns undefined for unknown platform", () => {
expect(getPlatformConfig("unknown")).toBeUndefined();
});
it("returns correct config for each platform", () => {
expect(getPlatformConfig("tiktok")?.name).toBe("TikTok");
expect(getPlatformConfig("xiaohongshu")?.name).toBe("小红书");
expect(getPlatformConfig("youtube")?.name).toBe("YouTube");
expect(getPlatformConfig("instagram")?.name).toBe("Instagram");
expect(getPlatformConfig("twitter")?.name).toBe("Twitter/X");
expect(getPlatformConfig("bilibili")?.name).toBe("哔哩哔哩");
expect(getPlatformConfig("weibo")?.name).toBe("微博");
});
});
+96
View File
@@ -0,0 +1,96 @@
import type { PlatformConfig } from "./types/content";
export const MVP_PLATFORMS: PlatformConfig[] = [
{
id: "douyin",
name: "抖音",
icon: "📱",
color: "#000000",
enabled: true,
endpoints: {
trending: "/api/v1/douyin/web/fetch_hot_search_result",
detail: "/api/v1/douyin/web/fetch_one_video",
},
},
{
id: "tiktok",
name: "TikTok",
icon: "🎵",
color: "#00F2EA",
enabled: true,
endpoints: {
trending: "/api/v1/tiktok/web/fetch_trending_post",
detail: "/api/v1/tiktok/web/fetch_post_detail",
},
},
{
id: "xiaohongshu",
name: "小红书",
icon: "📕",
color: "#FF2442",
enabled: true,
endpoints: {
trending: "/api/v1/xiaohongshu/app/v2/fetch_feed",
detail: "/api/v1/xiaohongshu/app/v2/fetch_note_detail",
},
},
{
id: "youtube",
name: "YouTube",
icon: "▶️",
color: "#FF0000",
enabled: true,
endpoints: {
trending: "/api/v1/youtube/web/get_trending_videos",
detail: "/api/v1/youtube/web/get_video_info",
},
},
{
id: "instagram",
name: "Instagram",
icon: "📷",
color: "#E4405F",
enabled: true,
endpoints: {
trending: "/api/v1/instagram/v1/fetch_explore_sections",
detail: "/api/v1/instagram/v2/fetch_post_info",
},
},
{
id: "twitter",
name: "Twitter/X",
icon: "𝕏",
color: "#000000",
enabled: true,
endpoints: {
trending: "/api/v1/twitter/web/fetch_trending",
detail: "/api/v1/twitter/web/fetch_tweet_detail",
},
},
{
id: "bilibili",
name: "哔哩哔哩",
icon: "📺",
color: "#00A1D6",
enabled: true,
endpoints: {
trending: "/api/v1/bilibili/web/fetch_com_popular",
detail: "/api/v1/bilibili/web/fetch_video_detail",
},
},
{
id: "weibo",
name: "微博",
icon: "🔥",
color: "#FF8200",
enabled: true,
endpoints: {
trending: "/api/v1/weibo/app/fetch_hot_search",
detail: "/api/v1/weibo/app/fetch_status_detail",
},
},
];
export function getPlatformConfig(platformId: string): PlatformConfig | undefined {
return MVP_PLATFORMS.find((p) => p.id === platformId);
}
+44
View File
@@ -0,0 +1,44 @@
export type Platform =
| "douyin"
| "tiktok"
| "xiaohongshu"
| "youtube"
| "instagram"
| "twitter"
| "bilibili"
| "weibo";
export interface ContentItem {
id: string;
title: string;
cover_url?: string;
video_url?: string;
author_name: string;
author_avatar?: string;
play_count?: number;
like_count?: number;
collect_count?: number;
comment_count?: number;
share_count?: number;
publish_time: string;
platform: Platform;
original_url: string;
tags?: string[];
}
export interface PlatformConfig {
id: Platform;
name: string;
icon: string;
color: string;
enabled: boolean;
endpoints: {
trending: string;
detail: string;
};
}
export interface PlatformAdapter {
fetchTrending(count: number): Promise<ContentItem[]>;
fetchDetail(id: string): Promise<ContentItem>;
}
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2017",
"module": "esnext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noEmit": true,
"isolatedModules": true,
"resolveJsonModule": true
},
"include": ["src/**/*.ts"]
}
+20
View File
@@ -0,0 +1,20 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
include: ["src/**/*.test.ts"],
coverage: {
provider: "v8",
reporter: ["text", "text-summary", "lcov"],
include: ["src/**"],
exclude: ["src/**/*.test.*"],
thresholds: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
},
});