🍉 Sync 2026-03-28 20:20:21

This commit is contained in:
github-actions[bot] 2026-03-28 20:20:21 +08:00
parent 344bf257f5
commit e732442006
20 changed files with 447 additions and 369 deletions

View File

@ -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() {

View File

@ -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,

View File

@ -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')

View File

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

View File

@ -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"))

View File

@ -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.") .. "<br>" ..
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

View File

@ -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 {

View File

@ -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",

View File

@ -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 || '');

View File

@ -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"

View File

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

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:=3
PKG_RELEASE:=4
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \

View File

@ -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")

View File

@ -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) {

View File

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

View File

@ -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)
-- 一些奇葩的链接用"&amp;"、"&lt;"当做"&""#"前后带空格
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)
-- 一些奇葩的链接用"&amp;"、"&lt;"当做"&""#"前后带空格
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

View File

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

View File

@ -54,7 +54,7 @@
</div>
<footer class="mobile">
<a href="https://github.com/derisamedia/luci-theme-alpha">{{ version.luciname }} | Alpha OS Theme v3.9.7</a>
<a href="https://github.com/derisamedia/luci-theme-alpha">{{ version.distname }} {{ version.distversion }} | Alpha OS Theme v3.9.7</a>
</footer>
</div>
<div class="navbar active">

View File

@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=mtproxy
PKG_VERSION:=3.5.2
PKG_RELEASE:=12
PKG_VERSION:=3.5.4
PKG_RELEASE:=13
PKG_MAINTAINER:=Kosntantine Shevlakov <shevlakov@132lan.ru>
PKG_LICENSE:=GPLv2
@ -10,7 +10,7 @@ PKG_LICENSE_FILES:=LICENSE
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/GetPageSpeed/MTProxy.git
PKG_SOURCE_VERSION:=5ca19870d810423fc8722ad114872fed1ebcc54f
PKG_SOURCE_VERSION:=61c43d02a92f2f7b439cc61f9072640b7efa0486
PKG_SOURCE_SUBDIR:=$(PKG_NAME)
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz

View File

@ -5,8 +5,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=fastfetch
PKG_VERSION:=2.60.0
PKG_RELEASE:=2
PKG_VERSION:=2.61.0
PKG_RELEASE:=3
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/fastfetch-cli/fastfetch/tar.gz/$(PKG_VERSION)?