diff --git a/package.json b/package.json index d16c086..b238eb7 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,9 @@ "license": "MIT", "dependencies": { "commander": "^11.0.0", - "puppeteer": "^21.11.0" + "puppeteer": "npm:rebrowser-puppeteer@^23.9.0", + "imap": "^0.8.19", + "mailparser": "^3.6.5" }, "engines": { "node": ">=14.0.0" diff --git a/src/cli.js b/src/cli.js index 18a7752..f44bdfd 100644 --- a/src/cli.js +++ b/src/cli.js @@ -56,6 +56,7 @@ if (registerTool) { .option('-o, --output ', '保存账号数据到文件') .option('--from-step ', '从第几步开始执行', '1') .option('--to-step ', '执行到第几步(不指定则执行全部)') + .option('--password-strategy ', '密码策略 (email=使用邮箱, random=随机)', 'email') .option('--keep-browser-open', '保持浏览器打开', false) .action(async (options) => { // 转换步骤参数为数字 diff --git a/src/tools/account-register/email-verification/connectors/imap-connector.js b/src/tools/account-register/email-verification/connectors/imap-connector.js new file mode 100644 index 0000000..46c1905 --- /dev/null +++ b/src/tools/account-register/email-verification/connectors/imap-connector.js @@ -0,0 +1,242 @@ +/** + * IMAP Connector - IMAP邮箱连接器 + * 支持QQ邮箱、Gmail、Outlook等支持IMAP的邮箱服务 + */ + +const Imap = require('imap'); +const { simpleParser } = require('mailparser'); +const logger = require('../../../../shared/logger'); + +class ImapConnector { + constructor(config) { + this.config = config; + this.imap = null; + this.connected = false; + } + + /** + * 连接到邮箱 + */ + async connect() { + return new Promise((resolve, reject) => { + this.imap = new Imap({ + user: this.config.user, + password: this.config.password, + host: this.config.host, + port: this.config.port, + tls: this.config.tls, + tlsOptions: this.config.tlsOptions || { rejectUnauthorized: false } + }); + + this.imap.once('ready', () => { + this.connected = true; + logger.success('IMAP', `已连接到邮箱: ${this.config.user}`); + resolve(); + }); + + this.imap.once('error', (err) => { + logger.error('IMAP', `连接失败: ${err.message}`); + reject(err); + }); + + this.imap.once('end', () => { + this.connected = false; + logger.info('IMAP', '连接已关闭'); + }); + + this.imap.connect(); + }); + } + + /** + * 断开连接 + */ + disconnect() { + if (this.imap && this.connected) { + this.imap.end(); + } + } + + /** + * 获取最新的邮件 + * @param {number} count - 获取数量 + * @param {number} sinceDays - 获取几天内的邮件 + * @returns {Promise} + */ + async getLatestEmails(count = 10, sinceDays = 1) { + if (!this.connected) { + await this.connect(); + } + + return new Promise((resolve, reject) => { + this.imap.openBox('INBOX', false, (err, box) => { + if (err) { + reject(err); + return; + } + + // 计算日期范围 + const sinceDate = new Date(); + sinceDate.setDate(sinceDate.getDate() - sinceDays); + + // 搜索条件:最近N天的邮件 + this.imap.search(['UNSEEN', ['SINCE', sinceDate]], async (err, results) => { + if (err) { + reject(err); + return; + } + + if (!results || results.length === 0) { + resolve([]); + return; + } + + // 只取最新的N封 + const uids = results.slice(-count); + const emails = []; + + const fetch = this.imap.fetch(uids, { + bodies: '', + markSeen: false + }); + + fetch.on('message', (msg) => { + let buffer = ''; + + msg.on('body', (stream) => { + stream.on('data', (chunk) => { + buffer += chunk.toString('utf8'); + }); + }); + + msg.once('end', async () => { + try { + const parsed = await simpleParser(buffer); + emails.push({ + uid: msg.uid, + from: parsed.from?.text || '', + to: parsed.to?.text || '', + subject: parsed.subject || '', + date: parsed.date, + text: parsed.text || '', + html: parsed.html || '', + headers: parsed.headers + }); + } catch (e) { + logger.warn('IMAP', `解析邮件失败: ${e.message}`); + } + }); + }); + + fetch.once('error', (err) => { + reject(err); + }); + + fetch.once('end', () => { + resolve(emails); + }); + }); + }); + }); + } + + /** + * 搜索包含特定关键词的邮件 + * @param {string} subject - 主题关键词 + * @param {number} sinceDays - 几天内 + * @returns {Promise} + */ + async searchBySubject(subject, sinceDays = 1) { + if (!this.connected) { + await this.connect(); + } + + return new Promise((resolve, reject) => { + this.imap.openBox('INBOX', false, (err, box) => { + if (err) { + reject(err); + return; + } + + const sinceDate = new Date(); + sinceDate.setDate(sinceDate.getDate() - sinceDays); + + this.imap.search([['SINCE', sinceDate], ['SUBJECT', subject]], async (err, results) => { + if (err) { + reject(err); + return; + } + + if (!results || results.length === 0) { + resolve([]); + return; + } + + const emails = []; + const fetch = this.imap.fetch(results, { + bodies: '', + markSeen: false + }); + + fetch.on('message', (msg) => { + let buffer = ''; + + msg.on('body', (stream) => { + stream.on('data', (chunk) => { + buffer += chunk.toString('utf8'); + }); + }); + + msg.once('end', async () => { + try { + const parsed = await simpleParser(buffer); + emails.push({ + uid: msg.uid, + from: parsed.from?.text || '', + to: parsed.to?.text || '', + subject: parsed.subject || '', + date: parsed.date, + text: parsed.text || '', + html: parsed.html || '', + headers: parsed.headers + }); + } catch (e) { + logger.warn('IMAP', `解析邮件失败: ${e.message}`); + } + }); + }); + + fetch.once('error', (err) => { + reject(err); + }); + + fetch.once('end', () => { + resolve(emails); + }); + }); + }); + }); + } + + /** + * 标记邮件为已读 + * @param {number} uid - 邮件UID + */ + async markAsRead(uid) { + if (!this.connected) { + return; + } + + return new Promise((resolve, reject) => { + this.imap.addFlags(uid, ['\\Seen'], (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } +} + +module.exports = ImapConnector; diff --git a/src/tools/account-register/email-verification/email-config.js b/src/tools/account-register/email-verification/email-config.js new file mode 100644 index 0000000..a9ea313 --- /dev/null +++ b/src/tools/account-register/email-verification/email-config.js @@ -0,0 +1,33 @@ +/** + * Email Configuration + * 邮箱配置 + */ + +module.exports = { + // 主邮箱配置(QQ邮箱 - 接收所有验证码) + primary: { + type: 'imap', + host: 'imap.qq.com', + port: 993, + user: '1695551@qq.com', + password: 'iogmboamejdsbjdh', // QQ邮箱授权码 + tls: true, + tlsOptions: { + rejectUnauthorized: false + } + }, + + // 可以配置备用邮箱(未来扩展) + backup: { + // type: 'imap', + // host: '...', + // ... + }, + + // 邮件搜索配置 + search: { + maxWaitTime: 60, // 最长等待时间(秒) + checkInterval: 3, // 检查间隔(秒) + maxEmails: 10 // 每次获取的邮件数量 + } +}; diff --git a/src/tools/account-register/email-verification/index.js b/src/tools/account-register/email-verification/index.js new file mode 100644 index 0000000..6f7334c --- /dev/null +++ b/src/tools/account-register/email-verification/index.js @@ -0,0 +1,161 @@ +/** + * Email Verification Service + * 邮箱验证码服务 - 统一入口 + */ + +const ImapConnector = require('./connectors/imap-connector'); +const WindsurfParser = require('./parsers/windsurf-parser'); +const emailConfig = require('./email-config'); +const logger = require('../../../shared/logger'); + +class EmailVerificationService { + constructor(config = null) { + this.config = config || emailConfig.primary; + this.connector = null; + this.parsers = [ + new WindsurfParser() + // 未来添加更多解析器 + // new GitHubParser(), + // new TwitterParser(), + ]; + } + + /** + * 获取验证码 + * @param {string} siteName - 网站名称(如 'windsurf') + * @param {string} recipientEmail - 接收验证码的邮箱地址 + * @param {number} timeout - 超时时间(秒) + * @returns {Promise} - 验证码 + */ + async getVerificationCode(siteName, recipientEmail, timeout = 60) { + logger.info('EmailVerification', `开始获取 ${siteName} 的验证码...`); + logger.info('EmailVerification', `接收邮箱: ${recipientEmail}`); + + try { + // 1. 连接邮箱 + this.connector = new ImapConnector(this.config); + await this.connector.connect(); + + // 2. 等待验证码邮件 + const startTime = Date.now(); + const checkInterval = emailConfig.search.checkInterval * 1000; // 转换为毫秒 + let attempts = 0; + + while (Date.now() - startTime < timeout * 1000) { + attempts++; + logger.info('EmailVerification', `第 ${attempts} 次检查邮件...`); + + // 获取最新邮件 + const emails = await this.connector.getLatestEmails(10, 1); + + if (emails && emails.length > 0) { + // 3. 查找匹配的邮件并提取验证码 + for (const email of emails.reverse()) { // 从最新的开始 + for (const parser of this.parsers) { + if (parser.canParse(email)) { + logger.success('EmailVerification', `找到匹配的邮件: ${email.subject}`); + + const code = parser.extractCode(email); + if (code) { + logger.success('EmailVerification', `成功提取验证码: ${code}`); + + // 标记为已读 + try { + await this.connector.markAsRead(email.uid); + } catch (e) { + // 忽略标记失败 + } + + // 断开连接 + this.connector.disconnect(); + + return code; + } + } + } + } + } + + // 等待一段时间后再检查 + logger.info('EmailVerification', `等待 ${emailConfig.search.checkInterval} 秒后重试...`); + await this.sleep(checkInterval); + } + + // 超时 + this.connector.disconnect(); + throw new Error(`获取验证码超时(${timeout}秒内未收到邮件)`); + + } catch (error) { + if (this.connector) { + this.connector.disconnect(); + } + throw error; + } + } + + /** + * 搜索特定主题的邮件并提取验证码 + * @param {string} subject - 邮件主题关键词 + * @param {number} timeout - 超时时间(秒) + * @returns {Promise} + */ + async getCodeBySubject(subject, timeout = 60) { + logger.info('EmailVerification', `搜索主题包含 "${subject}" 的邮件...`); + + try { + this.connector = new ImapConnector(this.config); + await this.connector.connect(); + + const startTime = Date.now(); + const checkInterval = emailConfig.search.checkInterval * 1000; + + while (Date.now() - startTime < timeout * 1000) { + const emails = await this.connector.searchBySubject(subject, 1); + + if (emails && emails.length > 0) { + for (const email of emails.reverse()) { + for (const parser of this.parsers) { + if (parser.canParse(email)) { + const code = parser.extractCode(email); + if (code) { + logger.success('EmailVerification', `提取到验证码: ${code}`); + this.connector.disconnect(); + return code; + } + } + } + } + } + + await this.sleep(checkInterval); + } + + this.connector.disconnect(); + throw new Error(`获取验证码超时`); + + } catch (error) { + if (this.connector) { + this.connector.disconnect(); + } + throw error; + } + } + + /** + * 添加新的解析器 + * @param {BaseParser} parser - 解析器实例 + */ + addParser(parser) { + this.parsers.push(parser); + } + + /** + * 休眠 + * @param {number} ms - 毫秒 + */ + sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} + +module.exports = EmailVerificationService; diff --git a/src/tools/account-register/email-verification/parsers/base-parser.js b/src/tools/account-register/email-verification/parsers/base-parser.js new file mode 100644 index 0000000..e80d85c --- /dev/null +++ b/src/tools/account-register/email-verification/parsers/base-parser.js @@ -0,0 +1,53 @@ +/** + * Base Parser - 邮件解析器基类 + * 所有网站的邮件解析器都继承此类 + */ + +class BaseParser { + constructor(siteName) { + this.siteName = siteName; + } + + /** + * 判断是否能解析此邮件 + * @param {Object} email - 邮件对象 + * @returns {boolean} + */ + canParse(email) { + throw new Error('Must implement canParse() method'); + } + + /** + * 从邮件中提取验证码 + * @param {Object} email - 邮件对象 + * @returns {string|null} - 验证码或null + */ + extractCode(email) { + throw new Error('Must implement extractCode() method'); + } + + /** + * 通用的验证码提取方法 + * @param {string} content - 邮件内容 + * @param {RegExp} pattern - 正则表达式 + * @returns {string|null} + */ + extractByRegex(content, pattern) { + if (!content) return null; + + const match = content.match(pattern); + return match ? match[1] : null; + } + + /** + * 从HTML中提取文本 + * @param {string} html - HTML内容 + * @returns {string} + */ + stripHtml(html) { + if (!html) return ''; + return html.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim(); + } +} + +module.exports = BaseParser; diff --git a/src/tools/account-register/email-verification/parsers/windsurf-parser.js b/src/tools/account-register/email-verification/parsers/windsurf-parser.js new file mode 100644 index 0000000..b7e7543 --- /dev/null +++ b/src/tools/account-register/email-verification/parsers/windsurf-parser.js @@ -0,0 +1,124 @@ +/** + * Windsurf Parser - Windsurf邮件解析器 + * 用于解析Windsurf发送的验证码邮件 + */ + +const BaseParser = require('./base-parser'); + +class WindsurfParser extends BaseParser { + constructor() { + super('Windsurf'); + + // Windsurf邮件的特征 + this.senderKeywords = ['windsurf', 'codeium', 'exafunction']; + this.subjectKeywords = ['verify', 'verification', 'code', '验证', 'welcome']; + + // 验证码的正则表达式(根据实际邮件调整) + this.codePatterns = [ + // HTML格式:

866172

+ /]*class="code[^"]*"[^>]*>(\d{6})<\/h1>/i, + // 常见格式 + /6 digit code[^0-9]*(\d{6})/i, + /verification code[^0-9]*(\d{6})/i, + /verify.*code:?\s*(\d{6})/i, + // 纯6位数字(最后尝试) + /\b(\d{6})\b/ + ]; + } + + /** + * 判断是否是Windsurf的验证码邮件 + */ + canParse(email) { + if (!email) return false; + + const from = (email.from || '').toLowerCase(); + const subject = (email.subject || '').toLowerCase(); + + // 检查发件人 + const hasSender = this.senderKeywords.some(keyword => + from.includes(keyword) + ); + + // 检查主题 + const hasSubject = this.subjectKeywords.some(keyword => + subject.includes(keyword) + ); + + return hasSender || hasSubject; + } + + /** + * 从邮件中提取验证码 + */ + extractCode(email) { + if (!email) return null; + + // 优先从HTML提取 + let code = this.extractFromHtml(email.html); + if (code) return code; + + // 其次从纯文本提取 + code = this.extractFromText(email.text); + if (code) return code; + + return null; + } + + /** + * 从HTML内容提取验证码 + */ + extractFromHtml(html) { + if (!html) return null; + + // 先尝试直接从HTML提取(保留HTML标签) + for (const pattern of this.codePatterns) { + const code = this.extractByRegex(html, pattern); + if (code && this.validateCode(code)) { + return code; + } + } + + // 如果HTML提取失败,再去除标签后尝试 + const text = this.stripHtml(html); + return this.extractFromText(text); + } + + /** + * 从文本内容提取验证码 + */ + extractFromText(text) { + if (!text) return null; + + // 尝试所有正则表达式 + for (const pattern of this.codePatterns) { + const code = this.extractByRegex(text, pattern); + if (code && this.validateCode(code)) { + return code; + } + } + + return null; + } + + /** + * 验证提取的验证码是否合理 + */ + validateCode(code) { + if (!code) return false; + + // Windsurf验证码是6位数字 + if (code.length !== 6) { + return false; + } + + // 应该是纯数字 + if (!/^\d{6}$/.test(code)) { + return false; + } + + return true; + } +} + +module.exports = WindsurfParser; diff --git a/src/tools/account-register/sites/windsurf.js b/src/tools/account-register/sites/windsurf.js index 897b18c..1ed14fa 100644 --- a/src/tools/account-register/sites/windsurf.js +++ b/src/tools/account-register/sites/windsurf.js @@ -12,6 +12,7 @@ const AccountDataGenerator = require('../generator'); const HumanBehavior = require('../utils/human-behavior'); +const EmailVerificationService = require('../email-verification'); const logger = require('../../../shared/logger'); class WindsurfRegister { @@ -20,6 +21,7 @@ class WindsurfRegister { this.siteUrl = 'https://windsurf.com/account/register'; this.dataGen = new AccountDataGenerator(); this.human = new HumanBehavior(); + this.emailService = new EmailVerificationService(); this.browser = null; this.page = null; this.currentStep = 0; @@ -66,7 +68,10 @@ class WindsurfRegister { name: options.name, email: options.email, username: options.username, - password: options.password, + password: { + strategy: options.passwordStrategy || 'email', + ...options.password + }, includePhone: false // Windsurf第一步不需要手机号 }); @@ -181,38 +186,44 @@ class WindsurfRegister { // 点击Continue按钮(使用人类行为) logger.info(this.siteName, ' → 点击Continue按钮...'); - // 尝试查找按钮 - let buttonSelector = null; - const possibleSelectors = [ - 'button:has-text("Continue")', - 'button[type="submit"]', - 'button.continue-button', - 'button[class*="continue"]' - ]; - - for (const selector of possibleSelectors) { - try { - const button = await this.page.$(selector); - if (button) { - buttonSelector = selector; - break; + // 等待按钮变为可点击状态(不再disabled) + try { + await this.page.waitForFunction( + () => { + const button = document.querySelector('button'); + if (!button) return false; + const text = button.textContent.trim(); + return text === 'Continue' && !button.disabled; + }, + { timeout: 10000 } + ); + + logger.info(this.siteName, ' → 按钮已激活'); + + // 点击未禁用的Continue按钮 + 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 (e) { - // 继续尝试下一个 } - } - - if (buttonSelector) { - await this.human.visualSearch(this.page, buttonSelector); - await this.human.humanClick(this.page, buttonSelector); - } else { - logger.warn(this.siteName, ' → 未找到Continue按钮,尝试按Enter键'); + } catch (error) { + logger.warn(this.siteName, ' → 按钮等待超时,尝试按Enter键'); await this.human.randomDelay(500, 1000); - await this.page.keyboard.press('Enter'); + await Promise.all([ + this.page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 15000 }).catch(() => {}), + this.page.keyboard.press('Enter') + ]); } - // 等待跳转或响应 - await this.human.randomDelay(2000, 4000); + // 额外等待确保页面稳定 + await this.human.randomDelay(1000, 2000); this.currentStep = 1; logger.success(this.siteName, `步骤 1 完成`); @@ -230,63 +241,185 @@ class WindsurfRegister { // 填写密码 logger.info(this.siteName, ' → 填写密码...'); await this.page.waitForSelector('#password', { timeout: 10000 }); + + // 先清空密码框 + await this.page.evaluate(() => { + const elem = document.querySelector('#password'); + if (elem) elem.value = ''; + }); + await this.human.humanType(this.page, '#password', this.accountData.password); // 填写确认密码 logger.info(this.siteName, ' → 填写确认密码...'); await this.page.waitForSelector('#passwordConfirmation', { timeout: 10000 }); + + // 先清空确认密码框(防止有残留) + await this.page.evaluate(() => { + const elem = document.querySelector('#passwordConfirmation'); + if (elem) elem.value = ''; + }); + await this.human.humanType(this.page, '#passwordConfirmation', this.accountData.password); + // 等待验证通过 + logger.info(this.siteName, ' → 等待密码验证...'); + await this.human.randomDelay(1000, 2000); + // 查找并点击Continue按钮 logger.info(this.siteName, ' → 点击Continue按钮...'); - const possibleSelectors = [ - 'button:has-text("Continue")', - 'button[type="submit"]', - 'button.continue-button', - 'button[class*="continue"]' - ]; - - let buttonSelector = null; - for (const selector of possibleSelectors) { - try { - const button = await this.page.$(selector); - if (button) { - buttonSelector = selector; - break; + // 等待按钮变为可点击状态(不再disabled) + try { + await this.page.waitForFunction( + () => { + const button = document.querySelector('button'); + if (!button) return false; + const text = button.textContent.trim(); + return text === 'Continue' && !button.disabled; + }, + { timeout: 10000 } + ); + + 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 (e) { - // 继续尝试 } - } - - if (buttonSelector) { - await this.human.visualSearch(this.page, buttonSelector); - await this.human.humanClick(this.page, buttonSelector); - } else { - logger.warn(this.siteName, ' → 未找到Continue按钮,尝试按Enter键'); + } catch (error) { + logger.warn(this.siteName, ' → 按钮等待超时,尝试按Enter键'); await this.human.randomDelay(500, 1000); - await this.page.keyboard.press('Enter'); + await Promise.all([ + this.page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 15000 }).catch(() => {}), + this.page.keyboard.press('Enter') + ]); } - // 等待跳转或响应 - await this.human.randomDelay(2000, 4000); + // 额外等待确保页面稳定 + await this.human.randomDelay(1000, 2000); this.currentStep = 2; logger.success(this.siteName, `步骤 2 完成`); } /** - * 步骤3: 邮箱验证(待实现) + * 步骤3: 邮箱验证 */ async step3_emailVerification() { logger.info(this.siteName, `[步骤 3/${this.getTotalSteps()}] 邮箱验证`); - // 邮箱验证逻辑 - // ... - - this.currentStep = 3; - logger.warn(this.siteName, '步骤 3 待实现(需要邮箱验证码)'); + try { + // 等待验证码页面加载 + await this.human.readPage(1, 2); + + // 获取验证码(从邮箱) + logger.info(this.siteName, ' → 正在从邮箱获取验证码...'); + logger.info(this.siteName, ` → 接收邮箱: ${this.accountData.email}`); + + const code = await this.emailService.getVerificationCode( + 'windsurf', + this.accountData.email, + 90 // 90秒超时 + ); + + logger.success(this.siteName, ` → 验证码: ${code}`); + + // 等待验证码输入框 + await this.human.randomDelay(1000, 2000); + + // 查找验证码输入框(可能有多种选择器) + const possibleSelectors = [ + '#code', + '#verificationCode', + 'input[name="code"]', + 'input[name="verificationCode"]', + 'input[type="text"][placeholder*="code" i]', + 'input[type="text"][placeholder*="验证" i]' + ]; + + let codeInputSelector = null; + for (const selector of possibleSelectors) { + try { + const input = await this.page.$(selector); + if (input) { + codeInputSelector = selector; + logger.info(this.siteName, ` → 找到验证码输入框: ${selector}`); + break; + } + } catch (e) { + // 继续尝试 + } + } + + if (codeInputSelector) { + // 填写验证码 + logger.info(this.siteName, ' → 填写验证码...'); + await this.human.humanType(this.page, codeInputSelector, code); + + // 查找并点击提交按钮 + logger.info(this.siteName, ' → 点击提交按钮...'); + + const buttonSelectors = [ + 'button:has-text("Verify")', + 'button:has-text("Continue")', + 'button:has-text("Submit")', + 'button[type="submit"]', + 'button.verify-button', + 'button[class*="verify"]' + ]; + + let buttonSelector = null; + for (const selector of buttonSelectors) { + try { + const button = await this.page.$(selector); + if (button) { + buttonSelector = selector; + break; + } + } catch (e) { + // 继续尝试 + } + } + + if (buttonSelector) { + await this.human.visualSearch(this.page, buttonSelector); + await this.human.humanClick(this.page, buttonSelector); + } else { + logger.warn(this.siteName, ' → 未找到提交按钮,尝试按Enter键'); + await this.human.randomDelay(500, 1000); + await this.page.keyboard.press('Enter'); + } + + // 等待验证完成 + await this.human.randomDelay(3000, 5000); + + this.currentStep = 3; + logger.success(this.siteName, `步骤 3 完成`); + + } else { + logger.error(this.siteName, ' → 未找到验证码输入框!'); + logger.warn(this.siteName, ' → 请手动输入验证码: ' + code); + + // 等待用户手动输入 + await this.human.randomDelay(30000, 30000); + + this.currentStep = 3; + } + + } catch (error) { + logger.error(this.siteName, `邮箱验证失败: ${error.message}`); + throw error; + } } /** diff --git a/src/tools/account-register/utils/human-behavior.js b/src/tools/account-register/utils/human-behavior.js index 048dd60..6a79677 100644 --- a/src/tools/account-register/utils/human-behavior.js +++ b/src/tools/account-register/utils/human-behavior.js @@ -27,23 +27,36 @@ class HumanBehavior { if (element) element.value = ''; }, selector); - // 逐字输入,随机延迟 - for (let i = 0; i < text.length; i++) { - const char = text[i]; - - // 每个字符的输入延迟 - const delay = this.randomInt(80, 180); - await page.type(selector, char, { delay }); - - // 偶尔停顿(模拟思考或查看) - if (Math.random() < 0.15) { - await this.randomDelay(300, 900); - } - - // 偶尔有短暂的快速输入(模拟熟悉的词语) - if (Math.random() < 0.1 && i < text.length - 3) { - i += 2; // 跳过几个字符,快速输入 + // 使用更可靠的方式:直接设置value然后触发input事件 + await page.evaluate((sel, value) => { + const element = document.querySelector(sel); + if (element) { + element.value = value; + element.dispatchEvent(new Event('input', { bubbles: true })); + element.dispatchEvent(new Event('change', { bubbles: true })); } + }, selector, text); + + // 模拟输入延迟 + await this.randomDelay(text.length * 100, text.length * 150); + + // 验证输入是否正确 + const actualValue = await page.evaluate((sel) => { + const element = document.querySelector(sel); + return element ? element.value : ''; + }, selector); + + if (actualValue !== text) { + console.warn(`输入验证失败: 期望 "${text}", 实际 "${actualValue}"`); + // 重试一次 + await page.evaluate((sel, value) => { + const element = document.querySelector(sel); + if (element) { + element.value = value; + element.dispatchEvent(new Event('input', { bubbles: true })); + element.dispatchEvent(new Event('change', { bubbles: true })); + } + }, selector, text); } // 输入完成后的短暂停顿