diff --git a/package/ctcgfw/luci-app-ssr-plus-Jo/Makefile b/package/ctcgfw/luci-app-ssr-plus-Jo/Makefile index a599a9d1b6..9dab3c2ea5 100644 --- a/package/ctcgfw/luci-app-ssr-plus-Jo/Makefile +++ b/package/ctcgfw/luci-app-ssr-plus-Jo/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-ssr-plus-Jo -PKG_VERSION:=150 -PKG_RELEASE:=20200301-5 +PKG_VERSION:=151 +PKG_RELEASE:=20200304-5 PKG_CONFIG_DEPENDS:= CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Trojan \ diff --git a/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/controller/shadowsocksr.lua b/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/controller/shadowsocksr.lua index fd8a9484f9..4251627f1e 100644 --- a/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/controller/shadowsocksr.lua +++ b/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/controller/shadowsocksr.lua @@ -28,8 +28,8 @@ entry({"admin", "vpn", "shadowsocksr", "appointlist"},form("shadowsocksr/appoint entry({"admin", "vpn", "shadowsocksr", "server"},arcombine(cbi("shadowsocksr/server"), cbi("shadowsocksr/server-config")),_("SSR Server"),22).leaf = true end entry({"admin", "vpn", "shadowsocksr", "status"},form("shadowsocksr/status"),_("Status"), 23).leaf = true - entry({"admin", "vpn", "shadowsocksr", "log"}, cbi("shadowsocksr/log"), _("Log"), 30).leaf = true - + entry({"admin", "vpn", "shadowsocksr", "logview"}, cbi("vssr/logview", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("Log") ,30).leaf=true + entry({"admin", "vpn", "shadowsocksr", "fileread"}, call("act_read"), nil).leaf=true entry({"admin", "vpn", "shadowsocksr", "refresh"}, call("refresh_data")) entry({"admin", "vpn", "shadowsocksr", "checkport"}, call("check_port")) entry({"admin", "vpn", "shadowsocksr", "checkports"}, call("check_ports")) @@ -65,7 +65,7 @@ function get_subscribe() end luci.sys.call('uci commit shadowsocksr') luci.sys.call( - "nohup /usr/share/shadowsocksr/subscribe.sh >/www/check_update.htm 2>/dev/null &") + "nohup /usr/bin/lua /usr/share/shadowsocksr/subscribe.lua >/www/check_update.htm 2>/dev/null &") e.error = 0 else @@ -372,5 +372,16 @@ function check_port() luci.http.write_json({ret = retstring , used = math.floor(tt*1000 + 0.5)}) end - +function act_read(lfile) + local NXFS = require "nixio.fs" + local HTTP = require "luci.http" + local lfile = HTTP.formvalue("lfile") + local ldata={} + ldata[#ldata+1] = NXFS.readfile(lfile) or "_nofile_" + if ldata[1] == "" then + ldata[1] = "_nodata_" + end + HTTP.prepare_content("application/json") + HTTP.write_json(ldata) +end diff --git a/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/model/cbi/shadowsocksr/logview.lua b/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/model/cbi/shadowsocksr/logview.lua new file mode 100644 index 0000000000..91b2776327 --- /dev/null +++ b/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/model/cbi/shadowsocksr/logview.lua @@ -0,0 +1,48 @@ +-- Copyright 2008 Steven Barth +-- Licensed to the public under the Apache License 2.0. + +local NXFS = require "nixio.fs" +local DISP = require "luci.dispatcher" +local HTTP = require "luci.http" +local UCI = luci.model.uci.cursor() + +m = Map("shadowsocksr") + +-- log directory +log_dir = UCI:get_first(m.config, "global", "log_dir") or "/tmp" +run_dir = UCI:get_first(m.config, "global", "run_dir") or "/var/etc" +local logfile_list = {} + +for path in (NXFS.glob("%s/ssr*" % log_dir) or function() end) do + logfile_list[#logfile_list+1] = path +end +for path in (NXFS.glob("%s/*.*" % run_dir) or function() end) do + logfile_list[#logfile_list+1] = path +end + +ns = m:section(TypedSection, "_dummy", translate("File Viewer")) +ns.addremove = false +ns.anonymous = true +function ns.cfgsections() + return{"_exrules"} +end + +lv = ns:option(DynamicList, "logfiles") +lv.template = "shadowsocksr/logsview" +lv.inputtitle = translate("Read / Reread log file") +lv.rows = 25 +lv.default = "" +for _, lfile in ipairs(logfile_list) do lv:value(lfile, lfile) end +function lv.cfgvalue(self, section) + if logfile_list[1] then + local lfile=logfile_list[1] + if NXFS.access(lfile) then + return lfile .. "\n" .. translate("Please press [Read] button") + end + return lfile .. "\n" .. translate("File not found or empty") + else + return log_dir .. "\/\n" .. translate("No files found") + end +end + +return m diff --git a/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/model/cbi/shadowsocksr/status.lua b/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/model/cbi/shadowsocksr/status.lua index d84c0cca01..5c6ea05851 100644 --- a/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/model/cbi/shadowsocksr/status.lua +++ b/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/model/cbi/shadowsocksr/status.lua @@ -1,7 +1,7 @@ -- Copyright (C) 2017 yushi studio -- Licensed to the public under the GNU General Public License v3. -local IPK_Version="20200301.150" +local IPK_Version="20200304.151" local m, s, o local redir_run=0 local reudp_run=0 @@ -96,6 +96,88 @@ if nixio.fs.access("/etc/china_ssr.txt") then ip_count = sys.exec("cat /etc/china_ssr.txt | wc -l") end +function processlist() + local data = {} + local netf = {} + local k + local ps = luci.util.execi("/bin/busybox top -bn1 | egrep -v dnsmasq") + local nets = luci.util.execi("netstat -netupl | egrep -v dnsmasq | awk '{print $1,$4,_,$6,$7}'") + + if not ps or not nets then + return + end + + for line in nets do +-- tcp 0 0 127.0.0.1:1234 0.0.0.0:* LISTEN 5103/v2ray +-- udp 0 0 127.0.0.1:1234 0.0.0.0:* 5147/v2ray +-- local proto, ip, port, nid = line:match("([^%s]+) +.* +([^ ]*):(%d+) +.* +(%d+)\/.*") + local proto, ip, port, nid = line:match("([^%s]+) (.*):(%d+)[^%d]+(%d+)\/.*") + local idx = tonumber(nid) + if idx and ip then + local newstr = "://" .. ip .. ":" .. port + local isnew = (netf[idx] and netf[idx]['listen']) and netf[idx]['listen']:match(proto .. newstr) or false + netf[idx] = { + ['listen'] = ((netf[idx] and netf[idx]['listen']) and (not isnew) and (netf[idx]['listen'] .. "\n" .. proto) or proto) .. newstr, + } + end + end +-- 5103 1 root S 661m 543% 0% /usr/bin/v2ray/v2ray -config /var/etc/shadowsocksr.json + for line in ps do + local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match( + "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][W ][ " .. status[idx]['COMMAND'] .. " -c " .. status[idx]['CONFIG'] .. font_off + sname = translate(status[idx]['COMMAND']) + break + end + end + end + local section = form:field(DummyValue,name,translate(name) .. ": " .. sname) + section.rawhtml = true + section.value = stat + return section +end + +procs=processlist() @@ -187,7 +269,12 @@ m = SimpleForm("Version") m.reset = false m.submit = false - +t = m:section(Table, procs, translate("Running Details: ") .. "(/var/etc)") +t:option(DummyValue, "PID", translate("PID")) +t:option(DummyValue, "COMMAND", translate("CMD")) +t:option(DummyValue, "LISTEN", translate("LISTEN")) +t:option(DummyValue, "%CPU", translate("CPU")) +t:option(DummyValue, "%MEM", translate("MEM")) s=m:field(DummyValue,"redir_run",translate("Global Client")) s.rawhtml = true diff --git a/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/model/cbi/shadowsocksr/subscription.lua b/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/model/cbi/shadowsocksr/subscription.lua index 5137f9cbc6..0053719f53 100644 --- a/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/model/cbi/shadowsocksr/subscription.lua +++ b/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/model/cbi/shadowsocksr/subscription.lua @@ -58,6 +58,12 @@ o = s:option(DummyValue, "", "") o.rawhtml = true o.template = "shadowsocksr/update_subscribe" +o = s:option(Button,"update",translate("Update All Subscribe Severs"),translate("No special needs, please click here to subscribe to update")) +o.inputstyle = "reload" +o.write = function() + luci.sys.call("bash /usr/share/shadowsocksr/subscribe.sh >>/tmp/vssr.log 2>&1") + luci.http.redirect(luci.dispatcher.build_url("admin", "vpn", "shadowsocksr", "servers")) +end o = s:option(Button,"delete",translate("Delete All Subscribe Severs")) diff --git a/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/view/shadowsocksr/logsview.htm b/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/view/shadowsocksr/logsview.htm new file mode 100644 index 0000000000..6a00a16494 --- /dev/null +++ b/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/view/shadowsocksr/logsview.htm @@ -0,0 +1,109 @@ + +<%- + local values = self:formvalue(section) + if not values then + values = self:cfgvalue(section) or {self.default} + end + + local function serialize_json(x, cb) + local rv, push = nil, cb + if not push then + rv = { } + push = function(tok) rv[#rv+1] = tok end + end + + if x == nil then + push("null") + elseif type(x) == "table" then + push("[") + for k = 1, #x do + if k > 1 then + push(",") + end + serialize_json(x[k], push) + end + push("]") + else + push('"%s"' % tostring(x):gsub('["%z\1-\31\\]', + function(c) return '\\u%04x' % c:byte(1) end)) + end + + if not cb then + return table.concat(rv, "") + end + end +-%> +<%+cbi/valueheader%> + + + + +<% +-- one button on top, one at the buttom +%> +

+ /> + +

+ +<% +-- set a readable style taken from openwrt theme for textarea#syslog +-- in openwrt theme there are problems with a width of 100 so we check for theme and set to lower value +%> + +

+ +<% +-- one button on top, one at the buttom +%> + /> +

+<%+cbi/valuefooter%> + diff --git a/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/view/shadowsocksr/update_subscribe.htm b/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/view/shadowsocksr/update_subscribe.htm index 5844465e21..8bcbfd3f9c 100644 --- a/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/view/shadowsocksr/update_subscribe.htm +++ b/package/ctcgfw/luci-app-ssr-plus-Jo/luasrc/view/shadowsocksr/update_subscribe.htm @@ -1,123 +1,109 @@ <%+cbi/valueheader%> - - + +
"> + size="0" value="<%= translate("Save And Start Subscribe") %> "> +
<%+cbi/valuefooter%> \ No newline at end of file diff --git a/package/ctcgfw/luci-app-ssr-plus-Jo/po/zh-cn/ssr-plus.po b/package/ctcgfw/luci-app-ssr-plus-Jo/po/zh-cn/ssr-plus.po index dc781918ec..13d0c95c5c 100644 --- a/package/ctcgfw/luci-app-ssr-plus-Jo/po/zh-cn/ssr-plus.po +++ b/package/ctcgfw/luci-app-ssr-plus-Jo/po/zh-cn/ssr-plus.po @@ -853,4 +853,14 @@ msgstr "SS/SSR/V2RAY 服务端" msgid "Delete All Subscribe Severs" msgstr "删除所有订阅服务器节点" +msgid "Update All Subscribe Severs" +msgstr "更新所有订阅服务器节点" +msgid "No special needs, please click here to subscribe to update" +msgstr "没有特殊需要,请点击这里订阅更新" + +msgid "Running Details:" +msgstr "进程详情:" + +msgid "File Viewer" +msgstr "文件查看器" diff --git a/package/ctcgfw/luci-app-ssr-plus-Jo/root/etc/init.d/shadowsocksr b/package/ctcgfw/luci-app-ssr-plus-Jo/root/etc/init.d/shadowsocksr index e6e66f731b..884d47cc89 100755 --- a/package/ctcgfw/luci-app-ssr-plus-Jo/root/etc/init.d/shadowsocksr +++ b/package/ctcgfw/luci-app-ssr-plus-Jo/root/etc/init.d/shadowsocksr @@ -217,7 +217,7 @@ start_rules() { local local_port=$(uci_get_by_name $GLOBAL_SERVER local_port) local lan_ac_ips=$(uci_get_by_type access_control lan_ac_ips) - local lan_ac_mode="b" + local lan_ac_mode=$(uci_get_by_type access_control lan_ac_mode) local router_proxy=$(uci_get_by_type access_control router_proxy) if [ "$GLOBAL_SERVER" = "$UDP_RELAY_SERVER" -a $kcp_flag = 0 ]; then ARG_UDP="-u" @@ -263,6 +263,7 @@ start_rules() { -i "$(uci_get_by_type access_control wan_bp_list)" \ -b "$(uci_get_by_type access_control wan_bp_ips)" \ -w "$(uci_get_by_type access_control wan_fw_ips)" \ + -B "$(uci_get_by_type access_control lan_bp_ips)" \ -p "$(uci_get_by_type access_control lan_fp_ips)" \ -G "$(uci_get_by_type access_control lan_gm_ips)" \ -D "$proxyport" \ @@ -679,11 +680,13 @@ start_redir() { for i in $(seq 1 $threads); do $sscmd --config /var/etc/trojan-ssr-retcp.json >/dev/null 2>&1 & done - echo "$(date "+%Y-%m-%d %H:%M:%S") $($sscmd --version 2>&1 | head -1) Started!" >> /tmp/ssrplus.log + echo "$(date "+%Y-%m-%d %H:%M:%S") $($sscmd --version 2>&1 | head -1) $threads Threads Started!" >> /tmp/ssrplus.log elif [ "$stype" == "v2ray" ] ;then + for i in $(seq 1 $threads); do $sscmd -config /var/etc/v2-ssr-retcp.json >/dev/null 2>&1 & - echo "$(date "+%Y-%m-%d %H:%M:%S") $($sscmd -version | head -1) Started!" >> /tmp/ssrplus.log + done + echo "$(date "+%Y-%m-%d %H:%M:%S") $($sscmd -version | head -1) $threads Threads Started!" >> /tmp/ssrplus.log fi diff --git a/package/ctcgfw/luci-app-ssr-plus-Jo/root/usr/bin/ssr-rules b/package/ctcgfw/luci-app-ssr-plus-Jo/root/usr/bin/ssr-rules index 1764424553..694101aa51 100755 --- a/package/ctcgfw/luci-app-ssr-plus-Jo/root/usr/bin/ssr-rules +++ b/package/ctcgfw/luci-app-ssr-plus-Jo/root/usr/bin/ssr-rules @@ -26,6 +26,7 @@ usage() { define access control mode -b wan ip of will be bypassed -w wan ip of will be forwarded + -B lan ip of will be bypassed proxy -p lan ip of will be global proxy -G lan ip of will be game mode proxy -D proxy ports @@ -88,7 +89,7 @@ ipset_r() { EOF ipset -N gfwlist hash:net 2>/dev/null $IPT -N SS_SPEC_WAN_AC - $IPT -I SS_SPEC_WAN_AC -d $server -j RETURN + $IPT -I SS_SPEC_WAN_AC -p tcp ! --dport 53 -d $server -j RETURN $IPT -A SS_SPEC_WAN_AC -m set --match-set ss_spec_wan_ac dst -j RETURN $IPT -A SS_SPEC_WAN_AC -j SS_SPEC_WAN_FW @@ -99,7 +100,7 @@ EOF $IPT -A SS_SPEC_WAN_AC -m set --match-set china dst -j RETURN $IPT -A SS_SPEC_WAN_AC -m set --match-set gfwlist dst -j SS_SPEC_WAN_FW $IPT -A SS_SPEC_WAN_AC -m set --match-set gmlan src -m set ! --match-set china dst -j SS_SPEC_WAN_FW - $IPT -I SS_SPEC_WAN_AC -d $server -j RETURN + $IPT -I SS_SPEC_WAN_AC -p tcp ! --dport 53 -d $server -j RETURN elif [ "$RUNMODE" = "oversea" ] ;then ipset -N oversea hash:net 2>/dev/null @@ -110,7 +111,7 @@ EOF for ip in $LAN_GM_IP; do ipset -! add gmlan $ip ; done $IPT -A SS_SPEC_WAN_AC -m set --match-set gmlan src -m set --match-set china dst -j SS_SPEC_WAN_FW - $IPT -I SS_SPEC_WAN_AC -d $server -j RETURN + $IPT -I SS_SPEC_WAN_AC -p tcp ! --dport 53 -d $server -j RETURN elif [ "$RUNMODE" = "routers" ] ;then ipset -! -R <<-EOF || return 1 @@ -127,14 +128,16 @@ EOF elif [ "$RUNMODE" = "all" ] ;then $IPT -N SS_SPEC_WAN_AC $IPT -A SS_SPEC_WAN_AC -j SS_SPEC_WAN_FW - $IPT -I SS_SPEC_WAN_AC -d $server -j RETURN + $IPT -I SS_SPEC_WAN_AC -p tcp ! --dport 53 -d $server -j RETURN fi ipset -N fplan hash:net 2>/dev/null for ip in $LAN_FP_IP; do ipset -! add fplan $ip ; done $IPT -I SS_SPEC_WAN_AC -m set --match-set fplan src -j SS_SPEC_WAN_FW - + ipset -N bplan hash:net 2>/dev/null + for ip in $LAN_BP_IP; do ipset -! add bplan $ip; done + $IPT -I SS_SPEC_WAN_AC -m set --match-set bplan src -j RETURN ipset -N whitelist hash:net 2>/dev/null ipset -N blacklist hash:net 2>/dev/null $IPT -I SS_SPEC_WAN_AC -m set --match-set blacklist src -j SS_SPEC_WAN_FW @@ -157,6 +160,7 @@ fw_rule() { $IPT -A SS_SPEC_WAN_FW -d 224.0.0.0/4 -j RETURN $IPT -A SS_SPEC_WAN_FW -d 240.0.0.0/4 -j RETURN $IPT -A SS_SPEC_WAN_FW -p tcp $PROXY_PORTS \ + $IPT -A SS_SPEC_WAN_FW -p tcp $PROXY_PORTS \ -j REDIRECT --to-ports $local_port 2>/dev/null || { loger 3 "Can't redirect, please check the iptables." exit 1 @@ -217,8 +221,8 @@ tp_rule() { $ipt -A SS_SPEC_TPROXY -p udp -d 192.168.0.0/16 -j RETURN $ipt -A SS_SPEC_TPROXY -p udp -d 224.0.0.0/4 -j RETURN $ipt -A SS_SPEC_TPROXY -p udp -d 240.0.0.0/4 -j RETURN - $ipt -A SS_SPEC_TPROXY -p udp -d $SERVER -j RETURN - + $ipt -A SS_SPEC_TPROXY -p udp ! --dport 53 -d $server -j RETURN + $ipt -A SS_SPEC_TPROXY -p udp -m set --match-set bplan src -j RETURN $ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set fplan src \ -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01 @@ -326,7 +330,7 @@ EOF return 0 } -while getopts ":s:l:S:L:i:e:a:b:w:p:G:D:k:oOuUfgrRczh" arg; do +while getopts ":s:l:S:L:i:e:a:B:b:w:p:G:D:k:oOuUfgrRczh" arg; do case "$arg" in s) server=$OPTARG @@ -349,6 +353,11 @@ while getopts ":s:l:S:L:i:e:a:b:w:p:G:D:k:oOuUfgrRczh" arg; do a) LAN_AC_IP=$OPTARG ;; + + B) + LAN_BP_IP=$OPTARG + ;; + b) WAN_BP_IP=$(for ip in $OPTARG; do echo $ip; done) ;; diff --git a/package/ctcgfw/luci-app-ssr-plus-Jo/root/usr/share/shadowsocksr/subscribe.lua b/package/ctcgfw/luci-app-ssr-plus-Jo/root/usr/share/shadowsocksr/subscribe.lua new file mode 100644 index 0000000000..23e8bfb410 --- /dev/null +++ b/package/ctcgfw/luci-app-ssr-plus-Jo/root/usr/share/shadowsocksr/subscribe.lua @@ -0,0 +1,440 @@ +#!/usr/bin/lua +------------------------------------------------ +-- This file is part of the luci-app-ssr-plus subscribe.lua +-- @author William Chan +------------------------------------------------ +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 luci = luci +local tinsert = table.insert +local ssub, slen, schar, sbyte, sformat, sgsub = string.sub, string.len, string.char, string.byte, string.format, string.gsub +local jsonParse, jsonStringify = luci.jsonc.parse, luci.jsonc.stringify +local b64decode = nixio.bin.b64decode +local 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 switch = ucic:get_first(name, 'server_subscribe', 'switch', '1') +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 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 +-- 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:gsub("+", " "):gsub("%%(%x%x)", get_urldecode) +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) + local raw = text + if not text then return '' end + text = text:gsub("%z", "") + text = text:gsub("_", "/") + text = text:gsub("-", "+") + local mod4 = #text % 4 + text = text .. string.sub('====', mod4 + 1) + local result = b64decode(text) + if result then + return result:gsub("%z", "") + else + return raw + end +end +-- 处理数据 +local function processData(szType, content) + local result = { + type = szType, + local_port = 1234, + kcp_param = '--nocomp' + } + 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]) + 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.server = info.add + result.server_port = info.port + result.transport = info.net + result.alter_id = info.aid + result.vmess_id = info.id + result.alias = info.ps + result.insecure = 1 + -- result.mux = 1 + -- result.concurrency = 8 + if info.net == 'ws' then + result.ws_host = info.host + result.ws_path = info.path + end + if info.net == 'h2' then + result.h2_host = info.host + result.h2_path = info.path + end + if info.net == 'tcp' then + result.tcp_guise = info.type + result.http_host = info.host + result.http_path = info.path + end + if info.net == 'kcp' 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 == '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 + end + if info.tls == "tls" or info.tls == "1" then + result.tls = "1" + result.tls_host = info.host + else + result.tls = "0" + end + elseif szType == "ss" then + local idx_sp = 0 + local alias = "" + if content:find("#") then + idx_sp = content:find("#") + alias = content:sub(idx_sp + 1, -1) + end + local info = content:sub(1, idx_sp - 1) + local hostInfo = split(base64Decode(info), "@") + local host = split(hostInfo[2], ":") + local userinfo = base64Decode(hostInfo[1]) + local method = userinfo:sub(1, userinfo:find(":") - 1) + local password = userinfo:sub(userinfo:find(":") + 1, #userinfo) + result.alias = UrlDecode(alias) + result.type = "ss" + result.server = host[1] + if host[2]:find("/%?") then + local query = split(host[2], "/%?") + result.server_port = query[1] + local params = {} + for _, v in pairs(split(query[2], '&')) do + local t = split(v, '=') + params[t[1]] = t[2] + end + if params.plugin then + local plugin_info = UrlDecode(params.plugin) + local idx_pn = plugin_info:find(";") + if idx_pn then + result.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 + else + result.server_port = host[2] + end + result.encrypt_method_ss = method + result.password = password + 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.plugin = content.plugin + result.plugin_opts = content.plugin_options + result.alias = "[" .. content.airport .. "] " .. content.remarks + elseif szType == "trojan" then + local idx_sp = 0 + local alias = "" + if content:find("#") then + idx_sp = content:find("#") + alias = content:sub(idx_sp + 1, -1) + end + local info = content:sub(1, idx_sp - 1) + local hostInfo = split(info, "@") + local host = split(hostInfo[2], ":") + local userinfo = hostInfo[1] + local password = userinfo + result.alias = UrlDecode(alias) + result.type = "trojan" + result.server = host[1] + -- 按照官方的建议 默认验证ssl证书 + result.insecure = "0" + result.tls = "1" + if host[2]:find("?") then + local query = split(host[2], "?") + result.server_port = query[1] + local params = {} + for _, v in pairs(split(query[2], '&')) do + local t = split(v, '=') + params[t[1]] = t[2] + end + + if params.peer then + -- 未指定peer(sni)默认使用remote addr + result.tls_host = params.peer + end + + if params.allowInsecure == "1" then + result.insecure = "1" + else + result.insecure = "0" + end + else + result.server_port = host[2] + end + result.password = password + end + 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 +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 + 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 nodes, szType + 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 + 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 + if dat[1] == 'ss' or dat[1] == 'trojan' then + result = processData(dat[1], dat[2]) + else + result = processData(dat[1], base64Decode(dat[2])) + end + end + else + log('跳过未知类型: ' .. szType) + end + -- log(result) + if result then + if result.alias:find("过期时间") or + result.alias:find("剩余流量") or + result.alias:find("QQ群") or + result.alias:find("官网") or + result.alias:find("防失联地址") or + not result.server or + 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]] + end + end + end + end + log('成功解析节点数量: ' ..#nodes) + else + log(url .. ': 获取内容为空') + end + end + 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) + 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 + 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 &") + end + else + log('没有服务器节点了,停止服务') + luci.sys.call("/etc/init.d/" .. name .. " stop > /dev/null 2>&1 &") + end + log('新增节点数量: ' ..add, '删除节点数量: ' .. del) + log("END SUBSCRIBE") + log('订阅更新成功') + end +end + +if subscribe_url and #subscribe_url > 0 then + xpcall(execute, function(e) + log(e) + log(debug.traceback()) + log("END SUBSCRIBE") + log('发生错误, 正在恢复服务') + local firstServer = ucic:get_first(name, uciType) + if firstServer then + luci.sys.call("/etc/init.d/" .. name .." restart > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早 + log('重启服务成功') + else + luci.sys.call("/etc/init.d/" .. name .." stop > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早 + log('停止服务成功') + end + end) +end