import { redactUrl } from "../../rss/redact"; import type { PromotionType, SitePromotion, SiteRelease } from "../types"; import { parseSiteTime } from "../util"; import type { IndexerDefinition } from "./schema"; import type { SpiderTorrent } from "./spider"; /** * MP 系数 → 统一促销类型。MP 只有 downloadvolumefactor/uploadvolumefactor/freedate 三个字段, * 类型名由系数组合推出;字段未配置(undefined)时为 unknown。 */ export function promotionFromFactors( dl: number | undefined, ul: number | undefined, freedate: string | null, ): SitePromotion { if (dl === undefined && ul === undefined) return { type: "unknown", downloadFactor: null, uploadFactor: null, expiresAt: null }; const d = dl ?? 1; const u = ul ?? 1; let type: PromotionType; if (d === 0) type = u >= 2 ? "2xfree" : "free"; else if (d === 0.5) type = u >= 2 ? "2x50%" : "50%"; else if (d === 0.3) type = "30%"; else if (d === 1 && u >= 2) type = "2x"; else if (d === 1 && u === 1) type = "none"; else type = "unknown"; return { type, downloadFactor: dl ?? null, uploadFactor: ul ?? null, expiresAt: type === "none" ? null : freedate }; } /** 从详情链接取站点资源 ID(NexusPHP `details.php?id=`)。 */ export function releaseIdFromUrl(url: string | undefined): string | null { if (!url) return null; return /[?&]id=(\d+)/.exec(url)?.[1] ?? null; } export function categoryName(def: IndexerDefinition, id: string | undefined): string | null { if (!id) return null; const named = def.umt.categoryNames?.[id]; if (named) return named; for (const k of ["movie", "tv", "music"] as const) { const hit = (def.category?.[k] ?? []).find((c) => String(c.id) === id); if (hit?.desc) return hit.desc; } return null; } /** SpiderTorrent(MP TorrentInfo 字段)→ SiteRelease。raw 保存完整 MP 字段(enclosure 脱敏)。 */ export function spiderTorrentToRelease( t: SpiderTorrent, def: IndexerDefinition, observedAt: string, ): SiteRelease | null { const id = releaseIdFromUrl(t.page_url) ?? releaseIdFromUrl(t.enclosure); if (!id || !t.title) return null; const tz = def.umt.timezone; const freedate = t.freedate ? parseSiteTime(t.freedate, tz) : null; const raw: Record = { ...t }; if (t.enclosure) raw.enclosure = redactUrl(t.enclosure); const release: SiteRelease = { siteId: def.id, siteReleaseId: id, title: t.title, category: { id: t.category_id ?? null, name: categoryName(def, t.category_id) }, sizeBytes: t.size ?? null, seeders: t.seeders ?? null, leechers: t.peers ?? null, snatched: t.grabs ?? null, promotion: promotionFromFactors(t.downloadvolumefactor, t.uploadvolumefactor, freedate), hr: t.hit_and_run === undefined ? "unknown" : t.hit_and_run, uploadedAt: parseSiteTime(t.pubdate, tz), detailUrl: t.page_url ?? new URL(`${def.umt.detailPath}?id=${id}`, def.domain).toString(), downloadUrl: t.enclosure ? redactUrl(t.enclosure) : new URL(`${def.umt.downloadPath}?id=${id}`, def.domain).toString(), tags: t.labels ?? [], raw, observedAt, }; if (t.description) release.subtitle = t.description; if (t.imdbid) release.imdbId = t.imdbid; return release; }