From dc006c70fdf5dac129b81669956e715420c30110 Mon Sep 17 00:00:00 2001 From: Alexander Kalinovsky Date: Tue, 8 Apr 2025 01:20:45 +0700 Subject: [PATCH] add form rendering call from app object --- .../bot/handlers/forms/entity_form.py | 16 ++-- src/quickbot/config/__init__.py | 1 + src/quickbot/main.py | 89 ++++++++++++++++--- 3 files changed, 90 insertions(+), 16 deletions(-) diff --git a/src/quickbot/bot/handlers/forms/entity_form.py b/src/quickbot/bot/handlers/forms/entity_form.py index 63a2af2..e063827 100644 --- a/src/quickbot/bot/handlers/forms/entity_form.py +++ b/src/quickbot/bot/handlers/forms/entity_form.py @@ -75,14 +75,14 @@ async def entity_item( state_data = kwargs["state_data"] await state.set_data(state_data) - if not entity_item: + if not entity_item and query: return await query.answer( text=(await Settings.get(Settings.APP_STRINGS_NOT_FOUND)) ) # is_owned = issubclass(entity_type, OwnedBotEntity) - if not check_entity_permission( + if query and not check_entity_permission( entity=entity_item, user=user, permission=EntityPermission.READ ): return await query.answer( @@ -352,6 +352,12 @@ async def entity_item( # state_data = kwargs["state_data"] # await state.set_data(state_data) - send_message = get_send_message(query) - - await send_message(text=item_text, reply_markup=keyboard_builder.as_markup()) + if query: + send_message = get_send_message(query) + await send_message(text=item_text, reply_markup=keyboard_builder.as_markup()) + else: + await app.bot.send_message( + chat_id=user.id, + text=item_text, + reply_markup=keyboard_builder.as_markup(), + ) diff --git a/src/quickbot/config/__init__.py b/src/quickbot/config/__init__.py index 72c3320..3cb939b 100644 --- a/src/quickbot/config/__init__.py +++ b/src/quickbot/config/__init__.py @@ -47,6 +47,7 @@ class Config(BaseSettings): else '' }" + TELEGRAM_BOT_USERNAME: str = "quickbot" TELEGRAM_BOT_SERVER: str = "https://api.telegram.org" TELEGRAM_BOT_SERVER_IS_LOCAL: bool = False TELEGRAM_BOT_TOKEN: str = "changethis" diff --git a/src/quickbot/main.py b/src/quickbot/main.py index 00979d9..5dc3ec2 100644 --- a/src/quickbot/main.py +++ b/src/quickbot/main.py @@ -1,5 +1,5 @@ from contextlib import asynccontextmanager -from typing import Callable, Any +from typing import Callable, Any, Generic, TypeVar from aiogram import Bot, Dispatcher from aiogram.client.session.aiohttp import AiohttpSession from aiogram.client.telegram import TelegramAPIServer @@ -9,20 +9,31 @@ from aiogram.utils.callback_answer import CallbackAnswerMiddleware from aiogram.utils.i18n import I18n from fastapi import FastAPI from fastapi.applications import Lifespan, AppType +from fastapi.datastructures import State from secrets import token_hex from logging import getLogger +from quickbot.utils.main import clear_state +from quickbot.utils.navigation import save_navigation_context + from .config import Config +from .bot.handlers.forms.entity_form import entity_item +from .db import get_db from .fsm.db_storage import DbStorage from .middleware.telegram import AuthMiddleware, I18nMiddleware +from .model.bot_entity import BotEntity from .model.user import UserBase from .model.entity_metadata import EntityMetadata from .model.descriptors import BotCommand +from .bot.handlers.context import CallbackCommand, ContextData from .router import Router logger = getLogger(__name__) +UserType = TypeVar("UserType", bound=UserBase, default=UserBase) +ConfigType = TypeVar("ConfigType", bound=Config, default=Config) + @asynccontextmanager async def default_lifespan(app: "QBotApp"): @@ -34,6 +45,8 @@ async def default_lifespan(app: "QBotApp"): await app.bot_init() + app.config.TELEGRAM_BOT_USERNAME = (await app.bot.get_me()).username + logger.info("qbot app started") if app.lifespan: @@ -53,15 +66,15 @@ async def default_lifespan(app: "QBotApp"): logger.info("qbot app stopped") -class QBotApp[UserType: UserBase](FastAPI): +class QBotApp(Generic[UserType, ConfigType], FastAPI): """ Main class for the QBot application """ def __init__( self, - user_class: UserType = None, - config: Config | None = None, + config: ConfigType = Config(), + user_class: type[UserType] = None, bot_start: Callable[ [ Callable[[Message, Any], tuple[UserType, bool]], @@ -73,12 +86,8 @@ class QBotApp[UserType: UserBase](FastAPI): lifespan: Lifespan[AppType] | None = None, lifespan_bot_init: bool = True, allowed_updates: list[str] | None = None, - *args, **kwargs, ): - if config is None: - config = Config() - if user_class is None: from .model.default_user import DefaultUser @@ -103,8 +112,8 @@ class QBotApp[UserType: UserBase](FastAPI): dp = Dispatcher(storage=DbStorage()) - i18n = I18n(path="locales", default_locale="en", domain="messages") - i18n_middleware = I18nMiddleware(user_class=user_class, i18n=i18n) + self.i18n = I18n(path="locales", default_locale="en", domain="messages") + i18n_middleware = I18nMiddleware(user_class=user_class, i18n=self.i18n) i18n_middleware.setup(dp) dp.callback_query.middleware(CallbackAnswerMiddleware()) @@ -128,7 +137,7 @@ class QBotApp[UserType: UserBase](FastAPI): self.lifespan_bot_init = lifespan_bot_init - super().__init__(lifespan=default_lifespan, *args, **kwargs) + super().__init__(lifespan=default_lifespan, **kwargs) from .api_route.telegram import router as telegram_router @@ -201,6 +210,64 @@ class QBotApp[UserType: UserBase](FastAPI): secret_token=self.bot_auth_token, ) + async def show_form( + self, + app_state: State, + user_id: int, + entity: type[BotEntity] | str, + entity_id: int, + form_name: str = None, + form_params: list[Any] = None, + **kwargs, + ): + f_params = [] + + if form_name: + f_params.append(form_name) + + if form_params: + f_params.extend(form_params) + + if isinstance(entity, type): + entity = entity.bot_entity_descriptor.name + + callback_data = ContextData( + command=CallbackCommand.ENTITY_ITEM, + entity_name=entity, + entity_id=entity_id, + form_params="&".join(f_params), + ) + + state = self.dp.fsm.get_context(bot=self.bot, chat_id=user_id, user_id=user_id) + state_data = await state.get_data() + clear_state(state_data=state_data) + stack = save_navigation_context( + callback_data=callback_data, state_data=state_data + ) + + db_session = kwargs.get("db_session") + if not db_session: + db_session = await get_db() + + user = await self.user_class.get( + session=db_session, + id=user_id, + ) + + with self.i18n.context(), self.i18n.use_locale(user.lang.value): + await entity_item( + query=None, + db_session=kwargs.get("db_session"), + callback_data=callback_data, + app=self, + user=user, + navigation_stack=stack, + state=state, + state_data=state_data, + i18n=self.i18n, + app_state=app_state, + ) + async def bot_close(self): await self.bot.delete_webhook() await self.bot.log_out()