"""Main menu (reply keyboard) and in-section navigation (inline keyboard), SPEC §4.0.""" from enum import StrEnum from aiogram.filters.callback_data import CallbackData from aiogram.types import InlineKeyboardMarkup, ReplyKeyboardMarkup from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder from app.texts import Texts class Section(StrEnum): COMMIT = "commit" STATUS = "status" LOGS = "logs" ALERTS = "alerts" MEMBERS = "members" class NavAction(StrEnum): BACK = "back" CANCEL = "cancel" class Nav(CallbackData, prefix="nav"): # Telegram limits callback_data to 64 bytes: only short ids here, never paths (ARCHITECTURE §2.1). action: NavAction def section_by_button(texts: Texts) -> dict[str, Section]: m = texts.menu return { m.commit: Section.COMMIT, m.status: Section.STATUS, m.logs: Section.LOGS, m.alerts: Section.ALERTS, m.members: Section.MEMBERS, } def main_menu(texts: Texts, *, is_admin: bool) -> ReplyKeyboardMarkup: m = texts.menu kb = ReplyKeyboardBuilder() kb.button(text=m.commit) kb.button(text=m.status) kb.button(text=m.logs) kb.button(text=m.alerts) if is_admin: kb.button(text=m.members) kb.adjust(2, 2, 1) return kb.as_markup(resize_keyboard=True, is_persistent=True, input_field_placeholder=m.placeholder) def section_nav(texts: Texts) -> InlineKeyboardMarkup: kb = InlineKeyboardBuilder() kb.button(text=texts.menu.back, callback_data=Nav(action=NavAction.BACK)) kb.button(text=texts.menu.cancel, callback_data=Nav(action=NavAction.CANCEL)) return kb.as_markup()