Merge pull request #2539 from BEDOLAGA-DEV/feat/remnawave-original-config-format

Feat/remnawave original config format
This commit is contained in:
c0mrade
2026-02-06 17:35:13 +03:00
committed by GitHub
+121 -35
View File
@@ -2917,7 +2917,12 @@ def _convert_remnawave_config_to_cabinet(config: dict[str, Any]) -> dict[str, An
async def _load_app_config_async() -> dict[str, Any]:
"""Load app config from RemnaWave (if configured) or local file."""
"""Load app config from RemnaWave (if configured) or local file.
When config comes from RemnaWave, returns the original format with
``_isRemnawave`` flag so the caller can serve it as-is (enriched with
deep links) instead of converting to the legacy step-based format.
"""
remnawave_uuid = _get_remnawave_config_uuid()
if remnawave_uuid:
@@ -2927,22 +2932,9 @@ async def _load_app_config_async() -> dict[str, Any]:
config = await api.get_subscription_page_config(remnawave_uuid)
if config and config.config:
logger.debug(f'Loaded app config from RemnaWave: {remnawave_uuid}')
# Debug: log raw RemnaWave config structure
import json
logger.debug(
f'RemnaWave raw config: {json.dumps(config.config, ensure_ascii=False, indent=2)[:2000]}'
)
converted = _convert_remnawave_config_to_cabinet(config.config)
logger.debug(f'Converted config platforms: {list(converted.get("platforms", {}).keys())}')
# Log first app from each platform
for platform, apps in converted.get('platforms', {}).items():
if apps:
first_app = apps[0]
logger.debug(
f'Platform {platform} first app: name={first_app.get("name")}, urlScheme={first_app.get("urlScheme")}'
)
return converted
raw = dict(config.config)
raw['_isRemnawave'] = True
return raw
except Exception as e:
logger.warning(f'Failed to load RemnaWave config, falling back to file: {e}')
@@ -3329,6 +3321,29 @@ async def get_happ_downloads(
}
def _resolve_button_url(
url: str,
subscription_url: str | None,
subscription_crypto_link: str | None,
) -> str:
"""Resolve template variables in button URLs.
Matches remnawave/subscription-page frontend TemplateEngine:
- {{SUBSCRIPTION_LINK}} -> plain subscription URL
- {{HAPP_CRYPT3_LINK}} -> crypto link
- {{HAPP_CRYPT4_LINK}} -> crypto link
"""
if not url:
return url
result = url
if subscription_url:
result = result.replace('{{SUBSCRIPTION_LINK}}', subscription_url)
if subscription_crypto_link:
result = result.replace('{{HAPP_CRYPT3_LINK}}', subscription_crypto_link)
result = result.replace('{{HAPP_CRYPT4_LINK}}', subscription_crypto_link)
return result
@router.get('/app-config')
async def get_app_config(
user: User = Depends(get_current_cabinet_user),
@@ -3345,13 +3360,97 @@ async def get_app_config(
# Load config from RemnaWave (if configured) or local file
config = await _load_app_config_async()
platforms_raw = config.get('platforms', {})
is_remnawave = config.pop('_isRemnawave', False)
hide_link = settings.should_hide_subscription_link()
# Строим platformNames из displayName каждой платформы RemnaWave
platform_names: dict[str, Any] = {}
for pk, pd in config.get('platforms', {}).items():
if isinstance(pd, dict) and 'displayName' in pd:
platform_names[pk] = pd['displayName']
# Фоллбэк для платформ без displayName (en достаточно)
fallback_names = {
'ios': {'en': 'iPhone/iPad'},
'android': {'en': 'Android'},
'macos': {'en': 'macOS'},
'windows': {'en': 'Windows'},
'linux': {'en': 'Linux'},
'androidTV': {'en': 'Android TV'},
'appleTV': {'en': 'Apple TV'},
}
for k, v in fallback_names.items():
if k not in platform_names:
platform_names[k] = v
if is_remnawave:
# ── RemnaWave original format ──
# Serve original blocks/svgLibrary enriched with deep links and resolved URLs.
platforms: dict[str, Any] = {}
for platform_key, platform_data in config.get('platforms', {}).items():
if not isinstance(platform_data, dict):
continue
apps = platform_data.get('apps', [])
if not isinstance(apps, list):
continue
enriched_apps = []
for app in apps:
if not isinstance(app, dict):
continue
# Generate deep link
deep_link = None
if subscription_url or subscription_crypto_link:
deep_link = _create_deep_link(app, subscription_url, subscription_crypto_link)
app['deepLink'] = deep_link
# Resolve templates only for subscriptionLink and copyButton (not external)
for block in app.get('blocks', []):
if not isinstance(block, dict):
continue
for btn in block.get('buttons', []):
if not isinstance(btn, dict):
continue
btn_type = btn.get('type', '')
if btn_type in ('subscriptionLink', 'copyButton'):
url = btn.get('url', '') or btn.get('link', '')
if url and '{{' in url:
btn['resolvedUrl'] = _resolve_button_url(
url,
subscription_url,
subscription_crypto_link,
)
enriched_apps.append(app)
if enriched_apps:
# Сохраняем platform-level поля (svgIconKey, displayName и т.д.)
platform_output = {k: v for k, v in platform_data.items() if k != 'apps'}
platform_output['apps'] = enriched_apps
platforms[platform_key] = platform_output
return {
'isRemnawave': True,
'platforms': platforms,
'svgLibrary': config.get('svgLibrary', {}),
'baseTranslations': config.get('baseTranslations'),
'baseSettings': config.get('baseSettings'),
'uiConfig': config.get('uiConfig', {}),
'platformNames': platform_names,
'hasSubscription': bool(subscription_url or subscription_crypto_link),
'subscriptionUrl': subscription_url,
'subscriptionCryptoLink': subscription_crypto_link,
'hideLink': hide_link,
'branding': config.get('brandingSettings', {}),
}
# ── Legacy file-based format ──
platforms_raw = config.get('platforms', {})
if not isinstance(platforms_raw, dict):
platforms_raw = {}
# Build response with deep links
platforms = {}
platforms_legacy: dict[str, Any] = {}
for platform_key, apps in platforms_raw.items():
if not isinstance(apps, list):
continue
@@ -3379,23 +3478,10 @@ async def get_app_config(
platform_apps.append(app_data)
if platform_apps:
platforms[platform_key] = platform_apps
# Platform display names for UI
platform_names = {
'ios': {'ru': 'iPhone/iPad', 'en': 'iPhone/iPad'},
'android': {'ru': 'Android', 'en': 'Android'},
'macos': {'ru': 'macOS', 'en': 'macOS'},
'windows': {'ru': 'Windows', 'en': 'Windows'},
'linux': {'ru': 'Linux', 'en': 'Linux'},
'androidTV': {'ru': 'Android TV', 'en': 'Android TV'},
'appleTV': {'ru': 'Apple TV', 'en': 'Apple TV'},
}
hide_link = settings.should_hide_subscription_link()
platforms_legacy[platform_key] = platform_apps
return {
'platforms': platforms,
'platforms': platforms_legacy,
'platformNames': platform_names,
'hasSubscription': bool(subscription_url or subscription_crypto_link),
'subscriptionUrl': subscription_url if not hide_link else None,