183 lines
5.5 KiB
JavaScript
Executable File
183 lines
5.5 KiB
JavaScript
Executable File
/**
|
||
* 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,
|
||
};
|