Enhancements

Fix incorrect flag for expanding rulesets to rules.
Add support for generating Clash rule-providers in Rule mode.
Rework Clash script generator.
Change global UDP/TFO/Skip Cert Verify settings in pref configuration to comments.
Optimize codes.
This commit is contained in:
Tindy X 2020-05-01 23:59:01 +08:00
parent 2c793a0f95
commit f6f2ced3c7
No known key found for this signature in database
GPG Key ID: C6AD413169968D58
6 changed files with 157 additions and 73 deletions

View File

@ -34,10 +34,10 @@ userinfo:
- {match: "^.*?流量:(?:.*?) 剩:(.*?)天$", replace: "left=$1d"}
node_pref:
udp_flag: false
tcp_fast_open_flag: false
# udp_flag: false
# tcp_fast_open_flag: false
sort_flag: false
skip_cert_verify_flag: false
# skip_cert_verify_flag: false
filter_deprecated_nodes: false
append_sub_userinfo: true
clash_use_new_field_name: false

View File

@ -76,10 +76,10 @@ time_rule=^Smart Access expire: (\d+)/(\d+)/(\d+)$|$1:$2:$3:0:0:0
time_rule=^.*?流量:(?:.*?) 剩:(.*)$|left=$1d
[node_pref]
udp_flag=false
tcp_fast_open_flag=false
;udp_flag=false
;tcp_fast_open_flag=false
sort_flag=false
skip_cert_verify_flag=false
;skip_cert_verify_flag=false
filter_deprecated_nodes=false
append_sub_userinfo=true
clash_use_new_field_name=false

View File

