loginNexusPhp (captcha / two-factor / bad credentials / blocked / Cloudflare classification, TOTP), SiteUserInfo.vip via profile.vip_css or umt.vipLevels, maxSeedingPages option, MTorrent parser honours apiUrl; testkit mock NexusPHP site gains takelogin with login guards and user detail pages. Co-Authored-By: Claude Opus 5.5 <[email protected]>
adapters/sites
PT 站点适配器,按 MoviePilot v3(提交 e6820a8)迁移:通用配置驱动 spider(观众等 NexusPHP 站)与馒头 M-Team API。仅对 @umt/testkit 的 mock 站点验证,未连接真实站点;选择器与 API 字段均待真实账号核验。
模块与上游对照
| 本项目 | MoviePilot 上游 | 说明 |
|---|---|---|
spider/schema.ts IndexerDefinition |
SiteSpider.__init__ 读取的 indexer dict 键 + app/application/site/sites.pyi |
站点定义 schema(domain / search.paths / params / batch / browse / category / torrents.list / torrents.fields DSL);umt 为本项目扩展 |
spider/spider.ts SiteSpider |
app/modules/indexer/spider/__init__.py SiteSpider |
getSearchUrl(=__get_search_url)、parse/getInfo(=parse/get_info 全部字段)、filterText/safeQuery、isLoginOrPermissionPage、selectMediaCategories、resolveCategoryMediaType、parseSizeMp(=foundation/size.parse_size) |
spider/template.ts |
jinja2.Template | nunjucks(Jinja 兼容)渲染 title/description/date 模板 |
spider/adapter.ts |
IndexerModule.search_torrents/refresh_torrents/__search_check/__clear_search_text、submission.download_torrent |
请求、Cookie 注入、登录失效识别、下载校验 |
spider/detail.ts |
—(MP 通用 spider 无详情页) | 本项目扩展,选择器在定义 umt.detail |
definitions/audiences.json |
MP 站点定义在加密 user.sites.v3.bin,不可读 |
按 MP schema 手写;字段结构参考 MovieClaw audiences.yaml(data-ref,未复制);加站只需新增 JSON 并在 registry.ts 登记 |
mteam/spider.ts |
app/modules/indexer/spider/mtorrent.py MTorrentSpider |
mteamSearchParams(=__get_params)、parseMteamResult(=__parse_result)、系数表、标签表、分类组 |
mteam/adapter.ts |
MTorrentSpider.search、__get_download_url + submission._resolve_indirect_download_url |
genDlToken(id 走 query)→ 临时直链 → 不带 Cookie GET |
mteam/client.ts |
MTorrentSpider.__process_response |
另加业务 code/message 检查(MP 只看 HTTP 200) |
rate-limit.ts |
SitesHelper.check(domain)(闭源流控) |
站点级令牌桶,list/search/detail/download 共享 |
与 MP 的偏离(只限安全、数据保真与去重)
- 原始分类:spider/馒头结果保留 MP 的
category(电影/电视剧/音乐/未知 投影),另加category_id原值;SiteRelease.category用原值 + 站点分类名(REQ §4.5,观众 403 综艺、馒头 404/405/407 不压成 tv)。 - 凭据:MP 候选携带
site_cookie,馒头 enclosure 为含x-api-key的 base64 JSON;本项目凭据只经SecretResolver在请求时注入,候选、raw、错误消息均不含 cookie/apikey/passkey/一次性直链(迁移账本 SA5/MT3)。 - 登录失效:MP 返回空列表并置
is_error;本项目抛SiteError('auth_expired'),不把登录页当成 0 条正常结果(REQ §4.7.1)。3xx 不自动跟随,跳 login 视为失效。 - 系数 selector 模式用浮点(MP
int()对 0.5 抛异常丢整条,SA4)。 - H&R 三态:定义里没有
hr字段时 MP 缺省 False;本项目映射为'unknown'(REQ §8.3 保守处理)。spider 层仍与 MP 一致:未配置不输出hit_and_run。 - HTML 解析用 htmlparser2 HTML 模式(与 lxml 一样不插入
<tbody>);:has("x")引号写法自动去引号(cheerio-select 不接受)。未移植 MP 的 Rust 加速与字幕搜索分支。 - 馒头:显式
categoryIds覆盖 mtype 分类组;freeOnly为本地过滤;业务 message 判定auth_invalid / rate_limited / not_found。
接口
SiteAdapter{ id, kind, displayName, definitionVersion, capabilities, list(page, filters), search(query, filters), detail(id), downloadTarget(id), healthCheck() },页码对外 1 起(MP 内部 0 起)。filters = { mtype?: 'movie'|'tv'|'music', categoryIds?, freeOnly?, page? }。
SiteRelease{ siteId, siteReleaseId, title, subtitle?, category{id,name}, sizeBytes, seeders, leechers, snatched, promotion{type, downloadFactor, uploadFactor, expiresAt}, hr, uploadedAt, detailUrl, downloadUrl?, infoHash?, imdbId?, doubanId?, tags[], raw(完整 MP 字段), observedAt }。促销类型由 MP 系数组合推出(0/1 free、0/2 2xfree、0.5/1 50%、0.5/2 2x50%、0.3 30%、1/2 2x、1/1 none),系数字段未配置为 unknown;expiresAt 为 MP freedate 按 umt.timezone 转 UTC。
错误 SiteError{kind}:auth_missing / auth_expired / auth_invalid(isAuthError,停止重试)、rate_limited(retryAfterMs,已顺延限流器)、parse_error、network、site_maintenance、not_found、unsupported。
观众定义待真实页面核验
行选择器、td:nth-child(4..8) 列号、span.torrent-subtitle-line、?cat= 分类、img.pro_* 促销 case 表、freedate 取促销图标 title、search_area=4 是否支持 IMDb、浏览 0 起分页、H&R 选择器(未配置 → unknown)、span.tags 标签、详情页 h1#top/td.rowhead/#kdescr。
馒头字段待真实账号核验
- 搜索体只发 MP 的
{keyword, categories, pageNumber, pageSize, visible}(无mode);浏览时keyword: null是否被接受。 - 数值字段为字符串还是数字(两者兼容);
totalPages字段名。 status.discount全集(MP 表:FREE/PERCENT_50/PERCENT_70/_2X_FREE/_2X_PERCENT_50/_2X,其余按 1)、discountEndTime/createdDate时区(按 +08:00;MPformat_timestamp对非数字字符串原样返回)。promotionRule{discount,endTime}与mallSingleFree{status,endDate}是否出现在 search 结果中。labelsNew/labels(MP 表 "0"–"7")。/api/torrent/detail(表单 id,MovieClaw 字段结构)是否可用、是否含descr。genDlToken(id 走 query,同 MP)直链有效期、次数与频率;返回空data的原因。- 错误 message 文案(停用/封禁/key 无效/频繁/不存在)与 HTTP 状态码。
categoryNames为自行编制的粗分类名;默认网页域tp.m-team.cc需按账号实际域名配置。- H&R:API 未提供,恒为
unknown。
W1-3c:MP 开源站点框架全量移植 + MovieClaw 站点定义
跟随更新工具与对照总表见 docs/porting/mp-sync-ledger.md(scripts/mp-sync/)。每个移植文件头有 SPDX-License-Identifier: GPL-3.0-only、@mp-source <上游路径> @ e6820a8 和函数对照。
专用 spider(spiders/,MP app/modules/indexer/spider/*.py)
parser 键(MP SPIDER_PARSER_CLASSES) |
文件 | 凭据 | 下载 |
|---|---|---|---|
Haidan |
spiders/haidan.ts |
cookie | 列表直链(仅内存),raw 脱敏 |
HDDolby |
spiders/hddolby.ts |
apiKey(+cookie) | download.php?id=&downhash=(仅内存) |
RousiPro |
spiders/rousi.ts |
apiKey(Bearer) | 现场请求详情接口取 data.download_url |
SunnyPT |
spiders/sunnypt.ts |
apiKey | 现场 download-token 换直链 |
TNodeSpider |
spiders/tnode.ts |
cookie + 首页 CSRF(实例内缓存 24h) | 直链 |
TorrentLeech |
spiders/torrentleech.ts |
cookie | 直链;中文关键词按 MP 返回空 |
Yema |
spiders/yema.ts |
apiKey(Authorization) | 现场 generateDownloadKey 换 token |
mTorrent |
spiders/mtorrent.ts(包装 mteam/*) |
apiKey | genDlToken |
createSpecializedAdapterFor(def, deps) 实现统一 SiteAdapter(list = 空关键词搜索;detail 为 unsupported);定义 umt.kind = "api",schema 见 definitions/schema.ts SpecializedSiteDefinitionSchema。MP 的 [base64(含 apikey 的请求配置)]url 两段式 enclosure 不生成,下载时按同一配置现场换直链并校验 .torrent。mteam/spider.ts 已与 mtorrent.py 逐条核对(修正:纯数字时间戳按 +08:00 转换)。
站点用户信息解析器(user-parsers/,MP app/modules/indexer/parser/*.py)
20 个 SiteSchema 全部移植:NexusPhp、NexusAudiences、NexusHhanclub、NexusProject、NexusRabbit、Gazelle、Unit3d、DiscuzX、IPTorrents、FileList、Small Horse、Zhixing、Bitpt、TNode、TorrentLeech、HDDolby、RousiPro、SunnyPT、Yema、MTorrent。
- 基类
SiteParserBase(base.ts)与 MP 相同的获取顺序:首页登录判定 →_parseSitePage→ 基础信息 → 详情 → 未读消息(fetchMessages)→ 流量 → 做种分页(上限 50 页);cookie/apikey 两种请求模式;200/500/403 视为有内容;Cloudflare 挑战页返回空。 - XPath 用
xpath+@xmldom/xmldom(dom.ts,htmlparser2 解析,不插入 tbody),表达式与 MP 逐字一致。 - 入口
refreshSiteUserData({name, url, schema, cookie|apikey|token, fetch?, limiter?})→SiteUserInfo{schema, domain, userId, username, userLevel, joinAt, upload, download, ratio, bonus, seeding, seedingSize, leeching, leechingSize, seedingInfo, messageUnread, messageUnreadContents, hrCount?, errMsg, raw};声明 schema 取不到 userid 时按 MP 顺序回退到其他 schema。 - 写操作:MTorrent / Rousi / NexusRabbit 读消息后标记已读,只在
fetchMessages: true时发生。
站点定义体系(definitions/)
- 23 个站点 JSON 由
scripts/mp-sync/movieclaw-import.mjs从 MovieClaw v0.27.0(c79bb1b0,MIT) 转换(见definitions/NOTICE-MovieClaw.md),每个带provenance;手工覆盖层scripts/mp-sync/movieclaw-overrides/*.json(观众、馒头沿用 W1-3b 值,HHClub/CHD 补分类名)。不要直接改 JSON,改覆盖层后重跑脚本。 - 加载时 zod 校验(
definitions/schema.ts:IndexerDefinitionSchema/MteamDefinitionSchema/SpecializedSiteDefinitionSchema,按umt.kind分派);注册表getSiteDefinition(id)、listSiteDefinitions({enabledOnly})、createSiteAdapter(def, deps)。 - 新增字段:
enabled、schema(MP 用户信息解析器)、provenance、umt.detail.fields(MovieClaw 逐字段详情选择器,支持 HHClub div 详情页)、umt.profile(MovieClaw 资料选择器,备用)、umt.login、umt.authSupported、umt.customClass。选择器扩展语法::attr(x)/::text/::next_sibling_text由spider/mc-selector.ts求值。
| 状态 | 站点 |
|---|---|
| 启用(主力,仍须真实账号核验) | audiences 观众、hhanclub 憨憨、chdbits 彩虹岛、mteam 馒头 |
enabled:false,未实测 |
agsvpt、hdarea、hddolby、hdfans、hdhome、hdsky、hdtime、hdvideo、keepfrds、nicept、ourbits、piggo、pter、pthome、pttime、soulvoice、ssd、tjupt、ttg |
未实测站点只经 schema 校验,选择器是 MovieClaw 作者在 2026-08/09 的实测值,未经本项目核验。TTG 需要自定义适配器(中文分类、/t/<id> 路径、做种/下载同列),通用 spider 结果不可靠;OurBits 的 cat[] 分类参数已由定义表达;hddolby 在 MovieClaw 中按 HTML 站点配置,MP 则用 API spider(HDDolby),两条路径待账号确认后二选一。
待真实站点核验(W1-3c 新增)
- HHClub:
div.torrent-table-sub-info卡片结构、?cat[]=分类链接(若 URL 编码为cat%5B%5D=则\d+会取错,需改 querystring 过滤)、promotion-tag-*新旧类名、div.torrent-info-hr、详情页 Tailwind 选择器、分页 0 起。 - CHDBits:域名
ptchdbits.co、font.subtitle、td.embedded > span[title]促销截止、a[href='logout.php']。 - 专用 spider 的 JSON 字段均按 MP 读取的键编写 fixture,真实响应待核;HDDolby
hr字段语义、page_number起点;TorrentLeech 时区;YematagList类型;直链缺失时的回退地址(TODO(verify))。 - 用户信息解析器 fixture 均为最小自编 HTML/JSON;
normalizeDatetime不处理相对时间(MP 用 dateparser)。 - 馒头
mode=adult独立索引(MovieClaw custom/mteam.py)未实现。