foliko 1.0.75 → 1.0.76
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/.claude/settings.local.json +159 -157
- package/cli/bin/foliko.js +12 -12
- package/cli/src/commands/chat.js +143 -143
- package/cli/src/commands/list.js +93 -93
- package/cli/src/index.js +75 -75
- package/cli/src/ui/chat-ui.js +201 -201
- package/cli/src/utils/ansi.js +40 -40
- package/cli/src/utils/markdown.js +292 -292
- package/examples/ambient-example.js +194 -194
- package/examples/basic.js +115 -115
- package/examples/bootstrap.js +121 -121
- package/examples/mcp-example.js +56 -56
- package/examples/skill-example.js +49 -49
- package/examples/test-chat.js +137 -137
- package/examples/test-mcp.js +85 -85
- package/examples/test-reload.js +59 -59
- package/examples/test-telegram.js +50 -50
- package/examples/test-tg-bot.js +45 -45
- package/examples/test-tg-simple.js +47 -47
- package/examples/test-tg.js +62 -62
- package/examples/test-think.js +43 -43
- package/examples/test-web-plugin.js +103 -103
- package/examples/test-weixin-feishu.js +103 -103
- package/examples/workflow.js +158 -158
- package/package.json +1 -1
- package/plugins/ai-plugin.js +102 -102
- package/plugins/ambient-agent/EventWatcher.js +113 -113
- package/plugins/ambient-agent/ExplorerLoop.js +640 -640
- package/plugins/ambient-agent/GoalManager.js +197 -197
- package/plugins/ambient-agent/Reflector.js +95 -95
- package/plugins/ambient-agent/StateStore.js +90 -90
- package/plugins/ambient-agent/constants.js +101 -101
- package/plugins/ambient-agent/index.js +579 -579
- package/plugins/audit-plugin.js +187 -187
- package/plugins/default-plugins.js +662 -662
- package/plugins/email/constants.js +64 -64
- package/plugins/email/handlers.js +461 -461
- package/plugins/email/index.js +278 -278
- package/plugins/email/monitor.js +269 -269
- package/plugins/email/parser.js +138 -138
- package/plugins/email/reply.js +151 -151
- package/plugins/email/utils.js +124 -124
- package/plugins/feishu-plugin.js +481 -481
- package/plugins/file-system-plugin.js +826 -826
- package/plugins/install-plugin.js +199 -199
- package/plugins/python-executor-plugin.js +367 -367
- package/plugins/python-plugin-loader.js +481 -481
- package/plugins/rules-plugin.js +294 -294
- package/plugins/scheduler-plugin.js +691 -691
- package/plugins/session-plugin.js +369 -369
- package/plugins/shell-executor-plugin.js +197 -197
- package/plugins/storage-plugin.js +240 -240
- package/plugins/subagent-plugin.js +845 -845
- package/plugins/telegram-plugin.js +482 -482
- package/plugins/think-plugin.js +345 -345
- package/plugins/tools-plugin.js +196 -196
- package/plugins/web-plugin.js +606 -606
- package/plugins/weixin-plugin.js +545 -545
- package/src/capabilities/index.js +11 -11
- package/src/capabilities/skill-manager.js +609 -609
- package/src/capabilities/workflow-engine.js +1109 -1109
- package/src/core/agent-chat.js +882 -882
- package/src/core/agent.js +892 -892
- package/src/core/framework.js +465 -465
- package/src/core/index.js +19 -19
- package/src/core/plugin-base.js +219 -219
- package/src/core/plugin-manager.js +863 -863
- package/src/core/provider.js +114 -114
- package/src/core/sub-agent-config.js +264 -264
- package/src/core/system-prompt-builder.js +120 -120
- package/src/core/tool-registry.js +517 -517
- package/src/core/tool-router.js +297 -297
- package/src/executors/executor-base.js +58 -58
- package/src/executors/mcp-executor.js +741 -741
- package/src/index.js +25 -25
- package/src/utils/circuit-breaker.js +301 -301
- package/src/utils/error-boundary.js +363 -363
- package/src/utils/error.js +374 -374
- package/src/utils/event-emitter.js +97 -97
- package/src/utils/id.js +133 -133
- package/src/utils/index.js +217 -217
- package/src/utils/logger.js +181 -181
- package/src/utils/plugin-helpers.js +90 -90
- package/src/utils/retry.js +122 -122
- package/src/utils/sandbox.js +292 -292
- package/test/tool-registry-validation.test.js +218 -218
- package/website/script.js +136 -136
- package/foliko-1.0.75.tgz +0 -0
|
@@ -1,482 +1,482 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Telegram 插件
|
|
3
|
-
* 支持绑定主Agent进行持续对话
|
|
4
|
-
*
|
|
5
|
-
* 配置:
|
|
6
|
-
* - botToken: Telegram Bot Token (必需)
|
|
7
|
-
* - allowedChats: 允许的聊天ID数组
|
|
8
|
-
* - groupMode: 是否启用群组模式
|
|
9
|
-
* - prefix: 命令前缀
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const { Plugin } = require('../src/core/plugin-base')
|
|
13
|
-
const { logger } = require('../src/utils/logger')
|
|
14
|
-
const log = logger.child('Telegram')
|
|
15
|
-
const { z } = require('zod')
|
|
16
|
-
|
|
17
|
-
// 转义 MarkdownV2 特殊字符
|
|
18
|
-
function escapeMarkdown(text) {
|
|
19
|
-
if (!text) return ''
|
|
20
|
-
return text.replace(/([\_*\[\]()~`>#+\-=|{}.!])/g, '\\$1')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
class TelegramPlugin extends Plugin {
|
|
24
|
-
constructor(config = {}) {
|
|
25
|
-
super()
|
|
26
|
-
this.name = 'telegram'
|
|
27
|
-
this.version = '2.1.0'
|
|
28
|
-
this.description = 'Telegram 对话插件,绑定主Agent进行持续对话'
|
|
29
|
-
this.priority = 80
|
|
30
|
-
this.enabled = false
|
|
31
|
-
this.systemPrompt = `你是一个有帮助的AI助手。回复内容不要使用markdown格式文本。
|
|
32
|
-
|
|
33
|
-
**重要:** 子Agent 匹配规则必须遵守:
|
|
34
|
-
- 根据【子 Agent 匹配表】,将任务委托给最匹配的子Agent处理
|
|
35
|
-
- 使用 subagent_call 工具并指定 agentName 来委托任务
|
|
36
|
-
- 只有当没有匹配的子Agent时,才直接调用工具
|
|
37
|
-
|
|
38
|
-
**命令执行规范:**
|
|
39
|
-
- 执行 npx skills add / npx skills remove / npx skills list 命令时,必须自动添加参数:-a openclaw -y
|
|
40
|
-
- 禁止添加 -g 参数
|
|
41
|
-
- 例如:npx skills add xxx 应该执行为 npx skills add xxx -a openclaw -y`
|
|
42
|
-
|
|
43
|
-
this.config = {
|
|
44
|
-
botToken: config.botToken || process.env.TELEGRAM_BOT_TOKEN,
|
|
45
|
-
allowedChats: config.allowedChats || [],
|
|
46
|
-
groupMode: config.groupMode || false,
|
|
47
|
-
prefix: config.prefix || '/'
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
this._framework = null
|
|
51
|
-
this._bot = null
|
|
52
|
-
this._sessionPlugin = null
|
|
53
|
-
this._sessionDeleteHandler = null
|
|
54
|
-
this._sessionAgents = new Map()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
install(framework) {
|
|
58
|
-
this._framework = framework
|
|
59
|
-
return this
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
start(framework) {
|
|
63
|
-
if (!this.config.botToken) {
|
|
64
|
-
log.warn(' No bot token. Set TELEGRAM_BOT_TOKEN env var.')
|
|
65
|
-
return this
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
this._framework = framework
|
|
69
|
-
this._sessionPlugin = framework.pluginManager.get('session')
|
|
70
|
-
|
|
71
|
-
if (this._sessionPlugin) {
|
|
72
|
-
this._sessionDeleteHandler = (sessionId) => {
|
|
73
|
-
if (sessionId.startsWith('telegram_')) {
|
|
74
|
-
log.info(` Session deleted: ${sessionId}`)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
this._sessionPlugin.on('session:deleted', this._sessionDeleteHandler)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
this._initBot()
|
|
81
|
-
return this
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
_initBot() {
|
|
85
|
-
try {
|
|
86
|
-
const TelegramBot = require('node-telegram-bot-api')
|
|
87
|
-
this._bot = new TelegramBot(this.config.botToken, { polling: true })
|
|
88
|
-
|
|
89
|
-
log.info(' Bot started successfully')
|
|
90
|
-
|
|
91
|
-
this._bot.setMyCommands([
|
|
92
|
-
{ command: 'start', description: '显示帮助信息' },
|
|
93
|
-
{ command: 'clear', description: '清除对话历史' },
|
|
94
|
-
{ command: 'history', description: '查看对话状态' }
|
|
95
|
-
]).catch((err) => {
|
|
96
|
-
log.error(' Failed to register commands:', err.message)
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
this._bot.on('message', (msg) => this._handleMessage(msg))
|
|
100
|
-
this._bot.on('edit_message', (msg) => this._handleEdit(msg))
|
|
101
|
-
this._bot.on('callback_query', (query) => this._handleCallback(query))
|
|
102
|
-
this._bot.on('polling_error', (err) => log.error(' Polling error:', err.message))
|
|
103
|
-
this._bot.on('error', (err) => log.error(' Bot error:', err.message))
|
|
104
|
-
|
|
105
|
-
if (this._framework) {
|
|
106
|
-
this._framework.on('agent:created', (agent) => {
|
|
107
|
-
log.info(' New agent created:', agent.name)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
// 监听统一通知事件
|
|
111
|
-
this._framework.on('notification', async (data) => {
|
|
112
|
-
await this._handleNotification(data)
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
// 监听 webhook 事件
|
|
116
|
-
this._framework.on('webhook:received', async (data) => {
|
|
117
|
-
await this._handleWebhookNotification(data)
|
|
118
|
-
})
|
|
119
|
-
}
|
|
120
|
-
} catch (err) {
|
|
121
|
-
log.error(' Failed to initialize bot:', err.message)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* 处理统一通知
|
|
127
|
-
*/
|
|
128
|
-
async _handleNotification(data) {
|
|
129
|
-
const { title, message, source, level } = data
|
|
130
|
-
|
|
131
|
-
if (!this._bot) {
|
|
132
|
-
log.warn(' Bot not ready, cannot send notification')
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// 确定 chatId
|
|
137
|
-
let chatId = null
|
|
138
|
-
let effectiveSessionId = data.sessionId
|
|
139
|
-
|
|
140
|
-
// 如果没有 sessionId,尝试从执行上下文获取
|
|
141
|
-
if (!effectiveSessionId) {
|
|
142
|
-
const ctx = this._framework.getExecutionContext()
|
|
143
|
-
if (ctx?.sessionId) {
|
|
144
|
-
effectiveSessionId = ctx.sessionId
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (effectiveSessionId && effectiveSessionId.startsWith('telegram_')) {
|
|
149
|
-
chatId = effectiveSessionId.replace('telegram_', '')
|
|
150
|
-
} else if (this._sessionPlugin) {
|
|
151
|
-
// 获取最近的 telegram 会话
|
|
152
|
-
const sessions = this._sessionPlugin.listSessions()
|
|
153
|
-
.filter(s => s.id.startsWith('telegram_'))
|
|
154
|
-
.sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
|
|
155
|
-
if (sessions.length > 0) {
|
|
156
|
-
chatId = sessions[0].id.replace('telegram_', '')
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (!chatId) {
|
|
161
|
-
log.warn(' No telegram session found for notification')
|
|
162
|
-
return
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// 格式化通知消息
|
|
166
|
-
const levelEmoji = {
|
|
167
|
-
info: 'ℹ️',
|
|
168
|
-
warning: '⚠️',
|
|
169
|
-
success: '✅',
|
|
170
|
-
error: '❌'
|
|
171
|
-
}
|
|
172
|
-
const emoji = levelEmoji[level] || 'ℹ️'
|
|
173
|
-
const notificationText = `${emoji} [${source}] ${title}\n\n${message}`
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
await this._bot.sendMessage(chatId, notificationText)
|
|
177
|
-
log.info(` Notification sent to chat ${chatId}`)
|
|
178
|
-
} catch (err) {
|
|
179
|
-
log.error(` Failed to send notification:`, err.message)
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* 处理 webhook 通知
|
|
185
|
-
*/
|
|
186
|
-
async _handleWebhookNotification(data) {
|
|
187
|
-
const { data: webhookData, response, sessionId } = data
|
|
188
|
-
|
|
189
|
-
if (!sessionId || !sessionId.startsWith('telegram_')) {
|
|
190
|
-
return
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const chatId = sessionId.replace('telegram_', '')
|
|
194
|
-
const notificationText = `📥 [Webhook 接收]\n\n路径: ${webhookData.path}\n方法: ${webhookData.method}\n\n处理结果: ${response || '处理中...'}`
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
await this._bot.sendMessage(chatId, notificationText)
|
|
198
|
-
log.info(` Webhook notification sent to chat ${chatId}`)
|
|
199
|
-
} catch (err) {
|
|
200
|
-
log.error(` Failed to send webhook notification:`, err.message)
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
_getMainAgent() {
|
|
205
|
-
if (this._framework._mainAgent) return this._framework._mainAgent
|
|
206
|
-
const agents = this._framework._agents || []
|
|
207
|
-
return agents.length > 0 ? agents[agents.length - 1] : null
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
_getSessionAgent(chatId) {
|
|
211
|
-
if (this._sessionAgents.has(chatId)) {
|
|
212
|
-
return { agent: this._sessionAgents.get(chatId), sessionId: `telegram_${chatId}` }
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const agent = this._framework.createSessionAgent(`telegram_${chatId}`, {
|
|
216
|
-
systemPrompt: this.systemPrompt,
|
|
217
|
-
sharedPrompt: `工作目录: {{WORK_DIR}}`,
|
|
218
|
-
metadata: { WORK_DIR: process.cwd() }
|
|
219
|
-
})
|
|
220
|
-
this._sessionAgents.set(chatId, agent)
|
|
221
|
-
|
|
222
|
-
if (this._sessionPlugin) {
|
|
223
|
-
this._sessionPlugin.getOrCreateSession(`telegram_${chatId}`, {
|
|
224
|
-
metadata: { platform: 'telegram', chatId }
|
|
225
|
-
})
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return { agent, sessionId: `telegram_${chatId}` }
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async _handleMessage(msg) {
|
|
232
|
-
if (!msg || !msg.chat) return
|
|
233
|
-
const chatId = msg.chat.id.toString()
|
|
234
|
-
|
|
235
|
-
if (msg.photo) { await this._handlePhoto(msg); return }
|
|
236
|
-
if (msg.document) { await this._handleDocument(msg); return }
|
|
237
|
-
if (!msg.text) return
|
|
238
|
-
|
|
239
|
-
const text = msg.text.trim()
|
|
240
|
-
if (text.startsWith(this.config.prefix)) {
|
|
241
|
-
await this._handleCommand(msg)
|
|
242
|
-
return
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (msg.chat.type === 'group' || msg.chat.type === 'supergroup') {
|
|
246
|
-
if (!this.config.groupMode) return
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (!this._checkPermission(chatId)) {
|
|
250
|
-
await this._sendMessage(chatId, '抱歉,您没有权限使用此 Bot。', msg.message_id)
|
|
251
|
-
return
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
await this._processChat(chatId, text, msg.message_id)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
async _handleCommand(msg) {
|
|
258
|
-
const chatId = msg.chat.id.toString()
|
|
259
|
-
const text = msg.text.trim()
|
|
260
|
-
const parts = text.split(' ')
|
|
261
|
-
const command = parts[0].substring(1)
|
|
262
|
-
const args = parts.slice(1).join(' ')
|
|
263
|
-
|
|
264
|
-
switch (command.toLowerCase()) {
|
|
265
|
-
case 'start':
|
|
266
|
-
await this._sendMessage(chatId,
|
|
267
|
-
'👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/start - 显示帮助\n/clear - 清除对话历史\n/history - 查看历史消息数',
|
|
268
|
-
msg.message_id)
|
|
269
|
-
break
|
|
270
|
-
case 'clear':
|
|
271
|
-
this._clearSession(chatId)
|
|
272
|
-
await this._sendMessage(chatId, '✅ 对话历史已清除', msg.message_id)
|
|
273
|
-
break
|
|
274
|
-
case 'history':
|
|
275
|
-
if (this._sessionPlugin) {
|
|
276
|
-
const session = this._sessionPlugin.getSession(`telegram_${chatId}`)
|
|
277
|
-
const sessions = this._sessionPlugin.listSessions().filter(s => s.id.startsWith('telegram_'))
|
|
278
|
-
await this._sendMessage(chatId,
|
|
279
|
-
`📊 当前状态\n\n会话数:${sessions.length}\n历史消息:${session?.messages?.length || 0}\n最后活跃:${session?.lastActive?.toLocaleString() || '无'}`,
|
|
280
|
-
msg.message_id)
|
|
281
|
-
} else {
|
|
282
|
-
await this._sendMessage(chatId, '❌ 会话服务不可用', msg.message_id)
|
|
283
|
-
}
|
|
284
|
-
break
|
|
285
|
-
default:
|
|
286
|
-
await this._processChat(chatId, text, msg.message_id)
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
async _processChat(chatId, text, replyToMessageId) {
|
|
291
|
-
const sessionInfo = this._getSessionAgent(chatId)
|
|
292
|
-
if (!sessionInfo) {
|
|
293
|
-
await this._sendMessage(chatId, '❌ AI 服务未初始化', replyToMessageId)
|
|
294
|
-
return
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const { agent, sessionId } = sessionInfo
|
|
298
|
-
|
|
299
|
-
if (this._sessionPlugin) {
|
|
300
|
-
this._sessionPlugin.addMessage(sessionId, { role: 'user', content: text })
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
let thinkingMsg
|
|
304
|
-
try {
|
|
305
|
-
thinkingMsg = await this._bot.sendMessage(chatId, '🤔 思考中...', {
|
|
306
|
-
reply_to_message_id: replyToMessageId
|
|
307
|
-
})
|
|
308
|
-
} catch (err) {
|
|
309
|
-
log.error(' Send thinking error:', err.message)
|
|
310
|
-
return
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
try {
|
|
314
|
-
let fullResponse = ''
|
|
315
|
-
for await (const chunk of agent.chatStream(text, { sessionId })) {
|
|
316
|
-
if (chunk.type === 'text' && chunk.text) {
|
|
317
|
-
fullResponse += chunk.text
|
|
318
|
-
if (fullResponse.length % 100 === 0) {
|
|
319
|
-
try {
|
|
320
|
-
await this._bot.editMessageText(`📝 ${escapeMarkdown(fullResponse)}▌`, {
|
|
321
|
-
chat_id: chatId,
|
|
322
|
-
message_id: thinkingMsg.message_id
|
|
323
|
-
})
|
|
324
|
-
} catch (e) { /* ignore */ }
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (this._sessionPlugin) {
|
|
330
|
-
this._sessionPlugin.addMessage(sessionId, { role: 'assistant', content: fullResponse })
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const safeResponse = escapeMarkdown(fullResponse) || '抱歉,我没有收到有效的回复。'
|
|
334
|
-
await this._bot.editMessageText(safeResponse, {
|
|
335
|
-
chat_id: chatId,
|
|
336
|
-
message_id: thinkingMsg.message_id,
|
|
337
|
-
parse_mode: 'MarkdownV2'
|
|
338
|
-
})
|
|
339
|
-
} catch (err) {
|
|
340
|
-
log.error(' Chat error:', err)
|
|
341
|
-
await this._bot.editMessageText(escapeMarkdown(`❌ 发生错误:${err.message}`), {
|
|
342
|
-
chat_id: chatId,
|
|
343
|
-
message_id: thinkingMsg.message_id
|
|
344
|
-
})
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
async _handleEdit(msg) { /* 支持编辑后重新处理 */ }
|
|
349
|
-
|
|
350
|
-
async _handleCallback(query) {
|
|
351
|
-
await this._bot.answerCallbackQuery(query.id, { text: '处理中...' })
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
_checkPermission(chatId) {
|
|
355
|
-
return this.config.allowedChats.length === 0 || this.config.allowedChats.includes(chatId)
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
async _handlePhoto(msg) {
|
|
359
|
-
const chatId = msg.chat.id.toString()
|
|
360
|
-
if (!this._checkPermission(chatId)) {
|
|
361
|
-
await this._sendMessage(chatId, '抱歉,您没有权限使用此 Bot。', msg.message_id)
|
|
362
|
-
return
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const caption = msg.caption?.trim() || ''
|
|
366
|
-
const photo = msg.photo[msg.photo.length - 1]
|
|
367
|
-
|
|
368
|
-
try {
|
|
369
|
-
const filePath = await this._downloadFile(photo.file_id, '.jpg', 'telegram_images')
|
|
370
|
-
await this._sendMessage(chatId, `收到图片${caption ? ': ' + caption : ''}\n已保存至: ${filePath}`, msg.message_id)
|
|
371
|
-
if (caption) {
|
|
372
|
-
await this._processChat(chatId, `图片:${filePath}, ${caption}`, msg.message_id)
|
|
373
|
-
}
|
|
374
|
-
} catch (err) {
|
|
375
|
-
log.error(' Failed to save photo:', err.message)
|
|
376
|
-
await this._sendMessage(chatId, '图片保存失败: ' + err.message, msg.message_id)
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
async _handleDocument(msg) {
|
|
381
|
-
const chatId = msg.chat.id.toString()
|
|
382
|
-
if (!this._checkPermission(chatId)) {
|
|
383
|
-
await this._sendMessage(chatId, '抱歉,您没有权限使用此 Bot。', msg.message_id)
|
|
384
|
-
return
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const fileName = msg.document.file_name || '未命名文件'
|
|
388
|
-
const caption = msg.caption?.trim() || ''
|
|
389
|
-
const ext = fileName.includes('.') ? fileName.split('.').pop() : 'bin'
|
|
390
|
-
|
|
391
|
-
try {
|
|
392
|
-
const filePath = await this._downloadFile(msg.document.file_id, ext, 'telegram_documents')
|
|
393
|
-
await this._sendMessage(chatId, `收到文件: ${fileName}\n已保存至: ${filePath}${caption ? '\n\n说明: ' + caption : ''}`, msg.message_id)
|
|
394
|
-
if (caption) {
|
|
395
|
-
await this._processChat(chatId, `文件:${filePath}, ${caption}`, msg.message_id)
|
|
396
|
-
}
|
|
397
|
-
} catch (err) {
|
|
398
|
-
log.error(' Failed to save document:', err.message)
|
|
399
|
-
await this._sendMessage(chatId, '文件保存失败: ' + err.message, msg.message_id)
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
async _downloadFile(fileId, ext, subDir) {
|
|
404
|
-
const path = require('path')
|
|
405
|
-
const fs = require('fs')
|
|
406
|
-
const saveDir = path.join(process.cwd(), '.agent', 'data', subDir)
|
|
407
|
-
if (!fs.existsSync(saveDir)) fs.mkdirSync(saveDir, { recursive: true })
|
|
408
|
-
|
|
409
|
-
const fileName = `${Date.now()}_${Math.random().toString(36).substring(7)}.${ext}`
|
|
410
|
-
const filePath = path.join(saveDir, fileName)
|
|
411
|
-
|
|
412
|
-
const savedPath = await this._bot.downloadFile(fileId, saveDir)
|
|
413
|
-
if (savedPath !== filePath) fs.renameSync(savedPath, filePath)
|
|
414
|
-
return filePath
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
_clearSession(chatId) {
|
|
418
|
-
if (this._sessionPlugin) {
|
|
419
|
-
this._sessionPlugin.deleteSession(`telegram_${chatId}`)
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
async _sendMessage(chatId, text, replyToMessageId = null) {
|
|
424
|
-
if (!this._bot) return
|
|
425
|
-
return this._bot.sendMessage(chatId, text, { reply_to_message_id: replyToMessageId })
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
getStatus() {
|
|
429
|
-
let sessions = []
|
|
430
|
-
if (this._sessionPlugin) {
|
|
431
|
-
sessions = this._sessionPlugin.listSessions()
|
|
432
|
-
.filter(s => s.id.startsWith('telegram_'))
|
|
433
|
-
.map(s => ({
|
|
434
|
-
chatId: s.id.replace('telegram_', ''),
|
|
435
|
-
historyLength: s.messageCount,
|
|
436
|
-
lastActive: s.lastActive
|
|
437
|
-
}))
|
|
438
|
-
}
|
|
439
|
-
return {
|
|
440
|
-
connected: !!this._bot,
|
|
441
|
-
sessionCount: sessions.length,
|
|
442
|
-
sessions,
|
|
443
|
-
config: {
|
|
444
|
-
groupMode: this.config.groupMode,
|
|
445
|
-
prefix: this.config.prefix,
|
|
446
|
-
allowedChatsCount: this.config.allowedChats.length
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
stopBot() {
|
|
452
|
-
if (this._bot) {
|
|
453
|
-
this._bot.stopPolling()
|
|
454
|
-
this._bot = null
|
|
455
|
-
log.info(' Bot stopped')
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
reload(framework) {
|
|
460
|
-
this._framework = framework
|
|
461
|
-
if (this.config.botToken) {
|
|
462
|
-
this.stopBot()
|
|
463
|
-
this._initBot()
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
uninstall() {
|
|
468
|
-
for (const agent of this._sessionAgents.values()) {
|
|
469
|
-
agent.destroy()
|
|
470
|
-
}
|
|
471
|
-
this._sessionAgents.clear()
|
|
472
|
-
this.stopBot()
|
|
473
|
-
if (this._sessionPlugin && this._sessionDeleteHandler) {
|
|
474
|
-
this._sessionPlugin.off('session:deleted', this._sessionDeleteHandler)
|
|
475
|
-
this._sessionDeleteHandler = null
|
|
476
|
-
}
|
|
477
|
-
this._sessionPlugin = null
|
|
478
|
-
this._framework = null
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
module.exports = { TelegramPlugin }
|
|
1
|
+
/**
|
|
2
|
+
* Telegram 插件
|
|
3
|
+
* 支持绑定主Agent进行持续对话
|
|
4
|
+
*
|
|
5
|
+
* 配置:
|
|
6
|
+
* - botToken: Telegram Bot Token (必需)
|
|
7
|
+
* - allowedChats: 允许的聊天ID数组
|
|
8
|
+
* - groupMode: 是否启用群组模式
|
|
9
|
+
* - prefix: 命令前缀
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { Plugin } = require('../src/core/plugin-base')
|
|
13
|
+
const { logger } = require('../src/utils/logger')
|
|
14
|
+
const log = logger.child('Telegram')
|
|
15
|
+
const { z } = require('zod')
|
|
16
|
+
|
|
17
|
+
// 转义 MarkdownV2 特殊字符
|
|
18
|
+
function escapeMarkdown(text) {
|
|
19
|
+
if (!text) return ''
|
|
20
|
+
return text.replace(/([\_*\[\]()~`>#+\-=|{}.!])/g, '\\$1')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class TelegramPlugin extends Plugin {
|
|
24
|
+
constructor(config = {}) {
|
|
25
|
+
super()
|
|
26
|
+
this.name = 'telegram'
|
|
27
|
+
this.version = '2.1.0'
|
|
28
|
+
this.description = 'Telegram 对话插件,绑定主Agent进行持续对话'
|
|
29
|
+
this.priority = 80
|
|
30
|
+
this.enabled = false
|
|
31
|
+
this.systemPrompt = `你是一个有帮助的AI助手。回复内容不要使用markdown格式文本。
|
|
32
|
+
|
|
33
|
+
**重要:** 子Agent 匹配规则必须遵守:
|
|
34
|
+
- 根据【子 Agent 匹配表】,将任务委托给最匹配的子Agent处理
|
|
35
|
+
- 使用 subagent_call 工具并指定 agentName 来委托任务
|
|
36
|
+
- 只有当没有匹配的子Agent时,才直接调用工具
|
|
37
|
+
|
|
38
|
+
**命令执行规范:**
|
|
39
|
+
- 执行 npx skills add / npx skills remove / npx skills list 命令时,必须自动添加参数:-a openclaw -y
|
|
40
|
+
- 禁止添加 -g 参数
|
|
41
|
+
- 例如:npx skills add xxx 应该执行为 npx skills add xxx -a openclaw -y`
|
|
42
|
+
|
|
43
|
+
this.config = {
|
|
44
|
+
botToken: config.botToken || process.env.TELEGRAM_BOT_TOKEN,
|
|
45
|
+
allowedChats: config.allowedChats || [],
|
|
46
|
+
groupMode: config.groupMode || false,
|
|
47
|
+
prefix: config.prefix || '/'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this._framework = null
|
|
51
|
+
this._bot = null
|
|
52
|
+
this._sessionPlugin = null
|
|
53
|
+
this._sessionDeleteHandler = null
|
|
54
|
+
this._sessionAgents = new Map()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
install(framework) {
|
|
58
|
+
this._framework = framework
|
|
59
|
+
return this
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
start(framework) {
|
|
63
|
+
if (!this.config.botToken) {
|
|
64
|
+
log.warn(' No bot token. Set TELEGRAM_BOT_TOKEN env var.')
|
|
65
|
+
return this
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this._framework = framework
|
|
69
|
+
this._sessionPlugin = framework.pluginManager.get('session')
|
|
70
|
+
|
|
71
|
+
if (this._sessionPlugin) {
|
|
72
|
+
this._sessionDeleteHandler = (sessionId) => {
|
|
73
|
+
if (sessionId.startsWith('telegram_')) {
|
|
74
|
+
log.info(` Session deleted: ${sessionId}`)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
this._sessionPlugin.on('session:deleted', this._sessionDeleteHandler)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this._initBot()
|
|
81
|
+
return this
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
_initBot() {
|
|
85
|
+
try {
|
|
86
|
+
const TelegramBot = require('node-telegram-bot-api')
|
|
87
|
+
this._bot = new TelegramBot(this.config.botToken, { polling: true })
|
|
88
|
+
|
|
89
|
+
log.info(' Bot started successfully')
|
|
90
|
+
|
|
91
|
+
this._bot.setMyCommands([
|
|
92
|
+
{ command: 'start', description: '显示帮助信息' },
|
|
93
|
+
{ command: 'clear', description: '清除对话历史' },
|
|
94
|
+
{ command: 'history', description: '查看对话状态' }
|
|
95
|
+
]).catch((err) => {
|
|
96
|
+
log.error(' Failed to register commands:', err.message)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
this._bot.on('message', (msg) => this._handleMessage(msg))
|
|
100
|
+
this._bot.on('edit_message', (msg) => this._handleEdit(msg))
|
|
101
|
+
this._bot.on('callback_query', (query) => this._handleCallback(query))
|
|
102
|
+
this._bot.on('polling_error', (err) => log.error(' Polling error:', err.message))
|
|
103
|
+
this._bot.on('error', (err) => log.error(' Bot error:', err.message))
|
|
104
|
+
|
|
105
|
+
if (this._framework) {
|
|
106
|
+
this._framework.on('agent:created', (agent) => {
|
|
107
|
+
log.info(' New agent created:', agent.name)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// 监听统一通知事件
|
|
111
|
+
this._framework.on('notification', async (data) => {
|
|
112
|
+
await this._handleNotification(data)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// 监听 webhook 事件
|
|
116
|
+
this._framework.on('webhook:received', async (data) => {
|
|
117
|
+
await this._handleWebhookNotification(data)
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
} catch (err) {
|
|
121
|
+
log.error(' Failed to initialize bot:', err.message)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 处理统一通知
|
|
127
|
+
*/
|
|
128
|
+
async _handleNotification(data) {
|
|
129
|
+
const { title, message, source, level } = data
|
|
130
|
+
|
|
131
|
+
if (!this._bot) {
|
|
132
|
+
log.warn(' Bot not ready, cannot send notification')
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 确定 chatId
|
|
137
|
+
let chatId = null
|
|
138
|
+
let effectiveSessionId = data.sessionId
|
|
139
|
+
|
|
140
|
+
// 如果没有 sessionId,尝试从执行上下文获取
|
|
141
|
+
if (!effectiveSessionId) {
|
|
142
|
+
const ctx = this._framework.getExecutionContext()
|
|
143
|
+
if (ctx?.sessionId) {
|
|
144
|
+
effectiveSessionId = ctx.sessionId
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (effectiveSessionId && effectiveSessionId.startsWith('telegram_')) {
|
|
149
|
+
chatId = effectiveSessionId.replace('telegram_', '')
|
|
150
|
+
} else if (this._sessionPlugin) {
|
|
151
|
+
// 获取最近的 telegram 会话
|
|
152
|
+
const sessions = this._sessionPlugin.listSessions()
|
|
153
|
+
.filter(s => s.id.startsWith('telegram_'))
|
|
154
|
+
.sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
|
|
155
|
+
if (sessions.length > 0) {
|
|
156
|
+
chatId = sessions[0].id.replace('telegram_', '')
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!chatId) {
|
|
161
|
+
log.warn(' No telegram session found for notification')
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 格式化通知消息
|
|
166
|
+
const levelEmoji = {
|
|
167
|
+
info: 'ℹ️',
|
|
168
|
+
warning: '⚠️',
|
|
169
|
+
success: '✅',
|
|
170
|
+
error: '❌'
|
|
171
|
+
}
|
|
172
|
+
const emoji = levelEmoji[level] || 'ℹ️'
|
|
173
|
+
const notificationText = `${emoji} [${source}] ${title}\n\n${message}`
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
await this._bot.sendMessage(chatId, notificationText)
|
|
177
|
+
log.info(` Notification sent to chat ${chatId}`)
|
|
178
|
+
} catch (err) {
|
|
179
|
+
log.error(` Failed to send notification:`, err.message)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 处理 webhook 通知
|
|
185
|
+
*/
|
|
186
|
+
async _handleWebhookNotification(data) {
|
|
187
|
+
const { data: webhookData, response, sessionId } = data
|
|
188
|
+
|
|
189
|
+
if (!sessionId || !sessionId.startsWith('telegram_')) {
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const chatId = sessionId.replace('telegram_', '')
|
|
194
|
+
const notificationText = `📥 [Webhook 接收]\n\n路径: ${webhookData.path}\n方法: ${webhookData.method}\n\n处理结果: ${response || '处理中...'}`
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
await this._bot.sendMessage(chatId, notificationText)
|
|
198
|
+
log.info(` Webhook notification sent to chat ${chatId}`)
|
|
199
|
+
} catch (err) {
|
|
200
|
+
log.error(` Failed to send webhook notification:`, err.message)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
_getMainAgent() {
|
|
205
|
+
if (this._framework._mainAgent) return this._framework._mainAgent
|
|
206
|
+
const agents = this._framework._agents || []
|
|
207
|
+
return agents.length > 0 ? agents[agents.length - 1] : null
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
_getSessionAgent(chatId) {
|
|
211
|
+
if (this._sessionAgents.has(chatId)) {
|
|
212
|
+
return { agent: this._sessionAgents.get(chatId), sessionId: `telegram_${chatId}` }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const agent = this._framework.createSessionAgent(`telegram_${chatId}`, {
|
|
216
|
+
systemPrompt: this.systemPrompt,
|
|
217
|
+
sharedPrompt: `工作目录: {{WORK_DIR}}`,
|
|
218
|
+
metadata: { WORK_DIR: process.cwd() }
|
|
219
|
+
})
|
|
220
|
+
this._sessionAgents.set(chatId, agent)
|
|
221
|
+
|
|
222
|
+
if (this._sessionPlugin) {
|
|
223
|
+
this._sessionPlugin.getOrCreateSession(`telegram_${chatId}`, {
|
|
224
|
+
metadata: { platform: 'telegram', chatId }
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return { agent, sessionId: `telegram_${chatId}` }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async _handleMessage(msg) {
|
|
232
|
+
if (!msg || !msg.chat) return
|
|
233
|
+
const chatId = msg.chat.id.toString()
|
|
234
|
+
|
|
235
|
+
if (msg.photo) { await this._handlePhoto(msg); return }
|
|
236
|
+
if (msg.document) { await this._handleDocument(msg); return }
|
|
237
|
+
if (!msg.text) return
|
|
238
|
+
|
|
239
|
+
const text = msg.text.trim()
|
|
240
|
+
if (text.startsWith(this.config.prefix)) {
|
|
241
|
+
await this._handleCommand(msg)
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (msg.chat.type === 'group' || msg.chat.type === 'supergroup') {
|
|
246
|
+
if (!this.config.groupMode) return
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!this._checkPermission(chatId)) {
|
|
250
|
+
await this._sendMessage(chatId, '抱歉,您没有权限使用此 Bot。', msg.message_id)
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
await this._processChat(chatId, text, msg.message_id)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async _handleCommand(msg) {
|
|
258
|
+
const chatId = msg.chat.id.toString()
|
|
259
|
+
const text = msg.text.trim()
|
|
260
|
+
const parts = text.split(' ')
|
|
261
|
+
const command = parts[0].substring(1)
|
|
262
|
+
const args = parts.slice(1).join(' ')
|
|
263
|
+
|
|
264
|
+
switch (command.toLowerCase()) {
|
|
265
|
+
case 'start':
|
|
266
|
+
await this._sendMessage(chatId,
|
|
267
|
+
'👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/start - 显示帮助\n/clear - 清除对话历史\n/history - 查看历史消息数',
|
|
268
|
+
msg.message_id)
|
|
269
|
+
break
|
|
270
|
+
case 'clear':
|
|
271
|
+
this._clearSession(chatId)
|
|
272
|
+
await this._sendMessage(chatId, '✅ 对话历史已清除', msg.message_id)
|
|
273
|
+
break
|
|
274
|
+
case 'history':
|
|
275
|
+
if (this._sessionPlugin) {
|
|
276
|
+
const session = this._sessionPlugin.getSession(`telegram_${chatId}`)
|
|
277
|
+
const sessions = this._sessionPlugin.listSessions().filter(s => s.id.startsWith('telegram_'))
|
|
278
|
+
await this._sendMessage(chatId,
|
|
279
|
+
`📊 当前状态\n\n会话数:${sessions.length}\n历史消息:${session?.messages?.length || 0}\n最后活跃:${session?.lastActive?.toLocaleString() || '无'}`,
|
|
280
|
+
msg.message_id)
|
|
281
|
+
} else {
|
|
282
|
+
await this._sendMessage(chatId, '❌ 会话服务不可用', msg.message_id)
|
|
283
|
+
}
|
|
284
|
+
break
|
|
285
|
+
default:
|
|
286
|
+
await this._processChat(chatId, text, msg.message_id)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async _processChat(chatId, text, replyToMessageId) {
|
|
291
|
+
const sessionInfo = this._getSessionAgent(chatId)
|
|
292
|
+
if (!sessionInfo) {
|
|
293
|
+
await this._sendMessage(chatId, '❌ AI 服务未初始化', replyToMessageId)
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const { agent, sessionId } = sessionInfo
|
|
298
|
+
|
|
299
|
+
if (this._sessionPlugin) {
|
|
300
|
+
this._sessionPlugin.addMessage(sessionId, { role: 'user', content: text })
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let thinkingMsg
|
|
304
|
+
try {
|
|
305
|
+
thinkingMsg = await this._bot.sendMessage(chatId, '🤔 思考中...', {
|
|
306
|
+
reply_to_message_id: replyToMessageId
|
|
307
|
+
})
|
|
308
|
+
} catch (err) {
|
|
309
|
+
log.error(' Send thinking error:', err.message)
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
let fullResponse = ''
|
|
315
|
+
for await (const chunk of agent.chatStream(text, { sessionId })) {
|
|
316
|
+
if (chunk.type === 'text' && chunk.text) {
|
|
317
|
+
fullResponse += chunk.text
|
|
318
|
+
if (fullResponse.length % 100 === 0) {
|
|
319
|
+
try {
|
|
320
|
+
await this._bot.editMessageText(`📝 ${escapeMarkdown(fullResponse)}▌`, {
|
|
321
|
+
chat_id: chatId,
|
|
322
|
+
message_id: thinkingMsg.message_id
|
|
323
|
+
})
|
|
324
|
+
} catch (e) { /* ignore */ }
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (this._sessionPlugin) {
|
|
330
|
+
this._sessionPlugin.addMessage(sessionId, { role: 'assistant', content: fullResponse })
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const safeResponse = escapeMarkdown(fullResponse) || '抱歉,我没有收到有效的回复。'
|
|
334
|
+
await this._bot.editMessageText(safeResponse, {
|
|
335
|
+
chat_id: chatId,
|
|
336
|
+
message_id: thinkingMsg.message_id,
|
|
337
|
+
parse_mode: 'MarkdownV2'
|
|
338
|
+
})
|
|
339
|
+
} catch (err) {
|
|
340
|
+
log.error(' Chat error:', err)
|
|
341
|
+
await this._bot.editMessageText(escapeMarkdown(`❌ 发生错误:${err.message}`), {
|
|
342
|
+
chat_id: chatId,
|
|
343
|
+
message_id: thinkingMsg.message_id
|
|
344
|
+
})
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async _handleEdit(msg) { /* 支持编辑后重新处理 */ }
|
|
349
|
+
|
|
350
|
+
async _handleCallback(query) {
|
|
351
|
+
await this._bot.answerCallbackQuery(query.id, { text: '处理中...' })
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
_checkPermission(chatId) {
|
|
355
|
+
return this.config.allowedChats.length === 0 || this.config.allowedChats.includes(chatId)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async _handlePhoto(msg) {
|
|
359
|
+
const chatId = msg.chat.id.toString()
|
|
360
|
+
if (!this._checkPermission(chatId)) {
|
|
361
|
+
await this._sendMessage(chatId, '抱歉,您没有权限使用此 Bot。', msg.message_id)
|
|
362
|
+
return
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const caption = msg.caption?.trim() || ''
|
|
366
|
+
const photo = msg.photo[msg.photo.length - 1]
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const filePath = await this._downloadFile(photo.file_id, '.jpg', 'telegram_images')
|
|
370
|
+
await this._sendMessage(chatId, `收到图片${caption ? ': ' + caption : ''}\n已保存至: ${filePath}`, msg.message_id)
|
|
371
|
+
if (caption) {
|
|
372
|
+
await this._processChat(chatId, `图片:${filePath}, ${caption}`, msg.message_id)
|
|
373
|
+
}
|
|
374
|
+
} catch (err) {
|
|
375
|
+
log.error(' Failed to save photo:', err.message)
|
|
376
|
+
await this._sendMessage(chatId, '图片保存失败: ' + err.message, msg.message_id)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async _handleDocument(msg) {
|
|
381
|
+
const chatId = msg.chat.id.toString()
|
|
382
|
+
if (!this._checkPermission(chatId)) {
|
|
383
|
+
await this._sendMessage(chatId, '抱歉,您没有权限使用此 Bot。', msg.message_id)
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const fileName = msg.document.file_name || '未命名文件'
|
|
388
|
+
const caption = msg.caption?.trim() || ''
|
|
389
|
+
const ext = fileName.includes('.') ? fileName.split('.').pop() : 'bin'
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
const filePath = await this._downloadFile(msg.document.file_id, ext, 'telegram_documents')
|
|
393
|
+
await this._sendMessage(chatId, `收到文件: ${fileName}\n已保存至: ${filePath}${caption ? '\n\n说明: ' + caption : ''}`, msg.message_id)
|
|
394
|
+
if (caption) {
|
|
395
|
+
await this._processChat(chatId, `文件:${filePath}, ${caption}`, msg.message_id)
|
|
396
|
+
}
|
|
397
|
+
} catch (err) {
|
|
398
|
+
log.error(' Failed to save document:', err.message)
|
|
399
|
+
await this._sendMessage(chatId, '文件保存失败: ' + err.message, msg.message_id)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async _downloadFile(fileId, ext, subDir) {
|
|
404
|
+
const path = require('path')
|
|
405
|
+
const fs = require('fs')
|
|
406
|
+
const saveDir = path.join(process.cwd(), '.agent', 'data', subDir)
|
|
407
|
+
if (!fs.existsSync(saveDir)) fs.mkdirSync(saveDir, { recursive: true })
|
|
408
|
+
|
|
409
|
+
const fileName = `${Date.now()}_${Math.random().toString(36).substring(7)}.${ext}`
|
|
410
|
+
const filePath = path.join(saveDir, fileName)
|
|
411
|
+
|
|
412
|
+
const savedPath = await this._bot.downloadFile(fileId, saveDir)
|
|
413
|
+
if (savedPath !== filePath) fs.renameSync(savedPath, filePath)
|
|
414
|
+
return filePath
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
_clearSession(chatId) {
|
|
418
|
+
if (this._sessionPlugin) {
|
|
419
|
+
this._sessionPlugin.deleteSession(`telegram_${chatId}`)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async _sendMessage(chatId, text, replyToMessageId = null) {
|
|
424
|
+
if (!this._bot) return
|
|
425
|
+
return this._bot.sendMessage(chatId, text, { reply_to_message_id: replyToMessageId })
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
getStatus() {
|
|
429
|
+
let sessions = []
|
|
430
|
+
if (this._sessionPlugin) {
|
|
431
|
+
sessions = this._sessionPlugin.listSessions()
|
|
432
|
+
.filter(s => s.id.startsWith('telegram_'))
|
|
433
|
+
.map(s => ({
|
|
434
|
+
chatId: s.id.replace('telegram_', ''),
|
|
435
|
+
historyLength: s.messageCount,
|
|
436
|
+
lastActive: s.lastActive
|
|
437
|
+
}))
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
connected: !!this._bot,
|
|
441
|
+
sessionCount: sessions.length,
|
|
442
|
+
sessions,
|
|
443
|
+
config: {
|
|
444
|
+
groupMode: this.config.groupMode,
|
|
445
|
+
prefix: this.config.prefix,
|
|
446
|
+
allowedChatsCount: this.config.allowedChats.length
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
stopBot() {
|
|
452
|
+
if (this._bot) {
|
|
453
|
+
this._bot.stopPolling()
|
|
454
|
+
this._bot = null
|
|
455
|
+
log.info(' Bot stopped')
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
reload(framework) {
|
|
460
|
+
this._framework = framework
|
|
461
|
+
if (this.config.botToken) {
|
|
462
|
+
this.stopBot()
|
|
463
|
+
this._initBot()
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
uninstall() {
|
|
468
|
+
for (const agent of this._sessionAgents.values()) {
|
|
469
|
+
agent.destroy()
|
|
470
|
+
}
|
|
471
|
+
this._sessionAgents.clear()
|
|
472
|
+
this.stopBot()
|
|
473
|
+
if (this._sessionPlugin && this._sessionDeleteHandler) {
|
|
474
|
+
this._sessionPlugin.off('session:deleted', this._sessionDeleteHandler)
|
|
475
|
+
this._sessionDeleteHandler = null
|
|
476
|
+
}
|
|
477
|
+
this._sessionPlugin = null
|
|
478
|
+
this._framework = null
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
module.exports = { TelegramPlugin }
|