@llblab/pi-telegram 0.2.10 → 0.4.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 +52 -19
- package/docs/README.md +2 -3
- package/docs/architecture.md +62 -31
- package/docs/locks.md +136 -0
- package/index.ts +323 -1880
- package/lib/api.ts +396 -60
- package/lib/attachments.ts +128 -16
- package/lib/commands.ts +648 -14
- package/lib/config.ts +169 -0
- package/lib/handlers.ts +474 -0
- package/lib/locks.ts +306 -0
- package/lib/media.ts +196 -46
- package/lib/menu.ts +920 -338
- package/lib/model.ts +647 -0
- package/lib/pi.ts +90 -0
- package/lib/polling.ts +240 -14
- package/lib/preview.ts +420 -25
- package/lib/queue.ts +1137 -110
- package/lib/registration.ts +214 -31
- package/lib/rendering.ts +560 -366
- package/lib/replies.ts +198 -8
- package/lib/routing.ts +217 -0
- package/lib/runtime.ts +475 -0
- package/lib/setup.ts +143 -1
- package/lib/status.ts +432 -13
- package/lib/turns.ts +217 -36
- 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/api.ts
CHANGED
|
@@ -1,30 +1,181 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Telegram API
|
|
3
|
-
* Wraps bot API calls, file downloads,
|
|
2
|
+
* Telegram API transport helpers
|
|
3
|
+
* Wraps bot API calls, file downloads, runtime transport binding, and Telegram temp-file cleanup
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { createWriteStream, openAsBlob } from "node:fs";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
readFile,
|
|
11
|
-
readdir,
|
|
12
|
-
stat,
|
|
13
|
-
unlink,
|
|
14
|
-
writeFile,
|
|
15
|
-
} from "node:fs/promises";
|
|
8
|
+
import { mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
9
|
+
import { homedir } from "node:os";
|
|
16
10
|
import { join } from "node:path";
|
|
17
11
|
import { Readable, Transform } from "node:stream";
|
|
18
12
|
import { pipeline } from "node:stream/promises";
|
|
19
13
|
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
export const TELEGRAM_FILE_MAX_BYTES = 50 * 1024 * 1024;
|
|
15
|
+
|
|
16
|
+
export function getTelegramInboundFileByteLimitFromEnv(
|
|
17
|
+
env: NodeJS.ProcessEnv,
|
|
18
|
+
names: string[],
|
|
19
|
+
defaultValue = TELEGRAM_FILE_MAX_BYTES,
|
|
20
|
+
): number {
|
|
21
|
+
for (const name of names) {
|
|
22
|
+
const rawValue = env[name]?.trim();
|
|
23
|
+
if (!rawValue) continue;
|
|
24
|
+
const parsed = Number(rawValue);
|
|
25
|
+
if (Number.isSafeInteger(parsed) && parsed > 0) return parsed;
|
|
26
|
+
}
|
|
27
|
+
return defaultValue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const TEMP_DIR = join(homedir(), ".pi", "agent", "tmp", "telegram");
|
|
31
|
+
const TELEGRAM_TEMP_FILE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
32
|
+
const TELEGRAM_INBOUND_FILE_MAX_BYTES = getTelegramInboundFileByteLimitFromEnv(
|
|
33
|
+
process.env,
|
|
34
|
+
["PI_TELEGRAM_INBOUND_FILE_MAX_BYTES", "TELEGRAM_MAX_FILE_SIZE_BYTES"],
|
|
35
|
+
TELEGRAM_FILE_MAX_BYTES,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export interface TelegramUser {
|
|
39
|
+
id: number;
|
|
40
|
+
is_bot: boolean;
|
|
41
|
+
first_name: string;
|
|
42
|
+
username?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface TelegramChat {
|
|
46
|
+
id: number;
|
|
47
|
+
type: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface TelegramPhotoSize {
|
|
51
|
+
file_id: string;
|
|
52
|
+
file_size?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface TelegramDocument {
|
|
56
|
+
file_id: string;
|
|
57
|
+
file_name?: string;
|
|
58
|
+
mime_type?: string;
|
|
59
|
+
file_size?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface TelegramVideo {
|
|
63
|
+
file_id: string;
|
|
64
|
+
file_name?: string;
|
|
65
|
+
mime_type?: string;
|
|
66
|
+
file_size?: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface TelegramAudio {
|
|
70
|
+
file_id: string;
|
|
71
|
+
file_name?: string;
|
|
72
|
+
mime_type?: string;
|
|
73
|
+
file_size?: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface TelegramVoice {
|
|
77
|
+
file_id: string;
|
|
78
|
+
mime_type?: string;
|
|
79
|
+
file_size?: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface TelegramAnimation {
|
|
83
|
+
file_id: string;
|
|
84
|
+
file_name?: string;
|
|
85
|
+
mime_type?: string;
|
|
86
|
+
file_size?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface TelegramSticker {
|
|
90
|
+
file_id: string;
|
|
91
|
+
emoji?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface TelegramMessage {
|
|
95
|
+
message_id: number;
|
|
96
|
+
chat: TelegramChat;
|
|
97
|
+
from?: TelegramUser;
|
|
98
|
+
text?: string;
|
|
99
|
+
caption?: string;
|
|
100
|
+
media_group_id?: string;
|
|
101
|
+
photo?: TelegramPhotoSize[];
|
|
102
|
+
document?: TelegramDocument;
|
|
103
|
+
video?: TelegramVideo;
|
|
104
|
+
audio?: TelegramAudio;
|
|
105
|
+
voice?: TelegramVoice;
|
|
106
|
+
animation?: TelegramAnimation;
|
|
107
|
+
sticker?: TelegramSticker;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface TelegramCallbackQuery {
|
|
111
|
+
id: string;
|
|
112
|
+
from: TelegramUser;
|
|
113
|
+
message?: TelegramMessage;
|
|
114
|
+
data?: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface TelegramReactionTypeEmoji {
|
|
118
|
+
type: "emoji";
|
|
119
|
+
emoji: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface TelegramReactionTypeCustomEmoji {
|
|
123
|
+
type: "custom_emoji";
|
|
124
|
+
custom_emoji_id: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface TelegramReactionTypePaid {
|
|
128
|
+
type: "paid";
|
|
26
129
|
}
|
|
27
130
|
|
|
131
|
+
export type TelegramReactionType =
|
|
132
|
+
| TelegramReactionTypeEmoji
|
|
133
|
+
| TelegramReactionTypeCustomEmoji
|
|
134
|
+
| TelegramReactionTypePaid;
|
|
135
|
+
|
|
136
|
+
export interface TelegramMessageReactionUpdated {
|
|
137
|
+
chat: TelegramChat;
|
|
138
|
+
message_id: number;
|
|
139
|
+
user?: TelegramUser;
|
|
140
|
+
actor_chat?: TelegramChat;
|
|
141
|
+
old_reaction: TelegramReactionType[];
|
|
142
|
+
new_reaction: TelegramReactionType[];
|
|
143
|
+
date: number;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface TelegramUpdate {
|
|
147
|
+
update_id: number;
|
|
148
|
+
message?: TelegramMessage;
|
|
149
|
+
edited_message?: TelegramMessage;
|
|
150
|
+
callback_query?: TelegramCallbackQuery;
|
|
151
|
+
message_reaction?: TelegramMessageReactionUpdated;
|
|
152
|
+
deleted_business_messages?: { message_ids?: unknown };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface TelegramSentMessage {
|
|
156
|
+
message_id: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface TelegramReplyParameters {
|
|
160
|
+
message_id: number;
|
|
161
|
+
allow_sending_without_reply: true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export type TelegramSendMessageBody = Record<string, unknown> & {
|
|
165
|
+
chat_id: number;
|
|
166
|
+
text: string;
|
|
167
|
+
parse_mode?: "HTML";
|
|
168
|
+
reply_markup?: unknown;
|
|
169
|
+
reply_parameters?: TelegramReplyParameters;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export type TelegramEditMessageTextBody = Record<string, unknown> & {
|
|
173
|
+
chat_id: number;
|
|
174
|
+
message_id: number;
|
|
175
|
+
text: string;
|
|
176
|
+
parse_mode?: "HTML";
|
|
177
|
+
};
|
|
178
|
+
|
|
28
179
|
interface TelegramApiResponse<T> {
|
|
29
180
|
ok: boolean;
|
|
30
181
|
result?: T;
|
|
@@ -76,6 +227,59 @@ export interface TelegramApiClient {
|
|
|
76
227
|
) => Promise<void>;
|
|
77
228
|
}
|
|
78
229
|
|
|
230
|
+
export interface TelegramBridgeApiRuntimeDeps {
|
|
231
|
+
client: TelegramApiClient;
|
|
232
|
+
tempDir: string;
|
|
233
|
+
maxFileSizeBytes: number;
|
|
234
|
+
tempFileMaxAgeMs: number;
|
|
235
|
+
recordRuntimeEvent: (
|
|
236
|
+
kind: "api" | "multipart" | "download",
|
|
237
|
+
error: unknown,
|
|
238
|
+
details?: Record<string, unknown>,
|
|
239
|
+
) => void;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface TelegramBridgeApiRuntime {
|
|
243
|
+
call: <TResponse>(
|
|
244
|
+
method: string,
|
|
245
|
+
body: Record<string, unknown>,
|
|
246
|
+
options?: TelegramApiCallOptions,
|
|
247
|
+
) => Promise<TResponse>;
|
|
248
|
+
callMultipart: <TResponse>(
|
|
249
|
+
method: string,
|
|
250
|
+
fields: Record<string, string>,
|
|
251
|
+
fileField: string,
|
|
252
|
+
filePath: string,
|
|
253
|
+
fileName: string,
|
|
254
|
+
options?: TelegramApiCallOptions,
|
|
255
|
+
) => Promise<TResponse>;
|
|
256
|
+
downloadFile: (fileId: string, suggestedName: string) => Promise<string>;
|
|
257
|
+
deleteWebhook: (signal?: AbortSignal) => Promise<boolean>;
|
|
258
|
+
getUpdates: (
|
|
259
|
+
body: Record<string, unknown>,
|
|
260
|
+
signal?: AbortSignal,
|
|
261
|
+
) => Promise<TelegramUpdate[]>;
|
|
262
|
+
setMyCommands: (
|
|
263
|
+
commands: readonly { command: string; description: string }[],
|
|
264
|
+
) => Promise<boolean>;
|
|
265
|
+
sendChatAction: (chatId: number, action: "typing") => Promise<boolean>;
|
|
266
|
+
sendTypingAction: (chatId: number) => Promise<unknown>;
|
|
267
|
+
sendMessageDraft: (
|
|
268
|
+
chatId: number,
|
|
269
|
+
draftId: number,
|
|
270
|
+
text: string,
|
|
271
|
+
) => Promise<boolean>;
|
|
272
|
+
sendMessage: (body: TelegramSendMessageBody) => Promise<TelegramSentMessage>;
|
|
273
|
+
editMessageText: (
|
|
274
|
+
body: TelegramEditMessageTextBody,
|
|
275
|
+
) => Promise<"edited" | "unchanged">;
|
|
276
|
+
answerCallbackQuery: (
|
|
277
|
+
callbackQueryId: string,
|
|
278
|
+
text?: string,
|
|
279
|
+
) => Promise<void>;
|
|
280
|
+
prepareTempDir: () => Promise<number>;
|
|
281
|
+
}
|
|
282
|
+
|
|
79
283
|
function sanitizeFileName(name: string): string {
|
|
80
284
|
return name.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
81
285
|
}
|
|
@@ -94,6 +298,12 @@ class TelegramApiHttpError extends Error {
|
|
|
94
298
|
}
|
|
95
299
|
}
|
|
96
300
|
|
|
301
|
+
export function isTelegramMessageNotModifiedError(error: unknown): boolean {
|
|
302
|
+
return (
|
|
303
|
+
error instanceof Error && error.message.includes("message is not modified")
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
97
307
|
function isRetryableTelegramApiError(error: unknown): boolean {
|
|
98
308
|
return (
|
|
99
309
|
error instanceof TelegramApiHttpError &&
|
|
@@ -160,9 +370,7 @@ async function writeTelegramDownloadResponse(
|
|
|
160
370
|
return;
|
|
161
371
|
}
|
|
162
372
|
await pipeline(
|
|
163
|
-
Readable.
|
|
164
|
-
response.body as unknown as Parameters<typeof Readable.fromWeb>[0],
|
|
165
|
-
),
|
|
373
|
+
Readable.from(response.body, { objectMode: false }),
|
|
166
374
|
createTelegramDownloadLimitTransform(maxFileSizeBytes),
|
|
167
375
|
createWriteStream(targetPath),
|
|
168
376
|
);
|
|
@@ -247,30 +455,6 @@ async function callTelegramWithRetry<TResponse>(
|
|
|
247
455
|
}
|
|
248
456
|
}
|
|
249
457
|
|
|
250
|
-
export async function readTelegramConfig(
|
|
251
|
-
configPath: string,
|
|
252
|
-
): Promise<TelegramConfig> {
|
|
253
|
-
try {
|
|
254
|
-
const content = await readFile(configPath, "utf8");
|
|
255
|
-
return JSON.parse(content) as TelegramConfig;
|
|
256
|
-
} catch {
|
|
257
|
-
return {};
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
export async function writeTelegramConfig(
|
|
262
|
-
agentDir: string,
|
|
263
|
-
configPath: string,
|
|
264
|
-
config: TelegramConfig,
|
|
265
|
-
): Promise<void> {
|
|
266
|
-
await mkdir(agentDir, { recursive: true });
|
|
267
|
-
await writeFile(
|
|
268
|
-
configPath,
|
|
269
|
-
JSON.stringify(config, null, "\t") + "\n",
|
|
270
|
-
"utf8",
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
458
|
export async function cleanupTelegramTempFiles(
|
|
275
459
|
tempDir: string,
|
|
276
460
|
maxAgeMs: number,
|
|
@@ -298,19 +482,32 @@ export async function cleanupTelegramTempFiles(
|
|
|
298
482
|
return removedCount;
|
|
299
483
|
}
|
|
300
484
|
|
|
485
|
+
export async function prepareTelegramTempDir(
|
|
486
|
+
tempDir: string,
|
|
487
|
+
maxAgeMs: number,
|
|
488
|
+
): Promise<number> {
|
|
489
|
+
await mkdir(tempDir, { recursive: true });
|
|
490
|
+
return cleanupTelegramTempFiles(tempDir, maxAgeMs);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function assertTelegramBotTokenConfigured(
|
|
494
|
+
botToken: string | undefined,
|
|
495
|
+
): string {
|
|
496
|
+
if (!botToken) throw new Error("Telegram bot token is not configured");
|
|
497
|
+
return botToken;
|
|
498
|
+
}
|
|
499
|
+
|
|
301
500
|
export async function callTelegram<TResponse>(
|
|
302
501
|
botToken: string | undefined,
|
|
303
502
|
method: string,
|
|
304
503
|
body: Record<string, unknown>,
|
|
305
504
|
options?: TelegramApiCallOptions,
|
|
306
505
|
): Promise<TResponse> {
|
|
307
|
-
|
|
308
|
-
throw new Error("Telegram bot token is not configured");
|
|
309
|
-
}
|
|
506
|
+
const configuredBotToken = assertTelegramBotTokenConfigured(botToken);
|
|
310
507
|
return callTelegramWithRetry(
|
|
311
508
|
method,
|
|
312
509
|
async () =>
|
|
313
|
-
fetch(`https://api.telegram.org/bot${
|
|
510
|
+
fetch(`https://api.telegram.org/bot${configuredBotToken}/${method}`, {
|
|
314
511
|
method: "POST",
|
|
315
512
|
headers: { "content-type": "application/json" },
|
|
316
513
|
body: JSON.stringify(body),
|
|
@@ -320,6 +517,21 @@ export async function callTelegram<TResponse>(
|
|
|
320
517
|
);
|
|
321
518
|
}
|
|
322
519
|
|
|
520
|
+
export type TelegramBotIdentityResponse = Pick<
|
|
521
|
+
TelegramApiResponse<TelegramUser>,
|
|
522
|
+
"ok" | "result" | "description"
|
|
523
|
+
>;
|
|
524
|
+
|
|
525
|
+
export async function fetchTelegramBotIdentity(
|
|
526
|
+
botToken: string,
|
|
527
|
+
fetchImpl: typeof fetch = fetch,
|
|
528
|
+
): Promise<TelegramBotIdentityResponse> {
|
|
529
|
+
const response = await fetchImpl(
|
|
530
|
+
`https://api.telegram.org/bot${botToken}/getMe`,
|
|
531
|
+
);
|
|
532
|
+
return response.json() as Promise<TelegramBotIdentityResponse>;
|
|
533
|
+
}
|
|
534
|
+
|
|
323
535
|
export async function callTelegramMultipart<TResponse>(
|
|
324
536
|
botToken: string | undefined,
|
|
325
537
|
method: string,
|
|
@@ -329,9 +541,7 @@ export async function callTelegramMultipart<TResponse>(
|
|
|
329
541
|
fileName: string,
|
|
330
542
|
options?: TelegramApiCallOptions,
|
|
331
543
|
): Promise<TResponse> {
|
|
332
|
-
|
|
333
|
-
throw new Error("Telegram bot token is not configured");
|
|
334
|
-
}
|
|
544
|
+
const configuredBotToken = assertTelegramBotTokenConfigured(botToken);
|
|
335
545
|
const fileBlob = await openAsBlob(filePath);
|
|
336
546
|
return callTelegramWithRetry(
|
|
337
547
|
method,
|
|
@@ -341,11 +551,14 @@ export async function callTelegramMultipart<TResponse>(
|
|
|
341
551
|
form.set(key, value);
|
|
342
552
|
}
|
|
343
553
|
form.set(fileField, fileBlob, fileName);
|
|
344
|
-
return fetch(
|
|
345
|
-
method
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
554
|
+
return fetch(
|
|
555
|
+
`https://api.telegram.org/bot${configuredBotToken}/${method}`,
|
|
556
|
+
{
|
|
557
|
+
method: "POST",
|
|
558
|
+
body: form,
|
|
559
|
+
signal: options?.signal,
|
|
560
|
+
},
|
|
561
|
+
);
|
|
349
562
|
},
|
|
350
563
|
options,
|
|
351
564
|
);
|
|
@@ -358,11 +571,9 @@ export async function downloadTelegramFile(
|
|
|
358
571
|
tempDir: string,
|
|
359
572
|
options?: TelegramFileDownloadOptions,
|
|
360
573
|
): Promise<string> {
|
|
361
|
-
|
|
362
|
-
throw new Error("Telegram bot token is not configured");
|
|
363
|
-
}
|
|
574
|
+
const configuredBotToken = assertTelegramBotTokenConfigured(botToken);
|
|
364
575
|
const file = await callTelegram<TelegramGetFileResult>(
|
|
365
|
-
|
|
576
|
+
configuredBotToken,
|
|
366
577
|
"getFile",
|
|
367
578
|
{ file_id: fileId },
|
|
368
579
|
{ signal: options?.signal },
|
|
@@ -374,7 +585,7 @@ export async function downloadTelegramFile(
|
|
|
374
585
|
`${randomUUID()}-${sanitizeFileName(suggestedName)}`,
|
|
375
586
|
);
|
|
376
587
|
const response = await fetch(
|
|
377
|
-
`https://api.telegram.org/file/bot${
|
|
588
|
+
`https://api.telegram.org/file/bot${configuredBotToken}/${file.file_path}`,
|
|
378
589
|
{ signal: options?.signal },
|
|
379
590
|
);
|
|
380
591
|
if (!response.ok) {
|
|
@@ -416,6 +627,131 @@ export async function answerTelegramCallbackQuery(
|
|
|
416
627
|
}
|
|
417
628
|
}
|
|
418
629
|
|
|
630
|
+
export function createTelegramChatActionSender<TAction extends string>(
|
|
631
|
+
sendChatAction: (chatId: number, action: TAction) => Promise<unknown>,
|
|
632
|
+
action: TAction,
|
|
633
|
+
): (chatId: number) => Promise<unknown> {
|
|
634
|
+
return (chatId) => sendChatAction(chatId, action);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
export function createDefaultTelegramBridgeApiRuntime(deps: {
|
|
638
|
+
getBotToken: () => string | undefined;
|
|
639
|
+
recordRuntimeEvent: TelegramBridgeApiRuntimeDeps["recordRuntimeEvent"];
|
|
640
|
+
}): TelegramBridgeApiRuntime {
|
|
641
|
+
return createTelegramBridgeApiRuntime({
|
|
642
|
+
client: createTelegramApiClient(deps.getBotToken),
|
|
643
|
+
tempDir: TEMP_DIR,
|
|
644
|
+
maxFileSizeBytes: TELEGRAM_INBOUND_FILE_MAX_BYTES,
|
|
645
|
+
tempFileMaxAgeMs: TELEGRAM_TEMP_FILE_MAX_AGE_MS,
|
|
646
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
export function createTelegramBridgeApiRuntime(
|
|
651
|
+
deps: TelegramBridgeApiRuntimeDeps,
|
|
652
|
+
): TelegramBridgeApiRuntime {
|
|
653
|
+
const callRecorded = async <TResponse>(
|
|
654
|
+
method: string,
|
|
655
|
+
body: Record<string, unknown>,
|
|
656
|
+
options?: TelegramApiCallOptions,
|
|
657
|
+
): Promise<TResponse> => {
|
|
658
|
+
try {
|
|
659
|
+
return await deps.client.call(method, body, options);
|
|
660
|
+
} catch (error) {
|
|
661
|
+
deps.recordRuntimeEvent("api", error, { method });
|
|
662
|
+
throw error;
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
return {
|
|
666
|
+
call: callRecorded,
|
|
667
|
+
callMultipart: async (
|
|
668
|
+
method,
|
|
669
|
+
fields,
|
|
670
|
+
fileField,
|
|
671
|
+
filePath,
|
|
672
|
+
fileName,
|
|
673
|
+
options,
|
|
674
|
+
) => {
|
|
675
|
+
try {
|
|
676
|
+
return await deps.client.callMultipart(
|
|
677
|
+
method,
|
|
678
|
+
fields,
|
|
679
|
+
fileField,
|
|
680
|
+
filePath,
|
|
681
|
+
fileName,
|
|
682
|
+
options,
|
|
683
|
+
);
|
|
684
|
+
} catch (error) {
|
|
685
|
+
deps.recordRuntimeEvent("multipart", error, { method, fileName });
|
|
686
|
+
throw error;
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
downloadFile: async (fileId, suggestedName) => {
|
|
690
|
+
try {
|
|
691
|
+
return await deps.client.downloadFile(
|
|
692
|
+
fileId,
|
|
693
|
+
suggestedName,
|
|
694
|
+
deps.tempDir,
|
|
695
|
+
{
|
|
696
|
+
maxFileSizeBytes: deps.maxFileSizeBytes,
|
|
697
|
+
},
|
|
698
|
+
);
|
|
699
|
+
} catch (error) {
|
|
700
|
+
deps.recordRuntimeEvent("download", error, { suggestedName });
|
|
701
|
+
throw error;
|
|
702
|
+
}
|
|
703
|
+
},
|
|
704
|
+
deleteWebhook: (signal) =>
|
|
705
|
+
callRecorded<boolean>(
|
|
706
|
+
"deleteWebhook",
|
|
707
|
+
{ drop_pending_updates: false },
|
|
708
|
+
{ signal },
|
|
709
|
+
),
|
|
710
|
+
getUpdates: (body, signal) =>
|
|
711
|
+
callRecorded<TelegramUpdate[]>("getUpdates", body, { signal }),
|
|
712
|
+
setMyCommands: (commands) =>
|
|
713
|
+
callRecorded<boolean>("setMyCommands", { commands }),
|
|
714
|
+
sendChatAction: (chatId, action) =>
|
|
715
|
+
callRecorded<boolean>("sendChatAction", {
|
|
716
|
+
chat_id: chatId,
|
|
717
|
+
action,
|
|
718
|
+
}),
|
|
719
|
+
sendTypingAction: createTelegramChatActionSender(
|
|
720
|
+
(chatId, action) =>
|
|
721
|
+
callRecorded<boolean>("sendChatAction", {
|
|
722
|
+
chat_id: chatId,
|
|
723
|
+
action,
|
|
724
|
+
}),
|
|
725
|
+
"typing",
|
|
726
|
+
),
|
|
727
|
+
sendMessageDraft: (chatId, draftId, text) => {
|
|
728
|
+
if (text.length === 0) return Promise.resolve(false);
|
|
729
|
+
return callRecorded<boolean>("sendMessageDraft", {
|
|
730
|
+
chat_id: chatId,
|
|
731
|
+
draft_id: draftId,
|
|
732
|
+
text,
|
|
733
|
+
});
|
|
734
|
+
},
|
|
735
|
+
sendMessage: (body) =>
|
|
736
|
+
callRecorded<TelegramSentMessage>("sendMessage", body),
|
|
737
|
+
editMessageText: async (body) => {
|
|
738
|
+
try {
|
|
739
|
+
await deps.client.call("editMessageText", body);
|
|
740
|
+
return "edited";
|
|
741
|
+
} catch (error) {
|
|
742
|
+
if (isTelegramMessageNotModifiedError(error)) return "unchanged";
|
|
743
|
+
deps.recordRuntimeEvent("api", error, { method: "editMessageText" });
|
|
744
|
+
throw error;
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
answerCallbackQuery: (callbackQueryId, text) => {
|
|
748
|
+
return deps.client.answerCallbackQuery(callbackQueryId, text);
|
|
749
|
+
},
|
|
750
|
+
prepareTempDir: () =>
|
|
751
|
+
prepareTelegramTempDir(deps.tempDir, deps.tempFileMaxAgeMs),
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
|
|
419
755
|
export function createTelegramApiClient(
|
|
420
756
|
getBotToken: () => string | undefined,
|
|
421
757
|
): TelegramApiClient {
|