2021-09-05 16:50:22 +08:00
|
|
|
|
#!/usr/bin/lua
|
2022-04-21 12:14:00 +08:00
|
|
|
|
|
2021-09-05 16:50:22 +08:00
|
|
|
|
------------------------------------------------
|
|
|
|
|
-- This file is part of the luci-app-ssr-plus subscribe.lua
|
|
|
|
|
-- @author William Chan <root@williamchan.me>
|
|
|
|
|
------------------------------------------------
|
2022-05-22 23:42:54 +08:00
|
|
|
|
|
|
|
|
|
|
2021-09-05 16:50:22 +08:00
|
|
|
|
require 'nixio'
|
|
|
|
|
require 'luci.util'
|
|
|
|
|
require 'luci.jsonc'
|
|
|
|
|
require 'luci.sys'
|
|
|
|
|
|
|
|
|
|
-- these global functions are accessed all the time by the event handler
|
|
|
|
|
-- so caching them is worth the effort
|
|
|
|
|
local luci = luci
|
|
|
|
|
local tinsert = table.insert
|
2022-05-22 23:42:54 +08:00
|
|
|
|
local ssub, slen, schar, sbyte, sformat, sgsub = string.sub, string.len, string.char, string.byte, string.format, string.gsub
|
2021-09-05 16:50:22 +08:00
|
|
|
|
local jsonParse, jsonStringify = luci.jsonc.parse, luci.jsonc.stringify
|
|
|
|
|
local b64decode = nixio.bin.b64decode
|
|
|
|
|
local cache = {}
|
|
|
|
|
local nodeResult = setmetatable({}, {__index = cache}) -- update result
|
|
|
|
|
local name = 'vssr'
|
|
|
|
|
local uciType = 'servers'
|
|
|
|
|
local ucic = luci.model.uci.cursor()
|
|
|
|
|
local proxy = ucic:get_first(name, 'server_subscribe', 'proxy', '0')
|
|
|
|
|
local switch = '0'
|
2022-05-22 23:42:54 +08:00
|
|
|
|
local subscribe_url = ucic:get_first(name, 'server_subscribe', 'subscribe_url', {})
|
|
|
|
|
local filter_words = ucic:get_first(name, 'server_subscribe', 'filter_words', '过期时间/剩余流量')
|
2021-09-05 16:50:22 +08:00
|
|
|
|
|
2022-04-21 12:14:00 +08:00
|
|
|
|
function print_r(t)
|
|
|
|
|
local print_r_cache = {}
|
|
|
|
|
local function sub_print_r(t, indent)
|
2021-09-05 16:50:22 +08:00
|
|
|
|
if (print_r_cache[tostring(t)]) then
|
2022-05-22 23:42:54 +08:00
|
|
|
|
print(indent .. '*' .. tostring(t))
|
2021-09-05 16:50:22 +08:00
|
|
|
|
else
|
2022-04-21 12:14:00 +08:00
|
|
|
|
print_r_cache[tostring(t)] = true
|
2022-05-22 23:42:54 +08:00
|
|
|
|
if (type(t) == 'table') then
|
2022-04-21 12:14:00 +08:00
|
|
|
|
for pos, val in pairs(t) do
|
2022-05-22 23:42:54 +08:00
|
|
|
|
if (type(val) == 'table') then
|
|
|
|
|
print(indent .. '[' .. pos .. '] => ' .. tostring(t) .. ' {')
|
|
|
|
|
sub_print_r(val, indent .. string.rep(' ', string.len(pos) + 8))
|
|
|
|
|
print(indent .. string.rep(' ', string.len(pos) + 6) .. '}')
|
|
|
|
|
elseif (type(val) == 'string') then
|
|
|
|
|
print(indent .. '[' .. pos .. '] => "' .. val .. '"')
|
2021-09-05 16:50:22 +08:00
|
|
|
|
else
|
2022-05-22 23:42:54 +08:00
|
|
|
|
print(indent .. '[' .. pos .. '] => ' .. tostring(val))
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
else
|
2022-04-21 12:14:00 +08:00
|
|
|
|
print(indent .. tostring(t))
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2022-05-22 23:42:54 +08:00
|
|
|
|
|
|
|
|
|
if (type(t) == 'table') then
|
|
|
|
|
print(tostring(t) .. ' {')
|
|
|
|
|
sub_print_r(t, ' ')
|
|
|
|
|
print('}')
|
2021-09-05 16:50:22 +08:00
|
|
|
|
else
|
2022-05-22 23:42:54 +08:00
|
|
|
|
sub_print_r(t, ' ')
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
|
|
|
|
print()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local log = function(...)
|
|
|
|
|
print(os.date('%Y-%m-%d %H:%M:%S ') .. table.concat({...}, ' '))
|
|
|
|
|
end
|
|
|
|
|
-- 分割字符串
|
|
|
|
|
local function split(full, sep)
|
|
|
|
|
full = full:gsub('%z', '') -- 这里不是很清楚 有时候结尾带个\0
|
|
|
|
|
local off, result = 1, {}
|
|
|
|
|
while true do
|
|
|
|
|
local nStart, nEnd = full:find(sep, off)
|
|
|
|
|
if not nEnd then
|
|
|
|
|
local res = ssub(full, off, slen(full))
|
|
|
|
|
if #res > 0 then -- 过滤掉 \0
|
|
|
|
|
tinsert(result, res)
|
|
|
|
|
end
|
|
|
|
|
break
|
|
|
|
|
else
|
|
|
|
|
tinsert(result, ssub(full, off, nStart - 1))
|
|
|
|
|
off = nEnd + 1
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return result
|
|
|
|
|
end
|
|
|
|
|
|
2022-04-21 12:14:00 +08:00
|
|
|
|
-- table去重
|
2021-09-05 16:50:22 +08:00
|
|
|
|
|
2022-04-21 12:14:00 +08:00
|
|
|
|
local function clone(object)
|
2021-09-05 16:50:22 +08:00
|
|
|
|
local lookup_table = {}
|
2022-04-21 12:14:00 +08:00
|
|
|
|
local function copyObj(object)
|
2022-05-22 23:42:54 +08:00
|
|
|
|
if type(object) ~= 'table' then
|
2021-09-05 16:50:22 +08:00
|
|
|
|
return object
|
|
|
|
|
elseif lookup_table[object] then
|
|
|
|
|
return lookup_table[object]
|
|
|
|
|
end
|
2022-04-21 12:14:00 +08:00
|
|
|
|
|
2021-09-05 16:50:22 +08:00
|
|
|
|
local new_table = {}
|
|
|
|
|
lookup_table[object] = new_table
|
2022-04-21 12:14:00 +08:00
|
|
|
|
for key, value in pairs(object) do
|
|
|
|
|
new_table[copyObj(key)] = copyObj(value)
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
2022-04-21 12:14:00 +08:00
|
|
|
|
return setmetatable(new_table, getmetatable(object))
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
2022-05-22 23:42:54 +08:00
|
|
|
|
|
2022-04-21 12:14:00 +08:00
|
|
|
|
return copyObj(object)
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function table_unique(list)
|
|
|
|
|
local temp1 = clone(list)
|
|
|
|
|
local temp2 = clone(list)
|
|
|
|
|
for k1, v1 in ipairs(temp1) do
|
|
|
|
|
for k2, v2 in ipairs(temp2) do
|
|
|
|
|
if v1.alias ~= v2.alias and v1.hashkey == v2.hashkey then
|
|
|
|
|
table.remove(temp1, k1)
|
|
|
|
|
table.remove(temp2, k1)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return temp1
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- urlencode
|
2022-05-22 23:42:54 +08:00
|
|
|
|
local function get_urlencode(c)
|
|
|
|
|
return sformat('%%%02X', sbyte(c))
|
|
|
|
|
end
|
2021-09-05 16:50:22 +08:00
|
|
|
|
|
|
|
|
|
local function urlEncode(szText)
|
|
|
|
|
local str = szText:gsub('([^0-9a-zA-Z ])', get_urlencode)
|
|
|
|
|
str = str:gsub(' ', '+')
|
|
|
|
|
return str
|
|
|
|
|
end
|
|
|
|
|
|
2022-05-22 23:42:54 +08:00
|
|
|
|
local function get_urldecode(h)
|
|
|
|
|
return schar(tonumber(h, 16))
|
|
|
|
|
end
|
|
|
|
|
|
2021-09-05 16:50:22 +08:00
|
|
|
|
local function UrlDecode(szText)
|
|
|
|
|
return szText:gsub('+', ' '):gsub('%%(%x%x)', get_urldecode)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- trim
|
|
|
|
|
local function trim(text)
|
2022-05-22 23:42:54 +08:00
|
|
|
|
if not text or text == '' then
|
|
|
|
|
return ''
|
|
|
|
|
end
|
2021-09-05 16:50:22 +08:00
|
|
|
|
return (sgsub(text, '^%s*(.-)%s*$', '%1'))
|
|
|
|
|
end
|
2022-05-22 23:42:54 +08:00
|
|
|
|
|
2021-09-05 16:50:22 +08:00
|
|
|
|
-- md5
|
|
|
|
|
local function md5(content)
|
2022-05-22 23:42:54 +08:00
|
|
|
|
local stdout = luci.sys.exec('echo "' .. urlEncode(content) .. '" | md5sum | cut -d " " -f1')
|
2021-09-05 16:50:22 +08:00
|
|
|
|
-- assert(nixio.errno() == 0)
|
|
|
|
|
return trim(stdout)
|
|
|
|
|
end
|
2022-05-22 23:42:54 +08:00
|
|
|
|
|
2021-09-05 16:50:22 +08:00
|
|
|
|
-- base64
|
|
|
|
|
local function base64Decode(text)
|
|
|
|
|
local raw = text
|
2022-05-22 23:42:54 +08:00
|
|
|
|
if not text then
|
|
|
|
|
return ''
|
|
|
|
|
end
|
2021-09-05 16:50:22 +08:00
|
|
|
|
text = text:gsub('%z', '')
|
|
|
|
|
text = text:gsub('_', '/')
|
|
|
|
|
text = text:gsub('-', '+')
|
|
|
|
|
local mod4 = #text % 4
|
|
|
|
|
text = text .. string.sub('====', mod4 + 1)
|
|
|
|
|
local result = b64decode(text)
|
|
|
|
|
if result then
|
|
|
|
|
return result:gsub('%z', '')
|
|
|
|
|
else
|
|
|
|
|
return raw
|
|
|
|
|
end
|
|
|
|
|
end
|
2022-05-22 23:42:54 +08:00
|
|
|
|
|
2021-09-05 16:50:22 +08:00
|
|
|
|
-- 处理数据
|
|
|
|
|
local function processData(szType, content, groupName)
|
|
|
|
|
local result = {
|
|
|
|
|
-- auth_enable = '0',
|
|
|
|
|
-- switch_enable = '1',
|
|
|
|
|
type = szType,
|
|
|
|
|
local_port = 1234,
|
|
|
|
|
-- timeout = 60, -- 不太确定 好像是死的
|
|
|
|
|
-- fast_open = 0,
|
|
|
|
|
-- kcp_enable = 0,
|
|
|
|
|
-- kcp_port = 0,
|
|
|
|
|
kcp_param = '--nocomp'
|
|
|
|
|
}
|
|
|
|
|
if szType == 'ssr' then
|
|
|
|
|
local dat = split(content, '/%?')
|
|
|
|
|
local hostInfo = split(dat[1], ':')
|
|
|
|
|
result.server = hostInfo[1]
|
|
|
|
|
result.server_port = hostInfo[2]
|
|
|
|
|
result.protocol = hostInfo[3]
|
|
|
|
|
result.encrypt_method = hostInfo[4]
|
|
|
|
|
result.obfs = hostInfo[5]
|
|
|
|
|
result.password = base64Decode(hostInfo[6])
|
|
|
|
|
local params = {}
|
|
|
|
|
for _, v in pairs(split(dat[2], '&')) do
|
|
|
|
|
local t = split(v, '=')
|
|
|
|
|
params[t[1]] = t[2]
|
|
|
|
|
end
|
|
|
|
|
result.obfs_param = base64Decode(params.obfsparam)
|
|
|
|
|
result.protocol_param = base64Decode(params.protoparam)
|
|
|
|
|
local group = base64Decode(params.group)
|
2022-05-22 23:42:54 +08:00
|
|
|
|
if group then
|
|
|
|
|
result.alias = '[' .. group .. '] '
|
|
|
|
|
end
|
2021-09-05 16:50:22 +08:00
|
|
|
|
result.alias = result.alias .. base64Decode(params.remarks)
|
|
|
|
|
elseif szType == 'vmess' then
|
|
|
|
|
local info = jsonParse(content)
|
|
|
|
|
result.type = 'v2ray'
|
|
|
|
|
result.server = info.add
|
|
|
|
|
result.server_port = info.port
|
|
|
|
|
result.transport = info.net
|
|
|
|
|
result.alter_id = info.aid
|
|
|
|
|
result.vmess_id = info.id
|
|
|
|
|
result.alias = groupName .. info.ps
|
|
|
|
|
-- result.mux = 1
|
|
|
|
|
-- result.concurrency = 8
|
|
|
|
|
if info.net == 'ws' then
|
|
|
|
|
result.ws_host = info.host
|
|
|
|
|
result.ws_path = info.path
|
|
|
|
|
end
|
|
|
|
|
if info.net == 'h2' then
|
|
|
|
|
result.h2_host = info.host
|
|
|
|
|
result.h2_path = info.path
|
|
|
|
|
end
|
|
|
|
|
if info.net == 'tcp' then
|
|
|
|
|
result.tcp_guise = info.type
|
|
|
|
|
result.http_host = info.host
|
|
|
|
|
result.http_path = info.path
|
|
|
|
|
end
|
|
|
|
|
if info.net == 'kcp' then
|
|
|
|
|
result.kcp_guise = info.type
|
|
|
|
|
result.mtu = 1350
|
|
|
|
|
result.tti = 50
|
|
|
|
|
result.uplink_capacity = 5
|
|
|
|
|
result.downlink_capacity = 20
|
|
|
|
|
result.read_buffer_size = 2
|
|
|
|
|
result.write_buffer_size = 2
|
|
|
|
|
end
|
|
|
|
|
if info.net == 'quic' then
|
|
|
|
|
result.quic_guise = info.type
|
|
|
|
|
result.quic_key = info.key
|
|
|
|
|
result.quic_security = info.securty
|
|
|
|
|
end
|
2022-05-22 23:42:54 +08:00
|
|
|
|
if info.security then
|
|
|
|
|
result.security = info.security
|
|
|
|
|
end
|
2021-09-05 16:50:22 +08:00
|
|
|
|
if info.tls == 'tls' or info.tls == '1' then
|
|
|
|
|
result.tls = '1'
|
|
|
|
|
result.tls_host = info.host
|
|
|
|
|
else
|
|
|
|
|
result.tls = '0'
|
|
|
|
|
end
|
|
|
|
|
elseif szType == 'ss' then
|
|
|
|
|
local idx_sp = 0
|
|
|
|
|
local alias = ''
|
|
|
|
|
if content:find('#') then
|
|
|
|
|
idx_sp = content:find('#')
|
|
|
|
|
alias = content:sub(idx_sp + 1, -1)
|
|
|
|
|
end
|
|
|
|
|
local info = content:sub(1, idx_sp - 1)
|
|
|
|
|
local hostInfo = split(base64Decode(info), '@')
|
|
|
|
|
local host = split(hostInfo[2], ':')
|
|
|
|
|
local userinfo = base64Decode(hostInfo[1])
|
|
|
|
|
local method = userinfo:sub(1, userinfo:find(':') - 1)
|
|
|
|
|
local password = userinfo:sub(userinfo:find(':') + 1, #userinfo)
|
|
|
|
|
result.alias = groupName .. UrlDecode(alias)
|
|
|
|
|
result.type = 'ss'
|
|
|
|
|
result.server = host[1]
|
|
|
|
|
if host[2]:find('/%?') then
|
|
|
|
|
local query = split(host[2], '/%?')
|
|
|
|
|
result.server_port = query[1]
|
|
|
|
|
local params = {}
|
|
|
|
|
for _, v in pairs(split(query[2], '&')) do
|
|
|
|
|
local t = split(v, '=')
|
|
|
|
|
params[t[1]] = t[2]
|
|
|
|
|
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)
|
2022-05-22 23:42:54 +08:00
|
|
|
|
result.plugin_opts = plugin_info:sub(idx_pn + 1, #plugin_info)
|
2021-09-05 16:50:22 +08:00
|
|
|
|
else
|
|
|
|
|
result.plugin = plugin_info
|
|
|
|
|
end
|
2022-04-21 12:14:00 +08:00
|
|
|
|
-- 部分机场下发的插件名为 simple-obfs,这里应该改为 obfs-local
|
2022-05-22 23:42:54 +08:00
|
|
|
|
if result.plugin == 'simple-obfs' then
|
|
|
|
|
result.plugin = 'obfs-local'
|
2022-04-21 12:14:00 +08:00
|
|
|
|
end
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
result.server_port = host[2]
|
|
|
|
|
end
|
|
|
|
|
result.encrypt_method_ss = method
|
|
|
|
|
result.password = password
|
|
|
|
|
elseif szType == 'trojan' then
|
|
|
|
|
local idx_sp = 0
|
|
|
|
|
local alias = ''
|
|
|
|
|
if content:find('#') then
|
|
|
|
|
idx_sp = content:find('#')
|
|
|
|
|
alias = content:sub(idx_sp + 1, -1)
|
|
|
|
|
end
|
|
|
|
|
local info = content:sub(1, idx_sp - 1)
|
|
|
|
|
local hostInfo = split(info, '@')
|
|
|
|
|
local host = split(hostInfo[2], ':')
|
|
|
|
|
local password = hostInfo[1]
|
|
|
|
|
result.alias = groupName .. UrlDecode(alias)
|
|
|
|
|
result.type = 'trojan'
|
|
|
|
|
result.server = host[1]
|
2022-09-24 23:44:17 +08:00
|
|
|
|
if content:find('allowInsecure=1') then
|
|
|
|
|
result.insecure = '1'
|
|
|
|
|
else
|
|
|
|
|
result.insecure = '0'
|
|
|
|
|
end
|
|
|
|
|
|
2021-09-05 16:50:22 +08:00
|
|
|
|
if host[2]:find('?') then
|
|
|
|
|
local query = split(host[2], '?')
|
|
|
|
|
result.server_port = query[1]
|
|
|
|
|
local params = {}
|
|
|
|
|
for _, v in pairs(split(query[2], '&')) do
|
|
|
|
|
local t = split(v, '=')
|
|
|
|
|
if t[1] == 'peer' then
|
|
|
|
|
result.peer = t[2]
|
|
|
|
|
result.tls = '1'
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
result.server_port = host[2]
|
|
|
|
|
end
|
|
|
|
|
result.password = password
|
|
|
|
|
elseif szType == 'ssd' then
|
|
|
|
|
result.type = 'ss'
|
|
|
|
|
result.server = content.server
|
|
|
|
|
result.server_port = content.port
|
|
|
|
|
result.password = content.password
|
|
|
|
|
result.encrypt_method_ss = content.encryption
|
2022-05-22 23:42:54 +08:00
|
|
|
|
if content.plugin == 'simple-obfs' then
|
|
|
|
|
result.plugin = 'obfs-local'
|
|
|
|
|
else
|
|
|
|
|
result.plugin = content.plugin
|
|
|
|
|
end
|
2021-09-05 16:50:22 +08:00
|
|
|
|
result.plugin_opts = content.plugin_options
|
2022-05-22 23:42:54 +08:00
|
|
|
|
|
2021-09-05 16:50:22 +08:00
|
|
|
|
result.alias = '[' .. content.airport .. '] ' .. content.remarks
|
|
|
|
|
end
|
|
|
|
|
if not result.alias then
|
|
|
|
|
if result.server and result.server_port then
|
|
|
|
|
result.alias = result.server .. ':' .. result.server_port
|
|
|
|
|
else
|
2022-05-22 23:42:54 +08:00
|
|
|
|
result.alias = 'NULL'
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
-- alias 不参与 hashkey 计算
|
|
|
|
|
local alias = result.alias
|
|
|
|
|
result.alias = nil
|
|
|
|
|
local switch_enable = result.switch_enable
|
|
|
|
|
result.switch_enable = nil
|
|
|
|
|
result.hashkey = md5(jsonStringify(result))
|
|
|
|
|
result.alias = alias
|
|
|
|
|
result.switch_enable = switch_enable
|
|
|
|
|
local vssrutil = require 'vssrutil'
|
|
|
|
|
result.flag = vssrutil.get_flag(result.alias, result.server)
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
end
|
2022-05-22 23:42:54 +08:00
|
|
|
|
|
2021-09-05 16:50:22 +08:00
|
|
|
|
-- wget
|
|
|
|
|
local function wget(url)
|
2022-05-22 23:42:54 +08:00
|
|
|
|
local stdout =
|
|
|
|
|
luci.sys.exec(
|
|
|
|
|
'wget-ssl -q --user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36" --no-check-certificate -t 3 -T 10 -O- "' .. url .. '"'
|
|
|
|
|
)
|
2021-09-05 16:50:22 +08:00
|
|
|
|
return trim(stdout)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function check_filer(result)
|
2022-04-21 12:14:00 +08:00
|
|
|
|
do
|
2022-05-22 23:42:54 +08:00
|
|
|
|
local filter_word = split(filter_words, '/')
|
2022-04-21 12:14:00 +08:00
|
|
|
|
for i, v in pairs(filter_word) do
|
|
|
|
|
if result.alias:find(v) then
|
2022-05-22 23:42:54 +08:00
|
|
|
|
log('订阅节点关键字过滤:“' .. v .. '” ,该节点被丢弃')
|
2022-04-21 12:14:00 +08:00
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local execute = function()
|
|
|
|
|
-- exec
|
|
|
|
|
do
|
|
|
|
|
if proxy == '0' then -- 不使用代理更新的话先暂停
|
|
|
|
|
log('服务正在暂停')
|
|
|
|
|
luci.sys.init.stop(name)
|
|
|
|
|
end
|
|
|
|
|
for k, url in ipairs(subscribe_url) do
|
2022-05-22 23:42:54 +08:00
|
|
|
|
local groupName = ''
|
|
|
|
|
urlTable = split(url, ',')
|
|
|
|
|
groupName = table.getn(urlTable) > 1 and '[' .. urlTable[1] .. '] ' or ''
|
2021-09-05 16:50:22 +08:00
|
|
|
|
url = table.getn(urlTable) > 1 and urlTable[2] or url
|
|
|
|
|
local raw = wget(url)
|
|
|
|
|
if #raw > 0 then
|
|
|
|
|
local nodes, szType
|
|
|
|
|
local groupHash = md5(url)
|
|
|
|
|
cache[groupHash] = {}
|
|
|
|
|
tinsert(nodeResult, {})
|
|
|
|
|
local index = #nodeResult
|
|
|
|
|
-- 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) do
|
|
|
|
|
tinsert(servers, setmetatable(server, {__index = extra}))
|
|
|
|
|
end
|
|
|
|
|
nodes = servers
|
|
|
|
|
else
|
|
|
|
|
-- ssd 外的格式
|
|
|
|
|
nodes = split(base64Decode(raw):gsub(' ', '\n'), '\n')
|
|
|
|
|
end
|
|
|
|
|
for _, v in ipairs(nodes) do
|
|
|
|
|
if v then
|
|
|
|
|
local result
|
|
|
|
|
if szType == 'ssd' then
|
|
|
|
|
result = processData(szType, v, groupName)
|
|
|
|
|
elseif not szType then
|
|
|
|
|
local node = trim(v)
|
|
|
|
|
local dat = split(node, '://')
|
|
|
|
|
if dat and dat[1] and dat[2] then
|
|
|
|
|
if dat[1] == 'ss' then
|
2022-05-22 23:42:54 +08:00
|
|
|
|
result = processData(dat[1], dat[2], groupName)
|
2021-09-05 16:50:22 +08:00
|
|
|
|
else
|
2022-05-22 23:42:54 +08:00
|
|
|
|
result = processData(dat[1], base64Decode(dat[2]), groupName)
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
log('跳过未知类型: ' .. szType)
|
|
|
|
|
end
|
|
|
|
|
-- log(result)
|
|
|
|
|
if result then
|
2022-05-22 23:42:54 +08:00
|
|
|
|
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]') then -- 中文做地址的 也没有人拿中文域名搞,就算中文域也有Puny Code SB 机场
|
|
|
|
|
log('丢弃无效节点: ' .. result.type .. ' 节点, ' .. result.alias)
|
2021-09-05 16:50:22 +08:00
|
|
|
|
else
|
2022-05-22 23:42:54 +08:00
|
|
|
|
log('成功解析: ' .. result.type .. ' 节点, ' .. result.alias)
|
2021-09-05 16:50:22 +08:00
|
|
|
|
result.grouphashkey = groupHash
|
|
|
|
|
tinsert(nodeResult[index], result)
|
2022-05-22 23:42:54 +08:00
|
|
|
|
cache[groupHash][result.hashkey] = nodeResult[index][#nodeResult[index]]
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
log('成功解析节点数量: ' .. #nodes)
|
|
|
|
|
else
|
2022-04-21 12:14:00 +08:00
|
|
|
|
log(url .. ': 获取内容为空')
|
|
|
|
|
end
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
-- diff
|
|
|
|
|
do
|
|
|
|
|
if next(nodeResult) == nil then
|
2022-05-22 23:42:54 +08:00
|
|
|
|
log('更新失败,没有可用的节点信息')
|
2022-04-21 12:14:00 +08:00
|
|
|
|
if proxy == '0' then
|
|
|
|
|
luci.sys.init.start(name)
|
|
|
|
|
log('订阅失败, 恢复服务')
|
|
|
|
|
end
|
|
|
|
|
return
|
|
|
|
|
end
|
2021-09-05 16:50:22 +08:00
|
|
|
|
local add, del = 0, 0
|
2022-05-22 23:42:54 +08:00
|
|
|
|
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
|
2022-04-21 12:14:00 +08:00
|
|
|
|
ucic:delete(name, old['.name'])
|
2022-05-22 23:42:54 +08:00
|
|
|
|
del = del + 1
|
2021-09-05 16:50:22 +08:00
|
|
|
|
else
|
2022-05-22 23:42:54 +08:00
|
|
|
|
local dat = nodeResult[old.grouphashkey][old.hashkey]
|
|
|
|
|
ucic:tset(name, old['.name'], dat)
|
|
|
|
|
-- 标记一下
|
|
|
|
|
setmetatable(nodeResult[old.grouphashkey][old.hashkey], {__index = {_ignore = true}})
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
2022-04-21 12:14:00 +08:00
|
|
|
|
else
|
2022-05-22 23:42:54 +08:00
|
|
|
|
if not old.alias then
|
|
|
|
|
if not old.server or old.server_port then
|
|
|
|
|
ucic:delete(name, old['.name'])
|
|
|
|
|
else
|
|
|
|
|
old.alias = old.server .. ':' .. old.server_port
|
|
|
|
|
log('忽略手动添加的节点: ' .. old.alias)
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
log('忽略手动添加的节点: ' .. old.alias)
|
|
|
|
|
end
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
2022-05-22 23:42:54 +08:00
|
|
|
|
)
|
2022-04-21 12:14:00 +08:00
|
|
|
|
|
2021-09-05 16:50:22 +08:00
|
|
|
|
for k, v in ipairs(nodeResult) do
|
|
|
|
|
-- 如果订阅节点中有相同的节点信息 需要先去重。
|
|
|
|
|
new_nodes = table_unique(v)
|
|
|
|
|
for kk, vv in ipairs(new_nodes) do
|
|
|
|
|
if not vv._ignore then
|
|
|
|
|
local section = ucic:add(name, uciType)
|
|
|
|
|
ucic:tset(name, section, vv)
|
|
|
|
|
ucic:set(name, section, 'switch_enable', switch)
|
|
|
|
|
add = add + 1
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
ucic:commit(name)
|
|
|
|
|
-- 如果服务器已经不见了把帮换一个
|
|
|
|
|
local globalServer = ucic:get_first(name, 'global', 'global_server', '')
|
|
|
|
|
local firstServer = ucic:get_first(name, uciType)
|
|
|
|
|
if not ucic:get(name, globalServer) then
|
|
|
|
|
if firstServer then
|
2022-05-22 23:42:54 +08:00
|
|
|
|
ucic:set(name, ucic:get_first(name, 'global'), 'global_server', firstServer)
|
2021-09-05 16:50:22 +08:00
|
|
|
|
ucic:commit(name)
|
|
|
|
|
log('当前主服务器已更新,正在自动更换。')
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
if firstServer then
|
2022-05-22 23:42:54 +08:00
|
|
|
|
luci.sys.call('/etc/init.d/' .. name .. ' restart > /dev/null 2>&1') -- 不加&的话日志会出现的更早
|
2021-09-05 16:50:22 +08:00
|
|
|
|
else
|
2022-05-22 23:42:54 +08:00
|
|
|
|
luci.sys.call('/etc/init.d/' .. name .. ' stop > /dev/null 2>&1') -- 不加&的话日志会出现的更早
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
|
|
|
|
log('新增节点数量: ' .. add, '删除节点数量: ' .. del)
|
2022-05-22 23:42:54 +08:00
|
|
|
|
log('更新成功服务启动成功')
|
2021-09-05 16:50:22 +08:00
|
|
|
|
log('END SUBSCRIBE')
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if subscribe_url and #subscribe_url > 0 then
|
2022-05-22 23:42:54 +08:00
|
|
|
|
xpcall(
|
|
|
|
|
execute,
|
|
|
|
|
function(e)
|
|
|
|
|
log(e)
|
|
|
|
|
log(debug.traceback())
|
|
|
|
|
log('发生错误, 正在恢复服务')
|
|
|
|
|
log('END SUBSCRIBE')
|
|
|
|
|
local firstServer = ucic:get_first(name, uciType)
|
|
|
|
|
if firstServer then
|
|
|
|
|
luci.sys.call('/etc/init.d/' .. name .. ' restart > /dev/null 2>&1') -- 不加&的话日志会出现的更早
|
|
|
|
|
else
|
|
|
|
|
luci.sys.call('/etc/init.d/' .. name .. ' stop > /dev/null 2>&1') -- 不加&的话日志会出现的更早
|
|
|
|
|
end
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|
2022-05-22 23:42:54 +08:00
|
|
|
|
)
|
2021-09-05 16:50:22 +08:00
|
|
|
|
end
|