@zhin.js/adapter-email 0.1.39 → 0.1.40
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 +8 -0
- package/package.json +10 -6
- package/src/adapter.ts +16 -0
- package/src/bot.ts +422 -0
- package/src/index.ts +31 -0
- package/src/types.ts +52 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-email",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.40",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Zhin.js adapter for Email (SMTP/IMAP)",
|
|
6
|
-
"main": "lib/index.js",
|
|
7
|
-
"types": "lib/index.d.ts",
|
|
6
|
+
"main": "./lib/index.js",
|
|
7
|
+
"types": "./lib/index.d.ts",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@types/imap": "^0.8.40",
|
|
10
10
|
"@types/nodemailer": "^6.4.14",
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"nodemailer": "^7.0.11"
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"zhin.js": "1.0.
|
|
16
|
+
"zhin.js": "1.0.52"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/mailparser": "^3.4.6",
|
|
20
20
|
"@types/node": "^20.10.6",
|
|
21
21
|
"typescript": "^5.3.3",
|
|
22
|
-
"zhin.js": "1.0.
|
|
22
|
+
"zhin.js": "1.0.52"
|
|
23
23
|
},
|
|
24
24
|
"keywords": [
|
|
25
25
|
"zhin",
|
|
@@ -42,9 +42,13 @@
|
|
|
42
42
|
"directory": "plugins/adapters/email"
|
|
43
43
|
},
|
|
44
44
|
"files": [
|
|
45
|
+
"src",
|
|
45
46
|
"lib",
|
|
46
|
-
"
|
|
47
|
+
"client",
|
|
48
|
+
"dist",
|
|
49
|
+
"skills",
|
|
47
50
|
"README.md",
|
|
51
|
+
"node",
|
|
48
52
|
"CHANGELOG.md"
|
|
49
53
|
],
|
|
50
54
|
"publishConfig": {
|
package/src/adapter.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
|
|
8
|
+
export class EmailAdapter extends Adapter<EmailBot> {
|
|
9
|
+
constructor(plugin: Plugin) {
|
|
10
|
+
super(plugin, 'email', []);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
createBot(config: EmailBotConfig): EmailBot {
|
|
14
|
+
return new EmailBot(this, config);
|
|
15
|
+
}
|
|
16
|
+
}
|
package/src/bot.ts
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Bot 实现
|
|
3
|
+
*/
|
|
4
|
+
import nodemailer from "nodemailer";
|
|
5
|
+
import Imap from "imap";
|
|
6
|
+
import { simpleParser, type ParsedMail, type Attachment } from "mailparser";
|
|
7
|
+
import { Bot, Message, SendOptions, SendContent, MessageSegment, segment } from "zhin.js";
|
|
8
|
+
import { EventEmitter } from "events";
|
|
9
|
+
import { createWriteStream, promises as fs } from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import type { EmailBotConfig, EmailMessage } from "./types.js";
|
|
12
|
+
import type { EmailAdapter } from "./adapter.js";
|
|
13
|
+
|
|
14
|
+
export class EmailBot extends EventEmitter implements Bot<EmailBotConfig, EmailMessage> {
|
|
15
|
+
$config: EmailBotConfig;
|
|
16
|
+
$connected: boolean = false;
|
|
17
|
+
private smtpTransporter: nodemailer.Transporter | null = null;
|
|
18
|
+
private imapConnection: Imap | null = null;
|
|
19
|
+
private checkTimer: NodeJS.Timeout | null = null;
|
|
20
|
+
|
|
21
|
+
get logger() {
|
|
22
|
+
return this.adapter.plugin.logger;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get $id() {
|
|
26
|
+
return this.$config.name;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
constructor(public adapter: EmailAdapter, config: EmailBotConfig) {
|
|
30
|
+
super();
|
|
31
|
+
this.$config = config;
|
|
32
|
+
|
|
33
|
+
// 设置默认值
|
|
34
|
+
this.$config.imap.checkInterval = this.$config.imap.checkInterval || 60000; // 1分钟
|
|
35
|
+
this.$config.imap.mailbox = this.$config.imap.mailbox || 'INBOX';
|
|
36
|
+
this.$config.imap.markSeen = this.$config.imap.markSeen !== false;
|
|
37
|
+
|
|
38
|
+
if (this.$config.attachments?.enabled) {
|
|
39
|
+
this.$config.attachments.downloadPath = this.$config.attachments.downloadPath || './downloads/email';
|
|
40
|
+
this.$config.attachments.maxFileSize = this.$config.attachments.maxFileSize || 10 * 1024 * 1024; // 10MB
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async $connect(): Promise<void> {
|
|
45
|
+
try {
|
|
46
|
+
// 初始化 SMTP 传输器
|
|
47
|
+
this.smtpTransporter = nodemailer.createTransport({
|
|
48
|
+
host: this.$config.smtp.host,
|
|
49
|
+
port: this.$config.smtp.port,
|
|
50
|
+
secure: this.$config.smtp.secure,
|
|
51
|
+
auth: this.$config.smtp.auth
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// 验证 SMTP 连接
|
|
55
|
+
await this.smtpTransporter!.verify();
|
|
56
|
+
this.logger.info(`SMTP connection verified for ${this.$config.smtp.auth.user}`);
|
|
57
|
+
|
|
58
|
+
// 初始化 IMAP 连接
|
|
59
|
+
this.imapConnection = new Imap({
|
|
60
|
+
user: this.$config.imap.user,
|
|
61
|
+
password: this.$config.imap.password,
|
|
62
|
+
host: this.$config.imap.host,
|
|
63
|
+
port: this.$config.imap.port,
|
|
64
|
+
tls: this.$config.imap.tls
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// 设置 IMAP 事件监听
|
|
68
|
+
this.setupImapListeners();
|
|
69
|
+
|
|
70
|
+
// 连接 IMAP
|
|
71
|
+
await new Promise<void>((resolve, reject) => {
|
|
72
|
+
this.imapConnection!.once('ready', resolve);
|
|
73
|
+
this.imapConnection!.once('error', reject);
|
|
74
|
+
this.imapConnection!.connect();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
this.logger.info(`IMAP connection established for ${this.$config.imap.user}`);
|
|
78
|
+
|
|
79
|
+
// 开始检查邮件
|
|
80
|
+
this.startEmailCheck();
|
|
81
|
+
this.$connected = true;
|
|
82
|
+
|
|
83
|
+
} catch (error) {
|
|
84
|
+
this.logger.error('Failed to connect email services:', error);
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async $disconnect(): Promise<void> {
|
|
90
|
+
this.$connected = false;
|
|
91
|
+
|
|
92
|
+
// 停止定时检查
|
|
93
|
+
if (this.checkTimer) {
|
|
94
|
+
clearInterval(this.checkTimer);
|
|
95
|
+
this.checkTimer = null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 关闭 IMAP 连接
|
|
99
|
+
if (this.imapConnection) {
|
|
100
|
+
this.imapConnection.end();
|
|
101
|
+
this.imapConnection = null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 关闭 SMTP 连接
|
|
105
|
+
if (this.smtpTransporter) {
|
|
106
|
+
this.smtpTransporter.close();
|
|
107
|
+
this.smtpTransporter = null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.logger.info('Email bot disconnected');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private setupImapListeners(): void {
|
|
114
|
+
if (!this.imapConnection) return;
|
|
115
|
+
|
|
116
|
+
this.imapConnection.on('mail', (numNewMsgs: number) => {
|
|
117
|
+
this.logger.debug(`Received ${numNewMsgs} new emails`);
|
|
118
|
+
this.checkForNewEmails();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
this.imapConnection.on('error', (error: any) => {
|
|
122
|
+
this.logger.error('IMAP error:', error);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
this.imapConnection.on('end', () => {
|
|
126
|
+
this.logger.info('IMAP connection ended');
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private startEmailCheck(): void {
|
|
131
|
+
if (this.checkTimer) return;
|
|
132
|
+
|
|
133
|
+
this.checkTimer = setInterval(() => {
|
|
134
|
+
this.checkForNewEmails();
|
|
135
|
+
}, this.$config.imap.checkInterval!);
|
|
136
|
+
|
|
137
|
+
// 立即检查一次
|
|
138
|
+
this.checkForNewEmails();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private async checkForNewEmails(): Promise<void> {
|
|
142
|
+
if (!this.imapConnection || !this.$connected) return;
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await new Promise<void>((resolve, reject) => {
|
|
146
|
+
this.imapConnection!.openBox(this.$config.imap.mailbox!, false, (error, box) => {
|
|
147
|
+
if (error) return reject(error);
|
|
148
|
+
|
|
149
|
+
// 搜索未读邮件
|
|
150
|
+
this.imapConnection!.search(['UNSEEN'], (error, results) => {
|
|
151
|
+
if (error) return reject(error);
|
|
152
|
+
|
|
153
|
+
if (results.length === 0) {
|
|
154
|
+
return resolve();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 获取邮件
|
|
158
|
+
const fetch = this.imapConnection!.fetch(results, {
|
|
159
|
+
bodies: '',
|
|
160
|
+
markSeen: this.$config.imap.markSeen
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
fetch.on('message', (msg, seqno) => {
|
|
164
|
+
this.handleImapMessage(msg, seqno);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
fetch.once('error', reject);
|
|
168
|
+
fetch.once('end', resolve);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
} catch (error) {
|
|
173
|
+
this.logger.error('Error checking for new emails:', error);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private handleImapMessage(msg: any, seqno: number): void {
|
|
178
|
+
let body = '';
|
|
179
|
+
let uid = 0;
|
|
180
|
+
|
|
181
|
+
msg.on('body', (stream: any) => {
|
|
182
|
+
stream.on('data', (chunk: any) => {
|
|
183
|
+
body += chunk.toString('utf8');
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
msg.once('attributes', (attrs: any) => {
|
|
188
|
+
uid = attrs.uid;
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
msg.once('end', async () => {
|
|
192
|
+
try {
|
|
193
|
+
const parsed = await simpleParser(body);
|
|
194
|
+
const emailMessage = this.parseEmailMessage(parsed, uid);
|
|
195
|
+
const formattedMessage = this.$formatMessage(emailMessage);
|
|
196
|
+
this.adapter.emit('message.receive', formattedMessage);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
this.logger.error('Error parsing email:', error);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private parseEmailMessage(parsed: ParsedMail, uid: number): EmailMessage {
|
|
204
|
+
const getAddressText = (addr: any): string[] => {
|
|
205
|
+
if (!addr) return [];
|
|
206
|
+
if (Array.isArray(addr)) {
|
|
207
|
+
return addr.map((a: any) => a.text || a.address || a.toString());
|
|
208
|
+
}
|
|
209
|
+
return [addr.text || addr.address || addr.toString()];
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
messageId: parsed.messageId || '',
|
|
214
|
+
from: parsed.from ? getAddressText(parsed.from)[0] || '' : '',
|
|
215
|
+
to: getAddressText(parsed.to),
|
|
216
|
+
cc: getAddressText(parsed.cc),
|
|
217
|
+
bcc: getAddressText(parsed.bcc),
|
|
218
|
+
subject: parsed.subject || '',
|
|
219
|
+
text: parsed.text || '',
|
|
220
|
+
html: parsed.html ? parsed.html.toString() : '',
|
|
221
|
+
attachments: parsed.attachments || [],
|
|
222
|
+
date: parsed.date || new Date(),
|
|
223
|
+
uid
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
$formatMessage(emailMsg: EmailMessage): Message<EmailMessage> {
|
|
228
|
+
// 确定频道类型和ID
|
|
229
|
+
const channelType = 'private';
|
|
230
|
+
const channelId = emailMsg.from;
|
|
231
|
+
|
|
232
|
+
// 解析邮件内容
|
|
233
|
+
const content = EmailBot.parseEmailContent(emailMsg);
|
|
234
|
+
|
|
235
|
+
const result = Message.from(emailMsg, {
|
|
236
|
+
$id: emailMsg.messageId,
|
|
237
|
+
$adapter: 'email',
|
|
238
|
+
$bot: this.$config.name,
|
|
239
|
+
$sender: {
|
|
240
|
+
id: emailMsg.from,
|
|
241
|
+
name: emailMsg.from.split('<')[0].trim() || emailMsg.from
|
|
242
|
+
},
|
|
243
|
+
$channel: {
|
|
244
|
+
id: channelId,
|
|
245
|
+
type: channelType as any
|
|
246
|
+
},
|
|
247
|
+
$raw: JSON.stringify(emailMsg),
|
|
248
|
+
$timestamp: emailMsg.date.getTime(),
|
|
249
|
+
$content: content,
|
|
250
|
+
$recall: async () => {
|
|
251
|
+
// 邮件适配器暂时不支持撤回消息
|
|
252
|
+
},
|
|
253
|
+
$reply: async (content: SendContent): Promise<string> => {
|
|
254
|
+
return await this.adapter.sendMessage({
|
|
255
|
+
context: this.$config.context,
|
|
256
|
+
bot: this.$config.name,
|
|
257
|
+
id: emailMsg.from,
|
|
258
|
+
type: 'private',
|
|
259
|
+
content
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
static parseEmailContent(email: EmailMessage): MessageSegment[] {
|
|
268
|
+
const segments: MessageSegment[] = [];
|
|
269
|
+
|
|
270
|
+
// 添加主题(如果有且不为空)
|
|
271
|
+
if (email.subject) {
|
|
272
|
+
segments.push(segment.text(`Subject: ${email.subject}\n\n`));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 添加文本内容
|
|
276
|
+
if (email.text) {
|
|
277
|
+
segments.push(segment.text(email.text));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 如果没有纯文本但有HTML,尝试转换
|
|
281
|
+
if (!email.text && email.html) {
|
|
282
|
+
// 简单的HTML到文本转换
|
|
283
|
+
const textFromHtml = email.html
|
|
284
|
+
.replace(/<[^>]*>/g, '') // 移除HTML标签
|
|
285
|
+
.replace(/ /g, ' ')
|
|
286
|
+
.replace(/&/g, '&')
|
|
287
|
+
.replace(/</g, '<')
|
|
288
|
+
.replace(/>/g, '>')
|
|
289
|
+
.replace(/"/g, '"')
|
|
290
|
+
.trim();
|
|
291
|
+
|
|
292
|
+
if (textFromHtml) {
|
|
293
|
+
segments.push(segment.text(textFromHtml));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// 处理附件
|
|
298
|
+
for (const attachment of email.attachments) {
|
|
299
|
+
if (attachment.contentType?.startsWith('image/')) {
|
|
300
|
+
segments.push(segment('image', {
|
|
301
|
+
filename: attachment.filename,
|
|
302
|
+
contentType: attachment.contentType,
|
|
303
|
+
size: attachment.size
|
|
304
|
+
}));
|
|
305
|
+
} else {
|
|
306
|
+
segments.push(segment('file', {
|
|
307
|
+
filename: attachment.filename,
|
|
308
|
+
contentType: attachment.contentType,
|
|
309
|
+
size: attachment.size
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return segments.length > 0 ? segments : [segment.text('(Empty email)')];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async $sendMessage(options: SendOptions): Promise<string> {
|
|
318
|
+
if (!this.smtpTransporter) {
|
|
319
|
+
throw new Error('SMTP transporter not initialized');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const mailOptions = await this.formatSendContent(options);
|
|
324
|
+
const info = await this.smtpTransporter.sendMail(mailOptions);
|
|
325
|
+
this.logger.debug('Email sent:', info.messageId);
|
|
326
|
+
} catch (error) {
|
|
327
|
+
this.logger.error('Failed to send email:', error);
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
return ''
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async $recallMessage(id: string): Promise<void> {
|
|
334
|
+
// 邮件适配器暂时不支持撤回消息
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private async formatSendContent(options: SendOptions): Promise<nodemailer.SendMailOptions> {
|
|
338
|
+
const mailOptions: nodemailer.SendMailOptions = {
|
|
339
|
+
from: this.$config.smtp.auth.user,
|
|
340
|
+
to: options.id,
|
|
341
|
+
subject: 'Message from Bot'
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
if (typeof options.content === 'string') {
|
|
345
|
+
mailOptions.text = options.content;
|
|
346
|
+
} else if (Array.isArray(options.content)) {
|
|
347
|
+
const textParts: string[] = [];
|
|
348
|
+
const htmlParts: string[] = [];
|
|
349
|
+
const attachments: any[] = [];
|
|
350
|
+
|
|
351
|
+
for (const item of options.content) {
|
|
352
|
+
if (typeof item === 'string') {
|
|
353
|
+
textParts.push(item);
|
|
354
|
+
htmlParts.push(item.replace(/\n/g, '<br>'));
|
|
355
|
+
} else {
|
|
356
|
+
const segment = item as MessageSegment;
|
|
357
|
+
switch (segment.type) {
|
|
358
|
+
case 'text':
|
|
359
|
+
const textContent = segment.data.text || segment.data.content || '';
|
|
360
|
+
textParts.push(textContent);
|
|
361
|
+
htmlParts.push(textContent.replace(/\n/g, '<br>'));
|
|
362
|
+
break;
|
|
363
|
+
case 'image':
|
|
364
|
+
if (segment.data.url) {
|
|
365
|
+
attachments.push({
|
|
366
|
+
filename: segment.data.filename || 'image.png',
|
|
367
|
+
path: segment.data.url
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
break;
|
|
371
|
+
case 'file':
|
|
372
|
+
if (segment.data.url) {
|
|
373
|
+
attachments.push({
|
|
374
|
+
filename: segment.data.filename || 'file',
|
|
375
|
+
path: segment.data.url
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (textParts.length > 0) {
|
|
384
|
+
mailOptions.text = textParts.join('\n');
|
|
385
|
+
mailOptions.html = htmlParts.join('<br>');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (attachments.length > 0) {
|
|
389
|
+
mailOptions.attachments = attachments;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// 如果有回复对象,可以在这里处理
|
|
394
|
+
// 邮件适配器暂时不支持回复对象
|
|
395
|
+
|
|
396
|
+
return mailOptions;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// 下载附件到本地
|
|
400
|
+
private async downloadAttachment(attachment: Attachment): Promise<string> {
|
|
401
|
+
if (!this.$config.attachments?.enabled || !this.$config.attachments.downloadPath) {
|
|
402
|
+
throw new Error('Attachment download is not enabled');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const downloadPath = this.$config.attachments.downloadPath;
|
|
406
|
+
await fs.mkdir(downloadPath, { recursive: true });
|
|
407
|
+
|
|
408
|
+
const filename = attachment.filename || `attachment_${Date.now()}`;
|
|
409
|
+
const filepath = path.join(downloadPath, filename);
|
|
410
|
+
|
|
411
|
+
return new Promise((resolve, reject) => {
|
|
412
|
+
const writeStream = createWriteStream(filepath);
|
|
413
|
+
writeStream.write(attachment.content);
|
|
414
|
+
writeStream.end();
|
|
415
|
+
|
|
416
|
+
writeStream.on('finish', () => resolve(filepath));
|
|
417
|
+
writeStream.on('error', reject);
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// 创建和注册适配器
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email 适配器入口:类型扩展、导出、注册
|
|
3
|
+
*/
|
|
4
|
+
import { usePlugin, type Plugin, type Context } from "zhin.js";
|
|
5
|
+
import { EmailAdapter } from "./adapter.js";
|
|
6
|
+
|
|
7
|
+
declare module "zhin.js" {
|
|
8
|
+
interface Adapters {
|
|
9
|
+
email: EmailAdapter;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export * from "./types.js";
|
|
14
|
+
export { EmailBot } from "./bot.js";
|
|
15
|
+
export { EmailAdapter } from "./adapter.js";
|
|
16
|
+
|
|
17
|
+
const plugin = usePlugin();
|
|
18
|
+
const { provide } = plugin;
|
|
19
|
+
|
|
20
|
+
provide({
|
|
21
|
+
name: "email",
|
|
22
|
+
description: "Email Bot Adapter",
|
|
23
|
+
mounted: async (p: Plugin) => {
|
|
24
|
+
const adapter = new EmailAdapter(p);
|
|
25
|
+
await adapter.start();
|
|
26
|
+
return adapter;
|
|
27
|
+
},
|
|
28
|
+
dispose: async (adapter: EmailAdapter) => {
|
|
29
|
+
await adapter.stop();
|
|
30
|
+
},
|
|
31
|
+
} as Context<"email">);
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email 适配器类型定义
|
|
3
|
+
*/
|
|
4
|
+
import type { Attachment } from "mailparser";
|
|
5
|
+
|
|
6
|
+
export interface SmtpConfig {
|
|
7
|
+
host: string;
|
|
8
|
+
port: number;
|
|
9
|
+
secure: boolean;
|
|
10
|
+
auth: {
|
|
11
|
+
user: string;
|
|
12
|
+
pass: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ImapConfig {
|
|
17
|
+
host: string;
|
|
18
|
+
port: number;
|
|
19
|
+
tls: boolean;
|
|
20
|
+
user: string;
|
|
21
|
+
password: string;
|
|
22
|
+
checkInterval?: number;
|
|
23
|
+
mailbox?: string;
|
|
24
|
+
markSeen?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface EmailBotConfig {
|
|
28
|
+
context: "email";
|
|
29
|
+
name: string;
|
|
30
|
+
smtp: SmtpConfig;
|
|
31
|
+
imap: ImapConfig;
|
|
32
|
+
attachments?: {
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
downloadPath?: string;
|
|
35
|
+
maxFileSize?: number;
|
|
36
|
+
allowedTypes?: string[];
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface EmailMessage {
|
|
41
|
+
messageId: string;
|
|
42
|
+
from: string;
|
|
43
|
+
to: string[];
|
|
44
|
+
cc?: string[];
|
|
45
|
+
bcc?: string[];
|
|
46
|
+
subject: string;
|
|
47
|
+
text?: string;
|
|
48
|
+
html?: string;
|
|
49
|
+
attachments: Attachment[];
|
|
50
|
+
date: Date;
|
|
51
|
+
uid: number;
|
|
52
|
+
}
|