From 0d714b4dff6d837626ca82d6f135fe02cd1db37e Mon Sep 17 00:00:00 2001 From: Egor Date: Thu, 25 Sep 2025 11:36:26 +0300 Subject: [PATCH] Handle Happ cryptolink without unsupported Telegram URL --- app/handlers/subscription.py | 52 +++++++++++++------ app/keyboards/inline.py | 91 ++++++++++++++++++--------------- app/utils/subscription_utils.py | 21 +++++++- locales/en.json | 1 + locales/ru.json | 1 + 5 files changed, 108 insertions(+), 58 deletions(-) diff --git a/app/handlers/subscription.py b/app/handlers/subscription.py index e6dab937..6f27d2db 100644 --- a/app/handlers/subscription.py +++ b/app/handlers/subscription.py @@ -1,17 +1,19 @@ -import logging -from datetime import datetime, timedelta -from aiogram import Dispatcher, types, F -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton -from aiogram.fsm.context import FSMContext -from sqlalchemy.ext.asyncio import AsyncSession +import html import json +import logging import os -from typing import Dict, List, Any, Tuple, Optional +from datetime import datetime, timedelta +from typing import Any, Dict, List, Optional, Tuple -from app.config import settings, PERIOD_PRICES, get_traffic_prices +from aiogram import F, Dispatcher, types +from aiogram.fsm.context import FSMContext +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from sqlalchemy.ext.asyncio import AsyncSession + +from app.config import PERIOD_PRICES, get_traffic_prices, settings from app.states import SubscriptionStates from app.database.crud.subscription import ( - get_subscription_by_user_id, create_trial_subscription, + get_subscription_by_user_id, create_trial_subscription, create_paid_subscription, extend_subscription, add_subscription_traffic, add_subscription_devices, add_subscription_squad, update_subscription_autopay, @@ -64,7 +66,10 @@ from app.utils.pricing_utils import ( format_period_description, ) from app.utils.pagination import paginate_list -from app.utils.subscription_utils import get_display_subscription_link +from app.utils.subscription_utils import ( + get_display_subscription_link, + is_supported_telegram_url, +) logger = logging.getLogger(__name__) @@ -4611,21 +4616,34 @@ async def handle_open_subscription_link( return if settings.is_happ_cryptolink_mode(): + escaped_subscription_link = html.escape(subscription_link) + can_open_directly = is_supported_telegram_url(subscription_link) + + link_line = texts.t( + "SUBSCRIPTION_HAPP_OPEN_LINK", + "🔓 Открыть ссылку в Happ", + ) + + if can_open_directly: + formatted_link_line = link_line.format(subscription_link=subscription_link) + else: + formatted_link_line = texts.t( + "SUBSCRIPTION_HAPP_OPEN_COPY", + "🔓 Скопируйте ссылку и откройте в Happ: {subscription_link}", + ).format(subscription_link=escaped_subscription_link) + happ_message = ( texts.t( "SUBSCRIPTION_HAPP_OPEN_TITLE", "🔗 Подключение через Happ", ) + "\n\n" - + texts.t( - "SUBSCRIPTION_HAPP_OPEN_LINK", - "🔓 Открыть ссылку в Happ", - ).format(subscription_link=subscription_link) + + formatted_link_line + "\n\n" + texts.t( "SUBSCRIPTION_HAPP_OPEN_HINT", "💡 Если ссылка не открывается автоматически, скопируйте её вручную: {subscription_link}", - ).format(subscription_link=subscription_link) + ).format(subscription_link=escaped_subscription_link) ) keyboard = get_happ_cryptolink_keyboard(subscription_link, db_user.language) @@ -4639,10 +4657,12 @@ async def handle_open_subscription_link( await callback.answer() return + escaped_subscription_link = html.escape(subscription_link) + link_text = ( texts.t("SUBSCRIPTION_DEVICE_LINK_TITLE", "🔗 Ссылка подписки:") + "\n\n" - + f"{subscription_link}\n\n" + + f"{escaped_subscription_link}\n\n" + texts.t("SUBSCRIPTION_LINK_USAGE_TITLE", "📱 Как использовать:") + "\n" + "\n".join( diff --git a/app/keyboards/inline.py b/app/keyboards/inline.py index 01e79ce4..92cd73b6 100644 --- a/app/keyboards/inline.py +++ b/app/keyboards/inline.py @@ -1,16 +1,21 @@ from typing import List, Optional -from aiogram import types -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton + +import logging from datetime import datetime -from app.database.models import User + +from aiogram import types +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup from sqlalchemy.ext.asyncio import AsyncSession -from app.config import settings, PERIOD_PRICES, TRAFFIC_PRICES +from app.config import PERIOD_PRICES, TRAFFIC_PRICES, settings +from app.database.models import User from app.localization.loader import DEFAULT_LANGUAGE from app.localization.texts import get_texts from app.utils.pricing_utils import format_period_description -from app.utils.subscription_utils import get_display_subscription_link -import logging +from app.utils.subscription_utils import ( + get_display_subscription_link, + is_supported_telegram_url, +) logger = logging.getLogger(__name__) @@ -255,48 +260,54 @@ def get_happ_download_button_row(texts) -> Optional[List[InlineKeyboardButton]]: def get_happ_cryptolink_keyboard( - subscription_link: str, + subscription_link: Optional[str], language: str = DEFAULT_LANGUAGE, ) -> InlineKeyboardMarkup: texts = get_texts(language) - buttons = [ - [ + buttons: List[List[InlineKeyboardButton]] = [] + + if is_supported_telegram_url(subscription_link): + buttons.append([ InlineKeyboardButton( text=texts.t("CONNECT_BUTTON", "🔗 Подключиться"), url=subscription_link, ) - ], + ]) + + buttons.extend( [ - InlineKeyboardButton( - text=texts.t("HAPP_PLATFORM_IOS", "🍎 iOS"), - callback_data="happ_download_ios", - ) - ], - [ - InlineKeyboardButton( - text=texts.t("HAPP_PLATFORM_ANDROID", "🤖 Android"), - callback_data="happ_download_android", - ) - ], - [ - InlineKeyboardButton( - text=texts.t("HAPP_PLATFORM_MACOS", "🖥️ Mac OS"), - callback_data="happ_download_macos", - ) - ], - [ - InlineKeyboardButton( - text=texts.t("HAPP_PLATFORM_WINDOWS", "💻 Windows"), - callback_data="happ_download_windows", - ) - ], - [ - InlineKeyboardButton( - text=texts.t("BACK_TO_MAIN_MENU_BUTTON", "⬅️ В главное меню"), - callback_data="back_to_menu", - ) - ], - ] + [ + InlineKeyboardButton( + text=texts.t("HAPP_PLATFORM_IOS", "🍎 iOS"), + callback_data="happ_download_ios", + ) + ], + [ + InlineKeyboardButton( + text=texts.t("HAPP_PLATFORM_ANDROID", "🤖 Android"), + callback_data="happ_download_android", + ) + ], + [ + InlineKeyboardButton( + text=texts.t("HAPP_PLATFORM_MACOS", "🖥️ Mac OS"), + callback_data="happ_download_macos", + ) + ], + [ + InlineKeyboardButton( + text=texts.t("HAPP_PLATFORM_WINDOWS", "💻 Windows"), + callback_data="happ_download_windows", + ) + ], + [ + InlineKeyboardButton( + text=texts.t("BACK_TO_MAIN_MENU_BUTTON", "⬅️ В главное меню"), + callback_data="back_to_menu", + ) + ], + ] + ) return InlineKeyboardMarkup(inline_keyboard=buttons) diff --git a/app/utils/subscription_utils.py b/app/utils/subscription_utils.py index 62db4142..f26bd9f2 100644 --- a/app/utils/subscription_utils.py +++ b/app/utils/subscription_utils.py @@ -1,10 +1,13 @@ import logging from datetime import datetime from typing import Optional -from sqlalchemy import select, delete, func +from urllib.parse import urlsplit + +from sqlalchemy import delete, func, select from sqlalchemy.ext.asyncio import AsyncSession -from app.database.models import Subscription, User + from app.config import settings +from app.database.models import Subscription, User logger = logging.getLogger(__name__) @@ -109,3 +112,17 @@ def get_display_subscription_link(subscription: Optional[Subscription]) -> Optio return crypto_link or base_link return base_link + + +def is_supported_telegram_url(url: Optional[str]) -> bool: + """Check whether Telegram allows using the given URL in inline buttons.""" + + if not url: + return False + + try: + scheme = urlsplit(url).scheme + except ValueError: + return False + + return scheme in {"http", "https", "tg", "ton"} diff --git a/locales/en.json b/locales/en.json index 84a68c61..c38bfe69 100644 --- a/locales/en.json +++ b/locales/en.json @@ -408,6 +408,7 @@ "SUBSCRIPTION_DEVICE_GUIDE_TITLE": "📱 Setup for {device_name}", "SUBSCRIPTION_HAPP_OPEN_TITLE": "🔗 Connect via Happ", "SUBSCRIPTION_HAPP_OPEN_LINK": "🔓 Open link in Happ", + "SUBSCRIPTION_HAPP_OPEN_COPY": "🔓 Copy the link and open it in Happ: {subscription_link}", "SUBSCRIPTION_HAPP_OPEN_HINT": "💡 If the link doesn't open automatically, copy it manually: {subscription_link}", "SUBSCRIPTION_DEVICE_LINK_TITLE": "🔗 Subscription link:", "SUBSCRIPTION_DEVICE_FEATURED_APP": "📋 Recommended app: {app_name}", diff --git a/locales/ru.json b/locales/ru.json index b1498e4e..6b785bbc 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -408,6 +408,7 @@ "SUBSCRIPTION_DEVICE_GUIDE_TITLE": "📱 Настройка для {device_name}", "SUBSCRIPTION_HAPP_OPEN_TITLE": "🔗 Подключение через Happ", "SUBSCRIPTION_HAPP_OPEN_LINK": "🔓 Открыть ссылку в Happ", + "SUBSCRIPTION_HAPP_OPEN_COPY": "🔓 Скопируйте ссылку и откройте в Happ: {subscription_link}", "SUBSCRIPTION_HAPP_OPEN_HINT": "💡 Если ссылка не открывается автоматически, скопируйте её вручную: {subscription_link}", "SUBSCRIPTION_DEVICE_LINK_TITLE": "🔗 Ссылка подписки:", "SUBSCRIPTION_DEVICE_FEATURED_APP": "📋 Рекомендуемое приложение: {app_name}",