@llblab/pi-telegram 0.16.5 → 0.16.6
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/BACKLOG.md +2 -0
- package/CHANGELOG.md +13 -0
- package/README.md +2 -0
- package/docs/architecture.md +1 -1
- package/lib/keyboard.ts +39 -0
- package/lib/lifecycle.ts +6 -0
- package/lib/outbound-attachments.ts +1 -0
- package/lib/outbound-buttons.ts +1 -0
- package/lib/outbound-voice.ts +2 -0
- package/lib/polling.ts +21 -8
- package/lib/preview.ts +2 -1
- package/lib/queue.ts +1 -1
- package/lib/replies.ts +13 -6
- package/lib/sections.ts +5 -13
- package/lib/telegram-api.ts +31 -6
- package/lib/updates.ts +2 -1
- package/package.json +1 -1
package/BACKLOG.md
CHANGED
|
@@ -4,3 +4,5 @@
|
|
|
4
4
|
- Context: `pi-telegram` is an extension/mobile companion, not a PTY supervisor. A soft `/new` that mutates session internals or filters context without TUI/runtime parity breaks the product boundary.
|
|
5
5
|
- Requirement: only add Telegram `/new` when Pi exposes a safe public API that invokes the same session-replacement path as terminal `/new`, including lifecycle, active-run handling, and TUI rerender semantics.
|
|
6
6
|
- Rejected for this extension: raw TTY injection, ANSI terminal clearing, private TUI container mutation, or running a shadow `pi` subprocess to control the current session.
|
|
7
|
+
|
|
8
|
+
- [ ] Consider splitting `lib/bindings.ts` if lifecycle/tool wiring grows further.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.16.6: Telegram Review Hardening Hotfix
|
|
6
|
+
|
|
7
|
+
- `[Guest Mode]` Deny `guest_message` updates until the bridge already has a paired Telegram user. Impact: guest mode can no longer become the first pairing surface or trigger guest file/handler processing before explicit DM pairing.
|
|
8
|
+
- `[Lifecycle]` Unref the compaction observer fallback timer when the host timer supports it. Impact: headless or shutdown paths are less likely to linger until the 5-minute safety timeout.
|
|
9
|
+
- `[Shutdown]` Stop polling before clearing active-turn/abort state, keep the abort controller visible until the polling promise settles, and record typing-cleanup failures without skipping polling abort. Impact: session shutdown and polling cleanup ordering is more deterministic.
|
|
10
|
+
- `[Replies]` Scope transport-level reply deduplication by chat id. Impact: equal Telegram message ids in different chats no longer suppress valid reply metadata for each other.
|
|
11
|
+
- `[Buttons]` Consume one-shot `telegram_button` callback actions after the first successful resolve. Impact: repeated taps on an old assistant-authored button no longer enqueue duplicate prompts.
|
|
12
|
+
- `[Buttons]` Centralized Telegram `callback_data` byte-limit guards for generated inline keyboards outside the section helper path. Impact: oversized generated button callbacks fail locally before Telegram rejects the message.
|
|
13
|
+
- `[Diagnostics]` Record `answerCallbackQuery` transport failures in runtime API diagnostics while keeping callback handling non-fatal. Impact: `/telegram-status` can explain failed Telegram callback acknowledgements instead of losing the signal silently.
|
|
14
|
+
- `[Tests]` Added regressions for shutdown during pending control, long-text, and media-group dispatch, for settings menu callbacks persisting voice/time changes to `telegram.json`, for malformed/boundary Markdown rendering, and for runtime outbound delivery retrying a transient Telegram API failure. Impact: high-risk queue/timer/settings/rendering/API paths are pinned at the bridge boundary.
|
|
15
|
+
- `[Docs]` Documented which environment-driven transport defaults should be set before launch because module-load constants intentionally capture them.
|
|
16
|
+
- `[Backlog]` Captured and narrowed the non-blocking 2026-06 review-swarm follow-ups for lifecycle shutdown hardening, reply/callback/button state cleanup, validation coverage, and bindings maintainability.
|
|
17
|
+
|
|
5
18
|
## 0.16.5: Context-Aware Prompt Guidance Hotfix
|
|
6
19
|
|
|
7
20
|
- `[Prompt Guidance]` Made before-agent-start Telegram guidance context-aware: unconfigured sessions receive no bridge suffix, local/TUI prompts receive only explicit direct-delivery guidance, and Telegram-originated turns keep the full inbound, phone-width, voice, and button contract. Impact: ordinary local replies no longer get raw Telegram action-comment syntax unless the current turn actually comes from Telegram.
|
package/README.md
CHANGED
|
@@ -68,6 +68,8 @@ Most day-to-day controls live in the Telegram menu or π commands. A few importa
|
|
|
68
68
|
- **Inbound file limit**: `PI_TELEGRAM_INBOUND_FILE_MAX_BYTES` or `TELEGRAM_MAX_FILE_SIZE_BYTES` changes the default 50 MiB Telegram download limit.
|
|
69
69
|
- **Outbound attachment limit**: `PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES` or `TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES` changes the default 50 MiB `telegram_attach` delivery limit.
|
|
70
70
|
|
|
71
|
+
Set these variables before launching π. Some transport defaults (notably Telegram temp directory and inbound/outbound byte-limit constants) are intentionally captured when the extension modules load, while setup-token defaults and agent-dir lookups used by config/locks are read through their runtime helpers.
|
|
72
|
+
|
|
71
73
|
## Use
|
|
72
74
|
|
|
73
75
|
Once paired, chat with your bot in Telegram. Text, images, files, replies, edits, media groups, and configured handler output are forwarded into π as Telegram-originated turns.
|
package/docs/architecture.md
CHANGED
|
@@ -117,7 +117,7 @@ Deleting `locks.json` resets runtime ownership without deleting Telegram configu
|
|
|
117
117
|
|
|
118
118
|
1. Poll updates through `getUpdates`.
|
|
119
119
|
2. Persist update offsets only after successful handling; repeated handler failures are bounded.
|
|
120
|
-
3. Filter to the paired private user.
|
|
120
|
+
3. Filter to the paired private user; guest-mode updates require an existing paired user and cannot establish first pairing.
|
|
121
121
|
4. Dispatch owned callbacks and controls before fallback prompt forwarding.
|
|
122
122
|
5. Coalesce media groups and likely split long text when needed.
|
|
123
123
|
6. Download files into `~/.pi/agent/tmp/telegram` with size limits and partial-download cleanup.
|
package/lib/keyboard.ts
CHANGED
|
@@ -12,3 +12,42 @@ export interface TelegramInlineKeyboardButton {
|
|
|
12
12
|
export interface TelegramInlineKeyboardMarkup {
|
|
13
13
|
inline_keyboard: TelegramInlineKeyboardButton[][];
|
|
14
14
|
}
|
|
15
|
+
|
|
16
|
+
export const TELEGRAM_CALLBACK_DATA_MAX_BYTES = 64;
|
|
17
|
+
|
|
18
|
+
export function getTelegramCallbackDataByteLength(value: string): number {
|
|
19
|
+
return new TextEncoder().encode(value).byteLength;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function assertTelegramCallbackData(
|
|
23
|
+
callbackData: string,
|
|
24
|
+
context = "Telegram callback_data",
|
|
25
|
+
): string {
|
|
26
|
+
const byteLength = getTelegramCallbackDataByteLength(callbackData);
|
|
27
|
+
if (byteLength > TELEGRAM_CALLBACK_DATA_MAX_BYTES) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`${context} exceeds ${TELEGRAM_CALLBACK_DATA_MAX_BYTES} bytes (${byteLength}). Use a shorter action/payload or store state behind a compact key.`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return callbackData;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function assertTelegramInlineKeyboardCallbackData(
|
|
36
|
+
replyMarkup: unknown,
|
|
37
|
+
context = "Telegram inline keyboard callback_data",
|
|
38
|
+
): void {
|
|
39
|
+
if (!replyMarkup || typeof replyMarkup !== "object") return;
|
|
40
|
+
const keyboard = (replyMarkup as { inline_keyboard?: unknown })
|
|
41
|
+
.inline_keyboard;
|
|
42
|
+
if (!Array.isArray(keyboard)) return;
|
|
43
|
+
for (const row of keyboard) {
|
|
44
|
+
if (!Array.isArray(row)) continue;
|
|
45
|
+
for (const button of row) {
|
|
46
|
+
if (!button || typeof button !== "object") continue;
|
|
47
|
+
const callbackData = (button as { callback_data?: unknown })
|
|
48
|
+
.callback_data;
|
|
49
|
+
if (typeof callbackData !== "string") continue;
|
|
50
|
+
assertTelegramCallbackData(callbackData, context);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
package/lib/lifecycle.ts
CHANGED
|
@@ -138,6 +138,11 @@ export function createTelegramSessionContextTracker(
|
|
|
138
138
|
|
|
139
139
|
type TelegramLifecycleTimer = number | ReturnType<typeof setTimeout>;
|
|
140
140
|
|
|
141
|
+
function unrefTelegramLifecycleTimer(timer: TelegramLifecycleTimer): void {
|
|
142
|
+
if (!timer || typeof timer !== "object") return;
|
|
143
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
144
|
+
}
|
|
145
|
+
|
|
141
146
|
export interface TelegramCompactionObserverRuntimeDeps<TContext> {
|
|
142
147
|
setCompactionInProgress: (inProgress: boolean) => void;
|
|
143
148
|
updateStatus: (ctx: TContext) => void;
|
|
@@ -196,6 +201,7 @@ export function createTelegramCompactionObserverRuntime<TContext>(
|
|
|
196
201
|
);
|
|
197
202
|
requestDispatch();
|
|
198
203
|
}, timeoutMs);
|
|
204
|
+
unrefTelegramLifecycleTimer(fallbackTimer);
|
|
199
205
|
},
|
|
200
206
|
onSessionCompact: (_event, ctx) => {
|
|
201
207
|
clearFallbackTimer();
|
|
@@ -474,6 +474,7 @@ export async function sendQueuedTelegramOutboundAttachments(
|
|
|
474
474
|
const method = isPhoto ? "sendPhoto" : "sendDocument";
|
|
475
475
|
const fieldName = isPhoto ? "photo" : "document";
|
|
476
476
|
const replyParameters = buildTelegramMultipartReplyParameters(
|
|
477
|
+
turn.chatId,
|
|
477
478
|
turn.replyToMessageId,
|
|
478
479
|
);
|
|
479
480
|
await deps.sendMultipart(
|
package/lib/outbound-buttons.ts
CHANGED
|
@@ -135,6 +135,7 @@ export function createTelegramButtonActionStore(
|
|
|
135
135
|
cleanup(currentTime);
|
|
136
136
|
const action = actions.get(callbackData);
|
|
137
137
|
if (!action) return undefined;
|
|
138
|
+
actions.delete(callbackData);
|
|
138
139
|
return { text: action.text, prompt: action.prompt };
|
|
139
140
|
},
|
|
140
141
|
};
|
package/lib/outbound-voice.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { unlink } from "node:fs/promises";
|
|
8
8
|
import { basename, extname } from "node:path";
|
|
9
9
|
|
|
10
|
+
import { assertTelegramInlineKeyboardCallbackData } from "./keyboard.ts";
|
|
10
11
|
import { getTelegramVoiceSynthesisProviders } from "./voice.ts";
|
|
11
12
|
|
|
12
13
|
export interface TelegramVoiceReplyTurnView {
|
|
@@ -125,6 +126,7 @@ export function createTelegramVoiceReplySender<THandler = unknown>(
|
|
|
125
126
|
},
|
|
126
127
|
): Promise<void> {
|
|
127
128
|
const voiceFilePath = await ensureTelegramVoiceFileFormat(filePath);
|
|
129
|
+
assertTelegramInlineKeyboardCallbackData(options?.replyMarkup);
|
|
128
130
|
await sendVoiceChatAction(deps, turn.chatId);
|
|
129
131
|
const replyParameters = buildVoiceReplyParameters(
|
|
130
132
|
options?.replyToPrompt,
|
package/lib/polling.ts
CHANGED
|
@@ -187,11 +187,21 @@ export function shouldStartTelegramPolling(
|
|
|
187
187
|
export async function stopTelegramPollingRuntime<TContext>(
|
|
188
188
|
deps: TelegramPollingRuntimeDeps<TContext>,
|
|
189
189
|
): Promise<void> {
|
|
190
|
-
deps.
|
|
191
|
-
deps.getPollingController()
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
190
|
+
const pollingPromise = deps.getPollingPromise();
|
|
191
|
+
const pollingController = deps.getPollingController();
|
|
192
|
+
try {
|
|
193
|
+
deps.stopTypingLoop();
|
|
194
|
+
} catch (error) {
|
|
195
|
+
deps.recordRuntimeEvent?.("polling", error, { phase: "typing-stop" });
|
|
196
|
+
}
|
|
197
|
+
pollingController?.abort();
|
|
198
|
+
await pollingPromise?.catch(() => undefined);
|
|
199
|
+
if (deps.getPollingPromise() === pollingPromise) {
|
|
200
|
+
deps.setPollingPromise(undefined);
|
|
201
|
+
}
|
|
202
|
+
if (deps.getPollingController() === pollingController) {
|
|
203
|
+
deps.setPollingController(undefined);
|
|
204
|
+
}
|
|
195
205
|
}
|
|
196
206
|
|
|
197
207
|
function updateTelegramPollingStatusSafely<TContext>(
|
|
@@ -224,9 +234,12 @@ export function startTelegramPollingRuntime<TContext>(
|
|
|
224
234
|
}
|
|
225
235
|
const controller = deps.createAbortController?.() ?? new AbortController();
|
|
226
236
|
deps.setPollingController(controller);
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
deps.
|
|
237
|
+
let promise: Promise<void>;
|
|
238
|
+
promise = deps.runPollLoop(ctx, controller.signal).finally(() => {
|
|
239
|
+
if (deps.getPollingPromise() === promise) deps.setPollingPromise(undefined);
|
|
240
|
+
if (deps.getPollingController() === controller) {
|
|
241
|
+
deps.setPollingController(undefined);
|
|
242
|
+
}
|
|
230
243
|
updateTelegramPollingStatusSafely(deps.updateStatus, ctx, {
|
|
231
244
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
232
245
|
});
|
package/lib/preview.ts
CHANGED
|
@@ -241,6 +241,7 @@ export interface TelegramPreviewMessageTransportDeps {
|
|
|
241
241
|
sendMessage: (body: TelegramSendMessageBody) => Promise<TelegramSentMessage>;
|
|
242
242
|
editMessageText: (body: TelegramEditMessageTextBody) => Promise<unknown>;
|
|
243
243
|
buildReplyParameters?: (
|
|
244
|
+
chatId: number,
|
|
244
245
|
replyToMessageId: number | undefined,
|
|
245
246
|
) => TelegramReplyParameters | undefined;
|
|
246
247
|
}
|
|
@@ -252,7 +253,7 @@ export function createTelegramPreviewMessageTransport(
|
|
|
252
253
|
deps.buildReplyParameters ?? buildTelegramReplyParameters;
|
|
253
254
|
return {
|
|
254
255
|
sendMessage: (chatId, text, options, replyToMessageId) => {
|
|
255
|
-
const replyParameters = getReplyParameters(replyToMessageId);
|
|
256
|
+
const replyParameters = getReplyParameters(chatId, replyToMessageId);
|
|
256
257
|
return deps.sendMessage({
|
|
257
258
|
chat_id: chatId,
|
|
258
259
|
text,
|
package/lib/queue.ts
CHANGED
|
@@ -1439,6 +1439,7 @@ export async function shutdownTelegramSessionRuntime<TQueueItem>(
|
|
|
1439
1439
|
deps: TelegramSessionShutdownRuntimeDeps<TQueueItem>,
|
|
1440
1440
|
): Promise<void> {
|
|
1441
1441
|
deps.unbindDeferredDispatchContext?.();
|
|
1442
|
+
await deps.stopPolling();
|
|
1442
1443
|
deps.applyState(buildTelegramSessionShutdownState<TQueueItem>());
|
|
1443
1444
|
deps.clearPendingMediaGroups();
|
|
1444
1445
|
deps.clearModelMenuState();
|
|
@@ -1448,7 +1449,6 @@ export async function shutdownTelegramSessionRuntime<TQueueItem>(
|
|
|
1448
1449
|
}
|
|
1449
1450
|
deps.clearActiveTurn();
|
|
1450
1451
|
deps.clearAbort();
|
|
1451
|
-
await deps.stopPolling();
|
|
1452
1452
|
}
|
|
1453
1453
|
|
|
1454
1454
|
export type TelegramSessionLifecycleRuntimeDeps<
|
package/lib/replies.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Owns rendered-message delivery, reply transport wiring, and plain or markdown final replies
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { assertTelegramInlineKeyboardCallbackData } from "./keyboard.ts";
|
|
7
8
|
import type {
|
|
8
9
|
TelegramReplyParameters,
|
|
9
10
|
TelegramSentMessage,
|
|
@@ -50,25 +51,29 @@ export function createReplyDedupRuntime(): ReplyDedupRuntime {
|
|
|
50
51
|
|
|
51
52
|
// --- Transport-level dedup ---
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
const lastRepliedToMessageIdByChat = new Map<number, number>();
|
|
54
55
|
|
|
55
56
|
export function resetTransportReplyDedup(): void {
|
|
56
|
-
|
|
57
|
+
lastRepliedToMessageIdByChat.clear();
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
export function buildTelegramReplyParameters(
|
|
61
|
+
chatId: number,
|
|
60
62
|
messageId: number | undefined,
|
|
61
63
|
): TelegramReplyParameters | undefined {
|
|
62
64
|
if (messageId === undefined) return undefined;
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
+
if (lastRepliedToMessageIdByChat.get(chatId) === messageId) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
lastRepliedToMessageIdByChat.set(chatId, messageId);
|
|
65
69
|
return { message_id: messageId, allow_sending_without_reply: true };
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
export function buildTelegramMultipartReplyParameters(
|
|
73
|
+
chatId: number,
|
|
69
74
|
messageId: number | undefined,
|
|
70
75
|
): string | undefined {
|
|
71
|
-
const parameters = buildTelegramReplyParameters(messageId);
|
|
76
|
+
const parameters = buildTelegramReplyParameters(chatId, messageId);
|
|
72
77
|
return parameters ? JSON.stringify(parameters) : undefined;
|
|
73
78
|
}
|
|
74
79
|
|
|
@@ -178,11 +183,12 @@ export async function sendTelegramRenderedChunks<TReplyMarkup>(
|
|
|
178
183
|
deps: TelegramReplyDeliveryDeps<TReplyMarkup>,
|
|
179
184
|
options?: { replyMarkup?: TReplyMarkup; replyToMessageId?: number },
|
|
180
185
|
): Promise<number | undefined> {
|
|
186
|
+
assertTelegramInlineKeyboardCallbackData(options?.replyMarkup);
|
|
181
187
|
let lastMessageId: number | undefined;
|
|
182
188
|
for (const [index, chunk] of chunks.entries()) {
|
|
183
189
|
const replyParameters =
|
|
184
190
|
index === 0
|
|
185
|
-
? buildTelegramReplyParameters(options?.replyToMessageId)
|
|
191
|
+
? buildTelegramReplyParameters(chatId, options?.replyToMessageId)
|
|
186
192
|
: undefined;
|
|
187
193
|
const sent = await deps.sendMessage({
|
|
188
194
|
chat_id: chatId,
|
|
@@ -204,6 +210,7 @@ export async function editTelegramRenderedMessage<TReplyMarkup>(
|
|
|
204
210
|
deps: TelegramReplyDeliveryDeps<TReplyMarkup>,
|
|
205
211
|
options?: { replyMarkup?: TReplyMarkup },
|
|
206
212
|
): Promise<number | undefined> {
|
|
213
|
+
assertTelegramInlineKeyboardCallbackData(options?.replyMarkup);
|
|
207
214
|
if (chunks.length === 0) return messageId;
|
|
208
215
|
const [firstChunk, ...remainingChunks] = chunks;
|
|
209
216
|
await deps.editMessage({
|
package/lib/sections.ts
CHANGED
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
* Owns section registration, global registry binding, token mapping, main-menu/settings row injection, and section callback dispatch
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import {
|
|
8
|
+
assertTelegramCallbackData,
|
|
9
|
+
type TelegramInlineKeyboardMarkup,
|
|
10
|
+
} from "./keyboard.ts";
|
|
8
11
|
|
|
9
12
|
const SECTION_REGISTRY_KEY = "__piTelegramSectionRegistry__";
|
|
10
|
-
const TELEGRAM_CALLBACK_DATA_MAX_BYTES = 64;
|
|
11
13
|
|
|
12
14
|
// --- Core Types ---
|
|
13
15
|
|
|
@@ -301,10 +303,6 @@ const BACK_NAV_ROW = {
|
|
|
301
303
|
text: "⬆️ Back",
|
|
302
304
|
} as const;
|
|
303
305
|
|
|
304
|
-
function getUtf8ByteLength(value: string): number {
|
|
305
|
-
return new TextEncoder().encode(value).byteLength;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
306
|
function sectionErrorMessage(error: unknown): string {
|
|
309
307
|
return error instanceof Error ? error.message : String(error);
|
|
310
308
|
}
|
|
@@ -317,13 +315,7 @@ function buildTelegramSectionCallbackData(
|
|
|
317
315
|
const data = payload
|
|
318
316
|
? `section:${token}:${action}:${payload}`
|
|
319
317
|
: `section:${token}:${action}`;
|
|
320
|
-
|
|
321
|
-
if (byteLength > TELEGRAM_CALLBACK_DATA_MAX_BYTES) {
|
|
322
|
-
throw new Error(
|
|
323
|
-
`Telegram section callback_data exceeds ${TELEGRAM_CALLBACK_DATA_MAX_BYTES} bytes (${byteLength}). Use a shorter action/payload or store state behind a compact key.`,
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
return data;
|
|
318
|
+
return assertTelegramCallbackData(data, "Telegram section callback_data");
|
|
327
319
|
}
|
|
328
320
|
|
|
329
321
|
function prependBackRow(
|
package/lib/telegram-api.ts
CHANGED
|
@@ -234,6 +234,14 @@ export interface TelegramFileDownloadOptions {
|
|
|
234
234
|
maxFileSizeBytes?: number;
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
export interface TelegramAnswerCallbackQueryOptions {
|
|
238
|
+
recordRuntimeEvent?: (
|
|
239
|
+
kind: "api",
|
|
240
|
+
error: unknown,
|
|
241
|
+
details?: Record<string, unknown>,
|
|
242
|
+
) => void;
|
|
243
|
+
}
|
|
244
|
+
|
|
237
245
|
export interface TelegramApiClient {
|
|
238
246
|
call: <TResponse>(
|
|
239
247
|
method: string,
|
|
@@ -664,6 +672,7 @@ export async function answerTelegramCallbackQuery(
|
|
|
664
672
|
botToken: string | undefined,
|
|
665
673
|
callbackQueryId: string,
|
|
666
674
|
text?: string,
|
|
675
|
+
options: TelegramAnswerCallbackQueryOptions = {},
|
|
667
676
|
): Promise<void> {
|
|
668
677
|
try {
|
|
669
678
|
await callTelegram<boolean>(
|
|
@@ -673,8 +682,10 @@ export async function answerTelegramCallbackQuery(
|
|
|
673
682
|
? { callback_query_id: callbackQueryId, text }
|
|
674
683
|
: { callback_query_id: callbackQueryId },
|
|
675
684
|
);
|
|
676
|
-
} catch {
|
|
677
|
-
|
|
685
|
+
} catch (error) {
|
|
686
|
+
options.recordRuntimeEvent?.("api", error, {
|
|
687
|
+
method: "answerCallbackQuery",
|
|
688
|
+
});
|
|
678
689
|
}
|
|
679
690
|
}
|
|
680
691
|
|
|
@@ -705,7 +716,9 @@ export function createDefaultTelegramBridgeApiRuntime(deps: {
|
|
|
705
716
|
recordRuntimeEvent: TelegramBridgeApiRuntimeDeps["recordRuntimeEvent"];
|
|
706
717
|
}): TelegramBridgeApiRuntime {
|
|
707
718
|
return createTelegramBridgeApiRuntime({
|
|
708
|
-
client: createTelegramApiClient(deps.getBotToken
|
|
719
|
+
client: createTelegramApiClient(deps.getBotToken, {
|
|
720
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
721
|
+
}),
|
|
709
722
|
tempDir: getTelegramApiTempDir(),
|
|
710
723
|
maxFileSizeBytes: TELEGRAM_INBOUND_FILE_MAX_BYTES,
|
|
711
724
|
tempFileMaxAgeMs: TELEGRAM_TEMP_FILE_MAX_AGE_MS,
|
|
@@ -834,8 +847,14 @@ export function createTelegramBridgeApiRuntime(
|
|
|
834
847
|
throw error;
|
|
835
848
|
}
|
|
836
849
|
},
|
|
837
|
-
answerCallbackQuery: (callbackQueryId, text) => {
|
|
838
|
-
|
|
850
|
+
answerCallbackQuery: async (callbackQueryId, text) => {
|
|
851
|
+
try {
|
|
852
|
+
await deps.client.answerCallbackQuery(callbackQueryId, text);
|
|
853
|
+
} catch (error) {
|
|
854
|
+
deps.recordRuntimeEvent("api", error, {
|
|
855
|
+
method: "answerCallbackQuery",
|
|
856
|
+
});
|
|
857
|
+
}
|
|
839
858
|
},
|
|
840
859
|
answerGuestQuery: (
|
|
841
860
|
guestQueryId: string,
|
|
@@ -876,6 +895,7 @@ export function createTelegramBridgeApiRuntime(
|
|
|
876
895
|
*/
|
|
877
896
|
export function createTelegramApiClient(
|
|
878
897
|
getBotToken: () => string | undefined,
|
|
898
|
+
options: TelegramAnswerCallbackQueryOptions = {},
|
|
879
899
|
): TelegramApiClient {
|
|
880
900
|
return {
|
|
881
901
|
call: async (method, body, options) => {
|
|
@@ -909,7 +929,12 @@ export function createTelegramApiClient(
|
|
|
909
929
|
);
|
|
910
930
|
},
|
|
911
931
|
answerCallbackQuery: async (callbackQueryId, text) => {
|
|
912
|
-
await answerTelegramCallbackQuery(
|
|
932
|
+
await answerTelegramCallbackQuery(
|
|
933
|
+
getBotToken(),
|
|
934
|
+
callbackQueryId,
|
|
935
|
+
text,
|
|
936
|
+
options,
|
|
937
|
+
);
|
|
913
938
|
},
|
|
914
939
|
};
|
|
915
940
|
}
|
package/lib/updates.ts
CHANGED
|
@@ -406,7 +406,8 @@ export function buildTelegramUpdateExecutionPlan<
|
|
|
406
406
|
return {
|
|
407
407
|
kind: "guest",
|
|
408
408
|
guestMessage: action.guestMessage,
|
|
409
|
-
|
|
409
|
+
// Guest mode is an extension of an already paired bridge, not a pairing surface.
|
|
410
|
+
shouldDeny: action.authorization.kind !== "allow",
|
|
410
411
|
};
|
|
411
412
|
}
|
|
412
413
|
}
|