🎨 Sync 2026-04-10 15:34:26

This commit is contained in:
github-actions[bot] committed 2026-04-10 15:34:26 +08:00
1 parent 9b1a57d792
commit 42174fc0b0
66 files changed
+6841 -2210

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-ssr-plus
PKG_VERSION:=190
PKG_RELEASE:=8
PKG_RELEASE:=9
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
@@ -5,6 +5,7 @@ require "nixio.fs"
require "luci.sys"
require "luci.http"
require "luci.jsonc"
local nixio = require "nixio"
require "luci.model.uci"
local uci = require "luci.model.uci".cursor()
@@ -146,11 +147,11 @@ local function set_apply_on_parse(map)
end
end
local has_xray = is_finded("xray")
local has_hysteria2 = is_finded("hysteria")
local has_ss_rust = is_finded("sslocal") or is_finded("ssserver")
local has_ss_libev = is_finded("ss-redir") or is_finded("ss-local")
local has_trojan = is_finded("trojan")
local has_xray = is_finded("xray")
local has_hysteria2 = is_finded("hysteria")
local server_table = {}
local encrypt_methods = {
@@ -277,25 +278,63 @@ o.value = sid
-- 新增一个选择框,用于选择 Xray 或 Hysteria2 核心
o = s:option(ListValue, "_xray_hy2_type", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Type", "Hysteria2")))
o.description = translate("The configured type also applies to the core specified when manually importing nodes.")
-- 设置默认 Xray 或 Hysteria2 核心
-- 动态添加选项
if has_xray then
o:value("v2ray", translate("Xray (Hysteria2)"))
end
-- 注意:Auto 选项使用特殊字符串 "__auto__" 而不是空字符串
o:value("__auto__", translate("Auto"))
if has_hysteria2 then
o:value("hysteria2", translate("Hysteria2"))
o:value("hysteria2", translate("Hysteria2"))
end
if has_xray then
o:value("v2ray", translate("Xray (Hysteria2)"))
end
-- 读取全局 xray_hy2_type
o.cfgvalue = function(self, section)
return self.map.uci:get("shadowsocksr", "@server_subscribe[0]", "xray_hy2_type") or "hysteria2"
local val = uci:get("shadowsocksr", "@server_subscribe[0]", "xray_hy2_type")
if val == nil or val == "" then
return "__auto__" -- 对应 Auto 选项
end
return val
end
o.rmempty = true
-- 保存时更新全局配置
o.write = function(self, section, value)
-- 更新 server_subscribe 的 xray_hy2_type
local old_value = self.map.uci:get("shadowsocksr", "@server_subscribe[0]", "xray_hy2_type")
if old_value ~= value then
self.map.uci:set("shadowsocksr", "@server_subscribe[0]", "xray_hy2_type", value)
end
if value == "__auto__" then
-- 删除全局配置
uci:delete("shadowsocksr", "@server_subscribe[0]", "xray_hy2_type")
else
-- 设置具体值
uci:set("shadowsocksr", "@server_subscribe[0]", "xray_hy2_type", value)
end
end
-- 新增一个选择框,用于选择 Xray 或 Trojan 核心
o = s:option(ListValue, "_xray_tj_type", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Type", "Trojan")))
o.description = translate("The configured type also applies to the core specified when manually importing nodes.")
-- 注意:Auto 选项使用特殊字符串 "__auto__" 而不是空字符串
o:value("__auto__", translate("Auto"))
if has_hysteria2 then
o:value("trojan", translate("Trojan"))
end
if has_xray then
o:value("v2ray", translate("Xray (Trojan)"))
end
-- 读取全局 xray_tj_type
o.cfgvalue = function(self, section)
local val = uci:get("shadowsocksr", "@server_subscribe[0]", "xray_tj_type")
if val == nil or val == "" then
return "__auto__" -- 对应 Auto 选项
end
return val
end
o.rmempty = true
-- 保存时更新全局配置
o.write = function(self, section, value)
if value == "__auto__" then
-- 删除全局配置
uci:delete("shadowsocksr", "@server_subscribe[0]", "xray_tj_type")
else
-- 设置具体值
uci:set("shadowsocksr", "@server_subscribe[0]", "xray_tj_type", value)
end
end
o = s:option(ListValue, "type", translate("Server Node Type"))
@@ -329,6 +368,29 @@ end
if is_finded("redsocks2") then
o:value("tun", translate("Network Tunnel"))
end
local old_cfgvalue = o.cfgvalue
o.cfgvalue = function(self, section)
local val = self.map.uci:get("shadowsocksr", section, "type")
if val == "ss-rust" or val == "ss-libev" then
return "ss"
end
if old_cfgvalue then
return old_cfgvalue(self, section)
end
return val
end
-- 重写 write,当用户选择 "ss" 时不写入(由 _ss_core 负责写入具体核心)
local old_write = o.write
o.write = function(self, section, value)
if value == "ss" then
return -- 不做任何写入,等待 _ss_core 写入
end
if old_write then
old_write(self, section, value)
else
self.map.uci:set("shadowsocksr", section, "type", value)
end
end
o.description = translate("Using incorrect encryption mothod may causes service fail to start")
@@ -343,29 +405,37 @@ end
o:depends("type", "tun")
o.description = translate("Redirect traffic to this network interface")
-- 新增一个选择框,用于选择 Shadowsocks 版本
o = s:option(ListValue, "_has_ss_type", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Version", "ShadowSocks")))
-- 新增一个选择框,用于选择 Shadowsocks 具体版本(仅当节点类型为 ss 或其具体子类型时显示)
o = s:option(ListValue, "_ss_core", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Version", "ShadowSocks")))
o.description = translate("Selection ShadowSocks Node Use Version.")
-- 设置默认 Shadowsocks 版本
-- 动态添加选项
if has_ss_rust then
o:value("ss-rust", translate("ShadowSocks-rust Version"))
o:value("ss-rust", translate("ShadowSocks-rust Version"))
end
if has_ss_libev then
o:value("ss-libev", translate("ShadowSocks-libev Version"))
o:value("ss-libev", translate("ShadowSocks-libev Version"))
end
-- 读取全局 ss_type
o.cfgvalue = function(self, section)
return self.map.uci:get("shadowsocksr", "@server_subscribe[0]", "ss_type") or "ss-rust"
-- 读取当前节点的 type 值,如果已经是具体核心则显示对应的选项
local node_type = self.map.uci:get("shadowsocksr", section, "type")
if node_type == "ss-rust" or node_type == "ss-libev" then
return node_type
end
-- 如果全局 ss_type 有值且为具体核心则返回该值
local ss_type = self.map.uci:get("shadowsocksr", "@server_subscribe[0]", "ss_type")
if ss_type == "ss-rust" or ss_type == "ss-libev" then
return ss_type
end
-- 如果节点 type 是旧的 "ss",则返回空,手动选择
return nil
end
-- 显示条件:当节点类型为 "ss" 或其具体核心时显示
o:depends("type", "ss")
o.rmempty = true
-- 保存时,将选择的值直接写入当前节点的 type 字段
o.write = function(self, section, value)
-- 更新 server_subscribe 的 ss_type
local old_value = self.map.uci:get("shadowsocksr", "@server_subscribe[0]", "ss_type")
if old_value ~= value then
self.map.uci:set("shadowsocksr", "@server_subscribe[0]", "ss_type", value)
end
if value and value ~= "" then
self.map.uci:set("shadowsocksr", section, "type", value)
end
end
o = s:option(ListValue, "v2ray_protocol", translate("V2Ray/XRay protocol"))
@@ -539,7 +609,7 @@ o = s:option(Value, "port_range", translate("Port hopping range"))
o.description = translate("Format as 10000:20000 or 10000-20000 Multiple groups are separated by commas (,).")
o:depends({type = "hysteria2", flag_port_hopping = true})
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_port_hopping = true})
--o.datatype = "portrange"
o.datatype = "or(uinteger,portrange)"
o.rmempty = true
o = s:option(Flag, "flag_transport", translate("Enable Transport Protocol Settings"))
@@ -554,9 +624,10 @@ o.default = "udp"
o.rmempty = true
o = s:option(Value, "hopinterval", translate("Port Hopping Interval(Unit:Second)"))
o.description = translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5.")
o:depends({type = "hysteria2", flag_transport = true, flag_port_hopping = true})
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_port_hopping = true})
o.datatype = "uinteger"
o.datatype = "or(uinteger,portrange)"
o.rmempty = true
o.default = "30"
@@ -295,4 +295,3 @@ end
m:section(SimpleSection).template = "shadowsocksr/status_bottom"
return m
@@ -70,9 +70,14 @@ local function set_apply_on_parse(map)
end
end
local has_ss_rust = is_finded("sslocal") or is_finded("ssserver")
local has_ss_libev = is_finded("ss-redir") or is_finded("ss-local")
local has_trojan = is_finded("trojan")
local has_xray = is_finded("xray")
local has_hysteria2 = is_finded("hysteria")
local ss_type_list = {}
local tj_type_list = {}
local hy2_type_list = {}
if has_hysteria2 then
@@ -82,19 +87,12 @@ if has_xray then
table.insert(hy2_type_list, { id = "v2ray", name = translate("Xray (Hysteria2)") })
end
-- 如果用户没有手动设置,则自动选择
if not xray_hy2_type or xray_hy2_type == "" then
if has_hysteria2 then
xray_hy2_type = "hysteria2"
elseif has_xray then
xray_hy2_type = "v2ray"
end
if has_trojan then
table.insert(tj_type_list, { id = "trojan", name = translate("Trojan") })
end
if has_xray then
table.insert(tj_type_list, { id = "v2ray", name = translate("Xray (Trojan)") })
end
local has_ss_rust = is_finded("sslocal") or is_finded("ssserver")
local has_ss_libev = is_finded("ss-redir") or is_finded("ss-local")
local ss_type_list = {}
if has_ss_rust then
table.insert(ss_type_list, { id = "ss-rust", name = translate("ShadowSocks-rust Version") })
@@ -106,17 +104,6 @@ if has_xray then
table.insert(ss_type_list, { id = "v2ray", name = translate("Xray (ShadowSocks)") })
end
-- 如果用户没有手动设置,则自动选择
if not ss_type or ss_type == "" then
if has_ss_rust then
ss_type = "ss-rust"
elseif has_ss_libev then
ss_type = "ss-libev"
elseif has_xray then
ss_type = "v2ray"
end
end
uci:foreach("shadowsocksr", "servers", function(s)
server_count = server_count + 1
end)
@@ -162,22 +149,87 @@ o:depends("auto_update", "1")
-- 确保 hy2_type_list 不为空
if #hy2_type_list > 0 then
local sid = uci:get_first("shadowsocksr", "server_subscribe")
if not sid then
uci:foreach("shadowsocksr", "server_subscribe", function(section)
sid = section[".name"]
return false
end)
end
if sid then
local old_val = uci:get("shadowsocksr", sid, "xray_hy2_type")
if old_val and old_val ~= "" then
if (old_val == "hysteria2" and not has_hysteria2) or
(old_val == "v2ray" and not has_xray) then
-- 核心不可用,设置为空(删除配置)
uci:set("shadowsocksr", sid, "xray_hy2_type", "")
uci:commit("shadowsocksr")
end
end
end
o = s:option(ListValue, "xray_hy2_type", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Type", "Hysteria2")))
o.description = translate("The configured type also applies to the core specified when manually importing nodes.")
o:value("", translate("Auto"))
for _, v in ipairs(hy2_type_list) do
o:value(v.id, v.name) -- 存储 "Xray" / "Hysteria2",但 UI 显示完整名称
end
o.default = xray_hy2_type -- 设置默认值
end
-- 确保 tj_type_list 不为空
if #tj_type_list > 0 then
local sid = uci:get_first("shadowsocksr", "server_subscribe")
if not sid then
uci:foreach("shadowsocksr", "server_subscribe", function(section)
sid = section[".name"]
return false
end)
end
if sid then
local old_val = uci:get("shadowsocksr", sid, "xray_tj_type")
if old_val and old_val ~= "" then
if (old_val == "trojan" and not has_trojan) or
(old_val == "v2ray" and not has_xray) then
-- 核心不可用,设置为空(删除配置)
uci:set("shadowsocksr", sid, "xray_tj_type", "")
uci:commit("shadowsocksr")
end
end
end
o = s:option(ListValue, "xray_tj_type", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Type", "Trojan")))
o.description = translate("The configured type also applies to the core specified when manually importing nodes.")
o:value("", translate("Auto"))
for _, v in ipairs(tj_type_list) do
o:value(v.id, v.name) -- 存储 "Xray" / "Trojan",但 UI 显示完整名称
end
end
-- 确保 ss_type_list 不为空
if #ss_type_list > 0 then
local sid = uci:get_first("shadowsocksr", "server_subscribe")
if not sid then
uci:foreach("shadowsocksr", "server_subscribe", function(section)
sid = section[".name"]
return false
end)
end
if sid then
local old_val = uci:get("shadowsocksr", sid, "ss_type")
if old_val and old_val ~= "" then
if (old_val == "ss-rust" and not has_ss_rust) or
(old_val == "ss-libev" and not has_ss_libev) or
(old_val == "v2ray" and not has_xray) then
-- 核心不可用,设置为空(删除配置)
uci:set("shadowsocksr", sid, "ss_type", "")
uci:commit("shadowsocksr")
end
end
end
o = s:option(ListValue, "ss_type", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Version", "ShadowSocks")))
o.description = translate("Selection ShadowSocks Node Use Version.")
o:value("", translate("Auto"))
for _, v in ipairs(ss_type_list) do
o:value(v.id, v.name) -- 存储 "ss-libev" / "ss-rust",但 UI 显示完整名称
end
o.default = ss_type -- 设置默认值
end
o = s:option(DynamicList, "subscribe_url", translate("Subscribe URL"))
@@ -3,11 +3,24 @@
local map = self.map
local ss_type = map:get("@server_subscribe[0]", "ss_type")
local xray_hy2_type = map:get("@server_subscribe[0]", "xray_hy2_type")
local xray_tj_type = map:get("@server_subscribe[0]", "xray_tj_type")
local has_ss_rust = luci.sys.exec('type -t -p sslocal 2>/dev/null || type -t -p ssserver 2>/dev/null') ~= ""
local has_ss_libev = luci.sys.exec('type -t -p ss-redir 2>/dev/null || type -t -p ss-local 2>/dev/null') ~= ""
local has_hysteria = luci.sys.exec('type -t -p hysteria 2>/dev/null') ~= ""
local has_trojan = luci.sys.exec('type -t -p trojan 2>/dev/null') ~= ""
local has_xray = luci.sys.exec('type -t -p xray 2>/dev/null') ~= ""
-%>
<script type="text/javascript">
//<![CDATA[
let ss_type = "<%=ss_type%>"
let xray_hy2_type = "<%=xray_hy2_type%>"
let xray_tj_type = "<%=xray_tj_type%>"
let has_ss_rust = "<%=has_ss_rust%>"
let has_ss_libev = "<%=has_ss_libev%>"
let has_hysteria = "<%=has_hysteria%>"
let has_trojan = "<%=has_trojan%>"
let has_xray = "<%=has_xray%>"
function padright(str, cnt, pad) {
return str + Array(cnt + 1).join(pad);
@@ -112,48 +125,105 @@ function import_ssr_url(btn, urlname, sid) {
return false;
}
if (xray_hy2_type === "hysteria2") {
// 普通 Hysteria2 导入函数
function importAsNormalHy2() {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = (ssu[0] === "hy2") ? "hysteria2" : ssu[0];
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
if (params.get("protocol") && params.get("protocol").trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.flag_transport')[0].checked = true; // 设置 flag_transport 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.flag_transport')[0].dispatchEvent(event); // 触发事件
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport_protocol')[0].value = params.get("protocol") || "udp";
}
if (params.get("lazy") === "1") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.lazy_mode')[0].checked = true;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.lazy_mode')[0].dispatchEvent(event);
}
if (params.get("sni") || params.get("alpn")) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true; // 设置 tls 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event); // 触发事件
if (params.get("sni")) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = params.get("sni") || "";
}
if (params.get("alpn") && params.get("alpn").trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_alpn')[0].value = params.get("alpn") || "";
}
}
if (params.get("pinSHA256") && params.get("pinSHA256").trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.pinsha256')[0].value = params.get("pinSHA256") || "";
}
}
// Xray Hysteria2 导入函数
function importAsXrayHy2() {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "v2ray"
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].value = (ssu[0] === "hy2") ? "hysteria2" : ssu[0];
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].dispatchEvent(event);
if (params.get("security") === "tls" || params.get("sni") || params.get("alpn") || (params.get("pcs") || params.get("vcn"))) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true; // 设置 tls 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event); // 触发事件
if (params.get("sni")) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = params.get("sni") || "";
}
if (params.get("alpn") && params.get("alpn").trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_alpn')[0].value = params.get("alpn") || "";
}
if (params.get("pcs") && params.get("pcs").trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_CertSha')[0].value = params.get("pcs") || "";
}
if (params.get("vcn") && params.get("vcn").trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_CertByName')[0].value = params.get("vcn") || "";
}
}
if (params.get("fm") && params.get("fm").trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_finalmask')[0].checked = true; // 设置 enable_finalmask 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_finalmask')[0].dispatchEvent(event); // 触发事件
document.getElementsByName('cbid.shadowsocksr.' + sid + '.finalmask')[0].value = params.get("fm") || "";
}
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
} else {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "v2ray"
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].value = (ssu[0] === "hy2") ? "hysteria2" : ssu[0];
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
}
// 主逻辑:智能核心选择(支持回退)
function hasCore(core) {
if (core === "hysteria") return has_hysteria === "true";
if (core === "xray") return has_xray === "true";
return false;
}
let finalMode = null; // true=Xray, false=普通, null=无核心
if (xray_hy2_type === "v2ray") {
if (hasCore("xray")) finalMode = true;
else if (hasCore("hysteria")) finalMode = false;
else finalMode = null;
} else if (xray_hy2_type === "hysteria2") {
if (hasCore("hysteria")) finalMode = false;
else if (hasCore("xray")) finalMode = true;
else finalMode = null;
} else {
// auto 或空:Hysteria2 无 type 参数,优先普通 Hysteria2
if (hasCore("hysteria")) finalMode = false;
else if (hasCore("xray")) finalMode = true;
else finalMode = null;
}
if (finalMode === null) {
s.innerHTML = "<font style=\'color:red\'><%:No available core (Hysteria2 or Xray) to import this node.%></font>";
return false;
}
if (finalMode === true) importAsXrayHy2();
else importAsNormalHy2();
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = url.hostname;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port || "443";
if (params.get("lazy") === "1") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.lazy_mode')[0].checked = true;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.lazy_mode')[0].dispatchEvent(event);
}
document.getElementsByName('cbid.shadowsocksr.' + sid + '.hy2_auth')[0].value = decodeURIComponent(url.username);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.hy2_auth')[0].dispatchEvent(event);
if (params.get("mport")) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.flag_port_hopping')[0].checked = true; // 设置 flag_port_hopping 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.flag_port_hopping')[0].dispatchEvent(event); // 触发事件
document.getElementsByName('cbid.shadowsocksr.' + sid + '.port_range')[0].value = params.get("mport") || "";
}
document.getElementsByName('cbid.shadowsocksr.' + sid + '.hy2_auth')[0].value = decodeURIComponent(url.username);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.hy2_auth')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.uplink_capacity')[0].value =
(params.get("upmbps") && params.get("upmbps").match(/\d+/)) ? params.get("upmbps").match(/\d+/)[0] : "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.downlink_capacity')[0].value =
@@ -165,25 +235,6 @@ function import_ssr_url(btn, urlname, sid) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.obfs_type')[0].value = params.get("obfs");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.salamander')[0].value = params.get("obfs-password") || params.get("obfs_password");
}
if (params.get("security") === "tls" || params.get("sni") || params.get("alpn")
|| (xray_hy2_type !== "hysteria2" && (params.get("pcs") || params.get("vcn")))) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true; // 设置 tls 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event); // 触发事件
if (params.get("sni") && params.get("protocol").trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = params.get("sni") || "";
}
if (params.get("alpn") && params.get("alpn").trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_alpn')[0].value = params.get("alpn") || "";
}
if (xray_hy2_type !== "hysteria2") {
if (params.get("pcs") && params.get("pcs").trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_CertSha')[0].value = params.get("pcs") || "";
}
if (params.get("vcn") && params.get("vcn").trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_CertByName')[0].value = params.get("vcn") || "";
}
}
}
var allowInsecureValue = params.get("allowInsecure") || params.get("insecure");
if (allowInsecureValue === "1" || allowInsecureValue === "true") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].checked = true; // 设置 insecure 为 true
@@ -218,7 +269,7 @@ function import_ssr_url(btn, urlname, sid) {
var params = Object.fromEntries(new URLSearchParams(query));
if ( ss_type !== "v2ray") {
function importAsNormalSS() {
// 普通 SS 导入逻辑
// 判断是否 SIP002 格式(即含 @)
if (url0.indexOf("@") !== -1) {
@@ -226,15 +277,28 @@ function import_ssr_url(btn, urlname, sid) {
var sipIndex = url0.indexOf("@");
// 先 URL 解码 base64 再解码
var userInfoB64 = decodeURIComponent(url0.substring(0, sipIndex));
// console.log("userInfoB64:", userInfoB64);
var userInfo = b64decsafe(userInfoB64);
if (userInfo && userInfo.indexOf(":") !== -1) {
var userInfo = userInfo;
} else {
var userInfo = userInfoB64;
}
// 如果没有冒号,且不是 base64 格式,我们可以尝试补齐它
if (userInfo.indexOf(":") === -1) {
userInfo = "none:" + userInfo;
}
// console.log("userInfo after decode:", userInfo);
var userInfoSplitIndex = userInfo.indexOf(":");
if(userInfoSplitIndex < 0) {
// 格式错误
s.innerHTML = "<font style='color:red'><%:Userinfo format error.%></font>";
break;
return false;
}
var method = userInfo.substring(0, userInfoSplitIndex);
// console.log("method:", method);
var password = userInfo.substring(userInfoSplitIndex + 1);
// console.log("password:", password);
var serverPart = url0.substring(sipIndex + 1);
var serverInfo = serverPart.split(":");
@@ -254,7 +318,7 @@ function import_ssr_url(btn, urlname, sid) {
var sstr = b64decsafe(decodedUrl0);
if (!sstr) {
s.innerHTML = "<font style='color:red'><%:Base64 sstr failed.%></font>";
break;
return false;
}
// 支持 SS2022 / 普通格式
@@ -276,7 +340,7 @@ function import_ssr_url(btn, urlname, sid) {
var port = mNormal[4];
} else {
s.innerHTML = "<font style='color:red'><%:SS URL base64 sstr format not recognized.%></font>";
break;
return false;
}
var plugin = "", pluginOpts = "";
@@ -314,9 +378,17 @@ function import_ssr_url(btn, urlname, sid) {
// === 填充配置项 ===
//var has_ss_type = (ss_type === "ss-rust") ? "ss-rust" : "ss-libev";
// 设置 _ss_core 的值
var ssCore = (ss_type === "ss-rust" || ss_type === "ss-libev")
? ss_type
: (has_ss_rust ? "ss-rust" : (has_ss_libev ? "ss-libev" : "ss-libev"));
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = ssu[0];
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '._ss_core')[0].value = ssCore;
document.getElementsByName('cbid.shadowsocksr.' + sid + '._ss_core')[0].dispatchEvent(event);
//document.getElementsByName('cbid.shadowsocksr.' + sid + '.has_ss_type')[0].value = has_ss_type;
//document.getElementsByName('cbid.shadowsocksr.' + sid + '.has_ss_type')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = server;
@@ -351,8 +423,10 @@ function import_ssr_url(btn, urlname, sid) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = decodeURIComponent(param);
}
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
return false;
} else {
return true;
}
function importAsXraySS() {
try {
// Xray SS 导入逻辑
// 拆分 @,判断是否是 base64 userinfo 的格式
@@ -366,7 +440,14 @@ function import_ssr_url(btn, urlname, sid) {
password = userinfo.slice(sepIndex + 1); //一些链接用明文uuid做密码
}
}
var url = new URL("http://" + url0 + (param ? "#" + encodeURIComponent(param) : ""));
// 已编码的别名
// var url = new URL("http://" + url0 + (param ? "#" + encodeURIComponent(param) : ""));
// 未编码的别名
// var url = new URL("http://" + url0 + (param ? "#" + param : ""));
// 兼容已编码和未编码的别名
var remarks = param ? decodeURIComponent(param) : "";
var encodedremarks = encodeURIComponent(remarks);
var url = new URL("http://" + url0 + (encodedremarks ? "#" + encodedremarks : ""));
} catch(e) {
alert(e);
@@ -411,7 +492,7 @@ function import_ssr_url(btn, urlname, sid) {
}
if (params.fm && params.fm.trim() !== "") {
setElementValue('cbid.shadowsocksr.' + sid + '.enable_finalmask', true); // 设置 enable_finalmask 为 true
setElementValue('cbid.shadowsocksr.' + sid + '.enable_finalmask', event); // 触发事件
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_finalmask', event); // 触发事件
setElementValue('cbid.shadowsocksr.' + sid + '.finalmask', params.fm || "");
}
@@ -514,8 +595,46 @@ function import_ssr_url(btn, urlname, sid) {
break;
}
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
return true;
}
// 主逻辑:智能核心选择(支持回退)
function hasSSCore() { return has_ss_rust === "true" || has_ss_libev === "true"; }
function hasXrayCore() { return has_xray === "true"; }
let finalSSType = null; // true=Xray, false=普通SS, null=无核心
if (ss_type === "v2ray") {
if (hasXrayCore()) finalSSType = true;
else if (hasSSCore()) finalSSType = false;
else finalSSType = null;
} else if (ss_type === "ss-rust" || ss_type === "ss-libev") {
let userSSCore = (ss_type === "ss-rust" && has_ss_rust === "true") || (ss_type === "ss-libev" && has_ss_libev === "true");
if (userSSCore) finalSSType = false;
else {
let otherSSCore = (ss_type === "ss-rust" && has_ss_libev === "true") || (ss_type === "ss-libev" && has_ss_rust === "true");
if (otherSSCore) finalSSType = false;
else if (hasXrayCore()) finalSSType = true;
else finalSSType = null;
}
} else {
// auto 或空:根据 type 参数决定
let hasType = params.type && params.type !== "";
if (hasType) {
if (hasXrayCore()) finalSSType = true;
else if (hasSSCore()) finalSSType = false;
else finalSSType = null;
} else {
if (hasSSCore()) finalSSType = false;
else if (hasXrayCore()) finalSSType = true;
else finalSSType = null;
}
}
if (finalSSType === null) {
s.innerHTML = "<font style=\'color:red\'><%:No available core (Shadowsocks or Xray) to import this node.%></font>";
return false;
}
if (finalSSType === true) importAsXraySS();
else importAsNormalSS();
return false;
case "ssr":
var sstr = b64decsafe((ssu[1] || "").replace(/#.*/, "").trim());
var ploc = sstr.indexOf("/?");
@@ -561,8 +680,8 @@ function import_ssr_url(btn, urlname, sid) {
return false;
}
if (!params.get("type")) {
// 普通 Trojan 导入逻辑
// 普通 Trojan 导入逻辑
function importAsNormalTrojan() {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "trojan";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
@@ -585,9 +704,11 @@ function import_ssr_url(btn, urlname, sid) {
}
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
return false;
} else {
// Xray Trojan 导入逻辑
return true;
}
// Xray Trojan 导入逻辑
function importAsXrayTrojan() {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "v2ray";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
@@ -690,8 +811,40 @@ function import_ssr_url(btn, urlname, sid) {
}
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
return true;
}
// 主逻辑:智能核心选择(支持回退)
function hasTrojanCore() { return has_trojan === "true"; }
function hasXrayCore() { return has_xray === "true"; }
let finalTrojanMode = null;
if (xray_tj_type === "v2ray") {
if (hasXrayCore()) finalTrojanMode = true;
else if (hasTrojanCore()) finalTrojanMode = false;
else finalTrojanMode = null;
} else if (xray_tj_type === "trojan") {
if (hasTrojanCore()) finalTrojanMode = false;
else if (hasXrayCore()) finalTrojanMode = true;
else finalTrojanMode = null;
} else {
let hasType = params.get("type") && params.get("type") !== "";
if (hasType) {
if (hasXrayCore()) finalTrojanMode = true;
else if (hasTrojanCore()) finalTrojanMode = false;
else finalTrojanMode = null;
} else {
if (hasTrojanCore()) finalTrojanMode = false;
else if (hasXrayCore()) finalTrojanMode = true;
else finalTrojanMode = null;
}
}
if (finalTrojanMode === null) {
s.innerHTML = "<font style=\'color:red\'><%:No available core (Trojan or Xray) to import this node.%></font>";
return false;
}
if (finalTrojanMode === true) importAsXrayTrojan();
else importAsNormalTrojan();
return false;
case "vmess":
var sstr = b64DecodeUnicode((ssu[1] || "").replace(/#.*/, "").trim());
var ploc = sstr.indexOf("/?");
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+38 -23
View File
@@ -611,15 +611,18 @@ gen_config_file() { #server1 type2 code3 local_port4 socks_port5 chain6 threads5
}
start_udp() {
local type=$(uci_get_by_name $UDP_RELAY_SERVER type)
local has_ss_type=$(uci_get_by_type server_subscribe ss_type)
local udp_relay_server_type=$(uci_get_by_name $UDP_RELAY_SERVER type)
local type=$udp_relay_server_type
if [ "$udp_relay_server_type" = "ss-rust" ] || [ "$udp_relay_server_type" = "ss-libev" ]; then
type="ss"
fi
redir_udp=1
case "$type" in
ss | ssr)
gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_port
if [ "$has_ss_type" = "ss-libev" -o "$type" = "ssr" ]; then
if [ "$udp_relay_server_type" = "ss-libev" ] || [ "$udp_relay_server_type" = "ssr" ]; then
ss_program="$(first_type ${type}-redir)"
elif [ "$has_ss_type" = "ss-rust" ]; then
elif [ "$udp_relay_server_type" = "ss-rust" ]; then
ss_program="$(first_type ${type}local)"
fi
echolog "$(get_name $type) program is: $ss_program"
@@ -858,14 +861,17 @@ shunt_dns_config_file_port() {
}
start_shunt() {
local type=$(uci_get_by_name $SHUNT_SERVER type)
local has_ss_type=$(uci_get_by_type server_subscribe ss_type)
local shunt_server_type=$(uci_get_by_name $SHUNT_SERVER type)
local type=$shunt_server_type
if [ "$shunt_server_type" = "ss-rust" ] || [ "$shunt_server_type" = "ss-libev" ]; then
type="ss"
fi
case "$type" in
ss | ssr)
gen_config_file $SHUNT_SERVER $type 3 $tmp_shunt_port
if [ "$has_ss_type" = "ss-libev" -o "$type" = "ssr" ]; then
if [ "$shunt_server_type" = "ss-libev" ] || [ "$shunt_server_type" = "ssr" ]; then
ss_program="$(first_type ${type}-redir)"
elif [ "$has_ss_type" = "ss-rust" ]; then
elif [ "$shunt_server_type" = "ss-rust" ]; then
ss_program="$(first_type ${type}local)"
fi
echolog "$(get_name $type) program is: $ss_program"
@@ -880,9 +886,9 @@ start_shunt() {
local tmp_port=$tmp_local_port
else
local tmp_port=$tmp_shunt_local_port
if [ "$has_ss_type" = "ss-libev" -o "$type" = "ssr" ]; then
if [ "$shunt_server_type" = "ss-libev" ] || [ "$shunt_server_type" = "ssr" ]; then
dns_ss_program="$(first_type ${type}-local)"
elif [ "$has_ss_type" = "ss-rust" ]; then
elif [ "$shunt_server_type" = "ss-rust" ]; then
dns_ss_program="$(first_type ${type}local)"
fi
# 获取当前软链接指向的执行文件路径
@@ -1009,14 +1015,17 @@ start_local() {
[ "$LOCAL_SERVER" = "nil" ] && return 1
local local_port=$(uci_get_by_type socks5_proxy local_port)
[ "$LOCAL_SERVER" == "$SHUNT_SERVER" ] && tmp_local_port=$local_port
local type=$(uci_get_by_name $LOCAL_SERVER type)
local has_ss_type=$(uci_get_by_type server_subscribe ss_type)
local local_server_type=$(uci_get_by_name $LOCAL_SERVER type)
local type=$local_server_type
if [ "$local_server_type" = "ss-rust" ] || [ "$local_server_type" = "ss-libev" ]; then
type="ss"
fi
case "$type" in
ss | ssr)
gen_config_file $LOCAL_SERVER $type 4 $local_port
if [ "$has_ss_type" = "ss-libev" -o "$type" = "ssr" ]; then
if [ "$local_server_type" = "ss-libev" ] || [ "$local_server_type" = "ssr" ]; then
ss_program="$(first_type ${type}-local)"
elif [ "$has_ss_type" = "ss-rust" ]; then
elif [ "$local_server_type" = "ss-rust" ]; then
ss_program="$(first_type ${type}local)"
fi
echolog "$(get_name $type) program is: $ss_program"
@@ -1124,14 +1133,17 @@ Start_Run() {
#}
fi
local tcp_port=$(uci_get_by_name $GLOBAL_SERVER local_port)
local type=$(uci_get_by_name $GLOBAL_SERVER type)
local has_ss_type=$(uci_get_by_type server_subscribe ss_type)
local global_server_type=$(uci_get_by_name $GLOBAL_SERVER type)
local type=$global_server_type
if [ "$global_server_type" = "ss-rust" ] || [ "$global_server_type" = "ss-libev" ]; then
type="ss"
fi
case "$type" in
ss | ssr)
gen_config_file $GLOBAL_SERVER $type 1 $tcp_port
if [ "$has_ss_type" = "ss-libev" -o "$type" = "ssr" ]; then
if [ "$global_server_type" = "ss-libev" ] || [ "$global_server_type" = "ssr" ]; then
ss_program="$(first_type ${type}-redir)"
elif [ "$has_ss_type" = "ss-rust" ]; then
elif [ "$global_server_type" = "ss-rust" ]; then
ss_program="$(first_type ${type}local)"
fi
echolog "$(get_name $type) program is: $ss_program"
@@ -1367,14 +1379,17 @@ start_server() {
fi
fi
fi
local type=$(uci_get_by_name $1 type)
local has_ss_type=$(uci_get_by_type server_subscribe ss_type)
local node_type=$(uci_get_by_name $1 type)
local type=$node_type
if [ "$node_type" = "ss-rust" ] || [ "$node_type" = "ss-libev" ]; then
type="ss"
fi
case "$type" in
ss | ssr)
gen_service_file ${type} $1 $TMP_PATH/ssr-server$server_count.json
if [ "$has_ss_type" = "ss-libev" -o "$type" = "ssr" ]; then
if [ "$node_type" = "ss-libev" ] || [ "$node_type" = "ssr" ]; then
ss_program="$(first_type ${type}-server)"
elif [ "$has_ss_type" = "ss-rust" ]; then
elif [ "$node_type" = "ss-rust" ]; then
ss_program="$(first_type ${type}server)"
fi
# 获取当前软链接指向的执行文件路径
@@ -1506,7 +1521,7 @@ start_xhttp_addr() {
# MD5 不同更新 download_address 地址文件
if [ "$md5_new" != "$md5_old" ]; then
mv -f "$tmp_file" "$xhttp_addr_file"
logger -t ssrplus-xhttp "xhttp_address.txt updated"
logger -t ssrplus-xhttp "download_address.txt updated"
else
rm -f "$tmp_file"
fi
+11 -15
View File
@@ -1241,7 +1241,6 @@ compare_rules() {
fi
# Generate temporary file for current rules
local temp_file=$(mktemp)
local rules_file=$(mktemp)
loger 7 "DEBUG: Temporary file path: $rules_file"
@@ -1255,18 +1254,18 @@ compare_rules() {
# Check if current rules were exported successfully
if [ ! -s "$rules_file" ] || ! grep -q "table" "$rules_file" 2>/dev/null; then
loger 4 "Failed to export current rules"
rm -f "$temp_file" "$rules_file"
rm -f "$rules_file"
return 1 # Export failed, need update
fi
# Compare current rules with rules in persistence file
if ! cmp -s "$rules_file" "$NFTABLES_RULES_FILE"; then
loger 6 "Rules differ, update needed"
rm -f "$temp_file" "$rules_file"
rm -f "$rules_file"
return 1 # Need update
fi
rm -f "$temp_file" "$rules_file"
rm -f "$rules_file"
loger 6 "Rules unchanged, no update needed"
return 0 # No update needed
}
@@ -1277,21 +1276,17 @@ persist_nftables_rules() {
return 0
fi
# If mode unchanged and persistence file exists, skip update
if [ "$MODE_CHANGED" = "0" ] && [ -f "$NFTABLES_RULES_FILE" ]; then
loger 6 "Mode unchanged and persistence file exists, skipping update"
return 0
fi
# Force update: skip comparison check and delete old file
if [ "$FORCE_UPDATE" = "1" ]; then
loger 6 "Force update requested, removing old persistence file"
rm -f "$NFTABLES_RULES_FILE" 2>/dev/null
# Non-force update: compare rules
# Otherwise, if persistence file exists, compare rules
elif [ -f "$NFTABLES_RULES_FILE" ]; then
if compare_rules; then
loger 6 "Rules unchanged, skipping persistence update"
return 0
else
loger 6 "Rules changed, updating persistence"
fi
fi
@@ -1299,7 +1294,7 @@ persist_nftables_rules() {
mkdir -p "$NFTABLES_RULES_DIR" 2>/dev/null
# Generate nftables rule file
cat <<-'EOF' >>$NFTABLES_RULES_FILE
cat <<-'EOF' > "$NFTABLES_RULES_FILE"
#!/usr/sbin/nft -f
# ShadowsocksR nftables rules
@@ -1374,17 +1369,18 @@ start_auto_update_daemon() {
echo $$ > "/var/run/ssr-rules-daemon.pid"
while true; do
sleep 300
sleep "$AUTO_UPDATE_INTERVAL"
if [ -x "/usr/bin/ssr-rules" ]; then
# -C returns 0 if rules are OK, non-zero if need update
if /usr/bin/ssr-rules -C >/dev/null 2>&1; then
logger -t ssr-rules[daemon] "Rules status OK, no update needed"
else
logger -t ssr-rules[daemon] "Rules changed or missing, updating persistence"
if /usr/bin/ssr-rules -P >/dev/null 2>&1; then
logger -t ssr-rules[daemon] "Persistence rules updated successfully"
else
logger -t ssr-rules[daemon] "Failed to update persistence"
fi
else
logger -t ssr-rules[daemon] "Rules status OK, no update needed"
fi
else
logger -t ssr-rules[daemon] "Script not found, exiting daemon"
@@ -25,6 +25,10 @@ local remarks = server.alias or ""
local b64decode = nixio.bin.b64decode
local b64encode = nixio.bin.b64encode
if server.type == "ss-rust" or server.type == "ss-libev" then
server.type = "ss"
end
-- base64 解码
local function base64Decode(text)
local raw = text
@@ -527,6 +531,18 @@ end
udpHop = (server.flag_port_hopping == "1") and {
ports = string.gsub(server.port_range, ":", "-"),
interval = (function(v)
if not v then return 30 end
if v:find("-", 1, true) then
local min, max = v:match("^(%d+)%-(%d+)$")
min = tonumber(min)
max = tonumber(max)
if min and max then
min = (min >= 5) and min or 5
max = (max >= min) and max or min
return min .. "-" .. max
end
return 30
end
v = tonumber((v or "30"):match("^%d+"))
return (v and v >= 5) and v or 30
end)(server.hopinterval)
@@ -736,12 +752,30 @@ local hysteria2 = {
listen = "0.0.0.0:" .. tonumber(socks_port),
disableUDP = false
} or nil,
transport = server.transport_protocol and {
transport = {
type = server.transport_protocol or "udp",
udp = (server.port_range and (server.hopinterval) and {
hopInterval = (server.port_range and (tonumber(server.hopinterval) .. "s") or nil)
} or nil)
} or nil,
udp = server.port_range and (function()
local udp = {}
local t = server.hopinterval
if not t then return nil end
if t:find("-", 1, true) then
local min, max = t:match("^(%d+)%-(%d+)$")
min = tonumber(min)
max = tonumber(max)
if min and max then
min = (min >= 5) and min or 5
max = (max >= min) and max or min
udp.minHopInterval = min .. "s"
udp.maxHopInterval = max .. "s"
return udp
end
end
t = tonumber((t or "30"):match("^%d+"))
t = (t and t >= 5) and t or 30
udp.hopInterval = t .. "s"
return udp
end)() or nil
},
--[[
tcpTProxy = (proto:find("tcp") and local_port ~= "0") and {
listen = "0.0.0.0:" .. tonumber(local_port)
@@ -27,10 +27,8 @@ config server_subscribe
option auto_update_week_time '*'
option auto_update_day_time '2'
option auto_update_min_time '0'
option ss_type 'ss-rust'
option url_test_url 'https://www.google.com/generate_204'
option user_agent 'v2rayN/9.99'
option xray_hy2_type 'hysteria2'
option filter_words '过期/套餐/剩余/QQ群/官网/防失联/回国'
config access_control
@@ -30,54 +30,20 @@ local subscribe_url = ucic:get_first(name, 'server_subscribe', 'subscribe_url',
local filter_words = ucic:get_first(name, 'server_subscribe', 'filter_words', '过期时间/剩余流量')
local save_words = ucic:get_first(name, 'server_subscribe', 'save_words', '')
local user_agent = ucic:get_first(name, 'server_subscribe', 'user_agent', 'v2rayN/9.99')
-- 读取 ss_type 设置
local ss_type = ucic:get_first(name, 'server_subscribe', 'ss_type', 'ss-rust')
-- 根据 ss_type 选择对应的程序
local ss_program = "sslocal"
if ss_type == "ss-rust" then
ss_program = "sslocal"
elseif ss_type == "ss-libev" then
ss_program = "ss-redir"
elseif ss_type == "v2ray" then
ss_program = "xray"
end
-- 从 UCI 配置读取 xray_hy2_type 设置
local xray_hy2_type = ucic:get_first(name, 'server_subscribe', 'xray_hy2_type', 'hysteria2')
local xray_hy2_program = "hysteria"
if xray_hy2_type == "v2ray" then
xray_hy2_program = "xray" -- Hysteria2 使用 Xray
elseif xray_hy2_type == "hysteria2" then
xray_hy2_program = "hysteria" -- Hysteria2 使用 Hysteria
end
local v2_ss_exists = luci.sys.exec('type -t -p ' .. ss_program .. ' 2>/dev/null') ~= ""
-- 初始化变量
local v2_ss = nil
local has_v2_ss_type = nil
if v2_ss_exists then
if ss_type == "v2ray" then
-- 使用 Xray
v2_ss = "v2ray"
has_v2_ss_type = "shadowsocks"
else
-- 使用 SS (rust 或 libev)
v2_ss = "ss"
end
end
local v2_tj = luci.sys.exec('type -t -p trojan') ~= "" and "trojan" or "v2ray"
-- 检查程序是否存在
local program_exists = luci.sys.exec('type -t -p ' .. xray_hy2_program .. ' 2>/dev/null') ~= ""
-- 初始化变量
local hy2_type = nil
local has_xray_hy2_type = nil
if program_exists then
-- 设置节点类型
if xray_hy2_type == "hysteria2" then
hy2_type = "hysteria2"
else
hy2_type = "v2ray" -- 当使用 Xray 时,节点类型是 "v2ray"
has_xray_hy2_type = "hysteria2" -- 可用的协议类型是 Hysteria2
end
end
local ss_type = ucic:get_first(name, 'server_subscribe', 'ss_type')
-- 读取 xray_hy2_type 设置
local xray_hy2_type = ucic:get_first(name, 'server_subscribe', 'xray_hy2_type')
-- 读取 xray_tj_type 设置
local xray_tj_type = ucic:get_first(name, 'server_subscribe', 'xray_tj_type')
local has_ss_rust = luci.sys.exec('type -t -p sslocal 2>/dev/null || type -t -p ssserver 2>/dev/null') ~= ""
local has_ss_libev = luci.sys.exec('type -t -p ss-redir 2>/dev/null || type -t -p ss-local 2>/dev/null') ~= ""
local has_hysteria = luci.sys.exec('type -t -p hysteria 2>/dev/null') ~= ""
local has_trojan = luci.sys.exec('type -t -p trojan 2>/dev/null') ~= ""
local has_xray = luci.sys.exec('type -t -p xray 2>/dev/null') ~= ""
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({...}, " "))
@@ -246,67 +212,66 @@ local function processData(szType, content, cfgid)
-- log(k.."="..v)
-- end
-- 如果 hy2 或 Xray 程序未安装则跳过订阅
if not hy2_type then
-- 自动决定模式(true=Xray, false=普通)
local xray_hy2_mode = false -- 默认普通模式
if xray_hy2_type == "v2ray" then
-- Xray 模式
if has_xray then
xray_hy2_mode = true
elseif has_hysteria then
xray_hy2_mode = false -- 回退到普通 Hysteria2
else
xray_hy2_mode = nil
end
elseif xray_hy2_type == "hysteria2" then
-- 普通 Hysteria2 模式
if has_hysteria then
xray_hy2_mode = false
elseif has_xray then
xray_hy2_mode = true -- 回退到 Xray
else
xray_hy2_mode = nil
end
else
-- auto 或空:优先普通 Hysteria2,若不存在则使用 Xray
if has_hysteria then
xray_hy2_mode = false
elseif has_xray then
xray_hy2_mode = true -- 回退到 Xray
else
xray_hy2_mode = nil
end
end
-- 如果无法确定模式,跳过该订阅
if xray_hy2_mode == nil then
return nil
end
if xray_hy2_type == "hysteria2" then
if params.protocol and params.protocol ~= "" then
result.flag_transport = "1"
result.transport_protocol = params.protocol
else
result.flag_transport = "1"
result.transport_protocol = "udp"
end
if xray_hy2_mode then
result.type = "v2ray"
result.v2ray_protocol = "hysteria2"
if params.fm and params.fm ~= "" then
result.enable_finalmask = "1"
result.finalmask = base64Encode(params.fm)
end
if params.pinSHA256 and params.pinSHA256 ~= "" then
result.pinsha256 = params.pinSHA256
end
else
result.v2ray_protocol = has_xray_hy2_type
end
local raw_alias = url.fragment and UrlDecode(url.fragment) or nil
result.raw_alias = raw_alias -- 新增
result.alias = raw_alias -- 临时赋值(后面会被覆盖)
result.type = hy2_type
result.server = url.host
result.server_port = url.port or 443
result.hy2_auth = url.user
result.uplink_capacity = tonumber((params.upmbps or ""):match("^(%d+)")) or nil
result.downlink_capacity = tonumber((params.downmbps or ""):match("^(%d+)")) or nil
if params.mport then
result.flag_port_hopping = "1"
result.port_range = params.mport
end
if params.obfs and params.obfs ~= "none" then
result.flag_obfs = "1"
result.obfs_type = params.obfs
result.salamander = params["obfs-password"] or params["obfs_password"]
end
if (params.security and params.security:lower() == "tls")
or (params.sni and params.sni ~= "")
or (params.alpn and params.alpn ~= "")
or (xray_hy2_type == "hysteria2" and (params.pcs or params.vcn)) then
result.tls = "1"
if params.sni then
result.tls_host = params.sni
end
if params.alpn and params.alpn ~= "" then
local alpn = {}
for v in params.alpn:gmatch("[^,;|%s]+") do
table.insert(alpn, v)
if (params.security and params.security:lower() == "tls")
or (params.sni and params.sni ~= "")
or (params.alpn and params.alpn ~= "")
or (params.pcs or params.vcn) then
result.tls = "1"
if params.sni then
result.tls_host = params.sni
end
if #alpn > 0 then
result.tls_alpn = table.concat(alpn, ",") -- 确保为字符串
if params.alpn and params.alpn ~= "" then
local alpn = {}
for v in params.alpn:gmatch("[^,;|%s]+") do
table.insert(alpn, v)
end
if #alpn > 0 then
result.tls_alpn = table.concat(alpn, ",") -- 确保为字符串
end
end
end
if xray_hy2_type ~= "hysteria2" then
if params.pcs then
result.tls_CertSha = params.pcs
end
@@ -314,6 +279,55 @@ local function processData(szType, content, cfgid)
result.tls_CertByName = params.vcn
end
end
else
result.type = "hysteria2"
if params.protocol and params.protocol ~= "" then
result.flag_transport = "1"
result.transport_protocol = params.protocol
else
result.flag_transport = "1"
result.transport_protocol = "udp"
end
if params.lazy and params.lazy ~= "" then
result.lazy_mode = "1"
end
if (params.sni and params.sni ~= "") or (params.alpn and params.alpn ~= "") then
result.tls = "1"
if params.sni then
result.tls_host = params.sni
end
if params.alpn and params.alpn ~= "" then
local alpn = {}
for v in params.alpn:gmatch("[^,;|%s]+") do
table.insert(alpn, v)
end
if #alpn > 0 then
result.tls_alpn = table.concat(alpn, ",") -- 确保为字符串
end
end
end
if params.pinSHA256 and params.pinSHA256 ~= "" then
result.pinsha256 = params.pinSHA256
end
end
local raw_alias = url.fragment and UrlDecode(url.fragment) or nil
result.raw_alias = raw_alias -- 新增
result.alias = raw_alias -- 临时赋值(后面会被覆盖)
result.server = url.host
result.server_port = url.port or 443
result.hy2_auth = url.user
if params.mport then
result.flag_port_hopping = "1"
result.port_range = params.mport
end
result.uplink_capacity = tonumber((params.upmbps or ""):match("^(%d+)")) or nil
result.downlink_capacity = tonumber((params.downmbps or ""):match("^(%d+)")) or nil
if params.obfs and params.obfs ~= "none" then
result.flag_obfs = "1"
result.obfs_type = params.obfs
result.salamander = params["obfs-password"] or params["obfs_password"]
end
if params.allowInsecure or params.insecure then
local insecure = params.allowInsecure or params.insecure
@@ -543,153 +557,69 @@ local function processData(szType, content, cfgid)
result.fast_open = params.tfo
end
if v2_ss ~= "v2ray" then
local is_old_format = find_index:find("@") and not find_index:find("://.*@")
local old_base64, host_port, userinfo, server, port, method, password
if is_old_format then
-- 旧格式:base64(method:pass)@host:port
old_base64, host_port = find_index:match("^([^@]+)@(.-)$")
log("SS 节点旧格式解析:", old_base64)
if not old_base64 or not host_port then
log("SS 节点旧格式解析失败:", find_index)
return nil
end
local decoded = base64Decode(UrlDecode(old_base64))
if not decoded then
log("SS base64 解码失败(旧格式):", old_base64)
return nil
end
userinfo = decoded
-- 自动决定模式(true=Xray, false=普通 SS)
local xray_ss_mode = false
if ss_type == "v2ray" then
-- Xray 模式
if has_xray then
xray_ss_mode = true
elseif has_ss_rust or has_ss_libev then
xray_ss_mode = false -- 回退到普通 SS
else
-- 新格式:base64(method:pass@host:port)
local decoded = base64Decode(UrlDecode(find_index))
if not decoded then
log("SS base64 解码失败(新格式):", find_index)
return nil
end
userinfo, host_port = decoded:match("^(.-)@(.-)$")
if not userinfo or not host_port then
log("SS 解码内容缺失 @ 分隔:", decoded)
return nil
end
xray_ss_mode = nil
end
-- 解析加密方式和密码(允许密码包含冒号)
local meth_pass = userinfo:find(":")
if not meth_pass then
log("SS 用户信息格式错误:", userinfo)
return nil
end
method = userinfo:sub(1, meth_pass - 1)
password = userinfo:sub(meth_pass + 1)
-- 判断密码是否经过url编码
local function isURLEncodedPassword(pwd)
if not pwd:find("%%[0-9A-Fa-f][0-9A-Fa-f]") then
return false
end
local ok, decoded = pcall(UrlDecode, pwd)
return ok and urlEncode(decoded) == pwd
end
local decoded = UrlDecode(password)
if isURLEncodedPassword(password) and decoded then
password = decoded
end
-- 解析服务器地址和端口(兼容 IPv6)
if host_port:find("^%[.*%]:%d+$") then
server, port = host_port:match("^%[(.*)%]:(%d+)$")
elseif ss_type == "ss-rust" or ss_type == "ss-libev" then
-- 普通 SS 模式
local user_core = (ss_type == "ss-rust" and has_ss_rust) or (ss_type == "ss-libev" and has_ss_libev)
if user_core then
xray_ss_mode = false -- 否则普通 SS
else
server, port = host_port:match("^(.-):(%d+)$")
end
if not server or not port then
log("SS 节点服务器信息格式错误:", host_port)
return nil
end
-- 如果 SS 程序未安装则跳过订阅
if not v2_ss then
return nil
end
-- 填充 result
result.type = v2_ss
result.encrypt_method_ss = method
result.password = password
result.server = server
result.server_port = port
-- 插件处理
if params.plugin then
local plugin_info = UrlDecode(params.plugin)
local idx_pn = plugin_info:find(";")
if idx_pn then
result.plugin = plugin_info:sub(1, idx_pn - 1)
result.plugin_opts = plugin_info:sub(idx_pn + 1, #plugin_info)
-- 指定的核心不存在,尝试另一个 SS 核心
local other_core = (ss_type == "ss-rust" and has_ss_libev) or (ss_type == "ss-libev" and has_ss_rust)
if other_core then
xray_ss_mode = false -- 使用存在的另一个 SS 核心
elseif has_xray then
xray_ss_mode = true -- 回退到 Xray
else
result.plugin = plugin_info
result.plugin_opts = ""
xray_ss_mode = nil
end
-- 部分机场下发的插件名为 simple-obfs,这里应该改为 obfs-local
if result.plugin == "simple-obfs" then
result.plugin = "obfs-local"
end
-- 如果插件不为 none,确保 enable_plugin 为 1
if result.plugin ~= "none" and result.plugin ~= "" then
result.enable_plugin = 1
end
elseif has_ss_type and has_ss_type ~= "ss-libev" then
if params["shadow-tls"] then
-- 特别处理 shadow-tls 作为插件
-- log("原始 shadow-tls 参数:", params["shadow-tls"])
local decoded_tls = base64Decode(UrlDecode(params["shadow-tls"]))
--log("SS 节点 shadow-tls 解码后:", decoded_tls or "nil")
if decoded_tls then
local ok, st = pcall(jsonParse, decoded_tls)
if ok and st then
result.plugin = "shadow-tls"
result.enable_plugin = 1
local version_flag = ""
if st.version and tonumber(st.version) then
version_flag = string.format("v%s=1;", st.version)
end
-- 合成 plugin_opts 格式:v%s=1;host=xxx;password=xxx
result.plugin_opts = string.format("%shost=%s;passwd=%s",
version_flag,
st.host or "",
st.password or "")
else
log("shadow-tls JSON 解析失败")
end
end
end
else
if params["shadow-tls"] then
log("错误:ShadowSocks-libev 不支持使用 shadow-tls 插件")
return nil, "ShadowSocks-libev 不支持使用 shadow-tls 插件"
end
end
-- 检查加密方法是否受支持
if not checkTabValue(encrypt_methods_ss)[method] then
-- 1202 年了还不支持 SS AEAD 的屑机场
-- log("不支持的SS加密方法:", method)
result.server = nil
end
else
-- ss_type 为空或 auto:根据链接中是否有 type 参数决定
local has_type = params.type and params.type ~= ""
if has_type then
-- 有 type 参数,优先 Xray
if has_xray then
xray_ss_mode = true
elseif has_ss_rust or has_ss_libev then
xray_ss_mode = false -- 回退到普通 SS
else
xray_ss_mode = nil
end
else
-- 无 type 参数,优先普通 SS
if has_ss_rust or has_ss_libev then
-- 普通 SS 模式
xray_ss_mode = false
elseif has_xray then
xray_ss_mode = true -- 回退到 Xray
else
xray_ss_mode = nil
end
end
end
-- 如果最终无可用核心,跳过该订阅
if xray_ss_mode == nil then
return nil
end
if xray_ss_mode then
local url = URL.parse("http://" .. info)
local params = url.query
-- 如果 Xray 程序未安装则跳过订阅
if not v2_ss then
return nil
end
result.type = v2_ss
result.v2ray_protocol = has_v2_ss_type
result.type = "v2ray"
result.v2ray_protocol = "shadowsocks"
result.server = url.host
result.server_port = url.port
@@ -816,6 +746,143 @@ local function processData(szType, content, cfgid)
result.tcp_path = params.path and UrlDecode(params.path) or nil
end
end
else
local is_old_format = find_index:find("@") and not find_index:find("://.*@")
local old_base64, host_port, userinfo, server, port, method, password
if is_old_format then
-- 旧格式:base64(method:pass)@host:port
old_base64, host_port = find_index:match("^([^@]+)@(.-)$")
log("SS 节点旧格式解析:", old_base64)
if not old_base64 or not host_port then
log("SS 节点旧格式解析失败:", find_index)
return nil
end
local decoded = base64Decode(UrlDecode(old_base64))
if not decoded then
log("SS base64 解码失败(旧格式):", old_base64)
return nil
end
userinfo = decoded
else
-- 新格式:base64(method:pass@host:port)
local decoded = base64Decode(UrlDecode(find_index))
if not decoded then
log("SS base64 解码失败(新格式):", find_index)
return nil
end
userinfo, host_port = decoded:match("^(.-)@(.-)$")
if not userinfo or not host_port then
log("SS 解码内容缺失 @ 分隔:", decoded)
return nil
end
end
-- 解析加密方式和密码(允许密码包含冒号)
local meth_pass = userinfo:find(":")
if not meth_pass then
log("SS 用户信息格式错误:", userinfo)
return nil
end
method = userinfo:sub(1, meth_pass - 1)
password = userinfo:sub(meth_pass + 1)
-- 判断密码是否经过url编码
local function isURLEncodedPassword(pwd)
if not pwd:find("%%[0-9A-Fa-f][0-9A-Fa-f]") then
return false
end
local ok, decoded = pcall(UrlDecode, pwd)
return ok and urlEncode(decoded) == pwd
end
local decoded = UrlDecode(password)
if isURLEncodedPassword(password) and decoded then
password = decoded
end
-- 解析服务器地址和端口(兼容 IPv6)
if host_port:find("^%[.*%]:%d+$") then
server, port = host_port:match("^%[(.*)%]:(%d+)$")
else
server, port = host_port:match("^(.-):(%d+)$")
end
if not server or not port then
log("SS 节点服务器信息格式错误:", host_port)
return nil
end
-- 填充 result
local xray_ss_type
if ss_type == "ss-rust" or ss_type == "ss-libev" then
xray_ss_type = ss_type
else
xray_ss_type = has_ss_rust and "ss-rust" or "ss-libev"
end
result.type = xray_ss_type
result.encrypt_method_ss = method
result.password = password
result.server = server
result.server_port = port
-- 插件处理
if params.plugin then
local plugin_info = UrlDecode(params.plugin)
local idx_pn = plugin_info:find(";")
if idx_pn then
result.plugin = plugin_info:sub(1, idx_pn - 1)
result.plugin_opts = plugin_info:sub(idx_pn + 1, #plugin_info)
else
result.plugin = plugin_info
result.plugin_opts = ""
end
-- 部分机场下发的插件名为 simple-obfs,这里应该改为 obfs-local
if result.plugin == "simple-obfs" then
result.plugin = "obfs-local"
end
-- 如果插件不为 none,确保 enable_plugin 为 1
if result.plugin ~= "none" and result.plugin ~= "" then
result.enable_plugin = 1
end
elseif has_ss_type and has_ss_type ~= "ss-libev" then
if params["shadow-tls"] then
-- 特别处理 shadow-tls 作为插件
-- log("原始 shadow-tls 参数:", params["shadow-tls"])
local decoded_tls = base64Decode(UrlDecode(params["shadow-tls"]))
--log("SS 节点 shadow-tls 解码后:", decoded_tls or "nil")
if decoded_tls then
local ok, st = pcall(jsonParse, decoded_tls)
if ok and st then
result.plugin = "shadow-tls"
result.enable_plugin = 1
local version_flag = ""
if st.version and tonumber(st.version) then
version_flag = string.format("v%s=1;", st.version)
end
-- 合成 plugin_opts 格式:v%s=1;host=xxx;password=xxx
result.plugin_opts = string.format("%shost=%s;passwd=%s",
version_flag,
st.host or "",
st.password or "")
else
log("shadow-tls JSON 解析失败")
end
end
end
else
if params["shadow-tls"] then
log("错误:ShadowSocks-libev 不支持使用 shadow-tls 插件")
return nil, "ShadowSocks-libev 不支持使用 shadow-tls 插件"
end
end
-- 检查加密方法是否受支持
if not checkTabValue(encrypt_methods_ss)[method] then
-- 1202 年了还不支持 SS AEAD 的屑机场
-- log("不支持的SS加密方法:", method)
result.server = nil
end
end
elseif szType == "sip008" then
result.type = v2_ss
@@ -936,102 +1003,143 @@ local function processData(szType, content, cfgid)
result.server_port = port
end
-- 如果 Tojan 程序未安装则跳过订阅
if not v2_tj or v2_tj == "" then
-- 自动决定模式(true=Xray, false=普通 Trojan)
local xray_tj_mode = false
if xray_tj_type == "v2ray" then
-- Xray 模式
if has_xray then
xray_tj_mode = true
elseif has_trojan then
xray_tj_mode = false -- 回退到普通 Trojan
else
xray_tj_mode = nil -- 两类核心均不存在,停止订阅
end
elseif xray_tj_type == "trojan" then
-- 普通 Trojan 模式
if has_trojan then
xray_tj_mode = false
elseif has_xray then
xray_tj_mode = true -- 回退到 Xray
else
xray_tj_mode = nil -- 两类核心均不存在,停止订阅
end
else
-- 全局配置为空或 auto,根据链接中是否有 type 参数决定
local has_type = params.type and params.type ~= ""
-- 有 type 参数,优先 Xray
if has_type then
if has_xray then
xray_tj_mode = true -- 有 type 参数使用 Xray
elseif has_trojan then
xray_tj_mode = false -- 否则普通 Trojan
else
xray_tj_mode = nil -- 两类核心均不存在,停止订阅
end
else
-- 无 type 参数,优先普通 Trojan
if has_trojan then
xray_tj_mode = false -- 普通 Trojan
elseif has_xray then
xray_tj_mode = true -- 否则使用 Xray
else
xray_tj_mode = nil -- 两类核心均不存在,停止订阅
end
end
end
-- 如果最终无可用核心,跳过该订阅
if xray_tj_mode == nil then
return nil
end
if params.type and params.type ~= "" then
v2_tj = "v2ray"
result.type = v2_tj
if xray_tj_mode then
result.type = "v2ray"
result.v2ray_protocol = "trojan"
if v2_tj ~= "trojan" then
if params.fp then
-- 处理 fingerprint 参数
result.fingerprint = params.fp
end
-- 处理 ech 参数
if params.ech and params.ech ~= "" then
result.enable_ech = "1"
result.ech_config = params.ech
end
-- 检查 finalmaskg 参数是否存在且非空
if params.fm and params.fm ~= "" then
result.enable_finalmask = "1"
result.finalmaskg = base64Encode(params.fm)
end
-- 处理传输协议
result.transport = params.type or "raw" -- 默认传输协议为 raw
if result.transport == "tcp" then
result.transport = "raw"
end
if result.transport == "splithttp" then
result.transport = "xhttp"
end
if params.pcs and params.pcs ~= "" then
result.tls_CertSha = params.pcs
end
if params.vcn and params.vcn ~= "" then
result.tls_CertByName = params.vcn
end
if result.transport == "ws" then
result.ws_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
result.ws_path = params.path and UrlDecode(params.path) or "/"
elseif result.transport == "httpupgrade" then
result.httpupgrade_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
result.httpupgrade_path = params.path and UrlDecode(params.path) or "/"
elseif result.transport == "xhttp" or result.transport == "splithttp" then
result.xhttp_mode = params.mode or "auto"
result.xhttp_host = params.host and UrlDecode(params.host) or nil
result.xhttp_path = params.path and UrlDecode(params.path) or "/"
-- 检查 extra 参数是否存在且非空
if params.extra and params.extra ~= "" then
result.enable_xhttp_extra = "1"
result.xhttp_extra = base64Encode(params.extra)
end
-- 尝试解析 JSON 数据
local success, Data = pcall(jsonParse, params.extra or "")
if success and type(Data) == "table" then
local address = (Data.extra and Data.extra.downloadSettings and Data.extra.downloadSettings.address)
or (Data.downloadSettings and Data.downloadSettings.address)
result.download_address = (address and address ~= "") and address:gsub("^%[", ""):gsub("%]$", "")
else
-- 如果解析失败,清空下载地址
result.download_address = nil
end
elseif result.transport == "http" or result.transport == "h2" then
result.transport = "h2"
result.h2_host = params.host and UrlDecode(params.host) or nil
result.h2_path = params.path and UrlDecode(params.path) or nil
elseif result.transport == "kcp" then
result.kcp_guise = params.headerType or "none"
if params.headerType and params.headerType == "dns" then
result.kcp_domain = params.host or ""
end
result.seed = params.seed
result.mtu = 1350
result.tti = 50
result.uplink_capacity = 5
result.downlink_capacity = 20
result.read_buffer_size = 2
result.write_buffer_size = 2
elseif result.transport == "quic" then
result.quic_guise = params.headerType or "none"
result.quic_security = params.quicSecurity or "none"
result.quic_key = params.key
elseif result.transport == "grpc" then
result.serviceName = params.serviceName
result.grpc_mode = params.mode or "gun"
elseif result.transport == "tcp" or result.transport == "raw" then
result.tcp_guise = params.headerType and params.headerType ~= "" and params.headerType or "none"
if result.tcp_guise == "http" then
result.tcp_host = params.host and UrlDecode(params.host) or nil
result.tcp_path = params.path and UrlDecode(params.path) or nil
end
end
else
result.type = v2_tj
if params.fp then
-- 处理 fingerprint 参数
result.fingerprint = params.fp
end
-- 处理 ech 参数
if params.ech and params.ech ~= "" then
result.enable_ech = "1"
result.ech_config = params.ech
end
-- 检查 finalmaskg 参数是否存在且非空
if params.fm and params.fm ~= "" then
result.enable_finalmask = "1"
result.finalmaskg = base64Encode(params.fm)
end
-- 处理传输协议
result.transport = params.type or "raw" -- 默认传输协议为 raw
if result.transport == "tcp" then
result.transport = "raw"
end
if result.transport == "splithttp" then
result.transport = "xhttp"
end
if params.pcs and params.pcs ~= "" then
result.tls_CertSha = params.pcs
end
if params.vcn and params.vcn ~= "" then
result.tls_CertByName = params.vcn
end
if result.transport == "ws" then
result.ws_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
result.ws_path = params.path and UrlDecode(params.path) or "/"
elseif result.transport == "httpupgrade" then
result.httpupgrade_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
result.httpupgrade_path = params.path and UrlDecode(params.path) or "/"
elseif result.transport == "xhttp" or result.transport == "splithttp" then
result.xhttp_mode = params.mode or "auto"
result.xhttp_host = params.host and UrlDecode(params.host) or nil
result.xhttp_path = params.path and UrlDecode(params.path) or "/"
-- 检查 extra 参数是否存在且非空
if params.extra and params.extra ~= "" then
result.enable_xhttp_extra = "1"
result.xhttp_extra = base64Encode(params.extra)
end
-- 尝试解析 JSON 数据
local success, Data = pcall(jsonParse, params.extra or "")
if success and type(Data) == "table" then
local address = (Data.extra and Data.extra.downloadSettings and Data.extra.downloadSettings.address)
or (Data.downloadSettings and Data.downloadSettings.address)
result.download_address = (address and address ~= "") and address:gsub("^%[", ""):gsub("%]$", "")
else
-- 如果解析失败,清空下载地址
result.download_address = nil
end
elseif result.transport == "http" or result.transport == "h2" then
result.transport = "h2"
result.h2_host = params.host and UrlDecode(params.host) or nil
result.h2_path = params.path and UrlDecode(params.path) or nil
elseif result.transport == "kcp" then
result.kcp_guise = params.headerType or "none"
if params.headerType and params.headerType == "dns" then
result.kcp_domain = params.host or ""
end
result.seed = params.seed
result.mtu = 1350
result.tti = 50
result.uplink_capacity = 5
result.downlink_capacity = 20
result.read_buffer_size = 2
result.write_buffer_size = 2
elseif result.transport == "quic" then
result.quic_guise = params.headerType or "none"
result.quic_security = params.quicSecurity or "none"
result.quic_key = params.key
elseif result.transport == "grpc" then
result.serviceName = params.serviceName
result.grpc_mode = params.mode or "gun"
elseif result.transport == "tcp" or result.transport == "raw" then
result.tcp_guise = params.headerType and params.headerType ~= "" and params.headerType or "none"
if result.tcp_guise == "http" then
result.tcp_host = params.host and UrlDecode(params.host) or nil
result.tcp_path = params.path and UrlDecode(params.path) or nil
end
end
else
result.type = "trojan"
end
elseif szType == "vless" then
local url = URL.parse("http://" .. content)