/** * 去除HTML标签,保留纯文本内容 * @param html HTML字符串 * @returns 纯文本字符串 */ function stripHtmlTags(html: string): string { if (!html) return ''; // 去除HTML标签 let text = html.replace(/<[^>]*>/g, ''); // 解码HTML实体 text = text .replace(/ /g, ' ') .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, "'") .replace(/'/g, "'"); // 清理多余的空白字符 text = text .replace(/\s+/g, ' ') // 多个空格合并为一个 .replace(/\n\s*\n/g, '\n') // 多个换行合并 .trim(); // 去除首尾空白 return text; } export async function get_access_token(tokenInfo: any, client_id: string, clientSecret: string) { // 检查token是否过期(提前5分钟刷新) const now = Date.now(); const expiryTime = tokenInfo.timestamp + (tokenInfo.expires_in * 1000); const shouldRefresh = now >= (expiryTime - 300000); // 5分钟 = 300000毫秒 if (!shouldRefresh) { return tokenInfo.access_token; } const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ 'client_id': client_id, 'client_secret': clientSecret, 'grant_type': 'refresh_token', 'refresh_token': tokenInfo.refresh_token }) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`HTTP error! status: ${response.status}, response: ${errorText}`); } const data = await response.json() as any; return data.access_token; } //https://learn.microsoft.com/zh-cn/graph/api/resources/mail-api-overview?view=graph-rest-1.0 /** * 获取邮件列表 */ interface ParsedEmail { id: string; subject: string; from: string; to: string[]; date: { created: string; received: string; sent: string; modified: string; }; text: string; html: string; importance: string; isRead: boolean; isDraft: boolean; hasAttachments: boolean; webLink: string; preview: string; categories: string[]; internetMessageId: string; metadata: { platform?: string; browser?: string; ip?: string; location?: string; }; } async function parseEmail(email: any): Promise { let content = ''; try { if (email.body.content) { content = email.body.content; } else { const response = await fetch(email.body.contentUrl); content = await response.text(); } //const parsed = await parser.parse(content); // 从HTML内容中提取元数据 const htmlContent = email.body.content || ''; const platformMatch = htmlContent.match(/Platform:\s*([^<\r\n]+)/); const browserMatch = htmlContent.match(/Browser:\s*([^<\r\n]+)/); const ipMatch = htmlContent.match(/IP address:\s*([^<\r\n]+)/); const locationMatch = htmlContent.match(/Country\/region:\s*([^<\r\n]+)/); // 处理邮件文本内容,去除HTML标签 const rawText = email.body.content || email.bodyPreview || ''; const text = stripHtmlTags(rawText); return { id: email.id, subject: email.subject, from: email.from.emailAddress.address, to: email.toRecipients.map((r: any) => r.emailAddress.address), date: { created: email.createdDateTime, received: email.receivedDateTime, sent: email.sentDateTime, modified: email.lastModifiedDateTime }, text: text, html: email.body.content || '', importance: email.importance, isRead: email.isRead, isDraft: email.isDraft, hasAttachments: email.hasAttachments, webLink: email.webLink, preview: email.bodyPreview, categories: email.categories, internetMessageId: email.internetMessageId, metadata: { platform: platformMatch?.[1]?.trim(), browser: browserMatch?.[1]?.trim(), ip: ipMatch?.[1]?.trim(), location: locationMatch?.[1]?.trim(), } }; } catch (error) { console.error('解析邮件失败:', error); return { id: email.id, subject: email.subject, from: email.from.emailAddress.address, to: email.toRecipients.map((r: any) => r.emailAddress.address), date: { created: email.createdDateTime, received: email.receivedDateTime, sent: email.sentDateTime, modified: email.lastModifiedDateTime }, text: stripHtmlTags(email.body.content || ''), html: email.body.content || '', importance: email.importance, isRead: email.isRead, isDraft: email.isDraft, hasAttachments: email.hasAttachments, webLink: email.webLink, preview: email.bodyPreview, categories: email.categories, internetMessageId: email.internetMessageId, metadata: {} }; } } /** * 激活邮箱 - 通过多种方式唤醒可能休眠的邮箱 */ export async function activateMailbox(accessToken: string): Promise { try { // 方法1: 获取用户配置文件信息 - 这会触发用户会话 await fetch('https://graph.microsoft.com/v1.0/me', { method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' } }); // 方法2: 获取邮箱设置 - 这会激活邮箱服务 await fetch('https://graph.microsoft.com/v1.0/me/mailboxSettings', { method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' } }); // 方法3: 获取邮件文件夹 - 这会触发邮箱同步 await fetch('https://graph.microsoft.com/v1.0/me/mailFolders', { method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' } }); // 方法4: 检查收件箱状态 await fetch('https://graph.microsoft.com/v1.0/me/mailFolders/inbox', { method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' } }); console.log('邮箱激活操作完成'); } catch (error) { console.warn('邮箱激活过程中出现错误:', error); // 不抛出错误,因为激活失败不应该阻止后续的邮件获取 } } /** * 强制同步邮箱 - 使用delta查询来触发同步 */ export async function syncMailbox(accessToken: string): Promise { try { // 使用delta查询来强制同步邮箱 const deltaEndpoint = 'https://graph.microsoft.com/v1.0/me/messages/delta'; await fetch(`${deltaEndpoint}?$top=1`, { method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' } }); console.log('邮箱同步操作完成'); } catch (error) { console.warn('邮箱同步过程中出现错误:', error); } } export async function getEmails(accessToken: string, limit = 50, forceActivate = false): Promise { // 如果需要强制激活邮箱 if (forceActivate) { await activateMailbox(accessToken); await syncMailbox(accessToken); // 等待一小段时间让同步生效 await new Promise(resolve => setTimeout(resolve, 2000)); } const endpoint = 'https://graph.microsoft.com/v1.0/me/messages'; // 添加更多查询参数来确保获取最新邮件 const queryParams = new URLSearchParams({ '$top': limit.toString(), '$orderby': 'receivedDateTime desc', '$select': 'id,subject,from,toRecipients,createdDateTime,receivedDateTime,sentDateTime,lastModifiedDateTime,body,importance,isRead,isDraft,hasAttachments,webLink,bodyPreview,categories,internetMessageId' }); const response = await fetch(`${endpoint}?${queryParams}`, { method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', 'Prefer': 'outlook.timezone="Asia/Shanghai"' // 设置时区 } }); const data = await response.json() as any; if (!response.ok) { throw new Error(`获取邮件失败: ${data.error?.message}`); } const emails = data.value; const parsedEmails = await Promise.all(emails.map(parseEmail)); return parsedEmails; } /** * 删除单个邮件 */ export async function deleteEmail(accessToken: string, emailId: string): Promise { const endpoint = `https://graph.microsoft.com/v1.0/me/messages/${emailId}`; const response = await fetch(endpoint, { method: 'DELETE', headers: { 'Authorization': `Bearer ${accessToken}` } }); if (!response.ok) { const errorData = await response.json() as any; throw new Error(`删除邮件失败: ${errorData.error?.message}`); } } /** * 清空邮箱 (批量删除邮件) */ export async function emptyMailbox(accessToken: string, batchSize = 50): Promise { let hasMoreEmails = true; let totalDeleted = 0; while (hasMoreEmails) { // 获取一批邮件 const emails = await getEmails(accessToken, batchSize); if (emails.length === 0) { hasMoreEmails = false; break; } // 删除该批次的每封邮件 const deletePromises = emails.map(email => deleteEmail(accessToken, email.id)); await Promise.all(deletePromises); totalDeleted += emails.length; console.log(`已删除 ${emails.length} 封邮件,累计: ${totalDeleted}`); } console.log('邮箱已清空'); } /** * 发送邮件 */ export async function sendEmail( accessToken: string, to: string[], subject: string, body: string, isHtml = false ): Promise { const endpoint = 'https://graph.microsoft.com/v1.0/me/sendMail'; const emailData = { message: { subject, body: { contentType: isHtml ? 'HTML' : 'Text', content: body }, toRecipients: to.map(recipient => ({ emailAddress: { address: recipient } })) } }; const response = await fetch(endpoint, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify(emailData) }); if (!response.ok) { const errorData = await response.json() as any; throw new Error(`发送邮件失败: ${errorData.error?.message}`); } }