foliko 1.0.48 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.0.48",
3
+ "version": "1.0.49",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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')
@@ -256,7 +256,7 @@ class WebPlugin extends Plugin {
256
256
  async _handleRoute(c, route, pathname) {
257
257
  const params = this._extractParams(route.path, pathname)
258
258
  const query = this._parseQuery(c)
259
- const body = await c.req.json().catch(() => ({}))
259
+ const body = await this._parseBody(c)
260
260
 
261
261
  const context = { params, query, body }
262
262
  const result = await this._executeHandler(route.handler, context)
@@ -265,7 +265,7 @@ class WebPlugin extends Plugin {
265
265
 
266
266
  async _handleWebhook(c, webhook) {
267
267
  const query = this._parseQuery(c)
268
- const body = await c.req.json().catch(() => ({}))
268
+ const body = await this._parseBody(c)
269
269
  const webhookData = {
270
270
  path: webhook.path,
271
271
  method: c.req.method,
@@ -490,6 +490,17 @@ class WebPlugin extends Plugin {
490
490
  return query
491
491
  }
492
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
+
493
504
  _getUrl(path='') {
494
505
  if (this._baseUrl) {
495
506
  return `${this._baseUrl}${path}`