@zhin.js/adapter-email 1.0.0 → 1.1.0
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/CHANGELOG.md +383 -0
- package/README.md +50 -102
- package/adapters/email.js +25 -0
- package/adapters/email.ts +35 -0
- package/lib/client.d.ts +23 -0
- package/lib/client.js +29 -0
- package/lib/endpoint.d.ts +28 -0
- package/lib/endpoint.js +335 -0
- package/lib/index.d.ts +4 -10
- package/lib/index.js +4 -23
- package/lib/protocol.d.ts +138 -0
- package/lib/protocol.js +245 -0
- package/lib/transport.d.ts +32 -0
- package/lib/transport.js +27 -0
- package/package.json +45 -12
- package/plugin.js +8 -0
- package/schema.json +169 -0
- package/src/client.ts +40 -0
- package/src/endpoint.ts +364 -0
- package/src/index.ts +35 -28
- package/src/protocol.ts +384 -0
- package/src/transport.ts +65 -0
- package/lib/adapter.d.ts +0 -11
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -14
- package/lib/adapter.js.map +0 -1
- package/lib/bot.d.ts +0 -33
- package/lib/bot.d.ts.map +0 -1
- package/lib/bot.js +0 -368
- package/lib/bot.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/types.d.ts +0 -49
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -2
- package/lib/types.js.map +0 -1
- package/src/adapter.ts +0 -16
- package/src/bot.ts +0 -420
- package/src/types.ts +0 -52
- /package/{skills/email/SKILL.md → agent/skills/email.md} +0 -0
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email SMTP/IMAP helpers (no legacy Adapter/Endpoint / segment-mapper).
|
|
3
|
+
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Attachment } from 'mailparser';
|
|
7
|
+
import { htmlToPlainTextWithBlockBreaks, isMediaRef, type MediaRef } from '@zhin.js/core';
|
|
8
|
+
import type { Segment } from '@zhin.js/core/runtime';
|
|
9
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
10
|
+
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
11
|
+
|
|
12
|
+
const logger = getLogger('email');
|
|
13
|
+
|
|
14
|
+
export interface SmtpConfig {
|
|
15
|
+
readonly host: string;
|
|
16
|
+
readonly port: number;
|
|
17
|
+
readonly secure: boolean;
|
|
18
|
+
readonly auth: {
|
|
19
|
+
readonly user: string;
|
|
20
|
+
readonly pass: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ImapConfig {
|
|
25
|
+
readonly host: string;
|
|
26
|
+
readonly port: number;
|
|
27
|
+
readonly tls: boolean;
|
|
28
|
+
readonly user: string;
|
|
29
|
+
readonly password: string;
|
|
30
|
+
readonly checkInterval?: number;
|
|
31
|
+
/** IMAP 断线重连基础间隔(指数退避基数),毫秒。 */
|
|
32
|
+
readonly reconnectInterval?: number;
|
|
33
|
+
readonly mailbox?: string;
|
|
34
|
+
readonly markSeen?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface EmailAttachmentsConfig {
|
|
38
|
+
readonly enabled: boolean;
|
|
39
|
+
readonly downloadPath?: string;
|
|
40
|
+
readonly maxFileSize?: number;
|
|
41
|
+
readonly allowedTypes?: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
45
|
+
export interface EmailAdapterConfig {
|
|
46
|
+
readonly id?: string;
|
|
47
|
+
readonly smtp?: SmtpConfig;
|
|
48
|
+
readonly imap?: ImapConfig;
|
|
49
|
+
readonly attachments?: EmailAttachmentsConfig;
|
|
50
|
+
/** Transitional: legacy root `endpoints[]` with `context: email`. */
|
|
51
|
+
readonly endpoints?: ReadonlyArray<Partial<ResolvedEmailConfig> & {
|
|
52
|
+
readonly context?: string;
|
|
53
|
+
}>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ResolvedEmailConfig {
|
|
57
|
+
readonly context: 'email';
|
|
58
|
+
readonly id: string;
|
|
59
|
+
readonly smtp: SmtpConfig;
|
|
60
|
+
readonly imap: Required<Pick<ImapConfig, 'checkInterval' | 'reconnectInterval' | 'mailbox' | 'markSeen'>> & ImapConfig;
|
|
61
|
+
readonly attachments?: {
|
|
62
|
+
readonly enabled: boolean;
|
|
63
|
+
readonly downloadPath: string;
|
|
64
|
+
readonly maxFileSize: number;
|
|
65
|
+
readonly allowedTypes?: readonly string[];
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface EmailMessage {
|
|
70
|
+
readonly messageId: string;
|
|
71
|
+
readonly from: string;
|
|
72
|
+
readonly to: readonly string[];
|
|
73
|
+
readonly cc?: readonly string[];
|
|
74
|
+
readonly bcc?: readonly string[];
|
|
75
|
+
readonly subject: string;
|
|
76
|
+
readonly text?: string;
|
|
77
|
+
readonly html?: string;
|
|
78
|
+
readonly attachments: readonly Attachment[];
|
|
79
|
+
readonly date: Date;
|
|
80
|
+
readonly uid: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface EmailWireSegment {
|
|
84
|
+
readonly type: string;
|
|
85
|
+
readonly data?: Record<string, unknown>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function resolveEmailConfig(config: EmailAdapterConfig = {}): ResolvedEmailConfig {
|
|
89
|
+
const entry = config.endpoints?.find((item) => item.context === 'email');
|
|
90
|
+
const smtp = config.smtp ?? entry?.smtp;
|
|
91
|
+
const imap = config.imap ?? entry?.imap;
|
|
92
|
+
if (!smtp?.host || !smtp.auth?.user || !imap?.host || !imap.user) {
|
|
93
|
+
throw new TypeError(
|
|
94
|
+
'Email adapter requires smtp + imap config (plugins.<key>.smtp/imap or endpoints with context: email)',
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
98
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
99
|
+
|| process.env.EMAIL_BOT_NAME
|
|
100
|
+
|| 'email-bot';
|
|
101
|
+
const attachmentsSource = config.attachments ?? entry?.attachments;
|
|
102
|
+
const attachments = attachmentsSource?.enabled
|
|
103
|
+
? {
|
|
104
|
+
enabled: true as const,
|
|
105
|
+
downloadPath: attachmentsSource.downloadPath || './downloads/email',
|
|
106
|
+
maxFileSize: Math.max(attachmentsSource.maxFileSize || 10 * 1024 * 1024, 1),
|
|
107
|
+
allowedTypes: attachmentsSource.allowedTypes,
|
|
108
|
+
}
|
|
109
|
+
: undefined;
|
|
110
|
+
return {
|
|
111
|
+
context: 'email',
|
|
112
|
+
id,
|
|
113
|
+
smtp,
|
|
114
|
+
imap: {
|
|
115
|
+
...imap,
|
|
116
|
+
// 数值下限:0/负数会导致 setInterval(0) 风暴
|
|
117
|
+
checkInterval: Math.max(imap.checkInterval ?? 60_000, 1_000),
|
|
118
|
+
reconnectInterval: Math.max(imap.reconnectInterval ?? 5_000, 1_000),
|
|
119
|
+
mailbox: imap.mailbox ?? 'INBOX',
|
|
120
|
+
markSeen: imap.markSeen !== false,
|
|
121
|
+
},
|
|
122
|
+
attachments,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function htmlToText(html: string): string {
|
|
127
|
+
return htmlToPlainTextWithBlockBreaks(html);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function addressListText(addr: unknown): string[] {
|
|
131
|
+
if (!addr) return [];
|
|
132
|
+
if (Array.isArray(addr)) {
|
|
133
|
+
return addr.map((item) => addressText(item));
|
|
134
|
+
}
|
|
135
|
+
return [addressText(addr)];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function addressText(addr: unknown): string {
|
|
139
|
+
if (!addr || typeof addr !== 'object') return String(addr ?? '');
|
|
140
|
+
const record = addr as { text?: string; address?: string };
|
|
141
|
+
return record.text || record.address || String(addr);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function parseEmailMessage(
|
|
145
|
+
parsed: {
|
|
146
|
+
messageId?: string;
|
|
147
|
+
from?: unknown;
|
|
148
|
+
to?: unknown;
|
|
149
|
+
cc?: unknown;
|
|
150
|
+
bcc?: unknown;
|
|
151
|
+
subject?: string;
|
|
152
|
+
text?: string;
|
|
153
|
+
html?: string | false;
|
|
154
|
+
attachments?: Attachment[];
|
|
155
|
+
date?: Date;
|
|
156
|
+
},
|
|
157
|
+
uid: number,
|
|
158
|
+
): EmailMessage {
|
|
159
|
+
return {
|
|
160
|
+
messageId: parsed.messageId || '',
|
|
161
|
+
from: parsed.from ? addressListText(parsed.from)[0] || '' : '',
|
|
162
|
+
to: addressListText(parsed.to),
|
|
163
|
+
cc: addressListText(parsed.cc),
|
|
164
|
+
bcc: addressListText(parsed.bcc),
|
|
165
|
+
subject: parsed.subject || '',
|
|
166
|
+
text: parsed.text || '',
|
|
167
|
+
html: parsed.html ? String(parsed.html) : '',
|
|
168
|
+
attachments: parsed.attachments || [],
|
|
169
|
+
date: parsed.date || new Date(),
|
|
170
|
+
uid,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Build inbound text for OutboundMessageService.receive (gateway owns reply routing). */
|
|
175
|
+
export function formatInboundContent(email: EmailMessage): string {
|
|
176
|
+
const parts: string[] = [];
|
|
177
|
+
if (email.subject) parts.push(`Subject: ${email.subject}`, '');
|
|
178
|
+
if (email.text) {
|
|
179
|
+
parts.push(email.text);
|
|
180
|
+
} else if (email.html) {
|
|
181
|
+
const fromHtml = htmlToText(email.html);
|
|
182
|
+
if (fromHtml) parts.push(fromHtml);
|
|
183
|
+
}
|
|
184
|
+
for (const attachment of email.attachments) {
|
|
185
|
+
const kind = attachment.contentType?.startsWith('image/') ? 'image' : 'file';
|
|
186
|
+
const name = attachment.filename || 'attachment';
|
|
187
|
+
parts.push(`[${kind}: ${name}]`);
|
|
188
|
+
}
|
|
189
|
+
const text = parts.join('\n').trim();
|
|
190
|
+
return text || '(Empty email)';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** 已落盘的入站附件(attachments.enabled 下载结果)。 */
|
|
194
|
+
export interface SavedEmailAttachment {
|
|
195
|
+
readonly filename: string;
|
|
196
|
+
readonly path: string;
|
|
197
|
+
readonly contentType?: string;
|
|
198
|
+
readonly size?: number;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 入站邮件 → canonical Segment[](与 formatInboundContent 纯文本视图同源双轨)。
|
|
203
|
+
* 已落盘附件映射为 image/file 段,MediaRef kind=path 指向下载路径;
|
|
204
|
+
* 未下载的附件(disabled / 被过滤)只保留 content 里的占位文本。
|
|
205
|
+
*/
|
|
206
|
+
export function formatInboundSegments(
|
|
207
|
+
email: EmailMessage,
|
|
208
|
+
savedAttachments: readonly SavedEmailAttachment[] = [],
|
|
209
|
+
): Segment[] {
|
|
210
|
+
const out: Segment[] = [];
|
|
211
|
+
const content = formatInboundContent(email);
|
|
212
|
+
if (content) out.push({ type: 'text', data: { text: content } });
|
|
213
|
+
for (const saved of savedAttachments) {
|
|
214
|
+
const type = saved.contentType?.startsWith('image/') ? 'image' : 'file';
|
|
215
|
+
out.push({
|
|
216
|
+
type,
|
|
217
|
+
data: {
|
|
218
|
+
media: {
|
|
219
|
+
kind: 'path',
|
|
220
|
+
value: saved.path,
|
|
221
|
+
...(saved.contentType ? { mime_type: saved.contentType } : {}),
|
|
222
|
+
},
|
|
223
|
+
name: saved.filename,
|
|
224
|
+
...(type === 'image' ? { alt: saved.filename } : {}),
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* 入站归一化 → ConversationRef:Email 无群/频道概念,所有入站邮件都是
|
|
233
|
+
* 与发件人地址的 private 会话(id = 发件人地址)。
|
|
234
|
+
*/
|
|
235
|
+
export function emailInboundConversation(endpointKey: string, email: EmailMessage): ConversationRef {
|
|
236
|
+
return {
|
|
237
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
238
|
+
kind: 'private',
|
|
239
|
+
id: email.from,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function senderDisplayName(from: string): string {
|
|
244
|
+
const name = from.split('<')[0]?.trim();
|
|
245
|
+
return name || from;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* nodemailer 附件的最小形状:url/path 走 `path`(URL 由 nodemailer 拉流、
|
|
250
|
+
* 本地路径读盘),base64 走 `content` + `encoding: 'base64'` 直发。
|
|
251
|
+
*/
|
|
252
|
+
export type EmailOutboundAttachment =
|
|
253
|
+
| { filename: string; path: string }
|
|
254
|
+
| { filename: string; content: string; encoding: 'base64' };
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* image/audio/video/file 段 → nodemailer 附件(canonical MediaRef 唯一来源):
|
|
258
|
+
* - kind=url / path → attachment.path;
|
|
259
|
+
* - kind=base64 → attachment.content(data: URL 前缀剥离);
|
|
260
|
+
* - kind=file(平台不透明引用)邮件无对应概念,丢弃留痕;
|
|
261
|
+
* - 缺 media 同样 warn + 丢弃。
|
|
262
|
+
*/
|
|
263
|
+
function mediaSegmentToAttachment(
|
|
264
|
+
type: string,
|
|
265
|
+
data: Record<string, unknown>,
|
|
266
|
+
): EmailOutboundAttachment | null {
|
|
267
|
+
const media = data.media;
|
|
268
|
+
if (!isMediaRef(media)) {
|
|
269
|
+
logger.warn(formatCompact({
|
|
270
|
+
op: 'email_outbound_media_dropped',
|
|
271
|
+
type,
|
|
272
|
+
reason: 'missing_media_ref',
|
|
273
|
+
}));
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
if (media.kind === 'file') {
|
|
277
|
+
logger.warn(formatCompact({
|
|
278
|
+
op: 'email_outbound_media_dropped',
|
|
279
|
+
type,
|
|
280
|
+
reason: 'unsupported_kind',
|
|
281
|
+
kind: media.kind,
|
|
282
|
+
}));
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
const filename = attachmentFileName(type, data, media);
|
|
286
|
+
if (media.kind === 'base64') {
|
|
287
|
+
const value = media.value.startsWith('data:')
|
|
288
|
+
? media.value.slice(media.value.indexOf(',') + 1)
|
|
289
|
+
: media.value;
|
|
290
|
+
return { filename, content: value, encoding: 'base64' };
|
|
291
|
+
}
|
|
292
|
+
return { filename, path: media.value };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function attachmentFileName(
|
|
296
|
+
type: string,
|
|
297
|
+
data: Record<string, unknown>,
|
|
298
|
+
media: MediaRef,
|
|
299
|
+
): string {
|
|
300
|
+
if (media.file_name) return media.file_name;
|
|
301
|
+
if (typeof data.name === 'string' && data.name) return data.name;
|
|
302
|
+
if (typeof data.alt === 'string' && data.alt) return data.alt;
|
|
303
|
+
return type === 'image' ? 'image.png' : 'file';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Wire-encode an already-rendered outbound payload into nodemailer options.
|
|
308
|
+
* Segment canonicalization is intentionally not done here.
|
|
309
|
+
*/
|
|
310
|
+
export function formatOutboundMail(
|
|
311
|
+
payload: unknown,
|
|
312
|
+
options: { readonly from: string; readonly to: string; readonly subject?: string },
|
|
313
|
+
): {
|
|
314
|
+
from: string;
|
|
315
|
+
to: string;
|
|
316
|
+
subject: string;
|
|
317
|
+
text?: string;
|
|
318
|
+
html?: string;
|
|
319
|
+
attachments?: EmailOutboundAttachment[];
|
|
320
|
+
} {
|
|
321
|
+
const mail = {
|
|
322
|
+
from: options.from,
|
|
323
|
+
to: options.to,
|
|
324
|
+
subject: options.subject ?? 'Message from Bot',
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
if (typeof payload === 'string') {
|
|
328
|
+
return { ...mail, text: payload };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const segments: Array<string | EmailWireSegment> = Array.isArray(payload)
|
|
332
|
+
? payload as Array<string | EmailWireSegment>
|
|
333
|
+
: payload && typeof payload === 'object' && 'type' in (payload as object)
|
|
334
|
+
? [payload as EmailWireSegment]
|
|
335
|
+
: [];
|
|
336
|
+
|
|
337
|
+
if (segments.length === 0) {
|
|
338
|
+
return {
|
|
339
|
+
...mail,
|
|
340
|
+
text: payload == null ? '' : typeof payload === 'object'
|
|
341
|
+
? JSON.stringify(payload)
|
|
342
|
+
: String(payload),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const textParts: string[] = [];
|
|
347
|
+
const htmlParts: string[] = [];
|
|
348
|
+
const attachments: EmailOutboundAttachment[] = [];
|
|
349
|
+
|
|
350
|
+
for (const item of segments) {
|
|
351
|
+
if (typeof item === 'string') {
|
|
352
|
+
textParts.push(item);
|
|
353
|
+
htmlParts.push(item.replace(/\n/g, '<br>'));
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
const data = item.data ?? {};
|
|
357
|
+
switch (item.type) {
|
|
358
|
+
case 'text': {
|
|
359
|
+
const textContent = String(data.text ?? data.content ?? '');
|
|
360
|
+
textParts.push(textContent);
|
|
361
|
+
htmlParts.push(textContent.replace(/\n/g, '<br>'));
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
case 'image':
|
|
365
|
+
case 'audio':
|
|
366
|
+
case 'video':
|
|
367
|
+
case 'file': {
|
|
368
|
+
const attachment = mediaSegmentToAttachment(item.type, data);
|
|
369
|
+
if (attachment) attachments.push(attachment);
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
default:
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
...mail,
|
|
379
|
+
...(textParts.length > 0
|
|
380
|
+
? { text: textParts.join('\n'), html: htmlParts.join('<br>') }
|
|
381
|
+
: {}),
|
|
382
|
+
...(attachments.length > 0 ? { attachments } : {}),
|
|
383
|
+
};
|
|
384
|
+
}
|
package/src/transport.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email SMTP/IMAP transport factories (nodemailer + imap).
|
|
3
|
+
*/
|
|
4
|
+
import nodemailer from 'nodemailer';
|
|
5
|
+
import Imap from 'imap';
|
|
6
|
+
import type { ResolvedEmailConfig } from './protocol.js';
|
|
7
|
+
|
|
8
|
+
export interface EmailSmtpTransport {
|
|
9
|
+
verify(): Promise<void>;
|
|
10
|
+
sendMail(options: unknown): Promise<{ messageId?: string }>;
|
|
11
|
+
close(): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface EmailImapTransport {
|
|
15
|
+
once(event: 'ready' | 'error', listener: (...args: unknown[]) => void): void;
|
|
16
|
+
on(event: string, listener: (...args: unknown[]) => void): void;
|
|
17
|
+
connect(): void;
|
|
18
|
+
end(): void;
|
|
19
|
+
openBox(
|
|
20
|
+
mailbox: string,
|
|
21
|
+
openReadWrite: boolean,
|
|
22
|
+
callback: (error: Error | null, box?: unknown) => void,
|
|
23
|
+
): void;
|
|
24
|
+
search(
|
|
25
|
+
criteria: string[],
|
|
26
|
+
callback: (error: Error | null, results: number[]) => void,
|
|
27
|
+
): void;
|
|
28
|
+
fetch(
|
|
29
|
+
results: number[],
|
|
30
|
+
options: { bodies: string; markSeen?: boolean },
|
|
31
|
+
): {
|
|
32
|
+
on(event: 'message', listener: (msg: EmailImapFetchMessage, seqno: number) => void): void;
|
|
33
|
+
once(event: 'error' | 'end', listener: (error?: Error) => void): void;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface EmailImapFetchMessage {
|
|
38
|
+
on(event: 'body', listener: (stream: NodeJS.ReadableStream) => void): void;
|
|
39
|
+
once(event: 'attributes', listener: (attrs: { uid?: number }) => void): void;
|
|
40
|
+
once(event: 'end', listener: () => void): void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function defaultCreateSmtp(config: ResolvedEmailConfig['smtp']): EmailSmtpTransport {
|
|
44
|
+
const transporter = nodemailer.createTransport({
|
|
45
|
+
host: config.host,
|
|
46
|
+
port: config.port,
|
|
47
|
+
secure: config.secure,
|
|
48
|
+
auth: { user: config.auth.user, pass: config.auth.pass },
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
verify: () => transporter.verify().then(() => undefined),
|
|
52
|
+
sendMail: (options) => transporter.sendMail(options as nodemailer.SendMailOptions),
|
|
53
|
+
close: () => transporter.close(),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function defaultCreateImap(config: ResolvedEmailConfig['imap']): EmailImapTransport {
|
|
58
|
+
return new Imap({
|
|
59
|
+
user: config.user,
|
|
60
|
+
password: config.password,
|
|
61
|
+
host: config.host,
|
|
62
|
+
port: config.port,
|
|
63
|
+
tls: config.tls,
|
|
64
|
+
}) as unknown as EmailImapTransport;
|
|
65
|
+
}
|
package/lib/adapter.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Email 适配器
|
|
3
|
-
*/
|
|
4
|
-
import { Adapter, Plugin } from "zhin.js";
|
|
5
|
-
import { EmailBot } from "./bot.js";
|
|
6
|
-
import type { EmailBotConfig } from "./types.js";
|
|
7
|
-
export declare class EmailAdapter extends Adapter<EmailBot> {
|
|
8
|
-
constructor(plugin: Plugin);
|
|
9
|
-
createBot(config: EmailBotConfig): EmailBot;
|
|
10
|
-
}
|
|
11
|
-
//# sourceMappingURL=adapter.d.ts.map
|
package/lib/adapter.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,qBAAa,YAAa,SAAQ,OAAO,CAAC,QAAQ,CAAC;gBACnC,MAAM,EAAE,MAAM;IAI1B,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,QAAQ;CAG9C"}
|
package/lib/adapter.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Email 适配器
|
|
3
|
-
*/
|
|
4
|
-
import { Adapter } from "zhin.js";
|
|
5
|
-
import { EmailBot } from "./bot.js";
|
|
6
|
-
export class EmailAdapter extends Adapter {
|
|
7
|
-
constructor(plugin) {
|
|
8
|
-
super(plugin, 'email', []);
|
|
9
|
-
}
|
|
10
|
-
createBot(config) {
|
|
11
|
-
return new EmailBot(this, config);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
//# sourceMappingURL=adapter.js.map
|
package/lib/adapter.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,OAAO,EAAU,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGpC,MAAM,OAAO,YAAa,SAAQ,OAAiB;IAC/C,YAAY,MAAc;QACtB,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED,SAAS,CAAC,MAAsB;QAC5B,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;CACJ"}
|
package/lib/bot.d.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { Bot, Message, MessageSegment, SendOptions } from 'zhin.js';
|
|
2
|
-
import { EventEmitter } from "events";
|
|
3
|
-
import type { EmailBotConfig, EmailMessage } from "./types.js";
|
|
4
|
-
import type { EmailAdapter } from "./adapter.js";
|
|
5
|
-
export declare class EmailBot extends EventEmitter implements Bot<EmailBotConfig, EmailMessage> {
|
|
6
|
-
adapter: EmailAdapter;
|
|
7
|
-
$config: EmailBotConfig;
|
|
8
|
-
$connected: boolean;
|
|
9
|
-
private smtpTransporter;
|
|
10
|
-
private imapConnection;
|
|
11
|
-
private checkTimer;
|
|
12
|
-
get logger(): import("zhin.js").Logger;
|
|
13
|
-
get $id(): string;
|
|
14
|
-
constructor(adapter: EmailAdapter, config: EmailBotConfig);
|
|
15
|
-
$connect(): Promise<void>;
|
|
16
|
-
$disconnect(): Promise<void>;
|
|
17
|
-
private setupImapListeners;
|
|
18
|
-
private startEmailCheck;
|
|
19
|
-
private checkForNewEmails;
|
|
20
|
-
private handleImapMessage;
|
|
21
|
-
private parseEmailMessage;
|
|
22
|
-
$formatMessage(emailMsg: EmailMessage): Message<EmailMessage>;
|
|
23
|
-
static parseEmailContent(email: EmailMessage): MessageSegment[];
|
|
24
|
-
$sendMessage(options: SendOptions): Promise<string>;
|
|
25
|
-
$recallMessage(id: string): Promise<void>;
|
|
26
|
-
private formatSendContent;
|
|
27
|
-
private downloadAttachment;
|
|
28
|
-
/**
|
|
29
|
-
* HTML → 纯文本转换,处理常见标签和实体
|
|
30
|
-
*/
|
|
31
|
-
static htmlToText(html: string): string;
|
|
32
|
-
}
|
|
33
|
-
//# sourceMappingURL=bot.d.ts.map
|
package/lib/bot.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"AAMA,OAAO,EAAiB,GAAG,EAAE,OAAO,EAAE,cAAc,EAAwB,WAAW,EAAkC,MAAM,SAAS,CAAC;AACzI,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,qBAAa,QAAS,SAAQ,YAAa,YAAW,GAAG,CAAC,cAAc,EAAE,YAAY,CAAC;IAehE,OAAO,EAAE,YAAY;IAdxC,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,OAAO,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAuC;IAC9D,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,UAAU,CAA+B;IAEjD,IAAI,MAAM,6BAEX;IAED,IAAI,GAAG,WAEJ;gBAEkB,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc;IAe1D,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA6CzB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBlC,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,eAAe;YAWT,iBAAiB;IAoC/B,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,iBAAiB;IAwBzB,cAAc,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAwC7D,MAAM,CAAC,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,cAAc,EAAE;IAyCzD,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAgBnD,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAIjC,iBAAiB;YA+DjB,kBAAkB;IAqBhC;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAG1C"}
|