Files
bili-sync-ai/web/src/lib/api.ts

364 lines
12 KiB
TypeScript

import type {
ApiError,
ApiResponse,
ClearAndResetVideoResponse,
CollectionsResponse,
Config,
DashBoardResponse,
FavoritesResponse,
FullSyncVideoSourceRequest,
FullSyncVideoSourceResponse,
QrcodeGenerateResponse as GenerateQrcodeResponse,
InsertCollectionRequest,
InsertFavoriteRequest,
InsertSubmissionRequest,
Notifier,
QrcodePollResponse as PollQrcodeResponse,
ResetFilteredVideosResponse,
ResetFilteredVideoStatusRequest,
ResetVideoResponse,
ResetVideoStatusRequest,
SysInfo,
TaskStatus,
UpdateFilteredVideoStatusRequest,
UpdateFilteredVideoStatusResponse,
UpdateVideoSourceRequest,
UpdateVideoSourceResponse,
UpdateVideoStatusRequest,
UpdateVideoStatusResponse,
UppersResponse,
VideoResponse,
VideoSourcesDetailsResponse,
VideoSourcesResponse,
VideosRequest,
VideosResponse
} from './types';
import { wsManager } from './ws';
// API 基础配置
const API_BASE_URL = '/api';
// HTTP 客户端类
class ApiClient {
private baseURL: string;
private defaultHeaders: Record<string, string>;
constructor(baseURL: string = API_BASE_URL) {
this.baseURL = baseURL;
this.defaultHeaders = {
'Content-Type': 'application/json'
};
const token = localStorage.getItem('authToken');
if (token) {
this.defaultHeaders['Authorization'] = token;
}
}
// 设置认证 token
setAuthToken(token?: string) {
if (token) {
this.defaultHeaders['Authorization'] = token;
localStorage.setItem('authToken', token);
} else {
delete this.defaultHeaders['Authorization'];
localStorage.removeItem('authToken');
}
}
getAuthToken(): string | null {
return this.defaultHeaders['Authorization'] || localStorage.getItem('authToken');
}
// 清除认证 token
clearAuthToken() {
delete this.defaultHeaders['Authorization'];
localStorage.removeItem('authToken');
// 断开 WebSocket 连接,因为 token 已经无效
wsManager.disconnect();
}
// 通用请求方法
private async request<T>(
url: string,
method: string = 'GET',
body?: unknown,
params?: Record<string, unknown>
): Promise<ApiResponse<T>> {
// 构建完整的 URL
let fullUrl = `${this.baseURL}${url}`;
if (params) {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
searchParams.append(key, String(value));
}
});
const queryString = searchParams.toString();
if (queryString) {
fullUrl += `?${queryString}`;
}
}
const config: RequestInit = {
method,
headers: this.defaultHeaders
};
if (body && method !== 'GET') {
config.body = JSON.stringify(body);
}
try {
const response = await fetch(fullUrl, config);
if (!response.ok) {
const errorText = await response.text();
let errorMessage: string;
try {
const errorJson = JSON.parse(errorText);
errorMessage = errorJson.message || errorJson.error || '请求失败';
} catch {
errorMessage = errorText || `HTTP ${response.status}: ${response.statusText}`;
}
throw {
message: errorMessage,
status: response.status
} as ApiError;
}
return await response.json();
} catch (error) {
if (error && typeof error === 'object' && 'status' in error) {
throw error;
}
throw {
message: error instanceof Error ? error.message : '网络请求失败',
status: 0
} as ApiError;
}
}
// GET 请求
private async get<T>(url: string, params?: Record<string, unknown>): Promise<ApiResponse<T>> {
return this.request<T>(url, 'GET', undefined, params);
}
// POST 请求
private async post<T>(url: string, data?: unknown): Promise<ApiResponse<T>> {
return this.request<T>(url, 'POST', data);
}
// PUT 请求
private async put<T>(url: string, data?: unknown): Promise<ApiResponse<T>> {
return this.request<T>(url, 'PUT', data);
}
async getVideoSources(): Promise<ApiResponse<VideoSourcesResponse>> {
return this.get<VideoSourcesResponse>('/video-sources');
}
async getVideos(params?: VideosRequest): Promise<ApiResponse<VideosResponse>> {
return this.get<VideosResponse>('/videos', params as Record<string, unknown>);
}
async getVideo(id: number): Promise<ApiResponse<VideoResponse>> {
return this.get<VideoResponse>(`/videos/${id}`);
}
async resetVideoStatus(
id: number,
request: ResetVideoStatusRequest
): Promise<ApiResponse<ResetVideoResponse>> {
return this.post<ResetVideoResponse>(`/videos/${id}/reset-status`, request);
}
async clearAndResetVideoStatus(id: number): Promise<ApiResponse<ClearAndResetVideoResponse>> {
return this.post<ClearAndResetVideoResponse>(`/videos/${id}/clear-and-reset-status`);
}
async resetFilteredVideoStatus(
request: ResetFilteredVideoStatusRequest
): Promise<ApiResponse<ResetFilteredVideosResponse>> {
return this.post<ResetFilteredVideosResponse>('/videos/reset-status', request);
}
async updateVideoStatus(
id: number,
request: UpdateVideoStatusRequest
): Promise<ApiResponse<UpdateVideoStatusResponse>> {
return this.post<UpdateVideoStatusResponse>(`/videos/${id}/update-status`, request);
}
async updateFilteredVideoStatus(
request: UpdateFilteredVideoStatusRequest
): Promise<ApiResponse<UpdateFilteredVideoStatusResponse>> {
return this.post<UpdateFilteredVideoStatusResponse>('/videos/update-status', request);
}
async getCreatedFavorites(): Promise<ApiResponse<FavoritesResponse>> {
return this.get<FavoritesResponse>('/me/favorites');
}
async getFollowedCollections(
pageNum?: number,
pageSize?: number
): Promise<ApiResponse<CollectionsResponse>> {
const params = {
page_num: pageNum,
page_size: pageSize
};
return this.get<CollectionsResponse>('/me/collections', params as Record<string, unknown>);
}
async getFollowedUppers(
pageNum?: number,
pageSize?: number,
name?: string
): Promise<ApiResponse<UppersResponse>> {
const params = {
page_num: pageNum,
page_size: pageSize,
name: name
};
return this.get<UppersResponse>('/me/uppers', params as Record<string, unknown>);
}
async insertFavorite(request: InsertFavoriteRequest): Promise<ApiResponse<boolean>> {
return this.post<boolean>('/video-sources/favorites', request);
}
async insertCollection(request: InsertCollectionRequest): Promise<ApiResponse<boolean>> {
return this.post<boolean>('/video-sources/collections', request);
}
async insertSubmission(request: InsertSubmissionRequest): Promise<ApiResponse<boolean>> {
return this.post<boolean>('/video-sources/submissions', request);
}
async getVideoSourcesDetails(): Promise<ApiResponse<VideoSourcesDetailsResponse>> {
return this.get<VideoSourcesDetailsResponse>('/video-sources/details');
}
async updateVideoSource(
type: string,
id: number,
request: UpdateVideoSourceRequest
): Promise<ApiResponse<UpdateVideoSourceResponse>> {
return this.put<UpdateVideoSourceResponse>(`/video-sources/${type}/${id}`, request);
}
async removeVideoSource(type: string, id: number): Promise<ApiResponse<boolean>> {
return this.request<boolean>(`/video-sources/${type}/${id}`, 'DELETE');
}
async evaluateVideoSourceRules(type: string, id: number): Promise<ApiResponse<boolean>> {
return this.post<boolean>(`/video-sources/${type}/${id}/evaluate`, null);
}
async fullSyncVideoSource(
type: string,
id: number,
data: FullSyncVideoSourceRequest
): Promise<ApiResponse<FullSyncVideoSourceResponse>> {
return this.post<FullSyncVideoSourceResponse>(`/video-sources/${type}/${id}/full-sync`, data);
}
async getDefaultPath(type: string, name: string): Promise<ApiResponse<string>> {
return this.get<string>(`/video-sources/${type}/default-path`, { name });
}
async testNotifier(notifier: Notifier): Promise<ApiResponse<boolean>> {
return this.post<boolean>('/config/notifiers/ping', notifier);
}
async getConfig(): Promise<ApiResponse<Config>> {
return this.get<Config>('/config');
}
async updateConfig(config: Config): Promise<ApiResponse<Config>> {
return this.put<Config>('/config', config);
}
async getDashboard(): Promise<ApiResponse<DashBoardResponse>> {
return this.get<DashBoardResponse>('/dashboard');
}
async triggerDownloadTask(): Promise<ApiResponse<boolean>> {
return this.post<boolean>('/task/download');
}
async generateQrcode(): Promise<ApiResponse<GenerateQrcodeResponse>> {
return this.post<GenerateQrcodeResponse>('/login/qrcode/generate');
}
async pollQrcode(qrcodeKey: string): Promise<ApiResponse<PollQrcodeResponse>> {
return this.get<PollQrcodeResponse>('/login/qrcode/poll', { qrcode_key: qrcodeKey });
}
subscribeToLogs(onMessage: (data: string) => void) {
return wsManager.subscribeToLogs(onMessage);
}
subscribeToSysInfo(onMessage: (data: SysInfo) => void) {
return wsManager.subscribeToSysInfo(onMessage);
}
subscribeToTasks(onMessage: (data: TaskStatus) => void) {
return wsManager.subscribeToTasks(onMessage);
}
}
// 创建默认的 API 客户端实例
export const apiClient = new ApiClient();
// 导出 API 方法的便捷函数
const api = {
getVideoSources: () => apiClient.getVideoSources(),
getVideos: (params?: VideosRequest) => apiClient.getVideos(params),
getVideo: (id: number) => apiClient.getVideo(id),
resetVideoStatus: (id: number, request: ResetVideoStatusRequest) =>
apiClient.resetVideoStatus(id, request),
clearAndResetVideoStatus: (id: number) => apiClient.clearAndResetVideoStatus(id),
resetFilteredVideoStatus: (request: ResetFilteredVideoStatusRequest) =>
apiClient.resetFilteredVideoStatus(request),
updateVideoStatus: (id: number, request: UpdateVideoStatusRequest) =>
apiClient.updateVideoStatus(id, request),
updateFilteredVideoStatus: (request: UpdateFilteredVideoStatusRequest) =>
apiClient.updateFilteredVideoStatus(request),
getCreatedFavorites: () => apiClient.getCreatedFavorites(),
getFollowedCollections: (pageNum?: number, pageSize?: number) =>
apiClient.getFollowedCollections(pageNum, pageSize),
getFollowedUppers: (pageNum?: number, pageSize?: number, name?: string) =>
apiClient.getFollowedUppers(pageNum, pageSize, name),
insertFavorite: (request: InsertFavoriteRequest) => apiClient.insertFavorite(request),
insertCollection: (request: InsertCollectionRequest) => apiClient.insertCollection(request),
insertSubmission: (request: InsertSubmissionRequest) => apiClient.insertSubmission(request),
getVideoSourcesDetails: () => apiClient.getVideoSourcesDetails(),
updateVideoSource: (type: string, id: number, request: UpdateVideoSourceRequest) =>
apiClient.updateVideoSource(type, id, request),
removeVideoSource: (type: string, id: number) => apiClient.removeVideoSource(type, id),
evaluateVideoSourceRules: (type: string, id: number) =>
apiClient.evaluateVideoSourceRules(type, id),
fullSyncVideoSource: (type: string, id: number, data: { delete_local: boolean }) =>
apiClient.fullSyncVideoSource(type, id, data),
getDefaultPath: (type: string, name: string) => apiClient.getDefaultPath(type, name),
testNotifier: (notifier: Notifier) => apiClient.testNotifier(notifier),
getConfig: () => apiClient.getConfig(),
updateConfig: (config: Config) => apiClient.updateConfig(config),
getDashboard: () => apiClient.getDashboard(),
triggerDownloadTask: () => apiClient.triggerDownloadTask(),
generateQrcode: () => apiClient.generateQrcode(),
pollQrcode: (qrcodeKey: string) => apiClient.pollQrcode(qrcodeKey),
subscribeToSysInfo: (onMessage: (data: SysInfo) => void) =>
apiClient.subscribeToSysInfo(onMessage),
subscribeToLogs: (onMessage: (data: string) => void) => apiClient.subscribeToLogs(onMessage),
subscribeToTasks: (onMessage: (data: TaskStatus) => void) =>
apiClient.subscribeToTasks(onMessage),
setAuthToken: (token: string) => apiClient.setAuthToken(token),
getAuthToken: () => apiClient.getAuthToken(),
clearAuthToken: () => apiClient.clearAuthToken()
};
export default api;