@elizaos/plugin-telegram 1.0.0-beta.7 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -52,29 +52,126 @@ import {
52
52
  createUniqueUuid,
53
53
  logger
54
54
  } from "@elizaos/core";
55
+ import { Markup as Markup2 } from "telegraf";
55
56
 
56
57
  // src/utils.ts
57
- function escapeMarkdown(text) {
58
- if (text.startsWith("```") && text.endsWith("```")) {
59
- return text;
58
+ import { Markup } from "telegraf";
59
+ var TELEGRAM_RESERVED_REGEX = /([_*[\]()~`>#+\-=|{}.!\\])/g;
60
+ function escapePlainText(text) {
61
+ if (!text) return "";
62
+ return text.replace(TELEGRAM_RESERVED_REGEX, "\\$1");
63
+ }
64
+ function escapePlainTextPreservingBlockquote(text) {
65
+ if (!text) return "";
66
+ return text.split("\n").map((line) => {
67
+ const match = line.match(/^(>+\s?)(.*)$/);
68
+ if (match) {
69
+ return match[1] + escapePlainText(match[2]);
70
+ }
71
+ return escapePlainText(line);
72
+ }).join("\n");
73
+ }
74
+ function escapeCode(text) {
75
+ if (!text) return "";
76
+ return text.replace(/([`\\])/g, "\\$1");
77
+ }
78
+ function escapeUrl(url) {
79
+ if (!url) return "";
80
+ return url.replace(/([)\\])/g, "\\$1");
81
+ }
82
+ function convertMarkdownToTelegram(markdown) {
83
+ const replacements = [];
84
+ function storeReplacement(formatted) {
85
+ const placeholder = `\0${replacements.length}\0`;
86
+ replacements.push(formatted);
87
+ return placeholder;
60
88
  }
61
- const parts = text.split(/(```[\s\S]*?```)/g);
62
- return parts.map((part, index) => {
63
- if (index % 2 === 1) {
64
- return part;
89
+ let converted = markdown;
90
+ converted = converted.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
91
+ const escapedCode = escapeCode(code);
92
+ const formatted = "```" + (lang || "") + "\n" + escapedCode + "```";
93
+ return storeReplacement(formatted);
94
+ });
95
+ converted = converted.replace(/`([^`]+)`/g, (match, code) => {
96
+ const escapedCode = escapeCode(code);
97
+ const formatted = "`" + escapedCode + "`";
98
+ return storeReplacement(formatted);
99
+ });
100
+ converted = converted.replace(
101
+ /$begin:math:display$([^$end:math:display$]+)]$begin:math:text$([^)]+)$end:math:text$/g,
102
+ (match, text, url) => {
103
+ const formattedText = escapePlainText(text);
104
+ const escapedURL = escapeUrl(url);
105
+ const formatted = `[${formattedText}](${escapedURL})`;
106
+ return storeReplacement(formatted);
107
+ }
108
+ );
109
+ converted = converted.replace(/\*\*([^*]+)\*\*/g, (match, content) => {
110
+ const formattedContent = escapePlainText(content);
111
+ const formatted = `*${formattedContent}*`;
112
+ return storeReplacement(formatted);
113
+ });
114
+ converted = converted.replace(/~~([^~]+)~~/g, (match, content) => {
115
+ const formattedContent = escapePlainText(content);
116
+ const formatted = `~${formattedContent}~`;
117
+ return storeReplacement(formatted);
118
+ });
119
+ converted = converted.replace(/(?<!\*)\*([^*\n]+)\*(?!\*)/g, (match, content) => {
120
+ const formattedContent = escapePlainText(content);
121
+ const formatted = `_${formattedContent}_`;
122
+ return storeReplacement(formatted);
123
+ });
124
+ converted = converted.replace(/_([^_\n]+)_/g, (match, content) => {
125
+ const formattedContent = escapePlainText(content);
126
+ const formatted = `_${formattedContent}_`;
127
+ return storeReplacement(formatted);
128
+ });
129
+ converted = converted.replace(/^(#{1,6})\s*(.*)$/gm, (match, hashes, headerContent) => {
130
+ const formatted = `*${escapePlainText(headerContent.trim())}*`;
131
+ return storeReplacement(formatted);
132
+ });
133
+ const NULL_CHAR = String.fromCharCode(0);
134
+ const PLACEHOLDER_PATTERN = new RegExp(`(${NULL_CHAR}\\d+${NULL_CHAR})`, "g");
135
+ const PLACEHOLDER_TEST = new RegExp(`^${NULL_CHAR}\\d+${NULL_CHAR}$`);
136
+ const PLACEHOLDER_REPLACE = new RegExp(`${NULL_CHAR}(\\d+)${NULL_CHAR}`, "g");
137
+ const finalEscaped = converted.split(PLACEHOLDER_PATTERN).map((segment) => {
138
+ if (PLACEHOLDER_TEST.test(segment)) {
139
+ return segment;
140
+ } else {
141
+ return escapePlainTextPreservingBlockquote(segment);
65
142
  }
66
- return part.replace(/`.*?`/g, (match) => match).replace(/([*_`\\])/g, "\\$1");
67
143
  }).join("");
144
+ const finalResult = finalEscaped.replace(PLACEHOLDER_REPLACE, (_, index) => {
145
+ return replacements[parseInt(index)];
146
+ });
147
+ return finalResult;
148
+ }
149
+ function convertToTelegramButtons(buttons) {
150
+ if (!buttons) return [];
151
+ return buttons.map((button) => {
152
+ switch (button.kind) {
153
+ case "login":
154
+ return Markup.button.login(button.text, button.url);
155
+ case "url":
156
+ return Markup.button.url(button.text, button.url);
157
+ }
158
+ });
68
159
  }
69
160
 
70
161
  // src/messageManager.ts
71
162
  import fs from "node:fs";
72
163
  var MAX_MESSAGE_LENGTH = 4096;
73
164
  var getChannelType = (chat) => {
74
- if (chat.type === "private") return ChannelType.DM;
75
- if (chat.type === "supergroup") return ChannelType.GROUP;
76
- if (chat.type === "channel") return ChannelType.GROUP;
77
- if (chat.type === "group") return ChannelType.GROUP;
165
+ switch (chat.type) {
166
+ case "private":
167
+ return ChannelType.DM;
168
+ case "group":
169
+ case "supergroup":
170
+ case "channel":
171
+ return ChannelType.GROUP;
172
+ default:
173
+ throw new Error(`Unrecognized Telegram chat type: ${chat.type}`);
174
+ }
78
175
  };
