@llblab/pi-telegram 0.46.0 → 0.47.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.
Files changed (199) hide show
  1. package/BACKLOG.md +0 -1
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +2 -2
  4. package/dist/api/activity.d.ts +6 -0
  5. package/dist/api/activity.js +6 -0
  6. package/dist/api/commands.d.ts +6 -0
  7. package/dist/api/commands.js +6 -0
  8. package/dist/api/delivery.d.ts +6 -0
  9. package/dist/api/delivery.js +6 -0
  10. package/dist/api/inbound.d.ts +6 -0
  11. package/dist/api/inbound.js +6 -0
  12. package/dist/api/keyboard.d.ts +6 -0
  13. package/dist/api/keyboard.js +6 -0
  14. package/dist/api/outbound.d.ts +6 -0
  15. package/dist/api/outbound.js +6 -0
  16. package/dist/api/sections.d.ts +7 -0
  17. package/dist/api/sections.js +6 -0
  18. package/dist/api/status.d.ts +6 -0
  19. package/dist/api/status.js +6 -0
  20. package/dist/api/updates.d.ts +6 -0
  21. package/dist/api/updates.js +6 -0
  22. package/dist/api/voice.d.ts +6 -0
  23. package/dist/api/voice.js +6 -0
  24. package/dist/index.d.ts +6 -0
  25. package/dist/index.js +6 -0
  26. package/dist/lib/activity-verbosity.d.ts +52 -0
  27. package/dist/lib/activity-verbosity.js +596 -0
  28. package/dist/lib/activity.d.ts +220 -0
  29. package/dist/lib/activity.js +574 -0
  30. package/dist/lib/agent-messages.d.ts +28 -0
  31. package/dist/lib/agent-messages.js +86 -0
  32. package/dist/lib/bindings.d.ts +250 -0
  33. package/dist/lib/bindings.js +936 -0
  34. package/dist/lib/bus-api.d.ts +18 -0
  35. package/dist/lib/bus-api.js +254 -0
  36. package/dist/lib/bus-follower.d.ts +464 -0
  37. package/dist/lib/bus-follower.js +1686 -0
  38. package/dist/lib/bus-leader.d.ts +292 -0
  39. package/dist/lib/bus-leader.js +2242 -0
  40. package/dist/lib/bus-transport.d.ts +64 -0
  41. package/dist/lib/bus-transport.js +140 -0
  42. package/dist/lib/bus.d.ts +518 -0
  43. package/dist/lib/bus.js +2055 -0
  44. package/dist/lib/channel-posts.d.ts +170 -0
  45. package/dist/lib/channel-posts.js +611 -0
  46. package/dist/lib/command-templates.d.ts +70 -0
  47. package/dist/lib/command-templates.js +744 -0
  48. package/dist/lib/commands.d.ts +541 -0
  49. package/dist/lib/commands.js +1155 -0
  50. package/dist/lib/config.d.ts +252 -0
  51. package/dist/lib/config.js +889 -0
  52. package/dist/lib/delivery.d.ts +163 -0
  53. package/dist/lib/delivery.js +542 -0
  54. package/dist/lib/extension.d.ts +7 -0
  55. package/dist/lib/extension.js +1608 -0
  56. package/dist/lib/generative-app-worker.mjs +104 -0
  57. package/dist/lib/generative-apps.d.ts +212 -0
  58. package/dist/lib/generative-apps.js +1006 -0
  59. package/dist/lib/inbound.d.ts +91 -0
  60. package/dist/lib/inbound.js +502 -0
  61. package/dist/lib/journal.d.ts +665 -0
  62. package/dist/lib/journal.js +3701 -0
  63. package/dist/lib/keyboard.d.ts +23 -0
  64. package/dist/lib/keyboard.js +37 -0
  65. package/dist/lib/lifecycle.d.ts +157 -0
  66. package/dist/lib/lifecycle.js +395 -0
  67. package/dist/lib/locks.d.ts +164 -0
  68. package/dist/lib/locks.js +1208 -0
  69. package/dist/lib/logging.d.ts +50 -0
  70. package/dist/lib/logging.js +274 -0
  71. package/dist/lib/media.d.ts +181 -0
  72. package/dist/lib/media.js +602 -0
  73. package/dist/lib/menu-model.d.ts +217 -0
  74. package/dist/lib/menu-model.js +663 -0
  75. package/dist/lib/menu-queue.d.ts +37 -0
  76. package/dist/lib/menu-queue.js +408 -0
  77. package/dist/lib/menu-settings.d.ts +116 -0
  78. package/dist/lib/menu-settings.js +595 -0
  79. package/dist/lib/menu-status.d.ts +32 -0
  80. package/dist/lib/menu-status.js +112 -0
  81. package/dist/lib/menu-thinking.d.ts +29 -0
  82. package/dist/lib/menu-thinking.js +82 -0
  83. package/dist/lib/menu.d.ts +171 -0
  84. package/dist/lib/menu.js +323 -0
  85. package/dist/lib/model.d.ts +127 -0
  86. package/dist/lib/model.js +409 -0
  87. package/dist/lib/outbound-attachments.d.ts +241 -0
  88. package/dist/lib/outbound-attachments.js +639 -0
  89. package/dist/lib/outbound-buttons.d.ts +69 -0
  90. package/dist/lib/outbound-buttons.js +248 -0
  91. package/dist/lib/outbound-markup.d.ts +42 -0
  92. package/dist/lib/outbound-markup.js +678 -0
  93. package/dist/lib/outbound-voice.d.ts +55 -0
  94. package/dist/lib/outbound-voice.js +152 -0
  95. package/dist/lib/outbound.d.ts +185 -0
  96. package/dist/lib/outbound.js +516 -0
  97. package/dist/lib/ownership.d.ts +77 -0
  98. package/dist/lib/ownership.js +174 -0
  99. package/dist/lib/paths.d.ts +40 -0
  100. package/dist/lib/paths.js +94 -0
  101. package/dist/lib/pi.d.ts +72 -0
  102. package/dist/lib/pi.js +121 -0
  103. package/dist/lib/polling.d.ts +351 -0
  104. package/dist/lib/polling.js +1196 -0
  105. package/dist/lib/preview.d.ts +192 -0
  106. package/dist/lib/preview.js +614 -0
  107. package/dist/lib/prompt-templates.d.ts +24 -0
  108. package/dist/lib/prompt-templates.js +118 -0
  109. package/dist/lib/prompts.d.ts +57 -0
  110. package/dist/lib/prompts.js +167 -0
  111. package/dist/lib/queue.d.ts +766 -0
  112. package/dist/lib/queue.js +1965 -0
  113. package/dist/lib/recovery.d.ts +86 -0
  114. package/dist/lib/recovery.js +285 -0
  115. package/dist/lib/rendering.d.ts +20 -0
  116. package/dist/lib/rendering.js +983 -0
  117. package/dist/lib/replies.d.ts +214 -0
  118. package/dist/lib/replies.js +737 -0
  119. package/dist/lib/routing.d.ts +207 -0
  120. package/dist/lib/routing.js +2282 -0
  121. package/dist/lib/runtime.d.ts +172 -0
  122. package/dist/lib/runtime.js +402 -0
  123. package/dist/lib/sections.d.ts +165 -0
  124. package/dist/lib/sections.js +327 -0
  125. package/dist/lib/setup.d.ts +82 -0
  126. package/dist/lib/setup.js +150 -0
  127. package/dist/lib/skills.d.ts +8 -0
  128. package/dist/lib/skills.js +12 -0
  129. package/dist/lib/status.d.ts +442 -0
  130. package/dist/lib/status.js +1006 -0
  131. package/dist/lib/sync.d.ts +179 -0
  132. package/dist/lib/sync.js +782 -0
  133. package/dist/lib/target.d.ts +20 -0
  134. package/dist/lib/target.js +27 -0
  135. package/dist/lib/telegram-api.d.ts +541 -0
  136. package/dist/lib/telegram-api.js +1159 -0
  137. package/dist/lib/text-groups.d.ts +89 -0
  138. package/dist/lib/text-groups.js +317 -0
  139. package/dist/lib/thread-cleanup-manager.d.ts +288 -0
  140. package/dist/lib/thread-cleanup-manager.js +560 -0
  141. package/dist/lib/thread-display.d.ts +46 -0
  142. package/dist/lib/thread-display.js +257 -0
  143. package/dist/lib/thread-naming.d.ts +46 -0
  144. package/dist/lib/thread-naming.js +78 -0
  145. package/dist/lib/thread-reconciler.d.ts +239 -0
  146. package/dist/lib/thread-reconciler.js +644 -0
  147. package/dist/lib/threads.d.ts +621 -0
  148. package/dist/lib/threads.js +3679 -0
  149. package/dist/lib/time-injection.d.ts +15 -0
  150. package/dist/lib/time-injection.js +56 -0
  151. package/dist/lib/turns.d.ts +109 -0
  152. package/dist/lib/turns.js +500 -0
  153. package/dist/lib/updates.d.ts +1290 -0
  154. package/dist/lib/updates.js +3634 -0
  155. package/dist/lib/voice.d.ts +117 -0
  156. package/dist/lib/voice.js +174 -0
  157. package/dist/lib/workspace-admission.d.ts +264 -0
  158. package/dist/lib/workspace-admission.js +1136 -0
  159. package/dist/lib/workspace-retirement.d.ts +219 -0
  160. package/dist/lib/workspace-retirement.js +587 -0
  161. package/dist/lib/workspace-slots.d.ts +30 -0
  162. package/dist/lib/workspace-slots.js +54 -0
  163. package/dist/package.json +126 -0
  164. package/dist/pi-telegram/index.js +1 -0
  165. package/dist/skills/generated-control-surface/SKILL.md +107 -0
  166. package/dist/skills/generated-control-surface/references/capability-adapters.md +27 -0
  167. package/dist/skills/generated-control-surface/references/layout-and-state.md +37 -0
  168. package/dist/skills/generative-apps/SKILL.md +115 -0
  169. package/dist/skills/show-me/SKILL.md +166 -0
  170. package/dist/skills/show-me/references/telegram-surfaces.md +43 -0
  171. package/dist/skills/telegram-bridge/SKILL.md +129 -0
  172. package/dist/skills/telegram-bridge/references/configuration.md +15 -0
  173. package/dist/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
  174. package/dist/skills/telegram-bridge/references/diagnosis.md +18 -0
  175. package/docs/architecture.md +1 -1
  176. package/docs/compact-matrix-literal.md +10 -3
  177. package/docs/generative-apps.md +15 -13
  178. package/docs/multi-instance-bus.md +4 -2
  179. package/docs/outbound.md +1 -1
  180. package/docs/public-api.md +2 -2
  181. package/lib/bindings.ts +89 -6
  182. package/lib/bus-follower.ts +7 -3
  183. package/lib/bus-leader.ts +44 -19
  184. package/lib/bus.ts +4 -1
  185. package/lib/commands.ts +2 -2
  186. package/lib/config.ts +14 -7
  187. package/lib/delivery.ts +38 -6
  188. package/lib/extension.ts +19 -0
  189. package/lib/generative-apps.ts +379 -13
  190. package/lib/menu-settings.ts +21 -5
  191. package/lib/outbound-markup.ts +16 -7
  192. package/lib/telegram-api.ts +12 -1
  193. package/lib/thread-display.ts +96 -18
  194. package/package.json +56 -13
  195. package/scripts/build-dist.mjs +44 -0
  196. package/scripts/measure-bus.mjs +8 -1
  197. package/scripts/measure-workspace.mjs +8 -1
  198. package/skills/generative-apps/SKILL.md +1 -1
  199. package/skills/telegram-bridge/SKILL.md +1 -1
