Диспетчер и роутеры

В основе обработки событий в 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). Это позволяет гибко управлять маршрутизацией: один роутер может обрабатывать только события из групп, другой - только из личных сообщений.

Порядок обработки

Когда приходит событие (например, новое сообщение), оно проверяется на соответствие фильтрам обработчиков в каждом роутере по очереди:

  1. Сначала проверяются обработчики dispatcher.

  2. Затем проверяются вложенные роутеры в том порядке, в котором они были подключены через include().

  3. Внутри каждого роутера обработчики проверяются в порядке их определения в коде.

Если обработчик найден и все фильтры прошли успешно, событие обрабатывается, и дальнейшее распространение останавливается. Если ни один обработчик в текущем роутере не подошел, управление передается следующему роутеру в списке.

Пример: приоритет обработчиков

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)

Описание

@router.message_created

MessageCreated

Новое сообщение от пользователя (текст, фото, файлы и т.д.).

@router.message_callback

MessageCallback

Нажатие на кнопку Inline-клавиатуры.

@router.message_edited

MessageEdited

Пользователь отредактировал ранее отправленное сообщение.

@router.message_removed

MessageRemoved

Пользователь удалил сообщение.

@router.bot_started

BotStarted

Пользователь нажал кнопку «Запустить» или впервые начал диалог с ботом.

@router.bot_stopped

BotStopped

Пользователь заблокировал бота.

@router.user_added_to_chat

UserAddedToChat

В групповой чат добавлен новый участник.

@router.user_removed_from_chat

UserRemovedFromChat

Участник покинул групповой чат или был удален.

@router.bot_added_to_chat

BotAddedToChat

Бот добавлен в групповой чат.

@router.bot_removed_from_chat

BotRemovedFromChat

Бот удален из группового чата.

@router.chat_title_changed

ChatTitleChanged

Название группового чата изменено.

@router.dialog_cleared

DialogCleared

История переписки очищена.

@router.dialog_removed

DialogRemoved

Диалог удален.

@router.dialog_muted / @router.dialog_unmuted

DialogMuted / DialogUnmuted

Уведомления в диалоге отключены или включены.

@router.error

ErrorEvent

Произошла ошибка при обработке другого события.

Сигналы жизненного цикла

Эти события не приходят от Max API, а генерируются самим фреймворком при запуске и остановке.

Декоратор регистрации

Тип сигнала

Описание

@router.before_startup

BeforeStartup

Вызывается перед запуском процесса получения обновлений (Polling/Webhook).

@router.after_startup

AfterStartup

Вызывается сразу после успешного старта.

@router.before_shutdown

BeforeShutdown

Вызывается перед началом процесса остановки бота (Graceful Shutdown).

@router.after_shutdown

AfterShutdown

Вызывается после полной остановки и закрытия соединений.