├── README.md ├── LICENSE └── _worker.js /README.md: -------------------------------------------------------------------------------- 1 | # 魔改版定制汇聚订阅 CF-Workers-SUB 可以配合cfno1优选订阅使用 2 | 3 | ### 魔改版定制汇聚订阅+cfno1优选订阅,它是一个通过 Cloudflare Workers 搭建,将你任意节点与多个订阅汇聚成专属于你的订阅链接,自建节点(只支持vmess和vless)可以配合cfno1订阅刷新优选ip和端口 4 | 5 | MJJ-Telegram交流群:[魔改版MJJ交流群](https://t.me/bestvpschat) 6 | CM-Telegram交流群:[@CMLiussss原版交流群](https://t.me/CMLiussss) 7 | 8 | ## Pages 部署方法 [视频教程](https://youtu.be/9npcBXZTSe4) 9 | ### 1. 部署 Cloudflare Pages: 10 | - 在 Github 上先 Fork 本项目,并点上 Star !!! 11 | - 在 Cloudflare Pages 控制台中选择 `连接到 Git`后,选中 `CF-Workers-SUB`项目后点击 `开始设置`。 12 | 13 | ### 2. 给 Pages绑定 自定义域: 14 | - 在 Pages控制台的 `自定义域`选项卡,下方点击 `设置自定义域`。 15 | - 填入你的自定义次级域名,注意不要使用你的根域名,例如: 16 | 您分配到的域名是 `fuck.cloudns.biz`,则添加自定义域填入 `sub.fuck.cloudns.biz`即可; 17 | - 按照 Cloudflare 的要求将返回你的域名DNS服务商,添加 该自定义域 `sub`的 CNAME记录 `CF-Workers-SUB.pages.dev` 后,点击 `激活域`即可。 18 | 19 | ### 3. 修改 快速订阅入口 : 20 | 21 | 例如您的pages项目域名为:`sub.fuck.cloudns.biz`; 22 | - 添加 `TOKEN` 变量,快速订阅访问入口,默认值为: `auto` ,获取订阅器默认节点订阅地址即 `/auto` ,例如 `https://sub.fuck.cloudns.biz/auto` 23 | 24 | ### 4. 添加你的节点和订阅链接: 25 | - 添加 `LINK` 变量,参数添加你的自建节点链接和机场订阅链接,确保每行一个链接,例如: 26 | ``` 27 | vless://b7a392e2-4ef0-4496-90bc-1c37bb234904@cf.090227.xyz:443?encryption=none&security=tls&sni=edgetunnel-2z2.pages.dev&fp=random&type=ws&host=edgetunnel-2z2.pages.dev&path=%2F%3Fed%3D2048#%E5%8A%A0%E5%85%A5%E6%88%91%E7%9A%84%E9%A2%91%E9%81%93t.me%2FCMLiussss%E8%A7%A3%E9%94%81%E6%9B%B4%E5%A4%9A%E4%BC%98%E9%80%89%E8%8A%82%E7%82%B9 28 | vmess://ew0KICAidiI6ICIyIiwNCiAgInBzIjogIuWKoOWFpeaIkeeahOmikemBk3QubWUvQ01MaXVzc3Nz6Kej6ZSB5pu05aSa5LyY6YCJ6IqC54K5PuiLseWbvSDlgKvmlabph5Hono3ln44iLA0KICAiYWRkIjogImNmLjA5MDIyNy54eXoiLA0KICAicG9ydCI6ICI4NDQzIiwNCiAgImlkIjogIjAzZmNjNjE4LWI5M2QtNjc5Ni02YWVkLThhMzhjOTc1ZDU4MSIsDQogICJhaWQiOiAiMCIsDQogICJzY3kiOiAiYXV0byIsDQogICJuZXQiOiAid3MiLA0KICAidHlwZSI6ICJub25lIiwNCiAgImhvc3QiOiAicHBmdjJ0bDl2ZW9qZC1tYWlsbGF6eS5wYWdlcy5kZXYiLA0KICAicGF0aCI6ICIvamFkZXIuZnVuOjQ0My9saW5rdndzIiwNCiAgInRscyI6ICJ0bHMiLA0KICAic25pIjogInBwZnYydGw5dmVvamQtbWFpbGxhenkucGFnZXMuZGV2IiwNCiAgImFscG4iOiAiIiwNCiAgImZwIjogIiINCn0= 29 | https://sub.xf.free.hr/auto 30 | https://hy2sub.pages.dev 31 | ``` 32 | 33 | 34 | 35 | ## Workers 部署方法 36 | ### 1. 部署 Cloudflare Worker: 37 | 38 | - 在 Cloudflare Worker 控制台中创建一个新的 Worker。 39 | - 将 [worker.js](https://github.com/cmliu/CF-Workers-SUB/blob/main/_worker.js) 的内容粘贴到 Worker 编辑器中。 40 | 41 | 42 | ### 2. 修改 订阅入口 : 43 | 44 | 例如您的workers项目域名为:`sub.cmliussss.workers.dev`; 45 | - 通过修改 `mytoken` 赋值内容,达到修改你专属订阅的入口,避免订阅泄漏。 46 | ``` 47 | let mytoken = 'auto'; 48 | ``` 49 | 如上所示,你的订阅地址则如下: 50 | ```url 51 | https://sub.cmliussss.workers.dev/auto 52 | 或 53 | https://sub.cmliussss.workers.dev/?token=auto 54 | ``` 55 | 56 | 57 | ### 3. 添加你的节点或订阅链接: 58 | 59 | **3.1 修改 MainData 参数示例** 60 | 61 | - 修改 `MainData` 参数添加你的自建节点,例如: 62 | 63 | ```js 64 | const MainData = ` 65 | vless://b7a392e2-4ef0-4496-90bc-1c37bb234904@cf.090227.xyz:443?encryption=none&security=tls&sni=edgetunnel-2z2.pages.dev&fp=random&type=ws&host=edgetunnel-2z2.pages.dev&path=%2F%3Fed%3D2048#%E5%8A%A0%E5%85%A5%E6%88%91%E7%9A%84%E9%A2%91%E9%81%93t.me%2FCMLiussss%E8%A7%A3%E9%94%81%E6%9B%B4%E5%A4%9A%E4%BC%98%E9%80%89%E8%8A%82%E7%82%B9 66 | vmess://ew0KICAidiI6ICIyIiwNCiAgInBzIjogIuWKoOWFpeaIkeeahOmikemBk3QubWUvQ01MaXVzc3Nz6Kej6ZSB5pu05aSa5LyY6YCJ6IqC54K5PuiLseWbvSDlgKvmlabph5Hono3ln44iLA0KICAiYWRkIjogImNmLjA5MDIyNy54eXoiLA0KICAicG9ydCI6ICI4NDQzIiwNCiAgImlkIjogIjAzZmNjNjE4LWI5M2QtNjc5Ni02YWVkLThhMzhjOTc1ZDU4MSIsDQogICJhaWQiOiAiMCIsDQogICJzY3kiOiAiYXV0byIsDQogICJuZXQiOiAid3MiLA0KICAidHlwZSI6ICJub25lIiwNCiAgImhvc3QiOiAicHBmdjJ0bDl2ZW9qZC1tYWlsbGF6eS5wYWdlcy5kZXYiLA0KICAicGF0aCI6ICIvamFkZXIuZnVuOjQ0My9saW5rdndzIiwNCiAgInRscyI6ICJ0bHMiLA0KICAic25pIjogInBwZnYydGw5dmVvamQtbWFpbGxhenkucGFnZXMuZGV2IiwNCiAgImFscG4iOiAiIiwNCiAgImZwIjogIiINCn0= 67 | ` 68 | ``` 69 | 注意!`MainData`参数的特殊引号必须保留,否则代码异常。 70 | 71 | 72 | 73 | **3.2 修改 urls 参数示例** 74 | 75 | - 修改 `urls` 参数,在脚本中设置 `urls` 变量为 你的订阅链接 的 URL。例如: 76 | 77 | ```js 78 | const urls = [ 79 | 'https://sub.xf.free.hr/auto', 80 | 'https://hy2sub.pages.dev', 81 | ]; 82 | ``` 83 | 注意!订阅链接内容必须为`base64`格式。 84 | 85 | ## 变量说明 86 | | 变量名 | 示例 | 备注 | 87 | |--------|---------|-----| 88 | | LINK | vless://b7a39... vmess://ew0K... https://sub... | 可同时放入多个节点链接与多个订阅链接, 链接之间用换行做间隔 | 89 | | TOKEN | auto | 快速订阅内置节点的订阅路径地址 /auto | 90 | | TGTOKEN | 6894123456:XXXXXXXXXX0qExVsBPUhHDAbXXXXXqWXgBA | 发送TG通知的机器人token | 91 | | TGID | 6946912345 | 接收TG通知的账户数字ID | 92 | | SUBAPI | apiurl.v1.mk | clash、singbox等 订阅转换后端 | 93 | | SUBCONFIG | [https://raw.github.../ACL4SSR_Online_MultiCountry.ini](https://raw.githubusercontent.com/cmliu/ACL4SSR/main/Clash/config/ACL4SSR_Online_MultiCountry.ini) | clash、singbox等 订阅转换配置文件 | 94 | | SUBPROXYURL | https://cfno1.pages.dev/sub | 魔改版本优选IP订阅 | 95 | 96 | ## SUBPROXYURL 支持多个优选IP订阅,通过换行符分割,请确保每行一个订阅链接 97 | ## 注意事项 98 | 项目中,TGTOKEN和TGID在使用时需要先到Telegram注册并获取。其中,TGTOKEN是telegram bot的凭证,TGID是用来接收通知的telegram用户或者组的id。 99 | 100 | 101 | ## Star 星星走起 102 | [![Stargazers over time](https://starchart.cc/cmliu/CF-Workers-SUB.svg?variant=adaptive)](https://starchart.cc/cmliu/CF-Workers-SUB) 103 | 104 | 105 | # 感谢 106 | [mianayang](https://github.com/mianayang/myself/blob/main/cf-workers/sub/sub.js)、[ACL4SSR](https://github.com/ACL4SSR/ACL4SSR/tree/master/Clash/config)、[肥羊](https://github.com/youshandefeiyang/sub-web-modify) 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /_worker.js: -------------------------------------------------------------------------------- 1 | 2 | // 部署完成后在网址后面加上这个,获取自建节点和机场聚合节点,/?token=auto或/auto或 3 | 4 | let mytoken = 'auto'; //可以随便取,或者uuid生成,https://1024tools.com/uuid 5 | let BotToken =''; //可以为空,或者@BotFather中输入/start,/newbot,并关注机器人 6 | let ChatID =''; //可以为空,或者@userinfobot中获取,/start 7 | let TG = 0; //小白勿动, 开发者专用,1 为推送所有的访问信息,0 为不推送订阅转换后端的访问信息与异常访问 8 | let FileName = 'CF-Workers-SUB'; 9 | let SUBUpdateTime = 6; //自定义订阅更新时间,单位小时 10 | let total = 99;//TB 11 | let timestamp = 4102329600000;//2099-12-31 12 | 13 | //节点链接 + 订阅链接 14 | let MainData = ` 15 | vmess://ew0KICAidiI6ICIyIiwNCiAgInBzIjogIm9yYS1zZy12bS1iMHpyZnJwYiIsDQogICJhZGQiOiAib3Jhc2cuZWN1cC5ueWMubW4iLA0KICAicG9ydCI6ICI0NDMiLA0KICAiaWQiOiAiMmUyMzUyNTgtNTYxYS00ZjE5LTkwZTAtMzM2ZjgwYWM5MzQ1IiwNCiAgImFpZCI6ICIwIiwNCiAgInNjeSI6ICJhdXRvIiwNCiAgIm5ldCI6ICJ3cyIsDQogICJ0eXBlIjogIm5vbmUiLA0KICAiaG9zdCI6ICJvcmFzZy5lY3VwLm55Yy5tbiIsDQogICJwYXRoIjogIi8/ZWQ9MjA0OCIsDQogICJ0bHMiOiAidGxzIiwNCiAgInNuaSI6ICJvcmFzZy5lY3VwLm55Yy5tbiIsDQogICJhbHBuIjogIiIsDQogICJmcCI6ICIiDQp9 16 | ` 17 | 18 | let urls = []; 19 | let subconverter = "subapi-loadbalancing.pages.dev"; //在线订阅转换后端,目前使用CM的订阅转换功能。支持自建psub 可自行搭建https://github.com/bulianglin/psub 20 | let subconfig = "https://raw.githubusercontent.com/cmliu/ACL4SSR/main/Clash/config/ACL4SSR_Online_MultiCountry.ini"; //订阅配置文件 21 | 22 | let subproxyUrl = ` 23 | https://yu.qqyy.qzz.io/yuu 24 | `; 25 | let encodedData = ''; 26 | let lastDebugInfo = []; 27 | let subdomains = ''; 28 | 29 | export default { 30 | async fetch (request,env) { 31 | const userAgentHeader = request.headers.get('User-Agent'); 32 | const userAgent = userAgentHeader ? userAgentHeader.toLowerCase() : "null"; 33 | const url = new URL(request.url); 34 | const token = url.searchParams.get('token'); 35 | mytoken = env.TOKEN || mytoken; 36 | BotToken = env.TGTOKEN || BotToken; 37 | ChatID = env.TGID || ChatID; 38 | TG = env.TG || TG; 39 | subconverter = env.SUBAPI || subconverter; 40 | subconfig = env.SUBCONFIG || subconfig; 41 | FileName = env.SUBNAME || FileName; 42 | MainData = env.LINK || MainData; 43 | if(env.LINKSUB) urls = await ADD(env.LINKSUB); 44 | subproxyUrl = env.SUBPROXYURL || subproxyUrl; 45 | subdomains = env.SUBDOMAIN || subdomains; 46 | 47 | const currentDate = new Date(); 48 | currentDate.setHours(0, 0, 0, 0); 49 | const timeTemp = Math.ceil(currentDate.getTime() / 1000); 50 | const fakeToken = await MD5MD5(`${mytoken}${timeTemp}`); 51 | //console.log(`${fakeUserID}\n${fakeHostName}`); // 打印fakeID 52 | 53 | let UD = Math.floor(((timestamp - Date.now())/timestamp * total * 1099511627776 )/2); 54 | total = total * 1099511627776 ; 55 | let expire= Math.floor(timestamp / 1000) ; 56 | SUBUpdateTime = env.SUBUPTIME || SUBUpdateTime; 57 | 58 | // 获取优选IP端口订阅数据 59 | lastDebugInfo = []; 60 | encodedData = await fetchMultipleSubscriptions(subproxyUrl); 61 | 62 | if (url.pathname === "/debug") { 63 | const decodedCombined = decodeAggregatedData(encodedData); 64 | const lines = decodedCombined ? normalizeLines(decodedCombined) : []; 65 | const ipPortList = decodedCombined ? parseIPPort(lines.join('\n')) : []; 66 | let 重新汇总所有链接_调试 = await ADD(MainData + '\n' + (urls && urls.length ? urls.join('\n') : '')); 67 | let 调试替换结果 = []; 68 | const additionalName = "@bestvpschat"; 69 | const domains_debug = subdomains ? await ADD(subdomains) : []; 70 | for (let x of 重新汇总所有链接_调试) { 71 | if (!x.toLowerCase().startsWith('http')) { 72 | const newLinks = getEncodedNewLinks(x, additionalName); 73 | if (newLinks && newLinks.length) { 74 | for (const n of newLinks) { 75 | 调试替换结果.push(n); 76 | } 77 | } 78 | } 79 | if (domains_debug && domains_debug.length) { 80 | const domLinks = getDomainNewLinks(x, domains_debug, additionalName); 81 | if (domLinks && domLinks.length) { 82 | for (const d of domLinks) { 83 | 调试替换结果.push(d); 84 | } 85 | } 86 | } 87 | } 88 | 89 | const ipListPreview = ipPortList.slice(0, 100).map((x, i) => `${i+1}. ${x.ip}:${x.port}${x.name ? ' #' + x.name : ''}`).join('\n'); 90 | const replacePreview = 调试替换结果.slice(0, 100).join('\n'); 91 | const sourceDebug = lastDebugInfo && lastDebugInfo.length ? lastDebugInfo.map((d, i) => `${i+1}. URL: ${d.url}\n ok=${d.ok} status=${d.status} length=${d.length}${d.ct ? ` ct=${d.ct}` : ''}${d.base64!==undefined ? ` base64=${d.base64}` : ''}${d.error ? `\n error=${d.error}` : ''}${d.sample ? `\n sample=${d.sample}` : ''}`).join('\n\n') : '无'; 92 | const html = `CF-Workers-SUB Debug 93 |

