Files
UHD_Media_Tool/packages/adapters/src/sites/spider/map.ts
T
rootandClaude Opus 5.5 d9f2af2f3c feat(adapters): port MoviePilot open site framework (7 spiders, 20 user parsers) + MovieClaw v0.27.0 site definitions + mp-sync tools (W1-3c)
- sites/spiders: Haidan/HDDolby/RousiPro/SunnyPT/TNode/TorrentLeech/Yema (+mTorrent wrapper), specialized SiteAdapter, indirect download resolved at download time
- sites/user-parsers: SiteParserBase + all 20 MP SiteSchema parsers (lxml XPath via xpath+xmldom), refreshSiteUserData with schema fallback
- sites/definitions: zod SiteDefinition schemas, 23 MovieClaw v0.27.0 (MIT) definitions via scripts/mp-sync/movieclaw-import.mjs, registry getSiteDefinition/listSiteDefinitions, NOTICE
- scripts/mp-sync: ledger.json (upstream sha256), check.mjs, build-ledger.mjs; docs/porting/mp-sync-ledger.md
- license-and-provenance: add copied-mit kind, re-anchor MovieClaw rows to v0.27.0

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 18:39:27 +08:00

83 lines
3.2 KiB
TypeScript

import { redactUrl } from "../../rss/redact.ts";
import type { PromotionType, SitePromotion, SiteRelease } from "../types.ts";
import { parseSiteTime } from "../util.ts";
import type { IndexerDefinition } from "./schema.ts";
import type { SpiderTorrent } from "./spider.ts";
/**
* 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<string, unknown> = { ...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;
}