├── .github └── workflows │ ├── obfuscator.yml │ └── sync.yml ├── LICENSE ├── README.md ├── _worker.js └── 明文源码.js /.github/workflows/obfuscator.yml: -------------------------------------------------------------------------------- 1 | name: Obfuscate and Commit 2 | 3 | on: 4 | #push: 5 | # paths: 6 | # - '明文源码.js' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | obfuscate: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Use Node.js 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: "16" 24 | 25 | - name: Install dependencies 26 | run: npm install -g javascript-obfuscator 27 | 28 | - name: Obfuscate code 29 | run: | 30 | javascript-obfuscator 明文源码.js --output _worker.js \ 31 | --compact true \ 32 | --control-flow-flattening true \ 33 | --control-flow-flattening-threshold 1 \ 34 | --dead-code-injection true \ 35 | --dead-code-injection-threshold 1 \ 36 | --identifier-names-generator hexadecimal \ 37 | --rename-globals true \ 38 | --string-array true \ 39 | --string-array-encoding 'rc4' \ 40 | --string-array-threshold 1 \ 41 | --transform-object-keys true \ 42 | --unicode-escape-sequence true 43 | 44 | - name: Commit changes 45 | run: | 46 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 47 | git config --local user.name "github-actions[bot]" 48 | git add _worker.js 49 | git commit -m "Obfuscate _worker.js" || echo "No changes to commit" 50 | 51 | - name: Push changes 52 | uses: ad-m/github-push-action@master 53 | with: 54 | github_token: ${{ secrets.GITHUB_TOKEN }} 55 | branch: ${{ github.ref }} -------------------------------------------------------------------------------- /.github/workflows/sync.yml: -------------------------------------------------------------------------------- 1 | name: Upstream Sync 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | schedule: 8 | - cron: "0 0 * * *" # every day 9 | workflow_dispatch: 10 | watch: 11 | types: started 12 | 13 | jobs: 14 | sync_latest_from_upstream: 15 | name: Sync latest commits from upstream repo 16 | runs-on: ubuntu-latest 17 | if: ${{ github.event.repository.fork }} 18 | 19 | steps: 20 | # Step 1: run a standard checkout action 21 | - name: Checkout target repo 22 | uses: actions/checkout@v3 23 | 24 | # Step 2: run the sync action 25 | - name: Sync upstream changes 26 | id: sync 27 | uses: aormsby/Fork-Sync-With-Upstream-action@v3.4 28 | with: 29 | upstream_sync_repo: cmliu/epeius 30 | upstream_sync_branch: main 31 | target_sync_branch: main 32 | target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set 33 | 34 | # Set test_mode true to run tests instead of the true action!! 35 | test_mode: false 36 | 37 | - name: Sync check 38 | if: failure() 39 | run: | 40 | echo "[Error] 由于上游仓库的 workflow 文件变更,导致 GitHub 自动暂停了本次自动更新,你需要手动 Sync Fork 一次." 41 | echo "[Error] Due to a change in the workflow file of the upstream repository, GitHub has automatically suspended the scheduled automatic update. You need to manually sync your fork." 42 | exit 1 43 | -------------------------------------------------------------------------------- /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 | # 🚀 epeius 2 | 这是一个基于 CF Worker 平台的脚本,在原版的基础上修改了显示 Trojan 配置信息转换为订阅内容。使用该脚本,你可以方便地将 Trojan 配置信息使用在线配置转换到 Clash 或 Singbox 等工具中。 3 | 4 | - **一步到位**部署视频教程:https://youtu.be/MBlAqYajVSY ***小白必看 一步到胃 最佳推荐!!!*** 5 | - **自制优选**订阅视频教程:https://youtu.be/jOhq3QpXG_I *折腾自己的专属订阅* 6 | - **进阶使用**技巧视频教程:https://youtu.be/0Cd8uTNJj1Q *然后成为折腾的王* 7 | 8 | Telegram交流群:[@CMLiussss](https://t.me/CMLiussss) 9 | 10 | ## ⚠️ 免责声明 11 | 12 | 本免责声明适用于 GitHub 上的 “epeius” 项目(以下简称“本项目”),项目链接为:https://github.com/cmliu/epeius 。 13 | 14 | ### 用途 15 | 本项目仅供教育、研究和安全测试目的而设计和开发。旨在为安全研究人员、学术界人士及技术爱好者提供一个探索和实践网络通信技术的工具。 16 | 17 | ### 合法性 18 | 在下载和使用本项目代码时,必须遵守使用者所适用的法律和规定。使用者有责任确保其行为符合所在地区的法律框架、规章制度及其他相关规定。 19 | 20 | ### 免责 21 | 1. 作为本项目的 **二次开发作者**(以下简称“作者”),我 **cmliu** 强调本项目仅应用于合法、道德和教育目的。 22 | 2. 作者不认可、不支持亦不鼓励任何形式的非法使用。如果发现本项目被用于任何非法或不道德的活动,作者将对此强烈谴责。 23 | 3. 作者对任何人或组织利用本项目代码从事的任何非法活动不承担责任。使用本项目代码所产生的任何后果,均由使用者自行承担。 24 | 4. 作者不对使用本项目代码可能引起的任何直接或间接损害负责。 25 | 5. 为避免任何意外后果或法律风险,使用者应在使用本项目代码后的 24 小时内删除代码。 26 | 27 | 通过使用本项目代码,使用者即表示理解并同意本免责声明的所有条款。如使用者不同意这些条款,应立即停止使用本项目。 28 | 29 | 作者保留随时更新本免责声明的权利,且不另行通知。最新版本的免责声明将发布在本项目的 GitHub 页面上。 30 | 31 | ## 🔥 风险提示 32 | - 通过提交虚假的节点配置给订阅服务,避免节点配置信息泄露。 33 | - 另外,您也可以选择自行部署 [WorkerVless2sub 订阅生成服务](https://github.com/cmliu/WorkerVless2sub),这样既可以利用订阅生成器的便利。 34 | 35 | ## 💡 如何使用? 36 | ### ⚙️ Workers 部署方法 [视频教程](https://www.youtube.com/watch?v=MBlAqYajVSY&t=169s) 37 | 38 |
39 | 「 Workers 部署文字教程 」 40 | 41 | 1. 部署 CF Worker: 42 | - 在 CF Worker 控制台中创建一个新的 Worker。 43 | - 将 [worker.js](https://github.com/cmliu/epeius/blob/main/_worker.js) 的内容粘贴到 Worker 编辑器中。 44 | - 将第 3 行 `password` 修改成你自己的 **密码** 45 | 46 | 2. 添加优选线路: 47 | - 给 `addresses` 按格式添加优选域名/优选IP,若不带端口号 TLS默认端口为443,#号后为备注别名,例如: 48 | ```js 49 | let addresses = [ 50 | //当sub为空时启用本地优选域名/优选IP 51 | 'www.visa.com.sg#官方优选域名', 52 | 'www.wto.org:8443#官方优选域名', 53 | 'visa.cn:2087', 54 | 'icook.hk', 55 | ]; 56 | ``` 57 | - 或 给 `sub` 添加 **Trojan优选订阅生成器** 地址,例如: 58 | ```js 59 | let sub = 'Trojan.cmliussss.net'; 60 | ``` 61 | 62 | 3. 访问订阅内容: 63 | - 访问 `https://[YOUR-WORKERS-URL]/[PASSWORD]` 即可获取订阅内容。 64 | - 例如 `https://trojan.google.workers.dev/auto` 就是你的通用自适应订阅地址。 65 | - 例如 `https://trojan.google.workers.dev/auto?sub` Base64订阅格式,适用PassWall,SSR+等。 66 | - 例如 `https://trojan.google.workers.dev/auto?clash` Clash订阅格式,适用OpenClash等。 67 | - 例如 `https://trojan.google.workers.dev/auto?sb` singbox订阅格式,适用singbox等。 68 | 69 | 4. 给 workers绑定 自定义域: 70 | - 在 workers控制台的 `触发器`选项卡,下方点击 `添加自定义域`。 71 | - 填入你已转入 CF 域名解析服务的次级域名,例如:`trojan.google.com`后 点击`添加自定义域`,等待证书生效即可。 72 | 73 |
74 | 75 | ### 🛠 Pages 上传 部署方法 76 | 77 |
78 | 「 Pages 上传文件部署文字教程 」 79 | 80 | 1. 部署 CF Pages: 81 | - 下载 [main.zip](https://github.com/cmliu/epeius/archive/refs/heads/main.zip) 文件,并点上 Star !!! 82 | - 在 CF Pages 控制台中选择 `上传资产`后,为你的项目取名后点击 `创建项目`,然后上传你下载好的 [main.zip](https://github.com/cmliu/epeius/archive/refs/heads/main.zip) 文件后点击 `部署站点`。 83 | - 部署完成后点击 `继续处理站点` 后,选择 `设置` > `环境变量` > **制作**为生产环境定义变量 > `添加变量`。 84 | 变量名称填写**PASSWORD**,值则为你的密码,后点击 `保存`即可。 85 | - 返回 `部署` 选项卡,在右下角点击 `创建新部署` 后,重新上传 [main.zip](https://github.com/cmliu/epeius/archive/refs/heads/main.zip) 文件后点击 `保存并部署` 即可。 86 | 87 | 2. 添加优选线路: 88 | - 添加变量 `ADD` 本地静态的优选线路,若不带端口号 TLS默认端口为443,#号后为备注别名,例如: 89 | ``` 90 | 12315.cf.090227.xyz:443#加入我的频道t.me/CMLiussss解锁更多优选节点 91 | visa.cn#你可以只放域名 如下 92 | www.visa.com.sg 93 | time.is#也可以放域名带端口 如下 94 | www.wto.org:8443 95 | chatgpt.com:2087#节点名放在井号之后即可 96 | icook.hk#若不带端口号默认端口为443 97 | 104.17.152.41#IP也可以 98 | [2606:4700:e7:25:4b9:f8f8:9bfb:774a]#IPv6也OK 99 | ``` 100 | 101 | 3. 访问订阅内容: 102 | - 访问 `https://[YOUR-PAGES-URL]/[PASSWORD]` 即可获取订阅内容。 103 | - 例如 `https://epeius.pages.dev/auto` 就是你的通用自适应订阅地址。 104 | - 例如 `https://epeius.pages.dev/auto?sub` Base64订阅格式,适用PassWall,SSR+等。 105 | - 例如 `https://epeius.pages.dev/auto?clash` Clash订阅格式,适用OpenClash等。 106 | - 例如 `https://epeius.pages.dev/auto?sb` singbox订阅格式,适用singbox等。 107 | - 例如 `https://epeius.pages.dev/auto?surge` surge订阅格式,适用surge 4/5。 108 | 109 | 4. 给 Pages绑定 CNAME自定义域: 110 | - 在 Pages控制台的 `自定义域`选项卡,下方点击 `设置自定义域`。 111 | - 填入你的自定义次级域名,注意不要使用你的根域名,例如: 112 | 您分配到的域名是 `fuck.cloudns.biz`,则添加自定义域填入 `lizi.fuck.cloudns.biz`即可; 113 | - 按照 CF 的要求将返回你的域名DNS服务商,添加 该自定义域 `lizi`的 CNAME记录 `epeius.pages.dev` 后,点击 `激活域`即可。 114 | 115 |
116 | 117 | ### 🛠 Pages GitHub 部署方法 [视频教程](https://www.youtube.com/watch?v=0Cd8uTNJj1Q&t=96s) 118 | 119 |
120 | 「 Pages GitHub 部署文字教程 」 121 | 122 | 1. 部署 CF Pages: 123 | - 在 Github 上先 Fork 本项目,并点上 Star !!! 124 | - 在 CF Pages 控制台中选择 `连接到 Git`后,选中 `epeius`项目后点击 `开始设置`。 125 | - 在 `设置构建和部署`页面下方,选择 `环境变量(高级)`后并 `添加变量`, 126 | 变量名称填写**PASSWORD**,值则为你的密码,后点击 `保存并部署`即可。 127 | 128 | 2. 添加优选线路: 129 | - 添加变量 `ADD` 本地静态的优选线路,若不带端口号 TLS默认端口为443,#号后为备注别名,例如: 130 | ``` 131 | 12315.cf.090227.xyz:443#加入我的频道t.me/CMLiussss解锁更多优选节点 132 | visa.cn#你可以只放域名 如下 133 | www.visa.com.sg 134 | time.is#也可以放域名带端口 如下 135 | www.wto.org:8443 136 | chatgpt.com:2087#节点名放在井号之后即可 137 | icook.hk#若不带端口号默认端口为443 138 | 104.17.152.41#IP也可以 139 | [2606:4700:e7:25:4b9:f8f8:9bfb:774a]#IPv6也OK 140 | ``` 141 | 142 | 3. 访问订阅内容: 143 | - 访问 `https://[YOUR-PAGES-URL]/[PASSWORD]` 即可获取订阅内容。 144 | - 例如 `https://epeius.pages.dev/auto` 就是你的通用自适应订阅地址。 145 | - 例如 `https://epeius.pages.dev/auto?sub` Base64订阅格式,适用PassWall,SSR+等。 146 | - 例如 `https://epeius.pages.dev/auto?clash` Clash订阅格式,适用OpenClash等。 147 | - 例如 `https://epeius.pages.dev/auto?sb` singbox订阅格式,适用singbox等。 148 | - 例如 `https://epeius.pages.dev/auto?surge` surge订阅格式,适用surge 4/5。 149 | 150 | 4. 给 Pages绑定 CNAME自定义域: 151 | - 在 Pages控制台的 `自定义域`选项卡,下方点击 `设置自定义域`。 152 | - 填入你的自定义次级域名,注意不要使用你的根域名,例如: 153 | 您分配到的域名是 `fuck.cloudns.biz`,则添加自定义域填入 `lizi.fuck.cloudns.biz`即可; 154 | - 按照 CF 的要求将返回你的域名DNS服务商,添加 该自定义域 `lizi`的 CNAME记录 `epeius.pages.dev` 后,点击 `激活域`即可。 155 | 156 |
157 | 158 | ## 🔑 变量说明 159 | | 变量名 | 示例 | 备注 | 160 | |--------|---------|-----| 161 | | PASSWORD | `auto` | 可以取任意值 | 162 | | SCV | `false`或`0` | 是否跳过TLS证书验证(默认`true`开启跳过证书验证) | 163 | | PROXYIP | `proxyip.cmliussss.net:443` | 备选作为访问CFCDN站点的代理节点(支持多ProxyIP, ProxyIP之间使用`,`或`换行`作间隔) | 164 | | HTTP | `user:password@127.0.0.1:8080`或`127.0.0.1:8080` | 优先作为访问CFCDN站点的HTTP代理(支持多HTTP代理之间使用`,`或`换行`作间隔) | 165 | | SOCKS5 | `user:password@127.0.0.1:1080`或`127.0.0.1:1080` | 优先作为访问CFCDN站点的SOCKS5代理(支持多socks5, socks5之间使用`,`或`换行`作间隔) | 166 | | GO2SOCKS5 | `blog.cmliussss.com`,`*.ip111.cn`,`*google.com` | 设置`SOCKS5`或`HTTP`变量之后,可设置强制使用socks5访问名单(设置为`*`可作为全局代理) | 167 | | ADD | `www.csgo.com:2087`,`icook.hk` | 本地优选域名/优选IP(支持多元素之间`,`或`换行`作间隔) | 168 | | ADDAPI | [https://raw.github.../addressesapi.txt](https://raw.githubusercontent.com/cmliu/WorkerVless2sub/main/addressesapi.txt) | 不解释, 懂得都懂 | 169 | | ADDCSV | [https://raw.github.../addressescsv.csv](https://raw.githubusercontent.com/cmliu/WorkerVless2sub/main/addressescsv.csv) | 不解释, 懂得都懂 | 170 | | DLS | `8` | `ADDCSV`测速结果满足速度下限 | 171 | | CSVREMARK | `1` | CSV备注所在列偏移量 | 172 | | TGTOKEN | `6894123456:XXXXXXXXXX0qExVsBPUhHDAbXXXXXqWXgBA` | 发送TG通知的机器人token | 173 | | TGID | `6946912345` | 接收TG通知的账户数字ID | 174 | | SUB | `Trojan.cmliussss.net` | 优选订阅生成器域名(使用订阅器将放弃`ADD`内的本地优选订阅内容) | 175 | | SUBAPI | `SUBAPI.cmliussss.net` | clash、singbox等 订阅转换后端 | 176 | | SUBCONFIG | [https://raw.github.../ACL4SSR_Online_Mini.ini](https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online_Mini.ini) | clash、singbox等 订阅转换配置文件 | 177 | | SUBEMOJI | `false` | 订阅转换是否启用Emoji(默认`true`) | 178 | | SUBNAME | `epeius` | 订阅名称 | 179 | | RPROXYIP | `false` | 设为 true 即可强制获取订阅器分配的ProxyIP(需订阅器支持)| 180 | | URL302 | `https://t.me/CMLiussss` | 主页302跳转(支持多url, url之间使用`,`或`换行`作间隔, 小白别用) | 181 | | URL | `https://blog.cmliussss.com` | 主页反代伪装(支持多url, url之间使用`,`或`换行`作间隔, 乱设容易触发反诈) | 182 | | CFPORTS | `2053`,`2096`,`8443` | CF账户标准端口列表 | 183 | 184 | ## ❗ 注意事项 185 | 186 | ### 开启在线编辑优选列表 187 | - 绑定名为`KV`的KV空间,即可在无`SUB`的前提下,在配置页实现在线编辑`ADD`与`ADDAPI`优选列表; 188 | 189 | ### **关于`SOCKS5`与`PROXYIP`:** 190 | - 填入`SOCKS5`后,将停用`PROXYIP`。请确保**二者选其一使用**! 191 | 192 | ### **关于`SUB`与`ADD*`变量:** 193 | - 填入`SUB`后,将停用由`ADD*`类变量生成的订阅内容。请确保**二者选其一使用**! 194 | 195 | ### **当`SUB`和`ADD*`均为空时:** 196 | - 脚本将自动生成基于CF随机IP的线路,每次更新订阅时会生成不同的随机IP,确保您的订阅不会失联! 197 | 198 | ## 🔧 实用技巧 199 | 本项目提供灵活的订阅配置方案,支持通过URL参数快速自定义订阅内容。 200 | - 示例订阅地址: `https://epeius.pages.dev/auto` 201 | 202 | 1. 更换**订阅生成器**的订阅地址 203 | 204 | 快速切换订阅生成器至 `Trojan.cmliussss.net`: 205 | ```url 206 | https://epeius.pages.dev/auto?sub=Trojan.cmliussss.net 207 | ``` 208 | 209 | 2. 更换**PROXYIP**的订阅地址 210 | 211 | 快速更换PROXYIP为 `proxyip.cmliussss.net`: 212 | ```url 213 | https://epeius.pages.dev/auto?proxyip=proxyip.cmliussss.net 214 | ``` 215 | 216 | 3. 更换**SOCKS5**的订阅地址 217 | 218 | 快速设置SOCKS5代理为 `user:password@127.0.0.1:1080`: 219 | ```url 220 | https://epeius.pages.dev/auto?socks5=user:password@127.0.0.1:1080 221 | ``` 222 | 223 | - 通过提交多个参数快速修改的订阅地址 224 | 225 | 例如同时修改**订阅生成器**和**PROXYIP**: 226 | ```url 227 | https://epeius.pages.dev/auto?sub=Trojan.cmliussss.net&proxyip=proxyip.cmliussss.net 228 | ``` 229 | 230 | 4. 该项目部署的节点可通过节点PATH(路径)的方式,使用指定的`PROXYIP`或`SOCKS5`!!!** 231 | 232 | - 指定 `PROXYIP` 案例 233 | ```url 234 | /proxyip=proxyip.cmliussss.net 235 | /?proxyip=proxyip.cmliussss.net 236 | /proxyip.cmliussss.net (仅限于域名开头为'proxyip.'的域名) 237 | ``` 238 | 239 | - 指定 `SOCKS5` 案例 240 | ```url 241 | /socks5=user:password@127.0.0.1:1080 242 | /?socks5=user:password@127.0.0.1:1080 243 | /socks://dXNlcjpwYXNzd29yZA==@127.0.0.1:1080 (默认激活全局SOCKS5) 244 | /socks5://user:password@127.0.0.1:1080 (默认激活全局SOCKS5) 245 | ``` 246 | 247 | - 指定 `HTTP代理` 案例 248 | ```url 249 | /http://user:password@127.0.0.1:8080 (默认激活全局SOCKS5) 250 | ``` 251 | 252 | 5. **当你的`ADDAPI`可作为`PROXYIP`时,可在`ADDAPI`变量末位添加`?proxyip=true`,即可在生成节点时使用优选IP自身作为`PROXYIP`** 253 | - 指定 `ADDAPI` 作为 `PROXYIP` 案例 254 | ```url 255 | https://raw.githubusercontent.com/cmliu/WorkerVless2sub/main/addressesapi.txt?proxyip=true 256 | ``` 257 | 258 | ## ⭐ Star 星星走起 259 | [![Stargazers over time](https://starchart.cc/cmliu/epeius.svg?variant=adaptive)](https://starchart.cc/cmliu/epeius) 260 | 261 | ## 💻 已适配客户端 262 | ### Windows 263 | - [v2rayN](https://github.com/2dust/v2rayN) 264 | - clash.meta([FlClash](https://github.com/chen08209/FlClash),[mihomo-party](https://github.com/mihomo-party-org/mihomo-party),[clash-verge-rev](https://github.com/clash-verge-rev/clash-verge-rev),[Clash Nyanpasu](https://github.com/keiko233/clash-nyanpasu)) 265 | ### IOS 266 | - Surge,小火箭 267 | - sing-box([SFI](https://sing-box.sagernet.org/zh/clients/apple/)) 268 | ### 安卓 269 | - clash.meta([ClashMetaForAndroid](https://github.com/MetaCubeX/ClashMetaForAndroid),[FlClash](https://github.com/chen08209/FlClash)) 270 | - sing-box([SFA](https://github.com/SagerNet/sing-box)) 271 | ### MacOS 272 | - clash.meta([FlClash](https://github.com/chen08209/FlClash),[mihomo-party](https://github.com/mihomo-party-org/mihomo-party)) 273 | 274 | 275 | # 🙏 特别鸣谢 276 | ### 💖 赞助支持 - 提供云服务器维持[订阅转换服务](https://sub.cmliussss.net/) 277 | - [Alice Networks LTD](https://url.cmliussss.com/alice) 278 | - [VTEXS Enterprise Cloud](https://console.vtexs.com/?affid=1532) 279 | ### 🛠 开源代码引用 280 | - [ca110us](https://github.com/ca110us/epeius) 281 | - [xream](https://github.com/xream) 282 | - [zizifn](https://github.com/zizifn/edgetunnel) 283 | - [3Kmfi6HP](https://github.com/6Kmfi6HP/EDtunnel) 284 | - [emn178](https://github.com/emn178/js-sha256) 285 | - [ACL4SSR](https://github.com/ACL4SSR/ACL4SSR/tree/master/Clash/config) 286 | - [SHIJS1999](https://github.com/SHIJS1999/cloudflare-worker-vless-ip) 287 | - [股神](https://t.me/CF_NAT/38889) -------------------------------------------------------------------------------- /明文源码.js: -------------------------------------------------------------------------------- 1 | 2 | import { connect } from "cloudflare:sockets"; 3 | 4 | let password = ''; 5 | let proxyIP = ''; 6 | //let sub = ''; 7 | let subConverter = atob('U1VCQVBJLkNNTGl1c3Nzcy5uZXQ='); 8 | let subConfig = atob('aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0FDTDRTU1IvQUNMNFNTUi9tYXN0ZXIvQ2xhc2gvY29uZmlnL0FDTDRTU1JfT25saW5lX01pbmlfTXVsdGlNb2RlLmluaQ=='); 9 | let subProtocol = 'https'; 10 | let subEmoji = 'true'; 11 | let socks5Address = ''; 12 | let parsedSocks5Address = {}; 13 | let enableSocks = false; 14 | let enableHttp = false; 15 | const expire = 4102329600;//2099-12-31 16 | let proxyIPs; 17 | let socks5s; 18 | let go2Socks5s = [ 19 | '*ttvnw.net', 20 | '*tapecontent.net', 21 | '*cloudatacdn.com', 22 | '*.loadshare.org', 23 | ]; 24 | let addresses = []; 25 | let addressesapi = []; 26 | let addressescsv = []; 27 | let DLS = 8; 28 | let remarkIndex = 1;//CSV备注所在列偏移量 29 | let FileName = 'epeius'; 30 | let BotToken = ''; 31 | let ChatID = ''; 32 | let proxyhosts = []; 33 | let proxyhostsURL = ''; 34 | let RproxyIP = 'false'; 35 | let httpsPorts = ["2053", "2083", "2087", "2096", "8443"]; 36 | let sha224Password; 37 | const regex = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[.*\]):?(\d+)?#?(.*)?$/; 38 | let proxyIPPool = []; 39 | let path = '/?ed=2560'; 40 | let link = []; 41 | let banHosts = [atob('c3BlZWQuY2xvdWRmbGFyZS5jb20=')]; 42 | let SCV = 'true'; 43 | let allowInsecure = '&allowInsecure=1'; 44 | export default { 45 | async fetch(request, env, ctx) { 46 | try { 47 | const UA = request.headers.get('User-Agent') || 'null'; 48 | const userAgent = UA.toLowerCase(); 49 | password = env.PASSWORD || env.pswd || env.UUID || env.uuid || env.TOKEN || password; 50 | if (!password) { 51 | return new Response('请设置你的PASSWORD变量,或尝试重试部署,检查变量是否生效?', { 52 | status: 404, 53 | headers: { 54 | "Content-Type": "text/plain;charset=utf-8", 55 | } 56 | }); 57 | } 58 | sha224Password = env.SHA224 || env.SHA224PASS || sha224(password); 59 | //console.log(sha224Password); 60 | 61 | const currentDate = new Date(); 62 | currentDate.setHours(0, 0, 0, 0); // 设置时间为当天 63 | const timestamp = Math.ceil(currentDate.getTime() / 1000); 64 | const fakeUserIDMD5 = await MD5MD5(`${password}${timestamp}`); 65 | const fakeUserID = [ 66 | fakeUserIDMD5.slice(0, 8), 67 | fakeUserIDMD5.slice(8, 12), 68 | fakeUserIDMD5.slice(12, 16), 69 | fakeUserIDMD5.slice(16, 20), 70 | fakeUserIDMD5.slice(20) 71 | ].join('-'); 72 | 73 | const fakeHostName = `${fakeUserIDMD5.slice(6, 9)}.${fakeUserIDMD5.slice(13, 19)}`; 74 | 75 | proxyIP = env.PROXYIP || env.proxyip || proxyIP; 76 | proxyIPs = await ADD(proxyIP); 77 | proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)]; 78 | 79 | socks5Address = env.HTTP || env.SOCKS5 || socks5Address; 80 | socks5s = await ADD(socks5Address); 81 | socks5Address = socks5s[Math.floor(Math.random() * socks5s.length)]; 82 | enableHttp = env.HTTP ? true : socks5Address.toLowerCase().includes('http://'); 83 | socks5Address = socks5Address.split('//')[1] || socks5Address; 84 | if (env.GO2SOCKS5) go2Socks5s = await ADD(env.GO2SOCKS5); 85 | if (env.CFPORTS) httpsPorts = await ADD(env.CFPORTS); 86 | if (env.BAN) banHosts = await ADD(env.BAN); 87 | if (socks5Address) { 88 | try { 89 | parsedSocks5Address = socks5AddressParser(socks5Address); 90 | RproxyIP = env.RPROXYIP || 'false'; 91 | enableSocks = true; 92 | } catch (err) { 93 | /** @type {Error} */ 94 | let e = err; 95 | console.log(e.toString()); 96 | RproxyIP = env.RPROXYIP || !proxyIP ? 'true' : 'false'; 97 | enableSocks = false; 98 | } 99 | } else { 100 | RproxyIP = env.RPROXYIP || !proxyIP ? 'true' : 'false'; 101 | } 102 | 103 | const upgradeHeader = request.headers.get("Upgrade"); 104 | const url = new URL(request.url); 105 | if (!upgradeHeader || upgradeHeader !== "websocket") { 106 | if (env.ADD) addresses = await ADD(env.ADD); 107 | if (env.ADDAPI) addressesapi = await ADD(env.ADDAPI); 108 | if (env.ADDCSV) addressescsv = await ADD(env.ADDCSV); 109 | DLS = Number(env.DLS) || DLS; 110 | remarkIndex = Number(env.CSVREMARK) || remarkIndex; 111 | BotToken = env.TGTOKEN || BotToken; 112 | ChatID = env.TGID || ChatID; 113 | FileName = env.SUBNAME || FileName; 114 | subEmoji = env.SUBEMOJI || env.EMOJI || subEmoji; 115 | if (subEmoji == '0') subEmoji = 'false'; 116 | if (env.LINK) link = await ADD(env.LINK); 117 | let sub = env.SUB || ''; 118 | subConverter = env.SUBAPI || subConverter; 119 | if (subConverter.includes("http://")) { 120 | subConverter = subConverter.split("//")[1]; 121 | subProtocol = 'http'; 122 | } else { 123 | subConverter = subConverter.split("//")[1] || subConverter; 124 | } 125 | subConfig = env.SUBCONFIG || subConfig; 126 | if (url.searchParams.has('sub') && url.searchParams.get('sub') !== '') sub = url.searchParams.get('sub').toLowerCase(); 127 | 128 | if (url.searchParams.has('proxyip')) { 129 | path = `/proxyip=${url.searchParams.get('proxyip')}`; 130 | RproxyIP = 'false'; 131 | } else if (url.searchParams.has('socks5')) { 132 | path = `/?socks5=${url.searchParams.get('socks5')}`; 133 | RproxyIP = 'false'; 134 | } else if (url.searchParams.has('socks')) { 135 | path = `/?socks5=${url.searchParams.get('socks')}`; 136 | RproxyIP = 'false'; 137 | } 138 | SCV = env.SCV || SCV; 139 | if (!SCV || SCV == '0' || SCV == 'false') allowInsecure = ''; 140 | else SCV = 'true'; 141 | switch (url.pathname) { 142 | case '/': 143 | if (env.URL302) return Response.redirect(env.URL302, 302); 144 | else if (env.URL) return await proxyURL(env.URL, url); 145 | else return new Response(JSON.stringify(request.cf, null, 4), { 146 | status: 200, 147 | headers: { 148 | 'content-type': 'application/json', 149 | }, 150 | }); 151 | case `/${fakeUserID}`: 152 | const fakeConfig = await get特洛伊Config(password, request.headers.get('Host'), sub, 'CF-Workers-SUB', RproxyIP, url, fakeUserID, fakeHostName, env); 153 | return new Response(`${fakeConfig}`, { status: 200 }); 154 | case `/${password}/edit`: 155 | const html = await KV(request, env); 156 | return html; 157 | case `/${password}`: 158 | await sendMessage(`#获取订阅 ${FileName}`, request.headers.get('CF-Connecting-IP'), `UA: ${UA}\n域名: ${url.hostname}\n入口: ${url.pathname + url.search}`); 159 | const 特洛伊Config = await get特洛伊Config(password, request.headers.get('Host'), sub, UA, RproxyIP, url, fakeUserID, fakeHostName, env); 160 | const now = Date.now(); 161 | //const timestamp = Math.floor(now / 1000); 162 | const today = new Date(now); 163 | today.setHours(0, 0, 0, 0); 164 | const UD = Math.floor(((now - today.getTime()) / 86400000) * 24 * 1099511627776 / 2); 165 | let pagesSum = UD; 166 | let workersSum = UD; 167 | let total = 24 * 1099511627776; 168 | 169 | if (userAgent && (userAgent.includes('mozilla') || userAgent.includes('subconverter'))) { 170 | return new Response(特洛伊Config, { 171 | status: 200, 172 | headers: { 173 | "Content-Type": "text/html;charset=utf-8", 174 | "Profile-Update-Interval": "6", 175 | "Subscription-Userinfo": `upload=${pagesSum}; download=${workersSum}; total=${total}; expire=${expire}`, 176 | "Cache-Control": "no-store", 177 | } 178 | }); 179 | } else { 180 | return new Response(特洛伊Config, { 181 | status: 200, 182 | headers: { 183 | "Content-Disposition": `attachment; filename=${FileName}; filename*=utf-8''${encodeURIComponent(FileName)}`, 184 | //"Content-Type": "text/plain;charset=utf-8", 185 | "Profile-Update-Interval": "6", 186 | "Subscription-Userinfo": `upload=${pagesSum}; download=${workersSum}; total=${total}; expire=${expire}`, 187 | } 188 | }); 189 | } 190 | default: 191 | if (env.URL302) return Response.redirect(env.URL302, 302); 192 | else if (env.URL) return await proxyURL(env.URL, url); 193 | else return new Response('不用怀疑!你PASSWORD就是错的!!!', { status: 404 }); 194 | } 195 | } else { 196 | socks5Address = url.searchParams.get('socks5') || socks5Address; 197 | if (new RegExp('/socks5=', 'i').test(url.pathname)) socks5Address = url.pathname.split('5=')[1]; 198 | else if (new RegExp('/socks://', 'i').test(url.pathname) || new RegExp('/socks5://', 'i').test(url.pathname) || new RegExp('/http://', 'i').test(url.pathname)) { 199 | enableHttp = url.pathname.includes('http://'); 200 | socks5Address = url.pathname.split('://')[1].split('#')[0]; 201 | if (socks5Address.includes('@')) { 202 | let userPassword = socks5Address.split('@')[0].replaceAll('%3D', '='); 203 | const base64Regex = /^(?:[A-Z0-9+/]{4})*(?:[A-Z0-9+/]{2}==|[A-Z0-9+/]{3}=)?$/i; 204 | if (base64Regex.test(userPassword) && !userPassword.includes(':')) userPassword = atob(userPassword); 205 | socks5Address = `${userPassword}@${socks5Address.split('@')[1]}`; 206 | } 207 | go2Socks5s = ['all in']; 208 | } 209 | 210 | if (socks5Address) { 211 | try { 212 | parsedSocks5Address = socks5AddressParser(socks5Address); 213 | enableSocks = true; 214 | } catch (err) { 215 | /** @type {Error} */ 216 | let e = err; 217 | console.log(e.toString()); 218 | enableSocks = false; 219 | } 220 | } else { 221 | enableSocks = false; 222 | } 223 | 224 | if (url.searchParams.has('proxyip')) { 225 | proxyIP = url.searchParams.get('proxyip'); 226 | enableSocks = false; 227 | } else if (new RegExp('/proxyip=', 'i').test(url.pathname)) { 228 | proxyIP = url.pathname.toLowerCase().split('/proxyip=')[1]; 229 | enableSocks = false; 230 | } else if (new RegExp('/proxyip.', 'i').test(url.pathname)) { 231 | proxyIP = `proxyip.${url.pathname.toLowerCase().split("/proxyip.")[1]}`; 232 | enableSocks = false; 233 | } else if (new RegExp('/pyip=', 'i').test(url.pathname)) { 234 | proxyIP = url.pathname.toLowerCase().split('/pyip=')[1]; 235 | enableSocks = false; 236 | } 237 | 238 | return await 特洛伊OverWSHandler(request); 239 | } 240 | } catch (err) { 241 | let e = err; 242 | return new Response(e.toString()); 243 | } 244 | } 245 | }; 246 | 247 | async function 特洛伊OverWSHandler(request) { 248 | const webSocketPair = new WebSocketPair(); 249 | const [client, webSocket] = Object.values(webSocketPair); 250 | webSocket.accept(); 251 | let address = ""; 252 | let portWithRandomLog = ""; 253 | const log = (info, event) => { 254 | console.log(`[${address}:${portWithRandomLog}] ${info}`, event || ""); 255 | }; 256 | const earlyDataHeader = request.headers.get("sec-websocket-protocol") || ""; 257 | const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log); 258 | let remoteSocketWapper = { 259 | value: null 260 | }; 261 | let udpStreamWrite = null; 262 | readableWebSocketStream.pipeTo(new WritableStream({ 263 | async write(chunk, controller) { 264 | if (udpStreamWrite) { 265 | return udpStreamWrite(chunk); 266 | } 267 | if (remoteSocketWapper.value) { 268 | const writer = remoteSocketWapper.value.writable.getWriter(); 269 | await writer.write(chunk); 270 | writer.releaseLock(); 271 | return; 272 | } 273 | const { 274 | hasError, 275 | message, 276 | portRemote = 443, 277 | addressRemote = "", 278 | rawClientData, 279 | addressType 280 | } = await parse特洛伊Header(chunk); 281 | address = addressRemote; 282 | portWithRandomLog = `${portRemote}--${Math.random()} tcp`; 283 | if (hasError) { 284 | throw new Error(message); 285 | return; 286 | } 287 | if (!banHosts.includes(addressRemote)) { 288 | log(`处理 TCP 出站连接 ${addressRemote}:${portRemote}`); 289 | handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, log, addressType); 290 | } else { 291 | throw new Error(`黑名单关闭 TCP 出站连接 ${addressRemote}:${portRemote}`); 292 | } 293 | }, 294 | close() { 295 | log(`readableWebSocketStream is closed`); 296 | }, 297 | abort(reason) { 298 | log(`readableWebSocketStream is aborted`, JSON.stringify(reason)); 299 | } 300 | })).catch((err) => { 301 | log("readableWebSocketStream pipeTo error", err); 302 | }); 303 | return new Response(null, { 304 | status: 101, 305 | // @ts-ignore 306 | webSocket: client 307 | }); 308 | } 309 | 310 | async function parse特洛伊Header(buffer) { 311 | if (buffer.byteLength < 56) { 312 | return { 313 | hasError: true, 314 | message: "invalid data" 315 | }; 316 | } 317 | let crLfIndex = 56; 318 | if (new Uint8Array(buffer.slice(56, 57))[0] !== 0x0d || new Uint8Array(buffer.slice(57, 58))[0] !== 0x0a) { 319 | return { 320 | hasError: true, 321 | message: "invalid header format (missing CR LF)" 322 | }; 323 | } 324 | const password = new TextDecoder().decode(buffer.slice(0, crLfIndex)); 325 | if (password !== sha224Password) { 326 | return { 327 | hasError: true, 328 | message: "invalid password" 329 | }; 330 | } 331 | 332 | const socks5DataBuffer = buffer.slice(crLfIndex + 2); 333 | if (socks5DataBuffer.byteLength < 6) { 334 | return { 335 | hasError: true, 336 | message: "invalid SOCKS5 request data" 337 | }; 338 | } 339 | 340 | const view = new DataView(socks5DataBuffer); 341 | const cmd = view.getUint8(0); 342 | if (cmd !== 1) { 343 | return { 344 | hasError: true, 345 | message: "unsupported command, only TCP (CONNECT) is allowed" 346 | }; 347 | } 348 | 349 | const atype = view.getUint8(1); 350 | // 0x01: IPv4 address 351 | // 0x03: Domain name 352 | // 0x04: IPv6 address 353 | let addressLength = 0; 354 | let addressIndex = 2; 355 | let address = ""; 356 | switch (atype) { 357 | case 1: 358 | addressLength = 4; 359 | address = new Uint8Array( 360 | socks5DataBuffer.slice(addressIndex, addressIndex + addressLength) 361 | ).join("."); 362 | break; 363 | case 3: 364 | addressLength = new Uint8Array( 365 | socks5DataBuffer.slice(addressIndex, addressIndex + 1) 366 | )[0]; 367 | addressIndex += 1; 368 | address = new TextDecoder().decode( 369 | socks5DataBuffer.slice(addressIndex, addressIndex + addressLength) 370 | ); 371 | break; 372 | case 4: 373 | addressLength = 16; 374 | const dataView = new DataView(socks5DataBuffer.slice(addressIndex, addressIndex + addressLength)); 375 | const ipv6 = []; 376 | for (let i = 0; i < 8; i++) { 377 | ipv6.push(dataView.getUint16(i * 2).toString(16)); 378 | } 379 | address = ipv6.join(":"); 380 | break; 381 | default: 382 | return { 383 | hasError: true, 384 | message: `invalid addressType is ${atype}` 385 | }; 386 | } 387 | 388 | if (!address) { 389 | return { 390 | hasError: true, 391 | message: `address is empty, addressType is ${atype}` 392 | }; 393 | } 394 | 395 | const portIndex = addressIndex + addressLength; 396 | const portBuffer = socks5DataBuffer.slice(portIndex, portIndex + 2); 397 | const portRemote = new DataView(portBuffer).getUint16(0); 398 | return { 399 | hasError: false, 400 | addressRemote: address, 401 | portRemote, 402 | rawClientData: socks5DataBuffer.slice(portIndex + 4), 403 | addressType: atype 404 | }; 405 | } 406 | 407 | async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, log, addressType) { 408 | async function useSocks5Pattern(address) { 409 | if (go2Socks5s.includes(atob('YWxsIGlu')) || go2Socks5s.includes(atob('Kg=='))) return true; 410 | return go2Socks5s.some(pattern => { 411 | let regexPattern = pattern.replace(/\*/g, '.*'); 412 | let regex = new RegExp(`^${regexPattern}$`, 'i'); 413 | return regex.test(address); 414 | }); 415 | } 416 | async function connectAndWrite(address, port, socks = false, http = false) { 417 | log(`connected to ${address}:${port}`); 418 | //if (/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(address)) address = `${atob('d3d3Lg==')}${address}${atob('LmlwLjA5MDIyNy54eXo=')}`; 419 | // 先确定连接方式,再创建连接 420 | const tcpSocket = socks 421 | ? (http ? await httpConnect(address, port, log) : await socks5Connect(addressType, address, port, log)) 422 | : connect({ hostname: address, port: port }); 423 | remoteSocket.value = tcpSocket; 424 | //log(`connected to ${address}:${port}`); 425 | const writer = tcpSocket.writable.getWriter(); 426 | await writer.write(rawClientData); 427 | writer.releaseLock(); 428 | return tcpSocket; 429 | } 430 | async function nat64() { 431 | if (!useSocks) { 432 | const nat64Proxyip = `[${await resolveToIPv6(addressRemote)}]`; 433 | log(`NAT64 代理连接到 ${nat64Proxyip}:443`); 434 | tcpSocket = await connectAndWrite(nat64Proxyip, '443'); 435 | } 436 | tcpSocket.closed.catch(error => { 437 | console.log('retry tcpSocket closed error', error); 438 | }).finally(() => { 439 | safeCloseWebSocket(webSocket); 440 | }) 441 | remoteSocketToWS(tcpSocket, webSocket, null, log); 442 | } 443 | async function retry() { 444 | if (enableSocks) { 445 | tcpSocket = await connectAndWrite(addressRemote, portRemote, true, enableHttp); 446 | } else { 447 | if (!proxyIP || proxyIP == '') { 448 | proxyIP = atob('UFJPWFlJUC50cDEuMDkwMjI3Lnh5eg=='); 449 | } else if (proxyIP.includes(']:')) { 450 | portRemote = proxyIP.split(']:')[1] || portRemote; 451 | proxyIP = proxyIP.split(']:')[0] + "]" || proxyIP; 452 | } else if (proxyIP.split(':').length === 2) { 453 | portRemote = proxyIP.split(':')[1] || portRemote; 454 | proxyIP = proxyIP.split(':')[0] || proxyIP; 455 | } 456 | if (proxyIP.includes('.tp')) portRemote = proxyIP.split('.tp')[1].split('.')[0] || portRemote; 457 | tcpSocket = await connectAndWrite(proxyIP.toLowerCase() || addressRemote, portRemote); 458 | } 459 | /* 460 | tcpSocket.closed.catch((error) => { 461 | console.log("retry tcpSocket closed error", error); 462 | }).finally(() => { 463 | safeCloseWebSocket(webSocket); 464 | }); 465 | */ 466 | remoteSocketToWS(tcpSocket, webSocket, nat64, log); 467 | } 468 | let useSocks = false; 469 | if (go2Socks5s.length > 0 && enableSocks) useSocks = await useSocks5Pattern(addressRemote); 470 | let tcpSocket = await connectAndWrite(addressRemote, portRemote, useSocks, enableHttp); 471 | remoteSocketToWS(tcpSocket, webSocket, retry, log); 472 | } 473 | 474 | function makeReadableWebSocketStream(webSocketServer, earlyDataHeader, log) { 475 | let readableStreamCancel = false; 476 | const stream = new ReadableStream({ 477 | start(controller) { 478 | webSocketServer.addEventListener("message", (event) => { 479 | if (readableStreamCancel) { 480 | return; 481 | } 482 | const message = event.data; 483 | controller.enqueue(message); 484 | }); 485 | webSocketServer.addEventListener("close", () => { 486 | safeCloseWebSocket(webSocketServer); 487 | if (readableStreamCancel) { 488 | return; 489 | } 490 | controller.close(); 491 | }); 492 | webSocketServer.addEventListener("error", (err) => { 493 | log("webSocketServer error"); 494 | controller.error(err); 495 | }); 496 | const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader); 497 | if (error) { 498 | controller.error(error); 499 | } else if (earlyData) { 500 | controller.enqueue(earlyData); 501 | } 502 | }, 503 | pull(controller) { }, 504 | cancel(reason) { 505 | if (readableStreamCancel) { 506 | return; 507 | } 508 | log(`readableStream was canceled, due to ${reason}`); 509 | readableStreamCancel = true; 510 | safeCloseWebSocket(webSocketServer); 511 | } 512 | }); 513 | return stream; 514 | } 515 | 516 | async function remoteSocketToWS(remoteSocket, webSocket, retry, log) { 517 | let hasIncomingData = false; 518 | await remoteSocket.readable.pipeTo( 519 | new WritableStream({ 520 | start() { }, 521 | /** 522 | * 523 | * @param {Uint8Array} chunk 524 | * @param {*} controller 525 | */ 526 | async write(chunk, controller) { 527 | hasIncomingData = true; 528 | if (webSocket.readyState !== WS_READY_STATE_OPEN) { 529 | controller.error( 530 | "webSocket connection is not open" 531 | ); 532 | } 533 | webSocket.send(chunk); 534 | }, 535 | close() { 536 | log(`remoteSocket.readable is closed, hasIncomingData: ${hasIncomingData}`); 537 | }, 538 | abort(reason) { 539 | console.error("remoteSocket.readable abort", reason); 540 | } 541 | }) 542 | ).catch((error) => { 543 | console.error( 544 | `remoteSocketToWS error:`, 545 | error.stack || error 546 | ); 547 | safeCloseWebSocket(webSocket); 548 | }); 549 | if (hasIncomingData === false && retry) { 550 | log(`retry`); 551 | retry(); 552 | } 553 | } 554 | /* 555 | function isValidSHA224(hash) { 556 | const sha224Regex = /^[0-9a-f]{56}$/i; 557 | return sha224Regex.test(hash); 558 | } 559 | */ 560 | function base64ToArrayBuffer(base64Str) { 561 | if (!base64Str) { 562 | return { earlyData: undefined, error: null }; 563 | } 564 | try { 565 | base64Str = base64Str.replace(/-/g, "+").replace(/_/g, "/"); 566 | const decode = atob(base64Str); 567 | const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0)); 568 | return { earlyData: arryBuffer.buffer, error: null }; 569 | } catch (error) { 570 | return { earlyData: undefined, error }; 571 | } 572 | } 573 | 574 | let WS_READY_STATE_OPEN = 1; 575 | let WS_READY_STATE_CLOSING = 2; 576 | 577 | function safeCloseWebSocket(socket) { 578 | try { 579 | if (socket.readyState === WS_READY_STATE_OPEN || socket.readyState === WS_READY_STATE_CLOSING) { 580 | socket.close(); 581 | } 582 | } catch (error) { 583 | console.error("safeCloseWebSocket error", error); 584 | } 585 | } 586 | 587 | /* 588 | export { 589 | worker_default as 590 | default 591 | }; 592 | //# sourceMappingURL=worker.js.map 593 | */ 594 | 595 | function revertFakeInfo(content, userID, hostName, fakeUserID, fakeHostName, isBase64) { 596 | if (isBase64) content = atob(content);//Base64解码 597 | content = content.replace(new RegExp(fakeUserID, 'g'), userID).replace(new RegExp(fakeHostName, 'g'), hostName); 598 | //console.log(content); 599 | if (isBase64) content = btoa(content);//Base64编码 600 | 601 | return content; 602 | } 603 | 604 | async function MD5MD5(text) { 605 | const encoder = new TextEncoder(); 606 | 607 | const firstPass = await crypto.subtle.digest('MD5', encoder.encode(text)); 608 | const firstPassArray = Array.from(new Uint8Array(firstPass)); 609 | const firstHex = firstPassArray.map(b => b.toString(16).padStart(2, '0')).join(''); 610 | 611 | const secondPass = await crypto.subtle.digest('MD5', encoder.encode(firstHex.slice(7, 27))); 612 | const secondPassArray = Array.from(new Uint8Array(secondPass)); 613 | const secondHex = secondPassArray.map(b => b.toString(16).padStart(2, '0')).join(''); 614 | 615 | return secondHex.toLowerCase(); 616 | } 617 | 618 | async function ADD(内容) { 619 | // 将制表符、双引号、单引号和换行符都替换为逗号 620 | // 然后将连续的多个逗号替换为单个逗号 621 | var 替换后的内容 = 内容.replace(/[ |"'\r\n]+/g, ',').replace(/,+/g, ','); 622 | 623 | // 删除开头和结尾的逗号(如果有的话) 624 | if (替换后的内容.charAt(0) == ',') 替换后的内容 = 替换后的内容.slice(1); 625 | if (替换后的内容.charAt(替换后的内容.length - 1) == ',') 替换后的内容 = 替换后的内容.slice(0, 替换后的内容.length - 1); 626 | 627 | // 使用逗号分割字符串,得到地址数组 628 | const 地址数组 = 替换后的内容.split(','); 629 | 630 | return 地址数组; 631 | } 632 | 633 | async function proxyURL(proxyURL, url) { 634 | const URLs = await ADD(proxyURL); 635 | const fullURL = URLs[Math.floor(Math.random() * URLs.length)]; 636 | // 解析目标 URL 637 | let parsedURL = new URL(fullURL); 638 | console.log(parsedURL); 639 | // 提取并可能修改 URL 组件 640 | let URLProtocol = parsedURL.protocol.slice(0, -1) || 'https'; 641 | let URLHostname = parsedURL.hostname; 642 | let URLPathname = parsedURL.pathname; 643 | let URLSearch = parsedURL.search; 644 | // 处理 pathname 645 | if (URLPathname.charAt(URLPathname.length - 1) == '/') { 646 | URLPathname = URLPathname.slice(0, -1); 647 | } 648 | URLPathname += url.pathname; 649 | // 构建新的 URL 650 | let newURL = `${URLProtocol}://${URLHostname}${URLPathname}${URLSearch}`; 651 | // 反向代理请求 652 | let response = await fetch(newURL); 653 | // 创建新的响应 654 | let newResponse = new Response(response.body, { 655 | status: response.status, 656 | statusText: response.statusText, 657 | headers: response.headers 658 | }); 659 | // 添加自定义头部,包含 URL 信息 660 | //newResponse.headers.set('X-Proxied-By', 'Cloudflare Worker'); 661 | //newResponse.headers.set('X-Original-URL', fullURL); 662 | newResponse.headers.set('X-New-URL', newURL); 663 | return newResponse; 664 | } 665 | 666 | function 配置信息(密码, 域名地址) { 667 | const 啥啥啥_写的这是啥啊 = 'dHJvamFu'; 668 | const 协议类型 = atob(啥啥啥_写的这是啥啊); 669 | 670 | const 别名 = FileName; 671 | let 地址 = 域名地址; 672 | let 端口 = 443; 673 | 674 | const 传输层协议 = 'ws'; 675 | const 伪装域名 = 域名地址; 676 | const 路径 = path; 677 | 678 | let 传输层安全 = ['tls', true]; 679 | const SNI = 域名地址; 680 | const 指纹 = 'randomized'; 681 | 682 | const v2ray = `${协议类型}://${encodeURIComponent(密码)}@${地址}:${端口}?security=${传输层安全[0]}&sni=${SNI}&alpn=h3&fp=${指纹}&type=${传输层协议}&host=${伪装域名}&path=${encodeURIComponent(路径) + allowInsecure}&fragment=1,40-60,30-50,tlshello#${encodeURIComponent(别名)}` 683 | const clash = `- {name: ${别名}, server: ${地址}, port: ${端口}, udp: false, client-fingerprint: ${指纹}, type: ${协议类型}, password: ${密码}, sni: ${SNI}, alpn: [h3], skip-cert-verify: ${SCV}, network: ${传输层协议}, ws-opts: {path: "${路径}", headers: {Host: ${伪装域名}}}}`; 684 | 685 | return [v2ray, clash]; 686 | } 687 | 688 | let subParams = ['sub', 'base64', 'b64', 'clash', 'singbox', 'sb', 'surge']; 689 | const cmad = decodeURIComponent(atob(`dGVsZWdyYW0lMjAlRTQlQkElQTQlRTYlQjUlODElRTclQkUlQTQlMjAlRTYlOEElODAlRTYlOUMlQUYlRTUlQTQlQTclRTQlQkQlQUMlN0UlRTUlOUMlQTglRTclQkElQkYlRTUlOEYlOTElRTclODklOEMhJTNDYnIlM0UKJTNDYSUyMGhyZWYlM0QlMjdodHRwcyUzQSUyRiUyRnQubWUlMkZDTUxpdXNzc3MlMjclM0VodHRwcyUzQSUyRiUyRnQubWUlMkZDTUxpdXNzc3MlM0MlMkZhJTNFJTNDYnIlM0UKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tJTNDYnIlM0UKZ2l0aHViJTIwJUU5JUExJUI5JUU3JTlCJUFFJUU1JTlDJUIwJUU1JTlEJTgwJTIwU3RhciFTdGFyIVN0YXIhISElM0NiciUzRQolM0NhJTIwaHJlZiUzRCUyN2h0dHBzJTNBJTJGJTJGZ2l0aHViLmNvbSUyRmNtbGl1JTJGZXBlaXVzJTI3JTNFaHR0cHMlM0ElMkYlMkZnaXRodWIuY29tJTJGY21saXUlMkZlcGVpdXMlM0MlMkZhJTNFJTNDYnIlM0UKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tJTNDYnIlM0UKJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIzJTIz`)); 690 | async function get特洛伊Config(password, hostName, sub, UA, RproxyIP, _url, fakeUserID, fakeHostName, env) { 691 | if (sub) { 692 | const match = sub.match(/^(?:https?:\/\/)?([^\/]+)/); 693 | if (match) { 694 | sub = match[1]; 695 | } 696 | const subs = await ADD(sub); 697 | if (subs.length > 1) sub = subs[0]; 698 | } else { 699 | if (env.KV) { 700 | await 迁移地址列表(env); 701 | const 优选地址列表 = await env.KV.get('ADD.txt'); 702 | if (优选地址列表) { 703 | const 优选地址数组 = await ADD(优选地址列表); 704 | const 分类地址 = { 705 | 接口地址: new Set(), 706 | 链接地址: new Set(), 707 | 优选地址: new Set() 708 | }; 709 | 710 | for (const 元素 of 优选地址数组) { 711 | if (元素.startsWith('https://')) { 712 | 分类地址.接口地址.add(元素); 713 | } else if (元素.includes('://')) { 714 | 分类地址.链接地址.add(元素); 715 | } else { 716 | 分类地址.优选地址.add(元素); 717 | } 718 | } 719 | 720 | addressesapi = [...分类地址.接口地址]; 721 | link = [...分类地址.链接地址]; 722 | addresses = [...分类地址.优选地址]; 723 | } 724 | } 725 | 726 | if ((addresses.length + addressesapi.length + addressescsv.length) == 0) { 727 | // 定义 Cloudflare IP 范围的 CIDR 列表 728 | let cfips = [ 729 | '103.21.244.0/24', 730 | '104.16.0.0/13', 731 | '104.24.0.0/14', 732 | '172.64.0.0/14', 733 | '104.16.0.0/14', 734 | '104.24.0.0/15', 735 | '141.101.64.0/19', 736 | '172.64.0.0/14', 737 | '188.114.96.0/21', 738 | '190.93.240.0/21', 739 | '162.159.152.0/23', 740 | '104.16.0.0/13', 741 | '104.24.0.0/14', 742 | '172.64.0.0/14', 743 | '104.16.0.0/14', 744 | '104.24.0.0/15', 745 | '141.101.64.0/19', 746 | '172.64.0.0/14', 747 | '188.114.96.0/21', 748 | '190.93.240.0/21', 749 | ]; 750 | 751 | // 生成符合给定 CIDR 范围的随机 IP 地址 752 | function generateRandomIPFromCIDR(cidr) { 753 | const [base, mask] = cidr.split('/'); 754 | const baseIP = base.split('.').map(Number); 755 | const subnetMask = 32 - parseInt(mask, 10); 756 | const maxHosts = Math.pow(2, subnetMask) - 1; 757 | const randomHost = Math.floor(Math.random() * maxHosts); 758 | 759 | const randomIP = baseIP.map((octet, index) => { 760 | if (index < 2) return octet; 761 | if (index === 2) return (octet & (255 << (subnetMask - 8))) + ((randomHost >> 8) & 255); 762 | return (octet & (255 << subnetMask)) + (randomHost & 255); 763 | }); 764 | 765 | return randomIP.join('.'); 766 | } 767 | addresses = addresses.concat('127.0.0.1:1234#CFnat'); 768 | let counter = 1; 769 | const randomPorts = httpsPorts.concat('443'); 770 | addresses = addresses.concat( 771 | cfips.map(cidr => generateRandomIPFromCIDR(cidr) + ':' + randomPorts[Math.floor(Math.random() * randomPorts.length)] + '#CF随机节点' + String(counter++).padStart(2, '0')) 772 | ); 773 | } 774 | } 775 | 776 | const userAgent = UA.toLowerCase(); 777 | const Config = 配置信息(password, hostName); 778 | const v2ray = Config[0]; 779 | const clash = Config[1]; 780 | let proxyhost = ""; 781 | if (hostName.includes(".workers.dev")) { 782 | if (proxyhostsURL && (!proxyhosts || proxyhosts.length == 0)) { 783 | try { 784 | const response = await fetch(proxyhostsURL); 785 | 786 | if (!response.ok) { 787 | console.error('获取地址时出错:', response.status, response.statusText); 788 | return; // 如果有错误,直接返回 789 | } 790 | 791 | const text = await response.text(); 792 | const lines = text.split('\n'); 793 | // 过滤掉空行或只包含空白字符的行 794 | const nonEmptyLines = lines.filter(line => line.trim() !== ''); 795 | 796 | proxyhosts = proxyhosts.concat(nonEmptyLines); 797 | } catch (error) { 798 | //console.error('获取地址时出错:', error); 799 | } 800 | } 801 | if (proxyhosts.length != 0) proxyhost = proxyhosts[Math.floor(Math.random() * proxyhosts.length)] + "/"; 802 | } 803 | 804 | if (userAgent.includes('mozilla') && !subParams.some(_searchParams => _url.searchParams.has(_searchParams))) { 805 | let surge = `Surge订阅地址:
https://${proxyhost}${hostName}/${password}?surge
`; 806 | if (hostName.includes(".workers.dev")) surge = "Surge订阅必须绑定自定义域"; 807 | const newSocks5s = socks5s.map(socks5Address => { 808 | if (socks5Address.includes('@')) return socks5Address.split('@')[1]; 809 | else if (socks5Address.includes('//')) return socks5Address.split('//')[1]; 810 | else return socks5Address; 811 | }); 812 | 813 | let socks5List = ''; 814 | if (go2Socks5s.length > 0 && enableSocks) { 815 | socks5List = `${(enableHttp ? "HTTP" : "Socks5") + decodeURIComponent('%EF%BC%88%E7%99%BD%E5%90%8D%E5%8D%95%EF%BC%89%3A%20')}`; 816 | if (go2Socks5s.includes(atob('YWxsIGlu')) || go2Socks5s.includes(atob('Kg=='))) socks5List += `${decodeURIComponent('%E6%89%80%E6%9C%89%E6%B5%81%E9%87%8F')}
`; 817 | else socks5List += `
  ${go2Socks5s.join('
  ')}
`; 818 | } 819 | 820 | let 订阅器 = ''; 821 | if (sub) { 822 | if (enableSocks) 订阅器 += `CFCDN(访问方式): ${enableHttp ? "HTTP" : "Socks5"}
  ${newSocks5s.join('
  ')}
${socks5List}`; 823 | else if (proxyIP && proxyIP != '') 订阅器 += `CFCDN(访问方式): ProxyIP
  ${proxyIPs.join('
  ')}
`; 824 | else if (RproxyIP == 'true') 订阅器 += `CFCDN(访问方式): 自动获取ProxyIP
`; 825 | else 订阅器 += `CFCDN(访问方式): 无法访问, 需要您设置 proxyIP/PROXYIP !!!
` 826 | 订阅器 += `
SUB(优选订阅生成器): ${sub}`; 827 | } else { 828 | if (enableSocks) 订阅器 += `CFCDN(访问方式): ${enableHttp ? "HTTP" : "Socks5"}
  ${newSocks5s.join('
  ')}
${socks5List}`; 829 | else if (proxyIP && proxyIP != '') 订阅器 += `CFCDN(访问方式): ProxyIP
  ${proxyIPs.join('
  ')}
`; 830 | else 订阅器 += `CFCDN(访问方式): 无法访问, 需要您设置 proxyIP/PROXYIP !!!
`; 831 | let 判断是否绑定KV空间 = ''; 832 | if (env.KV) 判断是否绑定KV空间 = ` 编辑优选列表`; 833 | 订阅器 += `
您的订阅内容由 内置 addresses/ADD* 参数变量提供${判断是否绑定KV空间}
`; 834 | if (addresses.length > 0) 订阅器 += `ADD(TLS优选域名&IP):
  ${addresses.join('
  ')}
`; 835 | if (addressesapi.length > 0) 订阅器 += `ADDAPI(TLS优选域名&IP 的 API):
  ${addressesapi.join('
  ')}
`; 836 | if (addressescsv.length > 0) 订阅器 += `ADDCSV(IPTest测速csv文件 限速 ${DLS} ):
  ${addressescsv.join('
  ')}
`; 837 | } 838 | 839 | const 节点配置页 = ` 840 | ################################################################
841 | Subscribe / sub 订阅地址, 点击链接自动 复制订阅链接生成订阅二维码
842 | ---------------------------------------------------------------
843 | 自适应订阅地址:
844 | https://${proxyhost}${hostName}/${password}
845 |
846 | Base64订阅地址:
847 | https://${proxyhost}${hostName}/${password}?b64
848 |
849 | clash订阅地址:
850 | https://${proxyhost}${hostName}/${password}?clash
851 |
852 | singbox订阅地址:
853 | https://${proxyhost}${hostName}/${password}?sb
854 |
855 | loon订阅地址:
856 | https://${proxyhost}${hostName}/${password}?loon
857 |
858 | ${surge} 859 | 实用订阅技巧∨
860 | 877 | 878 | 910 | ---------------------------------------------------------------
911 | ################################################################
912 | ${FileName} 配置信息
913 | ---------------------------------------------------------------
914 | HOST: ${hostName}
915 | PASSWORD: ${password}
916 | SHA224: ${sha224Password}
917 | FAKEPASS: ${fakeUserID}
918 | UA: ${UA}
919 | SCV(跳过TLS证书验证): ${SCV}
920 |
921 | ${订阅器}
922 | SUBAPI(订阅转换后端): ${subProtocol}://${subConverter}
923 | SUBCONFIG(订阅转换配置文件): ${subConfig}
924 | ---------------------------------------------------------------
925 | ################################################################
926 | v2ray
927 | ---------------------------------------------------------------
928 | ${v2ray}
929 |
930 | ---------------------------------------------------------------
931 | ################################################################
932 | clash-meta
933 | ---------------------------------------------------------------
934 | ${clash}
935 | ---------------------------------------------------------------
936 | ################################################################
937 | ${cmad} 938 | `; 939 | return `
${节点配置页}
`; 940 | } else { 941 | if (typeof fetch != 'function') { 942 | return 'Error: fetch is not available in this environment.'; 943 | } 944 | // 如果是使用默认域名,则改成一个workers的域名,订阅器会加上代理 945 | if (hostName.includes(".workers.dev")) { 946 | fakeHostName = `${fakeHostName}.workers.dev`; 947 | } else { 948 | fakeHostName = `${fakeHostName}.xyz` 949 | } 950 | 951 | let url = `https://${sub}/sub?host=${fakeHostName}&pw=${fakeUserID}&password=${fakeUserID + atob('JmVwZWl1cz1jbWxpdSZwcm94eWlwPQ==') + RproxyIP}&path=${encodeURIComponent(path)}`; 952 | let isBase64 = true; 953 | let newAddressesapi = []; 954 | let newAddressescsv = []; 955 | 956 | if (!sub || sub == "") { 957 | if (hostName.includes('workers.dev')) { 958 | if (proxyhostsURL && (!proxyhosts || proxyhosts.length == 0)) { 959 | try { 960 | const response = await fetch(proxyhostsURL); 961 | 962 | if (!response.ok) { 963 | console.error('获取地址时出错:', response.status, response.statusText); 964 | return; // 如果有错误,直接返回 965 | } 966 | 967 | const text = await response.text(); 968 | const lines = text.split('\n'); 969 | // 过滤掉空行或只包含空白字符的行 970 | const nonEmptyLines = lines.filter(line => line.trim() !== ''); 971 | 972 | proxyhosts = proxyhosts.concat(nonEmptyLines); 973 | } catch (error) { 974 | console.error('获取地址时出错:', error); 975 | } 976 | } 977 | // 使用Set对象去重 978 | proxyhosts = [...new Set(proxyhosts)]; 979 | } 980 | 981 | newAddressesapi = await getAddressesapi(addressesapi); 982 | newAddressescsv = await getAddressescsv('TRUE'); 983 | url = `https://${hostName}/${fakeUserID + _url.search}`; 984 | } 985 | 986 | if (!userAgent.includes(('CF-Workers-SUB').toLowerCase()) && !_url.searchParams.has('b64') && !_url.searchParams.has('base64')) { 987 | if ((userAgent.includes('clash') && !userAgent.includes('nekobox')) || (_url.searchParams.has('clash'))) { 988 | url = `${subProtocol}://${subConverter}/sub?target=clash&url=${encodeURIComponent(url)}&insert=false&config=${encodeURIComponent(subConfig)}&emoji=${subEmoji}&list=false&tfo=false&scv=${SCV}&fdn=false&sort=false&new_name=true`; 989 | isBase64 = false; 990 | } else if (userAgent.includes('sing-box') || userAgent.includes('singbox') || _url.searchParams.has('singbox') || _url.searchParams.has('sb')) { 991 | url = `${subProtocol}://${subConverter}/sub?target=singbox&url=${encodeURIComponent(url)}&insert=false&config=${encodeURIComponent(subConfig)}&emoji=${subEmoji}&list=false&tfo=false&scv=${SCV}&fdn=false&sort=false&new_name=true`; 992 | isBase64 = false; 993 | } else if (userAgent.includes('surge') || _url.searchParams.has('surge')) { 994 | url = `${subProtocol}://${subConverter}/sub?target=surge&ver=4&url=${encodeURIComponent(url)}&insert=false&config=${encodeURIComponent(subConfig)}&emoji=${subEmoji}&list=false&xudp=false&udp=false&tfo=false&expand=true&scv=${SCV}&fdn=false`; 995 | isBase64 = false; 996 | } else if (userAgent.includes('loon') || _url.searchParams.has('loon')) { 997 | url = `${subProtocol}://${subConverter}/sub?target=loon&url=${encodeURIComponent(url)}&insert=false&config=${encodeURIComponent(subConfig)}&emoji=${subEmoji}&list=false&tfo=false&scv=${SCV}&fdn=false&sort=false&new_name=true`; 998 | isBase64 = false; 999 | } 1000 | } 1001 | 1002 | try { 1003 | let content; 1004 | if ((!sub || sub == "") && isBase64 == true) { 1005 | content = await subAddresses(fakeHostName, fakeUserID, userAgent, newAddressesapi, newAddressescsv); 1006 | } else { 1007 | const response = await fetch(url, { 1008 | headers: { 1009 | 'User-Agent': atob('Q0YtV29ya2Vycy1lcGVpdXMvY21saXU='), 1010 | } 1011 | }); 1012 | content = await response.text(); 1013 | } 1014 | 1015 | if (_url.pathname == `/${fakeUserID}`) return content; 1016 | 1017 | content = revertFakeInfo(content, password, hostName, fakeUserID, fakeHostName, isBase64); 1018 | if (userAgent.includes('surge') || _url.searchParams.has('surge')) content = surge(content, `https://${hostName}/${password}?surge`); 1019 | return content; 1020 | } catch (error) { 1021 | console.error('Error fetching content:', error); 1022 | return `Error fetching content: ${error.message}`; 1023 | } 1024 | } 1025 | } 1026 | 1027 | async function sendMessage(type, ip, add_data = "") { 1028 | if (BotToken !== '' && ChatID !== '') { 1029 | let msg = ""; 1030 | const response = await fetch(`http://ip-api.com/json/${ip}?lang=zh-CN`); 1031 | if (response.status == 200) { 1032 | const ipInfo = await response.json(); 1033 | msg = `${type}\nIP: ${ip}\n国家: ${ipInfo.country}\n城市: ${ipInfo.city}\n组织: ${ipInfo.org}\nASN: ${ipInfo.as}\n${add_data}`; 1034 | } else { 1035 | msg = `${type}\nIP: ${ip}\n${add_data}`; 1036 | } 1037 | 1038 | let url = "https://api.telegram.org/bot" + BotToken + "/sendMessage?chat_id=" + ChatID + "&parse_mode=HTML&text=" + encodeURIComponent(msg); 1039 | return fetch(url, { 1040 | method: 'get', 1041 | headers: { 1042 | 'Accept': 'text/html,application/xhtml+xml,application/xml;', 1043 | 'Accept-Encoding': 'gzip, deflate, br', 1044 | 'User-Agent': 'Mozilla/5.0 Chrome/90.0.4430.72' 1045 | } 1046 | }); 1047 | } 1048 | } 1049 | 1050 | /** 1051 | * 1052 | * @param {number} addressType 1053 | * @param {string} addressRemote 1054 | * @param {number} portRemote 1055 | * @param {function} log The logging function. 1056 | */ 1057 | async function socks5Connect(addressType, addressRemote, portRemote, log) { 1058 | const { username, password, hostname, port } = parsedSocks5Address; 1059 | // Connect to the SOCKS server 1060 | const socket = connect({ 1061 | hostname, 1062 | port, 1063 | }); 1064 | 1065 | // Request head format (Worker -> Socks Server): 1066 | // +----+----------+----------+ 1067 | // |VER | NMETHODS | METHODS | 1068 | // +----+----------+----------+ 1069 | // | 1 | 1 | 1 to 255 | 1070 | // +----+----------+----------+ 1071 | 1072 | // https://en.wikipedia.org/wiki/SOCKS#SOCKS5 1073 | // For METHODS: 1074 | // 0x00 NO AUTHENTICATION REQUIRED 1075 | // 0x02 USERNAME/PASSWORD https://datatracker.ietf.org/doc/html/rfc1929 1076 | const socksGreeting = new Uint8Array([5, 2, 0, 2]); 1077 | 1078 | const writer = socket.writable.getWriter(); 1079 | 1080 | await writer.write(socksGreeting); 1081 | log('sent socks greeting'); 1082 | 1083 | const reader = socket.readable.getReader(); 1084 | const encoder = new TextEncoder(); 1085 | let res = (await reader.read()).value; 1086 | // Response format (Socks Server -> Worker): 1087 | // +----+--------+ 1088 | // |VER | METHOD | 1089 | // +----+--------+ 1090 | // | 1 | 1 | 1091 | // +----+--------+ 1092 | if (res[0] !== 0x05) { 1093 | log(`socks server version error: ${res[0]} expected: 5`); 1094 | return; 1095 | } 1096 | if (res[1] === 0xff) { 1097 | log("no acceptable methods"); 1098 | return; 1099 | } 1100 | 1101 | // if return 0x0502 1102 | if (res[1] === 0x02) { 1103 | log("socks server needs auth"); 1104 | if (!username || !password) { 1105 | log("please provide username/password"); 1106 | return; 1107 | } 1108 | // +----+------+----------+------+----------+ 1109 | // |VER | ULEN | UNAME | PLEN | PASSWD | 1110 | // +----+------+----------+------+----------+ 1111 | // | 1 | 1 | 1 to 255 | 1 | 1 to 255 | 1112 | // +----+------+----------+------+----------+ 1113 | const authRequest = new Uint8Array([ 1114 | 1, 1115 | username.length, 1116 | ...encoder.encode(username), 1117 | password.length, 1118 | ...encoder.encode(password) 1119 | ]); 1120 | await writer.write(authRequest); 1121 | res = (await reader.read()).value; 1122 | // expected 0x0100 1123 | if (res[0] !== 0x01 || res[1] !== 0x00) { 1124 | log("fail to auth socks server"); 1125 | return; 1126 | } 1127 | } 1128 | 1129 | // Request data format (Worker -> Socks Server): 1130 | // +----+-----+-------+------+----------+----------+ 1131 | // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 1132 | // +----+-----+-------+------+----------+----------+ 1133 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 1134 | // +----+-----+-------+------+----------+----------+ 1135 | // ATYP: address type of following address 1136 | // 0x01: IPv4 address 1137 | // 0x03: Domain name 1138 | // 0x04: IPv6 address 1139 | // DST.ADDR: desired destination address 1140 | // DST.PORT: desired destination port in network octet order 1141 | 1142 | // addressType 1143 | // 0x01: IPv4 address 1144 | // 0x03: Domain name 1145 | // 0x04: IPv6 address 1146 | // 1--> ipv4 addressLength =4 1147 | // 2--> domain name 1148 | // 3--> ipv6 addressLength =16 1149 | let DSTADDR; // DSTADDR = ATYP + DST.ADDR 1150 | switch (addressType) { 1151 | case 1: 1152 | DSTADDR = new Uint8Array( 1153 | [1, ...addressRemote.split('.').map(Number)] 1154 | ); 1155 | break; 1156 | case 3: 1157 | DSTADDR = new Uint8Array( 1158 | [3, addressRemote.length, ...encoder.encode(addressRemote)] 1159 | ); 1160 | break; 1161 | case 4: 1162 | DSTADDR = new Uint8Array( 1163 | [4, ...addressRemote.split(':').flatMap(x => [parseInt(x.slice(0, 2), 16), parseInt(x.slice(2), 16)])] 1164 | ); 1165 | break; 1166 | default: 1167 | log(`invild addressType is ${addressType}`); 1168 | return; 1169 | } 1170 | const socksRequest = new Uint8Array([5, 1, 0, ...DSTADDR, portRemote >> 8, portRemote & 0xff]); 1171 | await writer.write(socksRequest); 1172 | log('sent socks request'); 1173 | 1174 | res = (await reader.read()).value; 1175 | // Response format (Socks Server -> Worker): 1176 | // +----+-----+-------+------+----------+----------+ 1177 | // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 1178 | // +----+-----+-------+------+----------+----------+ 1179 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 1180 | // +----+-----+-------+------+----------+----------+ 1181 | if (res[1] === 0x00) { 1182 | log("socks connection opened"); 1183 | } else { 1184 | log("fail to open socks connection"); 1185 | return; 1186 | } 1187 | writer.releaseLock(); 1188 | reader.releaseLock(); 1189 | return socket; 1190 | } 1191 | 1192 | /** 1193 | * 建立 HTTP 代理连接 1194 | * @param {string} addressRemote 目标地址(可以是 IP 或域名) 1195 | * @param {number} portRemote 目标端口 1196 | * @param {function} log 日志记录函数 1197 | */ 1198 | async function httpConnect(addressRemote, portRemote, log) { 1199 | const { username, password, hostname, port } = parsedSocks5Address; 1200 | const sock = await connect({ 1201 | hostname: hostname, 1202 | port: port 1203 | }); 1204 | 1205 | // 构建HTTP CONNECT请求 1206 | let connectRequest = `CONNECT ${addressRemote}:${portRemote} HTTP/1.1\r\n`; 1207 | connectRequest += `Host: ${addressRemote}:${portRemote}\r\n`; 1208 | 1209 | // 添加代理认证(如果需要) 1210 | if (username && password) { 1211 | const authString = `${username}:${password}`; 1212 | const base64Auth = btoa(authString); 1213 | connectRequest += `Proxy-Authorization: Basic ${base64Auth}\r\n`; 1214 | } 1215 | 1216 | connectRequest += `User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\r\n`; 1217 | connectRequest += `Proxy-Connection: Keep-Alive\r\n`; 1218 | connectRequest += `Connection: Keep-Alive\r\n`; // 添加标准 Connection 头 1219 | connectRequest += `\r\n`; 1220 | 1221 | log(`正在连接到 ${addressRemote}:${portRemote} 通过代理 ${hostname}:${port}`); 1222 | 1223 | try { 1224 | // 发送连接请求 1225 | const writer = sock.writable.getWriter(); 1226 | await writer.write(new TextEncoder().encode(connectRequest)); 1227 | writer.releaseLock(); 1228 | } catch (err) { 1229 | console.error('发送HTTP CONNECT请求失败:', err); 1230 | throw new Error(`发送HTTP CONNECT请求失败: ${err.message}`); 1231 | } 1232 | 1233 | // 读取HTTP响应 1234 | const reader = sock.readable.getReader(); 1235 | let respText = ''; 1236 | let connected = false; 1237 | let responseBuffer = new Uint8Array(0); 1238 | 1239 | try { 1240 | while (true) { 1241 | const { value, done } = await reader.read(); 1242 | if (done) { 1243 | console.error('HTTP代理连接中断'); 1244 | throw new Error('HTTP代理连接中断'); 1245 | } 1246 | 1247 | // 合并接收到的数据 1248 | const newBuffer = new Uint8Array(responseBuffer.length + value.length); 1249 | newBuffer.set(responseBuffer); 1250 | newBuffer.set(value, responseBuffer.length); 1251 | responseBuffer = newBuffer; 1252 | 1253 | // 将收到的数据转换为文本 1254 | respText = new TextDecoder().decode(responseBuffer); 1255 | 1256 | // 检查是否收到完整的HTTP响应头 1257 | if (respText.includes('\r\n\r\n')) { 1258 | // 分离HTTP头和可能的数据部分 1259 | const headersEndPos = respText.indexOf('\r\n\r\n') + 4; 1260 | const headers = respText.substring(0, headersEndPos); 1261 | 1262 | log(`收到HTTP代理响应: ${headers.split('\r\n')[0]}`); 1263 | 1264 | // 检查响应状态 1265 | if (headers.startsWith('HTTP/1.1 200') || headers.startsWith('HTTP/1.0 200')) { 1266 | connected = true; 1267 | 1268 | // 如果响应头之后还有数据,我们需要保存这些数据以便后续处理 1269 | if (headersEndPos < responseBuffer.length) { 1270 | const remainingData = responseBuffer.slice(headersEndPos); 1271 | // 创建一个缓冲区来存储这些数据,以便稍后使用 1272 | const dataStream = new ReadableStream({ 1273 | start(controller) { 1274 | controller.enqueue(remainingData); 1275 | } 1276 | }); 1277 | 1278 | // 创建一个新的TransformStream来处理额外数据 1279 | const { readable, writable } = new TransformStream(); 1280 | dataStream.pipeTo(writable).catch(err => console.error('处理剩余数据错误:', err)); 1281 | 1282 | // 替换原始readable流 1283 | // @ts-ignore 1284 | sock.readable = readable; 1285 | } 1286 | } else { 1287 | const errorMsg = `HTTP代理连接失败: ${headers.split('\r\n')[0]}`; 1288 | console.error(errorMsg); 1289 | throw new Error(errorMsg); 1290 | } 1291 | break; 1292 | } 1293 | } 1294 | } catch (err) { 1295 | reader.releaseLock(); 1296 | throw new Error(`处理HTTP代理响应失败: ${err.message}`); 1297 | } 1298 | 1299 | reader.releaseLock(); 1300 | 1301 | if (!connected) { 1302 | throw new Error('HTTP代理连接失败: 未收到成功响应'); 1303 | } 1304 | 1305 | log(`HTTP代理连接成功: ${addressRemote}:${portRemote}`); 1306 | return sock; 1307 | } 1308 | 1309 | /** 1310 | * 1311 | * @param {string} address 1312 | */ 1313 | function socks5AddressParser(address) { 1314 | let [latter, former] = address.split("@").reverse(); 1315 | let username, password, hostname, port; 1316 | if (former) { 1317 | const formers = former.split(":"); 1318 | if (formers.length !== 2) { 1319 | throw new Error('Invalid SOCKS address format'); 1320 | } 1321 | [username, password] = formers; 1322 | } 1323 | const latters = latter.split(":"); 1324 | port = Number(latters.pop()); 1325 | if (isNaN(port)) { 1326 | throw new Error('Invalid SOCKS address format'); 1327 | } 1328 | hostname = latters.join(":"); 1329 | const regex = /^\[.*\]$/; 1330 | if (hostname.includes(":") && !regex.test(hostname)) { 1331 | throw new Error('Invalid SOCKS address format'); 1332 | } 1333 | //if (/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(hostname)) hostname = `${atob('d3d3Lg==')}${hostname}${atob('LmlwLjA5MDIyNy54eXo=')}`; 1334 | return { 1335 | username, 1336 | password, 1337 | hostname, 1338 | port, 1339 | } 1340 | } 1341 | 1342 | function isValidIPv4(address) { 1343 | const ipv4Regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; 1344 | return ipv4Regex.test(address); 1345 | } 1346 | 1347 | function subAddresses(host, pw, userAgent, newAddressesapi, newAddressescsv) { 1348 | addresses = addresses.concat(newAddressesapi); 1349 | addresses = addresses.concat(newAddressescsv); 1350 | // 使用Set对象去重 1351 | const uniqueAddresses = [...new Set(addresses)]; 1352 | 1353 | const responseBody = uniqueAddresses.map(address => { 1354 | let port = "-1"; 1355 | let addressid = address; 1356 | 1357 | const match = addressid.match(regex); 1358 | if (!match) { 1359 | if (address.includes(':') && address.includes('#')) { 1360 | const parts = address.split(':'); 1361 | address = parts[0]; 1362 | const subParts = parts[1].split('#'); 1363 | port = subParts[0]; 1364 | addressid = subParts[1]; 1365 | } else if (address.includes(':')) { 1366 | const parts = address.split(':'); 1367 | address = parts[0]; 1368 | port = parts[1]; 1369 | } else if (address.includes('#')) { 1370 | const parts = address.split('#'); 1371 | address = parts[0]; 1372 | addressid = parts[1]; 1373 | } 1374 | 1375 | if (addressid.includes(':')) { 1376 | addressid = addressid.split(':')[0]; 1377 | } 1378 | } else { 1379 | address = match[1]; 1380 | port = match[2] || port; 1381 | addressid = match[3] || address; 1382 | } 1383 | 1384 | const httpsPorts = ["2053", "2083", "2087", "2096", "8443"]; 1385 | if (!isValidIPv4(address) && port == "-1") { 1386 | for (let httpsPort of httpsPorts) { 1387 | if (address.includes(httpsPort)) { 1388 | port = httpsPort; 1389 | break; 1390 | } 1391 | } 1392 | } 1393 | if (port == "-1") port = "443"; 1394 | 1395 | let 伪装域名 = host; 1396 | let 最终路径 = path; 1397 | let 节点备注 = ''; 1398 | const matchingProxyIP = proxyIPPool.find(proxyIP => proxyIP.includes(address)); 1399 | if (matchingProxyIP) 最终路径 = `/proxyip=${matchingProxyIP}`; 1400 | 1401 | if (proxyhosts.length > 0 && (伪装域名.includes('.workers.dev'))) { 1402 | 最终路径 = `/${伪装域名}${最终路径}`; 1403 | 伪装域名 = proxyhosts[Math.floor(Math.random() * proxyhosts.length)]; 1404 | 节点备注 = ` 已启用临时域名中转服务,请尽快绑定自定义域!`; 1405 | } 1406 | 1407 | let 密码 = pw; 1408 | if (!userAgent.includes('subconverter')) 密码 = encodeURIComponent(pw); 1409 | 1410 | const 啥啥啥_写的这是啥啊 = 'dHJvamFu'; 1411 | const 协议类型 = atob(啥啥啥_写的这是啥啊); 1412 | const 特洛伊Link = `${协议类型}://${密码}@${address}:${port}?security=tls&sni=${伪装域名}&fp=randomized&type=ws&host=${伪装域名}&path=${encodeURIComponent(最终路径) + allowInsecure}&fragment=1,40-60,30-50,tlshello#${encodeURIComponent(addressid + 节点备注)}`; 1413 | 1414 | return 特洛伊Link; 1415 | }).join('\n'); 1416 | 1417 | let base64Response = responseBody; // 重新进行 Base64 编码 1418 | if (link.length > 0) base64Response += '\n' + link.join('\n'); 1419 | return btoa(base64Response); 1420 | } 1421 | 1422 | async function getAddressesapi(api) { 1423 | if (!api || api.length === 0) return []; 1424 | 1425 | let newapi = ""; 1426 | 1427 | // 创建一个AbortController对象,用于控制fetch请求的取消 1428 | const controller = new AbortController(); 1429 | 1430 | const timeout = setTimeout(() => { 1431 | controller.abort(); // 取消所有请求 1432 | }, 2000); // 2秒后触发 1433 | 1434 | try { 1435 | // 使用Promise.allSettled等待所有API请求完成,无论成功或失败 1436 | // 对api数组进行遍历,对每个API地址发起fetch请求 1437 | const responses = await Promise.allSettled(api.map(apiUrl => fetch(apiUrl, { 1438 | method: 'get', 1439 | headers: { 1440 | 'Accept': 'text/html,application/xhtml+xml,application/xml;', 1441 | 'User-Agent': atob('Q0YtV29ya2Vycy1lcGVpdXMvY21saXU=') 1442 | }, 1443 | signal: controller.signal // 将AbortController的信号量添加到fetch请求中,以便于需要时可以取消请求 1444 | }).then(response => response.ok ? response.text() : Promise.reject()))); 1445 | 1446 | // 遍历所有响应 1447 | for (const [index, response] of responses.entries()) { 1448 | // 检查响应状态是否为'fulfilled',即请求成功完成 1449 | if (response.status === 'fulfilled') { 1450 | // 获取响应的内容 1451 | const content = await response.value; 1452 | 1453 | const lines = content.split(/\r?\n/); 1454 | let 节点备注 = ''; 1455 | let 测速端口 = '443'; 1456 | if (lines[0].split(',').length > 3) { 1457 | const idMatch = api[index].match(/id=([^&]*)/); 1458 | if (idMatch) 节点备注 = idMatch[1]; 1459 | const portMatch = api[index].match(/port=([^&]*)/); 1460 | if (portMatch) 测速端口 = portMatch[1]; 1461 | 1462 | for (let i = 1; i < lines.length; i++) { 1463 | const columns = lines[i].split(',')[0]; 1464 | if (columns) { 1465 | newapi += `${columns}:${测速端口}${节点备注 ? `#${节点备注}` : ''}\n`; 1466 | if (api[index].includes('proxyip=true')) proxyIPPool.push(`${columns}:${测速端口}`); 1467 | } 1468 | } 1469 | } else { 1470 | // 验证当前apiUrl是否带有'proxyip=true' 1471 | if (api[index].includes('proxyip=true')) { 1472 | // 如果URL带有'proxyip=true',则将内容添加到proxyIPPool 1473 | proxyIPPool = proxyIPPool.concat((await ADD(content)).map(item => { 1474 | const baseItem = item.split('#')[0] || item; 1475 | if (baseItem.includes(':')) { 1476 | const port = baseItem.split(':')[1]; 1477 | if (!httpsPorts.includes(port)) { 1478 | return baseItem; 1479 | } 1480 | } else { 1481 | return `${baseItem}:443`; 1482 | } 1483 | return null; // 不符合条件时返回 null 1484 | }).filter(Boolean)); // 过滤掉 null 值 1485 | } 1486 | // 将内容添加到newapi中 1487 | newapi += content + '\n'; 1488 | } 1489 | } 1490 | } 1491 | } catch (error) { 1492 | console.error(error); 1493 | } finally { 1494 | // 无论成功或失败,最后都清除设置的超时定时器 1495 | clearTimeout(timeout); 1496 | } 1497 | 1498 | const newAddressesapi = await ADD(newapi); 1499 | 1500 | // 返回处理后的结果 1501 | return newAddressesapi; 1502 | } 1503 | 1504 | async function getAddressescsv(tls) { 1505 | if (!addressescsv || addressescsv.length === 0) { 1506 | return []; 1507 | } 1508 | 1509 | let newAddressescsv = []; 1510 | 1511 | for (const csvUrl of addressescsv) { 1512 | try { 1513 | const response = await fetch(csvUrl); 1514 | 1515 | if (!response.ok) { 1516 | console.error('获取CSV地址时出错:', response.status, response.statusText); 1517 | continue; 1518 | } 1519 | 1520 | const text = await response.text();// 使用正确的字符编码解析文本内容 1521 | let lines; 1522 | if (text.includes('\r\n')) { 1523 | lines = text.split('\r\n'); 1524 | } else { 1525 | lines = text.split('\n'); 1526 | } 1527 | 1528 | // 检查CSV头部是否包含必需字段 1529 | const header = lines[0].split(','); 1530 | const tlsIndex = header.indexOf('TLS'); 1531 | 1532 | const ipAddressIndex = 0;// IP地址在 CSV 头部的位置 1533 | const portIndex = 1;// 端口在 CSV 头部的位置 1534 | const dataCenterIndex = tlsIndex + remarkIndex; // 数据中心是 TLS 的后一个字段 1535 | 1536 | if (tlsIndex === -1) { 1537 | console.error('CSV文件缺少必需的字段'); 1538 | continue; 1539 | } 1540 | 1541 | // 从第二行开始遍历CSV行 1542 | for (let i = 1; i < lines.length; i++) { 1543 | const columns = lines[i].split(','); 1544 | const speedIndex = columns.length - 1; // 最后一个字段 1545 | // 检查TLS是否为"TRUE"且速度大于DLS 1546 | if (columns[tlsIndex].toUpperCase() === tls && parseFloat(columns[speedIndex]) > DLS) { 1547 | const ipAddress = columns[ipAddressIndex]; 1548 | const port = columns[portIndex]; 1549 | const dataCenter = columns[dataCenterIndex]; 1550 | 1551 | const formattedAddress = `${ipAddress}:${port}#${dataCenter}`; 1552 | newAddressescsv.push(formattedAddress); 1553 | if (csvUrl.includes('proxyip=true') && columns[tlsIndex].toUpperCase() == 'true' && !httpsPorts.includes(port)) { 1554 | // 如果URL带有'proxyip=true',则将内容添加到proxyIPPool 1555 | proxyIPPool.push(`${ipAddress}:${port}`); 1556 | } 1557 | } 1558 | } 1559 | } catch (error) { 1560 | console.error('获取CSV地址时出错:', error); 1561 | continue; 1562 | } 1563 | } 1564 | 1565 | return newAddressescsv; 1566 | } 1567 | 1568 | function surge(content, url) { 1569 | let 每行内容; 1570 | if (content.includes('\r\n')) { 1571 | 每行内容 = content.split('\r\n'); 1572 | } else { 1573 | 每行内容 = content.split('\n'); 1574 | } 1575 | 1576 | let 输出内容 = ""; 1577 | for (let x of 每行内容) { 1578 | if (x.includes(atob('PSB0cm9qYW4s'))) { 1579 | const host = x.split("sni=")[1].split(",")[0]; 1580 | const 备改内容 = `skip-cert-verify=true, tfo=false, udp-relay=false`; 1581 | const 正确内容 = `skip-cert-verify=true, ws=true, ws-path=${path}, ws-headers=Host:"${host}", tfo=false, udp-relay=false`; 1582 | 输出内容 += x.replace(new RegExp(备改内容, 'g'), 正确内容).replace("[", "").replace("]", "") + '\n'; 1583 | } else { 1584 | 输出内容 += x + '\n'; 1585 | } 1586 | } 1587 | 1588 | 输出内容 = `#!MANAGED-CONFIG ${url} interval=86400 strict=false` + 输出内容.substring(输出内容.indexOf('\n')); 1589 | return 输出内容; 1590 | } 1591 | 1592 | /** 1593 | * [js-sha256]{@link https://github.com/emn178/js-sha256} 1594 | * 1595 | * @version 0.11.0 (modified by cmliu) 1596 | * @description 本代码基于 js-sha256 项目改编,添加了 SHA-224 哈希算法的实现。 1597 | * @author Chen, Yi-Cyuan [emn178@gmail.com], modified by cmliu 1598 | * @copyright Chen, Yi-Cyuan 2014-2024 1599 | * @license MIT 1600 | * 1601 | * @modifications 重写并实现了 sha224 函数,引用请注明出处。修改日期:2024-12-04,Github:cmliu 1602 | */ 1603 | function sha224(输入字符串) { 1604 | // 内部常量和函数 1605 | const 常量K = [ 1606 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 1607 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 1608 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 1609 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 1610 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 1611 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 1612 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 1613 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 1614 | ]; 1615 | 1616 | function utf8编码(字符串) { 1617 | return unescape(encodeURIComponent(字符串)); 1618 | } 1619 | 1620 | function 字节转十六进制(字节数组) { 1621 | let 十六进制 = ''; 1622 | for (let i = 0; i < 字节数组.length; i++) { 1623 | 十六进制 += ((字节数组[i] >>> 4) & 0x0F).toString(16); 1624 | 十六进制 += (字节数组[i] & 0x0F).toString(16); 1625 | } 1626 | return 十六进制; 1627 | } 1628 | 1629 | function sha224核心(输入字符串) { 1630 | // SHA-224的初始哈希值 1631 | let 哈希值 = [ 1632 | 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 1633 | 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 1634 | ]; 1635 | 1636 | // 预处理 1637 | const 消息长度 = 输入字符串.length * 8; 1638 | 输入字符串 += String.fromCharCode(0x80); 1639 | while ((输入字符串.length * 8) % 512 !== 448) { 1640 | 输入字符串 += String.fromCharCode(0); 1641 | } 1642 | 1643 | // 64位消息长度 1644 | const 消息长度高位 = Math.floor(消息长度 / 0x100000000); 1645 | const 消息长度低位 = 消息长度 & 0xFFFFFFFF; 1646 | 输入字符串 += String.fromCharCode( 1647 | (消息长度高位 >>> 24) & 0xFF, (消息长度高位 >>> 16) & 0xFF, 1648 | (消息长度高位 >>> 8) & 0xFF, 消息长度高位 & 0xFF, 1649 | (消息长度低位 >>> 24) & 0xFF, (消息长度低位 >>> 16) & 0xFF, 1650 | (消息长度低位 >>> 8) & 0xFF, 消息长度低位 & 0xFF 1651 | ); 1652 | 1653 | const 字数组 = []; 1654 | for (let i = 0; i < 输入字符串.length; i += 4) { 1655 | 字数组.push( 1656 | (输入字符串.charCodeAt(i) << 24) | 1657 | (输入字符串.charCodeAt(i + 1) << 16) | 1658 | (输入字符串.charCodeAt(i + 2) << 8) | 1659 | 输入字符串.charCodeAt(i + 3) 1660 | ); 1661 | } 1662 | 1663 | // 主要压缩循环 1664 | for (let i = 0; i < 字数组.length; i += 16) { 1665 | const w = new Array(64).fill(0); 1666 | for (let j = 0; j < 16; j++) { 1667 | w[j] = 字数组[i + j]; 1668 | } 1669 | 1670 | for (let j = 16; j < 64; j++) { 1671 | const s0 = 右旋转(w[j - 15], 7) ^ 右旋转(w[j - 15], 18) ^ (w[j - 15] >>> 3); 1672 | const s1 = 右旋转(w[j - 2], 17) ^ 右旋转(w[j - 2], 19) ^ (w[j - 2] >>> 10); 1673 | w[j] = (w[j - 16] + s0 + w[j - 7] + s1) >>> 0; 1674 | } 1675 | 1676 | let [a, b, c, d, e, f, g, h0] = 哈希值; 1677 | 1678 | for (let j = 0; j < 64; j++) { 1679 | const S1 = 右旋转(e, 6) ^ 右旋转(e, 11) ^ 右旋转(e, 25); 1680 | const ch = (e & f) ^ (~e & g); 1681 | const temp1 = (h0 + S1 + ch + 常量K[j] + w[j]) >>> 0; 1682 | const S0 = 右旋转(a, 2) ^ 右旋转(a, 13) ^ 右旋转(a, 22); 1683 | const maj = (a & b) ^ (a & c) ^ (b & c); 1684 | const temp2 = (S0 + maj) >>> 0; 1685 | 1686 | h0 = g; 1687 | g = f; 1688 | f = e; 1689 | e = (d + temp1) >>> 0; 1690 | d = c; 1691 | c = b; 1692 | b = a; 1693 | a = (temp1 + temp2) >>> 0; 1694 | } 1695 | 1696 | 哈希值[0] = (哈希值[0] + a) >>> 0; 1697 | 哈希值[1] = (哈希值[1] + b) >>> 0; 1698 | 哈希值[2] = (哈希值[2] + c) >>> 0; 1699 | 哈希值[3] = (哈希值[3] + d) >>> 0; 1700 | 哈希值[4] = (哈希值[4] + e) >>> 0; 1701 | 哈希值[5] = (哈希值[5] + f) >>> 0; 1702 | 哈希值[6] = (哈希值[6] + g) >>> 0; 1703 | 哈希值[7] = (哈希值[7] + h0) >>> 0; 1704 | } 1705 | 1706 | // 截断到224位 1707 | return 哈希值.slice(0, 7); 1708 | } 1709 | 1710 | function 右旋转(数值, 位数) { 1711 | return ((数值 >>> 位数) | (数值 << (32 - 位数))) >>> 0; 1712 | } 1713 | 1714 | // 主函数逻辑 1715 | const 编码输入 = utf8编码(输入字符串); 1716 | const 哈希结果 = sha224核心(编码输入); 1717 | 1718 | // 转换为十六进制字符串 1719 | return 字节转十六进制( 1720 | 哈希结果.flatMap(h => [ 1721 | (h >>> 24) & 0xFF, 1722 | (h >>> 16) & 0xFF, 1723 | (h >>> 8) & 0xFF, 1724 | h & 0xFF 1725 | ]) 1726 | ); 1727 | } 1728 | 1729 | async function 迁移地址列表(env, txt = 'ADD.txt') { 1730 | const 旧数据 = await env.KV.get(`/${txt}`); 1731 | const 新数据 = await env.KV.get(txt); 1732 | 1733 | if (旧数据 && !新数据) { 1734 | // 写入新位置 1735 | await env.KV.put(txt, 旧数据); 1736 | // 删除旧数据 1737 | await env.KV.delete(`/${txt}`); 1738 | return true; 1739 | } 1740 | return false; 1741 | } 1742 | 1743 | async function KV(request, env, txt = 'ADD.txt') { 1744 | try { 1745 | // POST请求处理 1746 | if (request.method === "POST") { 1747 | if (!env.KV) return new Response("未绑定KV空间", { status: 400 }); 1748 | try { 1749 | const content = await request.text(); 1750 | await env.KV.put(txt, content); 1751 | return new Response("保存成功"); 1752 | } catch (error) { 1753 | console.error('保存KV时发生错误:', error); 1754 | return new Response("保存失败: " + error.message, { status: 500 }); 1755 | } 1756 | } 1757 | 1758 | // GET请求部分 1759 | let content = ''; 1760 | let hasKV = !!env.KV; 1761 | 1762 | if (hasKV) { 1763 | try { 1764 | content = await env.KV.get(txt) || ''; 1765 | } catch (error) { 1766 | console.error('读取KV时发生错误:', error); 1767 | content = '读取数据时发生错误: ' + error.message; 1768 | } 1769 | } 1770 | 1771 | const html = ` 1772 | 1773 | 1774 | 1775 | 优选订阅列表 1776 | 1777 | 1778 | 1838 | 1839 | 1840 | ################################################################
1841 | ${FileName} 优选订阅列表:
1842 | ---------------------------------------------------------------
1843 |   注意事项∨
1844 |
1845 | ${decodeURIComponent(atob('JTA5JTA5JTA5JTA5JTA5JTNDc3Ryb25nJTNFMS4lM0MlMkZzdHJvbmclM0UlMjBBRERBUEklMjAlRTUlQTYlODIlRTYlOUUlOUMlRTYlOTglQUYlRTUlOEYlOEQlRTQlQkIlQTNJUCVFRiVCQyU4QyVFNSU4RiVBRiVFNCVCRCU5QyVFNCVCOCVCQVBST1hZSVAlRTclOUElODQlRTglQUYlOUQlRUYlQkMlOEMlRTUlOEYlQUYlRTUlQjAlODYlMjIlM0Zwcm94eWlwJTNEdHJ1ZSUyMiVFNSU4RiU4MiVFNiU5NSVCMCVFNiVCNyVCQiVFNSU4QSVBMCVFNSU4OCVCMCVFOSU5MyVCRSVFNiU4RSVBNSVFNiU5QyVBQiVFNSVCMCVCRSVFRiVCQyU4QyVFNCVCRSU4QiVFNSVBNiU4MiVFRiVCQyU5QSUzQ2JyJTNFCiUwOSUwOSUwOSUwOSUwOSUyNm5ic3AlM0IlMjZuYnNwJTNCaHR0cHMlM0ElMkYlMkZyYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tJTJGY21saXUlMkZXb3JrZXJWbGVzczJzdWIlMkZtYWluJTJGYWRkcmVzc2VzYXBpLnR4dCUzQ3N0cm9uZyUzRSUzRnByb3h5aXAlM0R0cnVlJTNDJTJGc3Ryb25nJTNFJTNDYnIlM0UlM0NiciUzRQolMDklMDklMDklMDklMDklM0NzdHJvbmclM0UyLiUzQyUyRnN0cm9uZyUzRSUyMEFEREFQSSUyMCVFNSVBNiU4MiVFNiU5RSU5QyVFNiU5OCVBRiUyMCUzQ2ElMjBocmVmJTNEJTI3aHR0cHMlM0ElMkYlMkZnaXRodWIuY29tJTJGWElVMiUyRkNsb3VkZmxhcmVTcGVlZFRlc3QlMjclM0VDbG91ZGZsYXJlU3BlZWRUZXN0JTNDJTJGYSUzRSUyMCVFNyU5QSU4NCUyMGNzdiUyMCVFNyVCQiU5MyVFNiU5RSU5QyVFNiU5NiU4NyVFNCVCQiVCNiVFRiVCQyU4QyVFNCVCRSU4QiVFNSVBNiU4MiVFRiVCQyU5QSUzQ2JyJTNFCiUwOSUwOSUwOSUwOSUwOSUyNm5ic3AlM0IlMjZuYnNwJTNCaHR0cHMlM0ElMkYlMkZyYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tJTJGY21saXUlMkZXb3JrZXJWbGVzczJzdWIlMkZtYWluJTJGQ2xvdWRmbGFyZVNwZWVkVGVzdC5jc3YlM0NiciUzRSUzQ2JyJTNFCiUwOSUwOSUwOSUwOSUwOSUyNm5ic3AlM0IlMjZuYnNwJTNCLSUyMCVFNSVBNiU4MiVFOSU5QyU4MCVFNiU4QyU4NyVFNSVBRSU5QTIwNTMlRTclQUIlQUYlRTUlOEYlQTMlRTUlOEYlQUYlRTUlQjAlODYlMjIlM0Zwb3J0JTNEMjA1MyUyMiVFNSU4RiU4MiVFNiU5NSVCMCVFNiVCNyVCQiVFNSU4QSVBMCVFNSU4OCVCMCVFOSU5MyVCRSVFNiU4RSVBNSVFNiU5QyVBQiVFNSVCMCVCRSVFRiVCQyU4QyVFNCVCRSU4QiVFNSVBNiU4MiVFRiVCQyU5QSUzQ2JyJTNFCiUwOSUwOSUwOSUwOSUwOSUyNm5ic3AlM0IlMjZuYnNwJTNCaHR0cHMlM0ElMkYlMkZyYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tJTJGY21saXUlMkZXb3JrZXJWbGVzczJzdWIlMkZtYWluJTJGQ2xvdWRmbGFyZVNwZWVkVGVzdC5jc3YlM0NzdHJvbmclM0UlM0Zwb3J0JTNEMjA1MyUzQyUyRnN0cm9uZyUzRSUzQ2JyJTNFJTNDYnIlM0UKJTA5JTA5JTA5JTA5JTA5JTI2bmJzcCUzQiUyNm5ic3AlM0ItJTIwJUU1JUE2JTgyJUU5JTlDJTgwJUU2JThDJTg3JUU1JUFFJTlBJUU4JThBJTgyJUU3JTgyJUI5JUU1JUE0JTg3JUU2JUIzJUE4JUU1JThGJUFGJUU1JUIwJTg2JTIyJTNGaWQlM0RDRiVFNCVCQyU5OCVFOSU4MCU4OSUyMiVFNSU4RiU4MiVFNiU5NSVCMCVFNiVCNyVCQiVFNSU4QSVBMCVFNSU4OCVCMCVFOSU5MyVCRSVFNiU4RSVBNSVFNiU5QyVBQiVFNSVCMCVCRSVFRiVCQyU4QyVFNCVCRSU4QiVFNSVBNiU4MiVFRiVCQyU5QSUzQ2JyJTNFCiUwOSUwOSUwOSUwOSUwOSUyNm5ic3AlM0IlMjZuYnNwJTNCaHR0cHMlM0ElMkYlMkZyYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tJTJGY21saXUlMkZXb3JrZXJWbGVzczJzdWIlMkZtYWluJTJGQ2xvdWRmbGFyZVNwZWVkVGVzdC5jc3YlM0NzdHJvbmclM0UlM0ZpZCUzRENGJUU0JUJDJTk4JUU5JTgwJTg5JTNDJTJGc3Ryb25nJTNFJTNDYnIlM0UlM0NiciUzRQolMDklMDklMDklMDklMDklMjZuYnNwJTNCJTI2bmJzcCUzQi0lMjAlRTUlQTYlODIlRTklOUMlODAlRTYlOEMlODclRTUlQUUlOUElRTUlQTQlOUElRTQlQjglQUElRTUlOEYlODIlRTYlOTUlQjAlRTUlODglOTklRTklOUMlODAlRTglQTYlODElRTQlQkQlQkYlRTclOTQlQTglMjclMjYlMjclRTUlODElOUElRTklOTclQjQlRTklOUElOTQlRUYlQkMlOEMlRTQlQkUlOEIlRTUlQTYlODIlRUYlQkMlOUElM0NiciUzRQolMDklMDklMDklMDklMDklMjZuYnNwJTNCJTI2bmJzcCUzQmh0dHBzJTNBJTJGJTJGcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSUyRmNtbGl1JTJGV29ya2VyVmxlc3Myc3ViJTJGbWFpbiUyRkNsb3VkZmxhcmVTcGVlZFRlc3QuY3N2JTNGaWQlM0RDRiVFNCVCQyU5OCVFOSU4MCU4OSUzQ3N0cm9uZyUzRSUyNiUzQyUyRnN0cm9uZyUzRXBvcnQlM0QyMDUzJTNDYnIlM0U='))} 1846 |
1847 |
1848 | ${hasKV ? ` 1849 | 1852 |
1853 | 1854 | 1855 | 1856 |
1857 |
1858 | ################################################################
1859 | ${cmad} 1860 | ` : '

未绑定KV空间

'} 1861 |
1862 | 1863 | 1991 | 1992 | 1993 | `; 1994 | 1995 | return new Response(html, { 1996 | headers: { "Content-Type": "text/html;charset=utf-8" } 1997 | }); 1998 | } catch (error) { 1999 | console.error('处理请求时发生错误:', error); 2000 | return new Response("服务器错误: " + error.message, { 2001 | status: 500, 2002 | headers: { "Content-Type": "text/plain;charset=utf-8" } 2003 | }); 2004 | } 2005 | } 2006 | 2007 | async function resolveToIPv6(target) { 2008 | // 检查是否为IPv4 2009 | function isIPv4(str) { 2010 | const parts = str.split('.'); 2011 | return parts.length === 4 && parts.every(part => { 2012 | const num = parseInt(part, 10); 2013 | return num >= 0 && num <= 255 && part === num.toString(); 2014 | }); 2015 | } 2016 | 2017 | // 检查是否为IPv6 2018 | function isIPv6(str) { 2019 | return str.includes(':') && /^[0-9a-fA-F:]+$/.test(str); 2020 | } 2021 | 2022 | // 获取域名的IPv4地址 2023 | async function fetchIPv4(domain) { 2024 | const url = `https://cloudflare-dns.com/dns-query?name=${domain}&type=A`; 2025 | const response = await fetch(url, { 2026 | headers: { 'Accept': 'application/dns-json' } 2027 | }); 2028 | 2029 | if (!response.ok) throw new Error('DNS查询失败'); 2030 | 2031 | const data = await response.json(); 2032 | const ipv4s = (data.Answer || []) 2033 | .filter(record => record.type === 1) 2034 | .map(record => record.data); 2035 | 2036 | if (ipv4s.length === 0) throw new Error('未找到IPv4地址'); 2037 | return ipv4s[Math.floor(Math.random() * ipv4s.length)]; 2038 | } 2039 | 2040 | // 查询NAT64 IPv6地址 2041 | async function queryNAT64(domain) { 2042 | const socket = connect(atob('ZG90Lm5hdDY0LmRrOjg1Mw=='), { 2043 | secureTransport: 'on', 2044 | allowHalfOpen: false 2045 | }); 2046 | 2047 | const writer = socket.writable.getWriter(); 2048 | const reader = socket.readable.getReader(); 2049 | 2050 | try { 2051 | // 发送DNS查询 2052 | const query = buildDNSQuery(domain); 2053 | const queryWithLength = new Uint8Array(query.length + 2); 2054 | queryWithLength[0] = query.length >> 8; 2055 | queryWithLength[1] = query.length & 0xFF; 2056 | queryWithLength.set(query, 2); 2057 | await writer.write(queryWithLength); 2058 | 2059 | // 读取响应 2060 | const response = await readDNSResponse(reader); 2061 | const ipv6s = parseIPv6(response); 2062 | 2063 | return ipv6s.length > 0 ? ipv6s[0] : '未找到IPv6地址'; 2064 | } finally { 2065 | await writer.close(); 2066 | await reader.cancel(); 2067 | } 2068 | } 2069 | 2070 | // 构建DNS查询包 2071 | function buildDNSQuery(domain) { 2072 | const buffer = new ArrayBuffer(512); 2073 | const view = new DataView(buffer); 2074 | let offset = 0; 2075 | 2076 | // DNS头部 2077 | view.setUint16(offset, Math.floor(Math.random() * 65536)); offset += 2; // ID 2078 | view.setUint16(offset, 0x0100); offset += 2; // 标志 2079 | view.setUint16(offset, 1); offset += 2; // 问题数 2080 | view.setUint16(offset, 0); offset += 6; // 答案数/权威数/附加数 2081 | 2082 | // 域名编码 2083 | for (const label of domain.split('.')) { 2084 | view.setUint8(offset++, label.length); 2085 | for (let i = 0; i < label.length; i++) { 2086 | view.setUint8(offset++, label.charCodeAt(i)); 2087 | } 2088 | } 2089 | view.setUint8(offset++, 0); // 结束标记 2090 | 2091 | // 查询类型和类 2092 | view.setUint16(offset, 28); offset += 2; // AAAA记录 2093 | view.setUint16(offset, 1); offset += 2; // IN类 2094 | 2095 | return new Uint8Array(buffer, 0, offset); 2096 | } 2097 | 2098 | // 读取DNS响应 2099 | async function readDNSResponse(reader) { 2100 | const chunks = []; 2101 | let totalLength = 0; 2102 | let expectedLength = null; 2103 | 2104 | while (true) { 2105 | const { value, done } = await reader.read(); 2106 | if (done) break; 2107 | 2108 | chunks.push(value); 2109 | totalLength += value.length; 2110 | 2111 | if (expectedLength === null && totalLength >= 2) { 2112 | expectedLength = (chunks[0][0] << 8) | chunks[0][1]; 2113 | } 2114 | 2115 | if (expectedLength !== null && totalLength >= expectedLength + 2) { 2116 | break; 2117 | } 2118 | } 2119 | 2120 | // 合并数据并跳过长度前缀 2121 | const fullResponse = new Uint8Array(totalLength); 2122 | let offset = 0; 2123 | for (const chunk of chunks) { 2124 | fullResponse.set(chunk, offset); 2125 | offset += chunk.length; 2126 | } 2127 | 2128 | return fullResponse.slice(2); 2129 | } 2130 | 2131 | // 解析IPv6地址 2132 | function parseIPv6(response) { 2133 | const view = new DataView(response.buffer); 2134 | let offset = 12; // 跳过DNS头部 2135 | 2136 | // 跳过问题部分 2137 | while (view.getUint8(offset) !== 0) { 2138 | offset += view.getUint8(offset) + 1; 2139 | } 2140 | offset += 5; 2141 | 2142 | const answers = []; 2143 | const answerCount = view.getUint16(6); // 答案数量 2144 | 2145 | for (let i = 0; i < answerCount; i++) { 2146 | // 跳过名称 2147 | if ((view.getUint8(offset) & 0xC0) === 0xC0) { 2148 | offset += 2; 2149 | } else { 2150 | while (view.getUint8(offset) !== 0) { 2151 | offset += view.getUint8(offset) + 1; 2152 | } 2153 | offset++; 2154 | } 2155 | 2156 | const type = view.getUint16(offset); offset += 2; 2157 | offset += 6; // 跳过类和TTL 2158 | const dataLength = view.getUint16(offset); offset += 2; 2159 | 2160 | if (type === 28 && dataLength === 16) { // AAAA记录 2161 | const parts = []; 2162 | for (let j = 0; j < 8; j++) { 2163 | parts.push(view.getUint16(offset + j * 2).toString(16)); 2164 | } 2165 | answers.push(parts.join(':')); 2166 | } 2167 | offset += dataLength; 2168 | } 2169 | 2170 | return answers; 2171 | } 2172 | 2173 | try { 2174 | // 判断输入类型并处理 2175 | if (isIPv6(target)) { 2176 | return target; // IPv6直接返回 2177 | } 2178 | 2179 | let domain; 2180 | if (isIPv4(target)) { 2181 | domain = target + atob('LmlwLjA5MDIyNy54eXo='); // IPv4转换为NAT64域名 2182 | } else { 2183 | // 域名先解析IPv4再转NAT64 2184 | const ipv4 = await fetchIPv4(target); 2185 | domain = ipv4 + atob('LmlwLjA5MDIyNy54eXo='); 2186 | } 2187 | 2188 | return await queryNAT64(domain); 2189 | } catch (error) { 2190 | console.error('解析错误:', error); 2191 | return `解析失败: ${error.message}`; 2192 | } 2193 | } 2194 | --------------------------------------------------------------------------------