fix: guide mode buttons — support external type alias, extract urlScheme from blocks
From PR #2923 by @dotX12, with improvements: - Support type: "external" as alias for "externalLink" in app config - Extract urlScheme from subscriptionLink buttons in blocks[] when not at root - Wrap custom URL schemes in HTTPS redirect for Telegram compatibility - Fallback to plain subscription URL when no redirect template configured Improvements over original PR: - Also check btn.get('url') not just btn.get('link') for scheme extraction - Validate extracted scheme contains :// before accepting - Skip redundant redirect wrapping when create_deep_link already wrapped
This commit is contained in:
@@ -406,11 +406,37 @@ async def get_apps_for_platform_async(device_type: str, language: str = 'ru') ->
|
|||||||
|
|
||||||
def normalize_app(app: dict[str, Any]) -> dict[str, Any]:
|
def normalize_app(app: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""Normalize Remnawave app dict to a unified format with blocks."""
|
"""Normalize Remnawave app dict to a unified format with blocks."""
|
||||||
|
|
||||||
|
# Extract urlScheme from blocks if not present at root level
|
||||||
|
url_scheme = app.get('urlScheme', '')
|
||||||
|
|
||||||
|
if not url_scheme:
|
||||||
|
# Try to extract from subscriptionLink button in blocks
|
||||||
|
blocks = app.get('blocks', [])
|
||||||
|
for block in blocks:
|
||||||
|
if not isinstance(block, dict):
|
||||||
|
continue
|
||||||
|
buttons = block.get('buttons', [])
|
||||||
|
for btn in buttons:
|
||||||
|
if not isinstance(btn, dict):
|
||||||
|
continue
|
||||||
|
if btn.get('type') == 'subscriptionLink':
|
||||||
|
link = btn.get('link', '') or btn.get('url', '')
|
||||||
|
if '{{SUBSCRIPTION_LINK}}' in link:
|
||||||
|
url_scheme = link.split('{{SUBSCRIPTION_LINK}}')[0]
|
||||||
|
break
|
||||||
|
if url_scheme:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Validate extracted scheme contains ://
|
||||||
|
if url_scheme and '://' not in url_scheme:
|
||||||
|
url_scheme = ''
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': app.get('id', app.get('name', 'unknown')),
|
'id': app.get('id', app.get('name', 'unknown')),
|
||||||
'name': app.get('name', ''),
|
'name': app.get('name', ''),
|
||||||
'isFeatured': app.get('featured', app.get('isFeatured', False)),
|
'isFeatured': app.get('featured', app.get('isFeatured', False)),
|
||||||
'urlScheme': app.get('urlScheme', ''),
|
'urlScheme': url_scheme,
|
||||||
'isNeedBase64Encoding': app.get('isNeedBase64Encoding', False),
|
'isNeedBase64Encoding': app.get('isNeedBase64Encoding', False),
|
||||||
'blocks': app.get('blocks', []),
|
'blocks': app.get('blocks', []),
|
||||||
'_raw': app,
|
'_raw': app,
|
||||||
|
|||||||
+29
-7
@@ -8,6 +8,12 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
|
|
||||||
from app.config import PERIOD_PRICES, settings
|
from app.config import PERIOD_PRICES, settings
|
||||||
from app.database.models import User
|
from app.database.models import User
|
||||||
|
from app.handlers.subscription.common import (
|
||||||
|
build_redirect_link,
|
||||||
|
create_deep_link,
|
||||||
|
get_localized_value,
|
||||||
|
resolve_button_url,
|
||||||
|
)
|
||||||
from app.localization.loader import DEFAULT_LANGUAGE
|
from app.localization.loader import DEFAULT_LANGUAGE
|
||||||
from app.localization.texts import get_texts
|
from app.localization.texts import get_texts
|
||||||
from app.utils.miniapp_buttons import build_miniapp_or_callback_button
|
from app.utils.miniapp_buttons import build_miniapp_or_callback_button
|
||||||
@@ -2554,9 +2560,6 @@ def get_device_selection_keyboard(
|
|||||||
platforms: list[dict] | None = None,
|
platforms: list[dict] | None = None,
|
||||||
sub_id: int | None = None,
|
sub_id: int | None = None,
|
||||||
) -> InlineKeyboardMarkup:
|
) -> InlineKeyboardMarkup:
|
||||||
from app.config import settings
|
|
||||||
from app.handlers.subscription.common import get_localized_value
|
|
||||||
|
|
||||||
texts = get_texts(language)
|
texts = get_texts(language)
|
||||||
back_cb = f'sm:{sub_id}' if sub_id and settings.is_multi_tariff_enabled() else 'menu_subscription'
|
back_cb = f'sm:{sub_id}' if sub_id and settings.is_multi_tariff_enabled() else 'menu_subscription'
|
||||||
|
|
||||||
@@ -2609,8 +2612,6 @@ def get_connection_guide_keyboard(
|
|||||||
has_other_apps: bool = False,
|
has_other_apps: bool = False,
|
||||||
sub_id: int | None = None,
|
sub_id: int | None = None,
|
||||||
) -> InlineKeyboardMarkup:
|
) -> InlineKeyboardMarkup:
|
||||||
from app.handlers.subscription.common import create_deep_link, get_localized_value, resolve_button_url
|
|
||||||
|
|
||||||
texts = get_texts(language)
|
texts = get_texts(language)
|
||||||
back_cb = f'sm:{sub_id}' if sub_id and settings.is_multi_tariff_enabled() else 'menu_subscription'
|
back_cb = f'sm:{sub_id}' if sub_id and settings.is_multi_tariff_enabled() else 'menu_subscription'
|
||||||
|
|
||||||
@@ -2623,6 +2624,10 @@ def get_connection_guide_keyboard(
|
|||||||
if not isinstance(btn, dict):
|
if not isinstance(btn, dict):
|
||||||
continue
|
continue
|
||||||
btn_type = btn.get('type', '')
|
btn_type = btn.get('type', '')
|
||||||
|
# Support both 'external' and 'externalLink' for backward compatibility
|
||||||
|
if btn_type == 'external':
|
||||||
|
btn_type = 'externalLink'
|
||||||
|
|
||||||
btn_text = btn.get('text', {})
|
btn_text = btn.get('text', {})
|
||||||
if isinstance(btn_text, dict):
|
if isinstance(btn_text, dict):
|
||||||
btn_text = get_localized_value(btn_text, language)
|
btn_text = get_localized_value(btn_text, language)
|
||||||
@@ -2644,9 +2649,26 @@ def get_connection_guide_keyboard(
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
elif btn_type == 'subscriptionLink':
|
elif btn_type == 'subscriptionLink':
|
||||||
|
# First try to resolve the button's URL template
|
||||||
url = resolved_url or resolve_button_url(btn_url, subscription_url)
|
url = resolved_url or resolve_button_url(btn_url, subscription_url)
|
||||||
deep_link = create_deep_link(app.get('_raw', app), subscription_url)
|
|
||||||
final_url = deep_link or url or subscription_url
|
# If button has no template, try deep link
|
||||||
|
if not btn_url or '{{SUBSCRIPTION_LINK}}' not in btn_url:
|
||||||
|
deep_link = create_deep_link(app.get('_raw', app), subscription_url)
|
||||||
|
final_url = deep_link or url or subscription_url
|
||||||
|
else:
|
||||||
|
final_url = url or subscription_url
|
||||||
|
|
||||||
|
# Telegram doesn't support custom URL schemes — wrap with redirect
|
||||||
|
if final_url and not final_url.startswith(('http://', 'https://')):
|
||||||
|
template = settings.get_happ_cryptolink_redirect_template()
|
||||||
|
if template:
|
||||||
|
wrapped_url = build_redirect_link(final_url, template)
|
||||||
|
if wrapped_url:
|
||||||
|
final_url = wrapped_url
|
||||||
|
else:
|
||||||
|
final_url = subscription_url
|
||||||
|
|
||||||
if final_url:
|
if final_url:
|
||||||
keyboard.append(
|
keyboard.append(
|
||||||
[
|
[
|
||||||
|
|||||||
Reference in New Issue
Block a user