@llblab/pi-telegram 0.2.9 → 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 +40 -26
- package/docs/architecture.md +62 -35
- package/index.ts +388 -1936
- package/lib/api.ts +647 -76
- package/lib/attachments.ts +128 -16
- package/lib/commands.ts +721 -0
- package/lib/config.ts +157 -0
- package/lib/media.ts +211 -36
- package/lib/menu.ts +920 -338
- package/lib/model.ts +647 -0
- package/lib/pi.ts +80 -0
- package/lib/polling.ts +264 -18
- package/lib/preview.ts +451 -29
- package/lib/queue.ts +1134 -110
- package/lib/registration.ts +127 -28
- package/lib/rendering.ts +575 -281
- 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 +207 -17
- package/lib/updates.ts +392 -99
- package/package.json +18 -3
- package/AGENTS.md +0 -91
- package/BACKLOG.md +0 -5
- package/CHANGELOG.md +0 -23
- package/lib/model-switch.ts +0 -62
- package/tests/api.test.ts +0 -89
- package/tests/attachments.test.ts +0 -132
- package/tests/config.test.ts +0 -80
- package/tests/media.test.ts +0 -77
- package/tests/menu.test.ts +0 -676
- package/tests/polling.test.ts +0 -129
- package/tests/preview.test.ts +0 -441
- package/tests/queue.test.ts +0 -3245
- package/tests/registration.test.ts +0 -268
- package/tests/rendering.test.ts +0 -475
- package/tests/replies.test.ts +0 -142
- package/tests/turns.test.ts +0 -132
- package/tests/updates.test.ts +0 -357
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,9 +104,24 @@ export function getAuthorizedTelegramCallbackQuery(
|
|
|
117
104
|
}
|
|
118
105
|
|
|
119
106
|
export function getAuthorizedTelegramMessage(
|
|
120
|
-
update:
|
|
121
|
-
):
|
|
122
|
-
const message = update.message
|
|
107
|
+
update: TelegramUpdateRouting,
|
|
108
|
+
): TelegramUpdateMessage | undefined {
|
|
109
|
+
const message = update.message;
|
|
110
|
+
if (
|
|
111
|
+
!message ||
|
|
112
|
+
message.chat.type !== "private" ||
|
|
113
|
+
!message.from ||
|
|
114
|
+
message.from.is_bot
|
|
115
|
+
) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
return message;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function getAuthorizedTelegramEditedMessage(
|
|
122
|
+
update: TelegramUpdateRouting,
|
|
123
|
+
): TelegramUpdateMessage | undefined {
|
|
124
|
+
const message = update.edited_message;
|
|
123
125
|
if (
|
|
124
126
|
!message ||
|
|
125
127
|
message.chat.type !== "private" ||
|
|
@@ -133,35 +135,54 @@ export function getAuthorizedTelegramMessage(
|
|
|
133
135
|
|
|
134
136
|
// --- Flow ---
|
|
135
137
|
|
|
136
|
-
export interface
|
|
138
|
+
export interface TelegramMessageReactionUpdated {
|
|
137
139
|
chat: { type: string };
|
|
138
|
-
user?:
|
|
140
|
+
user?: TelegramUser;
|
|
141
|
+
message_id: number;
|
|
142
|
+
old_reaction: TelegramReactionType[];
|
|
143
|
+
new_reaction: TelegramReactionType[];
|
|
139
144
|
}
|
|
140
145
|
|
|
141
|
-
export interface
|
|
142
|
-
extends
|
|
143
|
-
message_reaction?:
|
|
146
|
+
export interface TelegramUpdateFlow
|
|
147
|
+
extends TelegramUpdateRouting, TelegramUpdateDeletion {
|
|
148
|
+
message_reaction?: TelegramMessageReactionUpdated;
|
|
144
149
|
}
|
|
145
150
|
|
|
146
|
-
export type TelegramUpdateFlowAction
|
|
151
|
+
export type TelegramUpdateFlowAction<
|
|
152
|
+
TReactionUpdate extends TelegramMessageReactionUpdated =
|
|
153
|
+
TelegramMessageReactionUpdated,
|
|
154
|
+
TCallbackQuery extends TelegramCallbackQuery = TelegramCallbackQuery,
|
|
155
|
+
TMessage extends TelegramUpdateMessage = TelegramUpdateMessage,
|
|
156
|
+
> =
|
|
147
157
|
| { kind: "ignore" }
|
|
148
158
|
| { kind: "deleted"; messageIds: number[] }
|
|
149
|
-
| { kind: "reaction"; reactionUpdate:
|
|
159
|
+
| { kind: "reaction"; reactionUpdate: TReactionUpdate }
|
|
150
160
|
| {
|
|
151
161
|
kind: "callback";
|
|
152
|
-
query:
|
|
162
|
+
query: TCallbackQuery;
|
|
153
163
|
authorization: TelegramAuthorizationState;
|
|
154
164
|
}
|
|
155
165
|
| {
|
|
156
166
|
kind: "message";
|
|
157
|
-
message:
|
|
167
|
+
message: TMessage & { from: TelegramUser };
|
|
168
|
+
authorization: TelegramAuthorizationState;
|
|
169
|
+
}
|
|
170
|
+
| {
|
|
171
|
+
kind: "edited-message";
|
|
172
|
+
message: TMessage & { from: TelegramUser };
|
|
158
173
|
authorization: TelegramAuthorizationState;
|
|
159
174
|
};
|
|
160
175
|
|
|
161
|
-
export function buildTelegramUpdateFlowAction
|
|
162
|
-
|
|
176
|
+
export function buildTelegramUpdateFlowAction<
|
|
177
|
+
TUpdate extends TelegramUpdateFlow,
|
|
178
|
+
>(
|
|
179
|
+
update: TUpdate,
|
|
163
180
|
allowedUserId?: number,
|
|
164
|
-
): TelegramUpdateFlowAction
|
|
181
|
+
): TelegramUpdateFlowAction<
|
|
182
|
+
NonNullable<TUpdate["message_reaction"]>,
|
|
183
|
+
NonNullable<TUpdate["callback_query"]>,
|
|
184
|
+
NonNullable<TUpdate["message"] | TUpdate["edited_message"]>
|
|
185
|
+
> {
|
|
165
186
|
const deletedMessageIds = extractDeletedTelegramMessageIds(update);
|
|
166
187
|
if (deletedMessageIds.length > 0) {
|
|
167
188
|
return { kind: "deleted", messageIds: deletedMessageIds };
|
|
@@ -173,7 +194,7 @@ export function buildTelegramUpdateFlowAction(
|
|
|
173
194
|
if (query) {
|
|
174
195
|
return {
|
|
175
196
|
kind: "callback",
|
|
176
|
-
query
|
|
197
|
+
query: query as NonNullable<TUpdate["callback_query"]>,
|
|
177
198
|
authorization: getTelegramAuthorizationState(
|
|
178
199
|
query.from.id,
|
|
179
200
|
allowedUserId,
|
|
@@ -184,42 +205,72 @@ export function buildTelegramUpdateFlowAction(
|
|
|
184
205
|
if (message?.from) {
|
|
185
206
|
return {
|
|
186
207
|
kind: "message",
|
|
187
|
-
message: message as
|
|
208
|
+
message: message as NonNullable<
|
|
209
|
+
TUpdate["message"] | TUpdate["edited_message"]
|
|
210
|
+
> & { from: TelegramUser },
|
|
188
211
|
authorization: getTelegramAuthorizationState(
|
|
189
212
|
message.from.id,
|
|
190
213
|
allowedUserId,
|
|
191
214
|
),
|
|
192
215
|
};
|
|
193
216
|
}
|
|
217
|
+
const editedMessage = getAuthorizedTelegramEditedMessage(update);
|
|
218
|
+
if (editedMessage?.from) {
|
|
219
|
+
return {
|
|
220
|
+
kind: "edited-message",
|
|
221
|
+
message: editedMessage as NonNullable<
|
|
222
|
+
TUpdate["message"] | TUpdate["edited_message"]
|
|
223
|
+
> & { from: TelegramUser },
|
|
224
|
+
authorization: getTelegramAuthorizationState(
|
|
225
|
+
editedMessage.from.id,
|
|
226
|
+
allowedUserId,
|
|
227
|
+
),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
194
230
|
return { kind: "ignore" };
|
|
195
231
|
}
|
|
196
232
|
|
|
197
233
|
// --- Execution Planning ---
|
|
198
234
|
|
|
199
|
-
export type TelegramUpdateExecutionPlan
|
|
235
|
+
export type TelegramUpdateExecutionPlan<
|
|
236
|
+
TReactionUpdate extends TelegramMessageReactionUpdated =
|
|
237
|
+
TelegramMessageReactionUpdated,
|
|
238
|
+
TCallbackQuery extends TelegramCallbackQuery = TelegramCallbackQuery,
|
|
239
|
+
TMessage extends TelegramUpdateMessage = TelegramUpdateMessage,
|
|
240
|
+
> =
|
|
200
241
|
| { kind: "ignore" }
|
|
201
242
|
| { kind: "deleted"; messageIds: number[] }
|
|
202
243
|
| {
|
|
203
244
|
kind: "reaction";
|
|
204
|
-
reactionUpdate:
|
|
245
|
+
reactionUpdate: TReactionUpdate;
|
|
205
246
|
}
|
|
206
247
|
| {
|
|
207
248
|
kind: "callback";
|
|
208
|
-
query:
|
|
249
|
+
query: TCallbackQuery;
|
|
209
250
|
shouldPair: boolean;
|
|
210
251
|
shouldDeny: boolean;
|
|
211
252
|
}
|
|
212
253
|
| {
|
|
213
254
|
kind: "message";
|
|
214
|
-
message:
|
|
255
|
+
message: TMessage & { from: TelegramUser };
|
|
215
256
|
shouldPair: boolean;
|
|
216
257
|
shouldNotifyPaired: boolean;
|
|
217
258
|
shouldDeny: boolean;
|
|
259
|
+
}
|
|
260
|
+
| {
|
|
261
|
+
kind: "edited-message";
|
|
262
|
+
message: TMessage & { from: TelegramUser };
|
|
263
|
+
shouldPair: boolean;
|
|
264
|
+
shouldDeny: boolean;
|
|
218
265
|
};
|
|
219
266
|
|
|
220
|
-
export function buildTelegramUpdateExecutionPlan
|
|
221
|
-
|
|
222
|
-
|
|
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> {
|
|
223
274
|
switch (action.kind) {
|
|
224
275
|
case "ignore":
|
|
225
276
|
return { kind: "ignore" };
|
|
@@ -242,13 +293,26 @@ export function buildTelegramUpdateExecutionPlan(
|
|
|
242
293
|
shouldNotifyPaired: action.authorization.kind === "pair",
|
|
243
294
|
shouldDeny: action.authorization.kind === "deny",
|
|
244
295
|
};
|
|
296
|
+
case "edited-message":
|
|
297
|
+
return {
|
|
298
|
+
kind: "edited-message",
|
|
299
|
+
message: action.message,
|
|
300
|
+
shouldPair: action.authorization.kind === "pair",
|
|
301
|
+
shouldDeny: action.authorization.kind === "deny",
|
|
302
|
+
};
|
|
245
303
|
}
|
|
246
304
|
}
|
|
247
305
|
|
|
248
|
-
export function buildTelegramUpdateExecutionPlanFromUpdate
|
|
249
|
-
|
|
306
|
+
export function buildTelegramUpdateExecutionPlanFromUpdate<
|
|
307
|
+
TUpdate extends TelegramUpdateFlow,
|
|
308
|
+
>(
|
|
309
|
+
update: TUpdate,
|
|
250
310
|
allowedUserId?: number,
|
|
251
|
-
): TelegramUpdateExecutionPlan
|
|
311
|
+
): TelegramUpdateExecutionPlan<
|
|
312
|
+
NonNullable<TUpdate["message_reaction"]>,
|
|
313
|
+
NonNullable<TUpdate["callback_query"]>,
|
|
314
|
+
NonNullable<TUpdate["message"] | TUpdate["edited_message"]>
|
|
315
|
+
> {
|
|
252
316
|
return buildTelegramUpdateExecutionPlan(
|
|
253
317
|
buildTelegramUpdateFlowAction(update, allowedUserId),
|
|
254
318
|
);
|
|
@@ -256,33 +320,31 @@ export function buildTelegramUpdateExecutionPlanFromUpdate(
|
|
|
256
320
|
|
|
257
321
|
// --- Runtime ---
|
|
258
322
|
|
|
259
|
-
export interface TelegramUpdateRuntimeDeps
|
|
260
|
-
|
|
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;
|
|
261
331
|
removePendingMediaGroupMessages: (messageIds: number[]) => void;
|
|
262
332
|
removeQueuedTelegramTurnsByMessageIds: (
|
|
263
333
|
messageIds: number[],
|
|
264
|
-
ctx:
|
|
334
|
+
ctx: TContext,
|
|
265
335
|
) => number;
|
|
266
336
|
handleAuthorizedTelegramReactionUpdate: (
|
|
267
|
-
reactionUpdate:
|
|
268
|
-
|
|
269
|
-
TelegramUpdateExecutionPlan,
|
|
270
|
-
{ kind: "reaction" }
|
|
271
|
-
>["reactionUpdate"]
|
|
272
|
-
>,
|
|
273
|
-
ctx: ExtensionContext,
|
|
337
|
+
reactionUpdate: TReactionUpdate,
|
|
338
|
+
ctx: TContext,
|
|
274
339
|
) => Promise<void>;
|
|
275
|
-
pairTelegramUserIfNeeded: (
|
|
276
|
-
userId: number,
|
|
277
|
-
ctx: ExtensionContext,
|
|
278
|
-
) => Promise<boolean>;
|
|
340
|
+
pairTelegramUserIfNeeded: (userId: number, ctx: TContext) => Promise<boolean>;
|
|
279
341
|
answerCallbackQuery: (
|
|
280
342
|
callbackQueryId: string,
|
|
281
343
|
text?: string,
|
|
282
344
|
) => Promise<void>;
|
|
283
345
|
handleAuthorizedTelegramCallbackQuery: (
|
|
284
|
-
query:
|
|
285
|
-
ctx:
|
|
346
|
+
query: TCallbackQuery,
|
|
347
|
+
ctx: TContext,
|
|
286
348
|
) => Promise<void>;
|
|
287
349
|
sendTextReply: (
|
|
288
350
|
chatId: number,
|
|
@@ -290,22 +352,77 @@ export interface TelegramUpdateRuntimeDeps {
|
|
|
290
352
|
text: string,
|
|
291
353
|
) => Promise<number | undefined>;
|
|
292
354
|
handleAuthorizedTelegramMessage: (
|
|
293
|
-
message:
|
|
294
|
-
|
|
295
|
-
{ kind: "message" }
|
|
296
|
-
>["message"],
|
|
297
|
-
ctx: ExtensionContext,
|
|
355
|
+
message: TMessage,
|
|
356
|
+
ctx: TContext,
|
|
298
357
|
) => Promise<void>;
|
|
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>;
|
|
384
|
+
answerCallbackQuery: (
|
|
385
|
+
callbackQueryId: string,
|
|
386
|
+
text?: string,
|
|
387
|
+
) => Promise<void>;
|
|
388
|
+
handleAuthorizedTelegramCallbackQuery: (
|
|
389
|
+
query: TCallbackQuery,
|
|
390
|
+
ctx: TContext,
|
|
391
|
+
) => Promise<void>;
|
|
392
|
+
sendTextReply: (
|
|
393
|
+
chatId: number,
|
|
394
|
+
replyToMessageId: number,
|
|
395
|
+
text: string,
|
|
396
|
+
) => Promise<number | undefined>;
|
|
397
|
+
handleAuthorizedTelegramMessage: (
|
|
398
|
+
message: TMessage,
|
|
399
|
+
ctx: TContext,
|
|
400
|
+
) => Promise<void>;
|
|
401
|
+
handleAuthorizedTelegramEditedMessage: (
|
|
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,
|
|
414
|
+
) => Promise<void>;
|
|
415
|
+
handleUpdate: (update: TUpdate, ctx: TContext) => Promise<void>;
|
|
299
416
|
}
|
|
300
417
|
|
|
301
418
|
function getTelegramCallbackQueryId(
|
|
302
|
-
query:
|
|
419
|
+
query: TelegramCallbackQuery,
|
|
303
420
|
): string | undefined {
|
|
304
421
|
return typeof query.id === "string" ? query.id : undefined;
|
|
305
422
|
}
|
|
306
423
|
|
|
307
424
|
function getTelegramMessageReplyTarget(
|
|
308
|
-
message:
|
|
425
|
+
message: TelegramUpdateMessage,
|
|
309
426
|
): { chatId: number; messageId: number } | undefined {
|
|
310
427
|
if (
|
|
311
428
|
typeof message.chat.id !== "number" ||
|
|
@@ -319,10 +436,18 @@ function getTelegramMessageReplyTarget(
|
|
|
319
436
|
};
|
|
320
437
|
}
|
|
321
438
|
|
|
322
|
-
export async function executeTelegramUpdate
|
|
323
|
-
|
|
439
|
+
export async function executeTelegramUpdate<
|
|
440
|
+
TUpdate extends TelegramUpdateFlow,
|
|
441
|
+
TContext = unknown,
|
|
442
|
+
>(
|
|
443
|
+
update: TUpdate,
|
|
324
444
|
allowedUserId: number | undefined,
|
|
325
|
-
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
|
+
>,
|
|
326
451
|
): Promise<void> {
|
|
327
452
|
await executeTelegramUpdatePlan(
|
|
328
453
|
buildTelegramUpdateExecutionPlanFromUpdate(update, allowedUserId),
|
|
@@ -330,9 +455,168 @@ export async function executeTelegramUpdate(
|
|
|
330
455
|
);
|
|
331
456
|
}
|
|
332
457
|
|
|
333
|
-
export
|
|
334
|
-
|
|
335
|
-
|
|
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
|
+
>,
|
|
336
620
|
): Promise<void> {
|
|
337
621
|
if (plan.kind === "ignore") return;
|
|
338
622
|
if (plan.kind === "deleted") {
|
|
@@ -368,7 +652,12 @@ export async function executeTelegramUpdatePlan(
|
|
|
368
652
|
? await deps.pairTelegramUserIfNeeded(plan.message.from.id, deps.ctx)
|
|
369
653
|
: false;
|
|
370
654
|
const replyTarget = getTelegramMessageReplyTarget(plan.message);
|
|
371
|
-
if (
|
|
655
|
+
if (
|
|
656
|
+
plan.kind === "message" &&
|
|
657
|
+
pairedNow &&
|
|
658
|
+
plan.shouldNotifyPaired &&
|
|
659
|
+
replyTarget
|
|
660
|
+
) {
|
|
372
661
|
await deps.sendTextReply(
|
|
373
662
|
replyTarget.chatId,
|
|
374
663
|
replyTarget.messageId,
|
|
@@ -385,5 +674,9 @@ export async function executeTelegramUpdatePlan(
|
|
|
385
674
|
}
|
|
386
675
|
return;
|
|
387
676
|
}
|
|
677
|
+
if (plan.kind === "edited-message") {
|
|
678
|
+
await deps.handleAuthorizedTelegramEditedMessage(plan.message, deps.ctx);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
388
681
|
await deps.handleAuthorizedTelegramMessage(plan.message, deps.ctx);
|
|
389
682
|
}
|