|
@@ -173,34 +173,6 @@ local function isCompleteJSON(str)
|
|
|
local success, _ = pcall(jsonParse, str)
|
|
|
return success
|
|
|
end
|
|
|
-local function detectNodeType(rawContent)
|
|
|
- -- 去掉 # 前后空格,处理 HTML 转义 &
|
|
|
- local content = trim(rawContent:gsub("&[a-zA-Z]+;", "&"):gsub("%s*#%s*", "#"))
|
|
|
-
|
|
|
- -- 找到 # 分隔位置
|
|
|
- local idx_sp = content:find("#") or 0
|
|
|
- local info = content:sub(1, idx_sp > 0 and idx_sp - 1 or #content):gsub("/%?", "?")
|
|
|
-
|
|
|
- -- 拆 base64 主体和 ? 参数部分
|
|
|
- local uri_main, query_str = info:match("^([^?]+)%??(.*)$")
|
|
|
-
|
|
|
- local params = {}
|
|
|
- if query_str and query_str ~= "" then
|
|
|
- for _, v in ipairs(split(query_str, '&')) do
|
|
|
- local t = split(v, '=')
|
|
|
- if #t >= 2 then
|
|
|
- params[t[1]] = UrlDecode(t[2])
|
|
|
- end
|
|
|
- end
|
|
|
- end
|
|
|
-
|
|
|
- -- 判断是否是 Xray-SS 节点
|
|
|
- if params["type"] then
|
|
|
- return "shadowsocks"
|
|
|
- else
|
|
|
- return "ss"
|
|
|
- end
|
|
|
-end
|
|
|
-- 处理数据
|
|
|
local function processData(szType, content)
|
|
|
local result = {type = szType, local_port = 1234, kcp_param = '--nocomp'}
|
|
@@ -211,14 +183,6 @@ local function processData(szType, content)
|
|
|
end
|
|
|
end
|
|
|
|
|
|
- -- 协议头识别
|
|
|
- if szType == "ss" then
|
|
|
- local nodeType = detectNodeType(content)
|
|
|
- if nodeType == "shadowsocks" then
|
|
|
- szType = "shadowsocks" -- 替换类型
|
|
|
- end
|
|
|
- end
|
|
|
-
|
|
|
if szType == "hysteria2" or szType == "hy2" then
|
|
|
local url = URL.parse("http://" .. content)
|
|
|
local params = url.query
|
|
@@ -261,7 +225,7 @@ local function processData(szType, content)
|
|
|
end
|
|
|
if params.alpn then
|
|
|
local alpn = {}
|
|
|
- for v in params.alpn:gmatch("[^,]+") do
|
|
|
+ for v in params.alpn:gmatch("[^,;|%s]+") do
|
|
|
table.insert(alpn, v)
|
|
|
end
|
|
|
result.tls_alpn = alpn
|
|
@@ -435,7 +399,6 @@ local function processData(szType, content)
|
|
|
result.security = info.security
|
|
|
end
|
|
|
elseif szType == "ss" then
|
|
|
- local content = trim(content:gsub("&[a-zA-Z]+;", "&"):gsub("%s*#%s*", "#"))
|
|
|
local idx_sp = content:find("#") or 0
|
|
|
local alias = ""
|
|
|
if idx_sp > 0 then
|
|
@@ -456,153 +419,255 @@ local function processData(szType, content)
|
|
|
end
|
|
|
end
|
|
|
|
|
|
- local is_old_format = uri_main:find("@") and not uri_main:find("://.*@")
|
|
|
- local base64_str, host_port, userinfo, server, port, method, password
|
|
|
+ if not params.type or params.type == "" then
|
|
|
+ local is_old_format = uri_main:find("@") and not uri_main:find("://.*@")
|
|
|
+ local base64_str, host_port, userinfo, server, port, method, password
|
|
|
|
|
|
- if is_old_format then
|
|
|
- -- 旧格式:base64(method:pass)@host:port
|
|
|
- base64_str, host_port = uri_main:match("^([^@]+)@(.-)$")
|
|
|
- log("SS 节点旧格式解析:", base64_str)
|
|
|
- if not base64_str or not host_port then
|
|
|
- log("SS 节点旧格式解析失败:", uri_main)
|
|
|
- return nil
|
|
|
- end
|
|
|
- local decoded = base64Decode(UrlDecode(base64_str))
|
|
|
- if not decoded then
|
|
|
- log("SS base64 解码失败(旧格式):", base64_str)
|
|
|
- return nil
|
|
|
- end
|
|
|
- userinfo = decoded
|
|
|
- else
|
|
|
- -- 新格式:base64(method:pass@host:port)
|
|
|
- local decoded = base64Decode(UrlDecode(uri_main))
|
|
|
- if not decoded then
|
|
|
- log("SS base64 解码失败(新格式):", uri_main)
|
|
|
- return nil
|
|
|
+ if is_old_format then
|
|
|
+ -- 旧格式:base64(method:pass)@host:port
|
|
|
+ base64_str, host_port = uri_main:match("^([^@]+)@(.-)$")
|
|
|
+ log("SS 节点旧格式解析:", base64_str)
|
|
|
+ if not base64_str or not host_port then
|
|
|
+ log("SS 节点旧格式解析失败:", uri_main)
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+ local decoded = base64Decode(UrlDecode(base64_str))
|
|
|
+ if not decoded then
|
|
|
+ log("SS base64 解码失败(旧格式):", base64_str)
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+ userinfo = decoded
|
|
|
+ else
|
|
|
+ -- 新格式:base64(method:pass@host:port)
|
|
|
+ local decoded = base64Decode(UrlDecode(uri_main))
|
|
|
+ if not decoded then
|
|
|
+ log("SS base64 解码失败(新格式):", uri_main)
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+ userinfo, host_port = decoded:match("^(.-)@(.-)$")
|
|
|
+ if not userinfo or not host_port then
|
|
|
+ log("SS 解码内容缺失 @ 分隔:", decoded)
|
|
|
+ return nil
|
|
|
+ end
|
|
|
end
|
|
|
- userinfo, host_port = decoded:match("^(.-)@(.-)$")
|
|
|
- if not userinfo or not host_port then
|
|
|
- log("SS 解码内容缺失 @ 分隔:", decoded)
|
|
|
+
|
|
|
+ -- 解析加密方式和密码(允许密码包含冒号)
|
|
|
+ local split_pos = userinfo:find(":")
|
|
|
+ if not split_pos then
|
|
|
+ log("SS 用户信息格式错误:", userinfo)
|
|
|
return nil
|
|
|
end
|
|
|
- end
|
|
|
-
|
|
|
- -- 解析加密方式和密码(允许密码包含冒号)
|
|
|
- local split_pos = userinfo:find(":")
|
|
|
- if not split_pos then
|
|
|
- log("SS 用户信息格式错误:", userinfo)
|
|
|
- return nil
|
|
|
- end
|
|
|
- method = userinfo:sub(1, split_pos - 1)
|
|
|
- password = userinfo:sub(split_pos + 1)
|
|
|
+ method = userinfo:sub(1, split_pos - 1)
|
|
|
+ password = userinfo:sub(split_pos + 1)
|
|
|
|
|
|
- -- 判断密码是否经过url编码
|
|
|
- local function isURLEncodedPassword(pwd)
|
|
|
- if not pwd:find("%%[0-9A-Fa-f][0-9A-Fa-f]") then
|
|
|
- return false
|
|
|
+ -- 判断密码是否经过url编码
|
|
|
+ local function isURLEncodedPassword(pwd)
|
|
|
+ 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
|
|
|
end
|
|
|
- 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
|
|
|
- end
|
|
|
-
|
|
|
- -- 解析服务器地址和端口(兼容 IPv6)
|
|
|
- if host_port:find("^%[.*%]:%d+$") then
|
|
|
- server, port = host_port:match("^%[(.*)%]:(%d+)$")
|
|
|
- else
|
|
|
- server, port = host_port:match("^(.-):(%d+)$")
|
|
|
- end
|
|
|
- if not server or not port then
|
|
|
- log("SS 节点服务器信息格式错误:", host_port)
|
|
|
- return nil
|
|
|
- end
|
|
|
-
|
|
|
- -- 如果 SS 程序未安装则跳过订阅
|
|
|
- if not (v2_ss or has_ss_type) then
|
|
|
- return nil
|
|
|
- end
|
|
|
-
|
|
|
- -- 填充 result
|
|
|
- result.alias = alias
|
|
|
- result.type = v2_ss
|
|
|
- result.v2ray_protocol = (v2_ss == "v2ray") and "shadowsocks" or nil
|
|
|
- result.has_ss_type = has_ss_type
|
|
|
- result.encrypt_method_ss = method
|
|
|
- result.password = password
|
|
|
- result.server = server
|
|
|
- result.server_port = port
|
|
|
-
|
|
|
- -- 仅在 v2ray + shadowsocks 协议时处理 ECH
|
|
|
- if v2_ss == "v2ray" and result.v2ray_protocol == "shadowsocks" then
|
|
|
- if params.ech and params.ech ~= "" then
|
|
|
- result.enable_ech = "1"
|
|
|
- result.ech_config = ech
|
|
|
+ local decoded = UrlDecode(password)
|
|
|
+ if isURLEncodedPassword(password) and decoded then
|
|
|
+ password = decoded
|
|
|
end
|
|
|
- end
|
|
|
|
|
|
- -- 插件处理
|
|
|
- if params.plugin then
|
|
|
- local plugin_info = UrlDecode(params.plugin)
|
|
|
- local idx_pn = plugin_info:find(";")
|
|
|
- if idx_pn then
|
|
|
- result.plugin = plugin_info:sub(1, idx_pn - 1)
|
|
|
- result.plugin_opts = plugin_info:sub(idx_pn + 1, #plugin_info)
|
|
|
+ -- 解析服务器地址和端口(兼容 IPv6)
|
|
|
+ if host_port:find("^%[.*%]:%d+$") then
|
|
|
+ server, port = host_port:match("^%[(.*)%]:(%d+)$")
|
|
|
else
|
|
|
- result.plugin = plugin_info
|
|
|
- result.plugin_opts = ""
|
|
|
+ server, port = host_port:match("^(.-):(%d+)$")
|
|
|
end
|
|
|
- -- 部分机场下发的插件名为 simple-obfs,这里应该改为 obfs-local
|
|
|
- if result.plugin == "simple-obfs" then
|
|
|
- result.plugin = "obfs-local"
|
|
|
+ if not server or not port then
|
|
|
+ log("SS 节点服务器信息格式错误:", host_port)
|
|
|
+ return nil
|
|
|
end
|
|
|
- -- 如果插件不为 none,确保 enable_plugin 为 1
|
|
|
- if result.plugin ~= "none" and result.plugin ~= "" then
|
|
|
- result.enable_plugin = 1
|
|
|
+
|
|
|
+ -- 如果 SS 程序未安装则跳过订阅
|
|
|
+ if not (v2_ss or has_ss_type) then
|
|
|
+ return nil
|
|
|
end
|
|
|
- elseif has_ss_type and has_ss_type ~= "ss-libev" then
|
|
|
- if params["shadow-tls"] then
|
|
|
- -- 特别处理 shadow-tls 作为插件
|
|
|
- -- log("原始 shadow-tls 参数:", params["shadow-tls"])
|
|
|
- local decoded_tls = base64Decode(UrlDecode(params["shadow-tls"]))
|
|
|
- --log("SS 节点 shadow-tls 解码后:", decoded_tls or "nil")
|
|
|
- if decoded_tls then
|
|
|
- local ok, st = pcall(jsonParse, decoded_tls)
|
|
|
- if ok and st then
|
|
|
-
|
|
|
- result.plugin = "shadow-tls"
|
|
|
- result.enable_plugin = 1
|
|
|
+
|
|
|
+ -- 填充 result
|
|
|
+ result.alias = alias
|
|
|
+ result.type = v2_ss
|
|
|
+ result.v2ray_protocol = (v2_ss == "v2ray") and "shadowsocks" or nil
|
|
|
+ result.has_ss_type = has_ss_type
|
|
|
+ result.encrypt_method_ss = method
|
|
|
+ result.password = password
|
|
|
+ result.server = server
|
|
|
+ result.server_port = port
|
|
|
+
|
|
|
+ -- 仅在 v2ray + shadowsocks 协议时处理 ECH
|
|
|
+ if v2_ss == "v2ray" and result.v2ray_protocol == "shadowsocks" then
|
|
|
+ if params.ech and params.ech ~= "" then
|
|
|
+ result.enable_ech = "1"
|
|
|
+ result.ech_config = ech
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 插件处理
|
|
|
+ if params.plugin then
|
|
|
+ local plugin_info = UrlDecode(params.plugin)
|
|
|
+ local idx_pn = plugin_info:find(";")
|
|
|
+ if idx_pn then
|
|
|
+ result.plugin = plugin_info:sub(1, idx_pn - 1)
|
|
|
+ result.plugin_opts = plugin_info:sub(idx_pn + 1, #plugin_info)
|
|
|
+ else
|
|
|
+ result.plugin = plugin_info
|
|
|
+ result.plugin_opts = ""
|
|
|
+ end
|
|
|
+ -- 部分机场下发的插件名为 simple-obfs,这里应该改为 obfs-local
|
|
|
+ if result.plugin == "simple-obfs" then
|
|
|
+ result.plugin = "obfs-local"
|
|
|
+ end
|
|
|
+ -- 如果插件不为 none,确保 enable_plugin 为 1
|
|
|
+ if result.plugin ~= "none" and result.plugin ~= "" then
|
|
|
+ result.enable_plugin = 1
|
|
|
+ end
|
|
|
+ elseif has_ss_type and has_ss_type ~= "ss-libev" then
|
|
|
+ if params["shadow-tls"] then
|
|
|
+ -- 特别处理 shadow-tls 作为插件
|
|
|
+ -- log("原始 shadow-tls 参数:", params["shadow-tls"])
|
|
|
+ local decoded_tls = base64Decode(UrlDecode(params["shadow-tls"]))
|
|
|
+ --log("SS 节点 shadow-tls 解码后:", decoded_tls or "nil")
|
|
|
+ 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)
|
|
|
- end
|
|
|
+ local version_flag = ""
|
|
|
+ if st.version and tonumber(st.version) then
|
|
|
+ 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,
|
|
|
- st.host or "",
|
|
|
- st.password or "")
|
|
|
- else
|
|
|
- log("shadow-tls JSON 解析失败")
|
|
|
+ -- 合成 plugin_opts 格式:v%s=1;host=xxx;password=xxx
|
|
|
+ result.plugin_opts = string.format("%shost=%s;passwd=%s",
|
|
|
+ version_flag,
|
|
|
+ st.host or "",
|
|
|
+ st.password or "")
|
|
|
+ else
|
|
|
+ log("shadow-tls JSON 解析失败")
|
|
|
+ end
|
|
|
end
|
|
|
end
|
|
|
+ else
|
|
|
+ if params["shadow-tls"] then
|
|
|
+ log("错误:ShadowSocks-libev 不支持使用 shadow-tls 插件")
|
|
|
+ return nil, "ShadowSocks-libev 不支持使用 shadow-tls 插件"
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 检查加密方法是否受支持
|
|
|
+ if not checkTabValue(encrypt_methods_ss)[method] then
|
|
|
+ -- 1202 年了还不支持 SS AEAD 的屑机场
|
|
|
+ -- log("不支持的SS加密方法:", method)
|
|
|
+ result.server = nil
|
|
|
end
|
|
|
else
|
|
|
- if params["shadow-tls"] then
|
|
|
- log("错误:ShadowSocks-libev 不支持使用 shadow-tls 插件")
|
|
|
- return nil, "ShadowSocks-libev 不支持使用 shadow-tls 插件"
|
|
|
+ local url = URL.parse("http://" .. info)
|
|
|
+ local params = url.query
|
|
|
+
|
|
|
+ result.alias = alias
|
|
|
+ result.type = "v2ray"
|
|
|
+ result.v2ray_protocol = "shadowsocks"
|
|
|
+ result.server = url.host
|
|
|
+ result.server_port = url.port
|
|
|
+
|
|
|
+ -- 判断 @ 前部分是否为 Base64
|
|
|
+ local is_base64_decoded = base64Decode(UrlDecode(url.user))
|
|
|
+ if is_base64_decoded:find(":") then
|
|
|
+ -- 新格式:method:password
|
|
|
+ result.encrypt_method_ss, result.password = is_base64_decoded:match("^(.-):(.*)$")
|
|
|
+ else
|
|
|
+ -- 旧格式:UUID 直接作为密码
|
|
|
+ result.password = url.user
|
|
|
+ result.encrypt_method_ss = params.encryption or "none"
|
|
|
end
|
|
|
- end
|
|
|
|
|
|
- -- 检查加密方法是否受支持
|
|
|
- if not checkTabValue(encrypt_methods_ss)[method] then
|
|
|
- -- 1202 年了还不支持 SS AEAD 的屑机场
|
|
|
- -- log("不支持的SS加密方法:", method)
|
|
|
- result.server = nil
|
|
|
+ result.transport = params.type or "raw"
|
|
|
+ if result.transport == "tcp" then
|
|
|
+ result.transport = "raw"
|
|
|
+ end
|
|
|
+ if result.transport == "splithttp" then
|
|
|
+ result.transport = "xhttp"
|
|
|
+ end
|
|
|
+ result.tls = (params.security == "tls" or params.security == "xtls") and "1" or "0"
|
|
|
+ if params.alpn and params.alpn ~= "" then
|
|
|
+ local alpn = {}
|
|
|
+ for v in params.alpn:gmatch("[^,;|%s]+") do
|
|
|
+ table.insert(alpn, v)
|
|
|
+ end
|
|
|
+ result.tls_alpn = params.alpn
|
|
|
+ end
|
|
|
+ result.tls_host = params.sni
|
|
|
+ result.tls_flow = (params.security == "tls" or params.security == "reality") and params.flow or nil
|
|
|
+ result.fingerprint = params.fp
|
|
|
+ result.reality = (params.security == "reality") and "1" or "0"
|
|
|
+ result.reality_publickey = params.pbk and UrlDecode(params.pbk) or nil
|
|
|
+ result.reality_shortid = params.sid
|
|
|
+ result.reality_spiderx = params.spx and UrlDecode(params.spx) or nil
|
|
|
+ -- 检查 ech 参数是否存在且非空
|
|
|
+ result.enable_ech = (params.ech and params.ech ~= "") and "1" or nil
|
|
|
+ result.ech_config = (params.ech and params.ech ~= "") and params.ech or nil
|
|
|
+ -- 检查 pqv 参数是否存在且非空
|
|
|
+ result.enable_mldsa65verify = (params.pqv and params.pqv ~= "") and "1" or nil
|
|
|
+ result.reality_mldsa65verify = (params.pqv and params.pqv ~= "") and params.pqv or nil
|
|
|
+ if result.transport == "ws" then
|
|
|
+ result.ws_host = (result.tls ~= "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 (params.host and UrlDecode(params.host)) or nil
|
|
|
+ result.httpupgrade_path = params.path and UrlDecode(params.path) or "/"
|
|
|
+ elseif result.transport == "xhttp" or result.transport == "splithttp" then
|
|
|
+ result.xhttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
|
|
|
+ result.xhttp_mode = params.mode or "auto"
|
|
|
+ result.xhttp_path = params.path and UrlDecode(params.path) or "/"
|
|
|
+ -- 检查 extra 参数是否存在且非空
|
|
|
+ result.enable_xhttp_extra = (params.extra and params.extra ~= "") and "1" or nil
|
|
|
+ result.xhttp_extra = (params.extra and params.extra ~= "") and params.extra or nil
|
|
|
+ -- 尝试解析 JSON 数据
|
|
|
+ local success, Data = pcall(jsonParse, params.extra or "")
|
|
|
+ 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 or nil
|
|
|
+ else
|
|
|
+ -- 如果解析失败,清空下载地址
|
|
|
+ result.download_address = nil
|
|
|
+ end
|
|
|
+ -- make it compatible with bullshit, "h2" transport is non-existent at all
|
|
|
+ elseif result.transport == "http" or result.transport == "h2" then
|
|
|
+ result.transport = "h2"
|
|
|
+ result.h2_host = params.host and UrlDecode(params.host) or nil
|
|
|
+ result.h2_path = params.path and UrlDecode(params.path) or nil
|
|
|
+ elseif result.transport == "kcp" then
|
|
|
+ result.kcp_guise = params.headerType or "none"
|
|
|
+ result.seed = params.seed
|
|
|
+ result.mtu = 1350
|
|
|
+ result.tti = 50
|
|
|
+ result.uplink_capacity = 5
|
|
|
+ result.downlink_capacity = 20
|
|
|
+ result.read_buffer_size = 2
|
|
|
+ result.write_buffer_size = 2
|
|
|
+ elseif result.transport == "quic" then
|
|
|
+ result.quic_guise = params.headerType or "none"
|
|
|
+ result.quic_security = params.quicSecurity or "none"
|
|
|
+ result.quic_key = params.key
|
|
|
+ elseif result.transport == "grpc" then
|
|
|
+ result.serviceName = params.serviceName
|
|
|
+ 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
|
|
|
+ result.tcp_host = params.host and UrlDecode(params.host) or nil
|
|
|
+ result.tcp_path = params.path and UrlDecode(params.path) or nil
|
|
|
+ end
|
|
|
+ end
|
|
|
end
|
|
|
elseif szType == "sip008" then
|
|
|
result.type = v2_ss
|
|
@@ -661,18 +726,16 @@ local function processData(szType, content)
|
|
|
-- 分离服务器地址和端口
|
|
|
local hostParts = split(hostPort, ":")
|
|
|
|
|
|
- local server = hostParts[1]
|
|
|
- local port = hostParts[2]
|
|
|
-
|
|
|
-- 验证服务器地址和端口
|
|
|
if #hostParts < 2 then
|
|
|
--log("Trojan节点格式错误: 缺少端口号")
|
|
|
return nil
|
|
|
end
|
|
|
|
|
|
+ local server = hostParts[1]
|
|
|
+ local port = hostParts[2]
|
|
|
+
|
|
|
result.alias = UrlDecode(alias)
|
|
|
- result.type = v2_tj
|
|
|
- result.v2ray_protocol = "trojan"
|
|
|
result.server = server
|
|
|
result.password = userinfo
|
|
|
|
|
@@ -687,8 +750,8 @@ local function processData(szType, content)
|
|
|
result.server_port = queryParts[1]
|
|
|
|
|
|
-- 解析查询参数
|
|
|
- for _, v in pairs(split(queryParts[2], '&')) do
|
|
|
- local t = split(v, '=')
|
|
|
+ for _, v in pairs(split(queryParts[2], "&")) do
|
|
|
+ local t = split(v, "=")
|
|
|
if #t >= 2 then
|
|
|
params[t[1]] = t[2]
|
|
|
end
|
|
@@ -709,82 +772,93 @@ local function processData(szType, content)
|
|
|
-- 处理 insecure 参数
|
|
|
result.insecure = params.allowInsecure
|
|
|
end
|
|
|
+ if params.tfo then
|
|
|
+ -- 处理 insecure 参数
|
|
|
+ result.fast_open = params.tfo
|
|
|
+ end
|
|
|
else
|
|
|
result.server_port = port
|
|
|
end
|
|
|
|
|
|
-- 如果 Tojan 程序未安装则跳过订阅
|
|
|
- if not v2_tj then
|
|
|
+ if not v2_tj or v2_tj == "" then
|
|
|
return nil
|
|
|
end
|
|
|
|
|
|
- if v2_tj ~= "trojan" then
|
|
|
- if params.fp then
|
|
|
- -- 处理 fingerprint 参数
|
|
|
- result.fingerprint = params.fp
|
|
|
- end
|
|
|
- -- 处理 ech 参数
|
|
|
- if params.ech then
|
|
|
- result.enable_ech = "1"
|
|
|
- result.ech_config = params.ech
|
|
|
- end
|
|
|
- -- 处理传输协议
|
|
|
- result.transport = params.type or "raw" -- 默认传输协议为 raw
|
|
|
- if result.transport == "tcp" then
|
|
|
- result.transport = "raw"
|
|
|
- end
|
|
|
- if result.transport == "splithttp" then
|
|
|
- result.transport = "xhttp"
|
|
|
- end
|
|
|
- if result.transport == "ws" then
|
|
|
- result.ws_host = (result.tls ~= "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 (params.host and UrlDecode(params.host)) or nil
|
|
|
- result.httpupgrade_path = params.path and UrlDecode(params.path) or "/"
|
|
|
- elseif result.transport == "xhttp" or result.transport == "splithttp" then
|
|
|
- result.xhttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
|
|
|
- result.xhttp_mode = params.mode or "auto"
|
|
|
- result.xhttp_path = params.path and UrlDecode(params.path) or "/"
|
|
|
- -- 检查 extra 参数是否存在且非空
|
|
|
- result.enable_xhttp_extra = (params.extra and params.extra ~= "") and "1" or nil
|
|
|
- result.xhttp_extra = (params.extra and params.extra ~= "") and params.extra or nil
|
|
|
- -- 尝试解析 JSON 数据
|
|
|
- local success, Data = pcall(jsonParse, params.extra or "")
|
|
|
- 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 or nil
|
|
|
- else
|
|
|
- -- 如果解析失败,清空下载地址
|
|
|
- result.download_address = nil
|
|
|
+ if params.type and params.type ~= "" then
|
|
|
+ v2_tj = "v2ray"
|
|
|
+ result.type = v2_tj
|
|
|
+ result.v2ray_protocol = "trojan"
|
|
|
+ if v2_tj ~= "trojan" then
|
|
|
+ if params.fp then
|
|
|
+ -- 处理 fingerprint 参数
|
|
|
+ result.fingerprint = params.fp
|
|
|
end
|
|
|
- elseif result.transport == "http" or result.transport == "h2" then
|
|
|
- result.transport = "h2"
|
|
|
- result.h2_host = params.host and UrlDecode(params.host) or nil
|
|
|
- result.h2_path = params.path and UrlDecode(params.path) or nil
|
|
|
- elseif result.transport == "kcp" then
|
|
|
- result.kcp_guise = params.headerType or "none"
|
|
|
- result.seed = params.seed
|
|
|
- result.mtu = 1350
|
|
|
- result.tti = 50
|
|
|
- result.uplink_capacity = 5
|
|
|
- result.downlink_capacity = 20
|
|
|
- result.read_buffer_size = 2
|
|
|
- result.write_buffer_size = 2
|
|
|
- elseif result.transport == "quic" then
|
|
|
- result.quic_guise = params.headerType or "none"
|
|
|
- result.quic_security = params.quicSecurity or "none"
|
|
|
- result.quic_key = params.key
|
|
|
- elseif result.transport == "grpc" then
|
|
|
- result.serviceName = params.serviceName
|
|
|
- result.grpc_mode = params.mode or "gun"
|
|
|
- elseif result.transport == "tcp" or result.transport == "raw" then
|
|
|
- result.tcp_guise = params.headerType and params.headerType ~= "" and params.headerType or "none"
|
|
|
- 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
|
|
|
+ -- 处理 ech 参数
|
|
|
+ if params.ech then
|
|
|
+ result.enable_ech = "1"
|
|
|
+ result.ech_config = params.ech
|
|
|
end
|
|
|
+ -- 处理传输协议
|
|
|
+ result.transport = params.type or "raw" -- 默认传输协议为 raw
|
|
|
+ if result.transport == "tcp" then
|
|
|
+ result.transport = "raw"
|
|
|
+ end
|
|
|
+ if result.transport == "splithttp" then
|
|
|
+ result.transport = "xhttp"
|
|
|
+ end
|
|
|
+ if result.transport == "ws" then
|
|
|
+ result.ws_host = (result.tls ~= "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 (params.host and UrlDecode(params.host)) or nil
|
|
|
+ result.httpupgrade_path = params.path and UrlDecode(params.path) or "/"
|
|
|
+ elseif result.transport == "xhttp" or result.transport == "splithttp" then
|
|
|
+ result.xhttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
|
|
|
+ result.xhttp_mode = params.mode or "auto"
|
|
|
+ result.xhttp_path = params.path and UrlDecode(params.path) or "/"
|
|
|
+ -- 检查 extra 参数是否存在且非空
|
|
|
+ result.enable_xhttp_extra = (params.extra and params.extra ~= "") and "1" or nil
|
|
|
+ result.xhttp_extra = (params.extra and params.extra ~= "") and params.extra or nil
|
|
|
+ -- 尝试解析 JSON 数据
|
|
|
+ local success, Data = pcall(jsonParse, params.extra or "")
|
|
|
+ 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 or nil
|
|
|
+ else
|
|
|
+ -- 如果解析失败,清空下载地址
|
|
|
+ result.download_address = nil
|
|
|
+ end
|
|
|
+ elseif result.transport == "http" or result.transport == "h2" then
|
|
|
+ result.transport = "h2"
|
|
|
+ result.h2_host = params.host and UrlDecode(params.host) or nil
|
|
|
+ result.h2_path = params.path and UrlDecode(params.path) or nil
|
|
|
+ elseif result.transport == "kcp" then
|
|
|
+ result.kcp_guise = params.headerType or "none"
|
|
|
+ result.seed = params.seed
|
|
|
+ result.mtu = 1350
|
|
|
+ result.tti = 50
|
|
|
+ result.uplink_capacity = 5
|
|
|
+ result.downlink_capacity = 20
|
|
|
+ result.read_buffer_size = 2
|
|
|
+ result.write_buffer_size = 2
|
|
|
+ elseif result.transport == "quic" then
|
|
|
+ result.quic_guise = params.headerType or "none"
|
|
|
+ result.quic_security = params.quicSecurity or "none"
|
|
|
+ result.quic_key = params.key
|
|
|
+ elseif result.transport == "grpc" then
|
|
|
+ result.serviceName = params.serviceName
|
|
|
+ result.grpc_mode = params.mode or "gun"
|
|
|
+ elseif result.transport == "tcp" or result.transport == "raw" then
|
|
|
+ result.tcp_guise = params.headerType and params.headerType ~= "" and params.headerType or "none"
|
|
|
+ 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
|
|
|
+ end
|
|
|
+ else
|
|
|
+ result.type = v2_tj
|
|
|
end
|
|
|
end
|
|
|
elseif szType == "vless" then
|
|
@@ -808,114 +882,7 @@ local function processData(szType, content)
|
|
|
result.tls = (params.security == "tls" or params.security == "xtls") and "1" or "0"
|
|
|
if params.alpn and params.alpn ~= "" then
|
|
|
local alpn = {}
|
|
|
- for v in params.alpn:gmatch("[^,]+") do
|
|
|
- table.insert(alpn, v)
|
|
|
- end
|
|
|
- result.tls_alpn = alpn
|
|
|
- end
|
|
|
- result.tls_host = params.sni
|
|
|
- result.tls_flow = (params.security == "tls" or params.security == "reality") and params.flow or nil
|
|
|
- result.fingerprint = params.fp
|
|
|
- result.reality = (params.security == "reality") and "1" or "0"
|
|
|
- result.reality_publickey = params.pbk and UrlDecode(params.pbk) or nil
|
|
|
- result.reality_shortid = params.sid
|
|
|
- result.reality_spiderx = params.spx and UrlDecode(params.spx) or nil
|
|
|
- -- 检查 ech 参数是否存在且非空
|
|
|
- result.enable_ech = (params.ech and params.ech ~= "") and "1" or nil
|
|
|
- result.ech_config = (params.ech and params.ech ~= "") and params.ech or nil
|
|
|
- -- 检查 pqv 参数是否存在且非空
|
|
|
- result.enable_mldsa65verify = (params.pqv and params.pqv ~= "") and "1" or nil
|
|
|
- result.reality_mldsa65verify = (params.pqv and params.pqv ~= "") and params.pqv or nil
|
|
|
- if result.transport == "ws" then
|
|
|
- result.ws_host = (result.tls ~= "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 (params.host and UrlDecode(params.host)) or nil
|
|
|
- result.httpupgrade_path = params.path and UrlDecode(params.path) or "/"
|
|
|
- elseif result.transport == "xhttp" or result.transport == "splithttp" then
|
|
|
- result.xhttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
|
|
|
- result.xhttp_mode = params.mode or "auto"
|
|
|
- result.xhttp_path = params.path and UrlDecode(params.path) or "/"
|
|
|
- -- 检查 extra 参数是否存在且非空
|
|
|
- result.enable_xhttp_extra = (params.extra and params.extra ~= "") and "1" or nil
|
|
|
- result.xhttp_extra = (params.extra and params.extra ~= "") and params.extra or nil
|
|
|
- -- 尝试解析 JSON 数据
|
|
|
- local success, Data = pcall(jsonParse, params.extra or "")
|
|
|
- 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 or nil
|
|
|
- else
|
|
|
- -- 如果解析失败,清空下载地址
|
|
|
- result.download_address = nil
|
|
|
- end
|
|
|
- -- make it compatible with bullshit, "h2" transport is non-existent at all
|
|
|
- elseif result.transport == "http" or result.transport == "h2" then
|
|
|
- result.transport = "h2"
|
|
|
- result.h2_host = params.host and UrlDecode(params.host) or nil
|
|
|
- result.h2_path = params.path and UrlDecode(params.path) or nil
|
|
|
- elseif result.transport == "kcp" then
|
|
|
- result.kcp_guise = params.headerType or "none"
|
|
|
- result.seed = params.seed
|
|
|
- result.mtu = 1350
|
|
|
- result.tti = 50
|
|
|
- result.uplink_capacity = 5
|
|
|
- result.downlink_capacity = 20
|
|
|
- result.read_buffer_size = 2
|
|
|
- result.write_buffer_size = 2
|
|
|
- elseif result.transport == "quic" then
|
|
|
- result.quic_guise = params.headerType or "none"
|
|
|
- result.quic_security = params.quicSecurity or "none"
|
|
|
- result.quic_key = params.key
|
|
|
- elseif result.transport == "grpc" then
|
|
|
- result.serviceName = params.serviceName
|
|
|
- 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
|
|
|
- result.tcp_host = params.host and UrlDecode(params.host) or nil
|
|
|
- result.tcp_path = params.path and UrlDecode(params.path) or nil
|
|
|
- end
|
|
|
- end
|
|
|
- elseif szType == "shadowsocks" then
|
|
|
- local content = trim(content:gsub("&[a-zA-Z]+;", "&"):gsub("%s*#%s*", "#"))
|
|
|
- local idx_sp = content:find("#") or 0
|
|
|
- local alias = ""
|
|
|
- if idx_sp > 0 then
|
|
|
- alias = UrlDecode(content:sub(idx_sp + 1))
|
|
|
- end
|
|
|
- local info = content:sub(1, idx_sp > 0 and idx_sp - 1 or #content)
|
|
|
- local url = URL.parse("http://" .. info)
|
|
|
- local params = url.query
|
|
|
-
|
|
|
- result.alias = alias
|
|
|
- result.type = "v2ray"
|
|
|
- result.v2ray_protocol = "shadowsocks"
|
|
|
- result.server = url.host
|
|
|
- result.server_port = url.port
|
|
|
-
|
|
|
- -- 判断 @ 前部分是否为 Base64
|
|
|
- local is_base64_decoded = base64Decode(UrlDecode(url.user))
|
|
|
- if is_base64_decoded:find(":") then
|
|
|
- -- 新格式:method:password
|
|
|
- result.encrypt_method_ss, result.password = is_base64_decoded:match("^(.-):(.*)$")
|
|
|
- else
|
|
|
- -- 旧格式:UUID 直接作为密码
|
|
|
- result.password = url.user
|
|
|
- result.encrypt_method_ss = params.encryption or "none"
|
|
|
- end
|
|
|
-
|
|
|
- result.transport = params.type or "raw"
|
|
|
- if result.transport == "tcp" then
|
|
|
- result.transport = "raw"
|
|
|
- end
|
|
|
- if result.transport == "splithttp" then
|
|
|
- result.transport = "xhttp"
|
|
|
- end
|
|
|
- result.tls = (params.security == "tls" or params.security == "xtls") and "1" or "0"
|
|
|
- if params.alpn and params.alpn ~= "" then
|
|
|
- local alpn = {}
|
|
|
- for v in params.alpn:gmatch("[^,]+") do
|
|
|
+ for v in params.alpn:gmatch("[^,;|%s]+") do
|
|
|
table.insert(alpn, v)
|
|
|
end
|
|
|
result.tls_alpn = alpn
|
|
@@ -1181,47 +1148,53 @@ local execute = function()
|
|
|
-- 其他 base64 格式
|
|
|
else
|
|
|
-- ssd 外的格式
|
|
|
- nodes = split(base64Decode(raw):gsub(" ", "_"), "\n")
|
|
|
+ nodes = split(base64Decode(raw):gsub("\r\n", "\n"), "\n")
|
|
|
end
|
|
|
for _, v in ipairs(nodes) do
|
|
|
- if v then
|
|
|
- local result
|
|
|
- if szType then
|
|
|
- result = processData(szType, v)
|
|
|
- elseif not szType then
|
|
|
- local node = trim(v)
|
|
|
- local dat = split(node, "://")
|
|
|
- if dat and dat[1] and dat[2] then
|
|
|
- local dat3 = ""
|
|
|
- if dat[3] then
|
|
|
- dat3 = "://" .. dat[3]
|
|
|
+ if v and not string.match(v, "^%s*$") then
|
|
|
+ xpcall(function()
|
|
|
+ local result
|
|
|
+ if szType then
|
|
|
+ result = processData(szType, v)
|
|
|
+ elseif not szType then
|
|
|
+ local node = trim(v)
|
|
|
+ -- 一些奇葩的链接用"&"、"<"当做"&","#"前后带空格
|
|
|
+ local link = node:gsub("&[a-zA-Z]+;", "&"):gsub("%s*#%s*", "#")
|
|
|
+ local dat = split(link, "://")
|
|
|
+ if dat and dat[1] and dat[2] then
|
|
|
+ local dat3 = ""
|
|
|
+ if dat[3] then
|
|
|
+ dat3 = "://" .. dat[3]
|
|
|
+ end
|
|
|
+ if dat[1] == 'ss' or dat[1] == 'trojan' then
|
|
|
+ result = processData(dat[1], dat[2] .. dat3)
|
|
|
+ else
|
|
|
+ result = processData(dat[1], base64Decode(dat[2]))
|
|
|
+ end
|
|
|
end
|
|
|
- if dat[1] == 'ss' or dat[1] == 'trojan' then
|
|
|
- result = processData(dat[1], dat[2] .. dat3)
|
|
|
+ else
|
|
|
+ log('跳过未知类型: ' .. szType)
|
|
|
+ end
|
|
|
+ -- log(result)
|
|
|
+ if result then
|
|
|
+ -- 中文做地址的 也没有人拿中文域名搞,就算中文域也有Puny Code SB 机场
|
|
|
+ if not result.server or not result.server_port
|
|
|
+ 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
|
|
|
- result = processData(dat[1], base64Decode(dat[2]))
|
|
|
+ -- log('成功解析: ' .. result.type ..' 节点, ' .. result.alias)
|
|
|
+ result.grouphashkey = groupHash
|
|
|
+ tinsert(nodeResult[index], result)
|
|
|
+ cache[groupHash][result.hashkey] = nodeResult[index][#nodeResult[index]]
|
|
|
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.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)
|
|
@@ -1304,7 +1277,7 @@ local execute = function()
|
|
|
luci.sys.call("/etc/init.d/" .. name .. " stop > /dev/null 2>&1 &")
|
|
|
end
|
|
|
end
|
|
|
- log('新增节点数量: ' .. add, '删除节点数量: ' .. del)
|
|
|
+ log('新增节点数量: ' .. add .. ', 删除节点数量: ' .. del)
|
|
|
log('订阅更新成功')
|
|
|
end
|
|
|
end
|