immortalwrt/package/lienol/luci-app-passwall/root/usr/share/passwall/subscribe.lua

912 lines
26 KiB
Lua
Raw Normal View History

#!/usr/bin/lua
------------------------------------------------
-- @author William Chan <root@williamchan.me>
------------------------------------------------
require 'nixio'
require 'luci.model.uci'
require 'luci.util'
require 'luci.jsonc'
require 'luci.sys'
local _api = require "luci.model.cbi.passwall.api.api"
-- 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
local ssub, slen, schar, sbyte, sformat, sgsub = string.sub, string.len, string.char, string.byte, string.format, string.gsub
local jsonParse, jsonStringify = luci.jsonc.parse, luci.jsonc.stringify
local b64decode = nixio.bin.b64decode
local nodeResult = {} -- update result
local application = 'passwall'
local uciType = 'nodes'
local ucic2 = luci.model.uci.cursor()
local arg2 = arg[2]
local allowInsecure_default = ucic2:get_bool(application, "@global_subscribe[0]", "allowInsecure")
ucic2:revert(application)
local log = function(...)
if arg2 then
local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
if arg2 == "log" then
local f, err = io.open("/var/log/passwall.log", "a")
if f and err == nil then
f:write(result .. "\n")
f:close()
end
elseif arg2 == "print" then
print(result)
end
end
end
-- 获取各项动态配置的当前服务器,可以用 get 和 set get必须要获取到节点表
local CONFIG = {}
do
local function import_config(protocol)
local node_num = ucic2:get(application, "@global_other[0]", protocol .. "_node_num") or 1
for i = 1, node_num, 1 do
local name = string.upper(protocol)
local szType = "@global[0]"
local option = protocol .. "_node" .. i
local node = ucic2:get(application, szType, option)
local currentNode
if node then
currentNode = ucic2:get_all(application, node)
end
CONFIG[#CONFIG + 1] = {
log = true,
remarks = name .. "节点" .. i,
node = node,
currentNode = currentNode,
set = function(server)
ucic2:set(application, szType, option, server)
end
}
end
end
import_config("tcp")
import_config("udp")
ucic2:foreach(application, "socks", function(t)
local node = t.node
local currentNode
if node then
currentNode = ucic2:get_all(application, node)
end
CONFIG[#CONFIG + 1] = {
log = true,
remarks = "Socks节点" .. t[".name"],
currentNode = currentNode,
set = function(server)
ucic2:set(application, t[".name"], "node", server)
end
}
end)
local tcp_node1_table = ucic2:get(application, "@auto_switch[0]", "tcp_node1")
if tcp_node1_table then
local nodes = {}
local new_nodes = {}
for k,v in ipairs(tcp_node1_table) do
local node = v
local currentNode
if node then
currentNode = ucic2:get_all(application, node)
end
nodes[#nodes + 1] = {
log = false,
node = node,
currentNode = currentNode,
remarks = node,
set = function(server)
for kk, vv in pairs(CONFIG) do
if (vv.remarks == "自动切换TCP_1节点列表") then
table.insert(vv.new_nodes, server)
end
end
end
}
end
CONFIG[#CONFIG + 1] = {
remarks = "自动切换TCP_1节点列表",
nodes = nodes,
new_nodes = new_nodes,
set = function()
for kk, vv in pairs(CONFIG) do
if (vv.remarks == "自动切换TCP_1节点列表") then
log("刷新自动切换列表")
ucic2:set_list(application, "@auto_switch[0]", "tcp_node1", vv.new_nodes)
end
end
end
}
end
ucic2:foreach(application, uciType, function(node)
if node.type == 'V2ray' and node.protocol == '_shunt' then
local node_id = node[".name"]
ucic2:foreach(application, "shunt_rules", function(e)
local _node_id = node[e[".name"]] or nil
local _node
if _node_id then
_node = ucic2:get_all(application, _node_id)
end
CONFIG[#CONFIG + 1] = {
log = false,
currentNode = _node,
remarks = "V2ray分流" .. e.remarks .. "节点",
set = function(server)
ucic2:set(application, node_id, e[".name"], server)
end
}
end)
local default_node_id = node.default_node
local default_node
if default_node_id then
default_node = ucic2:get_all(application, default_node_id)
end
CONFIG[#CONFIG + 1] = {
log = false,
currentNode = default_node,
remarks = "V2ray分流默认节点",
set = function(server)
ucic2:set(application, node_id, "default_node", server)
end
}
elseif node.type == 'V2ray' and node.protocol == '_balancing' then
local node_id = node[".name"]
local nodes = {}
local new_nodes = {}
if node.balancing_node then
for k, v in pairs(node.balancing_node) do
local node = v
local currentNode
if node then
currentNode = ucic2:get_all(application, node)
end
nodes[#nodes + 1] = {
log = false,
node = node,
currentNode = currentNode,
remarks = node,
set = function(server)
for kk, vv in pairs(CONFIG) do
if (vv.remarks == "V2ray负载均衡节点列表" .. node_id) then
table.insert(vv.new_nodes, server)
end
end
end
}
end
end
CONFIG[#CONFIG + 1] = {
remarks = "V2ray负载均衡节点列表" .. node_id,
nodes = nodes,
new_nodes = new_nodes,
set = function()
for kk, vv in pairs(CONFIG) do
if (vv.remarks == "V2ray负载均衡节点列表" .. node_id) then
log("刷新V2ray负载均衡节点列表")
ucic2:foreach(application, uciType, function(node2)
if node2[".name"] == node[".name"] then
local index = node2[".index"]
ucic2:set_list(application, "@nodes[" .. index .. "]", "balancing_node", vv.new_nodes)
end
end)
end
end
end
}
end
end)
for k, v in pairs(CONFIG) do
if v.nodes and type(v.nodes) == "table" then
for kk, vv in pairs(v.nodes) do
if vv.currentNode == nil then
CONFIG[k].nodes[kk] = nil
end
end
else
if v.currentNode == nil then
CONFIG[k] = nil
end
end
end
end
-- 判断是否过滤节点关键字
local filter_keyword_enabled = ucic2:get(application, "@global_subscribe[0]", "filter_enabled")
local filter_keyword_table = ucic2:get(application, "@global_subscribe[0]", "filter_keyword")
local filter_keyword_discarded = ucic2:get(application, "@global_subscribe[0]", "filter_keyword_discarded")
local function is_filter_keyword(value)
if filter_keyword_enabled and filter_keyword_enabled == "1" then
if filter_keyword_table then
if filter_keyword_discarded and filter_keyword_discarded == "1" then
for k,v in ipairs(filter_keyword_table) do
if value:find(v) then
return true
end
end
else
local result = true
for k,v in ipairs(filter_keyword_table) do
if value:find(v) then
result = false
end
end
return result
end
end
end
return false
end
-- 分割字符串
local function split(full, sep)
if full then
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
return {}
end
-- urlencode
-- local function get_urlencode(c) return sformat("%%%02X", sbyte(c)) end
-- local function urlEncode(szText)
-- local str = szText:gsub("([^0-9a-zA-Z ])", get_urlencode)
-- str = str:gsub(" ", "+")
-- return str
-- end
local function get_urldecode(h) return schar(tonumber(h, 16)) end
local function UrlDecode(szText)
return (szText and szText:gsub("+", " "):gsub("%%(%x%x)", get_urldecode)) or nil
end
-- trim
local function trim(text)
if not text or text == "" then return "" end
return (sgsub(text, "^%s*(.-)%s*$", "%1"))
end
-- base64
local function base64Decode(text)
local raw = text
if not text then return '' end
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
-- 处理数据
local function processData(szType, content, add_mode)
log(content, add_mode)
local result = {
timeout = 60,
add_mode = add_mode,
is_sub = add_mode == '导入' and 0 or 1
}
if szType == 'ssr' then
local dat = split(content, "/%?")
local hostInfo = split(dat[1], ':')
result.type = "SSR"
result.address = hostInfo[1]
result.port = hostInfo[2]
result.ssr_protocol = hostInfo[3]
result.ssr_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)
if group then result.group = group end
result.remarks = base64Decode(params.remarks)
elseif szType == 'vmess' then
local info = jsonParse(content)
result.type = 'V2ray'
result.address = info.add
result.port = info.port
result.protocol = 'vmess'
result.transport = info.net
result.alter_id = info.aid
result.vmess_id = info.id
result.remarks = info.ps
-- result.mux = 1
-- result.mux_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
if info.type and info.type ~= "http" then
info.type = "none"
end
result.tcp_guise = info.type
result.tcp_guise_http_host = info.host
result.tcp_guise_http_path = info.path
end
if info.net == 'kcp' then
result.mkcp_guise = info.type
result.mkcp_mtu = 1350
result.mkcp_tti = 50
result.mkcp_uplinkCapacity = 5
result.mkcp_downlinkCapacity = 20
result.mkcp_readBufferSize = 2
result.mkcp_writeBufferSize = 2
end
if info.net == 'quic' then
result.quic_guise = info.type
result.quic_key = info.key
result.quic_security = info.securty
end
if not info.security then result.security = "auto" end
if info.tls == "tls" or info.tls == "1" then
result.stream_security = "tls"
result.tls_serverName = info.host
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
else
result.stream_security = "none"
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 hostInfoLen = #hostInfo
local host = nil
local userinfo = nil
if hostInfoLen > 2 then
host = split(hostInfo[hostInfoLen], ":")
userinfo = {}
for i = 1, hostInfoLen - 1 do
tinsert(userinfo, hostInfo[i])
end
userinfo = table.concat(userinfo, '@')
else
host = split(hostInfo[2], ":")
userinfo = base64Decode(hostInfo[1])
end
local method = userinfo:sub(1, userinfo:find(":") - 1)
local password = userinfo:sub(userinfo:find(":") + 1, #userinfo)
result.remarks = UrlDecode(alias)
result.type = "SS"
result.address = host[1]
if host[2] and host[2]:find("/%?") then
local query = split(host[2], "/%?")
result.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.ss_plugin = plugin_info:sub(1, idx_pn - 1)
result.ss_plugin_opts =
plugin_info:sub(idx_pn + 1, #plugin_info)
else
result.ss_plugin = plugin_info
end
end
if result.ss_plugin and result.ss_plugin == "simple-obfs" then
result.ss_plugin = "obfs-local"
end
else
result.port = host[2]
end
result.ss_encrypt_method = method
result.password = password
2020-02-18 16:49:12 +08:00
elseif szType == "trojan" then
local alias = ""
if content:find("#") then
local idx_sp = content:find("#")
alias = content:sub(idx_sp + 1, -1)
content = content:sub(0, idx_sp - 1)
end
result.type = "Trojan"
result.remarks = UrlDecode(alias)
if content:find("@") then
local Info = split(content, "@")
result.password = UrlDecode(Info[1])
local port = "443"
Info[2] = (Info[2] or ""):gsub("/%?", "?")
local hostInfo = nil
if Info[2]:find(":") then
hostInfo = split(Info[2], ":")
result.address = hostInfo[1]
local idx_port = 2
if hostInfo[2]:find("?") then
hostInfo = split(hostInfo[2], "?")
idx_port = 1
end
if hostInfo[idx_port] ~= "" then port = hostInfo[idx_port] end
else
if Info[2]:find("?") then
hostInfo = split(Info[2], "?")
end
result.address = hostInfo and hostInfo[1] or Info[2]
end
local peer, sni = nil, ""
local allowInsecure = allowInsecure_default
local query = split(Info[2], "?")
2020-02-18 16:49:12 +08:00
local params = {}
for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=')
params[string.lower(t[1])] = UrlDecode(t[2])
end
if params.allowinsecure then
allowInsecure = params.allowinsecure
end
if params.peer then peer = params.peer end
sni = params.sni and params.sni or ""
if params.mux and params.mux == "1" then result.mux = "1" end
if params.ws and params.ws == "1" then
result.trojan_transport = "ws"
if params.wshost then result.ws_host = params.wshost end
if params.wspath then result.ws_path = params.wspath end
if sni == "" and params.wshost then sni = params.wshost end
end
if params.ss and params.ss == "1" then
result.ss_aead = "1"
if params.ssmethod then result.ss_aead_method = string.lower(params.ssmethod) end
if params.sspasswd then result.ss_aead_pwd = params.sspasswd end
end
result.port = port
if result.mux or result.trojan_transport == "ws" or result.ss_aead then
result.type = "Trojan-Go"
result.fingerprint = "firefox"
end
result.stream_security = 'tls'
result.tls_serverName = peer and peer or sni
result.tls_allowInsecure = allowInsecure and "1" or "0"
end
elseif szType == "trojan-go" then
local alias = ""
if content:find("#") then
local idx_sp = content:find("#")
alias = content:sub(idx_sp + 1, -1)
content = content:sub(0, idx_sp - 1)
end
result.type = "Trojan-Go"
result.remarks = UrlDecode(alias)
if content:find("@") then
local Info = split(content, "@")
result.password = UrlDecode(Info[1])
local port = "443"
Info[2] = (Info[2] or ""):gsub("/%?", "?")
local hostInfo = nil
if Info[2]:find(":") then
hostInfo = split(Info[2], ":")
result.address = hostInfo[1]
local idx_port = 2
if hostInfo[2]:find("?") then
hostInfo = split(hostInfo[2], "?")
idx_port = 1
2020-02-18 16:49:12 +08:00
end
if hostInfo[idx_port] ~= "" then port = hostInfo[idx_port] end
else
if Info[2]:find("?") then
hostInfo = split(Info[2], "?")
end
result.address = hostInfo and hostInfo[1] or Info[2]
end
local peer, sni = nil, ""
local allowInsecure = allowInsecure_default
local query = split(Info[2], "?")
local params = {}
for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=')
params[string.lower(t[1])] = UrlDecode(t[2])
end
if params.allowinsecure then
allowInsecure = params.allowinsecure
end
if params.peer then peer = params.peer end
sni = params.sni and params.sni or ""
if params.mux and params.mux == "1" then result.mux = "1" end
if params.type and params.type == "ws" then
result.trojan_transport = "ws"
if params.host then result.ws_host = params.host end
if params.path then result.ws_path = params.path end
if sni == "" and params.host then sni = params.host end
end
if params.encryption and params.encryption:match('^ss;[^;:]*[;:].*$') then
result.ss_aead = "1"
result.ss_aead_method, result.ss_aead_pwd = params.encryption:match('^ss;([^;:]*)[;:](.*)$')
result.ss_aead_method = string.lower(result.ss_aead_method)
2020-02-18 16:49:12 +08:00
end
result.port = port
result.fingerprint = "firefox"
result.stream_security = 'tls'
result.tls_serverName = peer and peer or sni
result.tls_allowInsecure = allowInsecure and "1" or "0"
2020-02-18 16:49:12 +08:00
end
elseif szType == "ssd" then
result.type = "SS"
result.address = content.server
result.port = content.port
result.password = content.password
result.ss_encrypt_method = content.encryption
result.ss_plugin = content.plugin
result.ss_plugin_opts = content.plugin_options
result.group = content.airport
result.remarks = content.remarks
else
log('暂时不支持' .. szType .. "类型的节点订阅,跳过此节点。")
return nil
end
if not 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
-- curl
local function curl(url)
local ua = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36"
local stdout = luci.sys.exec('curl -sL --user-agent "' .. ua .. '" -k --retry 3 --connect-timeout 3 "' .. url .. '"')
return trim(stdout)
end
local function truncate_nodes()
local is_stop = 0
local function clear(type)
local node_num = ucic2:get(application, "@global_other[0]", type .. "_node_num") or 1
for i = 1, node_num, 1 do
local node = ucic2:get(application, "@global[0]", type .. "_node" .. i)
if node then
local is_sub_node = ucic2:get(application, node, "is_sub") or 0
if is_sub_node == "1" then
is_stop = 1
ucic2:set(application, '@global[0]', type .. "_node" .. i, "nil")
end
end
end
end
clear("tcp")
clear("udp")
ucic2:foreach(application, "socks", function(t)
local node = t.node
if node then
local is_sub_node = ucic2:get(application, node, "is_sub") or 0
if is_sub_node == "1" then
is_stop = 1
ucic2:set(application, t[".name"], "node", "nil")
end
end
end)
ucic2:foreach(application, uciType, function(node)
if (node.is_sub or node.hashkey) and node.add_mode ~= '导入' then
ucic2:delete(application, node['.name'])
end
end)
ucic2:commit(application)
if is_stop == 1 then
luci.sys.call("/etc/init.d/" .. application .. " restart > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早
end
log('在线订阅节点已全部删除')
end
local function select_node(nodes, config)
local server
if config.currentNode then
-- 特别优先级 V2ray分流 + 备注
if config.currentNode.type == 'V2ray' and config.currentNode.protocol == '_shunt' then
for id, node in pairs(nodes) do
if node.remarks == config.currentNode.remarks then
log('选择【' .. config.remarks .. '】V2ray分流匹配节点' .. node.remarks)
server = id
break
end
end
end
-- 特别优先级 V2ray负载均衡 + 备注
if config.currentNode.type == 'V2ray' and config.currentNode.protocol == '_balancing' then
for id, node in pairs(nodes) do
if node.remarks == config.currentNode.remarks then
log('选择【' .. config.remarks .. '】V2ray负载均衡匹配节点' .. node.remarks)
server = id
break
end
end
end
-- 第一优先级 cfgid
if not server then
for id, node in pairs(nodes) do
if id == config.currentNode['.name'] then
if config.log == nil or config.log == true then
log('选择【' .. config.remarks .. '】第一匹配节点:' .. node.remarks)
end
server = id
break
end
end
end
-- 第二优先级 IP + 端口
if not server then
for id, node in pairs(nodes) do
if node.address and node.port then
if node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port then
if config.log == nil or config.log == true then
log('选择【' .. config.remarks .. '】第二匹配节点:' .. node.remarks)
end
server = id
break
end
end
end
end
-- 第三优先级 IP
if not server then
for id, node in pairs(nodes) do
if node.address then
if node.address == config.currentNode.address then
if config.log == nil or config.log == true then
log('选择【' .. config.remarks .. '】第三匹配节点:' .. node.remarks)
end
server = id
break
end
end
end
end
-- 第四优先级备注
if not server then
for id, node in pairs(nodes) do
if node.remarks then
if node.remarks == config.currentNode.remarks then
if config.log == nil or config.log == true then
log('选择【' .. config.remarks .. '】第四匹配节点:' .. node.remarks)
end
server = id
break
end
end
end
end
end
-- 还不行 随便找一个
if not server then
server = ucic2:get_all(application, '@' .. uciType .. '[0]')
if server then
if config.log == nil or config.log == true then
log('' .. config.remarks .. '' .. '无法找到最匹配的节点,当前已更换为:' .. server.remarks)
end
server = server[".name"]
end
end
if server then
config.set(server)
end
end
local function update_node(manual)
if next(nodeResult) == nil then
log("更新失败,没有可用的节点信息")
return
end
-- delet all for subscribe nodes
ucic2:foreach(application, uciType, function(node)
-- 如果是手动导入的节点就不参与删除
if manual == 0 and (node.is_sub or node.hashkey) and node.add_mode ~= '导入' then
ucic2:delete(application, node['.name'])
end
end)
for _, v in ipairs(nodeResult) do
for _, vv in ipairs(v) do
local uuid = _api.gen_uuid()
local cfgid = ucic2:section(application, uciType, uuid)
cfgid = uuid
for kkk, vvv in pairs(vv) do
ucic2:set(application, cfgid, kkk, vvv)
end
end
end
ucic2:commit(application)
if next(CONFIG) then
local nodes = {}
local ucic3 = uci.cursor()
ucic3:foreach(application, uciType, function(node)
nodes[node['.name']] = node
end)
for _, config in pairs(CONFIG) do
if config.nodes and type(config.nodes) == "table" then
for kk, vv in pairs(config.nodes) do
select_node(nodes, vv)
end
config.set()
else
select_node(nodes, config)
end
end
--[[
for k, v in pairs(CONFIG) do
if type(v.new_nodes) == "table" and #v.new_nodes > 0 then
for kk, vv in pairs(v.new_nodes) do
print(vv)
end
else
print(v.new_nodes)
end
end
]]--
ucic2:commit(application)
luci.sys.call("/etc/init.d/" .. application .. " restart > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早
end
end
local function parse_link(raw, remark, manual)
if raw and #raw > 0 then
local add_mode
local nodes, szType
local all_nodes = {}
tinsert(nodeResult, all_nodes)
-- SSD 似乎是这种格式 ssd:// 开头的
if raw:find('ssd://') then
szType = 'ssd'
add_mode = remark
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 外的格式
if manual then
nodes = split(raw:gsub(" ", "\n"), "\n")
add_mode = '导入'
else
nodes = split(base64Decode(raw):gsub(" ", "\n"), "\n")
add_mode = remark
end
end
for _, v in ipairs(nodes) do
if v then
local result
if szType == 'ssd' then
result = processData(szType, v, add_mode)
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' or dat[1] == 'trojan' or dat[1] == 'trojan-go' then
result = processData(dat[1], dat[2], add_mode)
else
result = processData(dat[1], base64Decode(dat[2]), add_mode)
end
end
else
log('跳过未知类型: ' .. szType)
end
-- log(result)
if result then
if is_filter_keyword(result.remarks) or
not result.address or
result.remarks == "NULL" or
result.address:match("[^0-9a-zA-Z%-%_%.%s]") or -- 中文做地址的 也没有人拿中文域名搞就算中文域也有Puny Code SB 机场
not result.address:find("%.") or -- 虽然没有.也算域,不过应该没有人会这样干吧
result.address:sub(#result.address) == "." -- 结尾是.
then
log('丢弃无效节点: ' .. result.type .. ' 节点, ' .. result.remarks)
else
tinsert(all_nodes, result)
end
end
end
end
log('成功解析节点数量: ' .. #all_nodes)
else
if not manual then
log('获取到的节点内容为空...')
end
end
end
local execute = function()
-- exec
do
ucic2:foreach(application, "subscribe_list", function(obj)
local enabled = obj.enabled or nil
if enabled and enabled == "1" then
local remark = obj.remark
local url = obj.url
log('正在订阅: ' .. url)
local raw = curl(url)
parse_link(raw, remark)
end
end)
-- diff
update_node(0)
end
end
if arg[1] then
if arg[1] == "start" then
local count = luci.sys.exec("echo -n $(uci show " .. application .. " | grep @subscribe_list | grep -c \"enabled='1'\")")
if count and tonumber(count) > 0 then
log('开始订阅...')
xpcall(execute, function(e)
log(e)
log(debug.traceback())
log('发生错误, 正在恢复服务')
end)
log('订阅完毕...')
2020-02-18 13:09:37 +08:00
else
log('未设置订阅或未启用订阅, 请检查设置...')
end
elseif arg[1] == "add" then
local f = assert(io.open("/tmp/links.conf", 'r'))
local content = f:read('*all')
f:close()
local nodes = split(content:gsub(" ", "\n"), "\n")
for _, raw in ipairs(nodes) do
parse_link(raw, nil, 1)
end
update_node(1)
luci.sys.call("rm -f /tmp/links.conf")
elseif arg[1] == "truncate" then
truncate_nodes()
end
end