feat: Complete the new routing system's implementation &&

update sing-box to 1.9.3
This commit is contained in:
unknown 2024-06-15 00:08:40 +03:30
parent 215f0a17b0
commit 4142de8535
No known key found for this signature in database
GPG Key ID: C2CA486E4F771093
35 changed files with 1382 additions and 666 deletions

View File

@ -3,12 +3,12 @@
#include "fmt/includes.h"
#include "fmt/Preset.hpp"
#include "main/QJS.hpp"
#include "rpc/gRPC.h"
#include <QApplication>
#include <QFile>
#include <QFileInfo>
#define BOX_UNDERLYING_DNS dataStore->core_box_underlying_dns.isEmpty() ? "underlying://0.0.0.0" : dataStore->core_box_underlying_dns
#define BOX_UNDERLYING_DNS_EXPORT dataStore->core_box_underlying_dns.isEmpty() ? (status->forExport ? "local" : "underlying://0.0.0.0") : dataStore->core_box_underlying_dns
namespace NekoGui {
@ -151,35 +151,10 @@ namespace NekoGui {
return chainTagOut;
}
#define DOMAIN_USER_RULE \
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->proxy_domain)) { \
if (dataStore->routing->dns_routing) status->domainListDNSRemote += line; \
status->domainListRemote += line; \
} \
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->direct_domain)) { \
if (dataStore->routing->dns_routing) status->domainListDNSDirect += line; \
status->domainListDirect += line; \
} \
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->block_domain)) { \
status->domainListBlock += line; \
}
#define IP_USER_RULE \
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->block_ip)) { \
status->ipListBlock += line; \
} \
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->proxy_ip)) { \
status->ipListRemote += line; \
} \
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->direct_ip)) { \
status->ipListDirect += line; \
}
QString BuildChainInternal(int chainId, const QList<std::shared_ptr<ProxyEntity>> &ents,
const std::shared_ptr<BuildConfigStatus> &status) {
QString chainTag = "c-" + Int2String(chainId);
QString chainTagOut;
bool muxApplied = false;
QString pastTag;
int pastExternalStat = 0;
@ -290,7 +265,6 @@ namespace NekoGui {
// Outbound
QJsonObject outbound;
auto stream = GetStreamSettings(ent->bean.get());
if (thisExternalStat > 0) {
auto extR = ent->bean->BuildExternal(ext_mapping_port, ext_socks_port, thisExternalStat);
@ -309,71 +283,9 @@ namespace NekoGui {
outbound["type"] = "socks";
outbound["server"] = "127.0.0.1";
outbound["server_port"] = ext_socks_port;
} else {
const auto coreR = ent->bean->BuildCoreObjSingBox();
if (coreR.outbound.isEmpty()) {
status->result->error = "unsupported outbound";
return {};
}
if (!coreR.error.isEmpty()) { // rejected
status->result->error = coreR.error;
return {};
}
outbound = coreR.outbound;
}
// outbound misc
outbound["tag"] = tagOut;
ent->traffic_data->id = ent->id;
ent->traffic_data->tag = tagOut.toStdString();
status->result->outboundStats += ent->traffic_data;
// mux common
auto needMux = ent->type == "vmess" || ent->type == "trojan" || ent->type == "vless" || ent->type == "shadowsocks";
needMux &= dataStore->mux_concurrency > 0;
if (stream != nullptr) {
if (stream->network == "grpc" || stream->network == "quic" || (stream->network == "http" && stream->security == "tls")) {
needMux = false;
}
}
auto mux_state = ent->bean->mux_state;
if (mux_state == 0) {
if (!dataStore->mux_default_on && !ent->bean->enable_brutal) needMux = false;
} else if (mux_state == 1) {
needMux = true;
} else if (mux_state == 2) {
needMux = false;
}
if (ent->type == "vless" && outbound["flow"] != "") {
needMux = false;
}
// common
// apply domain_strategy
outbound["domain_strategy"] = dataStore->routing->outbound_domain_strategy;
// apply mux
if (!muxApplied && needMux) {
auto muxObj = QJsonObject{
{"enabled", true},
{"protocol", dataStore->mux_protocol},
{"padding", dataStore->mux_padding},
{"max_streams", dataStore->mux_concurrency},
};
if (ent->bean->enable_brutal) {
auto brutalObj = QJsonObject{
{"enabled", true},
{"up_mbps", ent->bean->brutal_speed},
{"down_mbps", ent->bean->brutal_speed},
};
muxObj["max_connections"] = 1;
muxObj["brutal"] = brutalObj;
}
outbound["multiplex"] = muxObj;
muxApplied = true;
}
BuildOutbound(ent, status, outbound, tagOut);
// apply custom outbound settings
MergeJson(QString2QJsonObject(ent->bean->custom_outbound), outbound);
@ -388,7 +300,7 @@ namespace NekoGui {
}
if (!IsIpAddress(serverAddress)) {
status->domainListDNSDirect += "full:" + serverAddress;
status->domainListDNSDirect += serverAddress;
}
status->outbounds += outbound;
@ -400,6 +312,72 @@ namespace NekoGui {
return chainTagOut;
}
void BuildOutbound(const std::shared_ptr<ProxyEntity> &ent, const std::shared_ptr<BuildConfigStatus> &status, QJsonObject& outbound, const QString& tag) {
const auto coreR = ent->bean->BuildCoreObjSingBox();
if (coreR.outbound.isEmpty()) {
status->result->error = "unsupported outbound";
return;
}
if (!coreR.error.isEmpty()) { // rejected
status->result->error = coreR.error;
return;
}
outbound = coreR.outbound;
// outbound misc
outbound["tag"] = tag;
ent->traffic_data->id = ent->id;
ent->traffic_data->tag = tag.toStdString();
status->result->outboundStats += ent->traffic_data;
// mux common
auto needMux = ent->type == "vmess" || ent->type == "trojan" || ent->type == "vless" || ent->type == "shadowsocks";
needMux &= dataStore->mux_concurrency > 0;
auto stream = GetStreamSettings(ent->bean.get());
if (stream != nullptr) {
if (stream->network == "grpc" || stream->network == "quic" || (stream->network == "http" && stream->security == "tls")) {
needMux = false;
}
}
auto mux_state = ent->bean->mux_state;
if (mux_state == 0) {
if (!dataStore->mux_default_on && !ent->bean->enable_brutal) needMux = false;
} else if (mux_state == 1) {
needMux = true;
} else if (mux_state == 2) {
needMux = false;
}
if (ent->type == "vless" && outbound["flow"] != "") {
needMux = false;
}
// common
// apply domain_strategy
outbound["domain_strategy"] = dataStore->routing->outbound_domain_strategy;
// apply mux
if (needMux) {
auto muxObj = QJsonObject{
{"enabled", true},
{"protocol", dataStore->mux_protocol},
{"padding", dataStore->mux_padding},
{"max_streams", dataStore->mux_concurrency},
};
if (ent->bean->enable_brutal) {
auto brutalObj = QJsonObject{
{"enabled", true},
{"up_mbps", ent->bean->brutal_speed},
{"down_mbps", ent->bean->brutal_speed},
};
muxObj["max_connections"] = 1;
muxObj["brutal"] = brutalObj;
}
outbound["multiplex"] = muxObj;
}
}
// SingBox
void BuildConfigSingBox(const std::shared_ptr<BuildConfigStatus> &status) {
@ -467,15 +445,11 @@ namespace NekoGui {
auto tagProxy = BuildChain(0, status);
if (!status->result->error.isEmpty()) return;
// direct & bypass & block
// direct & block & dns-out
status->outbounds += QJsonObject{
{"type", "direct"},
{"tag", "direct"},
};
status->outbounds += QJsonObject{
{"type", "direct"},
{"tag", "bypass"},
};
status->outbounds += QJsonObject{
{"type", "block"},
{"tag", "block"},
@ -493,60 +467,89 @@ namespace NekoGui {
status->result->coreConfig.insert("inbounds", status->inbounds);
status->result->coreConfig.insert("outbounds", status->outbounds);
// Routing
// geopath
auto geoip = FindCoreAsset("geoip.db");
auto geosite = FindCoreAsset("geosite.db");
if (geoip.isEmpty()) status->result->error = +"geoip.db not found";
if (geosite.isEmpty()) status->result->error = +"geosite.db not found";
// sing-box common rule object
auto make_rule = [&](const QStringList &list, bool isIP = false) {
QJsonObject rule;
//
QJsonArray ip_cidr;
QJsonArray geoip;
//
QJsonArray domain_keyword;
QJsonArray domain_subdomain;
QJsonArray domain_regexp;
QJsonArray domain_full;
QJsonArray geosite;
for (auto item: list) {
if (isIP) {
if (item.startsWith("geoip:")) {
geoip += item.replace("geoip:", "");
} else {
ip_cidr += item;
}
} else {
// https://www.v2fly.org/config/dns.html#dnsobject
if (item.startsWith("geosite:")) {
geosite += item.replace("geosite:", "");
} else if (item.startsWith("full:")) {
domain_full += item.replace("full:", "").toLower();
} else if (item.startsWith("domain:")) {
domain_subdomain += item.replace("domain:", "").toLower();
} else if (item.startsWith("regexp:")) {
domain_regexp += item.replace("regexp:", "").toLower();
} else if (item.startsWith("keyword:")) {
domain_keyword += item.replace("keyword:", "").toLower();
} else {
domain_full += item.toLower();
}
}
}
if (isIP) {
if (ip_cidr.isEmpty() && geoip.isEmpty()) return rule;
rule["ip_cidr"] = ip_cidr;
rule["geoip"] = geoip;
} else {
if (domain_keyword.isEmpty() && domain_subdomain.isEmpty() && domain_regexp.isEmpty() && domain_full.isEmpty() && geosite.isEmpty()) {
return rule;
}
rule["domain"] = domain_full;
rule["domain_suffix"] = domain_subdomain; // v2ray Subdomain => sing-box suffix
rule["domain_keyword"] = domain_keyword;
rule["domain_regex"] = domain_regexp;
rule["geosite"] = geosite;
}
return rule;
};
// manage routing section
auto routeObj = QJsonObject{
{"auto_detect_interface", true},
{
"geoip",
QJsonObject{
{"path", geoip},
},
},
{
"geosite",
QJsonObject{
{"path", geosite},
},
}};
if (!status->forTest) routeObj["final"] = dataStore->routing->def_outbound;
if (status->forExport) {
routeObj.remove("geoip");
routeObj.remove("geosite");
}
auto routeChain = NekoGui::profileManager->GetRouteChain(NekoGui::dataStore->routing->current_route_id);
if (routeChain == nullptr) {
MessageBoxWarning("Corrupted Data", "Routing profile does not exist, try resetting the route profile in Routing Settings");
return;
}
auto neededOutbounds = routeChain->get_used_outbounds();
auto neededRuleSets = routeChain->get_used_rule_sets();
std::map<int, QString> outboundMap;
outboundMap[-1] = "proxy";
outboundMap[-2] = "direct";
outboundMap[-3] = "block";
outboundMap[-4] = "dns-out";
int suffix = 0;
for (const auto &item: *neededOutbounds) {
if (item < 0) continue;
auto neededEnt = NekoGui::profileManager->GetProfile(item);
if (neededEnt == nullptr) {
MessageBoxWarning("Invalid Data", "The routing profile is referencing outbounds that no longer exists, consider revising your settings");
return;
}
QJsonObject currOutbound;
QString tag = "rout-" + Int2String(suffix++);
BuildOutbound(neededEnt, status, currOutbound, tag);
status->outbounds += currOutbound;
outboundMap[item] = tag;
// add to dns direct resolve
if (!IsIpAddress(neededEnt->bean->serverAddress)) {
status->domainListDNSDirect << neededEnt->bean->serverAddress;
}
}
routeObj["rules"] = routeChain->get_route_rules(false, outboundMap);
auto ruleSetArray = QJsonArray();
for (const auto &item: *neededRuleSets) {
ruleSetArray += QJsonObject{
{"type", "local"},
{"tag", item},
{"format", "binary"},
{"path", RULE_SETS_DIR + QString("/%1.srs").arg(item)},
};
if (QFile(QString(RULE_SETS_DIR + "/%1.srs").arg(item)).exists()) continue;
bool ok;
auto err = NekoGui_rpc::defaultClient->CompileGeoSet(&ok, item.contains("_IP") ? NekoGui_rpc::GeoRuleSetType::ip : NekoGui_rpc::GeoRuleSetType::site, item.toStdString());
if (!ok) {
MW_show_log("Failed to generate rule set asset for " + item);
status->result->error = err;
return;
}
}
routeObj["rule_set"] = ruleSetArray;
status->result->coreConfig["route"] = routeObj;
// DNS settings
// final add DNS
QJsonObject dns;
QJsonArray dnsServers;
@ -600,6 +603,16 @@ namespace NekoGui {
};
}
// Direct dns domains
QJsonArray directDnsDomains;
for (const auto &item: status->domainListDNSDirect) {
directDnsDomains.append(item);
}
dnsRules += QJsonObject{
{"domain", directDnsDomains},
{"server", "dns-direct"},
};
// Underlying 100% Working DNS
dnsServers += QJsonObject{
{"tag", "dns-local"},
@ -607,28 +620,6 @@ namespace NekoGui {
{"detour", "direct"},
};
// sing-box dns rule object
auto add_rule_dns = [&](const QStringList &list, const QString &server) {
auto rule = make_rule(list, false);
if (rule.isEmpty()) return;
rule["server"] = server;
dnsRules += rule;
};
add_rule_dns(status->domainListDNSRemote, "dns-remote");
add_rule_dns(status->domainListDNSDirect, "dns-direct");
// built-in rules
if (!status->forTest) {
dnsRules += QJsonObject{
{"query_type", QJsonArray{32, 33}},
{"server", "dns-block"},
};
dnsRules += QJsonObject{
{"domain_suffix", ".lan"},
{"server", "dns-block"},
};
}
// fakedns rule
if (dataStore->fake_dns && dataStore->spmode_vpn && !status->forTest) {
dnsRules += QJsonObject{
@ -646,99 +637,6 @@ namespace NekoGui {
}
status->result->coreConfig.insert("dns", dns);
// Routing
// dns hijack
if (!status->forTest) {
status->routingRules += QJsonObject{
{"protocol", "dns"},
{"outbound", "dns-out"},
};
}
// sing-box routing rule object
auto add_rule_route = [&](const QStringList &list, bool isIP, const QString &out) {
auto rule = make_rule(list, isIP);
if (rule.isEmpty()) return;
rule["outbound"] = out;
status->routingRules += rule;
};
// final add user rule
add_rule_route(status->domainListBlock, false, "block");
add_rule_route(status->domainListRemote, false, tagProxy);
add_rule_route(status->domainListDirect, false, "bypass");
add_rule_route(status->ipListBlock, true, "block");
add_rule_route(status->ipListRemote, true, tagProxy);
add_rule_route(status->ipListDirect, true, "bypass");
// built-in rules
status->routingRules += QJsonObject{
{"ip_cidr", QJsonArray{"224.0.0.0/3", "ff00::/8"}},
{"outbound", "block"},
};
status->routingRules += QJsonObject{
{"source_ip_cidr", QJsonArray{"224.0.0.0/3", "ff00::/8"}},
{"outbound", "block"},
};
// tun user rule
if (dataStore->spmode_vpn && !status->forTest) {
auto match_out = dataStore->vpn_rule_white ? "proxy" : "bypass";
QString process_name_rule = dataStore->vpn_rule_process.trimmed();
if (!process_name_rule.isEmpty()) {
auto arr = SplitLinesSkipSharp(process_name_rule);
QJsonObject rule{{"outbound", match_out},
{"process_name", QList2QJsonArray(arr)}};
status->routingRules += rule;
}
QString cidr_rule = dataStore->vpn_rule_cidr.trimmed();
if (!cidr_rule.isEmpty()) {
auto arr = SplitLinesSkipSharp(cidr_rule);
QJsonObject rule{{"outbound", match_out},
{"ip_cidr", QList2QJsonArray(arr)}};
status->routingRules += rule;
}
auto autoBypassExternalProcessPaths = getAutoBypassExternalProcessPaths(status->result);
if (!autoBypassExternalProcessPaths.isEmpty()) {
QJsonObject rule{{"outbound", "bypass"},
{"process_name", QList2QJsonArray(autoBypassExternalProcessPaths)}};
status->routingRules += rule;
}
}
// geopath
auto geoip = FindCoreAsset("geoip.db");
auto geosite = FindCoreAsset("geosite.db");
if (geoip.isEmpty()) status->result->error = +"geoip.db not found";
if (geosite.isEmpty()) status->result->error = +"geosite.db not found";
// final add routing rule
auto routeObj = QJsonObject{
{"auto_detect_interface", true},
{
"geoip",
QJsonObject{
{"path", geoip},
},
},
{
"geosite",
QJsonObject{
{"path", geosite},
},
}};
if (!status->forTest) routeObj["final"] = dataStore->routing->def_outbound;
if (status->forExport) {
routeObj.remove("geoip");
routeObj.remove("geosite");
routeObj.remove("auto_detect_interface");
}
status->result->coreConfig.insert("route", routeObj);
// experimental
QJsonObject experimentalObj;

View File

@ -28,14 +28,7 @@ namespace NekoGui {
// xxList is V2Ray format string list
QStringList domainListDNSRemote;
QStringList domainListDNSDirect;
QStringList domainListRemote;
QStringList domainListDirect;
QStringList ipListRemote;
QStringList ipListDirect;
QStringList domainListBlock;
QStringList ipListBlock;
// config format
@ -52,4 +45,6 @@ namespace NekoGui {
QString BuildChainInternal(int chainId, const QList<std::shared_ptr<ProxyEntity>> &ents,
const std::shared_ptr<BuildConfigStatus> &status);
void BuildOutbound(const std::shared_ptr<ProxyEntity> &ent, const std::shared_ptr<BuildConfigStatus> &status, QJsonObject& outbound, const QString& tag);
} // namespace NekoGui

View File

@ -40,7 +40,7 @@ namespace NekoGui {
routes = {};
profilesIdOrder = filterIntJsonFile("profiles");
groupsIdOrder = filterIntJsonFile("groups");
routesIdOrder = filterIntJsonFile("routes");
routesIdOrder = filterIntJsonFile("route_profiles");
// Load Proxys
QList<int> delProfile;
for (auto id: profilesIdOrder) {
@ -79,9 +79,9 @@ namespace NekoGui {
}
// Load Routing profiles
for (auto id : routesIdOrder) {
auto route = LoadRouteChain(QString("routes/%1.json").arg(id));
auto route = LoadRouteChain(QString("route_profiles/%1.json").arg(id));
if (route == nullptr) {
MW_show_log(QString("File routes/%1.json is corrupted, consider manually handling it").arg(id));
MW_show_log(QString("File route_profiles/%1.json is corrupted, consider manually handling it").arg(id));
continue;
}
@ -183,7 +183,7 @@ namespace NekoGui {
}
std::shared_ptr<RoutingChain> ProfileManager::LoadRouteChain(const QString &jsonPath) {
std::shared_ptr<RoutingChain> routingChain;
auto routingChain = std::make_shared<RoutingChain>();
routingChain->fn = jsonPath;
if (!routingChain->Load()) {
return nullptr;
@ -402,6 +402,34 @@ namespace NekoGui {
return GetGroup(dataStore->current_group);
}
RouteRule::RouteRule() {
_add(new configItem("name", &name, itemType::string));
_add(new configItem("ip_version", &ip_version, itemType::string));
_add(new configItem("protocol", &protocol, itemType::string));
_add(new configItem("domain", &domain, itemType::stringList));
_add(new configItem("domain_suffix", &domain_suffix, itemType::stringList));
_add(new configItem("domain_keyword", &domain_keyword, itemType::stringList));
_add(new configItem("domain_regex", &domain_regex, itemType::stringList));
_add(new configItem("source_ip_cidr", &source_ip_cidr, itemType::stringList));
_add(new configItem("source_ip_is_private", &source_ip_is_private, itemType::boolean));
_add(new configItem("ip_cidr", &ip_cidr, itemType::stringList));
_add(new configItem("ip_is_private", &ip_is_private, itemType::boolean));
_add(new configItem("source_port", &source_port, itemType::stringList));
_add(new configItem("source_port_range", &source_port_range, itemType::stringList));
_add(new configItem("port", &port, itemType::stringList));
_add(new configItem("port_range", &port_range, itemType::stringList));
_add(new configItem("process_name", &process_name, itemType::stringList));
_add(new configItem("process_path", &process_path, itemType::stringList));
_add(new configItem("rule_set", &rule_set, itemType::stringList));
_add(new configItem("invert", &invert, itemType::boolean));
_add(new configItem("outboundID", &outboundID, itemType::integer));
}
RoutingChain::RoutingChain() {
_add(new configItem("id", &id, itemType::integer));
_add(new configItem("name", &name, itemType::string));
_add(new configItem("rules", &castedRules, itemType::jsonStoreList));
}
std::shared_ptr<RoutingChain> ProfileManager::NewRouteChain() {
auto route = std::make_shared<RoutingChain>();
@ -415,15 +443,15 @@ namespace NekoGui {
return routesIdOrder.last() + 1;
}
bool ProfileManager::AddRouteChain(const std::shared_ptr<RoutingChain> chain) {
bool ProfileManager::AddRouteChain(const std::shared_ptr<RoutingChain>& chain) {
if (chain->id >= 0) {
return false;
}
chain->id = NewRouteChainID();
chain->fn = QString("route_profiles/%1.json").arg(chain->id);
routes[chain->id] = chain;
routesIdOrder.push_back(chain->id);
chain->fn = QString("routes/%1.json").arg(chain->id);
chain->Save();
return true;
@ -433,6 +461,25 @@ namespace NekoGui {
return routes.count(id) > 0 ? routes[id] : nullptr;
}
void ProfileManager::UpdateRouteChains(const QList<std::shared_ptr<RoutingChain>>& newChain) {
routes.clear();
routesIdOrder.clear();
for (const auto &item: newChain) {
if (!AddRouteChain(item)) {
routes[item->id] = item;
item->Save();
}
routesIdOrder << item->id;
}
auto currFiles = filterIntJsonFile("route_profiles");
for (const auto &item: currFiles) { // clean up removed route profiles
if (!routes.count(item)) {
QFile(QString(ROUTES_PREFIX+"%1.json").arg(item)).remove();
}
}
}
QList<std::shared_ptr<ProxyEntity>> Group::Profiles() const {
QList<std::shared_ptr<ProxyEntity>> ret;
for (const auto &[_, profile]: profileManager->profiles) {

View File

@ -48,10 +48,12 @@ namespace NekoGui {
std::shared_ptr<Group> CurrentGroup();
bool AddRouteChain(std::shared_ptr<RoutingChain> chain);
bool AddRouteChain(const std::shared_ptr<RoutingChain>& chain);
std::shared_ptr<RoutingChain> GetRouteChain(int id);
void UpdateRouteChains(const QList<std::shared_ptr<RoutingChain>>& newChain);
private:
// sort by id
QList<int> profilesIdOrder;

View File

@ -7,43 +7,57 @@ namespace NekoGui {
QJsonArray get_as_array(const QList<QString>& str, bool castToNum = false) {
QJsonArray res;
for (const auto &item: str) {
if (item.trimmed().isEmpty()) continue;
if (castToNum) res.append(item.toInt());
else res.append(item);
}
return res;
}
QJsonObject RouteRule::get_rule_json(bool forView) const {
bool isValidStrArray(const QStringList& arr) {
for (const auto& item: arr) {
if (!item.trimmed().isEmpty()) return true;
}
return false;
}
QJsonObject RouteRule::get_rule_json(bool forView, const QString& outboundTag) const {
QJsonObject obj;
if (ip_version != "") obj["ip_version"] = ip_version.toInt();
if (network != "") obj["network"] = network;
if (protocol != "") obj["protocol"] = protocol;
if (!domain.empty()) obj["domain"] = get_as_array(domain);
if (!domain_suffix.empty()) obj["domain_suffix"] = get_as_array(domain_suffix);
if (!domain_keyword.empty()) obj["domain_keyword"] = get_as_array(domain_keyword);
if (!domain_regex.empty()) obj["domain_regex"] = get_as_array(domain_regex);
if (!source_ip_cidr.empty()) obj["source_ip_cidr"] = get_as_array(source_ip_cidr);
if (source_ip_is_private != nullptr) obj["source_ip_is_private"] = *source_ip_is_private;
if (!ip_cidr.empty()) obj["ip_cidr"] = get_as_array(ip_cidr);
if (ip_is_private != nullptr) obj["ip_is_private"] = *ip_is_private;
if (!source_port.empty()) obj["source_port"] = get_as_array(source_port, true);
if (!source_port_range.empty()) obj["source_port_range"] = get_as_array(source_port_range);
if (!port.empty()) obj["port"] = get_as_array(port, true);
if (!port_range.empty()) obj["port_range"] = get_as_array(port_range);
if (!process_name.empty()) obj["process_name"] = get_as_array(process_name);
if (!process_path.empty()) obj["process_path"] = get_as_array(process_path);
if (!rule_set.empty()) obj["rule_set"] = get_as_array(rule_set);
if (isValidStrArray(domain)) obj["domain"] = get_as_array(domain);
if (isValidStrArray(domain_suffix)) obj["domain_suffix"] = get_as_array(domain_suffix);
if (isValidStrArray(domain_keyword)) obj["domain_keyword"] = get_as_array(domain_keyword);
if (isValidStrArray(domain_regex)) obj["domain_regex"] = get_as_array(domain_regex);
if (isValidStrArray(source_ip_cidr)) obj["source_ip_cidr"] = get_as_array(source_ip_cidr);
if (source_ip_is_private) obj["source_ip_is_private"] = source_ip_is_private;
if (isValidStrArray(ip_cidr)) obj["ip_cidr"] = get_as_array(ip_cidr);
if (ip_is_private) obj["ip_is_private"] = ip_is_private;
if (isValidStrArray(source_port)) obj["source_port"] = get_as_array(source_port, true);
if (isValidStrArray(source_port_range)) obj["source_port_range"] = get_as_array(source_port_range);
if (isValidStrArray(port)) obj["port"] = get_as_array(port, true);
if (isValidStrArray(port_range)) obj["port_range"] = get_as_array(port_range);
if (isValidStrArray(process_name)) obj["process_name"] = get_as_array(process_name);
if (isValidStrArray(process_path)) obj["process_path"] = get_as_array(process_path);
if (isValidStrArray(rule_set)) obj["rule_set"] = get_as_array(rule_set);
if (invert) obj["invert"] = invert;
if (forView) {
switch (outboundID) { // TODO use constants
case -1:
obj["outbound"] = "proxy";
break;
case -2:
obj["outbound"] = "direct";
break;
case -3:
obj["outbound"] = "block";
break;
case -4:
obj["outbound"] = "dns_out";
break;
default:
auto prof = NekoGui::profileManager->GetProfile(outboundID);
if (prof == nullptr) {
@ -53,13 +67,13 @@ namespace NekoGui {
obj["outbound"] = prof->bean->DisplayName();
}
} else {
obj["outbound"] = outboundID;
if (!outboundTag.isEmpty()) obj["outbound"] = outboundTag;
else obj["outbound"] = outboundID;
}
return obj;
}
// TODO use constant for field names
QStringList RouteRule::get_attributes() {
return {
@ -111,13 +125,13 @@ namespace NekoGui {
}
QStringList RouteRule::get_current_value_string(const QString& fieldName) {
if (fieldName == "ip_version" && ip_version != "") {
if (fieldName == "ip_version") {
return {ip_version};
}
if (fieldName == "network" && network != "") {
if (fieldName == "network") {
return {network};
}
if (fieldName == "protocol" && protocol != "") {
if (fieldName == "protocol") {
return {protocol};
}
if (fieldName == "domain") return domain;
@ -136,41 +150,20 @@ namespace NekoGui {
return {};
}
bool* RouteRule::get_current_value_bool(const QString& fieldName) const {
QString RouteRule::get_current_value_bool(const QString& fieldName) const {
if (fieldName == "source_ip_is_private") {
return source_ip_is_private;
return source_ip_is_private? "true":"false";
}
if (fieldName == "ip_is_private") {
return ip_is_private;
return ip_is_private? "true":"false";
}
if (fieldName == "invert") {
return reinterpret_cast<bool*>(invert);
return invert? "true":"false";
}
return nullptr;
}
void RouteRule::set_field_value(const QString& fieldName, const QStringList& value) {
/*
* "ip_version",
"network",
"protocol",
"domain",
"domain_suffix",
"domain_keyword",
"domain_regex",
"source_ip_cidr",
"source_ip_is_private",
"ip_cidr",
"ip_is_private",
"source_port",
"source_port_range",
"port",
"port_range",
"process_name",
"process_path",
"rule_set",
"invert",
*/
if (fieldName == "ip_version") {
ip_version = value[0];
}
@ -196,13 +189,13 @@ namespace NekoGui {
source_ip_cidr = value;
}
if (fieldName == "source_ip_is_private") {
source_ip_is_private = reinterpret_cast<bool*>((value[0] == "true"));
source_ip_is_private = value[0]=="true";
}
if (fieldName == "ip_cidr") {
ip_cidr = value;
}
if (fieldName == "ip_is_private") {
ip_is_private = reinterpret_cast<bool*>((value[0] == "true"));
ip_is_private = value[0]=="true";
}
if (fieldName == "source_port") {
source_port = value;
@ -230,10 +223,16 @@ namespace NekoGui {
}
}
QJsonArray RoutingChain::get_route_rules(bool forView) {
bool RouteRule::isEmpty() const {
return get_rule_json().keys().length() == 1;
}
QJsonArray RoutingChain::get_route_rules(bool forView, std::map<int, QString> outboundMap) {
QJsonArray res;
for (const auto &item: Rules) {
auto rule_json = item->get_rule_json(forView);
auto outboundTag = QString();
if (outboundMap.count(item->outboundID)) outboundTag = outboundMap[item->outboundID];
auto rule_json = item->get_rule_json(forView, outboundTag);
if (rule_json.empty()) {
MW_show_log("Aborted generating routing section, an error has occurred");
return {};
@ -245,12 +244,63 @@ namespace NekoGui {
}
std::shared_ptr<RoutingChain> RoutingChain::GetDefaultChain() {
auto defaultChain = RoutingChain();
defaultChain.name = "Default";
auto defaultRule = RouteRule();
defaultRule.protocol = {"dns"};
defaultRule.outboundID = -4;
defaultChain.Rules << std::make_shared<RouteRule>(defaultRule);
return std::make_shared<RoutingChain>(defaultChain);
auto defaultChain = std::make_shared<RoutingChain>();
defaultChain->name = "Default";
auto defaultRule = std::make_shared<RouteRule>();
defaultRule->protocol = "dns";
defaultRule->outboundID = -4;
defaultChain->Rules << defaultRule;
return defaultChain;
}
std::shared_ptr<QList<int>> RoutingChain::get_used_outbounds() {
auto res = std::make_shared<QList<int>>();
for (const auto& item: Rules) {
res->push_back(item->outboundID);
}
return res;
}
std::shared_ptr<QStringList> RoutingChain::get_used_rule_sets() {
auto res = std::make_shared<QStringList>();
for (const auto& item: Rules) {
for (const auto& ruleItem: item->rule_set) {
res->push_back(ruleItem);
}
}
return res;
}
bool RoutingChain::Save() {
castedRules.clear();
for (const auto &item: Rules) {
castedRules.push_back(dynamic_cast<JsonStore*>(item.get()));
}
return JsonStore::Save();
}
void RoutingChain::FromJson(QJsonObject object) {
for (const auto &key: object.keys()) {
if (_map.count(key) == 0) {
continue;
}
auto value = object[key];
auto item = _map[key].get();
if (item == nullptr) continue;
if (item->type == itemType::jsonStoreList) {
// it is of rule type
if (!value.isArray()) continue;
Rules.clear();
auto arr = value.toArray();
for (auto obj : arr) {
auto rule = std::make_shared<RouteRule>();
rule->FromJson(obj.toObject());
Rules << rule;
}
}
}
JsonStore::FromJson(object);
}
}

View File

@ -7,6 +7,8 @@ namespace NekoGui {
class RouteRule : public JsonStore {
public:
RouteRule();
QString name = "";
QString ip_version;
QString network;
@ -16,9 +18,9 @@ namespace NekoGui {
QList<QString> domain_keyword;
QList<QString> domain_regex;
QList<QString> source_ip_cidr;
bool* source_ip_is_private = nullptr;
bool source_ip_is_private = false;
QList<QString> ip_cidr;
bool* ip_is_private = nullptr;
bool ip_is_private = false;
QList<QString> source_port;
QList<QString> source_port_range;
QList<QString> port;
@ -27,15 +29,16 @@ namespace NekoGui {
QList<QString> process_path;
QList<QString> rule_set;
bool invert = false;
int outboundID = -1; // -1 is invalid -2 is direct -3 is block -4 is dns_out
int outboundID = -2; // -1 is proxy -2 is direct -3 is block -4 is dns_out
[[nodiscard]] QJsonObject get_rule_json(bool forView = false) const;
[[nodiscard]] QJsonObject get_rule_json(bool forView = false, const QString& outboundTag = "") const;
static QStringList get_attributes();
static inputType get_input_type(const QString& fieldName);
static QStringList get_values_for_field(const QString& fieldName);
QStringList get_current_value_string(const QString& fieldName);
[[nodiscard]] bool* get_current_value_bool(const QString& fieldName) const;
[[nodiscard]] QString get_current_value_bool(const QString& fieldName) const;
void set_field_value(const QString& fieldName, const QStringList& value);
[[nodiscard]] bool isEmpty() const;
};
class RoutingChain : public JsonStore {
@ -43,9 +46,20 @@ namespace NekoGui {
int id = -1;
QString name = "";
QList<std::shared_ptr<RouteRule>> Rules;
QList<JsonStore*> castedRules;
QJsonArray get_route_rules(bool forView = false);
RoutingChain();
bool Save() override;
void FromJson(QJsonObject object);
QJsonArray get_route_rules(bool forView = false, std::map<int, QString> outboundMap = {});
static std::shared_ptr<RoutingChain> GetDefaultChain();
std::shared_ptr<QList<int>> get_used_outbounds();
std::shared_ptr<QStringList> get_used_rule_sets();
};
} // namespace NekoGui

View File

@ -25,7 +25,7 @@ namespace NekoGui_fmt {
}
} else if (network == "http") {
if (!path.isEmpty()) transport["path"] = path;
if (!host.isEmpty()) transport["host"] = QList2QJsonArray(host.split(","));
if (!host.isEmpty()) transport["host"] = QListStr2QJsonArray(host.split(","));
} else if (network == "grpc") {
if (!path.isEmpty()) transport["service_name"] = path;
} else if (network == "httpupgrade") {
@ -39,7 +39,7 @@ namespace NekoGui_fmt {
{"type", "http"},
{"method", "GET"},
{"path", path},
{"headers", QJsonObject{{"Host", QList2QJsonArray(host.split(","))}}},
{"headers", QJsonObject{{"Host", QListStr2QJsonArray(host.split(","))}}},
};
outbound->insert("transport", transport);
}
@ -53,7 +53,7 @@ namespace NekoGui_fmt {
tls["certificate"] = certificate.trimmed();
}
if (!alpn.trimmed().isEmpty()) {
tls["alpn"] = QList2QJsonArray(alpn.split(","));
tls["alpn"] = QListStr2QJsonArray(alpn.split(","));
}
QString fp = utlsFingerprint;
if (!reality_pbk.trimmed().isEmpty()) {
@ -182,7 +182,7 @@ namespace NekoGui_fmt {
{"certificate", caText.trimmed()},
{"server_name", sni},
};
if (!alpn.trimmed().isEmpty()) coreTlsObj["alpn"] = QList2QJsonArray(alpn.split(","));
if (!alpn.trimmed().isEmpty()) coreTlsObj["alpn"] = QListStr2QJsonArray(alpn.split(","));
if (proxy_type == proxy_Hysteria2) coreTlsObj["alpn"] = "h3";
QJsonObject outbound{

View File

@ -141,7 +141,7 @@ namespace NekoGui_fmt {
relay["zero_rtt_handshake"] = zeroRttHandshake;
relay["disable_sni"] = disableSni;
if (!heartbeat.trimmed().isEmpty()) relay["heartbeat"] = heartbeat;
if (!alpn.trimmed().isEmpty()) relay["alpn"] = QList2QJsonArray(alpn.split(","));
if (!alpn.trimmed().isEmpty()) relay["alpn"] = QListStr2QJsonArray(alpn.split(","));
if (!caText.trimmed().isEmpty()) {
WriteTempFile("tuic_" + GetRandomString(10) + ".crt", caText.toUtf8());

View File

@ -3,13 +3,13 @@ module nekobox_core
go 1.19
require (
github.com/Mahdi-zarei/sing-box-extra v0.0.0-20240524210208-2f5446580565
github.com/Mahdi-zarei/sing-box-extra v0.0.0-20240614112142-907408da0959
github.com/matsuridayo/libneko v0.0.0-20230913024055-5277a5bfc889
github.com/sagernet/sing-box v1.9.0-rc.21
github.com/sagernet/sing-box v1.9.3
grpc_server v1.0.0
)
replace github.com/sagernet/sing-box => github.com/Mahdi-zarei/sing-box v1.3.5-0.20240524203629-e284713b657d
replace github.com/sagernet/sing-box => github.com/Mahdi-zarei/sing-box v1.3.5-0.20240613091205-668453f192a9
require (
berty.tech/go-libtor v1.0.385 // indirect
@ -55,16 +55,16 @@ require (
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f // indirect
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba // indirect
github.com/sagernet/quic-go v0.43.1-beta.1 // indirect
github.com/sagernet/quic-go v0.43.1-beta.2 // indirect
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
github.com/sagernet/sing v0.4.0-beta.20 // indirect
github.com/sagernet/sing-dns v0.2.0-beta.18 // indirect
github.com/sagernet/sing v0.4.1 // indirect
github.com/sagernet/sing-dns v0.2.0 // indirect
github.com/sagernet/sing-mux v0.2.0 // indirect
github.com/sagernet/sing-quic v0.2.0-beta.5 // indirect
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
github.com/sagernet/sing-shadowsocks2 v0.2.0 // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect
github.com/sagernet/sing-tun v0.3.0-beta.6 // indirect
github.com/sagernet/sing-tun v0.3.2 // indirect
github.com/sagernet/sing-vmess v0.1.8 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 // indirect
@ -84,7 +84,7 @@ require (
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.0 // indirect

View File

@ -4,10 +4,10 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Mahdi-zarei/sing-box v1.3.5-0.20240524203629-e284713b657d h1:l+/67xmhHesa9urn+sdBfiwuvpt+r4Wj7hwrFnWSq+A=
github.com/Mahdi-zarei/sing-box v1.3.5-0.20240524203629-e284713b657d/go.mod h1:7OFR+knnBYl9AeUpGT+EGFkhzM8KPXSZbbMi/vFoCDY=
github.com/Mahdi-zarei/sing-box-extra v0.0.0-20240524210208-2f5446580565 h1:GrE6+ZvQWjmbDtcOQh3avtXKTynt72iLUytMWNWZUt0=
github.com/Mahdi-zarei/sing-box-extra v0.0.0-20240524210208-2f5446580565/go.mod h1:H3YA/kY0yKLmSP359MpY7oms4dQ9YW4s3dcobiarQS8=
github.com/Mahdi-zarei/sing-box v1.3.5-0.20240613091205-668453f192a9 h1:sB4V56lOFjDwefldsXLF0LldVFIr3K6eXXVTqb/PjQc=
github.com/Mahdi-zarei/sing-box v1.3.5-0.20240613091205-668453f192a9/go.mod h1:6Rx5nzbqIfN7HlUaHgO/IdkP7fDPPQ/U/TAC5asEjSM=
github.com/Mahdi-zarei/sing-box-extra v0.0.0-20240614112142-907408da0959 h1:1UClVl6qz9Su3gFUcY9ot21yXcvn4yPjbDfBMB1Bd+Q=
github.com/Mahdi-zarei/sing-box-extra v0.0.0-20240614112142-907408da0959/go.mod h1:umyw9HvgSA5opzUDaTL2w9fzY+6zIhmMY0pTiZfY7Lg=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
@ -134,15 +134,15 @@ github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0=
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk=
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/quic-go v0.43.1-beta.1 h1:alizUjpvWYcz08dBCQsULOd+1xu0o7UtlyYf6SLbRNg=
github.com/sagernet/quic-go v0.43.1-beta.1/go.mod h1:BkrQYeop7Jx3hN3TW8/76CXcdhYiNPyYEBL/BVJ1ifc=
github.com/sagernet/quic-go v0.43.1-beta.2 h1:6YRCE9t1Q3UbNX1/dJGqpwFQbh6DXC6XBrQr2xp6hXY=
github.com/sagernet/quic-go v0.43.1-beta.2/go.mod h1:BkrQYeop7Jx3hN3TW8/76CXcdhYiNPyYEBL/BVJ1ifc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.4.0-beta.20 h1:8rEepj4LMcR0Wd389fJIziv/jr3MBtX5qXBHsfxJ+dY=
github.com/sagernet/sing v0.4.0-beta.20/go.mod h1:PFQKbElc2Pke7faBLv8oEba5ehtKO21Ho+TkYemTI3Y=
github.com/sagernet/sing-dns v0.2.0-beta.18 h1:6vzXZThRdA7YUzBOpSbUT48XRumtl/KIpIHFSOP0za8=
github.com/sagernet/sing-dns v0.2.0-beta.18/go.mod h1:k/dmFcQpg6+m08gC1yQBy+13+QkuLqpKr4bIreq4U24=
github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk=
github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
github.com/sagernet/sing-dns v0.2.0 h1:dka3weRX6+CrYO3v+hrTy2z68rCOCZXNBiNXpLZ6JNs=
github.com/sagernet/sing-dns v0.2.0/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg=
github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=
github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
github.com/sagernet/sing-quic v0.2.0-beta.5 h1:ceKFLd1iS5AtM+pScKmcDp5k7R6WgYIe8vl6nB0aVsE=
@ -153,8 +153,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/sing-tun v0.3.0-beta.6 h1:L11kMrM7UfUW0pzQiU66Fffh4o86KZc1SFGbkYi8Ma8=
github.com/sagernet/sing-tun v0.3.0-beta.6/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ=
github.com/sagernet/sing-tun v0.3.2 h1:z0bLUT/YXH9RrJS9DsIpB0Bb9afl2hVJOmHd0zA3HJY=
github.com/sagernet/sing-tun v0.3.2/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ=
github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
@ -257,8 +257,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"
"grpc_server"
@ -150,3 +151,53 @@ func (s *server) ListConnections(ctx context.Context, in *gen.EmptyReq) (*gen.Li
}
return out, nil
}
func (s *server) GetGeoIPList(ctx context.Context, in *gen.EmptyReq) (*gen.GetGeoIPListResponse, error) {
resp, err := boxmain.ListGeoip()
if err != nil {
return nil, err
}
res := make([]string, 0)
for _, r := range resp {
r += "_IP"
res = append(res, r)
}
return &gen.GetGeoIPListResponse{Items: res}, nil
}
func (s *server) GetGeoSiteList(ctx context.Context, in *gen.EmptyReq) (*gen.GetGeoSiteListResponse, error) {
resp, err := boxmain.GeositeList()
if err != nil {
return nil, err
}
res := make([]string, 0)
for _, r := range resp {
r += "_SITE"
res = append(res, r)
}
return &gen.GetGeoSiteListResponse{Items: res}, nil
}
func (s *server) CompileGeoIPToSrs(ctx context.Context, in *gen.CompileGeoIPToSrsRequest) (*gen.EmptyResp, error) {
category := strings.TrimSuffix(in.Item, "_IP")
err := boxmain.CompileRuleSet(category, boxmain.IpRuleSet, "./rule_sets/"+in.Item+".srs")
if err != nil {
return nil, err
}
return &gen.EmptyResp{}, nil
}
func (s *server) CompileGeoSiteToSrs(ctx context.Context, in *gen.CompileGeoSiteToSrsRequest) (*gen.EmptyResp, error) {
category := strings.TrimSuffix(in.Item, "_SITE")
err := boxmain.CompileRuleSet(category, boxmain.SiteRuleSet, "./rule_sets/"+in.Item+".srs")
if err != nil {
return nil, err
}
return &gen.EmptyResp{}, nil
}

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.31.0
// protoc v5.26.1
// source: libcore.proto
@ -819,6 +819,194 @@ func (x *ListConnectionsResp) GetNekorayConnectionsJson() string {
return ""
}
type GetGeoIPListResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Items []string `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"`
}
func (x *GetGeoIPListResponse) Reset() {
*x = GetGeoIPListResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_libcore_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetGeoIPListResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetGeoIPListResponse) ProtoMessage() {}
func (x *GetGeoIPListResponse) ProtoReflect() protoreflect.Message {
mi := &file_libcore_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetGeoIPListResponse.ProtoReflect.Descriptor instead.
func (*GetGeoIPListResponse) Descriptor() ([]byte, []int) {
return file_libcore_proto_rawDescGZIP(), []int{11}
}
func (x *GetGeoIPListResponse) GetItems() []string {
if x != nil {
return x.Items
}
return nil
}
type GetGeoSiteListResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Items []string `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"`
}
func (x *GetGeoSiteListResponse) Reset() {
*x = GetGeoSiteListResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_libcore_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetGeoSiteListResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetGeoSiteListResponse) ProtoMessage() {}
func (x *GetGeoSiteListResponse) ProtoReflect() protoreflect.Message {
mi := &file_libcore_proto_msgTypes[12]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetGeoSiteListResponse.ProtoReflect.Descriptor instead.
func (*GetGeoSiteListResponse) Descriptor() ([]byte, []int) {
return file_libcore_proto_rawDescGZIP(), []int{12}
}
func (x *GetGeoSiteListResponse) GetItems() []string {
if x != nil {
return x.Items
}
return nil
}
type CompileGeoIPToSrsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Item string `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"`
}
func (x *CompileGeoIPToSrsRequest) Reset() {
*x = CompileGeoIPToSrsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_libcore_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CompileGeoIPToSrsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CompileGeoIPToSrsRequest) ProtoMessage() {}
func (x *CompileGeoIPToSrsRequest) ProtoReflect() protoreflect.Message {
mi := &file_libcore_proto_msgTypes[13]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CompileGeoIPToSrsRequest.ProtoReflect.Descriptor instead.
func (*CompileGeoIPToSrsRequest) Descriptor() ([]byte, []int) {
return file_libcore_proto_rawDescGZIP(), []int{13}
}
func (x *CompileGeoIPToSrsRequest) GetItem() string {
if x != nil {
return x.Item
}
return ""
}
type CompileGeoSiteToSrsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Item string `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"`
}
func (x *CompileGeoSiteToSrsRequest) Reset() {
*x = CompileGeoSiteToSrsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_libcore_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CompileGeoSiteToSrsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CompileGeoSiteToSrsRequest) ProtoMessage() {}
func (x *CompileGeoSiteToSrsRequest) ProtoReflect() protoreflect.Message {
mi := &file_libcore_proto_msgTypes[14]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CompileGeoSiteToSrsRequest.ProtoReflect.Descriptor instead.
func (*CompileGeoSiteToSrsRequest) Descriptor() ([]byte, []int) {
return file_libcore_proto_rawDescGZIP(), []int{14}
}
func (x *CompileGeoSiteToSrsRequest) GetItem() string {
if x != nil {
return x.Item
}
return ""
}
var File_libcore_proto protoreflect.FileDescriptor
var file_libcore_proto_rawDesc = []byte{
@ -905,13 +1093,25 @@ var file_libcore_proto_rawDesc = []byte{
0x12, 0x38, 0x0a, 0x18, 0x6e, 0x65, 0x6b, 0x6f, 0x72, 0x61, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x6e,
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x16, 0x6e, 0x65, 0x6b, 0x6f, 0x72, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x73, 0x6f, 0x6e, 0x2a, 0x32, 0x0a, 0x08, 0x54, 0x65,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x73, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x14, 0x47, 0x65,
0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
0x09, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x2e, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x47,
0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
0x09, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x2e, 0x0a, 0x18, 0x43, 0x6f, 0x6d, 0x70,
0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x30, 0x0a, 0x1a, 0x43, 0x6f, 0x6d, 0x70,
0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x2a, 0x32, 0x0a, 0x08, 0x54, 0x65,
0x73, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x63, 0x70, 0x50, 0x69, 0x6e,
0x67, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x72, 0x6c, 0x54, 0x65, 0x73, 0x74, 0x10, 0x01,
0x12, 0x0c, 0x0a, 0x08, 0x46, 0x75, 0x6c, 0x6c, 0x54, 0x65, 0x73, 0x74, 0x10, 0x02, 0x2a, 0x27,
0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09,
0x0a, 0x05, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x6f, 0x77,
0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x10, 0x01, 0x32, 0x94, 0x03, 0x0a, 0x0e, 0x4c, 0x69, 0x62, 0x63,
0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x10, 0x01, 0x32, 0xb8, 0x05, 0x0a, 0x0e, 0x4c, 0x69, 0x62, 0x63,
0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x45, 0x78,
0x69, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70,
0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e,
@ -936,9 +1136,27 @@ var file_libcore_proto_rawDesc = []byte{
0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x11, 0x2e, 0x6c, 0x69,
0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1c,
0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e,
0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x11,
0x5a, 0x0f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65,
0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x40,
0x0a, 0x0c, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x11,
0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65,
0x71, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x47,
0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x44, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69,
0x73, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70,
0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1f, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e,
0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x11, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c,
0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12, 0x21, 0x2e, 0x6c, 0x69,
0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f,
0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12,
0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65,
0x73, 0x70, 0x12, 0x4e, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f,
0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12, 0x23, 0x2e, 0x6c, 0x69, 0x62, 0x63,
0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69,
0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12,
0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65,
0x73, 0x70, 0x42, 0x11, 0x5a, 0x0f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65,
0x72, 0x2f, 0x67, 0x65, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -954,21 +1172,25 @@ func file_libcore_proto_rawDescGZIP() []byte {
}
var file_libcore_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_libcore_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_libcore_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
var file_libcore_proto_goTypes = []interface{}{
(TestMode)(0), // 0: libcore.TestMode
(UpdateAction)(0), // 1: libcore.UpdateAction
(*EmptyReq)(nil), // 2: libcore.EmptyReq
(*EmptyResp)(nil), // 3: libcore.EmptyResp
(*ErrorResp)(nil), // 4: libcore.ErrorResp
(*LoadConfigReq)(nil), // 5: libcore.LoadConfigReq
(*TestReq)(nil), // 6: libcore.TestReq
(*TestResp)(nil), // 7: libcore.TestResp
(*QueryStatsReq)(nil), // 8: libcore.QueryStatsReq
(*QueryStatsResp)(nil), // 9: libcore.QueryStatsResp
(*UpdateReq)(nil), // 10: libcore.UpdateReq
(*UpdateResp)(nil), // 11: libcore.UpdateResp
(*ListConnectionsResp)(nil), // 12: libcore.ListConnectionsResp
(TestMode)(0), // 0: libcore.TestMode
(UpdateAction)(0), // 1: libcore.UpdateAction
(*EmptyReq)(nil), // 2: libcore.EmptyReq
(*EmptyResp)(nil), // 3: libcore.EmptyResp
(*ErrorResp)(nil), // 4: libcore.ErrorResp
(*LoadConfigReq)(nil), // 5: libcore.LoadConfigReq
(*TestReq)(nil), // 6: libcore.TestReq
(*TestResp)(nil), // 7: libcore.TestResp
(*QueryStatsReq)(nil), // 8: libcore.QueryStatsReq
(*QueryStatsResp)(nil), // 9: libcore.QueryStatsResp
(*UpdateReq)(nil), // 10: libcore.UpdateReq
(*UpdateResp)(nil), // 11: libcore.UpdateResp
(*ListConnectionsResp)(nil), // 12: libcore.ListConnectionsResp
(*GetGeoIPListResponse)(nil), // 13: libcore.GetGeoIPListResponse
(*GetGeoSiteListResponse)(nil), // 14: libcore.GetGeoSiteListResponse
(*CompileGeoIPToSrsRequest)(nil), // 15: libcore.CompileGeoIPToSrsRequest
(*CompileGeoSiteToSrsRequest)(nil), // 16: libcore.CompileGeoSiteToSrsRequest
}
var file_libcore_proto_depIdxs = []int32{
0, // 0: libcore.TestReq.mode:type_name -> libcore.TestMode
@ -981,15 +1203,23 @@ var file_libcore_proto_depIdxs = []int32{
6, // 7: libcore.LibcoreService.Test:input_type -> libcore.TestReq
8, // 8: libcore.LibcoreService.QueryStats:input_type -> libcore.QueryStatsReq
2, // 9: libcore.LibcoreService.ListConnections:input_type -> libcore.EmptyReq
3, // 10: libcore.LibcoreService.Exit:output_type -> libcore.EmptyResp
11, // 11: libcore.LibcoreService.Update:output_type -> libcore.UpdateResp
4, // 12: libcore.LibcoreService.Start:output_type -> libcore.ErrorResp
4, // 13: libcore.LibcoreService.Stop:output_type -> libcore.ErrorResp
7, // 14: libcore.LibcoreService.Test:output_type -> libcore.TestResp
9, // 15: libcore.LibcoreService.QueryStats:output_type -> libcore.QueryStatsResp
12, // 16: libcore.LibcoreService.ListConnections:output_type -> libcore.ListConnectionsResp
10, // [10:17] is the sub-list for method output_type
3, // [3:10] is the sub-list for method input_type
2, // 10: libcore.LibcoreService.GetGeoIPList:input_type -> libcore.EmptyReq
2, // 11: libcore.LibcoreService.GetGeoSiteList:input_type -> libcore.EmptyReq
15, // 12: libcore.LibcoreService.CompileGeoIPToSrs:input_type -> libcore.CompileGeoIPToSrsRequest
16, // 13: libcore.LibcoreService.CompileGeoSiteToSrs:input_type -> libcore.CompileGeoSiteToSrsRequest
3, // 14: libcore.LibcoreService.Exit:output_type -> libcore.EmptyResp
11, // 15: libcore.LibcoreService.Update:output_type -> libcore.UpdateResp
4, // 16: libcore.LibcoreService.Start:output_type -> libcore.ErrorResp
4, // 17: libcore.LibcoreService.Stop:output_type -> libcore.ErrorResp
7, // 18: libcore.LibcoreService.Test:output_type -> libcore.TestResp
9, // 19: libcore.LibcoreService.QueryStats:output_type -> libcore.QueryStatsResp
12, // 20: libcore.LibcoreService.ListConnections:output_type -> libcore.ListConnectionsResp
13, // 21: libcore.LibcoreService.GetGeoIPList:output_type -> libcore.GetGeoIPListResponse
14, // 22: libcore.LibcoreService.GetGeoSiteList:output_type -> libcore.GetGeoSiteListResponse
3, // 23: libcore.LibcoreService.CompileGeoIPToSrs:output_type -> libcore.EmptyResp
3, // 24: libcore.LibcoreService.CompileGeoSiteToSrs:output_type -> libcore.EmptyResp
14, // [14:25] is the sub-list for method output_type
3, // [3:14] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
@ -1133,6 +1363,54 @@ func file_libcore_proto_init() {
return nil
}
}
file_libcore_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetGeoIPListResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_libcore_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetGeoSiteListResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_libcore_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CompileGeoIPToSrsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_libcore_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CompileGeoSiteToSrsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
@ -1140,7 +1418,7 @@ func file_libcore_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_libcore_proto_rawDesc,
NumEnums: 2,
NumMessages: 11,
NumMessages: 15,
NumExtensions: 0,
NumServices: 1,
},

View File

@ -12,6 +12,11 @@ service LibcoreService {
rpc Test(TestReq) returns (TestResp) {}
rpc QueryStats(QueryStatsReq) returns (QueryStatsResp) {}
rpc ListConnections(EmptyReq) returns (ListConnectionsResp) {}
//
rpc GetGeoIPList(EmptyReq) returns (GetGeoIPListResponse);
rpc GetGeoSiteList(EmptyReq) returns (GetGeoSiteListResponse);
rpc CompileGeoIPToSrs(CompileGeoIPToSrsRequest) returns (EmptyResp);
rpc CompileGeoSiteToSrs(CompileGeoSiteToSrsRequest) returns (EmptyResp);
}
message EmptyReq {}
@ -93,3 +98,19 @@ message UpdateResp {
message ListConnectionsResp {
string nekoray_connections_json = 1;
}
message GetGeoIPListResponse {
repeated string items = 1;
}
message GetGeoSiteListResponse {
repeated string items = 2;
}
message CompileGeoIPToSrsRequest {
string item = 1;
}
message CompileGeoSiteToSrsRequest {
string item = 1;
}

View File

@ -19,13 +19,17 @@ import (
const _ = grpc.SupportPackageIsVersion7
const (
LibcoreService_Exit_FullMethodName = "/libcore.LibcoreService/Exit"
LibcoreService_Update_FullMethodName = "/libcore.LibcoreService/Update"
LibcoreService_Start_FullMethodName = "/libcore.LibcoreService/Start"
LibcoreService_Stop_FullMethodName = "/libcore.LibcoreService/Stop"
LibcoreService_Test_FullMethodName = "/libcore.LibcoreService/Test"
LibcoreService_QueryStats_FullMethodName = "/libcore.LibcoreService/QueryStats"
LibcoreService_ListConnections_FullMethodName = "/libcore.LibcoreService/ListConnections"
LibcoreService_Exit_FullMethodName = "/libcore.LibcoreService/Exit"
LibcoreService_Update_FullMethodName = "/libcore.LibcoreService/Update"
LibcoreService_Start_FullMethodName = "/libcore.LibcoreService/Start"
LibcoreService_Stop_FullMethodName = "/libcore.LibcoreService/Stop"
LibcoreService_Test_FullMethodName = "/libcore.LibcoreService/Test"
LibcoreService_QueryStats_FullMethodName = "/libcore.LibcoreService/QueryStats"
LibcoreService_ListConnections_FullMethodName = "/libcore.LibcoreService/ListConnections"
LibcoreService_GetGeoIPList_FullMethodName = "/libcore.LibcoreService/GetGeoIPList"
LibcoreService_GetGeoSiteList_FullMethodName = "/libcore.LibcoreService/GetGeoSiteList"
LibcoreService_CompileGeoIPToSrs_FullMethodName = "/libcore.LibcoreService/CompileGeoIPToSrs"
LibcoreService_CompileGeoSiteToSrs_FullMethodName = "/libcore.LibcoreService/CompileGeoSiteToSrs"
)
// LibcoreServiceClient is the client API for LibcoreService service.
@ -39,6 +43,10 @@ type LibcoreServiceClient interface {
Test(ctx context.Context, in *TestReq, opts ...grpc.CallOption) (*TestResp, error)
QueryStats(ctx context.Context, in *QueryStatsReq, opts ...grpc.CallOption) (*QueryStatsResp, error)
ListConnections(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ListConnectionsResp, error)
GetGeoIPList(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*GetGeoIPListResponse, error)
GetGeoSiteList(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*GetGeoSiteListResponse, error)
CompileGeoIPToSrs(ctx context.Context, in *CompileGeoIPToSrsRequest, opts ...grpc.CallOption) (*EmptyResp, error)
CompileGeoSiteToSrs(ctx context.Context, in *CompileGeoSiteToSrsRequest, opts ...grpc.CallOption) (*EmptyResp, error)
}
type libcoreServiceClient struct {
@ -112,6 +120,42 @@ func (c *libcoreServiceClient) ListConnections(ctx context.Context, in *EmptyReq
return out, nil
}
func (c *libcoreServiceClient) GetGeoIPList(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*GetGeoIPListResponse, error) {
out := new(GetGeoIPListResponse)
err := c.cc.Invoke(ctx, LibcoreService_GetGeoIPList_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *libcoreServiceClient) GetGeoSiteList(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*GetGeoSiteListResponse, error) {
out := new(GetGeoSiteListResponse)
err := c.cc.Invoke(ctx, LibcoreService_GetGeoSiteList_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *libcoreServiceClient) CompileGeoIPToSrs(ctx context.Context, in *CompileGeoIPToSrsRequest, opts ...grpc.CallOption) (*EmptyResp, error) {
out := new(EmptyResp)
err := c.cc.Invoke(ctx, LibcoreService_CompileGeoIPToSrs_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *libcoreServiceClient) CompileGeoSiteToSrs(ctx context.Context, in *CompileGeoSiteToSrsRequest, opts ...grpc.CallOption) (*EmptyResp, error) {
out := new(EmptyResp)
err := c.cc.Invoke(ctx, LibcoreService_CompileGeoSiteToSrs_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// LibcoreServiceServer is the server API for LibcoreService service.
// All implementations must embed UnimplementedLibcoreServiceServer
// for forward compatibility
@ -123,6 +167,10 @@ type LibcoreServiceServer interface {
Test(context.Context, *TestReq) (*TestResp, error)
QueryStats(context.Context, *QueryStatsReq) (*QueryStatsResp, error)
ListConnections(context.Context, *EmptyReq) (*ListConnectionsResp, error)
GetGeoIPList(context.Context, *EmptyReq) (*GetGeoIPListResponse, error)
GetGeoSiteList(context.Context, *EmptyReq) (*GetGeoSiteListResponse, error)
CompileGeoIPToSrs(context.Context, *CompileGeoIPToSrsRequest) (*EmptyResp, error)
CompileGeoSiteToSrs(context.Context, *CompileGeoSiteToSrsRequest) (*EmptyResp, error)
mustEmbedUnimplementedLibcoreServiceServer()
}
@ -151,6 +199,18 @@ func (UnimplementedLibcoreServiceServer) QueryStats(context.Context, *QueryStats
func (UnimplementedLibcoreServiceServer) ListConnections(context.Context, *EmptyReq) (*ListConnectionsResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListConnections not implemented")
}
func (UnimplementedLibcoreServiceServer) GetGeoIPList(context.Context, *EmptyReq) (*GetGeoIPListResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetGeoIPList not implemented")
}
func (UnimplementedLibcoreServiceServer) GetGeoSiteList(context.Context, *EmptyReq) (*GetGeoSiteListResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetGeoSiteList not implemented")
}
func (UnimplementedLibcoreServiceServer) CompileGeoIPToSrs(context.Context, *CompileGeoIPToSrsRequest) (*EmptyResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method CompileGeoIPToSrs not implemented")
}
func (UnimplementedLibcoreServiceServer) CompileGeoSiteToSrs(context.Context, *CompileGeoSiteToSrsRequest) (*EmptyResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method CompileGeoSiteToSrs not implemented")
}
func (UnimplementedLibcoreServiceServer) mustEmbedUnimplementedLibcoreServiceServer() {}
// UnsafeLibcoreServiceServer may be embedded to opt out of forward compatibility for this service.
@ -290,6 +350,78 @@ func _LibcoreService_ListConnections_Handler(srv interface{}, ctx context.Contex
return interceptor(ctx, in, info, handler)
}
func _LibcoreService_GetGeoIPList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(EmptyReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LibcoreServiceServer).GetGeoIPList(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: LibcoreService_GetGeoIPList_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LibcoreServiceServer).GetGeoIPList(ctx, req.(*EmptyReq))
}
return interceptor(ctx, in, info, handler)
}
func _LibcoreService_GetGeoSiteList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(EmptyReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LibcoreServiceServer).GetGeoSiteList(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: LibcoreService_GetGeoSiteList_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LibcoreServiceServer).GetGeoSiteList(ctx, req.(*EmptyReq))
}
return interceptor(ctx, in, info, handler)
}
func _LibcoreService_CompileGeoIPToSrs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CompileGeoIPToSrsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LibcoreServiceServer).CompileGeoIPToSrs(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: LibcoreService_CompileGeoIPToSrs_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LibcoreServiceServer).CompileGeoIPToSrs(ctx, req.(*CompileGeoIPToSrsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _LibcoreService_CompileGeoSiteToSrs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CompileGeoSiteToSrsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LibcoreServiceServer).CompileGeoSiteToSrs(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: LibcoreService_CompileGeoSiteToSrs_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LibcoreServiceServer).CompileGeoSiteToSrs(ctx, req.(*CompileGeoSiteToSrsRequest))
}
return interceptor(ctx, in, info, handler)
}
// LibcoreService_ServiceDesc is the grpc.ServiceDesc for LibcoreService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -325,6 +457,22 @@ var LibcoreService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListConnections",
Handler: _LibcoreService_ListConnections_Handler,
},
{
MethodName: "GetGeoIPList",
Handler: _LibcoreService_GetGeoIPList_Handler,
},
{
MethodName: "GetGeoSiteList",
Handler: _LibcoreService_GetGeoSiteList_Handler,
},
{
MethodName: "CompileGeoIPToSrs",
Handler: _LibcoreService_CompileGeoIPToSrs_Handler,
},
{
MethodName: "CompileGeoSiteToSrs",
Handler: _LibcoreService_CompileGeoSiteToSrs_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "libcore.proto",

View File

@ -1,3 +1 @@
protoc -I . --go_out=. --go_opt paths=source_relative --go-grpc_out=. --go-grpc_opt paths=source_relative libcore.proto
# protoc -I . --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` libcore.proto
protoc -I . --go_out=. --go_opt paths=source_relative --go-grpc_out=. --go-grpc_opt paths=source_relative libcore.proto

View File

@ -25,5 +25,5 @@ popd
#### Go: nekobox_core ####
pushd go/cmd/nekobox_core
go build -v -o $DEST -trimpath -ldflags "-w -s -X $neko_common.Version_neko=$version_standalone" -tags "with_clash_api,with_gvisor,with_quic,with_wireguard,with_utls,with_ech"
go build -v -o $DEST -trimpath -ldflags "-w -s -X $neko_common.Version_neko=$version_standalone" -tags "with_clash_api,with_gvisor,with_quic,with_wireguard,with_utls,with_ech,with_dhcp"
popd

View File

@ -86,16 +86,28 @@ namespace NekoGui_ConfigItem {
case itemType::boolean:
object.insert(item->name, *(bool *) item->ptr);
break;
case itemType::stringList:
object.insert(item->name, QList2QJsonArray<QString>(*(QList<QString> *) item->ptr));
case itemType::stringList: {
if (QListStr2QJsonArray(*(QList<QString> *) item->ptr).isEmpty()) continue;
object.insert(item->name, QListStr2QJsonArray(*(QList<QString> *) item->ptr));
break;
case itemType::integerList:
object.insert(item->name, QList2QJsonArray<int>(*(QList<int> *) item->ptr));
}
case itemType::integerList: {
if (QListInt2QJsonArray(*(QList<int> *) item->ptr).isEmpty()) continue;
object.insert(item->name, QListInt2QJsonArray(*(QList<int> *) item->ptr));
break;
}
case itemType::jsonStore:
// _add 时应关联对应 JsonStore 的指针
object.insert(item->name, ((JsonStore *) item->ptr)->ToJson());
break;
case itemType::jsonStoreList:
QJsonArray jsonArray;
auto arr = *(QList<JsonStore*> *) item->ptr;
for ( JsonStore* obj : arr) {
jsonArray.push_back(obj->ToJson());
}
object.insert(item->name, jsonArray);
break;
}
}
return object;
@ -330,6 +342,7 @@ namespace NekoGui {
if (!Preset::SingBox::DomainStrategy.contains(domain_strategy)) domain_strategy = "";
if (!Preset::SingBox::DomainStrategy.contains(outbound_domain_strategy)) outbound_domain_strategy = "";
_add(new configItem("current_route_id", &this->current_route_id, itemType::integer));
_add(new configItem("default_outbound", &this->def_outbound, itemType::string));
//
_add(new configItem("remote_dns", &this->remote_dns, itemType::string));
_add(new configItem("remote_dns_strategy", &this->remote_dns_strategy, itemType::string));
@ -345,20 +358,7 @@ namespace NekoGui {
}
QStringList Routing::List() {
QDir dr(ROUTES_PREFIX);
return dr.entryList(QDir::Files);
}
bool Routing::SetToActive(const QString &name) {
NekoGui::dataStore->routing = std::make_unique<Routing>();
NekoGui::dataStore->routing->load_control_must = true;
NekoGui::dataStore->routing->fn = ROUTES_PREFIX + name;
auto ok = NekoGui::dataStore->routing->Load();
if (ok) {
NekoGui::dataStore->active_routing = name;
NekoGui::dataStore->Save();
}
return ok;
return {"Default"};
}
// NO default extra core

View File

@ -18,5 +18,6 @@ namespace NekoGui {
} // namespace NekoGui
#define IS_NEKO_BOX (NekoGui::coreType == NekoGui::CoreType::SING_BOX)
#define ROUTES_PREFIX_NAME QString(IS_NEKO_BOX ? "routes_box" : "routes")
#define ROUTES_PREFIX_NAME QString("route_profiles")
#define ROUTES_PREFIX QString(ROUTES_PREFIX_NAME + "/")
#define RULE_SETS_DIR QString("rule_sets")

View File

@ -10,6 +10,7 @@ namespace NekoGui_ConfigItem {
stringList,
integerList,
jsonStore,
jsonStoreList,
};
class configItem {
@ -57,11 +58,11 @@ namespace NekoGui_ConfigItem {
QByteArray ToJsonBytes();
void FromJson(QJsonObject object);
virtual void FromJson(QJsonObject object);
void FromJsonBytes(const QByteArray &data);
bool Save();
virtual bool Save();
bool Load();
};

View File

@ -25,8 +25,6 @@ namespace NekoGui {
explicit Routing(int preset = 0);
static QStringList List();
static bool SetToActive(const QString &name);
};
class ExtraCore : public JsonStore {
@ -179,7 +177,7 @@ namespace NekoGui {
void UpdateStartedId(int id);
QString GetUserAgent(bool isDefault = false) const;
[[nodiscard]] QString GetUserAgent(bool isDefault = false) const;
};
extern DataStore *dataStore;

View File

@ -105,17 +105,26 @@ QString QJsonObject2QString(const QJsonObject &jsonObject, bool compact) {
return QJsonDocument(jsonObject).toJson(compact ? QJsonDocument::Compact : QJsonDocument::Indented);
}
template<typename T>
QJsonArray QList2QJsonArray(const QList<T> &list) {
QJsonArray QListStr2QJsonArray(const QList<QString> &list) {
QVariantList list2;
bool isEmpty = true;
for (auto &item: list) {
if (item.trimmed().isEmpty()) continue;
list2.append(item);
isEmpty = false;
}
if (isEmpty) return {};
else return QJsonArray::fromVariantList(list2);
}
QJsonArray QListInt2QJsonArray(const QList<int> &list) {
QVariantList list2;
for (auto &item: list)
list2.append(item);
return QJsonArray::fromVariantList(list2);
}
template QJsonArray QList2QJsonArray<int>(const QList<int> &list);
template QJsonArray QList2QJsonArray<QString>(const QList<QString> &list);
QList<int> QJsonArray2QListInt(const QJsonArray &arr) {
QList<int> list2;
for (auto item: arr)

View File

@ -83,8 +83,9 @@ QJsonObject QString2QJsonObject(const QString &jsonString);
QString QJsonObject2QString(const QJsonObject &jsonObject, bool compact);
template<typename T>
QJsonArray QList2QJsonArray(const QList<T> &list);
QJsonArray QListInt2QJsonArray(const QList<int> &list);
QJsonArray QListStr2QJsonArray(const QList<QString> &list);
QList<int> QJsonArray2QListInt(const QJsonArray &arr);

View File

@ -187,20 +187,16 @@ int main(int argc, char* argv[]) {
if (!dir.exists(ROUTES_PREFIX_NAME)) {
dir_success &= dir.mkdir(ROUTES_PREFIX_NAME);
}
if (!dir.exists(RULE_SETS_DIR)) {
dir_success &= dir.mkdir(RULE_SETS_DIR);
}
if (!dir_success) {
QMessageBox::warning(nullptr, "Error", "No permission to write " + dir.absolutePath());
return 1;
}
// Load dataStore
switch (NekoGui::coreType) {
case NekoGui::CoreType::SING_BOX:
NekoGui::dataStore->fn = "groups/nekobox.json";
break;
default:
MessageBoxWarning("Error", "Unknown coreType.");
return 0;
}
NekoGui::dataStore->fn = "groups/nekobox.json";
auto isLoaded = NekoGui::dataStore->Load();
if (!isLoaded) {
NekoGui::dataStore->Save();
@ -211,7 +207,7 @@ int main(int argc, char* argv[]) {
// load routing
NekoGui::dataStore->routing = std::make_unique<NekoGui::Routing>();
NekoGui::dataStore->routing->fn = ROUTES_PREFIX + NekoGui::dataStore->active_routing;
NekoGui::dataStore->routing->fn = ROUTES_PREFIX + "Default";
isLoaded = NekoGui::dataStore->routing->Load();
if (!isLoaded) {
NekoGui::dataStore->routing->Save();

View File

@ -3,8 +3,6 @@
#include <utility>
#include <QStringList>
#ifndef NKR_NO_GRPC
#include "main/NekoGui.hpp"
#include <QCoreApplication>
@ -16,6 +14,8 @@
#include <QMutex>
#include <QAbstractNetworkCache>
#include <iostream>
namespace QtGrpc {
const char *GrpcAcceptEncodingHeader = "grpc-accept-encoding";
const char *AcceptEncodingHeader = "accept-encoding";
@ -280,6 +280,78 @@ namespace NekoGui_rpc {
return reply;
}
}
} // namespace NekoGui_rpc
#endif
QStringList Client::GetGeoList(bool *rpcOK, GeoRuleSetType mode) {
switch (mode) {
case GeoRuleSetType::ip: {
libcore::EmptyReq req;
libcore::GetGeoIPListResponse resp;
auto status = default_grpc_channel->Call("GetGeoIPList", req, &resp);
if (status == QNetworkReply::NoError) {
QStringList res;
for (const auto & i : resp.items()) {
res.append(QString::fromStdString(i));
}
*rpcOK = true;
return res;
} else {
NOT_OK
return {};
}
}
case GeoRuleSetType::site: {
libcore::EmptyReq req;
libcore::GetGeoSiteListResponse resp;
auto status = default_grpc_channel->Call("GetGeoSiteList", req, &resp);
if (status == QNetworkReply::NoError) {
QStringList res;
for (const auto & i : resp.items()) {
res.append(QString::fromStdString(i));
}
*rpcOK = true;
return res;
} else {
NOT_OK
return {};
}
}
}
return {};
}
QString Client::CompileGeoSet(bool *rpcOK, GeoRuleSetType mode, std::string category) {
switch (mode) {
case ip: {
libcore::CompileGeoIPToSrsRequest req;
libcore::EmptyResp resp;
req.set_item(category);
auto status = default_grpc_channel->Call("CompileGeoIPToSrs", req, &resp);
if (status == QNetworkReply::NoError) {
*rpcOK = true;
return "";
} else {
NOT_OK
return qt_error_string(status);
}
}
case site: {
libcore::CompileGeoSiteToSrsRequest req;
libcore::EmptyResp resp;
req.set_item(category);
auto status = default_grpc_channel->Call("CompileGeoSiteToSrs", req, &resp);
if (status == QNetworkReply::NoError) {
*rpcOK = true;
return "";
} else {
NOT_OK
return qt_error_string(status);
}
}
}
}
} // namespace NekoGui_rpc

View File

@ -10,6 +10,8 @@ namespace QtGrpc {
}
namespace NekoGui_rpc {
enum GeoRuleSetType {ip, site};
class Client {
public:
explicit Client(std::function<void(const QString &)> onError, const QString &target, const QString &token);
@ -28,6 +30,10 @@ namespace NekoGui_rpc {
libcore::UpdateResp Update(bool *rpcOK, const libcore::UpdateReq &request);
QStringList GetGeoList(bool *rpcOK, GeoRuleSetType mode);
QString CompileGeoSet(bool *rpcOK, GeoRuleSetType mode, std::string category);
private:
std::function<std::unique_ptr<QtGrpc::Http2GrpcChannelPrivate>()> make_grpc_channel;
std::unique_ptr<QtGrpc::Http2GrpcChannelPrivate> default_grpc_channel;

View File

@ -1,7 +1,6 @@
#include "dialog_manage_routes.h"
#include "ui_dialog_manage_routes.h"
#include "db/Database.hpp"
//#include "ui_RouteItem.h"
#include "3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp"
#include "3rdparty/qv2ray/v3/components/GeositeReader/GeositeReader.hpp"
@ -11,53 +10,47 @@
#include <QFile>
#include <QMessageBox>
QList<QString> getRouteProfiles() {
auto routeProfiles = NekoGui::profileManager->routes;
QList<QString> res;
for (const auto &item: routeProfiles) {
res << item.second->name;
}
return res;
}
int getRouteID(const QString& name) {
auto routeProfiles = NekoGui::profileManager->routes;
for (const auto &item: routeProfiles) {
if (item.second->name == name) return item.first;
}
return -1;
}
QString getRouteName(int id) {
return NekoGui::profileManager->routes.count(id) ? NekoGui::profileManager->routes[id]->name : "";
}
QList<QString> deleteItemFromList(const QList<QString>& base, const QString& target) {
QList<QString> res;
for (const auto &item: base) {
if (item == target) continue;
res << item;
}
return res;
}
void DialogManageRoutes::reloadProfileItems() {
QSignalBlocker blocker = QSignalBlocker(ui->route_prof); // apparently the currentIndexChanged will make us crash if we clear the QComboBox
ui->route_prof->clear();
blocker.unblock();
ui->route_profiles->clear();
ui->route_prof->addItems(currentRouteProfiles);
ui->route_profiles->addItems(currentRouteProfiles);
bool selectedChainGone = true;
int i=0;
for (const auto &item: chainList) {
ui->route_prof->addItem(item->name);
ui->route_profiles->addItem(item->name);
if (item->id == currentRouteProfileID) {
ui->route_prof->setCurrentIndex(i);
selectedChainGone=false;
}
i++;
}
if (selectedChainGone) {
currentRouteProfileID=0;
ui->route_prof->setCurrentIndex(0);
}
}
DialogManageRoutes::DialogManageRoutes(QWidget *parent) : QDialog(parent), ui(new Ui::DialogManageRoutes) {
ui->setupUi(this);
currentRouteProfiles = getRouteProfiles();
auto profiles = NekoGui::profileManager->routes;
for (const auto &item: profiles) {
chainList << item.second;
}
if (chainList.empty()) {
auto defaultChain = NekoGui::RoutingChain::GetDefaultChain();
NekoGui::profileManager->AddRouteChain(defaultChain);
chainList.append(defaultChain);
}
currentRouteProfileID = NekoGui::dataStore->routing->current_route_id;
if (currentRouteProfileID < 0) currentRouteProfileID = chainList[0]->id;
QStringList qsValue = {""};
QString dnsHelpDocumentUrl;
ui->default_out->setCurrentText(NekoGui::dataStore->routing->def_outbound);
ui->outbound_domain_strategy->addItems(Preset::SingBox::DomainStrategy);
ui->domainStrategyCombo->addItems(Preset::SingBox::DomainStrategy);
qsValue += QString("prefer_ipv4 prefer_ipv6 ipv4_only ipv6_only").split(" ");
@ -96,17 +89,11 @@ DialogManageRoutes::DialogManageRoutes(QWidget *parent) : QDialog(parent), ui(ne
ui->direct_dns_strategy->setCurrentText(NekoGui::dataStore->routing->direct_dns_strategy);
ui->dns_final_out->setCurrentText(NekoGui::dataStore->routing->dns_final_out);
reloadProfileItems();
ui->route_prof->setCurrentText(getRouteName(NekoGui::dataStore->routing->current_route_id));
connect(ui->delete_route, &QPushButton::clicked, this, [=]{
auto current = ui->route_profiles->currentItem()->text();
currentRouteProfiles = deleteItemFromList(currentRouteProfiles, current);
reloadProfileItems();
connect(ui->route_prof, &QComboBox::currentIndexChanged, this, [=](const int& idx) {
currentRouteProfileID = chainList[idx]->id;
});
ADD_ASTERISK(this)
}
@ -115,7 +102,7 @@ DialogManageRoutes::~DialogManageRoutes() {
}
void DialogManageRoutes::accept() {
if (currentRouteProfiles.empty()) {
if (chainList.empty()) {
MessageBoxInfo("Invalid settings", "Routing profile cannot be empty");
return;
}
@ -132,7 +119,55 @@ void DialogManageRoutes::accept() {
NekoGui::dataStore->routing->direct_dns_strategy = ui->direct_dns_strategy->currentText();
NekoGui::dataStore->routing->dns_final_out = ui->dns_final_out->currentText();
// TODO add mine
NekoGui::profileManager->UpdateRouteChains(chainList);
NekoGui::dataStore->routing->current_route_id = currentRouteProfileID;
NekoGui::dataStore->routing->def_outbound = ui->default_out->currentText();
//
QStringList msg{"UpdateDataStore"};
msg << "RouteChanged";
MW_dialog_message("", msg.join(","));
QDialog::accept();
}
void DialogManageRoutes::on_new_route_clicked() {
routeChainWidget = new RouteItem(this, NekoGui::ProfileManager::NewRouteChain());
routeChainWidget->setWindowModality(Qt::ApplicationModal);
routeChainWidget->show();
connect(routeChainWidget, &RouteItem::settingsChanged, this, [=](const std::shared_ptr<NekoGui::RoutingChain>& chain) {
chainList << chain;
reloadProfileItems();
});
}
void DialogManageRoutes::on_edit_route_clicked() {
auto idx = ui->route_profiles->currentRow();
if (idx < 0) return;
routeChainWidget = new RouteItem(this, chainList[idx]);
routeChainWidget->setWindowModality(Qt::ApplicationModal);
routeChainWidget->show();
connect(routeChainWidget, &RouteItem::settingsChanged, this, [=](const std::shared_ptr<NekoGui::RoutingChain>& chain) {
chainList[idx] = chain;
reloadProfileItems();
});
}
void DialogManageRoutes::on_delete_route_clicked() {
auto idx = ui->route_profiles->currentRow();
if (idx < 0) return;
if (chainList.size() == 1) {
MessageBoxWarning("Invalid operation", "Routing Profiles cannot be empty, try adding another profile or editing this one");
return;
}
auto profileToDel = chainList[idx];
chainList.removeAt(idx);
if (profileToDel->id == currentRouteProfileID) {
currentRouteProfileID = chainList[0]->id;
}
reloadProfileItems();
}

View File

@ -5,6 +5,7 @@
#include "3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.hpp"
#include "main/NekoGui.hpp"
#include "widget/RouteItem.h"
QT_BEGIN_NAMESPACE
namespace Ui {
@ -23,9 +24,13 @@ public:
private:
Ui::DialogManageRoutes *ui;
RouteItem* routeChainWidget;
void reloadProfileItems();
QList<QString> currentRouteProfiles;
QList<std::shared_ptr<NekoGui::RoutingChain>> chainList;
int currentRouteProfileID = -1;
public slots:
void accept() override;

View File

@ -27,45 +27,6 @@
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QGridLayout" name="gridLayout_2">
<item row="3" column="1">
<widget class="QComboBox" name="outbound_domain_strategy">
<property name="editable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Server Address Strategy</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="toolTip">
<string notr="true">For V2Ray, it sets routing.domainStrategy
For sing-box, it sets inbound.domain_strategy</string>
</property>
<property name="text">
<string>Domain Strategy</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Sniffing Mode</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="domainStrategyCombo">
<property name="editable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="sniffing_mode">
<item>
@ -85,8 +46,26 @@ For sing-box, it sets inbound.domain_strategy</string>
</item>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="route_prof"/>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Sniffing Mode</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Server Address Strategy</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="domainStrategyCombo">
<property name="editable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="route_prof_l">
@ -95,6 +74,48 @@ For sing-box, it sets inbound.domain_strategy</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="route_prof"/>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="outbound_domain_strategy">
<property name="editable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="toolTip">
<string notr="true">For V2Ray, it sets routing.domainStrategy
For sing-box, it sets inbound.domain_strategy</string>
</property>
<property name="text">
<string>Domain Strategy</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="default_out">
<item>
<property name="text">
<string>proxy</string>
</property>
</item>
<item>
<property name="text">
<string>direct</string>
</property>
</item>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="default_out_l">
<property name="text">
<string>Default Outbound</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -92,17 +92,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
}
// software_name
if (IS_NEKO_BOX) {
software_name = "NekoBox";
software_core_name = "sing-box";
// replace default values
if (NekoGui::dataStore->log_level == "warning") NekoGui::dataStore->log_level = "info";
if (NekoGui::dataStore->mux_protocol.isEmpty()) NekoGui::dataStore->mux_protocol = "h2mux";
//
if (QDir("dashboard").count() == 0) {
QDir().mkdir("dashboard");
QFile::copy(":/neko/dashboard-notice.html", "dashboard/index.html");
}
software_name = "NekoBox";
software_core_name = "sing-box";
// replace default values
if (NekoGui::dataStore->log_level == "warning") NekoGui::dataStore->log_level = "info";
if (NekoGui::dataStore->mux_protocol.isEmpty()) NekoGui::dataStore->mux_protocol = "h2mux";
//
if (QDir("dashboard").count() == 0) {
QDir().mkdir("dashboard");
QFile::copy(":/neko/dashboard-notice.html", "dashboard/index.html");
}
// top bar
@ -275,17 +273,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
ui->menuActive_Server->addAction(a);
if (++active_server_item_count == 100) break;
}
// active routing
for (const auto &old: ui->menuActive_Routing->actions()) {
ui->menuActive_Routing->removeAction(old);
old->deleteLater();
}
for (const auto &name: NekoGui::Routing::List()) {
auto a = new QAction(name, this);
a->setCheckable(true);
a->setChecked(name == NekoGui::dataStore->active_routing);
ui->menuActive_Routing->addAction(a);
}
});
connect(ui->menuActive_Server, &QMenu::triggered, this, [=](QAction *a) {
bool ok;
@ -297,24 +284,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
neko_start(id);
}
});
connect(ui->menuActive_Routing, &QMenu::triggered, this, [=](QAction *a) {
auto fn = a->text();
if (!fn.isEmpty()) {
NekoGui::Routing r;
r.load_control_must = true;
r.fn = ROUTES_PREFIX + fn;
if (r.Load()) {
if (QMessageBox::question(GetMessageBoxParent(), software_name, tr("Load routing and apply: %1").arg(fn) + "\n" ) == QMessageBox::Yes) {
NekoGui::Routing::SetToActive(fn);
if (NekoGui::dataStore->started_id >= 0) {
neko_start(NekoGui::dataStore->started_id);
} else {
refresh_status();
}
}
}
}
});
connect(ui->actionRemember_last_proxy, &QAction::triggered, this, [=](bool checked) {
NekoGui::dataStore->remember_enable = checked;
NekoGui::dataStore->Save();
@ -531,6 +500,7 @@ void MainWindow::dialog_message_impl(const QString &sender, const QString &info)
if (info.contains("UpdateDataStore")) {
auto suggestRestartProxy = NekoGui::dataStore->Save();
if (info.contains("RouteChanged")) {
NekoGui::dataStore->routing->Save();
suggestRestartProxy = true;
}
if (info.contains("NeedRestart")) {

View File

@ -179,7 +179,7 @@ private:
void HotkeyEvent(const QString &key);
// grpc and ...
// grpc
static void setup_grpc();

View File

@ -408,12 +408,6 @@
</property>
<addaction name="actionfake_2"/>
</widget>
<widget class="QMenu" name="menuActive_Routing">
<property name="title">
<string>Active Routing</string>
</property>
<addaction name="actionfake_3"/>
</widget>
<addaction name="actionShow_window"/>
<addaction name="menu_add_from_clipboard2"/>
<addaction name="menu_scan_qr"/>
@ -422,7 +416,6 @@
<addaction name="actionRemember_last_proxy"/>
<addaction name="actionAllow_LAN"/>
<addaction name="menuActive_Server"/>
<addaction name="menuActive_Routing"/>
<addaction name="menu_spmode"/>
<addaction name="menu_program_preference"/>
<addaction name="separator"/>

View File

@ -409,7 +409,6 @@ void MainWindow::neko_stop(bool crash, bool sem) {
},
DS_cores);
#ifndef NKR_NO_GRPC
NekoGui_traffic::trafficLooper->loop_enabled = false;
NekoGui_traffic::trafficLooper->loop_mutex.lock();
if (NekoGui::dataStore->traffic_loop_interval != 0) {
@ -431,7 +430,6 @@ void MainWindow::neko_stop(bool crash, bool sem) {
return false;
}
}
#endif
NekoGui::dataStore->UpdateStartedId(-1919);
NekoGui::dataStore->need_keep_vpn_off = false;
@ -475,7 +473,6 @@ void MainWindow::neko_stop(bool crash, bool sem) {
void MainWindow::CheckUpdate() {
// on new thread...
#ifndef NKR_NO_GRPC
bool ok;
libcore::UpdateReq request;
request.set_action(libcore::UpdateAction::Check);
@ -536,5 +533,4 @@ void MainWindow::CheckUpdate() {
QDesktopServices::openUrl(QUrl(response.release_url().c_str()));
}
});
#endif
}
}

View File

@ -2,7 +2,8 @@
#include "ui_RouteItem.h"
#include "db/RouteEntity.h"
#include "db/Database.hpp"
#include <iostream>
#include "rpc/gRPC.h"
int RouteItem::getIndexOf(const QString& name) const {
for (int i=0;i<chain->Rules.size();i++) {
@ -13,7 +14,8 @@ int RouteItem::getIndexOf(const QString& name) const {
}
QString get_outbound_name(int id) {
// -2 is direct -3 is block -4 is dns_out
// -1 is proxy -2 is direct -3 is block -4 is dns_out
if (id == -1) return "proxy";
if (id == -2) return "direct";
if (id == -3) return "block";
if (id == -4) return "dns_out";
@ -23,6 +25,7 @@ QString get_outbound_name(int id) {
}
int get_outbound_id(const QString& name) {
if (name == "proxy") return -1;
if (name == "direct") return -2;
if (name == "block") return -3;
if (name == "dns_out") return -4;
@ -45,18 +48,42 @@ QStringList get_all_outbounds() {
}
RouteItem::RouteItem(QWidget *parent, const std::shared_ptr<NekoGui::RoutingChain>& routeChain)
: QGroupBox(parent), ui(new Ui::RouteItem) {
: QDialog(parent), ui(new Ui::RouteItem) {
ui->setupUi(this);
// make a copy
chain = routeChain;
// add the default rule if empty
if (chain->Rules.empty()) {
auto routeItem = std::make_shared<NekoGui::RouteRule>();
routeItem->name = "dns-hijack";
routeItem->protocol = "dns";
routeItem->outboundID = -4;
chain->Rules << routeItem;
}
// setup rule set helper
bool ok; // for now we discard this
auto geoIpList = NekoGui_rpc::defaultClient->GetGeoList(&ok, NekoGui_rpc::GeoRuleSetType::ip);
auto geoSiteList = NekoGui_rpc::defaultClient->GetGeoList(&ok, NekoGui_rpc::GeoRuleSetType::site);
geo_items << geoIpList << geoSiteList;
helperModel = new QStringListModel(geo_items, this);
ui->rule_set_helper->hide();
ui->rule_set_helper->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->rule_set_helper->setSelectionMode(QAbstractItemView::SingleSelection);
ui->rule_set_helper->setSelectionRectVisible(false);
ui->rule_set_helper->setModel(helperModel);
connect(ui->rule_set_helper, &QListView::clicked, this, [=](const QModelIndex& index){
applyRuleHelperSelect(index);
});
std::map<QString, int> valueMap;
for (auto &item: chain->Rules) {
auto baseName = item->name;
int randPart;
if (baseName == "") {
randPart = GetRandomUint64()%1000;
randPart = int(GetRandomUint64()%1000);
baseName = "rule_" + Int2String(randPart);
lastNum = std::max(lastNum, randPart);
}
@ -64,7 +91,7 @@ RouteItem::RouteItem(QWidget *parent, const std::shared_ptr<NekoGui::RoutingChai
valueMap[baseName]++;
if (valueMap[baseName] > 1) {
valueMap[baseName]--;
randPart = GetRandomUint64()%1000;
randPart = int(GetRandomUint64()%1000);
baseName = "rule_" + Int2String(randPart);
lastNum = std::max(lastNum, randPart);
continue;
@ -75,13 +102,17 @@ RouteItem::RouteItem(QWidget *parent, const std::shared_ptr<NekoGui::RoutingChai
ui->route_items->addItem(item->name);
}
QStringList outboundOptions = {"direct", "block", "dns_out"};
QStringList outboundOptions = {"proxy", "direct", "block", "dns_out"};
outboundOptions << get_all_outbounds();
ui->route_name->setText(chain->name);
ui->rule_attr->addItems(NekoGui::RouteRule::get_attributes());
ui->rule_out->addItems(outboundOptions);
ui->rule_attr_text->hide();
ui->rule_attr_data->setTitle("");
ui->rule_attr_box->setEnabled(false);
ui->rule_preview->setEnabled(false);
updateRuleSection();
connect(ui->route_name, &QLineEdit::textChanged, this, [=](const QString& text) {
chain->name = text;
@ -93,7 +124,7 @@ RouteItem::RouteItem(QWidget *parent, const std::shared_ptr<NekoGui::RoutingChai
for (int i=0;i<rules.size();i++) {
auto item = rules[i];
res += QJsonObject2QString(item.toObject(), false);
if (i != rules.size()-1) res+=",\n";
if (i != rules.size()-1) res+=",";
}
MessageBoxInfo("JSON object", res);
});
@ -101,17 +132,21 @@ RouteItem::RouteItem(QWidget *parent, const std::shared_ptr<NekoGui::RoutingChai
connect(ui->rule_name, &QLineEdit::textChanged, this, [=](const QString& text) {
if (currentIndex == -1) return;
chain->Rules[currentIndex]->name = text;
updateRouteItemsView();
});
connect(ui->rule_attr_selector, &QComboBox::currentTextChanged, this, [=](const QString& text){
if (currentIndex == -1) return;
chain->Rules[currentIndex]->set_field_value(ui->rule_attr->currentText(), {text});
updateRulePreview();
});
connect(ui->rule_attr_text, &QTextEdit::textChanged, this, [=] {
if (currentIndex == -1) return;
auto currentVal = ui->rule_attr_text->toPlainText().split('\n');
chain->Rules[currentIndex]->set_field_value(ui->rule_attr->currentText(), currentVal);
if (ui->rule_attr->currentText() == "rule_set") updateHelperItems(currentVal.last());
updateRulePreview();
});
connect(ui->rule_out, &QComboBox::currentTextChanged, this, [=](const QString& text) {
@ -122,53 +157,56 @@ RouteItem::RouteItem(QWidget *parent, const std::shared_ptr<NekoGui::RoutingChai
return;
}
chain->Rules[currentIndex]->outboundID = id;
updateRulePreview();
});
connect(ui->route_items, &QListWidget::itemClicked, this, [=](const QListWidgetItem *item) {
auto idx = getIndexOf(item->text());
if (idx == -1) return;
currentIndex = idx;
auto ruleItem = chain->Rules[idx];
ui->rule_out->setCurrentText(get_outbound_name(ruleItem->outboundID));
setDefaultRuleData(ruleItem->ip_version);
updateRuleSection();
});
connect(ui->rule_attr, &QComboBox::currentTextChanged, this, [=](const QString& text){
if (currentIndex == -1) return;
ui->rule_attr_data->setTitle(text);
auto inputType = NekoGui::RouteRule::get_input_type(text);
switch (inputType) {
case NekoGui::trufalse: {
QStringList items = {"", "true", "false"};
auto currentValPtr = chain->Rules[currentIndex]->get_current_value_bool(text);
QString currentVal = currentValPtr == nullptr ? "" : *currentValPtr ? "true" : "false";
showSelectItem(items, currentVal);
break;
}
case NekoGui::select: {
auto items = NekoGui::RouteRule::get_values_for_field(text);
auto currentVal = chain->Rules[currentIndex]->get_current_value_string(text)[0];
showSelectItem(items, currentVal);
break;
}
case NekoGui::text: {
auto currentItems = chain->Rules[currentIndex]->get_current_value_string(text);
showTextEnterItem(currentItems);
break;
}
}
updateRuleSection();
});
connect(ui->new_route_item, &QPushButton::clicked, this, &RouteItem::on_new_route_item_clicked);
connect(ui->moveup_route_item, &QPushButton::clicked, this, &RouteItem::on_moveup_route_item_clicked);
connect(ui->movedown_route_item, &QPushButton::clicked, this, &RouteItem::on_movedown_route_item_clicked);
connect(ui->delete_route_item, &QPushButton::clicked, this, &RouteItem::on_delete_route_item_clicked);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, [=]{
accept();
});
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [=]{
QDialog::reject();
});
}
RouteItem::~RouteItem() {
delete ui;
}
void RouteItem::accept() {
if (chain->name == "") {
MessageBoxWarning("Invalid operation", "Cannot create Route Profile with empty name");
return;
}
int i=0;
for (const auto& item: chain->Rules) {
if (item->isEmpty()) {
chain->Rules.remove(i);
i--;
}
i++;
}
if (chain->Rules.empty()) {
MessageBoxInfo("Empty Route Profile", "No valid rules are in the profile");
return;
}
emit settingsChanged(chain);
QDialog::accept();
}
void RouteItem::updateRouteItemsView() {
ui->route_items->clear();
if (chain->Rules.empty()) return;
@ -176,7 +214,45 @@ void RouteItem::updateRouteItemsView() {
for (const auto& item: chain->Rules) {
ui->route_items->addItem(item->name);
}
ui->route_items->setCurrentRow(currentIndex);
if (currentIndex != -1) ui->route_items->setCurrentRow(currentIndex);
}
void RouteItem::updateRuleSection() {
if (currentIndex == -1) return;
auto ruleItem = chain->Rules[currentIndex];
auto currentAttr = ui->rule_attr->currentText();
switch (ruleItem->get_input_type(currentAttr)) {
case NekoGui::trufalse: {
QStringList items = {"false", "true"};
QString currentVal = chain->Rules[currentIndex]->get_current_value_bool(currentAttr);
showSelectItem(items, currentVal);
break;
}
case NekoGui::select: {
auto items = NekoGui::RouteRule::get_values_for_field(currentAttr);
auto currentVal = chain->Rules[currentIndex]->get_current_value_string(currentAttr)[0];
showSelectItem(items, currentVal);
break;
}
case NekoGui::text: {
auto currentItems = chain->Rules[currentIndex]->get_current_value_string(currentAttr);
showTextEnterItem(currentItems);
break;
}
}
ui->rule_name->setText(ruleItem->name);
ui->rule_attr_box->setEnabled(true);
if (currentAttr == "rule_set") ui->rule_set_helper->show();
else ui->rule_set_helper->hide();
updateRulePreview();
}
void RouteItem::updateRulePreview() {
if (currentIndex == -1) return;
ui->rule_preview->setText(QJsonObject2QString(chain->Rules[currentIndex]->get_rule_json(true), false));
}
void RouteItem::setDefaultRuleData(const QString& currentData) {
@ -189,9 +265,7 @@ void RouteItem::showSelectItem(const QStringList& items, const QString& currentI
ui->rule_attr_text->hide();
ui->rule_attr_selector->clear();
ui->rule_attr_selector->show();
QStringList fullItems = {""};
fullItems << items;
ui->rule_attr_selector->addItems(fullItems);
ui->rule_attr_selector->addItems(items);
ui->rule_attr_selector->setCurrentText(currentItem);
adjustSize();
}
@ -204,13 +278,34 @@ void RouteItem::showTextEnterItem(const QStringList& items) {
adjustSize();
}
void RouteItem::updateHelperItems(const QString& base) {
ui->rule_set_helper->clearSelection();
current_helper_items.clear();
for (const auto& item: geo_items) {
if (item.contains(base)) current_helper_items << item;
}
helperModel->setStringList(current_helper_items);
ui->rule_set_helper->setModel(helperModel);
}
void RouteItem::applyRuleHelperSelect(const QModelIndex& index) {
auto option = ui->rule_set_helper->model()->data(index, Qt::DisplayRole).toString();
auto currentText = ui->rule_attr_text->toPlainText();
auto parts = currentText.split('\n');
parts[parts.size() - 1] = option;
ui->rule_attr_text->setText(parts.join('\n'));
}
void RouteItem::on_new_route_item_clicked() {
auto routeItem = std::make_shared<NekoGui::RouteRule>(NekoGui::RouteRule());
auto routeItem = std::make_shared<NekoGui::RouteRule>();
routeItem->name = "rule_" + Int2String(++lastNum);
chain->Rules << routeItem;
currentIndex = chain->Rules.size() - 1;
ui->rule_name->setText(routeItem->name);
currentIndex = chain->Rules.size()-1;
updateRouteItemsView();
setDefaultRuleData("");
updateRuleSection();
}
void RouteItem::on_moveup_route_item_clicked() {

View File

@ -2,7 +2,8 @@
#include <QWidget>
#include <QListWidgetItem>
#include <QGroupBox>
#include <QDialog>
#include <QStringListModel>
#include "db/RouteEntity.h"
@ -12,7 +13,7 @@ namespace Ui {
}
QT_END_NAMESPACE
class RouteItem : public QGroupBox {
class RouteItem : public QDialog {
Q_OBJECT
public:
@ -21,13 +22,19 @@ public:
std::shared_ptr<NekoGui::RoutingChain> chain;
signals:
void settingsChanged(const std::shared_ptr<NekoGui::RoutingChain> routeChain);
void settingsChanged(std::shared_ptr<NekoGui::RoutingChain> routingChain);
private:
Ui::RouteItem *ui;
int currentIndex = -1;
int lastNum;
int lastNum = 0;
QStringList geo_items;
QStringList current_helper_items;
QStringListModel* helperModel;
[[nodiscard]] int getIndexOf(const QString& name) const;
@ -37,11 +44,19 @@ private:
void setDefaultRuleData(const QString& currentData);
void updateRuleSection();
void updateRulePreview();
void updateRouteItemsView();
void updateHelperItems(const QString& base);
void applyRuleHelperSelect(const QModelIndex& index);
private slots:
void on_ok_button_clicked();
void on_cancel_button_clicked();
void accept() override;
void on_new_route_item_clicked();
void on_moveup_route_item_clicked();
void on_movedown_route_item_clicked();

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RouteItem</class>
<widget class="QGroupBox" name="RouteItem">
<widget class="QDialog" name="RouteItem">
<property name="geometry">
<rect>
<x>0</x>
@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>GroupBox</string>
<string>Route Profile</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
@ -81,7 +81,7 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<widget class="QGroupBox" name="rule_attr_box">
<property name="title">
<string>Rule Attributes</string>
</property>
@ -128,9 +128,22 @@
<string>Name_Placeholder</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QTextBrowser" name="rule_preview">
<property name="minimumSize">
<size>
<width>190</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="rule_attr_text"/>
</item>
<item>
<widget class="QListView" name="rule_set_helper"/>
</item>
<item>
<widget class="QComboBox" name="rule_attr_selector"/>
</item>
@ -140,29 +153,16 @@
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_4" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QPushButton" name="ok_button">
<property name="text">
<string>Ok</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancel_button">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>