Implement sing-box json subsciption format

This commit is contained in:
Nova 2025-02-25 01:54:01 +03:30
parent 20998445ad
commit 387cc409ee
11 changed files with 351 additions and 0 deletions

View File

@ -208,6 +208,7 @@ set(PROJECT_SOURCES
include/ui/mainwindow_interface.h
include/stats/connections/connectionLister.hpp
src/stats/connectionLister/connectionLister.cpp
src/configs/proxy/Json2Bean.cpp
)
# Qt exe

View File

@ -117,6 +117,8 @@ namespace NekoGui_fmt {
bool TryParseLink(const QString &link);
bool TryParseJson(const QJsonObject &obj);
QString ToShareLink() override;
};
} // namespace NekoGui_fmt

View File

@ -31,6 +31,8 @@ namespace NekoGui_fmt {
bool TryParseLink(const QString &link);
bool TryParseJson(const QJsonObject &obj);
QString ToShareLink() override;
};
}

View File

@ -32,6 +32,8 @@ namespace NekoGui_fmt {
bool TryParseLink(const QString &link);
bool TryParseJson(const QJsonObject &obj);
QString ToShareLink() override;
};
} // namespace NekoGui_fmt

View File

@ -30,6 +30,8 @@ namespace NekoGui_fmt {
bool TryParseLink(const QString &link);
bool TryParseJson(const QJsonObject &obj);
QString ToShareLink() override;
};
} // namespace NekoGui_fmt

View File

@ -29,6 +29,8 @@ namespace NekoGui_fmt {
bool TryParseLink(const QString &link);
bool TryParseJson(const QJsonObject &obj);
QString ToShareLink() override;
};
} // namespace NekoGui_fmt

View File

@ -26,6 +26,8 @@ namespace NekoGui_fmt {
bool TryParseLink(const QString &link);
bool TryParseJson(const QJsonObject &obj);
QString ToShareLink() override;
};
} // namespace NekoGui_fmt

View File

@ -44,6 +44,8 @@ namespace NekoGui_fmt {
bool TryParseLink(const QString &link);
bool TryParseJson(const QJsonObject &obj);
QString ToShareLink() override;
};
} // namespace NekoGui_fmt

View File

