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,52 @@
|
||||
import { createMockFetch, TNODE_HOME_HTML, TNODE_HOME_NO_TOKEN, TNODE_SEARCH_OK } from "@umt/testkit";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { adapterWith, expectSiteError, testDef, withTorrent } from "./testing.ts";
|
||||
import { parseTnodeToken, tnodeSearchParams, tnodeSpider } from "./tnode.ts";
|
||||
|
||||
const def = testDef("TNodeSpider", { domain: "https://tnode.example/" });
|
||||
|
||||
describe("TNodeSpider(MP tnode.py)", () => {
|
||||
it("__get_params / token 解析", () => {
|
||||
expect(tnodeSearchParams("tt0923811", 1)).toMatchObject({
|
||||
page: 2,
|
||||
size: 100,
|
||||
type: "imdbid",
|
||||
keyword: "tt0923811",
|
||||
});
|
||||
expect(tnodeSearchParams(null)).toMatchObject({ type: "title", keyword: "", category: [501, 502, 503, 504] });
|
||||
expect(parseTnodeToken(TNODE_HOME_HTML)).toBe("CSRF-TOKEN-XYZ");
|
||||
});
|
||||
|
||||
it("列表解析:CSRF token 缓存、系数直取、时间戳转 UTC", async () => {
|
||||
const m = createMockFetch({
|
||||
"GET /": { body: TNODE_HOME_HTML },
|
||||
"POST /api/torrent/advancedSearch": { body: TNODE_SEARCH_OK },
|
||||
});
|
||||
const a = adapterWith(def, tnodeSpider, m.fetch);
|
||||
const page = await a.search("eva");
|
||||
await a.list(1);
|
||||
expect(m.requests.filter((r) => r.url.pathname === "/")).toHaveLength(1);
|
||||
expect(m.requests[1]!.headers["x-csrf-token"]).toBe("CSRF-TOKEN-XYZ");
|
||||
const [x, y] = page.items;
|
||||
expect(x).toMatchObject({
|
||||
siteReleaseId: "3301",
|
||||
promotion: { type: "free", downloadFactor: 0, uploadFactor: 1 },
|
||||
uploadedAt: "2024-09-20T00:00:00.000Z",
|
||||
imdbId: "tt0923811",
|
||||
detailUrl: "https://tnode.example/torrent/info/3301",
|
||||
hr: "unknown",
|
||||
});
|
||||
expect(y!.promotion.type).toBe("2x50%");
|
||||
const t = withTorrent(m.fetch, (u) => u.pathname.startsWith("/api/torrent/download/"));
|
||||
await adapterWith(def, tnodeSpider, t.fetch).downloadTarget("3301");
|
||||
expect(t.hits[0]!.headers.get("cookie")).toBe("c=COOKIESECRET");
|
||||
});
|
||||
|
||||
it("取不到 token → auth_expired;缺 Cookie → auth_missing;搜索 HTTP 403 → auth_invalid", async () => {
|
||||
const m = createMockFetch({ "GET /": { body: TNODE_HOME_NO_TOKEN } });
|
||||
await expectSiteError(adapterWith(def, tnodeSpider, m.fetch).list(1), "auth_expired");
|
||||
await expectSiteError(adapterWith(def, tnodeSpider, m.fetch, {}).list(1), "auth_missing");
|
||||
const f = createMockFetch({ "GET /": { body: TNODE_HOME_HTML }, "/api/torrent/advancedSearch": { status: 403 } });
|
||||
await expectSiteError(adapterWith(def, tnodeSpider, f.fetch).list(1), "auth_invalid");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user