diff --git a/.github/update.log b/.github/update.log
index 8ba1cde732..60e0691b11 100644
--- a/.github/update.log
+++ b/.github/update.log
@@ -894,3 +894,4 @@ Update On Wed Jan 22 19:34:22 CET 2025
Update On Thu Jan 23 19:35:00 CET 2025
Update On Fri Jan 24 19:33:49 CET 2025
Update On Sat Jan 25 19:31:08 CET 2025
+Update On Sun Jan 26 19:30:14 CET 2025
diff --git a/brook/ping/ping.json b/brook/ping/ping.json
index c27dcb3aa9..22fcc96f7f 100644
--- a/brook/ping/ping.json
+++ b/brook/ping/ping.json
@@ -2,6 +2,6 @@
"version": "20250202",
"text": "Zhi - A Zero-Trust End-to-End Encrypted Instant Messaging App",
"link": "https://www.txthinking.com/zhi.html",
- "text_zh": "纸,一个零信任端到端加密的聊天应用",
- "link_zh": "https://www.txthinking.com/zhi.html"
+ "text_zh": "生成自用的中国域名直连模块",
+ "link_zh": "https://www.txthinking.com/talks/articles/china-list.article"
}
diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/controller/passwall2.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/controller/passwall2.lua
index efcb3bd569..ab80723227 100644
--- a/openwrt-passwall2/luci-app-passwall2/luasrc/controller/passwall2.lua
+++ b/openwrt-passwall2/luci-app-passwall2/luasrc/controller/passwall2.lua
@@ -7,6 +7,7 @@ local uci = api.uci -- in funtion index()
local http = require "luci.http"
local util = require "luci.util"
local i18n = require "luci.i18n"
+local fs = api.fs
function index()
if not nixio.fs.access("/etc/config/passwall2") then
@@ -45,7 +46,7 @@ function index()
entry({"admin", "services", appname, "socks_config"}, cbi(appname .. "/client/socks_config")).leaf = true
entry({"admin", "services", appname, "acl"}, cbi(appname .. "/client/acl"), _("Access control"), 98).leaf = true
entry({"admin", "services", appname, "acl_config"}, cbi(appname .. "/client/acl_config")).leaf = true
- entry({"admin", "services", appname, "log"}, form(appname .. "/client/log"), _("Watch Logs"), 999).leaf = true
+ entry({"admin", "services", appname, "log"}, form(appname .. "/client/log"), _("Log Maint"), 999).leaf = true
--[[ Server ]]
entry({"admin", "services", appname, "server"}, cbi(appname .. "/server/index"), _("Server-Side"), 99).leaf = true
@@ -84,6 +85,9 @@ function index()
entry({"admin", "services", appname, "check_" .. com}, call("com_check", com)).leaf = true
entry({"admin", "services", appname, "update_" .. com}, call("com_update", com)).leaf = true
end
+
+ --[[Backup]]
+ entry({"admin", "services", appname, "backup"}, call("create_backup")).leaf = true
end
local function http_write_json(content)
@@ -437,4 +441,21 @@ function com_update(comname)
http_write_json(json)
end
+function create_backup()
+ local backup_files = {
+ "/etc/config/passwall2",
+ "/etc/config/passwall2_server",
+ "/usr/share/passwall2/domains_excluded"
+ }
+ local date = os.date("%y%m%d%H%M")
+ local tar_file = "/tmp/passwall2-" .. date .. "-backup.tar.gz"
+ fs.remove(tar_file)
+ local cmd = "tar -czf " .. tar_file .. " " .. table.concat(backup_files, " ")
+ api.sys.call(cmd)
+ http.header("Content-Disposition", "attachment; filename=passwall2-" .. date .. "-backup.tar.gz")
+ http.header("X-Backup-Filename", "passwall2-" .. date .. "-backup.tar.gz")
+ http.prepare_content("application/octet-stream")
+ http.write(fs.readfile(tar_file))
+ fs.remove(tar_file)
+end
diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl_config.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl_config.lua
index b4064555f5..c850fdba5f 100644
--- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl_config.lua
+++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl_config.lua
@@ -1,14 +1,19 @@
local api = require "luci.passwall2.api"
local appname = api.appname
+
+m = Map(appname)
+api.set_apply_on_parse(m)
+
+if not arg[1] or not m:get(arg[1]) then
+ luci.http.redirect(api.url("acl"))
+end
+
local sys = api.sys
local port_validate = function(self, value, t)
return value:gsub("-", ":")
end
-m = Map(appname)
-api.set_apply_on_parse(m)
-
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = e
diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/haproxy.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/haproxy.lua
index b0c8b04ee7..584d7b2bc7 100644
--- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/haproxy.lua
+++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/haproxy.lua
@@ -28,16 +28,21 @@ o = s:option(Flag, "balancing_enable", translate("Enable Load Balancing"))
o.rmempty = false
o.default = false
+---- Console Login Auth
+o = s:option(Flag, "console_auth", translate("Console Login Auth"))
+o.default = false
+o:depends("balancing_enable", true)
+
---- Console Username
o = s:option(Value, "console_user", translate("Console Username"))
o.default = ""
-o:depends("balancing_enable", true)
+o:depends("console_auth", true)
---- Console Password
o = s:option(Value, "console_password", translate("Console Password"))
o.password = true
o.default = ""
-o:depends("balancing_enable", true)
+o:depends("console_auth", true)
---- Console Port
o = s:option(Value, "console_port", translate("Console Port"), translate(
diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/log.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/log.lua
index ff7e74fa19..384cf1b315 100644
--- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/log.lua
+++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/log.lua
@@ -1,8 +1,70 @@
local api = require "luci.passwall2.api"
-local appname = api.appname
+local appname = "passwall2"
+local http = require "luci.http"
+local fs = api.fs
+local sys = api.sys
f = SimpleForm(appname)
f.reset = false
f.submit = false
f:append(Template(appname .. "/log/log"))
-return f
+
+fb = SimpleForm('backup-restore')
+fb.reset = false
+fb.submit = false
+s = fb:section(SimpleSection, translate("Backup and Restore"), translate("Backup or Restore Client and Server Configurations.") ..
+ "
" ..
+ translate("Note: Restoring configurations across different versions may cause compatibility issues.") ..
+ "")
+
+s.anonymous = true
+s:append(Template(appname .. "/log/backup_restore"))
+
+local backup_files = {
+ "/etc/config/passwall2",
+ "/etc/config/passwall2_server",
+ "/usr/share/passwall2/domains_excluded"
+}
+
+local file_path = '/tmp/passwall2_upload.tar.gz'
+local temp_dir = '/tmp/passwall2_bak'
+local fd
+http.setfilehandler(function(meta, chunk, eof)
+ if not fd and meta and meta.name == "ulfile" and chunk then
+ sys.call("rm -rf " .. temp_dir)
+ fs.remove(file_path)
+ fd = nixio.open(file_path, "w")
+ sys.call("echo '' > /tmp/log/passwall2.log")
+ end
+ if fd and chunk then
+ fd:write(chunk)
+ end
+ if eof and fd then
+ fd:close()
+ fd = nil
+ if fs.access(file_path) then
+ api.log(" * PassWall2 配置文件上传成功…")
+ sys.call("mkdir -p " .. temp_dir)
+ if sys.call("tar -xzf " .. file_path .. " -C " .. temp_dir) == 0 then
+ for _, backup_file in ipairs(backup_files) do
+ local temp_file = temp_dir .. backup_file
+ if fs.access(temp_file) then
+ sys.call("cp -f " .. temp_file .. " " .. backup_file)
+ end
+ end
+ api.log(" * PassWall2 配置还原成功…")
+ api.log(" * 重启 PassWall2 服务中…\n")
+ sys.call('/etc/init.d/passwall2 restart > /dev/null 2>&1 &')
+ sys.call('/etc/init.d/passwall2_server restart > /dev/null 2>&1 &')
+ else
+ api.log(" * PassWall2 配置文件解压失败,请重试!")
+ end
+ else
+ api.log(" * PassWall2 配置文件上传失败,请重试!")
+ end
+ sys.call("rm -rf " .. temp_dir)
+ fs.remove(file_path)
+ end
+end)
+
+return f, fb
diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua
index e561c2cea1..e480dacc5d 100644
--- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua
+++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua
@@ -1,5 +1,6 @@
local api = require "luci.passwall2.api"
local appname = api.appname
+local uci = api.uci
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
local has_singbox = api.finded_com("singbox")
@@ -41,10 +42,28 @@ end
m = Map(appname)
api.set_apply_on_parse(m)
+if api.is_js_luci() then
+ m.on_after_apply = function(self)
+ uci:foreach(appname, "subscribe_list", function(e)
+ uci:delete(appname, e[".name"], "md5")
+ end)
+ uci:commit(appname)
+ end
+end
+
-- [[ Subscribe Settings ]]--
s = m:section(TypedSection, "global_subscribe", "")
s.anonymous = true
+function m.commit_handler(self)
+ if self.no_commit then
+ return
+ end
+ self.uci:foreach(appname, "subscribe_list", function(e)
+ self:del(e[".name"], "md5")
+ end)
+end
+
o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode"))
o:value("0", translate("Close"))
o:value("1", translate("Discard List"))
@@ -112,13 +131,15 @@ o:value("ipv6_only", translate("IPv6 Only"))
o = s:option(Button, "_stop", translate("Delete All Subscribe Node"))
o.inputstyle = "remove"
function o.write(e, e)
- luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate > /dev/null 2>&1")
+ luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate all-node > /dev/null 2>&1")
+ m.no_commit = true
end
o = s:option(Button, "_update", translate("Manual subscription All"))
o.inputstyle = "apply"
function o.write(t, n)
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start > /dev/null 2>&1 &")
+ m.no_commit = true
luci.http.redirect(api.url("log"))
end
@@ -151,17 +172,23 @@ o.validate = function(self, value, t)
end
end
-o = s:option(DummyValue, "_node_count")
+o = s:option(DummyValue, "_node_count", translate("Subscribe Info"))
o.rawhtml = true
o.cfgvalue = function(t, n)
local remark = m:get(n, "remark") or ""
+ local str = m:get(n, "rem_traffic") or ""
+ local expired_date = m:get(n, "expired_date") or ""
+ if expired_date ~= "" then
+ str = str .. (str ~= "" and "/" or "") .. expired_date
+ end
+ str = str ~= "" and "
" .. str or ""
local num = 0
m.uci:foreach(appname, "nodes", function(s)
if s["add_from"] ~= "" and s["add_from"] == remark then
num = num + 1
end
end)
- return string.format("%s", remark .. " " .. translate("Node num") .. ": " .. num, num)
+ return string.format("%s%s", translate("Node num") .. ": " .. num, str)
end
o = s:option(Value, "url", translate("Subscribe URL"))
@@ -173,12 +200,14 @@ o.inputstyle = "remove"
function o.write(t, n)
local remark = m:get(n, "remark") or ""
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate " .. remark .. " > /dev/null 2>&1")
+ m.no_commit = true
end
o = s:option(Button, "_update", translate("Manual subscription"))
o.inputstyle = "apply"
function o.write(t, n)
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start " .. n .. " > /dev/null 2>&1 &")
+ m.no_commit = true
luci.http.redirect(api.url("log"))
end
diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua
index 64d37e2897..a2b17111b8 100644
--- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua
+++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua
@@ -1,5 +1,14 @@
local api = require "luci.passwall2.api"
local appname = api.appname
+
+m = Map(appname)
+m.redirect = api.url("node_subscribe")
+api.set_apply_on_parse(m)
+
+if not arg[1] or not m:get(arg[1]) then
+ luci.http.redirect(m.redirect)
+end
+
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
local has_singbox = api.finded_com("singbox")
@@ -38,14 +47,14 @@ if has_hysteria2 then
table.insert(hysteria2_type, s)
end
-m = Map(appname)
-m.redirect = api.url("node_subscribe")
-api.set_apply_on_parse(m)
-
s = m:section(NamedSection, arg[1])
s.addremove = false
s.dynamic = false
+function m.commit_handler(self)
+ self:del(arg[1], "md5")
+end
+
o = s:option(Value, "remark", translate("Subscribe Remark"))
o.rmempty = false
diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua
index 2f66f7616c..6fbeb069f9 100644
--- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua
+++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua
@@ -111,13 +111,16 @@ if (os.execute("lsmod | grep -i REDIRECT >/dev/null") == 0 and os.execute("lsmod
o:value("redirect", "REDIRECT")
o:value("tproxy", "TPROXY")
o:depends("ipv6_tproxy", false)
+ o.remove = function(self, section)
+ -- 禁止在隐藏时删除
+ end
o = s:option(ListValue, "_tcp_proxy_way", translate("TCP Proxy Way"))
o.default = "tproxy"
o:value("tproxy", "TPROXY")
o:depends("ipv6_tproxy", true)
o.write = function(self, section, value)
- return self.map:set(section, "tcp_proxy_way", value)
+ self.map:set(section, "tcp_proxy_way", value)
end
if os.execute("lsmod | grep -i ip6table_mangle >/dev/null") == 0 or os.execute("lsmod | grep -i nft_tproxy >/dev/null") == 0 then
@@ -210,6 +213,7 @@ if has_xray then
o = s_xray_noise:option(ListValue, "type", translate("Type"))
o:value("rand", "rand")
o:value("str", "str")
+ o:value("hex", "hex")
o:value("base64", "base64")
o = s_xray_noise:option(Value, "packet", translate("Packet"))
diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua
index fa87ec0452..c970d0f106 100644
--- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua
+++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua
@@ -1,11 +1,16 @@
local api = require "luci.passwall2.api"
local appname = api.appname
-local has_singbox = api.finded_com("singbox")
-local has_xray = api.finded_com("xray")
m = Map(appname)
api.set_apply_on_parse(m)
+if not arg[1] or not m:get(arg[1]) then
+ luci.http.redirect(api.url())
+end
+
+local has_singbox = api.finded_com("singbox")
+local has_xray = api.finded_com("xray")
+
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = e
diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua
index b10af78282..1252194976 100644
--- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua
+++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua
@@ -569,14 +569,16 @@ o = s:option(Value, _n("xhttp_path"), translate("XHTTP Path"))
o.placeholder = "/"
o:depends({ [_n("transport")] = "xhttp" })
-o = s:option(TextValue, _n("xhttp_extra"), translate("XHTTP Extra"), translate("An XHTTP extra object in raw json"))
+o = s:option(Flag, _n("use_xhttp_extra"), translate("XHTTP Extra"))
+o.default = "0"
o:depends({ [_n("transport")] = "xhttp" })
+
+o = s:option(TextValue, _n("xhttp_extra"), " ", translate("An XHttpObject in JSON format, used for sharing."))
+o:depends({ [_n("use_xhttp_extra")] = true })
o.rows = 15
o.wrap = "off"
o.custom_write = function(self, section, value)
-
m:set(section, self.option:sub(1 + #option_prefix), value)
-
local success, data = pcall(jsonc.parse, value)
if success and data then
local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address)
@@ -597,6 +599,10 @@ o.validate = function(self, value)
end
return value
end
+o.custom_remove = function(self, section, value)
+ m:del(section, self.option:sub(1 + #option_prefix))
+ m:del(section, "download_address")
+end
-- [[ Mux.Cool ]]--
o = s:option(Flag, _n("mux"), "Mux", translate("Enable Mux.Cool"))
diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua
index ad3d2204a5..d508d71551 100644
--- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua
+++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua
@@ -149,8 +149,26 @@ o = s:option(Value, _n("reality_dest"), translate("Dest"))
o.default = "google.com:443"
o:depends({ [_n("reality")] = true })
-o = s:option(Value, _n("reality_serverNames"), translate("serverNames"))
+o = s:option(DynamicList, _n("reality_serverNames"), translate("serverNames"))
o:depends({ [_n("reality")] = true })
+function o.write(self, section, value)
+ local t = {}
+ local t2 = {}
+ if type(value) == "table" then
+ local x
+ for _, x in ipairs(value) do
+ if x and #x > 0 then
+ if not t2[x] then
+ t2[x] = x
+ t[#t+1] = x
+ end
+ end
+ end
+ else
+ t = { value }
+ end
+ return DynamicList.write(self, section, t)
+end
o = s:option(ListValue, _n("alpn"), translate("alpn"))
o.default = "h2,http/1.1"
diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/api.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/api.lua
index 5dd8468d76..3499e1a128 100644
--- a/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/api.lua
+++ b/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/api.lua
@@ -1232,11 +1232,16 @@ function luci_types(id, m, s, type_name, option_prefix)
end
s.fields[key].remove = function(self, section)
if s.fields["type"]:formvalue(id) == type_name then
- if self.rewrite_option and rewrite_option_table[self.rewrite_option] == 1 then
- m:del(section, self.rewrite_option)
+ -- 添加自定义 custom_remove 属性,如果有自定义的 custom_remove 函数,则使用自定义的 remove 逻辑
+ if self.custom_remove then
+ self:custom_remove(section)
else
- if self.option:find(option_prefix) == 1 then
- m:del(section, self.option:sub(1 + #option_prefix))
+ if self.rewrite_option and rewrite_option_table[self.rewrite_option] == 1 then
+ m:del(section, self.rewrite_option)
+ else
+ if self.option:find(option_prefix) == 1 then
+ m:del(section, self.option:sub(1 + #option_prefix))
+ end
end
end
end
diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_xray.lua
index f7dd5c35ed..d8cbd949d7 100644
--- a/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_xray.lua
+++ b/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_xray.lua
@@ -533,9 +533,7 @@ function gen_config_server(node)
config.inbounds[1].streamSettings.realitySettings = {
show = false,
dest = node.reality_dest,
- serverNames = {
- node.reality_serverNames
- },
+ serverNames = node.reality_serverNames or {},
privateKey = node.reality_private_key,
shortIds = node.reality_shortId or ""
} or nil
diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/global/faq.htm b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/global/faq.htm
index 08ead501f9..ae9d0aa8e6 100644
--- a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/global/faq.htm
+++ b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/global/faq.htm
@@ -47,16 +47,9 @@ local api = require "luci.passwall2.api"
diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm
index 2cd22fb76c..27b1aec7b7 100644
--- a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm
+++ b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm
@@ -379,7 +379,13 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
params += opt.query("host", dom_prefix + "xhttp_host");
params += opt.query("path", dom_prefix + "xhttp_path");
params += opt.query("mode", dom_prefix + "xhttp_mode");
- params += opt.query("extra", dom_prefix + "xhttp_extra");
+ if (opt.get(dom_prefix + "use_xhttp_extra").checked) {
+ params += opt.query("extra", dom_prefix + "xhttp_extra");
+ }
+ } else if (v_transport === "httpupgrade") {
+ v_transport = "httpupgrade";
+ params += opt.query("host", dom_prefix + "httpupgrade_host");
+ params += opt.query("path", dom_prefix + "httpupgrade_path");
}
params += "&type=" + v_transport;
@@ -1245,7 +1251,11 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
opt.set(dom_prefix + 'xhttp_host', queryParam.host || "");
opt.set(dom_prefix + 'xhttp_path', queryParam.path || "");
opt.set(dom_prefix + 'xhttp_mode', queryParam.mode || "auto");
+ opt.set(dom_prefix + 'use_xhttp_extra', !!queryParam.extra);
opt.set(dom_prefix + 'xhttp_extra', queryParam.extra || "");
+ } else if (queryParam.type === "httpupgrade") {
+ opt.set(dom_prefix + 'httpupgrade_host', queryParam.host || "");
+ opt.set(dom_prefix + 'httpupgrade_path', queryParam.path || "");
}
if (m.hash) {
diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/node_list/node_list.htm b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/node_list/node_list.htm
index 70db75fc1c..48066f7628 100644
--- a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/node_list/node_list.htm
+++ b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/node_list/node_list.htm
@@ -46,9 +46,19 @@ table td, .table .td {
color: red !important;
}
+._now_use_bg {
+ background: #5e72e445 !important;
+}
+
.ping a:hover{
text-decoration : underline;
}
+
+@media (prefers-color-scheme: dark) {
+ ._now_use_bg {
+ background: #4a90e2 !important;
+ }
+}
" data-index="<%=self.index%>" data-depends="<%=pcdata(self:deplist2json(section))%>">
-
-
-
\ No newline at end of file
+
+
+
+
+
+
diff --git a/openwrt-passwall2/luci-app-passwall2/po/zh-cn/passwall2.po b/openwrt-passwall2/luci-app-passwall2/po/zh-cn/passwall2.po
index a2121e55d6..8950086d07 100644
--- a/openwrt-passwall2/luci-app-passwall2/po/zh-cn/passwall2.po
+++ b/openwrt-passwall2/luci-app-passwall2/po/zh-cn/passwall2.po
@@ -46,9 +46,6 @@ msgstr "规则列表"
msgid "Access control"
msgstr "访问控制"
-msgid "Watch Logs"
-msgstr "查看日志"
-
msgid "Node Config"
msgstr "节点配置"
@@ -199,18 +196,12 @@ msgstr "客户端DNS和默认网关必须指向本路由器。"
msgid "If you have a wrong DNS process, the consequences are at your own risk!"
msgstr "如果你自行配置了错误的DNS流程,后果自负!"
-msgid "Restore the default configuration method. Input example in the address bar:"
-msgstr "恢复默认配置方法,地址栏输入例:"
-
msgid "Hide menu method, input example in the address bar:"
msgstr "隐藏菜单方法,地址栏输入例:"
msgid "After the hidden to the display, input example in the address bar:"
msgstr "当你隐藏后想再次显示,地址栏输入例:"
-msgid "Are you sure to reset?"
-msgstr "你确定要恢复吗?"
-
msgid "Are you sure to hide?"
msgstr "你确定要隐藏吗?"
@@ -235,9 +226,6 @@ msgstr "对于移动设备,可通过重新接入网络的方式清除。比如
msgid "Please make sure your device's network settings point both the DNS server and default gateway to this router, to ensure DNS queries are properly routed."
msgstr "请确认您设备的网络设置,客户端DNS服务器和默认网关应均指向本路由器,以确保DNS查询正确路由。"
-msgid "Restore to default configuration:"
-msgstr "恢复默认配置:"
-
msgid "Browser access:"
msgstr "浏览器访问:"
@@ -718,6 +706,9 @@ msgstr "请输入节点关键字,注意区分空格、大写和小写。"
msgid "Enable Load Balancing"
msgstr "开启负载均衡"
+msgid "Console Login Auth"
+msgstr "控制台登录认证"
+
msgid "Console Username"
msgstr "控制台账号"
@@ -919,6 +910,9 @@ msgstr "节点订阅"
msgid "Subscribe Remark"
msgstr "订阅备注(机场)"
+msgid "Subscribe Info"
+msgstr "订阅信息"
+
msgid "Subscribe URL"
msgstr "订阅网址"
@@ -1417,8 +1411,8 @@ msgstr "客户端文件不适合当前设备。"
msgid "Can't move new file to path: %s"
msgstr "无法移动新文件到:%s"
-msgid "An XHTTP extra object in raw json"
-msgstr "一个 json 格式的 XHTTP extra object"
+msgid "An XHttpObject in JSON format, used for sharing."
+msgstr "JSON 格式的 XHttpObject,用来实现分享。"
msgid "Enable Mux.Cool"
msgstr "启用 Mux.Cool"
@@ -1611,3 +1605,45 @@ msgstr "仅 IPv4"
msgid "IPv6 Only"
msgstr "仅 IPv6"
+
+msgid "Log Maint"
+msgstr "日志维护"
+
+msgid "Backup and Restore"
+msgstr "备份还原"
+
+msgid "Backup or Restore Client and Server Configurations."
+msgstr "备份或还原客户端及服务端配置。"
+
+msgid "Note: Restoring configurations across different versions may cause compatibility issues."
+msgstr "注意:不同版本间的配置恢复可能会导致兼容性问题。"
+
+msgid "Create Backup File"
+msgstr "创建备份文件"
+
+msgid "Restore Backup File"
+msgstr "恢复备份文件"
+
+msgid "DL Backup"
+msgstr "下载备份"
+
+msgid "RST Backup"
+msgstr "恢复备份"
+
+msgid "UL Restore"
+msgstr "上传恢复"
+
+msgid "CLOSE WIN"
+msgstr "关闭窗口"
+
+msgid "Restore to default configuration"
+msgstr "恢复默认配置"
+
+msgid "Do Reset"
+msgstr "执行重置"
+
+msgid "Do you want to restore the client to default settings?"
+msgstr "是否要恢复客户端默认配置?"
+
+msgid "Are you sure you want to restore the client to default settings?"
+msgstr "是否真的要恢复客户端默认配置?"
diff --git a/openwrt-passwall2/luci-app-passwall2/root/usr/share/passwall2/0_default_config b/openwrt-passwall2/luci-app-passwall2/root/usr/share/passwall2/0_default_config
index 83e60ae887..79dce26964 100644
--- a/openwrt-passwall2/luci-app-passwall2/root/usr/share/passwall2/0_default_config
+++ b/openwrt-passwall2/luci-app-passwall2/root/usr/share/passwall2/0_default_config
@@ -55,6 +55,8 @@ config global_app
config global_subscribe
option filter_keyword_mode '1'
+ list filter_discard_list '距离下次重置剩余'
+ list filter_discard_list '套餐到期'
list filter_discard_list '过期时间'
list filter_discard_list '剩余流量'
list filter_discard_list 'QQ群'
diff --git a/openwrt-passwall2/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua b/openwrt-passwall2/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua
index 10d6ddf11c..ee6d4c129a 100755
--- a/openwrt-passwall2/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua
+++ b/openwrt-passwall2/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua
@@ -379,6 +379,24 @@ local function trim(text)
return (sgsub(text, "^%s*(.-)%s*$", "%1"))
end
+-- 取机场信息(剩余流量、到期时间)
+local subscribe_info = {}
+local function get_subscribe_info(cfgid, value)
+ if type(cfgid) ~= "string" or cfgid == "" or type(value) ~= "string" then
+ return
+ end
+ value = value:gsub("%s+", "")
+ local expired_date = value:match("套餐到期:(.+)")
+ local rem_traffic = value:match("剩余流量:(.+)")
+ subscribe_info[cfgid] = subscribe_info[cfgid] or {expired_date = "", rem_traffic = ""}
+ if expired_date then
+ subscribe_info[cfgid]["expired_date"] = expired_date
+ end
+ if rem_traffic then
+ subscribe_info[cfgid]["rem_traffic"] = rem_traffic
+ end
+end
+
-- 处理数据
local function processData(szType, content, add_mode, add_from)
--log(content, add_mode, add_from)
@@ -519,6 +537,10 @@ local function processData(szType, content, add_mode, add_from)
result.download_address = nil
end
end
+ if info.net == 'httpupgrade' then
+ result.httpupgrade_host = info.host
+ result.httpupgrade_path = info.path
+ end
if not info.security then result.security = "auto" end
if info.tls == "tls" or info.tls == "1" then
result.tls = "1"
@@ -882,6 +904,10 @@ local function processData(szType, content, add_mode, add_from)
result.xhttp_host = params.host
result.xhttp_path = params.path
end
+ if params.type == 'httpupgrade' then
+ result.httpupgrade_host = params.host
+ result.httpupgrade_path = params.path
+ end
result.encryption = params.encryption or "none"
@@ -1021,6 +1047,21 @@ local function processData(szType, content, add_mode, add_from)
if params.type == 'xhttp' or params.type == 'splithttp' then
result.xhttp_host = params.host
result.xhttp_path = params.path
+ result.xhttp_mode = params.mode or "auto"
+ result.use_xhttp_extra = (params.extra and params.extra ~= "") and "1" or nil
+ result.xhttp_extra = (params.extra and params.extra ~= "") and params.extra or nil
+ local success, Data = pcall(jsonParse, params.extra)
+ if success and Data then
+ local address = (Data.extra and Data.extra.downloadSettings and Data.extra.downloadSettings.address)
+ or (Data.downloadSettings and Data.downloadSettings.address)
+ result.download_address = address and address ~= "" and address or nil
+ else
+ result.download_address = nil
+ end
+ end
+ if params.type == 'httpupgrade' then
+ result.httpupgrade_host = params.host
+ result.httpupgrade_path = params.path
end
result.encryption = params.encryption or "none"
@@ -1279,7 +1320,7 @@ local function truncate_nodes(add_from)
end)
if add_from then
uci:foreach(appname, "subscribe_list", function(o)
- if o.remark == add_from then
+ if add_from == "all-node" or add_from == o.remark then
uci:delete(appname, o['.name'], "md5")
end
end)
@@ -1438,6 +1479,16 @@ local function update_node(manual)
end
end
end
+ -- 更新机场信息
+ for cfgid, info in pairs(subscribe_info) do
+ for key, value in pairs(info) do
+ if value ~= "" then
+ uci:set(appname, cfgid, key, value)
+ else
+ uci:delete(appname, cfgid, key)
+ end
+ end
+ end
api.uci_save(uci, appname, true)
if next(CONFIG) then
@@ -1469,7 +1520,7 @@ local function update_node(manual)
luci.sys.call("/etc/init.d/" .. appname .. " restart > /dev/null 2>&1 &")
end
-local function parse_link(raw, add_mode, add_from)
+local function parse_link(raw, add_mode, add_from, cfgid)
if raw and #raw > 0 then
local nodes, szType
local node_list = {}
@@ -1531,6 +1582,9 @@ local function parse_link(raw, add_mode, add_from)
else
tinsert(node_list, result)
end
+ if add_mode == "2" then
+ get_subscribe_info(cfgid, result.remarks)
+ end
end
end, function (err)
--log(err)
@@ -1634,7 +1688,7 @@ local execute = function()
log('订阅:【' .. remark .. '】没有变化,无需更新。')
else
os.remove("/tmp/" .. cfgid)
- parse_link(raw, "2", remark)
+ parse_link(raw, "2", remark, cfgid)
uci:set(appname, cfgid, "md5", new_md5)
end
else
diff --git a/small/luci-app-fchomo/root/etc/fchomo/scripts/fchomo.uc b/small/luci-app-fchomo/root/etc/fchomo/scripts/fchomo.uc
old mode 100755
new mode 100644
diff --git a/small/luci-app-fchomo/root/usr/share/rpcd/ucode/luci.fchomo b/small/luci-app-fchomo/root/usr/share/rpcd/ucode/luci.fchomo
old mode 100755
new mode 100644
diff --git a/small/luci-app-passwall2/luasrc/controller/passwall2.lua b/small/luci-app-passwall2/luasrc/controller/passwall2.lua
index efcb3bd569..ab80723227 100644
--- a/small/luci-app-passwall2/luasrc/controller/passwall2.lua
+++ b/small/luci-app-passwall2/luasrc/controller/passwall2.lua
@@ -7,6 +7,7 @@ local uci = api.uci -- in funtion index()
local http = require "luci.http"
local util = require "luci.util"
local i18n = require "luci.i18n"
+local fs = api.fs
function index()
if not nixio.fs.access("/etc/config/passwall2") then
@@ -45,7 +46,7 @@ function index()
entry({"admin", "services", appname, "socks_config"}, cbi(appname .. "/client/socks_config")).leaf = true
entry({"admin", "services", appname, "acl"}, cbi(appname .. "/client/acl"), _("Access control"), 98).leaf = true
entry({"admin", "services", appname, "acl_config"}, cbi(appname .. "/client/acl_config")).leaf = true
- entry({"admin", "services", appname, "log"}, form(appname .. "/client/log"), _("Watch Logs"), 999).leaf = true
+ entry({"admin", "services", appname, "log"}, form(appname .. "/client/log"), _("Log Maint"), 999).leaf = true
--[[ Server ]]
entry({"admin", "services", appname, "server"}, cbi(appname .. "/server/index"), _("Server-Side"), 99).leaf = true
@@ -84,6 +85,9 @@ function index()
entry({"admin", "services", appname, "check_" .. com}, call("com_check", com)).leaf = true
entry({"admin", "services", appname, "update_" .. com}, call("com_update", com)).leaf = true
end
+
+ --[[Backup]]
+ entry({"admin", "services", appname, "backup"}, call("create_backup")).leaf = true
end
local function http_write_json(content)
@@ -437,4 +441,21 @@ function com_update(comname)
http_write_json(json)
end
+function create_backup()
+ local backup_files = {
+ "/etc/config/passwall2",
+ "/etc/config/passwall2_server",
+ "/usr/share/passwall2/domains_excluded"
+ }
+ local date = os.date("%y%m%d%H%M")
+ local tar_file = "/tmp/passwall2-" .. date .. "-backup.tar.gz"
+ fs.remove(tar_file)
+ local cmd = "tar -czf " .. tar_file .. " " .. table.concat(backup_files, " ")
+ api.sys.call(cmd)
+ http.header("Content-Disposition", "attachment; filename=passwall2-" .. date .. "-backup.tar.gz")
+ http.header("X-Backup-Filename", "passwall2-" .. date .. "-backup.tar.gz")
+ http.prepare_content("application/octet-stream")
+ http.write(fs.readfile(tar_file))
+ fs.remove(tar_file)
+end
diff --git a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl_config.lua b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl_config.lua
index b4064555f5..c850fdba5f 100644
--- a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl_config.lua
+++ b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl_config.lua
@@ -1,14 +1,19 @@
local api = require "luci.passwall2.api"
local appname = api.appname
+
+m = Map(appname)
+api.set_apply_on_parse(m)
+
+if not arg[1] or not m:get(arg[1]) then
+ luci.http.redirect(api.url("acl"))
+end
+
local sys = api.sys
local port_validate = function(self, value, t)
return value:gsub("-", ":")
end
-m = Map(appname)
-api.set_apply_on_parse(m)
-
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = e
diff --git a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/haproxy.lua b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/haproxy.lua
index b0c8b04ee7..584d7b2bc7 100644
--- a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/haproxy.lua
+++ b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/haproxy.lua
@@ -28,16 +28,21 @@ o = s:option(Flag, "balancing_enable", translate("Enable Load Balancing"))
o.rmempty = false
o.default = false
+---- Console Login Auth
+o = s:option(Flag, "console_auth", translate("Console Login Auth"))
+o.default = false
+o:depends("balancing_enable", true)
+
---- Console Username
o = s:option(Value, "console_user", translate("Console Username"))
o.default = ""
-o:depends("balancing_enable", true)
+o:depends("console_auth", true)
---- Console Password
o = s:option(Value, "console_password", translate("Console Password"))
o.password = true
o.default = ""
-o:depends("balancing_enable", true)
+o:depends("console_auth", true)
---- Console Port
o = s:option(Value, "console_port", translate("Console Port"), translate(
diff --git a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/log.lua b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/log.lua
index ff7e74fa19..384cf1b315 100644
--- a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/log.lua
+++ b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/log.lua
@@ -1,8 +1,70 @@
local api = require "luci.passwall2.api"
-local appname = api.appname
+local appname = "passwall2"
+local http = require "luci.http"
+local fs = api.fs
+local sys = api.sys
f = SimpleForm(appname)
f.reset = false
f.submit = false
f:append(Template(appname .. "/log/log"))
-return f
+
+fb = SimpleForm('backup-restore')
+fb.reset = false
+fb.submit = false
+s = fb:section(SimpleSection, translate("Backup and Restore"), translate("Backup or Restore Client and Server Configurations.") ..
+ "
" ..
+ translate("Note: Restoring configurations across different versions may cause compatibility issues.") ..
+ "")
+
+s.anonymous = true
+s:append(Template(appname .. "/log/backup_restore"))
+
+local backup_files = {
+ "/etc/config/passwall2",
+ "/etc/config/passwall2_server",
+ "/usr/share/passwall2/domains_excluded"
+}
+
+local file_path = '/tmp/passwall2_upload.tar.gz'
+local temp_dir = '/tmp/passwall2_bak'
+local fd
+http.setfilehandler(function(meta, chunk, eof)
+ if not fd and meta and meta.name == "ulfile" and chunk then
+ sys.call("rm -rf " .. temp_dir)
+ fs.remove(file_path)
+ fd = nixio.open(file_path, "w")
+ sys.call("echo '' > /tmp/log/passwall2.log")
+ end
+ if fd and chunk then
+ fd:write(chunk)
+ end
+ if eof and fd then
+ fd:close()
+ fd = nil
+ if fs.access(file_path) then
+ api.log(" * PassWall2 配置文件上传成功…")
+ sys.call("mkdir -p " .. temp_dir)
+ if sys.call("tar -xzf " .. file_path .. " -C " .. temp_dir) == 0 then
+ for _, backup_file in ipairs(backup_files) do
+ local temp_file = temp_dir .. backup_file
+ if fs.access(temp_file) then
+ sys.call("cp -f " .. temp_file .. " " .. backup_file)
+ end
+ end
+ api.log(" * PassWall2 配置还原成功…")
+ api.log(" * 重启 PassWall2 服务中…\n")
+ sys.call('/etc/init.d/passwall2 restart > /dev/null 2>&1 &')
+ sys.call('/etc/init.d/passwall2_server restart > /dev/null 2>&1 &')
+ else
+ api.log(" * PassWall2 配置文件解压失败,请重试!")
+ end
+ else
+ api.log(" * PassWall2 配置文件上传失败,请重试!")
+ end
+ sys.call("rm -rf " .. temp_dir)
+ fs.remove(file_path)
+ end
+end)
+
+return f, fb
diff --git a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua
index e561c2cea1..e480dacc5d 100644
--- a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua
+++ b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua
@@ -1,5 +1,6 @@
local api = require "luci.passwall2.api"
local appname = api.appname
+local uci = api.uci
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
local has_singbox = api.finded_com("singbox")
@@ -41,10 +42,28 @@ end
m = Map(appname)
api.set_apply_on_parse(m)
+if api.is_js_luci() then
+ m.on_after_apply = function(self)
+ uci:foreach(appname, "subscribe_list", function(e)
+ uci:delete(appname, e[".name"], "md5")
+ end)
+ uci:commit(appname)
+ end
+end
+
-- [[ Subscribe Settings ]]--
s = m:section(TypedSection, "global_subscribe", "")
s.anonymous = true
+function m.commit_handler(self)
+ if self.no_commit then
+ return
+ end
+ self.uci:foreach(appname, "subscribe_list", function(e)
+ self:del(e[".name"], "md5")
+ end)
+end
+
o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode"))
o:value("0", translate("Close"))
o:value("1", translate("Discard List"))
@@ -112,13 +131,15 @@ o:value("ipv6_only", translate("IPv6 Only"))
o = s:option(Button, "_stop", translate("Delete All Subscribe Node"))
o.inputstyle = "remove"
function o.write(e, e)
- luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate > /dev/null 2>&1")
+ luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate all-node > /dev/null 2>&1")
+ m.no_commit = true
end
o = s:option(Button, "_update", translate("Manual subscription All"))
o.inputstyle = "apply"
function o.write(t, n)
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start > /dev/null 2>&1 &")
+ m.no_commit = true
luci.http.redirect(api.url("log"))
end
@@ -151,17 +172,23 @@ o.validate = function(self, value, t)
end
end
-o = s:option(DummyValue, "_node_count")
+o = s:option(DummyValue, "_node_count", translate("Subscribe Info"))
o.rawhtml = true
o.cfgvalue = function(t, n)
local remark = m:get(n, "remark") or ""
+ local str = m:get(n, "rem_traffic") or ""
+ local expired_date = m:get(n, "expired_date") or ""
+ if expired_date ~= "" then
+ str = str .. (str ~= "" and "/" or "") .. expired_date
+ end
+ str = str ~= "" and "
" .. str or ""
local num = 0
m.uci:foreach(appname, "nodes", function(s)
if s["add_from"] ~= "" and s["add_from"] == remark then
num = num + 1
end
end)
- return string.format("%s", remark .. " " .. translate("Node num") .. ": " .. num, num)
+ return string.format("%s%s", translate("Node num") .. ": " .. num, str)
end
o = s:option(Value, "url", translate("Subscribe URL"))
@@ -173,12 +200,14 @@ o.inputstyle = "remove"
function o.write(t, n)
local remark = m:get(n, "remark") or ""
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate " .. remark .. " > /dev/null 2>&1")
+ m.no_commit = true
end
o = s:option(Button, "_update", translate("Manual subscription"))
o.inputstyle = "apply"
function o.write(t, n)
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start " .. n .. " > /dev/null 2>&1 &")
+ m.no_commit = true
luci.http.redirect(api.url("log"))
end
diff --git a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua
index 64d37e2897..a2b17111b8 100644
--- a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua
+++ b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua
@@ -1,5 +1,14 @@
local api = require "luci.passwall2.api"
local appname = api.appname
+
+m = Map(appname)
+m.redirect = api.url("node_subscribe")
+api.set_apply_on_parse(m)
+
+if not arg[1] or not m:get(arg[1]) then
+ luci.http.redirect(m.redirect)
+end
+
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
local has_singbox = api.finded_com("singbox")
@@ -38,14 +47,14 @@ if has_hysteria2 then
table.insert(hysteria2_type, s)
end
-m = Map(appname)
-m.redirect = api.url("node_subscribe")
-api.set_apply_on_parse(m)
-
s = m:section(NamedSection, arg[1])
s.addremove = false
s.dynamic = false
+function m.commit_handler(self)
+ self:del(arg[1], "md5")
+end
+
o = s:option(Value, "remark", translate("Subscribe Remark"))
o.rmempty = false
diff --git a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua
index 2f66f7616c..6fbeb069f9 100644
--- a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua
+++ b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua
@@ -111,13 +111,16 @@ if (os.execute("lsmod | grep -i REDIRECT >/dev/null") == 0 and os.execute("lsmod
o:value("redirect", "REDIRECT")
o:value("tproxy", "TPROXY")
o:depends("ipv6_tproxy", false)
+ o.remove = function(self, section)
+ -- 禁止在隐藏时删除
+ end
o = s:option(ListValue, "_tcp_proxy_way", translate("TCP Proxy Way"))
o.default = "tproxy"
o:value("tproxy", "TPROXY")
o:depends("ipv6_tproxy", true)
o.write = function(self, section, value)
- return self.map:set(section, "tcp_proxy_way", value)
+ self.map:set(section, "tcp_proxy_way", value)
end
if os.execute("lsmod | grep -i ip6table_mangle >/dev/null") == 0 or os.execute("lsmod | grep -i nft_tproxy >/dev/null") == 0 then
@@ -210,6 +213,7 @@ if has_xray then
o = s_xray_noise:option(ListValue, "type", translate("Type"))
o:value("rand", "rand")
o:value("str", "str")
+ o:value("hex", "hex")
o:value("base64", "base64")
o = s_xray_noise:option(Value, "packet", translate("Packet"))
diff --git a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua
index fa87ec0452..c970d0f106 100644
--- a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua
+++ b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua
@@ -1,11 +1,16 @@
local api = require "luci.passwall2.api"
local appname = api.appname
-local has_singbox = api.finded_com("singbox")
-local has_xray = api.finded_com("xray")
m = Map(appname)
api.set_apply_on_parse(m)
+if not arg[1] or not m:get(arg[1]) then
+ luci.http.redirect(api.url())
+end
+
+local has_singbox = api.finded_com("singbox")
+local has_xray = api.finded_com("xray")
+
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = e
diff --git a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua
index b10af78282..1252194976 100644
--- a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua
+++ b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua
@@ -569,14 +569,16 @@ o = s:option(Value, _n("xhttp_path"), translate("XHTTP Path"))
o.placeholder = "/"
o:depends({ [_n("transport")] = "xhttp" })
-o = s:option(TextValue, _n("xhttp_extra"), translate("XHTTP Extra"), translate("An XHTTP extra object in raw json"))
+o = s:option(Flag, _n("use_xhttp_extra"), translate("XHTTP Extra"))
+o.default = "0"
o:depends({ [_n("transport")] = "xhttp" })
+
+o = s:option(TextValue, _n("xhttp_extra"), " ", translate("An XHttpObject in JSON format, used for sharing."))
+o:depends({ [_n("use_xhttp_extra")] = true })
o.rows = 15
o.wrap = "off"
o.custom_write = function(self, section, value)
-
m:set(section, self.option:sub(1 + #option_prefix), value)
-
local success, data = pcall(jsonc.parse, value)
if success and data then
local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address)
@@ -597,6 +599,10 @@ o.validate = function(self, value)
end
return value
end
+o.custom_remove = function(self, section, value)
+ m:del(section, self.option:sub(1 + #option_prefix))
+ m:del(section, "download_address")
+end
-- [[ Mux.Cool ]]--
o = s:option(Flag, _n("mux"), "Mux", translate("Enable Mux.Cool"))
diff --git a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua
index ad3d2204a5..d508d71551 100644
--- a/small/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua
+++ b/small/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua
@@ -149,8 +149,26 @@ o = s:option(Value, _n("reality_dest"), translate("Dest"))
o.default = "google.com:443"
o:depends({ [_n("reality")] = true })
-o = s:option(Value, _n("reality_serverNames"), translate("serverNames"))
+o = s:option(DynamicList, _n("reality_serverNames"), translate("serverNames"))
o:depends({ [_n("reality")] = true })
+function o.write(self, section, value)
+ local t = {}
+ local t2 = {}
+ if type(value) == "table" then
+ local x
+ for _, x in ipairs(value) do
+ if x and #x > 0 then
+ if not t2[x] then
+ t2[x] = x
+ t[#t+1] = x
+ end
+ end
+ end
+ else
+ t = { value }
+ end
+ return DynamicList.write(self, section, t)
+end
o = s:option(ListValue, _n("alpn"), translate("alpn"))
o.default = "h2,http/1.1"
diff --git a/small/luci-app-passwall2/luasrc/passwall2/api.lua b/small/luci-app-passwall2/luasrc/passwall2/api.lua
index 5dd8468d76..3499e1a128 100644
--- a/small/luci-app-passwall2/luasrc/passwall2/api.lua
+++ b/small/luci-app-passwall2/luasrc/passwall2/api.lua
@@ -1232,11 +1232,16 @@ function luci_types(id, m, s, type_name, option_prefix)
end
s.fields[key].remove = function(self, section)
if s.fields["type"]:formvalue(id) == type_name then
- if self.rewrite_option and rewrite_option_table[self.rewrite_option] == 1 then
- m:del(section, self.rewrite_option)
+ -- 添加自定义 custom_remove 属性,如果有自定义的 custom_remove 函数,则使用自定义的 remove 逻辑
+ if self.custom_remove then
+ self:custom_remove(section)
else
- if self.option:find(option_prefix) == 1 then
- m:del(section, self.option:sub(1 + #option_prefix))
+ if self.rewrite_option and rewrite_option_table[self.rewrite_option] == 1 then
+ m:del(section, self.rewrite_option)
+ else
+ if self.option:find(option_prefix) == 1 then
+ m:del(section, self.option:sub(1 + #option_prefix))
+ end
end
end
end
diff --git a/small/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/small/luci-app-passwall2/luasrc/passwall2/util_xray.lua
index f7dd5c35ed..d8cbd949d7 100644
--- a/small/luci-app-passwall2/luasrc/passwall2/util_xray.lua
+++ b/small/luci-app-passwall2/luasrc/passwall2/util_xray.lua
@@ -533,9 +533,7 @@ function gen_config_server(node)
config.inbounds[1].streamSettings.realitySettings = {
show = false,
dest = node.reality_dest,
- serverNames = {
- node.reality_serverNames
- },
+ serverNames = node.reality_serverNames or {},
privateKey = node.reality_private_key,
shortIds = node.reality_shortId or ""
} or nil
diff --git a/small/luci-app-passwall2/luasrc/view/passwall2/global/faq.htm b/small/luci-app-passwall2/luasrc/view/passwall2/global/faq.htm
index 08ead501f9..ae9d0aa8e6 100644
--- a/small/luci-app-passwall2/luasrc/view/passwall2/global/faq.htm
+++ b/small/luci-app-passwall2/luasrc/view/passwall2/global/faq.htm
@@ -47,16 +47,9 @@ local api = require "luci.passwall2.api"
diff --git a/small/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm b/small/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm
index 2cd22fb76c..27b1aec7b7 100644
--- a/small/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm
+++ b/small/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm
@@ -379,7 +379,13 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
params += opt.query("host", dom_prefix + "xhttp_host");
params += opt.query("path", dom_prefix + "xhttp_path");
params += opt.query("mode", dom_prefix + "xhttp_mode");
- params += opt.query("extra", dom_prefix + "xhttp_extra");
+ if (opt.get(dom_prefix + "use_xhttp_extra").checked) {
+ params += opt.query("extra", dom_prefix + "xhttp_extra");
+ }
+ } else if (v_transport === "httpupgrade") {
+ v_transport = "httpupgrade";
+ params += opt.query("host", dom_prefix + "httpupgrade_host");
+ params += opt.query("path", dom_prefix + "httpupgrade_path");
}
params += "&type=" + v_transport;
@@ -1245,7 +1251,11 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
opt.set(dom_prefix + 'xhttp_host', queryParam.host || "");
opt.set(dom_prefix + 'xhttp_path', queryParam.path || "");
opt.set(dom_prefix + 'xhttp_mode', queryParam.mode || "auto");
+ opt.set(dom_prefix + 'use_xhttp_extra', !!queryParam.extra);
opt.set(dom_prefix + 'xhttp_extra', queryParam.extra || "");
+ } else if (queryParam.type === "httpupgrade") {
+ opt.set(dom_prefix + 'httpupgrade_host', queryParam.host || "");
+ opt.set(dom_prefix + 'httpupgrade_path', queryParam.path || "");
}
if (m.hash) {
diff --git a/small/luci-app-passwall2/luasrc/view/passwall2/node_list/node_list.htm b/small/luci-app-passwall2/luasrc/view/passwall2/node_list/node_list.htm
index 70db75fc1c..48066f7628 100644
--- a/small/luci-app-passwall2/luasrc/view/passwall2/node_list/node_list.htm
+++ b/small/luci-app-passwall2/luasrc/view/passwall2/node_list/node_list.htm
@@ -46,9 +46,19 @@ table td, .table .td {
color: red !important;
}
+._now_use_bg {
+ background: #5e72e445 !important;
+}
+
.ping a:hover{
text-decoration : underline;
}
+
+@media (prefers-color-scheme: dark) {
+ ._now_use_bg {
+ background: #4a90e2 !important;
+ }
+}
" data-index="<%=self.index%>" data-depends="<%=pcdata(self:deplist2json(section))%>">
-
-
-
\ No newline at end of file
+
+
+
+
+
+
diff --git a/small/luci-app-passwall2/po/zh-cn/passwall2.po b/small/luci-app-passwall2/po/zh-cn/passwall2.po
index a2121e55d6..8950086d07 100644
--- a/small/luci-app-passwall2/po/zh-cn/passwall2.po
+++ b/small/luci-app-passwall2/po/zh-cn/passwall2.po
@@ -46,9 +46,6 @@ msgstr "规则列表"
msgid "Access control"
msgstr "访问控制"
-msgid "Watch Logs"
-msgstr "查看日志"
-
msgid "Node Config"
msgstr "节点配置"
@@ -199,18 +196,12 @@ msgstr "客户端DNS和默认网关必须指向本路由器。"
msgid "If you have a wrong DNS process, the consequences are at your own risk!"
msgstr "如果你自行配置了错误的DNS流程,后果自负!"
-msgid "Restore the default configuration method. Input example in the address bar:"
-msgstr "恢复默认配置方法,地址栏输入例:"
-
msgid "Hide menu method, input example in the address bar:"
msgstr "隐藏菜单方法,地址栏输入例:"
msgid "After the hidden to the display, input example in the address bar:"
msgstr "当你隐藏后想再次显示,地址栏输入例:"
-msgid "Are you sure to reset?"
-msgstr "你确定要恢复吗?"
-
msgid "Are you sure to hide?"
msgstr "你确定要隐藏吗?"
@@ -235,9 +226,6 @@ msgstr "对于移动设备,可通过重新接入网络的方式清除。比如
msgid "Please make sure your device's network settings point both the DNS server and default gateway to this router, to ensure DNS queries are properly routed."
msgstr "请确认您设备的网络设置,客户端DNS服务器和默认网关应均指向本路由器,以确保DNS查询正确路由。"
-msgid "Restore to default configuration:"
-msgstr "恢复默认配置:"
-
msgid "Browser access:"
msgstr "浏览器访问:"
@@ -718,6 +706,9 @@ msgstr "请输入节点关键字,注意区分空格、大写和小写。"
msgid "Enable Load Balancing"
msgstr "开启负载均衡"
+msgid "Console Login Auth"
+msgstr "控制台登录认证"
+
msgid "Console Username"
msgstr "控制台账号"
@@ -919,6 +910,9 @@ msgstr "节点订阅"
msgid "Subscribe Remark"
msgstr "订阅备注(机场)"
+msgid "Subscribe Info"
+msgstr "订阅信息"
+
msgid "Subscribe URL"
msgstr "订阅网址"
@@ -1417,8 +1411,8 @@ msgstr "客户端文件不适合当前设备。"
msgid "Can't move new file to path: %s"
msgstr "无法移动新文件到:%s"
-msgid "An XHTTP extra object in raw json"
-msgstr "一个 json 格式的 XHTTP extra object"
+msgid "An XHttpObject in JSON format, used for sharing."
+msgstr "JSON 格式的 XHttpObject,用来实现分享。"
msgid "Enable Mux.Cool"
msgstr "启用 Mux.Cool"
@@ -1611,3 +1605,45 @@ msgstr "仅 IPv4"
msgid "IPv6 Only"
msgstr "仅 IPv6"
+
+msgid "Log Maint"
+msgstr "日志维护"
+
+msgid "Backup and Restore"
+msgstr "备份还原"
+
+msgid "Backup or Restore Client and Server Configurations."
+msgstr "备份或还原客户端及服务端配置。"
+
+msgid "Note: Restoring configurations across different versions may cause compatibility issues."
+msgstr "注意:不同版本间的配置恢复可能会导致兼容性问题。"
+
+msgid "Create Backup File"
+msgstr "创建备份文件"
+
+msgid "Restore Backup File"
+msgstr "恢复备份文件"
+
+msgid "DL Backup"
+msgstr "下载备份"
+
+msgid "RST Backup"
+msgstr "恢复备份"
+
+msgid "UL Restore"
+msgstr "上传恢复"
+
+msgid "CLOSE WIN"
+msgstr "关闭窗口"
+
+msgid "Restore to default configuration"
+msgstr "恢复默认配置"
+
+msgid "Do Reset"
+msgstr "执行重置"
+
+msgid "Do you want to restore the client to default settings?"
+msgstr "是否要恢复客户端默认配置?"
+
+msgid "Are you sure you want to restore the client to default settings?"
+msgstr "是否真的要恢复客户端默认配置?"
diff --git a/small/luci-app-passwall2/root/usr/share/passwall2/0_default_config b/small/luci-app-passwall2/root/usr/share/passwall2/0_default_config
index 83e60ae887..79dce26964 100644
--- a/small/luci-app-passwall2/root/usr/share/passwall2/0_default_config
+++ b/small/luci-app-passwall2/root/usr/share/passwall2/0_default_config
@@ -55,6 +55,8 @@ config global_app
config global_subscribe
option filter_keyword_mode '1'
+ list filter_discard_list '距离下次重置剩余'
+ list filter_discard_list '套餐到期'
list filter_discard_list '过期时间'
list filter_discard_list '剩余流量'
list filter_discard_list 'QQ群'
diff --git a/small/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua b/small/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua
index 10d6ddf11c..ee6d4c129a 100755
--- a/small/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua
+++ b/small/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua
@@ -379,6 +379,24 @@ local function trim(text)
return (sgsub(text, "^%s*(.-)%s*$", "%1"))
end
+-- 取机场信息(剩余流量、到期时间)
+local subscribe_info = {}
+local function get_subscribe_info(cfgid, value)
+ if type(cfgid) ~= "string" or cfgid == "" or type(value) ~= "string" then
+ return
+ end
+ value = value:gsub("%s+", "")
+ local expired_date = value:match("套餐到期:(.+)")
+ local rem_traffic = value:match("剩余流量:(.+)")
+ subscribe_info[cfgid] = subscribe_info[cfgid] or {expired_date = "", rem_traffic = ""}
+ if expired_date then
+ subscribe_info[cfgid]["expired_date"] = expired_date
+ end
+ if rem_traffic then
+ subscribe_info[cfgid]["rem_traffic"] = rem_traffic
+ end
+end
+
-- 处理数据
local function processData(szType, content, add_mode, add_from)
--log(content, add_mode, add_from)
@@ -519,6 +537,10 @@ local function processData(szType, content, add_mode, add_from)
result.download_address = nil
end
end
+ if info.net == 'httpupgrade' then
+ result.httpupgrade_host = info.host
+ result.httpupgrade_path = info.path
+ end
if not info.security then result.security = "auto" end
if info.tls == "tls" or info.tls == "1" then
result.tls = "1"
@@ -882,6 +904,10 @@ local function processData(szType, content, add_mode, add_from)
result.xhttp_host = params.host
result.xhttp_path = params.path
end
+ if params.type == 'httpupgrade' then
+ result.httpupgrade_host = params.host
+ result.httpupgrade_path = params.path
+ end
result.encryption = params.encryption or "none"
@@ -1021,6 +1047,21 @@ local function processData(szType, content, add_mode, add_from)
if params.type == 'xhttp' or params.type == 'splithttp' then
result.xhttp_host = params.host
result.xhttp_path = params.path
+ result.xhttp_mode = params.mode or "auto"
+ result.use_xhttp_extra = (params.extra and params.extra ~= "") and "1" or nil
+ result.xhttp_extra = (params.extra and params.extra ~= "") and params.extra or nil
+ local success, Data = pcall(jsonParse, params.extra)
+ if success and Data then
+ local address = (Data.extra and Data.extra.downloadSettings and Data.extra.downloadSettings.address)
+ or (Data.downloadSettings and Data.downloadSettings.address)
+ result.download_address = address and address ~= "" and address or nil
+ else
+ result.download_address = nil
+ end
+ end
+ if params.type == 'httpupgrade' then
+ result.httpupgrade_host = params.host
+ result.httpupgrade_path = params.path
end
result.encryption = params.encryption or "none"
@@ -1279,7 +1320,7 @@ local function truncate_nodes(add_from)
end)
if add_from then
uci:foreach(appname, "subscribe_list", function(o)
- if o.remark == add_from then
+ if add_from == "all-node" or add_from == o.remark then
uci:delete(appname, o['.name'], "md5")
end
end)
@@ -1438,6 +1479,16 @@ local function update_node(manual)
end
end
end
+ -- 更新机场信息
+ for cfgid, info in pairs(subscribe_info) do
+ for key, value in pairs(info) do
+ if value ~= "" then
+ uci:set(appname, cfgid, key, value)
+ else
+ uci:delete(appname, cfgid, key)
+ end
+ end
+ end
api.uci_save(uci, appname, true)
if next(CONFIG) then
@@ -1469,7 +1520,7 @@ local function update_node(manual)
luci.sys.call("/etc/init.d/" .. appname .. " restart > /dev/null 2>&1 &")
end
-local function parse_link(raw, add_mode, add_from)
+local function parse_link(raw, add_mode, add_from, cfgid)
if raw and #raw > 0 then
local nodes, szType
local node_list = {}
@@ -1531,6 +1582,9 @@ local function parse_link(raw, add_mode, add_from)
else
tinsert(node_list, result)
end
+ if add_mode == "2" then
+ get_subscribe_info(cfgid, result.remarks)
+ end
end
end, function (err)
--log(err)
@@ -1634,7 +1688,7 @@ local execute = function()
log('订阅:【' .. remark .. '】没有变化,无需更新。')
else
os.remove("/tmp/" .. cfgid)
- parse_link(raw, "2", remark)
+ parse_link(raw, "2", remark, cfgid)
uci:set(appname, cfgid, "md5", new_md5)
end
else
diff --git a/v2ray-core/go.mod b/v2ray-core/go.mod
index bdbd8db4eb..7be86c13a8 100644
--- a/v2ray-core/go.mod
+++ b/v2ray-core/go.mod
@@ -24,7 +24,7 @@ require (
github.com/pion/dtls/v2 v2.2.12
github.com/pion/transport/v2 v2.2.10
github.com/pires/go-proxyproto v0.8.0
- github.com/quic-go/quic-go v0.48.2
+ github.com/quic-go/quic-go v0.49.0
github.com/refraction-networking/utls v1.6.7
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb
github.com/stretchr/testify v1.10.0
@@ -86,7 +86,7 @@ require (
github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/xtaci/smux v1.5.24 // indirect
- go.uber.org/mock v0.4.0 // indirect
+ go.uber.org/mock v0.5.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/text v0.21.0 // indirect
diff --git a/v2ray-core/go.sum b/v2ray-core/go.sum
index f5976fb414..59669177f3 100644
--- a/v2ray-core/go.sum
+++ b/v2ray-core/go.sum
@@ -442,8 +442,8 @@ github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
-github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
-github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
+github.com/quic-go/quic-go v0.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94=
+github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
@@ -567,8 +567,8 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
-go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
-go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
+go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
+go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
diff --git a/xray-core/README.md b/xray-core/README.md
index 570237e429..033569b685 100644
--- a/xray-core/README.md
+++ b/xray-core/README.md
@@ -98,6 +98,7 @@
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
- Xray Tools
- [xray-knife](https://github.com/lilendian0x00/xray-knife)
+ - [xray-checker](https://github.com/kutovoys/xray-checker)
- Xray Wrapper
- [XTLS/libXray](https://github.com/XTLS/libXray)
- [xtlsapi](https://github.com/hiddify/xtlsapi)
diff --git a/xray-core/app/dns/nameserver_quic.go b/xray-core/app/dns/nameserver_quic.go
index 0691fac99f..997635a8ff 100644
--- a/xray-core/app/dns/nameserver_quic.go
+++ b/xray-core/app/dns/nameserver_quic.go
@@ -8,7 +8,7 @@ import (
"sync"
"time"
- "github.com/xtls/quic-go"
+ "github.com/quic-go/quic-go"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
diff --git a/xray-core/app/proxyman/inbound/worker.go b/xray-core/app/proxyman/inbound/worker.go
index 14df725dc6..6ef8cca4b7 100644
--- a/xray-core/app/proxyman/inbound/worker.go
+++ b/xray-core/app/proxyman/inbound/worker.go
@@ -2,6 +2,7 @@ package inbound
import (
"context"
+ "strings"
"sync"
"sync/atomic"
"time"
@@ -463,9 +464,19 @@ func (w *dsWorker) callback(conn stat.Connection) {
WriteCounter: w.downlinkCounter,
}
}
+ // For most of time, unix obviously have no source addr. But if we leave it empty, it will cause panic.
+ // So we use gateway as source for log.
+ // However, there are some special situations where a valid source address might be available.
+ // Such as the source address parsed from X-Forwarded-For in websocket.
+ // In that case, we keep it.
+ var source net.Destination
+ if !strings.Contains(conn.RemoteAddr().String(), "unix") {
+ source = net.DestinationFromAddr(conn.RemoteAddr())
+ } else {
+ source = net.UnixDestination(w.address)
+ }
ctx = session.ContextWithInbound(ctx, &session.Inbound{
- // Unix have no source addr, so we use gateway as source for log.
- Source: net.UnixDestination(w.address),
+ Source: source,
Gateway: net.UnixDestination(w.address),
Tag: w.tag,
Conn: conn,
diff --git a/xray-core/common/protocol/quic/sniff.go b/xray-core/common/protocol/quic/sniff.go
index 855ef9f782..779e291bd3 100644
--- a/xray-core/common/protocol/quic/sniff.go
+++ b/xray-core/common/protocol/quic/sniff.go
@@ -8,7 +8,7 @@ import (
"encoding/binary"
"io"
- "github.com/xtls/quic-go/quicvarint"
+ "github.com/quic-go/quic-go/quicvarint"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/bytespool"
diff --git a/xray-core/go.mod b/xray-core/go.mod
index 9491e051fa..dd93bf5cf3 100644
--- a/xray-core/go.mod
+++ b/xray-core/go.mod
@@ -12,6 +12,7 @@ require (
github.com/miekg/dns v1.1.62
github.com/pelletier/go-toml v1.9.5
github.com/pires/go-proxyproto v0.8.0
+ github.com/quic-go/quic-go v0.49.0
github.com/refraction-networking/utls v1.6.7
github.com/sagernet/sing v0.5.1
github.com/sagernet/sing-shadowsocks v0.2.7
@@ -19,7 +20,6 @@ require (
github.com/stretchr/testify v1.10.0
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
github.com/vishvananda/netlink v1.3.0
- github.com/xtls/quic-go v0.48.2
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.32.0
@@ -48,7 +48,7 @@ require (
github.com/quic-go/qpack v0.5.1 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
- go.uber.org/mock v0.4.0 // indirect
+ go.uber.org/mock v0.5.0 // indirect
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/text v0.21.0 // indirect
diff --git a/xray-core/go.sum b/xray-core/go.sum
index 91fbd76a83..b33ef529a9 100644
--- a/xray-core/go.sum
+++ b/xray-core/go.sum
@@ -54,6 +54,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
+github.com/quic-go/quic-go v0.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94=
+github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s=
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
@@ -74,8 +76,6 @@ github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQ
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
-github.com/xtls/quic-go v0.48.2 h1:59Gs+E9qtc9s0uniXYDA649gNEZlMWcNpFLyp9jfkuE=
-github.com/xtls/quic-go v0.48.2/go.mod h1:rcyY5J0JT+1d5pa5Y+FbCsXM7Zu79jE87ZSFOBfiH7Q=
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d h1:+B97uD9uHLgAAulhigmys4BVwZZypzK7gPN3WtpgRJg=
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@@ -89,8 +89,8 @@ go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiy
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
-go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
-go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
+go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
+go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
diff --git a/xray-core/transport/internet/splithttp/dialer.go b/xray-core/transport/internet/splithttp/dialer.go
index 6a276e8f52..b97c39e90f 100644
--- a/xray-core/transport/internet/splithttp/dialer.go
+++ b/xray-core/transport/internet/splithttp/dialer.go
@@ -13,8 +13,8 @@ import (
"sync/atomic"
"time"
- "github.com/xtls/quic-go"
- "github.com/xtls/quic-go/http3"
+ "github.com/quic-go/quic-go"
+ "github.com/quic-go/quic-go/http3"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
diff --git a/xray-core/transport/internet/splithttp/hub.go b/xray-core/transport/internet/splithttp/hub.go
index 58aec803c1..a9aba35050 100644
--- a/xray-core/transport/internet/splithttp/hub.go
+++ b/xray-core/transport/internet/splithttp/hub.go
@@ -13,8 +13,8 @@ import (
"sync"
"time"
- "github.com/xtls/quic-go"
- "github.com/xtls/quic-go/http3"
+ "github.com/quic-go/quic-go"
+ "github.com/quic-go/quic-go/http3"
goreality "github.com/xtls/reality"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
diff --git a/yt-dlp/.gitignore b/yt-dlp/.gitignore
index fdd904f7fe..8fcd0de641 100644
--- a/yt-dlp/.gitignore
+++ b/yt-dlp/.gitignore
@@ -92,6 +92,7 @@ updates_key.pem
*.class
*.isorted
*.stackdump
+uv.lock
# Generated
AUTHORS
diff --git a/yt-dlp/CONTRIBUTORS b/yt-dlp/CONTRIBUTORS
index 0102264180..7376b18015 100644
--- a/yt-dlp/CONTRIBUTORS
+++ b/yt-dlp/CONTRIBUTORS
@@ -715,3 +715,24 @@ Crypto90
MutantPiggieGolem1
Sanceilaks
Strkmn
+0x9fff00
+4ft35t
+7x11x13
+b5i
+cotko
+d3d9
+Dioarya
+finch71
+hexahigh
+InvalidUsernameException
+jixunmoe
+knackku
+krandor
+kvk-2015
+lonble
+msm595
+n10dollar
+NecroRomnt
+pjrobertson
+subsense
+test20140
diff --git a/yt-dlp/Changelog.md b/yt-dlp/Changelog.md
index b996d35f7a..3232c158b5 100644
--- a/yt-dlp/Changelog.md
+++ b/yt-dlp/Changelog.md
@@ -4,6 +4,60 @@
# To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master
-->
+### 2025.01.26
+
+#### Core changes
+- [Fix float comparison values in format filters](https://github.com/yt-dlp/yt-dlp/commit/f7d071e8aa3bf67ed7e0f881e749ca9ab50b3f8f) ([#11880](https://github.com/yt-dlp/yt-dlp/issues/11880)) by [bashonly](https://github.com/bashonly), [Dioarya](https://github.com/Dioarya)
+- **utils**: `sanitize_path`: [Fix some incorrect behavior](https://github.com/yt-dlp/yt-dlp/commit/fc12e724a3b4988cfc467d2981887dde48c26b69) ([#11923](https://github.com/yt-dlp/yt-dlp/issues/11923)) by [Grub4K](https://github.com/Grub4K)
+
+#### Extractor changes
+- **1tv**: [Support sport1tv.ru domain](https://github.com/yt-dlp/yt-dlp/commit/61ae5dc34ac775d6c122575e21ef2153b1273a2b) ([#11889](https://github.com/yt-dlp/yt-dlp/issues/11889)) by [kvk-2015](https://github.com/kvk-2015)
+- **abematv**: [Support season extraction](https://github.com/yt-dlp/yt-dlp/commit/c709cc41cbc16edc846e0a431cfa8508396d4cb6) ([#11771](https://github.com/yt-dlp/yt-dlp/issues/11771)) by [middlingphys](https://github.com/middlingphys)
+- **bilibili**
+ - [Support space `/lists/` URLs](https://github.com/yt-dlp/yt-dlp/commit/465167910407449354eb48e9861efd0819f53eb5) ([#11964](https://github.com/yt-dlp/yt-dlp/issues/11964)) by [c-basalt](https://github.com/c-basalt)
+ - [Support space video list extraction without login](https://github.com/yt-dlp/yt-dlp/commit/78912ed9c81f109169b828c397294a6cf8eacf41) ([#12089](https://github.com/yt-dlp/yt-dlp/issues/12089)) by [grqz](https://github.com/grqz)
+- **bilibilidynamic**: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/9676b05715b61c8c5dd5598871e60d8807fb1a86) ([#11838](https://github.com/yt-dlp/yt-dlp/issues/11838)) by [finch71](https://github.com/finch71), [grqz](https://github.com/grqz)
+- **bluesky**: [Prefer source format](https://github.com/yt-dlp/yt-dlp/commit/ccda63934df7de2823f0834218c4254c7c4d2e4c) ([#12154](https://github.com/yt-dlp/yt-dlp/issues/12154)) by [0x9fff00](https://github.com/0x9fff00)
+- **crunchyroll**: [Remove extractors](https://github.com/yt-dlp/yt-dlp/commit/ff44ed53061e065804da6275d182d7928cc03a5e) ([#12195](https://github.com/yt-dlp/yt-dlp/issues/12195)) by [seproDev](https://github.com/seproDev)
+- **dropout**: [Fix extraction](https://github.com/yt-dlp/yt-dlp/commit/164368610456e2d96b279f8b120dea08f7b1d74f) ([#12102](https://github.com/yt-dlp/yt-dlp/issues/12102)) by [bashonly](https://github.com/bashonly)
+- **eggs**: [Add extractors](https://github.com/yt-dlp/yt-dlp/commit/20c765d02385a105c8ef13b6f7a737491d29c19a) ([#11904](https://github.com/yt-dlp/yt-dlp/issues/11904)) by [seproDev](https://github.com/seproDev), [subsense](https://github.com/subsense)
+- **funimation**: [Remove extractors](https://github.com/yt-dlp/yt-dlp/commit/cdcf1e86726b8fa44f7e7126bbf1c18e1798d25c) ([#12167](https://github.com/yt-dlp/yt-dlp/issues/12167)) by [doe1080](https://github.com/doe1080)
+- **goodgame**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/e7cc02b14d8d323f805d14325a9c95593a170d28) ([#12173](https://github.com/yt-dlp/yt-dlp/issues/12173)) by [NecroRomnt](https://github.com/NecroRomnt)
+- **lbry**: [Support signed URLs](https://github.com/yt-dlp/yt-dlp/commit/de30f652ffb7623500215f5906844f2ae0d92c7b) ([#12138](https://github.com/yt-dlp/yt-dlp/issues/12138)) by [seproDev](https://github.com/seproDev)
+- **naver**: [Fix m3u8 formats extraction](https://github.com/yt-dlp/yt-dlp/commit/b3007c44cdac38187fc6600de76959a7079a44d1) ([#12037](https://github.com/yt-dlp/yt-dlp/issues/12037)) by [kclauhk](https://github.com/kclauhk)
+- **nest**: [Add extractors](https://github.com/yt-dlp/yt-dlp/commit/1ef3ee7500c4ab8c26f7fdc5b0ad1da4d16eec8e) ([#11747](https://github.com/yt-dlp/yt-dlp/issues/11747)) by [pabs3](https://github.com/pabs3), [seproDev](https://github.com/seproDev)
+- **niconico**: series: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/bc88b904cd02314da41ce1b2fdf046d0680fe965) ([#11822](https://github.com/yt-dlp/yt-dlp/issues/11822)) by [test20140](https://github.com/test20140)
+- **nrk**
+ - [Extract more formats](https://github.com/yt-dlp/yt-dlp/commit/89198bb23b4d03e0473ac408bfb50d67c2f71165) ([#12069](https://github.com/yt-dlp/yt-dlp/issues/12069)) by [hexahigh](https://github.com/hexahigh)
+ - [Fix extraction](https://github.com/yt-dlp/yt-dlp/commit/45732e2590a1bd0bc9608f5eb68c59341ca84f02) ([#12193](https://github.com/yt-dlp/yt-dlp/issues/12193)) by [hexahigh](https://github.com/hexahigh)
+- **patreon**: [Extract attachment filename as `alt_title`](https://github.com/yt-dlp/yt-dlp/commit/e2e73b5c65593ec0a5e685663e6ec0f4aaffc1f1) ([#12000](https://github.com/yt-dlp/yt-dlp/issues/12000)) by [msm595](https://github.com/msm595)
+- **pbs**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/13825ab77815ee6e1603abbecbb9f3795057b93c) ([#12024](https://github.com/yt-dlp/yt-dlp/issues/12024)) by [dirkf](https://github.com/dirkf), [krandor](https://github.com/krandor), [n10dollar](https://github.com/n10dollar)
+- **piramidetv**: [Add extractors](https://github.com/yt-dlp/yt-dlp/commit/af2c821d74049b519895288aca23cee81fc4b049) ([#10777](https://github.com/yt-dlp/yt-dlp/issues/10777)) by [HobbyistDev](https://github.com/HobbyistDev), [kclauhk](https://github.com/kclauhk), [seproDev](https://github.com/seproDev)
+- **redgifs**: [Support `/ifr/` URLs](https://github.com/yt-dlp/yt-dlp/commit/4850ce91d163579fa615c3c0d44c9bd64682c22b) ([#11805](https://github.com/yt-dlp/yt-dlp/issues/11805)) by [invertico](https://github.com/invertico)
+- **rtvslo.si**: show: [Extract more metadata](https://github.com/yt-dlp/yt-dlp/commit/3fc46086562857d5493cbcff687f76e4e4ed303f) ([#12136](https://github.com/yt-dlp/yt-dlp/issues/12136)) by [cotko](https://github.com/cotko)
+- **senategov**: [Fix extractors](https://github.com/yt-dlp/yt-dlp/commit/68221ecc87c6a3f3515757bac2a0f9674a38e3f2) ([#9361](https://github.com/yt-dlp/yt-dlp/issues/9361)) by [Grabien](https://github.com/Grabien), [seproDev](https://github.com/seproDev)
+- **soundcloud**
+ - [Extract more metadata](https://github.com/yt-dlp/yt-dlp/commit/6d304133ab32bcd1eb78ff1467f1a41dd9b66c33) ([#11945](https://github.com/yt-dlp/yt-dlp/issues/11945)) by [7x11x13](https://github.com/7x11x13)
+ - user: [Add `/comments` page support](https://github.com/yt-dlp/yt-dlp/commit/7bfb4f72e490310d2681c7f4815218a2ebbc73ee) ([#11999](https://github.com/yt-dlp/yt-dlp/issues/11999)) by [7x11x13](https://github.com/7x11x13)
+- **subsplash**: [Add extractors](https://github.com/yt-dlp/yt-dlp/commit/5d904b077d2f58ae44bdf208d2dcfcc3ff8347f5) ([#11054](https://github.com/yt-dlp/yt-dlp/issues/11054)) by [seproDev](https://github.com/seproDev), [subrat-lima](https://github.com/subrat-lima)
+- **theatercomplextownppv**: [Support `live` URLs](https://github.com/yt-dlp/yt-dlp/commit/797d2472a299692e01ad1500e8c3b7bc1daa7fe4) ([#11720](https://github.com/yt-dlp/yt-dlp/issues/11720)) by [bashonly](https://github.com/bashonly)
+- **vimeo**: [Fix thumbnail extraction](https://github.com/yt-dlp/yt-dlp/commit/9ff330948c92f6b2e1d9c928787362ab19cd6c62) ([#12142](https://github.com/yt-dlp/yt-dlp/issues/12142)) by [jixunmoe](https://github.com/jixunmoe)
+- **vimp**: Playlist: [Add support for tags](https://github.com/yt-dlp/yt-dlp/commit/d4f5be1735c8feaeb3308666e0b878e9782f529d) ([#11688](https://github.com/yt-dlp/yt-dlp/issues/11688)) by [FestplattenSchnitzel](https://github.com/FestplattenSchnitzel)
+- **weibo**: [Extend `_VALID_URL`](https://github.com/yt-dlp/yt-dlp/commit/a567f97b62ae9f6d6f5a9376c361512ab8dceda2) ([#12088](https://github.com/yt-dlp/yt-dlp/issues/12088)) by [4ft35t](https://github.com/4ft35t)
+- **xhamster**: [Various improvements](https://github.com/yt-dlp/yt-dlp/commit/3b99a0f0e07f0120ab416f34a8f5ab75d4fdf1d1) ([#11738](https://github.com/yt-dlp/yt-dlp/issues/11738)) by [knackku](https://github.com/knackku)
+- **xiaohongshu**: [Extract more formats](https://github.com/yt-dlp/yt-dlp/commit/f9f24ae376a9eaca777816479a4a29f6f0ce7681) ([#12147](https://github.com/yt-dlp/yt-dlp/issues/12147)) by [seproDev](https://github.com/seproDev)
+- **youtube**
+ - [Download `tv` client Innertube config](https://github.com/yt-dlp/yt-dlp/commit/326fb1ffaf4e8349f1fe8ba2a81839652e044bff) ([#12168](https://github.com/yt-dlp/yt-dlp/issues/12168)) by [coletdjnz](https://github.com/coletdjnz)
+ - [Extract `media_type` for livestreams](https://github.com/yt-dlp/yt-dlp/commit/421bc72103d1faed473a451299cd17d6abb433bb) ([#11605](https://github.com/yt-dlp/yt-dlp/issues/11605)) by [nosoop](https://github.com/nosoop)
+ - [Restore convenience workarounds](https://github.com/yt-dlp/yt-dlp/commit/f0d4b8a5d6354b294bc9631cf15a7160b7bad5de) ([#12181](https://github.com/yt-dlp/yt-dlp/issues/12181)) by [bashonly](https://github.com/bashonly)
+ - [Update `ios` player client](https://github.com/yt-dlp/yt-dlp/commit/de82acf8769282ce321a86737ecc1d4bef0e82a7) ([#12155](https://github.com/yt-dlp/yt-dlp/issues/12155)) by [b5i](https://github.com/b5i)
+ - [Use different PO token for GVS and Player](https://github.com/yt-dlp/yt-dlp/commit/6b91d232e316efa406035915532eb126fbaeea38) ([#12090](https://github.com/yt-dlp/yt-dlp/issues/12090)) by [coletdjnz](https://github.com/coletdjnz)
+ - tab: [Improve shorts title extraction](https://github.com/yt-dlp/yt-dlp/commit/76ac023ff02f06e8c003d104f02a03deeddebdcd) ([#11997](https://github.com/yt-dlp/yt-dlp/issues/11997)) by [bashonly](https://github.com/bashonly), [d3d9](https://github.com/d3d9)
+- **zdf**: [Fix extractors](https://github.com/yt-dlp/yt-dlp/commit/bb69f5dab79fb32c4ec0d50e05f7fa26d05d54ba) ([#11041](https://github.com/yt-dlp/yt-dlp/issues/11041)) by [InvalidUsernameException](https://github.com/InvalidUsernameException)
+
+#### Misc. changes
+- **cleanup**: Miscellaneous: [3b45319](https://github.com/yt-dlp/yt-dlp/commit/3b4531934465580be22937fecbb6e1a3a9e2334f) by [bashonly](https://github.com/bashonly), [lonble](https://github.com/lonble), [pjrobertson](https://github.com/pjrobertson), [seproDev](https://github.com/seproDev)
+
### 2025.01.15
#### Extractor changes
diff --git a/yt-dlp/README.md b/yt-dlp/README.md
index fd5547d36e..45c56434ab 100644
--- a/yt-dlp/README.md
+++ b/yt-dlp/README.md
@@ -1760,7 +1760,7 @@ $ yt-dlp --replace-in-metadata "title,uploader" "[ _]" "-"
# EXTRACTOR ARGUMENTS
-Some extractors accept additional arguments which can be passed using `--extractor-args KEY:ARGS`. `ARGS` is a `;` (semicolon) separated string of `ARG=VAL1,VAL2`. E.g. `--extractor-args "youtube:player-client=tv,mweb;formats=incomplete" --extractor-args "funimation:version=uncut"`
+Some extractors accept additional arguments which can be passed using `--extractor-args KEY:ARGS`. `ARGS` is a `;` (semicolon) separated string of `ARG=VAL1,VAL2`. E.g. `--extractor-args "youtube:player-client=tv,mweb;formats=incomplete" --extractor-args "twitter:api=syndication"`
Note: In CLI, `ARG` can use `-` instead of `_`; e.g. `youtube:player-client"` becomes `youtube:player_client"`
@@ -1795,13 +1795,6 @@ The following extractors use this feature:
* `is_live`: Bypass live HLS detection and manually set `live_status` - a value of `false` will set `not_live`, any other value (or no value) will set `is_live`
* `impersonate`: Target(s) to try and impersonate with the initial webpage request; e.g. `generic:impersonate=safari,chrome-110`. Use `generic:impersonate` to impersonate any available target, and use `generic:impersonate=false` to disable impersonation (default)
-#### funimation
-* `language`: Audio languages to extract, e.g. `funimation:language=english,japanese`
-* `version`: The video version to extract - `uncut` or `simulcast`
-
-#### crunchyrollbeta (Crunchyroll)
-* `hardsub`: One or more hardsub versions to extract (in order of preference), or `all` (default: `None` = no hardsubs will be extracted), e.g. `crunchyrollbeta:hardsub=en-US,de-DE`
-
#### vikichannel
* `video_types`: Types of videos to download - one or more of `episodes`, `movies`, `clips`, `trailers`
diff --git a/yt-dlp/devscripts/changelog_override.json b/yt-dlp/devscripts/changelog_override.json
index 079e2f7296..8aa7b7e2bc 100644
--- a/yt-dlp/devscripts/changelog_override.json
+++ b/yt-dlp/devscripts/changelog_override.json
@@ -239,5 +239,11 @@
"action": "add",
"when": "52c0ffe40ad6e8404d93296f575007b05b04c686",
"short": "[priority] **Login with OAuth is no longer supported for YouTube**\nDue to a change made by the site, yt-dlp is no longer able to support OAuth login for YouTube. [Read more](https://github.com/yt-dlp/yt-dlp/issues/11462#issuecomment-2471703090)"
+ },
+ {
+ "action": "change",
+ "when": "76ac023ff02f06e8c003d104f02a03deeddebdcd",
+ "short": "[ie/youtube:tab] Improve shorts title extraction (#11997)",
+ "authors": ["bashonly", "d3d9"]
}
]
diff --git a/yt-dlp/supportedsites.md b/yt-dlp/supportedsites.md
index 1420742d17..70909ef002 100644
--- a/yt-dlp/supportedsites.md
+++ b/yt-dlp/supportedsites.md
@@ -171,6 +171,7 @@
- **BilibiliCheese**
- **BilibiliCheeseSeason**
- **BilibiliCollectionList**
+ - **BiliBiliDynamic**
- **BilibiliFavoritesList**
- **BiliBiliPlayer**
- **BilibiliPlaylist**
@@ -303,10 +304,6 @@
- **CrowdBunker**
- **CrowdBunkerChannel**
- **Crtvg**
- - **crunchyroll**: [*crunchyroll*](## "netrc machine")
- - **crunchyroll:artist**: [*crunchyroll*](## "netrc machine")
- - **crunchyroll:music**: [*crunchyroll*](## "netrc machine")
- - **crunchyroll:playlist**: [*crunchyroll*](## "netrc machine")
- **CSpan**: C-SPAN
- **CSpanCongress**
- **CtsNews**: 華視新聞
@@ -393,6 +390,8 @@
- **Ebay**
- **egghead:course**: egghead.io course
- **egghead:lesson**: egghead.io lesson
+ - **eggs:artist**
+ - **eggs:single**
- **EinsUndEinsTV**: [*1und1tv*](## "netrc machine")
- **EinsUndEinsTVLive**: [*1und1tv*](## "netrc machine")
- **EinsUndEinsTVRecordings**: [*1und1tv*](## "netrc machine")
@@ -477,9 +476,6 @@
- **FrontendMastersCourse**: [*frontendmasters*](## "netrc machine")
- **FrontendMastersLesson**: [*frontendmasters*](## "netrc machine")
- **FujiTVFODPlus7**
- - **Funimation**: [*funimation*](## "netrc machine")
- - **funimation:page**: [*funimation*](## "netrc machine")
- - **funimation:show**: [*funimation*](## "netrc machine")
- **Funk**
- **Funker530**
- **Fux**
@@ -892,6 +888,8 @@
- **nebula:video**: [*watchnebula*](## "netrc machine")
- **NekoHacker**
- **NerdCubedFeed**
+ - **Nest**
+ - **NestClip**
- **netease:album**: 网易云音乐 - 专辑
- **netease:djradio**: 网易云音乐 - 电台
- **netease:mv**: 网易云音乐 - MV
@@ -1071,6 +1069,8 @@
- **Pinkbike**
- **Pinterest**
- **PinterestCollection**
+ - **PiramideTV**
+ - **PiramideTVChannel**
- **pixiv:sketch**
- **pixiv:sketch:user**
- **Pladform**
@@ -1396,6 +1396,8 @@
- **StretchInternet**
- **Stripchat**
- **stv:player**
+ - **Subsplash**
+ - **subsplash:playlist**
- **Substack**
- **SunPorno**
- **sverigesradio:episode**
diff --git a/yt-dlp/test/test_YoutubeDL.py b/yt-dlp/test/test_YoutubeDL.py
index 6b022a7eaa..17e081bc6e 100644
--- a/yt-dlp/test/test_YoutubeDL.py
+++ b/yt-dlp/test/test_YoutubeDL.py
@@ -486,11 +486,11 @@ class TestFormatSelection(unittest.TestCase):
def test_format_filtering(self):
formats = [
- {'format_id': 'A', 'filesize': 500, 'width': 1000},
- {'format_id': 'B', 'filesize': 1000, 'width': 500},
- {'format_id': 'C', 'filesize': 1000, 'width': 400},
- {'format_id': 'D', 'filesize': 2000, 'width': 600},
- {'format_id': 'E', 'filesize': 3000},
+ {'format_id': 'A', 'filesize': 500, 'width': 1000, 'aspect_ratio': 1.0},
+ {'format_id': 'B', 'filesize': 1000, 'width': 500, 'aspect_ratio': 1.33},
+ {'format_id': 'C', 'filesize': 1000, 'width': 400, 'aspect_ratio': 1.5},
+ {'format_id': 'D', 'filesize': 2000, 'width': 600, 'aspect_ratio': 1.78},
+ {'format_id': 'E', 'filesize': 3000, 'aspect_ratio': 0.56},
{'format_id': 'F'},
{'format_id': 'G', 'filesize': 1000000},
]
@@ -549,6 +549,31 @@ class TestFormatSelection(unittest.TestCase):
ydl.process_ie_result(info_dict)
self.assertEqual(ydl.downloaded_info_dicts, [])
+ ydl = YDL({'format': 'best[aspect_ratio=1]'})
+ ydl.process_ie_result(info_dict)
+ downloaded = ydl.downloaded_info_dicts[0]
+ self.assertEqual(downloaded['format_id'], 'A')
+
+ ydl = YDL({'format': 'all[aspect_ratio > 1.00]'})
+ ydl.process_ie_result(info_dict)
+ downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
+ self.assertEqual(downloaded_ids, ['D', 'C', 'B'])
+
+ ydl = YDL({'format': 'all[aspect_ratio < 1.00]'})
+ ydl.process_ie_result(info_dict)
+ downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
+ self.assertEqual(downloaded_ids, ['E'])
+
+ ydl = YDL({'format': 'best[aspect_ratio=1.5]'})
+ ydl.process_ie_result(info_dict)
+ downloaded = ydl.downloaded_info_dicts[0]
+ self.assertEqual(downloaded['format_id'], 'C')
+
+ ydl = YDL({'format': 'all[aspect_ratio!=1]'})
+ ydl.process_ie_result(info_dict)
+ downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
+ self.assertEqual(downloaded_ids, ['E', 'D', 'C', 'B'])
+
@patch('yt_dlp.postprocessor.ffmpeg.FFmpegMergerPP.available', False)
def test_default_format_spec_without_ffmpeg(self):
ydl = YDL({})
diff --git a/yt-dlp/test/test_utils.py b/yt-dlp/test/test_utils.py
index b3de14198e..8f81d0b1b7 100644
--- a/yt-dlp/test/test_utils.py
+++ b/yt-dlp/test/test_utils.py
@@ -249,17 +249,36 @@ class TestUtil(unittest.TestCase):
self.assertEqual(sanitize_path('abc/def...'), 'abc\\def..#')
self.assertEqual(sanitize_path('abc.../def'), 'abc..#\\def')
self.assertEqual(sanitize_path('abc.../def...'), 'abc..#\\def..#')
-
- self.assertEqual(sanitize_path('../abc'), '..\\abc')
- self.assertEqual(sanitize_path('../../abc'), '..\\..\\abc')
- self.assertEqual(sanitize_path('./abc'), 'abc')
- self.assertEqual(sanitize_path('./../abc'), '..\\abc')
-
- self.assertEqual(sanitize_path('\\abc'), '\\abc')
- self.assertEqual(sanitize_path('C:abc'), 'C:abc')
- self.assertEqual(sanitize_path('C:abc\\..\\'), 'C:..')
self.assertEqual(sanitize_path('C:\\abc:%(title)s.%(ext)s'), 'C:\\abc#%(title)s.%(ext)s')
+ # Check with nt._path_normpath if available
+ try:
+ import nt
+
+ nt_path_normpath = getattr(nt, '_path_normpath', None)
+ except Exception:
+ nt_path_normpath = None
+
+ for test, expected in [
+ ('C:\\', 'C:\\'),
+ ('../abc', '..\\abc'),
+ ('../../abc', '..\\..\\abc'),
+ ('./abc', 'abc'),
+ ('./../abc', '..\\abc'),
+ ('\\abc', '\\abc'),
+ ('C:abc', 'C:abc'),
+ ('C:abc\\..\\', 'C:'),
+ ('C:abc\\..\\def\\..\\..\\', 'C:..'),
+ ('C:\\abc\\xyz///..\\def\\', 'C:\\abc\\def'),
+ ('abc/../', '.'),
+ ('./abc/../', '.'),
+ ]:
+ result = sanitize_path(test)
+ assert result == expected, f'{test} was incorrectly resolved'
+ assert result == sanitize_path(result), f'{test} changed after sanitizing again'
+ if nt_path_normpath:
+ assert result == nt_path_normpath(test), f'{test} does not match nt._path_normpath'
+
def test_sanitize_url(self):
self.assertEqual(sanitize_url('//foo.bar'), 'http://foo.bar')
self.assertEqual(sanitize_url('httpss://foo.bar'), 'https://foo.bar')
diff --git a/yt-dlp/yt_dlp/YoutubeDL.py b/yt-dlp/yt_dlp/YoutubeDL.py
index f6155dd2e9..b7b19cf6e0 100644
--- a/yt-dlp/yt_dlp/YoutubeDL.py
+++ b/yt-dlp/yt_dlp/YoutubeDL.py
@@ -2121,7 +2121,7 @@ class YoutubeDL:
m = operator_rex.fullmatch(filter_spec)
if m:
try:
- comparison_value = int(m.group('value'))
+ comparison_value = float(m.group('value'))
except ValueError:
comparison_value = parse_filesize(m.group('value'))
if comparison_value is None:
diff --git a/yt-dlp/yt_dlp/extractor/_extractors.py b/yt-dlp/yt_dlp/extractor/_extractors.py
index aa8344bb29..2566a791c6 100644
--- a/yt-dlp/yt_dlp/extractor/_extractors.py
+++ b/yt-dlp/yt_dlp/extractor/_extractors.py
@@ -441,12 +441,6 @@ from .crowdbunker import (
CrowdBunkerIE,
)
from .crtvg import CrtvgIE
-from .crunchyroll import (
- CrunchyrollArtistIE,
- CrunchyrollBetaIE,
- CrunchyrollBetaShowIE,
- CrunchyrollMusicIE,
-)
from .cspan import (
CSpanCongressIE,
CSpanIE,
@@ -705,11 +699,6 @@ from .frontendmasters import (
FrontendMastersLessonIE,
)
from .fujitv import FujiTVFODPlus7IE
-from .funimation import (
- FunimationIE,
- FunimationPageIE,
- FunimationShowIE,
-)
from .funk import FunkIE
from .funker530 import Funker530IE
from .fuyintv import FuyinTVIE
diff --git a/yt-dlp/yt_dlp/extractor/abematv.py b/yt-dlp/yt_dlp/extractor/abematv.py
index b1343eed39..8c7131b10a 100644
--- a/yt-dlp/yt_dlp/extractor/abematv.py
+++ b/yt-dlp/yt_dlp/extractor/abematv.py
@@ -421,14 +421,15 @@ class AbemaTVIE(AbemaTVBaseIE):
class AbemaTVTitleIE(AbemaTVBaseIE):
- _VALID_URL = r'https?://abema\.tv/video/title/(?P[^?/]+)'
+ _VALID_URL = r'https?://abema\.tv/video/title/(?P[^?/#]+)/?(?:\?(?:[^#]+&)?s=(?P[^]+))?'
_PAGE_SIZE = 25
_TESTS = [{
- 'url': 'https://abema.tv/video/title/90-1597',
+ 'url': 'https://abema.tv/video/title/90-1887',
'info_dict': {
- 'id': '90-1597',
+ 'id': '90-1887',
'title': 'シャッフルアイランド',
+ 'description': 'md5:61b2425308f41a5282a926edda66f178',
},
'playlist_mincount': 2,
}, {
@@ -436,41 +437,54 @@ class AbemaTVTitleIE(AbemaTVBaseIE):
'info_dict': {
'id': '193-132',
'title': '真心が届く~僕とスターのオフィス・ラブ!?~',
+ 'description': 'md5:9b59493d1f3a792bafbc7319258e7af8',
},
'playlist_mincount': 16,
}, {
- 'url': 'https://abema.tv/video/title/25-102',
+ 'url': 'https://abema.tv/video/title/25-1nzan-whrxe',
'info_dict': {
- 'id': '25-102',
- 'title': 'ソードアート・オンライン アリシゼーション',
+ 'id': '25-1nzan-whrxe',
+ 'title': 'ソードアート・オンライン',
+ 'description': 'md5:c094904052322e6978495532bdbf06e6',
},
- 'playlist_mincount': 24,
+ 'playlist_mincount': 25,
+ }, {
+ 'url': 'https://abema.tv/video/title/26-2mzbynr-cph?s=26-2mzbynr-cph_s40',
+ 'info_dict': {
+ 'title': '〈物語〉シリーズ',
+ 'id': '26-2mzbynr-cph',
+ 'description': 'md5:e67873de1c88f360af1f0a4b84847a52',
+ },
+ 'playlist_count': 59,
}]
- def _fetch_page(self, playlist_id, series_version, page):
+ def _fetch_page(self, playlist_id, series_version, season_id, page):
+ query = {
+ 'seriesVersion': series_version,
+ 'offset': str(page * self._PAGE_SIZE),
+ 'order': 'seq',
+ 'limit': str(self._PAGE_SIZE),
+ }
+ if season_id:
+ query['seasonId'] = season_id
programs = self._call_api(
f'v1/video/series/{playlist_id}/programs', playlist_id,
note=f'Downloading page {page + 1}',
- query={
- 'seriesVersion': series_version,
- 'offset': str(page * self._PAGE_SIZE),
- 'order': 'seq',
- 'limit': str(self._PAGE_SIZE),
- })
+ query=query)
yield from (
self.url_result(f'https://abema.tv/video/episode/{x}')
for x in traverse_obj(programs, ('programs', ..., 'id')))
- def _entries(self, playlist_id, series_version):
+ def _entries(self, playlist_id, series_version, season_id):
return OnDemandPagedList(
- functools.partial(self._fetch_page, playlist_id, series_version),
+ functools.partial(self._fetch_page, playlist_id, series_version, season_id),
self._PAGE_SIZE)
def _real_extract(self, url):
- playlist_id = self._match_id(url)
+ playlist_id, season_id = self._match_valid_url(url).group('id', 'season')
series_info = self._call_api(f'v1/video/series/{playlist_id}', playlist_id)
return self.playlist_result(
- self._entries(playlist_id, series_info['version']), playlist_id=playlist_id,
+ self._entries(playlist_id, series_info['version'], season_id), playlist_id=playlist_id,
playlist_title=series_info.get('title'),
playlist_description=series_info.get('content'))
diff --git a/yt-dlp/yt_dlp/extractor/bilibili.py b/yt-dlp/yt_dlp/extractor/bilibili.py
index 33d9d92a0a..42b4e2d3c2 100644
--- a/yt-dlp/yt_dlp/extractor/bilibili.py
+++ b/yt-dlp/yt_dlp/extractor/bilibili.py
@@ -4,7 +4,9 @@ import hashlib
import itertools
import json
import math
+import random
import re
+import string
import time
import urllib.parse
import uuid
@@ -32,7 +34,6 @@ from ..utils import (
parse_qs,
parse_resolution,
qualities,
- sanitize_url,
smuggle_url,
srt_subtitles_timecode,
str_or_none,
@@ -1178,28 +1179,26 @@ class BilibiliSpaceBaseIE(BilibiliBaseIE):
class BilibiliSpaceVideoIE(BilibiliSpaceBaseIE):
- _VALID_URL = r'https?://space\.bilibili\.com/(?P\d+)(?P