Merge remote-tracking branch 'fork/master' into dev

# Conflicts:
#	.github/workflows/build.yml
#	.github/workflows/docker.yml
#	.gitignore
#	base/pref.example.toml
#	base/snippets/emoji.toml
#	base/snippets/emoji.txt
#	scripts/build.macos.release.sh
#	scripts/build.windows.release.sh
#	scripts/rules_config.conf
#	src/generator/config/subexport.cpp
#	src/handler/interfaces.cpp
#	src/handler/settings.cpp
#	src/parser/config/proxy.h
#	src/parser/subparser.cpp
#	src/parser/subparser.h
#	src/utils/map_extra.h
#	src/version.h
This commit is contained in:
asdlokj1qpi23
2025-03-25 14:08:43 +08:00
33 changed files with 887 additions and 729 deletions

View File

@@ -24,17 +24,15 @@ jobs:
os: ubuntu-latest
- arch: armv7
artifact: subconverter_armv7
os: ubuntu-latest
os: [self-hosted, linux, ARM]
- arch: aarch64
artifact: subconverter_aarch64
os: ubuntu-latest
os: [self-hosted, linux, ARM64]
runs-on: ${{ matrix.os }}
name: Linux ${{ matrix.arch }} Build
steps:
- name: Checkout base
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Add commit id into version
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h

View File

@@ -10,7 +10,7 @@ concurrency:
cancel-in-progress: true
env:
REGISTRY_IMAGE: asdlokj1qpi23/subconverter
REGISTRY_IMAGE: tindy2013/subconverter
jobs:
build:
@@ -22,9 +22,9 @@ jobs:
- platform: linux/386
os: ubuntu-latest
- platform: linux/arm/v7
os: ubuntu-latest
os: [self-hosted, linux, ARM]
- platform: linux/arm64
os: ubuntu-latest
os: [self-hosted, linux, ARM64]
runs-on: ${{ matrix.os }}
name: Build ${{ matrix.platform }} Image
steps:
@@ -37,8 +37,6 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

2
.gitignore vendored
View File

@@ -7,3 +7,5 @@ scripts/quickjspp
scripts/yaml-cpp
.DS_Store
src/.DS_Store
build

View File

@@ -5,7 +5,7 @@ socks-port: {{ default(global.clash.socks_port, "7891") }}
allow-lan: {{ default(global.clash.allow_lan, "true") }}
mode: Rule
log-level: {{ default(global.clash.log_level, "info") }}
external-controller: :9090
external-controller: {{ default(global.clash.external_controller, "127.0.0.1:9090") }}
{% if default(request.clash.dns, "") == "1" %}
dns:
enable: true
@@ -378,7 +378,16 @@ enhanced-mode-by-rule = true
"rules": [],
"auto_detect_interface": true
},
"experimental": {}
"experimental": {
"cache_file": {
"enabled": true,
"store_fakeip": true
},
"clash_api": {
"external_controller": "{{ default(global.clash.external_controller, "127.0.0.1:9090") }}",
"external_ui": "dashboard"
}
}
}
{% endif %}

View File

@@ -100,5 +100,14 @@
"rules": [],
"auto_detect_interface": true
},
"experimental": {}
"experimental": {
"cache_file": {
"enabled": true,
"store_fakeip": true
},
"clash_api": {
"external_controller": "127.0.0.1:9090",
"external_ui": "dashboard"
}
}
}

View File

@@ -109,13 +109,14 @@ filter_deprecated_nodes=false
append_sub_userinfo=true
clash_use_new_field_name=true
;Generate style of the proxies section of Clash subscriptions.
;Generate style of the proxies and proxy groups section of Clash subscriptions.
;Supported styles: block, flow, compact
;Block: - name: name1 Flow: - {name: name1, key: value} Compact: [{name: name1, key: value},{name: name2, key: value}]
; key: value - {name: name2, key: value}
; - name: name2
; key: value
clash_proxies_style=flow
clash_proxy_groups_style=block
;add Clash mode to sing-box rules, and add a GLOBAL group to end of outbounds
singbox_add_clash_modes=true
@@ -232,6 +233,7 @@ clash.http_port=7890
clash.socks_port=7891
clash.allow_lan=true
clash.log_level=info
clash.external_controller=127.0.0.1:9090
singbox.allow_lan=true
singbox.mixed_port=2080

View File

@@ -117,9 +117,9 @@ match = '^Smart Access expire: (\d+)/(\d+)/(\d+)$'
replace = '$1:$2:$3:0:0:0'
[node_pref]
#udp_flag = true
#udp_flag = false
#tcp_fast_open_flag = false
#skip_cert_verify_flag = true
#skip_cert_verify_flag = false
#tls13_flag = false
sort_flag = false
@@ -135,13 +135,14 @@ filter_deprecated_nodes = false
append_sub_userinfo = true
clash_use_new_field_name = true
# Generate style of the proxies section of Clash subscriptions.
# Generate style of the proxies and proxy groups section of Clash subscriptions.
# Supported styles: block, flow, compact
# Block: - name: name1 Flow: - {name: name1, key: value} Compact: [{name: name1, key: value},{name: name2, key: value}]
# key: value - {name: name2, key: value}
# - name: name2
# key: value
clash_proxies_style = "flow"
clash_proxy_groups_style = "block"
# add Clash mode to sing-box rules, and add a GLOBAL group to end of outbounds
singbox_add_clash_modes = true
@@ -243,6 +244,10 @@ value = "true"
key = "clash.log_level"
value = "info"
[[template.globals]]
key = "clash.external_controller"
value = "127.0.0.1:9090"
[[template.globals]]
key = "singbox.allow_lan"
value = "true"

View File

@@ -50,6 +50,7 @@ node_pref:
append_sub_userinfo: true
clash_use_new_field_name: true
clash_proxies_style: flow
clash_proxy_groups_style: block
singbox_add_clash_modes: true
rename_node:
# - {match: "\\(?((x|X)?(\\d+)(\\.?\\d+)?)((\\s?倍率?)|(x|X))\\)?", replace: "$1x"}
@@ -108,6 +109,7 @@ template:
- {key: clash.socks_port, value: 7891}
- {key: clash.allow_lan, value: true}
- {key: clash.log_level, value: info}
- {key: clash.external_controller, value: '127.0.0.1:9090'}
- {key: singbox.allow_lan, value: true}
- {key: singbox.mixed_port, value: 2080}

View File

@@ -23,7 +23,7 @@ match = "(?i:\\bJP[N]?\\d*\\b|Japan|Tokyo|Osaka|Saitama|日本|东京|大阪|埼
emoji = "🇯🇵"
[[emoji]]
match = "(?i:\\bK[O]?R\\d*\\b|Korea|(?<!North)Korea|首尔|韩|韓)"
match = "(?i:(?<!North\\s)(\\bK[O]?R\\d*\\b|Korea|首尔|韩|韓))"
emoji = "🇰🇷"
[[emoji]]
@@ -334,10 +334,6 @@ emoji = "🇹🇷"
match = "(乌拉圭|Uruguay)"
emoji = "🇺🇾"
[[emoji]]
match = "(梵蒂冈|Vatican)"
emoji = "🇻🇦"
[[emoji]]
match = "(Vietnam|越南)"
emoji = "🇻🇳"

View File

@@ -4,7 +4,7 @@
(?i:\bSG[P]?\d*\b|Singapore|新加坡|狮城|[^-]新),🇸🇬
(尼日利亚|Nigeria),🇳🇬
(?i:\bJP[N]?\d*\b|Japan|Tokyo|Osaka|Saitama|日本|东京|大阪|埼玉|[^-]日),🇯🇵
(?i:\bK[O]?R\d*\b|Korea|(?<!North)Korea|首尔|韩|韓),🇰🇷
(?i:(?<!North\s)(\bK[O]?R\d*\b|Korea|首尔|韩|韓)),🇰🇷
(?i:\bUS[A]?\d*\b|America|United.*?States|美国|[^-]美|波特兰|达拉斯|俄勒冈|凤凰城|费利蒙|硅谷|拉斯维加斯|洛杉矶|圣何塞|圣克拉拉|西雅图|芝加哥),🇺🇸
(Ascension|阿森松),🇦🇨
(?i:\bUAE\b|Dubai|阿联酋|迪拜),🇦🇪

View File

