luci-app-ssr-plus: optimize subscribe script and support ssd subscribe (#2726)

* luci-app-ssr-plus: optimize subscribe script and support ssd subscribe

* luci-app-ssr-plus: optimize ping test and added socket test

* luci-app-ssr-plus: fix script path

* luci-app-ssr-plus: optimize script

* luci-app-ssr-plus: optimize script and remove /tmp/ssrpro.log

* luci-app-ssr-plus: adjust log function

* luci-app-ssr-plus: fix index

* luci-app-ssr-plus: adjust content
This commit is contained in:
William Chan 2020-01-11 12:27:42 +08:00 committed by coolsnowwolf
parent 4da966dae3
commit 9fcb30d870
9 changed files with 557 additions and 162 deletions

View File

@ -9,26 +9,28 @@ function index()
end end
entry({"admin", "services", "shadowsocksr"},alias("admin", "services", "shadowsocksr", "client"),_("ShadowSocksR Plus+"), 10).dependent = true entry({"admin", "services", "shadowsocksr"}, alias("admin", "services", "shadowsocksr", "client"),_("ShadowSocksR Plus+"), 10).dependent = true
entry({"admin", "services", "shadowsocksr", "client"},cbi("shadowsocksr/client"),_("SSR Client"), 10).leaf = true entry({"admin", "services", "shadowsocksr", "client"}, cbi("shadowsocksr/client"),_("SSR Client"), 10).leaf = true
entry({"admin", "services", "shadowsocksr", "servers"}, arcombine(cbi("shadowsocksr/servers", {autoapply=true}), cbi("shadowsocksr/client-config")),_("Severs Nodes"), 20).leaf = true entry({"admin", "services", "shadowsocksr", "servers"}, arcombine(cbi("shadowsocksr/servers", {autoapply=true}), cbi("shadowsocksr/client-config")),_("Severs Nodes"), 20).leaf = true
entry({"admin", "services", "shadowsocksr", "control"},cbi("shadowsocksr/control"),_("Access Control"), 30).leaf = true entry({"admin", "services", "shadowsocksr", "control"},cbi("shadowsocksr/control"), _("Access Control"), 30).leaf = true
-- entry({"admin", "services", "shadowsocksr", "list"},form("shadowsocksr/list"),_("GFW List"), 40).leaf = true -- entry({"admin", "services", "shadowsocksr", "list"},form("shadowsocksr/list"),_("GFW List"), 40).leaf = true
entry({"admin", "services", "shadowsocksr", "advanced"},cbi("shadowsocksr/advanced"),_("Advanced Settings"), 50).leaf = true entry({"admin", "services", "shadowsocksr", "advanced"},cbi("shadowsocksr/advanced"),_("Advanced Settings"), 50).leaf = true
if nixio.fs.access("/usr/bin/ssr-server") then if nixio.fs.access("/usr/bin/ssr-server") then
entry({"admin", "services", "shadowsocksr", "server"},arcombine(cbi("shadowsocksr/server"), cbi("shadowsocksr/server-config")),_("SSR Server"), 60).leaf = true entry({"admin", "services", "shadowsocksr", "server"},arcombine(cbi("shadowsocksr/server"), cbi("shadowsocksr/server-config")),_("SSR Server"), 60).leaf = true
end end
entry({"admin", "services", "shadowsocksr", "status"},form("shadowsocksr/status"),_("Status"), 70).leaf = true entry({"admin", "services", "shadowsocksr", "status"},form("shadowsocksr/status"),_("Status"), 70).leaf = true
entry({"admin", "services", "shadowsocksr", "check"}, call("check_status")) entry({"admin", "services", "shadowsocksr", "check"}, call("check_status"))
entry({"admin", "services", "shadowsocksr", "refresh"}, call("refresh_data")) entry({"admin", "services", "shadowsocksr", "refresh"}, call("refresh_data"))
entry({"admin", "services", "shadowsocksr", "subscribe"}, call("subscribe"))
entry({"admin", "services", "shadowsocksr", "checkport"}, call("check_port")) entry({"admin", "services", "shadowsocksr", "checkport"}, call("check_port"))
entry({"admin", "services", "shadowsocksr", "log"},form("shadowsocksr/log"),_("Log"), 80).leaf = true entry({"admin", "services", "shadowsocksr", "log"},form("shadowsocksr/log"),_("Log"), 80).leaf = true
@ -39,31 +41,49 @@ function index()
end end
function subscribe()
luci.sys.call("/usr/bin/lua /usr/share/shadowsocksr/subscribe.lua >> /tmp/ssrplus.log 2>&1")
luci.http.prepare_content("application/json")
luci.http.write_json({ ret = 1 })
end
function act_status() function act_status()
local e={} local e={}
e.running=luci.sys.call("busybox ps -w | grep ssr-retcp | grep -v grep >/dev/null")==0 e.running=luci.sys.call("busybox ps -w | grep ssr-retcp | grep -v grep >/dev/null")==0
luci.http.prepare_content("application/json") luci.http.prepare_content("application/json")
luci.http.write_json(e) luci.http.write_json(e)
end end
function act_ping() function act_ping()
local e={} local e = {}
e.index=luci.http.formvalue("index") local domain = luci.http.formvalue("domain")
e.ping=luci.sys.exec("ping -c 1 -W 1 %q 2>&1 | grep -o 'time=[0-9]*.[0-9]' | awk -F '=' '{print$2}'"%luci.http.formvalue("domain")) local port = luci.http.formvalue("port")
luci.http.prepare_content("application/json") e.index = luci.http.formvalue("index")
luci.http.write_json(e) e.ping = luci.sys.exec("ping -c 1 -W 1 %q 2>&1 | grep -o 'time=[0-9]*.[0-9]' | awk -F '=' '{print$2}'" % domain)
local iret = luci.sys.call(" ipset add ss_spec_wan_ac " .. domain .. " 2>/dev/null")
local socket = nixio.socket("inet", "stream")
socket:setopt("socket", "rcvtimeo", 3)
socket:setopt("socket", "sndtimeo", 3)
e.socket = socket:connect(domain, port)
socket:close()
if (iret == 0) then
luci.sys.call(" ipset del ss_spec_wan_ac " .. domain)
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end end
function check_status() function check_status()
local set ="/usr/bin/ssr-check www." .. luci.http.formvalue("set") .. ".com 80 3 1" local set = "/usr/bin/ssr-check www." .. luci.http.formvalue("set") .. ".com 80 3 1"
sret=luci.sys.call(set) sret = luci.sys.call(set)
if sret== 0 then if sret == 0 then
retstring ="0" retstring ="0"
else else
retstring ="1" retstring ="1"
end end
luci.http.prepare_content("application/json") luci.http.prepare_content("application/json")
luci.http.write_json({ ret=retstring }) luci.http.write_json({ ret=retstring })
end end
function refresh_data() function refresh_data()
@ -72,82 +92,82 @@ local icount =0
if set == "gfw_data" then if set == "gfw_data" then
if nixio.fs.access("/usr/bin/wget-ssl") then if nixio.fs.access("/usr/bin/wget-ssl") then
refresh_cmd="wget-ssl --no-check-certificate https://cdn.jsdelivr.net/gh/gfwlist/gfwlist/gfwlist.txt -O /tmp/gfw.b64" refresh_cmd="wget-ssl --no-check-certificate https://cdn.jsdelivr.net/gh/gfwlist/gfwlist/gfwlist.txt -O /tmp/gfw.b64"
else else
refresh_cmd="wget -O /tmp/gfw.b64 http://iytc.net/tools/list.b64" refresh_cmd="wget -O /tmp/gfw.b64 http://iytc.net/tools/list.b64"
end end
sret=luci.sys.call(refresh_cmd .. " 2>/dev/null") sret=luci.sys.call(refresh_cmd .. " 2>/dev/null")
if sret== 0 then if sret== 0 then
luci.sys.call("/usr/bin/ssr-gfw") luci.sys.call("/usr/bin/ssr-gfw")
icount = luci.sys.exec("cat /tmp/gfwnew.txt | wc -l") icount = luci.sys.exec("cat /tmp/gfwnew.txt | wc -l")
if tonumber(icount)>1000 then if tonumber(icount)>1000 then
oldcount=luci.sys.exec("cat /etc/dnsmasq.ssr/gfw_list.conf | wc -l") oldcount=luci.sys.exec("cat /etc/dnsmasq.ssr/gfw_list.conf | wc -l")
if tonumber(icount) ~= tonumber(oldcount) then if tonumber(icount) ~= tonumber(oldcount) then
luci.sys.exec("cp -f /tmp/gfwnew.txt /etc/dnsmasq.ssr/gfw_list.conf") luci.sys.exec("cp -f /tmp/gfwnew.txt /etc/dnsmasq.ssr/gfw_list.conf")
retstring=tostring(math.ceil(tonumber(icount)/2)) retstring=tostring(math.ceil(tonumber(icount)/2))
else else
retstring ="0" retstring ="0"
end end
else else
retstring ="-1" retstring ="-1"
end end
luci.sys.exec("rm -f /tmp/gfwnew.txt ") luci.sys.exec("rm -f /tmp/gfwnew.txt ")
else else
retstring ="-1" retstring ="-1"
end end
elseif set == "ip_data" then elseif set == "ip_data" then
refresh_cmd="wget -O- 'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest' 2>/dev/null| awk -F\\| '/CN\\|ipv4/ { printf(\"%s/%d\\n\", $4, 32-log($5)/log(2)) }' > /tmp/china_ssr.txt" refresh_cmd="wget -O- 'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest' 2>/dev/null| awk -F\\| '/CN\\|ipv4/ { printf(\"%s/%d\\n\", $4, 32-log($5)/log(2)) }' > /tmp/china_ssr.txt"
sret=luci.sys.call(refresh_cmd) sret=luci.sys.call(refresh_cmd)
icount = luci.sys.exec("cat /tmp/china_ssr.txt | wc -l") icount = luci.sys.exec("cat /tmp/china_ssr.txt | wc -l")
if sret== 0 and tonumber(icount)>1000 then if sret== 0 and tonumber(icount)>1000 then
oldcount=luci.sys.exec("cat /etc/china_ssr.txt | wc -l") oldcount=luci.sys.exec("cat /etc/china_ssr.txt | wc -l")
if tonumber(icount) ~= tonumber(oldcount) then if tonumber(icount) ~= tonumber(oldcount) then
luci.sys.exec("cp -f /tmp/china_ssr.txt /etc/china_ssr.txt") luci.sys.exec("cp -f /tmp/china_ssr.txt /etc/china_ssr.txt")
retstring=tostring(tonumber(icount)) retstring=tostring(tonumber(icount))
else else
retstring ="0" retstring ="0"
end end
else else
retstring ="-1" retstring ="-1"
end end
luci.sys.exec("rm -f /tmp/china_ssr.txt ") luci.sys.exec("rm -f /tmp/china_ssr.txt ")
else else
local need_process = 0 local need_process = 0
if nixio.fs.access("/usr/bin/wget-ssl") then if nixio.fs.access("/usr/bin/wget-ssl") then
refresh_cmd="wget-ssl --no-check-certificate -O - https://easylist-downloads.adblockplus.org/easylistchina+easylist.txt > /tmp/adnew.conf" refresh_cmd="wget-ssl --no-check-certificate -O - https://easylist-downloads.adblockplus.org/easylistchina+easylist.txt > /tmp/adnew.conf"
need_process = 1 need_process = 1
else else
refresh_cmd="wget -O /tmp/ad.conf http://iytc.net/tools/ad.conf" refresh_cmd="wget -O /tmp/ad.conf http://iytc.net/tools/ad.conf"
end end
sret=luci.sys.call(refresh_cmd .. " 2>/dev/null") sret=luci.sys.call(refresh_cmd .. " 2>/dev/null")
if sret== 0 then if sret== 0 then
if need_process == 1 then if need_process == 1 then
luci.sys.call("/usr/bin/ssr-ad") luci.sys.call("/usr/bin/ssr-ad")
end end
icount = luci.sys.exec("cat /tmp/ad.conf | wc -l") icount = luci.sys.exec("cat /tmp/ad.conf | wc -l")
if tonumber(icount)>1000 then if tonumber(icount)>1000 then
if nixio.fs.access("/etc/dnsmasq.ssr/ad.conf") then if nixio.fs.access("/etc/dnsmasq.ssr/ad.conf") then
oldcount=luci.sys.exec("cat /etc/dnsmasq.ssr/ad.conf | wc -l") oldcount=luci.sys.exec("cat /etc/dnsmasq.ssr/ad.conf | wc -l")
else else
oldcount=0 oldcount=0
end end
if tonumber(icount) ~= tonumber(oldcount) then if tonumber(icount) ~= tonumber(oldcount) then
luci.sys.exec("cp -f /tmp/ad.conf /etc/dnsmasq.ssr/ad.conf") luci.sys.exec("cp -f /tmp/ad.conf /etc/dnsmasq.ssr/ad.conf")
retstring=tostring(math.ceil(tonumber(icount))) retstring=tostring(math.ceil(tonumber(icount)))
if oldcount==0 then if oldcount==0 then
luci.sys.call("/etc/init.d/dnsmasq restart") luci.sys.call("/etc/init.d/dnsmasq restart")
end end
else else
retstring ="0" retstring ="0"
end end
else else
retstring ="-1" retstring ="-1"
end end
luci.sys.exec("rm -f /tmp/ad.conf ") luci.sys.exec("rm -f /tmp/ad.conf ")
else else
retstring ="-1" retstring ="-1"
end end
end end
luci.http.prepare_content("application/json") luci.http.prepare_content("application/json")

View File

@ -1,6 +1,3 @@
local fs = require "nixio.fs"
local conffile = "/tmp/ssrpro.log"
f = SimpleForm("logview") f = SimpleForm("logview")
f.reset = false f.reset = false
f.submit = false f.submit = false
@ -8,8 +5,12 @@ t = f:field(TextValue, "conf")
t.rmempty = true t.rmempty = true
t.rows = 20 t.rows = 20
function t.cfgvalue() function t.cfgvalue()
luci.sys.exec("[ -f /tmp/ssrplus.log ] && sed '1!G;h;$!d' /tmp/ssrplus.log > /tmp/ssrpro.log") local logs = luci.util.execi("cat /tmp/ssrplus.log")
return fs.readfile(conffile) or "" local s = ""
for line in logs do
s = line .. "\n" .. s
end
return s
end end
t.readonly="readonly" t.readonly="readonly"

View File

@ -45,12 +45,18 @@ o = s:option(Flag, "proxy", translate("Through proxy update"))
o.rmempty = false o.rmempty = false
o.description = translate("Through proxy update list, Not Recommended ") o.description = translate("Through proxy update list, Not Recommended ")
o = s:option(Button,"update",translate("Update All Subscribe Severs")) o = s:option(Button,"subscribe", translate("Update All Subscribe Severs"))
o.inputstyle = "apply" o.rawhtml = true
o.write = function() o.template = "shadowsocksr/subscribe"
luci.sys.exec("bash /usr/share/shadowsocksr/subscribe.sh >>/tmp/ssrplus.log 2>&1")
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers"))
end -- o.inputstyle = "apply"
-- o.write = function()
-- luci.sys.call("lua /root/subscribe.lua >>/tmp/ssrplus.log 2>&1")
-- -- luci.sys.call("echo 123 >>/tmp/ssrplus.log 2>&1")
-- --luci.sys.exec("bash /usr/share/shadowsocksr/subscribe.sh >>/tmp/ssrplus.log 2>&1")
-- luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers"))
-- end
o = s:option(Button,"delete",translate("Delete all severs")) o = s:option(Button,"delete",translate("Delete all severs"))
@ -113,7 +119,11 @@ function o.cfgvalue(...)
return Value.cfgvalue(...) or "0" return Value.cfgvalue(...) or "0"
end end
o = s:option(DummyValue,"server",translate("Ping Latency")) o = s:option(DummyValue, "server_port", translate("Socket Connected"))
o.template="shadowsocksr/socket"
o.width="10%"
o = s:option(DummyValue, "server", translate("Ping Latency"))
o.template="shadowsocksr/ping" o.template="shadowsocksr/ping"
o.width="10%" o.width="10%"

View File

@ -9,16 +9,56 @@ local dsp = require "luci.dispatcher"
<script type="text/javascript"> <script type="text/javascript">
//<![CDATA[ //<![CDATA[
var pings = document.getElementsByClassName('pingtime'); const doms = document.getElementsByClassName('pingtime');
for(var i = 0; i < pings.length; i++) { const ports = document.getElementsByClassName("socket-connected")
XHR.get('<%=dsp.build_url("admin/services/shadowsocksr/ping")%>', { // 这也是卡的原罪 CGI 一下子处理不这么多
index: i, const xhr = (index) => {
domain: pings[i].getAttribute("hint") return new Promise((res) => {
const dom = doms[index];
const port = ports[index];
if (!dom) res()
XHR.get('<%=dsp.build_url("admin/services/shadowsocksr/ping")%>', {
index,
domain: dom.getAttribute("hint"),
port: port.getAttribute("hint")
}, },
function(x, result) { (x, result) => {
pings[result.index].innerHTML = (result.ping ? result.ping : "--") + " ms"; let col = '#ff0000';
} if (result.ping) {
); if (result.ping < 300) col = '#ff3300';
if (result.ping < 200) col = '#ff7700';
if (result.ping < 100) col = '#249400';
}
dom.innerHTML = `<font color="${col}">${(result.ping ? result.ping : "--") + " ms"}</font>`
if (result.socket) {
port.innerHTML = '<font color="#249400">ok</font>'
} else {
port.innerHTML = '<font color="#ff0000">fail</font>'
}
res()
});
})
} }
(async () => {
for (let group = 0; group < Math.ceil(doms.length / 5); group++) {
await Promise.all([
xhr(group * 5 + 0),
xhr(group * 5 + 1),
xhr(group * 5 + 2),
xhr(group * 5 + 3),
xhr(group * 5 + 4),
])
}
})()
// for(var i = 0; i < pings.length; i++) {
// XHR.get('<%=dsp.build_url("admin/services/shadowsocksr/ping")%>', {
// index: i,
// domain: pings[i].getAttribute("hint")
// },
// function(x, result) {
// pings[result.index].innerHTML = (result.ping ? result.ping : "--") + " ms";
// }
// );
// }
//]]> //]]>
</script> </script>

View File

@ -0,0 +1,3 @@
<%+cbi/valueheader%>
<span class="socket-connected" hint="<%=self:cfgvalue(section)%>">connecting</:wqspan>
<%+cbi/valuefooter%>

View File

@ -0,0 +1,23 @@
<%+cbi/valueheader%>
<script type="text/javascript">//<![CDATA[
function subscribe(btn,dataname) {
btn.disabled = true;
btn.value = '<%:Refresh...%> ';
murl=dataname;
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr","subscribe")%>', { set:murl }, function(x,rv) {
// 先简单刷新后期如果重构会考虑下如何组织lua shell JavaScript之间的代码逻辑和各自的调用逻辑
window.location.reload()
// btn.disabled = false;
// btn.value = '<%:Refresh Data %>';
});
return false;
}
//]]></script>
<input type="button" class="cbi-button cbi-input-apply" value="<%:Update All Subscribe Severs%> " onclick="return subscribe(this,'<%=self.option%>')" />
<!-- <span id="<%=self.option%>-status"><em><%=self.value%></em></span> -->
<%+cbi/valuefooter%>

