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
@@ -9,12 +9,12 @@
|
||||
* - `app/modules/indexer/parser/mtorrent.py`(api/member/profile)→ `healthCheck`
|
||||
* 详情 `/api/torrent/detail` 为本项目扩展(MP spider 无详情调用,字段结构参考 MovieClaw mteam.py,data-ref)。
|
||||
*/
|
||||
import { redactUrl } from "../../rss/redact";
|
||||
import { buildHttpContext, type SiteAdapterDeps } from "../deps";
|
||||
import { readBody, siteFetch } from "../http";
|
||||
import { healthFromError } from "../spider/adapter";
|
||||
import { promotionFromFactors } from "../spider/map";
|
||||
import type { SpiderTorrent } from "../spider/spider";
|
||||
import { redactUrl } from "../../rss/redact.ts";
|
||||
import { buildHttpContext, type SiteAdapterDeps } from "../deps.ts";
|
||||
import { readBody, siteFetch } from "../http.ts";
|
||||
import { healthFromError } from "../spider/adapter.ts";
|
||||
import { promotionFromFactors } from "../spider/map.ts";
|
||||
import type { SpiderTorrent } from "../spider/spider.ts";
|
||||
import {
|
||||
type DownloadTarget,
|
||||
type SiteAdapter,
|
||||
@@ -24,11 +24,11 @@ import {
|
||||
type SitePage,
|
||||
type SiteRelease,
|
||||
type SiteReleaseDetail,
|
||||
} from "../types";
|
||||
import { extractDoubanId, inspectTorrent, parseSiteTime } from "../util";
|
||||
import { mteamPost } from "./client";
|
||||
import { type MteamDefinition, mteamApiBase } from "./definition";
|
||||
import { type MteamTorrentJson, mteamSearchParams, parseMteamResult } from "./spider";
|
||||
} from "../types.ts";
|
||||
import { extractDoubanId, inspectTorrent, parseSiteTime } from "../util.ts";
|
||||
import { mteamPost } from "./client.ts";
|
||||
import { type MteamDefinition, mteamApiBase } from "./definition.ts";
|
||||
import { type MteamTorrentJson, mteamSearchParams, parseMteamResult } from "./spider.ts";
|
||||
|
||||
interface MteamSearchData {
|
||||
data?: MteamTorrentJson[] | null;
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
* M-Team API 调用。对应 MP `MTorrentSpider.search/__process_response`(MP 只看 HTTP 200);
|
||||
* 业务 code/message 检查与凭据/解析错误分层参考 MovieClaw `_post_api`(behavior-ref,迁移账本 MT2)。
|
||||
*/
|
||||
import { redactText } from "../../rss/redact";
|
||||
import { readText, type SiteHttpContext, siteFetch } from "../http";
|
||||
import { SiteError } from "../types";
|
||||
import { redactText } from "../../rss/redact.ts";
|
||||
import { readText, type SiteHttpContext, siteFetch } from "../http.ts";
|
||||
import { SiteError } from "../types.ts";
|
||||
|
||||
export type MteamApiPath =
|
||||
| "/api/torrent/search"
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { SiteCapabilities } from "../types";
|
||||
import mteamJson from "../definitions/mteam.json" with { type: "json" };
|
||||
import { parseMteamDefinition } from "../definitions/schema.ts";
|
||||
import type { DefinitionProvenance } from "../spider/schema.ts";
|
||||
import type { SiteCapabilities } from "../types.ts";
|
||||
|
||||
/**
|
||||
* 馒头站点定义:键名沿用 MP indexer dict(id/name/domain/parser/timeout),
|
||||
@@ -7,6 +10,10 @@ import type { SiteCapabilities } from "../types";
|
||||
export interface MteamDefinition {
|
||||
id: string;
|
||||
name: string;
|
||||
enabled?: boolean;
|
||||
/** MP 用户信息解析器 schema(MTorrent) */
|
||||
schema?: string;
|
||||
provenance?: DefinitionProvenance;
|
||||
/** 网页域(MP `_url`,详情页 = `${domain}detail/${id}`) */
|
||||
domain: string;
|
||||
parser: "mTorrent";
|
||||
@@ -22,41 +29,16 @@ export interface MteamDefinition {
|
||||
capabilities: SiteCapabilities;
|
||||
/** 原始分类 ID → 名称(自行编制,待核验) */
|
||||
categoryNames: Record<string, string>;
|
||||
authSupported?: string[];
|
||||
customClass?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const mteamDefinition: MteamDefinition = {
|
||||
id: "mteam",
|
||||
name: "馒头",
|
||||
domain: "https://tp.m-team.cc/",
|
||||
parser: "mTorrent",
|
||||
timeout: 15,
|
||||
umt: {
|
||||
version: "[email protected]",
|
||||
kind: "mteam",
|
||||
displayName: "馒头",
|
||||
timezone: "+08:00",
|
||||
rateLimit: { minIntervalMs: 1000, burst: 1 },
|
||||
capabilities: { list: true, search: true, detail: true, download: true, rss: false },
|
||||
categoryNames: {
|
||||
"401": "电影",
|
||||
"419": "电影",
|
||||
"420": "电影",
|
||||
"421": "电影",
|
||||
"439": "电影",
|
||||
"402": "剧集",
|
||||
"403": "综艺",
|
||||
"435": "剧集",
|
||||
"438": "剧集",
|
||||
"404": "纪录",
|
||||
"405": "动画",
|
||||
"407": "体育",
|
||||
"406": "音乐",
|
||||
"408": "音乐",
|
||||
"434": "音乐",
|
||||
},
|
||||
},
|
||||
};
|
||||
/**
|
||||
* 馒头定义:definitions/mteam.json(由 scripts/mp-sync/movieclaw-import.mjs 从 MovieClaw v0.27.0 mteam.yaml 转换,
|
||||
* W1-3b 原值经 movieclaw-overrides/mteam.json 覆盖),加载时 zod 校验。
|
||||
*/
|
||||
export const mteamDefinition: MteamDefinition = parseMteamDefinition(mteamJson);
|
||||
|
||||
/** MP `site_rules.extract_domain` 的简化:取主机名最后两级。 */
|
||||
export function rootDomain(url: string): string {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
SiteError,
|
||||
type SiteRateLimiter,
|
||||
staticSecretResolver,
|
||||
} from "../index";
|
||||
} from "../index.ts";
|
||||
|
||||
let api: MockMteamApi | undefined;
|
||||
afterEach(async () => {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* 2. 额外输出 `category_id`(原始分类 ID),`category` 仍是 MP 的媒体类型投影。
|
||||
* 3. 额外保留 `status` 原文到 raw,便于核验。
|
||||
*/
|
||||
import type { MediaKind, MpMediaType, SpiderTorrent } from "../spider/spider";
|
||||
import type { MediaKind, MpMediaType, SpiderTorrent } from "../spider/spider.ts";
|
||||
|
||||
export const MOVIE_CATEGORY = ["401", "419", "420", "421", "439", "405", "404"];
|
||||
export const TV_CATEGORY = ["403", "402", "435", "438", "404", "405"];
|
||||
@@ -87,10 +87,18 @@ export function getUploadVolumeFactor(discount: string | null | undefined): numb
|
||||
return discount ? (map[discount] ?? 1) : 1;
|
||||
}
|
||||
|
||||
/** MP `time_tools.format_timestamp`:非数字字符串原样返回,Unix 秒转本地时间(此处按站点时区交给调用方)。 */
|
||||
function formatTimestamp(v: string | null | undefined): string | undefined {
|
||||
/**
|
||||
* MP `time_tools.format_timestamp`:非纯数字字符串原样返回;Unix 秒 → 本地墙钟 "YYYY-MM-DD HH:MM:SS"。
|
||||
* MP 用进程本地时区(容器默认 Asia/Shanghai);这里固定 +08:00,与 adapter 按 `umt.timezone`(+08:00)转 UTC 配套。
|
||||
*/
|
||||
function formatTimestamp(v: string | number | null | undefined): string | undefined {
|
||||
if (v == null) return undefined;
|
||||
return String(v);
|
||||
if (typeof v === "string" && !/^\d+$/.test(v)) return v;
|
||||
const n = Number(v);
|
||||
if (!Number.isFinite(n)) return String(v);
|
||||
const d = new Date(Math.trunc(n) * 1000 + 8 * 3600_000);
|
||||
const p = (x: number) => String(x).padStart(2, "0");
|
||||
return `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())} ${p(d.getUTCHours())}:${p(d.getUTCMinutes())}:${p(d.getUTCSeconds())}`;
|
||||
}
|
||||
|
||||
function toInt(v: unknown): number {
|
||||
|
||||
Reference in new issue
Block a user