Files
dht/web/src/lib/api.ts
T

81 lines
2.9 KiB
TypeScript

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<T>(path: string, signal?: AbortSignal): Promise<T> {
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<T>
}
export function search(input: SearchInput, signal?: AbortSignal): Promise<SearchPage> {
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<SearchPage>(`/search?${params}`, signal)
}
export function getTorrent(infoHash: string, signal?: AbortSignal): Promise<TorrentDetail> {
return request<TorrentDetail>(`/torrents/${encodeURIComponent(infoHash)}`, signal)
}
export function getVariants(contentKey: string, signal?: AbortSignal): Promise<ContentVariants> {
return request<ContentVariants>(`/contents/${encodeURIComponent(contentKey)}?limit=100`, signal)
}
export function getStats(signal?: AbortSignal): Promise<ServiceStats> {
return request<ServiceStats>('/stats', signal)
}