claude-code-remote-remake/email-checker.js

364 lines
12 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* TaskPing 邮件检查器
* 更强大的邮件搜索和内容提取工具
*/
const Imap = require('node-imap');
const { simpleParser } = require('mailparser');
const { spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
class EmailChecker {
constructor() {
this.configPath = path.join(__dirname, 'config/channels.json');
this.config = null;
this.imap = null;
}
async start() {
console.log('🔍 TaskPing 强化邮件检查器启动\n');
// 加载配置
if (!this.loadConfig()) {
console.log('❌ 请先配置邮件: npm run config');
process.exit(1);
}
console.log(`📧 邮箱: ${this.config.imap.auth.user}`);
console.log(`📬 通知发送到: ${this.config.to}\n`);
try {
await this.connectToEmail();
await this.comprehensiveEmailCheck();
await this.startContinuousMonitoring();
} catch (error) {
console.error('❌ 启动失败:', error.message);
process.exit(1);
}
}
loadConfig() {
try {
const data = fs.readFileSync(this.configPath, 'utf8');
const config = JSON.parse(data);
if (!config.email?.enabled) {
console.log('❌ 邮件功能未启用');
return false;
}
this.config = config.email.config;
return true;
} catch (error) {
console.log('❌ 配置文件读取失败');
return false;
}
}
async connectToEmail() {
return new Promise((resolve, reject) => {
this.imap = new Imap({
user: this.config.imap.auth.user,
password: this.config.imap.auth.pass,
host: this.config.imap.host,
port: this.config.imap.port,
tls: this.config.imap.secure,
connTimeout: 60000,
authTimeout: 30000,
keepalive: true
});
this.imap.once('ready', () => {
console.log('✅ IMAP连接成功');
resolve();
});
this.imap.once('error', reject);
this.imap.connect();
});
}
async comprehensiveEmailCheck() {
console.log('\n🔍 开始全面邮件检查...\n');
await this.openInbox();
// 1. 检查最近24小时所有邮件不仅是未读
console.log('📅 1. 检查最近24小时所有邮件...');
await this.searchEmails('24h', false);
// 2. 检查最近1小时未读邮件
console.log('\n📧 2. 检查最近1小时未读邮件...');
await this.searchEmails('1h', true);
// 3. 检查主题包含特定关键词的邮件
console.log('\n🎯 3. 检查TaskPing相关邮件...');
await this.searchBySubject();
// 4. 检查来自特定发件人的邮件
console.log('\n👤 4. 检查来自目标邮箱的邮件...');
await this.searchByFrom();
}
async openInbox() {
return new Promise((resolve, reject) => {
this.imap.openBox('INBOX', false, (err, box) => {
if (err) {
reject(err);
return;
}
console.log(`📫 收件箱: 总计${box.messages.total}封邮件`);
resolve(box);
});
});
}
async searchEmails(timeRange, unseenOnly = false) {
return new Promise((resolve) => {
const since = new Date();
if (timeRange === '1h') {
since.setHours(since.getHours() - 1);
} else if (timeRange === '24h') {
since.setDate(since.getDate() - 1);
}
let searchCriteria = [['SINCE', since]];
if (unseenOnly) {
searchCriteria.push(['UNSEEN']);
}
console.log(`🔍 搜索条件: ${timeRange}, ${unseenOnly ? '仅未读' : '全部'}`);
this.imap.search(searchCriteria, (err, results) => {
if (err) {
console.error('❌ 搜索失败:', err.message);
resolve();
return;
}
console.log(`📨 找到 ${results.length} 封邮件`);
if (results.length > 0) {
this.analyzeEmails(results.slice(-5)); // 只分析最新5封
}
resolve();
});
});
}
async searchBySubject() {
return new Promise((resolve) => {
// 搜索主题包含Re:或TaskPing的邮件
this.imap.search([['OR', ['SUBJECT', 'Re:'], ['SUBJECT', 'TaskPing']]], (err, results) => {
if (err) {
console.error('❌ 主题搜索失败:', err.message);
resolve();
return;
}
console.log(`📨 找到 ${results.length} 封相关邮件`);
if (results.length > 0) {
this.analyzeEmails(results.slice(-3)); // 只分析最新3封
}
resolve();
});
});
}
async searchByFrom() {
return new Promise((resolve) => {
// 搜索来自目标邮箱的邮件
const targetEmail = this.config.to;
this.imap.search([['FROM', targetEmail]], (err, results) => {
if (err) {
console.error('❌ 发件人搜索失败:', err.message);
resolve();
return;
}
console.log(`📨 找到来自 ${targetEmail}${results.length} 封邮件`);
if (results.length > 0) {
this.analyzeEmails(results.slice(-3)); // 只分析最新3封
}
resolve();
});
});
}
analyzeEmails(emailUids) {
const fetch = this.imap.fetch(emailUids, {
bodies: '',
markSeen: false // 不标记为已读
});
fetch.on('message', (msg, seqno) => {
console.log(`\n📧 分析邮件 ${seqno}:`);
let buffer = '';
msg.on('body', (stream) => {
stream.on('data', (chunk) => {
buffer += chunk.toString('utf8');
});
stream.once('end', async () => {
try {
const parsed = await simpleParser(buffer);
await this.processEmailContent(parsed, seqno);
} catch (error) {
console.error(`❌ 解析邮件 ${seqno} 失败:`, error.message);
}
});
});
});
}
async processEmailContent(email, seqno) {
console.log(` 📄 主题: ${email.subject || '(无主题)'}`);
console.log(` 👤 发件人: ${email.from?.text || '(未知)'}`);
console.log(` 📅 时间: ${email.date || '(未知)'}`);
// 判断是否是潜在的回复邮件
const isPotentialReply = this.isPotentialReply(email);
console.log(` 🎯 潜在回复: ${isPotentialReply ? '是' : '否'}`);
if (isPotentialReply) {
const command = this.extractCommand(email);
console.log(` 💬 邮件内容长度: ${(email.text || '').length} 字符`);
console.log(` 📝 提取的内容:\n"${command.substring(0, 200)}${command.length > 200 ? '...' : ''}"`);
if (command && command.trim().length > 0) {
console.log(`\n🎉 发现有效命令! (邮件 ${seqno})`);
await this.handleCommand(command, seqno);
}
}
}
isPotentialReply(email) {
const subject = email.subject || '';
const from = email.from?.text || '';
const targetEmail = this.config.to;
// 检查多种条件
return (
subject.includes('[TaskPing]') ||
subject.match(/^(Re:|RE:|回复:)/i) ||
from.includes(targetEmail) ||
(email.text && email.text.length > 10) // 有实际内容
);
}
extractCommand(email) {
let text = email.text || '';
const lines = text.split('\n');
const commandLines = [];
for (const line of lines) {
// 停止处理当遇到原始邮件标记
if (line.includes('-----Original Message-----') ||
line.includes('--- Original Message ---') ||
(line.includes('在') && line.includes('写道:')) ||
(line.includes('On') && line.includes('wrote:')) ||
line.match(/^>\s*/) ||
line.includes('会话ID:') ||
line.includes('TaskPing <') ||
line.match(/\d{4}年\d{1,2}月\d{1,2}日/)) {
break;
}
// 跳过签名和空行
if (line.includes('--') ||
line.includes('Sent from') ||
line.includes('发自我的') ||
line.trim() === '') {
continue;
}
commandLines.push(line);
}
return commandLines.join('\n').trim();
}
async handleCommand(command, seqno) {
console.log(`\n🚀 处理命令 (来自邮件 ${seqno}):`);
console.log(`📝 命令内容: "${command}"`);
try {
// 复制到剪贴板
await this.copyToClipboard(command);
console.log('✅ 命令已复制到剪贴板');
// 发送通知
await this.sendNotification(command);
console.log('✅ 通知已发送');
console.log('\n🎯 请在Claude Code中粘贴命令 (Cmd+V)');
} catch (error) {
console.error('❌ 处理命令失败:', error.message);
}
}
async copyToClipboard(command) {
return new Promise((resolve, reject) => {
const pbcopy = spawn('pbcopy');
pbcopy.stdin.write(command);
pbcopy.stdin.end();
pbcopy.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error('剪贴板复制失败'));
}
});
});
}
async sendNotification(command) {
const shortCommand = command.length > 50 ? command.substring(0, 50) + '...' : command;
const script = `
display notification "邮件命令已复制到剪贴板请在Claude Code中粘贴" with title "TaskPing" subtitle "${shortCommand.replace(/"/g, '\\"')}" sound name "Glass"
`;
spawn('osascript', ['-e', script]);
}
async startContinuousMonitoring() {
console.log('\n👂 开始持续监控新邮件...');
console.log('💌 现在可以回复邮件测试功能');
console.log('🔍 每30秒检查一次新邮件\n');
setInterval(async () => {
try {
console.log('🔄 检查新邮件...');
await this.searchEmails('1h', false); // 搜索所有邮件,不只是未读
} catch (error) {
console.error('❌ 监控检查失败:', error.message);
}
}, 30000); // 每30秒检查一次
// 设置优雅关闭
process.on('SIGINT', () => {
console.log('\n🛑 停止邮件监控...');
if (this.imap) {
this.imap.end();
}
console.log('✅ 服务已停止');
process.exit(0);
});
process.stdin.resume();
}
}
// 启动检查器
const checker = new EmailChecker();
checker.start().catch(console.error);