- 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]>
83 lines
3.2 KiB
TypeScript
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;
|
|
}
|