Асинхронный фреймворк для MAX Bot API
Project description
maxio
Асинхронный Python-фреймворк для MAX Bot API
с внедрением зависимостей по аннотациям типов.
Объявляйте в сигнатуре хэндлера то, что нужно, — фреймворк подставит из контекста апдейта сам:
@app.message(Command("help"))
async def help_cmd(message: Message) -> None:
await message.answer("Это бот на фреймворке maxio!")
Никаких context["bot"], никакого middleware_data. Просто типы.
Установка
pip install maxio
Python 3.10+, зависимости: httpx + pydantic v2.
Быстрый старт
Получите токен у @MasterBot:
import os
from maxio import MaxBot, Message, Callback, Command, InlineKeyboard, Update, Bot
from maxio.keyboards import Button
app = MaxBot(token=os.environ["MAX_TOKEN"])
# Синяя кнопка Start шлёт событие bot_started (без message), а не /start.
@app.bot_started()
async def on_start(update: Update, bot: Bot):
kb = InlineKeyboard().row(Button.callback("Нажми меня", "ping"))
await bot.send_message("Привет! Я готов к работе.", chat_id=update.chat_id, keyboard=kb)
@app.message(Command("help"))
async def help_cmd(message: Message):
await message.answer("Это бот на фреймворке maxio!")
@app.callback()
async def on_ping(callback: Callback):
await callback.answer(notification="Понг!") # всплывашка на кнопке
if callback.message:
await callback.message.answer("Ты нажал кнопку!") # новое сообщение в чат
@app.message()
async def echo(message: Message):
await message.reply(message.text or "")
if __name__ == "__main__":
app.run()
MAX_TOKEN=<ваш_токен> python bot.py
Декораторы событий
Имена декораторов совпадают с типами событий MAX Bot API.
| Декоратор | Тип события | Когда срабатывает |
|---|---|---|
@app.message() |
message_created |
пришло новое сообщение от пользователя |
@app.message_edited() |
message_edited |
пользователь отредактировал своё сообщение |
@app.callback() |
message_callback |
нажата inline-кнопка (callback) под сообщением |
@app.bot_started() |
bot_started |
пользователь запустил бота — синяя кнопка Start |
@app.event(*types) |
любые | подписка на произвольные типы апдейтов (или все — без аргументов) |
Несколько хэндлеров — первый подходящий выигрывает (first-match-wins).
Чем message отличается от bot_started
Это частая путаница. Синяя кнопка Start в MAX не отправляет текст /start — она шлёт
отдельное событие bot_started, в котором нет объекта Message (есть chat_id, user
и опциональный payload из диплинка). Поэтому:
- хэндлер
@app.message(Command("start"))ловит только ручной ввод/start; - нажатие кнопки Start ловится через
@app.bot_started().
Если нужно, чтобы и кнопка, и команда здоровались одинаково — заведите оба обработчика.
Внутри bot_started нет Message, поэтому отвечать надо через Bot, указывая chat_id:
@app.bot_started()
async def on_start(update: Update, bot: Bot):
await bot.send_message("Привет! Нажми кнопку ниже.", chat_id=update.chat_id)
@app.message(Command("start")) # ручной ввод /start
async def start_cmd(message: Message):
await message.answer("Привет! Нажми кнопку ниже.")
callback — нажатия inline-кнопок
Срабатывает, когда пользователь жмёт кнопку, созданную через Button.callback(text, payload).
В обработчике доступен Callback; отвечать на нажатие нужно callback.answer(...), иначе у
пользователя останется «крутилка» на кнопке. Исходное сообщение с кнопкой лежит в
callback.message — у него есть привычные answer() / reply() для отправки в тот же чат.
@app.callback(CallbackPayload("buy")) # фильтр по payload кнопки
async def buy(callback: Callback):
await callback.answer(notification="Покупка оформлена") # всплывашка на кнопке
if callback.message:
await callback.message.answer("Спасибо за заказ!") # новое сообщение в чат
Фильтры
from maxio import Command, CallbackPayload
@app.message(Command("help"))
async def help_cmd(message: Message): ...
@app.callback(CallbackPayload("buy"))
async def buy(callback: Callback): ...
# Произвольный callable — тоже фильтр
@app.message(lambda u: u.message and len(u.message.text or "") > 100)
async def long_message(message: Message): ...
Inline-клавиатуры
from maxio import InlineKeyboard
from maxio.keyboards import Button
kb = (
InlineKeyboard()
.row(Button.callback("Да", "yes"), Button.callback("Нет", "no"))
.row(Button.link("Сайт", "https://example.com"))
)
await message.answer("Выберите:", keyboard=kb)
Методы API
@app.message()
async def handler(message: Message, bot: Bot):
me = await bot.get_me()
await bot.send_message(chat_id=message.recipient.chat_id, text=f"Я — {me.name}")
await bot.edit_message(message_id=message.message_id, text="Обновлено")
await bot.delete_message(message_id=message.message_id)
chats = await bot.get_chats()
Роутеры
Разбивайте хэндлеры по файлам через Router:
from maxio import Router
admin = Router()
users = Router()
app.include_routers(admin, users)
@admin.message(Command("ban"))
async def ban(message: Message): ...
Порядок проверки: app → роутеры в порядке include_routers. Middleware, зарегистрированная
на роутере, срабатывает только если хэндлер принадлежит этому роутеру.
Middleware
Middleware — callable-объект или функция, прикрепляется к MaxBot или Router:
from maxio.middleware import CallNextOuter
class TimingMiddleware:
async def __call__(self, update: Update, call_next: CallNextOuter) -> bool:
t = time.monotonic()
result = await call_next()
print(f"{update.update_type} → {time.monotonic() - t:.3f}s")
return result
app.outer_middleware(TimingMiddleware()) # на все апдейты
app.outer_middleware(log_fn, UpdateType.MESSAGE_CREATED) # только на нужный тип
Порядок: app.outer → router.outer → app.inner → router.inner → handler.
inner_middleware вызывается после выбора хэндлера и получает (handler_fn, kwargs, call_next) —
можно читать и изменять уже резолвленные аргументы.
FSM — диалоги с состоянием
from maxio import StatesGroup, State, StateFilter, FSMContext
class Form(StatesGroup):
waiting_name = State()
waiting_age = State()
@app.message(Command("register"))
async def start_form(message: Message, fsm: FSMContext) -> None:
await fsm.set_state(Form.waiting_name)
await message.answer("Как тебя зовут?")
@app.message(StateFilter(Form.waiting_name))
async def got_name(message: Message, fsm: FSMContext) -> None:
await fsm.update_data(name=message.text)
await fsm.set_state(Form.waiting_age)
await message.answer("Сколько тебе лет?")
@app.message(StateFilter(Form.waiting_age))
async def got_age(message: Message, fsm: FSMContext) -> None:
data = await fsm.get_data()
await fsm.clear()
await message.answer(f"{data['name']}, {message.text} лет — записал!")
FSMContext инжектируется в хэндлер по типу аннотации — никакого state.get_state() вручную.
По умолчанию состояния хранятся в памяти (MemoryStorage).
Своё хранилище: MaxBot(token, storage=MyRedisStorage()).
Медиа — загрузка и получение файлов
from maxio import HasMedia
from maxio import media
from maxio.enums import UploadType
from pathlib import Path
# Загрузить и отправить картинку
@app.message(Command("photo"))
async def send_photo(message: Message, bot: Bot) -> None:
token = await bot.upload(Path("photo.jpg"), UploadType.IMAGE)
await message.answer("Держи!", attachments=[media.image(token)])
# Принять картинку от пользователя
@app.message(HasMedia("image"))
async def got_photo(message: Message) -> None:
for photo in message.photos: # список PhotoAttachmentPayload
await message.answer(f"URL: {photo.url}")
# Принять любой файл
@app.message(HasMedia("file"))
async def got_file(message: Message) -> None:
for f in message.files: # список FileAttachmentPayload
await message.answer(f"Файл: {f.filename} ({f.size} байт)")
Bot.upload принимает bytes, IO[bytes] или Path; поддерживаемые типы: IMAGE, VIDEO, AUDIO, FILE.
DI — внедрение зависимостей
| Тип | Когда доступен |
|---|---|
Message |
message_created, message_edited, callback |
Callback |
message_callback |
User |
отправитель/инициатор события |
Chat |
message_chat_created |
FSMContext |
всегда (текущий контекст FSM) |
Update |
всегда (сырой апдейт) |
Bot |
всегда (HTTP-клиент, прямой доступ к API) |
Несовместимый тип → понятная ошибка MaxError с описанием проблемы.
Возможности
- Long polling с автоматическим переподключением
- Роутеры (
Router) иinclude_routersдля разбивки хэндлеров - Middleware: outer / inner, на
MaxBotи наRouter, по типам апдейтов - FSM:
StatesGroup,State,FSMContext,StateFilter,MemoryStorage - Медиа:
Bot.upload(),media.image/video/audio/file(),HasMediaфильтр - HTTP-клиент:
get_me,send_message,edit_message,delete_message,get_messages,get_chats,answer_callback,get_updates - Декораторы:
message,message_edited,callback,bot_started,event - DI по аннотациям типов — без
Depends() - Фильтры:
Command,CallbackPayload,HasMedia, любойCallable - Inline-клавиатуры: callback / link / request_geo_location
- Сахар:
message.answer(),message.reply(),callback.answer(),callback.message.answer() - Pydantic v2,
py.typed,mypy --strict✅
Спонсоры
|
sudoteach.com
Генеральный спонсор. Онлайн-школа программирования — там есть курс «Боты на maxio»: от установки до продакшна. |
bothost.ru
Официальный партнёр. Хостинг для ботов MAX и Telegram — деплой в один клик, мониторинг, автозапуск. |
Лицензия
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file maxio-0.3.0.tar.gz.
File metadata
- Download URL: maxio-0.3.0.tar.gz
- Upload date:
- Size: 32.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e8ccb2adbbdb0ff65a26c554b9bf3de27852b214887a897a1003d07d97d43e57
|
|
| MD5 |
ae73e42c64220cc08362b034b09d1001
|
|
| BLAKE2b-256 |
270359f3c7dfb0a89c1ff3cdb27a84097cf42d1f2f7a86c1923d0887e079eb7b
|
File details
Details for the file maxio-0.3.0-py3-none-any.whl.
File metadata
- Download URL: maxio-0.3.0-py3-none-any.whl
- Upload date:
- Size: 29.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2608cf91ec59035f71731dc436ceb4a89ff84fcea3bf350630da12f3fadae9e9
|
|
| MD5 |
df1b131ede55afb4c5485de463b4ecc3
|
|
| BLAKE2b-256 |
324e0b5d407dfb996585bc8de225e3808ff9ac91c65216e745aee75577d851a9
|