/** * 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} scanNow * @property {(page: import('puppeteer').Page, antiBot?: object, waitMs?: number) => Promise} 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, };