@ebowwa/channel-telegram 1.12.5 → 1.13.0
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/README.md +78 -44
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +3 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/restart.d.ts +7 -0
- package/dist/commands/restart.d.ts.map +1 -0
- package/dist/commands/restart.js +29 -0
- package/dist/commands/restart.js.map +1 -0
- package/dist/commands/settings.d.ts +8 -0
- package/dist/commands/settings.d.ts.map +1 -0
- package/dist/commands/settings.js +16 -0
- package/dist/commands/settings.js.map +1 -0
- package/dist/index.d.ts +83 -29
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +350 -712
- package/dist/index.js.map +1 -1
- package/package.json +9 -13
- package/src/commands/index.ts +3 -0
- package/src/commands/restart.ts +41 -0
- package/src/commands/settings.ts +24 -0
- package/src/index.ts +415 -823
- package/dist/mcp/client.d.ts +0 -50
- package/dist/mcp/client.d.ts.map +0 -1
- package/dist/mcp/client.js +0 -150
- package/dist/mcp/client.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -5
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js +0 -5
- package/dist/mcp/index.js.map +0 -1
- package/src/api/fetch-retry.js +0 -96
- package/src/api/keys.js +0 -25
- package/src/commands/cancel.js +0 -120
- package/src/commands/clear.js +0 -59
- package/src/commands/doppler.js +0 -118
- package/src/commands/git.js +0 -126
- package/src/commands/help.js +0 -74
- package/src/commands/index.js +0 -65
- package/src/commands/logs.js +0 -81
- package/src/commands/pause.js +0 -133
- package/src/commands/resources.js +0 -87
- package/src/commands/resume.js +0 -95
- package/src/commands/start.js +0 -68
- package/src/commands/status.js +0 -62
- package/src/commands/tools.js +0 -67
- package/src/commands/toolsoutput.js +0 -85
- package/src/commands/types.js +0 -5
- package/src/mcp/client.ts +0 -188
- package/src/mcp/index.ts +0 -5
package/dist/index.js
CHANGED
|
@@ -1,335 +1,181 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* @ebowwa/channel-telegram
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Telegram channel adapter implementing ChannelConnector.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
6
|
+
* This package handles:
|
|
7
|
+
* - Telegram protocol operations (polling, message delivery)
|
|
8
|
+
* - Built-in commands (/start, /help, /status, etc.)
|
|
9
|
+
* - Typing indicators, message chunking, access control
|
|
10
|
+
* - Message normalization to ChannelMessage format
|
|
11
|
+
*
|
|
12
|
+
* Intelligence (GLM, tools, memory) can be provided by:
|
|
13
|
+
* 1. This package (standalone mode with built-in tools)
|
|
14
|
+
* 2. External daemon/consumer (adapter mode via onMessage handler)
|
|
12
15
|
*/
|
|
13
|
-
import TelegramBot from
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
16
|
+
import TelegramBot from "node-telegram-bot-api";
|
|
17
|
+
import { createChannelId, } from "@ebowwa/channel-types";
|
|
18
|
+
import { ConversationMemory } from "./conversation-memory.js";
|
|
19
|
+
import { registerAllCommands } from "./commands/index.js";
|
|
20
|
+
// Re-export for external use
|
|
21
|
+
export { ConversationMemory } from "./conversation-memory.js";
|
|
22
|
+
export { registerAllCommands, isQuiet } from "./commands/index.js";
|
|
23
|
+
export function createTelegramConfigFromEnv() {
|
|
24
|
+
const allowedUsers = process.env.TELEGRAM_ALLOWED_USERS
|
|
25
|
+
?.split(",")
|
|
26
|
+
.map((s) => parseInt(s.trim(), 10))
|
|
27
|
+
.filter((n) => !isNaN(n));
|
|
28
|
+
const allowedChats = process.env.TELEGRAM_ALLOWED_CHATS
|
|
29
|
+
?.split(",")
|
|
30
|
+
.map((s) => parseInt(s.trim(), 10))
|
|
31
|
+
.filter((n) => !isNaN(n));
|
|
32
|
+
return {
|
|
33
|
+
botToken: process.env.TELEGRAM_BOT_TOKEN || "",
|
|
34
|
+
testChatId: process.env.TELEGRAM_TEST_CHAT_ID,
|
|
35
|
+
allowedUsers,
|
|
36
|
+
allowedChats,
|
|
37
|
+
conversationsFile: process.env.CONVERSATIONS_FILE,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// ============================================================
|
|
41
|
+
// TELEGRAM CHANNEL
|
|
42
|
+
// ============================================================
|
|
43
|
+
export class TelegramChannel {
|
|
44
|
+
id;
|
|
45
|
+
label = "Telegram";
|
|
46
|
+
capabilities = {
|
|
47
|
+
supports: {
|
|
48
|
+
text: true,
|
|
49
|
+
media: true,
|
|
50
|
+
replies: true,
|
|
51
|
+
threads: false,
|
|
52
|
+
reactions: true,
|
|
53
|
+
editing: true,
|
|
54
|
+
streaming: false,
|
|
36
55
|
},
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (!existsSync(path))
|
|
41
|
-
return `File not found: ${path}`;
|
|
42
|
-
const content = readFileSync(path, 'utf-8');
|
|
43
|
-
return content.length > 4000 ? content.slice(0, 4000) + '\n...[truncated]' : content;
|
|
44
|
-
}
|
|
45
|
-
catch (e) {
|
|
46
|
-
return `Error reading file: ${e.message}`;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
name: 'write_file',
|
|
52
|
-
description: 'Write content to a file. Creates the file if it does not exist, overwrites if it does.',
|
|
53
|
-
parameters: {
|
|
54
|
-
type: 'object',
|
|
55
|
-
properties: {
|
|
56
|
-
path: { type: 'string', description: 'Absolute file path to write' },
|
|
57
|
-
content: { type: 'string', description: 'Content to write to the file' }
|
|
58
|
-
},
|
|
59
|
-
required: ['path', 'content']
|
|
56
|
+
media: {
|
|
57
|
+
maxFileSize: 50 * 1024 * 1024, // 50MB
|
|
58
|
+
supportedMimeTypes: ["image/*", "video/*", "audio/*", "application/pdf"],
|
|
60
59
|
},
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
writeFileSync(path, content, 'utf-8');
|
|
66
|
-
return `Successfully wrote ${content.length} bytes to ${path}`;
|
|
67
|
-
}
|
|
68
|
-
catch (e) {
|
|
69
|
-
return `Error writing file: ${e.message}`;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
name: 'edit_file',
|
|
75
|
-
description: 'Edit a file by replacing a specific string with new content.',
|
|
76
|
-
parameters: {
|
|
77
|
-
type: 'object',
|
|
78
|
-
properties: {
|
|
79
|
-
path: { type: 'string', description: 'Absolute file path to edit' },
|
|
80
|
-
old_string: { type: 'string', description: 'The exact text to find and replace' },
|
|
81
|
-
new_string: { type: 'string', description: 'The text to replace it with' }
|
|
82
|
-
},
|
|
83
|
-
required: ['path', 'old_string', 'new_string']
|
|
60
|
+
rateLimits: {
|
|
61
|
+
messagesPerMinute: 30,
|
|
62
|
+
charactersPerMessage: 4096,
|
|
84
63
|
},
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
},
|
|
114
|
-
required: ['path', 'content']
|
|
115
|
-
},
|
|
116
|
-
handler: async (args) => {
|
|
117
|
-
const path = args.path;
|
|
118
|
-
const content = args.content;
|
|
119
|
-
try {
|
|
120
|
-
appendFileSync(path, content, 'utf-8');
|
|
121
|
-
return `Successfully appended ${content.length} bytes to ${path}`;
|
|
122
|
-
}
|
|
123
|
-
catch (e) {
|
|
124
|
-
return `Error appending to file: ${e.message}`;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
name: 'list_dir',
|
|
130
|
-
description: 'List contents of a directory. Shows files and subdirectories.',
|
|
131
|
-
parameters: {
|
|
132
|
-
type: 'object',
|
|
133
|
-
properties: {
|
|
134
|
-
path: { type: 'string', description: 'Directory path to list (default: current working directory)' }
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
handler: async (args) => {
|
|
138
|
-
const path = args.path || process.cwd();
|
|
139
|
-
try {
|
|
140
|
-
const items = execSync(`ls -la "${path}" 2>&1`).toString();
|
|
141
|
-
return items;
|
|
142
|
-
}
|
|
143
|
-
catch (e) {
|
|
144
|
-
return `Error listing directory: ${e.message}`;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
name: 'run_command',
|
|
150
|
-
description: 'Execute a shell command. Use for git, system info, etc. BE CAREFUL with destructive commands.',
|
|
151
|
-
parameters: {
|
|
152
|
-
type: 'object',
|
|
153
|
-
properties: {
|
|
154
|
-
command: { type: 'string', description: 'Shell command to execute' },
|
|
155
|
-
cwd: { type: 'string', description: 'Working directory (optional)' }
|
|
64
|
+
};
|
|
65
|
+
bot;
|
|
66
|
+
config;
|
|
67
|
+
memory;
|
|
68
|
+
tools;
|
|
69
|
+
messageHandler;
|
|
70
|
+
typingIntervals = new Map();
|
|
71
|
+
connected = false;
|
|
72
|
+
constructor(config) {
|
|
73
|
+
this.config = config;
|
|
74
|
+
this.id = createChannelId("telegram", "default");
|
|
75
|
+
this.bot = new TelegramBot(config.botToken, { polling: false });
|
|
76
|
+
this.memory = new ConversationMemory(config.conversationsFile);
|
|
77
|
+
this.tools = config.tools || [];
|
|
78
|
+
}
|
|
79
|
+
// ============================================================
|
|
80
|
+
// ChannelConnector Implementation
|
|
81
|
+
// ============================================================
|
|
82
|
+
async start() {
|
|
83
|
+
console.log("[TelegramChannel] Starting...");
|
|
84
|
+
// Start polling
|
|
85
|
+
this.bot = new TelegramBot(this.config.botToken, {
|
|
86
|
+
polling: {
|
|
87
|
+
interval: 300,
|
|
88
|
+
autoStart: true,
|
|
89
|
+
params: {
|
|
90
|
+
allowed_updates: ["message", "edited_message", "message_reaction", "callback_query"],
|
|
91
|
+
},
|
|
156
92
|
},
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return result || '(no output)';
|
|
170
|
-
}
|
|
171
|
-
catch (e) {
|
|
172
|
-
return e.stdout?.toString() || e.message;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
name: 'git_status',
|
|
178
|
-
description: 'Check git repository status',
|
|
179
|
-
parameters: {
|
|
180
|
-
type: 'object',
|
|
181
|
-
properties: {
|
|
182
|
-
cwd: { type: 'string', description: 'Repository path (optional)' }
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
handler: async (args) => {
|
|
186
|
-
const cwd = args.cwd || process.cwd();
|
|
187
|
-
try {
|
|
188
|
-
const status = execSync('git status 2>&1', { cwd }).toString();
|
|
189
|
-
const branch = execSync('git branch --show-current 2>&1', { cwd }).toString();
|
|
190
|
-
return `Branch: ${branch}\n\n${status}`;
|
|
191
|
-
}
|
|
192
|
-
catch (e) {
|
|
193
|
-
return `Error: ${e.message}`;
|
|
194
|
-
}
|
|
93
|
+
});
|
|
94
|
+
// Register built-in commands
|
|
95
|
+
registerAllCommands(this.bot, this.memory, this.tools);
|
|
96
|
+
// Setup message routing
|
|
97
|
+
this.setupMessageHandlers();
|
|
98
|
+
// Register Telegram commands menu
|
|
99
|
+
await this.registerCommands();
|
|
100
|
+
this.connected = true;
|
|
101
|
+
console.log("[TelegramChannel] Connected and polling");
|
|
102
|
+
// Send startup notification if configured
|
|
103
|
+
if (this.config.testChatId) {
|
|
104
|
+
await this.sendMessage(Number(this.config.testChatId), "Telegram channel adapter online");
|
|
195
105
|
}
|
|
196
|
-
}
|
|
197
|
-
{
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
106
|
+
}
|
|
107
|
+
async stop() {
|
|
108
|
+
console.log("[TelegramChannel] Stopping...");
|
|
109
|
+
await this.bot.stopPolling();
|
|
110
|
+
this.connected = false;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Set the message handler. The daemon/consumer provides intelligence.
|
|
114
|
+
*/
|
|
115
|
+
onMessage(handler) {
|
|
116
|
+
this.messageHandler = handler;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Send a response back to Telegram.
|
|
120
|
+
*/
|
|
121
|
+
async send(response) {
|
|
122
|
+
const chatId = this.extractChatId(response.replyTo);
|
|
123
|
+
if (!chatId) {
|
|
124
|
+
console.error("[TelegramChannel] Cannot send: no chat ID");
|
|
125
|
+
return;
|
|
211
126
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
description: 'List all available prompt templates',
|
|
216
|
-
parameters: { type: 'object', properties: {} },
|
|
217
|
-
handler: async () => {
|
|
218
|
-
const store = getStore(process.env.PROMPTS_FILE);
|
|
219
|
-
const prompts = store.list();
|
|
220
|
-
return prompts.map(p => `- ${p.id}: ${p.name}`).join('\n') || 'No prompts found';
|
|
127
|
+
const options = {};
|
|
128
|
+
if (response.content.replyToOriginal) {
|
|
129
|
+
options.reply_to_message_id = parseInt(response.replyTo.messageId, 10);
|
|
221
130
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
name: 'system_info',
|
|
225
|
-
description: 'Get system resource info (CPU, memory, disk, uptime)',
|
|
226
|
-
parameters: { type: 'object', properties: {} },
|
|
227
|
-
handler: async () => {
|
|
228
|
-
try {
|
|
229
|
-
const cpu = execSync('nproc 2>/dev/null || echo "unknown"').toString().trim();
|
|
230
|
-
const mem = execSync('free -h 2>/dev/null | grep Mem || echo "unknown"').toString().trim();
|
|
231
|
-
const disk = execSync('df -h / 2>/dev/null | tail -1 || echo "unknown"').toString().trim();
|
|
232
|
-
const uptime = execSync('uptime -p 2>/dev/null || uptime 2>/dev/null || echo "unknown"').toString().trim();
|
|
233
|
-
return `CPU cores: ${cpu}\nMemory: ${mem}\nDisk: ${disk}\nUptime: ${uptime}`;
|
|
234
|
-
}
|
|
235
|
-
catch (e) {
|
|
236
|
-
return `Error: ${e.message}`;
|
|
237
|
-
}
|
|
131
|
+
if (response.content.options?.parseMode) {
|
|
132
|
+
options.parse_mode = response.content.options.parseMode;
|
|
238
133
|
}
|
|
134
|
+
await this.sendLongMessage(chatId, response.content.text, options);
|
|
239
135
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
function getGLMTools() {
|
|
243
|
-
const allTools = [...TOOLS.map(t => ({
|
|
244
|
-
type: 'function',
|
|
245
|
-
function: {
|
|
246
|
-
name: t.name,
|
|
247
|
-
description: t.description,
|
|
248
|
-
parameters: t.parameters
|
|
249
|
-
}
|
|
250
|
-
}))];
|
|
251
|
-
// Add MCP tools if connected
|
|
252
|
-
if (mcpClient) {
|
|
253
|
-
const mcpTools = mcpClient.getTools();
|
|
254
|
-
for (const tool of mcpTools) {
|
|
255
|
-
allTools.push({
|
|
256
|
-
type: 'function',
|
|
257
|
-
function: {
|
|
258
|
-
name: tool.name,
|
|
259
|
-
description: tool.description,
|
|
260
|
-
parameters: tool.inputSchema
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
}
|
|
136
|
+
isConnected() {
|
|
137
|
+
return this.connected;
|
|
264
138
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
parameters: t.parameters
|
|
139
|
+
// ============================================================
|
|
140
|
+
// Public Helpers
|
|
141
|
+
// ============================================================
|
|
142
|
+
/**
|
|
143
|
+
* Get the underlying TelegramBot instance for advanced operations.
|
|
144
|
+
*/
|
|
145
|
+
getBot() {
|
|
146
|
+
return this.bot;
|
|
274
147
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if (tool) {
|
|
281
|
-
return tool.handler(args);
|
|
148
|
+
/**
|
|
149
|
+
* Get the conversation memory.
|
|
150
|
+
*/
|
|
151
|
+
getMemory() {
|
|
152
|
+
return this.memory;
|
|
282
153
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
try {
|
|
289
|
-
return await mcpClient.callTool(name, args);
|
|
290
|
-
}
|
|
291
|
-
catch (error) {
|
|
292
|
-
return `MCP tool error: ${error.message}`;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
154
|
+
/**
|
|
155
|
+
* Send a simple text message.
|
|
156
|
+
*/
|
|
157
|
+
async sendMessage(chatId, text, options) {
|
|
158
|
+
await this.bot.sendMessage(chatId, text, options);
|
|
295
159
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
memory;
|
|
302
|
-
typingIntervals = new Map();
|
|
303
|
-
constructor(token) {
|
|
304
|
-
// Enable polling with all required update types
|
|
305
|
-
this.bot = new TelegramBot(token, {
|
|
306
|
-
polling: {
|
|
307
|
-
interval: 300,
|
|
308
|
-
autoStart: true,
|
|
309
|
-
params: {
|
|
310
|
-
allowed_updates: ['message', 'edited_message', 'message_reaction', 'callback_query']
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
this.memory = new ConversationMemory(process.env.CONVERSATIONS_FILE);
|
|
160
|
+
/**
|
|
161
|
+
* Send typing indicator.
|
|
162
|
+
*/
|
|
163
|
+
async sendTyping(chatId) {
|
|
164
|
+
await this.bot.sendChatAction(chatId, "typing");
|
|
315
165
|
}
|
|
316
166
|
/**
|
|
317
|
-
* Start
|
|
318
|
-
* Telegram typing indicator lasts ~5 seconds, so refresh every 3 seconds
|
|
167
|
+
* Start continuous typing indicator (until response is ready).
|
|
319
168
|
*/
|
|
320
169
|
startTypingIndicator(chatId) {
|
|
321
|
-
// Clear any existing interval
|
|
322
170
|
this.stopTypingIndicator(chatId);
|
|
323
|
-
|
|
324
|
-
this.bot.sendChatAction(chatId, 'typing').catch(() => { });
|
|
325
|
-
// Set up periodic refresh every 3 seconds
|
|
171
|
+
this.bot.sendChatAction(chatId, "typing").catch(() => { });
|
|
326
172
|
const interval = setInterval(() => {
|
|
327
|
-
this.bot.sendChatAction(chatId,
|
|
173
|
+
this.bot.sendChatAction(chatId, "typing").catch(() => { });
|
|
328
174
|
}, 3000);
|
|
329
175
|
this.typingIntervals.set(chatId, interval);
|
|
330
176
|
}
|
|
331
177
|
/**
|
|
332
|
-
* Stop
|
|
178
|
+
* Stop typing indicator.
|
|
333
179
|
*/
|
|
334
180
|
stopTypingIndicator(chatId) {
|
|
335
181
|
const interval = this.typingIntervals.get(chatId);
|
|
@@ -339,456 +185,248 @@ export class TelegramGLMBot {
|
|
|
339
185
|
}
|
|
340
186
|
}
|
|
341
187
|
/**
|
|
342
|
-
*
|
|
188
|
+
* Check if user/chat is allowed.
|
|
343
189
|
*/
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
await this.bot.sendMessage(chatId, text);
|
|
348
|
-
return;
|
|
190
|
+
isAllowed(userId, chatId) {
|
|
191
|
+
if (!this.config.allowedUsers?.length && !this.config.allowedChats?.length) {
|
|
192
|
+
return true;
|
|
349
193
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
while (remaining.length > 0) {
|
|
354
|
-
if (remaining.length <= MAX_LENGTH) {
|
|
355
|
-
chunks.push(remaining);
|
|
356
|
-
break;
|
|
357
|
-
}
|
|
358
|
-
// Try to find a good break point (newline or space)
|
|
359
|
-
let breakPoint = remaining.lastIndexOf('\n', MAX_LENGTH);
|
|
360
|
-
if (breakPoint < 1000) {
|
|
361
|
-
breakPoint = remaining.lastIndexOf(' ', MAX_LENGTH);
|
|
362
|
-
}
|
|
363
|
-
if (breakPoint < 1000) {
|
|
364
|
-
breakPoint = MAX_LENGTH;
|
|
365
|
-
}
|
|
366
|
-
chunks.push(remaining.slice(0, breakPoint));
|
|
367
|
-
remaining = remaining.slice(breakPoint).trim();
|
|
194
|
+
if (this.config.allowedUsers?.length && userId) {
|
|
195
|
+
if (this.config.allowedUsers.includes(userId))
|
|
196
|
+
return true;
|
|
368
197
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
await this.bot.sendMessage(chatId, prefix + chunks[i]);
|
|
198
|
+
if (this.config.allowedChats?.length && chatId) {
|
|
199
|
+
if (this.config.allowedChats.includes(chatId))
|
|
200
|
+
return true;
|
|
373
201
|
}
|
|
202
|
+
return false;
|
|
374
203
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
catch (error) {
|
|
383
|
-
console.error('[MCP] Failed to initialize MCP client:', error);
|
|
384
|
-
// Continue without MCP - local tools still work
|
|
385
|
-
}
|
|
386
|
-
// Register all commands from commands/ directory
|
|
387
|
-
registerAllCommands(this.bot, this.memory, TOOLS);
|
|
388
|
-
// Register commands with Telegram for autocomplete
|
|
389
|
-
await this.bot.setMyCommands([
|
|
390
|
-
{ command: 'start', description: 'Start the bot' },
|
|
391
|
-
{ command: 'help', description: 'Show available commands' },
|
|
392
|
-
{ command: 'status', description: 'Check bot and API status' },
|
|
393
|
-
{ command: 'git', description: 'Check git status and GitHub auth' },
|
|
394
|
-
{ command: 'doppler', description: 'Check Doppler secrets status' },
|
|
395
|
-
{ command: 'logs', description: 'View recent bot logs' },
|
|
396
|
-
{ command: 'clear', description: 'Clear conversation history' },
|
|
397
|
-
{ command: 'tools', description: 'List available tools' },
|
|
398
|
-
{ command: 'cancel', description: 'Stop current task immediately' },
|
|
399
|
-
{ command: 'pause', description: 'Pause execution' },
|
|
400
|
-
{ command: 'resume', description: 'Resume after pause/cancel' },
|
|
401
|
-
{ command: 'quiet', description: 'Hide tool logging' },
|
|
402
|
-
{ command: 'verbose', description: 'Show tool logging' },
|
|
403
|
-
]);
|
|
404
|
-
// Handle all text messages with GLM-4.7
|
|
405
|
-
this.bot.on('message', async (msg) => {
|
|
406
|
-
if (!msg.text)
|
|
204
|
+
// ============================================================
|
|
205
|
+
// Message Routing (Internal)
|
|
206
|
+
// ============================================================
|
|
207
|
+
setupMessageHandlers() {
|
|
208
|
+
// Text messages (skip commands - handled by onText handlers)
|
|
209
|
+
this.bot.on("message", async (msg) => {
|
|
210
|
+
if (!msg.text || msg.text.startsWith("/"))
|
|
407
211
|
return;
|
|
408
|
-
if (msg.
|
|
212
|
+
if (!this.isAllowed(msg.from?.id, msg.chat.id))
|
|
409
213
|
return;
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
console.log(`[Telegram] ${userName}: ${msg.text}`);
|
|
425
|
-
}
|
|
426
|
-
// Build message with reply context if present
|
|
427
|
-
let messageWithContext = msg.text;
|
|
428
|
-
if (replyContext) {
|
|
429
|
-
messageWithContext = `[Replying to ${replyContext.originalFrom}'s message: "${replyContext.originalMessage.slice(0, 200)}${replyContext.originalMessage.length > 200 ? '...' : ''}"]\n\n${msg.text}`;
|
|
430
|
-
}
|
|
431
|
-
// Typing indicator is now handled by getGLMResponse with periodic refresh
|
|
432
|
-
// Get response with conversation history
|
|
433
|
-
const response = await this.getGLMResponse(chatId, messageWithContext, userName);
|
|
434
|
-
// Save to memory with message ID and reply context
|
|
435
|
-
this.memory.addWithReply(chatId, 'user', msg.text, {
|
|
436
|
-
messageId,
|
|
437
|
-
replyTo: replyContext ? {
|
|
438
|
-
messageId: replyContext.originalMessageId,
|
|
439
|
-
text: replyContext.originalMessage,
|
|
440
|
-
from: replyContext.originalFrom
|
|
441
|
-
} : undefined
|
|
442
|
-
});
|
|
443
|
-
this.memory.add(chatId, 'assistant', response);
|
|
444
|
-
// Send response - if replying, use reply_to_message_id for threading
|
|
445
|
-
if (replyContext) {
|
|
446
|
-
await this.bot.sendMessage(chatId, response, {
|
|
447
|
-
reply_to_message_id: messageId
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
await this.sendLongMessage(chatId, response);
|
|
214
|
+
const message = this.createChannelMessage(msg);
|
|
215
|
+
console.log(`[TelegramChannel] Message from ${message.sender.displayName}: ${message.text.slice(0, 50)}...`);
|
|
216
|
+
// Route to handler if set
|
|
217
|
+
if (this.messageHandler) {
|
|
218
|
+
try {
|
|
219
|
+
const response = await this.messageHandler(message);
|
|
220
|
+
if (response) {
|
|
221
|
+
await this.send(response);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
console.error("[TelegramChannel] Handler error:", error);
|
|
226
|
+
await this.sendMessage(msg.chat.id, `Error: ${error.message}`);
|
|
227
|
+
}
|
|
452
228
|
}
|
|
453
229
|
});
|
|
454
|
-
//
|
|
455
|
-
this.bot.on(
|
|
456
|
-
console.error('[Telegram] Polling error:', error.message);
|
|
457
|
-
});
|
|
458
|
-
// ====================================================================
|
|
459
|
-
// Message Edit Handler
|
|
460
|
-
// ====================================================================
|
|
461
|
-
this.bot.on('edited_message', async (msg) => {
|
|
230
|
+
// Edited messages
|
|
231
|
+
this.bot.on("edited_message", async (msg) => {
|
|
462
232
|
if (!msg.text)
|
|
463
233
|
return;
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
console.log(`[Telegram] Edit for unknown message ${messageId}`);
|
|
234
|
+
if (!this.isAllowed(msg.from?.id, msg.chat.id))
|
|
235
|
+
return;
|
|
236
|
+
const message = this.createChannelMessage(msg);
|
|
237
|
+
// Mark as edited in text
|
|
238
|
+
message.text = `[Edited] ${msg.text}`;
|
|
239
|
+
if (this.messageHandler) {
|
|
240
|
+
try {
|
|
241
|
+
const response = await this.messageHandler(message);
|
|
242
|
+
if (response) {
|
|
243
|
+
await this.send(response);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
console.error("[TelegramChannel] Handler error:", error);
|
|
248
|
+
}
|
|
480
249
|
}
|
|
481
250
|
});
|
|
482
|
-
//
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
.
|
|
498
|
-
.map(r => r.emoji);
|
|
499
|
-
// Find added reactions
|
|
500
|
-
const added = newEmojis.filter(e => !oldEmojis.includes(e));
|
|
501
|
-
const removed = oldEmojis.filter(e => !newEmojis.includes(e));
|
|
502
|
-
console.log(`[Telegram] REACTION by ${userName}: +${added.join(',')} -${removed.join(',')} on msg ${messageId}`);
|
|
503
|
-
// Update memory for each added reaction
|
|
504
|
-
for (const emoji of added) {
|
|
505
|
-
this.memory.addReaction(chatId, messageId, emoji, userId);
|
|
506
|
-
}
|
|
507
|
-
// Update memory for each removed reaction
|
|
508
|
-
for (const emoji of removed) {
|
|
509
|
-
this.memory.removeReaction(chatId, messageId, emoji, userId);
|
|
510
|
-
}
|
|
511
|
-
// Optionally respond to specific reactions
|
|
512
|
-
if (added.includes('👍') || added.includes('❤️')) {
|
|
513
|
-
// Positive feedback - could track for learning
|
|
514
|
-
console.log(`[Telegram] Positive feedback received on message ${messageId}`);
|
|
515
|
-
}
|
|
516
|
-
if (added.includes('👎')) {
|
|
517
|
-
// Negative feedback - could ask for clarification
|
|
518
|
-
await this.bot.sendMessage(chatId, `Thanks for the feedback, ${userName}. I'll try to do better!`);
|
|
251
|
+
// Reactions
|
|
252
|
+
this.bot.on("message_reaction", async (reaction) => {
|
|
253
|
+
const message = {
|
|
254
|
+
messageId: reaction.message_id?.toString(),
|
|
255
|
+
channelId: this.id,
|
|
256
|
+
timestamp: new Date(),
|
|
257
|
+
sender: {
|
|
258
|
+
id: reaction.user?.id?.toString(),
|
|
259
|
+
displayName: reaction.user?.first_name || "User",
|
|
260
|
+
},
|
|
261
|
+
text: "",
|
|
262
|
+
context: { isDM: reaction.chat?.type === "private" },
|
|
263
|
+
metadata: { type: "reaction", reaction: reaction.new_reaction },
|
|
264
|
+
};
|
|
265
|
+
if (this.messageHandler) {
|
|
266
|
+
await this.messageHandler(message);
|
|
519
267
|
}
|
|
520
268
|
});
|
|
521
|
-
//
|
|
522
|
-
|
|
523
|
-
// ====================================================================
|
|
524
|
-
this.bot.on('callback_query', async (query) => {
|
|
269
|
+
// Callback queries (button clicks)
|
|
270
|
+
this.bot.on("callback_query", async (query) => {
|
|
525
271
|
const chatId = query.message?.chat.id;
|
|
526
|
-
|
|
527
|
-
const data = query.data;
|
|
528
|
-
const userName = query.from.first_name || 'User';
|
|
529
|
-
if (!chatId || !data) {
|
|
272
|
+
if (!chatId || !query.data) {
|
|
530
273
|
await this.bot.answerCallbackQuery(query.id);
|
|
531
274
|
return;
|
|
532
275
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
// Could trigger follow-up actions here
|
|
552
|
-
break;
|
|
553
|
-
}
|
|
554
|
-
case 'reject': {
|
|
555
|
-
// Rejection action
|
|
556
|
-
await this.bot.sendMessage(chatId, `❌ Rejected by ${userName}`);
|
|
557
|
-
break;
|
|
558
|
-
}
|
|
559
|
-
case 'cancel': {
|
|
560
|
-
// Cancel current task
|
|
561
|
-
await this.bot.sendMessage(chatId, `🛑 Task cancelled by ${userName}`);
|
|
562
|
-
break;
|
|
276
|
+
const message = {
|
|
277
|
+
messageId: query.id,
|
|
278
|
+
channelId: this.id,
|
|
279
|
+
timestamp: new Date(),
|
|
280
|
+
sender: {
|
|
281
|
+
id: query.from?.id?.toString(),
|
|
282
|
+
displayName: query.from?.first_name || "User",
|
|
283
|
+
},
|
|
284
|
+
text: query.data,
|
|
285
|
+
context: { isDM: query.message?.chat.type === "private" },
|
|
286
|
+
metadata: { type: "callback" },
|
|
287
|
+
};
|
|
288
|
+
if (this.messageHandler) {
|
|
289
|
+
try {
|
|
290
|
+
const response = await this.messageHandler(message);
|
|
291
|
+
if (response) {
|
|
292
|
+
await this.send(response);
|
|
293
|
+
}
|
|
563
294
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const response = await this.getGLMResponse(chatId, `[User clicked button: "${data}". Respond appropriately.]`, userName);
|
|
567
|
-
await this.bot.sendMessage(chatId, response);
|
|
295
|
+
catch (error) {
|
|
296
|
+
console.error("[TelegramChannel] Callback handler error:", error);
|
|
568
297
|
}
|
|
569
298
|
}
|
|
570
|
-
// Always acknowledge the callback to remove loading state
|
|
571
299
|
await this.bot.answerCallbackQuery(query.id);
|
|
572
300
|
});
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
301
|
+
// Polling errors
|
|
302
|
+
this.bot.on("polling_error", (error) => {
|
|
303
|
+
console.error("[TelegramChannel] Polling error:", error.message);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Register Telegram command menu
|
|
308
|
+
*/
|
|
309
|
+
async registerCommands() {
|
|
310
|
+
await this.bot.setMyCommands([
|
|
311
|
+
{ command: "start", description: "Start the bot" },
|
|
312
|
+
{ command: "help", description: "Show available commands" },
|
|
313
|
+
{ command: "status", description: "Check bot status" },
|
|
314
|
+
{ command: "git", description: "Check git status" },
|
|
315
|
+
{ command: "doppler", description: "Check Doppler secrets" },
|
|
316
|
+
{ command: "logs", description: "View recent logs" },
|
|
317
|
+
{ command: "clear", description: "Clear conversation history" },
|
|
318
|
+
{ command: "tools", description: "List available tools" },
|
|
319
|
+
{ command: "restart", description: "Restart the bot service" },
|
|
320
|
+
{ command: "cancel", description: "Stop current task" },
|
|
321
|
+
{ command: "quiet", description: "Hide tool logging" },
|
|
322
|
+
{ command: "verbose", description: "Show tool logging" },
|
|
323
|
+
]);
|
|
582
324
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
325
|
+
/**
|
|
326
|
+
* Normalize Telegram message to ChannelMessage format.
|
|
327
|
+
*/
|
|
328
|
+
createChannelMessage(msg) {
|
|
329
|
+
const sender = {
|
|
330
|
+
id: msg.from?.id?.toString() || msg.chat.id.toString(),
|
|
331
|
+
username: msg.from?.username,
|
|
332
|
+
displayName: msg.from?.first_name || msg.from?.username || "User",
|
|
333
|
+
isBot: msg.from?.is_bot || false,
|
|
334
|
+
};
|
|
335
|
+
const context = {
|
|
336
|
+
isDM: msg.chat.type === "private",
|
|
337
|
+
groupName: msg.chat.type !== "private" ? msg.chat.title : undefined,
|
|
338
|
+
};
|
|
339
|
+
// Build reply context
|
|
340
|
+
let replyText;
|
|
341
|
+
if (msg.reply_to_message?.text) {
|
|
342
|
+
const replyFrom = msg.reply_to_message.from?.first_name || "User";
|
|
343
|
+
replyText = `[Replying to ${replyFrom}: "${msg.reply_to_message.text.slice(0, 100)}"]`;
|
|
587
344
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
{ role: 'system', content: systemContent },
|
|
600
|
-
...history,
|
|
601
|
-
{ role: 'user', content: userMessage }
|
|
602
|
-
];
|
|
603
|
-
try {
|
|
604
|
-
console.log(`🔄 Calling GLM-4.7 API for: ${userMessage} (${history.length} history)`);
|
|
605
|
-
// Loop to handle tool calls
|
|
606
|
-
let iterations = 0;
|
|
607
|
-
const maxIterations = 50; // Allow more tool calls for complex tasks
|
|
608
|
-
while (iterations < maxIterations) {
|
|
609
|
-
iterations++;
|
|
610
|
-
const response = await fetchWithRetry(ZAI_API_ENDPOINT, {
|
|
611
|
-
method: 'POST',
|
|
612
|
-
headers: {
|
|
613
|
-
'Content-Type': 'application/json',
|
|
614
|
-
'Authorization': `Bearer ${apiKey}`
|
|
615
|
-
},
|
|
616
|
-
body: JSON.stringify({
|
|
617
|
-
model: 'glm-4.7',
|
|
618
|
-
messages,
|
|
619
|
-
tools: getGLMTools(),
|
|
620
|
-
temperature,
|
|
621
|
-
max_tokens: maxTokens
|
|
622
|
-
})
|
|
623
|
-
}, 3, 1000);
|
|
624
|
-
const data = await response.json();
|
|
625
|
-
const message = data.choices?.[0]?.message;
|
|
626
|
-
if (!message) {
|
|
627
|
-
console.error('❌ Unexpected API response format:', data);
|
|
628
|
-
return '❌ Unexpected response from AI API.';
|
|
629
|
-
}
|
|
630
|
-
// Check if LLM wants to call tools
|
|
631
|
-
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
632
|
-
console.log(`🔧 LLM calling ${message.tool_calls.length} tool(s)`);
|
|
633
|
-
// Add assistant message with tool calls to history
|
|
634
|
-
messages.push({
|
|
635
|
-
role: 'assistant',
|
|
636
|
-
tool_calls: message.tool_calls
|
|
637
|
-
});
|
|
638
|
-
// Check quiet mode for Telegram messages
|
|
639
|
-
const quiet = isQuiet();
|
|
640
|
-
// Execute each tool and add results
|
|
641
|
-
for (const toolCall of message.tool_calls) {
|
|
642
|
-
const toolName = toolCall.function.name;
|
|
643
|
-
const args = JSON.parse(toolCall.function.arguments);
|
|
644
|
-
console.log(` → ${toolName}(${JSON.stringify(args)})`);
|
|
645
|
-
// Send tool call notification to Telegram (skip if quiet)
|
|
646
|
-
if (!quiet) {
|
|
647
|
-
// Sanitize to prevent entity parsing errors - strip Markdown special chars
|
|
648
|
-
const sanitizeForTelegram = (str) => {
|
|
649
|
-
return str
|
|
650
|
-
.replace(/[_*[\]()~`>#+=|{}.!-]/g, '') // Remove Markdown special chars
|
|
651
|
-
.replace(/\\/g, '') // Remove backslashes
|
|
652
|
-
.slice(0, 100); // Truncate to prevent long messages
|
|
653
|
-
};
|
|
654
|
-
const argsPreview = Object.keys(args).length > 0
|
|
655
|
-
? sanitizeForTelegram(JSON.stringify(args))
|
|
656
|
-
: '';
|
|
657
|
-
await this.bot.sendMessage(chatId, `🔧 ${toolName}${argsPreview ? ' ' + argsPreview : ''}`);
|
|
658
|
-
}
|
|
659
|
-
// Execute tool locally
|
|
660
|
-
const result = await executeTool(toolName, args);
|
|
661
|
-
console.log(` ← ${result.slice(0, 100)}...`);
|
|
662
|
-
// Send result to Telegram (skip if quiet)
|
|
663
|
-
if (!quiet) {
|
|
664
|
-
const sanitizedResult = result
|
|
665
|
-
.replace(/[_*[\]()~`>#+=|{}.!-]/g, '')
|
|
666
|
-
.replace(/\\/g, '');
|
|
667
|
-
const resultPreview = sanitizedResult.length > 500 ? sanitizedResult.slice(0, 500) + ' [truncated]' : sanitizedResult;
|
|
668
|
-
await this.bot.sendMessage(chatId, `📥 ${resultPreview}`);
|
|
669
|
-
}
|
|
670
|
-
// Add tool result to messages
|
|
671
|
-
messages.push({
|
|
672
|
-
role: 'tool',
|
|
673
|
-
name: toolName,
|
|
674
|
-
content: result
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
// Continue loop to get final response
|
|
678
|
-
continue;
|
|
679
|
-
}
|
|
680
|
-
// No tool calls - we have the final response
|
|
681
|
-
if (message.content) {
|
|
682
|
-
console.log(`✅ GLM-4.7 response: ${message.content.slice(0, 100)}...`);
|
|
683
|
-
return message.content;
|
|
345
|
+
return {
|
|
346
|
+
messageId: msg.message_id.toString(),
|
|
347
|
+
channelId: this.id,
|
|
348
|
+
timestamp: new Date(msg.date * 1000),
|
|
349
|
+
sender,
|
|
350
|
+
text: replyText ? `${replyText}\n\n${msg.text || ""}` : (msg.text || ""),
|
|
351
|
+
context,
|
|
352
|
+
replyTo: msg.reply_to_message
|
|
353
|
+
? {
|
|
354
|
+
messageId: msg.reply_to_message.message_id.toString(),
|
|
355
|
+
channelId: this.id,
|
|
684
356
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
}
|
|
688
|
-
return '⚠️ Reached tool limit (50). The AI was being very thorough! Try a more specific request.';
|
|
689
|
-
}
|
|
690
|
-
catch (error) {
|
|
691
|
-
console.error('❌ Error calling GLM-4.7:', error.message);
|
|
692
|
-
if (error.message.includes('429')) {
|
|
693
|
-
return '⚠️ Rate limited by API. Please try again in a moment.';
|
|
694
|
-
}
|
|
695
|
-
return `❌ Error: ${error.message}`;
|
|
696
|
-
}
|
|
697
|
-
finally {
|
|
698
|
-
// Always stop typing indicator when done
|
|
699
|
-
this.stopTypingIndicator(chatId);
|
|
700
|
-
}
|
|
357
|
+
: undefined,
|
|
358
|
+
};
|
|
701
359
|
}
|
|
702
|
-
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
'/start - Show welcome message\n' +
|
|
709
|
-
'/status - Check API status\n' +
|
|
710
|
-
'/git - Git & GitHub status\n' +
|
|
711
|
-
'/doppler - Doppler config\n' +
|
|
712
|
-
'/help - Show all commands\n' +
|
|
713
|
-
'Any message - Chat with GLM-4.7 AI');
|
|
714
|
-
console.log(`✅ Test message sent to chat ${chatId}`);
|
|
715
|
-
}
|
|
716
|
-
async stop() {
|
|
717
|
-
console.log('🛑 Stopping Telegram bot...');
|
|
718
|
-
this.bot.stopPolling();
|
|
360
|
+
extractChatId(replyTo) {
|
|
361
|
+
const accountId = replyTo.channelId.accountId;
|
|
362
|
+
const parsed = parseInt(accountId, 10);
|
|
363
|
+
if (!isNaN(parsed))
|
|
364
|
+
return parsed;
|
|
365
|
+
return null;
|
|
719
366
|
}
|
|
720
367
|
/**
|
|
721
|
-
*
|
|
722
|
-
* Always sends a startup message (crash or normal)
|
|
368
|
+
* Send long message by splitting into chunks.
|
|
723
369
|
*/
|
|
724
|
-
async
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const errorLines = crashLog.split('\n')
|
|
737
|
-
.filter(line => crashIndicators.some(ind => line.toLowerCase().includes(ind.toLowerCase())))
|
|
738
|
-
.slice(-10)
|
|
739
|
-
.join('\n');
|
|
740
|
-
if (errorLines) {
|
|
741
|
-
// Ask LLM to diagnose
|
|
742
|
-
const diagnosis = await this.getGLMResponse(chatId, `Analyze this crash log and tell me what went wrong in one short paragraph, then suggest a fix:
|
|
743
|
-
|
|
744
|
-
\`\`\`
|
|
745
|
-
${errorLines.slice(0, 1000)}
|
|
746
|
-
\`\`\`
|
|
747
|
-
|
|
748
|
-
Format: "Issue: [what happened]. Fix: [how to fix it]"`, 'CrashAnalyzer');
|
|
749
|
-
await this.sendLongMessage(chatId, `🏥 Crash Diagnosis:\n\n${diagnosis}`);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
else {
|
|
753
|
-
// Normal start - no crash detected
|
|
754
|
-
await this.bot.sendMessage(chatId, '✅ Bot started normally.\n\n' +
|
|
755
|
-
'Previous session ended cleanly. Ready to assist!');
|
|
370
|
+
async sendLongMessage(chatId, text, options) {
|
|
371
|
+
const MAX_LENGTH = 4000;
|
|
372
|
+
if (text.length <= MAX_LENGTH) {
|
|
373
|
+
await this.bot.sendMessage(chatId, text, options);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const chunks = [];
|
|
377
|
+
let remaining = text;
|
|
378
|
+
while (remaining.length > 0) {
|
|
379
|
+
if (remaining.length <= MAX_LENGTH) {
|
|
380
|
+
chunks.push(remaining);
|
|
381
|
+
break;
|
|
756
382
|
}
|
|
383
|
+
let breakPoint = remaining.lastIndexOf("\n", MAX_LENGTH);
|
|
384
|
+
if (breakPoint < 1000)
|
|
385
|
+
breakPoint = remaining.lastIndexOf(" ", MAX_LENGTH);
|
|
386
|
+
if (breakPoint < 1000)
|
|
387
|
+
breakPoint = MAX_LENGTH;
|
|
388
|
+
chunks.push(remaining.slice(0, breakPoint));
|
|
389
|
+
remaining = remaining.slice(breakPoint).trim();
|
|
757
390
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
await this.bot.sendMessage(chatId, '✅ Bot started fresh.\n\n' +
|
|
762
|
-
'No previous session data available. Ready to assist!');
|
|
391
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
392
|
+
const prefix = chunks.length > 1 ? `[${i + 1}/${chunks.length}] ` : "";
|
|
393
|
+
await this.bot.sendMessage(chatId, prefix + chunks[i], options);
|
|
763
394
|
}
|
|
764
395
|
}
|
|
765
396
|
}
|
|
766
|
-
//
|
|
397
|
+
// ============================================================
|
|
398
|
+
// FACTORY
|
|
399
|
+
// ============================================================
|
|
400
|
+
export function createTelegramChannel(config) {
|
|
401
|
+
return new TelegramChannel(config);
|
|
402
|
+
}
|
|
403
|
+
// ============================================================
|
|
404
|
+
// MAIN ENTRY POINT (for standalone testing)
|
|
405
|
+
// ============================================================
|
|
767
406
|
async function main() {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
console.error(
|
|
407
|
+
const config = createTelegramConfigFromEnv();
|
|
408
|
+
if (!config.botToken) {
|
|
409
|
+
console.error("TELEGRAM_BOT_TOKEN not found in environment");
|
|
771
410
|
process.exit(1);
|
|
772
411
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
process.on('SIGINT', async () => {
|
|
785
|
-
await bot.stop();
|
|
412
|
+
const channel = createTelegramChannel(config);
|
|
413
|
+
// Example: Echo handler for testing
|
|
414
|
+
channel.onMessage(async (msg) => {
|
|
415
|
+
console.log(`Received: ${msg.text}`);
|
|
416
|
+
return {
|
|
417
|
+
content: { text: `Echo: ${msg.text}` },
|
|
418
|
+
replyTo: { messageId: msg.messageId, channelId: msg.channelId },
|
|
419
|
+
};
|
|
420
|
+
});
|
|
421
|
+
process.on("SIGINT", async () => {
|
|
422
|
+
await channel.stop();
|
|
786
423
|
process.exit(0);
|
|
787
424
|
});
|
|
788
|
-
process.on(
|
|
789
|
-
await
|
|
425
|
+
process.on("SIGTERM", async () => {
|
|
426
|
+
await channel.stop();
|
|
790
427
|
process.exit(0);
|
|
791
428
|
});
|
|
429
|
+
await channel.start();
|
|
792
430
|
}
|
|
793
431
|
main().catch(console.error);
|
|
794
432
|
//# sourceMappingURL=index.js.map
|