@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 +57 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +399 -9
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +96 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +494 -8
- package/src/types.ts +105 -0
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,WAAW,MAAM,uBAAuB,CAAC;AAEhD,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,eAAe,EACpB,KAAK,mBAAmB,EAGxB,KAAK,cAAc,
|
|
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
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
395
|
-
if (
|
|
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))
|