@elizaos/plugin-telegram 1.0.0-beta.3 → 1.0.0-beta.32

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
@@ -54,6 +54,7 @@ import {
54
54
  } from "@elizaos/core";
55
55
 
56
56
  // src/utils.ts
57
+ import { Markup } from "telegraf";
57
58
  function escapeMarkdown(text) {
58
59
  if (text.startsWith("```") && text.endsWith("```")) {
59
60
  return text;
@@ -66,8 +67,20 @@ function escapeMarkdown(text) {
66
67
  return part.replace(/`.*?`/g, (match) => match).replace(/([*_`\\])/g, "\\$1");
67
68
  }).join("");
68
69
  }
70
+ function convertToTelegramButtons(buttons) {
71
+ if (!buttons) return [];
72
+ return buttons.map((button) => {
73
+ switch (button.kind) {
74
+ case "login":
75
+ return Markup.button.login(button.text, button.url);
76
+ case "url":
77
+ return Markup.button.url(button.text, button.url);
78
+ }
79
+ });
80
+ }
69
81
 
70
82
  // src/messageManager.ts
83
+ import { Markup as Markup2 } from "telegraf";
71
84
  import fs from "node:fs";
72
85
  var MAX_MESSAGE_LENGTH = 4096;
73
86
  var getChannelType = (chat) => {
@@ -127,7 +140,7 @@ ${description}]` };
127
140
  * Sends a message in chunks, handling attachments and splitting the message if necessary
128
141
  *
129
142
  * @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
143
+ * @param {TelegramContent} content - The content of the message to be sent
131
144
  * @param {number} [replyToMessageId] - The ID of the message to reply to, if any
132
145
  * @returns {Promise<Message.TextMessage[]>} - An array of TextMessage objects representing the messages sent
133
146
  */
@@ -158,11 +171,13 @@ ${description}]` };
158
171
  } else {
159
172
  const chunks = this.splitMessage(content.text);
160
173
  const sentMessages = [];
174
+ const telegramButtons = convertToTelegramButtons(content.buttons ?? []);
161
175
  for (let i = 0; i < chunks.length; i++) {
162
176
  const chunk = escapeMarkdown(chunks[i]);
163
177
  const sentMessage = await ctx.telegram.sendMessage(ctx.chat.id, chunk, {
164
178
  reply_parameters: i === 0 && replyToMessageId ? { message_id: replyToMessageId } : void 0,
165
- parse_mode: "Markdown"
179
+ parse_mode: "Markdown",
180
+ ...Markup2.inlineKeyboard(telegramButtons)
166
181
  });
167
182
  sentMessages.push(sentMessage);
168
183
  }
@@ -249,8 +264,11 @@ ${description}]` };
249
264
  const message = ctx.message;
250
265
  try {
251
266
  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());
267
+ const threadId = "is_topic_message" in message && message.is_topic_message ? (_a = message.message_thread_id) == null ? void 0 : _a.toString() : void 0;
268
+ const roomId = createUniqueUuid(
269
+ this.runtime,
270
+ threadId ? `${ctx.chat.id}-${threadId}` : ctx.chat.id.toString()
271
+ );
254
272
  const messageId = createUniqueUuid(this.runtime, (_b = message == null ? void 0 : message.message_id) == null ? void 0 : _b.toString());
255
273
  const imageInfo = await this.processImage(message);
256
274
  let messageText = "";
@@ -263,34 +281,6 @@ ${description}]` };
263
281
  if (!fullText) return;
264
282
  const chat = message.chat;
265
283
  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
284
  const memory = {
295
285
  id: messageId,
296
286
  entityId,
@@ -306,6 +296,7 @@ ${description}]` };
306
296
  };
