fix(sites): 观众健康检查误报未登录,健康失败原因细分(HC-1)
观众 2026 版首页没有 <a href=logout>:退出按钮在 userbar 的 data-uploader-actions JSON 里由脚本渲染,旧登录标志 a[href*="logout"] 永远不命中,健康检查报"未登录"而用户数据正常。 - audiences 定义 loginSuccess 改为 #info_block / usercp.php / logout 任一(0.2.1) - 健康检查判定细分:登录页 / 权限页 / 疑似人机验证 / 找不到登录标志(带选择器), 原始判定写入 last_health.detail,站点概览技术信息展示 - 首页只是找不到登录标志时,以 24 小时内用户数据解析成功为已登录证据(同 MP) - 新 SiteError 类 challenge(cf-mitigated: challenge),不算凭据错误;健康状态 challenge - testkit mock 增加 setHeaderLayout;加观众首页脱敏结构样本的差分测试 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
322ecb2f8c
commit
b1d30bd6ee
16 files changed
+433
-27
No files matched your search
@@ -10,6 +10,7 @@ site.baseUrl; site.cookie; // 请求 Cookie 需包含 site.cookie
|
||||
site.setLoggedIn(false); // 全站返回登录页(或 302 login.php)
|
||||
site.failNext(1, 429, "7"); // 限流 / 5xx
|
||||
site.setBrokenLayout(true); // 改版模拟
|
||||
site.setHeaderLayout("no_logout"); // 页头布局:classic / no_logout(观众 2026 版无 logout 链接)/ bare(无登录标志)
|
||||
site.infoHashOf("812345"); // 下载 .torrent 的 infohash
|
||||
```
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export {
|
||||
} from "./mock-mteam.ts";
|
||||
export {
|
||||
createMockNexusPhpSite,
|
||||
type MockHeaderLayout,
|
||||
type MockLoginGuard,
|
||||
type MockNexusPhpOptions,
|
||||
type MockNexusPhpSite,
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface MockNexusPhpOptions {
|
||||
}
|
||||
|
||||
export type MockLoginGuard = "none" | "captcha" | "two_factor" | "blocked";
|
||||
export type MockHeaderLayout = "classic" | "no_logout" | "bare";
|
||||
|
||||
export interface MockNexusPhpSite extends StartedServer {
|
||||
cookie: string;
|
||||
@@ -34,6 +35,12 @@ export interface MockNexusPhpSite extends StartedServer {
|
||||
setBrokenLayout(v: boolean): void;
|
||||
/** 切换登录关卡 */
|
||||
setLoginGuard(v: MockLoginGuard): void;
|
||||
/**
|
||||
* 已登录页头布局:classic(info_block 里有 logout 链接,缺省);
|
||||
* no_logout(观众 2026 版:退出只在 userbar 的 data-uploader-actions JSON 里,页面没有 logout 链接);
|
||||
* bare(已登录但没有 info_block / usercp / logout 任何登录标志)
|
||||
*/
|
||||
setHeaderLayout(v: MockHeaderLayout): void;
|
||||
/** takelogin.php 收到的 POST 次数 */
|
||||
readonly loginAttempts: number;
|
||||
/** 各 .torrent 的 infohash,供断言 */
|
||||
@@ -60,13 +67,30 @@ const AUDIENCES_USERBAR = `<div class="site-userbar">
|
||||
<a class="site-userbar__compact-tool" href="messages.php" title="收件箱 12/0">信</a>
|
||||
</div>`;
|
||||
|
||||
function shell(siteName: string, title: string, body: string, loggedIn: boolean): string {
|
||||
const userbar = loggedIn
|
||||
? `<div id="info_block"><a href="userdetails.php?id=10001">mockuser</a>
|
||||
/** 观众 2026 版 info_block:退出 / 控制面板在 data-uploader-actions JSON 里由脚本渲染(脱敏结构样本) */
|
||||
const AUDIENCES_NO_LOGOUT_BLOCK = `<table id="info_block"><tr><td><table><tr><td class="bottom">
|
||||
<div class="site-userbar site-userbar--torrents"><div class="site-userbar__compact-identity">
|
||||
<span class="torrent-uploader-wrap site-userbar__name" title="mockuser" data-uploader-actions="[{"href":"usercp.php","title":"控制面板"},{"href":"logout.php","title":"退出"}]"><span class="nowrap"><a href="userdetails.php?id=10001" class="VIP_Name"><b>mockuser</b></a></span></span>
|
||||
<a href="https://audiences.example/usercp.php?action=personal">设置</a></div></div>
|
||||
</td><td class="bottom"></td></tr></table></td></tr></table>`;
|
||||
|
||||
function shell(
|
||||
siteName: string,
|
||||
title: string,
|
||||
body: string,
|
||||
loggedIn: boolean,
|
||||
layout: MockHeaderLayout = "classic",
|
||||
): string {
|
||||
const userbar = !loggedIn
|
||||
? ""
|
||||
: layout === "no_logout"
|
||||
? `${AUDIENCES_NO_LOGOUT_BLOCK}${AUDIENCES_USERBAR}`
|
||||
: layout === "bare"
|
||||
? `<div class="welcome">mockuser</div>`
|
||||
: `<div id="info_block"><a href="userdetails.php?id=10001">mockuser</a>
|
||||
<span class="site-userbar__compact-metric--uploaded">12.5 TB</span>
|
||||
<span class="site-userbar__compact-metric--downloaded">3.2 TB</span>
|
||||
<a href="logout.php">退出</a></div>${AUDIENCES_USERBAR}`
|
||||
: "";
|
||||
<a href="logout.php">退出</a></div>${AUDIENCES_USERBAR}`;
|
||||
return `<!DOCTYPE html><html><head><meta charset="utf-8"><title>${esc(siteName)} :: ${esc(title)}</title></head>
|
||||
<body>${userbar}<div id="outer">${body}</div></body></html>`;
|
||||
}
|
||||
@@ -166,6 +190,7 @@ export async function createMockNexusPhpSite(opts: MockNexusPhpOptions = {}): Pr
|
||||
const username = opts.username ?? "mockuser";
|
||||
const password = opts.password ?? "mock-password";
|
||||
let broken = false;
|
||||
let layout: MockHeaderLayout = "classic";
|
||||
const failures: Array<{ status: number; retryAfter?: string }> = [];
|
||||
const torrents = new Map(releases.map((r) => [r.id, buildFakeTorrent({ name: r.title, length: 4 * 1024 * 1024 })]));
|
||||
|
||||
@@ -212,7 +237,7 @@ export async function createMockNexusPhpSite(opts: MockNexusPhpOptions = {}): Pr
|
||||
switch (url.pathname) {
|
||||
case "/index.php":
|
||||
case "/":
|
||||
return send(res, 200, shell(siteName, "首页", "<h2>最新公告</h2>", true));
|
||||
return send(res, 200, shell(siteName, "首页", "<h2>最新公告</h2>", true, layout));
|
||||
case "/userdetails.php":
|
||||
return send(res, 200, USER_DETAILS);
|
||||
case "/getusertorrentlistajax.php":
|
||||
@@ -278,6 +303,9 @@ export async function createMockNexusPhpSite(opts: MockNexusPhpOptions = {}): Pr
|
||||
setLoginGuard(v) {
|
||||
guard = v;
|
||||
},
|
||||
setHeaderLayout(v) {
|
||||
layout = v;
|
||||
},
|
||||
get loginAttempts() {
|
||||
return loginAttempts;
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user