from dataclasses import dataclass, field from typing import Any, Callable, TYPE_CHECKING from aiogram import Router, F from aiogram.types import Message, CallbackQuery, InlineKeyboardButton from aiogram.fsm.context import FSMContext from aiogram.utils.i18n import I18n from aiogram.utils.keyboard import InlineKeyboardBuilder from sqlmodel.ext.asyncio.session import AsyncSession from ..context import ContextData, CallbackCommand from ....model.user import UserBase from ....model.settings import Settings if TYPE_CHECKING: from ....main import QBotApp router = Router() @dataclass(kw_only = True) class CommandCallbackContext[UT: UserBase]: keyboard_builder: InlineKeyboardBuilder = field(default_factory = InlineKeyboardBuilder) message_text: str | None = None register_navigation: bool = True message: Message | CallbackQuery callback_data: ContextData db_session: AsyncSession user: UT app: "QBotApp" state_data: dict[str, Any] state: FSMContext i18n: I18n kwargs: dict[str, Any] = field(default_factory = dict) @dataclass(kw_only = True) class Command: name: str handler: Callable[[CommandCallbackContext], None] caption: str | dict[str, str] | None = None register_navigation: bool = True clear_navigation: bool = False clear_state: bool = True @router.message(F.text.startswith("/")) async def command_text(message: Message, **kwargs): str_command = message.text.lstrip("/") callback_data = ContextData(command = CallbackCommand.USER_COMMAND, user_command = str_command) await command_handler(message = message, callback_data = callback_data, **kwargs) @router.callback_query(ContextData.filter(F.command == CallbackCommand.USER_COMMAND)) async def command_callback(message: CallbackQuery, **kwargs): await command_handler(message = message, **kwargs) async def command_handler(message: Message | CallbackQuery, **kwargs): callback_data: ContextData = kwargs.pop("callback_data") str_command = callback_data.user_command app: "QBotApp" = kwargs.pop("app") command = app.bot_commands.get(str_command) if not command: return state: FSMContext = kwargs.pop("state") state_data = await state.get_data() if command.register_navigation: clear_state(state_data = state_data) if command.clear_navigation: state_data.pop("navigation_stack", None) state_data.pop("navigation_context", None) if command.register_navigation: stack = save_navigation_context(callback_data = callback_data, state_data = state_data) callback_context = CommandCallbackContext[app.user_class]( message = message, callback_data = callback_data, db_session = kwargs.pop("db_session"), user = kwargs.pop("user"), app = app, state_data = state_data, state = state, i18n = kwargs.pop("i18n"), kwargs = kwargs) await command.handler(callback_context) await state.set_data(state_data) if command.register_navigation: stack, navigation_context = get_navigation_context(state_data = state_data) back_callback_data = pop_navigation_context(stack = stack) if back_callback_data: callback_context.keyboard_builder.row( InlineKeyboardButton( text = (await Settings.get(Settings.APP_STRINGS_BACK_BTN)), callback_data = back_callback_data.pack())) send_message = get_send_message(message) if isinstance(message, CallbackCommand): message = message.message if callback_context.message_text: await send_message(text = callback_context.message_text, reply_markup = callback_context.keyboard_builder.as_markup()) else: await message.edit_reply_markup(reply_markup = callback_context.keyboard_builder.as_markup()) from ..common import get_send_message from ..navigation import save_navigation_context, get_navigation_context, clear_state, pop_navigation_context