mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 02:05:13 +08:00
🦄 Sync 2026-06-05 01:27:09
This commit is contained in:
1 parent
4c613c876b
commit
23b721b464
63 files changed
+10393
-11351
No files matched your search
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=beszel-agent
|
||||
PKG_VERSION:=0.18.7
|
||||
PKG_RELEASE:=4
|
||||
PKG_RELEASE:=5
|
||||
|
||||
ifeq ($(ARCH),aarch64)
|
||||
PKG_ARCH:=arm64
|
||||
@@ -45,7 +45,11 @@ endef
|
||||
|
||||
define Build/Compile
|
||||
if command -v upx >/dev/null 2>&1; then \
|
||||
upx --best --lzma $(PKG_BUILD_DIR)/beszel-agent || true; \
|
||||
if [ "$(BESZEL_GOARCH)" = "mips" ] || [ "$(BESZEL_GOARCH)" = "mipsle" ]; then \
|
||||
echo "Skipping UPX compression for MIPS architectures (prevents execution hang)..."; \
|
||||
else \
|
||||
upx --best --lzma $(PKG_BUILD_DIR)/beszel-agent || true; \
|
||||
fi; \
|
||||
fi
|
||||
endef
|
||||
|
||||
|
||||
@@ -1,21 +1,50 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-openclash
|
||||
PKG_VERSION:=0.47.075
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=0.47.097
|
||||
PKG_RELEASE:=4
|
||||
PKG_MAINTAINER:=vernesong <https://github.com/vernesong/OpenClash>
|
||||
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/$(PKG_NAME)/config
|
||||
config PACKAGE_kmod-inet-diag
|
||||
default y if PACKAGE_$(PKG_NAME)
|
||||
|
||||
config PACKAGE_luci-compat
|
||||
default y if PACKAGE_$(PKG_NAME)
|
||||
|
||||
config PACKAGE_kmod-nft-tproxy
|
||||
default y if PACKAGE_firewall4
|
||||
|
||||
config PACKAGE_kmod-ipt-nat
|
||||
default y if ! PACKAGE_firewall4
|
||||
|
||||
config PACKAGE_iptables-mod-tproxy
|
||||
default y if ! PACKAGE_firewall4
|
||||
|
||||
config PACKAGE_iptables-mod-extra
|
||||
default y if ! PACKAGE_firewall4
|
||||
|
||||
config PACKAGE_dnsmasq_full_ipset
|
||||
default y if ! PACKAGE_firewall4
|
||||
|
||||
config PACKAGE_dnsmasq_full_nftset
|
||||
default y if PACKAGE_firewall4
|
||||
|
||||
config PACKAGE_ipset
|
||||
default y if ! PACKAGE_firewall4
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
CATEGORY:=LuCI
|
||||
SUBMENU:=3. Applications
|
||||
TITLE:=LuCI support for clash
|
||||
PKGARCH:=all
|
||||
DEPENDS:=+luci-compat +dnsmasq-full +bash +curl +ca-bundle +ip-full +ruby \
|
||||
+ruby-yaml +kmod-inet-diag +kmod-nft-tproxy +kmod-tun +unzip
|
||||
DEPENDS:=+dnsmasq-full +bash +curl +ca-bundle +ip-full \
|
||||
+ruby +ruby-yaml +kmod-tun +unzip
|
||||
MAINTAINER:=vernesong
|
||||
endef
|
||||
|
||||
@@ -124,7 +153,8 @@ define Package/$(PKG_NAME)/postrm
|
||||
uci -q delete ucitrack.@openclash[-1]
|
||||
uci -q commit ucitrack
|
||||
}
|
||||
rm -rf /tmp/luci-*
|
||||
rm -rf /tmp/luci-indexcache /tmp/luci-indexcache.* /var/luci-indexcache /var/luci-indexcache.* >/dev/null 2>&1
|
||||
rm -rf /tmp/luci-modulecache /var/luci-modulecache >/dev/null 2>&1
|
||||
exit 0
|
||||
endef
|
||||
|
||||
|
||||
@@ -665,8 +665,12 @@ function get_sub_url(filename)
|
||||
string.gsub(s.url, '[^\n]+', function(w) table.insert(info_tb, w) end)
|
||||
end
|
||||
if #info_tb == 1 then
|
||||
local url, _ = parse_url_with_name(info_tb[1], filename)
|
||||
sub_url = url
|
||||
local url, name = parse_url_with_name(info_tb[1], filename)
|
||||
if url ~= info_tb[1] then
|
||||
table.insert(providers, {name = name, url = url})
|
||||
else
|
||||
sub_url = url
|
||||
end
|
||||
elseif #info_tb > 1 then
|
||||
for _, raw in ipairs(info_tb) do
|
||||
local url, name = parse_url_with_name(raw, filename)
|
||||
@@ -798,7 +802,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 +845,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 +1102,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 +1648,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 +2084,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 +2255,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
|
||||
|
||||
@@ -2455,9 +2453,16 @@ function action_switch_oc_setting()
|
||||
uci:set("openclash", "@overwrite[0]", "enable_respect_rules", tonumber(value))
|
||||
uci:commit("openclash")
|
||||
elseif setting == "oversea" then
|
||||
oversea_v6_setting = fs.uci_get_config("config", "ipv6_enable") or "0"
|
||||
if oversea_v6_setting ~= "0" then
|
||||
uci:set("openclash", "config", "china_ip6_route", value)
|
||||
end
|
||||
uci:set("openclash", "config", "china_ip_route", value)
|
||||
uci:commit("openclash")
|
||||
if is_running() then
|
||||
if oversea_v6_setting ~= "0" then
|
||||
uci:set("openclash", "@overwrite[0]", "china_ip6_route", value)
|
||||
end
|
||||
uci:set("openclash", "@overwrite[0]", "china_ip_route", value)
|
||||
uci:commit("openclash")
|
||||
luci.sys.exec("/etc/init.d/openclash restart >/dev/null 2>&1 &")
|
||||
@@ -2490,7 +2495,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 +2915,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 +2957,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 +3116,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 +3207,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
|
||||
|
||||
@@ -3323,7 +3328,7 @@ end
|
||||
function action_add_subscription()
|
||||
local name = luci.http.formvalue("name")
|
||||
local address = luci.http.formvalue("address")
|
||||
local sub_ua = luci.http.formvalue("sub_ua") or "clash.meta"
|
||||
local sub_ua = luci.http.formvalue("sub_ua") or "clash-verge/v2.4.5"
|
||||
local sub_convert = luci.http.formvalue("sub_convert") or "0"
|
||||
local convert_address = luci.http.formvalue("convert_address") or ""
|
||||
local template = luci.http.formvalue("template") or ""
|
||||
@@ -3351,9 +3356,10 @@ function action_add_subscription()
|
||||
local is_valid_url = false
|
||||
|
||||
if sub_convert == "1" then
|
||||
if string.find(address, "^https?://") and not string.find(address, "\n") and not string.find(address, "|") then
|
||||
is_valid_url = true
|
||||
elseif string.find(address, "\n") or string.find(address, "|") then
|
||||
local prefixed_http_pattern = "^[^,%s]+,https?://.+"
|
||||
local encoded_prefixed_http_pattern = "^[^%%%s]+%%2[Cc]https?%%3[Aa]%%2[Ff]%%2[Ff].+"
|
||||
|
||||
if string.find(address, "\n") or string.find(address, "|") then
|
||||
local links = {}
|
||||
if string.find(address, "\n") then
|
||||
for line in address:gmatch("[^\n]+") do
|
||||
@@ -3367,15 +3373,20 @@ function action_add_subscription()
|
||||
|
||||
for _, link in ipairs(links) do
|
||||
if link and link ~= "" then
|
||||
if string.find(link, "^https?://") or string.find(link, "^[a-zA-Z]+://") then
|
||||
if string.find(link, "^https?://")
|
||||
or string.find(link, "^[a-zA-Z]+://")
|
||||
or string.find(link, prefixed_http_pattern)
|
||||
or string.find(link, encoded_prefixed_http_pattern) then
|
||||
is_valid_url = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
if string.find(address, "^[a-zA-Z]+://") and
|
||||
not string.find(address, "\n") and not string.find(address, "|") then
|
||||
if string.find(address, "^https?://")
|
||||
or string.find(address, "^[a-zA-Z]+://")
|
||||
or string.find(address, prefixed_http_pattern)
|
||||
or string.find(address, encoded_prefixed_http_pattern) then
|
||||
is_valid_url = true
|
||||
end
|
||||
end
|
||||
@@ -3467,9 +3478,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 +3492,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 +3506,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 +3535,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 +3590,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 +3619,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 +3680,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 +3710,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 +3718,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 +3760,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 +3805,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 +3840,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 +3894,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 +3982,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 +4000,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) {
|
||||
@@ -328,6 +328,13 @@ o = s:taboption("meta", Flag, "enable_unified_delay", font_red..bold_on..transla
|
||||
o.description = font_red..bold_on..translate("Change The Delay Calculation Method To Remove Extra Delays Such as Handshaking")..bold_off..font_off
|
||||
o.default = "0"
|
||||
|
||||
o = s:taboption("meta", Value, "global_ua", translate("Global User-Agent"))
|
||||
o:value("0", translate("Disable"))
|
||||
o:value("clash-verge/v2.4.5")
|
||||
o:value("clash.meta/1.19.20")
|
||||
o:value("Clash")
|
||||
o.default = "0"
|
||||
|
||||
o = s:taboption("meta", ListValue, "find_process_mode", translate("Enable Process Rule"))
|
||||
o.description = translate("Whether to Enable Process Rules, Only Works on Routerself, If You Are Not Sure, Please Choose off Which Useful in Router Environment, Depend on kmod-inet-diag")
|
||||
o:value("0", translate("Disable"))
|
||||
@@ -497,15 +504,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
|
||||
|
||||
@@ -58,10 +60,10 @@ end
|
||||
---- UA
|
||||
o = s:option(Value, "sub_ua", "User-Agent")
|
||||
o.description = font_red..bold_on..translate("Used for Downloading Subscriptions, Defaults to Clash")..bold_off..font_off
|
||||
o:value("clash.meta")
|
||||
o:value("clash-verge/v1.5.1")
|
||||
o:value("clash-verge/v2.4.5")
|
||||
o:value("clash.meta/1.19.20")
|
||||
o:value("Clash")
|
||||
o.default = "clash.meta"
|
||||
o.default = "clash-verge/v2.4.5"
|
||||
o.rmempty = true
|
||||
|
||||
---- subconverter
|
||||
@@ -74,10 +76,10 @@ o = s:option(Value, "convert_address", translate("Convert Address"))
|
||||
o.rmempty = true
|
||||
o.description = font_red..bold_on..translate("Note: There is A Risk of Privacy Leakage in Online Convert")..bold_off..font_off
|
||||
o:depends("sub_convert", "1")
|
||||
o:value("https://api.wcc.best/sub", translate("api.wcc.best"))
|
||||
o:value("https://api.asailor.org/sub", translate("api.asailor.org"))
|
||||
o.default = "https://api.wcc.best/sub"
|
||||
o.placeholder = "https://api.wcc.best/sub"
|
||||
o:value("https://api.wcc.best/sub", translate("api.wcc.best"))
|
||||
o.default = "https://api.asailor.org/sub"
|
||||
o.placeholder = "https://api.asailor.org/sub"
|
||||
|
||||
---- Template
|
||||
o = s:option(ListValue, "template", translate("Template Name"))
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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) {
|
||||
@@ -265,17 +265,17 @@ o.placeholder = translate("5000 or 1234-2345")
|
||||
o.rmempty = true
|
||||
|
||||
o = s2:option(ListValue, "proto", translate("Proto"))
|
||||
o:value("both", translate("Both"))
|
||||
o:value("udp", translate("UDP"))
|
||||
o:value("tcp", translate("TCP"))
|
||||
o:value("both", translate("Both"))
|
||||
o.default = "tcp"
|
||||
o.default = "both"
|
||||
o.rmempty = false
|
||||
|
||||
o = s2:option(ListValue, "family", translate("Family"))
|
||||
o:value("both", translate("Both"))
|
||||
o:value("ipv4", translate("IPv4"))
|
||||
o:value("ipv6", translate("IPv6"))
|
||||
o:value("both", translate("Both"))
|
||||
o.default = "tcp"
|
||||
o.default = "both"
|
||||
o.rmempty = false
|
||||
|
||||
o = s2:option(ListValue, "interface", translate("Interface"))
|
||||
@@ -885,6 +885,62 @@ o.template = "openclash/other_stream_option"
|
||||
o.value = "OpenAI"
|
||||
o:depends("stream_auto_select_openai", "1")
|
||||
|
||||
-- Claude
|
||||
o = s:taboption("stream_enhance", Flag, "stream_auto_select_claude", font_red..translate("Claude")..font_off)
|
||||
o.default = 0
|
||||
o:depends("stream_auto_select", "1")
|
||||
|
||||
o = s:taboption("stream_enhance", Value, "stream_auto_select_group_key_claude", translate("Group Filter"))
|
||||
o.placeholder = "Claude|AI"
|
||||
o.description = translate("It Will Be Searched According To The Regex When Auto Search Group Fails")
|
||||
o:depends("stream_auto_select_claude", "1")
|
||||
o.rmempty = true
|
||||
|
||||
o = s:taboption("stream_enhance", Value, "stream_auto_select_region_key_claude", translate("Unlock Region Filter"))
|
||||
o.placeholder = "US"
|
||||
o.description = translate("It Will Be Selected Region(Country Shortcode) According To The Regex")
|
||||
o:depends("stream_auto_select_claude", "1")
|
||||
o.rmempty = true
|
||||
|
||||
o = s:taboption("stream_enhance", Value, "stream_auto_select_node_key_claude", translate("Unlock Nodes Filter"))
|
||||
o.description = translate("It Will Be Selected Nodes According To The Regex")
|
||||
o:depends("stream_auto_select_claude", "1")
|
||||
o.rmempty = true
|
||||
|
||||
o = s:taboption("stream_enhance", DummyValue, "Claude", translate("Manual Test"))
|
||||
o.rawhtml = true
|
||||
o.template = "openclash/other_stream_option"
|
||||
o.value = "Claude"
|
||||
o:depends("stream_auto_select_claude", "1")
|
||||
|
||||
-- Gemini
|
||||
o = s:taboption("stream_enhance", Flag, "stream_auto_select_gemini", font_red..translate("Gemini")..font_off)
|
||||
o.default = 0
|
||||
o:depends("stream_auto_select", "1")
|
||||
|
||||
o = s:taboption("stream_enhance", Value, "stream_auto_select_group_key_gemini", translate("Group Filter"))
|
||||
o.placeholder = "Gemini|AI"
|
||||
o.description = translate("It Will Be Searched According To The Regex When Auto Search Group Fails")
|
||||
o:depends("stream_auto_select_gemini", "1")
|
||||
o.rmempty = true
|
||||
|
||||
o = s:taboption("stream_enhance", Value, "stream_auto_select_region_key_gemini", translate("Unlock Region Filter"))
|
||||
o.placeholder = "US"
|
||||
o.description = translate("It Will Be Selected Region(Country Shortcode) According To The Regex")
|
||||
o:depends("stream_auto_select_gemini", "1")
|
||||
o.rmempty = true
|
||||
|
||||
o = s:taboption("stream_enhance", Value, "stream_auto_select_node_key_gemini", translate("Unlock Nodes Filter"))
|
||||
o.description = translate("It Will Be Selected Nodes According To The Regex")
|
||||
o:depends("stream_auto_select_gemini", "1")
|
||||
o.rmempty = true
|
||||
|
||||
o = s:taboption("stream_enhance", DummyValue, "Gemini", translate("Manual Test"))
|
||||
o.rawhtml = true
|
||||
o.template = "openclash/other_stream_option"
|
||||
o.value = "Gemini"
|
||||
o:depends("stream_auto_select_gemini", "1")
|
||||
|
||||
---- update Settings
|
||||
o = s:taboption("geo_update", Flag, "geo_auto_update", font_red..bold_on..translate("Auto Update GeoIP MMDB")..bold_off..font_off)
|
||||
o.default = 0
|
||||
@@ -912,9 +968,10 @@ o = s:taboption("geo_update", Value, "geo_custom_url")
|
||||
o.title = translate("Custom GeoIP MMDB URL")
|
||||
o.rmempty = true
|
||||
o.description = translate("Custom GeoIP MMDB URL, Click Button Below To Refresh After Edit")
|
||||
o:value("https://testingcf.jsdelivr.net/gh/alecthw/mmdb_china_ip_list@release/lite/Country.mmdb", translate("Alecthw-lite-Version")..translate("(Default mmdb)"))
|
||||
o:value("https://testingcf.jsdelivr.net/gh/alecthw/mmdb_china_ip_list@release/Country.mmdb", translate("Alecthw-Version")..translate("(All Info mmdb)"))
|
||||
o:value("https://testingcf.jsdelivr.net/gh/Hackl0us/GeoIP2-CN@release/Country.mmdb", translate("Hackl0us-Version")..translate("(Only CN)"))
|
||||
o:value("https://testingcf.jsdelivr.net/gh/alecthw/mmdb_china_ip_list@release/lite/Country.mmdb", translate("Alecthw-lite-testingcf-Version")..translate("(Default mmdb)"))
|
||||
o:value("https://testingcf.jsdelivr.net/gh/alecthw/mmdb_china_ip_list@release/Country.mmdb", translate("Alecthw-testingcf-Version")..translate("(All Info mmdb)"))
|
||||
o:value("https://testingcf.jsdelivr.net/gh/Hackl0us/GeoIP2-CN@release/Country.mmdb", translate("Hackl0us-testingcf-Version")..translate("(Only CN)"))
|
||||
o:value("https://github.com/alecthw/mmdb_china_ip_list/releases/latest/download/Country-lite.mmdb", translate("Alecthw-lite-github-Version"))
|
||||
o.default = "https://testingcf.jsdelivr.net/gh/alecthw/mmdb_china_ip_list@release/lite/Country.mmdb"
|
||||
o:depends("geo_auto_update", "1")
|
||||
|
||||
@@ -926,7 +983,7 @@ o.inputstyle = "reload"
|
||||
o.write = function()
|
||||
m.uci:set("openclash", "config", "enable", 1)
|
||||
m.uci:commit("openclash")
|
||||
SYS.call("/usr/share/openclash/openclash_ipdb.sh >/dev/null 2>&1 &")
|
||||
SYS.call("/usr/share/openclash/openclash_geo.sh ipdb >/dev/null 2>&1 &")
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash"))
|
||||
end
|
||||
|
||||
@@ -958,6 +1015,7 @@ o.rmempty = true
|
||||
o.description = translate("Custom GeoIP Dat URL, Click Button Below To Refresh After Edit")
|
||||
o:value("https://testingcf.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat", translate("Loyalsoldier-testingcf-jsdelivr-Version")..translate("(Default)"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat", translate("Loyalsoldier-fastly-jsdelivr-Version"))
|
||||
o:value("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", translate("Loyalsoldier-github-Version"))
|
||||
o.default = "https://testingcf.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
|
||||
o:depends("geoip_auto_update", "1")
|
||||
|
||||
@@ -969,7 +1027,7 @@ o.inputstyle = "reload"
|
||||
o.write = function()
|
||||
m.uci:set("openclash", "config", "enable", 1)
|
||||
m.uci:commit("openclash")
|
||||
SYS.call("/usr/share/openclash/openclash_geoip.sh >/dev/null 2>&1 &")
|
||||
SYS.call("/usr/share/openclash/openclash_geo.sh geoip >/dev/null 2>&1 &")
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash"))
|
||||
end
|
||||
|
||||
@@ -1001,6 +1059,7 @@ o.rmempty = true
|
||||
o.description = translate("Custom GeoSite Data URL, Click Button Below To Refresh After Edit")
|
||||
o:value("https://testingcf.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat", translate("Loyalsoldier-testingcf-jsdelivr-Version")..translate("(Default)"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat", translate("Loyalsoldier-fastly-jsdelivr-Version"))
|
||||
o:value("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", translate("Loyalsoldier-github-Version"))
|
||||
o.default = "https://testingcf.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
|
||||
o:depends("geosite_auto_update", "1")
|
||||
|
||||
@@ -1012,7 +1071,7 @@ o.inputstyle = "reload"
|
||||
o.write = function()
|
||||
m.uci:set("openclash", "config", "enable", 1)
|
||||
m.uci:commit("openclash")
|
||||
SYS.call("/usr/share/openclash/openclash_geosite.sh >/dev/null 2>&1 &")
|
||||
SYS.call("/usr/share/openclash/openclash_geo.sh geosite >/dev/null 2>&1 &")
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash"))
|
||||
end
|
||||
|
||||
@@ -1044,6 +1103,7 @@ o.rmempty = true
|
||||
o.description = translate("Custom Geo ASN Data URL, Click Button Below To Refresh After Edit")
|
||||
o:value("https://testingcf.jsdelivr.net/gh/xishang0128/geoip@release/GeoLite2-ASN.mmdb", translate("xishang0128-testingcf-jsdelivr-Version")..translate("(Default)"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/xishang0128/geoip@release/GeoLite2-ASN.mmdb", translate("xishang0128-fastly-jsdelivr-Version"))
|
||||
o:value("https://github.com/xishang0128/geoip/releases/latest/download/GeoLite2-ASN.mmdb", translate("xishang0128-github-Version"))
|
||||
o.default = "https://testingcf.jsdelivr.net/gh/xishang0128/geoip@release/GeoLite2-ASN.mmdb"
|
||||
o:depends("geoasn_auto_update", "1")
|
||||
|
||||
@@ -1055,7 +1115,7 @@ o.inputstyle = "reload"
|
||||
o.write = function()
|
||||
m.uci:set("openclash", "config", "enable", 1)
|
||||
m.uci:commit("openclash")
|
||||
SYS.call("/usr/share/openclash/openclash_geoasn.sh >/dev/null 2>&1 &")
|
||||
SYS.call("/usr/share/openclash/openclash_geo.sh geoasn >/dev/null 2>&1 &")
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash"))
|
||||
end
|
||||
|
||||
@@ -1088,6 +1148,7 @@ o:value("https://ispip.clang.cn/all_cn.txt", translate("Clang-CN")..translate("(
|
||||
o:value("https://ispip.clang.cn/all_cn_cidr.txt", translate("Clang-CN-CIDR"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/Hackl0us/GeoIP2-CN@release/CN-ip-cidr.txt", translate("Hackl0us-CN-CIDR-fastly-jsdelivr"))
|
||||
o:value("https://testingcf.jsdelivr.net/gh/Hackl0us/GeoIP2-CN@release/CN-ip-cidr.txt", translate("Hackl0us-CN-CIDR-testingcf-jsdelivr"))
|
||||
o:value("https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/direct-list.txt", translate("Loyalsoldier-github-Version"))
|
||||
o.default = "https://ispip.clang.cn/all_cn.txt"
|
||||
|
||||
o = s:taboption("chnr_update", Value, "chnr6_custom_url")
|
||||
|
||||
@@ -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
|
||||
@@ -51,7 +51,7 @@
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 10000;
|
||||
z-index: 5;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -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');
|
||||
@@ -2357,11 +2738,7 @@ var ConfigEditor = {
|
||||
model.querySelector('#overwrite-add-close').onclick = function() {
|
||||
document.body.removeChild(ocDiv);
|
||||
};
|
||||
overlay.onclick = function(e) {
|
||||
if (e.target === overlay) {
|
||||
document.body.removeChild(ocDiv);
|
||||
}
|
||||
};
|
||||
|
||||
model.querySelector('#overwrite-upload-cancel').onclick = function() {
|
||||
document.body.removeChild(ocDiv);
|
||||
};
|
||||
@@ -2439,11 +2816,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 +2858,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 +2881,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 +2984,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 +3046,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 +3073,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 +3147,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 +3193,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 +3223,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 +3285,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;
|
||||
|
||||
@@ -214,7 +214,7 @@
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-white);
|
||||
background: var(--bg-white) !important;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
transition: all var(--transition-fast);
|
||||
@@ -258,6 +258,10 @@
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.oc .form-textarea::placeholder {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.oc .form-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
@@ -553,6 +557,10 @@
|
||||
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.1);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .form-textarea::placeholder {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .checkmark {
|
||||
border-color: var(--border-light);
|
||||
background: var(--bg-gray);
|
||||
@@ -752,8 +760,8 @@
|
||||
<label for="subscribe-ua-input"><%:User-Agent%> (<%:Optional%>):</label>
|
||||
<div class="form-select-wrapper">
|
||||
<select id="subscribe-ua-input" class="form-select">
|
||||
<option value="clash.meta">clash.meta</option>
|
||||
<option value="clash-verge/v1.5.1">clash-verge/v1.5.1</option>
|
||||
<option value="clash-verge/v2.4.5">clash-verge/v2.4.5</option>
|
||||
<option value="clash.meta/1.19.20">clash.meta/1.19.20</option>
|
||||
<option value="Clash">clash</option>
|
||||
<option value="custom"><%:Custom%></option>
|
||||
</select>
|
||||
@@ -785,8 +793,8 @@
|
||||
<label for="convert-address-input"><%:Convert Address%>:</label>
|
||||
<div class="form-select-wrapper">
|
||||
<select id="convert-address-input" class="form-select">
|
||||
<option value="https://api.wcc.best/sub">api.wcc.best</option>
|
||||
<option value="https://api.asailor.org/sub">api.asailor.org</option>
|
||||
<option value="https://api.wcc.best/sub">api.wcc.best</option>
|
||||
<option value="custom"><%:Custom%></option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -1073,6 +1081,7 @@ var ConfigUploader = {
|
||||
} else {
|
||||
advancedOptionsContainer.style.display = 'none';
|
||||
self.resetAdvancedOptions();
|
||||
self.updateSubmitButton();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1082,6 +1091,7 @@ var ConfigUploader = {
|
||||
} else {
|
||||
subConvertOptions.style.display = 'none';
|
||||
}
|
||||
self.updateSubmitButton();
|
||||
});
|
||||
|
||||
convertAddressSelect.addEventListener('change', function() {
|
||||
@@ -1120,12 +1130,6 @@ var ConfigUploader = {
|
||||
}
|
||||
});
|
||||
|
||||
this.overlay.addEventListener('click', function(e) {
|
||||
if (e.target === self.overlay && !self.isProcessing) {
|
||||
self.hide();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && !self.isProcessing && self.overlay.classList.contains('show')) {
|
||||
self.hide();
|
||||
@@ -1197,7 +1201,7 @@ var ConfigUploader = {
|
||||
this.switchMode('file');
|
||||
document.getElementById('config-filename-input').value = '';
|
||||
document.getElementById('subscribe-url-input').value = '';
|
||||
document.getElementById('subscribe-ua-input').value = 'clash.meta';
|
||||
document.getElementById('subscribe-ua-input').value = 'clash-verge/v2.4.5';
|
||||
document.getElementById('subscribe-ua-custom').style.display = 'none';
|
||||
|
||||
document.getElementById('advanced-options-enable').checked = false;
|
||||
@@ -1226,7 +1230,7 @@ var ConfigUploader = {
|
||||
document.getElementById('subscribe-url-input').value = data.address;
|
||||
}
|
||||
if (data.sub_ua) {
|
||||
if (data.sub_ua === 'clash.meta' || data.sub_ua === 'clash-verge/v1.5.1' || data.sub_ua === 'Clash' || data.sub_ua === 'custom') {
|
||||
if (data.sub_ua === 'clash.meta/1.19.20' || data.sub_ua === 'clash-verge/v2.4.5' || data.sub_ua === 'Clash' || data.sub_ua === 'custom') {
|
||||
document.getElementById('subscribe-ua-input').value = data.sub_ua;
|
||||
if (data.sub_ua === 'custom') {
|
||||
document.getElementById('subscribe-ua-custom').style.display = 'block';
|
||||
@@ -1390,10 +1394,10 @@ var ConfigUploader = {
|
||||
updateSubmitButton: function() {
|
||||
var filename = document.getElementById('config-filename-input').value.trim();
|
||||
var submitBtn = document.getElementById('config-upload-submit');
|
||||
var isValid = false;
|
||||
var isValidFormat = false;
|
||||
|
||||
if (this.currentMode === 'file') {
|
||||
isValid = this.selectedFile && filename;
|
||||
isValidFormat = this.selectedFile && filename;
|
||||
} else if (this.currentMode === 'subscribe') {
|
||||
var url = document.getElementById('subscribe-url-input').value.trim();
|
||||
var advancedEnabled = document.getElementById('advanced-options-enable').checked;
|
||||
@@ -1401,25 +1405,29 @@ var ConfigUploader = {
|
||||
|
||||
if (url && filename) {
|
||||
if (subConvert) {
|
||||
var linkPattern = /^(?:https?:\/\/|[a-zA-Z]+:\/\/|[^,\s]+,https?:\/\/.+|[^%\s]+%2Chttps?%3A%2F%2F.+)$/i;
|
||||
|
||||
if (url.indexOf('\n') !== -1 || url.indexOf('|') !== -1) {
|
||||
var links = url.indexOf('\n') !== -1 ? url.split('\n') : url.split('|');
|
||||
var links = url.split(/[\n|]/);
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
var link = links[i].trim();
|
||||
if (link && (/^https?:\/\//.test(link) || /^[a-zA-Z]+:\/\//.test(link))) {
|
||||
isValid = true;
|
||||
if (link && linkPattern.test(link)) {
|
||||
isValidFormat = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isValid = /^https?:\/\//.test(url) || /^[a-zA-Z]+:\/\//.test(url);
|
||||
isValidFormat = linkPattern.test(url);
|
||||
}
|
||||
} else {
|
||||
isValid = /^https?:\/\//.test(url) && url.indexOf('\n') === -1 && url.indexOf('|') === -1;
|
||||
if (/^https?:\/\//.test(url) && url.indexOf('\n') === -1 && url.indexOf('|') === -1) {
|
||||
isValidFormat = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
submitBtn.disabled = !isValid || this.isProcessing;
|
||||
submitBtn.disabled = !isValidFormat || this.isProcessing;
|
||||
},
|
||||
|
||||
uploadFile: function() {
|
||||
@@ -1562,7 +1570,7 @@ var ConfigUploader = {
|
||||
}
|
||||
|
||||
if (userAgent === 'custom') {
|
||||
userAgent = subscribeUaCustom.value.trim() || 'clash.meta';
|
||||
userAgent = subscribeUaCustom.value.trim();
|
||||
}
|
||||
|
||||
if (!url || !filename) {
|
||||
@@ -1573,25 +1581,19 @@ var ConfigUploader = {
|
||||
var isValidFormat = false;
|
||||
|
||||
if (subConvert) {
|
||||
if (url.indexOf('\n') !== -1 || url.indexOf('|') !== -1) {
|
||||
var links = [];
|
||||
if (url.indexOf('\n') !== -1) {
|
||||
links = url.split('\n');
|
||||
} else {
|
||||
links = url.split('|');
|
||||
}
|
||||
var linkPattern = /^(?:https?:\/\/|[a-zA-Z]+:\/\/|[^,\s]+,https?:\/\/.+|[^%\s]+%2Chttps?%3A%2F%2F.+)$/i;
|
||||
|
||||
if (url.indexOf('\n') !== -1 || url.indexOf('|') !== -1) {
|
||||
var links = url.split(/[\n|]/);
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
var link = links[i].trim();
|
||||
if (link && (/^https?:\/\//.test(link) || /^[a-zA-Z]+:\/\//.test(link))) {
|
||||
if (link && linkPattern.test(link)) {
|
||||
isValidFormat = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (/^https?:\/\//.test(url) || /^[a-zA-Z]+:\/\//.test(url)) {
|
||||
isValidFormat = true;
|
||||
}
|
||||
isValidFormat = linkPattern.test(url);
|
||||
}
|
||||
} else {
|
||||
if (/^https?:\/\//.test(url) && url.indexOf('\n') === -1 && url.indexOf('|') === -1) {
|
||||
|
||||
@@ -824,6 +824,8 @@ function poll_log(){
|
||||
var currentContent = "";
|
||||
var bufferToProcess = [];
|
||||
|
||||
clearTimeout(r);
|
||||
|
||||
for(var i=0; i<titles.length; i++){
|
||||
if(titles[i].className === 'cbi-tab') {
|
||||
activeTabId = i;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ function act_manual_unlock_test_<%=stream_id_name%>(btn, type)
|
||||
{
|
||||
manual_<%=stream_id_name%>_type_legend.style.display = 'none';
|
||||
manual_<%=stream_id_name%>_type_output.innerHTML = '<textarea id="manual_<%=stream_id_name%>_type_output" class="cbi-input-textarea" style="width: 100%;display:inline" data-update="change" rows="10" cols="50" readonly="readonly" >'+x.responseText+'</textarea>';
|
||||
log_area(document.getElementById("manual_<%=stream_id_name%>_type_output"), 'manual_stream_unlock_test', 'true', manual_<%=stream_id_name%>_type_output.offsetWidth, '250px');
|
||||
log_editor(document.getElementById("manual_<%=stream_id_name%>_type_output"), 'manual_stream_unlock_test', 'true', manual_<%=stream_id_name%>_type_output.offsetWidth, '250px');
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
@@ -27,6 +28,7 @@
|
||||
|
||||
--control-height: 36px;
|
||||
--card-padding: 6px 8px;
|
||||
--version-right: 8px;
|
||||
--gap-size: 16px;
|
||||
}
|
||||
|
||||
@@ -37,6 +39,7 @@
|
||||
--text-primary: #ebebeb;
|
||||
--text-secondary: #d0cfcf;
|
||||
--text-title: #e5e7eb;
|
||||
--select-hover: #ebebeb;
|
||||
--border-color: #939393;
|
||||
--border-light: #6b7280;
|
||||
--hover-bg: #374151;
|
||||
@@ -62,7 +65,7 @@
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.select-popup {
|
||||
.oc .select-popup {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
@@ -77,9 +80,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 +102,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 +110,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 +140,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 +149,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 +165,7 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.config-item::after {
|
||||
.oc .config-item::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
@@ -175,7 +180,7 @@
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.config-editor-title {
|
||||
.oc .config-editor-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
@@ -187,7 +192,7 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.config-label {
|
||||
.oc .config-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
@@ -195,7 +200,7 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.config-label::after {
|
||||
.oc .config-label::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
@@ -206,7 +211,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 +227,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 +248,7 @@
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.select-class option {
|
||||
.oc .select-class option {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
@@ -255,12 +260,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 +273,7 @@
|
||||
background: var(--bg-gray);
|
||||
}
|
||||
|
||||
.select-option {
|
||||
.oc .select-option {
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
@@ -281,31 +286,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 +322,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 +343,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 +357,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 +403,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 +551,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>
|
||||
<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">
|
||||
<%: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 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;
|
||||
@@ -26,9 +27,13 @@
|
||||
--radius-lg: 8px;
|
||||
--transition-fast: 0.15s ease;
|
||||
--transition-normal: 0.3s ease;
|
||||
--stripe-color: rgba(255,255,255,0.24);
|
||||
--stripe-opacity: 0.5;
|
||||
--stripe-size: 40px;
|
||||
|
||||
--control-height: 32px;
|
||||
--card-padding: 6px 8px;
|
||||
--version-right: 8px;
|
||||
--gap-size: 10px;
|
||||
|
||||
--row-1-height: 160px;
|
||||
@@ -44,6 +49,7 @@
|
||||
--text-primary: #ebebeb;
|
||||
--text-secondary: #d0cfcf;
|
||||
--text-title: #e5e7eb;
|
||||
--select-hover: #ebebeb;
|
||||
--border-color: #939393;
|
||||
--border-light: #6b7280;
|
||||
--hover-bg: #374151;
|
||||
@@ -55,6 +61,8 @@
|
||||
--warning-color: #fa41c8;
|
||||
--watchdog-color: #c147f5;
|
||||
--info-color: #3b82f6;
|
||||
--stripe-color: rgba(0, 0, 0, 0.255);
|
||||
--stripe-opacity: 0.8;
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .plugin-toggle-slider {
|
||||
@@ -294,6 +302,13 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.oc .sub-card:hover {
|
||||
background: var(--hover-bg, #f3f4f6);
|
||||
border-color: var(--primary-color, #3b82f6);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.oc .sub-card.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -332,7 +347,7 @@
|
||||
|
||||
.oc .version-display-container {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
right: var(--version-right);
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
z-index: 2;
|
||||
@@ -906,12 +921,13 @@
|
||||
min-height: 24px;
|
||||
min-width: 0;
|
||||
gap: 8px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.oc .subscription-info-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 5px;
|
||||
flex: 1;
|
||||
min-height: 80px;
|
||||
padding-left: 20px;
|
||||
@@ -937,6 +953,30 @@
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.oc .subscription-progress-fill::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: linear-gradient(45deg, var(--stripe-color) 25%, transparent 25%, transparent 50%, var(--stripe-color) 50%, var(--stripe-color) 75%, transparent 75%, transparent);
|
||||
background-size: var(--stripe-size) var(--stripe-size);
|
||||
animation: oc-stripes 2s linear infinite;
|
||||
opacity: var(--stripe-opacity);
|
||||
pointer-events: none;
|
||||
mix-blend-mode: overlay;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@keyframes oc-stripes {
|
||||
to { background-position: 40px 0; }
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .subscription-progress-fill::before {
|
||||
background-image: linear-gradient(45deg, var(--stripe-color) 25%, transparent 25%, transparent 50%, var(--stripe-color) 50%, var(--stripe-color) 75%, transparent 75%, transparent);
|
||||
opacity: var(--stripe-opacity);
|
||||
}
|
||||
|
||||
.oc .subscription-progress-fill.high {
|
||||
@@ -962,20 +1002,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%;
|
||||
@@ -1106,10 +1241,22 @@
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
padding-left: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.oc .config-file-name .selected-file-dot {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.2em;
|
||||
margin-bottom: 0.2em;
|
||||
background: var(--primary-color);
|
||||
flex-shrink: 0;
|
||||
animation: gentlePulse 4s cubic-bezier(.17,.67,.83,.67) infinite;
|
||||
}
|
||||
|
||||
.oc .config-file-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1178,13 +1325,20 @@
|
||||
min-width: 0;
|
||||
max-width: 30vw;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.oc .file-info:hover {
|
||||
background: var(--hover-bg);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.oc .file-info-item {
|
||||
display: block;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
padding-right: 20px;
|
||||
padding: 2px 6px;
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -1528,6 +1682,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 +2067,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 +2692,7 @@
|
||||
|
||||
this.rawCurrentConfig = currentConfigFile;
|
||||
this.configList = configFiles;
|
||||
window.configFiles = configFiles;
|
||||
this.currentConfig = currentConfigFile;
|
||||
this.currentConfigIndex = -1;
|
||||
|
||||
@@ -2716,9 +2881,7 @@
|
||||
var displayName = this.formatDisplayName(configFile);
|
||||
if (configFile === this.rawCurrentConfig) {
|
||||
configNameElement.innerHTML =
|
||||
'<svg width="0.6em" height="0.6em" viewBox="0 0 24 24" fill="var(--primary-color)" style="vertical-align:middle;margin-right:0.2em;margin-bottom:0.2em;">' +
|
||||
'<circle cx="12" cy="12" r="8" fill="var(--primary-color)"/>' +
|
||||
'</svg>' + displayName;
|
||||
'<span class="selected-file-dot"></span>' + displayName;
|
||||
} else {
|
||||
configNameElement.textContent = displayName;
|
||||
}
|
||||
@@ -2829,12 +2992,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 +3078,8 @@
|
||||
SubscriptionManager.getSubscriptionInfo();
|
||||
}
|
||||
|
||||
OverwriteSubscribeManager.render(OverwriteSubscribeManager.data);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
@@ -2936,14 +3103,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() {
|
||||
@@ -3010,8 +3275,8 @@
|
||||
}
|
||||
|
||||
var needsErrorHandling = false;
|
||||
if (x && x.status == 200 && status.providers) {
|
||||
var newProvidersLength = (status.providers) ? status.providers.length : 0;
|
||||
if (x && x.status == 200 && status.providers.length > 0) {
|
||||
var newProvidersLength = status.providers.length;
|
||||
var cachedProvidersLength = (parsedData && parsedData.providers) ? parsedData.providers.length : 0;
|
||||
|
||||
if (newProvidersLength !== cachedProvidersLength && cachedProvidersLength > newProvidersLength) {
|
||||
@@ -3023,17 +3288,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 +3378,7 @@
|
||||
|
||||
infoText.textContent = infoString;
|
||||
infoText.title = tooltipString;
|
||||
this.enableOverflowDrag(infoText);
|
||||
},
|
||||
|
||||
displayMultipleProviders: function(providers, progressSection) {
|
||||
@@ -3169,6 +3436,7 @@
|
||||
|
||||
infoText.textContent = infoString;
|
||||
infoText.title = tooltipString;
|
||||
SubscriptionManager.enableOverflowDrag(infoText);
|
||||
|
||||
card.appendChild(infoText);
|
||||
progressSection.appendChild(card);
|
||||
@@ -3298,21 +3566,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);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3375,26 +3645,18 @@
|
||||
|
||||
bindEvents: function() {
|
||||
var textarea = document.getElementById('subscription-url-textarea');
|
||||
var overlay = document.getElementById('subscription-url-overlay');
|
||||
|
||||
if (textarea) {
|
||||
textarea.addEventListener('keydown', this.handleKeyDown.bind(this));
|
||||
}
|
||||
if (overlay) {
|
||||
overlay.addEventListener('click', this.handleOverlayClick.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
unbindEvents: function() {
|
||||
var textarea = document.getElementById('subscription-url-textarea');
|
||||
var overlay = document.getElementById('subscription-url-overlay');
|
||||
|
||||
if (textarea) {
|
||||
textarea.removeEventListener('keydown', this.handleKeyDown.bind(this));
|
||||
}
|
||||
if (overlay) {
|
||||
overlay.removeEventListener('click', this.handleOverlayClick.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
handleKeyDown: function(event) {
|
||||
@@ -3405,12 +3667,6 @@
|
||||
}
|
||||
},
|
||||
|
||||
handleOverlayClick: function(event) {
|
||||
if (event.target.id === 'subscription-url-overlay') {
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
submit: function() {
|
||||
var textarea = document.getElementById('subscription-url-textarea');
|
||||
if (!textarea) return;
|
||||
@@ -3478,6 +3734,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 +4530,12 @@
|
||||
DarkModeDetector.init();
|
||||
ConfigFileManager.init();
|
||||
|
||||
window.addEventListener('oc-overwrite-updated', function() {
|
||||
OverwriteSubscribeManager.load(true);
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
OverwriteSubscribeManager.init();
|
||||
SubscriptionManager.init();
|
||||
}, 300);
|
||||
|
||||
@@ -4745,7 +5251,7 @@
|
||||
}
|
||||
|
||||
function telegrampage() {
|
||||
url6 = 'https://t.me/ctcgfw_openwrt_discuss';
|
||||
url6 = 'https://t.me/openclash_group';
|
||||
winOpen(url6);
|
||||
}
|
||||
|
||||
@@ -5326,6 +5832,7 @@
|
||||
var filename = SubscriptionManager.extractFilename(currentConfig);
|
||||
localStorage.removeItem('sub_info_' + filename);
|
||||
SubscriptionManager.getSubscriptionInfo();
|
||||
OverwriteSubscribeManager.load(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5506,4 +6013,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;
|
||||
@@ -821,9 +815,9 @@ function sub_info_refresh_<%=idname%>(force) {
|
||||
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "sub_info_get")%>', {filename: "<%=filename%>"}, function(x, status) {
|
||||
var needsErrorHandling = false;
|
||||
if (x && x.status == 200 && status.providers) {
|
||||
if (x && x.status == 200 && status.providers.length > 0) {
|
||||
var save_info_length = (save_info && save_info.providers) ? save_info.providers.length : 0;
|
||||
var newProvidersLength = (status && status.providers) ? status.providers.length : 0;
|
||||
var newProvidersLength = status.providers.length;
|
||||
if (save_info_length !== newProvidersLength && save_info_length > newProvidersLength) {
|
||||
needsErrorHandling = true;
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,148 +1,110 @@
|
||||
<%+cbi/valueheader%>
|
||||
<div class="cbi-value-field" id="switch_dashboard_<%=self.option%>">
|
||||
<%:Collecting data...%>
|
||||
</div>
|
||||
<div class="cbi-value-field" id="delete_dashboard_<%=self.option%>">
|
||||
<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%>">
|
||||
<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Set to Default%>" onclick="return default_dashboard(this, '<%=self.option%>')"/>
|
||||
<div style="display: flex; flex-wrap: wrap; align-items: center; gap: 6px;">
|
||||
<div class="cbi-value-field" id="switch_dashboard_<%=self.option%>"><%:Collecting data...%></div>
|
||||
<div class="cbi-value-field" id="delete_dashboard_<%=self.option%>">
|
||||
<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%>">
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Set to Default%>" onclick="return default_dashboard(this, '<%=self.option%>')"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var btn_type_<%=self.option%> = "<%=self.option%>";
|
||||
var switch_dashboard_<%=self.option%> = document.getElementById('switch_dashboard_<%=self.option%>');
|
||||
var default_dashboard_<%=self.option%> = document.getElementById('default_dashboard_<%=self.option%>');
|
||||
var delete_dashboard_<%=self.option%> = document.getElementById('delete_dashboard_<%=self.option%>');
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "dashboard_type")%>', null, function(x, status) {
|
||||
if ( x && x.status == 200 ) {
|
||||
if ( btn_type_<%=self.option%> == "Dashboard" ) {
|
||||
if ( status.dashboard_type == "Meta" ) {
|
||||
switch_dashboard_<%=self.option%>.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Switch To Official Version%>" onclick="return switch_dashboard(this, btn_type_<%=self.option%>, \'Official\')"/>';
|
||||
}
|
||||
else {
|
||||
switch_dashboard_<%=self.option%>.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Switch To Meta Version%>" onclick="return switch_dashboard(this, btn_type_<%=self.option%>, \'Meta\')"/>';
|
||||
}
|
||||
}
|
||||
if ( btn_type_<%=self.option%> == "Yacd" ) {
|
||||
if ( status.yacd_type == "Meta" ) {
|
||||
switch_dashboard_<%=self.option%>.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Switch To Official Version%>" onclick="return switch_dashboard(this, btn_type_<%=self.option%>, \'Official\')"/>';
|
||||
}
|
||||
else {
|
||||
switch_dashboard_<%=self.option%>.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Switch To Meta Version%>" onclick="return switch_dashboard(this, btn_type_<%=self.option%>, \'Meta\')"/>';
|
||||
}
|
||||
}
|
||||
if ( btn_type_<%=self.option%> == "Metacubexd" ) {
|
||||
switch_dashboard_<%=self.option%>.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Update Metacubexd Version%>" onclick="return switch_dashboard(this, btn_type_<%=self.option%>, \'Official\')"/>';
|
||||
}
|
||||
if ( btn_type_<%=self.option%> == "Zashboard" ) {
|
||||
switch_dashboard_<%=self.option%>.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Update Zashboard Version%>" onclick="return switch_dashboard(this, btn_type_<%=self.option%>, \'Official\')"/>';
|
||||
}
|
||||
(function() {
|
||||
var name = "<%=self.option%>";
|
||||
|
||||
if ( status.default_dashboard == btn_type_<%=self.option%>.toLowerCase() ) {
|
||||
default_dashboard_<%=self.option%>.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Default%>" disabled="disabled" onclick="return default_dashboard(this, btn_type_<%=self.option%>)"/>';
|
||||
}
|
||||
function makeBtn(label, fn, args) {
|
||||
var argStr = args.map(function(a){ return "'" + a + "'"; }).join(', ');
|
||||
return '<input type="button" class="btn cbi-button cbi-button-apply" value="' + label + '" onclick="return ' + fn + '(this, ' + argStr + ')"/>';
|
||||
}
|
||||
|
||||
if ( !status[btn_type_<%=self.option%>.toLowerCase()] ) {
|
||||
default_dashboard_<%=self.option%>.firstElementChild.disabled = true;
|
||||
delete_dashboard_<%=self.option%>.firstElementChild.disabled = true;
|
||||
}
|
||||
var switchEl = document.getElementById('switch_dashboard_' + name);
|
||||
var defaultEl = document.getElementById('default_dashboard_' + name);
|
||||
var deleteEl = document.getElementById('delete_dashboard_' + name);
|
||||
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "dashboard_type")%>', null, function(x, status) {
|
||||
if (!x || x.status != 200) return;
|
||||
|
||||
var switchLabel;
|
||||
if (name == "Metacubexd") {
|
||||
switchLabel = ['<%:Update Metacubexd Version%>', 'Official'];
|
||||
} else if (name == "Zashboard") {
|
||||
switchLabel = ['<%:Update Zashboard Version%>', 'Official'];
|
||||
} else {
|
||||
var isMeta = (name == "Dashboard" ? status.dashboard_type : status.yacd_type) == "Meta";
|
||||
switchLabel = isMeta
|
||||
? ['<%:Switch To Official Version%>', 'Official']
|
||||
: ['<%:Switch To Meta Version%>', 'Meta'];
|
||||
}
|
||||
});
|
||||
switchEl.innerHTML = makeBtn(switchLabel[0], 'switch_dashboard', [name, switchLabel[1]]);
|
||||
|
||||
function switch_dashboard(btn, name, type)
|
||||
{
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Downloading File...%>';
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "switch_dashboard")%>', {name: name, type : type}, function(x, status) {
|
||||
if ( x && x.status == 200 ) {
|
||||
if ( status.download_state == "0" ) {
|
||||
if ( type == "Meta" ) {
|
||||
if ( name == "Dashboard" ) {
|
||||
document.getElementById("switch_dashboard_"+name).innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Switch Successful%> - <%:Switch To Official Version%>" onclick="return switch_dashboard(this, \'Dashboard\', \'Official\')"/>';
|
||||
}
|
||||
else
|
||||
{
|
||||
document.getElementById("switch_dashboard_"+name).innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Switch Successful%> - <%:Switch To Official Version%>" onclick="return switch_dashboard(this, \'Yacd\', \'Official\')"/>';
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( name == "Dashboard" ) {
|
||||
document.getElementById("switch_dashboard_"+name).innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Switch Successful%> - <%:Switch To Meta Version%>" onclick="return switch_dashboard(this, \'Dashboard\', \'Meta\')"/>';
|
||||
}
|
||||
else if ( name == "Yacd" )
|
||||
{
|
||||
document.getElementById("switch_dashboard_"+name).innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Switch Successful%> - <%:Switch To Meta Version%>" onclick="return switch_dashboard(this, \'Yacd\', \'Meta\')"/>';
|
||||
}
|
||||
else if ( name == "Metacubexd" ) {
|
||||
document.getElementById("switch_dashboard_"+name).innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Update Successful%> - <%:Update Metacubexd Version%>" onclick="return switch_dashboard(this, \'Metacubexd\', \'Official\')"/>';
|
||||
} else {
|
||||
document.getElementById("switch_dashboard_"+name).innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Update Successful%> - <%:Update Zashboard Version%>" onclick="return switch_dashboard(this, \'Zashboard\', \'Official\')"/>';
|
||||
}
|
||||
}
|
||||
document.getElementById("default_dashboard_"+name).firstElementChild.disabled = false;
|
||||
document.getElementById("delete_dashboard_"+name).firstElementChild.disabled = false;
|
||||
}
|
||||
else if ( status.download_state == "2" ) {
|
||||
btn.value = '<%:Unzip Error%>';
|
||||
}
|
||||
else {
|
||||
if ( name == "Metacubexd" || name == "Zashboard" ) {
|
||||
btn.value = '<%:Update Failed%>';
|
||||
}
|
||||
else {
|
||||
btn.value = '<%:Switch Failed%>';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
btn.disabled = false;
|
||||
return false;
|
||||
}
|
||||
if (status.default_dashboard == name.toLowerCase()) {
|
||||
defaultEl.innerHTML = makeBtn('<%:Default%>', 'default_dashboard', [name]);
|
||||
defaultEl.firstElementChild.disabled = true;
|
||||
}
|
||||
if (!status[name.toLowerCase()]) {
|
||||
defaultEl.firstElementChild.disabled = true;
|
||||
deleteEl.firstElementChild.disabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
function delete_dashboard(btn, name)
|
||||
{
|
||||
if ( confirm("<%:Are you sure you want to delete this panel?%>") ) {
|
||||
btn.disabled = true;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "delete_dashboard")%>', {name: name}, function(x, status) {
|
||||
if ( x && x.status == 200 ) {
|
||||
if ( status.delete_state == "1" ) {
|
||||
if ( document.getElementById('default_dashboard_' + name).firstElementChild.disabled ) {
|
||||
document.getElementById('default_dashboard_' + name).firstElementChild.value = '<%:Set to Default%>';
|
||||
}
|
||||
document.getElementById('default_dashboard_' + name).firstElementChild.disabled = true;
|
||||
}
|
||||
else {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
window.switch_dashboard = window.switch_dashboard || function(btn, n, type) {
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Downloading File...%>';
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "switch_dashboard")%>', {name: n, type: type}, function(x, status) {
|
||||
if (!x || x.status != 200) return;
|
||||
if (status.download_state == "0") {
|
||||
var isUpdate = (n == "Metacubexd" || n == "Zashboard");
|
||||
var nextLabel, nextType;
|
||||
if (isUpdate) {
|
||||
nextLabel = '<%:Update Successful%> - ' + (n == "Metacubexd" ? '<%:Update Metacubexd Version%>' : '<%:Update Zashboard Version%>');
|
||||
nextType = 'Official';
|
||||
} else {
|
||||
nextLabel = '<%:Switch Successful%> - ' + (type == "Meta" ? '<%:Switch To Official Version%>' : '<%:Switch To Meta Version%>');
|
||||
nextType = type == "Meta" ? 'Official' : 'Meta';
|
||||
}
|
||||
document.getElementById('switch_dashboard_' + n).innerHTML = makeBtn(nextLabel, 'switch_dashboard', [n, nextType]);
|
||||
document.getElementById('default_dashboard_' + n).firstElementChild.disabled = false;
|
||||
document.getElementById('delete_dashboard_' + n).firstElementChild.disabled = false;
|
||||
} else {
|
||||
btn.value = status.download_state == "2" ? '<%:Unzip Error%>'
|
||||
: (n == "Metacubexd" || n == "Zashboard") ? '<%:Update Failed%>' : '<%:Switch Failed%>';
|
||||
}
|
||||
btn.disabled = false;
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
function default_dashboard(btn, name)
|
||||
{
|
||||
btn.disabled = true;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "default_dashboard")%>', {name: name}, function(x, status) {
|
||||
if ( x && x.status == 200 ) {
|
||||
btn.value = '<%:Default%>';
|
||||
btn.disabled = true;
|
||||
var allBtns = document.querySelectorAll('[id^="default_dashboard_"]');
|
||||
for (var i = 0; i < allBtns.length; i++) {
|
||||
var btnEl = allBtns[i].firstElementChild;
|
||||
if (btnEl && btnEl !== btn && btnEl.value === '<%:Default%>') {
|
||||
btnEl.disabled = false;
|
||||
btnEl.value = '<%:Set to Default%>';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
window.delete_dashboard = window.delete_dashboard || function(btn, n) {
|
||||
if (!confirm("<%:Are you sure you want to delete this panel?%>")) return false;
|
||||
btn.disabled = true;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "delete_dashboard")%>', {name: n}, function(x, status) {
|
||||
if (!x || x.status != 200) { btn.disabled = false; return; }
|
||||
if (status.delete_state == "1") {
|
||||
var defBtn = document.getElementById('default_dashboard_' + n).firstElementChild;
|
||||
if (defBtn.disabled) defBtn.value = '<%:Set to Default%>';
|
||||
defBtn.disabled = true;
|
||||
} else btn.disabled = false;
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
window.default_dashboard = window.default_dashboard || function(btn, n) {
|
||||
btn.disabled = true;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "default_dashboard")%>', {name: n}, function(x, status) {
|
||||
if (!x || x.status != 200) { btn.disabled = false; return; }
|
||||
btn.value = '<%:Default%>';
|
||||
document.querySelectorAll('[id^="default_dashboard_"]').forEach(function(el) {
|
||||
var b = el.firstElementChild;
|
||||
if (b && b !== btn && b.value === '<%:Default%>') {
|
||||
b.disabled = false;
|
||||
b.value = '<%:Set to Default%>';
|
||||
}
|
||||
});
|
||||
});
|
||||
return false;
|
||||
};
|
||||
})();
|
||||
//]]></script>
|
||||
|
||||
<%+cbi/valuefooter%>
|
||||
<%+cbi/valuefooter%>
|
||||
@@ -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%>() {
|
||||
|
||||
@@ -1592,77 +1592,29 @@ msgstr "Error al descargar la lista CIDR de China (IPv4); inténtalo más tarde.
|
||||
msgid "Chnroute6 Cidr List Update Error, Please Try Again Later..."
|
||||
msgstr "Error al descargar la lista CIDR de China (IPv6); inténtalo más tarde."
|
||||
|
||||
msgid "Start Downloading GeoSite Database..."
|
||||
msgstr "Iniciando descarga de la base de datos GeoSite..."
|
||||
msgid "Download Failed: HTML Response Detected, Abort Update..."
|
||||
msgstr "Descarga fallida: respuesta HTML detectada, aborto de la actualización..."
|
||||
|
||||
msgid "GeoSite Database Download Success, Check Updated..."
|
||||
msgstr "GeoSite descargado correctamente, comprobando actualizaciones..."
|
||||
msgid "Download Failed: File Size Too Small, Abort Update..."
|
||||
msgstr "Descarga fallida: el archivo es demasiado pequeño, aborto de la actualización..."
|
||||
|
||||
msgid "GeoSite Database Has Been Updated, Starting To Replace The Old Version..."
|
||||
msgstr "GeoSite tiene una nueva versión; iniciando reemplazo..."
|
||||
msgid "Download Success, Check Updated..."
|
||||
msgstr "Descarga correcta, comprobando actualizaciones..."
|
||||
|
||||
msgid "GeoSite Database Update Successful!"
|
||||
msgstr "Base de datos GeoSite actualizada correctamente."
|
||||
msgid "Has Been Updated, Starting To Replace The Old Version..."
|
||||
msgstr "Tiene nueva versión; iniciando reemplazo..."
|
||||
|
||||
msgid "Updated GeoSite Database No Change, Do Nothing..."
|
||||
msgstr "GeoSite no presenta cambios; operación detenida."
|
||||
msgid "Update Successful!"
|
||||
msgstr "Actualizado correctamente."
|
||||
|
||||
msgid "GeoSite Database Update Error, Please Try Again Later..."
|
||||
msgstr "Error al descargar GeoSite; inténtalo más tarde."
|
||||
msgid "No Change, Do Nothing..."
|
||||
msgstr "Sin cambios, no hacer nada..."
|
||||
|
||||
msgid "Start Downloading GeoIP Dat..."
|
||||
msgstr "Iniciando descarga de GeoIP Dat..."
|
||||
msgid "Update Error, Please Try Again Later..."
|
||||
msgstr "Error al actualizar; inténtalo más tarde..."
|
||||
|
||||
msgid "GeoIP Dat Download Success, Check Updated..."
|
||||
msgstr "GeoIP Dat descargado correctamente, comprobando actualizaciones..."
|
||||
|
||||
msgid "GeoIP Dat Has Been Updated, Starting To Replace The Old Version..."
|
||||
msgstr "GeoIP Dat tiene una nueva versión; iniciando reemplazo..."
|
||||
|
||||
msgid "GeoIP Dat Update Successful!"
|
||||
msgstr "GeoIP Dat actualizado correctamente."
|
||||
|
||||
msgid "Updated GeoIP Dat No Change, Do Nothing..."
|
||||
msgstr "GeoIP Dat no presenta cambios; operación detenida."
|
||||
|
||||
msgid "GeoIP Dat Update Error, Please Try Again Later..."
|
||||
msgstr "Error al descargar GeoIP Dat; inténtalo más tarde."
|
||||
|
||||
msgid "Start Downloading Geoip Database..."
|
||||
msgstr "Iniciando descarga de la base de datos GEOIP..."
|
||||
|
||||
msgid "Geoip Database Download Success, Check Updated..."
|
||||
msgstr "GEOIP descargado correctamente, comprobando actualizaciones..."
|
||||
|
||||
msgid "Geoip Database Has Been Updated, Starting To Replace The Old Version..."
|
||||
msgstr "GEOIP tiene una nueva versión; iniciando reemplazo..."
|
||||
|
||||
msgid "Geoip Database Update Successful!"
|
||||
msgstr "Base de datos GEOIP actualizada correctamente."
|
||||
|
||||
msgid "Updated Geoip Database No Change, Do Nothing..."
|
||||
msgstr "GEOIP no presenta cambios; operación detenida."
|
||||
|
||||
msgid "Geoip Database Update Error, Please Try Again Later..."
|
||||
msgstr "Error al descargar GEOIP; inténtalo más tarde."
|
||||
|
||||
msgid "Start Downloading Geo ASN Database..."
|
||||
msgstr "Iniciando descarga de la base de datos GEO ASN..."
|
||||
|
||||
msgid "Geo ASN Database Download Success, Check Updated..."
|
||||
msgstr "GEO ASN descargado correctamente, comprobando actualizaciones..."
|
||||
|
||||
msgid "Geo ASN Database Has Been Updated, Starting To Replace The Old Version..."
|
||||
msgstr "GEO ASN tiene una nueva versión; iniciando reemplazo..."
|
||||
|
||||
msgid "Geo ASN Database Update Successful!"
|
||||
msgstr "Base de datos GEO ASN actualizada correctamente."
|
||||
|
||||
msgid "Updated Geo ASN Database No Change, Do Nothing..."
|
||||
msgstr "GEO ASN no presenta cambios; operación detenida."
|
||||
|
||||
msgid "Geo ASN Database Update Error, Please Try Again Later..."
|
||||
msgstr "Error al descargar GEO ASN; inténtalo más tarde."
|
||||
msgid "Start Downloading Database..."
|
||||
msgstr "Iniciando descarga de la base de datos..."
|
||||
|
||||
msgid "Start Getting"
|
||||
msgstr "Iniciando obtención de"
|
||||
@@ -1955,6 +1907,9 @@ msgstr "No se pudo analizar el archivo de configuración,"
|
||||
msgid "Edit Provider Path Failed,"
|
||||
msgstr "Fallo al modificar la ruta del proveedor,"
|
||||
|
||||
msgid "Edit Provider CDN Failed,"
|
||||
msgstr "Fallo al modificar la CDN del proveedor,"
|
||||
|
||||
msgid "Edit URL-Test Group Tolerance Option Failed,"
|
||||
msgstr "Fallo al modificar la tolerancia del grupo URL-Test,"
|
||||
|
||||
@@ -2111,77 +2066,11 @@ msgstr "Filtro de regiones"
|
||||
msgid "Unlock Nodes Filter"
|
||||
msgstr "Filtro de nodos desbloqueados"
|
||||
|
||||
msgid "Start Auto Select Proxy For Netflix Unlock..."
|
||||
msgstr "Iniciando selección automática de nodo para Netflix..."
|
||||
msgid "Start Auto Select Unlock Proxy For"
|
||||
msgstr "Iniciando selección automática de nodo"
|
||||
|
||||
msgid "Start Auto Select Proxy For Disney Plus Unlock..."
|
||||
msgstr "Iniciando selección automática de nodo para Disney Plus..."
|
||||
|
||||
msgid "Start Auto Select Proxy For HBO Max Unlock..."
|
||||
msgstr "Iniciando selección automática de nodo para HBO Max..."
|
||||
|
||||
msgid "Start Auto Select Proxy For YouTube Premium Unlock..."
|
||||
msgstr "Iniciando selección automática de nodo para YouTube Premium..."
|
||||
|
||||
msgid "Start Auto Select Proxy For TVB Anywhere+ Unlock..."
|
||||
msgstr "Iniciando selección automática de nodo para TVB Anywhere+..."
|
||||
|
||||
msgid "Start Auto Select Proxy For Amazon Prime Video Unlock..."
|
||||
msgstr "Iniciando selección automática de nodo para Amazon Prime Video..."
|
||||
|
||||
msgid "Start Auto Select Proxy For DAZN Unlock..."
|
||||
msgstr "Iniciando selección automática de nodo para DAZN..."
|
||||
|
||||
msgid "Start Auto Select Proxy For Paramount Plus Unlock..."
|
||||
msgstr "Iniciando selección automática de nodo para Paramount Plus..."
|
||||
|
||||
msgid "Start Auto Select Proxy For Discovery Plus Unlock..."
|
||||
msgstr "Iniciando selección automática de nodo para Discovery Plus..."
|
||||
|
||||
msgid "Start Auto Select Proxy For Bilibili Unlock..."
|
||||
msgstr "Iniciando selección automática de nodo para Bilibili..."
|
||||
|
||||
msgid "Start Auto Select Proxy For Google Not CN Unlock..."
|
||||
msgstr "Iniciando selección automática de nodo para Google fuera de China..."
|
||||
|
||||
msgid "Start Auto Select Proxy For OpenAI Unlock..."
|
||||
msgstr "Iniciando selección automática de nodo para OpenAI..."
|
||||
|
||||
msgid "Netflix Group:"
|
||||
msgstr "Grupo de Netflix:"
|
||||
|
||||
msgid "Disney Plus Group:"
|
||||
msgstr "Grupo de Disney Plus:"
|
||||
|
||||
msgid "HBO Max Group:"
|
||||
msgstr "Grupo de HBO Max:"
|
||||
|
||||
msgid "TVB Anywhere+ Group:"
|
||||
msgstr "Grupo de TVB Anywhere+:"
|
||||
|
||||
msgid "Amazon Prime Video Group:"
|
||||
msgstr "Grupo de Amazon Prime Video:"
|
||||
|
||||
msgid "YouTube Premium Group:"
|
||||
msgstr "Grupo de YouTube Premium:"
|
||||
|
||||
msgid "DAZN Group:"
|
||||
msgstr "Grupo de DAZN:"
|
||||
|
||||
msgid "Paramount Plus Group:"
|
||||
msgstr "Grupo de Paramount Plus:"
|
||||
|
||||
msgid "Discovery Plus Group:"
|
||||
msgstr "Grupo de Discovery Plus:"
|
||||
|
||||
msgid "Bilibili Group:"
|
||||
msgstr "Grupo de Bilibili:"
|
||||
|
||||
msgid "Google Group:"
|
||||
msgstr "Grupo de Google:"
|
||||
|
||||
msgid "OpenAI Group:"
|
||||
msgstr "Grupo de OpenAI:"
|
||||
msgid "Group:"
|
||||
msgstr "Grupo:"
|
||||
|
||||
msgid "full support"
|
||||
msgstr "desbloqueo completo"
|
||||
@@ -2666,8 +2555,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"
|
||||
@@ -2720,18 +2609,6 @@ msgstr "Habilita reglas por proceso (solo para procesos del router). Si no está
|
||||
msgid "Enable Process Rule"
|
||||
msgstr "Activar reglas por proceso"
|
||||
|
||||
msgid "Because there is a port range"
|
||||
msgstr "Debido a que existe un rango de puertos"
|
||||
|
||||
msgid "in the firewall rule settings"
|
||||
msgstr "en la configuración del firewall"
|
||||
|
||||
msgid "auto bypassing may cause the normal connection of the client not to reach the core, if necessary, please add your own in the access control!"
|
||||
msgstr "el bypass automático puede impedir que conexiones legítimas lleguen al núcleo. Añade reglas manuales si es necesario."
|
||||
|
||||
msgid "Start Add Port Bypassing Rules For Firewall Redirect and Firewall Rules..."
|
||||
msgstr "Añadiendo reglas de bypass de puertos para redirecciones de firewall..."
|
||||
|
||||
msgid "DNS Hijacking Mode is Dnsmasq Redirect..."
|
||||
msgstr "Modo de secuestro DNS: redirección Dnsmasq..."
|
||||
|
||||
@@ -3773,6 +3650,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 +3717,28 @@ 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"
|
||||
|
||||
msgid "Global User-Agent"
|
||||
msgstr "Agente de usuario global"
|
||||
|
||||
msgid "depended package reinstalling..."
|
||||
msgstr "reinstalando paquete dependiente..."
|
||||
|
||||
msgid "failed to install, please try to install it manually..."
|
||||
msgstr "no se pudo instalar; por favor, intenta instalarlo manualmente."
|
||||
@@ -1590,77 +1590,29 @@ msgstr "大陆 IP 白名单下载失败,请检查网络或稍后再试..."
|
||||
msgid "Chnroute6 Cidr List Update Error, Please Try Again Later..."
|
||||
msgstr "大陆 IPv6 白名单下载失败,请检查网络或稍后再试..."
|
||||
|
||||
msgid "Start Downloading GeoSite Database..."
|
||||
msgstr "开始下载 GeoSite 数据库..."
|
||||
msgid "Download Failed: HTML Response Detected, Abort Update..."
|
||||
msgstr "下载失败:检测到 HTML 响应(可能为 CDN 错误页面),终止更新..."
|
||||
|
||||
msgid "GeoSite Database Download Success, Check Updated..."
|
||||
msgstr "GeoSite 数据库下载成功,检查数据库版本是否更新..."
|
||||
msgid "Download Failed: File Size Too Small, Abort Update..."
|
||||
msgstr "下载失败:文件大小过小(可能被截断),终止更新..."
|
||||
|
||||
msgid "GeoSite Database Has Been Updated, Starting To Replace The Old Version..."
|
||||
msgstr "GeoSite 数据库版本有更新,开始替换数据库版本..."
|
||||
msgid "Download Success, Check Updated..."
|
||||
msgstr "下载成功,检查数据库是否已更新..."
|
||||
|
||||
msgid "GeoSite Database Update Successful!"
|
||||
msgstr "GeoSite 数据库更新成功!"
|
||||
msgid "Has Been Updated, Starting To Replace The Old Version..."
|
||||
msgstr "有新版本,开始替换旧版本..."
|
||||
|
||||
msgid "Updated GeoSite Database No Change, Do Nothing..."
|
||||
msgstr "GeoSite 数据库版本没有更新,停止继续操作..."
|
||||
msgid "Update Successful!"
|
||||
msgstr "更新成功!"
|
||||
|
||||
msgid "GeoSite Database Update Error, Please Try Again Later..."
|
||||
msgstr "GeoSite 数据库下载失败,请检查网络或稍后再试..."
|
||||
msgid "No Change, Do Nothing..."
|
||||
msgstr "无变化,停止操作..."
|
||||
|
||||
msgid "Start Downloading GeoIP Dat..."
|
||||
msgstr "开始下载 GeoIP Dat 数据库..."
|
||||
msgid "Update Error, Please Try Again Later..."
|
||||
msgstr "更新失败,请稍后再试..."
|
||||
|
||||
msgid "GeoIP Dat Download Success, Check Updated..."
|
||||
msgstr "GeoIP Dat 数据库下载成功,检查数据库版本是否更新..."
|
||||
|
||||
msgid "GeoIP Dat Has Been Updated, Starting To Replace The Old Version..."
|
||||
msgstr "GeoIP Dat 数据库版本有更新,开始替换数据库版本..."
|
||||
|
||||
msgid "GeoIP Dat Update Successful!"
|
||||
msgstr "GeoIP Dat 数据库更新成功!"
|
||||
|
||||
msgid "Updated GeoIP Dat No Change, Do Nothing..."
|
||||
msgstr "GeoIP Dat 数据库版本没有更新,停止继续操作..."
|
||||
|
||||
msgid "GeoIP Dat Update Error, Please Try Again Later..."
|
||||
msgstr "GeoIP Dat 数据库下载失败,请检查网络或稍后再试..."
|
||||
|
||||
msgid "Start Downloading Geoip Database..."
|
||||
msgstr "开始下载 GEOIP 数据库..."
|
||||
|
||||
msgid "Geoip Database Download Success, Check Updated..."
|
||||
msgstr "GEOIP 数据库下载成功,检查数据库版本是否更新..."
|
||||
|
||||
msgid "Geoip Database Has Been Updated, Starting To Replace The Old Version..."
|
||||
msgstr "GEOIP 数据库版本有更新,开始替换数据库版本..."
|
||||
|
||||
msgid "Geoip Database Update Successful!"
|
||||
msgstr "GEOIP 数据库更新成功!"
|
||||
|
||||
msgid "Updated Geoip Database No Change, Do Nothing..."
|
||||
msgstr "GEOIP 数据库版本没有更新,停止继续操作..."
|
||||
|
||||
msgid "Geoip Database Update Error, Please Try Again Later..."
|
||||
msgstr "GEOIP 数据库下载失败,请检查网络或稍后再试..."
|
||||
|
||||
msgid "Start Downloading Geo ASN Database..."
|
||||
msgstr "开始下载 GEO ASN 数据库..."
|
||||
|
||||
msgid "Geo ASN Database Download Success, Check Updated..."
|
||||
msgstr "GEO ASN 数据库下载成功,检查数据库版本是否更新..."
|
||||
|
||||
msgid "Geo ASN Database Has Been Updated, Starting To Replace The Old Version..."
|
||||
msgstr "GEO ASN 数据库版本有更新,开始替换数据库版本..."
|
||||
|
||||
msgid "Geo ASN Database Update Successful!"
|
||||
msgstr "GEO ASN 数据库更新成功!"
|
||||
|
||||
msgid "Updated Geo ASN Database No Change, Do Nothing..."
|
||||
msgstr "GEO ASN 数据库版本没有更新,停止继续操作..."
|
||||
|
||||
msgid "Geo ASN Database Update Error, Please Try Again Later..."
|
||||
msgstr "GEO ASN 数据库下载失败,请检查网络或稍后再试..."
|
||||
msgid "Start Downloading Database..."
|
||||
msgstr "开始下载数据库..."
|
||||
|
||||
msgid "Start Getting"
|
||||
msgstr "开始获取"
|
||||
@@ -1953,6 +1905,9 @@ msgstr "配置文件解析失败,"
|
||||
msgid "Edit Provider Path Failed,"
|
||||
msgstr "修改集路径失败,"
|
||||
|
||||
msgid "Edit Provider CDN Failed,"
|
||||
msgstr "修改集 CDN 地址失败,"
|
||||
|
||||
msgid "Edit URL-Test Group Tolerance Option Failed,"
|
||||
msgstr "修改 URL-Test 策略组容忍值失败,"
|
||||
|
||||
@@ -2109,77 +2064,11 @@ msgstr "解锁区域筛选"
|
||||
msgid "Unlock Nodes Filter"
|
||||
msgstr "解锁节点筛选"
|
||||
|
||||
msgid "Start Auto Select Proxy For Netflix Unlock..."
|
||||
msgstr "开始自动选择(检测)Netflix 解锁节点..."
|
||||
msgid "Start Auto Select Unlock Proxy For"
|
||||
msgstr "开始自动选择(检测)解锁节点"
|
||||
|
||||
msgid "Start Auto Select Proxy For Disney Plus Unlock..."
|
||||
msgstr "开始自动选择(检测)Disney Plus 解锁节点..."
|
||||
|
||||
msgid "Start Auto Select Proxy For HBO Max Unlock..."
|
||||
msgstr "开始自动选择(检测)HBO Max 解锁节点..."
|
||||
|
||||
msgid "Start Auto Select Proxy For YouTube Premium Unlock..."
|
||||
msgstr "开始自动选择(检测)YouTube Premium 解锁节点..."
|
||||
|
||||
msgid "Start Auto Select Proxy For TVB Anywhere+ Unlock..."
|
||||
msgstr "开始自动选择(检测)TVB Anywhere+ 解锁节点..."
|
||||
|
||||
msgid "Start Auto Select Proxy For Amazon Prime Video Unlock..."
|
||||
msgstr "开始自动选择(检测)Amazon Prime Video 解锁节点..."
|
||||
|
||||
msgid "Start Auto Select Proxy For DAZN Unlock..."
|
||||
msgstr "开始自动选择(检测)DAZN 解锁节点..."
|
||||
|
||||
msgid "Start Auto Select Proxy For Paramount Plus Unlock..."
|
||||
msgstr "开始自动选择(检测)Paramount Plus 解锁节点..."
|
||||
|
||||
msgid "Start Auto Select Proxy For Discovery Plus Unlock..."
|
||||
msgstr "开始自动选择(检测)Discovery Plus 解锁节点..."
|
||||
|
||||
msgid "Start Auto Select Proxy For Bilibili Unlock..."
|
||||
msgstr "开始自动选择(检测)Bilibili 解锁节点..."
|
||||
|
||||
msgid "Start Auto Select Proxy For Google Not CN Unlock..."
|
||||
msgstr "开始自动选择(检测)Google 非送中节点..."
|
||||
|
||||
msgid "Start Auto Select Proxy For OpenAI Unlock..."
|
||||
msgstr "开始自动选择(检测)OpenAI 解锁节点..."
|
||||
|
||||
msgid "Netflix Group:"
|
||||
msgstr "Netflix 策略组:"
|
||||
|
||||
msgid "Disney Plus Group:"
|
||||
msgstr "Disney Plus 策略组:"
|
||||
|
||||
msgid "HBO Max Group:"
|
||||
msgstr "HBO Max 策略组:"
|
||||
|
||||
msgid "TVB Anywhere+ Group:"
|
||||
msgstr "TVB Anywhere+ 策略组:"
|
||||
|
||||
msgid "Amazon Prime Video Group:"
|
||||
msgstr "Amazon Prime Video 策略组:"
|
||||
|
||||
msgid "YouTube Premium Group:"
|
||||
msgstr "YouTube Premium 策略组:"
|
||||
|
||||
msgid "DAZN Group:"
|
||||
msgstr "DAZN 策略组:"
|
||||
|
||||
msgid "Paramount Plus Group:"
|
||||
msgstr "Paramount Plus 策略组:"
|
||||
|
||||
msgid "Discovery Plus Group:"
|
||||
msgstr "Discovery Plus 策略组:"
|
||||
|
||||
msgid "Bilibili Group:"
|
||||
msgstr "Bilibili 策略组:"
|
||||
|
||||
msgid "Google Group:"
|
||||
msgstr "Google 策略组:"
|
||||
|
||||
msgid "OpenAI Group:"
|
||||
msgstr "OpenAI 策略组:"
|
||||
msgid "Group:"
|
||||
msgstr "策略组:"
|
||||
|
||||
msgid "full support"
|
||||
msgstr "完整解锁"
|
||||
@@ -2437,7 +2326,7 @@ msgid "Geodata Loader Mode"
|
||||
msgstr "Geodata 数据加载方式"
|
||||
|
||||
msgid "Memconservative"
|
||||
msgstr "小内存模式"
|
||||
msgstr "低内存模式"
|
||||
|
||||
msgid "Standard"
|
||||
msgstr "标准模式"
|
||||
@@ -2664,8 +2553,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 的连接"
|
||||
@@ -2718,18 +2607,6 @@ msgstr "是否启用进程规则,仅能匹配路由器自身进程,在路由
|
||||
msgid "Enable Process Rule"
|
||||
msgstr "启用进程规则"
|
||||
|
||||
msgid "Because there is a port range"
|
||||
msgstr "因为端口范围"
|
||||
|
||||
msgid "in the firewall rule settings"
|
||||
msgstr "存在于防火墙规则设置"
|
||||
|
||||
msgid "auto bypassing may cause the normal connection of the client not to reach the core, if necessary, please add your own in the access control!"
|
||||
msgstr "自动绕过后可能造成客户端的正常连接无法到达内核,如有需要,请在插件设置-来源流量访问控制中自行添加!"
|
||||
|
||||
msgid "Start Add Port Bypassing Rules For Firewall Redirect and Firewall Rules..."
|
||||
msgstr "正在根据防火墙端口转发和防火墙通信规则添加端口绕过规则..."
|
||||
|
||||
msgid "DNS Hijacking Mode is Dnsmasq Redirect..."
|
||||
msgstr "DNS 劫持模式为 Dnsmasq 转发..."
|
||||
|
||||
@@ -3780,6 +3657,12 @@ msgstr "编辑模块信息"
|
||||
msgid "Subscription"
|
||||
msgstr "订阅"
|
||||
|
||||
msgid "HTTP Module"
|
||||
msgstr "远程模块"
|
||||
|
||||
msgid "File Module"
|
||||
msgstr "本地模块"
|
||||
|
||||
msgid "Overwrite Module"
|
||||
msgstr "覆写模块"
|
||||
|
||||
@@ -3841,4 +3724,28 @@ 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 "配置已选择"
|
||||
|
||||
msgid "Global User-Agent"
|
||||
msgstr "全局 User-Agent"
|
||||
|
||||
msgid "depended package reinstalling..."
|
||||
msgstr "依赖正在重新安装..."
|
||||
|
||||
msgid "failed to install, please try to install it manually..."
|
||||
msgstr "依赖安装失败,请重试手动安装..."
|
||||
File diff suppressed because it is too large.
Load diff
Binary file not shown.
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 201 KiB |
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: 启用)
|
||||
@@ -117,6 +116,7 @@
|
||||
# GEOASN_CUSTOM_URL (GeoASN 数据自定义更新地址, URL or default)
|
||||
|
||||
# --- 其它功能 ---
|
||||
# GLOBAL_UA (全局 User-Agent, 0:不覆写,或具体 UA)
|
||||
# SMALL_FLASH_MEMORY (小闪存模式, 0: 禁用, 1: 启用)
|
||||
# DISABLE_QUIC_GO_GSO (禁用 QUIC GO GSO, 0: 插件不覆写, 1: 启用)
|
||||
# DELAY_START (延时启动, 0: 禁用 or 秒数)
|
||||
@@ -209,6 +209,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'
|
||||
|
||||
# ==========================================================
|
||||
# ==== 以下为仅修改插件设置示例 ====
|
||||
# ==========================================================
|
||||
|
||||
Binary file not shown.
@@ -174,8 +174,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
|
||||
@@ -264,6 +266,6 @@ sed -i '/.*kB maximum content size*/c\export let HTTP_MAX_CONTENT = 1024*10240;
|
||||
|
||||
/etc/init.d/uhttpd restart >/dev/null 2>&1
|
||||
|
||||
rm -f /tmp/luci-indexcache >/dev/null 2>&1
|
||||
rm -f /tmp/luci-indexcache.* >/dev/null 2>&1
|
||||
rm -rf /tmp/luci-indexcache /tmp/luci-indexcache.* /var/luci-indexcache /var/luci-indexcache.* >/dev/null 2>&1
|
||||
rm -rf /tmp/luci-modulecache /var/luci-modulecache >/dev/null 2>&1
|
||||
exit 0
|
||||
@@ -1,119 +1,457 @@
|
||||
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)
|
||||
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
|
||||
load(processed_content, *args, **kwargs)
|
||||
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
|
||||
yaml_content = original_dump(obj, **options)
|
||||
processed = fix_short_id_quotes(yaml_content)
|
||||
if io.nil?
|
||||
processed
|
||||
elsif io.respond_to?(:write)
|
||||
io.write(processed)
|
||||
io
|
||||
else
|
||||
processed
|
||||
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 = /^["'].*["']$/
|
||||
# fix_short_id_quotes:
|
||||
# Purpose: ensure YAML `short-id` values are emitted with the intended
|
||||
# representation when dumping or re-writing YAML files.
|
||||
# Behavior:
|
||||
# - Non-null, non-empty scalar `short-id` values (and items inside
|
||||
# `short-id` sequences) are written as double-quoted strings.
|
||||
# - Empty strings are preserved as empty quoted strings ("" remains "").
|
||||
# - Null values are preserved and may be emitted explicitly (e.g. `! "").
|
||||
# Lastly, use gsub to clean up any `! ""` tags
|
||||
# Examples:
|
||||
# Input: short-id: 00000000 -> Output: short-id: "00000000"
|
||||
# Input: short-id: "" -> Output: short-id: ""
|
||||
# Input: short-id: "abc123" -> Output: short-id: "abc123"
|
||||
# Input: short-id: "1600e237" -> Output: short-id: "1600e237"
|
||||
# Input: short-id: null -> Output: short-id: ""
|
||||
|
||||
# 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
|
||||
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
|
||||
stream = Psych.parse_stream(yaml_content)
|
||||
|
||||
begin
|
||||
# First, normalize inline-map style unquoted short-id.
|
||||
processed = yaml_content.gsub(INLINE_SHORT_ID_REGEX) do
|
||||
"#{$1}\"#{$2}\""
|
||||
end
|
||||
traverse = lambda do |node|
|
||||
case node
|
||||
when Psych::Nodes::Mapping
|
||||
children = node.children || []
|
||||
i = 0
|
||||
while i < children.length
|
||||
key = children[i]
|
||||
val = children[i + 1]
|
||||
if key.is_a?(Psych::Nodes::Scalar) && key.value == 'short-id'
|
||||
if val.is_a?(Psych::Nodes::Scalar)
|
||||
is_null_scalar = (val.tag == 'tag:yaml.org,2002:null') || (val.tag == '!!null') || (val.value =~ /^\s*(~|null|NULL|Null)\s*$/)
|
||||
unless is_null_scalar
|
||||
val.tag = nil
|
||||
val.style = defined?(Psych::Nodes::Scalar::DOUBLE_QUOTED) ? Psych::Nodes::Scalar::DOUBLE_QUOTED : 2
|
||||
end
|
||||
elsif val.is_a?(Psych::Nodes::Sequence)
|
||||
val.children.each do |child|
|
||||
if child.is_a?(Psych::Nodes::Scalar)
|
||||
is_null_child = (child.tag == 'tag:yaml.org,2002:null') || (child.tag == '!!null') || (child.value =~ /^\s*(~|null|NULL|Null)\s*$/)
|
||||
unless is_null_child
|
||||
child.tag = nil
|
||||
child.style = defined?(Psych::Nodes::Scalar::DOUBLE_QUOTED) ? Psych::Nodes::Scalar::DOUBLE_QUOTED : 2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
traverse.call(key) if key.respond_to?(:children)
|
||||
traverse.call(val) if val.respond_to?(:children)
|
||||
end
|
||||
i += 2
|
||||
end
|
||||
when Psych::Nodes::Sequence
|
||||
(node.children || []).each { |c| traverse.call(c) }
|
||||
when Psych::Nodes::Document, Psych::Nodes::Stream
|
||||
(node.children || []).each { |c| traverse.call(c) }
|
||||
else
|
||||
if node.respond_to?(:children)
|
||||
(node.children || []).each { |c| traverse.call(c) }
|
||||
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 <= 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
|
||||
stream.children.each do |doc_node|
|
||||
if doc_node.is_a?(Psych::Nodes::Document)
|
||||
traverse.call(doc_node.root) if doc_node.root
|
||||
end
|
||||
end
|
||||
|
||||
if stream.respond_to?(:to_yaml)
|
||||
processed_yaml = stream.to_yaml
|
||||
processed_yaml = processed_yaml.gsub(/^([ \t]*short-id:\s*)!\s*/, "\\1")
|
||||
processed_yaml
|
||||
else
|
||||
yaml_content
|
||||
end
|
||||
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
|
||||
@@ -86,17 +86,6 @@ DOWNLOAD_RESULT=$?
|
||||
|
||||
config_cus_up()
|
||||
{
|
||||
if [ -z "$CONFIG_PATH" ]; then
|
||||
for file_name in /etc/openclash/config/*
|
||||
do
|
||||
if [ -f "$file_name" ]; then
|
||||
CONFIG_PATH=$file_name
|
||||
break
|
||||
fi
|
||||
done
|
||||
uci -q set openclash.config.config_path="$CONFIG_PATH"
|
||||
uci commit openclash
|
||||
fi
|
||||
if [ -z "$subscribe_url_param" ]; then
|
||||
if [ -n "$key_match_param" ] || [ -n "$key_ex_match_param" ]; then
|
||||
LOG_OUT "Config File【$name】Start Picking Nodes..."
|
||||
@@ -192,6 +181,10 @@ config_su_check()
|
||||
mv "$CFG_FILE" "$CONFIG_FILE" 2>/dev/null
|
||||
LOG_OUT "Config File【$name】Update Successful!"
|
||||
fi
|
||||
if [ -z "$CONFIG_PATH" ]; then
|
||||
uci -q set openclash.config.config_path="$CONFIG_FILE"
|
||||
uci commit openclash
|
||||
fi
|
||||
if [ "$CONFIG_FILE" == "$CONFIG_PATH" ]; then
|
||||
restart=1
|
||||
fi
|
||||
@@ -335,13 +328,13 @@ sub_info_get()
|
||||
config_get "udp" "$section" "udp" ""
|
||||
config_get "skip_cert_verify" "$section" "skip_cert_verify" ""
|
||||
config_get "sort" "$section" "sort" ""
|
||||
config_get "convert_address" "$section" "convert_address" ""
|
||||
config_get "convert_address" "$section" "convert_address" "https://api.asailor.org/sub"
|
||||
config_get "template" "$section" "template" ""
|
||||
config_get "node_type" "$section" "node_type" ""
|
||||
config_get "rule_provider" "$section" "rule_provider" ""
|
||||
config_get "custom_template_url" "$section" "custom_template_url" ""
|
||||
config_get "de_ex_keyword" "$section" "de_ex_keyword" ""
|
||||
config_get "sub_ua" "$section" "sub_ua" "clash.meta"
|
||||
config_get "sub_ua" "$section" "sub_ua" "clash-verge/v2.4.5"
|
||||
|
||||
CONFIG_FILE="/etc/openclash/config/$name.yaml"
|
||||
CFG_FILE="/tmp/$name.yaml"
|
||||
|
||||
@@ -74,7 +74,7 @@ DOWNLOAD_FILE_CURL() {
|
||||
fi
|
||||
else
|
||||
CURL_OUTPUT=$(curl -w "\n%{http_code}" -SsL --connect-timeout 30 -m 60 --speed-time 30 --speed-limit 1 --retry 2 -H "User-Agent: ${DOWNLOAD_UA}" "$DOWNLOAD_URL" -o "$DOWNLOAD_PATH" 2>&1)
|
||||
EXIR_CODE=${PIPESTATUS[0]}
|
||||
EXIR_CODE=$?
|
||||
HTTP_CODE=$(echo "$CURL_OUTPUT" | tail -n1)
|
||||
|
||||
if [ "$EXIR_CODE" -ne 0 ] || [ "$HTTP_CODE" -ne 200 ]; then
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
#!/bin/bash
|
||||
. /usr/share/openclash/openclash_ps.sh
|
||||
. /usr/share/openclash/log.sh
|
||||
. /usr/share/openclash/openclash_curl.sh
|
||||
. /usr/share/openclash/uci.sh
|
||||
|
||||
set_lock() {
|
||||
exec 888>"/tmp/lock/openclash_update_databases.lock" 2>/dev/null
|
||||
flock -x 888 2>/dev/null
|
||||
}
|
||||
|
||||
del_lock() {
|
||||
flock -u 888 2>/dev/null
|
||||
rm -rf "/tmp/lock/openclash_update_databases.lock" 2>/dev/null
|
||||
}
|
||||
|
||||
set_lock
|
||||
inc_job_counter
|
||||
|
||||
small_flash_memory=$(uci_get_config "small_flash_memory")
|
||||
github_address_mod=$(uci_get_config "github_address_mod" || echo 0)
|
||||
restart=0
|
||||
|
||||
update_one() {
|
||||
local type="$1"
|
||||
local custom_key="$2"
|
||||
local tmpfile="$3"
|
||||
local path="$4"
|
||||
local name="$5"
|
||||
local min_size=10240
|
||||
local download_url=""
|
||||
|
||||
local custom_url=$(uci_get_config "$custom_key")
|
||||
|
||||
if [ "$small_flash_memory" != "1" ]; then
|
||||
mkdir -p /etc/openclash
|
||||
else
|
||||
mkdir -p /tmp/etc/openclash
|
||||
fi
|
||||
|
||||
case "$type" in
|
||||
geoip)
|
||||
if [ -z "$custom_url" ]; then
|
||||
if [ "$github_address_mod" != "0" ]; then
|
||||
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
|
||||
download_url="${github_address_mod}gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
|
||||
else
|
||||
download_url="${github_address_mod}https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||
fi
|
||||
else
|
||||
download_url="https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||
fi
|
||||
else
|
||||
download_url="$custom_url"
|
||||
fi
|
||||
;;
|
||||
geosite)
|
||||
if [ -z "$custom_url" ]; then
|
||||
if [ "$github_address_mod" != "0" ]; then
|
||||
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
|
||||
download_url="${github_address_mod}gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
|
||||
else
|
||||
download_url="${github_address_mod}https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||
fi
|
||||
else
|
||||
download_url="https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||
fi
|
||||
else
|
||||
download_url="$custom_url"
|
||||
fi
|
||||
;;
|
||||
geoasn)
|
||||
if [ -z "$custom_url" ]; then
|
||||
if [ "$github_address_mod" != "0" ]; then
|
||||
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
|
||||
download_url="${github_address_mod}gh/xishang0128/geoip@release/GeoLite2-ASN.mmdb"
|
||||
else
|
||||
download_url="${github_address_mod}https://github.com/xishang0128/geoip/releases/latest/download/GeoLite2-ASN.mmdb"
|
||||
fi
|
||||
else
|
||||
download_url="https://github.com/xishang0128/geoip/releases/latest/download/GeoLite2-ASN.mmdb"
|
||||
fi
|
||||
else
|
||||
download_url="$custom_url"
|
||||
fi
|
||||
;;
|
||||
ipdb)
|
||||
if [ -z "$custom_url" ]; then
|
||||
if [ "$github_address_mod" != "0" ]; then
|
||||
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
|
||||
download_url="${github_address_mod}gh/alecthw/mmdb_china_ip_list@release/lite/Country.mmdb"
|
||||
else
|
||||
download_url="${github_address_mod}https://raw.githubusercontent.com/alecthw/mmdb_china_ip_list/release/lite/Country.mmdb"
|
||||
fi
|
||||
else
|
||||
download_url="https://raw.githubusercontent.com/alecthw/mmdb_china_ip_list/release/lite/Country.mmdb"
|
||||
fi
|
||||
else
|
||||
download_url="$custom_url"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
LOG_OUT "【$name】Start Downloading Database..."
|
||||
|
||||
DOWNLOAD_FILE_CURL "$download_url" "/tmp/$tmpfile" "$path"
|
||||
DOWNLOAD_RESULT=$?
|
||||
if [ "$DOWNLOAD_RESULT" -eq 0 ] && [ -s "/tmp/$tmpfile" ]; then
|
||||
if head -c 512 "/tmp/$tmpfile" | grep -qiE "<!doctype|<html|<head|<body"; then
|
||||
LOG_OUT "【$name】Download Failed: HTML Response Detected, Abort Update..."
|
||||
rm -rf "/tmp/$tmpfile"
|
||||
elif [ $(du -b "/tmp/$tmpfile" 2>/dev/null | awk '{print $1}' || echo "$min_size") -lt "$min_size" ]; then
|
||||
LOG_OUT "【$name】Download Failed: File Size Too Small, Abort Update..."
|
||||
rm -rf "/tmp/$tmpfile"
|
||||
else
|
||||
LOG_OUT "【$name】Download Success, Check Updated..."
|
||||
cmp -s "/tmp/$tmpfile" "$path"
|
||||
if [ "$?" -ne 0 ]; then
|
||||
LOG_OUT "【$name】Has Been Updated, Starting To Replace The Old Version..."
|
||||
case "$type" in
|
||||
geoip)
|
||||
rm -rf "/etc/openclash/geoip.dat"
|
||||
;;
|
||||
geosite)
|
||||
rm -rf "/etc/openclash/geosite.dat"
|
||||
;;
|
||||
geoasn)
|
||||
rm -rf "/etc/openclash/GeoLite2-ASN.mmdb"
|
||||
;;
|
||||
esac
|
||||
mv "/tmp/$tmpfile" "$path" >/dev/null 2>&1
|
||||
LOG_OUT "【$name】Update Successful!"
|
||||
restart=1
|
||||
else
|
||||
LOG_OUT "【$name】No Change, Do Nothing..."
|
||||
fi
|
||||
fi
|
||||
elif [ "$DOWNLOAD_RESULT" -eq 2 ]; then
|
||||
LOG_OUT "【$name】No Change, Do Nothing..."
|
||||
else
|
||||
LOG_OUT "【$name】Update Error, Please Try Again Later..."
|
||||
fi
|
||||
|
||||
rm -rf "/tmp/$tmpfile" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
all)
|
||||
update_one geoip "geoip_custom_url" "GeoIP.dat" "/etc/openclash/GeoIP.dat" "GeoIP.dat"
|
||||
update_one geosite "geosite_custom_url" "GeoSite.dat" "/etc/openclash/GeoSite.dat" "GeoSite.dat"
|
||||
update_one geoasn "geoasn_custom_url" "GeoLite2-ASN.mmdb" "/etc/openclash/ASN.mmdb" "ASN.mmdb"
|
||||
update_one ipdb "geo_custom_url" "Country.mmdb" "/etc/openclash/Country.mmdb" "Country.mmdb"
|
||||
;;
|
||||
geoip)
|
||||
update_one geoip "geoip_custom_url" "GeoIP.dat" "/etc/openclash/GeoIP.dat" "GeoIP.dat"
|
||||
;;
|
||||
geosite)
|
||||
update_one geosite "geosite_custom_url" "GeoSite.dat" "/etc/openclash/GeoSite.dat" "GeoSite.dat"
|
||||
;;
|
||||
geoasn)
|
||||
update_one geoasn "geoasn_custom_url" "GeoLite2-ASN.mmdb" "/etc/openclash/ASN.mmdb" "ASN.mmdb"
|
||||
;;
|
||||
ipdb)
|
||||
update_one ipdb "geo_custom_url" "Country.mmdb" "/etc/openclash/Country.mmdb" "Country.mmdb"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {all|geoip|geosite|geoasn|ipdb}"
|
||||
del_lock
|
||||
dec_job_counter_and_restart "$restart"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
SLOG_CLEAN
|
||||
dec_job_counter_and_restart "$restart"
|
||||
del_lock
|
||||
@@ -1,70 +0,0 @@
|
||||
#!/bin/bash
|
||||
. /usr/share/openclash/openclash_ps.sh
|
||||
. /usr/share/openclash/log.sh
|
||||
. /usr/share/openclash/openclash_curl.sh
|
||||
. /usr/share/openclash/uci.sh
|
||||
|
||||
set_lock() {
|
||||
exec 874>"/tmp/lock/openclash_geoasn.lock" 2>/dev/null
|
||||
flock -x 874 2>/dev/null
|
||||
}
|
||||
|
||||
del_lock() {
|
||||
flock -u 874 2>/dev/null
|
||||
rm -rf "/tmp/lock/openclash_geoasn.lock" 2>/dev/null
|
||||
}
|
||||
|
||||
set_lock
|
||||
inc_job_counter
|
||||
|
||||
small_flash_memory=$(uci_get_config "small_flash_memory")
|
||||
GEOASN_CUSTOM_URL=$(uci_get_config "geoasn_custom_url")
|
||||
github_address_mod=$(uci_get_config "github_address_mod" || echo 0)
|
||||
restart=0
|
||||
|
||||
if [ "$small_flash_memory" != "1" ]; then
|
||||
geoasn_path="/etc/openclash/ASN.mmdb"
|
||||
mkdir -p /etc/openclash
|
||||
else
|
||||
geoasn_path="/tmp/etc/openclash/ASN.mmdb"
|
||||
mkdir -p /tmp/etc/openclash
|
||||
fi
|
||||
LOG_OUT "Start Downloading Geo ASN Database..."
|
||||
if [ -z "$GEOASN_CUSTOM_URL" ]; then
|
||||
if [ "$github_address_mod" != "0" ]; then
|
||||
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
|
||||
DOWNLOAD_URL="${github_address_mod}gh/xishang0128/geoip@release/GeoLite2-ASN.mmdb"
|
||||
else
|
||||
DOWNLOAD_URL="${github_address_mod}https://github.com/xishang0128/geoip/releases/latest/download/GeoLite2-ASN.mmdb"
|
||||
fi
|
||||
else
|
||||
DOWNLOAD_URL="https://github.com/xishang0128/geoip/releases/latest/download/GeoLite2-ASN.mmdb"
|
||||
fi
|
||||
else
|
||||
DOWNLOAD_URL=$GEOASN_CUSTOM_URL
|
||||
fi
|
||||
DOWNLOAD_FILE_CURL "$DOWNLOAD_URL" "/tmp/GeoLite2-ASN.mmdb" "$geoasn_path"
|
||||
DOWNLOAD_RESULT=$?
|
||||
if [ "$DOWNLOAD_RESULT" -eq 0 ] && [ -s "/tmp/GeoLite2-ASN.mmdb" ]; then
|
||||
LOG_OUT "Geo ASN Database Download Success, Check Updated..."
|
||||
cmp -s /tmp/GeoLite2-ASN.mmdb "$geoasn_path"
|
||||
if [ "$?" -ne "0" ]; then
|
||||
LOG_OUT "Geo ASN Database Has Been Updated, Starting To Replace The Old Version..."
|
||||
rm -rf "/etc/openclash/GeoLite2-ASN.mmdb"
|
||||
mv /tmp/GeoLite2-ASN.mmdb "$geoasn_path" >/dev/null 2>&1
|
||||
LOG_OUT "Geo ASN Database Update Successful!"
|
||||
restart=1
|
||||
else
|
||||
LOG_OUT "Updated Geo ASN Database No Change, Do Nothing..."
|
||||
fi
|
||||
elif [ "$DOWNLOAD_RESULT" -eq 2 ]; then
|
||||
LOG_OUT "Updated Geo ASN Database No Change, Do Nothing..."
|
||||
else
|
||||
LOG_OUT "Geo ASN Database Update Error, Please Try Again Later..."
|
||||
fi
|
||||
|
||||
rm -rf /tmp/GeoLite2-ASN.mmdb >/dev/null 2>&1
|
||||
|
||||
SLOG_CLEAN
|
||||
dec_job_counter_and_restart "$restart"
|
||||
del_lock
|
||||
@@ -1,70 +0,0 @@
|
||||
#!/bin/bash
|
||||
. /usr/share/openclash/openclash_ps.sh
|
||||
. /usr/share/openclash/log.sh
|
||||
. /usr/share/openclash/openclash_curl.sh
|
||||
. /usr/share/openclash/uci.sh
|
||||
|
||||
set_lock() {
|
||||
exec 873>"/tmp/lock/openclash_geoip.lock" 2>/dev/null
|
||||
flock -x 873 2>/dev/null
|
||||
}
|
||||
|
||||
del_lock() {
|
||||
flock -u 873 2>/dev/null
|
||||
rm -rf "/tmp/lock/openclash_geoip.lock" 2>/dev/null
|
||||
}
|
||||
|
||||
set_lock
|
||||
inc_job_counter
|
||||
|
||||
small_flash_memory=$(uci_get_config "small_flash_memory")
|
||||
GEOIP_CUSTOM_URL=$(uci_get_config "geoip_custom_url")
|
||||
github_address_mod=$(uci_get_config "github_address_mod" || echo 0)
|
||||
restart=0
|
||||
|
||||
if [ "$small_flash_memory" != "1" ]; then
|
||||
geoip_path="/etc/openclash/GeoIP.dat"
|
||||
mkdir -p /etc/openclash
|
||||
else
|
||||
geoip_path="/tmp/etc/openclash/GeoIP.dat"
|
||||
mkdir -p /tmp/etc/openclash
|
||||
fi
|
||||
LOG_OUT "Start Downloading GeoIP Dat..."
|
||||
if [ -z "$GEOIP_CUSTOM_URL" ]; then
|
||||
if [ "$github_address_mod" != "0" ]; then
|
||||
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
|
||||
DOWNLOAD_URL="${github_address_mod}gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
|
||||
else
|
||||
DOWNLOAD_URL="${github_address_mod}https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||
fi
|
||||
else
|
||||
DOWNLOAD_URL="https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||
fi
|
||||
else
|
||||
DOWNLOAD_URL=$GEOIP_CUSTOM_URL
|
||||
fi
|
||||
DOWNLOAD_FILE_CURL "$DOWNLOAD_URL" "/tmp/GeoIP.dat" "$geoip_path"
|
||||
DOWNLOAD_RESULT=$?
|
||||
if [ "$DOWNLOAD_RESULT" -eq 0 ] && [ -s "/tmp/GeoIP.dat" ]; then
|
||||
LOG_OUT "GeoIP Dat Download Success, Check Updated..."
|
||||
cmp -s /tmp/GeoIP.dat "$geoip_path"
|
||||
if [ "$?" -ne "0" ]; then
|
||||
LOG_OUT "GeoIP Dat Has Been Updated, Starting To Replace The Old Version..."
|
||||
rm -rf "/etc/openclash/geoip.dat"
|
||||
mv /tmp/GeoIP.dat "$geoip_path" >/dev/null 2>&1
|
||||
LOG_OUT "GeoIP Dat Update Successful!"
|
||||
restart=1
|
||||
else
|
||||
LOG_OUT "Updated GeoIP Dat No Change, Do Nothing..."
|
||||
fi
|
||||
elif [ "$DOWNLOAD_RESULT" -eq 2 ]; then
|
||||
LOG_OUT "Updated GeoIP Dat No Change, Do Nothing..."
|
||||
else
|
||||
LOG_OUT "GeoIP Dat Update Error, Please Try Again Later..."
|
||||
fi
|
||||
|
||||
rm -rf /tmp/GeoIP.dat >/dev/null 2>&1
|
||||
|
||||
SLOG_CLEAN
|
||||
dec_job_counter_and_restart "$restart"
|
||||
del_lock
|
||||
@@ -1,70 +0,0 @@
|
||||
#!/bin/bash
|
||||
. /usr/share/openclash/openclash_ps.sh
|
||||
. /usr/share/openclash/log.sh
|
||||
. /usr/share/openclash/openclash_curl.sh
|
||||
. /usr/share/openclash/uci.sh
|
||||
|
||||
set_lock() {
|
||||
exec 874>"/tmp/lock/openclash_geosite.lock" 2>/dev/null
|
||||
flock -x 874 2>/dev/null
|
||||
}
|
||||
|
||||
del_lock() {
|
||||
flock -u 874 2>/dev/null
|
||||
rm -rf "/tmp/lock/openclash_geosite.lock" 2>/dev/null
|
||||
}
|
||||
|
||||
set_lock
|
||||
inc_job_counter
|
||||
|
||||
small_flash_memory=$(uci_get_config "small_flash_memory")
|
||||
GEOSITE_CUSTOM_URL=$(uci_get_config "geosite_custom_url")
|
||||
github_address_mod=$(uci_get_config "github_address_mod" || echo 0)
|
||||
restart=0
|
||||
|
||||
if [ "$small_flash_memory" != "1" ]; then
|
||||
geosite_path="/etc/openclash/GeoSite.dat"
|
||||
mkdir -p /etc/openclash
|
||||
else
|
||||
geosite_path="/tmp/etc/openclash/GeoSite.dat"
|
||||
mkdir -p /tmp/etc/openclash
|
||||
fi
|
||||
LOG_OUT "Start Downloading GeoSite Database..."
|
||||
if [ -z "$GEOSITE_CUSTOM_URL" ]; then
|
||||
if [ "$github_address_mod" != "0" ]; then
|
||||
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
|
||||
DOWNLOAD_URL="${github_address_mod}gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
|
||||
else
|
||||
DOWNLOAD_URL="${github_address_mod}https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||
fi
|
||||
else
|
||||
DOWNLOAD_URL="https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||
fi
|
||||
else
|
||||
DOWNLOAD_URL=$GEOSITE_CUSTOM_URL
|
||||
fi
|
||||
DOWNLOAD_FILE_CURL "$DOWNLOAD_URL" "/tmp/GeoSite.dat" "$geosite_path"
|
||||
DOWNLOAD_RESULT=$?
|
||||
if [ "$DOWNLOAD_RESULT" -eq 0 ] && [ -s "/tmp/GeoSite.dat" ]; then
|
||||
LOG_OUT "GeoSite Database Download Success, Check Updated..."
|
||||
cmp -s /tmp/GeoSite.dat "$geosite_path"
|
||||
if [ "$?" -ne "0" ]; then
|
||||
LOG_OUT "GeoSite Database Has Been Updated, Starting To Replace The Old Version..."
|
||||
rm -rf "/etc/openclash/geosite.dat"
|
||||
mv /tmp/GeoSite.dat "$geosite_path" >/dev/null 2>&1
|
||||
LOG_OUT "GeoSite Database Update Successful!"
|
||||
restart=1
|
||||
else
|
||||
LOG_OUT "Updated GeoSite Database No Change, Do Nothing..."
|
||||
fi
|
||||
elif [ "$DOWNLOAD_RESULT" -eq 2 ]; then
|
||||
LOG_OUT "Updated GeoSite Database No Change, Do Nothing..."
|
||||
else
|
||||
LOG_OUT "GeoSite Database Update Error, Please Try Again Later..."
|
||||
fi
|
||||
|
||||
rm -rf /tmp/GeoSite.dat >/dev/null 2>&1
|
||||
|
||||
SLOG_CLEAN
|
||||
dec_job_counter_and_restart "$restart"
|
||||
del_lock
|
||||
@@ -1,69 +0,0 @@
|
||||
#!/bin/bash
|
||||
. /usr/share/openclash/openclash_ps.sh
|
||||
. /usr/share/openclash/log.sh
|
||||
. /usr/share/openclash/openclash_curl.sh
|
||||
. /usr/share/openclash/uci.sh
|
||||
|
||||
set_lock() {
|
||||
exec 880>"/tmp/lock/openclash_ipdb.lock" 2>/dev/null
|
||||
flock -x 880 2>/dev/null
|
||||
}
|
||||
|
||||
del_lock() {
|
||||
flock -u 880 2>/dev/null
|
||||
rm -rf "/tmp/lock/openclash_ipdb.lock" 2>/dev/null
|
||||
}
|
||||
|
||||
set_lock
|
||||
inc_job_counter
|
||||
|
||||
small_flash_memory=$(uci_get_config "small_flash_memory")
|
||||
GEOIP_CUSTOM_URL=$(uci_get_config "geo_custom_url")
|
||||
github_address_mod=$(uci_get_config "github_address_mod" || echo 0)
|
||||
restart=0
|
||||
|
||||
if [ "$small_flash_memory" != "1" ]; then
|
||||
geoip_path="/etc/openclash/Country.mmdb"
|
||||
mkdir -p /etc/openclash
|
||||
else
|
||||
geoip_path="/tmp/etc/openclash/Country.mmdb"
|
||||
mkdir -p /tmp/etc/openclash
|
||||
fi
|
||||
LOG_OUT "Start Downloading Geoip Database..."
|
||||
if [ -z "$GEOIP_CUSTOM_URL" ]; then
|
||||
if [ "$github_address_mod" != "0" ]; then
|
||||
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
|
||||
DOWNLOAD_URL="${github_address_mod}gh/alecthw/mmdb_china_ip_list@release/lite/Country.mmdb"
|
||||
else
|
||||
DOWNLOAD_URL="${github_address_mod}https://raw.githubusercontent.com/alecthw/mmdb_china_ip_list/release/lite/Country.mmdb"
|
||||
fi
|
||||
else
|
||||
DOWNLOAD_URL="https://raw.githubusercontent.com/alecthw/mmdb_china_ip_list/release/lite/Country.mmdb"
|
||||
fi
|
||||
else
|
||||
DOWNLOAD_URL=$GEOIP_CUSTOM_URL
|
||||
fi
|
||||
DOWNLOAD_FILE_CURL "$DOWNLOAD_URL" "/tmp/Country.mmdb" "$geoip_path"
|
||||
DOWNLOAD_RESULT=$?
|
||||
if [ "$DOWNLOAD_RESULT" -eq 0 ] && [ -s "/tmp/Country.mmdb" ]; then
|
||||
LOG_OUT "Geoip Database Download Success, Check Updated..."
|
||||
cmp -s /tmp/Country.mmdb "$geoip_path"
|
||||
if [ "$?" -ne 0 ]; then
|
||||
LOG_OUT "Geoip Database Has Been Updated, Starting To Replace The Old Version..."
|
||||
mv /tmp/Country.mmdb "$geoip_path" >/dev/null 2>&1
|
||||
LOG_OUT "Geoip Database Update Successful!"
|
||||
restart=1
|
||||
else
|
||||
LOG_OUT "Updated Geoip Database No Change, Do Nothing..."
|
||||
fi
|
||||
elif [ "$DOWNLOAD_RESULT" -eq 2 ]; then
|
||||
LOG_OUT "Updated Geoip Database No Change, Do Nothing..."
|
||||
else
|
||||
LOG_OUT "Geoip Database Update Error, Please Try Again Later..."
|
||||
fi
|
||||
|
||||
rm -rf /tmp/Country.mmdb >/dev/null 2>&1
|
||||
|
||||
SLOG_CLEAN
|
||||
dec_job_counter_and_restart "$restart"
|
||||
del_lock
|
||||
@@ -46,7 +46,7 @@ function unlock_auto_select()
|
||||
local no_old_region_unlock = {}
|
||||
local full_support_list = {}
|
||||
local fallback_select = {}
|
||||
local gorup_i18 = "Group:"
|
||||
local gorup_i18 = "【"..type.."】Group:"
|
||||
local no_group_find = "failed to search based on keywords and automatically obtain the group, please confirm the validity of the regex!"
|
||||
local full_support_no_area = "full support"
|
||||
local full_support = "full support, area:"
|
||||
@@ -119,6 +119,10 @@ function unlock_auto_select()
|
||||
key_group = FS.uci_get_config("config", "stream_auto_select_group_key_google_not_cn") or "google|谷歌"
|
||||
elseif type == "OpenAI" then
|
||||
key_group = FS.uci_get_config("config", "stream_auto_select_group_key_openai") or "OpenAI|ChatGPT"
|
||||
elseif type == "Claude" then
|
||||
key_group = FS.uci_get_config("config", "stream_auto_select_group_key_claude") or "Claude"
|
||||
elseif type == "Gemini" then
|
||||
key_group = FS.uci_get_config("config", "stream_auto_select_group_key_gemini") or "Gemini"
|
||||
end
|
||||
if not key_group then key_group = type end
|
||||
else
|
||||
@@ -148,9 +152,9 @@ function unlock_auto_select()
|
||||
--test now proxy
|
||||
region = proxy_unlock_test()
|
||||
if table_include(groups, now_name) then
|
||||
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..group_show.."】"
|
||||
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..group_show.."】"
|
||||
else
|
||||
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..group_show.." ➟ "..now_name.."】"
|
||||
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..group_show.." ➟ "..now_name.."】"
|
||||
end
|
||||
if status ~= 2 and status ~= 4 then
|
||||
region = proxy_unlock_test()
|
||||
@@ -244,7 +248,7 @@ function unlock_auto_select()
|
||||
end
|
||||
end
|
||||
if #(value.all) == 0 then
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..no_nodes_filter)
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..no_nodes_filter)
|
||||
break
|
||||
end
|
||||
--loop proxy test
|
||||
@@ -274,16 +278,16 @@ function unlock_auto_select()
|
||||
end
|
||||
end
|
||||
if #(proxies) == 0 then
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..group_show.."】"..no_nodes_filter)
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..group_show.."】"..no_nodes_filter)
|
||||
break
|
||||
end
|
||||
for p = 1, #(proxies) do
|
||||
proxy = proxies[p]
|
||||
if table_include(groups, proxy) then
|
||||
group_now = get_group_now(info, proxy)
|
||||
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..group_show.." ➟ "..group_now.."】"
|
||||
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..group_show.." ➟ "..group_now.."】"
|
||||
else
|
||||
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..group_show.." ➟ "..proxy.."】"
|
||||
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..group_show.." ➟ "..proxy.."】"
|
||||
end
|
||||
--skip tested proxy
|
||||
while true do
|
||||
@@ -303,7 +307,7 @@ function unlock_auto_select()
|
||||
table.insert(full_support_list, {value.all[i], group_name, proxy, region})
|
||||
if not all_test then
|
||||
print(now..full_support.."【"..region.."】")
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_success.."【"..proxy.."】"..area_i18.."【"..region.."】")
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_success.."【"..proxy.."】"..area_i18.."【"..region.."】")
|
||||
else
|
||||
print(now..full_support.."【"..region.."】")
|
||||
end
|
||||
@@ -312,7 +316,7 @@ function unlock_auto_select()
|
||||
table.insert(full_support_list, {value.all[i], group_name, proxy})
|
||||
if not all_test then
|
||||
print(now..full_support_no_area)
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_success.."【"..proxy.."】")
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_success.."【"..proxy.."】")
|
||||
else
|
||||
print(now..full_support_no_area)
|
||||
end
|
||||
@@ -372,16 +376,16 @@ function unlock_auto_select()
|
||||
end
|
||||
region = proxy_unlock_test()
|
||||
if table_include(groups, now_name) then
|
||||
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..group_show.."】"
|
||||
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..group_show.."】"
|
||||
else
|
||||
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..group_show.." ➟ "..now_name.."】"
|
||||
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..group_show.." ➟ "..now_name.."】"
|
||||
end
|
||||
if status == 2 then
|
||||
if region ~= "" then
|
||||
table.insert(full_support_list, {value.all[i], group_name, value.all[i], region})
|
||||
if not all_test then
|
||||
print(now..full_support.."【"..region.."】")
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, now_name).."】"..area_i18.."【"..region.."】")
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, now_name).."】"..area_i18.."【"..region.."】")
|
||||
else
|
||||
print(now..full_support.."【"..region.."】")
|
||||
end
|
||||
@@ -390,7 +394,7 @@ function unlock_auto_select()
|
||||
table.insert(full_support_list, {value.all[i], group_name, value.all[i]})
|
||||
if not all_test then
|
||||
print(now..full_support_no_area)
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, now_name).."】")
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, now_name).."】")
|
||||
else
|
||||
print(now..full_support_no_area)
|
||||
end
|
||||
@@ -469,24 +473,24 @@ function unlock_auto_select()
|
||||
group_now = group_now .. area_i18 .. "【"..v[4].."】"
|
||||
end
|
||||
if #full_support_list > 0 then
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_all_full_support..group_now)
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_all_full_support..group_now)
|
||||
elseif #no_old_region_unlock > 0 then
|
||||
if not all_test then
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_success_no_old_region..group_now)
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_success_no_old_region..group_now)
|
||||
else
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_all_no_old_region..group_now)
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_all_no_old_region..group_now)
|
||||
end
|
||||
elseif #other_region_unlock > 0 then
|
||||
if not all_test then
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_faild_other_region..group_now)
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_faild_other_region..group_now)
|
||||
else
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_all_other_region..group_now)
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_all_other_region..group_now)
|
||||
end
|
||||
else
|
||||
if not all_test then
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_faild..group_now)
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_faild..group_now)
|
||||
else
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_all_faild..group_now)
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_all_faild..group_now)
|
||||
end
|
||||
end
|
||||
close_connections()
|
||||
@@ -499,7 +503,7 @@ function unlock_auto_select()
|
||||
else
|
||||
group_now = value.name.." ➟ "..proxy_default
|
||||
end
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_faild.."【"..group_now.."】")
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_faild.."【"..group_now.."】")
|
||||
end
|
||||
end
|
||||
elseif #nodes_filter(get_group_now(info, value.name), info) ~= 0 then
|
||||
@@ -509,12 +513,12 @@ function unlock_auto_select()
|
||||
if not all_test then
|
||||
print(now..full_support.."【"..region.."】")
|
||||
end
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, value.name).."】"..area_i18.."【"..region.."】")
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, value.name).."】"..area_i18.."【"..region.."】")
|
||||
else
|
||||
if not all_test then
|
||||
print(now..full_support_no_area)
|
||||
end
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, value.name).."】")
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, value.name).."】")
|
||||
end
|
||||
if not all_test then
|
||||
break
|
||||
@@ -548,7 +552,7 @@ function unlock_auto_select()
|
||||
if status == 2 and not all_test then break end
|
||||
end
|
||||
if not group_match and not auto_get_group then
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..key_group.."】"..no_group_find)
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..key_group.."】"..no_group_find)
|
||||
end
|
||||
--write cache
|
||||
FS.writefile(unlock_cache_file, JSON.stringify(unlock_cache_info))
|
||||
@@ -817,6 +821,10 @@ function nodes_filter(t, info)
|
||||
regex = FS.uci_get_config("config", "stream_auto_select_node_key_google_not_cn") or ""
|
||||
elseif type == "OpenAI" then
|
||||
regex = FS.uci_get_config("config", "stream_auto_select_node_key_openai") or ""
|
||||
elseif type == "Claude" then
|
||||
regex = FS.uci_get_config("config", "stream_auto_select_node_key_claude") or ""
|
||||
elseif type == "Gemini" then
|
||||
regex = FS.uci_get_config("config", "stream_auto_select_node_key_gemini") or ""
|
||||
end
|
||||
|
||||
if class_type(t) == "table" then
|
||||
@@ -877,6 +885,10 @@ function proxy_unlock_test()
|
||||
region = google_not_cn_test()
|
||||
elseif type == "OpenAI" then
|
||||
region = openai_unlock_test()
|
||||
elseif type == "Claude" then
|
||||
region = claude_unlock_test()
|
||||
elseif type == "Gemini" then
|
||||
region = gemini_unlock_test()
|
||||
end
|
||||
return region
|
||||
end
|
||||
@@ -908,6 +920,10 @@ function auto_get_policy_group(passwd, ip, port)
|
||||
SYS.call('curl -sL -m 5 --limit-rate 1k -o /dev/null https://timeline.google.com &')
|
||||
elseif type == "OpenAI" then
|
||||
SYS.call('curl -sL -m 5 --limit-rate 1k -o /dev/null https://chatgpt.com/ &')
|
||||
elseif type == "Claude" then
|
||||
SYS.call('curl -sL -m 5 --limit-rate 1k -o /dev/null https://claude.ai/ &')
|
||||
elseif type == "Gemini" then
|
||||
SYS.call('curl -sL -m 5 --limit-rate 1k -o /dev/null https://gemini.google.com/ &')
|
||||
end
|
||||
os.execute("sleep 1")
|
||||
con = SYS.exec(string.format('curl -sL -m 5 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XGET http://%s:%s/connections', passwd, ip, port))
|
||||
@@ -976,6 +992,16 @@ function auto_get_policy_group(passwd, ip, port)
|
||||
auto_get_group = con.connections[i].chains[#(con.connections[i].chains)]
|
||||
break
|
||||
end
|
||||
elseif type == "Claude" then
|
||||
if string.match(con.connections[i].metadata.host, "claude%.ai") then
|
||||
auto_get_group = con.connections[i].chains[#(con.connections[i].chains)]
|
||||
break
|
||||
end
|
||||
elseif type == "Gemini" then
|
||||
if string.match(con.connections[i].metadata.host, "gemini%.google%.com") then
|
||||
auto_get_group = con.connections[i].chains[#(con.connections[i].chains)]
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1571,6 +1597,82 @@ function openai_unlock_test()
|
||||
return region
|
||||
end
|
||||
|
||||
function claude_unlock_test()
|
||||
status = 0
|
||||
local url = "https://claude.ai/"
|
||||
local region_url = "https://claude.ai/cdn-cgi/trace"
|
||||
local regex = FS.uci_get_config("config", "stream_auto_select_region_key_claude") or ""
|
||||
local region = ""
|
||||
local old_region = get_old_region()
|
||||
local old_regex = get_old_regex()
|
||||
local data = SYS.exec(string.format("curl -sIL --connect-timeout 5 -m 5 --speed-time 5 --speed-limit 1 --retry 2 -H 'Accept-Language: en' -H 'Content-Type: application/json' -H 'User-Agent: %s' '%s'", UA, url))
|
||||
if data then
|
||||
if string.find(data, "App unavailable in region") then
|
||||
status = 1
|
||||
else
|
||||
status = 2
|
||||
local region_data = SYS.exec(string.format("curl -sL --connect-timeout 5 -m 5 --speed-time 5 --speed-limit 1 --retry 2 -H 'Accept-Language: en' -H 'Content-Type: application/json' -H 'User-Agent: %s' '%s'", UA, region_url))
|
||||
if region_data and string.match(region_data, "loc=%a+") then
|
||||
region = string.upper(string.sub(string.match(region_data, "loc=%a+"), 5, -1))
|
||||
if not datamatch(region, regex) then
|
||||
status = 3
|
||||
elseif old_regex ~= regex and not all_test then
|
||||
status = 2
|
||||
elseif old_region ~= "" and region ~= old_region and not all_test then
|
||||
status = 4
|
||||
end
|
||||
if status == 2 and not all_test and region ~= old_region then
|
||||
write_cache(type, "old_region", region)
|
||||
end
|
||||
if status == 2 and not all_test and regex ~= old_regex then
|
||||
write_cache(type, "old_regex", regex)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return region
|
||||
end
|
||||
|
||||
function gemini_unlock_test()
|
||||
status = 0
|
||||
local url = "https://gemini.google.com/"
|
||||
local regex = FS.uci_get_config("config", "stream_auto_select_region_key_gemini") or ""
|
||||
local region = ""
|
||||
local old_region = get_old_region()
|
||||
local old_regex = get_old_regex()
|
||||
local data = SYS.exec(string.format("curl -sL --connect-timeout 5 -m 5 --speed-time 5 --speed-limit 1 --retry 2 -H 'Accept-Language: en' -H 'Content-Type: application/json' -H 'User-Agent: %s' '%s'", UA, url))
|
||||
if not data or string.sub(data, 1, 4) == "curl" then
|
||||
status = 1
|
||||
return region
|
||||
end
|
||||
|
||||
if string.find(data, "45631641,null,true", 1, true) then
|
||||
local cc = string.match(data, ',2,1,200,"([A-Z][A-Z][A-Z])"')
|
||||
if cc and cc ~= "" then
|
||||
region = cc
|
||||
end
|
||||
status = 2
|
||||
if region ~= "" then
|
||||
if not datamatch(region, regex) then
|
||||
status = 3
|
||||
elseif old_regex ~= regex and not all_test then
|
||||
status = 2
|
||||
elseif old_region ~= "" and region ~= old_region and not all_test then
|
||||
status = 4
|
||||
end
|
||||
if status == 2 and not all_test and region ~= old_region then
|
||||
write_cache(type, "old_region", region)
|
||||
end
|
||||
if status == 2 and not all_test and regex ~= old_regex then
|
||||
write_cache(type, "old_regex", regex)
|
||||
end
|
||||
end
|
||||
else
|
||||
status = 1
|
||||
end
|
||||
return region
|
||||
end
|
||||
|
||||
function network_test()
|
||||
local test_url1 = "https://www.gstatic.com/generate_204"
|
||||
local test_url2 = "https://cp.cloudflare.com/generate_204"
|
||||
|
||||
@@ -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
|
||||
@@ -57,7 +56,7 @@ LAST_VER=$(sed -n 1p "$LAST_OPVER" 2>/dev/null |sed "s/^v//g" |tr -d "\n")
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
OP_CV=$(rm -f /var/lock/opkg.lock && opkg status luci-app-openclash 2>/dev/null |grep 'Version' |awk -F 'Version: ' '{print $2}' 2>/dev/null)
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
OP_CV=$(apk list luci-app-openclash 2>/dev/null|grep 'installed' | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
|
||||
OP_CV=$(apk list luci-app-openclash 2>/dev/null|grep "installed" | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
|
||||
fi
|
||||
OP_LV=$(sed -n 1p "$LAST_OPVER" 2>/dev/null |sed "s/^v//g" |tr -d "\n")
|
||||
RELEASE_BRANCH=$(uci_get_config "release_branch" || echo "master")
|
||||
@@ -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"
|
||||
@@ -253,7 +227,7 @@ check_install_success()
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
current_version=$(rm -f /var/lock/opkg.lock && opkg status luci-app-openclash 2>/dev/null |grep 'Version' |awk -F 'Version: ' '{print $2}' 2>/dev/null)
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
current_version=$(apk list luci-app-openclash 2>/dev/null|grep 'installed' | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
|
||||
current_version=$(apk list luci-app-openclash 2>/dev/null |grep "installed" | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
|
||||
fi
|
||||
|
||||
if [ -n "$current_version" ] && [ "$current_version" = "$target_version" ]; then
|
||||
@@ -266,41 +240,39 @@ check_install_success()
|
||||
install_missing_packages() {
|
||||
local installed_before="$1"
|
||||
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
if [ -n "$installed_before" ]; then
|
||||
for pkg in $installed_before; do
|
||||
if ! opkg status "$pkg" >/dev/null 2>&1; then
|
||||
local retry_count=0
|
||||
local max_retries=3
|
||||
while [ $retry_count -lt $max_retries ]; do
|
||||
retry_count=$((retry_count + 1))
|
||||
local retry_count=0
|
||||
local max_retries=3
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
if opkg status "$pkg" >/dev/null 2>&1; then
|
||||
continue
|
||||
fi
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
if apk list "$pkg" |grep "installed" >/dev/null 2>&1; then
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
LOG_TIP "【$pkg】depended package reinstalling..."
|
||||
|
||||
while [ $retry_count -lt $max_retries ]; do
|
||||
retry_count=$((retry_count + 1))
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
opkg install "$pkg"
|
||||
if [ $? -eq 0 ]; then
|
||||
break
|
||||
else
|
||||
if [ $retry_count -lt $max_retries ]; then
|
||||
sleep 2
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
for pkg in $installed_before; do
|
||||
if ! apk info "$pkg" >/dev/null 2>&1; then
|
||||
local retry_count=0
|
||||
local max_retries=3
|
||||
while [ $retry_count -lt $max_retries ]; do
|
||||
retry_count=$((retry_count + 1))
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
apk add "$pkg"
|
||||
if [ $? -eq 0 ]; then
|
||||
break
|
||||
fi
|
||||
if [ $? -eq 0 ]; then
|
||||
break
|
||||
else
|
||||
if [ $retry_count -lt $max_retries ]; then
|
||||
sleep 2
|
||||
else
|
||||
if [ $retry_count -lt $max_retries ]; then
|
||||
sleep 2
|
||||
fi
|
||||
LOG_ERROR "【$pkg】failed to install, please try to install it manually..."
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
}
|
||||
@@ -324,7 +296,7 @@ while [ $install_retry_count -lt $max_install_retries ]; do
|
||||
opkg install /tmp/openclash.ipk
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
for pkg in $packages_to_check; do
|
||||
if apk info "$pkg" >/dev/null 2>&1; then
|
||||
if apk list "$pkg" |grep "installed" >/dev/null 2>&1; then
|
||||
installed_before="$installed_before $pkg"
|
||||
fi
|
||||
done
|
||||
@@ -357,9 +329,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 +339,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 +379,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)
|
||||
@@ -172,6 +171,8 @@ do
|
||||
stream_auto_select_bilibili=$(uci_get_config "stream_auto_select_bilibili" || echo 0)
|
||||
stream_auto_select_google_not_cn=$(uci_get_config "stream_auto_select_google_not_cn" || echo 0)
|
||||
stream_auto_select_openai=$(uci_get_config "stream_auto_select_openai" || echo 0)
|
||||
stream_auto_select_claude=$(uci_get_config "stream_auto_select_claude" || echo 0)
|
||||
stream_auto_select_gemini=$(uci_get_config "stream_auto_select_gemini" || echo 0)
|
||||
upnp_lease_file=$(uci -q get upnpd.config.upnp_lease_file)
|
||||
|
||||
#wait for core start complete
|
||||
@@ -362,66 +363,72 @@ 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..."
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Netflix】..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Netflix" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_disney" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Proxy For Disney Plus Unlock..."
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Disney Plus】..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Disney Plus" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_google_not_cn" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Proxy For Google Not CN Unlock..."
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Google Not CN】..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Google" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_ytb" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Proxy For YouTube Premium Unlock..."
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【YouTube Premium】..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "YouTube Premium" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_prime_video" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Proxy For Amazon Prime Video Unlock..."
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Amazon Prime Video】..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Amazon Prime Video" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_hbo_max" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Proxy For HBO Max Unlock..."
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【HBO Max】..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "HBO Max" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_tvb_anywhere" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Proxy For TVB Anywhere+ Unlock..."
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【TVB Anywhere+】..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "TVB Anywhere+" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_dazn" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Proxy For DAZN Unlock..."
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【DAZN】..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "DAZN" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_paramount_plus" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Proxy For Paramount Plus Unlock..."
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Paramount Plus】..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Paramount Plus" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_discovery_plus" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Proxy For Discovery Plus Unlock..."
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Discovery Plus】..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Discovery Plus" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_bilibili" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Proxy For Bilibili Unlock..."
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Bilibili】..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Bilibili" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_openai" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Proxy For OpenAI Unlock..."
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【OpenAI】..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "OpenAI" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_claude" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Claude】..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Claude" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_gemini" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Gemini】..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Gemini" >> $LOG_FILE
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
STREAM_AUTO_SELECT=$(expr "$STREAM_AUTO_SELECT" + 1)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -41,7 +41,7 @@ if [ "$1" = "fake-ip" ] && [ "$enable_redirect_dns" != "2" ]; then
|
||||
awk -v mode="$fake_ip_filter_mode" '
|
||||
!/^$/ && !/^#/ {
|
||||
# 跳过IPv4和IPv6地址
|
||||
if ($0 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ || $0 ~ /:/) {
|
||||
if ($0 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(\/[0-9]+)?$/ || $0 ~ /:/) {
|
||||
next
|
||||
}
|
||||
if (mode == "blacklist") {
|
||||
@@ -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
|
||||
@@ -378,6 +378,8 @@ begin
|
||||
smart_collect = '${44}' == '1'
|
||||
smart_collect_size = '${45}'
|
||||
fake_ip_range6 = '${46}'
|
||||
fake_ip_range6_enable = '${47}' == '1'
|
||||
global_ua = '${48}'
|
||||
default_dashboard = '$default_dashboard'
|
||||
yacd_type = '$yacd_type'
|
||||
dashboard_type = '$dashboard_type'
|
||||
@@ -408,6 +410,7 @@ begin
|
||||
Value['external-controller'] = '0.0.0.0:' + controller_port
|
||||
Value['secret'] = secret
|
||||
Value['bind-address'] = '*'
|
||||
Value['global-ua'] = global_ua if global_ua != '0'
|
||||
Value['external-ui'] = '/usr/share/openclash/ui'
|
||||
Value['external-ui-name'] = default_dashboard
|
||||
case default_dashboard
|
||||
@@ -480,7 +483,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_enable
|
||||
Value['dns']['fake-ip-range6'] = fake_ip_range6
|
||||
end
|
||||
end
|
||||
@@ -657,14 +660,13 @@ begin
|
||||
end
|
||||
if fake_ip_mode == 'fake-ip' && (china_ip_route || china_ip6_route)
|
||||
filter_mode = Value.dig('dns', 'fake-ip-filter-mode')
|
||||
filters = Value.dig('dns', 'fake-ip-filter') || []
|
||||
deleted_filters = filters.select { |f| f =~ /(geosite:?|rule-set:?).*(@cn|:cn|,cn|:china)/i }
|
||||
if filter_mode == 'blacklist' || filter_mode.nil?
|
||||
if !deleted_filters.any?
|
||||
(Value['dns']['fake-ip-filter'] ||= []) << 'geosite:cn'
|
||||
YAML.LOG_TIP('Because Need Ensure Bypassing IP Option Work, Added The Fake-IP-Filter Rule【geosite:cn】...')
|
||||
end
|
||||
else
|
||||
filter_rule = 'rule-set:oc-cn-domain'
|
||||
(Value['dns']['fake-ip-filter'] ||= []) << filter_rule
|
||||
end
|
||||
if filter_mode == 'whitelist'
|
||||
filters = Value.dig('dns', 'fake-ip-filter') || []
|
||||
deleted_filters = filters.select { |f| f =~ /(geosite:?|rule-set:?).*(@cn|:cn|,cn|:china)/i }
|
||||
if deleted_filters.any?
|
||||
Value['dns']['fake-ip-filter'] -= deleted_filters
|
||||
deleted_filters.each do |f|
|
||||
@@ -673,8 +675,14 @@ begin
|
||||
end
|
||||
end
|
||||
if filter_mode == 'rule'
|
||||
(Value['dns']['fake-ip-filter'] ||= []).unshift('GEOSITE,cn,real-ip')
|
||||
YAML.LOG_TIP('Because Need Ensure Bypassing IP Option Work, Added The Fake-IP-Filter Rule【GEOSITE,cn,real-ip】...')
|
||||
filter_rule = 'RULE-SET,oc-cn-domain,real-ip'
|
||||
(Value['dns']['fake-ip-filter'] ||= []).unshift(filter_rule)
|
||||
end
|
||||
Value['dns']['fake-ip-filter'].uniq!
|
||||
if filter_mode != 'whitelist'
|
||||
rule_set_hash = {'rule-providers+'=>{'oc-cn-domain'=>{'type'=>'http', 'interval'=>43200, 'behavior'=>'domain', 'format'=>'mrs', 'url'=>'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/cn.mrs', 'path'=> './rule_provider/oc-cn-domain.mrs'}}}
|
||||
Value = YAML.overwrite(Value, rule_set_hash)
|
||||
YAML.LOG_TIP('Because Need Ensure Bypassing IP Option Work, Added The Fake-IP-Filter Rule【%s】...' % [filter_rule])
|
||||
end
|
||||
end
|
||||
rescue Exception => e
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -21,6 +21,9 @@ yml_other_set()
|
||||
|
||||
begin
|
||||
thread_pool = [];
|
||||
# GEOIP replace
|
||||
geoip_pattern = /GEOIP,([A-Za-z]{2}),([^,]+)(,.*)?/;
|
||||
match_pattern = /(^MATCH.*|^FINAL.*)/;
|
||||
thread_pool << Thread.new{
|
||||
#BT/P2P DIRECT Rules
|
||||
begin
|
||||
@@ -102,10 +105,6 @@ yml_other_set()
|
||||
end;
|
||||
end;
|
||||
|
||||
# GEOIP replace
|
||||
geoip_pattern = /GEOIP,([A-Za-z]{2}),([^,]+)(,.*)?/;
|
||||
match_pattern = /(^MATCH.*|^FINAL.*)/;
|
||||
|
||||
Value['rules'].to_a.collect!{|x|
|
||||
x.to_s.gsub(geoip_pattern, 'GEOIP,\1,DIRECT\3').gsub(match_pattern, 'MATCH,DIRECT')
|
||||
};
|
||||
@@ -136,6 +135,18 @@ yml_other_set()
|
||||
{ file: '/etc/openclash/custom/openclash_custom_rules_2.list', position: 'bottom' }
|
||||
];
|
||||
|
||||
# 在开始对 custom_files 进行插入之前,记录当前 rules 中 GEOIP、MATCH、DST-PORT,80 的原始规则文本(锚点)
|
||||
anchors_orig = {}
|
||||
if Value.has_key?('rules') and not Value['rules'].to_a.empty? then
|
||||
anchors_orig[:dst80_rule] = Value['rules'].grep(/DST-PORT,80/).last
|
||||
anchors_orig[:match_rule] = Value['rules'].grep(match_pattern).first
|
||||
anchors_orig[:geo_rule] = Value['rules'].grep(geoip_pattern).first
|
||||
else
|
||||
anchors_orig[:dst80_rule] = nil
|
||||
anchors_orig[:match_rule] = nil
|
||||
anchors_orig[:geo_rule] = nil
|
||||
end;
|
||||
|
||||
custom_files.each{|file_info|
|
||||
if File::exist?(file_info[:file]) then
|
||||
custom_data = YAML.load_file(file_info[:file]);
|
||||
@@ -150,6 +161,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}$/;
|
||||
@@ -157,9 +175,10 @@ yml_other_set()
|
||||
cidr_regex = /\/\d+$/;
|
||||
rule_suffix_regex = /^no-resolve$|^src$/;
|
||||
|
||||
ip_rule_types = ['IP-CIDR', 'IP-CIDR6', 'SRC-IP-CIDR', 'SRC-IP-CIDR6', 'IP-SUFFIX', 'SRC-IP-SUFFIX'];
|
||||
transformed_rules = rules_array.map{|x|
|
||||
parts = x.split(',');
|
||||
if parts.length >= 2 then
|
||||
if parts.length >= 2 && ip_rule_types.include?(parts[0].strip.upcase) then
|
||||
ip_part = parts[1].strip;
|
||||
if ip_part !~ cidr_regex then
|
||||
# IPv4
|
||||
@@ -194,11 +213,15 @@ yml_other_set()
|
||||
if file_info[:position] == 'top' then
|
||||
valid_rules.reverse.each{|x| Value['rules'].insert(0,x)};
|
||||
else
|
||||
if Value['rules'].grep(/GEOIP/)[0].nil? or Value['rules'].grep(/GEOIP/)[0].empty? then
|
||||
ruby_add_index = Value['rules'].index(Value['rules'].grep(/DST-PORT,80/).last);
|
||||
ruby_add_index ||= Value['rules'].index(Value['rules'].grep(/(MATCH|FINAL)/).first);
|
||||
else
|
||||
ruby_add_index = Value['rules'].index(Value['rules'].grep(/GEOIP/).first);
|
||||
ruby_add_index = nil;
|
||||
if anchors_orig[:dst80_rule]
|
||||
ruby_add_index = Value['rules'].rindex(anchors_orig[:dst80_rule])
|
||||
end;
|
||||
if ruby_add_index.nil? and anchors_orig[:match_rule]
|
||||
ruby_add_index = Value['rules'].rindex(anchors_orig[:match_rule])
|
||||
end;
|
||||
if ruby_add_index.nil? and anchors_orig[:geo_rule]
|
||||
ruby_add_index = Value['rules'].rindex(anchors_orig[:geo_rule])
|
||||
end;
|
||||
ruby_add_index ||= -1;
|
||||
|
||||
@@ -209,6 +232,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;
|
||||
};
|
||||
|
||||
@@ -256,19 +284,13 @@ yml_other_set()
|
||||
thread_pool << Thread.new{
|
||||
threads = [];
|
||||
|
||||
#provider path
|
||||
#provider CDN
|
||||
begin
|
||||
provider_configs = {'proxy-providers' => 'proxy_provider', 'rule-providers' => 'rule_provider'};
|
||||
provider_configs.each do |provider_type, path_prefix|
|
||||
if Value.key?(provider_type) then
|
||||
if Value.key?(provider_type) && Value[provider_type].is_a?(Hash) then
|
||||
Value[provider_type].each{|name, config|
|
||||
threads << Thread.new {
|
||||
if config['path'] and not config['path'] =~ /.\/#{path_prefix}\/*/ then
|
||||
config['path'] = './'+path_prefix+'/'+File.basename(config['path']);
|
||||
elsif not config['path'] and config['type'] == 'http' then
|
||||
config['path'] = './'+path_prefix+'/'+name;
|
||||
end;
|
||||
|
||||
# CDN
|
||||
if '$github_address_mod' != '0' and config['url'] then
|
||||
if config['url'] =~ /^https:\/\/raw.githubusercontent.com/ then
|
||||
@@ -293,12 +315,12 @@ yml_other_set()
|
||||
end;
|
||||
end;
|
||||
rescue Exception => e
|
||||
YAML.LOG_ERROR('Edit Provider Path Failed,【' + e.message + '】');
|
||||
YAML.LOG_ERROR('Edit Provider CDN Failed,【' + e.message + '】');
|
||||
end;
|
||||
|
||||
# tolerance
|
||||
begin
|
||||
if '$tolerance' != '0' and Value.key?('proxy-groups') then
|
||||
if '$tolerance' != '0' and Value.key?('proxy-groups') and Value['proxy-groups'].is_a?(Array) then
|
||||
Value['proxy-groups'].each{|group|
|
||||
threads << Thread.new {
|
||||
if group['type'] == 'url-test' then
|
||||
@@ -314,7 +336,7 @@ yml_other_set()
|
||||
# URL-Test interval
|
||||
begin
|
||||
if '$urltest_interval_mod' != '0' then
|
||||
if Value.key?('proxy-groups') then
|
||||
if Value.key?('proxy-groups') and Value['proxy-groups'].is_a?(Array) then
|
||||
Value['proxy-groups'].each{|group|
|
||||
threads << Thread.new {
|
||||
if ['url-test', 'fallback', 'load-balance', 'smart'].include?(group['type']) then
|
||||
@@ -349,7 +371,7 @@ yml_other_set()
|
||||
};
|
||||
};
|
||||
end;
|
||||
if Value.key?('proxy-groups') then
|
||||
if Value.key?('proxy-groups') and Value['proxy-groups'].is_a?(Array) then
|
||||
Value['proxy-groups'].each{|group|
|
||||
threads << Thread.new {
|
||||
if ['url-test', 'fallback', 'load-balance', 'smart'].include?(group['type']) then
|
||||
@@ -365,7 +387,7 @@ yml_other_set()
|
||||
|
||||
# smart auto switch
|
||||
begin
|
||||
if ('${8}' == '1' or '${9}' == '1' or '${11}' != '0' or '${12}' != '0' or '${12}' == '1' or '${13}' == '1') and Value.key?('proxy-groups') then
|
||||
if ('${8}' == '1' or '${9}' == '1' or '${11}' != '0' or '${12}' != '0' or '${12}' == '1' or '${13}' == '1') and Value.key?('proxy-groups') and Value['proxy-groups'].is_a?(Array) then
|
||||
Value['proxy-groups'].each{|group|
|
||||
threads << Thread.new {
|
||||
if '${8}' == '1' and ['url-test', 'load-balance'].include?(group['type']) then
|
||||
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
/*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).jsyaml={})}(this,(function(e){"use strict";function t(e){return null==e}var n={isNothing:t,isObject:function(e){return"object"==typeof e&&null!==e},toArray:function(e){return Array.isArray(e)?e:t(e)?[]:[e]},repeat:function(e,t){var n,i="";for(n=0;n<t;n+=1)i+=e;return i},isNegativeZero:function(e){return 0===e&&Number.NEGATIVE_INFINITY===1/e},extend:function(e,t){var n,i,r,o;if(t)for(n=0,i=(o=Object.keys(t)).length;n<i;n+=1)e[r=o[n]]=t[r];return e}};function i(e,t){var n="",i=e.reason||"(unknown reason)";return e.mark?(e.mark.name&&(n+='in "'+e.mark.name+'" '),n+="("+(e.mark.line+1)+":"+(e.mark.column+1)+")",!t&&e.mark.snippet&&(n+="\n\n"+e.mark.snippet),i+" "+n):i}function r(e,t){Error.call(this),this.name="YAMLException",this.reason=e,this.mark=t,this.message=i(this,!1),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack||""}r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r.prototype.toString=function(e){return this.name+": "+i(this,e)};var o=r;function a(e,t,n,i,r){var o="",a="",l=Math.floor(r/2)-1;return i-t>l&&(t=i-l+(o=" ... ").length),n-i>l&&(n=i+l-(a=" ...").length),{str:o+e.slice(t,n).replace(/\t/g,"→")+a,pos:i-t+o.length}}function l(e,t){return n.repeat(" ",t-e.length)+e}var c=function(e,t){if(t=Object.create(t||null),!e.buffer)return null;t.maxLength||(t.maxLength=79),"number"!=typeof t.indent&&(t.indent=1),"number"!=typeof t.linesBefore&&(t.linesBefore=3),"number"!=typeof t.linesAfter&&(t.linesAfter=2);for(var i,r=/\r?\n|\r|\0/g,o=[0],c=[],s=-1;i=r.exec(e.buffer);)c.push(i.index),o.push(i.index+i[0].length),e.position<=i.index&&s<0&&(s=o.length-2);s<0&&(s=o.length-1);var u,p,f="",d=Math.min(e.line+t.linesAfter,c.length).toString().length,h=t.maxLength-(t.indent+d+3);for(u=1;u<=t.linesBefore&&!(s-u<0);u++)p=a(e.buffer,o[s-u],c[s-u],e.position-(o[s]-o[s-u]),h),f=n.repeat(" ",t.indent)+l((e.line-u+1).toString(),d)+" | "+p.str+"\n"+f;for(p=a(e.buffer,o[s],c[s],e.position,h),f+=n.repeat(" ",t.indent)+l((e.line+1).toString(),d)+" | "+p.str+"\n",f+=n.repeat("-",t.indent+d+3+p.pos)+"^\n",u=1;u<=t.linesAfter&&!(s+u>=c.length);u++)p=a(e.buffer,o[s+u],c[s+u],e.position-(o[s]-o[s+u]),h),f+=n.repeat(" ",t.indent)+l((e.line+u+1).toString(),d)+" | "+p.str+"\n";return f.replace(/\n$/,"")},s=["kind","multi","resolve","construct","instanceOf","predicate","represent","representName","defaultStyle","styleAliases"],u=["scalar","sequence","mapping"];var p=function(e,t){if(t=t||{},Object.keys(t).forEach((function(t){if(-1===s.indexOf(t))throw new o('Unknown option "'+t+'" is met in definition of "'+e+'" YAML type.')})),this.options=t,this.tag=e,this.kind=t.kind||null,this.resolve=t.resolve||function(){return!0},this.construct=t.construct||function(e){return e},this.instanceOf=t.instanceOf||null,this.predicate=t.predicate||null,this.represent=t.represent||null,this.representName=t.representName||null,this.defaultStyle=t.defaultStyle||null,this.multi=t.multi||!1,this.styleAliases=function(e){var t={};return null!==e&&Object.keys(e).forEach((function(n){e[n].forEach((function(e){t[String(e)]=n}))})),t}(t.styleAliases||null),-1===u.indexOf(this.kind))throw new o('Unknown kind "'+this.kind+'" is specified for "'+e+'" YAML type.')};function f(e,t){var n=[];return e[t].forEach((function(e){var t=n.length;n.forEach((function(n,i){n.tag===e.tag&&n.kind===e.kind&&n.multi===e.multi&&(t=i)})),n[t]=e})),n}function d(e){return this.extend(e)}d.prototype.extend=function(e){var t=[],n=[];if(e instanceof p)n.push(e);else if(Array.isArray(e))n=n.concat(e);else{if(!e||!Array.isArray(e.implicit)&&!Array.isArray(e.explicit))throw new o("Schema.extend argument should be a Type, [ Type ], or a schema definition ({ implicit: [...], explicit: [...] })");e.implicit&&(t=t.concat(e.implicit)),e.explicit&&(n=n.concat(e.explicit))}t.forEach((function(e){if(!(e instanceof p))throw new o("Specified list of YAML types (or a single Type object) contains a non-Type object.");if(e.loadKind&&"scalar"!==e.loadKind)throw new o("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.");if(e.multi)throw new o("There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.")})),n.forEach((function(e){if(!(e instanceof p))throw new o("Specified list of YAML types (or a single Type object) contains a non-Type object.")}));var i=Object.create(d.prototype);return i.implicit=(this.implicit||[]).concat(t),i.explicit=(this.explicit||[]).concat(n),i.compiledImplicit=f(i,"implicit"),i.compiledExplicit=f(i,"explicit"),i.compiledTypeMap=function(){var e,t,n={scalar:{},sequence:{},mapping:{},fallback:{},multi:{scalar:[],sequence:[],mapping:[],fallback:[]}};function i(e){e.multi?(n.multi[e.kind].push(e),n.multi.fallback.Line truncated
|
||||
/*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT */
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).jsyaml={})}(this,function(e){"use strict";function t(e){return null==e}var n={isNothing:t,isObject:function(e){return"object"==typeof e&&null!==e},toArray:function(e){return Array.isArray(e)?e:t(e)?[]:[e]},repeat:function(e,t){var n,i="";for(n=0;n<t;n+=1)i+=e;return i},isNegativeZero:function(e){return 0===e&&Number.NEGATIVE_INFINITY===1/e},extend:function(e,t){var n,i,r,o;if(t)for(n=0,i=(o=Object.keys(t)).length;n<i;n+=1)e[r=o[n]]=t[r];return e}};function i(e,t){var n="",i=e.reason||"(unknown reason)";return e.mark?(e.mark.name&&(n+='in "'+e.mark.name+'" '),n+="("+(e.mark.line+1)+":"+(e.mark.column+1)+")",!t&&e.mark.snippet&&(n+="\n\n"+e.mark.snippet),i+" "+n):i}function r(e,t){Error.call(this),this.name="YAMLException",this.reason=e,this.mark=t,this.message=i(this,!1),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack||""}r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r.prototype.toString=function(e){return this.name+": "+i(this,e)};var o=r;function a(e,t,n,i,r){var o="",a="",l=Math.floor(r/2)-1;return i-t>l&&(t=i-l+(o=" ... ").length),n-i>l&&(n=i+l-(a=" ...").length),{str:o+e.slice(t,n).replace(/\t/g,"→")+a,pos:i-t+o.length}}function l(e,t){return n.repeat(" ",t-e.length)+e}var c=function(e,t){if(t=Object.create(t||null),!e.buffer)return null;t.maxLength||(t.maxLength=79),"number"!=typeof t.indent&&(t.indent=1),"number"!=typeof t.linesBefore&&(t.linesBefore=3),"number"!=typeof t.linesAfter&&(t.linesAfter=2);for(var i,r=/\r?\n|\r|\0/g,o=[0],c=[],s=-1;i=r.exec(e.buffer);)c.push(i.index),o.push(i.index+i[0].length),e.position<=i.index&&s<0&&(s=o.length-2);s<0&&(s=o.length-1);var u,p,f="",d=Math.min(e.line+t.linesAfter,c.length).toString().length,h=t.maxLength-(t.indent+d+3);for(u=1;u<=t.linesBefore&&!(s-u<0);u++)p=a(e.buffer,o[s-u],c[s-u],e.position-(o[s]-o[s-u]),h),f=n.repeat(" ",t.indent)+l((e.line-u+1).toString(),d)+" | "+p.str+"\n"+f;for(p=a(e.buffer,o[s],c[s],e.position,h),f+=n.repeat(" ",t.indent)+l((e.line+1).toString(),d)+" | "+p.str+"\n",f+=n.repeat("-",t.indent+d+3+p.pos)+"^\n",u=1;u<=t.linesAfter&&!(s+u>=c.length);u++)p=a(e.buffer,o[s+u],c[s+u],e.position-(o[s]-o[s+u]),h),f+=n.repeat(" ",t.indent)+l((e.line+u+1).toString(),d)+" | "+p.str+"\n";return f.replace(/\n$/,"")},s=["kind","multi","resolve","construct","instanceOf","predicate","represent","representName","defaultStyle","styleAliases"],u=["scalar","sequence","mapping"];var p=function(e,t){if(t=t||{},Object.keys(t).forEach(function(t){if(-1===s.indexOf(t))throw new o('Unknown option "'+t+'" is met in definition of "'+e+'" YAML type.')}),this.options=t,this.tag=e,this.kind=t.kind||null,this.resolve=t.resolve||function(){return!0},this.construct=t.construct||function(e){return e},this.instanceOf=t.instanceOf||null,this.predicate=t.predicate||null,this.represent=t.represent||null,this.representName=t.representName||null,this.defaultStyle=t.defaultStyle||null,this.multi=t.multi||!1,this.styleAliases=function(e){var t={};return null!==e&&Object.keys(e).forEach(function(n){e[n].forEach(function(e){t[String(e)]=n})}),t}(t.styleAliases||null),-1===u.indexOf(this.kind))throw new o('Unknown kind "'+this.kind+'" is specified for "'+e+'" YAML type.')};function f(e,t){var n=[];return e[t].forEach(function(e){var t=n.length;n.forEach(function(n,i){n.tag===e.tag&&n.kind===e.kind&&n.multi===e.multi&&(t=i)}),n[t]=e}),n}function d(e){return this.extend(e)}d.prototype.extend=function(e){var t=[],n=[];if(e instanceof p)n.push(e);else if(Array.isArray(e))n=n.concat(e);else{if(!e||!Array.isArray(e.implicit)&&!Array.isArray(e.explicit))throw new o("Schema.extend argument should be a Type, [ Type ], or a schema definition ({ implicit: [...], explicit: [...] })");e.implicit&&(t=t.concat(e.implicit)),e.explicit&&(n=n.concat(e.explicit))}t.forEach(function(e){if(!(e instanceof p))throw new o("Specified list of YAML types (or a single Type object) contains a non-Type object.");if(e.loadKind&&"scalar"!==e.loadKind)throw new o("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.");if(e.multi)throw new o("There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.")}),n.forEach(function(e){if(!(e instanceof p))throw new o("Specified list of YAML types (or a single Type object) contains a non-Type object.")});var i=Object.create(d.prototype);return i.implicit=(this.implicit||[]).concat(t),i.explicit=(this.explicit||[]).concat(n),i.compiledImplicit=f(i,"implicit"),i.compiledExplicit=f(i,"explicit"),i.compiledTypeMap=function(){var e,t,n={scalar:{},sequence:{},mapping:{},fallback:{},multi:{scalar:[],sequence:[],mapping:[],fallback:[]}};function i(e){e.multi?(n.multi[e.kind].push(e),n.multi.fallback.push(e)):n[e.kiLine truncated
|
||||
+50
-44
@@ -1,49 +1,55 @@
|
||||
(function(){function diff_match_patch(){this.Diff_Timeout=1;this.Diff_EditCost=4;this.Match_Threshold=0.5;this.Match_Distance=1E3;this.Patch_DeleteThreshold=0.5;this.Patch_Margin=4;this.Match_MaxBits=32}
|
||||
diff_match_patch.prototype.diff_main=function(a,b,c,d){"undefined"==typeof d&&(d=0>=this.Diff_Timeout?Number.MAX_VALUE:(new Date).getTime()+1E3*this.Diff_Timeout);if(null==a||null==b)throw Error("Null input. (diff_main)");if(a==b)return a?[[0,a]]:[];"undefined"==typeof c&&(c=!0);var e=c,f=this.diff_commonPrefix(a,b);c=a.substring(0,f);a=a.substring(f);b=b.substring(f);var f=this.diff_commonSuffix(a,b),g=a.substring(a.length-f);a=a.substring(0,a.length-f);b=b.substring(0,b.length-f);a=this.diff_compute_(a,
|
||||
b,e,d);c&&a.unshift([0,c]);g&&a.push([0,g]);this.diff_cleanupMerge(a);return a};
|
||||
diff_match_patch.prototype.diff_compute_=function(a,b,c,d){if(!a)return[[1,b]];if(!b)return[[-1,a]];var e=a.length>b.length?a:b,f=a.length>b.length?b:a,g=e.indexOf(f);return-1!=g?(c=[[1,e.substring(0,g)],[0,f],[1,e.substring(g+f.length)]],a.length>b.length&&(c[0][0]=c[2][0]=-1),c):1==f.length?[[-1,a],[1,b]]:(e=this.diff_halfMatch_(a,b))?(f=e[0],a=e[1],g=e[2],b=e[3],e=e[4],f=this.diff_main(f,g,c,d),c=this.diff_main(a,b,c,d),f.concat([[0,e]],c)):c&&100<a.length&&100<b.length?this.diff_lineMode_(a,b,
|
||||
d):this.diff_bisect_(a,b,d)};
|
||||
diff_match_patch.prototype.diff_lineMode_=function(a,b,c){var d=this.diff_linesToChars_(a,b);a=d.chars1;b=d.chars2;d=d.lineArray;a=this.diff_main(a,b,!1,c);this.diff_charsToLines_(a,d);this.diff_cleanupSemantic(a);a.push([0,""]);for(var e=d=b=0,f="",g="";b<a.length;){switch(a[b][0]){case 1:e++;g+=a[b][1];break;case -1:d++;f+=a[b][1];break;case 0:if(1<=d&&1<=e){a.splice(b-d-e,d+e);b=b-d-e;d=this.diff_main(f,g,!1,c);for(e=d.length-1;0<=e;e--)a.splice(b,0,d[e]);b+=d.length}d=e=0;g=f=""}b++}a.pop();return a};
|
||||
diff_match_patch.prototype.diff_bisect_=function(a,b,c){for(var d=a.length,e=b.length,f=Math.ceil((d+e)/2),g=f,h=2*f,j=Array(h),i=Array(h),k=0;k<h;k++)j[k]=-1,i[k]=-1;j[g+1]=0;i[g+1]=0;for(var k=d-e,q=0!=k%2,r=0,t=0,p=0,w=0,v=0;v<f&&!((new Date).getTime()>c);v++){for(var n=-v+r;n<=v-t;n+=2){var l=g+n,m;m=n==-v||n!=v&&j[l-1]<j[l+1]?j[l+1]:j[l-1]+1;for(var s=m-n;m<d&&s<e&&a.charAt(m)==b.charAt(s);)m++,s++;j[l]=m;if(m>d)t+=2;else if(s>e)r+=2;else if(q&&(l=g+k-n,0<=l&&l<h&&-1!=i[l])){var u=d-i[l];if(m>=
|
||||
u)return this.diff_bisectSplit_(a,b,m,s,c)}}for(n=-v+p;n<=v-w;n+=2){l=g+n;u=n==-v||n!=v&&i[l-1]<i[l+1]?i[l+1]:i[l-1]+1;for(m=u-n;u<d&&m<e&&a.charAt(d-u-1)==b.charAt(e-m-1);)u++,m++;i[l]=u;if(u>d)w+=2;else if(m>e)p+=2;else if(!q&&(l=g+k-n,0<=l&&(l<h&&-1!=j[l])&&(m=j[l],s=g+m-l,u=d-u,m>=u)))return this.diff_bisectSplit_(a,b,m,s,c)}}return[[-1,a],[1,b]]};
|
||||
var diff_match_patch=function(){this.Diff_Timeout=1;this.Diff_EditCost=4;this.Match_Threshold=.5;this.Match_Distance=1E3;this.Patch_DeleteThreshold=.5;this.Patch_Margin=4;this.Match_MaxBits=32},DIFF_DELETE=-1,DIFF_INSERT=1,DIFF_EQUAL=0;diff_match_patch.Diff=function(a,b){this[0]=a;this[1]=b};diff_match_patch.Diff.prototype.length=2;diff_match_patch.Diff.prototype.toString=function(){return this[0]+","+this[1]};
|
||||
diff_match_patch.prototype.diff_main=function(a,b,c,d){"undefined"==typeof d&&(d=0>=this.Diff_Timeout?Number.MAX_VALUE:(new Date).getTime()+1E3*this.Diff_Timeout);if(null==a||null==b)throw Error("Null input. (diff_main)");if(a==b)return a?[new diff_match_patch.Diff(DIFF_EQUAL,a)]:[];"undefined"==typeof c&&(c=!0);var e=c,f=this.diff_commonPrefix(a,b);c=a.substring(0,f);a=a.substring(f);b=b.substring(f);f=this.diff_commonSuffix(a,b);var g=a.substring(a.length-f);a=a.substring(0,a.length-f);b=b.substring(0,
|
||||
b.length-f);a=this.diff_compute_(a,b,e,d);c&&a.unshift(new diff_match_patch.Diff(DIFF_EQUAL,c));g&&a.push(new diff_match_patch.Diff(DIFF_EQUAL,g));this.diff_cleanupMerge(a);return a};
|
||||
diff_match_patch.prototype.diff_compute_=function(a,b,c,d){if(!a)return[new diff_match_patch.Diff(DIFF_INSERT,b)];if(!b)return[new diff_match_patch.Diff(DIFF_DELETE,a)];var e=a.length>b.length?a:b,f=a.length>b.length?b:a,g=e.indexOf(f);return-1!=g?(c=[new diff_match_patch.Diff(DIFF_INSERT,e.substring(0,g)),new diff_match_patch.Diff(DIFF_EQUAL,f),new diff_match_patch.Diff(DIFF_INSERT,e.substring(g+f.length))],a.length>b.length&&(c[0][0]=c[2][0]=DIFF_DELETE),c):1==f.length?[new diff_match_patch.Diff(DIFF_DELETE,
|
||||
a),new diff_match_patch.Diff(DIFF_INSERT,b)]:(e=this.diff_halfMatch_(a,b))?(b=e[1],f=e[3],a=e[4],e=this.diff_main(e[0],e[2],c,d),c=this.diff_main(b,f,c,d),e.concat([new diff_match_patch.Diff(DIFF_EQUAL,a)],c)):c&&100<a.length&&100<b.length?this.diff_lineMode_(a,b,d):this.diff_bisect_(a,b,d)};
|
||||
diff_match_patch.prototype.diff_lineMode_=function(a,b,c){var d=this.diff_linesToChars_(a,b);a=d.chars1;b=d.chars2;d=d.lineArray;a=this.diff_main(a,b,!1,c);this.diff_charsToLines_(a,d);this.diff_cleanupSemantic(a);a.push(new diff_match_patch.Diff(DIFF_EQUAL,""));for(var e=d=b=0,f="",g="";b<a.length;){switch(a[b][0]){case DIFF_INSERT:e++;g+=a[b][1];break;case DIFF_DELETE:d++;f+=a[b][1];break;case DIFF_EQUAL:if(1<=d&&1<=e){a.splice(b-d-e,d+e);b=b-d-e;d=this.diff_main(f,g,!1,c);for(e=d.length-1;0<=e;e--)a.splice(b,
|
||||
0,d[e]);b+=d.length}d=e=0;g=f=""}b++}a.pop();return a};
|
||||
diff_match_patch.prototype.diff_bisect_=function(a,b,c){for(var d=a.length,e=b.length,f=Math.ceil((d+e)/2),g=2*f,h=Array(g),l=Array(g),k=0;k<g;k++)h[k]=-1,l[k]=-1;h[f+1]=0;l[f+1]=0;k=d-e;for(var m=0!=k%2,p=0,x=0,w=0,q=0,t=0;t<f&&!((new Date).getTime()>c);t++){for(var v=-t+p;v<=t-x;v+=2){var n=f+v;var r=v==-t||v!=t&&h[n-1]<h[n+1]?h[n+1]:h[n-1]+1;for(var y=r-v;r<d&&y<e&&a.charAt(r)==b.charAt(y);)r++,y++;h[n]=r;if(r>d)x+=2;else if(y>e)p+=2;else if(m&&(n=f+k-v,0<=n&&n<g&&-1!=l[n])){var u=d-l[n];if(r>=
|
||||
u)return this.diff_bisectSplit_(a,b,r,y,c)}}for(v=-t+w;v<=t-q;v+=2){n=f+v;u=v==-t||v!=t&&l[n-1]<l[n+1]?l[n+1]:l[n-1]+1;for(r=u-v;u<d&&r<e&&a.charAt(d-u-1)==b.charAt(e-r-1);)u++,r++;l[n]=u;if(u>d)q+=2;else if(r>e)w+=2;else if(!m&&(n=f+k-v,0<=n&&n<g&&-1!=h[n]&&(r=h[n],y=f+r-n,u=d-u,r>=u)))return this.diff_bisectSplit_(a,b,r,y,c)}}return[new diff_match_patch.Diff(DIFF_DELETE,a),new diff_match_patch.Diff(DIFF_INSERT,b)]};
|
||||
diff_match_patch.prototype.diff_bisectSplit_=function(a,b,c,d,e){var f=a.substring(0,c),g=b.substring(0,d);a=a.substring(c);b=b.substring(d);f=this.diff_main(f,g,!1,e);e=this.diff_main(a,b,!1,e);return f.concat(e)};
|
||||
diff_match_patch.prototype.diff_linesToChars_=function(a,b){function c(a){for(var b="",c=0,f=-1,g=d.length;f<a.length-1;){f=a.indexOf("\n",c);-1==f&&(f=a.length-1);var r=a.substring(c,f+1),c=f+1;(e.hasOwnProperty?e.hasOwnProperty(r):void 0!==e[r])?b+=String.fromCharCode(e[r]):(b+=String.fromCharCode(g),e[r]=g,d[g++]=r)}return b}var d=[],e={};d[0]="";var f=c(a),g=c(b);return{chars1:f,chars2:g,lineArray:d}};
|
||||
diff_match_patch.prototype.diff_linesToChars_=function(a,b){function c(a){for(var b="",c=0,g=-1,h=d.length;g<a.length-1;){g=a.indexOf("\n",c);-1==g&&(g=a.length-1);var l=a.substring(c,g+1);(e.hasOwnProperty?e.hasOwnProperty(l):void 0!==e[l])?b+=String.fromCharCode(e[l]):(h==f&&(l=a.substring(c),g=a.length),b+=String.fromCharCode(h),e[l]=h,d[h++]=l);c=g+1}return b}var d=[],e={};d[0]="";var f=4E4,g=c(a);f=65535;var h=c(b);return{chars1:g,chars2:h,lineArray:d}};
|
||||
diff_match_patch.prototype.diff_charsToLines_=function(a,b){for(var c=0;c<a.length;c++){for(var d=a[c][1],e=[],f=0;f<d.length;f++)e[f]=b[d.charCodeAt(f)];a[c][1]=e.join("")}};diff_match_patch.prototype.diff_commonPrefix=function(a,b){if(!a||!b||a.charAt(0)!=b.charAt(0))return 0;for(var c=0,d=Math.min(a.length,b.length),e=d,f=0;c<e;)a.substring(f,e)==b.substring(f,e)?f=c=e:d=e,e=Math.floor((d-c)/2+c);return e};
|
||||
diff_match_patch.prototype.diff_commonSuffix=function(a,b){if(!a||!b||a.charAt(a.length-1)!=b.charAt(b.length-1))return 0;for(var c=0,d=Math.min(a.length,b.length),e=d,f=0;c<e;)a.substring(a.length-e,a.length-f)==b.substring(b.length-e,b.length-f)?f=c=e:d=e,e=Math.floor((d-c)/2+c);return e};
|
||||
diff_match_patch.prototype.diff_commonOverlap_=function(a,b){var c=a.length,d=b.length;if(0==c||0==d)return 0;c>d?a=a.substring(c-d):c<d&&(b=b.substring(0,c));c=Math.min(c,d);if(a==b)return c;for(var d=0,e=1;;){var f=a.substring(c-e),f=b.indexOf(f);if(-1==f)return d;e+=f;if(0==f||a.substring(c-e)==b.substring(0,e))d=e,e++}};
|
||||
diff_match_patch.prototype.diff_halfMatch_=function(a,b){function c(a,b,c){for(var d=a.substring(c,c+Math.floor(a.length/4)),e=-1,g="",h,j,n,l;-1!=(e=b.indexOf(d,e+1));){var m=f.diff_commonPrefix(a.substring(c),b.substring(e)),s=f.diff_commonSuffix(a.substring(0,c),b.substring(0,e));g.length<s+m&&(g=b.substring(e-s,e)+b.substring(e,e+m),h=a.substring(0,c-s),j=a.substring(c+m),n=b.substring(0,e-s),l=b.substring(e+m))}return 2*g.length>=a.length?[h,j,n,l,g]:null}if(0>=this.Diff_Timeout)return null;
|
||||
var d=a.length>b.length?a:b,e=a.length>b.length?b:a;if(4>d.length||2*e.length<d.length)return null;var f=this,g=c(d,e,Math.ceil(d.length/4)),d=c(d,e,Math.ceil(d.length/2)),h;if(!g&&!d)return null;h=d?g?g[4].length>d[4].length?g:d:d:g;var j;a.length>b.length?(g=h[0],d=h[1],e=h[2],j=h[3]):(e=h[0],j=h[1],g=h[2],d=h[3]);h=h[4];return[g,d,e,j,h]};
|
||||
diff_match_patch.prototype.diff_cleanupSemantic=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=0,h=0,j=0,i=0;f<a.length;)0==a[f][0]?(c[d++]=f,g=j,h=i,i=j=0,e=a[f][1]):(1==a[f][0]?j+=a[f][1].length:i+=a[f][1].length,e&&(e.length<=Math.max(g,h)&&e.length<=Math.max(j,i))&&(a.splice(c[d-1],0,[-1,e]),a[c[d-1]+1][0]=1,d--,d--,f=0<d?c[d-1]:-1,i=j=h=g=0,e=null,b=!0)),f++;b&&this.diff_cleanupMerge(a);this.diff_cleanupSemanticLossless(a);for(f=1;f<a.length;){if(-1==a[f-1][0]&&1==a[f][0]){b=a[f-1][1];c=a[f][1];
|
||||
d=this.diff_commonOverlap_(b,c);e=this.diff_commonOverlap_(c,b);if(d>=e){if(d>=b.length/2||d>=c.length/2)a.splice(f,0,[0,c.substring(0,d)]),a[f-1][1]=b.substring(0,b.length-d),a[f+1][1]=c.substring(d),f++}else if(e>=b.length/2||e>=c.length/2)a.splice(f,0,[0,b.substring(0,e)]),a[f-1][0]=1,a[f-1][1]=c.substring(0,c.length-e),a[f+1][0]=-1,a[f+1][1]=b.substring(e),f++;f++}f++}};
|
||||
diff_match_patch.prototype.diff_cleanupSemanticLossless=function(a){function b(a,b){if(!a||!b)return 6;var c=a.charAt(a.length-1),d=b.charAt(0),e=c.match(diff_match_patch.nonAlphaNumericRegex_),f=d.match(diff_match_patch.nonAlphaNumericRegex_),g=e&&c.match(diff_match_patch.whitespaceRegex_),h=f&&d.match(diff_match_patch.whitespaceRegex_),c=g&&c.match(diff_match_patch.linebreakRegex_),d=h&&d.match(diff_match_patch.linebreakRegex_),i=c&&a.match(diff_match_patch.blanklineEndRegex_),j=d&&b.match(diff_match_patch.blanklineStartRegex_);
|
||||
return i||j?5:c||d?4:e&&!g&&h?3:g||h?2:e||f?1:0}for(var c=1;c<a.length-1;){if(0==a[c-1][0]&&0==a[c+1][0]){var d=a[c-1][1],e=a[c][1],f=a[c+1][1],g=this.diff_commonSuffix(d,e);if(g)var h=e.substring(e.length-g),d=d.substring(0,d.length-g),e=h+e.substring(0,e.length-g),f=h+f;for(var g=d,h=e,j=f,i=b(d,e)+b(e,f);e.charAt(0)===f.charAt(0);){var d=d+e.charAt(0),e=e.substring(1)+f.charAt(0),f=f.substring(1),k=b(d,e)+b(e,f);k>=i&&(i=k,g=d,h=e,j=f)}a[c-1][1]!=g&&(g?a[c-1][1]=g:(a.splice(c-1,1),c--),a[c][1]=
|
||||
h,j?a[c+1][1]=j:(a.splice(c+1,1),c--))}c++}};diff_match_patch.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/;diff_match_patch.whitespaceRegex_=/\s/;diff_match_patch.linebreakRegex_=/[\r\n]/;diff_match_patch.blanklineEndRegex_=/\n\r?\n$/;diff_match_patch.blanklineStartRegex_=/^\r?\n\r?\n/;
|
||||
diff_match_patch.prototype.diff_cleanupEfficiency=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=!1,h=!1,j=!1,i=!1;f<a.length;){if(0==a[f][0])a[f][1].length<this.Diff_EditCost&&(j||i)?(c[d++]=f,g=j,h=i,e=a[f][1]):(d=0,e=null),j=i=!1;else if(-1==a[f][0]?i=!0:j=!0,e&&(g&&h&&j&&i||e.length<this.Diff_EditCost/2&&3==g+h+j+i))a.splice(c[d-1],0,[-1,e]),a[c[d-1]+1][0]=1,d--,e=null,g&&h?(j=i=!0,d=0):(d--,f=0<d?c[d-1]:-1,j=i=!1),b=!0;f++}b&&this.diff_cleanupMerge(a)};
|
||||
diff_match_patch.prototype.diff_cleanupMerge=function(a){a.push([0,""]);for(var b=0,c=0,d=0,e="",f="",g;b<a.length;)switch(a[b][0]){case 1:d++;f+=a[b][1];b++;break;case -1:c++;e+=a[b][1];b++;break;case 0:1<c+d?(0!==c&&0!==d&&(g=this.diff_commonPrefix(f,e),0!==g&&(0<b-c-d&&0==a[b-c-d-1][0]?a[b-c-d-1][1]+=f.substring(0,g):(a.splice(0,0,[0,f.substring(0,g)]),b++),f=f.substring(g),e=e.substring(g)),g=this.diff_commonSuffix(f,e),0!==g&&(a[b][1]=f.substring(f.length-g)+a[b][1],f=f.substring(0,f.length-
|
||||
g),e=e.substring(0,e.length-g))),0===c?a.splice(b-d,c+d,[1,f]):0===d?a.splice(b-c,c+d,[-1,e]):a.splice(b-c-d,c+d,[-1,e],[1,f]),b=b-c-d+(c?1:0)+(d?1:0)+1):0!==b&&0==a[b-1][0]?(a[b-1][1]+=a[b][1],a.splice(b,1)):b++,c=d=0,f=e=""}""===a[a.length-1][1]&&a.pop();c=!1;for(b=1;b<a.length-1;)0==a[b-1][0]&&0==a[b+1][0]&&(a[b][1].substring(a[b][1].length-a[b-1][1].length)==a[b-1][1]?(a[b][1]=a[b-1][1]+a[b][1].substring(0,a[b][1].length-a[b-1][1].length),a[b+1][1]=a[b-1][1]+a[b+1][1],a.splice(b-1,1),c=!0):a[b][1].substring(0,
|
||||
a[b+1][1].length)==a[b+1][1]&&(a[b-1][1]+=a[b+1][1],a[b][1]=a[b][1].substring(a[b+1][1].length)+a[b+1][1],a.splice(b+1,1),c=!0)),b++;c&&this.diff_cleanupMerge(a)};diff_match_patch.prototype.diff_xIndex=function(a,b){var c=0,d=0,e=0,f=0,g;for(g=0;g<a.length;g++){1!==a[g][0]&&(c+=a[g][1].length);-1!==a[g][0]&&(d+=a[g][1].length);if(c>b)break;e=c;f=d}return a.length!=g&&-1===a[g][0]?f:f+(b-e)};
|
||||
diff_match_patch.prototype.diff_prettyHtml=function(a){for(var b=[],c=/&/g,d=/</g,e=/>/g,f=/\n/g,g=0;g<a.length;g++){var h=a[g][0],j=a[g][1],j=j.replace(c,"&").replace(d,"<").replace(e,">").replace(f,"¶<br>");switch(h){case 1:b[g]='<ins style="background:#e6ffe6;">'+j+"</ins>";break;case -1:b[g]='<del style="background:#ffe6e6;">'+j+"</del>";break;case 0:b[g]="<span>"+j+"</span>"}}return b.join("")};
|
||||
diff_match_patch.prototype.diff_text1=function(a){for(var b=[],c=0;c<a.length;c++)1!==a[c][0]&&(b[c]=a[c][1]);return b.join("")};diff_match_patch.prototype.diff_text2=function(a){for(var b=[],c=0;c<a.length;c++)-1!==a[c][0]&&(b[c]=a[c][1]);return b.join("")};diff_match_patch.prototype.diff_levenshtein=function(a){for(var b=0,c=0,d=0,e=0;e<a.length;e++){var f=a[e][0],g=a[e][1];switch(f){case 1:c+=g.length;break;case -1:d+=g.length;break;case 0:b+=Math.max(c,d),d=c=0}}return b+=Math.max(c,d)};
|
||||
diff_match_patch.prototype.diff_toDelta=function(a){for(var b=[],c=0;c<a.length;c++)switch(a[c][0]){case 1:b[c]="+"+encodeURI(a[c][1]);break;case -1:b[c]="-"+a[c][1].length;break;case 0:b[c]="="+a[c][1].length}return b.join("\t").replace(/%20/g," ")};
|
||||
diff_match_patch.prototype.diff_fromDelta=function(a,b){for(var c=[],d=0,e=0,f=b.split(/\t/g),g=0;g<f.length;g++){var h=f[g].substring(1);switch(f[g].charAt(0)){case "+":try{c[d++]=[1,decodeURI(h)]}catch(j){throw Error("Illegal escape in diff_fromDelta: "+h);}break;case "-":case "=":var i=parseInt(h,10);if(isNaN(i)||0>i)throw Error("Invalid number in diff_fromDelta: "+h);h=a.substring(e,e+=i);"="==f[g].charAt(0)?c[d++]=[0,h]:c[d++]=[-1,h];break;default:if(f[g])throw Error("Invalid diff operation in diff_fromDelta: "+
|
||||
f[g]);}}if(e!=a.length)throw Error("Delta length ("+e+") does not equal source text length ("+a.length+").");return c};diff_match_patch.prototype.match_main=function(a,b,c){if(null==a||null==b||null==c)throw Error("Null input. (match_main)");c=Math.max(0,Math.min(c,a.length));return a==b?0:a.length?a.substring(c,c+b.length)==b?c:this.match_bitap_(a,b,c):-1};
|
||||
diff_match_patch.prototype.match_bitap_=function(a,b,c){function d(a,d){var e=a/b.length,g=Math.abs(c-d);return!f.Match_Distance?g?1:e:e+g/f.Match_Distance}if(b.length>this.Match_MaxBits)throw Error("Pattern too long for this browser.");var e=this.match_alphabet_(b),f=this,g=this.Match_Threshold,h=a.indexOf(b,c);-1!=h&&(g=Math.min(d(0,h),g),h=a.lastIndexOf(b,c+b.length),-1!=h&&(g=Math.min(d(0,h),g)));for(var j=1<<b.length-1,h=-1,i,k,q=b.length+a.length,r,t=0;t<b.length;t++){i=0;for(k=q;i<k;)d(t,c+
|
||||
k)<=g?i=k:q=k,k=Math.floor((q-i)/2+i);q=k;i=Math.max(1,c-k+1);var p=Math.min(c+k,a.length)+b.length;k=Array(p+2);for(k[p+1]=(1<<t)-1;p>=i;p--){var w=e[a.charAt(p-1)];k[p]=0===t?(k[p+1]<<1|1)&w:(k[p+1]<<1|1)&w|((r[p+1]|r[p])<<1|1)|r[p+1];if(k[p]&j&&(w=d(t,p-1),w<=g))if(g=w,h=p-1,h>c)i=Math.max(1,2*c-h);else break}if(d(t+1,c)>g)break;r=k}return h};
|
||||
diff_match_patch.prototype.diff_commonOverlap_=function(a,b){var c=a.length,d=b.length;if(0==c||0==d)return 0;c>d?a=a.substring(c-d):c<d&&(b=b.substring(0,c));c=Math.min(c,d);if(a==b)return c;d=0;for(var e=1;;){var f=a.substring(c-e);f=b.indexOf(f);if(-1==f)return d;e+=f;if(0==f||a.substring(c-e)==b.substring(0,e))d=e,e++}};
|
||||
diff_match_patch.prototype.diff_halfMatch_=function(a,b){function c(a,b,c){for(var d=a.substring(c,c+Math.floor(a.length/4)),e=-1,g="",h,k,l,m;-1!=(e=b.indexOf(d,e+1));){var p=f.diff_commonPrefix(a.substring(c),b.substring(e)),u=f.diff_commonSuffix(a.substring(0,c),b.substring(0,e));g.length<u+p&&(g=b.substring(e-u,e)+b.substring(e,e+p),h=a.substring(0,c-u),k=a.substring(c+p),l=b.substring(0,e-u),m=b.substring(e+p))}return 2*g.length>=a.length?[h,k,l,m,g]:null}if(0>=this.Diff_Timeout)return null;
|
||||
var d=a.length>b.length?a:b,e=a.length>b.length?b:a;if(4>d.length||2*e.length<d.length)return null;var f=this,g=c(d,e,Math.ceil(d.length/4));d=c(d,e,Math.ceil(d.length/2));if(g||d)g=d?g?g[4].length>d[4].length?g:d:d:g;else return null;if(a.length>b.length){d=g[0];e=g[1];var h=g[2];var l=g[3]}else h=g[0],l=g[1],d=g[2],e=g[3];return[d,e,h,l,g[4]]};
|
||||
diff_match_patch.prototype.diff_cleanupSemantic=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=0,h=0,l=0,k=0;f<a.length;)a[f][0]==DIFF_EQUAL?(c[d++]=f,g=l,h=k,k=l=0,e=a[f][1]):(a[f][0]==DIFF_INSERT?l+=a[f][1].length:k+=a[f][1].length,e&&e.length<=Math.max(g,h)&&e.length<=Math.max(l,k)&&(a.splice(c[d-1],0,new diff_match_patch.Diff(DIFF_DELETE,e)),a[c[d-1]+1][0]=DIFF_INSERT,d--,d--,f=0<d?c[d-1]:-1,k=l=h=g=0,e=null,b=!0)),f++;b&&this.diff_cleanupMerge(a);this.diff_cleanupSemanticLossless(a);for(f=1;f<
|
||||
a.length;){if(a[f-1][0]==DIFF_DELETE&&a[f][0]==DIFF_INSERT){b=a[f-1][1];c=a[f][1];d=this.diff_commonOverlap_(b,c);e=this.diff_commonOverlap_(c,b);if(d>=e){if(d>=b.length/2||d>=c.length/2)a.splice(f,0,new diff_match_patch.Diff(DIFF_EQUAL,c.substring(0,d))),a[f-1][1]=b.substring(0,b.length-d),a[f+1][1]=c.substring(d),f++}else if(e>=b.length/2||e>=c.length/2)a.splice(f,0,new diff_match_patch.Diff(DIFF_EQUAL,b.substring(0,e))),a[f-1][0]=DIFF_INSERT,a[f-1][1]=c.substring(0,c.length-e),a[f+1][0]=DIFF_DELETE,
|
||||
a[f+1][1]=b.substring(e),f++;f++}f++}};
|
||||
diff_match_patch.prototype.diff_cleanupSemanticLossless=function(a){function b(a,b){if(!a||!b)return 6;var c=a.charAt(a.length-1),d=b.charAt(0),e=c.match(diff_match_patch.nonAlphaNumericRegex_),f=d.match(diff_match_patch.nonAlphaNumericRegex_),g=e&&c.match(diff_match_patch.whitespaceRegex_),h=f&&d.match(diff_match_patch.whitespaceRegex_);c=g&&c.match(diff_match_patch.linebreakRegex_);d=h&&d.match(diff_match_patch.linebreakRegex_);var k=c&&a.match(diff_match_patch.blanklineEndRegex_),l=d&&b.match(diff_match_patch.blanklineStartRegex_);
|
||||
return k||l?5:c||d?4:e&&!g&&h?3:g||h?2:e||f?1:0}for(var c=1;c<a.length-1;){if(a[c-1][0]==DIFF_EQUAL&&a[c+1][0]==DIFF_EQUAL){var d=a[c-1][1],e=a[c][1],f=a[c+1][1],g=this.diff_commonSuffix(d,e);if(g){var h=e.substring(e.length-g);d=d.substring(0,d.length-g);e=h+e.substring(0,e.length-g);f=h+f}g=d;h=e;for(var l=f,k=b(d,e)+b(e,f);e.charAt(0)===f.charAt(0);){d+=e.charAt(0);e=e.substring(1)+f.charAt(0);f=f.substring(1);var m=b(d,e)+b(e,f);m>=k&&(k=m,g=d,h=e,l=f)}a[c-1][1]!=g&&(g?a[c-1][1]=g:(a.splice(c-
|
||||
1,1),c--),a[c][1]=h,l?a[c+1][1]=l:(a.splice(c+1,1),c--))}c++}};diff_match_patch.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/;diff_match_patch.whitespaceRegex_=/\s/;diff_match_patch.linebreakRegex_=/[\r\n]/;diff_match_patch.blanklineEndRegex_=/\n\r?\n$/;diff_match_patch.blanklineStartRegex_=/^\r?\n\r?\n/;
|
||||
diff_match_patch.prototype.diff_cleanupEfficiency=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=!1,h=!1,l=!1,k=!1;f<a.length;)a[f][0]==DIFF_EQUAL?(a[f][1].length<this.Diff_EditCost&&(l||k)?(c[d++]=f,g=l,h=k,e=a[f][1]):(d=0,e=null),l=k=!1):(a[f][0]==DIFF_DELETE?k=!0:l=!0,e&&(g&&h&&l&&k||e.length<this.Diff_EditCost/2&&3==g+h+l+k)&&(a.splice(c[d-1],0,new diff_match_patch.Diff(DIFF_DELETE,e)),a[c[d-1]+1][0]=DIFF_INSERT,d--,e=null,g&&h?(l=k=!0,d=0):(d--,f=0<d?c[d-1]:-1,l=k=!1),b=!0)),f++;b&&this.diff_cleanupMerge(a)};
|
||||
diff_match_patch.prototype.diff_cleanupMerge=function(a){a.push(new diff_match_patch.Diff(DIFF_EQUAL,""));for(var b=0,c=0,d=0,e="",f="",g;b<a.length;)switch(a[b][0]){case DIFF_INSERT:d++;f+=a[b][1];b++;break;case DIFF_DELETE:c++;e+=a[b][1];b++;break;case DIFF_EQUAL:1<c+d?(0!==c&&0!==d&&(g=this.diff_commonPrefix(f,e),0!==g&&(0<b-c-d&&a[b-c-d-1][0]==DIFF_EQUAL?a[b-c-d-1][1]+=f.substring(0,g):(a.splice(0,0,new diff_match_patch.Diff(DIFF_EQUAL,f.substring(0,g))),b++),f=f.substring(g),e=e.substring(g)),
|
||||
g=this.diff_commonSuffix(f,e),0!==g&&(a[b][1]=f.substring(f.length-g)+a[b][1],f=f.substring(0,f.length-g),e=e.substring(0,e.length-g))),b-=c+d,a.splice(b,c+d),e.length&&(a.splice(b,0,new diff_match_patch.Diff(DIFF_DELETE,e)),b++),f.length&&(a.splice(b,0,new diff_match_patch.Diff(DIFF_INSERT,f)),b++),b++):0!==b&&a[b-1][0]==DIFF_EQUAL?(a[b-1][1]+=a[b][1],a.splice(b,1)):b++,c=d=0,f=e=""}""===a[a.length-1][1]&&a.pop();c=!1;for(b=1;b<a.length-1;)a[b-1][0]==DIFF_EQUAL&&a[b+1][0]==DIFF_EQUAL&&(a[b][1].substring(a[b][1].length-
|
||||
a[b-1][1].length)==a[b-1][1]?(a[b][1]=a[b-1][1]+a[b][1].substring(0,a[b][1].length-a[b-1][1].length),a[b+1][1]=a[b-1][1]+a[b+1][1],a.splice(b-1,1),c=!0):a[b][1].substring(0,a[b+1][1].length)==a[b+1][1]&&(a[b-1][1]+=a[b+1][1],a[b][1]=a[b][1].substring(a[b+1][1].length)+a[b+1][1],a.splice(b+1,1),c=!0)),b++;c&&this.diff_cleanupMerge(a)};
|
||||
diff_match_patch.prototype.diff_xIndex=function(a,b){var c=0,d=0,e=0,f=0,g;for(g=0;g<a.length;g++){a[g][0]!==DIFF_INSERT&&(c+=a[g][1].length);a[g][0]!==DIFF_DELETE&&(d+=a[g][1].length);if(c>b)break;e=c;f=d}return a.length!=g&&a[g][0]===DIFF_DELETE?f:f+(b-e)};
|
||||
diff_match_patch.prototype.diff_prettyHtml=function(a){for(var b=[],c=/&/g,d=/</g,e=/>/g,f=/\n/g,g=0;g<a.length;g++){var h=a[g][0],l=a[g][1].replace(c,"&").replace(d,"<").replace(e,">").replace(f,"¶<br>");switch(h){case DIFF_INSERT:b[g]='<ins style="background:#e6ffe6;">'+l+"</ins>";break;case DIFF_DELETE:b[g]='<del style="background:#ffe6e6;">'+l+"</del>";break;case DIFF_EQUAL:b[g]="<span>"+l+"</span>"}}return b.join("")};
|
||||
diff_match_patch.prototype.diff_text1=function(a){for(var b=[],c=0;c<a.length;c++)a[c][0]!==DIFF_INSERT&&(b[c]=a[c][1]);return b.join("")};diff_match_patch.prototype.diff_text2=function(a){for(var b=[],c=0;c<a.length;c++)a[c][0]!==DIFF_DELETE&&(b[c]=a[c][1]);return b.join("")};
|
||||
diff_match_patch.prototype.diff_levenshtein=function(a){for(var b=0,c=0,d=0,e=0;e<a.length;e++){var f=a[e][1];switch(a[e][0]){case DIFF_INSERT:c+=f.length;break;case DIFF_DELETE:d+=f.length;break;case DIFF_EQUAL:b+=Math.max(c,d),d=c=0}}return b+=Math.max(c,d)};
|
||||
diff_match_patch.prototype.diff_toDelta=function(a){for(var b=[],c=0;c<a.length;c++)switch(a[c][0]){case DIFF_INSERT:b[c]="+"+encodeURI(a[c][1]);break;case DIFF_DELETE:b[c]="-"+a[c][1].length;break;case DIFF_EQUAL:b[c]="="+a[c][1].length}return b.join("\t").replace(/%20/g," ")};
|
||||
diff_match_patch.prototype.diff_fromDelta=function(a,b){for(var c=[],d=0,e=0,f=b.split(/\t/g),g=0;g<f.length;g++){var h=f[g].substring(1);switch(f[g].charAt(0)){case "+":try{c[d++]=new diff_match_patch.Diff(DIFF_INSERT,decodeURI(h))}catch(k){throw Error("Illegal escape in diff_fromDelta: "+h);}break;case "-":case "=":var l=parseInt(h,10);if(isNaN(l)||0>l)throw Error("Invalid number in diff_fromDelta: "+h);h=a.substring(e,e+=l);"="==f[g].charAt(0)?c[d++]=new diff_match_patch.Diff(DIFF_EQUAL,h):c[d++]=
|
||||
new diff_match_patch.Diff(DIFF_DELETE,h);break;default:if(f[g])throw Error("Invalid diff operation in diff_fromDelta: "+f[g]);}}if(e!=a.length)throw Error("Delta length ("+e+") does not equal source text length ("+a.length+").");return c};diff_match_patch.prototype.match_main=function(a,b,c){if(null==a||null==b||null==c)throw Error("Null input. (match_main)");c=Math.max(0,Math.min(c,a.length));return a==b?0:a.length?a.substring(c,c+b.length)==b?c:this.match_bitap_(a,b,c):-1};
|
||||
diff_match_patch.prototype.match_bitap_=function(a,b,c){function d(a,d){var e=a/b.length,g=Math.abs(c-d);return f.Match_Distance?e+g/f.Match_Distance:g?1:e}if(b.length>this.Match_MaxBits)throw Error("Pattern too long for this browser.");var e=this.match_alphabet_(b),f=this,g=this.Match_Threshold,h=a.indexOf(b,c);-1!=h&&(g=Math.min(d(0,h),g),h=a.lastIndexOf(b,c+b.length),-1!=h&&(g=Math.min(d(0,h),g)));var l=1<<b.length-1;h=-1;for(var k,m,p=b.length+a.length,x,w=0;w<b.length;w++){k=0;for(m=p;k<m;)d(w,
|
||||
c+m)<=g?k=m:p=m,m=Math.floor((p-k)/2+k);p=m;k=Math.max(1,c-m+1);var q=Math.min(c+m,a.length)+b.length;m=Array(q+2);for(m[q+1]=(1<<w)-1;q>=k;q--){var t=e[a.charAt(q-1)];m[q]=0===w?(m[q+1]<<1|1)&t:(m[q+1]<<1|1)&t|(x[q+1]|x[q])<<1|1|x[q+1];if(m[q]&l&&(t=d(w,q-1),t<=g))if(g=t,h=q-1,h>c)k=Math.max(1,2*c-h);else break}if(d(w+1,c)>g)break;x=m}return h};
|
||||
diff_match_patch.prototype.match_alphabet_=function(a){for(var b={},c=0;c<a.length;c++)b[a.charAt(c)]=0;for(c=0;c<a.length;c++)b[a.charAt(c)]|=1<<a.length-c-1;return b};
|
||||
diff_match_patch.prototype.patch_addContext_=function(a,b){if(0!=b.length){for(var c=b.substring(a.start2,a.start2+a.length1),d=0;b.indexOf(c)!=b.lastIndexOf(c)&&c.length<this.Match_MaxBits-this.Patch_Margin-this.Patch_Margin;)d+=this.Patch_Margin,c=b.substring(a.start2-d,a.start2+a.length1+d);d+=this.Patch_Margin;(c=b.substring(a.start2-d,a.start2))&&a.diffs.unshift([0,c]);(d=b.substring(a.start2+a.length1,a.start2+a.length1+d))&&a.diffs.push([0,d]);a.start1-=c.length;a.start2-=c.length;a.length1+=
|
||||
c.length+d.length;a.length2+=c.length+d.length}};
|
||||
diff_match_patch.prototype.patch_make=function(a,b,c){var d;if("string"==typeof a&&"string"==typeof b&&"undefined"==typeof c)d=a,b=this.diff_main(d,b,!0),2<b.length&&(this.diff_cleanupSemantic(b),this.diff_cleanupEfficiency(b));else if(a&&"object"==typeof a&&"undefined"==typeof b&&"undefined"==typeof c)b=a,d=this.diff_text1(b);else if("string"==typeof a&&b&&"object"==typeof b&&"undefined"==typeof c)d=a;else if("string"==typeof a&&"string"==typeof b&&c&&"object"==typeof c)d=a,b=c;else throw Error("Unknown call format to patch_make.");
|
||||
if(0===b.length)return[];c=[];a=new diff_match_patch.patch_obj;for(var e=0,f=0,g=0,h=d,j=0;j<b.length;j++){var i=b[j][0],k=b[j][1];!e&&0!==i&&(a.start1=f,a.start2=g);switch(i){case 1:a.diffs[e++]=b[j];a.length2+=k.length;d=d.substring(0,g)+k+d.substring(g);break;case -1:a.length1+=k.length;a.diffs[e++]=b[j];d=d.substring(0,g)+d.substring(g+k.length);break;case 0:k.length<=2*this.Patch_Margin&&e&&b.length!=j+1?(a.diffs[e++]=b[j],a.length1+=k.length,a.length2+=k.length):k.length>=2*this.Patch_Margin&&
|
||||
e&&(this.patch_addContext_(a,h),c.push(a),a=new diff_match_patch.patch_obj,e=0,h=d,f=g)}1!==i&&(f+=k.length);-1!==i&&(g+=k.length)}e&&(this.patch_addContext_(a,h),c.push(a));return c};diff_match_patch.prototype.patch_deepCopy=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c],e=new diff_match_patch.patch_obj;e.diffs=[];for(var f=0;f<d.diffs.length;f++)e.diffs[f]=d.diffs[f].slice();e.start1=d.start1;e.start2=d.start2;e.length1=d.length1;e.length2=d.length2;b[c]=e}return b};
|
||||
diff_match_patch.prototype.patch_apply=function(a,b){if(0==a.length)return[b,[]];a=this.patch_deepCopy(a);var c=this.patch_addPadding(a);b=c+b+c;this.patch_splitMax(a);for(var d=0,e=[],f=0;f<a.length;f++){var g=a[f].start2+d,h=this.diff_text1(a[f].diffs),j,i=-1;if(h.length>this.Match_MaxBits){if(j=this.match_main(b,h.substring(0,this.Match_MaxBits),g),-1!=j&&(i=this.match_main(b,h.substring(h.length-this.Match_MaxBits),g+h.length-this.Match_MaxBits),-1==i||j>=i))j=-1}else j=this.match_main(b,h,g);
|
||||
if(-1==j)e[f]=!1,d-=a[f].length2-a[f].length1;else if(e[f]=!0,d=j-g,g=-1==i?b.substring(j,j+h.length):b.substring(j,i+this.Match_MaxBits),h==g)b=b.substring(0,j)+this.diff_text2(a[f].diffs)+b.substring(j+h.length);else if(g=this.diff_main(h,g,!1),h.length>this.Match_MaxBits&&this.diff_levenshtein(g)/h.length>this.Patch_DeleteThreshold)e[f]=!1;else{this.diff_cleanupSemanticLossless(g);for(var h=0,k,i=0;i<a[f].diffs.length;i++){var q=a[f].diffs[i];0!==q[0]&&(k=this.diff_xIndex(g,h));1===q[0]?b=b.substring(0,
|
||||
j+k)+q[1]+b.substring(j+k):-1===q[0]&&(b=b.substring(0,j+k)+b.substring(j+this.diff_xIndex(g,h+q[1].length)));-1!==q[0]&&(h+=q[1].length)}}}b=b.substring(c.length,b.length-c.length);return[b,e]};
|
||||
diff_match_patch.prototype.patch_addPadding=function(a){for(var b=this.Patch_Margin,c="",d=1;d<=b;d++)c+=String.fromCharCode(d);for(d=0;d<a.length;d++)a[d].start1+=b,a[d].start2+=b;var d=a[0],e=d.diffs;if(0==e.length||0!=e[0][0])e.unshift([0,c]),d.start1-=b,d.start2-=b,d.length1+=b,d.length2+=b;else if(b>e[0][1].length){var f=b-e[0][1].length;e[0][1]=c.substring(e[0][1].length)+e[0][1];d.start1-=f;d.start2-=f;d.length1+=f;d.length2+=f}d=a[a.length-1];e=d.diffs;0==e.length||0!=e[e.length-1][0]?(e.push([0,
|
||||
c]),d.length1+=b,d.length2+=b):b>e[e.length-1][1].length&&(f=b-e[e.length-1][1].length,e[e.length-1][1]+=c.substring(0,f),d.length1+=f,d.length2+=f);return c};
|
||||
diff_match_patch.prototype.patch_splitMax=function(a){for(var b=this.Match_MaxBits,c=0;c<a.length;c++)if(!(a[c].length1<=b)){var d=a[c];a.splice(c--,1);for(var e=d.start1,f=d.start2,g="";0!==d.diffs.length;){var h=new diff_match_patch.patch_obj,j=!0;h.start1=e-g.length;h.start2=f-g.length;""!==g&&(h.length1=h.length2=g.length,h.diffs.push([0,g]));for(;0!==d.diffs.length&&h.length1<b-this.Patch_Margin;){var g=d.diffs[0][0],i=d.diffs[0][1];1===g?(h.length2+=i.length,f+=i.length,h.diffs.push(d.diffs.shift()),
|
||||
j=!1):-1===g&&1==h.diffs.length&&0==h.diffs[0][0]&&i.length>2*b?(h.length1+=i.length,e+=i.length,j=!1,h.diffs.push([g,i]),d.diffs.shift()):(i=i.substring(0,b-h.length1-this.Patch_Margin),h.length1+=i.length,e+=i.length,0===g?(h.length2+=i.length,f+=i.length):j=!1,h.diffs.push([g,i]),i==d.diffs[0][1]?d.diffs.shift():d.diffs[0][1]=d.diffs[0][1].substring(i.length))}g=this.diff_text2(h.diffs);g=g.substring(g.length-this.Patch_Margin);i=this.diff_text1(d.diffs).substring(0,this.Patch_Margin);""!==i&&
|
||||
(h.length1+=i.length,h.length2+=i.length,0!==h.diffs.length&&0===h.diffs[h.diffs.length-1][0]?h.diffs[h.diffs.length-1][1]+=i:h.diffs.push([0,i]));j||a.splice(++c,0,h)}}};diff_match_patch.prototype.patch_toText=function(a){for(var b=[],c=0;c<a.length;c++)b[c]=a[c];return b.join("")};
|
||||
diff_match_patch.prototype.patch_addContext_=function(a,b){if(0!=b.length){if(null===a.start2)throw Error("patch not initialized");for(var c=b.substring(a.start2,a.start2+a.length1),d=0;b.indexOf(c)!=b.lastIndexOf(c)&&c.length<this.Match_MaxBits-this.Patch_Margin-this.Patch_Margin;)d+=this.Patch_Margin,c=b.substring(a.start2-d,a.start2+a.length1+d);d+=this.Patch_Margin;(c=b.substring(a.start2-d,a.start2))&&a.diffs.unshift(new diff_match_patch.Diff(DIFF_EQUAL,c));(d=b.substring(a.start2+a.length1,
|
||||
a.start2+a.length1+d))&&a.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,d));a.start1-=c.length;a.start2-=c.length;a.length1+=c.length+d.length;a.length2+=c.length+d.length}};
|
||||
diff_match_patch.prototype.patch_make=function(a,b,c){if("string"==typeof a&&"string"==typeof b&&"undefined"==typeof c){var d=a;b=this.diff_main(d,b,!0);2<b.length&&(this.diff_cleanupSemantic(b),this.diff_cleanupEfficiency(b))}else if(a&&"object"==typeof a&&"undefined"==typeof b&&"undefined"==typeof c)b=a,d=this.diff_text1(b);else if("string"==typeof a&&b&&"object"==typeof b&&"undefined"==typeof c)d=a;else if("string"==typeof a&&"string"==typeof b&&c&&"object"==typeof c)d=a,b=c;else throw Error("Unknown call format to patch_make.");
|
||||
if(0===b.length)return[];c=[];a=new diff_match_patch.patch_obj;for(var e=0,f=0,g=0,h=d,l=0;l<b.length;l++){var k=b[l][0],m=b[l][1];e||k===DIFF_EQUAL||(a.start1=f,a.start2=g);switch(k){case DIFF_INSERT:a.diffs[e++]=b[l];a.length2+=m.length;d=d.substring(0,g)+m+d.substring(g);break;case DIFF_DELETE:a.length1+=m.length;a.diffs[e++]=b[l];d=d.substring(0,g)+d.substring(g+m.length);break;case DIFF_EQUAL:m.length<=2*this.Patch_Margin&&e&&b.length!=l+1?(a.diffs[e++]=b[l],a.length1+=m.length,a.length2+=m.length):
|
||||
m.length>=2*this.Patch_Margin&&e&&(this.patch_addContext_(a,h),c.push(a),a=new diff_match_patch.patch_obj,e=0,h=d,f=g)}k!==DIFF_INSERT&&(f+=m.length);k!==DIFF_DELETE&&(g+=m.length)}e&&(this.patch_addContext_(a,h),c.push(a));return c};
|
||||
diff_match_patch.prototype.patch_deepCopy=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c],e=new diff_match_patch.patch_obj;e.diffs=[];for(var f=0;f<d.diffs.length;f++)e.diffs[f]=new diff_match_patch.Diff(d.diffs[f][0],d.diffs[f][1]);e.start1=d.start1;e.start2=d.start2;e.length1=d.length1;e.length2=d.length2;b[c]=e}return b};
|
||||
diff_match_patch.prototype.patch_apply=function(a,b){if(0==a.length)return[b,[]];a=this.patch_deepCopy(a);var c=this.patch_addPadding(a);b=c+b+c;this.patch_splitMax(a);for(var d=0,e=[],f=0;f<a.length;f++){var g=a[f].start2+d,h=this.diff_text1(a[f].diffs),l=-1;if(h.length>this.Match_MaxBits){var k=this.match_main(b,h.substring(0,this.Match_MaxBits),g);-1!=k&&(l=this.match_main(b,h.substring(h.length-this.Match_MaxBits),g+h.length-this.Match_MaxBits),-1==l||k>=l)&&(k=-1)}else k=this.match_main(b,h,
|
||||
g);if(-1==k)e[f]=!1,d-=a[f].length2-a[f].length1;else if(e[f]=!0,d=k-g,g=-1==l?b.substring(k,k+h.length):b.substring(k,l+this.Match_MaxBits),h==g)b=b.substring(0,k)+this.diff_text2(a[f].diffs)+b.substring(k+h.length);else if(g=this.diff_main(h,g,!1),h.length>this.Match_MaxBits&&this.diff_levenshtein(g)/h.length>this.Patch_DeleteThreshold)e[f]=!1;else{this.diff_cleanupSemanticLossless(g);h=0;var m;for(l=0;l<a[f].diffs.length;l++){var p=a[f].diffs[l];p[0]!==DIFF_EQUAL&&(m=this.diff_xIndex(g,h));p[0]===
|
||||
DIFF_INSERT?b=b.substring(0,k+m)+p[1]+b.substring(k+m):p[0]===DIFF_DELETE&&(b=b.substring(0,k+m)+b.substring(k+this.diff_xIndex(g,h+p[1].length)));p[0]!==DIFF_DELETE&&(h+=p[1].length)}}}b=b.substring(c.length,b.length-c.length);return[b,e]};
|
||||
diff_match_patch.prototype.patch_addPadding=function(a){for(var b=this.Patch_Margin,c="",d=1;d<=b;d++)c+=String.fromCharCode(d);for(d=0;d<a.length;d++)a[d].start1+=b,a[d].start2+=b;d=a[0];var e=d.diffs;if(0==e.length||e[0][0]!=DIFF_EQUAL)e.unshift(new diff_match_patch.Diff(DIFF_EQUAL,c)),d.start1-=b,d.start2-=b,d.length1+=b,d.length2+=b;else if(b>e[0][1].length){var f=b-e[0][1].length;e[0][1]=c.substring(e[0][1].length)+e[0][1];d.start1-=f;d.start2-=f;d.length1+=f;d.length2+=f}d=a[a.length-1];e=d.diffs;
|
||||
0==e.length||e[e.length-1][0]!=DIFF_EQUAL?(e.push(new diff_match_patch.Diff(DIFF_EQUAL,c)),d.length1+=b,d.length2+=b):b>e[e.length-1][1].length&&(f=b-e[e.length-1][1].length,e[e.length-1][1]+=c.substring(0,f),d.length1+=f,d.length2+=f);return c};
|
||||
diff_match_patch.prototype.patch_splitMax=function(a){for(var b=this.Match_MaxBits,c=0;c<a.length;c++)if(!(a[c].length1<=b)){var d=a[c];a.splice(c--,1);for(var e=d.start1,f=d.start2,g="";0!==d.diffs.length;){var h=new diff_match_patch.patch_obj,l=!0;h.start1=e-g.length;h.start2=f-g.length;""!==g&&(h.length1=h.length2=g.length,h.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,g)));for(;0!==d.diffs.length&&h.length1<b-this.Patch_Margin;){g=d.diffs[0][0];var k=d.diffs[0][1];g===DIFF_INSERT?(h.length2+=
|
||||
k.length,f+=k.length,h.diffs.push(d.diffs.shift()),l=!1):g===DIFF_DELETE&&1==h.diffs.length&&h.diffs[0][0]==DIFF_EQUAL&&k.length>2*b?(h.length1+=k.length,e+=k.length,l=!1,h.diffs.push(new diff_match_patch.Diff(g,k)),d.diffs.shift()):(k=k.substring(0,b-h.length1-this.Patch_Margin),h.length1+=k.length,e+=k.length,g===DIFF_EQUAL?(h.length2+=k.length,f+=k.length):l=!1,h.diffs.push(new diff_match_patch.Diff(g,k)),k==d.diffs[0][1]?d.diffs.shift():d.diffs[0][1]=d.diffs[0][1].substring(k.length))}g=this.diff_text2(h.diffs);
|
||||
g=g.substring(g.length-this.Patch_Margin);k=this.diff_text1(d.diffs).substring(0,this.Patch_Margin);""!==k&&(h.length1+=k.length,h.length2+=k.length,0!==h.diffs.length&&h.diffs[h.diffs.length-1][0]===DIFF_EQUAL?h.diffs[h.diffs.length-1][1]+=k:h.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,k)));l||a.splice(++c,0,h)}}};diff_match_patch.prototype.patch_toText=function(a){for(var b=[],c=0;c<a.length;c++)b[c]=a[c];return b.join("")};
|
||||
diff_match_patch.prototype.patch_fromText=function(a){var b=[];if(!a)return b;a=a.split("\n");for(var c=0,d=/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/;c<a.length;){var e=a[c].match(d);if(!e)throw Error("Invalid patch string: "+a[c]);var f=new diff_match_patch.patch_obj;b.push(f);f.start1=parseInt(e[1],10);""===e[2]?(f.start1--,f.length1=1):"0"==e[2]?f.length1=0:(f.start1--,f.length1=parseInt(e[2],10));f.start2=parseInt(e[3],10);""===e[4]?(f.start2--,f.length2=1):"0"==e[4]?f.length2=0:(f.start2--,f.length2=
|
||||
parseInt(e[4],10));for(c++;c<a.length;){e=a[c].charAt(0);try{var g=decodeURI(a[c].substring(1))}catch(h){throw Error("Illegal escape in patch_fromText: "+g);}if("-"==e)f.diffs.push([-1,g]);else if("+"==e)f.diffs.push([1,g]);else if(" "==e)f.diffs.push([0,g]);else if("@"==e)break;else if(""!==e)throw Error('Invalid patch mode "'+e+'" in: '+g);c++}}return b};diff_match_patch.patch_obj=function(){this.diffs=[];this.start2=this.start1=null;this.length2=this.length1=0};
|
||||
diff_match_patch.patch_obj.prototype.toString=function(){var a,b;a=0===this.length1?this.start1+",0":1==this.length1?this.start1+1:this.start1+1+","+this.length1;b=0===this.length2?this.start2+",0":1==this.length2?this.start2+1:this.start2+1+","+this.length2;a=["@@ -"+a+" +"+b+" @@\n"];var c;for(b=0;b<this.diffs.length;b++){switch(this.diffs[b][0]){case 1:c="+";break;case -1:c="-";break;case 0:c=" "}a[b+1]=c+encodeURI(this.diffs[b][1])+"\n"}return a.join("").replace(/%20/g," ")};
|
||||
this.diff_match_patch=diff_match_patch;this.DIFF_DELETE=-1;this.DIFF_INSERT=1;this.DIFF_EQUAL=0;})()
|
||||
parseInt(e[4],10));for(c++;c<a.length;){e=a[c].charAt(0);try{var g=decodeURI(a[c].substring(1))}catch(h){throw Error("Illegal escape in patch_fromText: "+g);}if("-"==e)f.diffs.push(new diff_match_patch.Diff(DIFF_DELETE,g));else if("+"==e)f.diffs.push(new diff_match_patch.Diff(DIFF_INSERT,g));else if(" "==e)f.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,g));else if("@"==e)break;else if(""!==e)throw Error('Invalid patch mode "'+e+'" in: '+g);c++}}return b};
|
||||
diff_match_patch.patch_obj=function(){this.diffs=[];this.start2=this.start1=null;this.length2=this.length1=0};
|
||||
diff_match_patch.patch_obj.prototype.toString=function(){for(var a=["@@ -"+(0===this.length1?this.start1+",0":1==this.length1?this.start1+1:this.start1+1+","+this.length1)+" +"+(0===this.length2?this.start2+",0":1==this.length2?this.start2+1:this.start2+1+","+this.length2)+" @@\n"],b,c=0;c<this.diffs.length;c++){switch(this.diffs[c][0]){case DIFF_INSERT:b="+";break;case DIFF_DELETE:b="-";break;case DIFF_EQUAL:b=" "}a[c+1]=b+encodeURI(this.diffs[c][1])+"\n"}return a.join("").replace(/%20/g," ")};
|
||||
this.diff_match_patch=diff_match_patch;this.DIFF_DELETE=DIFF_DELETE;this.DIFF_INSERT=DIFF_INSERT;this.DIFF_EQUAL=DIFF_EQUAL;
|
||||
@@ -166,7 +166,7 @@
|
||||
function copyObj(obj, target, overwrite) {
|
||||
if (!target) { target = {}; }
|
||||
for (var prop in obj)
|
||||
{ if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop)))
|
||||
{ if (Object.prototype.hasOwnProperty.call(obj, prop) && (overwrite !== false || !Object.prototype.hasOwnProperty.call(target, prop)))
|
||||
{ target[prop] = obj[prop]; } }
|
||||
return target
|
||||
}
|
||||
@@ -4426,7 +4426,7 @@
|
||||
// The element in which the editor lives.
|
||||
d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
|
||||
// See #6982. FIXME remove when this has been fixed for a while in Chrome
|
||||
if (chrome && chrome_version >= 105) { d.wrapper.style.clipPath = "inset(0px)"; }
|
||||
if (chrome && chrome_version === 105) { d.wrapper.style.clipPath = "inset(0px)"; }
|
||||
|
||||
// This attribute is respected by automatic translation systems such as Google Translate,
|
||||
// and may also be respected by tools used by human translators.
|
||||
@@ -9877,7 +9877,7 @@
|
||||
|
||||
addLegacyProps(CodeMirror);
|
||||
|
||||
CodeMirror.version = "5.65.18";
|
||||
CodeMirror.version = "5.65.21";
|
||||
|
||||
return CodeMirror;
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
INSTALL = install
|
||||
PREFIX = /usr/bin
|
||||
|
||||
po2lmo: src/po2lmo.o src/template_lmo.o
|
||||
$(CC) $(LDFLAGS) -o src/po2lmo src/po2lmo.o src/template_lmo.o
|
||||
|
||||
install:
|
||||
$(INSTALL) -m 755 src/po2lmo $(PREFIX)
|
||||
|
||||
clean:
|
||||
$(RM) src/po2lmo src/*.o
|
||||
Binary file not shown.
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* lmo - Lua Machine Objects - PO to LMO conversion tool
|
||||
*
|
||||
* Copyright (C) 2009-2012 Jo-Philipp Wich <xm@subsignal.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "template_lmo.h"
|
||||
|
||||
static void die(const char *msg)
|
||||
{
|
||||
fprintf(stderr, "Error: %s\n", msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void usage(const char *name)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s input.po output.lmo\n", name);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void print(const void *ptr, size_t size, size_t nmemb, FILE *stream)
|
||||
{
|
||||
if( fwrite(ptr, size, nmemb, stream) == 0 )
|
||||
die("Failed to write stdout");
|
||||
}
|
||||
|
||||
static int extract_string(const char *src, char *dest, int len)
|
||||
{
|
||||
int pos = 0;
|
||||
int esc = 0;
|
||||
int off = -1;
|
||||
|
||||
for( pos = 0; (pos < strlen(src)) && (pos < len); pos++ )
|
||||
{
|
||||
if( (off == -1) && (src[pos] == '"') )
|
||||
{
|
||||
off = pos + 1;
|
||||
}
|
||||
else if( off >= 0 )
|
||||
{
|
||||
if( esc == 1 )
|
||||
{
|
||||
switch (src[pos])
|
||||
{
|
||||
case '"':
|
||||
case '\\':
|
||||
off++;
|
||||
break;
|
||||
}
|
||||
dest[pos-off] = src[pos];
|
||||
esc = 0;
|
||||
}
|
||||
else if( src[pos] == '\\' )
|
||||
{
|
||||
dest[pos-off] = src[pos];
|
||||
esc = 1;
|
||||
}
|
||||
else if( src[pos] != '"' )
|
||||
{
|
||||
dest[pos-off] = src[pos];
|
||||
}
|
||||
else
|
||||
{
|
||||
dest[pos-off] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (off > -1) ? strlen(dest) : -1;
|
||||
}
|
||||
|
||||
static int cmp_index(const void *a, const void *b)
|
||||
{
|
||||
uint32_t x = ((const lmo_entry_t *)a)->key_id;
|
||||
uint32_t y = ((const lmo_entry_t *)b)->key_id;
|
||||
|
||||
if (x < y)
|
||||
return -1;
|
||||
else if (x > y)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void print_uint32(uint32_t x, FILE *out)
|
||||
{
|
||||
uint32_t y = htonl(x);
|
||||
print(&y, sizeof(uint32_t), 1, out);
|
||||
}
|
||||
|
||||
static void print_index(void *array, int n, FILE *out)
|
||||
{
|
||||
lmo_entry_t *e;
|
||||
|
||||
qsort(array, n, sizeof(*e), cmp_index);
|
||||
|
||||
for (e = array; n > 0; n--, e++)
|
||||
{
|
||||
print_uint32(e->key_id, out);
|
||||
print_uint32(e->val_id, out);
|
||||
print_uint32(e->offset, out);
|
||||
print_uint32(e->length, out);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char line[4096];
|
||||
char key[4096];
|
||||
char val[4096];
|
||||
char tmp[4096];
|
||||
int state = 0;
|
||||
int offset = 0;
|
||||
int length = 0;
|
||||
int n_entries = 0;
|
||||
void *array = NULL;
|
||||
lmo_entry_t *entry = NULL;
|
||||
uint32_t key_id, val_id;
|
||||
|
||||
FILE *in;
|
||||
FILE *out;
|
||||
|
||||
if( (argc != 3) || ((in = fopen(argv[1], "r")) == NULL) || ((out = fopen(argv[2], "w")) == NULL) )
|
||||
usage(argv[0]);
|
||||
|
||||
memset(line, 0, sizeof(key));
|
||||
memset(key, 0, sizeof(val));
|
||||
memset(val, 0, sizeof(val));
|
||||
|
||||
while( (NULL != fgets(line, sizeof(line), in)) || (state >= 2 && feof(in)) )
|
||||
{
|
||||
if( state == 0 && strstr(line, "msgid \"") == line )
|
||||
{
|
||||
switch(extract_string(line, key, sizeof(key)))
|
||||
{
|
||||
case -1:
|
||||
die("Syntax error in msgid");
|
||||
case 0:
|
||||
state = 1;
|
||||
break;
|
||||
default:
|
||||
state = 2;
|
||||
}
|
||||
}
|
||||
else if( state == 1 || state == 2 )
|
||||
{
|
||||
if( strstr(line, "msgstr \"") == line || state == 2 )
|
||||
{
|
||||
switch(extract_string(line, val, sizeof(val)))
|
||||
{
|
||||
case -1:
|
||||
state = 4;
|
||||
break;
|
||||
default:
|
||||
state = 3;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(extract_string(line, tmp, sizeof(tmp)))
|
||||
{
|
||||
case -1:
|
||||
state = 2;
|
||||
break;
|
||||
default:
|
||||
strcat(key, tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( state == 3 )
|
||||
{
|
||||
switch(extract_string(line, tmp, sizeof(tmp)))
|
||||
{
|
||||
case -1:
|
||||
state = 4;
|
||||
break;
|
||||
default:
|
||||
strcat(val, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
if( state == 4 )
|
||||
{
|
||||
if( strlen(key) > 0 && strlen(val) > 0 )
|
||||
{
|
||||
key_id = sfh_hash(key, strlen(key));
|
||||
val_id = sfh_hash(val, strlen(val));
|
||||
|
||||
if( key_id != val_id )
|
||||
{
|
||||
n_entries++;
|
||||
array = realloc(array, n_entries * sizeof(lmo_entry_t));
|
||||
entry = (lmo_entry_t *)array + n_entries - 1;
|
||||
|
||||
if (!array)
|
||||
die("Out of memory");
|
||||
|
||||
entry->key_id = key_id;
|
||||
entry->val_id = val_id;
|
||||
entry->offset = offset;
|
||||
entry->length = strlen(val);
|
||||
|
||||
length = strlen(val) + ((4 - (strlen(val) % 4)) % 4);
|
||||
|
||||
print(val, length, 1, out);
|
||||
offset += length;
|
||||
}
|
||||
}
|
||||
|
||||
state = 0;
|
||||
memset(key, 0, sizeof(key));
|
||||
memset(val, 0, sizeof(val));
|
||||
}
|
||||
|
||||
memset(line, 0, sizeof(line));
|
||||
}
|
||||
|
||||
print_index(array, n_entries, out);
|
||||
|
||||
if( offset > 0 )
|
||||
{
|
||||
print_uint32(offset, out);
|
||||
fsync(fileno(out));
|
||||
fclose(out);
|
||||
}
|
||||
else
|
||||
{
|
||||
fclose(out);
|
||||
unlink(argv[2]);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
return(0);
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
* lmo - Lua Machine Objects - Base functions
|
||||
*
|
||||
* Copyright (C) 2009-2010 Jo-Philipp Wich <xm@subsignal.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "template_lmo.h"
|
||||
|
||||
/*
|
||||
* Hash function from http://www.azillionmonkeys.com/qed/hash.html
|
||||
* Copyright (C) 2004-2008 by Paul Hsieh
|
||||
*/
|
||||
|
||||
uint32_t sfh_hash(const char *data, int len)
|
||||
{
|
||||
uint32_t hash = len, tmp;
|
||||
int rem;
|
||||
|
||||
if (len <= 0 || data == NULL) return 0;
|
||||
|
||||
rem = len & 3;
|
||||
len >>= 2;
|
||||
|
||||
/* Main loop */
|
||||
for (;len > 0; len--) {
|
||||
hash += sfh_get16(data);
|
||||
tmp = (sfh_get16(data+2) << 11) ^ hash;
|
||||
hash = (hash << 16) ^ tmp;
|
||||
data += 2*sizeof(uint16_t);
|
||||
hash += hash >> 11;
|
||||
}
|
||||
|
||||
/* Handle end cases */
|
||||
switch (rem) {
|
||||
case 3: hash += sfh_get16(data);
|
||||
hash ^= hash << 16;
|
||||
hash ^= data[sizeof(uint16_t)] << 18;
|
||||
hash += hash >> 11;
|
||||
break;
|
||||
case 2: hash += sfh_get16(data);
|
||||
hash ^= hash << 11;
|
||||
hash += hash >> 17;
|
||||
break;
|
||||
case 1: hash += *data;
|
||||
hash ^= hash << 10;
|
||||
hash += hash >> 1;
|
||||
}
|
||||
|
||||
/* Force "avalanching" of final 127 bits */
|
||||
hash ^= hash << 3;
|
||||
hash += hash >> 5;
|
||||
hash ^= hash << 4;
|
||||
hash += hash >> 17;
|
||||
hash ^= hash << 25;
|
||||
hash += hash >> 6;
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint32_t lmo_canon_hash(const char *str, int len)
|
||||
{
|
||||
char res[4096];
|
||||
char *ptr, prev;
|
||||
int off;
|
||||
|
||||
if (!str || len >= sizeof(res))
|
||||
return 0;
|
||||
|
||||
for (prev = ' ', ptr = res, off = 0; off < len; prev = *str, off++, str++)
|
||||
{
|
||||
if (isspace(*str))
|
||||
{
|
||||
if (!isspace(prev))
|
||||
*ptr++ = ' ';
|
||||
}
|
||||
else
|
||||
{
|
||||
*ptr++ = *str;
|
||||
}
|
||||
}
|
||||
|
||||
if ((ptr > res) && isspace(*(ptr-1)))
|
||||
ptr--;
|
||||
|
||||
return sfh_hash(res, ptr - res);
|
||||
}
|
||||
|
||||
lmo_archive_t * lmo_open(const char *file)
|
||||
{
|
||||
int in = -1;
|
||||
uint32_t idx_offset = 0;
|
||||
struct stat s;
|
||||
|
||||
lmo_archive_t *ar = NULL;
|
||||
|
||||
if (stat(file, &s) == -1)
|
||||
goto err;
|
||||
|
||||
if ((in = open(file, O_RDONLY)) == -1)
|
||||
goto err;
|
||||
|
||||
if ((ar = (lmo_archive_t *)malloc(sizeof(*ar))) != NULL)
|
||||
{
|
||||
memset(ar, 0, sizeof(*ar));
|
||||
|
||||
ar->fd = in;
|
||||
ar->size = s.st_size;
|
||||
|
||||
fcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC);
|
||||
|
||||
if ((ar->mmap = mmap(NULL, ar->size, PROT_READ, MAP_SHARED, ar->fd, 0)) == MAP_FAILED)
|
||||
goto err;
|
||||
|
||||
idx_offset = ntohl(*((const uint32_t *)
|
||||
(ar->mmap + ar->size - sizeof(uint32_t))));
|
||||
|
||||
if (idx_offset >= ar->size)
|
||||
goto err;
|
||||
|
||||
ar->index = (lmo_entry_t *)(ar->mmap + idx_offset);
|
||||
ar->length = (ar->size - idx_offset - sizeof(uint32_t)) / sizeof(lmo_entry_t);
|
||||
ar->end = ar->mmap + ar->size;
|
||||
|
||||
return ar;
|
||||
}
|
||||
|
||||
err:
|
||||
if (in > -1)
|
||||
close(in);
|
||||
|
||||
if (ar != NULL)
|
||||
{
|
||||
if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
|
||||
munmap(ar->mmap, ar->size);
|
||||
|
||||
free(ar);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void lmo_close(lmo_archive_t *ar)
|
||||
{
|
||||
if (ar != NULL)
|
||||
{
|
||||
if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
|
||||
munmap(ar->mmap, ar->size);
|
||||
|
||||
close(ar->fd);
|
||||
free(ar);
|
||||
|
||||
ar = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lmo_catalog_t *_lmo_catalogs = NULL;
|
||||
lmo_catalog_t *_lmo_active_catalog = NULL;
|
||||
|
||||
int lmo_load_catalog(const char *lang, const char *dir)
|
||||
{
|
||||
DIR *dh = NULL;
|
||||
char pattern[16];
|
||||
char path[PATH_MAX];
|
||||
struct dirent *de = NULL;
|
||||
|
||||
lmo_archive_t *ar = NULL;
|
||||
lmo_catalog_t *cat = NULL;
|
||||
|
||||
if (!lmo_change_catalog(lang))
|
||||
return 0;
|
||||
|
||||
if (!dir || !(dh = opendir(dir)))
|
||||
goto err;
|
||||
|
||||
if (!(cat = malloc(sizeof(*cat))))
|
||||
goto err;
|
||||
|
||||
memset(cat, 0, sizeof(*cat));
|
||||
|
||||
snprintf(cat->lang, sizeof(cat->lang), "%s", lang);
|
||||
snprintf(pattern, sizeof(pattern), "*.%s.lmo", lang);
|
||||
|
||||
while ((de = readdir(dh)) != NULL)
|
||||
{
|
||||
if (!fnmatch(pattern, de->d_name, 0))
|
||||
{
|
||||
snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
|
||||
ar = lmo_open(path);
|
||||
|
||||
if (ar)
|
||||
{
|
||||
ar->next = cat->archives;
|
||||
cat->archives = ar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dh);
|
||||
|
||||
cat->next = _lmo_catalogs;
|
||||
_lmo_catalogs = cat;
|
||||
|
||||
if (!_lmo_active_catalog)
|
||||
_lmo_active_catalog = cat;
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
if (dh) closedir(dh);
|
||||
if (cat) free(cat);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int lmo_change_catalog(const char *lang)
|
||||
{
|
||||
lmo_catalog_t *cat;
|
||||
|
||||
for (cat = _lmo_catalogs; cat; cat = cat->next)
|
||||
{
|
||||
if (!strncmp(cat->lang, lang, sizeof(cat->lang)))
|
||||
{
|
||||
_lmo_active_catalog = cat;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static lmo_entry_t * lmo_find_entry(lmo_archive_t *ar, uint32_t hash)
|
||||
{
|
||||
unsigned int m, l, r;
|
||||
uint32_t k;
|
||||
|
||||
l = 0;
|
||||
r = ar->length - 1;
|
||||
|
||||
while (1)
|
||||
{
|
||||
m = l + ((r - l) / 2);
|
||||
|
||||
if (r < l)
|
||||
break;
|
||||
|
||||
k = ntohl(ar->index[m].key_id);
|
||||
|
||||
if (k == hash)
|
||||
return &ar->index[m];
|
||||
|
||||
if (k > hash)
|
||||
{
|
||||
if (!m)
|
||||
break;
|
||||
|
||||
r = m - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
l = m + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int lmo_translate(const char *key, int keylen, char **out, int *outlen)
|
||||
{
|
||||
uint32_t hash;
|
||||
lmo_entry_t *e;
|
||||
lmo_archive_t *ar;
|
||||
|
||||
if (!key || !_lmo_active_catalog)
|
||||
return -2;
|
||||
|
||||
hash = lmo_canon_hash(key, keylen);
|
||||
|
||||
for (ar = _lmo_active_catalog->archives; ar; ar = ar->next)
|
||||
{
|
||||
if ((e = lmo_find_entry(ar, hash)) != NULL)
|
||||
{
|
||||
*out = ar->mmap + ntohl(e->offset);
|
||||
*outlen = ntohl(e->length);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void lmo_close_catalog(const char *lang)
|
||||
{
|
||||
lmo_archive_t *ar, *next;
|
||||
lmo_catalog_t *cat, *prev;
|
||||
|
||||
for (prev = NULL, cat = _lmo_catalogs; cat; prev = cat, cat = cat->next)
|
||||
{
|
||||
if (!strncmp(cat->lang, lang, sizeof(cat->lang)))
|
||||
{
|
||||
if (prev)
|
||||
prev->next = cat->next;
|
||||
else
|
||||
_lmo_catalogs = cat->next;
|
||||
|
||||
for (ar = cat->archives; ar; ar = next)
|
||||
{
|
||||
next = ar->next;
|
||||
lmo_close(ar);
|
||||
}
|
||||
|
||||
free(cat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* lmo - Lua Machine Objects - General header
|
||||
*
|
||||
* Copyright (C) 2009-2012 Jo-Philipp Wich <xm@subsignal.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _TEMPLATE_LMO_H_
|
||||
#define _TEMPLATE_LMO_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <fnmatch.h>
|
||||
#include <dirent.h>
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
|
||||
#if (defined(__GNUC__) && defined(__i386__))
|
||||
#define sfh_get16(d) (*((const uint16_t *) (d)))
|
||||
#else
|
||||
#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\
|
||||
+(uint32_t)(((const uint8_t *)(d))[0]) )
|
||||
#endif
|
||||
|
||||
|
||||
struct lmo_entry {
|
||||
uint32_t key_id;
|
||||
uint32_t val_id;
|
||||
uint32_t offset;
|
||||
uint32_t length;
|
||||
} __attribute__((packed));
|
||||
|
||||
typedef struct lmo_entry lmo_entry_t;
|
||||
|
||||
|
||||
struct lmo_archive {
|
||||
int fd;
|
||||
int length;
|
||||
uint32_t size;
|
||||
lmo_entry_t *index;
|
||||
char *mmap;
|
||||
char *end;
|
||||
struct lmo_archive *next;
|
||||
};
|
||||
|
||||
typedef struct lmo_archive lmo_archive_t;
|
||||
|
||||
|
||||
struct lmo_catalog {
|
||||
char lang[6];
|
||||
struct lmo_archive *archives;
|
||||
struct lmo_catalog *next;
|
||||
};
|
||||
|
||||
typedef struct lmo_catalog lmo_catalog_t;
|
||||
|
||||
|
||||
uint32_t sfh_hash(const char *data, int len);
|
||||
uint32_t lmo_canon_hash(const char *data, int len);
|
||||
|
||||
lmo_archive_t * lmo_open(const char *file);
|
||||
void lmo_close(lmo_archive_t *ar);
|
||||
|
||||
|
||||
extern lmo_catalog_t *_lmo_catalogs;
|
||||
extern lmo_catalog_t *_lmo_active_catalog;
|
||||
|
||||
int lmo_load_catalog(const char *lang, const char *dir);
|
||||
int lmo_change_catalog(const char *lang);
|
||||
int lmo_translate(const char *key, int keylen, char **out, int *outlen);
|
||||
void lmo_close_catalog(const char *lang);
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user