From 84e29bbf874cf1ae6fc7c55cea7d11c38cb6aa63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alessandro=20Calabr=C3=B2?= <153992315+Ales315@users.noreply.github.com> Date: Mon, 3 Feb 2025 19:16:04 +0100 Subject: [PATCH] Extract visitor data from YouTube API (#870) --- YoutubeExplode/Bridge/VisitorData.cs | 55 +++++++++++++++++++++++- YoutubeExplode/Videos/VideoController.cs | 2 +- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/YoutubeExplode/Bridge/VisitorData.cs b/YoutubeExplode/Bridge/VisitorData.cs index 0f378a19..ccf01b2a 100644 --- a/YoutubeExplode/Bridge/VisitorData.cs +++ b/YoutubeExplode/Bridge/VisitorData.cs @@ -1,6 +1,9 @@ using System; using System.Linq; -using System.Text; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text.Json; +using System.Threading.Tasks; using YoutubeExplode.Utils; using YoutubeExplode.Utils.Extensions; @@ -11,6 +14,7 @@ internal static class VisitorData private static readonly Random Random = new(); private static readonly char[] SessionIdAllowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".ToCharArray(); + private static string _visitorData = string.Empty; public static string Generate() => new ProtoBuilder() @@ -36,4 +40,53 @@ public static string Generate() => .ToBytes() ) .ToUrlEncodedBase64(); + + public static async Task ExtractFromYoutube(HttpClient http) + { + //avoid fetching visitor data for each request (?) could slow down the app + if (!string.IsNullOrEmpty(_visitorData)) + return _visitorData; + + //use the same iOS user agent (?) idk if it's necessary + http.DefaultRequestHeaders.UserAgent.ParseAdd( + "com.google.ios.youtube/19.45.4 (iPhone16,2; U; CPU iOS 18_1_0 like Mac OS X; US)" + ); + + //request JSON format + http.DefaultRequestHeaders.Accept.Add( + new MediaTypeWithQualityHeaderValue("application/json") + ); + + var url = "https://www.youtube.com/sw.js_data"; + HttpResponseMessage response = await http.GetAsync(url); + response.EnsureSuccessStatusCode(); + string jsonString = await response.Content.ReadAsStringAsync(); + + //Remove the prefix ")]}'" + if (jsonString.StartsWith(")]}'")) + jsonString = jsonString.Substring(4); + + using (JsonDocument doc = JsonDocument.Parse(jsonString)) + { + JsonElement root = doc.RootElement; + + //jsonArray[0][2][0][0][13] + var value = root[0] + .EnumerateArray() + .ElementAt(2) + .EnumerateArray() + .ElementAt(0) + .EnumerateArray() + .ElementAt(0) + .EnumerateArray() + .ElementAt(13) + .GetString(); + + if (value == null) + throw new Exception("Failed to fetch visitor data"); + + _visitorData = value; + } + return _visitorData; + } } diff --git a/YoutubeExplode/Videos/VideoController.cs b/YoutubeExplode/Videos/VideoController.cs index 241f2d61..6d4fa87a 100644 --- a/YoutubeExplode/Videos/VideoController.cs +++ b/YoutubeExplode/Videos/VideoController.cs @@ -76,7 +76,7 @@ public async ValueTask GetPlayerResponseAsync( "platform": "MOBILE", "osName": "IOS", "osVersion": "18.1.0.22B83", - "visitorData": {{Json.Serialize(VisitorData.Generate())}}, + "visitorData": {{Json.Serialize(await VisitorData.ExtractFromYoutube(Http))}}, "hl": "en", "gl": "US", "utcOffsetMinutes": 0