View File

@ -512,6 +512,9 @@ msgstr "所有端口(默认)"
msgid "Only Common Ports" msgid "Only Common Ports"
msgstr "仅常用端口不走P2P流量到代理" msgstr "仅常用端口不走P2P流量到代理"
msgid "Socket Connected"
msgstr "连接测试"
msgid "Ping Latency" msgid "Ping Latency"
msgstr "Ping延迟" msgstr "Ping延迟"

View File

@ -45,7 +45,7 @@ add_cron()
{ {
sed -i '/shadowsocksr/d' $CRON_FILE sed -i '/shadowsocksr/d' $CRON_FILE
sed -i '/ssrplus.log/d' $CRON_FILE && echo '0 1 * * * echo "" > /tmp/ssrplus.log' >> $CRON_FILE sed -i '/ssrplus.log/d' $CRON_FILE && echo '0 1 * * * echo "" > /tmp/ssrplus.log' >> $CRON_FILE
[ $(uci_get_by_type server_subscribe auto_update 0) -eq 1 ] && echo "0 $(uci_get_by_type server_subscribe auto_update_time) * * * /usr/share/shadowsocksr/subscribe.sh" >> $CRON_FILE [ $(uci_get_by_type server_subscribe auto_update 0) -eq 1 ] && echo "0 $(uci_get_by_type server_subscribe auto_update_time) * * * /usr/bin/lua /usr/share/shadowsocksr/subscribe.lua" >> $CRON_FILE
[ $(uci_get_by_type server_subscribe auto_update 0) -eq 1 ] && echo "0 $(uci_get_by_type server_subscribe auto_update_time) * * * /usr/share/shadowsocksr/update.sh" >> $CRON_FILE [ $(uci_get_by_type server_subscribe auto_update 0) -eq 1 ] && echo "0 $(uci_get_by_type server_subscribe auto_update_time) * * * /usr/share/shadowsocksr/update.sh" >> $CRON_FILE
crontab $CRON_FILE crontab $CRON_FILE
} }

