From 1c6fe29ea64840a1b22fb6cd2c7fa0a915fe5559 Mon Sep 17 00:00:00 2001 From: HystericalDragon <138737572+HystericalDragon@users.noreply.github.com> Date: Mon, 14 Aug 2023 09:00:53 +0800 Subject: [PATCH] feat: add ducksoft link for VMess --- fmt/Bean2Link.cpp | 80 ++++++++++++++++++++++++++++-------- fmt/Link2Bean.cpp | 46 ++++++++++++++++++++- main/NekoGui_DataStore.hpp | 1 + translations/zh_CN.ts | 8 ++++ ui/dialog_basic_settings.cpp | 2 + ui/dialog_basic_settings.ui | 10 +++++ 6 files changed, 128 insertions(+), 19 deletions(-) diff --git a/fmt/Bean2Link.cpp b/fmt/Bean2Link.cpp index 19257aac8..848ecd3b6 100644 --- a/fmt/Bean2Link.cpp +++ b/fmt/Bean2Link.cpp @@ -1,4 +1,3 @@ -#include "QUICBean.hpp" #include "db/ProxyEntity.hpp" #include "fmt/includes.h" @@ -99,22 +98,69 @@ namespace NekoGui_fmt { } QString VMessBean::ToShareLink() { - QJsonObject N{ - {"v", "2"}, - {"ps", name}, - {"add", serverAddress}, - {"port", Int2String(serverPort)}, - {"id", uuid}, - {"aid", Int2String(aid)}, - {"net", stream->network}, - {"host", stream->host}, - {"path", stream->path}, - {"type", stream->header_type}, - {"scy", security}, - {"tls", stream->security == "tls" ? "tls" : ""}, - {"sni", stream->sni}, - }; - return "vmess://" + QJsonObject2QString(N, true).toUtf8().toBase64(); + if (NekoGui::dataStore->old_share_link_format) { + // v2rayN format + QJsonObject N{ + {"v", "2"}, + {"ps", name}, + {"add", serverAddress}, + {"port", Int2String(serverPort)}, + {"id", uuid}, + {"aid", Int2String(aid)}, + {"net", stream->network}, + {"host", stream->host}, + {"path", stream->path}, + {"type", stream->header_type}, + {"scy", security}, + {"tls", stream->security == "tls" ? "tls" : ""}, + {"sni", stream->sni}, + }; + return "vmess://" + QJsonObject2QString(N, true).toUtf8().toBase64(); + } else { + // ducksoft format + QUrl url; + QUrlQuery query; + url.setScheme("vmess"); + url.setUserName(uuid); + url.setHost(serverAddress); + url.setPort(serverPort); + if (!name.isEmpty()) url.setFragment(name); + + query.addQueryItem("encryption", security); + + // security + auto security = stream->security; + if (security == "tls" && !stream->reality_pbk.trimmed().isEmpty()) security = "reality"; + query.addQueryItem("security", security); + + if (!stream->sni.isEmpty()) query.addQueryItem("sni", stream->sni); + if (stream->allow_insecure) query.addQueryItem("allowInsecure", "1"); + if (!stream->utlsFingerprint.isEmpty()) query.addQueryItem("fp", stream->utlsFingerprint); + + if (security == "reality") { + query.addQueryItem("pbk", stream->reality_pbk); + if (!stream->reality_sid.isEmpty()) query.addQueryItem("sid", stream->reality_sid); + if (!stream->reality_spx.isEmpty()) query.addQueryItem("spx", stream->reality_spx); + } + + // type + query.addQueryItem("type", stream->network); + + if (stream->network == "ws" || stream->network == "http") { + if (!stream->path.isEmpty()) query.addQueryItem("path", stream->path); + if (!stream->host.isEmpty()) query.addQueryItem("host", stream->host); + } else if (stream->network == "grpc") { + if (!stream->path.isEmpty()) query.addQueryItem("serviceName", stream->path); + } else if (stream->network == "tcp") { + if (stream->header_type == "http") { + query.addQueryItem("headerType", "http"); + query.addQueryItem("host", stream->host); + } + } + + url.setQuery(query); + return url.toString(QUrl::FullyEncoded); + } } QString NaiveBean::ToShareLink() { diff --git a/fmt/Link2Bean.cpp b/fmt/Link2Bean.cpp index dcc3d9aaf..443d9435a 100644 --- a/fmt/Link2Bean.cpp +++ b/fmt/Link2Bean.cpp @@ -1,4 +1,3 @@ -#include "QUICBean.hpp" #include "db/ProxyEntity.hpp" #include "fmt/includes.h" @@ -153,9 +152,52 @@ namespace NekoGui_fmt { stream->security = objN["tls"].toString(); // TODO quic & kcp return true; + } else { + // https://github.com/XTLS/Xray-core/discussions/716 + auto url = QUrl(link); + if (!url.isValid()) return false; + auto query = GetQuery(url); + + name = url.fragment(QUrl::FullyDecoded); + serverAddress = url.host(); + serverPort = url.port(); + uuid = url.userName(); + if (serverPort == -1) serverPort = 443; + + aid = 0; // “此分享标准仅针对 VMess AEAD 和 VLESS。” + security = GetQueryValue(query, "encryption", "auto"); + + // security + stream->network = GetQueryValue(query, "type", "tcp"); + stream->security = GetQueryValue(query, "security", "tls").replace("reality", "tls"); + auto sni1 = GetQueryValue(query, "sni"); + auto sni2 = GetQueryValue(query, "peer"); + if (!sni1.isEmpty()) stream->sni = sni1; + if (!sni2.isEmpty()) stream->sni = sni2; + if (!query.queryItemValue("allowInsecure").isEmpty()) stream->allow_insecure = true; + stream->reality_pbk = GetQueryValue(query, "pbk", ""); + stream->reality_sid = GetQueryValue(query, "sid", ""); + stream->reality_spx = GetQueryValue(query, "spx", ""); + stream->utlsFingerprint = GetQueryValue(query, "fp", ""); + + // type + if (stream->network == "ws") { + stream->path = GetQueryValue(query, "path", ""); + stream->host = GetQueryValue(query, "host", ""); + } else if (stream->network == "http") { + stream->path = GetQueryValue(query, "path", ""); + stream->host = GetQueryValue(query, "host", "").replace("|", ","); + } else if (stream->network == "grpc") { + stream->path = GetQueryValue(query, "serviceName", ""); + } else if (stream->network == "tcp") { + if (GetQueryValue(query, "headerType") == "http") { + stream->header_type = "http"; + stream->host = GetQueryValue(query, "host", ""); + } + } + return !(uuid.isEmpty() || serverAddress.isEmpty()); } - // Std Format return false; } diff --git a/main/NekoGui_DataStore.hpp b/main/NekoGui_DataStore.hpp index 8508b4fa0..7ccf53719 100644 --- a/main/NekoGui_DataStore.hpp +++ b/main/NekoGui_DataStore.hpp @@ -97,6 +97,7 @@ namespace NekoGui { QString test_download_url = "http://cachefly.cachefly.net/10mb.test"; int test_download_timeout = 30; int test_concurrent = 5; + bool old_share_link_format = true; int traffic_loop_interval = 1000; bool connection_statistics = false; int current_group = 0; // group id diff --git a/translations/zh_CN.ts b/translations/zh_CN.ts index b4bb953e3..e1eb90b23 100644 --- a/translations/zh_CN.ts +++ b/translations/zh_CN.ts @@ -163,6 +163,14 @@ Advanced system proxy settings. Please select a format. 高级系统代理设置。请选择一种格式。 + + Old Share Link Format + 旧分享链接格式 + + + Share VMess Link with v2rayN Format + 用 v2rayN 的格式分享 VMess 链接 + Clear servers before updating subscription 更新订阅前清除服务器 diff --git a/ui/dialog_basic_settings.cpp b/ui/dialog_basic_settings.cpp index 18244e087..19939e128 100644 --- a/ui/dialog_basic_settings.cpp +++ b/ui/dialog_basic_settings.cpp @@ -81,6 +81,7 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent) D_LOAD_INT(test_download_timeout) D_LOAD_STRING(test_latency_url) D_LOAD_STRING(test_download_url) + D_LOAD_BOOL(old_share_link_format) connect(ui->custom_inbound_edit, &QPushButton::clicked, this, [=] { C_EDIT_JSON_ALLOW_EMPTY(custom_inbound) @@ -272,6 +273,7 @@ void DialogBasicSettings::accept() { D_SAVE_INT(test_download_timeout) D_SAVE_STRING(test_latency_url) D_SAVE_STRING(test_download_url) + D_SAVE_BOOL(old_share_link_format) // Style diff --git a/ui/dialog_basic_settings.ui b/ui/dialog_basic_settings.ui index df407c2df..2fe47e32a 100644 --- a/ui/dialog_basic_settings.ui +++ b/ui/dialog_basic_settings.ui @@ -213,6 +213,16 @@ + + + + Share VMess Link with v2rayN Format + + + Old Share Link Format + + +