[
  {
    "path": "ClashForge.py",
    "content": "# -*- coding: utf-8 -*-\n# !/usr/bin/env python3\nimport base64\nimport subprocess\nimport threading\nimport time\nimport urllib.parse\nimport json\nimport glob\nimport re\nimport yaml\nimport random\nimport string\nimport httpx\nimport asyncio\nfrom itertools import chain\nfrom typing import Dict, List, Optional\nimport sys\nimport requests\nimport zipfile\nimport gzip\nimport shutil\nimport platform\nimport os\nfrom datetime import datetime\nfrom asyncio import Semaphore\nimport ssl\n\nssl._create_default_https_context = ssl._create_unverified_context\nimport warnings\n\nwarnings.filterwarnings('ignore')\nfrom requests_html import HTMLSession\nimport psutil\n\n# TEST_URL = \"http://www.gstatic.com/generate_204\"\nTEST_URL = \"http://www.pinterest.com\"\nCLASH_API_PORTS = [9090]\nCLASH_API_HOST = \"127.0.0.1\"\nCLASH_API_SECRET = \"\"\nTIMEOUT = 3\n# 存储所有节点的速度测试结果\nSPEED_TEST = False\nSPEED_TEST_LIMIT = 5  # 只测试前30个节点的下行速度，每个节点测试5秒\nresults_speed = []\nMAX_CONCURRENT_TESTS = 100\nLIMIT = 10000  # 最多保留LIMIT个节点\nCONFIG_FILE = 'clash_config.yaml'\nINPUT = \"input\"  # 从文件中加载代理节点，支持yaml/yml、txt(每条代理链接占一行)\nBAN = [\"中国\", \"China\", \"CN\", \"电信\", \"移动\", \"联通\"]\nheaders = {\n    'Accept-Charset': 'utf-8',\n    'Accept': 'text/html,application/x-yaml,*/*',\n    'User-Agent': 'Clash Verge/1.7.7'\n}\n\n# Clash 配置文件的基础结构\nclash_config_template = {\n    \"port\": 7890,\n    \"socks-port\": 7891,\n    \"redir-port\": 7892,\n    \"allow-lan\": True,\n    \"mode\": \"rule\",\n    \"log-level\": \"info\",\n    \"external-controller\": \"127.0.0.1:9090\",\n    \"geodata-mode\": True,\n    'geox-url': {'geoip': 'https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip.dat',\n                 'mmdb': 'https://raw.githubusercontent.com/Loyalsoldier/geoip/release/GeoLite2-Country.mmdb'},\n    \"dns\": {\n        \"enable\": True,\n        \"ipv6\": False,\n        \"default-nameserver\": [\n            \"223.5.5.5\",\n            \"119.29.29.29\"\n        ],\n        \"enhanced-mode\": \"fake-ip\",\n        \"fake-ip-range\": \"198.18.0.1/16\",\n        \"use-hosts\": True,\n        \"nameserver\": [\n            \"https://doh.pub/dns-query\",\n            \"https://dns.alidns.com/dns-query\"\n        ],\n        \"fallback\": [\n            \"https://doh.dns.sb/dns-query\",\n            \"https://dns.cloudflare.com/dns-query\",\n            \"https://dns.twnic.tw/dns-query\",\n            \"tls://8.8.4.4:853\"\n        ],\n        \"fallback-filter\": {\n            \"geoip\": True,\n            \"ipcidr\": [\n                \"240.0.0.0/4\",\n                \"0.0.0.0/32\"\n            ]\n        }\n    },\n    \"proxies\": [],\n    \"proxy-groups\": [\n        {\n            \"name\": \"节点选择\",\n            \"type\": \"select\",\n            \"proxies\": [\n                \"自动选择\",\n                \"故障转移\",\n                \"DIRECT\",\n                \"手动选择\"\n            ]\n        },\n        {\n            \"name\": \"自动选择\",\n            \"type\": \"url-test\",\n            \"exclude-filter\": \"(?i)中国|China|CN|电信|移动|联通\",\n            \"proxies\": [],\n            # \"url\": \"http://www.gstatic.com/generate_204\",\n            \"url\": \"http://www.pinterest.com\",\n            \"interval\": 300,\n            \"tolerance\": 50\n        },\n        {\n            \"name\": \"故障转移\",\n            \"type\": \"fallback\",\n            \"exclude-filter\": \"(?i)中国|China|CN|电信|移动|联通\",\n            \"proxies\": [],\n            \"url\": \"http://www.gstatic.com/generate_204\",\n            \"interval\": 300\n        },\n        {\n            \"name\": \"手动选择\",\n            \"type\": \"select\",\n            \"proxies\": []\n        },\n    ],\n    \"rules\": [\n        \"DOMAIN,app.adjust.com,DIRECT\",\n        \"DOMAIN,bdtj.tagtic.cn,DIRECT\",\n        \"DOMAIN,log.mmstat.com,DIRECT\",\n        \"DOMAIN,sycm.mmstat.com,DIRECT\",\n        \"DOMAIN-SUFFIX,blog.google,DIRECT\",\n        \"DOMAIN-SUFFIX,googletraveladservices.com,DIRECT\",\n        \"DOMAIN,dl.google.com,DIRECT\",\n        \"DOMAIN,dl.l.google.com,DIRECT\",\n        \"DOMAIN,fonts.googleapis.com,DIRECT\",\n        \"DOMAIN,fonts.gstatic.com,DIRECT\",\n        \"DOMAIN,mtalk.google.com,DIRECT\",\n        \"DOMAIN,alt1-mtalk.google.com,DIRECT\",\n        \"DOMAIN,alt2-mtalk.google.com,DIRECT\",\n        \"DOMAIN,alt3-mtalk.google.com,DIRECT\",\n        \"DOMAIN,alt4-mtalk.google.com,DIRECT\",\n        \"DOMAIN,alt5-mtalk.google.com,DIRECT\",\n        \"DOMAIN,alt6-mtalk.google.com,DIRECT\",\n        \"DOMAIN,alt7-mtalk.google.com,DIRECT\",\n        \"DOMAIN,alt8-mtalk.google.com,DIRECT\",\n        \"DOMAIN,fairplay.l.qq.com,DIRECT\",\n        \"DOMAIN,livew.l.qq.com,DIRECT\",\n        \"DOMAIN,vd.l.qq.com,DIRECT\",\n        \"DOMAIN,analytics.strava.com,DIRECT\",\n        \"DOMAIN,msg.umeng.com,DIRECT\",\n        \"DOMAIN,msg.umengcloud.com,DIRECT\",\n        \"PROCESS-NAME,com.ximalaya.ting.himalaya,节点选择\",\n        \"DOMAIN-SUFFIX,himalaya.com,节点选择\",\n        \"PROCESS-NAME,deezer.android.app,节点选择\",\n        \"DOMAIN-SUFFIX,deezer.com,节点选择\",\n        \"DOMAIN-SUFFIX,dzcdn.net,节点选择\",\n        \"PROCESS-NAME,com.tencent.ibg.joox,节点选择\",\n        \"PROCESS-NAME,com.tencent.ibg.jooxtv,节点选择\",\n        \"DOMAIN-SUFFIX,joox.com,节点选择\",\n        \"DOMAIN-KEYWORD,jooxweb-api,节点选择\",\n        \"PROCESS-NAME,com.skysoft.kkbox.android,节点选择\",\n        \"DOMAIN-SUFFIX,kkbox.com,节点选择\",\n        \"DOMAIN-SUFFIX,kkbox.com.tw,节点选择\",\n        \"DOMAIN-SUFFIX,kfs.io,节点选择\",\n        \"PROCESS-NAME,com.pandora.android,节点选择\",\n        \"DOMAIN-SUFFIX,pandora.com,节点选择\",\n        \"PROCESS-NAME,com.soundcloud.android,节点选择\",\n        \"DOMAIN-SUFFIX,p-cdn.us,节点选择\",\n        \"DOMAIN-SUFFIX,sndcdn.com,节点选择\",\n        \"DOMAIN-SUFFIX,soundcloud.com,节点选择\",\n        \"PROCESS-NAME,com.spotify.music,节点选择\",\n        \"DOMAIN-SUFFIX,pscdn.co,节点选择\",\n        \"DOMAIN-SUFFIX,scdn.co,节点选择\",\n        \"DOMAIN-SUFFIX,spotify.com,节点选择\",\n        \"DOMAIN-SUFFIX,spoti.fi,节点选择\",\n        \"DOMAIN-KEYWORD,spotify.com,节点选择\",\n        \"DOMAIN-KEYWORD,-spotify-com,节点选择\",\n        \"PROCESS-NAME,com.aspiro.tidal,节点选择\",\n        \"DOMAIN-SUFFIX,tidal.com,节点选择\",\n        \"PROCESS-NAME,com.google.android.apps.youtube.music,节点选择\",\n        \"PROCESS-NAME,com.google.android.youtube.tvmusic,节点选择\",\n        \"PROCESS-NAME,tv.abema,节点选择\",\n        \"DOMAIN-SUFFIX,abema.io,节点选择\",\n        \"DOMAIN-SUFFIX,abema.tv,节点选择\",\n        \"DOMAIN-SUFFIX,ameba.jp,节点选择\",\n        \"DOMAIN-SUFFIX,hayabusa.io,节点选择\",\n        \"DOMAIN-KEYWORD,abematv.akamaized.net,节点选择\",\n        \"PROCESS-NAME,com.channel4.ondemand,节点选择\",\n        \"DOMAIN-SUFFIX,c4assets.com,节点选择\",\n        \"DOMAIN-SUFFIX,channel4.com,节点选择\",\n        \"PROCESS-NAME,com.amazon.avod.thirdp,节点选择\",\n        \"DOMAIN-SUFFIX,aiv-cdn.net,节点选择\",\n        \"DOMAIN-SUFFIX,aiv-delivery.net,节点选择\",\n        \"DOMAIN-SUFFIX,amazonvideo.com,节点选择\",\n        \"DOMAIN-SUFFIX,primevideo.com,节点选择\",\n        \"DOMAIN-SUFFIX,media-amazon.com,节点选择\",\n        \"DOMAIN,atv-ps.amazon.com,节点选择\",\n        \"DOMAIN,fls-na.amazon.com,节点选择\",\n        \"DOMAIN,avodmp4s3ww-a.akamaihd.net,节点选择\",\n        \"DOMAIN,d25xi40x97liuc.cloudfront.net,节点选择\",\n        \"DOMAIN,dmqdd6hw24ucf.cloudfront.net,节点选择\",\n        \"DOMAIN,dmqdd6hw24ucf.cloudfront.net,节点选择\",\n        \"DOMAIN,d22qjgkvxw22r6.cloudfront.net,节点选择\",\n        \"DOMAIN,d1v5ir2lpwr8os.cloudfront.net,节点选择\",\n        \"DOMAIN,d27xxe7juh1us6.cloudfront.net,节点选择\",\n        \"DOMAIN-KEYWORD,avoddashs,节点选择\",\n        \"DOMAIN,linear.tv.apple.com,节点选择\",\n        \"DOMAIN,play-edge.itunes.apple.com,节点选择\",\n        \"PROCESS-NAME,tw.com.gamer.android.animad,节点选择\",\n        \"DOMAIN-SUFFIX,bahamut.com.tw,节点选择\",\n        \"DOMAIN-SUFFIX,gamer.com.tw,节点选择\",\n        \"DOMAIN,gamer-cds.cdn.hinet.net,节点选择\",\n        \"DOMAIN,gamer2-cds.cdn.hinet.net,节点选择\",\n        \"PROCESS-NAME,bbc.iplayer.android,节点选择\",\n        \"DOMAIN-SUFFIX,bbc.co.uk,节点选择\",\n        \"DOMAIN-SUFFIX,bbci.co.uk,节点选择\",\n        \"DOMAIN-KEYWORD,bbcfmt,节点选择\",\n        \"DOMAIN-KEYWORD,uk-live,节点选择\",\n        \"PROCESS-NAME,com.dazn,节点选择\",\n        \"DOMAIN-SUFFIX,dazn.com,节点选择\",\n        \"DOMAIN-SUFFIX,dazn-api.com,节点选择\",\n        \"DOMAIN,d151l6v8er5bdm.cloudfront.net,节点选择\",\n        \"DOMAIN-KEYWORD,voddazn,节点选择\",\n        \"PROCESS-NAME,com.disney.disneyplus,节点选择\",\n        \"DOMAIN-SUFFIX,bamgrid.com,节点选择\",\n        \"DOMAIN-SUFFIX,disneyplus.com,节点选择\",\n        \"DOMAIN-SUFFIX,disney-plus.net,节点选择\",\n        \"DOMAIN-SUFFIX,disney自动选择.com,节点选择\",\n        \"DOMAIN-SUFFIX,dssott.com,节点选择\",\n        \"DOMAIN,cdn.registerdisney.go.com,节点选择\",\n        \"PROCESS-NAME,com.dmm.app.movieplayer,节点选择\",\n        \"DOMAIN-SUFFIX,dmm.co.jp,节点选择\",\n        \"DOMAIN-SUFFIX,dmm.com,节点选择\",\n        \"DOMAIN-SUFFIX,dmm-extension.com,节点选择\",\n        \"PROCESS-NAME,com.tvbusa.encore,节点选择\",\n        \"DOMAIN-SUFFIX,encoretvb.com,节点选择\",\n        \"DOMAIN,edge.api.brightcove.com,节点选择\",\n        \"DOMAIN,bcbolt446c5271-a.akamaihd.net,节点选择\",\n        \"PROCESS-NAME,com.fox.now,节点选择\",\n        \"DOMAIN-SUFFIX,fox.com,节点选择\",\n        \"DOMAIN-SUFFIX,foxdcg.com,节点选择\",\n        \"DOMAIN-SUFFIX,theplatform.com,节点选择\",\n        \"DOMAIN-SUFFIX,uplynk.com,节点选择\",\n        \"DOMAIN-SUFFIX,foxplus.com,节点选择\",\n        \"DOMAIN,cdn-fox-networks-group-green.akamaized.net,节点选择\",\n        \"DOMAIN,d3cv4a9a9wh0bt.cloudfront.net,节点选择\",\n        \"DOMAIN,foxsports01-i.akamaihd.net,节点选择\",\n        \"DOMAIN,foxsports02-i.akamaihd.net,节点选择\",\n        \"DOMAIN,foxsports03-i.akamaihd.net,节点选择\",\n        \"DOMAIN,staticasiafox.akamaized.net,节点选择\",\n        \"PROCESS-NAME,com.hbo.hbonow,节点选择\",\n        \"DOMAIN-SUFFIX,hbo.com,节点选择\",\n        \"DOMAIN-SUFFIX,hbogo.com,节点选择\",\n        \"DOMAIN-SUFFIX,hbonow.com,节点选择\",\n        \"DOMAIN-SUFFIX,hbomax.com,节点选择\",\n        \"PROCESS-NAME,hk.hbo.hbogo,节点选择\",\n        \"DOMAIN-SUFFIX,hbogoasia.com,节点选择\",\n        \"DOMAIN-SUFFIX,hbogoasia.hk,节点选择\",\n        \"DOMAIN,bcbolthboa-a.akamaihd.net,节点选择\",\n        \"DOMAIN,players.brightcove.net,节点选择\",\n        \"DOMAIN,s3-ap-southeast-1.amazonaws.com,节点选择\",\n        \"DOMAIN,dai3fd1oh325y.cloudfront.net,节点选择\",\n        \"DOMAIN,44wilhpljf.execute-api.ap-southeast-1.amazonaws.com,节点选择\",\n        \"DOMAIN,hboasia1-i.akamaihd.net,节点选择\",\n        \"DOMAIN,hboasia2-i.akamaihd.net,节点选择\",\n        \"DOMAIN,hboasia3-i.akamaihd.net,节点选择\",\n        \"DOMAIN,hboasia4-i.akamaihd.net,节点选择\",\n        \"DOMAIN,hboasia5-i.akamaihd.net,节点选择\",\n        \"DOMAIN,cf-images.ap-southeast-1.prod.boltdns.net,节点选择\",\n        \"DOMAIN-SUFFIX,5itv.tv,节点选择\",\n        \"DOMAIN-SUFFIX,ocnttv.com,节点选择\",\n        \"PROCESS-NAME,com.hulu.plus,节点选择\",\n        \"DOMAIN-SUFFIX,hulu.com,节点选择\",\n        \"DOMAIN-SUFFIX,huluim.com,节点选择\",\n        \"DOMAIN-SUFFIX,hulustream.com,节点选择\",\n        \"PROCESS-NAME,jp.happyon.android,节点选择\",\n        \"DOMAIN-SUFFIX,happyon.jp,节点选择\",\n        \"DOMAIN-SUFFIX,hjholdings.jp,节点选择\",\n        \"DOMAIN-SUFFIX,hulu.jp,节点选择\",\n        \"PROCESS-NAME,air.ITVMobilePlayer,节点选择\",\n        \"DOMAIN-SUFFIX,itv.com,节点选择\",\n        \"DOMAIN-SUFFIX,itvstatic.com,节点选择\",\n        \"DOMAIN,itvpnpmobile-a.akamaihd.net,节点选择\",\n        \"PROCESS-NAME,com.kktv.kktv,节点选择\",\n        \"DOMAIN-SUFFIX,kktv.com.tw,节点选择\",\n        \"DOMAIN-SUFFIX,kktv.me,节点选择\",\n        \"DOMAIN,kktv-theater.kk.stream,节点选择\",\n        \"PROCESS-NAME,com.linecorp.linetv,节点选择\",\n        \"DOMAIN-SUFFIX,linetv.tw,节点选择\",\n        \"DOMAIN,d3c7rimkq79yfu.cloudfront.net,节点选择\",\n        \"PROCESS-NAME,com.litv.mobile.gp.litv,节点选择\",\n        \"DOMAIN-SUFFIX,litv.tv,节点选择\",\n        \"DOMAIN,litvfreemobile-hichannel.cdn.hinet.net,节点选择\",\n        \"PROCESS-NAME,com.mobileiq.demand5,节点选择\",\n        \"DOMAIN-SUFFIX,channel5.com,节点选择\",\n        \"DOMAIN-SUFFIX,my5.tv,节点选择\",\n        \"DOMAIN,d349g9zuie06uo.cloudfront.net,节点选择\",\n        \"PROCESS-NAME,com.tvb.mytvsuper,节点选择\",\n        \"DOMAIN-SUFFIX,mytvsuper.com,节点选择\",\n        \"DOMAIN-SUFFIX,tvb.com,节点选择\",\n        \"PROCESS-NAME,com.netflix.mediaclient,节点选择\",\n        \"DOMAIN-SUFFIX,netflix.com,节点选择\",\n        \"DOMAIN-SUFFIX,netflix.net,节点选择\",\n        \"DOMAIN-SUFFIX,nflxext.com,节点选择\",\n        \"DOMAIN-SUFFIX,nflximg.com,节点选择\",\n        \"DOMAIN-SUFFIX,nflximg.net,节点选择\",\n        \"DOMAIN-SUFFIX,nflxso.net,节点选择\",\n        \"DOMAIN-SUFFIX,nflxvideo.net,节点选择\",\n        \"DOMAIN-SUFFIX,netflixdnstest0.com,节点选择\",\n        \"DOMAIN-SUFFIX,netflixdnstest1.com,节点选择\",\n        \"DOMAIN-SUFFIX,netflixdnstest2.com,节点选择\",\n        \"DOMAIN-SUFFIX,netflixdnstest3.com,节点选择\",\n        \"DOMAIN-SUFFIX,netflixdnstest4.com,节点选择\",\n        \"DOMAIN-SUFFIX,netflixdnstest5.com,节点选择\",\n        \"DOMAIN-SUFFIX,netflixdnstest6.com,节点选择\",\n        \"DOMAIN-SUFFIX,netflixdnstest7.com,节点选择\",\n        \"DOMAIN-SUFFIX,netflixdnstest8.com,节点选择\",\n        \"DOMAIN-SUFFIX,netflixdnstest9.com,节点选择\",\n        \"DOMAIN-KEYWORD,dualstack.api自动选择-device-prod-nlb-,节点选择\",\n        \"DOMAIN-KEYWORD,dualstack.ichnaea-web-,节点选择\",\n        \"IP-CIDR,23.246.0.0/18,节点选择,no-resolve\",\n        \"IP-CIDR,37.77.184.0/21,节点选择,no-resolve\",\n        \"IP-CIDR,45.57.0.0/17,节点选择,no-resolve\",\n        \"IP-CIDR,64.120.128.0/17,节点选择,no-resolve\",\n        \"IP-CIDR,66.197.128.0/17,节点选择,no-resolve\",\n        \"IP-CIDR,108.175.32.0/20,节点选择,no-resolve\",\n        \"IP-CIDR,192.173.64.0/18,节点选择,no-resolve\",\n        \"IP-CIDR,198.38.96.0/19,节点选择,no-resolve\",\n        \"IP-CIDR,198.45.48.0/20,节点选择,no-resolve\",\n        \"IP-CIDR,34.210.42.111/32,节点选择,no-resolve\",\n        \"IP-CIDR,52.89.124.203/32,节点选择,no-resolve\",\n        \"IP-CIDR,54.148.37.5/32,节点选择,no-resolve\",\n        \"PROCESS-NAME,jp.nicovideo.android,节点选择\",\n        \"DOMAIN-SUFFIX,dmc.nico,节点选择\",\n        \"DOMAIN-SUFFIX,nicovideo.jp,节点选择\",\n        \"DOMAIN-SUFFIX,nimg.jp,节点选择\",\n        \"PROCESS-NAME,com.pccw.nowemobile,节点选择\",\n        \"DOMAIN-SUFFIX,nowe.com,节点选择\",\n        \"DOMAIN-SUFFIX,nowestatic.com,节点选择\",\n        \"PROCESS-NAME,com.pbs.video,节点选择\",\n        \"DOMAIN-SUFFIX,pbs.org,节点选择\",\n        \"DOMAIN-SUFFIX,phncdn.com,节点选择\",\n        \"DOMAIN-SUFFIX,phprcdn.com,节点选择\",\n        \"DOMAIN-SUFFIX,pornhub.com,节点选择\",\n        \"DOMAIN-SUFFIX,pornhubpremium.com,节点选择\",\n        \"PROCESS-NAME,com.twgood.android,节点选择\",\n        \"DOMAIN-SUFFIX,skyking.com.tw,节点选择\",\n        \"DOMAIN,hamifans.emome.net,节点选择\",\n        \"PROCESS-NAME,com.ss.android.ugc.trill,节点选择\",\n        \"DOMAIN-SUFFIX,byteoversea.com,节点选择\",\n        \"DOMAIN-SUFFIX,ibytedtos.com,节点选择\",\n        \"DOMAIN-SUFFIX,muscdn.com,节点选择\",\n        \"DOMAIN-SUFFIX,musical.ly,节点选择\",\n        \"DOMAIN-SUFFIX,tiktok.com,节点选择\",\n        \"DOMAIN-SUFFIX,tik-tokapi.com,节点选择\",\n        \"DOMAIN-SUFFIX,tiktokcdn.com,节点选择\",\n        \"DOMAIN-SUFFIX,tiktokv.com,节点选择\",\n        \"DOMAIN-KEYWORD,-tiktokcdn-com,节点选择\",\n        \"PROCESS-NAME,tv.twitch.android.app,节点选择\",\n        \"DOMAIN-SUFFIX,jtvnw.net,节点选择\",\n        \"DOMAIN-SUFFIX,ttvnw.net,节点选择\",\n        \"DOMAIN-SUFFIX,twitch.tv,节点选择\",\n        \"DOMAIN-SUFFIX,twitchcdn.net,节点选择\",\n        \"PROCESS-NAME,com.hktve.viutv,节点选择\",\n        \"DOMAIN-SUFFIX,viu.com,节点选择\",\n        \"DOMAIN-SUFFIX,viu.tv,节点选择\",\n        \"DOMAIN,api.viu.now.com,节点选择\",\n        \"DOMAIN,d1k2us671qcoau.cloudfront.net,节点选择\",\n        \"DOMAIN,d2anahhhmp1ffz.cloudfront.net,节点选择\",\n        \"DOMAIN,dfp6rglgjqszk.cloudfront.net,节点选择\",\n        \"PROCESS-NAME,com.google.android.youtube,节点选择\",\n        \"PROCESS-NAME,com.google.android.youtube.tv,节点选择\",\n        \"DOMAIN-SUFFIX,googlevideo.com,节点选择\",\n        \"DOMAIN-SUFFIX,youtube.com,节点选择\",\n        \"DOMAIN,youtubei.googleapis.com,节点选择\",\n        \"DOMAIN-SUFFIX,biliapi.net,节点选择\",\n        \"DOMAIN-SUFFIX,bilibili.com,节点选择\",\n        \"DOMAIN,upos-hz-mirrorakam.akamaized.net,节点选择\",\n        \"DOMAIN-SUFFIX,iq.com,节点选择\",\n        \"DOMAIN,cache.video.iqiyi.com,节点选择\",\n        \"DOMAIN,cache-video.iq.com,节点选择\",\n        \"DOMAIN,intl.iqiyi.com,节点选择\",\n        \"DOMAIN,intl-rcd.iqiyi.com,节点选择\",\n        \"DOMAIN,intl-subscription.iqiyi.com,节点选择\",\n        \"DOMAIN-KEYWORD,oversea-tw.inter.iqiyi.com,节点选择\",\n        \"DOMAIN-KEYWORD,oversea-tw.inter.ptqy.gitv.tv,节点选择\",\n        \"IP-CIDR,103.44.56.0/22,节点选择,no-resolve\",\n        \"IP-CIDR,118.26.32.0/23,节点选择,no-resolve\",\n        \"IP-CIDR,118.26.120.0/24,节点选择,no-resolve\",\n        \"IP-CIDR,223.119.62.225/28,节点选择,no-resolve\",\n        \"IP-CIDR,23.40.242.10/32,节点选择,no-resolve\",\n        \"IP-CIDR,23.40.241.251/32,节点选择,no-resolve\",\n        \"DOMAIN-SUFFIX,api.mgtv.com,节点选择\",\n        \"DOMAIN-SUFFIX,wetv.vip,节点选择\",\n        \"DOMAIN-SUFFIX,wetvinfo.com,节点选择\",\n        \"DOMAIN,testflight.apple.com,节点选择\",\n        \"DOMAIN-SUFFIX,appspot.com,节点选择\",\n        \"DOMAIN-SUFFIX,blogger.com,节点选择\",\n        \"DOMAIN-SUFFIX,getoutline.org,节点选择\",\n        \"DOMAIN-SUFFIX,gvt0.com,节点选择\",\n        \"DOMAIN-SUFFIX,gvt3.com,节点选择\",\n        \"DOMAIN-SUFFIX,xn--ngstr-lra8j.com,节点选择\",\n        \"DOMAIN-SUFFIX,ytimg.com,节点选择\",\n        \"DOMAIN-KEYWORD,google,节点选择\",\n        \"DOMAIN-KEYWORD,.blogspot.,节点选择\",\n        \"DOMAIN-SUFFIX,aka.ms,节点选择\",\n        \"DOMAIN-SUFFIX,onedrive.live.com,节点选择\",\n        \"DOMAIN,az416426.vo.msecnd.net,节点选择\",\n        \"DOMAIN,az668014.vo.msecnd.net,节点选择\",\n        \"DOMAIN-SUFFIX,cdninstagram.com,节点选择\",\n        \"DOMAIN-SUFFIX,facebook.com,节点选择\",\n        \"DOMAIN-SUFFIX,facebook.net,节点选择\",\n        \"DOMAIN-SUFFIX,fb.com,节点选择\",\n        \"DOMAIN-SUFFIX,fb.me,节点选择\",\n        \"DOMAIN-SUFFIX,fbaddins.com,节点选择\",\n        \"DOMAIN-SUFFIX,fbcdn.net,节点选择\",\n        \"DOMAIN-SUFFIX,fbsbx.com,节点选择\",\n        \"DOMAIN-SUFFIX,fbworkmail.com,节点选择\",\n        \"DOMAIN-SUFFIX,instagram.com,节点选择\",\n        \"DOMAIN-SUFFIX,m.me,节点选择\",\n        \"DOMAIN-SUFFIX,messenger.com,节点选择\",\n        \"DOMAIN-SUFFIX,oculus.com,节点选择\",\n        \"DOMAIN-SUFFIX,oculuscdn.com,节点选择\",\n        \"DOMAIN-SUFFIX,rocksdb.org,节点选择\",\n        \"DOMAIN-SUFFIX,whatsapp.com,节点选择\",\n        \"DOMAIN-SUFFIX,whatsapp.net,节点选择\",\n        \"DOMAIN-SUFFIX,pscp.tv,节点选择\",\n        \"DOMAIN-SUFFIX,periscope.tv,节点选择\",\n        \"DOMAIN-SUFFIX,t.co,节点选择\",\n        \"DOMAIN-SUFFIX,twimg.co,节点选择\",\n        \"DOMAIN-SUFFIX,twimg.com,节点选择\",\n        \"DOMAIN-SUFFIX,twitpic.com,节点选择\",\n        \"DOMAIN-SUFFIX,twitter.com,节点选择\",\n        \"DOMAIN-SUFFIX,x.com,节点选择\",\n        \"DOMAIN-SUFFIX,vine.co,节点选择\",\n        \"DOMAIN-SUFFIX,telegra.ph,节点选择\",\n        \"DOMAIN-SUFFIX,telegram.org,节点选择\",\n        \"IP-CIDR,91.108.4.0/22,节点选择,no-resolve\",\n        \"IP-CIDR,91.108.8.0/22,节点选择,no-resolve\",\n        \"IP-CIDR,91.108.12.0/22,节点选择,no-resolve\",\n        \"IP-CIDR,91.108.16.0/22,节点选择,no-resolve\",\n        \"IP-CIDR,91.108.20.0/22,节点选择,no-resolve\",\n        \"IP-CIDR,91.108.56.0/22,节点选择,no-resolve\",\n        \"IP-CIDR,149.154.160.0/20,节点选择,no-resolve\",\n        \"IP-CIDR,2001:b28:f23d::/48,节点选择,no-resolve\",\n        \"IP-CIDR,2001:b28:f23f::/48,节点选择,no-resolve\",\n        \"IP-CIDR,2001:67c:4e8::/48,节点选择,no-resolve\",\n        \"DOMAIN-SUFFIX,line.me,节点选择\",\n        \"DOMAIN-SUFFIX,line-apps.com,节点选择\",\n        \"DOMAIN-SUFFIX,line-scdn.net,节点选择\",\n        \"DOMAIN-SUFFIX,naver.jp,节点选择\",\n        \"IP-CIDR,103.2.30.0/23,节点选择,no-resolve\",\n        \"IP-CIDR,125.209.208.0/20,节点选择,no-resolve\",\n        \"IP-CIDR,147.92.128.0/17,节点选择,no-resolve\",\n        \"IP-CIDR,203.104.144.0/21,节点选择,no-resolve\",\n        \"DOMAIN-SUFFIX,amazon.co.jp,节点选择\",\n        \"DOMAIN,d3c33hcgiwev3.cloudfront.net,节点选择\",\n        \"DOMAIN,payments-jp.amazon.com,节点选择\",\n        \"DOMAIN,s3-ap-northeast-1.amazonaws.com,节点选择\",\n        \"DOMAIN,s3-ap-southeast-2.amazonaws.com,节点选择\",\n        \"DOMAIN,a248.e.akamai.net,节点选择\",\n        \"DOMAIN,a771.dscq.akamai.net,节点选择\",\n        \"DOMAIN-SUFFIX,4shared.com,节点选择\",\n        \"DOMAIN-SUFFIX,9cache.com,节点选择\",\n        \"DOMAIN-SUFFIX,9gag.com,节点选择\",\n        \"DOMAIN-SUFFIX,abc.com,节点选择\",\n        \"DOMAIN-SUFFIX,abc.net.au,节点选择\",\n        \"DOMAIN-SUFFIX,abebooks.com,节点选择\",\n        \"DOMAIN-SUFFIX,ao3.org,节点选择\",\n        \"DOMAIN-SUFFIX,apigee.com,节点选择\",\n        \"DOMAIN-SUFFIX,apkcombo.com,节点选择\",\n        \"DOMAIN-SUFFIX,apk-dl.com,节点选择\",\n        \"DOMAIN-SUFFIX,apkfind.com,节点选择\",\n        \"DOMAIN-SUFFIX,apkmirror.com,节点选择\",\n        \"DOMAIN-SUFFIX,apkmonk.com,节点选择\",\n        \"DOMAIN-SUFFIX,apkpure.com,节点选择\",\n        \"DOMAIN-SUFFIX,aptoide.com,节点选择\",\n        \"DOMAIN-SUFFIX,archive.is,节点选择\",\n        \"DOMAIN-SUFFIX,archive.org,节点选择\",\n        \"DOMAIN-SUFFIX,archiveofourown.com,节点选择\",\n        \"DOMAIN-SUFFIX,archiveofourown.org,节点选择\",\n        \"DOMAIN-SUFFIX,arte.tv,节点选择\",\n        \"DOMAIN-SUFFIX,artstation.com,节点选择\",\n        \"DOMAIN-SUFFIX,arukas.io,节点选择\",\n        \"DOMAIN-SUFFIX,ask.com,节点选择\",\n        \"DOMAIN-SUFFIX,avg.com,节点选择\",\n        \"DOMAIN-SUFFIX,avgle.com,节点选择\",\n        \"DOMAIN-SUFFIX,badoo.com,节点选择\",\n        \"DOMAIN-SUFFIX,bandwagonhost.com,节点选择\",\n        \"DOMAIN-SUFFIX,bangkokpost.com,节点选择\",\n        \"DOMAIN-SUFFIX,bbc.com,节点选择\",\n        \"DOMAIN-SUFFIX,behance.net,节点选择\",\n        \"DOMAIN-SUFFIX,bibox.com,节点选择\",\n        \"DOMAIN-SUFFIX,biggo.com.tw,节点选择\",\n        \"DOMAIN-SUFFIX,binance.com,节点选择\",\n        \"DOMAIN-SUFFIX,bit.ly,节点选择\",\n        \"DOMAIN-SUFFIX,bitcointalk.org,节点选择\",\n        \"DOMAIN-SUFFIX,bitfinex.com,节点选择\",\n        \"DOMAIN-SUFFIX,bitmex.com,节点选择\",\n        \"DOMAIN-SUFFIX,bit-z.com,节点选择\",\n        \"DOMAIN-SUFFIX,bloglovin.com,节点选择\",\n        \"DOMAIN-SUFFIX,bloomberg.cn,节点选择\",\n        \"DOMAIN-SUFFIX,bloomberg.com,节点选择\",\n        \"DOMAIN-SUFFIX,blubrry.com,节点选择\",\n        \"DOMAIN-SUFFIX,book.com.tw,节点选择\",\n        \"DOMAIN-SUFFIX,booklive.jp,节点选择\",\n        \"DOMAIN-SUFFIX,books.com.tw,节点选择\",\n        \"DOMAIN-SUFFIX,boslife.net,节点选择\",\n        \"DOMAIN-SUFFIX,box.com,节点选择\",\n        \"DOMAIN-SUFFIX,brave.com,节点选择\",\n        \"DOMAIN-SUFFIX,businessinsider.com,节点选择\",\n        \"DOMAIN-SUFFIX,buzzfeed.com,节点选择\",\n        \"DOMAIN-SUFFIX,bwh1.net,节点选择\",\n        \"DOMAIN-SUFFIX,castbox.fm,节点选择\",\n        \"DOMAIN-SUFFIX,cbc.ca,节点选择\",\n        \"DOMAIN-SUFFIX,cdw.com,节点选择\",\n        \"DOMAIN-SUFFIX,change.org,节点选择\",\n        \"DOMAIN-SUFFIX,channelnewsasia.com,节点选择\",\n        \"DOMAIN-SUFFIX,ck101.com,节点选择\",\n        \"DOMAIN-SUFFIX,clarionproject.org,节点选择\",\n        \"DOMAIN-SUFFIX,cloudcone.com,节点选择\",\n        \"DOMAIN-SUFFIX,clyp.it,节点选择\",\n        \"DOMAIN-SUFFIX,cna.com.tw,节点选择\",\n        \"DOMAIN-SUFFIX,comparitech.com,节点选择\",\n        \"DOMAIN-SUFFIX,conoha.jp,节点选择\",\n        \"DOMAIN-SUFFIX,crucial.com,节点选择\",\n        \"DOMAIN-SUFFIX,cts.com.tw,节点选择\",\n        \"DOMAIN-SUFFIX,cw.com.tw,节点选择\",\n        \"DOMAIN-SUFFIX,cyberctm.com,节点选择\",\n        \"DOMAIN-SUFFIX,dailymotion.com,节点选择\",\n        \"DOMAIN-SUFFIX,dailyview.tw,节点选择\",\n        \"DOMAIN-SUFFIX,daum.net,节点选择\",\n        \"DOMAIN-SUFFIX,daumcdn.net,节点选择\",\n        \"DOMAIN-SUFFIX,dcard.tw,节点选择\",\n        \"DOMAIN-SUFFIX,deadline.com,节点选择\",\n        \"DOMAIN-SUFFIX,deepdiscount.com,节点选择\",\n        \"DOMAIN-SUFFIX,depositphotos.com,节点选择\",\n        \"DOMAIN-SUFFIX,deviantart.com,节点选择\",\n        \"DOMAIN-SUFFIX,disconnect.me,节点选择\",\n        \"DOMAIN-SUFFIX,discordapp.com,节点选择\",\n        \"DOMAIN-SUFFIX,discordapp.net,节点选择\",\n        \"DOMAIN-SUFFIX,disqus.com,节点选择\",\n        \"DOMAIN-SUFFIX,dlercloud.com,节点选择\",\n        \"DOMAIN-SUFFIX,dmhy.org,节点选择\",\n        \"DOMAIN-SUFFIX,dns2go.com,节点选择\",\n        \"DOMAIN-SUFFIX,dowjones.com,节点选择\",\n        \"DOMAIN-SUFFIX,dropbox.com,节点选择\",\n        \"DOMAIN-SUFFIX,dropboxapi.com,节点选择\",\n        \"DOMAIN-SUFFIX,dropboxusercontent.com,节点选择\",\n        \"DOMAIN-SUFFIX,duckduckgo.com,节点选择\",\n        \"DOMAIN-SUFFIX,duyaoss.com,节点选择\",\n        \"DOMAIN-SUFFIX,dw.com,节点选择\",\n        \"DOMAIN-SUFFIX,dynu.com,节点选择\",\n        \"DOMAIN-SUFFIX,earthcam.com,节点选择\",\n        \"DOMAIN-SUFFIX,ebookservice.tw,节点选择\",\n        \"DOMAIN-SUFFIX,economist.com,节点选择\",\n        \"DOMAIN-SUFFIX,edgecastcdn.net,节点选择\",\n        \"DOMAIN-SUFFIX,edx-cdn.org,节点选择\",\n        \"DOMAIN-SUFFIX,elpais.com,节点选择\",\n        \"DOMAIN-SUFFIX,enanyang.my,节点选择\",\n        \"DOMAIN-SUFFIX,encyclopedia.com,节点选择\",\n        \"DOMAIN-SUFFIX,esoir.be,节点选择\",\n        \"DOMAIN-SUFFIX,etherscan.io,节点选择\",\n        \"DOMAIN-SUFFIX,euronews.com,节点选择\",\n        \"DOMAIN-SUFFIX,evozi.com,节点选择\",\n        \"DOMAIN-SUFFIX,exblog.jp,节点选择\",\n        \"DOMAIN-SUFFIX,feeder.co,节点选择\",\n        \"DOMAIN-SUFFIX,feedly.com,节点选择\",\n        \"DOMAIN-SUFFIX,feedx.net,节点选择\",\n        \"DOMAIN-SUFFIX,firech.at,节点选择\",\n        \"DOMAIN-SUFFIX,flickr.com,节点选择\",\n        \"DOMAIN-SUFFIX,flipboard.com,节点选择\",\n        \"DOMAIN-SUFFIX,flitto.com,节点选择\",\n        \"DOMAIN-SUFFIX,foreignpolicy.com,节点选择\",\n        \"DOMAIN-SUFFIX,fortawesome.com,节点选择\",\n        \"DOMAIN-SUFFIX,freetls.fastly.net,节点选择\",\n        \"DOMAIN-SUFFIX,friday.tw,节点选择\",\n        \"DOMAIN-SUFFIX,ft.com,节点选择\",\n        \"DOMAIN-SUFFIX,ftchinese.com,节点选择\",\n        \"DOMAIN-SUFFIX,ftimg.net,节点选择\",\n        \"DOMAIN-SUFFIX,gate.io,节点选择\",\n        \"DOMAIN-SUFFIX,genius.com,节点选择\",\n        \"DOMAIN-SUFFIX,getlantern.org,节点选择\",\n        \"DOMAIN-SUFFIX,getsync.com,节点选择\",\n        \"DOMAIN-SUFFIX,github.com,节点选择\",\n        \"DOMAIN-SUFFIX,github.io,节点选择\",\n        \"DOMAIN-SUFFIX,githubusercontent.com,节点选择\",\n        \"DOMAIN-SUFFIX,globalvoices.org,节点选择\",\n        \"DOMAIN-SUFFIX,goo.ne.jp,节点选择\",\n        \"DOMAIN-SUFFIX,goodreads.com,节点选择\",\n        \"DOMAIN-SUFFIX,gov.tw,节点选择\",\n        \"DOMAIN-SUFFIX,greatfire.org,节点选择\",\n        \"DOMAIN-SUFFIX,gumroad.com,节点选择\",\n        \"DOMAIN-SUFFIX,hbg.com,节点选择\",\n        \"DOMAIN-SUFFIX,heroku.com,节点选择\",\n        \"DOMAIN-SUFFIX,hightail.com,节点选择\",\n        \"DOMAIN-SUFFIX,hk01.com,节点选择\",\n        \"DOMAIN-SUFFIX,hkbf.org,节点选择\",\n        \"DOMAIN-SUFFIX,hkbookcity.com,节点选择\",\n        \"DOMAIN-SUFFIX,hkej.com,节点选择\",\n        \"DOMAIN-SUFFIX,hket.com,节点选择\",\n        \"DOMAIN-SUFFIX,hootsuite.com,节点选择\",\n        \"DOMAIN-SUFFIX,hudson.org,节点选择\",\n        \"DOMAIN-SUFFIX,huffpost.com,节点选择\",\n        \"DOMAIN-SUFFIX,hyread.com.tw,节点选择\",\n        \"DOMAIN-SUFFIX,ibtimes.com,节点选择\",\n        \"DOMAIN-SUFFIX,i-cable.com,节点选择\",\n        \"DOMAIN-SUFFIX,icij.org,节点选择\",\n        \"DOMAIN-SUFFIX,icoco.com,节点选择\",\n        \"DOMAIN-SUFFIX,imgur.com,节点选择\",\n        \"DOMAIN-SUFFIX,independent.co.uk,节点选择\",\n        \"DOMAIN-SUFFIX,initiummall.com,节点选择\",\n        \"DOMAIN-SUFFIX,inoreader.com,节点选择\",\n        \"DOMAIN-SUFFIX,insecam.org,节点选择\",\n        \"DOMAIN-SUFFIX,ipfs.io,节点选择\",\n        \"DOMAIN-SUFFIX,issuu.com,节点选择\",\n        \"DOMAIN-SUFFIX,istockphoto.com,节点选择\",\n        \"DOMAIN-SUFFIX,japantimes.co.jp,节点选择\",\n        \"DOMAIN-SUFFIX,jiji.com,节点选择\",\n        \"DOMAIN-SUFFIX,jinx.com,节点选择\",\n        \"DOMAIN-SUFFIX,jkforum.net,节点选择\",\n        \"DOMAIN-SUFFIX,joinmastodon.org,节点选择\",\n        \"DOMAIN-SUFFIX,justmysocks.net,节点选择\",\n        \"DOMAIN-SUFFIX,justpaste.it,节点选择\",\n        \"DOMAIN-SUFFIX,kadokawa.co.jp,节点选择\",\n        \"DOMAIN-SUFFIX,kakao.com,节点选择\",\n        \"DOMAIN-SUFFIX,kakaocorp.com,节点选择\",\n        \"DOMAIN-SUFFIX,kik.com,节点选择\",\n        \"DOMAIN-SUFFIX,kingkong.com.tw,节点选择\",\n        \"DOMAIN-SUFFIX,knowyourmeme.com,节点选择\",\n        \"DOMAIN-SUFFIX,kobo.com,节点选择\",\n        \"DOMAIN-SUFFIX,kobobooks.com,节点选择\",\n        \"DOMAIN-SUFFIX,kodingen.com,节点选择\",\n        \"DOMAIN-SUFFIX,lemonde.fr,节点选择\",\n        \"DOMAIN-SUFFIX,lepoint.fr,节点选择\",\n        \"DOMAIN-SUFFIX,lihkg.com,节点选择\",\n        \"DOMAIN-SUFFIX,linkedin.com,节点选择\",\n        \"DOMAIN-SUFFIX,limbopro.xyz,节点选择\",\n        \"DOMAIN-SUFFIX,listennotes.com,节点选择\",\n        \"DOMAIN-SUFFIX,livestream.com,节点选择\",\n        \"DOMAIN-SUFFIX,logimg.jp,节点选择\",\n        \"DOMAIN-SUFFIX,logmein.com,节点选择\",\n        \"DOMAIN-SUFFIX,mail.ru,节点选择\",\n        \"DOMAIN-SUFFIX,mailchimp.com,节点选择\",\n        \"DOMAIN-SUFFIX,marc.info,节点选择\",\n        \"DOMAIN-SUFFIX,matters.news,节点选择\",\n        \"DOMAIN-SUFFIX,maying.co,节点选择\",\n        \"DOMAIN-SUFFIX,medium.com,节点选择\",\n        \"DOMAIN-SUFFIX,mega.nz,节点选择\",\n        \"DOMAIN-SUFFIX,mergersandinquisitions.com,节点选择\",\n        \"DOMAIN-SUFFIX,mingpao.com,节点选择\",\n        \"DOMAIN-SUFFIX,mixi.jp,节点选择\",\n        \"DOMAIN-SUFFIX,mobile01.com,节点选择\",\n        \"DOMAIN-SUFFIX,mubi.com,节点选择\",\n        \"DOMAIN-SUFFIX,myspace.com,节点选择\",\n        \"DOMAIN-SUFFIX,myspacecdn.com,节点选择\",\n        \"DOMAIN-SUFFIX,nanyang.com,节点选择\",\n        \"DOMAIN-SUFFIX,nationalinterest.org,节点选择\",\n        \"DOMAIN-SUFFIX,naver.com,节点选择\",\n        \"DOMAIN-SUFFIX,nbcnews.com,节点选择\",\n        \"DOMAIN-SUFFIX,ndr.de,节点选择\",\n        \"DOMAIN-SUFFIX,neowin.net,节点选择\",\n        \"DOMAIN-SUFFIX,newstapa.org,节点选择\",\n        \"DOMAIN-SUFFIX,nexitally.com,节点选择\",\n        \"DOMAIN-SUFFIX,nhk.or.jp,节点选择\",\n        \"DOMAIN-SUFFIX,nii.ac.jp,节点选择\",\n        \"DOMAIN-SUFFIX,nikkei.com,节点选择\",\n        \"DOMAIN-SUFFIX,nitter.net,节点选择\",\n        \"DOMAIN-SUFFIX,nofile.io,节点选择\",\n        \"DOMAIN-SUFFIX,notion.so,节点选择\",\n        \"DOMAIN-SUFFIX,now.com,节点选择\",\n        \"DOMAIN-SUFFIX,nrk.no,节点选择\",\n        \"DOMAIN-SUFFIX,nuget.org,节点选择\",\n        \"DOMAIN-SUFFIX,nyaa.si,节点选择\",\n        \"DOMAIN-SUFFIX,nyt.com,节点选择\",\n        \"DOMAIN-SUFFIX,nytchina.com,节点选择\",\n        \"DOMAIN-SUFFIX,nytcn.me,节点选择\",\n        \"DOMAIN-SUFFIX,nytco.com,节点选择\",\n        \"DOMAIN-SUFFIX,nytimes.com,节点选择\",\n        \"DOMAIN-SUFFIX,nytimg.com,节点选择\",\n        \"DOMAIN-SUFFIX,nytlog.com,节点选择\",\n        \"DOMAIN-SUFFIX,nytstyle.com,节点选择\",\n        \"DOMAIN-SUFFIX,ok.ru,节点选择\",\n        \"DOMAIN-SUFFIX,okex.com,节点选择\",\n        \"DOMAIN-SUFFIX,on.cc,节点选择\",\n        \"DOMAIN-SUFFIX,orientaldaily.com.my,节点选择\",\n        \"DOMAIN-SUFFIX,overcast.fm,节点选择\",\n        \"DOMAIN-SUFFIX,paltalk.com,节点选择\",\n        \"DOMAIN-SUFFIX,parsevideo.com,节点选择\",\n        \"DOMAIN-SUFFIX,pawoo.net,节点选择\",\n        \"DOMAIN-SUFFIX,pbxes.com,节点选择\",\n        \"DOMAIN-SUFFIX,pcdvd.com.tw,节点选择\",\n        \"DOMAIN-SUFFIX,pchome.com.tw,节点选择\",\n        \"DOMAIN-SUFFIX,pcloud.com,节点选择\",\n        \"DOMAIN-SUFFIX,peing.net,节点选择\",\n        \"DOMAIN-SUFFIX,picacomic.com,节点选择\",\n        \"DOMAIN-SUFFIX,pinimg.com,节点选择\",\n        \"DOMAIN-SUFFIX,pixiv.net,节点选择\",\n        \"DOMAIN-SUFFIX,player.fm,节点选择\",\n        \"DOMAIN-SUFFIX,plurk.com,节点选择\",\n        \"DOMAIN-SUFFIX,po18.tw,节点选择\",\n        \"DOMAIN-SUFFIX,potato.im,节点选择\",\n        \"DOMAIN-SUFFIX,potatso.com,节点选择\",\n        \"DOMAIN-SUFFIX,prism-break.org,节点选择\",\n        \"DOMAIN-SUFFIX,proxifier.com,节点选择\",\n        \"DOMAIN-SUFFIX,pt.im,节点选择\",\n        \"DOMAIN-SUFFIX,pts.org.tw,节点选择\",\n        \"DOMAIN-SUFFIX,pubu.com.tw,节点选择\",\n        \"DOMAIN-SUFFIX,pubu.tw,节点选择\",\n        \"DOMAIN-SUFFIX,pureapk.com,节点选择\",\n        \"DOMAIN-SUFFIX,quora.com,节点选择\",\n        \"DOMAIN-SUFFIX,quoracdn.net,节点选择\",\n        \"DOMAIN-SUFFIX,qz.com,节点选择\",\n        \"DOMAIN-SUFFIX,radio.garden,节点选择\",\n        \"DOMAIN-SUFFIX,rakuten.co.jp,节点选择\",\n        \"DOMAIN-SUFFIX,rarbgprx.org,节点选择\",\n        \"DOMAIN-SUFFIX,reabble.com,节点选择\",\n        \"DOMAIN-SUFFIX,readingtimes.com.tw,节点选择\",\n        \"DOMAIN-SUFFIX,readmoo.com,节点选择\",\n        \"DOMAIN-SUFFIX,redbubble.com,节点选择\",\n        \"DOMAIN-SUFFIX,redd.it,节点选择\",\n        \"DOMAIN-SUFFIX,reddit.com,节点选择\",\n        \"DOMAIN-SUFFIX,redditmedia.com,节点选择\",\n        \"DOMAIN-SUFFIX,resilio.com,节点选择\",\n        \"DOMAIN-SUFFIX,reuters.com,节点选择\",\n        \"DOMAIN-SUFFIX,reutersmedia.net,节点选择\",\n        \"DOMAIN-SUFFIX,rfi.fr,节点选择\",\n        \"DOMAIN-SUFFIX,rixcloud.com,节点选择\",\n        \"DOMAIN-SUFFIX,roadshow.hk,节点选择\",\n        \"DOMAIN-SUFFIX,rsshub.app,节点选择\",\n        \"DOMAIN-SUFFIX,scmp.com,节点选择\",\n        \"DOMAIN-SUFFIX,scribd.com,节点选择\",\n        \"DOMAIN-SUFFIX,seatguru.com,节点选择\",\n        \"DOMAIN-SUFFIX,shadowsocks.org,节点选择\",\n        \"DOMAIN-SUFFIX,shindanmaker.com,节点选择\",\n        \"DOMAIN-SUFFIX,shopee.tw,节点选择\",\n        \"DOMAIN-SUFFIX,sina.com.hk,节点选择\",\n        \"DOMAIN-SUFFIX,slideshare.net,节点选择\",\n        \"DOMAIN-SUFFIX,softfamous.com,节点选择\",\n        \"DOMAIN-SUFFIX,spiegel.de,节点选择\",\n        \"DOMAIN-SUFFIX,ssrcloud.org,节点选择\",\n        \"DOMAIN-SUFFIX,startpage.com,节点选择\",\n        \"DOMAIN-SUFFIX,steamcommunity.com,节点选择\",\n        \"DOMAIN-SUFFIX,steemit.com,节点选择\",\n        \"DOMAIN-SUFFIX,steemitwallet.com,节点选择\",\n        \"DOMAIN-SUFFIX,straitstimes.com,节点选择\",\n        \"DOMAIN-SUFFIX,streamable.com,节点选择\",\n        \"DOMAIN-SUFFIX,streema.com,节点选择\",\n        \"DOMAIN-SUFFIX,t66y.com,节点选择\",\n        \"DOMAIN-SUFFIX,tapatalk.com,节点选择\",\n        \"DOMAIN-SUFFIX,teco-hk.org,节点选择\",\n        \"DOMAIN-SUFFIX,teco-mo.org,节点选择\",\n        \"DOMAIN-SUFFIX,teddysun.com,节点选择\",\n        \"DOMAIN-SUFFIX,textnow.me,节点选择\",\n        \"DOMAIN-SUFFIX,theguardian.com,节点选择\",\n        \"DOMAIN-SUFFIX,theinitium.com,节点选择\",\n        \"DOMAIN-SUFFIX,themoviedb.org,节点选择\",\n        \"DOMAIN-SUFFIX,thetvdb.com,节点选择\",\n        \"DOMAIN-SUFFIX,time.com,节点选择\",\n        \"DOMAIN-SUFFIX,tineye.com,节点选择\",\n        \"DOMAIN-SUFFIX,tiny.cc,节点选择\",\n        \"DOMAIN-SUFFIX,tinyurl.com,节点选择\",\n        \"DOMAIN-SUFFIX,torproject.org,节点选择\",\n        \"DOMAIN-SUFFIX,tumblr.com,节点选择\",\n        \"DOMAIN-SUFFIX,turbobit.net,节点选择\",\n        \"DOMAIN-SUFFIX,tutanota.com,节点选择\",\n        \"DOMAIN-SUFFIX,tvboxnow.com,节点选择\",\n        \"DOMAIN-SUFFIX,udn.com,节点选择\",\n        \"DOMAIN-SUFFIX,unseen.is,节点选择\",\n        \"DOMAIN-SUFFIX,upmedia.mg,节点选择\",\n        \"DOMAIN-SUFFIX,uptodown.com,节点选择\",\n        \"DOMAIN-SUFFIX,urbandictionary.com,节点选择\",\n        \"DOMAIN-SUFFIX,ustream.tv,节点选择\",\n        \"DOMAIN-SUFFIX,uwants.com,节点选择\",\n        \"DOMAIN-SUFFIX,v2fly.org,节点选择\",\n        \"DOMAIN-SUFFIX,v2ray.com,节点选择\",\n        \"DOMAIN-SUFFIX,viber.com,节点选择\",\n        \"DOMAIN-SUFFIX,videopress.com,节点选择\",\n        \"DOMAIN-SUFFIX,vimeo.com,节点选择\",\n        \"DOMAIN-SUFFIX,voachinese.com,节点选择\",\n        \"DOMAIN-SUFFIX,voanews.com,节点选择\",\n        \"DOMAIN-SUFFIX,voxer.com,节点选择\",\n        \"DOMAIN-SUFFIX,vzw.com,节点选择\",\n        \"DOMAIN-SUFFIX,w3schools.com,节点选择\",\n        \"DOMAIN-SUFFIX,washingtonpost.com,节点选择\",\n        \"DOMAIN-SUFFIX,wattpad.com,节点选择\",\n        \"DOMAIN-SUFFIX,whoer.net,节点选择\",\n        \"DOMAIN-SUFFIX,wikileaks.org,节点选择\",\n        \"DOMAIN-SUFFIX,wikimapia.org,节点选择\",\n        \"DOMAIN-SUFFIX,wikimedia.org,节点选择\",\n        \"DOMAIN-SUFFIX,wikinews.org,节点选择\",\n        \"DOMAIN-SUFFIX,wikipedia.org,节点选择\",\n        \"DOMAIN-SUFFIX,wikiquote.org,节点选择\",\n        \"DOMAIN-SUFFIX,wikiwand.com,节点选择\",\n        \"DOMAIN-SUFFIX,winudf.com,节点选择\",\n        \"DOMAIN-SUFFIX,wire.com,节点选择\",\n        \"DOMAIN-SUFFIX,wn.com,节点选择\",\n        \"DOMAIN-SUFFIX,wordpress.com,节点选择\",\n        \"DOMAIN-SUFFIX,workflow.is,节点选择\",\n        \"DOMAIN-SUFFIX,worldcat.org,节点选择\",\n        \"DOMAIN-SUFFIX,wsj.com,节点选择\",\n        \"DOMAIN-SUFFIX,wsj.net,节点选择\",\n        \"DOMAIN-SUFFIX,xhamster.com,节点选择\",\n        \"DOMAIN-SUFFIX,xn--90wwvt03e.com,节点选择\",\n        \"DOMAIN-SUFFIX,xn--i2ru8q2qg.com,节点选择\",\n        \"DOMAIN-SUFFIX,xnxx.com,节点选择\",\n        \"DOMAIN-SUFFIX,xvideos.com,节点选择\",\n        \"DOMAIN-SUFFIX,yahoo.com,节点选择\",\n        \"DOMAIN-SUFFIX,yandex.ru,节点选择\",\n        \"DOMAIN-SUFFIX,ycombinator.com,节点选择\",\n        \"DOMAIN-SUFFIX,yesasia.com,节点选择\",\n        \"DOMAIN-SUFFIX,yes-news.com,节点选择\",\n        \"DOMAIN-SUFFIX,yomiuri.co.jp,节点选择\",\n        \"DOMAIN-SUFFIX,you-get.org,节点选择\",\n        \"DOMAIN-SUFFIX,zaobao.com,节点选择\",\n        \"DOMAIN-SUFFIX,zb.com,节点选择\",\n        \"DOMAIN-SUFFIX,zello.com,节点选择\",\n        \"DOMAIN-SUFFIX,zeronet.io,节点选择\",\n        \"DOMAIN-SUFFIX,zoom.us,节点选择\",\n        \"DOMAIN,cc.tvbs.com.tw,节点选择\",\n        \"DOMAIN,ocsp.int-x3.letsencrypt.org,节点选择\",\n        \"DOMAIN,search.avira.com,节点选择\",\n        \"DOMAIN,us.weibo.com,节点选择\",\n        \"DOMAIN-KEYWORD,.pinterest.,节点选择\",\n        \"DOMAIN-SUFFIX,edu,节点选择\",\n        \"DOMAIN-SUFFIX,gov,节点选择\",\n        \"DOMAIN-SUFFIX,mil,节点选择\",\n        \"DOMAIN-SUFFIX,google,节点选择\",\n        \"DOMAIN-SUFFIX,abc.xyz,节点选择\",\n        \"DOMAIN-SUFFIX,advertisercommunity.com,节点选择\",\n        \"DOMAIN-SUFFIX,ampproject.org,节点选择\",\n        \"DOMAIN-SUFFIX,android.com,节点选择\",\n        \"DOMAIN-SUFFIX,androidify.com,节点选择\",\n        \"DOMAIN-SUFFIX,autodraw.com,节点选择\",\n        \"DOMAIN-SUFFIX,capitalg.com,节点选择\",\n        \"DOMAIN-SUFFIX,certificate-transparency.org,节点选择\",\n        \"DOMAIN-SUFFIX,chrome.com,节点选择\",\n        \"DOMAIN-SUFFIX,chromeexperiments.com,节点选择\",\n        \"DOMAIN-SUFFIX,chromestatus.com,节点选择\",\n        \"DOMAIN-SUFFIX,chromium.org,节点选择\",\n        \"DOMAIN-SUFFIX,creativelab5.com,节点选择\",\n        \"DOMAIN-SUFFIX,debug.com,节点选择\",\n        \"DOMAIN-SUFFIX,deepmind.com,节点选择\",\n        \"DOMAIN-SUFFIX,dialogflow.com,节点选择\",\n        \"DOMAIN-SUFFIX,firebaseio.com,节点选择\",\n        \"DOMAIN-SUFFIX,getmdl.io,节点选择\",\n        \"DOMAIN-SUFFIX,ggpht.com,节点选择\",\n        \"DOMAIN-SUFFIX,googleapis.cn,节点选择\",\n        \"DOMAIN-SUFFIX,gmail.com,节点选择\",\n        \"DOMAIN-SUFFIX,gmodules.com,节点选择\",\n        \"DOMAIN-SUFFIX,godoc.org,节点选择\",\n        \"DOMAIN-SUFFIX,golang.org,节点选择\",\n        \"DOMAIN-SUFFIX,gstatic.com,节点选择\",\n        \"DOMAIN-SUFFIX,gv.com,节点选择\",\n        \"DOMAIN-SUFFIX,gwtproject.org,节点选择\",\n        \"DOMAIN-SUFFIX,itasoftware.com,节点选择\",\n        \"DOMAIN-SUFFIX,madewithcode.com,节点选择\",\n        \"DOMAIN-SUFFIX,material.io,节点选择\",\n        \"DOMAIN-SUFFIX,page.link,节点选择\",\n        \"DOMAIN-SUFFIX,polymer-project.org,节点选择\",\n        \"DOMAIN-SUFFIX,recaptcha.net,节点选择\",\n        \"DOMAIN-SUFFIX,shattered.io,节点选择\",\n        \"DOMAIN-SUFFIX,synergyse.com,节点选择\",\n        \"DOMAIN-SUFFIX,telephony.goog,节点选择\",\n        \"DOMAIN-SUFFIX,tensorflow.org,节点选择\",\n        \"DOMAIN-SUFFIX,tfhub.dev,节点选择\",\n        \"DOMAIN-SUFFIX,tiltbrush.com,节点选择\",\n        \"DOMAIN-SUFFIX,waveprotocol.org,节点选择\",\n        \"DOMAIN-SUFFIX,waymo.com,节点选择\",\n        \"DOMAIN-SUFFIX,webmproject.org,节点选择\",\n        \"DOMAIN-SUFFIX,webrtc.org,节点选择\",\n        \"DOMAIN-SUFFIX,whatbrowser.org,节点选择\",\n        \"DOMAIN-SUFFIX,widevine.com,节点选择\",\n        \"DOMAIN-SUFFIX,x.company,节点选择\",\n        \"DOMAIN-SUFFIX,youtu.be,节点选择\",\n        \"DOMAIN-SUFFIX,yt.be,节点选择\",\n        \"DOMAIN-SUFFIX,ytimg.com,节点选择\",\n        \"DOMAIN-SUFFIX,t.me,节点选择\",\n        \"DOMAIN-SUFFIX,tdesktop.com,节点选择\",\n        \"DOMAIN-SUFFIX,telegram.me,节点选择\",\n        \"DOMAIN-SUFFIX,telesco.pe,节点选择\",\n        \"DOMAIN-KEYWORD,.facebook.,节点选择\",\n        \"DOMAIN-SUFFIX,facebookmail.com,节点选择\",\n        \"DOMAIN-SUFFIX,noxinfluencer.com,节点选择\",\n        \"DOMAIN-SUFFIX,smartmailcloud.com,节点选择\",\n        \"DOMAIN-SUFFIX,weebly.com,节点选择\",\n        \"DOMAIN-SUFFIX,twitter.jp,节点选择\",\n        \"DOMAIN-SUFFIX,appsto.re,节点选择\",\n        \"DOMAIN,books.itunes.apple.com,节点选择\",\n        \"DOMAIN,apps.apple.com,节点选择\",\n        \"DOMAIN,itunes.apple.com,节点选择\",\n        \"DOMAIN,api-glb-sea.smoot.apple.com,节点选择\",\n        \"DOMAIN-SUFFIX,smoot.apple.com,节点选择\",\n        \"DOMAIN,lookup-api.apple.com,节点选择\",\n        \"DOMAIN,beta.music.apple.com,节点选择\",\n        \"DOMAIN-SUFFIX,bing.com,节点选择\",\n        \"DOMAIN-SUFFIX,cccat.io,节点选择\",\n        \"DOMAIN-SUFFIX,dubox.com,节点选择\",\n        \"DOMAIN-SUFFIX,duboxcdn.com,节点选择\",\n        \"DOMAIN-SUFFIX,ifixit.com,节点选择\",\n        \"DOMAIN-SUFFIX,mangakakalot.com,节点选择\",\n        \"DOMAIN-SUFFIX,shopeemobile.com,节点选择\",\n        \"DOMAIN-SUFFIX,cloudcone.com.cn,节点选择\",\n        \"DOMAIN-SUFFIX,inkbunny.net,节点选择\",\n        \"DOMAIN-SUFFIX,metapix.net,节点选择\",\n        \"DOMAIN-SUFFIX,s3.amazonaws.com,节点选择\",\n        \"DOMAIN-SUFFIX,zaobao.com.sg,节点选择\",\n        \"DOMAIN,international-gfe.download.nvidia.com,节点选择\",\n        \"DOMAIN,ocsp.apple.com,节点选择\",\n        \"DOMAIN,store-images.s-microsoft.com,节点选择\",\n        \"DOMAIN-SUFFIX,qhres.com,DIRECT\",\n        \"DOMAIN-SUFFIX,qhimg.com,DIRECT\",\n        \"DOMAIN-SUFFIX,alibaba.com,DIRECT\",\n        \"DOMAIN-SUFFIX,alibabausercontent.com,DIRECT\",\n        \"DOMAIN-SUFFIX,alicdn.com,DIRECT\",\n        \"DOMAIN-SUFFIX,alikunlun.com,DIRECT\",\n        \"DOMAIN-SUFFIX,alipay.com,DIRECT\",\n        \"DOMAIN-SUFFIX,amap.com,DIRECT\",\n        \"DOMAIN-SUFFIX,autonavi.com,DIRECT\",\n        \"DOMAIN-SUFFIX,dingtalk.com,DIRECT\",\n        \"DOMAIN-SUFFIX,mxhichina.com,DIRECT\",\n        \"DOMAIN-SUFFIX,soku.com,DIRECT\",\n        \"DOMAIN-SUFFIX,taobao.com,DIRECT\",\n        \"DOMAIN-SUFFIX,tmall.com,DIRECT\",\n        \"DOMAIN-SUFFIX,tmall.hk,DIRECT\",\n        \"DOMAIN-SUFFIX,ykimg.com,DIRECT\",\n        \"DOMAIN-SUFFIX,youku.com,DIRECT\",\n        \"DOMAIN-SUFFIX,xiami.com,DIRECT\",\n        \"DOMAIN-SUFFIX,xiami.net,DIRECT\",\n        \"DOMAIN-SUFFIX,aaplimg.com,DIRECT\",\n        \"DOMAIN-SUFFIX,apple.co,DIRECT\",\n        \"DOMAIN-SUFFIX,apple.com,DIRECT\",\n        \"DOMAIN-SUFFIX,apple-cloudkit.com,DIRECT\",\n        \"DOMAIN-SUFFIX,appstore.com,DIRECT\",\n        \"DOMAIN-SUFFIX,cdn-apple.com,DIRECT\",\n        \"DOMAIN-SUFFIX,icloud.com,DIRECT\",\n        \"DOMAIN-SUFFIX,icloud-content.com,DIRECT\",\n        \"DOMAIN-SUFFIX,me.com,DIRECT\",\n        \"DOMAIN-SUFFIX,mzstatic.com,DIRECT\",\n        \"DOMAIN-KEYWORD,apple.com.akadns.net,DIRECT\",\n        \"DOMAIN-KEYWORD,icloud.com.akadns.net,DIRECT\",\n        \"DOMAIN-SUFFIX,baidu.com,DIRECT\",\n        \"DOMAIN-SUFFIX,baidubcr.com,DIRECT\",\n        \"DOMAIN-SUFFIX,baidupan.com,DIRECT\",\n        \"DOMAIN-SUFFIX,baidupcs.com,DIRECT\",\n        \"DOMAIN-SUFFIX,bdimg.com,DIRECT\",\n        \"DOMAIN-SUFFIX,bdstatic.com,DIRECT\",\n        \"DOMAIN-SUFFIX,yunjiasu-cdn.net,DIRECT\",\n        \"DOMAIN-SUFFIX,acgvideo.com,DIRECT\",\n        \"DOMAIN-SUFFIX,biliapi.com,DIRECT\",\n        \"DOMAIN-SUFFIX,biliapi.net,DIRECT\",\n        \"DOMAIN-SUFFIX,bilibili.com,DIRECT\",\n        \"DOMAIN-SUFFIX,bilibili.tv,DIRECT\",\n        \"DOMAIN-SUFFIX,hdslb.com,DIRECT\",\n        \"DOMAIN-SUFFIX,feiliao.com,DIRECT\",\n        \"DOMAIN-SUFFIX,pstatp.com,DIRECT\",\n        \"DOMAIN-SUFFIX,snssdk.com,DIRECT\",\n        \"DOMAIN-SUFFIX,iesdouyin.com,DIRECT\",\n        \"DOMAIN-SUFFIX,toutiao.com,DIRECT\",\n        \"DOMAIN-SUFFIX,cctv.com,DIRECT\",\n        \"DOMAIN-SUFFIX,cctvpic.com,DIRECT\",\n        \"DOMAIN-SUFFIX,livechina.com,DIRECT\",\n        \"DOMAIN-SUFFIX,didialift.com,DIRECT\",\n        \"DOMAIN-SUFFIX,didiglobal.com,DIRECT\",\n        \"DOMAIN-SUFFIX,udache.com,DIRECT\",\n        \"DOMAIN-SUFFIX,21cn.com,DIRECT\",\n        \"DOMAIN-SUFFIX,hitv.com,DIRECT\",\n        \"DOMAIN-SUFFIX,mgtv.com,DIRECT\",\n        \"DOMAIN-SUFFIX,iqiyi.com,DIRECT\",\n        \"DOMAIN-SUFFIX,iqiyipic.com,DIRECT\",\n        \"DOMAIN-SUFFIX,71.am,DIRECT\",\n        \"DOMAIN-SUFFIX,jd.com,DIRECT\",\n        \"DOMAIN-SUFFIX,jd.hk,DIRECT\",\n        \"DOMAIN-SUFFIX,jdpay.com,DIRECT\",\n        \"DOMAIN-SUFFIX,360buyimg.com,DIRECT\",\n        \"DOMAIN-SUFFIX,iciba.com,DIRECT\",\n        \"DOMAIN-SUFFIX,ksosoft.com,DIRECT\",\n        \"DOMAIN-SUFFIX,meitu.com,DIRECT\",\n        \"DOMAIN-SUFFIX,meitudata.com,DIRECT\",\n        \"DOMAIN-SUFFIX,meitustat.com,DIRECT\",\n        \"DOMAIN-SUFFIX,meipai.com,DIRECT\",\n        \"DOMAIN-SUFFIX,dianping.com,DIRECT\",\n        \"DOMAIN-SUFFIX,dpfile.com,DIRECT\",\n        \"DOMAIN-SUFFIX,meituan.com,DIRECT\",\n        \"DOMAIN-SUFFIX,meituan.net,DIRECT\",\n        \"DOMAIN-SUFFIX,duokan.com,DIRECT\",\n        \"DOMAIN-SUFFIX,mi.com,DIRECT\",\n        \"DOMAIN-SUFFIX,mi-img.com,DIRECT\",\n        \"DOMAIN-SUFFIX,miui.com,DIRECT\",\n        \"DOMAIN-SUFFIX,miwifi.com,DIRECT\",\n        \"DOMAIN-SUFFIX,xiaomi.com,DIRECT\",\n        \"DOMAIN-SUFFIX,xiaomi.net,DIRECT\",\n        \"DOMAIN-SUFFIX,hotmail.com,DIRECT\",\n        \"DOMAIN-SUFFIX,microsoft.com,DIRECT\",\n        \"DOMAIN-SUFFIX,msecnd.net,DIRECT\",\n        \"DOMAIN-SUFFIX,office365.com,DIRECT\",\n        \"DOMAIN-SUFFIX,outlook.com,DIRECT\",\n        \"DOMAIN-SUFFIX,s-microsoft.com,DIRECT\",\n        \"DOMAIN-SUFFIX,visualstudio.com,DIRECT\",\n        \"DOMAIN-SUFFIX,windows.com,DIRECT\",\n        \"DOMAIN-SUFFIX,windowsupdate.com,DIRECT\",\n        \"DOMAIN-SUFFIX,163.com,DIRECT\",\n        \"DOMAIN-SUFFIX,126.com,DIRECT\",\n        \"DOMAIN-SUFFIX,126.net,DIRECT\",\n        \"DOMAIN-SUFFIX,127.net,DIRECT\",\n        \"DOMAIN-SUFFIX,163yun.com,DIRECT\",\n        \"DOMAIN-SUFFIX,lofter.com,DIRECT\",\n        \"DOMAIN-SUFFIX,netease.com,DIRECT\",\n        \"DOMAIN-SUFFIX,ydstatic.com,DIRECT\",\n        \"DOMAIN-SUFFIX,paypal.com,DIRECT\",\n        \"DOMAIN-SUFFIX,paypal.me,DIRECT\",\n        \"DOMAIN-SUFFIX,paypalobjects.com,DIRECT\",\n        \"DOMAIN-SUFFIX,sina.com,DIRECT\",\n        \"DOMAIN-SUFFIX,weibo.com,DIRECT\",\n        \"DOMAIN-SUFFIX,weibocdn.com,DIRECT\",\n        \"DOMAIN-SUFFIX,sohu.com,DIRECT\",\n        \"DOMAIN-SUFFIX,sohucs.com,DIRECT\",\n        \"DOMAIN-SUFFIX,sohu-inc.com,DIRECT\",\n        \"DOMAIN-SUFFIX,v-56.com,DIRECT\",\n        \"DOMAIN-SUFFIX,sogo.com,DIRECT\",\n        \"DOMAIN-SUFFIX,sogou.com,DIRECT\",\n        \"DOMAIN-SUFFIX,sogoucdn.com,DIRECT\",\n        \"DOMAIN-SUFFIX,steamcontent.com,DIRECT\",\n        \"DOMAIN-SUFFIX,steampowered.com,DIRECT\",\n        \"DOMAIN-SUFFIX,steamstatic.com,DIRECT\",\n        \"DOMAIN-SUFFIX,gtimg.com,DIRECT\",\n        \"DOMAIN-SUFFIX,idqqimg.com,DIRECT\",\n        \"DOMAIN-SUFFIX,igamecj.com,DIRECT\",\n        \"DOMAIN-SUFFIX,myapp.com,DIRECT\",\n        \"DOMAIN-SUFFIX,myqcloud.com,DIRECT\",\n        \"DOMAIN-SUFFIX,qq.com,DIRECT\",\n        \"DOMAIN-SUFFIX,qqmail.com,DIRECT\",\n        \"DOMAIN-SUFFIX,servicewechat.com,DIRECT\",\n        \"DOMAIN-SUFFIX,tencent.com,DIRECT\",\n        \"DOMAIN-SUFFIX,tencent-cloud.net,DIRECT\",\n        \"DOMAIN-SUFFIX,tenpay.com,DIRECT\",\n        \"DOMAIN-SUFFIX,wechat.com,DIRECT\",\n        \"DOMAIN,file-igamecj.akamaized.net,DIRECT\",\n        \"DOMAIN-SUFFIX,ccgslb.com,DIRECT\",\n        \"DOMAIN-SUFFIX,ccgslb.net,DIRECT\",\n        \"DOMAIN-SUFFIX,chinanetcenter.com,DIRECT\",\n        \"DOMAIN-SUFFIX,meixincdn.com,DIRECT\",\n        \"DOMAIN-SUFFIX,ourdvs.com,DIRECT\",\n        \"DOMAIN-SUFFIX,staticdn.net,DIRECT\",\n        \"DOMAIN-SUFFIX,wangsu.com,DIRECT\",\n        \"DOMAIN-SUFFIX,ipip.net,DIRECT\",\n        \"DOMAIN-SUFFIX,ip.la,DIRECT\",\n        \"DOMAIN-SUFFIX,ip.sb,DIRECT\",\n        \"DOMAIN-SUFFIX,ip-cdn.com,DIRECT\",\n        \"DOMAIN-SUFFIX,ipv6-test.com,DIRECT\",\n        \"DOMAIN-SUFFIX,myip.la,DIRECT\",\n        \"DOMAIN-SUFFIX,test-ipv6.com,DIRECT\",\n        \"DOMAIN-SUFFIX,whatismyip.com,DIRECT\",\n        \"DOMAIN,ip.istatmenus.app,DIRECT\",\n        \"DOMAIN,sms.imagetasks.com,DIRECT\",\n        \"DOMAIN-SUFFIX,netspeedtestmaster.com,DIRECT\",\n        \"DOMAIN,speedtest.macpaw.com,DIRECT\",\n        \"DOMAIN-SUFFIX,acg.rip,DIRECT\",\n        \"DOMAIN-SUFFIX,animebytes.tv,DIRECT\",\n        \"DOMAIN-SUFFIX,awesome-hd.me,DIRECT\",\n        \"DOMAIN-SUFFIX,broadcasthe.net,DIRECT\",\n        \"DOMAIN-SUFFIX,chdbits.co,DIRECT\",\n        \"DOMAIN-SUFFIX,classix-unlimited.co.uk,DIRECT\",\n        \"DOMAIN-SUFFIX,comicat.org,DIRECT\",\n        \"DOMAIN-SUFFIX,empornium.me,DIRECT\",\n        \"DOMAIN-SUFFIX,gazellegames.net,DIRECT\",\n        \"DOMAIN-SUFFIX,hdbits.org,DIRECT\",\n        \"DOMAIN-SUFFIX,hdchina.org,DIRECT\",\n        \"DOMAIN-SUFFIX,hddolby.com,DIRECT\",\n        \"DOMAIN-SUFFIX,hdhome.org,DIRECT\",\n        \"DOMAIN-SUFFIX,hdsky.me,DIRECT\",\n        \"DOMAIN-SUFFIX,icetorrent.org,DIRECT\",\n        \"DOMAIN-SUFFIX,jpopsuki.eu,DIRECT\",\n        \"DOMAIN-SUFFIX,keepfrds.com,DIRECT\",\n        \"DOMAIN-SUFFIX,madsrevolution.net,DIRECT\",\n        \"DOMAIN-SUFFIX,morethan.tv,DIRECT\",\n        \"DOMAIN-SUFFIX,m-team.cc,DIRECT\",\n        \"DOMAIN-SUFFIX,myanonamouse.net,DIRECT\",\n        \"DOMAIN-SUFFIX,nanyangpt.com,DIRECT\",\n        \"DOMAIN-SUFFIX,ncore.cc,DIRECT\",\n        \"DOMAIN-SUFFIX,open.cd,DIRECT\",\n        \"DOMAIN-SUFFIX,ourbits.club,DIRECT\",\n        \"DOMAIN-SUFFIX,passthepopcorn.me,DIRECT\",\n        \"DOMAIN-SUFFIX,privatehd.to,DIRECT\",\n        \"DOMAIN-SUFFIX,pterclub.com,DIRECT\",\n        \"DOMAIN-SUFFIX,redacted.ch,DIRECT\",\n        \"DOMAIN-SUFFIX,springsunday.net,DIRECT\",\n        \"DOMAIN-SUFFIX,tjupt.org,DIRECT\",\n        \"DOMAIN-SUFFIX,totheglory.im,DIRECT\",\n        \"DOMAIN-SUFFIX,cn,DIRECT\",\n        \"DOMAIN-SUFFIX,115.com,DIRECT\",\n        \"DOMAIN-SUFFIX,360in.com,DIRECT\",\n        \"DOMAIN-SUFFIX,51ym.me,DIRECT\",\n        \"DOMAIN-SUFFIX,8686c.com,DIRECT\",\n        \"DOMAIN-SUFFIX,99.com,DIRECT\",\n        \"DOMAIN-SUFFIX,abchina.com,DIRECT\",\n        \"DOMAIN-SUFFIX,accuweather.com,DIRECT\",\n        \"DOMAIN-SUFFIX,aicoinstorge.com,DIRECT\",\n        \"DOMAIN-SUFFIX,air-matters.com,DIRECT\",\n        \"DOMAIN-SUFFIX,air-matters.io,DIRECT\",\n        \"DOMAIN-SUFFIX,aixifan.com,DIRECT\",\n        \"DOMAIN-SUFFIX,amd.com,DIRECT\",\n        \"DOMAIN-SUFFIX,b612.net,DIRECT\",\n        \"DOMAIN-SUFFIX,bdatu.com,DIRECT\",\n        \"DOMAIN-SUFFIX,beitaichufang.com,DIRECT\",\n        \"DOMAIN-SUFFIX,booking.com,DIRECT\",\n        \"DOMAIN-SUFFIX,bstatic.com,DIRECT\",\n        \"DOMAIN-SUFFIX,cailianpress.com,DIRECT\",\n        \"DOMAIN-SUFFIX,camera360.com,DIRECT\",\n        \"DOMAIN-SUFFIX,chaoxing.com,DIRECT\",\n        \"DOMAIN-SUFFIX,chaoxing.com,DIRECT\",\n        \"DOMAIN-SUFFIX,chinaso.com,DIRECT\",\n        \"DOMAIN-SUFFIX,chuimg.com,DIRECT\",\n        \"DOMAIN-SUFFIX,chunyu.mobi,DIRECT\",\n        \"DOMAIN-SUFFIX,cmbchina.com,DIRECT\",\n        \"DOMAIN-SUFFIX,cmbimg.com,DIRECT\",\n        \"DOMAIN-SUFFIX,ctrip.com,DIRECT\",\n        \"DOMAIN-SUFFIX,dfcfw.com,DIRECT\",\n        \"DOMAIN-SUFFIX,dji.net,DIRECT\",\n        \"DOMAIN-SUFFIX,docschina.org,DIRECT\",\n        \"DOMAIN-SUFFIX,douban.com,DIRECT\",\n        \"DOMAIN-SUFFIX,doubanio.com,DIRECT\",\n        \"DOMAIN-SUFFIX,douyu.com,DIRECT\",\n        \"DOMAIN-SUFFIX,dxycdn.com,DIRECT\",\n        \"DOMAIN-SUFFIX,dytt8.net,DIRECT\",\n        \"DOMAIN-SUFFIX,eastmoney.com,DIRECT\",\n        \"DOMAIN-SUFFIX,eudic.net,DIRECT\",\n        \"DOMAIN-SUFFIX,feng.com,DIRECT\",\n        \"DOMAIN-SUFFIX,fengkongcloud.com,DIRECT\",\n        \"DOMAIN-SUFFIX,frdic.com,DIRECT\",\n        \"DOMAIN-SUFFIX,futu5.com,DIRECT\",\n        \"DOMAIN-SUFFIX,futunn.com,DIRECT\",\n        \"DOMAIN-SUFFIX,gandi.net,DIRECT\",\n        \"DOMAIN-SUFFIX,gcores.com,DIRECT\",\n        \"DOMAIN-SUFFIX,geilicdn.com,DIRECT\",\n        \"DOMAIN-SUFFIX,getpricetag.com,DIRECT\",\n        \"DOMAIN-SUFFIX,gifshow.com,DIRECT\",\n        \"DOMAIN-SUFFIX,godic.net,DIRECT\",\n        \"DOMAIN-SUFFIX,hicloud.com,DIRECT\",\n        \"DOMAIN-SUFFIX,hongxiu.com,DIRECT\",\n        \"DOMAIN-SUFFIX,hostbuf.com,DIRECT\",\n        \"DOMAIN-SUFFIX,huxiucdn.com,DIRECT\",\n        \"DOMAIN-SUFFIX,huya.com,DIRECT\",\n        \"DOMAIN-SUFFIX,ibm.com,DIRECT\",\n        \"DOMAIN-SUFFIX,infinitynewtab.com,DIRECT\",\n        \"DOMAIN-SUFFIX,ithome.com,DIRECT\",\n        \"DOMAIN-SUFFIX,java.com,DIRECT\",\n        \"DOMAIN-SUFFIX,jianguoyun.com,DIRECT\",\n        \"DOMAIN-SUFFIX,jianshu.com,DIRECT\",\n        \"DOMAIN-SUFFIX,jianshu.io,DIRECT\",\n        \"DOMAIN-SUFFIX,jidian.im,DIRECT\",\n        \"DOMAIN-SUFFIX,kaiyanapp.com,DIRECT\",\n        \"DOMAIN-SUFFIX,kaspersky-labs.com,DIRECT\",\n        \"DOMAIN-SUFFIX,keepcdn.com,DIRECT\",\n        \"DOMAIN-SUFFIX,kkmh.com,DIRECT\",\n        \"DOMAIN-SUFFIX,lanzous.com,DIRECT\",\n        \"DOMAIN-SUFFIX,licdn.com,DIRECT\",\n        \"DOMAIN-SUFFIX,luojilab.com,DIRECT\",\n        \"DOMAIN-SUFFIX,maoyan.com,DIRECT\",\n        \"DOMAIN-SUFFIX,maoyun.tv,DIRECT\",\n        \"DOMAIN-SUFFIX,mls-cdn.com,DIRECT\",\n        \"DOMAIN-SUFFIX,mobike.com,DIRECT\",\n        \"DOMAIN-SUFFIX,moke.com,DIRECT\",\n        \"DOMAIN-SUFFIX,mubu.com,DIRECT\",\n        \"DOMAIN-SUFFIX,myzaker.com,DIRECT\",\n        \"DOMAIN-SUFFIX,nim-lang-cn.org,DIRECT\",\n        \"DOMAIN-SUFFIX,nvidia.com,DIRECT\",\n        \"DOMAIN-SUFFIX,oracle.com,DIRECT\",\n        \"DOMAIN-SUFFIX,originlab.com,DIRECT\",\n        \"DOMAIN-SUFFIX,qdaily.com,DIRECT\",\n        \"DOMAIN-SUFFIX,qidian.com,DIRECT\",\n        \"DOMAIN-SUFFIX,qyer.com,DIRECT\",\n        \"DOMAIN-SUFFIX,qyerstatic.com,DIRECT\",\n        \"DOMAIN-SUFFIX,raychase.net,DIRECT\",\n        \"DOMAIN-SUFFIX,ronghub.com,DIRECT\",\n        \"DOMAIN-SUFFIX,ruguoapp.com,DIRECT\",\n        \"DOMAIN-SUFFIX,sankuai.com,DIRECT\",\n        \"DOMAIN-SUFFIX,scomper.me,DIRECT\",\n        \"DOMAIN-SUFFIX,seafile.com,DIRECT\",\n        \"DOMAIN-SUFFIX,sm.ms,DIRECT\",\n        \"DOMAIN-SUFFIX,smzdm.com,DIRECT\",\n        \"DOMAIN-SUFFIX,snapdrop.net,DIRECT\",\n        \"DOMAIN-SUFFIX,snwx.com,DIRECT\",\n        \"DOMAIN-SUFFIX,s-reader.com,DIRECT\",\n        \"DOMAIN-SUFFIX,sspai.com,DIRECT\",\n        \"DOMAIN-SUFFIX,subhd.tv,DIRECT\",\n        \"DOMAIN-SUFFIX,takungpao.com,DIRECT\",\n        \"DOMAIN-SUFFIX,teamviewer.com,DIRECT\",\n        \"DOMAIN-SUFFIX,tianyancha.com,DIRECT\",\n        \"DOMAIN-SUFFIX,tophub.today,DIRECT\",\n        \"DOMAIN-SUFFIX,udacity.com,DIRECT\",\n        \"DOMAIN-SUFFIX,uning.com,DIRECT\",\n        \"DOMAIN-SUFFIX,weather.com,DIRECT\",\n        \"DOMAIN-SUFFIX,weico.cc,DIRECT\",\n        \"DOMAIN-SUFFIX,weidian.com,DIRECT\",\n        \"DOMAIN-SUFFIX,xiachufang.com,DIRECT\",\n        \"DOMAIN-SUFFIX,xiaoka.tv,DIRECT\",\n        \"DOMAIN-SUFFIX,ximalaya.com,DIRECT\",\n        \"DOMAIN-SUFFIX,xinhuanet.com,DIRECT\",\n        \"DOMAIN-SUFFIX,xmcdn.com,DIRECT\",\n        \"DOMAIN-SUFFIX,yangkeduo.com,DIRECT\",\n        \"DOMAIN-SUFFIX,yizhibo.com,DIRECT\",\n        \"DOMAIN-SUFFIX,zhangzishi.cc,DIRECT\",\n        \"DOMAIN-SUFFIX,zhihu.com,DIRECT\",\n        \"DOMAIN-SUFFIX,zhihuishu.com,DIRECT\",\n        \"DOMAIN-SUFFIX,zhimg.com,DIRECT\",\n        \"DOMAIN-SUFFIX,zhuihd.com,DIRECT\",\n        \"DOMAIN,download.jetbrains.com,DIRECT\",\n        \"DOMAIN,images-cn.ssl-images-amazon.com,DIRECT\",\n        \"DOMAIN-SUFFIX,local,DIRECT\",\n        \"IP-CIDR,192.168.0.0/16,DIRECT,no-resolve\",\n        \"IP-CIDR,10.0.0.0/8,DIRECT,no-resolve\",\n        \"IP-CIDR,172.16.0.0/12,DIRECT,no-resolve\",\n        \"IP-CIDR,127.0.0.0/8,DIRECT,no-resolve\",\n        \"IP-CIDR,100.64.0.0/10,DIRECT,no-resolve\",\n        \"IP-CIDR6,::1/128,DIRECT,no-resolve\",\n        \"IP-CIDR6,fc00::/7,DIRECT,no-resolve\",\n        \"IP-CIDR6,fe80::/10,DIRECT,no-resolve\",\n        \"IP-CIDR6,fd00::/8,DIRECT,no-resolve\",\n        \"GEOIP,CN,DIRECT\",\n        \"MATCH,节点选择\"\n    ]\n}\n\n\n# 解析 Hysteria2 链接\ndef parse_hysteria2_link(link):\n    link = link[14:]\n    parts = link.split('@')\n    uuid = parts[0]\n    server_info = parts[1].split('?')\n    server = server_info[0].split(':')[0]\n    port = int(server_info[0].split(':')[1].split('/')[0].strip())\n    query_params = urllib.parse.parse_qs(server_info[1] if len(server_info) > 1 else '')\n    insecure = '1' in query_params.get('insecure', ['0'])\n    sni = query_params.get('sni', [''])[0]\n    name = urllib.parse.unquote(link.split('#')[-1].strip())\n\n    return {\n        \"name\": f\"{name}\",\n        \"server\": server,\n        \"port\": port,\n        \"type\": \"hysteria2\",\n        \"password\": uuid,\n        \"auth\": uuid,\n        \"sni\": sni,\n        \"skip-cert-verify\": not insecure,\n        \"client-fingerprint\": \"chrome\"\n    }\n\n\n# 解析 Shadowsocks 链接\ndef parse_ss_link(link):\n    link = link[5:]\n    if \"#\" in link:\n        config_part, name = link.split('#')\n    else:\n        config_part, name = link, \"\"\n    decoded = base64.urlsafe_b64decode(config_part.split('@')[0] + '=' * (-len(config_part.split('@')[0]) % 4)).decode(\n        'utf-8')\n    method_passwd = decoded.split(':')\n    cipher, password = method_passwd if len(method_passwd) == 2 else (method_passwd[0], \"\")\n    server_info = config_part.split('@')[1]\n    server, port = server_info.split(':') if \":\" in server_info else (server_info, \"\")\n\n    return {\n        \"name\": urllib.parse.unquote(name),\n        \"type\": \"ss\",\n        \"server\": server,\n        \"port\": int(port),\n        \"cipher\": cipher,\n        \"password\": password,\n        \"udp\": True\n    }\n\n\n# 解析 Trojan 链接\ndef parse_trojan_link(link):\n    link = link[9:]\n    config_part, name = link.split('#')\n    user_info, host_info = config_part.split('@')\n    username, password = user_info.split(':') if \":\" in user_info else (\"\", user_info)\n    host, port_and_query = host_info.split(':') if \":\" in host_info else (host_info, \"\")\n    port, query = port_and_query.split('?', 1) if '?' in port_and_query else (port_and_query, \"\")\n\n    return {\n        \"name\": urllib.parse.unquote(name),\n        \"type\": \"trojan\",\n        \"server\": host,\n        \"port\": int(port),\n        \"password\": password,\n        \"sni\": urllib.parse.parse_qs(query).get(\"sni\", [\"\"])[0],\n        \"skip-cert-verify\": urllib.parse.parse_qs(query).get(\"skip-cert-verify\", [\"false\"])[0] == \"true\"\n    }\n\n\n# 解析 VLESS 链接\ndef parse_vless_link(link):\n    link = link[8:]\n    config_part, name = link.split('#')\n    user_info, host_info = config_part.split('@')\n    uuid = user_info\n    host, query = host_info.split('?', 1) if '?' in host_info else (host_info, \"\")\n    port = host.split(':')[-1] if ':' in host else \"\"\n    host = host.split(':')[0] if ':' in host else \"\"\n    return {\n        \"name\": urllib.parse.unquote(name),\n        \"type\": \"vless\",\n        \"server\": host,\n        \"port\": int(port),\n        \"uuid\": uuid,\n        \"security\": urllib.parse.parse_qs(query).get(\"security\", [\"none\"])[0],\n        \"tls\": urllib.parse.parse_qs(query).get(\"security\", [\"none\"])[0] == \"tls\",\n        \"sni\": urllib.parse.parse_qs(query).get(\"sni\", [\"\"])[0],\n        \"skip-cert-verify\": urllib.parse.parse_qs(query).get(\"skip-cert-verify\", [\"false\"])[0] == \"true\",\n        \"network\": urllib.parse.parse_qs(query).get(\"type\", [\"tcp\"])[0],\n        \"ws-opts\": {\n            \"path\": urllib.parse.parse_qs(query).get(\"path\", [\"\"])[0],\n            \"headers\": {\n                \"Host\": urllib.parse.parse_qs(query).get(\"host\", [\"\"])[0]\n            }\n        } if urllib.parse.parse_qs(query).get(\"type\", [\"tcp\"])[0] == \"ws\" else {}\n    }\n\n\n# 解析 VMESS 链接\ndef parse_vmess_link(link):\n    link = link[8:]\n    decoded_link = base64.urlsafe_b64decode(link + '=' * (-len(link) % 4)).decode(\"utf-8\")\n    vmess_info = json.loads(decoded_link)\n\n    return {\n        \"name\": urllib.parse.unquote(vmess_info.get(\"ps\", \"vmess\")),\n        \"type\": \"vmess\",\n        \"server\": vmess_info[\"add\"],\n        \"port\": int(vmess_info[\"port\"]),\n        \"uuid\": vmess_info[\"id\"],\n        \"alterId\": int(vmess_info.get(\"aid\", 0)),\n        \"cipher\": \"auto\",\n        \"network\": vmess_info.get(\"net\", \"tcp\"),\n        \"tls\": vmess_info.get(\"tls\", \"\") == \"tls\",\n        \"sni\": vmess_info.get(\"sni\", \"\"),\n        \"ws-opts\": {\n            \"path\": vmess_info.get(\"path\", \"\"),\n            \"headers\": {\n                \"Host\": vmess_info.get(\"host\", \"\")\n            }\n        } if vmess_info.get(\"net\", \"tcp\") == \"ws\" else {}\n    }\n\n\n# 解析ss订阅源\ndef parse_ss_sub(link):\n    new_links = []\n    try:\n        # 发送请求并获取内容\n        response = requests.get(link, headers=headers, verify=False, allow_redirects=True)\n        if response.status_code == 200:\n            data = response.json()\n            new_links = [{\"name\": x['remarks'], \"type\": \"ss\", \"server\": x['server'], \"port\": x['server_port'],\n                          \"cipher\": x['method'], \"password\": x['password'], \"udp\": True} for x in data]\n            return new_links\n    except requests.RequestException as e:\n        print(f\"请求错误: {e}\")\n        return new_links\n\n\ndef parse_md_link(link):\n    try:\n        # 发送请求并获取内容\n        response = requests.get(link)\n        response.raise_for_status()  # 检查请求是否成功\n        content = response.text\n        content = urllib.parse.unquote(content)\n        # 定义正则表达式模式，匹配所需的协议链接\n        pattern = r'(?:vless|vmess|trojan|hysteria2|ss):\\/\\/[^#\\s]*(?:#[^\\s]*)?'\n\n        # 使用re.findall()提取所有匹配的链接\n        matches = re.findall(pattern, content)\n        return matches\n\n    except requests.RequestException as e:\n        print(f\"请求错误: {e}\")\n        return []\n\n\n# js渲染页面\ndef js_render(url):\n    timeout = 4\n    if timeout > 15:\n        timeout = 15\n    browser_args = ['--no-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--disable-software-rasterizer',\n                    '--disable-setuid-sandbox']\n    session = HTMLSession(browser_args=browser_args)\n    r = session.get(f'{url}', headers=headers, timeout=timeout, verify=False)\n    # 等待页面加载完成，Requests-HTML 会自动等待 JavaScript 执行完成\n    r.html.render(timeout=timeout)\n    return r\n\n\n# je_render返回的text没有缩进，通过正则表达式匹配proxies下的所有代理节点\ndef match_nodes(text):\n    proxy_pattern = r\"\\{[^}]*name\\s*:\\s*['\\\"][^'\\\"]+['\\\"][^}]*server\\s*:\\s*[^,]+[^}]*\\}\"\n    nodes = re.findall(proxy_pattern, text, re.DOTALL)\n\n    # 将每个节点字符串转换为字典\n    proxies_list = []\n    for node in nodes:\n        # 使用yaml.safe_load来加载每个节点\n        node_dict = yaml.safe_load(node)\n        proxies_list.append(node_dict)\n\n    yaml_data = {\"proxies\": proxies_list}\n    return yaml_data\n\n\n# link非代理协议时(https)，请求url解析\ndef process_url(url):\n    isyaml = False\n    try:\n        # 发送GET请求\n        response = requests.get(url, headers=headers, verify=False, allow_redirects=True)\n        # 确保响应状态码为200\n        if response.status_code == 200:\n            content = response.content.decode('utf-8')\n            if 'proxies:' in content:\n                if '</pre>' in content:\n                    content = content.replace('<pre style=\"word-wrap: break-word; white-space: pre-wrap;\">',\n                                              '').replace('</pre>', '')\n                # YAML格式\n                yaml_data = yaml.safe_load(content)\n                if 'proxies' in yaml_data:\n                    isyaml = True\n                    proxies = yaml_data['proxies'] if yaml_data['proxies'] else []\n                    return proxies, isyaml\n            else:\n                # 尝试Base64解码\n                try:\n                    decoded_bytes = base64.b64decode(content)\n                    decoded_content = decoded_bytes.decode('utf-8')\n                    decoded_content = urllib.parse.unquote(decoded_content)\n                    return decoded_content.splitlines(), isyaml\n                except Exception as e:\n                    try:\n                        res = js_render(url)\n                        if 'external-controller' in res.html.text:\n                            # YAML格式\n                            try:\n                                yaml_data = yaml.safe_load(res.html.text)\n                            except Exception as e:\n                                yaml_data = match_nodes(res.html.text)\n                            finally:\n                                if 'proxies' in yaml_data:\n                                    isyaml = True\n                                    return yaml_data['proxies'], isyaml\n\n                        else:\n                            pattern = r'([A-Za-z0-9_+/\\-]+={0,2})'\n                            matches = re.findall(pattern, res.html.text)\n                            stdout = matches[-1] if matches else []\n                            decoded_bytes = base64.b64decode(stdout)\n                            decoded_content = decoded_bytes.decode('utf-8')\n                            return decoded_content.splitlines(), isyaml\n                    except Exception as e:\n                        # 如果不是Base64编码，直接按行处理\n                        return [], isyaml\n        else:\n            print(f\"Failed to retrieve data from {url}, status code: {response.status_code}\")\n            return [], isyaml\n    except requests.RequestException as e:\n        print(f\"An error occurred while requesting {url}: {e}\")\n        return [], isyaml\n\n\n# 解析不同的代理链接\ndef parse_proxy_link(link):\n    try:\n        if link.startswith(\"hysteria2://\") or link.startswith(\"hy2://\"):\n            return parse_hysteria2_link(link)\n        elif link.startswith(\"trojan://\"):\n            return parse_trojan_link(link)\n        elif link.startswith(\"ss://\"):\n            return parse_ss_link(link)\n        elif link.startswith(\"vless://\"):\n            return parse_vless_link(link)\n        elif link.startswith(\"vmess://\"):\n            return parse_vmess_link(link)\n    except Exception as e:\n        # print(e)\n        return None\n\n\n# 根据server和port共同约束去重\ndef deduplicate_proxies(proxies_list):\n    unique_proxies = []\n    seen = set()\n    for proxy in proxies_list:\n        key = (proxy['server'], proxy['port'], proxy['type'], proxy['password']) if proxy.get(\"password\") else (\n        proxy['server'], proxy['port'], proxy['type'])\n        if key not in seen:\n            seen.add(key)\n            unique_proxies.append(proxy)\n    return unique_proxies\n\n\n# 出现节点name相同时，加上4位随机字符串\ndef add_random_suffix(name, existing_names):\n    # 生成4位随机字符串\n    suffix = ''.join(random.choices(string.ascii_letters + string.digits, k=4))\n    new_name = f\"{name}-{suffix}\"\n    # 确保生成的新名字不在已存在的名字列表中\n    while new_name in existing_names:\n        suffix = ''.join(random.choices(string.ascii_letters + string.digits, k=4))\n        new_name = f\"{name}-{suffix}\"\n    return new_name\n\n\n# 从指定目录下的txt读取代理链接\ndef read_txt_files(folder_path):\n    all_lines = []  # 用于存储所有文件的行\n\n    # 使用 glob 获取指定文件夹下的所有 txt 文件\n    txt_files = glob.glob(os.path.join(folder_path, '*.txt'))\n\n    for file_path in txt_files:\n        with open(file_path, 'r', encoding='utf-8') as file:\n            # 读取文件内容并按行存入数组\n            lines = file.readlines()\n            all_lines.extend(line.strip() for line in lines)  # 去除每行的换行符并添加到数组中\n    if all_lines:\n        print(f'加载【{folder_path}】目录下所有txt中节点')\n    return all_lines\n\n\n# 从指定目录下的yaml/yml读取proxies\ndef read_yaml_files(folder_path):\n    load_nodes = []\n    # 使用 glob 获取指定文件夹下的所有 yaml/yml 文件\n    yaml_files = glob.glob(os.path.join(folder_path, '*.yaml'))\n    yaml_files.extend(glob.glob(os.path.join(folder_path, '*.yml')))\n\n    for file_path in yaml_files:\n        try:\n            with open(file_path, 'r', encoding='utf-8') as file:\n                # 读取并解析yaml文件\n                config = yaml.safe_load(file)\n                # 如果存在proxies字段，添加到nodes列表\n                if config and 'proxies' in config:\n                    load_nodes.extend(config['proxies'])\n        except Exception as e:\n            print(f\"Error reading {file_path}: {str(e)}\")\n    if load_nodes:\n        print(f'加载【{folder_path}】目录下yaml/yml中所有节点')\n    return load_nodes\n\n\n# 进行type过滤\ndef filter_by_types_alt(allowed_types, nodes):\n    # 进行过滤\n    return [x for x in nodes if x.get('type') in allowed_types]\n\n\n# 合并links列表\ndef merge_lists(*lists):\n    return [item for item in chain.from_iterable(lists) if item != '']\n\n\ndef handle_links(new_links, resolve_name_conflicts):\n    try:\n        for new_link in new_links:\n            if new_link.startswith((\"hysteria2://\", \"hy2://\", \"trojan://\", \"ss://\", \"vless://\", \"vmess://\")):\n                node = parse_proxy_link(new_link)\n                if node:\n                    resolve_name_conflicts(node)\n            else:\n                print(f\"跳过无效或不支持的链接: {new_link}\")\n    except Exception as e:\n        pass\n\n\n# 生成 Clash 配置文件\ndef generate_clash_config(links, load_nodes):\n    now = datetime.now()\n    print(f\"当前时间: {now}\\n---\")\n\n    final_nodes = []\n    existing_names = set()  # 存储所有节点名字以检查重复\n    config = clash_config_template.copy()\n\n    # 名称已存在的节点加随机后缀\n    def resolve_name_conflicts(node):\n        server = node.get(\"server\")\n        if not server:\n            # print(f'不存在sever，非节点')\n            return\n        name = str(node[\"name\"])\n        if not_contains(name):\n            if name in existing_names:\n                name = add_random_suffix(name, existing_names)\n            existing_names.add(name)\n            node[\"name\"] = name\n            final_nodes.append(node)\n\n    for node in load_nodes:\n        resolve_name_conflicts(node)\n\n    for link in links:\n        if link.startswith((\"hysteria2://\", \"hy2://\", \"trojan://\", \"ss://\", \"vless://\", \"vmess://\")):\n            node = parse_proxy_link(link)\n            if not node:\n                continue\n            resolve_name_conflicts(node)\n        else:\n            if '|links' in link or '.md' in link:\n                link = link.replace('|links', '')\n                new_links = parse_md_link(link)\n                handle_links(new_links, resolve_name_conflicts)\n            if '|ss' in link:\n                link = link.replace('|ss', '')\n                new_links = parse_ss_sub(link)\n                for node in new_links:\n                    resolve_name_conflicts(node)\n            if '{' in link:\n                link = resolve_template_url(link)\n            print(f'当前正在处理link: {link}')\n            # 处理非特定协议的链接\n            try:\n                new_links, isyaml = process_url(link)\n            except Exception as e:\n                print(f\"error: {e}\")\n                continue\n            if isyaml:\n                for node in new_links:\n                    resolve_name_conflicts(node)\n            else:\n                handle_links(new_links, resolve_name_conflicts)\n    final_nodes = deduplicate_proxies(final_nodes)\n    # 重置group中节点name\n    config[\"proxy-groups\"][1][\"proxies\"] = []\n    for node in final_nodes:\n        name = str(node[\"name\"])\n        if not_contains(name):\n            # 0节点选择 1 自动选择 2故障转移 3手动选择\n            config[\"proxy-groups\"][1][\"proxies\"].append(name)\n            proxies = list(set(config[\"proxy-groups\"][1][\"proxies\"]))\n            config[\"proxy-groups\"][1][\"proxies\"] = proxies\n            config[\"proxy-groups\"][2][\"proxies\"] = proxies\n            config[\"proxy-groups\"][3][\"proxies\"] = proxies\n    config[\"proxies\"] = final_nodes\n\n    if config[\"proxies\"]:\n        global CONFIG_FILE\n        CONFIG_FILE = CONFIG_FILE[:-5] if CONFIG_FILE.endswith('.json') else CONFIG_FILE\n        with open(CONFIG_FILE, \"w\", encoding=\"utf-8\") as f:\n            yaml.dump(config, f, allow_unicode=True, default_flow_style=False)\n        with open(f'{CONFIG_FILE}.json', \"w\", encoding=\"utf-8\") as f:\n            json.dump(config, f, ensure_ascii=False)\n        print(f\"已经生成Clash配置文件{CONFIG_FILE}|{CONFIG_FILE}.json\")\n    else:\n        print('没有节点数据更新')\n\n\n# 判断不包含\ndef not_contains(s):\n    return not any(k in s for k in BAN)\n\n\n# 自定义 Clash API 异常\nclass ClashAPIException(Exception):\n    \"\"\"自定义 Clash API 异常\"\"\"\n    pass\n\n\n# 代理测试结果类\nclass ProxyTestResult:\n    \"\"\"代理测试结果类\"\"\"\n\n    def __init__(self, name: str, delay: Optional[float] = None):\n        self.name = name\n        self.delay = delay if delay is not None else float('inf')\n        self.status = \"ok\" if delay is not None else \"fail\"\n        self.tested_time = datetime.now()\n\n    @property\n    def is_valid(self) -> bool:\n        return self.status == \"ok\"\n\n\ndef ensure_executable(file_path):\n    \"\"\" 确保文件具有可执行权限（仅适用于 Linux 和 macOS） \"\"\"\n    if platform.system().lower() in ['linux', 'darwin']:\n        os.chmod(file_path, 0o755)  # 设置文件为可执行\n\n\n# 处理 Clash 配置错误，解析错误信息并更新配置文件\ndef handle_clash_error(error_message, config_file_path):\n    start_time = time.time()\n    config_file_path = f'{config_file_path}.json' if os.path.exists(f'{config_file_path}.json') else config_file_path\n\n    proxy_index_match = re.search(r'proxy (\\d+):', error_message)\n    if not proxy_index_match:\n        return False\n\n    problem_index = int(proxy_index_match.group(1))\n\n    try:\n        # 读取配置文件\n        with open(config_file_path, 'r', encoding='utf-8') as file:\n            config = json.load(file)\n\n        # 获取要删除的节点的name\n        problem_proxy_name = config['proxies'][problem_index]['name']\n        # 删除问题节点\n        del config['proxies'][problem_index]\n\n        # 从所有proxy-groups中删除该节点引用\n        proxies = config['proxy-groups'][1][\"proxies\"]\n        proxies.remove(problem_proxy_name)\n        for group in config[\"proxy-groups\"][1:]:\n            group[\"proxies\"] = proxies\n\n        # 保存更新后的配置\n        with open(config_file_path, 'w', encoding='utf-8') as file:\n            file.write(json.dumps(config, ensure_ascii=False))\n\n        print(\n            f'配置异常：{error_message}修复配置异常，移除proxy[{problem_index}] {problem_proxy_name} 完毕，耗时{time.time() - start_time}s\\n')\n        return True\n\n    except Exception as e:\n        print(f\"处理配置文件时出错: {str(e)}\")\n        return False\n\n\n# 下载最新mihomo\ndef download_and_extract_latest_release():\n    url = \"https://api.github.com/repos/MetaCubeX/mihomo/releases/latest\"\n    response = requests.get(url)\n\n    if response.status_code != 200:\n        print(\"Failed to retrieve data\")\n        return\n\n    data = response.json()\n    assets = data.get(\"assets\", [])\n    os_type = platform.system().lower()\n    targets = {\n        \"darwin\": \"mihomo-darwin-amd64-compatible\",\n        \"linux\": \"mihomo-linux-amd64-compatible\",\n        \"windows\": \"mihomo-windows-amd64-compatible\"\n    }\n\n    # 确定下载链接和新名称\n    download_url = None\n    new_name = f\"clash-{os_type}\" if os_type != \"windows\" else \"clash.exe\"\n\n    # 检查是否已存在二进制文件\n    if os.path.exists(new_name):\n        return\n\n    for asset in assets:\n        name = asset.get(\"name\", \"\")\n        # 根据操作系统确定下载文件的名称和后缀\n        if os_type == \"darwin\" and targets[\"darwin\"] in name and name.endswith('.gz'):\n            download_url = asset[\"browser_download_url\"]\n            break\n        elif os_type == \"linux\" and targets[\"linux\"] in name and name.endswith('.gz'):\n            download_url = asset[\"browser_download_url\"]\n            break\n        elif os_type == \"windows\" and targets[\"windows\"] in name and name.endswith('.zip'):\n            download_url = asset[\"browser_download_url\"]\n            break\n\n    if download_url:\n        download_url = f\"{download_url}\"\n        print(f\"Downloading file from {download_url}\")\n        filename = download_url.split('/')[-1]\n        response = requests.get(download_url)\n\n        # 保存下载的文件\n        with open(filename, 'wb') as f:\n            f.write(response.content)\n\n        # 解压文件并重命名\n        extracted_files = []\n        if filename.endswith('.zip'):\n            with zipfile.ZipFile(filename, 'r') as zip_ref:\n                zip_ref.extractall()\n                extracted_files = zip_ref.namelist()\n        elif filename.endswith('.gz'):\n            with gzip.open(filename, 'rb') as f_in:\n                output_filename = filename[:-3]\n                with open(output_filename, 'wb') as f_out:\n                    shutil.copyfileobj(f_in, f_out)\n                    extracted_files.append(output_filename)\n\n        # 重命名并删除下载的文件\n        for file_name in extracted_files:\n            if os.path.exists(file_name):\n                os.rename(file_name, new_name)\n                break\n\n        os.remove(filename)  # 删除下载的压缩文件\n    else:\n        print(\"No suitable release found for the current operating system.\")\n\n\ndef read_output(pipe, output_lines):\n    while True:\n        line = pipe.readline()\n        if line:\n            output_lines.append(line)\n        else:\n            break\n\n\ndef kill_clash():\n    \"\"\"\n    在 macOS、Linux 和 Windows 上强制杀掉 Clash 进程。\n    支持配置文件：clash_config.yaml 和 clash_config.yaml.json\n    \"\"\"\n    # 根据操作系统定义 Clash 进程名\n    system = platform.system()\n    clash_process_names = {\n        \"Windows\": \"clash.exe\",\n        \"Linux\": \"clash-linux\",\n        \"Darwin\": \"clash-darwin\"  # macOS\n    }\n    config_files = [\"clash_config.yaml\", \"clash_config.yaml.json\"]\n\n    # 检查是否支持当前操作系统\n    if system not in clash_process_names:\n        print(\"不支持的操作系统\")\n        return\n\n    # 获取当前系统的 Clash 进程名\n    process_name = clash_process_names[system]\n\n    # 遍历所有进程，查找并终止 Clash 进程\n    for proc in psutil.process_iter(['pid', 'name', 'cmdline']):\n        try:\n            # 如果进程名不匹配，跳过\n            if proc.info['name'] != process_name:\n                continue\n\n            # 获取命令行参数并检查配置文件\n            cmdline = proc.info['cmdline']\n            if cmdline and len(cmdline) >= 3 and cmdline[1] == '-f' and cmdline[2] in config_files:\n                # 强制终止进程\n                proc.kill()\n                # print(f\"Clash 进程 (PID: {proc.pid}) 已终止 ({system})\")\n        except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):\n            # 忽略进程不存在、权限不足或僵尸进程的异常\n            pass\n\n    # print(f\"未找到 Clash 进程 ({system})\")\n\n\ndef start_clash():\n    download_and_extract_latest_release()\n    system_platform = platform.system().lower()\n\n    if system_platform == 'windows':\n        clash_binary = '.\\\\clash.exe'\n    elif system_platform in [\"linux\", \"darwin\"]:\n        clash_binary = f'./clash-{system_platform}'\n        ensure_executable(clash_binary)\n    else:\n        raise OSError(\"Unsupported operating system.\")\n\n    not_started = True\n\n    global CONFIG_FILE\n    CONFIG_FILE = f'{CONFIG_FILE}.json' if os.path.exists(f'{CONFIG_FILE}.json') else CONFIG_FILE\n    while not_started:\n        # print(f'加载配置{CONFIG_FILE}')\n        clash_process = subprocess.Popen(\n            [clash_binary, '-f', CONFIG_FILE],\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE,\n            text=True,\n            encoding='utf-8'\n        )\n\n        output_lines = []\n\n        # 启动线程来读取标准输出和标准错误\n        stdout_thread = threading.Thread(target=read_output, args=(clash_process.stdout, output_lines))\n\n        stdout_thread.start()\n\n        timeout = 3\n        start_time = time.time()\n        while time.time() - start_time < timeout:\n            stdout_thread.join(timeout=0.5)\n            if output_lines:\n                # 检查输出是否包含错误信息\n                if 'GeoIP.dat' in output_lines[-1]:\n                    print(output_lines[-1])\n                    time.sleep(5)\n                    if is_clash_api_running():\n                        return clash_process\n\n                if \"Parse config error\" in output_lines[-1]:\n                    if handle_clash_error(output_lines[-1], CONFIG_FILE):\n                        clash_process.kill()\n                        output_lines = []\n            if is_clash_api_running():\n                return clash_process\n\n        if not_started:\n            clash_process.kill()\n            continue\n        return clash_process\n\n\ndef is_clash_api_running():\n    try:\n        url = f\"http://{CLASH_API_HOST}:{CLASH_API_PORTS[0]}/configs\"\n        response = requests.get(url)\n        # 检查响应状态码，200表示正常\n        print(f'Clash API启动成功，开始批量检测')\n        return response.status_code == 200\n    except requests.exceptions.RequestException:\n        # 捕获所有请求异常，包括连接错误等\n        return False\n\n\n# 切换到指定代理节点\ndef switch_proxy(proxy_name='DIRECT'):\n    \"\"\"\n    切换 Clash 中策略组的代理节点。\n    :param proxy_name: 要切换到的代理节点名称\n    :return: 返回切换结果或错误信息\n    \"\"\"\n    url = f\"http://{CLASH_API_HOST}:{CLASH_API_PORTS[0]}/proxies/节点选择\"\n    data = {\n        \"name\": proxy_name\n    }\n\n    try:\n        response = requests.put(url, json=data)\n        # 检查响应状态\n        if response.status_code == 204:  # Clash API 切换成功返回 204 No Content\n            print(f\"切换到 '节点选择-{proxy_name}' successfully.\")\n            return {\"status\": \"success\", \"message\": f\"Switched to proxy '{proxy_name}'.\"}\n        else:\n            return response.json()\n    except Exception as e:\n        print(f\"Error occurred: {e}\")\n        return {\"status\": \"error\", \"message\": str(e)}\n\n\n# 调用ClashAPI\nclass ClashAPI:\n    def __init__(self, host: str, ports: List[int], secret: str = \"\"):\n        self.host = host\n        self.ports = ports\n        self.base_url = None  # 将在连接检查时设置\n        self.headers = {\n            \"Authorization\": f\"Bearer {secret}\" if secret else \"\",\n            \"Content-Type\": \"application/json\",\n            'Accept-Charset': 'utf-8',\n            'Accept': 'text/html,application/x-yaml,*/*',\n            'User-Agent': 'Clash Verge/1.7.7'\n        }\n        self.client = httpx.AsyncClient(timeout=TIMEOUT)\n        self.semaphore = Semaphore(MAX_CONCURRENT_TESTS)\n        self._test_results_cache: Dict[str, ProxyTestResult] = {}\n\n    async def __aenter__(self):\n        return self\n\n    async def __aexit__(self, exc_type, exc_val, exc_tb):\n        await self.client.aclose()\n\n    async def check_connection(self) -> bool:\n        \"\"\"检查与 Clash API 的连接状态，自动尝试不同端口\"\"\"\n        for port in self.ports:\n            try:\n                test_url = f\"http://{self.host}:{port}\"\n                response = await self.client.get(f\"{test_url}/version\")\n                if response.status_code == 200:\n                    version = response.json().get('version', 'unknown')\n                    print(f\"成功连接到 Clash API (端口 {port})，版本: {version}\")\n                    self.base_url = test_url\n                    return True\n            except httpx.RequestError:\n                print(f\"端口 {port} 连接失败，尝试下一个端口...\")\n                continue\n\n        print(\"所有端口均连接失败\")\n        print(f\"请确保 Clash 正在运行，并且 External Controller 已启用于以下端口之一: {', '.join(map(str, self.ports))}\")\n        return False\n\n    async def get_proxies(self) -> Dict:\n        \"\"\"获取所有代理节点信息\"\"\"\n        if not self.base_url:\n            raise ClashAPIException(\"未建立与 Clash API 的连接\")\n\n        try:\n            response = await self.client.get(\n                f\"{self.base_url}/proxies\",\n                headers=self.headers\n            )\n            response.raise_for_status()\n            return response.json()\n        except httpx.HTTPStatusError as e:\n            if e.response.status_code == 401:\n                print(\"认证失败，请检查 API Secret 是否正确\")\n            raise ClashAPIException(f\"HTTP 错误: {e}\")\n        except httpx.RequestError as e:\n            raise ClashAPIException(f\"请求错误: {e}\")\n\n    async def test_proxy_delay(self, proxy_name: str) -> ProxyTestResult:\n        \"\"\"测试指定代理节点的延迟，使用缓存避免重复测试\"\"\"\n        if not self.base_url:\n            raise ClashAPIException(\"未建立与 Clash API 的连接\")\n\n        # 检查缓存\n        if proxy_name in self._test_results_cache:\n            cached_result = self._test_results_cache[proxy_name]\n            # 如果测试结果不超过60秒，直接返回缓存的结果\n            if (datetime.now() - cached_result.tested_time).total_seconds() < 60:\n                return cached_result\n\n        async with self.semaphore:\n            try:\n                response = await self.client.get(\n                    f\"{self.base_url}/proxies/{urllib.parse.quote(proxy_name, safe='')}/delay\",\n                    headers=self.headers,\n                    params={\"url\": TEST_URL, \"timeout\": int(TIMEOUT * 1000)}\n                )\n                response.raise_for_status()\n                delay = response.json().get(\"delay\")\n                result = ProxyTestResult(proxy_name, delay)\n            except httpx.HTTPError:\n                result = ProxyTestResult(proxy_name)\n            except Exception as e:\n                result = ProxyTestResult(proxy_name)\n                # print(e)\n            finally:\n                # 更新缓存\n                self._test_results_cache[proxy_name] = result\n                return result\n\n\n# 更新clash配置\nclass ClashConfig:\n    \"\"\"Clash 配置管理类\"\"\"\n\n    def __init__(self, config_path: str):\n        self.config_path = config_path\n        self.config = self._load_config()\n        self.proxy_groups = self._get_proxy_groups()\n\n    def _load_config(self) -> dict:\n        \"\"\"加载配置文件\"\"\"\n        try:\n            with open(self.config_path, 'r', encoding='utf-8') as f:\n                return yaml.safe_load(f)\n        except FileNotFoundError:\n            print(f\"找不到配置文件: {self.config_path}\")\n            sys.exit(1)\n        except yaml.YAMLError as e:\n            print(f\"配置文件格式错误: {e}\")\n            sys.exit(1)\n\n    def _get_proxy_groups(self) -> List[Dict]:\n        \"\"\"获取所有代理组信息\"\"\"\n        return self.config.get(\"proxy-groups\", [])\n\n    def get_group_names(self) -> List[str]:\n        \"\"\"获取所有代理组名称\"\"\"\n        return [group[\"name\"] for group in self.proxy_groups]\n\n    def get_group_proxies(self, group_name: str) -> List[str]:\n        \"\"\"获取指定组的所有代理\"\"\"\n        for group in self.proxy_groups:\n            if group[\"name\"] == group_name:\n                return group.get(\"proxies\", [])\n        return []\n\n    def remove_invalid_proxies(self, results: List[ProxyTestResult]):\n        \"\"\"从配置中完全移除失效的节点\"\"\"\n        # 获取所有失效节点名称\n        invalid_proxies = {r.name for r in results if not r.is_valid}\n\n        if not invalid_proxies:\n            return\n\n        # 从 proxies 部分移除失效节点\n        valid_proxies = []\n        if \"proxies\" in self.config:\n            valid_proxies = [p for p in self.config[\"proxies\"]\n                             if p.get(\"name\") not in invalid_proxies]\n            self.config[\"proxies\"] = valid_proxies\n\n        # 从所有代理组中移除失效节点\n        for group in self.proxy_groups:\n            if \"proxies\" in group:\n                group[\"proxies\"] = [p for p in group[\"proxies\"] if p not in invalid_proxies]\n        global LIMIT\n        left = LIMIT if len(self.config['proxies']) > LIMIT else len(self.config['proxies'])\n        # LIMIT = LIMIT if len(self.config['proxies']) > LIMIT else len(self.config['proxies'])\n        print(f\"已从配置中移除 {len(invalid_proxies)} 个失效节点，最终保留{left}个延迟最小的节点\")\n\n    def keep_proxies_by_limit(self, proxy_names):\n        if \"proxies\" in self.config:\n            self.config[\"proxies\"] = [p for p in self.config[\"proxies\"] if p[\"name\"] in proxy_names]\n\n    def update_group_proxies(self, group_name: str, results: List[ProxyTestResult]):\n        \"\"\"更新指定组的代理列表，仅保留有效节点并按延迟排序\"\"\"\n        # 移除失效节点\n        self.remove_invalid_proxies(results)\n\n        # 获取有效节点并按延迟排序\n        valid_results = [r for r in results if r.is_valid]\n        valid_results = list(set(valid_results))\n        valid_results.sort(key=lambda x: x.delay)\n\n        # 更新代理组\n        proxy_names = [r.name for r in valid_results]\n        for group in self.proxy_groups:\n            if group[\"name\"] == group_name:\n                group[\"proxies\"] = proxy_names\n                break\n        return proxy_names\n\n    def save(self):\n        \"\"\"保存配置到文件\"\"\"\n        try:\n            # 保存新配置\n            yaml_cfg = self.config_path.strip('.json') if self.config_path.endswith('.json') else self.config_path\n            with open(yaml_cfg, 'w', encoding='utf-8') as f:\n                yaml.dump(self.config, f, allow_unicode=True, sort_keys=False)\n            # print(f\"新配置已保存到: {yaml_cfg}\")\n            with open(f'{yaml_cfg}.json', \"w\", encoding=\"utf-8\") as f:\n                json.dump(self.config, f, ensure_ascii=False)\n            # print(f'新配置已保存到: {yaml_cfg}.json')\n\n        except Exception as e:\n            print(f\"保存配置文件失败: {e}\")\n            sys.exit(1)\n\n\n# 打印测试结果摘要\ndef print_test_summary(group_name: str, results: List[ProxyTestResult]):\n    \"\"\"打印测试结果摘要\"\"\"\n    valid_results = [r for r in results if r.is_valid]\n    invalid_results = [r for r in results if not r.is_valid]\n    total = len(results)\n    valid = len(valid_results)\n    invalid = len(invalid_results)\n\n    print(f\"\\n策略组 '{group_name}' 测试结果:\")\n    print(f\"总节点数: {total}\")\n    print(f\"可用节点数: {valid}\")\n    print(f\"失效节点数: {invalid}\")\n\n    delays = []\n\n    if valid > 0:\n        avg_delay = sum(r.delay for r in valid_results) / valid\n        print(f\"平均延迟: {avg_delay:.2f}ms\")\n        print(\"\\n节点延迟统计:\")\n        sorted_results = sorted(valid_results, key=lambda x: x.delay)\n        for i, result in enumerate(sorted_results[:LIMIT], 1):\n            delays.append({\"name\": result.name, \"Delay_ms\": round(result.delay, 2)})\n            print(f\"{i}. {result.name}: {result.delay:.2f}ms\")\n    return delays\n\n\n# 测试一组代理节点\nasync def test_group_proxies(clash_api: ClashAPI, proxies: List[str]) -> List[ProxyTestResult]:\n    \"\"\"测试一组代理节点\"\"\"\n    print(f\"开始测试 {len(proxies)} 个节点 (最大并发: {MAX_CONCURRENT_TESTS})\")\n\n    # 创建所有测试任务\n    tasks = [clash_api.test_proxy_delay(proxy_name) for proxy_name in proxies]\n\n    # 使用进度显示执行所有任务\n    results = []\n    for future in asyncio.as_completed(tasks):\n        result = await future\n        results.append(result)\n        # 显示进度\n        done = len(results)\n        total = len(tasks)\n        print(f\"\\r进度: {done}/{total} ({done / total * 100:.1f}%)\", end=\"\", flush=True)\n\n    return results\n\n\nasync def proxy_clean():\n    # 更新全局配置\n    delays = []\n    global MAX_CONCURRENT_TESTS, TIMEOUT, CLASH_API_SECRET, LIMIT, CONFIG_FILE\n    CONFIG_FILE = f'{CONFIG_FILE}.json' if os.path.exists(f'{CONFIG_FILE}.json') else CONFIG_FILE\n    print(f\"===================节点批量检测基本信息======================\")\n    print(f\"配置文件: {CONFIG_FILE}\")\n    print(f\"API 端口: {CLASH_API_PORTS[0]}\")\n    print(f\"并发数量: {MAX_CONCURRENT_TESTS}\")\n    print(f\"超时时间: {TIMEOUT}秒\")\n    print(f\"保留节点：最多保留{LIMIT}个延迟最小的有效节点\")\n\n    # 加载配置\n    print(f'加载配置文件{CONFIG_FILE}')\n    config = ClashConfig(CONFIG_FILE)\n    available_groups = config.get_group_names()[1:]\n\n    # 确定要测试的策略组\n    groups_to_test = available_groups\n    invalid_groups = set(groups_to_test) - set(available_groups)\n    if invalid_groups:\n        print(f\"警告: 以下策略组不存在: {', '.join(invalid_groups)}\")\n        groups_to_test = list(set(groups_to_test) & set(available_groups))\n\n    if not groups_to_test:\n        print(\"错误: 没有找到要测试的有效策略组\")\n        print(f\"可用的策略组: {', '.join(available_groups)}\")\n        return\n\n    print(f\"\\n将测试以下策略组: {', '.join(groups_to_test)}\")\n\n    # 开始测试\n    start_time = datetime.now()\n\n    # 创建支持多端口的API实例\n    async with ClashAPI(CLASH_API_HOST, CLASH_API_PORTS, CLASH_API_SECRET) as clash_api:\n        if not await clash_api.check_connection():\n            return\n\n        try:\n            all_test_results = []  # 收集所有测试结果\n\n            # 测试策略组，只需要测试其中一个即可\n            group_name = groups_to_test[0]\n            print(f\"\\n======================== 开始测试策略组: {group_name} ====================\")\n            proxies = config.get_group_proxies(group_name)\n\n            if not proxies:\n                print(f\"策略组 '{group_name}' 中没有代理节点\")\n            else:\n                # 测试该组的所有节点\n                results = await test_group_proxies(clash_api, proxies)\n                all_test_results.extend(results)\n                # 打印测试结果摘要\n                delays = print_test_summary(group_name, results)\n\n            print('\\n===================移除失效节点并按延迟排序======================\\n')\n            # 一次性移除所有失效节点并更新配置\n            config.remove_invalid_proxies(all_test_results)\n\n            # 为每个组更新有效节点的顺序\n            proxy_names = set()\n            # 只对一个group的proxies排序即可\n            group_proxies = config.get_group_proxies(group_name)\n            group_results = [r for r in all_test_results if r.name in group_proxies]\n            if LIMIT:\n                group_results = group_results[:LIMIT]\n            for r in group_results:\n                proxy_names.add(r.name)\n\n            for group_name in groups_to_test:\n                proxy_names = config.update_group_proxies(group_name, group_results)\n                print(f\"'{group_name}'已按延迟大小重新排序\")\n\n            if LIMIT:\n                config.keep_proxies_by_limit(proxy_names)\n\n            # 保存更新后的配置\n            config.save()\n\n            if SPEED_TEST:\n                # 测速\n                print('\\n===================检测节点速度======================\\n')\n                sorted_proxy_names = start_download_test(proxy_names)\n                # 按测试重新排序\n                new_list = sorted_proxy_names.copy()\n                # 创建一个集合来跟踪已添加的元素\n                added_elements = set(new_list)\n                # 遍历 group_proxies，将不在 added_elements 中的元素添加到 new_list\n                group_proxies = config.get_group_proxies(group_name)\n                for item in group_proxies:\n                    if item not in added_elements:\n                        new_list.append(item)\n                        added_elements.add(item)  # 将新添加的元素加入集合中\n                # 排序好的节点名放入group-proxies\n                for group_name in groups_to_test:\n                    for group in config.proxy_groups:\n                        if group[\"name\"] == group_name:\n                            group[\"proxies\"] = new_list\n                # 保存更新后的配置\n                config.save()\n\n            # 显示总耗时\n            total_time = (datetime.now() - start_time).total_seconds()\n            print(f\"\\n总耗时: {total_time:.2f} 秒\")\n            return delays\n        except ClashAPIException as e:\n            print(f\"Clash API 错误: {e}\")\n        except Exception as e:\n            print(f\"发生错误: {e}\")\n            raise\n\n\n# 获取当前时间的各个组成部分\ndef parse_datetime_variables():\n    now = datetime.now()\n    return {\n        'Y': str(now.year),\n        'm': str(now.month).zfill(2),\n        'd': str(now.day).zfill(2),\n        'H': str(now.hour).zfill(2),\n        'M': str(now.minute).zfill(2),\n        'S': str(now.second).zfill(2)\n    }\n\n\n# 移除URL中的代理前缀\ndef strip_proxy_prefix(url):\n    proxy_pattern = r'^https?://[^/]+/https://'\n    match = re.match(proxy_pattern, url)\n    if match:\n        real_url = re.sub(proxy_pattern, 'https://', url)\n        proxy_prefix = url[:match.end() - 8]\n        return real_url, proxy_prefix\n    return url, None\n\n\n# 判断是否为GitHub raw URL\ndef is_github_raw_url(url):\n    return 'raw.githubusercontent.com' in url\n\n\n# 从URL中提取文件模式，返回占位符前后的部分\ndef extract_file_pattern(url):\n    # 查找形如 {x}<suffix> 的模式\n    match = re.search(r'\\{x\\}(\\.[a-zA-Z0-9]+)(?:/|$)', url)\n    if match:\n        return match.group(1)  # 返回文件后缀，如 '.yaml', '.txt', '.json'\n    return None\n\n\n# 从GitHub API获取匹配指定后缀的文件名\ndef get_github_filename(github_url, file_suffix):\n    match = re.match(r'https://raw\\.githubusercontent\\.com/([^/]+)/([^/]+)/[^/]+/[^/]+/([^/]+)', github_url)\n    if not match:\n        raise ValueError(\"无法从URL中提取owner和repo信息\")\n    owner, repo, branch = match.groups()\n\n    # 构建API URL\n    path_part = github_url.split(f'/refs/heads/{branch}/')[-1]\n    # 移除 {x}<suffix> 部分来获取目录路径\n    path_part = re.sub(r'\\{x\\}' + re.escape(file_suffix) + '(?:/|$)', '', path_part)\n    api_url = f\"https://api.github.com/repos/{owner}/{repo}/contents/{path_part}\"\n\n    response = requests.get(api_url)\n    if response.status_code != 200:\n        raise Exception(f\"GitHub API请求失败: {response.status_code} {response.text}\")\n\n    files = response.json()\n    matching_files = [f['name'] for f in files if f['name'].endswith(file_suffix)]\n\n    if not matching_files:\n        raise Exception(f\"未找到匹配的{file_suffix}文件\")\n\n    return matching_files[0]\n\n\n# 解析URL模板，支持任意组合的日期时间变量和分隔符\ndef parse_template(template_url, datetime_vars):\n    def replace_template(match):\n        \"\"\"替换单个模板块的内容\"\"\"\n        template_content = match.group(1)\n        if template_content == 'x':\n            return '{x}'  # 保持 {x} 不变，供后续处理\n\n        result = ''\n        # 用于临时存储当前字符\n        current_char = ''\n\n        # 遍历模板内容中的每个字符\n        for char in template_content:\n            if char in datetime_vars:\n                # 如果是日期时间变量，替换为对应值\n                if current_char:\n                    # 添加之前累积的非变量字符\n                    result += current_char\n                    current_char = ''\n                result += datetime_vars[char]\n            else:\n                # 如果是其他字符（分隔符），直接保留\n                current_char += char\n\n        # 添加最后可能剩余的非变量字符\n        if current_char:\n            result += current_char\n\n        return result\n\n    # 使用正则表达式查找并替换所有模板块\n    return re.sub(r'\\{([^}]+)\\}', replace_template, template_url)\n\n\n# 完整解析模板URL\ndef resolve_template_url(template_url):\n    # 先处理代理前缀\n    url, proxy_prefix = strip_proxy_prefix(template_url)\n\n    # 获取日期时间变量\n    datetime_vars = parse_datetime_variables()\n\n    # 替换日期时间变量\n    resolved_url = parse_template(url, datetime_vars)\n\n    # 如果是GitHub URL且包含{x}，则处理文件名\n    if is_github_raw_url(resolved_url) and '{x}' in resolved_url:\n        # 提取文件后缀\n        file_suffix = extract_file_pattern(resolved_url)\n        if file_suffix:\n            filename = get_github_filename(resolved_url, file_suffix)\n            # 替换 {x}<suffix> 为实际文件名\n            resolved_url = re.sub(r'\\{x\\}' + re.escape(file_suffix), filename, resolved_url)\n\n    # 如果有代理前缀，重新添加上\n    if proxy_prefix:\n        resolved_url = f\"{proxy_prefix}{resolved_url}\"\n\n    return resolved_url\n\n\ndef start_download_test(proxy_names, speed_limit=0.1):\n    \"\"\"\n    开始下载测试\n\n    \"\"\"\n    # 第一步：测试所有节点的下载速度\n    test_all_proxies(proxy_names[:SPEED_TEST_LIMIT])\n\n    # 过滤出速度大于等于 speed_limit 的节点\n    filtered_list = [item for item in results_speed if float(item[1]) >= float(f'{speed_limit}')]\n\n    # 按下载速度从大到小排序\n    sorted_proxy_names = []\n    sorted_list = sorted(filtered_list, key=lambda x: float(x[1]), reverse=True)\n    print(f'节点速度统计:')\n    for i, result in enumerate(sorted_list[:LIMIT], 1):\n        sorted_proxy_names.append(result[0])\n        print(f\"{i}. {result[0]}: {result[1]}Mb/s\")\n\n    return sorted_proxy_names\n\n\n# 测试所有代理节点的下载速度，并排序结果\ndef test_all_proxies(proxy_names):\n    try:\n        # 单线程节点速度下载测试\n        i = 0\n        for proxy_name in proxy_names:\n            i += 1\n            print(f\"\\r正在测速节点【{i}】: {proxy_name}\", flush=True, end='')\n            test_proxy_speed(proxy_name)\n\n        print(\"\\r\" + \" \" * 50 + \"\\r\", end='')  # 清空行并返回行首\n    except Exception as e:\n        print(f\"测试节点速度时出错: {e}\")\n\n\n# 测试指定代理节点的下载速度（下载5秒后停止）\ndef test_proxy_speed(proxy_name):\n    # 切换到该代理节点\n    switch_proxy(proxy_name)\n    # 设置代理\n    proxies = {\n        \"http\": 'http://127.0.0.1:7890',\n        \"https\": 'http://127.0.0.1:7890',\n    }\n\n    # 开始下载并测量时间\n    start_time = time.time()\n    # 计算总下载量\n    total_length = 0\n    # 测试下载时间（秒）\n    test_duration = 5  # 逐块下载，直到达到5秒钟为止\n\n    # 不断发起请求直到达到时间限制\n    while time.time() - start_time < test_duration:\n        try:\n            response = requests.get(\"http://speedtest.tele2.net/100MB.zip\", stream=True, proxies=proxies,\n                                    headers={'Cache-Control': 'no-cache'},\n                                    timeout=test_duration)\n            for data in response.iter_content(chunk_size=524288):\n                total_length += len(data)\n                if time.time() - start_time >= test_duration:\n                    break\n        except Exception as e:\n            print(f\"测试节点 {proxy_name} 下载失败: {e}\")\n\n    # 计算速度：Bps -> MB/s\n    elapsed_time = time.time() - start_time\n    speed = total_length / elapsed_time if elapsed_time > 0 else 0\n\n    results_speed.append((proxy_name, f\"{speed / 1024 / 1024:.2f}\"))  # 记录速度测试结果\n    return speed / 1024 / 1024  # 返回 MB/s\n\n\ndef upload_and_generate_urls(file_path=CONFIG_FILE):\n    # api_url = \"https://catbox.moe/user/api.php\"\n    # api_url = \"https://f2.252035.xyz/user/api.php\"\n    api_url = \"https://ade4e1d7-catbox.seczhcom.workers.dev/user/api.php\"\n    result = {\"clash_url\": None, \"singbox_url\": None}\n\n    try:\n        if not os.path.isfile(file_path):\n            print(f\"错误：文件 {file_path} 不存在。\")\n            return result\n        if os.path.getsize(file_path) > 209715200:\n            print(\"错误：文件大小超过 200MB 限制。\")\n            return result\n\n        # Upload Clash config\n        with open(file_path, 'rb') as file:\n            response = requests.post(api_url, data={\"reqtype\": \"fileupload\"}, files={\"fileToUpload\": file}, timeout=15,\n                                     verify=False)\n            if response.status_code == 200:\n                clash_url = response.text.strip()\n                result[\"clash_url\"] = clash_url\n                print(f\"Clash 配置文件上传成功！直链：{clash_url}\")\n\n                sb_full_url = f'https://url.v1.mk/sub?target=singbox&url={clash_url}&insert=false&config=https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online_Full_NoAuto.ini&emoji=true&list=false&xudp=false&udp=false&tfo=false&expand=true&scv=false&fdn=false'\n                encoded_url = base64.urlsafe_b64encode(sb_full_url.encode()).decode()\n                response = requests.post(\"https://v1.mk/short\", json={\"longUrl\": encoded_url})\n                if response.status_code == 200:\n                    data = response.json()\n                    if data.get(\"Code\") == 1:\n                        singbox_url = data[\"ShortUrl\"]\n                        result[\"singbox_url\"] = singbox_url\n                        print(f\"singbox 配置文件上传成功！直链：{singbox_url}\")\n\n    except Exception as e:\n        print(f\"发生错误：{e}\")\n\n    # 记录成功生成的链接到subs.json\n    subs_file = \"subs.json\"\n    if result[\"clash_url\"] or result[\"singbox_url\"]:\n        try:\n            # 初始化默认结构\n            subs_data = {\"clash\": [], \"singbox\": []}\n\n            # 尝试读取现有文件\n            if os.path.exists(subs_file):\n                try:\n                    with open(subs_file, 'r', encoding='utf-8') as f:\n                        subs_data = json.load(f)\n                except:\n                    pass  # 如果文件损坏，使用默认结构\n\n            # 添加新链接到记录中(避免重复)\n            if result[\"clash_url\"] and result[\"clash_url\"] not in subs_data.get(\"clash\", []):\n                if \"clash\" not in subs_data:\n                    subs_data[\"clash\"] = []\n                subs_data[\"clash\"].append(result[\"clash_url\"])\n\n            if result[\"singbox_url\"] and result[\"singbox_url\"] not in subs_data.get(\"singbox\", []):\n                if \"singbox\" not in subs_data:\n                    subs_data[\"singbox\"] = []\n                subs_data[\"singbox\"].append(result[\"singbox_url\"])\n\n            # 保存更新后的数据\n            with open(subs_file, 'w', encoding='utf-8') as f:\n                json.dump(subs_data, f, ensure_ascii=False, indent=2)\n\n            print(f\"已将订阅链接记录到 {subs_file}\")\n        except Exception as e:\n            print(f\"记录订阅链接失败: {str(e)}\")\n\n    return result\n\n\ndef work(links, check=False, allowed_types=[], only_check=False):\n    try:\n        if not only_check:\n            load_nodes = read_yaml_files(folder_path=INPUT)\n            if allowed_types:\n                load_nodes = filter_by_types_alt(allowed_types, nodes=load_nodes)\n            links = merge_lists(read_txt_files(folder_path=INPUT), links)\n            if links or load_nodes:\n                generate_clash_config(links, load_nodes)\n\n        if check or only_check:\n            clash_process = None\n            try:\n                # 启动clash\n                print(f\"===================启动clash并初始化配置======================\")\n                clash_process = start_clash()\n                # 切换节点到'节点选择-DIRECT'\n                switch_proxy('DIRECT')\n                asyncio.run(proxy_clean())\n                print(f'批量检测完毕')\n            except Exception as e:\n                print(\"Error calling Clash API:\", e)\n            finally:\n                print(f'关闭Clash API')\n                if clash_process is not None:\n                    clash_process.kill()\n\n    except KeyboardInterrupt:\n        print(\"\\n用户中断执行\")\n        sys.exit(0)\n    except Exception as e:\n        print(f\"程序执行失败: {e}\")\n        sys.exit(1)\n\n\nif __name__ == '__main__':\n    links = [\n        \"https://c7dabe95.proxy-978.pages.dev/767b6340-96dc-4aa0-8013-a8af7513d920?clash\",\n        \"https://cdn.jsdelivr.net/gh/xiaoji235/airport-free/clash/naidounode.txt\",\n        \"https://cdn.jsdelivr.net/gh/yangxiaoge/tvbox_cust@master/clash/Clash2.yml\",\n        \"https://gy.xiaozi.us.kg/sub?token=lzj666\",\n        \"https://igdux.top/5Hna\",\n        \"https://mxlsub.me/newfull\",\n        \"https://proxypool.link/ss/sub|ss\",\n        \"https://proxypool.link/trojan/sub\",\n        \"https://proxypool.link/vmess/sub\",\n        \"https://raw.githubusercontent.com/Q3dlaXpoaQ/V2rayN_Clash_Node_Getter/refs/heads/main/APIs/sc0.yaml\",\n        \"https://raw.githubusercontent.com/Q3dlaXpoaQ/V2rayN_Clash_Node_Getter/refs/heads/main/APIs/sc1.yaml\",\n        \"https://raw.githubusercontent.com/Q3dlaXpoaQ/V2rayN_Clash_Node_Getter/refs/heads/main/APIs/sc2.yaml\",\n        \"https://raw.githubusercontent.com/Q3dlaXpoaQ/V2rayN_Clash_Node_Getter/refs/heads/main/APIs/sc3.yaml\",\n        \"https://raw.githubusercontent.com/Q3dlaXpoaQ/V2rayN_Clash_Node_Getter/refs/heads/main/APIs/sc4.yaml\",\n        \"https://raw.githubusercontent.com/Roywaller/clash_subscription/refs/heads/main/clash_subscription.txt\",\n        \"https://raw.githubusercontent.com/Ruk1ng001/freeSub/main/clash.yaml\",\n        \"https://raw.githubusercontent.com/SoliSpirit/v2ray-configs/main/all_configs.txt\",\n        \"https://raw.githubusercontent.com/a2470982985/getNode/main/clash.yaml\",\n        \"https://raw.githubusercontent.com/aiboboxx/clashfree/refs/heads/main/clash.yml\",\n        \"https://raw.githubusercontent.com/aiboboxx/v2rayfree/refs/heads/main/README.md\",\n        \"https://raw.githubusercontent.com/anaer/Sub/refs/heads/main/clash.yaml\",\n        \"https://raw.githubusercontent.com/chengaopan/AutoMergePublicNodes/master/list.yml\",\n        \"https://raw.githubusercontent.com/ermaozi/get_subscribe/main/subscribe/clash.yml\",\n        \"https://raw.githubusercontent.com/firefoxmmx2/v2rayshare_subcription/refs/heads/main/subscription/clash_sub.yaml\",\n        \"https://raw.githubusercontent.com/free18/v2ray/refs/heads/main/c.yaml\",\n        \"https://raw.githubusercontent.com/go4sharing/sub/main/sub.yaml\",\n        \"https://raw.githubusercontent.com/leetomlee123/freenode/refs/heads/main/README.md\",\n        \"https://raw.githubusercontent.com/ljlfct01/ljlfct01.github.io/refs/heads/main/节点\",\n        \"https://raw.githubusercontent.com/mahdibland/SSAggregator/master/sub/sub_merge_yaml.yml\",\n        \"https://raw.githubusercontent.com/mahdibland/ShadowsocksAggregator/master/Eternity.yml\",\n        \"https://raw.githubusercontent.com/mahdibland/ShadowsocksAggregator/master/LogInfo.txt\",\n        \"https://raw.githubusercontent.com/mai19950/clashgithub_com/refs/heads/main/site\",\n        \"https://raw.githubusercontent.com/mfbpn/tg_mfbpn_sub/main/trial.yaml\",\n        \"https://raw.githubusercontent.com/mfuu/v2ray/master/clash.yaml\",\n        \"https://raw.githubusercontent.com/mgit0001/test_clash/refs/heads/main/heima.txt\",\n        \"https://raw.githubusercontent.com/peasoft/NoMoreWalls/master/list.meta.yml\",\n        \"https://raw.githubusercontent.com/peasoft/NoMoreWalls/master/list.yml\",\n        \"https://raw.githubusercontent.com/Pawdroid/Free-servers/refs/heads/main/sub\",\n        \"https://raw.githubusercontent.com/ripaojiedian/freenode/main/clash\",\n        \"https://raw.githubusercontent.com/shahidbhutta/Clash/refs/heads/main/Router\",\n        \"https://raw.githubusercontent.com/skka3134/Free-servers/refs/heads/main/README.md\",\n        \"https://raw.githubusercontent.com/snakem982/proxypool/main/source/clash-meta.yaml\",\n        \"https://raw.githubusercontent.com/vxiaov/free_proxies/main/clash/clash.provider.yaml\",\n        \"https://raw.githubusercontent.com/wangyingbo/yb_clashgithub_sub/main/clash_sub.yml\",\n        \"https://raw.githubusercontent.com/xiaoer8867785/jddy5/refs/heads/main/data/{Y_m_d}/{x}.yaml\",\n        \"https://raw.githubusercontent.com/xiaoji235/airport-free/refs/heads/main/clash/naidounode.txt\",\n        \"https://raw.githubusercontent.com/zhangkaiitugithub/passcro/main/speednodes.yaml\",\n        \"https://raw.githubusercontent.com/aiboboxx/clashfree/refs/heads/main/clash.yml\",\n        \"https://raw.githubusercontent.com/ljlfct01/ljlfct01.github.io/refs/heads/main/节点\",\n        \"https://raw.githubusercontent.com/mahdibland/ShadowsocksAggregator/master/LogInfo.txt\",\n        \"https://raw.githubusercontent.com/wangyingbo/yb_clashgithub_sub/main/clash_sub.yml\",\n        \"https://SOS.CMLiussss.net/auto\",\n        \"https://sub.fqzsnai.ggff.net/auto\",\n        \"https://sub.mikeone.ggff.net/sub?token=6e300fe82f12874e439b76693aa179fb\",\n        \"https://sub.reajason.eu.org/clash.yaml\",\n        \"https://v1.mk/HuaplNe\",\n        \"https://www.freeclashnode.com/uploads/{Y}/{m}/0-{Ymd}.yaml\",\n        \"https://www.freeclashnode.com/uploads/{Y}/{m}/1-{Ymd}.yaml\",\n        \"https://zrf.zrf.me/zrf\"\n    ]\n    work(links, check=True, only_check=False, allowed_types=[\"ss\", \"hysteria2\", \"hy2\", \"vless\", \"vmess\", \"trojan\"])\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM python:3.10.10-slim-bullseye\n\n# 设置工作目录\nWORKDIR /app\n\n# 复制文件到工作目录\nADD ClashForge.py .\nADD clash-linux .\nADD requirements.txt .\nADD WebUI.py .\nRUN mkdir input\n\n# 安装依赖\nRUN pip3 install -r requirements.txt && \\\n    apt-get update && \\\n    apt-get -f install -y --no-install-recommends \\\n    chromium \\\n    libpq-dev \\\n    gcc \\\n    libc6-dev \\\n    libxcursor1 \\\n    libxss1 \\\n    libpangocairo-1.0-0 \\\n    libgtk-3-0 && \\\n    rm -rf /var/lib/apt/lists/* && \\\n    rm -rf /usr/local/lib/python3.10/site-packages/distutils-precedence.pth && \\\n    export PYPPETEER_CHROMIUM_REVISION=1181205 && \\\n    pyppeteer-install\n\n# 暴露端口\nEXPOSE 8501\n\n# 运行 Streamlit\nCMD [\"python3\", \"-m\", \"streamlit\", \"run\", \"WebUI.py\"]\n"
  },
  {
    "path": "README.md",
    "content": "## 在线演示\nhttp://cf.252035.xyz/\n\n## 支持WebUI操作\n\nhttps://github.com/user-attachments/assets/a1a377a2-e93e-461d-9915-7f7f4dd6ff36\n\n## 功能\n- 将`hysteria2://|hy2://`、`trojan://`、`ss://`、`vless://`、`vmess://`协议链接转换为clash可用的代理节点配置\n- 支持从`input`目录下的所有txt文档中按行读取代理链接(每条代理链接占一行)\n- 支持从`input`目录下的所有yaml/yml读取proxies  \n- 支持指定代理类型过滤，指定参数allowed_types=['ss']\n- 支持从订阅源提取代理节点，支持订阅源类型:clash、v2ray、trojan、vmess、vless、ss源(链接需以\"|ss\"结尾)\n- 支持从某些github的README.md获取代理节点\n- 链接以\"|links\"结尾，表示链接返回内容中直接包含代理链接，用正则匹配出来\n- 支持占位符匹配，Y年m月d日H时M分 例:{Ymd} {Y_m_d} {Y-m-d}\n- 支持github文件名模糊匹配，{x}.yaml表示不确定文件名的yaml文件\n- 将所有获取的代理节点汇聚到一个配置文件里，自动去重，name重复自动添加随机后缀，分组`自动选择`、`故障转移`、`手动选择`，策略中排除了国内节点    \n- 支持全自动批量检测节点有效性，移除失效节点，并按延迟大小排序，无需人工介入（首次执行会自动下载mihomo最新release），自动移除异常节点，修复配置文件\n- 可以仅执行批量检测(配置文件名为同目录的clash_config.yaml)\n- 支持设置保留节点数，默认保留100个延迟最小的节点\n- 生成临时json配置，极大的提高了修复配置异常节点的效率，使用json格式处理几十万行的数据也非常快\n- 支持测试节点下载速度，按下载速度排序，默认只测试前30个节点(每个节点测试5秒)\n- 新增`WebUI.py`，运行方式`python3 -m streamlit run WebUI.py`，打开浏览器访问 `http://localhost:8501`\n- 新增生成永久订阅链接功能，订阅配置文件并不会保留在服务器本地，可以放心使用。文件存储使用的是https://catbox.moe/\n- 新增容器支持，启动方式`docker run -d --rm --name clashforge -p 8501:8501 2011820123/clashforge:latest`\n\n## 相关参考：\n- https://wiki.metacubex.one/config/  \n- https://github.com/Loyalsoldier/geoip  \n- https://github.com/MetaCubeX/mihomo/releases\n- https://github.com/fish2018/ClashForge/issues/11#issuecomment-2907599228 (如果需要自己实现反代catbox可以参考这里，提供Nginx和CF worker两种方式)\n\n"
  },
  {
    "path": "WebUI.py",
    "content": "import streamlit as st\nimport os\nimport io\nfrom contextlib import redirect_stdout\nimport time\nimport asyncio\nimport pandas as pd\nimport yaml\nimport requests\nimport json\nimport glob\nimport streamlit.components.v1 as components\n\n# 导入ClashForge模块\nfrom ClashForge import (\n    generate_clash_config, merge_lists, switch_proxy,\n    filter_by_types_alt, read_txt_files, read_yaml_files,\n    start_clash, proxy_clean, kill_clash,\n    ClashConfig, download_and_extract_latest_release,\n    upload_and_generate_urls\n)\n\ndef capture_output(func, *args, **kwargs):\n    \"\"\"捕获函数的标准输出并返回函数结果\"\"\"\n    f = io.StringIO()\n    with redirect_stdout(f):\n        result = func(*args, **kwargs)\n    return result\n\n# 设置页面配置\nst.set_page_config(\n    page_title=\"ClashForge Web\",\n    page_icon=\"🌐\",\n    layout=\"wide\",\n    initial_sidebar_state=\"expanded\",\n)\n\n# 添加一些基本样式\nst.markdown(\"\"\"\n<style>\n    .main-header {\n        font-size: 2.5rem;\n        color: #1E88E5;\n        text-align: center;\n        margin-bottom: 1rem;\n    }\n    .footer {\n        text-align: center;\n        margin-top: 2rem;\n        padding: 1rem;\n        font-size: 0.9rem;\n        color: #666;\n    }\n    .scrolling-text-container {\n        background-color: #f0f8ff;\n        border-left: 4px solid #1E88E5;\n        padding: 10px 15px;\n        margin-bottom: 20px;\n        border-radius: 4px;\n        box-shadow: 0 2px 5px rgba(0,0,0,0.1);\n        overflow: hidden;\n        position: relative;\n        width: 100%;\n    }\n    .marquee {\n        width: 100%;\n        overflow: hidden;\n        position: relative;\n    }\n    .marquee-content {\n        display: flex;\n        animation: marquee 20s linear infinite;\n        white-space: nowrap;\n    }\n    .marquee-item {\n        flex-shrink: 0;\n        padding: 0 20px;\n    }\n    @keyframes marquee {\n        0% { transform: translateX(0); }\n        100% { transform: translateX(-100%); }\n    }\n</style>\n\"\"\", unsafe_allow_html=True)\n\n# 滚动提示信息函数\ndef show_scrolling_tips():\n    # 计算距离下一次重置的剩余时间\n    current_time = time.time()\n    # 假设重置发生在每10分钟，即每小时的0, 10, 20, 30, 40, 50分钟\n    current_minute = int(time.strftime(\"%M\", time.localtime(current_time)))\n    current_second = int(time.strftime(\"%S\", time.localtime(current_time)))\n    \n    # 计算下一个10分钟整点\n    next_reset_minute = (current_minute // 10 + 1) * 10\n    if next_reset_minute == 60:\n        next_reset_minute = 0  # 下一个小时\n    \n    # 计算剩余分钟和秒数\n    if next_reset_minute == 0:\n        # 如果下一个重置点是下一个小时的0分，则剩余分钟是60-current_minute-1\n        remaining_minutes = 60 - current_minute - 1\n    else:\n        remaining_minutes = next_reset_minute - current_minute - 1\n    \n    remaining_seconds = 60 - current_second\n    \n    # 正确处理边界情况\n    if remaining_seconds == 60:\n        remaining_seconds = 0\n        remaining_minutes += 1\n    \n    # 确保时间不会出现负数\n    if remaining_minutes < 0:\n        remaining_minutes = 0\n        \n    if remaining_seconds < 0:\n        remaining_seconds = 0\n    \n    # 格式化剩余时间\n    remaining_time = f\"{remaining_minutes:01d}分{remaining_seconds:02d}秒\"\n    \n    tips = [\n        f\"⏱️ 提示：本站仅供演示，每10分钟重置一次，下次重置将在 {remaining_time} 后，建议本地部署\",\n        \"🔍 提示：延迟低不一定速度快，建议同时测试延迟和速，本站部署在香港VPS，带宽20Mbps，测试结果仅供参考 \"\n    ]\n\n    # 使用新的轮播结构实现真正无缝的滚动\n    tip_html = \"\"\n    for tip in tips:\n        tip_html += f'<div class=\"marquee-item\">{tip}</div>'\n    \n    st.markdown(f\"\"\"\n    <div class=\"scrolling-text-container\">\n        <div class=\"marquee\">\n            <div class=\"marquee-content\">\n                {tip_html}\n                {tip_html}\n                {tip_html}\n                {tip_html}\n            </div>\n        </div>\n    </div>\n    \"\"\", unsafe_allow_html=True)\n\n# 完善配置文件清理函数\ndef cleanup_config_files():\n    \"\"\"清理所有clash配置文件，包括带有多重后缀的文件\"\"\"\n    settings = load_settings()\n    settings[\"delays\"] = []\n    settings[\"speed\"] = {\n        \"results\": [],\n        \"group_name\": \"\",\n        \"node_count\": 0\n    }\n    settings[\"subscription_links\"] = {}  # 清空订阅链接\n    save_settings(settings)\n    # 清理所有clash_config开头的配置文件\n    config_files = glob.glob(\"clash_config*\")\n    cleaned_count = 0\n    \n    for file_path in config_files:\n        try:\n            os.remove(file_path)\n            cleaned_count += 1\n        except Exception as e:\n            print(f\"删除文件 {file_path} 失败: {str(e)}\")\n    \n    return cleaned_count\n\n# 去重函数\ndef remove_duplicate_proxies(config):\n    \"\"\"移除配置中的重复代理节点并更新代理组\"\"\"\n    if not config or \"proxies\" not in config:\n        return config\n    \n    # 第一步：去除proxies列表中的重复节点\n    unique_proxies = []\n    proxy_names = set()\n    duplicate_count = 0\n    \n    for proxy in config[\"proxies\"]:\n        if \"name\" in proxy and proxy[\"name\"] not in proxy_names:\n            proxy_names.add(proxy[\"name\"])\n            unique_proxies.append(proxy)\n        else:\n            duplicate_count += 1\n    \n    config[\"proxies\"] = unique_proxies\n    \n    # 第二步：处理proxy-groups中的重复节点引用\n    if \"proxy-groups\" in config:\n        for group in config[\"proxy-groups\"]:\n            if \"proxies\" in group:\n                # 为每个组去重\n                unique_group_proxies = []\n                added_proxies = set()\n                group_duplicate_count = 0\n                \n                for proxy_name in group[\"proxies\"]:\n                    # 如果节点名称不在已添加集合中，且是DIRECT/REJECT或存在于主proxies列表中\n                    if proxy_name not in added_proxies:\n                        if (proxy_name in [\"DIRECT\", \"REJECT\"] or \n                            proxy_name in proxy_names or\n                            proxy_name in [g[\"name\"] for g in config[\"proxy-groups\"] if \"name\" in g]):\n                            unique_group_proxies.append(proxy_name)\n                            added_proxies.add(proxy_name)\n                    else:\n                        group_duplicate_count += 1\n                \n                if group_duplicate_count > 0:\n                    print(f\"从组 '{group['name']}' 中移除了 {group_duplicate_count} 个重复节点引用\")\n                group[\"proxies\"] = unique_group_proxies\n    \n    if duplicate_count > 0:\n        print(f\"从主节点列表中移除了 {duplicate_count} 个重复节点\")\n    \n    return config\n\n# 设置文件路径\nSETTINGS_FILE = \"settings.json\"\n\n# 默认链接列表\nDEFAULT_LINKS = [\n\t\"https://proxypool.link/vmess/sub\"\n]\n\n# 加载设置\ndef load_settings():\n    if os.path.exists(SETTINGS_FILE):\n        try:\n            with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:\n                return json.load(f)\n        except Exception as e:\n            print(f\"加载设置文件出错: {e}\")\n    return {\"proxy_links\": DEFAULT_LINKS, \"delays\": [], \"subscription_links\": {}}\n\n# 保存设置\ndef save_settings(settings):\n    try:\n        with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:\n            json.dump(settings, f, ensure_ascii=False, indent=2)\n    except Exception as e:\n        print(f\"保存设置文件出错: {e}\")\n\n# 保存测速结果\ndef save_speed_test_results(results, group_name, node_count):\n    try:\n        settings = load_settings()\n        speed = {\n            \"results\": results, \n            \"group_name\": group_name,\n            \"node_count\": node_count\n        }\n        settings[\"speed\"] = speed\n        save_settings(settings)\n    except Exception as e:\n        print(f\"保存测速结果出错: {e}\")\n\n# 加载测速结果\ndef load_speed_test_results():\n    speed = {\"results\": [], \"group_name\": \"\", \"node_count\": 0}\n    if os.path.exists(SETTINGS_FILE):\n        try:\n            settings = load_settings()\n            speed = settings.get(\"speed\") if settings.get(\"speed\") else speed\n        except Exception as e:\n            print(f\"加载测速结果出错: {e}\")\n    return speed\n\n# 自定义配置生成函数\ndef custom_generate_clash_config(links, nodes=None):\n    \"\"\"包装generate_clash_config函数\"\"\"\n    # 先清理所有旧配置文件\n    cleanup_config_files()\n    \n    # 调用原始函数生成配置\n    result = capture_output(generate_clash_config,links,nodes)\n    \n    return result\n\n\ndef test_proxy_speed(proxy_name, test_url=\"https://speed.cloudflare.com/__down?bytes=100000000\", timeout=5):\n    \"\"\"测试代理下载速度\"\"\"\n    try:\n        print(f\"开始测试节点: {proxy_name}\")\n        \n        # 测试延迟\n        start_time = time.time()\n        delay_url = \"https://www.gstatic.com/generate_204\"\n        resp = requests.get(delay_url, timeout=3)  # 延迟测试固定用3秒超时\n        delay = int((time.time() - start_time) * 1000)  # 毫秒\n        \n        # 测试下载速度 (使用指定的时间段)\n        start_time = time.time()\n        total_length = 0\n        download_time = 0\n        \n        # 使用流式请求，这样我们可以控制下载时间\n        with requests.get(test_url, stream=True, timeout=10) as resp:\n            if resp.status_code != 200:\n                print(f\"测速请求失败: {resp.status_code}\")\n                return {\"speed\": 0, \"delay\": delay}\n                \n            # 下载直到达到指定的时间\n            for chunk in resp.iter_content(chunk_size=1024*1024):  # 1MB的块\n                if chunk:\n                    total_length += len(chunk)\n                    current_time = time.time()\n                    download_time = current_time - start_time\n                    \n                    # 如果已经下载了指定的时间，就停止\n                    if download_time >= timeout:\n                        break\n                        \n        # 计算速度：Bps -> MB/s\n        if download_time > 0:\n            speed_mbps = total_length / download_time / (1024 * 1024)\n        else:\n            speed_mbps = 0\n            \n        print(f\"节点 {proxy_name} 下载速度: {speed_mbps:.2f} MB/s, 延迟: {delay}ms\")\n        return {\"speed\": speed_mbps, \"delay\": delay}\n    \n    except requests.exceptions.Timeout:\n        print(f\"节点 {proxy_name} 测试超时\")\n        return {\"speed\": 0, \"delay\": 0}\n    except Exception as e:\n        print(f\"节点 {proxy_name} 测试出错: {str(e)}\")\n        return {\"speed\": 0, \"delay\": 0}\n\n# 标题\nst.markdown('<h1 class=\"main-header\">ClashForge WebUI</h1>', unsafe_allow_html=True)\n\n# 显示滚动提示\nshow_scrolling_tips()\n\n# 主要功能标签页\ntab1, tab2, tab3 = st.tabs([\"获取节点\", \"测速\", \"配置编辑\"])\n\n# 初始化会话状态\nif \"clash_process\" not in st.session_state:\n    st.session_state.clash_process = None\nif \"clash_running\" not in st.session_state:\n    st.session_state.clash_running = False\nif \"delays\" not in st.session_state:\n    settings = load_settings()\n    st.session_state.delays = settings.get(\"delays\", [])\n\n# 自动启动Clash\ndef is_clash_running():\n    \"\"\"检查Clash是否正在运行\"\"\"\n    try:\n        # 尝试访问Clash API来检查服务是否运行\n        response = requests.get(f\"http://127.0.0.1:9090/proxies\", timeout=2)\n        return response.status_code == 200\n    except:\n        return False\n\ndef _start_clash(rerun=True):\n    # 尝试自动启动Clash（如果未运行）\n    if os.path.exists(\"clash_config.yaml.json\") and not st.session_state.clash_running and not is_clash_running():\n        with st.spinner(\"正在启动Clash...\"):\n            st.session_state.clash_process = capture_output(start_clash)\n            if st.session_state.clash_process:\n                st.session_state.clash_running = True\n                if rerun:\n                    st.rerun()  # 重新运行应用以更新UI\n            else:\n                st.error(\"启动Clash失败，请检查Clash程序是否存在\")\n\ndef _stop_clash(rerun=True):\n    \"\"\"停止 Clash，首先尝试使用进程对象，如果失败则使用 PID\"\"\"\n    if st.session_state.clash_process:\n        try:\n            st.session_state.clash_process.kill()\n            st.session_state.clash_process = None\n            st.session_state.clash_running = False\n            # print(\"Clash 已成功停止\")\n            if rerun:\n                st.rerun()\n        except Exception as e:\n            print(f\"停止 Clash 失败: {str(e)}\")\n    \n    # 如果进程对象不可用，尝试使用 PID\n    kill_clash()\n    st.session_state.clash_process = None\n    st.session_state.clash_running = False\n    if rerun:\n        st.rerun()\n\n# 合并数据\ndef merge_node_data(data, delays):\n    if not delays:\n        return data\n    # 创建 DataFrame\n    df_data = pd.DataFrame(data)\n    df_delays = pd.DataFrame(delays)\n    \n    # 合并\n    df_merged = df_data.merge(\n        df_delays,\n        how=\"left\",  # 保留所有 data 中的记录\n        left_on=\"代理名称\",\n        right_on=\"name\"\n    )\n    \n    # 选择需要的列\n    df_final = df_merged[[\"序号\", \"代理名称\", \"Delay_ms\"]]\n    \n    # 按 Delay_ms 排序，NaN 排最后\n    df_final = df_final.sort_values(by=\"Delay_ms\", ascending=True, na_position=\"last\")\n    \n    # 重新生成序号\n    df_final[\"序号\"] = range(1, len(df_final) + 1)\n    \n    return df_final\n\n# 侧边栏\nwith st.sidebar:\n    st.header(\"配置\")\n    \n    # 代理类型过滤\n    st.subheader(\"代理类型\")\n    proxy_types = st.multiselect(\n        \"选择允许的代理类型\",\n        [\"ss\", \"ssr\", \"vmess\", \"vless\", \"trojan\", \"hysteria2\", \"hy2\"],\n        default=[\"ss\", \"ssr\", \"vmess\", \"vless\", \"trojan\", \"hysteria2\", \"hy2\"]\n    )\n    \n    # 确保input文件夹存在\n    if not os.path.exists(\"input\"):\n        os.makedirs(\"input\")\n    \n    # Clash 控制\n    st.subheader(\"Clash 控制\")\n    \n    # 显示当前状态\n    status_col1, status_col2 = st.columns([1, 3])\n    with status_col1:\n        st.markdown(\"**状态:**\")\n    with status_col2:\n        # 再次检查Clash状态以确保显示准确\n        if st.session_state.clash_running or is_clash_running():\n            # 如果检测到已运行但状态未更新，则更新状态\n            if not st.session_state.clash_running and is_clash_running():\n                st.session_state.clash_running = True\n            st.markdown(\"🟢 **运行中**\")\n        else:\n            st.markdown(\"🔴 **已停止**\")\n    \n    # 控制按钮\n    if not st.session_state.clash_running:\n        if st.button(\"▶️ 启动 Clash\"):\n            _start_clash()\n    else:\n        if st.button(\"⏹️ 停止 Clash\"):\n            _stop_clash()\n    \n    # 工具\n    st.subheader(\"工具\")\n    if st.button(\"下载最新版Clash\"):\n        with st.spinner(\"正在下载...\"):\n            capture_output(download_and_extract_latest_release)\n            st.success(\"下载并解压完成\")\n\n# 获取节点标签页\nwith tab1:\n    st.header(\"获取与管理节点\")\n    \n    # 新增：文件上传功能\n    st.subheader(\"上传配置文件\")\n\n    # 确保input文件夹存在\n    if not os.path.exists(\"input\"):\n        os.makedirs(\"input\")\n    \n    # 单列布局，文件上传后自动保存\n    uploaded_files = st.file_uploader(\n        \"上传配置文件到input文件夹（自动保存）\",\n        accept_multiple_files=True,\n        type=[\"yaml\", \"yml\", \"json\", \"txt\"],\n        help=\"支持YAML, JSON和TXT(每条代理节点占一行)格式的配置文件，上传后自动保存\"\n    )\n    \n    # 如果有文件被上传，自动保存\n    if uploaded_files:\n        saved_files = []\n        for uploaded_file in uploaded_files:\n            file_path = os.path.join(\"input\", uploaded_file.name)\n            \n            # 检查文件是否已经保存过（通过会话状态跟踪）\n            file_key = f\"saved_{uploaded_file.name}_{uploaded_file.size}\"\n            if file_key not in st.session_state:\n                # 读取上传的文件内容并保存\n                with open(file_path, \"wb\") as f:\n                    f.write(uploaded_file.getbuffer())\n                saved_files.append(uploaded_file.name)\n                \n                # 标记为已保存\n                st.session_state[file_key] = True\n        \n        # 如果有新保存的文件，显示成功消息\n        if saved_files:\n            st.success(f\"已自动保存 {len(saved_files)} 个文件到 input 文件夹: {', '.join(saved_files)}\")\n    \n    # 显示当前input文件夹中的文件\n    if os.path.exists(\"input\") and os.listdir(\"input\"):\n        with st.expander(\"已有配置文件\", expanded=False):\n            files = os.listdir(\"input\")\n            files = [f for f in files if f.endswith(('.yaml', '.yml', '.json', '.txt'))]\n            \n            if files:\n                # 创建文件列表表格\n                file_data = [{\"文件名\": file, \"大小\": f\"{os.path.getsize(os.path.join('input', file)) / 1024:.1f} KB\", \n                             \"修改时间\": time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(os.path.getmtime(os.path.join('input', file))))} \n                             for file in files]\n                \n                file_df = pd.DataFrame(file_data)\n                st.dataframe(\n                    file_df,\n                    use_container_width=True,\n                    column_config={\n                        \"文件名\": st.column_config.TextColumn(\"文件名\", width=\"large\"),\n                        \"大小\": st.column_config.TextColumn(\"大小\", width=\"small\"),\n                        \"修改时间\": st.column_config.TextColumn(\"修改时间\", width=\"medium\")\n                    },\n                    hide_index=True\n                )\n                \n                # 添加删除文件功能\n                if st.button(\"清空input文件夹\", key=\"clear_input_folder\"):\n                    for file in files:\n                        try:\n                            os.remove(os.path.join(\"input\", file))\n                        except Exception as e:\n                            st.error(f\"删除文件 {file} 失败: {str(e)}\")\n                    \n                    # 清除保存状态\n                    for key in list(st.session_state.keys()):\n                        if key.startswith(\"saved_\"):\n                            del st.session_state[key]\n                            \n                    st.success(\"成功清空input文件夹\")\n                    st.rerun()\n            else:\n                st.info(\"input文件夹中没有配置文件\")\n    \n    # 移到此处：代理链接配置\n    st.subheader(\"代理链接\")\n    # 加载设置\n    settings = load_settings()\n    # 创建回调函数来处理输入变化\n    def on_text_change():\n        # 当文本区域值发生变化时被调用\n        links_list = [link.strip() for link in st.session_state.proxy_links_input.split(\"\\n\") if link.strip()]\n        settings[\"proxy_links\"] = links_list\n        # 自动保存到设置文件\n        save_settings(settings)\n    \n    # 创建文本输入并绑定到会话状态和回调函数\n    proxy_links = st.text_area(\n        \"输入代理链接 (每行一个)\", \n        value=\"\\n\".join(settings[\"proxy_links\"]), \n        height=150,\n        key=\"proxy_links_input\",\n        on_change=on_text_change\n    )\n\n    # 添加更详细的格式说明\n    with st.expander(\"📌  链接格式说明\", expanded=False):\n        st.markdown(\"\"\"\n        ### 支持的链接格式\n        \n        #### 基本订阅链接\n        直接输入代理订阅链接，支持的订阅类型:\n        - Clash 配置链接\n        - V2ray 订阅链接\n        - Trojan 订阅链接\n        - Vmess 订阅链接\n        - Vless 订阅链接\n        - SS 订阅链接 (需添加 `|ss` 后缀)\n        \n        #### 特殊参数\n        - **特殊格式转换**：\n          - `链接|ss` - 将链接内容作为SS源处理\n          - `链接|links` - 从链接内容中按行正则匹配节点\n        \n        #### 动态时间占位符\n        支持在链接中使用时间占位符，会自动替换成当前日期/时间:\n        - `{Y}` - 四位年份 (2023)\n        - `{m}` - 两位月份 (01-12)\n        - `{d}` - 两位日期 (01-31)\n        - `{Ymd}` - 组合日期 (20230131)\n        - `{Y_m_d}` - 下划线分隔 (2023_01_31)\n        - `{Y-m-d}` - 横线分隔 (2023-01-31)\n        \n        #### GitHub 模糊匹配\n        - `https://raw.githubusercontent.com/user/repo/main/{x}.yaml` - 匹配任意yaml文件\n        \n        #### 示例\n        ```\n        https://cdn.jsdelivr.net/gh/xiaoji235/airport-free/clash/naidounode.txt\n        https://cdn.jsdelivr.net/gh/yangxiaoge/tvbox_cust@master/clash/Clash2.yml\n        https://gy.xiaozi.us.kg/sub?token=lzj666\n        https://igdux.top/5Hna\n        https://mxlsub.me/newfull\n        https://proxypool.link/ss/sub|ss\n        https://proxypool.link/trojan/sub\n        https://proxypool.link/vmess/sub\n        https://raw.githubusercontent.com/Q3dlaXpoaQ/V2rayN_Clash_Node_Getter/refs/heads/main/APIs/sc0.yaml\n        https://raw.githubusercontent.com/Q3dlaXpoaQ/V2rayN_Clash_Node_Getter/refs/heads/main/APIs/sc1.yaml\n        https://raw.githubusercontent.com/Q3dlaXpoaQ/V2rayN_Clash_Node_Getter/refs/heads/main/APIs/sc2.yaml\n        https://raw.githubusercontent.com/Q3dlaXpoaQ/V2rayN_Clash_Node_Getter/refs/heads/main/APIs/sc3.yaml\n        https://raw.githubusercontent.com/Q3dlaXpoaQ/V2rayN_Clash_Node_Getter/refs/heads/main/APIs/sc4.yaml\n        https://raw.githubusercontent.com/Roywaller/clash_subscription/refs/heads/main/clash_subscription.txt\n        https://raw.githubusercontent.com/Ruk1ng001/freeSub/main/clash.yaml\n        https://raw.githubusercontent.com/SoliSpirit/v2ray-configs/main/all_configs.txt\n        https://raw.githubusercontent.com/a2470982985/getNode/main/clash.yaml\n        https://raw.githubusercontent.com/aiboboxx/clashfree/refs/heads/main/clash.yml\n        https://raw.githubusercontent.com/aiboboxx/v2rayfree/refs/heads/main/README.md\n        https://raw.githubusercontent.com/anaer/Sub/refs/heads/main/clash.yaml\n        https://raw.githubusercontent.com/chengaopan/AutoMergePublicNodes/master/list.yml\n        https://raw.githubusercontent.com/ermaozi/get_subscribe/main/subscribe/clash.yml\n        https://raw.githubusercontent.com/firefoxmmx2/v2rayshare_subcription/refs/heads/main/subscription/clash_sub.yaml\n        https://raw.githubusercontent.com/free18/v2ray/refs/heads/main/c.yaml\n        https://raw.githubusercontent.com/go4sharing/sub/main/sub.yaml\n        https://raw.githubusercontent.com/leetomlee123/freenode/refs/heads/main/README.md\n        https://raw.githubusercontent.com/ljlfct01/ljlfct01.github.io/refs/heads/main/节点\n        https://raw.githubusercontent.com/mahdibland/SSAggregator/master/sub/sub_merge_yaml.yml\n        https://raw.githubusercontent.com/mahdibland/ShadowsocksAggregator/master/Eternity.yml\n        https://raw.githubusercontent.com/mahdibland/ShadowsocksAggregator/master/LogInfo.txt\n        https://raw.githubusercontent.com/mai19950/clashgithub_com/refs/heads/main/site\n        https://raw.githubusercontent.com/mfbpn/tg_mfbpn_sub/main/trial.yaml\n        https://raw.githubusercontent.com/mfuu/v2ray/master/clash.yaml\n        https://raw.githubusercontent.com/mgit0001/test_clash/refs/heads/main/heima.txt\n        https://raw.githubusercontent.com/peasoft/NoMoreWalls/master/list.meta.yml\n        https://raw.githubusercontent.com/peasoft/NoMoreWalls/master/list.yml\n        https://raw.githubusercontent.com/Pawdroid/Free-servers/refs/heads/main/sub\n        https://raw.githubusercontent.com/ripaojiedian/freenode/main/clash\n        https://raw.githubusercontent.com/shahidbhutta/Clash/refs/heads/main/Router\n        https://raw.githubusercontent.com/skka3134/Free-servers/refs/heads/main/README.md|linnks\n        https://raw.githubusercontent.com/snakem982/proxypool/main/source/clash-meta.yaml\n        https://raw.githubusercontent.com/vxiaov/free_proxies/main/clash/clash.provider.yaml\n        https://raw.githubusercontent.com/wangyingbo/yb_clashgithub_sub/main/clash_sub.yml\n        https://raw.githubusercontent.com/xiaoer8867785/jddy5/refs/heads/main/data/{Y_m_d}/{x}.yaml\n        https://raw.githubusercontent.com/xiaoji235/airport-free/refs/heads/main/clash/naidounode.txt\n        https://raw.githubusercontent.com/zhangkaiitugithub/passcro/main/speednodes.yaml\n        https://raw.githubusercontent.com/aiboboxx/clashfree/refs/heads/main/clash.yml\n        https://raw.githubusercontent.com/ljlfct01/ljlfct01.github.io/refs/heads/main/节点\n        https://raw.githubusercontent.com/mahdibland/ShadowsocksAggregator/master/LogInfo.txt\n        https://raw.githubusercontent.com/wangyingbo/yb_clashgithub_sub/main/clash_sub.yml\n        https://SOS.CMLiussss.net/auto\n        https://sub.fqzsnai.ggff.net/auto\n        https://sub.mikeone.ggff.net/sub?token=6e300fe82f12874e439b76693aa179fb\n        https://sub.reajason.eu.org/clash.yaml\n        https://v1.mk/HuaplNe\n        https://www.freeclashnode.com/uploads/{Y}/{m}/0-{Ymd}.yaml\n        https://www.freeclashnode.com/uploads/{Y}/{m}/1-{Ymd}.yaml\n        https://zrf.zrf.me/zrf\n        ```\n        \"\"\")\n    \n    # 处理链接 - 直接从设置中读取最新的链接\n    links = [link.strip() for link in settings[\"proxy_links\"] if link.strip()]\n    \n    # 显示当前保存的链接数量\n    st.caption(f\"当前保存了 {len(links)} 个链接，链接已自动保存\")\n    \n    st.divider()\n    \n    if 'show_greeting' not in st.session_state:\n        st.session_state.show_greeting = False\n\n    # 获取节点按钮\n    if st.button(\"批量获取节点\", key=\"get_nodes_btn\"):\n        # 停止clash\n        st.cache_data.clear() # 显式清理缓存\n        st.cache_resource.clear()\n        st.session_state.show_greeting = True  # 设置提示标志\n        _stop_clash()\n    if st.session_state.show_greeting:\n        st.session_state.show_greeting = False\n        # 清理所有旧配置文件\n        cleanup_config_files()\n        # 重新加载设置，确保使用最新保存的链接\n        settings = load_settings()\n        links = [link.strip() for link in settings[\"proxy_links\"] if link.strip()]\n        if not links and not (os.path.exists(\"input\") and any(f.endswith(('.yaml', '.yml', '.json', '.txt')) for f in os.listdir(\"input\"))):\n            st.warning(\"请先添加至少一个代理链接或上传配置文件\")\n        else:\n            # 清空测速结果（因为将要生成新配置）\n            st.session_state.speed_test_results = []\n            st.session_state.speed_test_group = \"\"\n            st.session_state.test_node_count = 0\n            save_speed_test_results([], \"\", 0)\n            \n            with st.spinner(\"正在获取代理并生成配置...\"):\n                # 显示进度条\n                progress_bar = st.progress(0)\n                progress_text = st.empty()\n                \n                # 阶段1：读取并合并节点\n                progress_text.text(\"读取节点中...\")\n                progress_bar.progress(0.1)\n                \n                # 从input文件夹读取节点\n                nodes = capture_output(read_yaml_files, folder_path=\"input\") or []\n                \n                # 过滤节点类型\n                if proxy_types and nodes:\n                    nodes = capture_output(filter_by_types_alt, proxy_types, nodes=nodes) or []\n                \n                # 读取链接\n                progress_bar.progress(0.3)\n                txt_links = capture_output(read_txt_files, folder_path=\"input\") or []\n                all_links = capture_output(merge_lists, txt_links, links) or links\n                \n                # 阶段2：生成配置\n                progress_text.text(\"生成配置文件中...\")\n                progress_bar.progress(0.5)\n\n                if all_links or nodes:\n                    # 使用自定义函数替代原函数，添加去重功能\n                    custom_generate_clash_config(all_links, nodes)\n                    all_links = []\n                    nodes = []\n                    # 阶段3：测试节点（如果Clash正在运行）\n                    _start_clash(rerun=False)\n                    clash_actually_running = st.session_state.clash_running or is_clash_running()\n                    if clash_actually_running:\n                        # 更新会话状态，确保一致性\n                        if not st.session_state.clash_running and is_clash_running():\n                            st.session_state.clash_running = True\n                        progress_text.text(\"测试节点延迟中...\")\n                        progress_bar.progress(0.7)\n                        try:\n                            switch_proxy('DIRECT')\n                            st.session_state.delays = capture_output(asyncio.run, proxy_clean())\n                            settings[\"delays\"] = st.session_state.delays\n                            save_settings(settings)\n                        except Exception as e:\n                            print(f\"proxy_clean 失败: {str(e)}\")\n                            st.session_state.delays = []\n                            settings[\"delays\"] = []\n                            save_settings(settings)\n                        finally:\n                            switch_proxy('DIRECT')\n                    # 完成\n                    progress_bar.progress(1.0)\n                    progress_text.text(\"完成！\")\n                    st.success(\"成功获取代理并生成配置文件\")\n                    st.rerun()\n                else:\n                    st.warning(\"没有找到可用的代理链接或节点\")\n\n    \n    # 显示节点列表\n    st.subheader(\"节点列表\")\n    if os.path.exists(\"clash_config.yaml\"):\n        config = ClashConfig(\"clash_config.yaml\")\n        group_names = config.get_group_names()\n        group_names = [x for x in group_names if x != '节点选择']\n        # 选择代理组\n        selected_group = st.selectbox(\n            \"选择代理组\", \n            options=group_names,\n            key=\"node_group_select\"\n        )\n        \n        # 显示节点\n        if selected_group:\n            proxies = config.get_group_proxies(selected_group)\n            st.write(f\"**{selected_group}** 包含 **{len(proxies)}** 个有效节点\")\n            \n            # 过滤特殊节点\n            proxies = [p for p in proxies if p not in [\"DIRECT\", \"REJECT\"]]\n            \n            # 创建数据表格\n            data = [{\"序号\": i+1, \"代理名称\": proxy} for i, proxy in enumerate(proxies)]\n            \n            # 显示表格\n            if data:\n                df = merge_node_data(data, st.session_state.delays)\n                st.dataframe(\n                    df,\n                    use_container_width=True,\n                    height=400,\n                    column_config={\n                        \"序号\": st.column_config.NumberColumn(\"序号\", width=60),\n                        \"代理名称\": st.column_config.TextColumn(\"代理名称\", width=\"large\"),\n                        \"Delay_ms\": st.column_config.NumberColumn(\"延迟(ms)\", format=\"%.2f ms\")\n                    },\n                    hide_index=True\n                )\n            else:\n                st.info(f\"{selected_group} 组中没有节点\")\n    else:\n        st.info(\"尚未生成配置文件，请先获取节点\")\n\n# 测速标签页\nwith tab2:\n    st.header(\"节点测速\")\n    \n    # 初始化会话状态中的测速结果\n    if \"speed_test_results\" not in st.session_state:\n        # 从文件加载测速结果\n        saved_results = load_speed_test_results()\n        st.session_state.speed_test_results = saved_results[\"results\"]\n        st.session_state.speed_test_group = saved_results[\"group_name\"]\n        st.session_state.test_node_count = saved_results[\"node_count\"]\n    \n    # 使用函数检查Clash是否真的在运行\n    clash_actually_running = st.session_state.clash_running or is_clash_running()\n    if not clash_actually_running:\n        st.warning(\"Clash 服务未运行，请稍等片刻或检查服务状态\")\n    else:\n        # 确保会话状态与实际状态一致\n        if not st.session_state.clash_running and is_clash_running():\n            st.session_state.clash_running = True\n        \n        # 测速设置\n        st.subheader(\"测速设置\")\n        col1, col2 = st.columns(2)\n        with col1:\n            test_node_count = st.number_input(\"测试节点数量\", \n                min_value=1, \n                max_value=100, \n                value=10, \n                step=1,\n                help=\"设置要测试的节点数量（按列表顺序）\")\n            \n        with col2:\n            test_timeout = st.number_input(\"下载测试时间 (秒)\", \n                min_value=1, \n                max_value=30, \n                value=3, \n                step=1,\n                help=\"每个节点将下载指定秒数后停止，时间越长测试越准确，但总耗时也更长\")\n        \n        # 选择要测速的代理组\n        if os.path.exists(\"clash_config.yaml\"):\n            config = ClashConfig(\"clash_config.yaml\")\n            group_names = config.get_group_names()\n            group_names = [x for x in group_names if x != '节点选择']\n            \n            selected_group = st.selectbox(\n                \"选择代理组进行测速\", \n                options=group_names,\n                key=\"speed_test_group_select\"\n            )\n            \n            # 显示节点和开始测速按钮\n            if selected_group:\n                all_proxies = config.get_group_proxies(selected_group)\n                all_proxies = [p for p in all_proxies if p not in [\"DIRECT\", \"REJECT\"]]\n                \n                if not all_proxies:\n                    st.info(f\"{selected_group} 组中没有节点\")\n                else:\n                    # 限制测试节点数量\n                    test_node_count = min(test_node_count, len(all_proxies))\n                    proxies = all_proxies[:test_node_count]\n                    \n                    st.write(f\"**{selected_group}** 包含 **{len(all_proxies)}** 个节点，将测试前 **{test_node_count}** 个\")\n                    \n                    # 开始测速按钮\n                    if st.button(\"开始测速\", key=\"start_speed_test\"):\n                        if not proxies:\n                            st.warning(\"没有可测试的节点\")\n                        else:\n                            with st.spinner(\"正在测速中...\"):\n                                # 创建进度条\n                                progress_bar = st.progress(0)\n                                progress_text = st.empty()\n                                \n                                # 创建结果列表\n                                results = []\n                                total = len(proxies)\n                                \n                                # 测试每个节点\n                                for i, proxy in enumerate(proxies):\n                                    progress_text.text(f\"测试节点 {i+1}/{total}: {proxy} (下载测试 {test_timeout}秒)\")\n                                    progress_bar.progress((i) / total)\n                                    \n                                    # 调用测速函数\n                                    try:\n                                        capture_output(switch_proxy, proxy_name=proxy)\n                                        speed_result = capture_output(test_proxy_speed, proxy_name=proxy, timeout=test_timeout)\n                                        \n                                        # 将结果添加到列表\n                                        if speed_result and isinstance(speed_result, dict):\n                                            speed_result[\"name\"] = proxy\n                                            results.append(speed_result)\n                                        else:\n                                            print(f\"节点 {proxy} 测速失败\")\n                                    except Exception as e:\n                                        print(f\"节点 {proxy} 测速出错: {str(e)}\")\n                                \n                                # 完成测速\n                                progress_bar.progress(1.0)\n                                progress_text.text(\"测速完成！\")\n                                \n                                # 按速度排序结果（不再过滤）\n                                if results:\n                                    # 按速度排序\n                                    results.sort(key=lambda x: x.get(\"speed\", 0), reverse=True)\n                                    # 格式化数据\n                                    table_data = []\n                                    sorted_proxy_names = []\n                                    for i, result in enumerate(results):\n                                        table_data.append({\n                                            \"序号\": i+1,\n                                            \"节点名称\": result.get(\"name\", \"未知\"),\n                                            \"下载速度 (MB/s)\": round(result.get(\"speed\", 0), 2),\n                                            \"延迟 (ms)\": result.get(\"delay\", 0)\n                                        })\n                                        sorted_proxy_names.append(result[\"name\"])\n                                    # 排序好的节点名放入clash_config.yaml的group-proxies\n                                    new_list = sorted_proxy_names.copy()\n                                    added_elements = set(new_list)\n                                    group_proxies = config.get_group_proxies(selected_group)\n                                    for item in group_proxies:\n                                        if item not in added_elements:\n                                            new_list.append(item)\n                                            added_elements.add(item) \n                                    for group_name in group_names:\n                                        for group in config.proxy_groups:\n                                            if group[\"name\"] == group_name:\n                                                group[\"proxies\"] = new_list\n                                    config.save()\n\n                                    # 将结果存储到会话状态\n                                    st.session_state.speed_test_results = table_data\n                                    st.session_state.speed_test_group = selected_group\n                                    st.session_state.test_node_count = test_node_count\n                                    \n                                    # 保存测速结果到文件\n                                    save_speed_test_results(table_data, selected_group, test_node_count)\n                                else:\n                                    # 如果没有结果，清空会话状态的测速结果\n                                    st.session_state.speed_test_results = []\n                                    st.session_state.speed_test_group = \"\"\n                                    st.session_state.test_node_count = 0\n                                    # 清空保存的测速结果\n                                    save_speed_test_results([], \"\", 0)\n                                    st.warning(\"测速失败，未获取到任何有效结果\")\n                                \n                                # 切换回DIRECT\n                                capture_output(switch_proxy, proxy_name=\"DIRECT\")\n                    \n                    # 显示保存在会话状态中的测速结果\n                    if st.session_state.speed_test_results:\n                        # 显示测试信息\n                        st.subheader(f\"测速结果 - {st.session_state.speed_test_group}\")\n                        st.info(f\"已测试 {len(st.session_state.speed_test_results)} 个节点，按下载速度排序\")\n                        \n                        # 显示结果表格\n                        df = pd.DataFrame(st.session_state.speed_test_results)\n                        st.dataframe(\n                            df,\n                            use_container_width=True,\n                            column_config={\n                                \"序号\": st.column_config.NumberColumn(\"序号\", width=60),\n                                \"节点名称\": st.column_config.TextColumn(\"节点名称\", width=\"large\"),\n                                \"下载速度 (MB/s)\": st.column_config.NumberColumn(\"下载速度 (MB/s)\", format=\"%.2f MB/s\"),\n                                \"延迟 (ms)\": st.column_config.NumberColumn(\"延迟 (ms)\", format=\"%d ms\")\n                            },\n                            hide_index=True\n                        )\n                        \n                        # 添加清除结果按钮\n                        if st.button(\"清除测速结果\", key=\"clear_speed_results\"):\n                            st.session_state.speed_test_results = []\n                            st.session_state.speed_test_group = \"\"\n                            st.session_state.test_node_count = 0\n                            # 清空保存的测速结果\n                            save_speed_test_results([], \"\", 0)\n                            st.rerun()\n        else:\n            st.info(\"尚未生成配置文件，请先在'获取节点'标签页获取节点\")\n\n# 配置编辑标签页\nwith tab3:\n    st.header(\"配置编辑\")\n    \n    # 获取订阅地址功能\n    st.subheader(\"生成订阅链接\")\n    st.info(\"此功能将生成永久订阅链接，即使重置也不会失效\")\n    \n    # 添加文件存储安全性提示\n    with st.expander(\"📌  关于订阅文件存储与安全性的说明\", expanded=True):\n        st.markdown(\"\"\"\n            **文件存储说明**:\n            - 默认同时生成`clash`和`singbox`订阅链接\n            - 您的订阅配置文件**不会**保存在服务器上，可以放心使用\n            - 文件实际托管在 [catbox.moe](https://catbox.moe) (需翻)的文件服务上\n        \"\"\")\n    # 加载保存的订阅链接（如果有）\n    settings = load_settings()\n    subscription_links = settings.get(\"subscription_links\", {})\n    \n    if not os.path.exists(\"clash_config.yaml\"):\n        st.warning(\"未找到配置文件，请先在'获取节点'标签页生成配置文件\")\n    else:\n        # 根据是否已有链接显示不同的按钮文本\n        button_text = \"重新生成链接\" if subscription_links and (\"clash_url\" in subscription_links or \"singbox_url\" in subscription_links) else \"生成订阅链接\"\n        \n        # 显示生成/重新生成按钮\n        if st.button(button_text, key=\"gen_subscription_links\"):\n            _stop_clash(rerun=False)\n            with st.spinner(\"正在上传配置并生成链接...\"):\n                try:\n                    # 调用upload_and_generate_urls方法获取订阅链接\n                    links = upload_and_generate_urls(\"clash_config.yaml\")\n                    \n                    # 显示链接结果\n                    if links and isinstance(links, dict):\n                        # 保存链接到设置文件\n                        settings[\"subscription_links\"] = links\n                        save_settings(settings)\n                        \n                        st.success(\"订阅链接生成成功\")\n                        st.rerun()  # 重新加载页面以显示保存的链接\n                    else:\n                        st.error(\"未获取到有效的订阅链接，返回数据类型：\" + str(type(links)))\n                        st.write(\"返回数据内容：\", links)\n                except Exception as e:\n                    st.error(f\"生成订阅链接失败: {str(e)}\")\n                    import traceback\n                    st.code(traceback.format_exc())\n        \n        # 如果已经有保存的订阅链接，显示链接和复制功能\n        if subscription_links and (\"clash_url\" in subscription_links or \"singbox_url\" in subscription_links):\n            # 显示Clash链接\n            if \"clash_url\" in subscription_links and subscription_links[\"clash_url\"]:\n                st.subheader(\"Clash 订阅\")\n                clash_link = subscription_links[\"clash_url\"]\n                \n                # 创建一行用于显示链接和复制按钮\n                col1, col2 = st.columns([4, 1])\n                with col1:\n                    st.code(clash_link)\n                with col2:\n                    # 使用HTML组件实现可靠的复制功能，优化样式适应手机\n                    components.html(\n                        f\"\"\"\n                        <style>\n                            @media (max-width: 768px) {{\n                                .copy-button-container {{\n                                    width: 100% !important;\n                                }}\n                                .copy-button {{\n                                    width: 100% !important;\n                                }}\n                            }}\n                        </style>\n                        <div style=\"display:flex; justify-content:center; align-items:center; height:100%;\" class=\"copy-button-container\">\n                            <button \n                                onclick=\"\n                                    navigator.clipboard.writeText('{clash_link}');\n                                    this.textContent = '已复制!';\n                                    setTimeout(() => this.textContent = '复制链接', 2000);\n                                    parent.postMessage({{type: 'streamlit:toast', data: {{icon: '✅', body: 'Clash链接已复制到剪贴板！'}} }}, '*');\n                                \"\n                                style=\"background-color: #4CAF50; color: white; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; font-size: 14px; white-space: nowrap; display: block;\"\n                                class=\"copy-button\"\n                            >\n                                复制链接\n                            </button>\n                        </div>\n                        \"\"\",\n                        height=73,  # 匹配code块高度\n                    )\n            \n            # 显示SingBox链接\n            if \"singbox_url\" in subscription_links and subscription_links[\"singbox_url\"]:\n                st.subheader(\"SingBox 订阅\")\n                singbox_link = subscription_links[\"singbox_url\"]\n                \n                # 创建一行用于显示链接和复制按钮\n                col1, col2 = st.columns([4, 1])\n                with col1:\n                    st.code(singbox_link)\n                with col2:\n                    # 使用HTML组件实现可靠的复制功能，优化样式适应手机\n                    components.html(\n                        f\"\"\"\n                        <style>\n                            @media (max-width: 768px) {{\n                                .copy-button-container {{\n                                    width: 100% !important;\n                                }}\n                                .copy-button {{\n                                    width: 100% !important;\n                                }}\n                            }}\n                        </style>\n                        <div style=\"display:flex; justify-content:center; align-items:center; height:100%;\" class=\"copy-button-container\">\n                            <button \n                                onclick=\"\n                                    navigator.clipboard.writeText('{singbox_link}');\n                                    this.textContent = '已复制!';\n                                    setTimeout(() => this.textContent = '复制链接', 2000);\n                                    parent.postMessage({{type: 'streamlit:toast', data: {{icon: '✅', body: 'SingBox链接已复制到剪贴板！'}} }}, '*');\n                                \"\n                                style=\"background-color: #4CAF50; color: white; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; font-size: 14px; white-space: nowrap; display: block;\"\n                                class=\"copy-button\"\n                            >\n                                复制链接\n                            </button>\n                        </div>\n                        \"\"\",\n                        height=73,  # 匹配code块高度\n                    )\n\n    st.divider()\n    \n    # 配置文件管理\n    st.subheader(\"配置文件管理\")\n    st.info(\"此功能可以删除当前生成的所有配置文件\")\n    \n    # 检查是否存在配置文件\n    config_files = []\n    if os.path.exists(\"clash_config.yaml\"):\n        config_files.append(\"clash_config.yaml\")\n    if os.path.exists(\"clash_config.yaml.json\"):\n        config_files.append(\"clash_config.yaml.json\")\n        \n    if not config_files:\n        st.warning(\"未找到配置文件，请先在'获取节点'标签页生成配置文件\")\n    else:\n        # 创建确认状态（如果不存在）\n        if \"confirm_clean\" not in st.session_state:\n            st.session_state.confirm_clean = False\n        \n        # 仅当未处于确认状态时显示清理按钮\n        if not st.session_state.confirm_clean:\n            if st.button(\"清理配置文件\", key=\"clean_config_files\"):\n                # 设置确认状态\n                st.session_state.confirm_clean = True\n                # 立即重新运行以隐藏按钮\n                st.rerun()\n        \n        # 显示确认窗口\n        if st.session_state.confirm_clean:\n            st.warning(\"您确定要删除所有配置文件吗？此操作不可恢复。\")\n            confirm_col1, confirm_col2 = st.columns(2)\n            with confirm_col1:\n                if st.button(\"确认删除\", key=\"confirm_clean_btn\"):\n                    deleted_files = []\n                    # 删除配置文件\n                    for config_file in config_files:\n                        try:\n                            os.remove(config_file)\n                            deleted_files.append(config_file)\n                        except Exception as e:\n                            st.error(f\"删除文件 {config_file} 失败: {str(e)}\")\n                    \n                    # 清空测速结果\n                    if \"speed_test_results\" in st.session_state:\n                        st.session_state.speed_test_results = []\n                        st.session_state.speed_test_group = \"\"\n                        st.session_state.test_node_count = 0\n                        save_speed_test_results([], \"\", 0)\n                    \n                    # 显示成功消息\n                    if deleted_files:\n                        st.success(f\"已成功删除以下配置文件: {', '.join(deleted_files)}\")\n                        st.session_state.confirm_clean = False\n                        # 延迟一秒后刷新页面\n                        time.sleep(1)\n                        st.rerun()\n            \n            with confirm_col2:\n                if st.button(\"取消\", key=\"cancel_clean_btn\"):\n                    st.session_state.confirm_clean = False\n                    st.rerun()\n    \n    st.divider()\n    \n    # 配置内容编辑\n    st.subheader(\"配置内容编辑\")\n    st.info(\"此功能可以直接编辑配置文件的内容\")\n    \n    if not config_files:\n        st.warning(\"未找到配置文件，请先在'获取节点'标签页生成配置文件\")\n    else:\n        # 选择要编辑的配置文件\n        selected_config = st.selectbox(\n            \"选择配置文件\", \n            options=config_files,\n            key=\"edit_config_select\"\n        )\n        \n        # 读取选中的配置文件内容\n        try:\n            with open(selected_config, 'r', encoding='utf-8') as f:\n                config_content = f.read()\n            \n            # 根据文件类型提供更好的编辑体验\n            file_type = \"yaml\" if selected_config.endswith(\".yaml\") else \"json\"\n            \n            edited_content = st.text_area(\n                \"配置内容（直接编辑）\", \n                value=config_content,\n                height=500,\n                key=\"config_editor\"\n            )\n            \n            # 尝试验证YAML/JSON格式\n            is_valid = True\n            validation_error = \"\"\n            \n            try:\n                if file_type == \"yaml\":\n                    yaml.safe_load(edited_content)\n                else:  # JSON\n                    json.loads(edited_content)\n            except Exception as e:\n                is_valid = False\n                validation_error = str(e)\n            \n            # 显示验证结果\n            if not is_valid:\n                st.error(f\"配置格式错误: {validation_error}\")\n                st.warning(\"请修复格式错误后再保存\")\n            \n            # 使用HTML组件创建并排按钮，确保在移动设备上也保持一行\n            save_disabled = \"disabled\" if not is_valid else \"\"\n            \n            # 格式化后下载\n            download_data = edited_content\n            if is_valid:\n                try:\n                    if file_type == \"yaml\":\n                        yaml_data = yaml.safe_load(edited_content)\n                        download_data = yaml.dump(yaml_data, default_flow_style=False, sort_keys=False, allow_unicode=True)\n                    elif file_type == \"json\":\n                        json_data = json.loads(edited_content)\n                        download_data = json.dumps(json_data, ensure_ascii=False, indent=2)\n                except:\n                    # 如果格式化失败，使用原始内容\n                    pass\n            \n            # 使用固定容器创建按钮布局，确保始终保持一行显示\n            button_container = st.container()\n            with button_container:\n                # 使用CSS设置按钮容器为flex布局\n                st.markdown(\"\"\"\n                <style>\n                    div[data-testid=\"column\"]:nth-of-type(1) .stButton,\n                    div[data-testid=\"column\"]:nth-of-type(2) .stButton {\n                        width: 100%;\n                        min-width: 0;\n                    }\n                    div[data-testid=\"column\"]:nth-of-type(1) .stButton > button,\n                    div[data-testid=\"column\"]:nth-of-type(2) .stButton > button {\n                        width: 100%;\n                        white-space: nowrap;\n                    }\n                    .button-flex-container div.row-widget.stHorizontal {\n                        flex-wrap: nowrap !important;\n                    }\n                </style>\n                <div class=\"button-flex-container\">\n                \"\"\", unsafe_allow_html=True)\n                \n                col1, col2 = st.columns(2)\n                with col1:\n                    save_clicked = st.button(\n                        \"保存修改\",\n                        key=\"save_config_btn\",\n                        disabled=not is_valid,\n                        help=\"保存修改到配置文件\",\n                        type=\"primary\",\n                        use_container_width=True\n                    )\n                    \n                    # 处理保存按钮点击\n                    if save_clicked:\n                        try:\n                            # 额外的格式化处理\n                            if file_type == \"yaml\" and is_valid:\n                                # 将YAML重新格式化保存\n                                yaml_data = yaml.safe_load(edited_content)\n                                with open(selected_config, 'w', encoding='utf-8') as f:\n                                    yaml.dump(yaml_data, f, default_flow_style=False, sort_keys=False, allow_unicode=True)\n                            elif file_type == \"json\" and is_valid:\n                                # 将JSON重新格式化保存\n                                json_data = json.loads(edited_content)\n                                with open(selected_config, 'w', encoding='utf-8') as f:\n                                    json.dump(json_data, f, ensure_ascii=False, indent=2)\n                            else:\n                                # 直接保存文本\n                                with open(selected_config, 'w', encoding='utf-8') as f:\n                                    f.write(edited_content)\n                            \n                            # 清空订阅链接，因为配置已经修改\n                            settings = load_settings()\n                            settings[\"subscription_links\"] = {}\n                            save_settings(settings)\n                            \n                            st.success(\"配置已保存并且订阅链接已重置\")\n                            st.rerun()  # 重新加载页面以反映变化\n                        except Exception as e:\n                            st.error(f\"保存配置失败: {str(e)}\")\n                \n                with col2:\n                    # 下载按钮\n                    st.download_button(\n                        label=\"下载配置文件\",\n                        data=download_data,\n                        file_name=selected_config,\n                        mime=\"text/plain\" if file_type == \"yaml\" else \"application/json\",\n                        key=\"download_config_btn\",\n                        help=\"下载当前配置文件\",\n                        type=\"secondary\",\n                        use_container_width=True\n                    )\n                \n                st.markdown(\"</div>\", unsafe_allow_html=True)\n        \n        except Exception as e:\n            st.error(f\"读取配置文件失败: {str(e)}\")\n\n\n# 页脚\nst.markdown(\"---\")\nst.markdown(\n    \"\"\"\n    <div class=\"footer\">\n        <a href=\"https://github.com/fish2018/ClashForge\">ClashForge</a> | \n        <a href=\"https://t.me/s/tgsearchers\">TG频道资源宇宙</a> | \n        <a href=\"https://proxy.252035.xyz/\">订阅转换</a>  \n    </div>\n    \"\"\",\n    unsafe_allow_html=True\n)\n\n# 不蒜子访问统计\nbusuanzi_html = \"\"\"\n<script async src=\"//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js\"></script>\n<div class=\"footer\" style=\"text-align: center;\">\n    <span id=\"busuanzi_container_site_pv\">\n        本站总访问量 <span id=\"busuanzi_value_site_pv\"></span> 次\n    </span>\n     | \n    <span id=\"busuanzi_container_site_uv\">\n        本站访客数 <span id=\"busuanzi_value_site_uv\"></span> 人\n    </span>\n</div>\n\"\"\"\ncomponents.html(busuanzi_html, height=100)\n\n"
  },
  {
    "path": "requirements.txt",
    "content": "httpx==0.27.2\nPyYAML==6.0.2\nstreamlit==1.30.0\npandas==2.1.3\nwatchdog==3.0.0\npsutil==7.0.0\nrequests==2.27.1\nrequests-html==0.10.0\nlxml_html_clean==0.2.1\npyppeteer==2.0.0\n"
  }
]