Browse Source

Merge pull request #1808 from zxlhhyccc/tuic

luci-app-ssr-plus: Add `TUIC` configuration import and subscribe.
zxl hhyccc 1 week ago
parent
commit
82d135eb27

+ 1 - 1
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua

@@ -264,7 +264,7 @@ s = m:section(NamedSection, sid, "servers")
 s.anonymous = true
 s.addremove = false
 
-o = s:option(DummyValue, "ssr_url", "SS/SSR/V2RAY/TROJAN/HYSTERIA2 URL")
+o = s:option(DummyValue, "ssr_url", "SS/SSR/V2RAY/TROJAN/TUIC/HYSTERIA2 URL")
 o.rawhtml = true
 o.template = "shadowsocksr/ssrurl"
 o.value = sid

+ 1 - 1
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua

@@ -27,7 +27,7 @@ local function is_finded(e)
 	return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= ""
 end
 
-m = Map("shadowsocksr", translate("ShadowSocksR Plus+ Settings"), translate("<h3>Support SS/SSR/V2RAY/XRAY/TROJAN/NAIVEPROXY/SOCKS5/TUN etc.</h3>"))
+m = Map("shadowsocksr", translate("ShadowSocksR Plus+ Settings"), translate("<h3>Support SS/SSR/V2RAY/XRAY/TROJAN/TUIC/HYSTERIA2/NAIVEPROXY/SOCKS5/TUN etc.</h3>"))
 m:section(SimpleSection).template = "shadowsocksr/status"
 
 local server_table = {}

+ 52 - 0
luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm

@@ -821,6 +821,58 @@ function import_ssr_url(btn, urlname, sid) {
 			}
 			s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
 			return false;
+		case "tuic":
+			var url0 = (ssu[1] || "");
+			var param = "";
+
+			// 先分离 #(alias)
+			var hashIndex = url0.indexOf("#");
+			if (hashIndex >= 0) {
+				param = url0.substring(hashIndex + 1);
+				url0 = url0.substring(0, hashIndex);
+			}
+
+			// 再分离 ? 或 /?(参数)
+			var queryIndex = (url0 = url0.replace('/?', '?')).indexOf("?");
+			var queryStr = "";
+			if (queryIndex >= 0) {
+				queryStr = url0.substring(queryIndex + 1);
+				url0 = url0.substring(0, queryIndex);
+			}
+
+			var params = Object.fromEntries(new URLSearchParams(queryStr));
+
+			var sipIndex = url0.indexOf("@");
+			var userInfo = url0.substring(0, sipIndex);      // 格式:uuid:password
+			var hostPart = url0.substring(sipIndex + 1);     // 格式:hostname:port
+			var userInfoSplitIndex = userInfo.indexOf(":");
+			if(userInfoSplitIndex < 0) {
+				// 格式错误
+				s.innerHTML = "<font style='color:red'><%:Userinfo format error.%></font>";
+				break;
+			}
+
+			var method = userInfo.substring(0, userInfoSplitIndex);
+			var password = userInfo.substring(userInfoSplitIndex + 1);
+
+			var url = new URL("http://" + hostPart); // 用 URL 提取 host 与 port
+
+			document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = ssu[0];
+			document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
+			document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = url.hostname;
+			document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port;
+			document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_uuid')[0].value = method;
+			document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_ip')[0].value = params.sni || "";
+			document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_passwd')[0].value = password;
+			document.getElementsByName('cbid.shadowsocksr.' + sid + '.udp_relay_mode')[0].value = params.udp_relay_mode || "native";
+			document.getElementsByName('cbid.shadowsocksr.' + sid + '.congestion_control')[0].value = params.congestion_control || "cubic";
+			document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_alpn')[0].value = params.alpn || "";
+
+			if (param != undefined) {
+				document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = decodeURIComponent(param);
+			}
+			s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
+			return false;
 		default:
 			s.innerHTML = "<font style=\'color:red\'><%:Invalid format.%></font>";
 			return false;

+ 18 - 13
luci-app-ssr-plus/po/templates/ssr-plus.pot

@@ -75,7 +75,9 @@ msgid "<font><b>"
 msgstr ""
 
 #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:30
-msgid "<h3>Support SS/SSR/V2RAY/XRAY/TROJAN/NAIVEPROXY/SOCKS5/TUN etc.</h3>"
+msgid ""
+"<h3>Support SS/SSR/V2RAY/XRAY/TROJAN/TUIC/HYSTERIA2/NAIVEPROXY/SOCKS5/TUN "
+"etc.</h3>"
 msgstr ""
 
 #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:151