Debug

94 |
SUBPROXYURL 源:
${(subproxyUrl || '').toString().replace(/
95 |
抓取结果(解码后)行数: ${lines.length}
96 |

源抓取状态

97 |
${sourceDebug}
98 |

解析到的 IPv4:端口 (前100条)

99 |
${ipListPreview || '无'}
100 |

替换结果 (前100条)

101 |
总计: ${调试替换结果.length}
102 |
${replacePreview || '无'}
103 | `; 104 | return new Response(html, { status: 200, headers: { 'content-type': 'text/html; charset=utf-8' } }); 105 | } 106 | 107 | let 重新汇总所有链接 = await ADD(MainData + '\n' + urls.join('\n')); 108 | let 自建节点 =""; 109 | let 订阅链接 =""; 110 | const domains = subdomains ? await ADD(subdomains) : []; 111 | for (let x of 重新汇总所有链接) { 112 | if (x.toLowerCase().startsWith('http')) { 113 | 订阅链接 += x + '\n'; 114 | } else { 115 | //这里裂变所有可替换节点的优选IP和端口 116 | const additionalName = "@bestvpschat"; // 你想要增加的名称 117 | const newLinks = getEncodedNewLinks(x, additionalName); 118 | 119 | if (newLinks.length > 0) { 120 | newLinks.forEach(newLink => { 121 | 自建节点 += newLink + '\n'; 122 | }); 123 | } else { 124 | 自建节点 += x + '\n'; 125 | } 126 | 127 | // 基于 SUBDOMAIN 的域名替换,端口固定 443 128 | if (domains && domains.length) { 129 | const domLinks = getDomainNewLinks(x, domains, additionalName); 130 | if (domLinks && domLinks.length) { 131 | domLinks.forEach(dl => { 自建节点 += dl + '\n'; }); 132 | } 133 | } 134 | 135 | //自建节点 += x + '\n'; 136 | } 137 | } 138 | MainData = 自建节点; 139 | urls = await ADD(订阅链接); 140 | 141 | if ( !(token == mytoken || token == fakeToken || url.pathname == ("/"+ mytoken) || url.pathname.includes("/"+ mytoken + "?")) ) { 142 | if ( TG == 1 && url.pathname !== "/" && url.pathname !== "/favicon.ico" ) await sendMessage(`#异常访问 ${FileName}`, request.headers.get('CF-Connecting-IP'), `UA: ${userAgent}\n域名: ${url.hostname}\n入口: ${url.pathname + url.search}`); 143 | const envKey = env.URL302 ? 'URL302' : (env.URL ? 'URL' : null); 144 | if (envKey) { 145 | const URLs = await ADD(env[envKey]); 146 | const URL = URLs[Math.floor(Math.random() * URLs.length)]; 147 | return envKey === 'URL302' ? Response.redirect(URL, 302) : fetch(new Request(URL, request)); 148 | } 149 | return new Response(await nginx(), { 150 | status: 200 , 151 | headers: { 152 | 'Content-Type': 'text/html; charset=UTF-8', 153 | }, 154 | }); 155 | } else { 156 | await sendMessage(`#获取订阅 ${FileName}`, request.headers.get('CF-Connecting-IP'), `UA: ${userAgentHeader}\n域名: ${url.hostname}\n入口: ${url.pathname + url.search}`); 157 | let 订阅格式 = 'base64'; 158 | if (userAgent.includes('null') || userAgent.includes('subconverter') || userAgent.includes('nekobox') || userAgent.includes(('CF-Workers-SUB').toLowerCase())){ 159 | 订阅格式 = 'base64'; 160 | } else if (userAgent.includes('clash') || ( url.searchParams.has('clash') && !userAgent.includes('subconverter'))){ 161 | 订阅格式 = 'clash'; 162 | } else if (userAgent.includes('sing-box') || userAgent.includes('singbox') || ( (url.searchParams.has('sb') || url.searchParams.has('singbox')) && !userAgent.includes('subconverter'))){ 163 | 订阅格式 = 'singbox'; 164 | } 165 | 166 | let subconverterUrl ; 167 | let 订阅转换URL = `${url.origin}/${await MD5MD5(fakeToken)}?token=${fakeToken}`; 168 | //console.log(订阅转换URL); 169 | let req_data = MainData; 170 | // 创建一个AbortController对象,用于控制fetch请求的取消 171 | const controller = new AbortController(); 172 | 173 | const timeout = setTimeout(() => { 174 | controller.abort(); // 取消所有请求 175 | }, 2000); // 2秒后触发 176 | 177 | 178 | let 追加UA = 'v2rayn'; 179 | if (url.searchParams.has('clash')){ 180 | 追加UA = 'clash'; 181 | } else if(url.searchParams.has('singbox')){ 182 | 追加UA = 'singbox'; 183 | } 184 | 185 | try { 186 | const responses = await Promise.allSettled(urls.map(url => 187 | fetch(url, { 188 | method: 'get', 189 | headers: { 190 | 'Accept': 'text/html,application/xhtml+xml,application/xml;', 191 | 'User-Agent': `${追加UA} cmliu/CF-Workers-SUB ${userAgentHeader}` 192 | }, 193 | signal: controller.signal // 将AbortController的信号量添加到fetch请求中,以便于需要时可以取消请求 194 | }).then(response => { 195 | if (response.ok) { 196 | return response.text().then(content => { 197 | // 这里可以顺便做内容检查 198 | if (content.includes('dns') && content.includes('proxies') && content.includes('proxy-groups')) { 199 | //console.log("clashsub: " + url); 200 | 订阅转换URL += "|" + url; 201 | } else if (content.includes('dns') && content.includes('outbounds') && content.includes('inbounds')){ 202 | //console.log("singboxsub: " + url); 203 | 订阅转换URL += "|" + url; 204 | } else { 205 | //console.log("未识别" + url); 206 | return content; // 保证链式调用中的下一个then可以接收到文本内容 207 | } 208 | //console.log(content); 209 | }); 210 | } else { 211 | return ""; // 如果response.ok为false,返回空字符串 212 | } 213 | }) 214 | )); 215 | 216 | for (const response of responses) { 217 | if (response.status === 'fulfilled' && response.value) { 218 | const content = response.value; 219 | req_data += base64Decode(content) + '\n'; 220 | } 221 | } 222 | 223 | } catch (error) { 224 | //console.error(error); 225 | } finally { 226 | // 无论成功或失败,最后都清除设置的超时定时器 227 | clearTimeout(timeout); 228 | } 229 | 230 | //修复中文错误 231 | const utf8Encoder = new TextEncoder(); 232 | const encodedData = utf8Encoder.encode(req_data); 233 | const text = String.fromCharCode.apply(null, encodedData); 234 | 235 | //去重 236 | const uniqueLines = new Set(text.split('\n')); 237 | const result = [...uniqueLines].join('\n'); 238 | console.log(result); 239 | 240 | const base64Data = btoa(result); 241 | 242 | if (订阅格式 == 'base64' || token == fakeToken){ 243 | return new Response(base64Data ,{ 244 | headers: { 245 | "content-type": "text/plain; charset=utf-8", 246 | "Profile-Update-Interval": `${SUBUpdateTime}`, 247 | "Subscription-Userinfo": `upload=${UD}; download=${UD}; total=${total}; expire=${expire}`, 248 | } 249 | }); 250 | } else if (订阅格式 == 'clash'){ 251 | subconverterUrl = `https://${subconverter}/sub?target=clash&url=${encodeURIComponent(订阅转换URL)}&insert=false&config=${encodeURIComponent(subconfig)}&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true`; 252 | } else if (订阅格式 == 'singbox'){ 253 | subconverterUrl = `https://${subconverter}/sub?target=singbox&url=${encodeURIComponent(订阅转换URL)}&insert=false&config=${encodeURIComponent(subconfig)}&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true`; 254 | } 255 | console.log(订阅转换URL); 256 | try { 257 | const subconverterResponse = await fetch(subconverterUrl); 258 | 259 | if (!subconverterResponse.ok) { 260 | return new Response(base64Data ,{ 261 | headers: { 262 | "content-type": "text/plain; charset=utf-8", 263 | "Profile-Update-Interval": `${SUBUpdateTime}`, 264 | "Subscription-Userinfo": `upload=${UD}; download=${UD}; total=${total}; expire=${expire}`, 265 | } 266 | }); 267 | //throw new Error(`Error fetching subconverterUrl: ${subconverterResponse.status} ${subconverterResponse.statusText}`); 268 | } 269 | let subconverterContent = await subconverterResponse.text(); 270 | if (订阅格式 == 'clash') subconverterContent =await clashFix(subconverterContent); 271 | return new Response(subconverterContent, { 272 | headers: { 273 | "Content-Disposition": `attachment; filename*=utf-8''${encodeURIComponent(FileName)}; filename=${FileName}`, 274 | "content-type": "text/plain; charset=utf-8", 275 | "Profile-Update-Interval": `${SUBUpdateTime}`, 276 | "Subscription-Userinfo": `upload=${UD}; download=${UD}; total=${total}; expire=${expire}`, 277 | 278 | }, 279 | }); 280 | } catch (error) { 281 | return new Response(base64Data ,{ 282 | headers: { 283 | "content-type": "text/plain; charset=utf-8", 284 | "Profile-Update-Interval": `${SUBUpdateTime}`, 285 | "Subscription-Userinfo": `upload=${UD}; download=${UD}; total=${total}; expire=${expire}`, 286 | } 287 | }); 288 | } 289 | } 290 | } 291 | }; 292 | 293 | async function ADD(envadd) { 294 | var addtext = envadd.replace(/[ "'|\r\n]+/g, ',').replace(/,+/g, ','); // 将空格、双引号、单引号和换行符替换为逗号 295 | //console.log(addtext); 296 | if (addtext.charAt(0) == ',') addtext = addtext.slice(1); 297 | if (addtext.charAt(addtext.length -1) == ',') addtext = addtext.slice(0, addtext.length - 1); 298 | const add = addtext.split(','); 299 | //console.log(add); 300 | return add ; 301 | } 302 | 303 | async function nginx() { 304 | const text = ` 305 | 306 | 307 | 308 | Welcome to nginx! 309 | 316 | 317 | 318 |

Welcome to nginx!

319 |

If you see this page, the nginx web server is successfully installed and 320 | working. Further configuration is required.

321 | 322 |

For online documentation and support please refer to 323 | nginx.org.
324 | Commercial support is available at 325 | nginx.com.

326 | 327 |

Thank you for using nginx.

328 | 329 | 330 | ` 331 | return text ; 332 | } 333 | 334 | async function sendMessage(type, ip, add_data = "") { 335 | if ( BotToken !== '' && ChatID !== ''){ 336 | let msg = ""; 337 | const response = await fetch(`http://ip-api.com/json/${ip}?lang=zh-CN`); 338 | if (response.status == 200) { 339 | const ipInfo = await response.json(); 340 | msg = `${type}\nIP: ${ip}\n国家: ${ipInfo.country}\n城市: ${ipInfo.city}\n组织: ${ipInfo.org}\nASN: ${ipInfo.as}\n${add_data}`; 341 | } else { 342 | msg = `${type}\nIP: ${ip}\n${add_data}`; 343 | } 344 | 345 | let url = "https://api.telegram.org/bot"+ BotToken +"/sendMessage?chat_id=" + ChatID + "&parse_mode=HTML&text=" + encodeURIComponent(msg); 346 | return fetch(url, { 347 | method: 'get', 348 | headers: { 349 | 'Accept': 'text/html,application/xhtml+xml,application/xml;', 350 | 'Accept-Encoding': 'gzip, deflate, br', 351 | 'User-Agent': 'Mozilla/5.0 Chrome/90.0.4430.72' 352 | } 353 | }); 354 | } 355 | } 356 | 357 | function base64Decode(str) { 358 | const bytes = new Uint8Array(atob(str).split('').map(c => c.charCodeAt(0))); 359 | const decoder = new TextDecoder('utf-8'); 360 | return decoder.decode(bytes); 361 | } 362 | 363 | async function MD5MD5(text) { 364 | const encoder = new TextEncoder(); 365 | 366 | const firstPass = await crypto.subtle.digest('MD5', encoder.encode(text)); 367 | const firstPassArray = Array.from(new Uint8Array(firstPass)); 368 | const firstHex = firstPassArray.map(b => b.toString(16).padStart(2, '0')).join(''); 369 | 370 | const secondPass = await crypto.subtle.digest('MD5', encoder.encode(firstHex.slice(7, 27))); 371 | const secondPassArray = Array.from(new Uint8Array(secondPass)); 372 | const secondHex = secondPassArray.map(b => b.toString(16).padStart(2, '0')).join(''); 373 | 374 | return secondHex.toLowerCase(); 375 | } 376 | 377 | async function fetchSubscription(url) { 378 | try { 379 | const response = await fetch(url); 380 | if (!response.ok) { 381 | throw new Error('Network response was not ok'); 382 | } 383 | const text = await response.text(); 384 | return text; 385 | } catch (error) { 386 | console.error('There has been a problem with your fetch operation:', error); 387 | } 388 | } 389 | 390 | async function fetchSubscriptionDebug(url) { 391 | const info = { url, ok: false, status: 0, ct: '', length: 0, base64: undefined, sample: '', error: '' }; 392 | try { 393 | const response = await fetch(url, { 394 | method: 'GET', 395 | headers: { 396 | 'Accept': 'text/plain, text/*, */*;q=0.1', 397 | 'Cache-Control': 'no-cache', 398 | 'Pragma': 'no-cache', 399 | 'User-Agent': 'CF-Workers-SUB Debug/1.0' 400 | } 401 | }); 402 | info.status = response.status; 403 | info.ct = response.headers.get('content-type') || ''; 404 | const text = await response.text(); 405 | info.length = text.length; 406 | info.ok = response.ok; 407 | try { info.base64 = isBase64(text); } catch (e) { info.base64 = false; } 408 | info.sample = text.slice(0, 200).replace(/\n/g, ' '); 409 | return { info, text }; 410 | } catch (error) { 411 | info.error = String(error && error.message ? error.message : error); 412 | return { info, text: '' }; 413 | } 414 | } 415 | 416 | async function fetchMultipleSubscriptions(urls) { 417 | try { 418 | const urlArray = urls.split('\n').slice(0, 10); 419 | const results = await Promise.all(urlArray.map(async (url) => { 420 | const trimmedUrl = url.trim(); // 去除可能存在的多余空格 421 | if (trimmedUrl) { 422 | const { info, text } = await fetchSubscriptionDebug(trimmedUrl); 423 | (lastDebugInfo || (lastDebugInfo = [])).push(info); 424 | if (!info.ok || !text) return ''; 425 | let decodedResult = text; 426 | if (isBase64(text)) { 427 | decodedResult = base64Decode(text); 428 | } 429 | const tokens = normalizeLines(decodedResult); 430 | return base64Encode(tokens.slice(0, 100).join('\n')); 431 | } 432 | return ''; // 返回空字符串以确保结果数组长度一致 433 | })); 434 | return results.join('\n'); // 将所有结果拼接成一个字符串 435 | } catch (error) { 436 | console.error('There has been a problem with your fetch operation:', error); 437 | } 438 | } 439 | 440 | function clashFix(content) { 441 | if(content.includes('wireguard') && !content.includes('remote-dns-resolve')){ 442 | let lines; 443 | if (content.includes('\r\n')){ 444 | lines = content.split('\r\n'); 445 | } else { 446 | lines = content.split('\n'); 447 | } 448 | 449 | let result = ""; 450 | for (let line of lines) { 451 | if (line.includes('type: wireguard')) { 452 | const 备改内容 = `, mtu: 1280, udp: true`; 453 | const 正确内容 = `, mtu: 1280, remote-dns-resolve: true, udp: true`; 454 | result += line.replace(new RegExp(备改内容, 'g'), 正确内容) + '\n'; 455 | } else { 456 | result += line + '\n'; 457 | } 458 | } 459 | 460 | content = result; 461 | } 462 | return content; 463 | } 464 | 465 | // 判断字符串是否为Base64编码 466 | function isBase64(str) { 467 | try { 468 | return btoa(atob(str)) === str; 469 | } catch (err) { 470 | return false; 471 | } 472 | } 473 | 474 | // Base64编码函数 475 | function base64Encode(str) { 476 | try { 477 | const encoder = new TextEncoder(); 478 | const bytes = encoder.encode(str); 479 | let binary = ''; 480 | for (let i = 0; i < bytes.length; i++) { 481 | binary += String.fromCharCode(bytes[i]); 482 | } 483 | return btoa(binary); 484 | } catch (error) { 485 | console.error('There has been a problem with your Base64 encoding operation:', error); 486 | } 487 | } 488 | 489 | 490 | // 规范化订阅内容为逐行的 IP:端口[#名称] 491 | function normalizeLines(text) { 492 | if (!text) return []; 493 | // 统一换行符,然后按空白/逗号切分,过滤空条目 494 | const tokens = text.replace(/\r/g, '\n').split(/[\s,]+/).filter(Boolean); 495 | // 仅保留包含 ":" 的候选 496 | const candidates = tokens.filter(t => /:\d+/.test(t)); 497 | return candidates; 498 | } 499 | 500 | // 解聚合的 encodedData,兼容多段 base64 逐行拼接 501 | function decodeAggregatedData(data) { 502 | if (!data) return ''; 503 | const parts = data.split('\n').filter(Boolean); 504 | let combined = ''; 505 | for (const part of parts) { 506 | let segment = part; 507 | if (isBase64(part)) { 508 | try { segment = base64Decode(part); } catch (e) { /* ignore */ } 509 | } 510 | if (combined) combined += '\n'; 511 | combined += segment; 512 | } 513 | return combined; 514 | } 515 | 516 | // 解析IP、端口和名称信息 517 | function parseIPPort(data) { 518 | const lines = data.split('\n'); 519 | const ipPortList = lines.map(line => { 520 | let match = line.match(/@([^:]+):(\d+)(#(.*))?/); 521 | if (!match) { 522 | // 尝试匹配纯 IP:端口 格式 523 | match = line.match(/([^:]+):(\d+)(#(.*))?/); 524 | } 525 | if (match) { 526 | if (isValidIPv4(match[1])) { 527 | const lastHashIndex = line.lastIndexOf('#'); 528 | let name = lastHashIndex !== -1 ? line.substring(lastHashIndex + 1) : ''; 529 | return { ip: match[1], port: match[2], name }; 530 | } 531 | } 532 | return null; 533 | }).filter(entry => entry !== null); // 过滤掉无效的条目 534 | return ipPortList; 535 | } 536 | 537 | function isValidIPv4(ip) { 538 | const ipv4Pattern = /^(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]?)$/; 539 | return ipv4Pattern.test(ip); 540 | } 541 | 542 | // 替换vmess链接中的IP和端口,并在原有ps标签基础上增加新的名称 543 | function replaceVmessIPPort(template, ip, port, newName) { 544 | // 使用正则表达式获取ps标签的值 545 | const psMatch = template.match(/"ps":\s*"(.*?)"/); 546 | const originalName = psMatch ? decodeURIComponent(psMatch[1]) : ''; 547 | const finalName = originalName ? `${originalName}|${newName}` : newName; 548 | 549 | const replacedTemplate = template 550 | .replace(/"add":\s*".*?"/, `"add": "${ip}"`) 551 | .replace(/"port":\s*"\d+"/, `"port": "${port}"`) 552 | .replace(/"ps":\s*".*?"/, `"ps": "${finalName}"`); 553 | 554 | return replacedTemplate; 555 | } 556 | 557 | // 替换vless链接中的IP和端口,并在原有名称基础上增加新的名称 558 | function replaceVlessIPPort(template, ip, port, newName) { 559 | const originalNameMatch = template.match(/#(.*)$/); 560 | const originalName = originalNameMatch ? decodeURIComponent(originalNameMatch[1]) : ''; 561 | const finalName = originalName ? `${originalName}|${newName}` : newName; 562 | return template.replace(/@[^:]+:\d+/, `@${ip}:${port}`).replace(/#.*/, `#${finalName}`); 563 | } 564 | 565 | // 获取encodedNewLinks的方法 566 | function getEncodedNewLinks(templateLink, additionalName) { 567 | const newLinks = []; 568 | 569 | // 判断是vmess还是vless链接 570 | const isVmess = templateLink.startsWith("vmess://"); 571 | const isVless = templateLink.startsWith("vless://"); 572 | 573 | // 去掉前缀 574 | if (isVmess) { 575 | templateLink = templateLink.slice(8); 576 | } else if (isVless) { 577 | templateLink = templateLink.slice(8); 578 | } 579 | 580 | // 判断templateLink是否为Base64编码并进行解码(仅针对vmess) 581 | if (isVmess && isBase64(templateLink)) { 582 | templateLink = base64Decode(templateLink); 583 | } 584 | 585 | if (encodedData) { 586 | const decodedData = decodeAggregatedData(encodedData); 587 | if (decodedData) { 588 | const ipPortList = parseIPPort(decodedData); 589 | ipPortList.forEach(({ ip, port, name }) => { 590 | const finalName = name ? `${name}|${additionalName}` : additionalName; 591 | let newLink; 592 | if (isVmess) { 593 | newLink = replaceVmessIPPort(templateLink, ip, port, finalName); 594 | const encodedNewLink = base64Encode(newLink); 595 | newLinks.push(`vmess://${encodedNewLink}`); 596 | } else if (isVless) { 597 | newLink = replaceVlessIPPort(templateLink, ip, port, finalName); 598 | newLinks.push(`vless://${newLink}`); 599 | } 600 | }); 601 | } 602 | } 603 | 604 | return newLinks; 605 | } 606 | 607 | // 提取域名(去除协议与路径) 608 | function extractHostname(input) { 609 | try { 610 | if (/^https?:\/\//i.test(input)) { 611 | const u = new URL(input); 612 | return u.hostname; 613 | } 614 | // 去除可能携带的路径/端口 615 | return input.split('/')[0].split(':')[0].trim(); 616 | } catch (e) { 617 | return input; 618 | } 619 | } 620 | 621 | // 基于域名列表生成替换链接(端口固定 443) 622 | function getDomainNewLinks(templateLink, domains, additionalName) { 623 | const newLinks = []; 624 | 625 | // 判断是vmess还是vless链接 626 | const isVmess = templateLink.startsWith("vmess://"); 627 | const isVless = templateLink.startsWith("vless://"); 628 | 629 | // 去掉前缀 630 | if (isVmess) { 631 | templateLink = templateLink.slice(8); 632 | } else if (isVless) { 633 | templateLink = templateLink.slice(8); 634 | } 635 | 636 | // 判断templateLink是否为Base64编码并进行解码(仅针对vmess) 637 | if (isVmess && isBase64(templateLink)) { 638 | templateLink = base64Decode(templateLink); 639 | } 640 | 641 | domains.forEach(d => { 642 | const host = extractHostname(d); 643 | if (!host) return; 644 | const port = '443'; 645 | let newLink; 646 | if (isVmess) { 647 | newLink = replaceVmessIPPort(templateLink, host, port, additionalName); 648 | const encodedNewLink = base64Encode(newLink); 649 | newLinks.push(`vmess://${encodedNewLink}`); 650 | } else if (isVless) { 651 | newLink = replaceVlessIPPort(templateLink, host, port, additionalName); 652 | newLinks.push(`vless://${newLink}`); 653 | } 654 | }); 655 | 656 | return newLinks; 657 | } 658 | --------------------------------------------------------------------------------