@elizaos/plugin-telegram 2.0.0-alpha.7 → 2.0.0-alpha.9

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/dist/index.js ADDED
@@ -0,0 +1,1820 @@
1
+ // src/constants.ts
2
+ var MESSAGE_CONSTANTS = {
3
+ MAX_MESSAGES: 50,
4
+ RECENT_MESSAGE_COUNT: 5,
5
+ CHAT_HISTORY_COUNT: 10,
6
+ DEFAULT_SIMILARITY_THRESHOLD: 0.6,
7
+ DEFAULT_SIMILARITY_THRESHOLD_FOLLOW_UPS: 0.4,
8
+ INTEREST_DECAY_TIME: 5 * 60 * 1e3,
9
+ // 5 minutes
10
+ PARTIAL_INTEREST_DECAY: 3 * 60 * 1e3
11
+ // 3 minutes
12
+ };
13
+ var TELEGRAM_SERVICE_NAME = "telegram";
14
+
15
+ // src/service.ts
16
+ import {
17
+ ChannelType as ChannelType2,
18
+ EventType as EventType2,
19
+ Role,
20
+ Service,
21
+ createUniqueUuid as createUniqueUuid2,
22
+ logger as logger3
23
+ } from "@elizaos/core";
24
+ import { Telegraf } from "telegraf";
25
+
26
+ // src/messageManager.ts
27
+ import {
28
+ ChannelType,
29
+ EventType,
30
+ ModelType,
31
+ ServiceType,
32
+ createUniqueUuid,
33
+ logger as logger2
34
+ } from "@elizaos/core";
35
+ import { Markup as Markup2 } from "telegraf";
36
+
37
+ // src/utils.ts
38
+ import { Markup } from "telegraf";
39
+ import { logger } from "@elizaos/core";
40
+ var TELEGRAM_RESERVED_REGEX = /([_*[\]()~`>#+\-=|{}.!\\])/g;
41
+ function escapePlainText(text) {
42
+ if (!text) return "";
43
+ return text.replace(TELEGRAM_RESERVED_REGEX, "\\$1");
44
+ }
45
+ function escapePlainTextPreservingBlockquote(text) {
46
+ if (!text) return "";
47
+ return text.split("\n").map((line) => {
48
+ const match = line.match(/^(>+\s?)(.*)$/);
49
+ if (match) {
50
+ return match[1] + escapePlainText(match[2]);
51
+ }
52
+ return escapePlainText(line);
53
+ }).join("\n");
54
+ }
55
+ function escapeCode(text) {
56
+ if (!text) return "";
57
+ return text.replace(/([`\\])/g, "\\$1");
58
+ }
59
+ function escapeUrl(url) {
60
+ if (!url) return "";
61
+ return url.replace(/([)\\])/g, "\\$1");
62
+ }
63
+ function convertMarkdownToTelegram(markdown) {
64
+ const replacements = [];
65
+ function storeReplacement(formatted) {
66
+ const placeholder = `\0${replacements.length}\0`;
67
+ replacements.push(formatted);
68
+ return placeholder;
69
+ }
70
+ let converted = markdown;
71
+ converted = converted.replace(/```(\w+)?\n([\s\S]*?)```/g, (_match, lang, code) => {
72
+ const escapedCode = escapeCode(code);
73
+ const formatted = "```" + (lang || "") + "\n" + escapedCode + "```";
74
+ return storeReplacement(formatted);
75
+ });
76
+ converted = converted.replace(/`([^`]+)`/g, (_match, code) => {
77
+ const escapedCode = escapeCode(code);
78
+ const formatted = "`" + escapedCode + "`";
79
+ return storeReplacement(formatted);
80
+ });
81
+ converted = converted.replace(
82
+ /$begin:math:display$([^$end:math:display$]+)]$begin:math:text$([^)]+)$end:math:text$/g,
83
+ (_match, text, url) => {
84
+ const formattedText = escapePlainText(text);
85
+ const escapedURL = escapeUrl(url);
86
+ const formatted = `[${formattedText}](${escapedURL})`;
87
+ return storeReplacement(formatted);
88
+ }
89
+ );
90
+ converted = converted.replace(/\*\*([^*]+)\*\*/g, (_match, content) => {
91
+ const formattedContent = escapePlainText(content);
92
+ const formatted = `*${formattedContent}*`;
93
+ return storeReplacement(formatted);
94
+ });
95
+ converted = converted.replace(/~~([^~]+)~~/g, (_match, content) => {
96
+ const formattedContent = escapePlainText(content);
97
+ const formatted = `~${formattedContent}~`;
98
+ return storeReplacement(formatted);
99
+ });
100
+ converted = converted.replace(/(?<!\*)\*([^*\n]+)\*(?!\*)/g, (_match, content) => {
101
+ const formattedContent = escapePlainText(content);
102
+ const formatted = `_${formattedContent}_`;
103
+ return storeReplacement(formatted);
104
+ });
105
+ converted = converted.replace(/_([^_\n]+)_/g, (_match, content) => {
106
+ const formattedContent = escapePlainText(content);
107
+ const formatted = `_${formattedContent}_`;
108
+ return storeReplacement(formatted);
109
+ });
110
+ converted = converted.replace(/^(#{1,6})\s*(.*)$/gm, (_match, _hashes, headerContent) => {
111
+ const formatted = `*${escapePlainText(headerContent.trim())}*`;
112
+ return storeReplacement(formatted);
113
+ });
114
+ const NULL_CHAR = String.fromCharCode(0);
115
+ const PLACEHOLDER_PATTERN = new RegExp(`(${NULL_CHAR}\\d+${NULL_CHAR})`, "g");
116
+ const PLACEHOLDER_TEST = new RegExp(`^${NULL_CHAR}\\d+${NULL_CHAR}$`);
117
+ const PLACEHOLDER_REPLACE = new RegExp(`${NULL_CHAR}(\\d+)${NULL_CHAR}`, "g");
118
+ const finalEscaped = converted.split(PLACEHOLDER_PATTERN).map((segment) => {
119
+ if (PLACEHOLDER_TEST.test(segment)) {
120
+ return segment;
121
+ } else {
122
+ return escapePlainTextPreservingBlockquote(segment);
123
+ }
124
+ }).join("");
125
+ const finalResult = finalEscaped.replace(PLACEHOLDER_REPLACE, (_, index) => {
126
+ return replacements[parseInt(index)];
127
+ });
128
+ return finalResult;
129
+ }
130
+ function convertToTelegramButtons(buttons) {
131
+ if (!buttons) return [];
132
+ const telegramButtons = [];
133
+ for (const button of buttons) {
134
+ if (!button || !button.text || !button.url) {
135
+ logger.warn({ button }, "Invalid button configuration, skipping");
136
+ continue;
137
+ }
138
+ let telegramButton;
139
+ switch (button.kind) {
140
+ case "login":
141
+ telegramButton = Markup.button.login(button.text, button.url);
142
+ break;
143
+ case "url":
144
+ telegramButton = Markup.button.url(button.text, button.url);
145
+ break;
146
+ default:
147
+ logger.warn({ src: "plugin:telegram", buttonKind: button.kind }, "Unknown button kind, treating as URL button");
148
+ telegramButton = Markup.button.url(button.text, button.url);
149
+ break;
150
+ }
151
+ telegramButtons.push(telegramButton);
152
+ }
153
+ return telegramButtons;
154
+ }
155
+ function cleanText(text) {
156
+ if (!text) return "";
157
+ return text.split("\0").join("");
158
+ }
159
+
160
+ // src/messageManager.ts
161
+ import fs from "fs";
162
+ var MAX_MESSAGE_LENGTH = 4096;
163
+ var getChannelType = (chat) => {
164
+ switch (chat.type) {
165
+ case "private":
166
+ return ChannelType.DM;
167
+ case "group":
168
+ case "supergroup":
169
+ case "channel":
170
+ return ChannelType.GROUP;
171
+ default:
172
+ throw new Error(`Unrecognized Telegram chat type: ${chat.type}`);
173
+ }
174
+ };
175
+ var MessageManager = class {
176
+ bot;
177
+ runtime;
178
+ /**
179
+ * Constructor for creating a new instance of a BotAgent.
180
+ *
181
+ * @param {Telegraf<Context>} bot - The Telegraf instance used for interacting with the bot platform.
182
+ * @param {IAgentRuntime} runtime - The runtime environment for the agent.
183
+ */
184
+ constructor(bot, runtime) {
185
+ this.bot = bot;
186
+ this.runtime = runtime;
187
+ }
188
+ /**
189
+ * Process an image from a Telegram message to extract the image URL and description.
190
+ *
191
+ * @param {Message} message - The Telegram message object containing the image.
192
+ * @returns {Promise<{ description: string } | null>} The description of the processed image or null if no image found.
193
+ */
194
+ async processImage(message) {
195
+ try {
196
+ let imageUrl = null;
197
+ logger2.debug({ src: "plugin:telegram", agentId: this.runtime.agentId, messageId: message.message_id }, "Processing image from message");
198
+ if ("photo" in message && message.photo?.length > 0) {
199
+ const photo = message.photo[message.photo.length - 1];
200
+ const fileLink = await this.bot.telegram.getFileLink(photo.file_id);
201
+ imageUrl = fileLink.toString();
202
+ } else if ("document" in message && message.document?.mime_type?.startsWith("image/") && !message.document?.mime_type?.startsWith("application/pdf")) {
203
+ const fileLink = await this.bot.telegram.getFileLink(message.document.file_id);
204
+ imageUrl = fileLink.toString();
205
+ }
206
+ if (imageUrl) {
207
+ const { title, description } = await this.runtime.useModel(
208
+ ModelType.IMAGE_DESCRIPTION,
209
+ imageUrl
210
+ );
211
+ return { description: `[Image: ${title}
212
+ ${description}]` };
213
+ }
214
+ } catch (error) {
215
+ logger2.error({ src: "plugin:telegram", agentId: this.runtime.agentId, error: error instanceof Error ? error.message : String(error) }, "Error processing image");
216
+ }
217
+ return null;
218
+ }
219
+ /**
220
+ * Process a document from a Telegram message to extract the document URL and description.
221
+ * Handles PDFs and other document types by converting them to text when possible.
222
+ *
223
+ * @param {Message} message - The Telegram message object containing the document.
224
+ * @returns {Promise<{ description: string } | null>} The description of the processed document or null if no document found.
225
+ */
226
+ async processDocument(message) {
227
+ try {
228
+ if (!("document" in message) || !message.document) {
229
+ return null;
230
+ }
231
+ const document = message.document;
232
+ const fileLink = await this.bot.telegram.getFileLink(document.file_id);
233
+ const documentUrl = fileLink.toString();
234
+ logger2.debug({ src: "plugin:telegram", agentId: this.runtime.agentId, fileName: document.file_name, mimeType: document.mime_type, fileSize: document.file_size }, "Processing document");
235
+ const documentProcessor = this.getDocumentProcessor(document.mime_type);
236
+ if (documentProcessor) {
237
+ return await documentProcessor(document, documentUrl);
238
+ }
239
+ return {
240
+ title: `Document: ${document.file_name || "Unknown Document"}`,
241
+ fullText: "",
242
+ formattedDescription: `[Document: ${document.file_name || "Unknown Document"}
243
+ Type: ${document.mime_type || "unknown"}
244
+ Size: ${document.file_size || 0} bytes]`,
245
+ fileName: document.file_name || "Unknown Document",
246
+ mimeType: document.mime_type,
247
+ fileSize: document.file_size
248
+ };
249
+ } catch (error) {
250
+ logger2.error({ src: "plugin:telegram", agentId: this.runtime.agentId, error: error instanceof Error ? error.message : String(error) }, "Error processing document");
251
+ return null;
252
+ }
253
+ }
254
+ /**
255
+ * Get the appropriate document processor based on MIME type.
256
+ */
257
+ getDocumentProcessor(mimeType) {
258
+ if (!mimeType) return null;
259
+ const processors = {
260
+ "application/pdf": this.processPdfDocument.bind(this),
261
+ "text/": this.processTextDocument.bind(this),
262
+ // covers text/plain, text/csv, text/markdown, etc.
263
+ "application/json": this.processTextDocument.bind(this)
264
+ };
265
+ for (const [pattern, processor] of Object.entries(processors)) {
266
+ if (mimeType.startsWith(pattern)) {
267
+ return processor;
268
+ }
269
+ }
270
+ return null;
271
+ }
272
+ /**
273
+ * Process PDF documents by converting them to text.
274
+ */
275
+ async processPdfDocument(document, documentUrl) {
276
+ try {
277
+ const pdfService = this.runtime.getService(ServiceType.PDF);
278
+ if (!pdfService) {
279
+ logger2.warn({ src: "plugin:telegram", agentId: this.runtime.agentId }, "PDF service not available, using fallback");
280
+ return {
281
+ title: `PDF Document: ${document.file_name || "Unknown Document"}`,
282
+ fullText: "",
283
+ formattedDescription: `[PDF Document: ${document.file_name || "Unknown Document"}
284
+ Size: ${document.file_size || 0} bytes
285
+ Unable to extract text content]`,
286
+ fileName: document.file_name || "Unknown Document",
287
+ mimeType: document.mime_type,
288
+ fileSize: document.file_size
289
+ };
290
+ }
291
+ const response = await fetch(documentUrl);
292
+ if (!response.ok) {
293
+ throw new Error(`Failed to fetch PDF: ${response.status}`);
294
+ }
295
+ const pdfBuffer = await response.arrayBuffer();
296
+ const text = await pdfService.convertPdfToText(Buffer.from(pdfBuffer));
297
+ logger2.debug({ src: "plugin:telegram", agentId: this.runtime.agentId, fileName: document.file_name, charactersExtracted: text.length }, "PDF processed successfully");
298
+ return {
299
+ title: document.file_name || "Unknown Document",
300
+ fullText: text,
301
+ formattedDescription: `[PDF Document: ${document.file_name || "Unknown Document"}
302
+ Size: ${document.file_size || 0} bytes
303
+ Text extracted successfully: ${text.length} characters]`,
304
+ fileName: document.file_name || "Unknown Document",
305
+ mimeType: document.mime_type,
306
+ fileSize: document.file_size
307
+ };
308
+ } catch (error) {
309
+ logger2.error({ src: "plugin:telegram", agentId: this.runtime.agentId, fileName: document.file_name, error: error instanceof Error ? error.message : String(error) }, "Error processing PDF document");
310
+ return {
311
+ title: `PDF Document: ${document.file_name || "Unknown Document"}`,
312
+ fullText: "",
313
+ formattedDescription: `[PDF Document: ${document.file_name || "Unknown Document"}
314
+ Size: ${document.file_size || 0} bytes
315
+ Error: Unable to extract text content]`,
316
+ fileName: document.file_name || "Unknown Document",
317
+ mimeType: document.mime_type,
318
+ fileSize: document.file_size
319
+ };
320
+ }
321
+ }
322
+ /**
323
+ * Process text documents by fetching their content.
324
+ */
325
+ async processTextDocument(document, documentUrl) {
326
+ try {
327
+ const response = await fetch(documentUrl);
328
+ if (!response.ok) {
329
+ throw new Error(`Failed to fetch text document: ${response.status}`);
330
+ }
331
+ const text = await response.text();
332
+ logger2.debug({ src: "plugin:telegram", agentId: this.runtime.agentId, fileName: document.file_name, charactersExtracted: text.length }, "Text document processed successfully");
333
+ return {
334
+ title: document.file_name || "Unknown Document",
335
+ fullText: text,
336
+ formattedDescription: `[Text Document: ${document.file_name || "Unknown Document"}
337
+ Size: ${document.file_size || 0} bytes
338
+ Text extracted successfully: ${text.length} characters]`,
339
+ fileName: document.file_name || "Unknown Document",
340
+ mimeType: document.mime_type,
341
+ fileSize: document.file_size
342
+ };
343
+ } catch (error) {
344
+ logger2.error({ src: "plugin:telegram", agentId: this.runtime.agentId, fileName: document.file_name, error: error instanceof Error ? error.message : String(error) }, "Error processing text document");
345
+ return {
346
+ title: `Text Document: ${document.file_name || "Unknown Document"}`,
347
+ fullText: "",
348
+ formattedDescription: `[Text Document: ${document.file_name || "Unknown Document"}
349
+ Size: ${document.file_size || 0} bytes
350
+ Error: Unable to read content]`,
351
+ fileName: document.file_name || "Unknown Document",
352
+ mimeType: document.mime_type,
353
+ fileSize: document.file_size
354
+ };
355
+ }
356
+ }
357
+ /**
358
+ * Processes the message content, documents, and images to generate
359
+ * processed content and media attachments.
360
+ *
361
+ * @param {Message} message The message to process
362
+ * @returns {Promise<{ processedContent: string; attachments: Media[] }>} Processed content and media attachments
363
+ */
364
+ async processMessage(message) {
365
+ let processedContent = "";
366
+ let attachments = [];
367
+ if ("text" in message && message.text) {
368
+ processedContent = message.text;
369
+ } else if ("caption" in message && message.caption) {
370
+ processedContent = message.caption;
371
+ }
372
+ if ("document" in message && message.document) {
373
+ const document = message.document;
374
+ const documentInfo = await this.processDocument(message);
375
+ if (documentInfo) {
376
+ try {
377
+ const fileLink = await this.bot.telegram.getFileLink(document.file_id);
378
+ const title = documentInfo.title;
379
+ const fullText = documentInfo.fullText;
380
+ if (fullText) {
381
+ const documentContent = `
382
+
383
+ --- DOCUMENT CONTENT ---
384
+ Title: ${title}
385
+
386
+ Full Content:
387
+ ${fullText}
388
+ --- END DOCUMENT ---
389
+
390
+ `;
391
+ processedContent += documentContent;
392
+ }
393
+ attachments.push({
394
+ id: document.file_id,
395
+ url: fileLink.toString(),
396
+ title,
397
+ source: document.mime_type?.startsWith("application/pdf") ? "PDF" : "Document",
398
+ description: documentInfo.formattedDescription,
399
+ text: fullText
400
+ });
401
+ logger2.debug({ src: "plugin:telegram", agentId: this.runtime.agentId, fileName: documentInfo.fileName }, "Document processed successfully");
402
+ } catch (error) {
403
+ logger2.error({ src: "plugin:telegram", agentId: this.runtime.agentId, fileName: documentInfo.fileName, error: error instanceof Error ? error.message : String(error) }, "Error processing document");
404
+ attachments.push({
405
+ id: document.file_id,
406
+ url: "",
407
+ title: `Document: ${documentInfo.fileName}`,
408
+ source: "Document",
409
+ description: `Document processing failed: ${documentInfo.fileName}`,
410
+ text: `Document: ${documentInfo.fileName}
411
+ Size: ${documentInfo.fileSize || 0} bytes
412
+ Type: ${documentInfo.mimeType || "unknown"}`
413
+ });
414
+ }
415
+ } else {
416
+ attachments.push({
417
+ id: document.file_id,
418
+ url: "",
419
+ title: `Document: ${document.file_name || "Unknown Document"}`,
420
+ source: "Document",
421
+ description: `Document: ${document.file_name || "Unknown Document"}`,
422
+ text: `Document: ${document.file_name || "Unknown Document"}
423
+ Size: ${document.file_size || 0} bytes
424
+ Type: ${document.mime_type || "unknown"}`
425
+ });
426
+ }
427
+ }
428
+ if ("photo" in message && message.photo?.length > 0) {
429
+ const imageInfo = await this.processImage(message);
430
+ if (imageInfo) {
431
+ const photo = message.photo[message.photo.length - 1];
432
+ const fileLink = await this.bot.telegram.getFileLink(photo.file_id);
433
+ attachments.push({
434
+ id: photo.file_id,
435
+ url: fileLink.toString(),
436
+ title: "Image Attachment",
437
+ source: "Image",
438
+ description: imageInfo.description,
439
+ text: imageInfo.description
440
+ });
441
+ }
442
+ }
443
+ logger2.debug({ src: "plugin:telegram", agentId: this.runtime.agentId, hasContent: !!processedContent, attachmentsCount: attachments.length }, "Message processed");
444
+ return { processedContent, attachments };
445
+ }
446
+ /**
447
+ * Sends a message in chunks, handling attachments and splitting the message if necessary
448
+ *
449
+ * @param {Context} ctx - The context object representing the current state of the bot
450
+ * @param {TelegramContent} content - The content of the message to be sent
451
+ * @param {number} [replyToMessageId] - The ID of the message to reply to, if any
452
+ * @returns {Promise<Message.TextMessage[]>} - An array of TextMessage objects representing the messages sent
453
+ */
454
+ async sendMessageInChunks(ctx, content, replyToMessageId) {
455
+ if (content.attachments && content.attachments.length > 0) {
456
+ content.attachments.map(async (attachment) => {
457
+ const typeMap = {
458
+ "image/gif": "animation" /* ANIMATION */,
459
+ image: "photo" /* PHOTO */,
460
+ doc: "document" /* DOCUMENT */,
461
+ video: "video" /* VIDEO */,
462
+ audio: "audio" /* AUDIO */
463
+ };
464
+ let mediaType = void 0;
465
+ for (const prefix in typeMap) {
466
+ if (attachment.contentType?.startsWith(prefix)) {
467
+ mediaType = typeMap[prefix];
468
+ break;
469
+ }
470
+ }
471
+ if (!mediaType) {
472
+ throw new Error(
473
+ `Unsupported Telegram attachment content type: ${attachment.contentType}`
474
+ );
475
+ }
476
+ await this.sendMedia(ctx, attachment.url, mediaType, attachment.description);
477
+ });
478
+ return [];
479
+ } else {
480
+ const chunks = this.splitMessage(content.text ?? "");
481
+ const sentMessages = [];
482
+ const telegramButtons = convertToTelegramButtons(content.buttons ?? []);
483
+ if (!ctx.chat) {
484
+ logger2.error({ src: "plugin:telegram", agentId: this.runtime.agentId }, "sendMessageInChunks: ctx.chat is undefined");
485
+ return [];
486
+ }
487
+ await ctx.telegram.sendChatAction(ctx.chat.id, "typing");
488
+ for (let i = 0; i < chunks.length; i++) {
489
+ const chunk = convertMarkdownToTelegram(chunks[i]);
490
+ if (!ctx.chat) {
491
+ logger2.error({ src: "plugin:telegram", agentId: this.runtime.agentId }, "sendMessageInChunks loop: ctx.chat is undefined");
492
+ continue;
493
+ }
494
+ const sentMessage = await ctx.telegram.sendMessage(ctx.chat.id, chunk, {
495
+ reply_parameters: i === 0 && replyToMessageId ? { message_id: replyToMessageId } : void 0,
496
+ parse_mode: "MarkdownV2",
497
+ ...Markup2.inlineKeyboard(telegramButtons)
498
+ });
499
+ sentMessages.push(sentMessage);
500
+ }
501
+ return sentMessages;
502
+ }
503
+ }
504
+ /**
505
+ * Sends media to a chat using the Telegram API.
506
+ *
507
+ * @param {Context} ctx - The context object containing information about the current chat.
508
+ * @param {string} mediaPath - The path to the media to be sent, either a URL or a local file path.
509
+ * @param {MediaType} type - The type of media being sent (PHOTO, VIDEO, DOCUMENT, AUDIO, or ANIMATION).
510
+ * @param {string} [caption] - Optional caption for the media being sent.
511
+ *
512
+ * @returns {Promise<void>} A Promise that resolves when the media is successfully sent.
513
+ */
514
+ async sendMedia(ctx, mediaPath, type, caption) {
515
+ try {
516
+ const isUrl = /^(http|https):\/\//.test(mediaPath);
517
+ const sendFunctionMap = {
518
+ ["photo" /* PHOTO */]: ctx.telegram.sendPhoto.bind(ctx.telegram),
519
+ ["video" /* VIDEO */]: ctx.telegram.sendVideo.bind(ctx.telegram),
520
+ ["document" /* DOCUMENT */]: ctx.telegram.sendDocument.bind(ctx.telegram),
521
+ ["audio" /* AUDIO */]: ctx.telegram.sendAudio.bind(ctx.telegram),
522
+ ["animation" /* ANIMATION */]: ctx.telegram.sendAnimation.bind(ctx.telegram)
523
+ };
524
+ const sendFunction = sendFunctionMap[type];
525
+ if (!sendFunction) {
526
+ throw new Error(`Unsupported media type: ${type}`);
527
+ }
528
+ if (!ctx.chat) {
529
+ throw new Error("sendMedia: ctx.chat is undefined");
530
+ }
531
+ if (isUrl) {
532
+ await sendFunction(ctx.chat.id, mediaPath, { caption });
533
+ } else {
534
+ if (!fs.existsSync(mediaPath)) {
535
+ throw new Error(`File not found at path: ${mediaPath}`);
536
+ }
537
+ const fileStream = fs.createReadStream(mediaPath);
538
+ try {
539
+ if (!ctx.chat) {
540
+ throw new Error("sendMedia (file): ctx.chat is undefined");
541
+ }
542
+ await sendFunction(ctx.chat.id, { source: fileStream }, { caption });
543
+ } finally {
544
+ fileStream.destroy();
545
+ }
546
+ }
547
+ logger2.debug({ src: "plugin:telegram", agentId: this.runtime.agentId, mediaType: type, mediaPath }, "Media sent successfully");
548
+ } catch (error) {
549
+ logger2.error({ src: "plugin:telegram", agentId: this.runtime.agentId, mediaType: type, mediaPath, error: error instanceof Error ? error.message : String(error) }, "Failed to send media");
550
+ throw error;
551
+ }
552
+ }
553
+ /**
554
+ * Splits a given text into an array of strings based on the maximum message length.
555
+ *
556
+ * @param {string} text - The text to split into chunks.
557
+ * @returns {string[]} An array of strings with each element representing a chunk of the original text.
558
+ */
559
+ splitMessage(text) {
560
+ const chunks = [];
561
+ if (!text) return chunks;
562
+ let currentChunk = "";
563
+ const lines = text.split("\n");
564
+ for (const line of lines) {
565
+ if (currentChunk.length + line.length + 1 <= MAX_MESSAGE_LENGTH) {
566
+ currentChunk += (currentChunk ? "\n" : "") + line;
567
+ } else {
568
+ if (currentChunk) chunks.push(currentChunk);
569
+ currentChunk = line;
570
+ }
571
+ }
572
+ if (currentChunk) chunks.push(currentChunk);
573
+ return chunks;
574
+ }
575
+ /**
576
+ * Handle incoming messages from Telegram and process them accordingly.
577
+ * @param {Context} ctx - The context object containing information about the message.
578
+ * @returns {Promise<void>}
579
+ */
580
+ async handleMessage(ctx) {
581
+ if (!ctx.message || !ctx.from) return;
582
+ const message = ctx.message;
583
+ try {
584
+ const entityId = createUniqueUuid(this.runtime, ctx.from.id.toString());
585
+ const threadId = "is_topic_message" in message && message.is_topic_message ? message.message_thread_id?.toString() : void 0;
586
+ if (!ctx.chat) {
587
+ logger2.error({ src: "plugin:telegram", agentId: this.runtime.agentId }, "handleMessage: ctx.chat is undefined");
588
+ return;
589
+ }
590
+ const telegramRoomid = threadId ? `${ctx.chat.id}-${threadId}` : ctx.chat.id.toString();
591
+ const roomId = createUniqueUuid(this.runtime, telegramRoomid);
592
+ const messageId = createUniqueUuid(this.runtime, message?.message_id?.toString());
593
+ const { processedContent, attachments } = await this.processMessage(message);
594
+ const cleanedContent = cleanText(processedContent);
595
+ const cleanedAttachments = attachments.map((att) => ({
596
+ ...att,
597
+ text: cleanText(att.text),
598
+ description: cleanText(att.description),
599
+ title: cleanText(att.title)
600
+ }));
601
+ if (!cleanedContent && cleanedAttachments.length === 0) {
602
+ return;
603
+ }
604
+ const chat = message.chat;
605
+ const channelType = getChannelType(chat);
606
+ const sourceId = createUniqueUuid(this.runtime, "" + chat.id);
607
+ await this.runtime.ensureConnection({
608
+ entityId,
609
+ roomId,
610
+ userName: ctx.from.username,
611
+ name: ctx.from.first_name,
612
+ source: "telegram",
613
+ channelId: telegramRoomid,
614
+ type: channelType,
615
+ worldId: createUniqueUuid(this.runtime, roomId),
616
+ worldName: telegramRoomid
617
+ });
618
+ const memory = {
619
+ id: messageId,
620
+ entityId,
621
+ agentId: this.runtime.agentId,
622
+ roomId,
623
+ content: {
624
+ text: cleanedContent || " ",
625
+ attachments: cleanedAttachments,
626
+ source: "telegram",
627
+ channelType,
628
+ inReplyTo: "reply_to_message" in message && message.reply_to_message ? createUniqueUuid(this.runtime, message.reply_to_message.message_id.toString()) : void 0
629
+ },
630
+ metadata: {
631
+ entityName: ctx.from.first_name,
632
+ entityUserName: ctx.from.username,
633
+ fromBot: ctx.from.is_bot,
634
+ // include very technical/exact reference to this user for security reasons
635
+ // don't remove or change this, spartan needs this
636
+ fromId: chat.id,
637
+ sourceId,
638
+ // why message? all Memories contain content (which is basically a message)
639
+ // what are the other types? see MemoryType
640
+ type: "message"
641
+ // MemoryType.MESSAGE
642
+ // scope: `shared`, `private`, or `room`
643
+ },
644
+ createdAt: message.date * 1e3
645
+ };
646
+ const callback = async (content, _files) => {
647
+ try {
648
+ if (!content.text) return [];
649
+ let sentMessages = false;
650
+ if (content?.channelType === "DM") {
651
+ sentMessages = [];
652
+ if (ctx.from) {
653
+ const res = await this.bot.telegram.sendMessage(ctx.from.id, content.text);
654
+ sentMessages.push(res);
655
+ }
656
+ } else {
657
+ sentMessages = await this.sendMessageInChunks(ctx, content, message.message_id);
658
+ }
659
+ if (!Array.isArray(sentMessages)) return [];
660
+ const memories = [];
661
+ for (let i = 0; i < sentMessages.length; i++) {
662
+ const sentMessage = sentMessages[i];
663
+ const responseMemory = {
664
+ id: createUniqueUuid(this.runtime, sentMessage.message_id.toString()),
665
+ entityId: this.runtime.agentId,
666
+ agentId: this.runtime.agentId,
667
+ roomId,
668
+ content: {
669
+ ...content,
670
+ source: "telegram",
671
+ text: sentMessage.text,
672
+ inReplyTo: messageId,
673
+ channelType
674
+ },
675
+ createdAt: sentMessage.date * 1e3
676
+ };
677
+ await this.runtime.createMemory(responseMemory, "messages");
678
+ memories.push(responseMemory);
679
+ }
680
+ return memories;
681
+ } catch (error) {
682
+ logger2.error({ src: "plugin:telegram", agentId: this.runtime.agentId, error: error instanceof Error ? error.message : String(error) }, "Error in message callback");
683
+ return [];
684
+ }
685
+ };
686
+ if (this.runtime.hasElizaOS()) {
687
+ await this.runtime.elizaOS.handleMessage(this.runtime.agentId, memory, {
688
+ onResponse: async (content) => {
689
+ await callback(content);
690
+ }
691
+ });
692
+ } else if (this.runtime.messageService) {
693
+ await this.runtime.messageService.handleMessage(this.runtime, memory, callback);
694
+ } else {
695
+ logger2.error({ src: "plugin:telegram", agentId: this.runtime.agentId }, "Message service is not available");
696
+ throw new Error(
697
+ "Message service is not initialized. Ensure the message service is properly configured."
698
+ );
699
+ }
700
+ } catch (error) {
701
+ logger2.error({ src: "plugin:telegram", agentId: this.runtime.agentId, chatId: ctx.chat?.id, messageId: ctx.message?.message_id, from: ctx.from?.username || ctx.from?.id, error: error instanceof Error ? error.message : String(error) }, "Error handling Telegram message");
702
+ throw error;
703
+ }
704
+ }
705
+ /**
706
+ * Handles the reaction event triggered by a user reacting to a message.
707
+ * @param {NarrowedContext<Context<Update>, Update.MessageReactionUpdate>} ctx The context of the message reaction update
708
+ * @returns {Promise<void>} A Promise that resolves when the reaction handling is complete
709
+ */
710
+ async handleReaction(ctx) {
711
+ if (!ctx.update.message_reaction || !ctx.from) return;
712
+ const reaction = ctx.update.message_reaction;
713
+ const reactedToMessageId = reaction.message_id;
714
+ const originalMessagePlaceholder = {
715
+ message_id: reactedToMessageId,
716
+ chat: reaction.chat,
717
+ from: ctx.from,
718
+ date: Math.floor(Date.now() / 1e3)
719
+ };
720
+ const reactionType = reaction.new_reaction[0].type;
721
+ const reactionEmoji = reaction.new_reaction[0].type;
722
+ try {
723
+ const entityId = createUniqueUuid(this.runtime, ctx.from.id.toString());
724
+ const roomId = createUniqueUuid(this.runtime, ctx.chat.id.toString());
725
+ const reactionId = createUniqueUuid(
726
+ this.runtime,
727
+ `${reaction.message_id}-${ctx.from.id}-${Date.now()}`
728
+ );
729
+ const memory = {
730
+ id: reactionId,
731
+ entityId,
732
+ agentId: this.runtime.agentId,
733
+ roomId,
734
+ content: {
735
+ channelType: getChannelType(reaction.chat),
736
+ text: `Reacted with: ${reactionType === "emoji" ? reactionEmoji : reactionType}`,
737
+ source: "telegram",
738
+ inReplyTo: createUniqueUuid(this.runtime, reaction.message_id.toString())
739
+ },
740
+ createdAt: Date.now()
741
+ };
742
+ const callback = async (content) => {
743
+ try {
744
+ const replyText = content.text ?? "";
745
+ const sentMessage = await ctx.reply(replyText);
746
+ const responseMemory = {
747
+ id: createUniqueUuid(this.runtime, sentMessage.message_id.toString()),
748
+ entityId: this.runtime.agentId,
749
+ agentId: this.runtime.agentId,
750
+ roomId,
751
+ content: {
752
+ ...content,
753
+ inReplyTo: reactionId
754
+ },
755
+ createdAt: sentMessage.date * 1e3
756
+ };
757
+ return [responseMemory];
758
+ } catch (error) {
759
+ logger2.error({ src: "plugin:telegram", agentId: this.runtime.agentId, error: error instanceof Error ? error.message : String(error) }, "Error in reaction callback");
760
+ return [];
761
+ }
762
+ };
763
+ this.runtime.emitEvent(EventType.REACTION_RECEIVED, {
764
+ runtime: this.runtime,
765
+ message: memory,
766
+ callback,
767
+ source: "telegram",
768
+ ctx,
769
+ originalMessage: originalMessagePlaceholder,
770
+ // Cast needed due to placeholder
771
+ reactionString: reactionType === "emoji" ? reactionEmoji : reactionType,
772
+ originalReaction: reaction.new_reaction[0]
773
+ });
774
+ this.runtime.emitEvent("TELEGRAM_REACTION_RECEIVED" /* REACTION_RECEIVED */, {
775
+ runtime: this.runtime,
776
+ message: memory,
777
+ callback,
778
+ source: "telegram",
779
+ ctx,
780
+ originalMessage: originalMessagePlaceholder,
781
+ // Cast needed due to placeholder
782
+ reactionString: reactionType === "emoji" ? reactionEmoji : reactionType,
783
+ originalReaction: reaction.new_reaction[0]
784
+ });
785
+ } catch (error) {
786
+ logger2.error({ src: "plugin:telegram", agentId: this.runtime.agentId, error: error instanceof Error ? error.message : String(error) }, "Error handling reaction");
787
+ }
788
+ }
789
+ /**
790
+ * Sends a message to a Telegram chat and emits appropriate events
791
+ * @param {number | string} chatId - The Telegram chat ID to send the message to
792
+ * @param {Content} content - The content to send
793
+ * @param {number} [replyToMessageId] - Optional message ID to reply to
794
+ * @returns {Promise<Message.TextMessage[]>} The sent messages
795
+ */
796
+ async sendMessage(chatId, content, replyToMessageId) {
797
+ try {
798
+ const ctx = {
799
+ chat: { id: chatId },
800
+ telegram: this.bot.telegram
801
+ };
802
+ const sentMessages = await this.sendMessageInChunks(
803
+ ctx,
804
+ content,
805
+ replyToMessageId
806
+ );
807
+ if (!sentMessages?.length) return [];
808
+ const roomId = createUniqueUuid(this.runtime, chatId.toString());
809
+ const memories = [];
810
+ for (const sentMessage of sentMessages) {
811
+ const memory = {
812
+ id: createUniqueUuid(this.runtime, sentMessage.message_id.toString()),
813
+ entityId: this.runtime.agentId,
814
+ agentId: this.runtime.agentId,
815
+ roomId,
816
+ content: {
817
+ ...content,
818
+ text: sentMessage.text,
819
+ source: "telegram",
820
+ channelType: getChannelType({
821
+ id: typeof chatId === "string" ? Number.parseInt(chatId, 10) : chatId,
822
+ type: "private"
823
+ // Default to private, will be overridden if in context
824
+ })
825
+ },
826
+ createdAt: sentMessage.date * 1e3
827
+ };
828
+ await this.runtime.createMemory(memory, "messages");
829
+ memories.push(memory);
830
+ }
831
+ if (memories.length > 0) {
832
+ this.runtime.emitEvent(EventType.MESSAGE_SENT, {
833
+ runtime: this.runtime,
834
+ message: memories[0],
835
+ source: "telegram"
836
+ });
837
+ }
838
+ this.runtime.emitEvent(
839
+ "TELEGRAM_MESSAGE_SENT" /* MESSAGE_SENT */,
840
+ {
841
+ runtime: this.runtime,
842
+ source: "telegram",
843
+ originalMessages: sentMessages,
844
+ chatId,
845
+ message: memories[0]
846
+ }
847
+ );
848
+ return sentMessages;
849
+ } catch (error) {
850
+ logger2.error({ src: "plugin:telegram", agentId: this.runtime.agentId, chatId, error: error instanceof Error ? error.message : String(error) }, "Error sending message to Telegram");
851
+ return [];
852
+ }
853
+ }
854
+ };
855
+
856
+ // src/service.ts
857
+ var TelegramService = class _TelegramService extends Service {
858
+ static serviceType = TELEGRAM_SERVICE_NAME;
859
+ capabilityDescription = "The agent is able to send and receive messages on telegram";
860
+ bot;
861
+ messageManager;
862
+ options;
863
+ knownChats = /* @__PURE__ */ new Map();
864
+ syncedEntityIds = /* @__PURE__ */ new Set();
865
+ /**
866
+ * Constructor for TelegramService class.
867
+ * @param {IAgentRuntime} runtime - The runtime object for the agent.
868
+ */
869
+ constructor(runtime) {
870
+ super(runtime);
871
+ if (!runtime) {
872
+ this.bot = null;
873
+ this.messageManager = null;
874
+ return;
875
+ }
876
+ logger3.debug({ src: "plugin:telegram", agentId: runtime.agentId }, "Constructing TelegramService");
877
+ const botToken = runtime.getSetting("TELEGRAM_BOT_TOKEN");
878
+ if (!botToken || botToken.trim() === "") {
879
+ logger3.warn({ src: "plugin:telegram", agentId: runtime.agentId }, "Bot token not provided, Telegram functionality unavailable");
880
+ this.bot = null;
881
+ this.messageManager = null;
882
+ return;
883
+ }
884
+ this.options = {
885
+ telegram: {
886
+ apiRoot: runtime.getSetting("TELEGRAM_API_ROOT") || process.env.TELEGRAM_API_ROOT || "https://api.telegram.org"
887
+ }
888
+ };
889
+ try {
890
+ this.bot = new Telegraf(botToken, this.options);
891
+ this.messageManager = new MessageManager(this.bot, this.runtime);
892
+ logger3.debug({ src: "plugin:telegram", agentId: runtime.agentId }, "TelegramService constructor completed");
893
+ } catch (error) {
894
+ logger3.error({ src: "plugin:telegram", agentId: runtime.agentId, error: error instanceof Error ? error.message : String(error) }, "Failed to initialize Telegram bot");
895
+ this.bot = null;
896
+ this.messageManager = null;
897
+ }
898
+ }
899
+ /**
900
+ * Starts the Telegram service for the given runtime.
901
+ *
902
+ * @param {IAgentRuntime} runtime - The agent runtime to start the Telegram service for.
903
+ * @returns {Promise<TelegramService>} A promise that resolves with the initialized TelegramService.
904
+ */
905
+ static async start(runtime) {
906
+ const service = new _TelegramService(runtime);
907
+ if (!service.bot) {
908
+ logger3.warn({ src: "plugin:telegram", agentId: runtime.agentId }, "Service started without bot functionality");
909
+ return service;
910
+ }
911
+ const maxRetries = 5;
912
+ let retryCount = 0;
913
+ let lastError = null;
914
+ while (retryCount < maxRetries) {
915
+ try {
916
+ logger3.info({ src: "plugin:telegram", agentId: runtime.agentId, agentName: runtime.character.name }, "Starting Telegram bot");
917
+ await service.initializeBot();
918
+ service.setupMiddlewares();
919
+ service.setupMessageHandlers();
920
+ await service.bot.telegram.getMe();
921
+ logger3.success({ src: "plugin:telegram", agentId: runtime.agentId, agentName: runtime.character.name }, "Telegram bot started successfully");
922
+ return service;
923
+ } catch (error) {
924
+ lastError = error instanceof Error ? error : new Error(String(error));
925
+ logger3.error({ src: "plugin:telegram", agentId: runtime.agentId, attempt: retryCount + 1, error: lastError.message }, "Initialization attempt failed");
926
+ retryCount++;
927
+ if (retryCount < maxRetries) {
928
+ const delay = 2 ** retryCount * 1e3;
929
+ logger3.info({ src: "plugin:telegram", agentId: runtime.agentId, delaySeconds: delay / 1e3 }, "Retrying initialization");
930
+ await new Promise((resolve) => setTimeout(resolve, delay));
931
+ }
932
+ }
933
+ }
934
+ logger3.error({ src: "plugin:telegram", agentId: runtime.agentId, maxRetries, error: lastError?.message }, "Initialization failed after all attempts");
935
+ return service;
936
+ }
937
+ /**
938
+ * Stops the agent runtime.
939
+ * @param {IAgentRuntime} runtime - The agent runtime to stop
940
+ */
941
+ static async stop(runtime) {
942
+ const tgClient = runtime.getService(TELEGRAM_SERVICE_NAME);
943
+ if (tgClient) {
944
+ await tgClient.stop();
945
+ }
946
+ }
947
+ /**
948
+ * Asynchronously stops the bot.
949
+ *
950
+ * @returns A Promise that resolves once the bot has stopped.
951
+ */
952
+ async stop() {
953
+ this.bot?.stop();
954
+ }
955
+ /**
956
+ * Initializes the Telegram bot by launching it, getting bot info, and setting up message manager.
957
+ * @returns {Promise<void>} A Promise that resolves when the initialization is complete.
958
+ */
959
+ async initializeBot() {
960
+ this.bot?.start((ctx) => {
961
+ this.runtime.emitEvent(
962
+ "TELEGRAM_SLASH_START" /* SLASH_START */,
963
+ {
964
+ ctx,
965
+ runtime: this.runtime,
966
+ source: "telegram"
967
+ }
968
+ );
969
+ });
970
+ this.bot?.launch({
971
+ dropPendingUpdates: true,
972
+ allowedUpdates: ["message", "message_reaction"]
973
+ });
974
+ const botInfo = await this.bot.telegram.getMe();
975
+ logger3.debug({ src: "plugin:telegram", agentId: this.runtime.agentId, botId: botInfo.id, botUsername: botInfo.username }, "Bot info retrieved");
976
+ process.once("SIGINT", () => this.bot?.stop("SIGINT"));
977
+ process.once("SIGTERM", () => this.bot?.stop("SIGTERM"));
978
+ }
979
+ /**
980
+ * Sets up the middleware chain for preprocessing messages before they reach handlers.
981
+ * This critical method establishes a sequential processing pipeline that:
982
+ *
983
+ * 1. Authorization - Verifies if a chat is allowed to interact with the bot based on configured settings
984
+ * 2. Chat Discovery - Ensures chat entities and worlds exist in the runtime, creating them if needed
985
+ * 3. Forum Topics - Handles Telegram forum topics as separate rooms for better conversation management
986
+ * 4. Entity Synchronization - Ensures message senders are properly synchronized as entities
987
+ *
988
+ * The middleware chain runs in sequence for each message, with each step potentially
989
+ * enriching the context or stopping processing if conditions aren't met.
990
+ * This preprocessing is essential for maintaining consistent state before message handlers execute.
991
+ *
992
+ * @private
993
+ */
994
+ setupMiddlewares() {
995
+ this.bot?.use(this.authorizationMiddleware.bind(this));
996
+ this.bot?.use(this.chatAndEntityMiddleware.bind(this));
997
+ }
998
+ /**
999
+ * Authorization middleware - checks if chat is allowed to interact with the bot
1000
+ * based on the TELEGRAM_ALLOWED_CHATS configuration.
1001
+ *
1002
+ * @param {Context} ctx - The context of the incoming update
1003
+ * @param {Function} next - The function to call to proceed to the next middleware
1004
+ * @returns {Promise<void>}
1005
+ * @private
1006
+ */
1007
+ async authorizationMiddleware(ctx, next) {
1008
+ if (!await this.isGroupAuthorized(ctx)) {
1009
+ logger3.debug({ src: "plugin:telegram", agentId: this.runtime.agentId, chatId: ctx.chat?.id }, "Chat not authorized, skipping");
1010
+ return;
1011
+ }
1012
+ await next();
1013
+ }
1014
+ /**
1015
+ * Chat and entity management middleware - handles new chats, forum topics, and entity synchronization.
1016
+ * This middleware implements decision logic to determine which operations are needed based on
1017
+ * the chat type and whether we've seen this chat before.
1018
+ *
1019
+ * @param {Context} ctx - The context of the incoming update
1020
+ * @param {Function} next - The function to call to proceed to the next middleware
1021
+ * @returns {Promise<void>}
1022
+ * @private
1023
+ */
1024
+ async chatAndEntityMiddleware(ctx, next) {
1025
+ if (!ctx.chat) return next();
1026
+ const chatId = ctx.chat.id.toString();
1027
+ if (!this.knownChats.has(chatId)) {
1028
+ await this.handleNewChat(ctx);
1029
+ return next();
1030
+ }
1031
+ await this.processExistingChat(ctx);
1032
+ await next();
1033
+ }
1034
+ /**
1035
+ * Process an existing chat based on chat type and message properties.
1036
+ * Different chat types require different processing steps.
1037
+ *
1038
+ * @param {Context} ctx - The context of the incoming update
1039
+ * @returns {Promise<void>}
1040
+ * @private
1041
+ */
1042
+ async processExistingChat(ctx) {
1043
+ if (!ctx.chat) return;
1044
+ const chat = ctx.chat;
1045
+ if (chat.type === "supergroup" && chat.is_forum && ctx.message?.message_thread_id) {
1046
+ try {
1047
+ await this.handleForumTopic(ctx);
1048
+ } catch (error) {
1049
+ logger3.error({ src: "plugin:telegram", agentId: this.runtime.agentId, chatId: chat.id, error: error instanceof Error ? error.message : String(error) }, "Error handling forum topic");
1050
+ }
1051
+ }
1052
+ if (ctx.from && ctx.chat.type !== "private") {
1053
+ await this.syncEntity(ctx);
1054
+ }
1055
+ }
1056
+ /**
1057
+ * Sets up message and reaction handlers for the bot.
1058
+ * Configures event handlers to process incoming messages and reactions.
1059
+ *
1060
+ * @private
1061
+ */
1062
+ setupMessageHandlers() {
1063
+ this.bot?.on("message", async (ctx) => {
1064
+ try {
1065
+ await this.messageManager.handleMessage(ctx);
1066
+ } catch (error) {
1067
+ logger3.error({ src: "plugin:telegram", agentId: this.runtime.agentId, error: error instanceof Error ? error.message : String(error) }, "Error handling message");
1068
+ }
1069
+ });
1070
+ this.bot?.on("message_reaction", async (ctx) => {
1071
+ try {
1072
+ await this.messageManager.handleReaction(ctx);
1073
+ } catch (error) {
1074
+ logger3.error({ src: "plugin:telegram", agentId: this.runtime.agentId, error: error instanceof Error ? error.message : String(error) }, "Error handling reaction");
1075
+ }
1076
+ });
1077
+ }
1078
+ /**
1079
+ * Checks if a group is authorized, based on the TELEGRAM_ALLOWED_CHATS setting.
1080
+ * @param {Context} ctx - The context of the incoming update.
1081
+ * @returns {Promise<boolean>} A Promise that resolves with a boolean indicating if the group is authorized.
1082
+ */
1083
+ async isGroupAuthorized(ctx) {
1084
+ const chatId = ctx.chat?.id.toString();
1085
+ if (!chatId) return false;
1086
+ const allowedChats = this.runtime.getSetting("TELEGRAM_ALLOWED_CHATS");
1087
+ if (!allowedChats) {
1088
+ return true;
1089
+ }
1090
+ try {
1091
+ const allowedChatsList = JSON.parse(allowedChats);
1092
+ return allowedChatsList.includes(chatId);
1093
+ } catch (error) {
1094
+ logger3.error({ src: "plugin:telegram", agentId: this.runtime.agentId, error: error instanceof Error ? error.message : String(error) }, "Error parsing TELEGRAM_ALLOWED_CHATS");
1095
+ return false;
1096
+ }
1097
+ }
1098
+ /**
1099
+ * Synchronizes an entity from a message context with the runtime system.
1100
+ * This method handles three cases:
1101
+ * 1. Message sender - most common case
1102
+ * 2. New chat member - when a user joins the chat
1103
+ * 3. Left chat member - when a user leaves the chat
1104
+ *
1105
+ * @param {Context} ctx - The context of the incoming update
1106
+ * @returns {Promise<void>}
1107
+ * @private
1108
+ */
1109
+ async syncEntity(ctx) {
1110
+ if (!ctx.chat) return;
1111
+ const chat = ctx.chat;
1112
+ const chatId = chat.id.toString();
1113
+ const worldId = createUniqueUuid2(this.runtime, chatId);
1114
+ const roomId = createUniqueUuid2(
1115
+ this.runtime,
1116
+ ctx.message?.message_thread_id ? `${ctx.chat.id}-${ctx.message.message_thread_id}` : ctx.chat.id.toString()
1117
+ );
1118
+ await this.syncMessageSender(ctx, worldId, roomId, chatId);
1119
+ await this.syncNewChatMember(ctx, worldId, roomId, chatId);
1120
+ await this.syncLeftChatMember(ctx);
1121
+ }
1122
+ /**
1123
+ * Synchronizes the message sender entity with the runtime system.
1124
+ * This is the most common entity sync case.
1125
+ *
1126
+ * @param {Context} ctx - The context of the incoming update
1127
+ * @param {UUID} worldId - The ID of the world
1128
+ * @param {UUID} roomId - The ID of the room
1129
+ * @param {string} chatId - The ID of the chat
1130
+ * @returns {Promise<void>}
1131
+ * @private
1132
+ */
1133
+ async syncMessageSender(ctx, worldId, roomId, chatId) {
1134
+ if (ctx.from && !this.syncedEntityIds.has(ctx.from.id.toString())) {
1135
+ const telegramId = ctx.from.id.toString();
1136
+ const entityId = createUniqueUuid2(this.runtime, telegramId);
1137
+ await this.runtime.ensureConnection({
1138
+ entityId,
1139
+ roomId,
1140
+ userName: ctx.from.username,
1141
+ userId: telegramId,
1142
+ name: ctx.from.first_name || ctx.from.username || "Unknown User",
1143
+ source: "telegram",
1144
+ channelId: chatId,
1145
+ type: ChannelType2.GROUP,
1146
+ worldId
1147
+ });
1148
+ this.syncedEntityIds.add(entityId);
1149
+ }
1150
+ }
1151
+ /**
1152
+ * Synchronizes a new chat member entity with the runtime system.
1153
+ * Triggered when a user joins the chat.
1154
+ *
1155
+ * @param {Context} ctx - The context of the incoming update
1156
+ * @param {UUID} worldId - The ID of the world
1157
+ * @param {UUID} roomId - The ID of the room
1158
+ * @param {string} chatId - The ID of the chat
1159
+ * @returns {Promise<void>}
1160
+ * @private
1161
+ */
1162
+ async syncNewChatMember(ctx, worldId, roomId, chatId) {
1163
+ if (ctx.message && "new_chat_member" in ctx.message) {
1164
+ const newMember = ctx.message.new_chat_member;
1165
+ const telegramId = newMember.id.toString();
1166
+ const entityId = createUniqueUuid2(this.runtime, telegramId);
1167
+ if (this.syncedEntityIds.has(telegramId)) return;
1168
+ await this.runtime.ensureConnection({
1169
+ entityId,
1170
+ roomId,
1171
+ userName: newMember.username,
1172
+ userId: telegramId,
1173
+ name: newMember.first_name || newMember.username || "Unknown User",
1174
+ source: "telegram",
1175
+ channelId: chatId,
1176
+ type: ChannelType2.GROUP,
1177
+ worldId
1178
+ });
1179
+ this.syncedEntityIds.add(entityId);
1180
+ this.runtime.emitEvent(
1181
+ "TELEGRAM_ENTITY_JOINED" /* ENTITY_JOINED */,
1182
+ {
1183
+ runtime: this.runtime,
1184
+ entityId,
1185
+ worldId,
1186
+ source: "telegram",
1187
+ telegramUser: {
1188
+ id: newMember.id,
1189
+ username: newMember.username,
1190
+ first_name: newMember.first_name
1191
+ }
1192
+ }
1193
+ );
1194
+ }
1195
+ }
1196
+ /**
1197
+ * Updates entity status when a user leaves the chat.
1198
+ *
1199
+ * @param {Context} ctx - The context of the incoming update
1200
+ * @returns {Promise<void>}
1201
+ * @private
1202
+ */
1203
+ async syncLeftChatMember(ctx) {
1204
+ if (ctx.message && "left_chat_member" in ctx.message) {
1205
+ const leftMember = ctx.message.left_chat_member;
1206
+ const telegramId = leftMember.id.toString();
1207
+ const entityId = createUniqueUuid2(this.runtime, telegramId);
1208
+ const existingEntity = await this.runtime.getEntityById(entityId);
1209
+ if (existingEntity) {
1210
+ existingEntity.metadata = {
1211
+ ...existingEntity.metadata,
1212
+ status: "INACTIVE",
1213
+ leftAt: Date.now()
1214
+ };
1215
+ await this.runtime.updateEntity(existingEntity);
1216
+ }
1217
+ }
1218
+ }
1219
+ /**
1220
+ * Handles forum topics by creating appropriate rooms in the runtime system.
1221
+ * This enables proper conversation management for Telegram's forum feature.
1222
+ *
1223
+ * @param {Context} ctx - The context of the incoming update
1224
+ * @returns {Promise<void>}
1225
+ * @private
1226
+ */
1227
+ async handleForumTopic(ctx) {
1228
+ if (!ctx.chat || !ctx.message?.message_thread_id) return;
1229
+ const chat = ctx.chat;
1230
+ const chatId = chat.id.toString();
1231
+ const worldId = createUniqueUuid2(this.runtime, chatId);
1232
+ const room = await this.buildForumTopicRoom(ctx, worldId);
1233
+ if (!room) return;
1234
+ await this.runtime.ensureRoomExists(room);
1235
+ }
1236
+ /**
1237
+ * Builds entity for message sender
1238
+ */
1239
+ buildMsgSenderEntity(from) {
1240
+ if (!from) return null;
1241
+ const userId = createUniqueUuid2(this.runtime, from.id.toString());
1242
+ const telegramId = from.id.toString();
1243
+ return {
1244
+ id: userId,
1245
+ agentId: this.runtime.agentId,
1246
+ names: [from.first_name || from.username || "Unknown User"],
1247
+ metadata: {
1248
+ telegram: {
1249
+ id: telegramId,
1250
+ username: from.username,
1251
+ name: from.first_name || from.username || "Unknown User"
1252
+ }
1253
+ }
1254
+ };
1255
+ }
1256
+ /**
1257
+ * Handles new chat discovery and emits WORLD_JOINED event.
1258
+ * This is a critical function that ensures new chats are properly
1259
+ * registered in the runtime system and appropriate events are emitted.
1260
+ *
1261
+ * @param {Context} ctx - The context of the incoming update
1262
+ * @returns {Promise<void>}
1263
+ * @private
1264
+ */
1265
+ async handleNewChat(ctx) {
1266
+ if (!ctx.chat) return;
1267
+ const chat = ctx.chat;
1268
+ const chatId = chat.id.toString();
1269
+ this.knownChats.set(chatId, chat);
1270
+ const { chatTitle, channelType } = this.getChatTypeInfo(chat);
1271
+ const worldId = createUniqueUuid2(this.runtime, chatId);
1272
+ const existingWorld = await this.runtime.getWorld(worldId);
1273
+ if (existingWorld) {
1274
+ return;
1275
+ }
1276
+ const userId = ctx.from ? createUniqueUuid2(this.runtime, ctx.from.id.toString()) : null;
1277
+ let admins = [];
1278
+ let owner = null;
1279
+ if (chat.type === "group" || chat.type === "supergroup" || chat.type === "channel") {
1280
+ try {
1281
+ const chatAdmins = await ctx.getChatAdministrators();
1282
+ admins = chatAdmins;
1283
+ const foundOwner = admins.find(
1284
+ (admin) => admin.status === "creator"
1285
+ );
1286
+ owner = foundOwner || null;
1287
+ } catch (error) {
1288
+ logger3.warn({ src: "plugin:telegram", agentId: this.runtime.agentId, chatId, error: error instanceof Error ? error.message : String(error) }, "Could not get chat administrators");
1289
+ }
1290
+ }
1291
+ let ownerId = userId;
1292
+ if (owner) {
1293
+ ownerId = createUniqueUuid2(this.runtime, String(owner.user.id));
1294
+ }
1295
+ const world = {
1296
+ id: worldId,
1297
+ name: chatTitle,
1298
+ agentId: this.runtime.agentId,
1299
+ serverId: chatId,
1300
+ metadata: {
1301
+ source: "telegram",
1302
+ ...ownerId && { ownership: { ownerId } },
1303
+ roles: ownerId ? {
1304
+ [ownerId]: Role.OWNER
1305
+ } : {},
1306
+ chatType: chat.type,
1307
+ isForumEnabled: chat.type === "supergroup" && chat.is_forum
1308
+ }
1309
+ };
1310
+ await this.runtime.ensureWorldExists(world);
1311
+ const generalRoom = {
1312
+ id: createUniqueUuid2(this.runtime, chatId),
1313
+ name: chatTitle,
1314
+ source: "telegram",
1315
+ type: channelType,
1316
+ channelId: chatId,
1317
+ serverId: chatId,
1318
+ worldId
1319
+ };
1320
+ await this.runtime.ensureRoomExists(generalRoom);
1321
+ const rooms = [generalRoom];
1322
+ if (chat.type === "supergroup" && chat.is_forum && ctx.message?.message_thread_id) {
1323
+ const topicRoom = await this.buildForumTopicRoom(ctx, worldId);
1324
+ if (topicRoom) {
1325
+ rooms.push(topicRoom);
1326
+ await this.runtime.ensureRoomExists(topicRoom);
1327
+ }
1328
+ }
1329
+ const entities = await this.buildStandardizedEntities(chat);
1330
+ if (ctx.from) {
1331
+ const senderEntity = this.buildMsgSenderEntity(ctx.from);
1332
+ if (senderEntity && senderEntity.id && !entities.some((e) => e.id === senderEntity.id)) {
1333
+ entities.push(senderEntity);
1334
+ this.syncedEntityIds.add(senderEntity.id);
1335
+ }
1336
+ }
1337
+ await this.batchProcessEntities(
1338
+ entities,
1339
+ generalRoom.id,
1340
+ generalRoom.channelId,
1341
+ generalRoom.type,
1342
+ worldId
1343
+ );
1344
+ const telegramWorldPayload = {
1345
+ runtime: this.runtime,
1346
+ world,
1347
+ rooms,
1348
+ entities,
1349
+ source: "telegram",
1350
+ chat,
1351
+ botUsername: this.bot?.botInfo?.username
1352
+ };
1353
+ if (chat.type !== "private") {
1354
+ await this.runtime.emitEvent("TELEGRAM_WORLD_JOINED" /* WORLD_JOINED */, telegramWorldPayload);
1355
+ }
1356
+ await this.runtime.emitEvent(EventType2.WORLD_JOINED, {
1357
+ runtime: this.runtime,
1358
+ world,
1359
+ rooms,
1360
+ entities,
1361
+ source: "telegram"
1362
+ });
1363
+ }
1364
+ /**
1365
+ * Processes entities in batches to prevent overwhelming the system.
1366
+ *
1367
+ * @param {Entity[]} entities - The entities to process
1368
+ * @param {UUID} roomId - The ID of the room to connect entities to
1369
+ * @param {string} channelId - The channel ID
1370
+ * @param {ChannelType} roomType - The type of the room
1371
+ * @param {UUID} worldId - The ID of the world
1372
+ * @returns {Promise<void>}
1373
+ * @private
1374
+ */
1375
+ async batchProcessEntities(entities, roomId, channelId, roomType, worldId) {
1376
+ const batchSize = 50;
1377
+ for (let i = 0; i < entities.length; i += batchSize) {
1378
+ const entityBatch = entities.slice(i, i + batchSize);
1379
+ await Promise.all(
1380
+ entityBatch.map(async (entity) => {
1381
+ try {
1382
+ if (entity.id) {
1383
+ const telegramMetadata = entity.metadata?.telegram;
1384
+ await this.runtime.ensureConnection({
1385
+ entityId: entity.id,
1386
+ roomId,
1387
+ userName: telegramMetadata?.username,
1388
+ name: telegramMetadata?.name,
1389
+ userId: telegramMetadata?.id,
1390
+ source: "telegram",
1391
+ channelId,
1392
+ type: roomType,
1393
+ worldId
1394
+ });
1395
+ } else {
1396
+ logger3.warn({ src: "plugin:telegram", agentId: this.runtime.agentId, entityNames: entity.names }, "Skipping entity sync due to missing ID");
1397
+ }
1398
+ } catch (err) {
1399
+ const telegramMetadata = entity.metadata?.telegram;
1400
+ logger3.warn({ src: "plugin:telegram", agentId: this.runtime.agentId, username: telegramMetadata?.username, error: err instanceof Error ? err.message : String(err) }, "Failed to sync user");
1401
+ }
1402
+ })
1403
+ );
1404
+ if (i + batchSize < entities.length) {
1405
+ await new Promise((resolve) => setTimeout(resolve, 500));
1406
+ }
1407
+ }
1408
+ }
1409
+ /**
1410
+ * Gets chat title and channel type based on Telegram chat type.
1411
+ * Maps Telegram-specific chat types to standardized system types.
1412
+ *
1413
+ * @param {any} chat - The Telegram chat object
1414
+ * @returns {Object} Object containing chatTitle and channelType
1415
+ * @private
1416
+ */
1417
+ getChatTypeInfo(chat) {
1418
+ let chatTitle;
1419
+ let channelType;
1420
+ switch (chat.type) {
1421
+ case "private":
1422
+ chatTitle = `Chat with ${chat.first_name || "Unknown User"}`;
1423
+ channelType = ChannelType2.DM;
1424
+ break;
1425
+ case "group":
1426
+ chatTitle = chat.title || "Unknown Group";
1427
+ channelType = ChannelType2.GROUP;
1428
+ break;
1429
+ case "supergroup":
1430
+ chatTitle = chat.title || "Unknown Supergroup";
1431
+ channelType = ChannelType2.GROUP;
1432
+ break;
1433
+ case "channel":
1434
+ chatTitle = chat.title || "Unknown Channel";
1435
+ channelType = ChannelType2.FEED;
1436
+ break;
1437
+ default:
1438
+ chatTitle = "Unknown Chat";
1439
+ channelType = ChannelType2.GROUP;
1440
+ }
1441
+ return { chatTitle, channelType };
1442
+ }
1443
+ /**
1444
+ * Builds standardized entity representations from Telegram chat data.
1445
+ * Transforms Telegram-specific user data into system-standard Entity objects.
1446
+ *
1447
+ * @param {any} chat - The Telegram chat object
1448
+ * @returns {Promise<Entity[]>} Array of standardized Entity objects
1449
+ * @private
1450
+ */
1451
+ async buildStandardizedEntities(chat) {
1452
+ const entities = [];
1453
+ try {
1454
+ if (chat.type === "private" && chat.id) {
1455
+ const userId = createUniqueUuid2(this.runtime, chat.id.toString());
1456
+ entities.push({
1457
+ id: userId,
1458
+ names: [chat.first_name || "Unknown User"],
1459
+ agentId: this.runtime.agentId,
1460
+ metadata: {
1461
+ telegram: {
1462
+ id: chat.id.toString(),
1463
+ username: chat.username || "unknown",
1464
+ name: chat.first_name || "Unknown User"
1465
+ },
1466
+ source: "telegram"
1467
+ }
1468
+ });
1469
+ this.syncedEntityIds.add(userId);
1470
+ } else if (chat.type === "group" || chat.type === "supergroup") {
1471
+ try {
1472
+ const admins = await this.bot?.telegram.getChatAdministrators(chat.id);
1473
+ if (admins && admins.length > 0) {
1474
+ for (const admin of admins) {
1475
+ const userId = createUniqueUuid2(this.runtime, admin.user.id.toString());
1476
+ entities.push({
1477
+ id: userId,
1478
+ names: [admin.user.first_name || admin.user.username || "Unknown Admin"],
1479
+ agentId: this.runtime.agentId,
1480
+ metadata: {
1481
+ telegram: {
1482
+ id: admin.user.id.toString(),
1483
+ username: admin.user.username || "unknown",
1484
+ name: admin.user.first_name || "Unknown Admin",
1485
+ isAdmin: true,
1486
+ adminTitle: admin.custom_title || (admin.status === "creator" ? "Owner" : "Admin")
1487
+ },
1488
+ source: "telegram",
1489
+ roles: [admin.status === "creator" ? Role.OWNER : Role.ADMIN]
1490
+ }
1491
+ });
1492
+ this.syncedEntityIds.add(userId);
1493
+ }
1494
+ }
1495
+ } catch (error) {
1496
+ logger3.warn({ src: "plugin:telegram", agentId: this.runtime.agentId, chatId: chat.id, error: error instanceof Error ? error.message : String(error) }, "Could not fetch administrators");
1497
+ }
1498
+ }
1499
+ } catch (error) {
1500
+ logger3.error({ src: "plugin:telegram", agentId: this.runtime.agentId, error: error instanceof Error ? error.message : String(error) }, "Error building standardized entities");
1501
+ }
1502
+ return entities;
1503
+ }
1504
+ /**
1505
+ * Extracts and builds the room object for a forum topic from a message context.
1506
+ * This refactored method can be used both in middleware and when handling new chats.
1507
+ *
1508
+ * @param {Context} ctx - The context of the incoming update
1509
+ * @param {UUID} worldId - The ID of the world the topic belongs to
1510
+ * @returns {Promise<Room | null>} A Promise that resolves with the room or null if not a topic
1511
+ * @private
1512
+ */
1513
+ async buildForumTopicRoom(ctx, worldId) {
1514
+ if (!ctx.chat || !ctx.message?.message_thread_id) return null;
1515
+ if (ctx.chat.type !== "supergroup" || !ctx.chat.is_forum) return null;
1516
+ const chat = ctx.chat;
1517
+ const chatId = chat.id.toString();
1518
+ const threadId = ctx.message.message_thread_id.toString();
1519
+ const roomId = createUniqueUuid2(this.runtime, `${chatId}-${threadId}`);
1520
+ try {
1521
+ const replyMessage = JSON.parse(JSON.stringify(ctx.message));
1522
+ let topicName = `Topic #${threadId}`;
1523
+ if (replyMessage && typeof replyMessage === "object" && "forum_topic_created" in replyMessage && replyMessage.forum_topic_created) {
1524
+ const topicCreated = replyMessage.forum_topic_created;
1525
+ if (topicCreated && typeof topicCreated === "object" && "name" in topicCreated) {
1526
+ topicName = topicCreated.name;
1527
+ }
1528
+ } else if (replyMessage && typeof replyMessage === "object" && "reply_to_message" in replyMessage && replyMessage.reply_to_message && typeof replyMessage.reply_to_message === "object" && "forum_topic_created" in replyMessage.reply_to_message && replyMessage.reply_to_message.forum_topic_created) {
1529
+ const topicCreated = replyMessage.reply_to_message.forum_topic_created;
1530
+ if (topicCreated && typeof topicCreated === "object" && "name" in topicCreated) {
1531
+ topicName = topicCreated.name;
1532
+ }
1533
+ }
1534
+ const room = {
1535
+ id: roomId,
1536
+ name: topicName,
1537
+ source: "telegram",
1538
+ type: ChannelType2.GROUP,
1539
+ channelId: `${chatId}-${threadId}`,
1540
+ serverId: chatId,
1541
+ worldId,
1542
+ metadata: {
1543
+ threadId,
1544
+ isForumTopic: true,
1545
+ parentChatId: chatId
1546
+ }
1547
+ };
1548
+ return room;
1549
+ } catch (error) {
1550
+ logger3.error({ src: "plugin:telegram", agentId: this.runtime.agentId, chatId, threadId, error: error instanceof Error ? error.message : String(error) }, "Error building forum topic room");
1551
+ return null;
1552
+ }
1553
+ }
1554
+ static registerSendHandlers(runtime, serviceInstance) {
1555
+ if (serviceInstance && serviceInstance.bot) {
1556
+ runtime.registerSendHandler(
1557
+ "telegram",
1558
+ serviceInstance.handleSendMessage.bind(serviceInstance)
1559
+ );
1560
+ logger3.info({ src: "plugin:telegram", agentId: runtime.agentId }, "Registered send handler");
1561
+ } else {
1562
+ logger3.warn({ src: "plugin:telegram", agentId: runtime.agentId }, "Cannot register send handler, bot not initialized");
1563
+ }
1564
+ }
1565
+ async handleSendMessage(runtime, target, content) {
1566
+ if (!this.bot || !this.messageManager) {
1567
+ logger3.error({ src: "plugin:telegram", agentId: runtime.agentId }, "Bot not initialized, cannot send messages");
1568
+ throw new Error("Telegram bot is not initialized. Please provide TELEGRAM_BOT_TOKEN.");
1569
+ }
1570
+ let chatId;
1571
+ if (target.channelId) {
1572
+ chatId = target.channelId;
1573
+ } else if (target.roomId) {
1574
+ const room = await runtime.getRoom(target.roomId);
1575
+ chatId = room?.channelId;
1576
+ if (!chatId)
1577
+ throw new Error(`Could not resolve Telegram chat ID from roomId ${target.roomId}`);
1578
+ } else if (target.entityId) {
1579
+ logger3.error({ src: "plugin:telegram", agentId: runtime.agentId, entityId: target.entityId }, "Sending DMs via entityId not implemented");
1580
+ throw new Error("Sending DMs via entityId is not yet supported for Telegram.");
1581
+ } else {
1582
+ throw new Error("Telegram SendHandler requires channelId, roomId, or entityId.");
1583
+ }
1584
+ if (!chatId) {
1585
+ throw new Error(
1586
+ `Could not determine target Telegram chat ID for target: ${JSON.stringify(target)}`
1587
+ );
1588
+ }
1589
+ try {
1590
+ await this.messageManager.sendMessage(chatId, content);
1591
+ logger3.info({ src: "plugin:telegram", agentId: runtime.agentId, chatId }, "Message sent");
1592
+ } catch (error) {
1593
+ logger3.error({ src: "plugin:telegram", agentId: runtime.agentId, chatId, error: error instanceof Error ? error.message : String(error) }, "Error sending message");
1594
+ throw error;
1595
+ }
1596
+ }
1597
+ };
1598
+
1599
+ // src/tests.ts
1600
+ import { logger as logger4 } from "@elizaos/core";
1601
+ var TEST_IMAGE_URL = "https://github.com/elizaOS/awesome-eliza/blob/main/assets/eliza-logo.jpg?raw=true";
1602
+ var TelegramTestSuite = class {
1603
+ name = "telegram";
1604
+ telegramClient = null;
1605
+ bot = null;
1606
+ messageManager = null;
1607
+ tests;
1608
+ /**
1609
+ * Constructor for initializing a set of test cases for a Telegram bot.
1610
+ *
1611
+ * @constructor
1612
+ * @property {Array<Object>} tests - An array of test cases with name and corresponding test functions.
1613
+ * @property {string} tests.name - The name of the test case.
1614
+ * @property {function} tests.fn - The test function to be executed.
1615
+ */
1616
+ constructor() {
1617
+ this.tests = [
1618
+ {
1619
+ name: "Initialize and Validate Telegram Bot Connection",
1620
+ fn: this.testCreatingTelegramBot.bind(this)
1621
+ },
1622
+ {
1623
+ name: "Send Basic Text Message to Telegram Chat",
1624
+ fn: this.testSendingTextMessage.bind(this)
1625
+ },
1626
+ {
1627
+ name: "Send Text Message with an Image Attachment",
1628
+ fn: this.testSendingMessageWithAttachment.bind(this)
1629
+ },
1630
+ {
1631
+ name: "Handle and Process Incoming Telegram Messages",
1632
+ fn: this.testHandlingMessage.bind(this)
1633
+ },
1634
+ {
1635
+ name: "Process and Validate Image Attachments in Incoming Messages",
1636
+ fn: this.testProcessingImages.bind(this)
1637
+ }
1638
+ ];
1639
+ }
1640
+ /**
1641
+ * Retrieves the Telegram test chat ID from environment variables.
1642
+ *
1643
+ * Reference on getting the Telegram chat ID:
1644
+ * https://stackoverflow.com/a/32572159
1645
+ */
1646
+ /**
1647
+ * Validates the chat ID by checking if it is set in the runtime settings or environment variables.
1648
+ * If not set, an error is thrown with a message instructing to provide a valid chat ID.
1649
+ * @param {IAgentRuntime} runtime - The runtime object that provides access to the settings and environment variables.
1650
+ * @throws {Error} If TELEGRAM_TEST_CHAT_ID is not set in the runtime settings or environment variables.
1651
+ * @returns {string} The validated chat ID.
1652
+ */
1653
+ validateChatId(runtime) {
1654
+ const testChatId = runtime.getSetting("TELEGRAM_TEST_CHAT_ID") || process.env.TELEGRAM_TEST_CHAT_ID;
1655
+ if (!testChatId || typeof testChatId === "boolean") {
1656
+ throw new Error(
1657
+ "TELEGRAM_TEST_CHAT_ID is not set. Please provide a valid chat ID in the environment variables."
1658
+ );
1659
+ }
1660
+ return testChatId;
1661
+ }
1662
+ async getChatInfo(runtime) {
1663
+ try {
1664
+ const chatId = this.validateChatId(runtime);
1665
+ if (!this.bot) {
1666
+ throw new Error("Bot is not initialized.");
1667
+ }
1668
+ const chat = await this.bot.telegram.getChat(chatId);
1669
+ logger4.debug({ src: "plugin:telegram", chatId }, "Fetched real chat");
1670
+ return chat;
1671
+ } catch (error) {
1672
+ throw new Error(`Error fetching real Telegram chat: ${error}`);
1673
+ }
1674
+ }
1675
+ async testCreatingTelegramBot(runtime) {
1676
+ this.telegramClient = runtime.getService("telegram");
1677
+ if (!this.telegramClient || !this.telegramClient.messageManager) {
1678
+ throw new Error(
1679
+ "Telegram service or message manager not initialized - check TELEGRAM_BOT_TOKEN"
1680
+ );
1681
+ }
1682
+ this.bot = this.telegramClient.messageManager.bot;
1683
+ this.messageManager = this.telegramClient.messageManager;
1684
+ logger4.debug({ src: "plugin:telegram" }, "Telegram bot initialized successfully");
1685
+ }
1686
+ async testSendingTextMessage(runtime) {
1687
+ try {
1688
+ if (!this.bot) throw new Error("Bot not initialized.");
1689
+ const chatId = this.validateChatId(runtime);
1690
+ await this.bot.telegram.sendMessage(chatId, "Testing Telegram message!");
1691
+ logger4.debug({ src: "plugin:telegram", chatId }, "Message sent successfully");
1692
+ } catch (error) {
1693
+ throw new Error(`Error sending Telegram message: ${error}`);
1694
+ }
1695
+ }
1696
+ async testSendingMessageWithAttachment(runtime) {
1697
+ try {
1698
+ if (!this.messageManager) throw new Error("MessageManager not initialized.");
1699
+ if (!this.bot) throw new Error("Bot not initialized.");
1700
+ const chat = await this.getChatInfo(runtime);
1701
+ const mockContext = {
1702
+ chat,
1703
+ from: { id: 123, username: "TestUser" },
1704
+ telegram: this.bot.telegram
1705
+ };
1706
+ const messageContent = {
1707
+ text: "Here is an image attachment:",
1708
+ attachments: [
1709
+ {
1710
+ id: "123",
1711
+ title: "Sample Image",
1712
+ source: TEST_IMAGE_URL,
1713
+ text: "Sample Image",
1714
+ url: TEST_IMAGE_URL,
1715
+ contentType: "image/png",
1716
+ description: "Sample Image"
1717
+ }
1718
+ ]
1719
+ };
1720
+ await this.messageManager.sendMessageInChunks(
1721
+ mockContext,
1722
+ messageContent
1723
+ );
1724
+ logger4.success({ src: "plugin:telegram" }, "Message with image attachment sent successfully");
1725
+ } catch (error) {
1726
+ throw new Error(`Error sending Telegram message with attachment: ${error}`);
1727
+ }
1728
+ }
1729
+ async testHandlingMessage(runtime) {
1730
+ try {
1731
+ if (!this.bot) throw new Error("Bot not initialized.");
1732
+ if (!this.messageManager) throw new Error("MessageManager not initialized.");
1733
+ const chat = await this.getChatInfo(runtime);
1734
+ const mockContext = {
1735
+ chat,
1736
+ from: {
1737
+ id: 123,
1738
+ username: "TestUser",
1739
+ is_bot: false,
1740
+ first_name: "Test",
1741
+ last_name: "User"
1742
+ },
1743
+ message: {
1744
+ message_id: void 0,
1745
+ text: `@${this.bot.botInfo?.username}! Hello!`,
1746
+ date: Math.floor(Date.now() / 1e3),
1747
+ chat
1748
+ },
1749
+ telegram: this.bot.telegram
1750
+ };
1751
+ try {
1752
+ await this.messageManager.handleMessage(mockContext);
1753
+ } catch (error) {
1754
+ throw new Error(`Error handling Telegram message: ${error}`);
1755
+ }
1756
+ } catch (error) {
1757
+ throw new Error(`Error handling Telegram message: ${error}`);
1758
+ }
1759
+ }
1760
+ async testProcessingImages(runtime) {
1761
+ try {
1762
+ if (!this.bot) throw new Error("Bot not initialized.");
1763
+ if (!this.messageManager) throw new Error("MessageManager not initialized.");
1764
+ const chatId = this.validateChatId(runtime);
1765
+ const fileId = await this.getFileId(String(chatId), TEST_IMAGE_URL);
1766
+ const mockMessage = {
1767
+ message_id: 12345,
1768
+ chat: { id: chatId, type: "private" },
1769
+ date: Math.floor(Date.now() / 1e3),
1770
+ photo: [
1771
+ {
1772
+ file_id: fileId,
1773
+ file_unique_id: `unique_${fileId}`,
1774
+ width: 100,
1775
+ height: 100
1776
+ }
1777
+ ],
1778
+ text: `@${this.bot.botInfo?.username}!`
1779
+ };
1780
+ const result = await this.messageManager.processImage(mockMessage);
1781
+ if (!result || !result.description) {
1782
+ throw new Error("Error processing Telegram image or description not found");
1783
+ }
1784
+ const { description } = result;
1785
+ logger4.debug({ src: "plugin:telegram", description }, "Processing Telegram image successfully");
1786
+ } catch (error) {
1787
+ throw new Error(`Error processing Telegram image: ${error}`);
1788
+ }
1789
+ }
1790
+ async getFileId(chatId, imageUrl) {
1791
+ try {
1792
+ if (!this.bot) {
1793
+ throw new Error("Bot is not initialized.");
1794
+ }
1795
+ const message = await this.bot.telegram.sendPhoto(chatId, imageUrl);
1796
+ if (!message.photo || message.photo.length === 0) {
1797
+ throw new Error("No photo received in the message response.");
1798
+ }
1799
+ return message.photo[message.photo.length - 1].file_id;
1800
+ } catch (error) {
1801
+ logger4.error({ src: "plugin:telegram", chatId, error: error instanceof Error ? error.message : String(error) }, "Error sending image");
1802
+ throw error;
1803
+ }
1804
+ }
1805
+ };
1806
+
1807
+ // src/index.ts
1808
+ var telegramPlugin = {
1809
+ name: TELEGRAM_SERVICE_NAME,
1810
+ description: "Telegram client plugin",
1811
+ services: [TelegramService],
1812
+ tests: [new TelegramTestSuite()]
1813
+ };
1814
+ var index_default = telegramPlugin;
1815
+ export {
1816
+ MessageManager,
1817
+ TelegramService,
1818
+ index_default as default
1819
+ };
1820
+ //# sourceMappingURL=index.js.map