From 5690324f2752ce5da8881bf0f88dc387a389db7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alessandro=20Calabr=C3=B2?= <153992315+Ales315@users.noreply.github.com> Date: Sat, 25 Jan 2025 22:02:46 +0100 Subject: [PATCH] Update iOS client and add random visitor data to requests (#865) --- YoutubeExplode/Utils/ProtoBuilder.cs | 81 ++++++++++++++++++++ YoutubeExplode/Utils/YoutubeParsingHelper.cs | 45 +++++++++++ YoutubeExplode/Videos/VideoController.cs | 17 ++-- 3 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 YoutubeExplode/Utils/ProtoBuilder.cs create mode 100644 YoutubeExplode/Utils/YoutubeParsingHelper.cs diff --git a/YoutubeExplode/Utils/ProtoBuilder.cs b/YoutubeExplode/Utils/ProtoBuilder.cs new file mode 100644 index 00000000..1661ed6b --- /dev/null +++ b/YoutubeExplode/Utils/ProtoBuilder.cs @@ -0,0 +1,81 @@ +using System; +using System.IO; +using System.Text; + +namespace YoutubeExplode.Utils +{ + internal class ProtoBuilder + { + private MemoryStream _byteBuffer; + + public ProtoBuilder() + { + _byteBuffer = new MemoryStream(); + } + + public byte[] ToBytes() + { + return _byteBuffer.ToArray(); + } + + public string ToUrlencodedBase64() + { + var b64 = Convert + .ToBase64String(ToBytes()) + .Replace('+', '-') + .Replace('/', '_') + .TrimEnd('='); + return Uri.EscapeDataString(b64); + } + + private void WriteVarint(long val) + { + if (val == 0) + { + _byteBuffer.WriteByte(0); + } + else + { + long v = val; + while (v != 0) + { + byte b = (byte)(v & 0x7F); + v >>= 7; + + if (v != 0) + { + b |= 0x80; + } + _byteBuffer.WriteByte(b); + } + } + } + + private void Field(int field, byte wire) + { + long fbits = ((long)field) << 3; + long wbits = ((long)wire) & 0x07; + long val = fbits | wbits; + WriteVarint(val); + } + + public void Varint(int field, long val) + { + Field(field, 0); + WriteVarint(val); + } + + public void String(int field, string str) + { + var strBytes = Encoding.UTF8.GetBytes(str); + Bytes(field, strBytes); + } + + public void Bytes(int field, byte[] bytes) + { + Field(field, 2); + WriteVarint(bytes.Length); + _byteBuffer.Write(bytes, 0, bytes.Length); + } + } +} diff --git a/YoutubeExplode/Utils/YoutubeParsingHelper.cs b/YoutubeExplode/Utils/YoutubeParsingHelper.cs new file mode 100644 index 00000000..eb78b41a --- /dev/null +++ b/YoutubeExplode/Utils/YoutubeParsingHelper.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; + +namespace YoutubeExplode.Utils +{ + internal static class YoutubeParsingHelper + { + private static string CONTENT_PLAYBACK_NONCE_ALPHABET = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + + public static string GetRandomVisitorData() + { + Random r = new Random(); + ProtoBuilder pbE2 = new ProtoBuilder(); + pbE2.String(2, ""); + pbE2.Varint(4, r.Next(255) + 1); + + ProtoBuilder pbE = new ProtoBuilder(); + pbE.String(1, "US"); + pbE.Bytes(2, pbE2.ToBytes()); + + ProtoBuilder pb = new ProtoBuilder(); + pb.String(1, GenerateRandomStringFromAlphabet(CONTENT_PLAYBACK_NONCE_ALPHABET, 11, r)); + pb.Varint(5, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() / 1000 - r.Next(600000)); + pb.Bytes(6, pbE.ToBytes()); + return pb.ToUrlencodedBase64(); + } + + private static string GenerateRandomStringFromAlphabet( + string alphabet, + int length, + Random random + ) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) + { + sb.Append(alphabet.ElementAt(random.Next(alphabet.Length))); + } + return sb.ToString(); + } + } +} diff --git a/YoutubeExplode/Videos/VideoController.cs b/YoutubeExplode/Videos/VideoController.cs index 2f2c3de2..52173be1 100644 --- a/YoutubeExplode/Videos/VideoController.cs +++ b/YoutubeExplode/Videos/VideoController.cs @@ -11,6 +11,8 @@ internal class VideoController(HttpClient http) { protected HttpClient Http { get; } = http; + private string _visitorData = null!; + public async ValueTask GetVideoWatchPageAsync( VideoId videoId, CancellationToken cancellationToken = default @@ -61,6 +63,9 @@ public async ValueTask GetPlayerResponseAsync( "https://www.youtube.com/youtubei/v1/player" ); + if (_visitorData == null) + _visitorData = YoutubeParsingHelper.GetRandomVisitorData(); + request.Content = new StringContent( // lang=json $$""" @@ -70,14 +75,14 @@ public async ValueTask GetPlayerResponseAsync( "context": { "client": { "clientName": "IOS", - "clientVersion": "19.29.1", + "clientVersion": "19.45.4", "deviceMake": "Apple", "deviceModel": "iPhone16,2", + "platform": "MOBILE", + "osName": "IOS", + "osVersion": "18.1.0.22B83", + "visitorData": {{Json.Serialize(_visitorData)}}, "hl": "en", - "osName": "iPhone", - "osVersion": "17.5.1.21F90", - "timeZone": "UTC", - "userAgent": "com.google.ios.youtube/19.29.1 (iPhone16,2; U; CPU iOS 17_5_1 like Mac OS X;)", "gl": "US", "utcOffsetMinutes": 0 } @@ -90,7 +95,7 @@ public async ValueTask GetPlayerResponseAsync( // https://github.com/iv-org/invidious/issues/3230#issuecomment-1226887639 request.Headers.Add( "User-Agent", - "com.google.ios.youtube/19.29.1 (iPhone16,2; U; CPU iOS 17_5_1 like Mac OS X)" + "com.google.ios.youtube/19.45.4 (iPhone16,2; U; CPU iOS 18_1_0 like Mac OS X; US)" ); using var response = await Http.SendAsync(request, cancellationToken);