From b2c6003203c649ba70b1d362006befd44aa9c2ab Mon Sep 17 00:00:00 2001 From: max Date: Sun, 7 Sep 2025 22:18:56 +0200 Subject: [PATCH] [CHANGE] Split up json parsing, added getting account info --- .../Components/Dialogs/AccountDialog.razor | 18 ++-- .../Components/Dialogs/AccountDialog.razor.cs | 25 +++--- Manager.YouTube/Models/ClientInformation.cs | 1 + .../Models/Innertube/ChannelFetch.cs | 7 +- .../Models/Innertube/ClientState.cs | 3 + Manager.YouTube/Models/Innertube/WebImage.cs | 8 ++ Manager.YouTube/NetworkService.cs | 33 +++---- Manager.YouTube/Parsers/HtmlParser.cs | 9 +- .../Parsers/Json/ChannelJsonParser.cs | 87 +++++++++++++++++++ .../Parsers/Json/JsonAccountParser.cs | 43 +++++++++ Manager.YouTube/Parsers/Json/JsonParser.cs | 12 +++ 11 files changed, 198 insertions(+), 48 deletions(-) create mode 100644 Manager.YouTube/Models/Innertube/WebImage.cs create mode 100644 Manager.YouTube/Parsers/Json/ChannelJsonParser.cs create mode 100644 Manager.YouTube/Parsers/Json/JsonAccountParser.cs create mode 100644 Manager.YouTube/Parsers/Json/JsonParser.cs diff --git a/Manager.App/Components/Dialogs/AccountDialog.razor b/Manager.App/Components/Dialogs/AccountDialog.razor index b7c0b53..3992946 100644 --- a/Manager.App/Components/Dialogs/AccountDialog.razor +++ b/Manager.App/Components/Dialogs/AccountDialog.razor @@ -26,19 +26,27 @@ Account handle: @Client.External.Information.AccountHandle - - User agent: - @Client.UserAgent - - Logged in: @Client.External.State?.LoggedIn + + YouTube Premium: + @Client.External.Information.IsPremiumUser + + + + User agent: + @Client.UserAgent + InnerTube API key: @Client.External.State?.InnertubeApiKey + + InnerTube client: + @Client.External.State?.InnerTubeClient + InnerTube client version: @Client.External.State?.InnerTubeClientVersion diff --git a/Manager.App/Components/Dialogs/AccountDialog.razor.cs b/Manager.App/Components/Dialogs/AccountDialog.razor.cs index 1f2e5cc..363f816 100644 --- a/Manager.App/Components/Dialogs/AccountDialog.razor.cs +++ b/Manager.App/Components/Dialogs/AccountDialog.razor.cs @@ -67,22 +67,17 @@ namespace Manager.App.Components.Dialogs foreach (var cookieStr in cookies) { var parts = cookieStr.Split('=', 2); - if (parts.Length == 2) + if (parts.Length != 2) continue; + + var name = parts[0].Trim(); + var value = parts[1].Trim(); + + var cookie = new Cookie(name, value) { - var name = parts[0].Trim(); - var value = parts[1].Trim(); - - var cookie = new Cookie(name, value) - { - Expires = DateTime.Now.AddDays(1), - Path = "/", - }; - - if (!string.IsNullOrEmpty(domain)) - cookie.Domain = domain; - - collection.Add(cookie); - } + Path = "/", + Domain = domain + }; + collection.Add(cookie); } return collection; diff --git a/Manager.YouTube/Models/ClientInformation.cs b/Manager.YouTube/Models/ClientInformation.cs index b9e6aa8..607cf9e 100644 --- a/Manager.YouTube/Models/ClientInformation.cs +++ b/Manager.YouTube/Models/ClientInformation.cs @@ -5,4 +5,5 @@ public class ClientInformation public string? AccountName { get; set; } public string? AccountHandle { get; set; } public string? Description { get; set; } + public bool IsPremiumUser { get; set; } } \ No newline at end of file diff --git a/Manager.YouTube/Models/Innertube/ChannelFetch.cs b/Manager.YouTube/Models/Innertube/ChannelFetch.cs index 9e212ef..149deea 100644 --- a/Manager.YouTube/Models/Innertube/ChannelFetch.cs +++ b/Manager.YouTube/Models/Innertube/ChannelFetch.cs @@ -4,7 +4,10 @@ public class ChannelFetch { public bool NoIndex { get; set; } public bool Unlisted { get; set; } - public bool FamilyFriendly { get; set; } + public bool FamilySafe { get; set; } + public string? Handle { get; set; } + public string? Description { get; set; } public List AvailableCountries { get; set; } = []; - + public List AvatarImages { get; set; } = []; + public List BannerImages { get; set; } = []; } \ No newline at end of file diff --git a/Manager.YouTube/Models/Innertube/ClientState.cs b/Manager.YouTube/Models/Innertube/ClientState.cs index ad69622..bc131c0 100644 --- a/Manager.YouTube/Models/Innertube/ClientState.cs +++ b/Manager.YouTube/Models/Innertube/ClientState.cs @@ -15,6 +15,9 @@ public class ClientState : AdditionalJsonData [JsonPropertyName("SIGNIN_URL")] public string? SigninUrl { get; set; } + + [JsonPropertyName("INNERTUBE_CLIENT_NAME")] + public string? InnerTubeClient { get; set; } [JsonPropertyName("INNERTUBE_CLIENT_VERSION")] public string? InnerTubeClientVersion { get; set; } diff --git a/Manager.YouTube/Models/Innertube/WebImage.cs b/Manager.YouTube/Models/Innertube/WebImage.cs new file mode 100644 index 0000000..37e282b --- /dev/null +++ b/Manager.YouTube/Models/Innertube/WebImage.cs @@ -0,0 +1,8 @@ +namespace Manager.YouTube.Models.Innertube; + +public class WebImage +{ + public int Width { get; set; } + public int Height { get; set; } + public string Url { get; set; } = ""; +} \ No newline at end of file diff --git a/Manager.YouTube/NetworkService.cs b/Manager.YouTube/NetworkService.cs index 748f552..2826f75 100644 --- a/Manager.YouTube/NetworkService.cs +++ b/Manager.YouTube/NetworkService.cs @@ -5,6 +5,7 @@ using System.Text.Json.Nodes; using DotBased.Monads; using Manager.YouTube.Models.Innertube; using Manager.YouTube.Parsers; +using Manager.YouTube.Parsers.Json; using Manager.YouTube.Util; namespace Manager.YouTube; @@ -52,6 +53,8 @@ public static class NetworkService return ResultError.Error(e, "Error while parsing JSON!"); } + client.External.Information.IsPremiumUser = clientStateResult.Value.Item2; + return clientState == null ? ResultError.Fail("Unable to parse client state!") : clientState; } @@ -125,7 +128,8 @@ public static class NetworkService { return ResultError.Fail("Unable to get http client!"); } - + + string json; try { var response = await http.SendAsync(httpRequest); @@ -135,31 +139,14 @@ public static class NetworkService return ResultError.Fail(responseResult); } - var json = await response.Content.ReadAsStringAsync(); - var jsonDocument = JsonDocument.Parse(json); - - var matRuns = jsonDocument.RootElement - .GetProperty("actions")[0] - .GetProperty("openPopupAction") - .GetProperty("popup") - .GetProperty("multiPageMenuRenderer") - .GetProperty("header") - .GetProperty("activeAccountHeaderRenderer") - .GetProperty("manageAccountTitle") - .GetProperty("runs"); - - var firstElement = matRuns.EnumerateArray().FirstOrDefault(); - var id = firstElement.GetProperty("navigationEndpoint").GetProperty("browseEndpoint").GetProperty("browseId").GetString(); - if (string.IsNullOrWhiteSpace(id)) - { - return ResultError.Fail("Unable to get account id!"); - } - return id; + json = await response.Content.ReadAsStringAsync(); } catch (Exception e) { return ResultError.Error(e); } + + return JsonAccountParser.ParseAccountId(json); } public static async Task> GetChannelAsync(string channelId, YouTubeClient client) @@ -204,8 +191,10 @@ public static class NetworkService } var jsonContent = await response.Content.ReadAsStringAsync(); - + var parsed = ChannelJsonParser.ParseJsonToChannelData(jsonContent); return ResultError.Fail("Not implemented!"); } + + } \ No newline at end of file diff --git a/Manager.YouTube/Parsers/HtmlParser.cs b/Manager.YouTube/Parsers/HtmlParser.cs index 844d4c2..b00429c 100644 --- a/Manager.YouTube/Parsers/HtmlParser.cs +++ b/Manager.YouTube/Parsers/HtmlParser.cs @@ -5,7 +5,7 @@ namespace Manager.YouTube.Parsers; public static class HtmlParser { - public static Result<(string, string)> GetStateJson(string html) + public static Result<(string, bool)> GetStateJson(string html) { if (string.IsNullOrWhiteSpace(html)) { @@ -21,14 +21,15 @@ public static class HtmlParser return ResultError.Fail($"Could not find {setFunction} in html script nodes!"); var json = ExtractJson(scriptNode.InnerText, "ytcfg.set("); - var jsonText = ExtractJson(scriptNode.InnerText, "setMessage("); - if (string.IsNullOrWhiteSpace(json) || string.IsNullOrWhiteSpace(jsonText)) + if (string.IsNullOrWhiteSpace(json)) { return ResultError.Fail($"Could not find {setFunction} in html script nodes!"); } - return (json, jsonText); + var isPremiumUser = html.Contains("logo-type=\"YOUTUBE_PREMIUM_LOGO\"", StringComparison.OrdinalIgnoreCase); + + return (json, isPremiumUser); } static string? ExtractJson(string input, string marker) diff --git a/Manager.YouTube/Parsers/Json/ChannelJsonParser.cs b/Manager.YouTube/Parsers/Json/ChannelJsonParser.cs new file mode 100644 index 0000000..6a4172a --- /dev/null +++ b/Manager.YouTube/Parsers/Json/ChannelJsonParser.cs @@ -0,0 +1,87 @@ +using System.Text.Json; +using DotBased.Monads; +using Manager.YouTube.Models.Innertube; + +namespace Manager.YouTube.Parsers.Json; + +/// +/// Parsing functionality for the response from the innertube browse endpoint. +/// +public static class ChannelJsonParser +{ + public static Result ParseJsonToChannelData(string json) + { + try + { + var doc = JsonDocument.Parse(json); + var rootDoc = doc.RootElement; + + var microformat = rootDoc.GetProperty("microformat").GetProperty("microformatDataRenderer"); + + var availableCountries = microformat + .GetProperty("availableCountries") + .EnumerateArray() + .Select(e => e.GetString()) + .ToList(); + + var description = microformat.GetProperty("description").GetString(); + var noIndex = microformat.GetProperty("noindex").GetBoolean(); + var unlisted = microformat.GetProperty("unlisted").GetBoolean(); + var familySafe = microformat.GetProperty("familySafe").GetBoolean(); + + var avatarThumbnails = rootDoc + .GetProperty("metadata") + .GetProperty("channelMetadataRenderer") + .GetProperty("avatar") + .GetProperty("thumbnails") + .EnumerateArray(); + + var avatars = JsonParser.ParseImages(avatarThumbnails); + + var headerContent = rootDoc + .GetProperty("header") + .GetProperty("pageHeaderRenderer") + .GetProperty("content"); + + var metadataPartHandle = headerContent + .GetProperty("pageHeaderViewModel") + .GetProperty("metadata") + .GetProperty("contentMetadataViewModel") + .GetProperty("metadataRows") + .EnumerateArray() + .FirstOrDefault() + .GetProperty("metadataParts") + .EnumerateArray() + .FirstOrDefault() + .GetProperty("text") + .GetProperty("content").GetString(); + + var bannerImages = headerContent + .GetProperty("pageHeaderViewModel") + .GetProperty("banner") + .GetProperty("imageBannerViewModel") + .GetProperty("image") + .GetProperty("sources") + .EnumerateArray(); + + var banners = JsonParser.ParseImages(bannerImages); + + var resultFetch = new ChannelFetch + { + NoIndex = noIndex, + Unlisted = unlisted, + FamilySafe = familySafe, + Handle = metadataPartHandle, + Description = description, + AvailableCountries = availableCountries.OfType().ToList(), + AvatarImages = avatars, + BannerImages = banners + }; + return resultFetch; + } + catch (Exception e) + { + return ResultError.Error(e); + } + } +} \ No newline at end of file diff --git a/Manager.YouTube/Parsers/Json/JsonAccountParser.cs b/Manager.YouTube/Parsers/Json/JsonAccountParser.cs new file mode 100644 index 0000000..d324056 --- /dev/null +++ b/Manager.YouTube/Parsers/Json/JsonAccountParser.cs @@ -0,0 +1,43 @@ +using System.Text.Json; +using DotBased.Monads; + +namespace Manager.YouTube.Parsers.Json; + +/// +/// Parsing functionality for the response from endpoint: /youtubei/v1/account/account_menu +/// +public static class JsonAccountParser +{ + public static Result ParseAccountId(string json) + { + try + { + var jsonDocument = JsonDocument.Parse(json); + + var id = jsonDocument.RootElement + .GetProperty("actions")[0] + .GetProperty("openPopupAction") + .GetProperty("popup") + .GetProperty("multiPageMenuRenderer") + .GetProperty("header") + .GetProperty("activeAccountHeaderRenderer") + .GetProperty("manageAccountTitle") + .GetProperty("runs") + .EnumerateArray() + .FirstOrDefault() + .GetProperty("navigationEndpoint") + .GetProperty("browseEndpoint") + .GetProperty("browseId").GetString(); + + if (string.IsNullOrWhiteSpace(id)) + { + return ResultError.Fail("Unable to get account id!"); + } + return id; + } + catch (Exception e) + { + return ResultError.Error(e); + } + } +} \ No newline at end of file diff --git a/Manager.YouTube/Parsers/Json/JsonParser.cs b/Manager.YouTube/Parsers/Json/JsonParser.cs new file mode 100644 index 0000000..3ae103a --- /dev/null +++ b/Manager.YouTube/Parsers/Json/JsonParser.cs @@ -0,0 +1,12 @@ +using System.Text.Json; +using Manager.YouTube.Models.Innertube; + +namespace Manager.YouTube.Parsers.Json; + +public static class JsonParser +{ + public static List ParseImages(JsonElement.ArrayEnumerator array) => + array + .Select(image => new WebImage { Width = image.GetProperty("width").GetInt32(), Height = image.GetProperty("height").GetInt32(), Url = image.GetProperty("url").GetString() ?? "" }) + .ToList(); +} \ No newline at end of file