Files
links/patches/puppeteer-api/lib/sitekey-capture.js
T
2026-07-03 20:12:20 +08:00

183 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Turnstile sitekey 提前捕获:在 goto 前挂载监听,避免 60s 空等后 iframe 已销毁
*/
const {
extractSitekeyFromChallengeUrl,
extractSitekeyFromHtml,
extractTurnstileSitekey,
waitForTurnstileSurface,
} = require('./turnstile-handler');
/**
* @typedef {Object} SitekeyCaptureHandle
* @property {() => string|null} get
* @property {(page: import('puppeteer').Page, antiBot?: object) => Promise<string|null>} scanNow
* @property {(page: import('puppeteer').Page, antiBot?: object, waitMs?: number) => Promise<string|null>} resolveForSolver
* @property {() => void} detach
*/
/**
* 尝试从 URL 字符串解析并缓存 sitekey
* @param {string|null|undefined} url
* @param {{ sitekey: string|null, log: (msg: string) => void }} state
* @returns {string|null}
*/
function tryStoreSitekeyFromUrl(url, state) {
const key = extractSitekeyFromChallengeUrl(url || '');
if (key && !state.sitekey) {
state.sitekey = key;
state.log(`[CF] sitekey 捕获 (${key.slice(0, 12)}...) source=${url.slice(0, 80)}`);
}
return state.sitekey;
}
/**
* 在 page.goto 之前挂载 frame/response/target 监听
* @param {import('puppeteer').Page} page
* @param {import('puppeteer').Browser} [browser]
* @returns {SitekeyCaptureHandle}
*/
function attachSitekeyCapture(page, browser) {
const state = {
sitekey: null,
attached: false,
cleanups: [],
log: (msg) => console.log(msg),
};
if (page.__sitekeyCaptureHandle) {
return page.__sitekeyCaptureHandle;
}
const onFrameNavigated = (frame) => {
try {
tryStoreSitekeyFromUrl(frame.url(), state);
} catch (_) {
/* ignore */
}
};
page.on('framenavigated', onFrameNavigated);
state.cleanups.push(() => page.off('framenavigated', onFrameNavigated));
const onResponse = (response) => {
try {
tryStoreSitekeyFromUrl(response.url(), state);
} catch (_) {
/* ignore */
}
};
page.on('response', onResponse);
state.cleanups.push(() => page.off('response', onResponse));
if (browser && typeof browser.on === 'function') {
const onTargetCreated = (target) => {
try {
tryStoreSitekeyFromUrl(target.url(), state);
} catch (_) {
/* ignore */
}
};
browser.on('targetcreated', onTargetCreated);
state.cleanups.push(() => browser.off('targetcreated', onTargetCreated));
}
state.attached = true;
/** @type {SitekeyCaptureHandle} */
const handle = {
get: () => state.sitekey,
scanNow: async (scanPage, antiBot) => {
if (state.sitekey) {
return state.sitekey;
}
const frames = scanPage.frames();
for (const frame of frames) {
tryStoreSitekeyFromUrl(frame.url(), state);
if (state.sitekey) {
return state.sitekey;
}
}
const html = await scanPage.content().catch(() => '');
const fromHtml = extractSitekeyFromHtml(html);
if (fromHtml) {
state.sitekey = fromHtml;
state.log(`[CF] sitekey 从 HTML 提取 (${fromHtml.slice(0, 12)}...)`);
return fromHtml;
}
const browserRef = scanPage.browser();
if (browserRef) {
for (const target of browserRef.targets()) {
tryStoreSitekeyFromUrl(target.url(), state);
if (state.sitekey) {
return state.sitekey;
}
}
}
const configured = String(antiBot?.turnstileSitekey || '').trim();
if (configured && antiBot?.cfChallengeMode !== 'managed' && !antiBot?.cfCloudflareSolver) {
state.sitekey = configured;
state.log(`[CF] 使用 antiBot.turnstileSitekey (${configured.slice(0, 12)}...)`);
return configured;
}
return null;
},
resolveForSolver: async (scanPage, antiBot, waitMs = 25000) => {
let sitekey = await handle.scanNow(scanPage, antiBot);
if (sitekey) {
return sitekey;
}
sitekey = await waitForTurnstileSurface(scanPage, waitMs);
if (sitekey) {
state.sitekey = sitekey;
return sitekey;
}
sitekey = await extractTurnstileSitekey(scanPage);
if (sitekey) {
state.sitekey = sitekey;
return sitekey;
}
return handle.scanNow(scanPage, antiBot);
},
detach: () => {
for (const fn of state.cleanups) {
try {
fn();
} catch (_) {
/* ignore */
}
}
state.cleanups = [];
state.attached = false;
if (page.__sitekeyCaptureHandle === handle) {
delete page.__sitekeyCaptureHandle;
}
},
};
page.__sitekeyCaptureHandle = handle;
return handle;
}
/**
* 读取 page 上已挂载的 capture(无则 null
* @param {import('puppeteer').Page} page
* @returns {SitekeyCaptureHandle|null}
*/
function getSitekeyCapture(page) {
return page?.__sitekeyCaptureHandle || null;
}
module.exports = {
attachSitekeyCapture,
getSitekeyCapture,
tryStoreSitekeyFromUrl,
};