@llblab/pi-telegram 0.2.10 → 0.3.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/README.md +30 -19
- package/docs/architecture.md +51 -28
- package/index.ts +388 -1881
- package/lib/api.ts +396 -60
- package/lib/attachments.ts +128 -16
- package/lib/commands.ts +648 -14
- package/lib/config.ts +157 -0
- package/lib/media.ts +147 -41
- package/lib/menu.ts +920 -338
- package/lib/model.ts +647 -0
- package/lib/pi.ts +80 -0
- package/lib/polling.ts +240 -14
- package/lib/preview.ts +420 -25
- package/lib/queue.ts +1134 -110
- package/lib/registration.ts +127 -28
- package/lib/rendering.ts +560 -366
- package/lib/replies.ts +198 -8
- package/lib/runtime.ts +475 -0
- package/lib/setup.ts +129 -1
- package/lib/status.ts +428 -13
- package/lib/turns.ts +127 -23
- package/lib/updates.ts +340 -109
- package/package.json +18 -3
- package/AGENTS.md +0 -91
- package/BACKLOG.md +0 -5
- package/CHANGELOG.md +0 -34
- package/lib/model-switch.ts +0 -62
- package/lib/types.ts +0 -137
- package/tests/api.test.ts +0 -331
- package/tests/attachments.test.ts +0 -132
- package/tests/commands.test.ts +0 -85
- package/tests/config.test.ts +0 -80
- package/tests/media.test.ts +0 -166
- package/tests/menu.test.ts +0 -676
- package/tests/polling.test.ts +0 -202
- package/tests/preview.test.ts +0 -480
- package/tests/queue.test.ts +0 -3245
- package/tests/registration.test.ts +0 -268
- package/tests/rendering.test.ts +0 -526
- package/tests/replies.test.ts +0 -142
- package/tests/turns.test.ts +0 -247
- package/tests/updates.test.ts +0 -416
package/lib/updates.ts
CHANGED
|
@@ -3,24 +3,29 @@
|
|
|
3
3
|
* Owns update extraction, authorization, classification, execution planning, and runtime execution for Telegram updates
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import {
|
|
7
|
+
createTelegramUserPairingRuntime,
|
|
8
|
+
getTelegramAuthorizationState,
|
|
9
|
+
type TelegramAuthorizationState,
|
|
10
|
+
type TelegramUserPairingRuntimeDeps,
|
|
11
|
+
} from "./config.ts";
|
|
7
12
|
|
|
8
13
|
// --- Extraction ---
|
|
9
14
|
|
|
10
|
-
export interface
|
|
15
|
+
export interface TelegramReactionTypeEmoji {
|
|
11
16
|
type: "emoji";
|
|
12
17
|
emoji: string;
|
|
13
18
|
}
|
|
14
19
|
|
|
15
|
-
export interface
|
|
20
|
+
export interface TelegramReactionTypeNonEmoji {
|
|
16
21
|
type: string;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
export type
|
|
20
|
-
|
|
|
21
|
-
|
|
|
24
|
+
export type TelegramReactionType =
|
|
25
|
+
| TelegramReactionTypeEmoji
|
|
26
|
+
| TelegramReactionTypeNonEmoji;
|
|
22
27
|
|
|
23
|
-
export interface
|
|
28
|
+
export interface TelegramUpdateDeletion {
|
|
24
29
|
deleted_business_messages?: { message_ids?: unknown };
|
|
25
30
|
}
|
|
26
31
|
|
|
@@ -33,12 +38,12 @@ export function normalizeTelegramReactionEmoji(emoji: string): string {
|
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
export function collectTelegramReactionEmojis(
|
|
36
|
-
reactions:
|
|
41
|
+
reactions: TelegramReactionType[],
|
|
37
42
|
): Set<string> {
|
|
38
43
|
return new Set(
|
|
39
44
|
reactions
|
|
40
45
|
.filter(
|
|
41
|
-
(reaction): reaction is
|
|
46
|
+
(reaction): reaction is TelegramReactionTypeEmoji =>
|
|
42
47
|
reaction.type === "emoji",
|
|
43
48
|
)
|
|
44
49
|
.map((reaction) => normalizeTelegramReactionEmoji(reaction.emoji)),
|
|
@@ -46,7 +51,7 @@ export function collectTelegramReactionEmojis(
|
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
export function extractDeletedTelegramMessageIds(
|
|
49
|
-
update:
|
|
54
|
+
update: TelegramUpdateDeletion,
|
|
50
55
|
): number[] {
|
|
51
56
|
const deletedBusinessMessageIds =
|
|
52
57
|
update.deleted_business_messages?.message_ids;
|
|
@@ -58,55 +63,37 @@ export function extractDeletedTelegramMessageIds(
|
|
|
58
63
|
|
|
59
64
|
// --- Routing ---
|
|
60
65
|
|
|
61
|
-
export interface
|
|
66
|
+
export interface TelegramUser {
|
|
62
67
|
id: number;
|
|
63
68
|
is_bot: boolean;
|
|
64
69
|
}
|
|
65
70
|
|
|
66
|
-
export interface
|
|
71
|
+
export interface TelegramChat {
|
|
67
72
|
id?: number;
|
|
68
73
|
type: string;
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
export interface
|
|
72
|
-
chat:
|
|
73
|
-
from?:
|
|
76
|
+
export interface TelegramUpdateMessage {
|
|
77
|
+
chat: TelegramChat;
|
|
78
|
+
from?: TelegramUser;
|
|
74
79
|
message_id?: number;
|
|
75
80
|
}
|
|
76
81
|
|
|
77
|
-
export interface
|
|
82
|
+
export interface TelegramCallbackQuery {
|
|
78
83
|
id?: string;
|
|
79
|
-
from:
|
|
80
|
-
message?:
|
|
84
|
+
from: TelegramUser;
|
|
85
|
+
message?: TelegramUpdateMessage;
|
|
81
86
|
}
|
|
82
87
|
|
|
83
|
-
export interface
|
|
84
|
-
message?:
|
|
85
|
-
edited_message?:
|
|
86
|
-
callback_query?:
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export type TelegramAuthorizationState =
|
|
90
|
-
| { kind: "pair"; userId: number }
|
|
91
|
-
| { kind: "allow" }
|
|
92
|
-
| { kind: "deny" };
|
|
93
|
-
|
|
94
|
-
export function getTelegramAuthorizationState(
|
|
95
|
-
userId: number,
|
|
96
|
-
allowedUserId?: number,
|
|
97
|
-
): TelegramAuthorizationState {
|
|
98
|
-
if (allowedUserId === undefined) {
|
|
99
|
-
return { kind: "pair", userId };
|
|
100
|
-
}
|
|
101
|
-
if (userId === allowedUserId) {
|
|
102
|
-
return { kind: "allow" };
|
|
103
|
-
}
|
|
104
|
-
return { kind: "deny" };
|
|
88
|
+
export interface TelegramUpdateRouting {
|
|
89
|
+
message?: TelegramUpdateMessage;
|
|
90
|
+
edited_message?: TelegramUpdateMessage;
|
|
91
|
+
callback_query?: TelegramCallbackQuery;
|
|
105
92
|
}
|
|
106
93
|
|
|
107
94
|
export function getAuthorizedTelegramCallbackQuery(
|
|
108
|
-
update:
|
|
109
|
-
):
|
|
95
|
+
update: TelegramUpdateRouting,
|
|
96
|
+
): TelegramCallbackQuery | undefined {
|
|
110
97
|
const query = update.callback_query;
|
|
111
98
|
if (!query) return undefined;
|
|
112
99
|
const message = query.message;
|
|
@@ -117,8 +104,8 @@ export function getAuthorizedTelegramCallbackQuery(
|
|
|
117
104
|
}
|
|
118
105
|
|
|
119
106
|
export function getAuthorizedTelegramMessage(
|
|
120
|
-
update:
|
|
121
|
-
):
|
|
107
|
+
update: TelegramUpdateRouting,
|
|
108
|
+
): TelegramUpdateMessage | undefined {
|
|
122
109
|
const message = update.message;
|
|
123
110
|
if (
|
|
124
111
|
!message ||
|
|
@@ -132,8 +119,8 @@ export function getAuthorizedTelegramMessage(
|
|
|
132
119
|
}
|
|
133
120
|
|
|
134
121
|
export function getAuthorizedTelegramEditedMessage(
|
|
135
|
-
update:
|
|
136
|
-
):
|
|
122
|
+
update: TelegramUpdateRouting,
|
|
123
|
+
): TelegramUpdateMessage | undefined {
|
|
137
124
|
const message = update.edited_message;
|
|
138
125
|
if (
|
|
139
126
|
!message ||
|
|
@@ -148,40 +135,54 @@ export function getAuthorizedTelegramEditedMessage(
|
|
|
148
135
|
|
|
149
136
|
// --- Flow ---
|
|
150
137
|
|
|
151
|
-
export interface
|
|
138
|
+
export interface TelegramMessageReactionUpdated {
|
|
152
139
|
chat: { type: string };
|
|
153
|
-
user?:
|
|
140
|
+
user?: TelegramUser;
|
|
141
|
+
message_id: number;
|
|
142
|
+
old_reaction: TelegramReactionType[];
|
|
143
|
+
new_reaction: TelegramReactionType[];
|
|
154
144
|
}
|
|
155
145
|
|
|
156
|
-
export interface
|
|
157
|
-
extends
|
|
158
|
-
message_reaction?:
|
|
146
|
+
export interface TelegramUpdateFlow
|
|
147
|
+
extends TelegramUpdateRouting, TelegramUpdateDeletion {
|
|
148
|
+
message_reaction?: TelegramMessageReactionUpdated;
|
|
159
149
|
}
|
|
160
150
|
|
|
161
|
-
export type TelegramUpdateFlowAction
|
|
151
|
+
export type TelegramUpdateFlowAction<
|
|
152
|
+
TReactionUpdate extends TelegramMessageReactionUpdated =
|
|
153
|
+
TelegramMessageReactionUpdated,
|
|
154
|
+
TCallbackQuery extends TelegramCallbackQuery = TelegramCallbackQuery,
|
|
155
|
+
TMessage extends TelegramUpdateMessage = TelegramUpdateMessage,
|
|
156
|
+
> =
|
|
162
157
|
| { kind: "ignore" }
|
|
163
158
|
| { kind: "deleted"; messageIds: number[] }
|
|
164
|
-
| { kind: "reaction"; reactionUpdate:
|
|
159
|
+
| { kind: "reaction"; reactionUpdate: TReactionUpdate }
|
|
165
160
|
| {
|
|
166
161
|
kind: "callback";
|
|
167
|
-
query:
|
|
162
|
+
query: TCallbackQuery;
|
|
168
163
|
authorization: TelegramAuthorizationState;
|
|
169
164
|
}
|
|
170
165
|
| {
|
|
171
166
|
kind: "message";
|
|
172
|
-
message:
|
|
167
|
+
message: TMessage & { from: TelegramUser };
|
|
173
168
|
authorization: TelegramAuthorizationState;
|
|
174
169
|
}
|
|
175
170
|
| {
|
|
176
171
|
kind: "edited-message";
|
|
177
|
-
message:
|
|
172
|
+
message: TMessage & { from: TelegramUser };
|
|
178
173
|
authorization: TelegramAuthorizationState;
|
|
179
174
|
};
|
|
180
175
|
|
|
181
|
-
export function buildTelegramUpdateFlowAction
|
|
182
|
-
|
|
176
|
+
export function buildTelegramUpdateFlowAction<
|
|
177
|
+
TUpdate extends TelegramUpdateFlow,
|
|
178
|
+
>(
|
|
179
|
+
update: TUpdate,
|
|
183
180
|
allowedUserId?: number,
|
|
184
|
-
): TelegramUpdateFlowAction
|
|
181
|
+
): TelegramUpdateFlowAction<
|
|
182
|
+
NonNullable<TUpdate["message_reaction"]>,
|
|
183
|
+
NonNullable<TUpdate["callback_query"]>,
|
|
184
|
+
NonNullable<TUpdate["message"] | TUpdate["edited_message"]>
|
|
185
|
+
> {
|
|
185
186
|
const deletedMessageIds = extractDeletedTelegramMessageIds(update);
|
|
186
187
|
if (deletedMessageIds.length > 0) {
|
|
187
188
|
return { kind: "deleted", messageIds: deletedMessageIds };
|
|
@@ -193,7 +194,7 @@ export function buildTelegramUpdateFlowAction(
|
|
|
193
194
|
if (query) {
|
|
194
195
|
return {
|
|
195
196
|
kind: "callback",
|
|
196
|
-
query
|
|
197
|
+
query: query as NonNullable<TUpdate["callback_query"]>,
|
|
197
198
|
authorization: getTelegramAuthorizationState(
|
|
198
199
|
query.from.id,
|
|
199
200
|
allowedUserId,
|
|
@@ -204,7 +205,9 @@ export function buildTelegramUpdateFlowAction(
|
|
|
204
205
|
if (message?.from) {
|
|
205
206
|
return {
|
|
206
207
|
kind: "message",
|
|
207
|
-
message: message as
|
|
208
|
+
message: message as NonNullable<
|
|
209
|
+
TUpdate["message"] | TUpdate["edited_message"]
|
|
210
|
+
> & { from: TelegramUser },
|
|
208
211
|
authorization: getTelegramAuthorizationState(
|
|
209
212
|
message.from.id,
|
|
210
213
|
allowedUserId,
|
|
@@ -215,9 +218,9 @@ export function buildTelegramUpdateFlowAction(
|
|
|
215
218
|
if (editedMessage?.from) {
|
|
216
219
|
return {
|
|
217
220
|
kind: "edited-message",
|
|
218
|
-
message: editedMessage as
|
|
219
|
-
|
|
220
|
-
},
|
|
221
|
+
message: editedMessage as NonNullable<
|
|
222
|
+
TUpdate["message"] | TUpdate["edited_message"]
|
|
223
|
+
> & { from: TelegramUser },
|
|
221
224
|
authorization: getTelegramAuthorizationState(
|
|
222
225
|
editedMessage.from.id,
|
|
223
226
|
allowedUserId,
|
|
@@ -229,36 +232,45 @@ export function buildTelegramUpdateFlowAction(
|
|
|
229
232
|
|
|
230
233
|
// --- Execution Planning ---
|
|
231
234
|
|
|
232
|
-
export type TelegramUpdateExecutionPlan
|
|
235
|
+
export type TelegramUpdateExecutionPlan<
|
|
236
|
+
TReactionUpdate extends TelegramMessageReactionUpdated =
|
|
237
|
+
TelegramMessageReactionUpdated,
|
|
238
|
+
TCallbackQuery extends TelegramCallbackQuery = TelegramCallbackQuery,
|
|
239
|
+
TMessage extends TelegramUpdateMessage = TelegramUpdateMessage,
|
|
240
|
+
> =
|
|
233
241
|
| { kind: "ignore" }
|
|
234
242
|
| { kind: "deleted"; messageIds: number[] }
|
|
235
243
|
| {
|
|
236
244
|
kind: "reaction";
|
|
237
|
-
reactionUpdate:
|
|
245
|
+
reactionUpdate: TReactionUpdate;
|
|
238
246
|
}
|
|
239
247
|
| {
|
|
240
248
|
kind: "callback";
|
|
241
|
-
query:
|
|
249
|
+
query: TCallbackQuery;
|
|
242
250
|
shouldPair: boolean;
|
|
243
251
|
shouldDeny: boolean;
|
|
244
252
|
}
|
|
245
253
|
| {
|
|
246
254
|
kind: "message";
|
|
247
|
-
message:
|
|
255
|
+
message: TMessage & { from: TelegramUser };
|
|
248
256
|
shouldPair: boolean;
|
|
249
257
|
shouldNotifyPaired: boolean;
|
|
250
258
|
shouldDeny: boolean;
|
|
251
259
|
}
|
|
252
260
|
| {
|
|
253
261
|
kind: "edited-message";
|
|
254
|
-
message:
|
|
262
|
+
message: TMessage & { from: TelegramUser };
|
|
255
263
|
shouldPair: boolean;
|
|
256
264
|
shouldDeny: boolean;
|
|
257
265
|
};
|
|
258
266
|
|
|
259
|
-
export function buildTelegramUpdateExecutionPlan
|
|
260
|
-
|
|
261
|
-
|
|
267
|
+
export function buildTelegramUpdateExecutionPlan<
|
|
268
|
+
TReactionUpdate extends TelegramMessageReactionUpdated,
|
|
269
|
+
TCallbackQuery extends TelegramCallbackQuery,
|
|
270
|
+
TMessage extends TelegramUpdateMessage,
|
|
271
|
+
>(
|
|
272
|
+
action: TelegramUpdateFlowAction<TReactionUpdate, TCallbackQuery, TMessage>,
|
|
273
|
+
): TelegramUpdateExecutionPlan<TReactionUpdate, TCallbackQuery, TMessage> {
|
|
262
274
|
switch (action.kind) {
|
|
263
275
|
case "ignore":
|
|
264
276
|
return { kind: "ignore" };
|
|
@@ -291,10 +303,16 @@ export function buildTelegramUpdateExecutionPlan(
|
|
|
291
303
|
}
|
|
292
304
|
}
|
|
293
305
|
|
|
294
|
-
export function buildTelegramUpdateExecutionPlanFromUpdate
|
|
295
|
-
|
|
306
|
+
export function buildTelegramUpdateExecutionPlanFromUpdate<
|
|
307
|
+
TUpdate extends TelegramUpdateFlow,
|
|
308
|
+
>(
|
|
309
|
+
update: TUpdate,
|
|
296
310
|
allowedUserId?: number,
|
|
297
|
-
): TelegramUpdateExecutionPlan
|
|
311
|
+
): TelegramUpdateExecutionPlan<
|
|
312
|
+
NonNullable<TUpdate["message_reaction"]>,
|
|
313
|
+
NonNullable<TUpdate["callback_query"]>,
|
|
314
|
+
NonNullable<TUpdate["message"] | TUpdate["edited_message"]>
|
|
315
|
+
> {
|
|
298
316
|
return buildTelegramUpdateExecutionPlan(
|
|
299
317
|
buildTelegramUpdateFlowAction(update, allowedUserId),
|
|
300
318
|
);
|
|
@@ -302,33 +320,74 @@ export function buildTelegramUpdateExecutionPlanFromUpdate(
|
|
|
302
320
|
|
|
303
321
|
// --- Runtime ---
|
|
304
322
|
|
|
305
|
-
export interface TelegramUpdateRuntimeDeps
|
|
306
|
-
|
|
323
|
+
export interface TelegramUpdateRuntimeDeps<
|
|
324
|
+
TContext = unknown,
|
|
325
|
+
TReactionUpdate extends TelegramMessageReactionUpdated =
|
|
326
|
+
TelegramMessageReactionUpdated,
|
|
327
|
+
TCallbackQuery extends TelegramCallbackQuery = TelegramCallbackQuery,
|
|
328
|
+
TMessage extends TelegramUpdateMessage = TelegramUpdateMessage,
|
|
329
|
+
> {
|
|
330
|
+
ctx: TContext;
|
|
307
331
|
removePendingMediaGroupMessages: (messageIds: number[]) => void;
|
|
308
332
|
removeQueuedTelegramTurnsByMessageIds: (
|
|
309
333
|
messageIds: number[],
|
|
310
|
-
ctx:
|
|
334
|
+
ctx: TContext,
|
|
311
335
|
) => number;
|
|
312
336
|
handleAuthorizedTelegramReactionUpdate: (
|
|
313
|
-
reactionUpdate:
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
337
|
+
reactionUpdate: TReactionUpdate,
|
|
338
|
+
ctx: TContext,
|
|
339
|
+
) => Promise<void>;
|
|
340
|
+
pairTelegramUserIfNeeded: (userId: number, ctx: TContext) => Promise<boolean>;
|
|
341
|
+
answerCallbackQuery: (
|
|
342
|
+
callbackQueryId: string,
|
|
343
|
+
text?: string,
|
|
344
|
+
) => Promise<void>;
|
|
345
|
+
handleAuthorizedTelegramCallbackQuery: (
|
|
346
|
+
query: TCallbackQuery,
|
|
347
|
+
ctx: TContext,
|
|
348
|
+
) => Promise<void>;
|
|
349
|
+
sendTextReply: (
|
|
350
|
+
chatId: number,
|
|
351
|
+
replyToMessageId: number,
|
|
352
|
+
text: string,
|
|
353
|
+
) => Promise<number | undefined>;
|
|
354
|
+
handleAuthorizedTelegramMessage: (
|
|
355
|
+
message: TMessage,
|
|
356
|
+
ctx: TContext,
|
|
320
357
|
) => Promise<void>;
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
ctx:
|
|
324
|
-
) =>
|
|
358
|
+
handleAuthorizedTelegramEditedMessage: (
|
|
359
|
+
message: TMessage,
|
|
360
|
+
ctx: TContext,
|
|
361
|
+
) => unknown;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export interface TelegramUpdateRuntimeControllerDeps<
|
|
365
|
+
TContext = unknown,
|
|
366
|
+
TCallbackQuery extends TelegramCallbackQuery = TelegramCallbackQuery,
|
|
367
|
+
TMessage extends TelegramUpdateMessage = TelegramUpdateMessage,
|
|
368
|
+
> {
|
|
369
|
+
getAllowedUserId: () => number | undefined;
|
|
370
|
+
removePendingMediaGroupMessages: (messageIds: number[]) => void;
|
|
371
|
+
removeQueuedTelegramTurnsByMessageIds: (
|
|
372
|
+
messageIds: number[],
|
|
373
|
+
ctx: TContext,
|
|
374
|
+
) => number;
|
|
375
|
+
clearQueuedTelegramTurnPriorityByMessageId: (
|
|
376
|
+
messageId: number,
|
|
377
|
+
ctx: TContext,
|
|
378
|
+
) => boolean;
|
|
379
|
+
prioritizeQueuedTelegramTurnByMessageId: (
|
|
380
|
+
messageId: number,
|
|
381
|
+
ctx: TContext,
|
|
382
|
+
) => boolean;
|
|
383
|
+
pairTelegramUserIfNeeded: (userId: number, ctx: TContext) => Promise<boolean>;
|
|
325
384
|
answerCallbackQuery: (
|
|
326
385
|
callbackQueryId: string,
|
|
327
386
|
text?: string,
|
|
328
387
|
) => Promise<void>;
|
|
329
388
|
handleAuthorizedTelegramCallbackQuery: (
|
|
330
|
-
query:
|
|
331
|
-
ctx:
|
|
389
|
+
query: TCallbackQuery,
|
|
390
|
+
ctx: TContext,
|
|
332
391
|
) => Promise<void>;
|
|
333
392
|
sendTextReply: (
|
|
334
393
|
chatId: number,
|
|
@@ -336,29 +395,34 @@ export interface TelegramUpdateRuntimeDeps {
|
|
|
336
395
|
text: string,
|
|
337
396
|
) => Promise<number | undefined>;
|
|
338
397
|
handleAuthorizedTelegramMessage: (
|
|
339
|
-
message:
|
|
340
|
-
|
|
341
|
-
{ kind: "message" }
|
|
342
|
-
>["message"],
|
|
343
|
-
ctx: ExtensionContext,
|
|
398
|
+
message: TMessage,
|
|
399
|
+
ctx: TContext,
|
|
344
400
|
) => Promise<void>;
|
|
345
401
|
handleAuthorizedTelegramEditedMessage: (
|
|
346
|
-
message:
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
402
|
+
message: TMessage,
|
|
403
|
+
ctx: TContext,
|
|
404
|
+
) => unknown;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export interface TelegramUpdateRuntimeController<
|
|
408
|
+
TContext = unknown,
|
|
409
|
+
TUpdate extends TelegramUpdateFlow = TelegramUpdateFlow,
|
|
410
|
+
> {
|
|
411
|
+
handleAuthorizedReactionUpdate: (
|
|
412
|
+
reactionUpdate: NonNullable<TUpdate["message_reaction"]>,
|
|
413
|
+
ctx: TContext,
|
|
351
414
|
) => Promise<void>;
|
|
415
|
+
handleUpdate: (update: TUpdate, ctx: TContext) => Promise<void>;
|
|
352
416
|
}
|
|
353
417
|
|
|
354
418
|
function getTelegramCallbackQueryId(
|
|
355
|
-
query:
|
|
419
|
+
query: TelegramCallbackQuery,
|
|
356
420
|
): string | undefined {
|
|
357
421
|
return typeof query.id === "string" ? query.id : undefined;
|
|
358
422
|
}
|
|
359
423
|
|
|
360
424
|
function getTelegramMessageReplyTarget(
|
|
361
|
-
message:
|
|
425
|
+
message: TelegramUpdateMessage,
|
|
362
426
|
): { chatId: number; messageId: number } | undefined {
|
|
363
427
|
if (
|
|
364
428
|
typeof message.chat.id !== "number" ||
|
|
@@ -372,10 +436,18 @@ function getTelegramMessageReplyTarget(
|
|
|
372
436
|
};
|
|
373
437
|
}
|
|
374
438
|
|
|
375
|
-
export async function executeTelegramUpdate
|
|
376
|
-
|
|
439
|
+
export async function executeTelegramUpdate<
|
|
440
|
+
TUpdate extends TelegramUpdateFlow,
|
|
441
|
+
TContext = unknown,
|
|
442
|
+
>(
|
|
443
|
+
update: TUpdate,
|
|
377
444
|
allowedUserId: number | undefined,
|
|
378
|
-
deps: TelegramUpdateRuntimeDeps
|
|
445
|
+
deps: TelegramUpdateRuntimeDeps<
|
|
446
|
+
TContext,
|
|
447
|
+
NonNullable<TUpdate["message_reaction"]>,
|
|
448
|
+
NonNullable<TUpdate["callback_query"]>,
|
|
449
|
+
NonNullable<TUpdate["message"] | TUpdate["edited_message"]>
|
|
450
|
+
>,
|
|
379
451
|
): Promise<void> {
|
|
380
452
|
await executeTelegramUpdatePlan(
|
|
381
453
|
buildTelegramUpdateExecutionPlanFromUpdate(update, allowedUserId),
|
|
@@ -383,9 +455,168 @@ export async function executeTelegramUpdate(
|
|
|
383
455
|
);
|
|
384
456
|
}
|
|
385
457
|
|
|
386
|
-
export
|
|
387
|
-
|
|
388
|
-
|
|
458
|
+
export type TelegramPairedUpdateRuntimeControllerDeps<
|
|
459
|
+
TContext = unknown,
|
|
460
|
+
TUpdate extends TelegramUpdateFlow = TelegramUpdateFlow,
|
|
461
|
+
> = Omit<
|
|
462
|
+
TelegramUpdateRuntimeControllerDeps<
|
|
463
|
+
TContext,
|
|
464
|
+
NonNullable<TUpdate["callback_query"]>,
|
|
465
|
+
NonNullable<TUpdate["message"] | TUpdate["edited_message"]>
|
|
466
|
+
>,
|
|
467
|
+
"pairTelegramUserIfNeeded"
|
|
468
|
+
> &
|
|
469
|
+
TelegramUserPairingRuntimeDeps<TContext>;
|
|
470
|
+
|
|
471
|
+
export function createTelegramPairedUpdateRuntime<
|
|
472
|
+
TContext = unknown,
|
|
473
|
+
TUpdate extends TelegramUpdateFlow = TelegramUpdateFlow,
|
|
474
|
+
>(
|
|
475
|
+
deps: TelegramPairedUpdateRuntimeControllerDeps<TContext, TUpdate>,
|
|
476
|
+
): TelegramUpdateRuntimeController<TContext, TUpdate> {
|
|
477
|
+
return createTelegramUpdateRuntime({
|
|
478
|
+
getAllowedUserId: deps.getAllowedUserId,
|
|
479
|
+
removePendingMediaGroupMessages: deps.removePendingMediaGroupMessages,
|
|
480
|
+
removeQueuedTelegramTurnsByMessageIds:
|
|
481
|
+
deps.removeQueuedTelegramTurnsByMessageIds,
|
|
482
|
+
clearQueuedTelegramTurnPriorityByMessageId:
|
|
483
|
+
deps.clearQueuedTelegramTurnPriorityByMessageId,
|
|
484
|
+
prioritizeQueuedTelegramTurnByMessageId:
|
|
485
|
+
deps.prioritizeQueuedTelegramTurnByMessageId,
|
|
486
|
+
pairTelegramUserIfNeeded: createTelegramUserPairingRuntime({
|
|
487
|
+
getAllowedUserId: deps.getAllowedUserId,
|
|
488
|
+
setAllowedUserId: deps.setAllowedUserId,
|
|
489
|
+
persistConfig: deps.persistConfig,
|
|
490
|
+
updateStatus: deps.updateStatus,
|
|
491
|
+
}).pairIfNeeded,
|
|
492
|
+
answerCallbackQuery: deps.answerCallbackQuery,
|
|
493
|
+
handleAuthorizedTelegramCallbackQuery:
|
|
494
|
+
deps.handleAuthorizedTelegramCallbackQuery,
|
|
495
|
+
sendTextReply: deps.sendTextReply,
|
|
496
|
+
handleAuthorizedTelegramMessage: deps.handleAuthorizedTelegramMessage,
|
|
497
|
+
handleAuthorizedTelegramEditedMessage:
|
|
498
|
+
deps.handleAuthorizedTelegramEditedMessage,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export function createTelegramUpdateRuntime<
|
|
503
|
+
TContext = unknown,
|
|
504
|
+
TUpdate extends TelegramUpdateFlow = TelegramUpdateFlow,
|
|
505
|
+
>(
|
|
506
|
+
deps: TelegramUpdateRuntimeControllerDeps<
|
|
507
|
+
TContext,
|
|
508
|
+
NonNullable<TUpdate["callback_query"]>,
|
|
509
|
+
NonNullable<TUpdate["message"] | TUpdate["edited_message"]>
|
|
510
|
+
>,
|
|
511
|
+
): TelegramUpdateRuntimeController<TContext, TUpdate> {
|
|
512
|
+
const handleAuthorizedReactionUpdate = async (
|
|
513
|
+
reactionUpdate: NonNullable<TUpdate["message_reaction"]>,
|
|
514
|
+
ctx: TContext,
|
|
515
|
+
): Promise<void> => {
|
|
516
|
+
await handleAuthorizedTelegramReactionUpdate(reactionUpdate, {
|
|
517
|
+
allowedUserId: deps.getAllowedUserId(),
|
|
518
|
+
ctx,
|
|
519
|
+
removePendingMediaGroupMessages: deps.removePendingMediaGroupMessages,
|
|
520
|
+
removeQueuedTelegramTurnsByMessageIds:
|
|
521
|
+
deps.removeQueuedTelegramTurnsByMessageIds,
|
|
522
|
+
clearQueuedTelegramTurnPriorityByMessageId:
|
|
523
|
+
deps.clearQueuedTelegramTurnPriorityByMessageId,
|
|
524
|
+
prioritizeQueuedTelegramTurnByMessageId:
|
|
525
|
+
deps.prioritizeQueuedTelegramTurnByMessageId,
|
|
526
|
+
});
|
|
527
|
+
};
|
|
528
|
+
return {
|
|
529
|
+
handleAuthorizedReactionUpdate,
|
|
530
|
+
handleUpdate: (update, ctx) =>
|
|
531
|
+
executeTelegramUpdate(update, deps.getAllowedUserId(), {
|
|
532
|
+
ctx,
|
|
533
|
+
removePendingMediaGroupMessages: deps.removePendingMediaGroupMessages,
|
|
534
|
+
removeQueuedTelegramTurnsByMessageIds:
|
|
535
|
+
deps.removeQueuedTelegramTurnsByMessageIds,
|
|
536
|
+
handleAuthorizedTelegramReactionUpdate: handleAuthorizedReactionUpdate,
|
|
537
|
+
pairTelegramUserIfNeeded: deps.pairTelegramUserIfNeeded,
|
|
538
|
+
answerCallbackQuery: deps.answerCallbackQuery,
|
|
539
|
+
handleAuthorizedTelegramCallbackQuery:
|
|
540
|
+
deps.handleAuthorizedTelegramCallbackQuery,
|
|
541
|
+
sendTextReply: deps.sendTextReply,
|
|
542
|
+
handleAuthorizedTelegramMessage: deps.handleAuthorizedTelegramMessage,
|
|
543
|
+
handleAuthorizedTelegramEditedMessage:
|
|
544
|
+
deps.handleAuthorizedTelegramEditedMessage,
|
|
545
|
+
}),
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export interface AuthorizedTelegramReactionUpdateDeps<TContext> {
|
|
550
|
+
allowedUserId?: number;
|
|
551
|
+
ctx: TContext;
|
|
552
|
+
removePendingMediaGroupMessages: (messageIds: number[]) => void;
|
|
553
|
+
removeQueuedTelegramTurnsByMessageIds: (
|
|
554
|
+
messageIds: number[],
|
|
555
|
+
ctx: TContext,
|
|
556
|
+
) => number;
|
|
557
|
+
clearQueuedTelegramTurnPriorityByMessageId: (
|
|
558
|
+
messageId: number,
|
|
559
|
+
ctx: TContext,
|
|
560
|
+
) => boolean;
|
|
561
|
+
prioritizeQueuedTelegramTurnByMessageId: (
|
|
562
|
+
messageId: number,
|
|
563
|
+
ctx: TContext,
|
|
564
|
+
) => boolean;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export async function handleAuthorizedTelegramReactionUpdate<TContext>(
|
|
568
|
+
reactionUpdate: TelegramMessageReactionUpdated,
|
|
569
|
+
deps: AuthorizedTelegramReactionUpdateDeps<TContext>,
|
|
570
|
+
): Promise<void> {
|
|
571
|
+
const reactionUser = reactionUpdate.user;
|
|
572
|
+
if (
|
|
573
|
+
reactionUpdate.chat.type !== "private" ||
|
|
574
|
+
!reactionUser ||
|
|
575
|
+
reactionUser.is_bot ||
|
|
576
|
+
reactionUser.id !== deps.allowedUserId
|
|
577
|
+
) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
const oldEmojis = collectTelegramReactionEmojis(reactionUpdate.old_reaction);
|
|
581
|
+
const newEmojis = collectTelegramReactionEmojis(reactionUpdate.new_reaction);
|
|
582
|
+
const dislikeAdded = !oldEmojis.has("👎") && newEmojis.has("👎");
|
|
583
|
+
if (dislikeAdded) {
|
|
584
|
+
deps.removePendingMediaGroupMessages([reactionUpdate.message_id]);
|
|
585
|
+
deps.removeQueuedTelegramTurnsByMessageIds(
|
|
586
|
+
[reactionUpdate.message_id],
|
|
587
|
+
deps.ctx,
|
|
588
|
+
);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const likeRemoved = oldEmojis.has("👍") && !newEmojis.has("👍");
|
|
592
|
+
if (likeRemoved) {
|
|
593
|
+
deps.clearQueuedTelegramTurnPriorityByMessageId(
|
|
594
|
+
reactionUpdate.message_id,
|
|
595
|
+
deps.ctx,
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
const likeAdded = !oldEmojis.has("👍") && newEmojis.has("👍");
|
|
599
|
+
if (!likeAdded) return;
|
|
600
|
+
deps.prioritizeQueuedTelegramTurnByMessageId(
|
|
601
|
+
reactionUpdate.message_id,
|
|
602
|
+
deps.ctx,
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export async function executeTelegramUpdatePlan<
|
|
607
|
+
TContext = unknown,
|
|
608
|
+
TReactionUpdate extends TelegramMessageReactionUpdated =
|
|
609
|
+
TelegramMessageReactionUpdated,
|
|
610
|
+
TCallbackQuery extends TelegramCallbackQuery = TelegramCallbackQuery,
|
|
611
|
+
TMessage extends TelegramUpdateMessage = TelegramUpdateMessage,
|
|
612
|
+
>(
|
|
613
|
+
plan: TelegramUpdateExecutionPlan<TReactionUpdate, TCallbackQuery, TMessage>,
|
|
614
|
+
deps: TelegramUpdateRuntimeDeps<
|
|
615
|
+
TContext,
|
|
616
|
+
TReactionUpdate,
|
|
617
|
+
TCallbackQuery,
|
|
618
|
+
TMessage
|
|
619
|
+
>,
|
|
389
620
|
): Promise<void> {
|
|
390
621
|
if (plan.kind === "ignore") return;
|
|
391
622
|
if (plan.kind === "deleted") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-telegram",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Better Telegram DM bridge extension for pi",
|
|
6
6
|
"type": "module",
|
|
@@ -21,8 +21,19 @@
|
|
|
21
21
|
"url": "https://github.com/llblab/pi-telegram/issues"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
|
-
"test": "node --experimental-strip-types --test tests/*.test.ts"
|
|
24
|
+
"test": "node --experimental-strip-types --test tests/*.test.ts",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"audit": "npm audit",
|
|
27
|
+
"pack:check": "npm pack --dry-run",
|
|
28
|
+
"validate": "npm run typecheck && npm test && npm run audit && npm run pack:check"
|
|
25
29
|
},
|
|
30
|
+
"files": [
|
|
31
|
+
"index.ts",
|
|
32
|
+
"lib/",
|
|
33
|
+
"README.md",
|
|
34
|
+
"docs/",
|
|
35
|
+
"screenshot.png"
|
|
36
|
+
],
|
|
26
37
|
"publishConfig": {
|
|
27
38
|
"access": "public"
|
|
28
39
|
},
|
|
@@ -33,9 +44,13 @@
|
|
|
33
44
|
"image": "https://github.com/llblab/pi-telegram/raw/main/screenshot.png"
|
|
34
45
|
},
|
|
35
46
|
"peerDependencies": {
|
|
36
|
-
"@mariozechner/pi-ai": "*",
|
|
37
47
|
"@mariozechner/pi-agent-core": "*",
|
|
48
|
+
"@mariozechner/pi-ai": "*",
|
|
38
49
|
"@mariozechner/pi-coding-agent": "*",
|
|
39
50
|
"@sinclair/typebox": "*"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "latest",
|
|
54
|
+
"typescript": "latest"
|
|
40
55
|
}
|
|
41
56
|
}
|