diff --git a/docs/runbook/sites.md b/docs/runbook/sites.md
index 98c993ad..74a7e505 100644
--- a/docs/runbook/sites.md
+++ b/docs/runbook/sites.md
@@ -117,6 +117,7 @@
- 外部 ID 另写 `source_release.raw._detail`(`imdbId / doubanId / tmdb{type,id}`),列表快照 upsert 不覆盖它。身份解析把它和列表给的 `raw._site.imdbId / doubanId` 一起当显式 ID:本地召回查已接受的外部身份;需要查 TMDB 时,有 TMDB ID 直接取详情,否则 IMDb ID 走 `/find/{imdb}?external_source=imdb_id`,命中就不再按标题搜索;TMDB 不支持豆瓣 ID,豆瓣只用于本地召回。是否查 TMDB 仍按惰性识别规则(DEC-1),外部 ID 本身不触发查询。
- 截图:响应里只有代理路径 `/api/v1/img/site-detail/{releaseId}/{index}`,服务端从缓存取原始地址,公网校验(每跳重定向复核)、不带站点凭据、按文件头认图片(不放行 SVG)、≤ 8 MB,浏览器缓存 24 小时。站内附件如果需要登录才能看会显示为裂图,不会为截图消耗站点请求。
- 憨憨详情页(HH-2 按真实页面 `probe.labels` 核对):没有 `#kdescr`,内容分节为「标签 div + 相邻值 div」:`mediainfo`、`海报`、`豆瓣`(内有 `span.font-bold` 小标签 片名 / 年代 / 类别 / 导演 …)、`截图`、`种子文件`(只有「查看列表」按钮)、`发布者`、`发布时间`。对应规则:简介没有容器时取「豆瓣 / 影片信息 / IMDb」信息块,小标签补冒号、一项一行;截图取「截图」一节的 img(`data-src` 优先,海报不算截图),没有再取简介里的图;IMDb 依次取页面链接 → 简介 / 信息块 / MediaInfo 里的 `imdb.com/title/tt…` 或 `IMDB : tt…` 行(只收 `tt` + 7~8 位,不收孤立 tt 串),TMDB 取链接或 `TMDB : tv/294990` 行,豆瓣取链接或 `douban.com/subject/…`;兜底出的 IMDb / 豆瓣也回填到 `SiteRelease.imdbId / doubanId`;发布时间没有 `span[title]` 时取「发布时间 / 上传时间」字段文本。文件清单:定义 `umt.detail.page.fileListPath`(憨憨 `viewfilelist.php?id={id}`)配置了且页面上没有内联清单时,同一次详情刷新里再请求 1 次(走同一限流器、计数),失败只是没有清单。fixture `HHANCLUB_DETAIL_PAGE_2026` / `HHANCLUB_FILELIST_FRAGMENT`:标签名称与顺序来自真实页面,嵌套按已验证的标题 / 副标题结构推定,部署后打开一条憨憨资源的详情抽屉核对一次。
+- 观众详情页(DET-2 按真实页面核对,样本 `packages/adapters/test/differential/inputs/detail/rl-audiences-detail-*.html.txt` 已脱敏,回归测试 `test/differential/detail.test.ts`;列表页样本 `inputs/indexer/rl-audiences-list.html.txt` 进 MP 差分):标准 rowhead 表格,但有几处与通用 NexusPHP 不同,写在定义里。h1 的促销是 `font.free / font.halfdown …`(列表是 `img.pro_*`)加「剩余时间:」和到期 `span[title]`,用 `umt.detail.promo` 覆盖列表的促销字段;h1 的「剩余时间:」文本不进标题,标题区里的 `span[title]` 也不当发布时间(发布时间取「下载」行「发布于」的 span)。没有「同伴」行,做种 / 下载 / 完成数取基本信息行右侧 `#seeders / #leechers / a[title=完成]`(`umt.detail.fields.seeders / leechers / snatched`);种子 Hash 取 `#copyTorrentHashLink[data-hash]`(`fields.infoHash`,回填 `infoHash`)。副标题行右侧有豆瓣 / IMDb / 烂番茄 / Metacritic 评分徽标(`[title*=评分]`),解析时去掉;评分本身本工具没有字段,不收。标签是 `span.detail-tag-list > span.tags`,只取最内层。上传者在「下载」行的 `span.torrent-uploader-wrap`(匿名发布为「匿名」);另有「发布者带宽」一行,所以上传者标签改为精确匹配。简介开头的站点声明 `#ad_torrentdetail` 用 `umt.detail.page.descriptionStrip` 去掉;MediaInfo / BDInfo 是 `div.codetop`(文件名 / BDInfo)+ `div.show`(摘要表或同一份 BDInfo)+ `div.hide > div.codemain`(原文),三者都从简介里去掉;简介是 ` \r\n`,源码换行不再算一行(以前每行之间多一空行)。截图排除豆瓣海报(站点经 `dbimgs.audiences.me/?…doubanio.com…poster` 代理)与站点横幅 `/banners/`。IMDb / 豆瓣 ID 取简介里的链接;TMDB 页面上没有;单种 H&R 标记页面上没有(列表也没有),保持「未知」。文件清单不在页面上(点「N个文件」后 AJAX 加载,端点未核对),暂不另发请求。列表副标题改取 `span.torrent-subtitle-text`(`span.torrent-subtitle-line` 里先是标签,以前副标题前面会粘着「官方中字DIY…」)。
- 截图代理的 Referer:截图与站点同根域(憨憨图床 `pic.hhanclub.net`、`proxy-cover.hhanclub.net`)时带 `Referer: <站点地址>/`(防盗链),外部图床(如 pixhost)不带;任何情况都不带 Cookie。
- 解析:`packages/adapters/src/sites/spider/detail-page.ts`。简介取 MovieClaw `detail_description_css`(缺省 `#kdescr`);MediaInfo 取定义 `umt.detail.page.mediaInfo` → 「MediaInfo」字段标签 → 简介里第一个像 MediaInfo 的引用 / 代码块;文件清单取 `umt.detail.page.files` → MovieClaw `detail_file_list_css` → 「文件列表」字段 → `#filelist` 表格,不额外请求 `viewfilelist.php`。字段标签除 `td.rowhead` 外可在 `umt.detail.page.labelSelectors` 加(憨憨:`div.font-bold.leading-6`、`span.font-bold`)。`page.probe` 记录页面上找到的字段标签,只存缓存不展示,站点改版时用来排查。馒头从 API 的 `descr`(BBCode)与 `mediainfo` 取同样的结构。
diff --git a/packages/adapters/src/sites/definitions/audiences.json b/packages/adapters/src/sites/definitions/audiences.json
index 35e49702..6a9f130a 100644
--- a/packages/adapters/src/sites/definitions/audiences.json
+++ b/packages/adapters/src/sites/definitions/audiences.json
@@ -140,7 +140,7 @@
}
},
"description": {
- "selector": "span.torrent-subtitle-line"
+ "selector": "span.torrent-subtitle-text"
},
"category": {
"selector": "a[href*=\"?cat=\"]",
@@ -168,7 +168,7 @@
}
},
"umt": {
- "version": "audiences@0.2.0-unverified",
+ "version": "audiences@0.3.0",
"kind": "nexusphp",
"displayName": "观众",
"timezone": "+08:00",
@@ -225,7 +225,55 @@
},
"description": "#kdescr",
"headerScope": "h1#top",
- "notFoundMarkers": ["没有该ID的种子", "No torrent with ID"]
+ "notFoundMarkers": ["没有该ID的种子", "No torrent with ID"],
+ "fields": {
+ "seeders": "#seeders",
+ "leechers": "#leechers",
+ "snatched": "a[title='完成'][href*='viewsnatches.php']",
+ "infoHash": "#copyTorrentHashLink::attr(data-hash)"
+ },
+ "promo": {
+ "downloadvolumefactor": {
+ "case": {
+ "font.free": 0,
+ "font.twoupfree": 0,
+ "font.halfdown": 0.5,
+ "font.twouphalfdown": 0.5,
+ "font.thirtypercent": 0.3,
+ "img.pro_free": 0,
+ "img.pro_free2up": 0,
+ "img.pro_50pctdown": 0.5,
+ "img.pro_50pctdown2up": 0.5,
+ "img.pro_30pctdown": 0.3,
+ "*": 1
+ }
+ },
+ "uploadvolumefactor": {
+ "case": {
+ "font.twoup": 2,
+ "font.twoupfree": 2,
+ "font.twouphalfdown": 2,
+ "img.pro_50pctdown2up": 2,
+ "img.pro_free2up": 2,
+ "img.pro_2up": 2,
+ "*": 1
+ }
+ },
+ "freedate": {
+ "selector": "img[class^=\"pro_\"], span[title]",
+ "attribute": "title",
+ "filters": [
+ {
+ "name": "re_search",
+ "args": ["\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}", 0]
+ }
+ ]
+ }
+ },
+ "page": {
+ "uploader": "span.torrent-uploader-wrap",
+ "descriptionStrip": "#ad_torrentdetail"
+ }
}
}
}
diff --git a/packages/adapters/src/sites/definitions/schema.ts b/packages/adapters/src/sites/definitions/schema.ts
index 83fe8ae2..46186a65 100644
--- a/packages/adapters/src/sites/definitions/schema.ts
+++ b/packages/adapters/src/sites/definitions/schema.ts
@@ -139,8 +139,13 @@ const UmtNexusSchema = z.strictObject({
imdb: z.string().optional(),
douban: z.string().optional(),
fileList: z.string().optional(),
+ seeders: z.string().optional(),
+ leechers: z.string().optional(),
+ snatched: z.string().optional(),
+ infoHash: z.string().optional(),
})
.optional(),
+ promo: z.record(z.string(), SpiderFieldSelectorSchema).optional(),
page: z
.strictObject({
labelSelectors: z.array(z.string()).optional(),
@@ -150,6 +155,7 @@ const UmtNexusSchema = z.strictObject({
uploader: z.string().optional(),
files: z.string().optional(),
fileListPath: z.string().optional(),
+ descriptionStrip: z.string().optional(),
})
.optional(),
})
diff --git a/packages/adapters/src/sites/detail-text.ts b/packages/adapters/src/sites/detail-text.ts
index ea569a30..186a044e 100644
--- a/packages/adapters/src/sites/detail-text.ts
+++ b/packages/adapters/src/sites/detail-text.ts
@@ -8,6 +8,9 @@ import { extractDoubanId, extractImdbId } from "./util.ts";
const NOT_SCREENSHOT =
/(smilies|smiley|emoji|\/icons?\/|pic\/(?:trans|cattrans|flag)|\/flags?\/|avatar|\.svg(?:$|\?))/i;
+/** 按完整地址排除:豆瓣海报(含站点的豆瓣图片代理 `?https://img…doubanio.com/…poster…`)与站点横幅装饰图 */
+const NOT_SCREENSHOT_URL = /(doubanio\.com\/view\/photo\/[a-z_]*poster|\/banners?\/)/i;
+
export const MAX_SCREENSHOTS = 20;
export const MAX_FILES = 500;
@@ -36,7 +39,7 @@ export function screenshotUrl(src: string | null | undefined, base: string): str
return null;
}
if (u.protocol !== "http:" && u.protocol !== "https:") return null;
- if (NOT_SCREENSHOT.test(u.pathname)) return null;
+ if (NOT_SCREENSHOT.test(u.pathname) || NOT_SCREENSHOT_URL.test(u.href)) return null;
return u.toString();
}
diff --git a/packages/adapters/src/sites/spider/detail-page.ts b/packages/adapters/src/sites/spider/detail-page.ts
index 2f2cca81..821c20ca 100644
--- a/packages/adapters/src/sites/spider/detail-page.ts
+++ b/packages/adapters/src/sites/spider/detail-page.ts
@@ -43,17 +43,35 @@ const normLabel = (t: string) =>
.replace(/[::]+$/, "")
.toLowerCase();
-/** 保留换行的文本: 与块级元素换行 */
+/** 换行占位:先把 / 块级换行记成它,再去掉 HTML 源码里的换行(浏览器按空白处理),最后还原 */
+const BR = "\u0000";
+
+/**
+ * 保留换行的文本: 与块级元素换行。源码换行不算换行(观众的简介是 ` \r\n`,
+ * 两者都算会让每行之间多一空行);
里的换行保留。
+ */
function blockText($: CheerioAPI, el: El): string {
const c = el.clone();
c.find("script, style").remove();
- c.find("br").replaceWith("\n");
- c.find("div, p, li, tr, pre, fieldset, blockquote, h1, h2, h3, h4, h5, h6").each((_, n) => {
- $(n).append("\n");
+ c.find("pre, textarea").each((_, n) => {
+ $(n).text($(n).text().replace(/\r?\n/g, BR));
});
- return tidyText(c.text());
+ c.find("br").replaceWith(BR);
+ c.find("div, p, li, tr, pre, fieldset, blockquote, h1, h2, h3, h4, h5, h6").each((_, n) => {
+ $(n).append(BR);
+ });
+ const text = c
+ .text()
+ .replace(/\r?\n/g, " ")
+ // ` \r\n下一行` 的源码换行变成了行首一个空格
+ .replace(new RegExp(`${BR} `, "g"), BR)
+ .replaceAll(BR, "\n");
+ return tidyText(text);
}
+/** NexusPHP MediaInfo 控件的外壳:标题栏 div.codetop、摘要表 div.show、原文 div.hide(观众三者相邻) */
+const MI_WIDGET = "div.codetop, div.show, div.hide";
+
const SIZE_TAIL = /^(.*?)\s*[(([]\s*([\d.,]+\s*[KMGTP]?i?B)\s*[))\]]\s*$/i;
const HAS_EXT = /\.[A-Za-z0-9]{2,5}$/;
@@ -113,8 +131,9 @@ export function extractDetailPage($: CheerioAPI, conf: DetailConf, base: string)
};
addLabels(conf.rowLabel, (e) => e.nextAll(conf.rowValue).first());
for (const sel of p.labelSelectors ?? []) addLabels(sel, (e) => e.next());
- const byLabel = (names: string[]): El | null =>
- pairs.find((x) => names.some((n) => x.label.startsWith(normLabel(n))))?.value ?? null;
+ const byLabel = (names: string[], exact = false): El | null =>
+ pairs.find((x) => names.some((n) => (exact ? x.label === normLabel(n) : x.label.startsWith(normLabel(n)))))
+ ?.value ?? null;
page.probe.labels = [...new Set(pairs.map((x) => x.label))].slice(0, 40);
// 简介容器
@@ -170,10 +189,30 @@ export function extractDetailPage($: CheerioAPI, conf: DetailConf, base: string)
page.probe.imageCount = imgs.length;
page.screenshots = imgs.slice(0, MAX_SCREENSHOTS);
- // 简介纯文本:去掉 MediaInfo 块与图片
+ // 简介纯文本:去掉站点固定内容、MediaInfo 块(连同控件标题栏 / 摘要表)与图片
if (desc?.length) {
const c = desc.clone();
- if (miBlockIndex >= 0) c.find(MI_BLOCKS).eq(miBlockIndex).remove();
+ if (miBlockIndex >= 0) {
+ const isMi = (n: Parameters[0]) => looksLikeMediaInfo(blockText($, $(n)));
+ // 同一份 MediaInfo 常出现两次(摘要 div.show 与原文 div.hide):另外去掉最内层的 MediaInfo 块,
+ // 在控件外壳(div.codetop / div.show / div.hide)里时连同相邻外壳一起去掉
+ const extra = c
+ .find(MI_BLOCKS)
+ .filter((_, n) => isMi(n) && !$(n).find(MI_BLOCKS).toArray().some(isMi))
+ .map((_, n) => {
+ const e = $(n);
+ const shell = e.parent().is(MI_WIDGET) ? e.parent() : e;
+ const out = [shell.get(0)!];
+ if (!shell.is(MI_WIDGET)) return out;
+ for (let x = shell.prev(); x.is(MI_WIDGET) && out.length < 3; x = x.prev()) out.push(x.get(0)!);
+ for (let x = shell.next(); x.is(MI_WIDGET) && out.length < 4; x = x.next()) out.push(x.get(0)!);
+ return out;
+ })
+ .get();
+ c.find(MI_BLOCKS).eq(miBlockIndex).remove();
+ for (const w of extra) $(w).remove();
+ }
+ if (p.descriptionStrip) c.find(p.descriptionStrip).remove();
c.find("img").remove();
const t = blockText($, c);
if (t) page.descriptionText = t.slice(0, 20_000);
@@ -221,17 +260,16 @@ export function extractDetailPage($: CheerioAPI, conf: DetailConf, base: string)
// 标签
const tagEl = p.tags ? $(p.tags) : byLabel(LABELS.tags);
if (tagEl?.length) {
- const parts = tagEl.find("span, a").length
- ? tagEl
- .find("span, a")
- .map((_, n) => $(n).text().trim())
- .get()
- : tagEl.text().split(/[\s,,|/]+/);
+ // 只取最内层的 span / a(观众外面还包了一层 span.detail-tag-list,取外层会把全部标签连成一个)
+ const leaves = tagEl.find("span, a").filter((_, n) => $(n).find("span, a").length === 0);
+ const parts = leaves.length ? leaves.map((_, n) => $(n).text().trim()).get() : tagEl.text().split(/[\s,,|/]+/);
page.tags = [...new Set(parts.map((t) => t.trim()).filter((t) => t && t.length <= 20))].slice(0, 20);
}
// 上传者
- const upEl = p.uploader ? $(p.uploader).first() : byLabel(LABELS.uploader);
+ // 上传者标签精确匹配:观众另有「发布者带宽」一行
+ const upSel = p.uploader ? $(p.uploader).first() : null;
+ const upEl = upSel?.length ? upSel : byLabel(LABELS.uploader, true);
if (upEl?.length) {
const a = upEl.is("a") ? upEl : upEl.find("a[href*='userdetails.php']").first();
const name = (a.length ? a.text() : upEl.text()).replace(/\s+/g, " ").trim();
diff --git a/packages/adapters/src/sites/spider/detail.ts b/packages/adapters/src/sites/spider/detail.ts
index ce417a5c..6f57c57b 100644
--- a/packages/adapters/src/sites/spider/detail.ts
+++ b/packages/adapters/src/sites/spider/detail.ts
@@ -51,8 +51,15 @@ export function parseDetailPage(
.text()
)
.replace(/\s+/g, " ")
+ // 促销到期的「剩余时间:」是 h1 的直接文本节点(观众),不属于标题
+ .replace(/\s*(?:剩余时间|剩餘時間|Remaining(?: time)?)\s*[::]?\s*$/i, "")
.trim();
- const subtitle = f.subtitle ? (mcSelect($, null, f.subtitle) ?? undefined) : row(conf.labels.subtitle)?.text().trim();
+ const subtitleEl = f.subtitle ? null : row(conf.labels.subtitle)?.clone();
+ // 副标题行右侧的豆瓣 / IMDb / 烂番茄等评分徽标(观众)不算副标题
+ subtitleEl?.find("[title*='评分'], [title*='Rating'], img").remove();
+ const subtitle = f.subtitle
+ ? (mcSelect($, null, f.subtitle) ?? undefined)
+ : subtitleEl?.text().replace(/\s+/g, " ").trim();
const basicText = row(conf.labels.basicInfo)?.text().replace(/\s+/g, " ") ?? "";
const sizeText = f.size
? (/([\d.,]+\s*[KMGTP]?i?B)/i.exec(mcSelect($, null, f.size) ?? "")?.[1] ?? null)
@@ -64,6 +71,9 @@ export function parseDetailPage(
const peersText = row(conf.labels.peers)?.text().replace(/\s+/g, " ") ?? "";
const seeders = /(\d[\d,]*)\s*个?做种者|(\d[\d,]*)\s*seeder/i.exec(peersText);
const leechers = /(\d[\d,]*)\s*个?下载者|(\d[\d,]*)\s*leecher/i.exec(peersText);
+ // 站点把做种 / 下载 / 完成数放在其它位置时(观众:基本信息行右侧的 #seeders / #leechers / 完成链接)按选择器取
+ const countOf = (sel: string | undefined) => (sel ? parseCount(mcSelect($, null, sel) ?? undefined) : null);
+ const infoHash = mcSelect($, null, f.infoHash)?.toLowerCase();
const descEl = $(conf.description).first();
const descText = descEl.text().trim();
const descHtml = descEl.html() ?? "";
@@ -76,17 +86,22 @@ export function parseDetailPage(
list: def.torrents.list,
fields: Object.fromEntries(
(["downloadvolumefactor", "uploadvolumefactor", "freedate", "hr"] as const)
- .filter((k) => fields[k])
- .map((k) => [k, fields[k]]),
+ .map((k) => [k, conf.promo?.[k] ?? fields[k]] as const)
+ .filter(([, v]) => v),
),
},
browse: undefined,
};
const promo = new SiteSpider(promoDef).infoFrom($, $(conf.headerScope).first());
// 发布时间:带完整时间的 span[title] → 「发布时间 / 上传时间 / 添加时间」字段的文本(憨憨是 div 标签 + 值)
+ // 标题区(headerScope)里的 span[title] 是促销到期时间,不是发布时间
const timeTitle =
$("span[title]")
- .filter((_, el) => /\d{4}-\d{2}-\d{2} \d{2}:\d{2}/.test($(el).attr("title") ?? ""))
+ .filter(
+ (_, el) =>
+ /\d{4}-\d{2}-\d{2} \d{2}:\d{2}/.test($(el).attr("title") ?? "") &&
+ $(el).closest(conf.headerScope).length === 0,
+ )
.first()
.attr("title") ??
/(?:发布时间|上传时间|添加时间|Added)\s*[::]?\s*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}(?::\d{2})?)/.exec(
@@ -99,9 +114,9 @@ export function parseDetailPage(
title,
category: { id: catId, name: catText ?? categoryName(def, catId ?? undefined) },
sizeBytes: sizeText ? parseSizeMp(sizeText) : null,
- seeders: seeders ? parseCount(seeders[1] ?? seeders[2]) : null,
- leechers: leechers ? parseCount(leechers[1] ?? leechers[2]) : null,
- snatched: null,
+ seeders: seeders ? parseCount(seeders[1] ?? seeders[2]) : countOf(f.seeders),
+ leechers: leechers ? parseCount(leechers[1] ?? leechers[2]) : countOf(f.leechers),
+ snatched: countOf(f.snatched),
promotion: promotionFromFactors(
promo.downloadvolumefactor,
promo.uploadvolumefactor,
@@ -116,6 +131,7 @@ export function parseDetailPage(
observedAt,
};
if (subtitle) detail.subtitle = subtitle;
+ if (infoHash && /^[0-9a-f]{40}$/.test(infoHash)) detail.infoHash = infoHash;
if (descText) detail.descriptionText = descText.slice(0, 20_000);
const imdb = extractImdbId(mcSelect($, null, f.imdb)) ?? extractImdbId(descHtml) ?? extractImdbId(descText);
if (imdb) detail.imdbId = imdb;
diff --git a/packages/adapters/src/sites/spider/schema.ts b/packages/adapters/src/sites/spider/schema.ts
index 4a7d00b8..827ac40a 100644
--- a/packages/adapters/src/sites/spider/schema.ts
+++ b/packages/adapters/src/sites/spider/schema.ts
@@ -196,8 +196,27 @@ export interface UmtSiteExtension {
* 用于 HHClub 这类 div 详情页。
*/
fields?: Partial<
- Record<"title" | "subtitle" | "size" | "description" | "download" | "imdb" | "douban" | "fileList", string>
+ Record<
+ | "title"
+ | "subtitle"
+ | "size"
+ | "description"
+ | "download"
+ | "imdb"
+ | "douban"
+ | "fileList"
+ | "seeders"
+ | "leechers"
+ | "snatched"
+ | "infoHash",
+ string
+ >
>;
+ /**
+ * 详情页促销 / H&R 字段(downloadvolumefactor / uploadvolumefactor / freedate / hr),覆盖同名列表字段。
+ * 详情页头部的促销标记常与列表不同(列表 img.pro_free,详情 h1 里 font.free + 「剩余时间」span)。
+ */
+ promo?: Record;
/**
* 详情页扩展内容(简介 / MediaInfo / 截图 / 文件 / 标签 / 上传者)的站点特化,均可省略(走通用规则,见 detail-page.ts)。
* 选择器为普通 CSS。
@@ -216,6 +235,8 @@ export interface UmtSiteExtension {
* 每次详情刷新最多 1 次,失败不影响详情。
*/
fileListPath?: string;
+ /** 从简介里去掉的站点固定内容(如站点声明),普通 CSS */
+ descriptionStrip?: string;
};
};
/** 下载路径(按资源 ID 拼接) */
diff --git a/packages/adapters/test/differential/detail.test.ts b/packages/adapters/test/differential/detail.test.ts
new file mode 100644
index 00000000..6c55841a
--- /dev/null
+++ b/packages/adapters/test/differential/detail.test.ts
@@ -0,0 +1,96 @@
+/**
+ * 详情页解析回归(DET-2):观众 audiences.me 真实详情页(2026-09-30 抓取,脱敏:只留标题栏与详情表,
+ * 去掉用户栏 / 评论 / 「行为」行的 downhash 下载链接 / 用户链接)。MP 通用 SiteSpider 不解析详情页,
+ * 没有 MP 期望可比;期望值按页面原文逐项人工核对。
+ */
+import { describe, expect, it } from "vitest";
+import { audiencesDefinition } from "../../src/sites/index.ts";
+import { parseDetailPage } from "../../src/sites/spider/detail.ts";
+import { readInput } from "./diff-util.ts";
+
+const OBSERVED = "2026-09-30T12:11:30.000Z";
+const parse = (id: string) => {
+ const r = parseDetailPage(
+ readInput(`inputs/detail/rl-audiences-detail-${id}.html.txt`),
+ audiencesDefinition,
+ id,
+ OBSERVED,
+ );
+ expect(r.kind).toBe("ok");
+ return r.detail!;
+};
+
+describe("观众详情页(真实样本)", () => {
+ it("725858:原盘 DIY,50% 无期限,BDInfo", () => {
+ const d = parse("725858");
+ expect(d).toMatchObject({
+ title: "Rose 2026 1080p BluRay AVC DTS-HD MA 5.1-DIY@Audies",
+ // 副标题行右侧的豆瓣 / IMDb / 烂番茄 / Metacritic 评分不进副标题
+ subtitle: "罗斯 / 扑朔迷离的露丝(港)【德版原盘DIY 简繁字幕】",
+ category: { id: "401", name: "电影" },
+ sizeBytes: 23654532383,
+ seeders: 126,
+ leechers: 1,
+ snatched: 276,
+ promotion: { type: "50%", downloadFactor: 0.5, uploadFactor: 1, expiresAt: null },
+ hr: "unknown",
+ uploadedAt: "2026-09-27T09:25:07.000Z",
+ infoHash: "f7a77044521104ef2edafc0ab9bfc3774f763079",
+ imdbId: "tt25399154",
+ doubanId: "35322132",
+ tags: ["官方", "中字", "官字组", "DIY", "禁转"],
+ downloadUrl: "https://audiences.me/download.php?id=725858",
+ });
+ const p = d.page!;
+ expect(p.uploader).toBe("匿名");
+ expect(p.mediaInfo).toMatch(/^DISC INFO:\n\nDisc Label: {5}Rose\.2026/);
+ expect(p.mediaInfo).toContain("Presentation Graphics Chinese 15.892 kbps 繁体中文");
+ // 简介:没有站点声明、没有 BDInfo(页面上摘要 / 原文各一份)、行间没有多余空行
+ expect(p.descriptionText).toMatch(/^引用\n1\.原盘来自@4FR/);
+ expect(p.descriptionText).toContain("◎片 名 Rose\n◎年 代 2026\n");
+ expect(p.descriptionText).not.toMatch(/声明|DISC INFO|BDInfo/);
+ // 截图:豆瓣海报与站点横幅不算
+ expect(p.screenshots).toEqual([
+ "https://stor.picx.cx/images/2026/09/27/mvuGsV.jpg",
+ "https://stor.picx.cx/images/2026/09/27/mvuLud.jpg",
+ "https://stor.picx.cx/images/2026/09/27/mvuyvF.jpg",
+ "https://stor.picx.cx/images/2026/09/27/mvuTGH.jpg",
+ "https://stor.picx.cx/images/2026/09/27/mvu7AP.jpg",
+ ]);
+ expect(p.tmdb).toBeNull();
+ // 文件清单不在页面上(点击后 AJAX 加载,端点未核对),暂不另发请求
+ expect(p.files).toEqual([]);
+ });
+
+ it("727077:WEB-DL 剧集,免费带到期,MediaInfo", () => {
+ const d = parse("727077");
+ expect(d).toMatchObject({
+ // h1 里「剩余时间:」文本节点不进标题
+ title: "A Prophet S01E16 2026 2160p iQIYI WEB-DL H265 HDR DDP5.1-ADWeb",
+ subtitle:
+ "我不是大师 第16集 | 类型:剧情 悬疑 | 主演:李现 李一桐 吴刚 刘冠麟 果靖霖 | 长风起 / 将相 / 江相 / 我是个算命先生",
+ category: { id: "402", name: "剧集" },
+ sizeBytes: 3575560274,
+ seeders: 319,
+ leechers: 12,
+ snatched: 407,
+ // 免费到期 2026-10-01 18:08:55(+08:00);发布时间 2026-09-30 18:08:55 不能被促销到期时间顶替
+ promotion: { type: "free", downloadFactor: 0, uploadFactor: 1, expiresAt: "2026-10-01T10:08:55.000Z" },
+ uploadedAt: "2026-09-30T10:08:55.000Z",
+ infoHash: "20183f7fe0b46b28f86a225871a2bdd0fd95e888",
+ imdbId: "tt36758489",
+ doubanId: "37297001",
+ tags: ["官方", "国语", "中字", "HDR10"],
+ });
+ const p = d.page!;
+ expect(p.mediaInfo).toMatch(/^General\nUnique ID {32}: 274676047450136587438060874497470881694/);
+ expect(p.mediaInfo).toContain(
+ "\nVideo\nID : 1\nFormat : HEVC\n",
+ );
+ // MediaInfo 控件的标题栏(文件名)与摘要表也不留在简介里
+ expect(p.descriptionText).not.toMatch(/GeneralVideoAudio|Unique ID|\.mkv$/m);
+ expect(p.descriptionText).toMatch(/^引用\n请不要转发此种至PTTime/);
+ expect(p.screenshots).toHaveLength(6);
+ expect(p.screenshots.some((u) => /poster|banner/.test(u))).toBe(false);
+ });
+});
diff --git a/packages/adapters/test/differential/indexer.json b/packages/adapters/test/differential/indexer.json
index b1664674..a074fff8 100644
--- a/packages/adapters/test/differential/indexer.json
+++ b/packages/adapters/test/differential/indexer.json
@@ -40,7 +40,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -339,11 +339,11 @@
"source": "definition",
"id": "audiences",
"file": "packages/adapters/src/sites/definitions/audiences.json",
- "sha256": "fcf0aa51d84d2a192a149b802613f79163bc961f97fc8e576288627cb3d1bb08"
+ "sha256": "eff11683d4ac0a7c75abf8eea2ffa9dbc5fc44adbf83e05638b3f82de0d85b5b"
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -546,7 +546,7 @@
"source": "definition",
"id": "audiences",
"file": "packages/adapters/src/sites/definitions/audiences.json",
- "sha256": "fcf0aa51d84d2a192a149b802613f79163bc961f97fc8e576288627cb3d1bb08"
+ "sha256": "eff11683d4ac0a7c75abf8eea2ffa9dbc5fc44adbf83e05638b3f82de0d85b5b"
},
"input": {
"file": "inputs/indexer/tk-hhanclub-list.html.txt",
@@ -571,7 +571,7 @@
"source": "definition",
"id": "audiences",
"file": "packages/adapters/src/sites/definitions/audiences.json",
- "sha256": "fcf0aa51d84d2a192a149b802613f79163bc961f97fc8e576288627cb3d1bb08"
+ "sha256": "eff11683d4ac0a7c75abf8eea2ffa9dbc5fc44adbf83e05638b3f82de0d85b5b"
},
"input": {
"file": "inputs/indexer/tk-chdbits-list.html.txt",
@@ -674,7 +674,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -991,7 +991,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -1280,7 +1280,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -1583,7 +1583,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -1886,7 +1886,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -2203,7 +2203,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -2278,7 +2278,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -2581,7 +2581,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -2852,11 +2852,11 @@
"source": "definition",
"id": "hhanclub",
"file": "packages/adapters/src/sites/definitions/hhanclub.json",
- "sha256": "a15c4b4496a22368c85b7cba5bada167c2cc191893b632cbfda4ce9627a161d5"
+ "sha256": "7b765193b1298f7b319f9598cd88d3aadb507c6cf4e0bf9f42406ea0799cfac3"
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -2877,7 +2877,7 @@
"source": "definition",
"id": "hhanclub",
"file": "packages/adapters/src/sites/definitions/hhanclub.json",
- "sha256": "a15c4b4496a22368c85b7cba5bada167c2cc191893b632cbfda4ce9627a161d5"
+ "sha256": "7b765193b1298f7b319f9598cd88d3aadb507c6cf4e0bf9f42406ea0799cfac3"
},
"input": {
"file": "inputs/indexer/tk-hhanclub-list.html.txt",
@@ -2970,7 +2970,7 @@
"source": "definition",
"id": "hhanclub",
"file": "packages/adapters/src/sites/definitions/hhanclub.json",
- "sha256": "a15c4b4496a22368c85b7cba5bada167c2cc191893b632cbfda4ce9627a161d5"
+ "sha256": "7b765193b1298f7b319f9598cd88d3aadb507c6cf4e0bf9f42406ea0799cfac3"
},
"input": {
"file": "inputs/indexer/tk-chdbits-list.html.txt",
@@ -2999,7 +2999,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -3302,7 +3302,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -3619,7 +3619,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -3936,7 +3936,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -4239,7 +4239,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -4528,7 +4528,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -4831,7 +4831,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -5120,7 +5120,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -5423,7 +5423,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -5740,7 +5740,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -6017,7 +6017,7 @@
},
"input": {
"file": "inputs/indexer/tk-audiences-list.html.txt",
- "sha256": "c9f4e36ea44b1fddb36a90c9906f90e2e303413247efb487f8d296f143ed8f5f"
+ "sha256": "dbac4c78c5cb9611dc6b23fffa683b8ffdc6dc55ebf35f66058efa9cd1b3196e"
},
"query": {
"keyword": "x",
@@ -6088,11 +6088,11 @@
"source": "definition",
"id": "audiences",
"file": "packages/adapters/src/sites/definitions/audiences.json",
- "sha256": "fcf0aa51d84d2a192a149b802613f79163bc961f97fc8e576288627cb3d1bb08"
+ "sha256": "eff11683d4ac0a7c75abf8eea2ffa9dbc5fc44adbf83e05638b3f82de0d85b5b"
},
"input": {
"file": "inputs/indexer/tk-audiences-login.html.txt",
- "sha256": "e97696e6b9b1bfd5b14a7e16b18ca1e0cb98d553ad6363bb8d70a557741de92c"
+ "sha256": "211059cfefde33331b8e04482ebf8c6d50a591b389eb28fc6a7d53fa3c31182d"
},
"query": {
"keyword": "x",
@@ -6132,6 +6132,393 @@
"torrents": []
}
},
+ {
+ "id": "audiences@rl-audiences-list",
+ "definition": {
+ "source": "definition",
+ "id": "audiences",
+ "file": "packages/adapters/src/sites/definitions/audiences.json",
+ "sha256": "eff11683d4ac0a7c75abf8eea2ffa9dbc5fc44adbf83e05638b3f82de0d85b5b"
+ },
+ "input": {
+ "file": "inputs/indexer/rl-audiences-list.html.txt",
+ "sha256": "cc4ce084fdf1a8f514cfad312a92db71049f22b347c1b8d1fcc9f49e70c8ee82"
+ },
+ "query": {
+ "keyword": "x",
+ "search_type": "torrents"
+ },
+ "mp": {
+ "is_error": false,
+ "torrents": [
+ {
+ "title": "Only Bears 2026 1080p Creator Provided AAC AV1-PosiBear",
+ "description": "何止是熊 | 2026年熊族浪漫喜剧短片 | 标签:男同性恋 / 熊 / 浪漫",
+ "page_url": "https://audiences.me/details.php?id=727091&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=727091",
+ "grabs": 0,
+ "peers": 93,
+ "seeders": 1,
+ "size": 503222108,
+ "imdbid": null,
+ "downloadvolumefactor": 0,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-30 19:31:59",
+ "date_elapsed": "38分",
+ "freedate": null,
+ "labels": ["原创", "中字", "首发"],
+ "category": "电影"
+ },
+ {
+ "title": "Leviticus 2026 1080p BluRay AVC DTS-HD MA 5.1-DIY@Audies",
+ "description": "利未记 / 爱的魔样(台) / 少年同志驱魔记(港)【原盘DIY 简繁双语字幕】",
+ "page_url": "https://audiences.me/details.php?id=726624&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=726624",
+ "grabs": 199,
+ "peers": 5,
+ "seeders": 121,
+ "size": 24953759990,
+ "imdbid": "tt39143902",
+ "downloadvolumefactor": 0,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-29 17:56:48",
+ "date_elapsed": "1天\n2时",
+ "freedate": null,
+ "labels": ["官方", "中字", "官字组", "DIY"],
+ "category": "电影"
+ },
+ {
+ "title": "A New Dawn 2026 1080p BluRay Remux AVC DTS-HD MA 5.1-Nest@ADE",
+ "description": "花绿青绽放之日 / 花绿青绽放之时(港 / 台) / 新的黎明 / A New Dawn / Une aube nouvelle / 花緑青が明ける日に [中日双语字幕]",
+ "page_url": "https://audiences.me/details.php?id=726336&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=726336",
+ "grabs": 320,
+ "peers": 0,
+ "seeders": 142,
+ "size": 23643794964,
+ "imdbid": null,
+ "downloadvolumefactor": 0.5,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-29 00:20:16",
+ "date_elapsed": "1天\n19时",
+ "freedate": "50%",
+ "labels": ["官方", "中字", "官字组", "动画", "禁转", "应求"],
+ "category": "电影"
+ },
+ {
+ "title": "Out of the Past 1947 2160p UHD BluRay HEVC DV DTS-HD MA 2.0-Runrun@Audies",
+ "description": "漩涡之外 / 旧恨新欢(港) / 来自过去 / Build My Gallows High【IG菜单新增按钮】【华纳WAC版UHD原盘DIY/简繁双语中字】",
+ "page_url": "https://audiences.me/details.php?id=726249&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=726249",
+ "grabs": 208,
+ "peers": 0,
+ "seeders": 112,
+ "size": 63093069578,
+ "imdbid": "tt0039689",
+ "downloadvolumefactor": 0.5,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-28 21:28:11",
+ "date_elapsed": "1天\n22时",
+ "freedate": "50%",
+ "labels": ["官方", "中字", "官字组", "DIY", "Dolby Vision", "HDR10"],
+ "category": "电影"
+ },
+ {
+ "title": "The Invite 2026 1080p BluRay AVC TrueHD 5.1-DIY@Audies",
+ "description": "激情邀约 / 诱人饭聚(港) / 开放性邀请(台) / 多愁善感(美版) / 邀请【原盘DIY 简繁双语字幕】",
+ "page_url": "https://audiences.me/details.php?id=726196&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=726196",
+ "grabs": 246,
+ "peers": 0,
+ "seeders": 119,
+ "size": 30300994273,
+ "imdbid": "tt14173636",
+ "downloadvolumefactor": 0.5,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-28 15:29:02",
+ "date_elapsed": "2天\n4时",
+ "freedate": "50%",
+ "labels": ["官方", "中字", "官字组", "DIY", "禁转"],
+ "category": "电影"
+ },
+ {
+ "title": "Rose 2026 1080p BluRay AVC DTS-HD MA 5.1-DIY@Audies",
+ "description": "罗斯 / 扑朔迷离的露丝(港)【德版原盘DIY 简繁字幕】",
+ "page_url": "https://audiences.me/details.php?id=725858&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=725858",
+ "grabs": 276,
+ "peers": 1,
+ "seeders": 126,
+ "size": 23654532383,
+ "imdbid": "tt25399154",
+ "downloadvolumefactor": 0.5,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-27 17:25:07",
+ "date_elapsed": "3天",
+ "freedate": "50%",
+ "labels": ["官方", "中字", "官字组", "DIY", "禁转"],
+ "category": "电影"
+ },
+ {
+ "title": "Passenger 2026 1080p GBR BluRay AVC TrueHD 7.1 Atmos-DIY@Audies",
+ "description": "鬼上车 / 乘吓(港) / 亡命旅人 【英版原盘DIY/双语简繁字幕】",
+ "page_url": "https://audiences.me/details.php?id=725737&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=725737",
+ "grabs": 197,
+ "peers": 0,
+ "seeders": 85,
+ "size": 30161407836,
+ "imdbid": "tt33763941",
+ "downloadvolumefactor": 0.5,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-27 10:52:51",
+ "date_elapsed": "3天",
+ "freedate": "50%",
+ "labels": ["官方", "中字", "官字组", "DIY"],
+ "category": "电影"
+ },
+ {
+ "title": "The Spirealm Voyager S01E06 2026 2160p WEB-DL H265 DDP2.0-ADWeb",
+ "description": "[国创连载] 灵境行者 第06集 | 类型:动作 动画 奇幻 | 灵境行者之夜游神篇",
+ "page_url": "https://audiences.me/details.php?id=727090&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=727090",
+ "grabs": 364,
+ "peers": 16,
+ "seeders": 290,
+ "size": 694713057,
+ "imdbid": null,
+ "downloadvolumefactor": 0,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-30 19:31:15",
+ "date_elapsed": "39分",
+ "freedate": null,
+ "labels": ["官方", "国语", "中字", "动画"],
+ "category": "电视剧"
+ },
+ {
+ "title": "A Prophet S01E16 2026 2160p iQIYI WEB-DL H265 HDR DDP5.1-ADWeb",
+ "description": "我不是大师 第16集 | 类型:剧情 悬疑 | 主演:李现 李一桐 吴刚 刘冠麟 果靖霖 | 长风起 / 将相 / 江相 / 我是个算命先生",
+ "page_url": "https://audiences.me/details.php?id=727077&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=727077",
+ "grabs": 406,
+ "peers": 13,
+ "seeders": 318,
+ "size": 3575560274,
+ "imdbid": null,
+ "downloadvolumefactor": 0,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-30 18:08:55",
+ "date_elapsed": "2时\n1分",
+ "freedate": null,
+ "labels": ["官方", "国语", "中字", "HDR10"],
+ "category": "电视剧"
+ },
+ {
+ "title": "Heart Signal S09E34 2026 1080p WEB-DL H265 AAC-ADWeb",
+ "description": "心动的信号 第九季 正片 第10期下:最终抉择,玥儿做出最终决定 | 心动的信号9",
+ "page_url": "https://audiences.me/details.php?id=727036&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=727036",
+ "grabs": 139,
+ "peers": 3,
+ "seeders": 78,
+ "size": 558083604,
+ "imdbid": null,
+ "downloadvolumefactor": 0,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-30 17:01:46",
+ "date_elapsed": "3时\n9分",
+ "freedate": null,
+ "labels": ["官方", "国语", "中字"],
+ "category": "电视剧"
+ }
+ ]
+ },
+ "mp_rust": {
+ "is_error": false,
+ "torrents": [
+ {
+ "title": "Only Bears 2026 1080p Creator Provided AAC AV1-PosiBear",
+ "description": "何止是熊 | 2026年熊族浪漫喜剧短片 | 标签:男同性恋 / 熊 / 浪漫",
+ "page_url": "https://audiences.me/details.php?id=727091&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=727091",
+ "grabs": 0,
+ "peers": 93,
+ "seeders": 1,
+ "size": 503222108,
+ "imdbid": null,
+ "downloadvolumefactor": 0,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-30 19:31:59",
+ "date_elapsed": "38分",
+ "freedate": null,
+ "labels": ["原创", "中字", "首发"],
+ "category": "电影"
+ },
+ {
+ "title": "Leviticus 2026 1080p BluRay AVC DTS-HD MA 5.1-DIY@Audies",
+ "description": "利未记 / 爱的魔样(台) / 少年同志驱魔记(港)【原盘DIY 简繁双语字幕】",
+ "page_url": "https://audiences.me/details.php?id=726624&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=726624",
+ "grabs": 199,
+ "peers": 5,
+ "seeders": 121,
+ "size": 24953759990,
+ "imdbid": "tt39143902",
+ "downloadvolumefactor": 0,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-29 17:56:48",
+ "date_elapsed": "1天\n2时",
+ "freedate": null,
+ "labels": ["官方", "中字", "官字组", "DIY"],
+ "category": "电影"
+ },
+ {
+ "title": "A New Dawn 2026 1080p BluRay Remux AVC DTS-HD MA 5.1-Nest@ADE",
+ "description": "花绿青绽放之日 / 花绿青绽放之时(港 / 台) / 新的黎明 / A New Dawn / Une aube nouvelle / 花緑青が明ける日に [中日双语字幕]",
+ "page_url": "https://audiences.me/details.php?id=726336&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=726336",
+ "grabs": 320,
+ "peers": 0,
+ "seeders": 142,
+ "size": 23643794964,
+ "imdbid": null,
+ "downloadvolumefactor": 0.5,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-29 00:20:16",
+ "date_elapsed": "1天\n19时",
+ "freedate": "50%",
+ "labels": ["官方", "中字", "官字组", "动画", "禁转", "应求"],
+ "category": "电影"
+ },
+ {
+ "title": "Out of the Past 1947 2160p UHD BluRay HEVC DV DTS-HD MA 2.0-Runrun@Audies",
+ "description": "漩涡之外 / 旧恨新欢(港) / 来自过去 / Build My Gallows High【IG菜单新增按钮】【华纳WAC版UHD原盘DIY/简繁双语中字】",
+ "page_url": "https://audiences.me/details.php?id=726249&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=726249",
+ "grabs": 208,
+ "peers": 0,
+ "seeders": 112,
+ "size": 63093069578,
+ "imdbid": "tt0039689",
+ "downloadvolumefactor": 0.5,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-28 21:28:11",
+ "date_elapsed": "1天\n22时",
+ "freedate": "50%",
+ "labels": ["官方", "中字", "官字组", "DIY", "Dolby Vision", "HDR10"],
+ "category": "电影"
+ },
+ {
+ "title": "The Invite 2026 1080p BluRay AVC TrueHD 5.1-DIY@Audies",
+ "description": "激情邀约 / 诱人饭聚(港) / 开放性邀请(台) / 多愁善感(美版) / 邀请【原盘DIY 简繁双语字幕】",
+ "page_url": "https://audiences.me/details.php?id=726196&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=726196",
+ "grabs": 246,
+ "peers": 0,
+ "seeders": 119,
+ "size": 30300994273,
+ "imdbid": "tt14173636",
+ "downloadvolumefactor": 0.5,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-28 15:29:02",
+ "date_elapsed": "2天\n4时",
+ "freedate": "50%",
+ "labels": ["官方", "中字", "官字组", "DIY", "禁转"],
+ "category": "电影"
+ },
+ {
+ "title": "Rose 2026 1080p BluRay AVC DTS-HD MA 5.1-DIY@Audies",
+ "description": "罗斯 / 扑朔迷离的露丝(港)【德版原盘DIY 简繁字幕】",
+ "page_url": "https://audiences.me/details.php?id=725858&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=725858",
+ "grabs": 276,
+ "peers": 1,
+ "seeders": 126,
+ "size": 23654532383,
+ "imdbid": "tt25399154",
+ "downloadvolumefactor": 0.5,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-27 17:25:07",
+ "date_elapsed": "3天",
+ "freedate": "50%",
+ "labels": ["官方", "中字", "官字组", "DIY", "禁转"],
+ "category": "电影"
+ },
+ {
+ "title": "Passenger 2026 1080p GBR BluRay AVC TrueHD 7.1 Atmos-DIY@Audies",
+ "description": "鬼上车 / 乘吓(港) / 亡命旅人 【英版原盘DIY/双语简繁字幕】",
+ "page_url": "https://audiences.me/details.php?id=725737&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=725737",
+ "grabs": 197,
+ "peers": 0,
+ "seeders": 85,
+ "size": 30161407836,
+ "imdbid": "tt33763941",
+ "downloadvolumefactor": 0.5,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-27 10:52:51",
+ "date_elapsed": "3天",
+ "freedate": "50%",
+ "labels": ["官方", "中字", "官字组", "DIY"],
+ "category": "电影"
+ },
+ {
+ "title": "The Spirealm Voyager S01E06 2026 2160p WEB-DL H265 DDP2.0-ADWeb",
+ "description": "[国创连载] 灵境行者 第06集 | 类型:动作 动画 奇幻 | 灵境行者之夜游神篇",
+ "page_url": "https://audiences.me/details.php?id=727090&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=727090",
+ "grabs": 364,
+ "peers": 16,
+ "seeders": 290,
+ "size": 694713057,
+ "imdbid": null,
+ "downloadvolumefactor": 0,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-30 19:31:15",
+ "date_elapsed": "39分",
+ "freedate": null,
+ "labels": ["官方", "国语", "中字", "动画"],
+ "category": "电视剧"
+ },
+ {
+ "title": "A Prophet S01E16 2026 2160p iQIYI WEB-DL H265 HDR DDP5.1-ADWeb",
+ "description": "我不是大师 第16集 | 类型:剧情 悬疑 | 主演:李现 李一桐 吴刚 刘冠麟 果靖霖 | 长风起 / 将相 / 江相 / 我是个算命先生",
+ "page_url": "https://audiences.me/details.php?id=727077&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=727077",
+ "grabs": 406,
+ "peers": 13,
+ "seeders": 318,
+ "size": 3575560274,
+ "imdbid": null,
+ "downloadvolumefactor": 0,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-30 18:08:55",
+ "date_elapsed": "2时\n1分",
+ "freedate": null,
+ "labels": ["官方", "国语", "中字", "HDR10"],
+ "category": "电视剧"
+ },
+ {
+ "title": "Heart Signal S09E34 2026 1080p WEB-DL H265 AAC-ADWeb",
+ "description": "心动的信号 第九季 正片 第10期下:最终抉择,玥儿做出最终决定 | 心动的信号9",
+ "page_url": "https://audiences.me/details.php?id=727036&hit=1",
+ "enclosure": "https://audiences.me/download.php?id=727036",
+ "grabs": 139,
+ "peers": 3,
+ "seeders": 78,
+ "size": 558083604,
+ "imdbid": null,
+ "downloadvolumefactor": 0,
+ "uploadvolumefactor": 1,
+ "pubdate": "2026-09-30 17:01:46",
+ "date_elapsed": "3时\n9分",
+ "freedate": null,
+ "labels": ["官方", "国语", "中字"],
+ "category": "电视剧"
+ }
+ ]
+ }
+ },
{
"id": "hw-dsl-case@hw-dsl-rows",
"definition": {
diff --git a/packages/adapters/test/differential/inputs/detail/rl-audiences-detail-725858.html.txt b/packages/adapters/test/differential/inputs/detail/rl-audiences-detail-725858.html.txt
new file mode 100644
index 00000000..821c5583
--- /dev/null
+++ b/packages/adapters/test/differential/inputs/detail/rl-audiences-detail-725858.html.txt
@@ -0,0 +1,164 @@
+
+观众 :: 种子详情 - Powered by NexusPHP
+
+
Rose 2026 1080p BluRay AVC DTS-HD MA 5.1-DIY@Audies [50% ]
+
+下载 [Audiences].Rose.2026.1080p.BluRay.AVC.DTS-HD.MA.5.1-DIY@Audies.torrent 由 匿名 发布于3天前 副标题 罗斯 / 扑朔迷离的露丝(港)【德版原盘DIY 简繁字幕】
+基本信息 大小: 22.03 GB 类型: 电影 媒介: Blu-ray DIY 编码: H.264(AVC) 分辨率: 1080p 音频编码: DTS-HD MA 制作组: Audies126 1 276
+标签 官方 中字 官字组 DIY 禁转
+
+
+
+ 豆瓣信息
+ IMDb信息
+ 简介
引用 声明:
+Audiences提供的所有资源均是在网上搜集且由用户上传,不可用于任何形式的商业盈利活动!
+否则产生的一切后果将由您自己承担,本站将不对本站的任何内容负任何法律责任!
+该内容仅做为宽带测试、学习交流等使用,请在下载后24小时内尽快删除。
+如喜欢该作品请支持正版!正版!
+ 引用 1.原盘来自@4FR,字幕OCR自微博@乱耳飞舞字幕组。
+2.由BelamiMN@Audiences完成字幕逐句精校。
+3.追加简繁共两个字幕,右键调出。
+4.PowerDVD20,PotPlayer测试播放通过,其他播放器自行测试。
+5.在此感谢各位原作者及分享者!如有侵权请联系删除
+
+
+
+◎译 名 罗斯 / 扑朔迷离的露丝(港)
+◎片 名 Rose
+◎年 代 2026
+◎产 地 奥地利 / 德国
+◎类 别 剧情
+◎语 言 德语
+◎上映日期 2026-04-30(德国) / 2026-04-17(奥地利) / 2026-02-15(柏林电影节)
+◎IMDb链接
https://www.imdb.com/title/tt25399154/
+◎豆瓣评分 8.7/10 from 7625 users
+◎豆瓣链接
https://movie.douban.com/subject/35322132/
+◎导 演 马库斯·施莱泽 Markus Schleinzer
+◎编 剧 马库斯·施莱泽 Markus Schleinzer / 亚历山大·布罗姆 Alexander Brom
+◎主 演 桑德拉·惠勒 Sandra Hüller
+ 卡洛·布劳恩 Caro Braun
+ 玛丽莎·格罗瓦尔德 Marisa Growaldt
+ 戈德哈尔德·吉瑟 Godehard Giese
+ 奥古斯丁诺·伦肯 Augustino Renken
+ 罗伯特·格维斯戴克 Robert Gwisdek
+ 玛丽亚·德拉格斯 Maria Dragus
+ 斯文·埃里克·贝希托夫 Sven Eric Bechtolf
+ 赖纳·埃格 Rainer Egger
+ Maurice Leonhard Maurice Leonhard
+ Annalisa Hohl Annalisa Hohl
+ Anne Klein Anne Klein
+ 巴斯蒂安·特罗斯特 Bastian Trost
+
+◎标 签 女性 | 历史
+
+◎简 介
+
+ 故事取材于欧洲历史上多起女扮男装的真实事件。十七世纪早期,三十年战争结束后,一个女人伪装成一名叫“罗斯”的士兵出现在一个偏僻的新教村庄,声称自己是一座荒废庄园的继承者,努力获得村民的接纳。
+
+
+
+DISC INFO:
+
+Disc Label: Rose.2026.1080p.BluRay.AVC.DTS-HD.MA.5.1-DIY@Audies
+Disc Size: 23,653,376,335 bytes
+Protection: AACS
+Extras: BD-Java
+BDInfo: 0.7.5.8
+
+PLAYLIST REPORT:
+
+Name: 00008.MPLS
+Length: 1:34:13.250 (h:m:s.ms)
+Size: 23,379,640,320 bytes
+Total Bitrate: 33.08 Mbps
+
+VIDEO:
+
+Codec Bitrate Description
+----- ------- -----------
+MPEG-4 AVC Video 27450 kbps 1080p / 24 fps / 16:9 / High Profile 4.1
+
+AUDIO:
+
+Codec Language Bitrate Description
+----- -------- ------- -----------
+DTS-HD Master Audio German 1759 kbps 5.1 / 48 kHz / 1759 kbps / 16-bit (DTS Core: 5.1 / 48 kHz / 1509 kbps / 16-bit)
+DTS-HD Master Audio German 821 kbps 2.0 / 48 kHz / 821 kbps / 16-bit (DTS Core: 2.0 / 48 kHz / 512 kbps / 16-bit)
+DTS-HD Master Audio German 776 kbps 2.0 / 48 kHz / 776 kbps / 16-bit (DTS Core: 2.0 / 48 kHz / 256 kbps / 16-bit)
+
+SUBTITLES:
+
+Codec Language Bitrate Description
+----- -------- ------- -----------
+Presentation Graphics German 24.351 kbps
+Presentation Graphics Chinese 15.778 kbps 简体中文
+Presentation Graphics Chinese 15.892 kbps 繁体中文
+
+
+
+DISC INFO:
+
+Disc Label: Rose.2026.1080p.BluRay.AVC.DTS-HD.MA.5.1-DIY@Audies
+Disc Size: 23,653,376,335 bytes
+Protection: AACS
+Extras: BD-Java
+BDInfo: 0.7.5.8
+
+PLAYLIST REPORT:
+
+Name: 00008.MPLS
+Length: 1:34:13.250 (h:m:s.ms)
+Size: 23,379,640,320 bytes
+Total Bitrate: 33.08 Mbps
+
+VIDEO:
+
+Codec Bitrate Description
+----- ------- -----------
+MPEG-4 AVC Video 27450 kbps 1080p / 24 fps / 16:9 / High Profile 4.1
+
+AUDIO:
+
+Codec Language Bitrate Description
+----- -------- ------- -----------
+DTS-HD Master Audio German 1759 kbps 5.1 / 48 kHz / 1759 kbps / 16-bit (DTS Core: 5.1 / 48 kHz / 1509 kbps / 16-bit)
+DTS-HD Master Audio German 821 kbps 2.0 / 48 kHz / 821 kbps / 16-bit (DTS Core: 2.0 / 48 kHz / 512 kbps / 16-bit)
+DTS-HD Master Audio German 776 kbps 2.0 / 48 kHz / 776 kbps / 16-bit (DTS Core: 2.0 / 48 kHz / 256 kbps / 16-bit)
+
+SUBTITLES:
+
+Codec Language Bitrate Description
+----- -------- ------- -----------
+Presentation Graphics German 24.351 kbps
+Presentation Graphics Chinese 15.778 kbps 简体中文
+Presentation Graphics Chinese 15.892 kbps 繁体中文
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 其它版本 6个其它版本
+种子文件
+热度表
+查看: 741
+点击: 553
+完成: 276
+最近活动: 3分前
+
+
+
+
+
+
diff --git a/packages/adapters/test/differential/inputs/detail/rl-audiences-detail-727077.html.txt b/packages/adapters/test/differential/inputs/detail/rl-audiences-detail-727077.html.txt
new file mode 100644
index 00000000..12e83cd8
--- /dev/null
+++ b/packages/adapters/test/differential/inputs/detail/rl-audiences-detail-727077.html.txt
@@ -0,0 +1,192 @@
+
+观众 :: 种子详情 - Powered by NexusPHP
+
+
A Prophet S01E16 2026 2160p iQIYI WEB-DL H265 HDR DDP5.1-ADWeb [免费 ] 剩余时间:21时57分
+
+下载 [Audiences].我不是大师.A.Prophet.S01.2026.2160p.iQIYI.WEB-DL.H265.HDR.DDP5.1-ADWeb.torrent 由 匿名 发布于2时2分前 副标题 我不是大师 第16集 | 类型:剧情 悬疑 | 主演:李现 李一桐 吴刚 刘冠麟 果靖霖 | 长风起 / 将相 / 江相 / 我是个算命先生
+基本信息 大小: 3.33 GB 类型: 剧集 媒介: WEB-DL 编码: H.265(HEVC) 分辨率: 4K 音频编码: DD/AC3 制作组: ADWeb 季: 1 集: 16319 12 407
+标签 官方 国语 中字 HDR10
+
+
+
+ 豆瓣信息
+ IMDb信息
+ 简介
引用 声明:
+Audiences提供的所有资源均是在网上搜集且由用户上传,不可用于任何形式的商业盈利活动!
+否则产生的一切后果将由您自己承担,本站将不对本站的任何内容负任何法律责任!
+该内容仅做为宽带测试、学习交流等使用,请在下载后24小时内尽快删除。
+如喜欢该作品请支持正版!正版!
+ 引用 请不要转发此种至PTTime,谢谢!祝您观影愉快!
+If you support us, For safety reason, Please do not forward this torrent to PTTime, Thanks! Enjoy your movie!
+
+◎译 名 长风起 / 将相 / 江相 / 我是个算命先生
+◎片 名 我不是大师
+◎年 代 2026
+◎产 地 中国大陆
+◎类 别 剧情 / 悬疑
+◎语 言 汉语普通话
+◎上映日期 2026-09-24(中国大陆)
+◎IMDb链接
https://www.imdb.com/title/tt36758489/
+◎豆瓣评分 /10 from 0 users
+◎豆瓣链接
https://movie.douban.com/subject/37297001/
+◎集 数 24
+◎片 长 45分钟
+◎导 演 杨磊 Lei Yang
+◎编 剧 杨哲 Zhe Yang /
+◎主 演 李现 Xian Li
+ 李一桐 Yitong Li
+ 吴刚 Gang Wu
+ 刘冠麟 Guanlin Liu
+ 果靖霖 Jinglin Guo
+ 海一天 Yitian Hai
+ 梁天 Tian Liang
+ 岳旸 Yang Yue
+ 杨一威 Yiwei Yang
+ 张晶伟 Jingwei Zhang
+ 张晓谦 Xiaoqian Zhang
+ 黄小蕾 Xiaolei Huang
+ 薛佳凝 Jianing Xue
+ 罗海琼 Haiqiong Luo
+ 王沛禄 Peilu Wang
+ 魏千翔 Qianxiang Wei
+ 艾晓琪 Xiaoqi Ai
+ 宋奕星 Xinjiayi Song
+ 王奕婷 Yiting Wang
+ 刘美含 Meihan Liu
+ 黄奕 Yi Huang
+ 徐洋 Yang Xu
+ 孙爽 Shuang Sun
+ 李殿尊 Dianzun Li
+ 艾霖 Lin Ai
+
+◎简 介
+
+ 上官诚明全家惨遭天相派设局灭门,他化名潜伏,以牙还牙,以眼还眼,联手身陷泥淖却势要逆天改命的江飞燕,用连环骗局反噬仇敌,一路揭露骗术黑幕,最终瓦解天相派,解救受控者,惩恶扬善终彰善念。
+
+ 改编自易之小说《我是个算命先生》。
+
+
+
引用 ★★★★★ General Information ★★★★★
+
+RELEASE.NAME...: 我不是大师.A.Prophet.S01E16.2026.2160p.iQIYI.WEB-DL.H265.HDR.DDP5.1-ADWeb
+RELEASE.TIME...: 2026-09-30 10:08:19.286 UTC
+RELEASE.SIZE...: 3.33 GiB
+RELEASE.FORMAT.: Matroska
+DURATION.......: 00:44:30.991 (HH:MM:SS.MMM)
+OVERALL.BITRATE: 10.7 Mb/s
+VIDEO.CODEC....: HEVC Main 10@L5@Main @ 10.3 Mb/s
+FRAME.RATE.....: 25.000 FPS
+RESOLUTION.....: 3840 x 1920
+HDR.FORMAT.....: HDR Vivid, Version 1
+AUDIO.1........: 普通话 DDP Dolby Digital Plus 6 channels @ 256 kb/s
+AUDIO.2........: 普通话 AAC AAC 2 channels @ 192 kb/s
+UPLOADER.......: ADWeb
+
+
+
+
+
+
+
+General Video Audio(2) Subtitles Format: Matroska Duration: 44 min 30 s File Size: 3.33 GiB Bit Rate: 10.7mb/s Format: HEVC (10 bits) Bit Rate: 10.3mb/s Resolution: 3840*1920 (2.000) Frame Rate: 25.000 FPS HDR: HDR Vivid, Version 1 Chinese (China) E-AC-3 5.1ch @ 256kb/s (普通话 DDP) Chinese (China) AAC LC 2.0ch @ 192kb/s (普通话 AAC)
General
+Unique ID : 274676047450136587438060874497470881694 (0xCEA4AEB6F1D87A4D7C1CEDF56399DF9E)
+Complete name : 我不是大师.A.Prophet.S01E16.2026.2160p.iQIYI.WEB-DL.H265.HDR.DDP5.1-ADWeb.mkv
+Format : Matroska
+Format version : Version 4
+File size : 3.33 GiB
+Duration : 44 min 30 s
+Overall bit rate : 10.7 Mb/s
+Frame rate : 25.000 FPS
+Encoded date : 2026-09-30 10:08:15 UTC
+Writing application : mkvmerge 100.0 ('Do Hot Girls Like Chords') 64-bit
+Writing library : libebml v1.4.5 + libmatroska v1.7.1
+
+Video
+ID : 1
+Format : HEVC
+Format/Info : High Efficiency Video Coding
+Format profile : Main 10@L5@Main
+HDR format : HDR Vivid, Version 1
+Codec ID : V_MPEGH/ISO/HEVC
+Duration : 44 min 30 s
+Bit rate : 10.3 Mb/s
+Width : 3 840 pixels
+Height : 1 920 pixels
+Display aspect ratio : 2.000
+Frame rate mode : Constant
+Frame rate : 25.000 FPS
+Color space : YUV
+Chroma subsampling : 4:2:0
+Bit depth : 10 bits
+Bits/(Pixel*Frame) : 0.056
+Stream size : 3.19 GiB (96%)
+Default : Yes
+Forced : No
+Color range : Limited
+Color primaries : BT.2020
+Transfer characteristics : PQ
+Matrix coefficients : BT.2020 non-constant
+Mastering display color primaries : R: x=0.708000 y=0.292000, G: x=0.170000 y=0.797000, B: x=0.130000 y=0.046000, White point: x=0.312700 y=0.329000
+Mastering display luminance : min: 0.0001 cd/m2, max: 1000 cd/m2
+Maximum Content Light Level : 748 cd/m2
+Maximum Frame-Average Light Level : 25 cd/m2
+
+Audio #1
+ID : 2
+Format : E-AC-3
+Format/Info : Enhanced AC-3
+Commercial name : Dolby Digital Plus
+Codec ID : A_EAC3
+Duration : 44 min 30 s
+Bit rate mode : Constant
+Bit rate : 256 kb/s
+Channel(s) : 6 channels
+Channel layout : L R C LFE Ls Rs
+Sampling rate : 48.0 kHz
+Frame rate : 31.250 FPS (1536 SPF)
+Compression mode : Lossy
+Stream size : 81.5 MiB (2%)
+Title : 普通话 DDP
+Language : Chinese (China)
+Service kind : Complete Main
+Default : Yes
+Forced : No
+Dialog Normalization : -20 dB
+compr : -0.28 dB
+dialnorm_Average : -20 dB
+dialnorm_Minimum : -20 dB
+dialnorm_Maximum : -20 dB
+
+Audio #2
+ID : 3
+Format : AAC LC
+Format/Info : Advanced Audio Codec Low Complexity
+Codec ID : A_AAC-2
+Duration : 44 min 30 s
+Bit rate : 192 kb/s
+Channel(s) : 2 channels
+Channel layout : L R
+Sampling rate : 44.1 kHz
+Frame rate : 43.066 FPS (1024 SPF)
+Compression mode : Lossy
+Stream size : 60.5 MiB (2%)
+Title : 普通话 AAC
+Language : Chinese (China)
+Default : No
+Forced : No
+
+
+
+
+
+ 其它版本 46个其它版本
+种子文件
+热度表
+查看: 99
+点击: 492
+完成: 407
+最近活动: < 1分前
+
+
+
+
diff --git a/packages/adapters/test/differential/inputs/indexer/rl-audiences-list.html.txt b/packages/adapters/test/differential/inputs/indexer/rl-audiences-list.html.txt
new file mode 100644
index 00000000..211b3b92
--- /dev/null
+++ b/packages/adapters/test/differential/inputs/indexer/rl-audiences-list.html.txt
@@ -0,0 +1,203 @@
+
+观众 :: 种子 - Powered by NexusPHP
+
+
+
+ 类型
+
+
+
+
+ 标题
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+38分 479.91 MB 1
+93
+0
+匿名
+
+
+
+1天 2时 23.24 GB 121
+5
+199
+匿名
+
+
+
+1天 19时 22.02 GB 142
+0
+320
+匿名
+
+
+
+1天 22时 58.76 GB 112
+0
+208
+匿名
+
+
+
+2天 4时 28.22 GB 119
+0
+246
+匿名
+
+
+
+3天 22.03 GB 126
+1
+276
+匿名
+
+
+
+3天 28.09 GB 85
+0
+197
+匿名
+
+
+
+
+
+
+
+
+
+39分 662.53 MB 290
+16
+364
+匿名
+
+
+
+
+2时 1分 3.33 GB 318
+13
+406
+匿名
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+3时 9分 532.23 MB 78
+3
+139
+匿名
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/adapters/test/differential/inputs/indexer/tk-audiences-list.html.txt b/packages/adapters/test/differential/inputs/indexer/tk-audiences-list.html.txt
index d655a126..5f586f1b 100644
--- a/packages/adapters/test/differential/inputs/indexer/tk-audiences-list.html.txt
+++ b/packages/adapters/test/differential/inputs/indexer/tk-audiences-list.html.txt
@@ -2,11 +2,15 @@
1
类型 标题 评论 存活时间 大小 种子 下载 完成 发布者
0
@@ -20,7 +24,7 @@
0
@@ -34,7 +38,7 @@
0
@@ -62,7 +66,7 @@
0
diff --git a/packages/adapters/test/differential/inputs/indexer/tk-audiences-login.html.txt b/packages/adapters/test/differential/inputs/indexer/tk-audiences-login.html.txt
index 9bf89b86..41bf002e 100644
--- a/packages/adapters/test/differential/inputs/indexer/tk-audiences-login.html.txt
+++ b/packages/adapters/test/differential/inputs/indexer/tk-audiences-login.html.txt
@@ -1,3 +1,4 @@
观众 :: 登录
\ No newline at end of file
diff --git a/packages/testkit/src/sites/mock-nexusphp.ts b/packages/testkit/src/sites/mock-nexusphp.ts
index 2a1451cd..63d29525 100644
--- a/packages/testkit/src/sites/mock-nexusphp.ts
+++ b/packages/testkit/src/sites/mock-nexusphp.ts
@@ -106,7 +106,10 @@ function row(r: MockNexusRelease): string {
? `${esc(r.truncatedText)} `
: `${esc(r.title)} `;
const hr = r.hr ? ` ` : "";
- const sub = r.subtitle ? `${esc(r.subtitle)} ` : "";
+ // 观众真实结构(DET-2 核对):副标题行里先是标签 span,正文在 span.torrent-subtitle-text
+ const sub = r.subtitle
+ ? `${esc(r.subtitle)} `
+ : "";
return `
diff --git a/scripts/mp-sync/gen-differential.py b/scripts/mp-sync/gen-differential.py
index 267f0c0f..12b0025a 100644
--- a/scripts/mp-sync/gen-differential.py
+++ b/scripts/mp-sync/gen-differential.py
@@ -677,7 +677,9 @@ def gen_indexer(out_dir: Optional[str] = None) -> Path:
runs.append((sid, d, {"source": "definition", "id": site_id,
"file": str(f.relative_to(UMT_ROOT)), "sha256": _sha256(f.read_bytes())},
pg, {}, "x", "torrents"))
- for site_id, pg in (("audiences", "tk-audiences-login.html.txt"), ("chdbits", "tk-chdbits-login.html.txt")):
+ # rl-*:真实站点页面脱敏后的样本(DET-2 观众列表页),只配对应站点的定义
+ for site_id, pg in (("audiences", "tk-audiences-login.html.txt"), ("chdbits", "tk-chdbits-login.html.txt"),
+ ("audiences", "rl-audiences-list.html.txt")):
f, d = defs[site_id]
runs.append((f"{site_id}@{pg.removesuffix(".html.txt")}", d, {"source": "definition", "id": site_id,
"file": str(f.relative_to(UMT_ROOT)),
diff --git a/scripts/mp-sync/movieclaw-overrides/audiences.json b/scripts/mp-sync/movieclaw-overrides/audiences.json
index 25e2eba0..b41eee03 100644
--- a/scripts/mp-sync/movieclaw-overrides/audiences.json
+++ b/scripts/mp-sync/movieclaw-overrides/audiences.json
@@ -1,5 +1,5 @@
{
- "$comment": "W1-3b 手写观众定义(按 MP indexer schema;仅对 testkit mock 验证)。深合并到 MovieClaw v0.27.0 转换结果之上,本文件的值优先,MovieClaw 值只补空。 umt.detail.fields=null:保留 W1-3b rowhead 详情模型(已对 mock 验证),不采用 MovieClaw 默认逐字段选择器。",
+ "$comment": "W1-3b 手写观众定义(按 MP indexer schema;仅对 testkit mock 验证)。深合并到 MovieClaw v0.27.0 转换结果之上,本文件的值优先,MovieClaw 值只补空。 umt.detail.fields=null:保留 W1-3b rowhead 详情模型(已对 mock 验证),不采用 MovieClaw 默认逐字段选择器。 DET-2(2026-09-30 真实账号核对列表页 1 次 + 详情页 2 次):列表副标题取 span.torrent-subtitle-text(span.torrent-subtitle-line 里先是标签);详情 h1 促销是 font.free / font.halfdown 等 + 「剩余时间」span[title](umt.detail.promo);做种 / 下载 / 完成数在基本信息行右侧 #seeders / #leechers / a[title=完成](没有「同伴」行);种子 Hash 在 #copyTorrentHashLink[data-hash];上传者在 span.torrent-uploader-wrap(匿名为「匿名」);简介开头的站点声明 #ad_torrentdetail 去掉;文件清单不在页面上(点击「N个文件」后由 common.js 的 toggleFileList AJAX 加载,端点未核对),暂不另发请求。",
"id": "audiences",
"name": "观众",
"domain": "https://audiences.me/",
@@ -141,7 +141,7 @@
]
},
"description": {
- "selector": "span.torrent-subtitle-line"
+ "selector": "span.torrent-subtitle-text"
},
"labels": {
"selector": "span.tags"
@@ -159,7 +159,7 @@
}
},
"umt": {
- "version": "audiences@0.2.0-unverified",
+ "version": "audiences@0.3.0",
"kind": "nexusphp",
"displayName": "观众",
"timezone": "+08:00",
@@ -198,7 +198,54 @@
"description": "#kdescr",
"headerScope": "h1#top",
"notFoundMarkers": ["没有该ID的种子", "No torrent with ID"],
- "fields": null
+ "fields": {
+ "seeders": "#seeders",
+ "leechers": "#leechers",
+ "snatched": "a[title='完成'][href*='viewsnatches.php']",
+ "infoHash": "#copyTorrentHashLink::attr(data-hash)"
+ },
+ "promo": {
+ "downloadvolumefactor": {
+ "case": {
+ "font.free": 0,
+ "font.twoupfree": 0,
+ "font.halfdown": 0.5,
+ "font.twouphalfdown": 0.5,
+ "font.thirtypercent": 0.3,
+ "img.pro_free": 0,
+ "img.pro_free2up": 0,
+ "img.pro_50pctdown": 0.5,
+ "img.pro_50pctdown2up": 0.5,
+ "img.pro_30pctdown": 0.3,
+ "*": 1
+ }
+ },
+ "uploadvolumefactor": {
+ "case": {
+ "font.twoup": 2,
+ "font.twoupfree": 2,
+ "font.twouphalfdown": 2,
+ "img.pro_50pctdown2up": 2,
+ "img.pro_free2up": 2,
+ "img.pro_2up": 2,
+ "*": 1
+ }
+ },
+ "freedate": {
+ "selector": "img[class^=\"pro_\"], span[title]",
+ "attribute": "title",
+ "filters": [
+ {
+ "name": "re_search",
+ "args": ["\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}", 0]
+ }
+ ]
+ }
+ },
+ "page": {
+ "uploader": "span.torrent-uploader-wrap",
+ "descriptionStrip": "#ad_torrentdetail"
+ }
}
}
}