Skip to content

K1rL3s/maxo

maxo - библиотека и асинхронный Python-фреймворк для разработки ботов MAX (max.ru) и работы с MAX Bot API

maxo - библиотека и асинхронный Python-фреймворк для разработки ботов MAX (max.ru) и работы с MAX Bot API

License Status PyPI Downloads GitHub Repo Stars GitHub Repo Forks Supported python versions Docs Tests Lint Coverage DeepWiki Context7

Асинхронный Python-фреймворк для разработки ботов в MAX

Документация

Интерфейс основан на aiogram
maxo/dialogs сделано из aiogram_dialog
maxo/transport/webhook сделано из aiogram-webhook

Почему maxo?

  • Интерфейс намеренно близок к 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",
]

Для чего подходит 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()

FAQ

Что такое MAX?

MAX - российский мессенджер. У него есть открытое Bot API, для работы с которым и создан maxo.

Чем maxo отличается от aiogram?

maxo - отдельный фреймворк именно для ботов MAX, но интерфейс намеренно близок к aiogram, чтобы переход был максимально безболезненным. Диалоги (maxo.dialogs) портированы из aiogram_dialog, вебхуки (maxo.transport.webhook) - из aiogram-webhook.

Можно ли перенести бота с aiogram на maxo?

Код один в один не переносится: MAX и Telegram - разные платформы со своими типами и методами. Но подход остаётся тем же: роутеры, фильтры, хэндлеры, FSM и диалоги называются и ведут себя привычно, поэтому переучиваться почти не придётся.

Поддерживает ли maxo вебхуки?

Да. Поддерживается и long-polling, и webhook через aiohttp или fastapi - см. примеры выше.

Какой Python нужен?

Python 3.12, 3.13 или 3.14.

Где взять токен бота MAX?

На платформе для партнёров.

Как добавить FSM?

FSM встроена в maxo - есть MemoryStorage из коробки и опциональное хранилище в Redis (maxo[redis]). Подробности - в документации.

Можно ли отправлять фото, видео и файлы?

Да. maxo умеет отправлять и принимать вложения - фото, видео, аудио и документы - через InputFile (загрузка файла) или по токену уже загруженного медиа. Крупные файлы грузятся частями (resumable). Подробности - в документации.

Есть ли dependency injection?

Да, через интеграцию с dishka (maxo[dishka]). Зависимости можно прокидывать в хэндлеры, фильтры и мидлвари.

Можно ли обслуживать несколько ботов в одном приложении?

Пока частично. Готовый SimpleEngine обслуживает одного бота. Для мульти-бот сценария есть заготовки: PathRouting и QueryRouting извлекают токен бота из URL или query-параметра, а выбор бота по токену реализуется наследником WebhookEngine (метод _get_bot_from_request).

Как масштабировать бота под нагрузку?

Для продакшена используйте вебхуки: сервер MAX доставляет каждый апдейт один раз, и нагрузку можно распределить между воркерами (например, за Nginx или в Kubernetes). Long-polling для этого не подходит - при нескольких процессах с одним токеном апдейты дублируются.

Как тестировать бота без реального MAX?

Для диалогов есть maxo.dialogs.test_tools: BotClient эмулирует пользователя, MockMessageManager записывает отправленные сообщения, локаторы находят кнопки по тексту. Сценарий "клик по кнопке - смена окна - новый текст" проверяется без единого сетевого вызова. Пример - в examples/dialogs_testing.py.

maxo бесплатный? Какая лицензия?

Да, maxo - open-source под лицензией Apache 2.0. Можно использовать в том числе в коммерческих проектах.

Связь

Если у вас есть вопросы, вы можете задать их в Телеграме @maxo_py или Максе

About

🐍 Асинхронный Python-фреймворк для разработки ботов MAX Bot API (max.ru)

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

50 stars

Watchers

1 watching

Forks

Sponsor this project

Contributors

Languages