@ebowwa/channel-telegram 1.17.0 → 1.17.2

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.d.ts CHANGED
@@ -16,10 +16,10 @@
16
16
  import TelegramBot from "node-telegram-bot-api";
17
17
  import { type ChannelId, type ChannelResponse, type ChannelCapabilities, type MessageHandler } from "@ebowwa/channel-types";
18
18
  import { ConversationMemory } from "./conversation-memory.js";
19
- import type { Tool } from "./types.js";
19
+ import type { Tool, LiveLocationState, SendLocationOptions, EditLiveLocationOptions } from "./types.js";
20
20
  export { ConversationMemory } from "./conversation-memory.js";
21
21
  export { registerAllCommands, isQuiet } from "./commands/index.js";
22
- export type { Tool, MessageEntity, MessageEntityType, TelegramUser, ConversationMessage, ReactionType, ReactionTypeEmoji, ReactionTypeCustomEmoji, ChatMember, ChatMemberMember, ChatMemberRestricted, ChatMemberAdministrator, ChatPermissions, ChatAdministratorRights, SendMessageDraftParams, SetMessageReactionParams, SetChatMemberTagParams, PromoteChatMemberParams, LinkPreviewOptions, } from "./types.js";
22
+ export type { Tool, MessageEntity, MessageEntityType, TelegramUser, ConversationMessage, ReactionType, ReactionTypeEmoji, ReactionTypeCustomEmoji, ChatMember, ChatMemberMember, ChatMemberRestricted, ChatMemberAdministrator, ChatPermissions, ChatAdministratorRights, SendMessageDraftParams, SetMessageReactionParams, SetChatMemberTagParams, PromoteChatMemberParams, LinkPreviewOptions, TelegramLocation, TelegramVenue, LiveLocationState, SendLocationOptions, EditLiveLocationOptions, } from "./types.js";
23
23
  export { normalizeTelegramMessagingTarget, looksLikeTelegramTargetId, extractRawTarget, isUsernameTarget, isNumericIdTarget, } from "./utils/index.js";
