import os import sys import requests try: import yaml except ImportError: print("Ошибка: Отсутствует библиотека PyYAML. Установите её командой: pip install pyyaml") sys.exit(1) def fetch_and_prepare_configs(): # Забираем ссылку из секретов Gitea/GitHub sub_url = os.getenv("VPN_SUB_URL") if not sub_url: print("Ошибка: Переменная окружения VPN_SUB_URL не задана!") sys.exit(1) # Имитируем заголовки оригинального ядра Mihomo (Clash.Meta) для OpenClash headers = { "User-Agent": "Mihomo/1.18.3 Clash.Meta OpenClash", "Accept": "application/yaml, text/yaml, */*", "Connection": "keep-alive", } # Принудительно заставляем панель выдать мета-конфиг if "flag=" not in sub_url: separator = "&" if "?" in sub_url else "?" sub_url += f"{separator}flag=meta" try: print("Отправка запроса к серверу VPN...") response = requests.get(sub_url, headers=headers, timeout=20) response.raise_for_status() # Вызовет ошибку при статусе не 200 raw_content = response.text.strip() # ФАЙЛ 1: Сохраняем оригинальный чистый конфиг от провайдера with open("clash.yaml", "w", encoding="utf-8") as f: f.write(raw_content) print("Оригинальный конфиг успешно сохранен в clash.yaml") # Парсим YAML в словарь Python для безопасного редактирования config = yaml.safe_load(raw_content) if not isinstance(config, dict): print("Ошибка: Не удалось распарсить YAML конфиг. Проверьте ссылку-подписку.") sys.exit(1) # --------------------------------------------------------- # 1. Базовые настройки # --------------------------------------------------------- # ВАЖНО: Для работы правил (раздельного обхода) режим должен быть 'rule', а не 'global' config['mode'] = 'rule' config['external-controller'] = '0.0.0.0:9090' config['ipv6'] = False config['keep-alive-idle'] = 30 config['keep-alive-interval'] = 15 # --------------------------------------------------------- # 2. Заменяем секцию DNS # --------------------------------------------------------- config['dns'] = { 'enable': True, 'use-hosts': True, 'enhanced-mode': 'fake-ip', 'fake-ip-range': '198.18.0.1/16', 'default-nameserver': ['77.88.8.8', '8.8.8.8'], 'nameserver': ['77.88.8.8', '77.88.8.1'], 'fake-ip-filter': [ "*.lan", "stun.*.*.*", "stun.*.*", "time.windows.com", "time.nist.gov", "time.apple.com", "time.asia.apple.com", "*.openwrt.pool.ntp.org", "pool.ntp.org", "ntp.ubuntu.com", "time1.apple.com", "time2.apple.com", "time3.apple.com", "time4.apple.com", "time5.apple.com", "time6.apple.com", "time7.apple.com", "time1.google.com", "time2.google.com", "time3.google.com", "time4.google.com", "api.joox.com", "joox.com", "*.xiami.com", "*.msftconnecttest.com", "*.msftncsi.com", "+.xboxlive.com", "*.*.stun.playstation.net", "xbox.*.*.microsoft.com", "*.ipv6.microsoft.com", "speedtest.cros.wr.pvp.net" ] } # --------------------------------------------------------- # 3. Формируем группу Proxy (url-test) # --------------------------------------------------------- # Собираем все имена прокси из оригинального конфига proxy_names = [] if 'proxies' in config and isinstance(config['proxies'], list): # Если есть верхнеуровневый список proxies (стандарт Clash) proxy_names = [p.get('name') for p in config['proxies'] if isinstance(p, dict) and 'name' in p] else: # Фолбэк: собираем имена из существующих групп proxy-groups for group in config.get('proxy-groups', []): if isinstance(group, dict) and 'proxies' in group: proxy_names.extend(group['proxies']) # Убираем дубликаты, сохраняя порядок proxy_names = list(dict.fromkeys(proxy_names)) new_proxy_group = { 'name': 'Proxy', 'type': 'url-test', 'proxies': proxy_names, 'url': 'http://www.gstatic.com/generate_204', 'interval': 300, 'tolerance': 50 } # Заменяем старую главную группу (например, "→ Remnawave" или "PROXY") на новую updated_groups = [] replaced = False for group in config.get('proxy-groups', []): if isinstance(group, dict) and group.get('name') in ['→ Remnawave', 'PROXY', 'Proxy']: updated_groups.append(new_proxy_group) replaced = True else: updated_groups.append(group) # Если такой группы не было, добавляем её в начало if not replaced: updated_groups.insert(0, new_proxy_group) config['proxy-groups'] = updated_groups # --------------------------------------------------------- # 4. Заменяем правила (rules) # --------------------------------------------------------- config['rules'] = [ "RULE-SET,telegram,Proxy", "RULE-SET,geosite-discord,Proxy", "RULE-SET,discord-voice,Proxy", "RULE-SET,geosite-whatsapp,Proxy", "RULE-SET,geosite-viber,Proxy", "RULE-SET,geosite-signal,Proxy", "RULE-SET,geosite-youtube,Proxy", "RULE-SET,geosite-twitter,Proxy", "RULE-SET,geosite-facebook,Proxy", "RULE-SET,geosite-instagram,Proxy", "RULE-SET,geosite-openai,Proxy", "RULE-SET,geosite-anthropic,Proxy", "RULE-SET,geosite-google-gemini,Proxy", "RULE-SET,geosite-xai,Proxy", "RULE-SET,cdn-all,Proxy", "DOMAIN-SUFFIX,com,Proxy", "DOMAIN,git.darkmode.duckdns.org,Proxy", "RULE-SET,ru-blocked,Proxy", "GEOIP,RU,DIRECT", "DOMAIN,ivanovonews.ru,Proxy", "MATCH,DIRECT" ] # --------------------------------------------------------- # 5. Добавляем rule-providers # --------------------------------------------------------- config['rule-providers'] = { 'geosite-discord': { 'behavior': 'domain', 'type': 'http', 'url': 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/meta/geo/geosite/discord.yaml', 'interval': 86400 }, 'geosite-whatsapp': { 'behavior': 'domain', 'type': 'http', 'url': 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/meta/geo/geosite/whatsapp.yaml', 'interval': 86400 }, 'geosite-viber': { 'behavior': 'domain', 'type': 'http', 'url': 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/meta/geo/geosite/viber.yaml', 'interval': 86400 }, 'geosite-signal': { 'behavior': 'domain', 'type': 'http', 'url': 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/meta/geo/geosite/signal.yaml', 'interval': 86400 }, 'geosite-youtube': { 'behavior': 'domain', 'type': 'http', 'url': 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/meta/geo/geosite/youtube.yaml', 'interval': 86400 }, 'geosite-twitter': { 'behavior': 'domain', 'type': 'http', 'url': 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/meta/geo/geosite/twitter.yaml', 'interval': 86400 }, 'geosite-facebook': { 'behavior': 'domain', 'type': 'http', 'url': 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/meta/geo/geosite/facebook.yaml', 'interval': 86400 }, 'geosite-instagram': { 'behavior': 'domain', 'type': 'http', 'url': 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/meta/geo/geosite/instagram.yaml', 'interval': 86400 }, 'geosite-openai': { 'behavior': 'domain', 'type': 'http', 'url': 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/meta/geo/geosite/openai.yaml', 'interval': 86400 }, 'geosite-anthropic': { 'behavior': 'domain', 'type': 'http', 'url': 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/meta/geo/geosite/anthropic.yaml', 'interval': 86400 }, 'geosite-google-gemini': { 'behavior': 'domain', 'type': 'http', 'url': 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/meta/geo/geosite/google-gemini.yaml', 'interval': 86400 }, 'geosite-xai': { 'behavior': 'domain', 'type': 'http', 'url': 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/meta/geo/geosite/xai.yaml', 'interval': 86400 }, 'cdn-all': { 'behavior': 'ipcidr', 'type': 'http', 'url': 'https://raw.githubusercontent.com/123jjck/cdn-ip-ranges/refs/heads/main/cdn-only/cdn-only_plain_ipv4.txt', 'interval': 86400, 'format': 'text' }, 'telegram': { 'behavior': 'ipcidr', 'type': 'http', 'url': 'https://raw.githubusercontent.com/123jjck/cdn-ip-ranges/refs/heads/main/telegram/telegram_plain_ipv4.txt', 'interval': 86400, 'format': 'text' }, 'discord-voice': { 'behavior': 'ipcidr', 'type': 'http', 'url': 'https://raw.githubusercontent.com/123jjck/cdn-ip-ranges/refs/heads/main/discord-voice/discord-voice_plain_ipv4.txt', 'interval': 86400, 'format': 'text' }, 'ru-blocked': { 'behavior': 'domain', 'type': 'http', 'url': 'https://cdn.jsdelivr.net/gh/shvchk/unblock-net/lists/clash/ru-blocked', 'interval': 86400, 'format': 'text' } } # ФАЙЛ 2: Сохраняем модифицированный конфиг with open("clash_split.yaml", "w", encoding="utf-8") as f: # sort_keys=False сохраняет исходный порядок ключей # allow_unicode=True гарантирует, что эмодзи (🇩🇪, 🇷🇺) не превратятся в \u-последовательности # width=1000 предотвращает нежелательный перенос длинных строк yaml.dump(config, f, default_flow_style=False, sort_keys=False, allow_unicode=True, width=1000) print("Раздельный конфиг успешно сохранен в clash_split.yaml") except requests.exceptions.RequestException as e: print(f"Ошибка сети: {e}") sys.exit(1) except Exception as e: print(f"Критическая ошибка во время выполнения: {e}") sys.exit(1) if __name__ == "__main__": fetch_and_prepare_configs()