update 2025-07-29 09:50:00

This commit is contained in:
actions-user 2025-07-29 09:50:00 +08:00
parent d602e7e620
commit 8848075e6e
15 changed files with 247 additions and 66 deletions

View File

@ -84,6 +84,8 @@ function index()
entry({"admin", "services", appname, "update_rules"}, call("update_rules")).leaf = true entry({"admin", "services", appname, "update_rules"}, call("update_rules")).leaf = true
entry({"admin", "services", appname, "subscribe_del_node"}, call("subscribe_del_node")).leaf = true entry({"admin", "services", appname, "subscribe_del_node"}, call("subscribe_del_node")).leaf = true
entry({"admin", "services", appname, "subscribe_del_all"}, call("subscribe_del_all")).leaf = true entry({"admin", "services", appname, "subscribe_del_all"}, call("subscribe_del_all")).leaf = true
entry({"admin", "services", appname, "subscribe_manual"}, call("subscribe_manual")).leaf = true
entry({"admin", "services", appname, "subscribe_manual_all"}, call("subscribe_manual_all")).leaf = true
--[[rule_list]] --[[rule_list]]
entry({"admin", "services", appname, "read_rulelist"}, call("read_rulelist")).leaf = true entry({"admin", "services", appname, "read_rulelist"}, call("read_rulelist")).leaf = true
@ -721,3 +723,51 @@ function subscribe_del_all()
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate > /dev/null 2>&1") luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate > /dev/null 2>&1")
http.status(200, "OK") http.status(200, "OK")
end end
function subscribe_manual()
local section = http.formvalue("section") or ""
local current_url = http.formvalue("url") or ""
if section == "" or current_url == "" then
http_write_json({ success = false, msg = "Missing section or URL, skip." })
return
end
local uci_url = api.sh_uci_get(appname, section, "url")
if not uci_url or uci_url == "" then
http_write_json({ success = false, msg = i18n.translate("Please save and apply before manually subscribing.") })
return
end
if uci_url ~= current_url then
api.sh_uci_set(appname, section, "url", current_url, true)
end
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start " .. section .. " manual >/dev/null 2>&1 &")
http_write_json({ success = true, msg = "Subscribe triggered." })
end
function subscribe_manual_all()
local sections = http.formvalue("sections") or ""
local urls = http.formvalue("urls") or ""
if sections == "" or urls == "" then
http_write_json({ success = false, msg = "Missing section or URL, skip." })
return
end
local section_list = util.split(sections, ",")
local url_list = util.split(urls, ",")
-- 检查是否存在未保存配置
for i, section in ipairs(section_list) do
local uci_url = api.sh_uci_get(appname, section, "url")
if not uci_url or uci_url == "" then
http_write_json({ success = false, msg = i18n.translate("Please save and apply before manually subscribing.") })
return
end
end
-- 保存有变动的url
for i, section in ipairs(section_list) do
local current_url = url_list[i] or ""
local uci_url = api.sh_uci_get(appname, section, "url")
if current_url ~= "" and uci_url ~= current_url then
api.sh_uci_set(appname, section, "url", current_url, true)
end
end
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start all manual >/dev/null 2>&1 &")
http_write_json({ success = true, msg = "Subscribe triggered." })
end

View File

@ -136,14 +136,15 @@ function o.cfgvalue(self, section)
translate("Delete All Subscribe Node")) translate("Delete All Subscribe Node"))
end end
o = s:option(Button, "_update", translate("Manual subscription All")) o = s:option(DummyValue, "_update", translate("Manual subscription All"))
o.inputstyle = "apply" o.rawhtml = true
function o.write(t, n) o.cfgvalue = function(self, section)
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start all manual > /dev/null 2>&1 &") return string.format([[
luci.http.redirect(api.url("log")) <button type="button" class="cbi-button cbi-button-apply" onclick="ManualSubscribeAll()">%s</button>]],
translate("Manual subscription All"))
end end
s = m:section(TypedSection, "subscribe_list", "", "<font color='red'>" .. translate("Please input the subscription url first, save and submit before manual subscription.") .. "</font>") s = m:section(TypedSection, "subscribe_list", "", "<font color='red'>" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "</font>")
s.addremove = true s.addremove = true
s.anonymous = true s.anonymous = true
s.sortable = true s.sortable = true
@ -204,11 +205,12 @@ function o.cfgvalue(self, section)
remark, translate("Delete the subscribed node")) remark, translate("Delete the subscribed node"))
end end
o = s:option(Button, "_update", translate("Manual subscription")) o = s:option(DummyValue, "_update", translate("Manual subscription"))
o.inputstyle = "apply" o.rawhtml = true
function o.write(t, n) o.cfgvalue = function(self, section)
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start " .. n .. " manual > /dev/null 2>&1 &") return string.format([[
luci.http.redirect(api.url("log")) <button type="button" class="cbi-button cbi-button-apply" onclick="ManualSubscribe('%s')">%s</button>]],
section, translate("Manual subscription"))
end end
s:append(Template(appname .. "/node_subscribe/js")) s:append(Template(appname .. "/node_subscribe/js"))

