🐶 Sync 2025-11-02 14:26:26

This commit is contained in:
actions-user
2025-11-02 14:26:26 +08:00
parent 64bcc56c2a
commit ac011db799
1557 changed files with 746465 additions and 0 deletions

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View File

@@ -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

View 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

View 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

View 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

View 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>

View File

@@ -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%>

View 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%>

View 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%>

View 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>

View File

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

View 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%>

View 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%>

View 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>

View 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%>

View 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(); //一些奇葩的链接用"&amp;"当做"&""#"前后带空格
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%>

View 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>

View 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%>