- 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>
58 lines
2.3 KiB
Python
58 lines
2.3 KiB
Python
"""/start, /help, /menu, main menu buttons and in-section navigation."""
|
|
|
|
from aiogram import F, Router
|
|
from aiogram.filters import Command, CommandStart
|
|
from aiogram.fsm.context import FSMContext
|
|
from aiogram.types import CallbackQuery, Message
|
|
|
|
from app.bot.menu import Nav, NavAction, Section, main_menu, section_by_button, section_nav
|
|
from app.texts import Texts
|
|
|
|
|
|
def build_router(texts: Texts, admin_ids: frozenset[int]) -> Router:
|
|
router = Router(name="common")
|
|
sections = section_by_button(texts)
|
|
section_text = {
|
|
Section.COMMIT: texts.section.commit,
|
|
Section.STATUS: texts.section.status,
|
|
Section.LOGS: texts.section.logs,
|
|
Section.ALERTS: texts.section.alerts,
|
|
Section.MEMBERS: texts.section.members,
|
|
}
|
|
|
|
def is_admin(message: Message) -> bool:
|
|
return message.from_user is not None and message.from_user.id in admin_ids
|
|
|
|
@router.message(CommandStart())
|
|
async def start(message: Message, state: FSMContext) -> None:
|
|
await state.clear()
|
|
await message.answer(texts.start.welcome, reply_markup=main_menu(texts, is_admin=is_admin(message)))
|
|
|
|
@router.message(Command("menu"))
|
|
async def menu(message: Message, state: FSMContext) -> None:
|
|
await state.clear()
|
|
await message.answer(texts.menu.placeholder, reply_markup=main_menu(texts, is_admin=is_admin(message)))
|
|
|
|
@router.message(Command("help"))
|
|
async def help_(message: Message) -> None:
|
|
await message.answer(texts.help.text)
|
|
|
|
@router.message(F.text.in_(sections))
|
|
async def open_section(message: Message, state: FSMContext) -> None:
|
|
section = sections[message.text or ""]
|
|
if section is Section.MEMBERS and not is_admin(message):
|
|
return
|
|
await state.clear()
|
|
await message.answer(section_text[section], reply_markup=section_nav(texts))
|
|
|
|
@router.callback_query(Nav.filter())
|
|
async def navigate(query: CallbackQuery, callback_data: Nav, state: FSMContext) -> None:
|
|
# At a section's root "Back" and "Cancel" both close it; nested screens will override "Back" per section.
|
|
await state.clear()
|
|
text = texts.section.cancelled if callback_data.action is NavAction.CANCEL else texts.menu.placeholder
|
|
if isinstance(query.message, Message):
|
|
await query.message.edit_text(text)
|
|
await query.answer()
|
|
|
|
return router
|