🐶 Sync 2025-11-02 14:26:26
This commit is contained in:
168
luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua
Normal file
168
luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua
Normal file
@@ -0,0 +1,168 @@
|
||||
-- Copyright (C) 2017 yushi studio <ywb94@qq.com>
|
||||
-- Licensed to the public under the GNU General Public License v3.
|
||||
module("luci.controller.shadowsocksr", package.seeall)
|
||||
|
||||
function index()
|
||||
if not nixio.fs.access("/etc/config/shadowsocksr") then
|
||||
call("act_reset")
|
||||
end
|
||||
local page
|
||||
page = entry({"admin", "services", "shadowsocksr"}, alias("admin", "services", "shadowsocksr", "client"), _("ShadowSocksR Plus+"), 1)
|
||||
page.dependent = true
|
||||
page.acl_depends = { "luci-app-ssr-plus" }
|
||||
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")), _("Servers Nodes"), 20).leaf = true
|
||||
entry({"admin", "services", "shadowsocksr", "control"}, cbi("shadowsocksr/control"), _("Access Control"), 30).leaf = true
|
||||
entry({"admin", "services", "shadowsocksr", "advanced"}, cbi("shadowsocksr/advanced"), _("Advanced Settings"), 50).leaf = true
|
||||
entry({"admin", "services", "shadowsocksr", "server"}, arcombine(cbi("shadowsocksr/server"), cbi("shadowsocksr/server-config")), _("SSR Server"), 60).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", "refresh"}, call("refresh_data"))
|
||||
entry({"admin", "services", "shadowsocksr", "subscribe"}, call("subscribe"))
|
||||
entry({"admin", "services", "shadowsocksr", "checkport"}, call("check_port"))
|
||||
entry({"admin", "services", "shadowsocksr", "log"}, form("shadowsocksr/log"), _("Log"), 80).leaf = true
|
||||
entry({"admin", "services", "shadowsocksr", "get_log"}, call("get_log")).leaf = true
|
||||
entry({"admin", "services", "shadowsocksr", "clear_log"}, call("clear_log")).leaf = true
|
||||
entry({"admin", "services", "shadowsocksr", "run"}, call("act_status"))
|
||||
entry({"admin", "services", "shadowsocksr", "ping"}, call("act_ping"))
|
||||
entry({"admin", "services", "shadowsocksr", "reset"}, call("act_reset"))
|
||||
entry({"admin", "services", "shadowsocksr", "restart"}, call("act_restart"))
|
||||
entry({"admin", "services", "shadowsocksr", "delete"}, call("act_delete"))
|
||||
--[[Backup]]
|
||||
entry({"admin", "services", "shadowsocksr", "backup"}, call("create_backup")).leaf = true
|
||||
end
|
||||
|
||||
function subscribe()
|
||||
luci.sys.call("/usr/bin/lua /usr/share/shadowsocksr/subscribe.lua >>/var/log/ssrplus.log")
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({ret = 1})
|
||||
end
|
||||
|
||||
function act_status()
|
||||
local e = {}
|
||||
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.write_json(e)
|
||||
end
|
||||
|
||||
function act_ping()
|
||||
local e = {}
|
||||
local domain = luci.http.formvalue("domain")
|
||||
local port = luci.http.formvalue("port")
|
||||
local transport = luci.http.formvalue("transport")
|
||||
local wsPath = luci.http.formvalue("wsPath")
|
||||
local tls = luci.http.formvalue("tls")
|
||||
e.index = luci.http.formvalue("index")
|
||||
local iret = luci.sys.call("ipset add ss_spec_wan_ac " .. domain .. " 2>/dev/null")
|
||||
if transport == "ws" then
|
||||
local prefix = tls=='1' and "https://" or "http://"
|
||||
local address = prefix..domain..':'..port..wsPath
|
||||
local result = luci.sys.exec("curl --http1.1 -m 2 -ksN -o /dev/null -w 'time_connect=%{time_connect}\nhttp_code=%{http_code}' -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==' -H 'Sec-WebSocket-Version: 13' "..address)
|
||||
e.socket = string.match(result,"http_code=(%d+)")=="101"
|
||||
e.ping = tonumber(string.match(result, "time_connect=(%d+.%d%d%d)"))*1000
|
||||
else
|
||||
local socket = nixio.socket("inet", "stream")
|
||||
socket:setopt("socket", "rcvtimeo", 3)
|
||||
socket:setopt("socket", "sndtimeo", 3)
|
||||
e.socket = socket:connect(domain, port)
|
||||
socket:close()
|
||||
e.ping = luci.sys.exec(string.format("echo -n $(tcping -q -c 1 -i 1 -t 2 -p %s %s 2>&1 | grep -o 'time=[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null", port, domain))
|
||||
if (e.ping == "") then
|
||||
e.ping = luci.sys.exec("echo -n $(ping -c 1 -W 1 %q 2>&1 | grep -o 'time=[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null" % domain)
|
||||
if (e.ping == "") then
|
||||
-- UDP ping test using nping
|
||||
e.ping = luci.sys.exec(string.format("echo -n $(nping --udp -c 1 -p %s %s 2>/dev/null | grep -o 'Avg rtt: [0-9.]*ms' | awk '{print $3}' | sed 's/ms//' | head -1) 2>/dev/null", port, domain))
|
||||
end
|
||||
end
|
||||
end
|
||||
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
|
||||
|
||||
function check_status()
|
||||
local e = {}
|
||||
e.ret = luci.sys.call("/usr/bin/ssr-check www." .. luci.http.formvalue("set") .. ".com 80 3 1")
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
|
||||
function refresh_data()
|
||||
local set = luci.http.formvalue("set")
|
||||
local retstring = loadstring("return " .. luci.sys.exec("/usr/bin/lua /usr/share/shadowsocksr/update.lua " .. set))()
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(retstring)
|
||||
end
|
||||
|
||||
function check_port()
|
||||
local retstring = "<br /><br />"
|
||||
local s
|
||||
local server_name = ""
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local iret = 1
|
||||
uci:foreach("shadowsocksr", "servers", function(s)
|
||||
if s.alias then
|
||||
server_name = s.alias
|
||||
elseif s.server and s.server_port then
|
||||
server_name = "%s:%s" % {s.server, s.server_port}
|
||||
end
|
||||
iret = luci.sys.call("ipset add ss_spec_wan_ac " .. s.server .. " 2>/dev/null")
|
||||
socket = nixio.socket("inet", "stream")
|
||||
socket:setopt("socket", "rcvtimeo", 3)
|
||||
socket:setopt("socket", "sndtimeo", 3)
|
||||
ret = socket:connect(s.server, s.server_port)
|
||||
if tostring(ret) == "true" then
|
||||
socket:close()
|
||||
retstring = retstring .. "<font><b style='color:green'>[" .. server_name .. "] OK.</b></font><br />"
|
||||
else
|
||||
retstring = retstring .. "<font><b style='color:red'>[" .. server_name .. "] Error.</b></font><br />"
|
||||
end
|
||||
if iret == 0 then
|
||||
luci.sys.call("ipset del ss_spec_wan_ac " .. s.server)
|
||||
end
|
||||
end)
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({ret = retstring})
|
||||
end
|
||||
|
||||
function act_reset()
|
||||
luci.sys.call("/etc/init.d/shadowsocksr reset >/dev/null 2>&1")
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr"))
|
||||
end
|
||||
|
||||
function act_restart()
|
||||
luci.sys.call("/etc/init.d/shadowsocksr restart &")
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr"))
|
||||
end
|
||||
|
||||
function act_delete()
|
||||
luci.sys.call("/etc/init.d/shadowsocksr restart &")
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers"))
|
||||
end
|
||||
|
||||
function get_log()
|
||||
luci.http.write(luci.sys.exec("[ -f '/var/log/ssrplus.log' ] && cat /var/log/ssrplus.log"))
|
||||
end
|
||||
|
||||
function clear_log()
|
||||
luci.sys.call("echo '' > /var/log/ssrplus.log")
|
||||
end
|
||||
|
||||
function create_backup()
|
||||
local backup_files = {
|
||||
"/etc/config/shadowsocksr",
|
||||
"/etc/ssrplus/*"
|
||||
}
|
||||
local date = os.date("%Y-%m-%d-%H-%M-%S")
|
||||
local tar_file = "/tmp/shadowsocksr-" .. date .. "-backup.tar.gz"
|
||||
nixio.fs.remove(tar_file)
|
||||
local cmd = "tar -czf " .. tar_file .. " " .. table.concat(backup_files, " ")
|
||||
luci.sys.call(cmd)
|
||||
luci.http.header("Content-Disposition", "attachment; filename=shadowsocksr-" .. date .. "-backup.tar.gz")
|
||||
luci.http.header("X-Backup-Filename", "shadowsocksr-" .. date .. "-backup.tar.gz")
|
||||
luci.http.prepare_content("application/octet-stream")
|
||||
luci.http.write(nixio.fs.readfile(tar_file))
|
||||
nixio.fs.remove(tar_file)
|
||||
end
|
||||
432
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua
Normal file
432
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua
Normal file
@@ -0,0 +1,432 @@
|
||||
local m, s, o
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
|
||||
-- 获取 LAN IP 地址
|
||||
function lanip()
|
||||
local lan_ip
|
||||
lan_ip = luci.sys.exec("uci -q get network.lan.ipaddr 2>/dev/null | awk -F '/' '{print $1}' | tr -d '\n'")
|
||||
|
||||
if not lan_ip or lan_ip == "" then
|
||||
lan_ip = luci.sys.exec("ip address show $(uci -q -p /tmp/state get network.lan.ifname || uci -q -p /tmp/state get network.lan.device) | grep -w 'inet' | grep -Eo 'inet [0-9\.]+' | awk '{print $2}' | head -1 | tr -d '\n'")
|
||||
end
|
||||
|
||||
if not lan_ip or lan_ip == "" then
|
||||
lan_ip = luci.sys.exec("ip addr show | grep -w 'inet' | grep 'global' | grep -Eo 'inet [0-9\.]+' | awk '{print $2}' | head -n 1 | tr -d '\n'")
|
||||
end
|
||||
|
||||
return lan_ip
|
||||
end
|
||||
|
||||
local lan_ip = lanip()
|
||||
local server_table = {}
|
||||
local type_table = {}
|
||||
local function is_finded(e)
|
||||
return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= ""
|
||||
end
|
||||
|
||||
uci:foreach("shadowsocksr", "servers", function(s)
|
||||
if s.alias then
|
||||
server_table[s[".name"]] = "[%s]:%s" % {string.upper(s.v2ray_protocol or s.type), s.alias}
|
||||
elseif s.server and s.server_port then
|
||||
server_table[s[".name"]] = "[%s]:%s:%s" % {string.upper(s.v2ray_protocol or s.type), s.server, s.server_port}
|
||||
end
|
||||
if s.type then
|
||||
type_table[s[".name"]] = s.type
|
||||
end
|
||||
end)
|
||||
|
||||
local key_table = {}
|
||||
for key, _ in pairs(server_table) do
|
||||
table.insert(key_table, key)
|
||||
end
|
||||
|
||||
table.sort(key_table)
|
||||
|
||||
m = Map("shadowsocksr")
|
||||
-- [[ global ]]--
|
||||
s = m:section(TypedSection, "global", translate("Server failsafe auto swith and custom update settings"))
|
||||
s.anonymous = true
|
||||
|
||||
-- o = s:option(Flag, "monitor_enable", translate("Enable Process Deamon"))
|
||||
-- o.rmempty = false
|
||||
-- o.default = "1"
|
||||
|
||||
o = s:option(Flag, "enable_switch", translate("Enable Auto Switch"))
|
||||
o.rmempty = false
|
||||
o.default = "1"
|
||||
|
||||
o = s:option(Value, "switch_time", translate("Switch check cycly(second)"))
|
||||
o.datatype = "uinteger"
|
||||
o:depends("enable_switch", "1")
|
||||
o.default = 667
|
||||
|
||||
o = s:option(Value, "switch_timeout", translate("Check timout(second)"))
|
||||
o.datatype = "uinteger"
|
||||
o:depends("enable_switch", "1")
|
||||
o.default = 5
|
||||
|
||||
o = s:option(Value, "switch_try_count", translate("Check Try Count"))
|
||||
o.datatype = "uinteger"
|
||||
o:depends("enable_switch", "1")
|
||||
o.default = 3
|
||||
|
||||
o = s:option(Value, "gfwlist_url", translate("gfwlist Update url"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/gfwlist.txt", translate("v2fly/domain-list-community"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt", translate("Loyalsoldier/v2ray-rules-dat"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/Loukky/gfwlist-by-loukky/gfwlist.txt", translate("Loukky/gfwlist-by-loukky"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/gfwlist/gfwlist/gfwlist.txt", translate("gfwlist/gfwlist"))
|
||||
o.default = "https://fastly.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/gfwlist.txt"
|
||||
|
||||
o = s:option(Value, "chnroute_url", translate("Chnroute Update url"))
|
||||
o:value("https://ispip.clang.cn/all_cn.txt", translate("Clang.CN"))
|
||||
o:value("https://ispip.clang.cn/all_cn_cidr.txt", translate("Clang.CN.CIDR"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china.txt", translate("china-operator-ip"))
|
||||
o.default = "https://ispip.clang.cn/all_cn.txt"
|
||||
|
||||
o = s:option(Flag, "netflix_enable", translate("Enable Netflix Mode"))
|
||||
o.description = translate("When disabled shunt mode, will same time stopped shunt service.")
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "nfip_url", translate("nfip_url"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/QiuSimons/Netflix_IP/NF_only.txt", translate("Netflix IP Only"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/QiuSimons/Netflix_IP/getflix.txt", translate("Netflix and AWS"))
|
||||
o.default = "https://fastly.jsdelivr.net/gh/QiuSimons/Netflix_IP/NF_only.txt"
|
||||
o.description = translate("Customize Netflix IP Url")
|
||||
o:depends("netflix_enable", "1")
|
||||
|
||||
o = s:option(ListValue, "shunt_dns_mode", translate("DNS Query Mode For Shunt Mode"))
|
||||
if is_finded("dns2socks") then
|
||||
o:value("1", translate("Use DNS2SOCKS query and cache"))
|
||||
end
|
||||
if is_finded("dns2socks-rust") then
|
||||
o:value("2", translate("Use DNS2SOCKS-RUST query and cache"))
|
||||
end
|
||||
if is_finded("mosdns") then
|
||||
o:value("3", translate("Use MosDNS query"))
|
||||
end
|
||||
if is_finded("dnsproxy") then
|
||||
o:value("4", translate("Use DNSPROXY query and cache"))
|
||||
end
|
||||
if is_finded("chinadns-ng") then
|
||||
o:value("5", translate("Use ChinaDNS-NG query and cache"))
|
||||
end
|
||||
o:depends("netflix_enable", "1")
|
||||
o.default = 1
|
||||
|
||||
o = s:option(Value, "shunt_dnsserver", translate("Anti-pollution DNS Server For Shunt Mode"))
|
||||
o:value("8.8.4.4:53", translate("Google Public DNS (8.8.4.4)"))
|
||||
o:value("8.8.8.8:53", translate("Google Public DNS (8.8.8.8)"))
|
||||
o:value("208.67.222.222:53", translate("OpenDNS (208.67.222.222)"))
|
||||
o:value("208.67.220.220:53", translate("OpenDNS (208.67.220.220)"))
|
||||
o:value("209.244.0.3:53", translate("Level 3 Public DNS (209.244.0.3)"))
|
||||
o:value("209.244.0.4:53", translate("Level 3 Public DNS (209.244.0.4)"))
|
||||
o:value("4.2.2.1:53", translate("Level 3 Public DNS (4.2.2.1)"))
|
||||
o:value("4.2.2.2:53", translate("Level 3 Public DNS (4.2.2.2)"))
|
||||
o:value("4.2.2.3:53", translate("Level 3 Public DNS (4.2.2.3)"))
|
||||
o:value("4.2.2.4:53", translate("Level 3 Public DNS (4.2.2.4)"))
|
||||
o:value("1.1.1.1:53", translate("Cloudflare DNS (1.1.1.1)"))
|
||||
o:depends("shunt_dns_mode", "1")
|
||||
o:depends("shunt_dns_mode", "2")
|
||||
o.description = translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)")
|
||||
o.datatype = "ip4addrport"
|
||||
|
||||
o = s:option(ListValue, "shunt_mosdns_dnsserver", translate("Anti-pollution DNS Server"))
|
||||
o:value("tcp://8.8.4.4:53,tcp://8.8.8.8:53", translate("Google Public DNS"))
|
||||
o:value("tcp://208.67.222.222:53,tcp://208.67.220.220:53", translate("OpenDNS"))
|
||||
o:value("tcp://209.244.0.3:53,tcp://209.244.0.4:53", translate("Level 3 Public DNS-1 (209.244.0.3-4)"))
|
||||
o:value("tcp://4.2.2.1:53,tcp://4.2.2.2:53", translate("Level 3 Public DNS-2 (4.2.2.1-2)"))
|
||||
o:value("tcp://4.2.2.3:53,tcp://4.2.2.4:53", translate("Level 3 Public DNS-3 (4.2.2.3-4)"))
|
||||
o:value("tcp://1.1.1.1:53,tcp://1.0.0.1:53", translate("Cloudflare DNS"))
|
||||
o:depends("shunt_dns_mode", "3")
|
||||
o.description = translate("Custom DNS Server for MosDNS")
|
||||
|
||||
o = s:option(Flag, "shunt_mosdns_ipv6", translate("Disable IPv6 In MosDNS Query Mode (Shunt Mode)"))
|
||||
o:depends("shunt_dns_mode", "3")
|
||||
o.rmempty = false
|
||||
o.default = "0"
|
||||
|
||||
if is_finded("dnsproxy") then
|
||||
o = s:option(ListValue, "shunt_parse_method", translate("Select DNS parse Mode"))
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("When use DNS list file, please ensure list file exists and is formatted correctly.") .. "</li>" ..
|
||||
"<li>" .. translate("Tips: Dnsproxy DNS Parse List Path:") ..
|
||||
" <a href='http://" .. lan_ip .. "/cgi-bin/luci/admin/services/shadowsocksr/control' target='_blank'>" ..
|
||||
translate("Click here to view or manage the DNS list file") .. "</a>" .. "</li>" ..
|
||||
"</ul>"
|
||||
)
|
||||
o:value("single_dns", translate("Set Single DNS"))
|
||||
o:value("parse_file", translate("Use DNS List File"))
|
||||
o:depends("shunt_dns_mode", "4")
|
||||
o.rmempty = true
|
||||
o.default = "single_dns"
|
||||
|
||||
o = s:option(Value, "dnsproxy_shunt_forward", translate("Anti-pollution DNS Server"))
|
||||
o:value("sdns://AgUAAAAAAAAABzguOC40LjQgsKKKE4EwvtIbNjGjagI2607EdKSVHowYZtyvD9iPrkkHOC44LjQuNAovZG5zLXF1ZXJ5", translate("Google DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAAACC2vD25TAYM7EnyCH8Xw1-0g5OccnTsGH9vQUUH0njRtAxkbnMudHduaWMudHcKL2Rucy1xdWVyeQ", translate("TWNIC-101 DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAADzE4NS4yMjIuMjIyLjIyMiAOp5Svj-oV-Fz-65-8H2VKHLKJ0egmfEgrdPeAQlUFFA8xODUuMjIyLjIyMi4yMjIKL2Rucy1xdWVyeQ", translate("dns.sb DNSCrypt SDNS"))
|
||||
o:value("sdns://AgMAAAAAAAAADTE0OS4xMTIuMTEyLjkgsBkgdEu7dsmrBT4B4Ht-BQ5HPSD3n3vqQ1-v5DydJC8SZG5zOS5xdWFkOS5uZXQ6NDQzCi9kbnMtcXVlcnk", translate("Quad9 DNSCrypt SDNS"))
|
||||
o:value("sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", translate("AdGuard DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAABzEuMC4wLjGgENk8mGSlIfMGXMOlIlCcKvq7AVgcrZxtjon911-ep0cg63Ul-I8NlFj4GplQGb_TTLiczclX57DvMV8Q-JdjgRgSZG5zLmNsb3VkZmxhcmUuY29tCi9kbnMtcXVlcnk", translate("Cloudflare DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAADjEwNC4xNi4yNDkuMjQ5ABJjbG91ZGZsYXJlLWRucy5jb20KL2Rucy1xdWVyeQ", translate("cloudflare-dns.com DNSCrypt SDNS"))
|
||||
o:depends("shunt_parse_method", "single_dns")
|
||||
o.description = translate("Custom DNS Server (support: IP:Port or tls://IP:Port or https://IP/dns-query and other format).")
|
||||
|
||||
o = s:option(ListValue, "shunt_upstreams_logic_mode", translate("Defines the upstreams logic mode"))
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("Defines the upstreams logic mode, possible values: load_balance, parallel, fastest_addr (default: load_balance).") .. "</li>" .. "<li>" .. translate("When two or more DNS servers are deployed, enable this function.") .. "</li>" ..
|
||||
"</ul>"
|
||||
)
|
||||
o:value("load_balance", translate("load_balance"))
|
||||
o:value("parallel", translate("parallel"))
|
||||
o:value("fastest_addr", translate("fastest_addr"))
|
||||
o:depends("shunt_parse_method", "parse_file")
|
||||
o.rmempty = true
|
||||
o.default = "load_balance"
|
||||
|
||||
o = s:option(Flag, "shunt_dnsproxy_ipv6", translate("Disable IPv6 query mode"))
|
||||
o.description = translate("When disabled, all AAAA requests are not resolved.")
|
||||
o:depends("shunt_parse_method", "single_dns")
|
||||
o:depends("shunt_parse_method", "parse_file")
|
||||
o.rmempty = false
|
||||
o.default = "1"
|
||||
end
|
||||
|
||||
if is_finded("chinadns-ng") then
|
||||
o = s:option(Value, "chinadns_ng_shunt_dnsserver", translate("Anti-pollution DNS Server For Shunt Mode"))
|
||||
o:value("8.8.4.4:53", translate("Google Public DNS (8.8.4.4)"))
|
||||
o:value("8.8.8.8:53", translate("Google Public DNS (8.8.8.8)"))
|
||||
o:value("208.67.222.222:53", translate("OpenDNS (208.67.222.222)"))
|
||||
o:value("208.67.220.220:53", translate("OpenDNS (208.67.220.220)"))
|
||||
o:value("209.244.0.3:53", translate("Level 3 Public DNS (209.244.0.3)"))
|
||||
o:value("209.244.0.4:53", translate("Level 3 Public DNS (209.244.0.4)"))
|
||||
o:value("4.2.2.1:53", translate("Level 3 Public DNS (4.2.2.1)"))
|
||||
o:value("4.2.2.2:53", translate("Level 3 Public DNS (4.2.2.2)"))
|
||||
o:value("4.2.2.3:53", translate("Level 3 Public DNS (4.2.2.3)"))
|
||||
o:value("4.2.2.4:53", translate("Level 3 Public DNS (4.2.2.4)"))
|
||||
o:value("1.1.1.1:53", translate("Cloudflare DNS (1.1.1.1)"))
|
||||
o:depends("shunt_dns_mode", "5")
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)") .. "</li>" ..
|
||||
"<li>" .. translate("Muitiple DNS server can saperate with ','") .. "</li>" ..
|
||||
"</ul>"
|
||||
)
|
||||
|
||||
o = s:option(ListValue, "chinadns_ng_shunt_proto", translate("ChinaDNS-NG shunt query protocol"))
|
||||
o:value("none", translate("UDP/TCP upstream"))
|
||||
o:value("tcp", translate("TCP upstream"))
|
||||
o:value("udp", translate("UDP upstream"))
|
||||
o:value("tls", translate("DoT upstream (Need use wolfssl version)"))
|
||||
o:depends("shunt_dns_mode", "5")
|
||||
end
|
||||
|
||||
o = s:option(Flag, "apple_optimization", translate("Apple domains optimization"), translate("For Apple domains equipped with Chinese mainland CDN, always responsive to Chinese CDN IP addresses"))
|
||||
o.rmempty = false
|
||||
o.default = "1"
|
||||
|
||||
o = s:option(Value, "apple_url", translate("Apple Domains Update url"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf", translate("felixonmars/dnsmasq-china-list"))
|
||||
o.default = "https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf"
|
||||
o:depends("apple_optimization", "1")
|
||||
|
||||
o = s:option(Value, "apple_dns", translate("Apple Domains DNS"), translate("If empty, Not change Apple domains parsing DNS (Default is empty)"))
|
||||
o.rmempty = true
|
||||
o.default = ""
|
||||
o.datatype = "ip4addr"
|
||||
o:depends("apple_optimization", "1")
|
||||
|
||||
o = s:option(Flag, "adblock", translate("Enable adblock"))
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "adblock_url", translate("adblock_url"))
|
||||
o:value("https://raw.githubusercontent.com/neodevpro/neodevhost/master/lite_dnsmasq.conf", translate("NEO DEV HOST Lite"))
|
||||
o:value("https://raw.githubusercontent.com/neodevpro/neodevhost/master/dnsmasq.conf", translate("NEO DEV HOST Full"))
|
||||
o:value("https://anti-ad.net/anti-ad-for-dnsmasq.conf", translate("anti-AD"))
|
||||
o.default = "https://raw.githubusercontent.com/neodevpro/neodevhost/master/lite_dnsmasq.conf"
|
||||
o:depends("adblock", "1")
|
||||
o.description = translate("Support AdGuardHome and DNSMASQ format list")
|
||||
|
||||
o = s:option(Button, "Reset", translate("Reset to defaults"))
|
||||
o.inputstyle = "reload"
|
||||
o.write = function()
|
||||
luci.sys.call("/etc/init.d/shadowsocksr reset")
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers"))
|
||||
end
|
||||
|
||||
-- [[ SOCKS5 Proxy ]]--
|
||||
s = m:section(TypedSection, "socks5_proxy", translate("Global SOCKS5 Proxy Server"))
|
||||
s.anonymous = true
|
||||
|
||||
-- Enable/Disable Option
|
||||
o = s:option(Flag, "enabled", translate("Enable"))
|
||||
o.default = 0
|
||||
o.rmempty = false
|
||||
|
||||
-- Server Selection
|
||||
o = s:option(ListValue, "server", translate("Server"))
|
||||
o:value("same", translate("Same as Global Server"))
|
||||
for _, key in pairs(key_table) do
|
||||
o:value(key, server_table[key])
|
||||
end
|
||||
o.default = "same"
|
||||
o.rmempty = false
|
||||
|
||||
-- Dynamic value handling based on enabled/disabled state
|
||||
o.cfgvalue = function(self, section)
|
||||
local enabled = m:get(section, "enabled")
|
||||
if enabled == "0" then
|
||||
return m:get(section, "old_server")
|
||||
end
|
||||
return Value.cfgvalue(self, section)-- Default to `same` when enabled
|
||||
end
|
||||
|
||||
o.write = function(self, section, value)
|
||||
local enabled = m:get(section, "enabled")
|
||||
if enabled == "0" then
|
||||
local old_server = Value.cfgvalue(self, section)
|
||||
if old_server ~= "nil" then
|
||||
m:set(section, "old_server", old_server)
|
||||
end
|
||||
m:set(section, "server", "nil")
|
||||
else
|
||||
m:del(section, "old_server")
|
||||
-- Write the value normally when enabled
|
||||
Value.write(self, section, value)
|
||||
end
|
||||
end
|
||||
|
||||
-- Socks Auth
|
||||
if is_finded("xray") then
|
||||
o = s:option(ListValue, "socks5_auth", translate("Socks5 Auth Mode"), translate("Socks protocol auth methods, default:noauth."))
|
||||
o.default = "noauth"
|
||||
o:value("noauth", "NOAUTH")
|
||||
o:value("password", "PASSWORD")
|
||||
o.rmempty = true
|
||||
for key, server_type in pairs(type_table) do
|
||||
if server_type == "v2ray" then
|
||||
-- 如果服务器类型是 v2ray,则设置依赖项显示
|
||||
o:depends("server", key)
|
||||
end
|
||||
end
|
||||
o:depends({server = "same", disable = true})
|
||||
|
||||
-- Socks User
|
||||
o = s:option(Value, "socks5_user", translate("Socks5 User"), translate("Only when Socks5 Auth Mode is password valid, Mandatory."))
|
||||
o.rmempty = true
|
||||
o:depends("socks5_auth", "password")
|
||||
|
||||
-- Socks Password
|
||||
o = s:option(Value, "socks5_pass", translate("Socks5 Password"), translate("Only when Socks5 Auth Mode is password valid, Not mandatory."))
|
||||
o.password = true
|
||||
o.rmempty = true
|
||||
o:depends("socks5_auth", "password")
|
||||
|
||||
-- Socks Mixed
|
||||
o = s:option(Flag, "socks5_mixed", translate("Enabled Mixed"), translate("Mixed as an alias of socks, default:Enabled."))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
for key, server_type in pairs(type_table) do
|
||||
if server_type == "v2ray" then
|
||||
-- 如果服务器类型是 v2ray,则设置依赖项显示
|
||||
o:depends("server", key)
|
||||
end
|
||||
end
|
||||
o:depends({server = "same", disable = true})
|
||||
end
|
||||
|
||||
-- Local Port
|
||||
o = s:option(Value, "local_port", translate("Local Port"))
|
||||
o.datatype = "port"
|
||||
o.default = 1080
|
||||
o.rmempty = false
|
||||
|
||||
-- [[ fragmen Settings ]]--
|
||||
if is_finded("xray") then
|
||||
s = m:section(TypedSection, "global_xray_fragment", translate("Xray Fragment Settings"))
|
||||
s.anonymous = true
|
||||
|
||||
o = s:option(Flag, "fragment", translate("Fragment"), translate("TCP fragments, which can deceive the censorship system in some cases, such as bypassing SNI blacklists."))
|
||||
o.default = 0
|
||||
|
||||
o = s:option(ListValue, "fragment_packets", translate("Fragment Packets"), translate("\"1-3\" is for segmentation at TCP layer, applying to the beginning 1 to 3 data writes by the client. \"tlshello\" is for TLS client hello packet fragmentation."))
|
||||
o.default = "tlshello"
|
||||
o:value("tlshello", "tlshello")
|
||||
o:value("1-1", "1-1")
|
||||
o:value("1-2", "1-2")
|
||||
o:value("1-3", "1-3")
|
||||
o:value("1-5", "1-5")
|
||||
o:depends("fragment", true)
|
||||
|
||||
o = s:option(Value, "fragment_length", translate("Fragment Length"), translate("Fragmented packet length (byte)"))
|
||||
o.default = "100-200"
|
||||
o:depends("fragment", true)
|
||||
|
||||
o = s:option(Value, "fragment_interval", translate("Fragment Interval"), translate("Fragmentation interval (ms)"))
|
||||
o.default = "10-20"
|
||||
o:depends("fragment", true)
|
||||
|
||||
o = s:option(Value, "fragment_maxsplit", translate("Max Split"), translate("Limit the maximum number of splits."))
|
||||
o.default = "100-200"
|
||||
o:depends("fragment", true)
|
||||
|
||||
o = s:option(Flag, "noise", translate("Noise"), translate("UDP noise, Under some circumstances it can bypass some UDP based protocol restrictions."))
|
||||
o.default = 0
|
||||
|
||||
s = m:section(TypedSection, "xray_noise_packets", translate("Xray Noise Packets"))
|
||||
s.description = translate(
|
||||
"<font style='color:red'>" .. translate("To send noise packets, select \"Noise\" in Xray Settings.") .. "</font>" ..
|
||||
"<br/><font><b>" .. translate("For specific usage, see:") .. "</b></font>" ..
|
||||
"<a href='https://xtls.github.io/config/outbounds/freedom.html' target='_blank'>" ..
|
||||
"<font style='color:green'><b>" .. translate("Click to the page") .. "</b></font></a>")
|
||||
s.template = "cbi/tblsection"
|
||||
s.sortable = true
|
||||
s.anonymous = true
|
||||
s.addremove = true
|
||||
|
||||
s.remove = function(self, section)
|
||||
for k, v in pairs(self.children) do
|
||||
v.rmempty = true
|
||||
v.validate = nil
|
||||
end
|
||||
TypedSection.remove(self, section)
|
||||
end
|
||||
|
||||
o = s:option(Flag, "enabled", translate("Enable"))
|
||||
o.default = 1
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(ListValue, "type", translate("Type"))
|
||||
o.default = "base64"
|
||||
o:value("rand", "rand")
|
||||
o:value("str", "str")
|
||||
o:value("hex", "hex")
|
||||
o:value("base64", "base64")
|
||||
|
||||
o = s:option(Value, "domainStrategy", translate("Domain Strategy"))
|
||||
o.default = "UseIP"
|
||||
o:value("AsIs", "AsIs")
|
||||
o:value("UseIP", "UseIP")
|
||||
o:value("UseIPv4", "UseIPv4")
|
||||
o:value("ForceIP", "ForceIP")
|
||||
o:value("ForceIPv4", "ForceIPv4")
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "packet", translate("Packet"))
|
||||
o.datatype = "minlength(1)"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "delay", translate("Delay (ms)"))
|
||||
o.datatype = "or(uinteger,portrange)"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "applyto", translate("IP Type"))
|
||||
o.default = "IP"
|
||||
o:value("IP", "ALL")
|
||||
o:value("IPV4", "IPv4")
|
||||
o:value("IPV6", "IPv6")
|
||||
o.rmempty = false
|
||||
end
|
||||
|
||||
return m
|
||||
1487
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua
Normal file
1487
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua
Normal file
File diff suppressed because it is too large
Load Diff
283
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua
Normal file
283
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua
Normal file
@@ -0,0 +1,283 @@
|
||||
-- Copyright (C) 2017 yushi studio <ywb94@qq.com> github.com/ywb94
|
||||
-- Copyright (C) 2018 lean <coolsnowwolf@gmail.com> github.com/coolsnowwolf
|
||||
-- Licensed to the public under the GNU General Public License v3.
|
||||
|
||||
local m, s, sec, o
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
|
||||
-- 获取 LAN IP 地址
|
||||
function lanip()
|
||||
local lan_ip
|
||||
lan_ip = luci.sys.exec("uci -q get network.lan.ipaddr 2>/dev/null | awk -F '/' '{print $1}' | tr -d '\n'")
|
||||
|
||||
if not lan_ip or lan_ip == "" then
|
||||
lan_ip = luci.sys.exec("ip address show $(uci -q -p /tmp/state get network.lan.ifname || uci -q -p /tmp/state get network.lan.device) | grep -w 'inet' | grep -Eo 'inet [0-9\.]+' | awk '{print $2}' | head -1 | tr -d '\n'")
|
||||
end
|
||||
|
||||
if not lan_ip or lan_ip == "" then
|
||||
lan_ip = luci.sys.exec("ip addr show | grep -w 'inet' | grep 'global' | grep -Eo 'inet [0-9\.]+' | awk '{print $2}' | head -n 1 | tr -d '\n'")
|
||||
end
|
||||
|
||||
return lan_ip
|
||||
end
|
||||
|
||||
local lan_ip = lanip()
|
||||
local validation = require "luci.cbi.datatypes"
|
||||
local function is_finded(e)
|
||||
return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= ""
|
||||
end
|
||||
|
||||
m = Map("shadowsocksr", translate("ShadowSocksR Plus+ Settings"))
|
||||
m:section(SimpleSection).template = "shadowsocksr/status"
|
||||
|
||||
local server_table = {}
|
||||
uci:foreach("shadowsocksr", "servers", function(s)
|
||||
if s.alias then
|
||||
server_table[s[".name"]] = "[%s]:%s" % {string.upper(s.v2ray_protocol or s.type), s.alias}
|
||||
elseif s.server and s.server_port then
|
||||
server_table[s[".name"]] = "[%s]:%s:%s" % {string.upper(s.v2ray_protocol or s.type), s.server, s.server_port}
|
||||
end
|
||||
end)
|
||||
|
||||
local key_table = {}
|
||||
for key, _ in pairs(server_table) do
|
||||
table.insert(key_table, key)
|
||||
end
|
||||
|
||||
table.sort(key_table)
|
||||
|
||||
-- [[ Global Setting ]]--
|
||||
s = m:section(TypedSection, "global")
|
||||
s.anonymous = true
|
||||
|
||||
o = s:option(ListValue, "global_server", translate("Main Server"))
|
||||
o:value("nil", translate("Disable"))
|
||||
for _, key in pairs(key_table) do
|
||||
o:value(key, server_table[key])
|
||||
end
|
||||
o.default = "nil"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(ListValue, "udp_relay_server", translate("Game Mode UDP Server"))
|
||||
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
|
||||
|
||||
if uci:get_first("shadowsocksr", 'global', 'netflix_enable', '0') == '1' then
|
||||
o = s:option(ListValue, "netflix_server", translate("Netflix Node"))
|
||||
o:value("nil", 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.default = "nil"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Flag, "netflix_proxy", translate("External Proxy Mode"))
|
||||
o.rmempty = false
|
||||
o.description = translate("Forward Netflix Proxy through Main Proxy")
|
||||
o.default = "0"
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "threads", translate("Multi Threads Option"))
|
||||
o:value("0", translate("Auto Threads"))
|
||||
o:value("1", translate("1 Thread"))
|
||||
o:value("2", translate("2 Threads"))
|
||||
o:value("4", translate("4 Threads"))
|
||||
o:value("8", translate("8 Threads"))
|
||||
o:value("16", translate("16 Threads"))
|
||||
o:value("32", translate("32 Threads"))
|
||||
o:value("64", translate("64 Threads"))
|
||||
o:value("128", translate("128 Threads"))
|
||||
o.default = "0"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(ListValue, "run_mode", translate("Running Mode"))
|
||||
o:value("gfw", translate("GFW List Mode"))
|
||||
o:value("router", translate("IP Route Mode"))
|
||||
o:value("all", translate("Global Mode"))
|
||||
o:value("oversea", translate("Oversea Mode"))
|
||||
o.default = gfw
|
||||
|
||||
o = s:option(ListValue, "dports", translate("Proxy Ports"))
|
||||
o:value("1", translate("All Ports"))
|
||||
o:value("2", translate("Only Common Ports"))
|
||||
o:value("3", translate("Custom Ports"))
|
||||
cp = s:option(Value, "custom_ports", translate("Enter Custom Ports"))
|
||||
cp:depends("dports", "3") -- 仅当用户选择“Custom Ports”时显示
|
||||
cp.placeholder = "e.g., 80,443,8080"
|
||||
o.default = 1
|
||||
|
||||
o = s:option(ListValue, "pdnsd_enable", translate("Resolve Dns Mode"))
|
||||
if is_finded("dns2tcp") then
|
||||
o:value("1", translate("Use DNS2TCP query"))
|
||||
end
|
||||
if is_finded("dns2socks") then
|
||||
o:value("2", translate("Use DNS2SOCKS query and cache"))
|
||||
end
|
||||
if is_finded("dns2socks-rust") then
|
||||
o:value("3", translate("Use DNS2SOCKS-RUST query and cache"))
|
||||
end
|
||||
if is_finded("mosdns") then
|
||||
o:value("4", translate("Use MOSDNS query (Not Support Oversea Mode)"))
|
||||
end
|
||||
if is_finded("dnsproxy") then
|
||||
o:value("5", translate("Use DNSPROXY query and cache"))
|
||||
end
|
||||
if is_finded("chinadns-ng") then
|
||||
o:value("6", translate("Use ChinaDNS-NG query and cache"))
|
||||
end
|
||||
o:value("0", translate("Use Local DNS Service listen port 5335"))
|
||||
o.default = 1
|
||||
|
||||
o = s:option(Value, "tunnel_forward", translate("Anti-pollution DNS Server"))
|
||||
o:value("8.8.4.4:53", translate("Google Public DNS (8.8.4.4)"))
|
||||
o:value("8.8.8.8:53", translate("Google Public DNS (8.8.8.8)"))
|
||||
o:value("208.67.222.222:53", translate("OpenDNS (208.67.222.222)"))
|
||||
o:value("208.67.220.220:53", translate("OpenDNS (208.67.220.220)"))
|
||||
o:value("209.244.0.3:53", translate("Level 3 Public DNS (209.244.0.3)"))
|
||||
o:value("209.244.0.4:53", translate("Level 3 Public DNS (209.244.0.4)"))
|
||||
o:value("4.2.2.1:53", translate("Level 3 Public DNS (4.2.2.1)"))
|
||||
o:value("4.2.2.2:53", translate("Level 3 Public DNS (4.2.2.2)"))
|
||||
o:value("4.2.2.3:53", translate("Level 3 Public DNS (4.2.2.3)"))
|
||||
o:value("4.2.2.4:53", translate("Level 3 Public DNS (4.2.2.4)"))
|
||||
o:value("1.1.1.1:53", translate("Cloudflare DNS (1.1.1.1)"))
|
||||
o:value("114.114.114.114:53", translate("Oversea Mode DNS-1 (114.114.114.114)"))
|
||||
o:value("114.114.115.115:53", translate("Oversea Mode DNS-2 (114.114.115.115)"))
|
||||
o:depends("pdnsd_enable", "1")
|
||||
o:depends("pdnsd_enable", "2")
|
||||
o:depends("pdnsd_enable", "3")
|
||||
o.description = translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)")
|
||||
o.datatype = "ip4addrport"
|
||||
o.default = "8.8.4.4:53"
|
||||
|
||||
o = s:option(ListValue, "tunnel_forward_mosdns", translate("Anti-pollution DNS Server"))
|
||||
o:value("tcp://8.8.4.4:53,tcp://8.8.8.8:53", translate("Google Public DNS"))
|
||||
o:value("tcp://208.67.222.222:53,tcp://208.67.220.220:53", translate("OpenDNS"))
|
||||
o:value("tcp://209.244.0.3:53,tcp://209.244.0.4:53", translate("Level 3 Public DNS-1 (209.244.0.3-4)"))
|
||||
o:value("tcp://4.2.2.1:53,tcp://4.2.2.2:53", translate("Level 3 Public DNS-2 (4.2.2.1-2)"))
|
||||
o:value("tcp://4.2.2.3:53,tcp://4.2.2.4:53", translate("Level 3 Public DNS-3 (4.2.2.3-4)"))
|
||||
o:value("tcp://1.1.1.1:53,tcp://1.0.0.1:53", translate("Cloudflare DNS"))
|
||||
o:depends("pdnsd_enable", "4")
|
||||
o.description = translate("Custom DNS Server for MosDNS")
|
||||
|
||||
o = s:option(Flag, "mosdns_ipv6", translate("Disable IPv6 in MOSDNS query mode"))
|
||||
o:depends("pdnsd_enable", "4")
|
||||
o.rmempty = false
|
||||
o.default = "1"
|
||||
|
||||
if is_finded("dnsproxy") then
|
||||
o = s:option(ListValue, "parse_method", translate("Select DNS parse Mode"))
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("When use DNS list file, please ensure list file exists and is formatted correctly.") .. "</li>" ..
|
||||
"<li>" .. translate("Tips: Dnsproxy DNS Parse List Path:") ..
|
||||
" <a href='http://" .. lan_ip .. "/cgi-bin/luci/admin/services/shadowsocksr/control' target='_blank'>" ..
|
||||
translate("Click here to view or manage the DNS list file") .. "</a>" .. "</li>" ..
|
||||
"</ul>"
|
||||
)
|
||||
o:value("single_dns", translate("Set Single DNS"))
|
||||
o:value("parse_file", translate("Use DNS List File"))
|
||||
o:depends("pdnsd_enable", "5")
|
||||
o.rmempty = true
|
||||
o.default = "single_dns"
|
||||
|
||||
o = s:option(Value, "dnsproxy_tunnel_forward", translate("Anti-pollution DNS Server"))
|
||||
o:value("sdns://AgUAAAAAAAAABzguOC40LjQgsKKKE4EwvtIbNjGjagI2607EdKSVHowYZtyvD9iPrkkHOC44LjQuNAovZG5zLXF1ZXJ5", translate("Google DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAAACC2vD25TAYM7EnyCH8Xw1-0g5OccnTsGH9vQUUH0njRtAxkbnMudHduaWMudHcKL2Rucy1xdWVyeQ", translate("TWNIC-101 DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAADzE4NS4yMjIuMjIyLjIyMiAOp5Svj-oV-Fz-65-8H2VKHLKJ0egmfEgrdPeAQlUFFA8xODUuMjIyLjIyMi4yMjIKL2Rucy1xdWVyeQ", translate("dns.sb DNSCrypt SDNS"))
|
||||
o:value("sdns://AgMAAAAAAAAADTE0OS4xMTIuMTEyLjkgsBkgdEu7dsmrBT4B4Ht-BQ5HPSD3n3vqQ1-v5DydJC8SZG5zOS5xdWFkOS5uZXQ6NDQzCi9kbnMtcXVlcnk", translate("Quad9 DNSCrypt SDNS"))
|
||||
o:value("sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", translate("AdGuard DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAABzEuMC4wLjGgENk8mGSlIfMGXMOlIlCcKvq7AVgcrZxtjon911-ep0cg63Ul-I8NlFj4GplQGb_TTLiczclX57DvMV8Q-JdjgRgSZG5zLmNsb3VkZmxhcmUuY29tCi9kbnMtcXVlcnk", translate("Cloudflare DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAADjEwNC4xNi4yNDkuMjQ5ABJjbG91ZGZsYXJlLWRucy5jb20KL2Rucy1xdWVyeQ", translate("cloudflare-dns.com DNSCrypt SDNS"))
|
||||
o:depends("parse_method", "single_dns")
|
||||
o.description = translate("Custom DNS Server (support: IP:Port or tls://IP:Port or https://IP/dns-query and other format).")
|
||||
|
||||
o = s:option(ListValue, "upstreams_logic_mode", translate("Defines the upstreams logic mode"))
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("Defines the upstreams logic mode, possible values: load_balance, parallel, fastest_addr (default: load_balance).") .. "</li>" ..
|
||||
"<li>" .. translate("When two or more DNS servers are deployed, enable this function.") .. "</li>" ..
|
||||
"</ul>"
|
||||
)
|
||||
o:value("load_balance", translate("load_balance"))
|
||||
o:value("parallel", translate("parallel"))
|
||||
o:value("fastest_addr", translate("fastest_addr"))
|
||||
o:depends("parse_method", "parse_file")
|
||||
o.rmempty = true
|
||||
o.default = "load_balance"
|
||||
|
||||
o = s:option(Flag, "dnsproxy_ipv6", translate("Disable IPv6 query mode"))
|
||||
o.description = translate("When disabled, all AAAA requests are not resolved.")
|
||||
o:depends("parse_method", "single_dns")
|
||||
o:depends("parse_method", "parse_file")
|
||||
o.rmempty = false
|
||||
o.default = "1"
|
||||
end
|
||||
|
||||
if is_finded("chinadns-ng") then
|
||||
o = s:option(Value, "chinadns_ng_tunnel_forward", translate("Anti-pollution DNS Server"))
|
||||
o:value("8.8.4.4:53", translate("Google Public DNS (8.8.4.4)"))
|
||||
o:value("8.8.8.8:53", translate("Google Public DNS (8.8.8.8)"))
|
||||
o:value("208.67.222.222:53", translate("OpenDNS (208.67.222.222)"))
|
||||
o:value("208.67.220.220:53", translate("OpenDNS (208.67.220.220)"))
|
||||
o:value("209.244.0.3:53", translate("Level 3 Public DNS (209.244.0.3)"))
|
||||
o:value("209.244.0.4:53", translate("Level 3 Public DNS (209.244.0.4)"))
|
||||
o:value("4.2.2.1:53", translate("Level 3 Public DNS (4.2.2.1)"))
|
||||
o:value("4.2.2.2:53", translate("Level 3 Public DNS (4.2.2.2)"))
|
||||
o:value("4.2.2.3:53", translate("Level 3 Public DNS (4.2.2.3)"))
|
||||
o:value("4.2.2.4:53", translate("Level 3 Public DNS (4.2.2.4)"))
|
||||
o:value("1.1.1.1:53", translate("Cloudflare DNS (1.1.1.1)"))
|
||||
o:depends("pdnsd_enable", "6")
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)") .. "</li>" ..
|
||||
"<li>" .. translate("Muitiple DNS server can saperate with ','") .. "</li>" ..
|
||||
"</ul>"
|
||||
)
|
||||
|
||||
o = s:option(ListValue, "chinadns_ng_proto", translate("ChinaDNS-NG query protocol"))
|
||||
o:value("none", translate("UDP/TCP upstream"))
|
||||
o:value("tcp", translate("TCP upstream"))
|
||||
o:value("udp", translate("UDP upstream"))
|
||||
o:value("tls", translate("DoT upstream (Need use wolfssl version)"))
|
||||
o:depends("pdnsd_enable", "6")
|
||||
|
||||
o = s:option(Value, "chinadns_forward", translate("Domestic DNS Server"))
|
||||
o:value("", translate("Disable ChinaDNS-NG"))
|
||||
o:value("wan", translate("Use DNS from WAN"))
|
||||
o:value("wan_114", translate("Use DNS from WAN and 114DNS"))
|
||||
o:value("114.114.114.114:53", translate("Nanjing Xinfeng 114DNS (114.114.114.114)"))
|
||||
o:value("119.29.29.29:53", translate("DNSPod Public DNS (119.29.29.29)"))
|
||||
o:value("223.5.5.5:53", translate("AliYun Public DNS (223.5.5.5)"))
|
||||
o:value("180.76.76.76:53", translate("Baidu Public DNS (180.76.76.76)"))
|
||||
o:value("101.226.4.6:53", translate("360 Security DNS (China Telecom) (101.226.4.6)"))
|
||||
o:value("123.125.81.6:53", translate("360 Security DNS (China Unicom) (123.125.81.6)"))
|
||||
o:value("1.2.4.8:53", translate("CNNIC SDNS (1.2.4.8)"))
|
||||
o:depends({pdnsd_enable = "1", run_mode = "router"})
|
||||
o:depends({pdnsd_enable = "2", run_mode = "router"})
|
||||
o:depends({pdnsd_enable = "3", run_mode = "router"})
|
||||
o:depends({pdnsd_enable = "5", run_mode = "router"})
|
||||
o:depends({pdnsd_enable = "6", run_mode = "router"})
|
||||
o.description = translate("Custom DNS Server format as IP:PORT (default: disabled)")
|
||||
o.validate = function(self, value, section)
|
||||
if (section and value) then
|
||||
if value == "wan" or value == "wan_114" then
|
||||
return value
|
||||
end
|
||||
|
||||
if validation.ip4addrport(value) then
|
||||
return value
|
||||
end
|
||||
|
||||
return nil, translate("Expecting: %s"):format(translate("valid address:port"))
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
||||
|
||||
173
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua
Normal file
173
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua
Normal file
@@ -0,0 +1,173 @@
|
||||
require "luci.ip"
|
||||
require "nixio.fs"
|
||||
require "luci.sys"
|
||||
local m, s, o
|
||||
|
||||
local function is_finded(e)
|
||||
return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= ""
|
||||
end
|
||||
|
||||
m = Map("shadowsocksr")
|
||||
|
||||
s = m:section(TypedSection, "access_control")
|
||||
s.anonymous = true
|
||||
|
||||
-- Interface control
|
||||
s:tab("Interface", translate("Interface control"))
|
||||
o = s:taboption("Interface", DynamicList, "Interface", translate("Interface"))
|
||||
o.template = "cbi/network_netlist"
|
||||
o.widget = "checkbox"
|
||||
o.nocreate = true
|
||||
o.unspecified = true
|
||||
o.description = translate("Listen only on the given interface or, if unspecified, on all")
|
||||
|
||||
-- Part of WAN
|
||||
s:tab("wan_ac", translate("WAN IP AC"))
|
||||
|
||||
o = s:taboption("wan_ac", DynamicList, "wan_bp_ips", translate("WAN White List IP"))
|
||||
o.datatype = "ip4addr"
|
||||
|
||||
o = s:taboption("wan_ac", DynamicList, "wan_fw_ips", translate("WAN Force Proxy IP"))
|
||||
o.datatype = "ip4addr"
|
||||
|
||||
-- Part of LAN
|
||||
s:tab("lan_ac", translate("LAN IP AC"))
|
||||
|
||||
o = s:taboption("lan_ac", ListValue, "lan_ac_mode", translate("LAN Access Control"))
|
||||
o:value("0", translate("Disable"))
|
||||
o:value("w", translate("Allow listed only"))
|
||||
o:value("b", translate("Allow all except listed"))
|
||||
o.rmempty = false
|
||||
|
||||
o = s:taboption("lan_ac", DynamicList, "lan_ac_ips", translate("LAN Host List"))
|
||||
o.datatype = "ipaddr"
|
||||
luci.ip.neighbors({family = 4}, function(entry)
|
||||
if entry.reachable then
|
||||
o:value(entry.dest:string())
|
||||
end
|
||||
end)
|
||||
o:depends("lan_ac_mode", "w")
|
||||
o:depends("lan_ac_mode", "b")
|
||||
|
||||
o = s:taboption("lan_ac", DynamicList, "lan_bp_ips", translate("LAN Bypassed Host List"))
|
||||
o.datatype = "ipaddr"
|
||||
luci.ip.neighbors({family = 4}, function(entry)
|
||||
if entry.reachable then
|
||||
o:value(entry.dest:string())
|
||||
end
|
||||
end)
|
||||
|
||||
o = s:taboption("lan_ac", DynamicList, "lan_fp_ips", translate("LAN Force Proxy Host List"))
|
||||
o.datatype = "ipaddr"
|
||||
luci.ip.neighbors({family = 4}, function(entry)
|
||||
if entry.reachable then
|
||||
o:value(entry.dest:string())
|
||||
end
|
||||
end)
|
||||
|
||||
o = s:taboption("lan_ac", DynamicList, "lan_gm_ips", translate("Game Mode Host List"))
|
||||
o.datatype = "ipaddr"
|
||||
luci.ip.neighbors({family = 4}, function(entry)
|
||||
if entry.reachable then
|
||||
o:value(entry.dest:string())
|
||||
end
|
||||
end)
|
||||
|
||||
-- Part of Self
|
||||
-- s:tab("self_ac", translate("Router Self AC"))
|
||||
-- o = s:taboption("self_ac",ListValue, "router_proxy", translate("Router Self Proxy"))
|
||||
-- o:value("1", translatef("Normal Proxy"))
|
||||
-- o:value("0", translatef("Bypassed Proxy"))
|
||||
-- o:value("2", translatef("Forwarded Proxy"))
|
||||
-- o.rmempty = false
|
||||
|
||||
s:tab("esc", translate("Bypass Domain List"))
|
||||
local escconf = "/etc/ssrplus/white.list"
|
||||
o = s:taboption("esc", TextValue, "escconf")
|
||||
o.rows = 13
|
||||
o.wrap = "off"
|
||||
o.rmempty = true
|
||||
o.cfgvalue = function(self, section)
|
||||
return nixio.fs.readfile(escconf) or ""
|
||||
end
|
||||
o.write = function(self, section, value)
|
||||
nixio.fs.writefile(escconf, value:gsub("\r\n", "\n"))
|
||||
end
|
||||
o.remove = function(self, section, value)
|
||||
nixio.fs.writefile(escconf, "")
|
||||
end
|
||||
|
||||
s:tab("block", translate("Black Domain List"))
|
||||
local blockconf = "/etc/ssrplus/black.list"
|
||||
o = s:taboption("block", TextValue, "blockconf")
|
||||
o.rows = 13
|
||||
o.wrap = "off"
|
||||
o.rmempty = true
|
||||
o.cfgvalue = function(self, section)
|
||||
return nixio.fs.readfile(blockconf) or " "
|
||||
end
|
||||
o.write = function(self, section, value)
|
||||
nixio.fs.writefile(blockconf, value:gsub("\r\n", "\n"))
|
||||
end
|
||||
o.remove = function(self, section, value)
|
||||
nixio.fs.writefile(blockconf, "")
|
||||
end
|
||||
|
||||
s:tab("denydomain", translate("Deny Domain List"))
|
||||
local denydomainconf = "/etc/ssrplus/deny.list"
|
||||
o = s:taboption("denydomain", TextValue, "denydomainconf")
|
||||
o.rows = 13
|
||||
o.wrap = "off"
|
||||
o.rmempty = true
|
||||
o.cfgvalue = function(self, section)
|
||||
return nixio.fs.readfile(denydomainconf) or " "
|
||||
end
|
||||
o.write = function(self, section, value)
|
||||
nixio.fs.writefile(denydomainconf, value:gsub("\r\n", "\n"))
|
||||
end
|
||||
o.remove = function(self, section, value)
|
||||
nixio.fs.writefile(denydomainconf, "")
|
||||
end
|
||||
|
||||
s:tab("netflix", translate("Netflix Domain List"))
|
||||
local netflixconf = "/etc/ssrplus/netflix.list"
|
||||
o = s:taboption("netflix", TextValue, "netflixconf")
|
||||
o.rows = 13
|
||||
o.wrap = "off"
|
||||
o.rmempty = true
|
||||
o.cfgvalue = function(self, section)
|
||||
return nixio.fs.readfile(netflixconf) or " "
|
||||
end
|
||||
o.write = function(self, section, value)
|
||||
nixio.fs.writefile(netflixconf, value:gsub("\r\n", "\n"))
|
||||
end
|
||||
o.remove = function(self, section, value)
|
||||
nixio.fs.writefile(netflixconf, "")
|
||||
end
|
||||
|
||||
if is_finded("dnsproxy") then
|
||||
s:tab("dnsproxy", translate("Dnsproxy Parse List"))
|
||||
local dnsproxyconf = "/etc/ssrplus/dnsproxy_dns.list"
|
||||
o = s:taboption("dnsproxy", TextValue, "dnsproxyconf", "", "<font style=color:red>" .. translate("Specifically for edit dnsproxy DNS parse files.") .. "</font>")
|
||||
o.rows = 13
|
||||
o.wrap = "off"
|
||||
o.rmempty = true
|
||||
o.cfgvalue = function(self, section)
|
||||
return nixio.fs.readfile(dnsproxyconf) or " "
|
||||
end
|
||||
o.write = function(self, section, value)
|
||||
nixio.fs.writefile(dnsproxyconf, value:gsub("\r\n", "\n"))
|
||||
end
|
||||
o.remove = function(self, section, value)
|
||||
nixio.fs.writefile(dnsproxyconf, "")
|
||||
end
|
||||
end
|
||||
|
||||
if luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0 then
|
||||
m.apply_on_parse = true
|
||||
function m.on_apply(self)
|
||||
luci.sys.call("/etc/init.d/shadowsocksr reload > /dev/null 2>&1 &")
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
||||
102
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/log.lua
Normal file
102
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/log.lua
Normal file
@@ -0,0 +1,102 @@
|
||||
require "luci.util"
|
||||
require "nixio.fs"
|
||||
require "luci.sys"
|
||||
require "luci.http"
|
||||
|
||||
f = SimpleForm("logview")
|
||||
f.reset = false
|
||||
f.submit = false
|
||||
f:append(Template("shadowsocksr/log"))
|
||||
|
||||
-- 自定义 log 函数
|
||||
function log(...)
|
||||
local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
|
||||
local f, err = io.open("/var/log/ssrplus.log", "a")
|
||||
if f and err == nil then
|
||||
f:write(result .. "\n")
|
||||
f:close()
|
||||
end
|
||||
end
|
||||
|
||||
-- 创建备份与恢复表单
|
||||
fb = SimpleForm('backup-restore')
|
||||
fb.reset = false
|
||||
fb.submit = false
|
||||
s = fb:section(SimpleSection, translate("Backup and Restore"), translate("Backup or Restore Client and Server Configurations.") ..
|
||||
"<br><font style='color:red'><b>" ..
|
||||
translate("Note: Restoring configurations across different versions may cause compatibility issues.") ..
|
||||
"</b></font>")
|
||||
s.anonymous = true
|
||||
s:append(Template("shadowsocksr/backup_restore"))
|
||||
|
||||
-- 定义备份目标文件和目录
|
||||
local backup_targets = {
|
||||
files = {
|
||||
"/etc/config/shadowsocksr"
|
||||
},
|
||||
dirs = {
|
||||
"/etc/ssrplus"
|
||||
}
|
||||
}
|
||||
|
||||
local file_path = '/tmp/shadowsocksr_upload.tar.gz'
|
||||
local temp_dir = '/tmp/shadowsocksr_bak'
|
||||
local fd
|
||||
|
||||
-- 处理文件上传
|
||||
luci.http.setfilehandler(function(meta, chunk, eof)
|
||||
if not fd and meta and meta.name == "ulfile" and chunk then
|
||||
-- 初始化上传处理
|
||||
luci.sys.call("rm -rf " .. temp_dir)
|
||||
nixio.fs.remove(file_path)
|
||||
fd = nixio.open(file_path, "w")
|
||||
luci.sys.call("echo '' > /var/log/ssrplus.log")
|
||||
end
|
||||
|
||||
if fd and chunk then
|
||||
fd:write(chunk)
|
||||
end
|
||||
|
||||
if eof and fd then
|
||||
fd:close()
|
||||
fd = nil
|
||||
if nixio.fs.access(file_path) then
|
||||
log(" * shadowsocksr 配置文件上传成功…") -- 使用自定义的 log 函数
|
||||
luci.sys.call("mkdir -p " .. temp_dir)
|
||||
|
||||
if luci.sys.call("tar -xzf " .. file_path .. " -C " .. temp_dir) == 0 then
|
||||
-- 处理文件还原
|
||||
for _, target in ipairs(backup_targets.files) do
|
||||
local temp_file = temp_dir .. target
|
||||
if nixio.fs.access(temp_file) then
|
||||
luci.sys.call(string.format("cp -f '%s' '%s'", temp_file, target))
|
||||
log(" * 文件 " .. target .. " 还原成功…") -- 使用自定义的 log 函数
|
||||
end
|
||||
end
|
||||
|
||||
-- 处理目录还原
|
||||
for _, target in ipairs(backup_targets.dirs) do
|
||||
local temp_dir_path = temp_dir .. target
|
||||
if nixio.fs.access(temp_dir_path) then
|
||||
luci.sys.call(string.format("cp -rf '%s'/* '%s/'", temp_dir_path, target))
|
||||
log(" * 目录 " .. target .. " 还原成功…") -- 使用自定义的 log 函数
|
||||
end
|
||||
end
|
||||
|
||||
log(" * shadowsocksr 配置还原成功…") -- 使用自定义的 log 函数
|
||||
log(" * 重启 shadowsocksr 服务中…\n") -- 使用自定义的 log 函数
|
||||
luci.sys.call('/etc/init.d/shadowsocksr restart > /dev/null 2>&1 &')
|
||||
else
|
||||
log(" * shadowsocksr 配置文件解压失败,请重试!") -- 使用自定义的 log 函数
|
||||
end
|
||||
else
|
||||
log(" * shadowsocksr 配置文件上传失败,请重试!") -- 使用自定义的 log 函数
|
||||
end
|
||||
|
||||
-- 清理临时文件
|
||||
luci.sys.call("rm -rf " .. temp_dir)
|
||||
nixio.fs.remove(file_path)
|
||||
end
|
||||
end)
|
||||
|
||||
return f, fb
|
||||
@@ -0,0 +1,154 @@
|
||||
-- Copyright (C) 2017 yushi studio <ywb94@qq.com>
|
||||
-- Licensed to the public under the GNU General Public License v3.
|
||||
require "luci.http"
|
||||
require "luci.dispatcher"
|
||||
require "nixio.fs"
|
||||
|
||||
local m, s, o
|
||||
local sid = arg[1]
|
||||
|
||||
local encrypt_methods = {
|
||||
"rc4-md5",
|
||||
"rc4-md5-6",
|
||||
"rc4",
|
||||
"table",
|
||||
"aes-128-cfb",
|
||||
"aes-192-cfb",
|
||||
"aes-256-cfb",
|
||||
"aes-128-ctr",
|
||||
"aes-192-ctr",
|
||||
"aes-256-ctr",
|
||||
"bf-cfb",
|
||||
"camellia-128-cfb",
|
||||
"camellia-192-cfb",
|
||||
"camellia-256-cfb",
|
||||
"cast5-cfb",
|
||||
"des-cfb",
|
||||
"idea-cfb",
|
||||
"rc2-cfb",
|
||||
"seed-cfb",
|
||||
"salsa20",
|
||||
"chacha20",
|
||||
"chacha20-ietf"
|
||||
}
|
||||
|
||||
local encrypt_methods_ss = {
|
||||
-- aead
|
||||
"aes-128-gcm",
|
||||
"aes-192-gcm",
|
||||
"aes-256-gcm",
|
||||
"chacha20-ietf-poly1305",
|
||||
"xchacha20-ietf-poly1305",
|
||||
-- aead 2022
|
||||
"2022-blake3-aes-128-gcm",
|
||||
"2022-blake3-aes-256-gcm",
|
||||
"2022-blake3-chacha20-poly1305"
|
||||
--[[ stream
|
||||
"table",
|
||||
"rc4",
|
||||
"rc4-md5",
|
||||
"aes-128-cfb",
|
||||
"aes-192-cfb",
|
||||
"aes-256-cfb",
|
||||
"aes-128-ctr",
|
||||
"aes-192-ctr",
|
||||
"aes-256-ctr",
|
||||
"bf-cfb",
|
||||
"camellia-128-cfb",
|
||||
"camellia-192-cfb",
|
||||
"camellia-256-cfb",
|
||||
"salsa20",
|
||||
"chacha20",
|
||||
"chacha20-ietf" ]]
|
||||
}
|
||||
|
||||
local protocol = {"origin"}
|
||||
|
||||
obfs = {"plain", "http_simple", "http_post"}
|
||||
|
||||
m = Map("shadowsocksr", translate("Edit ShadowSocksR Server"))
|
||||
|
||||
m.redirect = luci.dispatcher.build_url("admin/services/shadowsocksr/server")
|
||||
if m.uci:get("shadowsocksr", sid) ~= "server_config" then
|
||||
luci.http.redirect(m.redirect)
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ Server Setting ]]--
|
||||
s = m:section(NamedSection, sid, "server_config")
|
||||
s.anonymous = true
|
||||
s.addremove = false
|
||||
|
||||
o = s:option(Flag, "enable", translate("Enable"))
|
||||
o.default = 1
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(ListValue, "type", translate("Server Type"))
|
||||
o:value("socks5", translate("Socks5"))
|
||||
if nixio.fs.access("/usr/bin/ssserver") or nixio.fs.access("/usr/bin/ss-server") then
|
||||
o:value("ss", translate("ShadowSocks"))
|
||||
end
|
||||
if nixio.fs.access("/usr/bin/ssr-server") then
|
||||
o:value("ssr", translate("ShadowsocksR"))
|
||||
end
|
||||
o.default = "socks5"
|
||||
|
||||
o = s:option(Value, "server_port", translate("Server Port"))
|
||||
o.datatype = "port"
|
||||
math.randomseed(tostring(os.time()):reverse():sub(1, 7))
|
||||
o.default = math.random(10240, 20480)
|
||||
o.rmempty = false
|
||||
o.description = translate("warning! Please do not reuse the port!")
|
||||
|
||||
o = s:option(Value, "timeout", translate("Connection Timeout"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 60
|
||||
o.rmempty = false
|
||||
o:depends("type", "ss")
|
||||
o:depends("type", "ssr")
|
||||
|
||||
o = s:option(Value, "username", translate("Username"))
|
||||
o.rmempty = false
|
||||
o:depends("type", "socks5")
|
||||
|
||||
o = s:option(Value, "password", translate("Password"))
|
||||
o.password = true
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(ListValue, "encrypt_method", translate("Encrypt Method"))
|
||||
for _, v in ipairs(encrypt_methods) do
|
||||
o:value(v)
|
||||
end
|
||||
o.rmempty = false
|
||||
o:depends("type", "ssr")
|
||||
|
||||
o = s:option(ListValue, "encrypt_method_ss", translate("Encrypt Method"))
|
||||
for _, v in ipairs(encrypt_methods_ss) do
|
||||
o:value(v)
|
||||
end
|
||||
o.rmempty = false
|
||||
o:depends("type", "ss")
|
||||
|
||||
o = s:option(ListValue, "protocol", translate("Protocol"))
|
||||
for _, v in ipairs(protocol) do
|
||||
o:value(v)
|
||||
end
|
||||
o.rmempty = false
|
||||
o:depends("type", "ssr")
|
||||
|
||||
o = s:option(ListValue, "obfs", translate("Obfs"))
|
||||
for _, v in ipairs(obfs) do
|
||||
o:value(v)
|
||||
end
|
||||
o.rmempty = false
|
||||
o:depends("type", "ssr")
|
||||
|
||||
o = s:option(Value, "obfs_param", translate("Obfs param (optional)"))
|
||||
o:depends("type", "ssr")
|
||||
|
||||
o = s:option(Flag, "fast_open", translate("TCP Fast Open"))
|
||||
o.rmempty = false
|
||||
o:depends("type", "ss")
|
||||
o:depends("type", "ssr")
|
||||
|
||||
return m
|
||||
138
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua
Normal file
138
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua
Normal file
@@ -0,0 +1,138 @@
|
||||
-- Copyright (C) 2017 yushi studio <ywb94@qq.com>
|
||||
-- Licensed to the public under the GNU General Public License v3.
|
||||
require "luci.http"
|
||||
require "luci.dispatcher"
|
||||
local m, sec, o
|
||||
local encrypt_methods = {
|
||||
"table",
|
||||
"rc4",
|
||||
"rc4-md5",
|
||||
"rc4-md5-6",
|
||||
"aes-128-cfb",
|
||||
"aes-192-cfb",
|
||||
"aes-256-cfb",
|
||||
"aes-128-ctr",
|
||||
"aes-192-ctr",
|
||||
"aes-256-ctr",
|
||||
"bf-cfb",
|
||||
"camellia-128-cfb",
|
||||
"camellia-192-cfb",
|
||||
"camellia-256-cfb",
|
||||
"cast5-cfb",
|
||||
"des-cfb",
|
||||
"idea-cfb",
|
||||
"rc2-cfb",
|
||||
"seed-cfb",
|
||||
"salsa20",
|
||||
"chacha20",
|
||||
"chacha20-ietf"
|
||||
}
|
||||
|
||||
local encrypt_methods_ss = {
|
||||
-- aead
|
||||
"aes-128-gcm",
|
||||
"aes-192-gcm",
|
||||
"aes-256-gcm",
|
||||
"chacha20-ietf-poly1305",
|
||||
"xchacha20-ietf-poly1305",
|
||||
-- aead 2022
|
||||
"2022-blake3-aes-128-gcm",
|
||||
"2022-blake3-aes-256-gcm",
|
||||
"2022-blake3-chacha20-poly1305"
|
||||
--[[ stream
|
||||
"table",
|
||||
"rc4",
|
||||
"rc4-md5",
|
||||
"aes-128-cfb",
|
||||
"aes-192-cfb",
|
||||
"aes-256-cfb",
|
||||
"aes-128-ctr",
|
||||
"aes-192-ctr",
|
||||
"aes-256-ctr",
|
||||
"bf-cfb",
|
||||
"camellia-128-cfb",
|
||||
"camellia-192-cfb",
|
||||
"camellia-256-cfb",
|
||||
"salsa20",
|
||||
"chacha20",
|
||||
"chacha20-ietf" ]]
|
||||
}
|
||||
|
||||
local protocol = {
|
||||
"origin",
|
||||
"verify_deflate",
|
||||
"auth_sha1_v4",
|
||||
"auth_aes128_sha1",
|
||||
"auth_aes128_md5",
|
||||
"auth_chain_a"
|
||||
}
|
||||
|
||||
obfs = {
|
||||
"plain",
|
||||
"http_simple",
|
||||
"http_post",
|
||||
"random_head",
|
||||
"tls1.2_ticket_auth",
|
||||
"tls1.2_ticket_fastauth"
|
||||
}
|
||||
|
||||
m = Map("shadowsocksr")
|
||||
-- [[ Global Setting ]]--
|
||||
sec = m:section(TypedSection, "server_global", translate("Global Setting"))
|
||||
sec.anonymous = true
|
||||
|
||||
o = sec:option(Flag, "enable_server", translate("Enable Server"))
|
||||
o.rmempty = false
|
||||
|
||||
-- [[ Server Setting ]]--
|
||||
sec = m:section(TypedSection, "server_config", translate("Server Setting"))
|
||||
sec.anonymous = true
|
||||
sec.addremove = true
|
||||
sec.template = "cbi/tblsection"
|
||||
sec.extedit = luci.dispatcher.build_url("admin/services/shadowsocksr/server/%s")
|
||||
function sec.create(...)
|
||||
local sid = TypedSection.create(...)
|
||||
if sid then
|
||||
luci.http.redirect(sec.extedit % sid)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
o = sec:option(Flag, "enable", translate("Enable"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("0")
|
||||
end
|
||||
o.rmempty = false
|
||||
|
||||
o = sec:option(DummyValue, "type", translate("Server Type"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or "ss"
|
||||
end
|
||||
|
||||
o = sec:option(DummyValue, "server_port", translate("Server Port"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or "-"
|
||||
end
|
||||
|
||||
o = sec:option(DummyValue, "username", translate("Username"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or "-"
|
||||
end
|
||||
|
||||
o = sec:option(DummyValue, "encrypt_method", translate("Encrypt Method"))
|
||||
function o.cfgvalue(self, section)
|
||||
local method = self.map:get(section, "encrypt_method") or self.map:get(section, "encrypt_method_ss")
|
||||
return method and method:upper() or "-"
|
||||
end
|
||||
|
||||
o = sec:option(DummyValue, "protocol", translate("Protocol"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or "-"
|
||||
end
|
||||
|
||||
o = sec:option(DummyValue, "obfs", translate("Obfs"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or "-"
|
||||
end
|
||||
|
||||
return m
|
||||
274
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua
Normal file
274
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua
Normal file
@@ -0,0 +1,274 @@
|
||||
-- Licensed to the public under the GNU General Public License v3.
|
||||
require "luci.http"
|
||||
require "luci.sys"
|
||||
require "nixio.fs"
|
||||
require "luci.dispatcher"
|
||||
require "luci.model.uci"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
|
||||
local m, s, o, node
|
||||
local server_count = 0
|
||||
|
||||
-- 确保正确判断程序是否存在
|
||||
local function is_finded(e)
|
||||
return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= ""
|
||||
end
|
||||
|
||||
-- 优化 CBI UI(新版 LuCI 专用)
|
||||
local function optimize_cbi_ui()
|
||||
luci.http.write([[
|
||||
<script type="text/javascript">
|
||||
// 修正上移、下移按钮名称
|
||||
document.querySelectorAll("input.btn.cbi-button.cbi-button-up").forEach(function(btn) {
|
||||
btn.value = "]] .. translate("Move up") .. [[";
|
||||
});
|
||||
document.querySelectorAll("input.btn.cbi-button.cbi-button-down").forEach(function(btn) {
|
||||
btn.value = "]] .. translate("Move down") .. [[";
|
||||
});
|
||||
// 删除控件和说明之间的多余换行
|
||||
document.querySelectorAll("div.cbi-value-description").forEach(function(descDiv) {
|
||||
var prev = descDiv.previousSibling;
|
||||
while (prev && prev.nodeType === Node.TEXT_NODE && prev.textContent.trim() === "") {
|
||||
prev = prev.previousSibling;
|
||||
}
|
||||
if (prev && prev.nodeType === Node.ELEMENT_NODE && prev.tagName === "BR") {
|
||||
prev.remove();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
]])
|
||||
end
|
||||
|
||||
local has_ss_rust = is_finded("sslocal") or is_finded("ssserver")
|
||||
local has_ss_libev = is_finded("ss-redir") or is_finded("ss-local")
|
||||
|
||||
local ss_type_list = {}
|
||||
|
||||
if has_ss_rust then
|
||||
table.insert(ss_type_list, { id = "ss-rust", name = translate("ShadowSocks-rust Version") })
|
||||
end
|
||||
if has_ss_libev then
|
||||
table.insert(ss_type_list, { id = "ss-libev", name = translate("ShadowSocks-libev Version") })
|
||||
end
|
||||
|
||||
-- 如果用户没有手动设置,则自动选择
|
||||
if ss_type == "" then
|
||||
if has_ss_rust then
|
||||
ss_type = "ss-rust"
|
||||
elseif has_ss_libev then
|
||||
ss_type = "ss-libev"
|
||||
end
|
||||
end
|
||||
|
||||
uci:foreach("shadowsocksr", "servers", function(s)
|
||||
server_count = server_count + 1
|
||||
end)
|
||||
|
||||
m = Map("shadowsocksr", translate("Servers subscription and manage"))
|
||||
|
||||
-- Server Subscribe
|
||||
s = m:section(TypedSection, "server_subscribe")
|
||||
s.anonymous = true
|
||||
|
||||
o = s:option(Flag, "auto_update", translate("Auto Update"))
|
||||
o.rmempty = false
|
||||
o.description = translate("Auto Update Server subscription, GFW list and CHN route")
|
||||
|
||||
o = s:option(ListValue, "auto_update_week_time", translate("Update Time (Every Week)"))
|
||||
o:value('*', translate("Every Day"))
|
||||
o:value("1", translate("Every Monday"))
|
||||
o:value("2", translate("Every Tuesday"))
|
||||
o:value("3", translate("Every Wednesday"))
|
||||
o:value("4", translate("Every Thursday"))
|
||||
o:value("5", translate("Every Friday"))
|
||||
o:value("6", translate("Every Saturday"))
|
||||
o:value("0", translate("Every Sunday"))
|
||||
o.default = "*"
|
||||
o.rmempty = true
|
||||
o:depends("auto_update", "1")
|
||||
|
||||
o = s:option(ListValue, "auto_update_day_time", translate("Update time (every day)"))
|
||||
for t = 0, 23 do
|
||||
o:value(t, t .. ":00")
|
||||
end
|
||||
o.default = 2
|
||||
o.rmempty = true
|
||||
o:depends("auto_update", "1")
|
||||
|
||||
o = s:option(ListValue, "auto_update_min_time", translate("Update Interval (min)"))
|
||||
for i = 0, 59 do
|
||||
o:value(i, i .. ":00")
|
||||
end
|
||||
o.default = 30
|
||||
o.rmempty = true
|
||||
o:depends("auto_update", "1")
|
||||
|
||||
-- 确保 ss_type_list 不为空
|
||||
if #ss_type_list > 0 then
|
||||
o = s:option(ListValue, "ss_type", string.format("<b><span style='color:red;'>%s</span></b>", translate("ShadowSocks Node Use Version")))
|
||||
o.description = translate("Selection ShadowSocks Node Use Version.")
|
||||
for _, v in ipairs(ss_type_list) do
|
||||
o:value(v.id, v.name) -- 存储 "ss-libev" / "ss-rust",但 UI 显示完整名称
|
||||
end
|
||||
o.default = ss_type -- 设置默认值
|
||||
o.write = function(self, section, value)
|
||||
-- 更新 Shadowsocks 节点的 has_ss_type
|
||||
uci:foreach("shadowsocksr", "servers", function(s)
|
||||
local node_type = uci:get("shadowsocksr", s[".name"], "type") -- 获取节点类型
|
||||
if node_type == "ss" then -- 仅修改 Shadowsocks 节点
|
||||
local old_value = uci:get("shadowsocksr", s[".name"], "has_ss_type")
|
||||
if old_value ~= value then
|
||||
uci:set("shadowsocksr", s[".name"], "has_ss_type", value)
|
||||
end
|
||||
end
|
||||
end)
|
||||
-- 更新当前 section 的 ss_type
|
||||
Value.write(self, section, value)
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, "subscribe_url", translate("Subscribe URL"))
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(Value, "filter_words", translate("Subscribe Filter Words"))
|
||||
o.rmempty = true
|
||||
o.description = translate("Filter Words splited by /")
|
||||
|
||||
o = s:option(Value, "save_words", translate("Subscribe Save Words"))
|
||||
o.rmempty = true
|
||||
o.description = translate("Save Words splited by /")
|
||||
|
||||
o = s:option(Button, "update_Sub", translate("Update Subscribe List"))
|
||||
o.inputstyle = "reload"
|
||||
o.description = translate("Update subscribe url list first")
|
||||
o.write = function()
|
||||
uci:commit("shadowsocksr")
|
||||
luci.sys.exec("rm -rf /tmp/sub_md5_*")
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers"))
|
||||
end
|
||||
|
||||
o = s:option(Flag, "allow_insecure", translate("Allow subscribe Insecure nodes By default"))
|
||||
o.rmempty = false
|
||||
o.description = translate("Subscribe nodes allows insecure connection as TLS client (insecure)")
|
||||
o.default = "0"
|
||||
|
||||
o = s:option(Flag, "switch", translate("Subscribe Default Auto-Switch"))
|
||||
o.rmempty = false
|
||||
o.description = translate("Subscribe new add server default Auto-Switch on")
|
||||
o.default = "1"
|
||||
|
||||
o = s:option(Flag, "proxy", translate("Through proxy update"))
|
||||
o.rmempty = false
|
||||
o.description = translate("Through proxy update list, Not Recommended ")
|
||||
|
||||
o = s:option(Button, "subscribe", translate("Update All Subscribe Servers"))
|
||||
o.rawhtml = true
|
||||
o.template = "shadowsocksr/subscribe"
|
||||
|
||||
o = s:option(Button, "delete", translate("Delete All Subscribe Servers"))
|
||||
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 or s.isSubscribe then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end)
|
||||
uci:save("shadowsocksr")
|
||||
uci:commit("shadowsocksr")
|
||||
for file in nixio.fs.glob("/tmp/sub_md5_*") do
|
||||
nixio.fs.remove(file)
|
||||
end
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "delete"))
|
||||
return
|
||||
end
|
||||
|
||||
o = s:option(Value, "user_agent", translate("User-Agent"))
|
||||
o.default = "v2rayN/9.99"
|
||||
o:value("curl", "Curl")
|
||||
o:value("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Linux")
|
||||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Windows")
|
||||
o:value("v2rayN/9.99", "v2rayN")
|
||||
|
||||
-- [[ Servers Manage ]]--
|
||||
s = m:section(TypedSection, "servers")
|
||||
s.anonymous = true
|
||||
s.addremove = true
|
||||
s.template = "cbi/tblsection"
|
||||
s.sortable = true
|
||||
s.extedit = luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers", "%s")
|
||||
function s.create(...)
|
||||
local sid = TypedSection.create(...)
|
||||
if sid then
|
||||
luci.http.redirect(s.extedit % sid)
|
||||
return
|
||||
end
|
||||
end
|
||||
s.render = function(self, ...)
|
||||
Map.render(self, ...)
|
||||
if type(optimize_cbi_ui) == "function" then
|
||||
optimize_cbi_ui()
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "type", translate("Type"))
|
||||
function o.cfgvalue(self, section)
|
||||
return m:get(section, "v2ray_protocol") or Value.cfgvalue(self, section) or translate("None")
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "alias", translate("Alias"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("None")
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "server_port", translate("Server Port"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or "N/A"
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "server_port", translate("Socket Connected"))
|
||||
o.template = "shadowsocksr/socket"
|
||||
o.width = "10%"
|
||||
o.render = function(self, section, scope)
|
||||
self.transport = s:cfgvalue(section).transport
|
||||
if self.transport == 'ws' then
|
||||
self.ws_path = s:cfgvalue(section).ws_path
|
||||
self.tls = s:cfgvalue(section).tls
|
||||
end
|
||||
DummyValue.render(self, section, scope)
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "server", translate("Ping Latency"))
|
||||
o.template = "shadowsocksr/ping"
|
||||
o.width = "10%"
|
||||
|
||||
local global_server = uci:get_first('shadowsocksr', 'global', 'global_server')
|
||||
|
||||
node = s:option(Button, "apply_node", translate("Apply"))
|
||||
node.inputstyle = "apply"
|
||||
node.render = function(self, section, scope)
|
||||
if section == global_server then
|
||||
self.title = translate("Reapply")
|
||||
else
|
||||
self.title = translate("Apply")
|
||||
end
|
||||
Button.render(self, section, scope)
|
||||
end
|
||||
node.write = function(self, section)
|
||||
uci:set("shadowsocksr", '@global[0]', 'global_server', section)
|
||||
uci:save("shadowsocksr")
|
||||
uci:commit("shadowsocksr")
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "restart"))
|
||||
end
|
||||
|
||||
o = s:option(Flag, "switch_enable", translate("Auto Switch"))
|
||||
o.rmempty = false
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or 1
|
||||
end
|
||||
|
||||
m:append(Template("shadowsocksr/server_list"))
|
||||
|
||||
return m
|
||||
213
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua
Normal file
213
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua
Normal file
@@ -0,0 +1,213 @@
|
||||
-- Copyright (C) 2017 yushi studio <ywb94@qq.com>
|
||||
-- Licensed to the public under the GNU General Public License v3.
|
||||
require "nixio.fs"
|
||||
require "luci.sys"
|
||||
require "luci.model.uci"
|
||||
local m, s, o
|
||||
local redir_run = 0
|
||||
local reudp_run = 0
|
||||
local sock5_run = 0
|
||||
local server_run = 0
|
||||
local kcptun_run = 0
|
||||
local tunnel_run = 0
|
||||
local gfw_count = 0
|
||||
local ad_count = 0
|
||||
local ip_count = 0
|
||||
local nfip_count = 0
|
||||
local Process_list = luci.sys.exec("busybox ps -w")
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
-- html constants
|
||||
font_blue = [[<b style=color:green>]]
|
||||
style_blue = [[<b style=color:red>]]
|
||||
font_off = [[</b>]]
|
||||
bold_on = [[<strong>]]
|
||||
bold_off = [[</strong>]]
|
||||
local kcptun_version = translate("Unknown")
|
||||
local kcp_file = "/usr/bin/kcptun-client"
|
||||
if not nixio.fs.access(kcp_file) then
|
||||
kcptun_version = translate("Not exist")
|
||||
else
|
||||
if not nixio.fs.access(kcp_file, "rwx", "rx", "rx") then
|
||||
nixio.fs.chmod(kcp_file, 755)
|
||||
end
|
||||
kcptun_version = "<b>" ..luci.sys.exec(kcp_file .. " -v | awk '{printf $3}'") .. "</b>"
|
||||
if not kcptun_version or kcptun_version == "" then
|
||||
kcptun_version = translate("Unknown")
|
||||
end
|
||||
end
|
||||
|
||||
if nixio.fs.access("/etc/ssrplus/gfw_list.conf") then
|
||||
gfw_count = tonumber(luci.sys.exec("cat /etc/ssrplus/gfw_list.conf | wc -l")) / 2
|
||||
end
|
||||
|
||||
if nixio.fs.access("/etc/ssrplus/ad.conf") then
|
||||
ad_count = tonumber(luci.sys.exec("cat /etc/ssrplus/ad.conf | wc -l"))
|
||||
end
|
||||
|
||||
if nixio.fs.access("/etc/ssrplus/china_ssr.txt") then
|
||||
ip_count = tonumber(luci.sys.exec("cat /etc/ssrplus/china_ssr.txt | wc -l"))
|
||||
end
|
||||
|
||||
if nixio.fs.access("/etc/ssrplus/applechina.conf") then
|
||||
apple_count = tonumber(luci.sys.exec("cat /etc/ssrplus/applechina.conf | wc -l"))
|
||||
end
|
||||
|
||||
if nixio.fs.access("/etc/ssrplus/netflixip.list") then
|
||||
nfip_count = tonumber(luci.sys.exec("cat /etc/ssrplus/netflixip.list | wc -l"))
|
||||
end
|
||||
|
||||
if Process_list:find("udp.only.ssr.reudp") then
|
||||
reudp_run = 1
|
||||
end
|
||||
|
||||
if Process_list:find("tcp.only.ssr.retcp") then
|
||||
redir_run = 1
|
||||
end
|
||||
|
||||
if Process_list:find("tcp.udp.ssr.local") then
|
||||
sock5_run = 1
|
||||
end
|
||||
|
||||
if Process_list:find("tcp.udp.ssr.retcp") then
|
||||
redir_run = 1
|
||||
reudp_run = 1
|
||||
end
|
||||
|
||||
if Process_list:find("local.ssr.retcp") then
|
||||
redir_run = 1
|
||||
sock5_run = 1
|
||||
end
|
||||
|
||||
if Process_list:find("local.udp.ssr.retcp") then
|
||||
reudp_run = 1
|
||||
redir_run = 1
|
||||
sock5_run = 1
|
||||
end
|
||||
|
||||
if Process_list:find("kcptun.client") then
|
||||
kcptun_run = 1
|
||||
end
|
||||
|
||||
if Process_list:find("ssr.server") then
|
||||
server_run = 1
|
||||
end
|
||||
|
||||
if Process_list:find("ssrplus/bin/dns2tcp") or
|
||||
Process_list:find("ssrplus/bin/mosdns") or
|
||||
Process_list:find("dnsproxy.*127.0.0.1.*5335") or
|
||||
Process_list:find("chinadns.*127.0.0.1.*5335") or
|
||||
(Process_list:find("ssrplus.dns") and Process_list:find("dns2socks.*127.0.0.1.*127.0.0.1.5335")) then
|
||||
pdnsd_run = 1
|
||||
end
|
||||
|
||||
m = SimpleForm("Version")
|
||||
m.reset = false
|
||||
m.submit = false
|
||||
|
||||
s = m:field(DummyValue, "redir_run", translate("Global Client"))
|
||||
s.rawhtml = true
|
||||
if redir_run == 1 then
|
||||
s.value = font_blue .. bold_on .. translate("Running") .. bold_off .. font_off
|
||||
else
|
||||
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
|
||||
end
|
||||
|
||||
s = m:field(DummyValue, "reudp_run", translate("Game Mode UDP Relay"))
|
||||
s.rawhtml = true
|
||||
if reudp_run == 1 then
|
||||
s.value = font_blue .. bold_on .. translate("Running") .. bold_off .. font_off
|
||||
else
|
||||
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
|
||||
end
|
||||
|
||||
if uci:get_first("shadowsocksr", 'global', 'pdnsd_enable', '0') ~= '0' then
|
||||
s = m:field(DummyValue, "pdnsd_run", translate("DNS Anti-pollution"))
|
||||
s.rawhtml = true
|
||||
if pdnsd_run == 1 then
|
||||
s.value = font_blue .. bold_on .. translate("Running") .. bold_off .. font_off
|
||||
else
|
||||
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
|
||||
end
|
||||
end
|
||||
|
||||
s = m:field(DummyValue, "sock5_run", translate("Global SOCKS5 Proxy Server"))
|
||||
s.rawhtml = true
|
||||
if sock5_run == 1 then
|
||||
s.value = font_blue .. bold_on .. translate("Running") .. bold_off .. font_off
|
||||
else
|
||||
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
|
||||
end
|
||||
|
||||
s = m:field(DummyValue, "server_run", translate("Local Servers"))
|
||||
s.rawhtml = true
|
||||
if server_run == 1 then
|
||||
s.value = font_blue .. bold_on .. translate("Running") .. bold_off .. font_off
|
||||
else
|
||||
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
|
||||
end
|
||||
|
||||
if nixio.fs.access("/usr/bin/kcptun-client") then
|
||||
s = m:field(DummyValue, "kcp_version", translate("KcpTun Version"))
|
||||
s.rawhtml = true
|
||||
s.value = kcptun_version
|
||||
s = m:field(DummyValue, "kcptun_run", translate("KcpTun"))
|
||||
s.rawhtml = true
|
||||
if kcptun_run == 1 then
|
||||
s.value = font_blue .. bold_on .. translate("Running") .. bold_off .. font_off
|
||||
else
|
||||
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
|
||||
end
|
||||
end
|
||||
|
||||
s = m:field(Button, "Restart", translate("Restart ShadowSocksR Plus+"))
|
||||
s.inputtitle = translate("Restart Service")
|
||||
s.inputstyle = "reload"
|
||||
s.write = function()
|
||||
luci.sys.call("/etc/init.d/shadowsocksr restart >/dev/null 2>&1 &")
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "client"))
|
||||
end
|
||||
|
||||
s = m:field(DummyValue, "google", translate("Google Connectivity"))
|
||||
s.value = translate("No Check")
|
||||
s.template = "shadowsocksr/check"
|
||||
|
||||
s = m:field(DummyValue, "baidu", translate("Baidu Connectivity"))
|
||||
s.value = translate("No Check")
|
||||
s.template = "shadowsocksr/check"
|
||||
|
||||
s = m:field(DummyValue, "gfw_data", translate("GFW List Data"))
|
||||
s.rawhtml = true
|
||||
s.template = "shadowsocksr/refresh"
|
||||
s.value = gfw_count .. " " .. translate("Records")
|
||||
|
||||
s = m:field(DummyValue, "ip_data", translate("China IP Data"))
|
||||
s.rawhtml = true
|
||||
s.template = "shadowsocksr/refresh"
|
||||
s.value = ip_count .. " " .. translate("Records")
|
||||
|
||||
if uci:get_first("shadowsocksr", 'global', 'apple_optimization', '0') ~= '0' then
|
||||
s = m:field(DummyValue, "apple_data", translate("Apple Domains Data"))
|
||||
s.rawhtml = true
|
||||
s.template = "shadowsocksr/refresh"
|
||||
s.value = apple_count .. " " .. translate("Records")
|
||||
end
|
||||
|
||||
if uci:get_first("shadowsocksr", 'global', 'netflix_enable', '0') ~= '0' then
|
||||
s = m:field(DummyValue, "nfip_data", translate("Netflix IP Data"))
|
||||
s.rawhtml = true
|
||||
s.template = "shadowsocksr/refresh"
|
||||
s.value = nfip_count .. " " .. translate("Records")
|
||||
end
|
||||
|
||||
if uci:get_first("shadowsocksr", 'global', 'adblock', '0') == '1' then
|
||||
s = m:field(DummyValue, "ad_data", translate("Advertising Data"))
|
||||
s.rawhtml = true
|
||||
s.template = "shadowsocksr/refresh"
|
||||
s.value = ad_count .. " " .. translate("Records")
|
||||
end
|
||||
|
||||
s = m:field(DummyValue, "check_port", translate("Check Server Port"))
|
||||
s.template = "shadowsocksr/checkport"
|
||||
s.value = translate("No Check")
|
||||
|
||||
return m
|
||||
152
luci-app-ssr-plus/luasrc/view/shadowsocksr/backup_restore.htm
Normal file
152
luci-app-ssr-plus/luasrc/view/shadowsocksr/backup_restore.htm
Normal file
@@ -0,0 +1,152 @@
|
||||
<div class="cbi-value" id="_backup_div">
|
||||
<label class="cbi-value-title"><%:Create Backup File%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="btn cbi-button cbi-button-save" type="button" onclick="dl_backup()" value="<%:DL Backup%>" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-value" id="_upload_div">
|
||||
<label class="cbi-value-title"><%:Restore Backup File%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" id="upload-btn" value="<%:RST Backup%>" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-value" id="_reset_div">
|
||||
<label class="cbi-value-title"><%:Restore to default configuration%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="do_reset()" value="<%:Do Reset%>" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="upload-modal" class="up-modal" style="display:none;">
|
||||
<div class="up-modal-content">
|
||||
<h3><%:Restore Backup File%></h3>
|
||||
<div class="cbi-value" id="_upload_div">
|
||||
<div class="up-cbi-value-field">
|
||||
<input class="cbi-input-file" type="file" id="ulfile" name="ulfile" accept=".tar.gz" required />
|
||||
<br />
|
||||
<div class="up-button-container">
|
||||
<input class="btn cbi-button cbi-button-apply" type="submit" value="<%:UL Restore%>" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" id="upload-close" value="<%:CLOSE WIN%>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.up-modal {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border: 2px solid #ccc;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.5);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.up-modal-content {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.up-button-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.up-cbi-value-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// JavaScript 版本的 url 函数
|
||||
function url(...args) {
|
||||
let url = "/cgi-bin/luci/admin/services/shadowsocksr";
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] !== "") {
|
||||
url += "/" + args[i];
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
// 上传按钮点击事件
|
||||
document.getElementById("upload-btn").addEventListener("click", function() {
|
||||
document.getElementById("upload-modal").style.display = "block";
|
||||
});
|
||||
|
||||
// 关闭上传模态框
|
||||
document.getElementById("upload-close").addEventListener("click", function() {
|
||||
document.getElementById("upload-modal").style.display = "none";
|
||||
});
|
||||
|
||||
// 备份下载函数
|
||||
function dl_backup(btn) {
|
||||
fetch(url("backup"), { // 使用 JavaScript 版本的 url 函数
|
||||
method: 'POST',
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error("备份失败!");
|
||||
}
|
||||
const filename = response.headers.get("X-Backup-Filename");
|
||||
if (!filename) {
|
||||
return;
|
||||
}
|
||||
return response.blob().then(blob => ({ blob, filename }));
|
||||
})
|
||||
.then(result => {
|
||||
if (!result) return;
|
||||
const { blob, filename } = result;
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
})
|
||||
.catch(error => alert(error.message));
|
||||
}
|
||||
|
||||
// 恢复出厂设置
|
||||
function do_reset(btn) {
|
||||
if (confirm("<%: Do you want to restore the client to default settings?%>")) {
|
||||
setTimeout(function () {
|
||||
if (confirm("<%: Are you sure you want to restore the client to default settings?%>")) {
|
||||
// 清理日志
|
||||
var xhr1 = new XMLHttpRequest();
|
||||
xhr1.open("GET", url("clear_log"), true); // 使用 JavaScript 版本的 url 函数
|
||||
xhr1.send();
|
||||
// 恢复出厂
|
||||
var xhr2 = new XMLHttpRequest();
|
||||
xhr2.open("GET", url("reset"), true); // 使用 JavaScript 版本的 url 函数
|
||||
xhr2.send();
|
||||
// 处理响应
|
||||
xhr2.onload = function() {
|
||||
if (xhr2.status === 200) {
|
||||
window.location.href = url("reset");
|
||||
}
|
||||
};
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,4 @@
|
||||
<%+cbi/valueheader%>
|
||||
<input class="cbi-input-file" style="width: 400px" type="file" id="ulfile" name="ulfile" />
|
||||
<input type="submit" class="cbi-button cbi-input-apply" name="upload" value="<%:Upload%>" />
|
||||
<%+cbi/valuefooter%>
|
||||
29
luci-app-ssr-plus/luasrc/view/shadowsocksr/check.htm
Normal file
29
luci-app-ssr-plus/luasrc/view/shadowsocksr/check.htm
Normal file
@@ -0,0 +1,29 @@
|
||||
<%+cbi/valueheader%>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
function check_connect(btn,urlname)
|
||||
{
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Check...%>';
|
||||
murl=urlname;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr","check")%>',
|
||||
{ set:murl },
|
||||
function(x,rv)
|
||||
{
|
||||
var s = document.getElementById(urlname+'-status');
|
||||
if (s)
|
||||
{
|
||||
if (rv.ret=="0")
|
||||
s.innerHTML ="<font color='green'>"+"<%:Connect OK%>"+"</font>";
|
||||
else
|
||||
s.innerHTML ="<font color='red'>"+"<%:Connect Error%>"+"</font>";
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.value = '<%:Check Connect%>';
|
||||
}
|
||||
);
|
||||
return false;
|
||||
}
|
||||
//]]></script>
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Check Connect%>" onclick="return check_connect(this,'<%=self.option%>')" />
|
||||
<span id="<%=self.option%>-status"><em><%=self.value%></em></span>
|
||||
<%+cbi/valuefooter%>
|
||||
25
luci-app-ssr-plus/luasrc/view/shadowsocksr/checkport.htm
Normal file
25
luci-app-ssr-plus/luasrc/view/shadowsocksr/checkport.htm
Normal file
@@ -0,0 +1,25 @@
|
||||
<%+cbi/valueheader%>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
function check_port(btn)
|
||||
{
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Check...%>';
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr","checkport")%>',
|
||||
null,
|
||||
function(x,rv)
|
||||
{
|
||||
var s = document.getElementById('<%=self.option%>-status');
|
||||
if (s)
|
||||
{
|
||||
s.innerHTML =rv.ret;
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.value = '<%:Check Server%>';
|
||||
}
|
||||
);
|
||||
return false;
|
||||
}
|
||||
//]]></script>
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Check Server%>" onclick="return check_port(this)" />
|
||||
<span id="<%=self.option%>-status"><em><%=self.value%></em></span>
|
||||
<%+cbi/valuefooter%>
|
||||
37
luci-app-ssr-plus/luasrc/view/shadowsocksr/log.htm
Normal file
37
luci-app-ssr-plus/luasrc/view/shadowsocksr/log.htm
Normal file
@@ -0,0 +1,37 @@
|
||||
<%
|
||||
local dsp = require "luci.dispatcher"
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function clearlog(btn) {
|
||||
XHR.get('<%=dsp.build_url("admin/services/shadowsocksr/clear_log")%>', null,
|
||||
function(x, data) {
|
||||
if (x && x.status == 200) {
|
||||
var log_textarea = document.getElementById('log_textarea');
|
||||
log_textarea.innerHTML = "";
|
||||
log_textarea.scrollTop = log_textarea.scrollHeight;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
XHR.poll(5, '<%=dsp.build_url("admin/services/shadowsocksr/get_log")%>', null,
|
||||
function(x, data) {
|
||||
if (x && x.status == 200) {
|
||||
var log_textarea = document.getElementById('log_textarea');
|
||||
// 将日志分行处理,移除最后一行空行但保留中间空行
|
||||
var logs = x.responseText.split("\n");
|
||||
if (logs[logs.length - 1].trim() === "") {
|
||||
logs.pop(); // 删除最后的空行
|
||||
}
|
||||
logs = logs.reverse().join("\n"); // 倒序排列
|
||||
log_textarea.innerHTML = logs;
|
||||
}
|
||||
}
|
||||
);
|
||||
//]]>
|
||||
</script>
|
||||
<fieldset class="cbi-section" id="_log_fieldset">
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="clearlog()" value="<%:Clear logs%>" />
|
||||
<textarea id="log_textarea" class="cbi-input-textarea" style="width: 100%;margin-top: 10px;" data-update="change" rows="20" wrap="off" readonly="readonly"></textarea>
|
||||
</fieldset>
|
||||
3
luci-app-ssr-plus/luasrc/view/shadowsocksr/ping.htm
Normal file
3
luci-app-ssr-plus/luasrc/view/shadowsocksr/ping.htm
Normal file
@@ -0,0 +1,3 @@
|
||||
<%+cbi/valueheader%>
|
||||
<span class="pingtime" hint="<%=self:cfgvalue(section)%>">-- ms</span>
|
||||
<%+cbi/valuefooter%>
|
||||
37
luci-app-ssr-plus/luasrc/view/shadowsocksr/refresh.htm
Normal file
37
luci-app-ssr-plus/luasrc/view/shadowsocksr/refresh.htm
Normal file
@@ -0,0 +1,37 @@
|
||||
<%+cbi/valueheader%>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
function refresh_data(btn,dataname)
|
||||
{
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Refresh...%> ';
|
||||
murl=dataname;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr","refresh")%>',
|
||||
{ set:murl },
|
||||
function(x,rv)
|
||||
{
|
||||
var s = document.getElementById(dataname+'-status');
|
||||
if (s)
|
||||
{
|
||||
switch (rv.ret)
|
||||
{
|
||||
case 0:
|
||||
s.innerHTML ="<font color='green'>"+"<%:Refresh OK!%> "+"<%:Total Records:%>"+rv.retcount+"</font>";
|
||||
break;
|
||||
case 1:
|
||||
s.innerHTML ="<font color='green'>"+"<%:No new data!%> "+"</font>";
|
||||
break;
|
||||
default:
|
||||
s.innerHTML ="<font color='red'>"+"<%:Refresh Error!%> "+"</font>";
|
||||
break;
|
||||
}
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.value = '<%:Refresh Data %>';
|
||||
}
|
||||
);
|
||||
return false;
|
||||
}
|
||||
//]]></script>
|
||||
<input type="button" class="btn cbi-button cbi-button-reload" value="<%:Refresh Data%> " onclick="return refresh_data(this,'<%=self.option%>')" />
|
||||
<span id="<%=self.option%>-status"><em><%=self.value%></em></span>
|
||||
<%+cbi/valuefooter%>
|
||||
25
luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm
Normal file
25
luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm
Normal file
@@ -0,0 +1,25 @@
|
||||
<%+cbi/valueheader%>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
function reset(btn,dataname) {
|
||||
var s = document.getElementById(dataname + '-status');
|
||||
var reset = prompt('<%:Really reset all changes?%><%:Please fill in reset%>',"")
|
||||
if (reset == null || reset == "") {
|
||||
return false;
|
||||
}
|
||||
if (reset != "reset") {
|
||||
s.innerHTML = "<font color='red'><%:The content entered is incorrect!%></font>";
|
||||
return false;
|
||||
}
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Perform reset%>';
|
||||
murl=dataname;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr","reset")%>', { set:murl }, function(x,rv) {
|
||||
btn.value = '<%:Reset complete%>';
|
||||
s.innerHTML = "<font color='green'><%:Reset complete%></font>";
|
||||
});
|
||||
return false;
|
||||
}
|
||||
//]]></script>
|
||||
<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Perform reset%> " onclick="return reset(this,'<%=self.option%>')" />
|
||||
<span id="<%=self.option%>-status"></span>
|
||||
<%+cbi/valuefooter%>
|
||||
158
luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm
Normal file
158
luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm
Normal file
@@ -0,0 +1,158 @@
|
||||
<%#
|
||||
Copyright 2018-2019 Lienol <lawlienol@gmail.com>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
window.addEventListener('load', function () {
|
||||
const doms = document.getElementsByClassName('pingtime');
|
||||
const ports = document.getElementsByClassName("socket-connected");
|
||||
const transports = document.getElementsByClassName("transport");
|
||||
const wsPaths = document.getElementsByClassName("wsPath");
|
||||
const tlss = document.getElementsByClassName("tls");
|
||||
const xhr = (index) => {
|
||||
return new Promise((res) => {
|
||||
const dom = doms[index];
|
||||
const port = ports[index];
|
||||
const transport = transports[index];
|
||||
const wsPath = wsPaths[index];
|
||||
const tls = tlss[index];
|
||||
if (!dom) res();
|
||||
port.innerHTML = '<font color="#0072c3">connect</font>';
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin/services/shadowsocksr/ping")%>', {
|
||||
index,
|
||||
domain: dom.getAttribute("hint"),
|
||||
port: port.getAttribute("hint"),
|
||||
transport: transport.getAttribute("hint"),
|
||||
wsPath: wsPath.getAttribute("hint"),
|
||||
tls: tls.getAttribute("hint")
|
||||
},
|
||||
(x, result) => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
};
|
||||
let task = -1;
|
||||
const thread = () => {
|
||||
task = task + 1;
|
||||
if (doms[task]) {
|
||||
xhr(task).then(thread);
|
||||
}
|
||||
};
|
||||
for (let i = 0; i < 20; i++) {
|
||||
thread();
|
||||
}
|
||||
});
|
||||
|
||||
function cbi_row_drop(fromId, toId, store, isToBottom) {
|
||||
var fromNode = document.getElementById(fromId);
|
||||
var toNode = document.getElementById(toId);
|
||||
if (!fromNode || !toNode) return false;
|
||||
|
||||
var table = fromNode.parentNode;
|
||||
while (table && table.nodeName.toLowerCase() != "table")
|
||||
table = table.parentNode;
|
||||
if (!table) return false;
|
||||
|
||||
var ids = [];
|
||||
if (isToBottom) {
|
||||
toNode.parentNode.appendChild(fromNode);
|
||||
} else {
|
||||
fromNode.parentNode.insertBefore(fromNode, toNode);
|
||||
}
|
||||
for (var idx = 2; idx < table.rows.length; idx++) {
|
||||
table.rows[idx].className = table.rows[idx].className.replace(
|
||||
/cbi-rowstyle-[12]/,
|
||||
"cbi-rowstyle-" + (1 + (idx % 2))
|
||||
);
|
||||
|
||||
if (table.rows[idx].id && table.rows[idx].id.match(/-([^\-]+)$/))
|
||||
ids.push(RegExp.$1);
|
||||
}
|
||||
var input = document.getElementById(store);
|
||||
if (input) input.value = ids.join(" ");
|
||||
return false;
|
||||
}
|
||||
|
||||
// set tr draggable
|
||||
function enableDragForTable(table_selector, store) {
|
||||
// 添加 CSS 样式
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
tr[draggable="true"] {
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
var trs = document.querySelectorAll(table_selector + " tr");
|
||||
if (!trs || trs.length.length < 3) {
|
||||
return;
|
||||
}
|
||||
function ondragstart(ev) {
|
||||
ev.dataTransfer.setData("Text", ev.target.id);
|
||||
}
|
||||
function ondrop(ev) {
|
||||
var from = ev.dataTransfer.getData("Text");
|
||||
cbi_row_drop(from, this.id, store);
|
||||
}
|
||||
function ondragover(ev) {
|
||||
ev.preventDefault();
|
||||
ev.dataTransfer.dropEffect = "move";
|
||||
}
|
||||
function moveToTop(id) {
|
||||
var top = document.querySelectorAll(table_selector + " tr")[2];
|
||||
cbi_row_drop(id, top.id, store);
|
||||
}
|
||||
function moveToBottom(id) {
|
||||
//console.log('moveToBottom:', id);
|
||||
var trList = document.querySelectorAll(table_selector + " tr");
|
||||
var bottom = trList[trList.length - 1];
|
||||
cbi_row_drop(id, bottom.id, store, true);
|
||||
}
|
||||
for (let index = 2; index < trs.length; index++) {
|
||||
const el = trs[index];
|
||||
el.setAttribute("draggable", true);
|
||||
el.ondragstart = ondragstart;
|
||||
el.ondrop = ondrop;
|
||||
el.ondragover = ondragover;
|
||||
// reset the behaviors of the btns
|
||||
var upBtns = el.querySelectorAll(".cbi-button.cbi-button-up");
|
||||
if (upBtns && upBtns.length > 0) {
|
||||
upBtns.forEach(function (_el) {
|
||||
_el.onclick = function () {
|
||||
moveToTop(el.id);
|
||||
};
|
||||
});
|
||||
}
|
||||
var downBtns = el.querySelectorAll(".cbi-button.cbi-button-down");
|
||||
if (downBtns && downBtns.length > 0) {
|
||||
downBtns.forEach(function (_el) {
|
||||
_el.onclick = function () {
|
||||
moveToBottom(el.id);
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// enable
|
||||
enableDragForTable(
|
||||
"#cbi-shadowsocksr-servers table",
|
||||
"cbi.sts.shadowsocksr.servers"
|
||||
);
|
||||
//]]>
|
||||
</script>
|
||||
6
luci-app-ssr-plus/luasrc/view/shadowsocksr/socket.htm
Normal file
6
luci-app-ssr-plus/luasrc/view/shadowsocksr/socket.htm
Normal file
@@ -0,0 +1,6 @@
|
||||
<%+cbi/valueheader%>
|
||||
<span class="socket-connected" hint="<%=self:cfgvalue(section)%>">wait</span>
|
||||
<span class="transport" hint="<%=self.transport%>"></span>
|
||||
<span class="wsPath" hint="<%=self.ws_path%>"></span>
|
||||
<span class="tls" hint="<%=self.tls%>"></span>
|
||||
<%+cbi/valuefooter%>
|
||||
821
luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm
Normal file
821
luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm
Normal file
@@ -0,0 +1,821 @@
|
||||
<%+cbi/valueheader%>
|
||||
<%
|
||||
local map = self.map
|
||||
local ss_type = map:get("@server_subscribe[0]", "ss_type")
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
let ss_type = "<%=ss_type%>"
|
||||
|
||||
function padright(str, cnt, pad) {
|
||||
return str + Array(cnt + 1).join(pad);
|
||||
}
|
||||
|
||||
function b64EncodeUnicode(str) {
|
||||
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
|
||||
return String.fromCharCode('0x' + p1);
|
||||
}));
|
||||
}
|
||||
|
||||
function b64encutf8safe(str) {
|
||||
return b64EncodeUnicode(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, '');
|
||||
}
|
||||
|
||||
function b64DecodeUnicode(str) {
|
||||
return decodeURIComponent(Array.prototype.map.call(atob(str), function (c) {
|
||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
}).join(''));
|
||||
}
|
||||
|
||||
function b64decutf8safe(str) {
|
||||
var l;
|
||||
str = str.replace(/-/g, "+").replace(/_/g, "/");
|
||||
l = str.length;
|
||||
l = (4 - l % 4) % 4;
|
||||
if (l) str = padright(str, l, "=");
|
||||
return b64DecodeUnicode(str);
|
||||
}
|
||||
|
||||
function b64encsafe(str) {
|
||||
return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, '')
|
||||
}
|
||||
|
||||
function b64decsafe(str) {
|
||||
var l;
|
||||
str = str.replace(/-/g, "+").replace(/_/g, "/");
|
||||
l = str.length;
|
||||
l = (4 - l % 4) % 4;
|
||||
if (l) str = padright(str, l, "=");
|
||||
return atob(str);
|
||||
}
|
||||
|
||||
function dictvalue(d, key) {
|
||||
var v = d[key];
|
||||
if (typeof (v) == 'undefined' || v == '') return '';
|
||||
return b64decsafe(v);
|
||||
}
|
||||
|
||||
function export_ssr_url(btn, urlname, sid) {
|
||||
var s = document.getElementById(urlname + '-status');
|
||||
if (!s) return false;
|
||||
var v_server = document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0];
|
||||
var v_port = document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0];
|
||||
var v_protocol = document.getElementsByName('cbid.shadowsocksr.' + sid + '.protocol')[0];
|
||||
var v_method = document.getElementsByName('cbid.shadowsocksr.' + sid + '.encrypt_method')[0];
|
||||
var v_obfs = document.getElementsByName('cbid.shadowsocksr.' + sid + '.obfs')[0];
|
||||
var v_password = document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0];
|
||||
var v_obfs_param = document.getElementsByName('cbid.shadowsocksr.' + sid + '.obfs_param')[0];
|
||||
var v_protocol_param = document.getElementsByName('cbid.shadowsocksr.' + sid + '.protocol_param')[0];
|
||||
var v_alias = document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0];
|
||||
var ssr_str = v_server.value + ":" + v_port.value + ":" + v_protocol.value + ":" + v_method.value + ":" + v_obfs.value + ":" + b64encsafe(v_password.value) + "/?obfsparam=" + b64encsafe(v_obfs_param.value) + "&protoparam=" + b64encsafe(v_protocol_param.value) + "&remarks=" + b64encutf8safe(v_alias.value);
|
||||
var textarea = document.createElement("textarea");
|
||||
textarea.textContent = "ssr://" + b64encsafe(ssr_str);
|
||||
textarea.style.position = "fixed";
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
try {
|
||||
document.execCommand("copy"); // Security exception may be thrown by some browsers.
|
||||
s.innerHTML = "<font color='green'><%:Copy SSR to clipboard successfully.%></font>";
|
||||
} catch (ex) {
|
||||
s.innerHTML = "<font color='red'><%:Unable to copy SSR to clipboard.%></font>";
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function import_ssr_url(btn, urlname, sid) {
|
||||
var s = document.getElementById(urlname + '-status');
|
||||
if (!s) return false;
|
||||
var ssrurl = prompt("<%:Paste sharing link here%>", "");
|
||||
if (ssrurl == null || ssrurl == "") {
|
||||
s.innerHTML = "<font color='red'><%:User cancelled.%></font>";
|
||||
return false;
|
||||
}
|
||||
s.innerHTML = "";
|
||||
//var ssu = ssrurl.match(/ssr:\/\/([A-Za-z0-9_-]+)/i);
|
||||
ssrurl = ssrurl.replace(/&([a-zA-Z]+);/g, '&').replace(/\s*#\s*/, '#').trim(); //一些奇葩的链接用"&"当做"&","#"前后带空格
|
||||
var ssu = ssrurl.split('://');
|
||||
//console.log(ssu.length);
|
||||
if (ssu[0] === "ss") {
|
||||
var queryStr = "";
|
||||
if (ssu[1].indexOf("?") > -1) {
|
||||
queryStr = ssu[1].split("?")[1]; // 提取 ? 后面的参数
|
||||
}
|
||||
var params = new URLSearchParams(queryStr);
|
||||
if (params.get("type")) {
|
||||
// 替换协议头
|
||||
ssrurl = ssrurl.replace(/^ss:\/\//i, "shadowsocks://");
|
||||
var ssu = ssrurl.split('://');
|
||||
}
|
||||
}
|
||||
var event = document.createEvent("HTMLEvents");
|
||||
event.initEvent("change", true, true);
|
||||
switch (ssu[0]) {
|
||||
case "hysteria2":
|
||||
case "hy2":
|
||||
try {
|
||||
var url = new URL("http://" + ssu[1]);
|
||||
var params = url.searchParams;
|
||||
} catch(e) {
|
||||
alert(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = (ssu[0] === "hy2") ? "hysteria2" : ssu[0];
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = url.hostname;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port || "443";
|
||||
if (params.get("lazy") === "1") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.lazy_mode')[0].checked = true;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.lazy_mode')[0].dispatchEvent(event);
|
||||
}
|
||||
if (params.get("mport")) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.flag_port_hopping')[0].checked = true; // 设置 flag_port_hopping 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.flag_port_hopping')[0].dispatchEvent(event); // 触发事件
|
||||
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.port_range')[0].value = params.get("mport") || "";
|
||||
}
|
||||
if (params.get("protocol")) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.flag_transport')[0].checked = true; // 设置 flag_transport 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.flag_transport')[0].dispatchEvent(event); // 触发事件
|
||||
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport_protocol')[0].value = params.get("protocol") || "udp";
|
||||
}
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.hy2_auth')[0].value = decodeURIComponent(url.username);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.hy2_auth')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.uplink_capacity')[0].value =
|
||||
(params.get("upmbps") && params.get("upmbps").match(/\d+/)) ? params.get("upmbps").match(/\d+/)[0] : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.downlink_capacity')[0].value =
|
||||
(params.get("downmbps") && params.get("downmbps").match(/\d+/)) ? params.get("downmbps").match(/\d+/)[0] : "";
|
||||
if (params.get("obfs") && params.get("obfs") !== "none") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.flag_obfs')[0].checked = true; // 设置 flag_obfs 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.flag_obfs')[0].dispatchEvent(event); // 触发事件
|
||||
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.obfs_type')[0].value = params.get("obfs");
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.salamander')[0].value = params.get("obfs-password") || params.get("obfs_password");
|
||||
}
|
||||
if (params.get("sni") || params.get("alpn")) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true; // 设置 flag_obfs 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event); // 触发事件
|
||||
if (params.get("sni")) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = params.get("sni") || "";
|
||||
}
|
||||
if (params.get("alpn")) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_alpn')[0].value = params.get("alpn") || "";
|
||||
}
|
||||
}
|
||||
if (params.get("insecure") === "1") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].checked = true;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].dispatchEvent(event);
|
||||
}
|
||||
if (params.get("pinSHA256")) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.pinsha256')[0].value = params.get("pinSHA256") || "";
|
||||
}
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
|
||||
|
||||
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
|
||||
return false;
|
||||
case "ss":
|
||||
var url0 = (ssu[1] || "");
|
||||
var param = "";
|
||||
|
||||
// 先分离 #(alias)
|
||||
var hashIndex = url0.indexOf("#");
|
||||
if (hashIndex >= 0) {
|
||||
param = url0.substring(hashIndex + 1);
|
||||
url0 = url0.substring(0, hashIndex);
|
||||
}
|
||||
|
||||
// 再分离 ? 或 /?(参数)
|
||||
var queryIndex = (url0 = url0.replace('/?', '?')).indexOf("?");
|
||||
var queryStr = "";
|
||||
if (queryIndex >= 0) {
|
||||
queryStr = url0.substring(queryIndex + 1);
|
||||
url0 = url0.substring(0, queryIndex);
|
||||
}
|
||||
|
||||
var params = Object.fromEntries(new URLSearchParams(queryStr));
|
||||
|
||||
// 判断是否 SIP002 格式(即含 @)
|
||||
if (url0.indexOf("@") !== -1) {
|
||||
// === SIP002 格式 ===
|
||||
var sipIndex = url0.indexOf("@");
|
||||
// 先 URL 解码 base64 再解码
|
||||
var userInfoB64 = decodeURIComponent(url0.substring(0, sipIndex));
|
||||
var userInfo = b64decsafe(userInfoB64);
|
||||
var userInfoSplitIndex = userInfo.indexOf(":");
|
||||
if(userInfoSplitIndex < 0) {
|
||||
// 格式错误
|
||||
s.innerHTML = "<font style='color:red'><%:Userinfo format error.%></font>";
|
||||
break;
|
||||
}
|
||||
var method = userInfo.substring(0, userInfoSplitIndex);
|
||||
var password = userInfo.substring(userInfoSplitIndex + 1);
|
||||
var serverPart = url0.substring(sipIndex + 1);
|
||||
var serverInfo = serverPart.split(":");
|
||||
|
||||
var server = serverInfo[0];
|
||||
var port = serverInfo[1];
|
||||
|
||||
var plugin = "", pluginOpts = "";
|
||||
if (params.plugin) {
|
||||
var pluginParams = decodeURIComponent(params.plugin).split(";");
|
||||
plugin = pluginParams.shift();
|
||||
pluginOpts = pluginParams.join(";");
|
||||
}
|
||||
} else {
|
||||
// === Base64 SS2022 / 普通格式 的整体编码格式 ===
|
||||
// 先 URL 解码整个字符串
|
||||
var decodedUrl0 = decodeURIComponent(url0);
|
||||
var sstr = b64decsafe(decodedUrl0);
|
||||
if (!sstr) {
|
||||
s.innerHTML = "<font style='color:red'><%:Base64 sstr failed.%></font>";
|
||||
break;
|
||||
}
|
||||
|
||||
// 支持 SS2022 / 普通格式
|
||||
var regex2022 = /^([^:]+):([^:]+):([^@]+)@([^:]+):(\d+)$/;
|
||||
var regexNormal = /^([^:]+):([^@]+)@([^:]+):(\d+)$/;
|
||||
|
||||
var m2022 = sstr.match(regex2022);
|
||||
var mNormal = sstr.match(regexNormal);
|
||||
|
||||
if (m2022) {
|
||||
var method = m2022[1];
|
||||
var password = m2022[2] + ":" + m2022[3];
|
||||
var server = m2022[4];
|
||||
var port = m2022[5];
|
||||
} else if (mNormal) {
|
||||
var method = mNormal[1];
|
||||
var password = mNormal[2];
|
||||
var server = mNormal[3];
|
||||
var port = mNormal[4];
|
||||
} else {
|
||||
s.innerHTML = "<font style='color:red'><%:SS URL base64 sstr format not recognized.%></font>";
|
||||
break;
|
||||
}
|
||||
|
||||
var plugin = "", pluginOpts = "";
|
||||
if (params["shadow-tls"]) {
|
||||
try {
|
||||
var decoded_tls = JSON.parse(atob(decodeURIComponent(params["shadow-tls"])));
|
||||
plugin = "shadow-tls";
|
||||
var versionFlag = "";
|
||||
if (decoded_tls.version && !isNaN(decoded_tls.version)) {
|
||||
versionFlag = "v" + decoded_tls.version + "=1;";
|
||||
}
|
||||
pluginOpts = versionFlag + "host=" + (decoded_tls.host || "") + ";passwd=" + (decoded_tls.password || "");
|
||||
} catch (e) {
|
||||
console.log("shadow-tls decode failed:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 判断密码是否经过url编码
|
||||
const isURLEncodedPassword = function(pwd) {
|
||||
if (!/%[0-9A-Fa-f]{2}/.test(pwd)) return false;
|
||||
try {
|
||||
const decoded = decodeURIComponent(pwd.replace(/\+/g, "%20"));
|
||||
const reencoded = encodeURIComponent(decoded);
|
||||
return reencoded === pwd;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isURLEncodedPassword(password)) {
|
||||
password = decodeURIComponent(password); // 解码URL编码
|
||||
} else {
|
||||
password = password; // 保持原始值
|
||||
}
|
||||
|
||||
// === 填充配置项 ===
|
||||
var has_ss_type = (ss_type === "ss-rust") ? "ss-rust" : "ss-libev";
|
||||
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = ssu[0];
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.has_ss_type')[0].value = has_ss_type;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.has_ss_type')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = server;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = port;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = password || "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.encrypt_method_ss')[0].value = method;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.encrypt_method_ss')[0].dispatchEvent(event);
|
||||
|
||||
if (plugin && plugin !== "none") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].checked = true; // 设置 enable_plugin 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].dispatchEvent(event); // 触发事件
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.plugin')[0].value = plugin;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.plugin')[0].dispatchEvent(event);
|
||||
if (plugin !== undefined) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.plugin_opts')[0].value = pluginOpts || "";
|
||||
}
|
||||
} else {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].checked = false;
|
||||
}
|
||||
|
||||
if (param != undefined) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = decodeURIComponent(param);
|
||||
}
|
||||
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
|
||||
return false;
|
||||
case "ssr":
|
||||
var sstr = b64decsafe((ssu[1] || "").replace(/#.*/, "").trim());
|
||||
var ploc = sstr.indexOf("/?");
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = ssu[0];
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
var url0, param = "";
|
||||
if (ploc > 0) {
|
||||
url0 = sstr.substr(0, ploc);
|
||||
param = sstr.substr(ploc + 2);
|
||||
}
|
||||
var ssm = url0.match(/^(.+):([^:]+):([^:]*):([^:]+):([^:]*):([^:]+)/);
|
||||
if (!ssm || ssm.length < 7) return false;
|
||||
var pdict = {};
|
||||
if (param.length > 2) {
|
||||
var a = param.split('&');
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
var b = a[i].split('=');
|
||||
pdict[decodeURIComponent(b[0])] = decodeURIComponent(b[1] || '');
|
||||
}
|
||||
}
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = ssm[1];
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = ssm[2];
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.protocol')[0].value = ssm[3];
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.encrypt_method')[0].value = ssm[4];
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.obfs')[0].value = ssm[5];
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = b64decsafe(ssm[6]);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.obfs_param')[0].value = dictvalue(pdict, 'obfsparam');
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.protocol_param')[0].value = dictvalue(pdict, 'protoparam');
|
||||
var rem = pdict['remarks'];
|
||||
if (typeof (rem) != 'undefined' && rem != '' && rem.length > 0) document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = b64decutf8safe(rem);
|
||||
s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
|
||||
return false;
|
||||
case "trojan":
|
||||
try {
|
||||
var url = new URL("http://" + ssu[1]);
|
||||
var params = url.searchParams;
|
||||
} catch(e) {
|
||||
alert(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "v2ray";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].value = "trojan";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = url.hostname;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port || "80";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = decodeURIComponent(url.username);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = params.get("sni");
|
||||
if (params.get("allowInsecure") === "1") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].checked = true; // 设置 insecure 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].dispatchEvent(event); // 触发事件
|
||||
}
|
||||
if (params.get("ech") && params.get("ech").trim() !== "") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_ech')[0].checked = true; // 设置 enable_ech 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_ech')[0].dispatchEvent(event); // 触发事件
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ech_config')[0].value = params.get("ech");
|
||||
|
||||
}
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].value =
|
||||
params.get("type") == "http" ? "h2" :
|
||||
(["xhttp", "splithttp"].includes(params.get("type")) ? "xhttp" :
|
||||
(["tcp", "raw"].includes(params.get("type")) ? "raw" :
|
||||
(params.get("type") || "raw")));
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].dispatchEvent(event);
|
||||
if (params.get("security") === "tls") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_alpn')[0].value = params.get("alpn") || "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.fingerprint')[0].value = params.get("fp") || "";
|
||||
}
|
||||
switch (params.get("type")) {
|
||||
case "ws":
|
||||
if (params.get("security") !== "tls") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ws_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
|
||||
}
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ws_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
|
||||
break;
|
||||
case "httpupgrade":
|
||||
if (params.get("security") !== "tls") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.httpupgrade_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
|
||||
}
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.httpupgrade_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
|
||||
break;
|
||||
case "xhttp":
|
||||
case "splithttp":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_mode')[0].value = params.get("mode") || "auto";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
|
||||
if (params.get("extra") && params.get("extra").trim() !== "") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].checked = true; // 设置 enable_xhttp_extra 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].dispatchEvent(event); // 触发事件
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_extra')[0].value = params.get("extra") || "";
|
||||
}
|
||||
break;
|
||||
case "kcp":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.kcp_guise')[0].value = params.get("headerType") || "none";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.seed')[0].value = params.get("seed") || "";
|
||||
break;
|
||||
case "http":
|
||||
/* this is non-standard, bullshit */
|
||||
case "h2":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "";
|
||||
break;
|
||||
case "quic":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_guise')[0].value = params.get("headerType") || "none";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_security')[0].value = params.get("quicSecurity") || "none";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_key')[0].value = params.get("key") || "";
|
||||
break;
|
||||
case "grpc":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.serviceName')[0].value = params.get("serviceName") || "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.grpc_mode')[0].value = params.get("mode") || "gun";
|
||||
break;
|
||||
case "raw":
|
||||
case "tcp":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tcp_guise')[0].value = params.get("headerType") || "none";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tcp_guise')[0].dispatchEvent(event);
|
||||
if (params.get("headerType") === "http") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
|
||||
return false;
|
||||
case "vmess":
|
||||
var sstr = b64DecodeUnicode((ssu[1] || "").replace(/#.*/, "").trim());
|
||||
var ploc = sstr.indexOf("/?");
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "v2ray";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].value = "vmess";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].dispatchEvent(event);
|
||||
var url0, param = "";
|
||||
if (ploc > 0) {
|
||||
url0 = sstr.substr(0, ploc);
|
||||
param = sstr.substr(ploc + 2);
|
||||
}
|
||||
var ssm = JSON.parse(sstr);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = ssm.ps;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = ssm.add;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = ssm.port;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alter_id')[0].value = ssm.aid;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.vmess_id')[0].value = ssm.id;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].value =
|
||||
(["xhttp", "splithttp"].includes(ssm.net) ? "xhttp" :
|
||||
(["tcp", "raw"].includes(ssm.net) ? "raw" :
|
||||
(ssm.net || "raw")));
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].dispatchEvent(event);
|
||||
if (ssm.net === "raw" || ssm.net === "tcp") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tcp_guise')[0].value = ssm.type;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tcp_guise')[0].dispatchEvent(event);
|
||||
if (ssm.type === "http") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_host')[0].value = ssm.host;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_path')[0].value = ssm.path;
|
||||
}
|
||||
}
|
||||
if (ssm.net == "ws") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ws_host')[0].value = ssm.host;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ws_path')[0].value = ssm.path;
|
||||
}
|
||||
if (ssm.net == "httpupgrade") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.httpupgrade_host')[0].value = ssm.host;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.httpupgrade_path')[0].value = ssm.path;
|
||||
}
|
||||
if (ssm.net == "xhttp" || ssm.net == "splithttp") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_mode')[0].value = ssm.mode || "auto";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_host')[0].value = ssm.host;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_path')[0].value = ssm.path;
|
||||
if (ssm.extra !== "" && ssm.extra !== undefined) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].checked = true; // 设置 enable_xhttp_extra 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].dispatchEvent(event); // 触发事件
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_extra')[0].value = ssm.extra;
|
||||
}
|
||||
}
|
||||
if (ssm.net == "h2") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_host')[0].value = ssm.host;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_path')[0].value = ssm.path;
|
||||
}
|
||||
if (ssm.net == "quic") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_security')[0].value = ssm.securty;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_key')[0].value = ssm.key;
|
||||
}
|
||||
if (ssm.net == "kcp") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.kcp_guise')[0].value = ssm.type;
|
||||
}
|
||||
if (ssm.tls == "tls") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event);
|
||||
if (ssm.fp !== "" && ssm.fp !== undefined) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.fingerprint')[0].value = ssm.fp;
|
||||
}
|
||||
if (ssm.alpn !== "" && ssm.alpn !== undefined) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_alpn')[0].value = ssm.alpn;
|
||||
}
|
||||
if (ssm.host !== "" && ssm.host !== undefined) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = ssm.sni || ssm.host;
|
||||
}
|
||||
if (ssm.ech !== "" && ssm.ech !== undefined) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_ech')[0].checked = true; // 设置 enable_ech 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_ech')[0].dispatchEvent(event); // 触发事件
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ech_config')[0].value = ssm.ech;
|
||||
}
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].checked =
|
||||
!!(ssm.allowInsecure ?? ssm.allowlnsecure ?? ssm['skip-cert-verify']); // 设置 insecure 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].dispatchEvent(event); // 触发事件
|
||||
}
|
||||
if (ssm.mux !== undefined) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.mux')[0].checked = true;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.mux')[0].dispatchEvent(event);
|
||||
}
|
||||
s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
|
||||
return false;
|
||||
case "vless":
|
||||
try {
|
||||
var url = new URL("http://" + ssu[1]);
|
||||
var params = url.searchParams;
|
||||
} catch(e) {
|
||||
alert(e);
|
||||
return false;
|
||||
}
|
||||
// Check if the elements exist before trying to modify them
|
||||
function setElementValue(name, value) {
|
||||
const element = document.getElementsByName(name)[0];
|
||||
if (element) {
|
||||
if (element.type === "checkbox" || element.type === "radio") {
|
||||
element.checked = value === true;
|
||||
} else {
|
||||
element.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
function dispatchEventIfExists(name, event) {
|
||||
const element = document.getElementsByName(name)[0];
|
||||
if (element) {
|
||||
element.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.alias', url.hash ? decodeURIComponent(url.hash.slice(1)) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.type', "v2ray");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.type', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.v2ray_protocol', "vless");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.v2ray_protocol', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.server', url.hostname);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.server_port', url.port || "80");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.vmess_id', url.username);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.transport',
|
||||
params.get("type") === "http" ? "h2" :
|
||||
(["xhttp", "splithttp"].includes(params.get("type")) ? "xhttp" :
|
||||
(["tcp", "raw"].includes(params.get("type")) ? "raw" :
|
||||
(params.get("type") || "raw")))
|
||||
);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.transport', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.vless_encryption', params.get("encryption") || "none");
|
||||
if ([ "tls", "xtls", "reality" ].includes(params.get("security"))) {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.' + params.get("security"), true);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.' + params.get("security"), event);
|
||||
|
||||
if (params.get("security") === "tls") {
|
||||
if (params.get("ech") && params.get("ech").trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.enable_ech', true); // 设置 enable_ech 为 true
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_ech', event); // 触发事件
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ech_config', params.get("ech") || "");
|
||||
}
|
||||
if (params.get("allowInsecure") === "1") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.insecure', true); // 设置 insecure 为 true
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.insecure', event); // 触发事件
|
||||
}
|
||||
}
|
||||
if (params.get("security") === "reality") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_publickey', params.get("pbk") ? decodeURIComponent(params.get("pbk")) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_shortid', params.get("sid") || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_spiderx', params.get("spx") ? decodeURIComponent(params.get("spx")) : "");
|
||||
if (params.get("pqv") && params.get("pqv").trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.enable_mldsa65verify', true); // 设置 enable_mldsa65verify 为 true
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_mldsa65verify', event); // 触发事件
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_mldsa65verify', params.get("pqv") || "");
|
||||
}
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_flow', params.get("flow") || "none");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tls_flow', event);
|
||||
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_alpn', params.get("alpn") || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.fingerprint', params.get("fp") || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_host', params.get("sni") || "");
|
||||
}
|
||||
switch (params.get("type")) {
|
||||
case "ws":
|
||||
if (params.get("security") !== "tls") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ws_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ws_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
|
||||
break;
|
||||
case "httpupgrade":
|
||||
if (params.get("security") !== "tls") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
|
||||
break;
|
||||
case "xhttp":
|
||||
case "splithttp":
|
||||
if (params.get("security") !== "tls") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_mode', params.get("mode") || "auto");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
|
||||
if (params.get("extra") && params.get("extra").trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', true); // 设置 enable_xhttp_extra 为 true
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', event); // 触发事件
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_extra', params.get("extra") || "");
|
||||
}
|
||||
break;
|
||||
case "kcp":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.kcp_guise', params.get("headerType") || "none");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.seed', params.get("seed") || "");
|
||||
break;
|
||||
case "http":
|
||||
/* this is non-standard, bullshit */
|
||||
case "h2":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.h2_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.h2_path', params.get("path") ? decodeURIComponent(params.get("path")) : "");
|
||||
break;
|
||||
case "quic":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.quic_guise', params.get("headerType") || "none");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.quic_security', params.get("quicSecurity") || "none");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.quic_key', params.get("key") || "");
|
||||
break;
|
||||
case "grpc":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.serviceName', params.get("serviceName") || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.grpc_mode', params.get("mode") || "gun");
|
||||
break;
|
||||
case "tcp":
|
||||
case "raw":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tcp_guise', params.get("headerType") || "none");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tcp_guise', event);
|
||||
if (params.get("headerType") === "http") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.http_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.http_path', params.get("path") ? decodeURIComponent(params.get("path")) : "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
|
||||
return false;
|
||||
case "shadowsocks":
|
||||
try {
|
||||
// 处理完整 ss:// 链接
|
||||
var urlinfo = ssu[1];
|
||||
// 拆分 @,判断是否是 base64 userinfo 的格式
|
||||
var parts = urlinfo.split("@");
|
||||
if (parts.length > 1) {
|
||||
// @ 前是 base64(method:password),后面是 server:port?params
|
||||
var userinfo = b64decsafe(parts[0]);
|
||||
var sepIndex = userinfo.indexOf(":");
|
||||
if (sepIndex > -1) {
|
||||
method = userinfo.slice(0, sepIndex);
|
||||
password = userinfo.slice(sepIndex + 1); //一些链接用明文uuid做密码
|
||||
}
|
||||
}
|
||||
var url = new URL("http://" + urlinfo);
|
||||
|
||||
var params = url.searchParams;
|
||||
|
||||
} catch(e) {
|
||||
alert(e);
|
||||
return false;
|
||||
}
|
||||
// Check if the elements exist before trying to modify them
|
||||
function setElementValue(name, value) {
|
||||
const element = document.getElementsByName(name)[0];
|
||||
if (element) {
|
||||
if (typeof value === 'boolean') {
|
||||
element.checked = value;
|
||||
} else {
|
||||
element.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
function dispatchEventIfExists(name, event) {
|
||||
const element = document.getElementsByName(name)[0];
|
||||
if (element) {
|
||||
element.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.alias', url.hash ? decodeURIComponent(url.hash.slice(1)) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.type', "v2ray");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.type', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.v2ray_protocol', "shadowsocks");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.v2ray_protocol', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.server', url.hostname);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.server_port', url.port || "80");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.password', password || url.username);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.transport',
|
||||
params.get("type") === "http" ? "h2" :
|
||||
(["xhttp", "splithttp"].includes(params.get("type")) ? "xhttp" :
|
||||
(["tcp", "raw"].includes(params.get("type")) ? "raw" :
|
||||
(params.get("type") || "raw")))
|
||||
);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.transport', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.encrypt_method_ss', method || params.get("encryption") || "none");
|
||||
if ([ "tls", "xtls", "reality" ].includes(params.get("security"))) {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.' + params.get("security"), true);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.' + params.get("security"), event);
|
||||
|
||||
if (params.get("security") === "tls") {
|
||||
if (params.get("ech") && params.get("ech").trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.enable_ech', true); // 设置 enable_ech 为 true
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_ech', event); // 触发事件
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ech_config', params.get("ech") || "");
|
||||
}
|
||||
if (params.get("allowInsecure") === "1") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.insecure', true); // 设置 insecure 为 true
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.insecure', event); // 触发事件
|
||||
}
|
||||
}
|
||||
if (params.get("security") === "reality") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_publickey', params.get("pbk") ? decodeURIComponent(params.get("pbk")) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_shortid', params.get("sid") || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_spiderx', params.get("spx") ? decodeURIComponent(params.get("spx")) : "");
|
||||
if (params.get("pqv") && params.get("pqv").trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.enable_mldsa65verify', true); // 设置 enable_mldsa65verify 为 true
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_mldsa65verify', event); // 触发事件
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_mldsa65verify', params.get("pqv") || "");
|
||||
}
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_flow', params.get("flow") || "none");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tls_flow', event);
|
||||
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_alpn', params.get("alpn") || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.fingerprint', params.get("fp") || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_host', params.get("sni") || "");
|
||||
}
|
||||
switch (params.get("type")) {
|
||||
case "ws":
|
||||
if (params.get("security") !== "tls") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ws_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ws_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
|
||||
break;
|
||||
case "httpupgrade":
|
||||
if (params.get("security") !== "tls") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
|
||||
break;
|
||||
case "xhttp":
|
||||
case "splithttp":
|
||||
if (params.get("security") !== "tls") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_mode', params.get("mode") || "auto");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
|
||||
if (params.get("extra") && params.get("extra").trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', true); // 设置 enable_xhttp_extra 为 true
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', event); // 触发事件
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_extra', params.get("extra") || "");
|
||||
}
|
||||
break;
|
||||
case "kcp":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.kcp_guise', params.get("headerType") || "none");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.seed', params.get("seed") || "");
|
||||
break;
|
||||
case "http":
|
||||
/* this is non-standard, bullshit */
|
||||
case "h2":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.h2_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.h2_path', params.get("path") ? decodeURIComponent(params.get("path")) : "");
|
||||
break;
|
||||
case "quic":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.quic_guise', params.get("headerType") || "none");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.quic_security', params.get("quicSecurity") || "none");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.quic_key', params.get("key") || "");
|
||||
break;
|
||||
case "grpc":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.serviceName', params.get("serviceName") || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.grpc_mode', params.get("mode") || "gun");
|
||||
break;
|
||||
case "tcp":
|
||||
case "raw":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tcp_guise', params.get("headerType") || "none");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tcp_guise', event);
|
||||
if (params.get("headerType") === "http") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.http_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.http_path', params.get("path") ? decodeURIComponent(params.get("path")) : "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
|
||||
return false;
|
||||
default:
|
||||
s.innerHTML = "<font color='red'><%:Invalid format.%></font>";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Import%>" onclick="return import_ssr_url(this, '<%=self.option%>', '<%=self.value%>')" />
|
||||
<span id="<%=self.option%>-status"></span>
|
||||
<%+cbi/valuefooter%>
|
||||
22
luci-app-ssr-plus/luasrc/view/shadowsocksr/status.htm
Normal file
22
luci-app-ssr-plus/luasrc/view/shadowsocksr/status.htm
Normal file
@@ -0,0 +1,22 @@
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
XHR.poll(3, '<%=url([[admin]], [[services]], [[shadowsocksr]], [[run]])%>', null,
|
||||
function(x, data) {
|
||||
var tb = document.getElementById('shadowsocksr_status');
|
||||
if (data && tb) {
|
||||
if (data.running) {
|
||||
var links = '<em><b style=color:green>ShadowsocksR Plus+ <%:RUNNING%></b></em>';
|
||||
tb.innerHTML = links;
|
||||
} else {
|
||||
tb.innerHTML = '<em><b style=color:red>ShadowsocksR Plus+ <%:NOT RUNNING%></b></em>';
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
//]]>
|
||||
</script>
|
||||
<style>.mar-10 {margin-left: 50px; margin-right: 10px;}</style>
|
||||
<fieldset class="cbi-section">
|
||||
<p id="shadowsocksr_status">
|
||||
<em><%:Collecting data...%></em>
|
||||
</p>
|
||||
</fieldset>
|
||||
18
luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm
Normal file
18
luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm
Normal file
@@ -0,0 +1,18 @@
|
||||
<%+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="btn cbi-button cbi-button-apply" value="<%:Update All Subscribe Servers%> " onclick="return subscribe(this,'<%=self.option%>')" />
|
||||
<!-- <span id="<%=self.option%>-status"><em><%=self.value%></em></span> -->
|
||||
<%+cbi/valuefooter%>
|
||||
Reference in New Issue
Block a user