View File

@ -132,7 +132,7 @@ if api.compare_versions(xray_version, ">=", "1.8.10") then
end end
-- 探测地址 -- 探测地址
local ucpu = s:option(Flag, _n("useCustomProbeUrl"), translate("Use Custome Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL.")) local ucpu = s:option(Flag, _n("useCustomProbeUrl"), translate("Use Custom Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL."))
ucpu:depends({ [_n("balancingStrategy")] = "leastPing" }) ucpu:depends({ [_n("balancingStrategy")] = "leastPing" })
ucpu:depends({ [_n("balancingStrategy")] = "leastLoad" }) ucpu:depends({ [_n("balancingStrategy")] = "leastLoad" })
@ -373,6 +373,19 @@ o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translat
o.default = "0" o.default = "0"
o:depends({ [_n("tls")] = true, [_n("reality")] = false }) o:depends({ [_n("tls")] = true, [_n("reality")] = false })
o = s:option(Flag, _n("ech"), translate("ECH"))
o.default = "0"
o:depends({ [_n("tls")] = true, [_n("flow")] = "", [_n("reality")] = false })
o = s:option(TextValue, _n("ech_config"), translate("ECH Config"))
o.default = ""
o.rows = 5
o.wrap = "soft"
o:depends({ [_n("ech")] = true })
o.validate = function(self, value)
return api.trim(value:gsub("[\r\n]", ""))
end
-- [[ REALITY部分 ]] -- -- [[ REALITY部分 ]] --
o = s:option(Value, _n("reality_publicKey"), translate("Public Key")) o = s:option(Value, _n("reality_publicKey"), translate("Public Key"))
o:depends({ [_n("tls")] = true, [_n("reality")] = true }) o:depends({ [_n("tls")] = true, [_n("reality")] = true })
@ -403,6 +416,19 @@ o.default = "chrome"
o:depends({ [_n("tls")] = true, [_n("utls")] = true }) o:depends({ [_n("tls")] = true, [_n("utls")] = true })
o:depends({ [_n("tls")] = true, [_n("reality")] = true }) o:depends({ [_n("tls")] = true, [_n("reality")] = true })
o = s:option(Flag, _n("use_mldsa65Verify"), translate("ML-DSA-65"))
o.default = "0"
o:depends({ [_n("tls")] = true, [_n("reality")] = true })
o = s:option(TextValue, _n("reality_mldsa65Verify"), "ML-DSA-65 " .. translate("Public key"))
o.default = ""
o.rows = 5
o.wrap = "soft"
o:depends({ [_n("use_mldsa65Verify")] = true })
o.validate = function(self, value)
return api.trim(value:gsub("[\r\n]", ""))
end
o = s:option(ListValue, _n("transport"), translate("Transport")) o = s:option(ListValue, _n("transport"), translate("Transport"))
o:value("raw", "RAW (TCP)") o:value("raw", "RAW (TCP)")
o:value("mkcp", "mKCP") o:value("mkcp", "mKCP")

View File