@@ -219,7 +221,7 @@ msgstr ""
 msgid "Baidu Public DNS (180.76.76.76)"
 msgstr ""
 
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:233
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:223
 msgid "Base64 sstr failed."
 msgstr ""
 
@@ -973,17 +975,19 @@ msgstr ""
 msgid "If you have a self-signed certificate,please check the box"
 msgstr ""
 
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:819
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:883
 msgid "Import"
 msgstr ""
 
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:177
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:320
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:352
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:448
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:535
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:665
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:810
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:165
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:310
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:452
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:485
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:516
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:604
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:692
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:822
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:874
 msgid "Import configuration information successfully."
 msgstr ""
 
@@ -1003,7 +1007,7 @@ msgstr ""
 msgid "Invalid JSON format"
 msgstr ""
 
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:813
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:877
 msgid "Invalid format."
 msgstr ""
 
@@ -1590,7 +1594,7 @@ msgstr ""
 msgid "Running Mode"
 msgstr ""
 
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:255
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:245
 msgid "SS URL base64 sstr format not recognized."
 msgstr ""
 
@@ -2063,7 +2067,8 @@ msgstr ""
 msgid "User-Agent"
 msgstr ""
 
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:210
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:200
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:851
 msgid "Userinfo format error."
 msgstr ""
 

+ 22 - 15
luci-app-ssr-plus/po/zh_Hans/ssr-plus.po

@@ -77,8 +77,12 @@ msgid "<font><b>"
 msgstr ""
 
 #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:30
-msgid "<h3>Support SS/SSR/V2RAY/XRAY/TROJAN/NAIVEPROXY/SOCKS5/TUN etc.</h3>"
-msgstr "<h3>支持 SS/SSR/V2RAY/XRAY/TROJAN/NAIVEPROXY/SOCKS5/TUN 等协议。</h3>"
+msgid ""
+"<h3>Support SS/SSR/V2RAY/XRAY/TROJAN/TUIC/HYSTERIA2/NAIVEPROXY/SOCKS5/TUN "
+"etc.</h3>"
+msgstr ""
+"<h3>支持 SS/SSR/V2RAY/XRAY/TROJAN/TUIC/HYSTERIA2/NAIVEPROXY/SOCKS5/TUN 等协"
+"议。</h3>"
 
 #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:151
 #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:177
@@ -221,7 +225,7 @@ msgstr "【百度】连通性检查"
 msgid "Baidu Public DNS (180.76.76.76)"
 msgstr ""
 
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:233
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:223
 msgid "Base64 sstr failed."
 msgstr "Base64 解码失败。"
 
@@ -984,17 +988,19 @@ msgstr ""
 msgid "If you have a self-signed certificate,please check the box"
 msgstr "如果你使用自签证书,请选择"
 
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:819
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:883
 msgid "Import"
 msgstr "导入配置信息"
 
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:177
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:320
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:352
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:448
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:535
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:665
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:810
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:165
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:310
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:452
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:485
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:516
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:604
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:692
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:822
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:874
 msgid "Import configuration information successfully."
 msgstr "导入配置信息成功。"
 
@@ -1014,7 +1020,7 @@ msgstr "接口控制"
 msgid "Invalid JSON format"
 msgstr "无效的 JSON 格式"
 
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:813
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:877
 msgid "Invalid format."
 msgstr "无效的格式。"
 
@@ -1603,7 +1609,7 @@ msgstr "运行中"
 msgid "Running Mode"
 msgstr "运行模式"
 
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:255
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:245
 msgid "SS URL base64 sstr format not recognized."
 msgstr "无法识别 SS URL 的 Base64 格式。"
 
@@ -1799,7 +1805,7 @@ msgstr "订阅节点关键字保留检查"
 
 #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:130
 msgid "Subscribe URL"
-msgstr "SS/SSR/V2/TROJAN 订阅 URL"
+msgstr "SS/SSR/V2/TROJAN/HY2/TUIC 订阅 URL"
 
 #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:157
 msgid "Subscribe new add server default Auto-Switch on"
@@ -2079,7 +2085,8 @@ msgstr "用户已取消。"
 msgid "User-Agent"
 msgstr "用户代理(User-Agent)"
 
-#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:210
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:200
+#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:851
 msgid "Userinfo format error."
 msgstr "用户信息格式错误。"
 

+ 86 - 2
luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua

