feat(adapters): RSS and PT site adapters ported from MoviePilot v3 + mocks (W1-3b)

- rss: port of MP RssHelper._parse_impl/get_rss_link semantics with safe
  XML (entities off, DTD/ENTITY rejected, size/item caps), ETag/Last-Modified,
  backoff + Retry-After, circuit breaker with manual reset, passkey redaction,
  overlap-window GUID+digest dedupe
- sites/spider: TS port of MP SiteSpider (indexer schema, search URL builder,
  field DSL, filters, Jinja templates via nunjucks, login-page detection);
  audiences definition hand-written as JSON; raw category id preserved;
  shared per-site token bucket
- sites/mteam: port of MP MTorrentSpider (params, result mapping, promo
  factors, genDlToken flow) without embedding the API key in enclosures;
  business code/message checks
- testkit: mock RSS server, mock NexusPHP site, mock M-Team API
- docs: W1-3b migration mapping appended to porting ledger

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-27 18:10:34 +08:00
1 parent ef2e504a94
commit a6d049db81
47 files changed
+5414

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
/**
* 随产品发布的站点注册表(迁移账本 SA1)。
* 对应 MP `SitesHelper.get_indexers()/get_indexer(domain)`(闭源,sites.pyi)与
* `IndexerModule` 按 `indexer.parser` 分派专用解析器(`mTorrent` → MTorrentSpider,其余 → SiteSpider)。
* 加站:新增 definitions/<site>.json 并在此登记。
*/
import { audiencesDefinition } from "./definitions";
import type { SiteAdapterDeps } from "./deps";
import { createMteamAdapter } from "./mteam/adapter";
import { type MteamDefinition, mteamDefinition } from "./mteam/definition";
import { createSpiderSiteAdapter } from "./spider/adapter";
import type { IndexerDefinition } from "./spider/schema";
import type { SiteAdapter } from "./types";
export type SiteDefinition = IndexerDefinition | MteamDefinition;
export const SITE_DEFINITIONS: Record<string, SiteDefinition> = {
audiences: audiencesDefinition,
mteam: mteamDefinition,
};
function isMteam(def: SiteDefinition): def is MteamDefinition {
return (def as MteamDefinition).parser === "mTorrent";
}
export function createSiteAdapter(def: SiteDefinition, deps: SiteAdapterDeps): SiteAdapter {
return isMteam(def) ? createMteamAdapter(def, deps) : createSpiderSiteAdapter(def, deps);
}
export function getSiteAdapter(siteId: string, deps: SiteAdapterDeps): SiteAdapter {
const def = SITE_DEFINITIONS[siteId];
if (!def) throw new Error(`未知站点:${siteId}`);
return createSiteAdapter(def, deps);
}