Files
subconverter/main.cpp
Tindy X 53f31b5d3a Enhancements
Fix port generation for Surge ShadowsocksR configuration.
Fix compatibility with some non-standard subscription.
Add some node preference tags.
2019-12-13 17:56:39 +08:00

588 lines
19 KiB
C++

#include <iostream>
#include <string>
#include <mutex>
#include <unistd.h>
#include <yaml-cpp/yaml.h>
#include "socket.h"
#include "misc.h"
#include "nodeinfo.h"
#include "speedtestutil.h"
#include "nodemanip.h"
#include "ini_reader.h"
#include "webget.h"
#include "webserver.h"
#include "subexport.h"
#include "multithread.h"
#include "version.h"
//common settings
std::string pref_path = "pref.ini";
string_array def_exclude_remarks, def_include_remarks, rulesets;
std::vector<ruleset_content> ruleset_content_array;
std::string listen_address = "127.0.0.1", default_url, managed_config_prefix;
int listen_port = 25500, max_pending_connections = 10, max_concurrent_threads = 4;
bool api_mode = true, write_managed_config = false, update_ruleset_on_request = false, overwrite_original_rules = true;
bool print_debug_info = false, cfw_child_process = false;
extern std::string custom_group;
//multi-thread lock
extern std::mutex on_configuring;
//preferences
string_array renames, emojis;
bool add_emoji = false, remove_old_emoji = false, append_proxy_type = true;
bool udp_flag = false, tfo_flag = false, do_sort = false;
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;
std::string surge_ssr_path;
#ifndef _WIN32
void SetConsoleTitle(std::string title)
{
system(std::string("echo \"\\033]0;" + title + "\\007\\c\"").data());
}
#endif // _WIN32
void setcd(char *argv[])
{
std::string path;
char szTmp[1024];
#ifndef _WIN32
path.assign(argv[0]);
if(path[0] != '/')
{
getcwd(szTmp, 1023);
path.assign(szTmp);
path.append("/");
path.append(argv[0]);
}
path = path.substr(0, path.rfind("/") + 1);
#else
GetModuleFileName(NULL, szTmp, 1023);
strrchr(szTmp, '\\')[1] = '\0';
path.assign(szTmp);
#endif // _WIN32
chdir(path.data());
}
std::string refreshRulesets(string_array &ruleset_list, std::vector<ruleset_content> &rca)
{
eraseElements(rca);
std::string rule_group, rule_url;
ruleset_content rc;
std::string proxy;
if(proxy_ruleset == "SYSTEM")
proxy = getSystemProxy();
else if(proxy_ruleset == "NONE")
proxy = "";
else
proxy = proxy_ruleset;
for(std::string &x : ruleset_list)
{
/*
vArray = split(x, ",");
if(vArray.size() != 2)
continue;
rule_group = trim(vArray[0]);
rule_url = trim(vArray[1]);
*/
if(x.find(",") == x.npos)
continue;
rule_group = trim(x.substr(0, x.find(",")));
rule_url = trim(x.substr(x.find(",") + 1));
if(rule_url.find("[]") == 0)
{
std::cerr<<"Adding rule '"<<rule_url.substr(2)<<","<<rule_group<<"'."<<std::endl;
rc = {rule_group, "", rule_url};
rca.emplace_back(rc);
continue;
}
else
{
std::cerr<<"Updating ruleset url '"<<rule_url<<"' with group '"<<rule_group<<"'."<<std::endl;
if(fileExist(rule_url))
{
rc = {rule_group, rule_url, fileGet(rule_url, false)};
}
else if(rule_url.find("http://") == 0 || rule_url.find("https://") == 0)
{
rc = {rule_group, rule_url, webGet(rule_url, proxy)};
}
else
continue;
}
if(rc.rule_content.size())
rca.emplace_back(rc);
else
std::cerr<<"Warning: No data was fetched from this link. Skipping..."<<std::endl;
}
return "done";
}
void readConf()
{
guarded_mutex guard(on_configuring);
std::cerr<<"Reading preference settings..."<<std::endl;
eraseElements(def_exclude_remarks);
eraseElements(def_include_remarks);
eraseElements(clash_extra_group);
eraseElements(rulesets);
string_array emojis_temp, renames_temp;
INIReader ini;
ini.allow_dup_section_titles = true;
//ini.do_utf8_to_gbk = true;
ini.ParseFile(pref_path);
ini.EnterSection("common");
if(ini.ItemExist("api_mode"))
api_mode = ini.GetBool("api_mode");
if(ini.ItemExist("default_url"))
default_url = ini.Get("default_url");
if(ini.ItemPrefixExist("exclude_remarks"))
ini.GetAll("exclude_remarks", def_exclude_remarks);
if(ini.ItemPrefixExist("include_remarks"))
ini.GetAll("include_remarks", def_include_remarks);
if(ini.ItemExist("clash_rule_base"))
clash_rule_base = ini.Get("clash_rule_base");
if(ini.ItemExist("surge_rule_base"))
surge_rule_base = ini.Get("surge_rule_base");
if(ini.ItemExist("surfboard_rule_base"))
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("append_proxy_type"))
append_proxy_type = ini.GetBool("append_proxy_type");
if(ini.ItemExist("proxy_ruleset"))
proxy_ruleset = ini.Get("proxy_ruleset");
if(ini.ItemExist("proxy_subscription"))
proxy_subscription = ini.Get("proxy_subscription");
if(ini.ItemPrefixExist("rename_node"))
{
ini.GetAll("rename_node", renames_temp);
safe_set_renames(renames_temp);
}
if(ini.SectionExist("surge_external_proxy"))
{
ini.EnterSection("surge_external_proxy");
if(ini.ItemExist("surge_ssr_path"))
surge_ssr_path = ini.Get("surge_ssr_path");
}
if(ini.SectionExist("node_pref"))
{
ini.EnterSection("node_pref");
if(ini.ItemExist("udp_flag"))
udp_flag = ini.GetBool("udp_flag");
if(ini.ItemExist("tcp_fast_open_flag"))
tfo_flag = ini.GetBool("tcp_fast_open_flag");
if(ini.ItemExist("sort_flag"))
do_sort = ini.GetBool("sort_flag");
}
ini.EnterSection("managed_config");
if(ini.ItemExist("write_managed_config"))
write_managed_config = ini.GetBool("write_managed_config");
if(ini.ItemExist("managed_config_prefix"))
managed_config_prefix = ini.Get("managed_config_prefix");
ini.EnterSection("emojis");
if(ini.ItemExist("add_emoji"))
add_emoji = ini.GetBool("add_emoji");
if(ini.ItemExist("remove_old_emoji"))
remove_old_emoji = ini.GetBool("remove_old_emoji");
if(ini.ItemPrefixExist("rule"))
{
ini.GetAll("rule", emojis_temp);
safe_set_emojis(emojis_temp);
}
ini.EnterSection("ruleset");
if(ini.GetBool("enabled"))
{
if(ini.ItemExist("overwrite_original_rules"))
overwrite_original_rules = ini.GetBool("overwrite_original_rules");
if(ini.ItemExist("update_ruleset_on_request"))
update_ruleset_on_request = ini.GetBool("update_ruleset_on_request");
if(ini.ItemPrefixExist("surge_ruleset"))
ini.GetAll("surge_ruleset", rulesets);
}
else
{
overwrite_original_rules = false;
update_ruleset_on_request = false;
}
ini.EnterSection("clash_proxy_group");
if(ini.ItemPrefixExist("custom_proxy_group"))
ini.GetAll("custom_proxy_group", clash_extra_group);
ini.EnterSection("server");
if(ini.ItemExist("listen"))
listen_address = ini.Get("listen");
if(ini.ItemExist("port"))
listen_port = ini.GetInt("port");
ini.EnterSection("advanced");
if(ini.ItemExist("print_debug_info"))
print_debug_info = ini.GetBool("print_debug_info");
if(ini.ItemExist("max_pending_connections"))
max_pending_connections = ini.GetInt("max_pending_connections");
if(ini.ItemExist("max_concurrent_threads"))
max_concurrent_threads = ini.GetInt("max_concurrent_threads");
std::cerr<<"Read preference settings completed."<<std::endl;
}
std::string subconverter(RESPONSE_CALLBACK_ARGS)
{
std::string target = getUrlArg(argument, "target"), url = UrlDecode(getUrlArg(argument, "url")), emoji = getUrlArg(argument, "emoji");
std::string group = UrlDecode(getUrlArg(argument, "group")), upload = getUrlArg(argument, "upload"), upload_path = getUrlArg(argument, "upload_path"), version = getUrlArg(argument, "ver");
std::string append_type = getUrlArg(argument, "append_type"), tfo = getUrlArg(argument, "tfo"), udp = getUrlArg(argument, "udp"), nodelist = getUrlArg(argument, "list");
std::string include = UrlDecode(getUrlArg(argument, "include")), exclude = UrlDecode(getUrlArg(argument, "exclude")), sort_flag = getUrlArg(argument, "sort");
std::string base_content, output_content;
string_array extra_group, extra_ruleset, include_remarks, exclude_remarks;
std::string groups = urlsafe_base64_decode(getUrlArg(argument, "groups")), ruleset = urlsafe_base64_decode(getUrlArg(argument, "ruleset"));
std::vector<ruleset_content> rca;
if(!url.size())
url = default_url;
if(!url.size() || !target.size())
return "Invalid request!";
if(!api_mode || cfw_child_process)
readConf();
if(groups.size())
{
extra_group = split(groups, "@");
if(!extra_group.size())
extra_group = clash_extra_group;
}
else
extra_group = clash_extra_group;
if(ruleset.size())
{
extra_ruleset = split(ruleset, "@");
if(!extra_ruleset.size())
{
if(update_ruleset_on_request || cfw_child_process)
refreshRulesets(rulesets, ruleset_content_array);
rca = ruleset_content_array;
}
else
{
refreshRulesets(extra_ruleset, rca);
}
}
else
{
if(update_ruleset_on_request || cfw_child_process)
refreshRulesets(rulesets, ruleset_content_array);
rca = ruleset_content_array;
}
extra_settings ext;
if(emoji.size())
{
ext.add_emoji = ext.remove_emoji = emoji == "true";
}
else
{
ext.add_emoji = add_emoji;
ext.remove_emoji = remove_old_emoji;
}
if(append_type.size())
ext.append_proxy_type = append_type == "true";
else
ext.append_proxy_type = append_proxy_type;
std::string proxy;
if(proxy_subscription == "SYSTEM")
proxy = getSystemProxy();
else if(proxy_subscription == "NONE")
proxy = "";
else
proxy = proxy_subscription;
ext.tfo = tfo.size() ? tfo == "true" : tfo_flag;
ext.udp = udp.size() ? udp == "true" : udp_flag;
ext.sort_flag = sort_flag.size() ? sort_flag == "true" : do_sort;
ext.nodelist = nodelist == "true";
ext.surge_ssr_path = surge_ssr_path;
string_array urls = split(url, "|");
std::vector<nodeInfo> nodes;
int groupID = 0;
if(include.size() && regValid(include))
include_remarks.emplace_back(include);
else
include_remarks = def_include_remarks;
if(exclude.size() && regValid(exclude))
exclude_remarks.emplace_back(exclude);
else
exclude_remarks = def_exclude_remarks;
if(group.size())
custom_group = group;
for(std::string &x : urls)
{
x = trim(x);
std::cerr<<"Fetching node data from url '"<<x<<"'."<<std::endl;
addNodes(x, nodes, groupID, proxy, exclude_remarks, include_remarks);
groupID++;
}
if(!nodes.size())
return "No nodes were found!";
std::cerr<<"Generate target: ";
if(target == "clash" || target == "clashr")
{
std::cerr<<"Clash"<<((target == "clashr") ? "R" : "")<<std::endl;
if(fileExist(clash_rule_base))
base_content = fileGet(clash_rule_base, false);
else
base_content = webGet(clash_rule_base, getSystemProxy());
output_content = netchToClash(nodes, base_content, rca, extra_group, target == "clashr", ext);
if(upload == "true")
uploadGist("clash", upload_path, output_content, false);
return output_content;
}
else if(target == "surge")
{
int surge_ver = version.size() ? to_int(version, 3) : 3;
std::cerr<<"Surge "<<surge_ver<<std::endl;
if(fileExist(surge_rule_base))
base_content = fileGet(surge_rule_base, false);
else
base_content = webGet(surge_rule_base, getSystemProxy());
output_content = netchToSurge(nodes, base_content, rca, extra_group, surge_ver, ext);
if(upload == "true")
uploadGist("surge" + version, upload_path, output_content, true);
if(write_managed_config && managed_config_prefix.size() && !ext.nodelist)
output_content = "#!MANAGED-CONFIG " + managed_config_prefix + "/sub?" + argument + "\n\n" + output_content;
return output_content;
}
else if(target == "surfboard")
{
std::cerr<<"Surfboard"<<std::endl;
if(fileExist(surfboard_rule_base))
base_content = fileGet(surfboard_rule_base, false);
else
base_content = webGet(surfboard_rule_base, getSystemProxy());
output_content = netchToSurge(nodes, base_content, rca, extra_group, 2, ext);
if(upload == "true")
uploadGist("surfboard", upload_path, output_content, true);
if(write_managed_config && managed_config_prefix.size())
output_content = "#!MANAGED-CONFIG " + managed_config_prefix + "/sub?" + argument + "\n\n" + output_content;
return output_content;
}
else if(target == "mellow")
{
std::cerr<<"Mellow"<<std::endl;
if(fileExist(mellow_rule_base))
base_content = fileGet(mellow_rule_base, false);
else
base_content = webGet(mellow_rule_base, getSystemProxy());
output_content = netchToMellow(nodes, base_content, rca, extra_group, ext);
if(upload == "true")
uploadGist("mellow", upload_path, output_content, true);
return output_content;
}
else if(target == "ss")
{
std::cerr<<"SS"<<std::endl;
output_content = netchToSS(nodes, ext);
if(upload == "true")
uploadGist("ss", upload_path, output_content, false);
return output_content;
}
else if(target == "ssr")
{
std::cerr<<"SSR"<<std::endl;
output_content = netchToSSR(nodes, ext);
if(upload == "true")
uploadGist("ssr", upload_path, output_content, false);
return output_content;
}
else if(target == "v2ray")
{
std::cerr<<"v2rayN"<<std::endl;
output_content = netchToVMess(nodes, ext);
if(upload == "true")
uploadGist("v2ray", upload_path, output_content, false);
return output_content;
}
else if(target == "quan")
{
std::cerr<<"Quantumult"<<std::endl;
output_content = netchToQuan(nodes, ext);
if(upload == "true")
uploadGist("quan", upload_path, output_content, false);
return output_content;
}
else if(target == "quanx")
{
std::cerr<<"Quantumult X"<<std::endl;
output_content = netchToQuanX(nodes, ext);
if(upload == "true")
uploadGist("quanx", upload_path, output_content, false);
return output_content;
}
else if(target == "ssd")
{
std::cerr<<"SSD"<<std::endl;
output_content = netchToSSD(nodes, group, ext);
if(upload == "true")
uploadGist("ssd", upload_path, output_content, false);
return output_content;
}
else
{
std::cerr<<"Unspecified"<<std::endl;
return std::string();
}
}
void chkArg(int argc, char *argv[])
{
for(int i = 1; i < argc; i++)
{
if(strcmp(argv[i], "-cfw") == 0)
cfw_child_process = true;
else if(strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--file") == 0)
pref_path.assign(argv[++i]);
}
}
int main(int argc, char *argv[])
{
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0)
{
fprintf(stderr, "WSAStartup failed.\n");
return 1;
}
SetConsoleOutputCP(65001);
#endif // _WIN32
#ifndef _DEBUG
setcd(argv);
#endif // _DEBUG
SetConsoleTitle("subconverter " VERSION);
chkArg(argc, argv);
readConf();
if(!update_ruleset_on_request)
refreshRulesets(rulesets, ruleset_content_array);
append_response("GET", "/refreshrules", "text/plain", [](RESPONSE_CALLBACK_ARGS) -> std::string
{
return refreshRulesets(rulesets, ruleset_content_array);
});
append_response("GET", "/readconf", "text/plain", [](RESPONSE_CALLBACK_ARGS) -> std::string
{
readConf();
return "done";
});
append_response("GET", "/sub", "text/plain;charset=utf-8", subconverter);
append_response("GET", "/clash", "text/plain;charset=utf-8", [](RESPONSE_CALLBACK_ARGS) -> std::string
{
return subconverter(argument + "&target=clash", postdata);
});
append_response("GET", "/clashr", "text/plain;charset=utf-8", [](RESPONSE_CALLBACK_ARGS) -> std::string
{
return subconverter(argument + "&target=clashr", postdata);
});
append_response("GET", "/surge", "text/plain;charset=utf-8", [](RESPONSE_CALLBACK_ARGS) -> std::string
{
return subconverter(argument + "&target=surge", postdata);
});
append_response("GET", "/surfboard", "text/plain;charset=utf-8", [](RESPONSE_CALLBACK_ARGS) -> std::string
{
return subconverter(argument + "&target=surfboard", postdata);
});
append_response("GET", "/mellow", "text/plain;charset=utf-8", [](RESPONSE_CALLBACK_ARGS) -> std::string
{
return subconverter(argument + "&target=mellow", postdata);
});
append_response("GET", "/ss", "text/plain", [](RESPONSE_CALLBACK_ARGS) -> std::string
{
return subconverter(argument + "&target=ss", postdata);
});
append_response("GET", "/ssr", "text/plain", [](RESPONSE_CALLBACK_ARGS) -> std::string
{
return subconverter(argument + "&target=ssr", postdata);
});
append_response("GET", "/v2ray", "text/plain", [](RESPONSE_CALLBACK_ARGS) -> std::string
{
return subconverter(argument + "&target=v2ray", postdata);
});
append_response("GET", "/quan", "text/plain", [](RESPONSE_CALLBACK_ARGS) -> std::string
{
return subconverter(argument + "&target=quan", postdata);
});
append_response("GET", "/quanx", "text/plain;charset=utf-8", [](RESPONSE_CALLBACK_ARGS) -> std::string
{
return subconverter(argument + "&target=quanx", postdata);
});
append_response("GET", "/ssd", "text/plain", [](RESPONSE_CALLBACK_ARGS) -> std::string
{
return subconverter(argument + "&target=ssd", postdata);
});
if(!api_mode)
{
append_response("GET", "/get", "text/plain;charset=utf-8", [](RESPONSE_CALLBACK_ARGS) -> std::string
{
std::string url = UrlDecode(getUrlArg(argument, "url"));
return webGet(url, "");
});
append_response("GET", "/getlocal", "text/plain;charset=utf-8", [](RESPONSE_CALLBACK_ARGS) -> std::string
{
return fileGet(UrlDecode(getUrlArg(argument, "path")));
});
}
listener_args args = {listen_address, listen_port, max_pending_connections, max_concurrent_threads};
std::cout<<"Serving HTTP @ http://"<<listen_address<<":"<<listen_port<<std::endl;
start_web_server_multi(&args);
#ifdef _WIN32
WSACleanup();
#endif // _WIN32
return 0;
}