maxo - библиотека и асинхронный Python-фреймворк для разработки ботов MAX (max.ru) и работы с MAX Bot API
Асинхронный 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
bot = Bot("TOKEN")
dp = Dispatcher()
@dp.message_created()
async def echo_handler(message: MessageCreated) -> None:
text = message.text or "Текста нет"
await message.answer(text)
dp.run_polling(bot)
Команды
from maxo import Bot, Dispatcher
from maxo.routing.filters import Command, DeeplinkFilter
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")
dp.run_polling(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.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("Вы нажали на кнопку!")
dp.run_polling(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.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)
dp.run_polling(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 или Максе
Metadata
Release files for maxo 0.8.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| maxo-0.8.2.tar.gz | 229.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| maxo-0.8.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 669.8 kB
Release files / maxo-0.8.2.tar.gz
| Download URL | maxo-0.8.2.tar.gz |
|---|---|
| Size | 229.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e374a70b70d68d2bc2db9ee649d61c01242e2020b640ecfb458fa82cc138e10d
|
|
BLAKE2b-256 checksum How to use checksums |
dc4f6be1b35f0feeb15f0697ac6edeba1548fffa8835993d3da50a9984edfd3d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / maxo-0.8.2-py3-none-any.whl
| Download URL | maxo-0.8.2-py3-none-any.whl |
|---|---|
| Size | 440.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4b1efd2c33208ebff32649814cb5ccd7a2e5587492e90fc92ed964be90b89ad0
|
|
BLAKE2b-256 checksum How to use checksums |
aefdd14e4ae5cb125bd5afdf7dfb0290fc16eca47e0399552c170f0a8f9564ce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|