This commit is contained in:
dengqichen 2025-11-17 00:02:03 +08:00
parent 10439f5af1
commit 585d374140
3 changed files with 316 additions and 152 deletions

View File

@ -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"
},

View File

@ -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)) {

View File

@ -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 {
/**
* 通用方法点击按钮并等待页面跳转
* 使用页面特征URLDOM元素来判断是否真的跳转了
* @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();
// 点击按钮
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验证失败无法继续');
// 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验证后页面未成功跳转到验证码页面');
}
if (verifyResult === 'manual') {
logger.info(this.siteName, '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 完成`);
// 验证是否进入支付页面检查是否有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, ' → 未找到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;
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, ' → 选择银行卡支付方式...');
// 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(1000, 2000);
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 });
try {
await this.page.waitForSelector('#cardNumber', { timeout: 15000 });
await this.page.click('#cardNumber');
await this.human.randomDelay(500, 1000);
await this.page.type('#cardNumber', card.number, { delay: 100 });
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, ' → 填写有效期...');
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.page.type('#cardExpiry', expiry, { delay: 100 });
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...');
try {
await this.page.click('#cardCvc');
await this.human.randomDelay(300, 500);
await this.page.type('#cardCvc', card.cvv, { delay: 100 });
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, ' → 填写持卡人姓名...');
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.page.type('#billingName', fullName, { delay: 100 });
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, ' → 选择地址:中国澳门特别行政区...');
try {
await this.page.waitForSelector('#billingCountry', { timeout: 15000 });
await this.page.select('#billingCountry', 'MO');
await this.human.randomDelay(1000, 2000);
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) {