// 迁移来源:MoviePilot app/modules/qbittorrent/qbittorrent.py get_completed_torrents(Q6)与 __init__.py __normalize_torrent_state(Q23),e6820a8。 /** * qB 原始 state → 观测态。与 MoviePilot 不同:未识别/error/missingFiles 绝不兜底为完成 * (见 docs/porting/moviepilot-downloader-and-sites.md §1.4、QA5)。 */ export type QbObservedState = | "downloading" | "stalled" | "paused" | "completed_paused" | "seeding" | "checking" | "moving" | "error" | "missing_files" | "queued" | "metadata" | "unknown"; const MAP: Record = { downloading: "downloading", forceddl: "downloading", allocating: "downloading", checkingresumedata: "checking", checkingdl: "checking", checkingup: "checking", metadl: "metadata", forcedmetadl: "metadata", queueddl: "queued", stalleddl: "stalled", pauseddl: "paused", stoppeddl: "paused", pausedup: "completed_paused", stoppedup: "completed_paused", uploading: "seeding", stalledup: "seeding", forcedup: "seeding", queuedup: "seeding", moving: "moving", error: "error", missingfiles: "missing_files", }; export function mapQbState(raw: string | null | undefined): QbObservedState { if (!raw) return "unknown"; return MAP[raw.toLowerCase()] ?? "unknown"; } const NOT_DONE = new Set([ "allocating", "checkingdl", "checkingup", "checkingresumedata", "downloading", "error", "forceddl", "missingfiles", "metadl", "forcedmetadl", "moving", "queueddl", "stalleddl", "unknown", ]); /** 严格完成判定(QK4):progress>=1 && amountLeft<=0 && state 属于已知终态。 */ export function isDownloadComplete(t: { progress: number; amountLeft: number; state: string }): boolean { const s = t.state.toLowerCase(); if (!(s in MAP) || NOT_DONE.has(s)) return false; return t.progress >= 1 && t.amountLeft <= 0; }