mirror of
https://github.com/bolucat/Archive.git
synced 2026-04-23 00:17:16 +08:00
Update On Tue Apr 7 21:19:03 CEST 2026
This commit is contained in:
+1
-2
@@ -106,9 +106,8 @@ make menuconfig
|
||||
* [MIT License](https://github.com/vernesong/OpenClash/blob/master/LICENSE)
|
||||
* 内核 [Mihomo](https://github.com/MetaCubeX/mihomo) by [MetaCubeX](https://github.com/MetaCubeX)
|
||||
* 本项目代码基于 [Luci For Clash](https://github.com/frainzy1477/luci-app-clash) by [frainzy1477](https://github.com/frainzy1477)
|
||||
* GEOIP数据库 [GeoLite2](https://dev.maxmind.com/geoip/geoip2/geolite2/) by [MaxMind](https://www.maxmind.com)
|
||||
* IP检查 [IP](https://ip.skk.moe/) by [SukkaW](https://ip.skk.moe/)
|
||||
* 控制面板 [zashboard](https://github.com/Zephyruso/zashboard) by [Dreamacro](https://github.com/Zephyruso)
|
||||
* 控制面板 [zashboard](https://github.com/Zephyruso/zashboard) by [Zephyruso](https://github.com/Zephyruso)
|
||||
* 控制面板 [yacd](https://github.com/haishanh/yacd) by [haishanh](https://github.com/haishanh)
|
||||
* 流媒体解锁检测 [RegionRestrictionCheck](https://github.com/lmc999/RegionRestrictionCheck) by [lmc999](https://github.com/lmc999)
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
[
|
||||
{
|
||||
"zh": "覆写模块从 v0.47.081 开始支持 [YAML] 块来进行配置文件覆写,格式使用 yaml, 从而降低代码覆写编辑的难度, 使用方式请参考 default 文件的示例。",
|
||||
"en": "Overwrite module now supports YAML blocks for configuration file overwriting, using YAML format, which reduces the difficulty of code overwriting. Please refer to the default file example for usage."
|
||||
},
|
||||
{
|
||||
"zh": "覆写模块现已支持远程订阅,更加方便插件的远程管理及统一配置、一键订阅,欢迎各位分享配置。",
|
||||
"en": "Overwrite module now supports subscription, more convenient remote management and configuration, one-click subscription, welcome to share the configuration."
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-openclash
|
||||
PKG_VERSION:=0.47.075
|
||||
PKG_VERSION:=0.47.086
|
||||
PKG_MAINTAINER:=vernesong <https://github.com/vernesong/OpenClash>
|
||||
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||
|
||||
@@ -798,7 +798,7 @@ function sub_info_get()
|
||||
|
||||
if #providers_data == 0 then
|
||||
if not url_result then
|
||||
luci.http.status(400, "Subscription information not found")
|
||||
luci.http.status(500, "Subscription information not found")
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -841,7 +841,7 @@ function action_switch_rule_mode()
|
||||
mode = luci.http.formvalue("rule_mode")
|
||||
|
||||
if not mode then
|
||||
luci.http.status(400, "Missing parameters")
|
||||
luci.http.status(500, "Missing parameters")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -1098,7 +1098,7 @@ end
|
||||
function action_default_dashboard()
|
||||
local default_dashboard = luci.http.formvalue("name")
|
||||
if not default_dashboard or (default_dashboard ~= "Dashboard" and default_dashboard ~= "Yacd" and default_dashboard ~= "Metacubexd" and default_dashboard ~= "Zashboard") then
|
||||
luci.http.status(400, "Set Failed")
|
||||
luci.http.status(500, "Set Failed")
|
||||
return
|
||||
end
|
||||
if not fs.isdirectory("/usr/share/openclash/ui/" .. string.lower(default_dashboard)) then
|
||||
@@ -1644,6 +1644,13 @@ function rename_file()
|
||||
uci:set("openclash", s[".name"], "name", fs.filename(new_file_name))
|
||||
end
|
||||
end)
|
||||
|
||||
uci:foreach("openclash", "subscribe_info",
|
||||
function(s)
|
||||
if s.name == fs.filename(old_file_name) and fs.filename(new_file_name) ~= new_file_name then
|
||||
uci:set("openclash", s[".name"], "name", fs.filename(new_file_name))
|
||||
end
|
||||
end)
|
||||
|
||||
uci:foreach("openclash", "groups",
|
||||
function(s)
|
||||
@@ -2073,114 +2080,101 @@ function action_myip_check()
|
||||
luci.http.write_json(result)
|
||||
end
|
||||
|
||||
function action_website_check()
|
||||
local domain = luci.http.formvalue("domain")
|
||||
local result = {
|
||||
success = false,
|
||||
response_time = 0,
|
||||
error = ""
|
||||
}
|
||||
function latency_test(addr)
|
||||
local result = { success = false, response_time = 0, error = "" }
|
||||
|
||||
if not domain then
|
||||
if not addr then
|
||||
result.error = "Missing domain parameter"
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(result)
|
||||
return
|
||||
return result
|
||||
end
|
||||
|
||||
local test_domain = domain
|
||||
local test_url
|
||||
|
||||
if test_domain:match("^https?://") then
|
||||
test_domain = test_domain:gsub("^https?://([^/]+)/?.*$", "%1")
|
||||
if addr:match("^https?://") then
|
||||
addr = addr:gsub("^https?://([^/]+)/?.*$", "%1")
|
||||
end
|
||||
|
||||
if domain == "https://raw.githubusercontent.com/" or test_domain == "raw.githubusercontent.com" then
|
||||
test_url = "https://raw.githubusercontent.com/vernesong/OpenClash/dev/img/logo.png"
|
||||
local urls = {}
|
||||
if addr == "raw.githubusercontent.com" then
|
||||
table.insert(urls, "https://raw.githubusercontent.com/vernesong/OpenClash/dev/img/logo.png")
|
||||
else
|
||||
test_url = "https://" .. test_domain .. "/favicon.ico"
|
||||
table.insert(urls, "https://" .. addr .. "/favicon.ico")
|
||||
end
|
||||
|
||||
local cmd = string.format(
|
||||
'curl -sL -m 5 --connect-timeout 3 --retry 2 -w "%%{http_code},%%{time_total},%%{time_connect},%%{time_appconnect}" "%s" -o /dev/null 2>/dev/null',
|
||||
test_url
|
||||
)
|
||||
table.insert(urls, "https://" .. addr)
|
||||
|
||||
local output = luci.sys.exec(cmd)
|
||||
for i, test_url in ipairs(urls) do
|
||||
local cmd = string.format(
|
||||
'curl -sI -m 5 --connect-timeout 3 --retry 2 -w "%%{http_code},%%{time_total},%%{time_connect},%%{time_appconnect}" "%s" -o /dev/null 2>/dev/null',
|
||||
test_url
|
||||
)
|
||||
|
||||
if output and output ~= "" then
|
||||
local http_code, time_total, time_connect, time_appconnect = output:match("(%d+),([%d%.]+),([%d%.]+),([%d%.]+)")
|
||||
local output = luci.sys.exec(cmd)
|
||||
if output and output ~= "" then
|
||||
local http_code, time_total, time_connect, time_appconnect =
|
||||
output:match("(%d+),([%d%.]+),([%d%.]+),([%d%.]+)")
|
||||
|
||||
if http_code and tonumber(http_code) then
|
||||
local code = tonumber(http_code)
|
||||
local response_time = 0
|
||||
if time_appconnect and tonumber(time_appconnect) and tonumber(time_appconnect) > 0 then
|
||||
response_time = math.floor(tonumber(time_appconnect) * 1000)
|
||||
elseif time_connect and tonumber(time_connect) then
|
||||
response_time = math.floor(tonumber(time_connect) * 1000)
|
||||
else
|
||||
response_time = math.floor((tonumber(time_total) or 0) * 1000)
|
||||
if not http_code then
|
||||
http_code, time_total, time_appconnect = output:match("(%d+),([%d%.]+),([%d%.]+)")
|
||||
time_connect = nil
|
||||
end
|
||||
|
||||
if code >= 200 and code < 400 then
|
||||
result.success = true
|
||||
result.response_time = response_time
|
||||
elseif code == 403 or code == 404 then
|
||||
result.success = true
|
||||
result.response_time = response_time
|
||||
else
|
||||
local fallback_url
|
||||
if domain == "https://raw.githubusercontent.com/" or test_domain == "raw.githubusercontent.com" then
|
||||
fallback_url = "https://raw.githubusercontent.com/vernesong/OpenClash/dev/img/logo.png"
|
||||
if http_code and tonumber(http_code) then
|
||||
local code = tonumber(http_code)
|
||||
local response_time = 0
|
||||
|
||||
if time_appconnect and tonumber(time_appconnect) and tonumber(time_appconnect) > 0 then
|
||||
response_time = math.floor(tonumber(time_appconnect) * 1000)
|
||||
elseif time_connect and tonumber(time_connect) and tonumber(time_connect) > 0 then
|
||||
response_time = math.floor(tonumber(time_connect) * 1000)
|
||||
else
|
||||
fallback_url = "https://" .. test_domain .. "/"
|
||||
response_time = math.floor((tonumber(time_total) or 0) * 1000)
|
||||
end
|
||||
local fallback_cmd = string.format(
|
||||
'curl -sI -m 5 --connect-timeout 3 -w "%%{http_code},%%{time_total},%%{time_appconnect}" "%s" -o /dev/null 2>/dev/null',
|
||||
fallback_url
|
||||
)
|
||||
local fallback_output = luci.sys.exec(fallback_cmd)
|
||||
|
||||
if fallback_output and fallback_output ~= "" then
|
||||
local fb_code, fb_total, fb_appconnect = fallback_output:match("(%d+),([%d%.]+),([%d%.]+)")
|
||||
if fb_code and tonumber(fb_code) then
|
||||
local fb_code_num = tonumber(fb_code)
|
||||
local fb_response_time = 0
|
||||
if fb_appconnect and tonumber(fb_appconnect) and tonumber(fb_appconnect) > 0 then
|
||||
fb_response_time = math.floor(tonumber(fb_appconnect) * 1000)
|
||||
else
|
||||
fb_response_time = math.floor((tonumber(fb_total) or 0) * 1000)
|
||||
end
|
||||
|
||||
if fb_code_num >= 200 and fb_code_num < 400 then
|
||||
result.success = true
|
||||
result.response_time = fb_response_time
|
||||
elseif fb_code_num == 403 or fb_code_num == 404 then
|
||||
result.success = true
|
||||
result.response_time = fb_response_time
|
||||
else
|
||||
result.success = false
|
||||
result.error = "HTTP " .. fb_code_num
|
||||
result.response_time = fb_response_time
|
||||
end
|
||||
else
|
||||
result.success = false
|
||||
result.error = "Connection failed"
|
||||
end
|
||||
if (code >= 200 and code < 400) or code == 403 or code == 404 then
|
||||
result.success = true
|
||||
result.response_time = response_time
|
||||
return result
|
||||
else
|
||||
if i == #urls then
|
||||
result.success = false
|
||||
result.error = "HTTP " .. code
|
||||
result.response_time = response_time
|
||||
return result
|
||||
end
|
||||
end
|
||||
else
|
||||
if i == #urls then
|
||||
result.success = false
|
||||
result.error = "Connection failed"
|
||||
result.error = "Invalid response"
|
||||
return result
|
||||
end
|
||||
end
|
||||
else
|
||||
result.success = false
|
||||
result.error = "Invalid response"
|
||||
if i == #urls then
|
||||
result.success = false
|
||||
result.error = "No response"
|
||||
return result
|
||||
end
|
||||
end
|
||||
else
|
||||
result.success = false
|
||||
result.error = "No response"
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
function action_website_check()
|
||||
local domain = luci.http.formvalue("domain")
|
||||
|
||||
if not domain then
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({
|
||||
success = false,
|
||||
response_time = 0,
|
||||
error = "Missing domain parameter"
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local result = latency_test(domain)
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(result)
|
||||
end
|
||||
@@ -2257,7 +2251,7 @@ function action_switch_oc_setting()
|
||||
local value = luci.http.formvalue("value")
|
||||
|
||||
if not setting or not value then
|
||||
luci.http.status(400, "Missing parameters")
|
||||
luci.http.status(500, "Missing parameters")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -2490,7 +2484,7 @@ function action_switch_oc_setting()
|
||||
end
|
||||
uci:commit("openclash")
|
||||
else
|
||||
luci.http.status(400, "Invalid setting")
|
||||
luci.http.status(500, "Invalid setting")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -2910,14 +2904,14 @@ function action_oc_action()
|
||||
local config_file = luci.http.formvalue("config_file")
|
||||
|
||||
if not action then
|
||||
luci.http.status(400, "Missing action parameter")
|
||||
luci.http.status(500, "Missing action parameter")
|
||||
return
|
||||
end
|
||||
|
||||
if config_file and config_file ~= "" then
|
||||
local config_path = "/etc/openclash/config/" .. config_file
|
||||
if not fs.access(config_path) then
|
||||
luci.http.status(404, "Config file not found")
|
||||
luci.http.status(500, "Config file not found")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -2952,7 +2946,7 @@ function action_oc_action()
|
||||
luci.sys.call("ps | grep openclash | grep -v grep | awk '{print $1}' | xargs -r kill -9 >/dev/null 2>&1")
|
||||
luci.sys.call("/etc/init.d/openclash restart >/dev/null 2>&1")
|
||||
else
|
||||
luci.http.status(400, "Invalid action parameter")
|
||||
luci.http.status(500, "Invalid action parameter")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -3111,7 +3105,7 @@ function action_config_file_read()
|
||||
local config_file = luci.http.formvalue("config_file")
|
||||
|
||||
if not config_file then
|
||||
luci.http.status(400, "Missing config_file parameter")
|
||||
luci.http.status(500, "Missing config_file parameter")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -3202,12 +3196,12 @@ function action_config_file_save()
|
||||
end
|
||||
|
||||
if not config_file then
|
||||
luci.http.status(400, "Missing config_file parameter")
|
||||
luci.http.status(500, "Missing config_file parameter")
|
||||
return
|
||||
end
|
||||
|
||||
if not content then
|
||||
luci.http.status(400, "Missing content parameter")
|
||||
luci.http.status(500, "Missing content parameter")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -3467,9 +3461,7 @@ function action_add_subscription()
|
||||
end
|
||||
end
|
||||
if #params > 0 then
|
||||
for i, param in ipairs(params) do
|
||||
uci:set_list("openclash", section_id, "custom_params", param)
|
||||
end
|
||||
uci:set_list("openclash", section_id, "custom_params", params)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3483,9 +3475,7 @@ function action_add_subscription()
|
||||
end
|
||||
end
|
||||
if #keywords > 0 then
|
||||
for i, kw in ipairs(keywords) do
|
||||
uci:set_list("openclash", section_id, "keyword", kw)
|
||||
end
|
||||
uci:set_list("openclash", section_id, "keyword", keywords)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3499,9 +3489,7 @@ function action_add_subscription()
|
||||
end
|
||||
end
|
||||
if #ex_keywords > 0 then
|
||||
for i, ex_kw in ipairs(ex_keywords) do
|
||||
uci:set_list("openclash", section_id, "ex_keyword", ex_kw)
|
||||
end
|
||||
uci:set_list("openclash", section_id, "ex_keyword", ex_keywords)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3530,6 +3518,16 @@ end
|
||||
function action_upload_overwrite()
|
||||
local upload = luci.http.formvalue("config_file")
|
||||
local filename = luci.http.formvalue("filename")
|
||||
local config_values = {}
|
||||
local raw_config = luci.http.formvalue("config") or ""
|
||||
if raw_config ~= "" then
|
||||
for line in raw_config:gmatch("[^\n]+") do
|
||||
local config_value = line:match("^%s*(.-)%s*$")
|
||||
if config_value and config_value ~= "" then
|
||||
table.insert(config_values, config_value)
|
||||
end
|
||||
end
|
||||
end
|
||||
local enable = luci.http.formvalue("enable")
|
||||
local order = luci.http.formvalue("order")
|
||||
luci.http.prepare_content("application/json")
|
||||
@@ -3575,6 +3573,10 @@ function action_upload_overwrite()
|
||||
uci:foreach("openclash", "config_overwrite", function(s)
|
||||
if s.name == section_name then
|
||||
found = true
|
||||
uci:delete("openclash", s[".name"], "config")
|
||||
if #config_values > 0 then
|
||||
uci:set_list("openclash", s[".name"], "config", config_values)
|
||||
end
|
||||
if s.enable == nil or (s.enable ~= nil and enable ~= nil) then
|
||||
if enable == nil then
|
||||
enable = 0
|
||||
@@ -3600,6 +3602,10 @@ function action_upload_overwrite()
|
||||
local sid = uci:add("openclash", "config_overwrite")
|
||||
uci:set("openclash", sid, "name", section_name)
|
||||
uci:set("openclash", sid, "type", "file")
|
||||
uci:delete("openclash", sid, "config")
|
||||
if #config_values > 0 then
|
||||
uci:set_list("openclash", sid, "config", config_values)
|
||||
end
|
||||
if enable ~= nil then
|
||||
uci:set("openclash", sid, "enable", tostring(enable))
|
||||
else
|
||||
@@ -3657,8 +3663,22 @@ function action_overwrite_subscribe_info()
|
||||
local result = {}
|
||||
uci:foreach("openclash", "config_overwrite", function(s)
|
||||
if s.name then
|
||||
local config_value = ""
|
||||
if s.config then
|
||||
local config_list = {}
|
||||
for _, item in ipairs(s.config) do
|
||||
if item and item ~= "" then
|
||||
table.insert(config_list, tostring(item))
|
||||
end
|
||||
end
|
||||
if #config_list > 0 then
|
||||
config_value = config_list
|
||||
end
|
||||
end
|
||||
|
||||
result[s.name] = {
|
||||
url = s.url or "",
|
||||
config = config_value,
|
||||
update_days = s.update_days or "",
|
||||
update_hour = s.update_hour or "",
|
||||
order = tonumber(s.order) or 0,
|
||||
@@ -3673,7 +3693,7 @@ function action_overwrite_subscribe_info()
|
||||
return
|
||||
elseif method == "POST" then
|
||||
if not section_name then
|
||||
luci.http.status(400, "Missing filename")
|
||||
luci.http.status(500, "Missing filename")
|
||||
return
|
||||
end
|
||||
local url = luci.http.formvalue("url") or ""
|
||||
@@ -3681,6 +3701,16 @@ function action_overwrite_subscribe_info()
|
||||
local update_hour = luci.http.formvalue("update_hour") or ""
|
||||
local order = luci.http.formvalue("order")
|
||||
local param = luci.http.formvalue("param") or ""
|
||||
local config_values = {}
|
||||
local raw_config = luci.http.formvalue("config") or ""
|
||||
if raw_config ~= "" then
|
||||
for line in raw_config:gmatch("[^\n]+") do
|
||||
local config_value = line:match("^%s*(.-)%s*$")
|
||||
if config_value and config_value ~= "" then
|
||||
table.insert(config_values, config_value)
|
||||
end
|
||||
end
|
||||
end
|
||||
typ = luci.http.formvalue("type") or typ or "file"
|
||||
local enable = luci.http.formvalue("enable")
|
||||
|
||||
@@ -3713,6 +3743,10 @@ function action_overwrite_subscribe_info()
|
||||
if s.name == old_section_name then
|
||||
uci:set("openclash", s[".name"], "name", section_name)
|
||||
uci:set("openclash", s[".name"], "url", url)
|
||||
uci:delete("openclash", s[".name"], "config")
|
||||
if #config_values > 0 then
|
||||
uci:set_list("openclash", s[".name"], "config", config_values)
|
||||
end
|
||||
uci:set("openclash", s[".name"], "update_days", update_days)
|
||||
uci:set("openclash", s[".name"], "update_hour", update_hour)
|
||||
uci:set("openclash", s[".name"], "type", typ)
|
||||
@@ -3754,6 +3788,10 @@ function action_overwrite_subscribe_info()
|
||||
uci:foreach("openclash", "config_overwrite", function(s)
|
||||
if s.name == section_name then
|
||||
uci:set("openclash", s[".name"], "url", url)
|
||||
uci:delete("openclash", s[".name"], "config")
|
||||
if #config_values > 0 then
|
||||
uci:set_list("openclash", s[".name"], "config", config_values)
|
||||
end
|
||||
uci:set("openclash", s[".name"], "update_days", update_days)
|
||||
uci:set("openclash", s[".name"], "update_hour", update_hour)
|
||||
uci:set("openclash", s[".name"], "type", typ)
|
||||
@@ -3785,6 +3823,10 @@ function action_overwrite_subscribe_info()
|
||||
local sid = uci:add("openclash", "config_overwrite")
|
||||
uci:set("openclash", sid, "name", section_name)
|
||||
uci:set("openclash", sid, "url", url)
|
||||
uci:delete("openclash", sid, "config")
|
||||
if #config_values > 0 then
|
||||
uci:set_list("openclash", sid, "config", config_values)
|
||||
end
|
||||
uci:set("openclash", sid, "update_days", update_days)
|
||||
uci:set("openclash", sid, "update_hour", update_hour)
|
||||
uci:set("openclash", sid, "type", typ)
|
||||
@@ -3835,7 +3877,7 @@ function action_overwrite_subscribe_info()
|
||||
luci.http.write_json({status="success"})
|
||||
return
|
||||
else
|
||||
luci.http.status(405, "Method Not Allowed")
|
||||
luci.http.status(500, "Method Not Allowed")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3923,7 +3965,7 @@ end
|
||||
function action_get_subscribe_data()
|
||||
local filename = luci.http.formvalue("filename")
|
||||
if not filename then
|
||||
luci.http.status(400, "Bad Request")
|
||||
luci.http.status(500, "Bad Request")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -3941,7 +3983,7 @@ end
|
||||
function action_get_subscribe_info_data()
|
||||
local filename = luci.http.formvalue("filename")
|
||||
if not filename then
|
||||
luci.http.status(400, "Bad Request")
|
||||
luci.http.status(500, "Bad Request")
|
||||
return
|
||||
end
|
||||
luci.http.prepare_content("application/json")
|
||||
|
||||
@@ -11,7 +11,7 @@ local datatype = require "luci.cbi.datatypes"
|
||||
|
||||
-- 优化 CBI UI(新版 LuCI 专用)
|
||||
local function optimize_cbi_ui()
|
||||
luci.http.write([[
|
||||
HTTP.write([[
|
||||
<script type="text/javascript">
|
||||
// 修正上移、下移按钮名称
|
||||
document.querySelectorAll("input.btn.cbi-button.cbi-button-up").forEach(function(btn) {
|
||||
@@ -497,15 +497,15 @@ ds.anonymous = true
|
||||
ds.addremove = true
|
||||
ds.sortable = true
|
||||
ds.template = "openclash/tblsection"
|
||||
ds.extedit = luci.dispatcher.build_url("admin/services/openclash/custom-dns-edit/%s")
|
||||
ds.extedit = DISP.build_url("admin/services/openclash/custom-dns-edit/%s")
|
||||
function ds.create(self, section)
|
||||
local sid = TypedSection.create(self, section)
|
||||
if sid then
|
||||
local name = luci.http.formvalue("cbi.cts.tagname.".. self.config .. "." .. self.sectiontype)
|
||||
local name = HTTP.formvalue("cbi.cts.tagname.".. self.config .. "." .. self.sectiontype)
|
||||
if name and #name > 0 then
|
||||
self.map.uci:set("openclash", sid, "group", name)
|
||||
end
|
||||
luci.http.redirect(ds.extedit % sid)
|
||||
HTTP.redirect(ds.extedit % sid)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,6 +5,8 @@ local uci = luci.model.uci.cursor()
|
||||
local fs = require "luci.openclash"
|
||||
local sys = require "luci.sys"
|
||||
local json = require "luci.jsonc"
|
||||
local HTTP = require "luci.http"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local sid = arg[1]
|
||||
|
||||
font_red = [[<b style=color:red>]]
|
||||
@@ -22,9 +24,9 @@ m.description=translate("Convert Subscribe function of Online is Supported By su
|
||||
"<br/>"..translate("If you need to customize the external configuration file (subscription conversion template), please write it according to the instructions, upload it to the accessible location of the external network, and fill in the address correctly when using it")..
|
||||
"<br/>"..
|
||||
"<br/>"..translate("If you have a recommended external configuration file (subscription conversion template), you can modify by following The file format of /usr/share/openclash/res/sub_ini.list and pr")
|
||||
m.redirect = luci.dispatcher.build_url("admin/services/openclash/config-subscribe")
|
||||
m.redirect = DISP.build_url("admin/services/openclash/config-subscribe")
|
||||
if m.uci:get(openclash, sid) ~= "config_subscribe" then
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -182,7 +184,7 @@ o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:commit(openclash)
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button,"Back", " ")
|
||||
@@ -190,7 +192,7 @@ o.inputtitle = translate("Back Settings")
|
||||
o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
m.uci:revert(openclash, sid)
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
m:append(Template("openclash/toolbar_show"))
|
||||
|
||||
@@ -11,7 +11,7 @@ local uci = require "luci.model.uci".cursor()
|
||||
|
||||
-- 优化 CBI UI(新版 LuCI 专用)
|
||||
local function optimize_cbi_ui()
|
||||
luci.http.write([[
|
||||
HTTP.write([[
|
||||
<script type="text/javascript">
|
||||
// 修正上移、下移按钮名称
|
||||
document.querySelectorAll("input.btn.cbi-button.cbi-button-up").forEach(function(btn) {
|
||||
@@ -90,11 +90,11 @@ s.anonymous = true
|
||||
s.addremove = true
|
||||
s.sortable = true
|
||||
s.template = "cbi/tblsection"
|
||||
s.extedit = luci.dispatcher.build_url("admin/services/openclash/config-subscribe-edit/%s")
|
||||
s.extedit = DISP.build_url("admin/services/openclash/config-subscribe-edit/%s")
|
||||
function s.create(...)
|
||||
local sid = TypedSection.create(...)
|
||||
if sid then
|
||||
luci.http.redirect(s.extedit % sid)
|
||||
HTTP.redirect(s.extedit % sid)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
@@ -204,14 +204,14 @@ end
|
||||
btnis.write=function(a,t)
|
||||
uci:set("openclash", "config", "config_path", "/etc/openclash/config/"..e[t].name)
|
||||
uci:commit("openclash")
|
||||
HTTP.redirect(luci.dispatcher.build_url("admin", "services", "openclash", "config"))
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "config"))
|
||||
end
|
||||
|
||||
btned=tb:option(Button,"edit",translate("Edit"))
|
||||
btned.inputstyle="apply"
|
||||
btned.write=function(a,t)
|
||||
local file_path = "etc/openclash/config/" .. fs.basename(e[t].name)
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "other-file-edit", "config", "%s") % file_path)
|
||||
local file_path = "/etc/openclash/config/" .. fs.basename(e[t].name)
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "other-file-edit", "config") .. "?file=" .. HTTP.urlencode(file_path))
|
||||
end
|
||||
|
||||
btnrn=tb:option(DummyValue,"/etc/openclash/config/",translate("Rename"))
|
||||
@@ -262,8 +262,8 @@ btnapply.write = function(self, t)
|
||||
local action = self.map:formvalue("cbid." .. self.map.config .. "." .. t .. ".actions")
|
||||
|
||||
if action == "servers_manage" then
|
||||
local file_path = "etc/openclash/config/" .. fs.basename(e[t].name)
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "servers", "%s") % file_path)
|
||||
local file_path = "/etc/openclash/config/" .. fs.basename(e[t].name)
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "servers") .. "?file=" .. HTTP.urlencode(file_path))
|
||||
elseif action == "copy" then
|
||||
local num = 1
|
||||
while true do
|
||||
@@ -273,7 +273,7 @@ btnapply.write = function(self, t)
|
||||
break
|
||||
end
|
||||
end
|
||||
HTTP.redirect(luci.dispatcher.build_url("admin", "services", "openclash", "config"))
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "config"))
|
||||
elseif action == "download" then
|
||||
local sPath, sFile, fd, block
|
||||
sPath = "/etc/openclash/config/"..e[t].name
|
||||
|
||||
@@ -3,6 +3,8 @@ local openclash = "openclash"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local fs = require "luci.openclash"
|
||||
local SYS = require "luci.sys"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local HTTP = require "luci.http"
|
||||
local sid = arg[1]
|
||||
|
||||
font_red = [[<b style=color:red>]]
|
||||
@@ -13,9 +15,9 @@ bold_off = [[</strong>]]
|
||||
|
||||
m = Map(openclash, translate("Add Custom DNS Servers"))
|
||||
m.pageaction = false
|
||||
m.redirect = luci.dispatcher.build_url("admin/services/openclash/config-overwrite")
|
||||
m.redirect = DISP.build_url("admin/services/openclash/config-overwrite")
|
||||
if m.uci:get(openclash, sid) ~= "dns_servers" then
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -167,7 +169,7 @@ o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:commit(openclash)
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button,"Back", " ")
|
||||
@@ -175,7 +177,7 @@ o.inputtitle = translate("Back Settings")
|
||||
o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
m.uci:revert(openclash, sid)
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
m:append(Template("openclash/toolbar_show"))
|
||||
|
||||
@@ -4,15 +4,14 @@ local openclash = "openclash"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local fs = require "luci.openclash"
|
||||
local sys = require "luci.sys"
|
||||
local HTTP = require "luci.http"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local sid = arg[1]
|
||||
local file_path = ""
|
||||
local file_path = fs.get_file_path_from_request()
|
||||
|
||||
for i = 2, #(arg) do
|
||||
file_path = file_path .. "/" .. luci.http.urlencode(arg[i])
|
||||
end
|
||||
|
||||
if not fs.isfile(file_path) and file_path ~= "" then
|
||||
file_path = luci.http.urldecode(file_path)
|
||||
if not file_path then
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "servers"))
|
||||
return
|
||||
end
|
||||
|
||||
font_red = [[<b style=color:red>]]
|
||||
@@ -33,9 +32,9 @@ end
|
||||
|
||||
m = Map(openclash, translate("Edit Group"))
|
||||
m.pageaction = false
|
||||
m.redirect = luci.dispatcher.build_url("admin/services/openclash/servers%s" % file_path)
|
||||
m.redirect = DISP.build_url("admin/services/openclash/servers") .. "?file=" .. HTTP.urlencode(file_path)
|
||||
if m.uci:get(openclash, sid) ~= "groups" then
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -314,11 +313,11 @@ o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
local old_name = m.uci:get(openclash, sid, "old_name") or ""
|
||||
local new_name = luci.http.formvalue("cbid.openclash." .. sid .. ".name") or m.uci:get(openclash, sid, "name")
|
||||
local new_name = HTTP.formvalue("cbid.openclash." .. sid .. ".name") or m.uci:get(openclash, sid, "name")
|
||||
sync_group_name(sid, old_name, new_name)
|
||||
m.uci:set(openclash, sid, "old_name", new_name)
|
||||
m.uci:commit(openclash)
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button,"Back", " ")
|
||||
@@ -326,7 +325,7 @@ o.inputtitle = translate("Back Settings")
|
||||
o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
m.uci:revert(openclash, sid)
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
m:append(Template("openclash/toolbar_show"))
|
||||
|
||||
@@ -2,19 +2,17 @@ local NXFS = require "nixio.fs"
|
||||
local SYS = require "luci.sys"
|
||||
local HTTP = require "luci.http"
|
||||
local fs = require "luci.openclash"
|
||||
local file_path = ""
|
||||
local DISP = require "luci.dispatcher"
|
||||
local file_path = fs.get_file_path_from_request()
|
||||
|
||||
for i = 2, #(arg) do
|
||||
file_path = file_path .. "/" .. luci.http.urlencode(arg[i])
|
||||
end
|
||||
|
||||
if not fs.isfile(file_path) and file_path ~= "" then
|
||||
file_path = luci.http.urldecode(file_path)
|
||||
if not file_path then
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "%s") % arg[1])
|
||||
return
|
||||
end
|
||||
|
||||
m = Map("openclash", translate("File Edit"))
|
||||
m.pageaction = false
|
||||
m.redirect = luci.dispatcher.build_url("admin/services/openclash/"..arg[1])
|
||||
m.redirect = DISP.build_url("admin", "services", "openclash", "other-file-edit", "%s") % arg[1].."?file="..HTTP.urlencode(file_path)
|
||||
s = m:section(TypedSection, "openclash")
|
||||
s.anonymous = true
|
||||
s.addremove=false
|
||||
@@ -47,14 +45,14 @@ o = a:option(Button, "Commit", " ")
|
||||
o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button,"Back", " ")
|
||||
o.inputtitle = translate("Back Settings")
|
||||
o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "%s") % arg[1])
|
||||
end
|
||||
|
||||
m:append(Template("openclash/config_editor"))
|
||||
|
||||
@@ -3,16 +3,15 @@ local m, s, o
|
||||
local openclash = "openclash"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local sys = require "luci.sys"
|
||||
local HTTP = require "luci.http"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local sid = arg[1]
|
||||
local fs = require "luci.openclash"
|
||||
local file_path = ""
|
||||
local file_path = fs.get_file_path_from_request()
|
||||
|
||||
for i = 2, #(arg) do
|
||||
file_path = file_path .. "/" .. luci.http.urlencode(arg[i])
|
||||
end
|
||||
|
||||
if not fs.isfile(file_path) and file_path ~= "" then
|
||||
file_path = luci.http.urldecode(file_path)
|
||||
if not file_path then
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "servers"))
|
||||
return
|
||||
end
|
||||
|
||||
font_red = [[<b style=color:red>]]
|
||||
@@ -33,9 +32,9 @@ end
|
||||
|
||||
m = Map(openclash, translate("Edit Proxy-Provider"))
|
||||
m.pageaction = false
|
||||
m.redirect = luci.dispatcher.build_url("admin/services/openclash/servers%s" % file_path)
|
||||
m.redirect = DISP.build_url("admin/services/openclash/servers") .. "?file=" .. HTTP.urlencode(file_path)
|
||||
if m.uci:get(openclash, sid) ~= "proxy-provider" then
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -182,7 +181,7 @@ o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:commit(openclash)
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button,"Back", " ")
|
||||
@@ -190,7 +189,7 @@ o.inputtitle = translate("Back Settings")
|
||||
o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
m.uci:revert(openclash, sid)
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
m:append(Template("openclash/toolbar_show"))
|
||||
|
||||
+2
-2
@@ -38,8 +38,8 @@ p.inputstyle="apply"
|
||||
Button.render(p,x,r)
|
||||
end
|
||||
btned1.write=function(r,x)
|
||||
local file_path = "etc/openclash/proxy_provider/" .. fs.basename(p[x].name)
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "other-file-edit", "proxy-provider-file-manage", "%s") %file_path)
|
||||
local file_path = "/etc/openclash/proxy_provider/" .. fs.basename(p[x].name)
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "other-file-edit", "proxy-provider-file-manage") .. "?file=" .. HTTP.urlencode(file_path))
|
||||
end
|
||||
|
||||
btndl1 = tb1:option(Button,"download1",translate("Download Config"))
|
||||
|
||||
+2
-2
@@ -38,8 +38,8 @@ g.inputstyle="apply"
|
||||
Button.render(g,n,h)
|
||||
end
|
||||
btned1.write=function(h,n)
|
||||
local file_path = "etc/openclash/rule_provider/" .. fs.basename(g[n].name)
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "other-file-edit", "rule-providers-file-manage", "%s") %file_path)
|
||||
local file_path = "/etc/openclash/rule_provider/" .. fs.basename(g[n].name)
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "other-file-edit", "rule-providers-file-manage") .. "?file=" .. HTTP.urlencode(file_path))
|
||||
end
|
||||
|
||||
btndl2 = tb2:option(Button,"download2",translate("Download Config"))
|
||||
|
||||
@@ -4,16 +4,15 @@ local openclash = "openclash"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local fs = require "luci.openclash"
|
||||
local sys = require "luci.sys"
|
||||
local HTTP = require "luci.http"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local sid = arg[1]
|
||||
local uuid = luci.sys.exec("cat /proc/sys/kernel/random/uuid")
|
||||
local file_path = ""
|
||||
local file_path = fs.get_file_path_from_request()
|
||||
|
||||
for i = 2, #(arg) do
|
||||
file_path = file_path .. "/" .. luci.http.urlencode(arg[i])
|
||||
end
|
||||
|
||||
if not fs.isfile(file_path) and file_path ~= "" then
|
||||
file_path = luci.http.urldecode(file_path)
|
||||
if not file_path then
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "servers"))
|
||||
return
|
||||
end
|
||||
|
||||
font_red = [[<b style=color:red>]]
|
||||
@@ -108,10 +107,10 @@ local obfs = {
|
||||
|
||||
m = Map(openclash, translate("Edit Server"))
|
||||
m.pageaction = false
|
||||
m.redirect = luci.dispatcher.build_url("admin/services/openclash/servers%s" % file_path)
|
||||
m.redirect = DISP.build_url("admin/services/openclash/servers") .. "?file=" .. HTTP.urlencode(file_path)
|
||||
|
||||
if m.uci:get(openclash, sid) ~= "servers" then
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -158,6 +157,8 @@ o:value("http", translate("HTTP(S)"))
|
||||
o:value("direct", translate("DIRECT"))
|
||||
o:value("dns", translate("DNS"))
|
||||
o:value("ssh", translate("SSH"))
|
||||
o:value("masque", translate("MASQUE"))
|
||||
o:value("trusttunnel", translate("TrustTunnel"))
|
||||
|
||||
o.description = translate("Using incorrect encryption mothod may causes service fail to start")
|
||||
|
||||
@@ -184,6 +185,8 @@ o:depends("type", "snell")
|
||||
o:depends("type", "socks5")
|
||||
o:depends("type", "http")
|
||||
o:depends("type", "ssh")
|
||||
o:depends("type", "masque")
|
||||
o:depends("type", "trusttunnel")
|
||||
|
||||
o = s:option(Value, "port", translate("Server Port"))
|
||||
o.datatype = "port"
|
||||
@@ -205,6 +208,8 @@ o:depends("type", "snell")
|
||||
o:depends("type", "socks5")
|
||||
o:depends("type", "http")
|
||||
o:depends("type", "ssh")
|
||||
o:depends("type", "masque")
|
||||
o:depends("type", "trusttunnel")
|
||||
|
||||
o = s:option(Flag, "flag_port_hopping", translate("Enable Port Hopping"))
|
||||
o:depends("type", "hysteria")
|
||||
@@ -508,6 +513,8 @@ o:depends({type = "snell", snell_version = "3"})
|
||||
o:depends("type", "wireguard")
|
||||
o:depends("type", "direct")
|
||||
o:depends("type", "anytls")
|
||||
o:depends("type", "masque")
|
||||
o:depends("type", "trusttunnel")
|
||||
|
||||
o = s:option(ListValue, "udp_over_tcp", translate("udp-over-tcp"))
|
||||
o.rmempty = true
|
||||
@@ -549,6 +556,7 @@ o.default = "tcp"
|
||||
o:value("tcp", translate("tcp"))
|
||||
o:value("ws", translate("websocket (ws)"))
|
||||
o:value("grpc", translate("grpc"))
|
||||
o:value("xhttp", translate("xhttp"))
|
||||
o:depends("type", "vless")
|
||||
|
||||
o = s:option(ListValue, "obfs_vmess", translate("obfs-mode"))
|
||||
@@ -637,6 +645,21 @@ o.placeholder = translate("Host: v2ray.com")
|
||||
o:depends("obfs_vmess", "websocket")
|
||||
o:depends("obfs_vless", "ws")
|
||||
|
||||
o = s:option(Value, "xhttp_opts_path", translate("xhttp-opts-path"))
|
||||
o.rmempty = true
|
||||
o.placeholder = translate("/path")
|
||||
o:depends("obfs_vless", "xhttp")
|
||||
|
||||
o = s:option(Value, "xhttp_opts_host", translate("xhttp-opts-host"))
|
||||
o.rmempty = true
|
||||
o.placeholder = translate("xxx.com")
|
||||
o:depends("obfs_vless", "xhttp")
|
||||
|
||||
o = s:option(Value, "vless_encryption", translate("encryption"))
|
||||
o.rmempty = true
|
||||
o.placeholder = translate("mlkem768x25519plus.native/xorpub/random.1rtt/0rtt.(padding len).(padding gap).(X25519 Password).(ML-KEM-768 Client)...")
|
||||
o:depends("obfs_vless", "tcp")
|
||||
|
||||
o = s:option(Value, "vless_flow", translate("flow"))
|
||||
o.rmempty = true
|
||||
o.default = "xtls-rprx-direct"
|
||||
@@ -696,6 +719,7 @@ o:depends("type", "hysteria")
|
||||
o:depends("type", "hysteria2")
|
||||
o:depends("type", "tuic")
|
||||
o:depends("type", "anytls")
|
||||
o:depends("type", "trusttunnel")
|
||||
|
||||
-- [[ TLS ]]--
|
||||
o = s:option(ListValue, "tls", translate("TLS"))
|
||||
@@ -743,6 +767,7 @@ o:depends("type", "http")
|
||||
o:depends("type", "hysteria")
|
||||
o:depends("type", "hysteria2")
|
||||
o:depends("type", "anytls")
|
||||
o:depends("type", "trusttunnel")
|
||||
|
||||
-- [[ headers ]]--
|
||||
o = s:option(DynamicList, "http_headers", translate("headers"))
|
||||
@@ -787,6 +812,7 @@ o:value("h2")
|
||||
o:value("http/1.1")
|
||||
o:depends("type", "trojan")
|
||||
o:depends("type", "anytls")
|
||||
o:depends("type", "trusttunnel")
|
||||
|
||||
-- [[ alpn ]]--
|
||||
o = s:option(DynamicList, "hysteria_alpn", translate("alpn"))
|
||||
@@ -936,7 +962,7 @@ o:value("true")
|
||||
o:value("false")
|
||||
o:depends("type", "vmess")
|
||||
|
||||
-- [[ AnyTLS ]]--
|
||||
-- [[ AnyTLS ]] --
|
||||
o = s:option(Value, "idle_session_check_interval", translate("idle-session-check-interval"))
|
||||
o.rmempty = true
|
||||
o.default = "30"
|
||||
@@ -952,6 +978,70 @@ o.rmempty = true
|
||||
o.default = "0"
|
||||
o:depends("type", "anytls")
|
||||
|
||||
-- [[ MASQUE ]] --
|
||||
o = s:option(Value, "masque_private_key", translate("private-key"))
|
||||
o:depends("type", "masque")
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(Value, "masque_public_key", translate("public-key"))
|
||||
o:depends("type", "masque")
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(Value, "masque_ip", translate("IP"))
|
||||
o:depends("type", "masque")
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(Value, "masque_ipv6", translate("IPv6"))
|
||||
o:depends("type", "masque")
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(Value, "masque_mtu", translate("MTU"))
|
||||
o:depends("type", "masque")
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(ListValue, "masque_remote_dns_resolve", translate("Remote DNS Resolve"))
|
||||
o.rmempty = true
|
||||
o.default = "true"
|
||||
o:value("true")
|
||||
o:value("false")
|
||||
o:depends("type", "masque")
|
||||
|
||||
o = s:option(DynamicList, "masque_dns", translate("DNS"))
|
||||
o.rmempty = true
|
||||
o.placeholder = translate("8.8.8.8")
|
||||
o:depends("type", "masque")
|
||||
|
||||
-- [[ TrustTunnel ]] --
|
||||
o = s:option(Value, "trusttunnel_username", translate("Username"))
|
||||
o.rmempty = false
|
||||
o.placeholder = "user"
|
||||
o:depends("type", "trusttunnel")
|
||||
|
||||
o = s:option(Value, "trusttunnel_password", translate("Password"))
|
||||
o.password = true
|
||||
o.rmempty = false
|
||||
o:depends("type", "trusttunnel")
|
||||
|
||||
o = s:option(ListValue, "trusttunnel_health_check", translate("Health Check"))
|
||||
o.rmempty = true
|
||||
o.default = "true"
|
||||
o:value("true")
|
||||
o:value("false")
|
||||
o:depends("type", "trusttunnel")
|
||||
|
||||
o = s:option(ListValue, "trusttunnel_quic", translate("QUIC"))
|
||||
o.rmempty = true
|
||||
o.default = "false"
|
||||
o:value("true")
|
||||
o:value("false")
|
||||
o:depends("type", "trusttunnel")
|
||||
|
||||
o = s:option(ListValue, "trusttunnel_congestion_controller", translate("Congestion Controller"))
|
||||
o.rmempty = true
|
||||
o.default = "bbr"
|
||||
o:value("bbr")
|
||||
o:depends("type", "trusttunnel")
|
||||
|
||||
-- [[ Fast Open ]]--
|
||||
o = s:option(ListValue, "fast_open", translate("Fast Open"))
|
||||
o.rmempty = true
|
||||
@@ -1010,6 +1100,7 @@ o:depends({type = "vmess", obfs_vmess = "http"})
|
||||
o:depends({type = "vmess", obfs_vmess = "h2"})
|
||||
o:depends({type = "vmess", obfs_vmess = "grpc"})
|
||||
o:depends("type", "anytls")
|
||||
o:depends("type", "trusttunnel")
|
||||
|
||||
-- [[ ip version ]]--
|
||||
o = s:option(ListValue, "ip_version", translate("IP Version"))
|
||||
@@ -1204,7 +1295,7 @@ o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:commit(openclash)
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button,"Back", " ")
|
||||
@@ -1212,7 +1303,7 @@ o.inputtitle = translate("Back Settings")
|
||||
o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
m.uci:revert(openclash, sid)
|
||||
luci.http.redirect(m.redirect)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
m:append(Template("openclash/toolbar_show"))
|
||||
|
||||
@@ -3,18 +3,18 @@ local m, s, o
|
||||
local openclash = "openclash"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local fs = require "luci.openclash"
|
||||
local file_path = ""
|
||||
local HTTP = require "luci.http"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local file_path = fs.get_file_path_from_request()
|
||||
|
||||
for i = 1, #(arg) do
|
||||
file_path = file_path .. "/" .. luci.http.urlencode(arg[i])
|
||||
end
|
||||
|
||||
if not fs.isfile(file_path) and file_path ~= "" then
|
||||
file_path = luci.http.urldecode(file_path)
|
||||
if not file_path then
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "config"))
|
||||
return
|
||||
end
|
||||
|
||||
m = Map(openclash, translate("Servers & Groups manage"))
|
||||
m.pageaction = false
|
||||
m.redirect = DISP.build_url("admin/services/openclash/servers") .. "?file=" .. HTTP.urlencode(file_path)
|
||||
m.description=translate("Attention:")..
|
||||
"<br/>"..translate("1. Before modifying the configuration file, please click the button below to read the configuration file")..
|
||||
"<br/>"..translate("2. Proxy-providers address can be directly filled in the subscription link")..
|
||||
@@ -28,15 +28,15 @@ gs.anonymous = true
|
||||
gs.addremove = true
|
||||
gs.sortable = true
|
||||
gs.template = "openclash/tblsection"
|
||||
gs.extedit = luci.dispatcher.build_url("admin/services/openclash/groups-config/%s"..file_path)
|
||||
gs.extedit = DISP.build_url("admin/services/openclash/groups-config/%s").."?file="..file_path
|
||||
function gs.create(self, section)
|
||||
local sid = TypedSection.create(self, section)
|
||||
if sid then
|
||||
local name = luci.http.formvalue("cbi.cts.tagname.".. self.config .. "." .. self.sectiontype)
|
||||
local name = HTTP.formvalue("cbi.cts.tagname.".. self.config .. "." .. self.sectiontype)
|
||||
if name and #name > 0 then
|
||||
self.map.uci:set("openclash", sid, "config", name)
|
||||
end
|
||||
luci.http.redirect(gs.extedit % sid)
|
||||
HTTP.redirect(gs.extedit % sid)
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -70,15 +70,15 @@ ps.anonymous = true
|
||||
ps.addremove = true
|
||||
ps.sortable = true
|
||||
ps.template = "openclash/tblsection"
|
||||
ps.extedit = luci.dispatcher.build_url("admin/services/openclash/proxy-provider-config/%s"..file_path)
|
||||
ps.extedit = DISP.build_url("admin/services/openclash/proxy-provider-config/%s").."?file="..file_path
|
||||
function ps.create(self, section)
|
||||
local sid = TypedSection.create(self, section)
|
||||
if sid then
|
||||
local name = luci.http.formvalue("cbi.cts.tagname.".. self.config .. "." .. self.sectiontype)
|
||||
local name = HTTP.formvalue("cbi.cts.tagname.".. self.config .. "." .. self.sectiontype)
|
||||
if name and #name > 0 then
|
||||
self.map.uci:set("openclash", sid, "config", name)
|
||||
end
|
||||
luci.http.redirect(ps.extedit % sid)
|
||||
HTTP.redirect(ps.extedit % sid)
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -111,15 +111,15 @@ ss.anonymous = true
|
||||
ss.addremove = true
|
||||
ss.sortable = true
|
||||
ss.template = "openclash/tblsection"
|
||||
ss.extedit = luci.dispatcher.build_url("admin/services/openclash/servers-config/%s"..file_path)
|
||||
ss.extedit = DISP.build_url("admin/services/openclash/servers-config/%s").."?file="..file_path
|
||||
function ss.create(self, section)
|
||||
local sid = TypedSection.create(self, section)
|
||||
if sid then
|
||||
local name = luci.http.formvalue("cbi.cts.tagname.".. self.config .. "." .. self.sectiontype)
|
||||
local name = HTTP.formvalue("cbi.cts.tagname.".. self.config .. "." .. self.sectiontype)
|
||||
if name and #name > 0 then
|
||||
self.map.uci:set("openclash", sid, "config", name)
|
||||
end
|
||||
luci.http.redirect(ss.extedit % sid)
|
||||
HTTP.redirect(ss.extedit % sid)
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -223,6 +223,7 @@ o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:commit("openclash")
|
||||
luci.sys.call("/usr/share/openclash/yml_groups_get.sh \"%s\" 2>/dev/null" % file_path)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button, "Commit", " ")
|
||||
@@ -230,6 +231,7 @@ o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:commit("openclash")
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button, "Apply", " ")
|
||||
@@ -238,13 +240,14 @@ o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:commit("openclash")
|
||||
luci.sys.call("/usr/share/openclash/yml_groups_set.sh \"%s\" >/dev/null 2>&1 &" % file_path)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button,"Back", " ")
|
||||
o.inputtitle = translate("Back Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "openclash", "config"))
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "config"))
|
||||
end
|
||||
|
||||
m:append(Template("openclash/toolbar_show"))
|
||||
|
||||
@@ -18,7 +18,7 @@ end
|
||||
|
||||
-- 优化 CBI UI(新版 LuCI 专用)
|
||||
local function optimize_cbi_ui()
|
||||
luci.http.write([[
|
||||
HTTP.write([[
|
||||
<script type="text/javascript">
|
||||
// 修正上移、下移按钮名称
|
||||
document.querySelectorAll("input.btn.cbi-button.cbi-button-up").forEach(function(btn) {
|
||||
|
||||
@@ -31,6 +31,7 @@ local fs = require "nixio.fs"
|
||||
local nutil = require "nixio.util"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local SYS = require "luci.sys"
|
||||
local HTTP = require "luci.http"
|
||||
|
||||
local type = type
|
||||
local string = string
|
||||
@@ -335,4 +336,24 @@ function uci_get_config(section, key)
|
||||
val = uci:get("openclash", section, key)
|
||||
end
|
||||
return val
|
||||
end
|
||||
|
||||
function get_file_path_from_request()
|
||||
local file_path
|
||||
local referer = HTTP.getenv("HTTP_REFERER")
|
||||
if referer then
|
||||
local _, _, file_value = referer:find("file=([^&]*)$")
|
||||
if file_value and file_value ~= "" then
|
||||
file_path = HTTP.urldecode(file_value)
|
||||
end
|
||||
end
|
||||
|
||||
if not file_path or file_path == "/" then
|
||||
file_path = HTTP.formvalue("file")
|
||||
if not file_path then
|
||||
file_path = HTTP.urldecode(file_path)
|
||||
end
|
||||
end
|
||||
|
||||
return file_path
|
||||
end
|
||||
@@ -110,6 +110,12 @@
|
||||
white-space: nowrap;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.oc .config-editor-title #editTitle,
|
||||
.oc .config-editor-title #config-file-name {
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
cursor: text;
|
||||
}
|
||||
.oc .config-editor-title .config-file-name {
|
||||
color: var(--primary-color);
|
||||
font-weight: 700;
|
||||
@@ -362,6 +368,149 @@
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.oc .overwrite-config-dropdown {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.oc .overwrite-config-dropdown-btn {
|
||||
width: 100%;
|
||||
min-height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 6px 10px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.oc .overwrite-config-dropdown.form-select-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
.oc .overwrite-config-dropdown-btn.form-select {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
}
|
||||
.oc .overwrite-config-dropdown-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: calc(100% - 20px);
|
||||
text-align: left;
|
||||
}
|
||||
.oc .overwrite-config-dropdown-arrow {
|
||||
margin-left: 8px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 4px solid var(--text-secondary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.oc .overwrite-config-dropdown-panel {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-white);
|
||||
box-shadow: var(--shadow-md);
|
||||
z-index: 10003;
|
||||
}
|
||||
.oc .overwrite-config-dropdown.open .overwrite-config-dropdown-panel {
|
||||
display: block;
|
||||
}
|
||||
.oc .overwrite-config-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
background: var(--bg-white);
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.oc .overwrite-config-option:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.oc .overwrite-config-option:hover {
|
||||
background: var(--primary-color);
|
||||
color: var(--select-hover);
|
||||
}
|
||||
.oc[data-darkmode="true"] .overwrite-config-option {
|
||||
background: var(--bg-gray);
|
||||
border-bottom-color: var(--border-light);
|
||||
}
|
||||
.oc[data-darkmode="true"] .overwrite-config-option:hover {
|
||||
background: var(--primary-color);
|
||||
color: var(--select-hover);
|
||||
}
|
||||
.oc .overwrite-config-option:hover .overwrite-config-option-state {
|
||||
border-color: var(--select-hover);
|
||||
}
|
||||
.oc[data-darkmode="true"] .overwrite-config-option-state {
|
||||
background: var(--bg-white);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
.oc .overwrite-config-option.disabled-by-all {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.55;
|
||||
background: var(--bg-gray);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.oc .overwrite-config-option.disabled-by-all:hover {
|
||||
background: var(--bg-gray);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.oc .overwrite-config-option.disabled-by-all .overwrite-config-option-state {
|
||||
border-color: var(--border-light);
|
||||
color: transparent;
|
||||
}
|
||||
.oc .overwrite-config-option-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.oc .overwrite-config-option-left input[type="checkbox"] {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.oc .overwrite-config-option-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
}
|
||||
.oc .overwrite-config-option-state {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: transparent;
|
||||
background: var(--bg-white);
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
.oc .overwrite-config-option.selected .overwrite-config-option-state {
|
||||
border-color: var(--primary-color);
|
||||
background: var(--primary-color);
|
||||
color: var(--select-hover);
|
||||
}
|
||||
.oc .overwrite-config-dropdown.disabled .overwrite-config-dropdown-btn {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.oc .overwrite-card-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
@@ -1407,6 +1556,11 @@ var ConfigEditor = {
|
||||
.then(function(data) {
|
||||
if (data.status === 'success') {
|
||||
self.overwriteSubInfo = (data && data.data) ? data.data : {};
|
||||
Object.keys(self.overwriteSubInfo).forEach(function(key) {
|
||||
var subInfo = self.overwriteSubInfo[key] || {};
|
||||
subInfo.config = self.parseOverwriteConfigValue(subInfo.config);
|
||||
self.overwriteSubInfo[key] = subInfo;
|
||||
});
|
||||
self.overwriteFiles.sort(function(a, b) {
|
||||
var an = a.name || (a.path ? a.path.split('/').pop() : '');
|
||||
var bn = b.name || (b.path ? b.path.split('/').pop() : '');
|
||||
@@ -1530,6 +1684,7 @@ var ConfigEditor = {
|
||||
formData.append('update_hour', sub.update_hour || '');
|
||||
formData.append('order', (typeof sub.order !== 'undefined' && sub.order !== null && sub.order !== '') ? sub.order : idx);
|
||||
formData.append('param', sub.param || '');
|
||||
formData.append('config', self.parseOverwriteConfigValue(sub.config).join('\n'));
|
||||
formData.append('enable', newEnable);
|
||||
fetch('/cgi-bin/luci/admin/services/openclash/overwrite_subscribe_info', {
|
||||
method: 'POST',
|
||||
@@ -1590,6 +1745,7 @@ var ConfigEditor = {
|
||||
formData.append('update_hour', sub.update_hour || '');
|
||||
formData.append('order', (typeof sub.order !== 'undefined' && sub.order !== null && sub.order !== '') ? sub.order : idx);
|
||||
formData.append('param', sub.param || '');
|
||||
formData.append('config', self.parseOverwriteConfigValue(sub.config).join('\n'));
|
||||
formData.append('enable', typeof sub.enable !== 'undefined' ? sub.enable : 1);
|
||||
fetch('/cgi-bin/luci/admin/services/openclash/overwrite_subscribe_info', {
|
||||
method: 'POST',
|
||||
@@ -2116,6 +2272,7 @@ var ConfigEditor = {
|
||||
formData.append('update_days', sub.update_days || '');
|
||||
formData.append('update_hour', sub.update_hour || '');
|
||||
formData.append('param', sub.param || '');
|
||||
formData.append('config', self.parseOverwriteConfigValue(sub.config).join('\n'));
|
||||
formData.append('order', idx);
|
||||
reqs.push(fetch('/cgi-bin/luci/admin/services/openclash/overwrite_subscribe_info', {
|
||||
method: 'POST',
|
||||
@@ -2127,12 +2284,225 @@ var ConfigEditor = {
|
||||
});
|
||||
},
|
||||
|
||||
getOverwriteConfigFiles: function() {
|
||||
var rawList = [];
|
||||
if (window.configFiles && Array.isArray(window.configFiles)) {
|
||||
rawList = window.configFiles;
|
||||
} else if (window.ConfigFileManager && Array.isArray(window.ConfigFileManager.configList)) {
|
||||
rawList = window.ConfigFileManager.configList;
|
||||
}
|
||||
|
||||
return rawList.map(function(file) {
|
||||
if (typeof file === 'string') {
|
||||
return {
|
||||
name: file,
|
||||
path: file
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: file.name || file.filename || file.path || '',
|
||||
path: file.path || file.filepath || file.name || ''
|
||||
};
|
||||
}).filter(function(file) {
|
||||
return !!file.path;
|
||||
});
|
||||
},
|
||||
|
||||
parseOverwriteConfigValue: function(value) {
|
||||
if (!value) return [];
|
||||
if (Array.isArray(value)) {
|
||||
return value.filter(function(item) { return !!item; });
|
||||
}
|
||||
return String(value).split(/[\r\n,;]+/).map(function(item) {
|
||||
return item.trim();
|
||||
}).filter(function(item) {
|
||||
return !!item;
|
||||
});
|
||||
},
|
||||
|
||||
renderOverwriteConfigDropdown: function(configValue, dropdownId) {
|
||||
var files = this.getOverwriteConfigFiles();
|
||||
var selected = this.parseOverwriteConfigValue(configValue);
|
||||
var selectedMap = {};
|
||||
selected.forEach(function(item) {
|
||||
selectedMap[item] = true;
|
||||
});
|
||||
|
||||
if (!selected.length) {
|
||||
selectedMap['all'] = true;
|
||||
}
|
||||
|
||||
var allChecked = !!selectedMap['all'];
|
||||
|
||||
var allOptionHtml = `
|
||||
<label class="overwrite-config-option${allChecked ? ' selected' : ''}" data-path="all" data-all-option="1">
|
||||
<span class="overwrite-config-option-left">
|
||||
<input type="checkbox" value="all"${allChecked ? ' checked' : ''}>
|
||||
<span class="overwrite-config-option-name" title="<%:Use For All Config File%>"><%:Use For All Config File%></span>
|
||||
</span>
|
||||
<span class="overwrite-config-option-state">${allChecked ? '✓' : ''}</span>
|
||||
</label>
|
||||
`;
|
||||
|
||||
var fileOptionsHtml = files.length ? files.map(function(file) {
|
||||
var checked = (!allChecked && (selectedMap[file.path] || selectedMap[file.name])) ? ' checked' : '';
|
||||
var selectedClass = checked ? ' selected' : '';
|
||||
var disabledClass = allChecked ? ' disabled-by-all' : '';
|
||||
var stateText = checked ? '✓' : '';
|
||||
var disabled = allChecked;
|
||||
return `
|
||||
<label class="overwrite-config-option${selectedClass}${disabledClass}" data-path="${file.path}">
|
||||
<span class="overwrite-config-option-left">
|
||||
<input type="checkbox" value="${file.path}"${checked}${disabled ? ' disabled' : ''}>
|
||||
<span class="overwrite-config-option-name" title="${file.name}">${file.name}</span>
|
||||
</span>
|
||||
<span class="overwrite-config-option-state">${stateText}</span>
|
||||
</label>
|
||||
`;
|
||||
}).join('') : '<div class="overwrite-config-option"><span class="overwrite-config-option-name"><%:No config files found%></span></div>';
|
||||
|
||||
var optionsHtml = allOptionHtml + fileOptionsHtml;
|
||||
|
||||
return `
|
||||
<div class="overwrite-config-dropdown form-select-wrapper" id="${dropdownId}">
|
||||
<button type="button" class="overwrite-config-dropdown-btn form-select">
|
||||
<span class="overwrite-config-dropdown-text"><%:Use For All Config File%></span>
|
||||
<span class="overwrite-config-dropdown-arrow"></span>
|
||||
</button>
|
||||
<div class="overwrite-config-dropdown-panel">
|
||||
${optionsHtml}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
updateOverwriteConfigDropdownLabel: function(dropdown) {
|
||||
if (!dropdown) return;
|
||||
var textEl = dropdown.querySelector('.overwrite-config-dropdown-text');
|
||||
if (!textEl) return;
|
||||
var checked = dropdown.querySelectorAll('.overwrite-config-option input[type="checkbox"]:checked');
|
||||
if (!checked.length) {
|
||||
textEl.textContent = '<%:Use For All Config File%>';
|
||||
return;
|
||||
}
|
||||
if (checked.length === 1) {
|
||||
var oneName = checked[0].closest('.overwrite-config-option').querySelector('.overwrite-config-option-name');
|
||||
textEl.textContent = oneName ? oneName.textContent : '<%:1 file selected%>';
|
||||
return;
|
||||
}
|
||||
textEl.textContent = checked.length + ' <%:files selected%>';
|
||||
},
|
||||
|
||||
bindOverwriteConfigDropdown: function(container) {
|
||||
if (!container || container.dataset.inited === '1') return;
|
||||
container.dataset.inited = '1';
|
||||
|
||||
var self = this;
|
||||
var btn = container.querySelector('.overwrite-config-dropdown-btn');
|
||||
var panel = container.querySelector('.overwrite-config-dropdown-panel');
|
||||
|
||||
function syncAllExclusiveState() {
|
||||
var allInput = container.querySelector('.overwrite-config-option input[type="checkbox"][value="all"]');
|
||||
var allOption = allInput ? allInput.closest('.overwrite-config-option') : null;
|
||||
var allState = allOption ? allOption.querySelector('.overwrite-config-option-state') : null;
|
||||
var allSelected = !!(allInput && allInput.checked);
|
||||
|
||||
if (allOption) {
|
||||
allOption.classList.toggle('selected', allSelected);
|
||||
}
|
||||
if (allState) {
|
||||
allState.textContent = allSelected ? '✓' : '';
|
||||
}
|
||||
|
||||
container.querySelectorAll('.overwrite-config-option input[type="checkbox"]').forEach(function(input) {
|
||||
if (input.value === 'all') return;
|
||||
var option = input.closest('.overwrite-config-option');
|
||||
var state = option ? option.querySelector('.overwrite-config-option-state') : null;
|
||||
if (allSelected) {
|
||||
input.checked = false;
|
||||
}
|
||||
input.disabled = allSelected;
|
||||
if (option) {
|
||||
option.classList.toggle('selected', input.checked);
|
||||
option.classList.toggle('disabled-by-all', allSelected);
|
||||
option.setAttribute('aria-disabled', allSelected ? 'true' : 'false');
|
||||
}
|
||||
if (state) {
|
||||
state.textContent = input.checked ? '✓' : '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.updateOverwriteConfigDropdownLabel(container);
|
||||
syncAllExclusiveState();
|
||||
this.updateOverwriteConfigDropdownLabel(container);
|
||||
|
||||
if (btn && !btn.disabled) {
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
container.classList.toggle('open');
|
||||
});
|
||||
}
|
||||
|
||||
container.querySelectorAll('.overwrite-config-option input[type="checkbox"]').forEach(function(input) {
|
||||
input.addEventListener('change', function() {
|
||||
if (input.value === 'all') {
|
||||
syncAllExclusiveState();
|
||||
} else if (input.checked) {
|
||||
var allInput = container.querySelector('.overwrite-config-option input[type="checkbox"][value="all"]');
|
||||
if (allInput) {
|
||||
allInput.checked = false;
|
||||
}
|
||||
syncAllExclusiveState();
|
||||
}
|
||||
|
||||
var option = input.closest('.overwrite-config-option');
|
||||
var state = option ? option.querySelector('.overwrite-config-option-state') : null;
|
||||
if (option) {
|
||||
option.classList.toggle('selected', input.checked);
|
||||
}
|
||||
if (state) {
|
||||
state.textContent = input.checked ? '✓' : '';
|
||||
}
|
||||
self.updateOverwriteConfigDropdownLabel(container);
|
||||
});
|
||||
});
|
||||
|
||||
if (panel) {
|
||||
panel.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!container.contains(e.target)) {
|
||||
container.classList.remove('open');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getOverwriteConfigSelection: function(root, dropdownId) {
|
||||
var dropdown = root.querySelector('#' + dropdownId);
|
||||
if (!dropdown) return [];
|
||||
var selected = Array.from(dropdown.querySelectorAll('.overwrite-config-option input[type="checkbox"]:checked')).map(function(input) {
|
||||
return input.value;
|
||||
}).filter(function(value) {
|
||||
return !!value;
|
||||
});
|
||||
if (selected.indexOf('all') !== -1) {
|
||||
return ['all'];
|
||||
}
|
||||
return selected;
|
||||
},
|
||||
|
||||
renderOverwriteSubForm: function(options) {
|
||||
var name = options.name || '';
|
||||
var sub = options.sub || {};
|
||||
var readonly = !!options.readonly;
|
||||
var showTabs = !!options.showTabs;
|
||||
var activeTab = options.activeTab || 'file';
|
||||
var fileConfigDropdown = this.renderOverwriteConfigDropdown(sub.config || '', 'overwrite-upload-config-dropdown');
|
||||
var subscribeConfigDropdown = this.renderOverwriteConfigDropdown(sub.config || '', 'overwrite-subscribe-config-dropdown');
|
||||
|
||||
var tabsHtml = showTabs ? `
|
||||
<div class="upload-mode-selector">
|
||||
@@ -2160,6 +2530,10 @@ var ConfigEditor = {
|
||||
|
||||
var fileContent = `
|
||||
<form id="overwrite-upload-form-file" style="display:${activeTab==='file'?'block':'none'};">
|
||||
<div class="form-group" style="margin-bottom:20px;">
|
||||
<label for="overwrite-upload-config"><%:Config File%>:</label>
|
||||
${fileConfigDropdown}
|
||||
</div>
|
||||
<div class="upload-zone" id="overwrite-upload-zone">
|
||||
<div class="upload-icon">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1">
|
||||
@@ -2187,6 +2561,10 @@ var ConfigEditor = {
|
||||
<label><%:File Name%>:</label>
|
||||
<input type="text" class="form-input" name="filename" id="overwrite-subscribe-filename" value="${name}" ${readonly ? 'readonly' : ''} placeholder="<%:Please enter a filename%>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="overwrite-subscribe-config"><%:Config File%>:</label>
|
||||
${subscribeConfigDropdown}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><%:Type%>:</label>
|
||||
<div class="form-select-wrapper">
|
||||
@@ -2299,6 +2677,9 @@ var ConfigEditor = {
|
||||
ocDiv.appendChild(overlay);
|
||||
document.body.appendChild(ocDiv);
|
||||
|
||||
this.bindOverwriteConfigDropdown(model.querySelector('#overwrite-upload-config-dropdown'));
|
||||
this.bindOverwriteConfigDropdown(model.querySelector('#overwrite-subscribe-config-dropdown'));
|
||||
|
||||
var tabFile = model.querySelector('#overwrite-upload-mode-file');
|
||||
var tabSub = model.querySelector('#overwrite-upload-mode-subscribe');
|
||||
var contentFile = model.querySelector('#overwrite-upload-content-file');
|
||||
@@ -2439,11 +2820,13 @@ var ConfigEditor = {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
var fileContent = e.target.result;
|
||||
var selectedConfigPaths = self.getOverwriteConfigSelection(model, 'overwrite-upload-config-dropdown');
|
||||
var formData = new FormData();
|
||||
formData.append('filename', filename);
|
||||
formData.append('config_file', fileContent);
|
||||
formData.append('order', self.overwriteFileCardCount + 1);
|
||||
formData.append('enable', '0');
|
||||
formData.append('config', selectedConfigPaths.join('\n'));
|
||||
|
||||
fetch('/cgi-bin/luci/admin/services/openclash/upload_overwrite', {
|
||||
method: 'POST',
|
||||
@@ -2479,6 +2862,7 @@ var ConfigEditor = {
|
||||
var update_hour = form.querySelector('#overwrite-subscribe-update-hour').value;
|
||||
var type = form.querySelector('#overwrite-subscribe-type').value;
|
||||
var param = form.querySelector('#overwrite-subscribe-param').value.trim();
|
||||
var selectedConfigPaths = self.getOverwriteConfigSelection(model, 'overwrite-subscribe-config-dropdown');
|
||||
if (!filename) {
|
||||
alert('<%:Please enter a filename%>');
|
||||
return;
|
||||
@@ -2501,6 +2885,7 @@ var ConfigEditor = {
|
||||
formData.append('param', param);
|
||||
formData.append('order', self.overwriteFileCardCount + 1);
|
||||
formData.append('enable', '0');
|
||||
formData.append('config', selectedConfigPaths.join('\n'));
|
||||
if (type === 'http') {
|
||||
formData.append('url', url);
|
||||
formData.append('update_days', update_days);
|
||||
@@ -2603,6 +2988,8 @@ var ConfigEditor = {
|
||||
ocDiv.appendChild(overlay);
|
||||
document.body.appendChild(ocDiv);
|
||||
|
||||
this.bindOverwriteConfigDropdown(model.querySelector('#overwrite-subscribe-config-dropdown'));
|
||||
|
||||
var typeSelect = model.querySelector('#overwrite-subscribe-type');
|
||||
var urlGroup = model.querySelector('#overwrite-subscribe-url-group');
|
||||
var updateGroup = model.querySelector('#overwrite-subscribe-update-group');
|
||||
@@ -2663,6 +3050,7 @@ var ConfigEditor = {
|
||||
var type = form.querySelector('#overwrite-subscribe-type').value;
|
||||
var url = form.querySelector('#overwrite-subscribe-url').value.trim();
|
||||
var param = form.querySelector('#overwrite-subscribe-param').value.trim();
|
||||
var selectedConfigPaths = self.getOverwriteConfigSelection(model, 'overwrite-subscribe-config-dropdown');
|
||||
|
||||
if (!newName) {
|
||||
alert('<%:Please enter a filename%>');
|
||||
@@ -2689,6 +3077,8 @@ var ConfigEditor = {
|
||||
formData.delete('update_days');
|
||||
formData.delete('update_hour');
|
||||
}
|
||||
formData.delete('config');
|
||||
formData.append('config', selectedConfigPaths.join('\n'));
|
||||
if (newName !== name) {
|
||||
formData.append('old_filename', name);
|
||||
}
|
||||
@@ -2761,6 +3151,13 @@ var ConfigEditor = {
|
||||
}
|
||||
this.hideMergeView();
|
||||
this.hide();
|
||||
|
||||
if (window.OverwriteSubscribeManager && typeof window.OverwriteSubscribeManager.load === 'function') {
|
||||
window.OverwriteSubscribeManager.load(true);
|
||||
}
|
||||
try {
|
||||
window.dispatchEvent(new Event('oc-overwrite-updated'));
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
updateZoom: function(newZoom) {
|
||||
@@ -2800,6 +3197,29 @@ var ConfigEditor = {
|
||||
this.updateZoom(100);
|
||||
},
|
||||
|
||||
isPointOnTextContent: function(el, clientX, clientY) {
|
||||
if (!el) return false;
|
||||
|
||||
var textWalker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
|
||||
var textNode;
|
||||
while ((textNode = textWalker.nextNode())) {
|
||||
if (!textNode.nodeValue || !textNode.nodeValue.trim()) {
|
||||
continue;
|
||||
}
|
||||
var range = document.createRange();
|
||||
range.selectNodeContents(textNode);
|
||||
var rects = range.getClientRects();
|
||||
for (var i = 0; i < rects.length; i++) {
|
||||
var rect = rects[i];
|
||||
if (clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
makeDraggable: function() {
|
||||
var self = this;
|
||||
var header = this.model.querySelector('.config-editor-header');
|
||||
@@ -2807,7 +3227,12 @@ var ConfigEditor = {
|
||||
var isDragging = false;
|
||||
|
||||
header.addEventListener('mousedown', function(e) {
|
||||
if (e.target.closest('.config-editor-actions')) {
|
||||
var target = e.target && e.target.nodeType === 1 ? e.target : e.target.parentElement;
|
||||
if (target && target.closest('.config-editor-actions')) {
|
||||
return;
|
||||
}
|
||||
var textEl = target ? target.closest('#editTitle, #config-file-name') : null;
|
||||
if (textEl && self.isPointOnTextContent(textEl, e.clientX, e.clientY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2864,7 +3289,13 @@ var ConfigEditor = {
|
||||
}
|
||||
|
||||
header.addEventListener('touchstart', function(e) {
|
||||
if (e.target.closest('.config-editor-actions')) {
|
||||
var target = e.target && e.target.nodeType === 1 ? e.target : e.target.parentElement;
|
||||
if (target && target.closest('.config-editor-actions')) {
|
||||
return;
|
||||
}
|
||||
var touch = e.touches && e.touches[0] ? e.touches[0] : null;
|
||||
var textEl = target ? target.closest('#editTitle, #config-file-name') : null;
|
||||
if (touch && textEl && self.isPointOnTextContent(textEl, touch.clientX, touch.clientY)) {
|
||||
return;
|
||||
}
|
||||
if (e.touches.length !== 1) return;
|
||||
|
||||
@@ -933,7 +933,7 @@
|
||||
}
|
||||
else {
|
||||
if (!refresh_http) {
|
||||
refresh_http = setInterval("HTTP.runcheck()", Math.floor(Math.random()*(10-5+1)+5)*1000);
|
||||
refresh_http = setInterval("HTTP.runcheck()", Math.floor(Math.random()*(20-5+1)+5)*1000);
|
||||
}
|
||||
if (!refresh_ip) {
|
||||
if (use_router_mode) {
|
||||
@@ -1074,7 +1074,7 @@
|
||||
get_router_ip_info();
|
||||
HTTP.runcheck();
|
||||
|
||||
refresh_http = setInterval("HTTP.runcheck()", Math.floor(Math.random()*(10-5+1)+5)*1000);
|
||||
refresh_http = setInterval("HTTP.runcheck()", Math.floor(Math.random()*(20-5+1)+5)*1000);
|
||||
if (localStorage.getItem('privacy_my_ip') !== 'true') {
|
||||
refresh_ip = setInterval("get_router_ip_info()", Math.floor(Math.random()*(40-15+1)+15)*1000);
|
||||
}
|
||||
@@ -1097,7 +1097,7 @@
|
||||
myip_Load();
|
||||
HTTP.runcheck();
|
||||
|
||||
refresh_http = setInterval("HTTP.runcheck()", Math.floor(Math.random()*(10-5+1)+5)*1000);
|
||||
refresh_http = setInterval("HTTP.runcheck()", Math.floor(Math.random()*(20-5+1)+5)*1000);
|
||||
if (localStorage.getItem('privacy_my_ip') !== 'true') {
|
||||
refresh_ip = setInterval("myip_Load()", Math.floor(Math.random()*(40-15+1)+15)*1000);
|
||||
}
|
||||
@@ -1249,7 +1249,7 @@
|
||||
if (use_router_mode) {
|
||||
get_router_ip_info();
|
||||
HTTP.runcheck();
|
||||
refresh_http = setInterval("HTTP.runcheck()", Math.floor(Math.random()*(10-5+1)+5)*1000);
|
||||
refresh_http = setInterval("HTTP.runcheck()", Math.floor(Math.random()*(20-5+1)+5)*1000);
|
||||
if (localStorage.getItem('privacy_my_ip') !== 'true') {
|
||||
refresh_ip = setInterval("get_router_ip_info()", Math.floor(Math.random()*(40-15+1)+15)*1000);
|
||||
}
|
||||
@@ -1268,7 +1268,7 @@
|
||||
myip_Load();
|
||||
HTTP.runcheck();
|
||||
|
||||
refresh_http = setInterval("HTTP.runcheck()", Math.floor(Math.random()*(10-5+1)+5)*1000);
|
||||
refresh_http = setInterval("HTTP.runcheck()", Math.floor(Math.random()*(20-5+1)+5)*1000);
|
||||
if (localStorage.getItem('privacy_my_ip') !== 'true') {
|
||||
refresh_ip = setInterval("myip_Load()", Math.floor(Math.random()*(40-15+1)+15)*1000);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
--text-primary: #374151;
|
||||
--text-secondary: #64748b;
|
||||
--text-title: #4d4d4d;
|
||||
--select-hover: #ebebeb;
|
||||
--border-color: #b1b1b1;
|
||||
--border-light: #e2e8f0;
|
||||
--hover-bg: #f8fafc;
|
||||
@@ -37,6 +38,7 @@
|
||||
--text-primary: #ebebeb;
|
||||
--text-secondary: #d0cfcf;
|
||||
--text-title: #e5e7eb;
|
||||
--select-hover: #ebebeb;
|
||||
--border-color: #939393;
|
||||
--border-light: #6b7280;
|
||||
--hover-bg: #374151;
|
||||
@@ -62,7 +64,7 @@
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.select-popup {
|
||||
.oc .select-popup {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
@@ -77,9 +79,11 @@
|
||||
width: 60%;
|
||||
box-shadow: var(--shadow-md);
|
||||
transition: all var(--transition-normal);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
.oc .close-btn {
|
||||
background: none;
|
||||
border: 1px solid var(--border-light);
|
||||
color: var(--text-secondary);
|
||||
@@ -97,7 +101,7 @@
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
.oc .close-btn:hover {
|
||||
background: var(--hover-bg) !important;
|
||||
border-color: var(--primary-color) !important;
|
||||
color: var(--primary-color) !important;
|
||||
@@ -105,23 +109,23 @@
|
||||
box-shadow: var(--shadow-sm) !important;
|
||||
}
|
||||
|
||||
.close-btn:focus {
|
||||
.oc .close-btn:focus {
|
||||
outline: none;
|
||||
box-shadow: var(--shadow-sm) !important;
|
||||
border-color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
.close-btn svg {
|
||||
.oc .close-btn svg {
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
.select-popup.hidden {
|
||||
.oc .select-popup.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select-popup-header {
|
||||
.oc .select-popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -135,7 +139,7 @@
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.select-config-section {
|
||||
.oc .select-config-section {
|
||||
background: var(--bg-gray);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-md);
|
||||
@@ -144,14 +148,14 @@
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.config-grid {
|
||||
.oc .config-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--gap-size);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.config-item {
|
||||
.oc .config-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
@@ -160,7 +164,7 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.config-item::after {
|
||||
.oc .config-item::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
@@ -175,7 +179,7 @@
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.config-editor-title {
|
||||
.oc .config-editor-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
@@ -187,7 +191,7 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.config-label {
|
||||
.oc .config-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
@@ -195,7 +199,7 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.config-label::after {
|
||||
.oc .config-label::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
@@ -206,7 +210,7 @@
|
||||
background-color: var(--border-color);
|
||||
}
|
||||
|
||||
.select-class {
|
||||
.oc .select-class {
|
||||
width: 100% !important;
|
||||
height: var(--control-height);
|
||||
padding: 8px 32px 8px 12px;
|
||||
@@ -222,17 +226,17 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.select-class:hover {
|
||||
.oc .select-class:hover {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.select-class:focus {
|
||||
.oc .select-class:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.help-link {
|
||||
.oc .help-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--primary-color);
|
||||
@@ -243,7 +247,7 @@
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.select-class option {
|
||||
.oc .select-class option {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
@@ -255,12 +259,12 @@
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.help-link:hover {
|
||||
.oc .help-link:hover {
|
||||
opacity: 1;
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.select-popup-body {
|
||||
.oc .select-popup-body {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--border-light);
|
||||
@@ -268,7 +272,7 @@
|
||||
background: var(--bg-gray);
|
||||
}
|
||||
|
||||
.select-option {
|
||||
.oc .select-option {
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
@@ -281,31 +285,31 @@
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.select-option span {
|
||||
.oc .select-option span {
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.select-option:last-child {
|
||||
.oc .select-option:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.select-option:hover {
|
||||
.oc .select-option:hover {
|
||||
background-color: var(--hover-bg);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.select-option[data-value="custom"] {
|
||||
.oc .select-option[data-value="custom"] {
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(99, 102, 241, 0.1));
|
||||
border-color: rgba(59, 130, 246, 0.2);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.select-option[data-value="custom"]:hover {
|
||||
.oc .select-option[data-value="custom"]:hover {
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(99, 102, 241, 0.2));
|
||||
}
|
||||
|
||||
.custom-option-input {
|
||||
.oc .custom-option-input {
|
||||
margin: 12px auto;
|
||||
padding: 12px;
|
||||
width: calc(100% - 24px) !important;
|
||||
@@ -317,18 +321,18 @@
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.custom-option-input:focus {
|
||||
.oc .custom-option-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.custom-option-input::placeholder {
|
||||
.oc .custom-option-input::placeholder {
|
||||
color: var(--text-secondary);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
#addCustomOption {
|
||||
.oc #addCustomOption {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
@@ -338,12 +342,12 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#addCustomOption:hover {
|
||||
.oc #addCustomOption:hover {
|
||||
background: #2563eb;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cdn-status {
|
||||
.oc .cdn-status {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
padding: 4px 8px;
|
||||
@@ -352,45 +356,45 @@
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.cdn-status.fast {
|
||||
.oc .cdn-status.fast {
|
||||
background: rgba(5, 150, 105, 0.1);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.cdn-status.medium {
|
||||
.oc .cdn-status.medium {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.cdn-status.slow {
|
||||
.oc .cdn-status.slow {
|
||||
background: rgba(220, 38, 38, 0.1);
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.cdn-status.error {
|
||||
.oc .cdn-status.error {
|
||||
background: rgba(220, 38, 38, 0.1);
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
.config-grid {
|
||||
.oc .config-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.config-item {
|
||||
.oc .config-item {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.select-class {
|
||||
.oc .select-class {
|
||||
max-width: 100%;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.select-popup {
|
||||
.oc .select-popup {
|
||||
min-width: 80%;
|
||||
width: 80%;
|
||||
padding: 15px;
|
||||
@@ -398,76 +402,76 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.config-grid {
|
||||
.oc .config-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.config-item {
|
||||
.oc .config-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.select-popup-header {
|
||||
.oc .select-popup-header {
|
||||
font-size: 16px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.select-config-section {
|
||||
.oc .select-config-section {
|
||||
padding: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.select-popup-body {
|
||||
.oc .select-popup-body {
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.select-option {
|
||||
.oc .select-option {
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.custom-option-input {
|
||||
.oc .custom-option-input {
|
||||
font-size: 13px;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 575px) {
|
||||
.select-popup {
|
||||
.oc .select-popup {
|
||||
min-width: 80%;
|
||||
width: 80%;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.select-popup-header {
|
||||
.oc .select-popup-header {
|
||||
font-size: 15px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.select-config-section {
|
||||
.oc .select-config-section {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.config-label {
|
||||
.oc .config-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.select-class {
|
||||
.oc .select-class {
|
||||
font-size: 12px;
|
||||
padding: 6px 28px 6px 10px;
|
||||
}
|
||||
|
||||
.config-item::after {
|
||||
.oc .config-item::after {
|
||||
right: 10px;
|
||||
border-left: 3px solid transparent;
|
||||
border-right: 3px solid transparent;
|
||||
border-top: 3px solid var(--text-secondary);
|
||||
}
|
||||
|
||||
.select-option {
|
||||
.oc .select-option {
|
||||
padding: 8px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -546,97 +550,99 @@
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<div id="selectPopup" class="oc select-popup hidden">
|
||||
<div class="select-popup-header">
|
||||
<div class="config-editor-title">
|
||||
<%:Check Update%>
|
||||
</div>
|
||||
<div class="config-editor-actions">
|
||||
<button type="button" class="icon-btn close-btn" onclick="closeSelectPopup()" title="<%:Close%>">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="select-config-section">
|
||||
<div class="config-grid">
|
||||
<div class="config-item">
|
||||
<div class="config-label"><%:Compiled Version%></div>
|
||||
<select class="select-class" id="CORE_VERSION_CDN">
|
||||
<option value="linux-386"><%:linux-386%></option>
|
||||
<option value="linux-amd64-v1"><%:linux-amd64-v1(x86-64)%></option>
|
||||
<option value="linux-amd64-v2"><%:linux-amd64-v2(x86-64)%></option>
|
||||
<option value="linux-amd64-v3"><%:linux-amd64-v3(x86-64)%></option>
|
||||
<option value="linux-armv5"><%:linux-armv5%></option>
|
||||
<option value="linux-armv6"><%:linux-armv6%></option>
|
||||
<option value="linux-armv7"><%:linux-armv7%></option>
|
||||
<option value="linux-arm64"><%:linux-arm64(armv8)%></option>
|
||||
<option value="linux-loong64-abi1"><%:linux-loong64-abi1%></option>
|
||||
<option value="linux-loong64-abi2"><%:linux-loong64-abi2%></option>
|
||||
<option value="linux-riscv64"><%:linux-riscv64%></option>
|
||||
<option value="linux-s390x"><%:linux-s390x%></option>
|
||||
<option value="linux-mips-hardfloat"><%:linux-mips-hardfloat%></option>
|
||||
<option value="linux-mips-softfloat"><%:linux-mips-softfloat%></option>
|
||||
<option value="linux-mips64"><%:linux-mips64%></option>
|
||||
<option value="linux-mips64le"><%:linux-mips64le%></option>
|
||||
<option value="linux-mipsle-softfloat"><%:linux-mipsle-softfloat%></option>
|
||||
<option value="linux-mipsle-hardfloat"><%:linux-mipsle-hardfloat%></option>
|
||||
<option value="0"><%:Not Set%></option>
|
||||
</select>
|
||||
<div class="oc">
|
||||
<div id="selectPopup" class="select-popup hidden">
|
||||
<div class="select-popup-header">
|
||||
<div class="config-editor-title">
|
||||
<%:Check Update%>
|
||||
</div>
|
||||
|
||||
<div class="config-item">
|
||||
<div class="config-label"><%:Release Branch%></div>
|
||||
<select class="select-class" id="RELEASE_BRANCH_CDN">
|
||||
<option value="master">Master</option>
|
||||
<option value="dev">Developer</option>
|
||||
</select>
|
||||
<div class="config-editor-actions">
|
||||
<button type="button" class="icon-btn close-btn" onclick="closeSelectPopup()" title="<%:Close%>">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="config-item">
|
||||
<div class="config-label">
|
||||
<%:Smart Core%>
|
||||
<a href="javascript:void(0);" onclick="window.open('https://github.com/vernesong/mihomo/releases', '_blank');" class="help-link" title="<%:View core infos that support smart group%>">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
|
||||
<line x1="12" y1="17" x2="12.01" y2="17"></line>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="select-config-section">
|
||||
<div class="config-grid">
|
||||
<div class="config-item">
|
||||
<div class="config-label"><%:Compiled Version%></div>
|
||||
<select class="select-class" id="CORE_VERSION_CDN">
|
||||
<option value="linux-386"><%:linux-386%></option>
|
||||
<option value="linux-amd64-v1"><%:linux-amd64-v1(x86-64)%></option>
|
||||
<option value="linux-amd64-v2"><%:linux-amd64-v2(x86-64)%></option>
|
||||
<option value="linux-amd64-v3"><%:linux-amd64-v3(x86-64)%></option>
|
||||
<option value="linux-armv5"><%:linux-armv5%></option>
|
||||
<option value="linux-armv6"><%:linux-armv6%></option>
|
||||
<option value="linux-armv7"><%:linux-armv7%></option>
|
||||
<option value="linux-arm64"><%:linux-arm64(armv8)%></option>
|
||||
<option value="linux-loong64-abi1"><%:linux-loong64-abi1%></option>
|
||||
<option value="linux-loong64-abi2"><%:linux-loong64-abi2%></option>
|
||||
<option value="linux-riscv64"><%:linux-riscv64%></option>
|
||||
<option value="linux-s390x"><%:linux-s390x%></option>
|
||||
<option value="linux-mips-hardfloat"><%:linux-mips-hardfloat%></option>
|
||||
<option value="linux-mips-softfloat"><%:linux-mips-softfloat%></option>
|
||||
<option value="linux-mips64"><%:linux-mips64%></option>
|
||||
<option value="linux-mips64le"><%:linux-mips64le%></option>
|
||||
<option value="linux-mipsle-softfloat"><%:linux-mipsle-softfloat%></option>
|
||||
<option value="linux-mipsle-hardfloat"><%:linux-mipsle-hardfloat%></option>
|
||||
<option value="0"><%:Not Set%></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="config-item">
|
||||
<div class="config-label"><%:Release Branch%></div>
|
||||
<select class="select-class" id="RELEASE_BRANCH_CDN">
|
||||
<option value="master">Master</option>
|
||||
<option value="dev">Developer</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="config-item">
|
||||
<div class="config-label">
|
||||
<%:Smart Core%>
|
||||
<a href="javascript:void(0);" onclick="window.open('https://github.com/vernesong/mihomo/releases', '_blank');" class="help-link" title="<%:View core infos that support smart group%>">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
|
||||
<line x1="12" y1="17" x2="12.01" y2="17"></line>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<select class="select-class" id="SMART_ENABLE_CDN">
|
||||
<option value="0"><%:Disabled%></option>
|
||||
<option value="1"><%:Enable%></option>
|
||||
</select>
|
||||
</div>
|
||||
<select class="select-class" id="SMART_ENABLE_CDN">
|
||||
<option value="0"><%:Disabled%></option>
|
||||
<option value="1"><%:Enable%></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="select-popup-body">
|
||||
<div class="select-option" data-value="https://raw.githubusercontent.com/">
|
||||
<span>https://raw.githubusercontent.com/ (<%:RAW address%>)</span>
|
||||
<div class="select-popup-body">
|
||||
<div class="select-option" data-value="https://raw.githubusercontent.com/">
|
||||
<span>https://raw.githubusercontent.com/ (<%:RAW address%>)</span>
|
||||
</div>
|
||||
<div class="select-option" data-value="https://fastly.jsdelivr.net/">
|
||||
<span>https://fastly.jsdelivr.net/</span>
|
||||
</div>
|
||||
<div class="select-option" data-value="https://testingcf.jsdelivr.net/">
|
||||
<span>https://testingcf.jsdelivr.net/</span>
|
||||
</div>
|
||||
<div class="select-option" data-value="https://cdn.jsdelivr.net/">
|
||||
<span>https://cdn.jsdelivr.net/</span>
|
||||
</div>
|
||||
<div class="select-option" data-value="custom">
|
||||
<span><%:Custom Your CDN URL%></span>
|
||||
<span style="display: flex; align-items: center; height: 100%;">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="none" style="display: block;">
|
||||
<polygon points="6,9 18,9 12,15"/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<input type="text" id="customOptionInput" class="custom-option-input" value="https://ghfast.top/" placeholder="<%:Type CDN URL, Format Like%> https://ghfast.top/" style="display: none;">
|
||||
<div class="select-option" id="addCustomOption" style="display: none;"><%:Add%></div>
|
||||
</div>
|
||||
<div class="select-option" data-value="https://fastly.jsdelivr.net/">
|
||||
<span>https://fastly.jsdelivr.net/</span>
|
||||
</div>
|
||||
<div class="select-option" data-value="https://testingcf.jsdelivr.net/">
|
||||
<span>https://testingcf.jsdelivr.net/</span>
|
||||
</div>
|
||||
<div class="select-option" data-value="https://cdn.jsdelivr.net/">
|
||||
<span>https://cdn.jsdelivr.net/</span>
|
||||
</div>
|
||||
<div class="select-option" data-value="custom">
|
||||
<span><%:Custom Your CDN URL%></span>
|
||||
<span style="display: flex; align-items: center; height: 100%;">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="none" style="display: block;">
|
||||
<polygon points="6,9 18,9 12,15"/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<input type="text" id="customOptionInput" class="custom-option-input" value="https://ghfast.top/" placeholder="<%:Type CDN URL, Format Like%> https://ghfast.top/" style="display: none;">
|
||||
<div class="select-option" id="addCustomOption" style="display: none;"><%:Add%></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@
|
||||
--text-primary: #374151;
|
||||
--text-secondary: #64748b;
|
||||
--text-title: #4d4d4d;
|
||||
--select-hover: #ebebeb;
|
||||
--border-color: #b1b1b1;
|
||||
--border-light: #e2e8f0;
|
||||
--hover-bg: #f8fafc;
|
||||
@@ -44,6 +45,7 @@
|
||||
--text-primary: #ebebeb;
|
||||
--text-secondary: #d0cfcf;
|
||||
--text-title: #e5e7eb;
|
||||
--select-hover: #ebebeb;
|
||||
--border-color: #939393;
|
||||
--border-light: #6b7280;
|
||||
--hover-bg: #374151;
|
||||
@@ -911,7 +913,7 @@
|
||||
.oc .subscription-info-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 5px;
|
||||
flex: 1;
|
||||
min-height: 80px;
|
||||
padding-left: 20px;
|
||||
@@ -962,20 +964,115 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.oc .subscription-actions-row {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.oc .subscription-overwrite-tags {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
margin-right: auto;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: none;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.oc .subscription-overwrite-tags::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.oc .subscription-overwrite-tags.drag-scroll-enabled {
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.oc .subscription-overwrite-tags.dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.oc .subscription-overwrite-tag {
|
||||
height: 28px;
|
||||
width: fit-content;
|
||||
padding: 0 8px;
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-white);
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
line-height: 1;
|
||||
box-sizing: border-box;
|
||||
transition: all var(--transition-fast);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.oc .subscription-overwrite-tag.type-http {
|
||||
color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.oc .subscription-overwrite-tag.type-file {
|
||||
color: var(--success-color);
|
||||
border-color: var(--success-color);
|
||||
}
|
||||
|
||||
.oc .subscription-overwrite-tag.type-unknown {
|
||||
color: var(--warning-color);
|
||||
border-color: var(--warning-color);
|
||||
}
|
||||
|
||||
.oc .subscription-action-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.oc .subscription-info-text {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.4;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
cursor: default;
|
||||
user-select: text;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.oc .subscription-info-text::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.oc .subscription-info-text.is-draggable {
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.oc .subscription-info-text.dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* Multiple Providers Grid Layout */
|
||||
.oc .subscription-providers-grid {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
padding: 8px 0;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
@@ -1528,6 +1625,13 @@
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
.oc .subscription-overwrite-tag {
|
||||
height: 24px;
|
||||
width: fit-content;
|
||||
font-size: 10px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.oc .icon-btn svg {
|
||||
width: 12px !important;
|
||||
height: 12px !important;
|
||||
@@ -1906,21 +2010,24 @@
|
||||
</div>
|
||||
<div id="subscription-info-text" class="subscription-info-text"><%:Collecting data...%></div>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<button type="button" class="icon-btn action-btn" id="refresh-subscription" title="<%:Refresh%>" onclick="return refreshSubscriptionInfo()">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M23 4v6h-6"></path>
|
||||
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="icon-btn action-btn" id="config-subscription-url" title="<%:Specify URL%>" onclick="return setSubscriptionUrl()">
|
||||
<svg width="14" height="14" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="4">
|
||||
<path d="M4 6H44V36H29L24 41L19 36H4V6Z"/>
|
||||
<path d="M23 21H25.0025" />
|
||||
<path d="M33.001 21H34.9999"/>
|
||||
<path d="M13.001 21H14.9999"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="card-actions subscription-actions-row">
|
||||
<div id="subscription-overwrite-tags" class="subscription-overwrite-tags" style="display: none;"></div>
|
||||
<div class="subscription-action-buttons">
|
||||
<button type="button" class="icon-btn action-btn" id="refresh-subscription" title="<%:Refresh%>" onclick="return refreshSubscriptionInfo()">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M23 4v6h-6"></path>
|
||||
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="icon-btn action-btn" id="config-subscription-url" title="<%:Specify URL%>" onclick="return setSubscriptionUrl()">
|
||||
<svg width="14" height="14" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="4">
|
||||
<path d="M4 6H44V36H29L24 41L19 36H4V6Z"/>
|
||||
<path d="M23 21H25.0025" />
|
||||
<path d="M33.001 21H34.9999"/>
|
||||
<path d="M13.001 21H14.9999"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2528,6 +2635,7 @@
|
||||
|
||||
this.rawCurrentConfig = currentConfigFile;
|
||||
this.configList = configFiles;
|
||||
window.configFiles = configFiles;
|
||||
this.currentConfig = currentConfigFile;
|
||||
this.currentConfigIndex = -1;
|
||||
|
||||
@@ -2829,12 +2937,14 @@
|
||||
}
|
||||
SubscriptionManager.getSubscriptionInfo();
|
||||
}
|
||||
OverwriteSubscribeManager.render(OverwriteSubscribeManager.data);
|
||||
} else {
|
||||
this.currentConfig = '';
|
||||
this.currentConfigIndex = -1;
|
||||
SubscriptionManager.currentConfigFile = '';
|
||||
this.hideSubscriptionDisplay();
|
||||
this.updateNavigationArrows();
|
||||
OverwriteSubscribeManager.render(OverwriteSubscribeManager.data);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2913,6 +3023,8 @@
|
||||
SubscriptionManager.getSubscriptionInfo();
|
||||
}
|
||||
|
||||
OverwriteSubscribeManager.render(OverwriteSubscribeManager.data);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
@@ -2936,14 +3048,112 @@
|
||||
updateTimer: null,
|
||||
isInitialized: false,
|
||||
|
||||
enableOverflowDrag: function(element) {
|
||||
if (!element) return;
|
||||
|
||||
function updateDragState() {
|
||||
var isOverflowing = element.scrollWidth > element.clientWidth + 1;
|
||||
if (isOverflowing) {
|
||||
element.classList.add('is-draggable');
|
||||
} else {
|
||||
element.classList.remove('is-draggable');
|
||||
element.classList.remove('dragging');
|
||||
element.scrollLeft = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!element._dragBound) {
|
||||
var isDragging = false;
|
||||
var startX = 0;
|
||||
var startScrollLeft = 0;
|
||||
var touchIdentifier = null;
|
||||
|
||||
function endDrag() {
|
||||
if (!isDragging) return;
|
||||
isDragging = false;
|
||||
touchIdentifier = null;
|
||||
element.classList.remove('dragging');
|
||||
}
|
||||
|
||||
element.addEventListener('mousedown', function(e) {
|
||||
if (!element.classList.contains('is-draggable')) return;
|
||||
isDragging = true;
|
||||
startX = e.pageX - element.getBoundingClientRect().left;
|
||||
startScrollLeft = element.scrollLeft;
|
||||
element.classList.add('dragging');
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
element.addEventListener('mousemove', function(e) {
|
||||
if (!isDragging) return;
|
||||
var x = e.pageX - element.getBoundingClientRect().left;
|
||||
var walk = (x - startX) * 1.2;
|
||||
element.scrollLeft = startScrollLeft - walk;
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
element.addEventListener('mouseleave', function() {
|
||||
endDrag();
|
||||
});
|
||||
|
||||
element.addEventListener('mouseup', function() {
|
||||
endDrag();
|
||||
});
|
||||
|
||||
element.addEventListener('touchstart', function(e) {
|
||||
if (!element.classList.contains('is-draggable')) return;
|
||||
if (!e.touches || e.touches.length !== 1) return;
|
||||
|
||||
var touch = e.touches[0];
|
||||
isDragging = true;
|
||||
touchIdentifier = touch.identifier;
|
||||
startX = touch.pageX - element.getBoundingClientRect().left;
|
||||
startScrollLeft = element.scrollLeft;
|
||||
element.classList.add('dragging');
|
||||
}, { passive: true });
|
||||
|
||||
element.addEventListener('touchmove', function(e) {
|
||||
if (!isDragging || !e.touches || e.touches.length === 0) return;
|
||||
|
||||
var touch = null;
|
||||
for (var i = 0; i < e.touches.length; i++) {
|
||||
if (e.touches[i].identifier === touchIdentifier) {
|
||||
touch = e.touches[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!touch) return;
|
||||
|
||||
var x = touch.pageX - element.getBoundingClientRect().left;
|
||||
var walk = (x - startX) * 1.2;
|
||||
element.scrollLeft = startScrollLeft - walk;
|
||||
e.preventDefault();
|
||||
}, { passive: false });
|
||||
|
||||
element.addEventListener('touchend', function() {
|
||||
endDrag();
|
||||
});
|
||||
|
||||
element.addEventListener('touchcancel', function() {
|
||||
endDrag();
|
||||
});
|
||||
|
||||
element.addEventListener('dragstart', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
element._dragBound = true;
|
||||
}
|
||||
|
||||
updateDragState();
|
||||
},
|
||||
|
||||
init: function() {
|
||||
if (this.isInitialized) return;
|
||||
this.isInitialized = true;
|
||||
|
||||
setTimeout(function() {
|
||||
SubscriptionManager.loadSubscriptionInfo();
|
||||
SubscriptionManager.startAutoUpdate();
|
||||
}, 500);
|
||||
SubscriptionManager.loadSubscriptionInfo();
|
||||
SubscriptionManager.startAutoUpdate();
|
||||
},
|
||||
|
||||
loadSubscriptionInfo: function() {
|
||||
@@ -3023,17 +3233,18 @@
|
||||
localStorage.setItem('sub_info_' + filename, JSON.stringify(status));
|
||||
if (status.providers && status.providers.length === 0) {
|
||||
SubscriptionManager.showNoInfo();
|
||||
return;
|
||||
}
|
||||
SubscriptionManager.displaySubscriptionInfo(status);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
needsErrorHandling = true;
|
||||
if (!cachedData) {
|
||||
SubscriptionManager.showNoInfo();
|
||||
}
|
||||
}
|
||||
|
||||
if (needsErrorHandling) {
|
||||
SubscriptionManager.handleError(cachedData);
|
||||
SubscriptionManager.handleError(status);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
@@ -3112,6 +3323,7 @@
|
||||
|
||||
infoText.textContent = infoString;
|
||||
infoText.title = tooltipString;
|
||||
this.enableOverflowDrag(infoText);
|
||||
},
|
||||
|
||||
displayMultipleProviders: function(providers, progressSection) {
|
||||
@@ -3169,6 +3381,7 @@
|
||||
|
||||
infoText.textContent = infoString;
|
||||
infoText.title = tooltipString;
|
||||
SubscriptionManager.enableOverflowDrag(infoText);
|
||||
|
||||
card.appendChild(infoText);
|
||||
progressSection.appendChild(card);
|
||||
@@ -3298,21 +3511,23 @@
|
||||
}
|
||||
},
|
||||
|
||||
handleError: function(cachedData) {
|
||||
if (!cachedData) {
|
||||
this.showNoInfo();
|
||||
}
|
||||
|
||||
handleError: function(status) {
|
||||
if (this.retryCount >= this.maxRetries) {
|
||||
this.retryCount = 0;
|
||||
if (this.currentConfigFile && cachedData) {
|
||||
localStorage.removeItem('sub_info_' + this.extractFilename(this.currentConfigFile));
|
||||
if (this.currentConfigFile && status && status.providers) {
|
||||
localStorage.setItem('sub_info_' + filename, JSON.stringify(status));
|
||||
}
|
||||
if (!status.providers || status.providers.length === 0) {
|
||||
this.showNoInfo();
|
||||
}
|
||||
if (status.providers && status.providers.length > 0) {
|
||||
SubscriptionManager.displaySubscriptionInfo(status);
|
||||
}
|
||||
} else {
|
||||
this.retryCount++;
|
||||
setTimeout(function() {
|
||||
SubscriptionManager.getSubscriptionInfo();
|
||||
}, 15000);
|
||||
}, 5000);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3478,6 +3693,251 @@
|
||||
}
|
||||
};
|
||||
|
||||
var OverwriteSubscribeManager = {
|
||||
container: null,
|
||||
data: null,
|
||||
isLoading: false,
|
||||
_dragBound: false,
|
||||
_isDragging: false,
|
||||
_dragStartX: 0,
|
||||
_dragStartScrollLeft: 0,
|
||||
_touchIdentifier: null,
|
||||
|
||||
init: function() {
|
||||
this.container = document.getElementById('subscription-overwrite-tags');
|
||||
if (!this.container) return;
|
||||
this.bindDragScroll();
|
||||
this.load(false);
|
||||
},
|
||||
|
||||
hasOverflow: function() {
|
||||
if (!this.container) return false;
|
||||
return this.container.scrollWidth > this.container.clientWidth + 1;
|
||||
},
|
||||
|
||||
updateDragScrollState: function() {
|
||||
if (!this.container) return;
|
||||
if (this.hasOverflow()) {
|
||||
this.container.classList.add('drag-scroll-enabled');
|
||||
} else {
|
||||
this.container.classList.remove('drag-scroll-enabled');
|
||||
this.container.classList.remove('dragging');
|
||||
}
|
||||
},
|
||||
|
||||
bindDragScroll: function() {
|
||||
if (!this.container || this._dragBound) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
function endDrag() {
|
||||
if (!self._isDragging) return;
|
||||
self._isDragging = false;
|
||||
self._touchIdentifier = null;
|
||||
self.container.classList.remove('dragging');
|
||||
}
|
||||
|
||||
this.container.addEventListener('mousedown', function(e) {
|
||||
if (!self.hasOverflow()) {
|
||||
return;
|
||||
}
|
||||
self._isDragging = true;
|
||||
self._dragStartX = e.pageX - self.container.offsetLeft;
|
||||
self._dragStartScrollLeft = self.container.scrollLeft;
|
||||
self.container.classList.add('dragging');
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
this.container.addEventListener('mousemove', function(e) {
|
||||
if (!self._isDragging) {
|
||||
return;
|
||||
}
|
||||
var x = e.pageX - self.container.offsetLeft;
|
||||
var walk = (x - self._dragStartX) * 1.5;
|
||||
self.container.scrollLeft = self._dragStartScrollLeft - walk;
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
this.container.addEventListener('mouseleave', function() {
|
||||
endDrag();
|
||||
});
|
||||
|
||||
this.container.addEventListener('mouseup', function() {
|
||||
endDrag();
|
||||
});
|
||||
|
||||
this.container.addEventListener('touchstart', function(e) {
|
||||
if (!self.hasOverflow() || !e.touches || e.touches.length !== 1) {
|
||||
return;
|
||||
}
|
||||
var touch = e.touches[0];
|
||||
self._isDragging = true;
|
||||
self._touchIdentifier = touch.identifier;
|
||||
self._dragStartX = touch.pageX - self.container.offsetLeft;
|
||||
self._dragStartScrollLeft = self.container.scrollLeft;
|
||||
self.container.classList.add('dragging');
|
||||
}, { passive: true });
|
||||
|
||||
this.container.addEventListener('touchmove', function(e) {
|
||||
if (!self._isDragging || !e.touches || e.touches.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var touch = null;
|
||||
for (var i = 0; i < e.touches.length; i++) {
|
||||
if (e.touches[i].identifier === self._touchIdentifier) {
|
||||
touch = e.touches[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!touch) {
|
||||
return;
|
||||
}
|
||||
|
||||
var x = touch.pageX - self.container.offsetLeft;
|
||||
var walk = (x - self._dragStartX) * 1.5;
|
||||
self.container.scrollLeft = self._dragStartScrollLeft - walk;
|
||||
e.preventDefault();
|
||||
}, { passive: false });
|
||||
|
||||
this.container.addEventListener('touchend', function() {
|
||||
endDrag();
|
||||
});
|
||||
|
||||
this.container.addEventListener('touchcancel', function() {
|
||||
endDrag();
|
||||
});
|
||||
|
||||
this.container.addEventListener('dragstart', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
this._dragBound = true;
|
||||
},
|
||||
|
||||
load: function(force) {
|
||||
if (!this.container || this.isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
var self = this;
|
||||
|
||||
StateManager.cachedXHRGet('<%=luci.dispatcher.build_url("admin", "services", "openclash", "overwrite_subscribe_info")%>', function(x, status) {
|
||||
self.isLoading = false;
|
||||
if (x && x.status == 200 && status && status.status === 'success' && status.data) {
|
||||
self.data = status.data;
|
||||
self.render(status.data);
|
||||
} else {
|
||||
self.render(null);
|
||||
}
|
||||
}, !!force);
|
||||
},
|
||||
|
||||
getTypeClass: function(type) {
|
||||
var normalizedType = String(type || '').toLowerCase();
|
||||
if (normalizedType === 'http') return 'type-http';
|
||||
if (normalizedType === 'file') return 'type-file';
|
||||
return 'type-unknown';
|
||||
},
|
||||
|
||||
parseModuleConfigList: function(configValue) {
|
||||
if (!Array.isArray(configValue)) {
|
||||
return [];
|
||||
}
|
||||
return configValue.map(function(item) {
|
||||
return String(item || '').trim();
|
||||
}).filter(function(item) {
|
||||
return !!item;
|
||||
});
|
||||
},
|
||||
|
||||
isModuleForCurrentConfig: function(moduleInfo, currentConfigPath) {
|
||||
var configList = this.parseModuleConfigList(moduleInfo ? moduleInfo.config : null);
|
||||
if (!configList.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (configList.indexOf('all') !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var fullPath = String(currentConfigPath || '').trim();
|
||||
if (!fullPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return configList.indexOf(fullPath) !== -1;
|
||||
},
|
||||
|
||||
getEnabledModules: function(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return [];
|
||||
}
|
||||
|
||||
var currentConfigPath = ConfigFileManager.getCurrentConfig() || ConfigFileManager.getSelectedConfig();
|
||||
|
||||
var modules = [];
|
||||
for (var moduleName in data) {
|
||||
if (!Object.prototype.hasOwnProperty.call(data, moduleName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var moduleInfo = data[moduleName] || {};
|
||||
if (parseInt(moduleInfo.enable, 10) === 1 && this.isModuleForCurrentConfig(moduleInfo, currentConfigPath)) {
|
||||
modules.push({
|
||||
name: moduleName,
|
||||
type: moduleInfo.type || 'unknown',
|
||||
order: parseInt(moduleInfo.order, 10) || 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
modules.sort(function(a, b) {
|
||||
if (a.order === b.order) {
|
||||
return String(a.name).localeCompare(String(b.name));
|
||||
}
|
||||
return a.order - b.order;
|
||||
});
|
||||
|
||||
return modules;
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
if (!this.container) return;
|
||||
|
||||
var enabledModules = this.getEnabledModules(data);
|
||||
this.container.innerHTML = '';
|
||||
|
||||
if (enabledModules.length === 0) {
|
||||
this.container.style.display = 'none';
|
||||
this.updateDragScrollState();
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < enabledModules.length; i++) {
|
||||
var moduleItem = enabledModules[i];
|
||||
var tag = document.createElement('span');
|
||||
tag.className = 'subscription-overwrite-tag ' + this.getTypeClass(moduleItem.type);
|
||||
tag.title = moduleItem.name;
|
||||
tag.textContent = moduleItem.name;
|
||||
if (moduleItem.type == 'http') {
|
||||
tag.title += ' [<%:HTTP Module%>]';
|
||||
} else if (moduleItem.type == 'file') {
|
||||
tag.title += ' [<%:File Module%>]';
|
||||
} else {
|
||||
tag.title += ' [<%:Unknown%>]';
|
||||
}
|
||||
this.container.appendChild(tag);
|
||||
}
|
||||
|
||||
this.container.style.display = 'flex';
|
||||
this.updateDragScrollState();
|
||||
}
|
||||
};
|
||||
|
||||
var LogManager = {
|
||||
isPolling: false,
|
||||
pollTimer: null,
|
||||
@@ -4029,7 +4489,12 @@
|
||||
DarkModeDetector.init();
|
||||
ConfigFileManager.init();
|
||||
|
||||
window.addEventListener('oc-overwrite-updated', function() {
|
||||
OverwriteSubscribeManager.load(true);
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
OverwriteSubscribeManager.init();
|
||||
SubscriptionManager.init();
|
||||
}, 300);
|
||||
|
||||
@@ -4745,7 +5210,7 @@
|
||||
}
|
||||
|
||||
function telegrampage() {
|
||||
url6 = 'https://t.me/ctcgfw_openwrt_discuss';
|
||||
url6 = 'https://t.me/openclash_group';
|
||||
winOpen(url6);
|
||||
}
|
||||
|
||||
@@ -5326,6 +5791,7 @@
|
||||
var filename = SubscriptionManager.extractFilename(currentConfig);
|
||||
localStorage.removeItem('sub_info_' + filename);
|
||||
SubscriptionManager.getSubscriptionInfo();
|
||||
OverwriteSubscribeManager.load(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5506,4 +5972,4 @@
|
||||
DarkModeDetector.init();
|
||||
}
|
||||
//]]></script>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -789,9 +789,9 @@ function sub_info_refresh_<%=idname%>(force) {
|
||||
|
||||
var save_info = (localStorage.getItem("sub_info_<%=filename%>")) ? JSON.parse(localStorage.getItem("sub_info_<%=filename%>")) : null;
|
||||
var shouldFetchNew = true;
|
||||
if (save_info && !force) {
|
||||
if (!force) {
|
||||
dispaly_progressbar('<%=idname%>', save_info);
|
||||
if (save_info.get_time) {
|
||||
if (save_info && save_info.get_time) {
|
||||
var currentTime = Math.floor(Date.now() / 1000);
|
||||
var cacheTime = parseInt(save_info.get_time);
|
||||
var timeDiff = currentTime - cacheTime;
|
||||
@@ -803,12 +803,6 @@ function sub_info_refresh_<%=idname%>(force) {
|
||||
}
|
||||
}
|
||||
|
||||
if (retry_<%=idname%> >= 3) {
|
||||
localStorage.removeItem("sub_info_<%=filename%>");
|
||||
retry_<%=idname%> = 0;
|
||||
shouldFetchNew = false;
|
||||
}
|
||||
|
||||
if (!shouldFetchNew) {
|
||||
s_<%=idname%> = setTimeout("sub_info_refresh_<%=idname%>(false)", 60000*15);
|
||||
return;
|
||||
@@ -839,8 +833,13 @@ function sub_info_refresh_<%=idname%>(force) {
|
||||
|
||||
if (needsErrorHandling && retry_<%=idname%> <= 2) {
|
||||
retry_<%=idname%>++;
|
||||
s_<%=idname%> = setTimeout("sub_info_refresh_<%=idname%>(false)", 15000);
|
||||
s_<%=idname%> = setTimeout("sub_info_refresh_<%=idname%>(false)", 5000);
|
||||
} else {
|
||||
retry_<%=idname%> = 0;
|
||||
dispaly_progressbar('<%=idname%>', status);
|
||||
if (status && status.providers) {
|
||||
localStorage.setItem("sub_info_<%=filename%>", JSON.stringify(status));
|
||||
}
|
||||
s_<%=idname%> = setTimeout("sub_info_refresh_<%=idname%>(false)", 60000*15);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<div class="cbi-value-field" id="switch_dashboard_<%=self.option%>">
|
||||
<%:Collecting data...%>
|
||||
</div>
|
||||
<div class="cbi-value-field" id="delete_dashboard_<%=self.option%>">
|
||||
<div class="cbi-value-field" id="delete_dashboard_<%=self.option%>" style="padding-left: 5px;">
|
||||
<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Delete%>" onclick="return delete_dashboard(this, '<%=self.option%>')"/>
|
||||
</div>
|
||||
<div class="cbi-value-field" id="default_dashboard_<%=self.option%>">
|
||||
<div class="cbi-value-field" id="default_dashboard_<%=self.option%>" style="padding-left: 5px;">
|
||||
<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Set to Default%>" onclick="return default_dashboard(this, '<%=self.option%>')"/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -51,11 +51,9 @@ else
|
||||
end
|
||||
end
|
||||
|
||||
local cfg_name
|
||||
if os.getenv("PATH_INFO") then
|
||||
local cfg_path = string.match(os.getenv("PATH_INFO"), "/config/(.+)$")
|
||||
cfg_name = cfg_path and string.match(cfg_path, "([^/]+)$") or nil
|
||||
end
|
||||
local file_path = fs.get_file_path_from_request()
|
||||
local cfg_name = (file_path and fs.basename(file_path)) or nil
|
||||
|
||||
if not cfg_name and uci:get("openclash", "config", "config_path") then
|
||||
cfg_name = fs.basename(uci:get("openclash", "config", "config_path"))
|
||||
end
|
||||
@@ -392,7 +390,13 @@ local sectiontype = "_"..self.config.."_"..string.match(self.sectiontype, "[%w_]
|
||||
<%- end
|
||||
%> alt="<%:Edit%>" title="<%:Edit%>" />
|
||||
<%- end; if self.addremove then -%>
|
||||
<input class="btn cbi-button cbi-button-remove" type="submit" value="<%:Delete%>" onclick="this.form.cbi_state='del-section'; return switch_to_tab<%=sectiontype%>()" name="cbi.rts.<%=self.config%>.<%=k%>" alt="<%:Delete%>" title="<%:Delete%>" />
|
||||
<%- if file_path then -%>
|
||||
<input class="btn cbi-button cbi-button-remove" type="submit" value="<%:Delete%>"
|
||||
onclick="this.form.cbi_state='del-section'; var redirectUrl = 'servers?file=<%=file_path%>'; var originalSubmit = this.form.onsubmit; this.form.onsubmit = function(e) { var result = true; if (originalSubmit) { result = originalSubmit.call(this, e); } if (result !== false) { setTimeout(function() { window.location.href = redirectUrl; }, 200); } return result; }; return switch_to_tab<%=sectiontype%>()"
|
||||
name="cbi.rts.<%=self.config%>.<%=k%>" alt="<%:Delete%>" title="<%:Delete%>" />
|
||||
<%- else -%>
|
||||
<input class="btn cbi-button cbi-button-remove" type="submit" value="<%:Delete%>" onclick="this.form.cbi_state='del-section'; return switch_to_tab<%=sectiontype%>()" name="cbi.rts.<%=self.config%>.<%=k%>" alt="<%:Delete%>" title="<%:Delete%>" />
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
</td>
|
||||
<%- end -%>
|
||||
@@ -503,7 +507,6 @@ local sectiontype = "_"..self.config.."_"..string.match(self.sectiontype, "[%w_]
|
||||
}
|
||||
|
||||
localStorage.setItem("id<%=sectiontype%>",'cbi-<%=self.config%>-<%=self.sectiontype%>');
|
||||
return true;
|
||||
};
|
||||
|
||||
function onload<%=sectiontype%>() {
|
||||
|
||||
@@ -2666,8 +2666,8 @@ msgstr "Inicio de TUN fallido; intentando reiniciar..."
|
||||
msgid "Core Start Failed, Please Check The Log Infos!"
|
||||
msgstr "Fallo al iniciar el núcleo. Revisa el registro de núcleo."
|
||||
|
||||
msgid "Core Status Abnormal, Please Check The Log Infos!"
|
||||
msgstr "Estado del núcleo anómalo. Consulta el registro de núcleo."
|
||||
msgid "Core Initial Configuration Timeout, Please Check The Log Infos!"
|
||||
msgstr "Tiempo de espera agotado en la configuración inicial del núcleo, ¡consulta la información del registro!"
|
||||
|
||||
msgid "Forced Sniff Pure IP Connections"
|
||||
msgstr "Forzar sniffing de conexiones IP puras"
|
||||
@@ -3773,6 +3773,12 @@ msgstr "Editar información de módulo"
|
||||
msgid "Subscription"
|
||||
msgstr "Suscripción"
|
||||
|
||||
msgid "HTTP Module"
|
||||
msgstr "Módulo HTTP"
|
||||
|
||||
msgid "File Module"
|
||||
msgstr "Módulo de archivo"
|
||||
|
||||
msgid "Overwrite Module"
|
||||
msgstr "Módulo de sobrescritura"
|
||||
|
||||
@@ -3834,4 +3840,19 @@ msgid "Serial Number"
|
||||
msgstr "Número de serie"
|
||||
|
||||
msgid "Set Custom Overwrite Script Failed,"
|
||||
msgstr "Fallo al ejecutar el script de sobrescritura personalizado,"
|
||||
msgstr "Fallo al ejecutar el script de sobrescritura personalizado,"
|
||||
|
||||
msgid "Load YAML Override Block"
|
||||
msgstr "Cargar bloque de sobrescritura YAML"
|
||||
|
||||
msgid "Invalid YAML Override format, skipped..."
|
||||
msgstr "Formato de sobrescritura YAML no válido, omitido..."
|
||||
|
||||
msgid "Parse YAML Override failed:"
|
||||
msgstr "Error al analizar bloque de sobrescritura YAML:"
|
||||
|
||||
msgid "YAML overwrite failed:"
|
||||
msgstr "Error en la sobrescritura YAML:"
|
||||
|
||||
msgid "files selected"
|
||||
msgstr "archivos seleccionados"
|
||||
|
||||
@@ -2664,8 +2664,8 @@ msgstr "TUN 接口启动失败,尝试重启内核..."
|
||||
msgid "Core Start Failed, Please Check The Log Infos!"
|
||||
msgstr "内核启动失败,请查看《内核日志》排查失败原因!"
|
||||
|
||||
msgid "Core Status Abnormal, Please Check The Log Infos!"
|
||||
msgstr "内核状态异常,请查看《内核日志》排查异常原因!"
|
||||
msgid "Core Initial Configuration Timeout, Please Check The Log Infos!"
|
||||
msgstr "内核加载配置超时,请查看《内核日志》排查原因!"
|
||||
|
||||
msgid "Forced Sniff Pure IP Connections"
|
||||
msgstr "强制探测(嗅探)所有纯 IP 的连接"
|
||||
@@ -3780,6 +3780,12 @@ msgstr "编辑模块信息"
|
||||
msgid "Subscription"
|
||||
msgstr "订阅"
|
||||
|
||||
msgid "HTTP Module"
|
||||
msgstr "远程模块"
|
||||
|
||||
msgid "File Module"
|
||||
msgstr "本地模块"
|
||||
|
||||
msgid "Overwrite Module"
|
||||
msgstr "覆写模块"
|
||||
|
||||
@@ -3841,4 +3847,19 @@ msgid "Serial Number"
|
||||
msgstr "序号"
|
||||
|
||||
msgid "Set Custom Overwrite Script Failed,"
|
||||
msgstr "自定义覆写命令执行失败,"
|
||||
msgstr "自定义覆写命令执行失败,"
|
||||
|
||||
msgid "Load YAML Override Block"
|
||||
msgstr "加载 YAML 覆写模块"
|
||||
|
||||
msgid "Invalid YAML Override format, skipped..."
|
||||
msgstr "无效的 YAML 覆写格式,跳过..."
|
||||
|
||||
msgid "Parse YAML Override failed:"
|
||||
msgstr "YAML 覆写模块解析失败:"
|
||||
|
||||
msgid "YAML overwrite failed:"
|
||||
msgstr "YAML 覆写模块执行失败:"
|
||||
|
||||
msgid "files selected"
|
||||
msgstr "配置已选择"
|
||||
|
||||
@@ -9,6 +9,7 @@ USE_PROCD=1
|
||||
. $IPKG_INSTROOT/usr/share/openclash/ruby.sh
|
||||
. $IPKG_INSTROOT/usr/share/openclash/log.sh
|
||||
. $IPKG_INSTROOT/usr/share/openclash/uci.sh
|
||||
. $IPKG_INSTROOT/usr/share/openclash/openclash_curl.sh
|
||||
|
||||
[ -f /etc/openwrt_release ] && {
|
||||
FW4=$(command -v fw4)
|
||||
@@ -35,25 +36,25 @@ add_cron()
|
||||
{
|
||||
[ "$(tail -n1 /etc/crontabs/root | wc -l)" -eq 0 ] && [ -n "$(cat /etc/crontabs/root 2>/dev/null)" ] && echo >> /etc/crontabs/root
|
||||
[ -z "$(grep "openclash.sh" "$CRON_FILE" 2>/dev/null)" ] && {
|
||||
[ "$(uci_get_config "auto_update")" -eq 1 ] && [ "$(uci_get_config "config_auto_update_mode")" -ne 1 ] && echo "0 $(uci_get_config "auto_update_time" || 1) * * $(uci_get_config "config_update_week_time" || 0) /usr/share/openclash/openclash.sh" >> $CRON_FILE
|
||||
[ "$(uci_get_config "auto_update")" -eq 1 ] && [ "$(uci_get_config "config_auto_update_mode")" -ne 1 ] && echo "0 $(uci_get_config "auto_update_time" || 1) * * $(uci_get_config "config_update_week_time" || 0) /usr/share/openclash/openclash.sh #openclash-cron-task" >> $CRON_FILE
|
||||
}
|
||||
[ -z "$(grep "openclash_ipdb.sh" "$CRON_FILE" 2>/dev/null)" ] && {
|
||||
[ "$(uci_get_config "geo_auto_update")" -eq 1 ] && echo "0 $(uci_get_config "geo_update_day_time" || 1) * * $(uci_get_config "geo_update_week_time" || 0) /usr/share/openclash/openclash_ipdb.sh" >> $CRON_FILE
|
||||
[ "$(uci_get_config "geo_auto_update")" -eq 1 ] && echo "0 $(uci_get_config "geo_update_day_time" || 1) * * $(uci_get_config "geo_update_week_time" || 0) /usr/share/openclash/openclash_ipdb.sh #openclash-cron-task" >> $CRON_FILE
|
||||
}
|
||||
[ -z "$(grep "openclash_geosite.sh" "$CRON_FILE" 2>/dev/null)" ] && {
|
||||
[ "$(uci_get_config "geosite_auto_update")" -eq 1 ] && echo "0 $(uci_get_config "geosite_update_day_time" || 1) * * $(uci_get_config "geosite_update_week_time" || 0) /usr/share/openclash/openclash_geosite.sh" >> $CRON_FILE
|
||||
[ "$(uci_get_config "geosite_auto_update")" -eq 1 ] && echo "0 $(uci_get_config "geosite_update_day_time" || 1) * * $(uci_get_config "geosite_update_week_time" || 0) /usr/share/openclash/openclash_geosite.sh #openclash-cron-task" >> $CRON_FILE
|
||||
}
|
||||
[ -z "$(grep "openclash_geoip.sh" "$CRON_FILE" 2>/dev/null)" ] && {
|
||||
[ "$(uci_get_config "geoip_auto_update")" -eq 1 ] && echo "0 $(uci_get_config "geoip_update_day_time" || 1) * * $(uci_get_config "geoip_update_week_time" || 0) /usr/share/openclash/openclash_geoip.sh" >> $CRON_FILE
|
||||
[ "$(uci_get_config "geoip_auto_update")" -eq 1 ] && echo "0 $(uci_get_config "geoip_update_day_time" || 1) * * $(uci_get_config "geoip_update_week_time" || 0) /usr/share/openclash/openclash_geoip.sh #openclash-cron-task" >> $CRON_FILE
|
||||
}
|
||||
[ -z "$(grep "openclash_geoasn.sh" "$CRON_FILE" 2>/dev/null)" ] && {
|
||||
[ "$(uci_get_config "geoasn_auto_update")" -eq 1 ] && echo "0 $(uci_get_config "geoasn_update_day_time" || 1) * * $(uci_get_config "geoasn_update_week_time" || 0) /usr/share/openclash/openclash_geoasn.sh" >> $CRON_FILE
|
||||
[ "$(uci_get_config "geoasn_auto_update")" -eq 1 ] && echo "0 $(uci_get_config "geoasn_update_day_time" || 1) * * $(uci_get_config "geoasn_update_week_time" || 0) /usr/share/openclash/openclash_geoasn.sh #openclash-cron-task" >> $CRON_FILE
|
||||
}
|
||||
[ -z "$(grep "openclash_chnroute.sh" "$CRON_FILE" 2>/dev/null)" ] && {
|
||||
[ "$(uci_get_config "chnr_auto_update")" -eq 1 ] && echo "0 $(uci_get_config "chnr_update_day_time" || 1) * * $(uci_get_config "chnr_update_week_time" || 0) /usr/share/openclash/openclash_chnroute.sh" >> $CRON_FILE
|
||||
[ "$(uci_get_config "chnr_auto_update")" -eq 1 ] && echo "0 $(uci_get_config "chnr_update_day_time" || 1) * * $(uci_get_config "chnr_update_week_time" || 0) /usr/share/openclash/openclash_chnroute.sh #openclash-cron-task" >> $CRON_FILE
|
||||
}
|
||||
[ -z "$(grep "/etc/init.d/openclash" "$CRON_FILE" 2>/dev/null)" ] && {
|
||||
[ "$(uci_get_config "auto_restart")" -eq 1 ] && echo "0 $(uci_get_config "auto_restart_day_time" || 1) * * $(uci_get_config "auto_restart_week_time" || 0) /etc/init.d/openclash restart" >> $CRON_FILE
|
||||
[ "$(uci_get_config "auto_restart")" -eq 1 ] && echo "0 $(uci_get_config "auto_restart_day_time" || 1) * * $(uci_get_config "auto_restart_week_time" || 0) /etc/init.d/openclash restart #openclash-cron-task" >> $CRON_FILE
|
||||
}
|
||||
|
||||
config_load "openclash"
|
||||
@@ -65,13 +66,7 @@ add_cron()
|
||||
|
||||
del_cron()
|
||||
{
|
||||
sed -i '/openclash.sh/d' $CRON_FILE
|
||||
sed -i '/openclash_ipdb.sh/d' $CRON_FILE
|
||||
sed -i '/openclash_geoip.sh/d' $CRON_FILE
|
||||
sed -i '/openclash_geosite.sh/d' $CRON_FILE
|
||||
sed -i '/openclash_geoasn.sh/d' $CRON_FILE
|
||||
sed -i '/openclash_chnroute.sh/d' $CRON_FILE
|
||||
sed -i '/\/etc\/init.d\/openclash/d' $CRON_FILE
|
||||
sed -i '/#openclash-cron-task/d' $CRON_FILE
|
||||
sed -i '/#openclash-overwrite-download/d' $CRON_FILE
|
||||
/etc/init.d/cron restart
|
||||
} >/dev/null 2>&1
|
||||
@@ -85,7 +80,7 @@ save_dnsmasq_server() {
|
||||
}
|
||||
|
||||
set_dnsmasq_server() {
|
||||
if [ -z "$1" ] || [ "$1" == "127.0.0.1#${dns_port}" ]; then
|
||||
if [ -z "$1" ] || [ "$1" == "127.0.0.1#${dns_port}" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -209,11 +204,11 @@ change_dns() {
|
||||
if [ "$1" -eq 1 ] && [ "$ipv6_dns" -eq 1 ] && [ -n "$(ip6tables -t mangle -L 2>&1 | grep -o 'Chain')" ]; then
|
||||
#dnsmasq answer ipv6
|
||||
uci -q set openclash.config.dnsmasq_filter_aaaa="$(uci -q get dhcp.@dnsmasq[0].filter_aaaa)"
|
||||
uci -q set dhcp.@dnsmasq[0].filter_aaaa=0
|
||||
uci -q set openclash.config.filter_aaaa_dns=1
|
||||
else
|
||||
uci -q set openclash.config.filter_aaaa_dns=0
|
||||
fi
|
||||
uci -q set dhcp.@dnsmasq[0].filter_aaaa=0
|
||||
uci -q set openclash.config.filter_aaaa_dns=1
|
||||
else
|
||||
uci -q set openclash.config.filter_aaaa_dns=0
|
||||
fi
|
||||
|
||||
uci -q commit dhcp
|
||||
uci -q commit openclash
|
||||
@@ -702,24 +697,25 @@ check_core_status()
|
||||
ip_="ip -6"
|
||||
fi
|
||||
|
||||
#wait 120s most for tun interface start
|
||||
while ( [ -n "$(pidof clash)" ] && [ -z "$($ip_ route list |grep utun)" ] && [ "$TUN_WAIT" -le 120 ] )
|
||||
#wait 300s most for core start
|
||||
while ( [ -n "$(pidof clash)" ] && [ -z "$($ip_ route list |grep utun)" ] && [ "$TUN_WAIT" -le 300 ] )
|
||||
do
|
||||
$ip_ link set utun up
|
||||
let TUN_WAIT++
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ -n "$(pidof clash)" ] && [ -z "$($ip_ route list |grep utun)" ] && [ "$TUN_WAIT" -gt 120 ]; then
|
||||
if [ -n "$(pidof clash)" ] && [ -z "$($ip_ route list |grep utun)" ] && [ "$TUN_WAIT" -gt 300 ]; then
|
||||
while ( [ -n "$(pidof clash)" ] && [ -z "$($ip_ route list |grep utun)" ] && [ "$TUN_RESTART" -le 3 ] )
|
||||
do
|
||||
LOG_WARN "TUN Interface Start Failed, Try to Restart Again..."
|
||||
start_run_core
|
||||
sleep 120
|
||||
let TUN_RESTART++
|
||||
sleep 300
|
||||
done
|
||||
if [ -n "$(pidof clash)" ] && [ -z "$($ip_ route list |grep utun)" ] && [ "$TUN_RESTART" -gt 3 ]; then
|
||||
LOG_WARN "TUN Interface Start Failed, Please Check The Dependence or Try to Restart Again!"
|
||||
LOG_ERROR "TUN Interface Start Failed, Please Check The Dependence or Try to Restart Again!"
|
||||
LOG_ERROR "Core Initial Configuration Timeout, Please Check The Log Infos!"
|
||||
start_fail
|
||||
fi
|
||||
fi
|
||||
@@ -742,7 +738,7 @@ check_core_status()
|
||||
fi
|
||||
else
|
||||
reg4='^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$'
|
||||
while ( [ -n "$(pidof clash)" ] && [ "$CORE_HTTP_CODE" != "200" ] && [ "$TUN_WAIT" -le 120 ] && [ -n "$(echo ${lan_ip} | grep -Eo ${reg4})" ] )
|
||||
while ( [ -n "$(pidof clash)" ] && [ "$CORE_HTTP_CODE" != "200" ] && [ "$TUN_WAIT" -le 300 ] && [ -n "$(echo ${lan_ip} | grep -Eo ${reg4})" ] )
|
||||
do
|
||||
CORE_HTTP_CODE=$(curl -m 5 -o /dev/null -s -w '%{http_code}' -H 'Content-Type: application/json' -H "Authorization: Bearer ${da_password}" -XGET http://${lan_ip}:${cn_port}/group)
|
||||
let TUN_WAIT++
|
||||
@@ -750,9 +746,10 @@ check_core_status()
|
||||
done
|
||||
if [ -z "$(echo ${lan_ip} | grep -Eo ${reg4})" ]; then
|
||||
LOG_ERROR "LAN IP Address Get Error, Please Check The LAN Interface Setting or Choose the Correct Interface in the Setting!"
|
||||
start_fail
|
||||
fi
|
||||
if [ "$CORE_HTTP_CODE" != "200" ]; then
|
||||
LOG_ERROR "Core Status Abnormal, Please Check The Log Infos!"
|
||||
if [ -n "$(pidof clash)" ] && [ "$CORE_HTTP_CODE" != "200" ]; then
|
||||
LOG_ERROR "Core Initial Configuration Timeout, Please Check The Log Infos!"
|
||||
start_fail
|
||||
fi
|
||||
fi
|
||||
@@ -1443,7 +1440,7 @@ if [ -n "$common_ports" ] && [ "$common_ports" != "0" ]; then
|
||||
fi
|
||||
|
||||
case $enable_redirect_dns in
|
||||
"1")
|
||||
"1")
|
||||
LOG_TIP "DNS Hijacking Mode is Dnsmasq Redirect..."
|
||||
;;
|
||||
"2")
|
||||
@@ -3244,9 +3241,9 @@ add_overwrite_cron()
|
||||
fi
|
||||
|
||||
eval "restart_flag=\${OVERWRITE_RESTART_FLAG_${name}}"
|
||||
cron_cmd="curl -fsSL -m 30 \"$url\" -o \"/etc/openclash/overwrite/$name\" >/dev/null 2>&1"
|
||||
cron_cmd="$cron source /usr/share/openclash/openclash_curl.sh && DOWNLOAD_FILE_CURL \"$url\" \"/etc/openclash/overwrite/$name\" \"/etc/openclash/overwrite/$name\""
|
||||
if [ "$restart_flag" = "1" ]; then
|
||||
cron_cmd="$cron_cmd && /etc/init.d/openclash restart"
|
||||
cron_cmd="$cron_cmd && [ \"\$?\" -eq 0 ] && /etc/init.d/openclash restart"
|
||||
fi
|
||||
cron_cmd="$cron_cmd #openclash-overwrite-download"
|
||||
|
||||
@@ -3276,6 +3273,26 @@ check_type() {
|
||||
esac
|
||||
}
|
||||
|
||||
overwrite_config_match_check()
|
||||
{
|
||||
local section="$1" name config
|
||||
config_get "name" "$section" "name" ""
|
||||
config_get "config" "$section" "config" ""
|
||||
|
||||
[ -z "$name" ] || [ "$name" != "$2" ] || [ -z "$config" ] && return
|
||||
|
||||
config_list_foreach "$section" "config" overwrite_config_match_item
|
||||
}
|
||||
|
||||
overwrite_config_match_item()
|
||||
{
|
||||
local config_path_item="$1"
|
||||
|
||||
[ -z "$config_path_item" ] && return
|
||||
[ "$config_path_item" = "all" ] && OVERWRITE_CONFIG_MATCHED=1 && return
|
||||
[ "$config_path_item" = "$(uci_get_config "config_path")" ] && OVERWRITE_CONFIG_MATCHED=1
|
||||
}
|
||||
|
||||
overwrite_file()
|
||||
{
|
||||
clear_overwrite_set
|
||||
@@ -3404,7 +3421,11 @@ EOF
|
||||
name=$(echo "$entry" | cut -d'|' -f2)
|
||||
sid=$(echo "$entry" | cut -d'|' -f3)
|
||||
enabled_flag=$(echo "$entry" | cut -d'|' -f4)
|
||||
OVERWRITE_CONFIG_MATCHED=0
|
||||
config_load "openclash"
|
||||
config_foreach overwrite_config_match_check "config_overwrite" "$name"
|
||||
|
||||
[ "$OVERWRITE_CONFIG_MATCHED" -eq 0 ] && continue
|
||||
[ "$enabled_flag" != "1" ] && continue
|
||||
[ -z "$name" ] && continue
|
||||
|
||||
@@ -3436,14 +3457,16 @@ EOF
|
||||
|
||||
in_general=0
|
||||
in_overwrite=0
|
||||
in_yaml=0
|
||||
download_file_lines=""
|
||||
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
trimmed=$(printf "%s" "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
case "$trimmed" in
|
||||
"[General]"*) in_general=1; in_overwrite=0; continue;;
|
||||
"[Overwrite]"*) in_general=0; in_overwrite=1; continue;;
|
||||
"["*"]"*) in_general=0; in_overwrite=0; continue;;
|
||||
"[General]"*) in_general=1; in_overwrite=0; in_yaml=0; continue;;
|
||||
"[Overwrite]"*) in_general=0; in_overwrite=1; in_yaml=0; continue;;
|
||||
"[YAML]"*) in_general=0; in_overwrite=0; in_yaml=1; continue;;
|
||||
"["*"]"*) in_general=0; in_overwrite=0; in_yaml=0; continue;;
|
||||
esac
|
||||
[ -z "$trimmed" ] && continue
|
||||
echo "$trimmed" | grep -qE '^[#;]' && continue
|
||||
@@ -3483,20 +3506,13 @@ ${trimmed}"
|
||||
LOG_TIP "DOWNLOAD FILE for【Download Job => file: $file, url: $url, path: $path, ua: ${ua:-null}, force: ${force:-false}】"
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
if [ -n "$ua" ]; then
|
||||
curl -fsSL -m 30 -H "User-Agent: $ua" "$url" -o "$path" >/dev/null 2>&1
|
||||
DOWNLOAD_FILE_CURL "$url" "$path" "$path" "$ua"
|
||||
else
|
||||
curl -fsSL -m 30 "$url" -o "$path" >/dev/null 2>&1
|
||||
fi
|
||||
rc=$?
|
||||
else
|
||||
if [ -n "$ua" ]; then
|
||||
wget -T 30 --user-agent="$ua" -qO "$path" "$url" >/dev/null 2>&1
|
||||
else
|
||||
wget -T 30 -qO "$path" "$url" >/dev/null 2>&1
|
||||
DOWNLOAD_FILE_CURL "$url" "$path" "$path"
|
||||
fi
|
||||
rc=$?
|
||||
fi
|
||||
if [ $rc -ne 0 ] && [ ! -f "$path" ]; then
|
||||
if [ $rc -eq 1 ] || [ ! -f "$path" ]; then
|
||||
LOG_ERROR "DOWNLOAD FILE failed for【Download Job => file: $file, url: $url, path: $path】"
|
||||
download_failed=1
|
||||
break
|
||||
@@ -3506,12 +3522,12 @@ ${trimmed}"
|
||||
LOG_TIP "Add Cron for【Cron Job => time: $cron, url: $url, path: $path, restart: ${restart:-false}】"
|
||||
if ! grep -q "$url" $CRON_FILE 2>/dev/null; then
|
||||
if [ -n "$ua" ]; then
|
||||
cron_cmd="$cron curl -fsSL -m 30 -H \"User-Agent: $ua\" \"$url\" -o \"$path\" >/dev/null 2>&1"
|
||||
cron_cmd="$cron source /usr/share/openclash/openclash_curl.sh && DOWNLOAD_FILE_CURL \"$url\" \"$path\" \"$path\" \"$ua\""
|
||||
else
|
||||
cron_cmd="$cron curl -fsSL -m 30 \"$url\" -o \"$path\" >/dev/null 2>&1"
|
||||
cron_cmd="$cron source /usr/share/openclash/openclash_curl.sh && DOWNLOAD_FILE_CURL \"$url\" \"$path\" \"$path\""
|
||||
fi
|
||||
if [ "$restart" = "1" ] || [ "$restart" = "true" ]; then
|
||||
cron_cmd="$cron_cmd && /etc/init.d/openclash restart"
|
||||
cron_cmd="$cron_cmd && [ \"\$?\" -eq 0 ] && /etc/init.d/openclash restart"
|
||||
fi
|
||||
cron_cmd="$cron_cmd #openclash-overwrite-download"
|
||||
echo "$cron_cmd" >> $CRON_FILE
|
||||
@@ -3523,12 +3539,15 @@ ${trimmed}"
|
||||
|
||||
in_general=0
|
||||
in_overwrite=0
|
||||
in_yaml=0
|
||||
yaml_content=""
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
trimmed=$(printf "%s" "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
case "$trimmed" in
|
||||
"[General]"*) in_general=1; in_overwrite=0; continue;;
|
||||
"[Overwrite]"*) in_general=0; in_overwrite=1; continue;;
|
||||
"["*"]"*) in_general=0; in_overwrite=0; continue;;
|
||||
"[General]"*) in_general=1; in_overwrite=0; in_yaml=0; continue;;
|
||||
"[Overwrite]"*) in_general=0; in_overwrite=1; in_yaml=0; continue;;
|
||||
"[YAML]"*) in_general=0; in_overwrite=0; in_yaml=1; continue;;
|
||||
"["*"]"*) in_general=0; in_overwrite=0; in_yaml=0; continue;;
|
||||
esac
|
||||
[ -z "$trimmed" ] && continue
|
||||
echo "$trimmed" | grep -qE '^[#;]' && continue
|
||||
@@ -3573,8 +3592,6 @@ ${trimmed}"
|
||||
val_key=$(eval "echo \"$val_clean\"")
|
||||
if check_type "$key_u" "$val_clean"; then
|
||||
uci -q set openclash.@overwrite[0]."$key_l"="$val_key"
|
||||
else
|
||||
LOG_WARN "skip General key type error【General Key => $name: $key_u, value: $val_key】"
|
||||
fi
|
||||
else
|
||||
LOG_WARN "skip General key not allowed【General Key => $name: $key_u】"
|
||||
@@ -3587,14 +3604,53 @@ ${trimmed}"
|
||||
else
|
||||
LOG_WARN "skip invalid Overwrite command【Ruby Script => $name: $trimmed】"
|
||||
fi
|
||||
elif [ "$in_yaml" -eq 1 ]; then
|
||||
yaml_content="${yaml_content}$(eval "echo \"$line\"")"$'\n'
|
||||
fi
|
||||
done < "$file"
|
||||
|
||||
if [ -n "$yaml_content" ]; then
|
||||
LOG_TIP "Load YAML Override Block【YAML Block => $name】"
|
||||
|
||||
cat >> "$overwrite_script" <<'EOF'
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
|
||||
# YAML Override Block Processing
|
||||
begin
|
||||
yaml_override_content = <<-'YAML_CONTENT'
|
||||
EOF
|
||||
|
||||
echo "$yaml_content" >> "$overwrite_script"
|
||||
|
||||
cat >> "$overwrite_script" <<'EOF'
|
||||
YAML_CONTENT
|
||||
|
||||
begin
|
||||
yaml_data = YAML.load(yaml_override_content)
|
||||
|
||||
if yaml_data.is_a?(Hash)
|
||||
Value = YAML.load_file('$CONFIG_FILE')
|
||||
Overwrite_Value = YAML.overwrite(Value, yaml_data)
|
||||
File.open('$CONFIG_FILE', 'w') do |f|
|
||||
YAML.dump(Overwrite_Value, f)
|
||||
end
|
||||
else
|
||||
YAML.LOG_WARN('Invalid YAML Override format, skipped...')
|
||||
end
|
||||
rescue => e
|
||||
YAML.LOG_ERROR('Parse YAML Override failed:【%s】' % [e.message])
|
||||
end
|
||||
rescue => e
|
||||
YAML.LOG_ERROR('Parse YAML Override failed:【%s】' % [e.message])
|
||||
end
|
||||
" >> $LOG_FILE 2>&1
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat >> "$overwrite_script" <<'EOF'
|
||||
if [ -f "/tmp/yaml_openclash_ruby_parts/$OPENCLASH_OVERWRITE_SID" ]; then
|
||||
ruby_code=$(cat "/tmp/yaml_openclash_ruby_parts/$OPENCLASH_OVERWRITE_SID")
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
|
||||
Value = YAML.load_file('$CONFIG_FILE');
|
||||
Value = YAML.load_file('$CONFIG_FILE')
|
||||
threads = []
|
||||
$ruby_code
|
||||
threads.each(&:join)
|
||||
@@ -3662,7 +3718,7 @@ get_config()
|
||||
else
|
||||
fakeip_range6=$(uci_get_config "fakeip_range6")
|
||||
fi
|
||||
[ -z "$fakeip_range6" ] && fakeip_range6="fdfe:dcba:9876::1/64"
|
||||
[ -z "$fakeip_range6" ] && fakeip_range6=0
|
||||
|
||||
lan_interface_name=$(uci_get_config "lan_interface_name" || echo 0)
|
||||
if [ "$lan_interface_name" = "0" ]; then
|
||||
@@ -3820,14 +3876,14 @@ start_service()
|
||||
fi
|
||||
|
||||
rm -rf /tmp/yaml_*
|
||||
}
|
||||
} >/dev/null 2>&1
|
||||
|
||||
echo "OpenClash Already Start!"
|
||||
}
|
||||
|
||||
stop_service()
|
||||
{
|
||||
enable=$(uci_get_config "enable")
|
||||
get_config
|
||||
|
||||
LOG_TIP "OpenClash Stoping..."
|
||||
LOG_OUT "Step 1: Backup The Current Groups State..."
|
||||
@@ -3869,6 +3925,7 @@ stop_service()
|
||||
|
||||
del_cron
|
||||
clear_overwrite_set
|
||||
rm -rf /tmp/openclash_jobs
|
||||
rm -rf /tmp/yaml_*
|
||||
} >/dev/null 2>&1
|
||||
|
||||
@@ -3877,7 +3934,6 @@ stop_service()
|
||||
|
||||
revert_dnsmasq()
|
||||
{
|
||||
get_config
|
||||
redirect_dns=$(uci_get_config "redirect_dns")
|
||||
dnsmasq_server=$(uci_get_config "dnsmasq_server")
|
||||
dnsmasq_noresolv=$(uci_get_config "dnsmasq_noresolv")
|
||||
@@ -3895,20 +3951,20 @@ restart()
|
||||
echo "OpenClash Restart..."
|
||||
LOG_TIP "OpenClash Restart..."
|
||||
check_run_quick
|
||||
stop
|
||||
stop_service
|
||||
start
|
||||
}
|
||||
|
||||
start_watchdog()
|
||||
{
|
||||
procd_open_instance "openclash-watchdog"
|
||||
procd_set_param command "/usr/share/openclash/openclash_watchdog.sh"
|
||||
procd_close_instance
|
||||
procd_set_param command "/usr/share/openclash/openclash_watchdog.sh"
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
reload_service()
|
||||
{
|
||||
enable=$(uci_get_config "enable")
|
||||
get_config
|
||||
MAX_RELOAD=10
|
||||
if pidof clash >/dev/null && [ "$enable" == "1" ] && [ "$1" == "firewall" ]; then
|
||||
#sleep for avoiding system unready
|
||||
@@ -3935,14 +3991,12 @@ reload_service()
|
||||
LOG_OUT "【${CUR_RELOAD_NUM}/$MAX_RELOAD】Reload OpenClash Firewall Rules..."
|
||||
revert_firewall
|
||||
do_run_mode
|
||||
get_config
|
||||
check_core_status &
|
||||
fi
|
||||
if pidof clash >/dev/null && [ "$enable" == "1" ] && [ "$1" == "manual" ]; then
|
||||
LOG_OUT "Manually Reload Firewall Rules..."
|
||||
revert_firewall
|
||||
do_run_mode
|
||||
get_config
|
||||
check_core_status &
|
||||
fi
|
||||
if pidof clash >/dev/null && [ "$enable" == "1" ] && [ "$1" == "revert" ]; then
|
||||
@@ -3952,7 +4006,6 @@ reload_service()
|
||||
fi
|
||||
if pidof clash >/dev/null && [ "$enable" == "1" ] && [ "$1" == "restore" ]; then
|
||||
do_run_mode
|
||||
get_config
|
||||
# used for config subscribe, not background for avoiding system unready
|
||||
check_core_status
|
||||
fi
|
||||
@@ -3960,12 +4013,11 @@ reload_service()
|
||||
|
||||
boot()
|
||||
{
|
||||
delay_start=$(uci_get_config "delay_start" || echo 0)
|
||||
enable=$(uci_get_config "enable")
|
||||
if [ "$delay_start" -gt 0 ] && [ "$enable" == "1" ]; then
|
||||
LOG_OUT "Enable Delay Start, OpenClash Will Start After【$delay_start】Seconds..."
|
||||
sleep "$delay_start"
|
||||
fi
|
||||
restart
|
||||
}
|
||||
|
||||
delay_start=$(uci_get_config "delay_start" || echo 0)
|
||||
enable=$(uci_get_config "enable")
|
||||
if [ "$delay_start" -gt 0 ] && [ "$enable" == "1" ]; then
|
||||
LOG_OUT "Enable Delay Start, OpenClash Will Start After【$delay_start】Seconds..."
|
||||
sleep "$delay_start"
|
||||
fi
|
||||
restart
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 201 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,21 @@
|
||||
rule-providers:
|
||||
## google:
|
||||
## type: http
|
||||
## path: ./rule1.yaml
|
||||
## url: "https://raw.githubusercontent.com/../Google.yaml"
|
||||
## interval: 600
|
||||
## proxy: DIRECT
|
||||
## behavior: classical
|
||||
## format: yaml
|
||||
## size-limit: 0
|
||||
## header:
|
||||
## User-Agent:
|
||||
## - "mihomo/1.18.3"
|
||||
## Authorization:
|
||||
## - 'token 1231231'
|
||||
## payload:
|
||||
## - 'DOMAIN-SUFFIX,google.com'
|
||||
|
||||
rules:
|
||||
##- PROCESS-NAME,curl,DIRECT #匹配路由自身进程(curl直连)
|
||||
##- DOMAIN-SUFFIX,google.com,Proxy #匹配域名后缀(交由Proxy代理服务器组)
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
rule-providers:
|
||||
## google:
|
||||
## type: http
|
||||
## path: ./rule1.yaml
|
||||
## url: "https://raw.githubusercontent.com/../Google.yaml"
|
||||
## interval: 600
|
||||
## proxy: DIRECT
|
||||
## behavior: classical
|
||||
## format: yaml
|
||||
## size-limit: 0
|
||||
## header:
|
||||
## User-Agent:
|
||||
## - "mihomo/1.18.3"
|
||||
## Authorization:
|
||||
## - 'token 1231231'
|
||||
## payload:
|
||||
## - 'DOMAIN-SUFFIX,google.com'
|
||||
rules:
|
||||
##- DOMAIN-SUFFIX,google.com,Proxy #匹配域名后缀(交由Proxy代理服务器组)
|
||||
##- DOMAIN-KEYWORD,google,Proxy #匹配域名关键字(交由Proxy代理服务器组)
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
|
||||
# ENABLE_TCP_CONCURRENT (启用 TCP 并发, 0: 插件不覆写, 1: 启用)
|
||||
# FIND_PROCESS_MODE (进程查找模式, 0: 插件不覆写, off: 关闭, always, strict)
|
||||
# GLOBAL_CLIENT_FINGERPRINT (全局客户端指纹, none/random/chrome/firefox/safari/ios/android/edge/360/qq/0: 插件不覆写)
|
||||
# ENABLE_UNIFIED_DELAY (启用统一延迟计算, 0: 插件不覆写, 1: 启用)
|
||||
|
||||
# ENABLE_RULE_PROXY (仅代理命中规则流量, 0: 禁用, 1: 启用)
|
||||
@@ -209,6 +208,298 @@
|
||||
#指定展示订阅信息的 URL 地址
|
||||
#SUB_INFO_URL = https://default-demo.yaml
|
||||
|
||||
# ==========================================================
|
||||
# ==== 以下为 YAML 块覆写示例 ====
|
||||
# ==========================================================
|
||||
# YAML 覆写块识别标记
|
||||
# [YAML]
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 操作符速查表:
|
||||
# key → 默认合并(Hash 递归合并,其他直接覆盖)
|
||||
# key! → 强制覆盖整个值
|
||||
# key+ → 数组后置追加
|
||||
# +key → 数组前置插入
|
||||
# key- → 数组差集删除 / 非数组则删除该键
|
||||
# key* → 批量条件更新(需配合 where/set)
|
||||
# <key>后缀 → 同上(+, !, *, -),适用于键名含特殊字符时
|
||||
# +<key> → 数组前置插入,适用于键名含特殊字符时
|
||||
# ----------------------------------------------------------
|
||||
|
||||
# 1. 默认合并 - 合并哈希键值,键不存在则添加,Hash 值递归合并
|
||||
# 示例:修改 dns.enable 并添加新键
|
||||
# dns:
|
||||
# enable: true
|
||||
# cache-algorithm: lru
|
||||
|
||||
# 示例:直接替换标量值
|
||||
# mixed-port: 10802
|
||||
# allow-lan: false
|
||||
|
||||
# 示例:合并 tun 配置(仅修改指定字段,其余保留)
|
||||
# tun:
|
||||
# enable: true
|
||||
# stack: gvisor
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
# 2. 强制覆盖 (key!) - 强制替换整个值,不做递归合并
|
||||
# 示例:替换整个 dns.fake-ip-filter 数组
|
||||
# dns:
|
||||
# fake-ip-filter!:
|
||||
# - '*.lan'
|
||||
# - 'new.domain.com'
|
||||
|
||||
# 示例:强制覆盖整个 rules 数组
|
||||
# rules!:
|
||||
# - DOMAIN-SUFFIX,example.com,DIRECT
|
||||
# - MATCH,PROXY
|
||||
|
||||
# 示例:使用 <> 强制覆盖整个 dns 配置(键名含特殊字符时)
|
||||
# <dns>!:
|
||||
# enable: false
|
||||
# nameserver:
|
||||
# - '114.114.114.114'
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
# 3. 数组后置 (key+) - 将新元素追加到数组末尾
|
||||
# 示例:向 dns.nameserver 末尾追加 DNS
|
||||
# dns:
|
||||
# nameserver+:
|
||||
# - '1.1.1.1'
|
||||
# - '8.8.8.8'
|
||||
|
||||
# 示例:向 rules 末尾追加规则
|
||||
# rules+:
|
||||
# - DOMAIN-SUFFIX,example.com,REJECT
|
||||
|
||||
# 示例:使用 <> 后置
|
||||
# dns:
|
||||
# <nameserver>+:
|
||||
# - '8.8.8.8'
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
# 4. 数组前置 (+key / +<key>) - 将新元素插入到数组开头
|
||||
# 示例:向 dns.nameserver 开头插入 DNS
|
||||
# dns:
|
||||
# +nameserver:
|
||||
# - '223.5.5.5'
|
||||
# - '119.29.29.29'
|
||||
|
||||
# 示例:向 rules 开头插入规则(优先匹配)
|
||||
# +rules:
|
||||
# - DOMAIN-SUFFIX,priority.com,DIRECT
|
||||
|
||||
# 示例:使用 +<> 前置插入(键名含特殊字符时)
|
||||
# dns:
|
||||
# +<nameserver>:
|
||||
# - '119.29.29.29'
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
# 5. 数组删除 (key-) - 从数组中移除指定元素,非数组则删除整个键
|
||||
# 示例:从 dns.nameserver 中移除指定 DNS
|
||||
# dns:
|
||||
# nameserver-:
|
||||
# - '8.8.8.8'
|
||||
# - '8.8.4.4'
|
||||
|
||||
# 示例:从 rules 中移除指定规则
|
||||
# rules-:
|
||||
# - DOMAIN-SUFFIX,old.com,REJECT
|
||||
|
||||
# 示例:删除整个键(值为 null/~ 或空)
|
||||
# dns:
|
||||
# cache-algorithm-:
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
# 6. 批量条件更新 (key*) - 按 where 条件匹配集合元素,用 set 子句更新
|
||||
#
|
||||
# 支持的集合类型:
|
||||
# - 数组(元素为 Hash 或 String)
|
||||
# - Hash(值为 Hash)
|
||||
#
|
||||
# where 条件支持:
|
||||
# - 普通值相等: type: select
|
||||
# - 正则匹配: name: '/^HK/'
|
||||
# - 数组包含: proxies: ['proxy1'] (检查数组是否包含所有指定元素)
|
||||
# - Hash 键匹配: key: 'some-key' (仅用于 Hash 类型集合)
|
||||
# - 字符串匹配: value: 'some-string' (仅用于字符串数组)
|
||||
#
|
||||
# set 子句支持的操作符(同顶层操作符):
|
||||
# field → 直接覆盖该字段
|
||||
# field! → 强制覆盖该字段
|
||||
# field+ → 数组后置追加
|
||||
# +field → 数组前置插入
|
||||
# field- → 数组差集删除 / 非数组则删除该键
|
||||
# value → 替换字符串数组中的匹配项
|
||||
# value: ~ → 删除字符串数组中的匹配项
|
||||
# key: ~ → 删除 Hash 中的匹配键(仅 Hash 集合)
|
||||
# key-: → 同上
|
||||
|
||||
# 示例:按 type 匹配,替换 proxy-groups 的 proxies
|
||||
# proxy-groups*:
|
||||
# where:
|
||||
# type: select
|
||||
# set:
|
||||
# proxies:
|
||||
# - 'new-proxy1'
|
||||
# - 'new-proxy2'
|
||||
|
||||
# 示例:按 type 匹配,向 proxies 末尾追加节点
|
||||
# proxy-groups*:
|
||||
# where:
|
||||
# type: select
|
||||
# set:
|
||||
# proxies+:
|
||||
# - 'new-proxy3'
|
||||
|
||||
# 示例:按 type 匹配,向 proxies 开头插入节点
|
||||
# proxy-groups*:
|
||||
# where:
|
||||
# type: url-test
|
||||
# set:
|
||||
# +proxies:
|
||||
# - 'new-proxy1'
|
||||
|
||||
# 示例:按 type 匹配,从 proxies 中移除指定节点
|
||||
# proxy-groups*:
|
||||
# where:
|
||||
# type: select
|
||||
# set:
|
||||
# proxies-:
|
||||
# - 'old-proxy1'
|
||||
# - 'old-proxy2'
|
||||
|
||||
# 示例:使用数组包含条件(proxies 中必须包含指定元素才匹配)
|
||||
# proxy-groups*:
|
||||
# where:
|
||||
# type: select
|
||||
# proxies:
|
||||
# - 'old-proxy1'
|
||||
# set:
|
||||
# proxies:
|
||||
# - 'new-proxy1'
|
||||
# - 'new-proxy2'
|
||||
|
||||
# 示例:使用正则匹配 name 字段
|
||||
# proxy-groups*:
|
||||
# where:
|
||||
# name: '/^HK/'
|
||||
# set:
|
||||
# +proxies:
|
||||
# - 'hk-new-proxy'
|
||||
|
||||
# 示例:删除 proxy-groups 中某个字段(set value 为 null/~)
|
||||
# proxy-groups*:
|
||||
# where:
|
||||
# name: Proxy
|
||||
# set:
|
||||
# icon-:
|
||||
|
||||
# 示例:按 type 匹配修改 proxies 中 socks5 节点端口
|
||||
# proxies*:
|
||||
# where:
|
||||
# type: socks5
|
||||
# set:
|
||||
# port: 1080
|
||||
|
||||
# 示例:更新 hosts Hash 中的指定键
|
||||
# hosts*:
|
||||
# where:
|
||||
# key: '*.mihomo.dev'
|
||||
# set:
|
||||
# '*.mihomo.dev': '::1'
|
||||
|
||||
# 示例:删除 hosts Hash 中的匹配键
|
||||
# hosts*:
|
||||
# where:
|
||||
# key: '*.old.dev'
|
||||
# set:
|
||||
# key-:
|
||||
|
||||
# 示例:替换字符串数组中的匹配项
|
||||
# rules*:
|
||||
# where:
|
||||
# value: 'DOMAIN-SUFFIX,old.com,REJECT'
|
||||
# set:
|
||||
# value: 'DOMAIN-SUFFIX,new.com,DIRECT'
|
||||
|
||||
# 示例:使用正则匹配并删除字符串数组中的规则
|
||||
# rules*:
|
||||
# where:
|
||||
# value: '/,REJECT$/'
|
||||
# set:
|
||||
# value:
|
||||
|
||||
# 示例:删除 fake-ip-filter 中的指定项
|
||||
# dns:
|
||||
# fake-ip-filter*:
|
||||
# where:
|
||||
# value: '+.localhost.ptlogin2.qq.com'
|
||||
# set:
|
||||
# value:
|
||||
|
||||
# 示例:使用 <> 批量更新
|
||||
# <proxy-groups>*:
|
||||
# where:
|
||||
# type: url-test
|
||||
# set:
|
||||
# interval: 300
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
# 7. 组合操作 - 在同一块内同时使用多个操作符
|
||||
# 示例:同时前置和后置追加 DNS(必须写在同一 dns 块内)
|
||||
# dns:
|
||||
# nameserver+:
|
||||
# - '1.0.0.1'
|
||||
# +nameserver:
|
||||
# - '119.29.29.29'
|
||||
|
||||
# 示例:同时删除旧 DNS 并前置插入新 DNS
|
||||
# dns:
|
||||
# nameserver-:
|
||||
# - '8.8.8.8'
|
||||
# +nameserver:
|
||||
# - '223.5.5.5'
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
# 8. <key> 语法补充 - 适用于键名含特殊字符时
|
||||
# 后缀支持:+(后置)、-(删除)、!(强制覆盖)、*(批量更新),空串为默认合并
|
||||
# 前置插入请使用 +<key> 写法(前缀形式)
|
||||
|
||||
# 示例:使用 <> 后置追加
|
||||
# dns:
|
||||
# <nameserver>+:
|
||||
# - '8.8.8.8'
|
||||
|
||||
# 示例:使用 <> 删除数组元素
|
||||
# dns:
|
||||
# <nameserver>-:
|
||||
# - '8.8.8.8'
|
||||
|
||||
# 示例:使用 <> 强制覆盖
|
||||
# dns:
|
||||
# <nameserver>!:
|
||||
# - '114.114.114.114'
|
||||
|
||||
# 示例:使用 <> 批量更新(等同于 key*)
|
||||
# <proxy-groups>*:
|
||||
# where:
|
||||
# type: url-test
|
||||
# set:
|
||||
# interval: 300
|
||||
|
||||
# 示例:使用 +<> 前置插入(键名含特殊字符时的前置写法)
|
||||
# dns:
|
||||
# +<nameserver>:
|
||||
# - '119.29.29.29'
|
||||
|
||||
# ==========================================================
|
||||
# ==== 以下为仅修改插件设置示例 ====
|
||||
# ==========================================================
|
||||
|
||||
@@ -166,8 +166,10 @@ if [ -f "/tmp/openclash.bak" ]; then
|
||||
cp -rf "/tmp/openclash/." "/etc/openclash/" >/dev/null 2>&1
|
||||
#ui
|
||||
if [ -d "/tmp/openclash_ui/" ]; then
|
||||
rm -rf "/usr/share/openclash/ui/" >/dev/null 2>&1
|
||||
cp -rf "/tmp/openclash_ui/." "/usr/share/openclash/ui/" >/dev/null 2>&1
|
||||
if [ -d "/tmp/openclash_ui/metacubexd/" ] || [ -d "/tmp/openclash_ui/zashboard/" ] || [ -d "/tmp/openclash_ui/yacd/" ] || [ -d "/tmp/openclash_ui/dashboard/" ]; then
|
||||
rm -rf "/usr/share/openclash/ui/" >/dev/null 2>&1
|
||||
cp -rf "/tmp/openclash_ui/." "/usr/share/openclash/ui/" >/dev/null 2>&1
|
||||
fi
|
||||
rm -rf "/tmp/openclash_ui/" >/dev/null 2>&1
|
||||
fi
|
||||
#pac
|
||||
|
||||
@@ -1,119 +1,442 @@
|
||||
module YAML
|
||||
class << self
|
||||
alias_method :load, :unsafe_load if YAML.respond_to? :unsafe_load
|
||||
alias_method :original_dump, :dump
|
||||
alias_method :original_load_file, :load_file
|
||||
end
|
||||
class << self
|
||||
alias_method :load, :unsafe_load if YAML.respond_to? :unsafe_load
|
||||
alias_method :original_dump, :dump
|
||||
alias_method :original_load_file, :load_file
|
||||
end
|
||||
|
||||
def self.LOG(info)
|
||||
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Info] " + "#{info}"
|
||||
end
|
||||
def self.LOG(info)
|
||||
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Info] " + "#{info}"
|
||||
end
|
||||
|
||||
def self.LOG_ERROR(info)
|
||||
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Error] " + "#{info}"
|
||||
end
|
||||
def self.LOG_ERROR(info)
|
||||
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Error] " + "#{info}"
|
||||
end
|
||||
|
||||
def self.LOG_WARN(info)
|
||||
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Warning] " + "#{info}"
|
||||
end
|
||||
def self.LOG_WARN(info)
|
||||
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Warning] " + "#{info}"
|
||||
end
|
||||
|
||||
def self.LOG_TIP(info)
|
||||
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Tip] " + "#{info}"
|
||||
end
|
||||
def self.LOG_TIP(info)
|
||||
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Tip] " + "#{info}"
|
||||
end
|
||||
|
||||
# Keep `short-id` as string before YAML parsing so leading zeros are preserved.
|
||||
# This is required for REALITY short-id values like `00000000`.
|
||||
def self.load_file(filename, *args, **kwargs)
|
||||
yaml_content = File.read(filename)
|
||||
processed_content = fix_short_id_quotes(yaml_content)
|
||||
# Keep `short-id` as string before YAML parsing so leading zeros are preserved.
|
||||
# This is required for REALITY short-id values like `00000000`.
|
||||
def self.load_file(filename, *args, **kwargs)
|
||||
yaml_content = File.read(filename)
|
||||
processed_content = fix_short_id_quotes(yaml_content)
|
||||
|
||||
if kwargs.empty?
|
||||
load(processed_content, *args)
|
||||
else
|
||||
load(processed_content, *args, **kwargs)
|
||||
end
|
||||
end
|
||||
if kwargs.empty?
|
||||
load(processed_content, *args)
|
||||
else
|
||||
load(processed_content, *args, **kwargs)
|
||||
end
|
||||
end
|
||||
|
||||
def self.dump(obj, io = nil, **options)
|
||||
begin
|
||||
if io.nil?
|
||||
yaml_content = original_dump(obj, **options)
|
||||
fix_short_id_quotes(yaml_content)
|
||||
elsif io.respond_to?(:write)
|
||||
require 'stringio'
|
||||
temp_io = StringIO.new
|
||||
original_dump(obj, temp_io, **options)
|
||||
yaml_content = temp_io.string
|
||||
processed_content = fix_short_id_quotes(yaml_content)
|
||||
io.write(processed_content)
|
||||
io
|
||||
else
|
||||
yaml_content = original_dump(obj, io, **options)
|
||||
fix_short_id_quotes(yaml_content)
|
||||
end
|
||||
rescue => e
|
||||
LOG_ERROR("Write file failed:【%s】" % [e.message])
|
||||
nil
|
||||
end
|
||||
end
|
||||
def self.dump(obj, io = nil, **options)
|
||||
begin
|
||||
if io.nil?
|
||||
yaml_content = original_dump(obj, **options)
|
||||
fix_short_id_quotes(yaml_content)
|
||||
elsif io.respond_to?(:write)
|
||||
require 'stringio'
|
||||
temp_io = StringIO.new
|
||||
original_dump(obj, temp_io, **options)
|
||||
yaml_content = temp_io.string
|
||||
processed_content = fix_short_id_quotes(yaml_content)
|
||||
io.write(processed_content)
|
||||
io
|
||||
else
|
||||
yaml_content = original_dump(obj, io, **options)
|
||||
fix_short_id_quotes(yaml_content)
|
||||
end
|
||||
rescue => e
|
||||
LOG_ERROR("Write file failed:【%s】" % [e.message])
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
SHORT_ID_REGEX = /^(\s*)short-id:\s*(.*)$/
|
||||
LIST_ITEM_REGEX = /^(\s*)-\s*(.*)$/
|
||||
KEY_REGEX = /^(\s*)([a-zA-Z0-9_-]+):\s*(.*)$/
|
||||
QUOTED_VALUE_REGEX = /^["'].*["']$/
|
||||
SHORT_ID_REGEX = /^(\s*)short-id:\s*(.*)$/
|
||||
LIST_ITEM_REGEX = /^(\s*)-\s*(.*)$/
|
||||
KEY_REGEX = /^(\s*)([a-zA-Z0-9_-]+):\s*(.*)$/
|
||||
QUOTED_VALUE_REGEX = /^(["'].*["']|null)$/
|
||||
|
||||
# Inline map support, e.g. reality-opts: { ..., short-id: 00000000 }
|
||||
INLINE_SHORT_ID_REGEX = /(short-id:\s*)(?!["'\[])([^\s,"'{}\[\]\n\r]+)(?=\s*(?:[,}\]\n\r]|$))/m.freeze
|
||||
# Inline map support, e.g. reality-opts: { ..., short-id: 00000000 }
|
||||
INLINE_SHORT_ID_REGEX = /(short-id:\s*)(?!["'\[]|null)([^\s,"'{}\[\]\n\r]+)(?=\s*(?:[,}\]\n\r]|$))/m.freeze
|
||||
|
||||
def self.fix_short_id_quotes(yaml_content)
|
||||
return yaml_content unless yaml_content.include?('short-id:')
|
||||
def self.fix_short_id_quotes(yaml_content)
|
||||
return yaml_content unless yaml_content.include?('short-id:')
|
||||
|
||||
begin
|
||||
# First, normalize inline-map style unquoted short-id.
|
||||
processed = yaml_content.gsub(INLINE_SHORT_ID_REGEX) do
|
||||
"#{$1}\"#{$2}\""
|
||||
end
|
||||
begin
|
||||
# First, normalize inline-map style unquoted short-id.
|
||||
processed = yaml_content.gsub(INLINE_SHORT_ID_REGEX) do
|
||||
"#{$1}\"#{$2}\""
|
||||
end
|
||||
|
||||
lines = processed.lines
|
||||
short_id_indices = lines.each_index.select { |i| lines[i] =~ SHORT_ID_REGEX }
|
||||
short_id_indices.each do |short_id_index|
|
||||
line = lines[short_id_index]
|
||||
if line =~ SHORT_ID_REGEX
|
||||
indent = $1
|
||||
value = $2.strip
|
||||
if value.empty?
|
||||
(short_id_index + 1...lines.size).each do |i|
|
||||
line = lines[i]
|
||||
next if line.strip.empty?
|
||||
if line[/^\s*/].length <= short_id_indent_len
|
||||
break
|
||||
end
|
||||
if line =~ LIST_ITEM_REGEX
|
||||
indent = $1
|
||||
value = $2.strip
|
||||
if value =~ KEY_REGEX
|
||||
break
|
||||
end
|
||||
if value !~ QUOTED_VALUE_REGEX
|
||||
lines[i] = "#{indent}- \"#{value}\"\n"
|
||||
end
|
||||
elsif line =~ KEY_REGEX
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
if value !~ QUOTED_VALUE_REGEX
|
||||
lines[short_id_index] = "#{indent}short-id: \"#{value}\"\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
lines.join
|
||||
rescue => e
|
||||
LOG_ERROR("Fix short-id values type failed:【%s】" % [e.message])
|
||||
yaml_content
|
||||
end
|
||||
end
|
||||
end
|
||||
lines = processed.lines
|
||||
short_id_indices = lines.each_index.select { |i| lines[i] =~ SHORT_ID_REGEX }
|
||||
short_id_indices.each do |short_id_index|
|
||||
line = lines[short_id_index]
|
||||
if line =~ SHORT_ID_REGEX
|
||||
indent = $1
|
||||
value = $2.strip
|
||||
if value.empty?
|
||||
(short_id_index + 1...lines.size).each do |i|
|
||||
line = lines[i]
|
||||
next if line.strip.empty?
|
||||
if line[/^\s*/].length <= indent.length
|
||||
break
|
||||
end
|
||||
if line =~ LIST_ITEM_REGEX
|
||||
indent = $1
|
||||
value = $2.strip
|
||||
if value =~ KEY_REGEX
|
||||
break
|
||||
end
|
||||
if value !~ QUOTED_VALUE_REGEX
|
||||
lines[i] = "#{indent}- \"#{value}\"\n"
|
||||
end
|
||||
elsif line =~ KEY_REGEX
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
if value !~ QUOTED_VALUE_REGEX
|
||||
lines[short_id_index] = "#{indent}short-id: \"#{value}\"\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
lines.join
|
||||
rescue => e
|
||||
LOG_ERROR("Fix short-id values type failed:【%s】" % [e.message])
|
||||
yaml_content
|
||||
end
|
||||
end
|
||||
|
||||
def self.overwrite(base, override)
|
||||
return override if base.nil?
|
||||
return base if override.nil?
|
||||
|
||||
current_key = nil
|
||||
current_operation = nil
|
||||
|
||||
begin
|
||||
case override
|
||||
when Hash
|
||||
result = base.is_a?(Hash) ? base.dup : {}
|
||||
|
||||
override.each do |key, value|
|
||||
current_key = key
|
||||
processed_key, operation = parse_key(key)
|
||||
current_operation = operation
|
||||
|
||||
applied = apply_operation(result[processed_key], value, operation)
|
||||
if applied.equal?(DELETED_SENTINEL)
|
||||
result.delete(processed_key)
|
||||
else
|
||||
result[processed_key] = applied
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
else
|
||||
override
|
||||
end
|
||||
rescue => e
|
||||
LOG_ERROR("YAML overwrite failed:【key: %s, operation: %s, error: %s】" % [current_key, current_operation, e.message])
|
||||
base
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.parse_key(key)
|
||||
key_str = key.to_s
|
||||
|
||||
# +<key>
|
||||
if key_str.start_with?('+<') && key_str.include?('>')
|
||||
close_idx = key_str.index('>')
|
||||
inner_key = key_str[2...close_idx]
|
||||
return inner_key, :prepend_array
|
||||
end
|
||||
|
||||
# <key>suffix
|
||||
if key_str.start_with?('<') && key_str.include?('>')
|
||||
close_idx = key_str.index('>')
|
||||
inner_key = key_str[1...close_idx]
|
||||
suffix = key_str[(close_idx + 1)..-1]
|
||||
return inner_key, determine_operation(suffix)
|
||||
end
|
||||
|
||||
# 前缀 +key
|
||||
if key_str.start_with?('+')
|
||||
return key_str[1..-1], :prepend_array
|
||||
end
|
||||
|
||||
# 尾部(支持 +, !, *, -)
|
||||
if key_str =~ /^(.*?)([+!*\-])$/
|
||||
return Regexp.last_match(1), determine_operation(Regexp.last_match(2))
|
||||
end
|
||||
|
||||
[key_str, :merge]
|
||||
end
|
||||
|
||||
def self.determine_operation(suffix)
|
||||
case suffix
|
||||
when '+'
|
||||
:append_array
|
||||
when '-'
|
||||
:delete
|
||||
when '!'
|
||||
:force_overwrite
|
||||
when '*'
|
||||
:batch_update
|
||||
else
|
||||
:merge
|
||||
end
|
||||
end
|
||||
|
||||
def self.match_value(target, condition)
|
||||
return false if target.nil? || condition.nil?
|
||||
|
||||
begin
|
||||
if condition.is_a?(String) && condition.start_with?('/') && condition.end_with?('/')
|
||||
pattern = condition[1...-1]
|
||||
regexp = Regexp.new(pattern)
|
||||
if target.is_a?(Array)
|
||||
target.any? { |item| item.to_s =~ regexp }
|
||||
else
|
||||
target.to_s =~ regexp
|
||||
end
|
||||
elsif condition.is_a?(Array) && target.is_a?(Array)
|
||||
condition.all? { |c| target.include?(c) }
|
||||
else
|
||||
target == condition
|
||||
end
|
||||
rescue => e
|
||||
LOG_ERROR("YAML overwrite failed:【(match value) => target: %s, condition: %s, error: %s】" % [target, condition, e.message])
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def self.deep_dup(obj)
|
||||
case obj
|
||||
when Array
|
||||
obj.map { |x| deep_dup(x) }
|
||||
when Hash
|
||||
obj.transform_values { |v| deep_dup(v) }
|
||||
else
|
||||
obj.dup rescue obj
|
||||
end
|
||||
end
|
||||
|
||||
def self.merge_hash(base, value, prepend: false)
|
||||
if prepend
|
||||
result = {}
|
||||
|
||||
value.each do |k, v|
|
||||
if base.key?(k)
|
||||
result[k] = apply_operation(base[k], v, :merge)
|
||||
else
|
||||
result[k] = deep_dup(v)
|
||||
end
|
||||
end
|
||||
|
||||
base.each do |k, v|
|
||||
result[k] = deep_dup(v) unless result.key?(k)
|
||||
end
|
||||
|
||||
result
|
||||
else
|
||||
result = deep_dup(base)
|
||||
|
||||
value.each do |k, v|
|
||||
if result.key?(k)
|
||||
result[k] = apply_operation(result[k], v, :merge)
|
||||
else
|
||||
result[k] = deep_dup(v)
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def self.delete_from_hash(base, value)
|
||||
result = deep_dup(base)
|
||||
|
||||
case value
|
||||
when Array
|
||||
value.each { |k| result.delete(k) }
|
||||
when Hash
|
||||
value.each do |k, v|
|
||||
if v.nil? || v == true
|
||||
result.delete(k)
|
||||
elsif result[k].is_a?(Hash) && v.is_a?(Hash)
|
||||
nested = apply_operation(result[k], v, :delete)
|
||||
if nested.equal?(DELETED_SENTINEL)
|
||||
result.delete(k)
|
||||
else
|
||||
result[k] = nested
|
||||
end
|
||||
else
|
||||
result.delete(k)
|
||||
end
|
||||
end
|
||||
else
|
||||
result.delete(value)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
DELETED_SENTINEL = Object.new.freeze
|
||||
|
||||
def self.apply_operation(base, value, operation)
|
||||
case operation
|
||||
when :delete
|
||||
if base.is_a?(Array) && value.is_a?(Array)
|
||||
base - value
|
||||
elsif base.is_a?(Array) && !value.nil?
|
||||
base - [value]
|
||||
elsif base.is_a?(Hash)
|
||||
delete_from_hash(base, value)
|
||||
else
|
||||
DELETED_SENTINEL
|
||||
end
|
||||
when :force_overwrite
|
||||
deep_dup(value)
|
||||
when :prepend_array
|
||||
if base.is_a?(Array) && value.is_a?(Array)
|
||||
(deep_dup(value) + base).uniq
|
||||
elsif base.is_a?(Hash) && value.is_a?(Hash)
|
||||
merge_hash(base, value, prepend: true)
|
||||
else
|
||||
deep_dup(value)
|
||||
end
|
||||
when :append_array
|
||||
if base.is_a?(Array) && value.is_a?(Array)
|
||||
base_dup = base.dup
|
||||
deep_dup(value).each { |v| base_dup.delete(v) }
|
||||
base_dup + deep_dup(value)
|
||||
elsif base.is_a?(Hash) && value.is_a?(Hash)
|
||||
merge_hash(base, value, prepend: false)
|
||||
else
|
||||
deep_dup(value)
|
||||
end
|
||||
when :batch_update
|
||||
batch_update_items(base, value)
|
||||
when :merge
|
||||
if base.is_a?(Hash) && value.is_a?(Hash)
|
||||
overwrite(base, value)
|
||||
elsif value.nil?
|
||||
base
|
||||
else
|
||||
deep_dup(value)
|
||||
end
|
||||
else
|
||||
deep_dup(value)
|
||||
end
|
||||
end
|
||||
|
||||
def self.apply_set_fields(item, set_values)
|
||||
keys_to_delete = []
|
||||
|
||||
set_values.each do |k, v|
|
||||
processed_key, operation = parse_key(k)
|
||||
result = apply_operation(item[processed_key], v, operation)
|
||||
if result.equal?(DELETED_SENTINEL)
|
||||
keys_to_delete << processed_key
|
||||
else
|
||||
item[processed_key] = result
|
||||
end
|
||||
end
|
||||
|
||||
keys_to_delete.each { |k| item.delete(k) }
|
||||
end
|
||||
|
||||
def self.match_item(item, where_conditions, key = nil)
|
||||
where_conditions.all? do |k, v|
|
||||
if k == 'key' && !key.nil?
|
||||
match_value(key, v)
|
||||
elsif item.is_a?(Hash)
|
||||
match_value(item[k] || item[k.to_s], v)
|
||||
elsif item.is_a?(String) && k == 'value'
|
||||
match_value(item, v)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.batch_update_items(collection, update_spec)
|
||||
return collection unless update_spec.is_a?(Hash)
|
||||
|
||||
begin
|
||||
where_conditions = update_spec['where'] || {}
|
||||
set_values = update_spec['set'] || {}
|
||||
|
||||
if collection.is_a?(Array)
|
||||
result = collection.dup
|
||||
delete_indices = []
|
||||
|
||||
result.each_with_index do |item, index|
|
||||
match = match_item(item, where_conditions)
|
||||
|
||||
if match
|
||||
if item.is_a?(Hash)
|
||||
apply_set_fields(item, set_values)
|
||||
elsif item.is_a?(String) && set_values.key?('value')
|
||||
new_value = set_values['value']
|
||||
if new_value.nil?
|
||||
delete_indices << index
|
||||
else
|
||||
result[index] = deep_dup(new_value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
delete_indices.reverse_each { |i| result.delete_at(i) }
|
||||
result
|
||||
elsif collection.is_a?(Hash)
|
||||
if where_conditions.any? { |k, _| k != 'key' } &&
|
||||
match_item(collection, where_conditions)
|
||||
result = collection.dup
|
||||
apply_set_fields(result, set_values)
|
||||
result
|
||||
else
|
||||
result = collection.dup
|
||||
keys_to_delete = []
|
||||
|
||||
result.each do |key, value|
|
||||
next unless value.is_a?(Hash)
|
||||
match = match_item(value, where_conditions, key)
|
||||
|
||||
if match
|
||||
if set_values.key?('key-') || (set_values.key?('key') && set_values['key'].nil?)
|
||||
keys_to_delete << key
|
||||
else
|
||||
apply_set_fields(value, set_values)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
keys_to_delete.each { |k| result.delete(k) }
|
||||
result
|
||||
end
|
||||
elsif collection.nil?
|
||||
nil
|
||||
else
|
||||
collection
|
||||
end
|
||||
rescue => e
|
||||
LOG_ERROR("YAML overwrite failed:【(batch update) => update_spec: %s, error: %s】" % [update_spec, e.message])
|
||||
collection
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -16,7 +16,6 @@ del_lock() {
|
||||
|
||||
set_lock
|
||||
inc_job_counter
|
||||
restart=0
|
||||
|
||||
if [ -n "$1" ] && [ "$1" != "one_key_update" ]; then
|
||||
/usr/share/openclash/openclash_version.sh "$1" 2>/dev/null
|
||||
@@ -29,7 +28,7 @@ fi
|
||||
if [ ! -f "/tmp/openclash_last_version" ]; then
|
||||
LOG_ERROR "Failed to get version information, please try again later..."
|
||||
SLOG_CLEAN
|
||||
dec_job_counter_and_restart "$restart"
|
||||
dec_job_counter_and_restart "0"
|
||||
del_lock
|
||||
exit 0
|
||||
fi
|
||||
@@ -69,14 +68,12 @@ if [ "$1" = "one_key_update" ]; then
|
||||
LOG_TIP "If the download fails, try setting the CDN in Overwrite Settings - General Settings - Github Address Modify Options"
|
||||
fi
|
||||
if [ -n "$2" ]; then
|
||||
/usr/share/openclash/openclash_core.sh "Meta" "$1" "$2" >/dev/null 2>&1 &
|
||||
/usr/share/openclash/openclash_core.sh "Meta" "$1" "$2" >/dev/null 2>&1
|
||||
github_address_mod="$2"
|
||||
else
|
||||
/usr/share/openclash/openclash_core.sh "Meta" "$1" >/dev/null 2>&1 &
|
||||
/usr/share/openclash/openclash_core.sh "Meta" "$1" >/dev/null 2>&1
|
||||
github_address_mod=0
|
||||
fi
|
||||
|
||||
wait
|
||||
else
|
||||
if [ "$github_address_mod" = "0" ]; then
|
||||
LOG_TIP "If the download fails, try setting the CDN in Overwrite Settings - General Settings - Github Address Modify Options"
|
||||
@@ -175,9 +172,8 @@ if [ -n "$OP_CV" ] && [ -n "$OP_LV" ] && version_compare "$OP_CV" "$OP_LV" && [
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
LOG_ERROR "【OpenClash - v$LAST_VER】Pre update test failed after 3 attempts, the file is saved in /tmp/openclash.apk, please try to update manually with【apk add -q --force-overwrite --clean-protected --allow-untrusted /tmp/openclash.apk】"
|
||||
fi
|
||||
|
||||
SLOG_CLEAN
|
||||
dec_job_counter_and_restart "$restart"
|
||||
dec_job_counter_and_restart "0"
|
||||
del_lock
|
||||
exit 0
|
||||
fi
|
||||
@@ -191,8 +187,8 @@ if [ -n "$OP_CV" ] && [ -n "$OP_LV" ] && version_compare "$OP_CV" "$OP_LV" && [
|
||||
LOG_ERROR "【OpenClash - v$LAST_VER】Download Failed after 3 attempts, please check the network or try again later!"
|
||||
rm -rf /tmp/openclash.ipk >/dev/null 2>&1
|
||||
rm -rf /tmp/openclash.apk >/dev/null 2>&1
|
||||
dec_job_counter_and_restart "0"
|
||||
SLOG_CLEAN
|
||||
dec_job_counter_and_restart "$restart"
|
||||
del_lock
|
||||
exit 0
|
||||
fi
|
||||
@@ -200,9 +196,8 @@ if [ -n "$OP_CV" ] && [ -n "$OP_LV" ] && version_compare "$OP_CV" "$OP_LV" && [
|
||||
done
|
||||
cat > /tmp/openclash_update.sh <<"EOF"
|
||||
#!/bin/sh
|
||||
START_LOG="/tmp/openclash_start.log"
|
||||
LOG_FILE="/tmp/openclash.log"
|
||||
LOGTIME=$(date "+%Y-%m-%d %H:%M:%S")
|
||||
. /usr/share/openclash/log.sh
|
||||
. /usr/share/openclash/openclash_ps.sh
|
||||
|
||||
UPDATE_LOCK="/tmp/lock/openclash_update_install.lock"
|
||||
mkdir -p /tmp/lock
|
||||
@@ -224,27 +219,6 @@ fi
|
||||
|
||||
trap 'del_update_lock; exit' INT TERM EXIT
|
||||
|
||||
LOG_ERROR()
|
||||
{
|
||||
if [ -n "${1}" ]; then
|
||||
echo -e "${1}" > $START_LOG
|
||||
echo -e "${LOGTIME} [Error] ${1}" >> $LOG_FILE
|
||||
fi
|
||||
}
|
||||
|
||||
LOG_TIP()
|
||||
{
|
||||
if [ -n "${1}" ]; then
|
||||
echo -e "${1}" > $START_LOG
|
||||
echo -e "${LOGTIME} [Tip] ${1}" >> $LOG_FILE
|
||||
fi
|
||||
}
|
||||
|
||||
SLOG_CLEAN()
|
||||
{
|
||||
echo "" > $START_LOG
|
||||
}
|
||||
|
||||
check_install_success()
|
||||
{
|
||||
local target_version="$1"
|
||||
@@ -357,9 +331,9 @@ else
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
LOG_ERROR "OpenClash update failed after 3 attempts, the file is saved in /tmp/openclash.apk, please try to update manually with【apk add -q --force-overwrite --clean-protected --allow-untrusted /tmp/openclash.apk】"
|
||||
fi
|
||||
SLOG_CLEAN
|
||||
fi
|
||||
|
||||
dec_job_counter_and_restart "0"
|
||||
SLOG_CLEAN
|
||||
del_update_lock
|
||||
EOF
|
||||
chmod 4755 /tmp/openclash_update.sh
|
||||
@@ -367,6 +341,8 @@ EOF
|
||||
if [ ! -f "/tmp/openclash_update.sh" ] || [ ! -s "/tmp/openclash_update.sh" ] || [ ! -x "/tmp/openclash_update.sh" ]; then
|
||||
LOG_ERROR "Failed to create update script!"
|
||||
rm -rf /tmp/openclash_update.sh
|
||||
dec_job_counter_and_restart "0"
|
||||
SLOG_CLEAN
|
||||
del_lock
|
||||
exit 1
|
||||
fi
|
||||
@@ -405,8 +381,8 @@ else
|
||||
else
|
||||
LOG_TIP "OpenClash has not been updated, stop continuing!"
|
||||
fi
|
||||
dec_job_counter_and_restart "0"
|
||||
SLOG_CLEAN
|
||||
dec_job_counter_and_restart "$restart"
|
||||
fi
|
||||
|
||||
del_lock
|
||||
|
||||
@@ -5,20 +5,10 @@
|
||||
. /usr/share/openclash/uci.sh
|
||||
|
||||
LOG_FILE="/tmp/openclash.log"
|
||||
CONFIG_FILE="/etc/openclash/$(uci_get_config "config_path" |awk -F '/' '{print $5}' 2>/dev/null)"
|
||||
ipv6_enable=$(uci_get_config "ipv6_enable" || echo 0)
|
||||
enable_redirect_dns=$(uci_get_config "enable_redirect_dns")
|
||||
dns_port=$(uci_get_config "dns_port")
|
||||
disable_masq_cache=$(uci_get_config "disable_masq_cache")
|
||||
cfg_update_interval=$(uci_get_config "config_update_interval" || echo 60)
|
||||
log_size=$(uci_get_config "log_size" || echo 1024)
|
||||
router_self_proxy=$(uci_get_config "router_self_proxy" || echo 1)
|
||||
stream_auto_select_interval=$(uci_get_config "stream_auto_select_interval" || echo 30)
|
||||
skip_proxy_address=$(uci_get_config "skip_proxy_address" || echo 0)
|
||||
CFG_UPDATE_INT=1
|
||||
CFG_UPDATE_INT=0
|
||||
SKIP_PROXY_ADDRESS=1
|
||||
SKIP_PROXY_ADDRESS_INTERVAL=30
|
||||
STREAM_AUTO_SELECT=1
|
||||
STREAM_AUTO_SELECT=0
|
||||
FIREWALL_RELOAD=0
|
||||
MAX_FIREWALL_RELOAD=3
|
||||
FW4=$(command -v fw4)
|
||||
@@ -155,11 +145,20 @@ end" 2>/dev/null >> $LOG_FILE
|
||||
|
||||
while :;
|
||||
do
|
||||
CONFIG_FILE="/etc/openclash/$(uci_get_config "config_path" |awk -F '/' '{print $5}' 2>/dev/null)"
|
||||
ipv6_enable=$(uci_get_config "ipv6_enable" || echo 0)
|
||||
enable_redirect_dns=$(uci_get_config "enable_redirect_dns")
|
||||
dns_port=$(uci_get_config "dns_port")
|
||||
disable_masq_cache=$(uci_get_config "disable_masq_cache")
|
||||
log_size=$(uci_get_config "log_size" || echo 1024)
|
||||
router_self_proxy=$(uci_get_config "router_self_proxy" || echo 1)
|
||||
skip_proxy_address=$(uci_get_config "skip_proxy_address" || echo 0)
|
||||
|
||||
cfg_update=$(uci_get_config "auto_update")
|
||||
cfg_update_mode=$(uci_get_config "config_auto_update_mode")
|
||||
cfg_update_interval_now=$(uci_get_config "config_update_interval" || echo 60)
|
||||
cfg_update_interval=$(uci_get_config "config_update_interval" || echo 60)
|
||||
stream_auto_select=$(uci_get_config "stream_auto_select" || echo 0)
|
||||
stream_auto_select_interval_now=$(uci_get_config "stream_auto_select_interval" || echo 30)
|
||||
stream_auto_select_interval=$(uci_get_config "stream_auto_select_interval" || echo 30)
|
||||
stream_auto_select_netflix=$(uci_get_config "stream_auto_select_netflix" || echo 0)
|
||||
stream_auto_select_disney=$(uci_get_config "stream_auto_select_disney" || echo 0)
|
||||
stream_auto_select_hbo_max=$(uci_get_config "stream_auto_select_hbo_max" || echo 0)
|
||||
@@ -362,18 +361,16 @@ fi
|
||||
|
||||
## 配置文件循环更新
|
||||
if [ "$cfg_update" -eq 1 ] && [ "$cfg_update_mode" -eq 1 ]; then
|
||||
[ "$cfg_update_interval" -ne "$cfg_update_interval_now" ] && CFG_UPDATE_INT=0 && cfg_update_interval="$cfg_update_interval_now"
|
||||
if [ "$CFG_UPDATE_INT" -ne 0 ]; then
|
||||
[ "$(expr "$CFG_UPDATE_INT" % "$cfg_update_interval_now")" -eq 0 ] && /usr/share/openclash/openclash.sh
|
||||
[ "$(expr "$CFG_UPDATE_INT" % "$cfg_update_interval")" -eq 0 ] && /usr/share/openclash/openclash.sh
|
||||
fi
|
||||
CFG_UPDATE_INT=$(expr "$CFG_UPDATE_INT" + 1)
|
||||
fi
|
||||
|
||||
##STREAMING_UNLOCK_CHECK
|
||||
if [ "$stream_auto_select" -eq 1 ] && [ "$router_self_proxy" -eq 1 ]; then
|
||||
[ "$stream_auto_select_interval" -ne "$stream_auto_select_interval_now" ] && STREAM_AUTO_SELECT=1 && stream_auto_select_interval="$stream_auto_select_interval_now"
|
||||
if [ "$STREAM_AUTO_SELECT" -ne 0 ]; then
|
||||
if [ "$(expr "$STREAM_AUTO_SELECT" % "$stream_auto_select_interval_now")" -eq 0 ] || [ "$STREAM_AUTO_SELECT" -eq 1 ]; then
|
||||
if [ "$(expr "$STREAM_AUTO_SELECT" % "$stream_auto_select_interval")" -eq 0 ] || [ "$STREAM_AUTO_SELECT" -eq 1 ]; then
|
||||
if [ "$stream_auto_select_netflix" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Proxy For Netflix Unlock..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Netflix" >> $LOG_FILE
|
||||
|
||||
@@ -672,6 +672,7 @@ proxies: # socks5
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
# grpc-user-agent: "grpc-go/1.36.0"
|
||||
# ping-interval: 0 # 默认关闭,单位为秒
|
||||
# ip-version: ipv4
|
||||
|
||||
# vless
|
||||
@@ -762,6 +763,7 @@ proxies: # socks5
|
||||
grpc-opts:
|
||||
grpc-service-name: "grpc"
|
||||
# grpc-user-agent: "grpc-go/1.36.0"
|
||||
# ping-interval: 0 # 默认关闭,单位为秒
|
||||
|
||||
reality-opts:
|
||||
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
|
||||
@@ -790,6 +792,69 @@ proxies: # socks5
|
||||
# v2ray-http-upgrade: false
|
||||
# v2ray-http-upgrade-fast-open: false
|
||||
|
||||
- name: "vless-xhttp"
|
||||
type: vless
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
udp: true
|
||||
tls: true
|
||||
network: xhttp
|
||||
alpn:
|
||||
- h2
|
||||
# ech-opts: ...
|
||||
# reality-opts: ...
|
||||
# skip-cert-verify: false
|
||||
# fingerprint: ...
|
||||
# certificate: ...
|
||||
# private-key: ...
|
||||
servername: xxx.com
|
||||
client-fingerprint: chrome
|
||||
encryption: ""
|
||||
xhttp-opts:
|
||||
path: "/"
|
||||
host: xxx.com
|
||||
# mode: "stream-one" # Available: "stream-one", "stream-up" or "packet-up"
|
||||
# headers:
|
||||
# X-Forwarded-For: ""
|
||||
# no-grpc-header: false
|
||||
# x-padding-bytes: "100-1000"
|
||||
# reuse-settings: # aka XMUX
|
||||
# max-connections: "16-32"
|
||||
# max-concurrency: "0"
|
||||
# c-max-reuse-times: "0"
|
||||
# h-max-request-times: "600-900"
|
||||
# h-max-reusable-secs: "1800-3000"
|
||||
# download-settings:
|
||||
# ## xhttp part
|
||||
# path: "/"
|
||||
# host: xxx.com
|
||||
# headers:
|
||||
# X-Forwarded-For: ""
|
||||
# no-grpc-header: false
|
||||
# x-padding-bytes: "100-1000"
|
||||
# reuse-settings: # aka XMUX
|
||||
# max-connections: "16-32"
|
||||
# max-concurrency: "0"
|
||||
# c-max-reuse-times: "0"
|
||||
# h-max-request-times: "600-900"
|
||||
# h-max-reusable-secs: "1800-3000"
|
||||
# ## proxy part
|
||||
# server: server
|
||||
# port: 443
|
||||
# tls: true
|
||||
# alpn:
|
||||
# - h2
|
||||
# ech-opts: ...
|
||||
# reality-opts: ...
|
||||
# skip-cert-verify: false
|
||||
# fingerprint: ...
|
||||
# certificate: ...
|
||||
# private-key: ...
|
||||
# servername: xxx.com
|
||||
# client-fingerprint: chrome
|
||||
|
||||
|
||||
# Trojan
|
||||
- name: "trojan"
|
||||
type: trojan
|
||||
@@ -833,6 +898,7 @@ proxies: # socks5
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
# grpc-user-agent: "grpc-go/1.36.0"
|
||||
# ping-interval: 0 # 默认关闭,单位为秒
|
||||
|
||||
- name: trojan-ws
|
||||
server: server
|
||||
@@ -1080,6 +1146,8 @@ proxies: # socks5
|
||||
# multiplexing: MULTIPLEXING_LOW
|
||||
# 如果想开启 0-RTT 握手,请设置为 HANDSHAKE_NO_WAIT,否则请设置为 HANDSHAKE_STANDARD。默认值为 HANDSHAKE_STANDARD
|
||||
# handshake-mode: HANDSHAKE_STANDARD
|
||||
# 一个 base64 字符串用于微调网络行为
|
||||
# traffic-pattern: ""
|
||||
|
||||
# sudoku
|
||||
- name: sudoku
|
||||
@@ -1090,15 +1158,17 @@ proxies: # socks5
|
||||
aead-method: chacha20-poly1305 # 可选:chacha20-poly1305、aes-128-gcm、none(不建议;且 enable-pure-downlink=false 时不可用)
|
||||
padding-min: 2 # 最小填充率(0-100)
|
||||
padding-max: 7 # 最大填充率(0-100,必须 >= padding-min)
|
||||
table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3
|
||||
# custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v,可随意组合。启用此处则需配置`table-type`为`prefer_entropy`
|
||||
# custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选,自定义字节布局列表(x/v/p),用于 xvp 模式轮换;非空时覆盖 custom-table
|
||||
http-mask: true # 是否启用http掩码
|
||||
# http-mask-mode: legacy # 可选:legacy(默认)、stream(split-stream)、poll、auto(先 stream 再 poll);stream/poll/auto 支持走 CDN/反代
|
||||
# http-mask-tls: true # 可选:仅在 http-mask-mode 为 stream/poll/auto 时生效;true 强制 https;false 强制 http(不会根据端口自动推断)
|
||||
# http-mask-host: "" # 可选:覆盖 Host/SNI(支持 example.com 或 example.com:443);仅在 http-mask-mode 为 stream/poll/auto 时生效
|
||||
# path-root: "" # 可选:HTTP 隧道端点一级路径前缀(双方需一致),例如 "aabbcc" 或 "/aabbcc/" => /aabbcc/session、/aabbcc/stream、/aabbcc/api/v1/upload
|
||||
# http-mask-multiplex: off # 可选:off(默认)、auto(复用底层 HTTP 连接,减少建链 RTT)、on(Sudoku mux 单隧道多目标;仅在 http-mask-mode=stream/poll/auto 生效)
|
||||
table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy、up_ascii_down_entropy、up_entropy_down_ascii
|
||||
# custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v,可随意组合;只对 entropy 方向生效
|
||||
# custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选,自定义字节布局列表(x/v/p),非空时覆盖 custom-table
|
||||
# 推荐:使用 httpmask 对象统一管理 HTTPMask 相关字段:
|
||||
httpmask:
|
||||
disable: false # true 禁用所有 HTTP 伪装/隧道
|
||||
mode: legacy # 可选:legacy(默认)、stream(split-stream)、poll、auto(先 stream 再 poll)、ws(WebSocket 隧道)
|
||||
# tls: true # 可选:按需开启 HTTPS/WSS
|
||||
# host: "" # 可选:覆盖 Host/SNI(支持 example.com 或 example.com:443);仅在 mode 为 stream/poll/auto/ws 时生效
|
||||
# path-root: "" # 可选:HTTP 隧道端点一级路径前缀(双方需一致),例如 "aabbcc" 或 "/aabbcc/" => /aabbcc/session、/aabbcc/stream、/aabbcc/api/v1/upload、/aabbcc/ws
|
||||
# multiplex: "off" # 可选字符串:off(默认)、auto(复用底层 HTTP 连接,减少建链 RTT)、on(Sudoku mux 单隧道多目标;仅在 mode=stream/poll/auto 生效;ws 强制 off)
|
||||
enable-pure-downlink: false # 可选:false=带宽优化下行(更快,要求 aead-method != none);true=纯 Sudoku 下行
|
||||
|
||||
# anytls
|
||||
@@ -1118,6 +1188,23 @@ proxies: # socks5
|
||||
# - http/1.1
|
||||
# skip-cert-verify: true
|
||||
|
||||
# trusttunnel
|
||||
- name: trusttunnel
|
||||
type: trusttunnel
|
||||
server: 1.2.3.4
|
||||
port: 443
|
||||
username: username
|
||||
password: password
|
||||
# client-fingerprint: chrome
|
||||
health-check: true
|
||||
udp: true
|
||||
# sni: "example.com"
|
||||
# alpn:
|
||||
# - h2
|
||||
# skip-cert-verify: true
|
||||
# quic: true # 默认为false
|
||||
# congestion-controller: bbr
|
||||
|
||||
# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
|
||||
- name: "dns-out"
|
||||
type: dns
|
||||
@@ -1555,6 +1642,12 @@ listeners:
|
||||
flow: xtls-rprx-vision
|
||||
# ws-path: "/" # 如果不为空则开启 websocket 传输层
|
||||
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
|
||||
# xhttp-config: # 如果不为空则开启 xhttp 传输层
|
||||
# path: "/"
|
||||
# host: ""
|
||||
# mode: auto # Available: "stream-one", "stream-up" or "packet-up"
|
||||
# no-sse-header: false
|
||||
# sc-stream-up-server-secs: "20-80"
|
||||
# -------------------------
|
||||
# vless encryption服务端配置:
|
||||
# (原生外观 / 只 XOR 公钥 / 全随机数。1-RTT 每次下发随机 300 到 600 秒的 ticket 以便 0-RTT 复用 / 只允许 1-RTT)
|
||||
@@ -1631,6 +1724,8 @@ listeners:
|
||||
users:
|
||||
username1: password1
|
||||
username2: password2
|
||||
# 一个 base64 字符串用于微调网络行为
|
||||
# traffic-pattern: ""
|
||||
|
||||
- name: sudoku-in-1
|
||||
type: sudoku
|
||||
@@ -1640,14 +1735,18 @@ listeners:
|
||||
aead-method: chacha20-poly1305 # 可选:chacha20-poly1305、aes-128-gcm、none(不建议;且 enable-pure-downlink=false 时不可用)
|
||||
padding-min: 1 # 最小填充率(0-100)
|
||||
padding-max: 15 # 最大填充率(0-100,必须 >= padding-min)
|
||||
table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3
|
||||
# custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v,可随意组合。启用此处则需配置`table-type`为`prefer_entropy`
|
||||
# custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选,自定义字节布局列表(x/v/p),用于 xvp 模式轮换;非空时覆盖 custom-table
|
||||
table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy、up_ascii_down_entropy、up_entropy_down_ascii
|
||||
# custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v,可随意组合;只对 entropy 方向生效
|
||||
# custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选,自定义字节布局列表(x/v/p),用于多表轮换;非空时覆盖 custom-table
|
||||
handshake-timeout: 5 # 可选(秒)
|
||||
enable-pure-downlink: false # 可选:false=带宽优化下行(更快,要求 aead-method != none);true=纯 Sudoku 下行
|
||||
disable-http-mask: false # 可选:禁用 http 掩码/隧道(默认为 false)
|
||||
# http-mask-mode: legacy # 可选:legacy(默认)、stream(split-stream)、poll、auto(先 stream 再 poll);stream/poll/auto 支持走 CDN/反代
|
||||
# path-root: "" # 可选:HTTP 隧道端点一级路径前缀(双方需一致),例如 "aabbcc" 或 "/aabbcc/" => /aabbcc/session、/aabbcc/stream、/aabbcc/api/v1/upload
|
||||
# 推荐:使用 httpmask 对象统一管理 HTTPMask 相关字段:
|
||||
httpmask:
|
||||
disable: false # true 禁用所有 HTTP 伪装/隧道
|
||||
mode: legacy # 可选:legacy(默认)、stream(split-stream)、poll、auto(先 stream 再 poll)、ws(WebSocket 隧道)
|
||||
# path-root: "" # 可选:HTTP 隧道端点一级路径前缀(双方需一致),例如 "aabbcc" 或 "/aabbcc/" => /aabbcc/session、/aabbcc/stream、/aabbcc/api/v1/upload、/aabbcc/ws
|
||||
#
|
||||
# fallback: "127.0.0.1:80" # 可选:用于可连接请求的回落转发,可与其他服务共端口
|
||||
|
||||
|
||||
|
||||
@@ -1734,6 +1833,30 @@ listeners:
|
||||
# masquerade: http://127.0.0.1:8080 #作为反向代理
|
||||
# masquerade: https://127.0.0.1:8080 #作为反向代理
|
||||
|
||||
- name: trusttunnel-in-1
|
||||
type: trusttunnel
|
||||
port: 10821 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
users:
|
||||
- username: 1
|
||||
password: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
|
||||
certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
|
||||
private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
network: ["tcp", "udp"] # http2+http3
|
||||
congestion-controller: bbr
|
||||
# 下面两项为mTLS配置项,如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
|
||||
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
|
||||
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||
# dC5jb20AAA==
|
||||
# -----END ECH KEYS-----
|
||||
|
||||
# 注意,listeners中的tun仅提供给高级用户使用,普通用户应使用顶层配置中的tun
|
||||
- name: tun-in-1
|
||||
type: tun
|
||||
|
||||
@@ -185,7 +185,7 @@ set_disable_qtype()
|
||||
yml_dns_get()
|
||||
{
|
||||
local section="$1" regex='^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$'
|
||||
local enabled port type ip group dns_type dns_address interface specific_group node_resolve http3 ecs_subnet ecs_override disable_qtype_param
|
||||
local enabled port type ip group dns_type dns_address interface specific_group node_resolve direct_nameserver http3 skip_cert_verify ecs_subnet ecs_override disable_qtype_param disable_qtype disable_ipv4 disable_ipv6 disable_reuse
|
||||
|
||||
config_get_bool "enabled" "$section" "enabled" "1"
|
||||
[ "$enabled" = "0" ] && return
|
||||
@@ -480,7 +480,7 @@ begin
|
||||
else
|
||||
Value['dns']['enhanced-mode'] = 'fake-ip'
|
||||
Value['dns']['fake-ip-range'] = fake_ip_range
|
||||
if Value['dns']['ipv6']
|
||||
if Value['dns']['ipv6'] and fake_ip_range6 != '0'
|
||||
Value['dns']['fake-ip-range6'] = fake_ip_range6
|
||||
end
|
||||
end
|
||||
|
||||
@@ -592,7 +592,7 @@ ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
|
||||
};
|
||||
end;
|
||||
|
||||
#Mieru
|
||||
#Mieru
|
||||
if x['type'] == 'mieru' then
|
||||
threads << Thread.new{
|
||||
#port-range
|
||||
@@ -1138,6 +1138,19 @@ ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
|
||||
uci_commands << uci_set + 'reality_short_id=\"' + x['reality-opts']['short-id'].to_s + '\"'
|
||||
end
|
||||
end
|
||||
if x.key?('encryption') then
|
||||
uci_commands << uci_set + 'vless_encryption=\"' + x['encryption'].to_s + '\"'
|
||||
end
|
||||
elsif x['network'].to_s == 'xhttp'
|
||||
uci_commands << uci_set + 'obfs_vless=xhttp'
|
||||
if x.key?('xhttp-opts') then
|
||||
if x['xhttp-opts'].key?('path') then
|
||||
uci_commands << uci_set + 'xhttp_opts_path=\"' + x['xhttp-opts']['path'].to_s + '\"'
|
||||
end
|
||||
if x['xhttp-opts'].key?('host') then
|
||||
uci_commands << uci_set + 'xhttp_opts_host=\"' + x['xhttp-opts']['host'].to_s + '\"'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
};
|
||||
@@ -1424,6 +1437,132 @@ ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
|
||||
};
|
||||
end;
|
||||
|
||||
if x['type'] == 'masque' then
|
||||
threads << Thread.new{
|
||||
#private-key
|
||||
if x.key?('private-key') then
|
||||
uci_commands << uci_set + 'masque_private_key=\"' + x['private-key'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
threads << Thread.new{
|
||||
#public-key
|
||||
if x.key?('public-key') then
|
||||
uci_commands << uci_set + 'masque_public_key=\"' + x['public-key'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
threads << Thread.new{
|
||||
#ip
|
||||
if x.key?('ip') then
|
||||
uci_commands << uci_set + 'masque_ip=\"' + x['ip'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
threads << Thread.new{
|
||||
#ipv6
|
||||
if x.key?('ipv6') then
|
||||
uci_commands << uci_set + 'masque_ipv6=\"' + x['ipv6'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
threads << Thread.new{
|
||||
#mtu
|
||||
if x.key?('mtu') then
|
||||
uci_commands << uci_set + 'masque_mtu=\"' + x['mtu'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
threads << Thread.new{
|
||||
#remote-dns-resolve
|
||||
if x.key?('remote-dns-resolve') then
|
||||
uci_commands << uci_set + 'masque_remote_dns_resolve=\"' + x['remote-dns-resolve'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
threads << Thread.new{
|
||||
#dns
|
||||
if x.key?('dns') then
|
||||
dns = uci_del + 'dns >/dev/null 2>&1'
|
||||
system(dns)
|
||||
x['dns'].each{
|
||||
|x|
|
||||
uci_commands << uci_add + 'masque_dns=\"' + x.to_s + '\"'
|
||||
}
|
||||
end
|
||||
};
|
||||
end;
|
||||
|
||||
if x['type'] == 'trusttunnel' then
|
||||
threads << Thread.new{
|
||||
#username
|
||||
if x.key?('username') then
|
||||
uci_commands << uci_set + 'trusttunnel_username=\"' + x['username'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
threads << Thread.new{
|
||||
#password
|
||||
if x.key?('password') then
|
||||
uci_commands << uci_set + 'trusttunnel_password=\"' + x['password'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
threads << Thread.new{
|
||||
#health-check
|
||||
if x.key?('health-check') then
|
||||
uci_commands << uci_set + 'trusttunnel_health_check=\"' + x['health-check'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
threads << Thread.new{
|
||||
#quic
|
||||
if x.key?('quic') then
|
||||
uci_commands << uci_set + 'trusttunnel_quic=\"' + x['quic'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
threads << Thread.new{
|
||||
#congestion-controller
|
||||
if x.key?('congestion-controller') then
|
||||
uci_commands << uci_set + 'trusttunnel_congestion_controller=\"' + x['congestion-controller'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
#alpn
|
||||
threads << Thread.new{
|
||||
if x.key?('alpn') then
|
||||
alpn = uci_del + 'alpn >/dev/null 2>&1'
|
||||
system(alpn)
|
||||
x['alpn'].each{
|
||||
|x|
|
||||
uci_commands << uci_add + 'alpn=\"' + x.to_s + '\"'
|
||||
}
|
||||
end
|
||||
};
|
||||
|
||||
#sni
|
||||
threads << Thread.new{
|
||||
if x.key?('sni') then
|
||||
uci_commands << uci_set + 'sni=\"' + x['sni'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
#skip-cert-verify
|
||||
threads << Thread.new{
|
||||
if x.key?('skip-cert-verify') then
|
||||
uci_commands << uci_set + 'skip_cert_verify=\"' + x['skip-cert-verify'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
#client_fingerprint
|
||||
threads << Thread.new{
|
||||
if x.key?('client-fingerprint') then
|
||||
uci_commands << uci_set + 'client_fingerprint=\"' + x['client-fingerprint'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
end;
|
||||
|
||||
#加入策略组
|
||||
threads << Thread.new{
|
||||
#加入策略组
|
||||
|
||||
@@ -186,135 +186,6 @@ yml_servers_set()
|
||||
config_get "name" "$section" "name" ""
|
||||
config_get "server" "$section" "server" ""
|
||||
config_get "port" "$section" "port" ""
|
||||
config_get "dialer_proxy" "$section" "dialer_proxy" ""
|
||||
config_get "cipher" "$section" "cipher" ""
|
||||
config_get "cipher_ssr" "$section" "cipher_ssr" ""
|
||||
config_get "password" "$section" "password" ""
|
||||
config_get "securitys" "$section" "securitys" ""
|
||||
config_get "udp" "$section" "udp" ""
|
||||
config_get "obfs" "$section" "obfs" ""
|
||||
config_get "obfs_ssr" "$section" "obfs_ssr" ""
|
||||
config_get "obfs_param" "$section" "obfs_param" ""
|
||||
config_get "obfs_vmess" "$section" "obfs_vmess" ""
|
||||
config_get "obfs_trojan" "$section" "obfs_trojan" ""
|
||||
config_get "protocol" "$section" "protocol" ""
|
||||
config_get "protocol_param" "$section" "protocol_param" ""
|
||||
config_get "host" "$section" "host" ""
|
||||
config_get "mux" "$section" "mux" ""
|
||||
config_get "custom" "$section" "custom" ""
|
||||
config_get "tls" "$section" "tls" ""
|
||||
config_get "skip_cert_verify" "$section" "skip_cert_verify" ""
|
||||
config_get "path" "$section" "path" ""
|
||||
config_get "alterId" "$section" "alterId" ""
|
||||
config_get "uuid" "$section" "uuid" ""
|
||||
config_get "auth_name" "$section" "auth_name" ""
|
||||
config_get "auth_pass" "$section" "auth_pass" ""
|
||||
config_get "psk" "$section" "psk" ""
|
||||
config_get "obfs_snell" "$section" "obfs_snell" ""
|
||||
config_get "snell_version" "$section" "snell_version" ""
|
||||
config_get "sni" "$section" "sni" ""
|
||||
config_get "alpn" "$section" "alpn" ""
|
||||
config_get "http_path" "$section" "http_path" ""
|
||||
config_get "keep_alive" "$section" "keep_alive" ""
|
||||
config_get "servername" "$section" "servername" ""
|
||||
config_get "h2_path" "$section" "h2_path" ""
|
||||
config_get "h2_host" "$section" "h2_host" ""
|
||||
config_get "grpc_service_name" "$section" "grpc_service_name" ""
|
||||
config_get "ws_opts_path" "$section" "ws_opts_path" ""
|
||||
config_get "ws_opts_headers" "$section" "ws_opts_headers" ""
|
||||
config_get "max_early_data" "$section" "max_early_data" ""
|
||||
config_get "early_data_header_name" "$section" "early_data_header_name" ""
|
||||
config_get "trojan_ws_path" "$section" "trojan_ws_path" ""
|
||||
config_get "trojan_ws_headers" "$section" "trojan_ws_headers" ""
|
||||
config_get "interface_name" "$section" "interface_name" ""
|
||||
config_get "routing_mark" "$section" "routing_mark" ""
|
||||
config_get "obfs_vless" "$section" "obfs_vless" ""
|
||||
config_get "vless_flow" "$section" "vless_flow" ""
|
||||
config_get "http_headers" "$section" "http_headers" ""
|
||||
config_get "hysteria_protocol" "$section" "hysteria_protocol" ""
|
||||
config_get "hysteria2_protocol" "$section" "hysteria2_protocol" ""
|
||||
config_get "hysteria_up" "$section" "hysteria_up" ""
|
||||
config_get "hysteria_down" "$section" "hysteria_down" ""
|
||||
config_get "hysteria_alpn" "$section" "hysteria_alpn" ""
|
||||
config_get "hysteria_obfs" "$section" "hysteria_obfs" ""
|
||||
config_get "hysteria_auth" "$section" "hysteria_auth" ""
|
||||
config_get "hysteria_auth_str" "$section" "hysteria_auth_str" ""
|
||||
config_get "hysteria_ca" "$section" "hysteria_ca" ""
|
||||
config_get "hysteria_ca_str" "$section" "hysteria_ca_str" ""
|
||||
config_get "recv_window_conn" "$section" "recv_window_conn" ""
|
||||
config_get "recv_window" "$section" "recv_window" ""
|
||||
config_get "disable_mtu_discovery" "$section" "disable_mtu_discovery" ""
|
||||
config_get "initial_stream_receive_window" "$section" "initial_stream_receive_window" ""
|
||||
config_get "max_stream_receive_window" "$section" "max_stream_receive_window" ""
|
||||
config_get "initial_connection_receive_window" "$section" "initial_connection_receive_window" ""
|
||||
config_get "max_connection_receive_window" "$section" "max_connection_receive_window" ""
|
||||
config_get "xudp" "$section" "xudp" ""
|
||||
config_get "packet_encoding" "$section" "packet_encoding" ""
|
||||
config_get "global_padding" "$section" "global_padding" ""
|
||||
config_get "authenticated_length" "$section" "authenticated_length" ""
|
||||
config_get "wg_ip" "$section" "wg_ip" ""
|
||||
config_get "wg_ipv6" "$section" "wg_ipv6" ""
|
||||
config_get "private_key" "$section" "private_key" ""
|
||||
config_get "public_key" "$section" "public_key" ""
|
||||
config_get "preshared_key" "$section" "preshared_key" ""
|
||||
config_get "wg_dns" "$section" "wg_dns" ""
|
||||
config_get "public_key" "$section" "public_key" ""
|
||||
config_get "preshared_key" "$section" "preshared_key" ""
|
||||
config_get "wg_mtu" "$section" "wg_mtu" ""
|
||||
config_get "tc_ip" "$section" "tc_ip" ""
|
||||
config_get "tc_token" "$section" "tc_token" ""
|
||||
config_get "tc_uuid" "$section" "tc_uuid" ""
|
||||
config_get "tc_password" "$section" "tc_password" ""
|
||||
config_get "udp_relay_mode" "$section" "udp_relay_mode" ""
|
||||
config_get "congestion_controller" "$section" "congestion_controller" ""
|
||||
config_get "tc_alpn" "$section" "tc_alpn" ""
|
||||
config_get "disable_sni" "$section" "disable_sni" ""
|
||||
config_get "reduce_rtt" "$section" "reduce_rtt" ""
|
||||
config_get "heartbeat_interval" "$section" "heartbeat_interval" ""
|
||||
config_get "request_timeout" "$section" "request_timeout" ""
|
||||
config_get "max_udp_relay_packet_size" "$section" "max_udp_relay_packet_size" ""
|
||||
config_get "fast_open" "$section" "fast_open" ""
|
||||
config_get "fingerprint" "$section" "fingerprint" ""
|
||||
config_get "ports" "$section" "ports" ""
|
||||
config_get "hop_interval" "$section" "hop_interval" ""
|
||||
config_get "max_open_streams" "$section" "max_open_streams" ""
|
||||
config_get "obfs_password" "$section" "obfs_password" ""
|
||||
config_get "packet_addr" "$section" "packet_addr" ""
|
||||
config_get "client_fingerprint" "$section" "client_fingerprint" ""
|
||||
config_get "ip_version" "$section" "ip_version" ""
|
||||
config_get "tfo" "$section" "tfo" ""
|
||||
config_get "udp_over_tcp" "$section" "udp_over_tcp" ""
|
||||
config_get "reality_public_key" "$section" "reality_public_key" ""
|
||||
config_get "reality_short_id" "$section" "reality_short_id" ""
|
||||
config_get "obfs_version_hint" "$section" "obfs_version_hint" ""
|
||||
config_get "obfs_restls_script" "$section" "obfs_restls_script" ""
|
||||
config_get "multiplex" "$section" "multiplex" ""
|
||||
config_get "multiplex_protocol" "$section" "multiplex_protocol" ""
|
||||
config_get "multiplex_max_connections" "$section" "multiplex_max_connections" ""
|
||||
config_get "multiplex_min_streams" "$section" "multiplex_min_streams" ""
|
||||
config_get "multiplex_max_streams" "$section" "multiplex_max_streams" ""
|
||||
config_get "multiplex_padding" "$section" "multiplex_padding" ""
|
||||
config_get "multiplex_statistic" "$section" "multiplex_statistic" ""
|
||||
config_get "multiplex_only_tcp" "$section" "multiplex_only_tcp" ""
|
||||
config_get "other_parameters" "$section" "other_parameters" ""
|
||||
config_get "hysteria_obfs_password" "$section" "hysteria_obfs_password" ""
|
||||
config_get "port_range" "$section" "port_range" ""
|
||||
config_get "username" "$section" "username" ""
|
||||
config_get "transport" "$section" "transport" "TCP"
|
||||
config_get "multiplexing" "$section" "multiplexing" "MULTIPLEXING_LOW"
|
||||
config_get "private_key" "$section" "private_key" ""
|
||||
config_get "private_key_passphrase" "$section" "private_key_passphrase" ""
|
||||
config_get "host_key" "$section" "host_key" ""
|
||||
config_get "host_key_algorithms" "$section" "host_key_algorithms" ""
|
||||
config_get "idle_session_check_interval" "$section" "idle_session_check_interval" ""
|
||||
config_get "idle_session_timeout" "$section" "idle_session_timeout" ""
|
||||
config_get "min_idle_session" "$section" "min_idle_session" ""
|
||||
config_get "sudoku_key" "$section" "sudoku_key" ""
|
||||
config_get "aead_method" "$section" "aead_method" "none"
|
||||
config_get "padding_min" "$section" "padding_min" ""
|
||||
config_get "padding_max" "$section" "padding_max" ""
|
||||
config_get "table_type" "$section" "table_type" "prefer_ascii"
|
||||
config_get "http_mask" "$section" "http_mask" "true"
|
||||
|
||||
if [ "$enabled" = "0" ]; then
|
||||
return
|
||||
@@ -336,11 +207,12 @@ yml_servers_set()
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -z "$password" ]; then
|
||||
if [ "$type" = "ss" ] || [ "$type" = "trojan" ] || [ "$type" = "ssr" ]; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
if [ "$type" = "ss" ] || [ "$type" = "trojan" ] || [ "$type" = "ssr" ]; then
|
||||
config_get "password" "$section" "password" ""
|
||||
if [ -z "$password" ]; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -z "$config" ] && [ "$config" != "$CONFIG_NAME" ] && [ "$config" != "all" ]; then
|
||||
return
|
||||
@@ -364,59 +236,27 @@ yml_servers_set()
|
||||
fi
|
||||
LOG_OUT "Start Writing【$CONFIG_NAME - $type - $name】Proxy To Config File..."
|
||||
|
||||
if [ "$obfs" != "none" ] && [ -n "$obfs" ]; then
|
||||
if [ "$obfs" = "websocket" ]; then
|
||||
obfss="plugin: v2ray-plugin"
|
||||
elif [ "$obfs" = "shadow-tls" ]; then
|
||||
obfss="plugin: shadow-tls"
|
||||
elif [ "$obfs" = "restls" ]; then
|
||||
obfss="plugin: restls"
|
||||
else
|
||||
obfss="plugin: obfs"
|
||||
fi
|
||||
else
|
||||
obfss=""
|
||||
fi
|
||||
|
||||
if [ "$obfs_vless" = "ws" ]; then
|
||||
obfs_vless="network: ws"
|
||||
fi
|
||||
|
||||
if [ "$obfs_vless" = "grpc" ]; then
|
||||
obfs_vless="network: grpc"
|
||||
fi
|
||||
|
||||
if [ "$obfs_vless" = "tcp" ]; then
|
||||
obfs_vless="network: tcp"
|
||||
fi
|
||||
|
||||
if [ "$obfs_vmess" = "websocket" ]; then
|
||||
obfs_vmess="network: ws"
|
||||
fi
|
||||
|
||||
if [ "$obfs_vmess" = "http" ]; then
|
||||
obfs_vmess="network: http"
|
||||
fi
|
||||
|
||||
if [ "$obfs_vmess" = "h2" ]; then
|
||||
obfs_vmess="network: h2"
|
||||
fi
|
||||
|
||||
if [ "$obfs_vmess" = "grpc" ]; then
|
||||
obfs_vmess="network: grpc"
|
||||
fi
|
||||
|
||||
if [ ! -z "$custom" ] && [ "$type" = "vmess" ]; then
|
||||
custom="Host: \"$custom\""
|
||||
fi
|
||||
|
||||
if [ ! -z "$path" ]; then
|
||||
if [ "$type" != "vmess" ]; then
|
||||
path="path: \"$path\""
|
||||
elif [ "$obfs_vmess" = "network: ws" ]; then
|
||||
path="ws-path: \"$path\""
|
||||
fi
|
||||
fi
|
||||
config_get "dialer_proxy" "$section" "dialer_proxy" ""
|
||||
config_get "udp" "$section" "udp" ""
|
||||
config_get "skip_cert_verify" "$section" "skip_cert_verify" ""
|
||||
config_get "tls" "$section" "tls" ""
|
||||
config_get "sni" "$section" "sni" ""
|
||||
config_get "alpn" "$section" "alpn" ""
|
||||
config_get "fingerprint" "$section" "fingerprint" ""
|
||||
config_get "client_fingerprint" "$section" "client_fingerprint" ""
|
||||
config_get "ip_version" "$section" "ip_version" ""
|
||||
config_get "tfo" "$section" "tfo" ""
|
||||
config_get "multiplex" "$section" "multiplex" ""
|
||||
config_get "multiplex_protocol" "$section" "multiplex_protocol" ""
|
||||
config_get "multiplex_max_connections" "$section" "multiplex_max_connections" ""
|
||||
config_get "multiplex_min_streams" "$section" "multiplex_min_streams" ""
|
||||
config_get "multiplex_max_streams" "$section" "multiplex_max_streams" ""
|
||||
config_get "multiplex_padding" "$section" "multiplex_padding" ""
|
||||
config_get "multiplex_statistic" "$section" "multiplex_statistic" ""
|
||||
config_get "multiplex_only_tcp" "$section" "multiplex_only_tcp" ""
|
||||
config_get "interface_name" "$section" "interface_name" ""
|
||||
config_get "routing_mark" "$section" "routing_mark" ""
|
||||
config_get "other_parameters" "$section" "other_parameters" ""
|
||||
|
||||
if [ "$client_fingerprint" = "none" ]; then
|
||||
client_fingerprint=""
|
||||
@@ -428,6 +268,35 @@ yml_servers_set()
|
||||
|
||||
#ss
|
||||
if [ "$type" = "ss" ]; then
|
||||
config_get "cipher" "$section" "cipher" ""
|
||||
config_get "obfs" "$section" "obfs" ""
|
||||
config_get "host" "$section" "host" ""
|
||||
config_get "mux" "$section" "mux" ""
|
||||
config_get "custom" "$section" "custom" ""
|
||||
config_get "path" "$section" "path" ""
|
||||
config_get "obfs_password" "$section" "obfs_password" ""
|
||||
config_get "obfs_version_hint" "$section" "obfs_version_hint" ""
|
||||
config_get "obfs_restls_script" "$section" "obfs_restls_script" ""
|
||||
config_get "udp_over_tcp" "$section" "udp_over_tcp" ""
|
||||
|
||||
if [ "$obfs" != "none" ] && [ -n "$obfs" ]; then
|
||||
if [ "$obfs" = "websocket" ]; then
|
||||
obfss="plugin: v2ray-plugin"
|
||||
elif [ "$obfs" = "shadow-tls" ]; then
|
||||
obfss="plugin: shadow-tls"
|
||||
elif [ "$obfs" = "restls" ]; then
|
||||
obfss="plugin: restls"
|
||||
else
|
||||
obfss="plugin: obfs"
|
||||
fi
|
||||
else
|
||||
obfss=""
|
||||
fi
|
||||
|
||||
if [ ! -z "$path" ]; then
|
||||
path="path: \"$path\""
|
||||
fi
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -528,6 +397,12 @@ fi
|
||||
|
||||
#ssr
|
||||
if [ "$type" = "ssr" ]; then
|
||||
config_get "cipher_ssr" "$section" "cipher_ssr" ""
|
||||
config_get "obfs_ssr" "$section" "obfs_ssr" ""
|
||||
config_get "protocol" "$section" "protocol" ""
|
||||
config_get "obfs_param" "$section" "obfs_param" ""
|
||||
config_get "protocol_param" "$section" "protocol_param" ""
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -557,6 +432,48 @@ fi
|
||||
|
||||
#vmess
|
||||
if [ "$type" = "vmess" ]; then
|
||||
config_get "uuid" "$section" "uuid" ""
|
||||
config_get "alterId" "$section" "alterId" ""
|
||||
config_get "securitys" "$section" "securitys" ""
|
||||
config_get "xudp" "$section" "xudp" ""
|
||||
config_get "packet_encoding" "$section" "packet_encoding" ""
|
||||
config_get "global_padding" "$section" "global_padding" ""
|
||||
config_get "authenticated_length" "$section" "authenticated_length" ""
|
||||
config_get "servername" "$section" "servername" ""
|
||||
config_get "obfs_vmess" "$section" "obfs_vmess" ""
|
||||
config_get "custom" "$section" "custom" ""
|
||||
config_get "path" "$section" "path" ""
|
||||
config_get "ws_opts_path" "$section" "ws_opts_path" ""
|
||||
config_get "ws_opts_headers" "$section" "ws_opts_headers" ""
|
||||
config_get "max_early_data" "$section" "max_early_data" ""
|
||||
config_get "early_data_header_name" "$section" "early_data_header_name" ""
|
||||
config_get "http_path" "$section" "http_path" ""
|
||||
config_get "keep_alive" "$section" "keep_alive" ""
|
||||
config_get "h2_path" "$section" "h2_path" ""
|
||||
config_get "h2_host" "$section" "h2_host" ""
|
||||
config_get "grpc_service_name" "$section" "grpc_service_name" ""
|
||||
|
||||
if [ "$obfs_vmess" = "websocket" ]; then
|
||||
obfs_vmess="network: ws"
|
||||
fi
|
||||
if [ "$obfs_vmess" = "http" ]; then
|
||||
obfs_vmess="network: http"
|
||||
fi
|
||||
if [ "$obfs_vmess" = "h2" ]; then
|
||||
obfs_vmess="network: h2"
|
||||
fi
|
||||
if [ "$obfs_vmess" = "grpc" ]; then
|
||||
obfs_vmess="network: grpc"
|
||||
fi
|
||||
|
||||
if [ ! -z "$custom" ]; then
|
||||
custom="Host: \"$custom\""
|
||||
fi
|
||||
|
||||
if [ ! -z "$path" ] && [ "$obfs_vmess" = "network: ws" ]; then
|
||||
path="ws-path: \"$path\""
|
||||
fi
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -702,6 +619,11 @@ fi
|
||||
|
||||
#anytls
|
||||
if [ "$type" = "anytls" ]; then
|
||||
config_get "password" "$section" "password" ""
|
||||
config_get "idle_session_check_interval" "$section" "idle_session_check_interval" ""
|
||||
config_get "idle_session_timeout" "$section" "idle_session_timeout" ""
|
||||
config_get "min_idle_session" "$section" "min_idle_session" ""
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -758,6 +680,11 @@ fi
|
||||
|
||||
#Mieru
|
||||
if [ "$type" = "mieru" ]; then
|
||||
config_get "port_range" "$section" "port_range" ""
|
||||
config_get "username" "$section" "username" ""
|
||||
config_get "transport" "$section" "transport" "TCP"
|
||||
config_get "multiplexing" "$section" "multiplexing" "MULTIPLEXING_LOW"
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -788,6 +715,21 @@ fi
|
||||
|
||||
#Tuic
|
||||
if [ "$type" = "tuic" ]; then
|
||||
config_get "tc_ip" "$section" "tc_ip" ""
|
||||
config_get "tc_token" "$section" "tc_token" ""
|
||||
config_get "tc_uuid" "$section" "tc_uuid" ""
|
||||
config_get "tc_password" "$section" "tc_password" ""
|
||||
config_get "udp_relay_mode" "$section" "udp_relay_mode" ""
|
||||
config_get "congestion_controller" "$section" "congestion_controller" ""
|
||||
config_get "tc_alpn" "$section" "tc_alpn" ""
|
||||
config_get "disable_sni" "$section" "disable_sni" ""
|
||||
config_get "reduce_rtt" "$section" "reduce_rtt" ""
|
||||
config_get "fast_open" "$section" "fast_open" ""
|
||||
config_get "heartbeat_interval" "$section" "heartbeat_interval" ""
|
||||
config_get "request_timeout" "$section" "request_timeout" ""
|
||||
config_get "max_udp_relay_packet_size" "$section" "max_udp_relay_packet_size" ""
|
||||
config_get "max_open_streams" "$section" "max_open_streams" ""
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -874,6 +816,14 @@ fi
|
||||
|
||||
#WireGuard
|
||||
if [ "$type" = "wireguard" ]; then
|
||||
config_get "wg_ip" "$section" "wg_ip" ""
|
||||
config_get "wg_ipv6" "$section" "wg_ipv6" ""
|
||||
config_get "private_key" "$section" "private_key" ""
|
||||
config_get "public_key" "$section" "public_key" ""
|
||||
config_get "preshared_key" "$section" "preshared_key" ""
|
||||
config_get "wg_dns" "$section" "wg_dns" ""
|
||||
config_get "wg_mtu" "$section" "wg_mtu" ""
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -905,7 +855,7 @@ cat >> "$SERVER_FILE" <<-EOF
|
||||
preshared-key: "$preshared_key"
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$preshared_key" ]; then
|
||||
if [ -n "$wg_dns" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
dns:
|
||||
EOF
|
||||
@@ -925,6 +875,22 @@ fi
|
||||
|
||||
#hysteria
|
||||
if [ "$type" = "hysteria" ]; then
|
||||
config_get "hysteria_protocol" "$section" "hysteria_protocol" ""
|
||||
config_get "hysteria_up" "$section" "hysteria_up" ""
|
||||
config_get "hysteria_down" "$section" "hysteria_down" ""
|
||||
config_get "hysteria_alpn" "$section" "hysteria_alpn" ""
|
||||
config_get "hysteria_obfs" "$section" "hysteria_obfs" ""
|
||||
config_get "hysteria_auth" "$section" "hysteria_auth" ""
|
||||
config_get "hysteria_auth_str" "$section" "hysteria_auth_str" ""
|
||||
config_get "hysteria_ca" "$section" "hysteria_ca" ""
|
||||
config_get "hysteria_ca_str" "$section" "hysteria_ca_str" ""
|
||||
config_get "recv_window_conn" "$section" "recv_window_conn" ""
|
||||
config_get "recv_window" "$section" "recv_window" ""
|
||||
config_get "disable_mtu_discovery" "$section" "disable_mtu_discovery" ""
|
||||
config_get "fast_open" "$section" "fast_open" ""
|
||||
config_get "ports" "$section" "ports" ""
|
||||
config_get "hop_interval" "$section" "hop_interval" ""
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -1029,6 +995,22 @@ fi
|
||||
|
||||
#hysteria2
|
||||
if [ "$type" = "hysteria2" ]; then
|
||||
config_get "password" "$section" "password" ""
|
||||
config_get "hysteria_up" "$section" "hysteria_up" ""
|
||||
config_get "hysteria_down" "$section" "hysteria_down" ""
|
||||
config_get "hysteria_alpn" "$section" "hysteria_alpn" ""
|
||||
config_get "hysteria_obfs" "$section" "hysteria_obfs" ""
|
||||
config_get "hysteria_obfs_password" "$section" "hysteria_obfs_password" ""
|
||||
config_get "hysteria_ca" "$section" "hysteria_ca" ""
|
||||
config_get "hysteria_ca_str" "$section" "hysteria_ca_str" ""
|
||||
config_get "initial_stream_receive_window" "$section" "initial_stream_receive_window" ""
|
||||
config_get "max_stream_receive_window" "$section" "max_stream_receive_window" ""
|
||||
config_get "initial_connection_receive_window" "$section" "initial_connection_receive_window" ""
|
||||
config_get "max_connection_receive_window" "$section" "max_connection_receive_window" ""
|
||||
config_get "ports" "$section" "ports" ""
|
||||
config_get "hysteria2_protocol" "$section" "hysteria2_protocol" ""
|
||||
config_get "hop_interval" "$section" "hop_interval" ""
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -1096,7 +1078,7 @@ EOF
|
||||
fi
|
||||
if [ -n "$max_stream_receive_window" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
max_stream_receive_window: "$max_stream_receive_window"
|
||||
max-stream-receive-window: "$max_stream_receive_window"
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$initial_connection_receive_window" ]; then
|
||||
@@ -1133,6 +1115,35 @@ fi
|
||||
|
||||
#vless
|
||||
if [ "$type" = "vless" ]; then
|
||||
config_get "uuid" "$section" "uuid" ""
|
||||
config_get "xudp" "$section" "xudp" ""
|
||||
config_get "packet_addr" "$section" "packet_addr" ""
|
||||
config_get "packet_encoding" "$section" "packet_encoding" ""
|
||||
config_get "servername" "$section" "servername" ""
|
||||
config_get "obfs_vless" "$section" "obfs_vless" ""
|
||||
config_get "ws_opts_path" "$section" "ws_opts_path" ""
|
||||
config_get "ws_opts_headers" "$section" "ws_opts_headers" ""
|
||||
config_get "grpc_service_name" "$section" "grpc_service_name" ""
|
||||
config_get "reality_public_key" "$section" "reality_public_key" ""
|
||||
config_get "reality_short_id" "$section" "reality_short_id" ""
|
||||
config_get "vless_flow" "$section" "vless_flow" ""
|
||||
config_get "xhttp_opts_path" "$section" "xhttp_opts_path" ""
|
||||
config_get "xhttp_opts_host" "$section" "xhttp_opts_host" ""
|
||||
config_get "vless_encryption" "$section" "vless_encryption" ""
|
||||
|
||||
if [ "$obfs_vless" = "ws" ]; then
|
||||
obfs_vless="network: ws"
|
||||
fi
|
||||
if [ "$obfs_vless" = "grpc" ]; then
|
||||
obfs_vless="network: grpc"
|
||||
fi
|
||||
if [ "$obfs_vless" = "tcp" ]; then
|
||||
obfs_vless="network: tcp"
|
||||
fi
|
||||
if [ "$obfs_vless" = "xhttp" ]; then
|
||||
obfs_vless="network: xhttp"
|
||||
fi
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -1232,6 +1243,11 @@ EOF
|
||||
if [ ! -z "$vless_flow" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
flow: "$vless_flow"
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$vless_encryption" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
encryption: "$vless_encryption"
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$reality_public_key" ] || [ -n "$reality_short_id" ]; then
|
||||
@@ -1247,6 +1263,21 @@ EOF
|
||||
if [ -n "$reality_short_id" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
short-id: "$reality_short_id"
|
||||
EOF
|
||||
fi
|
||||
fi
|
||||
if [ "$obfs_vless" = "network: xhttp" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
xhttp-opts:
|
||||
EOF
|
||||
if [ -n "$xhttp_opts_path" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
path: "$xhttp_opts_path"
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$xhttp_opts_host" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
host: "$xhttp_opts_host"
|
||||
EOF
|
||||
fi
|
||||
fi
|
||||
@@ -1276,6 +1307,13 @@ fi
|
||||
|
||||
#ssh
|
||||
if [ "$type" = "ssh" ]; then
|
||||
config_get "auth_name" "$section" "auth_name" ""
|
||||
config_get "auth_pass" "$section" "auth_pass" ""
|
||||
config_get "private_key" "$section" "private_key" ""
|
||||
config_get "private_key_passphrase" "$section" "private_key_passphrase" ""
|
||||
config_get "host_key" "$section" "host_key" ""
|
||||
config_get "host_key_algorithms" "$section" "host_key_algorithms" ""
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -1318,6 +1356,9 @@ fi
|
||||
|
||||
#socks5
|
||||
if [ "$type" = "socks5" ]; then
|
||||
config_get "auth_name" "$section" "auth_name" ""
|
||||
config_get "auth_pass" "$section" "auth_pass" ""
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -1358,6 +1399,10 @@ fi
|
||||
|
||||
#http
|
||||
if [ "$type" = "http" ]; then
|
||||
config_get "auth_name" "$section" "auth_name" ""
|
||||
config_get "auth_pass" "$section" "auth_pass" ""
|
||||
config_get "http_headers" "$section" "http_headers" ""
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -1399,6 +1444,11 @@ fi
|
||||
|
||||
#trojan
|
||||
if [ "$type" = "trojan" ]; then
|
||||
config_get "grpc_service_name" "$section" "grpc_service_name" ""
|
||||
config_get "obfs_trojan" "$section" "obfs_trojan" ""
|
||||
config_get "trojan_ws_path" "$section" "trojan_ws_path" ""
|
||||
config_get "trojan_ws_headers" "$section" "trojan_ws_headers" ""
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -1467,6 +1517,11 @@ fi
|
||||
|
||||
#snell
|
||||
if [ "$type" = "snell" ]; then
|
||||
config_get "psk" "$section" "psk" ""
|
||||
config_get "snell_version" "$section" "snell_version" ""
|
||||
config_get "obfs_snell" "$section" "obfs_snell" ""
|
||||
config_get "host" "$section" "host" ""
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -1490,6 +1545,13 @@ fi
|
||||
|
||||
#Sudoku
|
||||
if [ "$type" = "sudoku" ]; then
|
||||
config_get "sudoku_key" "$section" "sudoku_key" ""
|
||||
config_get "aead_method" "$section" "aead_method" "none"
|
||||
config_get "padding_min" "$section" "padding_min" ""
|
||||
config_get "padding_max" "$section" "padding_max" ""
|
||||
config_get "table_type" "$section" "table_type" "prefer_ascii"
|
||||
config_get "http_mask" "$section" "http_mask" "true"
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
@@ -1528,6 +1590,132 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
#MASQUE
|
||||
if [ "$type" = "masque" ]; then
|
||||
config_get "masque_private_key" "$section" "masque_private_key" ""
|
||||
config_get "masque_public_key" "$section" "masque_public_key" ""
|
||||
config_get "masque_ip" "$section" "masque_ip" ""
|
||||
config_get "masque_ipv6" "$section" "masque_ipv6" ""
|
||||
config_get "masque_mtu" "$section" "masque_mtu" ""
|
||||
config_get "masque_remote_dns_resolve" "$section" "masque_remote_dns_resolve" ""
|
||||
config_get "masque_dns" "$section" "masque_dns" ""
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
server: "$server"
|
||||
port: $port
|
||||
EOF
|
||||
if [ -n "$masque_private_key" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
private-key: "$masque_private_key"
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$masque_public_key" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
public-key: "$masque_public_key"
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$masque_ip" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
ip: "$masque_ip"
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$masque_ipv6" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
ipv6: "$masque_ipv6"
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$masque_mtu" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
mtu: $masque_mtu
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$masque_remote_dns_resolve" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
remote-dns-resolve: $masque_remote_dns_resolve
|
||||
EOF
|
||||
fi
|
||||
if [ ! -z "$udp" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
udp: $udp
|
||||
EOF
|
||||
fi
|
||||
if [ ! -z "$masque_dns" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
dns:
|
||||
EOF
|
||||
config_list_foreach "$section" "masque_dns" set_alpn
|
||||
fi
|
||||
fi
|
||||
|
||||
#TrustTunnel
|
||||
if [ "$type" = "trusttunnel" ]; then
|
||||
config_get "trusttunnel_username" "$section" "trusttunnel_username" ""
|
||||
config_get "trusttunnel_password" "$section" "trusttunnel_password" ""
|
||||
config_get "trusttunnel_health_check" "$section" "trusttunnel_health_check" ""
|
||||
config_get "trusttunnel_quic" "$section" "trusttunnel_quic" ""
|
||||
config_get "trusttunnel_congestion_controller" "$section" "trusttunnel_congestion_controller" ""
|
||||
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
- name: "$name"
|
||||
type: $type
|
||||
server: "$server"
|
||||
port: $port
|
||||
EOF
|
||||
if [ -n "$trusttunnel_username" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
username: "$trusttunnel_username"
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$trusttunnel_password" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
password: "$trusttunnel_password"
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$trusttunnel_health_check" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
health-check: $trusttunnel_health_check
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$trusttunnel_quic" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
quic: $trusttunnel_quic
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$trusttunnel_congestion_controller" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
congestion-controller: "$trusttunnel_congestion_controller"
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$client_fingerprint" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
client-fingerprint: "$client_fingerprint"
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$udp" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
udp: $udp
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$sni" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
sni: "$sni"
|
||||
EOF
|
||||
fi
|
||||
if [ ! -z "$alpn" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
alpn:
|
||||
EOF
|
||||
config_list_foreach "$section" "alpn" set_alpn
|
||||
fi
|
||||
if [ ! -z "$skip_cert_verify" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
skip-cert-verify: $skip_cert_verify
|
||||
EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
#ip_version
|
||||
if [ ! -z "$ip_version" ]; then
|
||||
cat >> "$SERVER_FILE" <<-EOF
|
||||
|
||||
@@ -150,6 +150,13 @@ yml_other_set()
|
||||
[]
|
||||
end;
|
||||
|
||||
rule_providers_array = case custom_data.class.to_s
|
||||
when 'Hash'
|
||||
custom_data['rule-providers'].to_a if custom_data['rule-providers'].class.to_s == 'Hash'
|
||||
else
|
||||
[]
|
||||
end;
|
||||
|
||||
next unless rules_array;
|
||||
|
||||
ipv4_regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||
@@ -209,6 +216,11 @@ yml_other_set()
|
||||
else
|
||||
Value['rules'] = valid_rules.uniq;
|
||||
end;
|
||||
|
||||
if rule_providers_array and not rule_providers_array.empty? then
|
||||
Value['rule-providers'] ||= {};
|
||||
Value['rule-providers'] = Value['rule-providers'].merge!(custom_data['rule-providers']);
|
||||
end;
|
||||
end;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user