dsh-email 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/lib/index.js +9 -1
- package/lib/mail-client.js +26 -8
- package/package.json +71 -74
package/README.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
# dsh-email
|
|
4
4
|
|
|
5
|
+
> **给 agent 一个邮箱**:八个国内邮箱预设开箱即用,收发/搜索/附件下载全搞定。
|
|
6
|
+
|
|
7
|
+
   
|
|
8
|
+
|
|
9
|
+
|
|
5
10
|

|
|
6
11
|
|
|
7
12
|
|
|
@@ -158,3 +163,4 @@ MIT。这是一个社区插件,与 DeepSeek 官方无关;`@deepseek-ai/*`
|
|
|
158
163
|
- [dsh-slack](https://github.com/STARDUSTLC666/dsh-slack) — Slack 通知/收件箱
|
|
159
164
|
- [dsh-dingtalk](https://github.com/STARDUSTLC666/dsh-dingtalk) — 钉钉群通知(零依赖)
|
|
160
165
|
- [dsh-email](https://github.com/STARDUSTLC666/dsh-email) — 邮件六件套 + Web 设置页
|
|
166
|
+
|
package/lib/index.js
CHANGED
|
@@ -118,6 +118,14 @@ const attachmentSchema = {
|
|
|
118
118
|
function oneText(text) {
|
|
119
119
|
return [{ type: 'text', text }];
|
|
120
120
|
}
|
|
121
|
+
function normalizeAttachmentPaths(value) {
|
|
122
|
+
if (value === undefined || value === null)
|
|
123
|
+
return undefined;
|
|
124
|
+
if (!Array.isArray(value) || value.some(path => typeof path !== 'string' || path.trim() === '')) {
|
|
125
|
+
throw new Error('attachments 必须是文件路径字符串数组(且不能包含空字符串)');
|
|
126
|
+
}
|
|
127
|
+
return value.map(path => path.trim());
|
|
128
|
+
}
|
|
121
129
|
function describeMessage(message) {
|
|
122
130
|
const from = message.from.map(a => a.name ?? a.address).filter(Boolean).join(', ') || '(未知)';
|
|
123
131
|
const flags = [
|
|
@@ -300,7 +308,7 @@ export function apply(ctx, config = {}) {
|
|
|
300
308
|
throw new Error('to 不能为空');
|
|
301
309
|
if (typeof args.subject !== 'string' || args.subject.trim() === '')
|
|
302
310
|
throw new Error('subject 不能为空');
|
|
303
|
-
return await getPool().send(args.account, args.to.trim(), args.subject.trim(), typeof args.text === 'string' ? args.text : undefined, typeof args.cc === 'string' && args.cc.trim() !== '' ? args.cc.trim() : undefined,
|
|
311
|
+
return await getPool().send(args.account, args.to.trim(), args.subject.trim(), typeof args.text === 'string' ? args.text : undefined, typeof args.cc === 'string' && args.cc.trim() !== '' ? args.cc.trim() : undefined, normalizeAttachmentPaths(args.attachments));
|
|
304
312
|
},
|
|
305
313
|
});
|
|
306
314
|
ctx.tools.register({
|
package/lib/mail-client.js
CHANGED
|
@@ -64,6 +64,11 @@ export function messageMatchesQuery(subject, fromText, body, query) {
|
|
|
64
64
|
|| fromText.toLowerCase().includes(q)
|
|
65
65
|
|| body.toLowerCase().includes(q);
|
|
66
66
|
}
|
|
67
|
+
function flattenAddressText(value) {
|
|
68
|
+
return flattenAddresses(value)
|
|
69
|
+
.map(a => (a.name ?? '') + ' ' + (a.address ?? ''))
|
|
70
|
+
.join(' ');
|
|
71
|
+
}
|
|
67
72
|
function toIso(date) {
|
|
68
73
|
return date instanceof Date ? date.toISOString() : '';
|
|
69
74
|
}
|
|
@@ -252,12 +257,13 @@ export class EmailPool {
|
|
|
252
257
|
const folderName = folder || cfg.inboxFolder;
|
|
253
258
|
return this.withImap(name, folderName, async (client) => {
|
|
254
259
|
// No nested OR and no TEXT search: several servers (QQ among them)
|
|
255
|
-
// silently answer those with empty or match-everything results.
|
|
256
|
-
//
|
|
260
|
+
// silently answer those with empty or match-everything results.
|
|
261
|
+
// subject/from/to/cc searches unioned client-side behave well everywhere.
|
|
257
262
|
const found = await Promise.all([
|
|
258
263
|
client.search({ subject: query }, { uid: true }),
|
|
259
264
|
client.search({ from: query }, { uid: true }),
|
|
260
265
|
client.search({ to: query }, { uid: true }),
|
|
266
|
+
client.search({ cc: query }, { uid: true }),
|
|
261
267
|
]);
|
|
262
268
|
const uids = [...new Set(found.flatMap(result => result === false ? [] : result))].sort((a, b) => a - b);
|
|
263
269
|
uids.reverse();
|
|
@@ -284,13 +290,19 @@ export class EmailPool {
|
|
|
284
290
|
if (out.length >= limit)
|
|
285
291
|
break;
|
|
286
292
|
const subject = message.envelope?.subject ?? '';
|
|
287
|
-
const
|
|
293
|
+
const recipientSearchText = [message.envelope?.from, message.envelope?.to, message.envelope?.cc]
|
|
294
|
+
.map(flattenAddressText).join(' ');
|
|
288
295
|
let body = '';
|
|
289
296
|
if (message.source !== undefined) {
|
|
290
|
-
|
|
291
|
-
|
|
297
|
+
try {
|
|
298
|
+
const parsed = await parseRawMessage(message.source, 4096);
|
|
299
|
+
body = parsed.text;
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// 单封邮件解析失败不应中断整批回退扫描,继续用 subject/from/to/cc 匹配。
|
|
303
|
+
}
|
|
292
304
|
}
|
|
293
|
-
if (messageMatchesQuery(subject,
|
|
305
|
+
if (messageMatchesQuery(subject, recipientSearchText, body, query)) {
|
|
294
306
|
out.push(listedFrom(message, message.size, structureHasAttachment(message.bodyStructure)));
|
|
295
307
|
}
|
|
296
308
|
}
|
|
@@ -300,7 +312,9 @@ export class EmailPool {
|
|
|
300
312
|
if (uids.length === 0)
|
|
301
313
|
return [];
|
|
302
314
|
const fetched = await client.fetchAll(uids, { uid: true, envelope: true, flags: true, size: true, bodyStructure: true }, { uid: true });
|
|
303
|
-
return fetched
|
|
315
|
+
return fetched
|
|
316
|
+
.map(message => listedFrom(message, message.size, structureHasAttachment(message.bodyStructure)))
|
|
317
|
+
.sort((a, b) => b.uid - a.uid);
|
|
304
318
|
}
|
|
305
319
|
async read(accountName, uid, folder) {
|
|
306
320
|
const name = this.resolveName(accountName);
|
|
@@ -396,7 +410,11 @@ export class EmailPool {
|
|
|
396
410
|
export async function validateAttachmentPaths(paths, maxBytes) {
|
|
397
411
|
const out = [];
|
|
398
412
|
let total = 0;
|
|
399
|
-
for (const
|
|
413
|
+
for (const rawPath of paths) {
|
|
414
|
+
if (typeof rawPath !== 'string' || rawPath.trim() === '') {
|
|
415
|
+
throw new MailError('附件路径无效:' + String(rawPath));
|
|
416
|
+
}
|
|
417
|
+
const path = rawPath.trim();
|
|
400
418
|
let info;
|
|
401
419
|
try {
|
|
402
420
|
info = await stat(path);
|
package/package.json
CHANGED
|
@@ -1,74 +1,71 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-email",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "IMAP/SMTP email tools for DeepSeek Harness: list, read, search and send mail, with QQ/163/126/Sina/Aliyun/Gmail/Outlook/iCloud presets.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "lib/index.js",
|
|
7
|
-
"types": "lib/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./lib/index.d.ts",
|
|
11
|
-
"default": "./lib/index.js"
|
|
12
|
-
},
|
|
13
|
-
"./client": "./lib/client.js",
|
|
14
|
-
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
15
|
-
"./package.json": "./package.json"
|
|
16
|
-
},
|
|
17
|
-
"files": [
|
|
18
|
-
"lib",
|
|
19
|
-
"cordis.patch.yml",
|
|
20
|
-
"README.md"
|
|
21
|
-
],
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
},
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"typescript": "^5.7.3"
|
|
73
|
-
}
|
|
74
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-email",
|
|
3
|
+
"version": "0.6.2",
|
|
4
|
+
"description": "IMAP/SMTP email tools for DeepSeek Harness: list, read, search and send mail, with QQ/163/126/Sina/Aliyun/Gmail/Outlook/iCloud presets.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./client": "./lib/client.js",
|
|
14
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"lib",
|
|
19
|
+
"cordis.patch.yml",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"keywords": [
|
|
23
|
+
"dsh-plugin",
|
|
24
|
+
"deepseek-harness",
|
|
25
|
+
"dsh",
|
|
26
|
+
"email",
|
|
27
|
+
"imap",
|
|
28
|
+
"smtp"
|
|
29
|
+
],
|
|
30
|
+
"author": "stardustlc",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/STARDUSTLC666/dsh-email"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/STARDUSTLC666/dsh-email/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/STARDUSTLC666/dsh-email#readme",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=20"
|
|
42
|
+
},
|
|
43
|
+
"dsh": {
|
|
44
|
+
"bundle": {
|
|
45
|
+
"patch": "./cordis.patch.yml"
|
|
46
|
+
},
|
|
47
|
+
"client": {
|
|
48
|
+
"inject": [
|
|
49
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
51
|
+
],
|
|
52
|
+
"platform": "web"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"imapflow": "^1.7.0",
|
|
57
|
+
"mailparser": "^3.9.15",
|
|
58
|
+
"nodemailer": "^9.0.5",
|
|
59
|
+
"schemastery": "^3.18.0",
|
|
60
|
+
"yaml": "^2.9.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/node": "^26.2.0",
|
|
64
|
+
"@types/nodemailer": "^8.0.1",
|
|
65
|
+
"typescript": "^5.7.3"
|
|
66
|
+
},
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "tsc",
|
|
69
|
+
"test": "pnpm run build && node --test \"test/*.test.mjs\""
|
|
70
|
+
}
|
|
71
|
+
}
|