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

912 lines
26 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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
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], "?")
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
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)
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"
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('订阅完毕...')
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