From ca42e1b16e5fba7eba47d537d2c6ed7c79fa381e Mon Sep 17 00:00:00 2001 From: "github-action[bot]" Date: Wed, 20 Aug 2025 20:53:09 +0200 Subject: [PATCH] Update On Wed Aug 20 20:53:09 CEST 2025 --- .github/update.log | 1 + clash-meta/adapter/outbound/wireguard.go | 92 +- clash-meta/docs/config.yaml | 10 + clash-meta/go.mod | 4 +- clash-meta/go.sum | 8 +- clash-meta/transport/vless/conn.go | 26 +- clash-meta/transport/vless/vision/conn.go | 28 +- clash-meta/transport/vless/vision/vision.go | 28 +- clash-nyanpasu/backend/Cargo.lock | 96 +- clash-nyanpasu/frontend/nyanpasu/package.json | 6 +- clash-nyanpasu/frontend/ui/package.json | 4 +- clash-nyanpasu/manifest/version.json | 6 +- clash-nyanpasu/pnpm-lock.yaml | 215 +++-- filebrowser/frontend/src/i18n/fr.json | 64 +- lede/feeds.conf.default | 4 +- lede/include/kernel-6.1 | 4 +- lede/include/kernel-6.12 | 4 +- lede/include/kernel-6.6 | 4 +- ...-genlmsg_multicast_allns-upstream-ba.patch | 24 +- lede/package/lean/default-settings/Makefile | 2 +- .../files/zzz-default-settings | 6 +- ...-2-qca-nss-ecm-support-PPPOE-offload.patch | 2 +- ...-2-qca-nss-ecm-support-PPPOE-offload.patch | 2 +- mieru/pkg/protocol/session.go | 4 +- mieru/pkg/socks5/copy.go | 30 +- mieru/pkg/socks5/request.go | 54 +- mieru/pkg/socks5/socks5.go | 6 +- mihomo/adapter/outbound/wireguard.go | 92 +- mihomo/docs/config.yaml | 10 + mihomo/go.mod | 4 +- mihomo/go.sum | 8 +- mihomo/transport/vless/conn.go | 26 +- mihomo/transport/vless/vision/conn.go | 28 +- mihomo/transport/vless/vision/vision.go | 28 +- openwrt-packages/luci-app-amlogic/Makefile | 2 +- .../root/usr/sbin/openwrt-kernel | 7 +- .../passwall/client/node_subscribe_config.lua | 6 +- shadowsocks-rust/Cargo.lock | 38 +- sing-box/common/badtls/read_wait.go | 4 + sing-box/common/badtls/read_wait_utls.go | 24 +- sing-box/common/tls/reality_client.go | 8 + sing-box/common/tlsfragment/conn.go | 3 + sing-box/common/tlsfragment/wait_stub.go | 4 + sing-box/common/tlsfragment/wait_windows.go | 3 + sing-box/dns/transport/dhcp/dhcp.go | 12 +- sing-box/dns/transport/dhcp/dhcp_shared.go | 2 +- sing-box/docs/changelog.md | 2 +- .../resources/view/homeproxy/node.js | 9 +- .../root/etc/init.d/homeproxy | 6 + .../passwall/client/node_subscribe_config.lua | 6 +- small/nikki/files/ucode/hijack.ut | 12 +- small/sing-box/Makefile | 4 +- small/v2ray-geodata/Makefile | 4 +- xray-core/go.mod | 4 +- xray-core/go.sum | 30 +- xray-core/transport/internet/dialer.go | 3 + yt-dlp/.github/workflows/build.yml | 124 +-- yt-dlp/CONTRIBUTORS | 6 + yt-dlp/Changelog.md | 53 +- yt-dlp/README.md | 8 +- yt-dlp/devscripts/changelog_override.json | 2 +- yt-dlp/pyproject.toml | 1 + yt-dlp/supportedsites.md | 38 +- yt-dlp/test/test_subtitles.py | 18 - yt-dlp/test/test_utils.py | 37 + yt-dlp/yt_dlp/YoutubeDL.py | 4 +- yt-dlp/yt_dlp/downloader/common.py | 16 +- yt-dlp/yt_dlp/extractor/_extractors.py | 37 +- yt-dlp/yt_dlp/extractor/atvat.py | 5 +- yt-dlp/yt_dlp/extractor/bet.py | 118 +-- yt-dlp/yt_dlp/extractor/bilibili.py | 47 +- yt-dlp/yt_dlp/extractor/cmt.py | 55 -- yt-dlp/yt_dlp/extractor/comedycentral.py | 68 +- yt-dlp/yt_dlp/extractor/common.py | 1 + yt-dlp/yt_dlp/extractor/faulio.py | 152 +++- yt-dlp/yt_dlp/extractor/francetv.py | 8 +- yt-dlp/yt_dlp/extractor/medialaan.py | 207 +++-- yt-dlp/yt_dlp/extractor/mtv.py | 830 +++++------------- yt-dlp/yt_dlp/extractor/nick.py | 256 +----- yt-dlp/yt_dlp/extractor/nrk.py | 36 +- yt-dlp/yt_dlp/extractor/puhutv.py | 58 +- yt-dlp/yt_dlp/extractor/southpark.py | 368 +++++--- yt-dlp/yt_dlp/extractor/spike.py | 46 - yt-dlp/yt_dlp/extractor/steam.py | 229 ++--- yt-dlp/yt_dlp/extractor/svt.py | 32 +- yt-dlp/yt_dlp/extractor/tiktok.py | 34 +- yt-dlp/yt_dlp/extractor/tvland.py | 37 - yt-dlp/yt_dlp/extractor/vh1.py | 65 +- yt-dlp/yt_dlp/extractor/vrt.py | 6 +- yt-dlp/yt_dlp/extractor/vtm.py | 78 +- yt-dlp/yt_dlp/extractor/youtube/_base.py | 4 - yt-dlp/yt_dlp/extractor/youtube/_video.py | 32 +- yt-dlp/yt_dlp/update.py | 1 + yt-dlp/yt_dlp/utils/_deprecated.py | 18 + yt-dlp/yt_dlp/utils/_utils.py | 32 +- yt-dlp/yt_dlp/version.py | 6 +- 96 files changed, 2231 insertions(+), 2065 deletions(-) delete mode 100644 yt-dlp/yt_dlp/extractor/cmt.py delete mode 100644 yt-dlp/yt_dlp/extractor/spike.py delete mode 100644 yt-dlp/yt_dlp/extractor/tvland.py diff --git a/.github/update.log b/.github/update.log index 659ecc6d50..46b0b6f095 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1095,3 +1095,4 @@ Update On Sat Aug 16 20:36:37 CEST 2025 Update On Sun Aug 17 20:39:03 CEST 2025 Update On Mon Aug 18 20:42:30 CEST 2025 Update On Tue Aug 19 20:36:45 CEST 2025 +Update On Wed Aug 20 20:53:01 CEST 2025 diff --git a/clash-meta/adapter/outbound/wireguard.go b/clash-meta/adapter/outbound/wireguard.go index 84ba1dbb12..f59033febe 100644 --- a/clash-meta/adapter/outbound/wireguard.go +++ b/clash-meta/adapter/outbound/wireguard.go @@ -87,15 +87,26 @@ type WireGuardPeerOption struct { } type AmneziaWGOption struct { - JC int `proxy:"jc"` - JMin int `proxy:"jmin"` - JMax int `proxy:"jmax"` - S1 int `proxy:"s1"` - S2 int `proxy:"s2"` - H1 uint32 `proxy:"h1"` - H2 uint32 `proxy:"h2"` - H3 uint32 `proxy:"h3"` - H4 uint32 `proxy:"h4"` + JC int `proxy:"jc,omitempty"` + JMin int `proxy:"jmin,omitempty"` + JMax int `proxy:"jmax,omitempty"` + S1 int `proxy:"s1,omitempty"` + S2 int `proxy:"s2,omitempty"` + H1 uint32 `proxy:"h1,omitempty"` + H2 uint32 `proxy:"h2,omitempty"` + H3 uint32 `proxy:"h3,omitempty"` + H4 uint32 `proxy:"h4,omitempty"` + + // AmneziaWG v1.5 + I1 string `proxy:"i1,omitempty"` + I2 string `proxy:"i2,omitempty"` + I3 string `proxy:"i3,omitempty"` + I4 string `proxy:"i4,omitempty"` + I5 string `proxy:"i5,omitempty"` + J1 string `proxy:"j1,omitempty"` + J2 string `proxy:"j2,omitempty"` + J3 string `proxy:"j3,omitempty"` + Itime int64 `proxy:"itime,omitempty"` } type wgSingErrorHandler struct { @@ -386,15 +397,60 @@ func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, er if !updateOnly { ipcConf += "private_key=" + w.option.PrivateKey + "\n" if w.option.AmneziaWGOption != nil { - ipcConf += "jc=" + strconv.Itoa(w.option.AmneziaWGOption.JC) + "\n" - ipcConf += "jmin=" + strconv.Itoa(w.option.AmneziaWGOption.JMin) + "\n" - ipcConf += "jmax=" + strconv.Itoa(w.option.AmneziaWGOption.JMax) + "\n" - ipcConf += "s1=" + strconv.Itoa(w.option.AmneziaWGOption.S1) + "\n" - ipcConf += "s2=" + strconv.Itoa(w.option.AmneziaWGOption.S2) + "\n" - ipcConf += "h1=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H1), 10) + "\n" - ipcConf += "h2=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H2), 10) + "\n" - ipcConf += "h3=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H3), 10) + "\n" - ipcConf += "h4=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H4), 10) + "\n" + if w.option.AmneziaWGOption.JC != 0 { + ipcConf += "jc=" + strconv.Itoa(w.option.AmneziaWGOption.JC) + "\n" + } + if w.option.AmneziaWGOption.JMin != 0 { + ipcConf += "jmin=" + strconv.Itoa(w.option.AmneziaWGOption.JMin) + "\n" + } + if w.option.AmneziaWGOption.JMax != 0 { + ipcConf += "jmax=" + strconv.Itoa(w.option.AmneziaWGOption.JMax) + "\n" + } + if w.option.AmneziaWGOption.S1 != 0 { + ipcConf += "s1=" + strconv.Itoa(w.option.AmneziaWGOption.S1) + "\n" + } + if w.option.AmneziaWGOption.S2 != 0 { + ipcConf += "s2=" + strconv.Itoa(w.option.AmneziaWGOption.S2) + "\n" + } + if w.option.AmneziaWGOption.H1 != 0 { + ipcConf += "h1=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H1), 10) + "\n" + } + if w.option.AmneziaWGOption.H2 != 0 { + ipcConf += "h2=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H2), 10) + "\n" + } + if w.option.AmneziaWGOption.H3 != 0 { + ipcConf += "h3=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H3), 10) + "\n" + } + if w.option.AmneziaWGOption.H4 != 0 { + ipcConf += "h4=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H4), 10) + "\n" + } + if w.option.AmneziaWGOption.I1 != "" { + ipcConf += "i1=" + w.option.AmneziaWGOption.I1 + "\n" + } + if w.option.AmneziaWGOption.I2 != "" { + ipcConf += "i2=" + w.option.AmneziaWGOption.I2 + "\n" + } + if w.option.AmneziaWGOption.I3 != "" { + ipcConf += "i3=" + w.option.AmneziaWGOption.I3 + "\n" + } + if w.option.AmneziaWGOption.I4 != "" { + ipcConf += "i4=" + w.option.AmneziaWGOption.I4 + "\n" + } + if w.option.AmneziaWGOption.I5 != "" { + ipcConf += "i5=" + w.option.AmneziaWGOption.I5 + "\n" + } + if w.option.AmneziaWGOption.J1 != "" { + ipcConf += "j1=" + w.option.AmneziaWGOption.J1 + "\n" + } + if w.option.AmneziaWGOption.J2 != "" { + ipcConf += "j2=" + w.option.AmneziaWGOption.J2 + "\n" + } + if w.option.AmneziaWGOption.J3 != "" { + ipcConf += "j3=" + w.option.AmneziaWGOption.J3 + "\n" + } + if w.option.AmneziaWGOption.Itime != 0 { + ipcConf += "itime=" + strconv.FormatInt(int64(w.option.AmneziaWGOption.Itime), 10) + "\n" + } } } if len(w.option.Peers) > 0 { diff --git a/clash-meta/docs/config.yaml b/clash-meta/docs/config.yaml index 445c61a388..80af843bf9 100644 --- a/clash-meta/docs/config.yaml +++ b/clash-meta/docs/config.yaml @@ -857,6 +857,16 @@ proxies: # socks5 # h2: 67543 # h4: 32345 # h3: 123123 + # # AmneziaWG v1.5 + # i1: + # i2: + # i3: "" + # i4: "" + # i5: "" + # j1: + # j2: + # j3: + # itime: 60 # tuic - name: tuic diff --git a/clash-meta/go.mod b/clash-meta/go.mod index 2e19c8a24a..5695c7827e 100644 --- a/clash-meta/go.mod +++ b/clash-meta/go.mod @@ -14,7 +14,7 @@ require ( github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 github.com/klauspost/compress v1.17.9 // lastest version compatible with golang1.20 github.com/mdlayher/netlink v1.7.2 - github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab + github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a github.com/metacubex/bart v0.20.5 github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b github.com/metacubex/blake3 v0.1.0 @@ -36,7 +36,7 @@ require ( github.com/metacubex/smux v0.0.0-20250503055512-501391591dee github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 github.com/metacubex/utls v1.8.1-0.20250811145843-49b4f106169a - github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 + github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20 github.com/mroth/weightedrand/v2 v2.1.0 github.com/openacid/low v0.1.21 diff --git a/clash-meta/go.sum b/clash-meta/go.sum index 18cbabb0a0..69811ad2ba 100644 --- a/clash-meta/go.sum +++ b/clash-meta/go.sum @@ -90,8 +90,8 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= -github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4= -github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI= +github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a h1:c1QSGpacSeQdBdWcEKZKGuWLcqIG2wxHEygAcXuDwS4= +github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY= github.com/metacubex/ascon v0.1.0 h1:6ZWxmXYszT1XXtwkf6nxfFhc/OTtQ9R3Vyj1jN32lGM= github.com/metacubex/ascon v0.1.0/go.mod h1:eV5oim4cVPPdEL8/EYaTZ0iIKARH9pnhAK/fcT5Kacc= github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM= @@ -141,8 +141,8 @@ github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nU github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/utls v1.8.1-0.20250811145843-49b4f106169a h1:IIzlVmDoB4+7b0BUcLZaY5+AirhhLFep3PhwkAFMRnQ= github.com/metacubex/utls v1.8.1-0.20250811145843-49b4f106169a/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ= -github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ= -github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y= +github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk= +github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU= diff --git a/clash-meta/transport/vless/conn.go b/clash-meta/transport/vless/conn.go index 180a9969d2..94ae71eee0 100644 --- a/clash-meta/transport/vless/conn.go +++ b/clash-meta/transport/vless/conn.go @@ -30,26 +30,22 @@ type Conn struct { } func (vc *Conn) Read(b []byte) (int, error) { - if vc.received { - return vc.ExtendedReader.Read(b) + if !vc.received { + if err := vc.recvResponse(); err != nil { + return 0, err + } + vc.received = true } - - if err := vc.recvResponse(); err != nil { - return 0, err - } - vc.received = true return vc.ExtendedReader.Read(b) } func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error { - if vc.received { - return vc.ExtendedReader.ReadBuffer(buffer) + if !vc.received { + if err := vc.recvResponse(); err != nil { + return err + } + vc.received = true } - - if err := vc.recvResponse(); err != nil { - return err - } - vc.received = true return vc.ExtendedReader.ReadBuffer(buffer) } @@ -190,7 +186,7 @@ func newConn(conn net.Conn, client *Client, dst *DstAddr) (net.Conn, error) { if client.Addons != nil { switch client.Addons.Flow { case XRV: - visionConn, err := vision.NewConn(c, c.id) + visionConn, err := vision.NewConn(c, conn, c.id) if err != nil { return nil, err } diff --git a/clash-meta/transport/vless/vision/conn.go b/clash-meta/transport/vless/vision/conn.go index a0e83f7145..b8367fb9e4 100644 --- a/clash-meta/transport/vless/vision/conn.go +++ b/clash-meta/transport/vless/vision/conn.go @@ -21,15 +21,16 @@ var ( ) type Conn struct { - net.Conn + net.Conn // should be *vless.Conn N.ExtendedReader N.ExtendedWriter - upstream net.Conn userUUID *uuid.UUID - tlsConn net.Conn - input *bytes.Reader - rawInput *bytes.Buffer + // tlsConn and it's internal variables + tlsConn net.Conn // maybe [*tls.Conn] or other tls-like conn + netConn net.Conn // tlsConn.NetConn() + input *bytes.Reader // &tlsConn.input or nil + rawInput *bytes.Buffer // &tlsConn.rawInput or nil needHandshake bool packetsToFilter int @@ -143,7 +144,7 @@ func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error { } if vc.input == nil && vc.rawInput == nil { vc.readProcess = false - vc.ExtendedReader = N.NewExtendedReader(vc.Conn) + vc.ExtendedReader = N.NewExtendedReader(vc.netConn) log.Debugln("XTLS Vision direct read start") } if needReturn { @@ -214,7 +215,7 @@ func (vc *Conn) WriteBuffer(buffer *buf.Buffer) (err error) { return err } if vc.writeDirect { - vc.ExtendedWriter = N.NewExtendedWriter(vc.Conn) + vc.ExtendedWriter = N.NewExtendedWriter(vc.netConn) log.Debugln("XTLS Vision direct write start") //time.Sleep(5 * time.Millisecond) } @@ -235,7 +236,7 @@ func (vc *Conn) WriteBuffer(buffer *buf.Buffer) (err error) { ApplyPadding(buffer2, command, nil, vc.isTLS) err = vc.ExtendedWriter.WriteBuffer(buffer2) if vc.writeDirect { - vc.ExtendedWriter = N.NewExtendedWriter(vc.Conn) + vc.ExtendedWriter = N.NewExtendedWriter(vc.netConn) log.Debugln("XTLS Vision direct write start") //time.Sleep(10 * time.Millisecond) } @@ -266,9 +267,9 @@ func (vc *Conn) NeedHandshake() bool { func (vc *Conn) Upstream() any { if vc.writeDirect || vc.readLastCommand == commandPaddingDirect { - return vc.Conn + return vc.netConn } - return vc.upstream + return vc.Conn } func (vc *Conn) ReaderPossiblyReplaceable() bool { @@ -293,3 +294,10 @@ func (vc *Conn) WriterReplaceable() bool { } return false } + +func (vc *Conn) Close() error { + if vc.ReaderReplaceable() || vc.WriterReplaceable() { // ignore send closeNotify alert in tls.Conn + return vc.netConn.Close() + } + return vc.Conn.Close() +} diff --git a/clash-meta/transport/vless/vision/vision.go b/clash-meta/transport/vless/vision/vision.go index 32634f0ce8..108e01774a 100644 --- a/clash-meta/transport/vless/vision/vision.go +++ b/clash-meta/transport/vless/vision/vision.go @@ -15,22 +15,17 @@ import ( "github.com/metacubex/mihomo/transport/vless/encryption" "github.com/gofrs/uuid/v5" - "github.com/metacubex/sing/common" ) var ErrNotTLS13 = errors.New("XTLS Vision based on TLS 1.3 outer connection") -type connWithUpstream interface { - net.Conn - common.WithUpstream -} - -func NewConn(conn connWithUpstream, userUUID *uuid.UUID) (*Conn, error) { +func NewConn(conn net.Conn, tlsConn net.Conn, userUUID *uuid.UUID) (*Conn, error) { c := &Conn{ ExtendedReader: N.NewExtendedReader(conn), ExtendedWriter: N.NewExtendedWriter(conn), - upstream: conn, + Conn: conn, userUUID: userUUID, + tlsConn: tlsConn, packetsToFilter: 6, needHandshake: true, readProcess: true, @@ -39,36 +34,31 @@ func NewConn(conn connWithUpstream, userUUID *uuid.UUID) (*Conn, error) { } var t reflect.Type var p unsafe.Pointer - switch underlying := conn.Upstream().(type) { + switch underlying := tlsConn.(type) { case *gotls.Conn: //log.Debugln("type tls") - c.Conn = underlying.NetConn() - c.tlsConn = underlying + c.netConn = underlying.NetConn() t = reflect.TypeOf(underlying).Elem() p = unsafe.Pointer(underlying) case *tlsC.Conn: //log.Debugln("type *tlsC.Conn") - c.Conn = underlying.NetConn() - c.tlsConn = underlying + c.netConn = underlying.NetConn() t = reflect.TypeOf(underlying).Elem() p = unsafe.Pointer(underlying) case *tlsC.UConn: //log.Debugln("type *tlsC.UConn") - c.Conn = underlying.NetConn() - c.tlsConn = underlying + c.netConn = underlying.NetConn() t = reflect.TypeOf(underlying.Conn).Elem() //log.Debugln("t:%v", t) p = unsafe.Pointer(underlying.Conn) case *encryption.ClientConn: //log.Debugln("type *encryption.ClientConn") - c.Conn = underlying.Conn - c.tlsConn = underlying + c.netConn = underlying.Conn t = reflect.TypeOf(underlying).Elem() p = unsafe.Pointer(underlying) case *encryption.ServerConn: //log.Debugln("type *encryption.ServerConn") - c.Conn = underlying.Conn - c.tlsConn = underlying + c.netConn = underlying.Conn t = reflect.TypeOf(underlying).Elem() p = unsafe.Pointer(underlying) default: diff --git a/clash-nyanpasu/backend/Cargo.lock b/clash-nyanpasu/backend/Cargo.lock index b1ed37f991..b7e9ed5b0f 100644 --- a/clash-nyanpasu/backend/Cargo.lock +++ b/clash-nyanpasu/backend/Cargo.lock @@ -691,7 +691,7 @@ version = "0.5.0" source = "git+https://github.com/libnyanpasu/auto-launch.git#729d5429dd689067047489af4a0a32f7013854c8" dependencies = [ "dirs 5.0.1", - "thiserror 2.0.15", + "thiserror 2.0.16", "windows-registry 0.3.0", "windows-result 0.2.0", ] @@ -1036,7 +1036,7 @@ dependencies = [ "static_assertions", "tap", "thin-vec", - "thiserror 2.0.15", + "thiserror 2.0.16", "time", ] @@ -1332,7 +1332,7 @@ dependencies = [ "semver 1.0.26", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] @@ -1618,7 +1618,7 @@ dependencies = [ "tauri-specta", "tempfile", "test-log", - "thiserror 2.0.15", + "thiserror 2.0.16", "time", "timeago", "tokio", @@ -1661,7 +1661,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] @@ -2338,7 +2338,7 @@ version = "0.1.0" source = "git+https://github.com/libnyanpasu/nyanpasu-utils.git#dc10823ed5249e837b3005ab71fec0abc7b53f2c" dependencies = [ "dirs 6.0.0", - "thiserror 2.0.15", + "thiserror 2.0.16", "tracing", "windows 0.61.3", ] @@ -2376,7 +2376,7 @@ dependencies = [ "objc2-foundation 0.3.1", "scopeguard", "smithay-client-toolkit", - "thiserror 2.0.15", + "thiserror 2.0.16", "widestring 1.2.0", "windows 0.59.0", "xcb", @@ -3590,7 +3590,7 @@ dependencies = [ "objc2-app-kit 0.3.1", "once_cell", "serde", - "thiserror 2.0.15", + "thiserror 2.0.16", "windows-sys 0.59.0", "x11rb", "xkeysym", @@ -5433,7 +5433,7 @@ dependencies = [ "once_cell", "png", "serde", - "thiserror 2.0.15", + "thiserror 2.0.16", "windows-sys 0.60.2", ] @@ -5458,7 +5458,7 @@ dependencies = [ "rustc-hash 1.1.0", "spirv", "strum 0.26.3", - "thiserror 2.0.15", + "thiserror 2.0.16", "unicode-ident", ] @@ -5868,7 +5868,7 @@ dependencies = [ "serde_json", "simd-json", "specta", - "thiserror 2.0.15", + "thiserror 2.0.16", "tokio", "tokio-util", "tracing", @@ -5905,7 +5905,7 @@ dependencies = [ "shared_child", "specta", "sysinfo", - "thiserror 2.0.15", + "thiserror 2.0.16", "tokio", "tracing", "tracing-attributes", @@ -6501,7 +6501,7 @@ dependencies = [ "objc2-osa-kit", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] @@ -6535,7 +6535,7 @@ dependencies = [ "owo-colors", "oxc-miette-derive", "textwrap", - "thiserror 2.0.15", + "thiserror 2.0.16", "unicode-width", ] @@ -6822,7 +6822,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror 2.0.15", + "thiserror 2.0.16", "ucd-trie", ] @@ -7398,7 +7398,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls", "socket2 0.5.10", - "thiserror 2.0.15", + "thiserror 2.0.16", "tokio", "tracing", "web-time", @@ -7419,7 +7419,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.15", + "thiserror 2.0.16", "tinyvec", "tracing", "web-time", @@ -7698,7 +7698,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] @@ -8323,9 +8323,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "indexmap 2.10.0", "itoa", @@ -9253,7 +9253,7 @@ dependencies = [ "tauri-runtime", "tauri-runtime-wry", "tauri-utils", - "thiserror 2.0.15", + "thiserror 2.0.16", "tokio", "tray-icon", "url", @@ -9306,7 +9306,7 @@ dependencies = [ "sha2 0.10.9", "syn 2.0.106", "tauri-utils", - "thiserror 2.0.15", + "thiserror 2.0.16", "time", "url", "uuid", @@ -9356,7 +9356,7 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] @@ -9388,7 +9388,7 @@ dependencies = [ "tauri", "tauri-plugin", "tauri-plugin-fs", - "thiserror 2.0.15", + "thiserror 2.0.16", "url", ] @@ -9409,7 +9409,7 @@ dependencies = [ "tauri", "tauri-plugin", "tauri-utils", - "thiserror 2.0.15", + "thiserror 2.0.16", "toml 0.8.23", "url", ] @@ -9426,7 +9426,7 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] @@ -9443,7 +9443,7 @@ dependencies = [ "serde_repr", "tauri", "tauri-plugin", - "thiserror 2.0.15", + "thiserror 2.0.16", "time", "url", ] @@ -9463,7 +9463,7 @@ dependencies = [ "sys-locale", "tauri", "tauri-plugin", - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] @@ -9493,7 +9493,7 @@ dependencies = [ "shared_child", "tauri", "tauri-plugin", - "thiserror 2.0.15", + "thiserror 2.0.16", "tokio", ] @@ -9521,7 +9521,7 @@ dependencies = [ "tauri", "tauri-plugin", "tempfile", - "thiserror 2.0.15", + "thiserror 2.0.16", "time", "tokio", "url", @@ -9546,7 +9546,7 @@ dependencies = [ "serde", "serde_json", "tauri-utils", - "thiserror 2.0.15", + "thiserror 2.0.16", "url", "windows 0.61.3", ] @@ -9591,7 +9591,7 @@ dependencies = [ "specta-typescript", "tauri", "tauri-specta-macros", - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] @@ -9636,7 +9636,7 @@ dependencies = [ "serde_json", "serde_with", "swift-rs", - "thiserror 2.0.15", + "thiserror 2.0.16", "toml 0.8.23", "url", "urlpattern", @@ -9661,7 +9661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" dependencies = [ "quick-xml 0.37.5", - "thiserror 2.0.15", + "thiserror 2.0.16", "windows 0.61.3", "windows-version", ] @@ -9803,11 +9803,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.15" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.15", + "thiserror-impl 2.0.16", ] [[package]] @@ -9823,9 +9823,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.15" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d29feb33e986b6ea906bd9c3559a856983f92371b3eaa5e83782a351623de0" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -10335,7 +10335,7 @@ dependencies = [ "once_cell", "png", "serde", - "thiserror 2.0.15", + "thiserror 2.0.16", "windows-sys 0.59.0", ] @@ -10390,7 +10390,7 @@ dependencies = [ "log", "rand 0.9.2", "sha1", - "thiserror 2.0.15", + "thiserror 2.0.16", "utf-8", ] @@ -10407,7 +10407,7 @@ dependencies = [ "log", "rand 0.9.2", "sha1", - "thiserror 2.0.15", + "thiserror 2.0.16", "utf-8", ] @@ -11149,7 +11149,7 @@ version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" dependencies = [ - "thiserror 2.0.15", + "thiserror 2.0.16", "windows 0.61.3", "windows-core 0.61.2", ] @@ -11211,7 +11211,7 @@ dependencies = [ "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.15", + "thiserror 2.0.16", "wgpu-core-deps-apple", "wgpu-core-deps-emscripten", "wgpu-core-deps-windows-linux-android", @@ -11285,7 +11285,7 @@ dependencies = [ "raw-window-handle", "renderdoc-sys", "smallvec", - "thiserror 2.0.15", + "thiserror 2.0.16", "wasm-bindgen", "web-sys", "wgpu-types", @@ -11303,7 +11303,7 @@ dependencies = [ "bytemuck", "js-sys", "log", - "thiserror 2.0.15", + "thiserror 2.0.16", "web-sys", ] @@ -12092,7 +12092,7 @@ dependencies = [ "os_pipe", "rustix 0.38.44", "tempfile", - "thiserror 2.0.15", + "thiserror 2.0.16", "tree_magic_mini", "wayland-backend", "wayland-client", @@ -12151,7 +12151,7 @@ dependencies = [ "sha2 0.10.9", "soup3", "tao-macros", - "thiserror 2.0.15", + "thiserror 2.0.16", "url", "webkit2gtk", "webkit2gtk-sys", diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json index b72ee2edb1..214ad1edd0 100644 --- a/clash-nyanpasu/frontend/nyanpasu/package.json +++ b/clash-nyanpasu/frontend/nyanpasu/package.json @@ -74,8 +74,8 @@ "@types/react-dom": "19.1.7", "@types/validator": "13.15.2", "@vitejs/plugin-legacy": "7.2.1", - "@vitejs/plugin-react": "5.0.0", - "@vitejs/plugin-react-swc": "4.0.0", + "@vitejs/plugin-react": "5.0.1", + "@vitejs/plugin-react-swc": "4.0.1", "change-case": "5.4.4", "clsx": "2.1.1", "core-js": "3.45.0", @@ -88,7 +88,7 @@ "unplugin-auto-import": "20.0.0", "unplugin-icons": "22.2.0", "validator": "13.15.15", - "vite": "7.1.2", + "vite": "7.1.3", "vite-plugin-html": "3.2.2", "vite-plugin-sass-dts": "1.3.31", "vite-plugin-svgr": "4.3.0", diff --git a/clash-nyanpasu/frontend/ui/package.json b/clash-nyanpasu/frontend/ui/package.json index 194bc90045..69fb2a3dee 100644 --- a/clash-nyanpasu/frontend/ui/package.json +++ b/clash-nyanpasu/frontend/ui/package.json @@ -20,7 +20,7 @@ "@tauri-apps/api": "2.6.0", "@types/d3": "7.4.3", "@types/react": "19.1.10", - "@vitejs/plugin-react": "5.0.0", + "@vitejs/plugin-react": "5.0.1", "ahooks": "3.9.3", "d3": "7.9.0", "framer-motion": "12.23.12", @@ -30,7 +30,7 @@ "react-i18next": "15.6.1", "react-use": "17.6.0", "tailwindcss": "4.1.12", - "vite": "7.1.2", + "vite": "7.1.3", "vite-tsconfig-paths": "5.1.4" }, "devDependencies": { diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index dbc6c6b0ce..e9e7d5f2f9 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -2,10 +2,10 @@ "manifest_version": 1, "latest": { "mihomo": "v1.19.12", - "mihomo_alpha": "alpha-4e20ed6", + "mihomo_alpha": "alpha-2790481", "clash_rs": "v0.9.0", "clash_premium": "2023-09-05-gdcc8d87", - "clash_rs_alpha": "0.9.0-alpha+sha.ac11887" + "clash_rs_alpha": "0.9.0-alpha+sha.51d55ef" }, "arch_template": { "mihomo": { @@ -69,5 +69,5 @@ "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" } }, - "updated_at": "2025-08-18T22:20:44.906Z" + "updated_at": "2025-08-19T22:21:14.276Z" } diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index b1b560bbc3..95af83b32b 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -359,7 +359,7 @@ importers: version: 1.131.27(@tanstack/react-router@1.131.27(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.27)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.5)(tiny-invariant@1.3.3) '@tanstack/router-plugin': specifier: 1.131.27 - version: 1.131.27(@tanstack/react-router@1.131.27(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) + version: 1.131.27(@tanstack/react-router@1.131.27(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) '@tauri-apps/plugin-clipboard-manager': specifier: 2.3.0 version: 2.3.0 @@ -395,13 +395,13 @@ importers: version: 13.15.2 '@vitejs/plugin-legacy': specifier: 7.2.1 - version: 7.2.1(terser@5.36.0)(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) + version: 7.2.1(terser@5.36.0)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) '@vitejs/plugin-react': - specifier: 5.0.0 - version: 5.0.0(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) + specifier: 5.0.1 + version: 5.0.1(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) '@vitejs/plugin-react-swc': - specifier: 4.0.0 - version: 4.0.0(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) + specifier: 4.0.1 + version: 4.0.1(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) change-case: specifier: 5.4.4 version: 5.4.4 @@ -439,20 +439,20 @@ importers: specifier: 13.15.15 version: 13.15.15 vite: - specifier: 7.1.2 - version: 7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) + specifier: 7.1.3 + version: 7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) vite-plugin-html: specifier: 3.2.2 - version: 3.2.2(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) + version: 3.2.2(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) vite-plugin-sass-dts: specifier: 1.3.31 - version: 1.3.31(postcss@8.5.6)(prettier@3.6.2)(sass-embedded@1.90.0)(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) + version: 1.3.31(postcss@8.5.6)(prettier@3.6.2)(sass-embedded@1.90.0)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) vite-plugin-svgr: specifier: 4.3.0 - version: 4.3.0(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) + version: 4.3.0(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) vite-tsconfig-paths: specifier: 5.1.4 - version: 5.1.4(typescript@5.9.2)(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) + version: 5.1.4(typescript@5.9.2)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) zod: specifier: 4.0.17 version: 4.0.17 @@ -487,8 +487,8 @@ importers: specifier: 19.1.10 version: 19.1.10 '@vitejs/plugin-react': - specifier: 5.0.0 - version: 5.0.0(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) + specifier: 5.0.1 + version: 5.0.1(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) ahooks: specifier: 3.9.3 version: 3.9.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -517,11 +517,11 @@ importers: specifier: 4.1.12 version: 4.1.12 vite: - specifier: 7.1.2 - version: 7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) + specifier: 7.1.3 + version: 7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) vite-tsconfig-paths: specifier: 5.1.4 - version: 5.1.4(typescript@5.9.2)(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) + version: 5.1.4(typescript@5.9.2)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) devDependencies: '@emotion/react': specifier: 11.14.0 @@ -546,7 +546,7 @@ importers: version: 5.2.0(typescript@5.9.2) vite-plugin-dts: specifier: 4.5.4 - version: 4.5.4(@types/node@22.17.2)(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) + version: 4.5.4(@types/node@22.17.2)(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) scripts: dependencies: @@ -670,6 +670,10 @@ packages: resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} engines: {node: '>=6.9.0'} + '@babel/core@7.28.3': + resolution: {integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.17.7': resolution: {integrity: sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==} engines: {node: '>=6.9.0'} @@ -678,6 +682,10 @@ packages: resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} engines: {node: '>=6.9.0'} + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.25.9': resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} engines: {node: '>=6.9.0'} @@ -761,6 +769,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.25.9': resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==} engines: {node: '>=6.9.0'} @@ -835,6 +849,10 @@ packages: resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.28.3': + resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.27.0': resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} engines: {node: '>=6.0.0'} @@ -845,6 +863,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.28.3': + resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1': resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==} engines: {node: '>=6.9.0'} @@ -1291,6 +1314,10 @@ packages: resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.28.3': + resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} + engines: {node: '>=6.9.0'} + '@babel/types@7.17.0': resolution: {integrity: sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==} engines: {node: '>=6.9.0'} @@ -1299,6 +1326,10 @@ packages: resolution: {integrity: sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==} engines: {node: '>=6.9.0'} + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + engines: {node: '>=6.9.0'} + '@bufbuild/protobuf@2.5.2': resolution: {integrity: sha512-foZ7qr0IsUBjzWIq+SuBLfdQCpJ1j8cTuNNT4owngTHoN5KsJb8L9t65fzz7SCeSWzescoOil/0ldqiL041ABg==} @@ -2516,8 +2547,8 @@ packages: '@types/react': optional: true - '@rolldown/pluginutils@1.0.0-beta.30': - resolution: {integrity: sha512-whXaSoNUFiyDAjkUF8OBpOm77Szdbk5lGNqFe6CbVbJFrhCCPinCbRA3NjawwlNHla1No7xvXXh+CpSxnPfUEw==} + '@rolldown/pluginutils@1.0.0-beta.32': + resolution: {integrity: sha512-QReCdvxiUZAPkvp1xpAg62IeNzykOFA6syH2CnClif4YmALN1XKpB39XneL80008UbtMShthSVDKmrx05N1q/g==} '@rollup/pluginutils@4.2.1': resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} @@ -3611,14 +3642,14 @@ packages: terser: ^5.16.0 vite: ^7.0.0 - '@vitejs/plugin-react-swc@4.0.0': - resolution: {integrity: sha512-4A1dThI578v07mpG4M+ziNn6lmlMlhtVCheL+2WLvClnLvEULi8rpAZThn2oEKn3GtFXFTOeko6eLRhx2V2kgA==} + '@vitejs/plugin-react-swc@4.0.1': + resolution: {integrity: sha512-NQhPjysi5duItyrMd5JWZFf2vNOuSMyw+EoZyTBDzk+DkfYD8WNrsUs09sELV2cr1P15nufsN25hsUBt4CKF9Q==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^4 || ^5 || ^6 || ^7 - '@vitejs/plugin-react@5.0.0': - resolution: {integrity: sha512-Jx9JfsTa05bYkS9xo0hkofp2dCmp1blrKjw9JONs5BTHOvJCgLbaPSuZLGSVJW6u2qe0tc4eevY0+gSNNi0YCw==} + '@vitejs/plugin-react@5.0.1': + resolution: {integrity: sha512-DE4UNaBXwtVoDJ0ccBdLVjFTWL70NRuWNCxEieTI3lrq9ORB9aOCQEKstwDXBl87NvFdbqh/p7eINGyj0BthJA==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -5113,8 +5144,9 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - fdir@6.4.6: - resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -8375,8 +8407,8 @@ packages: vite: optional: true - vite@7.1.2: - resolution: {integrity: sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==} + vite@7.1.3: + resolution: {integrity: sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -8711,6 +8743,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/core@7.28.3': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/helpers': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/generator@7.17.7': dependencies: '@babel/types': 7.28.1 @@ -8726,6 +8778,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.29 jsesc: 3.1.0 + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.25.9': dependencies: '@babel/types': 7.28.1 @@ -8858,6 +8918,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + '@babel/helper-optimise-call-expression@7.25.9': dependencies: '@babel/types': 7.28.1 @@ -8942,6 +9011,11 @@ snapshots: '@babel/template': 7.27.2 '@babel/types': 7.28.1 + '@babel/helpers@7.28.3': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + '@babel/parser@7.27.0': dependencies: '@babel/types': 7.28.1 @@ -8950,6 +9024,10 @@ snapshots: dependencies: '@babel/types': 7.28.1 + '@babel/parser@7.28.3': + dependencies: + '@babel/types': 7.28.2 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 @@ -9289,14 +9367,14 @@ snapshots: '@babel/core': 7.28.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.0)': + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.0)': + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-regenerator@7.28.1(@babel/core@7.28.0)': @@ -9521,6 +9599,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.28.3': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + '@babel/types@7.17.0': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -9532,6 +9622,11 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@babel/types@7.28.2': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@bufbuild/protobuf@2.5.2': {} '@commitlint/cli@19.8.1(@types/node@22.17.2)(typescript@5.9.2)': @@ -10692,7 +10787,7 @@ snapshots: optionalDependencies: '@types/react': 19.1.10 - '@rolldown/pluginutils@1.0.0-beta.30': {} + '@rolldown/pluginutils@1.0.0-beta.32': {} '@rollup/pluginutils@4.2.1': dependencies: @@ -11146,7 +11241,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.131.27(@tanstack/react-router@1.131.27(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1))': + '@tanstack/router-plugin@1.131.27(@tanstack/react-router@1.131.27(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.0 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) @@ -11164,7 +11259,7 @@ snapshots: zod: 3.25.76 optionalDependencies: '@tanstack/react-router': 1.131.27(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - vite: 7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) + vite: 7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -11776,7 +11871,7 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.10.1': optional: true - '@vitejs/plugin-legacy@7.2.1(terser@5.36.0)(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1))': + '@vitejs/plugin-legacy@7.2.1(terser@5.36.0)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.0 '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.0) @@ -11791,27 +11886,27 @@ snapshots: regenerator-runtime: 0.14.1 systemjs: 6.15.1 terser: 5.36.0 - vite: 7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) + vite: 7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react-swc@4.0.0(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1))': + '@vitejs/plugin-react-swc@4.0.1(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1))': dependencies: - '@rolldown/pluginutils': 1.0.0-beta.30 + '@rolldown/pluginutils': 1.0.0-beta.32 '@swc/core': 1.13.3 - vite: 7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) + vite: 7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) transitivePeerDependencies: - '@swc/helpers' - '@vitejs/plugin-react@5.0.0(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1))': + '@vitejs/plugin-react@5.0.1(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1))': dependencies: - '@babel/core': 7.28.0 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.0) - '@rolldown/pluginutils': 1.0.0-beta.30 + '@babel/core': 7.28.3 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.3) + '@rolldown/pluginutils': 1.0.0-beta.32 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) + vite: 7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -13654,7 +13749,7 @@ snapshots: dependencies: pend: 1.2.0 - fdir@6.4.6(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -16764,7 +16859,7 @@ snapshots: tinyglobby@0.2.14: dependencies: - fdir: 6.4.6(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 tmp@0.2.3: {} @@ -17188,7 +17283,7 @@ snapshots: - rollup - supports-color - vite-plugin-dts@4.5.4(@types/node@22.17.2)(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)): + vite-plugin-dts@4.5.4(@types/node@22.17.2)(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)): dependencies: '@microsoft/api-extractor': 7.51.0(@types/node@22.17.2) '@rollup/pluginutils': 5.1.4(rollup@4.46.2) @@ -17201,13 +17296,13 @@ snapshots: magic-string: 0.30.17 typescript: 5.9.2 optionalDependencies: - vite: 7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) + vite: 7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-html@3.2.2(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)): + vite-plugin-html@3.2.2(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)): dependencies: '@rollup/pluginutils': 4.2.1 colorette: 2.0.20 @@ -17221,42 +17316,42 @@ snapshots: html-minifier-terser: 6.1.0 node-html-parser: 5.4.2 pathe: 0.2.0 - vite: 7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) + vite: 7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) - vite-plugin-sass-dts@1.3.31(postcss@8.5.6)(prettier@3.6.2)(sass-embedded@1.90.0)(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)): + vite-plugin-sass-dts@1.3.31(postcss@8.5.6)(prettier@3.6.2)(sass-embedded@1.90.0)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)): dependencies: postcss: 8.5.6 postcss-js: 4.0.1(postcss@8.5.6) prettier: 3.6.2 sass-embedded: 1.90.0 - vite: 7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) + vite: 7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) - vite-plugin-svgr@4.3.0(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)): + vite-plugin-svgr@4.3.0(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)): dependencies: '@rollup/pluginutils': 5.1.3(rollup@4.46.2) '@svgr/core': 8.1.0(typescript@5.9.2) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.2)) - vite: 7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) + vite: 7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) transitivePeerDependencies: - rollup - supports-color - typescript - vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)): + vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)): dependencies: debug: 4.3.7 globrex: 0.1.2 tsconfck: 3.0.3(typescript@5.9.2) optionalDependencies: - vite: 7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) + vite: 7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) transitivePeerDependencies: - supports-color - typescript - vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1): + vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1): dependencies: esbuild: 0.25.0 - fdir: 6.4.6(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 rollup: 4.46.2 diff --git a/filebrowser/frontend/src/i18n/fr.json b/filebrowser/frontend/src/i18n/fr.json index cfe5702265..c9110f6626 100644 --- a/filebrowser/frontend/src/i18n/fr.json +++ b/filebrowser/frontend/src/i18n/fr.json @@ -77,14 +77,14 @@ "noPreview": "L'aperçu n'est pas disponible pour ce fichier." }, "help": { - "click": "Sélectionner un élément", + "click": "Sélectionner un fichier ou dossier", "ctrl": { - "click": "Sélectionner plusieurs éléments", + "click": "Sélectionner plusieurs fichiers ou dossiers", "f": "Ouvrir l'invité de recherche", - "s": "Télécharger l'élément actuel" + "s": "Enregistrer un fichier ou télécharger le dossier actuel" }, "del": "Supprimer les éléments sélectionnés", - "doubleClick": "Ouvrir un élément", + "doubleClick": "Ouvrir un fichier ou dossier", "esc": "Désélectionner et/ou fermer la boîte de dialogue", "f1": "Ouvrir l'aide", "f2": "Renommer le fichier", @@ -98,8 +98,8 @@ "passwordsDontMatch": "Les mots de passe ne concordent pas", "signup": "S'inscrire", "submit": "Se connecter", - "username": "Utilisateur", - "usernameTaken": "Le nom d'utilisateur est déjà pris", + "username": "Utilisateur·ice", + "usernameTaken": "Le nom d'utilisateur·ice est déjà pris", "wrongCredentials": "Identifiants incorrects !" }, "permanent": "Permanent", @@ -110,7 +110,7 @@ "deleteMessageMultiple": "Êtes-vous sûr de vouloir supprimer ces {count} élément(s) ?", "deleteMessageSingle": "Êtes-vous sûr de vouloir supprimer cet élément ?", "deleteMessageShare": "Êtes-vous sûr de vouloir supprimer ce partage ({path}) ?", - "deleteUser": "Êtes-vous sûr de vouloir supprimer cet utilisateur ?", + "deleteUser": "Êtes-vous sûr de vouloir supprimer cet·te utilisateur·ice ?", "deleteTitle": "Supprimer", "displayName": "Nom :", "download": "Télécharger", @@ -120,7 +120,7 @@ "filesSelected": "{count} éléments sélectionnés", "lastModified": "Dernière modification", "move": "Déplacer", - "moveMessage": "Choisissez l'emplacement où déplacer la sélection :", + "moveMessage": "Choisissez un nouveau dossier principal pour vos fichier(s)/dossier(s) :", "newArchetype": "Créer un nouveau post basé sur un archétype. Votre fichier sera créé dans le dossier de contenu.", "newDir": "Nouveau dossier", "newDirMessage": "Nom du nouveau dossier :", @@ -155,12 +155,12 @@ }, "settings": { "admin": "Admin", - "administrator": "Administrateur", + "administrator": "Administrateur·ice", "allowCommands": "Exécuter des commandes", "allowEdit": "Éditer, renommer et supprimer des fichiers ou des dossiers", "allowNew": "Créer de nouveaux fichiers et dossiers", "allowPublish": "Publier de nouveaux posts et pages", - "allowSignup": "Autoriser les utilisateurs à s'inscrire", + "allowSignup": "Autoriser les utilisateur·ices à s'inscrire", "avoidChanges": "(Laisser vide pour conserver l'actuel)", "branding": "Image de marque", "brandingDirectoryPath": "Chemin du dossier d'image de marque", @@ -169,17 +169,17 @@ "commandRunner": "Exécuteur de commandes", "commandRunnerHelp": "Ici, vous pouvez définir les commandes qui seront exécutées lors des événements nommés précédemments. Vous devez en écrire une par ligne. Les variables d'environnement {0} et {1} seront disponibles, {0} étant relatif à {1}. Pour plus d'informations sur cette fonctionnalité et les variables d'environnement disponibles, veuillez lire la {2}.", "commandsUpdated": "Commandes mises à jour !", - "createUserDir": "Créer automatiquement un dossier pour l'utilisateur", - "minimumPasswordLength": "Minimum password length", + "createUserDir": "Créer automatiquement un dossier pour l'utilisateur·ice", + "minimumPasswordLength": "Taille minimale du mot de passe", "tusUploads": "Uploads segmentés", "tusUploadsHelp": "File Browser prend en charge les uploads segmentés afin de permettre une gestion efficace, fiable et reprenable sur des réseaux instables.", "tusUploadsChunkSize": "Taille maximale autorisée par segment (les uploads directs seront utilisés pour les fichiers plus petits). Vous pouvez entrer un entier en octets ou une chaîne telle que 10MB, 1GB, etc.", "tusUploadsRetryCount": "Nombre de tentatives en cas d'échec d'un segment.", - "userHomeBasePath": "Chemin de base pour les répertoires personnels des utilisateurs", + "userHomeBasePath": "Chemin de base pour les dossiers personnels des utilisateur·ices", "userScopeGenerationPlaceholder": "Le périmètre sera généré automatiquement", - "createUserHomeDirectory": "Créer le répertoire personnel de l'utilisateur", + "createUserHomeDirectory": "Créer le dossier personnel de l'utilisateur·ice", "customStylesheet": "Feuille de style personnalisée", - "defaultUserDescription": "Paramètres par défaut pour les nouveaux utilisateurs.", + "defaultUserDescription": "Paramètres par défaut pour les nouveaux utilisateur·ices.", "disableExternalLinks": "Désactiver les liens externes (sauf la documentation)", "disableUsedDiskPercentage": "Désactiver le graphique de pourcentage d'utilisation du disque", "documentation": "documentation", @@ -188,12 +188,12 @@ "executeOnShellDescription": "Par défaut, File Browser exécute les commandes en appelant directement leurs binaires. Si vous voulez les exécuter sur un shell à la place (comme Bash ou PowerShell), vous pouvez le définir ici avec les arguments et les drapeaux requis. S'il est défini, la commande que vous exécutez sera ajoutée en tant qu'argument. Cela s'applique à la fois aux commandes utilisateur et aux crochets d'événements.", "globalRules": "Il s'agit d'un ensemble global de règles d'autorisation et d'interdiction. Elles s'appliquent à tous les utilisateurs. Vous pouvez définir des règles spécifiques sur les paramètres de chaque utilisateur pour remplacer celles-ci.", "globalSettings": "Paramètres globaux", - "hideDotfiles": "Cacher les fichiers de configuration utilisateur (dotfiles)", + "hideDotfiles": "Cacher les fichiers de configuration commançant par un point", "insertPath": "Insérer le chemin", "insertRegex": "Insérer une expression régulière", "instanceName": "Nom de l'instance", "language": "Langue", - "lockPassword": "Empêcher l'utilisateur de changer son mot de passe", + "lockPassword": "Empêcher l'utilisateur·ice de changer son mot de passe", "newPassword": "Votre nouveau mot de passe", "newPasswordConfirm": "Confirmation du nouveau mot de passe", "newUser": "Nouvel utilisateur", @@ -210,13 +210,13 @@ "share": "Partager des fichiers" }, "permissions": "Permissions", - "permissionsHelp": "Vous pouvez définir l'utilisateur comme étant un administrateur ou encore choisir les permissions individuellement. Si vous sélectionnez \"Administrateur\", toutes les autres options seront automatiquement activées. La gestion des utilisateurs est un privilège que seul l'administrateur possède.\n", + "permissionsHelp": "Vous pouvez définir l'utilisateur·ice comme étant administrateur·ice ou encore choisir les permissions individuellement. Si vous sélectionnez \"Administrateur·ice\", toutes les autres options seront automatiquement activées. La gestion des utilisateur·ices est un privilège que seul l'administrateur·ice possède.\n", "profileSettings": "Paramètres du profil", - "ruleExample1": "Bloque l'accès à tous les fichiers commençant par un point (comme par exemple .git, .gitignore) dans tous les dossiers", - "ruleExample2": "Bloque l'accès au fichier nommé \"Caddyfile\" à la racine du dossier utilisateur", + "ruleExample1": "Bloque l'accès à tous les fichiers commençant par un point (comme par exemple .git, .gitignore) dans tous les dossiers.\n", + "ruleExample2": "Bloque l'accès au fichier nommé \"Caddyfile\" à la racine du dossier utilisateur·ice.", "rules": "Règles", - "rulesHelp": "Vous pouvez définir ici un ensemble de règles pour cet utilisateur. Les fichiers bloqués ne seront pas affichés et ne seront pas accessibles par l'utilisateur. Les expressions régulières sont supportées et les chemins d'accès sont relatifs par rapport au dossier de l'utilisateur.\n", - "scope": "Portée du dossier utilisateur", + "rulesHelp": "Vous pouvez définir ici un ensemble de règles pour cet utilisateur·ice. Les fichiers bloqués ne seront pas affichés et ne seront pas accessibles par l'utilisateur·ice. Les expressions régulières sont supportées et les chemins d'accès sont relatifs par rapport au dossier de l'utilisateur·ice.\n", + "scope": "Portée du dossier utilisateur·ice", "setDateFormat": "Définir le format de la date", "settingsUpdated": "Les paramètres ont été mis à jour !", "shareDuration": "Durée du partage", @@ -224,21 +224,21 @@ "shareDeleted": "Partage supprimé !", "singleClick": "Utiliser un simple clic pour ouvrir les fichiers et les dossiers", "themes": { - "default": "System default", + "default": "Par défaut du système", "dark": "Sombre", "light": "Clair", "title": "Thème" }, - "user": "Utilisateur", + "user": "Utilisateur·ice", "userCommands": "Commandes", - "userCommandsHelp": "Une liste séparée par des espaces des commandes permises pour l'utilisateur. Exemple :\n", - "userCreated": "Utilisateur créé !", - "userDefaults": "Paramètres par défaut de l'utilisateur", - "userDeleted": "Utilisateur supprimé !", - "userManagement": "Gestion des utilisateurs", - "userUpdated": "Utilisateur mis à jour !", - "username": "Nom d'utilisateur", - "users": "Utilisateurs" + "userCommandsHelp": "Une liste séparée par des espaces des commandes permises pour l'utilisateur·ice. Exemple :\n", + "userCreated": "Utilisateur·ice créé !", + "userDefaults": "Paramètres par défaut de l'utilisateur.ice", + "userDeleted": "Utilisateur·ice supprimé !", + "userManagement": "Gestion des utilisateur·ices", + "userUpdated": "Utilisateur·ice mis à jour !", + "username": "Nom d'utilisateur·ice", + "users": "Utilisateur·ices" }, "sidebar": { "help": "Aide", diff --git a/lede/feeds.conf.default b/lede/feeds.conf.default index 98dcb2aa9e..c5ecc9ab3f 100644 --- a/lede/feeds.conf.default +++ b/lede/feeds.conf.default @@ -1,6 +1,7 @@ src-git packages https://github.com/coolsnowwolf/packages #src-git luci https://github.com/coolsnowwolf/luci -src-git luci https://github.com/coolsnowwolf/luci.git;openwrt-23.05 +#src-git luci https://github.com/coolsnowwolf/luci.git;openwrt-23.05 +src-git luci https://github.com/coolsnowwolf/luci.git;openwrt-24.10 src-git routing https://github.com/coolsnowwolf/routing src-git telephony https://github.com/coolsnowwolf/telephony.git #src-git helloworld https://github.com/fw876/helloworld.git @@ -8,4 +9,5 @@ src-git telephony https://github.com/coolsnowwolf/telephony.git #src-git video https://github.com/openwrt/video.git #src-git targets https://github.com/openwrt/targets.git #src-git oldpackages http://git.openwrt.org/packages.git +src-git qmodem https://github.com/FUjr/modem_feeds.git #src-link custom /usr/src/openwrt/custom-feed diff --git a/lede/include/kernel-6.1 b/lede/include/kernel-6.1 index 8af66fa9bb..2fd3ff192c 100644 --- a/lede/include/kernel-6.1 +++ b/lede/include/kernel-6.1 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.1 = .147 -LINUX_KERNEL_HASH-6.1.147 = 218f25663a41e3d811e84fa1c4acec50684898b2f6d0c8c0deb531d937e466f7 +LINUX_VERSION-6.1 = .148 +LINUX_KERNEL_HASH-6.1.148 = d9a03d3a2771c60fc726c58d3bba61123e5fd22d45ed27f6cf7a308c171180a1 diff --git a/lede/include/kernel-6.12 b/lede/include/kernel-6.12 index 11f05faf1c..51f0379fc1 100644 --- a/lede/include/kernel-6.12 +++ b/lede/include/kernel-6.12 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.12 = .41 -LINUX_KERNEL_HASH-6.12.41 = 6b19a3ae99423de2416964d67251d745910277af258b4c4c63e88fd87dbf0e27 +LINUX_VERSION-6.12 = .42 +LINUX_KERNEL_HASH-6.12.42 = 4804528a29cd20309a0b41c30e5aeffc35fa21ee3358f4a706d4586d003bc1fb diff --git a/lede/include/kernel-6.6 b/lede/include/kernel-6.6 index 71181a3fc5..048ae0b42a 100644 --- a/lede/include/kernel-6.6 +++ b/lede/include/kernel-6.6 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.6 = .101 -LINUX_KERNEL_HASH-6.6.101 = 8c4ff2869736538b9b0d88ea8dbf0332b79c6ecc40a32066768a754df1fae1c0 +LINUX_VERSION-6.6 = .102 +LINUX_KERNEL_HASH-6.6.102 = 80d2feb7334c30bacbe1e7dafa9ea415efb2c0ea4f4740ecbd1467cf5d94de5c diff --git a/lede/package/kernel/mac80211/patches/build/300-backports-handle-genlmsg_multicast_allns-upstream-ba.patch b/lede/package/kernel/mac80211/patches/build/300-backports-handle-genlmsg_multicast_allns-upstream-ba.patch index f4ffd4be0a..2e92fda374 100644 --- a/lede/package/kernel/mac80211/patches/build/300-backports-handle-genlmsg_multicast_allns-upstream-ba.patch +++ b/lede/package/kernel/mac80211/patches/build/300-backports-handle-genlmsg_multicast_allns-upstream-ba.patch @@ -17,26 +17,32 @@ Signed-off-by: Christian Marangi --- a/backport-include/net/genetlink.h +++ b/backport-include/net/genetlink.h -@@ -223,4 +223,15 @@ static inline int genlmsg_parse(const st +@@ -223,4 +223,17 @@ static inline int genlmsg_parse(const st } #endif /* LINUX_VERSION_IS_LESS(5,2,0) */ -+#if LINUX_VERSION_IN_RANGE(5,15,0,5,15,169) || \ ++#if LINUX_VERSION_IN_RANGE(5,10,0,5,10,222) || \ ++ LINUX_VERSION_IN_RANGE(5,15,0,5,15,169) || \ + LINUX_VERSION_IN_RANGE(6,1,0,6,1,115) || \ + LINUX_VERSION_IN_RANGE(6,6,0,6,6,58) +#define genlmsg_multicast_allns LINUX_BACKPORT(genlmsg_multicast_allns) +int backport_genlmsg_multicast_allns(const struct genl_family *family, + struct sk_buff *skb, u32 portid, + unsigned int group); -+#endif /* LINUX_VERSION_IN_RANGE(5,15,0,5,15,169) || ++#endif /* LINUX_VERSION_IN_RANGE(5,10,0,5,10,222) || ++ LINUX_VERSION_IN_RANGE(5,15,0,5,15,169) || + LINUX_VERSION_IN_RANGE(6,1,0,6,1,115) || + LINUX_VERSION_IN_RANGE(6,6,0,6,6,58) */ + #endif /* __BACKPORT_NET_GENETLINK_H */ --- a/compat/Makefile +++ b/compat/Makefile -@@ -22,7 +22,8 @@ compat-$(CPTCFG_KERNEL_5_9) += backport- - compat-$(CPTCFG_KERNEL_5_10) += backport-5.10.o +@@ -19,10 +19,11 @@ + compat-$(CPTCFG_KERNEL_5_3) += backport-5.3.o + compat-$(CPTCFG_KERNEL_5_5) += backport-5.5.o + compat-$(CPTCFG_KERNEL_5_9) += backport-5.9.o +-compat-$(CPTCFG_KERNEL_5_10) += backport-5.10.o ++compat-$(CPTCFG_KERNEL_5_10) += backport-5.10.o backport-genetlink.o compat-$(CPTCFG_KERNEL_5_11) += backport-5.11.o compat-$(CPTCFG_KERNEL_5_13) += backport-5.13.o -compat-$(CPTCFG_KERNEL_5_15) += backport-5.15.o @@ -55,13 +61,14 @@ Signed-off-by: Christian Marangi static const struct genl_family *find_family_real_ops(const struct genl_ops **ops) { const struct genl_family *family; -@@ -415,3 +416,63 @@ int backport_genlmsg_multicast_allns(con +@@ -415,3 +416,65 @@ int backport_genlmsg_multicast_allns(con return genlmsg_mcast(skb, portid, group); } EXPORT_SYMBOL_GPL(backport_genlmsg_multicast_allns); +#endif /* LINUX_VERSION_IS_LESS(5,2,0) */ + -+#if LINUX_VERSION_IN_RANGE(5,15,0,5,15,169) || \ ++#if LINUX_VERSION_IN_RANGE(5,10,0,5,10,222) || \ ++ LINUX_VERSION_IN_RANGE(5,15,0,5,15,169) || \ + LINUX_VERSION_IN_RANGE(6,1,0,6,1,115) || \ + LINUX_VERSION_IN_RANGE(6,6,0,6,6,58) +static int genlmsg_mcast(struct sk_buff *skb, u32 portid, unsigned long group) @@ -116,6 +123,7 @@ Signed-off-by: Christian Marangi + return genlmsg_mcast(skb, portid, group); +} +EXPORT_SYMBOL_GPL(backport_genlmsg_multicast_allns); -+#endif /* LINUX_VERSION_IN_RANGE(5,15,0,5,15,169) || ++#endif /* LINUX_VERSION_IN_RANGE(5,10,0,5,10,222) || ++ LINUX_VERSION_IN_RANGE(5,15,0,5,15,169) || + LINUX_VERSION_IN_RANGE(6,1,0,6,1,115) || + LINUX_VERSION_IN_RANGE(6,6,0,6,6,58) */ diff --git a/lede/package/lean/default-settings/Makefile b/lede/package/lean/default-settings/Makefile index e1afc1cf71..8e89143576 100644 --- a/lede/package/lean/default-settings/Makefile +++ b/lede/package/lean/default-settings/Makefile @@ -18,7 +18,7 @@ define Package/default-settings CATEGORY:=LuCI TITLE:=LuCI support for Default Settings PKGARCH:=all - DEPENDS:=+luci-base +luci +@LUCI_LANG_zh-cn +@LUCI_LANG_zh_Hans +@LUCI_LANG_en + DEPENDS:= +luci-compat +@LUCI_LANG_zh-cn +@LUCI_LANG_zh_Hans +@LUCI_LANG_en endef define Package/default-settings/description diff --git a/lede/package/lean/default-settings/files/zzz-default-settings b/lede/package/lean/default-settings/files/zzz-default-settings index 726634599d..941ca14588 100755 --- a/lede/package/lean/default-settings/files/zzz-default-settings +++ b/lede/package/lean/default-settings/files/zzz-default-settings @@ -47,14 +47,14 @@ sed -i '/check_signature/d' /etc/opkg.conf sed -i '/REDIRECT --to-ports 53/d' /etc/firewall.user -sed -i '/option disabled/d' /etc/config/wireless -sed -i '/set wireless.radio${devidx}.disabled/d' /lib/wifi/mac80211.sh - sed -i '/DISTRIB_REVISION/d' /etc/openwrt_release echo "DISTRIB_REVISION='R25.8.8'" >> /etc/openwrt_release sed -i '/DISTRIB_DESCRIPTION/d' /etc/openwrt_release echo "DISTRIB_DESCRIPTION='LEDE '" >> /etc/openwrt_release +sed -i '/OPENWRT_RELEASE/d' /usr/lib/os-release +echo 'OPENWRT_RELEASE="LEDE R25.8.8"' >> /usr/lib/os-release + sed -i '/log-facility/d' /etc/dnsmasq.conf echo "log-facility=/dev/null" >> /etc/dnsmasq.conf diff --git a/lede/target/linux/qualcommax/patches-6.12/0600-2-qca-nss-ecm-support-PPPOE-offload.patch b/lede/target/linux/qualcommax/patches-6.12/0600-2-qca-nss-ecm-support-PPPOE-offload.patch index c8180458a9..3ddf75c1b8 100644 --- a/lede/target/linux/qualcommax/patches-6.12/0600-2-qca-nss-ecm-support-PPPOE-offload.patch +++ b/lede/target/linux/qualcommax/patches-6.12/0600-2-qca-nss-ecm-support-PPPOE-offload.patch @@ -357,9 +357,9 @@ po->chan.private = sk; - po->chan.ops = &pppoe_chan_ops; + po->chan.ops = (struct ppp_channel_ops *)&pppoe_chan_ops; + po->chan.direct_xmit = true; error = ppp_register_net_channel(dev_net(dev), &po->chan); - if (error) { @@ -995,9 +996,80 @@ static int pppoe_fill_forward_path(struc return 0; } diff --git a/lede/target/linux/qualcommax/patches-6.6/0600-2-qca-nss-ecm-support-PPPOE-offload.patch b/lede/target/linux/qualcommax/patches-6.6/0600-2-qca-nss-ecm-support-PPPOE-offload.patch index f7271a6886..b6909cd12a 100644 --- a/lede/target/linux/qualcommax/patches-6.6/0600-2-qca-nss-ecm-support-PPPOE-offload.patch +++ b/lede/target/linux/qualcommax/patches-6.6/0600-2-qca-nss-ecm-support-PPPOE-offload.patch @@ -357,9 +357,9 @@ po->chan.private = sk; - po->chan.ops = &pppoe_chan_ops; + po->chan.ops = (struct ppp_channel_ops *)&pppoe_chan_ops; + po->chan.direct_xmit = true; error = ppp_register_net_channel(dev_net(dev), &po->chan); - if (error) { @@ -995,9 +996,80 @@ static int pppoe_fill_forward_path(struc return 0; } diff --git a/mieru/pkg/protocol/session.go b/mieru/pkg/protocol/session.go index 5d87e56924..bd3843fa6e 100644 --- a/mieru/pkg/protocol/session.go +++ b/mieru/pkg/protocol/session.go @@ -292,9 +292,7 @@ func (s *Session) Write(b []byte) (n int, err error) { }() // Before the first write, client needs to send open session request. - // Note: even in sessionAttached state, client is allowed to send more packets, - // so open session request is only sent when nextSend is 0. - if s.isClient && s.isState(sessionAttached) && s.nextSend == 0 { + if s.isClient && s.isState(sessionAttached) { s.oLock.Lock() seg := &segment{ metadata: &sessionStruct{ diff --git a/mieru/pkg/socks5/copy.go b/mieru/pkg/socks5/copy.go index 69f24d7b6a..e36fbd55e9 100644 --- a/mieru/pkg/socks5/copy.go +++ b/mieru/pkg/socks5/copy.go @@ -34,6 +34,7 @@ func BidiCopyUDP(udpConn *net.UDPConn, tunnelConn *apicommon.PacketOverStreamTun errCh := make(chan error, 2) go func() { + // udpConn -> tunnelConn buf := make([]byte, 1<<16) for { n, a, err := udpConn.ReadFrom(buf) @@ -60,6 +61,7 @@ func BidiCopyUDP(udpConn *net.UDPConn, tunnelConn *apicommon.PacketOverStreamTun } }() go func() { + // tunnelConn -> udpConn buf := make([]byte, 1<<16) for { n, err := tunnelConn.Read(buf) @@ -101,14 +103,38 @@ func BidiCopyUDP(udpConn *net.UDPConn, tunnelConn *apicommon.PacketOverStreamTun // BidiCopySocks5 does bi-directional data copy. // This function is aware of socks5 protocol and doesn't copy the socks5 reply header // sent by the server. -func BidiCopySocks5(conn, proxyConn io.ReadWriteCloser) error { +// If pendingReq is not empty, it is prepended to proxyConn. +func BidiCopySocks5(conn, proxyConn io.ReadWriteCloser, pendingReq []byte) error { errCh := make(chan error, 2) + go func() { - _, err := io.Copy(proxyConn, conn) + // conn -> proxyConn + buf := make([]byte, 1<<16) + reqLen := len(pendingReq) + if reqLen > 0 { + log.Debugf("HANDSHAKE_NO_WAIT mode socks5 client request: %v", pendingReq) + reqLen = copy(buf, pendingReq) + } + n, err := conn.Read(buf[reqLen:]) + if err != nil { + proxyConn.Close() + errCh <- fmt.Errorf("failed to read connection request from the client: %w", err) + return + } + n += reqLen + _, err = proxyConn.Write(buf[:n]) + if err != nil { + proxyConn.Close() + errCh <- fmt.Errorf("failed to write connection request to the server: %w", err) + return + } + _, err = io.Copy(proxyConn, conn) proxyConn.Close() errCh <- err }() + go func() { + // proxyConn -> conn connResp := make([]byte, 4) if _, err := io.ReadFull(proxyConn, connResp); err != nil { conn.Close() diff --git a/mieru/pkg/socks5/request.go b/mieru/pkg/socks5/request.go index 6c4b83374d..c6bbc9853c 100644 --- a/mieru/pkg/socks5/request.go +++ b/mieru/pkg/socks5/request.go @@ -421,16 +421,17 @@ func (s *Server) proxySocks5AuthReq(conn, proxyConn net.Conn) error { // proxySocks5ConnReq transfers the socks5 connection request and response // between socks5 client and server. -// If HandshakeNoWait is true, socks5 client will fake the connection response. -// If UDP association is used, return the created UDP connection, otherwise return nil. -func (s *Server) proxySocks5ConnReq(conn, proxyConn net.Conn) (*net.UDPConn, error) { - // Send the connection request to the server. +// If HandshakeNoWait is true, socks5 client will fake the connection response +// and return the pending connection request to the server. +// If UDP association is used, return the created UDP connection. +func (s *Server) proxySocks5ConnReq(conn, proxyConn net.Conn) (*net.UDPConn, []byte, error) { defer common.SetReadTimeout(conn, 0) defer common.SetReadTimeout(proxyConn, 0) common.SetReadTimeout(conn, s.config.HandshakeTimeout) + connReq := make([]byte, 4) if _, err := io.ReadFull(conn, connReq); err != nil { - return nil, fmt.Errorf("failed to get socks5 connection request: %w", err) + return nil, nil, fmt.Errorf("failed to get socks5 connection request: %w", err) } cmd := connReq[1] reqAddrType := connReq[3] @@ -442,25 +443,21 @@ func (s *Server) proxySocks5ConnReq(conn, proxyConn net.Conn) (*net.UDPConn, err case constant.Socks5FQDNAddress: reqFQDNLen = []byte{0} if _, err := io.ReadFull(conn, reqFQDNLen); err != nil { - return nil, fmt.Errorf("failed to get FQDN length: %w", err) + return nil, nil, fmt.Errorf("failed to get FQDN length: %w", err) } dstAddr = make([]byte, reqFQDNLen[0]+2) case constant.Socks5IPv6Address: dstAddr = make([]byte, 18) default: - return nil, fmt.Errorf("unsupported address type: %d", reqAddrType) + return nil, nil, fmt.Errorf("unsupported address type: %d", reqAddrType) } if _, err := io.ReadFull(conn, dstAddr); err != nil { - return nil, fmt.Errorf("failed to get destination address: %w", err) + return nil, nil, fmt.Errorf("failed to get destination address: %w", err) } if len(reqFQDNLen) != 0 { connReq = append(connReq, reqFQDNLen...) } connReq = append(connReq, dstAddr...) - if _, err := proxyConn.Write(connReq); err != nil { - return nil, fmt.Errorf("failed to write connection request to the server: %w", err) - } - log.Debugf("Sent socks5 request %v to server", connReq) // Fake server connection response if HandshakeNoWait is true. if s.config.HandshakeNoWait && cmd == constant.Socks5ConnectCmd { @@ -468,24 +465,30 @@ func (s *Server) proxySocks5ConnReq(conn, proxyConn net.Conn) (*net.UDPConn, err // Instead of using server bound address and port, use the proxy connection address and port. serverBoundAddr := model.NetAddrSpec{} if err := serverBoundAddr.From(proxyConn.RemoteAddr()); err != nil { - return nil, fmt.Errorf("failed to get proxy connection address: %w", err) + return nil, nil, fmt.Errorf("failed to get proxy connection address: %w", err) } resp.Write([]byte{constant.Socks5Version, 0, 0}) if err := serverBoundAddr.WriteToSocks5(&resp); err != nil { - return nil, fmt.Errorf("failed to write proxy connection address: %w", err) + return nil, nil, fmt.Errorf("failed to write proxy connection address: %w", err) } if _, err := conn.Write(resp.Bytes()); err != nil { - return nil, fmt.Errorf("failed to write fake connection response to the socks5 client: %w", err) + return nil, nil, fmt.Errorf("failed to write fake connection response to the socks5 client: %w", err) } - return nil, nil + return nil, connReq, nil } + // Send the connection request to the server. + if _, err := proxyConn.Write(connReq); err != nil { + return nil, nil, fmt.Errorf("failed to write connection request to the server: %w", err) + } + log.Debugf("Sent socks5 request %v to server", connReq) + // Get server connection response. common.SetReadTimeout(proxyConn, s.config.HandshakeTimeout) connResp := make([]byte, 4) if _, err := io.ReadFull(proxyConn, connResp); err != nil { - return nil, fmt.Errorf("failed to read connection response from the server: %w", err) + return nil, nil, fmt.Errorf("failed to read connection response from the server: %w", err) } respAddrType := connResp[3] var respFQDNLen []byte @@ -496,21 +499,22 @@ func (s *Server) proxySocks5ConnReq(conn, proxyConn net.Conn) (*net.UDPConn, err case constant.Socks5FQDNAddress: respFQDNLen = []byte{0} if _, err := io.ReadFull(proxyConn, respFQDNLen); err != nil { - return nil, fmt.Errorf("failed to get FQDN length: %w", err) + return nil, nil, fmt.Errorf("failed to get FQDN length: %w", err) } bindAddr = make([]byte, respFQDNLen[0]+2) case constant.Socks5IPv6Address: bindAddr = make([]byte, 18) default: - return nil, fmt.Errorf("unsupported address type: %d", respAddrType) + return nil, nil, fmt.Errorf("unsupported address type: %d", respAddrType) } if _, err := io.ReadFull(proxyConn, bindAddr); err != nil { - return nil, fmt.Errorf("failed to get bind address: %w", err) + return nil, nil, fmt.Errorf("failed to get bind address: %w", err) } if len(respFQDNLen) != 0 { connResp = append(connResp, respFQDNLen...) } connResp = append(connResp, bindAddr...) + log.Debugf("Received socks5 response %v from server", connResp) // Handle UDP association. var udpConn *net.UDPConn @@ -521,18 +525,18 @@ func (s *Server) proxySocks5ConnReq(conn, proxyConn net.Conn) (*net.UDPConn, err udpAddr := &net.UDPAddr{IP: net.IP{0, 0, 0, 0}, Port: 0} udpConn, err = net.ListenUDP("udp4", udpAddr) if err != nil { - return nil, fmt.Errorf("net.ListenUDP() failed: %w", err) + return nil, nil, fmt.Errorf("net.ListenUDP() failed: %w", err) } // Get the port number and rewrite the response. _, udpPortStr, err := net.SplitHostPort(udpConn.LocalAddr().String()) if err != nil { udpConn.Close() - return nil, fmt.Errorf("net.SplitHostPort() failed: %w", err) + return nil, nil, fmt.Errorf("net.SplitHostPort() failed: %w", err) } udpPort, err := strconv.Atoi(udpPortStr) if err != nil { udpConn.Close() - return nil, fmt.Errorf("strconv.Atoi() failed: %w", err) + return nil, nil, fmt.Errorf("strconv.Atoi() failed: %w", err) } lenResp := len(connResp) connResp[lenResp-2] = byte(udpPort >> 8) @@ -540,9 +544,9 @@ func (s *Server) proxySocks5ConnReq(conn, proxyConn net.Conn) (*net.UDPConn, err } if _, err := conn.Write(connResp); err != nil { - return nil, fmt.Errorf("failed to write connection response to the socks5 client: %w", err) + return nil, nil, fmt.Errorf("failed to write connection response to the socks5 client: %w", err) } - return udpConn, nil + return udpConn, nil, nil } // sendReply is used to send a reply message. diff --git a/mieru/pkg/socks5/socks5.go b/mieru/pkg/socks5/socks5.go index 25e78846e7..3093159ecb 100644 --- a/mieru/pkg/socks5/socks5.go +++ b/mieru/pkg/socks5/socks5.go @@ -197,7 +197,7 @@ func (s *Server) clientServeConn(conn net.Conn) error { } } - // Forward remaining bytes to proxy. + // Establish connection to proxy server. ctx := context.Background() var proxyConn net.Conn var err error @@ -213,7 +213,7 @@ func (s *Server) clientServeConn(conn net.Conn) error { return err } } - udpAssociateConn, err := s.proxySocks5ConnReq(conn, proxyConn) + udpAssociateConn, pendingConnReq, err := s.proxySocks5ConnReq(conn, proxyConn) if err != nil { HandshakeErrors.Add(1) proxyConn.Close() @@ -221,7 +221,7 @@ func (s *Server) clientServeConn(conn net.Conn) error { } if udpAssociateConn == nil { if s.config.HandshakeNoWait { - return BidiCopySocks5(conn, proxyConn) + return BidiCopySocks5(conn, proxyConn, pendingConnReq) } return common.BidiCopy(conn, proxyConn) } diff --git a/mihomo/adapter/outbound/wireguard.go b/mihomo/adapter/outbound/wireguard.go index 84ba1dbb12..f59033febe 100644 --- a/mihomo/adapter/outbound/wireguard.go +++ b/mihomo/adapter/outbound/wireguard.go @@ -87,15 +87,26 @@ type WireGuardPeerOption struct { } type AmneziaWGOption struct { - JC int `proxy:"jc"` - JMin int `proxy:"jmin"` - JMax int `proxy:"jmax"` - S1 int `proxy:"s1"` - S2 int `proxy:"s2"` - H1 uint32 `proxy:"h1"` - H2 uint32 `proxy:"h2"` - H3 uint32 `proxy:"h3"` - H4 uint32 `proxy:"h4"` + JC int `proxy:"jc,omitempty"` + JMin int `proxy:"jmin,omitempty"` + JMax int `proxy:"jmax,omitempty"` + S1 int `proxy:"s1,omitempty"` + S2 int `proxy:"s2,omitempty"` + H1 uint32 `proxy:"h1,omitempty"` + H2 uint32 `proxy:"h2,omitempty"` + H3 uint32 `proxy:"h3,omitempty"` + H4 uint32 `proxy:"h4,omitempty"` + + // AmneziaWG v1.5 + I1 string `proxy:"i1,omitempty"` + I2 string `proxy:"i2,omitempty"` + I3 string `proxy:"i3,omitempty"` + I4 string `proxy:"i4,omitempty"` + I5 string `proxy:"i5,omitempty"` + J1 string `proxy:"j1,omitempty"` + J2 string `proxy:"j2,omitempty"` + J3 string `proxy:"j3,omitempty"` + Itime int64 `proxy:"itime,omitempty"` } type wgSingErrorHandler struct { @@ -386,15 +397,60 @@ func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, er if !updateOnly { ipcConf += "private_key=" + w.option.PrivateKey + "\n" if w.option.AmneziaWGOption != nil { - ipcConf += "jc=" + strconv.Itoa(w.option.AmneziaWGOption.JC) + "\n" - ipcConf += "jmin=" + strconv.Itoa(w.option.AmneziaWGOption.JMin) + "\n" - ipcConf += "jmax=" + strconv.Itoa(w.option.AmneziaWGOption.JMax) + "\n" - ipcConf += "s1=" + strconv.Itoa(w.option.AmneziaWGOption.S1) + "\n" - ipcConf += "s2=" + strconv.Itoa(w.option.AmneziaWGOption.S2) + "\n" - ipcConf += "h1=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H1), 10) + "\n" - ipcConf += "h2=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H2), 10) + "\n" - ipcConf += "h3=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H3), 10) + "\n" - ipcConf += "h4=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H4), 10) + "\n" + if w.option.AmneziaWGOption.JC != 0 { + ipcConf += "jc=" + strconv.Itoa(w.option.AmneziaWGOption.JC) + "\n" + } + if w.option.AmneziaWGOption.JMin != 0 { + ipcConf += "jmin=" + strconv.Itoa(w.option.AmneziaWGOption.JMin) + "\n" + } + if w.option.AmneziaWGOption.JMax != 0 { + ipcConf += "jmax=" + strconv.Itoa(w.option.AmneziaWGOption.JMax) + "\n" + } + if w.option.AmneziaWGOption.S1 != 0 { + ipcConf += "s1=" + strconv.Itoa(w.option.AmneziaWGOption.S1) + "\n" + } + if w.option.AmneziaWGOption.S2 != 0 { + ipcConf += "s2=" + strconv.Itoa(w.option.AmneziaWGOption.S2) + "\n" + } + if w.option.AmneziaWGOption.H1 != 0 { + ipcConf += "h1=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H1), 10) + "\n" + } + if w.option.AmneziaWGOption.H2 != 0 { + ipcConf += "h2=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H2), 10) + "\n" + } + if w.option.AmneziaWGOption.H3 != 0 { + ipcConf += "h3=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H3), 10) + "\n" + } + if w.option.AmneziaWGOption.H4 != 0 { + ipcConf += "h4=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H4), 10) + "\n" + } + if w.option.AmneziaWGOption.I1 != "" { + ipcConf += "i1=" + w.option.AmneziaWGOption.I1 + "\n" + } + if w.option.AmneziaWGOption.I2 != "" { + ipcConf += "i2=" + w.option.AmneziaWGOption.I2 + "\n" + } + if w.option.AmneziaWGOption.I3 != "" { + ipcConf += "i3=" + w.option.AmneziaWGOption.I3 + "\n" + } + if w.option.AmneziaWGOption.I4 != "" { + ipcConf += "i4=" + w.option.AmneziaWGOption.I4 + "\n" + } + if w.option.AmneziaWGOption.I5 != "" { + ipcConf += "i5=" + w.option.AmneziaWGOption.I5 + "\n" + } + if w.option.AmneziaWGOption.J1 != "" { + ipcConf += "j1=" + w.option.AmneziaWGOption.J1 + "\n" + } + if w.option.AmneziaWGOption.J2 != "" { + ipcConf += "j2=" + w.option.AmneziaWGOption.J2 + "\n" + } + if w.option.AmneziaWGOption.J3 != "" { + ipcConf += "j3=" + w.option.AmneziaWGOption.J3 + "\n" + } + if w.option.AmneziaWGOption.Itime != 0 { + ipcConf += "itime=" + strconv.FormatInt(int64(w.option.AmneziaWGOption.Itime), 10) + "\n" + } } } if len(w.option.Peers) > 0 { diff --git a/mihomo/docs/config.yaml b/mihomo/docs/config.yaml index 445c61a388..80af843bf9 100644 --- a/mihomo/docs/config.yaml +++ b/mihomo/docs/config.yaml @@ -857,6 +857,16 @@ proxies: # socks5 # h2: 67543 # h4: 32345 # h3: 123123 + # # AmneziaWG v1.5 + # i1: + # i2: + # i3: "" + # i4: "" + # i5: "" + # j1: + # j2: + # j3: + # itime: 60 # tuic - name: tuic diff --git a/mihomo/go.mod b/mihomo/go.mod index 2e19c8a24a..5695c7827e 100644 --- a/mihomo/go.mod +++ b/mihomo/go.mod @@ -14,7 +14,7 @@ require ( github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 github.com/klauspost/compress v1.17.9 // lastest version compatible with golang1.20 github.com/mdlayher/netlink v1.7.2 - github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab + github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a github.com/metacubex/bart v0.20.5 github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b github.com/metacubex/blake3 v0.1.0 @@ -36,7 +36,7 @@ require ( github.com/metacubex/smux v0.0.0-20250503055512-501391591dee github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 github.com/metacubex/utls v1.8.1-0.20250811145843-49b4f106169a - github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 + github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20 github.com/mroth/weightedrand/v2 v2.1.0 github.com/openacid/low v0.1.21 diff --git a/mihomo/go.sum b/mihomo/go.sum index 18cbabb0a0..69811ad2ba 100644 --- a/mihomo/go.sum +++ b/mihomo/go.sum @@ -90,8 +90,8 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= -github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4= -github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI= +github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a h1:c1QSGpacSeQdBdWcEKZKGuWLcqIG2wxHEygAcXuDwS4= +github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY= github.com/metacubex/ascon v0.1.0 h1:6ZWxmXYszT1XXtwkf6nxfFhc/OTtQ9R3Vyj1jN32lGM= github.com/metacubex/ascon v0.1.0/go.mod h1:eV5oim4cVPPdEL8/EYaTZ0iIKARH9pnhAK/fcT5Kacc= github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM= @@ -141,8 +141,8 @@ github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nU github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/utls v1.8.1-0.20250811145843-49b4f106169a h1:IIzlVmDoB4+7b0BUcLZaY5+AirhhLFep3PhwkAFMRnQ= github.com/metacubex/utls v1.8.1-0.20250811145843-49b4f106169a/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ= -github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ= -github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y= +github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk= +github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU= diff --git a/mihomo/transport/vless/conn.go b/mihomo/transport/vless/conn.go index 180a9969d2..94ae71eee0 100644 --- a/mihomo/transport/vless/conn.go +++ b/mihomo/transport/vless/conn.go @@ -30,26 +30,22 @@ type Conn struct { } func (vc *Conn) Read(b []byte) (int, error) { - if vc.received { - return vc.ExtendedReader.Read(b) + if !vc.received { + if err := vc.recvResponse(); err != nil { + return 0, err + } + vc.received = true } - - if err := vc.recvResponse(); err != nil { - return 0, err - } - vc.received = true return vc.ExtendedReader.Read(b) } func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error { - if vc.received { - return vc.ExtendedReader.ReadBuffer(buffer) + if !vc.received { + if err := vc.recvResponse(); err != nil { + return err + } + vc.received = true } - - if err := vc.recvResponse(); err != nil { - return err - } - vc.received = true return vc.ExtendedReader.ReadBuffer(buffer) } @@ -190,7 +186,7 @@ func newConn(conn net.Conn, client *Client, dst *DstAddr) (net.Conn, error) { if client.Addons != nil { switch client.Addons.Flow { case XRV: - visionConn, err := vision.NewConn(c, c.id) + visionConn, err := vision.NewConn(c, conn, c.id) if err != nil { return nil, err } diff --git a/mihomo/transport/vless/vision/conn.go b/mihomo/transport/vless/vision/conn.go index a0e83f7145..b8367fb9e4 100644 --- a/mihomo/transport/vless/vision/conn.go +++ b/mihomo/transport/vless/vision/conn.go @@ -21,15 +21,16 @@ var ( ) type Conn struct { - net.Conn + net.Conn // should be *vless.Conn N.ExtendedReader N.ExtendedWriter - upstream net.Conn userUUID *uuid.UUID - tlsConn net.Conn - input *bytes.Reader - rawInput *bytes.Buffer + // tlsConn and it's internal variables + tlsConn net.Conn // maybe [*tls.Conn] or other tls-like conn + netConn net.Conn // tlsConn.NetConn() + input *bytes.Reader // &tlsConn.input or nil + rawInput *bytes.Buffer // &tlsConn.rawInput or nil needHandshake bool packetsToFilter int @@ -143,7 +144,7 @@ func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error { } if vc.input == nil && vc.rawInput == nil { vc.readProcess = false - vc.ExtendedReader = N.NewExtendedReader(vc.Conn) + vc.ExtendedReader = N.NewExtendedReader(vc.netConn) log.Debugln("XTLS Vision direct read start") } if needReturn { @@ -214,7 +215,7 @@ func (vc *Conn) WriteBuffer(buffer *buf.Buffer) (err error) { return err } if vc.writeDirect { - vc.ExtendedWriter = N.NewExtendedWriter(vc.Conn) + vc.ExtendedWriter = N.NewExtendedWriter(vc.netConn) log.Debugln("XTLS Vision direct write start") //time.Sleep(5 * time.Millisecond) } @@ -235,7 +236,7 @@ func (vc *Conn) WriteBuffer(buffer *buf.Buffer) (err error) { ApplyPadding(buffer2, command, nil, vc.isTLS) err = vc.ExtendedWriter.WriteBuffer(buffer2) if vc.writeDirect { - vc.ExtendedWriter = N.NewExtendedWriter(vc.Conn) + vc.ExtendedWriter = N.NewExtendedWriter(vc.netConn) log.Debugln("XTLS Vision direct write start") //time.Sleep(10 * time.Millisecond) } @@ -266,9 +267,9 @@ func (vc *Conn) NeedHandshake() bool { func (vc *Conn) Upstream() any { if vc.writeDirect || vc.readLastCommand == commandPaddingDirect { - return vc.Conn + return vc.netConn } - return vc.upstream + return vc.Conn } func (vc *Conn) ReaderPossiblyReplaceable() bool { @@ -293,3 +294,10 @@ func (vc *Conn) WriterReplaceable() bool { } return false } + +func (vc *Conn) Close() error { + if vc.ReaderReplaceable() || vc.WriterReplaceable() { // ignore send closeNotify alert in tls.Conn + return vc.netConn.Close() + } + return vc.Conn.Close() +} diff --git a/mihomo/transport/vless/vision/vision.go b/mihomo/transport/vless/vision/vision.go index 32634f0ce8..108e01774a 100644 --- a/mihomo/transport/vless/vision/vision.go +++ b/mihomo/transport/vless/vision/vision.go @@ -15,22 +15,17 @@ import ( "github.com/metacubex/mihomo/transport/vless/encryption" "github.com/gofrs/uuid/v5" - "github.com/metacubex/sing/common" ) var ErrNotTLS13 = errors.New("XTLS Vision based on TLS 1.3 outer connection") -type connWithUpstream interface { - net.Conn - common.WithUpstream -} - -func NewConn(conn connWithUpstream, userUUID *uuid.UUID) (*Conn, error) { +func NewConn(conn net.Conn, tlsConn net.Conn, userUUID *uuid.UUID) (*Conn, error) { c := &Conn{ ExtendedReader: N.NewExtendedReader(conn), ExtendedWriter: N.NewExtendedWriter(conn), - upstream: conn, + Conn: conn, userUUID: userUUID, + tlsConn: tlsConn, packetsToFilter: 6, needHandshake: true, readProcess: true, @@ -39,36 +34,31 @@ func NewConn(conn connWithUpstream, userUUID *uuid.UUID) (*Conn, error) { } var t reflect.Type var p unsafe.Pointer - switch underlying := conn.Upstream().(type) { + switch underlying := tlsConn.(type) { case *gotls.Conn: //log.Debugln("type tls") - c.Conn = underlying.NetConn() - c.tlsConn = underlying + c.netConn = underlying.NetConn() t = reflect.TypeOf(underlying).Elem() p = unsafe.Pointer(underlying) case *tlsC.Conn: //log.Debugln("type *tlsC.Conn") - c.Conn = underlying.NetConn() - c.tlsConn = underlying + c.netConn = underlying.NetConn() t = reflect.TypeOf(underlying).Elem() p = unsafe.Pointer(underlying) case *tlsC.UConn: //log.Debugln("type *tlsC.UConn") - c.Conn = underlying.NetConn() - c.tlsConn = underlying + c.netConn = underlying.NetConn() t = reflect.TypeOf(underlying.Conn).Elem() //log.Debugln("t:%v", t) p = unsafe.Pointer(underlying.Conn) case *encryption.ClientConn: //log.Debugln("type *encryption.ClientConn") - c.Conn = underlying.Conn - c.tlsConn = underlying + c.netConn = underlying.Conn t = reflect.TypeOf(underlying).Elem() p = unsafe.Pointer(underlying) case *encryption.ServerConn: //log.Debugln("type *encryption.ServerConn") - c.Conn = underlying.Conn - c.tlsConn = underlying + c.netConn = underlying.Conn t = reflect.TypeOf(underlying).Elem() p = unsafe.Pointer(underlying) default: diff --git a/openwrt-packages/luci-app-amlogic/Makefile b/openwrt-packages/luci-app-amlogic/Makefile index 6f901e9989..ae6bc7ac4d 100644 --- a/openwrt-packages/luci-app-amlogic/Makefile +++ b/openwrt-packages/luci-app-amlogic/Makefile @@ -16,7 +16,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-amlogic -PKG_VERSION:=3.1.266 +PKG_VERSION:=3.1.268 PKG_RELEASE:=1 PKG_LICENSE:=GPL-2.0 License diff --git a/openwrt-packages/luci-app-amlogic/root/usr/sbin/openwrt-kernel b/openwrt-packages/luci-app-amlogic/root/usr/sbin/openwrt-kernel index 620a7d4f5a..009f8383e8 100755 --- a/openwrt-packages/luci-app-amlogic/root/usr/sbin/openwrt-kernel +++ b/openwrt-packages/luci-app-amlogic/root/usr/sbin/openwrt-kernel @@ -268,7 +268,8 @@ update_kernel() { rm -f /boot/uInitrd* else valid_files="vmlinuz-${kernel_name} uInitrd-${kernel_name} config-${kernel_name} System.map-${kernel_name}" - rm -f /boot/initrd.img* + # wxy-oect: MODEL_ID numbers r304 and r306, require special handling of uInitrd + [[ "${MODEL_ID}" =~ ^(r304|r306)$ ]] || rm -f /boot/initrd.img* fi for f in ${valid_files}; do [[ -f "/boot/${f}" ]] || restore_kernel; done @@ -289,9 +290,7 @@ update_kernel() { fi # wxy-oect: MODEL_ID numbers r304 and r306, require special handling of uInitrd - [[ "${MODEL_ID}" == "r304" || "${MODEL_ID}" == "r306" ]] && { - rm -f uInitrd && ln -sf initrd.img-${kernel_name} uInitrd - } + [[ "${MODEL_ID}" =~ ^(r304|r306)$ ]] && ln -sf initrd.img-${kernel_name} uInitrd ) echo -e "(1/3) Unpacking [ boot-${kernel_name}.tar.gz ] done." diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua index fccedeb7de..e2bdc973c7 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua @@ -212,11 +212,11 @@ o:value("direct", translate("Direct Connection")) o:value("proxy", translate("Proxy")) o = s:option(Value, "user_agent", translate("User-Agent")) -o.default = "v2rayN/9.99" +o.default = "passwall" +o:value("passwall", "PassWall") +o:value("v2rayN/9.99", "v2rayN") o:value("curl", "Curl") o:value("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Linux") o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Windows") -o:value("Passwall/OpenWrt", "PassWall") -o:value("v2rayN/9.99", "v2rayN") return m diff --git a/shadowsocks-rust/Cargo.lock b/shadowsocks-rust/Cargo.lock index 29aaf38d29..350d474067 100644 --- a/shadowsocks-rust/Cargo.lock +++ b/shadowsocks-rust/Cargo.lock @@ -509,9 +509,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -800,7 +800,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] @@ -1403,7 +1403,7 @@ dependencies = [ "ring", "rustls", "serde", - "thiserror 2.0.15", + "thiserror 2.0.16", "tinyvec", "tokio", "tokio-rustls", @@ -1431,7 +1431,7 @@ dependencies = [ "rustls", "serde", "smallvec", - "thiserror 2.0.15", + "thiserror 2.0.16", "tokio", "tokio-rustls", "tracing", @@ -2451,7 +2451,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror 2.0.15", + "thiserror 2.0.16", "ucd-trie", ] @@ -2670,7 +2670,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2 0.5.10", - "thiserror 2.0.15", + "thiserror 2.0.16", "tokio", "tracing", "web-time", @@ -2691,7 +2691,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.15", + "thiserror 2.0.16", "tinyvec", "tracing", "web-time", @@ -2808,7 +2808,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] @@ -3322,7 +3322,7 @@ dependencies = [ "shadowsocks-crypto", "socket2 0.6.0", "spin", - "thiserror 2.0.15", + "thiserror 2.0.16", "tokio", "tokio-tfo", "trait-variant", @@ -3389,7 +3389,7 @@ dependencies = [ "snmalloc-rs", "sysexits", "tcmalloc", - "thiserror 2.0.15", + "thiserror 2.0.16", "time", "tokio", "tracing", @@ -3438,7 +3438,7 @@ dependencies = [ "smoltcp", "socket2 0.6.0", "spin", - "thiserror 2.0.15", + "thiserror 2.0.16", "tokio", "tokio-native-tls", "tokio-rustls", @@ -3729,11 +3729,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.15" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.15", + "thiserror-impl 2.0.16", ] [[package]] @@ -3749,9 +3749,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.15" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d29feb33e986b6ea906bd9c3559a856983f92371b3eaa5e83782a351623de0" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -4055,7 +4055,7 @@ dependencies = [ "libc", "log", "nix", - "thiserror 2.0.15", + "thiserror 2.0.16", "tokio", "tokio-util", "windows-sys 0.60.2", @@ -4739,7 +4739,7 @@ dependencies = [ "futures", "libloading", "log", - "thiserror 2.0.15", + "thiserror 2.0.16", "windows-sys 0.60.2", "winreg 0.55.0", ] diff --git a/sing-box/common/badtls/read_wait.go b/sing-box/common/badtls/read_wait.go index 334bcfa81e..9508a7e368 100644 --- a/sing-box/common/badtls/read_wait.go +++ b/sing-box/common/badtls/read_wait.go @@ -128,6 +128,10 @@ func (c *ReadWaitConn) Upstream() any { return c.Conn } +func (c *ReadWaitConn) ReaderReplaceable() bool { + return true +} + var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) func init() { diff --git a/sing-box/common/badtls/read_wait_utls.go b/sing-box/common/badtls/read_wait_utls.go index bba016e487..1facd30bd2 100644 --- a/sing-box/common/badtls/read_wait_utls.go +++ b/sing-box/common/badtls/read_wait_utls.go @@ -6,22 +6,26 @@ import ( "net" _ "unsafe" - "github.com/sagernet/sing/common" - "github.com/metacubex/utls" ) func init() { tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) { - tlsConn, loaded := common.Cast[*tls.UConn](conn) - if !loaded { - return + switch tlsConn := conn.(type) { + case *tls.UConn: + return true, func() error { + return utlsReadRecord(tlsConn.Conn) + }, func() error { + return utlsHandlePostHandshakeMessage(tlsConn.Conn) + } + case *tls.Conn: + return true, func() error { + return utlsReadRecord(tlsConn) + }, func() error { + return utlsHandlePostHandshakeMessage(tlsConn) + } } - return true, func() error { - return utlsReadRecord(tlsConn.Conn) - }, func() error { - return utlsHandlePostHandshakeMessage(tlsConn.Conn) - } + return }) } diff --git a/sing-box/common/tls/reality_client.go b/sing-box/common/tls/reality_client.go index ca0e1e0460..d056966c9a 100644 --- a/sing-box/common/tls/reality_client.go +++ b/sing-box/common/tls/reality_client.go @@ -307,3 +307,11 @@ func (c *realityClientConnWrapper) Upstream() any { func (c *realityClientConnWrapper) CloseWrite() error { return c.Close() } + +func (c *realityClientConnWrapper) ReaderReplaceable() bool { + return true +} + +func (c *realityClientConnWrapper) WriterReplaceable() bool { + return true +} diff --git a/sing-box/common/tlsfragment/conn.go b/sing-box/common/tlsfragment/conn.go index cb29000ed5..040663bd2f 100644 --- a/sing-box/common/tlsfragment/conn.go +++ b/sing-box/common/tlsfragment/conn.go @@ -109,6 +109,9 @@ func (c *Conn) Write(b []byte) (n int, err error) { if err != nil { return } + if i != len(splitIndexes) { + time.Sleep(c.fallbackDelay) + } } } } diff --git a/sing-box/common/tlsfragment/wait_stub.go b/sing-box/common/tlsfragment/wait_stub.go index 7e451a0414..6a1cd88942 100644 --- a/sing-box/common/tlsfragment/wait_stub.go +++ b/sing-box/common/tlsfragment/wait_stub.go @@ -9,6 +9,10 @@ import ( ) func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error { + _, err := conn.Write(payload) + if err != nil { + return err + } time.Sleep(fallbackDelay) return nil } diff --git a/sing-box/common/tlsfragment/wait_windows.go b/sing-box/common/tlsfragment/wait_windows.go index 118a204dbf..49706ca574 100644 --- a/sing-box/common/tlsfragment/wait_windows.go +++ b/sing-box/common/tlsfragment/wait_windows.go @@ -16,6 +16,9 @@ func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fal err := winiphlpapi.WriteAndWaitAck(ctx, conn, payload) if err != nil { if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + if _, err := conn.Write(payload); err != nil { + return err + } time.Sleep(fallbackDelay) return nil } diff --git a/sing-box/dns/transport/dhcp/dhcp.go b/sing-box/dns/transport/dhcp/dhcp.go index b4be198c7c..b56a60e7b8 100644 --- a/sing-box/dns/transport/dhcp/dhcp.go +++ b/sing-box/dns/transport/dhcp/dhcp.go @@ -76,6 +76,8 @@ func NewRawTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer: dialer, logger: logger, networkManager: service.FromContext[adapter.NetworkManager](ctx), + ndots: 1, + attempts: 2, } } @@ -83,13 +85,15 @@ func (t *Transport) Start(stage adapter.StartStage) error { if stage != adapter.StartStateStart { return nil } - _, err := t.Fetch() - if err != nil { - t.logger.Error(E.Cause(err, "fetch DNS servers")) - } if t.interfaceName == "" { t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated) } + go func() { + _, err := t.Fetch() + if err != nil { + t.logger.Error(E.Cause(err, "fetch DNS servers")) + } + }() return nil } diff --git a/sing-box/dns/transport/dhcp/dhcp_shared.go b/sing-box/dns/transport/dhcp/dhcp_shared.go index e470b1da5d..31b92fb5ac 100644 --- a/sing-box/dns/transport/dhcp/dhcp_shared.go +++ b/sing-box/dns/transport/dhcp/dhcp_shared.go @@ -85,7 +85,7 @@ func (t *Transport) tryOneName(ctx context.Context, servers []M.Socksaddr, fqdn var lastErr error for i := 0; i < t.attempts; i++ { for j := 0; j < sLen; j++ { - server := servers[j%sLen] + server := servers[j] question := message.Question[0] question.Name = fqdn response, err := t.exchangeOne(ctx, server, question, C.DNSTimeout, false, true) diff --git a/sing-box/docs/changelog.md b/sing-box/docs/changelog.md index 79cf1c107b..85f792f9c1 100644 --- a/sing-box/docs/changelog.md +++ b/sing-box/docs/changelog.md @@ -2,7 +2,7 @@ icon: material/alert-decagram --- -#### 1.13.0-alpha.4 +#### 1.13.0-alpha.5 * Fixes and improvements diff --git a/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/node.js b/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/node.js index 0a425ae1ce..166866a0d5 100644 --- a/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/node.js +++ b/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/node.js @@ -126,12 +126,15 @@ function parseShareLink(uri, features) { url = new URL('http://' + uri[1]); let userinfo; - if (url.username && url.password) + if (url.username && url.password) { /* User info encoded with URIComponent */ userinfo = [url.username, decodeURIComponent(url.password)]; - else if (url.username) + } else if (url.username) { /* User info encoded with base64 */ userinfo = hp.decodeBase64Str(decodeURIComponent(url.username)).split(':'); + if (userinfo.length > 1) + userinfo = [userinfo[0], userinfo.slice(1).join(':')] + } if (!hp.shadowsocks_encrypt_methods.includes(userinfo[0])) return null; @@ -140,7 +143,7 @@ function parseShareLink(uri, features) { if (url.search && url.searchParams.get('plugin')) { let plugin_info = url.searchParams.get('plugin').split(';'); plugin = plugin_info[0]; - plugin_opts = plugin_info.slice(1) ? plugin_info.slice(1).join(';') : null; + plugin_opts = (plugin_info.length > 1) ? plugin_info.slice(1).join(';') : null; } config = { diff --git a/small/luci-app-homeproxy/root/etc/init.d/homeproxy b/small/luci-app-homeproxy/root/etc/init.d/homeproxy index 9cdbd9f6f5..888bba7a9c 100755 --- a/small/luci-app-homeproxy/root/etc/init.d/homeproxy +++ b/small/luci-app-homeproxy/root/etc/init.d/homeproxy @@ -149,6 +149,9 @@ start_service() { procd_set_param command "$PROG" procd_append_param command run --config "$RUN_DIR/sing-box-c.json" + # QUIC-GO GSO is broken on kernel 6.6 currently + uname -r | grep -Eq "^6\.6" && procd_set_param env "QUIC_GO_DISABLE_GSO"="true" + if [ -x "/sbin/ujail" ] && [ "$routing_mode" != "custom" ] && ! grep -Eq '"type": "(wireguard|tun)"' "$RUN_DIR/sing-box-c.json"; then procd_add_jail "sing-box-c" log procfs procd_add_jail_mount "$RUN_DIR/sing-box-c.json" @@ -190,6 +193,9 @@ start_service() { procd_set_param command "$PROG" procd_append_param command run --config "$RUN_DIR/sing-box-s.json" + # QUIC-GO GSO is broken on kernel 6.6 currently + uname -r | grep -Eq "^6\.6" && procd_set_param env "QUIC_GO_DISABLE_GSO"="true" + if [ -x "/sbin/ujail" ]; then procd_add_jail "sing-box-s" log procfs procd_add_jail_mount "$RUN_DIR/sing-box-s.json" diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua index fccedeb7de..e2bdc973c7 100644 --- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua +++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua @@ -212,11 +212,11 @@ o:value("direct", translate("Direct Connection")) o:value("proxy", translate("Proxy")) o = s:option(Value, "user_agent", translate("User-Agent")) -o.default = "v2rayN/9.99" +o.default = "passwall" +o:value("passwall", "PassWall") +o:value("v2rayN/9.99", "v2rayN") o:value("curl", "Curl") o:value("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Linux") o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Windows") -o:value("Passwall/OpenWrt", "PassWall") -o:value("v2rayN/9.99", "v2rayN") return m diff --git a/small/nikki/files/ucode/hijack.ut b/small/nikki/files/ucode/hijack.ut index 15c1487064..dde34d64b5 100644 --- a/small/nikki/files/ucode/hijack.ut +++ b/small/nikki/files/ucode/hijack.ut @@ -108,15 +108,15 @@ -%} table inet nikki { + {% if (length(dns_hijack_nfproto) > 0): %} set dns_hijack_nfproto { type nf_proto flags interval - {% if (length(dns_hijack_nfproto) > 0): %} elements = { {{ join(', ', dns_hijack_nfproto) }} } - {% endif %} } + {% endif %} set proxy_nfproto { type nf_proto @@ -421,7 +421,9 @@ table inet nikki { {% elif (cgroups_version == 2): %} socket cgroupv2 level 2 "services/{{ cgroup_name }}" counter return {% endif %} + {% if (length(dns_hijack_nfproto) > 0): %} meta nfproto @dns_hijack_nfproto jump router_dns_hijack + {% endif %} {% if (tcp_mode == 'redirect'): %} fib daddr type { local, broadcast, anycast, multicast } counter return ct direction reply counter return @@ -459,7 +461,9 @@ table inet nikki { meta nfproto ipv6 meta l4proto . th dport != @proxy_dport counter return meta l4proto { tcp, udp } ip dscp @bypass_dscp {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return meta l4proto { tcp, udp } ip6 dscp @bypass_dscp counter return + {% if (length(dns_hijack_nfproto) > 0): %} meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 counter return + {% endif %} {% if (tcp_mode == 'tproxy'): %} meta nfproto @proxy_nfproto meta l4proto tcp jump router_tproxy {% elif (tcp_mode == 'tun'): %} @@ -486,7 +490,9 @@ table inet nikki { {% if (lan_proxy): %} chain dstnat { type nat hook prerouting priority dstnat + 1; policy accept; + {% if (length(dns_hijack_nfproto) > 0): %} iifname @lan_inbound_device meta nfproto @dns_hijack_nfproto jump lan_dns_hijack + {% endif %} {% if (tcp_mode == 'redirect'): %} fib daddr type { local, broadcast, anycast, multicast } counter return ct direction reply counter return @@ -519,7 +525,9 @@ table inet nikki { meta nfproto ipv6 meta l4proto . th dport != @proxy_dport counter return meta l4proto { tcp, udp } ip dscp @bypass_dscp {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return meta l4proto { tcp, udp } ip6 dscp @bypass_dscp counter return + {% if (length(dns_hijack_nfproto) > 0): %} meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 counter return + {% endif %} {% if (tcp_mode == 'tproxy'): %} iifname @lan_inbound_device meta nfproto @proxy_nfproto meta l4proto tcp jump lan_tproxy {% elif (tcp_mode == 'tun'): %} diff --git a/small/sing-box/Makefile b/small/sing-box/Makefile index 3076312c37..d576f10678 100644 --- a/small/sing-box/Makefile +++ b/small/sing-box/Makefile @@ -6,12 +6,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=sing-box -PKG_VERSION:=1.12.1 +PKG_VERSION:=1.12.2 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/SagerNet/sing-box/tar.gz/v$(PKG_VERSION)? -PKG_HASH:=8c7de6f996c9d3ad363d60b52828dc649a579ae8a5f0b596fc8ff7ea7622908d +PKG_HASH:=95d902c008ed0b414ab29408dc565310fffe435a15753e02d10ca5c8e6837ce5 PKG_LICENSE:=GPL-3.0-or-later PKG_LICENSE_FILES:=LICENSE diff --git a/small/v2ray-geodata/Makefile b/small/v2ray-geodata/Makefile index 32ac757e6e..d1309b424b 100644 --- a/small/v2ray-geodata/Makefile +++ b/small/v2ray-geodata/Makefile @@ -21,13 +21,13 @@ define Download/geoip HASH:=54761d8691a5756fdb08d2cd4d0a9c889dbaab786e1cf758592e09fb00377f53 endef -GEOSITE_VER:=20250819114505 +GEOSITE_VER:=20250820044243 GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER) define Download/geosite URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/ URL_FILE:=dlc.dat FILE:=$(GEOSITE_FILE) - HASH:=0388dba07e0567e5f35e728f595be0e6ee8b98dcddacd15745499ddcf58cbfa8 + HASH:=08eaf7b6e93ff4422eac2919673ec53f5840643ab318e891981e0f3bd51100f9 endef GEOSITE_IRAN_VER:=202508180044 diff --git a/xray-core/go.mod b/xray-core/go.mod index 55f2196235..701196ac11 100644 --- a/xray-core/go.mod +++ b/xray-core/go.mod @@ -26,7 +26,7 @@ require ( golang.org/x/sync v0.16.0 golang.org/x/sys v0.35.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 - google.golang.org/grpc v1.74.2 + google.golang.org/grpc v1.75.0 google.golang.org/protobuf v1.36.7 gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5 h12.io/socks v1.0.3 @@ -52,7 +52,7 @@ require ( golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.35.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/xray-core/go.sum b/xray-core/go.sum index c0d8f92c33..d0e015fcd8 100644 --- a/xray-core/go.sum +++ b/xray-core/go.sum @@ -80,16 +80,16 @@ github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7/go.mod h1:XxvnCCgBee4 github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= @@ -139,10 +139,12 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= -google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/xray-core/transport/internet/dialer.go b/xray-core/transport/internet/dialer.go index 4ac478ecdb..e0da30c2ad 100644 --- a/xray-core/transport/internet/dialer.go +++ b/xray-core/transport/internet/dialer.go @@ -237,6 +237,9 @@ func DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig } outboundName = ob.Name origTargetAddr = ob.OriginalTarget.Address + if origTargetAddr == nil { + origTargetAddr = ob.Target.Address + } } if sockopt == nil { return effectiveSystemDialer.Dial(ctx, src, dest, sockopt) diff --git a/yt-dlp/.github/workflows/build.yml b/yt-dlp/.github/workflows/build.yml index ec5d4020ab..4a0220d702 100644 --- a/yt-dlp/.github/workflows/build.yml +++ b/yt-dlp/.github/workflows/build.yml @@ -24,9 +24,6 @@ on: windows: default: true type: boolean - windows32: - default: true - type: boolean origin: required: false default: '' @@ -65,11 +62,7 @@ on: default: true type: boolean windows: - description: yt-dlp.exe, yt-dlp_win.zip - default: true - type: boolean - windows32: - description: yt-dlp_x86.exe + description: yt-dlp.exe, yt-dlp_win.zip, yt-dlp_x86.exe, yt-dlp_win_x86.zip, yt-dlp_arm64.exe, yt-dlp_win_arm64.zip default: true type: boolean origin: @@ -340,36 +333,73 @@ jobs: windows: needs: process if: inputs.windows - runs-on: windows-latest + permissions: + contents: read + actions: write # For cleaning up cache + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - arch: 'x64' + runner: windows-2025 + python_version: '3.10' + suffix: '' + - arch: 'x86' + runner: windows-2025 + python_version: '3.10' + suffix: '_x86' + - arch: 'arm64' + runner: windows-11-arm + python_version: '3.13' # arm64 only has Python >= 3.11 available + suffix: '_arm64' steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: ${{ matrix.python_version }} + architecture: ${{ matrix.arch }} + + - name: Restore cached requirements + id: restore-cache + if: matrix.arch == 'arm64' + uses: actions/cache/restore@v4 + env: + SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1 + with: + path: | + /yt-dlp-build-venv + key: cache-reqs-${{ github.job }}_${{ matrix.arch }}-${{ matrix.python_version }}-${{ github.ref }} + - name: Install Requirements - run: | # Custom pyinstaller built with https://github.com/yt-dlp/pyinstaller-builds + run: | + python -m venv /yt-dlp-build-venv + /yt-dlp-build-venv/Scripts/Activate.ps1 python devscripts/install_deps.py -o --include build - python devscripts/install_deps.py --include curl-cffi - python -m pip install -U "https://yt-dlp.github.io/Pyinstaller-Builds/x64/pyinstaller-6.15.0-py3-none-any.whl" + python devscripts/install_deps.py ${{ (matrix.arch != 'x86' && '--include curl-cffi') || '' }} + # Use custom pyinstaller built with https://github.com/yt-dlp/pyinstaller-builds + python -m pip install -U "https://yt-dlp.github.io/Pyinstaller-Builds/${{ matrix.arch }}/pyinstaller-6.15.0-py3-none-any.whl" - name: Prepare run: | python devscripts/update-version.py -c "${{ inputs.channel }}" -r "${{ needs.process.outputs.origin }}" "${{ inputs.version }}" python devscripts/make_lazy_extractors.py + - name: Build run: | + /yt-dlp-build-venv/Scripts/Activate.ps1 python -m bundle.pyinstaller python -m bundle.pyinstaller --onedir - Compress-Archive -Path ./dist/yt-dlp/* -DestinationPath ./dist/yt-dlp_win.zip + Compress-Archive -Path ./dist/yt-dlp${{ matrix.suffix }}/* -DestinationPath ./dist/yt-dlp_win${{ matrix.suffix }}.zip - name: Verify --update-to if: vars.UPDATE_TO_VERIFICATION run: | - foreach ($name in @("yt-dlp")) { + foreach ($name in @("yt-dlp${{ matrix.suffix }}")) { Copy-Item "./dist/${name}.exe" "./dist/${name}_downgraded.exe" $version = & "./dist/${name}.exe" --version - & "./dist/${name}_downgraded.exe" -v --update-to yt-dlp/yt-dlp@2023.03.04 + & "./dist/${name}_downgraded.exe" -v --update-to yt-dlp/yt-dlp@2025.08.20 $downgraded_version = & "./dist/${name}_downgraded.exe" --version if ($version -eq $downgraded_version) { exit 1 @@ -379,57 +409,28 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v4 with: - name: build-bin-${{ github.job }} + name: build-bin-${{ github.job }}-${{ matrix.arch }} path: | - dist/yt-dlp.exe - dist/yt-dlp_win.zip + dist/yt-dlp${{ matrix.suffix }}.exe + dist/yt-dlp_win${{ matrix.suffix }}.zip compression-level: 0 - windows32: - needs: process - if: inputs.windows32 - runs-on: windows-latest + - name: Cleanup cache + if: | + matrix.arch == 'arm64' && steps.restore-cache.outputs.cache-hit == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + cache_key: cache-reqs-${{ github.job }}_${{ matrix.arch }}-${{ matrix.python_version }}-${{ github.ref }} + run: | + gh cache delete "${cache_key}" - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - name: Cache requirements + if: matrix.arch == 'arm64' + uses: actions/cache/save@v4 with: - python-version: "3.10" - architecture: "x86" - - name: Install Requirements - run: | - python devscripts/install_deps.py -o --include build - python devscripts/install_deps.py - python -m pip install -U "https://yt-dlp.github.io/Pyinstaller-Builds/x86/pyinstaller-6.15.0-py3-none-any.whl" - - - name: Prepare - run: | - python devscripts/update-version.py -c "${{ inputs.channel }}" -r "${{ needs.process.outputs.origin }}" "${{ inputs.version }}" - python devscripts/make_lazy_extractors.py - - name: Build - run: | - python -m bundle.pyinstaller - - - name: Verify --update-to - if: vars.UPDATE_TO_VERIFICATION - run: | - foreach ($name in @("yt-dlp_x86")) { - Copy-Item "./dist/${name}.exe" "./dist/${name}_downgraded.exe" - $version = & "./dist/${name}.exe" --version - & "./dist/${name}_downgraded.exe" -v --update-to yt-dlp/yt-dlp@2023.03.04 - $downgraded_version = & "./dist/${name}_downgraded.exe" --version - if ($version -eq $downgraded_version) { - exit 1 - } - } - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: build-bin-${{ github.job }} path: | - dist/yt-dlp_x86.exe - compression-level: 0 + /yt-dlp-build-venv + key: cache-reqs-${{ github.job }}_${{ matrix.arch }}-${{ matrix.python_version }}-${{ github.ref }} meta_files: if: always() && !cancelled() @@ -440,7 +441,6 @@ jobs: - linux_arm - macos - windows - - windows32 runs-on: ubuntu-latest steps: - name: Download artifacts diff --git a/yt-dlp/CONTRIBUTORS b/yt-dlp/CONTRIBUTORS index 629ef7f74a..37a0e100b9 100644 --- a/yt-dlp/CONTRIBUTORS +++ b/yt-dlp/CONTRIBUTORS @@ -800,3 +800,9 @@ iribeirocampos rolandcrosby Sojiroh tchebb +AzartX47 +e2dk4r +junyilou +PierreMesure +Randalix +runarmod diff --git a/yt-dlp/Changelog.md b/yt-dlp/Changelog.md index 61ad25f1eb..08a8494010 100644 --- a/yt-dlp/Changelog.md +++ b/yt-dlp/Changelog.md @@ -4,13 +4,64 @@ # To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master --> +### 2025.08.20 + +#### Core changes +- [Warn against using `-f mp4`](https://github.com/yt-dlp/yt-dlp/commit/70f56699515e0854a4853d214dce11b61d432387) ([#13915](https://github.com/yt-dlp/yt-dlp/issues/13915)) by [seproDev](https://github.com/seproDev) +- **utils**: [Add improved `jwt_encode` function](https://github.com/yt-dlp/yt-dlp/commit/35da8df4f843cb8f0656a301e5bebbf47d64d69a) ([#14071](https://github.com/yt-dlp/yt-dlp/issues/14071)) by [bashonly](https://github.com/bashonly) + +#### Extractor changes +- [Extract avif storyboard formats from MPD manifests](https://github.com/yt-dlp/yt-dlp/commit/770119bdd15c525ba4338503f0eb68ea4baedf10) ([#14016](https://github.com/yt-dlp/yt-dlp/issues/14016)) by [doe1080](https://github.com/doe1080) +- `_rta_search`: [Do not assume `age_limit` is `0`](https://github.com/yt-dlp/yt-dlp/commit/6ae3543d5a1feea0c546571fd2782b024c108eac) ([#13985](https://github.com/yt-dlp/yt-dlp/issues/13985)) by [doe1080](https://github.com/doe1080) +- **adobetv**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/c22660aed5fadb4ac29bdf25db4e8016414153cc) ([#13917](https://github.com/yt-dlp/yt-dlp/issues/13917)) by [doe1080](https://github.com/doe1080) +- **bilibili**: [Handle Bangumi redirection](https://github.com/yt-dlp/yt-dlp/commit/6ca9165648ac9a07c012de639faf50a97cbe0991) ([#14038](https://github.com/yt-dlp/yt-dlp/issues/14038)) by [grqz](https://github.com/grqz), [junyilou](https://github.com/junyilou) +- **faulio**: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/74b4b3b00516e92a60250e0626272a6826459057) ([#13907](https://github.com/yt-dlp/yt-dlp/issues/13907)) by [CasperMcFadden95](https://github.com/CasperMcFadden95) +- **francetv**: site: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/7b8a8abb98165a53c026e2a3f52faee608df1f20) ([#14082](https://github.com/yt-dlp/yt-dlp/issues/14082)) by [bashonly](https://github.com/bashonly) +- **medialaan**: [Rework extractors](https://github.com/yt-dlp/yt-dlp/commit/86d74e5cf0e06c53c931ccdbdd497e3f2c4d2fe2) ([#14015](https://github.com/yt-dlp/yt-dlp/issues/14015)) by [doe1080](https://github.com/doe1080) +- **mtv**: [Overhaul extractors](https://github.com/yt-dlp/yt-dlp/commit/8df121ba59208979aa713822781891347abd03d1) ([#14052](https://github.com/yt-dlp/yt-dlp/issues/14052)) by [bashonly](https://github.com/bashonly), [doe1080](https://github.com/doe1080), [Randalix](https://github.com/Randalix), [seproDev](https://github.com/seproDev) +- **niconico**: live: [Support age-restricted streams](https://github.com/yt-dlp/yt-dlp/commit/374ea049f531959bcccf8a1e6bc5659d228a780e) ([#13549](https://github.com/yt-dlp/yt-dlp/issues/13549)) by [doe1080](https://github.com/doe1080) +- **nrktvepisode**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/7540aa1da1800769af40381f423825a1a8826377) ([#14065](https://github.com/yt-dlp/yt-dlp/issues/14065)) by [runarmod](https://github.com/runarmod) +- **puhutv**: [Fix playlists extraction](https://github.com/yt-dlp/yt-dlp/commit/36e873822bdb2c5aba3780dd3ae32cbae564c6cd) ([#11955](https://github.com/yt-dlp/yt-dlp/issues/11955)) by [e2dk4r](https://github.com/e2dk4r) +- **steam**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/d3d1ac8eb2f9e96f3d75292e0effe2b1bccece3b) ([#14008](https://github.com/yt-dlp/yt-dlp/issues/14008)) by [AzartX47](https://github.com/AzartX47) +- **svt**: [Extract forced subs under separate lang code](https://github.com/yt-dlp/yt-dlp/commit/82a139020417a501f261d9fe02cefca01b1e12e4) ([#14062](https://github.com/yt-dlp/yt-dlp/issues/14062)) by [PierreMesure](https://github.com/PierreMesure) +- **tiktok**: user: [Avoid infinite loop during extraction](https://github.com/yt-dlp/yt-dlp/commit/edf55e81842fcfa6c302528d7f33ccd5081b37ef) ([#14032](https://github.com/yt-dlp/yt-dlp/issues/14032)) by [bashonly](https://github.com/bashonly) (With fixes in [471a2b6](https://github.com/yt-dlp/yt-dlp/commit/471a2b60e0a3e056960d9ceb1ebf57908428f752)) +- **vimeo** + - album: [Support embed-only and non-numeric albums](https://github.com/yt-dlp/yt-dlp/commit/d8200ff0a4699e06c9f7daca8f8531f8b98e68f2) ([#14021](https://github.com/yt-dlp/yt-dlp/issues/14021)) by [bashonly](https://github.com/bashonly) + - event: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/0f6b915822fb64bd944126fdacd401975c9f06ed) ([#14064](https://github.com/yt-dlp/yt-dlp/issues/14064)) by [bashonly](https://github.com/bashonly) +- **weibo** + - [Fix extractors](https://github.com/yt-dlp/yt-dlp/commit/8e3f8065af1415caeff788c5c430703dd0d8f576) ([#14012](https://github.com/yt-dlp/yt-dlp/issues/14012)) by [AzartX47](https://github.com/AzartX47), [bashonly](https://github.com/bashonly) + - [Support more URLs and --no-playlist](https://github.com/yt-dlp/yt-dlp/commit/404bd889d0e0b62ad72b7281e3fefdc0497080b3) ([#14035](https://github.com/yt-dlp/yt-dlp/issues/14035)) by [bashonly](https://github.com/bashonly) +- **youtube** + - [Add `es5` and `es6` player JS variants](https://github.com/yt-dlp/yt-dlp/commit/f2919bd28eac905f1267c62b83738a02bb5b4e04) ([#14005](https://github.com/yt-dlp/yt-dlp/issues/14005)) by [bashonly](https://github.com/bashonly) + - [Add `playback_wait` extractor-arg](https://github.com/yt-dlp/yt-dlp/commit/f63a7e41d120ef84f0f2274b0962438e3272d2fa) by [bashonly](https://github.com/bashonly) + - [Default to `main` player JS variant](https://github.com/yt-dlp/yt-dlp/commit/df0553153e41f81e3b30aa5bb1d119c61bd449ac) ([#14079](https://github.com/yt-dlp/yt-dlp/issues/14079)) by [bashonly](https://github.com/bashonly) + - [Extract title and description from initial data](https://github.com/yt-dlp/yt-dlp/commit/7bc53ae79930b36f4f947679545c75f36e9f0ddd) ([#14078](https://github.com/yt-dlp/yt-dlp/issues/14078)) by [bashonly](https://github.com/bashonly) + - [Handle required preroll waiting period](https://github.com/yt-dlp/yt-dlp/commit/a97f4cb57e61e19be61a7d5ac19665d4b567c960) ([#14081](https://github.com/yt-dlp/yt-dlp/issues/14081)) by [bashonly](https://github.com/bashonly) + - [Remove default player params](https://github.com/yt-dlp/yt-dlp/commit/d154dc3dcf0c7c75dbabb6cd1aca66fdd806f858) ([#14081](https://github.com/yt-dlp/yt-dlp/issues/14081)) by [bashonly](https://github.com/bashonly) + - tab: [Fix playlists tab extraction](https://github.com/yt-dlp/yt-dlp/commit/8a8861d53864c8a38e924bc0657ead5180f17268) ([#14030](https://github.com/yt-dlp/yt-dlp/issues/14030)) by [bashonly](https://github.com/bashonly) + +#### Downloader changes +- [Support `available_at` format field](https://github.com/yt-dlp/yt-dlp/commit/438d3f06b3c41bdef8112d40b75d342186e91a16) ([#13980](https://github.com/yt-dlp/yt-dlp/issues/13980)) by [bashonly](https://github.com/bashonly) + +#### Postprocessor changes +- **xattrmetadata**: [Only set "Where From" attribute on macOS](https://github.com/yt-dlp/yt-dlp/commit/bdeb3eb3f29eebbe8237fbc5186e51e7293eea4a) ([#13999](https://github.com/yt-dlp/yt-dlp/issues/13999)) by [bashonly](https://github.com/bashonly) + +#### Misc. changes +- **build** + - [Add Windows ARM64 builds](https://github.com/yt-dlp/yt-dlp/commit/07247d6c20fef1ad13b6f71f6355a44d308cf010) ([#14003](https://github.com/yt-dlp/yt-dlp/issues/14003)) by [bashonly](https://github.com/bashonly) + - [Bump PyInstaller version to 6.15.0 for Windows](https://github.com/yt-dlp/yt-dlp/commit/681ed2153de754c2c885fdad09ab71fffa8114f9) ([#14002](https://github.com/yt-dlp/yt-dlp/issues/14002)) by [bashonly](https://github.com/bashonly) + - [Discontinue `darwin_legacy_exe` support](https://github.com/yt-dlp/yt-dlp/commit/aea85d525e1007bb64baec0e170c054292d0858a) ([#13860](https://github.com/yt-dlp/yt-dlp/issues/13860)) by [bashonly](https://github.com/bashonly) +- **cleanup** + - [Remove dead extractors](https://github.com/yt-dlp/yt-dlp/commit/6f4c1bb593da92f0ce68229d0c813cdbaf1314da) ([#13996](https://github.com/yt-dlp/yt-dlp/issues/13996)) by [doe1080](https://github.com/doe1080) + - Miscellaneous: [c2fc4f3](https://github.com/yt-dlp/yt-dlp/commit/c2fc4f3e7f6d757250183b177130c64beee50520) by [bashonly](https://github.com/bashonly) + ### 2025.08.11 #### Important changes - **The minimum *recommended* Python version has been raised to 3.10** Since Python 3.9 will reach end-of-life in October 2025, support for it will be dropped soon. [Read more](https://github.com/yt-dlp/yt-dlp/issues/13858) - **darwin_legacy_exe builds are being discontinued** -This release's `yt-dlp_macos_legacy` binary will likely be the last one. [Read more](https://github.com/yt-dlp/yt-dlp/issues/13857) +This release's `yt-dlp_macos_legacy` binary will likely be the last one. [Read more](https://github.com/yt-dlp/yt-dlp/issues/13856) - **linux_armv7l_exe builds are being discontinued** This release's `yt-dlp_linux_armv7l` binary could be the last one. [Read more](https://github.com/yt-dlp/yt-dlp/issues/13976) diff --git a/yt-dlp/README.md b/yt-dlp/README.md index aa8b1d4f24..411c8725d5 100644 --- a/yt-dlp/README.md +++ b/yt-dlp/README.md @@ -106,10 +106,13 @@ File|Description File|Description :---|:--- [yt-dlp_x86.exe](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_x86.exe)|Windows (Win8+) standalone x86 (32-bit) binary +[yt-dlp_arm64.exe](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_arm64.exe)|Windows (Win10+) standalone arm64 (64-bit) binary [yt-dlp_linux](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux)|Linux standalone x64 binary [yt-dlp_linux_armv7l](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux_armv7l)|Linux standalone armv7l (32-bit) binary [yt-dlp_linux_aarch64](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux_aarch64)|Linux standalone aarch64 (64-bit) binary -[yt-dlp_win.zip](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_win.zip)|Unpackaged Windows executable (no auto-update) +[yt-dlp_win.zip](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_win.zip)|Unpackaged Windows (Win8+) x64 executable (no auto-update) +[yt-dlp_win_x86.zip](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_win_x86.zip)|Unpackaged Windows (Win8+) x86 executable (no auto-update) +[yt-dlp_win_arm64.zip](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_win_arm64.zip)|Unpackaged Windows (Win10+) arm64 executable (no auto-update) [yt-dlp_macos.zip](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos.zip)|Unpackaged MacOS (10.15+) executable (no auto-update) #### Misc @@ -1803,7 +1806,7 @@ The following extractors use this feature: * `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. -* `player_js_variant`: The player javascript variant to use for signature and nsig deciphering. The known variants are: `main`, `tce`, `tv`, `tv_es6`, `phone`, `tablet`. Only `main` is recommended as a possible workaround; the others are for debugging purposes. The default is to use what is prescribed by the site, and can be selected with `actual` +* `player_js_variant`: The player javascript variant to use for signature and nsig deciphering. The known variants are: `main`, `tce`, `tv`, `tv_es6`, `phone`, `tablet`. The default is `main`, and the others are for debugging purposes. You can use `actual` to go with what is prescribed by the site * `comment_sort`: `top` or `new` (default) - choose comment sorting mode (on YouTube's side) * `max_comments`: Limit the amount of comments to gather. Comma-separated list of integers representing `max-comments,max-parents,max-replies,max-replies-per-thread`. Default is `all,all,all,all` * E.g. `all,all,1000,10` will get a maximum of 1000 replies total, with up to 10 replies per thread. `1000,all,100` will get a maximum of 1000 comments, with a maximum of 100 replies total @@ -1816,6 +1819,7 @@ The following extractors use this feature: * `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) * `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) +* `playback_wait`: Duration (in seconds) to wait inbetween the extraction and download stages in order to ensure the formats are available. The default is `6` seconds #### 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/devscripts/changelog_override.json b/yt-dlp/devscripts/changelog_override.json index 9b808a7481..f551b44e9c 100644 --- a/yt-dlp/devscripts/changelog_override.json +++ b/yt-dlp/devscripts/changelog_override.json @@ -287,7 +287,7 @@ { "action": "add", "when": "cc5a5caac5fbc0d605b52bde0778d6fd5f97b5ab", - "short": "[priority] **darwin_legacy_exe builds are being discontinued**\nThis release's `yt-dlp_macos_legacy` binary will likely be the last one. [Read more](https://github.com/yt-dlp/yt-dlp/issues/13857)" + "short": "[priority] **darwin_legacy_exe builds are being discontinued**\nThis release's `yt-dlp_macos_legacy` binary will likely be the last one. [Read more](https://github.com/yt-dlp/yt-dlp/issues/13856)" }, { "action": "add", diff --git a/yt-dlp/pyproject.toml b/yt-dlp/pyproject.toml index d8c3d9e822..35f81423a8 100644 --- a/yt-dlp/pyproject.toml +++ b/yt-dlp/pyproject.toml @@ -315,6 +315,7 @@ banned-from = [ "yt_dlp.utils.error_to_compat_str".msg = "Use `str` instead." "yt_dlp.utils.bytes_to_intlist".msg = "Use `list` instead." "yt_dlp.utils.intlist_to_bytes".msg = "Use `bytes` instead." +"yt_dlp.utils.jwt_encode_hs256".msg = "Use `yt_dlp.utils.jwt_encode` instead." "yt_dlp.utils.decodeArgument".msg = "Do not use" "yt_dlp.utils.decodeFilename".msg = "Do not use" "yt_dlp.utils.encodeFilename".msg = "Do not use" diff --git a/yt-dlp/supportedsites.md b/yt-dlp/supportedsites.md index 26d5dab42c..323ff31930 100644 --- a/yt-dlp/supportedsites.md +++ b/yt-dlp/supportedsites.md @@ -44,11 +44,7 @@ The only reliable way to check if a site is supported is to try it. - **ADN**: [*animationdigitalnetwork*](## "netrc machine") Animation Digital Network - **ADNSeason**: [*animationdigitalnetwork*](## "netrc machine") Animation Digital Network - **AdobeConnect** - - **adobetv**: (**Currently broken**) - - **adobetv:channel**: (**Currently broken**) - - **adobetv:embed**: (**Currently broken**) - - **adobetv:show**: (**Currently broken**) - - **adobetv:video** + - **adobetv** - **AdultSwim** - **aenetworks**: A+E Networks: A&E, Lifetime, History.com, FYI Network and History Vault - **aenetworks:collection** @@ -100,7 +96,6 @@ The only reliable way to check if a site is supported is to try it. - **ARD** - **ARDMediathek** - **ARDMediathekCollection** - - **Arkena** - **Art19** - **Art19Show** - **arte.sky.it** @@ -155,9 +150,8 @@ The only reliable way to check if a site is supported is to try it. - **Beatport** - **Beeg** - **BehindKink**: (**Currently broken**) - - **Bellator** - **BerufeTV** - - **Bet**: (**Currently broken**) + - **Bet** - **bfi:player**: (**Currently broken**) - **bfmtv** - **bfmtv:article** @@ -290,12 +284,10 @@ The only reliable way to check if a site is supported is to try it. - **CloudyCDN** - **Clubic**: (**Currently broken**) - **Clyp** - - **cmt.com**: (**Currently broken**) - **CNBCVideo** - **CNN** - **CNNIndonesia** - **ComedyCentral** - - **ComedyCentralTV** - **ConanClassic**: (**Currently broken**) - **CondeNast**: Condé Nast media group: Allure, Architectural Digest, Ars Technica, Bon Appétit, Brides, Condé Nast, Condé Nast Traveler, Details, Epicurious, GQ, Glamour, Golf Digest, SELF, Teen Vogue, The New Yorker, Vanity Fair, Vogue, W Magazine, WIRED - **CONtv** @@ -445,6 +437,7 @@ The only reliable way to check if a site is supported is to try it. - **fancode:live**: [*fancode*](## "netrc machine") (**Currently broken**) - **fancode:vod**: [*fancode*](## "netrc machine") (**Currently broken**) - **Fathom** + - **Faulio** - **FaulioLive** - **faz.net** - **fc2**: [*fc2*](## "netrc machine") @@ -700,8 +693,8 @@ The only reliable way to check if a site is supported is to try it. - **lbry:channel**: odysee.com channels - **lbry:playlist**: odysee.com playlists - **LCI** - - **Lcp** - - **LcpPlay** + - **Lcp**: (**Currently broken**) + - **LcpPlay**: (**Currently broken**) - **Le**: 乐视网 - **LearningOnScreen** - **Lecture2Go**: (**Currently broken**) @@ -840,12 +833,6 @@ The only reliable way to check if a site is supported is to try it. - **MSN** - **mtg**: MTG services - **mtv** - - **mtv.de**: (**Currently broken**) - - **mtv.it** - - **mtv.it:programma** - - **mtv:video** - - **mtvjapan** - - **mtvservices:embedded** - **MTVUutisetArticle**: (**Currently broken**) - **MuenchenTV**: münchen.tv (**Currently broken**) - **MujRozhlas** @@ -945,9 +932,6 @@ The only reliable way to check if a site is supported is to try it. - **NhkVodProgram** - **nhl.com** - **nick.com** - - **nick.de** - - **nickelodeon:br** - - **nickelodeonru** - **niconico**: [*niconico*](## "netrc machine") ニコニコ動画 - **niconico:history**: NicoNico user history or likes. Requires cookies. - **niconico:live**: [*niconico*](## "netrc machine") ニコニコ生放送 @@ -1049,7 +1033,6 @@ The only reliable way to check if a site is supported is to try it. - **Panopto** - **PanoptoList** - **PanoptoPlaylist** - - **ParamountNetwork** - **ParamountPlus** - **ParamountPlusSeries** - **ParamountPressExpress** @@ -1088,7 +1071,6 @@ The only reliable way to check if a site is supported is to try it. - **PiramideTVChannel** - **pixiv:sketch** - **pixiv:​sketch:user** - - **Pladform** - **PlanetMarathi** - **Platzi**: [*platzi*](## "netrc machine") - **PlatziCourse**: [*platzi*](## "netrc machine") @@ -1377,8 +1359,9 @@ The only reliable way to check if a site is supported is to try it. - **southpark.cc.com:español** - **southpark.de** - **southpark.lat** - - **southpark.nl** - - **southparkstudios.dk** + - **southparkstudios.co.uk** + - **southparkstudios.com.br** + - **southparkstudios.nu** - **SovietsCloset** - **SovietsClosetPlaylist** - **SpankBang** @@ -1557,7 +1540,6 @@ The only reliable way to check if a site is supported is to try it. - **TVer** - **tvigle**: Интернет-телевидение Tvigle.ru - **TVIPlayer** - - **tvland.com** - **TVN24**: (**Currently broken**) - **TVNoe**: (**Currently broken**) - **tvopengr:embed**: tvopen.gr embedded videos @@ -1618,8 +1600,6 @@ The only reliable way to check if a site is supported is to try it. - **Vbox7** - **Veo** - **Vesti**: Вести.Ru (**Currently broken**) - - **Vevo** - - **VevoPlaylist** - **VGTV**: VGTV, BTTV, FTV, Aftenposten and Aftonbladet - **vh1.com** - **vhx:embed**: [*vimeo*](## "netrc machine") @@ -1698,7 +1678,7 @@ The only reliable way to check if a site is supported is to try it. - **vrsquare:section** - **VRT**: VRT NWS, Flanders News, Flandern Info and Sporza - **vrtmax**: [*vrtnu*](## "netrc machine") VRT MAX (formerly VRT NU) - - **VTM**: (**Currently broken**) + - **VTM** - **VTV** - **VTVGo** - **VTXTV**: [*vtxtv*](## "netrc machine") diff --git a/yt-dlp/test/test_subtitles.py b/yt-dlp/test/test_subtitles.py index efd69b33d9..53e0b4eaf8 100644 --- a/yt-dlp/test/test_subtitles.py +++ b/yt-dlp/test/test_subtitles.py @@ -14,7 +14,6 @@ from yt_dlp.extractor import ( NRKTVIE, PBSIE, CeskaTelevizeIE, - ComedyCentralIE, DailymotionIE, DemocracynowIE, LyndaIE, @@ -279,23 +278,6 @@ class TestNPOSubtitles(BaseTestSubtitles): self.assertEqual(md5(subtitles['nl']), 'fc6435027572b63fb4ab143abd5ad3f4') -@is_download_test -@unittest.skip('IE broken') -class TestMTVSubtitles(BaseTestSubtitles): - url = 'http://www.cc.com/video-clips/p63lk0/adam-devine-s-house-party-chasing-white-swans' - IE = ComedyCentralIE - - def getInfoDict(self): - return super().getInfoDict()['entries'][0] - - def test_allsubtitles(self): - self.DL.params['writesubtitles'] = True - self.DL.params['allsubtitles'] = True - subtitles = self.getSubtitles() - self.assertEqual(set(subtitles.keys()), {'en'}) - self.assertEqual(md5(subtitles['en']), '78206b8d8a0cfa9da64dc026eea48961') - - @is_download_test class TestNRKSubtitles(BaseTestSubtitles): url = 'http://tv.nrk.no/serie/ikke-gjoer-dette-hjemme/DMPV73000411/sesong-2/episode-1' diff --git a/yt-dlp/test/test_utils.py b/yt-dlp/test/test_utils.py index 44747efda6..dce07c3626 100644 --- a/yt-dlp/test/test_utils.py +++ b/yt-dlp/test/test_utils.py @@ -71,6 +71,8 @@ from yt_dlp.utils import ( iri_to_uri, is_html, js_to_json, + jwt_decode_hs256, + jwt_encode, limit_length, locked_file, lowercase_escape, @@ -2180,6 +2182,41 @@ Line 1 assert int_or_none(v=10) == 10, 'keyword passed positional should call function' assert int_or_none(scale=0.1)(10) == 100, 'call after partial application should call the function' + _JWT_KEY = '12345678' + _JWT_HEADERS_1 = {'a': 'b'} + _JWT_HEADERS_2 = {'typ': 'JWT', 'alg': 'HS256'} + _JWT_HEADERS_3 = {'typ': 'JWT', 'alg': 'RS256'} + _JWT_HEADERS_4 = {'c': 'd', 'alg': 'ES256'} + _JWT_DECODED = { + 'foo': 'bar', + 'qux': 'baz', + } + _JWT_SIMPLE = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJxdXgiOiJiYXoifQ.fKojvTWqnjNTbsdoDTmYNc4tgYAG3h_SWRzM77iLH0U' + _JWT_WITH_EXTRA_HEADERS = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImEiOiJiIn0.eyJmb28iOiJiYXIiLCJxdXgiOiJiYXoifQ.Ia91-B77yasfYM7jsB6iVKLew-3rO6ITjNmjWUVXCvQ' + _JWT_WITH_REORDERED_HEADERS = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIiLCJxdXgiOiJiYXoifQ.slg-7COta5VOfB36p3tqV4MGPV6TTA_ouGnD48UEVq4' + _JWT_WITH_REORDERED_HEADERS_AND_RS256_ALG = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIiLCJxdXgiOiJiYXoifQ.XWp496oVgQnoits0OOocutdjxoaQwn4GUWWxUsKENPM' + _JWT_WITH_EXTRA_HEADERS_AND_ES256_ALG = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImMiOiJkIn0.eyJmb28iOiJiYXIiLCJxdXgiOiJiYXoifQ.oM_tc7IkfrwkoRh43rFFE1wOi3J3mQGwx7_lMyKQqDg' + + def test_jwt_encode(self): + def test(expected, headers={}): + self.assertEqual(jwt_encode(self._JWT_DECODED, self._JWT_KEY, headers=headers), expected) + + test(self._JWT_SIMPLE) + test(self._JWT_WITH_EXTRA_HEADERS, headers=self._JWT_HEADERS_1) + test(self._JWT_WITH_REORDERED_HEADERS, headers=self._JWT_HEADERS_2) + test(self._JWT_WITH_REORDERED_HEADERS_AND_RS256_ALG, headers=self._JWT_HEADERS_3) + test(self._JWT_WITH_EXTRA_HEADERS_AND_ES256_ALG, headers=self._JWT_HEADERS_4) + + def test_jwt_decode_hs256(self): + def test(inp): + self.assertEqual(jwt_decode_hs256(inp), self._JWT_DECODED) + + test(self._JWT_SIMPLE) + test(self._JWT_WITH_EXTRA_HEADERS) + test(self._JWT_WITH_REORDERED_HEADERS) + test(self._JWT_WITH_REORDERED_HEADERS_AND_RS256_ALG) + test(self._JWT_WITH_EXTRA_HEADERS_AND_ES256_ALG) + if __name__ == '__main__': unittest.main() diff --git a/yt-dlp/yt_dlp/YoutubeDL.py b/yt-dlp/yt_dlp/YoutubeDL.py index 5ef2be21e5..76a760a5a8 100644 --- a/yt-dlp/yt_dlp/YoutubeDL.py +++ b/yt-dlp/yt_dlp/YoutubeDL.py @@ -599,7 +599,7 @@ class YoutubeDL: _NUMERIC_FIELDS = { 'width', 'height', 'asr', 'audio_channels', 'fps', 'tbr', 'abr', 'vbr', 'filesize', 'filesize_approx', - 'timestamp', 'release_timestamp', + 'timestamp', 'release_timestamp', 'available_at', 'duration', 'view_count', 'like_count', 'dislike_count', 'repost_count', 'average_rating', 'comment_count', 'age_limit', 'start_time', 'end_time', @@ -609,7 +609,7 @@ class YoutubeDL: _format_fields = { # NB: Keep in sync with the docstring of extractor/common.py - 'url', 'manifest_url', 'manifest_stream_number', 'ext', 'format', 'format_id', 'format_note', + 'url', 'manifest_url', 'manifest_stream_number', 'ext', 'format', 'format_id', 'format_note', 'available_at', 'width', 'height', 'aspect_ratio', 'resolution', 'dynamic_range', 'tbr', 'abr', 'acodec', 'asr', 'audio_channels', 'vbr', 'fps', 'vcodec', 'container', 'filesize', 'filesize_approx', 'rows', 'columns', 'hls_media_playlist_data', 'player_url', 'protocol', 'fragment_base_url', 'fragments', 'is_from_start', 'is_dash_periods', 'request_data', diff --git a/yt-dlp/yt_dlp/downloader/common.py b/yt-dlp/yt_dlp/downloader/common.py index 7bc70a51a2..3dd86a971d 100644 --- a/yt-dlp/yt_dlp/downloader/common.py +++ b/yt-dlp/yt_dlp/downloader/common.py @@ -455,14 +455,26 @@ class FileDownloader: self._finish_multiline_status() return True, False + sleep_note = '' if subtitle: sleep_interval = self.params.get('sleep_interval_subtitles') or 0 else: min_sleep_interval = self.params.get('sleep_interval') or 0 + max_sleep_interval = self.params.get('max_sleep_interval') or 0 + + if available_at := info_dict.get('available_at'): + forced_sleep_interval = available_at - int(time.time()) + if forced_sleep_interval > min_sleep_interval: + sleep_note = 'as required by the site' + min_sleep_interval = forced_sleep_interval + if forced_sleep_interval > max_sleep_interval: + max_sleep_interval = forced_sleep_interval + sleep_interval = random.uniform( - min_sleep_interval, self.params.get('max_sleep_interval') or min_sleep_interval) + min_sleep_interval, max_sleep_interval or min_sleep_interval) + if sleep_interval > 0: - self.to_screen(f'[download] Sleeping {sleep_interval:.2f} seconds ...') + self.to_screen(f'[download] Sleeping {sleep_interval:.2f} seconds {sleep_note}...') time.sleep(sleep_interval) ret = self.real_download(filename, info_dict) diff --git a/yt-dlp/yt_dlp/extractor/_extractors.py b/yt-dlp/yt_dlp/extractor/_extractors.py index 6c2eb6cddd..25bad4f0dc 100644 --- a/yt-dlp/yt_dlp/extractor/_extractors.py +++ b/yt-dlp/yt_dlp/extractor/_extractors.py @@ -398,16 +398,12 @@ from .cloudflarestream import CloudflareStreamIE from .cloudycdn import CloudyCDNIE from .clubic import ClubicIE from .clyp import ClypIE -from .cmt import CMTIE from .cnbc import CNBCVideoIE from .cnn import ( CNNIE, CNNIndonesiaIE, ) -from .comedycentral import ( - ComedyCentralIE, - ComedyCentralTVIE, -) +from .comedycentral import ComedyCentralIE from .commonmistakes import ( BlobIE, CommonMistakesIE, @@ -629,7 +625,10 @@ from .fancode import ( FancodeVodIE, ) from .fathom import FathomIE -from .faulio import FaulioLiveIE +from .faulio import ( + FaulioIE, + FaulioLiveIE, +) from .faz import FazIE from .fc2 import ( FC2IE, @@ -1180,15 +1179,7 @@ from .moview import MoviewPlayIE from .moviezine import MoviezineIE from .movingimage import MovingImageIE from .msn import MSNIE -from .mtv import ( - MTVDEIE, - MTVIE, - MTVItaliaIE, - MTVItaliaProgrammaIE, - MTVJapanIE, - MTVServicesEmbeddedIE, - MTVVideoIE, -) +from .mtv import MTVIE from .muenchentv import MuenchenTVIE from .murrtube import ( MurrtubeIE, @@ -1330,12 +1321,7 @@ from .nhk import ( NhkVodProgramIE, ) from .nhl import NHLIE -from .nick import ( - NickBrIE, - NickDeIE, - NickIE, - NickRuIE, -) +from .nick import NickIE from .niconico import ( NiconicoHistoryIE, NiconicoIE, @@ -1923,12 +1909,13 @@ from .soundgasm import ( SoundgasmProfileIE, ) from .southpark import ( + SouthParkComBrIE, + SouthParkCoUkIE, SouthParkDeIE, SouthParkDkIE, SouthParkEsIE, SouthParkIE, SouthParkLatIE, - SouthParkNlIE, ) from .sovietscloset import ( SovietsClosetIE, @@ -1939,10 +1926,6 @@ from .spankbang import ( SpankBangPlaylistIE, ) from .spiegel import SpiegelIE -from .spike import ( - BellatorIE, - ParamountNetworkIE, -) from .sport5 import Sport5IE from .sportbox import SportBoxIE from .sportdeutschland import SportDeutschlandIE @@ -1976,6 +1959,7 @@ from .startrek import StarTrekIE from .startv import StarTVIE from .steam import ( SteamCommunityBroadcastIE, + SteamCommunityIE, SteamIE, ) from .stitcher import ( @@ -2207,7 +2191,6 @@ from .tvc import ( from .tver import TVerIE from .tvigle import TvigleIE from .tviplayer import TVIPlayerIE -from .tvland import TVLandIE from .tvn24 import TVN24IE from .tvnoe import TVNoeIE from .tvopengr import ( diff --git a/yt-dlp/yt_dlp/extractor/atvat.py b/yt-dlp/yt_dlp/extractor/atvat.py index 37bb616952..b05eccf182 100644 --- a/yt-dlp/yt_dlp/extractor/atvat.py +++ b/yt-dlp/yt_dlp/extractor/atvat.py @@ -4,7 +4,7 @@ from .common import InfoExtractor from ..utils import ( ExtractorError, float_or_none, - jwt_encode_hs256, + jwt_encode, try_get, ) @@ -83,11 +83,10 @@ class ATVAtIE(InfoExtractor): 'nbf': int(not_before.timestamp()), 'exp': int(expire.timestamp()), } - jwt_token = jwt_encode_hs256(payload, self._ENCRYPTION_KEY, headers={'kid': self._ACCESS_ID}) videos = self._download_json( 'https://vas-v4.p7s1video.net/4.0/getsources', content_id, 'Downloading videos JSON', query={ - 'token': jwt_token.decode('utf-8'), + 'token': jwt_encode(payload, self._ENCRYPTION_KEY, headers={'kid': self._ACCESS_ID}), }) video_id, videos_data = next(iter(videos['data'].items())) diff --git a/yt-dlp/yt_dlp/extractor/bet.py b/yt-dlp/yt_dlp/extractor/bet.py index 3a8e743092..d8fc47f7b3 100644 --- a/yt-dlp/yt_dlp/extractor/bet.py +++ b/yt-dlp/yt_dlp/extractor/bet.py @@ -1,79 +1,47 @@ -from .mtv import MTVServicesInfoExtractor -from ..utils import unified_strdate +from .mtv import MTVServicesBaseIE -class BetIE(MTVServicesInfoExtractor): - _WORKING = False - _VALID_URL = r'https?://(?:www\.)?bet\.com/(?:[^/]+/)+(?P.+?)\.html' - _TESTS = [ - { - 'url': 'http://www.bet.com/news/politics/2014/12/08/in-bet-exclusive-obama-talks-race-and-racism.html', - 'info_dict': { - 'id': '07e96bd3-8850-3051-b856-271b457f0ab8', - 'display_id': 'in-bet-exclusive-obama-talks-race-and-racism', - 'ext': 'flv', - 'title': 'A Conversation With President Obama', - 'description': 'President Obama urges persistence in confronting racism and bias.', - 'duration': 1534, - 'upload_date': '20141208', - 'thumbnail': r're:(?i)^https?://.*\.jpg$', - 'subtitles': { - 'en': 'mincount:2', - }, - }, - 'params': { - # rtmp download - 'skip_download': True, - }, +class BetIE(MTVServicesBaseIE): + _VALID_URL = r'https?://(?:www\.)?bet\.com/(?:video-clips|episodes)/(?P[\da-z]{6})' + _TESTS = [{ + 'url': 'https://www.bet.com/video-clips/w9mk7v', + 'info_dict': { + 'id': '3022d121-d191-43fd-b5fb-b2c26f335497', + 'ext': 'mp4', + 'display_id': 'w9mk7v', + 'title': 'New Normal', + 'description': 'md5:d7898c124713b4646cecad9d16ff01f3', + 'duration': 30.08, + 'series': 'Tyler Perry\'s Sistas', + 'season': 'Season 0', + 'season_number': 0, + 'episode': 'Episode 0', + 'episode_number': 0, + 'timestamp': 1755269073, + 'upload_date': '20250815', }, - { - 'url': 'http://www.bet.com/video/news/national/2014/justice-for-ferguson-a-community-reacts.html', - 'info_dict': { - 'id': '9f516bf1-7543-39c4-8076-dd441b459ba9', - 'display_id': 'justice-for-ferguson-a-community-reacts', - 'ext': 'flv', - 'title': 'Justice for Ferguson: A Community Reacts', - 'description': 'A BET News special.', - 'duration': 1696, - 'upload_date': '20141125', - 'thumbnail': r're:(?i)^https?://.*\.jpg$', - 'subtitles': { - 'en': 'mincount:2', - }, - }, - 'params': { - # rtmp download - 'skip_download': True, - }, + 'params': {'skip_download': 'm3u8'}, + }, { + 'url': 'https://www.bet.com/episodes/nmce72/tyler-perry-s-sistas-heavy-is-the-crown-season-9-ep-5', + 'info_dict': { + 'id': '6427562b-3029-11f0-b405-16fff45bc035', + 'ext': 'mp4', + 'display_id': 'nmce72', + 'title': 'Heavy Is the Crown', + 'description': 'md5:1ed345d3157a50572d2464afcc7a652a', + 'channel': 'BET', + 'duration': 2550.0, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref', + 'series': 'Tyler Perry\'s Sistas', + 'season': 'Season 9', + 'season_number': 9, + 'episode': 'Episode 5', + 'episode_number': 5, + 'timestamp': 1755165600, + 'upload_date': '20250814', + 'release_timestamp': 1755129600, + 'release_date': '20250814', }, - ] - - _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/bet-mrss-player' - - def _get_feed_query(self, uri): - return { - 'uuid': uri, - } - - def _extract_mgid(self, webpage): - return self._search_regex(r'data-uri="([^"]+)', webpage, 'mgid') - - def _real_extract(self, url): - display_id = self._match_id(url) - - webpage = self._download_webpage(url, display_id) - mgid = self._extract_mgid(webpage) - videos_info = self._get_videos_info(mgid) - - info_dict = videos_info['entries'][0] - - upload_date = unified_strdate(self._html_search_meta('date', webpage)) - description = self._html_search_meta('description', webpage) - - info_dict.update({ - 'display_id': display_id, - 'description': description, - 'upload_date': upload_date, - }) - - return info_dict + 'params': {'skip_download': 'm3u8'}, + 'skip': 'Requires provider sign-in', + }] diff --git a/yt-dlp/yt_dlp/extractor/bilibili.py b/yt-dlp/yt_dlp/extractor/bilibili.py index d00ac63176..cd9bf6b165 100644 --- a/yt-dlp/yt_dlp/extractor/bilibili.py +++ b/yt-dlp/yt_dlp/extractor/bilibili.py @@ -304,7 +304,7 @@ class BilibiliBaseIE(InfoExtractor): class BiliBiliIE(BilibiliBaseIE): - _VALID_URL = r'https?://(?:www\.)?bilibili\.com/(?:video/|festival/[^/?#]+\?(?:[^#]*&)?bvid=)[aAbB][vV](?P[^/?#&]+)' + _VALID_URL = r'https?://(?:www\.)?bilibili\.com/(?:video/|festival/[^/?#]+\?(?:[^#]*&)?bvid=)(?P[aAbB][vV])(?P[^/?#&]+)' _TESTS = [{ 'url': 'https://www.bilibili.com/video/BV13x41117TL', @@ -563,7 +563,7 @@ class BiliBiliIE(BilibiliBaseIE): }, }], }, { - 'note': '301 redirect to bangumi link', + 'note': 'redirect from bvid to bangumi link via redirect_url', 'url': 'https://www.bilibili.com/video/BV1TE411f7f1', 'info_dict': { 'id': '288525', @@ -580,7 +580,27 @@ class BiliBiliIE(BilibiliBaseIE): 'duration': 1183.957, 'timestamp': 1571648124, 'upload_date': '20191021', - 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$', + 'thumbnail': r're:https?://.*\.(jpg|jpeg|png)$', + }, + }, { + 'note': 'redirect from aid to bangumi link via redirect_url', + 'url': 'https://www.bilibili.com/video/av114868162141203', + 'info_dict': { + 'id': '1933368', + 'title': 'PV 引爆变革的起点', + 'ext': 'mp4', + 'duration': 63.139, + 'series': '时光代理人', + 'series_id': '5183', + 'season': '第三季', + 'season_number': 4, + 'season_id': '105212', + 'episode': '引爆变革的起点', + 'episode_number': 1, + 'episode_id': '1933368', + 'timestamp': 1752849001, + 'upload_date': '20250718', + 'thumbnail': r're:https?://.*\.(jpg|jpeg|png)$', }, }, { 'note': 'video has subtitles, which requires login', @@ -636,7 +656,7 @@ class BiliBiliIE(BilibiliBaseIE): }] def _real_extract(self, url): - video_id = self._match_id(url) + video_id, prefix = self._match_valid_url(url).group('id', 'prefix') headers = self.geo_verification_headers() webpage, urlh = self._download_webpage_handle(url, video_id, headers=headers) if not self._match_valid_url(urlh.url): @@ -644,7 +664,24 @@ class BiliBiliIE(BilibiliBaseIE): headers['Referer'] = url - initial_state = self._search_json(r'window\.__INITIAL_STATE__\s*=', webpage, 'initial state', video_id) + initial_state = self._search_json(r'window\.__INITIAL_STATE__\s*=', webpage, 'initial state', video_id, default=None) + if not initial_state: + if self._search_json(r'\bwindow\._riskdata_\s*=', webpage, 'risk', video_id, default={}).get('v_voucher'): + raise ExtractorError('You have exceeded the rate limit. Try again later', expected=True) + query = {'platform': 'web'} + prefix = prefix.upper() + if prefix == 'BV': + query['bvid'] = prefix + video_id + elif prefix == 'AV': + query['aid'] = video_id + detail = self._download_json( + 'https://api.bilibili.com/x/web-interface/wbi/view/detail', video_id, + note='Downloading redirection URL', errnote='Failed to download redirection URL', + query=self._sign_wbi(query, video_id), headers=headers) + new_url = traverse_obj(detail, ('data', 'View', 'redirect_url', {url_or_none})) + if new_url and BiliBiliBangumiIE.suitable(new_url): + return self.url_result(new_url, BiliBiliBangumiIE) + raise ExtractorError('Unable to extract initial state') if traverse_obj(initial_state, ('error', 'trueCode')) == -403: self.raise_login_required() diff --git a/yt-dlp/yt_dlp/extractor/cmt.py b/yt-dlp/yt_dlp/extractor/cmt.py deleted file mode 100644 index 8e53b7fbf8..0000000000 --- a/yt-dlp/yt_dlp/extractor/cmt.py +++ /dev/null @@ -1,55 +0,0 @@ -from .mtv import MTVIE - -# TODO: Remove - Reason: Outdated Site - - -class CMTIE(MTVIE): # XXX: Do not subclass from concrete IE - _WORKING = False - IE_NAME = 'cmt.com' - _VALID_URL = r'https?://(?:www\.)?cmt\.com/(?:videos|shows|(?:full-)?episodes|video-clips)/(?P[^/]+)' - - _TESTS = [{ - 'url': 'http://www.cmt.com/videos/garth-brooks/989124/the-call-featuring-trisha-yearwood.jhtml#artist=30061', - 'md5': 'e6b7ef3c4c45bbfae88061799bbba6c2', - 'info_dict': { - 'id': '989124', - 'ext': 'mp4', - 'title': 'Garth Brooks - "The Call (featuring Trisha Yearwood)"', - 'description': 'Blame It All On My Roots', - }, - 'skip': 'Video not available', - }, { - 'url': 'http://www.cmt.com/videos/misc/1504699/still-the-king-ep-109-in-3-minutes.jhtml#id=1739908', - 'md5': 'e61a801ca4a183a466c08bd98dccbb1c', - 'info_dict': { - 'id': '1504699', - 'ext': 'mp4', - 'title': 'Still The King Ep. 109 in 3 Minutes', - 'description': 'Relive or catch up with Still The King by watching this recap of season 1, episode 9.', - 'timestamp': 1469421000.0, - 'upload_date': '20160725', - }, - }, { - 'url': 'http://www.cmt.com/shows/party-down-south/party-down-south-ep-407-gone-girl/1738172/playlist/#id=1738172', - 'only_matching': True, - }, { - 'url': 'http://www.cmt.com/full-episodes/537qb3/nashville-the-wayfaring-stranger-season-5-ep-501', - 'only_matching': True, - }, { - 'url': 'http://www.cmt.com/video-clips/t9e4ci/nashville-juliette-in-2-minutes', - 'only_matching': True, - }] - - def _extract_mgid(self, webpage, url): - mgid = self._search_regex( - r'MTVN\.VIDEO\.contentUri\s*=\s*([\'"])(?P.+?)\1', - webpage, 'mgid', group='mgid', default=None) - if not mgid: - mgid = self._extract_triforce_mgid(webpage) - return mgid - - def _real_extract(self, url): - video_id = self._match_id(url) - webpage = self._download_webpage(url, video_id) - mgid = self._extract_mgid(webpage, url) - return self.url_result(f'http://media.mtvnservices.com/embed/{mgid}') diff --git a/yt-dlp/yt_dlp/extractor/comedycentral.py b/yt-dlp/yt_dlp/extractor/comedycentral.py index 27d295bb38..b9f0d66d1c 100644 --- a/yt-dlp/yt_dlp/extractor/comedycentral.py +++ b/yt-dlp/yt_dlp/extractor/comedycentral.py @@ -1,55 +1,27 @@ -from .mtv import MTVServicesInfoExtractor +from .mtv import MTVServicesBaseIE -class ComedyCentralIE(MTVServicesInfoExtractor): - _VALID_URL = r'https?://(?:www\.)?cc\.com/(?:episodes|video(?:-clips)?|collection-playlist|movies)/(?P[0-9a-z]{6})' - _FEED_URL = 'http://comedycentral.com/feeds/mrss/' - +class ComedyCentralIE(MTVServicesBaseIE): + _VALID_URL = r'https?://(?:www\.)?cc\.com/video-clips/(?P[\da-z]{6})' _TESTS = [{ - 'url': 'http://www.cc.com/video-clips/5ke9v2/the-daily-show-with-trevor-noah-doc-rivers-and-steve-ballmer---the-nba-player-strike', - 'md5': 'b8acb347177c680ff18a292aa2166f80', + 'url': 'https://www.cc.com/video-clips/wl12cx', 'info_dict': { - 'id': '89ccc86e-1b02-4f83-b0c9-1d9592ecd025', + 'id': 'dec6953e-80c8-43b3-96cd-05e9230e704d', 'ext': 'mp4', - 'title': 'The Daily Show with Trevor Noah|August 28, 2020|25|25149|Doc Rivers and Steve Ballmer - The NBA Player Strike', - 'description': 'md5:5334307c433892b85f4f5e5ac9ef7498', - 'timestamp': 1598670000, - 'upload_date': '20200829', + 'display_id': 'wl12cx', + 'title': 'Alison Brie and Dave Franco -"Together"- Extended Interview', + 'description': 'md5:ec68e38d3282f863de9cde0ce5cd231c', + 'duration': 516.76, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:', + 'series': 'The Daily Show', + 'season': 'Season 30', + 'season_number': 30, + 'episode': 'Episode 0', + 'episode_number': 0, + 'timestamp': 1753973314, + 'upload_date': '20250731', + 'release_timestamp': 1753977914, + 'release_date': '20250731', }, - }, { - 'url': 'http://www.cc.com/episodes/pnzzci/drawn-together--american-idol--parody-clip-show-season-3-ep-314', - 'only_matching': True, - }, { - 'url': 'https://www.cc.com/video/k3sdvm/the-daily-show-with-jon-stewart-exclusive-the-fourth-estate', - 'only_matching': True, - }, { - 'url': 'https://www.cc.com/collection-playlist/cosnej/stand-up-specials/t6vtjb', - 'only_matching': True, - }, { - 'url': 'https://www.cc.com/movies/tkp406/a-cluesterfuenke-christmas', - 'only_matching': True, + 'params': {'skip_download': 'm3u8'}, }] - - -class ComedyCentralTVIE(MTVServicesInfoExtractor): - _VALID_URL = r'https?://(?:www\.)?comedycentral\.tv/folgen/(?P[0-9a-z]{6})' - _TESTS = [{ - 'url': 'https://www.comedycentral.tv/folgen/pxdpec/josh-investigates-klimawandel-staffel-1-ep-1', - 'info_dict': { - 'id': '15907dc3-ec3c-11e8-a442-0e40cf2fc285', - 'ext': 'mp4', - 'title': 'Josh Investigates', - 'description': 'Steht uns das Ende der Welt bevor?', - }, - }] - _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed' - _GEO_COUNTRIES = ['DE'] - - def _get_feed_query(self, uri): - return { - 'accountOverride': 'intl.mtvi.com', - 'arcEp': 'web.cc.tv', - 'ep': 'b9032c3a', - 'imageEp': 'web.cc.tv', - 'mgid': uri, - } diff --git a/yt-dlp/yt_dlp/extractor/common.py b/yt-dlp/yt_dlp/extractor/common.py index fb15a8e8ce..573a9c5a0a 100644 --- a/yt-dlp/yt_dlp/extractor/common.py +++ b/yt-dlp/yt_dlp/extractor/common.py @@ -263,6 +263,7 @@ class InfoExtractor: * a string in the format of CLIENT[:OS] * a list or a tuple of CLIENT[:OS] strings or ImpersonateTarget instances * a boolean value; True means any impersonate target is sufficient + * available_at Unix timestamp of when a format will be available to download * downloader_options A dictionary of downloader options (For internal use only) * http_chunk_size Chunk size for HTTP downloads diff --git a/yt-dlp/yt_dlp/extractor/faulio.py b/yt-dlp/yt_dlp/extractor/faulio.py index a5d5c750b4..9c6c13e0e0 100644 --- a/yt-dlp/yt_dlp/extractor/faulio.py +++ b/yt-dlp/yt_dlp/extractor/faulio.py @@ -2,18 +2,152 @@ import re import urllib.parse from .common import InfoExtractor -from ..utils import js_to_json, url_or_none +from ..utils import int_or_none, js_to_json, url_or_none from ..utils.traversal import traverse_obj -class FaulioLiveIE(InfoExtractor): +class FaulioBaseIE(InfoExtractor): _DOMAINS = ( 'aloula.sba.sa', 'bahry.com', 'maraya.sba.net.ae', 'sat7plus.org', ) - _VALID_URL = fr'https?://(?:{"|".join(map(re.escape, _DOMAINS))})/(?:(?:en|ar|fa)/)?live/(?P[a-zA-Z0-9-]+)' + _LANGUAGES = ('ar', 'en', 'fa') + _BASE_URL_RE = fr'https?://(?:{"|".join(map(re.escape, _DOMAINS))})/(?:(?:{"|".join(_LANGUAGES)})/)?' + + def _get_headers(self, url): + parsed_url = urllib.parse.urlparse(url) + return { + 'Referer': url, + 'Origin': f'{parsed_url.scheme}://{parsed_url.hostname}', + } + + def _get_api_base(self, url, video_id): + webpage = self._download_webpage(url, video_id) + config_data = self._search_json( + r'window\.__NUXT__\.config=', webpage, 'config', video_id, transform_source=js_to_json) + return config_data['public']['TRANSLATIONS_API_URL'] + + +class FaulioIE(FaulioBaseIE): + _VALID_URL = fr'{FaulioBaseIE._BASE_URL_RE}(?:episode|media)/(?P[a-zA-Z0-9-]+)' + _TESTS = [{ + 'url': 'https://aloula.sba.sa/en/episode/29102', + 'info_dict': { + 'id': 'aloula.faulio.com_29102', + 'ext': 'mp4', + 'display_id': 'هذا-مكانك-03-004-v-29102', + 'title': 'الحلقة 4', + 'episode': 'الحلقة 4', + 'description': '', + 'series': 'هذا مكانك', + 'season': 'Season 3', + 'season_number': 3, + 'episode_number': 4, + 'thumbnail': r're:^https?://.*\.jpg$', + 'duration': 4855, + 'age_limit': 3, + }, + }, { + 'url': 'https://bahry.com/en/media/1191', + 'info_dict': { + 'id': 'bahry.faulio.com_1191', + 'ext': 'mp4', + 'display_id': 'Episode-4-1191', + 'title': 'Episode 4', + 'episode': 'Episode 4', + 'description': '', + 'series': 'Wild Water', + 'season': 'Season 1', + 'season_number': 1, + 'episode_number': 4, + 'thumbnail': r're:^https?://.*\.jpg$', + 'duration': 1653, + 'age_limit': 0, + }, + }, { + 'url': 'https://maraya.sba.net.ae/episode/127735', + 'info_dict': { + 'id': 'maraya.faulio.com_127735', + 'ext': 'mp4', + 'display_id': 'عبدالله-الهاجري---عبدالرحمن-المطروشي-127735', + 'title': 'عبدالله الهاجري - عبدالرحمن المطروشي', + 'episode': 'عبدالله الهاجري - عبدالرحمن المطروشي', + 'description': 'md5:53de01face66d3d6303221e5a49388a0', + 'series': 'أبناؤنا في الخارج', + 'season': 'Season 3', + 'season_number': 3, + 'episode_number': 7, + 'thumbnail': r're:^https?://.*\.jpg$', + 'duration': 1316, + 'age_limit': 0, + }, + }, { + 'url': 'https://sat7plus.org/episode/18165', + 'info_dict': { + 'id': 'sat7.faulio.com_18165', + 'ext': 'mp4', + 'display_id': 'ep-13-ADHD-18165', + 'title': 'ADHD and creativity', + 'episode': 'ADHD and creativity', + 'description': '', + 'series': 'ADHD Podcast', + 'season': 'Season 1', + 'season_number': 1, + 'episode_number': 13, + 'thumbnail': r're:^https?://.*\.jpg$', + 'duration': 2492, + 'age_limit': 0, + }, + }, { + 'url': 'https://aloula.sba.sa/en/episode/0', + 'only_matching': True, + }] + + def _real_extract(self, url): + video_id = self._match_id(url) + api_base = self._get_api_base(url, video_id) + video_info = self._download_json(f'{api_base}/video/{video_id}', video_id, fatal=False) + player_info = self._download_json(f'{api_base}/video/{video_id}/player', video_id) + + headers = self._get_headers(url) + formats = [] + subtitles = {} + if hls_url := traverse_obj(player_info, ('settings', 'protocols', 'hls', {url_or_none})): + fmts, subs = self._extract_m3u8_formats_and_subtitles( + hls_url, video_id, 'mp4', m3u8_id='hls', fatal=False, headers=headers) + formats.extend(fmts) + self._merge_subtitles(subs, target=subtitles) + + if mpd_url := traverse_obj(player_info, ('settings', 'protocols', 'dash', {url_or_none})): + fmts, subs = self._extract_mpd_formats_and_subtitles( + mpd_url, video_id, mpd_id='dash', fatal=False, headers=headers) + formats.extend(fmts) + self._merge_subtitles(subs, target=subtitles) + + return { + 'id': f'{urllib.parse.urlparse(api_base).hostname}_{video_id}', + **traverse_obj(traverse_obj(video_info, ('blocks', 0)), { + 'display_id': ('slug', {str}), + 'title': ('title', {str}), + 'episode': ('title', {str}), + 'description': ('description', {str}), + 'series': ('program_title', {str}), + 'season_number': ('season_number', {int_or_none}), + 'episode_number': ('episode', {int_or_none}), + 'thumbnail': ('image', {url_or_none}), + 'duration': ('duration', 'total', {int_or_none}), + 'age_limit': ('age_rating', {int_or_none}), + }), + 'formats': formats, + 'subtitles': subtitles, + 'http_headers': headers, + } + + +class FaulioLiveIE(FaulioBaseIE): + _VALID_URL = fr'{FaulioBaseIE._BASE_URL_RE}live/(?P[a-zA-Z0-9-]+)' _TESTS = [{ 'url': 'https://aloula.sba.sa/live/saudiatv', 'info_dict': { @@ -69,27 +203,24 @@ class FaulioLiveIE(InfoExtractor): def _real_extract(self, url): video_id = self._match_id(url) - webpage = self._download_webpage(url, video_id) - - config_data = self._search_json( - r'window\.__NUXT__\.config=', webpage, 'config', video_id, transform_source=js_to_json) - api_base = config_data['public']['TRANSLATIONS_API_URL'] + api_base = self._get_api_base(url, video_id) channel = traverse_obj( self._download_json(f'{api_base}/channels', video_id), (lambda k, v: v['url'] == video_id, any)) + headers = self._get_headers(url) formats = [] subtitles = {} if hls_url := traverse_obj(channel, ('streams', 'hls', {url_or_none})): fmts, subs = self._extract_m3u8_formats_and_subtitles( - hls_url, video_id, 'mp4', m3u8_id='hls', live=True, fatal=False) + hls_url, video_id, 'mp4', m3u8_id='hls', live=True, fatal=False, headers=headers) formats.extend(fmts) self._merge_subtitles(subs, target=subtitles) if mpd_url := traverse_obj(channel, ('streams', 'mpd', {url_or_none})): fmts, subs = self._extract_mpd_formats_and_subtitles( - mpd_url, video_id, mpd_id='dash', fatal=False) + mpd_url, video_id, mpd_id='dash', fatal=False, headers=headers) formats.extend(fmts) self._merge_subtitles(subs, target=subtitles) @@ -101,5 +232,6 @@ class FaulioLiveIE(InfoExtractor): }), 'formats': formats, 'subtitles': subtitles, + 'http_headers': headers, 'is_live': True, } diff --git a/yt-dlp/yt_dlp/extractor/francetv.py b/yt-dlp/yt_dlp/extractor/francetv.py index 54c2c53aca..873b4eb4d5 100644 --- a/yt-dlp/yt_dlp/extractor/francetv.py +++ b/yt-dlp/yt_dlp/extractor/francetv.py @@ -363,13 +363,7 @@ class FranceTVSiteIE(FranceTVBaseInfoExtractor): display_id = self._match_id(url) webpage = self._download_webpage(url, display_id) nextjs_data = self._search_nextjs_v13_data(webpage, display_id) - - if get_first(nextjs_data, ('isLive', {bool})): - # For livestreams we need the id of the stream instead of the currently airing episode id - video_id = get_first(nextjs_data, ('options', 'id', {str})) - else: - video_id = get_first(nextjs_data, ('video', ('playerReplayId', 'siId'), {str})) - + video_id = get_first(nextjs_data, ('options', 'id', {str})) if not video_id: raise ExtractorError('Unable to extract video ID') diff --git a/yt-dlp/yt_dlp/extractor/medialaan.py b/yt-dlp/yt_dlp/extractor/medialaan.py index c80b6dff12..5aa5d4dfb0 100644 --- a/yt-dlp/yt_dlp/extractor/medialaan.py +++ b/yt-dlp/yt_dlp/extractor/medialaan.py @@ -1,15 +1,73 @@ -import re - from .common import InfoExtractor from ..utils import ( + clean_html, + determine_ext, extract_attributes, int_or_none, - mimetype2ext, - parse_iso8601, + parse_resolution, + str_or_none, + url_or_none, ) +from ..utils.traversal import find_elements, traverse_obj -class MedialaanIE(InfoExtractor): +class MedialaanBaseIE(InfoExtractor): + def _extract_from_mychannels_api(self, mychannels_id): + webpage = self._download_webpage( + f'https://mychannels.video/embed/{mychannels_id}', mychannels_id) + brand_config = self._search_json( + r'window\.mychannels\.brand_config\s*=', webpage, 'brand config', mychannels_id) + response = self._download_json( + f'https://api.mychannels.world/v1/embed/video/{mychannels_id}', + mychannels_id, headers={'X-Mychannels-Brand': brand_config['brand']}) + + formats = [] + for stream in traverse_obj(response, ( + 'streams', lambda _, v: url_or_none(v['url']), + )): + source_url = stream['url'] + ext = determine_ext(source_url) + if ext == 'm3u8': + formats.extend(self._extract_m3u8_formats( + source_url, mychannels_id, 'mp4', m3u8_id='hls', fatal=False)) + else: + format_id = traverse_obj(stream, ('quality', {str})) + formats.append({ + 'ext': ext, + 'format_id': format_id, + 'url': source_url, + **parse_resolution(format_id), + }) + + return { + 'id': mychannels_id, + 'formats': formats, + **traverse_obj(response, { + 'title': ('title', {clean_html}), + 'description': ('description', {clean_html}, filter), + 'duration': ('durationMs', {int_or_none(scale=1000)}, {lambda x: x if x >= 0 else None}), + 'genres': ('genre', 'title', {str}, filter, all, filter), + 'is_live': ('live', {bool}), + 'release_timestamp': ('publicationTimestampMs', {int_or_none(scale=1000)}), + 'tags': ('tags', ..., 'title', {str}, filter, all, filter), + 'thumbnail': ('image', 'baseUrl', {url_or_none}), + }), + **traverse_obj(response, ('channel', { + 'channel': ('title', {clean_html}), + 'channel_id': ('id', {str_or_none}), + })), + **traverse_obj(response, ('organisation', { + 'uploader': ('title', {clean_html}), + 'uploader_id': ('id', {str_or_none}), + })), + **traverse_obj(response, ('show', { + 'series': ('title', {clean_html}), + 'series_id': ('id', {str_or_none}), + })), + } + + +class MedialaanIE(MedialaanBaseIE): _VALID_URL = r'''(?x) https?:// (?: @@ -32,7 +90,7 @@ class MedialaanIE(InfoExtractor): tubantia| volkskrant )\.nl - )/video/(?:[^/]+/)*[^/?&#]+~p + )/videos?/(?:[^/?#]+/)*[^/?&#]+(?:-|~p) ) (?P\d+) ''' @@ -42,19 +100,83 @@ class MedialaanIE(InfoExtractor): 'id': '193993', 'ext': 'mp4', 'title': 'De terugkeer van Ally de Aap en wie vertrekt er nog bij NAC?', - 'thumbnail': r're:https?://images\.mychannels\.video/imgix/.+', - 'timestamp': 1611663540, - 'upload_date': '20210126', + 'description': 'In een nieuwe Gegenpressing video bespreken Yadran Blanco en Dennis Kas het nieuws omrent NAC.', 'duration': 238, - }, - 'params': { - 'skip_download': True, + 'channel': 'BN DeStem', + 'channel_id': '418', + 'genres': ['Sports'], + 'release_date': '20210126', + 'release_timestamp': 1611663540, + 'series': 'Korte Reportage', + 'series_id': '972', + 'tags': 'count:2', + 'thumbnail': r're:https?://images\.mychannels\.video/imgix/.+\.(?:jpe?g|png)', + 'uploader': 'BN De Stem', + 'uploader_id': '26', }, }, { 'url': 'https://www.gelderlander.nl/video/kanalen/degelderlander~c320/series/snel-nieuws~s984/noodbevel-in-doetinchem-politie-stuurt-mensen-centrum-uit~p194093', - 'only_matching': True, + 'info_dict': { + 'id': '194093', + 'ext': 'mp4', + 'title': 'Noodbevel in Doetinchem: politie stuurt mensen centrum uit', + 'description': 'md5:77e85b2cb26cfff9dc1fe2b1db524001', + 'duration': 44, + 'channel': 'De Gelderlander', + 'channel_id': '320', + 'genres': ['News'], + 'release_date': '20210126', + 'release_timestamp': 1611690600, + 'series': 'Snel Nieuws', + 'series_id': '984', + 'tags': 'count:1', + 'thumbnail': r're:https?://images\.mychannels\.video/imgix/.+\.(?:jpe?g|png)', + 'uploader': 'De Gelderlander', + 'uploader_id': '25', + }, }, { - 'url': 'https://embed.mychannels.video/sdk/production/193993?options=TFTFF_default', + 'url': 'https://www.7sur7.be/videos/production/lla-tendance-tiktok-qui-enflamme-lespagne-707650', + 'info_dict': { + 'id': '707650', + 'ext': 'mp4', + 'title': 'La tendance TikTok qui enflamme l’Espagne', + 'description': 'md5:c7ec4cb733190f227fc8935899f533b5', + 'duration': 70, + 'channel': 'Lifestyle', + 'channel_id': '770', + 'genres': ['Beauty & Lifestyle'], + 'release_date': '20240906', + 'release_timestamp': 1725617330, + 'series': 'Lifestyle', + 'series_id': '1848', + 'tags': 'count:1', + 'thumbnail': r're:https?://images\.mychannels\.video/imgix/.+\.(?:jpe?g|png)', + 'uploader': '7sur7', + 'uploader_id': '67', + }, + }, { + 'url': 'https://mychannels.video/embed/313117', + 'info_dict': { + 'id': '313117', + 'ext': 'mp4', + 'title': str, + 'description': 'md5:255e2e52f6fe8a57103d06def438f016', + 'channel': 'AD', + 'channel_id': '238', + 'genres': ['News'], + 'live_status': 'is_live', + 'release_date': '20241225', + 'release_timestamp': 1735169425, + 'series': 'Nieuws Update', + 'series_id': '3337', + 'tags': 'count:1', + 'thumbnail': r're:https?://images\.mychannels\.video/imgix/.+\.(?:jpe?g|png)', + 'uploader': 'AD', + 'uploader_id': '1', + }, + 'params': {'skip_download': 'Livestream'}, + }, { + 'url': 'https://embed.mychannels.video/sdk/production/193993', 'only_matching': True, }, { 'url': 'https://embed.mychannels.video/script/production/193993', @@ -62,9 +184,6 @@ class MedialaanIE(InfoExtractor): }, { 'url': 'https://embed.mychannels.video/production/193993', 'only_matching': True, - }, { - 'url': 'https://mychannels.video/embed/193993', - 'only_matching': True, }, { 'url': 'https://embed.mychannels.video/embed/193993', 'only_matching': True, @@ -75,51 +194,31 @@ class MedialaanIE(InfoExtractor): 'id': '1576607', 'ext': 'mp4', 'title': 'Tom Waes blaastest', + 'channel': 'De Morgen', + 'channel_id': '352', + 'description': 'Tom Waes werkt mee aan een alcoholcampagne op Werchter', 'duration': 62, + 'genres': ['News'], + 'release_date': '20250705', + 'release_timestamp': 1751730795, + 'series': 'Nieuwsvideo\'s', + 'series_id': '1683', + 'tags': 'count:1', 'thumbnail': r're:https?://video-images\.persgroep\.be/aws_generated.+\.jpg', - 'timestamp': 1751730795, - 'upload_date': '20250705', + 'uploader': 'De Morgen', + 'uploader_id': '17', }, 'params': {'extractor_args': {'generic': {'impersonate': ['chrome']}}}, }] @classmethod def _extract_embed_urls(cls, url, webpage): - entries = [] - for element in re.findall(r'(]+data-mychannels-type="video"[^>]*>)', webpage): - mychannels_id = extract_attributes(element).get('data-mychannels-id') - if mychannels_id: - entries.append('https://mychannels.video/embed/' + mychannels_id) - return entries + yield from traverse_obj(webpage, ( + {find_elements(tag='div', attr='data-mychannels-type', value='video', html=True)}, + ..., {extract_attributes}, 'data-mychannels-id', {str}, filter, + {lambda x: f'https://mychannels.video/embed/{x}'})) def _real_extract(self, url): - production_id = self._match_id(url) - production = self._download_json( - 'https://embed.mychannels.video/sdk/production/' + production_id, - production_id, query={'options': 'UUUU_default'})['productions'][0] - title = production['title'] + mychannels_id = self._match_id(url) - formats = [] - for source in (production.get('sources') or []): - src = source.get('src') - if not src: - continue - ext = mimetype2ext(source.get('type')) - if ext == 'm3u8': - formats.extend(self._extract_m3u8_formats( - src, production_id, 'mp4', 'm3u8_native', - m3u8_id='hls', fatal=False)) - else: - formats.append({ - 'ext': ext, - 'url': src, - }) - - return { - 'id': production_id, - 'title': title, - 'formats': formats, - 'thumbnail': production.get('posterUrl'), - 'timestamp': parse_iso8601(production.get('publicationDate'), ' '), - 'duration': int_or_none(production.get('duration')) or None, - } + return self._extract_from_mychannels_api(mychannels_id) diff --git a/yt-dlp/yt_dlp/extractor/mtv.py b/yt-dlp/yt_dlp/extractor/mtv.py index 34e015dfcd..59319d4b53 100644 --- a/yt-dlp/yt_dlp/extractor/mtv.py +++ b/yt-dlp/yt_dlp/extractor/mtv.py @@ -1,652 +1,268 @@ -import re -import xml.etree.ElementTree +import base64 +import json +import time +import urllib.parse from .common import InfoExtractor -from ..networking import HEADRequest, Request +from ..networking.exceptions import HTTPError from ..utils import ( ExtractorError, - RegexNotFoundError, - find_xpath_attr, - fix_xml_ampersands, float_or_none, int_or_none, - join_nonempty, - strip_or_none, - timeconvert, - try_get, - unescapeHTML, + js_to_json, + jwt_decode_hs256, + parse_iso8601, + parse_qs, + update_url, update_url_query, - url_basename, - xpath_text, + url_or_none, ) +from ..utils.traversal import require, traverse_obj -def _media_xml_tag(tag): - return f'{{http://search.yahoo.com/mrss/}}{tag}' - - -class MTVServicesInfoExtractor(InfoExtractor): - _MOBILE_TEMPLATE = None - _LANG = None +class MTVServicesBaseIE(InfoExtractor): + _GEO_BYPASS = False + _GEO_COUNTRIES = ['US'] + _CACHE_SECTION = 'mtvservices' + _ACCESS_TOKEN_KEY = 'access' + _REFRESH_TOKEN_KEY = 'refresh' + _MEDIA_TOKEN_KEY = 'media' + _token_cache = {} @staticmethod - def _id_from_uri(uri): - return uri.split(':')[-1] + def _jwt_is_expired(token): + return jwt_decode_hs256(token)['exp'] - time.time() < 120 @staticmethod - def _remove_template_parameter(url): - # Remove the templates, like &device={device} - return re.sub(r'&[^=]*?={.*?}(?=(&|$))', '', url) + def _get_auth_suite_data(config): + return traverse_obj(config, { + 'clientId': ('clientId', {str}), + 'countryCode': ('countryCode', {str}), + }) - def _get_feed_url(self, uri, url=None): - return self._FEED_URL - - def _get_thumbnail_url(self, uri, itemdoc): - search_path = '{}/{}'.format(_media_xml_tag('group'), _media_xml_tag('thumbnail')) - thumb_node = itemdoc.find(search_path) - if thumb_node is None: - return None - return thumb_node.get('url') or thumb_node.text or None - - def _extract_mobile_video_formats(self, mtvn_id): - webpage_url = self._MOBILE_TEMPLATE % mtvn_id - req = Request(webpage_url) - # Otherwise we get a webpage that would execute some javascript - req.headers['User-Agent'] = 'curl/7' - webpage = self._download_webpage(req, mtvn_id, - 'Downloading mobile page') - metrics_url = unescapeHTML(self._search_regex(r'.+?_lc_promo.*?)\1', webpage, - 'data zone', default=data_zone, group='zone') + media_token = tokens.get(self._MEDIA_TOKEN_KEY) + if media_token and not self._jwt_is_expired(media_token): + return media_token - feed_url = try_get( - triforce_feed, lambda x: x['manifest']['zones'][data_zone]['feed'], - str) - if not feed_url: - return + access_token = self._get_fresh_access_token(config, display_id) + if not jwt_decode_hs256(access_token).get('accessMethods'): + # MTVServices uses a custom AdobePass oauth flow which is incompatible with AdobePassIE + mso_id = self.get_param('ap_mso') + if not mso_id: + raise ExtractorError( + 'This video is only available for users of participating TV providers. ' + 'Use --ap-mso to specify Adobe Pass Multiple-system operator Identifier and pass ' + 'cookies from a browser session where you are signed-in to your provider.', expected=True) - feed = self._download_json(feed_url, video_id, fatal=False) - if not feed: - return + auth_suite_data = json.dumps( + self._get_auth_suite_data(config), separators=(',', ':')).encode() + callback_url = update_url_query(config['callbackURL'], { + 'authSuiteData': urllib.parse.quote(base64.b64encode(auth_suite_data).decode()), + 'mvpdCode': mso_id, + }) + auth_url = self._call_auth_api( + f'mvpd/{mso_id}/login', config, display_id, + 'Retrieving provider authentication URL', + query={'callbackUrl': callback_url}, + headers={'Authorization': f'Bearer {access_token}'})['authenticationUrl'] + res = self._download_webpage_handle(auth_url, display_id, 'Downloading provider auth page') + # XXX: The following "provider-specific code" likely only works if mso_id == Comcast_SSO + # BEGIN provider-specific code + redirect_url = self._search_json( + r'initInterstitialRedirect\(', res[0], 'redirect JSON', + display_id, transform_source=js_to_json)['continue'] + urlh = self._request_webpage(redirect_url, display_id, 'Requesting provider redirect page') + authorization_code = parse_qs(urlh.url)['authorizationCode'][-1] + # END provider-specific code + self._call_auth_api( + f'access/mvpd/{mso_id}', config, display_id, + 'Submitting authorization code to MTVNServices', + query={'authorizationCode': authorization_code}, data=b'', + headers={'Authorization': f'Bearer {access_token}'}) + access_token = self._get_fresh_access_token(config, display_id, force_refresh=True) - return try_get(feed, lambda x: x['result']['data']['id'], str) + tokens[self._MEDIA_TOKEN_KEY] = self._call_auth_api( + 'mediaToken', config, display_id, 'Fetching media token', data={ + 'content': {('id' if k == 'videoId' else k): v for k, v in video_config.items()}, + 'resourceId': resource_id, + }, headers={'Authorization': f'Bearer {access_token}'})['mediaToken'] - @staticmethod - def _extract_child_with_type(parent, t): - for c in parent['children']: - if c.get('type') == t: - return c + self.cache.store(self._CACHE_SECTION, resource_id, tokens) + return tokens[self._MEDIA_TOKEN_KEY] + + def _real_extract(self, url): + display_id = self._match_id(url) - def _extract_mgid(self, webpage): try: - # the url can be http://media.mtvnservices.com/fb/{mgid}.swf - # or http://media.mtvnservices.com/{mgid} - og_url = self._og_search_video_url(webpage) - mgid = url_basename(og_url) - if mgid.endswith('.swf'): - mgid = mgid[:-4] - except RegexNotFoundError: - mgid = None + data = self._download_json( + update_url(url, query=None), display_id, + query={'json': 'true'}) + except ExtractorError as e: + if isinstance(e.cause, HTTPError) and e.cause.status == 404 and not self.suitable(e.cause.response.url): + self.raise_geo_restricted(countries=self._GEO_COUNTRIES) + raise - if mgid is None or ':' not in mgid: - mgid = self._search_regex( - [r'data-mgid="(.*?)"', r'swfobject\.embedSWF\(".*?(mgid:.*?)"'], - webpage, 'mgid', default=None) + flex_wrapper = traverse_obj(data, ( + 'children', lambda _, v: v['type'] == 'MainContainer', + (None, ('children', lambda _, v: v['type'] == 'AviaWrapper')), + 'children', lambda _, v: v['type'] == 'FlexWrapper', {dict}, any)) + video_detail = traverse_obj(flex_wrapper, ( + (None, ('children', lambda _, v: v['type'] == 'AuthSuiteWrapper')), + 'children', lambda _, v: v['type'] == 'Player', + 'props', 'videoDetail', {dict}, any)) + if not video_detail: + video_detail = traverse_obj(data, ( + 'children', ..., ('handleTVEAuthRedirection', None), + 'videoDetail', {dict}, any, {require('video detail')})) - if not mgid: - sm4_embed = self._html_search_meta( - 'sm4:video:embed', webpage, 'sm4 embed', default='') - mgid = self._search_regex( - r'embed/(mgid:.+?)["\'&?/]', sm4_embed, 'mgid', default=None) + mgid = video_detail['mgid'] + video_id = mgid.rpartition(':')[2] + service_url = traverse_obj(video_detail, ('videoServiceUrl', {url_or_none}, {update_url(query=None)})) + if not service_url: + raise ExtractorError('This content is no longer available', expected=True) - if not mgid: - mgid = self._extract_triforce_mgid(webpage) + headers = {} + if video_detail.get('authRequired'): + # The vast majority of provider-locked content has been moved to Paramount+ + # BetIE is the only extractor that is currently known to reach this code path + video_config = traverse_obj(flex_wrapper, ( + 'children', lambda _, v: v['type'] == 'AuthSuiteWrapper', + 'props', 'videoConfig', {dict}, any, {require('video config')})) + config = traverse_obj(data, ( + 'props', 'authSuiteConfig', {dict}, {require('auth suite config')})) + headers['X-VIA-TVE-MEDIATOKEN'] = self._get_media_token(video_config, config, display_id) - if not mgid: - data = self._parse_json(self._search_regex( - r'__DATA__\s*=\s*({.+?});', webpage, 'data'), None) - main_container = self._extract_child_with_type(data, 'MainContainer') - ab_testing = self._extract_child_with_type(main_container, 'ABTesting') - video_player = self._extract_child_with_type(ab_testing or main_container, 'VideoPlayer') - if video_player: - mgid = try_get(video_player, lambda x: x['props']['media']['video']['config']['uri']) - else: - flex_wrapper = self._extract_child_with_type(ab_testing or main_container, 'FlexWrapper') - auth_suite_wrapper = self._extract_child_with_type(flex_wrapper, 'AuthSuiteWrapper') - player = self._extract_child_with_type(auth_suite_wrapper or flex_wrapper, 'Player') - if player: - mgid = try_get(player, lambda x: x['props']['videoDetail']['mgid']) + stream_info = self._download_json( + service_url, video_id, 'Downloading API JSON', 'Unable to download API JSON', + query={'clientPlatform': 'desktop'}, headers=headers)['stitchedstream'] - if not mgid: - raise ExtractorError('Could not extract mgid') + manifest_type = stream_info['manifesttype'] + if manifest_type == 'hls': + formats, subtitles = self._extract_m3u8_formats_and_subtitles( + stream_info['source'], video_id, 'mp4', m3u8_id=manifest_type) + elif manifest_type == 'dash': + formats, subtitles = self._extract_mpd_formats_and_subtitles( + stream_info['source'], video_id, mpd_id=manifest_type) + else: + self.raise_no_formats(f'Unsupported manifest type "{manifest_type}"') + formats, subtitles = [], {} - return mgid - - def _real_extract(self, url): - title = url_basename(url) - webpage = self._download_webpage(url, title) - mgid = self._extract_mgid(webpage) - return self._get_videos_info(mgid, url=url) + return { + **traverse_obj(video_detail, { + 'title': ('title', {str}), + 'channel': ('channel', 'name', {str}), + 'thumbnails': ('images', ..., {'url': ('url', {url_or_none})}), + 'description': (('fullDescription', 'description'), {str}, any), + 'series': ('parentEntity', 'title', {str}), + 'season_number': ('seasonNumber', {int_or_none}), + 'episode_number': ('episodeAiringOrder', {int_or_none}), + 'duration': ('duration', 'milliseconds', {float_or_none(scale=1000)}), + 'timestamp': (( + ('originalPublishDate', {parse_iso8601}), + ('publishDate', 'timestamp', {int_or_none})), any), + 'release_timestamp': (( + ('originalAirDate', {parse_iso8601}), + ('airDate', 'timestamp', {int_or_none})), any), + }), + 'id': video_id, + 'display_id': display_id, + 'formats': formats, + 'subtitles': subtitles, + } -class MTVServicesEmbeddedIE(MTVServicesInfoExtractor): - IE_NAME = 'mtvservices:embedded' - _VALID_URL = r'https?://media\.mtvnservices\.com/embed/(?P.+?)(\?|/|$)' - _EMBED_REGEX = [r']+?src=(["\'])(?P(?:https?:)?//media\.mtvnservices\.com/embed/.+?)\1'] - - _TEST = { - # From http://www.thewrap.com/peter-dinklage-sums-up-game-of-thrones-in-45-seconds-video/ - 'url': 'http://media.mtvnservices.com/embed/mgid:uma:video:mtv.com:1043906/cp~vid%3D1043906%26uri%3Dmgid%3Auma%3Avideo%3Amtv.com%3A1043906', - 'md5': 'cb349b21a7897164cede95bd7bf3fbb9', - 'info_dict': { - 'id': '1043906', - 'ext': 'mp4', - 'title': 'Peter Dinklage Sums Up \'Game Of Thrones\' In 45 Seconds', - 'description': '"Sexy sexy sexy, stabby stabby stabby, beautiful language," says Peter Dinklage as he tries summarizing "Game of Thrones" in under a minute.', - 'timestamp': 1400126400, - 'upload_date': '20140515', - }, - } - - def _get_feed_url(self, uri, url=None): - video_id = self._id_from_uri(uri) - config = self._download_json( - f'http://media.mtvnservices.com/pmt/e1/access/index.html?uri={uri}&configtype=edge', video_id) - return self._remove_template_parameter(config['feedWithQueryParams']) - - def _real_extract(self, url): - mobj = self._match_valid_url(url) - mgid = mobj.group('mgid') - return self._get_videos_info(mgid) - - -class MTVIE(MTVServicesInfoExtractor): +class MTVIE(MTVServicesBaseIE): IE_NAME = 'mtv' - _VALID_URL = r'https?://(?:www\.)?mtv\.com/(?:video-clips|(?:full-)?episodes)/(?P[^/?#.]+)' - _FEED_URL = 'http://www.mtv.com/feeds/mrss/' - + _VALID_URL = r'https?://(?:www\.)?mtv\.com/(?:video-clips|episodes)/(?P[\da-z]{6})' _TESTS = [{ - 'url': 'http://www.mtv.com/video-clips/vl8qof/unlocking-the-truth-trailer', - 'md5': '1edbcdf1e7628e414a8c5dcebca3d32b', + 'url': 'https://www.mtv.com/video-clips/syolsj', 'info_dict': { - 'id': '5e14040d-18a4-47c4-a582-43ff602de88e', + 'id': '213ea7f8-bac7-4a43-8cd5-8d8cb8c8160f', 'ext': 'mp4', - 'title': 'Unlocking The Truth|July 18, 2016|1|101|Trailer', - 'description': '"Unlocking the Truth" premieres August 17th at 11/10c.', - 'timestamp': 1468846800, - 'upload_date': '20160718', + 'display_id': 'syolsj', + 'title': 'The Challenge: Vets & New Threats', + 'description': 'md5:c4d2e90a5fff6463740fbf96b2bb6a41', + 'duration': 95.0, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref', + 'series': 'The Challenge', + 'season': 'Season 41', + 'season_number': 41, + 'episode': 'Episode 0', + 'episode_number': 0, + 'timestamp': 1753945200, + 'upload_date': '20250731', + 'release_timestamp': 1753945200, + 'release_date': '20250731', }, + 'params': {'skip_download': 'm3u8'}, }, { - 'url': 'http://www.mtv.com/full-episodes/94tujl/unlocking-the-truth-gates-of-hell-season-1-ep-101', - 'only_matching': True, - }, { - 'url': 'http://www.mtv.com/episodes/g8xu7q/teen-mom-2-breaking-the-wall-season-7-ep-713', - 'only_matching': True, + 'url': 'https://www.mtv.com/episodes/uzvigh', + 'info_dict': { + 'id': '364e8b9e-e415-11ef-b405-16fff45bc035', + 'ext': 'mp4', + 'display_id': 'uzvigh', + 'title': 'CT Tamburello and Johnny Bananas', + 'description': 'md5:364cea52001e9c13f92784e3365c6606', + 'channel': 'MTV', + 'duration': 1260.0, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref', + 'series': 'Ridiculousness', + 'season': 'Season 47', + 'season_number': 47, + 'episode': 'Episode 19', + 'episode_number': 19, + 'timestamp': 1753318800, + 'upload_date': '20250724', + 'release_timestamp': 1753318800, + 'release_date': '20250724', + }, + 'params': {'skip_download': 'm3u8'}, }] - - -class MTVJapanIE(MTVServicesInfoExtractor): - IE_NAME = 'mtvjapan' - _VALID_URL = r'https?://(?:www\.)?mtvjapan\.com/videos/(?P[0-9a-z]+)' - - _TEST = { - 'url': 'http://www.mtvjapan.com/videos/prayht/fresh-info-cadillac-escalade', - 'info_dict': { - 'id': 'bc01da03-6fe5-4284-8880-f291f4e368f5', - 'ext': 'mp4', - 'title': '【Fresh Info】Cadillac ESCALADE Sport Edition', - }, - 'params': { - 'skip_download': True, - }, - } - _GEO_COUNTRIES = ['JP'] - _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed' - - def _get_feed_query(self, uri): - return { - 'arcEp': 'mtvjapan.com', - 'mgid': uri, - } - - -class MTVVideoIE(MTVServicesInfoExtractor): - IE_NAME = 'mtv:video' - _VALID_URL = r'''(?x)^https?:// - (?:(?:www\.)?mtv\.com/videos/.+?/(?P[0-9]+)/[^/]+$| - m\.mtv\.com/videos/video\.rbml\?.*?id=(?P[^&]+))''' - - _FEED_URL = 'http://www.mtv.com/player/embed/AS3/rss/' - - _TESTS = [ - { - 'url': 'http://www.mtv.com/videos/misc/853555/ours-vh1-storytellers.jhtml', - 'md5': '850f3f143316b1e71fa56a4edfd6e0f8', - 'info_dict': { - 'id': '853555', - 'ext': 'mp4', - 'title': 'Taylor Swift - "Ours (VH1 Storytellers)"', - 'description': 'Album: Taylor Swift performs "Ours" for VH1 Storytellers at Harvey Mudd College.', - 'timestamp': 1352610000, - 'upload_date': '20121111', - }, - }, - ] - - def _get_thumbnail_url(self, uri, itemdoc): - return 'http://mtv.mtvnimages.com/uri/' + uri - - def _real_extract(self, url): - mobj = self._match_valid_url(url) - video_id = mobj.group('videoid') - uri = mobj.groupdict().get('mgid') - if uri is None: - webpage = self._download_webpage(url, video_id) - - # Some videos come from Vevo.com - m_vevo = re.search( - r'(?s)isVevoVideo = true;.*?vevoVideoId = "(.*?)";', webpage) - if m_vevo: - vevo_id = m_vevo.group(1) - self.to_screen(f'Vevo video detected: {vevo_id}') - return self.url_result(f'vevo:{vevo_id}', ie='Vevo') - - uri = self._html_search_regex(r'/uri/(.*?)\?', webpage, 'uri') - return self._get_videos_info(uri) - - -class MTVDEIE(MTVServicesInfoExtractor): - _WORKING = False - IE_NAME = 'mtv.de' - _VALID_URL = r'https?://(?:www\.)?mtv\.de/(?:musik/videoclips|folgen|news)/(?P[0-9a-z]+)' - _TESTS = [{ - 'url': 'http://www.mtv.de/musik/videoclips/2gpnv7/Traum', - 'info_dict': { - 'id': 'd5d472bc-f5b7-11e5-bffd-a4badb20dab5', - 'ext': 'mp4', - 'title': 'Traum', - 'description': 'Traum', - }, - 'params': { - # rtmp download - 'skip_download': True, - }, - 'skip': 'Blocked at Travis CI', - }, { - # mediagen URL without query (e.g. http://videos.mtvnn.com/mediagen/e865da714c166d18d6f80893195fcb97) - 'url': 'http://www.mtv.de/folgen/6b1ylu/teen-mom-2-enthuellungen-S5-F1', - 'info_dict': { - 'id': '1e5a878b-31c5-11e7-a442-0e40cf2fc285', - 'ext': 'mp4', - 'title': 'Teen Mom 2', - 'description': 'md5:dc65e357ef7e1085ed53e9e9d83146a7', - }, - 'params': { - # rtmp download - 'skip_download': True, - }, - 'skip': 'Blocked at Travis CI', - }, { - 'url': 'http://www.mtv.de/news/glolix/77491-mtv-movies-spotlight--pixels--teil-3', - 'info_dict': { - 'id': 'local_playlist-4e760566473c4c8c5344', - 'ext': 'mp4', - 'title': 'Article_mtv-movies-spotlight-pixels-teil-3_short-clips_part1', - 'description': 'MTV Movies Supercut', - }, - 'params': { - # rtmp download - 'skip_download': True, - }, - 'skip': 'Das Video kann zur Zeit nicht abgespielt werden.', - }] - _GEO_COUNTRIES = ['DE'] - _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed' - - def _get_feed_query(self, uri): - return { - 'arcEp': 'mtv.de', - 'mgid': uri, - } - - -class MTVItaliaIE(MTVServicesInfoExtractor): - IE_NAME = 'mtv.it' - _VALID_URL = r'https?://(?:www\.)?mtv\.it/(?:episodi|video|musica)/(?P[0-9a-z]+)' - _TESTS = [{ - 'url': 'http://www.mtv.it/episodi/24bqab/mario-una-serie-di-maccio-capatonda-cavoli-amario-episodio-completo-S1-E1', - 'info_dict': { - 'id': '0f0fc78e-45fc-4cce-8f24-971c25477530', - 'ext': 'mp4', - 'title': 'Cavoli amario (episodio completo)', - 'description': 'md5:4962bccea8fed5b7c03b295ae1340660', - 'series': 'Mario - Una Serie Di Maccio Capatonda', - 'season_number': 1, - 'episode_number': 1, - }, - 'params': { - 'skip_download': True, - }, - }] - _GEO_COUNTRIES = ['IT'] - _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed' - - def _get_feed_query(self, uri): - return { - 'arcEp': 'mtv.it', - 'mgid': uri, - } - - -class MTVItaliaProgrammaIE(MTVItaliaIE): # XXX: Do not subclass from concrete IE - IE_NAME = 'mtv.it:programma' - _VALID_URL = r'https?://(?:www\.)?mtv\.it/(?:programmi|playlist)/(?P[0-9a-z]+)' - _TESTS = [{ - # program page: general - 'url': 'http://www.mtv.it/programmi/s2rppv/mario-una-serie-di-maccio-capatonda', - 'info_dict': { - 'id': 'a6f155bc-8220-4640-aa43-9b95f64ffa3d', - 'title': 'Mario - Una Serie Di Maccio Capatonda', - 'description': 'md5:72fbffe1f77ccf4e90757dd4e3216153', - }, - 'playlist_count': 2, - 'params': { - 'skip_download': True, - }, - }, { - # program page: specific season - 'url': 'http://www.mtv.it/programmi/d9ncjf/mario-una-serie-di-maccio-capatonda-S2', - 'info_dict': { - 'id': '4deeb5d8-f272-490c-bde2-ff8d261c6dd1', - 'title': 'Mario - Una Serie Di Maccio Capatonda - Stagione 2', - }, - 'playlist_count': 34, - 'params': { - 'skip_download': True, - }, - }, { - # playlist page + redirect - 'url': 'http://www.mtv.it/playlist/sexy-videos/ilctal', - 'info_dict': { - 'id': 'dee8f9ee-756d-493b-bf37-16d1d2783359', - 'title': 'Sexy Videos', - }, - 'playlist_mincount': 145, - 'params': { - 'skip_download': True, - }, - }] - _GEO_COUNTRIES = ['IT'] - _FEED_URL = 'http://www.mtv.it/feeds/triforce/manifest/v8' - - def _get_entries(self, title, url): - while True: - pg = self._search_regex(r'/(\d+)$', url, 'entries', '1') - entries = self._download_json(url, title, f'page {pg}') - url = try_get( - entries, lambda x: x['result']['nextPageURL'], str) - entries = try_get( - entries, ( - lambda x: x['result']['data']['items'], - lambda x: x['result']['data']['seasons']), - list) - for entry in entries or []: - if entry.get('canonicalURL'): - yield self.url_result(entry['canonicalURL']) - if not url: - break - - def _real_extract(self, url): - query = {'url': url} - info_url = update_url_query(self._FEED_URL, query) - video_id = self._match_id(url) - info = self._download_json(info_url, video_id).get('manifest') - - redirect = try_get( - info, lambda x: x['newLocation']['url'], str) - if redirect: - return self.url_result(redirect) - - title = info.get('title') - video_id = try_get( - info, lambda x: x['reporting']['itemId'], str) - parent_id = try_get( - info, lambda x: x['reporting']['parentId'], str) - - playlist_url = current_url = None - for z in (info.get('zones') or {}).values(): - if z.get('moduleName') in ('INTL_M304', 'INTL_M209'): - info_url = z.get('feed') - if z.get('moduleName') in ('INTL_M308', 'INTL_M317'): - playlist_url = playlist_url or z.get('feed') - if z.get('moduleName') in ('INTL_M300',): - current_url = current_url or z.get('feed') - - if not info_url: - raise ExtractorError('No info found') - - if video_id == parent_id: - video_id = self._search_regex( - r'([^\/]+)/[^\/]+$', info_url, 'video_id') - - info = self._download_json(info_url, video_id, 'Show infos') - info = try_get(info, lambda x: x['result']['data'], dict) - title = title or try_get( - info, ( - lambda x: x['title'], - lambda x: x['headline']), - str) - description = try_get(info, lambda x: x['content'], str) - - if current_url: - season = try_get( - self._download_json(playlist_url, video_id, 'Seasons info'), - lambda x: x['result']['data'], dict) - current = try_get( - season, lambda x: x['currentSeason'], str) - seasons = try_get( - season, lambda x: x['seasons'], list) or [] - - if current in [s.get('eTitle') for s in seasons]: - playlist_url = current_url - - title = re.sub( - r'[-|]\s*(?:mtv\s*italia|programma|playlist)', - '', title, flags=re.IGNORECASE).strip() - - return self.playlist_result( - self._get_entries(title, playlist_url), - video_id, title, description) diff --git a/yt-dlp/yt_dlp/extractor/nick.py b/yt-dlp/yt_dlp/extractor/nick.py index 653b10b9d0..91cefd21da 100644 --- a/yt-dlp/yt_dlp/extractor/nick.py +++ b/yt-dlp/yt_dlp/extractor/nick.py @@ -1,224 +1,48 @@ -from .mtv import MTVServicesInfoExtractor -from ..utils import update_url_query +from .mtv import MTVServicesBaseIE -class NickIE(MTVServicesInfoExtractor): +class NickIE(MTVServicesBaseIE): IE_NAME = 'nick.com' - _VALID_URL = r'https?://(?P(?:www\.)?nick(?:jr)?\.com)/(?:[^/]+/)?(?Pvideos/clip|[^/]+/videos|episodes/[^/]+)/(?P[^/?#.]+)' - _FEED_URL = 'http://udat.mtvnservices.com/service1/dispatch.htm' - _GEO_COUNTRIES = ['US'] + _VALID_URL = r'https?://(?:www\.)?nick\.com/(?:video-clips|episodes)/(?P[\da-z]{6})' _TESTS = [{ - 'url': 'https://www.nick.com/episodes/sq47rw/spongebob-squarepants-a-place-for-pets-lockdown-for-love-season-13-ep-1', + 'url': 'https://www.nick.com/episodes/u3smw8/wylde-pak-best-summer-ever-season-1-ep-1', 'info_dict': { - 'description': 'md5:0650a9eb88955609d5c1d1c79292e234', - 'title': 'A Place for Pets/Lockdown for Love', - }, - 'playlist': [ - { - 'md5': 'cb8a2afeafb7ae154aca5a64815ec9d6', - 'info_dict': { - 'id': '85ee8177-d6ce-48f8-9eee-a65364f8a6df', - 'ext': 'mp4', - 'title': 'SpongeBob SquarePants: "A Place for Pets/Lockdown for Love" S1', - 'description': 'A Place for Pets/Lockdown for Love: When customers bring pets into the Krusty Krab, Mr. Krabs realizes pets are more profitable than owners. Plankton ruins another date with Karen, so she puts the Chum Bucket on lockdown until he proves his affection.', - - }, - }, - { - 'md5': '839a04f49900a1fcbf517020d94e0737', - 'info_dict': { - 'id': '2e2a9960-8fd4-411d-868b-28eb1beb7fae', - 'ext': 'mp4', - 'title': 'SpongeBob SquarePants: "A Place for Pets/Lockdown for Love" S2', - 'description': 'A Place for Pets/Lockdown for Love: When customers bring pets into the Krusty Krab, Mr. Krabs realizes pets are more profitable than owners. Plankton ruins another date with Karen, so she puts the Chum Bucket on lockdown until he proves his affection.', - - }, - }, - { - 'md5': 'f1145699f199770e2919ee8646955d46', - 'info_dict': { - 'id': 'dc91c304-6876-40f7-84a6-7aece7baa9d0', - 'ext': 'mp4', - 'title': 'SpongeBob SquarePants: "A Place for Pets/Lockdown for Love" S3', - 'description': 'A Place for Pets/Lockdown for Love: When customers bring pets into the Krusty Krab, Mr. Krabs realizes pets are more profitable than owners. Plankton ruins another date with Karen, so she puts the Chum Bucket on lockdown until he proves his affection.', - - }, - }, - { - 'md5': 'd463116875aee2585ee58de3b12caebd', - 'info_dict': { - 'id': '5d929486-cf4c-42a1-889a-6e0d183a101a', - 'ext': 'mp4', - 'title': 'SpongeBob SquarePants: "A Place for Pets/Lockdown for Love" S4', - 'description': 'A Place for Pets/Lockdown for Love: When customers bring pets into the Krusty Krab, Mr. Krabs realizes pets are more profitable than owners. Plankton ruins another date with Karen, so she puts the Chum Bucket on lockdown until he proves his affection.', - - }, - }, - ], - }, { - 'url': 'http://www.nickjr.com/blues-clues-and-you/videos/blues-clues-and-you-original-209-imagination-station/', - 'info_dict': { - 'id': '31631529-2fc5-430b-b2ef-6a74b4609abd', + 'id': 'eb9d4db0-274a-11ef-a913-0e37995d42c9', 'ext': 'mp4', - 'description': 'md5:9d65a66df38e02254852794b2809d1cf', - 'title': 'Blue\'s Imagination Station', + 'display_id': 'u3smw8', + 'title': 'Best Summer Ever?', + 'description': 'md5:c737a0ade3fbc09d569c3b3d029a7792', + 'channel': 'Nickelodeon', + 'duration': 1296.0, + 'thumbnail': r're:https://assets\.nick\.com/uri/mgid:arc:imageassetref:', + 'series': 'Wylde Pak', + 'season': 'Season 1', + 'season_number': 1, + 'episode': 'Episode 1', + 'episode_number': 1, + 'timestamp': 1746100800, + 'upload_date': '20250501', + 'release_timestamp': 1746100800, + 'release_date': '20250501', }, - 'skip': 'Not accessible?', + 'params': {'skip_download': 'm3u8'}, + }, { + 'url': 'https://www.nick.com/video-clips/0p4706/spongebob-squarepants-spongebob-loving-the-krusty-krab-for-7-minutes', + 'info_dict': { + 'id': '4aac2228-5295-4076-b986-159513cf4ce4', + 'ext': 'mp4', + 'display_id': '0p4706', + 'title': 'SpongeBob Loving the Krusty Krab for 7 Minutes!', + 'description': 'md5:72bf59babdf4e6d642187502864e111d', + 'duration': 423.423, + 'thumbnail': r're:https://assets\.nick\.com/uri/mgid:arc:imageassetref:', + 'series': 'SpongeBob SquarePants', + 'season': 'Season 0', + 'season_number': 0, + 'episode': 'Episode 0', + 'episode_number': 0, + 'timestamp': 1663819200, + 'upload_date': '20220922', + }, + 'params': {'skip_download': 'm3u8'}, }] - - def _get_feed_query(self, uri): - return { - 'feed': 'nick_arc_player_prime', - 'mgid': uri, - } - - def _real_extract(self, url): - domain, video_type, display_id = self._match_valid_url(url).groups() - if video_type.startswith('episodes'): - return super()._real_extract(url) - video_data = self._download_json( - f'http://{domain}/data/video.endLevel.json', - display_id, query={ - 'urlKey': display_id, - }) - return self._get_videos_info(video_data['player'] + video_data['id']) - - -class NickBrIE(MTVServicesInfoExtractor): - IE_NAME = 'nickelodeon:br' - _VALID_URL = r'''(?x) - https?:// - (?: - (?P(?:www\.)?nickjr|mundonick\.uol)\.com\.br| - (?:www\.)?nickjr\.[a-z]{2}| - (?:www\.)?nickelodeonjunior\.fr - ) - /(?:programas/)?[^/]+/videos/(?:episodios/)?(?P[^/?\#.]+) - ''' - _TESTS = [{ - 'url': 'http://www.nickjr.com.br/patrulha-canina/videos/210-labirinto-de-pipoca/', - 'only_matching': True, - }, { - 'url': 'http://mundonick.uol.com.br/programas/the-loud-house/videos/muitas-irmas/7ljo9j', - 'only_matching': True, - }, { - 'url': 'http://www.nickjr.nl/paw-patrol/videos/311-ge-wol-dig-om-terug-te-zijn/', - 'only_matching': True, - }, { - 'url': 'http://www.nickjr.de/blaze-und-die-monster-maschinen/videos/f6caaf8f-e4e8-4cc1-b489-9380d6dcd059/', - 'only_matching': True, - }, { - 'url': 'http://www.nickelodeonjunior.fr/paw-patrol-la-pat-patrouille/videos/episode-401-entier-paw-patrol/', - 'only_matching': True, - }] - - def _real_extract(self, url): - domain, display_id = self._match_valid_url(url).groups() - webpage = self._download_webpage(url, display_id) - uri = self._search_regex( - r'data-(?:contenturi|mgid)="([^"]+)', webpage, 'mgid') - video_id = self._id_from_uri(uri) - config = self._download_json( - 'http://media.mtvnservices.com/pmt/e1/access/index.html', - video_id, query={ - 'uri': uri, - 'configtype': 'edge', - }, headers={ - 'Referer': url, - }) - info_url = self._remove_template_parameter(config['feedWithQueryParams']) - if info_url == 'None': - if domain.startswith('www.'): - domain = domain[4:] - content_domain = { - 'mundonick.uol': 'mundonick.com.br', - 'nickjr': 'br.nickelodeonjunior.tv', - }[domain] - query = { - 'mgid': uri, - 'imageEp': content_domain, - 'arcEp': content_domain, - } - if domain == 'nickjr.com.br': - query['ep'] = 'c4b16088' - info_url = update_url_query( - 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed', query) - return self._get_videos_info_from_url(info_url, video_id) - - -class NickDeIE(MTVServicesInfoExtractor): - IE_NAME = 'nick.de' - _VALID_URL = r'https?://(?:www\.)?(?Pnick\.(?:de|com\.pl|ch)|nickelodeon\.(?:nl|be|at|dk|no|se))/[^/]+/(?:[^/]+/)*(?P[^/?#&]+)' - _TESTS = [{ - 'url': 'http://www.nick.de/playlist/3773-top-videos/videos/episode/17306-zu-wasser-und-zu-land-rauchende-erdnusse', - 'only_matching': True, - }, { - 'url': 'http://www.nick.de/shows/342-icarly', - 'only_matching': True, - }, { - 'url': 'http://www.nickelodeon.nl/shows/474-spongebob/videos/17403-een-kijkje-in-de-keuken-met-sandy-van-binnenuit', - 'only_matching': True, - }, { - 'url': 'http://www.nickelodeon.at/playlist/3773-top-videos/videos/episode/77993-das-letzte-gefecht', - 'only_matching': True, - }, { - 'url': 'http://www.nick.com.pl/seriale/474-spongebob-kanciastoporty/wideo/17412-teatr-to-jest-to-rodeo-oszolom', - 'only_matching': True, - }, { - 'url': 'http://www.nickelodeon.no/program/2626-bulderhuset/videoer/90947-femteklasse-veronica-vs-vanzilla', - 'only_matching': True, - }, { - 'url': 'http://www.nickelodeon.dk/serier/2626-hojs-hus/videoer/761-tissepause', - 'only_matching': True, - }, { - 'url': 'http://www.nickelodeon.se/serier/2626-lugn-i-stormen/videos/998-', - 'only_matching': True, - }, { - 'url': 'http://www.nick.ch/shows/2304-adventure-time-abenteuerzeit-mit-finn-und-jake', - 'only_matching': True, - }, { - 'url': 'http://www.nickelodeon.be/afspeellijst/4530-top-videos/videos/episode/73917-inval-broodschapper-lariekoek-arie', - 'only_matching': True, - }] - - def _get_feed_url(self, uri, url=None): - video_id = self._id_from_uri(uri) - config = self._download_json( - f'http://media.mtvnservices.com/pmt/e1/access/index.html?uri={uri}&configtype=edge&ref={url}', video_id) - return self._remove_template_parameter(config['feedWithQueryParams']) - - -class NickRuIE(MTVServicesInfoExtractor): - IE_NAME = 'nickelodeonru' - _VALID_URL = r'https?://(?:www\.)nickelodeon\.(?:ru|fr|es|pt|ro|hu|com\.tr)/[^/]+/(?:[^/]+/)*(?P[^/?#&]+)' - _TESTS = [{ - 'url': 'http://www.nickelodeon.ru/shows/henrydanger/videos/episodes/3-sezon-15-seriya-licenziya-na-polyot/pmomfb#playlist/7airc6', - 'only_matching': True, - }, { - 'url': 'http://www.nickelodeon.ru/videos/smotri-na-nickelodeon-v-iyule/g9hvh7', - 'only_matching': True, - }, { - 'url': 'http://www.nickelodeon.fr/programmes/bob-l-eponge/videos/le-marathon-de-booh-kini-bottom-mardi-31-octobre/nfn7z0', - 'only_matching': True, - }, { - 'url': 'http://www.nickelodeon.es/videos/nickelodeon-consejos-tortitas/f7w7xy', - 'only_matching': True, - }, { - 'url': 'http://www.nickelodeon.pt/series/spongebob-squarepants/videos/a-bolha-de-tinta-gigante/xutq1b', - 'only_matching': True, - }, { - 'url': 'http://www.nickelodeon.ro/emisiuni/shimmer-si-shine/video/nahal-din-bomboane/uw5u2k', - 'only_matching': True, - }, { - 'url': 'http://www.nickelodeon.hu/musorok/spongyabob-kockanadrag/videok/episodes/buborekfujas-az-elszakadt-nadrag/q57iob#playlist/k6te4y', - 'only_matching': True, - }, { - 'url': 'http://www.nickelodeon.com.tr/programlar/sunger-bob/videolar/kayip-yatak/mgqbjy', - 'only_matching': True, - }] - - def _real_extract(self, url): - video_id = self._match_id(url) - webpage = self._download_webpage(url, video_id) - mgid = self._extract_mgid(webpage, url) - return self.url_result(f'http://media.mtvnservices.com/embed/{mgid}') diff --git a/yt-dlp/yt_dlp/extractor/nrk.py b/yt-dlp/yt_dlp/extractor/nrk.py index 1400f1536d..61f81fa5ed 100644 --- a/yt-dlp/yt_dlp/extractor/nrk.py +++ b/yt-dlp/yt_dlp/extractor/nrk.py @@ -446,9 +446,10 @@ class NRKTVIE(InfoExtractor): class NRKTVEpisodeIE(InfoExtractor): - _VALID_URL = r'https?://tv\.nrk\.no/serie/(?P[^/]+/sesong/(?P\d+)/episode/(?P\d+))' + _VALID_URL = r'https?://tv\.nrk\.no/serie/(?P[^/?#]+/sesong/(?P\d+)/episode/(?P\d+))' _TESTS = [{ 'url': 'https://tv.nrk.no/serie/hellums-kro/sesong/1/episode/2', + 'add_ie': [NRKIE.ie_key()], 'info_dict': { 'id': 'MUHH36005220', 'ext': 'mp4', @@ -485,26 +486,23 @@ class NRKTVEpisodeIE(InfoExtractor): }] def _real_extract(self, url): - display_id, season_number, episode_number = self._match_valid_url(url).groups() + display_id, season_number, episode_number = self._match_valid_url(url).group( + 'id', 'season_number', 'episode_number') + webpage, urlh = self._download_webpage_handle(url, display_id) - webpage = self._download_webpage(url, display_id) + if NRKTVIE.suitable(urlh.url): + nrk_id = NRKTVIE._match_id(urlh.url) + else: + nrk_id = self._search_json( + r']+\bid="pageData"[^>]*>', webpage, + 'page data', display_id)['initialState']['selectedEpisodePrfId'] + if not re.fullmatch(NRKTVIE._EPISODE_RE, nrk_id): + raise ExtractorError('Unable to extract NRK ID') - info = self._search_json_ld(webpage, display_id, default={}) - nrk_id = info.get('@id') or self._html_search_meta( - 'nrk:program-id', webpage, default=None) or self._search_regex( - rf'data-program-id=["\']({NRKTVIE._EPISODE_RE})', webpage, - 'nrk id') - assert re.match(NRKTVIE._EPISODE_RE, nrk_id) - - info.update({ - '_type': 'url', - 'id': nrk_id, - 'url': f'nrk:{nrk_id}', - 'ie_key': NRKIE.ie_key(), - 'season_number': int(season_number), - 'episode_number': int(episode_number), - }) - return info + return self.url_result( + f'nrk:{nrk_id}', NRKIE, nrk_id, + season_number=int(season_number), + episode_number=int(episode_number)) class NRKTVSerieBaseIE(NRKBaseIE): diff --git a/yt-dlp/yt_dlp/extractor/puhutv.py b/yt-dlp/yt_dlp/extractor/puhutv.py index b62050ecd5..a9552ec944 100644 --- a/yt-dlp/yt_dlp/extractor/puhutv.py +++ b/yt-dlp/yt_dlp/extractor/puhutv.py @@ -6,6 +6,7 @@ from ..utils import ( int_or_none, parse_resolution, str_or_none, + traverse_obj, try_get, unified_timestamp, url_or_none, @@ -18,20 +19,21 @@ class PuhuTVIE(InfoExtractor): IE_NAME = 'puhutv' _TESTS = [{ # film - 'url': 'https://puhutv.com/sut-kardesler-izle', - 'md5': 'a347470371d56e1585d1b2c8dab01c96', + 'url': 'https://puhutv.com/bi-kucuk-eylul-meselesi-izle', + 'md5': '4de98170ccb84c05779b1f046b3c86f8', 'info_dict': { - 'id': '5085', - 'display_id': 'sut-kardesler', + 'id': '11909', + 'display_id': 'bi-kucuk-eylul-meselesi', 'ext': 'mp4', - 'title': 'Süt Kardeşler', - 'description': 'md5:ca09da25b7e57cbb5a9280d6e48d17aa', + 'title': 'Bi Küçük Eylül Meselesi', + 'description': 'md5:c2ab964c6542b7b26acacca0773adce2', 'thumbnail': r're:^https?://.*\.jpg$', - 'duration': 4832.44, - 'creator': 'Arzu Film', - 'timestamp': 1561062602, + 'duration': 6176.96, + 'creator': 'Ay Yapım', + 'creators': ['Ay Yapım'], + 'timestamp': 1561062749, 'upload_date': '20190620', - 'release_year': 1976, + 'release_year': 2014, 'view_count': int, 'tags': list, }, @@ -181,7 +183,7 @@ class PuhuTVSerieIE(InfoExtractor): 'playlist_mincount': 205, }, { # a film detail page which is using same url with serie page - 'url': 'https://puhutv.com/kaybedenler-kulubu-detay', + 'url': 'https://puhutv.com/bizim-icin-sampiyon-detay', 'only_matching': True, }] @@ -194,24 +196,19 @@ class PuhuTVSerieIE(InfoExtractor): has_more = True while has_more is True: season = self._download_json( - f'https://galadriel.puhutv.com/seasons/{season_id}', + f'https://appservice.puhutv.com/api/seasons/{season_id}/episodes?v=2', season_id, f'Downloading page {page}', query={ 'page': page, - 'per': 40, - }) - episodes = season.get('episodes') - if isinstance(episodes, list): - for ep in episodes: - slug_path = str_or_none(ep.get('slugPath')) - if not slug_path: - continue - video_id = str_or_none(int_or_none(ep.get('id'))) - yield self.url_result( - f'https://puhutv.com/{slug_path}', - ie=PuhuTVIE.ie_key(), video_id=video_id, - video_title=ep.get('name') or ep.get('eventLabel')) + 'per': 100, + })['data'] + + for episode in traverse_obj(season, ('episodes', lambda _, v: v.get('slug') or v['assets'][0]['slug'])): + slug = episode.get('slug') or episode['assets'][0]['slug'] + yield self.url_result( + f'https://puhutv.com/{slug}', PuhuTVIE, episode.get('id'), episode.get('name')) + page += 1 - has_more = season.get('hasMore') + has_more = traverse_obj(season, 'has_more') def _real_extract(self, url): playlist_id = self._match_id(url) @@ -226,7 +223,10 @@ class PuhuTVSerieIE(InfoExtractor): self._extract_entries(seasons), playlist_id, info.get('name')) # For films, these are using same url with series - video_id = info.get('slug') or info['assets'][0]['slug'] + video_id = info.get('slug') + if video_id: + video_id = video_id.removesuffix('-detay') + else: + video_id = info['assets'][0]['slug'].removesuffix('-izle') return self.url_result( - f'https://puhutv.com/{video_id}-izle', - PuhuTVIE.ie_key(), video_id) + f'https://puhutv.com/{video_id}-izle', PuhuTVIE, video_id) diff --git a/yt-dlp/yt_dlp/extractor/southpark.py b/yt-dlp/yt_dlp/extractor/southpark.py index 3d661a86ac..159ba5a4bb 100644 --- a/yt-dlp/yt_dlp/extractor/southpark.py +++ b/yt-dlp/yt_dlp/extractor/southpark.py @@ -1,58 +1,94 @@ -from .mtv import MTVServicesInfoExtractor +from .mtv import MTVServicesBaseIE -class SouthParkIE(MTVServicesInfoExtractor): +class SouthParkIE(MTVServicesBaseIE): IE_NAME = 'southpark.cc.com' - _VALID_URL = r'https?://(?:www\.)?(?Psouthpark(?:\.cc|studios)\.com/((?:video-)?clips|(?:full-)?episodes|collections)/(?P.+?)(\?|#|$))' - - _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed' - + _VALID_URL = r'https?://(?:www\.)?southpark(?:\.cc|studios)\.com/(?:video-clips|episodes|collections)/(?P[^?#]+)' _TESTS = [{ 'url': 'https://southpark.cc.com/video-clips/d7wr06/south-park-you-all-agreed-to-counseling', 'info_dict': { + 'id': '31929ad5-8269-11eb-8774-70df2f866ace', 'ext': 'mp4', + 'display_id': 'd7wr06/south-park-you-all-agreed-to-counseling', 'title': 'You All Agreed to Counseling', - 'description': 'Kenny, Cartman, Stan, and Kyle visit Mr. Mackey and ask for his help getting Mrs. Nelson to come back. Mr. Mackey reveals the only way to get things back to normal is to get the teachers vaccinated.', + 'description': 'md5:01f78fb306c7042f3f05f3c78edfc212', + 'duration': 134.552, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:', + 'series': 'South Park', + 'season': 'Season 24', + 'season_number': 24, + 'episode': 'Episode 2', + 'episode_number': 2, 'timestamp': 1615352400, 'upload_date': '20210310', + 'release_timestamp': 1615352400, + 'release_date': '20210310', }, + 'params': {'skip_download': 'm3u8'}, }, { - 'url': 'http://southpark.cc.com/collections/7758/fan-favorites/1', + 'url': 'https://southpark.cc.com/episodes/940f8z/south-park-cartman-gets-an-anal-probe-season-1-ep-1', + 'info_dict': { + 'id': '5fb8887e-ecfd-11e0-aca6-0026b9414f30', + 'ext': 'mp4', + 'display_id': '940f8z/south-park-cartman-gets-an-anal-probe-season-1-ep-1', + 'title': 'Cartman Gets An Anal Probe', + 'description': 'md5:964e1968c468545752feef102b140300', + 'channel': 'Comedy Central', + 'duration': 1319.0, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:', + 'series': 'South Park', + 'season': 'Season 1', + 'season_number': 1, + 'episode': 'Episode 1', + 'episode_number': 1, + 'timestamp': 871473600, + 'upload_date': '19970813', + 'release_timestamp': 871473600, + 'release_date': '19970813', + }, + 'params': {'skip_download': 'm3u8'}, + }, { + 'url': 'https://southpark.cc.com/collections/dejukt/south-park-best-of-mr-mackey/tphx9j', 'only_matching': True, }, { 'url': 'https://www.southparkstudios.com/episodes/h4o269/south-park-stunning-and-brave-season-19-ep-1', 'only_matching': True, }] - def _get_feed_query(self, uri): - return { - 'accountOverride': 'intl.mtvi.com', - 'arcEp': 'shared.southpark.global', - 'ep': '90877963', - 'imageEp': 'shared.southpark.global', - 'mgid': uri, - } - -class SouthParkEsIE(SouthParkIE): # XXX: Do not subclass from concrete IE +class SouthParkEsIE(MTVServicesBaseIE): IE_NAME = 'southpark.cc.com:español' - _VALID_URL = r'https?://(?:www\.)?(?Psouthpark\.cc\.com/es/episodios/(?P.+?)(\?|#|$))' - _LANG = 'es' - + _VALID_URL = r'https?://(?:www\.)?southpark\.cc\.com/es/episodios/(?P[^?#]+)' _TESTS = [{ - 'url': 'http://southpark.cc.com/es/episodios/s01e01-cartman-consigue-una-sonda-anal#source=351c1323-0b96-402d-a8b9-40d01b2e9bde&position=1&sort=!airdate', + 'url': 'https://southpark.cc.com/es/episodios/er4a32/south-park-aumento-de-peso-4000-temporada-1-ep-2', 'info_dict': { - 'title': 'Cartman Consigue Una Sonda Anal', - 'description': 'Cartman Consigue Una Sonda Anal', + 'id': '5fb94f0c-ecfd-11e0-aca6-0026b9414f30', + 'ext': 'mp4', + 'display_id': 'er4a32/south-park-aumento-de-peso-4000-temporada-1-ep-2', + 'title': 'Aumento de peso 4000', + 'description': 'md5:a939b4819ea74c245a0cde180de418c0', + 'channel': 'Comedy Central', + 'duration': 1320.0, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:', + 'series': 'South Park', + 'season': 'Season 1', + 'season_number': 1, + 'episode': 'Episode 2', + 'episode_number': 2, + 'timestamp': 872078400, + 'upload_date': '19970820', + 'release_timestamp': 872078400, + 'release_date': '19970820', }, - 'playlist_count': 4, - 'skip': 'Geo-restricted', + 'params': {'skip_download': 'm3u8'}, }] -class SouthParkDeIE(SouthParkIE): # XXX: Do not subclass from concrete IE +class SouthParkDeIE(MTVServicesBaseIE): IE_NAME = 'southpark.de' - _VALID_URL = r'https?://(?:www\.)?(?Psouthpark\.de/(?:(en/(videoclip|collections|episodes|video-clips))|(videoclip|collections|folgen))/(?P(?P.+?)/.+?)(?:\?|#|$))' + _VALID_URL = r'https?://(?:www\.)?southpark\.de/(?:en/)?(?:videoclip|collections|episodes|video-clips|folgen)/(?P[^?#]+)' + _GEO_COUNTRIES = ['DE'] + _GEO_BYPASS = True _TESTS = [{ 'url': 'https://www.southpark.de/videoclip/rsribv/south-park-rueckzug-zum-gummibonbon-wald', 'only_matching': True, @@ -66,123 +102,253 @@ class SouthParkDeIE(SouthParkIE): # XXX: Do not subclass from concrete IE # clip 'url': 'https://www.southpark.de/en/video-clips/ct46op/south-park-tooth-fairy-cartman', 'info_dict': { - 'id': 'e99d45ea-ed00-11e0-aca6-0026b9414f30', 'ext': 'mp4', + 'id': 'e99d45ea-ed00-11e0-aca6-0026b9414f30', + 'display_id': 'ct46op/south-park-tooth-fairy-cartman', 'title': 'Tooth Fairy Cartman', - 'description': 'md5:db02e23818b4dc9cb5f0c5a7e8833a68', + 'description': 'Cartman steals Butters\' tooth and gets four dollars for it.', + 'duration': 93.26, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:', + 'series': 'South Park', + 'season': 'Season 4', + 'season_number': 4, + 'episode': 'Episode 1', + 'episode_number': 1, + 'timestamp': 954990360, + 'upload_date': '20000406', + 'release_timestamp': 954990360, + 'release_date': '20000406', }, + 'params': {'skip_download': 'm3u8'}, }, { # episode 'url': 'https://www.southpark.de/en/episodes/yy0vjs/south-park-the-pandemic-special-season-24-ep-1', 'info_dict': { - 'id': 'f5fbd823-04bc-11eb-9b1b-0e40cf2fc285', 'ext': 'mp4', - 'title': 'South Park', + 'id': '230a4f02-f583-11ea-834d-70df2f866ace', + 'display_id': 'yy0vjs/south-park-the-pandemic-special-season-24-ep-1', + 'title': 'The Pandemic Special', 'description': 'md5:ae0d875eff169dcbed16b21531857ac1', + 'channel': 'Comedy Central', + 'duration': 2724.0, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:', + 'series': 'South Park', + 'season': 'Season 24', + 'season_number': 24, + 'episode': 'Episode 1', + 'episode_number': 1, + 'timestamp': 1601932260, + 'upload_date': '20201005', + 'release_timestamp': 1601932270, + 'release_date': '20201005', }, + 'params': {'skip_download': 'm3u8'}, }, { # clip 'url': 'https://www.southpark.de/videoclip/ct46op/south-park-zahnfee-cartman', 'info_dict': { - 'id': 'e99d45ea-ed00-11e0-aca6-0026b9414f30', 'ext': 'mp4', + 'id': 'e99d45ea-ed00-11e0-aca6-0026b9414f30', + 'display_id': 'ct46op/south-park-zahnfee-cartman', 'title': 'Zahnfee Cartman', 'description': 'md5:b917eec991d388811d911fd1377671ac', + 'duration': 93.26, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:', + 'series': 'South Park', + 'season': 'Season 4', + 'season_number': 4, + 'episode': 'Episode 1', + 'episode_number': 1, + 'timestamp': 954990360, + 'upload_date': '20000406', + 'release_timestamp': 954990360, + 'release_date': '20000406', }, + 'params': {'skip_download': 'm3u8'}, }, { # episode - 'url': 'https://www.southpark.de/folgen/242csn/south-park-her-mit-dem-hirn-staffel-1-ep-7', + 'url': 'https://www.southpark.de/folgen/4r4367/south-park-katerstimmung-staffel-12-ep-3', 'info_dict': { - 'id': '607115f3-496f-40c3-8647-2b0bcff486c0', 'ext': 'mp4', - 'title': 'md5:South Park | Pink Eye | E 0107 | HDSS0107X deu | Version: 634312 | Comedy Central S1', + 'id': '68c79aa4-ecfd-11e0-aca6-0026b9414f30', + 'display_id': '4r4367/south-park-katerstimmung-staffel-12-ep-3', + 'title': 'Katerstimmung', + 'description': 'md5:94e0e2cd568ffa635e0725518bb4b180', + 'channel': 'Comedy Central', + 'duration': 1320.0, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:', + 'series': 'South Park', + 'season': 'Season 12', + 'season_number': 12, + 'episode': 'Episode 3', + 'episode_number': 3, + 'timestamp': 1206504000, + 'upload_date': '20080326', + 'release_timestamp': 1206504000, + 'release_date': '20080326', }, + 'params': {'skip_download': 'm3u8'}, }] - def _get_feed_url(self, uri, url=None): - video_id = self._id_from_uri(uri) - config = self._download_json( - f'http://media.mtvnservices.com/pmt/e1/access/index.html?uri={uri}&configtype=edge&ref={url}', video_id) - return self._remove_template_parameter(config['feedWithQueryParams']) - def _get_feed_query(self, uri): - return - - -class SouthParkLatIE(SouthParkIE): # XXX: Do not subclass from concrete IE +class SouthParkLatIE(MTVServicesBaseIE): IE_NAME = 'southpark.lat' - _VALID_URL = r'https?://(?:www\.)?southpark\.lat/(?:en/)?(?:video-?clips?|collections|episod(?:e|io)s)/(?P[^/?#&]+)' + _VALID_URL = r'https?://(?:www\.)?southpark\.lat/(?:en/)?(?:video-?clips?|collections|episod(?:e|io)s)/(?P[^?#]+)' + _GEO_COUNTRIES = ['MX'] + _GEO_BYPASS = True _TESTS = [{ 'url': 'https://www.southpark.lat/en/video-clips/ct46op/south-park-tooth-fairy-cartman', - 'only_matching': True, + 'info_dict': { + 'ext': 'mp4', + 'id': 'e99d45ea-ed00-11e0-aca6-0026b9414f30', + 'display_id': 'ct46op/south-park-tooth-fairy-cartman', + 'title': 'Tooth Fairy Cartman', + 'description': 'Cartman steals Butters\' tooth and gets four dollars for it.', + 'duration': 93.26, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:', + 'series': 'South Park', + 'season': 'Season 4', + 'season_number': 4, + 'episode': 'Episode 1', + 'episode_number': 1, + 'timestamp': 954990360, + 'upload_date': '20000406', + 'release_timestamp': 954990360, + 'release_date': '20000406', + }, + 'params': {'skip_download': 'm3u8'}, }, { 'url': 'https://www.southpark.lat/episodios/9h0qbg/south-park-orgia-gatuna-temporada-3-ep-7', - 'only_matching': True, + 'info_dict': { + 'ext': 'mp4', + 'id': '600d273a-ecfd-11e0-aca6-0026b9414f30', + 'display_id': '9h0qbg/south-park-orgia-gatuna-temporada-3-ep-7', + 'title': 'Orgía Gatuna ', + 'description': 'md5:73c6648413f5977026abb792a25c65d5', + 'channel': 'Comedy Central', + 'duration': 1319.0, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:', + 'series': 'South Park', + 'season': 'Season 3', + 'season_number': 3, + 'episode': 'Episode 7', + 'episode_number': 7, + 'timestamp': 931924800, + 'upload_date': '19990714', + 'release_timestamp': 931924800, + 'release_date': '19990714', + }, + 'params': {'skip_download': 'm3u8'}, }, { 'url': 'https://www.southpark.lat/en/collections/29ve08/south-park-heating-up/lydbrc', 'only_matching': True, - }, { - # clip - 'url': 'https://www.southpark.lat/en/video-clips/ct46op/south-park-tooth-fairy-cartman', - 'info_dict': { - 'id': 'e99d45ea-ed00-11e0-aca6-0026b9414f30', - 'ext': 'mp4', - 'title': 'Tooth Fairy Cartman', - 'description': 'md5:db02e23818b4dc9cb5f0c5a7e8833a68', - }, - }, { - # episode - 'url': 'https://www.southpark.lat/episodios/9h0qbg/south-park-orgia-gatuna-temporada-3-ep-7', - 'info_dict': { - 'id': 'f5fbd823-04bc-11eb-9b1b-0e40cf2fc285', - 'ext': 'mp4', - 'title': 'South Park', - 'description': 'md5:ae0d875eff169dcbed16b21531857ac1', - }, - }] - - def _get_feed_url(self, uri, url=None): - video_id = self._id_from_uri(uri) - config = self._download_json( - f'http://media.mtvnservices.com/pmt/e1/access/index.html?uri={uri}&configtype=edge&ref={url}', - video_id) - return self._remove_template_parameter(config['feedWithQueryParams']) - - def _get_feed_query(self, uri): - return - - -class SouthParkNlIE(SouthParkIE): # XXX: Do not subclass from concrete IE - IE_NAME = 'southpark.nl' - _VALID_URL = r'https?://(?:www\.)?(?Psouthpark\.nl/(?:clips|(?:full-)?episodes|collections)/(?P.+?)(\?|#|$))' - _FEED_URL = 'http://www.southpark.nl/feeds/video-player/mrss/' - - _TESTS = [{ - 'url': 'http://www.southpark.nl/full-episodes/s18e06-freemium-isnt-free', - 'info_dict': { - 'title': 'Freemium Isn\'t Free', - 'description': 'Stan is addicted to the new Terrance and Phillip mobile game.', - }, - 'playlist_mincount': 3, }] -class SouthParkDkIE(SouthParkIE): # XXX: Do not subclass from concrete IE - IE_NAME = 'southparkstudios.dk' - _VALID_URL = r'https?://(?:www\.)?(?Psouthparkstudios\.(?:dk|nu)/(?:clips|full-episodes|collections)/(?P.+?)(\?|#|$))' - _FEED_URL = 'http://www.southparkstudios.dk/feeds/video-player/mrss/' - +class SouthParkDkIE(MTVServicesBaseIE): + IE_NAME = 'southparkstudios.nu' + _VALID_URL = r'https?://(?:www\.)?southparkstudios\.nu/(?:video-clips|episodes|collections)/(?P[^?#]+)' + _GEO_COUNTRIES = ['DK'] + _GEO_BYPASS = True _TESTS = [{ - 'url': 'http://www.southparkstudios.dk/full-episodes/s18e07-grounded-vindaloop', + 'url': 'https://www.southparkstudios.nu/episodes/y3uvvc/south-park-grounded-vindaloop-season-18-ep-7', 'info_dict': { + 'ext': 'mp4', + 'id': 'f60690a7-21a7-4ee7-8834-d7099a8707ab', + 'display_id': 'y3uvvc/south-park-grounded-vindaloop-season-18-ep-7', 'title': 'Grounded Vindaloop', 'description': 'Butters is convinced he\'s living in a virtual reality.', + 'channel': 'Comedy Central', + 'duration': 1319.0, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:', + 'series': 'South Park', + 'season': 'Season 18', + 'season_number': 18, + 'episode': 'Episode 7', + 'episode_number': 7, + 'timestamp': 1415847600, + 'upload_date': '20141113', + 'release_timestamp': 1415768400, + 'release_date': '20141112', }, - 'playlist_mincount': 3, + 'params': {'skip_download': 'm3u8'}, }, { - 'url': 'http://www.southparkstudios.dk/collections/2476/superhero-showdown/1', + 'url': 'https://www.southparkstudios.nu/collections/8dk7kr/south-park-best-of-south-park/sd5ean', 'only_matching': True, }, { - 'url': 'http://www.southparkstudios.nu/collections/2476/superhero-showdown/1', + 'url': 'https://www.southparkstudios.nu/video-clips/k42mrf/south-park-kick-the-baby', + 'only_matching': True, + }] + + +class SouthParkComBrIE(MTVServicesBaseIE): + IE_NAME = 'southparkstudios.com.br' + _VALID_URL = r'https?://(?:www\.)?southparkstudios\.com\.br/(?:en/)?(?:video-clips|episodios|collections|episodes)/(?P[^?#]+)' + _GEO_COUNTRIES = ['BR'] + _GEO_BYPASS = True + _TESTS = [{ + 'url': 'https://www.southparkstudios.com.br/video-clips/3vifo0/south-park-welcome-to-mar-a-lago7', + 'info_dict': { + 'ext': 'mp4', + 'id': 'ccc3e952-7352-11f0-b405-16fff45bc035', + 'display_id': '3vifo0/south-park-welcome-to-mar-a-lago7', + 'title': 'Welcome to Mar-a-Lago', + 'description': 'The President welcomes Mr. Mackey to Mar-a-Lago, a magical place where anything can happen.', + 'duration': 139.223, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:', + 'series': 'South Park', + 'season': 'Season 27', + 'season_number': 27, + 'episode': 'Episode 2', + 'episode_number': 2, + 'timestamp': 1754546400, + 'upload_date': '20250807', + 'release_timestamp': 1754546400, + 'release_date': '20250807', + }, + 'params': {'skip_download': 'm3u8'}, + }, { + 'url': 'https://www.southparkstudios.com.br/episodios/940f8z/south-park-cartman-ganha-uma-sonda-anal-temporada-1-ep-1', + 'only_matching': True, + }, { + 'url': 'https://www.southparkstudios.com.br/collections/8dk7kr/south-park-best-of-south-park/sd5ean', + 'only_matching': True, + }, { + 'url': 'https://www.southparkstudios.com.br/en/episodes/5v0oap/south-park-south-park-the-25th-anniversary-concert-ep-1', + 'only_matching': True, + }] + + +class SouthParkCoUkIE(MTVServicesBaseIE): + IE_NAME = 'southparkstudios.co.uk' + _VALID_URL = r'https?://(?:www\.)?southparkstudios\.co\.uk/(?:video-clips|collections|episodes)/(?P[^?#]+)' + _GEO_COUNTRIES = ['UK'] + _GEO_BYPASS = True + _TESTS = [{ + 'url': 'https://www.southparkstudios.co.uk/video-clips/8kabfr/south-park-respectclydesauthority', + 'info_dict': { + 'ext': 'mp4', + 'id': 'f6d9af23-734e-11f0-b405-16fff45bc035', + 'display_id': '8kabfr/south-park-respectclydesauthority', + 'title': '#RespectClydesAuthority', + 'description': 'After learning about Clyde\'s Podcast, Cartman needs to see it for himself.', + 'duration': 45.045, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:', + 'series': 'South Park', + 'season': 'Season 27', + 'season_number': 27, + 'episode': 'Episode 2', + 'episode_number': 2, + 'timestamp': 1754546400, + 'upload_date': '20250807', + 'release_timestamp': 1754546400, + 'release_date': '20250807', + }, + 'params': {'skip_download': 'm3u8'}, + }, { + 'url': 'https://www.southparkstudios.co.uk/episodes/e1yoxn/south-park-imaginationland-season-11-ep-10', + 'only_matching': True, + }, { + 'url': 'https://www.southparkstudios.co.uk/collections/8dk7kr/south-park-best-of-south-park/sd5ean', 'only_matching': True, }] diff --git a/yt-dlp/yt_dlp/extractor/spike.py b/yt-dlp/yt_dlp/extractor/spike.py deleted file mode 100644 index 5c1c78d8fc..0000000000 --- a/yt-dlp/yt_dlp/extractor/spike.py +++ /dev/null @@ -1,46 +0,0 @@ -from .mtv import MTVServicesInfoExtractor - - -class BellatorIE(MTVServicesInfoExtractor): - _VALID_URL = r'https?://(?:www\.)?bellator\.com/[^/]+/[\da-z]{6}(?:[/?#&]|$)' - _TESTS = [{ - 'url': 'http://www.bellator.com/fight/atwr7k/bellator-158-michael-page-vs-evangelista-cyborg', - 'info_dict': { - 'title': 'Michael Page vs. Evangelista Cyborg', - 'description': 'md5:0d917fc00ffd72dd92814963fc6cbb05', - }, - 'playlist_count': 3, - }, { - 'url': 'http://www.bellator.com/video-clips/bw6k7n/bellator-158-foundations-michael-venom-page', - 'only_matching': True, - }] - - _FEED_URL = 'http://www.bellator.com/feeds/mrss/' - _GEO_COUNTRIES = ['US'] - - -class ParamountNetworkIE(MTVServicesInfoExtractor): - _VALID_URL = r'https?://(?:www\.)?paramountnetwork\.com/[^/]+/[\da-z]{6}(?:[/?#&]|$)' - _TESTS = [{ - 'url': 'http://www.paramountnetwork.com/episodes/j830qm/lip-sync-battle-joel-mchale-vs-jim-rash-season-2-ep-13', - 'info_dict': { - 'id': '37ace3a8-1df6-48be-85b8-38df8229e241', - 'ext': 'mp4', - 'title': 'Lip Sync Battle|April 28, 2016|2|209|Joel McHale Vs. Jim Rash|Act 1', - 'description': 'md5:a739ca8f978a7802f67f8016d27ce114', - }, - 'params': { - # m3u8 download - 'skip_download': True, - }, - }] - - _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed' - _GEO_COUNTRIES = ['US'] - - def _get_feed_query(self, uri): - return { - 'arcEp': 'paramountnetwork.com', - 'imageEp': 'paramountnetwork.com', - 'mgid': uri, - } diff --git a/yt-dlp/yt_dlp/extractor/steam.py b/yt-dlp/yt_dlp/extractor/steam.py index b7f8ac3ae7..71d94815e7 100644 --- a/yt-dlp/yt_dlp/extractor/steam.py +++ b/yt-dlp/yt_dlp/extractor/steam.py @@ -1,142 +1,175 @@ -import re +import json from .common import InfoExtractor +from .youtube import YoutubeIE from ..utils import ( ExtractorError, + clean_html, extract_attributes, - get_element_by_class, + join_nonempty, + js_to_json, str_or_none, + url_or_none, +) +from ..utils.traversal import ( + find_element, + find_elements, + traverse_obj, + trim_str, ) class SteamIE(InfoExtractor): - _VALID_URL = r'''(?x) - https?://(?:store\.steampowered|steamcommunity)\.com/ - (?:agecheck/)? - (?Pvideo|app)/ #If the page is only for videos or for a game - (?P\d+)/? - (?P\d*)(?P\??) # For urltype == video we sometimes get the videoID - | - https?://(?:www\.)?steamcommunity\.com/sharedfiles/filedetails/\?id=(?P[0-9]+) - ''' - _VIDEO_PAGE_TEMPLATE = 'http://store.steampowered.com/video/%s/' - _AGECHECK_TEMPLATE = 'http://store.steampowered.com/agecheck/video/%s/?snr=1_agecheck_agecheck__age-gate&ageDay=1&ageMonth=January&ageYear=1970' + _VALID_URL = r'https?://store\.steampowered\.com(?:/agecheck)?/app/(?P\d+)/?(?:[^?/#]+/?)?(?:[?#]|$)' _TESTS = [{ - 'url': 'http://store.steampowered.com/video/105600/', - 'playlist': [ - { - 'md5': '695242613303ffa2a4c44c9374ddc067', - 'info_dict': { - 'id': '256785003', - 'ext': 'mp4', - 'title': 'Terraria video 256785003', - 'thumbnail': r're:^https://cdn\.[^\.]+\.steamstatic\.com', - }, - }, - { - 'md5': '6a294ee0c4b1f47f5bb76a65e31e3592', - 'info_dict': { - 'id': '2040428', - 'ext': 'mp4', - 'title': 'Terraria video 2040428', - 'thumbnail': r're:^https://cdn\.[^\.]+\.steamstatic\.com', - }, - }, - ], + 'url': 'https://store.steampowered.com/app/105600', 'info_dict': { 'id': '105600', 'title': 'Terraria', }, - 'params': { - 'playlistend': 2, - }, + 'playlist_mincount': 3, }, { 'url': 'https://store.steampowered.com/app/271590/Grand_Theft_Auto_V/', 'info_dict': { 'id': '271590', - 'title': 'Grand Theft Auto V', + 'title': 'Grand Theft Auto V Legacy', }, - 'playlist_count': 23, + 'playlist_mincount': 26, }] def _real_extract(self, url): - m = self._match_valid_url(url) - file_id = m.group('fileID') - if file_id: - video_url = url - playlist_id = file_id - else: - game_id = m.group('gameID') - playlist_id = game_id - video_url = self._VIDEO_PAGE_TEMPLATE % playlist_id + app_id = self._match_id(url) - self._set_cookie('steampowered.com', 'wants_mature_content', '1') - self._set_cookie('steampowered.com', 'birthtime', '944006401') - self._set_cookie('steampowered.com', 'lastagecheckage', '1-0-2000') + self._set_cookie('store.steampowered.com', 'wants_mature_content', '1') + self._set_cookie('store.steampowered.com', 'birthtime', '946652401') + self._set_cookie('store.steampowered.com', 'lastagecheckage', '1-January-2000') - webpage = self._download_webpage(video_url, playlist_id) + webpage = self._download_webpage(url, app_id) + app_name = traverse_obj(webpage, ({find_element(cls='apphub_AppName')}, {clean_html})) - if re.search(']+>Please enter your birth date to continue:', webpage) is not None: - video_url = self._AGECHECK_TEMPLATE % playlist_id - self.report_age_confirmation() - webpage = self._download_webpage(video_url, playlist_id) - - videos = re.findall(r'(]+id=[\'"]highlight_movie_(\d+)[\'"][^>]+>)', webpage) entries = [] - playlist_title = get_element_by_class('apphub_AppName', webpage) - for movie, movie_id in videos: - if not movie: - continue - movie = extract_attributes(movie) - if not movie_id: - continue - entry = { - 'id': movie_id, - 'title': f'{playlist_title} video {movie_id}', - } + for data_prop in traverse_obj(webpage, ( + {find_elements(cls='highlight_player_item highlight_movie', html=True)}, + ..., {extract_attributes}, 'data-props', {json.loads}, {dict}, + )): formats = [] - if movie: - entry['thumbnail'] = movie.get('data-poster') - for quality in ('', '-hd'): - for ext in ('webm', 'mp4'): - video_url = movie.get(f'data-{ext}{quality}-source') - if video_url: - formats.append({ - 'format_id': ext + quality, - 'url': video_url, - }) - entry['formats'] = formats - entries.append(entry) - embedded_videos = re.findall(r'(]+>)', webpage) - for evideos in embedded_videos: - evideos = extract_attributes(evideos).get('src') - video_id = self._search_regex(r'youtube\.com/embed/([0-9A-Za-z_-]{11})', evideos, 'youtube_video_id', default=None) - if video_id: - entries.append({ - '_type': 'url_transparent', - 'id': video_id, - 'url': video_id, - 'ie_key': 'Youtube', - }) - if not entries: - raise ExtractorError('Could not find any videos') + if hls_manifest := traverse_obj(data_prop, ('hlsManifest', {url_or_none})): + formats.extend(self._extract_m3u8_formats( + hls_manifest, app_id, 'mp4', m3u8_id='hls', fatal=False)) + for dash_manifest in traverse_obj(data_prop, ('dashManifests', ..., {url_or_none})): + formats.extend(self._extract_mpd_formats( + dash_manifest, app_id, mpd_id='dash', fatal=False)) - return self.playlist_result(entries, playlist_id, playlist_title) + movie_id = traverse_obj(data_prop, ('id', {trim_str(start='highlight_movie_')})) + entries.append({ + 'id': movie_id, + 'title': join_nonempty(app_name, 'video', movie_id, delim=' '), + 'formats': formats, + 'series': app_name, + 'series_id': app_id, + 'thumbnail': traverse_obj(data_prop, ('screenshot', {url_or_none})), + }) + + return self.playlist_result(entries, app_id, app_name) + + +class SteamCommunityIE(InfoExtractor): + _VALID_URL = r'https?://(?:www\.)?steamcommunity\.com/sharedfiles/filedetails(?:/?\?(?:[^#]+&)?id=|/)(?P\d+)' + _TESTS = [{ + 'url': 'https://steamcommunity.com/sharedfiles/filedetails/2717708756', + 'info_dict': { + 'id': '39Sp2mB1Ly8', + 'ext': 'mp4', + 'title': 'Gmod Stamina System + Customisable HUD', + 'age_limit': 0, + 'availability': 'public', + 'categories': ['Gaming'], + 'channel': 'Zworld Gmod', + 'channel_follower_count': int, + 'channel_id': 'UCER1FWFSdMMiTKBnnEDBPaw', + 'channel_url': 'https://www.youtube.com/channel/UCER1FWFSdMMiTKBnnEDBPaw', + 'chapters': 'count:3', + 'comment_count': int, + 'description': 'md5:0ba8d8e550231211fa03fac920e5b0bf', + 'duration': 162, + 'like_count': int, + 'live_status': 'not_live', + 'media_type': 'video', + 'playable_in_embed': True, + 'tags': 'count:20', + 'thumbnail': r're:https?://i\.ytimg\.com/vi/.+', + 'timestamp': 1641955348, + 'upload_date': '20220112', + 'uploader': 'Zworld Gmod', + 'uploader_id': '@gmod-addons', + 'uploader_url': 'https://www.youtube.com/@gmod-addons', + 'view_count': int, + }, + 'add_ie': ['Youtube'], + 'params': {'skip_download': 'm3u8'}, + }, { + 'url': 'https://steamcommunity.com/sharedfiles/filedetails/?id=3544291945', + 'info_dict': { + 'id': '5JZZlsAdsvI', + 'ext': 'mp4', + 'title': 'Memories', + 'age_limit': 0, + 'availability': 'public', + 'categories': ['Gaming'], + 'channel': 'Bombass Team', + 'channel_follower_count': int, + 'channel_id': 'UCIJgtNyCV53IeSkzg3FWSFA', + 'channel_url': 'https://www.youtube.com/channel/UCIJgtNyCV53IeSkzg3FWSFA', + 'comment_count': int, + 'description': 'md5:1b8a103a5d67a3c48d07c065de7e2c63', + 'duration': 83, + 'like_count': int, + 'live_status': 'not_live', + 'media_type': 'video', + 'playable_in_embed': True, + 'tags': 'count:10', + 'thumbnail': r're:https?://i\.ytimg\.com/vi/.+', + 'timestamp': 1754427291, + 'upload_date': '20250805', + 'uploader': 'Bombass Team', + 'uploader_id': '@BombassTeam', + 'uploader_url': 'https://www.youtube.com/@BombassTeam', + 'view_count': int, + }, + 'add_ie': ['Youtube'], + 'params': {'skip_download': 'm3u8'}, + }] + + def _real_extract(self, url): + file_id = self._match_id(url) + webpage = self._download_webpage(url, file_id) + + flashvars = self._search_json( + r'var\s+rgMovieFlashvars\s*=', webpage, 'flashvars', + file_id, default={}, transform_source=js_to_json) + youtube_id = ( + traverse_obj(flashvars, (..., 'YOUTUBE_VIDEO_ID', {str}, any)) + or traverse_obj(webpage, ( + {find_element(cls='movieFrame modal', html=True)}, {extract_attributes}, 'id', {str}))) + if not youtube_id: + raise ExtractorError('No video found', expected=True) + + return self.url_result(youtube_id, YoutubeIE) class SteamCommunityBroadcastIE(InfoExtractor): - _VALID_URL = r'https?://steamcommunity\.(?:com)/broadcast/watch/(?P\d+)' + _VALID_URL = r'https?://(?:www\.)?steamcommunity\.com/broadcast/watch/(?P\d+)' _TESTS = [{ 'url': 'https://steamcommunity.com/broadcast/watch/76561199073851486', 'info_dict': { 'id': '76561199073851486', - 'title': r're:Steam Community :: pepperm!nt :: Broadcast 2022-06-26 \d{2}:\d{2}', 'ext': 'mp4', + 'title': str, 'uploader_id': '1113585758', 'uploader': 'pepperm!nt', 'live_status': 'is_live', }, - 'skip': 'Stream has ended', + 'params': {'skip_download': 'Livestream'}, }] def _real_extract(self, url): diff --git a/yt-dlp/yt_dlp/extractor/svt.py b/yt-dlp/yt_dlp/extractor/svt.py index a48d7858d4..da82691ca2 100644 --- a/yt-dlp/yt_dlp/extractor/svt.py +++ b/yt-dlp/yt_dlp/extractor/svt.py @@ -88,7 +88,7 @@ class SVTBaseIE(InfoExtractor): 'id': video_id, 'title': title, 'formats': formats, - 'subtitles': subtitles, + 'subtitles': self._fixup_subtitles(subtitles), 'duration': duration, 'timestamp': timestamp, 'age_limit': age_limit, @@ -99,6 +99,16 @@ class SVTBaseIE(InfoExtractor): 'is_live': is_live, } + @staticmethod + def _fixup_subtitles(subtitles): + # See: https://github.com/yt-dlp/yt-dlp/issues/14020 + fixed_subtitles = {} + for lang, subs in subtitles.items(): + for sub in subs: + fixed_lang = f'{lang}-forced' if 'text-open' in sub['url'] else lang + fixed_subtitles.setdefault(fixed_lang, []).append(sub) + return fixed_subtitles + class SVTPlayIE(SVTBaseIE): IE_NAME = 'svt:play' @@ -115,6 +125,26 @@ class SVTPlayIE(SVTBaseIE): ) ''' _TESTS = [{ + 'url': 'https://www.svtplay.se/video/eXYgwZb/sverige-och-kriget/1-utbrottet', + 'md5': '2382036fd6f8c994856c323fe51c426e', + 'info_dict': { + 'id': 'ePBvGRq', + 'ext': 'mp4', + 'title': '1. Utbrottet', + 'description': 'md5:02291cc3159dbc9aa95d564e77a8a92b', + 'series': 'Sverige och kriget', + 'episode': '1. Utbrottet', + 'timestamp': 1746921600, + 'upload_date': '20250511', + 'duration': 3585, + 'thumbnail': r're:^https?://(?:.*[\.-]jpg|www.svtstatic.se/image/.*)$', + 'age_limit': 0, + 'subtitles': {'sv': 'count:3', 'sv-forced': 'count:3'}, + }, + 'params': { + 'skip_download': 'm3u8', + }, + }, { 'url': 'https://www.svtplay.se/video/30479064', 'md5': '2382036fd6f8c994856c323fe51c426e', 'info_dict': { diff --git a/yt-dlp/yt_dlp/extractor/tiktok.py b/yt-dlp/yt_dlp/extractor/tiktok.py index 18407a0820..025e7a55d8 100644 --- a/yt-dlp/yt_dlp/extractor/tiktok.py +++ b/yt-dlp/yt_dlp/extractor/tiktok.py @@ -921,6 +921,8 @@ class TikTokUserIE(TikTokBaseIE): 'id': 'MS4wLjABAAAAepiJKgwWhulvCpSuUVsp7sgVVsFJbbNaLeQ6OQ0oAJERGDUIXhb2yxxHZedsItgT', 'title': 'corgibobaa', }, + 'expected_warnings': ['TikTok API keeps sending the same page'], + 'params': {'extractor_retries': 10}, }, { 'url': 'https://www.tiktok.com/@6820838815978423302', 'playlist_mincount': 5, @@ -928,6 +930,8 @@ class TikTokUserIE(TikTokBaseIE): 'id': 'MS4wLjABAAAA0tF1nBwQVVMyrGu3CqttkNgM68Do1OXUFuCY0CRQk8fEtSVDj89HqoqvbSTmUP2W', 'title': '6820838815978423302', }, + 'expected_warnings': ['TikTok API keeps sending the same page'], + 'params': {'extractor_retries': 10}, }, { 'url': 'https://www.tiktok.com/@meme', 'playlist_mincount': 593, @@ -935,12 +939,16 @@ class TikTokUserIE(TikTokBaseIE): 'id': 'MS4wLjABAAAAiKfaDWeCsT3IHwY77zqWGtVRIy9v4ws1HbVi7auP1Vx7dJysU_hc5yRiGywojRD6', 'title': 'meme', }, + 'expected_warnings': ['TikTok API keeps sending the same page'], + 'params': {'extractor_retries': 10}, }, { 'url': 'tiktokuser:MS4wLjABAAAAM3R2BtjzVT-uAtstkl2iugMzC6AtnpkojJbjiOdDDrdsTiTR75-8lyWJCY5VvDrZ', 'playlist_mincount': 31, 'info_dict': { 'id': 'MS4wLjABAAAAM3R2BtjzVT-uAtstkl2iugMzC6AtnpkojJbjiOdDDrdsTiTR75-8lyWJCY5VvDrZ', }, + 'expected_warnings': ['TikTok API keeps sending the same page'], + 'params': {'extractor_retries': 10}, }] _API_BASE_URL = 'https://www.tiktok.com/api/creator/item_list/' @@ -979,7 +987,7 @@ class TikTokUserIE(TikTokBaseIE): 'webcast_language': 'en', } - def _entries(self, sec_uid, user_name): + def _entries(self, sec_uid, user_name, fail_early=False): display_id = user_name or sec_uid seen_ids = set() @@ -1023,10 +1031,13 @@ class TikTokUserIE(TikTokBaseIE): if cursor < 1472706000000 or not traverse_obj(response, 'hasMorePrevious'): return - # User directly passed sec_uid via prefix URL, bypassing our private account detection - if not user_name and not seen_ids: + # This code path is ideally only reached when one of the following is true: + # 1. TikTok profile is private and webpage detection was bypassed due to a tiktokuser:sec_uid URL + # 2. TikTok profile is *not* private but all of their videos are private + if fail_early and not seen_ids: self.raise_login_required( - 'This user\'s account is likely private. Log into an account that has access') + 'This user\'s account is likely either private or all of their videos are private. ' + 'Log into an account that has access') def _extract_sec_uid_from_embed(self, user_name): webpage = self._download_webpage( @@ -1053,8 +1064,9 @@ class TikTokUserIE(TikTokBaseIE): def _real_extract(self, url): user_name, sec_uid = self._match_id(url), None - if mobj := re.fullmatch(r'MS4wLjABAAAA[\w-]{64}', user_name): - user_name, sec_uid = None, mobj.group(0) + if re.fullmatch(r'MS4wLjABAAAA[\w-]{64}', user_name): + user_name, sec_uid = None, user_name + fail_early = True else: webpage = self._download_webpage( self._UPLOADER_URL_FORMAT % user_name, user_name, @@ -1065,8 +1077,12 @@ class TikTokUserIE(TikTokBaseIE): if detail.get('statusCode') == 10222: self.raise_login_required( 'This user\'s account is private. Log into an account that has access') - sec_uid = traverse_obj(detail, ( - 'userInfo', 'user', 'secUid', {str})) or self._extract_sec_uid_from_embed(user_name) + sec_uid = traverse_obj(detail, ('userInfo', 'user', 'secUid', {str})) + if sec_uid: + fail_early = not traverse_obj(detail, ('userInfo', 'itemList', ...)) + else: + sec_uid = self._extract_sec_uid_from_embed(user_name) + fail_early = False if not sec_uid: raise ExtractorError( @@ -1074,7 +1090,7 @@ class TikTokUserIE(TikTokBaseIE): 'from a video posted by this user, try using "tiktokuser:channel_id" as the ' 'input URL (replacing `channel_id` with its actual value)', expected=True) - return self.playlist_result(self._entries(sec_uid, user_name), sec_uid, user_name) + return self.playlist_result(self._entries(sec_uid, user_name, fail_early), sec_uid, user_name) class TikTokBaseListIE(TikTokBaseIE): # XXX: Conventionally, base classes should end with BaseIE/InfoExtractor diff --git a/yt-dlp/yt_dlp/extractor/tvland.py b/yt-dlp/yt_dlp/extractor/tvland.py deleted file mode 100644 index 481d5eb19e..0000000000 --- a/yt-dlp/yt_dlp/extractor/tvland.py +++ /dev/null @@ -1,37 +0,0 @@ -from .mtv import MTVServicesInfoExtractor - -# TODO: Remove - Reason not used anymore - Service moved to youtube - - -class TVLandIE(MTVServicesInfoExtractor): - IE_NAME = 'tvland.com' - _VALID_URL = r'https?://(?:www\.)?tvland\.com/(?:video-clips|(?:full-)?episodes)/(?P[^/?#.]+)' - _FEED_URL = 'http://www.tvland.com/feeds/mrss/' - _TESTS = [{ - # Geo-restricted. Without a proxy metadata are still there. With a - # proxy it redirects to http://m.tvland.com/app/ - 'url': 'https://www.tvland.com/episodes/s04pzf/everybody-loves-raymond-the-dog-season-1-ep-19', - 'info_dict': { - 'description': 'md5:84928e7a8ad6649371fbf5da5e1ad75a', - 'title': 'The Dog', - }, - 'playlist_mincount': 5, - 'skip': '404 Not found', - }, { - 'url': 'https://www.tvland.com/video-clips/4n87f2/younger-a-first-look-at-younger-season-6', - 'md5': 'e2c6389401cf485df26c79c247b08713', - 'info_dict': { - 'id': '891f7d3c-5b5b-4753-b879-b7ba1a601757', - 'ext': 'mp4', - 'title': 'Younger|April 30, 2019|6|NO-EPISODE#|A First Look at Younger Season 6', - 'description': 'md5:595ea74578d3a888ae878dfd1c7d4ab2', - 'upload_date': '20190430', - 'timestamp': 1556658000, - }, - 'params': { - 'skip_download': True, - }, - }, { - 'url': 'http://www.tvland.com/full-episodes/iu0hz6/younger-a-kiss-is-just-a-kiss-season-3-ep-301', - 'only_matching': True, - }] diff --git a/yt-dlp/yt_dlp/extractor/vh1.py b/yt-dlp/yt_dlp/extractor/vh1.py index 53d5a7108e..4a462ce7cb 100644 --- a/yt-dlp/yt_dlp/extractor/vh1.py +++ b/yt-dlp/yt_dlp/extractor/vh1.py @@ -1,33 +1,50 @@ -from .mtv import MTVServicesInfoExtractor - -# TODO: Remove - Reason: Outdated Site +from .mtv import MTVServicesBaseIE -class VH1IE(MTVServicesInfoExtractor): +class VH1IE(MTVServicesBaseIE): IE_NAME = 'vh1.com' - _FEED_URL = 'http://www.vh1.com/feeds/mrss/' + _VALID_URL = r'https?://(?:www\.)?vh1\.com/(?:video-clips|episodes)/(?P[\da-z]{6})' _TESTS = [{ - 'url': 'https://www.vh1.com/episodes/0aqivv/nick-cannon-presents-wild-n-out-foushee-season-16-ep-12', + 'url': 'https://www.vh1.com/episodes/d06ta1/barely-famous-barely-famous-season-1-ep-1', 'info_dict': { - 'title': 'Fousheé', - 'description': 'Fousheé joins Team Evolutions fight against Nick and Team Revolution in Baby Daddy, Baby Mama; Kick Em Out the Classroom; Backseat of My Ride and Wildstyle; and Fousheé performs.', - }, - 'playlist_mincount': 4, - 'skip': '404 Not found', - }, { - # Clip - 'url': 'https://www.vh1.com/video-clips/e0sja0/nick-cannon-presents-wild-n-out-foushee-clap-for-him', - 'info_dict': { - 'id': 'a07563f7-a37b-4e7f-af68-85855c2c7cc3', + 'id': '4af4cf2c-a854-11e4-9596-0026b9414f30', 'ext': 'mp4', - 'title': 'Fousheé - "clap for him"', - 'description': 'Singer Fousheé hits the Wild N Out: In the Dark stage with a performance of the tongue-in-cheek track "clap for him" from her 2021 album "time machine."', - 'upload_date': '20210826', + 'display_id': 'd06ta1', + 'title': 'Barely Famous', + 'description': 'md5:6da5c9d88012eba0a80fc731c99b5fed', + 'channel': 'VH1', + 'duration': 1280.0, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:', + 'series': 'Barely Famous', + 'season': 'Season 1', + 'season_number': 1, + 'episode': 'Episode 1', + 'episode_number': 1, + 'timestamp': 1426680000, + 'upload_date': '20150318', + 'release_timestamp': 1426680000, + 'release_date': '20150318', }, - 'params': { - # m3u8 download - 'skip_download': True, + 'params': {'skip_download': 'm3u8'}, + }, { + 'url': 'https://www.vh1.com/video-clips/ryzt2n/love-hip-hop-miami-love-hip-hop-miami-season-5-recap', + 'info_dict': { + 'id': '59e62974-4a5c-4417-91c3-5044cb2f4ce2', + 'ext': 'mp4', + 'display_id': 'ryzt2n', + 'title': 'Love & Hip Hop Miami - Season 5 Recap', + 'description': 'md5:4e49c65d0007bfc8d06db555a6b76ef0', + 'duration': 792.083, + 'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:', + 'series': 'Love & Hip Hop Miami', + 'season': 'Season 6', + 'season_number': 6, + 'episode': 'Episode 0', + 'episode_number': 0, + 'timestamp': 1732597200, + 'upload_date': '20241126', + 'release_timestamp': 1732597200, + 'release_date': '20241126', }, + 'params': {'skip_download': 'm3u8'}, }] - - _VALID_URL = r'https?://(?:www\.)?vh1\.com/(?:video-clips|episodes)/(?P[^/?#.]+)' diff --git a/yt-dlp/yt_dlp/extractor/vrt.py b/yt-dlp/yt_dlp/extractor/vrt.py index 6e5514eefd..079feab454 100644 --- a/yt-dlp/yt_dlp/extractor/vrt.py +++ b/yt-dlp/yt_dlp/extractor/vrt.py @@ -14,7 +14,7 @@ from ..utils import ( get_element_html_by_class, int_or_none, jwt_decode_hs256, - jwt_encode_hs256, + jwt_encode, make_archive_id, merge_dicts, parse_age_limit, @@ -98,9 +98,9 @@ class VRTBaseIE(InfoExtractor): 'Content-Type': 'application/json', }, data=json.dumps({ 'identityToken': id_token or '', - 'playerInfo': jwt_encode_hs256(player_info, self._JWT_SIGNING_KEY, headers={ + 'playerInfo': jwt_encode(player_info, self._JWT_SIGNING_KEY, headers={ 'kid': self._JWT_KEY_ID, - }).decode(), + }), }, separators=(',', ':')).encode())['vrtPlayerToken'] return self._download_json( diff --git a/yt-dlp/yt_dlp/extractor/vtm.py b/yt-dlp/yt_dlp/extractor/vtm.py index 41b41ec171..031ec3fbb8 100644 --- a/yt-dlp/yt_dlp/extractor/vtm.py +++ b/yt-dlp/yt_dlp/extractor/vtm.py @@ -1,60 +1,42 @@ -from .common import InfoExtractor -from ..utils import ( - int_or_none, - parse_iso8601, - try_get, -) +from .medialaan import MedialaanBaseIE +from ..utils import str_or_none +from ..utils.traversal import require, traverse_obj -class VTMIE(InfoExtractor): - _WORKING = False - _VALID_URL = r'https?://(?:www\.)?vtm\.be/([^/?&#]+)~v(?P[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})' - _TEST = { +class VTMIE(MedialaanBaseIE): + _VALID_URL = r'https?://(?:www\.)?vtm\.be/[^/?#]+~v(?P[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12})' + _TESTS = [{ 'url': 'https://vtm.be/gast-vernielt-genkse-hotelkamer~ve7534523-279f-4b4d-a5c9-a33ffdbe23e1', - 'md5': '37dca85fbc3a33f2de28ceb834b071f8', 'info_dict': { 'id': '192445', 'ext': 'mp4', 'title': 'Gast vernielt Genkse hotelkamer', - 'timestamp': 1611060180, - 'upload_date': '20210119', + 'channel': 'VTM', + 'channel_id': '867', + 'description': 'md5:75fce957d219646ff1b65ba449ab97b5', 'duration': 74, - # TODO: fix url _type result processing - # 'series': 'Op Interventie', + 'genres': ['Documentaries'], + 'release_date': '20210119', + 'release_timestamp': 1611060180, + 'series': 'Op Interventie', + 'series_id': '2658', + 'tags': 'count:2', + 'thumbnail': r're:https?://images\.mychannels\.video/imgix/.+\.(?:jpe?g|png)', + 'uploader': 'VTM', + 'uploader_id': '74', }, - } + }] + + def _real_initialize(self): + if not self._get_cookies('https://vtm.be/').get('authId'): + self.raise_login_required() def _real_extract(self, url): - uuid = self._match_id(url) - video = self._download_json( - 'https://omc4vm23offuhaxx6hekxtzspi.appsync-api.eu-west-1.amazonaws.com/graphql', - uuid, query={ - 'query': '''{ - getComponent(type: Video, uuid: "%s") { - ... on Video { - description - duration - myChannelsVideo - program { - title - } - publishedAt - title - } - } -}''' % uuid, # noqa: UP031 - }, headers={ - 'x-api-key': 'da2-lz2cab4tfnah3mve6wiye4n77e', - })['data']['getComponent'] + video_id = self._match_id(url) + webpage = self._download_webpage(url, video_id) + apollo_state = self._search_json( + r'window\.__APOLLO_STATE__\s*=', webpage, 'apollo state', video_id) + mychannels_id = traverse_obj(apollo_state, ( + f'Video:{{"uuid":"{video_id}"}}', 'myChannelsVideo', {str_or_none}, {require('mychannels ID')})) - return { - '_type': 'url', - 'id': uuid, - 'title': video.get('title'), - 'url': 'http://mychannels.video/embed/%d' % video['myChannelsVideo'], - 'description': video.get('description'), - 'timestamp': parse_iso8601(video.get('publishedAt')), - 'duration': int_or_none(video.get('duration')), - 'series': try_get(video, lambda x: x['program']['title']), - 'ie_key': 'Medialaan', - } + return self._extract_from_mychannels_api(mychannels_id) diff --git a/yt-dlp/yt_dlp/extractor/youtube/_base.py b/yt-dlp/yt_dlp/extractor/youtube/_base.py index d28550794a..64980650ec 100644 --- a/yt-dlp/yt_dlp/extractor/youtube/_base.py +++ b/yt-dlp/yt_dlp/extractor/youtube/_base.py @@ -105,7 +105,6 @@ INNERTUBE_CLIENTS = { 'INNERTUBE_CONTEXT_CLIENT_NAME': 1, 'SUPPORTS_COOKIES': True, **WEB_PO_TOKEN_POLICIES, - 'PLAYER_PARAMS': '8AEB2AMB', }, # Safari UA returns pre-merged video+audio 144p/240p/360p/720p/1080p HLS formats 'web_safari': { @@ -119,7 +118,6 @@ INNERTUBE_CLIENTS = { 'INNERTUBE_CONTEXT_CLIENT_NAME': 1, 'SUPPORTS_COOKIES': True, **WEB_PO_TOKEN_POLICIES, - 'PLAYER_PARAMS': '8AEB2AMB', }, 'web_embedded': { 'INNERTUBE_CONTEXT': { @@ -282,7 +280,6 @@ INNERTUBE_CLIENTS = { 'userAgent': 'Mozilla/5.0 (iPad; CPU OS 16_7_10 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1,gzip(gfe)', }, }, - 'PLAYER_PARAMS': '8AEB2AMB', 'INNERTUBE_CONTEXT_CLIENT_NAME': 2, 'GVS_PO_TOKEN_POLICY': { StreamingProtocol.HTTPS: GvsPoTokenPolicy( @@ -314,7 +311,6 @@ INNERTUBE_CLIENTS = { }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 7, 'SUPPORTS_COOKIES': True, - 'PLAYER_PARAMS': '8AEB2AMB', }, 'tv_simply': { 'INNERTUBE_CONTEXT': { diff --git a/yt-dlp/yt_dlp/extractor/youtube/_video.py b/yt-dlp/yt_dlp/extractor/youtube/_video.py index 14582c5f98..2bf6f01a90 100644 --- a/yt-dlp/yt_dlp/extractor/youtube/_video.py +++ b/yt-dlp/yt_dlp/extractor/youtube/_video.py @@ -2020,7 +2020,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): if not player_url: return - requested_js_variant = self._configuration_arg('player_js_variant', [''])[0] or 'actual' + requested_js_variant = self._configuration_arg('player_js_variant', [''])[0] or 'main' if requested_js_variant in self._PLAYER_JS_VARIANT_MAP: player_id = self._extract_player_info(player_url) original_url = player_url @@ -3850,11 +3850,33 @@ class YoutubeIE(YoutubeBaseInfoExtractor): player_responses, (..., 'microformat', 'playerMicroformatRenderer'), expected_type=dict) + # Fallbacks in case player responses are missing metadata + initial_sdcr = traverse_obj(initial_data, ( + 'engagementPanels', ..., 'engagementPanelSectionListRenderer', + 'content', 'structuredDescriptionContentRenderer', {dict}, any)) + initial_description = traverse_obj(initial_sdcr, ( + 'items', ..., 'expandableVideoDescriptionBodyRenderer', + 'attributedDescriptionBodyText', 'content', {str}, any)) + # videoDescriptionHeaderRenderer also has publishDate/channel/handle/ucid, but not needed + initial_vdhr = traverse_obj(initial_sdcr, ( + 'items', ..., 'videoDescriptionHeaderRenderer', {dict}, any)) or {} + initial_video_details_renderer = traverse_obj(initial_data, ( + 'playerOverlays', 'playerOverlayRenderer', 'videoDetails', + 'playerOverlayVideoDetailsRenderer', {dict})) or {} + initial_title = ( + self._get_text(initial_vdhr, 'title') + or self._get_text(initial_video_details_renderer, 'title')) + translated_title = self._get_text(microformats, (..., 'title')) video_title = ((self._preferred_lang and translated_title) or get_first(video_details, 'title') # primary or translated_title or search_meta(['og:title', 'twitter:title', 'title'])) + if not video_title and initial_title: + self.report_warning( + 'No title found in player responses; falling back to title from initial data. ' + 'Other metadata may also be missing') + video_title = initial_title translated_description = self._get_text(microformats, (..., 'description')) original_description = get_first(video_details, 'shortDescription') video_description = ( @@ -3862,6 +3884,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor): # If original description is blank, it will be an empty string. # Do not prefer translated description in this case. or original_description if original_description is not None else translated_description) + if video_description is None: + video_description = initial_description multifeed_metadata_list = get_first( player_responses, @@ -4020,6 +4044,12 @@ class YoutubeIE(YoutubeBaseInfoExtractor): if needs_live_processing: self._prepare_live_from_start_formats( formats, video_id, live_start_time, url, webpage_url, smuggled_data, live_status == 'is_live') + elif live_status != 'is_live': + # Handle pre-playback waiting period + playback_wait = int_or_none(self._configuration_arg('playback_wait', [None])[0], default=6) + available_at = int(time.time()) + playback_wait + for fmt in formats: + fmt['available_at'] = available_at formats.extend(self._extract_storyboard(player_responses, duration)) diff --git a/yt-dlp/yt_dlp/update.py b/yt-dlp/yt_dlp/update.py index dd948cd521..a17a806432 100644 --- a/yt-dlp/yt_dlp/update.py +++ b/yt-dlp/yt_dlp/update.py @@ -114,6 +114,7 @@ _FILE_SUFFIXES = { 'zip': '', 'win_exe': '.exe', 'win_x86_exe': '_x86.exe', + 'win_arm64_exe': '_arm64.exe', 'darwin_exe': '_macos', 'linux_exe': '_linux', 'linux_aarch64_exe': '_linux_aarch64', diff --git a/yt-dlp/yt_dlp/utils/_deprecated.py b/yt-dlp/yt_dlp/utils/_deprecated.py index e4762699b7..4797cfef53 100644 --- a/yt-dlp/yt_dlp/utils/_deprecated.py +++ b/yt-dlp/yt_dlp/utils/_deprecated.py @@ -1,4 +1,8 @@ """Deprecated - New code should avoid these""" +import base64 +import hashlib +import hmac +import json import warnings from ..compat.compat_utils import passthrough_module @@ -28,4 +32,18 @@ def intlist_to_bytes(xs): return struct.pack('%dB' % len(xs), *xs) +def jwt_encode_hs256(payload_data, key, headers={}): + header_data = { + 'alg': 'HS256', + 'typ': 'JWT', + } + if headers: + header_data.update(headers) + header_b64 = base64.b64encode(json.dumps(header_data).encode()) + payload_b64 = base64.b64encode(json.dumps(payload_data).encode()) + h = hmac.new(key.encode(), header_b64 + b'.' + payload_b64, hashlib.sha256) + signature_b64 = base64.b64encode(h.digest()) + return header_b64 + b'.' + payload_b64 + b'.' + signature_b64 + + compiled_regex_type = type(re.compile('')) diff --git a/yt-dlp/yt_dlp/utils/_utils.py b/yt-dlp/yt_dlp/utils/_utils.py index a5471da4df..3adc1d6be2 100644 --- a/yt-dlp/yt_dlp/utils/_utils.py +++ b/yt-dlp/yt_dlp/utils/_utils.py @@ -4739,22 +4739,36 @@ def time_seconds(**kwargs): return time.time() + dt.timedelta(**kwargs).total_seconds() -# create a JSON Web Signature (jws) with HS256 algorithm -# the resulting format is in JWS Compact Serialization # implemented following JWT https://www.rfc-editor.org/rfc/rfc7519.html # implemented following JWS https://www.rfc-editor.org/rfc/rfc7515.html -def jwt_encode_hs256(payload_data, key, headers={}): +def jwt_encode(payload_data, key, *, alg='HS256', headers=None): + assert alg in ('HS256',), f'Unsupported algorithm "{alg}"' + + def jwt_json_bytes(obj): + return json.dumps(obj, separators=(',', ':')).encode() + + def jwt_b64encode(bytestring): + return base64.urlsafe_b64encode(bytestring).rstrip(b'=') + header_data = { - 'alg': 'HS256', + 'alg': alg, 'typ': 'JWT', } if headers: - header_data.update(headers) - header_b64 = base64.b64encode(json.dumps(header_data).encode()) - payload_b64 = base64.b64encode(json.dumps(payload_data).encode()) + # Allow re-ordering of keys if both 'alg' and 'typ' are present + if 'alg' in headers and 'typ' in headers: + header_data = headers + else: + header_data.update(headers) + + header_b64 = jwt_b64encode(jwt_json_bytes(header_data)) + payload_b64 = jwt_b64encode(jwt_json_bytes(payload_data)) + + # HS256 is the only algorithm currently supported h = hmac.new(key.encode(), header_b64 + b'.' + payload_b64, hashlib.sha256) - signature_b64 = base64.b64encode(h.digest()) - return header_b64 + b'.' + payload_b64 + b'.' + signature_b64 + signature_b64 = jwt_b64encode(h.digest()) + + return (header_b64 + b'.' + payload_b64 + b'.' + signature_b64).decode() # can be extended in future to verify the signature and parse header and return the algorithm used if it's not HS256 diff --git a/yt-dlp/yt_dlp/version.py b/yt-dlp/yt_dlp/version.py index d2d51136e8..d7606ca399 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.08.11' +__version__ = '2025.08.20' -RELEASE_GIT_HEAD = '5e4ceb35cf997af0dbf100e1de37f4e2bcbaa0b7' +RELEASE_GIT_HEAD = 'c2fc4f3e7f6d757250183b177130c64beee50520' VARIANT = None @@ -12,4 +12,4 @@ CHANNEL = 'stable' ORIGIN = 'yt-dlp/yt-dlp' -_pkg_version = '2025.08.11' +_pkg_version = '2025.08.20'