Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

TeleKit

PyPI Python PyPI Downloads

Telekit

Telekit is a declarative, developer-friendly library for building Telegram bots. It gives developers a dedicated Sender for composing and sending messages and a Chain for handling dialogue between the user and the bot. The library also handles inline keyboards and callback routing automatically, letting you focus on the bot's behavior instead of repetitive tasks.

import telekit

class MyStartHandler(telekit.Handler):
    @classmethod
    def init_handler(cls):
        cls.on.command('start').invoke(cls.handle_start)

    def handle_start(self):
        self.chain.sender.set_text("Hello!")
        self.chain.sender.set_photo("robot.png")
        self.chain.send()

telekit.Server("BOT_TOKEN").polling()

Send "Hello!" with a photo on /start

Telekit comes with a built-in DSL, allowing developers to create fully interactive bots with minimal code. It also integrates Jinja, giving you loops, conditionals, expressions, and filters to generate dynamic content.

@ main {
    title   = "🎉 Fun Facts Quiz";
    message = "Test your knowledge with 10 fun questions!";

    buttons {
        question_1("Start Quiz");
    }
}

See the full example

Even in its beta stage, Telekit accelerates bot development, offering typed command parameters, text styling via Bold(), Italic(), a built-in declarative calendar picker (!), emoji game results for 🎲 🎯 🏀 ⚽ 🎳 🎰, and much more out of the box. Its declarative design makes bots easier to read, maintain, and extend.

Key features:

GitHub PyPI Telegram Community

Contents

Overview

In Telekit, dialogs read like normal method calls. You point to the next step. Telekit calls it when the user replies.

Entries

No state machines. Just tell Telekit which method should handle the next user message.

def handle(self):
    self.chain.sender.set_text("👋 Hello! What is your name?")
    self.chain.set_entry_text(self.handle_name)
    self.chain.send()

def handle_name(self, name: str):
    self.chain.sender.set_text(f"Nice to meet you, {name}!")
    self.chain.send()

handle sends a message and registers handle_name as the next step with set_entry_text. When the user replies, Telekit calls handle_name and passes the reply as a plain str.

That's it. No enums. No manual state tracking. No boilerplate.

Inline Keyboards

The fastest way to add buttons to a message. Pass a dict where each key is the button label and each value is the callback to run when pressed:

self.chain.set_inline_keyboard(
    {
        "✏️ Change": self.change_name,
        "❌ Delete": self.delete,
    }
)

row_width controls how many buttons appear per row:

self.chain.set_inline_keyboard(
    {
        "One":   self.one,
        "Two":   self.two,
        "Three": self.three,
        "Four":  self.four,
        "Five":  self.five,
    },
    row_width=(3, 2)  # first row: 3 buttons, second row: 2
)
╭──────────┬──────────┬──────────╮
│   One    │   Two    │  Three   │
├──────────┴──┬───────┴──────────┤
│    Four     │       Five       │
╰─────────────┴──────────────────╯

For precise row layout or conditional buttons, use InlineKeyboard, a builder you compose step by step:

self.chain.set_keyboard(
    InlineKeyboard()
        .add_callback("-", self.decrement, style="danger")
        .add_callback("+", self.increment, style="success")
    .row()
        .add_callback("↺ Reset", self.reset)
)
╭──────────┬──────────╮
│    -     │    +     │
├──────────┴──────────┤
│       ↺ Reset       │
╰─────────────────────╯

InlineKeyboard is built by chaining method calls. Just call .row() to start a new row.

Not just callback buttons

Method Description
add_callback(...) Button that fires a callback function.
add_link(...) Button that opens a URL.
add_copy(...) Button that copies text to the clipboard.
add_alert(...) Button that shows a popup alert dialog.
add_notification(...) Button that shows a brief top-of-chat notification.
add_static(...) Decorative button with no action.
add_webapp(...) Button that opens a Telegram Mini App.
add_suggest(...) Button that simulates the user sending a message.

Reply Keyboards

Reply keyboards replace the system keyboard with buttons at the bottom of the chat. Tapping one sends its text as a message, or triggers a system action like sharing a phone number or location.

self.chain.set_keyboard(
    ReplyKeyboard(one_time_keyboard=True)
        .add_text("Hello!")
        .add_text("Hi")
    .row()
        .add_contact("📱 Share phone")
        .add_location("📍 Share location")
)

Command Parameters

Telekit can parse and validate command parameters for you.

from telekit.parameters import *

class GreetHandler(telekit.Handler):
    @classmethod
    def init_handler(cls) -> None:
        cls.on.command("greet", params=[Int(), Str()]).invoke(cls.handle)

    def handle(self, age: int | None = None, name: str | None = None):
        if age is None or name is None:
            self.chain.sender.set_text("Usage: /greet <age> <name>")
        else:
            self.chain.sender.set_text(f"Hello, {name}! You are {age} years old. Next year you'll turn {age + 1} 😅")
        self.chain.send()

Now /greet 128 Dracula or even /greet 64 "Alice Reingold" are parsed automatically.

