@llblab/pi-telegram 0.17.5 → 0.18.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/AGENTS.md +67 -32
- package/BACKLOG.md +59 -19
- package/CHANGELOG.md +36 -15
- package/README.md +63 -35
- package/docs/README.md +3 -1
- package/docs/architecture.md +55 -23
- package/docs/callback-namespaces.md +1 -1
- package/docs/inbound.md +1 -1
- package/docs/locks.md +0 -2
- package/docs/multi-instance-bus.md +483 -0
- package/docs/outbound.md +4 -3
- package/docs/public-api.md +12 -10
- package/docs/sections.md +2 -2
- package/docs/ui-style.md +76 -0
- package/index.ts +789 -32
- package/lib/bindings.ts +68 -12
- package/lib/bus-api.ts +314 -0
- package/lib/bus-follower.ts +853 -0
- package/lib/bus-leader.ts +915 -0
- package/lib/bus.ts +866 -0
- package/lib/command-templates.ts +9 -11
- package/lib/commands.ts +133 -47
- package/lib/config.ts +53 -5
- package/lib/lifecycle.ts +23 -7
- package/lib/locks.ts +230 -66
- package/lib/media.ts +30 -2
- package/lib/menu-model.ts +48 -17
- package/lib/menu-queue.ts +51 -20
- package/lib/menu-settings.ts +9 -5
- package/lib/menu-status.ts +3 -0
- package/lib/menu-thinking.ts +3 -0
- package/lib/menu.ts +67 -26
- package/lib/outbound-attachments.ts +102 -17
- package/lib/outbound-buttons.ts +6 -2
- package/lib/outbound-voice.ts +31 -11
- package/lib/outbound.ts +6 -4
- package/lib/ownership.ts +119 -0
- package/lib/pi.ts +26 -3
- package/lib/polling.ts +477 -7
- package/lib/preview.ts +141 -88
- package/lib/prompt-templates.ts +3 -3
- package/lib/prompts.ts +80 -30
- package/lib/queue.ts +193 -91
- package/lib/rendering.ts +0 -25
- package/lib/replies.ts +187 -55
- package/lib/routing.ts +1673 -9
- package/lib/runtime-log.ts +123 -0
- package/lib/runtime.ts +84 -12
- package/lib/sections.ts +28 -21
- package/lib/setup.ts +1 -1
- package/lib/status.ts +532 -9
- package/lib/sync.ts +618 -0
- package/lib/target.ts +49 -0
- package/lib/telegram-api.ts +405 -40
- package/lib/text-groups.ts +5 -1
- package/lib/thread-reconciler.ts +915 -0
- package/lib/threads.ts +2205 -0
- package/lib/turns.ts +48 -3
- package/lib/updates.ts +355 -32
- package/package.json +24 -2
- package/docs/telegram-bot-api-rich-messages.md +0 -890
package/lib/updates.ts
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
* Owns update extraction, authorization, classification, execution planning, runtime execution, and the public update-handler registry
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import {
|
|
8
|
+
createTelegramPrivateTarget,
|
|
9
|
+
createTelegramThreadTarget,
|
|
10
|
+
type TelegramTarget,
|
|
11
|
+
} from "./target.ts";
|
|
7
12
|
import {
|
|
8
13
|
createTelegramUserPairingRuntime,
|
|
9
14
|
getTelegramAuthorizationState,
|
|
@@ -122,6 +127,48 @@ export interface TelegramUpdateMessage {
|
|
|
122
127
|
chat: TelegramChat;
|
|
123
128
|
from?: TelegramUser;
|
|
124
129
|
message_id?: number;
|
|
130
|
+
message_thread_id?: number;
|
|
131
|
+
forum_topic_created?: unknown;
|
|
132
|
+
forum_topic_closed?: unknown;
|
|
133
|
+
forum_topic_reopened?: unknown;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type TelegramTopicLifecycleKind = "created" | "closed" | "reopened";
|
|
137
|
+
|
|
138
|
+
export interface TelegramTopicLifecycleUpdate<
|
|
139
|
+
TMessage = TelegramUpdateMessage,
|
|
140
|
+
> {
|
|
141
|
+
kind: TelegramTopicLifecycleKind;
|
|
142
|
+
message: TMessage;
|
|
143
|
+
target: TelegramTarget & { threadId: number };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function getTelegramTopicLifecycleUpdate<
|
|
147
|
+
TMessage extends TelegramUpdateMessage,
|
|
148
|
+
>(
|
|
149
|
+
message: TMessage | undefined,
|
|
150
|
+
): TelegramTopicLifecycleUpdate<TMessage> | undefined {
|
|
151
|
+
if (
|
|
152
|
+
!message ||
|
|
153
|
+
typeof message.chat.id !== "number" ||
|
|
154
|
+
typeof message.message_thread_id !== "number"
|
|
155
|
+
) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
const target: TelegramTarget & { threadId: number } = {
|
|
159
|
+
...createTelegramThreadTarget(message.chat.id, message.message_thread_id),
|
|
160
|
+
threadId: message.message_thread_id,
|
|
161
|
+
};
|
|
162
|
+
if (message.forum_topic_created !== undefined) {
|
|
163
|
+
return { kind: "created", message, target };
|
|
164
|
+
}
|
|
165
|
+
if (message.forum_topic_closed !== undefined) {
|
|
166
|
+
return { kind: "closed", message, target };
|
|
167
|
+
}
|
|
168
|
+
if (message.forum_topic_reopened !== undefined) {
|
|
169
|
+
return { kind: "reopened", message, target };
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
125
172
|
}
|
|
126
173
|
|
|
127
174
|
export interface TelegramCallbackQuery {
|
|
@@ -139,6 +186,15 @@ export interface TelegramGuestMessage {
|
|
|
139
186
|
reply_to_message?: TelegramUpdateMessage;
|
|
140
187
|
}
|
|
141
188
|
|
|
189
|
+
export function getTelegramMessageTarget(
|
|
190
|
+
message: TelegramUpdateMessage,
|
|
191
|
+
): TelegramTarget | undefined {
|
|
192
|
+
if (typeof message.chat.id !== "number") return undefined;
|
|
193
|
+
return typeof message.message_thread_id === "number"
|
|
194
|
+
? createTelegramThreadTarget(message.chat.id, message.message_thread_id)
|
|
195
|
+
: createTelegramPrivateTarget(message.chat.id);
|
|
196
|
+
}
|
|
197
|
+
|
|
142
198
|
export interface TelegramUpdateRouting {
|
|
143
199
|
message?: TelegramUpdateMessage;
|
|
144
200
|
edited_message?: TelegramUpdateMessage;
|
|
@@ -148,44 +204,34 @@ export interface TelegramUpdateRouting {
|
|
|
148
204
|
|
|
149
205
|
export function getAuthorizedTelegramCallbackQuery(
|
|
150
206
|
update: TelegramUpdateRouting,
|
|
207
|
+
allowedUserId?: number,
|
|
151
208
|
): TelegramCallbackQuery | undefined {
|
|
152
209
|
const query = update.callback_query;
|
|
153
|
-
if (!query) return undefined;
|
|
210
|
+
if (!query || query.from.is_bot) return undefined;
|
|
154
211
|
const message = query.message;
|
|
155
|
-
if (!message
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return query;
|
|
212
|
+
if (!message) return undefined;
|
|
213
|
+
if (message.chat.type === "private") return query;
|
|
214
|
+
return query.from.id === allowedUserId ? query : undefined;
|
|
159
215
|
}
|
|
160
216
|
|
|
161
217
|
export function getAuthorizedTelegramMessage(
|
|
162
218
|
update: TelegramUpdateRouting,
|
|
219
|
+
allowedUserId?: number,
|
|
163
220
|
): TelegramUpdateMessage | undefined {
|
|
164
221
|
const message = update.message;
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
!message.from ||
|
|
169
|
-
message.from.is_bot
|
|
170
|
-
) {
|
|
171
|
-
return undefined;
|
|
172
|
-
}
|
|
173
|
-
return message;
|
|
222
|
+
if (!message || !message.from || message.from.is_bot) return undefined;
|
|
223
|
+
if (message.chat.type === "private") return message;
|
|
224
|
+
return message.from.id === allowedUserId ? message : undefined;
|
|
174
225
|
}
|
|
175
226
|
|
|
176
227
|
export function getAuthorizedTelegramEditedMessage(
|
|
177
228
|
update: TelegramUpdateRouting,
|
|
229
|
+
allowedUserId?: number,
|
|
178
230
|
): TelegramUpdateMessage | undefined {
|
|
179
231
|
const message = update.edited_message;
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
!message.from ||
|
|
184
|
-
message.from.is_bot
|
|
185
|
-
) {
|
|
186
|
-
return undefined;
|
|
187
|
-
}
|
|
188
|
-
return message;
|
|
232
|
+
if (!message || !message.from || message.from.is_bot) return undefined;
|
|
233
|
+
if (message.chat.type === "private") return message;
|
|
234
|
+
return message.from.id === allowedUserId ? message : undefined;
|
|
189
235
|
}
|
|
190
236
|
|
|
191
237
|
export function getAuthorizedTelegramGuestMessage(
|
|
@@ -200,8 +246,54 @@ export function getAuthorizedTelegramGuestMessage(
|
|
|
200
246
|
|
|
201
247
|
// --- Flow ---
|
|
202
248
|
|
|
249
|
+
export interface TelegramMessageOwnershipView {
|
|
250
|
+
instanceId: string;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export type TelegramMessageOwnershipLookup = (
|
|
254
|
+
chatId: number,
|
|
255
|
+
messageId: number,
|
|
256
|
+
) => TelegramMessageOwnershipView | undefined;
|
|
257
|
+
|
|
258
|
+
export interface TelegramTargetOwnershipView {
|
|
259
|
+
instanceId: string;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export type TelegramTargetOwnershipLookup = (
|
|
263
|
+
target: TelegramTarget,
|
|
264
|
+
) => TelegramTargetOwnershipView | undefined;
|
|
265
|
+
|
|
266
|
+
export interface TelegramForeignOwnedUpdateForwarder<
|
|
267
|
+
TContext,
|
|
268
|
+
TReactionUpdate extends TelegramMessageReactionUpdated =
|
|
269
|
+
TelegramMessageReactionUpdated,
|
|
270
|
+
TCallbackQuery extends TelegramCallbackQuery = TelegramCallbackQuery,
|
|
271
|
+
TMessage extends TelegramUpdateMessage = TelegramUpdateMessage,
|
|
272
|
+
> {
|
|
273
|
+
forwardCallback?: (input: {
|
|
274
|
+
query: TCallbackQuery;
|
|
275
|
+
ownership: TelegramMessageOwnershipView;
|
|
276
|
+
ctx: TContext;
|
|
277
|
+
}) => Promise<boolean> | boolean;
|
|
278
|
+
forwardReaction?: (input: {
|
|
279
|
+
reactionUpdate: TReactionUpdate;
|
|
280
|
+
ownership: TelegramMessageOwnershipView;
|
|
281
|
+
ctx: TContext;
|
|
282
|
+
}) => Promise<boolean> | boolean;
|
|
283
|
+
forwardMessage?: (input: {
|
|
284
|
+
message: TMessage;
|
|
285
|
+
ownership: TelegramTargetOwnershipView;
|
|
286
|
+
ctx: TContext;
|
|
287
|
+
}) => Promise<boolean> | boolean;
|
|
288
|
+
forwardEditedMessage?: (input: {
|
|
289
|
+
message: TMessage;
|
|
290
|
+
ownership: TelegramTargetOwnershipView;
|
|
291
|
+
ctx: TContext;
|
|
292
|
+
}) => Promise<boolean> | boolean;
|
|
293
|
+
}
|
|
294
|
+
|
|
203
295
|
export interface TelegramMessageReactionUpdated {
|
|
204
|
-
chat: { type: string };
|
|
296
|
+
chat: { id?: number; type: string };
|
|
205
297
|
user?: TelegramUser;
|
|
206
298
|
message_id: number;
|
|
207
299
|
old_reaction: TelegramReactionType[];
|
|
@@ -223,6 +315,10 @@ export type TelegramUpdateFlowAction<
|
|
|
223
315
|
| { kind: "ignore" }
|
|
224
316
|
| { kind: "deleted"; messageIds: number[] }
|
|
225
317
|
| { kind: "reaction"; reactionUpdate: TReactionUpdate }
|
|
318
|
+
| {
|
|
319
|
+
kind: "topic-lifecycle";
|
|
320
|
+
lifecycle: TelegramTopicLifecycleUpdate<TMessage>;
|
|
321
|
+
}
|
|
226
322
|
| {
|
|
227
323
|
kind: "callback";
|
|
228
324
|
query: TCallbackQuery;
|
|
@@ -262,7 +358,11 @@ export function buildTelegramUpdateFlowAction<
|
|
|
262
358
|
if (update.message_reaction) {
|
|
263
359
|
return { kind: "reaction", reactionUpdate: update.message_reaction };
|
|
264
360
|
}
|
|
265
|
-
const
|
|
361
|
+
const topicLifecycle = getTelegramTopicLifecycleUpdate(update.message);
|
|
362
|
+
if (topicLifecycle) {
|
|
363
|
+
return { kind: "topic-lifecycle", lifecycle: topicLifecycle };
|
|
364
|
+
}
|
|
365
|
+
const query = getAuthorizedTelegramCallbackQuery(update, allowedUserId);
|
|
266
366
|
if (query) {
|
|
267
367
|
return {
|
|
268
368
|
kind: "callback",
|
|
@@ -273,7 +373,7 @@ export function buildTelegramUpdateFlowAction<
|
|
|
273
373
|
),
|
|
274
374
|
};
|
|
275
375
|
}
|
|
276
|
-
const message = getAuthorizedTelegramMessage(update);
|
|
376
|
+
const message = getAuthorizedTelegramMessage(update, allowedUserId);
|
|
277
377
|
if (message?.from) {
|
|
278
378
|
return {
|
|
279
379
|
kind: "message",
|
|
@@ -286,7 +386,10 @@ export function buildTelegramUpdateFlowAction<
|
|
|
286
386
|
),
|
|
287
387
|
};
|
|
288
388
|
}
|
|
289
|
-
const editedMessage = getAuthorizedTelegramEditedMessage(
|
|
389
|
+
const editedMessage = getAuthorizedTelegramEditedMessage(
|
|
390
|
+
update,
|
|
391
|
+
allowedUserId,
|
|
392
|
+
);
|
|
290
393
|
if (editedMessage?.from) {
|
|
291
394
|
return {
|
|
292
395
|
kind: "edited-message",
|
|
@@ -330,6 +433,10 @@ export type TelegramUpdateExecutionPlan<
|
|
|
330
433
|
kind: "reaction";
|
|
331
434
|
reactionUpdate: TReactionUpdate;
|
|
332
435
|
}
|
|
436
|
+
| {
|
|
437
|
+
kind: "topic-lifecycle";
|
|
438
|
+
lifecycle: TelegramTopicLifecycleUpdate<TMessage>;
|
|
439
|
+
}
|
|
333
440
|
| {
|
|
334
441
|
kind: "callback";
|
|
335
442
|
query: TCallbackQuery;
|
|
@@ -380,6 +487,8 @@ export function buildTelegramUpdateExecutionPlan<
|
|
|
380
487
|
return { kind: "deleted", messageIds: action.messageIds };
|
|
381
488
|
case "reaction":
|
|
382
489
|
return { kind: "reaction", reactionUpdate: action.reactionUpdate };
|
|
490
|
+
case "topic-lifecycle":
|
|
491
|
+
return { kind: "topic-lifecycle", lifecycle: action.lifecycle };
|
|
383
492
|
case "callback":
|
|
384
493
|
return {
|
|
385
494
|
kind: "callback",
|
|
@@ -437,6 +546,15 @@ export interface TelegramUpdateRuntimeDeps<
|
|
|
437
546
|
TMessage extends TelegramUpdateMessage = TelegramUpdateMessage,
|
|
438
547
|
> {
|
|
439
548
|
ctx: TContext;
|
|
549
|
+
getCurrentInstanceId?: () => string | undefined;
|
|
550
|
+
getMessageOwnership?: TelegramMessageOwnershipLookup;
|
|
551
|
+
getTargetOwnership?: TelegramTargetOwnershipLookup;
|
|
552
|
+
foreignOwnedUpdateForwarder?: TelegramForeignOwnedUpdateForwarder<
|
|
553
|
+
TContext,
|
|
554
|
+
TReactionUpdate,
|
|
555
|
+
TCallbackQuery,
|
|
556
|
+
TMessage
|
|
557
|
+
>;
|
|
440
558
|
removePendingMediaGroupMessages: (messageIds: number[]) => void;
|
|
441
559
|
removeQueuedTelegramTurnsByMessageIds: (
|
|
442
560
|
messageIds: number[],
|
|
@@ -446,6 +564,10 @@ export interface TelegramUpdateRuntimeDeps<
|
|
|
446
564
|
reactionUpdate: TReactionUpdate,
|
|
447
565
|
ctx: TContext,
|
|
448
566
|
) => Promise<void>;
|
|
567
|
+
handleTelegramTopicLifecycleUpdate?: (
|
|
568
|
+
lifecycle: TelegramTopicLifecycleUpdate<TMessage>,
|
|
569
|
+
ctx: TContext,
|
|
570
|
+
) => Promise<void> | void;
|
|
449
571
|
pairTelegramUserIfNeeded: (userId: number, ctx: TContext) => Promise<boolean>;
|
|
450
572
|
answerCallbackQuery: (
|
|
451
573
|
callbackQueryId: string,
|
|
@@ -460,6 +582,7 @@ export interface TelegramUpdateRuntimeDeps<
|
|
|
460
582
|
chatId: number,
|
|
461
583
|
replyToMessageId: number,
|
|
462
584
|
text: string,
|
|
585
|
+
options?: { target?: { chatId: number; threadId?: number } },
|
|
463
586
|
) => Promise<number | undefined>;
|
|
464
587
|
handleAuthorizedTelegramMessage: (
|
|
465
588
|
message: TMessage,
|
|
@@ -473,6 +596,11 @@ export interface TelegramUpdateRuntimeDeps<
|
|
|
473
596
|
guestMessage: TelegramGuestMessage & { from: TelegramUser },
|
|
474
597
|
ctx: TContext,
|
|
475
598
|
) => Promise<void>;
|
|
599
|
+
/** Called when the owner writes in an unbound thread no live instance owns. */
|
|
600
|
+
handleUnboundTelegramTopicMessage?: (
|
|
601
|
+
message: TMessage & { from: TelegramUser },
|
|
602
|
+
ctx: TContext,
|
|
603
|
+
) => Promise<void>;
|
|
476
604
|
}
|
|
477
605
|
|
|
478
606
|
export interface TelegramUpdateRuntimeControllerDeps<
|
|
@@ -481,19 +609,31 @@ export interface TelegramUpdateRuntimeControllerDeps<
|
|
|
481
609
|
TMessage extends TelegramUpdateMessage = TelegramUpdateMessage,
|
|
482
610
|
> {
|
|
483
611
|
getAllowedUserId: () => number | undefined;
|
|
612
|
+
getCurrentInstanceId?: () => string | undefined;
|
|
613
|
+
getMessageOwnership?: TelegramMessageOwnershipLookup;
|
|
614
|
+
getTargetOwnership?: TelegramTargetOwnershipLookup;
|
|
615
|
+
foreignOwnedUpdateForwarder?: TelegramForeignOwnedUpdateForwarder<
|
|
616
|
+
TContext,
|
|
617
|
+
TelegramMessageReactionUpdated,
|
|
618
|
+
TCallbackQuery,
|
|
619
|
+
TMessage
|
|
620
|
+
>;
|
|
484
621
|
removePendingMediaGroupMessages: (messageIds: number[]) => void;
|
|
485
622
|
removeQueuedTelegramTurnsByMessageIds: (
|
|
486
623
|
messageIds: number[],
|
|
487
624
|
ctx: TContext,
|
|
625
|
+
scope?: { chatId?: number; threadId?: number },
|
|
488
626
|
) => number;
|
|
489
627
|
clearQueuedTelegramTurnPriorityByMessageId: (
|
|
490
628
|
messageId: number,
|
|
491
629
|
ctx: TContext,
|
|
630
|
+
scope?: { chatId?: number; threadId?: number },
|
|
492
631
|
) => boolean;
|
|
493
632
|
prioritizeQueuedTelegramTurnByMessageId: (
|
|
494
633
|
messageId: number,
|
|
495
634
|
ctx: TContext,
|
|
496
635
|
priorityEmoji?: string,
|
|
636
|
+
scope?: { chatId?: number; threadId?: number },
|
|
497
637
|
) => boolean;
|
|
498
638
|
pairTelegramUserIfNeeded: (userId: number, ctx: TContext) => Promise<boolean>;
|
|
499
639
|
answerCallbackQuery: (
|
|
@@ -509,6 +649,7 @@ export interface TelegramUpdateRuntimeControllerDeps<
|
|
|
509
649
|
chatId: number,
|
|
510
650
|
replyToMessageId: number,
|
|
511
651
|
text: string,
|
|
652
|
+
options?: { target?: { chatId: number; threadId?: number } },
|
|
512
653
|
) => Promise<number | undefined>;
|
|
513
654
|
handleAuthorizedTelegramMessage: (
|
|
514
655
|
message: TMessage,
|
|
@@ -522,6 +663,15 @@ export interface TelegramUpdateRuntimeControllerDeps<
|
|
|
522
663
|
guestMessage: TelegramGuestMessage & { from: TelegramUser },
|
|
523
664
|
ctx: TContext,
|
|
524
665
|
) => Promise<void>;
|
|
666
|
+
handleTelegramTopicLifecycleUpdate?: (
|
|
667
|
+
lifecycle: TelegramTopicLifecycleUpdate<TMessage>,
|
|
668
|
+
ctx: TContext,
|
|
669
|
+
) => Promise<void> | void;
|
|
670
|
+
/** Called when the owner writes in an unbound thread no live instance owns. */
|
|
671
|
+
handleUnboundTelegramTopicMessage?: (
|
|
672
|
+
message: TMessage & { from: TelegramUser },
|
|
673
|
+
ctx: TContext,
|
|
674
|
+
) => Promise<void>;
|
|
525
675
|
}
|
|
526
676
|
|
|
527
677
|
export interface TelegramUpdateRuntimeController<
|
|
@@ -543,7 +693,7 @@ function getTelegramCallbackQueryId(
|
|
|
543
693
|
|
|
544
694
|
function getTelegramMessageReplyTarget(
|
|
545
695
|
message: TelegramUpdateMessage,
|
|
546
|
-
): { chatId: number; messageId: number } | undefined {
|
|
696
|
+
): { chatId: number; messageId: number; threadId?: number } | undefined {
|
|
547
697
|
if (
|
|
548
698
|
typeof message.chat.id !== "number" ||
|
|
549
699
|
typeof message.message_id !== "number"
|
|
@@ -553,9 +703,84 @@ function getTelegramMessageReplyTarget(
|
|
|
553
703
|
return {
|
|
554
704
|
chatId: message.chat.id,
|
|
555
705
|
messageId: message.message_id,
|
|
706
|
+
...(typeof message.message_thread_id === "number"
|
|
707
|
+
? { threadId: message.message_thread_id }
|
|
708
|
+
: {}),
|
|
556
709
|
};
|
|
557
710
|
}
|
|
558
711
|
|
|
712
|
+
function getForeignTelegramMessageOwnership(
|
|
713
|
+
target: { chatId: number; messageId: number } | undefined,
|
|
714
|
+
deps: {
|
|
715
|
+
getCurrentInstanceId?: () => string | undefined;
|
|
716
|
+
getMessageOwnership?: TelegramMessageOwnershipLookup;
|
|
717
|
+
},
|
|
718
|
+
): TelegramMessageOwnershipView | undefined {
|
|
719
|
+
if (!target || !deps.getMessageOwnership || !deps.getCurrentInstanceId) {
|
|
720
|
+
return undefined;
|
|
721
|
+
}
|
|
722
|
+
const currentInstanceId = deps.getCurrentInstanceId();
|
|
723
|
+
if (!currentInstanceId) return undefined;
|
|
724
|
+
const ownership = deps.getMessageOwnership(target.chatId, target.messageId);
|
|
725
|
+
return ownership && ownership.instanceId !== currentInstanceId
|
|
726
|
+
? ownership
|
|
727
|
+
: undefined;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function getForeignTelegramCallbackOwnership(
|
|
731
|
+
query: TelegramCallbackQuery,
|
|
732
|
+
deps: {
|
|
733
|
+
getCurrentInstanceId?: () => string | undefined;
|
|
734
|
+
getMessageOwnership?: TelegramMessageOwnershipLookup;
|
|
735
|
+
getTargetOwnership?: TelegramTargetOwnershipLookup;
|
|
736
|
+
},
|
|
737
|
+
): TelegramMessageOwnershipView | undefined {
|
|
738
|
+
return (
|
|
739
|
+
getForeignTelegramMessageOwnership(
|
|
740
|
+
getTelegramCallbackMessageTarget(query),
|
|
741
|
+
deps,
|
|
742
|
+
) ??
|
|
743
|
+
getForeignTelegramTargetOwnership(
|
|
744
|
+
query.message ? getTelegramMessageTarget(query.message) : undefined,
|
|
745
|
+
deps,
|
|
746
|
+
)
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function getTelegramCallbackMessageTarget(
|
|
751
|
+
query: TelegramCallbackQuery,
|
|
752
|
+
): { chatId: number; messageId: number } | undefined {
|
|
753
|
+
return query.message
|
|
754
|
+
? getTelegramMessageReplyTarget(query.message)
|
|
755
|
+
: undefined;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function getTelegramReactionMessageTarget(
|
|
759
|
+
reactionUpdate: TelegramMessageReactionUpdated,
|
|
760
|
+
): { chatId: number; messageId: number } | undefined {
|
|
761
|
+
return typeof reactionUpdate.chat.id === "number"
|
|
762
|
+
? { chatId: reactionUpdate.chat.id, messageId: reactionUpdate.message_id }
|
|
763
|
+
: undefined;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function getForeignTelegramTargetOwnership(
|
|
767
|
+
target: TelegramTarget | undefined,
|
|
768
|
+
deps: {
|
|
769
|
+
getCurrentInstanceId?: () => string | undefined;
|
|
770
|
+
getTargetOwnership?: TelegramTargetOwnershipLookup;
|
|
771
|
+
},
|
|
772
|
+
): TelegramTargetOwnershipView | undefined {
|
|
773
|
+
if (!target || !deps.getTargetOwnership || !deps.getCurrentInstanceId) {
|
|
774
|
+
return undefined;
|
|
775
|
+
}
|
|
776
|
+
const currentInstanceId = deps.getCurrentInstanceId();
|
|
777
|
+
if (!currentInstanceId) return undefined;
|
|
778
|
+
const ownership = deps.getTargetOwnership(target);
|
|
779
|
+
return ownership && ownership.instanceId !== currentInstanceId
|
|
780
|
+
? ownership
|
|
781
|
+
: undefined;
|
|
782
|
+
}
|
|
783
|
+
|
|
559
784
|
export async function executeTelegramUpdate<
|
|
560
785
|
TUpdate extends TelegramUpdateFlow,
|
|
561
786
|
TContext = unknown,
|
|
@@ -596,6 +821,11 @@ export function createTelegramPairedUpdateRuntime<
|
|
|
596
821
|
): TelegramUpdateRuntimeController<TContext, TUpdate> {
|
|
597
822
|
return createTelegramUpdateRuntime({
|
|
598
823
|
getAllowedUserId: deps.getAllowedUserId,
|
|
824
|
+
getCurrentInstanceId: deps.getCurrentInstanceId,
|
|
825
|
+
getMessageOwnership: deps.getMessageOwnership,
|
|
826
|
+
getTargetOwnership: deps.getTargetOwnership,
|
|
827
|
+
handleTelegramTopicLifecycleUpdate: deps.handleTelegramTopicLifecycleUpdate,
|
|
828
|
+
foreignOwnedUpdateForwarder: deps.foreignOwnedUpdateForwarder,
|
|
599
829
|
removePendingMediaGroupMessages: deps.removePendingMediaGroupMessages,
|
|
600
830
|
removeQueuedTelegramTurnsByMessageIds:
|
|
601
831
|
deps.removeQueuedTelegramTurnsByMessageIds,
|
|
@@ -619,6 +849,7 @@ export function createTelegramPairedUpdateRuntime<
|
|
|
619
849
|
deps.handleAuthorizedTelegramEditedMessage,
|
|
620
850
|
handleAuthorizedTelegramGuestMessage:
|
|
621
851
|
deps.handleAuthorizedTelegramGuestMessage,
|
|
852
|
+
handleUnboundTelegramTopicMessage: deps.handleUnboundTelegramTopicMessage,
|
|
622
853
|
});
|
|
623
854
|
}
|
|
624
855
|
|
|
@@ -642,6 +873,9 @@ export function createTelegramUpdateRuntime<
|
|
|
642
873
|
removePendingMediaGroupMessages: deps.removePendingMediaGroupMessages,
|
|
643
874
|
removeQueuedTelegramTurnsByMessageIds:
|
|
644
875
|
deps.removeQueuedTelegramTurnsByMessageIds,
|
|
876
|
+
getCurrentInstanceId: deps.getCurrentInstanceId,
|
|
877
|
+
getMessageOwnership: deps.getMessageOwnership,
|
|
878
|
+
foreignOwnedUpdateForwarder: deps.foreignOwnedUpdateForwarder,
|
|
645
879
|
clearQueuedTelegramTurnPriorityByMessageId:
|
|
646
880
|
deps.clearQueuedTelegramTurnPriorityByMessageId,
|
|
647
881
|
prioritizeQueuedTelegramTurnByMessageId:
|
|
@@ -653,10 +887,16 @@ export function createTelegramUpdateRuntime<
|
|
|
653
887
|
handleUpdate: (update, ctx) =>
|
|
654
888
|
executeTelegramUpdate(update, deps.getAllowedUserId(), {
|
|
655
889
|
ctx,
|
|
890
|
+
getCurrentInstanceId: deps.getCurrentInstanceId,
|
|
891
|
+
getMessageOwnership: deps.getMessageOwnership,
|
|
892
|
+
getTargetOwnership: deps.getTargetOwnership,
|
|
893
|
+
foreignOwnedUpdateForwarder: deps.foreignOwnedUpdateForwarder,
|
|
656
894
|
removePendingMediaGroupMessages: deps.removePendingMediaGroupMessages,
|
|
657
895
|
removeQueuedTelegramTurnsByMessageIds:
|
|
658
896
|
deps.removeQueuedTelegramTurnsByMessageIds,
|
|
659
897
|
handleAuthorizedTelegramReactionUpdate: handleAuthorizedReactionUpdate,
|
|
898
|
+
handleTelegramTopicLifecycleUpdate:
|
|
899
|
+
deps.handleTelegramTopicLifecycleUpdate,
|
|
660
900
|
pairTelegramUserIfNeeded: deps.pairTelegramUserIfNeeded,
|
|
661
901
|
answerCallbackQuery: deps.answerCallbackQuery,
|
|
662
902
|
answerGuestQuery: deps.answerGuestQuery,
|
|
@@ -668,6 +908,8 @@ export function createTelegramUpdateRuntime<
|
|
|
668
908
|
deps.handleAuthorizedTelegramEditedMessage,
|
|
669
909
|
handleAuthorizedTelegramGuestMessage:
|
|
670
910
|
deps.handleAuthorizedTelegramGuestMessage,
|
|
911
|
+
handleUnboundTelegramTopicMessage:
|
|
912
|
+
deps.handleUnboundTelegramTopicMessage,
|
|
671
913
|
}),
|
|
672
914
|
};
|
|
673
915
|
}
|
|
@@ -675,19 +917,25 @@ export function createTelegramUpdateRuntime<
|
|
|
675
917
|
export interface AuthorizedTelegramReactionUpdateDeps<TContext> {
|
|
676
918
|
allowedUserId?: number;
|
|
677
919
|
ctx: TContext;
|
|
920
|
+
getCurrentInstanceId?: () => string | undefined;
|
|
921
|
+
getMessageOwnership?: TelegramMessageOwnershipLookup;
|
|
922
|
+
foreignOwnedUpdateForwarder?: TelegramForeignOwnedUpdateForwarder<TContext>;
|
|
678
923
|
removePendingMediaGroupMessages: (messageIds: number[]) => void;
|
|
679
924
|
removeQueuedTelegramTurnsByMessageIds: (
|
|
680
925
|
messageIds: number[],
|
|
681
926
|
ctx: TContext,
|
|
927
|
+
scope?: { chatId?: number; threadId?: number },
|
|
682
928
|
) => number;
|
|
683
929
|
clearQueuedTelegramTurnPriorityByMessageId: (
|
|
684
930
|
messageId: number,
|
|
685
931
|
ctx: TContext,
|
|
932
|
+
scope?: { chatId?: number; threadId?: number },
|
|
686
933
|
) => boolean;
|
|
687
934
|
prioritizeQueuedTelegramTurnByMessageId: (
|
|
688
935
|
messageId: number,
|
|
689
936
|
ctx: TContext,
|
|
690
937
|
priorityEmoji?: string,
|
|
938
|
+
scope?: { chatId?: number; threadId?: number },
|
|
691
939
|
) => boolean;
|
|
692
940
|
}
|
|
693
941
|
|
|
@@ -695,15 +943,30 @@ export async function handleAuthorizedTelegramReactionUpdate<TContext>(
|
|
|
695
943
|
reactionUpdate: TelegramMessageReactionUpdated,
|
|
696
944
|
deps: AuthorizedTelegramReactionUpdateDeps<TContext>,
|
|
697
945
|
): Promise<void> {
|
|
946
|
+
const foreignOwnership = getForeignTelegramMessageOwnership(
|
|
947
|
+
getTelegramReactionMessageTarget(reactionUpdate),
|
|
948
|
+
deps,
|
|
949
|
+
);
|
|
950
|
+
if (foreignOwnership) {
|
|
951
|
+
await deps.foreignOwnedUpdateForwarder?.forwardReaction?.({
|
|
952
|
+
reactionUpdate,
|
|
953
|
+
ownership: foreignOwnership,
|
|
954
|
+
ctx: deps.ctx,
|
|
955
|
+
});
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
698
958
|
const reactionUser = reactionUpdate.user;
|
|
959
|
+
if (!reactionUser || reactionUser.is_bot) return;
|
|
699
960
|
if (
|
|
700
|
-
reactionUpdate.chat.type !== "private"
|
|
701
|
-
!reactionUser ||
|
|
702
|
-
reactionUser.is_bot ||
|
|
961
|
+
reactionUpdate.chat.type !== "private" &&
|
|
703
962
|
reactionUser.id !== deps.allowedUserId
|
|
704
963
|
) {
|
|
705
964
|
return;
|
|
706
965
|
}
|
|
966
|
+
const reactionScope =
|
|
967
|
+
typeof reactionUpdate.chat.id === "number"
|
|
968
|
+
? { chatId: reactionUpdate.chat.id }
|
|
969
|
+
: undefined;
|
|
707
970
|
const oldEmojis = collectTelegramReactionEmojis(reactionUpdate.old_reaction);
|
|
708
971
|
const newEmojis = collectTelegramReactionEmojis(reactionUpdate.new_reaction);
|
|
709
972
|
if (
|
|
@@ -717,6 +980,7 @@ export async function handleAuthorizedTelegramReactionUpdate<TContext>(
|
|
|
717
980
|
deps.removeQueuedTelegramTurnsByMessageIds(
|
|
718
981
|
[reactionUpdate.message_id],
|
|
719
982
|
deps.ctx,
|
|
983
|
+
reactionScope,
|
|
720
984
|
);
|
|
721
985
|
return;
|
|
722
986
|
}
|
|
@@ -732,6 +996,7 @@ export async function handleAuthorizedTelegramReactionUpdate<TContext>(
|
|
|
732
996
|
deps.clearQueuedTelegramTurnPriorityByMessageId(
|
|
733
997
|
reactionUpdate.message_id,
|
|
734
998
|
deps.ctx,
|
|
999
|
+
reactionScope,
|
|
735
1000
|
);
|
|
736
1001
|
}
|
|
737
1002
|
const addedPriorityEmoji = getAddedTelegramReactionEmoji(
|
|
@@ -744,6 +1009,7 @@ export async function handleAuthorizedTelegramReactionUpdate<TContext>(
|
|
|
744
1009
|
reactionUpdate.message_id,
|
|
745
1010
|
deps.ctx,
|
|
746
1011
|
addedPriorityEmoji,
|
|
1012
|
+
reactionScope,
|
|
747
1013
|
);
|
|
748
1014
|
}
|
|
749
1015
|
|
|
@@ -784,7 +1050,33 @@ export async function executeTelegramUpdatePlan<
|
|
|
784
1050
|
);
|
|
785
1051
|
return;
|
|
786
1052
|
}
|
|
1053
|
+
if (plan.kind === "topic-lifecycle") {
|
|
1054
|
+
await deps.handleTelegramTopicLifecycleUpdate?.(plan.lifecycle, deps.ctx);
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
787
1057
|
if (plan.kind === "callback") {
|
|
1058
|
+
const foreignOwnership = getForeignTelegramCallbackOwnership(
|
|
1059
|
+
plan.query,
|
|
1060
|
+
deps,
|
|
1061
|
+
);
|
|
1062
|
+
if (foreignOwnership) {
|
|
1063
|
+
const forwarded =
|
|
1064
|
+
(await deps.foreignOwnedUpdateForwarder?.forwardCallback?.({
|
|
1065
|
+
query: plan.query,
|
|
1066
|
+
ownership: foreignOwnership,
|
|
1067
|
+
ctx: deps.ctx,
|
|
1068
|
+
})) ?? false;
|
|
1069
|
+
if (!forwarded) {
|
|
1070
|
+
const callbackQueryId = getTelegramCallbackQueryId(plan.query);
|
|
1071
|
+
if (callbackQueryId) {
|
|
1072
|
+
await deps.answerCallbackQuery(
|
|
1073
|
+
callbackQueryId,
|
|
1074
|
+
"This Telegram message belongs to another Pi instance.",
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
788
1080
|
if (plan.shouldPair) {
|
|
789
1081
|
await deps.pairTelegramUserIfNeeded(plan.query.from.id, deps.ctx);
|
|
790
1082
|
}
|
|
@@ -817,6 +1109,35 @@ export async function executeTelegramUpdatePlan<
|
|
|
817
1109
|
}
|
|
818
1110
|
return;
|
|
819
1111
|
}
|
|
1112
|
+
const foreignTargetOwnership = getForeignTelegramTargetOwnership(
|
|
1113
|
+
getTelegramMessageTarget(plan.message),
|
|
1114
|
+
deps,
|
|
1115
|
+
);
|
|
1116
|
+
if (foreignTargetOwnership) {
|
|
1117
|
+
if (plan.kind === "edited-message") {
|
|
1118
|
+
await deps.foreignOwnedUpdateForwarder?.forwardEditedMessage?.({
|
|
1119
|
+
message: plan.message,
|
|
1120
|
+
ownership: foreignTargetOwnership,
|
|
1121
|
+
ctx: deps.ctx,
|
|
1122
|
+
});
|
|
1123
|
+
} else {
|
|
1124
|
+
await deps.foreignOwnedUpdateForwarder?.forwardMessage?.({
|
|
1125
|
+
message: plan.message,
|
|
1126
|
+
ownership: foreignTargetOwnership,
|
|
1127
|
+
ctx: deps.ctx,
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
const messageTarget = getTelegramMessageTarget(plan.message);
|
|
1133
|
+
if (
|
|
1134
|
+
plan.kind === "message" &&
|
|
1135
|
+
messageTarget?.threadId != null &&
|
|
1136
|
+
deps.handleUnboundTelegramTopicMessage
|
|
1137
|
+
) {
|
|
1138
|
+
await deps.handleUnboundTelegramTopicMessage(plan.message, deps.ctx);
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
820
1141
|
const pairedNow = plan.shouldPair
|
|
821
1142
|
? await deps.pairTelegramUserIfNeeded(plan.message.from.id, deps.ctx)
|
|
822
1143
|
: false;
|
|
@@ -831,6 +1152,7 @@ export async function executeTelegramUpdatePlan<
|
|
|
831
1152
|
replyTarget.chatId,
|
|
832
1153
|
replyTarget.messageId,
|
|
833
1154
|
"Telegram bridge paired with this account.",
|
|
1155
|
+
{ target: replyTarget },
|
|
834
1156
|
);
|
|
835
1157
|
}
|
|
836
1158
|
if (plan.shouldDeny) {
|
|
@@ -839,6 +1161,7 @@ export async function executeTelegramUpdatePlan<
|
|
|
839
1161
|
replyTarget.chatId,
|
|
840
1162
|
replyTarget.messageId,
|
|
841
1163
|
"This bot is not authorized for your account.",
|
|
1164
|
+
{ target: replyTarget },
|
|
842
1165
|
);
|
|
843
1166
|
}
|
|
844
1167
|
return;
|
|
@@ -953,7 +1276,7 @@ export function createTelegramUpdateHandle<TUpdate, TContext>(
|
|
|
953
1276
|
): (update: TUpdate, ctx: TContext) => Promise<void> {
|
|
954
1277
|
const registry = deps.registry ?? getOrCreateUpdateHandlerRegistry();
|
|
955
1278
|
const { defaultHandle } = deps;
|
|
956
|
-
return async
|
|
1279
|
+
return async (update, ctx) => {
|
|
957
1280
|
const verdict = await registry.dispatch(update);
|
|
958
1281
|
if (verdict === "consume") return;
|
|
959
1282
|
await defaultHandle(update, ctx);
|