@ -9,6 +9,8 @@ namespace NekoGui_sub {
void update(const QString &str, bool needParse);
void updateSingBox(const QString &str);
int gid_add_to = -1;
QList<std::shared_ptr<NekoGui::ProxyEntity>> updated_order;

View File

@ -0,0 +1,226 @@
#include <include/configs/proxy/QUICBean.hpp>
#include <include/configs/proxy/ShadowSocksBean.hpp>
#include <include/configs/proxy/SocksHttpBean.hpp>
#include <include/configs/proxy/TrojanVLESSBean.hpp>
#include <include/configs/proxy/VMessBean.hpp>
#include <include/configs/proxy/WireguardBean.h>
#include "include/configs/proxy/SSHBean.h"
namespace NekoGui_fmt
{
bool QUICBean::TryParseJson(const QJsonObject& obj)
{
auto type = obj["type"].toString();
if (type == "hysteria")
{
name = obj["tag"].toString();
proxy_type = proxy_Hysteria;
serverAddress = obj["server"].isString() ? obj["server"].toString() : "127.0.0.1";
serverPort = obj["server_port"].isDouble() ? obj["port"].toInt() : 1080;
hop_interval = obj["hop_interval"].toString();
uploadMbps = obj["up_mbps"].isDouble() ? obj["up_mbps"].toInt() : 0;
downloadMbps = obj["down_mbps"].isDouble() ? obj["down_mbps"].toInt() : 0;
obfsPassword = obj["obfs"].toString();
authPayloadType = obj["auth"].isString() ? hysteria_auth_base64 : hysteria_auth_string;
authPayload = obj["auth"].isString() ? obj["auth"].toString() : obj["auth_str"].toString();
disableMtuDiscovery = obj["disable_mtu_discovery"].toBool();
connectionReceiveWindow = obj["recv_window_conn"].toInt();
streamReceiveWindow = obj["recv_window"].toInt();
alpn = obj["tls"].toObject()["alpn"].isArray() ? QJsonArray2QListString(obj["tls"].toObject()["alpn"].toArray()).join(",") : obj["tls"].toObject()["alpn"].toString();
sni = obj["tls"].toObject()["server_name"].toString();
disableSni = obj["tls"].toObject()["disable_sni"].toBool();
allowInsecure = obj["tls"].toObject()["insecure"].toBool();
return true;
}
if (type == "hysteria2")
{
name = obj["tag"].toString();
proxy_type = proxy_Hysteria2;
serverAddress = obj["server"].isString() ? obj["server"].toString() : "127.0.0.1";
serverPort = obj["server_port"].isDouble() ? obj["port"].toInt() : 1080;
serverPorts = obj["server_ports"].isArray() ? QJsonArray2QListString(obj["server_ports"].toArray()) : QStringList();
hop_interval = obj["hop_interval"].toString();
uploadMbps = obj["up_mbps"].isDouble() ? obj["up_mbps"].toInt() : 0;
downloadMbps = obj["down_mbps"].isDouble() ? obj["down_mbps"].toInt() : 0;
password = obj["password"].toString();
obfsPassword = obj["obfs"].toObject()["password"].toString();
alpn = obj["tls"].toObject()["alpn"].isArray() ? QJsonArray2QListString(obj["tls"].toObject()["alpn"].toArray()).join(",") : obj["tls"].toObject()["alpn"].toString();
sni = obj["tls"].toObject()["server_name"].toString();
disableSni = obj["tls"].toObject()["disable_sni"].toBool();
allowInsecure = obj["tls"].toObject()["insecure"].toBool();
return true;
}
if (type == "tuic")
{
name = obj["tag"].toString();
proxy_type = proxy_TUIC;
serverAddress = obj["server"].isString() ? obj["server"].toString() : "127.0.0.1";
serverPort = obj["server_port"].isDouble() ? obj["port"].toInt() : 1080;
uuid = obj["uuid"].toString();
password = obj["password"].toString();
congestionControl = obj["congestion_control"].toString();
udpRelayMode = obj["udp_relay_mode"].toString();
uos = obj["udp_over_stream"].toBool();
zeroRttHandshake = obj["zero_rtt_handshake"].toBool();
heartbeat = obj["heartbeat"].toString();
alpn = obj["tls"].toObject()["alpn"].isArray() ? QJsonArray2QListString(obj["tls"].toObject()["alpn"].toArray()).join(",") : obj["tls"].toObject()["alpn"].toString();
sni = obj["tls"].toObject()["server_name"].toString();
disableSni = obj["tls"].toObject()["disable_sni"].toBool();
allowInsecure = obj["tls"].toObject()["insecure"].toBool();
return true;
}
return false;
}
bool ShadowSocksBean::TryParseJson(const QJsonObject& obj)
{
name = obj["tag"].toString();
serverAddress = obj["server"].toString();
serverPort = obj["server_port"].toInt();
method = obj["method"].toString();
password = obj["password"].toString();
plugin = obj["plugin"].toString();
uot = obj["udp_over_tcp"].toBool();
mux_state = obj["multiplex"].isObject() ? (obj["multiplex"].toObject()["enabled"].toBool() ? 1 : 2) : 0;
return true;
}
bool SocksHttpBean::TryParseJson(const QJsonObject& obj)
{
name = obj["tag"].toString();
socks_http_type = obj["type"] == "http" ? type_HTTP : type_Socks5;
serverAddress = obj["server"].toString();
serverPort = obj["server_port"].toInt();
username = obj["username"].toString();
password = obj["password"].toString();
stream->security = obj["tls"].isObject() ? "tls" : "";
stream->sni = obj["tls"].toObject()["server_name"].toString();
stream->allow_insecure = obj["tls"].toObject()["insecure"].toBool();
return true;
}
bool SSHBean::TryParseJson(const QJsonObject& obj)
{
name = obj["tag"].toString();
serverAddress = obj["server"].toString();
serverPort = obj["server_port"].toInt();
user = obj["user"].toString();
password = obj["password"].toString();
privateKey = obj["private_key"].toString();
privateKeyPath = obj["private_key_path"].toString();
privateKeyPass = obj["private_key_passphrase"].toString();
hostKey = QJsonArray2QListString(obj["host_key"].toArray());
hostKeyAlgs = QJsonArray2QListString(obj["host_key_algorithms"].toArray());
clientVersion = obj["client_version"].toString();
return true;
}
bool TrojanVLESSBean::TryParseJson(const QJsonObject& obj)
{
name = obj["tag"].toString();
proxy_type = obj["type"].toString() == "trojan" ? proxy_Trojan : proxy_VLESS;
serverAddress = obj["server"].toString();
serverPort = obj["server_port"].toInt();
password = obj["password"].toString();
if (proxy_type == proxy_VLESS) password = obj["uuid"].toString();
flow = obj["flow"].toString();
stream->packet_encoding = obj["packet_encoding"].toString();
mux_state = obj["multiplex"].isObject() ? (obj["multiplex"].toObject()["enabled"].toBool() ? 1 : 2) : 0;
stream->security = obj["tls"].isObject() ? "tls" : "";
if (obj["tls"].toObject()["reality"].toObject()["enabled"].toBool())
{
stream->security = "reality";
}
stream->reality_pbk = obj["tls"].toObject()["reality"].toObject()["public_key"].toString();
stream->reality_sid = obj["tls"].toObject()["reality"].toObject()["short_id"].toString();
stream->utlsFingerprint = obj["tls"].toObject()["utls"].toObject()["fingerprint"].toString();
stream->sni = obj["tls"].toObject()["server_name"].toString();
stream->alpn = obj["tls"].toObject()["alpn"].isArray() ? QJsonArray2QListString(obj["tls"].toObject()["alpn"].toArray()).join(",") : obj["tls"].toObject()["alpn"].toString();
stream->allow_insecure = obj["tls"].toObject()["insecure"].toBool();
stream->network = obj["transport"].toObject()["type"].toString();
if (stream->network == "ws")
{
stream->path = obj["transport"].toObject()["path"].toString();
stream->host = obj["transport"].toObject()["host"].toString();
} else if (stream->network == "http")
{
stream->path = obj["transport"].toObject()["path"].toString();
stream->host = obj["transport"].toObject()["host"].toString();
stream->method = obj["transport"].toObject()["method"].toString();
} else if (stream->network == "httpupgrade")
{
stream->path = obj["transport"].toObject()["path"].toString();
stream->host = obj["transport"].toObject()["host"].toString();
} else if (stream->network == "grpc")
{
stream->path = obj["transport"].toObject()["service_name"].toString();
}
return true;
}
bool VMessBean::TryParseJson(const QJsonObject& obj)
{
name = obj["tag"].toString();
serverAddress = obj["server"].toString();
serverPort = obj["server_port"].toInt();
uuid = obj["uuid"].toString();
security = obj["security"].toString();
aid = obj["alter_id"].toInt();
stream->packet_encoding = obj["packet_encoding"].toString();
mux_state = obj["multiplex"].isObject() ? (obj["multiplex"].toObject()["enabled"].toBool() ? 1 : 2) : 0;
stream->security = obj["tls"].isObject() ? "tls" : "";
if (obj["tls"].toObject()["reality"].toObject()["enabled"].toBool())
{
stream->security = "reality";
}
stream->reality_pbk = obj["tls"].toObject()["reality"].toObject()["public_key"].toString();
stream->reality_sid = obj["tls"].toObject()["reality"].toObject()["short_id"].toString();
stream->utlsFingerprint = obj["tls"].toObject()["utls"].toObject()["fingerprint"].toString();
stream->sni = obj["tls"].toObject()["server_name"].toString();
stream->alpn = obj["tls"].toObject()["alpn"].isArray() ? QJsonArray2QListString(obj["tls"].toObject()["alpn"].toArray()).join(",") : obj["tls"].toObject()["alpn"].toString();
stream->allow_insecure = obj["tls"].toObject()["insecure"].toBool();
stream->network = obj["transport"].toObject()["type"].toString();
if (stream->network == "ws")
{
stream->path = obj["transport"].toObject()["path"].toString();
stream->host = obj["transport"].toObject()["host"].toString();
} else if (stream->network == "http")
{
stream->path = obj["transport"].toObject()["path"].toString();
stream->host = obj["transport"].toObject()["host"].toString();
stream->method = obj["transport"].toObject()["method"].toString();
} else if (stream->network == "httpupgrade")
{
stream->path = obj["transport"].toObject()["path"].toString();
stream->host = obj["transport"].toObject()["host"].toString();
} else if (stream->network == "grpc")
{
stream->path = obj["transport"].toObject()["service_name"].toString();
}
return true;
}
bool WireguardBean::TryParseJson(const QJsonObject& obj)
{
name = obj["tag"].toString();
auto peers = obj["peers"].toArray();
if (peers.empty()) return false;
serverAddress = peers[0].toObject()["address"].toString();
serverPort = peers[0].toObject()["server_port"].toInt();
publicKey = peers[0].toObject()["public_key"].toString();
reserved = QJsonArray2QListInt(peers[0].toObject()["reserved"].toArray());
workerCount = obj["workers"].toInt();
privateKey = obj["private_key"].toString();
localAddress = QJsonArray2QListString(obj["address"].toArray());
MTU = obj["mtu"].toInt();
useSystemInterface = obj["system"].toBool();
return true;
}
}

View File

@ -6,6 +6,7 @@
#include <QInputDialog>
#include <QUrlQuery>
#include <absl/strings/internal/str_format/extension.h>
#ifndef NKR_NO_YAML
@ -84,6 +85,13 @@ namespace NekoGui_sub {
return;
}
// SingBox
if (str.contains("outbounds") || str.contains("endpoints"))
{
updateSingBox(str);
return;
}
// Multi line
if (str.count("\n") > 0 && needParse) {
auto list = Disect(str);
@ -222,6 +230,106 @@ namespace NekoGui_sub {
updated_order += ent;
}
void RawUpdater::updateSingBox(const QString& str)
{
auto json = QString2QJsonObject(str);
auto outbounds = json["outbounds"].toArray() + json["endpoints"].toArray();
for (auto o : outbounds)
{
auto out = o.toObject();
if (out.isEmpty())
{
MW_show_log("invalid outbound: " + o.toVariant().toString());
continue;
}
std::shared_ptr<NekoGui::ProxyEntity> ent;
// SOCKS
if (out["type"] == "socks") {
ent = NekoGui::ProfileManager::NewProxyEntity("socks");
auto ok = ent->SocksHTTPBean()->TryParseJson(out);
if (!ok) continue;
}
// HTTP
if (out["type"] == "http") {
auto ok = ent->SocksHTTPBean()->TryParseJson(out);
if (!ok) continue;
}
// ShadowSocks
if (out["type"] == "shadowsocks") {
ent = NekoGui::ProfileManager::NewProxyEntity("shadowsocks");
auto ok = ent->ShadowSocksBean()->TryParseJson(out);
if (!ok) continue;
}
// VMess
if (out["type"] == "vmess") {
ent = NekoGui::ProfileManager::NewProxyEntity("vmess");
auto ok = ent->VMessBean()->TryParseJson(out);
if (!ok) continue;
}
// VLESS
if (out["type"] == "vless") {
ent = NekoGui::ProfileManager::NewProxyEntity("vless");
auto ok = ent->TrojanVLESSBean()->TryParseJson(out);
if (!ok) continue;
}
// Trojan
if (out["type"] == "trojan") {
ent = NekoGui::ProfileManager::NewProxyEntity("trojan");
auto ok = ent->TrojanVLESSBean()->TryParseJson(out);
if (!ok) continue;
}
// Hysteria1
if (out["type"] == "hysteria") {
ent = NekoGui::ProfileManager::NewProxyEntity("hysteria");
auto ok = ent->QUICBean()->TryParseJson(out);
if (!ok) continue;
}
// Hysteria2
if (out["type"] == "hysteria2") {
ent = NekoGui::ProfileManager::NewProxyEntity("hysteria2");
auto ok = ent->QUICBean()->TryParseJson(out);
if (!ok) continue;
}
// TUIC
if (out["type"] == "tuic") {
ent = NekoGui::ProfileManager::NewProxyEntity("tuic");
auto ok = ent->QUICBean()->TryParseJson(out);
if (!ok) continue;
}
// Wireguard
if (out["type"] == "wireguard") {
ent = NekoGui::ProfileManager::NewProxyEntity("wireguard");
auto ok = ent->WireguardBean()->TryParseJson(out);
if (!ok) continue;
}
// SSH
if (out["type"] == "ssh") {
ent = NekoGui::ProfileManager::NewProxyEntity("ssh");
auto ok = ent->SSHBean()->TryParseJson(out);
if (!ok) continue;
}
if (ent == nullptr) continue;
NekoGui::profileManager->AddProfile(ent, gid_add_to);
updated_order += ent;
}
}
#ifndef NKR_NO_YAML
QString Node2QString(const YAML::Node &n, const QString &def = "") {