A2C工单修复
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
Reference in New Issue
Block a user