npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@icanseeuanywhere/telekaf

v4.16.8

Published

Modern Telegram Bot Framework (Bot API 10.1 + Rich Messages)

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 swap npm install telegrafnpm install @icanseeuanywhere/telekaf and 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

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', '👍')         // ![👍](tg://emoji?id=...)
MD.time(1647531900, 'wDT')         // ![](tg://time?unix=...&format=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 same draft_id are 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

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

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/telekaf

or

$ yarn add @icanseeuanywhere/telekaf

or

$ pnpm add @icanseeuanywhere/telekaf

Telegraf 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);

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_id
  • File path
  • Url
  • Buffer
  • ReadStream

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:

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 @kafkat.me/kafk6

Upstream project: telegraf/telegraf by The Telegraf Contributors.