const axios = require('axios'); const logger = require('../../../shared/logger'); /** * YesCaptcha API 封装 * 支持 hCaptcha、reCaptcha、Turnstile 等验证码识别 */ class YesCaptchaAPI { constructor() { this.apiKey = process.env.YESCAPTCHA_API_KEY; this.apiUrl = 'https://api.yescaptcha.com'; } /** * 识别 hCaptcha * @param {string} siteKey - 网站的 sitekey * @param {string} pageUrl - 网页 URL * @param {object} options - 可选参数 {isInvisible, rqdata, userAgent} * @returns {Promise} - hCaptcha token */ async solveHCaptcha(siteKey, pageUrl, options = {}) { if (!this.apiKey) { throw new Error('YesCaptcha API Key 未配置'); } logger.info('YesCaptcha', '开始识别 hCaptcha...'); logger.info('YesCaptcha', `SiteKey: ${siteKey.substring(0, 20)}...`); try { // 1. 创建任务 const requestBody = { clientKey: this.apiKey, task: { type: 'HCaptchaTaskProxyless', websiteURL: pageUrl, websiteKey: siteKey } }; // 添加可选参数 if (options.isInvisible !== undefined) { requestBody.task.isInvisible = options.isInvisible; } if (options.rqdata) { requestBody.task.rqdata = options.rqdata; } if (options.userAgent) { requestBody.task.userAgent = options.userAgent; } logger.info('YesCaptcha', `请求 URL: ${pageUrl}`); logger.info('YesCaptcha', `API Key: ${this.apiKey.substring(0, 10)}...`); const createTaskResponse = await axios.post(`${this.apiUrl}/createTask`, requestBody); if (createTaskResponse.data.errorId !== 0) { logger.error('YesCaptcha', `API 返回错误: ${JSON.stringify(createTaskResponse.data)}`); throw new Error(`创建任务失败: ${createTaskResponse.data.errorDescription}`); } const taskId = createTaskResponse.data.taskId; logger.info('YesCaptcha', `任务已创建,TaskID: ${taskId}`); // 2. 轮询获取结果(最多等待120秒) const maxAttempts = 40; // 120秒 / 3秒 let attempts = 0; while (attempts < maxAttempts) { attempts++; // 等待3秒再查询 await new Promise(resolve => setTimeout(resolve, 3000)); logger.info('YesCaptcha', `查询结果... (${attempts}/${maxAttempts})`); 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('YesCaptcha', `✓ 识别成功!耗时: ${attempts * 3}秒`); // 返回 userAgent 如果有的话 if (getResultResponse.data.solution.userAgent) { logger.info('YesCaptcha', `返回的 UserAgent: ${getResultResponse.data.solution.userAgent.substring(0, 50)}...`); } return token; } else if (status === 'processing') { // 继续等待 continue; } else { throw new Error(`未知状态: ${status}`); } } throw new Error('识别超时(120秒)'); } catch (error) { // 详细的错误日志 if (error.response) { // API 返回了错误响应 logger.error('YesCaptcha', `HTTP ${error.response.status}: ${error.response.statusText}`); logger.error('YesCaptcha', `响应数据: ${JSON.stringify(error.response.data)}`); if (error.response.status === 400) { logger.error('YesCaptcha', '可能原因:'); logger.error('YesCaptcha', ' 1. API Key 无效或已过期'); logger.error('YesCaptcha', ' 2. 余额不足'); logger.error('YesCaptcha', ' 3. siteKey 或 URL 格式错误'); } } else if (error.request) { // 请求已发送但没有收到响应 logger.error('YesCaptcha', '无法连接到 YesCaptcha API'); } else { // 其他错误 logger.error('YesCaptcha', `识别失败: ${error.message}`); } throw error; } } } module.exports = YesCaptchaAPI;