Files
links/patches/puppeteer-api/lib/cf-detector.js
T
2026-07-01 01:26:27 +08:00

133 lines
4.2 KiB
JavaScript
Executable File
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.
/**
* Cloudflare / Turnstile 挑战页检测
*
* antiBot.cfClearanceRequired === falseA2C 业务域):仅真实 CF 中间页算 blocked
* 不因页面引用 turnstile.js 或缺少 cf_clearance cookie 误判。
*/
/**
* URL 是否处于 CF Managed Challenge 中间态(Whatshub 短链常见 __cf_chl_rt_tk
* @param {string} url
*/
function urlIndicatesCfChallenge(url) {
if (!url || typeof url !== 'string') {
return false;
}
return url.includes('__cf_chl_rt_tk')
|| url.includes('__cf_chl_tk')
|| url.includes('__cf_chl_f_tk')
|| url.includes('/cdn-cgi/challenge-platform')
|| url.includes('/cdn-cgi/challenge');
}
/**
* 是否要求 cf_clearance 才视为过盾完成(A2C user.a2c.chat 为 false
* @param {object|null|undefined} antiBot
*/
function isCfClearanceRequired(antiBot) {
return antiBot?.cfClearanceRequired !== false;
}
/**
* @typedef {Object} CloudflareState
* @property {boolean} blocked 是否处于 CF 挑战中
* @property {string|null} challengeType turnstile|js_challenge|unknown|null
* @property {boolean} hasCfClearance 是否已有 cf_clearance cookie
* @property {string} url 当前页面 URL
* @property {string} title 页面 title
*/
/**
* 检测页面是否被 Cloudflare 拦截
* @param {import('puppeteer').Page} page
* @param {object|null} [antiBot]
* @returns {Promise<CloudflareState>}
*/
async function detectCloudflare(page, antiBot = null) {
const url = page.url();
const title = await page.title().catch(() => '');
const cookies = await page.cookies().catch(() => []);
const hasCfClearance = cookies.some((c) => c.name === 'cf_clearance');
const domSignals = await page.evaluate(() => {
const hasTurnstileInput = !!document.querySelector('[name="cf-turnstile-response"]');
const hasTurnstileWidget = !!document.querySelector('.cf-turnstile, [data-sitekey]');
const hasChallengeRunning = !!document.querySelector('#challenge-running, #cf-challenge-running');
const titleText = document.title || '';
const bodyText = (document.body && document.body.innerText) ? document.body.innerText.slice(0, 500) : '';
return {
hasTurnstileInput,
hasTurnstileWidget,
hasChallengeRunning,
titleHasJustAMoment: titleText.includes('Just a moment'),
bodyHasJustAMoment: bodyText.includes('Just a moment') || bodyText.includes('Checking your browser'),
};
}).catch(() => ({
hasTurnstileInput: false,
hasTurnstileWidget: false,
hasChallengeRunning: false,
titleHasJustAMoment: false,
bodyHasJustAMoment: false,
}));
const urlChallenge = urlIndicatesCfChallenge(url);
const realChallenge = urlChallenge
|| domSignals.titleHasJustAMoment
|| domSignals.bodyHasJustAMoment
|| domSignals.hasChallengeRunning
|| title.includes('Just a moment');
const turnstileSignals = domSignals.hasTurnstileInput || domSignals.hasTurnstileWidget;
let challengeType = null;
if (turnstileSignals || url.includes('__cf_chl')) {
challengeType = 'turnstile';
} else if (realChallenge) {
challengeType = 'js_challenge';
}
const requireClearance = isCfClearanceRequired(antiBot);
let blocked;
if (requireClearance) {
blocked = !hasCfClearance && (realChallenge || turnstileSignals);
} else {
blocked = realChallenge;
}
return {
blocked,
challengeType,
hasCfClearance,
url,
title,
};
}
/**
* 检测 Turnstile 是否已通过
* @param {import('puppeteer').Page} page
* @returns {Promise<boolean>}
*/
async function isTurnstileSolved(page) {
const cookies = await page.cookies().catch(() => []);
if (cookies.some((c) => c.name === 'cf_clearance')) {
return true;
}
const tokenLen = await page.evaluate(() => {
const input = document.querySelector('[name="cf-turnstile-response"]');
return input && input.value ? input.value.length : 0;
}).catch(() => 0);
return tokenLen > 20;
}
module.exports = {
detectCloudflare,
isTurnstileSolved,
urlIndicatesCfChallenge,
isCfClearanceRequired,
};