mirror of
https://github.com/asdlokj1qpi233/subconverter.git
synced 2025-10-27 20:03:01 +00:00
Add experimental Clash script generator
This commit is contained in:
@@ -108,7 +108,9 @@ std::string getRuleset(RESPONSE_CALLBACK_ARGS)
|
||||
}
|
||||
|
||||
std::string proxy = parseProxy(proxy_ruleset);
|
||||
output_content = fetchFile(url, proxy, cache_ruleset);
|
||||
string_array vArray = split(url, "|");
|
||||
for(std::string &x : vArray)
|
||||
output_content += fetchFile(x, proxy, cache_ruleset) + "\n";
|
||||
|
||||
if(!output_content.size())
|
||||
{
|
||||
@@ -129,7 +131,6 @@ std::string getRuleset(RESPONSE_CALLBACK_ARGS)
|
||||
if(type_int == 3 || type_int == 4)
|
||||
output_content = "payload:\n";
|
||||
|
||||
string_array vArray;
|
||||
while(getline(ss, strLine, delimiter))
|
||||
{
|
||||
switch(type_int)
|
||||
@@ -152,11 +153,14 @@ std::string getRuleset(RESPONSE_CALLBACK_ARGS)
|
||||
switch(hash_(vArray[0]))
|
||||
{
|
||||
case "DOMAIN-SUFFIX"_hash:
|
||||
strLine = " - '." + vArray[1] + "'";
|
||||
strLine = " - '." + vArray[1] + "'";
|
||||
break;
|
||||
case "DOMAIN"_hash:
|
||||
strLine = " - '" + vArray[1] + "'";
|
||||
strLine = " - '" + vArray[1] + "'";
|
||||
break;
|
||||
//case "DOMAIN_KEYWORD"_hash:
|
||||
//strLine = " - '." + vArray[1] + ".*'";
|
||||
//break;
|
||||
}
|
||||
output_content += strLine + "\n";
|
||||
continue;
|
||||
@@ -166,7 +170,7 @@ std::string getRuleset(RESPONSE_CALLBACK_ARGS)
|
||||
vArray = split(strLine, ",");
|
||||
if(vArray.size() < 2)
|
||||
continue;
|
||||
output_content += " - " + vArray[1] + "\n";
|
||||
output_content += " - '" + vArray[1] + "'\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -192,6 +196,10 @@ std::string getRuleset(RESPONSE_CALLBACK_ARGS)
|
||||
output_content.append(strLine + "\n");
|
||||
}
|
||||
|
||||
if(type_int == 3 && output_content == "payload:\n")
|
||||
output_content += " - '--placeholder--'";
|
||||
if(type_int == 4 && output_content == "payload:\n")
|
||||
output_content += " - '255.255.255.255/32'";
|
||||
return output_content;
|
||||
}
|
||||
|
||||
@@ -1073,7 +1081,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS)
|
||||
std::string include = UrlDecode(getUrlArg(argument, "include")), exclude = UrlDecode(getUrlArg(argument, "exclude")), sort_flag = getUrlArg(argument, "sort");
|
||||
std::string scv = getUrlArg(argument, "scv"), fdn = getUrlArg(argument, "fdn"), expand = getUrlArg(argument, "expand"), append_sub_userinfo = getUrlArg(argument, "append_info");
|
||||
std::string dev_id = getUrlArg(argument, "dev_id"), filename = getUrlArg(argument, "filename"), interval_str = getUrlArg(argument, "interval"), strict_str = getUrlArg(argument, "strict");
|
||||
std::string clash_new_field = getUrlArg(argument, "new_name");
|
||||
std::string clash_new_field = getUrlArg(argument, "new_name"), clash_script = getUrlArg(argument, "script");
|
||||
std::string base_content, output_content;
|
||||
string_array extra_group, extra_ruleset, include_remarks = def_include_remarks, exclude_remarks = def_exclude_remarks;
|
||||
std::string groups = urlsafe_base64_decode(getUrlArg(argument, "groups")), ruleset = urlsafe_base64_decode(getUrlArg(argument, "ruleset")), config = UrlDecode(getUrlArg(argument, "config"));
|
||||
@@ -1152,6 +1160,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS)
|
||||
ext.skip_cert_verify = scv.size() ? scv == "true" : scv_flag;
|
||||
ext.filter_deprecated = fdn.size() ? fdn == "true" : filter_deprecated;
|
||||
ext.clash_new_field_name = clash_new_field.size() ? clash_new_field == "true" : clash_use_new_field_name;
|
||||
ext.clash_script = clash_script == "true";
|
||||
|
||||
ext.nodelist = nodelist == "true";
|
||||
ext.surge_ssr_path = surge_ssr_path;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "socket.h"
|
||||
#include "string_hash.h"
|
||||
#include "logger.h"
|
||||
#include "templates.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@@ -586,7 +587,7 @@ std::string rulesetToClashStr(YAML::Node &base_rule, std::vector<ruleset_content
|
||||
void rulesetToSurge(INIReader &base_rule, std::vector<ruleset_content> &ruleset_content_array, int surge_ver, bool overwrite_original_rules, std::string remote_path_prefix)
|
||||
{
|
||||
string_array allRules;
|
||||
std::string rule_group, rule_path, retrived_rules, strLine;
|
||||
std::string rule_group, rule_path, retrieved_rules, strLine;
|
||||
std::stringstream strStrm;
|
||||
|
||||
switch(surge_ver) //other version: -3 for Surfboard, -4 for Loon
|
||||
@@ -694,17 +695,17 @@ void rulesetToSurge(INIReader &base_rule, std::vector<ruleset_content> &ruleset_
|
||||
}
|
||||
else
|
||||
continue;
|
||||
retrived_rules = x.rule_content.get();
|
||||
if(retrived_rules.empty())
|
||||
retrieved_rules = x.rule_content.get();
|
||||
if(retrieved_rules.empty())
|
||||
{
|
||||
writeLog(0, "Failed to fetch ruleset or ruleset is empty: '" + x.rule_path + "'!", LOG_LEVEL_WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
char delimiter = count(retrived_rules.begin(), retrived_rules.end(), '\n') < 1 ? '\r' : '\n';
|
||||
char delimiter = count(retrieved_rules.begin(), retrieved_rules.end(), '\n') < 1 ? '\r' : '\n';
|
||||
|
||||
strStrm.clear();
|
||||
strStrm<<retrived_rules;
|
||||
strStrm<<retrieved_rules;
|
||||
std::string::size_type lineSize;
|
||||
while(getline(strStrm, strLine, delimiter))
|
||||
{
|
||||
@@ -1172,6 +1173,12 @@ 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)
|
||||
{
|
||||
renderClashScript(yamlnode, ruleset_content_array, ext.managed_config_prefix);
|
||||
return YAML::Dump(yamlnode);
|
||||
}
|
||||
|
||||
std::string output_content = rulesetToClashStr(yamlnode, ruleset_content_array, ext.overwrite_original_rules, ext.clash_new_field_name);
|
||||
output_content.insert(0, YAML::Dump(yamlnode));
|
||||
|
||||
@@ -2107,7 +2114,7 @@ void netchToQuanX(std::vector<nodeInfo> &nodes, INIReader &ini, std::vector<rule
|
||||
proxyStr += ", fast-open=true";
|
||||
if(ext.udp)
|
||||
proxyStr += ", udp-relay=true";
|
||||
if(ext.skip_cert_verify && (x.linkType == SPEEDTEST_MESSAGE_FOUNDHTTP || x.linkType == SPEEDTEST_MESSAGE_FOUNDTROJAN))
|
||||
if(ext.skip_cert_verify)
|
||||
proxyStr += ", tls-verification=false";
|
||||
proxyStr += ", tag=" + remark;
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ struct extra_settings
|
||||
bool skip_cert_verify = false;
|
||||
bool filter_deprecated = false;
|
||||
bool clash_new_field_name = false;
|
||||
bool clash_script = false;
|
||||
std::string surge_ssr_path;
|
||||
std::string managed_config_prefix;
|
||||
std::string quanx_dev_id;
|
||||
|
||||
@@ -126,3 +126,145 @@ int render_template(const std::string &content, const template_args &vars, std::
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
const std::string clash_script_template = R"(def main(ctx, md):
|
||||
{% for rule in rules %}
|
||||
{% if rule.set == "true" %}{% include "group_template" %}{% endif %}
|
||||
{% if not rule.keyword == "" %}{% include "keyword_template" %}{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
ip = ctx.resolve_ip(md.host)
|
||||
if ip == "":
|
||||
return "{{ match_group }}"
|
||||
geoips = { {{ geoips }} }
|
||||
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):
|
||||
ctx.log('[Script] matched {{ rule.group }} DOMAIN rule')
|
||||
return "{{ rule.group }}"
|
||||
if ctx.rule_providers["{{ rule.group }}_ipcidr"].match(md):
|
||||
ctx.log('[Script] matched {{ rule.group }} IP rule')
|
||||
return "{{ rule.group }}")";
|
||||
|
||||
const std::string clash_script_keyword_template = R"( keywords = [{{ rule.keyword }}]
|
||||
for keyword in keywords:
|
||||
if keyword in md.host:
|
||||
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)
|
||||
{
|
||||
nlohmann::json data;
|
||||
std::string match_group, geoips, retrieved_rules;
|
||||
std::string strLine, rule_group, rule_path;
|
||||
std::stringstream strStrm;
|
||||
string_array vArray, groups;
|
||||
string_map keywords, urls;
|
||||
for(ruleset_content &x : ruleset_content_array)
|
||||
{
|
||||
rule_group = x.rule_group;
|
||||
rule_path = x.rule_path;
|
||||
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"))
|
||||
{
|
||||
vArray = split(strLine, ",");
|
||||
if(vArray.size() < 2)
|
||||
continue;
|
||||
geoips += "\"" + vArray[1] + "\": \"" + rule_group + "\",";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(fileExist(rule_path) || startsWith(rule_path, "https://") || startsWith(rule_path, "http://") || startsWith(rule_path, "data:"))
|
||||
{
|
||||
if(urls.find(rule_group) == urls.end())
|
||||
urls[rule_group] = rule_path;
|
||||
else
|
||||
urls[rule_group] += "|" + rule_path;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
retrieved_rules = x.rule_content.get();
|
||||
if(retrieved_rules.empty())
|
||||
{
|
||||
writeLog(0, "Failed to fetch ruleset or ruleset is empty: '" + x.rule_path + "'!", LOG_LEVEL_WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
char delimiter = std::count(retrieved_rules.begin(), retrieved_rules.end(), '\n') < 1 ? '\r' : '\n';
|
||||
|
||||
strStrm.clear();
|
||||
strStrm<<retrieved_rules;
|
||||
std::string::size_type lineSize;
|
||||
while(getline(strStrm, strLine, delimiter))
|
||||
{
|
||||
lineSize = strLine.size();
|
||||
if(lineSize)
|
||||
{
|
||||
strLine = regTrim(strLine);
|
||||
lineSize = strLine.size();
|
||||
}
|
||||
if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored
|
||||
continue;
|
||||
|
||||
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] + "\"";
|
||||
else
|
||||
keywords[rule_group] += ",\"" + vArray[1] + "\"";
|
||||
}
|
||||
}
|
||||
if(std::find(groups.begin(), groups.end(), rule_group) == groups.end())
|
||||
groups.emplace_back(rule_group);
|
||||
}
|
||||
}
|
||||
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);
|
||||
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
|
||||
{
|
||||
std::string output_content = env.render(tmpl, data);
|
||||
base_rule["script"]["code"] = output_content;
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -14,5 +14,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);
|
||||
|
||||
#endif // TEMPLATES_H_INCLUDED
|
||||
|
||||
Reference in New Issue
Block a user