307
297
  const callback = async (content, _files) => {
308
298
  try {
299
+ if (!content.text) return [];
309
300
  const sentMessages = await this.sendMessageInChunks(ctx, content, message.message_id);
310
301
  if (!sentMessages) return [];
311
302
  const memories = [];
@@ -360,7 +351,7 @@ ${description}]` };
360
351
  }
361
352
  /**
362
353
  * 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
354
+ * @param {NarrowedContext<Context<Update>, Update.MessageReactionUpdate>} ctx The context of the message reaction update
364
355
  * @returns {Promise<void>} A Promise that resolves when the reaction handling is complete
365
356
  */
366
357
  async handleReaction(ctx) {
@@ -371,7 +362,6 @@ ${description}]` };
371
362
  try {
372
363
  const entityId = createUniqueUuid(this.runtime, ctx.from.id.toString());
373
364
  const roomId = createUniqueUuid(this.runtime, ctx.chat.id.toString());
374
- const worldId = createUniqueUuid(this.runtime, ctx.chat.id.toString());
375
365
  const reactionId = createUniqueUuid(
376
366
  this.runtime,
377
367
  `${reaction.message_id}-${ctx.from.id}-${Date.now()}`
@@ -472,7 +462,9 @@ ${description}]` };
472
462
  }
473
463
  this.runtime.emitEvent(EventType.MESSAGE_SENT, {
474
464
  runtime: this.runtime,
475
- messages: memories,
465
+ message: {
466
+ content
467
+ },
476
468
  roomId,
477
469
  source: "telegram"
478
470
  });
@@ -496,6 +488,7 @@ var TelegramService = class _TelegramService extends Service {
496
488
  messageManager;
497
489
  options;
498
490
  knownChats = /* @__PURE__ */ new Map();
491
+ syncedEntityIds = /* @__PURE__ */ new Set();
499
492
  /**
500
493
  * Constructor for TelegramService class.
501
494
  * @param {IAgentRuntime} runtime - The runtime object for the agent.
@@ -532,6 +525,7 @@ var TelegramService = class _TelegramService extends Service {
532
525
  );
533
526
  logger2.log("\u{1F680} Starting Telegram bot...");
534
527
  await service.initializeBot();
528
+ service.setupMiddlewares();
535
529
  service.setupMessageHandlers();
536
530
  await service.bot.telegram.getMe();
537
531
  return service;
@@ -584,6 +578,106 @@ var TelegramService = class _TelegramService extends Service {
584
578
  process.once("SIGINT", () => this.bot.stop("SIGINT"));
585
579
  process.once("SIGTERM", () => this.bot.stop("SIGTERM"));
586
580
  }
581
+ /**
582
+ * Sets up the middleware chain for preprocessing messages before they reach handlers.
583
+ * This critical method establishes a sequential processing pipeline that:
584
+ *
585
+ * 1. Authorization - Verifies if a chat is allowed to interact with the bot based on configured settings
586
+ * 2. Chat Discovery - Ensures chat entities and worlds exist in the runtime, creating them if needed
587
+ * 3. Forum Topics - Handles Telegram forum topics as separate rooms for better conversation management
588
+ * 4. Entity Synchronization - Ensures message senders are properly synchronized as entities
589
+ *
590
+ * The middleware chain runs in sequence for each message, with each step potentially
591
+ * enriching the context or stopping processing if conditions aren't met.
592
+ * This preprocessing is essential for maintaining consistent state before message handlers execute.
593
+ *
594
+ * @private
595
+ */
596
+ setupMiddlewares() {
597
+ this.bot.use(this.authorizationMiddleware.bind(this));
598
+ this.bot.use(this.chatAndEntityMiddleware.bind(this));
599
+ }
600
+ /**
601
+ * Authorization middleware - checks if chat is allowed to interact with the bot
602
+ * based on the TELEGRAM_ALLOWED_CHATS configuration.
603
+ *
604
+ * @param {Context} ctx - The context of the incoming update
605
+ * @param {Function} next - The function to call to proceed to the next middleware
606
+ * @returns {Promise<void>}
607
+ * @private
608
+ */
609
+ async authorizationMiddleware(ctx, next) {
610
+ if (!await this.isGroupAuthorized(ctx)) {
611
+ logger2.debug("Chat not authorized, skipping message processing");
612
+ return;
613
+ }
614
+ await next();
615
+ }
616
+ /**
617
+ * Chat and entity management middleware - handles new chats, forum topics, and entity synchronization.
618
+ * This middleware implements decision logic to determine which operations are needed based on
619
+ * the chat type and whether we've seen this chat before.
620
+ *
621
+ * @param {Context} ctx - The context of the incoming update
622
+ * @param {Function} next - The function to call to proceed to the next middleware
623
+ * @returns {Promise<void>}
624
+ * @private
625
+ */
626
+ async chatAndEntityMiddleware(ctx, next) {
627
+ if (!ctx.chat) return next();
628
+ const chatId = ctx.chat.id.toString();
629
+ if (!this.knownChats.has(chatId)) {
630
+ await this.handleNewChat(ctx);
631
+ return next();
632
+ }
633
+ await this.processExistingChat(ctx);
634
+ await next();
635
+ }
636
+ /**
637
+ * Process an existing chat based on chat type and message properties.
638
+ * Different chat types require different processing steps.
639
+ *
640
+ * @param {Context} ctx - The context of the incoming update
641
+ * @returns {Promise<void>}
642
+ * @private
643
+ */
644
+ async processExistingChat(ctx) {
645
+ var _a;
646
+ if (!ctx.chat) return;
647
+ const chat = ctx.chat;
648
+ if (chat.type === "supergroup" && chat.is_forum && ((_a = ctx.message) == null ? void 0 : _a.message_thread_id)) {
649
+ try {
650
+ await this.handleForumTopic(ctx);
651
+ } catch (error) {
652
+ logger2.error(`Error handling forum topic: ${error}`);
653
+ }
654
+ }
655
+ if (ctx.from && ctx.chat.type !== "private") {
656
+ await this.syncEntity(ctx);
657
+ }
658
+ }
659
+ /**
660
+ * Sets up message and reaction handlers for the bot.
661
+ * Configures event handlers to process incoming messages and reactions.
662
+ *
663
+ * @private
664
+ */
665
+ setupMessageHandlers() {
666
+ this.bot.on("message", async (ctx) => {
667
+ try {
668
+ await this.messageManager.handleMessage(ctx);
669
+ } catch (error) {
670
+ logger2.error("Error handling message:", error);
671
+ }
672
+ });
673
+ this.bot.on("message_reaction", async (ctx) => {
674
+ try {
675
+ await this.messageManager.handleReaction(ctx);
676
+ } catch (error) {
677
+ logger2.error("Error handling reaction:", error);
678
+ }
679
+ });
680
+ }
587
681
  /**
588
682
  * Checks if a group is authorized, based on the TELEGRAM_ALLOWED_CHATS setting.
589
683
  * @param {Context} ctx - The context of the incoming update.
@@ -593,9 +687,6 @@ var TelegramService = class _TelegramService extends Service {
593
687
  var _a;
594
688
  const chatId = (_a = ctx.chat) == null ? void 0 : _a.id.toString();
595
689
  if (!chatId) return false;
596
- if (!this.knownChats.has(chatId)) {
597
- await this.handleNewChat(ctx);
598
- }
599
690
  const allowedChats = this.runtime.getSetting("TELEGRAM_ALLOWED_CHATS");
600
691
  if (!allowedChats) {
601
692
  return true;
@@ -609,39 +700,196 @@ var TelegramService = class _TelegramService extends Service {
609
700
  }
610
701
  }
611
702
  /**
612
- * Handles new chat discovery and emits WORLD_JOINED event
703
+ * Synchronizes an entity from a message context with the runtime system.
704
+ * This method handles three cases:
705
+ * 1. Message sender - most common case
706
+ * 2. New chat member - when a user joins the chat
707
+ * 3. Left chat member - when a user leaves the chat
708
+ *
709
+ * @param {Context} ctx - The context of the incoming update
710
+ * @returns {Promise<void>}
711
+ * @private
712
+ */
713
+ async syncEntity(ctx) {
714
+ var _a;
715
+ if (!ctx.chat) return;
716
+ const chat = ctx.chat;
717
+ const chatId = chat.id.toString();
718
+ const worldId = createUniqueUuid2(this.runtime, chatId);
719
+ const roomId = createUniqueUuid2(
720
+ this.runtime,
721
+ ((_a = ctx.message) == null ? void 0 : _a.message_thread_id) ? `${ctx.chat.id}-${ctx.message.message_thread_id}` : ctx.chat.id.toString()
722
+ );
723
+ await this.syncMessageSender(ctx, worldId, roomId, chatId);
724
+ await this.syncNewChatMember(ctx, worldId, roomId, chatId);
725
+ await this.syncLeftChatMember(ctx);
726
+ }
727
+ /**
728
+ * Synchronizes the message sender entity with the runtime system.
729
+ * This is the most common entity sync case.
730
+ *
731
+ * @param {Context} ctx - The context of the incoming update
732
+ * @param {UUID} worldId - The ID of the world
733
+ * @param {UUID} roomId - The ID of the room
734
+ * @param {string} chatId - The ID of the chat
735
+ * @returns {Promise<void>}
736
+ * @private
737
+ */
738
+ async syncMessageSender(ctx, worldId, roomId, chatId) {
739
+ if (ctx.from && !this.syncedEntityIds.has(ctx.from.id.toString())) {
740
+ const telegramId = ctx.from.id.toString();
741
+ const entityId = createUniqueUuid2(this.runtime, telegramId);
742
+ await this.runtime.ensureConnection({
743
+ entityId,
744
+ roomId,
745
+ userName: ctx.from.username,
746
+ userId: telegramId,
747
+ name: ctx.from.first_name || ctx.from.username || "Unknown User",
748
+ source: "telegram",
749
+ channelId: chatId,
750
+ serverId: chatId,
751
+ type: ChannelType2.GROUP,
752
+ worldId
753
+ });
754
+ this.syncedEntityIds.add(entityId);
755
+ }
756
+ }
757
+ /**
758
+ * Synchronizes a new chat member entity with the runtime system.
759
+ * Triggered when a user joins the chat.
760
+ *
761
+ * @param {Context} ctx - The context of the incoming update
762
+ * @param {UUID} worldId - The ID of the world
763
+ * @param {UUID} roomId - The ID of the room
764
+ * @param {string} chatId - The ID of the chat
765
+ * @returns {Promise<void>}
766
+ * @private
767
+ */
768
+ async syncNewChatMember(ctx, worldId, roomId, chatId) {
769
+ if (ctx.message && "new_chat_member" in ctx.message) {
770
+ const newMember = ctx.message.new_chat_member;
771
+ const telegramId = newMember.id.toString();
772
+ const entityId = createUniqueUuid2(this.runtime, telegramId);
773
+ if (this.syncedEntityIds.has(telegramId)) return;
774
+ await this.runtime.ensureConnection({
775
+ entityId,
776
+ roomId,
777
+ userName: newMember.username,
778
+ userId: telegramId,
779
+ name: newMember.first_name || newMember.username || "Unknown User",
780
+ source: "telegram",
781
+ channelId: chatId,
782
+ serverId: chatId,
783
+ type: ChannelType2.GROUP,
784
+ worldId
785
+ });
786
+ this.syncedEntityIds.add(entityId);
787
+ this.runtime.emitEvent(["TELEGRAM_ENTITY_JOINED" /* ENTITY_JOINED */], {
788
+ runtime: this.runtime,
789
+ entityId,
790
+ worldId,
791
+ newMember,
792
+ ctx
793
+ });
794
+ }
795
+ }
796
+ /**
797
+ * Updates entity status when a user leaves the chat.
798
+ *
799
+ * @param {Context} ctx - The context of the incoming update
800
+ * @returns {Promise<void>}
801
+ * @private
802
+ */
803
+ async syncLeftChatMember(ctx) {
804
+ if (ctx.message && "left_chat_member" in ctx.message) {
805
+ const leftMember = ctx.message.left_chat_member;
806
+ const telegramId = leftMember.id.toString();
807
+ const entityId = createUniqueUuid2(this.runtime, telegramId);
808
+ const existingEntity = await this.runtime.getEntityById(entityId);
809
+ if (existingEntity) {
810
+ existingEntity.metadata = {
811
+ ...existingEntity.metadata,
812
+ status: "INACTIVE",
813
+ leftAt: Date.now()
814
+ };
815
+ await this.runtime.updateEntity(existingEntity);
816
+ }
817
+ }
818
+ }
819
+ /**
820
+ * Handles forum topics by creating appropriate rooms in the runtime system.
821
+ * This enables proper conversation management for Telegram's forum feature.
822
+ *
823
+ * @param {Context} ctx - The context of the incoming update
824
+ * @returns {Promise<void>}
825
+ * @private
826
+ */
827
+ async handleForumTopic(ctx) {
828
+ var _a;
829
+ if (!ctx.chat || !((_a = ctx.message) == null ? void 0 : _a.message_thread_id)) return;
830
+ const chat = ctx.chat;
831
+ const chatId = chat.id.toString();
832
+ const worldId = createUniqueUuid2(this.runtime, chatId);
833
+ const room = await this.buildForumTopicRoom(ctx, worldId);
834
+ if (!room) return;
835
+ await this.runtime.ensureRoomExists(room);
836
+ }
837
+ /**
838
+ * Builds entity for message sender
839
+ */
840
+ buildMsgSenderEntity(from) {
841
+ if (!from) return null;
842
+ const userId = createUniqueUuid2(this.runtime, from.id.toString());
843
+ const telegramId = from.id.toString();
844
+ return {
845
+ id: userId,
846
+ agentId: this.runtime.agentId,
847
+ names: [from.first_name || from.username || "Unknown User"],
848
+ metadata: {
849
+ telegram: {
850
+ id: telegramId,
851
+ username: from.username,
852
+ name: from.first_name || from.username || "Unknown User"
853
+ }
854
+ }
855
+ };
856
+ }
857
+ /**
858
+ * Handles new chat discovery and emits WORLD_JOINED event.
859
+ * This is a critical function that ensures new chats are properly
860
+ * registered in the runtime system and appropriate events are emitted.
861
+ *
613
862
  * @param {Context} ctx - The context of the incoming update
863
+ * @returns {Promise<void>}
864
+ * @private
614
865
  */
615
866
  async handleNewChat(ctx) {
867
+ var _a;
616
868
  if (!ctx.chat) return;
617
869
  const chat = ctx.chat;
618
870
  const chatId = chat.id.toString();
619
871
  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
- }
872
+ const { chatTitle, channelType } = this.getChatTypeInfo(chat);
643
873
  const worldId = createUniqueUuid2(this.runtime, chatId);
644
- const roomId = createUniqueUuid2(this.runtime, chatId);
874
+ const existingWorld = await this.runtime.getWorld(worldId);
875
+ if (existingWorld) {
876
+ return;
877
+ }
878
+ const userId = ctx.from ? createUniqueUuid2(this.runtime, ctx.from.id.toString()) : null;
879
+ let admins = [];
880
+ let owner = null;
881
+ if (chat.type === "group" || chat.type === "supergroup" || chat.type === "channel") {
882
+ try {
883
+ admins = await ctx.getChatAdministrators();
884
+ owner = admins.find((admin) => admin.status === "creator");
885
+ } catch (error) {
886
+ logger2.warn(`Could not get chat administrators: ${error.message}`);
887
+ }
888
+ }
889
+ let ownerId = userId;
890
+ if (owner) {
891
+ ownerId = createUniqueUuid2(this.runtime, String(owner.user.id));
892
+ }
645
893
  const world = {
646
894
  id: worldId,
647
895
  name: chatTitle,
@@ -649,14 +897,17 @@ var TelegramService = class _TelegramService extends Service {
649
897
  serverId: chatId,
650
898
  metadata: {
651
899
  source: "telegram",
652
- ownership: { ownerId: chatId },
653
- roles: {
654
- [chatId]: Role.OWNER
655
- }
900
+ ownership: { ownerId },
901
+ roles: ownerId ? {
902
+ [ownerId]: Role.OWNER
903
+ } : {},
904
+ chatType: chat.type,
905
+ isForumEnabled: chat.type === "supergroup" && chat.is_forum
656
906
  }
657
907
  };
658
- const room = {
659
- id: roomId,
908
+ await this.runtime.ensureWorldExists(world);
909
+ const generalRoom = {
910
+ id: createUniqueUuid2(this.runtime, chatId),
660
911
  name: chatTitle,
661
912
  source: "telegram",
662
913
  type: channelType,
@@ -664,204 +915,242 @@ var TelegramService = class _TelegramService extends Service {
664
915
  serverId: chatId,
665
916
  worldId
666
917
  };
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}`);
918
+ await this.runtime.ensureRoomExists(generalRoom);
919
+ const rooms = [generalRoom];
920
+ if (chat.type === "supergroup" && chat.is_forum && ((_a = ctx.message) == null ? void 0 : _a.message_thread_id)) {
921
+ const topicRoom = await this.buildForumTopicRoom(ctx, worldId);
922
+ if (topicRoom) {
923
+ rooms.push(topicRoom);
924
+ }
925
+ await this.runtime.ensureRoomExists(topicRoom);
926
+ }
927
+ const entities = await this.buildStandardizedEntities(chat);
928
+ if (ctx.from) {
929
+ const senderEntity = this.buildMsgSenderEntity(ctx.from);
930
+ if (senderEntity && !entities.some((e) => e.id === senderEntity.id)) {
931
+ entities.push(senderEntity);
932
+ this.syncedEntityIds.add(senderEntity.id);
717
933
  }
718
934
  }
