203 lines
6.9 KiB
JavaScript
203 lines
6.9 KiB
JavaScript
/**
|
||
* 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<string>} - 验证码
|
||
*/
|
||
async getVerificationCode(siteName, recipientEmail, timeout = 60) {
|
||
logger.info('EmailVerification', `开始获取 ${siteName} 的验证码...`);
|
||
logger.info('EmailVerification', `接收邮箱: ${recipientEmail}`);
|
||
|
||
try {
|
||
// 1. 初始化连接器
|
||
this.connector = new ImapConnector(this.config);
|
||
|
||
// 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} 次检查邮件...`);
|
||
|
||
// 关键:每次都重新连接以获取最新邮件(QQ邮箱IMAP有延迟问题)
|
||
if (attempts > 1) {
|
||
logger.info('EmailVerification', '断开并重新连接以刷新邮件...');
|
||
this.connector.disconnect();
|
||
await this.sleep(1000);
|
||
}
|
||
|
||
await this.connector.connect();
|
||
|
||
// 获取最新邮件(倒序排列)
|
||
const emails = await this.connector.getLatestEmails(20, 1);
|
||
|
||
if (emails && emails.length > 0) {
|
||
logger.info('EmailVerification', `找到 ${emails.length} 封邮件`);
|
||
|
||
// 打印最近5条邮件信息(倒序,最新的在前)
|
||
const recentEmails = emails.slice(-5).reverse();
|
||
logger.info('EmailVerification', '='.repeat(60));
|
||
logger.info('EmailVerification', '最近5条邮件:');
|
||
recentEmails.forEach((email, index) => {
|
||
const dateStr = email.date ? new Date(email.date).toLocaleString('zh-CN') : 'N/A';
|
||
logger.info('EmailVerification', ` ${index + 1}. 时间: ${dateStr}`);
|
||
logger.info('EmailVerification', ` 发件人: ${email.from}`);
|
||
logger.info('EmailVerification', ` 主题: ${email.subject}`);
|
||
logger.info('EmailVerification', ` 收件人: ${email.to}`);
|
||
});
|
||
logger.info('EmailVerification', '='.repeat(60));
|
||
|
||
// 3. 查找匹配的邮件并提取验证码(从最新的开始)
|
||
for (const email of emails.reverse()) {
|
||
logger.info('EmailVerification', `检查邮件: 发件人="${email.from}", 主题="${email.subject}", 收件人="${email.to}"`);
|
||
|
||
// 关键:检查收件人是否匹配
|
||
const emailTo = (email.to || '').toLowerCase();
|
||
const isForRecipient = emailTo.includes(recipientEmail.toLowerCase());
|
||
|
||
if (!isForRecipient) {
|
||
logger.info('EmailVerification', ` ✗ 跳过:收件人不匹配(需要:${recipientEmail})`);
|
||
continue;
|
||
}
|
||
|
||
logger.info('EmailVerification', ` ✓ 收件人匹配!`);
|
||
|
||
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;
|
||
} else {
|
||
logger.warn('EmailVerification', ` 邮件匹配但无法提取验证码`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
logger.warn('EmailVerification', `未找到匹配的Windsurf验证码邮件`);
|
||
} else {
|
||
logger.info('EmailVerification', `没有邮件`);
|
||
}
|
||
|
||
// 等待一段时间后再检查
|
||
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<string>}
|
||
*/
|
||
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;
|