Handlers¶
Command handlers and message processing with aiogram.
Handler Structure¶
Handlers are defined in src/delivery/bot/handlers.py:
from aiogram import Router
from aiogram.filters import Command
from aiogram.types import Message
router = Router()
@router.message(Command(commands=["start"]))
async def handle_start_command(message: Message) -> None:
if message.from_user is None:
return
await message.answer("Hello! This is a bot.")
@router.message(Command(commands=["id"]))
async def handle_id_command(message: Message) -> None:
if message.from_user is None:
return
await message.answer(
f"User Id: <b>{message.from_user.id}</b>\n"
f"Chat Id: <b>{message.chat.id}</b>",
)
Filters¶
Command Filter¶
Match specific commands:
from aiogram.filters import Command
@router.message(Command(commands=["help"]))
async def handle_help(message: Message) -> None:
await message.answer("Help message here")
# Multiple commands
@router.message(Command(commands=["start", "help"]))
async def handle_start_or_help(message: Message) -> None:
await message.answer("Welcome or help!")
Text Filter¶
Match specific text:
from aiogram import F
@router.message(F.text == "ping")
async def handle_ping(message: Message) -> None:
await message.answer("pong")
# Case-insensitive
@router.message(F.text.lower() == "hello")
async def handle_hello(message: Message) -> None:
await message.answer("Hi there!")
# Contains text
@router.message(F.text.contains("help"))
async def handle_help_request(message: Message) -> None:
await message.answer("How can I help?")
Content Type Filter¶
Match by message content:
from aiogram.types import ContentType
@router.message(F.content_type == ContentType.PHOTO)
async def handle_photo(message: Message) -> None:
await message.answer("Nice photo!")
@router.message(F.content_type == ContentType.DOCUMENT)
async def handle_document(message: Message) -> None:
await message.answer("Document received!")
Combined Filters¶
@router.message(F.text, F.chat.type == "private")
async def handle_private_text(message: Message) -> None:
await message.answer("Private message received")
Command Arguments¶
Parse arguments from commands:
@router.message(Command(commands=["greet"]))
async def handle_greet(message: Message) -> None:
# /greet John
if message.text is None:
return
parts = message.text.split(maxsplit=1)
name = parts[1] if len(parts) > 1 else "stranger"
await message.answer(f"Hello, {name}!")
Callback Queries¶
Handle inline button clicks:
from aiogram.types import CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
@router.message(Command(commands=["menu"]))
async def show_menu(message: Message) -> None:
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[
InlineKeyboardButton(text="Option 1", callback_data="option_1"),
InlineKeyboardButton(text="Option 2", callback_data="option_2"),
]
])
await message.answer("Choose an option:", reply_markup=keyboard)
@router.callback_query(F.data == "option_1")
async def handle_option_1(callback: CallbackQuery) -> None:
await callback.answer("You chose option 1!")
await callback.message.edit_text("Option 1 selected")
@router.callback_query(F.data == "option_2")
async def handle_option_2(callback: CallbackQuery) -> None:
await callback.answer("You chose option 2!")
await callback.message.edit_text("Option 2 selected")
State Management¶
For multi-step conversations:
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
class Form(StatesGroup):
name = State()
age = State()
@router.message(Command(commands=["form"]))
async def start_form(message: Message, state: FSMContext) -> None:
await state.set_state(Form.name)
await message.answer("What's your name?")
@router.message(Form.name)
async def process_name(message: Message, state: FSMContext) -> None:
await state.update_data(name=message.text)
await state.set_state(Form.age)
await message.answer("How old are you?")
@router.message(Form.age)
async def process_age(message: Message, state: FSMContext) -> None:
data = await state.get_data()
name = data.get("name")
age = message.text
await state.clear()
await message.answer(f"Hello {name}, you are {age} years old!")
Middleware¶
Add custom middleware:
from typing import Any, Awaitable, Callable, Dict
from aiogram import BaseMiddleware
from aiogram.types import Message
class LoggingMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable[[Message, Dict[str, Any]], Awaitable[Any]],
event: Message,
data: Dict[str, Any],
) -> Any:
print(f"Received: {event.text}")
result = await handler(event, data)
print(f"Handled: {event.text}")
return result
# Register in dispatcher factory
dispatcher.message.middleware(LoggingMiddleware())
Error Handling¶
Handle errors in handlers:
from aiogram.types import ErrorEvent
@router.error()
async def handle_error(event: ErrorEvent) -> None:
import logging
logging.exception("Error in handler", exc_info=event.exception)
if event.update.message:
await event.update.message.answer(
"An error occurred. Please try again."
)
Dependency Injection¶
Access container in handlers:
# In __main__.py
dispatcher.run_polling(bot, container=container)
# In handlers
from punq import Container
@router.message(Command(commands=["user_count"]))
async def handle_user_count(
message: Message,
container: Container,
) -> None:
from core.user.models import User
count = User.objects.count()
await message.answer(f"Total users: {count}")
Organizing Handlers¶
Split handlers into modules:
delivery/bot/
├── handlers/
│ ├── __init__.py
│ ├── commands.py # Command handlers
│ ├── callbacks.py # Callback query handlers
│ └── admin.py # Admin commands
└── factories.py
# handlers/__init__.py
from aiogram import Router
from .commands import router as commands_router
from .callbacks import router as callbacks_router
main_router = Router()
main_router.include_router(commands_router)
main_router.include_router(callbacks_router)
Best Practices¶
1. Always Check for None¶
@router.message(Command(commands=["start"]))
async def handle_start(message: Message) -> None:
if message.from_user is None:
return # Guard against None
await message.answer(f"Hello, {message.from_user.first_name}!")
2. Use HTML Parse Mode¶
3. Handle Errors Gracefully¶
@router.message(Command(commands=["data"]))
async def handle_data(message: Message) -> None:
try:
data = fetch_data()
await message.answer(f"Data: {data}")
except Exception:
await message.answer("Failed to fetch data. Please try again.")
4. Use Inline Keyboards for Actions¶
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Confirm", callback_data="confirm")],
[InlineKeyboardButton(text="Cancel", callback_data="cancel")],
])
await message.answer("Are you sure?", reply_markup=keyboard)
Related Topics¶
- Bot & Dispatcher Factories — Factory configuration
- Your First Bot Command — Tutorial
- aiogram Documentation — Official docs