diff --git a/bot/handlers/user/start.py b/bot/handlers/user/start.py index 927925e..61c4386 100644 --- a/bot/handlers/user/start.py +++ b/bot/handlers/user/start.py @@ -14,6 +14,7 @@ from db.models import User from bot.keyboards.inline.user_keyboards import ( get_main_menu_inline_keyboard, + get_bot_interface_inline_keyboard, get_language_selection_keyboard, get_channel_subscription_keyboard, get_information_links_keyboard, @@ -29,6 +30,25 @@ from bot.utils.callback_answer import safe_answer_callback router = Router(name="user_start_router") +async def should_show_trial_button( + settings: Settings, + subscription_service: SubscriptionService, + session: AsyncSession, + user_id: int) -> bool: + if not settings.TRIAL_ENABLED: + return False + + if hasattr(subscription_service, 'has_had_any_subscription') and callable( + getattr(subscription_service, 'has_had_any_subscription')): + return not await subscription_service.has_had_any_subscription( + session, user_id) + + logging.error( + "Method has_had_any_subscription is missing in SubscriptionService!" + ) + return False + + async def send_main_menu(target_event: Union[types.Message, types.CallbackQuery], settings: Settings, @@ -61,18 +81,8 @@ async def send_main_menu(target_event: Union[types.Message, _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) - show_trial_button_in_menu = False - if settings.TRIAL_ENABLED: - if hasattr( - subscription_service, 'has_had_any_subscription') and callable( - getattr(subscription_service, 'has_had_any_subscription')): - if not await subscription_service.has_had_any_subscription( - session, user_id): - show_trial_button_in_menu = True - else: - logging.error( - "Method has_had_any_subscription is missing in SubscriptionService for send_main_menu!" - ) + show_trial_button_in_menu = await should_show_trial_button( + settings, subscription_service, session, user_id) text = _(key="main_menu_greeting", user_name=user_full_name) reply_markup = get_main_menu_inline_keyboard(current_lang, i18n, settings, @@ -123,6 +133,68 @@ async def send_main_menu(target_event: Union[types.Message, ) +async def send_bot_interface_menu( + target_event: Union[types.Message, types.CallbackQuery], + settings: Settings, + i18n_data: dict, + subscription_service: SubscriptionService, + session: AsyncSession, + is_edit: bool = False): + current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE) + i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") + + if not i18n: + logging.error("i18n_instance missing in send_bot_interface_menu.") + return + + user_id = target_event.from_user.id + show_trial_button_in_menu = await should_show_trial_button( + settings, subscription_service, session, user_id) + + text = i18n.gettext(current_lang, "bot_interface_menu_title") + reply_markup = get_bot_interface_inline_keyboard( + current_lang, i18n, settings, show_trial_button_in_menu) + + target_message_obj: Optional[types.Message] = None + if isinstance(target_event, types.Message): + target_message_obj = target_event + elif isinstance(target_event, types.CallbackQuery) and target_event.message: + target_message_obj = target_event.message + + if not target_message_obj: + logging.error( + "send_bot_interface_menu: target_message_obj is None for user %s.", + user_id, + ) + return + + try: + if is_edit: + await target_message_obj.edit_text(text, reply_markup=reply_markup) + else: + await target_message_obj.answer(text, reply_markup=reply_markup) + + if isinstance(target_event, types.CallbackQuery): + await safe_answer_callback(target_event) + except Exception as e_send_edit: + logging.warning( + "Failed to send/edit bot interface menu (user: %s, is_edit: %s): %s - %s.", + user_id, + is_edit, + type(e_send_edit).__name__, + e_send_edit, + ) + if is_edit: + try: + await target_message_obj.answer(text, reply_markup=reply_markup) + except Exception as e_send_new: + logging.error( + "Also failed to send new bot interface menu for user %s: %s", + user_id, + e_send_new, + ) + + async def ensure_required_channel_subscription( event: Union[types.Message, types.CallbackQuery], settings: Settings, @@ -640,6 +712,31 @@ async def start_command_handler(message: types.Message, is_edit=False) +@router.message(Command("tg")) +async def tg_interface_command_handler(message: types.Message, + state: FSMContext, + settings: Settings, + i18n_data: dict, + subscription_service: SubscriptionService, + session: AsyncSession): + await state.clear() + + current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE) + i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") + db_user = await user_dal.get_user_by_id(session, message.from_user.id) + if not await ensure_required_channel_subscription(message, settings, i18n, + current_lang, session, + db_user): + return + + await send_bot_interface_menu(message, + settings, + i18n_data, + subscription_service, + session, + is_edit=False) + + @router.callback_query(F.data == "channel_subscription:verify") async def verify_channel_subscription_callback( callback: types.CallbackQuery, diff --git a/bot/keyboards/inline/user_keyboards.py b/bot/keyboards/inline/user_keyboards.py index 4ad8f55..eba78aa 100644 --- a/bot/keyboards/inline/user_keyboards.py +++ b/bot/keyboards/inline/user_keyboards.py @@ -13,6 +13,37 @@ def get_main_menu_inline_keyboard( _ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs) builder = InlineKeyboardBuilder() + if settings.SUBSCRIPTION_MINI_APP_URL: + builder.row( + InlineKeyboardButton( + text=_(key="menu_personal_account_button"), + web_app=WebAppInfo(url=settings.SUBSCRIPTION_MINI_APP_URL), + ) + ) + else: + builder.row( + InlineKeyboardButton( + text=_(key="menu_personal_account_button"), + callback_data="main_action:my_subscription", + ) + ) + + if settings.SUPPORT_LINK: + builder.row( + InlineKeyboardButton(text=_(key="menu_support_button"), + url=settings.SUPPORT_LINK)) + + return builder.as_markup() + + +def get_bot_interface_inline_keyboard( + lang: str, + i18n_instance, + settings: Settings, + show_trial_button: bool = False) -> InlineKeyboardMarkup: + _ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs) + builder = InlineKeyboardBuilder() + if show_trial_button and settings.TRIAL_ENABLED: builder.row( InlineKeyboardButton(text=_(key="menu_activate_trial_button"), @@ -21,20 +52,12 @@ def get_main_menu_inline_keyboard( builder.row( InlineKeyboardButton(text=_(key="menu_subscribe_inline"), callback_data="main_action:subscribe")) - if settings.SUBSCRIPTION_MINI_APP_URL: - builder.row( - InlineKeyboardButton( - text=_(key="menu_my_subscription_inline"), - web_app=WebAppInfo(url=settings.SUBSCRIPTION_MINI_APP_URL), - ) - ) - else: - builder.row( - InlineKeyboardButton( - text=_(key="menu_my_subscription_inline"), - callback_data="main_action:my_subscription", - ) + builder.row( + InlineKeyboardButton( + text=_(key="menu_my_subscription_inline"), + callback_data="main_action:my_subscription", ) + ) referral_button = InlineKeyboardButton( text=_(key="menu_referral_inline"), diff --git a/bot/main_bot.py b/bot/main_bot.py index 192c1ad..c53a7d9 100644 --- a/bot/main_bot.py +++ b/bot/main_bot.py @@ -115,7 +115,7 @@ async def on_startup_configured(dispatcher: Dispatcher): try: menu_text = i18n_instance.gettext( settings.DEFAULT_LANGUAGE, - "menu_my_subscription_inline", + "menu_personal_account_button", ) await bot.set_chat_menu_button( menu_button=MenuButtonWebApp( @@ -132,14 +132,20 @@ async def on_startup_configured(dispatcher: Dispatcher): f"STARTUP: Failed to register mini app domain: {e}", exc_info=True ) - if settings.START_COMMAND_DESCRIPTION: - try: - await bot.set_my_commands([ - BotCommand(command="start", description=settings.START_COMMAND_DESCRIPTION) - ]) - logging.info("STARTUP: /start command description set.") - except Exception as e: - logging.error(f"STARTUP: Failed to set bot commands: {e}", exc_info=True) + try: + bot_commands = [ + BotCommand(command="tg", description="Интерфейс в боте"), + ] + if settings.START_COMMAND_DESCRIPTION: + bot_commands.insert( + 0, + BotCommand(command="start", + description=settings.START_COMMAND_DESCRIPTION), + ) + await bot.set_my_commands(bot_commands) + logging.info("STARTUP: bot command descriptions set.") + except Exception as e: + logging.error(f"STARTUP: Failed to set bot commands: {e}", exc_info=True) # Initialize message queue manager try: diff --git a/locales/en.json b/locales/en.json index 7d0758b..d53c201 100644 --- a/locales/en.json +++ b/locales/en.json @@ -7,9 +7,10 @@ "channel_subscription_verified_success": "✅ Subscription confirmed! You're good to go.", "webapp_auth_success": "✅ Login confirmed. Return to the Web App.", "webapp_auth_expired": "The login link has expired. Return to the Web App and try again.", - "main_menu_greeting": "Hi, {user_name}! 👋\nWhat would you like to do?\n\nTo set up the connection on your device, go to \"My subscription\" and click \"Connect\". If needed, add access days in the \"Buy\" section.", + "main_menu_greeting": "Hi, {user_name}! 👋\nOpen \"Personal Account\" to set up the connection, view your subscription, or renew access.", "menu_activate_trial_button": "🆓 Free Trial", "menu_subscribe_inline": "🚀 Purchase", + "menu_personal_account_button": "🔑 Personal Account", "menu_my_subscription_inline": "🔐 My Subscription", "no_subscription_options_available": "Subscription issuance is not configured by the bot administrator", "menu_referral_inline": "🎁 Invite friend", @@ -20,6 +21,7 @@ "menu_support_button": "💬 Support", "menu_terms_button": "📄 Terms of Service", "menu_info_button": "ℹ️ Information", + "bot_interface_menu_title": "Bot interface", "info_links_message": "Choose a document:", "privacy_policy_button": "🔒 Privacy Policy", "user_agreement_button": "📄 User Agreement", diff --git a/locales/ru.json b/locales/ru.json index f381e6b..59e881c 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -7,9 +7,10 @@ "channel_subscription_verified_success": "✅ Подписка подтверждена! Можно продолжать.", "webapp_auth_success": "✅ Вход подтвержден. Вернитесь в Web App.", "webapp_auth_expired": "Ссылка авторизации устарела. Вернитесь в Web App и попробуйте еще раз.", - "main_menu_greeting": "Привет, {user_name}! 👋\nЧто бы вы хотели сделать?\n\nДля настройки подключения на устройстве перейдите в раздел \"Моя подписка\" и нажмите кнопку \"Подключиться\". При необходимости добавьте дни доступа в разделе \"Купить\".", + "main_menu_greeting": "Привет, {user_name}! 👋\nПерейдите в \"Личный кабинет\", чтобы настроить подключение, посмотреть подписку или продлить доступ.", "menu_activate_trial_button": "🆓 Пробный период", "menu_subscribe_inline": "🚀 Купить", + "menu_personal_account_button": "🔑 Личный кабинет", "menu_my_subscription_inline": "🔐 Моя подписка", "no_subscription_options_available": "Выдача подписки не настроена администратором бота", "menu_referral_inline": "🎁 Пригласить друга", @@ -20,6 +21,7 @@ "menu_support_button": "💬 Поддержка", "menu_terms_button": "📄 Условия сервиса", "menu_info_button": "ℹ️ Информация", + "bot_interface_menu_title": "Интерфейс в боте", "info_links_message": "Выберите документ:", "privacy_policy_button": "🔒 Политика конфиденциальности", "user_agreement_button": "📄 Пользовательское соглашение",