24
24
  export interface TelegramConfig {
25
25
  botToken: string;
@@ -53,6 +53,9 @@ export declare class TelegramChannel {
53
53
  private typingIntervals;
54
54
  private connected;
55
55
  private activeStreams;
56
+ private liveLocations;
57
+ private liveLocationCleanupInterval?;
58
+ private locationData;
56
59
  constructor(config: TelegramConfig);
57
60
  start(): Promise<void>;
58
61
  stop(): Promise<void>;
@@ -112,6 +115,58 @@ export declare class TelegramChannel {
112
115
  * Check if user/chat is allowed.
113
116
  */
114
117
  isAllowed(userId?: number, chatId?: number): boolean;
118
+ /**
119
+ * Send a static or live location.
120
+ * If live_period is set, the location can be updated with editLiveLocation.
121
+ */
122
+ sendLocation(chatId: number, options: SendLocationOptions): Promise<TelegramBot.Message>;
123
+ /**
124
+ * Edit a live location message.
125
+ * Updates the location and returns the edited message.
126
+ */
127
+ editLiveLocation(chatId: number, messageId: number, options: EditLiveLocationOptions): Promise<TelegramBot.Message | boolean>;
128
+ /**
129
+ * Stop a live location message.
130
+ * The location will no longer update.
131
+ */
132
+ stopLiveLocation(chatId: number, messageId: number): Promise<TelegramBot.Message | boolean>;
133
+ /**
134
+ * Get live location state for a specific message.
135
+ */
136
+ getLiveLocation(chatId: number, messageId: number): LiveLocationState | undefined;
137
+ /**
138
+ * Get all active live locations being tracked.
139
+ */
140
+ getActiveLiveLocations(): LiveLocationState[];
141
+ /**
142
+ * Get live locations shared by a specific user.
143
+ */
144
+ getLiveLocationsByUser(userId: number): LiveLocationState[];
145
+ /**
146
+ * Clean up expired live locations.
147
+ */
148
+ private cleanupExpiredLiveLocations;
149
+ /**
150
+ * Start the live location cleanup timer (runs every 5 minutes).
151
+ */
152
+ private startLiveLocationCleanupTimer;
153
+ /**
154
+ * Start periodic cleanup of expired live locations.
155
+ */
156
+ private startLiveLocationCleanup;
157
+ /**
158
+ * Stop live location cleanup interval.
159
+ */
160
+ private stopLiveLocationCleanup;
161
+ /**
162
+ * Handle incoming location message (static or live).
163
+ * Updates internal tracking and routes to message handler.
164
+ */
165
+ private handleLocationMessage;
166
+ /**
167
+ * Handle live location update (edited_message with location).
168
+ */
169
+ private handleLocationUpdate;
115
170
  private setupMessageHandlers;
116
171
  /**
117
172
  * Register Telegram command menu
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -16,7 +16,6 @@
16
16
  import TelegramBot from "node-telegram-bot-api";
17
17
  import { createChannelId, } from "@ebowwa/channel-types";
18
18
  import { ConversationMemory } from "./conversation-memory.js";
19
- import { registerAllCommands } from "./commands/index.js";
20
19
  // ============================================================
21
20
  // PLUGIN EXPORTS (ChannelPlugin Pattern)
22
21
  // ============================================================
@@ -73,6 +72,11 @@ export class TelegramChannel {
73
72
  connected = false;
74
73
  // Streaming state
75
74
  activeStreams = new Map();
75
+ // Live location tracking state
76
+ liveLocations = new Map();
77
+ liveLocationCleanupInterval;
78
+ // Location data storage for message metadata
79
+ locationData = new Map();
76
80
  constructor(config) {
77
81
  this.config = config;
78
82
  this.id = createChannelId("telegram", "default");
@@ -93,6 +97,9 @@ export class TelegramChannel {
93
97
  reactions: true,
94
98
  editing: true,
95
99
  streaming: this.nativeStreaming,
100
+ locations: true,
101
+ liveLocations: true,
102
+ venues: true,
96
103
  },
97
104
  media: {
98
105
  maxFileSize: 50 * 1024 * 1024, // 50MB
@@ -119,9 +126,13 @@ export class TelegramChannel {
119
126
  },
120
127
  },
121
128
  });
122
- // Register built-in commands
123
- console.log(`[TelegramChannel] Registering commands with ${this.tools.length} tools`);
124
- registerAllCommands(this.bot, this.memory, this.tools);
129
+ // Start live location cleanup interval (every 5 minutes)
130
+ this.startLiveLocationCleanupTimer();
131
+ // NOTE: Commands are NOT registered here with bot.onText() handlers.
132
+ // Instead, all messages (including commands) flow through the message event
133
+ // to the daemon's messageHandler, which uses @ebowwa/channel-command for unified handling.
134
+ // This ensures commands have access to the full daemon context (config, tools, etc.)
135
+ console.log(`[TelegramChannel] Ready with ${this.tools.length} tools (commands handled by daemon)`);
125
136
  // Setup message routing
126
137
  this.setupMessageHandlers();
127
138
  // Register Telegram commands menu (non-blocking, ignore rate limits)
@@ -131,6 +142,13 @@ export class TelegramChannel {
131
142
  }
132
143
  async stop() {
133
144
  console.log("[TelegramChannel] Stopping...");
145
+ // Stop live location cleanup timer
146
+ if (this.liveLocationCleanupInterval) {
147
+ clearInterval(this.liveLocationCleanupInterval);
148
+ this.liveLocationCleanupInterval = undefined;
149
+ }
150
+ // Clear all live locations
151
+ this.liveLocations.clear();
134
152
  await this.bot.stopPolling();
135
153
  this.connected = false;
136
154
  }
@@ -384,16 +402,369 @@ export class TelegramChannel {
384
402
  return false;
385
403
  }
386
404
  // ============================================================
405
+ // Live Location Methods
406
+ // ============================================================
407
+ /**
408
+ * Send a static or live location.
409
+ * If live_period is set, the location can be updated with editLiveLocation.
410
+ */
411
+ async sendLocation(chatId, options) {
412
+ const result = await this.bot.sendLocation(chatId, options.latitude, options.longitude, {
413
+ live_period: options.live_period,
414
+ disable_notification: options.disable_notification,
415
+ });
416
+ // Track live location if live_period is set
417
+ if (options.live_period && result.location) {
418
+ const key = `${chatId}:${result.message_id}`;
419
+ const expiresAt = options.live_period
420
+ ? new Date(Date.now() + options.live_period * 1000)
421
+ : undefined;
422
+ const locationData = {
423
+ latitude: result.location.latitude,
424
+ longitude: result.location.longitude,
425
+ live_period: options.live_period,
426
+ heading: options.heading,
427
+ horizontal_accuracy: options.horizontal_accuracy,
428
+ proximity_alert_radius: options.proximity_alert_radius,
429
+ };
430
+ this.liveLocations.set(key, {
431
+ chatId,
432
+ messageId: result.message_id,
433
+ userId: result.from?.id || 0,
434
+ location: locationData,
435
+ startedAt: new Date(),
436
+ expiresAt,
437
+ updateCount: 0,
438
+ });
439
+ console.log(`[TelegramChannel] Started tracking live location: ${key}, expires in ${options.live_period}s`);
440
+ }
441
+ return result;
442
+ }
443
+ /**
444
+ * Edit a live location message.
445
+ * Updates the location and returns the edited message.
446
+ */
447
+ async editLiveLocation(chatId, messageId, options) {
448
+ const result = await this.bot.editMessageLiveLocation(options.latitude, options.longitude, {
449
+ chat_id: chatId,
450
+ message_id: messageId,
451
+ });
452
+ // Update tracked state
453
+ const key = `${chatId}:${messageId}`;
454
+ const state = this.liveLocations.get(key);
455
+ if (state) {
456
+ state.location = {
457
+ ...state.location,
458
+ latitude: options.latitude,
459
+ longitude: options.longitude,
460
+ horizontal_accuracy: options.horizontal_accuracy,
461
+ heading: options.heading,
462
+ proximity_alert_radius: options.proximity_alert_radius,
463
+ };
464
+ state.updateCount++;
465
+ console.log(`[TelegramChannel] Updated live location: ${key} (update #${state.updateCount})`);
466
+ }
467
+ return result;
468
+ }
469
+ /**
470
+ * Stop a live location message.
471
+ * The location will no longer update.
472
+ */
473
+ async stopLiveLocation(chatId, messageId) {
474
+ const result = await this.bot.stopMessageLiveLocation({
475
+ chat_id: chatId,
476
+ message_id: messageId,
477
+ });
478
+ // Remove from tracking
479
+ const key = `${chatId}:${messageId}`;
480
+ const state = this.liveLocations.get(key);
481
+ if (state) {
482
+ console.log(`[TelegramChannel] Stopped live location: ${key} (had ${state.updateCount} updates)`);
483
+ this.liveLocations.delete(key);
484
+ }
485
+ return result;
486
+ }
487
+ /**
488
+ * Get live location state for a specific message.
489
+ */
490
+ getLiveLocation(chatId, messageId) {
491
+ const key = `${chatId}:${messageId}`;
492
+ return this.liveLocations.get(key);
493
+ }
494
+ /**
495
+ * Get all active live locations being tracked.
496
+ */
497
+ getActiveLiveLocations() {
498
+ // Clean up expired locations first
499
+ this.cleanupExpiredLiveLocations();
500
+ return Array.from(this.liveLocations.values());
501
+ }
502
+ /**
503
+ * Get live locations shared by a specific user.
504
+ */
505
+ getLiveLocationsByUser(userId) {
506
+ this.cleanupExpiredLiveLocations();
507
+ return Array.from(this.liveLocations.values()).filter((state) => state.userId === userId);
508
+ }
509
+ /**
510
+ * Clean up expired live locations.
511
+ */
512
+ cleanupExpiredLiveLocations() {
513
+ const now = new Date();
514
+ for (const [key, state] of this.liveLocations) {
515
+ if (state.expiresAt && state.expiresAt < now) {
516
+ console.log(`[TelegramChannel] Cleaned up expired live location: ${key}`);
517
+ this.liveLocations.delete(key);
518
+ }
519
+ }
520
+ }
521
+ /**
522
+ * Start the live location cleanup timer (runs every 5 minutes).
523
+ */
524
+ startLiveLocationCleanupTimer() {
525
+ // Clean up every 5 minutes
526
+ this.liveLocationCleanupInterval = setInterval(() => {
527
+ this.cleanupExpiredLiveLocations();
528
+ }, 5 * 60 * 1000);
529
+ console.log("[TelegramChannel] Started live location cleanup timer");
530
+ }
531
+ /**
532
+ * Start periodic cleanup of expired live locations.
533
+ */
534
+ startLiveLocationCleanup() {
535
+ // Clean up every 5 minutes
536
+ this.liveLocationCleanupInterval = setInterval(() => {
537
+ this.cleanupExpiredLiveLocations();
538
+ }, 5 * 60 * 1000);
539
+ }
540
+ /**
541
+ * Stop live location cleanup interval.
542
+ */
543
+ stopLiveLocationCleanup() {
544
+ if (this.liveLocationCleanupInterval) {
545
+ clearInterval(this.liveLocationCleanupInterval);
546
+ this.liveLocationCleanupInterval = undefined;
547
+ }
548
+ }
549
+ /**
550
+ * Handle incoming location message (static or live).
551
+ * Updates internal tracking and routes to message handler.
552
+ */
553
+ handleLocationMessage(msg) {
554
+ if (!msg.location)
555
+ return null;
556
+ // Cast to extended location type that includes all Telegram properties
557
+ const rawLocation = msg.location;
558
+ const location = {
559
+ latitude: rawLocation.latitude,
560
+ longitude: rawLocation.longitude,
561
+ horizontal_accuracy: rawLocation.horizontal_accuracy,
562
+ live_period: rawLocation.live_period,
563
+ heading: rawLocation.heading,
564
+ proximity_alert_radius: rawLocation.proximity_alert_radius,
565
+ };
566
+ // Track live locations from users
567
+ if (location.live_period) {
568
+ const key = `${msg.chat.id}:${msg.message_id}`;
569
+ const expiresAt = new Date(Date.now() + location.live_period * 1000);
570
+ this.liveLocations.set(key, {
571
+ chatId: msg.chat.id,
572
+ messageId: msg.message_id,
573
+ userId: msg.from?.id || 0,
574
+ location,
575
+ startedAt: new Date(),
576
+ expiresAt,
577
+ updateCount: 0,
578
+ });
579
+ console.log(`[TelegramChannel] Tracking user live location: ${key}, expires in ${location.live_period}s`);
580
+ }
581
+ // Create ChannelMessage
582
+ const sender = {
583
+ id: msg.from?.id?.toString() || msg.chat.id.toString(),
584
+ username: msg.from?.username,
585
+ displayName: msg.from?.first_name || msg.from?.username || "User",
586
+ isBot: msg.from?.is_bot || false,
587
+ };
588
+ const context = {
589
+ isDM: msg.chat.type === "private",
590
+ groupName: msg.chat.type !== "private" ? msg.chat.title : undefined,
591
+ };
592
+ const chatChannelId = createChannelId("telegram", msg.chat.id.toString());
593
+ // Build text representation for location
594
+ const locationText = location.live_period
595
+ ? `[Live Location] ${location.latitude.toFixed(6)}, ${location.longitude.toFixed(6)} (live for ${Math.floor(location.live_period / 60)}m)`
596
+ : `[Location] ${location.latitude.toFixed(6)}, ${location.longitude.toFixed(6)}`;
597
+ // Store location info in a separate map for retrieval
598
+ const locationKey = `${chatChannelId.accountId}:${msg.message_id}`;
599
+ this.locationData.set(locationKey, {
600
+ type: "location",
601
+ location,
602
+ isLive: !!location.live_period,
603
+ venue: msg.venue,
604
+ });
605
+ // Build media attachment with location data
606
+ const media = [];
607
+ if (location.live_period) {
608
+ // Live location
609
+ media.push({
610
+ type: "location",
611
+ location: {
612
+ type: "live",
613
+ latitude: location.latitude,
614
+ longitude: location.longitude,
615
+ accuracy: location.horizontal_accuracy,
616
+ heading: location.heading,
617
+ sessionId: `live_${msg.chat.id}_${msg.message_id}`,
618
+ startedAt: new Date(msg.date * 1000),
619
+ expiresAt: new Date(Date.now() + location.live_period * 1000),
620
+ livePeriod: location.live_period,
621
+ updateCount: 0,
622
+ status: "active",
623
+ },
624
+ });
625
+ }
626
+ else {
627
+ // Static location
628
+ media.push({
629
+ type: "location",
630
+ location: {
631
+ type: "static",
632
+ latitude: location.latitude,
633
+ longitude: location.longitude,
634
+ accuracy: location.horizontal_accuracy,
635
+ heading: location.heading,
636
+ timestamp: new Date(msg.date * 1000),
637
+ },
638
+ });
639
+ }
640
+ // Add venue info if present
641
+ if (msg.venue) {
642
+ media.push({
643
+ type: "venue",
644
+ location: {
645
+ type: "static",
646
+ latitude: location.latitude,
647
+ longitude: location.longitude,
648
+ timestamp: new Date(msg.date * 1000),
649
+ label: msg.venue.title,
650
+ address: msg.venue.address,
651
+ },
652
+ });
653
+ }
654
+ return {
655
+ messageId: msg.message_id.toString(),
656
+ channelId: chatChannelId,
657
+ timestamp: new Date(msg.date * 1000),
658
+ sender,
659
+ text: locationText,
660
+ media,
661
+ context,
662
+ };
663
+ }
664
+ /**
665
+ * Handle live location update (edited_message with location).
666
+ */
667
+ handleLocationUpdate(msg) {
668
+ if (!msg.location)
669
+ return null;
670
+ const key = `${msg.chat.id}:${msg.message_id}`;
671
+ const state = this.liveLocations.get(key);
672
+ // Cast to extended location type
673
+ const rawLocation = msg.location;
674
+ if (state) {
675
+ // Update tracked location
676
+ state.location = {
677
+ latitude: rawLocation.latitude,
678
+ longitude: rawLocation.longitude,
679
+ horizontal_accuracy: rawLocation.horizontal_accuracy,
680
+ heading: rawLocation.heading,
681
+ proximity_alert_radius: rawLocation.proximity_alert_radius,
682
+ live_period: rawLocation.live_period,
683
+ };
684
+ state.updateCount++;
685
+ console.log(`[TelegramChannel] Live location update: ${key} (update #${state.updateCount})`);
686
+ }
687
+ // Create ChannelMessage for the update
688
+ const sender = {
689
+ id: msg.from?.id?.toString() || msg.chat.id.toString(),
690
+ username: msg.from?.username,
691
+ displayName: msg.from?.first_name || msg.from?.username || "User",
692
+ isBot: msg.from?.is_bot || false,
693
+ };
694
+ const context = {
695
+ isDM: msg.chat.type === "private",
696
+ groupName: msg.chat.type !== "private" ? msg.chat.title : undefined,
697
+ };
698
+ const chatChannelId = createChannelId("telegram", msg.chat.id.toString());
699
+ // Store location update info
700
+ const locationKey = `${chatChannelId.accountId}:${msg.message_id}`;
701
+ this.locationData.set(locationKey, {
702
+ type: "location_update",
703
+ location: {
704
+ latitude: rawLocation.latitude,
705
+ longitude: rawLocation.longitude,
706
+ horizontal_accuracy: rawLocation.horizontal_accuracy,
707
+ heading: rawLocation.heading,
708
+ proximity_alert_radius: rawLocation.proximity_alert_radius,
709
+ },
710
+ isLive: true,
711
+ updateNumber: state?.updateCount || 0,
712
+ });
713
+ // Build media attachment with live location update data
714
+ const media = [{
715
+ type: "location",
716
+ location: {
717
+ type: "live",
718
+ latitude: rawLocation.latitude,
719
+ longitude: rawLocation.longitude,
720
+ accuracy: rawLocation.horizontal_accuracy,
721
+ heading: rawLocation.heading,
722
+ sessionId: `live_${msg.chat.id}_${msg.message_id}`,
723
+ startedAt: state?.startedAt || new Date(msg.date * 1000),
724
+ expiresAt: state?.expiresAt,
725
+ updateCount: state?.updateCount || 0,
726
+ status: "active",
727
+ },
728
+ }];
729
+ return {
730
+ messageId: msg.message_id.toString(),
731
+ channelId: chatChannelId,
732
+ timestamp: new Date(msg.date * 1000),
733
+ sender,
734
+ text: `[Live Location Update] ${rawLocation.latitude.toFixed(6)}, ${rawLocation.longitude.toFixed(6)}`,
735
+ media,
736
+ context,
737
+ };
738
+ }
739
+ // ============================================================
387
740
  // Message Routing (Internal)
388
741
  // ============================================================
389
742
  setupMessageHandlers() {
390
- // Text messages (skip commands - handled by onText handlers, except /prompt which is passed to handler)
743
+ // Text messages - all go to the daemon's messageHandler (including commands)
744
+ // The daemon uses @ebowwa/channel-command for unified command handling with full context
391
745
  this.bot.on("message", async (msg) => {
392
- if (!msg.text)
746
+ // Check for location messages first
747
+ if (msg.location) {
748
+ if (!this.isAllowed(msg.from?.id, msg.chat.id))
749
+ return;
750
+ const message = this.handleLocationMessage(msg);
751
+ if (message && this.messageHandler) {
752
+ console.log(`[TelegramChannel] Location from ${message.sender.displayName}: ${message.text}`);
753
+ try {
754
+ const response = await this.messageHandler(message);
755
+ if (response) {
756
+ await this.send(response);
757
+ }
758
+ }
759
+ catch (error) {
760
+ console.error("[TelegramChannel] Location handler error:", error);
761
+ }
762
+ }
393
763
  return;
394
- // Skip commands except /prompt and /queue which should be handled by the daemon
395
- if (msg.text.startsWith("/") && !msg.text.startsWith("/prompt") && !msg.text.startsWith("/queue"))
764
+ }
765
+ if (!msg.text)
396
766
  return;
767
+ // All messages flow to the daemon, including commands
397
768
  if (!this.isAllowed(msg.from?.id, msg.chat.id))
398
769
  return;
399
770
  const message = this.createChannelMessage(msg);
@@ -412,8 +783,27 @@ export class TelegramChannel {
412
783
  }
413
784
  }
414
785
  });
415
- // Edited messages
786
+ // Edited messages (includes live location updates)
416
787
  this.bot.on("edited_message", async (msg) => {
788
+ // Handle live location updates
789
+ if (msg.location) {
790
+ if (!this.isAllowed(msg.from?.id, msg.chat.id))
791
+ return;
792
+ const message = this.handleLocationUpdate(msg);
793
+ if (message && this.messageHandler) {
794
+ console.log(`[TelegramChannel] Live location update from ${message.sender.displayName}`);
795
+ try {
796
+ const response = await this.messageHandler(message);
797
+ if (response) {
798
+ await this.send(response);
799
+ }
800
+ }
801
+ catch (error) {
802
+ console.error("[TelegramChannel] Location update handler error:", error);
803
+ }
804
+ }
805
+ return;
806
+ }
417
807
  if (!msg.text)
418
808
  return;
419
809
  if (!this.isAllowed(msg.from?.id, msg.chat.id))