227 lines
6.9 KiB
JavaScript
227 lines
6.9 KiB
JavaScript
/**
|
||
* CapSolver API - hCaptcha/Turnstile 自动识别
|
||
* 使用官方API方式,不依赖浏览器扩展
|
||
*/
|
||
|
||
const axios = require('axios');
|
||
const logger = require('../../../shared/logger');
|
||
|
||
class CapSolverAPI {
|
||
constructor(apiKey) {
|
||
this.apiKey = apiKey || process.env.CAPSOLVER_API_KEY;
|
||
this.apiUrl = 'https://api.capsolver.com';
|
||
|
||
if (!this.apiKey) {
|
||
logger.warn('CapSolver', '未配置API Key,自动识别将不可用');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 解决 hCaptcha
|
||
* @param {string} siteKey - 网站的 siteKey
|
||
* @param {string} pageUrl - 当前页面URL
|
||
* @returns {Promise<string>} token
|
||
*/
|
||
async solveHCaptcha(siteKey, pageUrl) {
|
||
if (!this.apiKey) {
|
||
throw new Error('CapSolver API Key 未配置');
|
||
}
|
||
|
||
logger.info('CapSolver', '开始识别 hCaptcha...');
|
||
logger.info('CapSolver', `SiteKey: ${siteKey.substring(0, 20)}...`);
|
||
|
||
try {
|
||
// 1. 创建任务
|
||
const requestBody = {
|
||
clientKey: this.apiKey,
|
||
task: {
|
||
type: 'HCaptchaTaskProxyless',
|
||
websiteURL: pageUrl,
|
||
websiteKey: siteKey
|
||
}
|
||
};
|
||
|
||
logger.info('CapSolver', `请求 URL: ${pageUrl}`);
|
||
logger.info('CapSolver', `API Key: ${this.apiKey.substring(0, 10)}...`);
|
||
|
||
const createTaskResponse = await axios.post(`${this.apiUrl}/createTask`, requestBody);
|
||
|
||
if (createTaskResponse.data.errorId !== 0) {
|
||
logger.error('CapSolver', `API 返回错误: ${JSON.stringify(createTaskResponse.data)}`);
|
||
throw new Error(`创建任务失败: ${createTaskResponse.data.errorDescription}`);
|
||
}
|
||
|
||
const taskId = createTaskResponse.data.taskId;
|
||
logger.info('CapSolver', `任务创建成功 (ID: ${taskId})`);
|
||
|
||
// 2. 轮询获取结果
|
||
let attempts = 0;
|
||
const maxAttempts = 60; // 最多等待2分钟
|
||
|
||
while (attempts < maxAttempts) {
|
||
attempts++;
|
||
await this.delay(2000); // 每2秒查询一次
|
||
|
||
const getResultResponse = await axios.post(`${this.apiUrl}/getTaskResult`, {
|
||
clientKey: this.apiKey,
|
||
taskId: taskId
|
||
});
|
||
|
||
if (getResultResponse.data.errorId !== 0) {
|
||
throw new Error(`获取结果失败: ${getResultResponse.data.errorDescription}`);
|
||
}
|
||
|
||
const status = getResultResponse.data.status;
|
||
|
||
if (status === 'ready') {
|
||
const token = getResultResponse.data.solution.gRecaptchaResponse;
|
||
logger.success('CapSolver', `✓ hCaptcha 识别成功!(耗时: ${attempts * 2}秒)`);
|
||
return token;
|
||
}
|
||
|
||
if (status === 'failed') {
|
||
throw new Error('CapSolver 识别失败');
|
||
}
|
||
|
||
// status === 'processing'
|
||
if (attempts % 5 === 0) {
|
||
logger.info('CapSolver', `识别中... (${attempts * 2}/${maxAttempts * 2}秒)`);
|
||
}
|
||
}
|
||
|
||
throw new Error('CapSolver 识别超时(2分钟)');
|
||
|
||
} catch (error) {
|
||
// 详细的错误日志
|
||
if (error.response) {
|
||
// API 返回了错误响应
|
||
logger.error('CapSolver', `HTTP ${error.response.status}: ${error.response.statusText}`);
|
||
logger.error('CapSolver', `响应数据: ${JSON.stringify(error.response.data)}`);
|
||
|
||
if (error.response.status === 400) {
|
||
logger.error('CapSolver', '可能原因:');
|
||
logger.error('CapSolver', ' 1. API Key 无效或已过期');
|
||
logger.error('CapSolver', ' 2. 余额不足');
|
||
logger.error('CapSolver', ' 3. siteKey 或 URL 格式错误');
|
||
}
|
||
} else if (error.request) {
|
||
// 请求已发送但没有收到响应
|
||
logger.error('CapSolver', '无法连接到 CapSolver API');
|
||
} else {
|
||
// 其他错误
|
||
logger.error('CapSolver', `识别失败: ${error.message}`);
|
||
}
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 解决 Cloudflare Turnstile
|
||
* @param {string} siteKey - 网站的 siteKey
|
||
* @param {string} pageUrl - 当前页面URL
|
||
* @returns {Promise<string>} token
|
||
*/
|
||
async solveTurnstile(siteKey, pageUrl) {
|
||
if (!this.apiKey) {
|
||
throw new Error('CapSolver API Key 未配置');
|
||
}
|
||
|
||
logger.info('CapSolver', '开始识别 Turnstile...');
|
||
logger.info('CapSolver', `SiteKey: ${siteKey.substring(0, 20)}...`);
|
||
|
||
try {
|
||
// 1. 创建任务
|
||
const createTaskResponse = await axios.post(`${this.apiUrl}/createTask`, {
|
||
clientKey: this.apiKey,
|
||
task: {
|
||
type: 'AntiTurnstileTaskProxyLess',
|
||
websiteURL: pageUrl,
|
||
websiteKey: siteKey
|
||
}
|
||
});
|
||
|
||
if (createTaskResponse.data.errorId !== 0) {
|
||
throw new Error(`创建任务失败: ${createTaskResponse.data.errorDescription}`);
|
||
}
|
||
|
||
const taskId = createTaskResponse.data.taskId;
|
||
logger.info('CapSolver', `任务创建成功 (ID: ${taskId})`);
|
||
|
||
// 2. 轮询获取结果
|
||
let attempts = 0;
|
||
const maxAttempts = 60; // 最多等待2分钟
|
||
|
||
while (attempts < maxAttempts) {
|
||
attempts++;
|
||
await this.delay(2000);
|
||
|
||
const getResultResponse = await axios.post(`${this.apiUrl}/getTaskResult`, {
|
||
clientKey: this.apiKey,
|
||
taskId: taskId
|
||
});
|
||
|
||
if (getResultResponse.data.errorId !== 0) {
|
||
throw new Error(`获取结果失败: ${getResultResponse.data.errorDescription}`);
|
||
}
|
||
|
||
const status = getResultResponse.data.status;
|
||
|
||
if (status === 'ready') {
|
||
const token = getResultResponse.data.solution.token;
|
||
logger.success('CapSolver', `✓ Turnstile 识别成功!(耗时: ${attempts * 2}秒)`);
|
||
return token;
|
||
}
|
||
|
||
if (status === 'failed') {
|
||
throw new Error('CapSolver 识别失败');
|
||
}
|
||
|
||
if (attempts % 5 === 0) {
|
||
logger.info('CapSolver', `识别中... (${attempts * 2}/${maxAttempts * 2}秒)`);
|
||
}
|
||
}
|
||
|
||
throw new Error('CapSolver 识别超时(2分钟)');
|
||
|
||
} catch (error) {
|
||
logger.error('CapSolver', `识别失败: ${error.message}`);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查余额
|
||
*/
|
||
async getBalance() {
|
||
if (!this.apiKey) {
|
||
throw new Error('CapSolver API Key 未配置');
|
||
}
|
||
|
||
try {
|
||
const response = await axios.post(`${this.apiUrl}/getBalance`, {
|
||
clientKey: this.apiKey
|
||
});
|
||
|
||
if (response.data.errorId !== 0) {
|
||
throw new Error(`获取余额失败: ${response.data.errorDescription}`);
|
||
}
|
||
|
||
const balance = response.data.balance;
|
||
logger.info('CapSolver', `账户余额: $${balance}`);
|
||
return balance;
|
||
} catch (error) {
|
||
logger.error('CapSolver', `获取余额失败: ${error.message}`);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 延迟函数
|
||
*/
|
||
async delay(ms) {
|
||
return new Promise(resolve => setTimeout(resolve, ms));
|
||
}
|
||
}
|
||
|
||
module.exports = CapSolverAPI;
|