1.77.6: SERP sorry-重定向按验证码语义(不换号+15min冷却);浏览池保温释放跳过+60min持有上限;速率-门壳实验与监控工具

This commit is contained in:
BigCode
2026-10-29 15:54:37 +08:00
commit 05d9462414
25 changed files with 3973 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
/** # 保活脚本状态机测试(不走浏览器;模拟 ctx.page/gTask/adminFetch)
* 复刻节点 script.run 的管线:包函数 → Transpiler → new Function 实例化。
*/
import { readFileSync } from "node:fs";
const src = readFileSync(new URL("./admin/scripts/keepalive-flow.ts", import.meta.url), "utf8");
const wrapped = `async function __task__(ctx) {\n${src}\n}`;
const js = new Bun.Transpiler({ loader: "ts" }).transformSync(wrapped);
const factory = new Function(`${js}\nreturn __task__;`)() as (ctx: any) => Promise<unknown>;
/** # 函数 模拟页面(导航 URL 由脚本自己 goto,我们记录每次 goto 的 url) */
function mockPage(urlsOnGoto: (url: string) => string) {
let url = "about:blank";
return {
url: () => url,
viewportSize: async () => ({ width: 1280, height: 720 }),
goto: async (u: string) => { url = urlsOnGoto(u); return { url }; },
waitForLoadState: async () => { },
evaluate: async (code: string) => {
if (code.includes("sorry")) return '{"sorry":false,"captchaForm":false,"unusual":false}';
if (code.includes("closest")) return null;
if (code.includes("getBoundingClientRect")) return null;
return '{"host":"www.google.com","act":""}';
},
$$eval: async () => [],
locator: (_sel: string) => ({ first: () => ({ count: async () => 0, isVisible: async () => false, click: async () => { }, fill: async () => { } }) }),
keyboard: { type: async () => { }, press: async () => { } },
mouse: { move: async () => { }, down: async () => { }, up: async () => { }, wheel: async () => { } },
};
}
/** # 函数 构造一组可被任意改的假数据页 */
function makeCtx(opts: {
data?: any;
captchaOn?: boolean;
adminOk?: boolean;
reloadExprOn?: boolean;
}) {
return makeCtxArg(opts);
}
function makeCtxArg(opts: { data?: any; captchaOn?: boolean; adminOk?: boolean }) {
const calls: string[] = [];
let cur = JSON.parse(JSON.stringify(opts.data ?? {}));
const page = mockPage((u: string) => (u.startsWith("https://www.google.com/search") && opts.captchaOn ? "https://www.google.com/sorry/index?id=xx" : u.startsWith("https://www.google") ? "https://www.google.com/" : u));
const page2: any = page;
page2.evaluate = async (code: string) => {
if (code.includes("unusual")) return opts.captchaOn ? '{"sorry":true,"captchaForm":true,"unusual":true}' : '{"sorry":false,"captchaForm":false,"unusual":false}';
if (code.includes("closest")) return null;
return '{"host":"www.google.com","act":""}';
};
const ctx = {
profileId: "21111111",
page: page2,
sleep: async (ms: number) => { /* 不真的睡,秒进 */ },
log: (...a: any[]) => calls.push("L " + a.join(" ")),
getTaskState: async () => ({ exists: !!Object.keys(cur).length, state: null, data: cur }),
setTaskState: async (state: string, data?: any) => {
cur = { ...cur, ...(data ?? {}) };
calls.push(`S ${state} next=${cur.nextCheckAt ?? "-"}`);
return true;
},
adminFetch: async (path: string, init?: any) => {
if (opts.adminOk === false) return null;
if (path === `/api/profiles/${ctx.profileId}` && init?.method === "GET") return { ok: true, status: 200, json: { data: { name: "keepalive-8655-US", browserKind: "veil", browserTheme: "light", node: "OVH-FR", tags: ["keepalive"], notes: "ka slot=8655", proxy: { id: "68b1...uuid" }, browser: { locale: "en-US", timezone: "America/New_York" } } } };
if (path === "/api/profiles" && init?.method === "POST") return { ok: true, status: 201, json: { data: { id: "99999999" } } };
if (path === `/api/profiles/${ctx.profileId}` && init?.method === "DELETE") return { ok: true, status: 200, json: { data: null } };
return { ok: false, status: 404, json: null };
},
};
ctx.setTaskState = async (state: string, data?: any) => {
cur = { ...cur, ...(data ?? {}) };
calls.push(`S ${state} next=${cur.nextCheckAt ?? "-"}`);
return true;
};
(ctx as any).calls = calls;
(ctx as any).cur = cur;
return ctx as any;
}
/** # 用例 1:全新档案 → v1 → 等待-v2 */
{
const c = makeCtxArg({ data: {} });
const r = await factory(c);
const r1 = await factory(c);
console.log("case1 fresh:", r, "| 再跑:", r1);
console.log(" 状态轨迹:", c.calls.filter(s => s.startsWith("S ")).join(" / "));
}
/** # 用例 2:v1 已达 2h → v2 完成 */
{
const c = makeCtxArg({ data: { v1At: new Date(Date.now() - 2.2 * 3600e3).toISOString() } });
const r = await factory(c);
const r2 = await factory(c); // v2 刚完成,2h 未到
console.log("case2 v2:", r, "| 紧接:", r2);
}
/** # 用例 3:s1 全链(x/amazon/google)完成后 s2 立即跑,次日收敛 */
{
const c = makeCtxArg({ data: { v1At: new Date(Date.now() - 5 * 3600e3).toISOString(), v2At: new Date(Date.now() - 2.1 * 3600e3).toISOString(), s1At: undefined } });
// 修正:s1At 未定,先跑 s1
c.cur.s1At = undefined;
const r = await factory(c);
const r2 = await factory(c); // s2
const r3 = await factory(c); // 当日再跑 → idle-today
console.log("case3 s1:", r, "| s2:", r2, "| 同日再跑:", r3);
}
/** # 用例 4:s2 命中 CAPTCHA → 放行删建路径(admin 正常) */
{
const c = makeCtxArg({ data: { v1At: new Date(Date.now() - 3 * 3600e3).toISOString(), v2At: new Date(Date.now() - 2.2 * 3600e3).toISOString() }, captchaOn: true });
// 没写 s1At:会走 s1(cap 命中即重建)
c.cur.s1At = new Date(Date.now() - 3600e3).toISOString();
const r = await factory(c);
console.log("case4 captcha:", r, "| 状态尾:", c.calls.filter(s => s.startsWith("S ")).slice(-2).join(" / "));
}
/** # 用例 5:admin 关闭 → CAPTCHA 挂起 30 分钟 */
{
const c = makeCtxArg({ data: { v1At: new Date(Date.now() - 3 * 3600e3).toISOString(), v2At: new Date(Date.now() - 2.2 * 3600e3).toISOString(), s1At: new Date(Date.now() - 3600e3).toISOString() }, captchaOn: true, adminOk: false });
const r = await factory(c);
console.log("case5 admin-null:", r);
}