@icanseeuanywhere/telekaf 4.16.3

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 (172) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +978 -0
  3. package/filters.d.ts +1 -0
  4. package/filters.js +1 -0
  5. package/format.d.ts +1 -0
  6. package/format.js +1 -0
  7. package/future.d.ts +1 -0
  8. package/future.js +1 -0
  9. package/lib/button.js +101 -0
  10. package/lib/cli.mjs +105 -0
  11. package/lib/composer.js +582 -0
  12. package/lib/context.js +1277 -0
  13. package/lib/core/helpers/args.js +58 -0
  14. package/lib/core/helpers/check.js +56 -0
  15. package/lib/core/helpers/compact.js +17 -0
  16. package/lib/core/helpers/deunionize.js +13 -0
  17. package/lib/core/helpers/formatting.js +91 -0
  18. package/lib/core/helpers/util.js +50 -0
  19. package/lib/core/network/client.js +320 -0
  20. package/lib/core/network/error.js +21 -0
  21. package/lib/core/network/multipart-stream.js +61 -0
  22. package/lib/core/network/polling.js +87 -0
  23. package/lib/core/network/webhook.js +54 -0
  24. package/lib/core/types/rich-message.js +102 -0
  25. package/lib/core/types/typegram.js +28 -0
  26. package/lib/filters.js +69 -0
  27. package/lib/format.js +38 -0
  28. package/lib/future.js +166 -0
  29. package/lib/index.js +49 -0
  30. package/lib/input.js +61 -0
  31. package/lib/markup.js +111 -0
  32. package/lib/middleware.js +2 -0
  33. package/lib/reactions.js +84 -0
  34. package/lib/router.js +46 -0
  35. package/lib/scenes/base.js +39 -0
  36. package/lib/scenes/context.js +104 -0
  37. package/lib/scenes/index.js +21 -0
  38. package/lib/scenes/stage.js +49 -0
  39. package/lib/scenes/wizard/context.js +31 -0
  40. package/lib/scenes/wizard/index.js +45 -0
  41. package/lib/scenes.js +17 -0
  42. package/lib/session.js +166 -0
  43. package/lib/telegraf.js +246 -0
  44. package/lib/telegram-types.js +6 -0
  45. package/lib/telegram.js +1265 -0
  46. package/lib/types.js +2 -0
  47. package/lib/utils.js +5 -0
  48. package/markup.d.ts +1 -0
  49. package/markup.js +1 -0
  50. package/package.json +138 -0
  51. package/scenes.d.ts +1 -0
  52. package/scenes.js +1 -0
  53. package/session.d.ts +1 -0
  54. package/session.js +1 -0
  55. package/src/button.ts +182 -0
  56. package/src/composer.ts +1008 -0
  57. package/src/context.ts +1739 -0
  58. package/src/core/helpers/args.ts +63 -0
  59. package/src/core/helpers/check.ts +71 -0
  60. package/src/core/helpers/compact.ts +18 -0
  61. package/src/core/helpers/deunionize.ts +26 -0
  62. package/src/core/helpers/formatting.ts +119 -0
  63. package/src/core/helpers/util.ts +96 -0
  64. package/src/core/network/client.ts +396 -0
  65. package/src/core/network/error.ts +29 -0
  66. package/src/core/network/multipart-stream.ts +45 -0
  67. package/src/core/network/polling.ts +94 -0
  68. package/src/core/network/webhook.ts +58 -0
  69. package/src/core/types/rich-message.ts +474 -0
  70. package/src/core/types/typegram.ts +55 -0
  71. package/src/filters.ts +109 -0
  72. package/src/format.ts +110 -0
  73. package/src/future.ts +231 -0
  74. package/src/index.ts +18 -0
  75. package/src/input.ts +59 -0
  76. package/src/markup.ts +142 -0
  77. package/src/middleware.ts +24 -0
  78. package/src/reactions.ts +118 -0
  79. package/src/router.ts +55 -0
  80. package/src/scenes/base.ts +52 -0
  81. package/src/scenes/context.ts +136 -0
  82. package/src/scenes/index.ts +21 -0
  83. package/src/scenes/stage.ts +71 -0
  84. package/src/scenes/wizard/context.ts +58 -0
  85. package/src/scenes/wizard/index.ts +63 -0
  86. package/src/scenes.ts +1 -0
  87. package/src/session.ts +204 -0
  88. package/src/telegraf.ts +354 -0
  89. package/src/telegram-types.ts +250 -0
  90. package/src/telegram.ts +1671 -0
  91. package/src/types.ts +2 -0
  92. package/src/utils.ts +1 -0
  93. package/types.d.ts +1 -0
  94. package/types.js +1 -0
  95. package/typings/button.d.ts +36 -0
  96. package/typings/button.d.ts.map +1 -0
  97. package/typings/composer.d.ts +227 -0
  98. package/typings/composer.d.ts.map +1 -0
  99. package/typings/context.d.ts +693 -0
  100. package/typings/context.d.ts.map +1 -0
  101. package/typings/core/helpers/args.d.ts +11 -0
  102. package/typings/core/helpers/args.d.ts.map +1 -0
  103. package/typings/core/helpers/check.d.ts +56 -0
  104. package/typings/core/helpers/check.d.ts.map +1 -0
  105. package/typings/core/helpers/compact.d.ts +4 -0
  106. package/typings/core/helpers/compact.d.ts.map +1 -0
  107. package/typings/core/helpers/deunionize.d.ts +18 -0
  108. package/typings/core/helpers/deunionize.d.ts.map +1 -0
  109. package/typings/core/helpers/formatting.d.ts +30 -0
  110. package/typings/core/helpers/formatting.d.ts.map +1 -0
  111. package/typings/core/helpers/util.d.ts +27 -0
  112. package/typings/core/helpers/util.d.ts.map +1 -0
  113. package/typings/core/network/client.d.ts +55 -0
  114. package/typings/core/network/client.d.ts.map +1 -0
  115. package/typings/core/network/error.d.ts +16 -0
  116. package/typings/core/network/error.d.ts.map +1 -0
  117. package/typings/core/network/multipart-stream.d.ts +19 -0
  118. package/typings/core/network/multipart-stream.d.ts.map +1 -0
  119. package/typings/core/network/polling.d.ts +16 -0
  120. package/typings/core/network/polling.d.ts.map +1 -0
  121. package/typings/core/network/webhook.d.ts +7 -0
  122. package/typings/core/network/webhook.d.ts.map +1 -0
  123. package/typings/core/types/rich-message.d.ts +295 -0
  124. package/typings/core/types/rich-message.d.ts.map +1 -0
  125. package/typings/core/types/typegram.d.ts +45 -0
  126. package/typings/core/types/typegram.d.ts.map +1 -0
  127. package/typings/filters.d.ts +18 -0
  128. package/typings/filters.d.ts.map +1 -0
  129. package/typings/format.d.ts +22 -0
  130. package/typings/format.d.ts.map +1 -0
  131. package/typings/future.d.ts +12 -0
  132. package/typings/future.d.ts.map +1 -0
  133. package/typings/index.d.ts +16 -0
  134. package/typings/index.d.ts.map +1 -0
  135. package/typings/input.d.ts +55 -0
  136. package/typings/input.d.ts.map +1 -0
  137. package/typings/markup.d.ts +27 -0
  138. package/typings/markup.d.ts.map +1 -0
  139. package/typings/middleware.d.ts +8 -0
  140. package/typings/middleware.d.ts.map +1 -0
  141. package/typings/reactions.d.ts +32 -0
  142. package/typings/reactions.d.ts.map +1 -0
  143. package/typings/router.d.ts +21 -0
  144. package/typings/router.d.ts.map +1 -0
  145. package/typings/scenes/base.d.ts +22 -0
  146. package/typings/scenes/base.d.ts.map +1 -0
  147. package/typings/scenes/context.d.ts +36 -0
  148. package/typings/scenes/context.d.ts.map +1 -0
  149. package/typings/scenes/index.d.ts +11 -0
  150. package/typings/scenes/index.d.ts.map +1 -0
  151. package/typings/scenes/stage.d.ts +24 -0
  152. package/typings/scenes/stage.d.ts.map +1 -0
  153. package/typings/scenes/wizard/context.d.ts +29 -0
  154. package/typings/scenes/wizard/context.d.ts.map +1 -0
  155. package/typings/scenes/wizard/index.d.ts +16 -0
  156. package/typings/scenes/wizard/index.d.ts.map +1 -0
  157. package/typings/scenes.d.ts +2 -0
  158. package/typings/scenes.d.ts.map +1 -0
  159. package/typings/session.d.ts +55 -0
  160. package/typings/session.d.ts.map +1 -0
  161. package/typings/telegraf.d.ts +117 -0
  162. package/typings/telegraf.d.ts.map +1 -0
  163. package/typings/telegram-types.d.ts +134 -0
  164. package/typings/telegram-types.d.ts.map +1 -0
  165. package/typings/telegram.d.ts +691 -0
  166. package/typings/telegram.d.ts.map +1 -0
  167. package/typings/types.d.ts +3 -0
  168. package/typings/types.d.ts.map +1 -0
  169. package/typings/utils.d.ts +2 -0
  170. package/typings/utils.d.ts.map +1 -0
  171. package/utils.d.ts +1 -0
  172. package/utils.js +1 -0
