luci-app-ssr-plus: optimize subscribe (#3665)

* luci-app-ssr-plus: optimize subscribe

1: Sort by airport.
2: Select node optimize.
3: Full update, delete md5 hashkey.

* luci-app-ssr-plus: select first server

* luci-app-ssr-plus: fix uci bug
This commit is contained in:
William Chan 2020-03-09 01:45:58 +08:00 committed by GitHub
parent 15bc5a44de
commit d5ddb7e8a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 148 additions and 97 deletions

View File

@ -59,8 +59,8 @@ o = s:option(Button,"delete",translate("Delete All Subscribe Severs"))
o.inputstyle = "reset"
o.description = string.format(translate("Server Count") .. ": %d", server_count)
o.write = function()
uci:delete_all("shadowsocksr", "servers", function(s)
if s["hashkey"] then
uci:delete_all("shadowsocksr", "servers", function(s)
if s.hashkey or s.isSubscribe then
return true
else
return false

View File

@ -7,7 +7,7 @@ require 'nixio'
require 'luci.util'
require 'luci.jsonc'
require 'luci.sys'
require 'uci'
-- these global functions are accessed all the time by the event handler
-- so caching them is worth the effort
local luci = luci
@ -15,18 +15,48 @@ 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 cache = {}
local nodeResult = setmetatable({}, { __index = cache }) -- update result
local name = 'shadowsocksr'
local nodeResult = {} -- update result
local application = 'shadowsocksr'
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', '1')
local subscribe_url = ucic:get_first(name, 'server_subscribe', 'subscribe_url', {})
local ucic2 = uci.cursor()
local proxy = ucic2:get(application, '@server_subscribe[0]', 'proxy') or '0'
local switch = ucic2:get(application, '@server_subscribe[0]', 'switch') or '1'
local subscribe_url = ucic2:get(application, '@server_subscribe[0]', 'subscribe_url') or {}
ucic2:revert(application)
local log = function(...)
print(os.date("%Y-%m-%d %H:%M:%S ") .. table.concat({ ... }, " "))
end
-- 获取各项动态配置的当前服务器,可以用 get 和 set get必须要获取到节点表
local CONFIG = {
GLOBAL_SERVER = {
remarks = '主节点',
type = "global", option = "global_server",
set = function(server)
ucic2:set(application, '@global[0]', "global_server", server)
end
}
}
do
for k, v in pairs(CONFIG) do
local currentNode
if v.get then
currentNode = v.get()
else
local cfgid = ucic2:get(application, '@' .. v.type .. '[0]', v.option)
if cfgid then
currentNode = ucic2:get_all(application, cfgid)
end
end
if currentNode then
CONFIG[k].currentNode = currentNode
else
CONFIG[k] = nil
end
end
end
-- 分割字符串
local function split(full, sep)
full = full:gsub("%z", "") -- 这里不是很清楚 有时候结尾带个\0
@ -47,15 +77,15 @@ local function split(full, sep)
return result
end
-- urlencode
local function get_urlencode(c)
return sformat("%%%02X", sbyte(c))
end
-- 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 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))
@ -71,12 +101,7 @@ local function trim(text)
end
return (sgsub(text, "^%s*(.-)%s*$", "%1"))
end
-- md5
local function md5(content)
local stdout = luci.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
@ -96,9 +121,10 @@ end
-- 处理数据
local function processData(szType, content)
local result = {
type = szType,
local_port = 1234,
kcp_param = '--nocomp'
type = szType,
local_port = 1234,
kcp_param = '--nocomp',
isSubscribe = 1,
}
if szType == 'ssr' then
local dat = split(content, "/%?")
@ -243,12 +269,12 @@ local function processData(szType, content)
local t = split(v, '=')
params[t[1]] = t[2]
end
if params.peer then
-- 未指定peersni默认使用remote addr
result.tls_host = params.peer
end
if params.allowInsecure == "1" then
result.insecure = "1"
else
@ -262,14 +288,6 @@ local function processData(szType, content)
if not result.alias then
result.alias = result.server .. ':' .. result.server_port
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
-- wget
@ -278,20 +296,71 @@ local function wget(url)
return trim(stdout)
end
local function select_node(nodes, config)
local server
-- 第一优先级 IP + 端口
for id, node in pairs(nodes) do
if node.server .. ':' .. node.server_port == config.currentNode.server .. ':' .. config.currentNode.server_port then
log('选择【' .. config.remarks .. '】第一匹配节点:' .. node.alias)
server = id
break
end
end
-- 第二优先级 IP
if not server then
for id, node in pairs(nodes) do
if node.server == config.currentNode.server then
log('选择【' .. config.remarks .. '】第二匹配节点:' .. node.alias)
server = id
break
end
end
end
-- 第三优先级备注
if not server then
for id, node in pairs(nodes) do
if node.alias == config.currentNode.alias then
log('选择【' .. config.remarks .. '】第三匹配节点:' .. node.alias)
server = id
break
end
end
end
-- 第四 cfgid
if not server then
for id, node in pairs(nodes) do
if id == config.currentNode['.name'] then
log('选择【' .. config.remarks .. '】第四匹配节点:' .. node.alias)
server = id
break
end
end
end
-- 还不行 随便找一个
if not server then
server = ucic2:get(application, '@'.. uciType .. '[0]')
if server then
log('无法找到最匹配的节点,当前已更换为' .. ucic2:get_all(application, server).alias)
end
end
if server then
config.set(server)
end
end
local execute = function()
-- exec
do
if proxy == '0' then -- 不使用代理更新的话先暂停
log('服务正在暂停')
luci.sys.init.stop(name)
luci.sys.init.stop(application)
end
for k, url in ipairs(subscribe_url) do
local raw = wget(url)
if #raw > 0 then
local nodes, szType
local groupHash = md5(url)
cache[groupHash] = {}
tinsert(nodeResult, {})
local all_odes = {}
tinsert(nodeResult, all_odes)
local index = #nodeResult
-- SSD 似乎是这种格式 ssd:// 开头的
if raw:find('ssd://') then
@ -341,14 +410,12 @@ local execute = function()
result.alias:find("官网") or
result.alias:find("防失联地址") or
not result.server or
result.server:match("[^0-9a-zA-Z%-%.%s]") -- 中文做地址的 也没有人拿中文域名搞就算中文域也有Puny Code SB 机场
result.server:match("[^0-9a-zA-Z%_%-%.%s]") -- 中文做地址的 也没有人拿中文域名搞就算中文域也有Puny Code SB 机场
then
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]]
tinsert(all_odes, result)
end
end
end
@ -361,61 +428,45 @@ local execute = function()
end
-- diff
do
if next(nodeResult) == nil then
log("更新失败,没有可用的节点信息")
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
old.alias = old.server .. ':' .. old.server_port
end
log('忽略手动添加的节点: ' .. old.alias)
assert(next(nodeResult), '更新失败,没有可用的节点信息')
-- delete all for subscribe nodes
ucic2:foreach(application, uciType, function(node)
if node.isSubscribe or node.hashkey then -- 兼容之前的hashkey
ucic2:delete(application, node['.name'])
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
for _, v in ipairs(nodeResult) do
for _, vv in ipairs(v) do
vv.switch_enable = switch
local cfgid = ucic2:add(application, uciType)
for kkk, vvv in pairs(vv) do
ucic2:set(application, cfgid, kkk, vvv)
end
end
end
ucic:commit(name)
-- 如果原有服务器节点已经不见了就尝试换为第一个节点
local globalServer = ucic:get_first(name, 'global', 'global_server', '')
local firstServer = ucic:get_first(name, uciType)
if firstServer then
if not ucic:get(name, globalServer) then
luci.sys.call("/etc/init.d/" .. name .. " stop > /dev/null 2>&1 &")
ucic:commit(name)
ucic:set(name, ucic:get_first(name, 'global'), 'global_server', ucic:get_first(name, uciType))
ucic:commit(name)
log('当前主服务器节点已被删除,正在自动更换为第一个节点。')
luci.sys.call("/etc/init.d/" .. name .. " start > /dev/null 2>&1 &")
else
log('维持当前主服务器节点。')
luci.sys.call("/etc/init.d/" .. name .." restart > /dev/null 2>&1 &")
ucic2:commit(application)
local ucic3 = uci.cursor()
-- repair configuration
if next(CONFIG) then
local nodes = {}
ucic3:foreach(application, uciType, function(node)
if node.server and node.server_port and node.alias then
nodes[node['.name']] = node
end
end)
for _, config in pairs(CONFIG) do
select_node(nodes, config)
end
else
log('没有服务器节点了,停止服务')
luci.sys.call("/etc/init.d/" .. name .. " stop > /dev/null 2>&1 &")
ucic3:commit(application)
end
log('新增节点数量: ' ..add, '删除节点数量: ' .. del)
-- select first server
local globalServer = ucic3:get(application, '@global[0]', 'global_server') or ''
if not globalServer or not ucic3:get_all(application, globalServer) then
ucic3:set(application, '@global[0]', 'global_server', select(2, ucic3:get(application, '@' .. uciType .. '[0]')))
ucic3:commit(application)
log('当前没有主节点,自动选择第一个节点开启服务。')
end
luci.sys.call("/etc/init.d/" .. application .." restart > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早
log('订阅更新成功')
end
end
@ -425,13 +476,13 @@ if subscribe_url and #subscribe_url > 0 then
log(e)
log(debug.traceback())
log('发生错误, 正在恢复服务')
local firstServer = ucic:get_first(name, uciType)
if firstServer then
luci.sys.call("/etc/init.d/" .. name .." restart > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早
if CONFIG.GLOBAL_SERVER and CONFIG.GLOBAL_SERVER.currentNode then
luci.sys.call("/etc/init.d/" .. application .." restart > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早
log('重启服务成功')
else
luci.sys.call("/etc/init.d/" .. name .." stop > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早
luci.sys.call("/etc/init.d/" .. application .." stop > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早
log('停止服务成功')
end
end)
end