@llblab/pi-telegram 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -1
- package/CHANGELOG.md +15 -0
- package/README.md +16 -13
- package/docs/architecture.md +26 -16
- package/index.ts +199 -251
- package/lib/api.ts +277 -42
- package/lib/commands.ts +87 -0
- package/lib/media.ts +70 -1
- package/lib/polling.ts +25 -5
- package/lib/preview.ts +239 -0
- package/lib/rendering.ts +686 -49
- package/lib/replies.ts +2 -181
- package/lib/turns.ts +86 -0
- package/lib/types.ts +137 -0
- package/lib/updates.ts +64 -2
- package/package.json +1 -1
- package/tests/api.test.ts +243 -1
- package/tests/commands.test.ts +85 -0
- package/tests/media.test.ts +90 -1
- package/tests/menu.test.ts +46 -15
- package/tests/polling.test.ts +73 -0
- package/tests/preview.test.ts +480 -0
- package/tests/queue.test.ts +3 -0
- package/tests/rendering.test.ts +175 -2
- package/tests/replies.test.ts +2 -222
- package/tests/turns.test.ts +115 -0
- package/tests/updates.test.ts +72 -7
package/lib/replies.ts
CHANGED
|
@@ -1,187 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Telegram reply
|
|
3
|
-
* Owns
|
|
2
|
+
* Telegram reply delivery helpers
|
|
3
|
+
* Owns rendered-message delivery, reply transport wiring, and plain or markdown final replies
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { TelegramRenderedChunk, TelegramRenderMode } from "./rendering.ts";
|
|
7
7
|
|
|
8
|
-
// --- Preview ---
|
|
9
|
-
|
|
10
|
-
export interface TelegramPreviewStateLike {
|
|
11
|
-
mode: "draft" | "message";
|
|
12
|
-
draftId?: number;
|
|
13
|
-
messageId?: number;
|
|
14
|
-
pendingText: string;
|
|
15
|
-
lastSentText: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface TelegramPreviewRuntimeState extends TelegramPreviewStateLike {
|
|
19
|
-
flushTimer?: ReturnType<typeof setTimeout>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface TelegramPreviewRuntimeDeps {
|
|
23
|
-
getState: () => TelegramPreviewRuntimeState | undefined;
|
|
24
|
-
setState: (state: TelegramPreviewRuntimeState | undefined) => void;
|
|
25
|
-
clearScheduledFlush: (state: TelegramPreviewRuntimeState) => void;
|
|
26
|
-
maxMessageLength: number;
|
|
27
|
-
renderPreviewText: (markdown: string) => string;
|
|
28
|
-
getDraftSupport: () => "unknown" | "supported" | "unsupported";
|
|
29
|
-
setDraftSupport: (support: "unknown" | "supported" | "unsupported") => void;
|
|
30
|
-
allocateDraftId: () => number;
|
|
31
|
-
sendDraft: (chatId: number, draftId: number, text: string) => Promise<void>;
|
|
32
|
-
sendMessage: (
|
|
33
|
-
chatId: number,
|
|
34
|
-
text: string,
|
|
35
|
-
) => Promise<TelegramSentMessageLike>;
|
|
36
|
-
editMessageText: (
|
|
37
|
-
chatId: number,
|
|
38
|
-
messageId: number,
|
|
39
|
-
text: string,
|
|
40
|
-
) => Promise<void>;
|
|
41
|
-
renderTelegramMessage: (
|
|
42
|
-
text: string,
|
|
43
|
-
options?: { mode?: TelegramRenderMode },
|
|
44
|
-
) => TelegramRenderedChunk[];
|
|
45
|
-
sendRenderedChunks: (
|
|
46
|
-
chatId: number,
|
|
47
|
-
chunks: TelegramRenderedChunk[],
|
|
48
|
-
) => Promise<number | undefined>;
|
|
49
|
-
editRenderedMessage: (
|
|
50
|
-
chatId: number,
|
|
51
|
-
messageId: number,
|
|
52
|
-
chunks: TelegramRenderedChunk[],
|
|
53
|
-
) => Promise<number | undefined>;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function buildTelegramPreviewFlushText(options: {
|
|
57
|
-
state: TelegramPreviewStateLike;
|
|
58
|
-
maxMessageLength: number;
|
|
59
|
-
renderPreviewText: (markdown: string) => string;
|
|
60
|
-
}): string | undefined {
|
|
61
|
-
const rawText = options.state.pendingText.trim();
|
|
62
|
-
const previewText = options.renderPreviewText(rawText).trim();
|
|
63
|
-
if (!previewText || previewText === options.state.lastSentText) {
|
|
64
|
-
return undefined;
|
|
65
|
-
}
|
|
66
|
-
return previewText.length > options.maxMessageLength
|
|
67
|
-
? previewText.slice(0, options.maxMessageLength)
|
|
68
|
-
: previewText;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function buildTelegramPreviewFinalText(
|
|
72
|
-
state: TelegramPreviewStateLike,
|
|
73
|
-
): string | undefined {
|
|
74
|
-
const finalText = (state.pendingText.trim() || state.lastSentText).trim();
|
|
75
|
-
return finalText || undefined;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function shouldUseTelegramDraftPreview(options: {
|
|
79
|
-
draftSupport: "unknown" | "supported" | "unsupported";
|
|
80
|
-
}): boolean {
|
|
81
|
-
return options.draftSupport !== "unsupported";
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export async function clearTelegramPreview(
|
|
85
|
-
chatId: number,
|
|
86
|
-
deps: TelegramPreviewRuntimeDeps,
|
|
87
|
-
): Promise<void> {
|
|
88
|
-
const state = deps.getState();
|
|
89
|
-
if (!state) return;
|
|
90
|
-
deps.clearScheduledFlush(state);
|
|
91
|
-
deps.setState(undefined);
|
|
92
|
-
if (state.mode !== "draft" || state.draftId === undefined) return;
|
|
93
|
-
try {
|
|
94
|
-
await deps.sendDraft(chatId, state.draftId, "");
|
|
95
|
-
} catch {
|
|
96
|
-
// ignore
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export async function flushTelegramPreview(
|
|
101
|
-
chatId: number,
|
|
102
|
-
deps: TelegramPreviewRuntimeDeps,
|
|
103
|
-
): Promise<void> {
|
|
104
|
-
const state = deps.getState();
|
|
105
|
-
if (!state) return;
|
|
106
|
-
state.flushTimer = undefined;
|
|
107
|
-
const truncated = buildTelegramPreviewFlushText({
|
|
108
|
-
state,
|
|
109
|
-
maxMessageLength: deps.maxMessageLength,
|
|
110
|
-
renderPreviewText: deps.renderPreviewText,
|
|
111
|
-
});
|
|
112
|
-
if (!truncated) return;
|
|
113
|
-
if (shouldUseTelegramDraftPreview({ draftSupport: deps.getDraftSupport() })) {
|
|
114
|
-
const draftId = state.draftId ?? deps.allocateDraftId();
|
|
115
|
-
state.draftId = draftId;
|
|
116
|
-
try {
|
|
117
|
-
await deps.sendDraft(chatId, draftId, truncated);
|
|
118
|
-
deps.setDraftSupport("supported");
|
|
119
|
-
state.mode = "draft";
|
|
120
|
-
state.lastSentText = truncated;
|
|
121
|
-
return;
|
|
122
|
-
} catch {
|
|
123
|
-
deps.setDraftSupport("unsupported");
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (state.messageId === undefined) {
|
|
127
|
-
const sent = await deps.sendMessage(chatId, truncated);
|
|
128
|
-
state.messageId = sent.message_id;
|
|
129
|
-
state.mode = "message";
|
|
130
|
-
state.lastSentText = truncated;
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
await deps.editMessageText(chatId, state.messageId, truncated);
|
|
134
|
-
state.mode = "message";
|
|
135
|
-
state.lastSentText = truncated;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export async function finalizeTelegramPreview(
|
|
139
|
-
chatId: number,
|
|
140
|
-
deps: TelegramPreviewRuntimeDeps,
|
|
141
|
-
): Promise<boolean> {
|
|
142
|
-
const state = deps.getState();
|
|
143
|
-
if (!state) return false;
|
|
144
|
-
await flushTelegramPreview(chatId, deps);
|
|
145
|
-
const finalText = buildTelegramPreviewFinalText(state);
|
|
146
|
-
if (!finalText) {
|
|
147
|
-
await clearTelegramPreview(chatId, deps);
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
if (state.mode === "draft") {
|
|
151
|
-
await deps.sendMessage(chatId, finalText);
|
|
152
|
-
await clearTelegramPreview(chatId, deps);
|
|
153
|
-
return true;
|
|
154
|
-
}
|
|
155
|
-
deps.setState(undefined);
|
|
156
|
-
return state.messageId !== undefined;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export async function finalizeTelegramMarkdownPreview(
|
|
160
|
-
chatId: number,
|
|
161
|
-
markdown: string,
|
|
162
|
-
deps: TelegramPreviewRuntimeDeps,
|
|
163
|
-
): Promise<boolean> {
|
|
164
|
-
const state = deps.getState();
|
|
165
|
-
if (!state) return false;
|
|
166
|
-
await flushTelegramPreview(chatId, deps);
|
|
167
|
-
const chunks = deps.renderTelegramMessage(markdown, { mode: "markdown" });
|
|
168
|
-
if (chunks.length === 0) {
|
|
169
|
-
await clearTelegramPreview(chatId, deps);
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
if (state.mode === "draft") {
|
|
173
|
-
await deps.sendRenderedChunks(chatId, chunks);
|
|
174
|
-
await clearTelegramPreview(chatId, deps);
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
if (state.messageId === undefined) return false;
|
|
178
|
-
await deps.editRenderedMessage(chatId, state.messageId, chunks);
|
|
179
|
-
deps.setState(undefined);
|
|
180
|
-
return true;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// --- Delivery ---
|
|
184
|
-
|
|
185
8
|
export interface TelegramSentMessageLike {
|
|
186
9
|
message_id: number;
|
|
187
10
|
}
|
|
@@ -278,8 +101,6 @@ export async function editTelegramRenderedMessage<TReplyMarkup>(
|
|
|
278
101
|
return messageId;
|
|
279
102
|
}
|
|
280
103
|
|
|
281
|
-
// --- Reply Runtime ---
|
|
282
|
-
|
|
283
104
|
export interface TelegramReplyRuntimeDeps {
|
|
284
105
|
renderTelegramMessage: (
|
|
285
106
|
text: string,
|
package/lib/turns.ts
CHANGED
|
@@ -89,6 +89,92 @@ export function buildTelegramTurnPrompt(options: {
|
|
|
89
89
|
return prompt;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
function splitTelegramPromptAttachmentSuffix(prompt: string): {
|
|
93
|
+
promptWithoutAttachments: string;
|
|
94
|
+
attachmentSuffix: string;
|
|
95
|
+
attachmentFiles: DownloadedTelegramTurnFileLike[];
|
|
96
|
+
} {
|
|
97
|
+
const marker = "\n\nTelegram attachments were saved locally:";
|
|
98
|
+
const markerIndex = prompt.indexOf(marker);
|
|
99
|
+
if (markerIndex === -1) {
|
|
100
|
+
return {
|
|
101
|
+
promptWithoutAttachments: prompt,
|
|
102
|
+
attachmentSuffix: "",
|
|
103
|
+
attachmentFiles: [],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const promptWithoutAttachments = prompt.slice(0, markerIndex);
|
|
107
|
+
const attachmentSuffix = prompt.slice(markerIndex);
|
|
108
|
+
const attachmentFiles = attachmentSuffix
|
|
109
|
+
.split("\n")
|
|
110
|
+
.map((line) => line.match(/^- (.+)$/)?.[1]?.trim())
|
|
111
|
+
.filter((path): path is string => !!path)
|
|
112
|
+
.map((path) => ({ path, fileName: basename(path), isImage: false }));
|
|
113
|
+
return { promptWithoutAttachments, attachmentSuffix, attachmentFiles };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function buildEditedTelegramPromptText(options: {
|
|
117
|
+
existingPrompt: string;
|
|
118
|
+
telegramPrefix: string;
|
|
119
|
+
rawText: string;
|
|
120
|
+
}): { text: string; attachmentFiles: DownloadedTelegramTurnFileLike[] } {
|
|
121
|
+
const { promptWithoutAttachments, attachmentSuffix, attachmentFiles } =
|
|
122
|
+
splitTelegramPromptAttachmentSuffix(options.existingPrompt);
|
|
123
|
+
const currentMessageMarker = "Current Telegram message:";
|
|
124
|
+
const currentMessageIndex = promptWithoutAttachments.lastIndexOf(
|
|
125
|
+
currentMessageMarker,
|
|
126
|
+
);
|
|
127
|
+
if (currentMessageIndex !== -1) {
|
|
128
|
+
const prefix = promptWithoutAttachments.slice(
|
|
129
|
+
0,
|
|
130
|
+
currentMessageIndex + currentMessageMarker.length,
|
|
131
|
+
);
|
|
132
|
+
const separator = options.rawText.length > 0 ? "\n" : "";
|
|
133
|
+
return {
|
|
134
|
+
text: `${prefix}${separator}${options.rawText}${attachmentSuffix}`,
|
|
135
|
+
attachmentFiles,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const promptText =
|
|
139
|
+
options.rawText.length > 0
|
|
140
|
+
? `${options.telegramPrefix} ${options.rawText}`
|
|
141
|
+
: options.telegramPrefix;
|
|
142
|
+
return {
|
|
143
|
+
text: `${promptText}${attachmentSuffix}`,
|
|
144
|
+
attachmentFiles,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function updateTelegramPromptTurnText(options: {
|
|
149
|
+
turn: PendingTelegramTurn;
|
|
150
|
+
telegramPrefix: string;
|
|
151
|
+
rawText: string;
|
|
152
|
+
}): PendingTelegramTurn {
|
|
153
|
+
let attachmentFiles: DownloadedTelegramTurnFileLike[] = [];
|
|
154
|
+
const nextContent = options.turn.content.map((block, index) => {
|
|
155
|
+
if (index !== 0 || block.type !== "text") return block;
|
|
156
|
+
const updated = buildEditedTelegramPromptText({
|
|
157
|
+
existingPrompt: block.text,
|
|
158
|
+
telegramPrefix: options.telegramPrefix,
|
|
159
|
+
rawText: options.rawText,
|
|
160
|
+
});
|
|
161
|
+
attachmentFiles = updated.attachmentFiles;
|
|
162
|
+
return {
|
|
163
|
+
...block,
|
|
164
|
+
text: updated.text,
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
return {
|
|
168
|
+
...options.turn,
|
|
169
|
+
content: nextContent,
|
|
170
|
+
historyText: formatTelegramHistoryText(options.rawText, attachmentFiles),
|
|
171
|
+
statusSummary: formatTelegramTurnStatusSummary(
|
|
172
|
+
options.rawText,
|
|
173
|
+
attachmentFiles,
|
|
174
|
+
),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
92
178
|
export async function buildTelegramPromptTurn(options: {
|
|
93
179
|
telegramPrefix: string;
|
|
94
180
|
messages: TelegramTurnMessageLike[];
|
package/lib/types.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Bot API transport shape types
|
|
3
|
+
* Centralizes Telegram update, message, callback, reaction, and response shapes used by the runtime
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface TelegramApiResponse<T> {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
result?: T;
|
|
9
|
+
description?: string;
|
|
10
|
+
error_code?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface TelegramUser {
|
|
14
|
+
id: number;
|
|
15
|
+
is_bot: boolean;
|
|
16
|
+
first_name: string;
|
|
17
|
+
username?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TelegramChat {
|
|
21
|
+
id: number;
|
|
22
|
+
type: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TelegramPhotoSize {
|
|
26
|
+
file_id: string;
|
|
27
|
+
file_size?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface TelegramDocument {
|
|
31
|
+
file_id: string;
|
|
32
|
+
file_name?: string;
|
|
33
|
+
mime_type?: string;
|
|
34
|
+
file_size?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface TelegramVideo {
|
|
38
|
+
file_id: string;
|
|
39
|
+
file_name?: string;
|
|
40
|
+
mime_type?: string;
|
|
41
|
+
file_size?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface TelegramAudio {
|
|
45
|
+
file_id: string;
|
|
46
|
+
file_name?: string;
|
|
47
|
+
mime_type?: string;
|
|
48
|
+
file_size?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TelegramVoice {
|
|
52
|
+
file_id: string;
|
|
53
|
+
mime_type?: string;
|
|
54
|
+
file_size?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface TelegramAnimation {
|
|
58
|
+
file_id: string;
|
|
59
|
+
file_name?: string;
|
|
60
|
+
mime_type?: string;
|
|
61
|
+
file_size?: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface TelegramSticker {
|
|
65
|
+
file_id: string;
|
|
66
|
+
emoji?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface TelegramMessage {
|
|
70
|
+
message_id: number;
|
|
71
|
+
chat: TelegramChat;
|
|
72
|
+
from?: TelegramUser;
|
|
73
|
+
text?: string;
|
|
74
|
+
caption?: string;
|
|
75
|
+
media_group_id?: string;
|
|
76
|
+
photo?: TelegramPhotoSize[];
|
|
77
|
+
document?: TelegramDocument;
|
|
78
|
+
video?: TelegramVideo;
|
|
79
|
+
audio?: TelegramAudio;
|
|
80
|
+
voice?: TelegramVoice;
|
|
81
|
+
animation?: TelegramAnimation;
|
|
82
|
+
sticker?: TelegramSticker;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface TelegramCallbackQuery {
|
|
86
|
+
id: string;
|
|
87
|
+
from: TelegramUser;
|
|
88
|
+
message?: TelegramMessage;
|
|
89
|
+
data?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface TelegramReactionTypeEmoji {
|
|
93
|
+
type: "emoji";
|
|
94
|
+
emoji: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface TelegramReactionTypeCustomEmoji {
|
|
98
|
+
type: "custom_emoji";
|
|
99
|
+
custom_emoji_id: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface TelegramReactionTypePaid {
|
|
103
|
+
type: "paid";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type TelegramReactionType =
|
|
107
|
+
| TelegramReactionTypeEmoji
|
|
108
|
+
| TelegramReactionTypeCustomEmoji
|
|
109
|
+
| TelegramReactionTypePaid;
|
|
110
|
+
|
|
111
|
+
export interface TelegramMessageReactionUpdated {
|
|
112
|
+
chat: TelegramChat;
|
|
113
|
+
message_id: number;
|
|
114
|
+
user?: TelegramUser;
|
|
115
|
+
actor_chat?: TelegramChat;
|
|
116
|
+
old_reaction: TelegramReactionType[];
|
|
117
|
+
new_reaction: TelegramReactionType[];
|
|
118
|
+
date: number;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface TelegramUpdate {
|
|
122
|
+
update_id: number;
|
|
123
|
+
message?: TelegramMessage;
|
|
124
|
+
edited_message?: TelegramMessage;
|
|
125
|
+
callback_query?: TelegramCallbackQuery;
|
|
126
|
+
message_reaction?: TelegramMessageReactionUpdated;
|
|
127
|
+
deleted_business_messages?: { message_ids?: unknown };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface TelegramSentMessage {
|
|
131
|
+
message_id: number;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface TelegramBotCommand {
|
|
135
|
+
command: string;
|
|
136
|
+
description: string;
|
|
137
|
+
}
|
package/lib/updates.ts
CHANGED
|
@@ -119,7 +119,22 @@ export function getAuthorizedTelegramCallbackQuery(
|
|
|
119
119
|
export function getAuthorizedTelegramMessage(
|
|
120
120
|
update: TelegramUpdateRoutingLike,
|
|
121
121
|
): TelegramMessageLike | undefined {
|
|
122
|
-
const message = update.message
|
|
122
|
+
const message = update.message;
|
|
123
|
+
if (
|
|
124
|
+
!message ||
|
|
125
|
+
message.chat.type !== "private" ||
|
|
126
|
+
!message.from ||
|
|
127
|
+
message.from.is_bot
|
|
128
|
+
) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
return message;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function getAuthorizedTelegramEditedMessage(
|
|
135
|
+
update: TelegramUpdateRoutingLike,
|
|
136
|
+
): TelegramMessageLike | undefined {
|
|
137
|
+
const message = update.edited_message;
|
|
123
138
|
if (
|
|
124
139
|
!message ||
|
|
125
140
|
message.chat.type !== "private" ||
|
|
@@ -156,6 +171,11 @@ export type TelegramUpdateFlowAction =
|
|
|
156
171
|
kind: "message";
|
|
157
172
|
message: TelegramMessageLike & { from: TelegramUserLike };
|
|
158
173
|
authorization: TelegramAuthorizationState;
|
|
174
|
+
}
|
|
175
|
+
| {
|
|
176
|
+
kind: "edited-message";
|
|
177
|
+
message: TelegramMessageLike & { from: TelegramUserLike };
|
|
178
|
+
authorization: TelegramAuthorizationState;
|
|
159
179
|
};
|
|
160
180
|
|
|
161
181
|
export function buildTelegramUpdateFlowAction(
|
|
@@ -191,6 +211,19 @@ export function buildTelegramUpdateFlowAction(
|
|
|
191
211
|
),
|
|
192
212
|
};
|
|
193
213
|
}
|
|
214
|
+
const editedMessage = getAuthorizedTelegramEditedMessage(update);
|
|
215
|
+
if (editedMessage?.from) {
|
|
216
|
+
return {
|
|
217
|
+
kind: "edited-message",
|
|
218
|
+
message: editedMessage as TelegramMessageLike & {
|
|
219
|
+
from: TelegramUserLike;
|
|
220
|
+
},
|
|
221
|
+
authorization: getTelegramAuthorizationState(
|
|
222
|
+
editedMessage.from.id,
|
|
223
|
+
allowedUserId,
|
|
224
|
+
),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
194
227
|
return { kind: "ignore" };
|
|
195
228
|
}
|
|
196
229
|
|
|
@@ -215,6 +248,12 @@ export type TelegramUpdateExecutionPlan =
|
|
|
215
248
|
shouldPair: boolean;
|
|
216
249
|
shouldNotifyPaired: boolean;
|
|
217
250
|
shouldDeny: boolean;
|
|
251
|
+
}
|
|
252
|
+
| {
|
|
253
|
+
kind: "edited-message";
|
|
254
|
+
message: TelegramMessageLike & { from: TelegramUserLike };
|
|
255
|
+
shouldPair: boolean;
|
|
256
|
+
shouldDeny: boolean;
|
|
218
257
|
};
|
|
219
258
|
|
|
220
259
|
export function buildTelegramUpdateExecutionPlan(
|
|
@@ -242,6 +281,13 @@ export function buildTelegramUpdateExecutionPlan(
|
|
|
242
281
|
shouldNotifyPaired: action.authorization.kind === "pair",
|
|
243
282
|
shouldDeny: action.authorization.kind === "deny",
|
|
244
283
|
};
|
|
284
|
+
case "edited-message":
|
|
285
|
+
return {
|
|
286
|
+
kind: "edited-message",
|
|
287
|
+
message: action.message,
|
|
288
|
+
shouldPair: action.authorization.kind === "pair",
|
|
289
|
+
shouldDeny: action.authorization.kind === "deny",
|
|
290
|
+
};
|
|
245
291
|
}
|
|
246
292
|
}
|
|
247
293
|
|
|
@@ -296,6 +342,13 @@ export interface TelegramUpdateRuntimeDeps {
|
|
|
296
342
|
>["message"],
|
|
297
343
|
ctx: ExtensionContext,
|
|
298
344
|
) => Promise<void>;
|
|
345
|
+
handleAuthorizedTelegramEditedMessage: (
|
|
346
|
+
message: Extract<
|
|
347
|
+
TelegramUpdateExecutionPlan,
|
|
348
|
+
{ kind: "edited-message" }
|
|
349
|
+
>["message"],
|
|
350
|
+
ctx: ExtensionContext,
|
|
351
|
+
) => Promise<void>;
|
|
299
352
|
}
|
|
300
353
|
|
|
301
354
|
function getTelegramCallbackQueryId(
|
|
@@ -368,7 +421,12 @@ export async function executeTelegramUpdatePlan(
|
|
|
368
421
|
? await deps.pairTelegramUserIfNeeded(plan.message.from.id, deps.ctx)
|
|
369
422
|
: false;
|
|
370
423
|
const replyTarget = getTelegramMessageReplyTarget(plan.message);
|
|
371
|
-
if (
|
|
424
|
+
if (
|
|
425
|
+
plan.kind === "message" &&
|
|
426
|
+
pairedNow &&
|
|
427
|
+
plan.shouldNotifyPaired &&
|
|
428
|
+
replyTarget
|
|
429
|
+
) {
|
|
372
430
|
await deps.sendTextReply(
|
|
373
431
|
replyTarget.chatId,
|
|
374
432
|
replyTarget.messageId,
|
|
@@ -385,5 +443,9 @@ export async function executeTelegramUpdatePlan(
|
|
|
385
443
|
}
|
|
386
444
|
return;
|
|
387
445
|
}
|
|
446
|
+
if (plan.kind === "edited-message") {
|
|
447
|
+
await deps.handleAuthorizedTelegramEditedMessage(plan.message, deps.ctx);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
388
450
|
await deps.handleAuthorizedTelegramMessage(plan.message, deps.ctx);
|
|
389
451
|
}
|