719
- const worldPayload = {
935
+ await this.batchProcessEntities(
936
+ entities,
937
+ generalRoom.id,
938
+ generalRoom.channelId,
939
+ generalRoom.serverId,
940
+ generalRoom.type,
941
+ worldId
942
+ );
943
+ const telegramWorldPayload = {
720
944
  runtime: this.runtime,
721
945
  world,
722
- rooms: [room],
723
- entities: users,
724
- source: "telegram"
725
- };
726
- const telegramWorldPayload = {
727
- ...worldPayload,
728
- chat
946
+ rooms,
947
+ entities,
948
+ source: "telegram",
949
+ chat,
950
+ botUsername: this.bot.botInfo.username
729
951
  };
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);
952
+ if (chat.type !== "private") {
953
+ await this.runtime.emitEvent("TELEGRAM_WORLD_JOINED" /* WORLD_JOINED */, telegramWorldPayload);
734
954
  }
955
+ await this.runtime.emitEvent(EventType2.WORLD_JOINED, {
956
+ runtime: this.runtime,
957
+ world,
958
+ rooms,
959
+ entities,
960
+ source: "telegram"
961
+ });
735
962
  }
736
963
  /**
737
- * Sets up message and reaction handlers for the bot.
964
+ * Processes entities in batches to prevent overwhelming the system.
738
965
  *
966
+ * @param {Entity[]} entities - The entities to process
967
+ * @param {UUID} roomId - The ID of the room to connect entities to
968
+ * @param {string} channelId - The channel ID
969
+ * @param {string} serverId - The server ID
970
+ * @param {ChannelType} roomType - The type of the room
971
+ * @param {UUID} worldId - The ID of the world
972
+ * @returns {Promise<void>}
739
973
  * @private
740
- * @returns {void}
741
974
  */
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);
975
+ async batchProcessEntities(entities, roomId, channelId, serverId, roomType, worldId) {
976
+ const batchSize = 50;
977
+ for (let i = 0; i < entities.length; i += batchSize) {
978
+ const entityBatch = entities.slice(i, i + batchSize);
979
+ await Promise.all(
980
+ entityBatch.map(async (entity) => {
981
+ var _a, _b, _c, _d, _e, _f, _g, _h;
982
+ try {
983
+ await this.runtime.ensureConnection({
984
+ entityId: entity.id,
985
+ roomId,
986
+ userName: (_b = (_a = entity.metadata) == null ? void 0 : _a.telegram) == null ? void 0 : _b.username,
987
+ name: (_d = (_c = entity.metadata) == null ? void 0 : _c.telegram) == null ? void 0 : _d.name,
988
+ userId: (_f = (_e = entity.metadata) == null ? void 0 : _e.telegram) == null ? void 0 : _f.id,
989
+ source: "telegram",
990
+ channelId,
991
+ serverId,
992
+ type: roomType,
993
+ worldId
994
+ });
995
+ } catch (err) {
996
+ logger2.warn(`Failed to sync user ${(_h = (_g = entity.metadata) == null ? void 0 : _g.telegram) == null ? void 0 : _h.username}: ${err}`);
997
+ }
998
+ })
999
+ );
1000
+ if (i + batchSize < entities.length) {
1001
+ await new Promise((resolve) => setTimeout(resolve, 500));
757
1002
  }
758
- });
1003
+ }
759
1004
  }
