Files
links/patches/puppeteer-api/lib/turnstile-handler.js
T

91 lines
2.9 KiB
JavaScript
Raw Normal View History

2026-06-20 04:47:34 +08:00
/**
* Turnstile 内置求解:等待 widget 自动完成 + token 轮询
*/
const { detectCloudflare, isTurnstileSolved } = require('./cf-detector');
/**
* @typedef {Object} TurnstileResult
* @property {boolean} success
* @property {string} stage built_in|timeout|already_clear
* @property {number} elapsedMs
*/
/**
* 等待 Turnstile 通过(内置点击 / 自动跳转)
* @param {import('puppeteer').Page} page
* @param {{ timeoutMs?: number, pollMs?: number, useBuiltInClick?: boolean }} [options]
* @returns {Promise<TurnstileResult>}
*/
async function waitForTurnstile(page, options = {}) {
const timeoutMs = options.timeoutMs ?? 60000;
const pollMs = options.pollMs ?? 500;
const started = Date.now();
const initial = await detectCloudflare(page);
if (!initial.blocked || initial.hasCfClearance) {
return { success: true, stage: 'already_clear', elapsedMs: Date.now() - started };
}
// real browser 已开启 turnstile:true 内置点击;此处轮询等待结果
while (Date.now() - started < timeoutMs) {
if (await isTurnstileSolved(page)) {
return { success: true, stage: 'built_in', elapsedMs: Date.now() - started };
}
const state = await detectCloudflare(page);
if (!state.blocked) {
return { success: true, stage: 'built_in', elapsedMs: Date.now() - started };
}
await new Promise((r) => setTimeout(r, pollMs));
}
return { success: false, stage: 'timeout', elapsedMs: Date.now() - started };
}
/**
* 从页面提取 Turnstile sitekey
* @param {import('puppeteer').Page} page
* @returns {Promise<string|null>}
*/
async function extractTurnstileSitekey(page) {
return page.evaluate(() => {
const widget = document.querySelector('.cf-turnstile[data-sitekey], [data-sitekey]');
if (widget) {
return widget.getAttribute('data-sitekey');
}
const iframe = document.querySelector('iframe[src*="challenges.cloudflare.com"]');
if (iframe && iframe.src) {
const match = iframe.src.match(/[?&]sitekey=([^&]+)/);
if (match) return decodeURIComponent(match[1]);
}
return null;
}).catch(() => null);
}
/**
* 将 Captcha API 返回的 token 注入页面
* @param {import('puppeteer').Page} page
* @param {string} token
*/
async function injectTurnstileToken(page, token) {
await page.evaluate((turnstileToken) => {
const input = document.querySelector('[name="cf-turnstile-response"]');
if (input) {
input.value = turnstileToken;
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
}
const form = document.querySelector('form');
if (form) {
form.submit();
}
}, token);
}
module.exports = {
waitForTurnstile,
extractTurnstileSitekey,
injectTurnstileToken,
};