diff --git a/luci-app-fchomo/root/usr/libexec/fchomo/update_resources.sh b/luci-app-fchomo/root/usr/libexec/fchomo/update_resources.sh
index 68f83aef..a55b11af 100755
--- a/luci-app-fchomo/root/usr/libexec/fchomo/update_resources.sh
+++ b/luci-app-fchomo/root/usr/libexec/fchomo/update_resources.sh
@@ -17,7 +17,7 @@ log() {
}
to_upper() {
- echo -e "$1" | tr "[a-z]" "[A-Z]"
+ echo -e "$1" | tr "a-z" "A-Z"
}
get_local_ver() {
diff --git a/luci-app-fchomo/root/usr/share/fchomo/generate_client.uc b/luci-app-fchomo/root/usr/share/fchomo/generate_client.uc
index 3d2f18db..c0c56bd5 100644
--- a/luci-app-fchomo/root/usr/share/fchomo/generate_client.uc
+++ b/luci-app-fchomo/root/usr/share/fchomo/generate_client.uc
@@ -82,7 +82,7 @@ const listen_interfaces = uci.get(uciconf, uciroute, 'listen_interfaces') || nul
lan_proxy_mac_addrs = uci.get(uciconf, uciroute, 'lan_proxy_mac_addrs') || null,
proxy_router = (uci.get(uciconf, uciroute, 'proxy_router') === '0') ? null : true,
top_upstream = (uci.get(uciconf, uciroute, 'top_upstream') === '1') || null,
- client_enabled = uci.get(uciconf, uciroute, 'client_enabled' === '1') || null,
+ client_enabled = (uci.get(uciconf, uciroute, 'client_enabled') === '1') || null,
routing_tcpport = uci.get(uciconf, uciroute, 'routing_tcpport') || [],
routing_udpport = uci.get(uciconf, uciroute, 'routing_udpport') || [],
routing_mode = uci.get(uciconf, uciroute, 'routing_mode') || null,
diff --git a/luci-app-fchomo/root/usr/share/rpcd/ucode/luci.fchomo b/luci-app-fchomo/root/usr/share/rpcd/ucode/luci.fchomo
index a399543d..a258d915 100644
--- a/luci-app-fchomo/root/usr/share/rpcd/ucode/luci.fchomo
+++ b/luci-app-fchomo/root/usr/share/rpcd/ucode/luci.fchomo
@@ -50,7 +50,7 @@ const methods = {
const type = req.args?.type;
let result = {};
- const fd = popen('/usr/bin/mihomo generate ' + type + ` ${req.args?.params ?? ''}`);
+ const fd = popen('/usr/bin/mihomo generate ' + type + (req.args?.params ? ' ' + shellQuote(req.args.params) : ''));
if (fd) {
for (let line = fd.read('line'); length(line); line = fd.read('line')) {
if (type === 'uuid')
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua
index 444c40bf..431a60dc 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua
@@ -184,11 +184,16 @@ if has_xray then
o.default = "100-200"
o:depends("fragment", true)
- o = s_xray:option(Value, "fragment_interval", translate("Fragment Interval"), translate("Fragmentation interval (ms)"))
+ o = s_xray:option(Value, "fragment_delay", translate("Fragment Delay"), translate("Fragmentation interval (ms)"))
o.datatype = "or(uinteger,portrange)"
o.default = "10-20"
o:depends("fragment", true)
+ o = s_xray:option(Value, "fragment_maxSplit", translate("Max Split"), translate("Limit the maximum number of splits."))
+ o.datatype = "or(uinteger,portrange)"
+ o.default = "100-200"
+ o:depends("fragment", true)
+
o = s_xray:option(Flag, "noise", translate("Noise"), translate("UDP noise, Under some circumstances it can bypass some UDP based protocol restrictions."))
o.default = 0
@@ -231,11 +236,12 @@ if has_xray then
o = s_xray_noise:option(ListValue, "type", translate("Type"))
o:value("rand", "rand")
+ o:value("array", "array")
o:value("str", "str")
o:value("hex", "hex")
o:value("base64", "base64")
- o = s_xray_noise:option(Value, "packet", translate("Packet"))
+ o = s_xray_noise:option(Value, "packet", translate("Packet | Rand Length"))
o.datatype = "minlength(1)"
o.rmempty = false
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua
index c677aadc..e06eb72d 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua
@@ -67,8 +67,10 @@ o.rewrite_option = o.option
o = s:option(Value, _n("recv_window_conn"), translate("QUIC connection receive window"))
o.rewrite_option = o.option
+o = s:option(Value, _n("idle_timeout"), translate("Idle Timeout"), translate("Example:") .. "30s (4s~120s)")
+o.rewrite_option = o.option
-o = s:option(Value, _n("idle_timeout"), translate("Idle Timeout"), translate("Example:") .. "30s (4s-120s)")
+o = s:option(Value, _n("keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Example:") .. "10s (2s~60s)")
o.rewrite_option = o.option
o = s:option(Flag, _n("disable_mtu_discovery"), translate("Disable MTU detection"))
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
index 6c0cd877..24f295d6 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
@@ -328,7 +328,10 @@ o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password"))
o.password = true
o:depends({ [_n("protocol")] = "hysteria2"})
-o = s:option(Value, _n("hysteria2_idle_timeout"), translate("Idle Timeout"), translate("Example:") .. "30s (4s-120s)")
+o = s:option(Value, _n("hysteria2_idle_timeout"), translate("Idle Timeout"), translate("Example:") .. "30s (4s~120s)")
+o:depends({ [_n("protocol")] = "hysteria2"})
+
+o = s:option(Value, _n("hysteria2_keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Example:") .. "10s (2s~60s)")
o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(Flag, _n("hysteria2_disable_mtu_discovery"), translate("Disable MTU detection"))
@@ -406,7 +409,7 @@ o.validate = function(self, value)
end
o = s:option(ListValue, _n("ech_ForceQuery"), translate("ECH Query Policy"), translate("Controls the policy used when performing DNS queries for ECH configuration."))
-o.default = "none"
+o.default = "full"
o:value("none")
o:value("half")
o:value("full")
@@ -694,10 +697,12 @@ o:depends({ [_n("protocol")] = "shadowsocks" })
o:depends({ [_n("protocol")] = "wireguard" })
o:depends({ [_n("protocol")] = "hysteria2" })
-o = s:option(TextValue, _n("finalmask"), " ", translate("An FinalMaskObject in JSON format, used for sharing."))
+o = s:option(TextValue, _n("finalmask"), " ")
o:depends({ [_n("use_finalmask")] = true })
o.rows = 10
o.wrap = "off"
+o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "
" ..
+ translate("Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings.")
o.custom_cfgvalue = function(self, section, value)
local raw = m:get(section, "finalmask")
if raw then
diff --git a/luci-app-passwall/luasrc/passwall/util_hysteria2.lua b/luci-app-passwall/luasrc/passwall/util_hysteria2.lua
index 4053bc91..f69c7164 100644
--- a/luci-app-passwall/luasrc/passwall/util_hysteria2.lua
+++ b/luci-app-passwall/luasrc/passwall/util_hysteria2.lua
@@ -90,14 +90,14 @@ function gen_config(var)
quic = {
initStreamReceiveWindow = (node.hysteria2_recv_window) and tonumber(node.hysteria2_recv_window) or nil,
initConnReceiveWindow = (node.hysteria2_recv_window_conn) and tonumber(node.hysteria2_recv_window_conn) or nil,
- maxIdleTimeout = (function()
- local timeoutStr = tostring(node.hysteria2_idle_timeout or "")
- local timeout = tonumber(timeoutStr:match("^%d+"))
- if timeout and timeout >= 4 and timeout <= 120 then
- return tostring(timeout) .. "s"
- end
- return nil
- end)(),
+ maxIdleTimeout = (function(t)
+ t = tonumber(tostring(t or "30"):match("^%d+"))
+ return (t and t >= 4 and t <= 120) and t .. "s" or "30s"
+ end)(node.hysteria2_idle_timeout),
+ keepAlivePeriod = (function(t)
+ t = tonumber(tostring(t or "0"):match("^%d+"))
+ return (t and t >= 2 and t <= 60) and t .. "s" or nil
+ end)(node.hysteria2_keep_alive_period),
disablePathMTUDiscovery = (node.hysteria2_disable_mtu_discovery) and true or false,
},
bandwidth = (node.hysteria2_up_mbps or node.hysteria2_down_mbps) and {
diff --git a/luci-app-passwall/luasrc/passwall/util_xray.lua b/luci-app-passwall/luasrc/passwall/util_xray.lua
index f340bb64..f00ae406 100644
--- a/luci-app-passwall/luasrc/passwall/util_xray.lua
+++ b/luci-app-passwall/luasrc/passwall/util_xray.lua
@@ -8,20 +8,6 @@ local fs = api.fs
local xray_version = api.get_app_version("xray")
-local function get_noise_packets()
- local noises = {}
- uci:foreach(appname, "xray_noise_packets", function(n)
- local noise = (n.enabled == "1") and {
- type = n.type,
- packet = n.packet,
- delay = string.find(n.delay, "-") and n.delay or tonumber(n.delay)
- } or nil
- table.insert(noises, noise)
- end)
- if #noises == 0 then noises = nil end
- return noises
-end
-
local function get_domain_excluded()
local path = string.format("/usr/share/%s/rules/domains_excluded", appname)
local content = fs.readfile(path)
@@ -149,8 +135,7 @@ function gen_outbound(flag, node, tag, proxy_table)
sockopt = {
mark = 255,
tcpFastOpen = (node.tcp_fast_open == "1") and true or nil,
- tcpMptcp = (node.tcpMptcp == "1") and true or nil,
- dialerProxy = (fragment or noise) and "dialerproxy" or nil
+ tcpMptcp = (node.tcpMptcp == "1") and true or nil
},
network = node.transport,
security = node.stream_security,
@@ -172,7 +157,7 @@ function gen_outbound(flag, node, tag, proxy_table)
return node.tls_CertByName
end)(),
echConfigList = (node.ech == "1") and node.ech_config or nil,
- echForceQuery = (node.ech == "1") and (node.ech_ForceQuery or "none") or nil
+ echForceQuery = (node.ech == "1") and (node.ech_ForceQuery or "full") or nil
} or nil,
realitySettings = (node.stream_security == "reality") and {
serverName = node.tls_serverName,
@@ -263,7 +248,8 @@ function gen_outbound(flag, node, tag, proxy_table)
} or nil,
finalmask = (function()
local finalmask = {}
- if node.transport == "mkcp" then
+ local TP = node.transport
+ if TP == "mkcp" then
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
local udp = {}
@@ -280,7 +266,7 @@ function gen_outbound(flag, node, tag, proxy_table)
end
udp[#udp+1] = c
finalmask.udp = udp
- elseif node.transport == "hysteria" then
+ elseif TP == "hysteria" then
if node.hysteria2_obfs_type and node.hysteria2_obfs_type ~= "" then
finalmask.udp = {{
type = node.hysteria2_obfs_type,
@@ -306,9 +292,20 @@ function gen_outbound(flag, node, tag, proxy_table)
t = tonumber(tostring(t or "30"):match("^%d+"))
return (t and t >= 4 and t <= 120) and t or 30
end)(node.hysteria2_idle_timeout),
+ keepAlivePeriod = (function(t)
+ t = tonumber(tostring(t or "0"):match("^%d+"))
+ return (t and t >= 2 and t <= 60) and t or nil
+ end)(node.hysteria2_keep_alive_period),
disablePathMTUDiscovery = tonumber(node.hysteria2_disable_mtu_discovery) == 1
}
end
+ if fragment and fragment_table and ({raw=1, ws=1, httpupgrade=1, grpc=1, xhttp=1})[TP] then
+ finalmask.tcp = finalmask.tcp or {}
+ finalmask.tcp[#finalmask.tcp+1] = api.clone(fragment_table)
+ end
+ if noise and noise_table and (TP == "mkcp" or (TP == "xhttp" and node.alpn == "h3")) then
+ finalmask.udp[#finalmask.udp+1] = api.clone(noise_table)
+ end
if node.finalmask and node.finalmask ~= "" then
local ok, fm = pcall(jsonc.parse, api.base64Decode(node.finalmask))
if ok and type(fm) == "table" then
@@ -755,6 +752,38 @@ function gen_config(var)
local xray_settings = uci:get_all(appname, "@global_xray[0]") or {}
+ if xray_settings.fragment == "1" then
+ local delay = xray_settings.fragment_delay
+ fragment_table = {
+ type = "fragment",
+ settings = {
+ packets = xray_settings.fragment_packets,
+ length = xray_settings.fragment_length,
+ delay = string.find(delay, "-") and delay or tonumber(delay),
+ maxSplit = xray_settings.fragment_maxSplit
+ }
+ }
+ end
+
+ if xray_settings.noise == "1" then
+ local noises = {}
+ uci:foreach(appname, "xray_noise_packets", function(n)
+ if n.enabled == "1" then
+ local noise = {
+ rand = (n.type == "rand") and (string.find(n.packet, "-") and n.packet or tonumber(n.packet)) or nil,
+ type = (n.type ~= "rand") and n.type or nil,
+ packet = (n.type ~= "rand") and n.packet or nil,
+ delay = string.find(n.delay, "-") and n.delay or tonumber(n.delay)
+ }
+ table.insert(noises, noise)
+ end
+ end)
+ noise_table = #noises > 0 and {
+ type = "noise",
+ settings = { reset = 0, noise = noises }
+ } or nil
+ end
+
if node_id then
local node = uci:get_all(appname, node_id)
local balancers = {}
@@ -1663,27 +1692,6 @@ function gen_config(var)
}
}
- if xray_settings.fragment == "1" or xray_settings.noise == "1" then
- table.insert(outbounds, {
- protocol = "freedom",
- tag = "dialerproxy",
- settings = {
- domainStrategy = (direct_dns_query_strategy and direct_dns_query_strategy ~= "") and direct_dns_query_strategy or "UseIP",
- fragment = (xray_settings.fragment == "1") and {
- packets = (xray_settings.fragment_packets and xray_settings.fragment_packets ~= "") and xray_settings.fragment_packets,
- length = (xray_settings.fragment_length and xray_settings.fragment_length ~= "") and xray_settings.fragment_length,
- interval = (xray_settings.fragment_interval and xray_settings.fragment_interval ~= "") and xray_settings.fragment_interval
- } or nil,
- noises = (xray_settings.noise == "1") and get_noise_packets() or nil
- },
- streamSettings = {
- sockopt = {
- mark = 255
- }
- }
- })
- end
-
local direct_outbound = {
protocol = "freedom",
tag = "direct",
diff --git a/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm b/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm
index e179bc47..83165135 100644
--- a/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm
+++ b/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm
@@ -1312,12 +1312,7 @@ local current_node = map:get(section)
queryParam.security = queryParam.security || "tls";
opt.set(dom_prefix + 'tls', queryParam.security === "tls");
if (queryParam.security === "tls") {
- var tls_serverName = queryParam.peer;
- if (queryParam.sni) {
- tls_serverName = queryParam.sni
- }
- tls_serverName = tls_serverName || "";
- opt.set(dom_prefix + 'tls_serverName', tls_serverName);
+ opt.set(dom_prefix + 'tls_serverName', queryParam.peer || queryParam.sni || "");
opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
opt.set(dom_prefix + 'tls_CertSha', queryParam.pcs || '');
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
diff --git a/luci-app-passwall/po/zh-cn/passwall.po b/luci-app-passwall/po/zh-cn/passwall.po
index 3bd1ebc2..fd697d9c 100644
--- a/luci-app-passwall/po/zh-cn/passwall.po
+++ b/luci-app-passwall/po/zh-cn/passwall.po
@@ -706,6 +706,9 @@ msgstr "QUIC 连接接收窗口"
msgid "QUIC concurrent bidirectional streams"
msgstr "QUIC 并发双向流的最大数量"
+msgid "QUIC KeepAlive interval"
+msgstr "QUIC KeepAlive 间隔"
+
msgid "Disable MTU detection"
msgstr "禁用 MTU 检测"
@@ -1855,6 +1858,9 @@ msgstr "启用 Multipath TCP,需在服务端和客户端配置中同时启用
msgid "An FinalMaskObject in JSON format, used for sharing."
msgstr "JSON 格式的 FinalMaskObject,用来实现分享。"
+msgid "Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings."
+msgstr "自定义 finalmask 将覆盖 mkcp、hysteria2、fragment、noise 等相关配置。"
+
msgid "Fragment"
msgstr "分片"
@@ -1873,12 +1879,18 @@ msgstr "分片包长"
msgid "Fragmented packet length (byte)"
msgstr "分片包长 (byte)"
-msgid "Fragment Interval"
-msgstr "分片间隔"
+msgid "Fragment Delay"
+msgstr "分片延迟"
msgid "Fragmentation interval (ms)"
msgstr "分片间隔(ms)"
+msgid "Max Split"
+msgstr "最大分片数"
+
+msgid "Limit the maximum number of splits."
+msgstr "限制分片的最大数量。"
+
msgid "Split handshake data into multiple TLS records for better censorship evasion. Low overhead. Recommended to enable first."
msgstr "将握手数据拆分为多个 TLS 记录,提升抗封锁能力,几乎不增加延迟,建议优先启用。"
@@ -1897,8 +1909,8 @@ msgstr "在 Xray 设置中勾选 “噪声” 以发送噪声包。"
msgid "Xray Noise Packets"
msgstr "Xray 噪声数据包"
-msgid "Packet"
-msgstr "数据包"
+msgid "Packet | Rand Length"
+msgstr "数据包 | Rand 长度"
msgid "Delay (ms)"
msgstr "延迟(ms)"
diff --git a/luci-app-passwall/root/usr/share/passwall/subscribe.lua b/luci-app-passwall/root/usr/share/passwall/subscribe.lua
index 938aa945..636a9740 100755
--- a/luci-app-passwall/root/usr/share/passwall/subscribe.lua
+++ b/luci-app-passwall/root/usr/share/passwall/subscribe.lua
@@ -997,14 +997,10 @@ local function processData(szType, content, add_mode, group)
else
result.address = host_port
end
-
- local peer, sni = nil, ""
- if params.peer then peer = params.peer end
- sni = params.sni and params.sni or ""
result.port = port
result.tls = '1'
- result.tls_serverName = peer and peer or sni
+ result.tls_serverName = params.peer or params.sni or ""
result.tls_CertSha = params.pcs
result.tls_CertByName = params.vcn
@@ -1346,7 +1342,7 @@ local function processData(szType, content, add_mode, group)
result.hysteria_obfs = params.obfsParam
result.hysteria_auth_type = "string"
result.hysteria_auth_password = params.auth
- result.tls_serverName = params.peer
+ result.tls_serverName = params.peer or params.sni or ""
params.allowinsecure = params.allowinsecure or params.insecure
if params.allowinsecure and (params.allowinsecure == "1" or params.allowinsecure == "0") then
result.tls_allowInsecure = params.allowinsecure
diff --git a/luci-app-ssr-plus/Makefile b/luci-app-ssr-plus/Makefile
index 27eeefa4..9cbb8305 100644
--- a/luci-app-ssr-plus/Makefile
+++ b/luci-app-ssr-plus/Makefile
@@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-ssr-plus
PKG_VERSION:=190
-PKG_RELEASE:=3
+PKG_RELEASE:=4
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua
index d89fa115..b2950b93 100644
--- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua
+++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua
@@ -1323,7 +1323,7 @@ if is_finded("xray") then
o = s:option(ListValue, "ech_ForceQuery", translate("ECH Query Policy"))
o.description = translate("Controls the policy used when performing DNS queries for ECH configuration.")
- o.default = "none"
+ o.default = "full"
o:value("none")
o:value("half")
o:value("full")
diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm
index 21f21326..0d0eb3c2 100644
--- a/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm
+++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm
@@ -40,9 +40,13 @@ table td, .table .td {
.cbi-section-table th:nth-child(2) {
width: 180px;
max-width: 180px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ cursor: help;
+}
+
+.cbi-section-table td:nth-child(2) div {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
.node-wrapper {
@@ -115,9 +119,9 @@ table td, .table .td {
}*/
.socket-connected {
- flex: 0 0 40px;
- white-space: nowrap;
- min-width: 0;
+ flex: 0 0 40px;
+ white-space: nowrap;
+ min-width: 0;
}
@media screen and (max-width: 1152px) {
@@ -160,6 +164,14 @@ table td, .table .td {
color: #555;
}
+ /* 手机端完整显示节点名称 */
+ .cbi-section-table td:nth-child(2) div {
+ overflow: visible;
+ white-space: normal;
+ text-overflow: clip;
+ word-break: break-word;
+ }
+
.cbi-section-table td.cbi-section-actions {
display: block !important;
width: 100% !important;
@@ -697,6 +709,15 @@ table td, .table .td {
// 初始化排序功能
initSortable();
+ // 为节点名称列添加 title 属性
+ document.querySelectorAll("#cbi-shadowsocksr-servers table tbody tr").forEach(function(row) {
+ var nameCell = row.querySelector("td:nth-child(2)");
+ if (nameCell) {
+ var fullName = nameCell.innerText.trim();
+ if (fullName) nameCell.setAttribute("title", fullName);
+ }
+ });
+
// 绑定保存按钮点击事件
var saveBtn = document.getElementById("save_order_btn");
if (saveBtn) {
diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua
index 8f43b978..87700815 100755
--- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua
+++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua
@@ -337,7 +337,7 @@ end
return server.tls_CertByName
end)(),
echConfigList = (server.enable_ech == "1") and server.ech_config or nil,
- echForceQuery = (server.enable_ech == "1") and (server.ech_ForceQuery or "none") or nil
+ echForceQuery = (server.enable_ech == "1") and (server.ech_ForceQuery or "full") or nil
} or nil,
xtlsSettings = (server.xtls == '1') and server.tls_host and {
-- xtls
diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua
index af1bc987..ce196877 100755
--- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua
+++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua
@@ -217,7 +217,7 @@ local function checkTabValue(tab)
end
-- JSON完整性检查
local function isCompleteJSON(str)
- -- 检查JSON格式
+ -- 检查JSON格式
if type(str) ~= "string" or str:match("^%s*$") then
return false
end
@@ -269,7 +269,9 @@ local function processData(szType, content, cfgid)
result.v2ray_protocol = has_xray_hy2_type
end
- result.alias = url.fragment and UrlDecode(url.fragment) or nil
+ 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
@@ -360,10 +362,11 @@ local function processData(szType, content, cfgid)
-- 拼接 alias
local alias = ""
if group ~= "" then
- alias = "[" .. group .. "] "
+ raw_alias = "[" .. group .. "] "
end
- alias = alias .. remarks
- result.alias = alias
+ raw_alias = raw_alias .. remarks
+ result.raw_alias = raw_alias -- 新增
+ result.alias = raw_alias -- 临时赋值(后面会被覆盖)
elseif szType == "vmess" then
-- 去掉前后空白和注释
local link = trim(content:gsub("#.*$", ""))
@@ -387,7 +390,8 @@ local function processData(szType, content, cfgid)
result.server_port = info.port
result.alter_id = info.aid
result.vmess_id = info.id
- result.alias = info.ps
+ result.raw_alias = info.ps -- 新增
+ result.alias = info.ps -- 临时赋值(后面会被覆盖)
-- 调整传输协议
if info.net == "tcp" then
@@ -515,7 +519,9 @@ local function processData(szType, content, cfgid)
alias = content:sub(idx_sp + 1, -1)
content = content:sub(0, idx_sp - 1):gsub("/%?", "?")
end
- result.alias = UrlDecode(alias)
+ local raw_alias = UrlDecode(alias)
+ result.raw_alias = raw_alias -- 新增
+ result.alias = raw_alias -- 临时赋值(后面会被覆盖)
-- 拆 base64 主体和 ? 参数部分
local info = content
@@ -582,13 +588,13 @@ local function processData(szType, content, cfgid)
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
+ 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
+ if isURLEncodedPassword(password) and decoded then
+ password = decoded
end
-- 解析服务器地址和端口(兼容 IPv6)
@@ -642,18 +648,16 @@ local function processData(szType, content, cfgid)
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)
+ 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,
+ version_flag,
st.host or "",
st.password or "")
else
@@ -694,14 +698,14 @@ local function processData(szType, content, cfgid)
-- 新格式:method:password
result.encrypt_method_ss, result.password = is_base64:match("^(.-):(.*)$")
else
- -- 旧格式:UUID 直接作为密码
- result.password = url.user
- result.encrypt_method_ss = params.encryption or "none"
+ -- 旧格式:UUID 直接作为密码
+ result.password = url.user
+ result.encrypt_method_ss = params.encryption or "none"
end
if params.udp then
- -- 处理 udp 参数
- result.uot = params.udp
+ -- 处理 udp 参数
+ result.uot = params.udp
end
result.transport = params.type or "raw"
@@ -806,7 +810,7 @@ local function processData(szType, content, cfgid)
result.grpc_mode = params.mode or "gun"
elseif result.transport == "tcp" or result.transport == "raw" then
result.tcp_guise = params.headerType or "none"
- if result.tcp_guise == "http" then
+ 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
@@ -826,7 +830,8 @@ local function processData(szType, content, cfgid)
result.encrypt_method_ss = content.method
result.plugin = content.plugin
result.plugin_opts = content.plugin_opts
- result.alias = content.remarks
+ result.raw_alias = content.remarks -- 新增
+ result.alias = content.remarks -- 临时赋值(后面会被覆盖)
if not checkTabValue(encrypt_methods_ss)[content.method] then
result.server = nil
end
@@ -843,7 +848,9 @@ local function processData(szType, content, cfgid)
result.password = content.password
result.encrypt_method_ss = content.method
result.plugin_opts = content.plugin_options
- result.alias = "[" .. content.airport .. "] " .. content.remarks
+ local raw_alias = "[" .. content.airport .. "] " .. content.remarks
+ result.raw_alias = raw_alias -- 新增
+ result.alias = raw_alias -- 临时赋值(后面会被覆盖)
if content.plugin == "simple-obfs" then
result.plugin = "obfs-local"
else
@@ -860,11 +867,13 @@ local function processData(szType, content, cfgid)
alias = content:sub(idx_sp + 1, -1)
content = content:sub(0, idx_sp - 1)
end
- result.alias = UrlDecode(alias)
+ local raw_alias = UrlDecode(alias)
+ result.raw_alias = raw_alias -- 新增
+ result.alias = raw_alias -- 临时赋值(后面会被覆盖)
-- 分离和提取 password
local Info = content
- local params = {}
+ local params = {}
if Info:find("@") then
local contents = split(Info, "@")
result.password = UrlDecode(contents[1])
@@ -984,7 +993,7 @@ local function processData(szType, content, cfgid)
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("%]$", "")
+ result.download_address = (address and address ~= "") and address:gsub("^%[", ""):gsub("%]$", "")
else
-- 如果解析失败,清空下载地址
result.download_address = nil
@@ -1027,7 +1036,9 @@ local function processData(szType, content, cfgid)
local url = URL.parse("http://" .. content)
local params = url.query
- result.alias = url.fragment and UrlDecode(url.fragment) or nil
+ local raw_alias = url.fragment and UrlDecode(url.fragment) or nil
+ result.raw_alias = raw_alias -- 新增
+ result.alias = raw_alias -- 临时赋值(后面会被覆盖)
result.type = "v2ray"
result.v2ray_protocol = "vless"
result.server = url.host
@@ -1113,11 +1124,9 @@ local function processData(szType, content, cfgid)
if result.transport == "ws" then
result.ws_host = (result.tls ~= "1" and result.reality ~= "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 result.reality ~= "1") and (params.host and UrlDecode(params.host)) or nil
result.httpupgrade_path = params.path and UrlDecode(params.path) or "/"
-
elseif result.transport == "xhttp" then
result.xhttp_mode = params.mode or "auto"
result.xhttp_host = params.host and UrlDecode(params.host) or nil
@@ -1134,7 +1143,7 @@ local function processData(szType, content, cfgid)
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("%]$", "")
+ result.download_address = (address and address ~= "") and address:gsub("^%[", ""):gsub("%]$", "")
else
result.download_address = nil
end
@@ -1180,7 +1189,9 @@ local function processData(szType, content, cfgid)
alias = content:sub(idx_sp + 1, -1)
content = content:sub(0, idx_sp - 1)
end
- result.alias = UrlDecode(alias)
+ local raw_alias = UrlDecode(alias)
+ result.raw_alias = raw_alias -- 新增
+ result.alias = raw_alias -- 临时赋值(后面会被覆盖)
-- 分离和提取 uuid 和 password
local Info = content
@@ -1242,7 +1253,7 @@ local function processData(szType, content, cfgid)
if params.disable_sni then
if params.disable_sni == "1" or params.disable_sni == "0" then
result.disable_sni = params.disable_sni
- else
+ else
result.disable_sni = string.lower(params.disable_sni) == "true" and "1" or "0"
end
end
@@ -1251,7 +1262,7 @@ local function processData(szType, content, cfgid)
if params.zero_rtt_handshake then
if params.zero_rtt_handshake == "1" or params.zero_rtt_handshake == "0" then
result.zero_rtt_handshake = params.zero_rtt_handshake
- else
+ else
result.zero_rtt_handshake = string.lower(params.zero_rtt_handshake) == "true" and "1" or "0"
end
end
@@ -1260,7 +1271,7 @@ local function processData(szType, content, cfgid)
if params.dual_stack then
if params.dual_stack == "1" or params.dual_stack == "0" then
result.dual_stack = params.dual_stack
- else
+ else
result.dual_stack = string.lower(params.dual_stack) == "true" and "1" or "0"
end
-- 处理 ipstack_prefer 参数
@@ -1277,12 +1288,14 @@ local function processData(szType, content, cfgid)
end
end
end
+
if not result.alias then
if result.server and result.server_port then
result.alias = result.server .. ':' .. result.server_port
else
result.alias = "NULL"
end
+ result.raw_alias = result.alias
end
-- alias 不参与 hashkey 计算
local alias = result.alias
@@ -1352,44 +1365,42 @@ local function curl(url, user_agent)
end
local function check_filer(result)
- do
- -- 过滤的关键词列表
- local filter_word = split(filter_words, "/")
- -- 保留的关键词列表
- local check_save = false
- if save_words ~= nil and save_words ~= "" and save_words ~= "NULL" then
- check_save = true
+ -- 过滤的关键词列表
+ local filter_word = split(filter_words, "/")
+ -- 保留的关键词列表
+ local check_save = false
+ if save_words ~= nil and save_words ~= "" and save_words ~= "NULL" then
+ check_save = true
+ end
+ local save_word = split(save_words, "/")
+
+ -- 检查结果
+ local filter_result = false
+ local save_result = true
+
+ -- 检查是否存在过滤关键词
+ for i, v in pairs(filter_word) do
+ if tostring(result.alias):find(v, nil, true) then
+ filter_result = true
end
- local save_word = split(save_words, "/")
+ end
- -- 检查结果
- local filter_result = false
- local save_result = true
-
- -- 检查是否存在过滤关键词
- for i, v in pairs(filter_word) do
+ -- 检查是否打开了保留关键词检查,并且进行过滤
+ if check_save == true then
+ for i, v in pairs(save_word) do
if tostring(result.alias):find(v, nil, true) then
- filter_result = true
+ save_result = false
end
end
+ else
+ save_result = false
+ end
- -- 检查是否打开了保留关键词检查,并且进行过滤
- if check_save == true then
- for i, v in pairs(save_word) do
- if tostring(result.alias):find(v, nil, true) then
- save_result = false
- end
- end
- else
- save_result = false
- end
-
- -- 不等时返回
- if filter_result == true or save_result == true then
- return true
- else
- return false
- end
+ -- 不等时返回
+ if filter_result == true or save_result == true then
+ return true
+ else
+ return false
end
end
@@ -1410,131 +1421,154 @@ local function loadOldNodes(groupHash)
end
local execute = function()
- -- exec
- do
- --local updated = false
- local service_stopped = false
- for k, url in ipairs(subscribe_url) do
- local raw, new_md5 = curl(url)
- log("raw 长度: "..#raw)
- local groupHash = md5(url)
- local old_md5 = read_old_md5(groupHash)
+ local updated = false
+ local service_stopped = false
+ for k, url in ipairs(subscribe_url) do
+ local raw, new_md5 = curl(url)
+ log("raw 长度: "..#raw)
+ local groupHash = md5(url)
+ local old_md5 = read_old_md5(groupHash)
- log("处理订阅: " .. url)
- log("groupHash: " .. groupHash)
- log("old_md5: " .. tostring(old_md5))
- log("new_md5: " .. tostring(new_md5))
+ log("处理订阅: " .. url)
+ log("groupHash: " .. groupHash)
+ log("old_md5: " .. tostring(old_md5))
+ log("new_md5: " .. tostring(new_md5))
- if #raw > 0 then
- if old_md5 and new_md5 == old_md5 then
- log("订阅未变化, 跳过无需更新的订阅: " .. url)
- -- 防止 diff 阶段误删未更新订阅节点
- loadOldNodes(groupHash)
- --ucic:foreach(name, uciType, function(s)
- -- if s.grouphashkey == groupHash and s.hashkey then
- -- cache[groupHash][s.hashkey] = s
- -- tinsert(nodeResult[index], s)
- -- end
- --end)
- else
- updated = true
- -- 保存更新后的 MD5 值到以 groupHash 为标识的临时文件中,用于下次订阅更新时进行对比
- write_new_md5(groupHash, new_md5)
-
- -- 暂停服务(仅当 MD5 有变化时才执行)
- if proxy == '0' and not service_stopped then
- log('服务正在暂停')
- luci.sys.init.stop(name)
- service_stopped = true
- end
-
- cache[groupHash] = {}
- tinsert(nodeResult, {})
- local index = #nodeResult
- local nodes, szType
-
- -- SSD 似乎是这种格式 ssd:// 开头的
- if raw:find('ssd://') then
- szType = 'ssd'
- local nEnd = select(2, raw:find('ssd://'))
- nodes = base64Decode(raw:sub(nEnd + 1, #raw))
- nodes = jsonParse(nodes)
- local extra = {
- airport = nodes.airport,
- port = nodes.port,
- encryption = nodes.encryption,
- password = nodes.password
- }
- local servers = {}
- -- SS里面包着 干脆直接这样
- for _, server in ipairs(nodes.servers or {}) do
- tinsert(servers, setmetatable(server, {__index = extra}))
- end
- nodes = servers
- -- SS SIP008 直接使用 Json 格式
- elseif jsonParse(raw) then
- nodes = jsonParse(raw).servers or jsonParse(raw)
- if nodes[1] and nodes[1].server and nodes[1].method then
- szType = 'sip008'
- end
- -- 其他 base64 格式
- else
- -- ssd 外的格式
- nodes = split(base64Decode(raw):gsub("\r\n", "\n"), "\n")
- end
- for _, v in ipairs(nodes) do
- if v and not string.match(v, "^%s*$") then
- xpcall(function()
- local result
- if szType then
- result = processData(szType, v)
- elseif not szType then
- local node = trim(v)
- -- 一些奇葩的链接用"&"、"<"当做"&","#"前后带空格
- local link = node:gsub("&[a-zA-Z]+;", "&"):gsub("%s*#%s*", "#")
- local dat = split(link, "://")
- if dat and dat[1] and dat[2] then
- local dat3 = ""
- if dat[3] then
- dat3 = "://" .. dat[3]
- end
- 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]))
- end
- end
- else
- log('跳过未知类型: ' .. szType)
- end
- -- log(result)
- if result then
- -- 中文做地址的 也没有人拿中文域名搞,就算中文域也有Puny Code SB 机场
- if not result.server or not result.server_port
- or result.server == "127.0.0.1"
- or result.alias == "NULL"
- or check_filer(result)
- or result.server:match("[^0-9a-zA-Z%-_%.%s]")
- or cache[groupHash][result.hashkey]
- then
- log('丢弃无效节点: ' .. result.alias)
- else
- -- log('成功解析: ' .. result.type ..' 节点, ' .. result.alias)
- result.grouphashkey = groupHash
- tinsert(nodeResult[index], result)
- cache[groupHash][result.hashkey] = nodeResult[index][#nodeResult[index]]
- end
- end
- end, function(err)
- log(string.format("解析节点出错: %s\n原始数据: %s", tostring(err), tostring(v)))
- end)
- end
- end
- log('成功解析节点数量: ' .. #nodes)
- end
+ if #raw > 0 then
+ if old_md5 and new_md5 == old_md5 then
+ log("订阅未变化, 跳过无需更新的订阅: " .. url)
+ -- 防止 diff 阶段误删未更新订阅节点
+ loadOldNodes(groupHash)
+ --ucic:foreach(name, uciType, function(s)
+ -- if s.grouphashkey == groupHash and s.hashkey then
+ -- cache[groupHash][s.hashkey] = s
+ -- tinsert(nodeResult[index], s)
+ -- end
+ --end)
else
- log(url .. ': 获取内容为空')
+ updated = true
+ -- 保存更新后的 MD5 值到以 groupHash 为标识的临时文件中,用于下次订阅更新时进行对比
+ write_new_md5(groupHash, new_md5)
+
+ -- 暂停服务(仅当 MD5 有变化时才执行)
+ if proxy == '0' and not service_stopped then
+ log('服务正在暂停')
+ luci.sys.init.stop(name)
+ service_stopped = true
+ end
+
+ cache[groupHash] = {}
+ tinsert(nodeResult, {})
+ local index = #nodeResult
+ local nodes, szType
+
+ -- SSD 似乎是这种格式 ssd:// 开头的
+ if raw:find('ssd://') then
+ szType = 'ssd'
+ local nEnd = select(2, raw:find('ssd://'))
+ nodes = base64Decode(raw:sub(nEnd + 1, #raw))
+ nodes = jsonParse(nodes)
+ local extra = {
+ airport = nodes.airport,
+ port = nodes.port,
+ encryption = nodes.encryption,
+ password = nodes.password
+ }
+ local servers = {}
+ -- SS里面包着 干脆直接这样
+ for _, server in ipairs(nodes.servers or {}) do
+ tinsert(servers, setmetatable(server, {__index = extra}))
+ end
+ nodes = servers
+ -- SS SIP008 直接使用 Json 格式
+ elseif jsonParse(raw) then
+ nodes = jsonParse(raw).servers or jsonParse(raw)
+ if nodes[1] and nodes[1].server and nodes[1].method then
+ szType = 'sip008'
+ end
+ -- 其他 base64 格式
+ else
+ -- ssd 外的格式
+ nodes = split(base64Decode(raw):gsub("\r\n", "\n"), "\n")
+ end
+
+ -- 临时存储该订阅解析出的节点(带原始别名)
+ local groupRawNodes = {}
+
+ for _, v in ipairs(nodes) do
+ if v and not string.match(v, "^%s*$") then
+ xpcall(function()
+ local result
+ if szType then
+ result = processData(szType, v)
+ elseif not szType then
+ local node = trim(v)
+ -- 一些奇葩的链接用"&"、"<"当做"&","#"前后带空格
+ local link = node:gsub("&[a-zA-Z]+;", "&"):gsub("%s*#%s*", "#")
+ local dat = split(link, "://")
+ if dat and dat[1] and dat[2] then
+ local dat3 = ""
+ if dat[3] then
+ dat3 = "://" .. dat[3]
+ end
+ 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]))
+ end
+ end
+ else
+ log('跳过未知类型: ' .. szType)
+ end
+ -- log(result)
+ if result then
+ -- 中文做地址的 也没有人拿中文域名搞,就算中文域也有Puny Code SB 机场
+ if not result.server or not result.server_port
+ or result.server == "127.0.0.1"
+ or result.alias == "NULL"
+ or check_filer(result)
+ or result.server:match("[^0-9a-zA-Z%-_%.%s]")
+ or cache[groupHash][result.hashkey] then
+ log('丢弃无效节点: ' .. result.alias)
+ else
+ -- 暂存节点
+ table.insert(groupRawNodes, result)
+ end
+ end
+ end, function(err)
+ log(string.format("解析节点出错: %s\n原始数据: %s", tostring(err), tostring(v)))
+ end)
+ end
+ end
+
+ -- 对该组节点进行别名编号:重复节点加后缀,唯一节点不加
+ local freq = {}
+ for _, node in ipairs(groupRawNodes) do
+ local raw = node.raw_alias or ""
+ freq[raw] = (freq[raw] or 0) + 1
+ end
+ local aliasCount = {}
+ for _, node in ipairs(groupRawNodes) do
+ local raw = node.raw_alias or ""
+ if freq[raw] > 1 then
+ local count = (aliasCount[raw] or 0) + 1
+ aliasCount[raw] = count
+ node.alias = raw .. "_" .. count
+ else
+ node.alias = raw
+ end
+ -- 清理临时字段
+ node.raw_alias = nil
+ -- 存入 nodeResult
+ node.grouphashkey = groupHash
+ table.insert(nodeResult[index], node)
+ cache[groupHash][node.hashkey] = node
+ end
+
+ log('成功解析节点数量: ' .. #groupRawNodes)
end
+ else
+ log(url .. ': 获取内容为空')
end
end
-- 输出日志并判断是否需要进行 diff
@@ -1543,106 +1577,105 @@ local execute = function()
log('保留手动添加的节点。')
return
end
- -- diff
- do
- if next(nodeResult) == nil then
- log("更新失败,没有可用的节点信息")
- if proxy == '0' then
- luci.sys.init.start(name)
- log('订阅失败, 恢复服务')
- end
- return
- end
- local add, del = 0, 0
- ucic:foreach(name, uciType, function(old)
- if old.grouphashkey or old.hashkey then -- 没有 hash 的不参与删除
- if not nodeResult[old.grouphashkey] or not nodeResult[old.grouphashkey][old.hashkey] then
- ucic:delete(name, old['.name'])
- del = del + 1
- else
- local dat = nodeResult[old.grouphashkey][old.hashkey]
- ucic:tset(name, old['.name'], dat)
- -- 标记一下
- setmetatable(nodeResult[old.grouphashkey][old.hashkey], {__index = {_ignore = true}})
- end
- else
- if not old.alias then
- if old.server or old.server_port then
- old.alias = old.server .. ':' .. old.server_port
- log('忽略手动添加的节点: ' .. old.alias)
- else
- ucic:delete(name, old['.name'])
- end
- else
- log('忽略手动添加的节点: ' .. old.alias)
- end
- end
- end)
- -- 1583-1620 行为生成 sid
- -- 记录已使用编号
- local used_sid = {}
- local next_sid = 1
- -- 扫描已有 section
- ucic:foreach(name, uciType, function(s)
- local num = s[".name"]:match("^cfg(%x%x)") -- 提取两位十六进制序号
- if num then
- local n = tonumber(num, 16)
- used_sid[n] = true
- end
- end)
- -- 获取下一个可用编号(O(1))
- local function get_next_sid()
- while used_sid[next_sid] do
- next_sid = next_sid + 1
- end
- used_sid[next_sid] = true
- return next_sid
- end
- for _, v in ipairs(nodeResult) do
- for _, vv in ipairs(v) do
- if not vv._ignore then
- local sid = ucic:add(name, uciType)
- if sid then
- local suffix = sid:sub(-4)
- ucic:delete(name, sid)
- local id = get_next_sid()
- local cfgid = string.format("cfg%02x%s", id, suffix)
- local section = ucic:section(name, uciType, cfgid)
- if section then
- ucic:tset(name, section, vv)
- ucic:set(name, section, "switch_enable", switch)
- add = add + 1
- end
- end
- end
- end
+ -- diff 阶段
+ if next(nodeResult) == nil then
+ log("更新失败,没有可用的节点信息")
+ if proxy == '0' then
+ luci.sys.init.start(name)
+ log('订阅失败, 恢复服务')
end
- ucic:commit(name)
- -- 如果原有服务器节点已经不见了就尝试换为第一个节点
- local globalServer = ucic:get_first(name, 'global', 'global_server', '')
- if globalServer ~= "nil" then
- local firstServer = ucic:get_first(name, uciType)
- if firstServer then
- if not ucic:get(name, globalServer) then
- luci.sys.call("/etc/init.d/" .. name .. " stop > /dev/null 2>&1 &")
- ucic:commit(name)
- ucic:set(name, ucic:get_first(name, 'global'), 'global_server', firstServer)
- ucic:commit(name)
- log('当前主服务器节点已被删除,正在自动更换为第一个节点。')
- luci.sys.call("/etc/init.d/" .. name .. " start > /dev/null 2>&1 &")
+ return
+ end
+ local add, del = 0, 0
+ ucic:foreach(name, uciType, function(old)
+ if old.grouphashkey or old.hashkey then -- 没有 hash 的不参与删除
+ if not nodeResult[old.grouphashkey] or not nodeResult[old.grouphashkey][old.hashkey] then
+ ucic:delete(name, old['.name'])
+ del = del + 1
+ else
+ local dat = nodeResult[old.grouphashkey][old.hashkey]
+ ucic:tset(name, old['.name'], dat)
+ -- 标记一下
+ setmetatable(nodeResult[old.grouphashkey][old.hashkey], {__index = {_ignore = true}})
+ end
+ else
+ if not old.alias then
+ if old.server or old.server_port then
+ old.alias = old.server .. ':' .. old.server_port
+ log('忽略手动添加的节点: ' .. old.alias)
else
- log('维持当前主服务器节点。')
- luci.sys.call("/etc/init.d/" .. name .. " restart > /dev/null 2>&1 &")
+ ucic:delete(name, old['.name'])
end
else
- log('没有服务器节点了,停止服务')
- luci.sys.call("/etc/init.d/" .. name .. " stop > /dev/null 2>&1 &")
+ log('忽略手动添加的节点: ' .. old.alias)
end
end
- log('新增节点数量: ' .. add .. ', 删除节点数量: ' .. del)
- log('订阅更新成功')
+ end)
+ -- 1615-1653 行为生成 sid
+ -- 记录已使用编号
+ local used_sid = {}
+ local next_sid = 1
+ -- 扫描已有 section
+ ucic:foreach(name, uciType, function(s)
+ local num = s[".name"]:match("^cfg(%x%x)") -- 提取两位十六进制序号
+ if num then
+ local n = tonumber(num, 16)
+ used_sid[n] = true
+ end
+ end)
+ -- 获取下一个可用编号(O(1))
+ local function get_next_sid()
+ while used_sid[next_sid] do
+ next_sid = next_sid + 1
+ end
+ used_sid[next_sid] = true
+ return next_sid
end
+
+ for _, v in ipairs(nodeResult) do
+ for _, vv in ipairs(v) do
+ if not vv._ignore then
+ local sid = ucic:add(name, uciType)
+ if sid then
+ local suffix = sid:sub(-4)
+ ucic:delete(name, sid)
+ local id = get_next_sid()
+ local cfgid = string.format("cfg%02x%s", id, suffix)
+ local section = ucic:section(name, uciType, cfgid)
+ if section then
+ ucic:tset(name, section, vv)
+ ucic:set(name, section, "switch_enable", switch)
+ add = add + 1
+ end
+ end
+ end
+ end
+ end
+ ucic:commit(name)
+ -- 如果原有服务器节点已经不见了就尝试换为第一个节点
+ local globalServer = ucic:get_first(name, 'global', 'global_server', '')
+ if globalServer ~= "nil" then
+ local firstServer = ucic:get_first(name, uciType)
+ if firstServer then
+ if not ucic:get(name, globalServer) then
+ luci.sys.call("/etc/init.d/" .. name .. " stop > /dev/null 2>&1 &")
+ ucic:commit(name)
+ ucic:set(name, ucic:get_first(name, 'global'), 'global_server', firstServer)
+ ucic:commit(name)
+ log('当前主服务器节点已被删除,正在自动更换为第一个节点。')
+ luci.sys.call("/etc/init.d/" .. name .. " start > /dev/null 2>&1 &")
+ else
+ log('维持当前主服务器节点。')
+ luci.sys.call("/etc/init.d/" .. name .. " restart > /dev/null 2>&1 &")
+ end
+ else
+ log('没有服务器节点了,停止服务')
+ luci.sys.call("/etc/init.d/" .. name .. " stop > /dev/null 2>&1 &")
+ end
+ end
+ log('新增节点数量: ' .. add .. ', 删除节点数量: ' .. del)
+ log('订阅更新成功')
end
if subscribe_url and #subscribe_url > 0 then
diff --git a/luci-theme-alpha/Makefile b/luci-theme-alpha/Makefile
index 4076f5c9..5ccc03cc 100644
--- a/luci-theme-alpha/Makefile
+++ b/luci-theme-alpha/Makefile
@@ -11,7 +11,7 @@ THEME_TITLE:=Alpha
PKG_NAME:=luci-theme-$(THEME_NAME)
PKG_VERSION:=3.9.7_beta
-PKG_RELEASE:=3
+PKG_RELEASE:=4
include $(INCLUDE_DIR)/package.mk
@@ -19,7 +19,7 @@ define Package/luci-theme-$(THEME_NAME)
SECTION:=luci
CATEGORY:=LuCI
SUBMENU:=9. Themes
- DEPENDS:=+libc
+ DEPENDS:=+libc +luci-app-alpha-config
TITLE:=LuCi Theme For OpenWrt And Alpha OS ONLY - $(THEME_TITLE)
URL:=http://facebook.com/derisamedia/
PKGARCH:=all
diff --git a/luci-theme-alpha/template/footer.ut b/luci-theme-alpha/template/footer.ut
index f6740eb1..efaa4da0 100644
--- a/luci-theme-alpha/template/footer.ut
+++ b/luci-theme-alpha/template/footer.ut
@@ -54,7 +54,7 @@