dasdasd
This commit is contained in:
parent
06d39edb64
commit
82d62145f1
3
.env
3
.env
@ -9,3 +9,6 @@ MYSQL_PORT=3306
|
||||
MYSQL_USER=windsurf-auto-register
|
||||
MYSQL_PASSWORD=Qichen5210523
|
||||
MYSQL_DATABASE=windsurf-auto-register
|
||||
|
||||
# CapSolver 验证码识别
|
||||
CAPSOLVER_API_KEY=CAP-0FCDDA4906E87D9F4FF68EAECD34E320876FBA70E4F30EA1ADCD264EDB15E4BF
|
||||
|
||||
@ -18,6 +18,7 @@ const EmailVerificationService = require('../email-verification');
|
||||
const { DEFAULT_CONFIG } = require('../config');
|
||||
const CardGenerator = require('../../card-generator/generator');
|
||||
const database = require('../../database');
|
||||
const CapSolverAPI = require('../utils/capsolver-api');
|
||||
|
||||
class WindsurfRegister {
|
||||
constructor(options = {}) {
|
||||
@ -26,6 +27,7 @@ class WindsurfRegister {
|
||||
this.dataGen = new AccountDataGenerator();
|
||||
this.human = new HumanBehavior();
|
||||
this.emailService = new EmailVerificationService();
|
||||
this.capsolver = new CapSolverAPI();
|
||||
this.browser = null;
|
||||
this.page = null;
|
||||
this.currentStep = 0;
|
||||
@ -922,44 +924,18 @@ class WindsurfRegister {
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤6: 填写支付信息
|
||||
* 填写银行卡表单
|
||||
*/
|
||||
async step6_fillPayment() {
|
||||
logger.info(this.siteName, `[步骤 6/${this.getTotalSteps()}] 填写支付信息`);
|
||||
|
||||
try {
|
||||
// 等待页面加载
|
||||
await this.human.readPage(3, 5);
|
||||
|
||||
// 1. 生成信用卡信息(使用银联卡)
|
||||
logger.info(this.siteName, ' → 生成银联卡信息...');
|
||||
const cardGen = new CardGenerator();
|
||||
const card = cardGen.generate('unionpay');
|
||||
logger.info(this.siteName, ` → 卡号: ${card.number}`);
|
||||
logger.info(this.siteName, ` → 有效期: ${card.month}/${card.year}`);
|
||||
logger.info(this.siteName, ` → CVV: ${card.cvv}`);
|
||||
|
||||
// 保存卡信息供后续使用
|
||||
this.cardInfo = {
|
||||
number: card.number,
|
||||
month: card.month,
|
||||
year: card.year,
|
||||
cvv: card.cvv,
|
||||
country: 'MO'
|
||||
};
|
||||
|
||||
// 2. 点击选择"银行卡"支付方式
|
||||
async fillCardForm(card, isRetry = false) {
|
||||
if (!isRetry) {
|
||||
// 首次填写:选择支付方式
|
||||
logger.info(this.siteName, ' → 选择银行卡支付方式...');
|
||||
const cardRadio = await this.page.$('input[type="radio"][value="card"]');
|
||||
if (cardRadio) {
|
||||
await cardRadio.click();
|
||||
logger.success(this.siteName, ' → ✓ 已点击银行卡选项');
|
||||
|
||||
// 等待支付表单完全加载(等待骨架屏消失,表单元素出现)
|
||||
logger.info(this.siteName, ' → 等待支付表单加载...');
|
||||
await this.human.randomDelay(3000, 5000);
|
||||
|
||||
// 等待所有必需的支付字段都加载完成
|
||||
try {
|
||||
await this.page.waitForFunction(
|
||||
() => {
|
||||
@ -967,7 +943,6 @@ class WindsurfRegister {
|
||||
const cardExpiry = document.querySelector('#cardExpiry');
|
||||
const cardCvc = document.querySelector('#cardCvc');
|
||||
const billingName = document.querySelector('#billingName');
|
||||
// 检查所有字段都存在且可见
|
||||
return cardNumber && cardExpiry && cardCvc && billingName;
|
||||
},
|
||||
{ timeout: 30000 }
|
||||
@ -977,186 +952,281 @@ class WindsurfRegister {
|
||||
logger.warn(this.siteName, ` → 等待表单超时: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 填写卡号
|
||||
logger.info(this.siteName, ' → 填写卡号...');
|
||||
await this.page.waitForSelector('#cardNumber', { visible: true, timeout: 10000 });
|
||||
await this.page.click('#cardNumber');
|
||||
await this.human.randomDelay(500, 1000);
|
||||
await this.page.type('#cardNumber', card.number, { delay: 250 });
|
||||
|
||||
// 4. 填写有效期(月份/年份)
|
||||
logger.info(this.siteName, ' → 填写有效期...');
|
||||
await this.page.click('#cardExpiry');
|
||||
await this.human.randomDelay(300, 500);
|
||||
const expiry = `${card.month}${card.year}`; // 格式: MMYY
|
||||
await this.page.type('#cardExpiry', expiry, { delay: 250 });
|
||||
|
||||
// 5. 填写CVC
|
||||
logger.info(this.siteName, ' → 填写CVC...');
|
||||
await this.page.click('#cardCvc');
|
||||
await this.human.randomDelay(300, 500);
|
||||
await this.page.type('#cardCvc', card.cvv, { delay: 250 });
|
||||
|
||||
// 6. 填写持卡人姓名
|
||||
}
|
||||
|
||||
// 填写卡号
|
||||
logger.info(this.siteName, ' → 填写卡号...');
|
||||
const cardNumberField = await this.page.$('#cardNumber');
|
||||
await cardNumberField.click();
|
||||
await this.human.randomDelay(300, 500);
|
||||
if (isRetry) {
|
||||
await this.page.keyboard.down('Control');
|
||||
await this.page.keyboard.press('A');
|
||||
await this.page.keyboard.up('Control');
|
||||
}
|
||||
await this.page.keyboard.press('Backspace');
|
||||
await this.human.randomDelay(500, 1000);
|
||||
await cardNumberField.type(card.number, { delay: 250 });
|
||||
|
||||
// 填写有效期
|
||||
logger.info(this.siteName, ' → 填写有效期...');
|
||||
const cardExpiryField = await this.page.$('#cardExpiry');
|
||||
await cardExpiryField.click();
|
||||
await this.human.randomDelay(200, 300);
|
||||
if (isRetry) {
|
||||
await this.page.keyboard.down('Control');
|
||||
await this.page.keyboard.press('A');
|
||||
await this.page.keyboard.up('Control');
|
||||
}
|
||||
await this.page.keyboard.press('Backspace');
|
||||
await this.human.randomDelay(300, 500);
|
||||
const expiry = `${card.month}${card.year}`;
|
||||
await cardExpiryField.type(expiry, { delay: 250 });
|
||||
|
||||
// 填写CVC
|
||||
logger.info(this.siteName, ' → 填写CVC...');
|
||||
const cardCvcField = await this.page.$('#cardCvc');
|
||||
await cardCvcField.click();
|
||||
await this.human.randomDelay(200, 300);
|
||||
if (isRetry) {
|
||||
await this.page.keyboard.down('Control');
|
||||
await this.page.keyboard.press('A');
|
||||
await this.page.keyboard.up('Control');
|
||||
}
|
||||
await this.page.keyboard.press('Backspace');
|
||||
await this.human.randomDelay(300, 500);
|
||||
await cardCvcField.type(card.cvv, { delay: 250 });
|
||||
|
||||
if (!isRetry) {
|
||||
// 首次填写:填写持卡人姓名和地址
|
||||
logger.info(this.siteName, ' → 填写持卡人姓名...');
|
||||
await this.page.click('#billingName');
|
||||
await this.human.randomDelay(300, 500);
|
||||
const fullName = `${this.accountData.firstName} ${this.accountData.lastName}`;
|
||||
await this.page.type('#billingName', fullName, { delay: 200 });
|
||||
|
||||
// 7. 选择地址:中国澳门特别行政区
|
||||
logger.info(this.siteName, ' → 选择地址:中国澳门特别行政区...');
|
||||
await this.page.select('#billingCountry', 'MO');
|
||||
await this.human.randomDelay(1000, 2000);
|
||||
|
||||
// 8. 填写地址信息(如果需要)
|
||||
// 等待地址字段加载
|
||||
await this.human.randomDelay(1000, 2000);
|
||||
|
||||
// 检查是否需要填写地址行1和行2
|
||||
const addressFields = await this.page.$$('input[placeholder*="地址"]');
|
||||
if (addressFields.length > 0) {
|
||||
logger.info(this.siteName, ' → 填写地址信息...');
|
||||
// 填写简单的地址
|
||||
await addressFields[0].type('Macau', { delay: 100 });
|
||||
if (addressFields[1]) {
|
||||
await this.human.randomDelay(300, 500);
|
||||
await addressFields[1].type('Macao', { delay: 100 });
|
||||
}
|
||||
}
|
||||
|
||||
// 9. 点击订阅按钮并检测卡片拒绝(支持重试)
|
||||
logger.info(this.siteName, ' → 点击订阅按钮...');
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
const maxRetries = 5; // 最多重试5次
|
||||
let retryCount = 0;
|
||||
let paymentSuccess = false;
|
||||
|
||||
while (!paymentSuccess && retryCount < maxRetries) {
|
||||
const submitButton = await this.page.$('button[type="submit"][data-testid="hosted-payment-submit-button"]');
|
||||
if (!submitButton) {
|
||||
logger.warn(this.siteName, ' → 未找到订阅按钮');
|
||||
break;
|
||||
}
|
||||
|
||||
if (retryCount > 0) {
|
||||
logger.info(this.siteName, ` → 第 ${retryCount + 1} 次尝试提交...`);
|
||||
}
|
||||
|
||||
await submitButton.click();
|
||||
logger.success(this.siteName, ' → ✓ 已点击订阅按钮');
|
||||
|
||||
// 等待一下让页面响应
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
|
||||
// 检测是否出现"银行卡被拒绝"错误
|
||||
const cardRejected = await this.page.evaluate(() => {
|
||||
const errorDiv = document.querySelector('.FieldError-container');
|
||||
if (errorDiv) {
|
||||
const errorText = errorDiv.textContent;
|
||||
return errorText.includes('银行卡被拒绝') ||
|
||||
errorText.includes('card was declined') ||
|
||||
errorText.includes('被拒绝');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 hCaptcha 验证码
|
||||
*/
|
||||
async handleHCaptcha() {
|
||||
const hasHCaptcha = await this.page.evaluate(() => {
|
||||
const hcaptchaFrame = document.querySelector('iframe[src*="hcaptcha.com"]');
|
||||
const hcaptchaCheckbox = document.querySelector('.h-captcha');
|
||||
return !!(hcaptchaFrame || hcaptchaCheckbox);
|
||||
});
|
||||
|
||||
if (!hasHCaptcha) return true;
|
||||
|
||||
logger.warn(this.siteName, ' → 检测到 hCaptcha 验证码');
|
||||
|
||||
if (this.capsolver.apiKey) {
|
||||
try {
|
||||
logger.info(this.siteName, ' → 尝试使用 CapSolver 自动识别...');
|
||||
const siteKey = await this.page.evaluate(() => {
|
||||
const hcaptchaDiv = document.querySelector('.h-captcha');
|
||||
return hcaptchaDiv ? hcaptchaDiv.getAttribute('data-sitekey') : null;
|
||||
});
|
||||
|
||||
if (cardRejected) {
|
||||
retryCount++;
|
||||
logger.warn(this.siteName, ` → ⚠️ 银行卡被拒绝!(第 ${retryCount}/${maxRetries} 次)`);
|
||||
|
||||
if (retryCount >= maxRetries) {
|
||||
logger.error(this.siteName, ` → ✗ 已达到最大重试次数 (${maxRetries})`);
|
||||
throw new Error(`银行卡被拒绝,已重试 ${maxRetries} 次`);
|
||||
}
|
||||
|
||||
// 生成新的银行卡
|
||||
logger.info(this.siteName, ' → 生成新的银行卡信息...');
|
||||
const cardGen = new CardGenerator();
|
||||
const newCard = cardGen.generate('unionpay');
|
||||
logger.info(this.siteName, ` → 新卡号: ${newCard.number}`);
|
||||
logger.info(this.siteName, ` → 有效期: ${newCard.month}/${newCard.year}`);
|
||||
logger.info(this.siteName, ` → CVV: ${newCard.cvv}`);
|
||||
|
||||
// 更新卡信息
|
||||
this.cardInfo = {
|
||||
number: newCard.number,
|
||||
month: newCard.month,
|
||||
year: newCard.year,
|
||||
cvv: newCard.cvv,
|
||||
country: 'MO'
|
||||
};
|
||||
|
||||
// 清空并重新填写卡号
|
||||
logger.info(this.siteName, ' → 清空并重新填写卡号...');
|
||||
const cardNumberField = await this.page.$('#cardNumber');
|
||||
await cardNumberField.click({ clickCount: 3 });
|
||||
await this.page.keyboard.press('Backspace');
|
||||
await this.human.randomDelay(500, 1000);
|
||||
await cardNumberField.type(newCard.number, { delay: 250 });
|
||||
|
||||
// 清空并重新填写有效期
|
||||
logger.info(this.siteName, ' → 清空并重新填写有效期...');
|
||||
const cardExpiryField = await this.page.$('#cardExpiry');
|
||||
await cardExpiryField.click({ clickCount: 3 });
|
||||
await this.page.keyboard.press('Backspace');
|
||||
await this.human.randomDelay(300, 500);
|
||||
const expiry = `${newCard.month}${newCard.year}`;
|
||||
await cardExpiryField.type(expiry, { delay: 250 });
|
||||
|
||||
// 清空并重新填写CVC
|
||||
logger.info(this.siteName, ' → 清空并重新填写CVC...');
|
||||
const cardCvcField = await this.page.$('#cardCvc');
|
||||
await cardCvcField.click({ clickCount: 3 });
|
||||
await this.page.keyboard.press('Backspace');
|
||||
await this.human.randomDelay(300, 500);
|
||||
await cardCvcField.type(newCard.cvv, { delay: 250 });
|
||||
|
||||
logger.success(this.siteName, ' → ✓ 已更新银行卡信息,准备重试...');
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
|
||||
// 继续下一次循环重试
|
||||
continue;
|
||||
}
|
||||
|
||||
// 没有错误,等待支付处理完成
|
||||
logger.info(this.siteName, ' → 等待支付处理...');
|
||||
logger.info(this.siteName, ' → 将持续等待直到支付完成(无时间限制)...');
|
||||
|
||||
const paymentStartTime = Date.now();
|
||||
let paymentComplete = false;
|
||||
|
||||
while (!paymentComplete) {
|
||||
if (siteKey) {
|
||||
const currentUrl = this.page.url();
|
||||
|
||||
// 检测是否已经离开 Stripe 页面(支付成功的标志)
|
||||
if (!currentUrl.includes('stripe.com') && !currentUrl.includes('checkout.stripe.com')) {
|
||||
paymentComplete = true;
|
||||
paymentSuccess = true;
|
||||
const totalTime = ((Date.now() - paymentStartTime) / 1000).toFixed(1);
|
||||
|
||||
// 记录注册时间(支付成功的时间)
|
||||
this.registrationTime = new Date();
|
||||
logger.success(this.siteName, ` → ✓ 支付成功!已离开Stripe页面 (耗时: ${totalTime}秒)`);
|
||||
logger.info(this.siteName, ` → 当前页面: ${currentUrl}`);
|
||||
logger.info(this.siteName, ` → 注册时间: ${this.registrationTime.toLocaleString('zh-CN')}`);
|
||||
break;
|
||||
const token = await this.capsolver.solveHCaptcha(siteKey, currentUrl);
|
||||
await this.page.evaluate((token) => {
|
||||
const textarea = document.querySelector('[name="h-captcha-response"]');
|
||||
if (textarea) textarea.value = token;
|
||||
if (window.hcaptcha && window.hcaptcha.setResponse) {
|
||||
window.hcaptcha.setResponse(token);
|
||||
}
|
||||
}, token);
|
||||
logger.success(this.siteName, ' → ✓ hCaptcha 自动识别成功');
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(this.siteName, ` → ✗ 自动识别失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 手动等待
|
||||
logger.warn(this.siteName, ' → 请手动完成验证码(等待120秒)...');
|
||||
const startWait = Date.now();
|
||||
while (Date.now() - startWait < 120000) {
|
||||
const captchaSolved = await this.page.evaluate(() => {
|
||||
const response = document.querySelector('[name="h-captcha-response"]');
|
||||
return response && response.value.length > 0;
|
||||
});
|
||||
if (captchaSolved) {
|
||||
logger.success(this.siteName, ' → ✓ 验证码已完成');
|
||||
return true;
|
||||
}
|
||||
if ((Date.now() - startWait) % 10000 === 0) {
|
||||
const elapsed = Math.floor((Date.now() - startWait) / 1000);
|
||||
logger.info(this.siteName, ` → 等待验证码... (${elapsed}秒)`);
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查银行卡是否被拒绝
|
||||
*/
|
||||
async checkCardRejected() {
|
||||
return await this.page.evaluate(() => {
|
||||
const errorContainers = [
|
||||
'.FieldError-container',
|
||||
'[class*="Error"]',
|
||||
'[class*="error"]',
|
||||
'.error-message'
|
||||
];
|
||||
for (const selector of errorContainers) {
|
||||
const elements = document.querySelectorAll(selector);
|
||||
for (const el of elements) {
|
||||
const text = el.textContent || '';
|
||||
if (text.includes('银行卡') && text.includes('拒绝') ||
|
||||
text.includes('您的银行卡被拒绝了') ||
|
||||
text.includes('card was declined') ||
|
||||
text.includes('被拒绝') ||
|
||||
text.includes('declined')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 每5秒输出一次进度
|
||||
const elapsed = Date.now() - paymentStartTime;
|
||||
if (elapsed > 0 && elapsed % 5000 === 0) {
|
||||
logger.info(this.siteName, ` → 支付处理中... 已用时 ${(elapsed/1000).toFixed(0)}秒`);
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待支付成功
|
||||
*/
|
||||
async waitForPaymentSuccess() {
|
||||
const paymentStartTime = Date.now();
|
||||
logger.info(this.siteName, ' → 等待支付处理...');
|
||||
|
||||
while (true) {
|
||||
const currentUrl = this.page.url();
|
||||
if (!currentUrl.includes('stripe.com') && !currentUrl.includes('checkout.stripe.com')) {
|
||||
const totalTime = ((Date.now() - paymentStartTime) / 1000).toFixed(1);
|
||||
this.registrationTime = new Date();
|
||||
logger.success(this.siteName, ` → ✓ 支付成功!(耗时: ${totalTime}秒)`);
|
||||
logger.info(this.siteName, ` → 当前页面: ${currentUrl}`);
|
||||
return true;
|
||||
}
|
||||
const elapsed = Date.now() - paymentStartTime;
|
||||
if (elapsed > 0 && elapsed % 5000 === 0) {
|
||||
logger.info(this.siteName, ` → 支付处理中... (${(elapsed/1000).toFixed(0)}秒)`);
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交支付(递归重试)
|
||||
*/
|
||||
async submitPayment(retryCount = 0, maxRetries = 5) {
|
||||
if (retryCount >= maxRetries) {
|
||||
throw new Error(`银行卡被拒绝,已重试 ${maxRetries} 次`);
|
||||
}
|
||||
|
||||
if (retryCount > 0) {
|
||||
logger.info(this.siteName, ` → 第 ${retryCount + 1} 次尝试提交...`);
|
||||
}
|
||||
|
||||
// 点击订阅按钮
|
||||
const submitButton = await this.page.$('button[type="submit"][data-testid="hosted-payment-submit-button"]');
|
||||
if (!submitButton) {
|
||||
logger.warn(this.siteName, ' → 未找到订阅按钮');
|
||||
return false;
|
||||
}
|
||||
|
||||
await submitButton.click();
|
||||
logger.success(this.siteName, ' → ✓ 已点击订阅按钮');
|
||||
await this.human.randomDelay(3000, 5000);
|
||||
|
||||
// 处理验证码
|
||||
await this.handleHCaptcha();
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
|
||||
// 检查卡是否被拒绝
|
||||
const cardRejected = await this.checkCardRejected();
|
||||
if (cardRejected) {
|
||||
logger.warn(this.siteName, ` → ⚠️ 银行卡被拒绝!(第 ${retryCount + 1}/${maxRetries} 次)`);
|
||||
|
||||
// 额外等待页面稳定
|
||||
// 生成新卡
|
||||
logger.info(this.siteName, ' → 生成新的银行卡信息...');
|
||||
const cardGen = new CardGenerator();
|
||||
const newCard = cardGen.generate('unionpay');
|
||||
logger.info(this.siteName, ` → 新卡号: ${newCard.number}`);
|
||||
|
||||
this.cardInfo = {
|
||||
number: newCard.number,
|
||||
month: newCard.month,
|
||||
year: newCard.year,
|
||||
cvv: newCard.cvv,
|
||||
country: 'MO'
|
||||
};
|
||||
|
||||
// 重新填写卡信息
|
||||
await this.fillCardForm(newCard, true);
|
||||
logger.success(this.siteName, ' → ✓ 已更新银行卡信息');
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
|
||||
// 递归重试
|
||||
return await this.submitPayment(retryCount + 1, maxRetries);
|
||||
}
|
||||
|
||||
// 等待支付成功
|
||||
await this.waitForPaymentSuccess();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤6: 填写支付信息
|
||||
*/
|
||||
async step6_fillPayment() {
|
||||
logger.info(this.siteName, `[步骤 6/${this.getTotalSteps()}] 填写支付信息`);
|
||||
|
||||
try {
|
||||
await this.human.readPage(3, 5);
|
||||
|
||||
// 生成银行卡
|
||||
logger.info(this.siteName, ' → 生成银联卡信息...');
|
||||
const cardGen = new CardGenerator();
|
||||
const card = cardGen.generate('unionpay');
|
||||
logger.info(this.siteName, ` → 卡号: ${card.number}`);
|
||||
logger.info(this.siteName, ` → 有效期: ${card.month}/${card.year}`);
|
||||
logger.info(this.siteName, ` → CVV: ${card.cvv}`);
|
||||
|
||||
this.cardInfo = {
|
||||
number: card.number,
|
||||
month: card.month,
|
||||
year: card.year,
|
||||
cvv: card.cvv,
|
||||
country: 'MO'
|
||||
};
|
||||
|
||||
// 填写卡表单
|
||||
await this.fillCardForm(card);
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
|
||||
// 提交支付(递归重试)
|
||||
await this.submitPayment();
|
||||
|
||||
this.currentStep = 6;
|
||||
logger.success(this.siteName, `步骤 6 完成`);
|
||||
|
||||
@ -1278,34 +1348,8 @@ class WindsurfRegister {
|
||||
logger.warn(this.siteName, ' → ⚠️ 未找到账单周期信息');
|
||||
}
|
||||
|
||||
// 4. 汇总打印所有信息
|
||||
logger.info(this.siteName, '');
|
||||
logger.info(this.siteName, '┌─────────────────────────────────────────────────────┐');
|
||||
logger.info(this.siteName, '│ 订阅信息汇总 │');
|
||||
logger.info(this.siteName, '└─────────────────────────────────────────────────────┘');
|
||||
|
||||
if (this.registrationTime) {
|
||||
logger.info(this.siteName, `📅 注册时间: ${this.registrationTime.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})}`);
|
||||
}
|
||||
|
||||
if (quotaInfo) {
|
||||
logger.info(this.siteName, `📊 使用配额: ${quotaInfo.used} / ${quotaInfo.total}`);
|
||||
logger.info(this.siteName, `💎 剩余配额: ${quotaInfo.total - parseFloat(quotaInfo.used)}`);
|
||||
}
|
||||
|
||||
if (billingInfo && billingInfo.days) {
|
||||
logger.info(this.siteName, `🔄 下次账单: ${billingInfo.days} 天后`);
|
||||
logger.info(this.siteName, `📆 账单日期: ${billingInfo.date}`);
|
||||
}
|
||||
|
||||
logger.info(this.siteName, '');
|
||||
// 4. 简要打印关键信息
|
||||
logger.success(this.siteName, `✓ 配额: ${quotaInfo ? `${quotaInfo.used}/${quotaInfo.total}` : 'N/A'} | 下次账单: ${billingInfo?.days || 'N/A'}天后`);
|
||||
|
||||
// 保存订阅信息供后续使用
|
||||
this.quotaInfo = quotaInfo;
|
||||
|
||||
202
src/tools/account-register/utils/capsolver-api.js
Normal file
202
src/tools/account-register/utils/capsolver-api.js
Normal file
@ -0,0 +1,202 @@
|
||||
/**
|
||||
* 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 createTaskResponse = await axios.post(`${this.apiUrl}/createTask`, {
|
||||
clientKey: this.apiKey,
|
||||
task: {
|
||||
type: 'HCaptchaTaskProxyless',
|
||||
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); // 每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) {
|
||||
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;
|
||||
Loading…
Reference in New Issue
Block a user