├── .github └── workflows │ ├── obfuscator.yml │ ├── sync.yml │ └── test.yml ├── AutoTest.txt ├── CloudflareST ├── LICENSE ├── README.md ├── _worker.js ├── csv_to_txt.py └── src.js /.github/workflows/obfuscator.yml: -------------------------------------------------------------------------------- 1 | name: 自动混淆 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'src.js' 7 | workflow_dispatch: 8 | schedule: 9 | - cron: '0 4 * * *' 10 | 11 | jobs: 12 | obfuscate: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Use Node.js 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: "16" 26 | 27 | - name: 安装依赖 28 | run: npm install -g javascript-obfuscator 29 | 30 | - name: 混淆代码 31 | run: | 32 | javascript-obfuscator src.js --output _worker.js \ 33 | --compact true \ 34 | --identifier-names-generator mangled \ 35 | --rename-globals true 36 | 37 | - name: 提交更改 38 | run: | 39 | git config --local user.email "action@github.com" 40 | git config --local user.name "GitHub Action" 41 | git add _worker.js 42 | git commit -m "自动混淆" || echo "No changes to commit" 43 | 44 | - name: 上传更改 45 | uses: ad-m/github-push-action@master 46 | with: 47 | github_token: ${{ secrets.GITHUB_TOKEN }} 48 | branch: ${{ github.ref }} -------------------------------------------------------------------------------- /.github/workflows/sync.yml: -------------------------------------------------------------------------------- 1 | name: 上游同步 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | schedule: 8 | - cron: '0 22 * * *' 9 | workflow_dispatch: 10 | 11 | jobs: 12 | sync: 13 | name: 条件判断 14 | runs-on: ubuntu-latest 15 | if: ${{ github.event.repository.fork }} 16 | 17 | steps: 18 | - name: 拉取仓库代码 19 | uses: actions/checkout@v3 20 | 21 | - name: 同步上游更改 22 | id: sync 23 | uses: aormsby/Fork-Sync-With-Upstream-action@v3.4 24 | with: 25 | upstream_sync_repo: ImLTHQ/edge-tunnel 26 | upstream_sync_branch: main 27 | target_sync_branch: main 28 | target_repo_token: ${{ secrets.GITHUB_TOKEN }} 29 | 30 | - name: 同步检查 31 | if: failure() 32 | run: | 33 | echo "[Error] 由于上游仓库的 workflow 文件变更,导致 GitHub 自动暂停了本次自动更新,你需要手动 Sync Fork 一次" 34 | exit 1 # 退出,表示任务失败 35 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: 自动测速 2 | 3 | on: 4 | schedule: 5 | - cron: '0 16 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: 拉取仓库代码 17 | uses: actions/checkout@v4 18 | 19 | - name: 下载 CloudFlare IP 列表 20 | run: | 21 | wget -qO- https://www.cloudflare-cn.com/ips-v4/ | tee ip.txt 22 | 23 | - name: 配置 24 | run: | 25 | set -e 26 | chmod +x CloudflareST 27 | pip install pandas 28 | 29 | - name: 测速 30 | run: | 31 | set -e 32 | ./CloudflareST -cfcolo HKG -sl 5 -n 1000 -dn 5 -o HKG.csv 33 | ./CloudflareST -cfcolo HKG -sl 5 -n 1000 -dn 5 -o KHH.csv 34 | ./CloudflareST -cfcolo HKG -sl 5 -n 1000 -dn 5 -o SIN.csv 35 | ./CloudflareST -cfcolo NRT -sl 5 -n 1000 -dn 5 -o NRT.csv 36 | ./CloudflareST -cfcolo SEA -sl 5 -n 1000 -dn 5 -o SEA.csv 37 | rm -f ip.txt 38 | python csv_to_txt.py 39 | 40 | - name: 提交并推送更改 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | run: | 44 | set -e 45 | git config --local user.email "action@github.com" 46 | git config --local user.name "GitHub Action" 47 | git add *.txt 48 | git commit -m "自动优选" 49 | git push 50 | -------------------------------------------------------------------------------- /AutoTest.txt: -------------------------------------------------------------------------------- 1 | 172.67.226.112#香港 1 2 | 104.23.113.246#香港 2 3 | 104.23.118.89#香港 3 4 | 172.67.254.165#香港 4 5 | 104.24.37.216#香港 5 6 | 172.64.231.206#台湾 1 7 | 172.64.159.207#台湾 2 8 | 172.67.243.58#台湾 3 9 | 172.64.146.197#台湾 4 10 | 172.64.155.3#台湾 5 11 | 172.64.229.173#新加坡 1 12 | 172.64.230.107#新加坡 2 13 | 104.23.107.81#新加坡 3 14 | 172.64.231.125#新加坡 4 15 | 104.23.130.210#新加坡 5 16 | 172.64.230.62#东京 1 17 | 172.67.7.0#东京 2 18 | 172.64.152.77#东京 3 19 | 172.64.154.114#东京 4 20 | 172.64.229.113#东京 5 21 | 172.64.159.177#西雅图 1 22 | 172.64.154.192#西雅图 2 23 | 172.64.156.52#西雅图 3 24 | 104.23.124.197#西雅图 4 25 | 172.64.145.250#西雅图 5 26 | -------------------------------------------------------------------------------- /CloudflareST: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImLTHQ/edgetunnel/37a65dcda6cf40126d682520eec3de0e744aec5e/CloudflareST -------------------------------------------------------------------------------- /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 | # Edge Tunnel 2 | 3 | Edge Tunnel 是一个基于 Cloudflare Pages 的免费代理解决方案, 配置精简, 适合新手快速上手 4 | 5 | ## 项目特点 6 | 7 | - **免费**:利用 Cloudflare Pages 免费托管 8 | - **简单**:Fork 即可使用, 无需复杂配置 9 | - **易用**:通过环境变量灵活配置 10 | - **高速**:依托 Cloudflare 全球网络加速 11 | - **多种方案**:支持 SOCKS5, 反代, NAT64 12 | - **兼容性强**:支持 v2ray 和 clash 客户端 13 | - **自动同步**:支持 GitHub Actions 自动同步上游仓库 14 | - **社区支持**:欢迎参与讨论和贡献代码 15 | 16 | > **欢迎各位大佬指正代码中存在的问题!** 17 | 18 | [![Stargazers over time](https://starchart.cc/ImLTHQ/edgetunnel.svg?variant=adaptive)](https://starchart.cc/ImLTHQ/edgetunnel) 19 | 20 | 如果本项目对您有帮助, 请点 Star 支持 ! 21 | 22 | ## 使用方法 23 | 24 | 1. **Fork 本项目** 25 | 2. **创建 Cloudflare Pages** 26 | - **导入您 Fork 的仓库** 27 | - **添加环境变量** 28 | - **保存并部署** 29 | 3. **导入订阅并开始使用** 30 | 31 |
32 | 建议操作:启用 GitHub Actions 同步上游仓库 33 | 34 | 1. 进入您 Fork 的仓库 35 | 2. 打开 `Actions` 选项卡, 点击 `Enable workflow`, 选择 `上游同步` 36 | 3. 启用后可自动同步作者的最新更新 37 | 38 |
39 | 40 | ## 环境变量说明 41 | 42 | | 变量名 | 示例值 | 说明 | 43 | |-|-|-| 44 | | `SUB_PATH` | `订阅路径` | 域名/`订阅路径` | 45 | | `TXT_URL` | `https://raw.domain.com/CFST.txt` | 优选 IP 列表,格式:`地址:端口#节点名称`,端口默认 443 | 46 | | `PROXY_IP` | `ts.hpc.tw:443` | 反代地址和端口,端口不填默认 443 | 47 | | `SOCKS5` | `账号:密码@地址:端口` | SOCKS5 代理配置 | 48 | | `SOCKS5_GLOBAL` | `true` | 是否启用全局 SOCKS5 代理 | 49 | | `DOH` | `1.1.1.1` | DoH 地址 | 50 | | `NAT64` | `2001:67c:2960:6464::` | NAT64 IPv6 地址前缀 | 51 | 52 | ## 提醒 53 | 54 | - CloudFlare 明文禁止优选IP和使用CF Pages部署代理, 封号风险自己承担 55 | - 部分用户可能需要使用v2ray的分片功能才能正常上网, 给Pages绑定自定义域名也许可以解决 56 | 57 | ## 感谢 58 | - [zizifn](https://github.com/zizifn) 原作者 59 | - [XIU2](https://github.com/XIU2) 优选IP程序作者 60 | -------------------------------------------------------------------------------- /_worker.js: -------------------------------------------------------------------------------- 1 | const a0D=a0b;function a0b(a,b){const c=a0a();return a0b=function(d,e){d=d-0x140;let f=c[d];return f;},a0b(a,b);}(function(a,b){const C=a0b,c=a();while(!![]){try{const d=-parseInt(C(0x15a))/0x1+parseInt(C(0x15c))/0x2+-parseInt(C(0x15b))/0x3*(parseInt(C(0x172))/0x4)+-parseInt(C(0x183))/0x5*(parseInt(C(0x168))/0x6)+parseInt(C(0x150))/0x7+parseInt(C(0x155))/0x8+-parseInt(C(0x145))/0x9*(-parseInt(C(0x167))/0xa);if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a0a,0xc641e));import{connect}from'cloudflare:sockets';let a0c='订阅路径',a0d,a0e='https://raw.githubusercontent.com/ImLTHQ/edgetunnel/main/AutoTest.txt',a0f=[],a0g=![],a0h=![],a0i=a0D(0x178),a0j=a0D(0x16d),a0k=a0D(0x14f);export default{async 'fetch'(a,b){const E=a0D;a0c=b[E(0x192)]??a0c,a0d=a0v(),a0e=b['TXT_URL']??a0e,a0g=b[E(0x165)]??a0g,a0h=b[E(0x148)]??a0h,a0i=b['PROXY_IP']??a0i,a0j=b['NAT64']??a0j,a0k=b[E(0x197)]??a0k;const c=a[E(0x15d)][E(0x142)](E(0x198)),d=c=='websocket',e=c?.[E(0x18f)]()!==E(0x14a),f=new URL(a[E(0x159)]);if(e){if(f[E(0x166)]=='/'+encodeURIComponent(a0c)){const g=a[E(0x15d)][E(0x142)](E(0x149))[E(0x18f)](),h={'v2ray':a0z,'clash':a0A,'tips':a0w},j=Object['keys'](h)[E(0x189)](l=>g[E(0x152)](l));a0f=await a0x();const k=h[j||'tips'];return k(a[E(0x15d)][E(0x142)](E(0x18d)));}else return new Response(null,{'status':0x194});}if(d)return await a0l(a);}};async function a0l(a){const F=a0D,[b,c]=new WebSocketPair(),d=a['headers'][F(0x142)](F(0x19b)),e=a0m(d);return await a0n(e,c),new Response(null,{'status':0x65,'webSocket':b});}function a0a(){const a2=['application/dns-json','accept','buffer','&path=','nodeConfig','SOCKS5','pathname','11330vdzfCA','6wJcPWt','text/html;charset=utf-8','getUint16','text/plain;charset=utf-8','from','2001:67c:2960:6464::','\x0a订阅-','replace','\x0aproxies:\x0a','pop','132008OzTRnS','trim','decode','padStart','/?ed=2560','opened','ts.hpc.tw','?encryption=none&security=tls&sni=','data','charCodeAt','https://','\x20\x20\x20\x20-\x20','vless://','\x0a\x0a请把链接导入\x20Clash\x20或\x20V2Ray\x0a','close','filter','json','5169765XjAiqc','value','writable','proxyConfig','&type=A','addEventListener','find','\x0a\x20\x20network:\x20ws\x0a\x20\x20ws-opts:\x0a\x20\x20\x20\x20path:\x20\x22/?ed=2560\x22\x0a\x20\x20\x20\x20headers:\x0a\x20\x20\x20\x20\x20\x20Host:\x20','getReader','\x0a-\x20name:\x20♻️\x20故障转移\x0a\x20\x20type:\x20fallback\x0a\x20\x20url:\x20https://www.google.com/generate_204\x0a\x20\x20interval:\x2030\x0a\x20\x20proxies:\x0a','Host','text','toLowerCase','getWriter','read','SUB_PATH','releaseLock','/dns-query?name=','slice','encode','DOH','Upgrade','send','push','sec-websocket-protocol','\x0a\x20\x20\x20\x20\x20\x20User-Agent:\x20Chrome','split','\x0a\x20\x20port:\x20','readable','length','get','padEnd','flatMap','13716LBixWZ','-\x20name:\x20','\x0a\x0aproxy-groups:\x0a-\x20name:\x20🚀\x20节点选择\x0a\x20\x20type:\x20select\x0a\x20\x20proxies:\x0a\x20\x20\x20\x20-\x20♻️\x20延迟优选\x0a\x20\x20\x20\x20-\x20♻️\x20故障转移\x0a','SOCKS5_GLOBAL','User-Agent','websocket','reverse','&fp=chrome&type=ws&host=','节点\x20','write','1.1.1.1','6117678DTVjBA','SOCKS5未连通','includes','\x0a\x20\x20udp:\x20true\x0a\x20\x20tls:\x20true\x0a\x20\x20sni:\x20','map','6148248blsaDO','toString','\x0a-\x20name:\x20♻️\x20延迟优选\x0a\x20\x20type:\x20url-test\x0a\x20\x20url:\x20https://www.google.com/generate_204\x0a\x20\x20interval:\x2030\x0a\x20\x20tolerance:\x2050\x0a\x20\x20proxies:\x0a','join','url','1496570wZExjE','12neuCzU','210832MjHGpL','headers','message','SOCKS5握手失败'];a0a=function(){return a2;};return a0a();}function a0m(a){const G=a0D;a=a[G(0x16f)](/-/g,'+')[G(0x16f)](/_/g,'/');const b=atob(a),c=Uint8Array['from'](b,d=>d[G(0x17b)](0x0));return c[G(0x162)];}async function a0n(a,b,c){const H=a0D;if(a0q(new Uint8Array(a[H(0x195)](0x1,0x11)))!==a0d)return null;const d=new Uint8Array(a)[0x11],e=0x12+d+0x1,f=a[H(0x195)](e,e+0x2),g=new DataView(f)['getUint16'](0x0),h=e+0x2,j=new Uint8Array(a[H(0x195)](h,h+0x1)),k=j[0x0];let l=0x0,m='',n=h+0x1;switch(k){case 0x1:l=0x4,m=new Uint8Array(a[H(0x195)](n,n+l))['join']('.');break;case 0x2:l=new Uint8Array(a[H(0x195)](n,n+0x1))[0x0],n+=0x1,m=new TextDecoder()[H(0x174)](a[H(0x195)](n,n+l));break;case 0x3:l=0x10;const p=new DataView(a[H(0x195)](n,n+l)),q=[];for(let r=0x0;r<0x8;r++){q[H(0x19a)](p[H(0x16a)](r*0x2)['toString'](0x10));}m=q[H(0x158)](':');break;}const o=a[H(0x195)](n+l);if(a0h&&a0g)c=await a0t(k,m,g),await c[H(0x177)];else try{c=await connect({'hostname':m,'port':g,'allowHalfOpen':!![]}),await c[H(0x177)];}catch{if(a0g)try{c=await a0t(k,m,g),await c[H(0x177)];}catch{try{const s=k===0x1?a0o(m):a0o(await a0p(m));c=await connect({'hostname':s,'port':g}),await c[H(0x177)];}catch{let [t,u]=a0i[H(0x19d)](':');c=await connect({'hostname':t,'port':u||0x1bb}),await c[H(0x177)];}}else try{const v=k===0x1?a0o(m):a0o(await a0p(m));c=await connect({'hostname':v,'port':g}),await c[H(0x177)];}catch{let [w,x]=a0i[H(0x19d)](':');c=await connect({'hostname':w,'port':x||0x1bb}),await c['opened'];}}a0s(b,c,o);}function a0o(a){const I=a0D,b=a[I(0x19d)]('.')[I(0x154)](c=>(+c)[I(0x156)](0x10)[I(0x175)](0x2,'0'));return'['+a0j+b[0x0]+b[0x1]+':'+b[0x2]+b[0x3]+']';}async function a0p(a){const J=a0D,{Answer:b}=await(await fetch(J(0x17c)+a0k+J(0x194)+a+J(0x187),{'headers':{'Accept':J(0x160)}}))[J(0x182)]();return b[J(0x189)](({type:c})=>c===0x1)[J(0x17a)];}function a0q(a,b=0x0){const K=a0D,c=(a0r[a[b+0x0]]+a0r[a[b+0x1]]+a0r[a[b+0x2]]+a0r[a[b+0x3]]+'-'+a0r[a[b+0x4]]+a0r[a[b+0x5]]+'-'+a0r[a[b+0x6]]+a0r[a[b+0x7]]+'-'+a0r[a[b+0x8]]+a0r[a[b+0x9]]+'-'+a0r[a[b+0xa]]+a0r[a[b+0xb]]+a0r[a[b+0xc]]+a0r[a[b+0xd]]+a0r[a[b+0xe]]+a0r[a[b+0xf]])[K(0x18f)]();return c;}const a0r=[];for(let a0B=0x0;a0B<0x100;++a0B){a0r['push']((a0B+0x100)[a0D(0x156)](0x10)[a0D(0x195)](0x1));}async function a0s(a,b,c){const L=a0D;a[L(0x161)](),await a[L(0x199)](new Uint8Array([0x0,0x0])[L(0x162)]);const d=b[L(0x185)][L(0x190)](),e=b[L(0x140)]['getReader']();if(c)await d[L(0x14e)](c);a[L(0x188)](L(0x15e),async g=>{const M=L;await d[M(0x14e)](g[M(0x17a)]);}),f(),((async()=>{const N=L;while(!![]){const {value:g,done:h}=await e[N(0x191)]();if(h)break;if(g)await a[N(0x199)](g);}})());async function f(){const O=L;while(!![]){await new Promise(g=>setTimeout(g,0x2710)),d[O(0x14e)](new Uint8Array(0x0)),a['send']('');}}}async function a0t(a,b,c){const P=a0D,{username:d,password:e,hostname:f,port:g}=await a0u(a0g),h=connect({'hostname':f,'port':g});try{await h[P(0x177)];}catch{return new Response(P(0x151),{'status':0x190});}const j=h['writable']['getWriter'](),k=h[P(0x140)][P(0x18b)](),l=new TextEncoder(),m=new Uint8Array([0x5,0x2,0x0,0x2]);await j[P(0x14e)](m);let n=(await k[P(0x191)]())['value'];if(n[0x1]===0x2){if(!d||!e)return q();const r=new Uint8Array([0x1,d[P(0x141)],...l[P(0x196)](d),e[P(0x141)],...l[P(0x196)](e)]);await j[P(0x14e)](r),n=(await k[P(0x191)]())[P(0x184)];if(n[0x0]!==0x1||n[0x1]!==0x0)return q();}let o;switch(a){case 0x1:o=new Uint8Array([0x1,...b[P(0x19d)]('.')['map'](Number)]);break;case 0x2:o=new Uint8Array([0x3,b[P(0x141)],...l[P(0x196)](b)]);break;case 0x3:o=new Uint8Array([0x4,...b[P(0x19d)](':')[P(0x144)](s=>[parseInt(s['slice'](0x0,0x2),0x10),parseInt(s[P(0x195)](0x2),0x10)])]);break;default:return q();}const p=new Uint8Array([0x5,0x1,0x0,...o,c>>0x8,c&0xff]);await j[P(0x14e)](p),n=(await k[P(0x191)]())[P(0x184)];if(n[0x0]!==0x5||n[0x1]!==0x0)return q();j[P(0x193)](),k[P(0x193)]();return h;function q(){const Q=P;return j[Q(0x193)](),k['releaseLock'](),h[Q(0x180)](),new Response(Q(0x15f),{'status':0x190});}}async function a0u(a){const R=a0D,[b,c]=a[R(0x19d)]('@')[R(0x14b)]();let d,e,f,g;if(c){const j=c[R(0x19d)](':');d=j[0x0],e=j[0x1];}const h=b[R(0x19d)](':');return g=Number(h[R(0x171)]()),f=h[R(0x158)](':'),{'username':d,'password':e,'hostname':f,'port':g};}function a0v(){const S=a0D,a=Array[S(0x16c)](new TextEncoder()[S(0x196)](a0c))['map'](d=>d[S(0x156)](0x10)[S(0x175)](0x2,'0'))[S(0x158)]('')[S(0x195)](0x0,0x14)[S(0x143)](0x14,'0'),b=a[S(0x195)](0x0,0x8),c=a[S(0x195)](-0xc);return b+'-0000-4000-8000-'+c;}async function a0w(){const T=a0D,a=T(0x16e)+a0c+T(0x17f);return new Response(a,{'status':0xc8,'headers':{'Content-Type':T(0x169)}});}async function a0x(){const U=a0D;if(a0e){const a=await fetch(a0e),b=await a[U(0x18e)]();return b[U(0x19d)]('\x0a')['map'](c=>c[U(0x173)]())[U(0x181)](c=>c);}return[];}function a0y(a,b){const V=a0D;return a['unshift'](b+'#原生节点'),a[V(0x154)]((c,d)=>{const W=V,[e,f=W(0x14d)+(d+0x1)]=c[W(0x19d)]('#'),g=e[W(0x19d)](':'),h=g[W(0x141)]>0x1?Number(g[W(0x171)]()):0x1bb,j=g[W(0x158)](':');return{'地址':j,'端口':h,'节点名字':f};});}function a0z(a){const Y=a0D,b=a0y(a0f,a),c=b['map'](({地址:d,端口:e,节点名字:f})=>{const X=a0b;return X(0x17e)+a0d+'@'+d+':'+e+X(0x179)+a+X(0x14c)+a+X(0x163)+encodeURIComponent(X(0x176))+'#'+f;})[Y(0x158)]('\x0a');return new Response(c,{'status':0xc8,'headers':{'Content-Type':Y(0x16b)}});}function a0A(a){const a1=a0D,b=a0y(a0f,a),c=g=>{const Z=a0b;return g[Z(0x154)](({地址:h,端口:j,节点名字:k})=>{const a0=Z;return{'nodeConfig':a0(0x146)+k+'\x0a\x20\x20type:\x20vless\x0a\x20\x20server:\x20'+h+a0(0x19e)+j+'\x0a\x20\x20uuid:\x20'+a0d+a0(0x153)+a+a0(0x18a)+a+a0(0x19c),'proxyConfig':a0(0x17d)+k};});},d=c(b)[a1(0x154)](g=>g[a1(0x164)])['join']('\x0a'),e=c(b)[a1(0x154)](g=>g[a1(0x186)])['join']('\x0a'),f=a1(0x170)+d+a1(0x147)+e+a1(0x157)+e+a1(0x18c)+e+'\x0a\x0arules:\x0a\x20\x20-\x20GEOSITE,category-ads-all,REJECT\x0a\x0a\x20\x20-\x20GEOSITE,cn,DIRECT\x0a\x20\x20-\x20GEOIP,CN,DIRECT,no-resolve\x0a\x0a\x20\x20-\x20MATCH,🚀\x20节点选择\x0a';return new Response(f,{'status':0xc8,'headers':{'Content-Type':a1(0x16b)}});} -------------------------------------------------------------------------------- /csv_to_txt.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | csv_files = [ 4 | "HKG.csv", 5 | "KHH.csv", 6 | "SIN.csv", 7 | "NRT.csv", 8 | "SEA.csv" 9 | ] 10 | 11 | area_names = [ 12 | "香港", 13 | "台湾", 14 | "新加坡", 15 | "东京", 16 | "西雅图" 17 | ] 18 | 19 | def csv_to_txt(csv_files, area_names, output_filename): 20 | with open(output_filename, 'w', encoding='utf-8') as f: 21 | for csv_file, area in zip(csv_files, area_names): 22 | df = pd.read_csv(csv_file, encoding='utf-8') 23 | for i, (ip, speed) in enumerate(zip(df.iloc[:, 0], df.iloc[:, 5]), start=1): 24 | f.write(f"{ip}#{area} {i}\n") 25 | 26 | csv_to_txt(csv_files, area_names, "AutoTest.txt") -------------------------------------------------------------------------------- /src.js: -------------------------------------------------------------------------------- 1 | import { connect } from "cloudflare:sockets"; 2 | 3 | // 配置区块 4 | let 订阅路径 = "订阅路径"; 5 | 6 | let 验证UUID; 7 | 8 | let 优选链接 = "https://raw.githubusercontent.com/ImLTHQ/edgetunnel/main/AutoTest.txt"; 9 | let 优选列表 = []; 10 | 11 | let SOCKS5代理 = false; 12 | let SOCKS5全局代理 = false; 13 | 14 | let 反代IP = "ts.hpc.tw"; 15 | 16 | let NAT64前缀 = "2001:67c:2960:6464::"; 17 | let DOH地址 = "1.1.1.1"; 18 | 19 | // 网页入口 20 | export default { 21 | async fetch(访问请求, env) { 22 | 订阅路径 = env.SUB_PATH ?? 订阅路径; 23 | 验证UUID = 生成UUID(); 24 | 优选链接 = env.TXT_URL ?? 优选链接; 25 | SOCKS5代理 = env.SOCKS5 ?? SOCKS5代理; 26 | SOCKS5全局代理 = env.SOCKS5_GLOBAL ?? SOCKS5全局代理; 27 | 反代IP = env.PROXY_IP ?? 反代IP; 28 | NAT64前缀 = env.NAT64 ?? NAT64前缀; 29 | DOH地址 = env.DOH ?? DOH地址; 30 | 31 | const 读取我的请求标头 = 访问请求.headers.get("Upgrade"); 32 | const WS请求 = 读取我的请求标头 == "websocket"; 33 | const 不是WS请求 = 读取我的请求标头?.toLowerCase() !== "websocket"; 34 | const url = new URL(访问请求.url); 35 | 36 | if (不是WS请求) { 37 | if (url.pathname == `/${encodeURIComponent(订阅路径)}`) { 38 | const 用户代理 = 访问请求.headers.get("User-Agent").toLowerCase(); 39 | const 配置生成器 = { 40 | v2ray: v2ray配置文件, 41 | clash: clash配置文件, 42 | tips: 提示界面, 43 | }; 44 | const 工具 = Object.keys(配置生成器).find((工具) => 用户代理.includes(工具)); 45 | 优选列表 = await 获取优选列表(); 46 | const 生成配置 = 配置生成器[工具 || "tips"]; 47 | return 生成配置(访问请求.headers.get("Host")); 48 | } else { 49 | return new Response(null, { status: 404 }); 50 | } 51 | } 52 | 53 | if (WS请求) { 54 | return await 升级WS请求(访问请求); 55 | } 56 | }, 57 | }; 58 | 59 | // 脚本主要架构 60 | // 第一步,读取和构建基础访问结构 61 | async function 升级WS请求(访问请求) { 62 | const [客户端, WS接口] = new WebSocketPair(); //创建WS接口对象 63 | const 读取我的加密访问内容数据头 = 访问请求.headers.get('sec-websocket-protocol'); //读取访问标头中的WS通信数据 64 | const 解密数据 = 使用64位加解密(读取我的加密访问内容数据头); //解密目标访问数据,传递给TCP握手进程 65 | await 解析VL标头(解密数据, WS接口); //解析VL数据并进行TCP握手 66 | return new Response(null, { status: 101, webSocket: 客户端 }); //一切准备就绪后,回复客户端WS连接升级成功 67 | } 68 | 69 | function 使用64位加解密(还原混淆字符) { 70 | 还原混淆字符 = 还原混淆字符.replace(/-/g, "+").replace(/_/g, "/"); 71 | const 解密数据 = atob(还原混淆字符); 72 | const 解密 = Uint8Array.from(解密数据, (c) => c.charCodeAt(0)); 73 | return 解密.buffer; 74 | } 75 | 76 | // 第二步,解读VL协议数据,创建TCP握手(直连、SOCKS5、反代、NAT64) 77 | async function 解析VL标头(VL数据, WS接口, TCP接口) { 78 | if (验证VL的密钥(new Uint8Array(VL数据.slice(1, 17))) !== 验证UUID) { 79 | return null; 80 | } 81 | 82 | const 获取数据定位 = new Uint8Array(VL数据)[17]; 83 | const 提取端口索引 = 18 + 获取数据定位 + 1; 84 | const 建立端口缓存 = VL数据.slice(提取端口索引, 提取端口索引 + 2); 85 | const 访问端口 = new DataView(建立端口缓存).getUint16(0); 86 | 87 | const 提取地址索引 = 提取端口索引 + 2; 88 | const 建立地址缓存 = new Uint8Array(VL数据.slice(提取地址索引, 提取地址索引 + 1)); 89 | const 识别地址类型 = 建立地址缓存[0]; 90 | 91 | let 地址长度 = 0; 92 | let 访问地址 = ""; 93 | let 地址信息索引 = 提取地址索引 + 1; 94 | 95 | switch (识别地址类型) { 96 | case 1: 97 | 地址长度 = 4; 98 | 访问地址 = new Uint8Array(VL数据.slice(地址信息索引, 地址信息索引 + 地址长度)).join("."); 99 | break; 100 | case 2: 101 | 地址长度 = new Uint8Array(VL数据.slice(地址信息索引, 地址信息索引 + 1))[0]; 102 | 地址信息索引 += 1; 103 | 访问地址 = new TextDecoder().decode(VL数据.slice(地址信息索引, 地址信息索引 + 地址长度)); 104 | break; 105 | case 3: 106 | 地址长度 = 16; 107 | const dataView = new DataView(VL数据.slice(地址信息索引, 地址信息索引 + 地址长度)); 108 | const ipv6 = []; 109 | for (let i = 0; i < 8; i++) { 110 | ipv6.push(dataView.getUint16(i * 2).toString(16)); 111 | } 112 | 访问地址 = ipv6.join(":"); 113 | break; 114 | } 115 | 116 | const 写入初始数据 = VL数据.slice(地址信息索引 + 地址长度); 117 | 118 | // 优先SOCKS5全局 119 | if (SOCKS5全局代理 && SOCKS5代理) { 120 | TCP接口 = await 创建SOCKS5接口(识别地址类型, 访问地址, 访问端口); 121 | await TCP接口.opened; 122 | } else { 123 | try { 124 | TCP接口 = await connect({ hostname: 访问地址, port: 访问端口, allowHalfOpen: true }); 125 | await TCP接口.opened; 126 | } catch { 127 | // 尝试SOCKS5 128 | if (SOCKS5代理) { 129 | try { 130 | TCP接口 = await 创建SOCKS5接口(识别地址类型, 访问地址, 访问端口); 131 | await TCP接口.opened; 132 | } catch { 133 | // SOCKS5失败 尝试NAT64 134 | try { 135 | const NAT64地址 = 识别地址类型 === 1 136 | ? 转换IPv4到NAT64(访问地址) 137 | : 转换IPv4到NAT64(await 解析域名到IPv4(访问地址)); 138 | TCP接口 = await connect({ hostname: NAT64地址, port: 访问端口 }); 139 | await TCP接口.opened; 140 | } catch { 141 | // NAT64失败 尝试反代 142 | let [反代IP地址, 反代IP端口] = 反代IP.split(":"); 143 | TCP接口 = await connect({ 144 | hostname: 反代IP地址, 145 | port: 反代IP端口 || 443, 146 | }); 147 | await TCP接口.opened; 148 | } 149 | } 150 | } else { 151 | // 没有SOCKS5 尝试NAT64 152 | try { 153 | const NAT64地址 = 识别地址类型 === 1 154 | ? 转换IPv4到NAT64(访问地址) 155 | : 转换IPv4到NAT64(await 解析域名到IPv4(访问地址)); 156 | TCP接口 = await connect({ hostname: NAT64地址, port: 访问端口 }); 157 | await TCP接口.opened; 158 | } catch { 159 | // NAT64失败 尝试反代 160 | let [反代IP地址, 反代IP端口] = 反代IP.split(":"); 161 | TCP接口 = await connect({ 162 | hostname: 反代IP地址, 163 | port: 反代IP端口 || 443, 164 | }); 165 | await TCP接口.opened; 166 | } 167 | } 168 | } 169 | } 170 | 建立传输管道(WS接口, TCP接口, 写入初始数据); 171 | } 172 | 173 | // 将IPv4地址转换为NAT64 IPv6地址 174 | function 转换IPv4到NAT64(ipv4地址) { 175 | const 十六进制 = ipv4地址.split(".").map(段 => (+段).toString(16).padStart(2, "0")); 176 | return `[${NAT64前缀}${十六进制[0]}${十六进制[1]}:${十六进制[2]}${十六进制[3]}]`; 177 | } 178 | 179 | // 解析域名到IPv4地址 180 | async function 解析域名到IPv4(域名) { 181 | const { Answer } = await (await fetch(`https://${DOH地址}/dns-query?name=${域名}&type=A`, { 182 | headers: { "Accept": "application/dns-json" } 183 | })).json(); 184 | return Answer.find(({ type }) => type === 1).data; 185 | } 186 | 187 | function 验证VL的密钥(arr, offset = 0) { 188 | const uuid = ( 189 | 转换密钥格式[arr[offset + 0]] + 190 | 转换密钥格式[arr[offset + 1]] + 191 | 转换密钥格式[arr[offset + 2]] + 192 | 转换密钥格式[arr[offset + 3]] + 193 | "-" + 194 | 转换密钥格式[arr[offset + 4]] + 195 | 转换密钥格式[arr[offset + 5]] + 196 | "-" + 197 | 转换密钥格式[arr[offset + 6]] + 198 | 转换密钥格式[arr[offset + 7]] + 199 | "-" + 200 | 转换密钥格式[arr[offset + 8]] + 201 | 转换密钥格式[arr[offset + 9]] + 202 | "-" + 203 | 转换密钥格式[arr[offset + 10]] + 204 | 转换密钥格式[arr[offset + 11]] + 205 | 转换密钥格式[arr[offset + 12]] + 206 | 转换密钥格式[arr[offset + 13]] + 207 | 转换密钥格式[arr[offset + 14]] + 208 | 转换密钥格式[arr[offset + 15]] 209 | ).toLowerCase(); 210 | return uuid; 211 | } 212 | 213 | const 转换密钥格式 = []; 214 | for (let i = 0; i < 256; ++i) { 215 | 转换密钥格式.push((i + 256).toString(16).slice(1)); 216 | } 217 | 218 | // 第三步,创建客户端WS-CF-目标的传输通道并监听状态 219 | async function 建立传输管道(WS接口, TCP接口, 写入初始数据) { 220 | WS接口.accept(); 221 | await WS接口.send(new Uint8Array([0, 0]).buffer); 222 | 223 | const 传输数据 = TCP接口.writable.getWriter(); 224 | const 读取数据 = TCP接口.readable.getReader(); 225 | 226 | if (写入初始数据) await 传输数据.write(写入初始数据); 227 | 228 | WS接口.addEventListener("message", async (event) => { 229 | await 传输数据.write(event.data); 230 | }); 231 | 定时双端保活(); 232 | (async () => { 233 | while (true) { 234 | const { value: 返回数据, done } = await 读取数据.read(); 235 | if (done) break; 236 | if (返回数据) await WS接口.send(返回数据); 237 | } 238 | })(); 239 | async function 定时双端保活() { 240 | while (true) { 241 | await new Promise(resolve => setTimeout(resolve, 10000)); 242 | 传输数据.write(new Uint8Array(0)); 243 | WS接口.send(''); 244 | } 245 | } 246 | } 247 | 248 | // SOCKS5部分 249 | async function 创建SOCKS5接口(识别地址类型, 访问地址, 访问端口) { 250 | const { username, password, hostname, port } = await 获取SOCKS5代理(SOCKS5代理); 251 | const SOCKS5接口 = connect({ hostname, port }); 252 | try { 253 | await SOCKS5接口.opened; 254 | } catch { 255 | return new Response("SOCKS5未连通", { status: 400 }); 256 | } 257 | const writer = SOCKS5接口.writable.getWriter(); 258 | const reader = SOCKS5接口.readable.getReader(); 259 | const encoder = new TextEncoder(); 260 | const socksGreeting = new Uint8Array([5, 2, 0, 2]); //支持无认证和用户名/密码认证 261 | await writer.write(socksGreeting); 262 | let res = (await reader.read()).value; 263 | if (res[1] === 0x02) { 264 | //用户名/密码认证 265 | if (!username || !password) { 266 | return 关闭接口并退出(); 267 | } 268 | const authRequest = new Uint8Array([1, username.length, ...encoder.encode(username), password.length, ...encoder.encode(password)]); 269 | await writer.write(authRequest); 270 | res = (await reader.read()).value; 271 | if (res[0] !== 0x01 || res[1] !== 0x00) { 272 | return 关闭接口并退出(); 273 | } 274 | } 275 | let 转换访问地址; 276 | switch (识别地址类型) { 277 | case 1: // IPv4 278 | 转换访问地址 = new Uint8Array([1, ...访问地址.split(".").map(Number)]); 279 | break; 280 | case 2: // 域名 281 | 转换访问地址 = new Uint8Array([3, 访问地址.length, ...encoder.encode(访问地址)]); 282 | break; 283 | case 3: // IPv6 284 | 转换访问地址 = new Uint8Array([4, ...访问地址.split(":").flatMap((x) => [parseInt(x.slice(0, 2), 16), parseInt(x.slice(2), 16)])]); 285 | break; 286 | default: 287 | return 关闭接口并退出(); 288 | } 289 | const socksRequest = new Uint8Array([5, 1, 0, ...转换访问地址, 访问端口 >> 8, 访问端口 & 0xff]); 290 | await writer.write(socksRequest); 291 | res = (await reader.read()).value; 292 | if (res[0] !== 0x05 || res[1] !== 0x00) { 293 | return 关闭接口并退出(); 294 | } 295 | writer.releaseLock(); 296 | reader.releaseLock(); 297 | return SOCKS5接口; 298 | function 关闭接口并退出() { 299 | writer.releaseLock(); 300 | reader.releaseLock(); 301 | SOCKS5接口.close(); 302 | return new Response("SOCKS5握手失败", { status: 400 }); 303 | } 304 | } 305 | async function 获取SOCKS5代理(SOCKS5) { 306 | const [latter, former] = SOCKS5.split("@").reverse(); 307 | let username, password, hostname, port; 308 | if (former) { 309 | const formers = former.split(":"); 310 | username = formers[0]; 311 | password = formers[1]; 312 | } 313 | const latters = latter.split(":"); 314 | port = Number(latters.pop()); 315 | hostname = latters.join(":"); 316 | return { username, password, hostname, port }; 317 | } 318 | 319 | // 其它 320 | function 生成UUID() { 321 | const 二十位 = Array.from(new TextEncoder().encode(订阅路径)) 322 | .map((byte) => byte.toString(16).padStart(2, "0")) 323 | .join("") 324 | .slice(0, 20) 325 | .padEnd(20, "0"); 326 | 327 | const 前八位 = 二十位 328 | .slice(0, 8); 329 | const 后十二位 = 二十位 330 | .slice(-12); 331 | 332 | return `${前八位}-0000-4000-8000-${后十二位}`; 333 | } 334 | 335 | async function 提示界面() { 336 | const 提示界面 = ` 337 | 订阅-${订阅路径} 338 | 344 | 请把链接导入 Clash 或 V2Ray 345 | `; 346 | 347 | return new Response(提示界面, { 348 | status: 200, 349 | headers: { "Content-Type": "text/html;charset=utf-8" }, 350 | }); 351 | } 352 | 353 | async function 获取优选列表() { 354 | if (优选链接) { 355 | const 读取优选文本 = await fetch(优选链接); 356 | const 转换优选文本 = await 读取优选文本.text(); 357 | return 转换优选文本 358 | .split("\n") 359 | .map((line) => line.trim()) 360 | .filter((line) => line); 361 | } 362 | return []; 363 | } 364 | 365 | function 处理优选列表(优选列表, hostName) { 366 | 优选列表.unshift(`${hostName}#原生节点`); 367 | return 优选列表.map((获取优选, index) => { 368 | const [地址端口, 节点名字 = `节点 ${index + 1}`] = 获取优选.split("#"); 369 | const 拆分地址端口 = 地址端口.split(":"); 370 | const 端口 = 拆分地址端口.length > 1 ? Number(拆分地址端口.pop()) : 443; 371 | const 地址 = 拆分地址端口.join(":"); 372 | return { 地址, 端口, 节点名字 }; 373 | }); 374 | } 375 | 376 | // 订阅页面 377 | function v2ray配置文件(hostName) { 378 | const 节点列表 = 处理优选列表(优选列表, hostName); 379 | const 配置内容 = 节点列表 380 | .map(({ 地址, 端口, 节点名字 }) => { 381 | return `vless://${验证UUID}@${地址}:${端口}?encryption=none&security=tls&sni=${hostName}&fp=chrome&type=ws&host=${hostName}&path=${encodeURIComponent("/?ed=2560")}#${节点名字}`; 382 | }) 383 | .join("\n"); 384 | 385 | return new Response(配置内容, { 386 | status: 200, 387 | headers: { "Content-Type": "text/plain;charset=utf-8" }, 388 | }); 389 | } 390 | 391 | function clash配置文件(hostName) { 392 | const 节点列表 = 处理优选列表(优选列表, hostName); 393 | const 生成节点 = (节点列表) => { 394 | return 节点列表.map(({ 地址, 端口, 节点名字 }) => { 395 | return { 396 | nodeConfig: `- name: ${节点名字} 397 | type: vless 398 | server: ${地址} 399 | port: ${端口} 400 | uuid: ${验证UUID} 401 | udp: true 402 | tls: true 403 | sni: ${hostName} 404 | network: ws 405 | ws-opts: 406 | path: "/?ed=2560" 407 | headers: 408 | Host: ${hostName} 409 | User-Agent: Chrome`, 410 | proxyConfig: ` - ${节点名字}`, 411 | }; 412 | }); 413 | }; 414 | 415 | const 节点配置 = 生成节点(节点列表) 416 | .map((node) => node.nodeConfig) 417 | .join("\n"); 418 | const 代理配置 = 生成节点(节点列表) 419 | .map((node) => node.proxyConfig) 420 | .join("\n"); 421 | 422 | const 配置内容 = ` 423 | proxies: 424 | ${节点配置} 425 | 426 | proxy-groups: 427 | - name: 🚀 节点选择 428 | type: select 429 | proxies: 430 | - ♻️ 延迟优选 431 | - ♻️ 故障转移 432 | ${代理配置} 433 | - name: ♻️ 延迟优选 434 | type: url-test 435 | url: https://www.google.com/generate_204 436 | interval: 30 437 | tolerance: 50 438 | proxies: 439 | ${代理配置} 440 | - name: ♻️ 故障转移 441 | type: fallback 442 | url: https://www.google.com/generate_204 443 | interval: 30 444 | proxies: 445 | ${代理配置} 446 | 447 | rules: 448 | - GEOSITE,category-ads-all,REJECT 449 | 450 | - GEOSITE,cn,DIRECT 451 | - GEOIP,CN,DIRECT,no-resolve 452 | 453 | - MATCH,🚀 节点选择 454 | `; 455 | 456 | return new Response(配置内容, { 457 | status: 200, 458 | headers: { "Content-Type": "text/plain;charset=utf-8" }, 459 | }); 460 | } 461 | --------------------------------------------------------------------------------