760
1005
  /**
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
1006
+ * Gets chat title and channel type based on Telegram chat type.
1007
+ * Maps Telegram-specific chat types to standardized system types.
1008
+ *
1009
+ * @param {any} chat - The Telegram chat object
1010
+ * @returns {Object} Object containing chatTitle and channelType
1011
+ * @private
763
1012
  */
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",
1013
+ getChatTypeInfo(chat) {
1014
+ let chatTitle;
1015
+ let channelType;
1016
+ switch (chat.type) {
1017
+ case "private":
1018
+ chatTitle = `Chat with ${chat.first_name || "Unknown User"}`;
1019
+ channelType = ChannelType2.DM;
1020
+ break;
1021
+ case "group":
1022
+ chatTitle = chat.title || "Unknown Group";
1023
+ channelType = ChannelType2.GROUP;
1024
+ break;
1025
+ case "supergroup":
1026
+ chatTitle = chat.title || "Unknown Supergroup";
1027
+ channelType = ChannelType2.GROUP;
1028
+ break;
1029
+ case "channel":
1030
+ chatTitle = chat.title || "Unknown Channel";
1031
+ channelType = ChannelType2.FEED;
1032
+ break;
1033
+ default:
1034
+ chatTitle = "Unknown Chat";
1035
+ channelType = ChannelType2.GROUP;
1036
+ }
1037
+ return { chatTitle, channelType };
1038
+ }
1039
+ /**
1040
+ * Builds standardized entity representations from Telegram chat data.
1041
+ * Transforms Telegram-specific user data into system-standard Entity objects.
1042
+ *
1043
+ * @param {any} chat - The Telegram chat object
1044
+ * @returns {Promise<Entity[]>} Array of standardized Entity objects
1045
+ * @private
1046
+ */
1047
+ async buildStandardizedEntities(chat) {
1048
+ const entities = [];
1049
+ try {
1050
+ if (chat.type === "private" && chat.id) {
1051
+ const userId = createUniqueUuid2(this.runtime, chat.id.toString());
1052
+ entities.push({
1053
+ id: userId,
1054
+ names: [chat.first_name || "Unknown User"],
1055
+ agentId: this.runtime.agentId,
796
1056
  metadata: {
797
- joinedAt: Date.now()
1057
+ telegram: {
1058
+ id: chat.id.toString(),
1059
+ username: chat.username || "unknown",
1060
+ name: chat.first_name || "Unknown User"
1061
+ },
1062
+ source: "telegram"
798
1063
  }
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
1064
+ });
1065
+ this.syncedEntityIds.add(userId);
1066
+ } else if (chat.type === "group" || chat.type === "supergroup") {
1067
+ try {
1068
+ const admins = await this.bot.telegram.getChatAdministrators(chat.id);
1069
+ if (admins && admins.length > 0) {
1070
+ for (const admin of admins) {
1071
+ const userId = createUniqueUuid2(this.runtime, admin.user.id.toString());
1072
+ entities.push({
1073
+ id: userId,
1074
+ names: [admin.user.first_name || admin.user.username || "Unknown Admin"],
1075
+ agentId: this.runtime.agentId,
1076
+ metadata: {
1077
+ telegram: {
1078
+ id: admin.user.id.toString(),
1079
+ username: admin.user.username || "unknown",
1080
+ name: admin.user.first_name || "Unknown Admin",
1081
+ isAdmin: true,
1082
+ adminTitle: admin.custom_title || (admin.status === "creator" ? "Owner" : "Admin")
1083
+ },
1084
+ source: "telegram",
1085
+ roles: [admin.status === "creator" ? Role.OWNER : Role.ADMIN]
1086
+ }
1087
+ });
1088
+ this.syncedEntityIds.add(userId);
1089
+ }
806
1090
  }
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);
1091
+ } catch (error) {
1092
+ logger2.warn(`Could not fetch administrators for chat ${chat.id}: ${error}`);
1093
+ }
815
1094
  }
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
- );
1095
+ } catch (error) {
1096
+ logger2.error(
1097
+ `Error building standardized entities: ${error instanceof Error ? error.message : String(error)}`
1098
+ );
1099
+ }
1100
+ return entities;
1101
+ }
1102
+ /**
1103
+ * Extracts and builds the room object for a forum topic from a message context.
1104
+ * This refactored method can be used both in middleware and when handling new chats.
1105
+ *
1106
+ * @param {Context} ctx - The context of the incoming update
1107
+ * @param {UUID} worldId - The ID of the world the topic belongs to
1108
+ * @returns {Promise<Room | null>} A Promise that resolves with the room or null if not a topic
1109
+ * @private
1110
+ */
1111
+ async buildForumTopicRoom(ctx, worldId) {
1112
+ var _a;
1113
+ if (!ctx.chat || !((_a = ctx.message) == null ? void 0 : _a.message_thread_id)) return null;
1114
+ if (ctx.chat.type !== "supergroup" || !ctx.chat.is_forum) return null;
1115
+ const chat = ctx.chat;
1116
+ const chatId = chat.id.toString();
1117
+ const threadId = ctx.message.message_thread_id.toString();
1118
+ const roomId = createUniqueUuid2(this.runtime, `${chatId}-${threadId}`);
1119
+ try {
1120
+ const replyMessage = JSON.parse(JSON.stringify(ctx.message));
1121
+ let topicName = `Topic #${threadId}`;
1122
+ if (replyMessage && typeof replyMessage === "object" && "forum_topic_created" in replyMessage && replyMessage.forum_topic_created) {
1123
+ const topicCreated = replyMessage.forum_topic_created;
1124
+ if (topicCreated && typeof topicCreated === "object" && "name" in topicCreated) {
1125
+ topicName = topicCreated.name;
1126
+ }
1127
+ } 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) {
1128
+ const topicCreated = replyMessage.reply_to_message.forum_topic_created;
1129
+ if (topicCreated && typeof topicCreated === "object" && "name" in topicCreated) {
1130
+ topicName = topicCreated.name;
860
1131
  }
861
- } catch (error) {
862
- logger2.error(`Error handling Telegram entity leaving chat ${chatId}:`, error);
863
1132
  }
864
- });
1133
+ const room = {
1134
+ id: roomId,
1135
+ name: topicName,
1136
+ source: "telegram",
1137
+ type: ChannelType2.GROUP,
1138
+ channelId: `${chatId}-${threadId}`,
1139
+ serverId: chatId,
1140
+ worldId,
1141
+ metadata: {
1142
+ threadId,
1143
+ isForumTopic: true,
1144
+ parentChatId: chatId
1145
+ }
1146
+ };
1147
+ return room;
1148
+ } catch (error) {
1149
+ logger2.error(
1150
+ `Error building forum topic room: ${error instanceof Error ? error.message : String(error)}`
1151
+ );
1152
+ return null;
1153
+ }
865
1154
  }
866
1155
  };
867
1156