79
176
  var MessageManager = class {
80
177
  bot;
@@ -97,15 +194,14 @@ var MessageManager = class {
97
194
  * @returns {Promise<{ description: string } | null>} The description of the processed image or null if no image found.
98
195
  */
99
196
  async processImage(message) {
100
- var _a, _b, _c;
101
197
  try {
102
198
  let imageUrl = null;
103
199
  logger.info(`Telegram Message: ${message}`);
104
- if ("photo" in message && ((_a = message.photo) == null ? void 0 : _a.length) > 0) {
200
+ if ("photo" in message && message.photo?.length > 0) {
105
201
  const photo = message.photo[message.photo.length - 1];
106
202
  const fileLink = await this.bot.telegram.getFileLink(photo.file_id);
107
203
  imageUrl = fileLink.toString();
108
- } else if ("document" in message && ((_c = (_b = message.document) == null ? void 0 : _b.mime_type) == null ? void 0 : _c.startsWith("image/"))) {
204
+ } else if ("document" in message && message.document?.mime_type?.startsWith("image/")) {
109
205
  const fileLink = await this.bot.telegram.getFileLink(message.document.file_id);
110
206
  imageUrl = fileLink.toString();
111
207
  }
@@ -127,7 +223,7 @@ ${description}]` };
127
223
  * Sends a message in chunks, handling attachments and splitting the message if necessary
128
224
  *
129
225
  * @param {Context} ctx - The context object representing the current state of the bot
130
- * @param {Content} content - The content of the message to be sent
226
+ * @param {TelegramContent} content - The content of the message to be sent
131
227
  * @param {number} [replyToMessageId] - The ID of the message to reply to, if any
132
228
  * @returns {Promise<Message.TextMessage[]>} - An array of TextMessage objects representing the messages sent
133
229
  */
@@ -143,7 +239,7 @@ ${description}]` };
143
239
  };
144
240
  let mediaType = void 0;
145
241
  for (const prefix in typeMap) {
146
- if (attachment.contentType.startsWith(prefix)) {
242
+ if (attachment.contentType?.startsWith(prefix)) {
147
243
  mediaType = typeMap[prefix];
148
244
  break;
149
245
  }
@@ -155,14 +251,26 @@ ${description}]` };
155
251
  }
156
252
  await this.sendMedia(ctx, attachment.url, mediaType, attachment.description);
157
253
  });
254
+ return [];
158
255
  } else {
159
- const chunks = this.splitMessage(content.text);
256
+ const chunks = this.splitMessage(content.text ?? "");
160
257
  const sentMessages = [];
258
+ const telegramButtons = convertToTelegramButtons(content.buttons ?? []);
259
+ if (!ctx.chat) {
260
+ logger.error("sendMessageInChunks: ctx.chat is undefined");
261
+ return [];
262
+ }
263
+ await ctx.telegram.sendChatAction(ctx.chat.id, "typing");
161
264
  for (let i = 0; i < chunks.length; i++) {
162
- const chunk = escapeMarkdown(chunks[i]);
265
+ const chunk = convertMarkdownToTelegram(chunks[i]);
266
+ if (!ctx.chat) {
267
+ logger.error("sendMessageInChunks loop: ctx.chat is undefined");
268
+ continue;
269
+ }
163
270
  const sentMessage = await ctx.telegram.sendMessage(ctx.chat.id, chunk, {
164
271
  reply_parameters: i === 0 && replyToMessageId ? { message_id: replyToMessageId } : void 0,
165
- parse_mode: "Markdown"
272
+ parse_mode: "MarkdownV2",
273
+ ...Markup2.inlineKeyboard(telegramButtons)
166
274
  });
167
275
  sentMessages.push(sentMessage);
168
276
  }
@@ -193,6 +301,9 @@ ${description}]` };
193
301
  if (!sendFunction) {
194
302
  throw new Error(`Unsupported media type: ${type}`);
195
303
  }
304
+ if (!ctx.chat) {
305
+ throw new Error("sendMedia: ctx.chat is undefined");
306
+ }
196
307
  if (isUrl) {
197
308
  await sendFunction(ctx.chat.id, mediaPath, { caption });
198
309
  } else {
@@ -201,6 +312,9 @@ ${description}]` };
201
312
  }
202
313
  const fileStream = fs.createReadStream(mediaPath);
203
314
  try {
315
+ if (!ctx.chat) {
316
+ throw new Error("sendMedia (file): ctx.chat is undefined");
317
+ }
204
318
  await sendFunction(ctx.chat.id, { source: fileStream }, { caption });
205
319
  } finally {
206
320
  fileStream.destroy();
@@ -210,8 +324,10 @@ ${description}]` };
210
324
  `${type.charAt(0).toUpperCase() + type.slice(1)} sent successfully: ${mediaPath}`
211
325
  );
212
326
  } catch (error) {
213
- logger.error(`Failed to send ${type}. Path: ${mediaPath}. Error: ${error.message}`);
214
- logger.debug(error.stack);
327
+ const errorMessage = error instanceof Error ? error.message : String(error);
328
+ logger.error(`Failed to send ${type}. Path: ${mediaPath}. Error: ${errorMessage}`, {
329
+ originalError: error
330
+ });
215
331
  throw error;
216
332
  }
217
333
  }
@@ -224,6 +340,7 @@ ${description}]` };
224
340
  */
225
341
  splitMessage(text) {
226
342
  const chunks = [];
343
+ if (!text) return chunks;
227
344
  let currentChunk = "";
228
345
  const lines = text.split("\n");
229
346
  for (const line of lines) {
@@ -244,14 +361,20 @@ ${description}]` };
244
361
  * @returns {Promise<void>}
245
362
  */
246
363
  async handleMessage(ctx) {
247
- var _a, _b, _c, _d, _e, _f;
248
364
  if (!ctx.message || !ctx.from) return;
249
365
  const message = ctx.message;
250
366
  try {
251
367
  const entityId = createUniqueUuid(this.runtime, ctx.from.id.toString());
252
- const userName = ctx.from.username || ctx.from.first_name || "Unknown User";
253
- const roomId = createUniqueUuid(this.runtime, (_a = ctx.chat) == null ? void 0 : _a.id.toString());
254
- const messageId = createUniqueUuid(this.runtime, (_b = message == null ? void 0 : message.message_id) == null ? void 0 : _b.toString());
368
+ const threadId = "is_topic_message" in message && message.is_topic_message ? message.message_thread_id?.toString() : void 0;
369
+ if (!ctx.chat) {
370
+ logger.error("handleMessage: ctx.chat is undefined");
371
+ return;
372
+ }
373
+ const roomId = createUniqueUuid(
374
+ this.runtime,
375
+ threadId ? `${ctx.chat.id}-${threadId}` : ctx.chat.id.toString()
376
+ );
377
+ const messageId = createUniqueUuid(this.runtime, message?.message_id?.toString());
255
378
  const imageInfo = await this.processImage(message);
256
379
  let messageText = "";
257
380
  if ("text" in message && message.text) {
@@ -263,34 +386,6 @@ ${description}]` };
263
386
  if (!fullText) return;
264
387
  const chat = message.chat;
265
388
  const channelType = getChannelType(chat);
266
- const worldId = createUniqueUuid(
267
- this.runtime,
268
- chat.type === "private" ? `private_${chat.id}` : chat.id.toString()
269
- );
270
- const worldName = chat.type === "supergroup" ? chat.title : chat.type === "channel" ? chat.title : chat.type === "private" ? `Chat with ${chat.first_name || "Unknown"}` : "Telegram";
271
- const roomName = chat.type === "private" ? chat.first_name : chat.type === "supergroup" ? chat.title : chat.type === "channel" ? chat.title : chat.type === "group" ? chat.title : "Unknown Group";
272
- await this.runtime.ensureConnection({
273
- entityId,
274
- roomId,
275
- userName,
276
- name: userName,
277
- source: "telegram",
278
- channelId: ctx.chat.id.toString(),
279
- serverId: chat.type === "private" ? void 0 : chat.id.toString(),
280
- // Only set serverId for non-private chats
281
- type: channelType,
282
- worldId
283
- });
284
- const room = {
285
- id: roomId,
286
- name: roomName,
287
- source: "telegram",
288
- type: channelType,
289
- channelId: ctx.chat.id.toString(),
290
- serverId: chat.type === "private" ? void 0 : chat.id.toString(),
291
- worldId
292
- };
293
- await this.runtime.ensureRoomExists(room);
294
389
  const memory = {
295
390
  id: messageId,
296
391
  entityId,
@@ -306,6 +401,7 @@ ${description}]` };
306
401
  };
