From b085c0d0c1804092772b46bbc33ce2e77bbbc2a6 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Fri, 31 Jan 2020 19:30:04 +0800 Subject: [PATCH] Add support for exporting complete Quantumult configuration file Fix incorrect generation of Quantumult X configuratios. Fix broken group name option. Add external configuration support for customize Quantumult(X) base. Update Travis CI build script. Tweak Emoji match rules. Clean up codes. --- .travis.yml | 19 +-- base/config/example_external_config.ini | 2 + base/pref.ini | 7 +- base/quan.conf | 27 ++++ src/main.cpp | 51 +++++--- src/nodemanip.cpp | 4 +- src/subexport.cpp | 164 +++++++++++++++++++++--- src/subexport.h | 3 +- 8 files changed, 226 insertions(+), 51 deletions(-) create mode 100644 base/quan.conf diff --git a/.travis.yml b/.travis.yml index 563bb0a..46e0f9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: cpp -sudo: required +os: linux stages: - name: deploy if: branch = master - name: before_script if: branch = master -matrix: +jobs: include: - name: "macOS Build" os: osx @@ -15,9 +15,8 @@ matrix: - bash scripts/build.macos.release.sh deploy: provider: releases - api_key: "$GITHUB_OAUTH_TOKEN" + token: "$GITHUB_OAUTH_TOKEN" file: "subconverter_darwin64.tar.gz" - skip_cleanup: true draft: true on: tags: true @@ -29,9 +28,8 @@ matrix: - mv subconverter_linux64.tar.gz subconverter_linux32.tar.gz deploy: provider: releases - api_key: "$GITHUB_OAUTH_TOKEN" + token: "$GITHUB_OAUTH_TOKEN" file: "subconverter_linux32.tar.gz" - skip_cleanup: true draft: true on: tags: true @@ -41,9 +39,8 @@ matrix: - docker run -v $TRAVIS_BUILD_DIR:/root/workdir alpine:latest /bin/sh -c "apk add bash git && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" deploy: provider: releases - api_key: "$GITHUB_OAUTH_TOKEN" + token: "$GITHUB_OAUTH_TOKEN" file: "subconverter_linux64.tar.gz" - skip_cleanup: true draft: true on: tags: true @@ -56,9 +53,8 @@ matrix: - mv subconverter_linux64.tar.gz subconverter_armhf.tar.gz deploy: provider: releases - api_key: "$GITHUB_OAUTH_TOKEN" + token: "$GITHUB_OAUTH_TOKEN" file: "subconverter_armhf.tar.gz" - skip_cleanup: true draft: true on: tags: true @@ -71,9 +67,8 @@ matrix: - mv subconverter_linux64.tar.gz subconverter_aarch64.tar.gz deploy: provider: releases - api_key: "$GITHUB_OAUTH_TOKEN" + token: "$GITHUB_OAUTH_TOKEN" file: "subconverter_aarch64.tar.gz" - skip_cleanup: true draft: true on: tags: true diff --git a/base/config/example_external_config.ini b/base/config/example_external_config.ini index fe95f4f..d929f01 100644 --- a/base/config/example_external_config.ini +++ b/base/config/example_external_config.ini @@ -40,6 +40,8 @@ clash_rule_base=config/forcerule.yml ;surge_rule_base=surge.conf ;surfboard_rule_base=surfboard.conf ;mellow_rule_base=mellow.conf +;quan_rule_base=quan.conf +;quanx_rule_base=quanx.conf ;Options for renaming nodes ;rename=Test-(.*?)-(.*?)-(.*?)\((.*?)\)@\1\4x测试线路_自\2到\3 diff --git a/base/pref.ini b/base/pref.ini index f061c72..9cfca37 100644 --- a/base/pref.ini +++ b/base/pref.ini @@ -27,6 +27,9 @@ surfboard_rule_base=surfboard.conf ;Mellow config base used by the generator, supports local files/URL mellow_rule_base=mellow.conf +;Quantumult X config base used by the generator, supports local files/URL +quan_rule_base=quan.conf + ;Quantumult X config base used by the generator, supports local files/URL quanx_rule_base=quanx.conf @@ -146,8 +149,8 @@ rule=ES,🇪🇸 rule=EU,🇪🇺 rule=(Finland|芬兰|赫尔辛基),🇫🇮 rule=(FR|France|法国|巴黎),🇫🇷 -rule=(UK|England|UnitedKingdom|英国|英|伦敦),🇬🇧 -rule=(HK|HongKong|香港|深港|沪港|呼港|HKT|HKBN|HGC|WTT|CMI|穗港|京港|港),🇭🇰 +rule=(UK|England|United.*?Kingdom|英国|英|伦敦),🇬🇧 +rule=(?i)(HK|Hong.*?Kong|香港|深港|沪港|呼港|HKT|HKBN|HGC|WTT|CMI|穗港|京港|港),🇭🇰 rule=(Indonesia|印尼|印度尼西亚|雅加达),🇮🇩 rule=(Ireland|爱尔兰|都柏林),🇮🇪 rule=(India|印度|孟买),🇮🇳 diff --git a/base/quan.conf b/base/quan.conf new file mode 100644 index 0000000..fc531f4 --- /dev/null +++ b/base/quan.conf @@ -0,0 +1,27 @@ +[SERVER] + +[SOURCE] + +[BACKUP-SERVER] + +[SUSPEND-SSID] + +[POLICY] + +[DNS] +1.1.1.1 + +[REWRITE] + +[URL-REJECTION] + +[TCP] + +[GLOBAL] + +[HOST] + +[STATE] +STATE,AUTO + +[MITM] diff --git a/src/main.cpp b/src/main.cpp index a663bf9..379bd77 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -40,7 +40,7 @@ std::string proxy_ruleset, proxy_subscription; std::string clash_rule_base; string_array clash_extra_group; -std::string surge_rule_base, surfboard_rule_base, mellow_rule_base, quanx_rule_base; +std::string surge_rule_base, surfboard_rule_base, mellow_rule_base, quan_rule_base, quanx_rule_base; std::string surge_ssr_path; //pre-compiled rule bases @@ -172,6 +172,8 @@ void readConf() surfboard_rule_base = ini.Get("surfboard_rule_base"); if(ini.ItemExist("mellow_rule_base")) mellow_rule_base = ini.Get("mellow_rule_base"); + if(ini.ItemExist("quan_rule_base")) + quan_rule_base = ini.Get("quan_rule_base"); if(ini.ItemExist("quanx_rule_base")) quanx_rule_base = ini.Get("quanx_rule_base"); if(ini.ItemExist("append_proxy_type")) @@ -289,6 +291,8 @@ struct ExternalConfig std::string surge_rule_base; std::string surfboard_rule_base; std::string mellow_rule_base; + std::string quan_rule_base; + std::string quanx_rule_base; string_array rename; string_array emoji; bool overwrite_original_rules = false; @@ -305,7 +309,6 @@ int loadExternalConfig(std::string &path, ExternalConfig &ext, std::string proxy INIReader ini; ini.store_isolated_line = true; - ini.keep_empty_section = false; ini.SetIsolatedItemsSection("custom"); if(ini.Parse(base_content) != INIREADER_EXCEPTION_NONE) { @@ -327,6 +330,10 @@ int loadExternalConfig(std::string &path, ExternalConfig &ext, std::string proxy ext.surfboard_rule_base = ini.Get("surfboard_rule_base"); if(ini.ItemExist("mellow_rule_base")) ext.mellow_rule_base = ini.Get("mellow_rule_base"); + if(ini.ItemExist("quan_rule_base")) + ext.quan_rule_base = ini.Get("quan_rule_base"); + if(ini.ItemExist("quanx_rule_base")) + ext.quanx_rule_base = ini.Get("quanx_rule_base"); if(ini.ItemExist("overwrite_original_rules")) ext.overwrite_original_rules = ini.GetBool("overwrite_original_rules"); @@ -390,6 +397,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) //for external configuration std::string ext_clash_base = clash_rule_base, ext_surge_base = surge_rule_base, ext_mellow_base = mellow_rule_base, ext_surfboard_base = surfboard_rule_base; + std::string ext_quan_base = quan_rule_base, ext_quanx_base = quanx_rule_base; //validate urls if(!url.size()) @@ -434,6 +442,10 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) ext_surfboard_base = extconf.surfboard_rule_base; if(extconf.mellow_rule_base.size()) ext_mellow_base = extconf.mellow_rule_base; + if(extconf.quan_rule_base.size()) + ext_quan_base = extconf.quan_rule_base; + if(extconf.quanx_rule_base.size()) + ext_quanx_base = extconf.quanx_rule_base; if(extconf.rename.size()) ext.rename_array = extconf.rename; if(extconf.emoji.size()) @@ -530,10 +542,6 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) else exclude_remarks = def_exclude_remarks; - //check custom group name - if(group.size()) - custom_group = group; - //start parsing urls string_array stream_temp = safe_get_streams(), time_temp = safe_get_times(); for(std::string &x : urls) @@ -554,6 +562,11 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) return "No nodes were found!"; } + //check custom group name + if(group.size()) + for(nodeInfo &x : nodes) + x.group = group; + if(subInfo.size() && groupID == 1) extra_headers.emplace("Subscription-UserInfo", subInfo); @@ -667,26 +680,34 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) else if(target == "quan") { std::cerr<<"Quantumult"< &allNodes, int groupID, std if(linkType > 0) { explode(link, ss_libev, ssr_libev, override_conf_port, socksport, node); - if(custom_group.size() != 0) - node.group = custom_group; if(node.linkType == -1) { writeLog(LOG_TYPE_ERROR, "No valid link found."); diff --git a/src/subexport.cpp b/src/subexport.cpp index a3b5da0..6a30ff4 100644 --- a/src/subexport.cpp +++ b/src/subexport.cpp @@ -363,6 +363,9 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_ case -1: base_rule.SetCurrentSection("filter_local"); //Quantumult X break; + case -2: + base_rule.SetCurrentSection("TCP"); //Quantumult + break; default: base_rule.SetCurrentSection("Rule"); } @@ -382,7 +385,7 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_ if(strLine == "MATCH") strLine = "FINAL"; strLine += "," + rule_group; - if(surge_ver == -1) + if(surge_ver == -1 || surge_ver == -2) { if(std::count(strLine.begin(), strLine.end(), ',') > 2 && regReplace(strLine, rule_match_regex, "$2") == ",no-resolve") strLine = regReplace(strLine, rule_match_regex, "$1$3$2"); @@ -416,13 +419,13 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_ strStrm< 2 && regReplace(strLine, rule_match_regex, "$2") == ",no-resolve") strLine = regReplace(strLine, rule_match_regex, "$1$3$2"); @@ -1246,7 +1249,29 @@ std::string netchToVMess(std::vector &nodes, extra_settings &ext) return base64_encode(allLinks); } -std::string netchToQuan(std::vector &nodes, extra_settings &ext) +std::string netchToQuan(std::vector &nodes, std::string &base_conf, std::vector &ruleset_content_array, string_array &extra_proxy_group, extra_settings &ext) +{ + INIReader ini; + ini.store_any_line = true; + if(!ext.nodelist && ini.Parse(base_conf) != 0) + return std::string(); + + netchToQuan(nodes, ini, ruleset_content_array, extra_proxy_group, ext); + + if(ext.nodelist) + { + string_array allnodes; + ini.GetAll("SERVER", "{NONAME}", allnodes); + std::string allLinks = std::accumulate(allnodes.begin(), allnodes.end(), allnodes[0], [](std::string a, std::string b) + { + return std::move(a) + "\n" + std::move(b); + }); + return base64_encode(allLinks); + } + return ini.ToString(); +} + +void netchToQuan(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, string_array &extra_proxy_group, extra_settings &ext) { rapidjson::Document json; std::string type; @@ -1256,6 +1281,7 @@ std::string netchToQuan(std::vector &nodes, extra_settings &ext) std::string id, aid, transproto, faketype, host, path, quicsecure, quicsecret; std::string proxyStr, allLinks; bool tlssecure; + std::vector nodelist; std::for_each(nodes.begin(), nodes.end(), [ext](nodeInfo &x) { @@ -1275,6 +1301,8 @@ std::string netchToQuan(std::vector &nodes, extra_settings &ext) }); } + ini.SetCurrentSection("SERVER"); + ini.EraseSection(); for(nodeInfo &x : nodes) { json.Parse(x.proxyStr.data()); @@ -1309,7 +1337,9 @@ std::string netchToQuan(std::vector &nodes, extra_settings &ext) proxyStr += ", certificate=0"; if(transproto == "ws") proxyStr += ", obfs=ws, obfs-path=\"" + path + "\", obfs-header=\"Host: " + host + "\""; - proxyStr = "vmess://" + urlsafe_base64_encode(proxyStr); + + if(ext.nodelist) + proxyStr = "vmess://" + urlsafe_base64_encode(proxyStr); break; case SPEEDTEST_MESSAGE_FOUNDSSR: protocol = GetMember(json, "Protocol"); @@ -1317,27 +1347,121 @@ std::string netchToQuan(std::vector &nodes, extra_settings &ext) obfs = GetMember(json, "OBFS"); obfsparam = GetMember(json, "OBFSParam"); - proxyStr = "ssr://" + urlsafe_base64_encode(hostname + ":" + port + ":" + protocol + ":" + method + ":" + obfs + ":" + urlsafe_base64_encode(password) \ - + "/?group=" + urlsafe_base64_encode(x.group) + "&remarks=" + urlsafe_base64_encode(remark) \ - + "&obfsparam=" + urlsafe_base64_encode(obfsparam) + "&protoparam=" + urlsafe_base64_encode(protoparam)); + if(ext.nodelist) + { + proxyStr = "ssr://" + urlsafe_base64_encode(hostname + ":" + port + ":" + protocol + ":" + method + ":" + obfs + ":" + urlsafe_base64_encode(password) \ + + "/?group=" + urlsafe_base64_encode(x.group) + "&remarks=" + urlsafe_base64_encode(remark) \ + + "&obfsparam=" + urlsafe_base64_encode(obfsparam) + "&protoparam=" + urlsafe_base64_encode(protoparam)); + } + else + { + proxyStr = remark + " = shadowsocksr, " + hostname + ", " + port + ", " + method + ", \"" + password + "\", group=" + x.group + ", protocol=" + protocol + ", obfs=" + obfs; + if(protoparam.size()) + proxyStr += ", protocol_param=" + protoparam; + if(obfsparam.size()) + proxyStr += ", obfs_param=" + obfsparam; + } break; case SPEEDTEST_MESSAGE_FOUNDSS: plugin = GetMember(json, "Plugin"); pluginopts = GetMember(json, "PluginOption"); - proxyStr = "ss://" + urlsafe_base64_encode(method + ":" + password) + "@" + hostname + ":" + port; - if(plugin.size() & pluginopts.size()) + + if(ext.nodelist) { - proxyStr += "/?plugin=" + UrlEncode(plugin + ";" +pluginopts); + proxyStr = "ss://" + urlsafe_base64_encode(method + ":" + password) + "@" + hostname + ":" + port; + if(plugin.size() && pluginopts.size()) + { + proxyStr += "/?plugin=" + UrlEncode(plugin + ";" + pluginopts); + } + proxyStr += "&group=" + urlsafe_base64_encode(x.group) + "#" + UrlEncode(remark); + } + else + { + proxyStr = remark + " = shadowsocks, " + hostname + ", " + port + ", " + method + ", \"" + password + "\", group=" + x.group; + if(plugin == "simple-obfs" && pluginopts.size()) + { + proxyStr += ", " + replace_all_distinct(pluginopts, ";", ", "); + } } - proxyStr += "&group=" + urlsafe_base64_encode(x.group) + "#" + UrlEncode(remark); break; default: continue; } - allLinks += proxyStr + "\n"; + + ini.Set("{NONAME}", proxyStr); + nodelist.emplace_back(x); } - return base64_encode(allLinks); + if(ext.nodelist) + return; + + string_array filtered_nodelist; + ini.SetCurrentSection("POLICY"); + ini.EraseSection(); + + std::string singlegroup; + std::string name, proxies; + string_array vArray; + for(std::string &x : extra_proxy_group) + { + eraseElements(filtered_nodelist); + unsigned int rules_upper_bound = 0; + + vArray = split(x, "`"); + if(vArray.size() < 3) + continue; + + if(vArray[1] == "select") + { + type = "static"; + rules_upper_bound = vArray.size(); + } + else if(vArray[1] == "url-test") + { + if(vArray.size() < 5) + continue; + type = "auto"; + rules_upper_bound = vArray.size() - 2; + } + else if(vArray[1] == "fallback") + { + if(vArray.size() < 5) + continue; + type = "static"; + rules_upper_bound = vArray.size() - 2; + } + else if(vArray[1] == "load-balance") + { + if(vArray.size() < 5) + continue; + type = "balance, round-robin"; + rules_upper_bound = vArray.size() - 2; + } + else + continue; + + name = vArray[0]; + + for(unsigned int i = 2; i < rules_upper_bound; i++) + groupGenerate(vArray[i], nodelist, filtered_nodelist, true); + + if(!filtered_nodelist.size()) + filtered_nodelist.emplace_back("direct"); + + proxies = std::accumulate(std::next(filtered_nodelist.begin()), filtered_nodelist.end(), filtered_nodelist[0], [](std::string a, std::string b) + { + return std::move(a) + "\n" + std::move(b); + }); + + singlegroup = name + " : " + type; + if(type == "static") + singlegroup += ", " + filtered_nodelist[0]; + singlegroup += "\n" + proxies + "\n"; + ini.Set("{NONAME}", base64_encode(singlegroup)); + } + + if(ext.enable_rule_generator) + rulesetToSurge(ini, ruleset_content_array, -2, ext.overwrite_original_rules); } std::string netchToQuanX(std::vector &nodes, std::string &base_conf, std::vector &ruleset_content_array, string_array &extra_proxy_group, extra_settings &ext) @@ -1349,7 +1473,7 @@ std::string netchToQuanX(std::vector &nodes, std::string &base_conf, s netchToQuanX(nodes, ini, ruleset_content_array, extra_proxy_group, ext); - if(!ext.nodelist) + if(ext.nodelist) { string_array allnodes; ini.GetAll("server_local", "{NONAME}", allnodes); @@ -1419,11 +1543,15 @@ void netchToQuanX(std::vector &nodes, INIReader &ini, std::vector &nodes, extra_settings &ext); std::string netchToVMess(std::vector &nodes, extra_settings &ext); std::string netchToQuanX(std::vector &nodes, std::string &base_conf, std::vector &ruleset_content_array, string_array &extra_proxy_group, extra_settings &ext); void netchToQuanX(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, string_array &extra_proxy_group, extra_settings &ext); -std::string netchToQuan(std::vector &nodes, extra_settings &ext); +std::string netchToQuan(std::vector &nodes, std::string &base_conf, std::vector &ruleset_content_array, string_array &extra_proxy_group, extra_settings &ext); +void netchToQuan(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, string_array &extra_proxy_group, extra_settings &ext); std::string netchToSSD(std::vector &nodes, std::string &group, extra_settings &ext); std::string buildGistData(std::string name, std::string content); int uploadGist(std::string name, std::string path, std::string content, bool writeManageURL);