package/README.md ADDED
@@ -0,0 +1,978 @@
1
+ <header>
2
+
3
+ <div align="center">
4
+ <img src="docs/assets/logo.svg" alt="logo" height="90" align="center">
5
+ <h1 align="center">telegraf.js</h1>
6
+
7
+ <p>Modern Telegram Bot API framework for Node.js</p>
8
+
9
+ <a href="https://core.telegram.org/bots/api">
10
+ <img src="https://img.shields.io/badge/Bot%20API-v10.1-f36caf.svg?style=flat-square" alt="Bot API Version" />
11
+ </a>
12
+ <a href="https://packagephobia.com/result?p=telegraf,node-telegram-bot-api">
13
+ <img src="https://flat.badgen.net/packagephobia/install/telegraf" alt="install size" />
14
+ </a>
15
+ <a href="https://github.com/telegraf/telegraf">
16
+ <img src="https://img.shields.io/github/languages/top/telegraf/telegraf?style=flat-square&logo=github" alt="GitHub top language" />
17
+ </a>
18
+ <a href="https://telegram.me/TelegrafJSChat">
19
+ <img src="https://img.shields.io/badge/English%20chat-grey?style=flat-square&logo=telegram" alt="English chat" />
20
+ </a>
21
+ </div>
22
+
23
+ </header>
24
+
25
+ > **telekaf** (`@icanseeuanywhere/telekaf`) is an up-to-date fork of [telegraf](https://github.com/telegraf/telegraf) — the popular Telegram Bot framework for Node.js.
26
+ > This package tracks the latest Telegram Bot API releases and ships features ahead of the upstream package.
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
+
29
+ ---
30
+
31
+ ## 🆕 Rich Messages — Bot API 10.1
32
+
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
+
35
+ ### Table of Contents
36
+
37
+ - [Quick Start](#rich-messages-quick-start)
38
+ - [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)
43
+ - [Streaming with sendRichMessageDraft](#streaming-with-sendrichmessagedraft)
44
+ - [Inline / Web App Queries](#inline--web-app-queries)
45
+ - [TypeScript Usage](#typescript-usage-with-rich-messages)
46
+
47
+ ---
48
+
49
+ ### Rich Messages Quick Start
50
+
51
+ ```ts
52
+ import { Telegraf, RichMessage } from '@icanseeuanywhere/telekaf'
53
+ const { RichMessageBuilder: Builder, RT } = RichMessage
54
+
55
+ const bot = new Telegraf(process.env.BOT_TOKEN)
56
+
57
+ 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.'))
61
+ .divider()
62
+ .preformatted('npm install telegraf', 'bash')
63
+ .build()
64
+
65
+ await ctx.sendRichMessage(msg)
66
+ })
67
+
68
+ bot.launch()
69
+ process.once('SIGINT', () => bot.stop('SIGINT'))
70
+ process.once('SIGTERM', () => bot.stop('SIGTERM'))
71
+ ```
72
+
73
+ ---
74
+
75
+ ### Sending Methods
76
+
77
+ There are three ways to send rich messages:
78
+
79
+ | Method | Description |
80
+ |---|---|
81
+ | `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 |
86
+
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()
92
+
93
+ // Sends and quotes the user's message
94
+ await ctx.replyWithRichMessageContent(msg)
95
+ })
96
+ ```
97
+
98
+ The `extra` parameter accepts:
99
+
100
+ ```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 | ...
107
+ }
108
+ ```
109
+
110
+ ---
111
+
112
+ ### RichText — Inline Elements
113
+
114
+ `RichText` types are inline elements that compose the text inside paragraphs, headings, captions, etc.
115
+
116
+ #### Text Formatting
117
+
118
+ ```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:
131
+
132
+ ```ts
133
+ RT.bold(RT.italic(RT.plain('bold and italic')))
134
+ RT.underline(RT.strikethrough(RT.plain('underlined and crossed')))
135
+ ```
136
+
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
+ ```
143
+
144
+ #### Links & References
145
+
146
+ ```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
155
+
156
+ ```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')
163
+ ```
164
+
165
+ #### Dates, Emojis & Anchors
166
+
167
+ ```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
171
+ ```
172
+
173
+ #### Footnote References
174
+
175
+ ```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())
188
+ ```
189
+
190
+ ---
191
+
192
+ ### RichBlock — Block Elements
193
+
194
+ `RichBlock` types are block-level elements that form the structure of a rich message.
195
+
196
+ #### Text Blocks
197
+
198
+ ```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')
211
+
212
+ // Footer (smaller, muted text)
213
+ new Builder().paragraph(RT.plain('Article body...'))
214
+ // Footer block — add directly via .build() merge:
215
+ ```
216
+
217
+ ```ts
218
+ import type { RichBlock } from '@icanseeuanywhere/telekaf/types'
219
+
220
+ const footer: RichBlock = {
221
+ type: 'footer',
222
+ texts: [RT.plain('Published by '), RT.bold(RT.plain('MyBot'))],
223
+ }
224
+
225
+ const msg = new Builder()
226
+ .heading(1, RT.plain('News Title'))
227
+ .paragraph(RT.plain('Article body...'))
228
+ .divider()
229
+ .build()
230
+
231
+ // Append footer manually
232
+ msg.blocks.push(footer)
233
+ await ctx.sendRichMessage(msg)
234
+ ```
235
+
236
+ #### Lists
237
+
238
+ ```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
+ )
251
+ ```
252
+
253
+ #### Quotations
254
+
255
+ ```ts
256
+ // Block quotation
257
+ new Builder().blockQuote(
258
+ { type: 'paragraph', texts: [RT.italic(RT.plain('To be or not to be.'))] },
259
+ )
260
+
261
+ // Pull quotation (with optional caption)
262
+ import type { RichBlock } from '@icanseeuanywhere/telekaf/types'
263
+
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
+ }
269
+ ```
270
+
271
+ #### Media Blocks
272
+
273
+ ```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
+ }
310
+ ```
311
+
312
+ #### Table
313
+
314
+ ```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
323
+ [
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')] }] },
326
+ ],
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] })
340
+ ```
341
+
342
+ Column span and row span:
343
+
344
+ ```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
+ ```
348
+
349
+ #### Expandable Details
350
+
351
+ ```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
+ }
361
+ ```
362
+
363
+ #### Map
364
+
365
+ ```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
+ }
375
+ ```
376
+
377
+ #### Divider & Anchor
378
+
379
+ ```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
384
+ ```
385
+
386
+ #### Mathematical Expression Block
387
+
388
+ ```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)
397
+
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
+ ```
406
+
407
+ ---
408
+
409
+ ### `RichMessageBuilder` API
410
+
411
+ `RichMessageBuilder` is a fluent builder for constructing `InputRichMessage` objects.
412
+
413
+ ```ts
414
+ 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')
421
+ .divider()
422
+ .list(false,
423
+ [{ type: 'paragraph', texts: [RT.plain('Item A')] }],
424
+ [{ type: 'paragraph', texts: [RT.plain('Item B')] }],
425
+ )
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
432
+ ```
433
+
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.
456
+
457
+ ```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
488
+ ```
489
+
490
+ ---
491
+
492
+ ### Streaming with `sendRichMessageDraft`
493
+
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.
495
+
496
+ ```ts
497
+ import { RichMessage } from '@icanseeuanywhere/telekaf'
498
+ const { RichMessageBuilder: Builder, RT } = RichMessage
499
+
500
+ 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
+ )
507
+
508
+ // Simulate streaming by sending updated drafts
509
+ const steps = [
510
+ 'Gathering information...',
511
+ 'Processing data...',
512
+ 'Composing response...',
513
+ ]
514
+
515
+ for (const step of steps) {
516
+ await new Promise((r) => setTimeout(r, 800))
517
+ await ctx.sendRichMessageDraft(
518
+ new Builder()
519
+ .thinking({ type: 'paragraph', texts: [RT.plain(step)] })
520
+ .build()
521
+ )
522
+ }
523
+
524
+ // Send the final complete message
525
+ await ctx.sendRichMessage(
526
+ new Builder()
527
+ .heading(2, RT.plain('Answer'))
528
+ .paragraph(RT.plain('Here is the final response from the AI.'))
529
+ .build()
530
+ )
531
+ })
532
+ ```
533
+
534
+ ---
535
+
536
+ ### Inline / Web App Queries
537
+
538
+ `InputRichMessageContent` can be returned in results for inline queries, guest queries, and Web App queries.
539
+
540
+ ```ts
541
+ import { RichMessage } from '@icanseeuanywhere/telekaf'
542
+ const { RichMessageBuilder: Builder, RT } = RichMessage
543
+
544
+ bot.on('inline_query', async (ctx) => {
545
+ 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!'))
549
+ .build(),
550
+ }
551
+
552
+ await ctx.answerInlineQuery([
553
+ {
554
+ type: 'article',
555
+ id: '1',
556
+ title: 'Rich Message Result',
557
+ input_message_content: richContent,
558
+ },
559
+ ])
560
+ })
561
+ ```
562
+
563
+ ---
564
+
565
+ ### TypeScript Usage with Rich Messages
566
+
567
+ All types are exported from `telegraf` under the `RichMessage` namespace:
568
+
569
+ ```ts
570
+ import { RichMessage } from '@icanseeuanywhere/telekaf'
571
+
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
583
+
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
+ ```
593
+
594
+ You can also import types directly if needed:
595
+
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'
606
+ ```
607
+
608
+ ---
609
+
610
+ ## For 3.x users
611
+
612
+ - [3.x docs](https://telegraf.js.org/v3)
613
+ - [4.0 release notes](https://github.com/telegraf/telegraf/releases/tag/v4.0.0)
614
+
615
+ ## Introduction
616
+
617
+ Bots are special [Telegram](https://telegram.org) accounts designed to handle messages automatically.
618
+ Users can interact with bots by sending them command messages in private or group chats.
619
+ These accounts serve as an interface for code running somewhere on your server.
620
+
621
+ Telegraf is a library that makes it simple for you to develop your own Telegram bots using JavaScript or [TypeScript](https://www.typescriptlang.org/).
622
+
623
+ ### Features
624
+
625
+ - Full [Telegram Bot API 10.1](https://core.telegram.org/bots/api) support with **Rich Messages**
626
+ - [Excellent TypeScript typings](https://github.com/telegraf/telegraf/releases/tag/v4.0.0)
627
+ - [Lightweight](https://packagephobia.com/result?p=telegraf,node-telegram-bot-api)
628
+ - [AWS **λ**](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html)
629
+ / [Firebase](https://firebase.google.com/products/functions/)
630
+ / [Glitch](https://glitch.com/edit/#!/dashing-light)
631
+ / [Fly.io](https://fly.io/docs/languages-and-frameworks/node)
632
+ / Whatever ready
633
+ - `http/https/fastify/Connect.js/express.js` compatible webhooks
634
+ - Extensible
635
+
636
+ ### Example
637
+
638
+ ```js
639
+ const { Telegraf } = require('@icanseeuanywhere/telekaf')
640
+ const { message } = require('@icanseeuanywhere/telekaf/filters')
641
+
642
+ const bot = new Telegraf(process.env.BOT_TOKEN)
643
+ bot.start((ctx) => ctx.reply('Welcome'))
644
+ bot.help((ctx) => ctx.reply('Send me a sticker'))
645
+ bot.on(message('sticker'), (ctx) => ctx.reply('👍'))
646
+ bot.hears('hi', (ctx) => ctx.reply('Hey there'))
647
+ bot.launch()
648
+
649
+ // Enable graceful stop
650
+ process.once('SIGINT', () => bot.stop('SIGINT'))
651
+ process.once('SIGTERM', () => bot.stop('SIGTERM'))
652
+ ```
653
+
654
+ ```js
655
+ const { Telegraf } = require('@icanseeuanywhere/telekaf')
656
+
657
+ const bot = new Telegraf(process.env.BOT_TOKEN)
658
+ bot.command('oldschool', (ctx) => ctx.reply('Hello'))
659
+ bot.command('hipster', Telegraf.reply('λ'))
660
+ bot.launch()
661
+
662
+ // Enable graceful stop
663
+ process.once('SIGINT', () => bot.stop('SIGINT'))
664
+ process.once('SIGTERM', () => bot.stop('SIGTERM'))
665
+ ```
666
+
667
+ For additional bot examples see the new [`docs repo`](https://github.com/feathers-studio/telegraf-docs/).
668
+
669
+ ### Resources
670
+
671
+ - [Getting started](#getting-started)
672
+ - [API reference](https://telegraf.js.org/modules.html)
673
+ - Telegram groups (sorted by number of members):
674
+ - [English](https://t.me/TelegrafJSChat)
675
+ - [Russian](https://t.me/telegrafjs_ru)
676
+ - [Uzbek](https://t.me/botjs_uz)
677
+ - [Ethiopian](https://t.me/telegraf_et)
678
+ - [GitHub Discussions](https://github.com/telegraf/telegraf/discussions)
679
+ - [Dependent repositories](https://libraries.io/npm/telegraf/dependent_repositories)
680
+
681
+ ## Getting started
682
+
683
+ ### Telegram token
684
+
685
+ To use the [Telegram Bot API](https://core.telegram.org/bots/api),
686
+ you first have to [get a bot account](https://core.telegram.org/bots)
687
+ by [chatting with BotFather](https://core.telegram.org/bots#6-botfather).
688
+
689
+ BotFather will give you a _token_, something like `123456789:AbCdefGhIJKlmNoPQRsTUVwxyZ`.
690
+
691
+ ### Installation
692
+
693
+ ```shellscript
694
+ $ npm install @icanseeuanywhere/telekaf
695
+ ```
696
+
697
+ or
698
+
699
+ ```shellscript
700
+ $ yarn add @icanseeuanywhere/telekaf
701
+ ```
702
+
703
+ or
704
+
705
+ ```shellscript
706
+ $ pnpm add @icanseeuanywhere/telekaf
707
+ ```
708
+
709
+ ### `Telegraf` class
710
+
711
+ [`Telegraf`] instance represents your bot. It's responsible for obtaining updates and passing them to your handlers.
712
+
713
+ Start by [listening to commands](https://telegraf.js.org/classes/Telegraf-1.html#command) and [launching](https://telegraf.js.org/classes/Telegraf-1.html#launch) your bot.
714
+
715
+ ### `Context` class
716
+
717
+ `ctx` you can see in every example is a [`Context`] instance.
718
+ [`Telegraf`] creates one for each incoming update and passes it to your middleware.
719
+ It contains the `update`, `botInfo`, and `telegram` for making arbitrary Bot API requests,
720
+ as well as shorthand methods and getters.
721
+
722
+ This is probably the class you'll be using the most.
723
+
724
+ <!--
725
+ TODO: Verify and update list
726
+ Here is a list of
727
+
728
+ #### Known middleware
729
+
730
+ - [Internationalization](https://github.com/telegraf/telegraf-i18n)—simplifies selecting the right translation to use when responding to a user.
731
+ - [Redis powered session](https://github.com/telegraf/telegraf-session-redis)—store session data using Redis.
732
+ - [Local powered session (via lowdb)](https://github.com/RealSpeaker/telegraf-session-local)—store session data in a local file.
733
+ - [Rate-limiting](https://github.com/telegraf/telegraf-ratelimit)—apply rate limitting to chats or users.
734
+ - [Bottleneck powered throttling](https://github.com/KnightNiwrem/telegraf-throttler)—apply throttling to both incoming updates and outgoing API calls.
735
+ - [Menus via inline keyboards](https://github.com/EdJoPaTo/telegraf-inline-menu)—simplify creating interfaces based on menus.
736
+ - [Stateless Questions](https://github.com/EdJoPaTo/telegraf-stateless-question)—create stateless questions to Telegram users working in privacy mode.
737
+ - [Natural language processing via wit.ai](https://github.com/telegraf/telegraf-wit)
738
+ - [Natural language processing via recast.ai](https://github.com/telegraf/telegraf-recast)
739
+ - [Multivariate and A/B testing](https://github.com/telegraf/telegraf-experiments)—add experiments to see how different versions of a feature are used.
740
+ - [Powerfull bot stats via Mixpanel](https://github.com/telegraf/telegraf-mixpanel)
741
+ - [statsd integration](https://github.com/telegraf/telegraf-statsd)
742
+ - [and more...](https://www.npmjs.com/search?q=telegraf-)
743
+ -->
744
+
745
+ #### Shorthand methods
746
+
747
+ ```js
748
+ import { Telegraf } from '@icanseeuanywhere/telekaf'
749
+ import { message } from '@icanseeuanywhere/telekaf/filters'
750
+
751
+ const bot = new Telegraf(process.env.BOT_TOKEN)
752
+
753
+ bot.command('quit', async (ctx) => {
754
+ // Explicit usage
755
+ await ctx.telegram.leaveChat(ctx.message.chat.id)
756
+
757
+ // Using context shortcut
758
+ await ctx.leaveChat()
759
+ })
760
+
761
+ bot.on(message('text'), async (ctx) => {
762
+ // Explicit usage
763
+ await ctx.telegram.sendMessage(ctx.message.chat.id, `Hello ${ctx.state.role}`)
764
+
765
+ // Using context shortcut
766
+ await ctx.reply(`Hello ${ctx.state.role}`)
767
+ })
768
+
769
+ bot.on('callback_query', async (ctx) => {
770
+ // Explicit usage
771
+ await ctx.telegram.answerCbQuery(ctx.callbackQuery.id)
772
+
773
+ // Using context shortcut
774
+ await ctx.answerCbQuery()
775
+ })
776
+
777
+ bot.on('inline_query', async (ctx) => {
778
+ const result = []
779
+ // Explicit usage
780
+ await ctx.telegram.answerInlineQuery(ctx.inlineQuery.id, result)
781
+
782
+ // Using context shortcut
783
+ await ctx.answerInlineQuery(result)
784
+ })
785
+
786
+ bot.launch()
787
+
788
+ // Enable graceful stop
789
+ process.once('SIGINT', () => bot.stop('SIGINT'))
790
+ process.once('SIGTERM', () => bot.stop('SIGTERM'))
791
+ ```
792
+
793
+ ## Production
794
+
795
+ ### Webhooks
796
+
797
+ ```TS
798
+ import { Telegraf } from "telegraf";
799
+ import { message } from '@icanseeuanywhere/telekaf/filters';
800
+
801
+ const bot = new Telegraf(token);
802
+
803
+ bot.on(message("text"), ctx => ctx.reply("Hello"));
804
+
805
+ // Start webhook via launch method (preferred)
806
+ bot.launch({
807
+ webhook: {
808
+ // Public domain for webhook; e.g.: example.com
809
+ domain: webhookDomain,
810
+
811
+ // Port to listen on; e.g.: 8080
812
+ port: port,
813
+
814
+ // Optional path to listen for.
815
+ // `bot.secretPathComponent()` will be used by default
816
+ path: webhookPath,
817
+
818
+ // Optional secret to be sent back in a header for security.
819
+ // e.g.: `crypto.randomBytes(64).toString("hex")`
820
+ secretToken: randomAlphaNumericString,
821
+ },
822
+ });
823
+ ```
824
+
825
+ Use `createWebhook()` if you want to attach Telegraf to an existing http server.
826
+
827
+ <!-- global bot, tlsOptions -->
828
+
829
+ ```TS
830
+ import { createServer } from "http";
831
+
832
+ createServer(await bot.createWebhook({ domain: "example.com" })).listen(3000);
833
+ ```
834
+
835
+ ```TS
836
+ import { createServer } from "https";
837
+
838
+ createServer(tlsOptions, await bot.createWebhook({ domain: "example.com" })).listen(8443);
839
+ ```
840
+
841
+ - [AWS Lambda example integration](https://github.com/feathers-studio/telegraf-docs/tree/master/examples/functions/aws-lambda)
842
+ - [Google Cloud Functions example integration](https://github.com/feathers-studio/telegraf-docs/blob/master/examples/functions/google-cloud-function.ts)
843
+ - [`express` example integration](https://github.com/feathers-studio/telegraf-docs/blob/master/examples/webhook/express.ts)
844
+ - [`fastify` example integration](https://github.com/feathers-studio/telegraf-docs/blob/master/examples/webhook/fastify.ts)
845
+ - [`koa` example integration](https://github.com/feathers-studio/telegraf-docs/blob/master/examples/webhook/koa.ts)
846
+ - [NestJS framework integration module](https://github.com/bukhalo/nestjs-telegraf)
847
+ - [Cloudflare Workers integration module](https://github.com/Tsuk1ko/cfworker-middware-telegraf)
848
+ - Use [`bot.handleUpdate`](https://telegraf.js.org/classes/Telegraf-1.html#handleupdate) to write new integrations
849
+
850
+ ### Error handling
851
+
852
+ If middleware throws an error or times out, Telegraf calls `bot.handleError`. If it rethrows, update source closes, and then the error is printed to console and process terminates. If it does not rethrow, the error is swallowed.
853
+
854
+ Default `bot.handleError` always rethrows. You can overwrite it using `bot.catch` if you need to.
855
+
856
+ ⚠️ Swallowing unknown errors might leave the process in invalid state!
857
+
858
+ ℹ️ In production, `systemd` or [`pm2`](https://www.npmjs.com/package/pm2) can restart your bot if it exits for any reason.
859
+
860
+ ## Advanced topics
861
+
862
+ ### Working with files
863
+
864
+ Supported file sources:
865
+
866
+ - `Existing file_id`
867
+ - `File path`
868
+ - `Url`
869
+ - `Buffer`
870
+ - `ReadStream`
871
+
872
+ Also, you can provide an optional name of a file as `filename` when you send the file.
873
+
874
+ <!-- global bot, fs -->
875
+
876
+ ```js
877
+ bot.on('message', async (ctx) => {
878
+ // resend existing file by file_id
879
+ await ctx.replyWithSticker('123123jkbhj6b')
880
+
881
+ // send file
882
+ await ctx.replyWithVideo(Input.fromLocalFile('/path/to/video.mp4'))
883
+
884
+ // send stream
885
+ await ctx.replyWithVideo(
886
+ Input.fromReadableStream(fs.createReadStream('/path/to/video.mp4'))
887
+ )
888
+
889
+ // send buffer
890
+ await ctx.replyWithVoice(Input.fromBuffer(Buffer.alloc()))
891
+
892
+ // send url via Telegram server
893
+ await ctx.replyWithPhoto(Input.fromURL('https://picsum.photos/200/300/'))
894
+
895
+ // pipe url content
896
+ await ctx.replyWithPhoto(
897
+ Input.fromURLStream('https://picsum.photos/200/300/?random', 'kitten.jpg')
898
+ )
899
+ })
900
+ ```
901
+
902
+ ### Middleware
903
+
904
+ In addition to `ctx: Context`, each middleware receives `next: () => Promise<void>`.
905
+
906
+ As in Koa and some other middleware-based libraries,
907
+ `await next()` will call next middleware and wait for it to finish:
908
+
909
+ ```TS
910
+ import { Telegraf } from '@icanseeuanywhere/telekaf';
911
+ import { message } from '@icanseeuanywhere/telekaf/filters';
912
+
913
+ const bot = new Telegraf(process.env.BOT_TOKEN);
914
+
915
+ bot.use(async (ctx, next) => {
916
+ console.time(`Processing update ${ctx.update.update_id}`);
917
+ await next() // runs next middleware
918
+ // runs after next middleware finishes
919
+ console.timeEnd(`Processing update ${ctx.update.update_id}`);
920
+ })
921
+
922
+ bot.on(message('text'), (ctx) => ctx.reply('Hello World'));
923
+ bot.launch();
924
+
925
+ // Enable graceful stop
926
+ process.once('SIGINT', () => bot.stop('SIGINT'));
927
+ process.once('SIGTERM', () => bot.stop('SIGTERM'));
928
+ ```
929
+
930
+ With this simple ability, you can:
931
+
932
+ - extract information from updates and then `await next()` to avoid disrupting other middleware,
933
+ - like [`Composer`] and [`Router`], `await next()` for updates you don't wish to handle,
934
+ - like [`session`] and [`Scenes`], [extend the context](#extending-context) by mutating `ctx` before `await next()`,
935
+ - [intercept API calls](https://github.com/telegraf/telegraf/discussions/1267#discussioncomment-254525),
936
+ - reuse [other people's code](https://www.npmjs.com/search?q=telegraf-),
937
+ - do whatever **you** come up with!
938
+
939
+ [`Telegraf`]: https://telegraf.js.org/classes/Telegraf-1.html
940
+ [`Composer`]: https://telegraf.js.org/classes/Composer.html
941
+ [`Context`]: https://telegraf.js.org/classes/Context.html
942
+ [`Router`]: https://telegraf.js.org/classes/Router.html
943
+ [`session`]: https://telegraf.js.org/modules.html#session
944
+ [`Scenes`]: https://telegraf.js.org/modules/Scenes.html
945
+
946
+ ### Usage with TypeScript
947
+
948
+ Telegraf is written in TypeScript and therefore ships with declaration files for the entire library.
949
+ Moreover, it includes types for the complete Telegram API via the [`typegram`](https://github.com/KnorpelSenf/typegram) package.
950
+ While most types of Telegraf's API surface are self-explanatory, there's some notable things to keep in mind.
951
+
952
+ #### Extending `Context`
953
+
954
+ The exact shape of `ctx` can vary based on the installed middleware.
955
+ Some custom middleware might register properties on the context object that Telegraf is not aware of.
956
+ Consequently, you can change the type of `ctx` to fit your needs in order for you to have proper TypeScript types for your data.
957
+ This is done through Generics:
958
+
959
+ ```ts
960
+ import { Context, Telegraf } from '@icanseeuanywhere/telekaf'
961
+
962
+ // Define your own context type
963
+ interface MyContext extends Context {
964
+ myProp?: string
965
+ myOtherProp?: number
966
+ }
967
+
968
+ // Create your bot and tell it about your context type
969
+ const bot = new Telegraf<MyContext>('SECRET TOKEN')
970
+
971
+ // Register middleware and launch your bot as usual
972
+ bot.use((ctx, next) => {
973
+ // Yay, `myProp` is now available here as `string | undefined`!
974
+ ctx.myProp = ctx.chat?.first_name?.toUpperCase()
975
+ return next()
976
+ })
977
+ // ...
978
+ ```