307
402
  const callback = async (content, _files) => {
308
403
  try {
404
+ if (!content.text) return [];
309
405
  const sentMessages = await this.sendMessageInChunks(ctx, content, message.message_id);
310
406
  if (!sentMessages) return [];
311
407
  const memories = [];
@@ -319,6 +415,7 @@ ${description}]` };
319
415
  roomId,
320
416
  content: {
321
417
  ...content,
418
+ source: "telegram",
322
419
  text: sentMessage.text,
323
420
  inReplyTo: messageId,
324
421
  channelType
@@ -351,27 +448,33 @@ ${description}]` };
351
448
  } catch (error) {
352
449
  logger.error("Error handling Telegram message:", {
353
450
  error,
354
- chatId: (_c = ctx.chat) == null ? void 0 : _c.id,
355
- messageId: (_d = ctx.message) == null ? void 0 : _d.message_id,
356
- from: ((_e = ctx.from) == null ? void 0 : _e.username) || ((_f = ctx.from) == null ? void 0 : _f.id)
451
+ chatId: ctx.chat?.id,
452
+ messageId: ctx.message?.message_id,
453
+ from: ctx.from?.username || ctx.from?.id
357
454
  });
358
455
  throw error;
359
456
  }
360
457
  }
361
458
  /**
362
459
  * Handles the reaction event triggered by a user reacting to a message.
363
- * * @param {NarrowedContext<Context<Update>, Update.MessageReactionUpdate>} ctx The context of the message reaction update
460
+ * @param {NarrowedContext<Context<Update>, Update.MessageReactionUpdate>} ctx The context of the message reaction update
364
461
  * @returns {Promise<void>} A Promise that resolves when the reaction handling is complete
365
462
  */
366
463
  async handleReaction(ctx) {
367
464
  if (!ctx.update.message_reaction || !ctx.from) return;
368
465
  const reaction = ctx.update.message_reaction;
466
+ const reactedToMessageId = reaction.message_id;
467
+ const originalMessagePlaceholder = {
468
+ message_id: reactedToMessageId,
469
+ chat: reaction.chat,
470
+ from: ctx.from,
471
+ date: Math.floor(Date.now() / 1e3)
472
+ };
369
473
  const reactionType = reaction.new_reaction[0].type;
370
474
  const reactionEmoji = reaction.new_reaction[0].type;
371
475
  try {
372
476
  const entityId = createUniqueUuid(this.runtime, ctx.from.id.toString());
373
477
  const roomId = createUniqueUuid(this.runtime, ctx.chat.id.toString());
374
- const worldId = createUniqueUuid(this.runtime, ctx.chat.id.toString());
375
478
  const reactionId = createUniqueUuid(
376
479
  this.runtime,
377
480
  `${reaction.message_id}-${ctx.from.id}-${Date.now()}`
@@ -391,7 +494,8 @@ ${description}]` };
391
494
  };
392
495
  const callback = async (content) => {
393
496
  try {
394
- const sentMessage = await ctx.reply(content.text);
497
+ const replyText = content.text ?? "";
498
+ const sentMessage = await ctx.reply(replyText);
395
499
  const responseMemory = {
396
500
  id: createUniqueUuid(this.runtime, sentMessage.message_id.toString()),
397
501
  entityId: this.runtime.agentId,
@@ -413,7 +517,12 @@ ${description}]` };
413
517
  runtime: this.runtime,
414
518
  message: memory,
415
519
  callback,
416
- source: "telegram"
520
+ source: "telegram",
521
+ ctx,
522
+ originalMessage: originalMessagePlaceholder,
523
+ // Cast needed due to placeholder
524
+ reactionString: reactionType === "emoji" ? reactionEmoji : reactionType,
525
+ originalReaction: reaction.new_reaction[0]
417
526
  });
418
527
  this.runtime.emitEvent("TELEGRAM_REACTION_RECEIVED" /* REACTION_RECEIVED */, {
419
528
  runtime: this.runtime,
@@ -421,11 +530,14 @@ ${description}]` };
421
530
  callback,
422
531
  source: "telegram",
423
532
  ctx,
533
+ originalMessage: originalMessagePlaceholder,
534
+ // Cast needed due to placeholder
424
535
  reactionString: reactionType === "emoji" ? reactionEmoji : reactionType,
425
536
  originalReaction: reaction.new_reaction[0]
426
537
  });
427
538
  } catch (error) {
428
- logger.error("Error handling reaction:", error);
539
+ const errorMessage = error instanceof Error ? error.message : String(error);
540
+ logger.error("Error handling reaction:", { error: errorMessage, originalError: error });
429
541
  }
430
542
  }
431
543
  /**
@@ -446,7 +558,7 @@ ${description}]` };
446
558
  content,
447
559
  replyToMessageId
448
560
  );
449
- if (!(sentMessages == null ? void 0 : sentMessages.length)) return [];
561
+ if (!sentMessages?.length) return [];
450
562
  const roomId = createUniqueUuid(this.runtime, chatId.toString());
451
563
  const memories = [];
452
564
  for (const sentMessage of sentMessages) {
@@ -472,7 +584,9 @@ ${description}]` };
472
584
  }
473
585
  this.runtime.emitEvent(EventType.MESSAGE_SENT, {
474
586
  runtime: this.runtime,
475
- messages: memories,
587
+ message: {
588
+ content
589
+ },
476
590
  roomId,
477
591
  source: "telegram"
478
592
  });
@@ -482,7 +596,11 @@ ${description}]` };
482
596
  });
483
597
  return sentMessages;
484
598
  } catch (error) {
485
- logger.error("Error sending message to Telegram:", error);
599
+ const errorMessage = error instanceof Error ? error.message : String(error);
600
+ logger.error("Error sending message to Telegram:", {
601
+ error: errorMessage,
602
+ originalError: error
603
+ });
486
604
  return [];
487
605
  }
488
606
  }
