A2C工单修复
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* CapSolver / 2Captcha 过盾兜底:Turnstile token 注入 或 AntiCloudflareTask cf_clearance
|
||||
*
|
||||
* A2C 等业务域为 CF Managed Challenge(非独立 Turnstile 挂件),须优先 AntiCloudflareTask。
|
||||
*/
|
||||
const { sanitizePageUrlForCfSolver } = require('./cf-detector');
|
||||
const {
|
||||
injectTurnstileToken,
|
||||
recoverAfterTokenInject,
|
||||
extractTurnstileSitekey,
|
||||
} = require('./turnstile-handler');
|
||||
const {
|
||||
solveTurnstile,
|
||||
solveCloudflare,
|
||||
applyCfClearanceSolution,
|
||||
} = require('./captcha-solver');
|
||||
const { getSitekeyCapture } = require('./sitekey-capture');
|
||||
|
||||
/** @returns {boolean} */
|
||||
function hasCaptchaProxy() {
|
||||
return (process.env.CAPTCHA_PROXY || '').trim() !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否应使用 CapSolver AntiCloudflareTask(Managed Challenge / 5s 盾)
|
||||
* @param {object|null|undefined} antiBot
|
||||
*/
|
||||
function shouldPreferManagedCloudflareSolver(antiBot) {
|
||||
if (!antiBot || antiBot.cfCloudflareSolver === false) {
|
||||
return false;
|
||||
}
|
||||
if (antiBot.cfChallengeMode === 'managed') {
|
||||
return hasCaptchaProxy();
|
||||
}
|
||||
if (antiBot.cfCloudflareSolver === true && hasCaptchaProxy()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析可用于 AntiTurnstileTask 的 sitekey(排除 A2C 配置的 challenge sitekey)
|
||||
* @param {import('puppeteer').Page} page
|
||||
* @param {object} antiBot
|
||||
* @param {number} waitMs
|
||||
* @returns {Promise<string|null>}
|
||||
*/
|
||||
async function resolveTurnstileWidgetSitekey(page, antiBot, waitMs) {
|
||||
if (antiBot.cfChallengeMode === 'managed') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const capture = getSitekeyCapture(page);
|
||||
if (capture) {
|
||||
const fromCapture = await capture.resolveForSolver(page, antiBot, waitMs);
|
||||
if (fromCapture && antiBot.cfChallengeMode !== 'managed') {
|
||||
const configured = String(antiBot.turnstileSitekey || '').trim();
|
||||
if (configured && fromCapture === configured && antiBot.cfCloudflareSolver) {
|
||||
return null;
|
||||
}
|
||||
return fromCapture;
|
||||
}
|
||||
}
|
||||
|
||||
const dynamic = await extractTurnstileSitekey(page);
|
||||
if (dynamic) {
|
||||
return dynamic;
|
||||
}
|
||||
|
||||
const configured = String(antiBot?.turnstileSitekey || '').trim();
|
||||
if (configured && !antiBot.cfCloudflareSolver) {
|
||||
return configured;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* CapSolver AntiCloudflareTask 求解并注入 cf_clearance
|
||||
* @param {import('puppeteer').Page} page
|
||||
* @param {object} params
|
||||
*/
|
||||
async function runManagedCloudflareSolver(page, params) {
|
||||
const { cleanSolverUrl, waitForUrlSettled, timeoutMs } = params;
|
||||
|
||||
if (!hasCaptchaProxy()) {
|
||||
throw new Error('AntiCloudflareTask 需要配置 CAPTCHA_PROXY(静态代理 ip:port:user:pass)');
|
||||
}
|
||||
|
||||
console.log('[CF] A2C/Managed Challenge:使用 CapSolver AntiCloudflareTask');
|
||||
const html = await page.content().catch(() => '');
|
||||
const userAgent = await page.evaluate(() => navigator.userAgent).catch(() => '');
|
||||
const { cookies, userAgent: solvedUa, provider } = await solveCloudflare({
|
||||
pageUrl: cleanSolverUrl,
|
||||
html,
|
||||
userAgent,
|
||||
});
|
||||
console.log(`[CF] CapSolver AntiCloudflareTask (${provider}) 返回 cf_clearance`);
|
||||
await applyCfClearanceSolution(page, cleanSolverUrl, { cookies, userAgent: solvedUa || userAgent });
|
||||
if (typeof waitForUrlSettled === 'function') {
|
||||
await waitForUrlSettled(page).catch(() => {});
|
||||
}
|
||||
|
||||
return { mode: 'cloudflare', provider };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('puppeteer').Page} page
|
||||
* @param {object} options
|
||||
* @param {object} options.antiBot
|
||||
* @param {string} [options.navUrl]
|
||||
* @param {(page: import('puppeteer').Page) => Promise<string>} [options.waitForUrlSettled]
|
||||
* @param {number} [options.timeoutMs]
|
||||
* @returns {Promise<{ mode: 'turnstile'|'cloudflare', provider: string }>}
|
||||
*/
|
||||
async function runCaptchaApiFallback(page, options) {
|
||||
const {
|
||||
antiBot,
|
||||
navUrl = '',
|
||||
waitForUrlSettled,
|
||||
timeoutMs = 60000,
|
||||
} = options;
|
||||
|
||||
const cleanSolverUrl = sanitizePageUrlForCfSolver(navUrl || page.url());
|
||||
|
||||
if (shouldPreferManagedCloudflareSolver(antiBot)) {
|
||||
console.log(`[CF] Captcha API 求解 pageUrl=${cleanSolverUrl} mode=managed_challenge`);
|
||||
return runManagedCloudflareSolver(page, { cleanSolverUrl, waitForUrlSettled, timeoutMs });
|
||||
}
|
||||
|
||||
const sitekey = await resolveTurnstileWidgetSitekey(
|
||||
page,
|
||||
antiBot,
|
||||
Math.min(timeoutMs, 25000)
|
||||
);
|
||||
console.log(`[CF] Captcha API 求解 pageUrl=${cleanSolverUrl} sitekey=${sitekey ? 'ok' : 'missing'}`);
|
||||
|
||||
if (sitekey) {
|
||||
try {
|
||||
const { token, provider } = await solveTurnstile({ pageUrl: cleanSolverUrl, sitekey });
|
||||
console.log(`[CF] Captcha API Turnstile (${provider}) 返回 token,注入页面`);
|
||||
await injectTurnstileToken(page, token);
|
||||
await recoverAfterTokenInject(
|
||||
page,
|
||||
cleanSolverUrl,
|
||||
waitForUrlSettled,
|
||||
Math.min(timeoutMs, 45000)
|
||||
);
|
||||
return { mode: 'turnstile', provider };
|
||||
} catch (turnstileErr) {
|
||||
const msg = (turnstileErr?.message || '').toLowerCase();
|
||||
const isChallengeMismatch = msg.includes('challenge, not turnstile')
|
||||
|| msg.includes('not turnstile');
|
||||
if (isChallengeMismatch && antiBot.cfCloudflareSolver !== false && hasCaptchaProxy()) {
|
||||
console.warn(`[CF] Turnstile 任务失败(${turnstileErr.message}),降级 AntiCloudflareTask`);
|
||||
return runManagedCloudflareSolver(page, { cleanSolverUrl, waitForUrlSettled, timeoutMs });
|
||||
}
|
||||
throw turnstileErr;
|
||||
}
|
||||
}
|
||||
|
||||
if (antiBot?.cfCloudflareSolver !== false && hasCaptchaProxy()) {
|
||||
return runManagedCloudflareSolver(page, { cleanSolverUrl, waitForUrlSettled, timeoutMs });
|
||||
}
|
||||
|
||||
throw new Error('无法过盾:请配置 CAPTCHA_PROXY(A2C Managed Challenge)或提供有效 Turnstile sitekey');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
runCaptchaApiFallback,
|
||||
shouldPreferManagedCloudflareSolver,
|
||||
hasCaptchaProxy,
|
||||
};
|
||||
Reference in New Issue
Block a user