Declarative, developer-friendly library for building Telegram bots
Project description
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(), built-in emoji game results for 🎲 🎯 🏀 ⚽ 🎳 🎰, and much more out of the box. Its declarative design makes bots easier to read, maintain, and extend.
Key features:
- Declarative bot logic with chains for effortless handling of complex conversations
- Ready-to-use DSL for FAQs and other interactive scripts
- Automatic handling of message formatting via Sender and callback routing
- Deep Linking support with type-checked Command Parameters for flexible user input
- Built-in Permission and Logging system for user management
- Seamless integration with pyTelegramBotAPI
- Fast to develop and easy-to-extend code
GitHub PyPI Telegram Community
Contents
Overview
Telekit is a library for building Telegram bots where dialogs look like normal method calls. No bulky state machines. No scattered handlers.
The idea is simple: 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()
The handle method sends a message and registers handle_name as the next step using set_entry_text. When the user replies, Telekit automatically calls handle_name and passes the user's message as a plain str argument.
That's it. No enums. No manual state tracking. No boilerplate.
Inline Keyboards
Buttons can either return a value or call a method directly.
Choice keyboard — map button labels to values. The selected value is passed straight into your handler:
self.chain.set_inline_choice(
self.on_choice,
choices={
"Option 1": "Value 1",
"Option 2": "Value 2",
"Option 3": [3, "Yes, it's an array"],
}
)
def on_choice(self, choice: str | list):
print(f"{choice!r}") # "Value 1", "Value 2" or [3, "Yes, it's an array"]
Inside on_choice, you receive exactly what you defined in choices: a string, list, number, function — anything.
Callback keyboard — each button calls its own method:
self.chain.set_inline_keyboard({
"« Back": self.display_previous_page,
"Next »": self.display_next_page,
})
Useful for pagination, navigation, or menus.
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 64 "Alice Reingold" or /greet 128 Dracula are parsed automatically.
[!INFO] If arguments are invalid or missing, you simply receive
Noneand decide how to respond.
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" (lowercase, UPPERCASE, or mixed).
handle_helloasks for the user's name.set_entry_suggestionsattaches the user's Telegramfirst_nameas a suggestion button.handle_namestores the name inself.user_name.handle_feelingcompletes the flow and adds a"↺ Restart"button that routes back to the beginning.
It looks like regular Python. And reads like it too.
Sender
Want to add an image, document or 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
[!NOTE] Telekit automatically decides whether to use 'bot.send_message' or 'bot.send_photo' based on the content
Styles
Telekit lets you 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 generates HTML or MarkdownV2 automatically:
<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>
No manual escaping. No broken formatting because of one missing character.
Telekit DSL
If you prefer not to write dialog logic in Python, you can use the built-in DSL with Jinja support.
import telekit
class QuizHandler(telekit.TelekitDSL.Mixin):
@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
You can find a full quiz example and DSL reference in the repository.
Example Bot
You can launch an example bot by running the following code:
import telekit
telekit.example(YOUR_BOT_TOKEN)
It includes example commands, dialogs, keyboards, and style usage.
Why Telekit
- No FSM — just chains.
- Declarative, behavior-focused bot logic with minimal boilerplate.
- Automatic callback routing and input handling.
- Styles API for rich text (
Bold,Italic,Links) with automatic escaping. - Deep linking and typed command parameters.
- Built-in DSL for menus, FAQs, and simple bots.
- Zero-code Obsidian Canvas mode.
- Seamless integration with pyTelegramBotAPI.
Telekit doesn't try to be everything.
It tries to make Telegram bot development easier.
[!TIP] If you're interested and want to learn more, check out the Tutorial
Changes in version 2.3.0b1
Traits
| Name | Description |
|---|---|
TrackHandoffOrigin |
Tracks which handler transferred control to this one via handoff(). |
PaginatedChoice |
Adds a paginated inline keyboard for choosing from a list of items. |
TrackHandoffOrigin
TrackHandoffOrigin adds three members to any handler that inherits it:
| Member | Description |
|---|---|
handoff_origin |
The handler instance that handed off to this one, or None if invoked directly. |
is_handed_off |
True if this handler was reached via handoff(), False otherwise. |
handoff_back() |
Transfer control back to the origin handler via self.handoff_origin.handle() |
handoff_back_or(handler) |
Like handoff_back(), but falls back to handler on fail. |
Set TRACK_HANDOFF_ORIGIN = False on any subclass to opt out of tracking.
class MyHandler(TrackHandoffOrigin, telekit.Handler):
def handle(self):
...
self.chain.set_inline_keyboard({
"« Back": self.handoff_back_or(StartHandler)
})
self.chain.edit()
PaginatedChoice
Renders a paginated inline keyboard from any dict or iterable.
Navigation buttons (« Back, Next ») are added automatically.
| Member | Description |
|---|---|
paginated_choice(choices, on_choice, on_update, row_width) |
Display a paginated choice keyboard. |
self.chain.sender.set_title("🔤 What is your initial?")
self.chain.sender.set_message("Pick the first letter of your name")
self.chain.sender.set_remove_text(False)
self.paginated_choice(
choices="ABCDEFGHIJKLMNOPQRSTUVWXYZ",
on_choice=self.handle_letter,
row_width=5
)
(Click to see the result)
choices accepts a dict[str, T], or any Iterable[T].
If only one item is present, on_choice is called immediately without rendering a keyboard.
Utils
| Name | Description |
|---|---|
load_env |
Load all key-value pairs from a .env file into a dictionary. |
read_env_var |
Read a single variable by name from a .env file. |
read_tokenandread_canvas_pathnow support reading from.envfiles. Pass".env"to use the default key, or".env:KEY"to specify a custom one:
read_token(".env") # reads TOKEN
read_token(".env:BOT_TOKEN") # reads BOT_TOKEN
read_canvas_path(".env") # reads CANVAS_PATH
read_canvas_path(".env:MY_CANVAS") # reads MY_CANVAS
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file telekit-2.3.0b1.tar.gz.
File metadata
- Download URL: telekit-2.3.0b1.tar.gz
- Upload date:
- Size: 118.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11ebfec40c6ba6cdea205f7a91fe17182117d0c4fccc907d8e0569e21a37542d
|
|
| MD5 |
08b6594906bc58b22550c0e701a036d9
|
|
| BLAKE2b-256 |
fa0b810200693d00b1e5e40ccd08fadfe4df631cdcecdc409d0b323783030a8e
|
File details
Details for the file telekit-2.3.0b1-py3-none-any.whl.
File metadata
- Download URL: telekit-2.3.0b1-py3-none-any.whl
- Upload date:
- Size: 140.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8a5db6a4b96e0005dcec4ae1f2539d138fe844011fc2cf47be83eaa1aa45238b
|
|
| MD5 |
088875fae19f85d333841fd1711776d5
|
|
| BLAKE2b-256 |
0fc654f81e833387911bae3aefd3ed4d103a9900b968599af3f2e886dafb5312
|