@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.
- package/LICENSE +22 -0
- package/README.md +978 -0
- package/filters.d.ts +1 -0
- package/filters.js +1 -0
- package/format.d.ts +1 -0
- package/format.js +1 -0
- package/future.d.ts +1 -0
- package/future.js +1 -0
- package/lib/button.js +101 -0
- package/lib/cli.mjs +105 -0
- package/lib/composer.js +582 -0
- package/lib/context.js +1277 -0
- package/lib/core/helpers/args.js +58 -0
- package/lib/core/helpers/check.js +56 -0
- package/lib/core/helpers/compact.js +17 -0
- package/lib/core/helpers/deunionize.js +13 -0
- package/lib/core/helpers/formatting.js +91 -0
- package/lib/core/helpers/util.js +50 -0
- package/lib/core/network/client.js +320 -0
- package/lib/core/network/error.js +21 -0
- package/lib/core/network/multipart-stream.js +61 -0
- package/lib/core/network/polling.js +87 -0
- package/lib/core/network/webhook.js +54 -0
- package/lib/core/types/rich-message.js +102 -0
- package/lib/core/types/typegram.js +28 -0
- package/lib/filters.js +69 -0
- package/lib/format.js +38 -0
- package/lib/future.js +166 -0
- package/lib/index.js +49 -0
- package/lib/input.js +61 -0
- package/lib/markup.js +111 -0
- package/lib/middleware.js +2 -0
- package/lib/reactions.js +84 -0
- package/lib/router.js +46 -0
- package/lib/scenes/base.js +39 -0
- package/lib/scenes/context.js +104 -0
- package/lib/scenes/index.js +21 -0
- package/lib/scenes/stage.js +49 -0
- package/lib/scenes/wizard/context.js +31 -0
- package/lib/scenes/wizard/index.js +45 -0
- package/lib/scenes.js +17 -0
- package/lib/session.js +166 -0
- package/lib/telegraf.js +246 -0
- package/lib/telegram-types.js +6 -0
- package/lib/telegram.js +1265 -0
- package/lib/types.js +2 -0
- package/lib/utils.js +5 -0
- package/markup.d.ts +1 -0
- package/markup.js +1 -0
- package/package.json +138 -0
- package/scenes.d.ts +1 -0
- package/scenes.js +1 -0
- package/session.d.ts +1 -0
- package/session.js +1 -0
- package/src/button.ts +182 -0
- package/src/composer.ts +1008 -0
- package/src/context.ts +1739 -0
- package/src/core/helpers/args.ts +63 -0
- package/src/core/helpers/check.ts +71 -0
- package/src/core/helpers/compact.ts +18 -0
- package/src/core/helpers/deunionize.ts +26 -0
- package/src/core/helpers/formatting.ts +119 -0
- package/src/core/helpers/util.ts +96 -0
- package/src/core/network/client.ts +396 -0
- package/src/core/network/error.ts +29 -0
- package/src/core/network/multipart-stream.ts +45 -0
- package/src/core/network/polling.ts +94 -0
- package/src/core/network/webhook.ts +58 -0
- package/src/core/types/rich-message.ts +474 -0
- package/src/core/types/typegram.ts +55 -0
- package/src/filters.ts +109 -0
- package/src/format.ts +110 -0
- package/src/future.ts +231 -0
- package/src/index.ts +18 -0
- package/src/input.ts +59 -0
- package/src/markup.ts +142 -0
- package/src/middleware.ts +24 -0
- package/src/reactions.ts +118 -0
- package/src/router.ts +55 -0
- package/src/scenes/base.ts +52 -0
- package/src/scenes/context.ts +136 -0
- package/src/scenes/index.ts +21 -0
- package/src/scenes/stage.ts +71 -0
- package/src/scenes/wizard/context.ts +58 -0
- package/src/scenes/wizard/index.ts +63 -0
- package/src/scenes.ts +1 -0
- package/src/session.ts +204 -0
- package/src/telegraf.ts +354 -0
- package/src/telegram-types.ts +250 -0
- package/src/telegram.ts +1671 -0
- package/src/types.ts +2 -0
- package/src/utils.ts +1 -0
- package/types.d.ts +1 -0
- package/types.js +1 -0
- package/typings/button.d.ts +36 -0
- package/typings/button.d.ts.map +1 -0
- package/typings/composer.d.ts +227 -0
- package/typings/composer.d.ts.map +1 -0
- package/typings/context.d.ts +693 -0
- package/typings/context.d.ts.map +1 -0
- package/typings/core/helpers/args.d.ts +11 -0
- package/typings/core/helpers/args.d.ts.map +1 -0
- package/typings/core/helpers/check.d.ts +56 -0
- package/typings/core/helpers/check.d.ts.map +1 -0
- package/typings/core/helpers/compact.d.ts +4 -0
- package/typings/core/helpers/compact.d.ts.map +1 -0
- package/typings/core/helpers/deunionize.d.ts +18 -0
- package/typings/core/helpers/deunionize.d.ts.map +1 -0
- package/typings/core/helpers/formatting.d.ts +30 -0
- package/typings/core/helpers/formatting.d.ts.map +1 -0
- package/typings/core/helpers/util.d.ts +27 -0
- package/typings/core/helpers/util.d.ts.map +1 -0
- package/typings/core/network/client.d.ts +55 -0
- package/typings/core/network/client.d.ts.map +1 -0
- package/typings/core/network/error.d.ts +16 -0
- package/typings/core/network/error.d.ts.map +1 -0
- package/typings/core/network/multipart-stream.d.ts +19 -0
- package/typings/core/network/multipart-stream.d.ts.map +1 -0
- package/typings/core/network/polling.d.ts +16 -0
- package/typings/core/network/polling.d.ts.map +1 -0
- package/typings/core/network/webhook.d.ts +7 -0
- package/typings/core/network/webhook.d.ts.map +1 -0
- package/typings/core/types/rich-message.d.ts +295 -0
- package/typings/core/types/rich-message.d.ts.map +1 -0
- package/typings/core/types/typegram.d.ts +45 -0
- package/typings/core/types/typegram.d.ts.map +1 -0
- package/typings/filters.d.ts +18 -0
- package/typings/filters.d.ts.map +1 -0
- package/typings/format.d.ts +22 -0
- package/typings/format.d.ts.map +1 -0
- package/typings/future.d.ts +12 -0
- package/typings/future.d.ts.map +1 -0
- package/typings/index.d.ts +16 -0
- package/typings/index.d.ts.map +1 -0
- package/typings/input.d.ts +55 -0
- package/typings/input.d.ts.map +1 -0
- package/typings/markup.d.ts +27 -0
- package/typings/markup.d.ts.map +1 -0
- package/typings/middleware.d.ts +8 -0
- package/typings/middleware.d.ts.map +1 -0
- package/typings/reactions.d.ts +32 -0
- package/typings/reactions.d.ts.map +1 -0
- package/typings/router.d.ts +21 -0
- package/typings/router.d.ts.map +1 -0
- package/typings/scenes/base.d.ts +22 -0
- package/typings/scenes/base.d.ts.map +1 -0
- package/typings/scenes/context.d.ts +36 -0
- package/typings/scenes/context.d.ts.map +1 -0
- package/typings/scenes/index.d.ts +11 -0
- package/typings/scenes/index.d.ts.map +1 -0
- package/typings/scenes/stage.d.ts +24 -0
- package/typings/scenes/stage.d.ts.map +1 -0
- package/typings/scenes/wizard/context.d.ts +29 -0
- package/typings/scenes/wizard/context.d.ts.map +1 -0
- package/typings/scenes/wizard/index.d.ts +16 -0
- package/typings/scenes/wizard/index.d.ts.map +1 -0
- package/typings/scenes.d.ts +2 -0
- package/typings/scenes.d.ts.map +1 -0
- package/typings/session.d.ts +55 -0
- package/typings/session.d.ts.map +1 -0
- package/typings/telegraf.d.ts +117 -0
- package/typings/telegraf.d.ts.map +1 -0
- package/typings/telegram-types.d.ts +134 -0
- package/typings/telegram-types.d.ts.map +1 -0
- package/typings/telegram.d.ts +691 -0
- package/typings/telegram.d.ts.map +1 -0
- package/typings/types.d.ts +3 -0
- package/typings/types.d.ts.map +1 -0
- package/typings/utils.d.ts +2 -0
- package/typings/utils.d.ts.map +1 -0
- package/utils.d.ts +1 -0
- 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
|
+
```
|