Files
links/patches/puppeteer-api/lib/cf-detector.js
T
2026-06-29 04:54:41 +08:00

114 lines
3.6 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 挑战页检测
*/
/**
* 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');
}
/**
* @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
* @returns {Promise<CloudflareState>}
*/
async function detectCloudflare(page) {
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);
let challengeType = null;
if (domSignals.hasTurnstileInput || domSignals.hasTurnstileWidget || url.includes('__cf_chl')) {
challengeType = 'turnstile';
} else if (urlChallenge || domSignals.titleHasJustAMoment || domSignals.bodyHasJustAMoment) {
challengeType = 'js_challenge';
}
const blocked = !hasCfClearance && (
urlChallenge
|| domSignals.titleHasJustAMoment
|| domSignals.bodyHasJustAMoment
|| domSignals.hasTurnstileInput
|| domSignals.hasTurnstileWidget
|| domSignals.hasChallengeRunning
|| title.includes('Just a moment')
);
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,
};