@ -465,14 +465,6 @@ o.validate = function(self, value)
return value return value
end end
o = s:option(Flag, _n("pq_signature_schemes_enabled"), translate("PQ signature schemes"))
o.default = "0"
o:depends({ [_n("ech")] = true })
o = s:option(Flag, _n("dynamic_record_sizing_disabled"), translate("Disable adaptive sizing of TLS records"))
o.default = "0"
o:depends({ [_n("ech")] = true })
if singbox_tags:find("with_utls") then if singbox_tags:find("with_utls") then
o = s:option(Flag, _n("utls"), translate("uTLS")) o = s:option(Flag, _n("utls"), translate("uTLS"))
o.default = "0" o.default = "0"

View File

@ -186,6 +186,19 @@ o:value("h2,http/1.1")
o:value("h3,h2,http/1.1") o:value("h3,h2,http/1.1")
o:depends({ [_n("tls")] = true }) o:depends({ [_n("tls")] = true })
o = s:option(Flag, _n("use_mldsa65Seed"), translate("ML-DSA-65"))
o.default = "0"
o:depends({ [_n("reality")] = true })
o = s:option(TextValue, _n("reality_mldsa65Seed"), "ML-DSA-65 " .. translate("Private Key"))
o.default = ""
o.rows = 5
o.wrap = "soft"
o:depends({ [_n("use_mldsa65Seed")] = true })
o.validate = function(self, value)
return api.trim(value:gsub("[\r\n]", ""))
end
-- o = s:option(Value, _n("minversion"), translate("minversion")) -- o = s:option(Value, _n("minversion"), translate("minversion"))
-- o.default = "1.3" -- o.default = "1.3"
-- o:value("1.3") -- o:value("1.3")
@ -223,6 +236,19 @@ o.validate = function(self, value, t)
return nil return nil
end end
o = s:option(Flag, _n("ech"), translate("ECH"))
o.default = "0"
o:depends({ [_n("tls")] = true, [_n("flow")] = "", [_n("reality")] = false })
o = s:option(TextValue, _n("ech_key"), translate("ECH Key"))
o.default = ""
o.rows = 5
o.wrap = "soft"
o:depends({ [_n("ech")] = true })
o.validate = function(self, value)
return api.trim(value:gsub("[\r\n]", ""))
end
o = s:option(ListValue, _n("transport"), translate("Transport")) o = s:option(ListValue, _n("transport"), translate("Transport"))
o:value("raw", "RAW") o:value("raw", "RAW")
o:value("mkcp", "mKCP") o:value("mkcp", "mKCP")

View File

@ -323,14 +323,6 @@ o.validate = function(self, value)
return value return value
end end
o = s:option(Flag, _n("pq_signature_schemes_enabled"), translate("PQ signature schemes"))
o.default = "0"
o:depends({ [_n("ech")] = true })
o = s:option(Flag, _n("dynamic_record_sizing_disabled"), translate("Disable adaptive sizing of TLS records"))
o.default = "0"
o:depends({ [_n("ech")] = true })
o = s:option(ListValue, _n("transport"), translate("Transport")) o = s:option(ListValue, _n("transport"), translate("Transport"))
o:value("tcp", "TCP") o:value("tcp", "TCP")
o:value("http", "HTTP") o:value("http", "HTTP")

View File

@ -60,7 +60,8 @@ function uci_save(cursor, config, commit, apply)
end end
function sh_uci_get(config, section, option) function sh_uci_get(config, section, option)
exec_call(string.format("uci -q get %s.%s.%s", config, section, option)) local _, val = exec_call(string.format("uci -q get %s.%s.%s", config, section, option))
return val
end end
function sh_uci_set(config, section, option, val, commit) function sh_uci_set(config, section, option, val, commit)

View File

@ -1,11 +1,13 @@
local _M = {} local _M = {}
local function gh_release_url(self) local function gh_release_url(self)
return "https://api.github.com/repos/" .. self.repo .. "/releases/latest" --return "https://api.github.com/repos/" .. self.repo .. "/releases/latest"
return "https://github.com/xiaorouji/openwrt-passwall-packages/releases/download/api-cache/" .. string.lower(self.name) .. "-release-api.json"
end end
local function gh_pre_release_url(self) local function gh_pre_release_url(self)
return "https://api.github.com/repos/" .. self.repo .. "/releases?per_page=1" --return "https://api.github.com/repos/" .. self.repo .. "/releases?per_page=1"
return "https://github.com/xiaorouji/openwrt-passwall-packages/releases/download/api-cache/" .. string.lower(self.name) .. "-pre-release-api.json"
end end
-- 排序顺序定义 -- 排序顺序定义

