feat(sites): sync MoviePilot v3.2.4 spiders and M-Team subtitle APIs

This commit is contained in:
root committed 2026-11-06 00:06:12 +08:00
1 parent 3b9376c75a
commit c38e7131f1
26 files changed
+775 -56

No files matched your search

+14 -6
View File
@@ -1,7 +1,8 @@
/**
* @mp-source app/modules/indexer/__init__.py @ 3b156a5
* 通用 spider 站点适配器(NexusPHP 等 HTML 站)。
*
* 对应 MoviePilot e6820a8:
* 对应 MoviePilot v3.2.4:
* - `app/modules/indexer/__init__.py IndexerModule.search_torrents / refresh_torrents` → `search` / `list`
* (`__search_check` 的英文站不搜中文 → search 前检查;`__clear_search_text` → `clearSearchText`;
* `SitesHelper.check(domain)` 流控 → 站点令牌桶)
@@ -134,6 +135,7 @@ export function createSpiderSiteAdapter(input: IndexerDefinition, deps: SiteAdap
async function cookie(): Promise<string> {
const c = await deps.secrets.resolve({ siteId: def.id, kind: "cookie" });
if (!c && def.public) return "";
if (!c) throw new SiteError(def.id, "auth_missing", "未配置站点 Cookie");
return c;
}
@@ -153,9 +155,10 @@ export function createSpiderSiteAdapter(input: IndexerDefinition, deps: SiteAdap
/** 页面认不出来时的错误:像人机验证就报 challenge,否则 parse_error;都附页面样本 */
function unrecognized(page: FetchedPage, what: string, kind: SiteRequestKind, parseMessage: string): SiteError {
const rule = challengeRuleOfUnrecognizedPage(page.html);
const title = redactText(loadHtml(page.html)("title").first().text().replace(/\s+/g, " ").trim()).slice(0, 120);
const message = rule
? `${what}:${PAGE_LABEL[kind]}需要人机验证(Cloudflare 等),${challengeOutcomeText("unhandled")}`
: `${parseMessage}(页面样本见站点页技术信息)`;
: `${parseMessage}(HTTP ${page.status}${title ? `,页面标题:${title}` : ""};页面样本见站点页技术信息)`;
const sample = buildFailureSample({
kind: rule ? "challenge" : "parse_error",
requestKind: kind,
@@ -186,7 +189,11 @@ export function createSpiderSiteAdapter(input: IndexerDefinition, deps: SiteAdap
try {
res = await siteFetch(http, url, {
method: "GET",
headers: { cookie: await cookie(), accept: "text/html,application/xhtml+xml", referer: base },
headers: {
cookie: await cookie(),
accept: "text/html,application/xhtml+xml,application/rss+xml,application/xml",
referer: base,
},
redirect: "manual",
...(opts.signal ? { signal: opts.signal } : {}),
});
@@ -260,7 +267,8 @@ export function createSpiderSiteAdapter(input: IndexerDefinition, deps: SiteAdap
if (parsed.torrents.length === 0) {
const text = $("body").text();
const hasList =
$(normalizeSelector(spider.def.torrents.list.selector)).length > 0 || $("table.torrents").length > 0;
$(normalizeSelector(spider.def.torrents.list.selector)).length > 0 ||
$("table.torrents, rss > channel, feed").length > 0;
const empty = (ext.emptyMarkers ?? []).some((m) => text.includes(m));
// 认不出列表:先看是不是人机验证页(搜索页单独加盾、间歇触发),不是才报改版
if (!hasList && !empty) throw unrecognized(page$, what, kind, `${what}:页面结构无法识别(可能改版)`);
@@ -289,8 +297,8 @@ export function createSpiderSiteAdapter(input: IndexerDefinition, deps: SiteAdap
return {
items,
page: Math.max(1, page),
totalPages: maxRaw === null ? null : Math.max(page, (maxRaw as number) - start + 1),
totalPagesEstimated: true,
totalPages: parsed.totalPages ?? (maxRaw === null ? null : Math.max(page, (maxRaw as number) - start + 1)),
totalPagesEstimated: parsed.totalPages === undefined,
fetchedAt: observedAt,
};
}