@icanseeuanywhere/telekaf 4.16.4 → 4.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/README.md CHANGED
@@ -26,40 +26,55 @@
26
26
  > This package tracks the latest Telegram Bot API releases and ships features ahead of the upstream package.
27
27
  > It is a drop-in replacement: just swap `npm install telegraf` → `npm install @icanseeuanywhere/telekaf` and change your imports from `'telegraf'` to `'@icanseeuanywhere/telekaf'`.
28
28
 
29
+
29
30
  ---
30
31
 
31
32
  ## 🆕 Rich Messages — Bot API 10.1
32
33
 
33
- > Added in **Bot API 10.1** (June 11, 2026). Rich Messages allow bots to send highly structured content — headings, lists, tables, media collages, code blocks, mathematical expressions, AI thinking blocks, and more — with seamless streaming support for AI-generated replies.
34
+ > Added in **Bot API 10.1** (June 11, 2026). Rich Messages let bots send highly structured content using HTML or Markdown — headings, lists, tables, media collages, code blocks, math expressions, AI thinking blocks, and more — with streaming support for AI-generated replies.
34
35
 
35
36
  ### Table of Contents
36
37
 
37
- - [Quick Start](#rich-messages-quick-start)
38
+ - [Quick Start](#quick-start)
38
39
  - [Sending Methods](#sending-methods)
39
- - [RichText — Inline Elements](#richtext--inline-elements)
40
- - [RichBlock — Block Elements](#richblock--block-elements)
41
- - [Builder API](#richmessagebuilder-api)
42
- - [RT Helpers](#rt-helpers)
40
+ - [InputRichMessage Format](#inputrichmessage-format)
41
+ - [RichHTMLBuilder](#richhtmlbuilder)
42
+ - [Text Formatting](#text-formatting-html)
43
+ - [Headings & Paragraphs](#headings--paragraphs)
44
+ - [Lists](#lists-html)
45
+ - [Code Blocks](#code-blocks)
46
+ - [Blockquote & Pull Quote](#blockquote--pull-quote)
47
+ - [Math](#math-html)
48
+ - [Media — Photo, Video, Audio](#media--photo-video-audio)
49
+ - [Collage & Slideshow](#collage--slideshow)
50
+ - [Map](#map)
51
+ - [Table](#table-html)
52
+ - [Details (Expandable)](#details-expandable)
53
+ - [Footnote References](#footnote-references-html)
54
+ - [Anchors & In-document Links](#anchors--in-document-links)
55
+ - [Special Elements](#special-elements)
56
+ - [RichMarkdownBuilder](#richmarkdownbuilder)
43
57
  - [Streaming with sendRichMessageDraft](#streaming-with-sendrichmessagedraft)
58
+ - [reply\_markup with Rich Messages](#reply_markup-with-rich-messages)
44
59
  - [Inline / Web App Queries](#inline--web-app-queries)
45
- - [TypeScript Usage](#typescript-usage-with-rich-messages)
60
+ - [TypeScript](#typescript)
46
61
 
47
62
  ---
48
63
 
49
- ### Rich Messages Quick Start
64
+ ### Quick Start
50
65
 
51
66
  ```ts
52
67
  import { Telegraf, RichMessage } from '@icanseeuanywhere/telekaf'
53
- const { RichMessageBuilder: Builder, RT } = RichMessage
68
+ const { RichHTMLBuilder: HTML } = RichMessage
54
69
 
55
70
  const bot = new Telegraf(process.env.BOT_TOKEN)
56
71
 
57
72
  bot.command('hello', async (ctx) => {
58
- const msg = new Builder()
59
- .heading(1, RT.plain('Hello from Telegraf!'))
60
- .paragraph(RT.plain('This is a '), RT.bold(RT.plain('rich message')), RT.plain(' example.'))
73
+ const msg = new HTML()
74
+ .heading(1, 'Hello World!')
75
+ .paragraph(HTML.bold('Rich Messages') + ' are now supported in Bot API 10.1.')
61
76
  .divider()
62
- .preformatted('npm install telegraf', 'bash')
77
+ .ul('Headings', 'Lists', 'Tables', 'Media', 'Math', 'and more')
63
78
  .build()
64
79
 
65
80
  await ctx.sendRichMessage(msg)
@@ -74,478 +89,506 @@ process.once('SIGTERM', () => bot.stop('SIGTERM'))
74
89
 
75
90
  ### Sending Methods
76
91
 
77
- There are three ways to send rich messages:
78
-
79
92
  | Method | Description |
80
93
  |---|---|
81
94
  | `ctx.sendRichMessage(msg, extra?)` | Send a rich message to the current chat |
82
- | `ctx.replyWithRichMessageContent(msg, extra?)` | Reply to the current message with a rich message |
83
- | `ctx.sendRichMessageDraft(msg, extra?)` | Stream a partial rich message (for AI-generated replies) |
84
- | `ctx.telegram.sendRichMessage(chatId, msg, extra?)` | Explicit usage with chat ID |
85
- | `ctx.telegram.sendRichMessageDraft(chatId, msg, extra?)` | Explicit streaming with chat ID |
95
+ | `ctx.replyWithRichMessageContent(msg, extra?)` | Send a rich message quoting the current message |
96
+ | `ctx.sendRichMessageDraft(draftId, msg, extra?)` | Stream a partial draft (private chats only) |
97
+ | `ctx.telegram.sendRichMessage(chatId, msg, extra?)` | Explicit call with chat ID |
98
+ | `ctx.telegram.sendRichMessageDraft(chatId, draftId, msg, extra?)` | Explicit streaming with chat ID |
86
99
 
87
- ```ts
88
- bot.on('message', async (ctx) => {
89
- const msg = new Builder()
90
- .paragraph(RT.plain('I am replying to your message!'))
91
- .build()
100
+ **`extra` options for `sendRichMessage`:**
92
101
 
93
- // Sends and quotes the user's message
94
- await ctx.replyWithRichMessageContent(msg)
102
+ ```ts
103
+ await ctx.sendRichMessage(msg, {
104
+ message_thread_id: 123, // for forum topics
105
+ direct_messages_topic_id: 456, // for DM topics
106
+ disable_notification: true,
107
+ protect_content: true,
108
+ allow_paid_broadcast: false,
109
+ message_effect_id: 'effect_id',
110
+ reply_parameters: { message_id: ctx.message.message_id },
111
+ reply_markup: { inline_keyboard: [[{ text: 'OK', callback_data: 'ok' }]] },
95
112
  })
96
113
  ```
97
114
 
98
- The `extra` parameter accepts:
115
+ ---
116
+
117
+ ### InputRichMessage Format
118
+
119
+ `InputRichMessage` uses **either** `html` or `markdown` — not both:
99
120
 
100
121
  ```ts
101
- {
102
- message_thread_id?: number // for forum topics
103
- disable_notification?: boolean
104
- protect_content?: boolean
105
- reply_parameters?: ReplyParameters
106
- reply_markup?: InlineKeyboardMarkup | ReplyKeyboardMarkup | ...
122
+ // HTML format
123
+ const msg: InputRichMessage = {
124
+ html: '<h1>Title</h1><p>Body text</p>',
125
+ is_rtl: false,
126
+ skip_entity_detection: false,
127
+ }
128
+
129
+ // Markdown format
130
+ const msg: InputRichMessage = {
131
+ markdown: '# Title\n\nBody text',
107
132
  }
108
133
  ```
109
134
 
110
- ---
135
+ Use the builders — `RichHTMLBuilder` or `RichMarkdownBuilder` — to construct these conveniently.
111
136
 
112
- ### RichText — Inline Elements
137
+ ---
113
138
 
114
- `RichText` types are inline elements that compose the text inside paragraphs, headings, captions, etc.
139
+ ### RichHTMLBuilder
115
140
 
116
- #### Text Formatting
141
+ Import and instantiate:
117
142
 
118
143
  ```ts
119
- RT.plain('normal text')
120
- RT.bold(RT.plain('bold'))
121
- RT.italic(RT.plain('italic'))
122
- RT.underline(RT.plain('underlined'))
123
- RT.strikethrough(RT.plain('crossed out'))
124
- RT.spoiler(RT.plain('hidden until tapped'))
125
- RT.marked(RT.plain('highlighted')) // like a text marker
126
- RT.subscript(RT.plain('₂'))
127
- RT.superscript(RT.plain('²'))
128
- ```
129
-
130
- Nesting is supported:
144
+ import { RichMessage } from '@icanseeuanywhere/telekaf'
145
+ const { RichHTMLBuilder: HTML } = RichMessage
131
146
 
132
- ```ts
133
- RT.bold(RT.italic(RT.plain('bold and italic')))
134
- RT.underline(RT.strikethrough(RT.plain('underlined and crossed')))
147
+ const msg = new HTML()
148
+ .heading(1, 'Title')
149
+ .paragraph('Content')
150
+ .build() // returns InputRichMessage { html: '...' }
135
151
  ```
136
152
 
137
- #### Code & Math
138
-
139
- ```ts
140
- RT.code(RT.plain('const x = 1'), 'javascript') // inline code with language
141
- RT.math('E = mc^2') // inline mathematical expression
142
- ```
153
+ #### Text Formatting (HTML)
143
154
 
144
- #### Links & References
155
+ Static inline helpers — return strings to embed inside block methods:
145
156
 
146
157
  ```ts
147
- RT.url('https://telegram.org', RT.plain('Telegram'))
148
- RT.email('hello@example.com', RT.plain('Send email'))
149
- RT.phone('+6281234567890', RT.plain('Call us'))
150
- RT.bankCard('4111111111111111') // bank card number (auto-formatted)
151
- RT.anchorLink('section-1', RT.plain('Jump to section')) // link to an anchor in the same message
152
- ```
153
-
154
- #### Mentions & Commands
158
+ HTML.bold('bold text') // <b>bold text</b>
159
+ HTML.italic('italic text') // <i>italic text</i>
160
+ HTML.underline('underlined') // <u>underlined</u>
161
+ HTML.strikethrough('crossed out') // <s>crossed out</s>
162
+ HTML.spoiler('hidden until tapped') // <tg-spoiler>hidden</tg-spoiler>
163
+ HTML.code('inline code') // <code>inline code</code>
164
+ HTML.marked('highlighted') // <mark>highlighted</mark>
165
+ HTML.sub('subscript') // <sub>subscript</sub>
166
+ HTML.sup('superscript') // <sup>superscript</sup>
167
+
168
+ HTML.url('https://t.me', 'Telegram') // <a href="...">Telegram</a>
169
+ HTML.email('hi@bot.com', 'Email us') // <a href="mailto:...">Email us</a>
170
+ HTML.phone('+6281234567', 'Call us') // <a href="tel:...">Call us</a>
171
+ HTML.mention(123456789, 'Alice') // <a href="tg://user?id=...">Alice</a>
172
+ HTML.customEmoji('5368324170671202286', '👍') // <tg-emoji emoji-id="...">👍</tg-emoji>
173
+ HTML.time(1647531900, 'wDT', '22:45 tomorrow') // <tg-time unix="..." format="...">...</tg-time>
174
+ HTML.inlineMath('E = mc^2') // $E = mc^2$
175
+
176
+ // Nesting
177
+ HTML.bold(HTML.italic('bold italic'))
178
+ HTML.underline(HTML.spoiler('underlined spoiler'))
179
+ ```
180
+
181
+ #### Headings & Paragraphs
155
182
 
156
183
  ```ts
157
- RT.mention(RT.plain('John'), 123456789) // mention by user_id
158
- RT.mention(RT.plain('@john'), undefined, 'john') // mention by username
159
- RT.botCommand('/start')
160
- RT.botCommand('/search', 'mybot') // command for a specific bot
161
- RT.hashtag('telegraf')
162
- RT.cashtag('BTC')
184
+ new HTML()
185
+ .heading(1, 'Main Title')
186
+ .heading(2, 'Subtitle')
187
+ .heading(3, HTML.bold('Bold heading'))
188
+ .heading(4, 'H4')
189
+ .heading(5, 'H5')
190
+ .heading(6, 'H6')
191
+ .paragraph('Normal paragraph text.')
192
+ .paragraph(HTML.bold('Bold') + ' and ' + HTML.italic('italic') + ' combined.')
193
+ .footer('Footer text — smaller and muted')
194
+ .divider() // <hr/>
195
+ .build()
163
196
  ```
164
197
 
165
- #### Dates, Emojis & Anchors
198
+ #### Lists (HTML)
166
199
 
167
200
  ```ts
168
- RT.dateTime(Date.now() / 1000) // Unix timestamp — rendered as local time for user
169
- RT.customEmoji('5368324170671202286') // custom emoji by ID
170
- RT.anchor('my-section') // invisible anchor for deep linking
201
+ new HTML()
202
+ // Unordered list
203
+ .ul('First item', 'Second item', HTML.bold('Bold item'))
204
+
205
+ // Ordered list
206
+ .ol('Step one', 'Step two', 'Step three')
207
+
208
+ // Task list (checkboxes)
209
+ .taskList(
210
+ { text: 'Completed task', checked: true },
211
+ { text: 'Pending task', checked: false },
212
+ { text: HTML.bold('Important task'), checked: false },
213
+ )
214
+ .build()
171
215
  ```
172
216
 
173
- #### Footnote References
217
+ #### Code Blocks
174
218
 
175
219
  ```ts
176
- const builder = new Builder()
177
-
178
- // Register a footnote — returns its numeric ID
179
- const refId = builder.reference(RT.url('https://source.com', RT.plain('Source')))
180
-
181
- builder.paragraph(
182
- RT.plain('Some claim '),
183
- RT.referenceLink(refId, RT.plain('[1]')), // clickable footnote link
184
- RT.plain(' is true.'),
185
- )
186
-
187
- await ctx.sendRichMessage(builder.build())
220
+ new HTML()
221
+ .pre('npm install @icanseeuanywhere/telekaf', 'bash')
222
+ .pre('SELECT * FROM users WHERE active = 1;', 'sql')
223
+ .pre(
224
+ `const bot = new Telegraf(token)
225
+ bot.launch()`,
226
+ 'javascript'
227
+ )
228
+ .pre('plain preformatted block without language')
229
+ .build()
188
230
  ```
189
231
 
190
- ---
191
-
192
- ### RichBlock — Block Elements
193
-
194
- `RichBlock` types are block-level elements that form the structure of a rich message.
232
+ Supported language identifiers: `javascript`, `typescript`, `python`, `bash`, `sql`, `json`, `html`, `css`, etc.
195
233
 
196
- #### Text Blocks
234
+ #### Blockquote & Pull Quote
197
235
 
198
236
  ```ts
199
- // Paragraph
200
- new Builder().paragraph(RT.plain('First sentence. '), RT.bold(RT.plain('Bold part.')))
201
-
202
- // Section headings (h1–h6)
203
- new Builder()
204
- .heading(1, RT.plain('Main Title'))
205
- .heading(2, RT.plain('Sub-section'))
206
- .heading(3, RT.plain('Sub-sub-section'))
207
-
208
- // Preformatted / code block
209
- new Builder().preformatted('SELECT * FROM users;', 'sql')
210
- new Builder().preformatted('plain preformatted block')
237
+ new HTML()
238
+ // Standard block quotation
239
+ .blockQuote(HTML.italic('"To be or not to be."'))
240
+
241
+ // Pull quotation with attribution (cite)
242
+ .pullQuote(
243
+ HTML.italic('"Design is not just what it looks like."'),
244
+ 'Steve Jobs'
245
+ )
211
246
 
212
- // Footer (smaller, muted text)
213
- new Builder().paragraph(RT.plain('Article body...'))
214
- // Footer block — add directly via .build() merge:
247
+ // Nested formatting inside quote
248
+ .blockQuote(
249
+ HTML.bold('Telekaf') + ' supports ' + HTML.marked('highlighted') +
250
+ ' and ' + HTML.spoiler('spoiler') + ' text inside quotes.'
251
+ )
252
+ .build()
215
253
  ```
216
254
 
217
- ```ts
218
- import type { RichBlock } from '@icanseeuanywhere/telekaf/types'
255
+ #### Math (HTML)
219
256
 
220
- const footer: RichBlock = {
221
- type: 'footer',
222
- texts: [RT.plain('Published by '), RT.bold(RT.plain('MyBot'))],
223
- }
257
+ ```ts
258
+ new HTML()
259
+ // Inline math (embed inside paragraph)
260
+ .paragraph(
261
+ 'The formula is: ' + HTML.inlineMath('a^2 + b^2 = c^2')
262
+ )
224
263
 
225
- const msg = new Builder()
226
- .heading(1, RT.plain('News Title'))
227
- .paragraph(RT.plain('Article body...'))
228
- .divider()
264
+ // Block math expression
265
+ .mathBlock('\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}')
266
+ .mathBlock('F(x) = \\int_{-\\infty}^{x} f(t)\\,dt')
267
+ .mathBlock('E = mc^2')
229
268
  .build()
230
-
231
- // Append footer manually
232
- msg.blocks.push(footer)
233
- await ctx.sendRichMessage(msg)
234
269
  ```
235
270
 
236
- #### Lists
271
+ #### Media — Photo, Video, Audio
237
272
 
238
273
  ```ts
239
- // Unordered list
240
- new Builder().list(false,
241
- [{ type: 'paragraph', texts: [RT.plain('First item')] }],
242
- [{ type: 'paragraph', texts: [RT.plain('Second item')] }],
243
- [{ type: 'paragraph', texts: [RT.plain('Third item')] }],
244
- )
245
-
246
- // Ordered list
247
- new Builder().list(true,
248
- [{ type: 'paragraph', texts: [RT.plain('Step one')] }],
249
- [{ type: 'paragraph', texts: [RT.plain('Step two')] }],
250
- )
274
+ new HTML()
275
+ // Photo
276
+ .photo('https://example.com/photo.jpg')
277
+ .photo('https://example.com/photo.jpg', 'Caption text')
278
+ .photo('https://example.com/photo.jpg', 'Spoiler photo', /* spoiler */ true)
279
+
280
+ // Video
281
+ .video('https://example.com/video.mp4')
282
+ .video('https://example.com/video.mp4', 'Caption text')
283
+ .video('https://example.com/video.mp4', 'Spoiler video', /* spoiler */ true)
284
+
285
+ // Audio / Voice note (.ogg for voice)
286
+ .audio('https://example.com/audio.mp3')
287
+ .audio('https://example.com/audio.mp3', 'Audio caption')
288
+
289
+ // With figcaption (HTML figure)
290
+ .raw('<figure><img src="https://example.com/photo.jpg"/><figcaption>Caption <b>bold</b></figcaption></figure>')
291
+ .build()
251
292
  ```
252
293
 
253
- #### Quotations
294
+ You can also use a Telegram `file_id` in place of a URL once the file is uploaded.
254
295
 
255
- ```ts
256
- // Block quotation
257
- new Builder().blockQuote(
258
- { type: 'paragraph', texts: [RT.italic(RT.plain('To be or not to be.'))] },
259
- )
296
+ #### Collage & Slideshow
260
297
 
261
- // Pull quotation (with optional caption)
262
- import type { RichBlock } from '@icanseeuanywhere/telekaf/types'
298
+ ```ts
299
+ const img = (src) => `<img src="${src}"/>`
300
+ const vid = (src) => `<video src="${src}"></video>`
301
+
302
+ new HTML()
303
+ // Collage — displays as a grid
304
+ .collage(
305
+ img('https://example.com/photo1.jpg'),
306
+ img('https://example.com/photo2.jpg'),
307
+ vid('https://example.com/clip.mp4'),
308
+ )
263
309
 
264
- const pullQuote: RichBlock = {
265
- type: 'pull_quotation',
266
- blocks: [{ type: 'paragraph', texts: [RT.plain('Design is not just what it looks like.')] }],
267
- caption: { type: 'caption', texts: [RT.plain('— Steve Jobs')] },
268
- }
310
+ // Slideshow — swipeable carousel
311
+ .slideshow(
312
+ img('https://example.com/photo1.jpg'),
313
+ img('https://example.com/photo2.jpg'),
314
+ img('https://example.com/photo3.jpg'),
315
+ )
316
+ .build()
269
317
  ```
270
318
 
271
- #### Media Blocks
319
+ #### Map
272
320
 
273
321
  ```ts
274
- // Single photo
275
- new Builder().photo('AgACAgIAAxk...', { type: 'caption', texts: [RT.plain('A beautiful sunset')] })
276
-
277
- // Single video
278
- new Builder().video('BAACAgIAAxk...', { type: 'caption', texts: [RT.plain('Tutorial video')] })
279
-
280
- // Single audio
281
- new Builder().audio('CQACAgIAAxk...')
282
-
283
- // Voice note
284
- import type { RichBlock } from '@icanseeuanywhere/telekaf/types'
285
- const voiceBlock: RichBlock = {
286
- type: 'voice_note',
287
- file_id: 'AwACAgIAAxk...',
288
- }
289
-
290
- // Collage (multiple photos/videos in a grid)
291
- import type { RichBlock } from '@icanseeuanywhere/telekaf/types'
292
- const collage: RichBlock = {
293
- type: 'collage',
294
- media: [
295
- { type: 'photo', file_id: 'AgACAgIA...' },
296
- { type: 'photo', file_id: 'AgACAgIB...' },
297
- { type: 'video', file_id: 'BAACAgIA...' },
298
- ],
299
- caption: { type: 'caption', texts: [RT.plain('Our gallery')] },
300
- }
301
-
302
- // Slideshow (swipeable gallery)
303
- const slideshow: RichBlock = {
304
- type: 'slideshow',
305
- media: [
306
- { type: 'photo', file_id: 'AgACAgIA...' },
307
- { type: 'photo', file_id: 'AgACAgIB...' },
308
- ],
309
- }
322
+ new HTML()
323
+ .map(-6.2088, 106.8456) // Jakarta
324
+ .map(48.8584, 2.2945, 16) // Paris, zoom 16
325
+ .map(51.5074, -0.1278, 14, 'Our office in London') // with caption
326
+ .build()
310
327
  ```
311
328
 
312
- #### Table
329
+ #### Table (HTML)
313
330
 
314
331
  ```ts
315
- import type { RichBlock } from '@icanseeuanywhere/telekaf/types'
316
-
317
- const table: RichBlock = {
318
- type: 'table',
319
- is_bordered: true,
320
- is_striped: true,
321
- cells: [
322
- // Header row
332
+ new HTML()
333
+ .table(
323
334
  [
324
- { type: 'table_cell', is_header: true, content: [{ type: 'paragraph', texts: [RT.plain('Name')] }] },
325
- { type: 'table_cell', is_header: true, content: [{ type: 'paragraph', texts: [RT.plain('Score')] }] },
335
+ ['Name', 'Version', 'Downloads'], // header row (hasHeader: true)
336
+ ['telekaf', '4.16.5', '—' ],
337
+ ['telegraf','4.16.3', '~120k/wk' ],
326
338
  ],
327
- // Data rows
328
- [
329
- { type: 'table_cell', content: [{ type: 'paragraph', texts: [RT.plain('Alice')] }] },
330
- { type: 'table_cell', content: [{ type: 'paragraph', texts: [RT.plain('98')] }] },
331
- ],
332
- [
333
- { type: 'table_cell', content: [{ type: 'paragraph', texts: [RT.plain('Bob')] }] },
334
- { type: 'table_cell', content: [{ type: 'paragraph', texts: [RT.plain('87')] }] },
335
- ],
336
- ],
337
- }
338
-
339
- await ctx.sendRichMessage({ blocks: [table] })
339
+ { bordered: true, striped: true, hasHeader: true }
340
+ )
341
+ .build()
340
342
  ```
341
343
 
342
- Column span and row span:
344
+ For advanced table markup (colspan, rowspan, alignment), use `.raw()`:
343
345
 
344
346
  ```ts
345
- { type: 'table_cell', colspan: 2, content: [{ type: 'paragraph', texts: [RT.plain('Merged cell')] }] }
346
- { type: 'table_cell', rowspan: 3, align: 'center', content: [...] }
347
+ new HTML()
348
+ .raw(
349
+ '<table bordered striped>' +
350
+ '<tr><th>Name</th><th colspan="2">Details</th></tr>' +
351
+ '<tr><td>Alice</td><td align="center">98</td><td align="right">Pass</td></tr>' +
352
+ '</table>'
353
+ )
354
+ .build()
347
355
  ```
348
356
 
349
- #### Expandable Details
357
+ #### Details (Expandable)
350
358
 
351
359
  ```ts
352
- import type { RichBlock } from '@icanseeuanywhere/telekaf/types'
353
-
354
- const details: RichBlock = {
355
- type: 'details',
356
- header: [RT.plain('Click to expand')],
357
- blocks: [
358
- { type: 'paragraph', texts: [RT.plain('Hidden content revealed on tap.')] },
359
- ],
360
- }
360
+ new HTML()
361
+ // Collapsed by default
362
+ .details('Click to expand', '<p>Hidden content here.</p>')
363
+
364
+ // Open by default
365
+ .details(
366
+ HTML.bold('Changelog v4.16.5'),
367
+ '<ul><li>Fix InputRichMessage format</li><li>Add RichHTMLBuilder</li></ul>',
368
+ /* open */ true
369
+ )
370
+ .build()
361
371
  ```
362
372
 
363
- #### Map
373
+ #### Footnote References (HTML)
364
374
 
365
375
  ```ts
366
- import type { RichBlock } from '@icanseeuanywhere/telekaf/types'
367
-
368
- const map: RichBlock = {
369
- type: 'map',
370
- latitude: -6.2088,
371
- longitude: 106.8456,
372
- zoom: 14,
373
- caption: { type: 'caption', texts: [RT.plain('Our office in Jakarta')] },
374
- }
376
+ new HTML()
377
+ .paragraph(
378
+ 'Telekaf ' + HTML.ref('note-1', '[1]') + ' is based on Telegraf ' + HTML.ref('note-2', '[2]') + '.'
379
+ )
380
+ .divider()
381
+ .referenceDefinition('note-1', HTML.url('https://npmjs.com/package/@icanseeuanywhere/telekaf', 'telekaf on npm'))
382
+ .referenceDefinition('note-2', HTML.url('https://github.com/telegraf/telegraf', 'telegraf on GitHub'))
383
+ .build()
375
384
  ```
376
385
 
377
- #### Divider & Anchor
386
+ #### Anchors & In-document Links
378
387
 
379
388
  ```ts
380
- new Builder().divider() // horizontal rule
381
-
382
- import type { RichBlock } from '@icanseeuanywhere/telekaf/types'
383
- const anchor: RichBlock = { type: 'anchor', name: 'section-1' } // jump target
389
+ new HTML()
390
+ .raw(HTML.anchor('section-intro')) // invisible anchor target
391
+ .heading(2, 'Introduction')
392
+ .paragraph('Jump to: ' + HTML.anchorLink('section-api', 'API Reference'))
393
+ .raw(HTML.anchor('section-api'))
394
+ .heading(2, 'API Reference')
395
+ .build()
384
396
  ```
385
397
 
386
- #### Mathematical Expression Block
398
+ #### Special Elements
387
399
 
388
400
  ```ts
389
- import type { RichBlock } from '@icanseeuanywhere/telekaf/types'
390
- const mathBlock: RichBlock = {
391
- type: 'mathematical_expression',
392
- expression: '\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}',
393
- }
394
- ```
395
-
396
- #### Thinking Block (AI reasoning)
401
+ new HTML()
402
+ // AI thinking block (visible in sendRichMessageDraft)
403
+ .thinking(HTML.italic('Analyzing your request...'))
397
404
 
398
- Show a collapsible "thinking" section, useful for AI bots:
399
-
400
- ```ts
401
- new Builder().thinking(
402
- { type: 'paragraph', texts: [RT.plain('Let me reason through this step by step...')] },
403
- { type: 'paragraph', texts: [RT.plain('First, I consider the user intent.')] },
404
- )
405
+ // Raw HTML for anything not covered by builder methods
406
+ .raw('<tg-map lat="41.9" long="12.5" zoom="14"/>')
407
+ .raw('<aside>Pull quote<cite>The Author</cite></aside>')
408
+ .build()
405
409
  ```
406
410
 
407
411
  ---
408
412
 
409
- ### `RichMessageBuilder` API
413
+ ### RichMarkdownBuilder
410
414
 
411
- `RichMessageBuilder` is a fluent builder for constructing `InputRichMessage` objects.
415
+ Same API surface as `RichHTMLBuilder` but produces Markdown output:
412
416
 
413
417
  ```ts
414
418
  import { RichMessage } from '@icanseeuanywhere/telekaf'
415
- const { RichMessageBuilder: Builder, RT } = RichMessage
416
-
417
- const msg = new Builder()
418
- .heading(1, RT.plain('Title'))
419
- .paragraph(RT.plain('Body text'))
420
- .preformatted('code here', 'js')
419
+ const { RichMarkdownBuilder: MD } = RichMessage
420
+
421
+ const msg = new MD()
422
+ .heading(1, 'Rich Markdown')
423
+ .paragraph(
424
+ MD.bold('bold') + ' ' +
425
+ MD.italic('italic') + ' ' +
426
+ MD.strikethrough('strike') + ' ' +
427
+ MD.marked('==highlighted==') + ' ' +
428
+ MD.spoiler('||spoiler||') + ' ' +
429
+ MD.code('`code`')
430
+ )
421
431
  .divider()
422
- .list(false,
423
- [{ type: 'paragraph', texts: [RT.plain('Item A')] }],
424
- [{ type: 'paragraph', texts: [RT.plain('Item B')] }],
432
+ .ul('Item 1', 'Item 2', MD.bold('Bold item'))
433
+ .ol('Step 1', 'Step 2', 'Step 3')
434
+ .taskList(
435
+ { text: 'Done', checked: true },
436
+ { text: 'Pending', checked: false },
425
437
  )
426
- .blockQuote({ type: 'paragraph', texts: [RT.plain('Quote')] })
427
- .thinking({ type: 'paragraph', texts: [RT.plain('AI reasoning...')] })
428
- .photo('AgACAgIA...')
429
- .video('BAACAgIA...')
430
- .audio('CQACAgIA...')
431
- .build() // returns InputRichMessage
438
+ .divider()
439
+ .pre('console.log("hello")', 'javascript')
440
+ .divider()
441
+ .table(
442
+ ['Name', 'Score'],
443
+ [['Alice', '98'], ['Bob', '87']],
444
+ ['left', 'center'],
445
+ )
446
+ .divider()
447
+ .mathBlock('E = mc^2')
448
+ .divider()
449
+ .blockQuote(MD.italic('"Quote text"'), '— Author')
450
+ .divider()
451
+ .photo('https://example.com/photo.jpg', 'Photo caption')
452
+ .collage('https://example.com/1.jpg', 'https://example.com/2.jpg')
453
+ .slideshow('https://example.com/1.jpg', 'https://example.com/2.jpg')
454
+ .divider()
455
+ .details('Expand me', '### Hidden heading\n\n- item 1\n- item 2')
456
+ .divider()
457
+ .paragraph('See footnote' + MD.sup('[1]'))
458
+ .footnote('1', MD.url('https://t.me/kafk6', '@kafka'))
459
+ .build()
432
460
  ```
433
461
 
434
- #### Available builder methods
435
-
436
- | Method | Description |
437
- |---|---|
438
- | `.paragraph(...texts)` | Add a paragraph block |
439
- | `.heading(level, ...texts)` | Add a heading (h1–h6) |
440
- | `.preformatted(text, language?)` | Add a code/preformatted block |
441
- | `.divider()` | Add a horizontal divider |
442
- | `.list(isOrdered, ...items)` | Add an ordered or unordered list |
443
- | `.blockQuote(...blocks)` | Add a block quotation |
444
- | `.thinking(...blocks)` | Add an AI thinking block |
445
- | `.photo(fileId, caption?)` | Add a photo block |
446
- | `.video(fileId, caption?)` | Add a video block |
447
- | `.audio(fileId, caption?)` | Add an audio block |
448
- | `.reference(richText)` | Register a footnote, returns its numeric ID |
449
- | `.build()` | Returns the final `InputRichMessage` |
450
-
451
- ---
452
-
453
- ### RT Helpers
454
-
455
- `RT` is a collection of factory functions for creating `RichText` nodes concisely.
462
+ **All Markdown inline helpers:**
456
463
 
457
464
  ```ts
458
- import { RichMessage } from '@icanseeuanywhere/telekaf'
459
- const { RT } = RichMessage
460
-
461
- RT.plain('text')
462
- RT.bold(RT.plain('bold'))
463
- RT.italic(RT.plain('italic'))
464
- RT.underline(RT.plain('underline'))
465
- RT.strikethrough(RT.plain('strike'))
466
- RT.spoiler(RT.plain('spoiler'))
467
- RT.marked(RT.plain('highlight'))
468
- RT.subscript(RT.plain('sub'))
469
- RT.superscript(RT.plain('sup'))
470
- RT.code(RT.plain('x++'), 'cpp')
471
- RT.math('a^2 + b^2 = c^2')
472
- RT.url('https://t.me', RT.plain('Telegram'))
473
- RT.email('hi@bot.com')
474
- RT.phone('+1234567890')
475
- RT.bankCard('4111111111111111')
476
- RT.mention(RT.plain('Alice'), 123456789)
477
- RT.mention(RT.plain('@alice'), undefined, 'alice')
478
- RT.botCommand('/start')
479
- RT.botCommand('/help', 'mybot')
480
- RT.hashtag('news')
481
- RT.cashtag('ETH')
482
- RT.dateTime(1750000000)
483
- RT.customEmoji('5368324170671202286')
484
- RT.anchor('section-id')
485
- RT.anchorLink('section-id', RT.plain('Go to section'))
486
- RT.reference(1) // footnote number
487
- RT.referenceLink(1, RT.plain('[1]')) // clickable footnote
465
+ MD.bold('text') // **text**
466
+ MD.italic('text') // *text*
467
+ MD.underline('text') // <u>text</u>
468
+ MD.strikethrough('text') // ~~text~~
469
+ MD.spoiler('text') // ||text||
470
+ MD.code('text') // `text`
471
+ MD.marked('text') // ==text==
472
+ MD.sub('text') // <sub>text</sub>
473
+ MD.sup('text') // <sup>text</sup>
474
+ MD.url('https://...', 'label') // [label](url)
475
+ MD.email('a@b.com', 'label') // [label](mailto:a@b.com)
476
+ MD.phone('+123', 'label') // [label](tel:+123)
477
+ MD.mention(123456789, 'Alice') // [Alice](tg://user?id=123456789)
478
+ MD.customEmoji('id', '👍') // ![👍](tg://emoji?id=...)
479
+ MD.time(1647531900, 'wDT') // ![](tg://time?unix=...&format=wDT)
480
+ MD.inlineMath('a^2') // $a^2$
488
481
  ```
489
482
 
490
483
  ---
491
484
 
492
- ### Streaming with `sendRichMessageDraft`
485
+ ### Streaming with sendRichMessageDraft
493
486
 
494
- `sendRichMessageDraft` allows bots to stream partial rich messages, enabling AI bots to show replies as they are generated — similar to how LLMs stream tokens.
487
+ `sendRichMessageDraft` streams a partial rich message in private chats. The draft is ephemeral (30-second preview). You **must** finalize with `sendRichMessage` to persist it.
495
488
 
496
- ```ts
497
- import { RichMessage } from '@icanseeuanywhere/telekaf'
498
- const { RichMessageBuilder: Builder, RT } = RichMessage
489
+ - `chat_id` — private chat only (integer)
490
+ - `draft_id` — non-zero integer; updates with the same `draft_id` are animated
499
491
 
492
+ ```ts
500
493
  bot.command('ai', async (ctx) => {
501
- // Send an initial draft (partial content)
502
- await ctx.sendRichMessageDraft(
503
- new Builder()
504
- .thinking({ type: 'paragraph', texts: [RT.plain('Analyzing your request...')] })
505
- .build()
506
- )
494
+ const DRAFT_ID = 1 // any non-zero integer
507
495
 
508
- // Simulate streaming by sending updated drafts
509
496
  const steps = [
510
- 'Gathering information...',
511
- 'Processing data...',
512
- 'Composing response...',
497
+ 'Reading your request...',
498
+ 'Searching knowledge base...',
499
+ 'Composing answer...',
513
500
  ]
514
501
 
502
+ // Stream thinking blocks
515
503
  for (const step of steps) {
516
- await new Promise((r) => setTimeout(r, 800))
517
504
  await ctx.sendRichMessageDraft(
518
- new Builder()
519
- .thinking({ type: 'paragraph', texts: [RT.plain(step)] })
520
- .build()
505
+ DRAFT_ID,
506
+ new HTML().thinking(HTML.italic(step)).build()
521
507
  )
508
+ await new Promise((r) => setTimeout(r, 900))
522
509
  }
523
510
 
524
- // Send the final complete message
511
+ // Finalize — must call sendRichMessage after streaming
525
512
  await ctx.sendRichMessage(
526
- new Builder()
527
- .heading(2, RT.plain('Answer'))
528
- .paragraph(RT.plain('Here is the final response from the AI.'))
513
+ new HTML()
514
+ .heading(2, '🤖 AI Response')
515
+ .paragraph('Here is the final answer from the AI.')
516
+ .divider()
517
+ .footer(HTML.url('https://t.me/kafk6', '@kafka'))
529
518
  .build()
530
519
  )
531
520
  })
532
521
  ```
533
522
 
523
+ You can also call `sendRichMessageDraft` explicitly:
524
+
525
+ ```ts
526
+ // Explicit
527
+ await ctx.telegram.sendRichMessageDraft(
528
+ ctx.chat.id, // private chat integer ID
529
+ 42, // draft_id
530
+ new HTML().thinking('Processing...').build(),
531
+ { message_thread_id: 123 }
532
+ )
533
+ ```
534
+
535
+ ---
536
+
537
+ ### reply_markup with Rich Messages
538
+
539
+ All `sendRichMessage` calls accept a `reply_markup` option with inline keyboards:
540
+
541
+ ```ts
542
+ import { Markup } from '@icanseeuanywhere/telekaf'
543
+
544
+ const msg = new HTML()
545
+ .heading(2, 'Choose an option')
546
+ .paragraph('Tap a button below:')
547
+ .build()
548
+
549
+ await ctx.sendRichMessage(msg, {
550
+ reply_markup: Markup.inlineKeyboard([
551
+ [
552
+ Markup.button.callback('✅ Yes', 'answer:yes'),
553
+ Markup.button.callback('❌ No', 'answer:no'),
554
+ ],
555
+ [Markup.button.url('🌐 Visit', 'https://t.me/kafk6')],
556
+ ]).reply_markup,
557
+ })
558
+
559
+ bot.action('answer:yes', async (ctx) => {
560
+ await ctx.answerCbQuery('You chose Yes!')
561
+ })
562
+ ```
563
+
564
+ Or use `reply_markup` directly:
565
+
566
+ ```ts
567
+ await ctx.sendRichMessage(msg, {
568
+ reply_markup: {
569
+ inline_keyboard: [
570
+ [{ text: 'Button 1', callback_data: 'btn1' }],
571
+ [{ text: 'Open URL', url: 'https://telegram.org' }],
572
+ ],
573
+ },
574
+ })
575
+ ```
576
+
534
577
  ---
535
578
 
536
579
  ### Inline / Web App Queries
537
580
 
538
- `InputRichMessageContent` can be returned in results for inline queries, guest queries, and Web App queries.
581
+ Use `InputRichMessageContent` as `input_message_content` in inline query results:
539
582
 
540
583
  ```ts
541
584
  import { RichMessage } from '@icanseeuanywhere/telekaf'
542
- const { RichMessageBuilder: Builder, RT } = RichMessage
585
+ const { RichHTMLBuilder: HTML } = RichMessage
543
586
 
544
587
  bot.on('inline_query', async (ctx) => {
545
588
  const richContent: RichMessage.InputRichMessageContent = {
546
- rich_message: new Builder()
547
- .heading(1, RT.plain('Inline Result'))
548
- .paragraph(RT.plain('This was sent from an inline query!'))
589
+ rich_message: new HTML()
590
+ .heading(1, 'Result from Inline Query')
591
+ .paragraph('Sent via ' + HTML.url('https://t.me/kafk6', '@kafka') + '.')
549
592
  .build(),
550
593
  }
551
594
 
@@ -562,47 +605,35 @@ bot.on('inline_query', async (ctx) => {
562
605
 
563
606
  ---
564
607
 
565
- ### TypeScript Usage with Rich Messages
608
+ ### TypeScript
566
609
 
567
- All types are exported from `telegraf` under the `RichMessage` namespace:
610
+ All types are exported under the `RichMessage` namespace:
568
611
 
569
612
  ```ts
570
613
  import { RichMessage } from '@icanseeuanywhere/telekaf'
614
+ import type { ExtraSendRichMessage, ExtraSendRichMessageDraft } from '@icanseeuanywhere/telekaf/types'
571
615
 
572
- // Individual types
573
- const text: RichMessage.RichText = RT.bold(RT.plain('hello'))
574
- const block: RichMessage.RichBlock = { type: 'paragraph', texts: [text] }
575
- const msg: RichMessage.InputRichMessage = { blocks: [block] }
576
- const received: RichMessage.RichMessage = ctx.message.rich_message // on incoming messages
577
-
578
- // All RichText types
579
- type RichText = RichMessage.RichText
580
- type RichTextBold = RichMessage.RichTextBold
581
- type RichTextItalic = RichMessage.RichTextItalic
582
- // ... etc
616
+ // Builder types
617
+ const builder: RichMessage.RichHTMLBuilder = new RichMessage.RichHTMLBuilder()
618
+ const mdBuilder: RichMessage.RichMarkdownBuilder = new RichMessage.RichMarkdownBuilder()
583
619
 
584
- // All RichBlock types
585
- type RichBlock = RichMessage.RichBlock
586
- type RichBlockParagraph = RichMessage.RichBlockParagraph
587
- type RichBlockTable = RichMessage.RichBlockTable
588
- // ... etc
589
-
590
- // Extra types for sendRichMessage
591
- import type { ExtraSendRichMessage } from '@icanseeuanywhere/telekaf/types'
592
- ```
620
+ // Message types
621
+ const input: RichMessage.InputRichMessage = { html: '<p>hello</p>' }
622
+ const content: RichMessage.InputRichMessageContent = { rich_message: input }
623
+ const received: RichMessage.RichMessage = ctx.message.rich_message
593
624
 
594
- You can also import types directly if needed:
625
+ // Extra types
626
+ const extra: ExtraSendRichMessage = {
627
+ disable_notification: true,
628
+ reply_markup: { inline_keyboard: [] },
629
+ }
595
630
 
596
- ```ts
597
- import type {
598
- RichText,
599
- RichBlock,
600
- RichMessage,
601
- InputRichMessage,
602
- InputRichMessageContent,
603
- RichMessageBuilder,
604
- RT,
605
- } from '@icanseeuanywhere/telekaf/core/types/rich-message'
631
+ // Custom context with rich message
632
+ import { Context, Telegraf } from '@icanseeuanywhere/telekaf'
633
+ interface MyCtx extends Context {
634
+ session?: { lastDraftId: number }
635
+ }
636
+ const bot = new Telegraf<MyCtx>(process.env.BOT_TOKEN)
606
637
  ```
607
638
 
608
639
  ---
@@ -795,7 +826,7 @@ process.once('SIGTERM', () => bot.stop('SIGTERM'))
795
826
  ### Webhooks
796
827
 
797
828
  ```TS
798
- import { Telegraf } from "telegraf";
829
+ import { Telegraf } from "@icanseeuanywhere/telekaf";
799
830
  import { message } from '@icanseeuanywhere/telekaf/filters';
800
831
 
801
832
  const bot = new Telegraf(token);