增加 v2ray 分流功能

1.增加了V2ray简易分流模式,现在当你在使用v2ray作为主节点的时候,可以选择为几个主流的视频插件指定特定的节点来达到分流的目的。
2.修改了订阅代码。
3.修复一些节点列表的显示问题。
This commit is contained in:
jerrykuku 2020-02-09 14:32:59 +08:00
parent f64445ad84
commit d97d18ac67
10 changed files with 609 additions and 399 deletions

View File

@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-vssr
PKG_VERSION:=1.03
PKG_RELEASE:=20200118-1
PKG_VERSION:=1.04
PKG_RELEASE:=20200208-2
PKG_CONFIG_DEPENDS:= CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Server \

View File

@ -1,34 +1,45 @@
#### luci-app-vssr
### luci-app-vssr
a new SSR SS V2ray luci app bese luci-app-ssr-plus
只适配最新版 argon主题
#### Intro
写在前面插件改名的原因并非是要另起炉灶主要是自己想要的功能【视觉体验优先】和原版略有差异而且插件体积越来越大并不适合小ROM机器使用。【无trojan支持】
目前只适配最新版 argon主题 (其他主题下应该也可以用 但显示应该不会很完美)
目前Lean最新版本的openwrt 已经可以直接拉取源码到package/lean 下直接进行勾选并编译由于有部分文件和ssr+ 同文件名 所以不能同时编译。
1.基于lean ssr+ 全新修改的Vssr更名为Hello World 主要做了很多的修改和优化,同时感谢插件原作者所做出的的努力和贡献!
写在前面插件的初衷是优化操作体验和提升视觉感受所以插件体积会比较大并不适合小ROM机器使用。【无trojan支持】
2.节点列表支持国旗显示 TW节点为五星红旗 节点列表页面 打开自动ping.
还有些个人的想法首先这只是个Luci app说白了就是个GUI控制界面而已,本身并不能决定你设备的性能和节点的速度。决定你性能的东西是你的硬件方案 如cpu性能是否支持硬件aes加速还有就是几个主核心应用程序的版本。另外关于插件稳定性的问题Luci除非自身有逻辑性的BUG一般情况下是不会有稳定性差异的稳定性的差异来自于你固件的内核还有ss ssr v2ray 这几个核心插件的稳定性当然你的节点才是影响稳定性的最大因素。Luci 能决定的只有操作起来是否便利、顺手,还有对几个核心应用功能的适配挖掘而已。
3.优化了在节点列表页面点击应用后节点切换的速度。同时也优化了自动切换的速度。
### Update Log 2020-02-09
4.将节点订阅转移至 高级设置 请悉知 由于需要获取ip的国家code 新的订阅速度可能会比原来慢一点点 x86无影响 。
1.增加了V2ray简易分流模式现在当你在使用v2ray作为主节点的时候可以选择为几个主流的视频插件指定特定的节点来达到分流的目的。
2.修改了订阅代码。
3.修复一些节点列表的显示问题。
5.去掉了ss插件ss节点将通过v2ray进行代理支持ss的v2ray plugin可能会遇到老的加密方式不兼容的情况。
### Intro
6.给Hello World 增加了IP状态显示在页面底部 左边显示当前节点国旗 ip 和中文国家 右边 是四个网站的访问状态 可以访问是彩色 不能访问是灰色。
7.优化了国旗匹配方法在部分带有emoji counrty code的节点名称中 优先使用 emoji code 匹配国旗。
8.建议搭配argon theme能有最好的显示体验。
1.基于lean ssr+ 全新修改的Vssr更名为Hello World 主要做了很多的修改,同时感谢插件原作者所做出的的努力和贡献!
2.节点列表支持国旗显示 TW节点为五星红旗 节点列表页面 打开自动ping.
3.优化了在节点列表页面点击应用后节点切换的速度。同时也优化了自动切换的速度。
4.将节点订阅转移至 高级设置 请悉知 由于需要获取ip的国家code 新的订阅速度可能会比原来慢一点点 x86无影响。
5.去掉了ss插件ss节点将通过v2ray进行代理支持ss的v2ray plugin可能会遇到老的加密方式不兼容的情况。
6.给Hello World 增加了IP状态显示在页面底部 左边显示当前节点国旗 ip 和中文国家 右边 是四个网站的访问状态 可以访问是彩色 不能访问是灰色。
7.优化了国旗匹配方法在部分带有emoji counrty code的节点名称中 优先使用 emoji code 匹配国旗。
8.建议搭配argon theme能有最好的显示体验。
新修改插件难免有bug 请不要大惊小怪。欢迎提交bug。
#### Notice
需要的依赖有python3-maxminddb libmaxminddb 请自行添加
### How to use
假设你的lean openwrt最新版本19.07 在 lede 目录下
```
git clone https://github.com/jerrykuku/luci-app-vssr.git /lede/package/lean
#### 感谢
make menuconfig
make -j1 V=s
```
### 感谢
https://github.com/coolsnowwolf/lede
#### My other project
### My other project
Argon theme https://github.com/jerrykuku/luci-theme-argon
openwrt-nanopi-r1s-h5 https://github.com/jerrykuku/openwrt-nanopi-r1s-h5

