@llblab/pi-telegram 0.17.0 → 0.17.2
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 -2
- package/CHANGELOG.md +10 -0
- package/docs/architecture.md +3 -1
- package/lib/media.ts +92 -1
- package/lib/preview.ts +13 -3
- package/lib/prompts.ts +1 -1
- package/lib/replies.ts +59 -4
- package/lib/telegram-api.ts +7 -0
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -68,14 +68,14 @@ The core product loop is mobile continuation: start or supervise work in the ter
|
|
|
68
68
|
- Abort-history mode is scoped to Telegram-owned active turns. Internally this is `foldQueuedPromptsIntoHistory`: local/non-Telegram agent starts after abort clear it so the next Telegram prompt appends to the local queue instead of absorbing older queued turns as history
|
|
69
69
|
- `/start`, `/help`, and `/status` open the unified command-help/status-row/control menu; `/model`, `/thinking`, and `/queue` jump to sections directly; visible bot commands are `/start`, `/compact`, `/next`, `/continue`, `/abort`, `/stop`
|
|
70
70
|
- Command/menu emoji are fixed UI adornments owned by the `commands` map; do not add a persisted emoji toggle or Settings menu until there is a real setting to own
|
|
71
|
-
- Telegram `reply_to_message` context is prompt-only and must not affect slash-command parsing
|
|
71
|
+
- Telegram `reply_to_message` context is prompt-only and must not affect slash-command parsing; when Telegram includes `rich_message` blocks on a quoted rich reply, extract plain text from those blocks before falling back to raw `text`/`caption` so prompt context does not leak raw Rich Markdown source
|
|
72
72
|
- Long-lived timers, pollers, watchers, and deferred queue dispatch must be session-bound and avoid stale live π contexts after session replacement
|
|
73
73
|
- Do not add Telegram commands that imitate Pi interactive session replacement, navigation, or TUI rendering through private internals, ANSI terminal clearing, raw TTY injection, or a shadow `pi` subprocess. Features such as a real Telegram `/new` require a public Pi API that runs the same session-replacement path as the terminal command.
|
|
74
74
|
- In-flight `/model` switching is limited to Telegram-owned active turns; if a tool call is active, abort is delayed until the tool finishes
|
|
75
75
|
|
|
76
76
|
## 5.3 Telegram Delivery Semantics
|
|
77
77
|
|
|
78
|
-
- Assistant and guest replies use Telegram-native Rich Markdown via Rich Message APIs, not Markdown→HTML conversion. Bridge-owned UI surfaces such as commands, menus, status, queue controls, and sections should keep explicit Telegram HTML/plain rendering by default because readability and maintainability are higher there. Companion sections may explicitly choose Markdown, HTML, or plain text per view
|
|
78
|
+
- Assistant and guest replies use Telegram-native Rich Markdown via Rich Message APIs, not Markdown→HTML conversion. Bridge-owned UI surfaces such as commands, menus, status, queue controls, and sections should keep explicit Telegram HTML/plain rendering by default because readability and maintainability are higher there. Companion sections may explicitly choose Markdown, HTML, or plain text per view. Keep native Rich Markdown source close to model-authored Markdown, but normalize Bot-API-fragile equivalents when evidence shows a Telegram parser/client edge, such as space-after-marker blockquotes (`> quote` -> `>quote`) and dollar-prefixed ticker atoms (`$BLDR` -> `\$BLDR`) outside code fences/spans
|
|
79
79
|
- Use `docs/telegram-bot-api-rich-messages.md` as the local Bot API/Rich Messages reference for native Rich Markdown work
|
|
80
80
|
- Formula guidance belongs in the Telegram-turn prompt contract: use `$...$` for inline math and `$$...$$` for block math; backticks intentionally render formulas as literal code
|
|
81
81
|
- Real code blocks must stay literal and escaped
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.17.2: Indented List Rich Markdown Hotfix
|
|
6
|
+
|
|
7
|
+
- `[Rich Markdown]` Neutralized indented list markers before native Rich Markdown delivery by replacing leading list indentation with non-breaking spaces while preserving top-level list markers. Impact: assistant replies with bold section headers followed by two-space-indented bullets containing slash/hyphen text, `with`, and inline code no longer render raw Markdown or truncate around the inline-code span.
|
|
8
|
+
- `[Preview]` Pinned the same normalization path for native draft previews and editable/final Rich Markdown messages. Impact: risky replies use consistent Markdown safety behavior across draft, fallback edit, and final delivery paths.
|
|
9
|
+
- `[Tests]` Added regression coverage for the discovered formatting-only and truncation-risk fixtures, including payload-tail preservation through native Markdown splitting and send delivery. Impact: future Rich Markdown parser changes are less likely to reintroduce partial Telegram messages.
|
|
10
|
+
|
|
11
|
+
## 0.17.1: Rich Markdown Parser Hotfix
|
|
12
|
+
|
|
13
|
+
- `[Rich Markdown]` Normalize Bot-API-fragile source before native Rich Markdown delivery, including space-after-marker blockquotes and dollar-prefixed ticker atoms such as `$BLDR` / `$NTVE`, prefer Telegram `rich_message` blocks over raw `text`/`caption` when extracting quoted reply context for prompts, and keep Telegram copyability guidance generic by recommending inline code for short copyable literals. Impact: assistant replies are less likely to render as raw Markdown on Telegram Rich Message parser/client edges, replying to a native Rich Markdown bot message no longer injects raw Markdown source into `[reply]`, and prompts nudge copyable identifiers without ticker-specific bloat.
|
|
14
|
+
|
|
5
15
|
## 0.17.0: Native Rich Markdown Delivery
|
|
6
16
|
|
|
7
17
|
- `[Rich Markdown]` Assistant and guest replies now use Telegram-native Rich Message APIs directly: final assistant Markdown goes through `sendRichMessage`, streaming drafts go through `sendRichMessageDraft`, editable fallback previews finalize through `editMessageText.rich_message`, and guest replies use `InputRichMessageContent`. Impact: model-authored Markdown reaches Telegram as native Rich Markdown instead of passing through the legacy Markdown-to-HTML assistant path.
|
package/docs/architecture.md
CHANGED
|
@@ -52,7 +52,7 @@ The repository uses a **Flat Domain DAG**:
|
|
|
52
52
|
- `config` / `setup`: `telegram.json`, bot token setup, first-user pairing, authorization, env fallback, atomic persistence, and live config accessors.
|
|
53
53
|
- `locks` / `polling`: singleton polling ownership, takeover/restart behavior, long-poll controller state, offset persistence, and poll-loop wiring.
|
|
54
54
|
- `updates` / `routing`: update classification, authorization planning, callbacks, edited messages, reactions, and inbound route composition.
|
|
55
|
-
- `media` / `text-groups` / `time-injection` / `turns` / `inbound`: inbound text/media/file extraction, media-group debounce, long-text coalescing, optional `[time]` context, handler execution, and prompt-turn assembly/editing.
|
|
55
|
+
- `media` / `text-groups` / `time-injection` / `turns` / `inbound`: inbound text/media/file extraction, rich-message reply-context plaintext recovery, media-group debounce, long-text coalescing, optional `[time]` context, handler execution, and prompt-turn assembly/editing.
|
|
56
56
|
- `queue`: queue item contracts, lane admission/order, readiness gates, mutations, dispatch runtime, prompt/control enqueueing, and session/agent/tool lifecycle sequencing.
|
|
57
57
|
- `runtime`: session-local coordination primitives: counters, flags, setup guard, abort handler, typing timers, dispatch flags, and reset binding.
|
|
58
58
|
- `model` / `menu-model` / `menu-thinking` / `menu-status` / `menu-queue` / `menu-settings` / `menu` / `commands`: model identity, thinking levels, scoped model handling, menu render/callback behavior, slash commands, bot commands, and interactive controls.
|
|
@@ -196,6 +196,8 @@ Assistant replies use Telegram-native Rich Markdown. Final Markdown is sent dire
|
|
|
196
196
|
Assistant delivery guarantees:
|
|
197
197
|
|
|
198
198
|
- Model-authored Markdown is the source of truth; the bridge does not pre-render assistant Markdown to HTML.
|
|
199
|
+
- Before native Rich Markdown delivery, the bridge normalizes known Bot-API-fragile source forms without changing visible meaning, including space-after-marker blockquotes and dollar-prefixed ticker atoms that Telegram may otherwise treat as unterminated math.
|
|
200
|
+
- Quoted rich replies use Telegram `rich_message` blocks as the prompt-context source when available, so `[reply]` context receives rendered plain text instead of raw `InputRichMessage.markdown` fallback text.
|
|
199
201
|
- Long native Markdown replies are split only at Telegram Rich Message transport limits.
|
|
200
202
|
- Streaming previews pass assistant Markdown through to `sendRichMessageDraft` with ownership checks, voice suppression, serialized flushes, and an editable plain-message fallback when draft delivery is unavailable.
|
|
201
203
|
- Preview flushes are serialized so older edits cannot race newer drafts; final delivery waits for active draft flushes and does not perform a post-final draft-clear call.
|
package/lib/media.ts
CHANGED
|
@@ -29,10 +29,15 @@ export interface TelegramVoice {
|
|
|
29
29
|
mime_type?: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export interface TelegramRichMessage {
|
|
33
|
+
blocks?: unknown[];
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
export interface TelegramReplyToMessage {
|
|
33
37
|
message_id?: number;
|
|
34
38
|
text?: string;
|
|
35
39
|
caption?: string;
|
|
40
|
+
rich_message?: TelegramRichMessage;
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
export interface TelegramSticker {
|
|
@@ -43,6 +48,7 @@ export interface TelegramMediaMessage {
|
|
|
43
48
|
message_id: number;
|
|
44
49
|
text?: string;
|
|
45
50
|
caption?: string;
|
|
51
|
+
rich_message?: TelegramRichMessage;
|
|
46
52
|
reply_to_message?: TelegramReplyToMessage;
|
|
47
53
|
media_group_id?: string;
|
|
48
54
|
photo?: TelegramPhotoSize[];
|
|
@@ -173,10 +179,94 @@ function isImageMimeType(mimeType: string | undefined): boolean {
|
|
|
173
179
|
return mimeType?.toLowerCase().startsWith("image/") ?? false;
|
|
174
180
|
}
|
|
175
181
|
|
|
182
|
+
function getObjectField(value: unknown, field: string): unknown {
|
|
183
|
+
if (typeof value !== "object" || value === null || !(field in value)) {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
return Reflect.get(value, field);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function joinRichTextParts(parts: string[], separator = ""): string {
|
|
190
|
+
return parts.filter(Boolean).join(separator).trim();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function extractTelegramRichText(value: unknown): string {
|
|
194
|
+
if (typeof value === "string") return value;
|
|
195
|
+
if (Array.isArray(value)) {
|
|
196
|
+
return joinRichTextParts(value.map(extractTelegramRichText));
|
|
197
|
+
}
|
|
198
|
+
if (typeof value !== "object" || value === null) return "";
|
|
199
|
+
const text = getObjectField(value, "text");
|
|
200
|
+
if (text !== undefined) return extractTelegramRichText(text);
|
|
201
|
+
const expression = getObjectField(value, "expression");
|
|
202
|
+
if (typeof expression === "string") return expression;
|
|
203
|
+
const alternativeText = getObjectField(value, "alternative_text");
|
|
204
|
+
if (typeof alternativeText === "string") return alternativeText;
|
|
205
|
+
return "";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function extractTelegramRichBlockText(block: unknown): string {
|
|
209
|
+
if (typeof block !== "object" || block === null) return "";
|
|
210
|
+
const directText = extractTelegramRichText(getObjectField(block, "text"));
|
|
211
|
+
if (directText) return directText;
|
|
212
|
+
const summary = extractTelegramRichText(getObjectField(block, "summary"));
|
|
213
|
+
const nestedBlocks = extractTelegramRichMessageBlocksText(
|
|
214
|
+
getObjectField(block, "blocks"),
|
|
215
|
+
);
|
|
216
|
+
const items = getObjectField(block, "items");
|
|
217
|
+
const itemText = Array.isArray(items)
|
|
218
|
+
? items
|
|
219
|
+
.map((item) => {
|
|
220
|
+
const label = getObjectField(item, "label");
|
|
221
|
+
const body = extractTelegramRichMessageBlocksText(
|
|
222
|
+
getObjectField(item, "blocks"),
|
|
223
|
+
);
|
|
224
|
+
return typeof label === "string" && body ? `${label} ${body}` : body;
|
|
225
|
+
})
|
|
226
|
+
.filter(Boolean)
|
|
227
|
+
.join("\n")
|
|
228
|
+
: "";
|
|
229
|
+
const cells = getObjectField(block, "cells");
|
|
230
|
+
const cellText = Array.isArray(cells)
|
|
231
|
+
? cells
|
|
232
|
+
.map((row) =>
|
|
233
|
+
Array.isArray(row)
|
|
234
|
+
? row
|
|
235
|
+
.map((cell) => extractTelegramRichText(getObjectField(cell, "text")))
|
|
236
|
+
.filter(Boolean)
|
|
237
|
+
.join(" | ")
|
|
238
|
+
: "",
|
|
239
|
+
)
|
|
240
|
+
.filter(Boolean)
|
|
241
|
+
.join("\n")
|
|
242
|
+
: "";
|
|
243
|
+
const caption = extractTelegramRichText(getObjectField(block, "caption"));
|
|
244
|
+
return joinRichTextParts(
|
|
245
|
+
[summary, nestedBlocks, itemText, cellText, caption],
|
|
246
|
+
"\n",
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function extractTelegramRichMessageBlocksText(blocks: unknown): string {
|
|
251
|
+
if (!Array.isArray(blocks)) return "";
|
|
252
|
+
return joinRichTextParts(blocks.map(extractTelegramRichBlockText), "\n\n");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function extractTelegramRichMessageText(
|
|
256
|
+
richMessage: TelegramRichMessage | undefined,
|
|
257
|
+
): string {
|
|
258
|
+
return extractTelegramRichMessageBlocksText(richMessage?.blocks);
|
|
259
|
+
}
|
|
260
|
+
|
|
176
261
|
export function extractTelegramMessageText(
|
|
177
262
|
message: TelegramMediaMessage,
|
|
178
263
|
): string {
|
|
179
|
-
return (
|
|
264
|
+
return (
|
|
265
|
+
extractTelegramRichMessageText(message.rich_message) ||
|
|
266
|
+
message.text ||
|
|
267
|
+
message.caption ||
|
|
268
|
+
""
|
|
269
|
+
).trim();
|
|
180
270
|
}
|
|
181
271
|
|
|
182
272
|
function truncateTelegramReplyContextText(text: string): string {
|
|
@@ -188,6 +278,7 @@ export function extractTelegramReplyContextText(
|
|
|
188
278
|
message: TelegramMediaMessage,
|
|
189
279
|
): string {
|
|
190
280
|
const quoted = (
|
|
281
|
+
extractTelegramRichMessageText(message.reply_to_message?.rich_message) ||
|
|
191
282
|
message.reply_to_message?.text ||
|
|
192
283
|
message.reply_to_message?.caption ||
|
|
193
284
|
""
|
package/lib/preview.ts
CHANGED
|
@@ -10,7 +10,10 @@ import type {
|
|
|
10
10
|
TelegramSendMessageBody,
|
|
11
11
|
TelegramSentMessage,
|
|
12
12
|
} from "./telegram-api.ts";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
buildTelegramReplyParameters,
|
|
15
|
+
normalizeTelegramNativeMarkdown,
|
|
16
|
+
} from "./replies.ts";
|
|
14
17
|
import { stripTelegramCommentMarkupForPreview } from "./outbound.ts";
|
|
15
18
|
import { shouldSuppressPreviewForVoice } from "./voice.ts";
|
|
16
19
|
|
|
@@ -298,7 +301,10 @@ export function createTelegramNativeMarkdownMessageEditor<TReplyMarkup>(deps: {
|
|
|
298
301
|
deps.editMessageText({
|
|
299
302
|
chat_id: chatId,
|
|
300
303
|
message_id: messageId,
|
|
301
|
-
rich_message: {
|
|
304
|
+
rich_message: {
|
|
305
|
+
markdown: normalizeTelegramNativeMarkdown(markdown),
|
|
306
|
+
skip_entity_detection: true,
|
|
307
|
+
},
|
|
302
308
|
...(options?.replyMarkup ? { reply_markup: options.replyMarkup } : {}),
|
|
303
309
|
});
|
|
304
310
|
}
|
|
@@ -632,7 +638,11 @@ async function performTelegramPreviewFlush<
|
|
|
632
638
|
const draftId = state.draftId ?? deps.allocateDraftId();
|
|
633
639
|
state.draftId = draftId;
|
|
634
640
|
try {
|
|
635
|
-
await deps.sendDraft(
|
|
641
|
+
await deps.sendDraft(
|
|
642
|
+
chatId,
|
|
643
|
+
draftId,
|
|
644
|
+
normalizeTelegramNativeMarkdown(snapshot.sourceText),
|
|
645
|
+
);
|
|
636
646
|
deps.setDraftSupport("supported");
|
|
637
647
|
state.mode = "draft";
|
|
638
648
|
state.lastSentText = snapshot.text;
|
package/lib/prompts.ts
CHANGED
|
@@ -30,7 +30,7 @@ Telegram-originated turn context:
|
|
|
30
30
|
Telegram-visible output:
|
|
31
31
|
- Telegram is mobile-first: keep answers easy to scan, use headings/lists when useful, and avoid unnecessarily huge blocks of text.
|
|
32
32
|
- For formulas, use math delimiters like \`$E = mc^2$\` for inline formulas and \`$$\\nE = mc^2\\n$$\` for block formulas; do not wrap formulas in backticks unless they should render as literal code.
|
|
33
|
-
-
|
|
33
|
+
- Use inline code for short copyable literals (commands, paths, IDs, symbols); avoid wide monospace blocks unless structure or literal code requires them.
|
|
34
34
|
- For requested/generated files, call \`telegram_attach(local_path)\`; during Telegram turns it attaches files to the active reply, and during explicit local/TUI Telegram-delivery requests it sends files directly to the paired/default chat or an explicit \`chat_id\`. If a local/TUI user explicitly asks to send a text message to Telegram, use \`telegram_message\` with Markdown text; embed the same top-level \`telegram_button\` comments when inline prompt buttons are needed, because Telegram buttons must belong to a message. Direct local/TUI Telegram delivery requires this π instance to own \`/telegram-connect\`; if ownership is elsewhere, connect/take over first instead of bypassing the lock.
|
|
35
35
|
|
|
36
36
|
Native outbound actions:
|
package/lib/replies.ts
CHANGED
|
@@ -257,17 +257,72 @@ export async function sendTelegramPlainReply(
|
|
|
257
257
|
return deps.sendRenderedChunks(chunks);
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
function normalizeIndentedTelegramNativeMarkdownList(line: string): string {
|
|
261
|
+
return line.replace(
|
|
262
|
+
/^( +|\t+)([-*+] |\d+\. )/,
|
|
263
|
+
(_match, indent: string, marker: string) => {
|
|
264
|
+
const visibleIndent = indent
|
|
265
|
+
.replace(/ /g, "\u00A0")
|
|
266
|
+
.replace(/\t/g, "\u00A0\u00A0");
|
|
267
|
+
return `${visibleIndent}${marker}`;
|
|
268
|
+
},
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function normalizeTelegramNativeMarkdownLine(line: string): string {
|
|
273
|
+
let result = normalizeIndentedTelegramNativeMarkdownList(
|
|
274
|
+
line.replace(/^( {0,3}>)[ \t]/, "$1"),
|
|
275
|
+
);
|
|
276
|
+
const codeSpans: string[] = [];
|
|
277
|
+
result = result.replace(/`+[^`]*`+/g, (code) => {
|
|
278
|
+
const token = `\u0000${codeSpans.length}\u0000`;
|
|
279
|
+
codeSpans.push(code);
|
|
280
|
+
return token;
|
|
281
|
+
});
|
|
282
|
+
result = result.replace(
|
|
283
|
+
/(^|[^\\$])\$([A-Z][A-Z0-9]{1,})(?!\$)(?=\b|[.,;:)/-])/g,
|
|
284
|
+
(_match, prefix: string, ticker: string) => `${prefix}\\$${ticker}`,
|
|
285
|
+
);
|
|
286
|
+
return result.replace(/\u0000(\d+)\u0000/g, (_match, index) => codeSpans[Number(index)] ?? "");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function normalizeTelegramNativeMarkdown(markdown: string): string {
|
|
290
|
+
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
291
|
+
let fence: { marker: "`" | "~"; length: number } | undefined;
|
|
292
|
+
return lines
|
|
293
|
+
.map((line) => {
|
|
294
|
+
const fenceMatch = line.match(/^ {0,3}(`{3,}|~{3,})/);
|
|
295
|
+
const inFence = fence !== undefined;
|
|
296
|
+
if (!inFence && fenceMatch) {
|
|
297
|
+
const markerText = fenceMatch[1] ?? "```";
|
|
298
|
+
fence = { marker: markerText[0] as "`" | "~", length: markerText.length };
|
|
299
|
+
return line;
|
|
300
|
+
}
|
|
301
|
+
if (
|
|
302
|
+
inFence &&
|
|
303
|
+
new RegExp(`^ {0,3}${fence?.marker}{${fence?.length},}\\s*$`).test(line)
|
|
304
|
+
) {
|
|
305
|
+
fence = undefined;
|
|
306
|
+
return line;
|
|
307
|
+
}
|
|
308
|
+
if (!inFence) return normalizeTelegramNativeMarkdownLine(line);
|
|
309
|
+
return line;
|
|
310
|
+
})
|
|
311
|
+
.join("\n");
|
|
312
|
+
}
|
|
313
|
+
|
|
260
314
|
export function splitTelegramNativeMarkdown(markdown: string): string[] {
|
|
315
|
+
const normalizedMarkdown = normalizeTelegramNativeMarkdown(markdown);
|
|
261
316
|
if (
|
|
262
|
-
|
|
263
|
-
countTelegramNativeMarkdownBlocks(
|
|
317
|
+
normalizedMarkdown.length <= TELEGRAM_RICH_MESSAGE_MAX_CHARS &&
|
|
318
|
+
countTelegramNativeMarkdownBlocks(normalizedMarkdown) <= TELEGRAM_RICH_MESSAGE_MAX_BLOCKS
|
|
264
319
|
) {
|
|
265
|
-
return [
|
|
320
|
+
return [normalizedMarkdown];
|
|
266
321
|
}
|
|
267
322
|
const chunks: string[] = [];
|
|
268
323
|
let current = "";
|
|
269
324
|
let currentBlockCount = 0;
|
|
270
|
-
for (const rawBlock of splitTelegramNativeMarkdownBlocks(
|
|
325
|
+
for (const rawBlock of splitTelegramNativeMarkdownBlocks(normalizedMarkdown)) {
|
|
271
326
|
for (const block of splitTelegramNativeMarkdownCountedBlocks(rawBlock)) {
|
|
272
327
|
const blockCount = countTelegramNativeMarkdownBlocks(block);
|
|
273
328
|
const candidate = current ? `${current}\n\n${block}` : block;
|
package/lib/telegram-api.ts
CHANGED
|
@@ -101,12 +101,18 @@ export interface TelegramSticker {
|
|
|
101
101
|
emoji?: string;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
export interface TelegramRichMessage {
|
|
105
|
+
blocks?: unknown[];
|
|
106
|
+
is_rtl?: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
104
109
|
export interface TelegramMessage {
|
|
105
110
|
message_id: number;
|
|
106
111
|
chat: TelegramChat;
|
|
107
112
|
from?: TelegramUser;
|
|
108
113
|
text?: string;
|
|
109
114
|
caption?: string;
|
|
115
|
+
rich_message?: TelegramRichMessage;
|
|
110
116
|
media_group_id?: string;
|
|
111
117
|
photo?: TelegramPhotoSize[];
|
|
112
118
|
document?: TelegramDocument;
|
|
@@ -160,6 +166,7 @@ export interface TelegramGuestMessage {
|
|
|
160
166
|
date: number;
|
|
161
167
|
text?: string;
|
|
162
168
|
caption?: string;
|
|
169
|
+
rich_message?: TelegramRichMessage;
|
|
163
170
|
guest_query_id: string;
|
|
164
171
|
guest_bot_caller_user?: TelegramUser;
|
|
165
172
|
guest_bot_caller_chat?: TelegramChat;
|