├── .editorconfig ├── .env ├── .gitignore ├── LICENSE ├── README.md ├── assets └── image-20200116151711944.png ├── functions ├── SSR2Clash.js ├── SSRDecode.js ├── SSRFilter.js ├── SSRTest.js ├── SurgeProfile2SurgeList.js ├── TestGetEventInfo.js ├── addin │ ├── emoji.js │ └── ssr.js └── ds │ └── Surge.js ├── index.html ├── netlify.toml ├── package.json ├── webpack.config.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | BROWSER=none 2 | ESLINT=1 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # netlify 2 | .netlify/ 3 | 4 | # node 5 | node_modules/ 6 | /npm-debug.log* 7 | /yarn-error.log 8 | 9 | # production 10 | /dist 11 | 12 | # misc 13 | .DS_Store 14 | 15 | # umi 16 | .umi 17 | .umi-production -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Singee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ConfigConverter 2 | [](https://app.netlify.com/sites/ssrconvert/deploys) 3 | 4 | 将各种代理软件的配置文件进行转换 5 | 6 | ## WHY 7 | 8 | 自己的机场节点快破百了,在一个订阅链接里面,挑选起来很不方便 9 | 10 | 想找直接订阅过滤的api,发现都不是开源的,有可能会被记录订阅地址 11 | 12 | 为了解决这个问题,特地写了这个api,自己部署到服务器,所有内容均开源 13 | 14 | ## API Endpoint 15 | 16 | - `/api/SSRFilter` 对SSR订阅进行筛选 17 | 18 | | 地址 | 功能 | 参数 | 说明 | 19 | | :------------: | :-------------: | :-----: | :-------------------------------------: | 20 | | /api/SSRFilter | SSR订阅节点过滤 | sub | 订阅地址(放在最后喔) | 21 | | | | include | 要显示的节点名称正则表达式(需UrlEncode) | 22 | | | | exclude | 要移除的节点正则表达式(需UrlEncode) | 23 | | | | preview | 只要有值就直接预览生成的结果 | 24 | | | | | | 25 | | | | || 26 | 27 | 例如 28 | 29 | test.netlify.com/api/vmessfilter?preview=yes&filter=香港&sub=你的订阅地址 30 | 31 | 此处的香港没有urlencode,知晓一下 32 | 33 | ## 自部署 34 | 35 | 上述网址仅供演示使用,随时可能停止。自行使用请点击下面按钮部署至 netlify 36 | 37 | [](https://app.netlify.com/start/deploy?repository=https://github.com/sazs34/Convert) 38 | -------------------------------------------------------------------------------- /assets/image-20200116151711944.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uni-pai/Convert/ad34c81b38bea09840b856ad7256487f79c1e3be/assets/image-20200116151711944.png -------------------------------------------------------------------------------- /functions/SSR2Clash.js: -------------------------------------------------------------------------------- 1 | const fly = require("flyio"); 2 | const atob = require('atob'); 3 | const isUrl = require('is-url'); 4 | 5 | exports.handler = function(event, context, callback) { 6 | const { queryStringParameters } = event; 7 | 8 | const url = queryStringParameters['src']; 9 | 10 | if (!isUrl(url)) { 11 | return callback(null, { 12 | headers: { 13 | "Content-Type": "text/plain; charset=utf-8" 14 | }, 15 | statusCode: 400, 16 | body: "参数 src 无效,请检查是否提供了正确的节点订阅地址。" 17 | }); 18 | } 19 | 20 | fly.get(url).then(response => { 21 | const bodyDecoded = atob(response.data); 22 | const links = bodyDecoded.split('\n'); 23 | const filteredLinks = links.filter(link => { 24 | // Only support ss & ssr now 25 | if (link.startsWith('ss://')) return true; 26 | if (link.startsWith('ssr://')) return true; 27 | return false; 28 | }); 29 | 30 | if (filteredLinks.length == 0) { 31 | return callback(null, { 32 | headers: { 33 | "Content-Type": "text/plain; charset=utf-8" 34 | }, 35 | statusCode: 400, 36 | body: "订阅地址中没有节点信息。" 37 | }); 38 | } 39 | const processedLinks = new Array(); 40 | filteredLinks.forEach(link => { 41 | // 将订阅链接包装为对象 42 | if (link.startsWith('ss://')) { 43 | 44 | } 45 | 46 | // 过滤非 origin、plain 的 SSR 节点(Clash 暂时只支持 SS) 47 | 48 | // DEBUG 49 | processedLinks.push(link); 50 | }) 51 | 52 | if (processedLinks.length == 0) { 53 | return callback(null, { 54 | statusCode: 400, 55 | body: "订阅地址中没有节点信息。" 56 | }); 57 | } 58 | 59 | 60 | 61 | // DEBUG 62 | return callback(null, { 63 | headers: { 64 | "Content-Type": "text/plain; charset=utf-8" 65 | }, 66 | statusCode: 200, 67 | body: JSON.stringify(processedLinks) 68 | }); 69 | }).catch(error => { 70 | // 404 71 | if (error && !isNaN(error.status)) { 72 | return callback(null, { 73 | headers: { 74 | "Content-Type": "text/plain; charset=utf-8" 75 | }, 76 | statusCode: 400, 77 | body: "订阅地址网站出现了一个 " + String(error.status) + " 错误。" 78 | }); 79 | } 80 | 81 | // Unknown 82 | return callback(null, { 83 | headers: { 84 | "Content-Type": "text/plain; charset=utf-8" 85 | }, 86 | statusCode: 500, 87 | body: "Unexpected Error.\n" + JSON.stringify(error) 88 | }); 89 | }) 90 | 91 | } 92 | -------------------------------------------------------------------------------- /functions/SSRDecode.js: -------------------------------------------------------------------------------- 1 | const URLSafeBase64 = require('urlsafe-base64'); 2 | 3 | exports.handler = function(event, context, callback) { 4 | const { queryStringParameters } = event; 5 | const encodedStr = queryStringParameters['s']; 6 | if (!URLSafeBase64.validate(encodedStr)) { 7 | return callback(null, { 8 | headers: { 9 | "Content-Type": "text/plain; charset=utf-8" 10 | }, 11 | statusCode: 400, 12 | body: "参数无效" 13 | }) 14 | } 15 | 16 | const decodedStr = URLSafeBase64.decode(encodedStr).toString(); 17 | 18 | let host, port, protocol, method, obfs, base64password, password; 19 | let base64obfsparam, obfsparam, base64protoparam, protoparam, base64remarks, remarks, base64group, group, udpport, uot; 20 | 21 | const requiredParams = decodedStr.split(':'); 22 | if (requiredParams.length != 6) { 23 | return callback(null, { 24 | headers: { 25 | "Content-Type": "text/plain; charset=utf-8" 26 | }, 27 | statusCode: 400, 28 | body: "参数无效" 29 | }) 30 | } 31 | 32 | host = requiredParams[0]; 33 | port = requiredParams[1]; 34 | protocol = requiredParams[2]; 35 | method = requiredParams[3]; 36 | obfs = requiredParams[4]; 37 | 38 | const tempGroup = requiredParams[5].split('/?') 39 | base64password = tempGroup[0]; 40 | password = URLSafeBase64.decode(base64password).toString(); 41 | 42 | if (tempGroup.length > 1) { 43 | const optionalParams = tempGroup[1]; 44 | optionalParams.split('&').forEach(param => { 45 | const temp = param.split('='); 46 | let key = temp[0], value; 47 | if (temp.length > 1) { 48 | value = temp[1]; 49 | } 50 | 51 | if (value) { 52 | switch (key) { 53 | case 'obfsparam': 54 | base64obfsparam = value; 55 | obfsparam = URLSafeBase64.decode(base64obfsparam).toString(); 56 | break; 57 | case 'protoparam': 58 | base64protoparam = value; 59 | protoparam = URLSafeBase64.decode(base64protoparam).toString(); 60 | break; 61 | case 'remarks': 62 | base64remarks = value; 63 | remarks = URLSafeBase64.decode(base64remarks).toString(); 64 | break; 65 | case 'group': 66 | base64group = value; 67 | group = URLSafeBase64.decode(base64group).toString(); 68 | break; 69 | case 'udpport': 70 | udpport = value; 71 | break; 72 | case 'uot': 73 | uot = value; 74 | break; 75 | } 76 | } 77 | }) 78 | } 79 | 80 | result = { 81 | type: 'ss/ssr', 82 | host, port, protocol, method, obfs, base64password, password, 83 | base64obfsparam, obfsparam, base64protoparam, protoparam, base64remarks, remarks, base64group, group, udpport, uot 84 | } 85 | 86 | 87 | 88 | callback(null, { 89 | headers: { 90 | "Content-Type": "text/plain; charset=utf-8" 91 | }, 92 | statusCode: 200, 93 | body: JSON.stringify(result) 94 | }) 95 | } -------------------------------------------------------------------------------- /functions/SSRFilter.js: -------------------------------------------------------------------------------- 1 | const fly = require("flyio"); 2 | const atob = require('atob'); 3 | const btoa = require('btoa'); 4 | const isUrl = require('is-url'); 5 | const ssr = require('./addin/ssr'); 6 | const emoji = require('./addin/emoji'); 7 | 8 | exports.handler = function (event, context, callback) { 9 | const { 10 | queryStringParameters 11 | } = event; 12 | 13 | const url = queryStringParameters['sub']; 14 | 15 | const exclude = queryStringParameters['exclude']; //正则 16 | const include = queryStringParameters['include']; //正则 17 | const preview = queryStringParameters['preview']; //yes则预览,不传不预览 18 | const flag = queryStringParameters['flag']; //是否添加国旗,不传不处理,left:前面加国旗,right:后面加国旗,remove:移除国旗(如果有) 19 | 20 | if (!isUrl(url)) { 21 | return callback(null, { 22 | headers: { 23 | "Content-Type": "text/plain; charset=utf-8" 24 | }, 25 | statusCode: 400, 26 | body: "参数 sub 无效,请检查是否提供了正确的节点订阅地址。" 27 | }); 28 | } 29 | 30 | fly.get(url).then(response => { 31 | try { 32 | const bodyDecoded = atob(response.data); 33 | const links = bodyDecoded.split('\n'); 34 | //#region 支持协议过滤 35 | const filteredLinks = links.filter(link => { 36 | // Only support ssr now 37 | if (link.startsWith('ss://')) return true; 38 | if (link.startsWith('ssr://')) return true; 39 | return false; 40 | }); 41 | if (filteredLinks.length == 0) { 42 | return callback(null, { 43 | headers: { 44 | "Content-Type": "text/plain; charset=utf-8" 45 | }, 46 | statusCode: 400, 47 | body: "订阅地址中没有节点信息。" 48 | }); 49 | } 50 | //#endregion 51 | 52 | //#region 协议具体内容获取 53 | const ssrInfos = new Array(); 54 | const ssrLinks = new Array(); 55 | filteredLinks.forEach(link => { 56 | var result = ssr.analyseSSR(link); 57 | if (result == null) return true; 58 | //#region 协议根据名称进行过滤 59 | 60 | if (include && include != "" && !new RegExp(include).test(result.remarks)) { 61 | return true; 62 | } 63 | if (exclude && exclude != "" && new RegExp(exclude).test(result.remarks)) { 64 | return true; 65 | } 66 | if (flag) { 67 | result.remarks = emoji.flagProcess(result.remarks, flag); 68 | ssrLinks.push(ssr.getSsrShareLink(result)); 69 | } else { 70 | ssrLinks.push(link); 71 | } 72 | 73 | 74 | //#endregion 75 | 76 | ssrInfos.push(result); 77 | }); 78 | if (ssrInfos.length == 0) { 79 | return callback(null, { 80 | headers: { 81 | "Content-Type": "text/plain; charset=utf-8" 82 | }, 83 | statusCode: 400, 84 | body: "订阅节点全部解析失败" 85 | }); 86 | } 87 | //#endregion 88 | //#region 结果拼接 89 | if (preview) { 90 | return callback(null, { 91 | headers: { 92 | "Content-Type": "text/plain; charset=utf-8" 93 | }, 94 | statusCode: 200, 95 | body: JSON.stringify({ 96 | ssrInfos, 97 | ssrLinks 98 | }) 99 | }); 100 | } else { 101 | return callback(null, { 102 | headers: { 103 | "Content-Type": "text/plain; charset=utf-8" 104 | }, 105 | statusCode: 200, 106 | body: btoa(ssrLinks.join('\n')) 107 | }); 108 | } 109 | //#endregion 110 | } catch (e) { 111 | return callback(null, { 112 | headers: { 113 | "Content-Type": "text/plain; charset=utf-8" 114 | }, 115 | statusCode: 500, 116 | body: "Runtime Error.\n" + JSON.stringify(e) 117 | }); 118 | } 119 | }).catch(error => { 120 | // 404 121 | if (error && !isNaN(error.status)) { 122 | return callback(null, { 123 | headers: { 124 | "Content-Type": "text/plain; charset=utf-8" 125 | }, 126 | statusCode: 400, 127 | body: "订阅地址网站出现了一个 " + String(error.status) + " 错误。" 128 | }); 129 | } 130 | 131 | // Unknown 132 | return callback(null, { 133 | headers: { 134 | "Content-Type": "text/plain; charset=utf-8" 135 | }, 136 | statusCode: 500, 137 | body: "Unexpected Error.\n" + JSON.stringify(error) 138 | }); 139 | }) 140 | 141 | } 142 | -------------------------------------------------------------------------------- /functions/SSRTest.js: -------------------------------------------------------------------------------- 1 | const fly = require("flyio"); 2 | const atob = require('atob'); 3 | const btoa = require('btoa'); 4 | const isUrl = require('is-url'); 5 | const ssr = require('./addin/ssr'); 6 | const emoji = require('./addin/emoji'); 7 | 8 | exports.handler = function (event, context, callback) { 9 | const { 10 | queryStringParameters 11 | } = event; 12 | 13 | const url = queryStringParameters['sub']; 14 | 15 | const remove = queryStringParameters['remove']; //正则 16 | const filter = queryStringParameters['filter']; //正则 17 | const preview = queryStringParameters['preview']; //yes则预览,不传不预览 18 | const flag = queryStringParameters['flag']; //是否添加国旗,不传不处理,left:前面加国旗,right:后面加国旗,remove:移除国旗(如果有) 19 | 20 | if (!isUrl(url)) { 21 | return callback(null, { 22 | headers: { 23 | "Content-Type": "text/plain; charset=utf-8" 24 | }, 25 | statusCode: 400, 26 | body: "参数 sub 无效,请检查是否提供了正确的节点订阅地址。" 27 | }); 28 | } 29 | 30 | fly.get(url).then(response => { 31 | try { 32 | const bodyDecoded = atob(response.data); 33 | const links = bodyDecoded.split('\n'); 34 | //#region 支持协议过滤 35 | const filteredLinks = links.filter(link => { 36 | // Only support ssr now 37 | if (link.startsWith('ss://')) return true; 38 | if (link.startsWith('ssr://')) return true; 39 | return false; 40 | }); 41 | if (filteredLinks.length == 0) { 42 | return callback(null, { 43 | headers: { 44 | "Content-Type": "text/plain; charset=utf-8" 45 | }, 46 | statusCode: 400, 47 | body: "订阅地址中没有节点信息。" 48 | }); 49 | } 50 | //#endregion 51 | 52 | //#region 协议具体内容获取 53 | const ssrInfos = new Array(); 54 | const ssrLinks = new Array(); 55 | for (var linkIndex = 0; linkIndex < filteredLinks.length; linkIndex++) { 56 | var link = filteredLinks[linkIndex]; 57 | var result = ssr.analyseSSR(link); 58 | if (result == null) continue; 59 | //#region 协议根据名称进行过滤 60 | 61 | if (filter && filter != "" && !new RegExp(filter).test(result.remarks)) continue; 62 | if (remove && remove != "" && new RegExp(remove).test(result.remarks)) continue; 63 | if (flag) { 64 | result.remarks = emoji.flagProcess(result.remarks, flag); 65 | } 66 | ssrLinks.push(link); 67 | ssrLinks.push(ssr.getSsrShareLink(result)); 68 | ssrLinks.push('----------------------'); 69 | 70 | //#endregion 71 | ssrInfos.push(result); 72 | }; 73 | if (ssrInfos.length == 0) { 74 | return callback(null, { 75 | headers: { 76 | "Content-Type": "text/plain; charset=utf-8" 77 | }, 78 | statusCode: 400, 79 | body: "订阅节点全部解析失败" 80 | }); 81 | } 82 | //#endregion 83 | //#region 结果拼接 84 | if (preview) { 85 | return callback(null, { 86 | headers: { 87 | "Content-Type": "text/plain; charset=utf-8" 88 | }, 89 | statusCode: 200, 90 | body: JSON.stringify({ 91 | ssrLinks, 92 | ssrInfos 93 | }) 94 | }); 95 | } else { 96 | return callback(null, { 97 | headers: { 98 | "Content-Type": "text/plain; charset=utf-8" 99 | }, 100 | statusCode: 200, 101 | body: btoa(ssrLinks.join('\n')) 102 | }); 103 | } 104 | //#endregion 105 | } catch (e) { 106 | return callback(null, { 107 | headers: { 108 | "Content-Type": "text/plain; charset=utf-8" 109 | }, 110 | statusCode: 500, 111 | body: "Runtime Error.\n" + JSON.stringify(e) 112 | }); 113 | } 114 | }).catch(error => { 115 | // 404 116 | if (error && !isNaN(error.status)) { 117 | return callback(null, { 118 | headers: { 119 | "Content-Type": "text/plain; charset=utf-8" 120 | }, 121 | statusCode: 400, 122 | body: "订阅地址网站出现了一个 " + String(error.status) + " 错误。" 123 | }); 124 | } 125 | 126 | // Unknown 127 | return callback(null, { 128 | headers: { 129 | "Content-Type": "text/plain; charset=utf-8" 130 | }, 131 | statusCode: 500, 132 | body: "Unexpected Error.\n" + JSON.stringify(error) 133 | }); 134 | }) 135 | 136 | } 137 | -------------------------------------------------------------------------------- /functions/SurgeProfile2SurgeList.js: -------------------------------------------------------------------------------- 1 | const request = require('flyio'); 2 | const isUrl = require('is-url'); 3 | const Surge = require('./ds/Surge'); 4 | 5 | exports.handler = function (event, context, callback) { 6 | const { queryStringParameters } = event; 7 | const url = queryStringParameters['src']; 8 | const preset = queryStringParameters['preset']; 9 | const filter = queryStringParameters['filter']; 10 | const filterURL = queryStringParameters['filter_url']; 11 | 12 | console.log('url: ', url); 13 | 14 | if (!isUrl(url)) { 15 | console.log('URL is invlid'); 16 | return callback(null, { 17 | headers: { 18 | "Content-Type": "text/plain; charset=utf-8" 19 | }, 20 | statusCode: 400, 21 | body: "参数 src 无效,请检查是否提供了正确的 Surge Profile 托管地址。" 22 | }); 23 | } 24 | 25 | request.get(url).then(({ data }) => { 26 | console.log('Profile fetched success.'); 27 | const surge = new Surge(data); 28 | console.log('Build Surge object success.'); 29 | let result; 30 | 31 | if (preset) { 32 | result = surge.preset(preset); 33 | } else { 34 | result = surge.list(); 35 | if (filter) { 36 | result = result.filter(filter); 37 | } 38 | if (filterURL) { 39 | result = result.filterURL(filterURL); 40 | } 41 | result = result.generate(); 42 | } 43 | 44 | return callback(null, { 45 | headers: { 46 | "Content-Type": "text/plain; charset=utf-8" 47 | }, 48 | statusCode: 200, 49 | body: result 50 | }); 51 | }).catch(err => { 52 | return callback(null, { 53 | headers: { 54 | "Content-Type": "text/plain; charset=utf-8" 55 | }, 56 | statusCode: 400, 57 | body: err 58 | }); 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /functions/TestGetEventInfo.js: -------------------------------------------------------------------------------- 1 | exports.handler = function (event, context, callback) { 2 | const env = process.env; 3 | 4 | return callback(null, { 5 | headers: { 6 | "Content-Type": "text/plain; charset=utf-8" 7 | }, 8 | statusCode: 200, 9 | body: JSON.stringify({ event, context, env }) 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /functions/addin/emoji.js: -------------------------------------------------------------------------------- 1 | let nationalFlag = [ 2 | [ 3 | "🕹", 4 | "游戏", 5 | "game" 6 | ], 7 | [ 8 | "🏳️🌈", 9 | "剩余流量", 10 | "到期时间", 11 | "剩余" 12 | ], 13 | [ 14 | "🇦🇨", 15 | "AC" 16 | ], 17 | [ 18 | "🇦🇩", 19 | "AD", 20 | "安道尔" 21 | ], 22 | [ 23 | "🇦🇪", 24 | "AE" 25 | ], 26 | [ 27 | "🇦🇫", 28 | "AF" 29 | ], 30 | [ 31 | "🇦🇮", 32 | "AI" 33 | ], 34 | [ 35 | "🇦🇱", 36 | "AL" 37 | ], 38 | [ 39 | "🇦🇲", 40 | "AM" 41 | ], 42 | [ 43 | "🇦🇶", 44 | "AQ" 45 | ], 46 | [ 47 | "🇦🇷", 48 | "AR", 49 | "阿根廷" 50 | ], 51 | [ 52 | "🇦🇸", 53 | "AS", 54 | "美属萨摩亚" 55 | ], 56 | [ 57 | "🇦🇹", 58 | "AT", 59 | "奥地利", 60 | "维也纳" 61 | ], 62 | [ 63 | "🇦🇺", 64 | "AU", 65 | "澳大利亚", 66 | "悉尼" 67 | ], 68 | [ 69 | "🇦🇼", 70 | "AW" 71 | ], 72 | [ 73 | "🇦🇽", 74 | "AX" 75 | ], 76 | [ 77 | "🇦🇿", 78 | "AZ" 79 | ], 80 | [ 81 | "🇧🇧", 82 | "BB" 83 | ], 84 | [ 85 | "🇧🇩", 86 | "BD" 87 | ], 88 | [ 89 | "🇧🇪", 90 | "BE" 91 | ], 92 | [ 93 | "🇧🇫", 94 | "BF" 95 | ], 96 | [ 97 | "🇧🇬", 98 | "BG" 99 | ], 100 | [ 101 | "🇧🇭", 102 | "BH" 103 | ], 104 | [ 105 | "🇧🇮", 106 | "BI" 107 | ], 108 | [ 109 | "🇧🇯", 110 | "BJ" 111 | ], 112 | [ 113 | "🇧🇲", 114 | "BM" 115 | ], 116 | [ 117 | "🇧🇳", 118 | "BN" 119 | ], 120 | [ 121 | "🇧🇴", 122 | "BO" 123 | ], 124 | [ 125 | "🇧🇷", 126 | "BR", 127 | "巴西", 128 | "圣保罗" 129 | ], 130 | [ 131 | "🇧🇸", 132 | "BS" 133 | ], 134 | [ 135 | "🇧🇹", 136 | "BT" 137 | ], 138 | [ 139 | "🇧🇻", 140 | "BV" 141 | ], 142 | [ 143 | "🇧🇼", 144 | "BW" 145 | ], 146 | [ 147 | "🇧🇾", 148 | "BY" 149 | ], 150 | [ 151 | "🇧🇿", 152 | "BZ" 153 | ], 154 | [ 155 | "🇨🇦", 156 | "CA", 157 | "加拿大", 158 | "蒙特利尔" 159 | ], 160 | [ 161 | "🇨🇫", 162 | "CF" 163 | ], 164 | [ 165 | "🇨🇭", 166 | "CH" 167 | ], 168 | [ 169 | "🇨🇰", 170 | "CK" 171 | ], 172 | [ 173 | "🇨🇱", 174 | "CL" 175 | ], 176 | [ 177 | "🇨🇲", 178 | "CM" 179 | ], 180 | [ 181 | "🇨🇳", 182 | "CN", 183 | "中国", 184 | "上海", 185 | "徐州", 186 | "宁波", 187 | "深港", 188 | "宁波", 189 | "深圳", 190 | "台湾", 191 | "台北" 192 | ], 193 | [ 194 | "🇨🇴", 195 | "CO" 196 | ], 197 | [ 198 | "🇨🇵", 199 | "CP" 200 | ], 201 | [ 202 | "🇨🇷", 203 | "CR" 204 | ], 205 | [ 206 | "🇨🇺", 207 | "CU" 208 | ], 209 | [ 210 | "🇨🇻", 211 | "CV" 212 | ], 213 | [ 214 | "🇨🇼", 215 | "CW" 216 | ], 217 | [ 218 | "🇨🇽", 219 | "CX" 220 | ], 221 | [ 222 | "🇨🇾", 223 | "CY" 224 | ], 225 | [ 226 | "🇨🇿", 227 | "CZ" 228 | ], 229 | [ 230 | "🇩🇪", 231 | "DE", 232 | "德国", 233 | "法兰克福" 234 | ], 235 | [ 236 | "🇩🇬", 237 | "DG" 238 | ], 239 | [ 240 | "🇩🇯", 241 | "DJ" 242 | ], 243 | [ 244 | "🇩🇰", 245 | "DK" 246 | ], 247 | [ 248 | "🇩🇲", 249 | "DM" 250 | ], 251 | [ 252 | "🇩🇴", 253 | "DO" 254 | ], 255 | [ 256 | "🇩🇿", 257 | "DZ" 258 | ], 259 | [ 260 | "🇪🇦", 261 | "EA" 262 | ], 263 | [ 264 | "🇪🇨", 265 | "EC" 266 | ], 267 | [ 268 | "🇪🇪", 269 | "EE" 270 | ], 271 | [ 272 | "🇪🇬", 273 | "EG" 274 | ], 275 | [ 276 | "🇪🇭", 277 | "EH" 278 | ], 279 | [ 280 | "🇪🇷", 281 | "ER" 282 | ], 283 | [ 284 | "🇪🇸", 285 | "ES" 286 | ], 287 | [ 288 | "🇪🇹", 289 | "ET" 290 | ], 291 | [ 292 | "🇪🇺", 293 | "EU" 294 | ], 295 | [ 296 | "🇫🇮", 297 | "芬兰", 298 | "FI" 299 | ], 300 | [ 301 | "🇫🇯", 302 | "FJ" 303 | ], 304 | [ 305 | "🇫🇰", 306 | "FK" 307 | ], 308 | [ 309 | "🇫🇲", 310 | "FM" 311 | ], 312 | [ 313 | "🇫🇴", 314 | "FO" 315 | ], 316 | [ 317 | "🇫🇷", 318 | "FR", 319 | "兰斯", 320 | "法国", 321 | "巴黎", 322 | "Paris", 323 | "LCY-法国" 324 | ], 325 | [ 326 | "🇬🇦", 327 | "GA" 328 | ], 329 | [ 330 | "🇬🇧", 331 | "GB", 332 | "英国", 333 | "伦敦", 334 | "LCY-英国" 335 | ], 336 | [ 337 | "🇬🇩", 338 | "GD" 339 | ], 340 | [ 341 | "🇬🇪", 342 | "GE" 343 | ], 344 | [ 345 | "🇬🇫", 346 | "GF" 347 | ], 348 | [ 349 | "🇬🇬", 350 | "GG" 351 | ], 352 | [ 353 | "🇬🇭", 354 | "GH" 355 | ], 356 | [ 357 | "🇬🇮", 358 | "GI" 359 | ], 360 | [ 361 | "🇬🇱", 362 | "GL" 363 | ], 364 | [ 365 | "🇬🇲", 366 | "GM" 367 | ], 368 | [ 369 | "🇬🇳", 370 | "GN" 371 | ], 372 | [ 373 | "🇬🇵", 374 | "GP" 375 | ], 376 | [ 377 | "🇬🇶", 378 | "GQ" 379 | ], 380 | [ 381 | "🇬🇷", 382 | "GR" 383 | ], 384 | [ 385 | "🇬🇹", 386 | "GT" 387 | ], 388 | [ 389 | "🇬🇺", 390 | "GU" 391 | ], 392 | [ 393 | "🇬🇼", 394 | "GW" 395 | ], 396 | [ 397 | "🇬🇾", 398 | "GY" 399 | ], 400 | [ 401 | "🇭🇰", 402 | "HK", 403 | "香港", 404 | "LCY-香港" 405 | ], 406 | [ 407 | "🇭🇲", 408 | "HM" 409 | ], 410 | [ 411 | "🇭🇳", 412 | "HN" 413 | ], 414 | [ 415 | "🇭🇷", 416 | "HR" 417 | ], 418 | [ 419 | "🇭🇹", 420 | "HT" 421 | ], 422 | [ 423 | "🇭🇺", 424 | "HU" 425 | ], 426 | [ 427 | "🇮🇨", 428 | "IC" 429 | ], 430 | [ 431 | "🇮🇩", 432 | "ID", 433 | "雅加达" 434 | ], 435 | [ 436 | "🇮🇪", 437 | "IE", 438 | "爱尔兰", 439 | "都柏林" 440 | ], 441 | [ 442 | "🇮🇱", 443 | "IL" 444 | ], 445 | [ 446 | "🇮🇲", 447 | "IM" 448 | ], 449 | [ 450 | "🇮🇳", 451 | "IN", 452 | "印度", 453 | "孟买", 454 | "Mumbai" 455 | ], 456 | [ 457 | "🇮🇴", 458 | "IO" 459 | ], 460 | [ 461 | "🇮🇶", 462 | "IQ" 463 | ], 464 | [ 465 | "🇮🇷", 466 | "IR" 467 | ], 468 | [ 469 | "🇮🇸", 470 | "IS" 471 | ], 472 | [ 473 | "🇮🇹", 474 | "IT" 475 | ], 476 | [ 477 | "🇯🇪", 478 | "JE" 479 | ], 480 | [ 481 | "🇯🇲", 482 | "JM" 483 | ], 484 | [ 485 | "🇯🇴", 486 | "JO" 487 | ], 488 | [ 489 | "🇯🇵", 490 | "JP", 491 | "日本", 492 | "东京", 493 | "白河", 494 | "LCY-日本", 495 | "沪日" 496 | ], 497 | [ 498 | "🇰🇪", 499 | "KE" 500 | ], 501 | [ 502 | "🇰🇬", 503 | "KG" 504 | ], 505 | [ 506 | "🇰🇭", 507 | "KH" 508 | ], 509 | [ 510 | "🇰🇮", 511 | "KI" 512 | ], 513 | [ 514 | "🇰🇲", 515 | "KM" 516 | ], 517 | [ 518 | "🇰🇵", 519 | "KP" 520 | ], 521 | [ 522 | "🇰🇷", 523 | "KR", 524 | "韩国", 525 | "首尔", 526 | "LCY-腾讯云韩国" 527 | ], 528 | [ 529 | "🇰🇼", 530 | "KW" 531 | ], 532 | [ 533 | "🇰🇾", 534 | "KY" 535 | ], 536 | [ 537 | "🇰🇿", 538 | "KZ" 539 | ], 540 | [ 541 | "🇱🇦", 542 | "LA" 543 | ], 544 | [ 545 | "🇱🇧", 546 | "LB" 547 | ], 548 | [ 549 | "🇱🇮", 550 | "LI" 551 | ], 552 | [ 553 | "🇱🇰", 554 | "LK" 555 | ], 556 | [ 557 | "🇱🇷", 558 | "LR" 559 | ], 560 | [ 561 | "🇱🇸", 562 | "LS" 563 | ], 564 | [ 565 | "🇱🇹", 566 | "LT" 567 | ], 568 | [ 569 | "🇱🇺", 570 | "LU" 571 | ], 572 | [ 573 | "🇱🇻", 574 | "LV" 575 | ], 576 | [ 577 | "🇱🇾", 578 | "LY" 579 | ], 580 | [ 581 | "🇲🇦", 582 | "MA" 583 | ], 584 | [ 585 | "🇲🇨", 586 | "MC" 587 | ], 588 | [ 589 | "🇲🇩", 590 | "MD" 591 | ], 592 | [ 593 | "🇲🇪", 594 | "ME" 595 | ], 596 | [ 597 | "🇲🇫", 598 | "MF" 599 | ], 600 | [ 601 | "🇲🇬", 602 | "MG" 603 | ], 604 | [ 605 | "🇲🇭", 606 | "MH" 607 | ], 608 | [ 609 | "🇲🇰", 610 | "MK" 611 | ], 612 | [ 613 | "🇲🇱", 614 | "ML" 615 | ], 616 | [ 617 | "🇲🇲", 618 | "MM" 619 | ], 620 | [ 621 | "🇲🇳", 622 | "MN" 623 | ], 624 | [ 625 | "🇲🇴", 626 | "MO", 627 | "澳门" 628 | ], 629 | [ 630 | "🇲🇵", 631 | "MP" 632 | ], 633 | [ 634 | "🇲🇶", 635 | "MQ" 636 | ], 637 | [ 638 | "🇲🇷", 639 | "MR" 640 | ], 641 | [ 642 | "🇲🇸", 643 | "MS" 644 | ], 645 | [ 646 | "🇲🇹", 647 | "MT" 648 | ], 649 | [ 650 | "🇲🇺", 651 | "MU" 652 | ], 653 | [ 654 | "🇲🇻", 655 | "MV" 656 | ], 657 | [ 658 | "🇲🇼", 659 | "MW" 660 | ], 661 | [ 662 | "🇲🇽", 663 | "MX" 664 | ], 665 | [ 666 | "🇲🇾", 667 | "MY" 668 | ], 669 | [ 670 | "🇲🇿", 671 | "MZ" 672 | ], 673 | [ 674 | "🇳🇦", 675 | "NA" 676 | ], 677 | [ 678 | "🇳🇨", 679 | "NC" 680 | ], 681 | [ 682 | "🇳🇪", 683 | "NE" 684 | ], 685 | [ 686 | "🇳🇫", 687 | "NF" 688 | ], 689 | [ 690 | "🇳🇬", 691 | "NG" 692 | ], 693 | [ 694 | "🇳🇮", 695 | "NI" 696 | ], 697 | [ 698 | "🇳🇱", 699 | "NL", 700 | "荷兰" 701 | ], 702 | [ 703 | "🇳🇴", 704 | "NO" 705 | ], 706 | [ 707 | "🇳🇵", 708 | "NP" 709 | ], 710 | [ 711 | "🇳🇷", 712 | "NR" 713 | ], 714 | [ 715 | "🇳🇺", 716 | "NU" 717 | ], 718 | [ 719 | "🇳🇿", 720 | "NZ" 721 | ], 722 | [ 723 | "🇴🇲", 724 | "OM" 725 | ], 726 | [ 727 | "🇵🇦", 728 | "PA" 729 | ], 730 | [ 731 | "🇵🇪", 732 | "PE" 733 | ], 734 | [ 735 | "🇵🇫", 736 | "PF" 737 | ], 738 | [ 739 | "🇵🇬", 740 | "PG" 741 | ], 742 | [ 743 | "🇵🇭", 744 | "PH", 745 | "菲律宾", 746 | "LCY-菲律宾" 747 | ], 748 | [ 749 | "🇵🇰", 750 | "PK" 751 | ], 752 | [ 753 | "🇵🇱", 754 | "PL" 755 | ], 756 | [ 757 | "🇵🇷", 758 | "PR" 759 | ], 760 | [ 761 | "🇵🇹", 762 | "PT" 763 | ], 764 | [ 765 | "🇵🇼", 766 | "PW" 767 | ], 768 | [ 769 | "🇵🇾", 770 | "PY" 771 | ], 772 | [ 773 | "🇶🇦", 774 | "QA" 775 | ], 776 | [ 777 | "🇷🇪", 778 | "RE" 779 | ], 780 | [ 781 | "🇷🇴", 782 | "RO", 783 | "罗马尼亚", 784 | "Nitro" 785 | ], 786 | [ 787 | "🇷🇸", 788 | "RS" 789 | ], 790 | [ 791 | "🇷🇺", 792 | "RU", 793 | "俄罗斯", 794 | "莫斯科", 795 | "新西伯利亚", 796 | "LCY-新西伯利亚", 797 | "西伯利亚", 798 | "伯力" 799 | ], 800 | [ 801 | "🇷🇼", 802 | "RW" 803 | ], 804 | [ 805 | "🇸🇦", 806 | "SA" 807 | ], 808 | [ 809 | "🇸🇧", 810 | "SB" 811 | ], 812 | [ 813 | "🇸🇨", 814 | "SC" 815 | ], 816 | [ 817 | "🇸🇩", 818 | "SD" 819 | ], 820 | [ 821 | "🇸🇪", 822 | "SE" 823 | ], 824 | [ 825 | "🇸🇬", 826 | "SG", 827 | "新加坡", 828 | "LCY-新加坡" 829 | ], 830 | [ 831 | "🇸🇭", 832 | "SH" 833 | ], 834 | [ 835 | "🇸🇮", 836 | "SI" 837 | ], 838 | [ 839 | "🇸🇯", 840 | "SJ" 841 | ], 842 | [ 843 | "🇸🇰", 844 | "SK" 845 | ], 846 | [ 847 | "🇸🇱", 848 | "SL" 849 | ], 850 | [ 851 | "🇸🇲", 852 | "SM" 853 | ], 854 | [ 855 | "🇸🇳", 856 | "SN" 857 | ], 858 | [ 859 | "🇸🇴", 860 | "SO" 861 | ], 862 | [ 863 | "🇸🇻", 864 | "SV" 865 | ], 866 | [ 867 | "🇸🇽", 868 | "SX" 869 | ], 870 | [ 871 | "🇸🇾", 872 | "SY" 873 | ], 874 | [ 875 | "🇸🇿", 876 | "SZ" 877 | ], 878 | [ 879 | "🇹🇦", 880 | "TA" 881 | ], 882 | [ 883 | "🇹🇩", 884 | "TD" 885 | ], 886 | [ 887 | "🇹🇫", 888 | "TF" 889 | ], 890 | [ 891 | "🇹🇬", 892 | "TG" 893 | ], 894 | [ 895 | "🇹🇭", 896 | "泰国", 897 | "TH" 898 | ], 899 | [ 900 | "🇹🇯", 901 | "TJ" 902 | ], 903 | [ 904 | "🇹🇰", 905 | "TK" 906 | ], 907 | [ 908 | "🇹🇱", 909 | "TL" 910 | ], 911 | [ 912 | "🇹🇲", 913 | "TM" 914 | ], 915 | [ 916 | "🇹🇳", 917 | "TN" 918 | ], 919 | [ 920 | "🇹🇴", 921 | "TO" 922 | ], 923 | [ 924 | "🇹🇷", 925 | "TR", 926 | "土耳其", 927 | "伊斯坦布尔", 928 | "Istanbul" 929 | ], 930 | [ 931 | "🇹🇻", 932 | "TV" 933 | ], 934 | [ 935 | "🇨🇳", 936 | "TW", 937 | "台湾" 938 | ], 939 | [ 940 | "🇹🇿", 941 | "TZ" 942 | ], 943 | [ 944 | "🇺🇦", 945 | "UA" 946 | ], 947 | [ 948 | "🇺🇬", 949 | "UG" 950 | ], 951 | [ 952 | "🇬🇧", 953 | "UK", 954 | "英国" 955 | ], 956 | [ 957 | "🇺🇲", 958 | "UM" 959 | ], 960 | [ 961 | "🇺🇸", 962 | "US", 963 | "美国", 964 | "芝加哥", 965 | "洛杉矶", 966 | "圣克拉拉", 967 | "俄勒冈", 968 | "LCY-美国", 969 | "LCY-洛杉矶", 970 | "拉斯维加斯", 971 | "波特兰", 972 | "Polaris" 973 | ], 974 | [ 975 | "🇺🇾", 976 | "UY" 977 | ], 978 | [ 979 | "🇺🇿", 980 | "UZ" 981 | ], 982 | [ 983 | "🇻🇦", 984 | "VA" 985 | ], 986 | [ 987 | "🇻🇪", 988 | "VE" 989 | ], 990 | [ 991 | "🇻🇬", 992 | "VG" 993 | ], 994 | [ 995 | "🇻🇮", 996 | "VI" 997 | ], 998 | [ 999 | "🇻🇳", 1000 | "越南", 1001 | "VN" 1002 | ], 1003 | [ 1004 | "🇻🇺", 1005 | "VU" 1006 | ], 1007 | [ 1008 | "🇼🇸", 1009 | "WS" 1010 | ], 1011 | [ 1012 | "🇽🇰", 1013 | "XK" 1014 | ], 1015 | [ 1016 | "🇾🇪", 1017 | "YE" 1018 | ], 1019 | [ 1020 | "🇾🇹", 1021 | "YT" 1022 | ], 1023 | [ 1024 | "🇿🇦", 1025 | "ZA" 1026 | ], 1027 | [ 1028 | "🇿🇲", 1029 | "ZM" 1030 | ], 1031 | [ 1032 | "🇿🇼", 1033 | "ZW" 1034 | ] 1035 | ]; 1036 | let getFlag = name => { 1037 | var result = ""; 1038 | for (var x = 0; x < nationalFlag.length; x++) { 1039 | for (var y = 0; y < nationalFlag[x].length; y++) { 1040 | if (name.indexOf(nationalFlag[x][y]) >= 0) { 1041 | return nationalFlag[x][0]; 1042 | } 1043 | } 1044 | } 1045 | return result; 1046 | } 1047 | let removeFlag = name => { 1048 | for (var x = 0; x < nationalFlag.length; x++) { 1049 | if (name.indexOf(nationalFlag[x][0]) >= 0) { 1050 | name = name.replace(nationalFlag[x][0], ''); 1051 | break; 1052 | } 1053 | } 1054 | return name; 1055 | } 1056 | 1057 | let flagProcess = (name, type) => { 1058 | if (!name) return ""; 1059 | if (type == 'left') { 1060 | name = getFlag(name) + name; 1061 | } else if (type == 'right') { 1062 | name += getFlag(name); 1063 | } else if (type == 'remove') { 1064 | name = removeFlag(name); 1065 | } 1066 | return name; 1067 | } 1068 | 1069 | module.exports = { 1070 | getFlag, 1071 | removeFlag, 1072 | flagProcess, 1073 | } 1074 | -------------------------------------------------------------------------------- /functions/addin/ssr.js: -------------------------------------------------------------------------------- 1 | const URLSafeBase64 = require('urlsafe-base64'); 2 | const btoa = require('btoa'); 3 | let analyseSSR = ssrLink => { 4 | if (!ssrLink) return null; 5 | if (ssrLink.startsWith('ss://')) { 6 | return ssProcess(ssrLink); 7 | } else if (ssrLink.startsWith('ssr://')) { 8 | return ssrProcess(ssrLink); 9 | } else { 10 | return null; 11 | } 12 | } 13 | 14 | let ssrProcess = ssrLink => { 15 | let host, port, protocol, method, obfs, base64password, password; 16 | let base64obfsparam, obfsparam, base64protoparam, protoparam, base64remarks, remarks, base64group, group, udpport, uot; 17 | const encodedStr = ssrLink.startsWith('ssr://') ? ssrLink.replace(/ssr:\/\//, "") : ssrLink; 18 | const decodedStr = URLSafeBase64.decode(encodedStr).toString(); 19 | const requiredParams = decodedStr.split(':'); 20 | if (requiredParams.length != 6) { 21 | return null; 22 | } 23 | host = requiredParams[0]; 24 | port = requiredParams[1]; 25 | protocol = requiredParams[2]; 26 | method = requiredParams[3]; 27 | obfs = requiredParams[4]; 28 | const tempGroup = requiredParams[5].split('/?') 29 | base64password = tempGroup[0]; 30 | password = URLSafeBase64.decode(base64password).toString(); 31 | 32 | if (tempGroup.length > 1) { 33 | const optionalParams = tempGroup[1]; 34 | optionalParams.split('&').forEach(param => { 35 | const temp = param.split('='); 36 | let key = temp[0], 37 | value; 38 | if (temp.length > 1) { 39 | value = temp[1]; 40 | } 41 | 42 | if (value) { 43 | switch (key) { 44 | case 'obfsparam': 45 | base64obfsparam = value; 46 | obfsparam = URLSafeBase64.decode(base64obfsparam).toString(); 47 | break; 48 | case 'protoparam': 49 | base64protoparam = value; 50 | protoparam = URLSafeBase64.decode(base64protoparam).toString(); 51 | break; 52 | case 'remarks': 53 | base64remarks = value; 54 | remarks = URLSafeBase64.decode(base64remarks).toString(); 55 | break; 56 | case 'group': 57 | base64group = value; 58 | group = URLSafeBase64.decode(base64group).toString(); 59 | break; 60 | case 'udpport': 61 | udpport = value; 62 | break; 63 | case 'uot': 64 | uot = value; 65 | break; 66 | } 67 | } 68 | }) 69 | } 70 | 71 | return { 72 | type: 'ssr', 73 | host, 74 | port, 75 | protocol, 76 | method, 77 | obfs, 78 | base64password, 79 | password, 80 | base64obfsparam, 81 | obfsparam, 82 | base64protoparam, 83 | protoparam, 84 | base64remarks, 85 | remarks, 86 | base64group, 87 | group, 88 | udpport, 89 | uot 90 | } 91 | } 92 | let ssProcess = ssLink => { 93 | let host, port, protocol, method, remarks; 94 | 95 | const encodedStr = ssLink.startsWith('ss://') ? ssLink.replace(/ss:\/\//, "") : ssLink; 96 | let regexMatch = encodedStr.match(/#(.*?)$/); 97 | if (regexMatch != null) { 98 | remarks = decodeURIComponent(regexMatch[1]); 99 | } 100 | const decodedStr = URLSafeBase64.decode(encodedStr.split('#')[0]).toString(); 101 | const requiredParams = decodedStr.split('@'); 102 | if (requiredParams.length < 2) { 103 | return null; 104 | } 105 | var encrypt = requiredParams[0].split(':'); 106 | method = encrypt[0]; 107 | password = encrypt[1]; 108 | var addr = requiredParams[1].split(':'); 109 | host = addr[0]; 110 | port = addr[1]; 111 | return { 112 | type: 'ss', 113 | host, 114 | port, 115 | protocol, 116 | method, 117 | remarks 118 | } 119 | } 120 | 121 | let getSsrShareLink = ssrEntity => { 122 | let ssrLink = "ssr://"; 123 | let decodedStr = `${ssrEntity.host}:${ssrEntity.port}:${ssrEntity.protocol}:${ssrEntity.method}:${ssrEntity.obfs}:${ssrEntity.base64password}/`; 124 | let optionalParams = ""; 125 | if (ssrEntity.base64obfsparam != "" && ssrEntity.base64obfsparam != undefined) { 126 | optionalParams += `${optionalParams==""?"":"&"}obfsparam=${ssrEntity.base64obfsparam}` 127 | } 128 | if (ssrEntity.base64protoparam != "" && ssrEntity.base64protoparam != undefined) { 129 | optionalParams += `${optionalParams==""?"":"&"}protoparam=${ssrEntity.base64protoparam}` 130 | } 131 | if (ssrEntity.remarks) { 132 | optionalParams += `${optionalParams==""?"":"&"}remarks=${urlSafeBase64Encode(unescape(encodeURIComponent(ssrEntity.remarks)))}` 133 | } 134 | if (ssrEntity.base64group) { 135 | optionalParams += `${optionalParams==""?"":"&"}group=${ssrEntity.base64group}` 136 | } 137 | if (ssrEntity.udpport) { 138 | optionalParams += `${optionalParams==""?"":"&"}udpport=${ssrEntity.udpport}` 139 | } 140 | if (ssrEntity.uot) { 141 | optionalParams += `${optionalParams==""?"":"&"}uot=${ssrEntity.uot}` 142 | } 143 | decodedStr += `${optionalParams==""?"":"?"}${optionalParams}`; 144 | return `${ssrLink}${urlSafeBase64Encode(decodedStr)}`; 145 | } 146 | 147 | let urlSafeBase64Encode=ssr=>{ 148 | if (!ssr) return ssr; 149 | let base64=btoa(ssr); 150 | return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, ''); 151 | } 152 | 153 | module.exports = { 154 | analyseSSR, 155 | ssrProcess, 156 | ssProcess, 157 | getSsrShareLink 158 | } 159 | -------------------------------------------------------------------------------- /functions/ds/Surge.js: -------------------------------------------------------------------------------- 1 | const ini = require('ini'); 2 | 3 | class Names { 4 | constructor(names, Proxy) { 5 | this.names = names; 6 | this.Proxy = Proxy; 7 | } 8 | 9 | generate() { 10 | let result = ''; 11 | for (let name of this.names) { 12 | result += `${name} = ${this.Proxy[name]}\n` 13 | } 14 | return result; 15 | } 16 | 17 | filter(keyword) { 18 | const keywords = keyword.split('+'); 19 | const names = this.names.filter(name => { 20 | for (const kw of keywords) { 21 | if (name.indexOf(kw) !== -1) { 22 | return true; 23 | } 24 | } 25 | }) 26 | 27 | return new Names(names, this.Proxy); 28 | } 29 | 30 | filterURL(keyword) { 31 | const keywords = keyword.split('+'); 32 | const names = this.names.filter(name => { 33 | const splitResult = this.Proxy[name].split(','); 34 | const url = splitResult[1].trim(); 35 | 36 | for (const kw of keywords) { 37 | if (url.indexOf(kw) !== -1) { 38 | return true; 39 | } 40 | } 41 | }) 42 | 43 | return new Names(names, this.Proxy); 44 | } 45 | 46 | static join(...exists) { 47 | const names = []; 48 | const set = new Set(); 49 | for (const exist of exists) { 50 | exist.names.map(name => { 51 | if (!set.has(name)) { 52 | names.push(name); 53 | set.add(name); 54 | } 55 | }) 56 | } 57 | 58 | return new Names(names, exists[0].Proxy); 59 | } 60 | } 61 | 62 | class Surge { 63 | constructor(profile) { 64 | const config = ini.parse(profile); 65 | const { Proxy } = config; 66 | // console.log(Proxy); 67 | // console.log(Object.keys(Proxy)); 68 | this.Proxy = Proxy; 69 | } 70 | 71 | list() { 72 | return new Names(Object.keys(this.Proxy), this.Proxy); 73 | } 74 | 75 | preset(presetName) { 76 | if (presetName === 'netflix') { 77 | // default: HK+MO+TW 78 | return this.list().filter('HKT+HKBN+CTM+江苏中转+HINET').generate(); 79 | } else if (presetName === 'netflix_hk') { 80 | return this.list().filter('HKT+HKBN').generate(); 81 | } else if (presetName === 'netflix_mo') { 82 | return this.list().filter('CTM+江苏中转').generate(); 83 | } else if (presetName === 'netflix_tw') { 84 | return this.list().filter('HINET').generate(); 85 | } else if (presetName === 'netflix_jp') { 86 | return this.list().filter('IDCF+软银').generate(); 87 | } else if (presetName === 'netflix_us') { 88 | return this.list().filter('美国').filter('CN2').generate(); 89 | } else if (presetName === 'netflix_iplc') { 90 | const listBase = this.list().filter('IPLC'); 91 | return Names.join(listBase.filter('深港+沪港'), listBase.filterURL('hanggang.001+hanggang.002')).generate(); 92 | } 93 | 94 | return this.list().generate(); 95 | } 96 | 97 | } 98 | 99 | module.exports = Surge; -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |