项目从单体结构重构为 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>
72 lines
1.8 KiB
TypeScript
72 lines
1.8 KiB
TypeScript
import { waitForSlot } from "./rate-limiter";
|
|
|
|
const TIKHUB_BASE_URL = "https://api.tikhub.io";
|
|
|
|
// Runtime API Key (set via POST /api/settings)
|
|
let runtimeApiKey: string | null = null;
|
|
|
|
export function setRuntimeApiKey(key: string) {
|
|
runtimeApiKey = key;
|
|
}
|
|
|
|
export function getApiKey(): string | null {
|
|
return runtimeApiKey || process.env.TIKHUB_API_KEY || null;
|
|
}
|
|
|
|
export async function tikhubFetch<T>(
|
|
endpoint: string,
|
|
params?: Record<string, string>,
|
|
method: "GET" | "POST" = "GET",
|
|
body?: Record<string, unknown>
|
|
): Promise<T> {
|
|
const apiKey = getApiKey();
|
|
if (!apiKey) {
|
|
throw new TikHubError(401, "API Key 未配置,请在设置页面配置 TikHub API Key");
|
|
}
|
|
|
|
await waitForSlot();
|
|
|
|
const url = new URL(endpoint, TIKHUB_BASE_URL);
|
|
if (params) {
|
|
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
|
|
}
|
|
|
|
const res = await fetch(url.toString(), {
|
|
method,
|
|
headers: {
|
|
Authorization: `Bearer ${apiKey}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
...(method === "POST" ? { body: JSON.stringify(body || {}) } : {}),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
if (res.status === 401) {
|
|
throw new TikHubError(401, "API Key 无效,请检查配置");
|
|
}
|
|
if (res.status === 429) {
|
|
throw new TikHubError(429, "请求过于频繁,请稍后重试");
|
|
}
|
|
throw new TikHubError(res.status, `TikHub API 错误: ${res.status}`);
|
|
}
|
|
|
|
const json = await res.json();
|
|
|
|
// TikHub wraps all responses in { code, data, ... } envelope
|
|
// Unwrap to return the inner data directly
|
|
if (json?.code !== undefined && json?.data !== undefined) {
|
|
return json.data as T;
|
|
}
|
|
return json as T;
|
|
}
|
|
|
|
export class TikHubError extends Error {
|
|
constructor(
|
|
public statusCode: number,
|
|
message: string
|
|
) {
|
|
super(message);
|
|
this.name = "TikHubError";
|
|
}
|
|
}
|