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 CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  # dsh-email
4
4
 
5
+ > **给 agent 一个邮箱**:八个国内邮箱预设开箱即用,收发/搜索/附件下载全搞定。
6
+
7
+ ![npm version](https://img.shields.io/npm/v/dsh-email?label=npm&color=blue) ![npm downloads](https://img.shields.io/npm/dm/dsh-email) ![license](https://img.shields.io/npm/l/dsh-email) ![stars](https://img.shields.io/github/stars/STARDUSTLC666/dsh-email?style=social)
8
+
9
+
5
10
  ![dsh-email banner](https://raw.githubusercontent.com/STARDUSTLC666/dsh-email/main/assets/banner.png)
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, Array.isArray(args.attachments) ? args.attachments : 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({
@@ -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. Three
256
- // independent searches unioned client-side behave well everywhere.
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 fromText = flattenAddresses(message.envelope?.from).map(a => (a.name ?? '') + ' ' + (a.address ?? '')).join(' ');
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
- const parsed = await parseRawMessage(message.source, 4096);
291
- body = parsed.text;
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, fromText, body, query)) {
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.map(message => listedFrom(message, message.size, structureHasAttachment(message.bodyStructure)));
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 path of paths) {
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.0",
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
- "scripts": {
23
- "build": "tsc",
24
- "prepare": "tsc",
25
- "prepublishOnly": "pnpm run build",
26
- "test": "pnpm run build && node --test \"test/*.test.mjs\""
27
- },
28
- "keywords": [
29
- "dsh-plugin",
30
- "deepseek-harness",
31
- "dsh",
32
- "email",
33
- "imap",
34
- "smtp"
35
- ],
36
- "author": "stardustlc",
37
- "repository": {
38
- "type": "git",
39
- "url": "https://github.com/STARDUSTLC666/dsh-email"
40
- },
41
- "bugs": {
42
- "url": "https://github.com/STARDUSTLC666/dsh-email/issues"
43
- },
44
- "homepage": "https://github.com/STARDUSTLC666/dsh-email#readme",
45
- "license": "MIT",
46
- "packageManager": "pnpm@11.7.0",
47
- "engines": {
48
- "node": ">=20"
49
- },
50
- "dsh": {
51
- "bundle": {
52
- "patch": "./cordis.patch.yml"
53
- },
54
- "client": {
55
- "inject": [
56
- "@deepseek-ai/dsh-client-runtime",
57
- "@deepseek-ai/dsh-client-ui-settings"
58
- ],
59
- "platform": "web"
60
- }
61
- },
62
- "dependencies": {
63
- "imapflow": "^1.7.0",
64
- "mailparser": "^3.9.15",
65
- "nodemailer": "^9.0.5",
66
- "schemastery": "^3.18.0",
67
- "yaml": "^2.9.0"
68
- },
69
- "devDependencies": {
70
- "@types/node": "^26.2.0",
71
- "@types/nodemailer": "^8.0.1",
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
+ }