Handlers¶
A handler is a function (or a method) that runs when an update matches a trigger. You write it, decorate it with the trigger, and the library does the routing.
from dj_telegram_bot.core.handlers import command_handler
@command_handler("start")
def start(ctx):
ctx.bot.send_message(ctx.chat_id, "Hello!")
ctx is the handler object: it gives you the bot, the chat, the text, the user and more. The full list is in Handler object.
Pick a decorator¶
Most bots use @command_handler, @message_handler and @callback_handler. The others cover steps, inline mode and channel posts.
| Decorator | Runs for | Trigger is matched against |
|---|---|---|
@command_handler |
messages starting with / |
the text without the leading /, including arguments |
@message_handler |
every other message, and channel posts | the text (or caption) |
@message_step_handler |
same as above | the user's current step |
@callback_handler |
inline button presses | callback_data |
@callback_step_handler |
inline button presses | the user's current step |
@inline_handler |
inline queries | the query text |
@chosen_handler |
a chosen inline result | the result_id |
Imports
from dj_telegram_bot.core.handlers import (
command_handler,
message_handler,
callback_handler,
inline_handler,
chosen_handler,
message_step_handler,
callback_step_handler,
)
Function or class?¶
Tip
Start with functions. Reach for a class when you want to keep related handlers together.
Function handlers¶
The function receives the handler object, conventionally named ctx. Type-hint it to get autocomplete:
from dj_telegram_bot.core.handlers import CommandHandler, command_handler
@command_handler("start")
def start(ctx: CommandHandler):
ctx.bot.send_message(ctx.chat_id, "Hello!")
The class of ctx depends on the decorator:
| Decorator | ctx is a |
|---|---|
@command_handler |
CommandHandler |
@message_handler, @message_step_handler |
MessageHandler |
@callback_handler, @callback_step_handler |
CallBackQueryHandler |
@inline_handler |
InlineQueryHandler |
@chosen_handler |
ChosenInlineResultHandler |
One function can serve several triggers by stacking decorators:
@command_handler("help")
@message_handler("Help")
def help_(ctx):
...
Class handlers¶
Group related handlers in a class. Inherit from UpdateHandler and decorate methods:
from dj_telegram_bot.core.handlers import UpdateHandler, command_handler, callback_handler
class Shop(UpdateHandler):
@command_handler("shop")
def open_shop(self):
self.bot.send_message(self.chat_id, "Welcome to the shop")
@callback_handler("buy_*")
def buy(self):
item = self.callback_data.removeprefix("buy_")
self.bot.answer_callback_query(self.callback_query.id, text=f"Added {item}")
UpdateHandler has every member of every update type, so your editor shows self.bot, self.chat_id, self.callback_data and the rest in any method.
Note
Members that belong to another update type are empty ("" or None): self.callback_data is "" inside a @command_handler method.
Note
The library creates your class as Shop(update, bot) when a route matches. If you override __init__, keep that signature and call super().__init__(update, bot).
If a class only handles one update type, you can inherit from that class instead (CallBackQueryHandler, CommandHandler, ...). BaseHandler has only the members common to every update.
Triggers and wildcards¶
A trigger is an exact string, or a string with *:
| Trigger | Matches |
|---|---|
"start" |
exactly start |
"buy_*" |
anything starting with buy_ |
"*_done" |
anything ending with _done |
"*promo*" |
anything containing promo |
"*" |
everything |
* is only allowed at the start, the end, or both. "a*b" raises ValueError when the module loads.
Command arguments
ctx.command is the whole text after the slash. /start gives "start", but /start ref_42 gives "start ref_42", and /start@MyBot gives "start@MyBot". Use "start*" to accept arguments:
@command_handler("start*")
def start(ctx):
payload = ctx.command.removeprefix("start").strip() # "ref_42"
Lookup order¶
For every update the router tries, in this order, and uses the first match:
- exact trigger
- the user's step (message and callback handlers only)
- wildcards, highest
priorityfirst, then the longest literal part
@message_handler("*", priority=-10) # fallback, tried last
def fallback(ctx): ...
@message_handler("buy_*")
def buy(ctx): ...
@message_handler("buy_premium_*") # longer literal, wins over "buy_*"
def buy_premium(ctx): ...
Warning
Registering the same trigger twice with different functions logs a warning (Pattern 'x' in message overridden) and the last one wins.
Steps¶
A step is a short string stored on the user (user.step, default "home"). It lets you build conversations: ask something, set a step, handle the next message.
from dj_telegram_bot.core.handlers import command_handler, message_step_handler
@command_handler("register")
def register(ctx):
ctx.set_step("ask_name")
ctx.bot.send_message(ctx.chat_id, "What's your name?")
@message_step_handler("ask_name")
def got_name(ctx):
ctx.set_step("home")
ctx.bot.send_message(ctx.chat_id, f"Nice to meet you, {ctx.text}!")
Warning
Step handlers need a database user with user_id and step fields. Without one, user_step is an empty string and they never match.
- An exact trigger always wins over a step, so a user in
ask_namewho taps a button with an exact handler still reaches that handler. - Step handlers also fire for non-text messages (photo, contact, location).
ctx.textis empty in that case. callback_step_handlerdoes the same for inline button presses.
Unmatched updates¶
Info
If nothing matches, the update is silently ignored. Add a @message_handler("*", priority=-10) if you want a fallback reply.
Next¶
- Examples: working examples you can copy.
- Handler object: everything
ctx/selfgives you.