foliko 1.0.47 → 1.0.49
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/package.json +1 -1
- package/plugins/email.js +73 -2
- package/plugins/web-plugin.js +24 -3
package/package.json
CHANGED
package/plugins/email.js
CHANGED
|
@@ -11,7 +11,12 @@ class EmailPlugin extends Plugin {
|
|
|
11
11
|
super()
|
|
12
12
|
this.name = 'email'
|
|
13
13
|
this.version = '1.0.0'
|
|
14
|
-
this.description =
|
|
14
|
+
this.description = `邮件收发插件 - 支持读取和发送电子邮件
|
|
15
|
+
发送邮件支持附件:
|
|
16
|
+
- attachments.path: 本地文件路径
|
|
17
|
+
- attachments.url: 远程图片/文件URL(自动下载)
|
|
18
|
+
- attachments.content: Base64内容
|
|
19
|
+
- attachments.cid: 嵌入式图片CID(HTML中用 <img src="cid:xxx"> 引用)`
|
|
15
20
|
this.priority = 10
|
|
16
21
|
// 默认不启用,需要在 plugins.json 中设置 enabled: true
|
|
17
22
|
this.enabled = false
|
|
@@ -34,7 +39,14 @@ class EmailPlugin extends Plugin {
|
|
|
34
39
|
body: z.string().describe('邮件正文内容'),
|
|
35
40
|
cc: z.string().optional().describe('抄送邮箱地址,多个用逗号分隔'),
|
|
36
41
|
bcc: z.string().optional().describe('密送邮箱地址,多个用逗号分隔'),
|
|
37
|
-
isHtml: z.boolean().optional().describe('是否为HTML格式,默认false')
|
|
42
|
+
isHtml: z.boolean().optional().describe('是否为HTML格式,默认false'),
|
|
43
|
+
attachments: z.array(z.object({
|
|
44
|
+
filename: z.string().describe('附件文件名'),
|
|
45
|
+
path: z.string().optional().describe('本地文件路径'),
|
|
46
|
+
url: z.string().optional().describe('远程图片URL'),
|
|
47
|
+
content: z.string().optional().describe('Base64编码内容(可带前缀如 data:image/png;base64,)'),
|
|
48
|
+
cid: z.string().optional().describe('嵌入式图片的CID(用于在HTML中嵌入图片,如 <img src="cid:xxx">)')
|
|
49
|
+
})).optional().describe('附件列表,支持本地文件、远程URL或Base64内容')
|
|
38
50
|
}),
|
|
39
51
|
execute: async (args) => {
|
|
40
52
|
return this._sendEmail(args)
|
|
@@ -119,6 +131,10 @@ class EmailPlugin extends Plugin {
|
|
|
119
131
|
async _sendEmail(args) {
|
|
120
132
|
try {
|
|
121
133
|
const nodemailer = require('nodemailer')
|
|
134
|
+
const https = require('https')
|
|
135
|
+
const http = require('http')
|
|
136
|
+
const fs = require('fs')
|
|
137
|
+
const path = require('path')
|
|
122
138
|
|
|
123
139
|
const smtpConfig = {
|
|
124
140
|
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
|
@@ -142,6 +158,32 @@ class EmailPlugin extends Plugin {
|
|
|
142
158
|
bcc: args.bcc
|
|
143
159
|
}
|
|
144
160
|
|
|
161
|
+
// 处理附件
|
|
162
|
+
if (args.attachments && args.attachments.length > 0) {
|
|
163
|
+
mailOptions.attachments = []
|
|
164
|
+
for (const att of args.attachments) {
|
|
165
|
+
const attachment = { filename: att.filename }
|
|
166
|
+
|
|
167
|
+
if (att.path) {
|
|
168
|
+
// 本地文件
|
|
169
|
+
attachment.content = fs.createReadStream(att.path)
|
|
170
|
+
} else if (att.url) {
|
|
171
|
+
// 远程URL
|
|
172
|
+
attachment.content = await this._fetchUrl(att.url)
|
|
173
|
+
} else if (att.content) {
|
|
174
|
+
// Base64 内容
|
|
175
|
+
const base64Data = att.content.replace(/^data:[^;]+;base64,/, '')
|
|
176
|
+
attachment.content = Buffer.from(base64Data, 'base64')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (att.cid) {
|
|
180
|
+
attachment.cid = att.cid
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
mailOptions.attachments.push(attachment)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
145
187
|
const info = await transporter.sendMail(mailOptions)
|
|
146
188
|
return {
|
|
147
189
|
success: true,
|
|
@@ -159,6 +201,35 @@ class EmailPlugin extends Plugin {
|
|
|
159
201
|
}
|
|
160
202
|
}
|
|
161
203
|
|
|
204
|
+
_fetchUrl(url) {
|
|
205
|
+
return new Promise((resolve, reject) => {
|
|
206
|
+
const protocol = url.startsWith('https') ? https : http
|
|
207
|
+
const lib = url.startsWith('https') ? require('https') : require('http')
|
|
208
|
+
|
|
209
|
+
const request = lib.get(url, (response) => {
|
|
210
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
211
|
+
// 处理重定向
|
|
212
|
+
this._fetchUrl(response.headers.location).then(resolve).catch(reject)
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const chunks = []
|
|
217
|
+
response.on('data', (chunk) => chunks.push(chunk))
|
|
218
|
+
response.on('end', () => {
|
|
219
|
+
const buffer = Buffer.concat(chunks)
|
|
220
|
+
resolve(buffer)
|
|
221
|
+
})
|
|
222
|
+
response.on('error', reject)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
request.on('error', reject)
|
|
226
|
+
request.setTimeout(10000, () => {
|
|
227
|
+
request.destroy()
|
|
228
|
+
reject(new Error('下载超时'))
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
162
233
|
async _readEmails(args) {
|
|
163
234
|
try {
|
|
164
235
|
const Imap = require('imap-mkl')
|
package/plugins/web-plugin.js
CHANGED
|
@@ -39,6 +39,12 @@ class WebPlugin extends Plugin {
|
|
|
39
39
|
install(framework) {
|
|
40
40
|
this._framework = framework
|
|
41
41
|
this._registerTools()
|
|
42
|
+
|
|
43
|
+
// 将 WEB_BASE_URL 注入到 framework 的元数据,供所有 agent 使用
|
|
44
|
+
if (this._baseUrl && framework._mainAgent) {
|
|
45
|
+
framework._mainAgent.setMetadata('WEB_BASE_URL', this._baseUrl)
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
return this
|
|
43
49
|
}
|
|
44
50
|
|
|
@@ -48,6 +54,10 @@ class WebPlugin extends Plugin {
|
|
|
48
54
|
|
|
49
55
|
reload(framework) {
|
|
50
56
|
this._framework = framework
|
|
57
|
+
// 重新注入 WEB_BASE_URL
|
|
58
|
+
if (this._baseUrl && framework._mainAgent) {
|
|
59
|
+
framework._mainAgent.setMetadata('WEB_BASE_URL', this._baseUrl)
|
|
60
|
+
}
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
uninstall() {
|
|
@@ -246,7 +256,7 @@ class WebPlugin extends Plugin {
|
|
|
246
256
|
async _handleRoute(c, route, pathname) {
|
|
247
257
|
const params = this._extractParams(route.path, pathname)
|
|
248
258
|
const query = this._parseQuery(c)
|
|
249
|
-
const body = await
|
|
259
|
+
const body = await this._parseBody(c)
|
|
250
260
|
|
|
251
261
|
const context = { params, query, body }
|
|
252
262
|
const result = await this._executeHandler(route.handler, context)
|
|
@@ -255,7 +265,7 @@ class WebPlugin extends Plugin {
|
|
|
255
265
|
|
|
256
266
|
async _handleWebhook(c, webhook) {
|
|
257
267
|
const query = this._parseQuery(c)
|
|
258
|
-
const body = await
|
|
268
|
+
const body = await this._parseBody(c)
|
|
259
269
|
const webhookData = {
|
|
260
270
|
path: webhook.path,
|
|
261
271
|
method: c.req.method,
|
|
@@ -354,7 +364,7 @@ class WebPlugin extends Plugin {
|
|
|
354
364
|
}
|
|
355
365
|
|
|
356
366
|
console.log(`[Web] Route registered: ${method} ${path}`)
|
|
357
|
-
return { success: true, message: `Route ${method} ${path} registered`, route: { method, path, description } }
|
|
367
|
+
return { success: true, message: `Route ${method} ${path} registered`, url: this._getUrl(path), route: { method, path, description } }
|
|
358
368
|
}
|
|
359
369
|
|
|
360
370
|
async _registerWebhook(prompt, awaitResponse = false) {
|
|
@@ -480,6 +490,17 @@ class WebPlugin extends Plugin {
|
|
|
480
490
|
return query
|
|
481
491
|
}
|
|
482
492
|
|
|
493
|
+
async _parseBody(c) {
|
|
494
|
+
try {
|
|
495
|
+
const rawText = await c.req.text()
|
|
496
|
+
console.log(rawText)
|
|
497
|
+
if (!rawText) return {}
|
|
498
|
+
return JSON.parse(rawText)
|
|
499
|
+
} catch (e) {
|
|
500
|
+
return {}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
483
504
|
_getUrl(path='') {
|
|
484
505
|
if (this._baseUrl) {
|
|
485
506
|
return `${this._baseUrl}${path}`
|