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_USER=windsurf-auto-register
|
||||||
MYSQL_PASSWORD=Qichen5210523
|
MYSQL_PASSWORD=Qichen5210523
|
||||||
MYSQL_DATABASE=windsurf-auto-register
|
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 { DEFAULT_CONFIG } = require('../config');
|
||||||
const CardGenerator = require('../../card-generator/generator');
|
const CardGenerator = require('../../card-generator/generator');
|
||||||
const database = require('../../database');
|
const database = require('../../database');
|
||||||
|
const CapSolverAPI = require('../utils/capsolver-api');
|
||||||
|
|
||||||
class WindsurfRegister {
|
class WindsurfRegister {
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
@ -26,6 +27,7 @@ class WindsurfRegister {
|
|||||||
this.dataGen = new AccountDataGenerator();
|
this.dataGen = new AccountDataGenerator();
|
||||||
this.human = new HumanBehavior();
|
this.human = new HumanBehavior();
|
||||||
this.emailService = new EmailVerificationService();
|
this.emailService = new EmailVerificationService();
|
||||||
|
this.capsolver = new CapSolverAPI();
|
||||||
this.browser = null;
|
this.browser = null;
|
||||||
this.page = null;
|
this.page = null;
|
||||||
this.currentStep = 0;
|
this.currentStep = 0;
|
||||||
@ -922,44 +924,18 @@ class WindsurfRegister {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 步骤6: 填写支付信息
|
* 填写银行卡表单
|
||||||
*/
|
*/
|
||||||
async step6_fillPayment() {
|
async fillCardForm(card, isRetry = false) {
|
||||||
logger.info(this.siteName, `[步骤 6/${this.getTotalSteps()}] 填写支付信息`);
|
if (!isRetry) {
|
||||||
|
// 首次填写:选择支付方式
|
||||||
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. 点击选择"银行卡"支付方式
|
|
||||||
logger.info(this.siteName, ' → 选择银行卡支付方式...');
|
logger.info(this.siteName, ' → 选择银行卡支付方式...');
|
||||||
const cardRadio = await this.page.$('input[type="radio"][value="card"]');
|
const cardRadio = await this.page.$('input[type="radio"][value="card"]');
|
||||||
if (cardRadio) {
|
if (cardRadio) {
|
||||||
await cardRadio.click();
|
await cardRadio.click();
|
||||||
logger.success(this.siteName, ' → ✓ 已点击银行卡选项');
|
logger.success(this.siteName, ' → ✓ 已点击银行卡选项');
|
||||||
|
|
||||||
// 等待支付表单完全加载(等待骨架屏消失,表单元素出现)
|
|
||||||
logger.info(this.siteName, ' → 等待支付表单加载...');
|
|
||||||
await this.human.randomDelay(3000, 5000);
|
await this.human.randomDelay(3000, 5000);
|
||||||
|
|
||||||
// 等待所有必需的支付字段都加载完成
|
|
||||||
try {
|
try {
|
||||||
await this.page.waitForFunction(
|
await this.page.waitForFunction(
|
||||||
() => {
|
() => {
|
||||||
@ -967,7 +943,6 @@ class WindsurfRegister {
|
|||||||
const cardExpiry = document.querySelector('#cardExpiry');
|
const cardExpiry = document.querySelector('#cardExpiry');
|
||||||
const cardCvc = document.querySelector('#cardCvc');
|
const cardCvc = document.querySelector('#cardCvc');
|
||||||
const billingName = document.querySelector('#billingName');
|
const billingName = document.querySelector('#billingName');
|
||||||
// 检查所有字段都存在且可见
|
|
||||||
return cardNumber && cardExpiry && cardCvc && billingName;
|
return cardNumber && cardExpiry && cardCvc && billingName;
|
||||||
},
|
},
|
||||||
{ timeout: 30000 }
|
{ timeout: 30000 }
|
||||||
@ -977,186 +952,281 @@ class WindsurfRegister {
|
|||||||
logger.warn(this.siteName, ` → 等待表单超时: ${e.message}`);
|
logger.warn(this.siteName, ` → 等待表单超时: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 填写卡号
|
// 填写卡号
|
||||||
logger.info(this.siteName, ' → 填写卡号...');
|
logger.info(this.siteName, ' → 填写卡号...');
|
||||||
await this.page.waitForSelector('#cardNumber', { visible: true, timeout: 10000 });
|
const cardNumberField = await this.page.$('#cardNumber');
|
||||||
await this.page.click('#cardNumber');
|
await cardNumberField.click();
|
||||||
await this.human.randomDelay(500, 1000);
|
await this.human.randomDelay(300, 500);
|
||||||
await this.page.type('#cardNumber', card.number, { delay: 250 });
|
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 });
|
||||||
|
|
||||||
// 4. 填写有效期(月份/年份)
|
// 填写有效期
|
||||||
logger.info(this.siteName, ' → 填写有效期...');
|
logger.info(this.siteName, ' → 填写有效期...');
|
||||||
await this.page.click('#cardExpiry');
|
const cardExpiryField = await this.page.$('#cardExpiry');
|
||||||
await this.human.randomDelay(300, 500);
|
await cardExpiryField.click();
|
||||||
const expiry = `${card.month}${card.year}`; // 格式: MMYY
|
await this.human.randomDelay(200, 300);
|
||||||
await this.page.type('#cardExpiry', expiry, { delay: 250 });
|
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 });
|
||||||
|
|
||||||
// 5. 填写CVC
|
// 填写CVC
|
||||||
logger.info(this.siteName, ' → 填写CVC...');
|
logger.info(this.siteName, ' → 填写CVC...');
|
||||||
await this.page.click('#cardCvc');
|
const cardCvcField = await this.page.$('#cardCvc');
|
||||||
await this.human.randomDelay(300, 500);
|
await cardCvcField.click();
|
||||||
await this.page.type('#cardCvc', card.cvv, { delay: 250 });
|
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 });
|
||||||
|
|
||||||
// 6. 填写持卡人姓名
|
if (!isRetry) {
|
||||||
|
// 首次填写:填写持卡人姓名和地址
|
||||||
logger.info(this.siteName, ' → 填写持卡人姓名...');
|
logger.info(this.siteName, ' → 填写持卡人姓名...');
|
||||||
await this.page.click('#billingName');
|
await this.page.click('#billingName');
|
||||||
await this.human.randomDelay(300, 500);
|
await this.human.randomDelay(300, 500);
|
||||||
const fullName = `${this.accountData.firstName} ${this.accountData.lastName}`;
|
const fullName = `${this.accountData.firstName} ${this.accountData.lastName}`;
|
||||||
await this.page.type('#billingName', fullName, { delay: 200 });
|
await this.page.type('#billingName', fullName, { delay: 200 });
|
||||||
|
|
||||||
// 7. 选择地址:中国澳门特别行政区
|
|
||||||
logger.info(this.siteName, ' → 选择地址:中国澳门特别行政区...');
|
logger.info(this.siteName, ' → 选择地址:中国澳门特别行政区...');
|
||||||
await this.page.select('#billingCountry', 'MO');
|
await this.page.select('#billingCountry', 'MO');
|
||||||
await this.human.randomDelay(1000, 2000);
|
await this.human.randomDelay(1000, 2000);
|
||||||
|
|
||||||
// 8. 填写地址信息(如果需要)
|
|
||||||
// 等待地址字段加载
|
|
||||||
await this.human.randomDelay(1000, 2000);
|
|
||||||
|
|
||||||
// 检查是否需要填写地址行1和行2
|
|
||||||
const addressFields = await this.page.$$('input[placeholder*="地址"]');
|
const addressFields = await this.page.$$('input[placeholder*="地址"]');
|
||||||
if (addressFields.length > 0) {
|
if (addressFields.length > 0) {
|
||||||
logger.info(this.siteName, ' → 填写地址信息...');
|
logger.info(this.siteName, ' → 填写地址信息...');
|
||||||
// 填写简单的地址
|
|
||||||
await addressFields[0].type('Macau', { delay: 100 });
|
await addressFields[0].type('Macau', { delay: 100 });
|
||||||
if (addressFields[1]) {
|
if (addressFields[1]) {
|
||||||
await this.human.randomDelay(300, 500);
|
await this.human.randomDelay(300, 500);
|
||||||
await addressFields[1].type('Macao', { delay: 100 });
|
await addressFields[1].type('Macao', { delay: 100 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 9. 点击订阅按钮并检测卡片拒绝(支持重试)
|
/**
|
||||||
logger.info(this.siteName, ' → 点击订阅按钮...');
|
* 处理 hCaptcha 验证码
|
||||||
await this.human.randomDelay(2000, 3000);
|
*/
|
||||||
const maxRetries = 5; // 最多重试5次
|
async handleHCaptcha() {
|
||||||
let retryCount = 0;
|
const hasHCaptcha = await this.page.evaluate(() => {
|
||||||
let paymentSuccess = false;
|
const hcaptchaFrame = document.querySelector('iframe[src*="hcaptcha.com"]');
|
||||||
|
const hcaptchaCheckbox = document.querySelector('.h-captcha');
|
||||||
|
return !!(hcaptchaFrame || hcaptchaCheckbox);
|
||||||
|
});
|
||||||
|
|
||||||
while (!paymentSuccess && retryCount < maxRetries) {
|
if (!hasHCaptcha) return true;
|
||||||
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.warn(this.siteName, ' → 检测到 hCaptcha 验证码');
|
||||||
logger.info(this.siteName, ` → 第 ${retryCount + 1} 次尝试提交...`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await submitButton.click();
|
if (this.capsolver.apiKey) {
|
||||||
logger.success(this.siteName, ' → ✓ 已点击订阅按钮');
|
try {
|
||||||
|
logger.info(this.siteName, ' → 尝试使用 CapSolver 自动识别...');
|
||||||
// 等待一下让页面响应
|
const siteKey = await this.page.evaluate(() => {
|
||||||
await this.human.randomDelay(2000, 3000);
|
const hcaptchaDiv = document.querySelector('.h-captcha');
|
||||||
|
return hcaptchaDiv ? hcaptchaDiv.getAttribute('data-sitekey') : null;
|
||||||
// 检测是否出现"银行卡被拒绝"错误
|
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (cardRejected) {
|
if (siteKey) {
|
||||||
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) {
|
|
||||||
const currentUrl = this.page.url();
|
const currentUrl = this.page.url();
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 检测是否已经离开 Stripe 页面(支付成功的标志)
|
// 手动等待
|
||||||
if (!currentUrl.includes('stripe.com') && !currentUrl.includes('checkout.stripe.com')) {
|
logger.warn(this.siteName, ' → 请手动完成验证码(等待120秒)...');
|
||||||
paymentComplete = true;
|
const startWait = Date.now();
|
||||||
paymentSuccess = true;
|
while (Date.now() - startWait < 120000) {
|
||||||
const totalTime = ((Date.now() - paymentStartTime) / 1000).toFixed(1);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// 记录注册时间(支付成功的时间)
|
/**
|
||||||
this.registrationTime = new Date();
|
* 检查银行卡是否被拒绝
|
||||||
logger.success(this.siteName, ` → ✓ 支付成功!已离开Stripe页面 (耗时: ${totalTime}秒)`);
|
*/
|
||||||
logger.info(this.siteName, ` → 当前页面: ${currentUrl}`);
|
async checkCardRejected() {
|
||||||
logger.info(this.siteName, ` → 注册时间: ${this.registrationTime.toLocaleString('zh-CN')}`);
|
return await this.page.evaluate(() => {
|
||||||
break;
|
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);
|
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;
|
this.currentStep = 6;
|
||||||
logger.success(this.siteName, `步骤 6 完成`);
|
logger.success(this.siteName, `步骤 6 完成`);
|
||||||
|
|
||||||
@ -1278,34 +1348,8 @@ class WindsurfRegister {
|
|||||||
logger.warn(this.siteName, ' → ⚠️ 未找到账单周期信息');
|
logger.warn(this.siteName, ' → ⚠️ 未找到账单周期信息');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 汇总打印所有信息
|
// 4. 简要打印关键信息
|
||||||
logger.info(this.siteName, '');
|
logger.success(this.siteName, `✓ 配额: ${quotaInfo ? `${quotaInfo.used}/${quotaInfo.total}` : 'N/A'} | 下次账单: ${billingInfo?.days || 'N/A'}天后`);
|
||||||
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, '');
|
|
||||||
|
|
||||||
// 保存订阅信息供后续使用
|
// 保存订阅信息供后续使用
|
||||||
this.quotaInfo = quotaInfo;
|
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