import type { ContentVariants, SearchFilters, SearchPage, SearchSort, ServiceStats, TorrentDetail, } from '@/types/api' interface SearchInput { query: string offset: number limit: number sort: SearchSort filters: SearchFilters } function optionalNumber(params: URLSearchParams, key: string, value: string, scale = 1) { if (value.trim() === '') return const parsed = Number(value) if (Number.isFinite(parsed) && parsed >= 0) params.set(key, String(Math.round(parsed * scale))) } function optionalDate(params: URLSearchParams, key: string, value: string) { if (!value) return const timestamp = new Date(value).getTime() if (Number.isFinite(timestamp)) { const endOfDay = key.endsWith('_before') ? 86_399 : 0 params.set(key, String(Math.floor(timestamp / 1000) + endOfDay)) } } async function request(path: string, signal?: AbortSignal): Promise { const response = await fetch(path, { headers: { Accept: 'application/json' }, signal }) if (!response.ok) { let message = `请求失败 (${response.status})` try { const body = (await response.json()) as { error?: string } if (body.error) message = body.error } catch { // 保留包含状态码的稳定错误信息 } throw new Error(message) } return response.json() as Promise } export function search(input: SearchInput, signal?: AbortSignal): Promise { const params = new URLSearchParams({ q: input.query.trim(), offset: String(input.offset), limit: String(input.limit), sort: input.sort, }) const { filters } = input optionalNumber(params, 'min_size', filters.minSizeMiB, 1024 * 1024) optionalNumber(params, 'max_size', filters.maxSizeMiB, 1024 * 1024) optionalNumber(params, 'min_files', filters.minFiles) optionalNumber(params, 'max_files', filters.maxFiles) optionalDate(params, 'first_seen_after', filters.firstSeenAfter) optionalDate(params, 'first_seen_before', filters.firstSeenBefore) optionalDate(params, 'last_seen_after', filters.lastSeenAfter) optionalDate(params, 'last_seen_before', filters.lastSeenBefore) if (filters.extension.trim()) params.set('extension', filters.extension.trim().replace(/^\./, '')) if (filters.availability) params.set('availability', filters.availability) if (filters.heat) params.set('heat', filters.heat) return request(`/search?${params}`, signal) } export function getTorrent(infoHash: string, signal?: AbortSignal): Promise { return request(`/torrents/${encodeURIComponent(infoHash)}`, signal) } export function getVariants(contentKey: string, signal?: AbortSignal): Promise { return request(`/contents/${encodeURIComponent(contentKey)}?limit=100`, signal) } export function getStats(signal?: AbortSignal): Promise { return request('/stats', signal) }