A2C工单修复
This commit is contained in:
@@ -6,6 +6,10 @@ const {
|
||||
CAPTCHA_API_KEY,
|
||||
CAPTCHA_MAX_WAIT_MS,
|
||||
} = require('./constants');
|
||||
const { sanitizePageUrlForCfSolver } = require('./cf-detector');
|
||||
|
||||
/** AntiCloudflareTask 所需静态代理(ip:port:user:pass),未配置则不可用 */
|
||||
const CAPTCHA_PROXY = (process.env.CAPTCHA_PROXY || '').trim();
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
@@ -113,6 +117,147 @@ async function solveVia2Captcha({ pageUrl, sitekey, apiKey }) {
|
||||
throw new Error('2Captcha 求解超时');
|
||||
}
|
||||
|
||||
/**
|
||||
* CapSolver Managed Challenge(返回 cf_clearance cookie,需 CAPTCHA_PROXY)
|
||||
* @param {{ pageUrl: string, apiKey: string, proxy: string, html?: string, userAgent?: string }} params
|
||||
* @returns {Promise<{ cookies: Record<string, string>, userAgent?: string }>}
|
||||
*/
|
||||
async function solveViaCapSolverCloudflare({ pageUrl, apiKey, proxy, html, userAgent }) {
|
||||
const task = {
|
||||
type: 'AntiCloudflareTask',
|
||||
websiteURL: pageUrl,
|
||||
proxy,
|
||||
};
|
||||
if (html) {
|
||||
task.html = html.slice(0, 500000);
|
||||
}
|
||||
if (userAgent) {
|
||||
task.userAgent = userAgent;
|
||||
}
|
||||
|
||||
const create = await httpJsonPost('https://api.capsolver.com/createTask', {
|
||||
clientKey: apiKey,
|
||||
task,
|
||||
});
|
||||
|
||||
if (create.errorId !== 0 || !create.taskId) {
|
||||
throw new Error(`CapSolver AntiCloudflare createTask 失败: ${create.errorDescription || create.errorCode || 'unknown'}`);
|
||||
}
|
||||
|
||||
const deadline = Date.now() + CAPTCHA_MAX_WAIT_MS;
|
||||
while (Date.now() < deadline) {
|
||||
await new Promise((r) => setTimeout(r, 3000));
|
||||
const result = await httpJsonPost('https://api.capsolver.com/getTaskResult', {
|
||||
clientKey: apiKey,
|
||||
taskId: create.taskId,
|
||||
});
|
||||
|
||||
if (result.status === 'ready' && result.solution) {
|
||||
const cookies = result.solution.cookies || {};
|
||||
if (typeof cookies === 'string') {
|
||||
return { cookies: { cf_clearance: cookies }, userAgent: result.solution.userAgent };
|
||||
}
|
||||
if (result.solution.token && !cookies.cf_clearance) {
|
||||
return {
|
||||
cookies: { cf_clearance: result.solution.token },
|
||||
userAgent: result.solution.userAgent,
|
||||
};
|
||||
}
|
||||
return {
|
||||
cookies,
|
||||
userAgent: result.solution.userAgent,
|
||||
};
|
||||
}
|
||||
if (result.errorId !== 0) {
|
||||
throw new Error(`CapSolver AntiCloudflare getTaskResult 失败: ${result.errorDescription || result.errorCode}`);
|
||||
}
|
||||
}
|
||||
throw new Error('CapSolver AntiCloudflare 求解超时');
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一 Cloudflare Managed Challenge 求解
|
||||
* @param {{ pageUrl: string, provider?: string, apiKey?: string, proxy?: string, html?: string, userAgent?: string }} params
|
||||
* @returns {Promise<{ cookies: Record<string, string>, userAgent?: string, provider: string }>}
|
||||
*/
|
||||
async function solveCloudflare({ pageUrl, provider, apiKey, proxy, html, userAgent }) {
|
||||
const resolvedProvider = (provider || CAPTCHA_PROVIDER).toLowerCase();
|
||||
const resolvedKey = apiKey || CAPTCHA_API_KEY;
|
||||
const resolvedProxy = (proxy || CAPTCHA_PROXY).trim();
|
||||
const cleanPageUrl = sanitizePageUrlForCfSolver(pageUrl);
|
||||
|
||||
if (!resolvedKey) {
|
||||
throw new Error('未配置 CAPTCHA_API_KEY,无法使用 AntiCloudflareTask');
|
||||
}
|
||||
if (!resolvedProxy) {
|
||||
throw new Error('AntiCloudflareTask 需要配置 CAPTCHA_PROXY(静态代理 ip:port:user:pass)');
|
||||
}
|
||||
|
||||
if (resolvedProvider !== 'capsolver') {
|
||||
throw new Error('AntiCloudflareTask 当前仅支持 CapSolver');
|
||||
}
|
||||
|
||||
const { cookies, userAgent: solvedUa } = await solveViaCapSolverCloudflare({
|
||||
pageUrl: cleanPageUrl,
|
||||
apiKey: resolvedKey,
|
||||
proxy: resolvedProxy,
|
||||
html,
|
||||
userAgent,
|
||||
});
|
||||
|
||||
if (!cookies || !cookies.cf_clearance) {
|
||||
throw new Error('CapSolver AntiCloudflare 未返回 cf_clearance');
|
||||
}
|
||||
|
||||
return { cookies, userAgent: solvedUa || userAgent, provider: resolvedProvider };
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 CapSolver AntiCloudflare 返回的 cookie / UA 写入页面并 reload
|
||||
* @param {import('puppeteer').Page} page
|
||||
* @param {string} cleanUrl
|
||||
* @param {{ cookies: Record<string, string>, userAgent?: string }} solution
|
||||
*/
|
||||
async function applyCfClearanceSolution(page, cleanUrl, solution) {
|
||||
if (solution.userAgent) {
|
||||
await page.setUserAgent(solution.userAgent).catch(() => {});
|
||||
}
|
||||
|
||||
let host = '';
|
||||
let path = '/';
|
||||
try {
|
||||
const parsed = new URL(cleanUrl);
|
||||
host = parsed.hostname;
|
||||
path = parsed.pathname || '/';
|
||||
} catch (_) {
|
||||
host = '';
|
||||
}
|
||||
|
||||
for (const [name, value] of Object.entries(solution.cookies || {})) {
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
const cookieDomain = host.startsWith('.') ? host : `.${host}`;
|
||||
await page.setCookie({
|
||||
name,
|
||||
value: String(value),
|
||||
domain: cookieDomain,
|
||||
path: '/',
|
||||
secure: true,
|
||||
sameSite: 'None',
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
console.log(`[CF] cf_clearance 已注入,goto 业务页 url=${cleanUrl}`);
|
||||
await page.goto(cleanUrl, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 60000,
|
||||
}).catch(() => {});
|
||||
await page.waitForNetworkIdle({ idleTime: 500, timeout: 30000 }).catch(() => {
|
||||
console.warn('[CF] cf_clearance goto 后 networkidle 超时,继续后续流程');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一 Turnstile 求解入口
|
||||
* @param {{ pageUrl: string, sitekey: string, provider?: string, apiKey?: string }} params
|
||||
@@ -121,6 +266,7 @@ async function solveVia2Captcha({ pageUrl, sitekey, apiKey }) {
|
||||
async function solveTurnstile({ pageUrl, sitekey, provider, apiKey }) {
|
||||
const resolvedProvider = (provider || CAPTCHA_PROVIDER).toLowerCase();
|
||||
const resolvedKey = apiKey || CAPTCHA_API_KEY;
|
||||
const cleanPageUrl = sanitizePageUrlForCfSolver(pageUrl);
|
||||
|
||||
if (!sitekey) {
|
||||
throw new Error('无法提取 Turnstile sitekey');
|
||||
@@ -131,9 +277,9 @@ async function solveTurnstile({ pageUrl, sitekey, provider, apiKey }) {
|
||||
|
||||
let token;
|
||||
if (resolvedProvider === '2captcha') {
|
||||
token = await solveVia2Captcha({ pageUrl, sitekey, apiKey: resolvedKey });
|
||||
token = await solveVia2Captcha({ pageUrl: cleanPageUrl, sitekey, apiKey: resolvedKey });
|
||||
} else {
|
||||
token = await solveViaCapSolver({ pageUrl, sitekey, apiKey: resolvedKey });
|
||||
token = await solveViaCapSolver({ pageUrl: cleanPageUrl, sitekey, apiKey: resolvedKey });
|
||||
}
|
||||
|
||||
return { token, provider: resolvedProvider };
|
||||
@@ -149,5 +295,7 @@ function isCaptchaConfigured() {
|
||||
|
||||
module.exports = {
|
||||
solveTurnstile,
|
||||
solveCloudflare,
|
||||
applyCfClearanceSolution,
|
||||
isCaptchaConfigured,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user