View File

@ -159,9 +159,7 @@ function gen_outbound(flag, node, tag, proxy_table)
--max_version = "1.3", --max_version = "1.3",
ech = { ech = {
enabled = (node.ech == "1") and true or false, enabled = (node.ech == "1") and true or false,
config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {}, config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {}
pq_signature_schemes_enabled = node.pq_signature_schemes_enabled and true or false,
dynamic_record_sizing_disabled = node.dynamic_record_sizing_disabled and true or false
}, },
utls = { utls = {
enabled = (node.utls == "1" or node.reality == "1") and true or false, enabled = (node.utls == "1" or node.reality == "1") and true or false,
@ -380,9 +378,7 @@ function gen_outbound(flag, node, tag, proxy_table)
} or nil, } or nil,
ech = { ech = {
enabled = (node.ech == "1") and true or false, enabled = (node.ech == "1") and true or false,
config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {}, config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {}
pq_signature_schemes_enabled = node.pq_signature_schemes_enabled and true or false,
dynamic_record_sizing_disabled = node.dynamic_record_sizing_disabled and true or false
} }
} }
} }
@ -414,9 +410,7 @@ function gen_outbound(flag, node, tag, proxy_table)
} or nil, } or nil,
ech = { ech = {
enabled = (node.ech == "1") and true or false, enabled = (node.ech == "1") and true or false,
config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {}, config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {}
pq_signature_schemes_enabled = node.pq_signature_schemes_enabled and true or false,
dynamic_record_sizing_disabled = node.dynamic_record_sizing_disabled and true or false
} }
} }
} }
@ -448,9 +442,7 @@ function gen_outbound(flag, node, tag, proxy_table)
insecure = (node.tls_allowInsecure == "1") and true or false, insecure = (node.tls_allowInsecure == "1") and true or false,
ech = { ech = {
enabled = (node.ech == "1") and true or false, enabled = (node.ech == "1") and true or false,
config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {}, config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {}
pq_signature_schemes_enabled = node.pq_signature_schemes_enabled and true or false,
dynamic_record_sizing_disabled = node.dynamic_record_sizing_disabled and true or false
} }
} }
} }
@ -507,9 +499,7 @@ function gen_config_server(node)
if node.tls == "1" and node.ech == "1" then if node.tls == "1" and node.ech == "1" then
tls.ech = { tls.ech = {
enabled = true, enabled = true,
key = node.ech_key and split(node.ech_key:gsub("\\n", "\n"), "\n") or {}, key = node.ech_key and split(node.ech_key:gsub("\\n", "\n"), "\n") or {}
pq_signature_schemes_enabled = (node.pq_signature_schemes_enabled == "1") and true or false,
dynamic_record_sizing_disabled = (node.dynamic_record_sizing_disabled == "1") and true or false,
} }
end end

View File