Dialogue

Dialogs are built as a chain of steps. Each method waits for the user before continuing.

class DialogueHandler(telekit.Handler):

    @classmethod
    def init_handler(cls) -> None:
        cls.on.text("hello", "hi", "hey").invoke(cls.handle_hello)

    def handle_hello(self) -> None:
        self.chain.sender.set_text("👋 Hello! What is your name?")
        if self.user.first_name:
            self.chain.set_entry_suggestions([self.user.first_name])
        self.chain.set_entry_text(self.handle_name)
        self.chain.send()

    def handle_name(self, name: str) -> None:
        self.user_name = name
        self.chain.sender.set_text("Nice! How are you feeling today?")
        self.chain.set_entry_text(self.handle_feeling)
        self.chain.send()

    def handle_feeling(self, feeling: str) -> None:
        self.chain.sender.set_text(f"Got it, {self.user_name.title()}! You feel: {feeling}")
        self.chain.set_inline_keyboard({"↺ Restart": self.handle_hello})
        self.chain.send()

How it works:

  • The handler reacts to "hello", "hi", or "hey" in any case.
  • handle_hello asks for the user's name.ч
  • set_entry_suggestions attaches the user's Telegram first_name as a suggestion button.
  • handle_name stores the name in self.user_name.
  • handle_feeling closes the flow and adds a "↺ Restart" button that routes back to the start.

It reads like regular Python because it is regular Python.

Sender

Want to attach an image, document or add an effect in a single line?

self.chain.sender.set_effect(Effect.HEART) # Add effect to message. Use enum or string
self.chain.sender.set_photo("robot.png") # Attach photo. URL, file_id, or path
self.chain.sender.set_document("README.md") # Attach document. URL, file_id, or path
self.chain.sender.set_text_as_document("Hello, this is a text document!") # Convert string to text document
self.chain.sender.send_chat_action(ChatAction.TYPING) # Send chat action. Use enum or string

Styles

Describe formatting as objects instead of writing raw HTML or Markdown.

from telekit.styles import *

def handle(self) -> None:
    self.chain.sender.set_text(
        Bold("Text style examples:\n"),
        Stack(
            Bold("Bold text"),
            Italic("Italic text"),
            Bold(Italic("Bold + italic")),
            Link("Link", url="https://example.com"),
            BotLink("Deep link", username="MyBot", start="promo_42"),
            start="- {{index}}. ",
            sep=".\n",
        )
    )
    self.chain.send()

You describe structure. Telekit turns that structure into HTML or MarkdownV2:

<b>Text style examples:</b>

- 1. <b>Bold text</b>.
- 2. <i>Italic text</i>.
- 3. <b><i>Bold + italic</i></b>.
- 4. <a href="https://example.com">Link</a>.
- 5. <a href="https://t.me/MyBot?start=promo_42">Deep link</a>

You skip manual escaping and the broken formatting one stray character causes.

Telekit DSL

Prefer not to write dialog logic in Python? Use the built-in DSL with Jinja support.

import telekit

class QuizHandler(telekit.DSLHandler):
    @classmethod
    def init_handler(cls) -> None:
        cls.analyze_string(script)
        cls.on.command("start").invoke(cls.start_script)

script = """
$ timeout {
    time = 20; // 20 sec.
}

@ main {
    title   = "🎉 Fun Facts Quiz";
    message = "Test your knowledge with 10 fun questions!";

    buttons {
        next("Start Quiz");
    }
}

@ question_1 {
    title   = "🐶 Question 1";
    message = "Which animal is the fastest on land?";
    buttons {
        _lose("Elephant");
        next("Cheetah");       // correct answer
        _lose("Horse");
        _lose("Lion");
    }
}

/* ... */
"""

telekit.Server(BOT_TOKEN).polling()

Key features of the Telekit DSL:

  • Scene-based architecture
  • Anonymous scenes
  • Automatic navigation stack management
  • Input handling
  • Images support and link buttons
  • Template variables
  • Custom variables
  • Hooks (Python API integration)
  • Jinja template engine
🎆 Click to see what you can do with the DSL
Telekit Example 7 Telekit Example 8
Telekit Example 7 Telekit Example 8

Traits

Traits are reusable behavior modules you can mix into any handler.

Here's the built-in CalendarPick trait: a user picks a date from an inline calendar, and a callback handles the result.

from telekit.traits import CalendarPick

class CalendarHandler(CalendarPick, telekit.Handler):

    @classmethod
    def init_handler(cls) -> None:
        cls.on.command("calendar").invoke(cls.handle)

    def handle(self) -> None:
        self.chain.sender.set_title("📅 Choose a date")
        self.chain.sender.set_message("Select any date — past or future:")
        self.chain.sender.set_remove_text(False)

        self.calendar_pick(self.handle_date) # HERE

    def handle_date(self, date: datetime.date) -> None:
        self.chain.sender.set_text(f"You picked: {date}")
        self.chain.send()
Result
Telekit Calendar Example

Example Bot

Run this to launch an example bot:

import telekit

