├── .github └── workflows │ └── package-worker.yml ├── Clash └── config │ ├── ACL4SSR_Online_Full.ini │ ├── ACL4SSR_Online_Full_MultiMode.ini │ └── ACL4SSR_Online_Full_MultiMode_DE.ini ├── LICENSE ├── README.md ├── _worker.js └── worker.zip /.github/workflows/package-worker.yml: -------------------------------------------------------------------------------- 1 | name: Package Worker # 工作流程的名称 2 | 3 | on: # 触发事件 4 | workflow_dispatch: # 手动触发 5 | push: # 当代码被推送到仓库时触发 6 | paths: # 指定触发条件的文件路径 7 | - '_worker.js' # 当_worker.js文件发生变动时触发 8 | 9 | jobs: # 工作流程中的任务 10 | package-and-commit: # 任务名称 11 | runs-on: ubuntu-latest # 运行环境,这里使用最新版本的Ubuntu 12 | steps: # 任务步骤 13 | - name: Checkout Repository # 步骤名称,检出代码 14 | uses: actions/checkout@v2 # 使用actions/checkout动作 15 | 16 | - name: Zip the worker file # 将_worker.js文件打包成worker.zip 17 | run: zip worker.zip _worker.js # 使用zip命令直接打包 18 | 19 | - name: Commit and push the packaged file # 提交并推送打包后的文件 20 | uses: EndBug/add-and-commit@v7 # 使用EndBug/add-and-commit动作 21 | with: 22 | add: 'worker.zip' # 指定要提交的文件 23 | message: 'Automatically package and commit worker.zip' # 提交信息 24 | author_name: github-actions[bot] # 提交者名称 25 | author_email: actions[bot]@users.noreply.github.com # 提交者邮箱 26 | token: ${{ secrets.GH_TOKEN }} # 使用GH_TOKEN作为身份验证 27 | -------------------------------------------------------------------------------- /Clash/config/ACL4SSR_Online_Full.ini: -------------------------------------------------------------------------------- 1 | [custom] 2 | ;不要随意改变关键字,否则会导致出错 3 | ;acl4SSR规则 4 | 5 | ;去广告:支持 6 | ;自动测速:支持 7 | ;微软分流:支持 8 | ;苹果分流:支持 9 | ;增强中国IP段:支持 10 | ;增强国外GFW:支持 11 | 12 | ;设置规则标志位 13 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/LocalAreaNetwork.list 14 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/UnBan.list 15 | ruleset=🛑 广告拦截,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/BanAD.list 16 | ruleset=🍃 应用净化,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/BanProgramAD.list 17 | ruleset=📢 谷歌FCM,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/GoogleFCM.list 18 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/GoogleCN.list 19 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/SteamCN.list 20 | ruleset=Ⓜ️ 微软Bing,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Bing.list 21 | ruleset=Ⓜ️ 微软云盘,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/OneDrive.list 22 | ruleset=Ⓜ️ 微软服务,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Microsoft.list 23 | ruleset=🍎 苹果服务,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Apple.list 24 | ruleset=📲 电报消息,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Telegram.list 25 | ruleset=🤖 OpenAi,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/OpenAi.list 26 | ruleset=🤖 OpenAi,https://raw.githubusercontent.com/juewuy/ShellClash/master/rules/ai.list 27 | ruleset=🎶 网易音乐,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/NetEaseMusic.list 28 | ruleset=🎮 游戏平台,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Epic.list 29 | ruleset=🎮 游戏平台,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Origin.list 30 | ruleset=🎮 游戏平台,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Sony.list 31 | ruleset=🎮 游戏平台,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Steam.list 32 | ruleset=🎮 游戏平台,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Nintendo.list 33 | ruleset=📹 油管视频,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/YouTube.list 34 | ruleset=🎥 奈飞视频,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Netflix.list 35 | ruleset=📺 巴哈姆特,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Bahamut.list 36 | ruleset=📺 哔哩哔哩,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/BilibiliHMT.list 37 | ruleset=📺 哔哩哔哩,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Bilibili.list 38 | ruleset=🌏 国内媒体,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ChinaMedia.list 39 | ruleset=🌍 国外媒体,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ProxyMedia.list 40 | ruleset=🚀 节点选择,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ProxyGFWlist.list 41 | ruleset=🚀 节点选择,https://raw.githubusercontent.com/UlinoyaPed/ShellClash/dev/lists/proxy.list #personal 42 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/UlinoyaPed/ShellClash/dev/lists/direct.list #personal 43 | ;ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ChinaIp.list 44 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ChinaDomain.list 45 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ChinaCompanyIp.list 46 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Download.list 47 | ;ruleset=🎯 全球直连,[]GEOIP,LAN 48 | ruleset=🎯 全球直连,[]GEOIP,CN 49 | ruleset=🐟 漏网之鱼,[]FINAL 50 | ;设置规则标志位 51 | 52 | ;设置分组标志位 53 | custom_proxy_group=🚀 节点选择`select`[]♻️ 自动选择`[]🔯 故障转移`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 54 | custom_proxy_group=☑️ 手动切换`select`.* 55 | custom_proxy_group=♻️ 自动选择`url-test`[]🇭🇰 香港节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`http://www.gstatic.com/generate_204`300,,50 56 | custom_proxy_group=🔯 故障转移`fallback`[]🇭🇰 香港节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`http://www.gstatic.com/generate_204`300,,50 57 | custom_proxy_group=📶 官方优选`load-balance`(官方|防|电信|移动|CT|CU|CM|t.me|频道)`http://www.gstatic.com/generate_204`300,,50 58 | custom_proxy_group=📲 电报消息`select`[]🚀 节点选择`[]♻️ 自动选择`[]🇸🇬 狮城节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 59 | custom_proxy_group=🤖 OpenAi`select`[]🚀 节点选择`[]♻️ 自动选择`[]🇸🇬 狮城节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 60 | custom_proxy_group=📹 油管视频`select`[]🚀 节点选择`[]♻️ 自动选择`[]🇸🇬 狮城节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 61 | custom_proxy_group=🎥 奈飞视频`select`[]🚀 节点选择`[]♻️ 自动选择`[]🇸🇬 狮城节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 62 | custom_proxy_group=📺 巴哈姆特`select`[]🇹🇼 台湾节点`[]🚀 节点选择`[]☑️ 手动切换`[]DIRECT 63 | custom_proxy_group=📺 哔哩哔哩`select`[]🎯 全球直连`[]🇹🇼 台湾节点`[]🇭🇰 香港节点 64 | custom_proxy_group=🌍 国外媒体`select`[]🚀 节点选择`[]♻️ 自动选择`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 65 | custom_proxy_group=🌏 国内媒体`select`[]DIRECT`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]☑️ 手动切换 66 | custom_proxy_group=📢 谷歌FCM`select`[]DIRECT`[]🚀 节点选择`[]🇺🇲 美国节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换 67 | custom_proxy_group=Ⓜ️ 微软Bing`select`[]DIRECT`[]🚀 节点选择`[]🇺🇲 美国节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换 68 | custom_proxy_group=Ⓜ️ 微软云盘`select`[]DIRECT`[]🚀 节点选择`[]🇺🇲 美国节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换 69 | custom_proxy_group=Ⓜ️ 微软服务`select`[]DIRECT`[]🚀 节点选择`[]🇺🇲 美国节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换 70 | custom_proxy_group=🍎 苹果服务`select`[]DIRECT`[]🚀 节点选择`[]🇺🇲 美国节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换 71 | custom_proxy_group=🎮 游戏平台`select`[]DIRECT`[]🚀 节点选择`[]🇺🇲 美国节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换 72 | custom_proxy_group=🎶 网易音乐`select`[]DIRECT`[]🚀 节点选择`[]♻️ 自动选择`(网易|音乐|解锁|Music|NetEase) 73 | custom_proxy_group=🎯 全球直连`select`[]DIRECT`[]🚀 节点选择`[]♻️ 自动选择 74 | custom_proxy_group=🛑 广告拦截`select`[]REJECT`[]DIRECT 75 | custom_proxy_group=🍃 应用净化`select`[]REJECT`[]DIRECT 76 | custom_proxy_group=🐟 漏网之鱼`select`[]🚀 节点选择`[]♻️ 自动选择`[]DIRECT`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换 77 | custom_proxy_group=🇭🇰 香港节点`url-test`(港|澳门|HK|hk|Hong Kong|HongKong|hongkong|MO|HKG|MFM)`http://www.gstatic.com/generate_204`300,,50 78 | custom_proxy_group=🇯🇵 日本节点`url-test`(日本|川日|东京|大阪|泉日|埼玉|沪日|深日|[^-]日|JP|Japan|Tokyo|NRT|KIX)`http://www.gstatic.com/generate_204`300,,50 79 | custom_proxy_group=🇺🇲 美国节点`url-test`(美|波特兰|达拉斯|俄勒冈|凤凰城|费利蒙|硅谷|拉斯维加斯|洛杉矶|圣何塞|圣克拉拉|西雅图|芝加哥|US|United States|ATL|BUF|DFW|EWR|IAD|LAX|MCI|MIA|ORD|PHX|PDX|SEA|SJC)`http://www.gstatic.com/generate_204`300,,150 80 | custom_proxy_group=🇹🇼 台湾节点`url-test`(台|新北|彰化|TW|Taiwan|TPE|KHH)`http://www.gstatic.com/generate_204`300,,50 81 | custom_proxy_group=🇸🇬 狮城节点`url-test`(新加坡|坡|狮城|SG|Singapore|SIN)`http://www.gstatic.com/generate_204`300,,50 82 | custom_proxy_group=🇰🇷 韩国节点`url-test`(KR|Korea|KOR|Seoul|首尔|韩|韓|ICN)`http://www.gstatic.com/generate_204`300,,50 83 | ;设置分组标志位 84 | 85 | enable_rule_generator=true 86 | overwrite_original_rules=true 87 | 88 | ;clash_rule_base=https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/GeneralClashConfig.yml 89 | 90 | ;luck 91 | -------------------------------------------------------------------------------- /Clash/config/ACL4SSR_Online_Full_MultiMode.ini: -------------------------------------------------------------------------------- 1 | [custom] 2 | ;不要随意改变关键字,否则会导致出错 3 | ;acl4SSR规则 4 | 5 | ;去广告:支持 6 | ;自动测速:支持 7 | ;微软分流:支持 8 | ;苹果分流:支持 9 | ;增强中国IP段:支持 10 | ;增强国外GFW:支持 11 | 12 | ;设置规则标志位 13 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/LocalAreaNetwork.list 14 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/UnBan.list 15 | ruleset=🛑 广告拦截,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/BanAD.list 16 | ruleset=🍃 应用净化,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/BanProgramAD.list 17 | ruleset=📢 谷歌FCM,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/GoogleFCM.list 18 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/GoogleCN.list 19 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/SteamCN.list 20 | ruleset=Ⓜ️ 微软Bing,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Bing.list 21 | ruleset=Ⓜ️ 微软云盘,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/OneDrive.list 22 | ruleset=Ⓜ️ 微软服务,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Microsoft.list 23 | ruleset=🍎 苹果服务,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Apple.list 24 | ruleset=📲 电报消息,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Telegram.list 25 | ruleset=🤖 OpenAi,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/OpenAi.list 26 | ruleset=🤖 OpenAi,https://raw.githubusercontent.com/juewuy/ShellClash/master/rules/ai.list 27 | ruleset=🎶 网易音乐,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/NetEaseMusic.list 28 | ruleset=🎮 游戏平台,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Epic.list 29 | ruleset=🎮 游戏平台,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Origin.list 30 | ruleset=🎮 游戏平台,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Sony.list 31 | ruleset=🎮 游戏平台,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Steam.list 32 | ruleset=🎮 游戏平台,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Nintendo.list 33 | ruleset=📹 油管视频,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/YouTube.list 34 | ruleset=🎥 奈飞视频,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Netflix.list 35 | ruleset=📺 巴哈姆特,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Bahamut.list 36 | ruleset=📺 哔哩哔哩,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/BilibiliHMT.list 37 | ruleset=📺 哔哩哔哩,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Bilibili.list 38 | ruleset=🌏 国内媒体,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ChinaMedia.list 39 | ruleset=🌍 国外媒体,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ProxyMedia.list 40 | ruleset=🚀 节点选择,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ProxyGFWlist.list 41 | ruleset=🚀 节点选择,https://raw.githubusercontent.com/UlinoyaPed/ShellClash/dev/lists/proxy.list #personal 42 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/UlinoyaPed/ShellClash/dev/lists/direct.list #personal 43 | ;ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ChinaIp.list 44 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ChinaDomain.list 45 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ChinaCompanyIp.list 46 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Download.list 47 | ;ruleset=🎯 全球直连,[]GEOIP,LAN 48 | ruleset=🎯 全球直连,[]GEOIP,CN 49 | ruleset=🐟 漏网之鱼,[]FINAL 50 | ;设置规则标志位 51 | 52 | ;设置分组标志位 53 | custom_proxy_group=🚀 节点选择`select`[]♻️ 自动选择`[]🔯 故障转移`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 54 | custom_proxy_group=☑️ 手动切换`select`.* 55 | custom_proxy_group=♻️ 自动选择`url-test`[]🇭🇰 香港节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`http://www.gstatic.com/generate_204`300,,50 56 | custom_proxy_group=🔯 故障转移`fallback`[]🇭🇰 香港节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`http://www.gstatic.com/generate_204`300,,50 57 | custom_proxy_group=📶 官方优选`load-balance`(官方|防|电信|移动|CT|CU|CM|t.me|频道)`http://www.gstatic.com/generate_204`300,,50 58 | custom_proxy_group=📲 电报消息`select`[]🚀 节点选择`[]♻️ 自动选择`[]🇸🇬 狮城节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 59 | custom_proxy_group=🤖 OpenAi`select`[]🚀 节点选择`[]♻️ 自动选择`[]🇸🇬 狮城节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 60 | custom_proxy_group=📹 油管视频`select`[]🚀 节点选择`[]♻️ 自动选择`[]🇸🇬 狮城节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 61 | custom_proxy_group=🎥 奈飞视频`select`[]🚀 节点选择`[]♻️ 自动选择`[]🇸🇬 狮城节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 62 | custom_proxy_group=📺 巴哈姆特`select`[]🇹🇼 台湾节点`[]🚀 节点选择`[]☑️ 手动切换`[]DIRECT 63 | custom_proxy_group=📺 哔哩哔哩`select`[]🎯 全球直连`[]🇹🇼 台湾节点`[]🇭🇰 香港节点 64 | custom_proxy_group=🌍 国外媒体`select`[]🚀 节点选择`[]♻️ 自动选择`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 65 | custom_proxy_group=🌏 国内媒体`select`[]DIRECT`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]☑️ 手动切换 66 | custom_proxy_group=📢 谷歌FCM`select`[]DIRECT`[]🚀 节点选择`[]🇺🇲 美国节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换 67 | custom_proxy_group=Ⓜ️ 微软Bing`select`[]DIRECT`[]🚀 节点选择`[]🇺🇲 美国节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换 68 | custom_proxy_group=Ⓜ️ 微软云盘`select`[]DIRECT`[]🚀 节点选择`[]🇺🇲 美国节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换 69 | custom_proxy_group=Ⓜ️ 微软服务`select`[]DIRECT`[]🚀 节点选择`[]🇺🇲 美国节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换 70 | custom_proxy_group=🍎 苹果服务`select`[]DIRECT`[]🚀 节点选择`[]🇺🇲 美国节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换 71 | custom_proxy_group=🎮 游戏平台`select`[]DIRECT`[]🚀 节点选择`[]🇺🇲 美国节点`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换 72 | custom_proxy_group=🎶 网易音乐`select`[]DIRECT`[]🚀 节点选择`[]♻️ 自动选择`(网易|音乐|解锁|Music|NetEase) 73 | custom_proxy_group=🎯 全球直连`select`[]DIRECT`[]🚀 节点选择`[]♻️ 自动选择 74 | custom_proxy_group=🛑 广告拦截`select`[]REJECT`[]DIRECT 75 | custom_proxy_group=🍃 应用净化`select`[]REJECT`[]DIRECT 76 | custom_proxy_group=🐟 漏网之鱼`select`[]🚀 节点选择`[]♻️ 自动选择`[]DIRECT`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇰🇷 韩国节点`[]📶 官方优选`[]☑️ 手动切换 77 | custom_proxy_group=🇭🇰 香港节点`load-balance`(港|澳门|HK|hk|Hong Kong|HongKong|hongkong|MO|HKG|MFM)`http://www.gstatic.com/generate_204`300,,50 78 | custom_proxy_group=🇯🇵 日本节点`load-balance`(日本|川日|东京|大阪|泉日|埼玉|沪日|深日|[^-]日|JP|Japan|Tokyo|NRT|KIX)`http://www.gstatic.com/generate_204`300,,50 79 | custom_proxy_group=🇺🇲 美国节点`load-balance`(美|波特兰|达拉斯|俄勒冈|凤凰城|费利蒙|硅谷|拉斯维加斯|洛杉矶|圣何塞|圣克拉拉|西雅图|芝加哥|US|United States|ATL|BUF|DFW|EWR|IAD|LAX|MCI|MIA|ORD|PHX|PDX|SEA|SJC)`http://www.gstatic.com/generate_204`300,,150 80 | custom_proxy_group=🇹🇼 台湾节点`load-balance`(台|新北|彰化|TW|Taiwan|TPE|KHH)`http://www.gstatic.com/generate_204`300,,50 81 | custom_proxy_group=🇸🇬 狮城节点`load-balance`(新加坡|坡|狮城|SG|Singapore|SIN)`http://www.gstatic.com/generate_204`300,,50 82 | custom_proxy_group=🇰🇷 韩国节点`load-balance`(KR|Korea|KOR|Seoul|首尔|韩|韓|ICN)`http://www.gstatic.com/generate_204`300,,50 83 | ;设置分组标志位 84 | 85 | enable_rule_generator=true 86 | overwrite_original_rules=true 87 | 88 | ;clash_rule_base=https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/GeneralClashConfig.yml 89 | 90 | ;luck 91 | -------------------------------------------------------------------------------- /Clash/config/ACL4SSR_Online_Full_MultiMode_DE.ini: -------------------------------------------------------------------------------- 1 | [custom] 2 | ;不要随意改变关键字,否则会导致出错 3 | ;acl4SSR规则 4 | 5 | ;去广告:支持 6 | ;自动测速:支持 7 | ;微软分流:支持 8 | ;苹果分流:支持 9 | ;增强中国IP段:支持 10 | ;增强国外GFW:支持 11 | 12 | ;设置规则标志位 13 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/LocalAreaNetwork.list 14 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/UnBan.list 15 | ruleset=🛑 广告拦截,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/BanAD.list 16 | ruleset=🍃 应用净化,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/BanProgramAD.list 17 | ruleset=📢 谷歌FCM,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/GoogleFCM.list 18 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/GoogleCN.list 19 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/SteamCN.list 20 | ruleset=Ⓜ️ 微软Bing,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Bing.list 21 | ruleset=Ⓜ️ 微软云盘,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/OneDrive.list 22 | ruleset=Ⓜ️ 微软服务,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Microsoft.list 23 | ruleset=🍎 苹果服务,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Apple.list 24 | ruleset=📲 电报消息,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Telegram.list 25 | ruleset=🤖 OpenAi,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/OpenAi.list 26 | ruleset=🤖 OpenAi,https://raw.githubusercontent.com/juewuy/ShellClash/master/rules/ai.list 27 | ruleset=🎶 网易音乐,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/NetEaseMusic.list 28 | ruleset=🎮 游戏平台,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Epic.list 29 | ruleset=🎮 游戏平台,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Origin.list 30 | ruleset=🎮 游戏平台,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Sony.list 31 | ruleset=🎮 游戏平台,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Steam.list 32 | ruleset=🎮 游戏平台,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Nintendo.list 33 | ruleset=📹 油管视频,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/YouTube.list 34 | ruleset=🎥 奈飞视频,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Netflix.list 35 | ruleset=📺 巴哈姆特,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Bahamut.list 36 | ruleset=📺 哔哩哔哩,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/BilibiliHMT.list 37 | ruleset=📺 哔哩哔哩,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/Bilibili.list 38 | ruleset=🌏 国内媒体,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ChinaMedia.list 39 | ruleset=🌍 国外媒体,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ProxyMedia.list 40 | ruleset=🚀 节点选择,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ProxyGFWlist.list 41 | ruleset=🚀 节点选择,https://raw.githubusercontent.com/UlinoyaPed/ShellClash/dev/lists/proxy.list #personal 42 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/UlinoyaPed/ShellClash/dev/lists/direct.list #personal 43 | ;ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ChinaIp.list 44 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ChinaDomain.list 45 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ChinaCompanyIp.list 46 | ruleset=🎯 全球直连,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Download.list 47 | ;ruleset=🎯 全球直连,[]GEOIP,LAN 48 | ruleset=🎯 全球直连,[]GEOIP,CN 49 | ruleset=🐟 漏网之鱼,[]FINAL 50 | ;设置规则标志位 51 | 52 | ;设置分组标志位 53 | custom_proxy_group=🚀 节点选择`select`[]♻️ 自动选择`[]🔯 故障转移`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇰🇷 韩国节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇩🇪 德国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 54 | custom_proxy_group=☑️ 手动切换`select`.* 55 | custom_proxy_group=♻️ 自动选择`url-test`[]🇭🇰 香港节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`http://www.gstatic.com/generate_204`300,,50 56 | custom_proxy_group=🔯 故障转移`fallback`[]🇭🇰 香港节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]🇰🇷 韩国节点`http://www.gstatic.com/generate_204`300,,50 57 | custom_proxy_group=📶 官方优选`load-balance`(官方|防|电信|移动|CT|CU|CM|t.me|频道)`http://www.gstatic.com/generate_204`300,,50 58 | custom_proxy_group=📲 电报消息`select`[]🚀 节点选择`[]♻️ 自动选择`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇰🇷 韩国节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇩🇪 德国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 59 | custom_proxy_group=🤖 OpenAi`select`[]🚀 节点选择`[]♻️ 自动选择`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇰🇷 韩国节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇩🇪 德国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 60 | custom_proxy_group=📹 油管视频`select`[]🚀 节点选择`[]♻️ 自动选择`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇰🇷 韩国节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇩🇪 德国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 61 | custom_proxy_group=🎥 奈飞视频`select`[]🚀 节点选择`[]♻️ 自动选择`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇰🇷 韩国节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇩🇪 德国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 62 | custom_proxy_group=📺 巴哈姆特`select`[]🇹🇼 台湾节点`[]🚀 节点选择`[]☑️ 手动切换`[]DIRECT 63 | custom_proxy_group=📺 哔哩哔哩`select`[]🎯 全球直连`[]🇹🇼 台湾节点`[]🇭🇰 香港节点 64 | custom_proxy_group=🌍 国外媒体`select`[]🚀 节点选择`[]♻️ 自动选择`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇰🇷 韩国节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇩🇪 德国节点`[]📶 官方优选`[]☑️ 手动切换`[]DIRECT 65 | custom_proxy_group=🌏 国内媒体`select`[]DIRECT`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇯🇵 日本节点`[]☑️ 手动切换 66 | custom_proxy_group=📢 谷歌FCM`select`[]DIRECT`[]🚀 节点选择`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇰🇷 韩国节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇩🇪 德国节点`[]📶 官方优选`[]☑️ 手动切换 67 | custom_proxy_group=Ⓜ️ 微软Bing`select`[]DIRECT`[]🚀 节点选择`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇰🇷 韩国节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇩🇪 德国节点`[]📶 官方优选`[]☑️ 手动切换 68 | custom_proxy_group=Ⓜ️ 微软云盘`select`[]DIRECT`[]🚀 节点选择`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇰🇷 韩国节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇩🇪 德国节点`[]📶 官方优选`[]☑️ 手动切换 69 | custom_proxy_group=Ⓜ️ 微软服务`select`[]DIRECT`[]🚀 节点选择`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇰🇷 韩国节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇩🇪 德国节点`[]📶 官方优选`[]☑️ 手动切换 70 | custom_proxy_group=🍎 苹果服务`select`[]DIRECT`[]🚀 节点选择`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇰🇷 韩国节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇩🇪 德国节点`[]📶 官方优选`[]☑️ 手动切换 71 | custom_proxy_group=🎮 游戏平台`select`[]DIRECT`[]🚀 节点选择`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇰🇷 韩国节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇩🇪 德国节点`[]📶 官方优选`[]☑️ 手动切换 72 | custom_proxy_group=🎶 网易音乐`select`[]DIRECT`[]🚀 节点选择`[]♻️ 自动选择`(网易|音乐|解锁|Music|NetEase) 73 | custom_proxy_group=🎯 全球直连`select`[]DIRECT`[]🚀 节点选择`[]♻️ 自动选择 74 | custom_proxy_group=🛑 广告拦截`select`[]REJECT`[]DIRECT 75 | custom_proxy_group=🍃 应用净化`select`[]REJECT`[]DIRECT 76 | custom_proxy_group=🐟 漏网之鱼`select`[]🚀 节点选择`[]♻️ 自动选择`[]DIRECT`[]🇭🇰 香港节点`[]🇹🇼 台湾节点`[]🇸🇬 狮城节点`[]🇰🇷 韩国节点`[]🇯🇵 日本节点`[]🇺🇲 美国节点`[]🇩🇪 德国节点`[]📶 官方优选`[]☑️ 手动切换 77 | custom_proxy_group=🇭🇰 香港节点`load-balance`(港|澳门|HK|hk|Hong Kong|HongKong|hongkong|MO|HKG|MFM)`http://www.gstatic.com/generate_204`300,,50 78 | custom_proxy_group=🇹🇼 台湾节点`load-balance`(台|新北|彰化|TW|Taiwan|TPE|KHH)`http://www.gstatic.com/generate_204`300,,50 79 | custom_proxy_group=🇸🇬 狮城节点`load-balance`(新加坡|坡|狮城|SG|Singapore|SIN)`http://www.gstatic.com/generate_204`300,,50 80 | custom_proxy_group=🇰🇷 韩国节点`load-balance`(KR|Korea|KOR|Seoul|首尔|韩|韓|ICN)`http://www.gstatic.com/generate_204`300,,50 81 | custom_proxy_group=🇯🇵 日本节点`load-balance`(日本|川日|东京|大阪|泉日|埼玉|沪日|深日|[^-]日|JP|Japan|Tokyo|NRT|KIX)`http://www.gstatic.com/generate_204`300,,50 82 | custom_proxy_group=🇺🇲 美国节点`load-balance`(美|波特兰|达拉斯|俄勒冈|凤凰城|费利蒙|硅谷|拉斯维加斯|洛杉矶|圣何塞|圣克拉拉|西雅图|芝加哥|US|United States|ATL|BUF|DFW|EWR|IAD|LAX|MCI|MIA|ORD|PHX|PDX|SEA|SJC)`http://www.gstatic.com/generate_204`300,,150 83 | custom_proxy_group=🇩🇪 德国节点`load-balance`(DE|FRA|Frankfurt|德|法兰克福)`http://www.gstatic.com/generate_204`300,,50 84 | ;设置分组标志位 85 | 86 | enable_rule_generator=true 87 | overwrite_original_rules=true 88 | 89 | ;clash_rule_base=https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/GeneralClashConfig.yml 90 | 91 | ;luck 92 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloudflare Worker 2 Vless & Sub 2 | 这是一个基于 Cloudflare Worker 平台的脚本,在原版的基础上修改了显示 VLESS 配置信息转换为订阅内容。使用该脚本,你可以方便地将 VLESS 配置信息使用在线配置转换到 Clash 或 Singbox 等工具中。 3 | 4 | ### 用途 5 | 该项目被设计和开发仅供学习、研究和安全测试目的。它旨在为安全研究者、学术界人士和技术爱好者提供一个了解和实践网络通信技术的工具。 6 | 7 | ### 合法性 8 | 使用者在下载和使用该项目时,必须遵守当地法律和规定。使用者有责任确保他们的行为符合其所在地区的法律、规章以及其他适用的规定。 9 | 10 | ### 免责 11 | 1. 作为该项目的作者,我(以下简称“作者”)强调该项目应仅用于合法、道德和教育目的。 12 | 2. 作者不鼓励、不支持也不促进任何形式的非法使用该项目。如果发现该项目被用于非法或不道德的活动,作者将强烈谴责这种行为。 13 | 3. 作者对任何人或团体使用该项目进行的任何非法活动不承担责任。使用者使用该项目时产生的任何后果由使用者本人承担。 14 | 4. 作者不对使用该项目可能引起的任何直接或间接损害负责。 15 | 5. 通过使用该项目,使用者表示理解并同意本免责声明的所有条款。如果使用者不同意这些条款,应立即停止使用该项目。 16 | 17 | 作者保留随时更新本免责声明的权利,且不另行通知。最新的免责声明版本将会在该项目的 GitHub 页面上发布。 18 | 19 | ## 风险提示 20 | - 通过提交虚假的节点配置给订阅服务,避免节点配置信息泄露。 21 | 22 | - 将第 7 行 `userID` 修改成你自己的 **UUID** 。 23 | 24 | 2. 访问订阅内容: 25 | - 访问 `https://[YOUR-WORKERS-URL]/[UUID]` 即可获取订阅内容。 26 | - 例如 `https://vless.google.workers.dev/90cd4a77-141a-43c9-991b-08263cfe9c10` 就是你的订阅地址。 27 | 28 | 3. 给 workers绑定 自定义域: 29 | - 在 workers控制台的 `触发器`选项卡,下方点击 `添加自定义域`。 30 | - 填入你已转入 CloudFlare 域名解析服务的次级域名,例如:`vless.google.com`后 点击`添加自定义域`,等待证书生效即可。 31 | - **如果你是小白,你现在可以直接起飞,不用再往下看了!!!** 32 | 33 | 2. 访问订阅内容: 34 | - 访问 `https://[YOUR-PAGES-URL]/[YOUR-UUID]` 即可获取订阅内容。 35 | - 例如 `https://edgetunnel.pages.dev/90cd4a77-141a-43c9-991b-08263cfe9c10` 就是你的订阅地址。 36 | 37 | 38 | 39 | - 注意,如果您使用了自己的订阅地址,要求订阅生成器的 `SUB`域名 和 `[YOUR-PAGES-URL]`的域名 不同属一个顶级域名,否则会出现异常。您可以在 `SUB` 变量赋值为 Pages.dev 分配到的域名。 40 | 41 | 42 | -------------------------------------------------------------------------------- /_worker.js: -------------------------------------------------------------------------------- 1 | // version base on commit 43fad05dcdae3b723c53c226f8181fc5bd47223e, time is 2023-06-22 15:20:05 UTC. 2 | // @ts-ignore 3 | import { connect } from 'cloudflare:sockets'; 4 | 5 | // How to generate your own UUID: 6 | // [Windows] Press "Win + R", input cmd and run: Powershell -NoExit -Command "[guid]::NewGuid()" 7 | let userID = '90cd4a77-141a-43c9-991b-08263cfe9c10'; 8 | 9 | let proxyIP = '';// 小白勿动,该地址并不影响你的网速,这是给CF代理使用的。'cdn.xn--b6gac.eu.org, cdn-all.xn--b6gac.eu.org, workers.cloudflare.cyou' 10 | 11 | //let sub = '';// 留空则显示原版内容 12 | let sub = 'xiaobai-cc1.pages.dev';// 内置优选订阅生成器,可自行搭建 https://github.com/cmliu/WorkerVless2sub 13 | let subconverter = 'apiurl.v1.mk';// clash订阅转换后端,目前使用肥羊的订阅转换功能。自带虚假uuid和host订阅。 14 | let subconfig = "https://raw.githubusercontent.com/cmliu/ACL4SSR/main/Clash/config/ACL4SSR_Online_Full_MultiMode.ini"; //订阅配置文件 15 | // The user name and password do not contain special characters 16 | // Setting the address will ignore proxyIP 17 | // Example: user:pass@host:port or host:port 18 | let socks5Address = ''; 19 | let RproxyIP = 'false'; 20 | if (!isValidUUID(userID)) { 21 | throw new Error('uuid is not valid'); 22 | } 23 | 24 | let parsedSocks5Address = {}; 25 | let enableSocks = false; 26 | 27 | // 虚假uuid和hostname,用于发送给配置生成服务 28 | let fakeUserID = generateUUID(); 29 | let fakeHostName = generateRandomString(); 30 | let tls = true; 31 | export default { 32 | /** 33 | * @param {import("@cloudflare/workers-types").Request} request 34 | * @param {{UUID: string, PROXYIP: string}} env 35 | * @param {import("@cloudflare/workers-types").ExecutionContext} ctx 36 | * @returns {Promise} 37 | */ 38 | async fetch(request, env, ctx) { 39 | try { 40 | const userAgent = request.headers.get('User-Agent').toLowerCase(); 41 | userID = (env.UUID || userID).toLowerCase(); 42 | proxyIP = env.PROXYIP || proxyIP; 43 | const proxyIPs = await ADD(proxyIP); 44 | proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)]; 45 | //console.log(proxyIP); 46 | socks5Address = env.SOCKS5 || socks5Address; 47 | sub = env.SUB || sub; 48 | subconverter = env.SUBAPI || subconverter; 49 | subconfig = env.SUBCONFIG || subconfig; 50 | if (socks5Address) { 51 | RproxyIP = env.RPROXYIP || 'false'; 52 | try { 53 | parsedSocks5Address = socks5AddressParser(socks5Address); 54 | enableSocks = true; 55 | } catch (err) { 56 | /** @type {Error} */ let e = err; 57 | console.log(e.toString()); 58 | enableSocks = false; 59 | } 60 | } else { 61 | RproxyIP = env.RPROXYIP || !proxyIP ? 'true' : 'false'; 62 | } 63 | const upgradeHeader = request.headers.get('Upgrade'); 64 | const url = new URL(request.url); 65 | if (url.searchParams.has('notls')) tls = false; 66 | if (!upgradeHeader || upgradeHeader !== 'websocket') { 67 | // const url = new URL(request.url); 68 | switch (url.pathname.toLowerCase()) { 69 | case '/': 70 | return new Response(JSON.stringify(request.cf, null, 4), { status: 200 }); 71 | case `/${userID}`: { 72 | const vlessConfig = await getVLESSConfig(userID, request.headers.get('Host'), sub, userAgent, RproxyIP); 73 | const now = Date.now(); 74 | const timestamp = Math.floor(now / 1000); 75 | const expire = 4102329600;//2099-12-31 76 | const today = new Date(now); 77 | today.setHours(0, 0, 0, 0); 78 | const UD = Math.floor(((now - today.getTime())/86400000) * 24 * 1099511627776 / 2); 79 | if (userAgent && userAgent.includes('mozilla')){ 80 | return new Response(`${vlessConfig}`, { 81 | status: 200, 82 | headers: { 83 | "Content-Type": "text/plain;charset=utf-8", 84 | } 85 | }); 86 | } else { 87 | return new Response(`${vlessConfig}`, { 88 | status: 200, 89 | headers: { 90 | "Content-Disposition": "attachment; filename=edgetunnel; filename*=utf-8''edgetunnel", 91 | "Content-Type": "text/plain;charset=utf-8", 92 | "Profile-Update-Interval": "6", 93 | "Subscription-Userinfo": `upload=${UD}; download=${UD}; total=${24 * 1099511627776}; expire=${expire}`, 94 | } 95 | }); 96 | } 97 | } 98 | default: 99 | return new Response('Not found', { status: 404 }); 100 | } 101 | } else { 102 | if (new RegExp('/proxyip=', 'i').test(url.pathname)) proxyIP = url.pathname.split("=")[1]; 103 | else if (new RegExp('/proxyip.', 'i').test(url.pathname)) proxyIP = url.pathname.split("/proxyip.")[1]; 104 | else if (!proxyIP || proxyIP == '') proxyIP = 'proxyip.fxxk.dedyn.io'; 105 | return await vlessOverWSHandler(request); 106 | } 107 | } catch (err) { 108 | /** @type {Error} */ let e = err; 109 | return new Response(e.toString()); 110 | } 111 | }, 112 | }; 113 | 114 | 115 | 116 | 117 | /** 118 | * 119 | * @param {import("@cloudflare/workers-types").Request} request 120 | */ 121 | async function vlessOverWSHandler(request) { 122 | 123 | /** @type {import("@cloudflare/workers-types").WebSocket[]} */ 124 | // @ts-ignore 125 | const webSocketPair = new WebSocketPair(); 126 | const [client, webSocket] = Object.values(webSocketPair); 127 | 128 | webSocket.accept(); 129 | 130 | let address = ''; 131 | let portWithRandomLog = ''; 132 | const log = (/** @type {string} */ info, /** @type {string | undefined} */ event) => { 133 | console.log(`[${address}:${portWithRandomLog}] ${info}`, event || ''); 134 | }; 135 | const earlyDataHeader = request.headers.get('sec-websocket-protocol') || ''; 136 | 137 | const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log); 138 | 139 | /** @type {{ value: import("@cloudflare/workers-types").Socket | null}}*/ 140 | let remoteSocketWapper = { 141 | value: null, 142 | }; 143 | let isDns = false; 144 | 145 | // ws --> remote 146 | readableWebSocketStream.pipeTo(new WritableStream({ 147 | async write(chunk, controller) { 148 | if (isDns) { 149 | return await handleDNSQuery(chunk, webSocket, null, log); 150 | } 151 | if (remoteSocketWapper.value) { 152 | const writer = remoteSocketWapper.value.writable.getWriter() 153 | await writer.write(chunk); 154 | writer.releaseLock(); 155 | return; 156 | } 157 | 158 | const { 159 | hasError, 160 | message, 161 | addressType, 162 | portRemote = 443, 163 | addressRemote = '', 164 | rawDataIndex, 165 | vlessVersion = new Uint8Array([0, 0]), 166 | isUDP, 167 | } = processVlessHeader(chunk, userID); 168 | address = addressRemote; 169 | portWithRandomLog = `${portRemote}--${Math.random()} ${isUDP ? 'udp ' : 'tcp ' 170 | } `; 171 | if (hasError) { 172 | // controller.error(message); 173 | throw new Error(message); // cf seems has bug, controller.error will not end stream 174 | // webSocket.close(1000, message); 175 | return; 176 | } 177 | // if UDP but port not DNS port, close it 178 | if (isUDP) { 179 | if (portRemote === 53) { 180 | isDns = true; 181 | } else { 182 | // controller.error('UDP proxy only enable for DNS which is port 53'); 183 | throw new Error('UDP proxy only enable for DNS which is port 53'); // cf seems has bug, controller.error will not end stream 184 | return; 185 | } 186 | } 187 | // ["version", "附加信息长度 N"] 188 | const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]); 189 | const rawClientData = chunk.slice(rawDataIndex); 190 | 191 | if (isDns) { 192 | return handleDNSQuery(rawClientData, webSocket, vlessResponseHeader, log); 193 | } 194 | handleTCPOutBound(remoteSocketWapper, addressType, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log); 195 | }, 196 | close() { 197 | log(`readableWebSocketStream is close`); 198 | }, 199 | abort(reason) { 200 | log(`readableWebSocketStream is abort`, JSON.stringify(reason)); 201 | }, 202 | })).catch((err) => { 203 | log('readableWebSocketStream pipeTo error', err); 204 | }); 205 | 206 | return new Response(null, { 207 | status: 101, 208 | // @ts-ignore 209 | webSocket: client, 210 | }); 211 | } 212 | 213 | /** 214 | * Handles outbound TCP connections. 215 | * 216 | * @param {any} remoteSocket 217 | * @param {number} addressType The remote address type to connect to. 218 | * @param {string} addressRemote The remote address to connect to. 219 | * @param {number} portRemote The remote port to connect to. 220 | * @param {Uint8Array} rawClientData The raw client data to write. 221 | * @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket to pass the remote socket to. 222 | * @param {Uint8Array} vlessResponseHeader The VLESS response header. 223 | * @param {function} log The logging function. 224 | * @returns {Promise} The remote socket. 225 | */ 226 | async function handleTCPOutBound(remoteSocket, addressType, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) { 227 | async function connectAndWrite(address, port, socks = false) { 228 | /** @type {import("@cloudflare/workers-types").Socket} */ 229 | const tcpSocket = socks ? await socks5Connect(addressType, address, port, log) 230 | : connect({ 231 | hostname: address, 232 | port: port, 233 | }); 234 | remoteSocket.value = tcpSocket; 235 | log(`connected to ${address}:${port}`); 236 | const writer = tcpSocket.writable.getWriter(); 237 | await writer.write(rawClientData); // first write, normal is tls client hello 238 | writer.releaseLock(); 239 | return tcpSocket; 240 | } 241 | 242 | // if the cf connect tcp socket have no incoming data, we retry to redirect ip 243 | async function retry() { 244 | if (enableSocks) { 245 | tcpSocket = await connectAndWrite(addressRemote, portRemote, true); 246 | } else { 247 | tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote); 248 | } 249 | // no matter retry success or not, close websocket 250 | tcpSocket.closed.catch(error => { 251 | console.log('retry tcpSocket closed error', error); 252 | }).finally(() => { 253 | safeCloseWebSocket(webSocket); 254 | }) 255 | remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, null, log); 256 | } 257 | 258 | let tcpSocket = await connectAndWrite(addressRemote, portRemote); 259 | 260 | // when remoteSocket is ready, pass to websocket 261 | // remote--> ws 262 | remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, retry, log); 263 | } 264 | 265 | /** 266 | * 267 | * @param {import("@cloudflare/workers-types").WebSocket} webSocketServer 268 | * @param {string} earlyDataHeader for ws 0rtt 269 | * @param {(info: string)=> void} log for ws 0rtt 270 | */ 271 | function makeReadableWebSocketStream(webSocketServer, earlyDataHeader, log) { 272 | let readableStreamCancel = false; 273 | const stream = new ReadableStream({ 274 | start(controller) { 275 | webSocketServer.addEventListener('message', (event) => { 276 | if (readableStreamCancel) { 277 | return; 278 | } 279 | const message = event.data; 280 | controller.enqueue(message); 281 | }); 282 | 283 | // The event means that the client closed the client -> server stream. 284 | // However, the server -> client stream is still open until you call close() on the server side. 285 | // The WebSocket protocol says that a separate close message must be sent in each direction to fully close the socket. 286 | webSocketServer.addEventListener('close', () => { 287 | // client send close, need close server 288 | // if stream is cancel, skip controller.close 289 | safeCloseWebSocket(webSocketServer); 290 | if (readableStreamCancel) { 291 | return; 292 | } 293 | controller.close(); 294 | } 295 | ); 296 | webSocketServer.addEventListener('error', (err) => { 297 | log('webSocketServer has error'); 298 | controller.error(err); 299 | } 300 | ); 301 | // for ws 0rtt 302 | const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader); 303 | if (error) { 304 | controller.error(error); 305 | } else if (earlyData) { 306 | controller.enqueue(earlyData); 307 | } 308 | }, 309 | 310 | pull(controller) { 311 | // if ws can stop read if stream is full, we can implement backpressure 312 | // https://streams.spec.whatwg.org/#example-rs-push-backpressure 313 | }, 314 | cancel(reason) { 315 | // 1. pipe WritableStream has error, this cancel will called, so ws handle server close into here 316 | // 2. if readableStream is cancel, all controller.close/enqueue need skip, 317 | // 3. but from testing controller.error still work even if readableStream is cancel 318 | if (readableStreamCancel) { 319 | return; 320 | } 321 | log(`ReadableStream was canceled, due to ${reason}`) 322 | readableStreamCancel = true; 323 | safeCloseWebSocket(webSocketServer); 324 | } 325 | }); 326 | 327 | return stream; 328 | 329 | } 330 | 331 | // https://xtls.github.io/development/protocols/vless.html 332 | // https://github.com/zizifn/excalidraw-backup/blob/main/v2ray-protocol.excalidraw 333 | 334 | /** 335 | * 336 | * @param { ArrayBuffer} vlessBuffer 337 | * @param {string} userID 338 | * @returns 339 | */ 340 | function processVlessHeader( 341 | vlessBuffer, 342 | userID 343 | ) { 344 | if (vlessBuffer.byteLength < 24) { 345 | return { 346 | hasError: true, 347 | message: 'invalid data', 348 | }; 349 | } 350 | const version = new Uint8Array(vlessBuffer.slice(0, 1)); 351 | let isValidUser = false; 352 | let isUDP = false; 353 | if (stringify(new Uint8Array(vlessBuffer.slice(1, 17))) === userID) { 354 | isValidUser = true; 355 | } 356 | if (!isValidUser) { 357 | return { 358 | hasError: true, 359 | message: 'invalid user', 360 | }; 361 | } 362 | 363 | const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0]; 364 | //skip opt for now 365 | 366 | const command = new Uint8Array( 367 | vlessBuffer.slice(18 + optLength, 18 + optLength + 1) 368 | )[0]; 369 | 370 | // 0x01 TCP 371 | // 0x02 UDP 372 | // 0x03 MUX 373 | if (command === 1) { 374 | } else if (command === 2) { 375 | isUDP = true; 376 | } else { 377 | return { 378 | hasError: true, 379 | message: `command ${command} is not support, command 01-tcp,02-udp,03-mux`, 380 | }; 381 | } 382 | const portIndex = 18 + optLength + 1; 383 | const portBuffer = vlessBuffer.slice(portIndex, portIndex + 2); 384 | // port is big-Endian in raw data etc 80 == 0x005d 385 | const portRemote = new DataView(portBuffer).getUint16(0); 386 | 387 | let addressIndex = portIndex + 2; 388 | const addressBuffer = new Uint8Array( 389 | vlessBuffer.slice(addressIndex, addressIndex + 1) 390 | ); 391 | 392 | // 1--> ipv4 addressLength =4 393 | // 2--> domain name addressLength=addressBuffer[1] 394 | // 3--> ipv6 addressLength =16 395 | const addressType = addressBuffer[0]; 396 | let addressLength = 0; 397 | let addressValueIndex = addressIndex + 1; 398 | let addressValue = ''; 399 | switch (addressType) { 400 | case 1: 401 | addressLength = 4; 402 | addressValue = new Uint8Array( 403 | vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) 404 | ).join('.'); 405 | break; 406 | case 2: 407 | addressLength = new Uint8Array( 408 | vlessBuffer.slice(addressValueIndex, addressValueIndex + 1) 409 | )[0]; 410 | addressValueIndex += 1; 411 | addressValue = new TextDecoder().decode( 412 | vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) 413 | ); 414 | break; 415 | case 3: 416 | addressLength = 16; 417 | const dataView = new DataView( 418 | vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) 419 | ); 420 | // 2001:0db8:85a3:0000:0000:8a2e:0370:7334 421 | const ipv6 = []; 422 | for (let i = 0; i < 8; i++) { 423 | ipv6.push(dataView.getUint16(i * 2).toString(16)); 424 | } 425 | addressValue = ipv6.join(':'); 426 | // seems no need add [] for ipv6 427 | break; 428 | default: 429 | return { 430 | hasError: true, 431 | message: `invild addressType is ${addressType}`, 432 | }; 433 | } 434 | if (!addressValue) { 435 | return { 436 | hasError: true, 437 | message: `addressValue is empty, addressType is ${addressType}`, 438 | }; 439 | } 440 | 441 | return { 442 | hasError: false, 443 | addressRemote: addressValue, 444 | addressType, 445 | portRemote, 446 | rawDataIndex: addressValueIndex + addressLength, 447 | vlessVersion: version, 448 | isUDP, 449 | }; 450 | } 451 | 452 | 453 | /** 454 | * 455 | * @param {import("@cloudflare/workers-types").Socket} remoteSocket 456 | * @param {import("@cloudflare/workers-types").WebSocket} webSocket 457 | * @param {ArrayBuffer} vlessResponseHeader 458 | * @param {(() => Promise) | null} retry 459 | * @param {*} log 460 | */ 461 | async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) { 462 | // remote--> ws 463 | let remoteChunkCount = 0; 464 | let chunks = []; 465 | /** @type {ArrayBuffer | null} */ 466 | let vlessHeader = vlessResponseHeader; 467 | let hasIncomingData = false; // check if remoteSocket has incoming data 468 | await remoteSocket.readable 469 | .pipeTo( 470 | new WritableStream({ 471 | start() { 472 | }, 473 | /** 474 | * 475 | * @param {Uint8Array} chunk 476 | * @param {*} controller 477 | */ 478 | async write(chunk, controller) { 479 | hasIncomingData = true; 480 | // remoteChunkCount++; 481 | if (webSocket.readyState !== WS_READY_STATE_OPEN) { 482 | controller.error( 483 | 'webSocket.readyState is not open, maybe close' 484 | ); 485 | } 486 | if (vlessHeader) { 487 | webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer()); 488 | vlessHeader = null; 489 | } else { 490 | // seems no need rate limit this, CF seems fix this??.. 491 | // if (remoteChunkCount > 20000) { 492 | // // cf one package is 4096 byte(4kb), 4096 * 20000 = 80M 493 | // await delay(1); 494 | // } 495 | webSocket.send(chunk); 496 | } 497 | }, 498 | close() { 499 | log(`remoteConnection!.readable is close with hasIncomingData is ${hasIncomingData}`); 500 | // safeCloseWebSocket(webSocket); // no need server close websocket frist for some case will casue HTTP ERR_CONTENT_LENGTH_MISMATCH issue, client will send close event anyway. 501 | }, 502 | abort(reason) { 503 | console.error(`remoteConnection!.readable abort`, reason); 504 | }, 505 | }) 506 | ) 507 | .catch((error) => { 508 | console.error( 509 | `remoteSocketToWS has exception `, 510 | error.stack || error 511 | ); 512 | safeCloseWebSocket(webSocket); 513 | }); 514 | 515 | // seems is cf connect socket have error, 516 | // 1. Socket.closed will have error 517 | // 2. Socket.readable will be close without any data coming 518 | if (hasIncomingData === false && retry) { 519 | log(`retry`) 520 | retry(); 521 | } 522 | } 523 | 524 | /** 525 | * 526 | * @param {string} base64Str 527 | * @returns 528 | */ 529 | function base64ToArrayBuffer(base64Str) { 530 | if (!base64Str) { 531 | return { error: null }; 532 | } 533 | try { 534 | // go use modified Base64 for URL rfc4648 which js atob not support 535 | base64Str = base64Str.replace(/-/g, '+').replace(/_/g, '/'); 536 | const decode = atob(base64Str); 537 | const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0)); 538 | return { earlyData: arryBuffer.buffer, error: null }; 539 | } catch (error) { 540 | return { error }; 541 | } 542 | } 543 | 544 | /** 545 | * This is not real UUID validation 546 | * @param {string} uuid 547 | */ 548 | function isValidUUID(uuid) { 549 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 550 | return uuidRegex.test(uuid); 551 | } 552 | 553 | const WS_READY_STATE_OPEN = 1; 554 | const WS_READY_STATE_CLOSING = 2; 555 | /** 556 | * Normally, WebSocket will not has exceptions when close. 557 | * @param {import("@cloudflare/workers-types").WebSocket} socket 558 | */ 559 | function safeCloseWebSocket(socket) { 560 | try { 561 | if (socket.readyState === WS_READY_STATE_OPEN || socket.readyState === WS_READY_STATE_CLOSING) { 562 | socket.close(); 563 | } 564 | } catch (error) { 565 | console.error('safeCloseWebSocket error', error); 566 | } 567 | } 568 | 569 | const byteToHex = []; 570 | for (let i = 0; i < 256; ++i) { 571 | byteToHex.push((i + 256).toString(16).slice(1)); 572 | } 573 | function unsafeStringify(arr, offset = 0) { 574 | return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); 575 | } 576 | function stringify(arr, offset = 0) { 577 | const uuid = unsafeStringify(arr, offset); 578 | if (!isValidUUID(uuid)) { 579 | throw TypeError("Stringified UUID is invalid"); 580 | } 581 | return uuid; 582 | } 583 | 584 | /** 585 | * 586 | * @param {ArrayBuffer} udpChunk 587 | * @param {import("@cloudflare/workers-types").WebSocket} webSocket 588 | * @param {ArrayBuffer} vlessResponseHeader 589 | * @param {(string)=> void} log 590 | */ 591 | async function handleDNSQuery(udpChunk, webSocket, vlessResponseHeader, log) { 592 | // no matter which DNS server client send, we alwasy use hard code one. 593 | // beacsue someof DNS server is not support DNS over TCP 594 | try { 595 | const dnsServer = '8.8.4.4'; // change to 1.1.1.1 after cf fix connect own ip bug 596 | const dnsPort = 53; 597 | /** @type {ArrayBuffer | null} */ 598 | let vlessHeader = vlessResponseHeader; 599 | /** @type {import("@cloudflare/workers-types").Socket} */ 600 | const tcpSocket = connect({ 601 | hostname: dnsServer, 602 | port: dnsPort, 603 | }); 604 | 605 | log(`connected to ${dnsServer}:${dnsPort}`); 606 | const writer = tcpSocket.writable.getWriter(); 607 | await writer.write(udpChunk); 608 | writer.releaseLock(); 609 | await tcpSocket.readable.pipeTo(new WritableStream({ 610 | async write(chunk) { 611 | if (webSocket.readyState === WS_READY_STATE_OPEN) { 612 | if (vlessHeader) { 613 | webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer()); 614 | vlessHeader = null; 615 | } else { 616 | webSocket.send(chunk); 617 | } 618 | } 619 | }, 620 | close() { 621 | log(`dns server(${dnsServer}) tcp is close`); 622 | }, 623 | abort(reason) { 624 | console.error(`dns server(${dnsServer}) tcp is abort`, reason); 625 | }, 626 | })); 627 | } catch (error) { 628 | console.error( 629 | `handleDNSQuery have exception, error: ${error.message}` 630 | ); 631 | } 632 | } 633 | 634 | /** 635 | * 636 | * @param {number} addressType 637 | * @param {string} addressRemote 638 | * @param {number} portRemote 639 | * @param {function} log The logging function. 640 | */ 641 | async function socks5Connect(addressType, addressRemote, portRemote, log) { 642 | const { username, password, hostname, port } = parsedSocks5Address; 643 | // Connect to the SOCKS server 644 | const socket = connect({ 645 | hostname, 646 | port, 647 | }); 648 | 649 | // Request head format (Worker -> Socks Server): 650 | // +----+----------+----------+ 651 | // |VER | NMETHODS | METHODS | 652 | // +----+----------+----------+ 653 | // | 1 | 1 | 1 to 255 | 654 | // +----+----------+----------+ 655 | 656 | // https://en.wikipedia.org/wiki/SOCKS#SOCKS5 657 | // For METHODS: 658 | // 0x00 NO AUTHENTICATION REQUIRED 659 | // 0x02 USERNAME/PASSWORD https://datatracker.ietf.org/doc/html/rfc1929 660 | const socksGreeting = new Uint8Array([5, 2, 0, 2]); 661 | 662 | const writer = socket.writable.getWriter(); 663 | 664 | await writer.write(socksGreeting); 665 | log('sent socks greeting'); 666 | 667 | const reader = socket.readable.getReader(); 668 | const encoder = new TextEncoder(); 669 | let res = (await reader.read()).value; 670 | // Response format (Socks Server -> Worker): 671 | // +----+--------+ 672 | // |VER | METHOD | 673 | // +----+--------+ 674 | // | 1 | 1 | 675 | // +----+--------+ 676 | if (res[0] !== 0x05) { 677 | log(`socks server version error: ${res[0]} expected: 5`); 678 | return; 679 | } 680 | if (res[1] === 0xff) { 681 | log("no acceptable methods"); 682 | return; 683 | } 684 | 685 | // if return 0x0502 686 | if (res[1] === 0x02) { 687 | log("socks server needs auth"); 688 | if (!username || !password) { 689 | log("please provide username/password"); 690 | return; 691 | } 692 | // +----+------+----------+------+----------+ 693 | // |VER | ULEN | UNAME | PLEN | PASSWD | 694 | // +----+------+----------+------+----------+ 695 | // | 1 | 1 | 1 to 255 | 1 | 1 to 255 | 696 | // +----+------+----------+------+----------+ 697 | const authRequest = new Uint8Array([ 698 | 1, 699 | username.length, 700 | ...encoder.encode(username), 701 | password.length, 702 | ...encoder.encode(password) 703 | ]); 704 | await writer.write(authRequest); 705 | res = (await reader.read()).value; 706 | // expected 0x0100 707 | if (res[0] !== 0x01 || res[1] !== 0x00) { 708 | log("fail to auth socks server"); 709 | return; 710 | } 711 | } 712 | 713 | // Request data format (Worker -> Socks Server): 714 | // +----+-----+-------+------+----------+----------+ 715 | // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 716 | // +----+-----+-------+------+----------+----------+ 717 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 718 | // +----+-----+-------+------+----------+----------+ 719 | // ATYP: address type of following address 720 | // 0x01: IPv4 address 721 | // 0x03: Domain name 722 | // 0x04: IPv6 address 723 | // DST.ADDR: desired destination address 724 | // DST.PORT: desired destination port in network octet order 725 | 726 | // addressType 727 | // 1--> ipv4 addressLength =4 728 | // 2--> domain name 729 | // 3--> ipv6 addressLength =16 730 | let DSTADDR; // DSTADDR = ATYP + DST.ADDR 731 | switch (addressType) { 732 | case 1: 733 | DSTADDR = new Uint8Array( 734 | [1, ...addressRemote.split('.').map(Number)] 735 | ); 736 | break; 737 | case 2: 738 | DSTADDR = new Uint8Array( 739 | [3, addressRemote.length, ...encoder.encode(addressRemote)] 740 | ); 741 | break; 742 | case 3: 743 | DSTADDR = new Uint8Array( 744 | [4, ...addressRemote.split(':').flatMap(x => [parseInt(x.slice(0, 2), 16), parseInt(x.slice(2), 16)])] 745 | ); 746 | break; 747 | default: 748 | log(`invild addressType is ${addressType}`); 749 | return; 750 | } 751 | const socksRequest = new Uint8Array([5, 1, 0, ...DSTADDR, portRemote >> 8, portRemote & 0xff]); 752 | await writer.write(socksRequest); 753 | log('sent socks request'); 754 | 755 | res = (await reader.read()).value; 756 | // Response format (Socks Server -> Worker): 757 | // +----+-----+-------+------+----------+----------+ 758 | // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 759 | // +----+-----+-------+------+----------+----------+ 760 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 761 | // +----+-----+-------+------+----------+----------+ 762 | if (res[1] === 0x00) { 763 | log("socks connection opened"); 764 | } else { 765 | log("fail to open socks connection"); 766 | return; 767 | } 768 | writer.releaseLock(); 769 | reader.releaseLock(); 770 | return socket; 771 | } 772 | 773 | 774 | /** 775 | * 776 | * @param {string} address 777 | */ 778 | function socks5AddressParser(address) { 779 | let [latter, former] = address.split("@").reverse(); 780 | let username, password, hostname, port; 781 | if (former) { 782 | const formers = former.split(":"); 783 | if (formers.length !== 2) { 784 | throw new Error('Invalid SOCKS address format'); 785 | } 786 | [username, password] = formers; 787 | } 788 | const latters = latter.split(":"); 789 | port = Number(latters.pop()); 790 | if (isNaN(port)) { 791 | throw new Error('Invalid SOCKS address format'); 792 | } 793 | hostname = latters.join(":"); 794 | const regex = /^\[.*\]$/; 795 | if (hostname.includes(":") && !regex.test(hostname)) { 796 | throw new Error('Invalid SOCKS address format'); 797 | } 798 | return { 799 | username, 800 | password, 801 | hostname, 802 | port, 803 | } 804 | } 805 | 806 | function revertFakeInfo(content, userID, hostName, isBase64) { 807 | if (isBase64) content = atob(content);//Base64解码 808 | content = content.replace(new RegExp(fakeUserID, 'g'), userID).replace(new RegExp(fakeHostName, 'g'), hostName); 809 | if (isBase64) content = btoa(content);//Base64编码 810 | 811 | return content; 812 | } 813 | 814 | function generateRandomNumber() { 815 | let minNum = 100000; 816 | let maxNum = 999999; 817 | return Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum; 818 | } 819 | 820 | function generateRandomString() { 821 | let minLength = 2; 822 | let maxLength = 3; 823 | let length = Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength; 824 | let characters = 'abcdefghijklmnopqrstuvwxyz'; 825 | let result = ''; 826 | for (let i = 0; i < length; i++) { 827 | result += characters[Math.floor(Math.random() * characters.length)]; 828 | } 829 | return result; 830 | } 831 | 832 | function generateUUID() { 833 | let uuid = ''; 834 | for (let i = 0; i < 32; i++) { 835 | let num = Math.floor(Math.random() * 16); 836 | if (num < 10) { 837 | uuid += num; 838 | } else { 839 | uuid += String.fromCharCode(num + 55); 840 | } 841 | } 842 | return uuid.replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, '$1-$2-$3-$4-$5').toLowerCase(); 843 | } 844 | 845 | async function ADD(envadd) { 846 | var addtext = envadd.replace(/[ "'\r\n]+/g, ',').replace(/,+/g, ','); // 将空格、双引号、单引号和换行符替换为逗号 847 | //console.log(addtext); 848 | if (addtext.charAt(0) == ',') addtext = addtext.slice(1); 849 | if (addtext.charAt(addtext.length -1) == ',') addtext = addtext.slice(0, addtext.length - 1); 850 | const add = addtext.split(','); 851 | //console.log(add); 852 | return add ; 853 | } 854 | 855 | /** 856 | * @param {string} userID 857 | * @param {string | null} hostName 858 | * @param {string} sub 859 | * @param {string} userAgent 860 | * @returns {Promise} 861 | */ 862 | let vv = 'v'; 863 | let ll = 'l'; 864 | let ee = 'e'; 865 | let ss = 's'; 866 | async function getVLESSConfig(userID, hostName, sub, userAgent, RproxyIP) { 867 | // 如果sub为空,则显示原始内容 868 | if (!sub || sub === '') { 869 | const cmliuMain = `${vv}${ll}${ee}${ss}${ss}://${userID}@${hostName}:443?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#${hostName}`; 870 | 871 | return ` 872 | ################################################################ 873 | v2ray 874 | --------------------------------------------------------------- 875 | ${cmliuMain} 876 | --------------------------------------------------------------- 877 | ################################################################ 878 | clash-meta 879 | --------------------------------------------------------------- 880 | - type: ${vv}${ll}${ee}${ss}${ss} 881 | name: ${hostName} 882 | server: ${hostName} 883 | port: 443 884 | uuid: ${userID} 885 | network: ws 886 | tls: true 887 | udp: false 888 | sni: ${hostName} 889 | client-fingerprint: chrome 890 | ws-opts: 891 | path: "/?ed=2560" 892 | headers: 893 | host: ${hostName} 894 | --------------------------------------------------------------- 895 | ################################################################ 896 | `; 897 | } else if (sub && userAgent.includes('mozilla') && !userAgent.includes('linux x86')) { 898 | const cmliuMain = `${vv}${ll}${ee}${ss}${ss}://${userID}@${hostName}:443?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#${hostName}`; 899 | 900 | return ` 901 | ################################################################ 902 | Subscribe / sub 订阅地址, 支持 Base64、clash-meta、sing-box 订阅格式, 您的订阅内容由 ${sub} 提供维护支持, 自动获取ProxyIP: ${RproxyIP}. 903 | --------------------------------------------------------------- 904 | https://${hostName}/${userID} 905 | --------------------------------------------------------------- 906 | ################################################################ 907 | v2ray 908 | --------------------------------------------------------------- 909 | ${cmliuMain} 910 | --------------------------------------------------------------- 911 | ################################################################ 912 | clash-meta 913 | --------------------------------------------------------------- 914 | - type: ${vv}${ll}${ee}${ss}${ss} 915 | name: ${hostName} 916 | server: ${hostName} 917 | port: 443 918 | uuid: ${userID} 919 | network: ws 920 | tls: true 921 | udp: false 922 | sni: ${hostName} 923 | client-fingerprint: chrome 924 | ws-opts: 925 | path: "/?ed=2560" 926 | headers: 927 | host: ${hostName} 928 | --------------------------------------------------------------- 929 | 930 | `; 931 | } else { 932 | if (typeof fetch != 'function') { 933 | return 'Error: fetch is not available in this environment.'; 934 | } 935 | // 如果是使用默认域名,则改成一个workers的域名,订阅器会加上代理 936 | if (hostName.includes(".workers.dev")){ 937 | fakeHostName = `${fakeHostName}.${generateRandomString()}${generateRandomNumber()}.workers.dev`; 938 | } else if (hostName.includes(".pages.dev")){ 939 | fakeHostName = `${fakeHostName}.${generateRandomString()}${generateRandomNumber()}.pages.dev`; 940 | } else if (hostName.includes("worker") || hostName.includes("notls") || tls == false){ 941 | fakeHostName = `notls.${fakeHostName}${generateRandomNumber()}.net`; 942 | } else { 943 | fakeHostName = `${fakeHostName}.${generateRandomNumber()}.xyz` 944 | } 945 | let content = ""; 946 | let url = ""; 947 | let isBase64 = false; 948 | if (userAgent.includes('clash') && !userAgent.includes('nekobox')) { 949 | url = `https://${subconverter}/sub?target=clash&url=https%3A%2F%2F${sub}%2Fsub%3Fhost%3D${fakeHostName}%26uuid%3D${fakeUserID}%26edgetunnel%3Dcmliu%26proxyip%3D${RproxyIP}&insert=false&config=${encodeURIComponent(subconfig)}&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true`; 950 | } else if (userAgent.includes('sing-box') || userAgent.includes('singbox')) { 951 | url = `https://${subconverter}/sub?target=singbox&url=https%3A%2F%2F${sub}%2Fsub%3Fhost%3D${fakeHostName}%26uuid%3D${fakeUserID}%26edgetunnel%3Dcmliu%26proxyip%3D${RproxyIP}&insert=false&config=${encodeURIComponent(subconfig)}&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true`; 952 | } else { 953 | url = `https://${sub}/sub?host=${fakeHostName}&uuid=${fakeUserID}&edgetunnel=cmliu&proxyip=${RproxyIP}`; 954 | isBase64 = true; 955 | } 956 | try { 957 | const response = await fetch(url ,{ 958 | headers: { 959 | 'User-Agent': 'CF-Workers-edgetunnel/cmliu' 960 | }}); 961 | content = await response.text(); 962 | return revertFakeInfo(content, userID, hostName, isBase64); 963 | } catch (error) { 964 | console.error('Error fetching content:', error); 965 | return `Error fetching content: ${error.message}`; 966 | } 967 | } 968 | } 969 | -------------------------------------------------------------------------------- /worker.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaobaikeji831/edgetunnel/c9ce05d36b91b8402ed8ebe58d631cfeb6a1c5d8/worker.zip --------------------------------------------------------------------------------