|
|
@@ -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
|
|
|
- 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 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
|
|
|
+ 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
|
|
|
+ -- 检查是否打开了保留关键词检查,并且进行过滤
|
|
|
+ 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
|
|
|
- else
|
|
|
- save_result = false
|
|
|
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)
|
|
|
-
|
|
|
- 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
|
|
|
+ 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))
|
|
|
+
|
|
|
+ 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")
|
|
|
+ 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
|
|
|
- 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)
|
|
|
- -- 一些奇葩的链接用"&"、"<"当做"&","#"前后带空格
|
|
|
- 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
|
|
|
+ -- 其他 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)
|
|
|
+ -- 一些奇葩的链接用"&"、"<"当做"&","#"前后带空格
|
|
|
+ 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
|
|
|
- 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)
|
|
|
+ if dat[1] == 'ss' or dat[1] == 'trojan' or dat[1] == 'tuic' then
|
|
|
+ result = processData(dat[1], dat[2] .. dat3)
|
|
|
else
|
|
|
- -- log('成功解析: ' .. result.type ..' 节点, ' .. result.alias)
|
|
|
- result.grouphashkey = groupHash
|
|
|
- tinsert(nodeResult[index], result)
|
|
|
- cache[groupHash][result.hashkey] = nodeResult[index][#nodeResult[index]]
|
|
|
+ result = processData(dat[1], base64Decode(dat[2]))
|
|
|
end
|
|
|
end
|
|
|
- end, function(err)
|
|
|
- log(string.format("解析节点出错: %s\n原始数据: %s", tostring(err), tostring(v)))
|
|
|
- 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
|
|
|
- log('成功解析节点数量: ' .. #nodes)
|
|
|
end
|
|
|
- else
|
|
|
- log(url .. ': 获取内容为空')
|
|
|
+
|
|
|
+ -- 对该组节点进行别名编号:重复节点加后缀,唯一节点不加
|
|
|
+ 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
|
|
|
+
|
|
|
+ -- diff 阶段
|
|
|
+ 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
|
|
|
- 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
|
|
|
+ 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)
|
|
|
- -- 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)
|
|
|
+ -- 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
|
|
|
- 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('没有服务器节点了,停止服务')
|
|
|
+ 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
|
|
|
- log('新增节点数量: ' .. add .. ', 删除节点数量: ' .. del)
|
|
|
- log('订阅更新成功')
|
|
|
end
|
|
|
+ log('新增节点数量: ' .. add .. ', 删除节点数量: ' .. del)
|
|
|
+ log('订阅更新成功')
|
|
|
end
|
|
|
|
|
|
if subscribe_url and #subscribe_url > 0 then
|