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
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
* 移植自 MoviePilot(https://github.com/jxxghp/MoviePilot,GPLv3),本文件按 GPLv3 条款提供。
|
||||
* @mp-source app/modules/indexer/__init__.py @ e6820a8
|
||||
*
|
||||
* 站点用户信息解析器注册表与刷新入口。
|
||||
* - `IndexerModule.__init__` 里 `ModuleHelper.load('app.modules.indexer.parser', filter=有 schema)` → `USER_PARSER_CLASSES`
|
||||
* (按模块文件名字母序,与 pkgutil 遍历顺序一致;回退时按此顺序尝试)
|
||||
* - `IndexerModule.refresh_userdata` → `refreshSiteUserData`:按站点声明的 schema 取解析器;解析后 userid 为空且非公开站时,
|
||||
* 依次尝试其他 schema(兼容 schema 标注错误/变种站),其他 schema 的异常只记录不抛出。
|
||||
*
|
||||
* 与 MP 一致:声明 schema 解析器的异常原样抛出(由调用方记录),只有回退尝试的异常被吞掉。
|
||||
* 差异:结果不含凭据;`fetchMessages` 默认 false(MP 由全局 SITE_MESSAGE 控制)。
|
||||
*/
|
||||
import type { SiteParserBase, SiteParserClass } from "./base.ts";
|
||||
import { BitptSiteUserInfo } from "./bitpt.ts";
|
||||
import { extractDomain } from "./common.ts";
|
||||
import { DiscuzUserInfo } from "./discuz.ts";
|
||||
import { FileListSiteUserInfo } from "./file-list.ts";
|
||||
import { GazelleSiteUserInfo } from "./gazelle.ts";
|
||||
import { HDDolbySiteUserInfo } from "./hddolby.ts";
|
||||
import { IptSiteUserInfo } from "./ipt.ts";
|
||||
import { MTorrentSiteUserInfo } from "./mtorrent.ts";
|
||||
import { NexusAudiencesSiteUserInfo } from "./nexus-audiences.ts";
|
||||
import { NexusHhanclubSiteUserInfo } from "./nexus-hhanclub.ts";
|
||||
import { NexusPhpSiteUserInfo } from "./nexus-php.ts";
|
||||
import { NexusProjectSiteUserInfo } from "./nexus-project.ts";
|
||||
import { NexusRabbitSiteUserInfo } from "./nexus-rabbit.ts";
|
||||
import { RousiSiteUserInfo } from "./rousi.ts";
|
||||
import { SmallHorseSiteUserInfo } from "./small-horse.ts";
|
||||
import { SunnyPTSiteUserInfo } from "./sunnypt.ts";
|
||||
import { TNodeSiteUserInfo } from "./tnode.ts";
|
||||
import { TorrentLeechSiteUserInfo } from "./torrent-leech.ts";
|
||||
import type { SiteSchemaValue, SiteUserInfo, SiteUserParserOptions } from "./types.ts";
|
||||
import { Unit3dSiteUserInfo } from "./unit3d.ts";
|
||||
import { YemaSiteUserInfo } from "./yema.ts";
|
||||
import { ZhixingSiteUserInfo } from "./zhixing.ts";
|
||||
|
||||
/** 全部解析器(模块字母序:bitpt, discuz, file_list, gazelle, hddolby, ipt, mtorrent, nexus_*, rousi, …) */
|
||||
export const USER_PARSER_CLASSES: readonly SiteParserClass[] = [
|
||||
BitptSiteUserInfo,
|
||||
DiscuzUserInfo,
|
||||
FileListSiteUserInfo,
|
||||
GazelleSiteUserInfo,
|
||||
HDDolbySiteUserInfo,
|
||||
IptSiteUserInfo,
|
||||
MTorrentSiteUserInfo,
|
||||
NexusAudiencesSiteUserInfo,
|
||||
NexusHhanclubSiteUserInfo,
|
||||
NexusPhpSiteUserInfo,
|
||||
NexusProjectSiteUserInfo,
|
||||
NexusRabbitSiteUserInfo,
|
||||
RousiSiteUserInfo,
|
||||
SmallHorseSiteUserInfo,
|
||||
SunnyPTSiteUserInfo,
|
||||
TNodeSiteUserInfo,
|
||||
TorrentLeechSiteUserInfo,
|
||||
Unit3dSiteUserInfo,
|
||||
YemaSiteUserInfo,
|
||||
ZhixingSiteUserInfo,
|
||||
] as unknown as SiteParserClass[];
|
||||
|
||||
export function getUserParserClass(schema: string | null | undefined): SiteParserClass | undefined {
|
||||
if (!schema) return undefined;
|
||||
return USER_PARSER_CLASSES.find((c) => c.schema === schema);
|
||||
}
|
||||
|
||||
/** refresh_userdata 的站点入参(MP site dict 子集) */
|
||||
export interface SiteUserDataRequest extends Omit<SiteUserParserOptions, "siteName"> {
|
||||
name: string;
|
||||
/** 站点声明的解析 schema(SiteSchema 值) */
|
||||
schema?: string | null;
|
||||
/** 公开站:不报"未找到解析器",也不做 schema 回退 */
|
||||
public?: boolean;
|
||||
}
|
||||
|
||||
/** MP `refresh_userdata`:未找到解析器返回 null */
|
||||
export async function refreshSiteUserData(site: SiteUserDataRequest): Promise<SiteUserInfo | null> {
|
||||
const make = (schema: string | null | undefined): SiteParserBase | null => {
|
||||
const cls = getUserParserClass(schema);
|
||||
if (!cls) return null;
|
||||
const { name, schema: _s, public: _p, ...rest } = site;
|
||||
return new cls({ ...rest, siteName: name });
|
||||
};
|
||||
let obj = make(site.schema);
|
||||
if (!obj) return null;
|
||||
try {
|
||||
await obj.parse();
|
||||
if (!obj.userid && !site.public) {
|
||||
const tried = new Set<string>([String(site.schema)]);
|
||||
for (const cls of USER_PARSER_CLASSES) {
|
||||
const value = cls.schema as SiteSchemaValue | null;
|
||||
if (!value || tried.has(value)) continue;
|
||||
tried.add(value);
|
||||
const alt = make(value);
|
||||
if (!alt) continue;
|
||||
try {
|
||||
await alt.parse();
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
if (alt.userid) {
|
||||
obj = alt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const info = obj.toUserInfo();
|
||||
info.domain = extractDomain(site.url) || null;
|
||||
return info;
|
||||
} finally {
|
||||
obj.clear();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user