View File

@ -21,20 +21,36 @@ m = Map(vssr)
m:section(SimpleSection).template = "vssr/status"
local server_table = {}
local v2ray_table = {}
uci:foreach(vssr, "servers", function(s)
if s.alias then
server_table[s[".name"]] = "[%s]:%s" %{string.upper(s.type), s.alias}
elseif s.server and s.server_port then
server_table[s[".name"]] = "[%s]:%s:%s" %{string.upper(s.type), s.server, s.server_port}
end
if s.type == "v2ray" then
if s.alias then
v2ray_table[s[".name"]] = "[%s]:%s" %{string.upper(s.type), s.alias}
elseif s.server and s.server_port then
v2ray_table[s[".name"]] = "[%s]:%s:%s" %{string.upper(s.type), s.server, s.server_port}
end
end
end)
local key_table = {}
local key_table = {}
for key,_ in pairs(server_table) do
table.insert(key_table,key)
end
table.sort(key_table)
table.sort(key_table)
local key_table_v2 = {}
for key,_ in pairs(v2ray_table) do
table.insert(key_table_v2,key)
end
table.sort(key_table_v2)
-- [[ Global Setting ]]--
s = m:section(TypedSection, "global",translate("Basic Settings"))
@ -51,6 +67,46 @@ o:value("", translate("Disable"))
o:value("same", translate("Same as Global Server"))
for _,key in pairs(key_table) do o:value(key,server_table[key]) end
o = s:option(Flag, "v2ray_flow", translate("Open v2ray split-flow"))
o.rmempty = false
o.description = translate("When open v2ray split-flow,your main server must be a v2ray server")
o = s:option(ListValue, "youtube_server", translate("Youtube Proxy"))
o:value("nil", translate("Same as Global Server"))
for _,key in pairs(key_table_v2) do o:value(key,v2ray_table[key]) end
o:depends("v2ray_flow", "1")
o.default = "nil"
o = s:option(ListValue, "tw_video_server", translate("TaiWan Video Proxy"))
o:value("nil", translate("Same as Global Server"))
for _,key in pairs(key_table_v2) do o:value(key,v2ray_table[key]) end
o:depends("v2ray_flow", "1")
o.default = "nil"
o = s:option(ListValue, "netflix_server", translate("Netflix Proxy"))
o:value("nil", translate("Same as Global Server"))
for _,key in pairs(key_table_v2) do o:value(key,v2ray_table[key]) end
o:depends("v2ray_flow", "1")
o.default = "nil"
o = s:option(ListValue, "disney_server", translate("Diseny+ Proxy"))
o:value("nil", translate("Same as Global Server"))
for _,key in pairs(key_table_v2) do o:value(key,v2ray_table[key]) end
o:depends("v2ray_flow", "1")
o.default = "nil"
o = s:option(ListValue, "prime_server", translate("Prime Video Proxy"))
o:value("nil", translate("Same as Global Server"))
for _,key in pairs(key_table_v2) do o:value(key,v2ray_table[key]) end
o:depends("v2ray_flow", "1")
o.default = "nil"
o = s:option(ListValue, "threads", translate("Multi Threads Option"))
o:value("0", translate("Auto Threads"))
o:value("1", translate("1 Thread"))

View File

@ -4,8 +4,8 @@ math.randomseed(os.time())
<html>
<head>
<link rel="stylesheet" href="/luci-static/vssr/css/vssr.css??v=<%=math.random(1,100000)%>">
<script src="<%=media%>/js/jquery.min.js?v=git-19.190.55614-35357e4"></script>
<link rel="stylesheet" href="/luci-static/vssr/css/vssr.css?v=<%=math.random(1,100000)%>">
<script src="<%=media%>/js/jquery.min.js"></script>
</head>
<body>

View File