@ -157,14 +157,16 @@ function gen_outbound(flag, node, tag, proxy_table)
tlsSettings = (node.stream_security == "tls") and { tlsSettings = (node.stream_security == "tls") and {
serverName = node.tls_serverName, serverName = node.tls_serverName,
allowInsecure = (node.tls_allowInsecure == "1") and true or false, allowInsecure = (node.tls_allowInsecure == "1") and true or false,
fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil,
echConfigList = (node.ech == "1") and node.ech_config or nil
} or nil, } or nil,
realitySettings = (node.stream_security == "reality") and { realitySettings = (node.stream_security == "reality") and {
serverName = node.tls_serverName, serverName = node.tls_serverName,
publicKey = node.reality_publicKey, publicKey = node.reality_publicKey,
shortId = node.reality_shortId or "", shortId = node.reality_shortId or "",
spiderX = node.reality_spiderX or "/", spiderX = node.reality_spiderX or "/",
fingerprint = (node.type == "Xray" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or "chrome" fingerprint = (node.type == "Xray" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or "chrome",
mldsa65Verify = (node.use_mldsa65Verify == "1") and node.reality_mldsa65Verify or nil
} or nil, } or nil,
rawSettings = ((node.transport == "raw" or node.transport == "tcp") and node.protocol ~= "socks" and (node.tcp_guise and node.tcp_guise ~= "none")) and { rawSettings = ((node.transport == "raw" or node.transport == "tcp") and node.protocol ~= "socks" and (node.tcp_guise and node.tcp_guise ~= "none")) and {
header = { header = {
@ -463,7 +465,8 @@ function gen_config_server(node)
certificateFile = node.tls_certificateFile, certificateFile = node.tls_certificateFile,
keyFile = node.tls_keyFile keyFile = node.tls_keyFile
} }
} },
echServerKeys = (node.ech == "1") and node.ech_key or nil
} or nil, } or nil,
rawSettings = (node.transport == "raw" or node.transport == "tcp") and { rawSettings = (node.transport == "raw" or node.transport == "tcp") and {
header = { header = {
@ -548,7 +551,8 @@ function gen_config_server(node)
dest = node.reality_dest, dest = node.reality_dest,
serverNames = node.reality_serverNames or {}, serverNames = node.reality_serverNames or {},
privateKey = node.reality_private_key, privateKey = node.reality_private_key,
shortIds = node.reality_shortId or "" shortIds = node.reality_shortId or "",
mldsa65Seed = (node.use_mldsa65Seed == "1") and node.reality_mldsa65Seed or nil
} or nil } or nil
end end
end end

View File

@ -286,6 +286,7 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
params += opt.query("pbk", dom_prefix + "reality_publicKey"); params += opt.query("pbk", dom_prefix + "reality_publicKey");
params += opt.query("sid", dom_prefix + "reality_shortId"); params += opt.query("sid", dom_prefix + "reality_shortId");
params += opt.query("spx", dom_prefix + "reality_spiderX"); params += opt.query("spx", dom_prefix + "reality_spiderX");
params += opt.query("pqv", dom_prefix + "reality_mldsa65Verify");
} }
if (opt.get(dom_prefix + "flow") && opt.get(dom_prefix + "flow").value) { if (opt.get(dom_prefix + "flow") && opt.get(dom_prefix + "flow").value) {
let v_flow = opt.get(dom_prefix + "flow").value; let v_flow = opt.get(dom_prefix + "flow").value;
@ -294,6 +295,8 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
params += "&security=" + v_security; params += "&security=" + v_security;
params += opt.query("alpn", dom_prefix + "alpn"); params += opt.query("alpn", dom_prefix + "alpn");
params += opt.query("sni", dom_prefix + "tls_serverName"); params += opt.query("sni", dom_prefix + "tls_serverName");
params += opt.query("allowinsecure", dom_prefix + "tls_allowInsecure");
params += opt.query("ech", dom_prefix + "ech_config");
} }
if (opt.get(dom_prefix + "shadowtls")?.checked) { if (opt.get(dom_prefix + "shadowtls")?.checked) {
@ -461,6 +464,7 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
params += opt.query("pbk", dom_prefix + "reality_publicKey"); params += opt.query("pbk", dom_prefix + "reality_publicKey");
params += opt.query("sid", dom_prefix + "reality_shortId"); params += opt.query("sid", dom_prefix + "reality_shortId");
params += opt.query("spx", dom_prefix + "reality_spiderX"); params += opt.query("spx", dom_prefix + "reality_spiderX");
params += opt.query("pqv", dom_prefix + "reality_mldsa65Verify");
} }
if (opt.get(dom_prefix + "flow") && opt.get(dom_prefix + "flow").value) { if (opt.get(dom_prefix + "flow") && opt.get(dom_prefix + "flow").value) {
let v_flow = opt.get(dom_prefix + "flow").value; let v_flow = opt.get(dom_prefix + "flow").value;
@ -470,6 +474,7 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
params += opt.query("alpn", dom_prefix + "alpn"); params += opt.query("alpn", dom_prefix + "alpn");
params += opt.query("sni", dom_prefix + "tls_serverName"); params += opt.query("sni", dom_prefix + "tls_serverName");
params += opt.query("allowinsecure", dom_prefix + "tls_allowInsecure"); params += opt.query("allowinsecure", dom_prefix + "tls_allowInsecure");
params += opt.query("ech", dom_prefix + "ech_config");
} }
params += "#" + encodeURI(v_alias.value); params += "#" + encodeURI(v_alias.value);
@ -529,6 +534,7 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
params += opt.query("pbk", dom_prefix + "reality_publicKey"); params += opt.query("pbk", dom_prefix + "reality_publicKey");
params += opt.query("sid", dom_prefix + "reality_shortId"); params += opt.query("sid", dom_prefix + "reality_shortId");
params += opt.query("spx", dom_prefix + "reality_spiderX"); params += opt.query("spx", dom_prefix + "reality_spiderX");
params += opt.query("pqv", dom_prefix + "reality_mldsa65Verify");
} }
if (opt.get(dom_prefix + "flow") && opt.get(dom_prefix + "flow").value) { if (opt.get(dom_prefix + "flow") && opt.get(dom_prefix + "flow").value) {
let v_flow = opt.get(dom_prefix + "flow").value; let v_flow = opt.get(dom_prefix + "flow").value;
@ -538,6 +544,7 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
params += opt.query("alpn", dom_prefix + "alpn"); params += opt.query("alpn", dom_prefix + "alpn");
params += opt.query("sni", dom_prefix + "tls_serverName"); params += opt.query("sni", dom_prefix + "tls_serverName");
params += opt.query("allowinsecure", dom_prefix + "tls_allowInsecure"); params += opt.query("allowinsecure", dom_prefix + "tls_allowInsecure");
params += opt.query("ech", dom_prefix + "ech_config");
} }
params += "#" + encodeURI(v_alias.value); params += "#" + encodeURI(v_alias.value);
if (params[0] == "&") { if (params[0] == "&") {
@ -968,6 +975,8 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
opt.set(dom_prefix + 'utls', true); opt.set(dom_prefix + 'utls', true);
opt.set(dom_prefix + 'fingerprint', queryParam.fp); opt.set(dom_prefix + 'fingerprint', queryParam.fp);
} }
opt.set(dom_prefix + 'ech', !!queryParam.ech);
opt.set(dom_prefix + 'ech_config', queryParam.ech || '');
} }
if (queryParam.security == "reality") { if (queryParam.security == "reality") {
@ -983,6 +992,8 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
opt.set(dom_prefix + 'reality_publicKey', queryParam.pbk || ''); opt.set(dom_prefix + 'reality_publicKey', queryParam.pbk || '');
opt.set(dom_prefix + 'reality_shortId', queryParam.sid || ''); opt.set(dom_prefix + 'reality_shortId', queryParam.sid || '');
opt.set(dom_prefix + 'reality_spiderX', queryParam.spx || ''); opt.set(dom_prefix + 'reality_spiderX', queryParam.spx || '');
opt.set(dom_prefix + 'use_mldsa65Verify', !!queryParam.pqv);
opt.set(dom_prefix + 'reality_mldsa65Verify', queryParam.pqv || '');
} }
} }
@ -1347,6 +1358,8 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
opt.set(dom_prefix + 'utls', true); opt.set(dom_prefix + 'utls', true);
opt.set(dom_prefix + 'fingerprint', queryParam.fp); opt.set(dom_prefix + 'fingerprint', queryParam.fp);
} }
opt.set(dom_prefix + 'ech', !!queryParam.ech);
opt.set(dom_prefix + 'ech_config', queryParam.ech || '');
} }
if (queryParam.security == "reality") { if (queryParam.security == "reality") {
@ -1362,6 +1375,8 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
opt.set(dom_prefix + 'reality_publicKey', queryParam.pbk || ''); opt.set(dom_prefix + 'reality_publicKey', queryParam.pbk || '');
opt.set(dom_prefix + 'reality_shortId', queryParam.sid || ''); opt.set(dom_prefix + 'reality_shortId', queryParam.sid || '');
opt.set(dom_prefix + 'reality_spiderX', queryParam.spx || ''); opt.set(dom_prefix + 'reality_spiderX', queryParam.spx || '');
opt.set(dom_prefix + 'use_mldsa65Verify', !!queryParam.pqv);
opt.set(dom_prefix + 'reality_mldsa65Verify', queryParam.pqv || '');
} }
} }

