foliko 1.0.4 → 1.0.5

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.4",
3
+ "version": "1.0.5",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -12,6 +12,13 @@
12
12
  const { Plugin } = require('../src/core/plugin-base')
13
13
  const { z } = require('zod')
14
14
 
15
+ // 转义 MarkdownV2 特殊字符
16
+ function escapeMarkdown(text) {
17
+ if (!text) return ''
18
+ // MarkdownV2 需要转义的字符
19
+ return text.replace(/([\_*\[\]()~`>#+\-=|{}.!])/g, '\\$1')
20
+ }
21
+
15
22
  module.exports = function(Plugin) {
16
23
  return class TelegramPlugin extends Plugin {
17
24
  constructor(config = {}) {
@@ -155,9 +162,24 @@ module.exports = function(Plugin) {
155
162
  * 处理消息
156
163
  */
157
164
  async _handleMessage(msg) {
158
- if (!msg || !msg.chat || !msg.text) return
165
+ if (!msg || !msg.chat) return
159
166
 
160
167
  const chatId = msg.chat.id.toString()
168
+
169
+ // 处理图片消息
170
+ if (msg.photo) {
171
+ await this._handlePhoto(msg)
172
+ return
173
+ }
174
+
175
+ // 处理文档消息
176
+ if (msg.document) {
177
+ await this._handleDocument(msg)
178
+ return
179
+ }
180
+
181
+ if (!msg.text) return
182
+
161
183
  const text = msg.text.trim()
162
184
 
163
185
  // 命令处理
@@ -279,11 +301,13 @@ module.exports = function(Plugin) {
279
301
  // 保存助手回复到历史
280
302
  session.history.push({ role: 'assistant', content: fullResponse })
281
303
 
282
- // 发送最终回复
283
- await this._bot.editMessageText(fullResponse || '抱歉,我没有收到有效的回复。', {
304
+ // 发送最终回复(转义 Markdown 特殊字符)
305
+ const safeResponse = fullResponse || '抱歉,我没有收到有效的回复。'
306
+ await this._bot.editMessageText(safeResponse, {
284
307
  chat_id: chatId,
285
308
  message_id: thinkingMsg.message_id,
286
- })
309
+ parse_mode: 'MarkdownV2'
310
+ })
287
311
 
288
312
  } catch (err) {
289
313
  console.error('[Telegram] Chat error:', err)
@@ -318,6 +342,105 @@ module.exports = function(Plugin) {
318
342
  return this.config.allowedChats.includes(chatId)
319
343
  }
320
344
 
345
+ /**
346
+ * 处理图片消息
347
+ */
348
+ async _handlePhoto(msg) {
349
+ const chatId = msg.chat.id.toString()
350
+ const caption = msg.caption?.trim() || ''
351
+
352
+ // 权限检查
353
+ if (!this._checkPermission(chatId)) {
354
+ await this._sendMessage(chatId, '抱歉,您没有权限使用此 Bot。', msg.message_id)
355
+ return
356
+ }
357
+
358
+ // 获取最大尺寸的图片
359
+ const photo = msg.photo[msg.photo.length - 1]
360
+ console.log(`[Telegram] Photo received, file_id: ${photo.file_id}`)
361
+
362
+ try {
363
+ // 下载图片
364
+ const filePath = await this._downloadFile(photo.file_id, '.jpg', 'telegram_images')
365
+ console.log(`[Telegram] Photo saved: ${filePath}`)
366
+
367
+ const savedMsg = `收到图片${caption ? ': ' + caption : ''}\n已保存至: ${filePath}`
368
+ await this._sendMessage(chatId, savedMsg, msg.message_id)
369
+
370
+ // TODO: 调用 AI 视觉能力分析图片
371
+ // await this._analyzeImage(chatId, filePath, caption)
372
+ } catch (err) {
373
+ console.error('[Telegram] Failed to save photo:', err.message)
374
+ await this._sendMessage(chatId, '图片保存失败: ' + err.message, msg.message_id)
375
+ }
376
+ }
377
+
378
+ /**
379
+ * 处理文档消息
380
+ */
381
+ async _handleDocument(msg) {
382
+ const chatId = msg.chat.id.toString()
383
+ const fileName = msg.document.file_name || '未命名文件'
384
+ const caption = msg.caption?.trim() || ''
385
+
386
+ // 权限检查
387
+ if (!this._checkPermission(chatId)) {
388
+ await this._sendMessage(chatId, '抱歉,您没有权限使用此 Bot。', msg.message_id)
389
+ return
390
+ }
391
+
392
+ console.log(`[Telegram] Document received: ${fileName}, file_id: ${msg.document.file_id}`)
393
+
394
+ try {
395
+ // 根据文件扩展名确定保存目录和扩展名
396
+ const ext = fileName.includes('.') ? fileName.split('.').pop() : 'bin'
397
+ const filePath = await this._downloadFile(msg.document.file_id, ext, 'telegram_documents')
398
+ console.log(`[Telegram] Document saved: ${filePath}`)
399
+
400
+ const savedMsg = `收到文件: ${fileName}\n已保存至: ${filePath}${caption ? '\n\n说明: ' + caption : ''}`
401
+ await this._sendMessage(chatId, savedMsg, msg.message_id)
402
+ } catch (err) {
403
+ console.error('[Telegram] Failed to save document:', err.message)
404
+ await this._sendMessage(chatId, '文件保存失败: ' + err.message, msg.message_id)
405
+ }
406
+ }
407
+
408
+ /**
409
+ * 下载文件
410
+ * @param {string} fileId - Telegram 文件 ID
411
+ * @param {string} ext - 文件扩展名
412
+ * @param {string} subDir - 子目录名
413
+ * @returns {Promise<string>} 保存的文件路径
414
+ */
415
+ async _downloadFile(fileId, ext, subDir) {
416
+ return new Promise((resolve, reject) => {
417
+ const path = require('path')
418
+ const fs = require('fs')
419
+
420
+ // 创建保存目录
421
+ const saveDir = path.join(process.cwd(), '.agent', 'data', subDir)
422
+ if (!fs.existsSync(saveDir)) {
423
+ fs.mkdirSync(saveDir, { recursive: true })
424
+ }
425
+
426
+ // 生成文件名
427
+ const fileName = `${Date.now()}_${Math.random().toString(36).substring(7)}.${ext}`
428
+ const filePath = path.join(saveDir, fileName)
429
+
430
+ // 下载文件
431
+ this._bot.downloadFile(fileId, saveDir)
432
+ .then((savedPath) => {
433
+ // 重命名为期望的文件名
434
+ const finalPath = path.join(saveDir, fileName)
435
+ if (savedPath !== finalPath) {
436
+ fs.renameSync(savedPath, finalPath)
437
+ }
438
+ resolve(finalPath)
439
+ })
440
+ .catch(reject)
441
+ })
442
+ }
443
+
321
444
  /**
322
445
  * 清除会话
323
446
  */