From f30b3c7cbf1d47a6dda4bb457ce4201db7bf8d84 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Fri, 29 May 2020 19:15:05 +0800 Subject: [PATCH] Breaking changes Fix data URIs not correctly parsed. Fix downloading files may be caught in infinite redirect loops. Add experimental JavaScript support for providing subscriptions, filtering nodes, sorting nodes and generating groups. Add tag support for adding group name to a specific link. Add complete matcher support for Emoji and rename match rule. Optimize codes. Update build scripts. --- CMakeLists.txt | 4 + base/pref-new.yml | 3 + base/pref.ini | 17 ++- cmake/FindDuktape.cmake | 12 ++ scripts/Dockerfile | 17 ++- scripts/build.alpine.release.sh | 18 ++- scripts/build.clang.sh | 2 +- scripts/build.macos.release.sh | 21 ++- scripts/config.termux.sh | 20 ++- src/interfaces.cpp | 119 +++++++---------- src/misc.h | 4 +- src/nodemanip.cpp | 59 ++++++++- src/script.cpp | 224 ++++++++++++++++++++++++++++++++ src/script_duktape.h | 19 +++ src/speedtestutil.cpp | 131 +++++++++++-------- src/speedtestutil.h | 12 +- src/subexport.cpp | 207 ++++++++++++++++------------- src/subexport.h | 1 + src/webget.cpp | 9 +- 19 files changed, 650 insertions(+), 249 deletions(-) create mode 100644 cmake/FindDuktape.cmake create mode 100644 src/script.cpp create mode 100644 src/script_duktape.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 750f978..71401ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,6 +101,10 @@ TARGET_LINK_LIBRARIES(subconverter ${YAML_CPP_LIBRARY}) ADD_DEFINITIONS(-DPCRE2_STATIC) #ENDIF() +FIND_PACKAGE(Duktape REQUIRED) +INCLUDE_DIRECTORIES(${DUKTAPE_INCLUDE_DIRS}) +TARGET_LINK_LIBRARIES(subconverter ${DUKTAPE_LIBRARIES}) + IF(WIN32) TARGET_LINK_LIBRARIES(subconverter wsock32 ws2_32) ELSE() diff --git a/base/pref-new.yml b/base/pref-new.yml index 92da130..be5cf9a 100644 --- a/base/pref-new.yml +++ b/base/pref-new.yml @@ -7,6 +7,8 @@ common: prepend_insert_url: true exclude_remarks: ["(到期|剩余流量|时间|官网|产品)"] include_remarks: [] + enable_filter: false + filter_script: "" default_external_config: "" # config/example_external_config.yml base_path: base clash_rule_base: base/all_base.tpl @@ -39,6 +41,7 @@ node_pref: # udp_flag: false # tcp_fast_open_flag: false sort_flag: false + sort_script: "" # skip_cert_verify_flag: false filter_deprecated_nodes: false append_sub_userinfo: true diff --git a/base/pref.ini b/base/pref.ini index f3c922e..388c82c 100644 --- a/base/pref.ini +++ b/base/pref.ini @@ -22,6 +22,13 @@ exclude_remarks=(到期|剩余流量|时间|官网|产品) ;Only include nodes which remarks match the following patterns. Supports regular expression. ;include_remarks=V3.*港 +;Enable script support for filtering nodes +enable_filter=false +;Script used for filtering nodes. Supports inline script and script path. A "filter" function with 1 argument which is a node should be defined in the script. +;Example: Inline script: Set value to content of script. Replace all line break with "\n". +; Script path: Set value to "path:/path/to/script.js". +;filter_script=function filter(node) {\n const info = JSON.parse(node.ProxyInfo);\n if(info.EncryptMethod.includes('chacha20'))\n return true;\n return false;\n} + ;Setting an external config file as default when none is specified, supports local files/URL ;default_external_config=config/example_external_config.ini @@ -84,7 +91,12 @@ time_rule=^.*?流量:(?:.*?) 剩:(.*)$|left=$1d [node_pref] ;udp_flag=false ;tcp_fast_open_flag=false + sort_flag=false +;Script used for sorting nodes. A "compare" function with 2 arguments which are the 2 nodes to be compared should be defined in the script. Supports inline script and script path. +;Examples can be seen at the filter_script option in [common] section. +;sort_script=function compare(node_a, node_b) {\n const info_a = JSON.parse(node_a.ProxyInfo);\n const info_b = JSON.parse(node_b.ProxyInfo);\n return info_a.Remark > info_b.Remark;\n} + ;skip_cert_verify_flag=false filter_deprecated_nodes=false append_sub_userinfo=true @@ -152,7 +164,7 @@ update_ruleset_on_request=false surge_ruleset=!!import:snippets/rulesets.txt [clash_proxy_group] -;Generate Clash Proxy Group with the following patterns. Node filterting rule supports regular expression. +;Generate Clash Proxy Group with the following patterns. Node filtering rule supports regular expression. ;Format: Group_Name`select`Rule_1`Rule_2`... ; Group_Name`url-test|fallback|load-balance`Rule_1`Rule_2`...`test_url`interval[,timeout][,tolerance] ;Rule with "[]" prefix will be added directly. @@ -170,6 +182,9 @@ surge_ruleset=!!import:snippets/rulesets.txt ;custom_proxy_group=g1hk`select`!!GROUPID=0!!(HGC|HKBN|PCCW|HKT|hk|港) ;custom_proxy_group=sstw`select`!!GROUP=V2RayProvider!!(深台|彰化|新北|台|tw) +;Also supports using script for filtering nodes. A "filter" function with one argument which is an array of all available nodes should be defined in the script. +;custom_proxy_group=script`select`script:/path/to/script.js + ;for forcerule.yml ;custom_proxy_group=!!import:snippets/groups_forcerule.txt diff --git a/cmake/FindDuktape.cmake b/cmake/FindDuktape.cmake new file mode 100644 index 0000000..1b87aec --- /dev/null +++ b/cmake/FindDuktape.cmake @@ -0,0 +1,12 @@ +find_path(DUKTAPE_INCLUDE_DIRS duktape.h) + +find_library(DUKTAPE_LIBRARY duktape) +find_library(DUKTAPE_MODULE_LIBRARY duktape_module) + +set(DUKTAPE_LIBRARIES "${DUKTAPE_LIBRARY} ${DUKTAPE_MODULE_LIBRARY}") + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(DUKTAPE DEFAULT_MSG + DUKTAPE_INCLUDE_DIRS DUKTAPE_LIBRARY DUKTAPE_MODULE_LIBRARY) + +mark_as_advanced(DUKTAPE_INCLUDE_DIRS DUKTAPE_LIBRARY) diff --git a/scripts/Dockerfile b/scripts/Dockerfile index 018376e..355d480 100644 --- a/scripts/Dockerfile +++ b/scripts/Dockerfile @@ -3,7 +3,20 @@ MAINTAINER Tindy X # build minimized RUN apk add git g++ build-base linux-headers cmake && \ - apk add libressl-dev curl-dev rapidjson-dev libevent-dev pcre2-dev yaml-cpp-dev && \ + apk add libressl-dev curl-dev rapidjson-dev libevent-dev pcre2-dev yaml-cpp-dev python2 py2-pip && \ + git clone https://github.com/svaarala/duktape --depth=1 && \ + cd duktape && \ + pip2 install PyYAML && \ + python2 util/dist.py && \ + cd dist/src && \ + cc -c -O3 -o duktape.o duktape.c && \ + cc -c -O3 -o duk_module_node.o -I. ../extras/module-node/duk_module_node.c && \ + ar cr libduktape.a duktape.o && \ + ar cr libduktape_module.a duk_module_node.o && \ + install -m0644 *.a /usr/lib && \ + install -m0644 duk*.h /usr/include && \ + install -m0644 ../extras/module-node/duk_module_node.h /usr/include && \ + cd ../../.. && \ git clone https://github.com/tindy2013/subconverter && \ cd subconverter && \ cmake . && \ @@ -13,7 +26,7 @@ RUN apk add git g++ build-base linux-headers cmake && \ cd .. && \ rm -rf subconverter && \ apk add pcre2 libcurl yaml-cpp libevent && \ - apk del git gcc g++ build-base linux-headers cmake libressl-dev curl-dev rapidjson-dev libevent-dev pcre2-dev yaml-cpp-dev + apk del git gcc g++ build-base linux-headers cmake libressl-dev curl-dev rapidjson-dev libevent-dev pcre2-dev yaml-cpp-dev python2 py2-pip # set entry WORKDIR /base diff --git a/scripts/build.alpine.release.sh b/scripts/build.alpine.release.sh index 6079fbb..5fa222f 100644 --- a/scripts/build.alpine.release.sh +++ b/scripts/build.alpine.release.sh @@ -1,15 +1,29 @@ #!/bin/bash set -xe -apk add gcc g++ build-base linux-headers cmake make autoconf automake libtool +apk add gcc g++ build-base linux-headers cmake make autoconf automake libtool python2 py2-pip apk add openssl-dev openssl-libs-static curl curl-dev curl-static nghttp2-static zlib-dev rapidjson-dev libevent-dev libevent-static zlib-static pcre2-dev bzip2-static -git clone https://github.com/jbeder/yaml-cpp +git clone https://github.com/jbeder/yaml-cpp --depth=1 cd yaml-cpp cmake -DYAML_CPP_BUILD_TESTS=OFF -DYAML_CPP_BUILD_TOOLS=OFF . > /dev/null make install -j2 > /dev/null cd .. +git clone https://github.com/svaarala/duktape --depth=1 +cd duktape +pip2 install PyYAML +python2 util/dist.py +cd dist/src +cc -c -O3 -o duktape.o duktape.c +cc -c -O3 -o duk_module_node.o -I. ../extras/module-node/duk_module_node.c +ar cr libduktape.a duktape.o +ar cr libduktape_module.a duk_module_node.o +install -m0644 *.a /usr/lib +install -m0644 duk*.h /usr/include +install -m0644 ../extras/module-node/duk_module_node.h /usr/include +cd ../../.. + cmake . make -j2 rm subconverter diff --git a/scripts/build.clang.sh b/scripts/build.clang.sh index 5dc9165..ae2670d 100644 --- a/scripts/build.clang.sh +++ b/scripts/build.clang.sh @@ -15,6 +15,6 @@ c++ -std=c++17 -Wall -fexceptions -c src/upload.cpp -o obj/upload.o c++ -std=c++17 -Wall -fexceptions -c src/templates.cpp -o obj/templates.o c++ -std=c++17 -Wall -fexceptions -c src/webget.cpp -o obj/webget.o c++ -std=c++17 -Wall -fexceptions -c src/webserver_libevent.cpp -o obj/webserver_libevent.o -c++ -o subconverter obj/*.o -lpcre2-8 -levent -lpthread -lyaml-cpp -lcurl -lssl -lcrypto -lz -O3 -s +c++ -o subconverter obj/*.o -lpcre2-8 -levent -lpthread -lyaml-cpp -lcurl -lssl -lcrypto -lz -O3 -s -lduktape -lduktape_module chmod +x subconverter diff --git a/scripts/build.macos.release.sh b/scripts/build.macos.release.sh index 0eb76b7..a4f6146 100644 --- a/scripts/build.macos.release.sh +++ b/scripts/build.macos.release.sh @@ -3,7 +3,7 @@ set -xe brew reinstall rapidjson libevent zlib pcre2 bzip2 libssh2 pkgconfig -git clone https://github.com/curl/curl +git clone https://github.com/curl/curl --depth=1 cd curl #./buildconf > /dev/null #./configure --with-ssl=/usr/local/opt/openssl@1.1 --without-mbedtls --disable-ldap --disable-ldaps --disable-rtsp --without-libidn2 > /dev/null @@ -11,14 +11,29 @@ cmake -DHTTP_ONLY=ON -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=OFF -DOPENSSL_ROOT_ make -j8 > /dev/null cd .. -git clone https://github.com/jbeder/yaml-cpp +git clone https://github.com/jbeder/yaml-cpp --depth=1 cd yaml-cpp cmake -DYAML_CPP_BUILD_TESTS=OFF -DYAML_CPP_BUILD_TOOLS=OFF . > /dev/null make install -j8 > /dev/null cd .. +git clone https://github.com/svaarala/duktape --depth=1 +cd duktape +pip2 install PyYAML +python2 util/dist.py +cd dist/src +cc -c -O3 -o duktape.o duktape.c +cc -c -O3 -o duk_module_node.o -I. ../extras/module-node/duk_module_node.c +ar cr libduktape.a duktape.o +ar cr libduktape_module.a duk_module_node.o +install -m0644 *.a /usr/lib +install -m0644 duk*.h /usr/include +install -m0644 ../extras/module-node/duk_module_node.h /usr/include +cd ../../.. + cp curl/lib/libcurl.a . cp yaml-cpp/libyaml-cpp.a . +cp duktape/dist/src/*.a . cp /usr/local/lib/libevent.a . cp /usr/local/opt/zlib/lib/libz.a . cp /usr/local/opt/openssl@1.1/lib/libssl.a . @@ -31,7 +46,7 @@ export CMAKE_CXX_FLAGS="-I/usr/local/include -I/usr/local/opt/openssl@1.1/includ cmake -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl@1.1 . make -j8 rm subconverter -c++ -Xlinker -unexported_symbol -Xlinker "*" -o base/subconverter CMakeFiles/subconverter.dir/src/*.o libpcre2-8.a libevent.a libcurl.a libz.a libssl.a libcrypto.a libyaml-cpp.a libbz2.a libssh2.a -ldl -lpthread -O3 +c++ -Xlinker -unexported_symbol -Xlinker "*" -o base/subconverter CMakeFiles/subconverter.dir/src/*.o libpcre2-8.a libevent.a libcurl.a libz.a libssl.a libcrypto.a libyaml-cpp.a libbz2.a libssh2.a libduktape.a libduktape_module.a -ldl -lpthread -O3 cd base chmod +rx subconverter diff --git a/scripts/config.termux.sh b/scripts/config.termux.sh index 72521a2..b49a2ed 100644 --- a/scripts/config.termux.sh +++ b/scripts/config.termux.sh @@ -3,15 +3,29 @@ set -xe apt update apt install -y git cmake clang pkg-config -apt install -y libevent libcurl openssl pcre2 +apt install -y libevent libcurl openssl pcre2 python2 -git clone https://github.com/jbeder/yaml-cpp +git clone https://github.com/jbeder/yaml-cpp --depth=1 cd yaml-cpp cmake -DCMAKE_INSTALL_PREFIX=/data/data/com.termux/files/usr -DYAML_CPP_BUILD_TESTS=OFF -DYAML_CPP_BUILD_TOOLS=OFF . make install -j3 cd .. -git clone https://github.com/tencent/rapidjson +git clone https://github.com/tencent/rapidjson --depth=1 cd rapidjson cp -r include/* /data/data/com.termux/files/usr/include/ cd .. + +git clone https://github.com/svaarala/duktape --depth=1 +cd duktape +pip2 install PyYAML +python2 util/dist.py +cd dist/src +cc -c -O3 -o duktape.o duktape.c +cc -c -O3 -o duk_module_node.o -I. ../extras/module-node/duk_module_node.c +ar cr libduktape.a duktape.o +ar cr libduktape_module.a duk_module_node.o +install -m0644 *.a /data/data/com.termux/files/usr/lib +install -m0644 duk*.h /data/data/com.termux/files/usr/include +install -m0644 ../extras/module-node/duk_module_node.h /data/data/com.termux/files/usr/include +cd ../../.. diff --git a/src/interfaces.cpp b/src/interfaces.cpp index c1b80ab..c521147 100644 --- a/src/interfaces.cpp +++ b/src/interfaces.cpp @@ -19,6 +19,7 @@ #include "string_hash.h" #include "templates.h" #include "upload.h" +#include "script_duktape.h" #define MAX_EXTCONF_RULESET_COUNT 64 @@ -55,6 +56,7 @@ 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; +std::string sort_script, filter_script; std::string clash_rule_base; string_array clash_extra_group; @@ -84,6 +86,11 @@ template T safe_as (const YAML::Node& node) return T(); }; +template void operator >>= (const YAML::Node& node, T& i) +{ + i = safe_as(node); +}; + std::string parseProxy(const std::string &source) { std::string proxy = source; @@ -516,6 +523,9 @@ void readYAMLConf(YAML::Node &node) section["exclude_remarks"] >> def_exclude_remarks; if(section["include_remarks"].IsSequence()) section["include_remarks"] >> def_include_remarks; + filter_script = safe_as(section["enable_filter"]) ? safe_as(section["filter_script"]) : ""; + if(startsWith(filter_script, "path:")) + filter_script = fileGet(filter_script.substr(5), false); section["base_path"] >> base_path; section["clash_rule_base"] >> clash_rule_base; section["surge_rule_base"] >> surge_rule_base; @@ -561,6 +571,7 @@ void readYAMLConf(YAML::Node &node) tfo_flag.set(safe_as(section["tcp_fast_open_flag"])); scv_flag.set(safe_as(section["skip_cert_verify_flag"])); section["sort_flag"] >> do_sort; + section["sort_script"] >> sort_script; section["filter_deprecated_nodes"] >> filter_deprecated; section["append_sub_userinfo"] >> append_userinfo; section["clash_use_new_field_name"] >> clash_use_new_field_name; @@ -753,6 +764,9 @@ void readConf() ini.GetAll("exclude_remarks", def_exclude_remarks); if(ini.ItemPrefixExist("include_remarks")) ini.GetAll("include_remarks", def_include_remarks); + filter_script = ini.GetBool("enable_filter") ? replace_all_distinct(ini.Get("filter_script"), "\\n", "\n") : ""; + if(startsWith(filter_script, "path:")) + filter_script = fileGet(filter_script.substr(5), false); ini.GetIfExist("base_path", base_path); ini.GetIfExist("clash_rule_base", clash_rule_base); ini.GetIfExist("surge_rule_base", surge_rule_base); @@ -785,6 +799,7 @@ void readConf() tfo_flag.set(ini.Get("tcp_fast_open_flag")); scv_flag.set(ini.Get("skip_cert_verify_flag")); ini.GetBoolIfExist("sort_flag", do_sort); + sort_script = replace_all_distinct(ini.Get("sort_script"), "\\n", "\n"); 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); @@ -1163,7 +1178,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) /// switches with default value tribool upload = getUrlArg(argument, "upload"), emoji = getUrlArg(argument, "emoji"); tribool append_type = getUrlArg(argument, "append_type"), tfo = getUrlArg(argument, "tfo"), udp = getUrlArg(argument, "udp"), nodelist = getUrlArg(argument, "list"); - tribool sort_flag = getUrlArg(argument, "sort"); + tribool sort_flag = getUrlArg(argument, "sort"), use_sort_script = getUrlArg(argument, "sort_script"); tribool clash_new_field = getUrlArg(argument, "new_name"), clash_script = getUrlArg(argument, "script"), add_insert = getUrlArg(argument, "insert"); tribool scv = getUrlArg(argument, "scv"), fdn = getUrlArg(argument, "fdn"), expand = getUrlArg(argument, "expand"), append_sub_userinfo = getUrlArg(argument, "append_info"); tribool prepend_insert = getUrlArg(argument, "prepend"); @@ -1246,6 +1261,9 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) ext.skip_cert_verify.define(scv_flag); ext.sort_flag = sort_flag.get(do_sort); + use_sort_script.define(sort_script.size()); + if(ext.sort_flag && use_sort_script) + ext.sort_script = sort_script; 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(); @@ -1415,6 +1433,31 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) else nodes.emplace_back(x); } + //run filter script + if(filter_script.size()) + { + duk_context *ctx = duktape_init(); + if(ctx) + { + defer(duk_destroy_heap(ctx);) + if(duktape_peval(ctx, filter_script) == 0) + { + auto filter = [&](const nodeInfo &x) + { + duk_get_global_string(ctx, "filter"); + duktape_push_nodeinfo(ctx, x); + duk_pcall(ctx, 1); + return !duktape_get_res_bool(ctx); + }; + nodes.erase(std::remove_if(nodes.begin(), nodes.end(), filter), nodes.end()); + } + else + { + writeLog(0, "Error when trying to parse script:\n" + duktape_get_err_stack(ctx), LOG_LEVEL_ERROR); + duk_pop(ctx); /// pop err + } + } + } //check custom group name if(group.size()) @@ -1662,14 +1705,6 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) std::string simpleToClashR(RESPONSE_CALLBACK_ARGS) { std::string url = argument.size() <= 8 ? "" : argument.substr(8); - std::string base_content; - std::vector nodes; - string_array extra_group, extra_ruleset, include_remarks, exclude_remarks; - std::vector rca; - std::string subInfo; - - if(!url.size() && !api_mode) - url = default_url; if(!url.size() || argument.substr(0, 8) != "sublink=") { *status_code = 400; @@ -1680,71 +1715,7 @@ std::string simpleToClashR(RESPONSE_CALLBACK_ARGS) *status_code = 400; return "Please insert your subscription link instead of clicking the default link."; } - if(insert_url.size()) - url = insert_url + "|" + url; - if(!api_mode || cfw_child_process) - readConf(); - - extra_group = clash_extra_group; - - if(update_ruleset_on_request || cfw_child_process) - refreshRulesets(rulesets, ruleset_content_array); - rca = ruleset_content_array; - - extra_settings ext = {true, overwrite_original_rules, safe_get_renames(), safe_get_emojis(), add_emoji, remove_old_emoji, append_proxy_type, false, do_sort, filter_deprecated, clash_use_new_field_name, false, "", "", ""}; - - std::string proxy = parseProxy(proxy_subscription); - - include_remarks = def_include_remarks; - exclude_remarks = def_exclude_remarks; - - //start parsing urls - int groupID = 0; - string_array dummy; - string_array urls = split(url, "|"); - for(std::string &x : urls) - { - x = trim(x); - //std::cerr<<"Fetching node data from url '"< do_concat(__defer_deleter_,__LINE__) (nullptr, [&](...){x}); +template class __defer_struct final {private: T fn; bool __cancelled = false; public: __defer_struct(T func) : fn(std::move(func)) {} ~__defer_struct() {if(!__cancelled) fn();} void cancel() {__cancelled = true;} }; +//#define defer(x) std::unique_ptr do_concat(__defer_deleter_,__LINE__) (nullptr, [&](...){x}); +#define defer(x) __defer_struct do_concat(__defer_deleter,__COUNTER__) ([&](...){x;}); typedef std::string::size_type string_size; typedef std::vector string_array; diff --git a/src/nodemanip.cpp b/src/nodemanip.cpp index cca392d..cef809b 100644 --- a/src/nodemanip.cpp +++ b/src/nodemanip.cpp @@ -8,6 +8,7 @@ #include "logger.h" #include "webget.h" #include "speedtestutil.h" +#include "script_duktape.h" std::string override_conf_port; bool ss_libev, ssr_libev; @@ -26,18 +27,54 @@ int addNodes(std::string link, std::vector &allNodes, int groupID, std int linkType = -1; std::vector nodes; nodeInfo node; - std::string strSub, extra_headers; + std::string strSub, extra_headers, custom_group; // TODO: replace with startsWith if appropriate link = replace_all_distinct(link, "\"", ""); + + /// script:filepath,arg1,arg2,... + if(startsWith(link, "script:") && authorized) /// process subscription with script + { + writeLog(0, "Found script link. Start running...", LOG_LEVEL_INFO); + string_array args = split(link.substr(7), ","); + if(args.size() >= 1) + { + std::string script = fileGet(args[0], false); + duk_context *ctx = duktape_init(); + defer(duk_destroy_heap(ctx);) + duktape_peval(ctx, script); + duk_get_global_string(ctx, "parse"); + for(size_t i = 1; i < args.size(); i++) + duk_push_string(ctx, trim(args[i]).c_str()); + if(duk_pcall(ctx, args.size() - 1) == 0) + link = duktape_get_res_str(ctx); + else + { + writeLog(0, "Error when trying to evaluate script:\n" + duktape_get_err_stack(ctx), LOG_LEVEL_ERROR); + duk_pop(ctx); /// pop err + } + } + } + + /// tag:group_name,link + if(startsWith(link, "tag:")) + { + string_size pos = link.find(","); + if(pos != link.npos) + { + custom_group = link.substr(4, pos - 4); + link.erase(0, pos + 1); + } + } + writeLog(LOG_TYPE_INFO, "Received Link."); - if(strFind(link, "https://t.me/socks") || strFind(link, "tg://socks")) + if(startsWith(link, "https://t.me/socks") || startsWith(link, "tg://socks")) linkType = SPEEDTEST_MESSAGE_FOUNDSOCKS; - else if(strFind(link, "https://t.me/http") || strFind(link, "tg://http")) + else if(startsWith(link, "https://t.me/http") || startsWith(link, "tg://http")) linkType = SPEEDTEST_MESSAGE_FOUNDHTTP; - else if(startsWith(link, "http://") || startsWith(link, "https://") || startsWith(link, "data:") || strFind(link, "surge:///install-config")) + else if(startsWith(link, "http://") || startsWith(link, "https://") || startsWith(link, "data:") || startsWith(link, "surge:///install-config")) linkType = SPEEDTEST_MESSAGE_FOUNDSUB; - else if(strFind(link, "Netch://")) + else if(startsWith(link, "Netch://")) linkType = SPEEDTEST_MESSAGE_FOUNDNETCH; else if(fileExist(link)) linkType = SPEEDTEST_MESSAGE_FOUNDLOCAL; @@ -46,7 +83,7 @@ int addNodes(std::string link, std::vector &allNodes, int groupID, std { case SPEEDTEST_MESSAGE_FOUNDSUB: writeLog(LOG_TYPE_INFO, "Downloading subscription data..."); - if(strFind(link, "surge:///install-config")) //surge config link + if(startsWith(link, "surge:///install-config")) //surge config link link = UrlDecode(getUrlArg(link, "url")); strSub = webGet(link, proxy, extra_headers, cache_subscription); /* @@ -82,7 +119,11 @@ int addNodes(std::string link, std::vector &allNodes, int groupID, std } filterNodes(nodes, exclude_remarks, include_remarks, groupID); for(nodeInfo &x : nodes) + { x.groupID = groupID; + if(custom_group.size()) + x.group = custom_group; + } copyNodes(nodes, allNodes); } else @@ -110,7 +151,11 @@ int addNodes(std::string link, std::vector &allNodes, int groupID, std } filterNodes(nodes, exclude_remarks, include_remarks, groupID); for(nodeInfo &x : nodes) + { x.groupID = groupID; + if(custom_group.size()) + x.group = custom_group; + } copyNodes(nodes, allNodes); break; default: @@ -121,6 +166,8 @@ int addNodes(std::string link, std::vector &allNodes, int groupID, std return -1; } node.groupID = groupID; + if(custom_group.size()) + node.group = custom_group; allNodes.push_back(node); } return 0; diff --git a/src/script.cpp b/src/script.cpp new file mode 100644 index 0000000..b70182a --- /dev/null +++ b/src/script.cpp @@ -0,0 +1,224 @@ +#include +#include +#include +#include + +#include "misc.h" +#include "multithread.h" +#include "nodeinfo.h" + +extern int cache_config; + +std::string parseProxy(const std::string &source); + +std::string foldPathString(const std::string &path) +{ + std::string output = path; + string_size pos_up, pos_slash, pos_unres = 0; + do + { + pos_up = output.find("../", pos_unres); + if(pos_up == output.npos) + break; + else if(pos_up == 0) + { + pos_unres = pos_up + 3; + continue; + } + pos_slash = output.rfind("/", pos_up - 1); + if(pos_slash != output.npos) + { + pos_slash = output.rfind("/", pos_slash - 1); + if(pos_slash != output.npos) + output.erase(pos_slash + 1, pos_up - pos_slash + 2); + else + output.erase(0, pos_up + 3); + } + else + pos_unres = pos_up + 3; + } while(pos_up != output.npos); + return output; +} + +duk_ret_t cb_resolve_module(duk_context *ctx) +{ + const char *requested_id = duk_get_string(ctx, 0); + const char *parent_id = duk_get_string(ctx, 1); /* calling module */ + //const char *resolved_id; + std::string resolved_id; + if(strlen(parent_id)) + { + std::string parent_path = parent_id; + string_size pos = parent_path.rfind("/"); + if(pos != parent_path.npos) + resolved_id += parent_path.substr(0, pos + 1); + } + resolved_id += requested_id; + if(!endsWith(resolved_id, ".js")) + resolved_id += ".js"; + resolved_id = foldPathString(resolved_id); + + /* Arrive at the canonical module ID somehow. */ + std::cout<c_str()); + else + duk_push_undefined(ctx); + index++; + } + va_end(vl); + return duk_pcall(ctx, nargs); +} + +int duktape_get_res_int(duk_context *ctx) +{ + int retval = duk_to_int(ctx, -1); + duk_pop(ctx); + return retval; +} + +int duktape_push_nodeinfo(duk_context *ctx, const nodeInfo &node) +{ + duk_push_object(ctx); + duk_push_string(ctx, node.group.c_str()); + duk_put_prop_string(ctx, -2, "Group"); + duk_push_int(ctx, node.groupID); + duk_put_prop_string(ctx, -2, "GroupID"); + duk_push_int(ctx, node.id); + duk_put_prop_string(ctx, -2, "Index"); + duk_push_string(ctx, node.remarks.c_str()); + duk_put_prop_string(ctx, -2, "Remark"); + duk_push_string(ctx, node.proxyStr.c_str()); + duk_put_prop_string(ctx, -2, "ProxyInfo"); + return 0; +} + +int duktape_push_nodeinfo_arr(duk_context *ctx, const nodeInfo &node, duk_idx_t index) +{ + duk_push_object(ctx); + duk_push_string(ctx, "Group"); + duk_push_string(ctx, node.group.c_str()); + duk_def_prop(ctx, index - 2, DUK_DEFPROP_HAVE_VALUE); + duk_push_string(ctx, "GroupID"); + duk_push_int(ctx, node.groupID); + duk_def_prop(ctx, index - 2, DUK_DEFPROP_HAVE_VALUE); + duk_push_string(ctx, "Index"); + duk_push_int(ctx, node.id); + duk_def_prop(ctx, index - 2, DUK_DEFPROP_HAVE_VALUE); + duk_push_string(ctx, "Remark"); + duk_push_string(ctx, node.remarks.c_str()); + duk_def_prop(ctx, index - 2, DUK_DEFPROP_HAVE_VALUE); + duk_push_string(ctx, "ProxyInfo"); + duk_push_string(ctx, node.proxyStr.c_str()); + duk_def_prop(ctx, index - 2, DUK_DEFPROP_HAVE_VALUE); + return 0; +} + +std::string duktape_get_res_str(duk_context *ctx) +{ + std::string retstr = duk_safe_to_string(ctx, -1); + duk_pop(ctx); + return retstr; +} + +bool duktape_get_res_bool(duk_context *ctx) +{ + bool ret = duk_to_boolean(ctx, -1); + duk_pop(ctx); + return ret; +} + +std::string duktape_get_err_stack(duk_context *ctx) +{ + duk_get_prop_string(ctx, -1, "stack"); + std::string stackstr = duk_get_string(ctx, -1); + duk_pop(ctx); + return stackstr; +} diff --git a/src/script_duktape.h b/src/script_duktape.h new file mode 100644 index 0000000..f031f0d --- /dev/null +++ b/src/script_duktape.h @@ -0,0 +1,19 @@ +#ifndef SCRIPT_DUKTAPE_H_INCLUDED +#define SCRIPT_DUKTAPE_H_INCLUDED + +#include +#include + +#include "nodeinfo.h" + +duk_context *duktape_init(); +int duktape_push_nodeinfo(duk_context *ctx, const nodeInfo &node); +int duktape_push_nodeinfo_arr(duk_context *ctx, const nodeInfo &node, duk_idx_t index = -1); +int duktape_peval(duk_context *ctx, const std::string &script); +int duktape_call_function(duk_context *ctx, const std::string &name, size_t nargs, ...); +int duktape_get_res_int(duk_context *ctx); +std::string duktape_get_res_str(duk_context *ctx); +bool duktape_get_res_bool(duk_context *ctx); +std::string duktape_get_err_stack(duk_context *ctx); + +#endif // SCRIPT_DUKTAPE_H_INCLUDED diff --git a/src/speedtestutil.cpp b/src/speedtestutil.cpp index 7adc616..a49a2a5 100644 --- a/src/speedtestutil.cpp +++ b/src/speedtestutil.cpp @@ -107,7 +107,7 @@ void explodeVmess(std::string vmess, const std::string &custom_port, nodeInfo &n node.remarks = ps; node.server = add; node.port = to_int(port, 1); - node.proxyStr = vmessConstruct(add, port, type, id, aid, net, "auto", path, host, "", tls); + node.proxyStr = vmessConstruct(node.group, ps, add, port, type, id, aid, net, "auto", path, host, "", tls); } void explodeVmessConf(std::string content, const std::string &custom_port, bool libev, std::vector &nodes) @@ -185,11 +185,11 @@ void explodeVmessConf(std::string content, const std::string &custom_port, bool } } node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; - node.proxyStr = vmessConstruct(add, port, type, id, aid, net, cipher, path, host, edge, tls, udp, tfo, scv); node.group = V2RAY_DEFAULT_GROUP; node.remarks = add + ":" + port; node.server = add; node.port = to_int(port, 1); + node.proxyStr = vmessConstruct(node.group, node.remarks, add, port, type, id, aid, net, cipher, path, host, edge, tls, udp, tfo, scv); nodes.push_back(node); } return; @@ -242,19 +242,19 @@ void explodeVmessConf(std::string content, const std::string &custom_port, bool json["vmess"][i]["security"] >> cipher; group = V2RAY_DEFAULT_GROUP; node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; - node.proxyStr = vmessConstruct(add, port, type, id, aid, net, cipher, path, host, "", tls, udp, tfo, scv); + node.proxyStr = vmessConstruct(group, ps, add, port, type, id, aid, net, cipher, path, host, "", tls, udp, tfo, scv); break; case 3: //ss config json["vmess"][i]["id"] >> id; json["vmess"][i]["security"] >> cipher; group = SS_DEFAULT_GROUP; node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.proxyStr = ssConstruct(add, port, id, cipher, "", "", ps, libev, udp, tfo, scv); + node.proxyStr = ssConstruct(group, ps, add, port, id, cipher, "", "", libev, udp, tfo, scv); break; case 4: //socks config group = SOCKS_DEFAULT_GROUP; node.linkType = SPEEDTEST_MESSAGE_FOUNDSOCKS; - node.proxyStr = socksConstruct(ps, add, port, "", "", udp, tfo, scv); + node.proxyStr = socksConstruct(group, ps, add, port, "", "", udp, tfo, scv); break; default: continue; @@ -338,7 +338,7 @@ void explodeSS(std::string ss, bool libev, const std::string &custom_port, nodeI node.remarks = ps; node.server = server; node.port = to_int(port, 1); - node.proxyStr = ssConstruct(server, port, password, method, plugin, pluginopts, ps, libev); + node.proxyStr = ssConstruct(group, ps, server, port, password, method, plugin, pluginopts, libev); } void explodeSSD(std::string link, bool libev, const std::string &custom_port, std::vector &nodes) @@ -417,7 +417,7 @@ void explodeSSD(std::string link, bool libev, const std::string &custom_port, st node.remarks = remarks; node.server = server; node.port = to_int(port, 1); - node.proxyStr = ssConstruct(server, port, password, method, plugin, pluginopts, remarks, libev); + node.proxyStr = ssConstruct(group, remarks, server, port, password, method, plugin, pluginopts, libev); node.id = index; nodes.push_back(node); index++; @@ -461,7 +461,7 @@ void explodeSSAndroid(std::string ss, bool libev, const std::string &custom_port node.remarks = ps; node.server = server; node.port = to_int(port, 1); - node.proxyStr = ssConstruct(server, port, password, method, plugin, pluginopts, ps, libev); + node.proxyStr = ssConstruct(group, ps, server, port, password, method, plugin, pluginopts, libev); nodes.push_back(node); index++; } @@ -499,7 +499,7 @@ void explodeSSConf(std::string content, const std::string &custom_port, bool lib node.id = index; node.server = server; node.port = to_int(port, 1); - node.proxyStr = ssConstruct(server, port, password, method, plugin, pluginopts, ps, libev); + node.proxyStr = ssConstruct(group, ps, server, port, password, method, plugin, pluginopts, libev); nodes.push_back(node); index++; } @@ -561,7 +561,7 @@ void explodeSSR(std::string ssr, bool ss_libev, bool ssr_libev, const std::strin if(find(ss_ciphers.begin(), ss_ciphers.end(), method) != ss_ciphers.end() && (obfs.empty() || obfs == "plain") && (protocol.empty() || protocol == "origin")) { node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.proxyStr = ssConstruct(server, port, password, method, "", "", remarks, ss_libev); + node.proxyStr = ssConstruct(group, remarks, server, port, password, method, "", "", ss_libev); } else { @@ -597,7 +597,7 @@ void explodeSSRConf(std::string content, const std::string &custom_port, bool ss pluginopts = GetMember(json, "plugin_opts"); node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; node.group = SS_DEFAULT_GROUP; - node.proxyStr = ssConstruct(server, port, password, method, plugin, pluginopts, node.remarks, ss_libev); + node.proxyStr = ssConstruct(node.group, node.remarks, server, port, password, method, plugin, pluginopts, ss_libev); } else { @@ -687,7 +687,7 @@ void explodeSocks(std::string link, const std::string &custom_port, nodeInfo &no node.remarks = remarks; node.server = server; node.port = to_int(port, 1); - node.proxyStr = socksConstruct(remarks, server, port, username, password); + node.proxyStr = socksConstruct(group, remarks, server, port, username, password); } void explodeHTTP(std::string link, const std::string &custom_port, nodeInfo &node) @@ -714,7 +714,7 @@ void explodeHTTP(std::string link, const std::string &custom_port, nodeInfo &nod node.remarks = remarks; node.server = server; node.port = to_int(port, 1); - node.proxyStr = httpConstruct(remarks, server, port, username, password, strFind(link, "/https")); + node.proxyStr = httpConstruct(group, remarks, server, port, username, password, strFind(link, "/https")); } void explodeHTTPSub(std::string link, const std::string &custom_port, nodeInfo &node) @@ -776,7 +776,7 @@ void explodeHTTPSub(std::string link, const std::string &custom_port, nodeInfo & node.remarks = remarks; node.server = server; node.port = to_int(port, 1); - node.proxyStr = httpConstruct(remarks, server, port, username, password, tls); + node.proxyStr = httpConstruct(group, remarks, server, port, username, password, tls); } void explodeTrojan(std::string trojan, const std::string &custom_port, nodeInfo &node) @@ -827,7 +827,7 @@ void explodeTrojan(std::string trojan, const std::string &custom_port, nodeInfo node.remarks = remark; node.server = server; node.port = to_int(port, 1); - node.proxyStr = trojanConstruct(remark, server, port, psk, host, true); + node.proxyStr = trojanConstruct(node.group, remark, server, port, psk, host, true); } void explodeQuan(std::string quan, const std::string &custom_port, nodeInfo &node) @@ -889,23 +889,24 @@ void explodeQuan(std::string quan, const std::string &custom_port, nodeInfo &nod node.remarks = ps; node.server = add; node.port = to_int(port, 1); - node.proxyStr = vmessConstruct(add, port, type, id, aid, net, cipher, path, host, edge, tls); + node.proxyStr = vmessConstruct(group, ps, add, port, type, id, aid, net, cipher, path, host, edge, tls); } } void explodeNetch(std::string netch, bool ss_libev, bool ssr_libev, const std::string &custom_port, nodeInfo &node) { Document json; - std::string type, remark, address, port, username, password, method, plugin, pluginopts, protocol, protoparam, obfs, obfsparam, id, aid, transprot, faketype, host, edge, path, tls; + std::string type, group, remark, address, port, username, password, method, plugin, pluginopts, protocol, protoparam, obfs, obfsparam, id, aid, transprot, faketype, host, edge, path, tls; tribool udp, tfo, scv; netch = urlsafe_base64_decode(netch.substr(8)); json.Parse(netch.data()); if(json.HasParseError()) return; - json["Type"] >> type; - json["Remark"] >> remark; - json["Hostname"] >> address; + type = GetMember(json, "Type"); + group = GetMember(json, "Group"); + remark = GetMember(json, "Remark"); + address = GetMember(json, "Hostname"); udp = GetMember(json, "EnableUDP"); tfo = GetMember(json, "EnableTFO"); scv = GetMember(json, "AllowInsecure"); @@ -921,9 +922,11 @@ void explodeNetch(std::string netch, bool ss_libev, bool ssr_libev, const std::s case "SS"_hash: plugin = GetMember(json, "Plugin"); pluginopts = GetMember(json, "PluginOption"); - node.group = SS_DEFAULT_GROUP; + if(group.empty()) + group = SS_DEFAULT_GROUP; + node.group = group; node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.proxyStr = ssConstruct(address, port, password, method, plugin, pluginopts, remark, ss_libev, udp, tfo, scv); + node.proxyStr = ssConstruct(group, remark, address, port, password, method, plugin, pluginopts, ss_libev, udp, tfo, scv); break; case "SSR"_hash: protocol = GetMember(json, "Protocol"); @@ -932,17 +935,21 @@ void explodeNetch(std::string netch, bool ss_libev, bool ssr_libev, const std::s { plugin = GetMember(json, "Plugin"); pluginopts = GetMember(json, "PluginOption"); - node.group = SS_DEFAULT_GROUP; + if(group.empty()) + group = SS_DEFAULT_GROUP; + node.group = group; node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.proxyStr = ssConstruct(address, port, password, method, plugin, pluginopts, remark, ss_libev, udp, tfo, scv); + node.proxyStr = ssConstruct(group, remark, address, port, password, method, plugin, pluginopts, ss_libev, udp, tfo, scv); } else { protoparam = GetMember(json, "ProtocolParam"); obfsparam = GetMember(json, "OBFSParam"); - node.group = SSR_DEFAULT_GROUP; + if(group.empty()) + group = SSR_DEFAULT_GROUP; + node.group = group; node.linkType = SPEEDTEST_MESSAGE_FOUNDSSR; - node.proxyStr = ssrConstruct(SSR_DEFAULT_GROUP, remark, base64_encode(remark), address, port, protocol, method, obfs, password, obfsparam, protoparam, ssr_libev, udp, tfo, scv); + node.proxyStr = ssrConstruct(group, remark, base64_encode(remark), address, port, protocol, method, obfs, password, obfsparam, protoparam, ssr_libev, udp, tfo, scv); } break; case "VMess"_hash: @@ -955,34 +962,44 @@ void explodeNetch(std::string netch, bool ss_libev, bool ssr_libev, const std::s edge = GetMember(json, "Edge"); tls = GetMember(json, "TLSSecure"); node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; - node.group = V2RAY_DEFAULT_GROUP; - node.proxyStr = vmessConstruct(address, port, faketype, id, aid, transprot, method, path, host, edge, tls, udp, tfo, scv); + if(group.empty()) + group = V2RAY_DEFAULT_GROUP; + node.group = group; + node.proxyStr = vmessConstruct(group, remark, address, port, faketype, id, aid, transprot, method, path, host, edge, tls, udp, tfo, scv); break; case "Socks5"_hash: username = GetMember(json, "Username"); node.linkType = SPEEDTEST_MESSAGE_FOUNDSOCKS; - node.group = SOCKS_DEFAULT_GROUP; - node.proxyStr = socksConstruct(remark, address, port, username, password, udp, tfo, scv); + if(group.empty()) + group = SOCKS_DEFAULT_GROUP; + node.group = group; + node.proxyStr = socksConstruct(group, remark, address, port, username, password, udp, tfo, scv); break; case "HTTP"_hash: case "HTTPS"_hash: node.linkType = SPEEDTEST_MESSAGE_FOUNDHTTP; - node.group = HTTP_DEFAULT_GROUP; - node.proxyStr = httpConstruct(remark, address, port, username, password, type == "HTTPS", scv); + if(group.empty()) + group = HTTP_DEFAULT_GROUP; + node.group = group; + node.proxyStr = httpConstruct(group, remark, address, port, username, password, type == "HTTPS", scv); break; case "Trojan"_hash: host = GetMember(json, "Host"); tls = GetMember(json, "TLSSecure"); node.linkType = SPEEDTEST_MESSAGE_FOUNDTROJAN; - node.group = TROJAN_DEFAULT_GROUP; - node.proxyStr = trojanConstruct(remark, address, port, username, password, tls == "true", udp, tfo, scv); + if(group.empty()) + group = TROJAN_DEFAULT_GROUP; + node.group = group; + node.proxyStr = trojanConstruct(group, remark, address, port, username, password, tls == "true", udp, tfo, scv); break; case "Snell"_hash: obfs = GetMember(json, "OBFS"); host = GetMember(json, "Host"); node.linkType = SPEEDTEST_MESSAGE_FOUNDSNELL; - node.group = SNELL_DEFAULT_GROUP; - node.proxyStr = snellConstruct(remark, address, port, password, obfs, host, udp, tfo, scv); + if(group.empty()) + group = SNELL_DEFAULT_GROUP; + node.group = group; + node.proxyStr = snellConstruct(group, remark, address, port, password, obfs, host, udp, tfo, scv); break; default: return; @@ -1031,7 +1048,7 @@ void explodeClash(Node yamlnode, const std::string &custom_port, std::vector>= edge; node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; - node.proxyStr = vmessConstruct(server, port, type, id, aid, net, cipher, path, host, edge, tls, udp, tfo, scv); + node.proxyStr = vmessConstruct(group, ps, server, port, type, id, aid, net, cipher, path, host, edge, tls, udp, tfo, scv); break; case "ss"_hash: group = SS_DEFAULT_GROUP; @@ -1101,7 +1118,7 @@ void explodeClash(Node yamlnode, const std::string &custom_port, std::vector>= password; node.linkType = SPEEDTEST_MESSAGE_FOUNDSOCKS; - node.proxyStr = socksConstruct(ps, server, port, user, password); + node.proxyStr = socksConstruct(group, ps, server, port, user, password); break; case "ssr"_hash: group = SSR_DEFAULT_GROUP; @@ -1133,7 +1150,7 @@ void explodeClash(Node yamlnode, const std::string &custom_port, std::vector>= tls; node.linkType = SPEEDTEST_MESSAGE_FOUNDHTTP; - node.proxyStr = httpConstruct(ps, server, port, user, password, tls == "true", scv); + node.proxyStr = httpConstruct(group, ps, server, port, user, password, tls == "true", scv); break; case "trojan"_hash: group = TROJAN_DEFAULT_GROUP; @@ -1141,7 +1158,7 @@ void explodeClash(Node yamlnode, const std::string &custom_port, std::vector>= host; node.linkType = SPEEDTEST_MESSAGE_FOUNDTROJAN; - node.proxyStr = trojanConstruct(ps, server, port, password, host, true, udp, tfo, scv); + node.proxyStr = trojanConstruct(group, ps, server, port, password, host, true, udp, tfo, scv); break; case "snell"_hash: group = SNELL_DEFAULT_GROUP; @@ -1150,7 +1167,7 @@ void explodeClash(Node yamlnode, const std::string &custom_port, std::vector>= host; node.linkType = SPEEDTEST_MESSAGE_FOUNDSNELL; - node.proxyStr = snellConstruct(ps, server, port, password, obfs, host, udp, tfo, scv); + node.proxyStr = snellConstruct(group, ps, server, port, password, obfs, host, udp, tfo, scv); break; default: continue; @@ -1224,7 +1241,7 @@ void explodeShadowrocket(std::string rocket, const std::string &custom_port, nod node.remarks = remarks; node.server = add; node.port = to_int(port, 0); - node.proxyStr = vmessConstruct(add, port, type, id, aid, net, cipher, path, host, "", tls); + node.proxyStr = vmessConstruct(node.group, remarks, add, port, type, id, aid, net, cipher, path, host, "", tls); } void explodeKitsunebi(std::string kit, const std::string &custom_port, nodeInfo &node) @@ -1278,7 +1295,7 @@ void explodeKitsunebi(std::string kit, const std::string &custom_port, nodeInfo node.remarks = remarks; node.server = add; node.port = to_int(port, 0); - node.proxyStr = vmessConstruct(add, port, type, id, aid, net, cipher, path, host, "", tls); + node.proxyStr = vmessConstruct(node.group, remarks, add, port, type, id, aid, net, cipher, path, host, "", tls); } bool explodeSurge(std::string surge, const std::string &custom_port, std::vector &nodes, bool libev) @@ -1385,7 +1402,7 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; node.group = SS_DEFAULT_GROUP; - node.proxyStr = ssConstruct(server, port, password, method, plugin, pluginopts, remarks, libev, udp, tfo, scv); + node.proxyStr = ssConstruct(node.group, remarks, server, port, password, method, plugin, pluginopts, libev, udp, tfo, scv); } //else // continue; @@ -1425,7 +1442,7 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; node.group = SS_DEFAULT_GROUP; - node.proxyStr = ssConstruct(server, port, password, method, plugin, pluginopts, remarks, libev, udp, tfo, scv); + node.proxyStr = ssConstruct(node.group, remarks, server, port, password, method, plugin, pluginopts, libev, udp, tfo, scv); break; case "socks5"_hash: //surge 3 style socks5 proxy node.linkType = SPEEDTEST_MESSAGE_FOUNDSOCKS; @@ -1454,7 +1471,7 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector default: continue; } } - node.proxyStr = socksConstruct(remarks, server, port, username, password, udp, tfo, scv); + node.proxyStr = socksConstruct(node.group, remarks, server, port, username, password, udp, tfo, scv); break; case "vmess"_hash: //surge 4 style vmess proxy server = trim(configs[1]); @@ -1502,7 +1519,7 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; node.group = V2RAY_DEFAULT_GROUP; - node.proxyStr = vmessConstruct(server, port, "", id, "0", net, method, path, host, edge, tls, udp, tfo, scv); + node.proxyStr = vmessConstruct(node.group, remarks, server, port, "", id, "0", net, method, path, host, edge, tls, udp, tfo, scv); break; case "http"_hash: //http proxy node.linkType = SPEEDTEST_MESSAGE_FOUNDHTTP; @@ -1526,7 +1543,7 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector default: continue; } } - node.proxyStr = httpConstruct(remarks, server, port, username, password, false, scv); + node.proxyStr = httpConstruct(node.group, remarks, server, port, username, password, false, scv); break; case "trojan"_hash: // surge 4 style trojan proxy node.linkType = SPEEDTEST_MESSAGE_FOUNDTROJAN; @@ -1556,7 +1573,7 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector if(host.empty() && !isIPv4(server) && !isIPv6(server)) host = server; - node.proxyStr = trojanConstruct(remarks, server, port, password, host, true, udp, tfo, scv); + node.proxyStr = trojanConstruct(node.group, remarks, server, port, password, host, true, udp, tfo, scv); break; case "snell"_hash: node.linkType = SPEEDTEST_MESSAGE_FOUNDSNELL; @@ -1588,7 +1605,7 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector if(host.empty() && !isIPv4(server) && !isIPv6(server)) host = server; - node.proxyStr = snellConstruct(remarks, server, port, password, plugin, host, udp, tfo, scv); + node.proxyStr = snellConstruct(node.group, remarks, server, port, password, plugin, host, udp, tfo, scv); break; default: switch(hash_(remarks)) @@ -1642,7 +1659,7 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector { node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; node.group = SS_DEFAULT_GROUP; - node.proxyStr = ssConstruct(server, port, password, method, plugin, pluginopts, remarks, libev, udp, tfo, scv); + node.proxyStr = ssConstruct(node.group, remarks, server, port, password, method, plugin, pluginopts, libev, udp, tfo, scv); } break; case "vmess"_hash: //quantumult x style vmess link @@ -1687,7 +1704,7 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; node.group = V2RAY_DEFAULT_GROUP; - node.proxyStr = vmessConstruct(server, port, "", id, "0", net, method, path, host, "", tls, udp, tfo, scv); + node.proxyStr = vmessConstruct(node.group, remarks, server, port, "", id, "0", net, method, path, host, "", tls, udp, tfo, scv); break; case "trojan"_hash: //quantumult x style trojan link server = trim(configs[0].substr(0, configs[0].rfind(":"))); @@ -1722,7 +1739,7 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector node.linkType = SPEEDTEST_MESSAGE_FOUNDTROJAN; node.group = TROJAN_DEFAULT_GROUP; - node.proxyStr = trojanConstruct(remarks, server, port, password, host, tls == "true", udp, tfo, scv); + node.proxyStr = trojanConstruct(node.group, remarks, server, port, password, host, tls == "true", udp, tfo, scv); break; case "http"_hash: //quantumult x style http links server = trim(configs[0].substr(0, configs[0].rfind(":"))); @@ -1760,7 +1777,7 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector node.linkType = SPEEDTEST_MESSAGE_FOUNDHTTP; node.group = HTTP_DEFAULT_GROUP; - node.proxyStr = httpConstruct(remarks, server, port, username, password, tls == "true", scv); + node.proxyStr = httpConstruct(node.group, remarks, server, port, username, password, tls == "true", scv); break; default: continue; @@ -1810,7 +1827,7 @@ void explodeSSTap(std::string sstap, const std::string &custom_port, std::vector case 5: //socks 5 json["configs"][i]["username"] >> user; node.linkType = SPEEDTEST_MESSAGE_FOUNDSOCKS; - node.proxyStr = socksConstruct(remarks, server, port, user, pass); + node.proxyStr = socksConstruct(group, remarks, server, port, user, pass); break; case 6: //ss/ssr json["configs"][i]["protocol"] >> protocol; @@ -1819,7 +1836,7 @@ void explodeSSTap(std::string sstap, const std::string &custom_port, std::vector if(find(ss_ciphers.begin(), ss_ciphers.end(), cipher) != ss_ciphers.end() && protocol == "origin" && obfs == "plain") //is ss { node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.proxyStr = ssConstruct(server, port, pass, cipher, "", "", remarks, ss_libev); + node.proxyStr = ssConstruct(group, remarks, server, port, pass, cipher, "", "", ss_libev); } else //is ssr cipher { diff --git a/src/speedtestutil.h b/src/speedtestutil.h index 2bb3076..0cda682 100644 --- a/src/speedtestutil.h +++ b/src/speedtestutil.h @@ -7,13 +7,13 @@ #include "misc.h" #include "nodeinfo.h" -std::string vmessConstruct(std::string add, std::string port, std::string type, std::string id, std::string aid, std::string net, std::string cipher, std::string path, std::string host, std::string edge, std::string tls, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool()); +std::string vmessConstruct(std::string group, std::string remarks, std::string add, std::string port, std::string type, std::string id, std::string aid, std::string net, std::string cipher, std::string path, std::string host, std::string edge, std::string tls, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool()); std::string ssrConstruct(std::string group, std::string remarks, std::string remarks_base64, std::string server, std::string port, std::string protocol, std::string method, std::string obfs, std::string password, std::string obfsparam, std::string protoparam, bool libev, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool()); -std::string ssConstruct(std::string server, std::string port, std::string password, std::string method, std::string plugin, std::string pluginopts, std::string remarks, bool libev, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool()); -std::string socksConstruct(std::string remarks, std::string server, std::string port, std::string username, std::string password, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool()); -std::string httpConstruct(std::string remarks, std::string server, std::string port, std::string username, std::string password, bool tls, tribool scv = tribool()); -std::string trojanConstruct(std::string remarks, std::string server, std::string port, std::string password, std::string host, bool tlssecure, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool()); -std::string snellConstruct(std::string remarks, std::string server, std::string port, std::string password, std::string obfs, std::string host, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool()); +std::string ssConstruct(std::string group, std::string remarks, std::string server, std::string port, std::string password, std::string method, std::string plugin, std::string pluginopts, bool libev, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool()); +std::string socksConstruct(std::string group, std::string remarks, std::string server, std::string port, std::string username, std::string password, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool()); +std::string httpConstruct(std::string group, std::string remarks, std::string server, std::string port, std::string username, std::string password, bool tls, tribool scv = tribool()); +std::string trojanConstruct(std::string group, std::string remarks, std::string server, std::string port, std::string password, std::string host, bool tlssecure, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool()); +std::string snellConstruct(std::string group, std::string remarks, std::string server, std::string port, std::string password, std::string obfs, std::string host, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool()); void explodeVmess(std::string vmess, const std::string &custom_port, nodeInfo &node); void explodeSSR(std::string ssr, bool ss_libev, bool libev, const std::string &custom_port, nodeInfo &node); void explodeSS(std::string ss, bool libev, const std::string &custom_port, nodeInfo &node); diff --git a/src/subexport.cpp b/src/subexport.cpp index 2575f8e..6043e10 100644 --- a/src/subexport.cpp +++ b/src/subexport.cpp @@ -10,6 +10,7 @@ #include "string_hash.h" #include "logger.h" #include "templates.h" +#include "script_duktape.h" #include #include @@ -19,6 +20,7 @@ #include #include #include +#include #define MAX_RULES_COUNT 32768 @@ -78,7 +80,7 @@ std::string hostnameToIPAddr(const std::string &host) return retAddr; } -std::string vmessConstruct(std::string add, std::string port, std::string type, std::string id, std::string aid, std::string net, std::string cipher, std::string path, std::string host, std::string edge, std::string tls, tribool udp, tribool tfo, tribool scv) +std::string vmessConstruct(std::string group, std::string remarks, std::string add, std::string port, std::string type, std::string id, std::string aid, std::string net, std::string cipher, std::string path, std::string host, std::string edge, std::string tls, tribool udp, tribool tfo, tribool scv) { if(!path.size()) path = "/"; @@ -94,8 +96,10 @@ std::string vmessConstruct(std::string add, std::string port, std::string type, writer.StartObject(); writer.Key("Type"); writer.String("VMess"); + writer.Key("Group"); + writer.String(group.data()); writer.Key("Remark"); - writer.String(std::string(add + ":" + port).data()); + writer.String(remarks.data()); writer.Key("Hostname"); writer.String(add.data()); writer.Key("Port"); @@ -194,13 +198,15 @@ std::string ssrConstruct(std::string group, std::string remarks, std::string rem return sb.GetString(); } -std::string ssConstruct(std::string server, std::string port, std::string password, std::string method, std::string plugin, std::string pluginopts, std::string remarks, bool libev, tribool udp, tribool tfo, tribool scv) +std::string ssConstruct(std::string group, std::string remarks, std::string server, std::string port, std::string password, std::string method, std::string plugin, std::string pluginopts, bool libev, tribool udp, tribool tfo, tribool scv) { rapidjson::StringBuffer sb; rapidjson::Writer writer(sb); writer.StartObject(); writer.Key("Type"); writer.String("SS"); + writer.Key("Group"); + writer.String(group.data()); writer.Key("Remark"); writer.String(remarks.data()); writer.Key("Hostname"); @@ -234,13 +240,15 @@ std::string ssConstruct(std::string server, std::string port, std::string passwo return sb.GetString(); } -std::string socksConstruct(std::string remarks, std::string server, std::string port, std::string username, std::string password, tribool udp, tribool tfo, tribool scv) +std::string socksConstruct(std::string group, std::string remarks, std::string server, std::string port, std::string username, std::string password, tribool udp, tribool tfo, tribool scv) { rapidjson::StringBuffer sb; rapidjson::Writer writer(sb); writer.StartObject(); writer.Key("Type"); writer.String("Socks5"); + writer.Key("Group"); + writer.String(group.data()); writer.Key("Remark"); writer.String(remarks.data()); writer.Key("Hostname"); @@ -270,13 +278,15 @@ std::string socksConstruct(std::string remarks, std::string server, std::string return sb.GetString(); } -std::string httpConstruct(std::string remarks, std::string server, std::string port, std::string username, std::string password, bool tls, tribool scv) +std::string httpConstruct(std::string group, std::string remarks, std::string server, std::string port, std::string username, std::string password, bool tls, tribool scv) { rapidjson::StringBuffer sb; rapidjson::Writer writer(sb); writer.StartObject(); writer.Key("Type"); writer.String(tls ? "HTTPS" : "HTTP"); + writer.Key("Group"); + writer.String(group.data()); writer.Key("Remark"); writer.String(remarks.data()); writer.Key("Hostname"); @@ -296,13 +306,15 @@ std::string httpConstruct(std::string remarks, std::string server, std::string p return sb.GetString(); } -std::string trojanConstruct(std::string remarks, std::string server, std::string port, std::string password, std::string host, bool tlssecure, tribool udp, tribool tfo, tribool scv) +std::string trojanConstruct(std::string group, std::string remarks, std::string server, std::string port, std::string password, std::string host, bool tlssecure, tribool udp, tribool tfo, tribool scv) { rapidjson::StringBuffer sb; rapidjson::Writer writer(sb); writer.StartObject(); writer.Key("Type"); writer.String("Trojan"); + writer.Key("Group"); + writer.String(group.data()); writer.Key("Remark"); writer.String(remarks.data()); writer.Key("Hostname"); @@ -334,13 +346,15 @@ std::string trojanConstruct(std::string remarks, std::string server, std::string return sb.GetString(); } -std::string snellConstruct(std::string remarks, std::string server, std::string port, std::string password, std::string obfs, std::string host, tribool udp, tribool tfo, tribool scv) +std::string snellConstruct(std::string group, std::string remarks, std::string server, std::string port, std::string password, std::string obfs, std::string host, tribool udp, tribool tfo, tribool scv) { rapidjson::StringBuffer sb; rapidjson::Writer writer(sb); writer.StartObject(); writer.Key("Type"); writer.String("Snell"); + writer.Key("Group"); + writer.String(group.data()); writer.Key("Remark"); writer.String(remarks.data()); writer.Key("Hostname"); @@ -409,7 +423,7 @@ bool matchRange(std::string &range, int target) bool match = false; std::string range_begin_str, range_end_str; int range_begin = 0, range_end = 0; - const std::string reg_num = "-?\\d+", reg_range = "(\\d+)-(\\d+)", reg_not = "\\!-?(\\d+)", reg_not_range = "\\!(\\d+)-(\\d+)", reg_less = "(\\d+)-", reg_more = "(\\d+)\\+"; + static const std::string reg_num = "-?\\d+", reg_range = "(\\d+)-(\\d+)", reg_not = "\\!-?(\\d+)", reg_not_range = "\\!(\\d+)-(\\d+)", reg_less = "(\\d+)-", reg_more = "(\\d+)\\+"; for(std::string &x : vArray) { if(regMatch(x, reg_num)) @@ -460,15 +474,35 @@ bool matchRange(std::string &range, int target) return match; } -std::string nodeRename(const std::string &orig_remark, int groupID, const string_array &rename_array) +bool applyMatcher(const std::string &rule, std::string &real_rule, const nodeInfo &node) +{ + std::string group, ret_real_rule; + static const std::string groupid_regex = R"(^!!(?:GROUPID|INSERT)=([\d\-+!,]+)(?:!!(.*))?$)", group_regex = R"(^!!(?:GROUP)=(.*?)(?:!!(.*))?$)"; + if(startsWith(rule, "!!GROUP=")) + { + regGetMatch(rule, group_regex, 3, NULL, &group, &ret_real_rule); + real_rule = ret_real_rule; + return regFind(node.group, group); + } + else if(startsWith(rule, "!!GROUPID=") || startsWith(rule, "!!INSERT=")) + { + int dir = startsWith(rule, "!!INSERT=") ? -1 : 1; + regGetMatch(rule, groupid_regex, 3, NULL, &group, &ret_real_rule); + real_rule = ret_real_rule; + return matchRange(group, dir * node.groupID); + } + else + real_rule = rule; + return true; +} + +std::string nodeRename(const nodeInfo &node, const string_array &rename_array) { string_array vArray; - std::string targetRange, remark = orig_remark; - string_size pos; + std::string remark = node.remarks, real_rule; for(const std::string &x : rename_array) { - targetRange = std::to_string(groupID); vArray = split(x, "@"); if(vArray.size() == 1) { @@ -476,22 +510,11 @@ std::string nodeRename(const std::string &orig_remark, int groupID, const string } else if(vArray.size() != 2) continue; - if(startsWith(vArray[0], "!!GROUPID=")) - { - pos = vArray[0].find("!!", vArray[0].find("!!") + 2); - if(pos != vArray[0].npos) - { - targetRange = vArray[0].substr(10, pos - 10); - vArray[0] = vArray[0].substr(pos + 2); - } - else - continue; - } - if(matchRange(targetRange, groupID)) - remark = regReplace(remark, vArray[0], vArray[1]); + if(applyMatcher(vArray[0], real_rule, node) && real_rule.size()) + remark = regReplace(remark, real_rule, vArray[1]); } if(remark.empty()) - return orig_remark; + return node.remarks; return remark; } @@ -511,36 +534,21 @@ std::string removeEmoji(const std::string &orig_remark) return remark; } -std::string addEmoji(std::string remark, int groupID, const string_array &emoji_array) +std::string addEmoji(const nodeInfo &node, const string_array &emoji_array) { string_array vArray; - std::string targetRange; + std::string real_rule; string_size pos; for(const std::string &x : emoji_array) { - targetRange = std::to_string(groupID); - vArray = split(x, ","); - if(vArray.size() != 2) + pos = x.rfind(","); + if(pos == x.npos) continue; - if(startsWith(vArray[0], "!!GROUPID=")) - { - pos = vArray[0].find("!!", vArray[0].find("!!") + 2); - if(pos != vArray[0].npos) - { - targetRange = vArray[0].substr(10, pos - 10); - vArray[0] = vArray[0].substr(pos + 2); - } - else - continue; - } - if(matchRange(targetRange, groupID) && regFind(remark, vArray[0])) - { - remark = vArray[1] + " " + remark; - break; - } + if(applyMatcher(x.substr(0, pos), real_rule, node) && real_rule.size() && regFind(node.remarks, real_rule)) + return x.substr(pos + 1) + " " + node.remarks; } - return remark; + return node.remarks; } void processRemark(std::string &oldremark, std::string &newremark, string_array &remarks_list) @@ -934,61 +942,54 @@ void parseGroupTimes(const std::string &src, int *interval, int *tolerance, int return; } -void groupGenerate(std::string &rule, std::vector &nodelist, std::vector &filtered_nodelist, bool add_direct) +void groupGenerate(std::string &rule, std::vector &nodelist, string_array &filtered_nodelist, bool add_direct) { std::string group, real_rule; const std::string groupid_regex = R"(^!!(?:GROUPID|INSERT)=([\d\-+!,]+)(?:!!(.*))?$)", group_regex = R"(^!!(?:GROUP)=(.*?)(?:!!(.*))?$)"; - if(rule.find("[]") == 0 && add_direct) + if(startsWith(rule, "[]") && add_direct) { filtered_nodelist.emplace_back(rule.substr(2)); } - else if(rule.find("!!GROUP=") == 0) + else if(startsWith(rule, "script:")) { - regGetMatch(rule, group_regex, 3, NULL, &group, &real_rule); - if(real_rule.empty()) + duk_context *ctx = duktape_init(); + if(ctx) { - for(nodeInfo &y : nodelist) + defer(duk_destroy_heap(ctx);) + std::string script = fileGet(rule.substr(7), true); + if(duktape_peval(ctx, script) == 0) { - if(regFind(y.group, group) && std::find(filtered_nodelist.begin(), filtered_nodelist.end(), y.remarks) == filtered_nodelist.end()) - filtered_nodelist.emplace_back(y.remarks); + duk_get_global_string(ctx, "filter"); + duk_idx_t arr_idx = duk_push_array(ctx), node_idx = 0; + for(nodeInfo &x : nodelist) + { + duktape_push_nodeinfo_arr(ctx, x, -1); + duk_put_prop_index(ctx, arr_idx, node_idx++); + } + if(duk_pcall(ctx, 1) == 0) + { + std::string result_list = duktape_get_res_str(ctx); + filtered_nodelist = split(regTrim(result_list), "\n"); + } + else + { + writeLog(0, "Error when trying to evaluate script:\n" + duktape_get_err_stack(ctx), LOG_LEVEL_ERROR); + duk_pop(ctx); + } } - } - else - { - for(nodeInfo &y : nodelist) + else { - if(regFind(y.group, group) && regFind(y.remarks, real_rule) && std::find(filtered_nodelist.begin(), filtered_nodelist.end(), y.remarks) == filtered_nodelist.end()) - filtered_nodelist.emplace_back(y.remarks); - } - } - } - else if(rule.find("!!GROUPID=") == 0 || rule.find("!!INSERT=") == 0) - { - int dir = rule.find("!!INSERT=") == 0 ? -1 : 1; - regGetMatch(rule, groupid_regex, 3, NULL, &group, &real_rule); - if(real_rule.empty()) - { - for(nodeInfo &y : nodelist) - { - if(matchRange(group, dir * y.groupID) && std::find(filtered_nodelist.begin(), filtered_nodelist.end(), y.remarks) == filtered_nodelist.end()) - filtered_nodelist.emplace_back(y.remarks); - } - } - else - { - for(nodeInfo &y : nodelist) - { - if(matchRange(group, dir * y.groupID) && regFind(y.remarks, real_rule) && std::find(filtered_nodelist.begin(), filtered_nodelist.end(), y.remarks) == filtered_nodelist.end()) - filtered_nodelist.emplace_back(y.remarks); + writeLog(0, "Error when trying to parse script:\n" + duktape_get_err_stack(ctx), LOG_LEVEL_ERROR); + duk_pop(ctx); } } } else { - for(nodeInfo &y : nodelist) + for(nodeInfo &x : nodelist) { - if(regFind(y.remarks, rule) && std::find(filtered_nodelist.begin(), filtered_nodelist.end(), y.remarks) == filtered_nodelist.end()) - filtered_nodelist.emplace_back(y.remarks); + if(applyMatcher(rule, real_rule, x) && (real_rule.empty() || regFind(x.remarks, real_rule)) && std::find(filtered_nodelist.begin(), filtered_nodelist.end(), x.remarks) == filtered_nodelist.end()) + filtered_nodelist.emplace_back(x.remarks); } } } @@ -997,17 +998,45 @@ void preprocessNodes(std::vector &nodes, extra_settings &ext) { std::for_each(nodes.begin(), nodes.end(), [ext](nodeInfo &x) { - x.remarks = nodeRename(x.remarks, x.groupID, ext.rename_array); + x.remarks = nodeRename(x, ext.rename_array); if(ext.remove_emoji) x.remarks = trim(removeEmoji(x.remarks)); if(ext.add_emoji) - x.remarks = addEmoji(x.remarks, x.groupID, ext.emoji_array); + x.remarks = addEmoji(x, ext.emoji_array); }); if(ext.sort_flag) { - std::sort(nodes.begin(), nodes.end(), [](const nodeInfo &a, const nodeInfo &b) + bool failed = true; + if(ext.sort_script.size()) + { + duk_context *ctx = duktape_init(); + if(ctx) + { + defer(duk_destroy_heap(ctx);) + if(duktape_peval(ctx, ext.sort_script) == 0) + { + auto comparer = [&](const nodeInfo &a, const nodeInfo &b) + { + duk_get_global_string(ctx, "compare"); + /// push 2 nodeinfo + duktape_push_nodeinfo(ctx, a); + duktape_push_nodeinfo(ctx, b); + /// call function + return duktape_get_res_int(ctx); + }; + std::sort(nodes.begin(), nodes.end(), comparer); + failed = false; + } + else + { + writeLog(0, "Error when trying to parse script:\n" + duktape_get_err_stack(ctx), LOG_LEVEL_ERROR); + duk_pop(ctx); /// pop err + } + } + } + if(failed) std::sort(nodes.begin(), nodes.end(), [](const nodeInfo &a, const nodeInfo &b) { return a.remarks < b.remarks; }); diff --git a/src/subexport.h b/src/subexport.h index 9395185..fda689d 100644 --- a/src/subexport.h +++ b/src/subexport.h @@ -36,6 +36,7 @@ struct extra_settings tribool udp = false; tribool tfo = false; tribool skip_cert_verify = false; + std::string sort_script; }; void rulesetToClash(YAML::Node &base_rule, std::vector &ruleset_content_array, bool overwrite_original_rules, bool new_field_name); diff --git a/src/webget.cpp b/src/webget.cpp index 1ae4f7d..8947a7f 100644 --- a/src/webget.cpp +++ b/src/webget.cpp @@ -59,6 +59,7 @@ static inline void curl_set_common_options(CURL *curl_handle, const char *url) curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 0L); curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 20L); curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0L); curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, 15L); @@ -109,12 +110,12 @@ static std::string curlGet(const std::string &url, const std::string &proxy, std static std::string dataGet(const std::string &url) { if (!startsWith(url, "data:")) - return ""; + return std::string(); std::string::size_type comma = url.find(','); - if (comma == std::string::npos) - return ""; + if (comma == std::string::npos || comma == url.size() - 1) + return std::string(); - std::string data = UrlDecode(url.substr(comma)); + std::string data = UrlDecode(url.substr(comma + 1)); if (endsWith(url.substr(0, comma), ";base64")) { return urlsafe_base64_decode(data); } else {