View File

@ -3,6 +3,8 @@ local api = require "luci.passwall.api"
-%> -%>
<script type="text/javascript"> <script type="text/javascript">
//<![CDATA[ //<![CDATA[
var appname = "<%= api.appname %>"
function confirmDeleteNode(remark) { function confirmDeleteNode(remark) {
if (!confirm("<%:Delete the subscribed node%>: " + remark + " ?")) if (!confirm("<%:Delete the subscribed node%>: " + remark + " ?"))
return false; return false;
@ -34,5 +36,76 @@ local api = require "luci.passwall.api"
}); });
return false; return false;
} }
function ManualSubscribe(sectionId) {
var urlInput = document.querySelector("input[name='cbid." + appname + "." + sectionId + ".url']");
var currentUrl = urlInput ? urlInput.value.trim() : "";
if (!currentUrl) {
alert("<%:Subscribe URL cannot be empty.%>");
return;
}
fetch('<%= api.url("subscribe_manual") %>?section='
+ encodeURIComponent(sectionId)
+ '&url='
+ encodeURIComponent(currentUrl))
.then(response => response.json())
.then(data => {
if (!data.success) {
alert(data.msg || "Operation failed");
} else {
window.location.href = '<%= api.url("log") %>'
}
});
}
function ManualSubscribeAll() {
var sectionIds = [];
var urls = [];
var table = document.getElementById("cbi-" + appname + "-subscribe_list");
var editBtns = table ? table.getElementsByClassName("cbi-button cbi-button-edit") : [];
for (var i = 0; i < editBtns.length; i++) {
var btn = editBtns[i];
var onclickStr = btn.getAttribute("onclick");
if (!onclickStr) continue;
var id = onclickStr.substring(onclickStr.lastIndexOf('/') + 1, onclickStr.length - 1);
if (!id) continue;
var urlInput = document.querySelector("input[name='cbid." + appname + "." + id + ".url']");
var currentUrl = urlInput ? urlInput.value.trim() : "";
if (!currentUrl) {
alert("<%:Subscribe URL cannot be empty.%>");
return;
}
sectionIds.push(id);
urls.push(currentUrl);
}
if (sectionIds.length === 0) {
//alert("No subscriptions found.");
return;
}
var params = new URLSearchParams();
params.append("sections", sectionIds.join(","));
params.append("urls", urls.join(","));
fetch('<%= api.url("subscribe_manual_all") %>', {
method: 'POST',
body: params
})
.then(response => response.json())
.then(data => {
if (!data.success) {
alert(data.msg || "Operation failed");
} else {
window.location.href = '<%= api.url("log") %>'
}
});
}
//]]> //]]>
</script> </script>

