🎄 Sync 2026-05-06 19:35:12

This commit is contained in:
github-actions[bot] committed 2026-05-06 19:35:12 +08:00
1 parent 17a7679f9b
commit 80959a4700
50 files changed
+1787 -626

No files matched your search

@@ -23,6 +23,7 @@ local uci = api.uci
local fs = api.fs
local log = api.log
local i18n = api.i18n
local lyaml = require "lyaml"
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
@@ -477,6 +478,292 @@ local function set_ss_implementation(ss_type, result)
return result
end
local function parseClashNode(node, add_mode, group, sub_cfg)
local sub_allowinsecure = DEFAULT_ALLOWINSECURE
local sub_ss_type = DEFAULT_SS_TYPE
local sub_trojan_type = DEFAULT_TROJAN_TYPE
local sub_vmess_type = DEFAULT_VMESS_TYPE
local sub_vless_type = DEFAULT_VLESS_TYPE
local sub_hysteria2_type = DEFAULT_HYSTERIA2_TYPE
if sub_cfg then
if sub_cfg.allowInsecure and sub_cfg.allowInsecure ~= "1" then
sub_allowinsecure = nil
end
local ss_type = sub_cfg.ss_type or "global"
if ss_type ~= "global" and core_has[ss_type] then
sub_ss_type = ss_type
end
local trojan_type = sub_cfg.trojan_type or "global"
if trojan_type ~= "global" and core_has[trojan_type] then
sub_trojan_type = trojan_type
end
local vmess_type = sub_cfg.vmess_type or "global"
if vmess_type ~= "global" and core_has[vmess_type] then
sub_vmess_type = vmess_type
end
local vless_type = sub_cfg.vless_type or "global"
if vless_type ~= "global" and core_has[vless_type] then
sub_vless_type = vless_type
end
local hysteria2_type = sub_cfg.hysteria2_type or "global"
if hysteria2_type ~= "global" and core_has[hysteria2_type] then
sub_hysteria2_type = hysteria2_type
end
end
local result = {
timeout = 60,
add_mode = add_mode, -- `0` for manual configuration, `1` for import, `2` for subscription
group = group
}
result.remarks = node.name
result.address = node.server
result.port = node.port
if node.type == 'ss' then
result = set_ss_implementation(sub_ss_type, result)
if not result then return nil end
result.method = node.cipher
result.password = node.password
if node.plugin == "obfs" then
result.plugin = "obfs-local"
elseif node.plugin == "v2ray-plugin" then
result.plugin = "v2ray-plugin"
end
if node["plugin-opts"] then
if node.plugin == "obfs" then
local plugin_opts = ""
local opts_mode = node["plugin-opts"].mode
if opts_mode then
plugin_opts = plugin_opts .. "obfs=" .. opts_mode .. ";"
end
local opts_host = node["plugin-opts"].host
if opts_host then
plugin_opts = plugin_opts .. "obfs-host=" .. opts_host
end
result.plugin_opts = plugin_opts
elseif node.plugin == "v2ray-plugin" then
local plugin_opts = ""
local opts_mode = node["plugin-opts"].mode
local opts_tls = node["plugin-opts"].tls
if opts_tls then
plugin_opts = plugin_opts .. "tls;"
end
local opts_skip_cert_verify = node["plugin-opts"]["skip-cert-verify"]
local opts_host = node["plugin-opts"].host
if opts_host then
plugin_opts = plugin_opts .. "host=" .. opts_host .. ";"
end
local opts_path = node["plugin-opts"].path
local opts_mux = node["plugin-opts"].mux
if node["plugin-opts"].headers then
--todo
end
result.plugin_opts = plugin_opts
end
end
elseif node.type == 'ssr' then
if not has_ssr then
log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "SSR", "shadowsocksr-libev"))
return nil
end
result.type = "SSR"
result.method = node.cipher
result.password = node.password
result.obfs = node.obfs
result.protocol = node.protocol
result.obfs_param = node["obfs-param"]
result.protocol_param = node["protocol-param"]
elseif node.type == 'vmess' then
if sub_vmess_type == "sing-box" and has_singbox then
result.type = 'sing-box'
elseif sub_vmess_type == "xray" and has_xray then
result.type = "Xray"
else
log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "VMess", "VMess"))
return nil
end
result.protocol = 'vmess'
result.uuid = node.uuid
result.alter_id = node.alterId
result.security = node.cipher or "auto"
result.tcp_fast_open = node.tfo
result.tls = "0"
if node.tls then
result.tls = "1"
result.tls_serverName = node.servername or ""
local insecure = node["skip-cert-verify"]
result.tls_allowInsecure = insecure and "1" or "0"
if sub_allowinsecure then
result.tls_allowInsecure = "1"
end
end
result.transport = node.network and string.lower(node.network) or "tcp"
if result.type == "sing-box" and result.transport == "raw" then
result.transport = "tcp"
elseif result.type == "Xray" and result.transport == "tcp" then
result.transport = "raw"
end
if result.transport == 'ws' then
local ws_opts = node["ws-opts"]
if ws_opts then
if ws_opts.headers then
result.ws_host = ws_opts.headers.Host or ws_opts.headers.host
end
if ws_opts.path then
result.ws_path = ws_opts.path
if ws_opts["max-early-data"] then
if result.type == "sing-box" then
result.ws_enableEarlyData = "1"
result.ws_maxEarlyData = tonumber(ws_opts["max-early-data"])
result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol"
elseif result.type == "Xray" then
result.ws_path = result.ws_path .. "?ed=" .. ws_opts["max-early-data"]
end
end
end
end
elseif result.transport == 'h2' then
local h2_opts = node["h2-opts"]
if h2_opts then
if result.type == "sing-box" then
result.http_path = h2_opts.path
result.http_host = h2_opts.host
elseif result.type == "Xray" then
result.xhttp_mode = "stream-one"
result.xhttp_path = h2_opts.path
result.xhttp_host = h2_opts.host
end
end
elseif result.transport == 'grpc' then
local grpc_opts = node["grpc-opts"]
if grpc_opts then
result.grpc_serviceName = grpc_opts["grpc-service-name"]
end
end
elseif node.type == 'vless' then
if sub_vless_type == "sing-box" and has_singbox then
result.type = 'sing-box'
elseif sub_vless_type == "xray" and has_xray then
result.type = "Xray"
else
log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "VLESS", "VLESS"))
return nil
end
result.protocol = "vless"
result.uuid = node.uuid
result.tcp_fast_open = node.tfo
result.encryption = node.cipher or "none"
result.flow = node.flow
result.tls = "0"
if node.tls then
result.tls = "1"
result.tls_serverName = node.servername or ""
local insecure = node["skip-cert-verify"]
result.tls_allowInsecure = insecure and "1" or "0"
if sub_allowinsecure then
result.tls_allowInsecure = "1"
end
end
result.transport = node.network and string.lower(node.network) or "tcp"
if result.type == "sing-box" and result.transport == "raw" then
result.transport = "tcp"
elseif result.type == "Xray" and result.transport == "tcp" then
result.transport = "raw"
end
if result.transport == 'ws' then
local ws_opts = node["ws-opts"]
if ws_opts then
if ws_opts.headers then
result.ws_host = ws_opts.headers.Host or ws_opts.headers.host
end
if ws_opts.path then
result.ws_path = ws_opts.path
if ws_opts["max-early-data"] then
if result.type == "sing-box" then
result.ws_enableEarlyData = "1"
result.ws_maxEarlyData = tonumber(ws_opts["max-early-data"])
result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol"
elseif result.type == "Xray" then
result.ws_path = result.ws_path .. "?ed=" .. ws_opts["max-early-data"]
end
end
end
end
end
elseif node.type == 'trojan' then
if sub_trojan_type == "sing-box" and has_singbox then
result.type = 'sing-box'
result.protocol = 'trojan'
elseif sub_trojan_type == "xray" and has_xray then
result.type = 'Xray'
result.protocol = 'trojan'
else
log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "Trojan", "Trojan"))
return nil
end
result.password = node.password
result.tls = '1'
result.tls_serverName = node.sni or ""
local insecure = node["skip-cert-verify"]
result.tls_allowInsecure = insecure and "1" or "0"
if sub_allowinsecure then
result.tls_allowInsecure = "1"
end
if node.alpn then
--todo
end
if node.network == "grpc" then
if node["grpc-opts"] then
result.grpc_serviceName = node["grpc-opts"]["grpc-service-name"]
end
elseif node.network == "ws" then
local ws_opts = node["ws-opts"]
if ws_opts then
result.ws_path = ws_opts.path
if ws_opts.headers then
result.ws_host = ws_opts.headers.Host or ws_opts.headers.host
end
end
end
elseif node.type == 'anytls' then
if has_singbox then
result.type = 'sing-box'
result.protocol = "anytls"
else
log(2, i18n.translatef("Skip the %s node because the %s core program is not installed.", "AnyTLS", "AnyTLS", "Sing-Box 1.12"))
return nil
end
result.password = node.password
result.tls = '1'
result.tls_serverName = node.sni or ""
local insecure = node["skip-cert-verify"]
result.tls_allowInsecure = insecure and "1" or "0"
if sub_allowinsecure then
result.tls_allowInsecure = "1"
end
end
if not result.remarks or result.remarks == "" then
if result.address and result.port then
result.remarks = result.address .. ':' .. result.port
else
result.remarks = "NULL"
end
end
return result
end
-- Processing Clash data
local function processClashData(content, add_mode, group, sub_cfg)
local results = {}
for i, node in ipairs(content.proxies or {}) do
local result = parseClashNode(node, add_mode, group, sub_cfg)
if result then
table.insert(results, result)
end
end
return results
end
-- Processing data
local function processData(szType, content, add_mode, group, sub_cfg)
--log(2, content, add_mode, group)
@@ -650,9 +937,14 @@ local function processData(szType, content, add_mode, group, sub_cfg)
result.httpupgrade_host = info.host
result.httpupgrade_path = info.path
end
if not info.security then result.security = "auto" end
result.security = info.security or info.scy or "auto"
if info.tls == "tls" or info.tls == "1" then
result.tls = "1"
result.alpn = info.alpn
if info.fp and info.fp ~= "" then
result.utls = "1"
result.fingerprint = info.fp
end
result.tls_serverName = (info.sni and info.sni ~= "") and info.sni or info.host
result.tls_pinSHA256 = info.pcs
result.tls_CertByName = info.vcn
@@ -2001,7 +2293,7 @@ local function parse_link(raw, add_mode, group, sub_cfg)
if sub_cfg then
cfgid = sub_cfg[".name"]
end
local nodes, szType
local nodes, szType, clashTable
local node_list = {}
-- ssd appear to be in this format, starting with ssd://.
if raw:find('ssd://') then
@@ -2022,11 +2314,45 @@ local function parse_link(raw, add_mode, group, sub_cfg)
end
nodes = servers
else
-- Formats other than ssd
if add_mode == "1" then
nodes = split(raw, "\n")
-- Try parseYAML, if success, is clash.
local yamlTable = lyaml.load(raw)
if yamlTable and type(yamlTable) == "table" then
-- clash
szType = "clash"
clashTable = yamlTable
else
nodes = split(base64Decode(raw):gsub("\r\n", "\n"), "\n")
-- Formats other than ssd
if add_mode == "1" then
nodes = split(raw, "\n")
else
nodes = split(base64Decode(raw):gsub("\r\n", "\n"), "\n")
end
end
end
function nodeFilter(node)
if node then
if node.error_msg then
log(2, i18n.translatef("Discard node: %s, Reason:", node.remarks) .. " " .. node.error_msg)
elseif not node.type then
log(2, i18n.translatef("Discard node: %s, Reason:", node.remarks) .. " " .. i18n.translatef("No usable binary was found."))
elseif (add_mode == "2" and is_filter_keyword(sub_cfg, node.remarks)) or not node.address or node.remarks == "NULL" or node.address == "127.0.0.1" or
(not datatypes.hostname(node.address) and not (api.is_ip(node.address))) then
log(2, i18n.translatef("Discard filter nodes: %s type node %s", node.type, node.remarks))
else
tinsert(node_list, node)
end
if add_mode == "2" then
get_subscribe_info(cfgid, node.remarks)
end
end
end
if szType == "clash" and clashTable then
nodes = {}
local clashNodes = processClashData(clashTable, add_mode, group, sub_cfg)
for _, v in ipairs(clashNodes) do
nodeFilter(v)
end
end
@@ -2052,21 +2378,7 @@ local function parse_link(raw, add_mode, group, sub_cfg)
log(2, i18n.translatef("Skip unknown types:") .. " " .. szType)
end
-- log(2, result)
if result then
if result.error_msg then
log(2, i18n.translatef("Discard node: %s, Reason:", result.remarks) .. " " .. result.error_msg)
elseif not result.type then
log(2, i18n.translatef("Discard node: %s, Reason:", result.remarks) .. " " .. i18n.translatef("No usable binary was found."))
elseif (add_mode == "2" and is_filter_keyword(sub_cfg, result.remarks)) or not result.address or result.remarks == "NULL" or result.address == "127.0.0.1" or
(not datatypes.hostname(result.address) and not (api.is_ip(result.address))) then
log(2, i18n.translatef("Discard filter nodes: %s type node %s", result.type, result.remarks))
else
tinsert(node_list, result)
end
if add_mode == "2" then
get_subscribe_info(cfgid, result.remarks)
end
end
nodeFilter(result)
end, function (err)
--log(2, err)
log(2, v, i18n.translatef("Parsing error, skip this node."))