small/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua

1901 lines
61 KiB
Lua
Raw Normal View History

2023-04-27 11:31:00 +08:00
#!/usr/bin/lua
------------------------------------------------
-- @author William Chan <root@williamchan.me>
------------------------------------------------
require 'luci.util'
require 'luci.jsonc'
require 'luci.sys'
local appname = 'passwall2'
local api = require ("luci.passwall2.api")
local datatypes = require "luci.cbi.datatypes"
-- these global functions are accessed all the time by the event handler
-- so caching them is worth the effort
local tinsert = table.insert
local ssub, slen, schar, sbyte, sformat, sgsub = string.sub, string.len, string.char, string.byte, string.format, string.gsub
2023-12-30 20:14:29 +08:00
local split = api.split
2023-04-27 11:31:00 +08:00
local jsonParse, jsonStringify = luci.jsonc.parse, luci.jsonc.stringify
local base64Decode = api.base64Decode
2025-01-03 20:24:43 +08:00
local uci = api.uci
2025-01-03 00:15:53 +08:00
local fs = api.fs
2023-04-27 11:31:00 +08:00
uci:revert(appname)
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
2025-04-01 04:15:51 +08:00
local has_singbox = api.finded_com("sing-box")
2023-09-07 02:10:48 +08:00
local has_xray = api.finded_com("xray")
2024-01-11 02:11:32 +08:00
local has_hysteria2 = api.finded_com("hysteria")
2023-04-27 11:31:00 +08:00
local allowInsecure_default = true
2024-03-14 20:18:45 +08:00
local ss_type_default = uci:get(appname, "@global_subscribe[0]", "ss_type") or "shadowsocks-libev"
2024-01-11 02:11:32 +08:00
local trojan_type_default = uci:get(appname, "@global_subscribe[0]", "trojan_type") or "sing-box"
local vmess_type_default = uci:get(appname, "@global_subscribe[0]", "vmess_type") or "xray"
local vless_type_default = uci:get(appname, "@global_subscribe[0]", "vless_type") or "xray"
local hysteria2_type_default = uci:get(appname, "@global_subscribe[0]", "hysteria2_type") or "hysteria2"
2024-11-19 16:18:08 +08:00
local domain_strategy_default = uci:get(appname, "@global_subscribe[0]", "domain_strategy") or ""
local domain_strategy_node = ""
2023-04-27 11:31:00 +08:00
-- 判断是否过滤节点关键字
local filter_keyword_mode_default = uci:get(appname, "@global_subscribe[0]", "filter_keyword_mode") or "0"
local filter_keyword_discard_list_default = uci:get(appname, "@global_subscribe[0]", "filter_discard_list") or {}
local filter_keyword_keep_list_default = uci:get(appname, "@global_subscribe[0]", "filter_keep_list") or {}
local function is_filter_keyword(value)
if filter_keyword_mode_default == "1" then
for k,v in ipairs(filter_keyword_discard_list_default) do
if value:find(v, 1, true) then
return true
end
end
elseif filter_keyword_mode_default == "2" then
local result = true
for k,v in ipairs(filter_keyword_keep_list_default) do
if value:find(v, 1, true) then
result = false
end
end
return result
elseif filter_keyword_mode_default == "3" then
local result = false
for k,v in ipairs(filter_keyword_discard_list_default) do
if value:find(v, 1, true) then
result = true
end
end
for k,v in ipairs(filter_keyword_keep_list_default) do
if value:find(v, 1, true) then
result = false
end
end
return result
elseif filter_keyword_mode_default == "4" then
local result = true
for k,v in ipairs(filter_keyword_keep_list_default) do
if value:find(v, 1, true) then
result = false
end
end
for k,v in ipairs(filter_keyword_discard_list_default) do
if value:find(v, 1, true) then
result = true
end
end
return result
end
return false
end
local nodeResult = {} -- update result
local debug = false
local log = function(...)
if debug == true then
local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
print(result)
else
api.log(...)
end
end
2025-01-11 04:14:23 +08:00
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" then
nodes_table[#nodes_table + 1] = e
end
end
2023-04-27 11:31:00 +08:00
-- 获取各项动态配置的当前服务器,可以用 get 和 set get必须要获取到节点表
local CONFIG = {}
do
if true then
local szType = "@global[0]"
local option = "node"
local node_id = uci:get(appname, szType, option)
CONFIG[#CONFIG + 1] = {
log = true,
remarks = "节点",
currentNode = node_id and uci:get_all(appname, node_id) or nil,
set = function(o, server)
uci:set(appname, szType, option, server)
o.newNodeId = server
end
}
end
if true then
local i = 0
local option = "node"
uci:foreach(appname, "socks", function(t)
i = i + 1
2025-01-11 04:14:23 +08:00
local id = t[".name"]
2023-04-27 11:31:00 +08:00
local node_id = t[option]
CONFIG[#CONFIG + 1] = {
log = true,
2025-01-11 04:14:23 +08:00
id = id,
2023-04-27 11:31:00 +08:00
remarks = "Socks节点列表[" .. i .. "]",
currentNode = node_id and uci:get_all(appname, node_id) or nil,
set = function(o, server)
2025-01-11 04:14:23 +08:00
if not server or server == "" then
if #nodes_table > 0 then
server = nodes_table[1][".name"]
end
end
2023-04-27 11:31:00 +08:00
uci:set(appname, t[".name"], option, server)
o.newNodeId = server
end
}
2025-01-11 04:14:23 +08:00
if t.autoswitch_backup_node and #t.autoswitch_backup_node > 0 then
local flag = "Socks节点列表[" .. i .. "]备用节点的列表"
local currentNodes = {}
local newNodes = {}
for k, node_id in ipairs(t.autoswitch_backup_node) do
if node_id then
local currentNode = uci:get_all(appname, node_id) or nil
if currentNode then
currentNodes[#currentNodes + 1] = {
log = true,
remarks = flag .. "[" .. k .. "]",
currentNode = currentNode,
set = function(o, server)
if server and server ~= "nil" then
table.insert(o.newNodes, server)
end
end
}
end
end
end
CONFIG[#CONFIG + 1] = {
remarks = flag,
currentNodes = currentNodes,
newNodes = newNodes,
set = function(o, newNodes)
if o then
if not newNodes then newNodes = o.newNodes end
uci:set_list(appname, id, "autoswitch_backup_node", newNodes or {})
end
end
}
end
2023-04-27 11:31:00 +08:00
end)
end
2023-05-04 20:15:43 +08:00
if true then
local i = 0
local option = "lbss"
2025-04-01 04:15:51 +08:00
local function is_ip_port(str)
if type(str) ~= "string" then return false end
local ip, port = str:match("^([%d%.]+):(%d+)$")
return ip and datatypes.ipaddr(ip) and tonumber(port) and tonumber(port) <= 65535
end
2023-05-04 20:15:43 +08:00
uci:foreach(appname, "haproxy_config", function(t)
i = i + 1
local node_id = t[option]
CONFIG[#CONFIG + 1] = {
log = true,
id = t[".name"],
remarks = "HAProxy负载均衡节点列表[" .. i .. "]",
currentNode = node_id and uci:get_all(appname, node_id) or nil,
set = function(o, server)
2025-04-01 04:15:51 +08:00
-- 如果当前 lbss 值不是 ip:port 格式,才进行修改
if not is_ip_port(t[option]) then
uci:set(appname, t[".name"], option, server)
o.newNodeId = server
end
2023-09-01 02:10:35 +08:00
end,
delete = function(o)
2025-04-01 04:15:51 +08:00
-- 如果当前 lbss 值不是 ip:port 格式,才进行删除
if not is_ip_port(t[option]) then
uci:delete(appname, t[".name"])
end
2023-05-04 20:15:43 +08:00
end
}
end)
end
2023-04-27 11:31:00 +08:00
if true then
local i = 0
uci:foreach(appname, "acl_rule", function(t)
i = i + 1
local option = "node"
local node_id = t[option]
CONFIG[#CONFIG + 1] = {
log = true,
id = t[".name"],
remarks = "访问控制列表[" .. i .. "]",
currentNode = node_id and uci:get_all(appname, node_id) or nil,
set = function(o, server)
uci:set(appname, t[".name"], option, server)
o.newNodeId = server
end
}
end)
end
uci:foreach(appname, "nodes", function(node)
2024-04-23 00:12:48 +08:00
local node_id = node[".name"]
2023-04-27 11:31:00 +08:00
if node.protocol and node.protocol == '_shunt' then
local rules = {}
uci:foreach(appname, "shunt_rules", function(e)
if e[".name"] and e.remarks then
table.insert(rules, e)
end
end)
table.insert(rules, {
[".name"] = "default_node",
remarks = "默认"
})
table.insert(rules, {
[".name"] = "main_node",
remarks = "默认前置"
})
for k, e in pairs(rules) do
local _node_id = node[e[".name"]] or nil
2023-09-01 20:15:25 +08:00
if _node_id and api.parseURL(_node_id) then
2023-09-01 02:10:35 +08:00
else
CONFIG[#CONFIG + 1] = {
log = false,
currentNode = _node_id and uci:get_all(appname, _node_id) or nil,
remarks = "分流" .. e.remarks .. "节点",
set = function(o, server)
2024-12-19 00:16:57 +08:00
if not server then server = "" end
2023-09-01 02:10:35 +08:00
uci:set(appname, node_id, e[".name"], server)
o.newNodeId = server
end
}
end
2023-04-27 11:31:00 +08:00
end
elseif node.protocol and node.protocol == '_balancing' then
2025-01-11 04:14:23 +08:00
local flag = "Xray负载均衡节点[" .. node_id .. "]列表"
local currentNodes = {}
local newNodes = {}
2023-04-27 11:31:00 +08:00
if node.balancing_node then
for k, node in pairs(node.balancing_node) do
2025-01-11 04:14:23 +08:00
currentNodes[#currentNodes + 1] = {
2023-04-27 11:31:00 +08:00
log = false,
node = node,
currentNode = node and uci:get_all(appname, node) or nil,
remarks = node,
set = function(o, server)
2025-01-11 04:14:23 +08:00
if o and server and server ~= "nil" then
table.insert(o.newNodes, server)
2023-04-27 11:31:00 +08:00
end
end
}
end
end
CONFIG[#CONFIG + 1] = {
2025-01-11 04:14:23 +08:00
remarks = flag,
currentNodes = currentNodes,
newNodes = newNodes,
set = function(o, newNodes)
if o then
if not newNodes then newNodes = o.newNodes end
uci:set_list(appname, node_id, "balancing_node", newNodes or {})
2023-04-27 11:31:00 +08:00
end
end
}
2024-04-23 00:12:48 +08:00
--后备节点
local currentNode = uci:get_all(appname, node_id) or nil
if currentNode and currentNode.fallback_node then
CONFIG[#CONFIG + 1] = {
log = true,
id = node_id,
remarks = "Xray负载均衡节点[" .. node_id .. "]后备节点",
currentNode = uci:get_all(appname, currentNode.fallback_node) or nil,
set = function(o, server)
uci:set(appname, node_id, "fallback_node", server)
o.newNodeId = server
end,
delete = function(o)
uci:delete(appname, node_id, "fallback_node")
end
}
end
2025-03-02 20:22:12 +08:00
elseif node.protocol and node.protocol == '_urltest' then
local flag = "Sing-Box URLTest节点[" .. node_id .. "]列表"
local currentNodes = {}
local newNodes = {}
if node.urltest_node then
for k, node in pairs(node.urltest_node) do
currentNodes[#currentNodes + 1] = {
log = false,
node = node,
currentNode = node and uci:get_all(appname, node) or nil,
remarks = node,
set = function(o, server)
if o and server and server ~= "nil" then
table.insert(o.newNodes, server)
end
end
}
end
end
CONFIG[#CONFIG + 1] = {
remarks = flag,
currentNodes = currentNodes,
newNodes = newNodes,
set = function(o, newNodes)
if o then
if not newNodes then newNodes = o.newNodes end
uci:set_list(appname, node_id, "urltest_node", newNodes or {})
end
end
}
2024-04-23 00:12:48 +08:00
else
2024-12-17 00:17:40 +08:00
--前置代理节点
local currentNode = uci:get_all(appname, node_id) or nil
if currentNode and currentNode.preproxy_node then
CONFIG[#CONFIG + 1] = {
log = true,
id = node_id,
remarks = "节点[" .. node_id .. "]前置代理节点",
currentNode = uci:get_all(appname, currentNode.preproxy_node) or nil,
set = function(o, server)
uci:set(appname, node_id, "preproxy_node", server)
o.newNodeId = server
end,
delete = function(o)
uci:delete(appname, node_id, "preproxy_node")
end
}
end
2024-04-23 00:12:48 +08:00
--落地节点
local currentNode = uci:get_all(appname, node_id) or nil
if currentNode and currentNode.to_node then
CONFIG[#CONFIG + 1] = {
log = true,
id = node_id,
remarks = "节点[" .. node_id .. "]落地节点",
currentNode = uci:get_all(appname, currentNode.to_node) or nil,
set = function(o, server)
uci:set(appname, node_id, "to_node", server)
o.newNodeId = server
end,
delete = function(o)
uci:delete(appname, node_id, "to_node")
end
}
end
2023-04-27 11:31:00 +08:00
end
end)
for k, v in pairs(CONFIG) do
2025-01-11 04:14:23 +08:00
if v.currentNodes and type(v.currentNodes) == "table" then
for kk, vv in pairs(v.currentNodes) do
2023-04-27 11:31:00 +08:00
if vv.currentNode == nil then
2025-01-11 04:14:23 +08:00
CONFIG[k].currentNodes[kk] = nil
2023-04-27 11:31:00 +08:00
end
end
else
2023-09-03 02:09:17 +08:00
if v.currentNode == nil then
if v.delete then
v.delete()
end
2023-04-27 11:31:00 +08:00
CONFIG[k] = nil
end
end
end
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
2025-01-26 04:12:29 +08:00
-- 取机场信息(剩余流量、到期时间)
local subscribe_info = {}
local function get_subscribe_info(cfgid, value)
if type(cfgid) ~= "string" or cfgid == "" or type(value) ~= "string" then
return
end
value = value:gsub("%s+", "")
local expired_date = value:match("套餐到期:(.+)")
local rem_traffic = value:match("剩余流量:(.+)")
subscribe_info[cfgid] = subscribe_info[cfgid] or {expired_date = "", rem_traffic = ""}
if expired_date then
subscribe_info[cfgid]["expired_date"] = expired_date
end
if rem_traffic then
subscribe_info[cfgid]["rem_traffic"] = rem_traffic
end
end
2023-04-27 11:31:00 +08:00
-- 处理数据
local function processData(szType, content, add_mode, add_from)
--log(content, add_mode, add_from)
local result = {
timeout = 60,
add_mode = add_mode, --0为手动配置,1为导入,2为订阅
add_from = add_from
}
--ssr://base64(host:port:protocol:method:obfs:base64pass/?obfsparam=base64param&protoparam=base64param&remarks=base64remarks&group=base64group&udpport=0&uot=0)
if szType == 'ssr' then
result.type = "SSR"
local dat = split(content, "/%?")
local hostInfo = split(dat[1], ':')
if dat[1]:match('%[(.*)%]') then
result.address = dat[1]:match('%[(.*)%]')
else
result.address = hostInfo[#hostInfo-5]
end
result.port = hostInfo[#hostInfo-4]
result.protocol = hostInfo[#hostInfo-3]
result.method = hostInfo[#hostInfo-2]
result.obfs = hostInfo[#hostInfo-1]
result.password = base64Decode(hostInfo[#hostInfo])
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)
2024-01-11 02:11:32 +08:00
if has_singbox then
result.type = 'sing-box'
end
2023-04-27 11:31:00 +08:00
if has_xray then
result.type = 'Xray'
end
2024-01-11 02:11:32 +08:00
if vmess_type_default == "sing-box" and has_singbox then
2023-09-04 20:16:12 +08:00
result.type = 'sing-box'
end
2024-01-11 02:11:32 +08:00
if vmess_type_default == "xray" and has_xray then
result.type = "Xray"
end
result.alter_id = info.aid
2023-04-27 11:31:00 +08:00
result.address = info.add
result.port = info.port
result.protocol = 'vmess'
2023-09-12 23:31:11 +08:00
result.alter_id = info.aid
2023-04-27 11:31:00 +08:00
result.uuid = info.id
result.remarks = info.ps
-- result.mux = 1
-- result.mux_concurrency = 8
2024-02-01 08:41:48 +08:00
2024-11-20 00:16:51 +08:00
if not info.net then info.net = "tcp" end
info.net = string.lower(info.net)
if result.type == "sing-box" and info.net == "raw" then
2024-02-01 08:41:48 +08:00
info.net = "tcp"
2024-11-20 00:16:51 +08:00
elseif result.type == "Xray" and info.net == "tcp" then
info.net = "raw"
2024-02-01 08:41:48 +08:00
end
2024-12-25 00:15:02 +08:00
if info.net == 'h2' or info.net == 'http' then
info.net = "http"
result.transport = (result.type == "Xray") and "xhttp" or "http"
else
result.transport = info.net
end
2023-04-27 11:31:00 +08:00
if info.net == 'ws' then
result.ws_host = info.host
result.ws_path = info.path
2024-02-27 02:10:59 +08:00
if result.type == "sing-box" and info.path then
local ws_path_dat = split(info.path, "?")
local ws_path = ws_path_dat[1]
local ws_path_params = {}
for _, v in pairs(split(ws_path_dat[2], '&')) do
local t = split(v, '=')
ws_path_params[t[1]] = t[2]
end
if ws_path_params.ed and tonumber(ws_path_params.ed) then
result.ws_path = ws_path
result.ws_enableEarlyData = "1"
result.ws_maxEarlyData = tonumber(ws_path_params.ed)
result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol"
end
end
2023-04-27 11:31:00 +08:00
end
2024-12-25 00:15:02 +08:00
if info.net == "http" then
if result.type == "Xray" then
result.xhttp_mode = "stream-one"
result.xhttp_host = info.host
result.xhttp_path = info.path
else
result.http_host = info.host
result.http_path = info.path
end
2023-04-27 11:31:00 +08:00
end
2024-11-19 16:18:08 +08:00
if info.net == 'raw' or info.net == 'tcp' then
2023-04-27 11:31:00 +08:00
if info.type and info.type ~= "http" then
info.type = "none"
end
result.tcp_guise = info.type
2025-05-04 16:10:11 +08:00
result.tcp_guise_http_host = (info.host and info.host ~= "") and { info.host } or nil
result.tcp_guise_http_path = (info.path and info.path ~= "") and { info.path } or nil
2023-04-27 11:31:00 +08:00
end
if info.net == 'kcp' or info.net == 'mkcp' then
info.net = "mkcp"
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 info.net == 'grpc' then
result.grpc_serviceName = info.path
end
2024-11-19 16:18:08 +08:00
if info.net == 'xhttp' or info.net == 'splithttp' then
result.xhttp_host = info.host
result.xhttp_path = info.path
2024-12-03 20:27:19 +08:00
result.xhttp_mode = params.mode or "auto"
result.xhttp_extra = params.extra
local success, Data = pcall(jsonParse, params.extra)
if success and Data then
local address = (Data.extra and Data.extra.downloadSettings and Data.extra.downloadSettings.address)
or (Data.downloadSettings and Data.downloadSettings.address)
result.download_address = address and address ~= "" and address or nil
else
result.download_address = nil
end
2024-08-25 20:20:08 +08:00
end
2025-01-26 04:12:29 +08:00
if info.net == 'httpupgrade' then
result.httpupgrade_host = info.host
result.httpupgrade_path = info.path
end
2023-04-27 11:31:00 +08:00
if not info.security then result.security = "auto" end
if info.tls == "tls" or info.tls == "1" then
result.tls = "1"
result.tls_serverName = (info.sni and info.sni ~= "") and info.sni or info.host
2025-05-04 16:10:11 +08:00
info.allowinsecure = info.allowinsecure or info.insecure
if info.allowinsecure and (info.allowinsecure == "1" or info.allowinsecure == "0") then
result.tls_allowInsecure = info.allowinsecure
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
2023-04-27 11:31:00 +08:00
else
result.tls = "0"
end
2024-08-25 20:20:08 +08:00
2024-11-20 00:16:51 +08:00
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp" or result.transport == "splithttp") then
2024-08-25 20:20:08 +08:00
log("跳过节点:" .. result.remarks .."因Sing-Box不支持" .. szType .. "协议的" .. result.transport .. "传输方式需更换Xray。")
return nil
end
2023-04-27 11:31:00 +08:00
elseif szType == "ss" then
result.type = "SS"
--SS-URI = "ss://" userinfo "@" hostname ":" port [ "/" ] [ "?" plugin ] [ "#" tag ]
--userinfo = websafe-base64-encode-utf8(method ":" password)
--ss://YWVzLTEyOC1nY206dGVzdA@192.168.100.1:8888#Example1
--ss://cmM0LW1kNTpwYXNzd2Q@192.168.100.1:8888/?plugin=obfs-local%3Bobfs%3Dhttp#Example2
--ss://2022-blake3-aes-256-gcm:YctPZ6U7xPPcU%2Bgp3u%2B0tx%2FtRizJN9K8y%2BuKlW2qjlI%3D@192.168.100.1:8888#Example3
--ss://2022-blake3-aes-256-gcm:YctPZ6U7xPPcU%2Bgp3u%2B0tx%2FtRizJN9K8y%2BuKlW2qjlI%3D@192.168.100.1:8888/?plugin=v2ray-plugin%3Bserver#Example3
2024-08-28 04:13:27 +08:00
--ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTp0ZXN0@xxxxxx.com:443?type=ws&path=%2Ftestpath&host=xxxxxx.com&security=tls&fp=&alpn=h3%2Ch2%2Chttp%2F1.1&sni=xxxxxx.com#test-1%40ss
2023-04-27 11:31:00 +08:00
local idx_sp = 0
local alias = ""
if content:find("#") then
idx_sp = content:find("#")
alias = content:sub(idx_sp + 1, -1)
end
result.remarks = UrlDecode(alias)
2024-08-28 04:13:27 +08:00
local info = content:sub(1, idx_sp - 1):gsub("/%?", "?")
local params = {}
if info:find("?") then
local find_index = info:find("?")
local query = split(info, "?")
2023-04-27 11:31:00 +08:00
for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=')
2024-08-28 04:13:27 +08:00
params[t[1]] = UrlDecode(t[2])
2023-04-27 11:31:00 +08:00
end
if params.plugin then
2024-08-28 04:13:27 +08:00
local plugin_info = params.plugin
2023-04-27 11:31:00 +08:00
local idx_pn = plugin_info:find(";")
if idx_pn then
result.plugin = plugin_info:sub(1, idx_pn - 1)
result.plugin_opts =
plugin_info:sub(idx_pn + 1, #plugin_info)
else
result.plugin = plugin_info
end
end
if result.plugin and result.plugin == "simple-obfs" then
result.plugin = "obfs-local"
end
info = info:sub(1, find_index - 1)
end
2024-12-30 20:24:49 +08:00
local hostInfo = split(base64Decode(UrlDecode(info)), "@")
2023-04-27 11:31:00 +08:00
if hostInfo and #hostInfo > 0 then
local host_port = hostInfo[#hostInfo]
-- [2001:4860:4860::8888]:443
-- 8.8.8.8:443
if host_port:find(":") then
local sp = split(host_port, ":")
result.port = sp[#sp]
if api.is_ipv6addrport(host_port) then
result.address = api.get_ipv6_only(host_port)
else
result.address = sp[1]
end
else
result.address = host_port
end
local userinfo = nil
if #hostInfo > 2 then
userinfo = {}
for i = 1, #hostInfo - 1 do
tinsert(userinfo, hostInfo[i])
end
userinfo = table.concat(userinfo, '@')
else
userinfo = base64Decode(hostInfo[1])
end
local method = userinfo:sub(1, userinfo:find(":") - 1)
local password = userinfo:sub(userinfo:find(":") + 1, #userinfo)
result.method = method
result.password = password
2024-03-14 20:18:45 +08:00
if ss_type_default == "shadowsocks-rust" and has_ss_rust then
result.type = 'SS-Rust'
end
if ss_type_default == "xray" and has_xray then
result.type = 'Xray'
result.protocol = 'shadowsocks'
2024-11-20 00:16:51 +08:00
result.transport = 'raw'
2024-03-14 20:18:45 +08:00
end
if ss_type_default == "sing-box" and has_singbox then
result.type = 'sing-box'
result.protocol = 'shadowsocks'
end
2024-12-31 04:13:42 +08:00
if result.type ~= "Xray" then
result.method = (method:lower() == "chacha20-poly1305" and "chacha20-ietf-poly1305") or
(method:lower() == "xchacha20-poly1305" and "xchacha20-ietf-poly1305") or method
2024-03-14 20:18:45 +08:00
end
if result.plugin then
if result.type == 'Xray' then
2025-05-04 16:10:11 +08:00
-- obfs-local插件转换成xray支持的格式
if result.plugin ~= "obfs-local" then
result.error_msg = "Xray不支持 " .. result.plugin .. " 插件."
else
local obfs = result.plugin_opts:match("obfs=([^;]+)") or ""
local obfs_host = result.plugin_opts:match("obfs%-host=([^;]+)") or ""
if obfs == "" or obfs_host == "" then
result.error_msg = "SS " .. result.plugin .. " 插件选项不完整."
end
if obfs == "http" then
result.transport = "raw"
result.tcp_guise = "http"
result.tcp_guise_http_host = (obfs_host and obfs_host ~= "") and { obfs_host } or nil
elseif obfs == "tls" then
result.tls = "1"
result.tls_serverName = obfs_host
result.tls_allowInsecure = "1"
end
result.plugin = nil
result.plugin_opts = nil
end
2024-03-14 20:18:45 +08:00
end
if result.type == "sing-box" then
result.plugin_enabled = "1"
2023-04-27 11:31:00 +08:00
end
end
2024-03-14 20:18:45 +08:00
if result.type == "SS" then
local aead2022_methods = { "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" }
local aead2022 = false
for k, v in ipairs(aead2022_methods) do
if method:lower() == v:lower() then
aead2022 = true
2023-04-27 11:31:00 +08:00
end
end
2024-03-14 20:18:45 +08:00
if aead2022 then
-- shadowsocks-libev 不支持2022加密
result.error_msg = "shadowsocks-libev 不支持2022加密."
end
2023-04-27 11:31:00 +08:00
end
2024-08-28 04:13:27 +08:00
if params.type then
params.type = string.lower(params.type)
2024-11-20 00:16:51 +08:00
if result.type == "sing-box" and params.type == "raw" then
params.type = "tcp"
elseif result.type == "Xray" and params.type == "tcp" then
params.type = "raw"
end
2024-12-25 00:15:02 +08:00
if params.type == "h2" or params.type == "http" then
params.type = "http"
result.transport = (result.type == "Xray") and "xhttp" or "http"
else
result.transport = params.type
end
2024-08-28 04:13:27 +08:00
if result.type ~= "SS-Rust" and result.type ~= "SS" then
if params.type == 'ws' then
result.ws_host = params.host
result.ws_path = params.path
if result.type == "sing-box" and params.path then
local ws_path_dat = split(params.path, "?")
local ws_path = ws_path_dat[1]
local ws_path_params = {}
for _, v in pairs(split(ws_path_dat[2], '&')) do
local t = split(v, '=')
ws_path_params[t[1]] = t[2]
end
if ws_path_params.ed and tonumber(ws_path_params.ed) then
result.ws_path = ws_path
result.ws_enableEarlyData = "1"
result.ws_maxEarlyData = tonumber(ws_path_params.ed)
result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol"
end
end
end
2024-12-25 00:15:02 +08:00
if params.type == "http" then
2024-08-28 04:13:27 +08:00
if result.type == "sing-box" then
result.transport = "http"
result.http_host = params.host
result.http_path = params.path
2024-12-25 00:15:02 +08:00
elseif result.type == "Xray" then
result.transport = "xhttp"
result.xhttp_mode = "stream-one"
result.xhttp_host = params.host
result.xhttp_path = params.path
2024-08-28 04:13:27 +08:00
end
end
2024-11-19 16:18:08 +08:00
if params.type == 'raw' or params.type == 'tcp' then
2024-08-28 04:13:27 +08:00
result.tcp_guise = params.headerType or "none"
2025-05-04 16:10:11 +08:00
result.tcp_guise_http_host = (params.host and params.host ~= "") and { params.host } or nil
result.tcp_guise_http_path = (params.path and params.path ~= "") and { params.path } or nil
2024-08-28 04:13:27 +08:00
end
if params.type == 'kcp' or params.type == 'mkcp' then
result.transport = "mkcp"
result.mkcp_guise = params.headerType or "none"
result.mkcp_mtu = 1350
result.mkcp_tti = 50
result.mkcp_uplinkCapacity = 5
result.mkcp_downlinkCapacity = 20
result.mkcp_readBufferSize = 2
result.mkcp_writeBufferSize = 2
result.mkcp_seed = params.seed
end
if params.type == 'quic' then
result.quic_guise = params.headerType or "none"
result.quic_key = params.key
result.quic_security = params.quicSecurity or "none"
end
if params.type == 'grpc' then
if params.path then result.grpc_serviceName = params.path end
if params.serviceName then result.grpc_serviceName = params.serviceName end
2024-11-20 00:16:51 +08:00
result.grpc_mode = params.mode or "gun"
2024-08-28 04:13:27 +08:00
end
result.tls = "0"
if params.security == "tls" or params.security == "reality" then
result.tls = "1"
result.tls_serverName = (params.sni and params.sni ~= "") and params.sni or params.host
result.alpn = params.alpn
2025-05-04 16:10:11 +08:00
if params.fp and params.fp ~= "" then
result.utls = "1"
result.fingerprint = params.fp
end
2024-08-28 04:13:27 +08:00
if params.security == "reality" then
result.reality = "1"
result.reality_publicKey = params.pbk or nil
result.reality_shortId = params.sid or nil
result.reality_spiderX = params.spx or nil
end
end
2025-05-04 16:10:11 +08:00
params.allowinsecure = params.allowinsecure or params.insecure
if params.allowinsecure and (params.allowinsecure == "1" or params.allowinsecure == "0") then
result.tls_allowInsecure = params.allowinsecure
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
2024-08-28 04:13:27 +08:00
else
result.error_msg = "请更换Xray或Sing-Box来支持SS更多的传输方式."
end
end
2023-04-27 11:31:00 +08:00
end
elseif szType == "trojan" then
2024-08-12 20:23:45 +08:00
if trojan_type_default == "sing-box" and has_singbox then
result.type = 'sing-box'
elseif trojan_type_default == "xray" and has_xray then
result.type = 'Xray'
end
result.protocol = 'trojan'
2023-04-27 11:31:00 +08:00
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.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 query = split(Info[2], "?")
local host_port = query[1]
local params = {}
for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=')
2024-03-30 04:10:58 +08:00
if #t > 1 then
params[string.lower(t[1])] = UrlDecode(t[2])
end
2023-04-27 11:31:00 +08:00
end
-- [2001:4860:4860::8888]:443
-- 8.8.8.8:443
if host_port:find(":") then
local sp = split(host_port, ":")
port = sp[#sp]
if api.is_ipv6addrport(host_port) then
result.address = api.get_ipv6_only(host_port)
else
result.address = sp[1]
end
else
result.address = host_port
end
local peer, sni = nil, ""
if params.peer then peer = params.peer end
sni = params.sni and params.sni or ""
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
result.port = port
2024-08-12 20:23:45 +08:00
2023-04-27 11:31:00 +08:00
result.tls = '1'
result.tls_serverName = peer and peer or sni
2024-08-12 20:23:45 +08:00
2025-05-04 16:10:11 +08:00
params.allowinsecure = params.allowinsecure or params.insecure
2023-04-27 11:31:00 +08:00
if params.allowinsecure then
if params.allowinsecure == "1" or params.allowinsecure == "0" then
result.tls_allowInsecure = params.allowinsecure
else
result.tls_allowInsecure = string.lower(params.allowinsecure) == "true" and "1" or "0"
end
--log(result.remarks .. ' 使用节点AllowInsecure设定: '.. result.tls_allowInsecure)
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
2024-01-11 02:11:32 +08:00
2024-11-20 00:16:51 +08:00
if not params.type then params.type = "tcp" end
params.type = string.lower(params.type)
if result.type == "sing-box" and params.type == "raw" then
2024-08-12 20:23:45 +08:00
params.type = "tcp"
2024-11-20 00:16:51 +08:00
elseif result.type == "Xray" and params.type == "tcp" then
params.type = "raw"
2024-01-11 02:11:32 +08:00
end
2024-12-25 00:15:02 +08:00
if params.type == "h2" or params.type == "http" then
params.type = "http"
result.transport = (result.type == "Xray") and "xhttp" or "http"
else
result.transport = params.type
end
2024-08-12 20:23:45 +08:00
if params.type == 'ws' then
result.ws_host = params.host
result.ws_path = params.path
if result.type == "sing-box" and params.path then
local ws_path_dat = split(params.path, "?")
local ws_path = ws_path_dat[1]
local ws_path_params = {}
for _, v in pairs(split(ws_path_dat[2], '&')) do
local t = split(v, '=')
ws_path_params[t[1]] = t[2]
end
if ws_path_params.ed and tonumber(ws_path_params.ed) then
result.ws_path = ws_path
result.ws_enableEarlyData = "1"
result.ws_maxEarlyData = tonumber(ws_path_params.ed)
result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol"
end
end
end
2024-12-25 00:15:02 +08:00
if params.type == "http" then
2024-08-12 20:23:45 +08:00
if result.type == "sing-box" then
result.transport = "http"
result.http_host = params.host
result.http_path = params.path
2024-12-25 00:15:02 +08:00
elseif result.type == "Xray" then
result.transport = "xhttp"
result.xhttp_mode = "stream-one"
result.xhttp_host = params.host
result.xhttp_path = params.path
2024-08-12 20:23:45 +08:00
end
end
2024-11-19 16:18:08 +08:00
if params.type == 'raw' or params.type == 'tcp' then
2024-08-12 20:23:45 +08:00
result.tcp_guise = params.headerType or "none"
2025-05-04 16:10:11 +08:00
result.tcp_guise_http_host = (params.host and params.host ~= "") and { params.host } or nil
result.tcp_guise_http_path = (params.path and params.path ~= "") and { params.path } or nil
2024-08-12 20:23:45 +08:00
end
if params.type == 'kcp' or params.type == 'mkcp' then
result.transport = "mkcp"
result.mkcp_guise = params.headerType or "none"
result.mkcp_mtu = 1350
result.mkcp_tti = 50
result.mkcp_uplinkCapacity = 5
result.mkcp_downlinkCapacity = 20
result.mkcp_readBufferSize = 2
result.mkcp_writeBufferSize = 2
result.mkcp_seed = params.seed
end
if params.type == 'quic' then
result.quic_guise = params.headerType or "none"
result.quic_key = params.key
result.quic_security = params.quicSecurity or "none"
end
if params.type == 'grpc' then
if params.path then result.grpc_serviceName = params.path end
if params.serviceName then result.grpc_serviceName = params.serviceName end
2024-11-20 00:16:51 +08:00
result.grpc_mode = params.mode or "gun"
2024-08-12 20:23:45 +08:00
end
2024-11-20 00:16:51 +08:00
if params.type == 'xhttp' or params.type == 'splithttp' then
2024-11-19 16:18:08 +08:00
result.xhttp_host = params.host
result.xhttp_path = params.path
2024-08-25 20:20:08 +08:00
end
2025-01-26 04:12:29 +08:00
if params.type == 'httpupgrade' then
result.httpupgrade_host = params.host
result.httpupgrade_path = params.path
end
2024-08-12 20:23:45 +08:00
result.encryption = params.encryption or "none"
result.flow = params.flow or nil
2024-08-25 20:20:08 +08:00
2024-11-20 00:16:51 +08:00
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp" or result.transport == "splithttp") then
2024-08-25 20:20:08 +08:00
log("跳过节点:" .. result.remarks .."因Sing-Box不支持" .. szType .. "协议的" .. result.transport .. "传输方式需更换Xray。")
return nil
end
2023-04-27 11:31:00 +08:00
end
elseif szType == "ssd" then
result.type = "SS"
result.address = content.server
result.port = content.port
result.password = content.password
result.method = content.encryption
result.plugin = content.plugin
result.plugin_opts = content.plugin_options
result.group = content.airport
result.remarks = content.remarks
elseif szType == "vless" then
2023-09-04 20:16:12 +08:00
if has_singbox then
result.type = 'sing-box'
end
2023-09-10 02:09:19 +08:00
if has_xray then
result.type = 'Xray'
end
2024-01-11 02:11:32 +08:00
if vless_type_default == "sing-box" and has_singbox then
result.type = 'sing-box'
end
if vless_type_default == "xray" and has_xray then
result.type = "Xray"
end
2023-04-27 11:31:00 +08:00
result.protocol = "vless"
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.remarks = UrlDecode(alias)
if content:find("@") then
local Info = split(content, "@")
result.uuid = UrlDecode(Info[1])
local port = "443"
Info[2] = (Info[2] or ""):gsub("/%?", "?")
local query = split(Info[2], "?")
local host_port = query[1]
local params = {}
for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=')
params[t[1]] = UrlDecode(t[2])
end
-- [2001:4860:4860::8888]:443
-- 8.8.8.8:443
if host_port:find(":") then
local sp = split(host_port, ":")
port = sp[#sp]
if api.is_ipv6addrport(host_port) then
result.address = api.get_ipv6_only(host_port)
else
result.address = sp[1]
end
else
result.address = host_port
end
2024-11-20 00:16:51 +08:00
if not params.type then params.type = "tcp" end
params.type = string.lower(params.type)
if result.type == "sing-box" and params.type == "raw" then
2024-02-01 08:41:48 +08:00
params.type = "tcp"
2024-11-20 00:16:51 +08:00
elseif result.type == "Xray" and params.type == "tcp" then
params.type = "raw"
2024-02-01 08:41:48 +08:00
end
2024-12-25 00:15:02 +08:00
if params.type == "h2" or params.type == "http" then
params.type = "http"
result.transport = (result.type == "Xray") and "xhttp" or "http"
else
result.transport = params.type
end
2023-04-27 11:31:00 +08:00
if params.type == 'ws' then
result.ws_host = params.host
result.ws_path = params.path
2024-02-27 02:10:59 +08:00
if result.type == "sing-box" and params.path then
local ws_path_dat = split(params.path, "?")
local ws_path = ws_path_dat[1]
local ws_path_params = {}
for _, v in pairs(split(ws_path_dat[2], '&')) do
local t = split(v, '=')
ws_path_params[t[1]] = t[2]
end
if ws_path_params.ed and tonumber(ws_path_params.ed) then
result.ws_path = ws_path
result.ws_enableEarlyData = "1"
result.ws_maxEarlyData = tonumber(ws_path_params.ed)
result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol"
end
end
2023-04-27 11:31:00 +08:00
end
2024-12-25 00:15:02 +08:00
if params.type == "http" then
2024-08-12 20:23:45 +08:00
if result.type == "sing-box" then
result.transport = "http"
result.http_host = params.host
result.http_path = params.path
2024-12-25 00:15:02 +08:00
elseif result.type == "Xray" then
result.transport = "xhttp"
result.xhttp_mode = "stream-one"
result.xhttp_host = params.host
result.xhttp_path = params.path
2024-08-12 20:23:45 +08:00
end
2023-04-27 11:31:00 +08:00
end
2024-11-19 16:18:08 +08:00
if params.type == 'raw' or params.type == 'tcp' then
2023-04-27 11:31:00 +08:00
result.tcp_guise = params.headerType or "none"
2025-05-04 16:10:11 +08:00
result.tcp_guise_http_host = (params.host and params.host ~= "") and { params.host } or nil
result.tcp_guise_http_path = (params.path and params.path ~= "") and { params.path } or nil
2023-04-27 11:31:00 +08:00
end
if params.type == 'kcp' or params.type == 'mkcp' then
2024-08-12 20:23:45 +08:00
result.transport = "mkcp"
2023-04-27 11:31:00 +08:00
result.mkcp_guise = params.headerType or "none"
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 params.type == 'quic' then
result.quic_guise = params.headerType or "none"
result.quic_key = params.key
result.quic_security = params.quicSecurity or "none"
end
if params.type == 'grpc' then
if params.path then result.grpc_serviceName = params.path end
if params.serviceName then result.grpc_serviceName = params.serviceName end
2024-11-20 00:16:51 +08:00
result.grpc_mode = params.mode or "gun"
2023-04-27 11:31:00 +08:00
end
2024-11-20 00:16:51 +08:00
if params.type == 'xhttp' or params.type == 'splithttp' then
2024-11-19 16:18:08 +08:00
result.xhttp_host = params.host
result.xhttp_path = params.path
2025-01-26 04:12:29 +08:00
result.xhttp_mode = params.mode or "auto"
result.use_xhttp_extra = (params.extra and params.extra ~= "") and "1" or nil
result.xhttp_extra = (params.extra and params.extra ~= "") and params.extra or nil
local success, Data = pcall(jsonParse, params.extra)
if success and Data then
local address = (Data.extra and Data.extra.downloadSettings and Data.extra.downloadSettings.address)
or (Data.downloadSettings and Data.downloadSettings.address)
result.download_address = address and address ~= "" and address or nil
else
result.download_address = nil
end
end
if params.type == 'httpupgrade' then
result.httpupgrade_host = params.host
result.httpupgrade_path = params.path
2024-08-12 20:23:45 +08:00
end
2023-04-27 11:31:00 +08:00
result.encryption = params.encryption or "none"
2023-09-05 02:10:09 +08:00
result.flow = params.flow or nil
2023-04-27 11:31:00 +08:00
result.tls = "0"
if params.security == "tls" or params.security == "reality" then
result.tls = "1"
result.tls_serverName = (params.sni and params.sni ~= "") and params.sni or params.host
2024-08-12 20:23:45 +08:00
result.alpn = params.alpn
2025-05-04 16:10:11 +08:00
if params.fp and params.fp ~= "" then
result.utls = "1"
result.fingerprint = params.fp
end
2023-04-27 11:31:00 +08:00
if params.security == "reality" then
result.reality = "1"
result.reality_publicKey = params.pbk or nil
result.reality_shortId = params.sid or nil
result.reality_spiderX = params.spx or nil
end
end
result.port = port
2025-05-04 16:10:11 +08:00
params.allowinsecure = params.allowinsecure or params.insecure
if params.allowinsecure and (params.allowinsecure == "1" or params.allowinsecure == "0") then
result.tls_allowInsecure = params.allowinsecure
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
2024-08-25 20:20:08 +08:00
2024-11-20 00:16:51 +08:00
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp" or result.transport == "splithttp") then
2024-08-25 20:20:08 +08:00
log("跳过节点:" .. result.remarks .."因Sing-Box不支持" .. szType .. "协议的" .. result.transport .. "传输方式需更换Xray。")
return nil
end
2023-04-27 11:31:00 +08:00
end
elseif szType == 'hysteria' 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.remarks = UrlDecode(alias)
2024-07-18 04:11:57 +08:00
local dat = split(content:gsub("/%?", "?"), '%?')
2023-04-27 11:31:00 +08:00
local host_port = dat[1]
local params = {}
for _, v in pairs(split(dat[2], '&')) do
local t = split(v, '=')
if #t > 0 then
params[t[1]] = t[2]
end
end
-- [2001:4860:4860::8888]:443
-- 8.8.8.8:443
if host_port:find(":") then
local sp = split(host_port, ":")
result.port = sp[#sp]
if api.is_ipv6addrport(host_port) then
result.address = api.get_ipv6_only(host_port)
else
result.address = sp[1]
end
else
result.address = host_port
end
result.protocol = params.protocol
result.hysteria_obfs = params.obfsParam
result.hysteria_auth_type = "string"
result.hysteria_auth_password = params.auth
result.tls_serverName = params.peer
2025-05-04 16:10:11 +08:00
params.allowinsecure = params.allowinsecure or params.insecure
if params.allowinsecure and (params.allowinsecure == "1" or params.allowinsecure == "0") then
result.tls_allowInsecure = params.allowinsecure
--log(result.remarks ..' 使用节点AllowInsecure设定: '.. result.tls_allowInsecure)
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
2023-04-27 11:31:00 +08:00
end
result.hysteria_alpn = params.alpn
result.hysteria_up_mbps = params.upmbps
result.hysteria_down_mbps = params.downmbps
2025-05-15 00:10:07 +08:00
result.hysteria_hop = params.mport
2024-01-11 02:11:32 +08:00
if has_singbox then
result.type = 'sing-box'
result.protocol = "hysteria"
end
elseif szType == 'hysteria2' or szType == 'hy2' 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.remarks = UrlDecode(alias)
2024-01-14 02:10:36 +08:00
local Info = content
if content:find("@") then
local contents = split(content, "@")
result.hysteria2_auth_password = UrlDecode(contents[1])
Info = (contents[2] or ""):gsub("/%?", "?")
end
local query = split(Info, "?")
local host_port = query[1]
2024-01-11 02:11:32 +08:00
local params = {}
2024-01-14 02:10:36 +08:00
for _, v in pairs(split(query[2], '&')) do
2024-01-11 02:11:32 +08:00
local t = split(v, '=')
2024-03-30 04:10:58 +08:00
if #t > 1 then
params[string.lower(t[1])] = UrlDecode(t[2])
end
2024-01-11 02:11:32 +08:00
end
-- [2001:4860:4860::8888]:443
-- 8.8.8.8:443
if host_port:find(":") then
local sp = split(host_port, ":")
result.port = sp[#sp]
if api.is_ipv6addrport(host_port) then
result.address = api.get_ipv6_only(host_port)
else
result.address = sp[1]
end
else
result.address = host_port
end
result.tls_serverName = params.sni
2025-05-04 16:10:11 +08:00
params.allowinsecure = params.allowinsecure or params.insecure
if params.allowinsecure and (params.allowinsecure == "1" or params.allowinsecure == "0") then
result.tls_allowInsecure = params.allowinsecure
2024-01-11 02:11:32 +08:00
--log(result.remarks ..' 使用节点AllowInsecure设定: '.. result.tls_allowInsecure)
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
result.hysteria2_tls_pinSHA256 = params.pinSHA256
2025-04-12 00:17:39 +08:00
result.hysteria2_hop = params.mport
2024-01-11 02:11:32 +08:00
if hysteria2_type_default == "sing-box" and has_singbox then
result.type = 'sing-box'
result.protocol = "hysteria2"
2024-01-14 02:10:36 +08:00
if params["obfs-password"] then
result.hysteria2_obfs_type = "salamander"
result.hysteria2_obfs_password = params["obfs-password"]
end
2025-04-12 00:17:39 +08:00
elseif has_hysteria2 then
result.type = "Hysteria2"
if params["obfs-password"] then
result.hysteria2_obfs = params["obfs-password"]
end
2024-01-11 02:11:32 +08:00
end
2024-08-25 20:20:08 +08:00
elseif szType == 'tuic' 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.remarks = UrlDecode(alias)
local Info = content
if content:find("@") then
local contents = split(content, "@")
if contents[1]:find(":") then
local userinfo = split(contents[1], ":")
result.uuid = UrlDecode(userinfo[1])
result.password = UrlDecode(userinfo[2])
end
Info = (contents[2] or ""):gsub("/%?", "?")
end
local query = split(Info, "?")
local host_port = query[1]
local params = {}
for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=')
if #t > 1 then
params[string.lower(t[1])] = UrlDecode(t[2])
end
end
if host_port:find(":") then
local sp = split(host_port, ":")
result.port = sp[#sp]
if api.is_ipv6addrport(host_port) then
result.address = api.get_ipv6_only(host_port)
else
result.address = sp[1]
end
else
result.address = host_port
end
result.tls_serverName = params.sni
result.tuic_alpn = params.alpn or "default"
result.tuic_congestion_control = params.congestion_control or "cubic"
2025-05-15 00:10:07 +08:00
result.tuic_udp_relay_mode = params.udp_relay_mode or "native"
2025-05-04 16:10:11 +08:00
params.allowinsecure = params.allowinsecure or params.insecure
2024-08-25 20:20:08 +08:00
if params.allowinsecure then
if params.allowinsecure == "1" or params.allowinsecure == "0" then
result.tls_allowInsecure = params.allowinsecure
else
result.tls_allowInsecure = string.lower(params.allowinsecure) == "true" and "1" or "0"
end
--log(result.remarks .. ' 使用节点AllowInsecure设定: '.. result.tls_allowInsecure)
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
result.type = 'sing-box'
result.protocol = "tuic"
2025-05-04 16:10:11 +08:00
elseif szType == "anytls" then
result.type = 'sing-box'
result.protocol = "anytls"
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.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 query = split(Info[2], "?")
local host_port = query[1]
local params = {}
for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=')
params[t[1]] = UrlDecode(t[2])
end
-- [2001:4860:4860::8888]:443
-- 8.8.8.8:443
if host_port:find(":") then
local sp = split(host_port, ":")
port = sp[#sp]
if api.is_ipv6addrport(host_port) then
result.address = api.get_ipv6_only(host_port)
else
result.address = sp[1]
end
else
result.address = host_port
end
result.tls = "0"
if params.security == "tls" or params.security == "reality" then
result.tls = "1"
result.tls_serverName = (params.sni and params.sni ~= "") and params.sni or params.host
result.alpn = params.alpn
if params.fp and params.fp ~= "" then
result.utls = "1"
result.fingerprint = params.fp
end
if params.security == "reality" then
result.reality = "1"
result.reality_publicKey = params.pbk or nil
result.reality_shortId = params.sid or nil
end
end
result.port = port
params.allowinsecure = params.allowinsecure or params.insecure
if params.allowinsecure and (params.allowinsecure == "1" or params.allowinsecure == "0") then
result.tls_allowInsecure = params.allowinsecure
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
local singbox_version = api.get_app_version("sing-box")
local version_ge_1_12 = api.compare_versions(singbox_version:match("[^v]+"), ">=", "1.12.0")
if not has_singbox or not version_ge_1_12 then
log("跳过节点:" .. result.remarks ..",因" .. szType .. "类型的节点需要 Sing-Box 1.12 以上版本支持。")
return nil
end
end
2023-04-27 11:31:00 +08:00
else
log('暂时不支持' .. szType .. "类型的节点订阅,跳过此节点。")
return nil
end
if not result.remarks or 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
2024-12-06 00:18:04 +08:00
local function curl(url, file, ua, mode)
2025-04-01 04:15:51 +08:00
local curl_args = {
"-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3"
}
2024-03-20 00:13:01 +08:00
if ua and ua ~= "" and ua ~= "curl" then
2025-04-01 04:15:51 +08:00
curl_args[#curl_args + 1] = '--user-agent "' .. ua .. '"'
2023-04-27 11:31:00 +08:00
end
2025-04-01 04:15:51 +08:00
local return_code, result
2024-12-06 00:18:04 +08:00
if mode == "direct" then
2025-04-01 04:15:51 +08:00
return_code, result = api.curl_direct(url, file, curl_args)
2024-12-06 00:18:04 +08:00
elseif mode == "proxy" then
2025-04-01 04:15:51 +08:00
return_code, result = api.curl_proxy(url, file, curl_args)
2024-12-06 00:18:04 +08:00
else
2025-04-01 04:15:51 +08:00
return_code, result = api.curl_auto(url, file, curl_args)
2024-12-06 00:18:04 +08:00
end
2025-04-01 04:15:51 +08:00
return tonumber(result)
2023-04-27 11:31:00 +08:00
end
local function truncate_nodes(add_from)
for _, config in pairs(CONFIG) do
2025-01-11 04:14:23 +08:00
if config.currentNodes and #config.currentNodes > 0 then
local newNodes = {}
local removeNodesSet = {}
for k, v in pairs(config.currentNodes) do
if v.currentNode and v.currentNode.add_mode == "2" then
if (not add_from) or (add_from and add_from == v.currentNode.add_from) then
removeNodesSet[v.currentNode[".name"]] = true
end
end
end
for _, value in ipairs(config.currentNodes) do
if not removeNodesSet[value.currentNode[".name"]] then
newNodes[#newNodes + 1] = value.currentNode[".name"]
2023-04-27 11:31:00 +08:00
end
end
2025-01-11 04:14:23 +08:00
if config.set then
config.set(config, newNodes)
end
2023-04-27 11:31:00 +08:00
else
2023-09-03 02:09:17 +08:00
if config.currentNode and config.currentNode.add_mode == "2" then
2025-01-11 04:14:23 +08:00
if (not add_from) or (add_from and add_from == config.currentNode.add_from) then
if config.delete then
config.delete(config)
elseif config.set then
2024-12-19 00:16:57 +08:00
config.set(config, "")
2023-04-27 11:31:00 +08:00
end
end
end
end
end
uci:foreach(appname, "nodes", function(node)
if node.add_mode == "2" then
2025-01-11 04:14:23 +08:00
if (not add_from) or (add_from and add_from == node.add_from) then
2023-04-27 11:31:00 +08:00
uci:delete(appname, node['.name'])
end
end
end)
2025-02-07 16:16:48 +08:00
uci:foreach(appname, "subscribe_list", function(o)
if (not add_from) or add_from == o.remark then
uci:delete(appname, o['.name'], "md5")
end
end)
2025-01-03 00:15:53 +08:00
api.uci_save(uci, appname, true)
2023-04-27 11:31:00 +08:00
end
2025-01-11 04:14:23 +08:00
local function select_node(nodes, config, parentConfig)
2023-04-27 11:31:00 +08:00
if config.currentNode then
2023-09-01 02:10:35 +08:00
local server
-- 特别优先级 cfgid
if config.currentNode[".name"] then
2023-04-27 11:31:00 +08:00
for index, node in pairs(nodes) do
2023-09-01 02:10:35 +08:00
if node[".name"] == config.currentNode[".name"] then
log('更新【' .. config.remarks .. '】匹配节点:' .. node.remarks)
2023-04-27 11:31:00 +08:00
server = node[".name"]
break
end
end
end
-- 第一优先级 类型 + 备注 + IP + 端口
if not server then
for index, node in pairs(nodes) do
if config.currentNode.type and config.currentNode.remarks and config.currentNode.address and config.currentNode.port then
if node.type and node.remarks and node.address and node.port then
if node.type == config.currentNode.type and node.remarks == config.currentNode.remarks and (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 = node[".name"]
break
end
end
end
end
end
-- 第二优先级 类型 + IP + 端口
if not server then
for index, node in pairs(nodes) do
if config.currentNode.type and config.currentNode.address and config.currentNode.port then
if node.type and node.address and node.port then
if node.type == config.currentNode.type and (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 = node[".name"]
break
end
end
end
end
end
-- 第三优先级 IP + 端口
if not server then
for index, node in pairs(nodes) do
if config.currentNode.address and config.currentNode.port then
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 = node[".name"]
break
end
end
end
end
end
-- 第四优先级 IP
if not server then
for index, node in pairs(nodes) do
if config.currentNode.address then
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 = node[".name"]
break
end
end
end
end
end
-- 第五优先级备注
if not server then
for index, node in pairs(nodes) do
if config.currentNode.remarks then
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 = node[".name"]
break
end
end
end
end
end
2025-01-11 04:14:23 +08:00
if not parentConfig then
-- 还不行 随便找一个
if not server then
if #nodes_table > 0 then
if config.log == nil or config.log == true then
log('' .. config.remarks .. '' .. '无法找到最匹配的节点,当前已更换为:' .. nodes_table[1].remarks)
end
server = nodes_table[1][".name"]
2023-09-01 02:10:35 +08:00
end
2023-04-27 11:31:00 +08:00
end
end
2023-09-01 02:10:35 +08:00
if server then
2025-01-11 04:14:23 +08:00
if parentConfig then
config.set(parentConfig, server)
else
config.set(config, server)
end
2023-09-01 02:10:35 +08:00
end
else
2025-01-11 04:14:23 +08:00
if not parentConfig then
config.set(config, "")
end
2023-04-27 11:31:00 +08:00
end
end
local function update_node(manual)
if next(nodeResult) == nil then
2025-01-11 04:14:23 +08:00
log("没有可用的节点信息更新。")
2023-04-27 11:31:00 +08:00
return
end
local group = {}
for _, v in ipairs(nodeResult) do
group[v["remark"]] = true
end
if manual == 0 and next(group) then
uci:foreach(appname, "nodes", function(node)
-- 如果未发现新节点或手动导入的节点就不要删除了...
if node.add_mode == "2" and (node.add_from and group[node.add_from] == true) then
uci:delete(appname, node['.name'])
end
end)
end
for _, v in ipairs(nodeResult) do
local remark = v["remark"]
local list = v["list"]
for _, vv in ipairs(list) do
2025-01-03 20:24:43 +08:00
local cfgid = uci:section(appname, "nodes", api.gen_short_uuid())
2023-04-27 11:31:00 +08:00
for kkk, vvv in pairs(vv) do
2025-05-04 16:10:11 +08:00
if type(vvv) == "table" and next(vvv) ~= nil then
uci:set_list(appname, cfgid, kkk, vvv)
else
uci:set(appname, cfgid, kkk, vvv)
-- sing-box 域名解析策略
if kkk == "type" and vvv == "sing-box" then
uci:set(appname, cfgid, "domain_strategy", domain_strategy_node)
end
2024-11-19 16:18:08 +08:00
end
2023-04-27 11:31:00 +08:00
end
end
end
2025-01-26 04:12:29 +08:00
-- 更新机场信息
for cfgid, info in pairs(subscribe_info) do
for key, value in pairs(info) do
if value ~= "" then
uci:set(appname, cfgid, key, value)
else
uci:delete(appname, cfgid, key)
end
end
end
2025-01-03 00:15:53 +08:00
api.uci_save(uci, appname, true)
2023-04-27 11:31:00 +08:00
if next(CONFIG) then
local nodes = {}
uci:foreach(appname, "nodes", function(node)
nodes[#nodes + 1] = node
end)
for _, config in pairs(CONFIG) do
2025-01-11 04:14:23 +08:00
if config.currentNodes and #config.currentNodes > 0 then
for kk, vv in pairs(config.currentNodes) do
select_node(nodes, vv, config)
2023-04-27 11:31:00 +08:00
end
config.set(config)
else
select_node(nodes, config)
end
end
2025-01-03 00:15:53 +08:00
api.uci_save(uci, appname, true)
2023-04-27 11:31:00 +08:00
end
2024-07-18 04:11:57 +08:00
if arg[3] == "cron" then
2025-01-03 00:15:53 +08:00
if not fs.access("/var/lock/" .. appname .. ".lock") then
2024-07-18 04:11:57 +08:00
luci.sys.call("touch /tmp/lock/" .. appname .. "_cron.lock")
end
end
2023-04-27 11:31:00 +08:00
luci.sys.call("/etc/init.d/" .. appname .. " restart > /dev/null 2>&1 &")
end
2025-01-26 04:12:29 +08:00
local function parse_link(raw, add_mode, add_from, cfgid)
2023-04-27 11:31:00 +08:00
if raw and #raw > 0 then
local nodes, szType
local node_list = {}
-- 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 外的格式
if add_mode == "1" then
nodes = split(raw:gsub(" ", "\n"), "\n")
else
nodes = split(base64Decode(raw):gsub(" ", "\n"), "\n")
end
end
for _, v in ipairs(nodes) do
2025-04-12 00:17:39 +08:00
if v and not string.match(v, "^%s*$") then
2024-02-26 02:09:55 +08:00
xpcall(function ()
local result
if szType == 'ssd' then
result = processData(szType, v, add_mode, add_from)
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' then
result = processData(dat[1], dat[2], add_mode, add_from)
else
result = processData(dat[1], base64Decode(dat[2]), add_mode, add_from)
end
2023-04-27 11:31:00 +08:00
end
else
2024-02-26 02:09:55 +08:00
log('跳过未知类型: ' .. szType)
end
-- log(result)
if result then
2024-03-14 20:18:45 +08:00
if result.error_msg then
log('丢弃节点: ' .. result.remarks .. ", 原因:" .. result.error_msg)
elseif not result.type then
log('丢弃节点: ' .. result.remarks .. ", 找不到可使用二进制.")
2024-02-26 02:09:55 +08:00
elseif (add_mode == "2" and is_filter_keyword(result.remarks)) or not result.address or result.remarks == "NULL" or result.address == "127.0.0.1" or
(not datatypes.hostname(result.address) and not (api.is_ip(result.address))) then
log('丢弃过滤节点: ' .. result.type .. ' 节点, ' .. result.remarks)
else
tinsert(node_list, result)
end
2025-01-26 04:12:29 +08:00
if add_mode == "2" then
get_subscribe_info(cfgid, result.remarks)
end
2023-04-27 11:31:00 +08:00
end
2024-03-30 04:10:58 +08:00
end, function (err)
--log(err)
2024-02-26 02:09:55 +08:00
log(v, "解析错误,跳过此节点。")
2023-04-27 11:31:00 +08:00
end
2024-02-26 02:09:55 +08:00
)
2023-04-27 11:31:00 +08:00
end
end
if #node_list > 0 then
nodeResult[#nodeResult + 1] = {
remark = add_from,
list = node_list
}
end
log('成功解析【' .. add_from .. '】节点数量: ' .. #node_list)
else
if add_mode == "2" then
2025-04-01 04:15:51 +08:00
log('获取到的【' .. add_from .. '】订阅内容为空,可能是订阅地址无效,或是网络问题,请诊断!')
2023-04-27 11:31:00 +08:00
end
end
end
local execute = function()
do
local subscribe_list = {}
local fail_list = {}
if arg[2] then
string.gsub(arg[2], '[^' .. "," .. ']+', function(w)
subscribe_list[#subscribe_list + 1] = uci:get_all(appname, w) or {}
end)
else
uci:foreach(appname, "subscribe_list", function(o)
subscribe_list[#subscribe_list + 1] = o
end)
end
for index, value in ipairs(subscribe_list) do
local cfgid = value[".name"]
local remark = value.remark
local url = value.url
if value.allowInsecure and value.allowInsecure ~= "1" then
allowInsecure_default = nil
end
local filter_keyword_mode = value.filter_keyword_mode or "5"
if filter_keyword_mode == "0" then
filter_keyword_mode_default = "0"
elseif filter_keyword_mode == "1" then
filter_keyword_mode_default = "1"
filter_keyword_discard_list_default = value.filter_discard_list or {}
elseif filter_keyword_mode == "2" then
filter_keyword_mode_default = "2"
filter_keyword_keep_list_default = value.filter_keep_list or {}
elseif filter_keyword_mode == "3" then
filter_keyword_mode_default = "3"
filter_keyword_keep_list_default = value.filter_keep_list or {}
filter_keyword_discard_list_default = value.filter_discard_list or {}
elseif filter_keyword_mode == "4" then
filter_keyword_mode_default = "4"
filter_keyword_keep_list_default = value.filter_keep_list or {}
filter_keyword_discard_list_default = value.filter_discard_list or {}
end
2024-03-14 20:18:45 +08:00
local ss_type = value.ss_type or "global"
if ss_type ~= "global" then
ss_type_default = ss_type
2023-04-27 11:31:00 +08:00
end
2024-01-11 02:11:32 +08:00
local trojan_type = value.trojan_type or "global"
if trojan_type ~= "global" then
trojan_type_default = trojan_type
end
local vmess_type = value.vmess_type or "global"
if vmess_type ~= "global" then
vmess_type_default = vmess_type
end
local vless_type = value.vless_type or "global"
if vless_type ~= "global" then
vless_type_default = vless_type
end
local hysteria2_type = value.hysteria2_type or "global"
if hysteria2_type ~= "global" then
hysteria2_type_default = hysteria2_type
end
2024-11-19 16:18:08 +08:00
local domain_strategy = value.domain_strategy or "global"
if domain_strategy ~= "global" then
domain_strategy_node = domain_strategy
else
domain_strategy_node = domain_strategy_default
end
2023-04-27 11:31:00 +08:00
local ua = value.user_agent
2024-12-06 00:18:04 +08:00
local access_mode = value.access_mode
local result = (not access_mode) and "自动" or (access_mode == "direct" and "直连访问" or (access_mode == "proxy" and "通过代理" or "自动"))
log('正在订阅:【' .. remark .. '' .. url .. ' [' .. result .. ']')
2025-02-06 00:15:46 +08:00
local tmp_file = "/tmp/" .. cfgid
2025-04-01 04:15:51 +08:00
value.http_code = curl(url, tmp_file, ua, access_mode)
if value.http_code ~= 200 then
fail_list[#fail_list + 1] = value
else
if luci.sys.call("[ -f " .. tmp_file .. " ] && sed -i -e '/^[ \t]*$/d' -e '/^[ \t]*\r$/d' " .. tmp_file) == 0 then
local f = io.open(tmp_file, "r")
local stdout = f:read("*all")
f:close()
local raw_data = trim(stdout)
local old_md5 = value.md5 or ""
local new_md5 = luci.sys.exec("md5sum " .. tmp_file .. " 2>/dev/null | awk '{print $1}'"):gsub("\n", "")
os.remove(tmp_file)
if old_md5 == new_md5 then
log('订阅:【' .. remark .. '】没有变化,无需更新。')
else
parse_link(raw_data, "2", remark, cfgid)
uci:set(appname, cfgid, "md5", new_md5)
end
2025-01-11 04:14:23 +08:00
else
2025-04-01 04:15:51 +08:00
fail_list[#fail_list + 1] = value
2025-01-11 04:14:23 +08:00
end
2023-04-27 11:31:00 +08:00
end
allowInsecure_default = true
filter_keyword_mode_default = uci:get(appname, "@global_subscribe[0]", "filter_keyword_mode") or "0"
filter_keyword_discard_list_default = uci:get(appname, "@global_subscribe[0]", "filter_discard_list") or {}
filter_keyword_keep_list_default = uci:get(appname, "@global_subscribe[0]", "filter_keep_list") or {}
2024-03-14 20:18:45 +08:00
ss_type_default = uci:get(appname, "@global_subscribe[0]", "ss_type") or "shadowsocks-libev"
2024-01-11 02:11:32 +08:00
trojan_type_default = uci:get(appname, "@global_subscribe[0]", "trojan_type") or "sing-box"
vmess_type_default = uci:get(appname, "@global_subscribe[0]", "vmess_type") or "xray"
vless_type_default = uci:get(appname, "@global_subscribe[0]", "vless_type") or "xray"
hysteria2_type_default = uci:get(appname, "@global_subscribe[0]", "hysteria2_type") or "hysteria2"
2023-04-27 11:31:00 +08:00
end
if #fail_list > 0 then
for index, value in ipairs(fail_list) do
2025-04-01 04:15:51 +08:00
log(string.format('【%s】订阅失败可能是订阅地址无效或是网络问题请诊断[%s]', value.remark, tostring(value.http_code)))
2023-04-27 11:31:00 +08:00
end
end
update_node(0)
end
end
if arg[1] then
if arg[1] == "start" then
log('开始订阅...')
xpcall(execute, function(e)
log(e)
log(debug.traceback())
log('发生错误, 正在恢复服务')
end)
log('订阅完毕...')
elseif arg[1] == "add" then
local f = assert(io.open("/tmp/links.conf", 'r'))
2025-04-12 00:17:39 +08:00
local raw = f:read('*all')
2023-04-27 11:31:00 +08:00
f:close()
2025-04-12 00:17:39 +08:00
parse_link(raw, "1", "导入")
2023-04-27 11:31:00 +08:00
update_node(1)
luci.sys.call("rm -f /tmp/links.conf")
elseif arg[1] == "truncate" then
truncate_nodes(arg[2])
end
end