修复邮件监听服务的IMAP问题

- 解决client.idle()不兼容的问题
- 改用定期检查机制监听新邮件
- 修复邮件下载和解析方法
- 改善错误处理和日志记录
- 配置使用飞书邮箱接收回复

现在可以:
1. 成功连接飞书IMAP服务器
2. 读取和解析邮件内容
3. 检测TaskPing相关邮件
4. 等待用户回复测试

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
panda 2025-07-27 02:59:03 +08:00
parent be9eb49734
commit a920a41c90
5 changed files with 300 additions and 33 deletions

View File

@ -18,12 +18,12 @@
}
},
"imap": {
"host": "imap.gmail.com",
"host": "imap.feishu.cn",
"port": 993,
"secure": true,
"auth": {
"user": "jiaxicui446@gmail.com",
"pass": "your-gmail-app-password"
"user": "noreply@pandalla.ai",
"pass": "kKgS3tNReRTL3RQC"
}
},
"from": "TaskPing 通知系统 <noreply@pandalla.ai>",

View File

@ -5,5 +5,12 @@
"expiresAt": 1753559287,
"cwd": "/Users/jessytsui/dev/TaskPing",
"description": "测试会话"
},
"TESTMDKM0Q3M": {
"type": "pty",
"createdAt": 1753556089,
"expiresAt": 1753559689,
"cwd": "/Users/jessytsui/dev/TaskPing",
"description": "测试会话"
}
}

View File

@ -0,0 +1,13 @@
{
"id": "ace02d65-154a-4c95-9616-cb6e66ad06ec",
"created": "2025-07-26T18:49:27.369Z",
"expires": "2025-07-27T18:49:27.369Z",
"notification": {
"type": "completed",
"project": "TaskPing",
"message": "[TaskPing] 任务已完成Claude正在等待下一步指令"
},
"status": "waiting",
"commandCount": 0,
"maxCommands": 10
}

View File

