openkbs 0.0.66 → 0.0.70
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 +1 -0
- package/elastic/README.md +1 -1
- package/elastic/functions.md +5 -5
- package/elastic/pulse.md +2 -2
- package/package.json +2 -2
- package/scripts/deploy.js +68 -0
- package/src/actions.js +59 -0
- package/src/index.js +8 -6
- package/templates/.claude/skills/openkbs/SKILL.md +37 -8
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/README.md +55 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/app/instructions.txt +40 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/app/settings.json +41 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/openkbs.json +3 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/actions.js +141 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/handler.js +32 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/memoryHelpers.js +91 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onCronjob.js +105 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onPublicAPIRequest.js +165 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onRequest.js +2 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onResponse.js +2 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Frontend/contentRender.js +74 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Frontend/contentRender.json +3 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/auth/index.mjs +228 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/auth/package.json +7 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/posts/index.mjs +287 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/posts/package.json +10 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/settings.json +4 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/openkbs.json +16 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/site/index.html +658 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/site/settings.json +4 -0
- package/templates/.claude/skills/openkbs/patterns/cronjob-batch-processing.md +278 -0
- package/templates/.claude/skills/openkbs/patterns/cronjob-monitoring.md +341 -0
- package/templates/.claude/skills/openkbs/patterns/file-upload.md +205 -0
- package/templates/.claude/skills/openkbs/patterns/image-generation.md +139 -0
- package/templates/.claude/skills/openkbs/patterns/memory-system.md +264 -0
- package/templates/.claude/skills/openkbs/patterns/public-api-item-proxy.md +254 -0
- package/templates/.claude/skills/openkbs/patterns/scheduled-tasks.md +157 -0
- package/templates/.claude/skills/openkbs/patterns/telegram-webhook.md +424 -0
- package/templates/.claude/skills/openkbs/patterns/telegram.md +222 -0
- package/templates/.claude/skills/openkbs/patterns/vectordb-archive.md +231 -0
- package/templates/.claude/skills/openkbs/patterns/video-generation.md +145 -0
- package/templates/.claude/skills/openkbs/patterns/web-publishing.md +257 -0
- package/templates/.claude/skills/openkbs/reference/backend-sdk.md +13 -2
- package/templates/.claude/skills/openkbs/reference/elastic-services.md +61 -29
- package/templates/platform/README.md +35 -0
- package/templates/platform/agents/assistant/app/icon.png +0 -0
- package/templates/platform/agents/assistant/app/instructions.txt +13 -0
- package/templates/platform/agents/assistant/app/settings.json +13 -0
- package/templates/platform/agents/assistant/src/Events/actions.js +50 -0
- package/templates/platform/agents/assistant/src/Events/handler.js +54 -0
- package/templates/platform/agents/assistant/src/Events/onRequest.js +3 -0
- package/templates/platform/agents/assistant/src/Events/onRequest.json +5 -0
- package/templates/platform/agents/assistant/src/Events/onResponse.js +3 -0
- package/templates/platform/agents/assistant/src/Events/onResponse.json +5 -0
- package/templates/platform/agents/assistant/src/Frontend/contentRender.js +27 -0
- package/templates/platform/agents/assistant/src/Frontend/contentRender.json +10 -0
- package/templates/platform/functions/api/index.mjs +63 -0
- package/templates/platform/functions/api/package.json +4 -0
- package/templates/platform/openkbs.json +19 -0
- package/templates/platform/site/index.html +75 -0
- package/version.json +3 -3
- package/templates/.claude/skills/openkbs/examples/ai-copywriter-agent/scripts/run_job.js +0 -26
- package/templates/.claude/skills/openkbs/examples/ai-copywriter-agent/scripts/utils/agent_client.js +0 -265
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# Telegram Webhook Pattern
|
|
2
|
+
|
|
3
|
+
Complete working code for receiving and processing Telegram messages via webhook.
|
|
4
|
+
|
|
5
|
+
## Concept
|
|
6
|
+
|
|
7
|
+
Full Telegram bot integration with:
|
|
8
|
+
- Webhook setup/removal via query params
|
|
9
|
+
- Secret token validation
|
|
10
|
+
- Channel posts and direct messages
|
|
11
|
+
- Photo upload to OpenKBS storage
|
|
12
|
+
- Message editing and deletion
|
|
13
|
+
- Duplicate detection
|
|
14
|
+
- Autonomous chat processing
|
|
15
|
+
|
|
16
|
+
## onPublicAPIRequest.js
|
|
17
|
+
|
|
18
|
+
```javascript
|
|
19
|
+
import {
|
|
20
|
+
setAgentSetting,
|
|
21
|
+
getAgentSetting,
|
|
22
|
+
storeTelegramMessage,
|
|
23
|
+
getTelegramMessage,
|
|
24
|
+
deleteTelegramMessage
|
|
25
|
+
} from './memoryHelpers.js';
|
|
26
|
+
|
|
27
|
+
const AUTONOMOUS_CHAT_RULES = `**Autonomous Chat Rules:**
|
|
28
|
+
This chat has NO USER listening. You MUST:
|
|
29
|
+
1. ONLY output commands - no explanatory text
|
|
30
|
+
2. To communicate with user: Use sendToTelegramChannel command
|
|
31
|
+
3. Plain text "END" to finish the chat`;
|
|
32
|
+
|
|
33
|
+
export const handler = async ({ payload, queryStringParameters, headers }) => {
|
|
34
|
+
try {
|
|
35
|
+
const BOT_TOKEN = '{{secrets.telegramBotToken}}';
|
|
36
|
+
|
|
37
|
+
// Get channel ID from secrets or saved setting
|
|
38
|
+
let CHANNEL_ID = '{{secrets.telegramChannelID}}';
|
|
39
|
+
if (!CHANNEL_ID || CHANNEL_ID.includes('{{')) {
|
|
40
|
+
CHANNEL_ID = await getAgentSetting('agent_telegramChannelID');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// === WEBHOOK SETUP ===
|
|
44
|
+
if (queryStringParameters?.setupTelegramWebhook === 'true') {
|
|
45
|
+
return await setupWebhook(BOT_TOKEN, CHANNEL_ID);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// === WEBHOOK REMOVAL ===
|
|
49
|
+
if (queryStringParameters?.removeTelegramWebhook === 'true') {
|
|
50
|
+
return await removeWebhook(BOT_TOKEN);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// === VALIDATE SECRET TOKEN ===
|
|
54
|
+
const expectedToken = crypto.createHash('sha256')
|
|
55
|
+
.update(BOT_TOKEN).digest('hex').substring(0, 32);
|
|
56
|
+
|
|
57
|
+
const receivedToken = headers?.[
|
|
58
|
+
Object.keys(headers || {}).find(key =>
|
|
59
|
+
key.toLowerCase() === 'x-telegram-bot-api-secret-token'
|
|
60
|
+
)
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
if (receivedToken && receivedToken !== expectedToken) {
|
|
64
|
+
return { ok: false, error: 'Invalid secret token' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// === PROCESS UPDATES ===
|
|
68
|
+
const { message, channel_post, edited_message, edited_channel_post, callback_query } = payload;
|
|
69
|
+
|
|
70
|
+
// Handle channel posts
|
|
71
|
+
if (channel_post) {
|
|
72
|
+
return await handleChannelPost(channel_post, BOT_TOKEN, CHANNEL_ID);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Handle direct messages
|
|
76
|
+
if (message) {
|
|
77
|
+
return await handleDirectMessage(message, BOT_TOKEN);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Handle edited messages
|
|
81
|
+
if (edited_channel_post) {
|
|
82
|
+
return await handleEditedMessage(edited_channel_post);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (edited_message) {
|
|
86
|
+
return await handleEditedMessage(edited_message);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Handle button callbacks
|
|
90
|
+
if (callback_query) {
|
|
91
|
+
return { ok: true, processed: 'callback', data: callback_query.data };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { ok: true, message: 'Update type not handled' };
|
|
95
|
+
|
|
96
|
+
} catch (error) {
|
|
97
|
+
// Always return ok:true to prevent Telegram retries
|
|
98
|
+
return { ok: true, error: error.message };
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Webhook Setup/Removal
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
async function setupWebhook(BOT_TOKEN, CHANNEL_ID) {
|
|
107
|
+
// Check if already configured
|
|
108
|
+
const existing = await getAgentSetting('agent_telegramWebhookSetup');
|
|
109
|
+
if (existing) {
|
|
110
|
+
return { ok: false, error: 'Webhook already configured', setupDate: existing };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
// Generate secret token
|
|
115
|
+
const SECRET_TOKEN = crypto.createHash('sha256')
|
|
116
|
+
.update(BOT_TOKEN).digest('hex').substring(0, 32);
|
|
117
|
+
|
|
118
|
+
const WEBHOOK_URL = `https://chat.openkbs.com/publicAPIRequest?kbId=${openkbs.kbId}&source=telegram`;
|
|
119
|
+
|
|
120
|
+
// Remove any existing webhook first
|
|
121
|
+
await axios.post(`https://api.telegram.org/bot${BOT_TOKEN}/deleteWebhook`);
|
|
122
|
+
|
|
123
|
+
// Set new webhook
|
|
124
|
+
const response = await axios.post(`https://api.telegram.org/bot${BOT_TOKEN}/setWebhook`, {
|
|
125
|
+
url: WEBHOOK_URL,
|
|
126
|
+
allowed_updates: ['message', 'edited_message', 'channel_post', 'edited_channel_post', 'callback_query'],
|
|
127
|
+
drop_pending_updates: true,
|
|
128
|
+
secret_token: SECRET_TOKEN
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
if (!response.data.ok) {
|
|
132
|
+
throw new Error(response.data.description);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Save setup flag
|
|
136
|
+
const setupDate = new Date().toISOString();
|
|
137
|
+
await setAgentSetting('agent_telegramWebhookSetup', setupDate);
|
|
138
|
+
|
|
139
|
+
// Send test message
|
|
140
|
+
if (CHANNEL_ID) {
|
|
141
|
+
await axios.post(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, {
|
|
142
|
+
chat_id: CHANNEL_ID,
|
|
143
|
+
text: '✅ *Telegram Integration Active*\n\nWebhook configured successfully!',
|
|
144
|
+
parse_mode: 'Markdown'
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
ok: true,
|
|
150
|
+
message: 'Webhook configured',
|
|
151
|
+
webhookUrl: WEBHOOK_URL,
|
|
152
|
+
setupDate
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
} catch (error) {
|
|
156
|
+
return { ok: false, error: error.message };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function removeWebhook(BOT_TOKEN) {
|
|
161
|
+
const webhookSetup = await getAgentSetting('agent_telegramWebhookSetup');
|
|
162
|
+
if (webhookSetup) {
|
|
163
|
+
return { ok: false, error: 'Cannot remove active webhook. Remove internally first.' };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
await axios.post(`https://api.telegram.org/bot${BOT_TOKEN}/deleteWebhook`, {
|
|
167
|
+
drop_pending_updates: true
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return { ok: true, message: 'Webhook removed' };
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Message Handlers
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
async function handleChannelPost(post, BOT_TOKEN, CHANNEL_ID) {
|
|
178
|
+
const text = post.text || post.caption || '';
|
|
179
|
+
const photo = post.photo;
|
|
180
|
+
const messageId = post.message_id;
|
|
181
|
+
const chatId = post.chat.id;
|
|
182
|
+
const date = post.date;
|
|
183
|
+
|
|
184
|
+
// Auto-save channel ID on first message
|
|
185
|
+
if (!CHANNEL_ID) {
|
|
186
|
+
await setAgentSetting('agent_telegramChannelID', chatId.toString());
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Get sender info
|
|
190
|
+
let senderName = post.from?.username ||
|
|
191
|
+
post.from?.first_name ||
|
|
192
|
+
post.author_signature ||
|
|
193
|
+
post.sender_chat?.title ||
|
|
194
|
+
'Unknown';
|
|
195
|
+
|
|
196
|
+
// Check for duplicate
|
|
197
|
+
const existing = await getTelegramMessage(messageId);
|
|
198
|
+
if (existing) {
|
|
199
|
+
return { ok: true, duplicate: true, messageId };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Upload photo if present
|
|
203
|
+
let uploadedImageUrl = null;
|
|
204
|
+
if (photo?.length > 0) {
|
|
205
|
+
uploadedImageUrl = await uploadTelegramPhoto(photo, BOT_TOKEN);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Store message
|
|
209
|
+
await storeTelegramMessage(messageId, {
|
|
210
|
+
text: text.substring(0, 100000),
|
|
211
|
+
date,
|
|
212
|
+
type: 'channel',
|
|
213
|
+
from: senderName,
|
|
214
|
+
chatId,
|
|
215
|
+
...(uploadedImageUrl && { imageUrl: uploadedImageUrl })
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Build LLM message
|
|
219
|
+
let chatMessage;
|
|
220
|
+
if (uploadedImageUrl) {
|
|
221
|
+
chatMessage = JSON.stringify([
|
|
222
|
+
{ type: "text", text: `PROCESS_TELEGRAM_MESSAGE from ${senderName}\n\n${text || '[image]'}\n\n${AUTONOMOUS_CHAT_RULES}` },
|
|
223
|
+
{ type: "image_url", image_url: { url: uploadedImageUrl } }
|
|
224
|
+
]);
|
|
225
|
+
} else {
|
|
226
|
+
chatMessage = `PROCESS_TELEGRAM_MESSAGE from ${senderName}\n\n${senderName} wrote:\n"""\n${text}\n"""\n\n${AUTONOMOUS_CHAT_RULES}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Create chat for processing
|
|
230
|
+
const timeStr = new Date(date * 1000).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
|
|
231
|
+
await openkbs.chats({
|
|
232
|
+
chatTitle: `TG: ${senderName} - ${timeStr}`,
|
|
233
|
+
message: chatMessage
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
return { ok: true, processed: 'channel_post', messageId };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function handleDirectMessage(message, BOT_TOKEN) {
|
|
240
|
+
const text = message.text || message.caption || '';
|
|
241
|
+
const photo = message.photo;
|
|
242
|
+
const messageId = message.message_id;
|
|
243
|
+
const userName = message.from.username || message.from.first_name;
|
|
244
|
+
|
|
245
|
+
// Check duplicate
|
|
246
|
+
const existing = await getTelegramMessage(messageId);
|
|
247
|
+
if (existing) {
|
|
248
|
+
return { ok: true, duplicate: true };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Upload photo
|
|
252
|
+
let uploadedImageUrl = null;
|
|
253
|
+
if (photo?.length > 0) {
|
|
254
|
+
uploadedImageUrl = await uploadTelegramPhoto(photo, BOT_TOKEN);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Store message
|
|
258
|
+
await storeTelegramMessage(messageId, {
|
|
259
|
+
text: text.substring(0, 20000),
|
|
260
|
+
date: message.date,
|
|
261
|
+
type: 'direct',
|
|
262
|
+
from: userName,
|
|
263
|
+
chatId: message.chat.id,
|
|
264
|
+
userId: message.from.id,
|
|
265
|
+
...(uploadedImageUrl && { imageUrl: uploadedImageUrl })
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Create chat
|
|
269
|
+
const content = [
|
|
270
|
+
{ type: "text", text: `TELEGRAM DM from ${userName}\n\n"${text}"\n\n${AUTONOMOUS_CHAT_RULES}` }
|
|
271
|
+
];
|
|
272
|
+
if (uploadedImageUrl) {
|
|
273
|
+
content.push({ type: "image_url", image_url: { url: uploadedImageUrl } });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
await openkbs.chats({
|
|
277
|
+
chatTitle: `TG DM: ${userName}`,
|
|
278
|
+
message: JSON.stringify(content)
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return { ok: true, processed: 'message' };
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Photo Upload Helper
|
|
286
|
+
|
|
287
|
+
```javascript
|
|
288
|
+
async function uploadTelegramPhoto(photo, BOT_TOKEN) {
|
|
289
|
+
try {
|
|
290
|
+
// Get largest photo
|
|
291
|
+
const largestPhoto = photo[photo.length - 1];
|
|
292
|
+
|
|
293
|
+
// Get file path from Telegram
|
|
294
|
+
const fileInfo = await axios.get(
|
|
295
|
+
`https://api.telegram.org/bot${BOT_TOKEN}/getFile?file_id=${largestPhoto.file_id}`
|
|
296
|
+
);
|
|
297
|
+
const filePath = fileInfo.data.result.file_path;
|
|
298
|
+
|
|
299
|
+
// Download file
|
|
300
|
+
const fileUrl = `https://api.telegram.org/file/bot${BOT_TOKEN}/${filePath}`;
|
|
301
|
+
const fileResponse = await axios.get(fileUrl, { responseType: 'arraybuffer' });
|
|
302
|
+
|
|
303
|
+
// Generate filename
|
|
304
|
+
const timestamp = Date.now();
|
|
305
|
+
const ext = filePath.split('.').pop() || 'jpg';
|
|
306
|
+
const filename = `telegram_photo_${timestamp}.${ext}`;
|
|
307
|
+
|
|
308
|
+
// Get presigned URL and upload
|
|
309
|
+
const presignedUrl = await openkbs.kb({
|
|
310
|
+
action: 'createPresignedURL',
|
|
311
|
+
namespace: 'files',
|
|
312
|
+
fileName: filename,
|
|
313
|
+
fileType: 'image/jpeg',
|
|
314
|
+
presignedOperation: 'putObject'
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
await axios.put(presignedUrl, fileResponse.data, {
|
|
318
|
+
headers: { 'Content-Type': 'image/jpeg', 'Content-Length': fileResponse.data.length }
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
return `https://yourdomain.com/media/${filename}`;
|
|
322
|
+
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.error('Photo upload failed:', error);
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Edited Message Handler
|
|
331
|
+
|
|
332
|
+
```javascript
|
|
333
|
+
async function handleEditedMessage(edited) {
|
|
334
|
+
const messageId = edited.message_id;
|
|
335
|
+
const newText = edited.text;
|
|
336
|
+
const editDate = edited.edit_date;
|
|
337
|
+
|
|
338
|
+
const existing = await getTelegramMessage(messageId);
|
|
339
|
+
if (!existing) {
|
|
340
|
+
return { ok: true, note: 'Message not found' };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const itemId = `telegram_${messageId.toString().padStart(12, '0')}`;
|
|
344
|
+
|
|
345
|
+
// Special: "_delete" prefix removes message from history
|
|
346
|
+
if (newText?.trim().toLowerCase().startsWith('_delete')) {
|
|
347
|
+
await deleteTelegramMessage(itemId);
|
|
348
|
+
return { ok: true, action: 'deleted' };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Update message
|
|
352
|
+
await storeTelegramMessage(messageId, {
|
|
353
|
+
...existing,
|
|
354
|
+
text: newText,
|
|
355
|
+
edited: true,
|
|
356
|
+
editedAt: editDate,
|
|
357
|
+
originalText: existing.originalText || existing.text
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
return { ok: true, action: 'edited' };
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## memoryHelpers.js
|
|
365
|
+
|
|
366
|
+
```javascript
|
|
367
|
+
export async function storeTelegramMessage(messageId, data) {
|
|
368
|
+
// Zero-pad for proper DynamoDB sorting
|
|
369
|
+
const itemId = `telegram_${messageId.toString().padStart(12, '0')}`;
|
|
370
|
+
const body = { ...data, storedAt: new Date().toISOString() };
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
await openkbs.updateItem({ itemType: 'telegram', itemId, body });
|
|
374
|
+
} catch (e) {
|
|
375
|
+
await openkbs.createItem({ itemType: 'telegram', itemId, body });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export async function getTelegramMessage(messageId) {
|
|
380
|
+
const itemId = `telegram_${messageId.toString().padStart(12, '0')}`;
|
|
381
|
+
try {
|
|
382
|
+
const result = await openkbs.getItem(itemId);
|
|
383
|
+
return result?.item?.body;
|
|
384
|
+
} catch (e) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export async function deleteTelegramMessage(itemId) {
|
|
390
|
+
try {
|
|
391
|
+
await openkbs.deleteItem(itemId);
|
|
392
|
+
} catch (e) {}
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## settings.json - Priority Items
|
|
397
|
+
|
|
398
|
+
```json
|
|
399
|
+
{
|
|
400
|
+
"options": {
|
|
401
|
+
"priorityItems": [
|
|
402
|
+
{ "limit": 100, "prefix": "memory" },
|
|
403
|
+
{ "limit": 100, "prefix": "telegram" }
|
|
404
|
+
]
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Setup URL
|
|
410
|
+
|
|
411
|
+
```
|
|
412
|
+
https://chat.openkbs.com/publicAPIRequest?kbId=YOUR_KB_ID&setupTelegramWebhook=true
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
## Key Points
|
|
416
|
+
|
|
417
|
+
1. **Webhook setup via query param** - `?setupTelegramWebhook=true`
|
|
418
|
+
2. **Secret token validation** - SHA256 hash of bot token
|
|
419
|
+
3. **Always return `ok: true`** - Prevents Telegram retries
|
|
420
|
+
4. **Photo upload** - Download from Telegram, upload to OpenKBS storage
|
|
421
|
+
5. **Duplicate detection** - Check `getTelegramMessage` before processing
|
|
422
|
+
6. **Zero-padded messageId** - For proper DynamoDB string sorting
|
|
423
|
+
7. **Autonomous chat rules** - Tell LLM how to behave without user
|
|
424
|
+
8. **Edit as delete** - `_delete` prefix removes from history
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Telegram Integration Pattern
|
|
2
|
+
|
|
3
|
+
Complete working code for Telegram bot messaging and webhooks.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
1. Create bot with @BotFather
|
|
8
|
+
2. Get bot token
|
|
9
|
+
3. Add to secrets: `{{secrets.telegramBotToken}}`
|
|
10
|
+
4. Optional: Set channel ID in secrets or detect automatically
|
|
11
|
+
|
|
12
|
+
## actions.js
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
const TELEGRAM_BOT_TOKEN = '{{secrets.telegramBotToken}}';
|
|
16
|
+
|
|
17
|
+
// Helper to get channel ID from secrets or saved setting
|
|
18
|
+
async function getTelegramChannelId() {
|
|
19
|
+
const secretsChannelId = '{{secrets.telegramChannelID}}';
|
|
20
|
+
if (secretsChannelId && !secretsChannelId.includes('{{')) {
|
|
21
|
+
return secretsChannelId;
|
|
22
|
+
}
|
|
23
|
+
// Fallback to saved setting
|
|
24
|
+
return await getAgentSetting('agent_telegramChannelID');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Send text message to Telegram channel
|
|
28
|
+
async function sendToTelegramChannel(message, options = {}) {
|
|
29
|
+
try {
|
|
30
|
+
const channelId = await getTelegramChannelId();
|
|
31
|
+
if (!channelId) {
|
|
32
|
+
throw new Error('Telegram channel ID not configured');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const url = `https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`;
|
|
36
|
+
const response = await axios.post(url, {
|
|
37
|
+
chat_id: channelId,
|
|
38
|
+
text: message,
|
|
39
|
+
parse_mode: options.parse_mode || 'Markdown',
|
|
40
|
+
disable_notification: options.silent || false
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (response.data.ok) {
|
|
44
|
+
return { success: true, messageId: response.data.result.message_id };
|
|
45
|
+
}
|
|
46
|
+
throw new Error(response.data.description || 'Telegram error');
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return { success: false, error: error.message };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Send photo to Telegram channel
|
|
53
|
+
async function sendPhotoToTelegramChannel(photoUrl, caption = '') {
|
|
54
|
+
try {
|
|
55
|
+
const channelId = await getTelegramChannelId();
|
|
56
|
+
if (!channelId) {
|
|
57
|
+
throw new Error('Telegram channel ID not configured');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const url = `https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendPhoto`;
|
|
61
|
+
const response = await axios.post(url, {
|
|
62
|
+
chat_id: channelId,
|
|
63
|
+
photo: photoUrl,
|
|
64
|
+
caption: caption,
|
|
65
|
+
parse_mode: 'Markdown'
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (response.data.ok) {
|
|
69
|
+
return { success: true, messageId: response.data.result.message_id };
|
|
70
|
+
}
|
|
71
|
+
throw new Error(response.data.description || 'Telegram error');
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return { success: false, error: error.message };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Command: Send message to Telegram
|
|
78
|
+
[/<sendToTelegramChannel>([\s\S]*?)<\/sendToTelegramChannel>/s, async (match) => {
|
|
79
|
+
try {
|
|
80
|
+
const data = JSON.parse(match[1].trim());
|
|
81
|
+
const result = await sendToTelegramChannel(data.message, {
|
|
82
|
+
parse_mode: data.parse_mode || 'Markdown',
|
|
83
|
+
silent: data.silent || false
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
type: "TELEGRAM_MESSAGE_SENT",
|
|
88
|
+
success: result.success,
|
|
89
|
+
messageId: result.messageId,
|
|
90
|
+
error: result.error,
|
|
91
|
+
_meta_actions: ["REQUEST_CHAT_MODEL"]
|
|
92
|
+
};
|
|
93
|
+
} catch (e) {
|
|
94
|
+
return {
|
|
95
|
+
type: "TELEGRAM_ERROR",
|
|
96
|
+
error: e.message,
|
|
97
|
+
_meta_actions: ["REQUEST_CHAT_MODEL"]
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}],
|
|
101
|
+
|
|
102
|
+
// Command: Send photo to Telegram
|
|
103
|
+
[/<sendPhotoToTelegramChannel>([\s\S]*?)<\/sendPhotoToTelegramChannel>/s, async (match) => {
|
|
104
|
+
try {
|
|
105
|
+
const data = JSON.parse(match[1].trim());
|
|
106
|
+
const result = await sendPhotoToTelegramChannel(data.photoUrl, data.caption || '');
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
type: "TELEGRAM_PHOTO_SENT",
|
|
110
|
+
success: result.success,
|
|
111
|
+
messageId: result.messageId,
|
|
112
|
+
error: result.error,
|
|
113
|
+
_meta_actions: ["REQUEST_CHAT_MODEL"]
|
|
114
|
+
};
|
|
115
|
+
} catch (e) {
|
|
116
|
+
return {
|
|
117
|
+
type: "TELEGRAM_ERROR",
|
|
118
|
+
error: e.message,
|
|
119
|
+
_meta_actions: ["REQUEST_CHAT_MODEL"]
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Webhook Handler (onPublicAPIRequest.js)
|
|
126
|
+
|
|
127
|
+
Receive messages from Telegram:
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
export const handler = async (event) => {
|
|
131
|
+
const body = JSON.parse(event.body || '{}');
|
|
132
|
+
|
|
133
|
+
// Telegram webhook payload
|
|
134
|
+
if (body.message || body.channel_post) {
|
|
135
|
+
const msg = body.message || body.channel_post;
|
|
136
|
+
|
|
137
|
+
// Auto-detect and save channel ID
|
|
138
|
+
if (msg.chat?.id && msg.chat?.type === 'channel') {
|
|
139
|
+
await setAgentSetting('agent_telegramChannelID', String(msg.chat.id));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Store message for context
|
|
143
|
+
await storeTelegramMessage(msg.message_id, {
|
|
144
|
+
text: msg.text || '',
|
|
145
|
+
date: msg.date,
|
|
146
|
+
from: msg.from?.username || msg.chat?.title || 'Unknown',
|
|
147
|
+
chatId: msg.chat?.id,
|
|
148
|
+
type: msg.photo ? 'photo' : 'text'
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Create chat with message for agent to process
|
|
152
|
+
await openkbs.chats({
|
|
153
|
+
chatTitle: `Telegram: ${msg.from?.username || 'Channel'}`,
|
|
154
|
+
message: JSON.stringify([{
|
|
155
|
+
type: "text",
|
|
156
|
+
text: `[TELEGRAM] ${msg.from?.username || 'User'}: ${msg.text || '[media]'}`
|
|
157
|
+
}])
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
statusCode: 200,
|
|
163
|
+
body: JSON.stringify({ ok: true })
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## memoryHelpers.js - Store Telegram Messages
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
export async function storeTelegramMessage(messageId, data) {
|
|
172
|
+
const itemId = `telegram_${messageId}`;
|
|
173
|
+
const body = {
|
|
174
|
+
...data,
|
|
175
|
+
storedAt: new Date().toISOString()
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
await openkbs.updateItem({ itemType: 'telegram', itemId, body });
|
|
180
|
+
} catch (e) {
|
|
181
|
+
await openkbs.createItem({ itemType: 'telegram', itemId, body });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## settings.json - Priority Items for Telegram
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"options": {
|
|
191
|
+
"priorityItems": [
|
|
192
|
+
{ "limit": 100, "prefix": "memory" },
|
|
193
|
+
{ "limit": 50, "prefix": "telegram" }
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## instructions.txt (LLM prompt)
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
Telegram Commands:
|
|
203
|
+
|
|
204
|
+
Send message:
|
|
205
|
+
<sendToTelegramChannel>{"message": "*Bold* message with _italic_"}</sendToTelegramChannel>
|
|
206
|
+
|
|
207
|
+
Send photo:
|
|
208
|
+
<sendPhotoToTelegramChannel>{"photoUrl": "https://...", "caption": "Photo caption"}</sendPhotoToTelegramChannel>
|
|
209
|
+
|
|
210
|
+
Markdown supported: *bold*, _italic_, `code`, [link](url)
|
|
211
|
+
|
|
212
|
+
Incoming Telegram messages appear with [TELEGRAM] prefix.
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Key Points
|
|
216
|
+
|
|
217
|
+
1. **Bot token in secrets**: `{{secrets.telegramBotToken}}`
|
|
218
|
+
2. **Channel ID**: Auto-detected from first message or set in secrets
|
|
219
|
+
3. **Webhook setup**: Set webhook URL to your KB's public API endpoint
|
|
220
|
+
4. **Message storage**: Store in `telegram_` prefix for context injection
|
|
221
|
+
5. **Markdown mode**: Default parse_mode is Markdown
|
|
222
|
+
6. **Photo URL**: Must be publicly accessible URL
|