| |
| |
| |
| |
| |
| function stripHtmlTags(html: string): string { |
| if (!html) return ''; |
|
|
| |
| let text = html.replace(/<[^>]*>/g, ''); |
|
|
| |
| 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) { |
| |
| const now = Date.now(); |
| const expiryTime = tokenInfo.timestamp + (tokenInfo.expires_in * 1000); |
| const shouldRefresh = now >= (expiryTime - 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; |
| } |
|
|
| |
|
|
| |
| |
| |
| 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<ParsedEmail> { |
|
|
| let content = ''; |
|
|
| try { |
| if (email.body.content) { |
| content = email.body.content; |
| } else { |
| const response = await fetch(email.body.contentUrl); |
| content = await response.text(); |
| } |
|
|
| |
|
|
| |
| 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]+)/); |
|
|
| |
| 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<void> { |
| try { |
| |
| await fetch('https://graph.microsoft.com/v1.0/me', { |
| method: 'GET', |
| headers: { |
| 'Authorization': `Bearer ${accessToken}`, |
| 'Content-Type': 'application/json' |
| } |
| }); |
|
|
| |
| await fetch('https://graph.microsoft.com/v1.0/me/mailboxSettings', { |
| method: 'GET', |
| headers: { |
| 'Authorization': `Bearer ${accessToken}`, |
| 'Content-Type': 'application/json' |
| } |
| }); |
|
|
| |
| await fetch('https://graph.microsoft.com/v1.0/me/mailFolders', { |
| method: 'GET', |
| headers: { |
| 'Authorization': `Bearer ${accessToken}`, |
| 'Content-Type': 'application/json' |
| } |
| }); |
|
|
| |
| 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); |
| |
| } |
| } |
|
|
| |
| |
| |
| export async function syncMailbox(accessToken: string): Promise<void> { |
| try { |
| |
| 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<ParsedEmail[]> { |
| |
| 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<void> { |
| 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<void> { |
| 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<void> { |
| 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}`); |
| } |
| } |