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}",