Inline-клавиатуры
MAX передаёт inline-клавиатуру как attachment сообщения. aiomax2 сохраняет
эту wire-модель и добавляет короткий builder API.
Быстрый старт
from aiomax2.utils.keyboard import InlineKeyboardBuilder
builder = InlineKeyboardBuilder()
builder.button(text="Подтвердить", callback_data="confirm")
builder.button(text="Отменить", callback_data="cancel")
builder.adjust(2)
await message.answer(
"Выберите действие",
reply_markup=builder.as_markup(),
)
callback_data — только удобный alias aiomax2. На wire он становится полем
MAX payload; поле callback_data в API не отправляется.
reply_markup также является только Python convenience: библиотека добавляет
InlineKeyboardAttachmentRequest в attachments. Это работает в
Bot.send_message(), Message.answer(), Message.reply(),
Bot.edit_message(), Message.edit_text() и при изменении сообщения через
callback answer. Если attachments уже содержит inline_keyboard, передача
reply_markup вызывает ValidationError, чтобы не создать две клавиатуры
неявно.
При редактировании reply_markup=None означает «новая клавиатура не передана»
и не удаляет уже существующую inline-клавиатуру. Согласно семантике MAX
PUT /messages, отсутствие/null в attachments оставляет вложения без
изменений, а пустой список удаляет все вложения:
Warning
attachments=[] удалит не только клавиатуру, но также изображения, файлы
и любые другие attachments сообщения. Отдельный remove_reply_markup
пока не добавлен: безопасное сохранение остальных вложений потребовало бы
GET/merge workflow и отдельного design pass.
MAX также ограничивает сочетания вложений. NewMessageBody локально проверяет
один sticker или audio как единственное вложение, один file/contact с
необязательной inline-клавиатурой и не более 12 image/video/keyboard
attachments. Для contact клавиатура может содержать только одну кнопку.
Неизвестные будущие attachment types в raw dict остаются forward-compatible и
не блокируются этими правилами.
Типы кнопок MAX
builder = InlineKeyboardBuilder()
builder.callback(text="Подтвердить", payload="confirm")
builder.link(text="Документация", url="https://dev.max.ru")
builder.message(text="/help")
builder.clipboard(text="Скопировать", payload="ABC-123")
builder.request_contact(text="Поделиться контактом")
builder.request_geo_location(text="Отправить геопозицию", quick=False)
builder.open_app(
text="Открыть приложение",
web_app="your-web-app-id",
payload="start-screen",
)
Builder поддерживает все актуальные типы кнопок MAX: callback, link,
request_contact, request_geo_location, open_app, message и
clipboard. Telegram Reply Keyboard, ReplyKeyboardRemove и ForceReply в
MAX API отсутствуют и не эмулируются.
Компоновка
add(*buttons)добавляет готовые typed-кнопки и заполняет текущий ряд;row(*buttons, width=...)начинает новые ряды;adjust(*sizes, repeat=False)перекладывает все кнопки по размерам рядов;attach(other)присоединяет строки другого builder;as_markup()возвращаетInlineKeyboardAttachmentRequest;from_markup(markup)создаёт builder из low-level attachment.
Low-level API
Исходные модели остаются доступны без builder:
from aiomax2.types import (
CallbackButton,
InlineKeyboardAttachmentRequest,
Keyboard,
)
keyboard = InlineKeyboardAttachmentRequest(
payload=Keyboard(buttons=[[CallbackButton(text="Да", payload="yes")]])
)
await message.answer("Выберите", attachments=[keyboard])
Ограничения
Локальная Pydantic-валидация применяет ограничения актуальной документации и OpenAPI MAX:
- до 30 рядов и до 210 кнопок всего;
- до 7 обычных кнопок в ряду;
- до 3 кнопок в ряду, содержащем
link,open_app,request_geo_locationилиrequest_contact— это консервативная интерпретация формулировки MAX «до 3, если это кнопки типа ...», поскольку mixed-row semantics отдельно не уточнена; - текст кнопки — от 1 до 128 символов;
- callback payload — до 1024 символов;
- link URL — до 2048 символов;
- clipboard payload — до 1024 символов;
- open_app payload — до 512 символов; латинские буквы, цифры,
_и-(^[A-Za-z0-9_-]*$). Пустая строка допустима текущим pattern.
Callback answer
@router.callback_query(F.payload == "confirm")
async def confirm(callback: CallbackQuery) -> None:
await callback.answer(
notification="Подтверждено",
text="Готово",
)
@router.callback_query(F.payload == "cancel")
async def cancel(callback: CallbackQuery) -> None:
await callback.answer(notification="Отменено")
aiomax2 сериализует notification-only как
{"notification": "Отменено"} — без message: null и без пустого message
object, согласно текущему MAX API/OpenAPI-контракту. Фактическое отображение
уведомления зависит от поведения MAX server/client и требует live verification.
Пустой answer() сейчас не запрещён локально, потому что OpenAPI не делает ни
одно из двух полей обязательным; практического эффекта от такого запроса
ожидать не следует.
Обычный answer_callback() сохраняет совместимый результат bool. Для
диагностики ответа {"success": false, "message": "..."} используйте
answer_callback_result():