from types import NoneType, UnionType from aiogram import Router, F from aiogram.fsm.context import FSMContext from aiogram.types import Message, CallbackQuery, InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder from babel.support import LazyProxy from inspect import signature from sqlmodel.ext.asyncio.session import AsyncSession from typing import Any, get_args, get_origin, TYPE_CHECKING import ujson as json from ..context import ContextData, CallbackCommand, CommandContext from ...command_context_filter import CallbackCommandFilter from ....model.user import UserBase from ....model.settings import Settings from ....model.bot_entity import BotEntity from ....model.bot_enum import BotEnum from ....model.view_setting import ViewSetting from ....utils import get_local_text, deserialize from ....model.descriptors import (EntityFieldDescriptor, EntityDescriptor, EntityCaptionCallable, EntityItemCaptionCallable, EntityFieldCaptionCallable) if TYPE_CHECKING: from ....main import QBotApp router = Router() def get_send_message(message: Message | CallbackQuery): if isinstance(message, Message): return message.answer else: return message.message.edit_text # def get_local_text(text: str, lang: str): # try: # text_obj = json.loads(text) #@IgnoreException # return text_obj.get(lang, text_obj[list(text_obj.keys())[0]]) # except: # return text def get_value_repr(value: Any, field_descriptor: EntityFieldDescriptor, locale: str | None = None) -> str: type_ = field_descriptor.type_base if value is None: return "" if isinstance(value, bool): return "【✔︎】" if value else "【 】" elif field_descriptor.is_list: if issubclass(type_, BotEntity): if locale and type_.bot_entity_descriptor.fields_descriptors["name"].localizable: return "[" + ", ".join([get_local_text(text = item.name, locale = locale) for item in value]) + "]" else: return "[" + ", ".join([str(item.name) for item in value]) + "]" elif issubclass(type_, BotEnum): return "[" + ", ".join(item.localized(locale) for item in value) + "]" elif type_ == str: return "[" + ", ".join([f"\"{item}\"" for item in value]) + "]" else: return "[" + ", ".join([str(item) for item in value]) + "]" elif issubclass(type_, BotEntity): if type_.bot_entity_descriptor.fields_descriptors["name"].localizable: return get_local_text(text = value.name, locale = locale) return value.name elif issubclass(type_, BotEnum): return value.localized(locale) elif isinstance(value, str): if field_descriptor and field_descriptor.localizable: return get_local_text(text = value, locale = locale) return value elif isinstance(value, int): return str(value) elif isinstance(value, float): return str(value) else: return str(value) def get_callable_str(callable_str: str | LazyProxy | EntityCaptionCallable | EntityItemCaptionCallable | EntityFieldCaptionCallable, descriptor: EntityFieldDescriptor | EntityDescriptor, entity: Any = None, value: Any = None) -> str: if isinstance(callable_str, str): return callable_str elif isinstance(callable_str, LazyProxy): return callable_str.value elif callable(callable_str): args = signature(callable_str).parameters if len(args) == 1: return callable_str(descriptor) elif len(args) == 2: return callable_str(descriptor, entity) elif len(args) == 3: return callable_str(descriptor, entity, value) async def authorize_command(user: UserBase, callback_data: ContextData): if (callback_data.command == CallbackCommand.MENU_ENTRY_PARAMETERS or callback_data.context == CommandContext.SETTING_EDIT): allowed_roles = (await Settings.get(Settings.SECURITY_PARAMETERS_ROLES)) return any(role in user.roles for role in allowed_roles) return False def get_entity_descriptor(app: "QBotApp", callback_data: ContextData) -> EntityDescriptor | None: if callback_data.entity_name: return app.entity_metadata.entity_descriptors[callback_data.entity_name] return None def get_field_descriptor(app: "QBotApp", callback_data: ContextData) -> EntityFieldDescriptor | None: if callback_data.context == CommandContext.SETTING_EDIT: return Settings.list_params()[callback_data.field_name] elif callback_data.context in [CommandContext.ENTITY_CREATE, CommandContext.ENTITY_EDIT, CommandContext.ENTITY_FIELD_EDIT]: entity_descriptor = get_entity_descriptor(app, callback_data) if entity_descriptor: return entity_descriptor.fields_descriptors.get(callback_data.field_name) return None def add_pagination_controls(keyboard_builder: InlineKeyboardBuilder, callback_data: ContextData, total_pages: int, command: CallbackCommand, page: int): if total_pages > 1: navigation_buttons = [] ContextData(**callback_data.model_dump()).__setattr__ if total_pages > 10: navigation_buttons.append(InlineKeyboardButton(text = "⏮️", callback_data = ContextData( command = command, context = callback_data.context, entity_name = callback_data.entity_name, entity_id = callback_data.entity_id, field_name = callback_data.field_name, data = "1" if page != 1 else "skip", save_state = True).pack())) navigation_buttons.append(InlineKeyboardButton(text = "⏪️", callback_data = ContextData( command = command, context = callback_data.context, entity_name = callback_data.entity_name, entity_id = callback_data.entity_id, field_name = callback_data.field_name, data = str(max(page - 10, 1)) if page > 1 else "skip", save_state = True).pack())) navigation_buttons.append(InlineKeyboardButton(text = f"◀️", callback_data = ContextData( command = command, context = callback_data.context, entity_name = callback_data.entity_name, entity_id = callback_data.entity_id, field_name = callback_data.field_name, data = str(max(page - 1, 1)) if page > 1 else "skip", save_state = True).pack())) navigation_buttons.append(InlineKeyboardButton(text = f"▶️", callback_data = ContextData( command = command, context = callback_data.context, entity_name = callback_data.entity_name, entity_id = callback_data.entity_id, field_name = callback_data.field_name, data = str(min(page + 1, total_pages)) if page < total_pages else "skip", save_state = True).pack())) if total_pages > 10: navigation_buttons.append(InlineKeyboardButton(text = "⏩️", callback_data = ContextData( command = command, context = callback_data.context, entity_name = callback_data.entity_name, entity_id = callback_data.entity_id, field_name = callback_data.field_name, data = str(min(page + 10, total_pages)) if page < total_pages else "skip", save_state = True).pack())) navigation_buttons.append(InlineKeyboardButton(text = "⏭️", callback_data = ContextData( command = command, context = callback_data.context, entity_name = callback_data.entity_name, entity_id = callback_data.entity_id, field_name = callback_data.field_name, data = str(total_pages) if page != total_pages else "skip", save_state = True).pack())) keyboard_builder.row(*navigation_buttons) def add_filter_controls(keyboard_builder: InlineKeyboardBuilder, entity_descriptor: EntityDescriptor, filter: str = None, page: int = 1): field_name_descriptor = entity_descriptor.fields_descriptors["name"] if field_name_descriptor.caption: caption = get_callable_str(field_name_descriptor.caption, field_name_descriptor) else: caption = field_name_descriptor.name keyboard_builder.row( InlineKeyboardButton( text = f"🔎 {caption}{f": \"{filter}\"" if filter else ""}", callback_data = ContextData( command = CallbackCommand.VIEW_FILTER_EDIT, entity_name = entity_descriptor.name, data = str(page)).pack())) @router.callback_query(ContextData.filter(F.command == CallbackCommand.VIEW_FILTER_EDIT)) async def view_filter_edit(query: CallbackQuery, **kwargs): callback_data: ContextData = kwargs["callback_data"] state: FSMContext = kwargs["state"] state_data = await state.get_data() kwargs["state_data"] = state_data args = callback_data.data.split("&") page = int(args[0]) cmd = None if len(args) > 1: cmd = args[1] db_session: AsyncSession = kwargs["db_session"] app: "QBotApp" = kwargs["app"] user: UserBase = kwargs["user"] entity_descriptor = get_entity_descriptor(app = app, callback_data = callback_data) if cmd in ["cancel", "clear"]: if cmd == "clear": await ViewSetting.set_filter(session = db_session, user_id = user.id, entity_name = entity_descriptor.class_name, filter = None) context_data_bak = state_data.pop("context_data_bak") if context_data_bak: state_data["context_data"] = context_data_bak context_data = ContextData.unpack(context_data_bak) field_descriptor = get_field_descriptor(app, context_data) edit_prompt = state_data["edit_prompt"] current_value = await deserialize(session = db_session, type_ = field_descriptor.type_, value = state_data["value"]) page = int(state_data.pop("page")) kwargs.pop("callback_data") return await render_entity_picker(field_descriptor = field_descriptor, message = query, callback_data = context_data, current_value = current_value, edit_prompt = edit_prompt, page = page, **kwargs) else: return await route_callback(message = query, back = False, **kwargs) #await save_navigation_context(callback_data = callback_data, state = state) old_context_data = state_data.get("context_data") await state.update_data({"context_data": callback_data.pack(), "context_data_bak": old_context_data, "page": page}) send_message = get_send_message(query) await send_message(text = await Settings.get(Settings.APP_STRINGS_VIEW_FILTER_EDIT_PROMPT), reply_markup = InlineKeyboardBuilder().row( InlineKeyboardButton( text = await Settings.get(Settings.APP_STRINGS_CANCEL_BTN), callback_data = ContextData( command = CallbackCommand.VIEW_FILTER_EDIT, entity_name = entity_descriptor.name, data = f"{page}&cancel").pack()), InlineKeyboardButton( text = await Settings.get(Settings.APP_STRINGS_CLEAR_BTN), callback_data = ContextData( command = CallbackCommand.VIEW_FILTER_EDIT, entity_name = entity_descriptor.name, data = f"{page}&clear").pack())).as_markup()) @router.message(CallbackCommandFilter(command = CallbackCommand.VIEW_FILTER_EDIT)) async def view_filter_edit_input(message: Message, **kwargs): state: FSMContext = kwargs["state"] state_data = await state.get_data() kwargs["state_data"] = state_data callback_data = ContextData.unpack(state_data["context_data"]) db_session: AsyncSession = kwargs["db_session"] user: UserBase = kwargs["user"] app: "QBotApp" = kwargs["app"] entity_descriptor = get_entity_descriptor(app = app, callback_data = callback_data) filter = message.text await ViewSetting.set_filter(session = db_session, user_id = user.id, entity_name = entity_descriptor.class_name, filter = filter) #state_data.pop("context_data") #return await route_callback(message = message, back = False, **kwargs) context_data_bak = state_data.pop("context_data_bak") if context_data_bak: state_data["context_data"] = context_data_bak context_data = ContextData.unpack(context_data_bak) field_descriptor = get_field_descriptor(app, context_data) edit_prompt = state_data["edit_prompt"] current_value = await deserialize(session = db_session, type_ = field_descriptor.type_, value = state_data["value"]) page = int(state_data.pop("page")) return await render_entity_picker(field_descriptor = field_descriptor, message = message, callback_data = context_data, current_value = current_value, edit_prompt = edit_prompt, page = page, **kwargs) else: return await route_callback(message = message, back = False, **kwargs) from ..navigation import route_callback, save_navigation_context, clear_state, get_navigation_context from ..editors.entity import render_entity_picker