@@ -30,7 +30,7 @@ RUN set -xe && \
install -d /usr/include/date/ && \
install -m644 libcron/externals/date/include/date/* /usr/include/date/ && \
cd .. && \
git clone https://github.com/ToruNiina/toml11 --branch="v3.7.1" --depth=1 && \
git clone https://github.com/ToruNiina/toml11 --branch="v4.3.0" --depth=1 && \
cd toml11 && \
cmake -DCMAKE_CXX_STANDARD=11 . && \
make install -j $THREADS && \
@@ -53,6 +53,10 @@ RUN apk add --no-cache --virtual subconverter-deps pcre2 libcurl yaml-cpp
COPY --from=builder /subconverter/subconverter /usr/bin/
COPY --from=builder /subconverter/base /base/
ENV TZ=Africa/Abidjan
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime
RUN echo $TZ > /etc/timezone
# set entry
WORKDIR /base
CMD subconverter

View File

@@ -4,7 +4,7 @@ set -xe
apk add gcc g++ build-base linux-headers cmake make autoconf automake libtool python2 python3
apk add mbedtls-dev mbedtls-static zlib-dev rapidjson-dev zlib-static pcre2-dev
git clone https://github.com/curl/curl --depth=1 --branch curl-8_4_0
git clone https://github.com/curl/curl --depth=1 --branch curl-8_6_0
cd curl
cmake -DCURL_USE_MBEDTLS=ON -DHTTP_ONLY=ON -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_USE_LIBSSH2=OFF -DBUILD_CURL_EXE=OFF . > /dev/null
make install -j2 > /dev/null
@@ -34,7 +34,7 @@ cmake -DCMAKE_BUILD_TYPE=Release .
make libcron install -j3
cd ..
git clone https://github.com/ToruNiina/toml11 --branch="v3.7.1" --depth=1
git clone https://github.com/ToruNiina/toml11 --branch="v4.3.0" --depth=1
cd toml11
cmake -DCMAKE_CXX_STANDARD=11 .
make install -j4

View File

@@ -41,7 +41,7 @@ sudo install -d /usr/local/include/date/
sudo install -m644 libcron/externals/date/include/date/* /usr/local/include/date/
cd ..
git clone https://github.com/ToruNiina/toml11 --branch="v3.7.1" --depth=1
git clone https://github.com/ToruNiina/toml11 --branch="v4.3.0" --depth=1
cd toml11
cmake -DCMAKE_CXX_STANDARD=11 .
sudo make install -j6 > /dev/null

View File

@@ -1,33 +1,9 @@
#!/bin/bash
set -xe
# 获取系统架构
ARCH=$(uname -m)
if [ "$ARCH" == "x86_64" ]; then
TOOLCHAIN="mingw-w64-x86_64"
else
TOOLCHAIN="mingw-w64-i686"
fi
pacman -S --needed --noconfirm base-devel ${TOOLCHAIN}-toolchain ${TOOLCHAIN}-cmake ${TOOLCHAIN}-nghttp2 ${TOOLCHAIN}-openssl
git clone https://github.com/curl/curl --depth=1 --branch curl-8_8_0
git clone https://github.com/curl/curl --depth=1 --branch curl-8_6_0
cd curl
cmake -DCMAKE_BUILD_TYPE=Release \
-DCURL_USE_LIBSSH2=OFF \
-DHTTP_ONLY=ON \
-DCURL_USE_SCHANNEL=ON \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_CURL_EXE=OFF \
-DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" \
-G "Unix Makefiles" \
-DHAVE_LIBIDN2=OFF \
-DCURL_USE_LIBPSL=OFF \
-DCURL_STATICLIB=ON \
-DCURL_DISABLE_SOCKETPAIR=ON \
-DCURL_DISABLE_NONBLOCKING=ON .
cmake -DCMAKE_BUILD_TYPE=Release -DCURL_USE_LIBSSH2=OFF -DHTTP_ONLY=ON -DCURL_USE_SCHANNEL=ON -DBUILD_SHARED_LIBS=OFF -DBUILD_CURL_EXE=OFF -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" -G "Unix Makefiles" -DHAVE_LIBIDN2=OFF -DCURL_USE_LIBPSL=OFF .
make install -j4
cd ..
@@ -62,7 +38,7 @@ cmake -DRAPIDJSON_BUILD_DOC=OFF -DRAPIDJSON_BUILD_EXAMPLES=OFF -DRAPIDJSON_BUILD
make install -j4
cd ..
git clone https://github.com/ToruNiina/toml11 --branch="v3.7.1" --depth=1
git clone https://github.com/ToruNiina/toml11 --branch "v4.3.0" --depth=1
cd toml11
cmake -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" -G "Unix Makefiles" -DCMAKE_CXX_STANDARD=11 .
make install -j4

View File

@@ -1,23 +1,23 @@
[ACL4SSR]
name=ACL4SSR
url=https://github.com/ACL4SSR/ACL4SSR
checkout=1dc5c92b0c8ceaaecbc66530c309961f53e52c8c
branch=master
match=Clash/*.list|Clash/Ruleset/**
[ACL4SSR_config]
name=ACL4SSR
url=https://github.com/ACL4SSR/ACL4SSR
checkout=1dc5c92b0c8ceaaecbc66530c309961f53e52c8c
branch=master
match=Clash/config/**
dest=base/config/
keep_tree=false
[DivineEngine]
url=https://github.com/asdlokj1qpi233/Profiles.git
checkout=f6302d855192bd8d0be08319dff3e58ae7c2bd4e
match=Surge/Ruleset/**
[NobyDa]
url=https://github.com/NobyDa/Script
checkout=ae4c12f23de8078e02c373c9969b19af28257fcb
branch=master
match=Surge/*.list
[lhie1]
url=https://github.com/dler-io/Rules
branch=main
match=Surge/Surge 3/Provider/**

View File

@@ -22,10 +22,13 @@ def open_repo(path: str):
return None
def update_rules(repo_path, save_path, commit, matches, keep_tree):
def update_rules(repo_path: str, save_path: str, matches: list[str], keep_tree: bool):
os.makedirs(save_path, exist_ok=True)
for pattern in matches:
files = glob.glob(os.path.join(repo_path, pattern), recursive=True)
if len(files) == 0:
logging.warn(f"no files found for pattern {pattern}")
continue
for file in files:
if os.path.isdir(file):
continue
@@ -51,12 +54,13 @@ def main():
for section in config.sections():
repo = config.get(section, "name", fallback=section)
url = config.get(section, "url")
commit = config.get(section, "checkout")
commit = config.get(section, "commit", fallback=None)
branch = config.get(section, "branch", fallback=None)
matches = config.get(section, "match").split("|")
save_path = config.get(section, "dest", fallback=f"base/rules/{repo}")
keep_tree = config.getboolean(section, "keep_tree", fallback=True)
logging.info(f"reading files from url {url} with commit {commit} and matches {matches}, save to {save_path} keep_tree {keep_tree}")
logging.info(f"reading files from url {url}, matches {matches}, save to {save_path} keep_tree {keep_tree}")
repo_path = os.path.join("./tmp/repo/", repo)
@@ -67,8 +71,21 @@ def main():
else:
logging.info(f"repo {repo_path} exists")
try:
if commit is not None:
logging.info(f"checking out to commit {commit}")
r.git.checkout(commit)
update_rules(repo_path, save_path, commit, matches, keep_tree)
elif branch is not None:
logging.info(f"checking out to branch {branch}")
r.git.checkout(branch)
else:
logging.info(f"checking out to default branch")
r.active_branch.checkout()
except Exception as e:
logging.error(f"checkout failed {e}")
continue
update_rules(repo_path, save_path, matches, keep_tree)
shutil.rmtree("./tmp", ignore_errors=True)

View File

@@ -17,9 +17,9 @@ namespace toml
static ProxyGroupConfig from_toml(const value& v)
{
ProxyGroupConfig conf;
conf.Name = toml::find<String>(v, "name");
String type = toml::find<String>(v, "type");
String strategy = toml::find_or<String>(v, "strategy", "");
conf.Name = find<String>(v, "name");
String type = find<String>(v, "type");
String strategy = find_or<String>(v, "strategy", "");
switch(hash_(type))
{
case "select"_hash:
@@ -27,18 +27,18 @@ namespace toml
break;
case "url-test"_hash:
conf.Type = ProxyGroupType::URLTest;
conf.Url = toml::find<String>(v, "url");
conf.Interval = toml::find<Integer>(v, "interval");
conf.Tolerance = toml::find_or<Integer>(v, "tolerance", 0);
conf.Url = find<String>(v, "url");
conf.Interval = find<Integer>(v, "interval");
conf.Tolerance = find_or<Integer>(v, "tolerance", 0);
if(v.contains("lazy"))
conf.Lazy = toml::find_or<bool>(v, "lazy", false);
conf.Lazy = find_or<bool>(v, "lazy", false);
if(v.contains("evaluate-before-use"))
conf.EvaluateBeforeUse = toml::find_or(v, "evaluate-before-use", conf.EvaluateBeforeUse.get());
conf.EvaluateBeforeUse = find_or(v, "evaluate-before-use", conf.EvaluateBeforeUse.get());
break;
case "load-balance"_hash:
conf.Type = ProxyGroupType::LoadBalance;
conf.Url = toml::find<String>(v, "url");
conf.Interval = toml::find<Integer>(v, "interval");
conf.Url = find<String>(v, "url");
conf.Interval = find<Integer>(v, "interval");
switch(hash_(strategy))
{
case "consistent-hashing"_hash:
@@ -49,14 +49,14 @@ namespace toml
break;
}
if(v.contains("persistent"))
conf.Persistent = toml::find_or(v, "persistent", conf.Persistent.get());
conf.Persistent = find_or(v, "persistent", conf.Persistent.get());
break;
case "fallback"_hash:
conf.Type = ProxyGroupType::Fallback;
conf.Url = toml::find<String>(v, "url");
conf.Interval = toml::find<Integer>(v, "interval");
conf.Url = find<String>(v, "url");
conf.Interval = find<Integer>(v, "interval");
if(v.contains("evaluate-before-use"))
conf.EvaluateBeforeUse = toml::find_or(v, "evaluate-before-use", conf.EvaluateBeforeUse.get());
conf.EvaluateBeforeUse = find_or(v, "evaluate-before-use", conf.EvaluateBeforeUse.get());
break;
case "relay"_hash:
conf.Type = ProxyGroupType::Relay;
@@ -64,16 +64,26 @@ namespace toml
case "ssid"_hash:
conf.Type = ProxyGroupType::SSID;
break;
case "smart"_hash:
conf.Type = ProxyGroupType::Smart;
conf.Url = find<String>(v, "url");
conf.Interval = find<Integer>(v, "interval");
conf.Tolerance = find_or<Integer>(v, "tolerance", 0);
if(v.contains("lazy"))
conf.Lazy = find_or<bool>(v, "lazy", false);
if(v.contains("evaluate-before-use"))
conf.EvaluateBeforeUse = find_or(v, "evaluate-before-use", conf.EvaluateBeforeUse.get());
break;
default:
throw toml::syntax_error("Proxy Group has incorrect type, should be one of following:\n select, url-test, load-balance, fallback, relay, ssid", v.at("type").location());
throw serialization_error(format_error("Proxy Group has unsupported type!", v.at("type").location(), "should be one of following: select, url-test, load-balance, fallback, relay, ssid"), v.at("type").location());
}
conf.Timeout = toml::find_or(v, "timeout", 5);
conf.Proxies = toml::find_or<StrArray>(v, "rule", {});
conf.UsingProvider = toml::find_or<StrArray>(v, "use", {});
conf.Timeout = find_or(v, "timeout", 5);
conf.Proxies = find_or<StrArray>(v, "rule", {});
conf.UsingProvider = find_or<StrArray>(v, "use", {});
if(conf.Proxies.empty() && conf.UsingProvider.empty())
throw toml::syntax_error("Proxy Group must contains at least one of proxy match rule or provider", v.location());
throw serialization_error(format_error("Proxy Group must contains at least one of proxy match rule or provider!", v.location(), "here"), v.location());
if(v.contains("disable-udp"))
conf.DisableUdp = toml::find_or(v, "disable-udp", conf.DisableUdp.get());
conf.DisableUdp = find_or(v, "disable-udp", conf.DisableUdp.get());
return conf;
}
};
@@ -84,8 +94,8 @@ namespace toml
static RulesetConfig from_toml(const value& v)
{
RulesetConfig conf;
conf.Group = toml::find<String>(v, "group");
String type = toml::find_or<String>(v, "type", "surge-ruleset");
conf.Group = find<String>(v, "group");
String type = find_or<String>(v, "type", "surge-ruleset");
switch(hash_(type))
{
/*
@@ -122,10 +132,10 @@ namespace toml
conf.Url = type + ":";
break;
default:
throw toml::syntax_error("Ruleset has incorrect type, should be one of following:\n surge-ruleset, quantumultx, clash-domain, clash-ipcidr, clash-classic", v.at("type").location());
throw serialization_error(format_error("Ruleset has unsupported type!", v.at("type").location(), "should be one of following: surge-ruleset, quantumultx, clash-domain, clash-ipcidr, clash-classic"), v.at("type").location());
}
conf.Url += toml::find<String>(v, "ruleset");
conf.Interval = toml::find_or<Integer>(v, "interval", 86400);
conf.Url += find<String>(v, "ruleset");
conf.Interval = find_or<Integer>(v, "interval", 86400);
return conf;
}
};
@@ -138,14 +148,14 @@ namespace toml
RegexMatchConfig conf;
if(v.contains("script"))
{
conf.Script = toml::find<String>(v, "script");
conf.Script = find<String>(v, "script");
return conf;
}
conf.Match = toml::find<String>(v, "match");
conf.Match = find<String>(v, "match");
if(v.contains("emoji"))
conf.Replace = toml::find<String>(v, "emoji");
conf.Replace = find<String>(v, "emoji");
else
conf.Replace = toml::find<String>(v, "replace");
conf.Replace = find<String>(v, "replace");
return conf;
}
};
@@ -156,10 +166,10 @@ namespace toml
static CronTaskConfig from_toml(const value& v)
{
CronTaskConfig conf;
conf.Name = toml::find<String>(v, "name");
conf.CronExp = toml::find<String>(v, "cronexp");
conf.Path = toml::find<String>(v, "path");
conf.Timeout = toml::find_or<Integer>(v, "timeout", 0);
conf.Name = find<String>(v, "name");
conf.CronExp = find<String>(v, "cronexp");
conf.Path = find<String>(v, "path");
conf.Timeout = find_or<Integer>(v, "timeout", 0);
return conf;
}
};
@@ -220,6 +230,9 @@ namespace INIBinding
case "ssid"_hash:
conf.Type = ProxyGroupType::SSID;
break;
case "smart"_hash:
conf.Type = ProxyGroupType::Smart;
break;
default:
continue;
}

View File

@@ -3,17 +3,18 @@
#include "def.h"
enum ProxyGroupType
enum class ProxyGroupType
{
Select,
URLTest,
Fallback,
LoadBalance,
Relay,
SSID
SSID,
Smart
};
enum BalanceStrategy
enum class BalanceStrategy
{
ConsistentHashing,
RoundRobin
@@ -45,6 +46,7 @@ struct ProxyGroupConfig
case ProxyGroupType::Fallback: return "fallback";
case ProxyGroupType::Relay: return "relay";
case ProxyGroupType::SSID: return "ssid";
case ProxyGroupType::Smart: return "smart";
}
return "";
}

View File

@@ -3,7 +3,7 @@
#include "def.h"
enum RulesetType
enum class RulesetType
{
SurgeRuleset,
QuantumultX,

View File

@@ -161,7 +161,8 @@ void processRemark(std::string &remark, const string_array &remarks_list, bool p
}
std::string tempRemark = remark;
int cnt = 2;
while (std::find(remarks_list.cbegin(), remarks_list.cend(), tempRemark) != remarks_list.cend()) {
while(std::find(remarks_list.cbegin(), remarks_list.cend(), tempRemark) != remarks_list.cend())
{
tempRemark = remark + " " + std::to_string(cnt);
cnt++;
}
@@ -218,6 +219,30 @@ proxyToClash(std::vector<Proxy> &nodes, YAML::Node &yamlnode, const ProxyGroupCo
case "compact"_hash:
compact = true;
break;
bool proxy_block = false, proxy_compact = false, group_block = false, group_compact = false;
switch(hash_(ext.clash_proxies_style))
{
case "block"_hash:
proxy_block = true;
break;
default:
case "flow"_hash:
break;
case "compact"_hash:
proxy_compact = true;
break;
}
switch(hash_(ext.clash_proxy_groups_style))
{
case "block"_hash:
group_block = true;
break;
default:
case "flow"_hash:
break;
case "compact"_hash:
group_compact = true;
break;
}
for (Proxy &x: nodes) {
@@ -644,6 +669,9 @@ proxyToClash(std::vector<Proxy> &nodes, YAML::Node &yamlnode, const ProxyGroupCo
string_array filtered_nodelist;
singlegroup["name"] = x.Name;
if (x.Type == ProxyGroupType::Smart)
singlegroup["type"] = "url-test";
else
singlegroup["type"] = x.TypeStr();
switch (x.Type) {
@@ -666,6 +694,29 @@ proxyToClash(std::vector<Proxy> &nodes, YAML::Node &yamlnode, const ProxyGroupCo
break;
default:
continue;
switch(x.Type)
{
case ProxyGroupType::Select:
case ProxyGroupType::Relay:
break;
case ProxyGroupType::LoadBalance:
singlegroup["strategy"] = x.StrategyStr();
[[fallthrough]];
case ProxyGroupType::Smart:
[[fallthrough]];
case ProxyGroupType::URLTest:
if(!x.Lazy.is_undef())
singlegroup["lazy"] = x.Lazy.get();
[[fallthrough]];
case ProxyGroupType::Fallback:
singlegroup["url"] = x.Url;
if(x.Interval > 0)
singlegroup["interval"] = x.Interval;
if(x.Tolerance > 0)
singlegroup["tolerance"] = x.Tolerance;
break;
default:
continue;
}
if (!x.DisableUdp.is_undef())
singlegroup["disable-udp"] = x.DisableUdp.get();
@@ -681,7 +732,10 @@ proxyToClash(std::vector<Proxy> &nodes, YAML::Node &yamlnode, const ProxyGroupCo
}
if (!filtered_nodelist.empty())
singlegroup["proxies"] = filtered_nodelist;
//singlegroup.SetStyle(YAML::EmitterStyle::Flow);
if(group_block)
singlegroup.SetStyle(YAML::EmitterStyle::Block);
else
singlegroup.SetStyle(YAML::EmitterStyle::Flow);
bool replace_flag = false;
for (auto &&original_group: original_groups) {
@@ -694,6 +748,8 @@ proxyToClash(std::vector<Proxy> &nodes, YAML::Node &yamlnode, const ProxyGroupCo
if (!replace_flag)
original_groups.push_back(singlegroup);
}
if(group_compact)
original_groups.SetStyle(YAML::EmitterStyle::Flow);
if (ext.clash_new_field_name)
yamlnode["proxy-groups"] = original_groups;
@@ -975,11 +1031,18 @@ std::string proxyToSurge(std::vector<Proxy> &nodes, const std::string &base_conf
proxy += ", version=" + std::to_string(x.SnellVersion);
break;
case ProxyType::Hysteria2:
if (surge_ver < 4 && surge_ver != -3)
if(surge_ver < 4)
continue;
proxy = "hysteria2, " + hostname + ", " + port + ", password=" + password;
proxy = "hysteria, " + hostname + ", " + port + ", password=" + password;
if(x.DownSpeed)
proxy += ", download-bandwidth=" + x.DownSpeed;
if(!scv.is_undef())
proxy += ", skip-cert-verify=" + scv.get_str();
proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false");
if(!x.Fingerprint.empty())
proxy += ",server-cert-fingerprint-sha256=" + x.Fingerprint;
if(!x.SNI.empty())
proxy += ",sni=" + x.SNI;
break;
case ProxyType::WireGuard:
if (surge_ver < 4 && surge_ver != -3)
@@ -1011,7 +1074,8 @@ std::string proxyToSurge(std::vector<Proxy> &nodes, const std::string &base_conf
proxy += ", tfo=" + tfo.get_str();
if (!udp.is_undef())
proxy += ", udp-relay=" + udp.get_str();
if (underlying_proxy != "")
proxy += ", underlying-proxy=" + underlying_proxy;
if (ext.nodelist)
output_nodelist += x.Remark + " = " + proxy + "\n";
else {
@@ -1030,8 +1094,10 @@ std::string proxyToSurge(std::vector<Proxy> &nodes, const std::string &base_conf
string_array filtered_nodelist;
std::string group;
switch (x.Type) {
switch(x.Type)
{
case ProxyGroupType::Select:
case ProxyGroupType::Smart:
case ProxyGroupType::URLTest:
case ProxyGroupType::Fallback:
break;
@@ -1693,7 +1759,8 @@ void proxyToQuanX(std::vector<Proxy> &nodes, INIReader &ini, std::vector<Ruleset
std::string proxies = join(filtered_nodelist, ", ");
std::string singlegroup = type + "=" + x.Name + ", " + proxies;
if (type != "static") {
if(x.Type != ProxyGroupType::Select && x.Type != ProxyGroupType::SSID)
{
singlegroup += ", check-interval=" + std::to_string(x.Interval);
if (x.Tolerance > 0)
singlegroup += ", tolerance=" + std::to_string(x.Tolerance);

View File

@@ -40,6 +40,7 @@ struct extra_settings
bool clash_classical_ruleset = false;
std::string sort_script;
std::string clash_proxies_style = "flow";
std::string clash_proxy_groups_style = "flow";
bool authorized = false;
extra_settings() = default;

View File

@@ -357,10 +357,10 @@ int renderClashScript(YAML::Node &base_rule, std::vector<RulesetContent> &rulese
if(x.rule_type == RULESET_CLASH_IPCIDR || x.rule_type == RULESET_CLASH_DOMAIN || x.rule_type == RULESET_CLASH_CLASSICAL)
{
//rule_name = std::to_string(hash_(rule_group + rule_path));
rule_name = old_rule_name = findFileName(rule_path);
rule_name = old_rule_name = urlDecode(findFileName(rule_path));
int idx = 2;
while(std::find(groups.begin(), groups.end(), rule_name) != groups.end())
rule_name = old_rule_name + "_" + std::to_string(idx++);
rule_name = old_rule_name + " " + std::to_string(idx++);
names[rule_name] = rule_group;
urls[rule_name] = "*" + rule_path;
rule_type[rule_name] = x.rule_type;
@@ -386,10 +386,10 @@ int renderClashScript(YAML::Node &base_rule, std::vector<RulesetContent> &rulese
if(fileExist(rule_path, true) || isLink(rule_path))
{
//rule_name = std::to_string(hash_(rule_group + rule_path));
rule_name = old_rule_name = findFileName(rule_path);
rule_name = old_rule_name = urlDecode(findFileName(rule_path));
int idx = 2;
while(std::find(groups.begin(), groups.end(), rule_name) != groups.end())
rule_name = old_rule_name + "_" + std::to_string(idx++);
rule_name = old_rule_name + " " + std::to_string(idx++);
names[rule_name] = rule_group;
urls[rule_name] = rule_path_typed;
rule_type[rule_name] = x.rule_type;
@@ -436,9 +436,9 @@ int renderClashScript(YAML::Node &base_rule, std::vector<RulesetContent> &rulese
if(vArray.size() < 2)
continue;
if(keywords.find(rule_name) == keywords.end())
keywords[rule_name] = "\"" + vArray[1] + "\"";
keywords[rule_name] = "\"" + trim(vArray[1]) + "\"";
else
keywords[rule_name] += ",\"" + vArray[1] + "\"";
keywords[rule_name] += ",\"" + trim(vArray[1]) + "\"";
}
else
{
@@ -449,7 +449,7 @@ int renderClashScript(YAML::Node &base_rule, std::vector<RulesetContent> &rulese
}
else
{
strLine = vArray[0] + "," + vArray[1] + "," + rule_group;
strLine = vArray[0] + "," + trim(vArray[1]) + "," + rule_group;
if(vArray.size() > 2)
strLine += "," + vArray[2];
}
@@ -466,14 +466,16 @@ int renderClashScript(YAML::Node &base_rule, std::vector<RulesetContent> &rulese
}
}
if(has_domain[rule_name] && !script)
rules.emplace_back("RULE-SET," + rule_name + "_domain," + rule_group);
rules.emplace_back("RULE-SET," + rule_name + " (Domain)," + rule_group);
if(has_ipcidr[rule_name] && !script)
{
if(has_no_resolve)
rules.emplace_back("RULE-SET," + rule_name + "_ipcidr," + rule_group + ",no-resolve");
rules.emplace_back("RULE-SET," + rule_name + " (IP-CIDR)," + rule_group + ",no-resolve");
else
rules.emplace_back("RULE-SET," + rule_name + "_ipcidr," + rule_group);
rules.emplace_back("RULE-SET," + rule_name + " (IP-CIDR)," + rule_group);
}
if(!has_domain[rule_name] && !has_ipcidr[rule_name] && !script)
rules.emplace_back("RULE-SET," + rule_name + "," + rule_group);
if(std::find(groups.begin(), groups.end(), rule_name) == groups.end())
groups.emplace_back(rule_name);
}
@@ -488,14 +490,14 @@ int renderClashScript(YAML::Node &base_rule, std::vector<RulesetContent> &rulese
{
std::string yaml_key = x;
if(rule_type[x] != RULESET_CLASH_DOMAIN)
yaml_key += "_domain";
yaml_key += " (Domain)";
base_rule["rule-providers"][yaml_key]["type"] = "http";
base_rule["rule-providers"][yaml_key]["behavior"] = "domain";
if(url[0] == '*')
base_rule["rule-providers"][yaml_key]["url"] = url.substr(1);
else
base_rule["rule-providers"][yaml_key]["url"] = remote_path_prefix + "/getruleset?type=3&url=" + urlSafeBase64Encode(url);
base_rule["rule-providers"][yaml_key]["path"] = "./providers/rule-provider_" + yaml_key + ".yaml";
base_rule["rule-providers"][yaml_key]["path"] = "./providers/" + std::to_string(hash_(url)) + "_domain.yaml";
if(interval)
base_rule["rule-providers"][yaml_key]["interval"] = interval;
}
@@ -503,14 +505,14 @@ int renderClashScript(YAML::Node &base_rule, std::vector<RulesetContent> &rulese
{
std::string yaml_key = x;
if(rule_type[x] != RULESET_CLASH_IPCIDR)
yaml_key += "_ipcidr";
yaml_key += " (IP-CIDR)";
base_rule["rule-providers"][yaml_key]["type"] = "http";
base_rule["rule-providers"][yaml_key]["behavior"] = "ipcidr";
if(url[0] == '*')
base_rule["rule-providers"][yaml_key]["url"] = url.substr(1);
else
base_rule["rule-providers"][yaml_key]["url"] = remote_path_prefix + "/getruleset?type=4&url=" + urlSafeBase64Encode(url);
base_rule["rule-providers"][yaml_key]["path"] = "./providers/rule-provider_" + yaml_key + ".yaml";
base_rule["rule-providers"][yaml_key]["path"] = "./providers/" + std::to_string(hash_(url)) + "_ipcidr.yaml";
if(interval)
base_rule["rule-providers"][yaml_key]["interval"] = interval;
}
@@ -523,7 +525,7 @@ int renderClashScript(YAML::Node &base_rule, std::vector<RulesetContent> &rulese
base_rule["rule-providers"][yaml_key]["url"] = url.substr(1);
else
base_rule["rule-providers"][yaml_key]["url"] = remote_path_prefix + "/getruleset?type=6&url=" + urlSafeBase64Encode(url);
base_rule["rule-providers"][yaml_key]["path"] = "./providers/rule-provider_" + yaml_key + ".yaml";
base_rule["rule-providers"][yaml_key]["path"] = "./providers/" + std::to_string(hash_(url)) + ".yaml";
if(interval)
base_rule["rule-providers"][yaml_key]["interval"] = interval;
}

View File

@@ -63,6 +63,7 @@ const std::vector<UAProfile> UAMatchList = {
{"ClashForAndroid","","","clash",false},
{"ClashforWindows","\\/([0-9.]+)","0.11","clash",true},
{"ClashforWindows","","","clash",false},
{"clash-verge","","","clash",true},
{"ClashX Pro","","","clash",true},
{"ClashX","\\/([0-9.]+)","0.13","clash",true},
{"Clash","","","clash",true},
@@ -424,6 +425,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
argExpandRulesets.define(true);
ext.clash_proxies_style = global.clashProxiesStyle;
ext.clash_proxy_groups_style = global.clashProxyGroupsStyle;
/// read preference from argument, assign global var if not in argument
ext.tfo.define(argTFO).define(global.TFOFlag);
@@ -455,15 +457,19 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
/// load external configuration
if(argExternalConfig.empty())
argExternalConfig = global.defaultExtConfig;
if (!argExternalConfig.empty()) {
if(!argExternalConfig.empty())
{
//std::cerr<<"External configuration file provided. Loading...\n";
writeLog(0, "External configuration file provided. Loading...", LOG_LEVEL_INFO);
ExternalConfig extconf;
extconf.tpl_args = &tpl_args;
if (loadExternalConfig(argExternalConfig, extconf) == 0) {
if (!ext.nodelist) {
if(loadExternalConfig(argExternalConfig, extconf) == 0)
{
if(!ext.nodelist)
{
checkExternalBase(extconf.sssub_rule_base, lSSSubBase);
if (!lSimpleSubscription) {
if(!lSimpleSubscription)
{
checkExternalBase(extconf.clash_rule_base, lClashBase);
checkExternalBase(extconf.surge_rule_base, lSurgeBase);
checkExternalBase(extconf.surfboard_rule_base, lSurfboardBase);
@@ -492,32 +498,40 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
argAddEmoji.define(extconf.add_emoji);
argRemoveEmoji.define(extconf.remove_old_emoji);
}
} else {
if (!lSimpleSubscription) {
}
else
{
if(!lSimpleSubscription)
{
/// loading custom groups
if (!argCustomGroups.empty() && !ext.nodelist) {
if(!argCustomGroups.empty() && !ext.nodelist)
{
string_array vArray = split(argCustomGroups, "@");
lCustomProxyGroups = INIBinding::from<ProxyGroupConfig>::from_ini(vArray);
}
/// loading custom rulesets
if (!argCustomRulesets.empty() && !ext.nodelist) {
if(!argCustomRulesets.empty() && !ext.nodelist)
{
string_array vArray = split(argCustomRulesets, "@");
lCustomRulesets = INIBinding::from<RulesetConfig>::from_ini(vArray);
}
}
}
if (ext.enable_rule_generator && !ext.nodelist && !lSimpleSubscription) {
if(ext.enable_rule_generator && !ext.nodelist && !lSimpleSubscription)
{
if(lCustomRulesets != global.customRulesets)
refreshRulesets(lCustomRulesets, lRulesetContent);
else {
else
{
if(global.updateRulesetOnRequest)
refreshRulesets(global.customRulesets, global.rulesetsContent);
lRulesetContent = global.rulesetsContent;
}
}
if (!argEmoji.is_undef()) {
if(!argEmoji.is_undef())
{
argAddEmoji.set(argEmoji);
argRemoveEmoji.set(true);
}
@@ -537,7 +551,8 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
lExcludeRemarks = string_array{argExcludeRemark};
/// initialize script runtime
if (authorized && !global.scriptCleanContext) {
if(authorized && !global.scriptCleanContext)
{
ext.js_runtime = new qjs::Runtime();
script_runtime_init(*ext.js_runtime);
ext.js_context = new qjs::Context(*ext.js_runtime);
@@ -564,17 +579,21 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
parse_set.js_runtime = ext.js_runtime;
parse_set.js_context = ext.js_context;
if (!global.insertUrls.empty() && argEnableInsert) {
if(!global.insertUrls.empty() && argEnableInsert)
{
groupID = -1;
urls = split(global.insertUrls, "|");
importItems(urls, true);
for (std::string &x: urls) {
for(std::string &x : urls)
{
x = regTrim(x);
writeLog(0, "Fetching node data from url '" + x + "'.", LOG_LEVEL_INFO);
if (addNodes(x, insert_nodes, groupID, parse_set) == -1) {
if(addNodes(x, insert_nodes, groupID, parse_set) == -1)
{
if(global.skipFailedLinks)
writeLog(0, "The following link doesn't contain any valid node info: " + x, LOG_LEVEL_WARNING);
else {
else
{
*status_code = 400;
return "The following link doesn't contain any valid node info: " + x;
}
@@ -585,14 +604,17 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
urls = split(argUrl, "|");
importItems(urls, true);
groupID = 0;
for (std::string &x: urls) {
for(std::string &x : urls)
{
x = regTrim(x);
//std::cerr<<"Fetching node data from url '"<<x<<"'."<<std::endl;
writeLog(0, "Fetching node data from url '" + x + "'.", LOG_LEVEL_INFO);
if (addNodes(x, nodes, groupID, parse_set) == -1) {
if(addNodes(x, nodes, groupID, parse_set) == -1)
{
if(global.skipFailedLinks)
writeLog(0, "The following link doesn't contain any valid node info: " + x, LOG_LEVEL_WARNING);
else {
else
{
*status_code = 400;
return "The following link doesn't contain any valid node info: " + x;
}
@@ -600,7 +622,8 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
groupID++;
}
//exit if found nothing
if (nodes.empty() && insert_nodes.empty()) {
if(nodes.empty() && insert_nodes.empty())
{
*status_code = 400;
return "No nodes were found!";
}
@@ -611,17 +634,21 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
return "";
argPrependInsert.define(global.prependInsert);
if (argPrependInsert) {
if(argPrependInsert)
{
std::move(nodes.begin(), nodes.end(), std::back_inserter(insert_nodes));
nodes.swap(insert_nodes);
} else {
}
else
{
std::move(insert_nodes.begin(), insert_nodes.end(), std::back_inserter(nodes));
}
//run filter script
std::string filterScript = global.filterScript;
if(authorized && !argFilterScript.empty())
filterScript = argFilterScript;
if (!filterScript.empty()) {
if(!filterScript.empty())
{
if(startsWith(filterScript, "path:"))
filterScript = fileGet(filterScript.substr(5), false);
/*
@@ -647,13 +674,16 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
}
}
*/
script_safe_runner(ext.js_runtime, ext.js_context, [&](qjs::Context &ctx) {
try {
script_safe_runner(ext.js_runtime, ext.js_context, [&](qjs::Context &ctx)
{
try
{
ctx.eval(filterScript);
auto filter = (std::function<bool(const Proxy&)>) ctx.eval("filter");
nodes.erase(std::remove_if(nodes.begin(), nodes.end(), filter), nodes.end());
}
catch (qjs::exception) {
catch(qjs::exception)
{
script_print_stack(ctx);
}
}, global.scriptCleanContext);
@@ -686,27 +716,29 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
std::string managed_url = base64Decode(getUrlArg(argument, "profile_data"));
if(managed_url.empty())
managed_url = global.managedConfigPrefix + "/sub?" + joinArguments(argument);
size_t found;
//std::cerr<<"Generate target: ";
proxy = parseProxy(global.proxyConfig);
switch (hash_(argTarget)) {
case "clash"_hash:
case "clashr"_hash:
switch(hash_(argTarget))
{
case "clash"_hash: case "clashr"_hash:
writeLog(0, argTarget == "clashr" ? "Generate target: ClashR" : "Generate target: Clash", LOG_LEVEL_INFO);
tpl_args.local_vars["clash.new_field_name"] = ext.clash_new_field_name ? "true" : "false";
response.headers["profile-update-interval"] = std::to_string(interval / 3600);
if (ext.nodelist) {
if(ext.nodelist)
{
YAML::Node yamlnode;
proxyToClash(nodes, yamlnode, dummy_group, argTarget == "clashr", ext);
output_content = YAML::Dump(yamlnode);
} else {
if (render_template(fetchFile(lClashBase, proxy, global.cacheConfig), tpl_args, base_content,
global.templatePath) != 0) {
}
else
{
if(render_template(fetchFile(lClashBase, proxy, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0)
{
*status_code = 400;
return base_content;
}
output_content = proxyToClash(nodes, base_content, lRulesetContent, lCustomProxyGroups,
argTarget == "clashr", ext);
output_content = proxyToClash(nodes, base_content, lRulesetContent, lCustomProxyGroups, argTarget == "clashr", ext);
}
if(argUpload)
@@ -716,34 +748,35 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
writeLog(0, "Generate target: Surge " + std::to_string(intSurgeVer), LOG_LEVEL_INFO);
if (ext.nodelist) {
if(ext.nodelist)
{
output_content = proxyToSurge(nodes, base_content, dummy_ruleset, dummy_group, intSurgeVer, ext);
if(argUpload)
uploadGist("surge" + argSurgeVer + "list", argUploadPath, output_content, true);
} else {
if (render_template(fetchFile(lSurgeBase, proxy, global.cacheConfig), tpl_args, base_content,
global.templatePath) != 0) {
}
else
{
if(render_template(fetchFile(lSurgeBase, proxy, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0)
{
*status_code = 400;
return base_content;
}
output_content = proxyToSurge(nodes, base_content, lRulesetContent, lCustomProxyGroups, intSurgeVer,
ext);
output_content = proxyToSurge(nodes, base_content, lRulesetContent, lCustomProxyGroups, intSurgeVer, ext);
if(argUpload)
uploadGist("surge" + argSurgeVer, argUploadPath, output_content, true);
if(global.writeManagedConfig && !global.managedConfigPrefix.empty())
output_content = "#!MANAGED-CONFIG " + managed_url +
(interval ? " interval=" + std::to_string(interval) : "") \
output_content = "#!MANAGED-CONFIG " + managed_url + (interval ? " interval=" + std::to_string(interval) : "") \
+ " strict=" + std::string(strict ? "true" : "false") + "\n\n" + output_content;
}
break;
case "surfboard"_hash:
writeLog(0, "Generate target: Surfboard", LOG_LEVEL_INFO);
if (render_template(fetchFile(lSurfboardBase, proxy, global.cacheConfig), tpl_args, base_content,
global.templatePath) != 0) {
if(render_template(fetchFile(lSurfboardBase, proxy, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0)
{
*status_code = 400;
return base_content;
}
@@ -752,15 +785,14 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
uploadGist("surfboard", argUploadPath, output_content, true);
if(global.writeManagedConfig && !global.managedConfigPrefix.empty())
output_content =
"#!MANAGED-CONFIG " + managed_url + (interval ? " interval=" + std::to_string(interval) : "") \
output_content = "#!MANAGED-CONFIG " + managed_url + (interval ? " interval=" + std::to_string(interval) : "") \
+ " strict=" + std::string(strict ? "true" : "false") + "\n\n" + output_content;
break;
case "mellow"_hash:
writeLog(0, "Generate target: Mellow", LOG_LEVEL_INFO);
if (render_template(fetchFile(lMellowBase, proxy, global.cacheConfig), tpl_args, base_content,
global.templatePath) != 0) {
if(render_template(fetchFile(lMellowBase, proxy, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0)
{
*status_code = 400;
return base_content;
}
@@ -772,8 +804,8 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
case "sssub"_hash:
writeLog(0, "Generate target: SS Subscription", LOG_LEVEL_INFO);
if (render_template(fetchFile(lSSSubBase, proxy, global.cacheConfig), tpl_args, base_content,
global.templatePath) != 0) {
if(render_template(fetchFile(lSSSubBase, proxy, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0)
{
*status_code = 400;
return base_content;
}
@@ -813,9 +845,10 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
break;
case "quan"_hash:
writeLog(0, "Generate target: Quantumult", LOG_LEVEL_INFO);
if (!ext.nodelist) {
if (render_template(fetchFile(lQuanBase, proxy, global.cacheConfig), tpl_args, base_content,
global.templatePath) != 0) {
if(!ext.nodelist)
{
if(render_template(fetchFile(lQuanBase, proxy, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0)
{
*status_code = 400;
return base_content;
}
@@ -828,9 +861,10 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
break;
case "quanx"_hash:
writeLog(0, "Generate target: Quantumult X", LOG_LEVEL_INFO);
if (!ext.nodelist) {
if (render_template(fetchFile(lQuanXBase, proxy, global.cacheConfig), tpl_args, base_content,
global.templatePath) != 0) {
if(!ext.nodelist)
{
if(render_template(fetchFile(lQuanXBase, proxy, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0)
{
*status_code = 400;
return base_content;
}
@@ -843,9 +877,10 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
break;
case "loon"_hash:
writeLog(0, "Generate target: Loon", LOG_LEVEL_INFO);
if (!ext.nodelist) {
if (render_template(fetchFile(lLoonBase, proxy, global.cacheConfig), tpl_args, base_content,
global.templatePath) != 0) {
if(!ext.nodelist)
{
if(render_template(fetchFile(lLoonBase, proxy, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0)
{
*status_code = 400;
return base_content;
}
@@ -864,20 +899,16 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
break;
case "singbox"_hash:
writeLog(0, "Generate target: sing-box", LOG_LEVEL_INFO);
if (!ext.nodelist) {
if (render_template(fetchFile(lSingBoxBase, proxy, global.cacheConfig), tpl_args, base_content,
global.templatePath) != 0) {
if(!ext.nodelist)
{
if(render_template(fetchFile(lSingBoxBase, proxy, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0)
{
*status_code = 400;
return base_content;
}
}
output_content = proxyToSingBox(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext);
found = output_content.find('\u0000');
while (found != std::string::npos) {
output_content.erase(found, 1);
found = output_content.find('\u0000', found);
}
if(argUpload)
uploadGist("singbox", argUploadPath, output_content, false);
@@ -889,23 +920,23 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) {
}
writeLog(0, "Generate completed.", LOG_LEVEL_INFO);
if(!argFilename.empty())
response.headers.emplace("Content-Disposition",
"attachment; filename=\"" + argFilename + "\"; filename*=utf-8''" +
urlEncode(argFilename));
response.headers.emplace("Content-Disposition", "attachment; filename=\"" + argFilename + "\"; filename*=utf-8''" + urlEncode(argFilename));
return output_content;
}
std::string simpleToClashR(RESPONSE_CALLBACK_ARGS) {
std::string simpleToClashR(RESPONSE_CALLBACK_ARGS)
{
auto argument = joinArguments(request.argument);
int *status_code = &response.status_code;
std::string url = argument.size() <= 8 ? "" : argument.substr(8);
if (url.empty() || argument.substr(0, 8) != "sublink=") {
if(url.empty() || argument.substr(0, 8) != "sublink=")
{
*status_code = 400;
return "Invalid request!";
}
if (url == "sublink") {
if(url == "sublink")
{
*status_code = 400;
return "Please insert your subscription link instead of clicking the default link.";
}
@@ -914,7 +945,8 @@ std::string simpleToClashR(RESPONSE_CALLBACK_ARGS) {
return subconverter(request, response);
}
std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) {
std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS)
{
auto argument = joinArguments(request.argument);
int *status_code = &response.status_code;
@@ -922,19 +954,19 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) {
string_array dummy_str_array;
std::vector<Proxy> nodes;
std::string base_content, url = argument.size() <= 5 ? "" : argument.substr(5);
const std::string proxygroup_name = global.clashUseNewField ? "proxy-groups"
: "Proxy Group", rule_name = global.clashUseNewField
? "rules" : "Rule";
const std::string proxygroup_name = global.clashUseNewField ? "proxy-groups" : "Proxy Group", rule_name = global.clashUseNewField ? "rules" : "Rule";
ini.store_any_line = true;
if(url.empty())
url = global.defaultUrls;
if (url.empty() || argument.substr(0, 5) != "link=") {
if(url.empty() || argument.substr(0, 5) != "link=")
{
*status_code = 400;
return "Invalid request!";
}
if (url == "link") {
if(url == "link")
{
*status_code = 400;
return "Please insert your subscription link instead of clicking the default link.";
}
@@ -948,8 +980,8 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) {
tpl_args.request_params["target"] = "clash";
tpl_args.request_params["url"] = url;
if (render_template(fetchFile(global.clashBase, proxy, global.cacheConfig), tpl_args, base_content,
global.templatePath) != 0) {
if(render_template(fetchFile(global.clashBase, proxy, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0)
{
*status_code = 400;
return base_content;
}
@@ -957,14 +989,16 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) {
base_content = fetchFile(url, proxy, global.cacheConfig);
if (ini.parse(base_content) != INIREADER_EXCEPTION_NONE) {
if(ini.parse(base_content) != INIREADER_EXCEPTION_NONE)
{
std::string errmsg = "Parsing Surge config failed! Reason: " + ini.get_last_error();
//std::cerr<<errmsg<<"\n";
writeLog(0, errmsg, LOG_LEVEL_ERROR);
*status_code = 400;
return errmsg;
}
if (!ini.section_exist("Proxy") || !ini.section_exist("Proxy Group") || !ini.section_exist("Rule")) {
if(!ini.section_exist("Proxy") || !ini.section_exist("Proxy Group") || !ini.section_exist("Rule"))
{
std::string errmsg = "Incomplete surge config! Missing critical sections!";
//std::cerr<<errmsg<<"\n";
writeLog(0, errmsg, LOG_LEVEL_ERROR);
@@ -979,7 +1013,8 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) {
string_array links;
links.emplace_back(url);
YAML::Node singlegroup;
for (auto &x: section) {
for(auto &x : section)
{
singlegroup.reset();
name = x.first;
content = x.second;
@@ -987,12 +1022,12 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) {
if(dummy_str_array.empty())
continue;
type = dummy_str_array[0];
if (!(type == "select" || type == "url-test" || type == "fallback" ||
type == "load-balance")) //remove unsupported types
if(!(type == "select" || type == "url-test" || type == "fallback" || type == "load-balance")) //remove unsupported types
continue;
singlegroup["name"] = name;
singlegroup["type"] = type;
for (unsigned int i = 1; i < dummy_str_array.size(); i++) {
for(unsigned int i = 1; i < dummy_str_array.size(); i++)
{
if(startsWith(dummy_str_array[i], "url"))
singlegroup["url"] = trim(dummy_str_array[i].substr(dummy_str_array[i].find('=') + 1));
else if(startsWith(dummy_str_array[i], "interval"))
@@ -1017,13 +1052,16 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) {
parse_set.request_header = &request.headers;
parse_set.sub_info = &subInfo;
parse_set.authorized = !global.APIMode;
for (std::string &x: links) {
for(std::string &x : links)
{
//std::cerr<<"Fetching node data from url '"<<x<<"'."<<std::endl;
writeLog(0, "Fetching node data from url '" + x + "'.", LOG_LEVEL_INFO);
if (addNodes(x, nodes, 0, parse_set) == -1) {
if(addNodes(x, nodes, 0, parse_set) == -1)
{
if(global.skipFailedLinks)
writeLog(0, "The following link doesn't contain any valid node info: " + x, LOG_LEVEL_WARNING);
else {
else
{
*status_code = 400;
return "The following link doesn't contain any valid node info: " + x;
}
@@ -1031,7 +1069,8 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) {
}
//exit if found nothing
if (nodes.empty()) {
if(nodes.empty())
{
*status_code = 400;
return "No nodes were found!";
}
@@ -1051,7 +1090,8 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) {
section.clear();
ini.get_items("Proxy", section);
for (auto &x: section) {
for(auto &x : section)
{
singlegroup.reset();
name = x.first;
content = x.second;
@@ -1059,7 +1099,8 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) {
if(dummy_str_array.empty())
continue;
content = trim(dummy_str_array[0]);
switch (hash_(content)) {
switch(hash_(content))
{
case "direct"_hash:
singlegroup["name"] = name;
singlegroup["type"] = "select";
@@ -1084,8 +1125,10 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) {
std::string strLine;
std::stringstream ss;
std::string::size_type lineSize;
for (std::string &x: dummy_str_array) {
if (startsWith(x, "RULE-SET")) {
for(std::string &x : dummy_str_array)
{
if(startsWith(x, "RULE-SET"))
{
strArray = split(x, ",");
if(strArray.size() != 3)
continue;
@@ -1096,17 +1139,14 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) {
ss << content;
char delimiter = getLineBreak(content);
while (getline(ss, strLine, delimiter)) {
while(getline(ss, strLine, delimiter))
{
lineSize = strLine.size();
if(lineSize && strLine[lineSize - 1] == '\r') //remove line break
strLine.erase(--lineSize);
if (!lineSize || strLine[0] == ';' || strLine[0] == '#' ||
(lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored
if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored
continue;
else if (!std::any_of(ClashRuleTypes.begin(), ClashRuleTypes.end(),
[&strLine](const std::string &type) {
return startsWith(strLine, type);
})) //remove unsupported types
else if(!std::any_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [&strLine](const std::string& type){return startsWith(strLine, type);})) //remove unsupported types
continue;
strLine += strArray[2];
if(count_least(strLine, ',', 3))
@@ -1115,8 +1155,8 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) {
}
ss.clear();
continue;
} else if (!std::any_of(ClashRuleTypes.begin(), ClashRuleTypes.end(),
[&strLine](const std::string &type) { return startsWith(strLine, type); }))
}
else if(!std::any_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [&strLine](const std::string& type){return startsWith(strLine, type);}))
continue;
rule.push_back(x);
}
@@ -1127,13 +1167,15 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) {
return YAML::Dump(clash);
}
std::string getProfile(RESPONSE_CALLBACK_ARGS) {
std::string getProfile(RESPONSE_CALLBACK_ARGS)
{
auto &argument = request.argument;
int *status_code = &response.status_code;
std::string name = getUrlArg(argument, "name"), token = getUrlArg(argument, "token");
string_array profiles = split(name, "|");
if (token.empty() || profiles.empty()) {
if(token.empty() || profiles.empty())
{
*status_code = 403;
return "Forbidden";
}
@@ -1144,16 +1186,20 @@ std::string getProfile(RESPONSE_CALLBACK_ARGS) {
profile_content = vfs::vfs_get(name);
}
else */
if (fileExist(name)) {
if(fileExist(name))
{
profile_content = fileGet(name, true);
} else {
}
else
{
*status_code = 404;
return "Profile not found";
}
//std::cerr<<"Trying to load profile '" + name + "'.\n";
writeLog(0, "Trying to load profile '" + name + "'.", LOG_LEVEL_INFO);
INIReader ini;
if (ini.parse(profile_content) != INIREADER_EXCEPTION_NONE && !ini.section_exist("Profile")) {
if(ini.parse(profile_content) != INIREADER_EXCEPTION_NONE && !ini.section_exist("Profile"))
{
//std::cerr<<"Load profile failed! Reason: "<<ini.get_last_error()<<"\n";
writeLog(0, "Load profile failed! Reason: " + ini.get_last_error(), LOG_LEVEL_ERROR);
*status_code = 500;
@@ -1163,47 +1209,60 @@ std::string getProfile(RESPONSE_CALLBACK_ARGS) {
writeLog(0, "Trying to parse profile '" + name + "'.", LOG_LEVEL_INFO);
string_multimap contents;
ini.get_items("Profile", contents);
if (contents.empty()) {
if(contents.empty())
{
//std::cerr<<"Load profile failed! Reason: Empty Profile section\n";
writeLog(0, "Load profile failed! Reason: Empty Profile section", LOG_LEVEL_ERROR);
*status_code = 500;
return "Broken profile!";
}
auto profile_token = contents.find("profile_token");
if (profiles.size() == 1 && profile_token != contents.end()) {
if (token != profile_token->second) {
if(profiles.size() == 1 && profile_token != contents.end())
{
if(token != profile_token->second)
{
*status_code = 403;
return "Forbidden";
}
token = global.accessToken;
} else {
if (token != global.accessToken) {
}
else
{
if(token != global.accessToken)
{
*status_code = 403;
return "Forbidden";
}
}
/// check if more than one profile is provided
if (profiles.size() > 1) {
if(profiles.size() > 1)
{
writeLog(0, "Multiple profiles are provided. Trying to combine profiles...", LOG_TYPE_INFO);
std::string all_urls, url;
auto iter = contents.find("url");
if(iter != contents.end())
all_urls = iter->second;
for (size_t i = 1; i < profiles.size(); i++) {
for(size_t i = 1; i < profiles.size(); i++)
{
name = profiles[i];
if (!fileExist(name)) {
if(!fileExist(name))
{
writeLog(0, "Ignoring non-exist profile '" + name + "'...", LOG_LEVEL_WARNING);
continue;
}
if (ini.parse_file(name) != INIREADER_EXCEPTION_NONE && !ini.section_exist("Profile")) {
if(ini.parse_file(name) != INIREADER_EXCEPTION_NONE && !ini.section_exist("Profile"))
{
writeLog(0, "Ignoring broken profile '" + name + "'...", LOG_LEVEL_WARNING);
continue;
}
url = ini.get("Profile", "url");
if (!url.empty()) {
if(!url.empty())
{
all_urls += "|" + url;
writeLog(0, "Profile url from '" + name + "' added.", LOG_LEVEL_INFO);
} else {
}
else
{
writeLog(0, "Profile '" + name + "' does not have url key. Skipping...", LOG_LEVEL_INFO);
}
}
@@ -1211,8 +1270,7 @@ std::string getProfile(RESPONSE_CALLBACK_ARGS) {
}
contents.emplace("token", token);
contents.emplace("profile_data",
base64Encode(global.managedConfigPrefix + "/getprofile?" + joinArguments(argument)));
contents.emplace("profile_data", base64Encode(global.managedConfigPrefix + "/getprofile?" + joinArguments(argument)));
std::copy(argument.cbegin(), argument.cend(), std::inserter(contents, contents.end()));
request.argument = contents;
return subconverter(request, response);
@@ -1226,11 +1284,13 @@ std::string jinja2_webGet(const std::string &url)
return webGet(url, proxy, global.cacheConfig);
}*/
inline std::string intToStream(unsigned long long stream) {
inline std::string intToStream(unsigned long long stream)
{
char chrs[16] = {}, units[6] = {' ', 'K', 'M', 'G', 'T', 'P'};
double streamval = stream;
unsigned int level = 0;
while (streamval > 1024.0) {
while(streamval > 1024.0)
{
if(level >= 5)
break;
level++;
@@ -1240,19 +1300,20 @@ inline std::string intToStream(unsigned long long stream) {
return {chrs};
}
std::string subInfoToMessage(std::string subinfo) {
std::string subInfoToMessage(std::string subinfo)
{
using ull = unsigned long long;
subinfo = replaceAllDistinct(subinfo, "; ", "&");
std::string retdata, useddata = "N/A", totaldata = "N/A", expirydata = "N/A";
std::string upload = getUrlArg(subinfo, "upload"), download = getUrlArg(subinfo, "download"), total = getUrlArg(
subinfo, "total"), expire = getUrlArg(subinfo, "expire");
std::string upload = getUrlArg(subinfo, "upload"), download = getUrlArg(subinfo, "download"), total = getUrlArg(subinfo, "total"), expire = getUrlArg(subinfo, "expire");
ull used = to_number<ull>(upload, 0) + to_number<ull>(download, 0), tot = to_number<ull>(total, 0);
auto expiry = to_number<time_t>(expire, 0);
if(used != 0)
useddata = intToStream(used);
if(tot != 0)
totaldata = intToStream(tot);
if (expiry != 0) {
if(expiry != 0)
{
char buffer[30];
struct tm *dt = localtime(&expiry);
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M", dt);
@@ -1265,18 +1326,21 @@ std::string subInfoToMessage(std::string subinfo) {
return retdata;
}
int simpleGenerator() {
int simpleGenerator()
{
//std::cerr<<"\nReading generator configuration...\n";
writeLog(0, "Reading generator configuration...", LOG_LEVEL_INFO);
std::string config = fileGet("generate.ini"), path, profile, content;
if (config.empty()) {
if(config.empty())
{
//std::cerr<<"Generator configuration not found or empty!\n";
writeLog(0, "Generator configuration not found or empty!", LOG_LEVEL_ERROR);
return -1;
}
INIReader ini;
if (ini.parse(config) != INIREADER_EXCEPTION_NONE) {
if(ini.parse(config) != INIREADER_EXCEPTION_NONE)
{
//std::cerr<<"Generator configuration broken! Reason:"<<ini.get_last_error()<<"\n";
writeLog(0, "Generator configuration broken! Reason:" + ini.get_last_error(), LOG_LEVEL_ERROR);
return -2;
@@ -1285,15 +1349,18 @@ int simpleGenerator() {
writeLog(0, "Read generator configuration completed.\n", LOG_LEVEL_INFO);
string_array sections = ini.get_section_names();
if (!global.generateProfiles.empty()) {
if(!global.generateProfiles.empty())
{
//std::cerr<<"Generating with specific artifacts: \""<<gen_profile<<"\"...\n";
writeLog(0, "Generating with specific artifacts: \"" + global.generateProfiles + "\"...", LOG_LEVEL_INFO);
string_array targets = split(global.generateProfiles, ","), new_targets;
for (std::string &x: targets) {
for(std::string &x : targets)
{
x = trim(x);
if(std::find(sections.cbegin(), sections.cend(), x) != sections.cend())
new_targets.emplace_back(std::move(x));
else {
else
{
//std::cerr<<"Artifact \""<<x<<"\" not found in generator settings!\n";
writeLog(0, "Artifact \"" + x + "\" not found in generator settings!", LOG_LEVEL_ERROR);
return -3;
@@ -1301,7 +1368,8 @@ int simpleGenerator() {
}
sections = new_targets;
sections.shrink_to_fit();
} else
}
else
//std::cerr<<"Generating all artifacts...\n";
writeLog(0, "Generating all artifacts...", LOG_LEVEL_INFO);
@@ -1309,29 +1377,36 @@ int simpleGenerator() {
std::string proxy = parseProxy(global.proxySubscription);
Request request;
Response response;
for (std::string &x: sections) {
for(std::string &x : sections)
{
response.status_code = 200;
//std::cerr<<"Generating artifact '"<<x<<"'...\n";
writeLog(0, "Generating artifact '" + x + "'...", LOG_LEVEL_INFO);
ini.enter_section(x);
if(ini.item_exist("path"))
path = ini.get("path");
else {
else
{
//std::cerr<<"Artifact '"<<x<<"' output path missing! Skipping...\n\n";
writeLog(0, "Artifact '" + x + "' output path missing! Skipping...\n", LOG_LEVEL_ERROR);
continue;
}
if (ini.item_exist("profile")) {
if(ini.item_exist("profile"))
{
profile = ini.get("profile");
request.argument.emplace("name", urlEncode(profile));
request.argument.emplace("token", global.accessToken);
request.argument.emplace("expand", "true");
content = getProfile(request, response);
} else {
if (ini.get_bool("direct")) {
}
else
{
if(ini.get_bool("direct"))
{
std::string url = ini.get("url");
content = fetchFile(url, proxy, global.cacheSubscription);
if (content.empty()) {
if(content.empty())
{
//std::cerr<<"Artifact '"<<x<<"' generate ERROR! Please check your link.\n\n";
writeLog(0, "Artifact '" + x + "' generate ERROR! Please check your link.\n", LOG_LEVEL_ERROR);
if(sections.size() == 1)
@@ -1343,14 +1418,16 @@ int simpleGenerator() {
}
ini.get_items(allItems);
allItems.emplace("expand", "true");
for (auto &y: allItems) {
for(auto &y : allItems)
{
if(y.first == "path")
continue;
request.argument.emplace(y.first, y.second);
}
content = subconverter(request, response);
}
if (response.status_code != 200) {
if(response.status_code != 200)
{
//std::cerr<<"Artifact '"<<x<<"' generate ERROR! Reason: "<<content<<"\n\n";
writeLog(0, "Artifact '" + x + "' generate ERROR! Reason: " + content + "\n", LOG_LEVEL_ERROR);
if(sections.size() == 1)
@@ -1358,8 +1435,7 @@ int simpleGenerator() {
continue;
}
fileWrite(path, content, true);
auto iter = std::find_if(response.headers.begin(), response.headers.end(),
[](auto y) { return y.first == "Subscription-UserInfo"; });
auto iter = std::find_if(response.headers.begin(), response.headers.end(), [](auto y){ return y.first == "Subscription-UserInfo"; });
if(iter != response.headers.end())
writeLog(0, "User Info for artifact '" + x + "': " + subInfoToMessage(iter->second), LOG_LEVEL_INFO);
//std::cerr<<"Artifact '"<<x<<"' generate SUCCESS!\n\n";
@@ -1371,19 +1447,22 @@ int simpleGenerator() {
return 0;
}
std::string renderTemplate(RESPONSE_CALLBACK_ARGS) {
std::string renderTemplate(RESPONSE_CALLBACK_ARGS)
{
auto &argument = request.argument;
int *status_code = &response.status_code;
std::string path = getUrlArg(argument, "path");
writeLog(0, "Trying to render template '" + path + "'...", LOG_LEVEL_INFO);
if (!startsWith(path, global.templatePath) || !fileExist(path)) {
if(!startsWith(path, global.templatePath) || !fileExist(path))
{
*status_code = 404;
return "Not found";
}
std::string template_content = fetchFile(path, parseProxy(global.proxyConfig), global.cacheConfig);
if (template_content.empty()) {
if(template_content.empty())
{
*status_code = 400;
return "File empty or out of scope";
}
@@ -1392,16 +1471,19 @@ std::string renderTemplate(RESPONSE_CALLBACK_ARGS) {
//load request arguments as template variables
string_map req_arg_map;
for (auto &x: argument) {
for (auto &x : argument)
{
req_arg_map[x.first] = x.second;
}
tpl_args.request_params = req_arg_map;
std::string output_content;
if (render_template(template_content, tpl_args, output_content, global.templatePath) != 0) {
if(render_template(template_content, tpl_args, output_content, global.templatePath) != 0)
{
*status_code = 400;
writeLog(0, "Render failed with error.", LOG_LEVEL_WARNING);
} else
}
else
writeLog(0, "Render completed.", LOG_LEVEL_INFO);
return output_content;

View File

@@ -564,14 +564,14 @@ void readYAMLConf(YAML::Node &node)
writeLog(0, "Load preference settings in YAML format completed.", LOG_LEVEL_INFO);
}
//template <class T, class... U>
//void find_if_exist(const toml::value &v, const toml::key &k, T& target, U&&... args)
//{
// if(v.contains(k)) target = toml::find<T>(v, k);
// if constexpr (sizeof...(args) > 0) find_if_exist(v, std::forward<U>(args)...);
//}
template <class T, class... U>
void find_if_exist(const toml::value &v, const toml::value::key_type &k, T& target, U&&... args)
{
if(v.contains(k)) target = toml::find<T>(v, k);
if constexpr (sizeof...(args) > 0) find_if_exist(v, std::forward<U>(args)...);
}
void operate_toml_kv_table(const std::vector<toml::table> &arr, const toml::key &key_name, const toml::key &value_name, std::function<void (const toml::value&, const toml::value&)> binary_op)
void operate_toml_kv_table(const std::vector<toml::table> &arr, const toml::value::key_type &key_name, const toml::value::key_type &value_name, std::function<void (const toml::value&, const toml::value&)> binary_op)
{
for(const toml::table &table : arr)
{

View File

@@ -49,7 +49,7 @@ struct Settings
tribool UDPFlag, TFOFlag, skipCertVerify, TLS13Flag, enableInsert;
bool enableSort = false, updateStrict = false;
bool clashUseNewField = false, singBoxAddClashModes = true;
std::string clashProxiesStyle = "flow";
std::string clashProxiesStyle = "flow", clashProxyGroupsStyle = "block";
std::string proxyConfig, proxyRuleset, proxySubscription;
int updateInterval = 0;
std::string sortScript, filterScript;

View File

@@ -87,11 +87,13 @@ static int logger(CURL *handle, curl_infotype type, char *data, size_t size, voi
switch(type)
{
case CURLINFO_TEXT:
prefix = "CURL_INFO";
prefix = "CURL_INFO: ";
break;
case CURLINFO_HEADER_IN:
prefix = "CURL_HEADER: < ";
break;
case CURLINFO_HEADER_OUT:
prefix = "CURL_HEADER";
prefix = "CURL_HEADER: > ";
break;
case CURLINFO_DATA_IN:
case CURLINFO_DATA_OUT:
@@ -105,7 +107,6 @@ static int logger(CURL *handle, curl_infotype type, char *data, size_t size, voi
for(auto &x : lines)
{
std::string log_content = prefix;
log_content += ": ";
log_content += x;
writeLog(0, log_content, LOG_LEVEL_VERBOSE);
}
@@ -113,7 +114,6 @@ static int logger(CURL *handle, curl_infotype type, char *data, size_t size, voi
else
{
std::string log_content = prefix;
log_content += ": ";
log_content += trimWhitespace(content);
writeLog(0, log_content, LOG_LEVEL_VERBOSE);
}
@@ -172,7 +172,8 @@ static int curlGet(const FetchArgument &argument, FetchResult &result)
{
for(auto &x : *argument.request_headers)
{
header_list = curl_slist_append(header_list, (x.first + ": " + x.second).data());
auto header = x.first + ": " + x.second;
header_list = curl_slist_append(header_list, header.data());
}
if(!argument.request_headers->contains("User-Agent"))
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, user_agent_str);
@@ -233,7 +234,7 @@ static int curlGet(const FetchArgument &argument, FetchResult &result)
while(true)
{
retVal = curl_easy_perform(curl_handle);
if(retVal == CURLE_OK || max_fails <= fail_count)
if(retVal == CURLE_OK || max_fails <= fail_count || global.APIMode)
break;
else
fail_count++;

View File

@@ -233,10 +233,10 @@ int main(int argc, char *argv[])
}
}
std::string type = getUrlArg(request.argument, "type");
if(type == "form")
fileWrite(global.prefPath, getFormData(request.postdata), true);
else if(type == "direct")
if(type == "form" || type == "direct")
{
fileWrite(global.prefPath, request.postdata, true);
}
else
{
response.status_code = 501;

View File

@@ -9,7 +9,8 @@
using String = std::string;
using StringArray = std::vector<String>;
enum class ProxyType {
enum class ProxyType
{
Unknown,
Shadowsocks,
ShadowsocksR,

View File

@@ -2,6 +2,7 @@
#include <map>
#include <iostream>
#include <quickjspp.hpp>
#include <utility>
#include <quickjs/quickjs-libc.h>
#ifdef _WIN32
@@ -226,7 +227,7 @@ public:
qjs_fetch_Headers headers;
std::string cookies;
std::string postdata;
explicit qjs_fetch_Request(const std::string &url) : url(url) {}
explicit qjs_fetch_Request(std::string url) : url(std::move(url)) {}
};
class qjs_fetch_Response
@@ -389,7 +390,7 @@ void script_runtime_init(qjs::Runtime &runtime)
js_std_init_handlers(runtime.rt);
}
int ShowMsgbox(const std::string &title, std::string content, uint16_t type = 0)
int ShowMsgbox(const std::string &title, const std::string &content, uint16_t type = 0)
{
#ifdef _WIN32
if(!type)
@@ -424,7 +425,7 @@ struct Lambda {
uint32_t currentTime()
{
return time(NULL);
return time(nullptr);
}
int script_context_init(qjs::Context &context)
@@ -525,7 +526,7 @@ int script_context_init(qjs::Context &context)
)", "<import>", JS_EVAL_TYPE_MODULE);
return 0;
}
catch(qjs::exception)
catch(qjs::exception&)
{
script_print_stack(context);
return 1;

View File

@@ -47,10 +47,16 @@ static httplib::Server::Handler makeHandler(const responseRoute &rr)
{
continue;
}
req.headers[h.first] = h.second;
req.headers.emplace(h.first.data(), h.second.data());
}
req.argument = request.params;
if (request.get_header_value("Content-Type") == "application/x-www-form-urlencoded")
if (request.method == "POST" || request.method == "PUT" || request.method == "PATCH")
{
if (request.is_multipart_form_data() && !request.files.empty())
{
req.postdata = request.files.begin()->second.content;
}
else if (request.get_header_value("Content-Type") == "application/x-www-form-urlencoded")
{
req.postdata = urlDecode(request.body);
}
@@ -58,6 +64,7 @@ static httplib::Server::Handler makeHandler(const responseRoute &rr)
{
req.postdata = request.body;
}
}
auto result = rr.rc(req, resp);
response.status = resp.status_code;
for (auto &h: resp.headers)
@@ -163,6 +170,7 @@ int WebServer::start_web_server_multi(listener_args *args)
{
res.set_header("Access-Control-Allow-Headers", req.get_header_value("Access-Control-Request-Headers"));
}
res.set_header("Access-Control-Allow-Origin", "*");
return httplib::Server::HandlerResponse::Unhandled;
});
for (auto &x : redirect_map)
@@ -187,7 +195,7 @@ int WebServer::start_web_server_multi(listener_args *args)
{
try
{
std::rethrow_exception(e);
if (e) std::rethrow_exception(e);
}
catch (const httplib::Error &err)
{
@@ -212,6 +220,9 @@ int WebServer::start_web_server_multi(listener_args *args)
{
server.set_mount_point("/", serve_file_root);
}
server.new_task_queue = [args] {
return new httplib::ThreadPool(args->max_workers);
};
server.bind_to_port(args->listen_address, args->port, 0);
std::thread thread([&]()

View File

@@ -26,7 +26,8 @@ std::string getTime(int type)
format = "%Y%m%d-%H%M%S";
break;
case 2:
format = "%Y/%m/%d %a %H:%M:%S." + std::string(cMillis);
format = "%Y/%m/%d %a %H:%M:%S.";
format += cMillis;
break;
case 3:
default:

View File

@@ -5,9 +5,16 @@
#include <map>
#include <string.h>
struct strICaseComp {
bool operator()(const std::string &lhs, const std::string &rhs) const {
return strcasecmp(lhs.c_str(), rhs.c_str()) > 0;
struct strICaseComp
{
bool operator() (const std::string &lhs, const std::string &rhs) const
{
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(),
rhs.end(),
[](unsigned char c1, unsigned char c2)
{
return ::tolower(c1) < ::tolower(c2);
});
}
};

View File

@@ -3,88 +3,38 @@
#include <sstream>
#include <string>
#include <vector>
#include <stdlib.h>
#include <time.h>
#include <cstdlib>
#include <ctime>
#include <random>
#include "string.h"
#include "map_extra.h"
std::vector<std::string> split(const std::string &s, const std::string &separator)
{
string_size bpos = 0, epos = s.find(separator);
std::vector<std::string> result;
string_size i = 0;
while(i != s.size())
while(bpos < s.size())
{
int flag = 0;
while(i != s.size() && flag == 0)
{
flag = 1;
for(char x : separator)
if(s[i] == x)
{
++i;
flag = 0;
break;
}
}
flag = 0;
string_size j = i;
while(j != s.size() && flag == 0)
{
for(char x : separator)
if(s[j] == x)
{
flag = 1;
break;
}
if(flag == 0)
++j;
}
if(i != j)
{
result.push_back(s.substr(i, j-i));
i = j;
}
if(epos == std::string::npos)
epos = s.size();
result.push_back(s.substr(bpos, epos - bpos));
bpos = epos + separator.size();
epos = s.find(separator, bpos);
}
return result;
}
void split(std::vector<std::string_view> &result, std::string_view s, char separator)
{
string_size i = 0;
while (i != s.size())
string_size bpos = 0, epos = s.find(separator);
while(bpos < s.size())
{
int flag = 0;
while(i != s.size() && flag == 0)
{
flag = 1;
if(s[i] == separator)
{
++i;
flag = 0;
break;
}
}
flag = 0;
string_size j = i;
while(j != s.size() && flag == 0)
{
if(s[j] == separator)
{
flag = 1;
break;
}
++j;
}
if (i != j)
{
result.push_back(s.substr(i, j-i));
i = j;
}
if(epos == std::string_view::npos)
epos = s.size();
result.push_back(s.substr(bpos, epos - bpos));
bpos = epos + 1;
epos = s.find(separator, bpos);
}
}
@@ -141,7 +91,7 @@ std::string toUpper(const std::string &str)
void processEscapeChar(std::string &str)
{
string_size pos = str.find('\\');
while(pos != str.npos)
while(pos != std::string::npos)
{
if(pos == str.size())
break;
@@ -191,7 +141,7 @@ void processEscapeCharReverse(std::string &str)
int parseCommaKeyValue(const std::string &input, const std::string &separator, string_pair_array &result)
{
string_size bpos = 0, epos = input.find(',');
string_size bpos = 0, epos = input.find(separator);
std::string kv;
while(bpos < input.size())
{
@@ -200,9 +150,9 @@ int parseCommaKeyValue(const std::string &input, const std::string &separator, s
else if(epos && input[epos - 1] == '\\')
{
kv += input.substr(bpos, epos - bpos - 1);
kv += ',';
kv += separator;
bpos = epos + 1;
epos = input.find(',', bpos);
epos = input.find(separator, bpos);
continue;
}
kv += input.substr(bpos, epos - bpos);
@@ -213,9 +163,9 @@ int parseCommaKeyValue(const std::string &input, const std::string &separator, s
result.emplace_back(kv.substr(0, eqpos), kv.substr(eqpos + 1));
kv.clear();
bpos = epos + 1;
epos = input.find(',', bpos);
epos = input.find(separator, bpos);
}
if(kv.size())
if(!kv.empty())
{
string_size eqpos = kv.find('=');
if(eqpos == std::string::npos)
@@ -328,12 +278,12 @@ std::string getUrlArg(const std::string &url, const std::string &request)
while(pos)
{
pos = url.rfind(pattern, pos);
if(pos != url.npos)
if(pos != std::string::npos)
{
if(pos == 0 || url[pos - 1] == '&' || url[pos - 1] == '?')
{
pos += pattern.size();
return url.substr(pos, url.find("&", pos) - pos);
return url.substr(pos, url.find('&', pos) - pos);
}
}
else
@@ -410,23 +360,24 @@ bool isStrUTF8(const std::string &data)
std::string randomStr(int len)
{
std::string retData;
srand(time(NULL));
int cnt = 0;
while(cnt < len)
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 61);
for(int i = 0; i < len; i++)
{
switch((rand() % 3))
int r = dis(gen);
if (r < 26)
{
case 1:
retData += ('A' + rand() % 26);
break;
case 2:
retData += ('a' + rand() % 26);
break;
default:
retData += ('0' + rand() % 10);
break;
retData.push_back('a' + r);
}
else if (r < 52)
{
retData.push_back('A' + r - 26);
}
else
{
retData.push_back('0' + r - 52);
}
cnt++;
}
return retData;
}
@@ -451,7 +402,7 @@ int to_int(const std::string &str, int def_value)
std::string join(const string_array &arr, const std::string &delimiter)
{
if(arr.size() == 0)
if(arr.empty())
return "";
if(arr.size() == 1)
return arr[0];