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]>
This commit is contained in:
1 parent
cf85481d95
commit
d9f2af2f3c
166 files changed
+16577
-186
No files matched your search
@@ -4,11 +4,12 @@
|
||||
* 促销/H&R 复用站点定义中 downloadvolumefactor / uploadvolumefactor / freedate / hr 字段(与列表同语义)。
|
||||
*/
|
||||
import type { CheerioAPI } from "cheerio";
|
||||
import type { SiteReleaseDetail } from "../types";
|
||||
import { extractDoubanId, extractImdbId, parseCount, parseSiteTime } from "../util";
|
||||
import { categoryName, promotionFromFactors } from "./map";
|
||||
import type { IndexerDefinition } from "./schema";
|
||||
import { loadHtml, parseSizeMp, SiteSpider } from "./spider";
|
||||
import type { SiteReleaseDetail } from "../types.ts";
|
||||
import { extractDoubanId, extractImdbId, parseCount, parseSiteTime } from "../util.ts";
|
||||
import { categoryName, promotionFromFactors } from "./map.ts";
|
||||
import { mcSelect } from "./mc-selector.ts";
|
||||
import type { IndexerDefinition } from "./schema.ts";
|
||||
import { loadHtml, parseSizeMp, SiteSpider } from "./spider.ts";
|
||||
|
||||
export type DetailPageKind = "ok" | "login" | "not_found" | "unknown";
|
||||
|
||||
@@ -38,15 +39,23 @@ export function parseDetailPage(
|
||||
return found as ReturnType<CheerioAPI> | null;
|
||||
};
|
||||
|
||||
const title = h1
|
||||
.contents()
|
||||
.filter((_, n) => n.type === "text")
|
||||
.text()
|
||||
// 逐字段选择器(MovieClaw detail_*_css)优先于 rowhead 表格模型
|
||||
const f = conf.fields ?? {};
|
||||
const title = (
|
||||
f.title
|
||||
? (mcSelect($, null, f.title) ?? "")
|
||||
: h1
|
||||
.contents()
|
||||
.filter((_, n) => n.type === "text")
|
||||
.text()
|
||||
)
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
const subtitle = row(conf.labels.subtitle)?.text().trim();
|
||||
const subtitle = f.subtitle ? (mcSelect($, null, f.subtitle) ?? undefined) : row(conf.labels.subtitle)?.text().trim();
|
||||
const basicText = row(conf.labels.basicInfo)?.text().replace(/\s+/g, " ") ?? "";
|
||||
const sizeText = /(?:大小|Size)\s*[::]\s*([\d.,]+\s*[KMGTP]?i?B)/i.exec(basicText)?.[1] ?? null;
|
||||
const sizeText = f.size
|
||||
? (/([\d.,]+\s*[KMGTP]?i?B)/i.exec(mcSelect($, null, f.size) ?? "")?.[1] ?? null)
|
||||
: (/(?:大小|Size)\s*[::]\s*([\d.,]+\s*[KMGTP]?i?B)/i.exec(basicText)?.[1] ?? null);
|
||||
const catText = /(?:类型|Type)\s*[::]\s*(\S+)/i.exec(basicText)?.[1] ?? null;
|
||||
const catId = catText
|
||||
? (Object.entries(def.umt.categoryNames ?? {}).find(([, name]) => name === catText)?.[0] ?? null)
|
||||
@@ -102,9 +111,9 @@ export function parseDetailPage(
|
||||
};
|
||||
if (subtitle) detail.subtitle = subtitle;
|
||||
if (descText) detail.descriptionText = descText.slice(0, 20_000);
|
||||
const imdb = extractImdbId(descHtml) ?? extractImdbId(descText);
|
||||
const imdb = extractImdbId(mcSelect($, null, f.imdb)) ?? extractImdbId(descHtml) ?? extractImdbId(descText);
|
||||
if (imdb) detail.imdbId = imdb;
|
||||
const douban = extractDoubanId(descHtml);
|
||||
const douban = extractDoubanId(mcSelect($, null, f.douban)) ?? extractDoubanId(descHtml);
|
||||
if (douban) detail.doubanId = douban;
|
||||
return { kind: "ok", detail };
|
||||
}
|
||||
Reference in new issue
Block a user