From fd67e57d913cc01e4d2bf875d36fd8822b110921 Mon Sep 17 00:00:00 2001 From: "github-action[bot]" Date: Mon, 8 Dec 2025 19:42:39 +0100 Subject: [PATCH] Update On Mon Dec 8 19:42:39 CET 2025 --- .github/update.log | 1 + clash-meta/listener/sing_tun/dns.go | 8 +-- clash-meta/listener/sing_tun/prepare.go | 20 ++++++- clash-meta/listener/sing_tun/server.go | 12 +++- lede/include/kernel-5.10 | 4 +- lede/include/kernel-5.15 | 4 +- lede/include/kernel-5.4 | 4 +- lede/include/kernel-6.1 | 4 +- lede/include/kernel-6.12 | 4 +- lede/include/kernel-6.6 | 4 +- .../983-add-bcm-fullconenat-to-nft.patch | 4 +- mihomo/listener/sing_tun/dns.go | 8 +-- mihomo/listener/sing_tun/prepare.go | 20 ++++++- mihomo/listener/sing_tun/server.go | 12 +++- openwrt-packages/adguardhome/Makefile | 6 +- .../model/cbi/passwall/client/type/ray.lua | 9 +++ .../cbi/passwall/client/type/sing-box.lua | 12 ++++ .../luasrc/passwall/util_sing-box.lua | 14 ++++- .../luasrc/passwall/util_xray.lua | 13 +++- .../view/passwall/cbi/nodes_multiselect.htm | 8 +-- .../.github/workflows/build-msrv.yml | 18 +++--- shadowsocks-rust/Cargo.lock | 4 +- shadowsocks-rust/Cargo.toml | 4 +- shadowsocks-rust/bin/ssservice.rs | 5 +- shadowsocks-rust/clippy.toml | 2 +- .../crates/shadowsocks-service/Cargo.toml | 4 +- .../crates/shadowsocks-service/src/acl/mod.rs | 5 +- .../crates/shadowsocks-service/src/config.rs | 38 +++++------- .../src/local/loadbalancing/ping_balancer.rs | 5 +- .../src/local/loadbalancing/server_stat.rs | 2 +- .../src/local/net/udp/association.rs | 5 +- .../src/local/online_config/mod.rs | 5 +- .../src/local/redir/tcprelay/mod.rs | 5 +- .../src/local/redir/udprelay/mod.rs | 5 +- .../local/redir/udprelay/sys/unix/linux.rs | 5 +- .../local/redir/udprelay/sys/unix/macos.rs | 5 +- .../shadowsocks-service/src/local/tun/tcp.rs | 30 ++++------ .../shadowsocks-service/src/manager/server.rs | 5 +- .../src/server/udprelay.rs | 10 ++-- .../crates/shadowsocks/Cargo.toml | 2 +- .../crates/shadowsocks/src/config.rs | 10 ++-- .../crates/shadowsocks/src/net/sys/mod.rs | 5 +- .../shadowsocks/src/net/sys/unix/bsd/macos.rs | 5 +- .../shadowsocks/src/net/sys/unix/linux/mod.rs | 5 +- .../crates/shadowsocks/src/net/udp.rs | 40 +++++-------- .../src/relay/tcprelay/proxy_stream/server.rs | 10 ++-- shadowsocks-rust/src/service/local.rs | 10 ++-- shadowsocks-rust/src/service/manager.rs | 10 ++-- .../luci-static/resources/view/fchomo/node.js | 8 +-- small/luci-app-fchomo/po/templates/fchomo.pot | 18 ++++-- small/luci-app-fchomo/po/zh_Hans/fchomo.po | 20 ++++--- small/luci-app-fchomo/po/zh_Hant/fchomo.po | 20 ++++--- .../model/cbi/passwall/client/type/ray.lua | 9 +++ .../cbi/passwall/client/type/sing-box.lua | 12 ++++ .../luasrc/passwall/util_sing-box.lua | 14 ++++- .../luasrc/passwall/util_xray.lua | 13 +++- .../view/passwall/cbi/nodes_multiselect.htm | 15 +++-- small/v2ray-geodata/Makefile | 8 +-- .../ServiceLib/Handler/Fmt/ShadowsocksFmt.cs | 23 +++++-- .../Singbox/SingboxOutboundService.cs | 10 +++- .../common/platform/ctlcmd/attr_other.go | 10 ---- .../common/platform/ctlcmd/attr_windows.go | 12 ---- xray-core/common/platform/ctlcmd/ctlcmd.go | 50 ---------------- xray-core/common/platform/others.go | 9 --- xray-core/common/platform/platform.go | 13 ---- xray-core/common/platform/windows.go | 10 ---- xray-core/core/config.go | 6 +- xray-core/core/core.go | 2 +- xray-core/infra/conf/xray.go | 4 -- .../main/commands/all/convert/protobuf.go | 11 +--- xray-core/main/confloader/confloader.go | 12 ---- .../main/confloader/external/external.go | 11 ---- xray-core/proxy/proxy.go | 36 +++++------ yt-dlp/.github/workflows/build.yml | 18 +++--- yt-dlp/.github/workflows/challenge-tests.yml | 4 +- yt-dlp/.github/workflows/codeql.yml | 40 +++---------- yt-dlp/.github/workflows/core.yml | 4 +- yt-dlp/.github/workflows/download.yml | 8 +-- yt-dlp/.github/workflows/quick-test.yml | 8 +-- yt-dlp/.github/workflows/release-nightly.yml | 2 +- yt-dlp/.github/workflows/release.yml | 8 +-- yt-dlp/.github/workflows/test-workflows.yml | 8 +-- yt-dlp/CONTRIBUTING.md | 4 +- yt-dlp/CONTRIBUTORS | 11 +++- yt-dlp/Changelog.md | 60 ++++++++++++++++++- yt-dlp/Maintainers.md | 12 ++-- yt-dlp/Makefile | 6 +- yt-dlp/README.md | 22 +++---- yt-dlp/bundle/docker/linux/build.sh | 8 +-- yt-dlp/devscripts/changelog_override.json | 6 ++ yt-dlp/devscripts/install_deps.py | 26 ++++---- yt-dlp/devscripts/make_changelog.py | 8 ++- yt-dlp/pyproject.toml | 2 +- yt-dlp/supportedsites.md | 16 ++++- yt-dlp/test/test_utils.py | 3 + yt-dlp/yt_dlp/cookies.py | 9 ++- yt-dlp/yt_dlp/extractor/youtube/_video.py | 8 +-- .../extractor/youtube/jsc/_builtin/ejs.py | 4 +- .../youtube/jsc/_builtin/vendor/_info.py | 2 +- yt-dlp/yt_dlp/options.py | 2 +- yt-dlp/yt_dlp/postprocessor/ffmpeg.py | 4 +- yt-dlp/yt_dlp/utils/_jsruntime.py | 15 ++--- yt-dlp/yt_dlp/utils/_utils.py | 5 +- yt-dlp/yt_dlp/version.py | 6 +- 104 files changed, 588 insertions(+), 537 deletions(-) delete mode 100644 xray-core/common/platform/ctlcmd/attr_other.go delete mode 100644 xray-core/common/platform/ctlcmd/attr_windows.go delete mode 100644 xray-core/common/platform/ctlcmd/ctlcmd.go diff --git a/.github/update.log b/.github/update.log index 3b79ddc308..db7a8c1d68 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1205,3 +1205,4 @@ Update On Thu Dec 4 19:44:01 CET 2025 Update On Fri Dec 5 19:41:34 CET 2025 Update On Sat Dec 6 19:36:39 CET 2025 Update On Sun Dec 7 19:36:46 CET 2025 +Update On Mon Dec 8 19:42:31 CET 2025 diff --git a/clash-meta/listener/sing_tun/dns.go b/clash-meta/listener/sing_tun/dns.go index 82a9fdb6c1..317fefce2d 100644 --- a/clash-meta/listener/sing_tun/dns.go +++ b/clash-meta/listener/sing_tun/dns.go @@ -18,17 +18,11 @@ import ( "github.com/metacubex/sing/common/network" ) -type ListenerHandler struct { - *sing.ListenerHandler - DnsAdds []netip.AddrPort - DisableICMPForwarding bool -} - func (h *ListenerHandler) ShouldHijackDns(targetAddr netip.AddrPort) bool { if targetAddr.Addr().IsLoopback() && targetAddr.Port() == 53 { // cause by system stack return true } - for _, addrPort := range h.DnsAdds { + for _, addrPort := range h.DnsAddrPorts { if addrPort == targetAddr || (addrPort.Addr().IsUnspecified() && targetAddr.Port() == 53) { return true } diff --git a/clash-meta/listener/sing_tun/prepare.go b/clash-meta/listener/sing_tun/prepare.go index e59947b84b..e97771c445 100644 --- a/clash-meta/listener/sing_tun/prepare.go +++ b/clash-meta/listener/sing_tun/prepare.go @@ -2,6 +2,7 @@ package sing_tun import ( "context" + "net/netip" "time" "github.com/metacubex/mihomo/component/dialer" @@ -17,7 +18,7 @@ import ( func (h *ListenerHandler) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { switch network { case N.NetworkICMP: // our fork only send those type to PrepareConnection now - if h.DisableICMPForwarding || resolver.IsFakeIP(destination.Addr) { // skip fakeip and if ICMP handling is disabled + if h.DisableICMPForwarding || h.skipPingForwardingByAddr(destination.Addr) { // skip if ICMP handling is disabled or other condition log.Infoln("[ICMP] %s %s --> %s using fake ping echo", network, source, destination) return nil, nil } @@ -32,3 +33,20 @@ func (h *ListenerHandler) PrepareConnection(network string, source M.Socksaddr, } return nil, nil } + +func (h *ListenerHandler) skipPingForwardingByAddr(addr netip.Addr) bool { + for _, prefix := range h.Inet4Address { // skip in interface ipv4 range + if prefix.Contains(addr) { + return true + } + } + for _, prefix := range h.Inet6Address { // skip in interface ipv6 range + if prefix.Contains(addr) { + return true + } + } + if resolver.IsFakeIP(addr) { // skip in fakeIp pool + return true + } + return false +} diff --git a/clash-meta/listener/sing_tun/server.go b/clash-meta/listener/sing_tun/server.go index 87f413d431..41ba895b4a 100644 --- a/clash-meta/listener/sing_tun/server.go +++ b/clash-meta/listener/sing_tun/server.go @@ -67,6 +67,14 @@ type Listener struct { dnsServerIp []string } +type ListenerHandler struct { + *sing.ListenerHandler + DnsAddrPorts []netip.AddrPort + Inet4Address []netip.Prefix + Inet6Address []netip.Prefix + DisableICMPForwarding bool +} + var emptyAddressSet = []*netipx.IPSet{{}} func CalculateInterfaceName(name string) (tunName string) { @@ -268,7 +276,9 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis handler := &ListenerHandler{ ListenerHandler: h, - DnsAdds: dnsAdds, + DnsAddrPorts: dnsAdds, + Inet4Address: options.Inet4Address, + Inet6Address: options.Inet6Address, DisableICMPForwarding: options.DisableICMPForwarding, } l = &Listener{ diff --git a/lede/include/kernel-5.10 b/lede/include/kernel-5.10 index b90abf0a6f..4fdca787db 100644 --- a/lede/include/kernel-5.10 +++ b/lede/include/kernel-5.10 @@ -1,2 +1,2 @@ -LINUX_VERSION-5.10 = .246 -LINUX_KERNEL_HASH-5.10.246 = eb6b76c269d2dc09791638b10b9dcb9d79fd2abd45113a31fc03f68731caa875 +LINUX_VERSION-5.10 = .247 +LINUX_KERNEL_HASH-5.10.247 = 70c8b87ba1fcd8bfa663661934dc9bda92d0b5f3c0fc3197bb56399f69d9fe0c diff --git a/lede/include/kernel-5.15 b/lede/include/kernel-5.15 index c32d036773..9c8f79fe7b 100644 --- a/lede/include/kernel-5.15 +++ b/lede/include/kernel-5.15 @@ -1,2 +1,2 @@ -LINUX_VERSION-5.15 = .196 -LINUX_KERNEL_HASH-5.15.196 = 83157953598b026fb721c906c60dfdfd8e986f25ecb9910f3504f690e2770e05 +LINUX_VERSION-5.15 = .197 +LINUX_KERNEL_HASH-5.15.197 = fd218df8e2107a4443b6c29fef7f95aad167031e0fbdbc7a858ae8471360668a diff --git a/lede/include/kernel-5.4 b/lede/include/kernel-5.4 index 7238ef6f5f..980bb4c348 100644 --- a/lede/include/kernel-5.4 +++ b/lede/include/kernel-5.4 @@ -1,2 +1,2 @@ -LINUX_VERSION-5.4 = .301 -LINUX_KERNEL_HASH-5.4.301 = b7718766d060e6714bbe47004c71c360e844758f42fbf62cbaa5571119527962 +LINUX_VERSION-5.4 = .302 +LINUX_KERNEL_HASH-5.4.302 = ae6a3207f12aa4d6cfb0fa793ec9da4a6fcdfdcb57d869d63d6b77e3a8c1423d diff --git a/lede/include/kernel-6.1 b/lede/include/kernel-6.1 index b8fd4ba2b9..bca274dcdf 100644 --- a/lede/include/kernel-6.1 +++ b/lede/include/kernel-6.1 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.1 = .158 -LINUX_KERNEL_HASH-6.1.158 = ad068bfdb604ec0f4f7de385c8e7ab944008aa78a4aeeca94f53206e6726bfda +LINUX_VERSION-6.1 = .159 +LINUX_KERNEL_HASH-6.1.159 = 1f207ebe93980829ecc0a18b694816f22b715e9893767731651969a168342b9e diff --git a/lede/include/kernel-6.12 b/lede/include/kernel-6.12 index 73c2f33a10..c439fb2246 100644 --- a/lede/include/kernel-6.12 +++ b/lede/include/kernel-6.12 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.12 = .60 -LINUX_KERNEL_HASH-6.12.60 = a63096b2147411d683cecbf87622bb2ff4885bac2b3641d3d4f10250c89cdcf8 +LINUX_VERSION-6.12 = .61 +LINUX_KERNEL_HASH-6.12.61 = 1a69745105528676f12f29dc2494945d96cb23666dcc5223794abc22415f1735 diff --git a/lede/include/kernel-6.6 b/lede/include/kernel-6.6 index e5c3c5c302..7f4bc6fbfa 100644 --- a/lede/include/kernel-6.6 +++ b/lede/include/kernel-6.6 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.6 = .118 -LINUX_KERNEL_HASH-6.6.118 = 4bdddce35474afc8d26f74ebfbcd0e1045ecd15f69e60f53529dba143374b17d +LINUX_VERSION-6.6 = .119 +LINUX_KERNEL_HASH-6.6.119 = 3da09b980bb404cc28793479bb2d6c636522679215ffa65a04c893575253e5e8 diff --git a/lede/target/linux/generic/hack-6.1/983-add-bcm-fullconenat-to-nft.patch b/lede/target/linux/generic/hack-6.1/983-add-bcm-fullconenat-to-nft.patch index bb1cd62a36..dbedc1fbdf 100644 --- a/lede/target/linux/generic/hack-6.1/983-add-bcm-fullconenat-to-nft.patch +++ b/lede/target/linux/generic/hack-6.1/983-add-bcm-fullconenat-to-nft.patch @@ -53,8 +53,10 @@ if (priv->sreg_proto_min) { if (nft_dump_register(skb, NFTA_MASQ_REG_PROTO_MIN, priv->sreg_proto_min) || -@@ -112,6 +120,9 @@ static void nft_masq_eval(const struct n +@@ -112,6 +120,11 @@ static void nft_masq_eval(const struct n { ++ struct nft_masq *priv = nft_expr_priv(expr); ++ struct nf_nat_range2 range; switch (nft_pf(pkt)) { case NFPROTO_IPV4: + if (priv->fullcone) { diff --git a/mihomo/listener/sing_tun/dns.go b/mihomo/listener/sing_tun/dns.go index 82a9fdb6c1..317fefce2d 100644 --- a/mihomo/listener/sing_tun/dns.go +++ b/mihomo/listener/sing_tun/dns.go @@ -18,17 +18,11 @@ import ( "github.com/metacubex/sing/common/network" ) -type ListenerHandler struct { - *sing.ListenerHandler - DnsAdds []netip.AddrPort - DisableICMPForwarding bool -} - func (h *ListenerHandler) ShouldHijackDns(targetAddr netip.AddrPort) bool { if targetAddr.Addr().IsLoopback() && targetAddr.Port() == 53 { // cause by system stack return true } - for _, addrPort := range h.DnsAdds { + for _, addrPort := range h.DnsAddrPorts { if addrPort == targetAddr || (addrPort.Addr().IsUnspecified() && targetAddr.Port() == 53) { return true } diff --git a/mihomo/listener/sing_tun/prepare.go b/mihomo/listener/sing_tun/prepare.go index e59947b84b..e97771c445 100644 --- a/mihomo/listener/sing_tun/prepare.go +++ b/mihomo/listener/sing_tun/prepare.go @@ -2,6 +2,7 @@ package sing_tun import ( "context" + "net/netip" "time" "github.com/metacubex/mihomo/component/dialer" @@ -17,7 +18,7 @@ import ( func (h *ListenerHandler) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { switch network { case N.NetworkICMP: // our fork only send those type to PrepareConnection now - if h.DisableICMPForwarding || resolver.IsFakeIP(destination.Addr) { // skip fakeip and if ICMP handling is disabled + if h.DisableICMPForwarding || h.skipPingForwardingByAddr(destination.Addr) { // skip if ICMP handling is disabled or other condition log.Infoln("[ICMP] %s %s --> %s using fake ping echo", network, source, destination) return nil, nil } @@ -32,3 +33,20 @@ func (h *ListenerHandler) PrepareConnection(network string, source M.Socksaddr, } return nil, nil } + +func (h *ListenerHandler) skipPingForwardingByAddr(addr netip.Addr) bool { + for _, prefix := range h.Inet4Address { // skip in interface ipv4 range + if prefix.Contains(addr) { + return true + } + } + for _, prefix := range h.Inet6Address { // skip in interface ipv6 range + if prefix.Contains(addr) { + return true + } + } + if resolver.IsFakeIP(addr) { // skip in fakeIp pool + return true + } + return false +} diff --git a/mihomo/listener/sing_tun/server.go b/mihomo/listener/sing_tun/server.go index 87f413d431..41ba895b4a 100644 --- a/mihomo/listener/sing_tun/server.go +++ b/mihomo/listener/sing_tun/server.go @@ -67,6 +67,14 @@ type Listener struct { dnsServerIp []string } +type ListenerHandler struct { + *sing.ListenerHandler + DnsAddrPorts []netip.AddrPort + Inet4Address []netip.Prefix + Inet6Address []netip.Prefix + DisableICMPForwarding bool +} + var emptyAddressSet = []*netipx.IPSet{{}} func CalculateInterfaceName(name string) (tunName string) { @@ -268,7 +276,9 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis handler := &ListenerHandler{ ListenerHandler: h, - DnsAdds: dnsAdds, + DnsAddrPorts: dnsAdds, + Inet4Address: options.Inet4Address, + Inet6Address: options.Inet6Address, DisableICMPForwarding: options.DisableICMPForwarding, } l = &Listener{ diff --git a/openwrt-packages/adguardhome/Makefile b/openwrt-packages/adguardhome/Makefile index 32630469bb..1c00a8c17c 100644 --- a/openwrt-packages/adguardhome/Makefile +++ b/openwrt-packages/adguardhome/Makefile @@ -6,12 +6,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=adguardhome -PKG_VERSION:=0.107.70 +PKG_VERSION:=0.107.71 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/AdguardTeam/AdGuardHome/tar.gz/v$(PKG_VERSION)? -PKG_HASH:=5eb0c7076ab7b007f0e5afbaffc260d8950b5248bd04407464a2b2f0169694ba +PKG_HASH:=f3dde5da6ba48270ac25bd2f501c4ce1af54ddeef93fcd84ef3a8270cec9539f PKG_BUILD_DIR:=$(BUILD_DIR)/AdGuardHome-$(PKG_VERSION) PKG_LICENSE:=GPL-3.0-only @@ -58,7 +58,7 @@ define Download/adguardhome-frontend URL:=https://github.com/AdguardTeam/AdGuardHome/releases/download/v$(PKG_VERSION)/ URL_FILE:=AdGuardHome_frontend.tar.gz FILE:=$(FRONTEND_FILE) - HASH:=127658a4155d429fc8f7c6a4408107662a963b39f6db869f13dcd40aff12e6eb + HASH:=51b229a5dff010c17bd8894bbf4291907e93708c366801e32181be7f37dd4488 endef define Build/Prepare diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua index d3a707e1d2..5691f9e0cb 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua @@ -511,6 +511,9 @@ o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path")) o.placeholder = "/" o:depends({ [_n("tcp_guise")] = "http" }) +o = s:option(Value, _n("tcp_guise_http_user_agent"), translate("User-Agent")) +o:depends({ [_n("tcp_guise")] = "http" }) + -- [[ mKCP部分 ]]-- o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)
dns: Disguising traffic as DNS requests.')) @@ -558,6 +561,9 @@ o = s:option(Value, _n("ws_path"), translate("WebSocket Path")) o.placeholder = "/" o:depends({ [_n("transport")] = "ws" }) +o = s:option(Value, _n("ws_user_agent"), translate("User-Agent")) +o:depends({ [_n("transport")] = "ws" }) + o = s:option(Value, _n("ws_heartbeatPeriod"), translate("HeartbeatPeriod(second)")) o.datatype = "integer" o:depends({ [_n("transport")] = "ws" }) @@ -598,6 +604,9 @@ o = s:option(Value, _n("httpupgrade_path"), translate("HttpUpgrade Path")) o.placeholder = "/" o:depends({ [_n("transport")] = "httpupgrade" }) +o = s:option(Value, _n("httpupgrade_user_agent"), translate("User-Agent")) +o:depends({ [_n("transport")] = "httpupgrade" }) + -- [[ XHTTP部分 ]]-- o = s:option(ListValue, _n("xhttp_mode"), "XHTTP " .. translate("Mode")) o:depends({ [_n("transport")] = "xhttp" }) diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua index 5e27be824c..2452060385 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua @@ -609,6 +609,9 @@ o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path")) o.placeholder = "/" o:depends({ [_n("tcp_guise")] = "http" }) +o = s:option(Value, _n("tcp_guise_http_user_agent"), translate("User-Agent")) +o:depends({ [_n("tcp_guise")] = "http" }) + -- [[ HTTP部分 ]]-- o = s:option(DynamicList, _n("http_host"), translate("HTTP Host")) o:depends({ [_n("transport")] = "http" }) @@ -617,6 +620,9 @@ o = s:option(Value, _n("http_path"), translate("HTTP Path")) o.placeholder = "/" o:depends({ [_n("transport")] = "http" }) +o = s:option(Value, _n("http_user_agent"), translate("User-Agent")) +o:depends({ [_n("transport")] = "http" }) + o = s:option(Flag, _n("http_h2_health_check"), translate("Health check")) o:depends({ [_n("tls")] = true, [_n("transport")] = "http" }) @@ -636,6 +642,9 @@ o = s:option(Value, _n("ws_path"), translate("WebSocket Path")) o.placeholder = "/" o:depends({ [_n("transport")] = "ws" }) +o = s:option(Value, _n("ws_user_agent"), translate("User-Agent")) +o:depends({ [_n("transport")] = "ws" }) + o = s:option(Flag, _n("ws_enableEarlyData"), translate("Enable early data")) o:depends({ [_n("transport")] = "ws" }) @@ -654,6 +663,9 @@ o = s:option(Value, _n("httpupgrade_path"), translate("HTTPUpgrade Path")) o.placeholder = "/" o:depends({ [_n("transport")] = "httpupgrade" }) +o = s:option(Value, _n("httpupgrade_user_agent"), translate("User-Agent")) +o:depends({ [_n("transport")] = "httpupgrade" }) + -- [[ gRPC部分 ]]-- o = s:option(Value, _n("grpc_serviceName"), "ServiceName") o:depends({ [_n("transport")] = "grpc" }) diff --git a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua index 6ea08be46d..04c9618265 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -206,6 +206,9 @@ function gen_outbound(flag, node, tag, proxy_table) local first = node.tcp_guise_http_path[1] return (first == "" or not first) and "/" or first end)() or "/", + headers = { + ["User-Agent"] = node.tcp_guise_http_user_agent or nil + }, idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil, ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil, } @@ -217,6 +220,9 @@ function gen_outbound(flag, node, tag, proxy_table) type = "http", host = node.http_host or {}, path = node.http_path or "/", + headers = { + ["User-Agent"] = node.http_user_agent or nil + }, idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil, ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil, } @@ -227,7 +233,10 @@ function gen_outbound(flag, node, tag, proxy_table) v2ray_transport = { type = "ws", path = node.ws_path or "/", - headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil, + headers = { + Host = node.ws_host or nil, + ["User-Agent"] = node.ws_user_agent or nil + }, max_early_data = tonumber(node.ws_maxEarlyData) or nil, early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil --要与 Xray-core 兼容,请将其设置为 Sec-WebSocket-Protocol。它需要与服务器保持一致。 } @@ -238,6 +247,9 @@ function gen_outbound(flag, node, tag, proxy_table) type = "httpupgrade", host = node.httpupgrade_host, path = node.httpupgrade_path or "/", + headers = { + ["User-Agent"] = node.httpupgrade_user_agent or nil + } } end diff --git a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua index 385c04ffb9..77be6be021 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -179,7 +179,8 @@ function gen_outbound(flag, node, tag, proxy_table) return r end)() or {"/"}, headers = { - Host = node.tcp_guise_http_host or {} + Host = node.tcp_guise_http_host or {}, + ["User-Agent"] = node.tcp_guise_http_user_agent and {node.tcp_guise_http_user_agent} or nil } } or nil } @@ -200,7 +201,10 @@ function gen_outbound(flag, node, tag, proxy_table) } or nil, wsSettings = (node.transport == "ws") and { path = node.ws_path or "/", - host = node.ws_host or nil, + headers = { + Host = node.ws_host or nil, + ["User-Agent"] = node.ws_user_agent or nil + }, maxEarlyData = tonumber(node.ws_maxEarlyData) or nil, earlyDataHeaderName = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil, heartbeatPeriod = tonumber(node.ws_heartbeatPeriod) or nil @@ -215,7 +219,10 @@ function gen_outbound(flag, node, tag, proxy_table) } or nil, httpupgradeSettings = (node.transport == "httpupgrade") and { path = node.httpupgrade_path or "/", - host = node.httpupgrade_host + host = node.httpupgrade_host, + headers = { + ["User-Agent"] = node.httpupgrade_user_agent or nil + } } or nil, xhttpSettings = (node.transport == "xhttp") and { mode = node.xhttp_mode or "auto", diff --git a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multiselect.htm b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multiselect.htm index 3f758c712f..b5b18f67e4 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multiselect.htm +++ b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multiselect.htm @@ -65,14 +65,14 @@ end } -
+
+ style="width:100%;padding:6px;margin-bottom:8px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;max-height:36px;" />
    @@ -85,7 +85,7 @@ end style="cursor:pointer;padding:6px;background:#f0f0f0;border-radius:4px;margin-bottom:4px;display:flex;align-items:center;white-space:nowrap;"> <%=gname%> - + (0/<%=#items%>)
@@ -93,7 +93,7 @@ end
    <% for _, item in ipairs(items) do %> -
  • +
  • ExitCode { .about("A fast tunnel proxy that helps you bypass firewalls. (https://shadowsocks.org)"); // Allow running `ssservice` as symlink of `sslocal`, `ssserver` and `ssmanager` - if let Some(program_path) = env::args().next() { - if let Some(program_name) = Path::new(&program_path).file_name() { + if let Some(program_path) = env::args().next() + && let Some(program_name) = Path::new(&program_path).file_name() { match program_name.to_str() { Some("sslocal") => return local::main(&local::define_command_line_options(app).get_matches()), Some("ssserver") => return server::main(&server::define_command_line_options(app).get_matches()), @@ -27,7 +27,6 @@ fn main() -> ExitCode { _ => {} } } - } let matches = app .subcommand_required(true) diff --git a/shadowsocks-rust/clippy.toml b/shadowsocks-rust/clippy.toml index b339f7c33d..f3322b5fd2 100644 --- a/shadowsocks-rust/clippy.toml +++ b/shadowsocks-rust/clippy.toml @@ -1 +1 @@ -msrv = "1.85" +msrv = "1.88" diff --git a/shadowsocks-rust/crates/shadowsocks-service/Cargo.toml b/shadowsocks-rust/crates/shadowsocks-service/Cargo.toml index bb243eafc5..b99bd8bd57 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/Cargo.toml +++ b/shadowsocks-rust/crates/shadowsocks-service/Cargo.toml @@ -9,7 +9,7 @@ documentation = "https://docs.rs/shadowsocks-service" keywords = ["shadowsocks", "proxy", "socks", "socks5", "firewall"] license = "MIT" edition = "2024" -rust-version = "1.85" +rust-version = "1.88" [badges] maintenance = { status = "passively-maintained" } @@ -195,7 +195,7 @@ smoltcp = { version = "0.12", optional = true, default-features = false, feature ] } serde = { version = "1.0", features = ["derive"] } -json5 = "1.2" +json5 = "1.3" serde_json = "1.0" bson = { version = "3.0.0", features = ["serde"], optional = true } diff --git a/shadowsocks-rust/crates/shadowsocks-service/src/acl/mod.rs b/shadowsocks-rust/crates/shadowsocks-service/src/acl/mod.rs index cf470754af..c8e1e4ac6b 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/src/acl/mod.rs +++ b/shadowsocks-rust/crates/shadowsocks-service/src/acl/mod.rs @@ -208,15 +208,14 @@ impl ParsingRules { return; } } - } else if let Some(set_rule) = caps.get(2) { - if let Ok(set_rule) = str::from_utf8(set_rule.as_bytes()) { + } else if let Some(set_rule) = caps.get(2) + && let Ok(set_rule) = str::from_utf8(set_rule.as_bytes()) { let set_rule = set_rule.replace("\\.", "."); if self.add_set_rule_inner(&set_rule).is_ok() { trace!("REGEX-RULE {} => SET-RULE {}", rule, set_rule); return; } } - } } trace!("REGEX-RULE {}", rule); diff --git a/shadowsocks-rust/crates/shadowsocks-service/src/config.rs b/shadowsocks-rust/crates/shadowsocks-service/src/config.rs index 1ca2819c36..a449e94226 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/src/config.rs +++ b/shadowsocks-rust/crates/shadowsocks-service/src/config.rs @@ -2388,9 +2388,9 @@ impl Config { } // Security - if let Some(sec) = config.security { - if let Some(replay_attack) = sec.replay_attack { - if let Some(policy) = replay_attack.policy { + if let Some(sec) = config.security + && let Some(replay_attack) = sec.replay_attack + && let Some(policy) = replay_attack.policy { match policy.parse::() { Ok(p) => nconfig.security.replay_attack.policy = p, Err(..) => { @@ -2399,8 +2399,6 @@ impl Config { } } } - } - } if let Some(balancer) = config.balancer { nconfig.balancer = BalancerConfig { @@ -2620,19 +2618,17 @@ impl Config { } // Balancer related checks - if let Some(rtt) = self.balancer.max_server_rtt { - if rtt.as_secs() == 0 { + if let Some(rtt) = self.balancer.max_server_rtt + && rtt.as_secs() == 0 { let err = Error::new(ErrorKind::Invalid, "balancer.max_server_rtt must be > 0", None); return Err(err); } - } - if let Some(intv) = self.balancer.check_interval { - if intv.as_secs() == 0 { + if let Some(intv) = self.balancer.check_interval + && intv.as_secs() == 0 { let err = Error::new(ErrorKind::Invalid, "balancer.check_interval must be > 0", None); return Err(err); } - } } if self.config_type.is_server() && self.server.is_empty() { @@ -2667,12 +2663,11 @@ impl Config { let server = &inst.config; // Plugin shouldn't be an empty string - if let Some(plugin) = server.plugin() { - if plugin.plugin.trim().is_empty() { + if let Some(plugin) = server.plugin() + && plugin.plugin.trim().is_empty() { let err = Error::new(ErrorKind::Malformed, "`plugin` shouldn't be an empty string", None); return Err(err); } - } // Server's domain name shouldn't be an empty string match server.addr() { @@ -3072,14 +3067,13 @@ impl fmt::Display for Config { jconf.mode = Some(m.mode.to_string()); } - if jconf.method.is_none() { - if let Some(ref m) = m.method { + if jconf.method.is_none() + && let Some(ref m) = m.method { jconf.method = Some(m.to_string()); } - } - if jconf.plugin.is_none() { - if let Some(ref p) = m.plugin { + if jconf.plugin.is_none() + && let Some(ref p) = m.plugin { jconf.plugin = Some(p.plugin.clone()); if let Some(ref o) = p.plugin_opts { jconf.plugin_opts = Some(o.clone()); @@ -3088,7 +3082,6 @@ impl fmt::Display for Config { jconf.plugin_args = Some(p.plugin_args.clone()); } } - } } if self.no_delay { @@ -3194,8 +3187,8 @@ impl fmt::Display for Config { /// If value is in format `${VAR_NAME}` then it will try to read from `VAR_NAME` environment variable. /// It will return the original value if fails to read `${VAR_NAME}`. pub fn read_variable_field_value(value: &str) -> Cow<'_, str> { - if let Some(left_over) = value.strip_prefix("${") { - if let Some(var_name) = left_over.strip_suffix('}') { + if let Some(left_over) = value.strip_prefix("${") + && let Some(var_name) = left_over.strip_suffix('}') { match env::var(var_name) { Ok(value) => return value.into(), Err(err) => { @@ -3206,7 +3199,6 @@ pub fn read_variable_field_value(value: &str) -> Cow<'_, str> { } } } - } value.into() } diff --git a/shadowsocks-rust/crates/shadowsocks-service/src/local/loadbalancing/ping_balancer.rs b/shadowsocks-rust/crates/shadowsocks-service/src/local/loadbalancing/ping_balancer.rs index 8f99c836c1..7a538f9a1c 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/src/local/loadbalancing/ping_balancer.rs +++ b/shadowsocks-rust/crates/shadowsocks-service/src/local/loadbalancing/ping_balancer.rs @@ -163,11 +163,10 @@ impl PingBalancerBuilder { } pub async fn build(self) -> io::Result { - if let Some(intv) = self.check_best_interval { - if intv > self.check_interval { + if let Some(intv) = self.check_best_interval + && intv > self.check_interval { return Err(io::Error::other("check_interval must be >= check_best_interval")); } - } let (shared_context, task_abortable) = PingBalancerContext::new( self.servers, diff --git a/shadowsocks-rust/crates/shadowsocks-service/src/local/loadbalancing/server_stat.rs b/shadowsocks-rust/crates/shadowsocks-service/src/local/loadbalancing/server_stat.rs index dd80cd7a09..5e79b66577 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/src/local/loadbalancing/server_stat.rs +++ b/shadowsocks-rust/crates/shadowsocks-service/src/local/loadbalancing/server_stat.rs @@ -193,7 +193,7 @@ impl ServerStat { vlat_abs_diff.sort_unstable(); let abs_diff_median_mid = vlat_abs_diff.len() / 2; - self.data.latency_mad = if vlat_abs_diff.len() % 2 == 0 { + self.data.latency_mad = if vlat_abs_diff.len().is_multiple_of(2) { (vlat_abs_diff[abs_diff_median_mid] + vlat_abs_diff[abs_diff_median_mid - 1]) / 2 } else { vlat_abs_diff[abs_diff_median_mid] diff --git a/shadowsocks-rust/crates/shadowsocks-service/src/local/net/udp/association.rs b/shadowsocks-rust/crates/shadowsocks-service/src/local/net/udp/association.rs index a2f4648266..066b0fb0eb 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/src/local/net/udp/association.rs +++ b/shadowsocks-rust/crates/shadowsocks-service/src/local/net/udp/association.rs @@ -515,12 +515,11 @@ where } }; - if UDP_SOCKET_SUPPORT_DUAL_STACK { - if let SocketAddr::V4(saddr) = target_addr { + if UDP_SOCKET_SUPPORT_DUAL_STACK + && let SocketAddr::V4(saddr) = target_addr { let mapped_ip = saddr.ip().to_ipv6_mapped(); target_addr = SocketAddr::V6(SocketAddrV6::new(mapped_ip, saddr.port(), 0, 0)); } - } let n = socket.send_to(data, target_addr).await?; if n != data.len() { diff --git a/shadowsocks-rust/crates/shadowsocks-service/src/local/online_config/mod.rs b/shadowsocks-rust/crates/shadowsocks-service/src/local/online_config/mod.rs index 94300be9b3..277493f92e 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/src/local/online_config/mod.rs +++ b/shadowsocks-rust/crates/shadowsocks-service/src/local/online_config/mod.rs @@ -214,15 +214,14 @@ impl OnlineConfigService { // Check plugin whitelist if let Some(ref allowed_plugins) = self.allowed_plugins { for server in &online_config.server { - if let Some(plugin) = server.config.plugin() { - if !allowed_plugins.contains(&plugin.plugin) { + if let Some(plugin) = server.config.plugin() + && !allowed_plugins.contains(&plugin.plugin) { error!( "server-loader task found not allowed plugin: {}, url: {}", plugin.plugin, self.config_url ); return Err(io::Error::other(format!("not allowed plugin: {}", plugin.plugin))); } - } } } diff --git a/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs b/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs index 9a844ffd94..75a95b8a24 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs +++ b/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs @@ -63,11 +63,10 @@ async fn handle_redir_client( // Get forward address from socket // // Try to convert IPv4 mapped IPv6 address for dual-stack mode. - if let SocketAddr::V6(ref a) = daddr { - if let Some(v4) = to_ipv4_mapped(a.ip()) { + if let SocketAddr::V6(ref a) = daddr + && let Some(v4) = to_ipv4_mapped(a.ip()) { daddr = SocketAddr::new(IpAddr::from(v4), a.port()); } - } let target_addr = Address::from(daddr); establish_client_tcp_redir(context, balancer, s, peer_addr, &target_addr).await } diff --git a/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs b/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs index a6135377ea..02da0556fb 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs +++ b/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs @@ -296,11 +296,10 @@ impl RedirUdpServer { } // Try to convert IPv4 mapped IPv6 address for dual-stack mode. - if let SocketAddr::V6(ref a) = dst { - if let Some(v4) = to_ipv4_mapped(a.ip()) { + if let SocketAddr::V6(ref a) = dst + && let Some(v4) = to_ipv4_mapped(a.ip()) { dst = SocketAddr::new(IpAddr::from(v4), a.port()); } - } if let Err(err) = manager.send_to(src, Address::from(dst), pkt).await { debug!( diff --git a/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/linux.rs b/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/linux.rs index a54ab75858..84579d0d97 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/linux.rs +++ b/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/linux.rs @@ -71,8 +71,8 @@ impl UdpRedirSocket { socket.set_nonblocking(true)?; socket.set_reuse_address(true)?; - if reuse_port { - if let Err(err) = socket.set_reuse_port(true) { + if reuse_port + && let Err(err) = socket.set_reuse_port(true) { if let Some(libc::ENOPROTOOPT) = err.raw_os_error() { // SO_REUSEPORT is supported after 3.9 trace!("failed to set SO_REUSEPORT, error: {}", err); @@ -81,7 +81,6 @@ impl UdpRedirSocket { return Err(err); } } - } let sock_addr = SockAddr::from(addr); diff --git a/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/macos.rs b/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/macos.rs index 5bafa099c0..cc6bc65625 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/macos.rs +++ b/shadowsocks-rust/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/macos.rs @@ -52,8 +52,8 @@ impl UdpRedirSocket { socket.set_nonblocking(true)?; socket.set_reuse_address(true)?; - if reuse_port { - if let Err(err) = socket.set_reuse_port(true) { + if reuse_port + && let Err(err) = socket.set_reuse_port(true) { if let Some(libc::ENOPROTOOPT) = err.raw_os_error() { trace!("failed to set SO_REUSEPORT, error: {}", err); } else { @@ -61,7 +61,6 @@ impl UdpRedirSocket { return Err(err); } } - } let sock_addr = SockAddr::from(addr); diff --git a/shadowsocks-rust/crates/shadowsocks-service/src/local/tun/tcp.rs b/shadowsocks-rust/crates/shadowsocks-service/src/local/tun/tcp.rs index 58852881b8..dcbcc49ea2 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/src/local/tun/tcp.rs +++ b/shadowsocks-rust/crates/shadowsocks-service/src/local/tun/tcp.rs @@ -159,11 +159,10 @@ impl AsyncRead for TcpConnection { } // Nothing could be read. Wait for notify. - if let Some(old_waker) = control.recv_waker.replace(cx.waker().clone()) { - if !old_waker.will_wake(cx.waker()) { + if let Some(old_waker) = control.recv_waker.replace(cx.waker().clone()) + && !old_waker.will_wake(cx.waker()) { old_waker.wake(); } - } return Poll::Pending; } @@ -191,11 +190,10 @@ impl AsyncWrite for TcpConnection { // Write to buffer if control.send_buffer.is_full() { - if let Some(old_waker) = control.send_waker.replace(cx.waker().clone()) { - if !old_waker.will_wake(cx.waker()) { + if let Some(old_waker) = control.send_waker.replace(cx.waker().clone()) + && !old_waker.will_wake(cx.waker()) { old_waker.wake(); } - } return Poll::Pending; } @@ -224,11 +222,10 @@ impl AsyncWrite for TcpConnection { control.send_state = TcpSocketState::Close; } - if let Some(old_waker) = control.send_waker.replace(cx.waker().clone()) { - if !old_waker.will_wake(cx.waker()) { + if let Some(old_waker) = control.send_waker.replace(cx.waker().clone()) + && !old_waker.will_wake(cx.waker()) { old_waker.wake(); } - } self.manager_notify.notify(); Poll::Pending @@ -421,11 +418,10 @@ impl TcpTun { wake_receiver = true; } - if wake_receiver && control.recv_waker.is_some() { - if let Some(waker) = control.recv_waker.take() { + if wake_receiver && control.recv_waker.is_some() + && let Some(waker) = control.recv_waker.take() { waker.wake(); } - } // Check if writable let mut wake_sender = false; @@ -456,11 +452,10 @@ impl TcpTun { } } - if wake_sender && control.send_waker.is_some() { - if let Some(waker) = control.send_waker.take() { + if wake_sender && control.send_waker.is_some() + && let Some(waker) = control.send_waker.take() { waker.wake(); } - } } for socket_handle in sockets_to_remove { @@ -601,11 +596,10 @@ async fn handle_redir_client( // Get forward address from socket // // Try to convert IPv4 mapped IPv6 address for dual-stack mode. - if let SocketAddr::V6(ref a) = daddr { - if let Some(v4) = to_ipv4_mapped(a.ip()) { + if let SocketAddr::V6(ref a) = daddr + && let Some(v4) = to_ipv4_mapped(a.ip()) { daddr = SocketAddr::new(IpAddr::from(v4), a.port()); } - } let target_addr = Address::from(daddr); establish_client_tcp_redir(context, balancer, s, peer_addr, &target_addr).await } diff --git a/shadowsocks-rust/crates/shadowsocks-service/src/manager/server.rs b/shadowsocks-rust/crates/shadowsocks-service/src/manager/server.rs index 2f7ff1d23b..c8d939220c 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/src/manager/server.rs +++ b/shadowsocks-rust/crates/shadowsocks-service/src/manager/server.rs @@ -329,8 +329,8 @@ impl Manager { }; let pid_path = self.server_pid_path(port); - if pid_path.exists() { - if let Ok(mut pid_file) = File::open(&pid_path) { + if pid_path.exists() + && let Ok(mut pid_file) = File::open(&pid_path) { let mut pid_content = String::new(); if pid_file.read_to_string(&mut pid_content).is_ok() { let pid_content = pid_content.trim(); @@ -346,7 +346,6 @@ impl Manager { } } } - } let server_config_path = self.server_config_path(port); diff --git a/shadowsocks-rust/crates/shadowsocks-service/src/server/udprelay.rs b/shadowsocks-rust/crates/shadowsocks-service/src/server/udprelay.rs index d8e31bf128..4ee0bee9d2 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/src/server/udprelay.rs +++ b/shadowsocks-rust/crates/shadowsocks-service/src/server/udprelay.rs @@ -593,15 +593,14 @@ impl UdpAssociationContext { data: &[u8], control: &Option, ) { - if let Some(ref mut session) = self.client_session { - if peer_addr != self.peer_addr { + if let Some(ref mut session) = self.client_session + && peer_addr != self.peer_addr { debug!( "udp relay for {} changed to {}, session: {:?}", self.peer_addr, peer_addr, session.client_session_id ); self.peer_addr = peer_addr; } - } trace!( "udp relay {} -> {} with {} bytes, control: {:?}", @@ -737,11 +736,10 @@ impl UdpAssociationContext { // It is an undefined behavior in shadowsocks' protocol about how to handle IPv4-mapped-IPv6. // But for some implementations, they may expect the target address to be IPv4, because // the peer address is IPv4 when calling `sendto`. - if let Address::SocketAddress(SocketAddr::V6(ref v6)) = addr { - if let Some(v4) = to_ipv4_mapped(v6.ip()) { + if let Address::SocketAddress(SocketAddr::V6(ref v6)) = addr + && let Some(v4) = to_ipv4_mapped(v6.ip()) { addr = Address::SocketAddress(SocketAddr::new(v4.into(), v6.port())); } - } match self.client_session { None => { diff --git a/shadowsocks-rust/crates/shadowsocks/Cargo.toml b/shadowsocks-rust/crates/shadowsocks/Cargo.toml index d00c5c4b96..70ae2323a1 100644 --- a/shadowsocks-rust/crates/shadowsocks/Cargo.toml +++ b/shadowsocks-rust/crates/shadowsocks/Cargo.toml @@ -9,7 +9,7 @@ documentation = "https://docs.rs/shadowsocks-core" keywords = ["shadowsocks", "proxy", "socks", "socks5", "firewall"] license = "MIT" edition = "2024" -rust-version = "1.85" +rust-version = "1.88" [badges] maintenance = { status = "passively-maintained" } diff --git a/shadowsocks-rust/crates/shadowsocks/src/config.rs b/shadowsocks-rust/crates/shadowsocks/src/config.rs index fb4b0ace7d..4491c75570 100644 --- a/shadowsocks-rust/crates/shadowsocks/src/config.rs +++ b/shadowsocks-rust/crates/shadowsocks/src/config.rs @@ -695,21 +695,19 @@ impl ServerConfig { /// Get server's TCP external address pub fn tcp_external_addr(&self) -> &ServerAddr { - if let Some(plugin) = self.plugin() { - if plugin.plugin_mode.enable_tcp() { + if let Some(plugin) = self.plugin() + && plugin.plugin_mode.enable_tcp() { return self.plugin_addr.as_ref().unwrap_or(&self.addr); } - } &self.addr } /// Get server's UDP external address pub fn udp_external_addr(&self) -> &ServerAddr { - if let Some(plugin) = self.plugin() { - if plugin.plugin_mode.enable_udp() { + if let Some(plugin) = self.plugin() + && plugin.plugin_mode.enable_udp() { return self.plugin_addr.as_ref().unwrap_or(&self.addr); } - } &self.addr } diff --git a/shadowsocks-rust/crates/shadowsocks/src/net/sys/mod.rs b/shadowsocks-rust/crates/shadowsocks/src/net/sys/mod.rs index 0ebafcf65a..7e2b1ade03 100644 --- a/shadowsocks-rust/crates/shadowsocks/src/net/sys/mod.rs +++ b/shadowsocks-rust/crates/shadowsocks/src/net/sys/mod.rs @@ -167,15 +167,14 @@ static IP_STACK_CAPABILITIES: LazyLock = LazyLock::new(|| { } // Check IPv6 (::1) - if let Ok(ipv6_socket) = Socket::new(Domain::IPV6, Type::STREAM, Some(Protocol::TCP)) { - if ipv6_socket.set_only_v6(true).is_ok() { + if let Ok(ipv6_socket) = Socket::new(Domain::IPV6, Type::STREAM, Some(Protocol::TCP)) + && ipv6_socket.set_only_v6(true).is_ok() { let local_host = SockAddr::from(SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 0)); if ipv6_socket.bind(&local_host).is_ok() { caps.support_ipv6 = true; debug!("IpStackCapability support_ipv6=true"); } } - } // Check IPv4-mapped-IPv6 (127.0.0.1) if check_ipv4_mapped_ipv6_capability().is_ok() { diff --git a/shadowsocks-rust/crates/shadowsocks/src/net/sys/unix/bsd/macos.rs b/shadowsocks-rust/crates/shadowsocks/src/net/sys/unix/bsd/macos.rs index 9b1f277cbe..b760f9a751 100644 --- a/shadowsocks-rust/crates/shadowsocks/src/net/sys/unix/bsd/macos.rs +++ b/shadowsocks-rust/crates/shadowsocks/src/net/sys/unix/bsd/macos.rs @@ -390,11 +390,10 @@ pub async fn bind_outbound_udp_socket(bind_addr: &SocketAddr, config: &ConnectOp UdpSocket::from_std(socket.into())? }; - if !config.udp.allow_fragmentation { - if let Err(err) = set_disable_ip_fragmentation(af, &socket) { + if !config.udp.allow_fragmentation + && let Err(err) = set_disable_ip_fragmentation(af, &socket) { warn!("failed to disable IP fragmentation, error: {}", err); } - } // Set IP_BOUND_IF for BSD-like if let Some(ref iface) = config.bind_interface { diff --git a/shadowsocks-rust/crates/shadowsocks/src/net/sys/unix/linux/mod.rs b/shadowsocks-rust/crates/shadowsocks/src/net/sys/unix/linux/mod.rs index 92b24fc710..b77c7bf5c9 100644 --- a/shadowsocks-rust/crates/shadowsocks/src/net/sys/unix/linux/mod.rs +++ b/shadowsocks-rust/crates/shadowsocks/src/net/sys/unix/linux/mod.rs @@ -310,11 +310,10 @@ pub async fn bind_outbound_udp_socket(bind_addr: &SocketAddr, config: &ConnectOp UdpSocket::from_std(socket.into())? }; - if !config.udp.allow_fragmentation { - if let Err(err) = set_disable_ip_fragmentation(af, &socket) { + if !config.udp.allow_fragmentation + && let Err(err) = set_disable_ip_fragmentation(af, &socket) { warn!("failed to disable IP fragmentation, error: {}", err); } - } // Any traffic except localhost should be protected // This is a workaround for VPNService diff --git a/shadowsocks-rust/crates/shadowsocks/src/net/udp.rs b/shadowsocks-rust/crates/shadowsocks/src/net/udp.rs index 7a7c83a53e..00ac8c1d36 100644 --- a/shadowsocks-rust/crates/shadowsocks/src/net/udp.rs +++ b/shadowsocks-rust/crates/shadowsocks/src/net/udp.rs @@ -189,11 +189,10 @@ impl UdpSocket { /// Wrapper of `UdpSocket::poll_send` pub fn poll_send(&self, cx: &mut TaskContext<'_>, buf: &[u8]) -> Poll> { // Check MTU - if let Some(mtu) = self.mtu { - if buf.len() > mtu { + if let Some(mtu) = self.mtu + && buf.len() > mtu { return Err(make_mtu_error(buf.len(), mtu)).into(); } - } self.socket.poll_send(cx, buf) } @@ -202,11 +201,10 @@ impl UdpSocket { #[inline] pub async fn send(&self, buf: &[u8]) -> io::Result { // Check MTU - if let Some(mtu) = self.mtu { - if buf.len() > mtu { + if let Some(mtu) = self.mtu + && buf.len() > mtu { return Err(make_mtu_error(buf.len(), mtu)); } - } self.socket.send(buf).await } @@ -214,11 +212,10 @@ impl UdpSocket { /// Wrapper of `UdpSocket::poll_send_to` pub fn poll_send_to(&self, cx: &mut TaskContext<'_>, buf: &[u8], target: SocketAddr) -> Poll> { // Check MTU - if let Some(mtu) = self.mtu { - if buf.len() > mtu { + if let Some(mtu) = self.mtu + && buf.len() > mtu { return Err(make_mtu_error(buf.len(), mtu)).into(); } - } self.socket.poll_send_to(cx, buf, target) } @@ -227,11 +224,10 @@ impl UdpSocket { #[inline] pub async fn send_to(&self, buf: &[u8], target: A) -> io::Result { // Check MTU - if let Some(mtu) = self.mtu { - if buf.len() > mtu { + if let Some(mtu) = self.mtu + && buf.len() > mtu { return Err(make_mtu_error(buf.len(), mtu)); } - } self.socket.send_to(buf, target).await } @@ -241,11 +237,10 @@ impl UdpSocket { pub fn poll_recv(&self, cx: &mut TaskContext<'_>, buf: &mut ReadBuf<'_>) -> Poll> { ready!(self.socket.poll_recv(cx, buf))?; - if let Some(mtu) = self.mtu { - if buf.filled().len() > mtu { + if let Some(mtu) = self.mtu + && buf.filled().len() > mtu { return Err(make_mtu_error(buf.filled().len(), mtu)).into(); } - } Ok(()).into() } @@ -255,11 +250,10 @@ impl UdpSocket { pub async fn recv(&self, buf: &mut [u8]) -> io::Result { let n = self.socket.recv(buf).await?; - if let Some(mtu) = self.mtu { - if n > mtu { + if let Some(mtu) = self.mtu + && n > mtu { return Err(make_mtu_error(n, mtu)); } - } Ok(n) } @@ -269,11 +263,10 @@ impl UdpSocket { pub fn poll_recv_from(&self, cx: &mut TaskContext<'_>, buf: &mut ReadBuf<'_>) -> Poll> { let addr = ready!(self.socket.poll_recv_from(cx, buf))?; - if let Some(mtu) = self.mtu { - if buf.filled().len() > mtu { + if let Some(mtu) = self.mtu + && buf.filled().len() > mtu { return Err(make_mtu_error(buf.filled().len(), mtu)).into(); } - } Ok(addr).into() } @@ -283,11 +276,10 @@ impl UdpSocket { pub async fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { let (n, addr) = self.socket.recv_from(buf).await?; - if let Some(mtu) = self.mtu { - if n > mtu { + if let Some(mtu) = self.mtu + && n > mtu { return Err(make_mtu_error(n, mtu)); } - } Ok((n, addr)) } diff --git a/shadowsocks-rust/crates/shadowsocks/src/relay/tcprelay/proxy_stream/server.rs b/shadowsocks-rust/crates/shadowsocks/src/relay/tcprelay/proxy_stream/server.rs index ebbcaafee6..b8d41ee302 100644 --- a/shadowsocks-rust/crates/shadowsocks/src/relay/tcprelay/proxy_stream/server.rs +++ b/shadowsocks-rust/crates/shadowsocks/src/relay/tcprelay/proxy_stream/server.rs @@ -159,11 +159,10 @@ where // Wakeup writer task because we have already received the salt #[cfg(feature = "aead-cipher-2022")] - if let ProxyServerStreamWriteState::PrepareHeader(waker) = this.writer_state { - if let Some(waker) = waker.take() { + if let ProxyServerStreamWriteState::PrepareHeader(waker) = this.writer_state + && let Some(waker) = waker.take() { waker.wake(); } - } Ok(()).into() } @@ -190,11 +189,10 @@ where *(this.writer_state) = ProxyServerStreamWriteState::Established; } else { // Reader didn't receive the salt from client yet. - if let Some(waker) = waker.take() { - if !waker.will_wake(cx.waker()) { + if let Some(waker) = waker.take() + && !waker.will_wake(cx.waker()) { waker.wake(); } - } *waker = Some(cx.waker().clone()); return Poll::Pending; } diff --git a/shadowsocks-rust/src/service/local.rs b/shadowsocks-rust/src/service/local.rs index f142e3c47d..786f2ce586 100644 --- a/shadowsocks-rust/src/service/local.rs +++ b/shadowsocks-rust/src/service/local.rs @@ -755,17 +755,15 @@ pub fn create(matches: &ArgMatches) -> ShadowsocksResult<(Runtime, impl Future("TCP_REDIR") { + if RedirType::tcp_default() != RedirType::NotSupported + && let Some(tcp_redir) = matches.get_one::("TCP_REDIR") { local_config.tcp_redir = tcp_redir.parse::().expect("tcp-redir"); } - } - if RedirType::udp_default() != RedirType::NotSupported { - if let Some(udp_redir) = matches.get_one::("UDP_REDIR") { + if RedirType::udp_default() != RedirType::NotSupported + && let Some(udp_redir) = matches.get_one::("UDP_REDIR") { local_config.udp_redir = udp_redir.parse::().expect("udp-redir"); } - } } #[cfg(feature = "local-dns")] diff --git a/shadowsocks-rust/src/service/manager.rs b/shadowsocks-rust/src/service/manager.rs index 0eadd30fd4..c0e2d76964 100644 --- a/shadowsocks-rust/src/service/manager.rs +++ b/shadowsocks-rust/src/service/manager.rs @@ -404,17 +404,15 @@ pub fn create(matches: &ArgMatches) -> ShadowsocksResult<(Runtime, impl Future("ACL") { let acl = AccessControl::load_from_file(acl_file) diff --git a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js index 57117b104f..70e4852024 100644 --- a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js +++ b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js @@ -1587,13 +1587,13 @@ return view.extend({ so = ss.option(CBIBubblesValue, '_value', _('Value')); so.modalonly = false; - so = ss.option(form.ListValue, 'chain_head_sub', _('Destination')); + so = ss.option(form.ListValue, 'chain_head_sub', _('Destination provider')); so.load = L.bind(hm.loadProviderLabel, so, [['', _('-- Please choose --')]]); so.rmempty = false; so.depends('type', 'provider'); so.modalonly = true; - so = ss.option(form.ListValue, 'chain_head', _('Destination')); + so = ss.option(form.ListValue, 'chain_head', _('Destination proxy node')); so.load = L.bind(hm.loadNodeLabel, so, [['', _('-- Please choose --')]]); so.rmempty = false; so.validate = function(section_id, value) { @@ -1607,13 +1607,13 @@ return view.extend({ so.depends('type', 'node'); so.modalonly = true; - so = ss.option(form.ListValue, 'chain_tail_group', _('Transit')); + so = ss.option(form.ListValue, 'chain_tail_group', _('Transit proxy group')); so.load = L.bind(hm.loadProxyGroupLabel, so, [['', _('-- Please choose --')]]); so.rmempty = false; so.depends({chain_tail: /.+/, '!reverse': true}); so.modalonly = true; - so = ss.option(form.ListValue, 'chain_tail', _('Transit')); + so = ss.option(form.ListValue, 'chain_tail', _('Transit proxy node')); so.load = L.bind(hm.loadNodeLabel, so, [['', _('-- Please choose --')]]); so.rmempty = false; so.validate = function(section_id, value) { diff --git a/small/luci-app-fchomo/po/templates/fchomo.pot b/small/luci-app-fchomo/po/templates/fchomo.pot index 3ab8e17546..7d8381b2d9 100644 --- a/small/luci-app-fchomo/po/templates/fchomo.pot +++ b/small/luci-app-fchomo/po/templates/fchomo.pot @@ -550,15 +550,18 @@ msgstr "" msgid "Default DNS server" msgstr "" -#: htdocs/luci-static/resources/view/fchomo/node.js:1590 -#: htdocs/luci-static/resources/view/fchomo/node.js:1596 -msgid "Destination" -msgstr "" - #: htdocs/luci-static/resources/view/fchomo/node.js:631 msgid "Destination addresses allowed to be forwarded via Wireguard." msgstr "" +#: htdocs/luci-static/resources/view/fchomo/node.js:1590 +msgid "Destination provider" +msgstr "" + +#: htdocs/luci-static/resources/view/fchomo/node.js:1596 +msgid "Destination proxy node" +msgstr "" + #: htdocs/luci-static/resources/view/fchomo/node.js:225 msgid "Dial fields" msgstr "" @@ -2666,8 +2669,11 @@ msgid "Tproxy port" msgstr "" #: htdocs/luci-static/resources/view/fchomo/node.js:1610 +msgid "Transit proxy group" +msgstr "" + #: htdocs/luci-static/resources/view/fchomo/node.js:1616 -msgid "Transit" +msgid "Transit proxy node" msgstr "" #: htdocs/luci-static/resources/view/fchomo/node.js:347 diff --git a/small/luci-app-fchomo/po/zh_Hans/fchomo.po b/small/luci-app-fchomo/po/zh_Hans/fchomo.po index 4fd40fb1f3..77c997fcfd 100644 --- a/small/luci-app-fchomo/po/zh_Hans/fchomo.po +++ b/small/luci-app-fchomo/po/zh_Hans/fchomo.po @@ -561,15 +561,18 @@ msgstr "默认 DNS(由 WAN 下发)" msgid "Default DNS server" msgstr "默认 DNS 服务器" -#: htdocs/luci-static/resources/view/fchomo/node.js:1590 -#: htdocs/luci-static/resources/view/fchomo/node.js:1596 -msgid "Destination" -msgstr "落地" - #: htdocs/luci-static/resources/view/fchomo/node.js:631 msgid "Destination addresses allowed to be forwarded via Wireguard." msgstr "允许通过 WireGuard 转发的目的地址" +#: htdocs/luci-static/resources/view/fchomo/node.js:1590 +msgid "Destination provider" +msgstr "落地供应商" + +#: htdocs/luci-static/resources/view/fchomo/node.js:1596 +msgid "Destination proxy node" +msgstr "落地代理节点" + #: htdocs/luci-static/resources/view/fchomo/node.js:225 msgid "Dial fields" msgstr "拨号字段" @@ -2704,9 +2707,12 @@ msgid "Tproxy port" msgstr "Tproxy 端口" #: htdocs/luci-static/resources/view/fchomo/node.js:1610 +msgid "Transit proxy group" +msgstr "中转代理组" + #: htdocs/luci-static/resources/view/fchomo/node.js:1616 -msgid "Transit" -msgstr "中转" +msgid "Transit proxy node" +msgstr "中转代理节点" #: htdocs/luci-static/resources/view/fchomo/node.js:347 #: htdocs/luci-static/resources/view/fchomo/node.js:962 diff --git a/small/luci-app-fchomo/po/zh_Hant/fchomo.po b/small/luci-app-fchomo/po/zh_Hant/fchomo.po index 538dfab995..05335b9258 100644 --- a/small/luci-app-fchomo/po/zh_Hant/fchomo.po +++ b/small/luci-app-fchomo/po/zh_Hant/fchomo.po @@ -561,15 +561,18 @@ msgstr "預設 DNS(由 WAN 下發)" msgid "Default DNS server" msgstr "預設 DNS 伺服器" -#: htdocs/luci-static/resources/view/fchomo/node.js:1590 -#: htdocs/luci-static/resources/view/fchomo/node.js:1596 -msgid "Destination" -msgstr "落地" - #: htdocs/luci-static/resources/view/fchomo/node.js:631 msgid "Destination addresses allowed to be forwarded via Wireguard." msgstr "允許通過 WireGuard 轉發的目的位址" +#: htdocs/luci-static/resources/view/fchomo/node.js:1590 +msgid "Destination provider" +msgstr "落地供應商" + +#: htdocs/luci-static/resources/view/fchomo/node.js:1596 +msgid "Destination proxy node" +msgstr "落地代理節點" + #: htdocs/luci-static/resources/view/fchomo/node.js:225 msgid "Dial fields" msgstr "撥號欄位" @@ -2704,9 +2707,12 @@ msgid "Tproxy port" msgstr "Tproxy 連接埠" #: htdocs/luci-static/resources/view/fchomo/node.js:1610 +msgid "Transit proxy group" +msgstr "中轉代理組" + #: htdocs/luci-static/resources/view/fchomo/node.js:1616 -msgid "Transit" -msgstr "中轉" +msgid "Transit proxy node" +msgstr "中轉代理節點" #: htdocs/luci-static/resources/view/fchomo/node.js:347 #: htdocs/luci-static/resources/view/fchomo/node.js:962 diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua index d3a707e1d2..5691f9e0cb 100644 --- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua +++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua @@ -511,6 +511,9 @@ o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path")) o.placeholder = "/" o:depends({ [_n("tcp_guise")] = "http" }) +o = s:option(Value, _n("tcp_guise_http_user_agent"), translate("User-Agent")) +o:depends({ [_n("tcp_guise")] = "http" }) + -- [[ mKCP部分 ]]-- o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('
    none: default, no masquerade, data sent is packets with no characteristics.
    srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
    utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
    wechat-video: packets disguised as WeChat video calls.
    dtls: disguised as DTLS 1.2 packet.
    wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)
    dns: Disguising traffic as DNS requests.')) @@ -558,6 +561,9 @@ o = s:option(Value, _n("ws_path"), translate("WebSocket Path")) o.placeholder = "/" o:depends({ [_n("transport")] = "ws" }) +o = s:option(Value, _n("ws_user_agent"), translate("User-Agent")) +o:depends({ [_n("transport")] = "ws" }) + o = s:option(Value, _n("ws_heartbeatPeriod"), translate("HeartbeatPeriod(second)")) o.datatype = "integer" o:depends({ [_n("transport")] = "ws" }) @@ -598,6 +604,9 @@ o = s:option(Value, _n("httpupgrade_path"), translate("HttpUpgrade Path")) o.placeholder = "/" o:depends({ [_n("transport")] = "httpupgrade" }) +o = s:option(Value, _n("httpupgrade_user_agent"), translate("User-Agent")) +o:depends({ [_n("transport")] = "httpupgrade" }) + -- [[ XHTTP部分 ]]-- o = s:option(ListValue, _n("xhttp_mode"), "XHTTP " .. translate("Mode")) o:depends({ [_n("transport")] = "xhttp" }) diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua index 5e27be824c..2452060385 100644 --- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua +++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua @@ -609,6 +609,9 @@ o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path")) o.placeholder = "/" o:depends({ [_n("tcp_guise")] = "http" }) +o = s:option(Value, _n("tcp_guise_http_user_agent"), translate("User-Agent")) +o:depends({ [_n("tcp_guise")] = "http" }) + -- [[ HTTP部分 ]]-- o = s:option(DynamicList, _n("http_host"), translate("HTTP Host")) o:depends({ [_n("transport")] = "http" }) @@ -617,6 +620,9 @@ o = s:option(Value, _n("http_path"), translate("HTTP Path")) o.placeholder = "/" o:depends({ [_n("transport")] = "http" }) +o = s:option(Value, _n("http_user_agent"), translate("User-Agent")) +o:depends({ [_n("transport")] = "http" }) + o = s:option(Flag, _n("http_h2_health_check"), translate("Health check")) o:depends({ [_n("tls")] = true, [_n("transport")] = "http" }) @@ -636,6 +642,9 @@ o = s:option(Value, _n("ws_path"), translate("WebSocket Path")) o.placeholder = "/" o:depends({ [_n("transport")] = "ws" }) +o = s:option(Value, _n("ws_user_agent"), translate("User-Agent")) +o:depends({ [_n("transport")] = "ws" }) + o = s:option(Flag, _n("ws_enableEarlyData"), translate("Enable early data")) o:depends({ [_n("transport")] = "ws" }) @@ -654,6 +663,9 @@ o = s:option(Value, _n("httpupgrade_path"), translate("HTTPUpgrade Path")) o.placeholder = "/" o:depends({ [_n("transport")] = "httpupgrade" }) +o = s:option(Value, _n("httpupgrade_user_agent"), translate("User-Agent")) +o:depends({ [_n("transport")] = "httpupgrade" }) + -- [[ gRPC部分 ]]-- o = s:option(Value, _n("grpc_serviceName"), "ServiceName") o:depends({ [_n("transport")] = "grpc" }) diff --git a/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua index 6ea08be46d..04c9618265 100644 --- a/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -206,6 +206,9 @@ function gen_outbound(flag, node, tag, proxy_table) local first = node.tcp_guise_http_path[1] return (first == "" or not first) and "/" or first end)() or "/", + headers = { + ["User-Agent"] = node.tcp_guise_http_user_agent or nil + }, idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil, ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil, } @@ -217,6 +220,9 @@ function gen_outbound(flag, node, tag, proxy_table) type = "http", host = node.http_host or {}, path = node.http_path or "/", + headers = { + ["User-Agent"] = node.http_user_agent or nil + }, idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil, ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil, } @@ -227,7 +233,10 @@ function gen_outbound(flag, node, tag, proxy_table) v2ray_transport = { type = "ws", path = node.ws_path or "/", - headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil, + headers = { + Host = node.ws_host or nil, + ["User-Agent"] = node.ws_user_agent or nil + }, max_early_data = tonumber(node.ws_maxEarlyData) or nil, early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil --要与 Xray-core 兼容,请将其设置为 Sec-WebSocket-Protocol。它需要与服务器保持一致。 } @@ -238,6 +247,9 @@ function gen_outbound(flag, node, tag, proxy_table) type = "httpupgrade", host = node.httpupgrade_host, path = node.httpupgrade_path or "/", + headers = { + ["User-Agent"] = node.httpupgrade_user_agent or nil + } } end diff --git a/small/luci-app-passwall/luasrc/passwall/util_xray.lua b/small/luci-app-passwall/luasrc/passwall/util_xray.lua index 385c04ffb9..77be6be021 100644 --- a/small/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/small/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -179,7 +179,8 @@ function gen_outbound(flag, node, tag, proxy_table) return r end)() or {"/"}, headers = { - Host = node.tcp_guise_http_host or {} + Host = node.tcp_guise_http_host or {}, + ["User-Agent"] = node.tcp_guise_http_user_agent and {node.tcp_guise_http_user_agent} or nil } } or nil } @@ -200,7 +201,10 @@ function gen_outbound(flag, node, tag, proxy_table) } or nil, wsSettings = (node.transport == "ws") and { path = node.ws_path or "/", - host = node.ws_host or nil, + headers = { + Host = node.ws_host or nil, + ["User-Agent"] = node.ws_user_agent or nil + }, maxEarlyData = tonumber(node.ws_maxEarlyData) or nil, earlyDataHeaderName = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil, heartbeatPeriod = tonumber(node.ws_heartbeatPeriod) or nil @@ -215,7 +219,10 @@ function gen_outbound(flag, node, tag, proxy_table) } or nil, httpupgradeSettings = (node.transport == "httpupgrade") and { path = node.httpupgrade_path or "/", - host = node.httpupgrade_host + host = node.httpupgrade_host, + headers = { + ["User-Agent"] = node.httpupgrade_user_agent or nil + } } or nil, xhttpSettings = (node.transport == "xhttp") and { mode = node.xhttp_mode or "auto", diff --git a/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multiselect.htm b/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multiselect.htm index cc52fbdc99..b5b18f67e4 100644 --- a/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multiselect.htm +++ b/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multiselect.htm @@ -1,6 +1,5 @@ <%+cbi/valueheader%> <% -local api = require "luci.passwall.api" local cbid = "cbid." .. self.config .. "." .. section .. "." .. self.option -- 读取 MultiValue @@ -32,7 +31,7 @@ local group_order = {} for _, item in ipairs(values) do local g = item.group if not g or g == "" then - g = api.i18n.translate("default") + g = translate("default") end if not groups[g] then groups[g] = {} @@ -66,14 +65,14 @@ end } -
    +
    + style="width:100%;padding:6px;margin-bottom:8px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;max-height:36px;" />
      @@ -84,9 +83,9 @@ end
      - - <%=gname%> - + + <%=gname%> + (0/<%=#items%>)
      @@ -94,7 +93,7 @@ end
        <% for _, item in ipairs(items) do %> -
      • +
      • 0) { plugin = "v2ray-plugin"; + pluginArgs += "mux=0;"; } } @@ -222,6 +224,7 @@ public class ShadowsocksFmt : BaseFmt var path = pluginParts.FirstOrDefault(t => t.StartsWith("path=")); var hasTls = pluginParts.Any(t => t == "tls"); var certRaw = pluginParts.FirstOrDefault(t => t.StartsWith("certRaw=")); + var mux = pluginParts.FirstOrDefault(t => t.StartsWith("mux=")); var modeValue = mode.Replace("mode=", ""); if (modeValue == "websocket") @@ -234,7 +237,9 @@ public class ShadowsocksFmt : BaseFmt } if (!path.IsNullOrEmpty()) { - item.Path = path.Replace("path=", ""); + var pathValue = path.Replace("path=", ""); + pathValue = pathValue.Replace("\\=", "=").Replace("\\,", ",").Replace("\\\\", "\\"); + item.Path = pathValue; } } else if (modeValue == "quic") @@ -258,6 +263,16 @@ public class ShadowsocksFmt : BaseFmt item.Cert = certPem; } } + + if (!mux.IsNullOrEmpty()) + { + var muxValue = mux.Replace("mux=", ""); + var muxCount = muxValue.ToInt(); + if (muxCount > 0) + { + return null; + } + } } } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index f18f833c6f..79b10176a7 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -46,7 +46,10 @@ public partial class CoreConfigSingboxService { pluginArgs += "mode=websocket;"; pluginArgs += $"host={node.RequestHost};"; - pluginArgs += $"path={node.Path};"; + // https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172 + // Equal signs and commas [and backslashes] must be escaped with a backslash. + var path = node.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,"); + pluginArgs += $"path={path};"; } else if (node.Network == nameof(ETransport.quic)) { @@ -64,8 +67,6 @@ public partial class CoreConfigSingboxService var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim(); - // https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172 - // Equal signs and commas [and backslashes] must be escaped with a backslash. base64Content = base64Content.Replace("=", "\\="); pluginArgs += $"certRaw={base64Content};"; @@ -74,6 +75,9 @@ public partial class CoreConfigSingboxService if (pluginArgs.Length > 0) { outbound.plugin = "v2ray-plugin"; + pluginArgs += "mux=0;"; + // pluginStr remove last ';' + pluginArgs = pluginArgs[..^1]; outbound.plugin_opts = pluginArgs; } } diff --git a/xray-core/common/platform/ctlcmd/attr_other.go b/xray-core/common/platform/ctlcmd/attr_other.go deleted file mode 100644 index 3e1bc265d5..0000000000 --- a/xray-core/common/platform/ctlcmd/attr_other.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build !windows -// +build !windows - -package ctlcmd - -import "syscall" - -func getSysProcAttr() *syscall.SysProcAttr { - return nil -} diff --git a/xray-core/common/platform/ctlcmd/attr_windows.go b/xray-core/common/platform/ctlcmd/attr_windows.go deleted file mode 100644 index ab8ac064bd..0000000000 --- a/xray-core/common/platform/ctlcmd/attr_windows.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build windows -// +build windows - -package ctlcmd - -import "syscall" - -func getSysProcAttr() *syscall.SysProcAttr { - return &syscall.SysProcAttr{ - HideWindow: true, - } -} diff --git a/xray-core/common/platform/ctlcmd/ctlcmd.go b/xray-core/common/platform/ctlcmd/ctlcmd.go deleted file mode 100644 index bc28ace17d..0000000000 --- a/xray-core/common/platform/ctlcmd/ctlcmd.go +++ /dev/null @@ -1,50 +0,0 @@ -package ctlcmd - -import ( - "context" - "io" - "os" - "os/exec" - "strings" - - "github.com/xtls/xray-core/common/buf" - "github.com/xtls/xray-core/common/errors" - "github.com/xtls/xray-core/common/platform" -) - -func Run(args []string, input io.Reader) (buf.MultiBuffer, error) { - xctl := platform.GetToolLocation("xctl") - if _, err := os.Stat(xctl); err != nil { - return nil, errors.New("xctl doesn't exist").Base(err) - } - - var errBuffer buf.MultiBufferContainer - var outBuffer buf.MultiBufferContainer - - cmd := exec.Command(xctl, args...) - cmd.Stderr = &errBuffer - cmd.Stdout = &outBuffer - cmd.SysProcAttr = getSysProcAttr() - if input != nil { - cmd.Stdin = input - } - - if err := cmd.Start(); err != nil { - return nil, errors.New("failed to start xctl").Base(err) - } - - if err := cmd.Wait(); err != nil { - msg := "failed to execute xctl" - if errBuffer.Len() > 0 { - msg += ": \n" + strings.TrimSpace(errBuffer.MultiBuffer.String()) - } - return nil, errors.New(msg).Base(err) - } - - // log stderr, info message - if !errBuffer.IsEmpty() { - errors.LogInfo(context.Background(), " \n", strings.TrimSpace(errBuffer.MultiBuffer.String())) - } - - return outBuffer.MultiBuffer, nil -} diff --git a/xray-core/common/platform/others.go b/xray-core/common/platform/others.go index a405ac4801..be86b6faaa 100644 --- a/xray-core/common/platform/others.go +++ b/xray-core/common/platform/others.go @@ -8,19 +8,10 @@ import ( "path/filepath" ) -func ExpandEnv(s string) string { - return os.ExpandEnv(s) -} - func LineSeparator() string { return "\n" } -func GetToolLocation(file string) string { - toolPath := NewEnvFlag(ToolLocation).GetValue(getExecutableDir) - return filepath.Join(toolPath, file) -} - // GetAssetLocation searches for `file` in the env dir, the executable dir, and certain locations func GetAssetLocation(file string) string { assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir) diff --git a/xray-core/common/platform/platform.go b/xray-core/common/platform/platform.go index b865dc0db0..bc7baf655d 100644 --- a/xray-core/common/platform/platform.go +++ b/xray-core/common/platform/platform.go @@ -8,10 +8,8 @@ import ( ) const ( - PluginLocation = "xray.location.plugin" ConfigLocation = "xray.location.config" ConfdirLocation = "xray.location.confdir" - ToolLocation = "xray.location.tool" AssetLocation = "xray.location.asset" CertLocation = "xray.location.cert" @@ -79,17 +77,6 @@ func getExecutableDir() string { return filepath.Dir(exec) } -func getExecutableSubDir(dir string) func() string { - return func() string { - return filepath.Join(getExecutableDir(), dir) - } -} - -func GetPluginDirectory() string { - pluginDir := NewEnvFlag(PluginLocation).GetValue(getExecutableSubDir("plugins")) - return pluginDir -} - func GetConfigurationPath() string { configPath := NewEnvFlag(ConfigLocation).GetValue(getExecutableDir) return filepath.Join(configPath, "config.json") diff --git a/xray-core/common/platform/windows.go b/xray-core/common/platform/windows.go index cb25a1ad99..684ddc9c30 100644 --- a/xray-core/common/platform/windows.go +++ b/xray-core/common/platform/windows.go @@ -5,20 +5,10 @@ package platform import "path/filepath" -func ExpandEnv(s string) string { - // TODO - return s -} - func LineSeparator() string { return "\r\n" } -func GetToolLocation(file string) string { - toolPath := NewEnvFlag(ToolLocation).GetValue(getExecutableDir) - return filepath.Join(toolPath, file+".exe") -} - // GetAssetLocation searches for `file` in the env dir and the executable dir func GetAssetLocation(file string) string { assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir) diff --git a/xray-core/core/config.go b/xray-core/core/config.go index ec9e5aa436..8f2018e64f 100644 --- a/xray-core/core/config.go +++ b/xray-core/core/config.go @@ -64,7 +64,7 @@ func GetMergedConfig(args cmdarg.Arg) (string, error) { var files []*ConfigSource supported := []string{"json", "yaml", "toml"} for _, file := range args { - format := getFormat(file) + format := GetFormat(file) if slices.Contains(supported, format) { files = append(files, &ConfigSource{ Name: file, @@ -98,7 +98,7 @@ func getExtension(filename string) string { return filename[idx+1:] } -func getFormat(filename string) string { +func GetFormat(filename string) string { return GetFormatByExtension(getExtension(filename)) } @@ -112,7 +112,7 @@ func LoadConfig(formatName string, input interface{}) (*Config, error) { if formatName == "auto" { if file != "stdin:" { - f = getFormat(file) + f = GetFormat(file) } else { f = "json" } diff --git a/xray-core/core/core.go b/xray-core/core/core.go index 6adc82bb3c..96624d1dd4 100644 --- a/xray-core/core/core.go +++ b/xray-core/core/core.go @@ -19,7 +19,7 @@ import ( var ( Version_x byte = 25 Version_y byte = 12 - Version_z byte = 2 + Version_z byte = 8 ) var ( diff --git a/xray-core/infra/conf/xray.go b/xray-core/infra/conf/xray.go index e9cacc87e2..0dfb2a983a 100644 --- a/xray-core/infra/conf/xray.go +++ b/xray-core/infra/conf/xray.go @@ -3,8 +3,6 @@ package conf import ( "context" "encoding/json" - "log" - "os" "path/filepath" "strings" @@ -47,8 +45,6 @@ var ( "dns": func() interface{} { return new(DNSOutboundConfig) }, "wireguard": func() interface{} { return &WireGuardConfig{IsClient: true} }, }, "protocol", "settings") - - ctllog = log.New(os.Stderr, "xctl> ", 0) ) type SniffingConfig struct { diff --git a/xray-core/main/commands/all/convert/protobuf.go b/xray-core/main/commands/all/convert/protobuf.go index 74272c57b0..2f4296e1f7 100644 --- a/xray-core/main/commands/all/convert/protobuf.go +++ b/xray-core/main/commands/all/convert/protobuf.go @@ -3,7 +3,6 @@ package convert import ( "fmt" "os" - "strings" "github.com/xtls/xray-core/common/cmdarg" creflect "github.com/xtls/xray-core/common/reflect" @@ -61,7 +60,7 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) { } if len(optFile) > 0 { - switch core.GetFormatByExtension(getFileExtension(optFile)){ + switch core.GetFormat(optFile){ case "protobuf", "": fmt.Println("Output ProtoBuf file is ", optFile) default: @@ -106,11 +105,3 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) { } } } - -func getFileExtension(filename string) string { - idx := strings.LastIndexByte(filename, '.') - if idx == -1 { - return "" - } - return filename[idx+1:] -} diff --git a/xray-core/main/confloader/confloader.go b/xray-core/main/confloader/confloader.go index 315c500e24..b9652a67ed 100644 --- a/xray-core/main/confloader/confloader.go +++ b/xray-core/main/confloader/confloader.go @@ -10,12 +10,10 @@ import ( type ( configFileLoader func(string) (io.Reader, error) - extconfigLoader func([]string, io.Reader) (io.Reader, error) ) var ( EffectiveConfigFileLoader configFileLoader - EffectiveExtConfigLoader extconfigLoader ) // LoadConfig reads from a path/url/stdin @@ -27,13 +25,3 @@ func LoadConfig(file string) (io.Reader, error) { } return EffectiveConfigFileLoader(file) } - -// LoadExtConfig calls xctl to handle multiple config -// the actual work also in external module -func LoadExtConfig(files []string, reader io.Reader) (io.Reader, error) { - if EffectiveExtConfigLoader == nil { - return nil, errors.New("external config module not loaded").AtError() - } - - return EffectiveExtConfigLoader(files, reader) -} diff --git a/xray-core/main/confloader/external/external.go b/xray-core/main/confloader/external/external.go index 787de985a4..110b483da9 100644 --- a/xray-core/main/confloader/external/external.go +++ b/xray-core/main/confloader/external/external.go @@ -13,7 +13,6 @@ import ( "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/errors" - "github.com/xtls/xray-core/common/platform/ctlcmd" "github.com/xtls/xray-core/main/confloader" ) @@ -129,16 +128,6 @@ func FetchUnixSocketHTTPContent(target string) ([]byte, error) { return content, nil } -func ExtConfigLoader(files []string, reader io.Reader) (io.Reader, error) { - buf, err := ctlcmd.Run(append([]string{"convert"}, files...), reader) - if err != nil { - return nil, err - } - - return strings.NewReader(buf.String()), nil -} - func init() { confloader.EffectiveConfigFileLoader = ConfigLoader - confloader.EffectiveExtConfigLoader = ExtConfigLoader } diff --git a/xray-core/proxy/proxy.go b/xray-core/proxy/proxy.go index 7bb3f79ef6..d6a797cbb7 100644 --- a/xray-core/proxy/proxy.go +++ b/xray-core/proxy/proxy.go @@ -248,7 +248,7 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) { *withinPaddingBuffers = false *switchToDirectCopy = true } else { - errors.LogInfo(w.ctx, "XtlsRead unknown command ", *currentCommand, buffer.Len()) + errors.LogDebug(w.ctx, "XtlsRead unknown command ", *currentCommand, buffer.Len()) } } if w.trafficState.NumberOfPacketToFilter > 0 { @@ -269,9 +269,9 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) { w.rawInput = nil if inbound := session.InboundFromContext(w.ctx); inbound != nil && inbound.Conn != nil { - if w.isUplink && inbound.CanSpliceCopy == 2 { - inbound.CanSpliceCopy = 1 - } + // if w.isUplink && inbound.CanSpliceCopy == 2 { // TODO: enable uplink splice + // inbound.CanSpliceCopy = 1 + // } if !w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // ob need to be passed in due to context can have more than one ob w.ob.CanSpliceCopy = 1 } @@ -334,9 +334,9 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { if !w.isUplink && inbound.CanSpliceCopy == 2 { inbound.CanSpliceCopy = 1 } - if w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { - w.ob.CanSpliceCopy = 1 - } + // if w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // TODO: enable uplink splice + // w.ob.CanSpliceCopy = 1 + // } } rawConn, _, writerCounter := UnwrapRawConn(w.conn) w.Writer = buf.NewWriter(rawConn) @@ -478,7 +478,7 @@ func ReshapeMultiBuffer(ctx context.Context, buffer buf.MultiBuffer) buf.MultiBu buffer[i] = nil } buffer = buffer[:0] - errors.LogInfo(ctx, "ReshapeMultiBuffer ", toPrint) + errors.LogDebug(ctx, "ReshapeMultiBuffer ", toPrint) return mb2 } @@ -517,7 +517,7 @@ func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool b = nil } newbuffer.Extend(paddingLen) - errors.LogInfo(ctx, "XtlsPadding ", contentLen, " ", paddingLen, " ", command) + errors.LogDebug(ctx, "XtlsPadding ", contentLen, " ", paddingLen, " ", command) return newbuffer } @@ -564,7 +564,7 @@ func XtlsUnpadding(b *buf.Buffer, s *TrafficState, isUplink bool, ctx context.Co *remainingPadding = int32(data) << 8 case 1: *remainingPadding = *remainingPadding | int32(data) - errors.LogInfo(ctx, "Xtls Unpadding new block, content ", *remainingContent, " padding ", *remainingPadding, " command ", *currentCommand) + errors.LogDebug(ctx, "Xtls Unpadding new block, content ", *remainingContent, " padding ", *remainingPadding, " command ", *currentCommand) } *remainingCommand-- } else if *remainingContent > 0 { @@ -623,11 +623,11 @@ func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx conte cipherSuite := b.BytesRange(43+sessionIdLen+1, 43+sessionIdLen+3) trafficState.Cipher = uint16(cipherSuite[0])<<8 | uint16(cipherSuite[1]) } else { - errors.LogInfo(ctx, "XtlsFilterTls short server hello, tls 1.2 or older? ", b.Len(), " ", trafficState.RemainingServerHello) + errors.LogDebug(ctx, "XtlsFilterTls short server hello, tls 1.2 or older? ", b.Len(), " ", trafficState.RemainingServerHello) } } else if bytes.Equal(TlsClientHandShakeStart, startsBytes[:2]) && startsBytes[5] == TlsHandshakeTypeClientHello { trafficState.IsTLS = true - errors.LogInfo(ctx, "XtlsFilterTls found tls client hello! ", buffer.Len()) + errors.LogDebug(ctx, "XtlsFilterTls found tls client hello! ", buffer.Len()) } } if trafficState.RemainingServerHello > 0 { @@ -643,18 +643,18 @@ func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx conte } else if v != "TLS_AES_128_CCM_8_SHA256" { trafficState.EnableXtls = true } - errors.LogInfo(ctx, "XtlsFilterTls found tls 1.3! ", b.Len(), " ", v) + errors.LogDebug(ctx, "XtlsFilterTls found tls 1.3! ", b.Len(), " ", v) trafficState.NumberOfPacketToFilter = 0 return } else if trafficState.RemainingServerHello <= 0 { - errors.LogInfo(ctx, "XtlsFilterTls found tls 1.2! ", b.Len()) + errors.LogDebug(ctx, "XtlsFilterTls found tls 1.2! ", b.Len()) trafficState.NumberOfPacketToFilter = 0 return } - errors.LogInfo(ctx, "XtlsFilterTls inconclusive server hello ", b.Len(), " ", trafficState.RemainingServerHello) + errors.LogDebug(ctx, "XtlsFilterTls inconclusive server hello ", b.Len(), " ", trafficState.RemainingServerHello) } if trafficState.NumberOfPacketToFilter <= 0 { - errors.LogInfo(ctx, "XtlsFilterTls stop filtering", buffer.Len()) + errors.LogDebug(ctx, "XtlsFilterTls stop filtering", buffer.Len()) } } } @@ -736,7 +736,7 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net } } if splice { - errors.LogInfo(ctx, "CopyRawConn splice") + errors.LogDebug(ctx, "CopyRawConn splice") statWriter, _ := writer.(*dispatcher.SizeStatWriter) //runtime.Gosched() // necessary time.Sleep(time.Millisecond) // without this, there will be a rare ssl error for freedom splice @@ -779,7 +779,7 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net } func readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, readCounter stats.Counter) error { - errors.LogInfo(ctx, "CopyRawConn (maybe) readv") + errors.LogDebug(ctx, "CopyRawConn (maybe) readv") if err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil { return errors.New("failed to process response").Base(err) } diff --git a/yt-dlp/.github/workflows/build.yml b/yt-dlp/.github/workflows/build.yml index 8f5df4ce4d..9b58653bd7 100644 --- a/yt-dlp/.github/workflows/build.yml +++ b/yt-dlp/.github/workflows/build.yml @@ -196,7 +196,7 @@ jobs: UPDATE_TO: yt-dlp/yt-dlp@2025.09.05 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 # Needed for changelog @@ -257,7 +257,7 @@ jobs: SKIP_ONEFILE_BUILD: ${{ (!matrix.onefile && '1') || '' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Cache requirements if: matrix.cache_requirements @@ -320,7 +320,7 @@ jobs: UPDATE_TO: yt-dlp/yt-dlp@2025.09.05 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # NB: Building universal2 does not work with python from actions/setup-python - name: Cache requirements @@ -343,14 +343,14 @@ jobs: brew uninstall --ignore-dependencies python3 python3 -m venv ~/yt-dlp-build-venv source ~/yt-dlp-build-venv/bin/activate - python3 devscripts/install_deps.py --only-optional-groups --include-group build - python3 devscripts/install_deps.py --print --include-group pyinstaller > requirements.txt + python3 devscripts/install_deps.py --omit-default --include-extra build + python3 devscripts/install_deps.py --print --include-extra pyinstaller > requirements.txt # We need to ignore wheels otherwise we break universal2 builds python3 -m pip install -U --no-binary :all: -r requirements.txt # We need to fuse our own universal2 wheels for curl_cffi python3 -m pip install -U 'delocate==0.11.0' mkdir curl_cffi_whls curl_cffi_universal2 - python3 devscripts/install_deps.py --print --only-optional-groups --include-group curl-cffi > requirements.txt + python3 devscripts/install_deps.py --print --omit-default --include-extra curl-cffi > requirements.txt for platform in "macosx_11_0_arm64" "macosx_11_0_x86_64"; do python3 -m pip download \ --only-binary=:all: \ @@ -450,7 +450,7 @@ jobs: PYI_WHEEL: pyinstaller-${{ matrix.pyi_version }}-py3-none-${{ matrix.platform_tag }}.whl steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python_version }} @@ -484,11 +484,11 @@ jobs: mkdir /pyi-wheels python -m pip download -d /pyi-wheels --no-deps --require-hashes "pyinstaller@${Env:PYI_URL}#sha256=${Env:PYI_HASH}" python -m pip install --force-reinstall -U "/pyi-wheels/${Env:PYI_WHEEL}" - python devscripts/install_deps.py --only-optional-groups --include-group build + python devscripts/install_deps.py --omit-default --include-extra build if ("${Env:ARCH}" -eq "x86") { python devscripts/install_deps.py } else { - python devscripts/install_deps.py --include-group curl-cffi + python devscripts/install_deps.py --include-extra curl-cffi } - name: Prepare diff --git a/yt-dlp/.github/workflows/challenge-tests.yml b/yt-dlp/.github/workflows/challenge-tests.yml index 89895eb07b..68fe117191 100644 --- a/yt-dlp/.github/workflows/challenge-tests.yml +++ b/yt-dlp/.github/workflows/challenge-tests.yml @@ -35,7 +35,7 @@ jobs: env: QJS_VERSION: '2025-04-26' # Earliest version with rope strings steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: @@ -67,7 +67,7 @@ jobs: unzip quickjs.zip - name: Install test requirements run: | - python ./devscripts/install_deps.py --print --only-optional-groups --include-group test > requirements.txt + python ./devscripts/install_deps.py --print --omit-default --include-extra test > requirements.txt python ./devscripts/install_deps.py --print -c certifi -c requests -c urllib3 -c yt-dlp-ejs >> requirements.txt python -m pip install -U -r requirements.txt - name: Run tests diff --git a/yt-dlp/.github/workflows/codeql.yml b/yt-dlp/.github/workflows/codeql.yml index 6d4dbdf193..fda5351c08 100644 --- a/yt-dlp/.github/workflows/codeql.yml +++ b/yt-dlp/.github/workflows/codeql.yml @@ -2,7 +2,7 @@ name: "CodeQL" on: push: - branches: [ 'master', 'gh-pages', 'release' ] + branches: [ 'master' ] pull_request: # The branches below must be a subset of the branches above branches: [ 'master' ] @@ -11,7 +11,7 @@ on: jobs: analyze: - name: Analyze + name: Analyze (${{ matrix.language }}) runs-on: ubuntu-latest permissions: actions: read @@ -21,45 +21,19 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Use only 'java' to analyze code written in Java, Kotlin or both - # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + language: [ 'actions', 'javascript-typescript', 'python' ] steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - - # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh + build-mode: none - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: "/language:${{matrix.language}}" diff --git a/yt-dlp/.github/workflows/core.yml b/yt-dlp/.github/workflows/core.yml index 3cb17f2b7d..16c2b92b40 100644 --- a/yt-dlp/.github/workflows/core.yml +++ b/yt-dlp/.github/workflows/core.yml @@ -55,7 +55,7 @@ jobs: - os: windows-latest python-version: pypy-3.11 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} @@ -63,7 +63,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install test requirements - run: python ./devscripts/install_deps.py --include-group test --include-group curl-cffi + run: python ./devscripts/install_deps.py --include-extra test --include-extra curl-cffi - name: Run tests timeout-minutes: 15 continue-on-error: False diff --git a/yt-dlp/.github/workflows/download.yml b/yt-dlp/.github/workflows/download.yml index d075270d7b..62a2cf9ba0 100644 --- a/yt-dlp/.github/workflows/download.yml +++ b/yt-dlp/.github/workflows/download.yml @@ -9,13 +9,13 @@ jobs: if: "contains(github.event.head_commit.message, 'ci run dl')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 with: python-version: '3.10' - name: Install test requirements - run: python ./devscripts/install_deps.py --include-group dev + run: python ./devscripts/install_deps.py --include-extra dev - name: Run tests continue-on-error: true run: python ./devscripts/run_tests.py download @@ -36,13 +36,13 @@ jobs: - os: windows-latest python-version: pypy-3.11 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install test requirements - run: python ./devscripts/install_deps.py --include-group dev + run: python ./devscripts/install_deps.py --include-extra dev - name: Run tests continue-on-error: true run: python ./devscripts/run_tests.py download diff --git a/yt-dlp/.github/workflows/quick-test.yml b/yt-dlp/.github/workflows/quick-test.yml index a6e84b1d80..f72f6a5651 100644 --- a/yt-dlp/.github/workflows/quick-test.yml +++ b/yt-dlp/.github/workflows/quick-test.yml @@ -9,13 +9,13 @@ jobs: if: "!contains(github.event.head_commit.message, 'ci skip all')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Python 3.10 uses: actions/setup-python@v6 with: python-version: '3.10' - name: Install test requirements - run: python ./devscripts/install_deps.py --only-optional-groups --include-group test + run: python ./devscripts/install_deps.py --omit-default --include-extra test - name: Run tests timeout-minutes: 15 run: | @@ -26,12 +26,12 @@ jobs: if: "!contains(github.event.head_commit.message, 'ci skip all')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: '3.10' - name: Install dev dependencies - run: python ./devscripts/install_deps.py --only-optional-groups --include-group static-analysis + run: python ./devscripts/install_deps.py --omit-default --include-extra static-analysis - name: Make lazy extractors run: python ./devscripts/make_lazy_extractors.py - name: Run ruff diff --git a/yt-dlp/.github/workflows/release-nightly.yml b/yt-dlp/.github/workflows/release-nightly.yml index 26be60fe61..ac7e8cc675 100644 --- a/yt-dlp/.github/workflows/release-nightly.yml +++ b/yt-dlp/.github/workflows/release-nightly.yml @@ -12,7 +12,7 @@ jobs: outputs: commit: ${{ steps.check_for_new_commits.outputs.commit }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Check for new commits diff --git a/yt-dlp/.github/workflows/release.yml b/yt-dlp/.github/workflows/release.yml index afe1d384b4..e9facc0430 100644 --- a/yt-dlp/.github/workflows/release.yml +++ b/yt-dlp/.github/workflows/release.yml @@ -75,7 +75,7 @@ jobs: head_sha: ${{ steps.get_target.outputs.head_sha }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 @@ -170,7 +170,7 @@ jobs: id-token: write # mandatory for trusted publishing steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: actions/setup-python@v6 @@ -180,7 +180,7 @@ jobs: - name: Install Requirements run: | sudo apt -y install pandoc man - python devscripts/install_deps.py --only-optional-groups --include-group build + python devscripts/install_deps.py --omit-default --include-extra build - name: Prepare env: @@ -233,7 +233,7 @@ jobs: VERSION: ${{ needs.prepare.outputs.version }} HEAD_SHA: ${{ needs.prepare.outputs.head_sha }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: actions/download-artifact@v5 diff --git a/yt-dlp/.github/workflows/test-workflows.yml b/yt-dlp/.github/workflows/test-workflows.yml index d39ab8814b..e1a125461a 100644 --- a/yt-dlp/.github/workflows/test-workflows.yml +++ b/yt-dlp/.github/workflows/test-workflows.yml @@ -17,8 +17,8 @@ on: permissions: contents: read env: - ACTIONLINT_VERSION: "1.7.8" - ACTIONLINT_SHA256SUM: be92c2652ab7b6d08425428797ceabeb16e31a781c07bc388456b4e592f3e36a + ACTIONLINT_VERSION: "1.7.9" + ACTIONLINT_SHA256SUM: 233b280d05e100837f4af1433c7b40a5dcb306e3aa68fb4f17f8a7f45a7df7b4 ACTIONLINT_REPO: https://github.com/rhysd/actionlint jobs: @@ -26,7 +26,7 @@ jobs: name: Check workflows runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: "3.10" # Keep this in sync with release.yml's prepare job @@ -34,7 +34,7 @@ jobs: env: ACTIONLINT_TARBALL: ${{ format('actionlint_{0}_linux_amd64.tar.gz', env.ACTIONLINT_VERSION) }} run: | - python -m devscripts.install_deps --only-optional-groups --include-group test + python -m devscripts.install_deps --omit-default --include-extra test sudo apt -y install shellcheck python -m pip install -U pyflakes curl -LO "${ACTIONLINT_REPO}/releases/download/v${ACTIONLINT_VERSION}/${ACTIONLINT_TARBALL}" diff --git a/yt-dlp/CONTRIBUTING.md b/yt-dlp/CONTRIBUTING.md index 99f18b2f32..9a6111598c 100644 --- a/yt-dlp/CONTRIBUTING.md +++ b/yt-dlp/CONTRIBUTING.md @@ -177,7 +177,7 @@ While it is strongly recommended to use `hatch` for yt-dlp development, if you a ```shell # To only install development dependencies: -$ python -m devscripts.install_deps --include-group dev +$ python -m devscripts.install_deps --include-extra dev # Or, for an editable install plus dev dependencies: $ python -m pip install -e ".[default,dev]" @@ -763,7 +763,7 @@ Wrap all extracted numeric data into safe functions from [`yt_dlp/utils/`](yt_dl Use `url_or_none` for safe URL processing. -Use `traverse_obj` and `try_call` (superseeds `dict_get` and `try_get`) for safe metadata extraction from parsed JSON. +Use `traverse_obj` and `try_call` (supersedes `dict_get` and `try_get`) for safe metadata extraction from parsed JSON. Use `unified_strdate` for uniform `upload_date` or any `YYYYMMDD` meta field extraction, `unified_timestamp` for uniform `timestamp` extraction, `parse_filesize` for `filesize` extraction, `parse_count` for count meta fields extraction, `parse_resolution`, `parse_duration` for `duration` extraction, `parse_age_limit` for `age_limit` extraction. diff --git a/yt-dlp/CONTRIBUTORS b/yt-dlp/CONTRIBUTORS index 51369b35b6..70df48c996 100644 --- a/yt-dlp/CONTRIBUTORS +++ b/yt-dlp/CONTRIBUTORS @@ -828,9 +828,18 @@ krystophny matyb08 pha1n0q PierceLBrooks -sepro TheQWERTYCodr thomasmllt w4grfw WeidiDeng Zer0spectrum +0xvd +1bnBattuta +beliote +darkstar +Haytam001 +mrFlamel +oxyzenQ +putridambassador121 +RezSat +WhatAmISupposedToPutHere diff --git a/yt-dlp/Changelog.md b/yt-dlp/Changelog.md index b115fd045c..13c1bbd779 100644 --- a/yt-dlp/Changelog.md +++ b/yt-dlp/Changelog.md @@ -4,6 +4,64 @@ # To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master --> +### 2025.12.08 + +#### Core changes +- [Respect `PATHEXT` when locating JS runtime on Windows](https://github.com/yt-dlp/yt-dlp/commit/e564b4a8080cff48fa0c28f20272c05085ee6130) ([#15117](https://github.com/yt-dlp/yt-dlp/issues/15117)) by [Grub4K](https://github.com/Grub4K) +- **cookies**: [Fix `--cookies-from-browser` for new installs of Firefox 147+](https://github.com/yt-dlp/yt-dlp/commit/fa16dc5241ac1552074feee48e1c2605dc36d352) ([#15215](https://github.com/yt-dlp/yt-dlp/issues/15215)) by [bashonly](https://github.com/bashonly), [mbway](https://github.com/mbway) + +#### Extractor changes +- **agalega**: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/3cb5e4db54d44fe82d4eee94ae2f37cbce2e7dfc) ([#15105](https://github.com/yt-dlp/yt-dlp/issues/15105)) by [putridambassador121](https://github.com/putridambassador121) +- **alibaba**: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/c70b57c03e0c25767a5166620798297a2a4878fb) ([#15253](https://github.com/yt-dlp/yt-dlp/issues/15253)) by [seproDev](https://github.com/seproDev) +- **bitmovin**: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/45a3b42bb917e99b0b5c155c272ebf4a82a5bf66) ([#15064](https://github.com/yt-dlp/yt-dlp/issues/15064)) by [seproDev](https://github.com/seproDev) +- **digiteka**: [Rework extractor](https://github.com/yt-dlp/yt-dlp/commit/6842620d56e4c4e6affb90c2f8dff8a36dee852c) ([#14903](https://github.com/yt-dlp/yt-dlp/issues/14903)) by [beliote](https://github.com/beliote) +- **fc2**: live: [Raise appropriate error when stream is offline](https://github.com/yt-dlp/yt-dlp/commit/4433b3a217c9f430dc057643bfd7b6769eff4a45) ([#15180](https://github.com/yt-dlp/yt-dlp/issues/15180)) by [Zer0spectrum](https://github.com/Zer0spectrum) +- **floatplane**: [Add subtitle support](https://github.com/yt-dlp/yt-dlp/commit/b333ef1b3f961e292a8bf7052c54b54c81587a17) ([#15069](https://github.com/yt-dlp/yt-dlp/issues/15069)) by [seproDev](https://github.com/seproDev) +- **jtbc**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/947e7883406e5ea43687d6e4ff721cc0162c9664) ([#15047](https://github.com/yt-dlp/yt-dlp/issues/15047)) by [seproDev](https://github.com/seproDev) +- **loom**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/36b29bb3532e008a2aaf3d36d1c6fc3944137930) ([#15236](https://github.com/yt-dlp/yt-dlp/issues/15236)) by [bashonly](https://github.com/bashonly) +- **mave**: channel: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/5f66ac71f6637f768cd251509b0a932d0ce56427) ([#14915](https://github.com/yt-dlp/yt-dlp/issues/14915)) by [anlar](https://github.com/anlar) +- **medaltv**: [Rework extractor](https://github.com/yt-dlp/yt-dlp/commit/a4c72acc462668a938827370bd77084a1cd4733b) ([#15103](https://github.com/yt-dlp/yt-dlp/issues/15103)) by [seproDev](https://github.com/seproDev) +- **netapp**: [Add extractors](https://github.com/yt-dlp/yt-dlp/commit/20f83f208eae863250b35e2761adad88e91d85a1) ([#15122](https://github.com/yt-dlp/yt-dlp/issues/15122)) by [darkstar](https://github.com/darkstar) +- **nhk**: [Fix extractors](https://github.com/yt-dlp/yt-dlp/commit/12d411722a3d7a0382d1d230a904ecd4e20298b6) ([#14528](https://github.com/yt-dlp/yt-dlp/issues/14528)) by [garret1317](https://github.com/garret1317) +- **nowcanal**: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/4e680db1505dafb93313b1d42ffcd3f230fcc92a) ([#14584](https://github.com/yt-dlp/yt-dlp/issues/14584)) by [pferreir](https://github.com/pferreir) +- **patreon**: campaign: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/023e4db9afe0630c608621846856a1ca876d8bab) ([#15108](https://github.com/yt-dlp/yt-dlp/issues/15108)) by [thomasmllt](https://github.com/thomasmllt) +- **rinsefm**: [Fix extractors](https://github.com/yt-dlp/yt-dlp/commit/d6aa8c235d2e7d9374f79ec73af23a3859c76bea) ([#15020](https://github.com/yt-dlp/yt-dlp/issues/15020)) by [1bnBattuta](https://github.com/1bnBattuta), [seproDev](https://github.com/seproDev) +- **s4c**: [Fix geo-restricted content](https://github.com/yt-dlp/yt-dlp/commit/26c2545b87e2b22f134d1f567ed4d4b0b91c3253) ([#15196](https://github.com/yt-dlp/yt-dlp/issues/15196)) by [seproDev](https://github.com/seproDev) +- **soundcloudplaylist**: [Support new API URLs](https://github.com/yt-dlp/yt-dlp/commit/1dd84b9d1c33e50de49866b0d93c2596897ce506) ([#15071](https://github.com/yt-dlp/yt-dlp/issues/15071)) by [seproDev](https://github.com/seproDev) +- **sporteurope**: [Support new domain](https://github.com/yt-dlp/yt-dlp/commit/025191fea655ac879ca6dc68df358c26456a6e46) ([#15251](https://github.com/yt-dlp/yt-dlp/issues/15251)) by [bashonly](https://github.com/bashonly) +- **sproutvideo**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/2c9f0c3456057aff0631d9ea6d3eda70ffd8aabe) ([#15113](https://github.com/yt-dlp/yt-dlp/issues/15113)) by [bashonly](https://github.com/bashonly) +- **thechosen**: [Add extractors](https://github.com/yt-dlp/yt-dlp/commit/854fded114f3b7b33693c2d3418575d04014aa4b) ([#14183](https://github.com/yt-dlp/yt-dlp/issues/14183)) by [mrFlamel](https://github.com/mrFlamel) +- **thisoldhouse**: [Fix login support](https://github.com/yt-dlp/yt-dlp/commit/9daba4f442139ee2537746398afc5ac30b51c28c) ([#15097](https://github.com/yt-dlp/yt-dlp/issues/15097)) by [bashonly](https://github.com/bashonly) +- **tubitv**: series: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/2a777ecbd598de19a4c691ba1f790ccbec9cdbc4) ([#15018](https://github.com/yt-dlp/yt-dlp/issues/15018)) by [Zer0spectrum](https://github.com/Zer0spectrum) +- **urplay**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/c2e7e9cdb2261adde01048d161914b156a3bad51) ([#15120](https://github.com/yt-dlp/yt-dlp/issues/15120)) by [seproDev](https://github.com/seproDev) +- **web.archive**: youtube: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/7ec6b9bc40ee8a21b11cce83a09a07a37014062e) ([#15234](https://github.com/yt-dlp/yt-dlp/issues/15234)) by [seproDev](https://github.com/seproDev) +- **wistiachannel**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/0c696239ef418776ac6ba20284bd2f3976a011b4) ([#14218](https://github.com/yt-dlp/yt-dlp/issues/14218)) by [Sojiroh](https://github.com/Sojiroh) +- **xhamster**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/29e257037862f3b2ad65e6e8d2972f9ed89389e3) ([#15252](https://github.com/yt-dlp/yt-dlp/issues/15252)) by [0xvd](https://github.com/0xvd) +- **yfanefa**: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/af285016d2b14c4445109283e7c590b31542de88) ([#15032](https://github.com/yt-dlp/yt-dlp/issues/15032)) by [Haytam001](https://github.com/Haytam001) +- **youtube** + - [Add `use_ad_playback_context` extractor-arg](https://github.com/yt-dlp/yt-dlp/commit/f7acf3c1f42cc474927ecc452205d7877af36731) ([#15220](https://github.com/yt-dlp/yt-dlp/issues/15220)) by [WhatAmISupposedToPutHere](https://github.com/WhatAmISupposedToPutHere) + - [Allow `ejs` patch version to differ](https://github.com/yt-dlp/yt-dlp/commit/7bd79d92965fe9f84d7e1720eb6bb10fa9a10c77) ([#15263](https://github.com/yt-dlp/yt-dlp/issues/15263)) by [Grub4K](https://github.com/Grub4K) + - [Detect "super resolution" AI-upscaled formats](https://github.com/yt-dlp/yt-dlp/commit/4cb5e191efeebc3679f89c3c8ac819bcd511bb1f) ([#15050](https://github.com/yt-dlp/yt-dlp/issues/15050)) by [bashonly](https://github.com/bashonly) + - [Determine wait time from player response](https://github.com/yt-dlp/yt-dlp/commit/715af0c636b2b33fb3df1eb2ee37eac8262d43ac) ([#14646](https://github.com/yt-dlp/yt-dlp/issues/14646)) by [bashonly](https://github.com/bashonly), [WhatAmISupposedToPutHere](https://github.com/WhatAmISupposedToPutHere) + - [Extract all automatic caption languages](https://github.com/yt-dlp/yt-dlp/commit/419776ecf57269efb13095386a19ddc75c1f11b2) ([#15156](https://github.com/yt-dlp/yt-dlp/issues/15156)) by [bashonly](https://github.com/bashonly) + - [Improve message when no JS runtime is found](https://github.com/yt-dlp/yt-dlp/commit/1d43fa5af883f96af902a29544fc766f5e97fce6) ([#15266](https://github.com/yt-dlp/yt-dlp/issues/15266)) by [bashonly](https://github.com/bashonly) + - [Update ejs to 0.3.2](https://github.com/yt-dlp/yt-dlp/commit/0c7e4cfcaed95909d7c1c0a11b5a12881bcfdfd6) ([#15267](https://github.com/yt-dlp/yt-dlp/issues/15267)) by [bashonly](https://github.com/bashonly) + +#### Downloader changes +- [Fix playback wait time for ffmpeg downloads](https://github.com/yt-dlp/yt-dlp/commit/23f1ab346927ab73ad510fd7ba105a69e5291c66) ([#15066](https://github.com/yt-dlp/yt-dlp/issues/15066)) by [bashonly](https://github.com/bashonly) + +#### Postprocessor changes +- **ffmpeg**: [Fix uncaught error if bad --ffmpeg-location is given](https://github.com/yt-dlp/yt-dlp/commit/0eed3fe530d6ff4b668494c5b1d4d6fc1ade96f7) ([#15104](https://github.com/yt-dlp/yt-dlp/issues/15104)) by [bashonly](https://github.com/bashonly) +- **ffmpegmetadata**: [Add more tag mappings](https://github.com/yt-dlp/yt-dlp/commit/04050be583aae21f99932a674d1d2992ff016d5c) ([#14654](https://github.com/yt-dlp/yt-dlp/issues/14654)) by [garret1317](https://github.com/garret1317) + +#### Networking changes +- **Request Handler**: urllib: [Do not read after close](https://github.com/yt-dlp/yt-dlp/commit/6ee6a6fc58d6254ef944bd311e6890e208a75e98) ([#15049](https://github.com/yt-dlp/yt-dlp/issues/15049)) by [bashonly](https://github.com/bashonly) + +#### Misc. changes +- **build**: [Bump PyInstaller minimum version requirement to 6.17.0](https://github.com/yt-dlp/yt-dlp/commit/280165026886a1f1614ab527c34c66d71faa5d69) ([#15199](https://github.com/yt-dlp/yt-dlp/issues/15199)) by [bashonly](https://github.com/bashonly) +- **cleanup**: Miscellaneous: [7a52ff2](https://github.com/yt-dlp/yt-dlp/commit/7a52ff29d86efc8f3adeba977b2009ce40b8e52e) by [bashonly](https://github.com/bashonly), [oxyzenQ](https://github.com/oxyzenQ), [RezSat](https://github.com/RezSat), [seproDev](https://github.com/seproDev) +- **devscripts**: `install_deps`: [Align options/terms with PEP 735](https://github.com/yt-dlp/yt-dlp/commit/29fe515d8d3386b3406ff02bdabb967d6821bc02) ([#15200](https://github.com/yt-dlp/yt-dlp/issues/15200)) by [bashonly](https://github.com/bashonly) + ### 2025.11.12 #### Important changes @@ -64,7 +122,7 @@ yt-dlp now requires users to have an external JavaScript runtime (e.g. Deno) ins - **build**: [Bump musllinux Python version to 3.14](https://github.com/yt-dlp/yt-dlp/commit/646904cd3a79429ec5fdc43f904b3f57ae213f34) ([#14623](https://github.com/yt-dlp/yt-dlp/issues/14623)) by [bashonly](https://github.com/bashonly) - **cleanup** - Miscellaneous - - [c63b4e2](https://github.com/yt-dlp/yt-dlp/commit/c63b4e2a2b81cc78397c8709ef53ffd29bada213) by [bashonly](https://github.com/bashonly), [matyb08](https://github.com/matyb08), [sepro](https://github.com/sepro) + - [c63b4e2](https://github.com/yt-dlp/yt-dlp/commit/c63b4e2a2b81cc78397c8709ef53ffd29bada213) by [bashonly](https://github.com/bashonly), [matyb08](https://github.com/matyb08), [seproDev](https://github.com/seproDev) - [335653b](https://github.com/yt-dlp/yt-dlp/commit/335653be82d5ef999cfc2879d005397402eebec1) by [bashonly](https://github.com/bashonly), [seproDev](https://github.com/seproDev) - **devscripts**: [Improve `install_deps` script](https://github.com/yt-dlp/yt-dlp/commit/73922e66e437fb4bb618bdc119a96375081bf508) ([#14766](https://github.com/yt-dlp/yt-dlp/issues/14766)) by [bashonly](https://github.com/bashonly) - **test**: [Skip flaky tests if source unchanged](https://github.com/yt-dlp/yt-dlp/commit/ade8c2b36ff300edef87d48fd1ba835ac35c5b63) ([#14970](https://github.com/yt-dlp/yt-dlp/issues/14970)) by [bashonly](https://github.com/bashonly), [Grub4K](https://github.com/Grub4K) diff --git a/yt-dlp/Maintainers.md b/yt-dlp/Maintainers.md index 515505d882..43271e06d0 100644 --- a/yt-dlp/Maintainers.md +++ b/yt-dlp/Maintainers.md @@ -8,9 +8,7 @@ You can also find lists of all [contributors of yt-dlp](CONTRIBUTORS) and [autho Core Maintainers are responsible for reviewing and merging contributions, publishing releases, and steering the overall direction of the project. -**You can contact the core maintainers via `maintainers@yt-dlp.org`.** - -This is **NOT** a support channel. [Open an issue](https://github.com/yt-dlp/yt-dlp/issues/new/choose) if you need help or want to report a bug. +**You can contact the core maintainers via `maintainers@yt-dlp.org`.** This email address is **NOT** a support channel. [Open an issue](https://github.com/yt-dlp/yt-dlp/issues/new/choose) if you need help or want to report a bug. ### [coletdjnz](https://github.com/coletdjnz) @@ -18,6 +16,7 @@ This is **NOT** a support channel. [Open an issue](https://github.com/yt-dlp/yt- * Overhauled the networking stack and implemented support for `requests` and `curl_cffi` (`--impersonate`) HTTP clients * Reworked the plugin architecture to support installing plugins across all yt-dlp distributions (exe, pip, etc.) +* Implemented support for external JavaScript runtimes/engines * Maintains support for YouTube * Added and fixed support for various other sites @@ -25,9 +24,10 @@ This is **NOT** a support channel. [Open an issue](https://github.com/yt-dlp/yt- * Rewrote and maintains the build/release workflows and the self-updater: executables, automated/nightly/master releases, `--update-to` * Overhauled external downloader cookie handling +* Helped in implementing support for external JavaScript runtimes/engines * Added `--cookies-from-browser` support for Firefox containers -* Overhauled and maintains support for sites like Youtube, Vimeo, Twitter, TikTok, etc -* Added support for sites like Dacast, Kick, Loom, SproutVideo, Triller, Weverse, etc +* Maintains support for sites like YouTube, Vimeo, Twitter, TikTok, etc +* Added support for various sites ### [Grub4K](https://github.com/Grub4K) @@ -37,12 +37,14 @@ This is **NOT** a support channel. [Open an issue](https://github.com/yt-dlp/yt- * `--update-to`, self-updater rewrite, automated/nightly/master releases * Reworked internals like `traverse_obj`, various core refactors and bugs fixes * Implemented proper progress reporting for parallel downloads +* Implemented support for external JavaScript runtimes/engines * Improved/fixed/added Bundestag, crunchyroll, pr0gramm, Twitter, WrestleUniverse etc ### [sepro](https://github.com/seproDev) * UX improvements: Warn when ffmpeg is missing, warn when double-clicking exe +* Helped in implementing support for external JavaScript runtimes/engines * Code cleanup: Remove dead extractors, mark extractors as broken, enable/apply ruff rules * Improved/fixed/added ArdMediathek, DRTV, Floatplane, MagentaMusik, Naver, Nebula, OnDemandKorea, Vbox7 etc diff --git a/yt-dlp/Makefile b/yt-dlp/Makefile index 89aef9033b..134408d58d 100644 --- a/yt-dlp/Makefile +++ b/yt-dlp/Makefile @@ -202,9 +202,9 @@ CONTRIBUTORS: Changelog.md # The following EJS_-prefixed variables are auto-generated by devscripts/update_ejs.py # DO NOT EDIT! -EJS_VERSION = 0.3.1 -EJS_WHEEL_NAME = yt_dlp_ejs-0.3.1-py3-none-any.whl -EJS_WHEEL_HASH = sha256:a6e3548874db7c774388931752bb46c7f4642c044b2a189e56968f3d5ecab622 +EJS_VERSION = 0.3.2 +EJS_WHEEL_NAME = yt_dlp_ejs-0.3.2-py3-none-any.whl +EJS_WHEEL_HASH = sha256:f2dc6b3d1b909af1f13e021621b0af048056fca5fb07c4db6aa9bbb37a4f66a9 EJS_PY_FOLDERS = yt_dlp_ejs yt_dlp_ejs/yt yt_dlp_ejs/yt/solver EJS_PY_FILES = yt_dlp_ejs/__init__.py yt_dlp_ejs/_version.py yt_dlp_ejs/yt/__init__.py yt_dlp_ejs/yt/solver/__init__.py EJS_JS_FOLDERS = yt_dlp_ejs/yt/solver diff --git a/yt-dlp/README.md b/yt-dlp/README.md index 48f8893411..10a2290c5d 100644 --- a/yt-dlp/README.md +++ b/yt-dlp/README.md @@ -203,7 +203,7 @@ Python versions 3.10+ (CPython) and 3.11+ (PyPy) are supported. Other versions a On Windows, [Microsoft Visual C++ 2010 SP1 Redistributable Package (x86)](https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe) is also necessary to run yt-dlp. You probably already have this, but if the executable throws an error due to missing `MSVCR100.dll` you need to install it manually. --> -While all the other dependencies are optional, `ffmpeg`, `ffprobe`, `yt-dlp-ejs` and a JavaScript runtime are highly recommended +While all the other dependencies are optional, `ffmpeg`, `ffprobe`, `yt-dlp-ejs` and a supported JavaScript runtime/engine are highly recommended ### Strongly recommended @@ -215,7 +215,7 @@ While all the other dependencies are optional, `ffmpeg`, `ffprobe`, `yt-dlp-ejs` * [**yt-dlp-ejs**](https://github.com/yt-dlp/ejs) - Required for deciphering YouTube n/sig values. Licensed under [Unlicense](https://github.com/yt-dlp/ejs/blob/main/LICENSE), bundles [MIT](https://github.com/davidbonnet/astring/blob/main/LICENSE) and [ISC](https://github.com/meriyah/meriyah/blob/main/LICENSE.md) components. - A JavaScript runtime like [**deno**](https://deno.land) (recommended), [**node.js**](https://nodejs.org), [**bun**](https://bun.sh), or [**QuickJS**](https://bellard.org/quickjs/) is also required to run yt-dlp-ejs. See [the wiki](https://github.com/yt-dlp/yt-dlp/wiki/EJS). + A JavaScript runtime/engine like [**deno**](https://deno.land) (recommended), [**node.js**](https://nodejs.org), [**bun**](https://bun.sh), or [**QuickJS**](https://bellard.org/quickjs/) is also required to run yt-dlp-ejs. See [the wiki](https://github.com/yt-dlp/yt-dlp/wiki/EJS). ### Networking * [**certifi**](https://github.com/certifi/python-certifi)\* - Provides Mozilla's root certificate bundle. Licensed under [MPLv2](https://github.com/certifi/python-certifi/blob/master/LICENSE) @@ -228,7 +228,7 @@ While all the other dependencies are optional, `ffmpeg`, `ffprobe`, `yt-dlp-ejs` The following provide support for impersonating browser requests. This may be required for some sites that employ TLS fingerprinting. * [**curl_cffi**](https://github.com/lexiforest/curl_cffi) (recommended) - Python binding for [curl-impersonate](https://github.com/lexiforest/curl-impersonate). Provides impersonation targets for Chrome, Edge and Safari. Licensed under [MIT](https://github.com/lexiforest/curl_cffi/blob/main/LICENSE) - * Can be installed with the `curl-cffi` group, e.g. `pip install "yt-dlp[default,curl-cffi]"` + * Can be installed with the `curl-cffi` extra, e.g. `pip install "yt-dlp[default,curl-cffi]"` * Currently included in most builds *except* `yt-dlp` (Unix zipimport binary), `yt-dlp_x86` (Windows 32-bit) and `yt-dlp_musllinux_aarch64` @@ -265,7 +265,7 @@ To build the standalone executable, you must have Python and `pyinstaller` (plus You can run the following commands: ``` -python devscripts/install_deps.py --include-group pyinstaller +python devscripts/install_deps.py --include-extra pyinstaller python devscripts/make_lazy_extractors.py python -m bundle.pyinstaller ``` @@ -483,7 +483,7 @@ Tip: Use `CTRL`+`F` (or `Command`+`F`) to search by keywords two-letter ISO 3166-2 country code ## Video Selection: - -I, --playlist-items ITEM_SPEC Comma separated playlist_index of the items + -I, --playlist-items ITEM_SPEC Comma-separated playlist_index of the items to download. You can specify a range using "[START]:[STOP][:STEP]". For backward compatibility, START-STOP is also supported. @@ -1299,7 +1299,7 @@ The field names themselves (the part inside the parenthesis) can also have some 1. **Default**: A literal default value can be specified for when the field is empty using a `|` separator. This overrides `--output-na-placeholder`. E.g. `%(uploader|Unknown)s` -1. **More Conversions**: In addition to the normal format types `diouxXeEfFgGcrs`, yt-dlp additionally supports converting to `B` = **B**ytes, `j` = **j**son (flag `#` for pretty-printing, `+` for Unicode), `h` = HTML escaping, `l` = a comma separated **l**ist (flag `#` for `\n` newline-separated), `q` = a string **q**uoted for the terminal (flag `#` to split a list into different arguments), `D` = add **D**ecimal suffixes (e.g. 10M) (flag `#` to use 1024 as factor), and `S` = **S**anitize as filename (flag `#` for restricted) +1. **More Conversions**: In addition to the normal format types `diouxXeEfFgGcrs`, yt-dlp additionally supports converting to `B` = **B**ytes, `j` = **j**son (flag `#` for pretty-printing, `+` for Unicode), `h` = HTML escaping, `l` = a comma-separated **l**ist (flag `#` for `\n` newline-separated), `q` = a string **q**uoted for the terminal (flag `#` to split a list into different arguments), `D` = add **D**ecimal suffixes (e.g. 10M) (flag `#` to use 1024 as factor), and `S` = **S**anitize as filename (flag `#` for restricted) 1. **Unicode normalization**: The format type `U` can be used for NFC [Unicode normalization](https://docs.python.org/3/library/unicodedata.html#unicodedata.normalize). The alternate form flag (`#`) changes the normalization to NFD and the conversion flag `+` can be used for NFKC/NFKD compatibility equivalence normalization. E.g. `%(title)+.100U` is NFKC @@ -1798,8 +1798,8 @@ Metadata fields | From `track` | `track_number` `artist` | `artist`, `artists`, `creator`, `creators`, `uploader` or `uploader_id` `composer` | `composer` or `composers` -`genre` | `genre` or `genres` -`album` | `album` +`genre` | `genre`, `genres`, `categories` or `tags` +`album` | `album` or `series` `album_artist` | `album_artist` or `album_artists` `disc` | `disc_number` `show` | `series` @@ -1852,7 +1852,7 @@ The following extractors use this feature: #### youtube * `lang`: Prefer translated metadata (`title`, `description` etc) of this language code (case-sensitive). By default, the video primary language metadata is preferred, with a fallback to `en` translated. See [youtube/_base.py](https://github.com/yt-dlp/yt-dlp/blob/415b4c9f955b1a0391204bd24a7132590e7b3bdb/yt_dlp/extractor/youtube/_base.py#L402-L409) for the list of supported content language codes * `skip`: One or more of `hls`, `dash` or `translated_subs` to skip extraction of the m3u8 manifests, dash manifests and [auto-translated subtitles](https://github.com/yt-dlp/yt-dlp/issues/4090#issuecomment-1158102032) respectively -* `player_client`: Clients to extract video data from. The currently available clients are `web`, `web_safari`, `web_embedded`, `web_music`, `web_creator`, `mweb`, `ios`, `android`, `android_sdkless`, `android_vr`, `tv`, `tv_simply`, `tv_downgraded`, and `tv_embedded`. By default, `tv,android_sdkless,web` is used. If no JavaScript runtime is available, then `android_sdkless,web_safari,web` is used. If logged-in cookies are passed to yt-dlp, then `tv_downgraded,web_safari,web` is used for free accounts and `tv_downgraded,web_creator,web` is used for premium accounts. The `web_music` client is added for `music.youtube.com` URLs when logged-in cookies are used. The `web_embedded` client is added for age-restricted videos but only works if the video is embeddable. The `tv_embedded` and `web_creator` clients are added for age-restricted videos if account age-verification is required. Some clients, such as `web` and `web_music`, require a `po_token` for their formats to be downloadable. Some clients, such as `web_creator`, will only work with authentication. Not all clients support authentication via cookies. You can use `default` for the default clients, or you can use `all` for all clients (not recommended). You can prefix a client with `-` to exclude it, e.g. `youtube:player_client=default,-ios` +* `player_client`: Clients to extract video data from. The currently available clients are `web`, `web_safari`, `web_embedded`, `web_music`, `web_creator`, `mweb`, `ios`, `android`, `android_sdkless`, `android_vr`, `tv`, `tv_simply`, `tv_downgraded`, and `tv_embedded`. By default, `tv,android_sdkless,web` is used. If no JavaScript runtime/engine is available, then `android_sdkless,web_safari,web` is used. If logged-in cookies are passed to yt-dlp, then `tv_downgraded,web_safari,web` is used for free accounts and `tv_downgraded,web_creator,web` is used for premium accounts. The `web_music` client is added for `music.youtube.com` URLs when logged-in cookies are used. The `web_embedded` client is added for age-restricted videos but only works if the video is embeddable. The `tv_embedded` and `web_creator` clients are added for age-restricted videos if account age-verification is required. Some clients, such as `web` and `web_music`, require a `po_token` for their formats to be downloadable. Some clients, such as `web_creator`, will only work with authentication. Not all clients support authentication via cookies. You can use `default` for the default clients, or you can use `all` for all clients (not recommended). You can prefix a client with `-` to exclude it, e.g. `youtube:player_client=default,-ios` * `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player), `initial_data` (skip initial data/next ep request). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause issues such as missing formats or metadata. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) and [#12826](https://github.com/yt-dlp/yt-dlp/issues/12826) for more details * `webpage_skip`: Skip extraction of embedded webpage data. One or both of `player_response`, `initial_data`. These options are for testing purposes and don't skip any network requests * `player_params`: YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp. @@ -1867,14 +1867,14 @@ The following extractors use this feature: * `raise_incomplete_data`: `Incomplete Data Received` raises an error instead of reporting a warning * `data_sync_id`: Overrides the account Data Sync ID used in Innertube API requests. This may be needed if you are using an account with `youtube:player_skip=webpage,configs` or `youtubetab:skip=webpage` * `visitor_data`: Overrides the Visitor Data used in Innertube API requests. This should be used with `player_skip=webpage,configs` and without cookies. Note: this may have adverse effects if used improperly. If a session from a browser is wanted, you should pass cookies instead (which contain the Visitor ID) -* `po_token`: Proof of Origin (PO) Token(s) to use. Comma seperated list of PO Tokens in the format `CLIENT.CONTEXT+PO_TOKEN`, e.g. `youtube:po_token=web.gvs+XXX,web.player=XXX,web_safari.gvs+YYY`. Context can be any of `gvs` (Google Video Server URLs), `player` (Innertube player request) or `subs` (Subtitles) +* `po_token`: Proof of Origin (PO) Token(s) to use. Comma-separated list of PO Tokens in the format `CLIENT.CONTEXT+PO_TOKEN`, e.g. `youtube:po_token=web.gvs+XXX,web.player=XXX,web_safari.gvs+YYY`. Context can be any of `gvs` (Google Video Server URLs), `player` (Innertube player request) or `subs` (Subtitles) * `pot_trace`: Enable debug logging for PO Token fetching. Either `true` or `false` (default) * `fetch_pot`: Policy to use for fetching a PO Token from providers. One of `always` (always try fetch a PO Token regardless if the client requires one for the given context), `never` (never fetch a PO Token), or `auto` (default; only fetch a PO Token if the client requires one for the given context) * `jsc_trace`: Enable debug logging for JS Challenge fetching. Either `true` or `false` (default) * `use_ad_playback_context`: Skip preroll ads to eliminate the mandatory wait period before download. Do NOT use this when passing premium account cookies to yt-dlp, as it will result in a loss of premium formats. Only effective with the `web`, `web_safari`, `web_music` and `mweb` player clients. Either `true` or `false` (default) #### youtube-ejs -* `jitless`: Run suported Javascript engines in JIT-less mode. Supported runtimes are `deno`, `node` and `bun`. Provides better security at the cost of performance/speed. Do note that `node` and `bun` are still considered unsecure. Either `true` or `false` (default) +* `jitless`: Run supported Javascript engines in JIT-less mode. Supported runtimes are `deno`, `node` and `bun`. Provides better security at the cost of performance/speed. Do note that `node` and `bun` are still considered insecure. Either `true` or `false` (default) #### youtubepot-webpo * `bind_to_visitor_id`: Whether to use the Visitor ID instead of Visitor Data for caching WebPO tokens. Either `true` (default) or `false` diff --git a/yt-dlp/bundle/docker/linux/build.sh b/yt-dlp/bundle/docker/linux/build.sh index b30d40980e..d6d1791865 100755 --- a/yt-dlp/bundle/docker/linux/build.sh +++ b/yt-dlp/bundle/docker/linux/build.sh @@ -15,12 +15,12 @@ function venvpy { } INCLUDES=( - --include-group pyinstaller - --include-group secretstorage + --include-extra pyinstaller + --include-extra secretstorage ) if [[ -z "${EXCLUDE_CURL_CFFI:-}" ]]; then - INCLUDES+=(--include-group curl-cffi) + INCLUDES+=(--include-extra curl-cffi) fi runpy -m venv /yt-dlp-build-venv @@ -28,7 +28,7 @@ runpy -m venv /yt-dlp-build-venv source /yt-dlp-build-venv/bin/activate # Inside the venv we use venvpy instead of runpy venvpy -m ensurepip --upgrade --default-pip -venvpy -m devscripts.install_deps --only-optional-groups --include-group build +venvpy -m devscripts.install_deps --omit-default --include-extra build venvpy -m devscripts.install_deps "${INCLUDES[@]}" venvpy -m devscripts.make_lazy_extractors venvpy devscripts/update-version.py -c "${CHANNEL}" -r "${ORIGIN}" "${VERSION}" diff --git a/yt-dlp/devscripts/changelog_override.json b/yt-dlp/devscripts/changelog_override.json index ba3f9518fb..ae4c1ade38 100644 --- a/yt-dlp/devscripts/changelog_override.json +++ b/yt-dlp/devscripts/changelog_override.json @@ -319,5 +319,11 @@ "action": "add", "when": "6224a3898821965a7d6a2cb9cc2de40a0fd6e6bc", "short": "[priority] **An external JavaScript runtime is now required for full YouTube support**\nyt-dlp now requires users to have an external JavaScript runtime (e.g. Deno) installed in order to solve the JavaScript challenges presented by YouTube. [Read more](https://github.com/yt-dlp/yt-dlp/issues/15012)" + }, + { + "action": "change", + "when": "c63b4e2a2b81cc78397c8709ef53ffd29bada213", + "short": "[cleanup] Misc (#14767)", + "authors": ["bashonly", "seproDev", "matyb08"] } ] diff --git a/yt-dlp/devscripts/install_deps.py b/yt-dlp/devscripts/install_deps.py index 07c646a4c0..762213e2f9 100755 --- a/yt-dlp/devscripts/install_deps.py +++ b/yt-dlp/devscripts/install_deps.py @@ -25,16 +25,16 @@ def parse_args(): '-e', '--exclude-dependency', metavar='DEPENDENCY', action='append', help='exclude a dependency (can be used multiple times)') parser.add_argument( - '-i', '--include-group', metavar='GROUP', action='append', - help='include an optional dependency group (can be used multiple times)') + '-i', '--include-extra', metavar='EXTRA', action='append', + help='include an extra/optional-dependencies list (can be used multiple times)') parser.add_argument( '-c', '--cherry-pick', metavar='DEPENDENCY', action='append', help=( 'only include a specific dependency from the resulting dependency list ' '(can be used multiple times)')) parser.add_argument( - '-o', '--only-optional-groups', action='store_true', - help='omit default dependencies unless the "default" group is specified with --include-group') + '-o', '--omit-default', action='store_true', + help='omit the "default" extra unless it is explicitly included (it is included by default)') parser.add_argument( '-p', '--print', action='store_true', help='only print requirements to stdout') @@ -51,27 +51,27 @@ def uniq(arg) -> dict[str, None]: def main(): args = parse_args() project_table = parse_toml(read_file(args.input))['project'] - recursive_pattern = re.compile(rf'{project_table["name"]}\[(?P[\w-]+)\]') - optional_groups = project_table['optional-dependencies'] + recursive_pattern = re.compile(rf'{project_table["name"]}\[(?P[\w-]+)\]') + extras = project_table['optional-dependencies'] excludes = uniq(args.exclude_dependency) only_includes = uniq(args.cherry_pick) - include_groups = uniq(args.include_group) + include_extras = uniq(args.include_extra) - def yield_deps(group): - for dep in group: + def yield_deps(extra): + for dep in extra: if mobj := recursive_pattern.fullmatch(dep): - yield from optional_groups.get(mobj.group('group_name'), ()) + yield from extras.get(mobj.group('extra_name'), ()) else: yield dep targets = {} - if not args.only_optional_groups: + if not args.omit_default: # legacy: 'dependencies' is empty now targets.update(dict.fromkeys(project_table['dependencies'])) - targets.update(dict.fromkeys(yield_deps(optional_groups['default']))) + targets.update(dict.fromkeys(yield_deps(extras['default']))) - for include in filter(None, map(optional_groups.get, include_groups)): + for include in filter(None, map(extras.get, include_extras)): targets.update(dict.fromkeys(yield_deps(include))) def target_filter(target): diff --git a/yt-dlp/devscripts/make_changelog.py b/yt-dlp/devscripts/make_changelog.py index 88dbf74e4f..7b42f06607 100644 --- a/yt-dlp/devscripts/make_changelog.py +++ b/yt-dlp/devscripts/make_changelog.py @@ -251,7 +251,13 @@ class CommitRange: ''', re.VERBOSE | re.DOTALL) EXTRACTOR_INDICATOR_RE = re.compile(r'(?:Fix|Add)\s+Extractors?', re.IGNORECASE) REVERT_RE = re.compile(r'(?:\[[^\]]+\]\s+)?(?i:Revert)\s+([\da-f]{40})') - FIXES_RE = re.compile(r'(?i:(?:bug\s*)?fix(?:es)?(?:\s+bugs?)?(?:\s+in|\s+for)?|Improve)\s+([\da-f]{40})') + FIXES_RE = re.compile(r''' + (?i: + (?:bug\s*)?fix(?:es)?(?: + \s+(?:bugs?|regression(?:\s+introduced)?) + )?(?:\s+(?:in|for|from|by))? + |Improve + )\s+([\da-f]{40})''', re.VERBOSE) UPSTREAM_MERGE_RE = re.compile(r'Update to ytdl-commit-([\da-f]+)') def __init__(self, start, end, default_author=None): diff --git a/yt-dlp/pyproject.toml b/yt-dlp/pyproject.toml index d06e71d74b..79313ae2ec 100644 --- a/yt-dlp/pyproject.toml +++ b/yt-dlp/pyproject.toml @@ -56,7 +56,7 @@ default = [ "requests>=2.32.2,<3", "urllib3>=2.0.2,<3", "websockets>=13.0", - "yt-dlp-ejs==0.3.1", + "yt-dlp-ejs==0.3.2", ] curl-cffi = [ "curl-cffi>=0.5.10,!=0.6.*,!=0.7.*,!=0.8.*,!=0.9.*,<0.14; implementation_name=='cpython'", diff --git a/yt-dlp/supportedsites.md b/yt-dlp/supportedsites.md index 9ab6d26335..ee252ec3bf 100644 --- a/yt-dlp/supportedsites.md +++ b/yt-dlp/supportedsites.md @@ -50,8 +50,10 @@ The only reliable way to check if a site is supported is to try it. - **aenetworks:collection** - **aenetworks:show** - **AeonCo** + - **agalega:videos** - **AirTV** - **AitubeKZVideo** + - **Alibaba** - **AliExpressLive** - **AlJazeera** - **Allocine** @@ -190,6 +192,7 @@ The only reliable way to check if a site is supported is to try it. - **Biography** - **BitChute** - **BitChuteChannel** + - **Bitmovin** - **BlackboardCollaborate** - **BlackboardCollaborateLaunch** - **BleacherReport**: (**Currently broken**) @@ -731,7 +734,7 @@ The only reliable way to check if a site is supported is to try it. - **loc**: Library of Congress - **Loco** - **loom** - - **loom:folder** + - **loom:folder**: (**Currently broken**) - **LoveHomePorn** - **LRTRadio** - **LRTStream** @@ -762,7 +765,8 @@ The only reliable way to check if a site is supported is to try it. - **massengeschmack.tv** - **Masters** - **MatchTV** - - **Mave** + - **mave** + - **mave:channel** - **MBN**: mbn.co.kr (매일방송) - **MDR**: MDR.DE - **MedalTV** @@ -895,6 +899,8 @@ The only reliable way to check if a site is supported is to try it. - **NerdCubedFeed** - **Nest** - **NestClip** + - **NetAppCollection** + - **NetAppVideo** - **netease:album**: 网易云音乐 - 专辑 - **netease:djradio**: 网易云音乐 - 电台 - **netease:mv**: 网易云音乐 - MV @@ -962,6 +968,7 @@ The only reliable way to check if a site is supported is to try it. - **Nova**: TN.cz, Prásk.tv, Nova.cz, Novaplus.cz, FANDA.tv, Krásná.cz and Doma.cz - **NovaEmbed** - **NovaPlay** + - **NowCanal** - **nowness** - **nowness:playlist** - **nowness:series** @@ -1373,7 +1380,7 @@ The only reliable way to check if a site is supported is to try it. - **Spiegel** - **Sport5** - **SportBox**: (**Currently broken**) - - **SportDeutschland** + - **sporteurope** - **Spreaker** - **SpreakerShow** - **SpringboardPlatform** @@ -1461,6 +1468,8 @@ The only reliable way to check if a site is supported is to try it. - **TFO**: (**Currently broken**) - **theatercomplextown:ppv**: [*theatercomplextown*](## "netrc machine") - **theatercomplextown:vod**: [*theatercomplextown*](## "netrc machine") + - **TheChosen** + - **TheChosenGroup** - **TheGuardianPodcast** - **TheGuardianPodcastPlaylist** - **TheHighWire** @@ -1778,6 +1787,7 @@ The only reliable way to check if a site is supported is to try it. - **YapFiles**: (**Currently broken**) - **Yappy**: (**Currently broken**) - **YappyProfile** + - **yfanefa** - **YleAreena** - **YouJizz** - **youku**: 优酷 diff --git a/yt-dlp/test/test_utils.py b/yt-dlp/test/test_utils.py index 0865b39810..72f0eb7f76 100644 --- a/yt-dlp/test/test_utils.py +++ b/yt-dlp/test/test_utils.py @@ -1403,6 +1403,9 @@ class TestUtil(unittest.TestCase): self.assertEqual(version_tuple('1'), (1,)) self.assertEqual(version_tuple('10.23.344'), (10, 23, 344)) self.assertEqual(version_tuple('10.1-6'), (10, 1, 6)) # avconv style + self.assertEqual(version_tuple('invalid', lenient=True), (-1,)) + self.assertEqual(version_tuple('1.2.3', lenient=True), (1, 2, 3)) + self.assertEqual(version_tuple('12.34-something', lenient=True), (12, 34, -1)) def test_detect_exe_version(self): self.assertEqual(detect_exe_version('''ffmpeg version 1.2.1 diff --git a/yt-dlp/yt_dlp/cookies.py b/yt-dlp/yt_dlp/cookies.py index 5f7db7f386..23f90d6109 100644 --- a/yt-dlp/yt_dlp/cookies.py +++ b/yt-dlp/yt_dlp/cookies.py @@ -212,9 +212,16 @@ def _firefox_browser_dirs(): else: yield from map(os.path.expanduser, ( + # New installations of FF147+ respect the XDG base directory specification + # Ref: https://bugzilla.mozilla.org/show_bug.cgi?id=259356 + os.path.join(_config_home(), 'mozilla/firefox'), + # Existing FF version<=146 installations '~/.mozilla/firefox', - '~/snap/firefox/common/.mozilla/firefox', + # Flatpak XDG: https://docs.flatpak.org/en/latest/conventions.html#xdg-base-directories + '~/.var/app/org.mozilla.firefox/config/mozilla/firefox', '~/.var/app/org.mozilla.firefox/.mozilla/firefox', + # Snap installations do not respect the XDG base directory specification + '~/snap/firefox/common/.mozilla/firefox', )) diff --git a/yt-dlp/yt_dlp/extractor/youtube/_video.py b/yt-dlp/yt_dlp/extractor/youtube/_video.py index 24c4458d61..0ea3729a3d 100644 --- a/yt-dlp/yt_dlp/extractor/youtube/_video.py +++ b/yt-dlp/yt_dlp/extractor/youtube/_video.py @@ -2914,10 +2914,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor): if not (requested_clients or excluded_clients) and default_clients == self._DEFAULT_JSLESS_CLIENTS: self.report_warning( - f'No supported JavaScript runtime could be found. YouTube extraction without ' - f'a JS runtime has been deprecated, and some formats may be missing. ' - f'See {_EJS_WIKI_URL} for details on installing one. To silence this warning, ' - f'you can use --extractor-args "youtube:player_client=default"', only_once=True) + f'No supported JavaScript runtime could be found. Only deno is enabled by default; ' + f'to use another runtime add --js-runtimes RUNTIME[:PATH] to your command/config. ' + f'YouTube extraction without a JS runtime has been deprecated, and some formats may be missing. ' + f'See {_EJS_WIKI_URL} for details on installing one', only_once=True) if not requested_clients: requested_clients.extend(default_clients) diff --git a/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/ejs.py b/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/ejs.py index 52d7ecf170..11c8e7ac52 100644 --- a/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/ejs.py +++ b/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/ejs.py @@ -21,6 +21,7 @@ from yt_dlp.extractor.youtube.jsc.provider import ( ) from yt_dlp.extractor.youtube.pot._provider import configuration_arg from yt_dlp.extractor.youtube.pot.provider import provider_bug_report_message +from yt_dlp.utils import version_tuple from yt_dlp.utils._jsruntime import JsRuntimeInfo if _has_ejs: @@ -223,7 +224,8 @@ class EJSBaseJCP(JsChallengeProvider): skipped_components.append(script) continue if not self.is_dev: - if script.version != self._SCRIPT_VERSION: + # Matching patch version is expected to have same hash + if version_tuple(script.version, lenient=True)[:2] != version_tuple(self._SCRIPT_VERSION, lenient=True)[:2]: self.logger.warning( f'Challenge solver {script_type.value} script version {script.version} ' f'is not supported (source: {script.source.value}, variant: {script.variant}, supported version: {self._SCRIPT_VERSION})') diff --git a/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/vendor/_info.py b/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/vendor/_info.py index 1fe88a6c26..5e42ce3e50 100644 --- a/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/vendor/_info.py +++ b/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/vendor/_info.py @@ -1,6 +1,6 @@ # This file is generated by devscripts/update_ejs.py. DO NOT MODIFY! -VERSION = '0.3.1' +VERSION = '0.3.2' HASHES = { 'yt.solver.bun.lib.js': '6ff45e94de9f0ea936a183c48173cfa9ce526ee4b7544cd556428427c1dd53c8073ef0174e79b320252bf0e7c64b0032cc1cf9c4358f3fda59033b7caa01c241', 'yt.solver.core.js': '0cd96b2d3f319dfa62cae689efa7d930ef1706e95f5921794db5089b2262957ec0a17d73938d8975ea35d0309cbfb4c8e4418d5e219837215eee242890c8b64d', diff --git a/yt-dlp/yt_dlp/options.py b/yt-dlp/yt_dlp/options.py index 96c3a8a5d1..6b669d7cc0 100644 --- a/yt-dlp/yt_dlp/options.py +++ b/yt-dlp/yt_dlp/options.py @@ -689,7 +689,7 @@ def create_parser(): '-I', '--playlist-items', dest='playlist_items', metavar='ITEM_SPEC', default=None, help=( - 'Comma separated playlist_index of the items to download. ' + 'Comma-separated playlist_index of the items to download. ' 'You can specify a range using "[START]:[STOP][:STEP]". For backward compatibility, START-STOP is also supported. ' 'Use negative indices to count from the right and negative STEP to download in reverse order. ' 'E.g. "-I 1:3,7,-5::2" used on a playlist of size 15 will download the items at index 1,2,3,7,11,13,15')) diff --git a/yt-dlp/yt_dlp/postprocessor/ffmpeg.py b/yt-dlp/yt_dlp/postprocessor/ffmpeg.py index a850bdd102..0de39351e5 100644 --- a/yt-dlp/yt_dlp/postprocessor/ffmpeg.py +++ b/yt-dlp/yt_dlp/postprocessor/ffmpeg.py @@ -750,8 +750,8 @@ class FFmpegMetadataPP(FFmpegPostProcessor): add('track', 'track_number') add('artist', ('artist', 'artists', 'creator', 'creators', 'uploader', 'uploader_id')) add('composer', ('composer', 'composers')) - add('genre', ('genre', 'genres')) - add('album') + add('genre', ('genre', 'genres', 'categories', 'tags')) + add('album', ('album', 'series')) add('album_artist', ('album_artist', 'album_artists')) add('disc', 'disc_number') add('show', 'series') diff --git a/yt-dlp/yt_dlp/utils/_jsruntime.py b/yt-dlp/yt_dlp/utils/_jsruntime.py index 94db52bf19..4ea230da42 100644 --- a/yt-dlp/yt_dlp/utils/_jsruntime.py +++ b/yt-dlp/yt_dlp/utils/_jsruntime.py @@ -6,12 +6,7 @@ import functools import os.path import sys -from ._utils import _get_exe_version_output, detect_exe_version, int_or_none - - -def _runtime_version_tuple(v): - # NB: will return (0,) if `v` is an invalid version string - return tuple(int_or_none(x, default=0) for x in v.split('.')) +from ._utils import _get_exe_version_output, detect_exe_version, version_tuple _FALLBACK_PATHEXT = ('.COM', '.EXE', '.BAT', '.CMD') @@ -92,7 +87,7 @@ class DenoJsRuntime(JsRuntime): if not out: return None version = detect_exe_version(out, r'^deno (\S+)', 'unknown') - vt = _runtime_version_tuple(version) + vt = version_tuple(version, lenient=True) return JsRuntimeInfo( name='deno', path=path, version=version, version_tuple=vt, supported=vt >= self.MIN_SUPPORTED_VERSION) @@ -107,7 +102,7 @@ class BunJsRuntime(JsRuntime): if not out: return None version = detect_exe_version(out, r'^(\S+)', 'unknown') - vt = _runtime_version_tuple(version) + vt = version_tuple(version, lenient=True) return JsRuntimeInfo( name='bun', path=path, version=version, version_tuple=vt, supported=vt >= self.MIN_SUPPORTED_VERSION) @@ -122,7 +117,7 @@ class NodeJsRuntime(JsRuntime): if not out: return None version = detect_exe_version(out, r'^v(\S+)', 'unknown') - vt = _runtime_version_tuple(version) + vt = version_tuple(version, lenient=True) return JsRuntimeInfo( name='node', path=path, version=version, version_tuple=vt, supported=vt >= self.MIN_SUPPORTED_VERSION) @@ -140,7 +135,7 @@ class QuickJsRuntime(JsRuntime): is_ng = 'QuickJS-ng' in out version = detect_exe_version(out, r'^QuickJS(?:-ng)?\s+version\s+(\S+)', 'unknown') - vt = _runtime_version_tuple(version.replace('-', '.')) + vt = version_tuple(version, lenient=True) if is_ng: return JsRuntimeInfo( name='quickjs-ng', path=path, version=version, version_tuple=vt, diff --git a/yt-dlp/yt_dlp/utils/_utils.py b/yt-dlp/yt_dlp/utils/_utils.py index 65cd2373ce..b0ca950248 100644 --- a/yt-dlp/yt_dlp/utils/_utils.py +++ b/yt-dlp/yt_dlp/utils/_utils.py @@ -2895,8 +2895,9 @@ def limit_length(s, length): return s -def version_tuple(v): - return tuple(int(e) for e in re.split(r'[-.]', v)) +def version_tuple(v, *, lenient=False): + parse = int_or_none(default=-1) if lenient else int + return tuple(parse(e) for e in re.split(r'[-.]', v)) def is_outdated_version(version, limit, assume_new=True): diff --git a/yt-dlp/yt_dlp/version.py b/yt-dlp/yt_dlp/version.py index eee5324ac2..2d4d80901c 100644 --- a/yt-dlp/yt_dlp/version.py +++ b/yt-dlp/yt_dlp/version.py @@ -1,8 +1,8 @@ # Autogenerated by devscripts/update-version.py -__version__ = '2025.11.12' +__version__ = '2025.12.08' -RELEASE_GIT_HEAD = '335653be82d5ef999cfc2879d005397402eebec1' +RELEASE_GIT_HEAD = '7a52ff29d86efc8f3adeba977b2009ce40b8e52e' VARIANT = None @@ -12,4 +12,4 @@ CHANNEL = 'stable' ORIGIN = 'yt-dlp/yt-dlp' -_pkg_version = '2025.11.12' +_pkg_version = '2025.12.08'