@ -50,8 +50,7 @@ math.randomseed(os.time())
json = data.outboardip.replace(/[\r\n]/g,"");
json = eval("(" + json + ")");
if(json.flag == "tw"){
flag = "cn";
country = "中国"
country = "中国 台湾"
}else{
flag = json.flag;
country = json.country;

View File

@ -35,7 +35,7 @@
</div>
<a class="cbi-button ssr-button " type="button" value="" onclick="apply_node('<%=section%>')"
alt="应用" title="应用"><span class="icon-edit"></span> 应用</a>
alt="<%:Apply%>" title="<%:Apply%>"><span class="icon-ok"></span> <%:Apply%></a>
<%- if self.extedit then -%>
<a class="cbi-button ssr-button " type="button" value="" <%- if type(self.extedit) == "string" then
%> onclick="location.href='<%=self.extedit:format(section)%>'" <%- elseif type(self.extedit) == "function" then
@ -102,8 +102,6 @@
}
if (val.flag == undefined) {
val.flag = "un";
} else if (val.flag == "tw") {
val.flag = "cn";
}
$(id).find(".type .tp").text(val.type);
@ -133,6 +131,7 @@
}
$(document).ready(function () {
setTimeout(function() { check(); }, 500);
$(".cbi-page-actions").hide();
function check() {
$(".host_con").html("");

View File

@ -608,3 +608,24 @@ msgstr "导入配置信息"
msgid "Configuration Url"
msgstr "配置链接"
msgid "Open v2ray split-flow"
msgstr "开启V2ray分流"
msgid "When open v2ray split-flow,your main server must be a v2ray server"
msgstr "当使用v2ray分流功能时 主服务器必须为V2ray"
msgid "Youtube Proxy"
msgstr "Youtube 代理"
msgid "TaiWan Video Proxy"
msgstr "台湾视频服务代理"
msgid "Netflix Proxy"
msgstr "Netflix 代理"
msgid "Diseny+ Proxy"
msgstr "Diseny+ 代理"
msgid "Prime Video Proxy"
msgstr "Prime Video 代理"

View File

@ -1,96 +1,180 @@
local ucursor = require "luci.model.uci".cursor()
local ucursor = require"luci.model.uci".cursor()
local name = "vssr"
local json = require "luci.jsonc"
local server_section = arg[1]
local proto = arg[2]
local proto = arg[2]
local local_port = arg[3]
local host = arg[4]
local server = ucursor:get_all("vssr", server_section)
local v2ray_flow = ucursor:get_first(name, 'global', 'v2ray_flow', '0')
local youtube_server = ucursor:get_first(name, 'global', 'youtube_server')
local tw_video_server = ucursor:get_first(name, 'global', 'tw_video_server')
local netflix_server = ucursor:get_first(name, 'global', 'netflix_server')
local disney_server = ucursor:get_first(name, 'global', 'disney_server')
local prime_server = ucursor:get_first(name, 'global', 'prime_server')
local v2ray = {
log = {
-- error = "/var/ssrplus.log",
loglevel = "warning"
},
-- 传入连接
inbound = {
port = local_port,
protocol = "dokodemo-door",
settings = {
network = proto,
followRedirect = true
},
sniffing = {
enabled = true,
destOverride = { "http", "tls" }
}
},
-- 传出连接
outbound = {
protocol = "vmess",
settings = {
vnext = {
{
address = server.server,
port = tonumber(server.server_port),
users = {
{
id = server.vmess_id,
alterId = tonumber(server.alter_id),
security = server.security
function gen_outbound(server_node, tags)
local bound = {}
if server_node == "nil" then
bound = nil
else
local server = ucursor:get_all(name, server_node)
bound = {
tag = tags,
protocol = "vmess",
settings = {
vnext = {
{
address = server.server,
port = tonumber(server.server_port),
users = {
{
id = server.vmess_id,
alterId = tonumber(server.alter_id),
security = server.security
}
}
}
}
}
},
-- 底层传输配置
streamSettings = {
network = server.transport,
security = (server.tls == '1') and "tls" or "none",
tlsSettings = {allowInsecure = (server.insecure == "1") and true or false,serverName=server.ws_host,},
kcpSettings = (server.transport == "kcp") and {
mtu = tonumber(server.mtu),
tti = tonumber(server.tti),
uplinkCapacity = tonumber(server.uplink_capacity),
downlinkCapacity = tonumber(server.downlink_capacity),
congestion = (server.congestion == "1") and true or false,
readBufferSize = tonumber(server.read_buffer_size),
writeBufferSize = tonumber(server.write_buffer_size),
header = {
type = server.kcp_guise
}
} or nil,
wsSettings = (server.transport == "ws") and (server.ws_path ~= nil or server.ws_host ~= nil) and {
path = server.ws_path,
headers = (server.ws_host ~= nil) and {
Host = server.ws_host
},
-- 底层传输配置
streamSettings = {
network = server.transport,
security = (server.tls == '1') and "tls" or "none",
tlsSettings = {
allowInsecure = (server.insecure == "1") and true or false,
serverName = server.ws_host
},
kcpSettings = (server.transport == "kcp") and {
mtu = tonumber(server.mtu),
tti = tonumber(server.tti),
uplinkCapacity = tonumber(server.uplink_capacity),
downlinkCapacity = tonumber(server.downlink_capacity),
congestion = (server.congestion == "1") and true or false,
readBufferSize = tonumber(server.read_buffer_size),
writeBufferSize = tonumber(server.write_buffer_size),
header = {type = server.kcp_guise}
} or nil,
} or nil,
httpSettings = (server.transport == "h2") and {
path = server.h2_path,
host = server.h2_host,
} or nil,
quicSettings = (server.transport == "quic") and {
security = server.quic_security,
key = server.quic_key,
header = {
type = server.quic_guise
}
} or nil
},
mux = {
enabled = (server.mux == "1") and true or false,
concurrency = tonumber(server.concurrency)
}
},
-- 额外传出连接
outboundDetour = {
{
protocol = "freedom",
tag = "direct",
settings = { keep = "" }
wsSettings = (server.transport == "ws") and
(server.ws_path ~= nil or server.ws_host ~= nil) and {
path = server.ws_path,
headers = (server.ws_host ~= nil) and
{Host = server.ws_host} or nil
} or nil,
httpSettings = (server.transport == "h2") and
{path = server.h2_path, host = server.h2_host} or nil,
quicSettings = (server.transport == "quic") and {
security = server.quic_security,
key = server.quic_key,
header = {type = server.quic_guise}
} or nil
},
mux = {
enabled = (server.mux == "1") and true or false,
concurrency = tonumber(server.concurrency)
}
}
}
end
return bound
end
local outbounds_table = {}
table.insert(outbounds_table, gen_outbound(server_section, "main"))
if v2ray_flow == "1" then
table.insert(outbounds_table, gen_outbound(youtube_server, "youtube"))
table.insert(outbounds_table, gen_outbound(tw_video_server, "twvideo"))
table.insert(outbounds_table, gen_outbound(netflix_server, "netflix"))
table.insert(outbounds_table, gen_outbound(disney_server, "disney"))
table.insert(outbounds_table, gen_outbound(prime_server, "prime"))
end
-- rules gen
local youtube_rule = {
type = "field",
domain = {"youtube", "googlevideo.com", "gvt2.com", "youtu.be"},
outboundTag = "youtube"
}
local tw_video_rule = {
type = "field",
domain = {
"vidol.tv", "hinet.net", "books.com", "litv.tv", "pstatic.net",
"app-measurement.com", "kktv.com.tw", "gamer.com.tw"
},
outboundTag = "twvideo"
}
local netflix_rule = {
type = "field",
domain = {
"netflix.com", "netflix.net", "nflxso.net", "nflxext.com",
"nflximg.com", "nflximg.net", "nflxvideo.net"
},
ip = {
"23.246.0.0/12", "37.77.0.0/12", "45.57.0.0/12", "64.120.128.0/17",
"66.197.128.0/17", "108.175.0.0/12", "185.2.0.0/12", "185.9.188.0/22",
"192.173.64.0/18", "198.38.0.0/12", "198.45.0.0/12"
},
outboundTag = "netflix"
}
local disney_rule = {
type = "field",
domain = {
"cdn.registerdisney.go.com", "disneyplus.com", "disney-plus.net",
"dssott.com", "bamgrid.com", "execute-api.us-east-1.amazonaws.com"
},
outboundTag = "disney"
}
local prime_rule = {
type = "field",
domain = {"aiv-cdn.net", "amazonaws.com", "amazonvideo.com", "llnwd.net"},
outboundTag = "prime"
}
local rules_table = {}
if (youtube_server ~= "nil" and v2ray_flow == "1") then
table.insert(rules_table, youtube_rule)
end
if (tw_video_server ~= "nil" and v2ray_flow == "1") then
table.insert(rules_table, tw_video_rule)
end
if (netflix_server ~= "nil" and v2ray_flow == "1") then
table.insert(rules_table, netflix_rule)
end
if (disney_server ~= "nil" and v2ray_flow == "1") then
table.insert(rules_table, disney_rule)
end
if (prime_server ~= "nil" and v2ray_flow == "1") then
table.insert(rules_table, prime_rule)
end
local v2ray = {
log = {
-- error = "/var/ssrplus.log",
loglevel = "warning"
},
-- 传入连接
inbounds = {
{
port = local_port,
protocol = "dokodemo-door",
settings = {network = proto, followRedirect = true},
sniffing = {enabled = true, destOverride = {"http", "tls"}}
}
},
-- 传出连接
outbounds = outbounds_table,
routing = {domainStrategy = "IPIfNonMatch", rules = rules_table}
}
print(json.stringify(v2ray, 1))

View File

@ -10,9 +10,11 @@ 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, srep, sbyte, sformat, sgsub =
string.sub, string.len, string.char, string.rep, string.byte, string.format, string.gsub
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 = 'vssr'
@ -22,36 +24,36 @@ 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({ ... }, " "))
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
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))
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
local str = szText:gsub("([^0-9a-zA-Z ])", get_urlencode)
str = str:gsub(" ", "+")
return str
end
local function get_urldecode(h)
@ -63,287 +65,325 @@ end
-- trim
local function trim(text)
if not text or text == "" then
return ""
end
return (sgsub(text, "^%s*(.-)%s*$", "%1"))
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)
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)
local raw = text
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
local result = nixio.bin.b64decode(text)
if result then
return result:gsub("%z", "")
else
return raw
end
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 = {
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 content == '' then
result.server = ''
return result, hash
end
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
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'
}
result.hashkey = type(content) == 'string' and md5(content) or md5(jsonStringify(content))
elseif szType == "ss" then
local info = content:sub(1, content:find("#") - 1)
local alias = content:sub(content:find("#") + 1, #content)
local hostInfo = split(base64Decode(info, true), "@")
local host = split(hostInfo[2], ":")
local userinfo = base64Decode(hostInfo[1], true)
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 k, v in pairs(split(query[2], '&')) do
-- local t = split(v, '=')
-- params[t[1]] = t[2]
-- end
-- 这里似乎没什么用 我看数据结构没有写插件的支持 先抛弃
else
result.server_port = host[2]
end
result.encrypt_method_ss = method
result.password = password
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.bfsparam)
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.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 not info.security then
result.security = "auto"
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 == "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
local flag = luci.sys.exec('/usr/share/'..name..'/getflag.sh "'..result.alias..'" '..result.server)
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
-- 这里似乎没什么用 我看数据结构没有写插件的支持 先抛弃
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.alias = "[" .. content.airport .. "] " .. content.remarks
end
if not result.alias then
result.alias = result.server .. ':' .. result.server_port
end
local flag = luci.sys.exec('/usr/share/'..name..'/getflag.sh "'..result.alias..'" '..result.server)
result.flag = string.gsub(flag, '\n', '')
return result, hash
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)
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:// 开头的
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):gsub(" ", "\n"), "\n")
end
for _, v in ipairs(node) do
if v then
v = trim(v)
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
if dat[1] == 'ss' then
result, hash = processData(dat[1], dat[2])
else
result, hash = processData(dat[1], base64Decode(dat[2], true))
end
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), "node result is empty")
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
-- 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' 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
not result.server
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)
end
end
end
-- diff
do
assert(next(nodeResult), "node result is empty")
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 SUBSCRIBE")
log('更新成功服务正在启动')
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 SUBSCRIBE")
log('更新成功服务正在启动')
end
end
if subscribe_url and #subscribe_url > 0 then
xpcall(execute, function(e)
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 &") -- 不加&的话日志会出现的更早
else
luci.sys.call("/etc/init.d/" .. name .." stop > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早
end
end)
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 &") -- 不加&的话日志会出现的更早
else
luci.sys.call("/etc/init.d/" .. name .." stop > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早
end
end)
end

View File

@ -437,7 +437,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
.ssr-button {
background: none;
color: #525f7f;
padding: 0 0 0 6px;
padding: 0 0 0 0;
height: auto;
line-height: 1.5em;
}
@ -523,7 +523,7 @@ footer.mobile-hide{
text-align: right;
}
@media screen and (max-width: 1900px) {
@media screen and (max-width: 2000px) {
.pure-u-1-5 {
width: 25%;
}