mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-11 19:07:52 +08:00
🎉 Sync 2026-11-08 01:01:38
This commit is contained in:
1 parent
f7911ea0fb
commit
3d26c9667a
57 files changed
+4016
-1243
No files matched your search
@@ -624,7 +624,83 @@ local function encode_node(node)
|
||||
end
|
||||
end
|
||||
|
||||
function parseClashNode(raw, remark)
|
||||
local function parse_dns_endpoint(value)
|
||||
if type(value) ~= "string" then return nil end
|
||||
value = api.trim(value)
|
||||
-- Clash fragments and URL queries cannot be preserved by both node DNS backends.
|
||||
if value == "" or value:find("[%s#?@,]") then return nil end
|
||||
local url = value:find("://", 1, true) and value or "udp://" .. value
|
||||
local ok, endpoint = pcall(api.parseURL, url)
|
||||
if not ok or not endpoint then return nil end
|
||||
local protocol = endpoint.protocol
|
||||
if protocol ~= "udp" and protocol ~= "tcp" and protocol ~= "https" then return nil end
|
||||
local host = endpoint.hostname
|
||||
if not host or not (api.is_ip(host) or api.datatypes.hostname(host)) then return nil end
|
||||
local bracketed = endpoint.host:sub(1, 1) == "["
|
||||
local ipv6 = api.datatypes.ip6addr(host)
|
||||
if bracketed and not ipv6 then return nil end
|
||||
if protocol == "https" and ipv6 and not bracketed then return nil end
|
||||
local port = endpoint.port or (protocol == "https" and 443 or 53)
|
||||
if port < 1 or port > 65535 then return nil end
|
||||
if protocol ~= "https" and endpoint.pathname then return nil end
|
||||
return { protocol = protocol, host = host:lower():gsub("%.$", ""), port = port, url = url }
|
||||
end
|
||||
|
||||
local function is_loopback_dns(host)
|
||||
return host == "localhost" or (api.datatypes.ip4addr(host) and host:match("^127%.")) or host:match("^[0:]+:0*1$")
|
||||
end
|
||||
|
||||
local function is_unspecified_dns(host)
|
||||
return host == "0.0.0.0" or host:match("^[0:]+$")
|
||||
end
|
||||
|
||||
local function extract_clash_dns(data)
|
||||
local dns = data.dns
|
||||
if type(dns) ~= "table" or not dns["proxy-server-nameserver"] then return nil end
|
||||
-- A subscription-wide resolver cannot represent per-domain node DNS policies.
|
||||
local node_policy = dns["proxy-server-nameserver-policy"]
|
||||
if type(node_policy) == "table" and next(node_policy) then return nil end
|
||||
local listener = parse_dns_endpoint(dns.listen)
|
||||
local resolving = {}
|
||||
local policy = dns["nameserver-policy"]
|
||||
local has_policy = type(policy) == "table" and next(policy)
|
||||
local function pick(candidates, depth)
|
||||
if depth > 4 then return nil end
|
||||
if type(candidates) == "string" then candidates = { candidates } end
|
||||
if type(candidates) ~= "table" then return nil end
|
||||
for _, value in ipairs(candidates) do
|
||||
local endpoint = parse_dns_endpoint(value)
|
||||
if endpoint and not is_unspecified_dns(endpoint.host) then
|
||||
if is_loopback_dns(endpoint.host) then
|
||||
local key = endpoint.protocol .. "://" .. endpoint.host .. ":" .. endpoint.port
|
||||
local listen_matches = listener and listener.protocol ~= "https" and listener.port == endpoint.port and (
|
||||
listener.host == endpoint.host or
|
||||
(listener.host == "0.0.0.0" and not endpoint.host:find(":", 1, true)) or
|
||||
(is_unspecified_dns(listener.host) and listener.host ~= "0.0.0.0" and endpoint.host:find(":", 1, true)) or
|
||||
(endpoint.host == "localhost" and is_loopback_dns(listener.host)))
|
||||
if endpoint.protocol ~= "https" and dns.enable == true and listen_matches and not resolving[key] and not has_policy then
|
||||
resolving[key] = true
|
||||
local resolver = pick(dns.nameserver, depth + 1)
|
||||
resolving[key] = nil
|
||||
if resolver then return resolver end
|
||||
end
|
||||
elseif endpoint.protocol == "https" then
|
||||
return { domain_resolver = "https", domain_resolver_dns_https = endpoint.url }
|
||||
elseif api.datatypes.ip4addr(endpoint.host) then
|
||||
return { domain_resolver = endpoint.protocol, domain_resolver_dns = endpoint.host .. ":" .. endpoint.port }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local resolver = pick(dns["proxy-server-nameserver"], 0)
|
||||
if resolver then
|
||||
local address = resolver.domain_resolver_dns_https or resolver.domain_resolver .. "://" .. resolver.domain_resolver_dns
|
||||
api.log("订阅转换 → 已提取节点域名 DNS:" .. address)
|
||||
end
|
||||
return resolver
|
||||
end
|
||||
|
||||
function parseClashNode(raw, remark, auto_dns)
|
||||
if not raw then return "" end
|
||||
domain_redir = {}
|
||||
local ok, lyaml = pcall(require, "lyaml")
|
||||
@@ -662,7 +738,8 @@ function parseClashNode(raw, remark)
|
||||
end
|
||||
end
|
||||
|
||||
return #links > 0 and table.concat(links, "\n") or ""
|
||||
local resolver = #links > 0 and auto_dns and extract_clash_dns(data) or nil
|
||||
return #links > 0 and table.concat(links, "\n") or "", resolver
|
||||
end
|
||||
|
||||
function parse_clash_sub_info(headers)
|
||||
|
||||
@@ -1973,12 +1973,12 @@ local function update_node(manual)
|
||||
end
|
||||
-- sing-box/xray 节点域名解析
|
||||
if kkk == "type" and (vvv == "Xray" or vvv == "sing-box") then
|
||||
if domain_resolver then
|
||||
if domain_resolver and domain_resolver ~= "" and domain_resolver ~= "disable" then
|
||||
uci_set(cfgid, "domain_resolver", domain_resolver)
|
||||
if domain_resolver_dns then
|
||||
uci_set(cfgid, "domain_resolver_dns", domain_resolver_dns)
|
||||
elseif domain_resolver_dns_https then
|
||||
if domain_resolver == "https" and domain_resolver_dns_https then
|
||||
uci_set(cfgid, "domain_resolver_dns_https", domain_resolver_dns_https)
|
||||
elseif (domain_resolver == "udp" or domain_resolver == "tcp") and domain_resolver_dns then
|
||||
uci_set(cfgid, "domain_resolver_dns", domain_resolver_dns)
|
||||
end
|
||||
end
|
||||
if domain_strategy then
|
||||
@@ -2178,7 +2178,15 @@ local execute = function()
|
||||
local stdout = f:read("*all")
|
||||
f:close()
|
||||
local raw_data = api.trim(stdout)
|
||||
raw_data = parseClashNode(raw_data)
|
||||
local auto_dns = value.domain_resolver == nil or value.domain_resolver == ""
|
||||
local resolver
|
||||
raw_data, resolver = parseClashNode(raw_data, remark, auto_dns)
|
||||
if auto_dns and resolver then
|
||||
-- Only enrich this run's sub_cfg; keep the subscription's Auto setting.
|
||||
value.domain_resolver = resolver.domain_resolver
|
||||
value.domain_resolver_dns = resolver.domain_resolver_dns
|
||||
value.domain_resolver_dns_https = resolver.domain_resolver_dns_https
|
||||
end
|
||||
-- 优先提取标准 Clash 订阅响应头中的账户用量与到期时间(不受节点变化与否影响)
|
||||
if headers and headers ~= "" then
|
||||
subscribe_info[cfgid] = parse_clash_sub_info(headers)
|
||||
@@ -2187,8 +2195,15 @@ local execute = function()
|
||||
log_buffer = {}
|
||||
local parsed_nodes = parse_link(raw_data, "2", remark, value)
|
||||
local old_md5 = value.md5 or ""
|
||||
local new_md5 = #parsed_nodes > 0 and api.md5_string(jsonStringify(parsed_nodes)) or ""
|
||||
-- 仅当解析出有效节点且节点集合与上次完全一致时,跳过节点重写与服务重启
|
||||
local md5_content = jsonStringify(parsed_nodes)
|
||||
if value.domain_resolver and value.domain_resolver ~= "" and value.domain_resolver ~= "disable" then
|
||||
local protocol = value.domain_resolver
|
||||
local dns = (protocol == "udp" or protocol == "tcp") and value.domain_resolver_dns or ""
|
||||
local doh = protocol == "https" and value.domain_resolver_dns_https or ""
|
||||
md5_content = md5_content .. jsonStringify({protocol, dns or "", doh or ""})
|
||||
end
|
||||
local new_md5 = #parsed_nodes > 0 and api.md5_string(md5_content) or ""
|
||||
-- 节点集合和最终生效的 DNS 都未变化时,跳过节点重写与服务重启。
|
||||
if not manual_sub and new_md5 ~= "" and old_md5 == new_md5 then
|
||||
log_buffer = nil -- 无物理变动,直接丢弃暂存的丢弃与计数明细
|
||||
log('订阅:【' .. remark .. '】没有变化,无需更新。')
|
||||
@@ -2251,6 +2266,7 @@ elseif arg[1] == "add" then
|
||||
local f = assert(io.open("/tmp/links.conf", 'r'))
|
||||
local raw = f:read('*all')
|
||||
f:close()
|
||||
raw = parseClashNode(raw, arg[2])
|
||||
parse_link(raw, "1", arg[2])
|
||||
if update_node(1) then uci_save(true) end
|
||||
api.remove("/tmp/links.conf")
|
||||
|
||||
Reference in new issue
Block a user