Handle Happ cryptolink without unsupported Telegram URL

This commit is contained in:
Egor
2025-09-25 11:36:26 +03:00
parent cbf88226b4
commit 0d714b4dff
5 changed files with 108 additions and 58 deletions

View File

@@ -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",
"<a href=\"{subscription_link}\">🔓 Открыть ссылку в Happ</a>",
)
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: <code>{subscription_link}</code>",
).format(subscription_link=escaped_subscription_link)
happ_message = (
texts.t(
"SUBSCRIPTION_HAPP_OPEN_TITLE",
"🔗 <b>Подключение через Happ</b>",
)
+ "\n\n"
+ texts.t(
"SUBSCRIPTION_HAPP_OPEN_LINK",
"<a href=\"{subscription_link}\">🔓 Открыть ссылку в Happ</a>",
).format(subscription_link=subscription_link)
+ formatted_link_line
+ "\n\n"
+ texts.t(
"SUBSCRIPTION_HAPP_OPEN_HINT",
"💡 Если ссылка не открывается автоматически, скопируйте её вручную: <code>{subscription_link}</code>",
).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", "🔗 <b>Ссылка подписки:</b>")
+ "\n\n"
+ f"<code>{subscription_link}</code>\n\n"
+ f"<code>{escaped_subscription_link}</code>\n\n"
+ texts.t("SUBSCRIPTION_LINK_USAGE_TITLE", "📱 <b>Как использовать:</b>")
+ "\n"
+ "\n".join(

View File

@@ -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)

View File

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

View File

@@ -408,6 +408,7 @@
"SUBSCRIPTION_DEVICE_GUIDE_TITLE": "📱 <b>Setup for {device_name}</b>",
"SUBSCRIPTION_HAPP_OPEN_TITLE": "🔗 <b>Connect via Happ</b>",
"SUBSCRIPTION_HAPP_OPEN_LINK": "<a href=\"{subscription_link}\">🔓 Open link in Happ</a>",
"SUBSCRIPTION_HAPP_OPEN_COPY": "🔓 Copy the link and open it in Happ: <code>{subscription_link}</code>",
"SUBSCRIPTION_HAPP_OPEN_HINT": "💡 If the link doesn't open automatically, copy it manually: <code>{subscription_link}</code>",
"SUBSCRIPTION_DEVICE_LINK_TITLE": "🔗 <b>Subscription link:</b>",
"SUBSCRIPTION_DEVICE_FEATURED_APP": "📋 <b>Recommended app:</b> {app_name}",

View File

@@ -408,6 +408,7 @@
"SUBSCRIPTION_DEVICE_GUIDE_TITLE": "📱 <b>Настройка для {device_name}</b>",
"SUBSCRIPTION_HAPP_OPEN_TITLE": "🔗 <b>Подключение через Happ</b>",
"SUBSCRIPTION_HAPP_OPEN_LINK": "<a href=\"{subscription_link}\">🔓 Открыть ссылку в Happ</a>",
"SUBSCRIPTION_HAPP_OPEN_COPY": "🔓 Скопируйте ссылку и откройте в Happ: <code>{subscription_link}</code>",
"SUBSCRIPTION_HAPP_OPEN_HINT": "💡 Если ссылка не открывается автоматически, скопируйте её вручную: <code>{subscription_link}</code>",
"SUBSCRIPTION_DEVICE_LINK_TITLE": "🔗 <b>Ссылка подписки:</b>",
"SUBSCRIPTION_DEVICE_FEATURED_APP": "📋 <b>Рекомендуемое приложение:</b> {app_name}",