[CHANGE] Split up json parsing, added getting account info

This commit is contained in:
max
2025-09-07 22:18:56 +02:00
parent 3db61b599d
commit b2c6003203
11 changed files with 198 additions and 48 deletions

View File

@@ -26,19 +26,27 @@
<td>Account handle:</td> <td>Account handle:</td>
<td>@Client.External.Information.AccountHandle</td> <td>@Client.External.Information.AccountHandle</td>
</tr> </tr>
<tr>
<td>User agent:</td>
<td>@Client.UserAgent</td>
</tr>
<tr> <tr>
<td>Logged in:</td> <td>Logged in:</td>
<td style="@($"color: {(Client.External.State?.LoggedIn ?? false ? "green" : "red")}")">@Client.External.State?.LoggedIn</td> <td style="@($"color: {(Client.External.State?.LoggedIn ?? false ? "green" : "red")}")">@Client.External.State?.LoggedIn</td>
</tr> </tr>
<tr>
<td>YouTube Premium:</td>
<td style="@($"color: {(Client.External.Information.IsPremiumUser ? "green" : "red")}")">@Client.External.Information.IsPremiumUser</td>
</tr>
<tr>
<td>User agent:</td>
<td>@Client.UserAgent</td>
</tr>
<tr> <tr>
<td>InnerTube API key:</td> <td>InnerTube API key:</td>
<td>@Client.External.State?.InnertubeApiKey</td> <td>@Client.External.State?.InnertubeApiKey</td>
</tr> </tr>
<tr>
<td>InnerTube client:</td>
<td>@Client.External.State?.InnerTubeClient</td>
</tr>
<tr> <tr>
<td>InnerTube client version:</td> <td>InnerTube client version:</td>
<td>@Client.External.State?.InnerTubeClientVersion</td> <td>@Client.External.State?.InnerTubeClientVersion</td>

View File

@@ -67,22 +67,17 @@ namespace Manager.App.Components.Dialogs
foreach (var cookieStr in cookies) foreach (var cookieStr in cookies)
{ {
var parts = cookieStr.Split('=', 2); 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(); Path = "/",
var value = parts[1].Trim(); Domain = domain
};
var cookie = new Cookie(name, value) collection.Add(cookie);
{
Expires = DateTime.Now.AddDays(1),
Path = "/",
};
if (!string.IsNullOrEmpty(domain))
cookie.Domain = domain;
collection.Add(cookie);
}
} }
return collection; return collection;

View File

@@ -5,4 +5,5 @@ public class ClientInformation
public string? AccountName { get; set; } public string? AccountName { get; set; }
public string? AccountHandle { get; set; } public string? AccountHandle { get; set; }
public string? Description { get; set; } public string? Description { get; set; }
public bool IsPremiumUser { get; set; }
} }

View File

@@ -4,7 +4,10 @@ public class ChannelFetch
{ {
public bool NoIndex { get; set; } public bool NoIndex { get; set; }
public bool Unlisted { 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<string> AvailableCountries { get; set; } = []; public List<string> AvailableCountries { get; set; } = [];
public List<WebImage> AvatarImages { get; set; } = [];
public List<WebImage> BannerImages { get; set; } = [];
} }

View File

@@ -15,6 +15,9 @@ public class ClientState : AdditionalJsonData
[JsonPropertyName("SIGNIN_URL")] [JsonPropertyName("SIGNIN_URL")]
public string? SigninUrl { get; set; } public string? SigninUrl { get; set; }
[JsonPropertyName("INNERTUBE_CLIENT_NAME")]
public string? InnerTubeClient { get; set; }
[JsonPropertyName("INNERTUBE_CLIENT_VERSION")] [JsonPropertyName("INNERTUBE_CLIENT_VERSION")]
public string? InnerTubeClientVersion { get; set; } public string? InnerTubeClientVersion { get; set; }

View File

@@ -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; } = "";
}

View File

@@ -5,6 +5,7 @@ using System.Text.Json.Nodes;
using DotBased.Monads; using DotBased.Monads;
using Manager.YouTube.Models.Innertube; using Manager.YouTube.Models.Innertube;
using Manager.YouTube.Parsers; using Manager.YouTube.Parsers;
using Manager.YouTube.Parsers.Json;
using Manager.YouTube.Util; using Manager.YouTube.Util;
namespace Manager.YouTube; namespace Manager.YouTube;
@@ -52,6 +53,8 @@ public static class NetworkService
return ResultError.Error(e, "Error while parsing JSON!"); 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; 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!"); return ResultError.Fail("Unable to get http client!");
} }
string json;
try try
{ {
var response = await http.SendAsync(httpRequest); var response = await http.SendAsync(httpRequest);
@@ -135,31 +139,14 @@ public static class NetworkService
return ResultError.Fail(responseResult); return ResultError.Fail(responseResult);
} }
var json = await response.Content.ReadAsStringAsync(); 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;
} }
catch (Exception e) catch (Exception e)
{ {
return ResultError.Error(e); return ResultError.Error(e);
} }
return JsonAccountParser.ParseAccountId(json);
} }
public static async Task<Result<ChannelFetch>> GetChannelAsync(string channelId, YouTubeClient client) public static async Task<Result<ChannelFetch>> GetChannelAsync(string channelId, YouTubeClient client)
@@ -204,8 +191,10 @@ public static class NetworkService
} }
var jsonContent = await response.Content.ReadAsStringAsync(); var jsonContent = await response.Content.ReadAsStringAsync();
var parsed = ChannelJsonParser.ParseJsonToChannelData(jsonContent);
return ResultError.Fail("Not implemented!"); return ResultError.Fail("Not implemented!");
} }
} }

View File

@@ -5,7 +5,7 @@ namespace Manager.YouTube.Parsers;
public static class HtmlParser public static class HtmlParser
{ {
public static Result<(string, string)> GetStateJson(string html) public static Result<(string, bool)> GetStateJson(string html)
{ {
if (string.IsNullOrWhiteSpace(html)) if (string.IsNullOrWhiteSpace(html))
{ {
@@ -21,14 +21,15 @@ public static class HtmlParser
return ResultError.Fail($"Could not find {setFunction} in html script nodes!"); return ResultError.Fail($"Could not find {setFunction} in html script nodes!");
var json = ExtractJson(scriptNode.InnerText, "ytcfg.set("); 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 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) static string? ExtractJson(string input, string marker)

View File

@@ -0,0 +1,87 @@
using System.Text.Json;
using DotBased.Monads;
using Manager.YouTube.Models.Innertube;
namespace Manager.YouTube.Parsers.Json;
/// <summary>
/// Parsing functionality for the response from the innertube browse endpoint.
/// </summary>
public static class ChannelJsonParser
{
public static Result<ChannelFetch> 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<string>().ToList(),
AvatarImages = avatars,
BannerImages = banners
};
return resultFetch;
}
catch (Exception e)
{
return ResultError.Error(e);
}
}
}

View File

@@ -0,0 +1,43 @@
using System.Text.Json;
using DotBased.Monads;
namespace Manager.YouTube.Parsers.Json;
/// <summary>
/// Parsing functionality for the response from endpoint: /youtubei/v1/account/account_menu
/// </summary>
public static class JsonAccountParser
{
public static Result<string> 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);
}
}
}

View File

@@ -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<WebImage> 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();
}