Урок 77 из 84 · Telegram-бот на aiogram
Фильтры и роутеры
Содержание урока
Пока бот маленький, все хэндлеры можно держать в одном файле. Но когда команд становится двадцать, файл превращается в кашу. В этом уроке научимся точно выбирать, какие сообщения ловит хэндлер (фильтры), и раскладывать код по файлам (роутеры).
Магический фильтр F
F — это «шаблон» для проверки сообщения. Вы описываете условие, а aiogram сам проверяет его для каждого апдейта.
from aiogram import F
@router.message(F.text == "Привет") # текст ровно «Привет»
@router.message(F.text.lower() == "привет") # «привет», «ПРИВЕТ», «Привет»
@router.message(F.text.startswith("/")) # любая команда
@router.message(F.photo) # фото
@router.message(F.sticker) # стикер
@router.message(F.document) # файл
@router.message(F.voice) # голосовое
@router.message(F.from_user.id == 123456789) # только от конкретного пользователя
Каждая строка выше — отдельный пример; к одному хэндлеру обычно вешают один фильтр.
Условия можно комбинировать: & — «и», | — «или», ~ — «не»:
@router.message(F.text & ~F.text.startswith("/")) # текст, но не команда
async def only_text(message: Message):
...
@router.message(F.photo | F.document) # фото или файл
async def media(message: Message):
...
Пример: бот, который понимает фото
@router.message(F.photo)
async def got_photo(message: Message):
photo = message.photo[-1] # Telegram присылает несколько размеров, последний — самый большой
await message.answer(
f"Красивое фото! Размер: {photo.width}×{photo.height}\n"
f"file_id: {photo.file_id}"
)
file_id — это «адрес» файла на серверах Telegram. Сохраните его, и потом сможете отправить то же фото без повторной загрузки: await message.answer_photo(photo=file_id).
Свой фильтр
Если встроенных условий не хватает, напишите фильтр-класс. Например, пропускать только админов:
from aiogram.filters import BaseFilter
from aiogram.types import Message
ADMINS = {123456789, 987654321}
class IsAdmin(BaseFilter):
async def __call__(self, message: Message) -> bool:
return message.from_user.id in ADMINS
Используется как любой другой фильтр:
@router.message(Command("stats"), IsAdmin())
async def admin_stats(message: Message):
await message.answer("Статистика только для админов 📊")
Если передать в декоратор несколько фильтров через запятую, хэндлер сработает, только когда все они вернут True.
Роутеры: раскладываем код по файлам
Router — это контейнер для хэндлеров. Каждый файл создаёт свой роутер, а в главном файле все роутеры подключаются к диспетчеру.
Удобная структура проекта:
my_bot/
├── bot.py ← точка входа: создаём Bot и Dispatcher
├── .env
└── handlers/
├── __init__.py
├── common.py ← /start, /help
├── admin.py ← команды для админов
└── echo.py ← всё остальное
handlers/common.py:
from aiogram import Router
from aiogram.filters import Command, CommandStart
from aiogram.types import Message
router = Router()
@router.message(CommandStart())
async def cmd_start(message: Message):
await message.answer("Привет! Набери /help, чтобы узнать, что я умею.")
@router.message(Command("help"))
async def cmd_help(message: Message):
await message.answer("/start — начать\n/help — помощь")
handlers/echo.py:
from aiogram import F, Router
from aiogram.types import Message
router = Router()
@router.message(F.text)
async def echo(message: Message):
await message.answer(message.text)
bot.py:
import asyncio
import logging
import os
from aiogram import Bot, Dispatcher
from dotenv import load_dotenv
from handlers import admin, common, echo
load_dotenv()
async def main():
logging.basicConfig(level=logging.INFO)
bot = Bot(token=os.getenv("BOT_TOKEN"))
dp = Dispatcher()
# Порядок подключения = порядок проверки. Эхо — последним.
dp.include_routers(common.router, admin.router, echo.router)
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())
Теперь каждый файл отвечает за своё, а bot.py остаётся коротким, как бы ни рос бот.
Фильтр на весь роутер
Чтобы не писать IsAdmin() в каждом админском хэндлере, фильтр можно повесить на весь роутер сразу:
# handlers/admin.py
router = Router()
router.message.filter(IsAdmin()) # все хэндлеры сообщений этого роутера — только для админов
Практика
- Разложите эхо-бота из прошлого урока по структуре выше.
- Сделайте хэндлер, который на любой стикер отвечает «Классный стикер!».
- Добавьте админскую команду
/ping, которая отвечает «pong» только вам.