@ -165,9 +165,9 @@ function stripReply(text = '') {
}
// 处理邮件消息
async function handleMailMessage(source, uid) {
async function handleMailMessage(source, uid, parsedEmail = null) {
try {
const parsed = await simpleParser(source);
const parsed = parsedEmail || await simpleParser(source);
// 检查是否已处理过
const messageId = parsed.messageId;
@ -365,45 +365,132 @@ async function startImap() {
log.info(`Found ${messages.length} unread messages`);
for (const uid of messages) {
const { source } = await client.download(uid, '1', { uid: true });
const chunks = [];
for await (const chunk of source) {
chunks.push(chunk);
try {
log.debug({ uid }, 'Downloading message');
const message = await client.fetchOne(uid, {
bodyText: true,
bodyStructure: true,
envelope: true
}, { uid: true });
if (!message) {
log.warn({ uid }, 'Could not fetch message');
continue;
}
await handleMailMessage(Buffer.concat(chunks), uid);
// 使用简单的方式获取邮件内容
const emailText = message.bodyText?.value || '';
const envelope = message.envelope;
// 构造简单的邮件对象用于解析
const simpleEmail = {
from: envelope.from?.[0] ? {
text: `${envelope.from[0].name || ''} <${envelope.from[0].address}>`,
value: envelope.from
} : null,
subject: envelope.subject,
text: emailText,
date: envelope.date,
messageId: envelope.messageId,
headers: new Map()
};
await handleMailMessage(null, uid, simpleEmail);
// 标记为已读
await client.messageFlagsAdd(uid, ['\\Seen'], { uid: true });
} catch (downloadError) {
log.error({
uid,
error: downloadError.message,
code: downloadError.code
}, 'Failed to download message');
}
}
}
// 监听新邮件
log.info('Starting IMAP monitor...');
// 使用定期检查替代IDLE监听更稳定
log.info('Starting periodic email check...');
for await (const msg of client.idle()) {
if (msg.path === 'INBOX' && msg.type === 'exists') {
log.debug({ count: msg.count }, 'New message notification');
setInterval(async () => {
try {
const newMessages = await client.search({ seen: false });
if (newMessages.length > 0) {
log.debug({ count: newMessages.length }, 'Found new messages');
// 获取最新的邮件
const messages = await client.search({ seen: false });
for (const uid of messages) {
const { source } = await client.download(uid, '1', { uid: true });
const chunks = [];
for await (const chunk of source) {
chunks.push(chunk);
for (const uid of newMessages) {
// 检查是否已处理过这个UID
if (PROCESSED_MESSAGES.has(`uid_${uid}`)) {
continue;
}
await handleMailMessage(Buffer.concat(chunks), uid);
try {
log.debug({ uid }, 'Processing new message');
const message = await client.fetchOne(uid, {
bodyText: true,
bodyStructure: true,
envelope: true
}, { uid: true });
if (!message) {
log.warn({ uid }, 'Could not fetch new message');
continue;
}
// 使用简单的方式获取邮件内容
const emailText = message.bodyText?.value || '';
const envelope = message.envelope;
// 构造简单的邮件对象用于解析
const simpleEmail = {
from: envelope.from?.[0] ? {
text: `${envelope.from[0].name || ''} <${envelope.from[0].address}>`,
value: envelope.from
} : null,
subject: envelope.subject,
text: emailText,
date: envelope.date,
messageId: envelope.messageId,
headers: new Map()
};
await handleMailMessage(null, uid, simpleEmail);
// 标记为已处理
PROCESSED_MESSAGES.add(`uid_${uid}`);
// 标记为已读
await client.messageFlagsAdd(uid, ['\\Seen'], { uid: true });
} catch (processError) {
log.error({
uid,
error: processError.message,
code: processError.code
}, 'Failed to process new message');
}
}
}
} catch (checkError) {
log.error({ error: checkError.message }, 'Error during periodic check');
}
}, 10000); // 每10秒检查一次
// 保持连接活跃
log.info('Email monitoring active, checking every 10 seconds...');
// 无限等待(保持进程运行)
await new Promise(resolve => {
// 进程会一直运行直到被终止
});
} finally {
lock.release();
}
} catch (error) {
log.error({ error }, 'IMAP error');
log.error({
error: error.message,
code: error.code,
stack: error.stack
}, 'IMAP error');
throw error;
} finally {
await client.logout();

160
test-imap-connection.js Executable file
View File

@ -0,0 +1,160 @@
#!/usr/bin/env node
/**
* 测试 IMAP 连接
* 验证邮箱服务器连接和邮件读取
*/
const { ImapFlow } = require('imapflow');
const { simpleParser } = require('mailparser');
require('dotenv').config();
async function testImapConnection() {
console.log('🔍 测试 IMAP 连接...\n');
const client = new ImapFlow({
host: process.env.IMAP_HOST,
port: Number(process.env.IMAP_PORT || 993),
secure: process.env.IMAP_SECURE === 'true',
auth: {
user: process.env.IMAP_USER,
pass: process.env.IMAP_PASS
},
logger: false
});
try {
console.log(`连接到: ${process.env.IMAP_HOST}:${process.env.IMAP_PORT}`);
console.log(`账号: ${process.env.IMAP_USER}`);
console.log(`SSL: ${process.env.IMAP_SECURE}`);
console.log('');
// 连接
await client.connect();
console.log('✅ IMAP 连接成功');
// 打开收件箱
const lock = await client.getMailboxLock('INBOX');
console.log('✅ 收件箱已打开');
try {
// 获取邮箱状态
const status = await client.status('INBOX', {
messages: true,
unseen: true,
recent: true
});
console.log(`📧 邮箱状态:`);
console.log(` 总邮件数: ${status.messages}`);
console.log(` 未读邮件: ${status.unseen}`);
console.log(` 新邮件: ${status.recent}`);
console.log('');
// 搜索未读邮件
const unseenMessages = await client.search({ seen: false });
console.log(`🔍 找到 ${unseenMessages.length} 封未读邮件`);
if (unseenMessages.length > 0) {
console.log('📋 未读邮件列表:');
// 只处理最近的5封邮件
const recentMessages = unseenMessages.slice(-5);
for (const uid of recentMessages) {
try {
console.log(`\n📧 处理邮件 UID: ${uid}`);
// 获取邮件头信息
const envelope = await client.fetchOne(uid, {
envelope: true,
flags: true
}, { uid: true });
console.log(` 发件人: ${envelope.envelope?.from?.[0]?.address || 'unknown'}`);
console.log(` 主题: ${envelope.envelope?.subject || 'no subject'}`);
console.log(` 日期: ${envelope.envelope?.date?.toLocaleString('zh-CN') || 'unknown'}`);
console.log(` 标志: ${Array.isArray(envelope.flags) ? envelope.flags.join(', ') : 'none'}`);
// 下载并解析邮件
const message = await client.fetchOne(uid, {
source: true
}, { uid: true });
if (!message || !message.source) {
console.log(` ⚠️ 无法获取邮件内容`);
continue;
}
const chunks = [];
for await (const chunk of message.source) {
chunks.push(chunk);
}
const parsed = await simpleParser(Buffer.concat(chunks));
// 检查是否是 TaskPing 相关邮件
const isTaskPingReply = parsed.subject?.includes('TaskPing') ||
parsed.subject?.includes('[TaskPing') ||
parsed.text?.includes('TaskPing') ||
parsed.headers?.get('x-taskping-session-id');
if (isTaskPingReply) {
console.log(` 🎯 这是 TaskPing 相关邮件!`);
console.log(` 正文预览: ${(parsed.text || '').substring(0, 100)}...`);
// 尝试提取 Token
const tokenMatch = parsed.subject?.match(/\[TaskPing\s+#([A-Za-z0-9_-]+)\]/);
if (tokenMatch) {
console.log(` 🔑 找到 Token: ${tokenMatch[1]}`);
}
} else {
console.log(` 非 TaskPing 邮件,跳过`);
}
} catch (messageError) {
console.log(` ❌ 处理邮件失败: ${messageError.message}`);
}
}
}
} finally {
lock.release();
}
console.log('\n✅ IMAP 测试完成');
} catch (error) {
console.error('\n❌ IMAP 连接失败:', error.message);
console.log('\n可能的原因:');
console.log('1. 邮箱服务器地址或端口错误');
console.log('2. 用户名或密码错误');
console.log('3. IMAP 服务未开启');
console.log('4. 网络连接问题');
console.log('5. SSL/TLS 配置错误');
if (error.code) {
console.log(`\n错误代码: ${error.code}`);
}
} finally {
await client.logout();
}
}
// 主函数
async function main() {
console.log('╔══════════════════════════════════════════════════════════╗');
console.log('║ TaskPing IMAP Connection Test ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
// 检查配置
if (!process.env.IMAP_HOST || !process.env.IMAP_USER || !process.env.IMAP_PASS) {
console.error('❌ 缺少 IMAP 配置,请检查 .env 文件');
process.exit(1);
}
await testImapConnection();
}
// 运行
main().catch(console.error);