aaaaa
This commit is contained in:
parent
10439f5af1
commit
585d374140
@ -22,7 +22,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^11.0.0",
|
||||
"puppeteer": "npm:rebrowser-puppeteer@^23.9.0",
|
||||
"puppeteer-real-browser": "^1.3.12",
|
||||
"imap": "^0.8.19",
|
||||
"mailparser": "^3.6.5"
|
||||
},
|
||||
|
||||
@ -64,13 +64,6 @@ class EmailVerificationService {
|
||||
|
||||
logger.info('EmailVerification', `✓ 找到 ${emails.length} 封未读邮件`);
|
||||
|
||||
// 按日期倒序排序(最新的在前)
|
||||
emails.sort((a, b) => {
|
||||
const dateA = a.date ? new Date(a.date).getTime() : 0;
|
||||
const dateB = b.date ? new Date(b.date).getTime() : 0;
|
||||
return dateB - dateA;
|
||||
});
|
||||
|
||||
// 打印最近5条邮件信息
|
||||
const recentEmails = emails.slice(0, 5);
|
||||
logger.info('EmailVerification', '='.repeat(60));
|
||||
@ -85,12 +78,28 @@ class EmailVerificationService {
|
||||
logger.info('EmailVerification', '='.repeat(60));
|
||||
|
||||
// 查找匹配的邮件并提取验证码
|
||||
// 注意:QQ邮箱转发邮件后,收件人字段会被改写为QQ邮箱地址,所以不能检查收件人
|
||||
// 改为只检查发件人和主题,并按时间取最新的
|
||||
for (const email of emails) {
|
||||
if (isResolved) return;
|
||||
|
||||
logger.info('EmailVerification', `检查邮件: 发件人="${email.from}", 主题="${email.subject}", 时间="${email.date}"`);
|
||||
logger.info('EmailVerification', `检查邮件: 发件人="${email.from}", 主题="${email.subject}", 收件人="${email.to}", 时间="${email.date}"`);
|
||||
|
||||
// 收件人匹配:从收件人字段提取邮箱地址
|
||||
// 格式可能是: "Name<email@example.com>" 或 "email@example.com"
|
||||
const extractEmail = (str) => {
|
||||
if (!str) return '';
|
||||
const match = str.match(/<(.+?)>/) || str.match(/([^\s<>]+@[^\s<>]+)/);
|
||||
return match ? match[1].toLowerCase() : str.toLowerCase();
|
||||
};
|
||||
|
||||
const emailTo = extractEmail(email.to);
|
||||
const targetEmail = recipientEmail.toLowerCase();
|
||||
|
||||
if (!emailTo.includes(targetEmail)) {
|
||||
logger.info('EmailVerification', ` ✗ 跳过:收件人不匹配(期望:${targetEmail},实际:${emailTo})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.success('EmailVerification', ` ✓ 收件人匹配!`);
|
||||
|
||||
for (const parser of this.parsers) {
|
||||
if (parser.canParse(email)) {
|
||||
|
||||
@ -34,10 +34,11 @@ class WindsurfRegister {
|
||||
this.steps = [
|
||||
{ id: 1, name: '填写基本信息', method: 'step1_fillBasicInfo' },
|
||||
{ id: 2, name: '设置密码', method: 'step2_setPassword' },
|
||||
{ id: 3, name: '邮箱验证', method: 'step3_emailVerification' },
|
||||
{ id: 4, name: '跳过问卷', method: 'step4_skipSurvey' },
|
||||
{ id: 5, name: '选择计划', method: 'step5_selectPlan' },
|
||||
{ id: 6, name: '填写支付信息', method: 'step6_fillPayment' },
|
||||
{ id: 3, name: 'Cloudflare验证', method: 'step3_cloudflareVerification' },
|
||||
{ id: 4, name: '邮箱验证码', method: 'step4_emailVerificationCode' },
|
||||
{ id: 5, name: '跳过问卷', method: 'step5_skipSurvey' },
|
||||
{ id: 6, name: '选择计划', method: 'step6_selectPlan' },
|
||||
{ id: 7, name: '填写支付信息', method: 'step7_fillPayment' },
|
||||
// 根据实际注册流程继续添加
|
||||
];
|
||||
}
|
||||
@ -66,37 +67,88 @@ class WindsurfRegister {
|
||||
|
||||
/**
|
||||
* 通用方法:点击按钮并等待页面跳转
|
||||
* 使用页面特征(URL、DOM元素)来判断是否真的跳转了
|
||||
* @param {Function} checkPageChanged - 检查页面是否已跳转的函数,返回Promise<boolean>
|
||||
* @param {number} maxAttempts - 最多尝试次数
|
||||
* @param {string} actionName - 操作名称(用于日志)
|
||||
* @param {string} buttonText - 按钮文本过滤(可选,如"Continue")
|
||||
* @returns {Promise<boolean>} - 是否成功跳转
|
||||
*/
|
||||
async clickButtonAndWaitForPageChange(checkPageChanged, maxAttempts = 5, actionName = '点击按钮') {
|
||||
async clickButtonAndWaitForPageChange(checkPageChanged, maxAttempts = 5, actionName = '点击按钮', buttonText = null) {
|
||||
let pageChanged = false;
|
||||
let attempts = 0;
|
||||
let lastClickedAt = 0; // 记录上次点击的时间,避免重复点击
|
||||
|
||||
while (!pageChanged && attempts < maxAttempts) {
|
||||
attempts++;
|
||||
|
||||
// 记录点击前的页面特征
|
||||
const beforeUrl = this.page.url();
|
||||
const beforeHtml = await this.page.content();
|
||||
|
||||
// 查找未禁用的按钮
|
||||
const button = await this.page.$('button:not([disabled])');
|
||||
if (button) {
|
||||
const text = await this.page.evaluate(el => el.textContent.trim(), button);
|
||||
const buttons = await this.page.$$('button:not([disabled])');
|
||||
let targetButton = null;
|
||||
|
||||
// 如果指定了按钮文本,找到匹配的按钮
|
||||
if (buttonText) {
|
||||
for (const btn of buttons) {
|
||||
const text = await this.page.evaluate(el => el.textContent.trim(), btn);
|
||||
// 不区分大小写匹配
|
||||
if (text.toLowerCase().includes(buttonText.toLowerCase())) {
|
||||
targetButton = btn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没找到匹配的按钮,使用第一个(回退策略)
|
||||
if (!targetButton && buttons.length > 0) {
|
||||
logger.warn(this.siteName, ` → 未找到包含"${buttonText}"的按钮,使用第一个按钮`);
|
||||
targetButton = buttons[0];
|
||||
}
|
||||
} else {
|
||||
// 没有指定文本,使用第一个按钮
|
||||
targetButton = buttons[0];
|
||||
}
|
||||
|
||||
if (targetButton) {
|
||||
const text = await this.page.evaluate(el => el.textContent.trim(), targetButton);
|
||||
logger.info(this.siteName, ` → 第${attempts}次${actionName}"${text}"...`);
|
||||
await button.click();
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
|
||||
// 点击按钮
|
||||
const now = Date.now();
|
||||
if (now - lastClickedAt < 3000) {
|
||||
logger.warn(this.siteName, ` → 距离上次点击时间太短,等待中...`);
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
}
|
||||
|
||||
await targetButton.click();
|
||||
lastClickedAt = Date.now();
|
||||
|
||||
// 等待页面响应
|
||||
await this.human.randomDelay(3000, 4000);
|
||||
|
||||
// 检查URL是否改变
|
||||
const afterUrl = this.page.url();
|
||||
const urlChanged = afterUrl !== beforeUrl;
|
||||
|
||||
// 检查页面内容是否改变(至少改变10%)
|
||||
const afterHtml = await this.page.content();
|
||||
const contentChanged = Math.abs(afterHtml.length - beforeHtml.length) / beforeHtml.length > 0.1;
|
||||
|
||||
// 使用自定义检查函数判断页面是否跳转
|
||||
const changed = await checkPageChanged();
|
||||
const customChanged = await checkPageChanged();
|
||||
|
||||
if (changed) {
|
||||
logger.info(this.siteName, ` → URL变化: ${urlChanged}, 内容变化: ${contentChanged}, 自定义检查: ${customChanged}`);
|
||||
|
||||
if (urlChanged || customChanged) {
|
||||
pageChanged = true;
|
||||
logger.success(this.siteName, ` → ✓ 页面已跳转`);
|
||||
logger.success(this.siteName, ` → ✓ 页面已跳转 (${urlChanged ? 'URL变化' : ''}${customChanged ? ' 元素变化' : ''})`);
|
||||
break;
|
||||
}
|
||||
|
||||
logger.warn(this.siteName, ` → 页面未跳转,${attempts}/${maxAttempts}`);
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
await this.human.randomDelay(1000, 2000);
|
||||
} else {
|
||||
logger.warn(this.siteName, ` → 未找到可点击的按钮`);
|
||||
break;
|
||||
@ -131,13 +183,12 @@ class WindsurfRegister {
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化浏览器(使用rebrowser-puppeteer,自带反检测)
|
||||
* 初始化浏览器(使用puppeteer-real-browser,自动处理Cloudflare)
|
||||
*/
|
||||
async initBrowser() {
|
||||
// rebrowser-puppeteer 已经打好补丁,无需额外配置
|
||||
const puppeteer = require('puppeteer');
|
||||
const { connect } = require('puppeteer-real-browser');
|
||||
|
||||
logger.info(this.siteName, '启动浏览器(反检测模式)...');
|
||||
logger.info(this.siteName, '启动浏览器(自动Cloudflare绕过模式)...');
|
||||
|
||||
// 随机视口大小(模拟不同设备)
|
||||
const viewports = [
|
||||
@ -148,39 +199,36 @@ class WindsurfRegister {
|
||||
];
|
||||
const viewport = viewports[Math.floor(Math.random() * viewports.length)];
|
||||
|
||||
this.browser = await puppeteer.launch({
|
||||
headless: false, // 非无头模式更难被检测
|
||||
// 使用 puppeteer-real-browser 连接,启用 turnstile 自动处理 Cloudflare
|
||||
const result = await connect({
|
||||
turnstile: true, // 自动处理 Cloudflare Turnstile
|
||||
headless: false,
|
||||
args: [
|
||||
'--no-sandbox',
|
||||
'--disable-setuid-sandbox',
|
||||
'--disable-blink-features=AutomationControlled',
|
||||
'--disable-dev-shm-usage',
|
||||
`--window-size=${viewport.width},${viewport.height}`
|
||||
],
|
||||
ignoreDefaultArgs: ['--enable-automation']
|
||||
]
|
||||
});
|
||||
|
||||
this.page = await this.browser.newPage();
|
||||
this.browser = result.browser;
|
||||
this.page = result.page;
|
||||
|
||||
// 设置随机视口
|
||||
await this.page.setViewport(viewport);
|
||||
|
||||
// 随机用户代理
|
||||
const userAgents = [
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'
|
||||
];
|
||||
await this.page.setUserAgent(
|
||||
userAgents[Math.floor(Math.random() * userAgents.length)]
|
||||
);
|
||||
|
||||
// 设置语言和时区
|
||||
await this.page.setExtraHTTPHeaders({
|
||||
'Accept-Language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7'
|
||||
});
|
||||
|
||||
// 设置更长的导航超时时间
|
||||
await this.page.setDefaultNavigationTimeout(60000);
|
||||
|
||||
logger.success(this.siteName, `浏览器启动成功 (${viewport.width}x${viewport.height})`);
|
||||
|
||||
// 浏览器启动后等待一段时间,确保完全准备好
|
||||
logger.info(this.siteName, '等待浏览器完全准备...');
|
||||
await this.human.randomDelay(3000, 5000);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -256,7 +304,7 @@ class WindsurfRegister {
|
||||
return hasPasswordField || urlChanged;
|
||||
};
|
||||
|
||||
await this.clickButtonAndWaitForPageChange(checkPageChanged, 5, '点击Continue');
|
||||
await this.clickButtonAndWaitForPageChange(checkPageChanged, 5, '点击Continue', 'Continue');
|
||||
|
||||
// 额外等待确保页面稳定
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
@ -302,12 +350,10 @@ class WindsurfRegister {
|
||||
logger.info(this.siteName, ' → 等待密码验证...');
|
||||
await this.human.randomDelay(1000, 2000);
|
||||
|
||||
// 移除旧的验证逻辑,将在步骤3之前单独处理
|
||||
|
||||
// 查找并点击Continue按钮
|
||||
logger.info(this.siteName, ' → 点击Continue按钮...');
|
||||
|
||||
// 等待按钮变为可点击状态(不再disabled)- 步骤2
|
||||
// 等待按钮激活
|
||||
try {
|
||||
await this.page.waitForFunction(
|
||||
() => {
|
||||
@ -316,35 +362,31 @@ class WindsurfRegister {
|
||||
const text = button.textContent.trim();
|
||||
return text === 'Continue' && !button.disabled;
|
||||
},
|
||||
{ timeout: 20000 } // 增加超时时间,因为逐字符输入较慢
|
||||
{ timeout: 20000 }
|
||||
);
|
||||
|
||||
logger.info(this.siteName, ' → 按钮已激活');
|
||||
|
||||
// 使用更精确的选择器
|
||||
const button = await this.page.$('button:not([disabled])');
|
||||
if (button) {
|
||||
const text = await this.page.evaluate(el => el.textContent.trim(), button);
|
||||
if (text === 'Continue') {
|
||||
// 同时等待导航和点击
|
||||
await Promise.all([
|
||||
this.page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 15000 }).catch(() => {}),
|
||||
this.human.humanClick(this.page, 'button:not([disabled])')
|
||||
]);
|
||||
logger.success(this.siteName, ' → 已点击Continue按钮并等待跳转');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(this.siteName, ' → 按钮等待超时,尝试按Enter键');
|
||||
await this.human.randomDelay(500, 1000);
|
||||
await Promise.all([
|
||||
this.page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 15000 }).catch(() => {}),
|
||||
this.page.keyboard.press('Enter')
|
||||
]);
|
||||
} catch (e) {
|
||||
logger.warn(this.siteName, ' → 按钮等待超时,尝试继续');
|
||||
}
|
||||
|
||||
// 额外等待确保页面稳定
|
||||
await this.human.randomDelay(1000, 2000);
|
||||
// 使用通用方法点击按钮并等待页面跳转
|
||||
// 密码页面点击后会出现Cloudflare验证,检查验证元素是否出现
|
||||
const checkPageChanged = async () => {
|
||||
// 检查是否出现了Cloudflare验证页面的特征
|
||||
const hasCloudflare = await this.page.$('iframe[src*="challenges.cloudflare.com"]').then(el => !!el).catch(() => false);
|
||||
// 或者检查密码输入框是否消失
|
||||
const noPasswordField = await this.page.$('#password').then(el => !el).catch(() => true);
|
||||
return hasCloudflare || noPasswordField;
|
||||
};
|
||||
|
||||
const success = await this.clickButtonAndWaitForPageChange(checkPageChanged, 3, '点击Continue', 'Continue');
|
||||
|
||||
if (!success) {
|
||||
// 如果点击失败,尝试按Enter键
|
||||
logger.warn(this.siteName, ' → 点击失败,尝试按Enter键');
|
||||
await this.page.keyboard.press('Enter');
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
}
|
||||
|
||||
this.currentStep = 2;
|
||||
logger.success(this.siteName, `步骤 2 完成`);
|
||||
@ -456,27 +498,56 @@ class WindsurfRegister {
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤3: 邮箱验证
|
||||
* 步骤3: Cloudflare验证并进入验证码页面
|
||||
*/
|
||||
async step3_emailVerification() {
|
||||
logger.info(this.siteName, `[步骤 3/${this.getTotalSteps()}] 邮箱验证`);
|
||||
async step3_cloudflareVerification() {
|
||||
logger.info(this.siteName, `[步骤 3/${this.getTotalSteps()}] Cloudflare验证`);
|
||||
|
||||
// 先处理Cloudflare验证(如果有)
|
||||
const verifyResult = await this.handleCloudflareVerification();
|
||||
if (verifyResult === 'failed' || verifyResult === 'error') {
|
||||
throw new Error('Cloudflare验证失败,无法继续');
|
||||
}
|
||||
if (verifyResult === 'manual') {
|
||||
logger.info(this.siteName, 'Cloudflare需要手动验证,已等待完成');
|
||||
// puppeteer-real-browser 的 turnstile 会自动处理 Cloudflare
|
||||
logger.info(this.siteName, '[Cloudflare] turnstile 自动处理中...');
|
||||
|
||||
// 等待一段时间让 Cloudflare 自动验证完成
|
||||
await this.human.randomDelay(5000, 8000);
|
||||
logger.success(this.siteName, '[Cloudflare] ✓ 自动验证完成');
|
||||
|
||||
// 点击 Cloudflare 页面的 Continue 按钮进入验证码页面
|
||||
logger.info(this.siteName, '[Cloudflare] 点击Continue按钮进入验证码页面...');
|
||||
|
||||
const currentUrl = this.page.url();
|
||||
logger.info(this.siteName, ` → 当前URL: ${currentUrl}`);
|
||||
|
||||
// 使用通用方法检测页面跳转
|
||||
const checkPageChanged = async () => {
|
||||
const newUrl = this.page.url();
|
||||
// 检查URL是否变化或是否有验证码输入框
|
||||
const hasCodeInput = await this.page.$('input[type="text"]').then(el => !!el).catch(() => false);
|
||||
return newUrl !== currentUrl || hasCodeInput;
|
||||
};
|
||||
|
||||
// 点击Continue按钮并等待页面跳转
|
||||
const success = await this.clickButtonAndWaitForPageChange(checkPageChanged, 5, '点击Continue', 'Continue');
|
||||
|
||||
if (!success) {
|
||||
throw new Error('Cloudflare验证后页面未成功跳转到验证码页面');
|
||||
}
|
||||
|
||||
this.currentStep = 3;
|
||||
logger.success(this.siteName, `步骤 3 完成`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤4: 获取邮箱验证码并填写
|
||||
*/
|
||||
async step4_emailVerificationCode() {
|
||||
logger.info(this.siteName, `[步骤 4/${this.getTotalSteps()}] 邮箱验证码`);
|
||||
|
||||
try {
|
||||
// 等待验证码页面加载
|
||||
await this.human.readPage(1, 2);
|
||||
|
||||
// 延迟2秒后再获取验证码,让邮件有足够时间到达
|
||||
logger.info(this.siteName, ' → 延迟2秒,等待邮件到达...');
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
// 延迟5秒后再获取验证码,让邮件有足够时间到达
|
||||
logger.info(this.siteName, ' → 延迟5秒,等待邮件到达...');
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
// 获取验证码(从邮箱)
|
||||
logger.info(this.siteName, ' → 正在从邮箱获取验证码...');
|
||||
@ -547,8 +618,8 @@ class WindsurfRegister {
|
||||
|
||||
await this.clickButtonAndWaitForPageChange(checkPageChanged, 3, '点击Create account');
|
||||
|
||||
this.currentStep = 3;
|
||||
logger.success(this.siteName, `步骤 3 完成`);
|
||||
this.currentStep = 4;
|
||||
logger.success(this.siteName, `步骤 4 完成`);
|
||||
|
||||
} else {
|
||||
logger.error(this.siteName, ' → 未找到验证码输入框!');
|
||||
@ -557,7 +628,7 @@ class WindsurfRegister {
|
||||
// 等待用户手动输入
|
||||
await this.human.randomDelay(30000, 30000);
|
||||
|
||||
this.currentStep = 3;
|
||||
this.currentStep = 4;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
@ -567,10 +638,10 @@ class WindsurfRegister {
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤4: 跳过问卷调查
|
||||
* 步骤5: 跳过问卷调查
|
||||
*/
|
||||
async step4_skipSurvey() {
|
||||
logger.info(this.siteName, `[步骤 4/${this.getTotalSteps()}] 跳过问卷`);
|
||||
async step5_skipSurvey() {
|
||||
logger.info(this.siteName, `[步骤 5/${this.getTotalSteps()}] 跳过问卷`);
|
||||
|
||||
try {
|
||||
// 等待页面加载
|
||||
@ -599,11 +670,11 @@ class WindsurfRegister {
|
||||
// 等待页面跳转
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
|
||||
this.currentStep = 4;
|
||||
logger.success(this.siteName, `步骤 4 完成`);
|
||||
this.currentStep = 5;
|
||||
logger.success(this.siteName, `步骤 5 完成`);
|
||||
} else {
|
||||
logger.warn(this.siteName, ' → 未找到Skip按钮,可能已跳过此页面');
|
||||
this.currentStep = 4;
|
||||
this.currentStep = 5;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
@ -613,10 +684,10 @@ class WindsurfRegister {
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤5: 选择计划
|
||||
* 步骤6: 选择计划
|
||||
*/
|
||||
async step5_selectPlan() {
|
||||
logger.info(this.siteName, `[步骤 5/${this.getTotalSteps()}] 选择计划`);
|
||||
async step6_selectPlan() {
|
||||
logger.info(this.siteName, `[步骤 6/${this.getTotalSteps()}] 选择计划`);
|
||||
|
||||
try {
|
||||
// 等待页面加载
|
||||
@ -642,25 +713,33 @@ class WindsurfRegister {
|
||||
logger.info(this.siteName, ' → 点击"Select plan"按钮...');
|
||||
await selectButton.click();
|
||||
|
||||
// 等待页面跳转或加载
|
||||
// 等待页面跳转到支付页面
|
||||
logger.info(this.siteName, ' → 等待进入支付页面...');
|
||||
await this.human.randomDelay(3000, 5000);
|
||||
|
||||
this.currentStep = 5;
|
||||
logger.success(this.siteName, `步骤 5 完成`);
|
||||
} else {
|
||||
logger.warn(this.siteName, ' → 未找到Select plan按钮,尝试点击Skip');
|
||||
// 如果没有Select plan,尝试点击Skip
|
||||
const skipButtons = await this.page.$$('button');
|
||||
for (const btn of skipButtons) {
|
||||
const text = await this.page.evaluate(el => el.textContent?.trim(), btn);
|
||||
if (text && text.toLowerCase().includes('skip')) {
|
||||
await btn.click();
|
||||
logger.info(this.siteName, ' → 已点击Skip按钮');
|
||||
break;
|
||||
}
|
||||
// 验证是否进入支付页面(检查是否有Stripe元素或支付表单)
|
||||
const hasPaymentForm = await this.page.$('button[data-testid="card-accordion-item-button"]').then(el => !!el).catch(() => false);
|
||||
const hasStripeCheckout = await this.page.url().then(url => url.includes('checkout.stripe.com')).catch(() => false);
|
||||
|
||||
if (hasPaymentForm || hasStripeCheckout) {
|
||||
logger.success(this.siteName, ' → ✓ 已进入支付页面');
|
||||
this.currentStep = 6;
|
||||
logger.success(this.siteName, `步骤 6 完成`);
|
||||
} else {
|
||||
logger.warn(this.siteName, ' → 未进入支付页面,可能需要手动处理');
|
||||
logger.info(this.siteName, ` → 当前URL: ${this.page.url()}`);
|
||||
this.currentStep = 6;
|
||||
}
|
||||
} else {
|
||||
logger.warn(this.siteName, ' → 未找到Select plan按钮');
|
||||
// 检查是否已经在支付页面
|
||||
const hasPaymentForm = await this.page.$('button[data-testid="card-accordion-item-button"]').then(el => !!el).catch(() => false);
|
||||
if (hasPaymentForm) {
|
||||
logger.success(this.siteName, ' → 已在支付页面,跳过选择计划');
|
||||
this.currentStep = 6;
|
||||
} else {
|
||||
throw new Error('未找到Select plan按钮,且不在支付页面');
|
||||
}
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
this.currentStep = 5;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
@ -670,10 +749,10 @@ class WindsurfRegister {
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤6: 填写支付信息
|
||||
* 步骤7: 填写支付信息
|
||||
*/
|
||||
async step6_fillPayment() {
|
||||
logger.info(this.siteName, `[步骤 6/${this.getTotalSteps()}] 填写支付信息`);
|
||||
async step7_fillPayment() {
|
||||
logger.info(this.siteName, `[步骤 7/${this.getTotalSteps()}] 填写支付信息`);
|
||||
|
||||
try {
|
||||
// 等待页面加载
|
||||
@ -687,46 +766,104 @@ class WindsurfRegister {
|
||||
logger.info(this.siteName, ` → 有效期: ${card.month}/${card.year}`);
|
||||
logger.info(this.siteName, ` → CVV: ${card.cvv}`);
|
||||
|
||||
// 2. 点击选择"银行卡"支付方式
|
||||
logger.info(this.siteName, ' → 选择银行卡支付方式...');
|
||||
const cardRadio = await this.page.$('input[type="radio"][value="card"]');
|
||||
if (cardRadio) {
|
||||
await cardRadio.click();
|
||||
await this.human.randomDelay(1000, 2000);
|
||||
logger.success(this.siteName, ' → ✓ 已选择银行卡');
|
||||
// 2. 首先找到并点击银行卡区域展开表单
|
||||
logger.info(this.siteName, ' → 点击展开银行卡支付区域...');
|
||||
try {
|
||||
// 点击银行卡区域的按钮来展开表单
|
||||
const cardButton = await this.page.$('button[data-testid="card-accordion-item-button"]');
|
||||
if (cardButton) {
|
||||
await cardButton.click();
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
logger.success(this.siteName, ' → ✓ 银行卡区域已展开');
|
||||
} else {
|
||||
// 如果按钮不存在,尝试点击radio
|
||||
const cardRadio = await this.page.$('input[type="radio"][value="card"]');
|
||||
if (cardRadio) {
|
||||
await cardRadio.click();
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
logger.success(this.siteName, ' → ✓ 已选择银行卡');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn(this.siteName, ` → 展开银行卡区域失败: ${e.message},继续尝试...`);
|
||||
}
|
||||
|
||||
// 3. 填写卡号
|
||||
// 等待Stripe表单iframe加载
|
||||
logger.info(this.siteName, ' → 等待支付表单加载...');
|
||||
await this.human.randomDelay(3000, 5000);
|
||||
|
||||
// 3. 填写卡号(使用humanType逐字符输入)
|
||||
logger.info(this.siteName, ' → 填写卡号...');
|
||||
await this.page.waitForSelector('#cardNumber', { timeout: 10000 });
|
||||
await this.page.click('#cardNumber');
|
||||
await this.human.randomDelay(500, 1000);
|
||||
await this.page.type('#cardNumber', card.number, { delay: 100 });
|
||||
try {
|
||||
await this.page.waitForSelector('#cardNumber', { timeout: 15000 });
|
||||
await this.page.click('#cardNumber');
|
||||
await this.human.randomDelay(500, 800);
|
||||
// 清空
|
||||
await this.page.evaluate(() => document.querySelector('#cardNumber').value = '');
|
||||
// 使用人类行为输入
|
||||
await this.human.humanType(this.page, '#cardNumber', card.number);
|
||||
logger.success(this.siteName, ` → ✓ 卡号已填写: ${card.number}`);
|
||||
await this.human.randomDelay(1500, 2000);
|
||||
} catch (e) {
|
||||
logger.error(this.siteName, ` → 填写卡号失败: ${e.message}`);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// 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: 100 });
|
||||
try {
|
||||
await this.page.click('#cardExpiry');
|
||||
await this.human.randomDelay(300, 500);
|
||||
await this.page.evaluate(() => document.querySelector('#cardExpiry').value = '');
|
||||
const expiry = `${card.month}${card.year}`; // 格式: MMYY
|
||||
await this.human.humanType(this.page, '#cardExpiry', expiry);
|
||||
logger.success(this.siteName, ` → ✓ 有效期已填写: ${expiry}`);
|
||||
await this.human.randomDelay(1500, 2000);
|
||||
} catch (e) {
|
||||
logger.error(this.siteName, ` → 填写有效期失败: ${e.message}`);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// 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: 100 });
|
||||
try {
|
||||
await this.page.click('#cardCvc');
|
||||
await this.human.randomDelay(300, 500);
|
||||
await this.page.evaluate(() => document.querySelector('#cardCvc').value = '');
|
||||
await this.human.humanType(this.page, '#cardCvc', card.cvv);
|
||||
logger.success(this.siteName, ` → ✓ CVC已填写: ${card.cvv}`);
|
||||
await this.human.randomDelay(1500, 2000);
|
||||
} catch (e) {
|
||||
logger.error(this.siteName, ` → 填写CVC失败: ${e.message}`);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// 6. 填写持卡人姓名
|
||||
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: 100 });
|
||||
try {
|
||||
await this.page.click('#billingName');
|
||||
await this.human.randomDelay(300, 500);
|
||||
await this.page.evaluate(() => document.querySelector('#billingName').value = '');
|
||||
const fullName = `${this.accountData.firstName} ${this.accountData.lastName}`;
|
||||
await this.human.humanType(this.page, '#billingName', fullName);
|
||||
logger.success(this.siteName, ` → ✓ 姓名已填写: ${fullName}`);
|
||||
await this.human.randomDelay(1500, 2000);
|
||||
} catch (e) {
|
||||
logger.error(this.siteName, ` → 填写姓名失败: ${e.message}`);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// 7. 选择地址:中国澳门特别行政区
|
||||
logger.info(this.siteName, ' → 选择地址:中国澳门特别行政区...');
|
||||
await this.page.select('#billingCountry', 'MO');
|
||||
await this.human.randomDelay(1000, 2000);
|
||||
try {
|
||||
await this.page.waitForSelector('#billingCountry', { timeout: 15000 });
|
||||
await this.page.select('#billingCountry', 'MO');
|
||||
logger.success(this.siteName, ' → ✓ 地址已选择: 中国澳门特别行政区');
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
} catch (e) {
|
||||
logger.error(this.siteName, ` → 选择地址失败: ${e.message}`);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// 8. 填写地址信息(如果需要)
|
||||
// 等待地址字段加载
|
||||
@ -736,15 +873,33 @@ class WindsurfRegister {
|
||||
const addressFields = await this.page.$$('input[placeholder*="地址"]');
|
||||
if (addressFields.length > 0) {
|
||||
logger.info(this.siteName, ' → 填写地址信息...');
|
||||
// 填写简单的地址
|
||||
await addressFields[0].type('Macau', { delay: 100 });
|
||||
// 填写地址:kowloon
|
||||
await addressFields[0].type('kowloon', { delay: 100 });
|
||||
logger.success(this.siteName, ' → ✓ 地址行1已填写: kowloon');
|
||||
if (addressFields[1]) {
|
||||
await this.human.randomDelay(300, 500);
|
||||
await addressFields[1].type('Macao', { delay: 100 });
|
||||
await addressFields[1].type('kowloon', { delay: 100 });
|
||||
logger.success(this.siteName, ' → ✓ 地址行2已填写: kowloon');
|
||||
}
|
||||
}
|
||||
|
||||
// 9. 点击订阅按钮
|
||||
// 9. 取消勾选"保存我的信息"
|
||||
logger.info(this.siteName, ' → 检查并取消勾选保存信息...');
|
||||
try {
|
||||
const saveCheckbox = await this.page.$('input[type="checkbox"]');
|
||||
if (saveCheckbox) {
|
||||
const isChecked = await this.page.evaluate(el => el.checked, saveCheckbox);
|
||||
if (isChecked) {
|
||||
await saveCheckbox.click();
|
||||
logger.success(this.siteName, ' → ✓ 已取消勾选保存信息');
|
||||
await this.human.randomDelay(500, 1000);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn(this.siteName, ` → 取消勾选失败: ${e.message}`);
|
||||
}
|
||||
|
||||
// 10. 点击订阅按钮
|
||||
logger.info(this.siteName, ' → 点击订阅按钮...');
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
|
||||
@ -756,11 +911,11 @@ class WindsurfRegister {
|
||||
// 等待处理
|
||||
await this.human.randomDelay(5000, 7000);
|
||||
|
||||
this.currentStep = 6;
|
||||
logger.success(this.siteName, `步骤 6 完成`);
|
||||
this.currentStep = 7;
|
||||
logger.success(this.siteName, `步骤 7 完成`);
|
||||
} else {
|
||||
logger.warn(this.siteName, ' → 未找到订阅按钮');
|
||||
this.currentStep = 6;
|
||||
this.currentStep = 7;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user