""" asset_proxy.py — конфигурация HTTP-прокси для скачивания Roblox-ассетов. Roblox блокирует direct-traffic из РФ — нужно ходить через VPN/прокси. Текущая стратегия: 1. По умолчанию — НИЧЕГО НЕ КАЧАТЬ (mode='disabled'), пишем ассеты со status='pending'. Импорт продолжается, геометрия и скрипты работают. Mesh/Texture/Sound показываются как заглушки до скачки. 2. mode='cloudflare_worker' — ходить через Cloudflare Worker (см. cf-worker/asset-proxy.js). Worker принимает /asset?id= и проксирует на assetdelivery.roblox.com. 3. mode='direct' — без прокси (для тестов или если запускаем с зарубежного хоста). ENV: ROBLOX_PROXY_MODE = disabled | direct | cloudflare_worker ROBLOX_PROXY_URL = https://rbxl-proxy.workers.dev # для cloudflare_worker mode Использование в asset_downloader: from asset_proxy import get_proxy_config cfg = get_proxy_config() if cfg.mode == 'disabled': raise PendingDownload(rbx_asset_id) url = cfg.build_url(rbx_asset_id) resp = session.get(url, ...) """ import os from dataclasses import dataclass @dataclass class ProxyConfig: mode: str # disabled | direct | cloudflare_worker base_url: str # для direct mode = 'https://assetdelivery.roblox.com/v1/asset' # для cf worker = 'https://rbxl-proxy.workers.dev/asset' headers: dict # доп. заголовки (например auth для CF Worker) def build_url(self, rbx_asset_id: int) -> str: return f'{self.base_url}?id={rbx_asset_id}' class PendingDownload(Exception): """Бросается когда mode='disabled' — ассет не скачан но импорт продолжается.""" def __init__(self, rbx_asset_id: int): self.rbx_asset_id = rbx_asset_id super().__init__(f"asset {rbx_asset_id} download pending (proxy disabled)") @dataclass class HttpProxyConfig: """Дополнение к ProxyConfig для режима http_proxy. proxies — словарь который requests.Session() ест как есть. """ proxies: dict def get_proxy_config() -> ProxyConfig: mode = os.environ.get('ROBLOX_PROXY_MODE', 'disabled') if mode == 'direct': return ProxyConfig( mode='direct', base_url='https://assetdelivery.roblox.com/v1/asset', headers={'User-Agent': 'Roblox/WinInet'}, ) elif mode == 'cloudflare_worker': url = os.environ.get('ROBLOX_PROXY_URL') if not url: raise RuntimeError("ROBLOX_PROXY_URL не задан для cloudflare_worker mode") secret = os.environ.get('ROBLOX_PROXY_SECRET', '') return ProxyConfig( mode='cloudflare_worker', base_url=url.rstrip('/') + '/asset', headers={ 'User-Agent': 'rublox-rbxl-importer/1.0', 'X-Proxy-Auth': secret, }, ) elif mode == 'http_proxy': # Используем внешний HTTP-прокси для исходящего трафика. # base_url остаётся реальный Roblox CDN, запрос идёт через прокси. # ENV: ROBLOX_HTTP_PROXY = http://user:pass@host:port proxy_url = os.environ.get('ROBLOX_HTTP_PROXY', '') if not proxy_url: raise RuntimeError("ROBLOX_HTTP_PROXY не задан для http_proxy mode") return ProxyConfig( mode='http_proxy', base_url='https://assetdelivery.roblox.com/v1/asset', headers={'User-Agent': 'Roblox/WinInet'}, ) elif mode == 'disabled': return ProxyConfig(mode='disabled', base_url='', headers={}) else: raise RuntimeError(f"unknown ROBLOX_PROXY_MODE={mode!r}") def get_http_proxies() -> dict: """Для requests-сессии: {'http': ..., 'https': ...}.""" url = os.environ.get('ROBLOX_HTTP_PROXY', '') if not url: return {} return {'http': url, 'https': url}