From af26fab852bdb036b87ce5ec3073bd6692edfed1 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Sun, 16 Nov 2025 22:39:36 +0800 Subject: [PATCH] aaaaa --- .../connectors/imap-connector.js | 90 ++++++------ .../email-verification/index.js | 136 +++++++++--------- src/tools/account-register/sites/windsurf.js | 106 +++++++++----- 3 files changed, 178 insertions(+), 154 deletions(-) diff --git a/src/tools/account-register/email-verification/connectors/imap-connector.js b/src/tools/account-register/email-verification/connectors/imap-connector.js index 5690ea9..196f6f8 100644 --- a/src/tools/account-register/email-verification/connectors/imap-connector.js +++ b/src/tools/account-register/email-verification/connectors/imap-connector.js @@ -60,27 +60,23 @@ class ImapConnector { /** * 获取最新的邮件 * @param {number} count - 获取数量 - * @param {number} sinceDays - 获取几天内的邮件 + * @param {string} folder - 邮箱文件夹名称,默认'INBOX' * @returns {Promise} */ - async getLatestEmails(count = 10, sinceDays = 1) { + async getLatestEmails(count = 50, folder = 'INBOX') { if (!this.connected) { await this.connect(); } return new Promise((resolve, reject) => { - this.imap.openBox('INBOX', false, (err, box) => { + this.imap.openBox(folder, false, (err, box) => { if (err) { reject(err); return; } - // 计算日期范围 - const sinceDate = new Date(); - sinceDate.setDate(sinceDate.getDate() - sinceDays); - - // 搜索条件:最近N天的所有邮件(包括已读) - this.imap.search([['SINCE', sinceDate]], async (err, results) => { + // 搜索条件:只搜索未读邮件 + this.imap.search(['UNSEEN'], (err, results) => { if (err) { reject(err); return; @@ -91,50 +87,50 @@ class ImapConnector { return; } + logger.info('IMAP', `搜索到 ${results.length} 封未读邮件`); + // 只取最新的N封 const uids = results.slice(-count); const emails = []; + let processedCount = 0; + const totalCount = uids.length; const fetch = this.imap.fetch(uids, { bodies: '', - markSeen: false + markSeen: true }); fetch.on('message', (msg) => { - let buffer = ''; - msg.on('body', (stream) => { - stream.on('data', (chunk) => { - buffer += chunk.toString('utf8'); + simpleParser(stream, (err, parsed) => { + if (err) { + logger.warn('IMAP', `解析邮件失败: ${err.message}`); + } else { + 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 + }); + } + + processedCount++; + // 所有邮件都处理完后才resolve + if (processedCount === totalCount) { + logger.info('IMAP', `成功解析 ${emails.length} 封邮件`); + resolve(emails); + } }); }); - - 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); - }); }); }); }); @@ -161,7 +157,7 @@ class ImapConnector { const sinceDate = new Date(); sinceDate.setDate(sinceDate.getDate() - sinceDays); - this.imap.search([['SINCE', sinceDate], ['SUBJECT', subject]], async (err, results) => { + this.imap.search([['SINCE', sinceDate], ['SUBJECT', subject]], (err, results) => { if (err) { reject(err); return; @@ -175,21 +171,17 @@ class ImapConnector { const emails = []; const fetch = this.imap.fetch(results, { bodies: '', - markSeen: false + markSeen: true }); fetch.on('message', (msg) => { - let buffer = ''; - msg.on('body', (stream) => { - stream.on('data', (chunk) => { - buffer += chunk.toString('utf8'); - }); - }); + simpleParser(stream, (err, parsed) => { + if (err) { + logger.warn('IMAP', `解析邮件失败: ${err.message}`); + return; + } - msg.once('end', async () => { - try { - const parsed = await simpleParser(buffer); emails.push({ uid: msg.uid, from: parsed.from?.text || '', @@ -200,9 +192,7 @@ class ImapConnector { html: parsed.html || '', headers: parsed.headers }); - } catch (e) { - logger.warn('IMAP', `解析邮件失败: ${e.message}`); - } + }); }); }); diff --git a/src/tools/account-register/email-verification/index.js b/src/tools/account-register/email-verification/index.js index abe20e4..ba69768 100644 --- a/src/tools/account-register/email-verification/index.js +++ b/src/tools/account-register/email-verification/index.js @@ -27,40 +27,52 @@ class EmailVerificationService { * @param {number} timeout - 超时时间(秒) * @returns {Promise} - 验证码 */ - async getVerificationCode(siteName, recipientEmail, timeout = 60) { + async getVerificationCode(siteName, recipientEmail, timeout = 120) { logger.info('EmailVerification', `开始获取 ${siteName} 的验证码...`); logger.info('EmailVerification', `接收邮箱: ${recipientEmail}`); - try { - // 1. 初始化连接器 - this.connector = new ImapConnector(this.config); - - // 2. 等待验证码邮件 + return new Promise((resolve, reject) => { const startTime = Date.now(); - const checkInterval = emailConfig.search.checkInterval * 1000; // 转换为毫秒 - let attempts = 0; + const maxWaitTime = timeout * 1000; + + // 创建IMAP连接 + this.connector = new ImapConnector(this.config); + + let checkInterval; + let isResolved = false; - while (Date.now() - startTime < timeout * 1000) { - attempts++; - logger.info('EmailVerification', `第 ${attempts} 次检查邮件...`); - - // 关键:每次都重新连接以获取最新邮件(QQ邮箱IMAP有延迟问题) - if (attempts > 1) { - logger.info('EmailVerification', '断开并重新连接以刷新邮件...'); + const checkMail = async () => { + if (Date.now() - startTime > maxWaitTime) { + clearInterval(checkInterval); this.connector.disconnect(); - await this.sleep(1000); + if (!isResolved) { + isResolved = true; + reject(new Error('获取验证码超时')); + } + return; } - - await this.connector.connect(); - - // 获取最新邮件(倒序排列) - const emails = await this.connector.getLatestEmails(20, 1); - if (emails && emails.length > 0) { - logger.info('EmailVerification', `找到 ${emails.length} 封邮件`); + try { + // 获取最新邮件 + logger.info('EmailVerification', '正在搜索邮件...'); + const emails = await this.connector.getLatestEmails(50, 'INBOX'); - // 打印最近5条邮件信息(倒序,最新的在前) - const recentEmails = emails.slice(-5).reverse(); + if (!emails || emails.length === 0) { + logger.info('EmailVerification', '暂无未读邮件'); + return; + } + + 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)); logger.info('EmailVerification', '最近5条邮件:'); recentEmails.forEach((email, index) => { @@ -72,20 +84,13 @@ class EmailVerificationService { }); logger.info('EmailVerification', '='.repeat(60)); - // 3. 查找匹配的邮件并提取验证码(从最新的开始) - for (const email of emails.reverse()) { - logger.info('EmailVerification', `检查邮件: 发件人="${email.from}", 主题="${email.subject}", 收件人="${email.to}"`); + // 查找匹配的邮件并提取验证码 + // 注意:QQ邮箱转发邮件后,收件人字段会被改写为QQ邮箱地址,所以不能检查收件人 + // 改为只检查发件人和主题,并按时间取最新的 + for (const email of emails) { + if (isResolved) return; - // 关键:检查收件人是否匹配 - const emailTo = (email.to || '').toLowerCase(); - const isForRecipient = emailTo.includes(recipientEmail.toLowerCase()); - - if (!isForRecipient) { - logger.info('EmailVerification', ` ✗ 跳过:收件人不匹配(需要:${recipientEmail})`); - continue; - } - - logger.info('EmailVerification', ` ✓ 收件人匹配!`); + logger.info('EmailVerification', `检查邮件: 发件人="${email.from}", 主题="${email.subject}", 时间="${email.date}"`); for (const parser of this.parsers) { if (parser.canParse(email)) { @@ -93,45 +98,40 @@ class EmailVerificationService { const code = parser.extractCode(email); if (code) { - logger.success('EmailVerification', ` ✓ 成功提取验证码: ${code}`); - - // 标记为已读 - try { - await this.connector.markAsRead(email.uid); - } catch (e) { - // 忽略标记失败 - } - - // 断开连接 + clearInterval(checkInterval); this.connector.disconnect(); - - return code; + if (!isResolved) { + isResolved = true; + logger.success('EmailVerification', ` ✓ 成功提取验证码: ${code}`); + resolve(code); + } + return; } else { logger.warn('EmailVerification', ` 邮件匹配但无法提取验证码`); } } } } - logger.warn('EmailVerification', `未找到匹配的Windsurf验证码邮件`); - } else { - logger.info('EmailVerification', `没有邮件`); + + logger.warn('EmailVerification', `未找到匹配的验证码邮件`); + + } catch (err) { + logger.error('EmailVerification', `检查邮件失败: ${err.message}`); } + }; - // 等待一段时间后再检查 - 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; - } + // 连接成功后开始检查 + this.connector.connect().then(() => { + logger.success('EmailVerification', 'IMAP连接成功,开始监听验证码邮件...'); + checkMail(); + checkInterval = setInterval(checkMail, emailConfig.search.checkInterval * 1000); + }).catch((err) => { + if (!isResolved) { + isResolved = true; + reject(new Error(`IMAP连接失败: ${err.message}`)); + } + }); + }); } /** diff --git a/src/tools/account-register/sites/windsurf.js b/src/tools/account-register/sites/windsurf.js index 16af75d..6ee94c4 100644 --- a/src/tools/account-register/sites/windsurf.js +++ b/src/tools/account-register/sites/windsurf.js @@ -60,6 +60,52 @@ class WindsurfRegister { }; } + /** + * 通用方法:点击按钮并等待页面跳转 + * @param {Function} checkPageChanged - 检查页面是否已跳转的函数,返回Promise + * @param {number} maxAttempts - 最多尝试次数 + * @param {string} actionName - 操作名称(用于日志) + * @returns {Promise} - 是否成功跳转 + */ + async clickButtonAndWaitForPageChange(checkPageChanged, maxAttempts = 5, actionName = '点击按钮') { + let pageChanged = false; + let attempts = 0; + + while (!pageChanged && attempts < maxAttempts) { + attempts++; + + // 查找未禁用的按钮 + const button = await this.page.$('button:not([disabled])'); + if (button) { + const text = await this.page.evaluate(el => el.textContent.trim(), button); + logger.info(this.siteName, ` → 第${attempts}次${actionName}"${text}"...`); + await button.click(); + await this.human.randomDelay(2000, 3000); + + // 使用自定义检查函数判断页面是否跳转 + const changed = await checkPageChanged(); + + if (changed) { + pageChanged = true; + logger.success(this.siteName, ` → ✓ 页面已跳转`); + break; + } + + logger.warn(this.siteName, ` → 页面未跳转,${attempts}/${maxAttempts}`); + await this.human.randomDelay(2000, 3000); + } else { + logger.warn(this.siteName, ` → 未找到可点击的按钮`); + break; + } + } + + if (!pageChanged) { + logger.error(this.siteName, ` → ${maxAttempts}次尝试后页面仍未跳转`); + } + + return pageChanged; + } + /** * 生成账号数据(干运行模式) */ @@ -185,47 +231,31 @@ class WindsurfRegister { logger.warn(this.siteName, ' → 未找到同意条款checkbox,跳过'); } - // 点击Continue按钮(使用人类行为) + // 点击Continue按钮并等待跳转到密码页面 logger.info(this.siteName, ' → 点击Continue按钮...'); - // 等待按钮变为可点击状态(不再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 } - ); + // 等待按钮激活 + await this.page.waitForSelector('button:not([disabled])', { timeout: 10000 }); + logger.info(this.siteName, ' → 按钮已激活'); + + // 使用通用方法处理页面跳转 + const checkPageChanged = async () => { + // 检查是否有密码输入框 + const hasPasswordField = await this.page.evaluate(() => { + return !!document.querySelector('#password'); + }); - logger.info(this.siteName, ' → 按钮已激活'); + // 检查URL是否改变 + const currentUrl = this.page.url(); + const urlChanged = currentUrl !== this.siteUrl; - // 点击未禁用的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 (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') - ]); - } - + return hasPasswordField || urlChanged; + }; + + await this.clickButtonAndWaitForPageChange(checkPageChanged, 5, '点击Continue'); + // 额外等待确保页面稳定 - await this.human.randomDelay(1000, 2000); + await this.human.randomDelay(2000, 3000); this.currentStep = 1; logger.success(this.siteName, `步骤 1 完成`); @@ -440,6 +470,10 @@ class WindsurfRegister { // 等待验证码页面加载 await this.human.readPage(1, 2); + // 延迟15秒后再获取验证码,让邮件有足够时间到达 + logger.info(this.siteName, ' → 延迟15秒,等待邮件到达...'); + await new Promise(resolve => setTimeout(resolve, 15000)); + // 获取验证码(从邮箱) logger.info(this.siteName, ' → 正在从邮箱获取验证码...'); logger.info(this.siteName, ` → 接收邮箱: ${this.accountData.email}`);