maxo - библиотека и асинхронный Python-фреймворк для разработки ботов MAX (max.ru) и работы с MAX Bot API
Асинхронный Python-фреймворк для разработки ботов в MAX
Документация
Интерфейс основан на aiogram
maxo/dialogs сделано из aiogram_dialog
maxo/transport/webhook сделано из aiogram-webhook
- Интерфейс намеренно близок к
aiogram: роутеры, фильтры, мидлвари, FSM и диалоги работают так, как вы привыкли - Диалоги (
maxo.dialogs): окна, виджеты, пагинация, календарь. Интерфейс можно посмотреть в браузере до запуска бота, а сценарии - тестировать без сети - Long-polling и вебхуки (
aiohttp/fastapi), FSM с Redis, DI черезdishkaи фильтры наmagic_filter - Методы, типы и апдейты генерируются по официальной документации MAX Bot API - меньше расхождений с платформой
- 100% аннотаций и
mypy --strict- ошибки видно в IDE, а не в проде - Ошибки API типизированы: можно писать
except MaxBotTooManyRequestsError, а не разбирать голый HTTP-ответ - Российский доверенный сертификат уже вшит в HTTP-клиент, настраивать SSL для API MAX вручную не нужно
- Асинхронность на
aiohttpиunihttp, валидация данных черезadaptix, документация на русском
Через pip:
pip install maxo
В pyproject.toml:
[project]
dependencies = [
"maxo",
]- Разработка ботов MAX на Python
- Работа с MAX Bot API
- long-polling и webhook для MAX
- FSM, диалоги и inline-клавиатуры для ботов
- Миграция с aiogram-подхода на MAX
Больше примеров в примерах
from maxo import Bot, Dispatcher
from maxo.types import MessageCreated
from maxo.transport.long_polling import LongPolling
bot = Bot("TOKEN")
dp = Dispatcher()
@dp.message_created()
async def echo_handler(message: MessageCreated) -> None:
text = message.text or "Текста нет"
await message.answer(text)
LongPolling(dp).run(bot)from maxo import Bot, Dispatcher
from maxo.routing.filters import Command, DeeplinkFilter
from maxo.transport.long_polling import LongPolling
from maxo.types import BotStarted, MessageCreated
bot = Bot("TOKEN")
dp = Dispatcher()
@dp.bot_started(DeeplinkFilter())
async def deeplink_handler(bot_started: BotStarted, deeplink: str) -> None:
await bot_started.send_message(f"Привет! Я бот. Диплинк: {deeplink}")
@dp.bot_started()
async def start_handler(bot_started: BotStarted) -> None:
await bot_started.send_message(f"Привет! Я бот. А ты {bot_started.user.fullname}")
@dp.message_created(Command("help"))
async def help_handler(message: MessageCreated) -> None:
await message.send_message("За помощью обращайтесь в t.me/maxo_py")
LongPolling(dp).run(bot)from magic_filter import F
from maxo import Bot, Dispatcher
from maxo.integrations.magic_filter import MagicFilter
from maxo.routing.filters import CommandStart
from maxo.transport.long_polling import LongPolling
from maxo.types import MessageCallback, MessageCreated
from maxo.utils.builders import KeyboardBuilder
bot = Bot("TOKEN")
dp = Dispatcher()
@dp.message_created(CommandStart())
async def start_handler(message: MessageCreated) -> None:
maxo_url = "https://github.com/K1rL3s/maxo"
keyboard = (
KeyboardBuilder()
.add_callback(text="Колбэк", payload="click_me")
.add_message(text="Сообщение")
.add_link(text="Перейти в maxo", url=maxo_url)
.add_clipboard(text="Скопировать maxo", payload=maxo_url)
.add_request_contact(text="Поделиться контактами")
.add_request_geo_location(text="Поделиться геопозицией")
.adjust(2, 2, 1, 1)
)
await message.answer(text="Кнопочки :3", keyboard=keyboard.build())
@dp.message_callback(MagicFilter(F.payload == "click_me"))
async def button_handler(callback: MessageCallback) -> None:
await callback.callback_answer("Вы нажали на кнопку!")
LongPolling(dp).run(bot)Многошаговый сценарий - это окна и виджеты, переходами управляет менеджер
диалога. Окна можно отрисовать в HTML-превью без запуска бота, а сценарии -
тестировать без сети через maxo.dialogs.test_tools:
from maxo import Bot, Dispatcher
from maxo.dialogs import Dialog, DialogManager, StartMode, Window, setup_dialogs
from maxo.dialogs.widgets.kbd import Button
from maxo.dialogs.widgets.text import Const
from maxo.fsm import State, StatesGroup
from maxo.fsm.key_builder import DefaultKeyBuilder
from maxo.routing.filters import CommandStart
from maxo.transport.long_polling import LongPolling
from maxo.types import MessageCallback, MessageCreated
bot = Bot("TOKEN")
# Для диалогов нужен key builder с destiny
dp = Dispatcher(key_builder=DefaultKeyBuilder(with_destiny=True))
class MainState(StatesGroup):
main = State()
async def close_dialog(
callback: MessageCallback,
button: Button,
manager: DialogManager,
) -> None:
await manager.done()
dialog = Dialog(
Window(
Const("Главное меню"),
Button(Const("Закрыть"), id="close", on_click=close_dialog),
state=MainState.main,
),
)
@dp.message_created(CommandStart())
async def start_handler(
message: MessageCreated,
dialog_manager: DialogManager,
) -> None:
await dialog_manager.start(MainState.main, mode=StartMode.RESET_STACK)
dp.include(dialog)
setup_dialogs(dp)
LongPolling(dp).run(bot)import logging
from aiohttp import web
from maxo import Bot, Dispatcher, Router
from maxo.enums import TextFormat
from maxo.routing.utils import collect_used_updates
from maxo.transport.webhook.adapters.aiohttp import AiohttpWebAdapter
from maxo.transport.webhook.engines import SimpleEngine, WebhookEngine
from maxo.transport.webhook.routing import StaticRouting
from maxo.transport.webhook.security import Security, StaticSecretToken
from maxo.types import BotStarted, MessageCreated
bot = Bot("TOKEN")
router = Router()
@router.bot_started()
async def start_handler(bot_started: BotStarted) -> None:
await bot_started.send_message(
text=f"Привет из вебхука, {bot_started.user.first_name}!",
)
@router.message_created()
async def echo_handler(message: MessageCreated) -> None:
await message.answer(
text=message.message.body.html_text,
format=TextFormat.HTML,
)
@router.after_startup()
async def on_startup(dispatcher: Dispatcher, webhook_engine: WebhookEngine) -> None:
await webhook_engine.set_webhook(update_types=collect_used_updates(dispatcher))
def main() -> None:
dispatcher = Dispatcher()
dispatcher.include(router)
engine = SimpleEngine(
dispatcher,
bot,
web_adapter=AiohttpWebAdapter(),
routing=StaticRouting(url="https://example.com/webhook"),
security=Security(secret_token=StaticSecretToken("pepa_pig")),
)
app = web.Application()
engine.register(app)
web.run_app(app, host="127.0.0.1", port=8080)
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
main()MAX - российский мессенджер. У него есть открытое Bot API, для работы с которым и создан maxo.
maxo - отдельный фреймворк именно для ботов MAX, но интерфейс намеренно близок к aiogram, чтобы переход был максимально безболезненным. Диалоги (maxo.dialogs) портированы из aiogram_dialog, вебхуки (maxo.transport.webhook) - из aiogram-webhook.
Код один в один не переносится: MAX и Telegram - разные платформы со своими типами и методами. Но подход остаётся тем же: роутеры, фильтры, хэндлеры, FSM и диалоги называются и ведут себя привычно, поэтому переучиваться почти не придётся.
Да. Поддерживается и long-polling, и webhook через aiohttp или fastapi - см. примеры выше.
Python 3.12, 3.13 или 3.14.
FSM встроена в maxo - есть MemoryStorage из коробки и опциональное хранилище в Redis (maxo[redis]). Подробности - в документации.
Да. maxo умеет отправлять и принимать вложения - фото, видео, аудио и документы - через InputFile (загрузка файла) или по токену уже загруженного медиа. Крупные файлы грузятся частями (resumable). Подробности - в документации.
Да, через интеграцию с dishka (maxo[dishka]). Зависимости можно прокидывать в хэндлеры, фильтры и мидлвари.
Пока частично. Готовый SimpleEngine обслуживает одного бота. Для мульти-бот сценария есть заготовки: PathRouting и QueryRouting извлекают токен бота из URL или query-параметра, а выбор бота по токену реализуется наследником WebhookEngine (метод _get_bot_from_request).
Для продакшена используйте вебхуки: сервер MAX доставляет каждый апдейт один раз, и нагрузку можно распределить между воркерами (например, за Nginx или в Kubernetes). Long-polling для этого не подходит - при нескольких процессах с одним токеном апдейты дублируются.
Для диалогов есть maxo.dialogs.test_tools: BotClient эмулирует пользователя, MockMessageManager записывает отправленные сообщения, локаторы находят кнопки по тексту. Сценарий "клик по кнопке - смена окна - новый текст" проверяется без единого сетевого вызова. Пример - в examples/dialogs_testing.py.
Да, maxo - open-source под лицензией Apache 2.0. Можно использовать в том числе в коммерческих проектах.
Если у вас есть вопросы, вы можете задать их в Телеграме @maxo_py или Максе