#!/usr/bin/lua
------------------------------------------------
-- This file is part of the luci-app-bypass subscribe.lua
-- @author William Chan <root@williamchan.me>
-- @sirpdboy 2025 <herboy2008@gmail.com>
------------------------------------------------
require "luci.model.uci"
require "nixio"
require "luci.util"
SYS=require "luci.sys"
require "luci.jsonc"
local datatypes = require "luci.cbi.datatypes"
require "luci.model.ipkg"
-- 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
local jsonParse, jsonStringify = luci.jsonc.parse, luci.jsonc.stringify
local b64decode = nixio.bin.b64decode
local URL = require "url"
local cache = {}
local nodeResult = setmetatable({}, {__index = cache}) -- update result
local name = 'bypass'
local uciType = 'servers'
local ucic = luci.model.uci.cursor()
local proxy = ucic:get_first(name, 'server_subscribe', 'proxy', '0')
local switch = ucic:get_first(name, 'server_subscribe', 'switch', '0')
local subscribe_url = ucic:get_first(name, 'server_subscribe', 'subscribe_url', {})
local filter_words = ucic:get_first(name, 'server_subscribe', 'filter_words', '过期/时间/剩余/流量')
local filter_mode=ucic:get_first(name,'server_subscribe','filter_mode','0')
local insecure = ucic:get_first(name, 'server_subscribe', 'insecure', 1)
local v2_ss = SYS.exec('type -t -p ss-redir sslocal') ~= "" and "ss" or "v2ray"

local v2_tj = SYS.exec('type -t -p trojan') ~= "" and "trojan" or "v2ray"
local ua = ucic:get_first(name, 'server_subscribe', 'user_agent', '')
local log = function(...)
	local file=io.open("/tmp/log/bypass.log","a")
	io.output(file)
	io.write(os.date("%Y-%m-%d %H:%M:%S ").."Subscribe : "..table.concat({...}," ").."\n")
	io.close(file)
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
-- md5
local function md5(content)
	local stdout = SYS.exec('echo \"' .. urlEncode(content) .. '\" | md5sum | cut -d \" \" -f1')
	-- assert(nixio.errno() == 0)
	return trim(stdout)
end
-- base64
local function base64Decode(text)
	local raw = text
	if not text then return '' end
	text = text:gsub("%z", "")
	text = text:gsub("%c", "")
	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
-- 检查数组(table)中是否存在某个字符值
-- https://www.04007.cn/article/135.html
local function checkTabValue(tab)
	local revtab = {}
	for k,v in pairs(tab) do
		revtab[v] = true
	end
	return revtab
