github-actions[bot] commited on
Commit
3a7f0e0
·
1 Parent(s): 099ce91

Update from GitHub Actions

Browse files
functions/utils/mail.ts CHANGED
@@ -1,32 +1,60 @@
1
- import PostalMime from 'postal-mime';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  export async function get_access_token(tokenInfo: any, client_id: string, clientSecret: string) {
4
- // 检查token是否过期(提前5分钟刷新)
5
- const now = Date.now();
6
- const expiryTime = tokenInfo.timestamp + (tokenInfo.expires_in * 1000);
7
- const shouldRefresh = now >= (expiryTime - 300000); // 5分钟 = 300000毫秒
8
- if (!shouldRefresh) {
9
- return tokenInfo.access_token;
10
- }
11
- const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
12
- method: 'POST',
13
- headers: {
14
- 'Content-Type': 'application/x-www-form-urlencoded'
15
- },
16
- body: new URLSearchParams({
17
- 'client_id': client_id,
18
- 'client_secret': clientSecret,
19
- 'grant_type': 'refresh_token',
20
- 'refresh_token': tokenInfo.refresh_token
21
- })
22
- });
23
-
24
- if (!response.ok) {
25
- const errorText = await response.text();
26
- throw new Error(`HTTP error! status: ${response.status}, response: ${errorText}`);
27
- }
28
- const data = await response.json() as any;
29
- return data.access_token;
30
  }
31
 
32
  //https://learn.microsoft.com/zh-cn/graph/api/resources/mail-api-overview?view=graph-rest-1.0
@@ -64,9 +92,9 @@ interface ParsedEmail {
64
  }
65
 