@ -47,7 +47,8 @@ std::mutex on_configuring;
//preferences
string_array renames, emojis;
bool add_emoji = false, remove_old_emoji = false, append_proxy_type = false, filter_deprecated = true;
bool udp_flag = false, tfo_flag = false, scv_flag = false, do_sort = false, config_update_strict = false;
tribool udp_flag, tfo_flag, scv_flag;
bool do_sort = false, config_update_strict = false;
bool clash_use_new_field_name = false;
std::string proxy_config, proxy_ruleset, proxy_subscription;
int config_update_interval = 0;
@ -153,7 +154,7 @@ std::string getRuleset(RESPONSE_CALLBACK_ARGS)
switch(hash_(vArray[0]))
{
case "DOMAIN-SUFFIX"_hash:
strLine = " - '." + vArray[1] + "'";
strLine = " - '." + vArray[1] + "'\n - '" + vArray[1] + "'";
break;
case "DOMAIN"_hash:
strLine = " - '" + vArray[1] + "'";
@ -536,10 +537,15 @@ void readYAMLConf(YAML::Node &node)
if(node["node_pref"].IsDefined())
{
section = node["node_pref"];
/*
section["udp_flag"] >> udp_flag;
section["tcp_fast_open_flag"] >> tfo_flag;
section["sort_flag"] >> do_sort;
section["skip_cert_verify_flag"] >> scv_flag;
*/
udp_flag.set(safe_as<std::string>(section["udp_flag"]));
tfo_flag.set(safe_as<std::string>(section["tcp_fast_open_flag"]));
scv_flag.set(safe_as<std::string>(section["skip_cert_verify_flag"]));
section["sort_flag"] >> do_sort;
section["filter_deprecated_nodes"] >> filter_deprecated;
section["append_sub_userinfo"] >> append_userinfo;
section["clash_use_new_field_name"] >> clash_use_new_field_name;
@ -759,10 +765,15 @@ void readConf()
if(ini.SectionExist("node_pref"))
{
ini.EnterSection("node_pref");
/*
ini.GetBoolIfExist("udp_flag", udp_flag);
ini.GetBoolIfExist("tcp_fast_open_flag", tfo_flag);
ini.GetBoolIfExist("sort_flag", do_sort);
ini.GetBoolIfExist("skip_cert_verify_flag", scv_flag);
*/
udp_flag.set(ini.Get("udp_flag"));
tfo_flag.set(ini.Get("tcp_fast_open_flag"));
scv_flag.set(ini.Get("skip_cert_verify_flag"));
ini.GetBoolIfExist("sort_flag", do_sort);
ini.GetBoolIfExist("filter_deprecated_nodes", filter_deprecated);
ini.GetBoolIfExist("append_sub_userinfo", append_userinfo);
ini.GetBoolIfExist("clash_use_new_field_name", clash_use_new_field_name);
@ -1191,21 +1202,33 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS)
ext.remove_emoji = remove_old_emoji;
}
ext.append_proxy_type = append_type.get(append_proxy_type);
if(target == "clash" || target == "clashr")
expand.define(true);
/// read preference from argument
ext.tfo = tfo;
ext.udp = udp;
ext.skip_cert_verify = scv;
/// assign global var if not in argument
ext.tfo.define(tfo_flag);
ext.udp.define(udp_flag);
ext.skip_cert_verify.define(scv_flag);
ext.sort_flag = sort_flag.get(do_sort);
ext.filter_deprecated = fdn.get(filter_deprecated);
ext.clash_new_field_name = clash_new_field.get(clash_use_new_field_name);
ext.clash_script = clash_script.get();
if(!expand)
ext.clash_new_field_name = true;
else
ext.clash_script = false;
ext.nodelist = nodelist;
ext.surge_ssr_path = surge_ssr_path;
ext.quanx_dev_id = dev_id.size() ? dev_id : quanx_script_id;
ext.enable_rule_generator = enable_rule_generator;
ext.overwrite_original_rules = overwrite_original_rules;
if(expand)
if(!expand)
ext.managed_config_prefix = managed_config_prefix;
//load external configuration

View File

@ -1284,9 +1284,11 @@ std::string netchToClash(std::vector<nodeInfo> &nodes, std::string &base_conf, s
if(!ext.enable_rule_generator)
return YAML::Dump(yamlnode);
if(ext.clash_script)
if(ext.managed_config_prefix.size() || ext.clash_script)
{
renderClashScript(yamlnode, ruleset_content_array, ext.managed_config_prefix);
if(yamlnode["mode"].IsDefined())
yamlnode["mode"] = ext.clash_script ? "Script" : "Rule";
renderClashScript(yamlnode, ruleset_content_array, ext.managed_config_prefix, ext.clash_script, ext.overwrite_original_rules);
return YAML::Dump(yamlnode);
}

View File

@ -140,21 +140,24 @@ const std::string clash_script_template = R"(def main(ctx, md):
{% if not rule.keyword == "" %}{% include "keyword_template" %}{% endif %}
{% endfor %}
ip = ctx.resolve_ip(md.host)
if ip == "":
return "{{ match_group }}"
geoips = { {{ geoips }} }
ip = md.dst_ip
if ip == "":
ip = ctx.resolve_ip(md.host)
if ip == "":
ctx.log('[Script] dns lookup error use {{ match_group }}')
return "{{ match_group }}"
for key in geoips:
if ctx.geoip(ip) == key:
return geoips[key]
return "{{ match_group }}")";
const std::string clash_script_group_template = R"( if ctx.rule_providers["{{ rule.group }}_domain"].match(md):
const std::string clash_script_group_template = R"({% if rule.has_domain == "true" %} if ctx.rule_providers["{{ rule.name }}_domain"].match(md):
ctx.log('[Script] matched {{ rule.group }} DOMAIN rule')
return "{{ rule.group }}"
if ctx.rule_providers["{{ rule.group }}_ipcidr"].match(md):
return "{{ rule.group }}"{% endif %}
{% if rule.has_ipcidr == "true" %} if ctx.rule_providers["{{ rule.name }}_ipcidr"].match(md):
ctx.log('[Script] matched {{ rule.group }} IP rule')
return "{{ rule.group }}")";
return "{{ rule.group }}"{% endif %})";
const std::string clash_script_keyword_template = R"( keywords = [{{ rule.keyword }}]
for keyword in keywords:
@ -162,14 +165,20 @@ const std::string clash_script_keyword_template = R"( keywords = [{{ rule.keywo
ctx.log('[Script] matched {{ rule.group }} DOMAIN-KEYWORD rule')
return "{{ rule.group }}")";
int renderClashScript(YAML::Node &base_rule, std::vector<ruleset_content> &ruleset_content_array, std::string remote_path_prefix)
int renderClashScript(YAML::Node &base_rule, std::vector<ruleset_content> &ruleset_content_array, std::string remote_path_prefix, bool script, bool overwrite_original_rules)
{
nlohmann::json data;
std::string match_group, geoips, retrieved_rules;
std::string strLine, rule_group, rule_path;
std::string match_group, geoips, retrieved_rules;
std::string strLine, rule_group, rule_path, rule_name;
std::stringstream strStrm;
string_array vArray, groups;
string_map keywords, urls;
string_map keywords, urls, names;
std::map<std::string, bool> has_domain, has_ipcidr;
YAML::Node rules;
if(!overwrite_original_rules && base_rule["rules"].IsDefined())
rules = base_rule["rules"];
for(ruleset_content &x : ruleset_content_array)
{
rule_group = x.rule_group;
@ -177,28 +186,40 @@ int renderClashScript(YAML::Node &base_rule, std::vector<ruleset_content> &rules
if(rule_path.empty())
{
strLine = x.rule_content.get().substr(2);
if(startsWith(strLine, "MATCH") || startsWith(strLine, "FINAL"))
match_group = rule_group;
else if(startsWith(strLine, "GEOIP"))
if(script)
{
vArray = split(strLine, ",");
if(vArray.size() < 2)
continue;
geoips += "\"" + vArray[1] + "\": \"" + rule_group + "\",";
if(startsWith(strLine, "MATCH") || startsWith(strLine, "FINAL"))
match_group = rule_group;
else if(startsWith(strLine, "GEOIP"))
{
vArray = split(strLine, ",");
if(vArray.size() < 2)
continue;
geoips += "\"" + vArray[1] + "\": \"" + rule_group + "\",";
}
continue;
}
if(strLine.find("FINAL") == 0)
strLine.replace(0, 5, "MATCH");
strLine += "," + rule_group;
if(std::count(strLine.begin(), strLine.end(), ',') > 2)
strLine = regReplace(strLine, "^(.*?,.*?)(,.*)(,.*)$", "$1$3$2");
rules.push_back(strLine);
continue;
}
else
{
if(fileExist(rule_path) || startsWith(rule_path, "https://") || startsWith(rule_path, "http://") || startsWith(rule_path, "data:"))
if(remote_path_prefix.size())
{
if(urls.find(rule_group) == urls.end())
urls[rule_group] = rule_path;
if(fileExist(rule_path) || startsWith(rule_path, "https://") || startsWith(rule_path, "http://") || startsWith(rule_path, "data:"))
{
rule_name = std::to_string(hash_(rule_path));
names[rule_name] = rule_group;
urls[rule_name] = rule_path;
}
else
urls[rule_group] += "|" + rule_path;
continue;
}
else
continue;
retrieved_rules = x.rule_content.get();
if(retrieved_rules.empty())
@ -225,53 +246,89 @@ int renderClashScript(YAML::Node &base_rule, std::vector<ruleset_content> &rules
if(startsWith(strLine, "DOMAIN-KEYWORD,"))
{
vArray = split(strLine, ",");
if(vArray.size() < 2)
continue;
if(keywords.find(rule_group) == keywords.end())
keywords[rule_group] = "\"" + vArray[1] + "\"";
if(script)
{
vArray = split(strLine, ",");
if(vArray.size() < 2)
continue;
if(keywords.find(rule_name) == keywords.end())
keywords[rule_name] = "\"" + vArray[1] + "\"";
else
keywords[rule_name] += ",\"" + vArray[1] + "\"";
}
else
keywords[rule_group] += ",\"" + vArray[1] + "\"";
{
strLine += "," + rule_group;
if(std::count(strLine.begin(), strLine.end(), ',') > 2)
strLine = regReplace(strLine, "^(.*?,.*?)(,.*)(,.*)$", "$1$3$2");
rules.push_back(strLine);
}
}
else if(startsWith(strLine, "DOMAIN,") || startsWith(strLine, "DOMAIN-SUFFIX,"))
has_domain[rule_name] = true;
else if(startsWith(strLine, "IP-CIDR,") || startsWith(strLine, "IP-CIDR6,"))
has_ipcidr[rule_name] = true;
}
if(std::find(groups.begin(), groups.end(), rule_group) == groups.end())
groups.emplace_back(rule_group);
if(has_domain[rule_name] && !script)
rules.push_back("RULE-SET," + rule_group + "_" + rule_name + "_domain," + rule_group);
if(has_ipcidr[rule_name] && !script)
rules.push_back("RULE-SET," + rule_group + "_" + rule_name + "_ipcidr," + rule_group);
if(std::find(groups.begin(), groups.end(), rule_name) == groups.end())
groups.emplace_back(rule_name);
}
}
int index = 0;
for(std::string &x : groups)
{
std::string json_path = "rules." + std::to_string(index) + ".";
std::string url = urls[x], keyword = keywords[x];
base_rule["rule-providers"][x + "_domain"]["type"] = "http";
base_rule["rule-providers"][x + "_domain"]["behavior"] = "domain";
base_rule["rule-providers"][x + "_domain"]["url"] = remote_path_prefix + "/getruleset?type=3&url=" + urlsafe_base64_encode(url);
base_rule["rule-providers"][x + "_domain"]["path"] = "./rule-providers_" + getMD5(url + x + "domain") + ".yaml";
base_rule["rule-providers"][x + "_ipcidr"]["type"] = "http";
base_rule["rule-providers"][x + "_ipcidr"]["behavior"] = "ipcidr";
base_rule["rule-providers"][x + "_ipcidr"]["url"] = remote_path_prefix + "/getruleset?type=4&url=" + urlsafe_base64_encode(url);
base_rule["rule-providers"][x + "_ipcidr"]["path"] = "./rule-providers_" + getMD5(url + x + "ipcidr") + ".yaml";
parse_json_pointer(data, json_path + "group", x);
parse_json_pointer(data, json_path + "set", "true");
parse_json_pointer(data, json_path + "keyword", keyword);
std::string url = urls[x], keyword = keywords[x], name = names[x];
bool group_has_domain = has_domain[x], group_has_ipcidr = has_ipcidr[x];
if(group_has_domain)
{
base_rule["rule-providers"][name + "_" + x + "_domain"]["type"] = "http";
base_rule["rule-providers"][name + "_" + x + "_domain"]["behavior"] = "domain";
base_rule["rule-providers"][name + "_" + x + "_domain"]["url"] = remote_path_prefix + "/getruleset?type=3&url=" + urlsafe_base64_encode(url);
base_rule["rule-providers"][name + "_" + x + "_domain"]["path"] = "./rule-providers_" + x + "_domain.yaml";
}
if(group_has_ipcidr)
{
base_rule["rule-providers"][name + "_" + x + "_ipcidr"]["type"] = "http";
base_rule["rule-providers"][name + "_" + x + "_ipcidr"]["behavior"] = "ipcidr";
base_rule["rule-providers"][name + "_" + x + "_ipcidr"]["url"] = remote_path_prefix + "/getruleset?type=4&url=" + urlsafe_base64_encode(url);
base_rule["rule-providers"][name + "_" + x + "_ipcidr"]["path"] = "./rule-providers_" + x + "_ipcidr.yaml";
}
if(script)
{
parse_json_pointer(data, json_path + "has_domain", group_has_domain ? "true" : "false");
parse_json_pointer(data, json_path + "has_ipcidr", group_has_ipcidr ? "true" : "false");
parse_json_pointer(data, json_path + "name", name + "_" + x);
parse_json_pointer(data, json_path + "group", name);
parse_json_pointer(data, json_path + "set", "true");
parse_json_pointer(data, json_path + "keyword", keyword);
}
index++;
}
parse_json_pointer(data, "geoips", geoips.erase(geoips.size() - 1));
parse_json_pointer(data, "match_group", match_group);
inja::Environment env;
env.include_template("group_template", env.parse(clash_script_group_template));
env.include_template("keyword_template", env.parse(clash_script_keyword_template));
inja::Template tmpl = env.parse(clash_script_template);
try
if(script)
{
std::string output_content = env.render(tmpl, data);
base_rule["script"]["code"] = output_content;
}
catch (std::exception&)
{
return -1;
parse_json_pointer(data, "geoips", geoips.erase(geoips.size() - 1));
parse_json_pointer(data, "match_group", match_group);
inja::Environment env;
env.include_template("group_template", env.parse(clash_script_group_template));
env.include_template("keyword_template", env.parse(clash_script_keyword_template));
inja::Template tmpl = env.parse(clash_script_template);
try
{
std::string output_content = env.render(tmpl, data);
base_rule["script"]["code"] = output_content;
}
catch (std::exception&)
{
return -1;
}
}
else
base_rule["rules"] = rules;
return 0;
}

View File

@ -4,6 +4,8 @@
#include <string>
#include <map>
#include "subexport.h"
typedef std::map<std::string, std::string> string_map;
struct template_args
@ -14,6 +16,6 @@ struct template_args
};
int render_template(const std::string &content, const template_args &vars, std::string &output, const std::string &include_scope = "template");
int renderClashScript(YAML::Node &base_rule, std::vector<ruleset_content> &ruleset_content_array, std::string remote_path_prefix);
int renderClashScript(YAML::Node &base_rule, std::vector<ruleset_content> &ruleset_content_array, std::string remote_path_prefix, bool script, bool overwrite_original_rules);
#endif // TEMPLATES_H_INCLUDED