telekit.example(YOUR_BOT_TOKEN)

It includes example commands, dialogs, keyboards, and style usage.

Why Telekit

  • Chains instead of an FSM.
  • Declarative, behavior-focused bot logic with minimal boilerplate.
  • Automatic callback routing and input handling.
  • A Styles API for rich text (Bold, Italic, links) with automatic escaping.
  • Deep linking and typed command parameters.
  • A built-in DSL for menus, FAQs, and simple bots.
  • Reusable Traits for composable behavior, including a built-in calendar picker.
  • Zero-code mode Obsidian Canvas mode.
  • Works with pyTelegramBotAPI.

Telekit focuses on one job: making Telegram bot development easier.


Changes in version 2.6.0a1

v2.5.4 (bug-fix)

  • Fix: Update condition to check for None instead of truthy value in DSLHandler

v2.5.3 (bug-fix)

  • Fix: Remove _ parameters from _filter_entry and _handle_entry methods in DSLHandler

v2.5.2

  • Add random_ variable prefix in DSLHandler — resolves to a random choice from a list/tuple/str static variable

v2.5.1 (bug-fix)

  • Implement TelegramMarkdownV2Sanitizer for improved MarkdownV2 handling

v2.5.0 (final)

  • Refactor CalendarPick trait to use set_keyboard

v2.5.0b3

  • Added utils.Markers class
  • Added HTMLText class for handling Telegram HTML strings with tag-aware indexing and slicing.
  • Added PaginatedText trait for displaying long HTML text in a paginated format, supporting navigation and smart splitting.
  • Added __radd__ to TextEntity: "Regular" + Bold(" and Bold")
  • Added __mul__ to TextEntity: Bold("Text") * 3
  • Added enabled= parameter to TextEntity: Bold("bold text", enabled=is_text_bold)
  • Added TextBuilder class – a fluent message composition API mirroring InlineKeyboard's builder pattern
  • Added styles to telekit.types
  • Added utils.CyclicList
  • Fixed _answer_callback_query to always call bot.answer_callback_query(), even without a popup text

v2.5.0b2

  • Added the escape parameter to telekit.utils.*:
    • make_user_link
    • make_bot_link
  • Handler.handlers_dict now excludes private handlers (classes whose names start with _).
  • Added Debug.duplicate_handler_warnings to warn about duplicate handler names during initialization.
  • Added Handler.chat object (BETA)

v2.5.0b1

  • Added Sender.send_message method.
  • Added utils.make_mention utility for generating tg://user?id= mention links.
  • Added new inline button types to inline_buttons:
    • ContactButton — mentions a user by Telegram ID via tg://user?id=.
    • UserLinkButton — opens a user profile by username; supports pre-filled message text.
    • BotLinkButton — opens a bot by username; supports deep-link ?start= payload.
  • Added new methods to InlineKeyboard:
    • add_contact — adds a ContactButton.
    • add_user_link — adds a UserLinkButton.
    • add_bot_link — adds a BotLinkButton.

v2.5.0b0

  • Added support for t-strings (PEP 750, Python 3.14+) in TextEntity.

Metadata

Release files for telekit 2.6.0a1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for telekit 2.6.0a1
File Size Uploaded
telekit-2.6.0a1.tar.gz 167.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for telekit 2.6.0a1
File Interpreter ABI Platform
telekit-2.6.0a1-py3-none-any.whl Python 3 none any Details

Total release size: 362.1 kB

Release files / telekit-2.6.0a1.tar.gz

Download URL telekit-2.6.0a1.tar.gz
Size 167.2 kB
Tags Source
SHA-256 checksum
How to use checksums
9b6e3d2b931f126aabdd62b9f48770e8c5fd22c9f4a715cbf97bb5e90ac95bb5
BLAKE2b-256 checksum
How to use checksums
3cd282200abc9e6f3a1aa4bc9cb7fee7eba5a59dfdd75ecb6f460dc5b21cbf59
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.12.11

Release files / telekit-2.6.0a1-py3-none-any.whl

Download URL telekit-2.6.0a1-py3-none-any.whl
Size 194.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d8bee82d042fca571a56c48df2d82c9252117fc62e2bcdd5db229f73fb890b88
BLAKE2b-256 checksum
How to use checksums
9168208b9e9fe7698331aa832131f951634c0b53ac479735148d4f2eb2fb0fc6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.12.11

Release history Release notifications | RSS feed

This release

2.6.0a1 This release

2 release files

2.5.4

2 release files

2.5.3

2 release files

2.5.2

2 release files

2.5.1

2 release files

2.5.0

2 release files

2.4.0

2 release files

2.3.0

2 release files

2.2.0

2 release files

2.1.0

2 release files

2.0.1

2 release files

1.11.0

2 release files

1.9.0

2 release files

1.8.0

2 release files

1.7.0

2 release files

1.6.1

2 release files

1.6.0

2 release files

1.5.0

2 release files

1.4.2

2 release files

1.4.1

2 release files

1.4.0

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.2

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.10

2 release files

0.0.9

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page