66
  async function parseEmail(email: any): Promise<ParsedEmail> {
67
- const parser = new PostalMime();
68
  let content = '';
69
-
70
  try {
71
  if (email.body.content) {
72
  content = email.body.content;
@@ -74,8 +102,8 @@ async function parseEmail(email: any): Promise<ParsedEmail> {
74
  const response = await fetch(email.body.contentUrl);
75
  content = await response.text();
76
  }
77
-
78
- const parsed = await parser.parse(content);
79
 
80
  // 从HTML内容中提取元数据
81
  const htmlContent = email.body.content || '';
@@ -83,7 +111,11 @@ async function parseEmail(email: any): Promise<ParsedEmail> {
83
  const browserMatch = htmlContent.match(/Browser:\s*([^<\r\n]+)/);
84
  const ipMatch = htmlContent.match(/IP address:\s*([^<\r\n]+)/);
85
  const locationMatch = htmlContent.match(/Country\/region:\s*([^<\r\n]+)/);
86
-
 
 
 
 
87
  return {
88
  id: email.id,
89
  subject: email.subject,
@@ -95,8 +127,8 @@ async function parseEmail(email: any): Promise<ParsedEmail> {
95
  sent: email.sentDateTime,
96
  modified: email.lastModifiedDateTime
97
  },
98
- text: parsed.text || email.bodyPreview || '',
99
- html: parsed.html || email.body.content || '',
100
  importance: email.importance,
101
  isRead: email.isRead,
102
  isDraft: email.isDraft,
@@ -125,7 +157,7 @@ async function parseEmail(email: any): Promise<ParsedEmail> {
125
  sent: email.sentDateTime,
126
  modified: email.lastModifiedDateTime
127
  },
128
- text: email.bodyPreview || '',
129
  html: email.body.content || '',
130
  importance: email.importance,
131
  isRead: email.isRead,
@@ -141,109 +173,109 @@ async function parseEmail(email: any): Promise<ParsedEmail> {
141
  }
142
 
143
  export async function getEmails(accessToken: string, limit = 50): Promise<ParsedEmail[]> {
144
- const endpoint = 'https://graph.microsoft.com/v1.0/me/messages';
145
- const response = await fetch(`${endpoint}?$top=${limit}`, {
146
- method: 'GET',
147
- headers: {
148
- 'Authorization': `Bearer ${accessToken}`,
149
- 'Content-Type': 'application/json'
150
- }
151
- });
152
-
153
- const data = await response.json() as any;
154
-
155
- if (!response.ok) {
156
- throw new Error(`获取邮件失败: ${data.error?.message}`);
157
  }
158
- const emails = data.value;
159
- const parsedEmails = await Promise.all(emails.map(parseEmail));
160
- return parsedEmails;
 
 
 
161
  }
162
-
163
- /**
164
- * 删除单个邮件
165
- */
166
- export async function deleteEmail(accessToken: string, emailId: string): Promise<void> {
167
- const endpoint = `https://graph.microsoft.com/v1.0/me/messages/${emailId}`;
168
-
169
- const response = await fetch(endpoint, {
170
- method: 'DELETE',
171
- headers: {
172
- 'Authorization': `Bearer ${accessToken}`
173
- }
174
- });
175
-
176
- if (!response.ok) {
177
- const errorData = await response.json() as any;
178
- throw new Error(`删除邮件失败: ${errorData.error?.message}`);
179
  }
 
 
 
 
 
180
  }
181
-
182
- /**
183
- * 清空邮箱 (批量删除邮件)
184
- */
185
- export async function emptyMailbox(accessToken: string, batchSize = 50): Promise<void> {
186
- let hasMoreEmails = true;
187
- let totalDeleted = 0;
188
-
189
- while (hasMoreEmails) {
190
- // 获取一批邮件
191
- const emails = await getEmails(accessToken, batchSize);
192
-
193
- if (emails.length === 0) {
194
- hasMoreEmails = false;
195
- break;
196
- }
197
-
198
- // 删除该批次的每封邮件
199
- const deletePromises = emails.map(email => deleteEmail(accessToken, email.id));
200
- await Promise.all(deletePromises);
201
-
202
- totalDeleted += emails.length;
203
- console.log(`已删除 ${emails.length} 封邮件,累计: ${totalDeleted}`);
204
  }
205
-
206
- console.log('邮箱已清空');
 
 
 
 
 
207
  }
208
-
209
- /**
210
- * 发送邮件
211
- */
212
- export async function sendEmail(
213
- accessToken: string,
214
- to: string[],
215
- subject: string,
216
- body: string,
217
- isHtml = false
218
- ): Promise<void> {
219
- const endpoint = 'https://graph.microsoft.com/v1.0/me/sendMail';
220
-
221
- const emailData = {
222
- message: {
223
- subject,
224
- body: {
225
- contentType: isHtml ? 'HTML' : 'Text',
226
- content: body
227
- },
228
- toRecipients: to.map(recipient => ({
229
- emailAddress: {
230
- address: recipient
231
- }
232
- }))
233
- }
234
- };
235
-
236
- const response = await fetch(endpoint, {
237
- method: 'POST',
238
- headers: {
239
- 'Authorization': `Bearer ${accessToken}`,
240
- 'Content-Type': 'application/json'
241
  },
242
- body: JSON.stringify(emailData)
243
- });
244
-
245
- if (!response.ok) {
246
- const errorData = await response.json() as any;
247
- throw new Error(`发送邮件失败: ${errorData.error?.message}`);
248
  }
249
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * 去除HTML标签,保留纯文本内容
3
+ * @param html HTML字符串
4
+ * @returns 纯文本字符串
5
+ */
6
+ function stripHtmlTags(html: string): string {
7
+ if (!html) return '';
8
+
9
+ // 去除HTML标签
10
+ let text = html.replace(/<[^>]*>/g, '');
11
+
12
+ // 解码HTML实体
13
+ text = text
14
+ .replace(/&nbsp;/g, ' ')
15
+ .replace(/&amp;/g, '&')
16
+ .replace(/&lt;/g, '<')
17
+ .replace(/&gt;/g, '>')
18
+ .replace(/&quot;/g, '"')
19
+ .replace(/&#39;/g, "'")
20
+ .replace(/&apos;/g, "'");
21
+
22
+ // 清理多余的空白字符
23
+ text = text
24
+ .replace(/\s+/g, ' ') // 多个空格合并为一个
25
+ .replace(/\n\s*\n/g, '\n') // 多个换行合并
26
+ .trim(); // 去除首尾空白
27
+
28
+ return text;
29
+ }
30
 
31
  export async function get_access_token(tokenInfo: any, client_id: string, clientSecret: string) {
32
+ // 检查token是否过期(提前5分钟刷新)
33
+ const now = Date.now();
34
+ const expiryTime = tokenInfo.timestamp + (tokenInfo.expires_in * 1000);
35
+ const shouldRefresh = now >= (expiryTime - 300000); // 5分钟 = 300000毫秒
36
+ if (!shouldRefresh) {
37
+ return tokenInfo.access_token;
38
+ }
39
+ const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
40
+ method: 'POST',
41
+ headers: {
42
+ 'Content-Type': 'application/x-www-form-urlencoded'
43
+ },
44
+ body: new URLSearchParams({
45
+ 'client_id': client_id,
46
+ 'client_secret': clientSecret,
47
+ 'grant_type': 'refresh_token',
48
+ 'refresh_token': tokenInfo.refresh_token
49
+ })
50
+ });
51
+
52
+ if (!response.ok) {
53
+ const errorText = await response.text();
54
+ throw new Error(`HTTP error! status: ${response.status}, response: ${errorText}`);
55
+ }
56
+ const data = await response.json() as any;
57
+ return data.access_token;
58
  }
59
 
60
  //https://learn.microsoft.com/zh-cn/graph/api/resources/mail-api-overview?view=graph-rest-1.0
 
92
  }
93
 
94
  async function parseEmail(email: any): Promise<ParsedEmail> {
95
+
96
  let content = '';
97
+
98
  try {
99
  if (email.body.content) {
100
  content = email.body.content;
 
102
  const response = await fetch(email.body.contentUrl);
103
  content = await response.text();
104
  }
105
+
106
+ //const parsed = await parser.parse(content);
107
 
108
  // 从HTML内容中提取元数据
109
  const htmlContent = email.body.content || '';
 
111
  const browserMatch = htmlContent.match(/Browser:\s*([^<\r\n]+)/);
112
  const ipMatch = htmlContent.match(/IP address:\s*([^<\r\n]+)/);
113
  const locationMatch = htmlContent.match(/Country\/region:\s*([^<\r\n]+)/);
114
+
115
+ // 处理邮件文本内容,去除HTML标签
116
+ const rawText = email.body.content || email.bodyPreview || '';
117
+ const text = stripHtmlTags(rawText);
118
+
119
  return {
120
  id: email.id,
121
  subject: email.subject,
 
127
  sent: email.sentDateTime,
128
  modified: email.lastModifiedDateTime
129
  },
130
+ text: text,
131
+ html: email.body.content || '',
132
  importance: email.importance,
133
  isRead: email.isRead,
134
  isDraft: email.isDraft,
 
157
  sent: email.sentDateTime,
158
  modified: email.lastModifiedDateTime
159
  },
160
+ text: stripHtmlTags(email.body.content || ''),
161
  html: email.body.content || '',
162
  importance: email.importance,
163
  isRead: email.isRead,
 
173
  }
174
 
175
  export async function getEmails(accessToken: string, limit = 50): Promise<ParsedEmail[]> {
176
+ const endpoint = 'https://graph.microsoft.com/v1.0/me/messages';
177
+ const response = await fetch(`${endpoint}?$top=${limit}`, {
178
+ method: 'GET',
179
+ headers: {
180
+ 'Authorization': `Bearer ${accessToken}`,
181
+ 'Content-Type': 'application/json'
 
 
 
 
 
 
 
182
  }
183
+ });
184
+
185
+ const data = await response.json() as any;
186
+
187
+ if (!response.ok) {
188
+ throw new Error(`获取邮件失败: ${data.error?.message}`);
189
  }
190
+ const emails = data.value;
191
+ const parsedEmails = await Promise.all(emails.map(parseEmail));
192
+ return parsedEmails;
193
+ }
194
+
195
+ /**
196
+ * 删除单个邮件
197
+ */
198
+ export async function deleteEmail(accessToken: string, emailId: string): Promise<void> {
199
+ const endpoint = `https://graph.microsoft.com/v1.0/me/messages/${emailId}`;
200
+
201
+ const response = await fetch(endpoint, {
202
+ method: 'DELETE',
203
+ headers: {
204
+ 'Authorization': `Bearer ${accessToken}`
 
 
205
  }
206
+ });
207
+
208
+ if (!response.ok) {
209
+ const errorData = await response.json() as any;
210
+ throw new Error(`删除邮件失败: ${errorData.error?.message}`);
211
  }
212
+ }
213
+
214
+ /**
215
+ * 清空邮箱 (批量删除邮件)
216
+ */
217
+ export async function emptyMailbox(accessToken: string, batchSize = 50): Promise<void> {
218
+ let hasMoreEmails = true;
219
+ let totalDeleted = 0;
220
+
221
+ while (hasMoreEmails) {
222
+ // 获取一批邮件
223
+ const emails = await getEmails(accessToken, batchSize);
224
+
225
+ if (emails.length === 0) {
226
+ hasMoreEmails = false;
227
+ break;
 
 
 
 
 
 
 
228
  }
229
+
230
+ // 删除该批次的每封邮件
231
+ const deletePromises = emails.map(email => deleteEmail(accessToken, email.id));
232
+ await Promise.all(deletePromises);
233
+
234
+ totalDeleted += emails.length;
235
+ console.log(`已删除 ${emails.length} 封邮件,累计: ${totalDeleted}`);
236
  }
237
+
238
+ console.log('邮箱已清空');
239
+ }
240
+
241
+ /**
242
+ * 发送邮件
243
+ */
244
+ export async function sendEmail(
245
+ accessToken: string,
246
+ to: string[],
247
+ subject: string,
248
+ body: string,
249
+ isHtml = false
250
+ ): Promise<void> {
251
+ const endpoint = 'https://graph.microsoft.com/v1.0/me/sendMail';
252
+
253
+ const emailData = {
254
+ message: {
255
+ subject,
256
+ body: {
257
+ contentType: isHtml ? 'HTML' : 'Text',
258
+ content: body
 
 
 
 
 
 
 
 
 
 
 
259
  },
260
+ toRecipients: to.map(recipient => ({
261
+ emailAddress: {
262
+ address: recipient
263
+ }
264
+ }))
 
265
  }
266
+ };
267
+
268
+ const response = await fetch(endpoint, {
269
+ method: 'POST',
270
+ headers: {
271
+ 'Authorization': `Bearer ${accessToken}`,
272
+ 'Content-Type': 'application/json'
273
+ },
274
+ body: JSON.stringify(emailData)
275
+ });
276
+
277
+ if (!response.ok) {
278
+ const errorData = await response.json() as any;
279
+ throw new Error(`发送邮件失败: ${errorData.error?.message}`);
280
+ }
281
+ }
package-lock.json CHANGED
The diff for this file is too large to render. See raw diff
 
package.json CHANGED
@@ -20,7 +20,6 @@
20
  "monaco-editor": "^0.52.2",
21
  "pinia": "^3.0.1",
22
  "playwright": "^1.51.0",
23
- "postal-mime": "^2.4.3",
24
  "tailwindcss": "^4.0.14",
25
  "tdesign-vue-next": "^1.11.4",
26
  "unstorage": "^1.15.0",
 
20
  "monaco-editor": "^0.52.2",
21
  "pinia": "^3.0.1",
22
  "playwright": "^1.51.0",
 
23
  "tailwindcss": "^4.0.14",
24
  "tdesign-vue-next": "^1.11.4",
25
  "unstorage": "^1.15.0",
src/views/MailView.vue CHANGED
@@ -7,7 +7,7 @@ const loading = ref(false);
7
  const pagination = ref({
8
  current: 1,
9
  total: 0,
10
- pageSize: 18
11
  });
12
 
13
  const allData = ref<Account[]>([]);
 
7
  const pagination = ref({
8
  current: 1,
9
  total: 0,
10
+ pageSize: 15
11
  });
12
 
13
  const allData = ref<Account[]>([]);