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
+
+
+
-