diff --git a/app/handlers/subscription/__init__.py b/app/handlers/subscription/__init__.py
index 72d95127..c005e8a0 100644
--- a/app/handlers/subscription/__init__.py
+++ b/app/handlers/subscription/__init__.py
@@ -26,6 +26,7 @@ from .common import (
load_app_config,
load_app_config_async,
normalize_app,
+ render_guide_blocks,
resolve_button_url,
update_traffic_prices,
validate_traffic_price,
@@ -190,6 +191,7 @@ __all__ = [
'normalize_app',
'refresh_traffic_config',
'register_handlers',
+ 'render_guide_blocks',
'resolve_button_url',
'resume_subscription_checkout',
'return_to_saved_cart',
diff --git a/app/handlers/subscription/common.py b/app/handlers/subscription/common.py
index a98870bd..471b42f6 100644
--- a/app/handlers/subscription/common.py
+++ b/app/handlers/subscription/common.py
@@ -1,5 +1,6 @@
import asyncio
import base64
+import html as html_mod
import json
import time
from datetime import datetime
@@ -241,6 +242,31 @@ def format_additional_section(additional: Any, texts, language: str) -> str:
return '\n'.join(parts)
+def render_guide_blocks(blocks: list[dict], language: str) -> str:
+ """Render block-format guide steps to HTML text."""
+ parts: list[str] = []
+ step_num = 1
+ for block in blocks:
+ if not isinstance(block, dict):
+ continue
+ title = block.get('title', {})
+ desc = block.get('description', {})
+ title_text = html_mod.escape(
+ get_localized_value(title, language) if isinstance(title, dict) else str(title or '')
+ )
+ desc_text = html_mod.escape(get_localized_value(desc, language) if isinstance(desc, dict) else str(desc or ''))
+ if title_text or desc_text:
+ step = f'Шаг {step_num}'
+ if title_text:
+ step += f' - {title_text}'
+ step += ':'
+ if desc_text:
+ step += f'\n{desc_text}'
+ parts.append(step)
+ step_num += 1
+ return '\n\n'.join(parts)
+
+
def build_redirect_link(target_link: str | None, template: str | None) -> str | None:
if not target_link or not template:
return None
diff --git a/app/handlers/subscription/devices.py b/app/handlers/subscription/devices.py
index a598ff8f..0d5ff1c2 100644
--- a/app/handlers/subscription/devices.py
+++ b/app/handlers/subscription/devices.py
@@ -40,9 +40,9 @@ from .common import (
get_apps_for_device,
get_apps_for_platform_async,
get_device_name,
- get_localized_value,
get_step_description,
logger,
+ render_guide_blocks,
)
from .countries import _get_available_countries
@@ -1345,24 +1345,9 @@ async def handle_device_guide(callback: types.CallbackQuery, db_user: User, db:
)
if is_blocks_format:
- # Build guide text from blocks
- step_num = 1
- for block in featured_app.get('blocks', []):
- if not isinstance(block, dict):
- continue
- title = block.get('title', {})
- desc = block.get('description', {})
- title_text = get_localized_value(title, db_user.language) if isinstance(title, dict) else str(title or '')
- desc_text = get_localized_value(desc, db_user.language) if isinstance(desc, dict) else str(desc or '')
-
- if title_text or desc_text:
- guide_text += f'\n\nШаг {step_num}'
- if title_text:
- guide_text += f' - {title_text}'
- guide_text += ':'
- if desc_text:
- guide_text += f'\n{desc_text}'
- step_num += 1
+ blocks_text = render_guide_blocks(featured_app.get('blocks', []), db_user.language)
+ if blocks_text:
+ guide_text += '\n\n' + blocks_text
else:
# Legacy steps
installation_description = get_step_description(
@@ -1546,25 +1531,9 @@ async def handle_specific_app_guide(callback: types.CallbackQuery, db_user: User
)
if is_blocks_format:
- # Build guide from blocks
- step_num = 1
- for block in app.get('blocks', []):
- if not isinstance(block, dict):
- continue
- title = block.get('title', {})
- desc = block.get('description', {})
- title_text = get_localized_value(title, db_user.language) if isinstance(title, dict) else str(title or '')
- desc_text = get_localized_value(desc, db_user.language) if isinstance(desc, dict) else str(desc or '')
-
- if title_text or desc_text:
- guide_text += f'Шаг {step_num}'
- if title_text:
- guide_text += f' - {title_text}'
- guide_text += ':'
- if desc_text:
- guide_text += f'\n{desc_text}'
- guide_text += '\n\n'
- step_num += 1
+ blocks_text = render_guide_blocks(app.get('blocks', []), db_user.language)
+ if blocks_text:
+ guide_text += blocks_text + '\n\n'
else:
raw_app = app.get('_raw', app)
installation_description = get_step_description(raw_app, 'installationStep', db_user.language)
diff --git a/app/keyboards/inline.py b/app/keyboards/inline.py
index d59be2d0..6b83fc01 100644
--- a/app/keyboards/inline.py
+++ b/app/keyboards/inline.py
@@ -2437,6 +2437,17 @@ def get_connection_guide_keyboard(
)
]
)
+ else:
+ # Fallback: use raw subscription URL
+ keyboard.append(
+ [
+ InlineKeyboardButton(
+ text=texts.t('CONNECT_BUTTON', '🔗 Подключиться'),
+ url=subscription_url,
+ style='success',
+ )
+ ]
+ )
elif btn_type == 'copyButton':
url = resolved_url or resolve_button_url(btn_url, subscription_url)
if url: