- 新专用解析器(spiders/public.ts):RSS / Torznab / JSON API,搜索 + 最新列表 + 按资源 ID 取种子或磁力 - 手写定义(definitions/public.ts,public: true),并入内置定义表;定义视图按 public 标为 public_bt - 添加站点:公开 BT 站可从内置站一键添加(专用适配器、默认定时抓取、默认不进搜索集合),手填 RSS 保留 - 样例取自 2026-10-02 公开接口,截前两条 Co-Authored-By: Claude Opus 5.5 <[email protected]>
105 lines
4.9 KiB
TypeScript
105 lines
4.9 KiB
TypeScript
import {
|
|
ANIBT_RSS_LATEST,
|
|
ANIBT_TORZNAB_SEARCH,
|
|
ANIMEGARDEN_RESOURCES,
|
|
BANGUMIMOE_LATEST,
|
|
createMockFetch,
|
|
MIKAN_RSS_SEARCH,
|
|
NYAA_RSS_SEARCH,
|
|
} from "@umt/testkit";
|
|
import { describe, expect, it } from "vitest";
|
|
import { BUILTIN_SITE_DEFINITIONS } from "../definitions/index.ts";
|
|
import { anibtSpider, animeGardenSpider, bangumiMoeSpider, mikanSpider, nyaaSpider } from "./public.ts";
|
|
import { adapterWith, withTorrent } from "./testing.ts";
|
|
import type { SpecializedSiteDefinition } from "./types.ts";
|
|
|
|
const def = (id: string) => BUILTIN_SITE_DEFINITIONS[id] as SpecializedSiteDefinition;
|
|
const XML = { "content-type": "application/rss+xml; charset=utf-8" };
|
|
|
|
describe("公开 BT 站(本项目扩展)", () => {
|
|
it("Nyaa:RSS 搜索带 q;做种 / 下载 / info hash / 大小;下载按 ID 拼 .torrent", async () => {
|
|
const m = createMockFetch({ "GET /": { body: NYAA_RSS_SEARCH, headers: XML } });
|
|
const t = withTorrent(m.fetch, (u) => u.pathname.startsWith("/download/"));
|
|
const a = adapterWith(def("nyaa"), nyaaSpider, t.fetch);
|
|
const page = await a.search("葬送的芙莉莲");
|
|
expect(m.requests[0]!.url.searchParams.get("q")).toBe("葬送的芙莉莲");
|
|
expect(page.items[0]).toMatchObject({
|
|
siteId: "nyaa",
|
|
siteReleaseId: "2160092",
|
|
detailUrl: "https://nyaa.si/view/2160092",
|
|
seeders: 33,
|
|
leechers: 3,
|
|
snatched: 833,
|
|
infoHash: "ef3e7ad1b12bdd9fc341691d8866cd1fa8374a4b",
|
|
uploadedAt: "2026-09-12T15:45:00.000Z",
|
|
});
|
|
expect(page.items[0]!.sizeBytes).toBe(Math.round(6.6 * 1024 ** 3));
|
|
const got = await a.downloadTarget("2160092");
|
|
expect(got.kind).toBe("torrent");
|
|
expect(t.hits[0]!.url.pathname).toBe("/download/2160092.torrent");
|
|
});
|
|
|
|
it("蜜柑:搜索 RSS;资源 ID = info hash,北京时间转 UTC;下载取剧集页里的 .torrent", async () => {
|
|
const m = createMockFetch({
|
|
"GET /RSS/Search": { body: MIKAN_RSS_SEARCH, headers: XML },
|
|
"GET /Home/Episode/f8651444a2c0b3ff7e3da3cbd75da58ef5f09e19": {
|
|
body: '<a href="/Download/20260828/f8651444a2c0b3ff7e3da3cbd75da58ef5f09e19.torrent">下载</a>',
|
|
},
|
|
});
|
|
const t = withTorrent(m.fetch, (u) => u.pathname.startsWith("/Download/"));
|
|
const a = adapterWith(def("mikan"), mikanSpider, t.fetch);
|
|
const page = await a.search("葬送的芙莉莲");
|
|
expect(page.items[0]).toMatchObject({
|
|
siteReleaseId: "f8651444a2c0b3ff7e3da3cbd75da58ef5f09e19",
|
|
infoHash: "f8651444a2c0b3ff7e3da3cbd75da58ef5f09e19",
|
|
sizeBytes: 52076478464,
|
|
uploadedAt: "2026-08-27T21:32:00.000Z",
|
|
});
|
|
expect((await a.downloadTarget("f8651444a2c0b3ff7e3da3cbd75da58ef5f09e19")).kind).toBe("torrent");
|
|
expect(t.hits[0]!.url.pathname).toBe("/Download/20260828/f8651444a2c0b3ff7e3da3cbd75da58ef5f09e19.torrent");
|
|
});
|
|
|
|
it("AniBT:最新 RSS 带字幕组 / 分辨率标签与 Bangumi ID;搜索走 Torznab", async () => {
|
|
const m = createMockFetch({
|
|
"GET /rss/magnets.xml": { body: ANIBT_RSS_LATEST, headers: XML },
|
|
"GET /torznab/api": { body: ANIBT_TORZNAB_SEARCH, headers: XML },
|
|
});
|
|
const a = adapterWith(def("anibt"), anibtSpider, m.fetch);
|
|
const latest = await a.list(1);
|
|
expect(latest.items[0]).toMatchObject({
|
|
siteReleaseId: "rel_TC7FrmcvDwIw",
|
|
tags: expect.arrayContaining(["Nix-Raws", "1080p"]),
|
|
});
|
|
expect(latest.items[0]!.raw.bangumi_id).toBe("637339");
|
|
const found = await a.search("葬送");
|
|
expect(m.requests.at(-1)!.url.searchParams.get("t")).toBe("search");
|
|
expect(found.items[0]).toMatchObject({
|
|
siteReleaseId: "rel_U6lR5dHzDTZS",
|
|
sizeBytes: 52023960599,
|
|
infoHash: "f8651444a2c0b3ff7e3da3cbd75da58ef5f09e19",
|
|
});
|
|
});
|
|
|
|
it("AnimeGarden:JSON 搜索参数是数组;资源 ID = 来源-来源 ID,只给磁力", async () => {
|
|
const m = createMockFetch({ "GET https://api.animes.garden/resources": { body: ANIMEGARDEN_RESOURCES } });
|
|
const a = adapterWith(def("animegarden"), animeGardenSpider, m.fetch);
|
|
const page = await a.search("葬送");
|
|
expect(JSON.parse(m.requests[0]!.url.searchParams.get("search")!)).toEqual(["葬送"]);
|
|
expect(page.items[0]).toMatchObject({ siteReleaseId: "dmhy-725809", tags: ["dmhy", "合集"] });
|
|
const got = await a.downloadTarget("dmhy-725809");
|
|
expect(got).toMatchObject({ kind: "magnet" });
|
|
});
|
|
|
|
it("萌番组:最新 / 磁力;资源 ID 是 24 位 ObjectId", async () => {
|
|
const m = createMockFetch({ "GET /api/torrent/latest": { body: BANGUMIMOE_LATEST } });
|
|
const a = adapterWith(def("bangumimoe"), bangumiMoeSpider, m.fetch);
|
|
const page = await a.list(1);
|
|
expect(page.items[0]).toMatchObject({ siteReleaseId: "66f1c2d3e4f5a6b7c8d9e0f1", seeders: 12, snatched: 40 });
|
|
expect(page.items[0]!.sizeBytes).toBe(Math.round(345.6 * 1024 ** 2));
|
|
});
|
|
|
|
it("五个内置公开站都是 public 定义", () => {
|
|
for (const id of ["nyaa", "mikan", "anibt", "animegarden", "bangumimoe"]) expect(def(id).public).toBe(true);
|
|
});
|
|
});
|