end
-- 处理数据
local function processData(szType, content)
	local result = {type = szType, local_port = 1234, kcp_param = '--nocomp'}
	if szType == 'ssr' then

		result.type = 'ssr'
		local dat = split(content, "/%?")
		local hostInfo = split(dat[1], ':')
		if dat[1]:match('%[(.*)%]') then
			result.server = dat[1]:match('%[(.*)%]')
		else
			result.server = hostInfo[#hostInfo-5]
		end
		result.server_port = hostInfo[#hostInfo-4]
		result.protocol = hostInfo[#hostInfo-3]
		result.encrypt_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.alias = "[" .. group .. "] "
		end
		result.alias = result.alias .. base64Decode(params.remarks)
	elseif szType == 'vmess' then
		local info = jsonParse(content)
		result.type = 'v2ray'



		result.alter_id = info.aid

		result.server = info.add
		result.server_port = info.port
		result.v2ray_protocol = 'vmess'
		result.vmess_id = info.id
		result.alias = info.ps
		-- result.mux = 1
		-- result.concurrency = 8
		if not info.net then
			info.net = "tcp"
		end
		info.net = string.lower(info.net)
		result.transport = info.net
		if info.net == 'ws' then
			result.ws_host = info.host
			result.ws_path = info.path
		end
		if info.net == 'httpupgrade' then
			result.httpupgrade_host = info.host
			result.httpupgrade_path = info.path
		end
		if info.net == 'splithttp' then
			result.splithttp_host = info.host
			result.splithttp_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.http_host = info.host
			result.http_path = info.path
		end
		if info.net == 'kcp' or info.net == 'mkcp' then
			result.kcp_guise = info.type
			result.mtu = 1350
			result.tti = 50
			result.uplink_capacity = 5
			result.downlink_capacity = 20
			result.read_buffer_size = 2
			result.write_buffer_size = 2
		end

		if info.net == 'grpc' then
			if info.path then
				result.serviceName = info.path
			elseif info.serviceName then
				result.serviceName = info.serviceName
			end
		end
		if info.net == 'quic' then
			result.quic_guise = info.type
			result.quic_key = info.key
			result.quic_security = info.securty
		end
		if info.security then
			result.security = info.security
		else
			result.security = "auto"
		end
		if info.tls == "tls" or info.tls == "1" then
			result.tls = "1"
			result.tls_host = (info.sni and info.sni ~= "") and info.sni or info.host
			result.insecure = insecure and "1" or "0"
		else
			result.tls = "0"
		end
		-- https://www.v2fly.org/config/protocols/vmess.html#vmess-md5-认证信息-淘汰机制
		-- if info.aid and (tonumber(info.aid) > 0) then
		-- 	result.server = nil
		-- end
	elseif szType == "ss" then

	        result.type = v2_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

		local idx_sp = 0
		local alias = ""
		if content:find("#") then
			idx_sp = content:find("#")
			alias = content:sub(idx_sp + 1, -1)
		end
		result.alias = UrlDecode(alias)
		local info = content:sub(1, idx_sp - 1)
		if info:find("/%?") then
			local find_index = info:find("/%?")
			local query = split(info, "/%?")
			local params = {}
			for _, v in pairs(split(query[2], '&')) do
				local t = split(v, '=')
				params[t[1]] = t[2]
			end
			if params.plugin then
				local plugin_info = UrlDecode(params.plugin)
				local idx_pn = plugin_info:find(";")
				if idx_pn then
					result.plugin = plugin_info:sub(1, idx_pn - 1)
					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

		local hostInfo = split(base64Decode(info), "@")
		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.server_port = sp[#sp]

					result.server = sp[1]

			else
				result.server = 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.v2ray_protocol = (v2_ss == "v2ray") and "shadowsocks" or nil
			result.encrypt_method_ss = method
			result.password = password

			local aead = false
			for k, v in ipairs({"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305"}) do
				if method:lower() == v:lower() then
					aead = true
				end
			end
			if aead then

					result.type = v2_ss
					result.v2ray_protocol = 'shadowsocks'
					result.transport = 'tcp'
					if method:lower() == "chacha20-ietf-poly1305" then
						result.method = "chacha20-poly1305"
					end

			end
			local aead2022 = false
			for k, v in ipairs({"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"}) do
				if method:lower() == v:lower() then
					aead2022 = true
				end
			end
			if aead2022 then
					result.type = v2_ss
					result.v2ray_protocol = 'shadowsocks'
					result.transport = 'tcp'
			end
		end
	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.alias = UrlDecode(alias)
		result.type = "trojan"
		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[string.lower(t[1])] = UrlDecode(t[2])
			end
			if host_port:find(":") then
				local sp = split(host_port, ":")
				port = sp[#sp]
				result.server = sp[1]
			else
				result.server = host_port
			end

			local peer, sni = nil, ""
			if params.peer then peer = params.peer end
			sni = params.sni and params.sni or ""
			result.server_port = port
			result.tls = '1'
			result.tls_host = peer and peer or sni
			if params.allowinsecure then
				if params.allowinsecure == "1" or params.allowinsecure == "0" then
					result.insecure = params.allowinsecure
				else
					result.insecure = string.lower(params.allowinsecure) == "true" and "1" or "0"
				end
				--log(result.alias .. ' 使用节点AllowInsecure设定: '.. result.insecure)
			else
	    	                 -- 按照官方的建议 默认验证ssl证书
		                 result.insecure = insecure and "1" or "0"
			end
		end

	elseif szType == "ssd" then
		result.type = v2_ss
		result.v2ray_protocol = (v2_ss == "v2ray") and "shadowsocks" or nil
		result.server = content.server
		result.server_port = content.port
		result.password = content.password
		result.encrypt_method_ss = content.encryption
		result.plugin = content.plugin
		result.plugin_opts = content.plugin_options
		result.alias = "[" .. content.airport .. "] " .. content.remarks
		if content.plugin == "simple-obfs" then
			result.plugin = "obfs-local"
		else
			result.plugin = content.plugin
		end
		
	elseif szType == "vless" then
		result.type = "v2ray"
		result.v2ray_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.alias = UrlDecode(alias)
		if content:find("@") then
			local Info = split(content, "@")
			result.vmess_id = 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]
				result.server = sp[1]
			else
				result.server = host_port
			end

			if not params.type then
				params.type = "tcp"
			end
			params.type = string.lower(params.type)
			result.transport = params.type
			if params.type == 'ws' then

				result.ws_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
				result.ws_path = params.path and UrlDecode(params.path) or "/"
			elseif result.transport == "httpupgrade" then
				result.httpupgrade_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
				result.httpupgrade_path = params.path and UrlDecode(params.path) or "/"
			elseif result.transport == "splithttp" then
				result.splithttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
				result.splithttp_path = params.path and UrlDecode(params.path) or "/"
			-- make it compatible with bullshit, "h2" transport is non-existent at all
		        elseif result.transport == "http" or result.transport == "h2" then
			
				result.transport = "h2"
				result.h2_host = params.host and UrlDecode(params.host) or nil
				result.h2_path = params.path and UrlDecode(params.path) or nil
			elseif params.type == 'tcp' then
				result.tcp_guise = params.headerType or "none"
				if result.tcp_guise == "http" then
				result.tcp_host = params.host and UrlDecode(params.host) or nil
				result.tcp_path = params.path and UrlDecode(params.path) or nil
			       end
			elseif params.type == 'kcp' or params.type == 'mkcp' then
				params.type = "kcp"
				result.kcp_guise = params.headerType or "none"
				result.mtu = 1350
				result.tti = 50
				result.uplink_capacity = 5
				result.downlink_capacity = 20
				result.read_buffer_size = 2
				result.write_buffer_size = 2
				result.seed = params.seed
			elseif params.type == 'quic' then
				result.quic_guise = params.headerType or "none"
				result.quic_key = params.key
				result.quic_security = params.quicSecurity or "none"
			elseif params.type == 'grpc' then
				if params.path then result.serviceName = params.path end
				if params.serviceName then result.serviceName = params.serviceName end
				result.grpc_mode = params.mode
			end
			
			result.vless_encryption = params.encryption or "none"

			result.tls_flow = params.flow or nil

			result.tls = "0"
			if params.security == "tls" or params.security == "reality" then
				result.tls = "1"
				result.tls_host = (params.sni and params.sni ~= "") and params.sni or params.host
				result.fingerprint = (params.fp and params.fp ~= "") and params.fp or "chrome"
				if params.security == "reality" then
					result.reality = "1"
					result.tls = "0"
					result.reality_publickey = params.pbk or nil
					result.reality_shortId = params.sid or nil
					result.reality_spiderX = params.spx or nil
				end
			end
			result.xtls = (params.security == "xtls") and "1" or nil
			result.tls_flow = (result.tls == "1" or result.xtls == "1" or result.reality == "1") and params.flow or nil

			result.server_port = port
			result.insecure = insecure and "1" or "0"
		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.alias = UrlDecode(alias)
		local dat = split(content, '%?')
		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.server_port = sp[#sp]
			result.server = sp[1]
		else
			result.server = host_port
		end
		result.hy_obfs_password = params.obfsParam
		result.hy_auth_type = "string"
		result.hy_auth_password = params.auth
		result.tls_host = params.peer
		if params.insecure and (params.insecure == "1" or params.insecure == "0") then
			result.insecure = params.insecure
		else
			result.insecure = insecure and "1" or "0"
		end
		result.uplink_capacity = params.upmbps
		result.downlink_capacity = params.downmbps
		result.type = 'hysteria'
	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.alias = UrlDecode(alias)
		local Info = content
		if content:find("@") then
			local contents = split(content, "@")
			result.hy2_auth_password = UrlDecode(contents[1])
			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, '=')
			params[string.lower(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, ":")
			result.server_port = sp[#sp]
			result.server = sp[1]
		else
			result.server = host_port
		end
		result.tls_host = params.sni
		if params.insecure and (params.insecure == "1" or params.insecure == "0") then
			result.insecure = params.insecure
		else
			result.insecure = insecure and "1" or "0"
		end
		result.pinSHA256 = params.pinSHA256
		result.type = "hysteria2"
		if params["obfs-password"] then
			result.hy2_obfs_type = "salamander"
			result.hy2_obfs_password = params["obfs-password"]
		end
	else
		log('暂时不支持' .. szType .. "类型的节点订阅，跳过此节点。")
		return nil
	end
	if not result.alias then
		if result.server and result.server_port then
			result.alias = result.server .. ':' .. result.server_port
		else
			result.alias = "NULL"
		end
	end
	-- alias 不参与 hashkey 计算
	local alias = result.alias
	result.alias = nil
	local switch_enable = result.switch_enable
	result.switch_enable = nil
	result.hashkey = md5(jsonStringify(result))
	result.alias = alias
	result.switch_enable = switch_enable
	return result
end

local function curl(url)
	if not ua or ua == "" then
		ua = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36"
	end
	local stdout=SYS.exec('curl --retry 3 -m 10 -LfsA "'..ua..'" "'..url..'"')
	return trim(stdout)
end

local function check_filer(result)
	local filter_word=split(filter_words,"/")
	for i,v in pairs(filter_word) do
		if result.alias:find(v) then
			return filter_mode=='0'
		end
	end
	return filter_mode=='1'
end

local execute = function()
	-- exec
	do
		local i=0
		if proxy=='0' then
			log('服务正在暂停')
			SYS.init.stop(name)
		end
		log('--------------------开始订阅---------------------')
		for k,url in ipairs(subscribe_url) do
			i=i+1
			local raw=curl(url)
			if #raw > 0 then
				if i ~= 1 then
				     log('--------------------------------------------------')
				end
				log('解析第'..i..'个链接: *')
				local nodes, szType
				local num=0
				local groupHash = md5(url)
				cache[groupHash] = {}
				tinsert(nodeResult, {})
				local index = #nodeResult
				-- SSD 似乎是这种格式 ssd:// 开头的
				if raw:find('ssd://') then
					szType = 'ssd'
					local nEnd = select(2, raw:find('ssd://'))
					nodes = base64Decode(raw:sub(nEnd + 1, #raw))
					nodes = jsonParse(nodes)
					local extra={airport=nodes.airport,port=nodes.port,encryption=nodes.encryption,password=nodes.password}
					local servers = {}
					-- SS里面包着 干脆直接这样
					for _, server in ipairs(nodes.servers) do
						tinsert(servers, setmetatable(server, {__index = extra}))
					end
					nodes = servers
				else
					-- ssd 外的格式
					nodes = split(base64Decode(raw):gsub(" ", "\n"), "\n")
				end
				for _, v in ipairs(nodes) do
					if v then
				        xpcall(function ()
						local result
						if szType == 'ssd' then
							result = processData(szType, v)
						elseif not szType then
							local node = trim(v)
							local dat = split(node, "://")
							if dat and dat[1] and dat[2] then
								local dat3 = ""
								if dat[3] then
									dat3 = "://" .. dat[3]
								end
								if dat[1] == 'ss' or dat[1] == 'trojan' then
									result = processData(dat[1], dat[2] .. dat3)
								else
									result = processData(dat[1], base64Decode(dat[2]))
								end
							end
						else
							log('跳过未知类型: ' .. szType)
						end
						-- log(result)
						if result then
							-- 中文做地址的 也没有人拿中文域名搞，就算中文域也有Puny Code SB 机场
							if not result.type then
							 	log('丢弃节点:' .. result.alias .. ",找不到可使用二进制.")
							elseif not result.server or  result.alias == "NULL" or check_filer(result)  then
								num=num+1
								log('丢弃过滤节点: ' .. result.type .. ' 节点: ' .. result.alias )
							else
								-- log('成功解析: ' .. result.type ..' 节点: ' .. result.alias)
								result.grouphashkey = groupHash
								tinsert(nodeResult[index], result)
								cache[groupHash][result.hashkey] = nodeResult[index][#nodeResult[index]]
							end
						else
							num=num+1
						end
				           end, function ()
				           	log(v, "解析错误，跳过此节点。")
				           end
				       )
				       end
				end
				log('第'..i..'个链接成功解析节点数量: '..#nodes-num)
			else
				log('第'..i..'个链接获取内容为空，可能是订阅地址失效或是网络问题')
			end
		end
	end
	-- diff
	do
		if next(nodeResult) == nil then
			log("更新失败，没有可用的节点信息")
			if proxy == '0' then
				log('订阅失败, 恢复服务')
				SYS.init.start(name)
			end
			return
		end
		local add, del = 0, 0
		ucic:foreach(name, uciType, function(old)
			if old.grouphashkey or old.hashkey then -- 没有 hash 的不参与删除
				if not nodeResult[old.grouphashkey] or not nodeResult[old.grouphashkey][old.hashkey] then
					ucic:delete(name, old['.name'])
					del = del + 1
				else
					local dat = nodeResult[old.grouphashkey][old.hashkey]
					ucic:tset(name, old['.name'], dat)
					-- 标记一下
					setmetatable(nodeResult[old.grouphashkey][old.hashkey], {__index = {_ignore = true}})
				end
			else
				if not old.alias then
					if old.server or old.server_port then
						old.alias = old.server .. ':' .. old.server_port
						log('忽略手动添加的节点: ' .. old.alias)
					else
						ucic:delete(name, old['.name'])
					end
				else
					log('忽略手动添加的节点: ' .. old.alias)
				end
			end
		end)
		for k, v in ipairs(nodeResult) do
			-- 如果订阅节点中有相同的节点信息 需要先去重。
			for kk, vv in ipairs(v) do
				if not vv._ignore then
					local section = ucic:add(name, uciType)
					ucic:tset(name, section, vv)
					ucic:set(name, section, "switch_enable", switch)
					add = add + 1
				end
			end
		end
		log('--------------------------------------------------')
		log('新增节点数量: ' .. add, '删除节点数量: ' .. del)
		local globalServer = ucic:get_first(name, 'global', 'global_server', '')
		if globalServer ~= "nil" then
			local firstServer = ucic:get_first(name, uciType)
			if firstServer then
			        local nowserver=ucic:get(name, globalServer, 'alias', '')
				if not ucic:get(name, globalServer) then
					ucic:set(name, ucic:get_first(name, 'global'), 'global_server', ucic:get_first(name, uciType))
					ucic:commit(name)
					log('当前主服务器节点为空，自动更换到第一个节点。')
				else
					log('维持当前主服务器节点： '..ucic:get(name, globalServer, 'alias', ''))
				end
			else
				ucic:delete(name,ucic:get_first(name,'global'),'global_server')
				ucic:delete(name,ucic:get_first(name,'global'),'udp_relay_server')
				ucic:delete(name,ucic:get_first(name,'global'),'nf_server')
				ucic:commit(name)
				log('没有服务器节点了，停止服务')
				SYS.exec("/etc/init.d/" .. name .. " stop > /dev/null 2>&1 &")
			end
		end
		ucic:commit(name)

		if  globalServer == "nil" then
			SYS.init.stop(name)
	        end
	end
end

if subscribe_url and #subscribe_url > 0 then

	if SYS.call('curl -so /dev/null -m 5 www.baidu.com')~=0 then
		log("网络不通，停止更新订阅")
		return
	end

	xpcall(execute, function(e)
		log(e)
		log(debug.traceback())
		log('发生错误, 正在恢复服务')
	end)
	local enabled = ucic:get_first(name, 'global', 'enabled', 0)
		if  enabled ==  0   then 
				log('服务没有启用！')
				SYS.init.stop(name)
				--SYS.exec("/etc/init.d/" .. name .. " stop > /dev/null 2>&1 &")
		end
	log('--------------------订阅完毕---------------------')

end