@@ -42,6 +42,7 @@ local v2_ss = luci.sys.exec('type -t -p ' .. ss_program .. ' 2>/dev/null') ~= ""
 local has_ss_type = luci.sys.exec('type -t -p ' .. ss_program .. ' 2>/dev/null') ~= "" and ss_type
 local v2_tj = luci.sys.exec('type -t -p trojan') ~= "" and "trojan" or "v2ray"
 local hy2_type = luci.sys.exec('type -t -p hysteria') ~= "" and "hysteria2"
+local tuic_type = luci.sys.exec('type -t -p tuic-client') ~= "" and "tuic"
 local log = function(...)
 	print(os.date("%Y-%m-%d %H:%M:%S ") .. table.concat({...}, " "))
 end
@@ -711,7 +712,19 @@ local function processData(szType, content)
 			idx_sp = content:find("#")
 			alias = content:sub(idx_sp + 1, -1)
 		end
-		local info = content:sub(1, idx_sp > 0 and idx_sp - 1 or #content)
+		local info = content:sub(1, idx_sp > 0 and idx_sp - 1 or #content):gsub("/%?", "?")
+		local paramStr = ""
+
+		if info:find("?") then
+			paramStr = info:match("%?(.*)$") or ""
+			info = info:gsub("%?.*$", "") -- 去掉 ? 后的参数部分
+		end
+
+		-- 解析 query 参数(key=value&key2=value2)
+		for k, v in paramStr:gmatch("([^&=?]+)=([^&=?]+)") do
+			params[k] = UrlDecode(v)
+		end
+
 		local hostInfo = split(info, "@")
 
 		-- 基础验证
@@ -951,6 +964,77 @@ local function processData(szType, content)
 				result.tcp_path = params.path and UrlDecode(params.path) or nil
 			end
 		end
+	elseif szType == "tuic" then
+		local params = {}
+		local idx_sp = 0
+		local alias = ""
+
+		-- 提取别名(如果存在)
+		if content:find("#") then
+			idx_sp = content:find("#")
+			alias = content:sub(idx_sp + 1, -1)
+		end
+		local info = content:sub(1, idx_sp > 0 and idx_sp - 1 or #content):gsub("/%?", "?")
+		local paramStr = ""
+
+		if info:find("?") then
+			paramStr = info:match("%?(.*)$") or ""
+			info = info:gsub("%?.*$", "") -- 去掉 ? 后的参数部分
+		end
+
+		-- 解析 query 参数(key=value&key2=value2)
+		for k, v in paramStr:gmatch("([^&=?]+)=([^&=?]+)") do
+			params[k] = UrlDecode(v)
+		end
+
+		local hostInfo = split(info, "@")
+		-- 基础验证
+		if #hostInfo < 2 then
+			log("TUIC 节点格式错误: 缺少 @")
+			return nil
+		end
+
+		local userinfo = hostInfo[1]
+		local hostPort = hostInfo[2]
+
+		-- 分离 uuid 和 password
+		local userInfoSplit = split(userinfo, ":")
+		if #userInfoSplit < 2 then
+			log("TUIC 节点格式错误: 用户信息不完整")
+			return nil
+		end
+		local uuid = userInfoSplit[1]
+		local password = userInfoSplit[2]
+	
+		-- 分离服务器地址和端口
+		local hostParts = split(hostPort, ":")
+		-- 验证服务器地址和端口
+		if #hostParts < 2 then
+			log("TUIC 节点格式错误: 缺少端口号")
+			return nil
+		end
+		local server = hostParts[1]
+		local port = hostParts[2]
+
+		result.type = tuic_type
+		result.alias = UrlDecode(alias)
+		result.server = server
+		result.server_port = port
+		result.tuic_uuid = uuid
+		result.tuic_passwd = password
+
+		result.tuic_ip = params.sni or ""
+		result.udp_relay_mode = params.udp_relay_mode or "native"
+		result.congestion_control = params.congestion_control or "cubic"
+
+		-- alpn 支持逗号或分号分隔
+		if params.alpn and params.alpn ~= "" then
+			local alpn = {}
+			for v in params.alpn:gmatch("[^,;|%s]+") do
+				table.insert(alpn, v)
+			end
+			result.tuic_alpn = alpn
+		end
 	end
 	if not result.alias then
 		if result.server and result.server_port then
@@ -1166,7 +1250,7 @@ local execute = function()
 										if dat[3] then
 											dat3 = "://" .. dat[3]
 										end
-										if dat[1] == 'ss' or dat[1] == 'trojan' then
+										if dat[1] == 'ss' or dat[1] == 'trojan' or dat[1] == 'tuic' then
 											result = processData(dat[1], dat[2] .. dat3)
 										else
 											result = processData(dat[1], base64Decode(dat[2]))