@@ -496,6 +614,7 @@ var TelegramService = class _TelegramService extends Service {
496
614
  messageManager;
497
615
  options;
498
616
  knownChats = /* @__PURE__ */ new Map();
617
+ syncedEntityIds = /* @__PURE__ */ new Set();
499
618
  /**
500
619
  * Constructor for TelegramService class.
501
620
  * @param {IAgentRuntime} runtime - The runtime object for the agent.
@@ -532,6 +651,7 @@ var TelegramService = class _TelegramService extends Service {
532
651
  );
533
652
  logger2.log("\u{1F680} Starting Telegram bot...");
534
653
  await service.initializeBot();
654
+ service.setupMiddlewares();
535
655
  service.setupMessageHandlers();
536
656
  await service.bot.telegram.getMe();
537
657
  return service;
@@ -549,7 +669,7 @@ var TelegramService = class _TelegramService extends Service {
549
669
  }
550
670
  }
551
671
  throw new Error(
552
- `Telegram initialization failed after ${maxRetries} attempts. Last error: ${lastError == null ? void 0 : lastError.message}`
672
+ `Telegram initialization failed after ${maxRetries} attempts. Last error: ${lastError?.message}`
553
673
  );
554
674
  }
555
675
  /**
@@ -584,18 +704,113 @@ var TelegramService = class _TelegramService extends Service {
584
704
  process.once("SIGINT", () => this.bot.stop("SIGINT"));
585
705
  process.once("SIGTERM", () => this.bot.stop("SIGTERM"));
586
706
  }
707
+ /**
708
+ * Sets up the middleware chain for preprocessing messages before they reach handlers.
709
+ * This critical method establishes a sequential processing pipeline that:
710
+ *
711
+ * 1. Authorization - Verifies if a chat is allowed to interact with the bot based on configured settings
712
+ * 2. Chat Discovery - Ensures chat entities and worlds exist in the runtime, creating them if needed
713
+ * 3. Forum Topics - Handles Telegram forum topics as separate rooms for better conversation management
714
+ * 4. Entity Synchronization - Ensures message senders are properly synchronized as entities
715
+ *
716
+ * The middleware chain runs in sequence for each message, with each step potentially
717
+ * enriching the context or stopping processing if conditions aren't met.
718
+ * This preprocessing is essential for maintaining consistent state before message handlers execute.
719
+ *
720
+ * @private
721
+ */
722
+ setupMiddlewares() {
723
+ this.bot.use(this.authorizationMiddleware.bind(this));
724
+ this.bot.use(this.chatAndEntityMiddleware.bind(this));
725
+ }
726
+ /**
727
+ * Authorization middleware - checks if chat is allowed to interact with the bot
728
+ * based on the TELEGRAM_ALLOWED_CHATS configuration.
729
+ *
730
+ * @param {Context} ctx - The context of the incoming update
731
+ * @param {Function} next - The function to call to proceed to the next middleware
732
+ * @returns {Promise<void>}
733
+ * @private
734
+ */
735
+ async authorizationMiddleware(ctx, next) {
736
+ if (!await this.isGroupAuthorized(ctx)) {
737
+ logger2.debug("Chat not authorized, skipping message processing");
738
+ return;
739
+ }
740
+ await next();
741
+ }
742
+ /**
743
+ * Chat and entity management middleware - handles new chats, forum topics, and entity synchronization.
744
+ * This middleware implements decision logic to determine which operations are needed based on
745
+ * the chat type and whether we've seen this chat before.
746
+ *
747
+ * @param {Context} ctx - The context of the incoming update
748
+ * @param {Function} next - The function to call to proceed to the next middleware
749
+ * @returns {Promise<void>}
750
+ * @private
751
+ */
752
+ async chatAndEntityMiddleware(ctx, next) {
753
+ if (!ctx.chat) return next();
754
+ const chatId = ctx.chat.id.toString();
755
+ if (!this.knownChats.has(chatId)) {
756
+ await this.handleNewChat(ctx);
757
+ return next();
758
+ }
759
+ await this.processExistingChat(ctx);
760
+ await next();
761
+ }
762
+ /**
763
+ * Process an existing chat based on chat type and message properties.
764
+ * Different chat types require different processing steps.
765
+ *
766
+ * @param {Context} ctx - The context of the incoming update
767
+ * @returns {Promise<void>}
768
+ * @private
769
+ */
770
+ async processExistingChat(ctx) {
771
+ if (!ctx.chat) return;
772
+ const chat = ctx.chat;
773
+ if (chat.type === "supergroup" && chat.is_forum && ctx.message?.message_thread_id) {
774
+ try {
775
+ await this.handleForumTopic(ctx);
776
+ } catch (error) {
777
+ logger2.error(`Error handling forum topic: ${error}`);
778
+ }
779
+ }
780
+ if (ctx.from && ctx.chat.type !== "private") {
781
+ await this.syncEntity(ctx);
782
+ }
783
+ }
784
+ /**
785
+ * Sets up message and reaction handlers for the bot.
786
+ * Configures event handlers to process incoming messages and reactions.
787
+ *
788
+ * @private
789
+ */
790
+ setupMessageHandlers() {
791
+ this.bot.on("message", async (ctx) => {
792
+ try {
793
+ await this.messageManager.handleMessage(ctx);
794
+ } catch (error) {
795
+ logger2.error("Error handling message:", error);
796
+ }
797
+ });
798
+ this.bot.on("message_reaction", async (ctx) => {
799
+ try {
800
+ await this.messageManager.handleReaction(ctx);
801
+ } catch (error) {
802
+ logger2.error("Error handling reaction:", error);
803
+ }
804
+ });
805
+ }
587
806
  /**
588
807
  * Checks if a group is authorized, based on the TELEGRAM_ALLOWED_CHATS setting.
589
808
  * @param {Context} ctx - The context of the incoming update.
590
809
  * @returns {Promise<boolean>} A Promise that resolves with a boolean indicating if the group is authorized.
591
810
  */
592
811
  async isGroupAuthorized(ctx) {
593
- var _a;
594
- const chatId = (_a = ctx.chat) == null ? void 0 : _a.id.toString();
812
+ const chatId = ctx.chat?.id.toString();
595
813
  if (!chatId) return false;
596
- if (!this.knownChats.has(chatId)) {
597
- await this.handleNewChat(ctx);
598
- }
599
814
  const allowedChats = this.runtime.getSetting("TELEGRAM_ALLOWED_CHATS");
600
815
  if (!allowedChats) {
601
816
  return true;
@@ -609,39 +824,199 @@ var TelegramService = class _TelegramService extends Service {
609
824
  }
610
825
  }
611
826
  /**
612
- * Handles new chat discovery and emits WORLD_JOINED event
827
+ * Synchronizes an entity from a message context with the runtime system.
828
+ * This method handles three cases:
829
+ * 1. Message sender - most common case
830
+ * 2. New chat member - when a user joins the chat
831
+ * 3. Left chat member - when a user leaves the chat
832
+ *
833
+ * @param {Context} ctx - The context of the incoming update
834
+ * @returns {Promise<void>}
835
+ * @private
836
+ */
837
+ async syncEntity(ctx) {
838
+ if (!ctx.chat) return;
839
+ const chat = ctx.chat;
840
+ const chatId = chat.id.toString();
841
+ const worldId = createUniqueUuid2(this.runtime, chatId);
842
+ const roomId = createUniqueUuid2(
843
+ this.runtime,
844
+ ctx.message?.message_thread_id ? `${ctx.chat.id}-${ctx.message.message_thread_id}` : ctx.chat.id.toString()
845
+ );
846
+ await this.syncMessageSender(ctx, worldId, roomId, chatId);
847
+ await this.syncNewChatMember(ctx, worldId, roomId, chatId);
848
+ await this.syncLeftChatMember(ctx);
849
+ }
850
+ /**
851
+ * Synchronizes the message sender entity with the runtime system.
852
+ * This is the most common entity sync case.
853
+ *
854
+ * @param {Context} ctx - The context of the incoming update
855
+ * @param {UUID} worldId - The ID of the world
856
+ * @param {UUID} roomId - The ID of the room
857
+ * @param {string} chatId - The ID of the chat
858
+ * @returns {Promise<void>}
859
+ * @private
860
+ */
861
+ async syncMessageSender(ctx, worldId, roomId, chatId) {
862
+ if (ctx.from && !this.syncedEntityIds.has(ctx.from.id.toString())) {
863
+ const telegramId = ctx.from.id.toString();
864
+ const entityId = createUniqueUuid2(this.runtime, telegramId);
865
+ await this.runtime.ensureConnection({
866
+ entityId,
867
+ roomId,
868
+ userName: ctx.from.username,
869
+ userId: telegramId,
870
+ name: ctx.from.first_name || ctx.from.username || "Unknown User",
871
+ source: "telegram",
872
+ channelId: chatId,
873
+ serverId: chatId,
874
+ type: ChannelType2.GROUP,
875
+ worldId
876
+ });
877
+ this.syncedEntityIds.add(entityId);
878
+ }
879
+ }
880
+ /**
881
+ * Synchronizes a new chat member entity with the runtime system.
882
+ * Triggered when a user joins the chat.
883
+ *
884
+ * @param {Context} ctx - The context of the incoming update
885
+ * @param {UUID} worldId - The ID of the world
886
+ * @param {UUID} roomId - The ID of the room
887
+ * @param {string} chatId - The ID of the chat
888
+ * @returns {Promise<void>}
889
+ * @private
890
+ */
891
+ async syncNewChatMember(ctx, worldId, roomId, chatId) {
892
+ if (ctx.message && "new_chat_member" in ctx.message) {
893
+ const newMember = ctx.message.new_chat_member;
894
+ const telegramId = newMember.id.toString();
895
+ const entityId = createUniqueUuid2(this.runtime, telegramId);
896
+ if (this.syncedEntityIds.has(telegramId)) return;
897
+ await this.runtime.ensureConnection({
898
+ entityId,
899
+ roomId,
900
+ userName: newMember.username,
901
+ userId: telegramId,
902
+ name: newMember.first_name || newMember.username || "Unknown User",
903
+ source: "telegram",
904
+ channelId: chatId,
905
+ serverId: chatId,
906
+ type: ChannelType2.GROUP,
907
+ worldId
908
+ });
909
+ this.syncedEntityIds.add(entityId);
910
+ this.runtime.emitEvent(["TELEGRAM_ENTITY_JOINED" /* ENTITY_JOINED */], {
911
+ runtime: this.runtime,
912
+ entityId,
913
+ worldId,
914
+ newMember,
915
+ ctx
916
+ });
917
+ }
918
+ }
919
+ /**
920
+ * Updates entity status when a user leaves the chat.
921
+ *
922
+ * @param {Context} ctx - The context of the incoming update
923
+ * @returns {Promise<void>}
924
+ * @private
925
+ */
926
+ async syncLeftChatMember(ctx) {
927
+ if (ctx.message && "left_chat_member" in ctx.message) {
928
+ const leftMember = ctx.message.left_chat_member;
929
+ const telegramId = leftMember.id.toString();
930
+ const entityId = createUniqueUuid2(this.runtime, telegramId);
931
+ const existingEntity = await this.runtime.getEntityById(entityId);
932
+ if (existingEntity) {
933
+ existingEntity.metadata = {
934
+ ...existingEntity.metadata,
935
+ status: "INACTIVE",
936
+ leftAt: Date.now()
937
+ };
938
+ await this.runtime.updateEntity(existingEntity);
939
+ }
940
+ }
941
+ }
942
+ /**
943
+ * Handles forum topics by creating appropriate rooms in the runtime system.
944
+ * This enables proper conversation management for Telegram's forum feature.
945
+ *
946
+ * @param {Context} ctx - The context of the incoming update
947
+ * @returns {Promise<void>}
948
+ * @private
949
+ */
950
+ async handleForumTopic(ctx) {
951
+ if (!ctx.chat || !ctx.message?.message_thread_id) return;
952
+ const chat = ctx.chat;
953
+ const chatId = chat.id.toString();
954
+ const worldId = createUniqueUuid2(this.runtime, chatId);
955
+ const room = await this.buildForumTopicRoom(ctx, worldId);
956
+ if (!room) return;
957
+ await this.runtime.ensureRoomExists(room);
958
+ }
959
+ /**
960
+ * Builds entity for message sender
961
+ */
962
+ buildMsgSenderEntity(from) {
963
+ if (!from) return null;
964
+ const userId = createUniqueUuid2(this.runtime, from.id.toString());
965
+ const telegramId = from.id.toString();
966
+ return {
967
+ id: userId,
968
+ agentId: this.runtime.agentId,
969
+ names: [from.first_name || from.username || "Unknown User"],
970
+ metadata: {
971
+ telegram: {
972
+ id: telegramId,
973
+ username: from.username,
974
+ name: from.first_name || from.username || "Unknown User"
975
+ }
976
+ }
977
+ };
978
+ }
979
+ /**
980
+ * Handles new chat discovery and emits WORLD_JOINED event.
981
+ * This is a critical function that ensures new chats are properly
982
+ * registered in the runtime system and appropriate events are emitted.
983
+ *
613
984
  * @param {Context} ctx - The context of the incoming update
985
+ * @returns {Promise<void>}
986
+ * @private
614
987
  */
615
988
  async handleNewChat(ctx) {
616
989
  if (!ctx.chat) return;
617
990
  const chat = ctx.chat;
618
991
  const chatId = chat.id.toString();
619
992
  this.knownChats.set(chatId, chat);
620
- let chatTitle;
621
- let channelType;
622
- switch (chat.type) {
623
- case "private":
624
- chatTitle = `Chat with ${chat.first_name || "Unknown User"}`;
625
- channelType = ChannelType2.DM;
626
- break;
627
- case "group":
628
- chatTitle = chat.title || "Unknown Group";
629
- channelType = ChannelType2.GROUP;
630
- break;
631
- case "supergroup":
632
- chatTitle = chat.title || "Unknown Supergroup";
633
- channelType = ChannelType2.GROUP;
634
- break;
635
- case "channel":
636
- chatTitle = chat.title || "Unknown Channel";
637
- channelType = ChannelType2.FEED;
638
- break;
639
- default:
640
- chatTitle = "Unknown Chat";
641
- channelType = ChannelType2.GROUP;
642
- }
993
+ const { chatTitle, channelType } = this.getChatTypeInfo(chat);
643
994
  const worldId = createUniqueUuid2(this.runtime, chatId);
644
- const roomId = createUniqueUuid2(this.runtime, chatId);
995
+ const existingWorld = await this.runtime.getWorld(worldId);
996
+ if (existingWorld) {
997
+ return;
998
+ }
999
+ const userId = ctx.from ? createUniqueUuid2(this.runtime, ctx.from.id.toString()) : null;
1000
+ let admins = [];
1001
+ let owner = null;
1002
+ if (chat.type === "group" || chat.type === "supergroup" || chat.type === "channel") {
1003
+ try {
1004
+ const chatAdmins = await ctx.getChatAdministrators();
1005
+ admins = chatAdmins;
1006
+ const foundOwner = admins.find(
1007
+ (admin) => admin.status === "creator"
1008
+ );
1009
+ owner = foundOwner || null;
1010
+ } catch (error) {
1011
+ logger2.warn(
1012
+ `Could not get chat administrators: ${error instanceof Error ? error.message : String(error)}`
1013
+ );
1014
+ }
1015
+ }
1016
+ let ownerId = userId;
1017
+ if (owner) {
1018
+ ownerId = createUniqueUuid2(this.runtime, String(owner.user.id));
1019
+ }
645
1020
  const world = {
646
1021
  id: worldId,
647
1022
  name: chatTitle,
@@ -649,14 +1024,17 @@ var TelegramService = class _TelegramService extends Service {
649
1024
  serverId: chatId,
650
1025
  metadata: {
651
1026
  source: "telegram",
652
- ownership: { ownerId: chatId },
653
- roles: {
654
- [chatId]: Role.OWNER
655
- }
1027
+ ...ownerId && { ownership: { ownerId } },
1028
+ roles: ownerId ? {
1029
+ [ownerId]: Role.OWNER
1030
+ } : {},
1031
+ chatType: chat.type,
1032
+ isForumEnabled: chat.type === "supergroup" && chat.is_forum
656
1033
  }
657
1034
  };
658
- const room = {
659
- id: roomId,
1035
+ await this.runtime.ensureWorldExists(world);
1036
+ const generalRoom = {
1037
+ id: createUniqueUuid2(this.runtime, chatId),
660
1038
  name: chatTitle,
661
1039
  source: "telegram",
662
1040
  type: channelType,
@@ -664,204 +1042,289 @@ var TelegramService = class _TelegramService extends Service {
664
1042
  serverId: chatId,
665
1043
  worldId
666
1044
  };
667
- const users = [];
668
- if (chat.type === "private" && chat.id) {
669
- const userId = createUniqueUuid2(this.runtime, chat.id.toString());
670
- users.push({
671
- id: userId,
672
- names: [chat.first_name || "Unknown User"],
673
- agentId: this.runtime.agentId,
674
- metadata: {
675
- telegram: {
676
- id: chat.id.toString(),
677
- username: chat.username || "unknown",
678
- name: chat.first_name || "Unknown User"
679
- },
680
- source: "telegram"
681
- }
682
- });
683
- } else if (chat.type === "group" || chat.type === "supergroup") {
684
- try {
685
- const admins = await this.bot.telegram.getChatAdministrators(chat.id);
686
- if (admins && admins.length > 0) {
687
- for (const admin of admins) {
688
- const userId = createUniqueUuid2(this.runtime, admin.user.id.toString());
689
- users.push({
690
- id: userId,
691
- names: [admin.user.first_name || admin.user.username || "Unknown Admin"],
692
- agentId: this.runtime.agentId,
693
- metadata: {
694
- telegram: {
695
- id: admin.user.id.toString(),
696
- username: admin.user.username || "unknown",
697
- name: admin.user.first_name || "Unknown Admin",
698
- isAdmin: true,
699
- adminTitle: admin.custom_title || (admin.status === "creator" ? "Owner" : "Admin")
700
- },
701
- source: "telegram",
702
- roles: [admin.status === "creator" ? Role.OWNER : Role.ADMIN]
703
- }
704
- });
705
- }
706
- }
707
- try {
708
- const chatInfo = await this.bot.telegram.getChat(chat.id);
709
- if (chatInfo && "member_count" in chatInfo) {
710
- world.metadata.memberCount = chatInfo.member_count;
711
- }
712
- } catch (countError) {
713
- logger2.warn(`Could not get member count for chat ${chatId}: ${countError}`);
714
- }
715
- } catch (error) {
716
- logger2.warn(`Could not fetch administrators for chat ${chatId}: ${error}`);
1045
+ await this.runtime.ensureRoomExists(generalRoom);
1046
+ const rooms = [generalRoom];
1047
+ if (chat.type === "supergroup" && chat.is_forum && ctx.message?.message_thread_id) {
1048
+ const topicRoom = await this.buildForumTopicRoom(ctx, worldId);
1049
+ if (topicRoom) {
1050
+ rooms.push(topicRoom);
1051
+ await this.runtime.ensureRoomExists(topicRoom);
717
1052
  }
718
1053
  }
719
- const worldPayload = {
1054
+ const entities = await this.buildStandardizedEntities(chat);
1055
+ if (ctx.from) {
1056
+ const senderEntity = this.buildMsgSenderEntity(ctx.from);
1057
+ if (senderEntity && senderEntity.id && !entities.some((e) => e.id === senderEntity.id)) {
1058
+ entities.push(senderEntity);
1059
+ this.syncedEntityIds.add(senderEntity.id);
1060
+ }
1061
+ }
1062
+ await this.batchProcessEntities(
1063
+ entities,
1064
+ generalRoom.id,
1065
+ generalRoom.channelId,
1066
+ generalRoom.serverId,
1067
+ generalRoom.type,
1068
+ worldId
1069
+ );
1070
+ const telegramWorldPayload = {
720
1071
  runtime: this.runtime,
721
1072
  world,
722
- rooms: [room],
723
- entities: users,
724
- source: "telegram"
725
- };
726
- const telegramWorldPayload = {
727
- ...worldPayload,
728
- chat
1073
+ rooms,
1074
+ entities,
1075
+ source: "telegram",
1076
+ chat,
1077
+ botUsername: this.bot.botInfo.username
729
1078
  };
730
- this.runtime.emitEvent(EventType2.WORLD_JOINED, worldPayload);
731
- this.runtime.emitEvent("TELEGRAM_WORLD_JOINED" /* WORLD_JOINED */, telegramWorldPayload);
732
- if (chat.type === "group" || chat.type === "supergroup") {
733
- this.setupEntityTracking(chat.id);
1079
+ if (chat.type !== "private") {
1080
+ await this.runtime.emitEvent("TELEGRAM_WORLD_JOINED" /* WORLD_JOINED */, telegramWorldPayload);
734
1081
  }
1082
+ await this.runtime.emitEvent(EventType2.WORLD_JOINED, {
1083
+ runtime: this.runtime,
1084
+ world,
1085
+ rooms,
1086
+ entities,
1087
+ source: "telegram"
1088
+ });
735
1089
  }
736
1090
  /**
737
- * Sets up message and reaction handlers for the bot.
1091
+ * Processes entities in batches to prevent overwhelming the system.
738
1092
  *
1093
+ * @param {Entity[]} entities - The entities to process
1094
+ * @param {UUID} roomId - The ID of the room to connect entities to
1095
+ * @param {string} channelId - The channel ID
1096
+ * @param {string} serverId - The server ID
1097
+ * @param {ChannelType} roomType - The type of the room
1098
+ * @param {UUID} worldId - The ID of the world
1099
+ * @returns {Promise<void>}
739
1100
  * @private
740
- * @returns {void}
741
1101
  */
742
- setupMessageHandlers() {
743
- this.bot.on("message", async (ctx) => {
744
- try {
745
- if (!await this.isGroupAuthorized(ctx)) return;
746
- await this.messageManager.handleMessage(ctx);
747
- } catch (error) {
748
- logger2.error("Error handling message:", error);
749
- }
750
- });
751
- this.bot.on("message_reaction", async (ctx) => {
752
- try {
753
- if (!await this.isGroupAuthorized(ctx)) return;
754
- await this.messageManager.handleReaction(ctx);
755
- } catch (error) {
756
- logger2.error("Error handling reaction:", error);
1102
+ async batchProcessEntities(entities, roomId, channelId, serverId, roomType, worldId) {
1103
+ const batchSize = 50;
1104
+ for (let i = 0; i < entities.length; i += batchSize) {
1105
+ const entityBatch = entities.slice(i, i + batchSize);
1106
+ await Promise.all(
1107
+ entityBatch.map(async (entity) => {
1108
+ try {
1109
+ if (entity.id) {
1110
+ await this.runtime.ensureConnection({
1111
+ entityId: entity.id,
1112
+ roomId,
1113
+ userName: entity.metadata?.telegram?.username,
1114
+ name: entity.metadata?.telegram?.name,
1115
+ userId: entity.metadata?.telegram?.id,
1116
+ source: "telegram",
1117
+ channelId,
1118
+ serverId,
1119
+ type: roomType,
1120
+ worldId
1121
+ });
1122
+ } else {
1123
+ logger2.warn(
1124
+ `Skipping entity sync due to missing ID: ${JSON.stringify(entity.names)}`
1125
+ );
1126
+ }
1127
+ } catch (err) {
1128
+ logger2.warn(`Failed to sync user ${entity.metadata?.telegram?.username}: ${err}`);
1129
+ }
1130
+ })
1131
+ );
1132
+ if (i + batchSize < entities.length) {
1133
+ await new Promise((resolve) => setTimeout(resolve, 500));
757
1134
  }
758
- });
1135
+ }
759
1136
  }
760
1137
  /**
761
- * Sets up tracking for new entities in a group chat to sync them as entities
762
- * @param {number} chatId - The Telegram chat ID to track entities for
1138
+ * Gets chat title and channel type based on Telegram chat type.
1139
+ * Maps Telegram-specific chat types to standardized system types.
1140
+ *
1141
+ * @param {any} chat - The Telegram chat object
1142
+ * @returns {Object} Object containing chatTitle and channelType
1143
+ * @private
763
1144
  */
764
- setupEntityTracking(chatId) {
765
- const syncedEntityIds = /* @__PURE__ */ new Set();
766
- this.bot.on("message", async (ctx) => {
767
- if (!ctx.chat || ctx.chat.id !== chatId || !ctx.from) return;
768
- const entityId = ctx.from.id.toString();
769
- if (syncedEntityIds.has(entityId)) return;
770
- syncedEntityIds.add(entityId);
771
- const entityUuid = createUniqueUuid2(this.runtime, entityId);
772
- const worldId = createUniqueUuid2(this.runtime, chatId.toString());
773
- const chatIdStr = chatId.toString();
774
- try {
775
- await this.runtime.ensureConnection({
776
- entityId: entityUuid,
777
- roomId: createUniqueUuid2(this.runtime, chatIdStr),
778
- userName: ctx.from.username || ctx.from.first_name || "Unknown Entity",
779
- name: ctx.from.first_name || ctx.from.username || "Unknown Entity",
780
- source: "telegram",
781
- channelId: chatIdStr,
782
- serverId: chatIdStr,
783
- type: ChannelType2.GROUP,
784
- worldId
785
- });
786
- const entityJoinedPayload = {
787
- runtime: this.runtime,
788
- entityId: entityUuid,
789
- entity: {
790
- id: entityId,
791
- username: ctx.from.username || ctx.from.first_name || "Unknown Entity",
792
- displayName: ctx.from.first_name || ctx.from.username || "Unknown Entity"
793
- },
794
- worldId,
795
- source: "telegram",
1145
+ getChatTypeInfo(chat) {
1146
+ let chatTitle;
1147
+ let channelType;
1148
+ switch (chat.type) {
1149
+ case "private":
1150
+ chatTitle = `Chat with ${chat.first_name || "Unknown User"}`;
1151
+ channelType = ChannelType2.DM;
1152
+ break;
1153
+ case "group":
1154
+ chatTitle = chat.title || "Unknown Group";
1155
+ channelType = ChannelType2.GROUP;
1156
+ break;
1157
+ case "supergroup":
1158
+ chatTitle = chat.title || "Unknown Supergroup";
1159
+ channelType = ChannelType2.GROUP;
1160
+ break;
1161
+ case "channel":
1162
+ chatTitle = chat.title || "Unknown Channel";
1163
+ channelType = ChannelType2.FEED;
1164
+ break;
1165
+ default:
1166
+ chatTitle = "Unknown Chat";
1167
+ channelType = ChannelType2.GROUP;
1168
+ }
1169
+ return { chatTitle, channelType };
1170
+ }
1171
+ /**
1172
+ * Builds standardized entity representations from Telegram chat data.
1173
+ * Transforms Telegram-specific user data into system-standard Entity objects.
1174
+ *
1175
+ * @param {any} chat - The Telegram chat object
1176
+ * @returns {Promise<Entity[]>} Array of standardized Entity objects
1177
+ * @private
1178
+ */
1179
+ async buildStandardizedEntities(chat) {
1180
+ const entities = [];
1181
+ try {
1182
+ if (chat.type === "private" && chat.id) {
1183
+ const userId = createUniqueUuid2(this.runtime, chat.id.toString());
1184
+ entities.push({
1185
+ id: userId,
1186
+ names: [chat.first_name || "Unknown User"],
1187
+ agentId: this.runtime.agentId,
796
1188
  metadata: {
797
- joinedAt: Date.now()
1189
+ telegram: {
1190
+ id: chat.id.toString(),
1191
+ username: chat.username || "unknown",
1192
+ name: chat.first_name || "Unknown User"
1193
+ },
1194
+ source: "telegram"
798
1195
  }
799
- };
800
- const telegramEntityJoinedPayload = {
801
- ...entityJoinedPayload,
802
- telegramUser: {
803
- id: ctx.from.id,
804
- username: ctx.from.username,
805
- first_name: ctx.from.first_name
1196
+ });
1197
+ this.syncedEntityIds.add(userId);
1198
+ } else if (chat.type === "group" || chat.type === "supergroup") {
1199
+ try {
1200
+ const admins = await this.bot.telegram.getChatAdministrators(chat.id);
1201
+ if (admins && admins.length > 0) {
1202
+ for (const admin of admins) {
1203
+ const userId = createUniqueUuid2(this.runtime, admin.user.id.toString());
1204
+ entities.push({
1205
+ id: userId,
1206
+ names: [admin.user.first_name || admin.user.username || "Unknown Admin"],
1207
+ agentId: this.runtime.agentId,
1208
+ metadata: {
1209
+ telegram: {
1210
+ id: admin.user.id.toString(),
1211
+ username: admin.user.username || "unknown",
1212
+ name: admin.user.first_name || "Unknown Admin",
1213
+ isAdmin: true,
1214
+ adminTitle: admin.custom_title || (admin.status === "creator" ? "Owner" : "Admin")
1215
+ },
1216
+ source: "telegram",
1217
+ roles: [admin.status === "creator" ? Role.OWNER : Role.ADMIN]
1218
+ }
1219
+ });
1220
+ this.syncedEntityIds.add(userId);
1221
+ }
806
1222
  }
807
- };
808
- this.runtime.emitEvent(EventType2.ENTITY_JOINED, entityJoinedPayload);
809
- this.runtime.emitEvent("TELEGRAM_ENTITY_JOINED" /* ENTITY_JOINED */, telegramEntityJoinedPayload);
810
- logger2.info(
811
- `Tracked new Telegram entity: ${ctx.from.username || ctx.from.first_name || entityId}`
812
- );
813
- } catch (error) {
814
- logger2.error(`Error syncing new Telegram entity ${entityId} from chat ${chatId}:`, error);
1223
+ } catch (error) {
1224
+ logger2.warn(`Could not fetch administrators for chat ${chat.id}: ${error}`);
1225
+ }
815
1226
  }
816
- });
817
- this.bot.on("left_chat_member", async (ctx) => {
818
- var _a, _b;
819
- if (!((_a = ctx.message) == null ? void 0 : _a.left_chat_member) || ((_b = ctx.chat) == null ? void 0 : _b.id) !== chatId) return;
820
- const leftUser = ctx.message.left_chat_member;
821
- const entityId = createUniqueUuid2(this.runtime, leftUser.id.toString());
822
- const chatIdStr = chatId.toString();
823
- const worldId = createUniqueUuid2(this.runtime, chatIdStr);
824
- try {
825
- const entity = await this.runtime.getEntityById(entityId);
826
- if (entity) {
827
- entity.metadata = {
828
- ...entity.metadata,
829
- status: "INACTIVE",
830
- leftAt: Date.now()
831
- };
832
- await this.runtime.updateEntity(entity);
833
- const entityLeftPayload = {
834
- runtime: this.runtime,
835
- entityId,
836
- entity: {
837
- id: leftUser.id.toString(),
838
- username: leftUser.username || leftUser.first_name || "Unknown Entity",
839
- displayName: leftUser.first_name || leftUser.username || "Unknown Entity"
840
- },
841
- worldId,
842
- source: "telegram",
843
- metadata: {
844
- leftAt: Date.now()
845
- }
846
- };
847
- const telegramEntityLeftPayload = {
848
- ...entityLeftPayload,
849
- telegramUser: {
850
- id: leftUser.id,
851
- username: leftUser.username,
852
- first_name: leftUser.first_name
853
- }
854
- };
855
- this.runtime.emitEvent(EventType2.ENTITY_LEFT, entityLeftPayload);
856
- this.runtime.emitEvent("TELEGRAM_ENTITY_LEFT" /* ENTITY_LEFT */, telegramEntityLeftPayload);
857
- logger2.info(
858
- `Entity ${leftUser.username || leftUser.first_name || leftUser.id} left chat ${chatId}`
859
- );
1227
+ } catch (error) {
1228
+ logger2.error(
1229
+ `Error building standardized entities: ${error instanceof Error ? error.message : String(error)}`
1230
+ );
1231
+ }
1232
+ return entities;
1233
+ }
1234
+ /**
1235
+ * Extracts and builds the room object for a forum topic from a message context.
1236
+ * This refactored method can be used both in middleware and when handling new chats.
1237
+ *
1238
+ * @param {Context} ctx - The context of the incoming update
1239
+ * @param {UUID} worldId - The ID of the world the topic belongs to
1240
+ * @returns {Promise<Room | null>} A Promise that resolves with the room or null if not a topic
1241
+ * @private
1242
+ */
1243
+ async buildForumTopicRoom(ctx, worldId) {
1244
+ if (!ctx.chat || !ctx.message?.message_thread_id) return null;
1245
+ if (ctx.chat.type !== "supergroup" || !ctx.chat.is_forum) return null;
1246
+ const chat = ctx.chat;
1247
+ const chatId = chat.id.toString();
1248
+ const threadId = ctx.message.message_thread_id.toString();
1249
+ const roomId = createUniqueUuid2(this.runtime, `${chatId}-${threadId}`);
1250
+ try {
1251
+ const replyMessage = JSON.parse(JSON.stringify(ctx.message));
1252
+ let topicName = `Topic #${threadId}`;
1253
+ if (replyMessage && typeof replyMessage === "object" && "forum_topic_created" in replyMessage && replyMessage.forum_topic_created) {
1254
+ const topicCreated = replyMessage.forum_topic_created;
1255
+ if (topicCreated && typeof topicCreated === "object" && "name" in topicCreated) {
1256
+ topicName = topicCreated.name;
1257
+ }
1258
+ } 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) {
1259
+ const topicCreated = replyMessage.reply_to_message.forum_topic_created;
1260
+ if (topicCreated && typeof topicCreated === "object" && "name" in topicCreated) {
1261
+ topicName = topicCreated.name;
860
1262
  }
861
- } catch (error) {
862
- logger2.error(`Error handling Telegram entity leaving chat ${chatId}:`, error);
863
1263
  }
864
- });
1264
+ const room = {
1265
+ id: roomId,
1266
+ name: topicName,
1267
+ source: "telegram",
1268
+ type: ChannelType2.GROUP,
1269
+ channelId: `${chatId}-${threadId}`,
1270
+ serverId: chatId,
1271
+ worldId,
1272
+ metadata: {
1273
+ threadId,
1274
+ isForumTopic: true,
1275
+ parentChatId: chatId
1276
+ }
1277
+ };
1278
+ return room;
1279
+ } catch (error) {
1280
+ logger2.error(
1281
+ `Error building forum topic room: ${error instanceof Error ? error.message : String(error)}`
1282
+ );
1283
+ return null;
1284
+ }
1285
+ }
1286
+ static registerSendHandlers(runtime, serviceInstance) {
1287
+ if (serviceInstance) {
1288
+ runtime.registerSendHandler(
1289
+ "telegram",
1290
+ serviceInstance.handleSendMessage.bind(serviceInstance)
1291
+ );
1292
+ logger2.info("[Telegram] Registered send handler.");
1293
+ }
1294
+ }
1295
+ async handleSendMessage(runtime, target, content) {
1296
+ let chatId;
1297
+ if (target.channelId) {
1298
+ chatId = target.channelId;
1299
+ } else if (target.roomId) {
1300
+ const room = await runtime.getRoom(target.roomId);
1301
+ chatId = room?.channelId;
1302
+ if (!chatId)
1303
+ throw new Error(`Could not resolve Telegram chat ID from roomId ${target.roomId}`);
1304
+ } else if (target.entityId) {
1305
+ logger2.error("[Telegram SendHandler] Sending DMs via entityId not implemented yet.");
1306
+ throw new Error("Sending DMs via entityId is not yet supported for Telegram.");
1307
+ } else {
1308
+ throw new Error("Telegram SendHandler requires channelId, roomId, or entityId.");
1309
+ }
1310
+ if (!chatId) {
1311
+ throw new Error(
1312
+ `Could not determine target Telegram chat ID for target: ${JSON.stringify(target)}`
1313
+ );
1314
+ }
1315
+ try {
1316
+ await this.messageManager.sendMessage(chatId, content);
1317
+ logger2.info(`[Telegram SendHandler] Message sent to chat ID: ${chatId}`);
1318
+ } catch (error) {
1319
+ logger2.error(
1320
+ `[Telegram SendHandler] Error sending message: ${error instanceof Error ? error.message : String(error)}`,
1321
+ {
1322
+ target,
1323
+ content
1324
+ }
1325
+ );
1326
+ throw error;
1327
+ }
865
1328
  }
866
1329
  };
867
1330
 
@@ -931,6 +1394,9 @@ var TelegramTestSuite = class {
931
1394
  async getChatInfo(runtime) {
932
1395
  try {
933
1396
  const chatId = this.validateChatId(runtime);
1397
+ if (!this.bot) {
1398
+ throw new Error("Bot is not initialized.");
1399
+ }
934
1400
  const chat = await this.bot.telegram.getChat(chatId);
935
1401
  logger3.log(`Fetched real chat: ${JSON.stringify(chat)}`);
936
1402
  return chat;
@@ -957,6 +1423,7 @@ var TelegramTestSuite = class {
957
1423
  async testSendingMessageWithAttachment(runtime) {
958
1424
  try {
959
1425
  if (!this.messageManager) throw new Error("MessageManager not initialized.");
1426
+ if (!this.bot) throw new Error("Bot not initialized.");
960
1427
  const chat = await this.getChatInfo(runtime);
961
1428
  const mockContext = {
962
1429
  chat,
@@ -984,8 +1451,9 @@ var TelegramTestSuite = class {
984
1451
  }
985
1452
  }
986
1453
  async testHandlingMessage(runtime) {
987
- var _a;
988
1454
  try {
1455
+ if (!this.bot) throw new Error("Bot not initialized.");
1456
+ if (!this.messageManager) throw new Error("MessageManager not initialized.");
989
1457
  const chat = await this.getChatInfo(runtime);
990
1458
  const mockContext = {
991
1459
  chat,
@@ -998,7 +1466,7 @@ var TelegramTestSuite = class {
998
1466
  },
999
1467
  message: {
1000
1468
  message_id: void 0,
1001
- text: `@${(_a = this.bot.botInfo) == null ? void 0 : _a.username}! Hello!`,
1469
+ text: `@${this.bot.botInfo?.username}! Hello!`,
1002
1470
  date: Math.floor(Date.now() / 1e3),
1003
1471
  chat
1004
1472
  },
@@ -1014,21 +1482,30 @@ var TelegramTestSuite = class {
1014
1482
  }
1015
1483
  }
1016
1484
  async testProcessingImages(runtime) {
1017
- var _a;
1018
1485
  try {
1486
+ if (!this.bot) throw new Error("Bot not initialized.");
1487
+ if (!this.messageManager) throw new Error("MessageManager not initialized.");
1019
1488
  const chatId = this.validateChatId(runtime);
1020
1489
  const fileId = await this.getFileId(chatId, TEST_IMAGE_URL);
1021
1490
  const mockMessage = {
1022
- message_id: void 0,
1023
- chat: { id: chatId },
1491
+ message_id: 12345,
1492
+ chat: { id: chatId, type: "private" },
1024
1493
  date: Math.floor(Date.now() / 1e3),
1025
- photo: [{ file_id: fileId }],
1026
- text: `@${(_a = this.bot.botInfo) == null ? void 0 : _a.username}!`
1494
+ photo: [
1495
+ {
1496
+ file_id: fileId,
1497
+ file_unique_id: `unique_${fileId}`,
1498
+ width: 100,
1499
+ height: 100
1500
+ }
1501
+ ],
1502
+ text: `@${this.bot.botInfo?.username}!`
1027
1503
  };
1028
- const { description } = await this.messageManager.processImage(mockMessage);
1029
- if (!description) {
1030
- throw new Error("Error processing Telegram image");
1504
+ const result = await this.messageManager.processImage(mockMessage);
1505
+ if (!result || !result.description) {
1506
+ throw new Error("Error processing Telegram image or description not found");
1031
1507
  }
1508
+ const { description } = result;
1032
1509
  logger3.log(`Processing Telegram image successfully: ${description}`);
1033
1510
  } catch (error) {
1034
1511
  throw new Error(`Error processing Telegram image: ${error}`);
@@ -1036,7 +1513,13 @@ var TelegramTestSuite = class {
1036
1513
  }
1037
1514
  async getFileId(chatId, imageUrl) {
1038
1515
  try {
1516
+ if (!this.bot) {
1517
+ throw new Error("Bot is not initialized.");
1518
+ }
1039
1519
  const message = await this.bot.telegram.sendPhoto(chatId, imageUrl);
1520
+ if (!message.photo || message.photo.length === 0) {
1521
+ throw new Error("No photo received in the message response.");
1522
+ }
1040
1523
  return message.photo[message.photo.length - 1].file_id;
1041
1524
  } catch (error) {
1042
1525
  logger3.error(`Error sending image: ${error}`);
@@ -1054,6 +1537,8 @@ var telegramPlugin = {
1054
1537
  };
1055
1538
  var index_default = telegramPlugin;
1056
1539
  export {
1540
+ MessageManager,
1541
+ TelegramService,
1057
1542
  index_default as default
1058
1543
  };
1059
1544
  //# sourceMappingURL=index.js.map