View File

@ -0,0 +1,295 @@
#!/usr/bin/lua
------------------------------------------------
-- This file is part of the luci-app-ssr-plus subscribe.lua
-- @author William Chan <root@williamchan.me>
------------------------------------------------
require 'nixio'
require 'luci.util'
require 'luci.jsonc'
require 'luci.sys'
-- 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, srep, sbyte, sformat, sgsub =
string.sub, string.len, string.char, string.rep, string.byte, string.format, string.gsub
local cache = {}
local nodeResult = setmetatable({}, { __index = cache }) -- update result
local name = 'shadowsocksr'
local uciType = 'servers'
local ucic = luci.model.uci.cursor()
local proxy = ucic:get_first(name, 'server_subscribe', 'proxy', '0')
local subscribe_url = ucic:get_first(name, 'server_subscribe', 'subscribe_url', {})
local log = function(...)
print(os.date("%Y-%m-%d %H:%M:%S ") .. table.concat({ ... }, " "))
end
-- 分割字符串
local function split(full, sep)
full = full:gsub("%z", "") -- 这里不是很清楚 有时候结尾带个\0
local off, result = 1, {}
while true do
local 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, nEnd - 1))
off = nEnd + slen(sep)
end
end
return result
end
-- urlencode
local function get_urlencode(c)
return sformat("%%%02X", sbyte(c))
end
function urlEncode(szText)
local str = szText:gsub("([^0-9a-zA-Z ])", get_urlencode)
str = str:gsub(" ", "+")
return str
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 = luci.sys.exec('echo \"' .. urlEncode(content) .. '\" | md5sum | cut -d \" \" -f1')
-- assert(nixio.errno() == 0)
return trim(stdout)
end
-- base64
local function base64Decode(text, safe)
if not text then return '' end
text = text:gsub("%z", "")
if safe then
text = text:gsub("_", "/")
text = text:gsub("-", "+")
local mod4 = #text % 4
text = text .. string.sub('====', mod4 + 1)
end
return nixio.bin.b64decode(text):gsub("%z", "")
end
-- 处理数据
local function processData(szType, content)
local result = {
auth_enable = '0',
switch_enable = '1',
type = szType,
local_port = 1234,
timeout = 60, -- 不太确定 好像是死的
fast_open = 0,
kcp_enable = 0,
kcp_port = 0,
kcp_param = '--nocomp'
}
local hash
if type(content) == 'string' then
hash = md5(content)
else
hash = md5(luci.jsonc.stringify(content))
end
result.hashkey = hash
if szType == 'ssr' then
local dat = split(content, "/\\?")
local hostInfo = split(dat[1], ':')
result.server = hostInfo[1]
result.server_port = hostInfo[2]
result.protocol = hostInfo[3]
result.encrypt_method = hostInfo[4]
result.obfs = hostInfo[5]
result.password = base64Decode(hostInfo[6], true)
local params = {}
for k, v in pairs(split(dat[2], '&')) do
local t = split(v, '=')
params[t[1]] = t[2]
end
result.obfs_param = base64Decode(params.bfsparam, true)
result.protocol_param = base64Decode(params.protoparam, true)
local group = base64Decode(params.group, true)
if group then
result.alias = "[" .. group .. "] "
end
result.alias = result.alias .. base64Decode(params.remarks, true)
elseif szType == 'vmess' then
local info = luci.jsonc.parse(content)
result.type = 'v2ray'
result.server = info.add
result.server_port = info.port
result.tcp_guise = "none"
result.transport = info.net
result.alter_id = info.aid
result.vmess_id = info.id
result.alias = info.ps
result.ws_host = info.host
result.ws_path = info.path
result.h2_host = info.host
result.h2_path = info.path
if not info.security then
result.security = 'auto'
end
if info.tls == 'tls' or info.tls == '1' then
result.tls = '1'
else
result.tls = '0'
end
elseif szType == 'ssd' then
result.type = 'ss'
result.server = content.server
result.server_port = content.port
result.password = content.password
result.encrypt_method_ss = content.encryption
result.alias = '[' .. content.airport .. '] ' .. content.remarks
end
return result, hash
end
-- wget
local function wget(url)
local stdout = luci.sys.exec('wget-ssl --user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36" --no-check-certificate -t 3 -T 10 -O- ' .. url)
return trim(stdout)
end
local execute = function()
-- exec
do
-- subscribe_url = {'https://www.google.comc'}
if proxy == '0' then -- 不使用代理更新的话先暂停
log('服务正在暂停')
luci.sys.init.stop(name)
end
for k, url in ipairs(subscribe_url) do
local raw = wget(url)
if #raw > 0 then
local node, szType
local groupHash = md5(url)
cache[groupHash] = {}
tinsert(nodeResult, {})
local index = #nodeResult
-- SSD 似乎是这种格式 ssd:// 开头的 不知道 SS 什么格式 没支持 不清楚其他机场是否这样
if raw:find('ssd://') then
szType = 'ssd'
local nEnd = select(2, raw:find('ssd://'))
node = base64Decode(raw:sub(nEnd + 1, #raw), true)
node = luci.jsonc.parse(node)
local extra = {
airport = node.airport,
port = node.port,
encryption = node.encryption,
password = node.password
}
local servers = {}
-- SS里面包着 干脆直接这样
for _, server in ipairs(node.servers) do
tinsert(servers, setmetatable(server, { __index = extra }))
end
node = servers
else
-- ssd 外的格式
node = split(base64Decode(raw, true), "\n")
end
for _, v in ipairs(node) do
if v then
local result, hash
if szType == 'ssd' then
result, hash = processData(szType, v)
elseif not szType then
local dat = split(v, "://")
if dat and dat[1] and dat[2] then
result, hash = processData(dat[1], base64Decode( dat[2], true))
end
else
log('跳过未知类型: ' .. szType)
end
-- log(hash, result)
if hash and result then
if result.alias:find("过期时间") or
result.alias:find("剩余流量") or
result.alias:find("QQ群") or
result.alias:find("官网") or
result.server == ''
then
log('丢弃无效节点: ' .. result.type ..' 节点, ' .. result.alias)
else
log('成功解析: ' .. result.type ..' 节点, ' .. result.alias)
result.grouphashkey = groupHash
tinsert(nodeResult[index], result)
cache[groupHash][hash] = nodeResult[index][#nodeResult[index]]
end
end
end
end
log('成功解析节点数量: ' ..#node)
end
end
end
-- diff
do
assert(next(nodeResult))
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
log('忽略手动添加的节点: ' .. old.alias)
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)
add = add + 1
end
end
end
ucic:commit(name)
-- 如果服务器已经不见了把帮换一个
local globalServer = ucic:get_first(name, 'global', 'global_server', '')
local firstServer = ucic:get_first(name, uciType)
if not ucic:get(name, globalServer) then
if firstServer then
ucic:set(name, ucic:get_first(name, 'global'), 'global_server', firstServer)
ucic:commit(name)
log('当前主服务器已更新,正在自动更换。')
end
end
if firstServer then
luci.sys.call("/etc/init.d/" .. name .." restart > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早
else
luci.sys.call("/etc/init.d/" .. name .." stop > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早
end
log('新增节点数量: ' ..add, '删除节点数量: ' .. del)
log('更新成功服务正在启动')
end
end
if subscribe_url and #subscribe_url > 0 then
xpcall(execute, function()
log('发生错误, 正在恢复服务')
local firstServer = ucic:get_first(name, uciType)
if firstServer then
luci.sys.call("/etc/init.d/" .. name .." restart > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早
else
luci.sys.call("/etc/init.d/" .. name .." stop > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早
end
log('更新失败服务正在恢复')
log(debug.traceback())
end)
end