fix(sites): normalize HHClub RSS items, complete detail page parsing (HH-2)
- RSS: split NexusPHP "[category]name[subtitle][size][uploader]" titles in the site adapter layer (category id/name/media type, clean title, subtitle, size); snapshot fields win on merge; lazily renormalize old RSS rows and re-emit release.observed; unknown list fields show an em dash - Detail: HHClub section layout (douban info block as description, screenshots section, IMDb/TMDB from MediaInfo lines, upload time field, on-demand viewfilelist.php once); site CDN screenshots get a Referer - Site request count now includes site-bound RSS fetches - Origin view shows "自动化" when idle and "Agent" only after recent automation actions (lastAutomationAt); agent step titles no longer get squeezed to a sliver by long notes Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
4a212ed19e
commit
cabd772243
32 files changed
+816
-65
No files matched your search
@@ -1,8 +1,11 @@
|
||||
import { HHANCLUB_DETAIL_PAGE_FULL } from "@umt/testkit";
|
||||
import { HHANCLUB_DETAIL_PAGE_2026, HHANCLUB_DETAIL_PAGE_FULL, HHANCLUB_FILELIST_FRAGMENT } from "@umt/testkit";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { hhanclubDefinition } from "../definitions/index.ts";
|
||||
import { looksLikeMediaInfo, pageFromBbcode } from "../detail-text.ts";
|
||||
import { createSiteRateLimiter } from "../rate-limit.ts";
|
||||
import { getSiteDefinition } from "../registry.ts";
|
||||
import { staticSecretResolver } from "../types.ts";
|
||||
import { createSpiderSiteAdapter } from "./adapter.ts";
|
||||
import { parseDetailPage } from "./detail.ts";
|
||||
import type { IndexerDefinition } from "./schema.ts";
|
||||
|
||||
@@ -105,3 +108,58 @@ Format : AVC[/quote]
|
||||
expect(looksLikeMediaInfo("General\n一段文字\nVideo")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("详情页扩展内容(憨憨 2026 真实结构:分节 div,无 #kdescr)", () => {
|
||||
const { kind, detail } = parseDetailPage(HHANCLUB_DETAIL_PAGE_2026, hhanclubDefinition, "91003", OBSERVED);
|
||||
const page = detail!.page!;
|
||||
|
||||
it("MediaInfo 取「mediainfo」一节;IMDb / TMDB 从 MediaInfo 行兜底,豆瓣取页面链接", () => {
|
||||
expect(kind).toBe("ok");
|
||||
expect(page.mediaInfo).toContain("Complete name");
|
||||
expect(page).toMatchObject({ imdbId: "tt45950001", tmdb: { type: "tv", id: "290001" }, doubanId: "36000002" });
|
||||
expect(detail!.imdbId).toBe("tt45950001");
|
||||
});
|
||||
|
||||
it("简介取豆瓣信息块,小标签补冒号、一项一行", () => {
|
||||
expect(page.descriptionText).toContain("片名:示例剧集");
|
||||
expect(page.descriptionText).toContain("类别:爱情 / 古装");
|
||||
expect(page.descriptionText).toContain("一段剧情简介");
|
||||
});
|
||||
|
||||
it("截图取「截图」一节(优先 data-src),不含海报与占位图", () => {
|
||||
expect(page.screenshots).toEqual([
|
||||
"https://pic.hhanclub.net/img/2026/09/29/shot1.png",
|
||||
"https://img3.pixhost.cc/images/1/shot2.jpg",
|
||||
]);
|
||||
});
|
||||
|
||||
it("上传者、标签、发布时间(字段文本兜底)", () => {
|
||||
expect(page.uploader).toBe("匿名");
|
||||
expect(page.tags).toEqual(["官方", "国语", "中字", "杜比视界"]);
|
||||
expect(detail!.uploadedAt).toBe("2026-09-29T08:42:57.000Z");
|
||||
expect(page.files).toEqual([]);
|
||||
});
|
||||
|
||||
it("文件清单不在页面上 → 按需请求 viewfilelist.php 一次", async () => {
|
||||
const paths: string[] = [];
|
||||
const fetch = (async (input: RequestInfo | URL) => {
|
||||
const u = new URL(String(input));
|
||||
paths.push(`${u.pathname}${u.search}`);
|
||||
const body = u.pathname === "/viewfilelist.php" ? HHANCLUB_FILELIST_FRAGMENT : HHANCLUB_DETAIL_PAGE_2026;
|
||||
return new Response(body, { status: 200, headers: { "content-type": "text/html; charset=utf-8" } });
|
||||
}) as typeof globalThis.fetch;
|
||||
const limiter = createSiteRateLimiter({ minIntervalMs: 0 });
|
||||
const a = createSpiderSiteAdapter(hhanclubDefinition, {
|
||||
fetch,
|
||||
limiter,
|
||||
secrets: staticSecretResolver({ hhanclub: { cookie: "c=1" } }),
|
||||
});
|
||||
const d = await a.detail("91003");
|
||||
expect(paths).toEqual(["/details.php?id=91003&hit=1", "/viewfilelist.php?id=91003"]);
|
||||
expect(limiter.stats.granted).toBe(2);
|
||||
expect(d.page!.files).toEqual([
|
||||
{ name: "Example.Show.S01E18.2026.2160p.mkv", sizeBytes: Math.round(5.04 * 1024 ** 3) },
|
||||
{ name: "Example.Show.S01E19.2026.2160p.mkv", sizeBytes: Math.round(4.98 * 1024 ** 3) },
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user