View File

@ -445,7 +445,7 @@ msgstr "负载均衡策略"
msgid "Fallback Node" msgid "Fallback Node"
msgstr "后备节点" msgstr "后备节点"
msgid "Use Custome Probe URL" msgid "Use Custom Probe URL"
msgstr "使用自定义探测网址" msgstr "使用自定义探测网址"
msgid "By default the built-in probe URL will be used, enable this option to use a custom probe URL." msgid "By default the built-in probe URL will be used, enable this option to use a custom probe URL."
@ -1072,8 +1072,14 @@ msgstr "订阅网址"
msgid "Subscribe URL Access Method" msgid "Subscribe URL Access Method"
msgstr "订阅网址访问方式" msgstr "订阅网址访问方式"
msgid "Please input the subscription url first, save and submit before manual subscription." msgid "When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically."
msgstr "请输入订阅网址保存应用后再手动订阅。" msgstr "新增订阅请先保存并应用后再手动订阅;如仅修改订阅地址,可直接手动订阅,系统将自动保存。"
msgid "Please save and apply before manually subscribing."
msgstr "请先保存并应用后再手动订阅。"
msgid "Subscribe URL cannot be empty."
msgstr "订阅网址不能为空。"
msgid "Subscribe via proxy" msgid "Subscribe via proxy"
msgstr "通过代理订阅" msgstr "通过代理订阅"
@ -1706,16 +1712,10 @@ msgid "Protocol parameter. Enable length block encryption."
msgstr "协议参数。启用长度块加密。" msgstr "协议参数。启用长度块加密。"
msgid "ECH Config" msgid "ECH Config"
msgstr "ECH 密钥"
msgid "ECH Key"
msgstr "ECH 配置" msgstr "ECH 配置"
msgid "PQ signature schemes" msgid "ECH Key"
msgstr "后量子对等证书签名方案" msgstr "ECH 密钥"
msgid "Disable adaptive sizing of TLS records"
msgstr "禁用 TLS 记录的自适应大小调整"
msgid "Enable Multipath TCP, need to be enabled in both server and client configuration." msgid "Enable Multipath TCP, need to be enabled in both server and client configuration."
msgstr "启用 Multipath TCP需在服务端和客户端配置中同时启用。" msgstr "启用 Multipath TCP需在服务端和客户端配置中同时启用。"

