feat(bot): точка входа, конфиг, главное меню, доступ для ADMIN_IDS, healthcheck
- config: pydantic-settings по подсистемам, ADMIN_IDS через запятую - texts.yaml + валидация при старте (лишний/пропущенный ключ — ошибка сразу) - меню: reply-клавиатура разделов (Участники — только админам), инлайн «Назад»/«Отмена» с редактированием сообщения на месте, /start, /menu, /help - AccessMiddleware (outer) на сообщениях и кнопках: до #25 пускает только ADMIN_IDS, постороннему — одна фраза в сутки, устаревшие кнопки молча гасятся - heartbeat-файл + HEALTHCHECK в Dockerfile; JSON-логи - FSM пока в памяти (фабрика), хранилище в БД — в #5 - тесты на фейковой сессии aiogram через настоящий Dispatcher Refs #4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user