@icanseeuanywhere/telekaf
v4.16.8
Published
Modern Telegram Bot Framework (Bot API 10.1 + Rich Messages)
Maintainers
Readme
Developed & maintained by @kafk6
telekaf (
@icanseeuanywhere/telekaf) is an up-to-date fork of telegraf — the popular Telegram Bot framework for Node.js. This package tracks the latest Telegram Bot API releases and ships features ahead of the upstream package. It is a drop-in replacement: just swapnpm install telegraf→npm install @icanseeuanywhere/telekafand change your imports from'telegraf'to'@icanseeuanywhere/telekaf'.
🆕 Rich Messages — Bot API 10.1
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.
Table of Contents
- Quick Start
- Sending Methods
- InputRichMessage Format
- RichHTMLBuilder
- RichMarkdownBuilder
- Streaming with sendRichMessageDraft
- reply_markup with Rich Messages
- Inline / Web App Queries
- TypeScript
Quick Start
import { Telegraf, RichMessage } from '@icanseeuanywhere/telekaf'
const { RichHTMLBuilder: HTML } = RichMessage
const bot = new Telegraf(process.env.BOT_TOKEN)
bot.command('hello', async (ctx) => {
const msg = new HTML()
.heading(1, 'Hello World!')
.paragraph(HTML.bold('Rich Messages') + ' are now supported in Bot API 10.1.')
.divider()
.ul('Headings', 'Lists', 'Tables', 'Media', 'Math', 'and more')
.build()
await ctx.sendRichMessage(msg)
})
bot.launch()
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))Sending Methods
| Method | Description |
|---|---|
| ctx.sendRichMessage(msg, extra?) | Send a rich message to the current chat |
| ctx.replyWithRichMessageContent(msg, extra?) | Send a rich message quoting the current message |
| ctx.sendRichMessageDraft(draftId, msg, extra?) | Stream a partial draft (private chats only) |
| ctx.telegram.sendRichMessage(chatId, msg, extra?) | Explicit call with chat ID |
| ctx.telegram.sendRichMessageDraft(chatId, draftId, msg, extra?) | Explicit streaming with chat ID |
extra options for sendRichMessage:
await ctx.sendRichMessage(msg, {
message_thread_id: 123, // for forum topics
direct_messages_topic_id: 456, // for DM topics
disable_notification: true,
protect_content: true,
allow_paid_broadcast: false,
message_effect_id: 'effect_id',
reply_parameters: { message_id: ctx.message.message_id },
reply_markup: { inline_keyboard: [[{ text: 'OK', callback_data: 'ok' }]] },
})InputRichMessage Format
InputRichMessage uses either html or markdown — not both:
// HTML format
const msg: InputRichMessage = {
html: '<h1>Title</h1><p>Body text</p>',
is_rtl: false,
skip_entity_detection: false,
}
// Markdown format
const msg: InputRichMessage = {
markdown: '# Title\n\nBody text',
}Use the builders — RichHTMLBuilder or RichMarkdownBuilder — to construct these conveniently.
RichHTMLBuilder
Import and instantiate:
import { RichMessage } from '@icanseeuanywhere/telekaf'
const { RichHTMLBuilder: HTML } = RichMessage
const msg = new HTML()
.heading(1, 'Title')
.paragraph('Content')
.build() // returns InputRichMessage { html: '...' }Text Formatting (HTML)
Static inline helpers — return strings to embed inside block methods:
HTML.bold('bold text') // <b>bold text</b>
HTML.italic('italic text') // <i>italic text</i>
HTML.underline('underlined') // <u>underlined</u>
HTML.strikethrough('crossed out') // <s>crossed out</s>
HTML.spoiler('hidden until tapped') // <tg-spoiler>hidden</tg-spoiler>
HTML.code('inline code') // <code>inline code</code>
HTML.marked('highlighted') // <mark>highlighted</mark>
HTML.sub('subscript') // <sub>subscript</sub>
HTML.sup('superscript') // <sup>superscript</sup>
HTML.url('https://t.me', 'Telegram') // <a href="...">Telegram</a>
HTML.email('[email protected]', 'Email us') // <a href="mailto:...">Email us</a>
HTML.phone('+6281234567', 'Call us') // <a href="tel:...">Call us</a>
HTML.mention(123456789, 'Alice') // <a href="tg://user?id=...">Alice</a>
HTML.customEmoji('5368324170671202286', '👍') // <tg-emoji emoji-id="...">👍</tg-emoji>
HTML.time(1647531900, 'wDT', '22:45 tomorrow') // <tg-time unix="..." format="...">...</tg-time>
HTML.inlineMath('E = mc^2') // $E = mc^2$
// Nesting
HTML.bold(HTML.italic('bold italic'))
HTML.underline(HTML.spoiler('underlined spoiler'))Headings & Paragraphs
new HTML()
.heading(1, 'Main Title')
.heading(2, 'Subtitle')
.heading(3, HTML.bold('Bold heading'))
.heading(4, 'H4')
.heading(5, 'H5')
.heading(6, 'H6')
.paragraph('Normal paragraph text.')
.paragraph(HTML.bold('Bold') + ' and ' + HTML.italic('italic') + ' combined.')
.footer('Footer text — smaller and muted')
.divider() // <hr/>
.build()Lists (HTML)
new HTML()
// Unordered list
.ul('First item', 'Second item', HTML.bold('Bold item'))
// Ordered list
.ol('Step one', 'Step two', 'Step three')
// Task list (checkboxes)
.taskList(
{ text: 'Completed task', checked: true },
{ text: 'Pending task', checked: false },
{ text: HTML.bold('Important task'), checked: false },
)
.build()Code Blocks
new HTML()
.pre('npm install @icanseeuanywhere/telekaf', 'bash')
.pre('SELECT * FROM users WHERE active = 1;', 'sql')
.pre(
`const bot = new Telegraf(token)
bot.launch()`,
'javascript'
)
.pre('plain preformatted block without language')
.build()Supported language identifiers: javascript, typescript, python, bash, sql, json, html, css, etc.
Blockquote & Pull Quote
new HTML()
// Standard block quotation
.blockQuote(HTML.italic('"To be or not to be."'))
// Pull quotation with attribution (cite)
.pullQuote(
HTML.italic('"Design is not just what it looks like."'),
'Steve Jobs'
)
// Nested formatting inside quote
.blockQuote(
HTML.bold('Telekaf') + ' supports ' + HTML.marked('highlighted') +
' and ' + HTML.spoiler('spoiler') + ' text inside quotes.'
)
.build()Math (HTML)
new HTML()
// Inline math (embed inside paragraph)
.paragraph(
'The formula is: ' + HTML.inlineMath('a^2 + b^2 = c^2')
)
// Block math expression
.mathBlock('\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}')
.mathBlock('F(x) = \\int_{-\\infty}^{x} f(t)\\,dt')
.mathBlock('E = mc^2')
.build()Media — Photo, Video, Audio
new HTML()
// Photo
.photo('https://example.com/photo.jpg')
.photo('https://example.com/photo.jpg', 'Caption text')
.photo('https://example.com/photo.jpg', 'Spoiler photo', /* spoiler */ true)
// Video
.video('https://example.com/video.mp4')
.video('https://example.com/video.mp4', 'Caption text')
.video('https://example.com/video.mp4', 'Spoiler video', /* spoiler */ true)
// Audio / Voice note (.ogg for voice)
.audio('https://example.com/audio.mp3')
.audio('https://example.com/audio.mp3', 'Audio caption')
// With figcaption (HTML figure)
.raw('<figure><img src="https://example.com/photo.jpg"/><figcaption>Caption <b>bold</b></figcaption></figure>')
.build()You can also use a Telegram file_id in place of a URL once the file is uploaded.
Collage & Slideshow
const img = (src) => `<img src="${src}"/>`
const vid = (src) => `<video src="${src}"></video>`
new HTML()
// Collage — displays as a grid
.collage(
img('https://example.com/photo1.jpg'),
img('https://example.com/photo2.jpg'),
vid('https://example.com/clip.mp4'),
)
// Slideshow — swipeable carousel
.slideshow(
img('https://example.com/photo1.jpg'),
img('https://example.com/photo2.jpg'),
img('https://example.com/photo3.jpg'),
)
.build()Map
new HTML()
.map(-6.2088, 106.8456) // Jakarta
.map(48.8584, 2.2945, 16) // Paris, zoom 16
.map(51.5074, -0.1278, 14, 'Our office in London') // with caption
.build()Table (HTML)
new HTML()
.table(
[
['Name', 'Version', 'Downloads'], // header row (hasHeader: true)
['telekaf', '4.16.5', '—' ],
['telegraf','4.16.3', '~120k/wk' ],
],
{ bordered: true, striped: true, hasHeader: true }
)
.build()For advanced table markup (colspan, rowspan, alignment), use .raw():
new HTML()
.raw(
'<table bordered striped>' +
'<tr><th>Name</th><th colspan="2">Details</th></tr>' +
'<tr><td>Alice</td><td align="center">98</td><td align="right">Pass</td></tr>' +
'</table>'
)
.build()Details (Expandable)
new HTML()
// Collapsed by default
.details('Click to expand', '<p>Hidden content here.</p>')
// Open by default
.details(
HTML.bold('Changelog v4.16.5'),
'<ul><li>Fix InputRichMessage format</li><li>Add RichHTMLBuilder</li></ul>',
/* open */ true
)
.build()Footnote References (HTML)
new HTML()
.paragraph(
'Telekaf ' + HTML.ref('note-1', '[1]') + ' is based on Telegraf ' + HTML.ref('note-2', '[2]') + '.'
)
.divider()
.referenceDefinition('note-1', HTML.url('https://npmjs.com/package/@icanseeuanywhere/telekaf', 'telekaf on npm'))
.referenceDefinition('note-2', HTML.url('https://github.com/telegraf/telegraf', 'telegraf on GitHub'))
.build()Anchors & In-document Links
new HTML()
.raw(HTML.anchor('section-intro')) // invisible anchor target
.heading(2, 'Introduction')
.paragraph('Jump to: ' + HTML.anchorLink('section-api', 'API Reference'))
.raw(HTML.anchor('section-api'))
.heading(2, 'API Reference')
.build()Special Elements
new HTML()
// AI thinking block (visible in sendRichMessageDraft)
.thinking(HTML.italic('Analyzing your request...'))
// Raw HTML for anything not covered by builder methods
.raw('<tg-map lat="41.9" long="12.5" zoom="14"/>')
.raw('<aside>Pull quote<cite>The Author</cite></aside>')
.build()RichMarkdownBuilder
Same API surface as RichHTMLBuilder but produces Markdown output:
import { RichMessage } from '@icanseeuanywhere/telekaf'
const { RichMarkdownBuilder: MD } = RichMessage
const msg = new MD()
.heading(1, 'Rich Markdown')
.paragraph(
MD.bold('bold') + ' ' +
MD.italic('italic') + ' ' +
MD.strikethrough('strike') + ' ' +
MD.marked('==highlighted==') + ' ' +
MD.spoiler('||spoiler||') + ' ' +
MD.code('`code`')
)
.divider()
.ul('Item 1', 'Item 2', MD.bold('Bold item'))
.ol('Step 1', 'Step 2', 'Step 3')
.taskList(
{ text: 'Done', checked: true },
{ text: 'Pending', checked: false },
)
.divider()
.pre('console.log("hello")', 'javascript')
.divider()
.table(
['Name', 'Score'],
[['Alice', '98'], ['Bob', '87']],
['left', 'center'],
)
.divider()
.mathBlock('E = mc^2')
.divider()
.blockQuote(MD.italic('"Quote text"'), '— Author')
.divider()
.photo('https://example.com/photo.jpg', 'Photo caption')
.collage('https://example.com/1.jpg', 'https://example.com/2.jpg')
.slideshow('https://example.com/1.jpg', 'https://example.com/2.jpg')
.divider()
.details('Expand me', '### Hidden heading\n\n- item 1\n- item 2')
.divider()
.paragraph('See footnote' + MD.sup('[1]'))
.footnote('1', MD.url('https://t.me/kafk6', '@kafka'))
.build()All Markdown inline helpers:
MD.bold('text') // **text**
MD.italic('text') // *text*
MD.underline('text') // <u>text</u>
MD.strikethrough('text') // ~~text~~
MD.spoiler('text') // ||text||
MD.code('text') // `text`
MD.marked('text') // ==text==
MD.sub('text') // <sub>text</sub>
MD.sup('text') // <sup>text</sup>
MD.url('https://...', 'label') // [label](url)
MD.email('[email protected]', 'label') // [label](mailto:[email protected])
MD.phone('+123', 'label') // [label](tel:+123)
MD.mention(123456789, 'Alice') // [Alice](tg://user?id=123456789)
MD.customEmoji('id', '👍') // 
MD.time(1647531900, 'wDT') // 
MD.inlineMath('a^2') // $a^2$Streaming with sendRichMessageDraft
sendRichMessageDraft streams a partial rich message in private chats. The draft is ephemeral (30-second preview). You must finalize with sendRichMessage to persist it.
chat_id— private chat only (integer)draft_id— non-zero integer; updates with the samedraft_idare animated
bot.command('ai', async (ctx) => {
const DRAFT_ID = 1 // any non-zero integer
const steps = [
'Reading your request...',
'Searching knowledge base...',
'Composing answer...',
]
// Stream thinking blocks
for (const step of steps) {
await ctx.sendRichMessageDraft(
DRAFT_ID,
new HTML().thinking(HTML.italic(step)).build()
)
await new Promise((r) => setTimeout(r, 900))
}
// Finalize — must call sendRichMessage after streaming
await ctx.sendRichMessage(
new HTML()
.heading(2, '🤖 AI Response')
.paragraph('Here is the final answer from the AI.')
.divider()
.footer(HTML.url('https://t.me/kafk6', '@kafka'))
.build()
)
})You can also call sendRichMessageDraft explicitly:
// Explicit
await ctx.telegram.sendRichMessageDraft(
ctx.chat.id, // private chat integer ID
42, // draft_id
new HTML().thinking('Processing...').build(),
{ message_thread_id: 123 }
)reply_markup with Rich Messages
All sendRichMessage calls accept a reply_markup option with inline keyboards:
import { Markup } from '@icanseeuanywhere/telekaf'
const msg = new HTML()
.heading(2, 'Choose an option')
.paragraph('Tap a button below:')
.build()
await ctx.sendRichMessage(msg, {
reply_markup: Markup.inlineKeyboard([
[
Markup.button.callback('✅ Yes', 'answer:yes'),
Markup.button.callback('❌ No', 'answer:no'),
],
[Markup.button.url('🌐 Visit', 'https://t.me/kafk6')],
]).reply_markup,
})
bot.action('answer:yes', async (ctx) => {
await ctx.answerCbQuery('You chose Yes!')
})Or use reply_markup directly:
await ctx.sendRichMessage(msg, {
reply_markup: {
inline_keyboard: [
[{ text: 'Button 1', callback_data: 'btn1' }],
[{ text: 'Open URL', url: 'https://telegram.org' }],
],
},
})Inline / Web App Queries
Use InputRichMessageContent as input_message_content in inline query results:
import { RichMessage } from '@icanseeuanywhere/telekaf'
const { RichHTMLBuilder: HTML } = RichMessage
bot.on('inline_query', async (ctx) => {
const richContent: RichMessage.InputRichMessageContent = {
rich_message: new HTML()
.heading(1, 'Result from Inline Query')
.paragraph('Sent via ' + HTML.url('https://t.me/kafk6', '@kafka') + '.')
.build(),
}
await ctx.answerInlineQuery([
{
type: 'article',
id: '1',
title: 'Rich Message Result',
input_message_content: richContent,
},
])
})TypeScript
All types are exported under the RichMessage namespace:
import { RichMessage } from '@icanseeuanywhere/telekaf'
import type { ExtraSendRichMessage, ExtraSendRichMessageDraft } from '@icanseeuanywhere/telekaf/types'
// Builder types
const builder: RichMessage.RichHTMLBuilder = new RichMessage.RichHTMLBuilder()
const mdBuilder: RichMessage.RichMarkdownBuilder = new RichMessage.RichMarkdownBuilder()
// Message types
const input: RichMessage.InputRichMessage = { html: '<p>hello</p>' }
const content: RichMessage.InputRichMessageContent = { rich_message: input }
const received: RichMessage.RichMessage = ctx.message.rich_message
// Extra types
const extra: ExtraSendRichMessage = {
disable_notification: true,
reply_markup: { inline_keyboard: [] },
}
// Custom context with rich message
import { Context, Telegraf } from '@icanseeuanywhere/telekaf'
interface MyCtx extends Context {
session?: { lastDraftId: number }
}
const bot = new Telegraf<MyCtx>(process.env.BOT_TOKEN)For 3.x users
Introduction
Bots are special Telegram accounts designed to handle messages automatically. Users can interact with bots by sending them command messages in private or group chats. These accounts serve as an interface for code running somewhere on your server.
Telegraf is a library that makes it simple for you to develop your own Telegram bots using JavaScript or TypeScript.
Features
- Full Telegram Bot API 10.1 support with Rich Messages
- Excellent TypeScript typings
- Lightweight
- AWS λ / Firebase / Glitch / Fly.io / Whatever ready
http/https/fastify/Connect.js/express.jscompatible webhooks- Extensible
Example
const { Telegraf } = require('@icanseeuanywhere/telekaf')
const { message } = require('@icanseeuanywhere/telekaf/filters')
const bot = new Telegraf(process.env.BOT_TOKEN)
bot.start((ctx) => ctx.reply('Welcome'))
bot.help((ctx) => ctx.reply('Send me a sticker'))
bot.on(message('sticker'), (ctx) => ctx.reply('👍'))
bot.hears('hi', (ctx) => ctx.reply('Hey there'))
bot.launch()
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))const { Telegraf } = require('@icanseeuanywhere/telekaf')
const bot = new Telegraf(process.env.BOT_TOKEN)
bot.command('oldschool', (ctx) => ctx.reply('Hello'))
bot.command('hipster', Telegraf.reply('λ'))
bot.launch()
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))For additional bot examples see the new docs repo.
Resources
- Getting started
- API reference
- Telegram groups (sorted by number of members):
- GitHub Discussions
- Dependent repositories
Getting started
Telegram token
To use the Telegram Bot API, you first have to get a bot account by chatting with BotFather.
BotFather will give you a token, something like 123456789:AbCdefGhIJKlmNoPQRsTUVwxyZ.
Installation
$ npm install @icanseeuanywhere/telekafor
$ yarn add @icanseeuanywhere/telekafor
$ pnpm add @icanseeuanywhere/telekafTelegraf class
Telegraf instance represents your bot. It's responsible for obtaining updates and passing them to your handlers.
Start by listening to commands and launching your bot.
Context class
ctx you can see in every example is a Context instance.
Telegraf creates one for each incoming update and passes it to your middleware.
It contains the update, botInfo, and telegram for making arbitrary Bot API requests,
as well as shorthand methods and getters.
This is probably the class you'll be using the most.
Shorthand methods
import { Telegraf } from '@icanseeuanywhere/telekaf'
import { message } from '@icanseeuanywhere/telekaf/filters'
const bot = new Telegraf(process.env.BOT_TOKEN)
bot.command('quit', async (ctx) => {
// Explicit usage
await ctx.telegram.leaveChat(ctx.message.chat.id)
// Using context shortcut
await ctx.leaveChat()
})
bot.on(message('text'), async (ctx) => {
// Explicit usage
await ctx.telegram.sendMessage(ctx.message.chat.id, `Hello ${ctx.state.role}`)
// Using context shortcut
await ctx.reply(`Hello ${ctx.state.role}`)
})
bot.on('callback_query', async (ctx) => {
// Explicit usage
await ctx.telegram.answerCbQuery(ctx.callbackQuery.id)
// Using context shortcut
await ctx.answerCbQuery()
})
bot.on('inline_query', async (ctx) => {
const result = []
// Explicit usage
await ctx.telegram.answerInlineQuery(ctx.inlineQuery.id, result)
// Using context shortcut
await ctx.answerInlineQuery(result)
})
bot.launch()
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))Production
Webhooks
import { Telegraf } from "@icanseeuanywhere/telekaf";
import { message } from '@icanseeuanywhere/telekaf/filters';
const bot = new Telegraf(token);
bot.on(message("text"), ctx => ctx.reply("Hello"));
// Start webhook via launch method (preferred)
bot.launch({
webhook: {
// Public domain for webhook; e.g.: example.com
domain: webhookDomain,
// Port to listen on; e.g.: 8080
port: port,
// Optional path to listen for.
// `bot.secretPathComponent()` will be used by default
path: webhookPath,
// Optional secret to be sent back in a header for security.
// e.g.: `crypto.randomBytes(64).toString("hex")`
secretToken: randomAlphaNumericString,
},
});Use createWebhook() if you want to attach Telegraf to an existing http server.
import { createServer } from "http";
createServer(await bot.createWebhook({ domain: "example.com" })).listen(3000);import { createServer } from "https";
createServer(tlsOptions, await bot.createWebhook({ domain: "example.com" })).listen(8443);- AWS Lambda example integration
- Google Cloud Functions example integration
expressexample integrationfastifyexample integrationkoaexample integration- NestJS framework integration module
- Cloudflare Workers integration module
- Use
bot.handleUpdateto write new integrations
Error handling
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.
Default bot.handleError always rethrows. You can overwrite it using bot.catch if you need to.
⚠️ Swallowing unknown errors might leave the process in invalid state!
ℹ️ In production, systemd or pm2 can restart your bot if it exits for any reason.
Advanced topics
Working with files
Supported file sources:
Existing file_idFile pathUrlBufferReadStream
Also, you can provide an optional name of a file as filename when you send the file.
bot.on('message', async (ctx) => {
// resend existing file by file_id
await ctx.replyWithSticker('123123jkbhj6b')
// send file
await ctx.replyWithVideo(Input.fromLocalFile('/path/to/video.mp4'))
// send stream
await ctx.replyWithVideo(
Input.fromReadableStream(fs.createReadStream('/path/to/video.mp4'))
)
// send buffer
await ctx.replyWithVoice(Input.fromBuffer(Buffer.alloc()))
// send url via Telegram server
await ctx.replyWithPhoto(Input.fromURL('https://picsum.photos/200/300/'))
// pipe url content
await ctx.replyWithPhoto(
Input.fromURLStream('https://picsum.photos/200/300/?random', 'kitten.jpg')
)
})Middleware
In addition to ctx: Context, each middleware receives next: () => Promise<void>.
As in Koa and some other middleware-based libraries,
await next() will call next middleware and wait for it to finish:
import { Telegraf } from '@icanseeuanywhere/telekaf';
import { message } from '@icanseeuanywhere/telekaf/filters';
const bot = new Telegraf(process.env.BOT_TOKEN);
bot.use(async (ctx, next) => {
console.time(`Processing update ${ctx.update.update_id}`);
await next() // runs next middleware
// runs after next middleware finishes
console.timeEnd(`Processing update ${ctx.update.update_id}`);
})
bot.on(message('text'), (ctx) => ctx.reply('Hello World'));
bot.launch();
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'));
process.once('SIGTERM', () => bot.stop('SIGTERM'));With this simple ability, you can:
- extract information from updates and then
await next()to avoid disrupting other middleware, - like
ComposerandRouter,await next()for updates you don't wish to handle, - like
sessionandScenes, extend the context by mutatingctxbeforeawait next(), - intercept API calls,
- reuse other people's code,
- do whatever you come up with!
Usage with TypeScript
Telegraf is written in TypeScript and therefore ships with declaration files for the entire library.
Moreover, it includes types for the complete Telegram API via the typegram package.
While most types of Telegraf's API surface are self-explanatory, there's some notable things to keep in mind.
Extending Context
The exact shape of ctx can vary based on the installed middleware.
Some custom middleware might register properties on the context object that Telegraf is not aware of.
Consequently, you can change the type of ctx to fit your needs in order for you to have proper TypeScript types for your data.
This is done through Generics:
import { Context, Telegraf } from '@icanseeuanywhere/telekaf'
// Define your own context type
interface MyContext extends Context {
myProp?: string
myOtherProp?: number
}
// Create your bot and tell it about your context type
const bot = new Telegraf<MyContext>('SECRET TOKEN')
// Register middleware and launch your bot as usual
bot.use((ctx, next) => {
// Yay, `myProp` is now available here as `string | undefined`!
ctx.myProp = ctx.chat?.first_name?.toUpperCase()
return next()
})
// ...Author & Maintainer
This fork is maintained by @kafka — t.me/kafk6
Upstream project: telegraf/telegraf by The Telegraf Contributors.