View File

@ -8,7 +8,3 @@
120.53.53.53 120.53.53.53
180.184.1.1 180.184.1.1
180.184.2.2 180.184.2.2
203.208.39.192/28
203.208.40.0/23
34.149.0.0/16
72.18.83.0/24

View File

@ -820,11 +820,17 @@ local function processData(szType, content, add_mode, add_from)
result.utls = "1" result.utls = "1"
result.fingerprint = params.fp result.fingerprint = params.fp
end end
if params.ech and params.ech ~= "" then
result.ech = "1"
result.ech_config = params.ech
end
if params.security == "reality" then if params.security == "reality" then
result.reality = "1" result.reality = "1"
result.reality_publicKey = params.pbk or nil result.reality_publicKey = params.pbk or nil
result.reality_shortId = params.sid or nil result.reality_shortId = params.sid or nil
result.reality_spiderX = params.spx or nil result.reality_spiderX = params.spx or nil
result.use_mldsa65Verify = (params.pqv and params.pqv ~= "") and "1" or nil
result.reality_mldsa65Verify = params.pqv or nil
end end
end end
params.allowinsecure = params.allowinsecure or params.insecure params.allowinsecure = params.allowinsecure or params.insecure
@ -1196,11 +1202,17 @@ local function processData(szType, content, add_mode, add_from)
result.utls = "1" result.utls = "1"
result.fingerprint = params.fp result.fingerprint = params.fp
end end
if params.ech and params.ech ~= "" then
result.ech = "1"
result.ech_config = params.ech
end
if params.security == "reality" then if params.security == "reality" then
result.reality = "1" result.reality = "1"
result.reality_publicKey = params.pbk or nil result.reality_publicKey = params.pbk or nil
result.reality_shortId = params.sid or nil result.reality_shortId = params.sid or nil
result.reality_spiderX = params.spx or nil result.reality_spiderX = params.spx or nil
result.use_mldsa65Verify = (params.pqv and params.pqv ~= "") and "1" or nil
result.reality_mldsa65Verify = params.pqv or nil
end end
end end
@ -1913,7 +1925,6 @@ local execute = function()
local raw_data = api.trim(stdout) local raw_data = api.trim(stdout)
local old_md5 = value.md5 or "" local old_md5 = value.md5 or ""
local new_md5 = luci.sys.exec("md5sum " .. tmp_file .. " 2>/dev/null | awk '{print $1}'"):gsub("\n", "") local new_md5 = luci.sys.exec("md5sum " .. tmp_file .. " 2>/dev/null | awk '{print $1}'"):gsub("\n", "")
os.remove(tmp_file)
if not manual_sub and old_md5 == new_md5 then if not manual_sub and old_md5 == new_md5 then
log('订阅:【' .. remark .. '】没有变化,无需更新。') log('订阅:【' .. remark .. '】没有变化,无需更新。')
else else
@ -1924,6 +1935,7 @@ local execute = function()
fail_list[#fail_list + 1] = value fail_list[#fail_list + 1] = value
end end
end end
luci.sys.call("rm -f " .. tmp_file)
allowInsecure_default = nil allowInsecure_default = nil
filter_keyword_mode_default = uci:get(appname, "@global_subscribe[0]", "filter_keyword_mode") or "0" filter_keyword_mode_default = uci:get(appname, "@global_subscribe[0]", "filter_keyword_mode") or "0"
filter_keyword_discard_list_default = uci:get(appname, "@global_subscribe[0]", "filter_discard_list") or {} filter_keyword_discard_list_default = uci:get(appname, "@global_subscribe[0]", "filter_discard_list") or {}