import hashlib import logging from typing import Optional, Tuple from bot.services.panel_api_service import PanelApiService from bot.utils.ttl_cache import AsyncTTLCache from config.settings import Settings _CRYPT4_LINK_CACHES: dict[tuple[int, int], AsyncTTLCache] = {} async def _encrypt_raw_link(settings: Settings, raw_link: str) -> Optional[str]: """Encrypt the raw subscription URL using the panel's happ crypt4 API.""" async with PanelApiService(settings) as panel_service: encrypted_link = await panel_service.encrypt_happ_link(raw_link) if encrypted_link: return encrypted_link return None def _crypt4_link_cache(settings: Settings) -> Optional[AsyncTTLCache]: ttl_seconds = int(getattr(settings, "CRYPT4_LINK_CACHE_TTL_SECONDS", 3600) or 0) if ttl_seconds <= 0: return None cache_key = (id(settings), ttl_seconds) cache = _CRYPT4_LINK_CACHES.get(cache_key) if cache is None: cache = AsyncTTLCache( ttl_seconds=ttl_seconds, settings=settings, namespace="crypt4:links", ) _CRYPT4_LINK_CACHES[cache_key] = cache return cache async def _encrypt_raw_link_cached(settings: Settings, raw_link: str) -> Optional[str]: cache = _crypt4_link_cache(settings) if cache is None: return await _encrypt_raw_link(settings, raw_link) key = hashlib.sha256(raw_link.encode("utf-8")).hexdigest() return await cache.get_or_load(key, lambda: _encrypt_raw_link(settings, raw_link)) async def prepare_config_links( settings: Settings, raw_link: Optional[str] ) -> Tuple[Optional[str], Optional[str]]: """ Build the user-facing connection key and the URL for the connect button. Returns (display_link, button_link). When CRYPT4 is enabled the display link is encrypted and prefixed with happ://crypt4/ by panel API, and the button link is wrapped with CRYPT4_REDIRECT_URL if provided. """ if not raw_link: return None, None cleaned = raw_link.strip() if not cleaned: return None, None display_link = cleaned button_link = cleaned if settings.CRYPT4_ENABLED: encrypted_payload = await _encrypt_raw_link_cached(settings, cleaned) if encrypted_payload: display_link = encrypted_payload button_link = display_link else: logging.error( "CRYPT4_ENABLED is set but encryption failed; using raw link as fallback." ) redirect_base = (settings.CRYPT4_REDIRECT_URL or "").strip() if redirect_base and settings.CRYPT4_ENABLED and display_link: button_link = f"{redirect_base}{display_link}" return display_link, button_link