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

+18
View File
@@ -0,0 +1,18 @@
# testkit/rss
mock RSS/Atom 服务器(127.0.0.1 随机端口,node:http,无外部依赖)。
```ts
const feed = await createMockRssServer({ items, passkey: "pk", format: "rss" | "atom" });
feed.feedUrl; // 带 ?passkey= 的完整 URL
feed.addItem(item); // 新条目置前
feed.updateItem(guid, {title}); // 同 GUID 内容变化
feed.pinItem(guid); feed.shuffle(seed); // 置顶 / 乱序
feed.failNext(2, { status: 503, retryAfter: "120" });
feed.setRawBody(maliciousXml); // DTD/XXE/超大响应样本;null 恢复
feed.setConditional(false); // 关闭 ETag/Last-Modified
feed.requests; feed.notModifiedCount;
await feed.close();
```
ETag 随内容与版本变化;If-None-Match 命中或 If-Modified-Since 不早于最后修改时间返回 304。passkey 错误返回 403。