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; 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( url: string, method: string = 'GET', body?: unknown, params?: Record ): Promise> { // 构建完整的 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(url: string, params?: Record): Promise> { return this.request(url, 'GET', undefined, params); } // POST 请求 private async post(url: string, data?: unknown): Promise> { return this.request(url, 'POST', data); } // PUT 请求 private async put(url: string, data?: unknown): Promise> { return this.request(url, 'PUT', data); } async getVideoSources(): Promise> { return this.get('/video-sources'); } async getVideos(params?: VideosRequest): Promise> { return this.get('/videos', params as Record); } async getVideo(id: number): Promise> { return this.get(`/videos/${id}`); } async resetVideoStatus( id: number, request: ResetVideoStatusRequest ): Promise> { return this.post(`/videos/${id}/reset-status`, request); } async clearAndResetVideoStatus(id: number): Promise> { return this.post(`/videos/${id}/clear-and-reset-status`); } async resetFilteredVideoStatus( request: ResetFilteredVideoStatusRequest ): Promise> { return this.post('/videos/reset-status', request); } async updateVideoStatus( id: number, request: UpdateVideoStatusRequest ): Promise> { return this.post(`/videos/${id}/update-status`, request); } async updateFilteredVideoStatus( request: UpdateFilteredVideoStatusRequest ): Promise> { return this.post('/videos/update-status', request); } async getCreatedFavorites(): Promise> { return this.get('/me/favorites'); } async getFollowedCollections( pageNum?: number, pageSize?: number ): Promise> { const params = { page_num: pageNum, page_size: pageSize }; return this.get('/me/collections', params as Record); } async getFollowedUppers( pageNum?: number, pageSize?: number, name?: string ): Promise> { const params = { page_num: pageNum, page_size: pageSize, name: name }; return this.get('/me/uppers', params as Record); } async insertFavorite(request: InsertFavoriteRequest): Promise> { return this.post('/video-sources/favorites', request); } async insertCollection(request: InsertCollectionRequest): Promise> { return this.post('/video-sources/collections', request); } async insertSubmission(request: InsertSubmissionRequest): Promise> { return this.post('/video-sources/submissions', request); } async getVideoSourcesDetails(): Promise> { return this.get('/video-sources/details'); } async updateVideoSource( type: string, id: number, request: UpdateVideoSourceRequest ): Promise> { return this.put(`/video-sources/${type}/${id}`, request); } async removeVideoSource(type: string, id: number): Promise> { return this.request(`/video-sources/${type}/${id}`, 'DELETE'); } async evaluateVideoSourceRules(type: string, id: number): Promise> { return this.post(`/video-sources/${type}/${id}/evaluate`, null); } async fullSyncVideoSource( type: string, id: number, data: FullSyncVideoSourceRequest ): Promise> { return this.post(`/video-sources/${type}/${id}/full-sync`, data); } async getDefaultPath(type: string, name: string): Promise> { return this.get(`/video-sources/${type}/default-path`, { name }); } async testNotifier(notifier: Notifier): Promise> { return this.post('/config/notifiers/ping', notifier); } async getConfig(): Promise> { return this.get('/config'); } async updateConfig(config: Config): Promise> { return this.put('/config', config); } async getDashboard(): Promise> { return this.get('/dashboard'); } async triggerDownloadTask(): Promise> { return this.post('/task/download'); } async generateQrcode(): Promise> { return this.post('/login/qrcode/generate'); } async pollQrcode(qrcodeKey: string): Promise> { return this.get('/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;