Диспетчер и роутеры¶
В основе обработки событий в maxo лежит механизм маршрутизации, который позволяет гибко управлять потоком входящих обновлений.
Диспетчер (Dispatcher)¶
Dispatcher - корневой роутер вашего бота. Он отвечает за получение обновлений (через Long Polling или Webhook) и их распределение по цепочке обработчиков. Диспетчер также инициализирует хранилище состояний (FSM) и глобальные middleware.
import os
from maxo import Bot, Dispatcher
from maxo.routing.ctx import Ctx
from maxo.routing.updates import MessageCreated
from maxo.transport.long_polling import LongPolling
bot = Bot(token=os.environ["TOKEN"])
dispatcher = Dispatcher()
# Регистрация обработчиков прямо в диспетчере (так как он тоже Router)
@dispatcher.message_created()
async def echo(update: MessageCreated, ctx: Ctx):
await update.answer_text(update.message.body.text or "Текста нет")
# Запуск
LongPolling(dispatcher).run(bot)
Роутеры (Routers)¶
Router позволяет структурировать код бота, разбивая его на логические модули (например, «админка», «магазин», «техподдержка»). Вместо того чтобы регистрировать все обработчики в одном файле, вы можете разнести их по разным роутерам.
Вы можете создавать сколько угодно роутеров и вкладывать их друг в друга. Событие проходит по роутерам в порядке их регистрации.
from maxo import Router
admin_router = Router(name="admin")
shop_router = Router(name="shop")
# Регистрация обработчиков в роутерах
@admin_router.message_created(...)
async def admin_handler(...): ...
@shop_router.message_created(...)
async def shop_handler(...): ...
# Подключение роутеров к диспетчеру
dispatcher.include(admin_router)
dispatcher.include(shop_router)
Вложенность¶
Роутеры могут быть вложенными. Это позволяет создавать сложные иерархии обработки. Например, у вас может быть главный роутер для диалогов, который включает в себя под-роутеры для разных сценариев.
settings_router = Router()
profile_router = Router()
# Роутер профиля включает в себя роутер настроек
profile_router.include(settings_router)
# Диспетчер включает роутер профиля
dispatcher.include(profile_router)
Фильтры на уровне роутера¶
Помимо фильтров на отдельных обработчиках, вы можете установить фильтр на целый роутер (точнее - на его наблюдатель за конкретным типом события).
Если фильтр роутера не проходит, все обработчики этого типа в данном роутере будут пропущены, и событие перейдёт к следующему роутеру.
from maxo import Router
from maxo.routing.ctx import Ctx
from maxo.routing.filters import BaseFilter
from maxo.routing.updates import MessageCreated
class IsGroupChat(BaseFilter[MessageCreated]):
"""Пропускает только сообщения из групповых чатов."""
async def __call__(self, update: MessageCreated, ctx: Ctx) -> bool:
return update.message.recipient.chat_type == "chat"
# Создаём роутер и навешиваем фильтр на все его message_created обработчики
group_router = Router(name="group")
group_router.message_created.filter(IsGroupChat())
@group_router.message_created()
async def group_handler(update: MessageCreated, ctx: Ctx):
# Этот обработчик вызовется ТОЛЬКО для групповых чатов
await update.answer_text("Привет, группа!")
@group_router.message_created()
async def another_group_handler(update: MessageCreated, ctx: Ctx):
# Этот обработчик тоже только для групповых чатов
...
Примечание
Метод .filter() устанавливается на наблюдатель конкретного типа события (например, message_created, message_callback). Это позволяет гибко управлять маршрутизацией: один роутер может обрабатывать только события из групп, другой - только из личных сообщений.
Порядок обработки¶
Когда приходит событие (например, новое сообщение), оно проверяется на соответствие фильтрам обработчиков в каждом роутере по очереди:
Сначала проверяются обработчики
dispatcher.Затем проверяются вложенные роутеры в том порядке, в котором они были подключены через
include().Внутри каждого роутера обработчики проверяются в порядке их определения в коде.
Если обработчик найден и все фильтры прошли успешно, событие обрабатывается, и дальнейшее распространение останавливается. Если ни один обработчик в текущем роутере не подошел, управление передается следующему роутеру в списке.
Пример: приоритет обработчиков¶
from maxo import Dispatcher, Router
from maxo.routing.ctx import Ctx
from maxo.routing.filters import Command
from maxo.routing.updates import MessageCreated
dispatcher = Dispatcher()
admin_router = Router(name="admin")
user_router = Router(name="user")
# 1. Обработчик диспетчера - проверяется ПЕРВЫМ
@dispatcher.message_created(Command("start"))
async def global_start(update: MessageCreated, ctx: Ctx):
await update.answer_text("Глобальный /start")
# 2. Обработчик в admin_router - проверяется ВТОРЫМ
@admin_router.message_created(Command("start"))
async def admin_start(update: MessageCreated, ctx: Ctx):
# Этот обработчик НЕ будет вызван для /start,
# потому что глобальный обработчик уже перехватил событие
await update.answer_text("Админский /start")
# 3. Обработчик в user_router - проверяется ТРЕТЬИМ
@user_router.message_created()
async def echo(update: MessageCreated, ctx: Ctx):
# Обрабатывает всё, что не было перехвачено выше
await update.answer_text(update.message.body.text or "Текста нет")
# Порядок подключения определяет приоритет между роутерами
dispatcher.include(admin_router) # admin_router проверяется раньше
dispatcher.include(user_router) # user_router проверяется позже
Доступные события¶
Ниже приведен список всех событий, которые вы можете перехватывать и обрабатывать с помощью диспетчера и роутеров.
Декоратор регистрации |
Тип события (Update) |
Описание |
|---|---|---|
|
Новое сообщение от пользователя (текст, фото, файлы и т.д.). |
|
|
Нажатие на кнопку Inline-клавиатуры. |
|
|
Пользователь отредактировал ранее отправленное сообщение. |
|
|
Пользователь удалил сообщение. |
|
|
Пользователь нажал кнопку «Запустить» или впервые начал диалог с ботом. |
|
|
Пользователь заблокировал бота. |
|
|
В групповой чат добавлен новый участник. |
|
|
Участник покинул групповой чат или был удален. |
|
|
Бот добавлен в групповой чат. |
|
|
Бот удален из группового чата. |
|
|
Название группового чата изменено. |
|
|
История переписки очищена. |
|
|
Диалог удален. |
|
|
Уведомления в диалоге отключены или включены. |
|
|
Произошла ошибка при обработке другого события. |
Сигналы жизненного цикла¶
Эти события не приходят от Max API, а генерируются самим фреймворком при запуске и остановке.
Декоратор регистрации |
Тип сигнала |
Описание |
|---|---|---|
|
|
Вызывается перед запуском процесса получения обновлений (Polling/Webhook). |
|
|
Вызывается сразу после успешного старта. |
|
|
Вызывается перед началом процесса остановки бота (Graceful Shutdown). |
|
|
Вызывается после полной остановки и закрытия соединений. |