@@ -0,0 +1,983 @@
1
+ /**
2
+ * Telegram UI/compat rendering helpers
3
+ * Zones: telegram rendering, shared text utils
4
+ * Converts bridge-owned UI/status/menu/interactive text into Telegram-safe plain text and HTML chunks with chunk-boundary handling
5
+ */
6
+ export const MAX_MESSAGE_LENGTH = 4096;
7
+ const TELEGRAM_TABLE_GRAPHEME_SEGMENTER = typeof Intl.Segmenter === "function"
8
+ ? new Intl.Segmenter(undefined, { granularity: "grapheme" })
9
+ : undefined;
10
+ const TELEGRAM_TABLE_EMOJI_GRAPHEME_PATTERN = /\p{Extended_Pictographic}|\p{Emoji_Presentation}|\p{Regional_Indicator}/u;
11
+ const TELEGRAM_VOID_HTML_TAGS = new Set(["br", "hr"]);
12
+ export function escapeHtml(text) {
13
+ return text
14
+ .replace(/&/g, "&")
15
+ .replace(/</g, "&lt;")
16
+ .replace(/>/g, "&gt;");
17
+ }
18
+ export function escapeHtmlAttribute(text) {
19
+ return escapeHtml(text).replace(/"/g, "&quot;").replace(/'/g, "&#39;");
20
+ }
21
+ function getHtmlTagName(tag) {
22
+ return tag.match(/^<\/?\s*([a-zA-Z][\w-]*)/)?.[1]?.toLowerCase();
23
+ }
24
+ function isHtmlClosingTag(tag) {
25
+ return /^<\//.test(tag);
26
+ }
27
+ function isHtmlSelfClosingTag(tag) {
28
+ return /\/\s*>$/.test(tag);
29
+ }
30
+ function getHtmlClosingTags(openTags) {
31
+ return [...openTags]
32
+ .reverse()
33
+ .map((tag) => `</${tag.name}>`)
34
+ .join("");
35
+ }
36
+ function getHtmlOpeningTags(openTags) {
37
+ return openTags.map((tag) => tag.openTag).join("");
38
+ }
39
+ function updateOpenHtmlTags(tag, openTags) {
40
+ const name = getHtmlTagName(tag);
41
+ if (!name || TELEGRAM_VOID_HTML_TAGS.has(name))
42
+ return;
43
+ if (isHtmlClosingTag(tag)) {
44
+ const index = openTags.map((openTag) => openTag.name).lastIndexOf(name);
45
+ if (index !== -1)
46
+ openTags.splice(index, 1);
47
+ return;
48
+ }
49
+ if (isHtmlSelfClosingTag(tag))
50
+ return;
51
+ openTags.push({ name, openTag: tag });
52
+ }
53
+ export function chunkHtmlPreservingTags(html, maxLength) {
54
+ if (html.length <= maxLength)
55
+ return [html];
56
+ const chunks = [];
57
+ const openTags = [];
58
+ const tagPattern = /<\/?[a-zA-Z][^>]*>/g;
59
+ let current = "";
60
+ let index = 0;
61
+ const flushCurrent = () => {
62
+ if (current.length === 0)
63
+ return;
64
+ chunks.push(`${current}${getHtmlClosingTags(openTags)}`);
65
+ current = getHtmlOpeningTags(openTags);
66
+ };
67
+ const appendText = (text) => {
68
+ let remaining = text;
69
+ while (remaining.length > 0) {
70
+ const closingTags = getHtmlClosingTags(openTags);
71
+ const available = maxLength - current.length - closingTags.length;
72
+ if (available <= 0) {
73
+ flushCurrent();
74
+ continue;
75
+ }
76
+ const slice = remaining.slice(0, available);
77
+ current += slice;
78
+ remaining = remaining.slice(slice.length);
79
+ if (remaining.length > 0)
80
+ flushCurrent();
81
+ }
82
+ };
83
+ const appendTag = (tag) => {
84
+ const closingTags = isHtmlClosingTag(tag)
85
+ ? ""
86
+ : getHtmlClosingTags(openTags);
87
+ if (current.length + tag.length + closingTags.length > maxLength) {
88
+ flushCurrent();
89
+ }
90
+ current += tag;
91
+ updateOpenHtmlTags(tag, openTags);
92
+ };
93
+ for (const match of html.matchAll(tagPattern)) {
94
+ appendText(html.slice(index, match.index));
95
+ appendTag(match[0]);
96
+ index = match.index + match[0].length;
97
+ }
98
+ appendText(html.slice(index));
99
+ if (current.length > 0)
100
+ chunks.push(current);
101
+ return chunks;
102
+ }
103
+ // --- Plain Preview Rendering ---
104
+ function splitPlainMarkdownLine(line, maxLength = 1500) {
105
+ if (line.length <= maxLength)
106
+ return [line];
107
+ const words = line.split(/\s+/).filter(Boolean);
108
+ if (words.length === 0)
109
+ return [line];
110
+ const parts = [];
111
+ let current = "";
112
+ for (const word of words) {
113
+ const candidate = current.length === 0 ? word : `${current} ${word}`;
114
+ if (candidate.length <= maxLength) {
115
+ current = candidate;
116
+ continue;
117
+ }
118
+ if (current.length > 0) {
119
+ parts.push(current);
120
+ current = "";
121
+ }
122
+ if (word.length <= maxLength) {
123
+ current = word;
124
+ continue;
125
+ }
126
+ for (let i = 0; i < word.length; i += maxLength) {
127
+ parts.push(word.slice(i, i + maxLength));
128
+ }
129
+ }
130
+ if (current.length > 0) {
131
+ parts.push(current);
132
+ }
133
+ return parts.length > 0 ? parts : [line];
134
+ }
135
+ function isEscapedMarkdownCharacter(text, index) {
136
+ let backslashCount = 0;
137
+ for (let i = index - 1; i >= 0 && text[i] === "\\"; i--) {
138
+ backslashCount += 1;
139
+ }
140
+ return backslashCount % 2 === 1;
141
+ }
142
+ function findMarkdownClosingBracket(text, startIndex) {
143
+ let depth = 0;
144
+ for (let index = startIndex; index < text.length; index += 1) {
145
+ if (isEscapedMarkdownCharacter(text, index))
146
+ continue;
147
+ const char = text[index] ?? "";
148
+ if (char === "[") {
149
+ depth += 1;
150
+ continue;
151
+ }
152
+ if (char !== "]")
153
+ continue;
154
+ depth -= 1;
155
+ if (depth === 0)
156
+ return index;
157
+ }
158
+ return undefined;
159
+ }
160
+ function parseMarkdownLinkTarget(text, openParenIndex) {
161
+ let index = openParenIndex + 1;
162
+ while (index < text.length && /\s/.test(text[index] ?? "")) {
163
+ index += 1;
164
+ }
165
+ if (index >= text.length)
166
+ return undefined;
167
+ let destination = "";
168
+ if (text[index] === "<") {
169
+ const destinationStart = index + 1;
170
+ index += 1;
171
+ while (index < text.length) {
172
+ if (!isEscapedMarkdownCharacter(text, index) && text[index] === ">") {
173
+ break;
174
+ }
175
+ index += 1;
176
+ }
177
+ if (index >= text.length)
178
+ return undefined;
179
+ destination = text.slice(destinationStart, index).trim();
180
+ index += 1;
181
+ }
182
+ else {
183
+ const destinationStart = index;
184
+ let parenDepth = 0;
185
+ while (index < text.length) {
186
+ if (isEscapedMarkdownCharacter(text, index)) {
187
+ index += 1;
188
+ continue;
189
+ }
190
+ const char = text[index] ?? "";
191
+ if (/\s/.test(char) && parenDepth === 0)
192
+ break;
193
+ if (char === "(") {
194
+ parenDepth += 1;
195
+ index += 1;
196
+ continue;
197
+ }
198
+ if (char === ")") {
199
+ if (parenDepth === 0)
200
+ break;
201
+ parenDepth -= 1;
202
+ index += 1;
203
+ continue;
204
+ }
205
+ index += 1;
206
+ }
207
+ destination = text.slice(destinationStart, index).trim();
208
+ }
209
+ if (!destination)
210
+ return undefined;
211
+ while (index < text.length && /\s/.test(text[index] ?? "")) {
212
+ index += 1;
213
+ }
214
+ if (index < text.length &&
215
+ (text[index] === '"' || text[index] === "'" || text[index] === "(")) {
216
+ const titleDelimiter = text[index] ?? '"';
217
+ const closingTitleDelimiter = titleDelimiter === "(" ? ")" : titleDelimiter;
218
+ index += 1;
219
+ while (index < text.length) {
220
+ if (!isEscapedMarkdownCharacter(text, index) &&
221
+ text[index] === closingTitleDelimiter) {
222
+ break;
223
+ }
224
+ index += 1;
225
+ }
226
+ if (index >= text.length)
227
+ return undefined;
228
+ index += 1;
229
+ while (index < text.length && /\s/.test(text[index] ?? "")) {
230
+ index += 1;
231
+ }
232
+ }
233
+ if (text[index] !== ")")
234
+ return undefined;
235
+ return { destination, endIndex: index };
236
+ }
237
+ function isSupportedMarkdownLinkDestination(destination) {
238
+ return /^(?:https?:\/\/|mailto:)/i.test(destination.trim());
239
+ }
240
+ function parseMarkdownInlineLinkAt(text, index) {
241
+ const isImage = text[index] === "!" && text[index + 1] === "[";
242
+ const labelStartIndex = isImage ? index + 1 : index;
243
+ if (text[labelStartIndex] !== "[")
244
+ return undefined;
245
+ if (isEscapedMarkdownCharacter(text, labelStartIndex) ||
246
+ (isImage && isEscapedMarkdownCharacter(text, index))) {
247
+ return undefined;
248
+ }
249
+ const labelEndIndex = findMarkdownClosingBracket(text, labelStartIndex);
250
+ if (labelEndIndex === undefined || text[labelEndIndex + 1] !== "(") {
251
+ return undefined;
252
+ }
253
+ const target = parseMarkdownLinkTarget(text, labelEndIndex + 1);
254
+ if (!target)
255
+ return undefined;
256
+ return {
257
+ startIndex: index,
258
+ endIndex: target.endIndex,
259
+ label: text.slice(labelStartIndex + 1, labelEndIndex),
260
+ destination: target.destination,
261
+ isImage,
262
+ };
263
+ }
264
+ function parseMarkdownAutolinkAt(text, index) {
265
+ if (text[index] !== "<" || isEscapedMarkdownCharacter(text, index)) {
266
+ return undefined;
267
+ }
268
+ let endIndex = index + 1;
269
+ while (endIndex < text.length) {
270
+ if (!isEscapedMarkdownCharacter(text, endIndex) && text[endIndex] === ">") {
271
+ break;
272
+ }
273
+ endIndex += 1;
274
+ }
275
+ if (endIndex >= text.length)
276
+ return undefined;
277
+ const destination = text.slice(index + 1, endIndex).trim();
278
+ if (!isSupportedMarkdownLinkDestination(destination)) {
279
+ return undefined;
280
+ }
281
+ return { startIndex: index, endIndex, destination };
282
+ }
283
+ function replaceMarkdownLink(text, options) {
284
+ let result = "";
285
+ for (let index = 0; index < text.length;) {
286
+ const inlineLink = parseMarkdownInlineLinkAt(text, index);
287
+ if (inlineLink) {
288
+ result += options.renderInlineLink(inlineLink, isSupportedMarkdownLinkDestination(inlineLink.destination));
289
+ index = inlineLink.endIndex + 1;
290
+ continue;
291
+ }
292
+ const autolink = parseMarkdownAutolinkAt(text, index);
293
+ if (autolink) {
294
+ result += options.renderAutolink(autolink);
295
+ index = autolink.endIndex + 1;
296
+ continue;
297
+ }
298
+ result += text[index] ?? "";
299
+ index += 1;
300
+ }
301
+ return result;
302
+ }
303
+ function stripInlineMarkdownToPlainText(text) {
304
+ let result = replaceMarkdownLink(text, {
305
+ renderInlineLink: (link, supported) => {
306
+ const plainLabel = stripInlineMarkdownToPlainText(link.label).trim();
307
+ if (plainLabel.length > 0)
308
+ return plainLabel;
309
+ return supported ? link.destination : "";
310
+ },
311
+ renderAutolink: (link) => link.destination,
312
+ });
313
+ result = result.replace(/`([^`\n]+)`/g, "$1");
314
+ result = result.replace(/(\*\*\*|___)(.+?)\1/g, "$2");
315
+ result = result.replace(/(\*\*|__)(.+?)\1/g, "$2");
316
+ result = result.replace(/(\*|_)(.+?)\1/g, "$2");
317
+ result = result.replace(/~~(.+?)~~/g, "$1");
318
+ result = result.replace(/\\([\\`*_{}\[\]()#+\-.!>~|])/g, "$1");
319
+ return result;
320
+ }
321
+ function isMarkdownTableSeparator(line) {
322
+ return /^\s*\|?(?:\s*:?-{3,}:?\s*\|)+\s*:?-{3,}:?\s*\|?\s*$/.test(line);
323
+ }
324
+ function parseMarkdownFence(line) {
325
+ const match = line.match(/^\s*([`~]{3,})(.*)$/);
326
+ if (!match)
327
+ return undefined;
328
+ const fence = match[1] ?? "";
329
+ const marker = fence[0];
330
+ if ((marker !== "`" && marker !== "~") || /[^`~]/.test(fence)) {
331
+ return undefined;
332
+ }
333
+ if (!fence.split("").every((char) => char === marker))
334
+ return undefined;
335
+ return {
336
+ marker,
337
+ length: fence.length,
338
+ info: (match[2] ?? "").trim() || undefined,
339
+ };
340
+ }
341
+ function isFencedCodeStart(line) {
342
+ return parseMarkdownFence(line) !== undefined;
343
+ }
344
+ function isMatchingMarkdownFence(line, fence) {
345
+ const match = line.match(/^\s*([`~]{3,})\s*$/);
346
+ if (!match)
347
+ return false;
348
+ const candidate = match[1] ?? "";
349
+ return (candidate.length >= fence.length &&
350
+ candidate[0] === fence.marker &&
351
+ candidate.split("").every((char) => char === fence.marker));
352
+ }
353
+ function isIndentedCodeLine(line) {
354
+ return /^(?:\t| {4,})/.test(line);
355
+ }
356
+ function isIndentedMarkdownStructureLine(line) {
357
+ const trimmed = line.trimStart();
358
+ return (/^(?:[-*+]|\d+\.)\s+\[([ xX])\]\s+/.test(trimmed) ||
359
+ /^(?:[-*+]|\d+\.)\s+/.test(trimmed) ||
360
+ /^>\s?/.test(trimmed) ||
361
+ /^#{1,6}\s+/.test(trimmed) ||
362
+ parseMarkdownFence(trimmed) !== undefined);
363
+ }
364
+ function canStartIndentedCodeBlock(lines, index) {
365
+ const line = lines[index] ?? "";
366
+ if (!isIndentedCodeLine(line))
367
+ return false;
368
+ if (isIndentedMarkdownStructureLine(line))
369
+ return false;
370
+ if (index === 0)
371
+ return true;
372
+ return (lines[index - 1] ?? "").trim().length === 0;
373
+ }
374
+ function stripIndentedCodePrefix(line) {
375
+ if (line.startsWith("\t"))
376
+ return line.slice(1);
377
+ if (line.startsWith(" "))
378
+ return line.slice(4);
379
+ return line;
380
+ }
381
+ function normalizeMarkdownDocument(markdown) {
382
+ const lines = markdown.replace(/\r\n/g, "\n").split("\n");
383
+ let start = 0;
384
+ while (start < lines.length && (lines[start] ?? "").trim().length === 0) {
385
+ start += 1;
386
+ }
387
+ let end = lines.length;
388
+ while (end > start && (lines[end - 1] ?? "").trim().length === 0) {
389
+ end -= 1;
390
+ }
391
+ return lines.slice(start, end).join("\n");
392
+ }
393
+ function isMarkdownNumberedListMarker(marker) {
394
+ return /^\d+\.$/.test(marker);
395
+ }
396
+ function matchMarkdownHeadingLine(line) {
397
+ return line.match(/^(\s*)#{1,6}\s+(.+)$/);
398
+ }
399
+ // --- UI Markdown-to-Telegram-HTML Rendering ---
400
+ function renderDelimitedInlineStyle(text, delimiter, render) {
401
+ const escapedDelimiter = delimiter.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
402
+ const pattern = new RegExp(`(^|[^\\p{L}\\p{N}\\\\])(${escapedDelimiter})(?=\\S)([\\s\\S]+?)(?<=\\S)\\2(?=[^\\p{L}\\p{N}]|$)`, "gu");
403
+ return text.replace(pattern, (_match, prefix, _wrapped, content) => {
404
+ return `${prefix}${render(content)}`;
405
+ });
406
+ }
407
+ function makeInlineMarkdownToken(state, html) {
408
+ const token = `\uE000${state.tokens.length}\uE001`;
409
+ state.tokens.push(html);
410
+ return token;
411
+ }
412
+ function stashInlineMarkdownLinks(text, state, allowLinks) {
413
+ return replaceMarkdownLink(text, {
414
+ renderInlineLink: (link, supported) => {
415
+ const plainLabel = stripInlineMarkdownToPlainText(link.label).trim();
416
+ if (!allowLinks || !supported)
417
+ return plainLabel.length > 0 ? plainLabel : link.destination;
418
+ const renderedLabel = plainLabel.length > 0 ? plainLabel : link.destination;
419
+ return makeInlineMarkdownToken(state, `<a href="${escapeHtmlAttribute(link.destination)}">${escapeHtml(renderedLabel)}</a>`);
420
+ },
421
+ renderAutolink: (link) => {
422
+ if (!allowLinks)
423
+ return link.destination;
424
+ return makeInlineMarkdownToken(state, `<a href="${escapeHtmlAttribute(link.destination)}">${escapeHtml(link.destination)}</a>`);
425
+ },
426
+ });
427
+ }
428
+ function stashInlineMarkdownCodeSpans(text, state) {
429
+ return text.replace(/`([^`\n]+)`/g, (_match, code) => {
430
+ return makeInlineMarkdownToken(state, `<code>${escapeHtml(code)}</code>`);
431
+ });
432
+ }
433
+ function applyInlineMarkdownStyles(text) {
434
+ let result = renderDelimitedInlineStyle(text, "***", (content) => {
435
+ return `<b><i>${content}</i></b>`;
436
+ });
437
+ result = renderDelimitedInlineStyle(result, "___", (content) => {
438
+ return `<b><i>${content}</i></b>`;
439
+ });
440
+ result = renderDelimitedInlineStyle(result, "~~", (content) => {
441
+ return `<s>${content}</s>`;
442
+ });
443
+ result = renderDelimitedInlineStyle(result, "**", (content) => {
444
+ return `<b>${content}</b>`;
445
+ });
446
+ result = renderDelimitedInlineStyle(result, "__", (content) => {
447
+ return `<b>${content}</b>`;
448
+ });
449
+ result = renderDelimitedInlineStyle(result, "*", (content) => {
450
+ return `<i>${content}</i>`;
451
+ });
452
+ result = renderDelimitedInlineStyle(result, "_", (content) => {
453
+ return `<i>${content}</i>`;
454
+ });
455
+ return renderDelimitedInlineStyle(result, "||", (content) => {
456
+ return `<tg-spoiler>${content}</tg-spoiler>`;
457
+ });
458
+ }
459
+ function restoreInlineMarkdownTokens(text, state) {
460
+ return text.replace(/\uE000(\d+)\uE001/g, (_match, index) => state.tokens[Number(index)] ?? "");
461
+ }
462
+ function renderInlineMarkdown(text, options = {}) {
463
+ const tokenState = { tokens: [] };
464
+ let result = stashInlineMarkdownLinks(text, tokenState, options.allowLinks !== false);
465
+ result = stashInlineMarkdownCodeSpans(result, tokenState);
466
+ result = escapeHtml(result);
467
+ result = applyInlineMarkdownStyles(result);
468
+ result = result.replace(/\\([\\`*_{}\[\]()#+\-.!>~|])/g, "$1");
469
+ return restoreInlineMarkdownTokens(result, tokenState);
470
+ }
471
+ export function renderTelegramInlineMarkdownHtml(text, options = {}) {
472
+ return renderInlineMarkdown(text, options);
473
+ }
474
+ function buildListIndent(level) {
475
+ return "\u00A0".repeat(Math.max(0, level) * 2);
476
+ }
477
+ function parseMarkdownTableRow(line) {
478
+ const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
479
+ return trimmed
480
+ .split("|")
481
+ .map((cell) => stripInlineMarkdownToPlainText(cell.trim()));
482
+ }
483
+ function parseMarkdownQuoteLine(line) {
484
+ const match = line.match(/^\s*((?:>\s*)+)(.*)$/);
485
+ if (!match)
486
+ return undefined;
487
+ const markers = match[1] ?? "";
488
+ const depth = (markers.match(/>/g) ?? []).length;
489
+ return {
490
+ depth,
491
+ content: match[2] ?? "",
492
+ };
493
+ }
494
+ function renderMarkdownTextPiece(piece) {
495
+ const heading = matchMarkdownHeadingLine(piece);
496
+ if (heading) {
497
+ const indent = buildListIndent(Math.floor((heading[1] ?? "").length / 2));
498
+ return `${indent}<b>${renderInlineMarkdown(heading[2] ?? "")}</b>`;
499
+ }
500
+ const task = piece.match(/^(\s*)([-*+]|\d+\.)\s+\[([ xX])\]\s+(.+)$/);
501
+ if (task) {
502
+ const indent = buildListIndent(Math.floor((task[1] ?? "").length / 2));
503
+ const listMarker = task[2] ?? "-";
504
+ const checkboxMarker = (task[3] ?? " ").toLowerCase() === "x" ? "[x]" : "[ ]";
505
+ const taskPrefix = isMarkdownNumberedListMarker(listMarker)
506
+ ? `<code>${listMarker}</code> <code>${checkboxMarker}</code>`
507
+ : `<code>${checkboxMarker}</code>`;
508
+ return `${indent}${taskPrefix} ${renderInlineMarkdown(task[4] ?? "")}`;
509
+ }
510
+ const bullet = piece.match(/^(\s*)[-*+]\s+(.+)$/);
511
+ if (bullet) {
512
+ const indent = buildListIndent(Math.floor((bullet[1] ?? "").length / 2));
513
+ return `${indent}<code>-</code> ${renderInlineMarkdown(bullet[2] ?? "")}`;
514
+ }
515
+ const numbered = piece.match(/^(\s*)(\d+)\.\s+(.+)$/);
516
+ if (numbered) {
517
+ const indent = buildListIndent(Math.floor((numbered[1] ?? "").length / 2));
518
+ return `${indent}<code>${numbered[2]}.</code> ${renderInlineMarkdown(numbered[3] ?? "")}`;
519
+ }
520
+ const quote = piece.match(/^>\s?(.+)$/);
521
+ if (quote) {
522
+ return `<blockquote>${renderInlineMarkdown(quote[1] ?? "")}</blockquote>`;
523
+ }
524
+ if (/^([-*_]\s*){3,}$/.test(piece.trim()))
525
+ return "────────────";
526
+ return renderInlineMarkdown(piece);
527
+ }
528
+ function isPlainInlineMarkdownLine(line) {
529
+ if (line.trim().length === 0)
530
+ return false;
531
+ return (matchMarkdownHeadingLine(line) === null &&
532
+ !/^(\s*)([-*+]|\d+\.)\s+\[([ xX])\]\s+(.+)$/.test(line) &&
533
+ !/^(\s*)[-*+]\s+(.+)$/.test(line) &&
534
+ !/^(\s*)(\d+)\.\s+(.+)$/.test(line) &&
535
+ !/^>\s?(.+)$/.test(line) &&
536
+ !/^([-*_]\s*){3,}$/.test(line.trim()));
537
+ }
538
+ function renderMarkdownTextLines(block) {
539
+ const rendered = [];
540
+ const lines = block.split("\n");
541
+ const nonBlankLines = lines.filter((line) => line.trim().length > 0);
542
+ if (nonBlankLines.length > 1 &&
543
+ nonBlankLines.every(isPlainInlineMarkdownLine)) {
544
+ return renderInlineMarkdown(nonBlankLines.join("\n")).split("\n");
545
+ }
546
+ for (const line of lines) {
547
+ if (line.trim().length === 0)
548
+ continue;
549
+ for (const piece of splitPlainMarkdownLine(line)) {
550
+ rendered.push(renderMarkdownTextPiece(piece));
551
+ }
552
+ }
553
+ return rendered;
554
+ }
555
+ function sanitizeTelegramCodeLanguage(language) {
556
+ return language.split(/\s+/)[0]?.replace(/[^A-Za-z0-9_+.-]/g, "") ?? "";
557
+ }
558
+ function renderMarkdownCodeBlock(code, language) {
559
+ const safeLanguage = language ? sanitizeTelegramCodeLanguage(language) : "";
560
+ const open = safeLanguage
561
+ ? `<pre><code class="language-${escapeHtmlAttribute(safeLanguage)}">`
562
+ : "<pre><code>";
563
+ const close = "</code></pre>";
564
+ const maxContentLength = MAX_MESSAGE_LENGTH - open.length - close.length;
565
+ const chunks = [];
566
+ let current = "";
567
+ const pushCurrent = () => {
568
+ if (current.length === 0)
569
+ return;
570
+ chunks.push(`${open}${current}${close}`);
571
+ current = "";
572
+ };
573
+ const appendEscapedLine = (escapedLine) => {
574
+ if (escapedLine.length <= maxContentLength) {
575
+ const candidate = current.length === 0 ? escapedLine : `${current}\n${escapedLine}`;
576
+ if (candidate.length <= maxContentLength) {
577
+ current = candidate;
578
+ return;
579
+ }
580
+ pushCurrent();
581
+ current = escapedLine;
582
+ return;
583
+ }
584
+ pushCurrent();
585
+ for (let i = 0; i < escapedLine.length; i += maxContentLength) {
586
+ chunks.push(`${open}${escapedLine.slice(i, i + maxContentLength)}${close}`);
587
+ }
588
+ };
589
+ for (const line of code.split("\n")) {
590
+ appendEscapedLine(escapeHtml(line));
591
+ }
592
+ pushCurrent();
593
+ return chunks.length > 0 ? chunks : [`${open}${close}`];
594
+ }
595
+ function isTelegramTableEmojiGrapheme(grapheme) {
596
+ return (TELEGRAM_TABLE_EMOJI_GRAPHEME_PATTERN.test(grapheme) ||
597
+ grapheme.includes("\u20e3"));
598
+ }
599
+ function getTelegramTableCodePointWidth(char) {
600
+ const codePoint = char.codePointAt(0) ?? 0;
601
+ if (codePoint === 0 || codePoint < 32)
602
+ return 0;
603
+ if (/\p{Mark}/u.test(char))
604
+ return 0;
605
+ if ((codePoint >= 0xfe00 && codePoint <= 0xfe0f) || codePoint === 0x200d)
606
+ return 0;
607
+ if ((codePoint >= 0x1100 && codePoint <= 0x115f) ||
608
+ codePoint === 0x2329 ||
609
+ codePoint === 0x232a ||
610
+ (codePoint >= 0x2e80 && codePoint <= 0xa4cf) ||
611
+ (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
612
+ (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
613
+ (codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
614
+ (codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
615
+ (codePoint >= 0xff00 && codePoint <= 0xff60) ||
616
+ (codePoint >= 0xffe0 && codePoint <= 0xffe6)) {
617
+ return 2;
618
+ }
619
+ return 1;
620
+ }
621
+ function getTelegramTableGraphemes(text) {
622
+ if (TELEGRAM_TABLE_GRAPHEME_SEGMENTER) {
623
+ return Array.from(TELEGRAM_TABLE_GRAPHEME_SEGMENTER.segment(text), (segment) => segment.segment);
624
+ }
625
+ return Array.from(text);
626
+ }
627
+ function getTelegramTableCellWidth(text) {
628
+ return getTelegramTableGraphemes(text).reduce((width, grapheme) => {
629
+ if (isTelegramTableEmojiGrapheme(grapheme))
630
+ return width + 2;
631
+ return (width +
632
+ Array.from(grapheme).reduce((sum, char) => sum + getTelegramTableCodePointWidth(char), 0));
633
+ }, 0);
634
+ }
635
+ function padTelegramTableCellEnd(cell, width) {
636
+ const padding = width - getTelegramTableCellWidth(cell);
637
+ return padding > 0 ? `${cell}${" ".repeat(padding)}` : cell;
638
+ }
639
+ function renderMarkdownTableBlock(lines) {
640
+ const rows = lines.map(parseMarkdownTableRow);
641
+ const columnCount = Math.max(...rows.map((row) => row.length), 0);
642
+ const normalizedRows = rows.map((row) => {
643
+ const next = [...row];
644
+ while (next.length < columnCount) {
645
+ next.push("");
646
+ }
647
+ return next;
648
+ });
649
+ const widths = Array.from({ length: columnCount }, (_, columnIndex) => {
650
+ return Math.max(3, ...normalizedRows.map((row) => getTelegramTableCellWidth(row[columnIndex] ?? "")));
651
+ });
652
+ const formatRow = (row) => {
653
+ return row
654
+ .map((cell, columnIndex) => padTelegramTableCellEnd(cell ?? "", widths[columnIndex] ?? 3))
655
+ .join(" | ");
656
+ };
657
+ const separator = widths.map((width) => "-".repeat(width)).join(" | ");
658
+ const [header, ...body] = normalizedRows;
659
+ const tableLines = [
660
+ formatRow(header ?? []),
661
+ separator,
662
+ ...body.map(formatRow),
663
+ ];
664
+ return renderMarkdownCodeBlock(tableLines.join("\n"), "markdown");
665
+ }
666
+ function chunkRenderedHtmlLines(lines, wrapper) {
667
+ if (lines.length === 0)
668
+ return [];
669
+ const open = wrapper?.open ?? "";
670
+ const close = wrapper?.close ?? "";
671
+ const maxContentLength = MAX_MESSAGE_LENGTH - open.length - close.length;
672
+ const chunks = [];
673
+ let current = "";
674
+ const pushCurrent = () => {
675
+ if (current.length === 0)
676
+ return;
677
+ chunks.push(`${open}${current}${close}`);
678
+ current = "";
679
+ };
680
+ for (const line of lines) {
681
+ const candidate = current.length === 0 ? line : `${current}\n${line}`;
682
+ if (candidate.length <= maxContentLength) {
683
+ current = candidate;
684
+ continue;
685
+ }
686
+ pushCurrent();
687
+ if (line.length <= maxContentLength) {
688
+ current = line;
689
+ continue;
690
+ }
691
+ for (let i = 0; i < line.length; i += maxContentLength) {
692
+ chunks.push(`${open}${line.slice(i, i + maxContentLength)}${close}`);
693
+ }
694
+ }
695
+ pushCurrent();
696
+ return chunks;
697
+ }
698
+ function renderMarkdownTextBlock(block) {
699
+ return chunkRenderedHtmlLines(renderMarkdownTextLines(block));
700
+ }
701
+ function renderMarkdownQuoteBlock(lines) {
702
+ const inner = lines
703
+ .map((line) => {
704
+ const parsed = parseMarkdownQuoteLine(line);
705
+ if (!parsed)
706
+ return line;
707
+ const nestedIndent = "\u00A0".repeat(Math.max(0, parsed.depth - 1) * 2);
708
+ return `${nestedIndent}${parsed.content}`;
709
+ })
710
+ .join("\n");
711
+ return chunkRenderedHtmlLines(renderMarkdownTextLines(inner), {
712
+ open: "<blockquote>",
713
+ close: "</blockquote>",
714
+ });
715
+ }
716
+ function collectFencedMarkdownCodeLines(lines, index, fence) {
717
+ const codeLines = [];
718
+ let nextIndex = index + 1;
719
+ while (nextIndex < lines.length &&
720
+ !isMatchingMarkdownFence(lines[nextIndex] ?? "", fence)) {
721
+ codeLines.push(lines[nextIndex] ?? "");
722
+ nextIndex += 1;
723
+ }
724
+ const closed = nextIndex < lines.length;
725
+ if (closed)
726
+ nextIndex += 1;
727
+ return { codeLines, nextIndex, closed };
728
+ }
729
+ function collectMarkdownTableBlockLines(lines, index) {
730
+ const tableLines = [lines[index] ?? ""];
731
+ let nextIndex = index + 2;
732
+ while (nextIndex < lines.length) {
733
+ const tableLine = lines[nextIndex] ?? "";
734
+ if (tableLine.trim().length === 0 || !tableLine.includes("|"))
735
+ break;
736
+ tableLines.push(tableLine);
737
+ nextIndex += 1;
738
+ }
739
+ return { tableLines, nextIndex };
740
+ }
741
+ function collectIndentedMarkdownCodeLines(lines, index) {
742
+ const codeLines = [];
743
+ let nextIndex = index;
744
+ while (nextIndex < lines.length) {
745
+ const rawLine = lines[nextIndex] ?? "";
746
+ if (rawLine.trim().length === 0) {
747
+ codeLines.push("");
748
+ nextIndex += 1;
749
+ continue;
750
+ }
751
+ if (!isIndentedCodeLine(rawLine))
752
+ break;
753
+ codeLines.push(stripIndentedCodePrefix(rawLine));
754
+ nextIndex += 1;
755
+ }
756
+ return { codeLines, nextIndex };
757
+ }
758
+ function collectMarkdownQuoteBlockLines(lines, index) {
759
+ const quoteLines = [];
760
+ let nextIndex = index;
761
+ while (nextIndex < lines.length && /^\s*>/.test(lines[nextIndex] ?? "")) {
762
+ quoteLines.push(lines[nextIndex] ?? "");
763
+ nextIndex += 1;
764
+ }
765
+ return { quoteLines, nextIndex };
766
+ }
767
+ function isMarkdownTextBlockBoundary(lines, index) {
768
+ const current = lines[index] ?? "";
769
+ const following = lines[index + 1] ?? "";
770
+ if (current.trim().length === 0)
771
+ return true;
772
+ if (isFencedCodeStart(current))
773
+ return true;
774
+ if (canStartIndentedCodeBlock(lines, index))
775
+ return true;
776
+ if (/^\s*>/.test(current))
777
+ return true;
778
+ return current.includes("|") && isMarkdownTableSeparator(following);
779
+ }
780
+ function collectMarkdownTextBlockLines(lines, index) {
781
+ const textLines = [];
782
+ let nextIndex = index;
783
+ while (nextIndex < lines.length) {
784
+ if (isMarkdownTextBlockBoundary(lines, nextIndex))
785
+ break;
786
+ textLines.push(lines[nextIndex] ?? "");
787
+ nextIndex += 1;
788
+ }
789
+ return { textLines, nextIndex };
790
+ }
791
+ function renderMarkdownDocumentBlocks(normalizedMarkdown) {
792
+ const renderedBlocks = [];
793
+ let minimumBlankLinesBeforeNextBlock = 0;
794
+ const pushRenderedBlocks = (blocks, blankLinesBefore) => {
795
+ const effectiveBlankLinesBefore = renderedBlocks.length === 0
796
+ ? blankLinesBefore
797
+ : Math.max(blankLinesBefore, minimumBlankLinesBeforeNextBlock);
798
+ for (const [blockIndex, block] of blocks.entries()) {
799
+ renderedBlocks.push({
800
+ text: block,
801
+ blankLinesBefore: blockIndex === 0 ? effectiveBlankLinesBefore : 0,
802
+ });
803
+ }
804
+ minimumBlankLinesBeforeNextBlock = 0;
805
+ };
806
+ const lines = normalizedMarkdown.split("\n");
807
+ let index = 0;
808
+ let pendingBlankLines = 0;
809
+ while (index < lines.length) {
810
+ const line = lines[index] ?? "";
811
+ const nextLine = lines[index + 1] ?? "";
812
+ if (line.trim().length === 0) {
813
+ pendingBlankLines += 1;
814
+ index += 1;
815
+ continue;
816
+ }
817
+ const heading = matchMarkdownHeadingLine(line);
818
+ if (heading) {
819
+ pushRenderedBlocks(renderMarkdownTextBlock(line), renderedBlocks.length === 0
820
+ ? pendingBlankLines
821
+ : Math.max(pendingBlankLines, 1));
822
+ pendingBlankLines = 0;
823
+ minimumBlankLinesBeforeNextBlock = 1;
824
+ index += 1;
825
+ continue;
826
+ }
827
+ const fence = parseMarkdownFence(line);
828
+ if (fence) {
829
+ const block = collectFencedMarkdownCodeLines(lines, index, fence);
830
+ index = block.nextIndex;
831
+ pushRenderedBlocks(renderMarkdownCodeBlock(block.codeLines.join("\n"), fence.info), pendingBlankLines);
832
+ pendingBlankLines = 0;
833
+ continue;
834
+ }
835
+ if (line.includes("|") && isMarkdownTableSeparator(nextLine)) {
836
+ const block = collectMarkdownTableBlockLines(lines, index);
837
+ index = block.nextIndex;
838
+ pushRenderedBlocks(renderMarkdownTableBlock(block.tableLines), pendingBlankLines);
839
+ pendingBlankLines = 0;
840
+ continue;
841
+ }
842
+ if (canStartIndentedCodeBlock(lines, index)) {
843
+ const block = collectIndentedMarkdownCodeLines(lines, index);
844
+ index = block.nextIndex;
845
+ pushRenderedBlocks(renderMarkdownCodeBlock(block.codeLines.join("\n")), pendingBlankLines);
846
+ pendingBlankLines = 0;
847
+ continue;
848
+ }
849
+ if (/^\s*>/.test(line)) {
850
+ const block = collectMarkdownQuoteBlockLines(lines, index);
851
+ index = block.nextIndex;
852
+ pushRenderedBlocks(renderMarkdownQuoteBlock(block.quoteLines), pendingBlankLines);
853
+ pendingBlankLines = 0;
854
+ continue;
855
+ }
856
+ const block = collectMarkdownTextBlockLines(lines, index);
857
+ index = block.nextIndex;
858
+ pushRenderedBlocks(renderMarkdownTextBlock(block.textLines.join("\n")), pendingBlankLines);
859
+ pendingBlankLines = 0;
860
+ }
861
+ return renderedBlocks;
862
+ }
863
+ function flushTelegramMarkdownChunkAccumulator(accumulator) {
864
+ if (accumulator.current.length === 0)
865
+ return;
866
+ accumulator.chunks.push(accumulator.current);
867
+ accumulator.current = "";
868
+ }
869
+ function splitOversizedTelegramMarkdownBlock(text) {
870
+ const chunks = [];
871
+ for (let i = 0; i < text.length; i += MAX_MESSAGE_LENGTH) {
872
+ chunks.push(text.slice(i, i + MAX_MESSAGE_LENGTH));
873
+ }
874
+ return chunks;
875
+ }
876
+ function appendTelegramRenderedMarkdownBlock(accumulator, block) {
877
+ const separator = "\n".repeat(block.blankLinesBefore + 1);
878
+ const candidate = accumulator.current.length === 0
879
+ ? block.text
880
+ : `${accumulator.current}${separator}${block.text}`;
881
+ if (candidate.length <= MAX_MESSAGE_LENGTH) {
882
+ accumulator.current = candidate;
883
+ return;
884
+ }
885
+ flushTelegramMarkdownChunkAccumulator(accumulator);
886
+ if (block.text.length <= MAX_MESSAGE_LENGTH) {
887
+ accumulator.current = block.text;
888
+ return;
889
+ }
890
+ accumulator.chunks.push(...splitOversizedTelegramMarkdownBlock(block.text));
891
+ }
892
+ function chunkTelegramRenderedMarkdownBlocks(renderedBlocks) {
893
+ const accumulator = {
894
+ chunks: [],
895
+ current: "",
896
+ };
897
+ for (const block of renderedBlocks) {
898
+ appendTelegramRenderedMarkdownBlock(accumulator, block);
899
+ }
900
+ flushTelegramMarkdownChunkAccumulator(accumulator);
901
+ return accumulator.chunks;
902
+ }
903
+ function renderMarkdownToTelegramHtmlChunks(markdown) {
904
+ const normalized = normalizeMarkdownDocument(markdown);
905
+ if (normalized.length === 0)
906
+ return [];
907
+ return chunkTelegramRenderedMarkdownBlocks(renderMarkdownDocumentBlocks(normalized));
908
+ }
909
+ function chunkParagraphs(text) {
910
+ if (text.length <= MAX_MESSAGE_LENGTH)
911
+ return [text];
912
+ const normalized = text.replace(/\r\n/g, "\n");
913
+ const paragraphs = normalized.split(/\n\n+/);
914
+ const chunks = [];
915
+ let current = "";
916
+ const flushCurrent = () => {
917
+ if (current.trim().length > 0)
918
+ chunks.push(current);
919
+ current = "";
920
+ };
921
+ const splitLongBlock = (block) => {
922
+ if (block.length <= MAX_MESSAGE_LENGTH)
923
+ return [block];
924
+ const lines = block.split("\n");
925
+ const lineChunks = [];
926
+ let lineCurrent = "";
927
+ for (const line of lines) {
928
+ const candidate = lineCurrent.length === 0 ? line : `${lineCurrent}\n${line}`;
929
+ if (candidate.length <= MAX_MESSAGE_LENGTH) {
930
+ lineCurrent = candidate;
931
+ continue;
932
+ }
933
+ if (lineCurrent.length > 0) {
934
+ lineChunks.push(lineCurrent);
935
+ lineCurrent = "";
936
+ }
937
+ if (line.length <= MAX_MESSAGE_LENGTH) {
938
+ lineCurrent = line;
939
+ continue;
940
+ }
941
+ for (let i = 0; i < line.length; i += MAX_MESSAGE_LENGTH) {
942
+ lineChunks.push(line.slice(i, i + MAX_MESSAGE_LENGTH));
943
+ }
944
+ }
945
+ if (lineCurrent.length > 0) {
946
+ lineChunks.push(lineCurrent);
947
+ }
948
+ return lineChunks;
949
+ };
950
+ for (const paragraph of paragraphs) {
951
+ if (paragraph.length === 0)
952
+ continue;
953
+ const parts = splitLongBlock(paragraph);
954
+ for (const part of parts) {
955
+ const candidate = current.length === 0 ? part : `${current}\n\n${part}`;
956
+ if (candidate.length <= MAX_MESSAGE_LENGTH) {
957
+ current = candidate;
958
+ }
959
+ else {
960
+ flushCurrent();
961
+ current = part;
962
+ }
963
+ }
964
+ }
965
+ flushCurrent();
966
+ return chunks;
967
+ }
968
+ export function renderTelegramMessage(text, options) {
969
+ const mode = options?.mode ?? "plain";
970
+ if (mode === "plain") {
971
+ return chunkParagraphs(text).map((chunk) => ({ text: chunk }));
972
+ }
973
+ if (mode === "html") {
974
+ return chunkHtmlPreservingTags(text, MAX_MESSAGE_LENGTH).map((chunk) => ({
975
+ text: chunk,
976
+ parseMode: "HTML",
977
+ }));
978
+ }
979
+ return renderMarkdownToTelegramHtmlChunks(text).map((chunk) => ({
980
+ text: chunk,
981
+ parseMode: "HTML",
982
+ }));
983
+ }