diff --git a/Manager.App/Components/Dialogs/AccountDialog.razor b/Manager.App/Components/Dialogs/AccountDialog.razor
index 3992946..f75e676 100644
--- a/Manager.App/Components/Dialogs/AccountDialog.razor
+++ b/Manager.App/Components/Dialogs/AccountDialog.razor
@@ -20,11 +20,11 @@
Account name: |
- @Client.External.Information.AccountName |
+ @Client.External.Channel?.ChannelName |
Account handle: |
- @Client.External.Information.AccountHandle |
+ @Client.External.Channel?.Handle |
Logged in: |
@@ -32,17 +32,13 @@
YouTube Premium: |
- @Client.External.Information.IsPremiumUser |
+ @Client.External.State?.IsPremiumUser |
User agent: |
@Client.UserAgent |
-
- InnerTube API key: |
- @Client.External.State?.InnertubeApiKey |
-
InnerTube client: |
@Client.External.State?.InnerTubeClient |
@@ -51,16 +47,23 @@
InnerTube client version: |
@Client.External.State?.InnerTubeClientVersion |
+
+ InnerTube API key: |
+ @Client.External.State?.InnertubeApiKey |
+
Language: |
@Client.External.State?.InnerTubeContext?.InnerTubeClient?.HLanguage |
- @*@if (!string.IsNullOrWhiteSpace(Client.AccountImage))
+ @{
+ var avatar = Client.External.Channel?.AvatarImages.FirstOrDefault();
+ }
+ @if (avatar != null)
{
-
- }*@
+
+ }
diff --git a/Manager.App/Services/System/ClientManager.cs b/Manager.App/Services/System/ClientManager.cs
index 759b810..91a7cc8 100644
--- a/Manager.App/Services/System/ClientManager.cs
+++ b/Manager.App/Services/System/ClientManager.cs
@@ -1,3 +1,4 @@
+using DotBased.Monads;
using Manager.YouTube;
namespace Manager.App.Services.System;
@@ -17,4 +18,19 @@ public class ClientManager : BackgroundService
{
// Clear up
}
+
+ public async Task SaveClientAsync(YouTubeClient client)
+ {
+ return ResultError.Fail("Not implemented");
+ }
+
+ public async Task> LoadClientByIdAsync(string id)
+ {
+ if (string.IsNullOrWhiteSpace(id))
+ {
+ return ResultError.Fail("Client ID is empty!");
+ }
+
+ return ResultError.Fail("Not implemented");
+ }
}
\ No newline at end of file
diff --git a/Manager.YouTube/Models/ClientExternalData.cs b/Manager.YouTube/Models/ClientExternalData.cs
index 4674e0c..36d2128 100644
--- a/Manager.YouTube/Models/ClientExternalData.cs
+++ b/Manager.YouTube/Models/ClientExternalData.cs
@@ -5,7 +5,7 @@ namespace Manager.YouTube.Models;
public class ClientExternalData
{
public ClientState? State { get; set; }
- public ClientInformation Information { get; set; } = new();
+ public Channel? Channel { get; set; }
public List DatasyncIds { get; set; } = [];
public string GetDatasyncId()
diff --git a/Manager.YouTube/Models/ClientInformation.cs b/Manager.YouTube/Models/ClientInformation.cs
deleted file mode 100644
index 607cf9e..0000000
--- a/Manager.YouTube/Models/ClientInformation.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Manager.YouTube.Models;
-
-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/Channel.cs
similarity index 86%
rename from Manager.YouTube/Models/Innertube/ChannelFetch.cs
rename to Manager.YouTube/Models/Innertube/Channel.cs
index 149deea..ed65f70 100644
--- a/Manager.YouTube/Models/Innertube/ChannelFetch.cs
+++ b/Manager.YouTube/Models/Innertube/Channel.cs
@@ -1,10 +1,11 @@
namespace Manager.YouTube.Models.Innertube;
-public class ChannelFetch
+public class Channel
{
public bool NoIndex { get; set; }
public bool Unlisted { get; set; }
public bool FamilySafe { get; set; }
+ public string? ChannelName { get; set; }
public string? Handle { get; set; }
public string? Description { get; set; }
public List AvailableCountries { get; set; } = [];
diff --git a/Manager.YouTube/Models/Innertube/ClientState.cs b/Manager.YouTube/Models/Innertube/ClientState.cs
index bc131c0..3f1d912 100644
--- a/Manager.YouTube/Models/Innertube/ClientState.cs
+++ b/Manager.YouTube/Models/Innertube/ClientState.cs
@@ -4,6 +4,7 @@ namespace Manager.YouTube.Models.Innertube;
public class ClientState : AdditionalJsonData
{
+ public bool IsPremiumUser { get; set; }
[JsonPropertyName("INNERTUBE_API_KEY")]
public string? InnertubeApiKey { get; set; }
diff --git a/Manager.YouTube/NetworkService.cs b/Manager.YouTube/NetworkService.cs
index 2826f75..e4bcf87 100644
--- a/Manager.YouTube/NetworkService.cs
+++ b/Manager.YouTube/NetworkService.cs
@@ -1,200 +1,40 @@
-using System.Net.Mime;
-using System.Text;
-using System.Text.Json;
-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;
public static class NetworkService
{
- private const string Origin = "https://www.youtube.com";
-
- public static async Task> GetClientStateAsync(YouTubeClient client)
- {
- var httpRequest = new HttpRequestMessage
- {
- Method = HttpMethod.Get,
- RequestUri = new Uri(Origin)
- };
- httpRequest.Headers.Clear();
- httpRequest.Headers.UserAgent.ParseAdd(client.UserAgent);
-
- var http = client.GetHttpClient();
- if (http == null)
- {
- return ResultError.Fail("Unable to get http client!");
- }
-
- var response = await http.SendAsync(httpRequest);
- if (!response.IsSuccessStatusCode)
- {
- var responseResult = await response.Content.ReadAsStringAsync();
- return ResultError.Fail(responseResult);
- }
- var responseHtml = await response.Content.ReadAsStringAsync();
- var clientStateResult = HtmlParser.GetStateJson(responseHtml);
- if (clientStateResult is { IsSuccess: false, Error: not null })
- {
- return clientStateResult.Error;
- }
+ public const string Origin = "https://www.youtube.com";
- ClientState? clientState;
- try
- {
- clientState = JsonSerializer.Deserialize(clientStateResult.Value.Item1);
- }
- catch (Exception e)
- {
- 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;
- }
-
- public static async Task> GetDatasyncIds(YouTubeClient client)
+ public static async Task> MakeRequestAsync(HttpRequestMessage request, YouTubeClient client)
{
- if (client.External.State is not { LoggedIn: true } || client.CookieContainer.Count == 0)
+ request.Headers.Add("Origin", Origin);
+ request.Headers.UserAgent.ParseAdd(client.UserAgent);
+ if (client.SapisidCookie != null)
{
- return ResultError.Fail("Client is not logged in, requires logged in client for this endpoint (/getDatasyncIdsEndpoint).");
+ request.Headers.Authorization = AuthenticationUtilities.GetSapisidHashHeader(client.External.GetDatasyncId(), client.SapisidCookie.Value, Origin);
}
var httpClient = client.GetHttpClient();
if (httpClient == null)
{
- return ResultError.Fail("Unable to get http client!");
+ return ResultError.Fail("Failed getting http client!");
}
- var httpRequest = new HttpRequestMessage
- {
- Method = HttpMethod.Get,
- RequestUri = new Uri($"{Origin}/getDatasyncIdsEndpoint")
- };
- httpRequest.Headers.UserAgent.ParseAdd(client.UserAgent);
- httpRequest.Headers.Add("Origin", Origin);
-
- var response = await httpClient.SendAsync(httpRequest);
- if (!response.IsSuccessStatusCode)
- {
- var responseResult = await response.Content.ReadAsStringAsync();
- return ResultError.Fail(responseResult);
- }
-
- var responseContent = await response.Content.ReadAsStringAsync();
- var datasyncIdsJson = JsonNode.Parse(responseContent.Replace(")]}'", ""));
-
- var isLoggedOut = datasyncIdsJson?["responseContext"]?["mainAppWebResponseContext"]?["loggedOut"]
- .Deserialize() ?? true;
- if (!isLoggedOut)
- {
- return datasyncIdsJson?["datasyncIds"].Deserialize() ?? [];
- }
-
- return ResultError.Fail("Failed to get datasyncIds!");
- }
-
- public static async Task> GetCurrentAccountIdAsync(YouTubeClient client)
- {
- if (client.External.State is not { LoggedIn: true })
- {
- return ResultError.Fail("Client not logged in!");
- }
-
- var httpRequest = new HttpRequestMessage
- {
- Method = HttpMethod.Post,
- RequestUri = new Uri($"{Origin}/youtubei/v1/account/account_menu")
- };
- httpRequest.Headers.UserAgent.ParseAdd(client.UserAgent);
- httpRequest.Headers.Add("Origin", Origin);
-
- if (client.SapisidCookie != null)
- {
- httpRequest.Headers.Authorization = AuthenticationUtilities.GetSapisidHashHeader(client.External.GetDatasyncId(), client.SapisidCookie.Value, Origin);
- }
-
- var serializedContext = JsonSerializer.SerializeToNode(client.External.State.InnerTubeContext);
- var payload = new JsonObject { { "context", serializedContext } };
- httpRequest.Content = new StringContent(payload.ToJsonString(), Encoding.UTF8, MediaTypeNames.Application.Json);
-
- var http = client.GetHttpClient();
- if (http == null)
- {
- return ResultError.Fail("Unable to get http client!");
- }
-
- string json;
try
{
- var response = await http.SendAsync(httpRequest);
+ var response = await httpClient.SendAsync(request);
+ var contentString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
- var responseResult = await response.Content.ReadAsStringAsync();
- return ResultError.Fail(responseResult);
+ return ResultError.Fail(contentString);
}
-
- json = await response.Content.ReadAsStringAsync();
+ return contentString;
}
catch (Exception e)
{
return ResultError.Error(e);
}
-
- return JsonAccountParser.ParseAccountId(json);
}
-
- public static async Task> GetChannelAsync(string channelId, YouTubeClient client)
- {
- if (client.External.State == null)
- {
- return ResultError.Fail("No client state!");
- }
-
- if (string.IsNullOrWhiteSpace(channelId))
- {
- return ResultError.Fail("Channel id is empty!");
- }
-
- var httpClient = client.GetHttpClient();
- if (httpClient == null)
- {
- return ResultError.Fail("Unable to get http client!");
- }
-
- var serializedContext = JsonSerializer.SerializeToNode(client.External.State.InnerTubeContext);
- var payload = new JsonObject { { "context", serializedContext }, { "browseId", channelId } };
- var requestMessage = new HttpRequestMessage
- {
- Method = HttpMethod.Post,
- RequestUri = new Uri($"{Origin}/youtubei/v1/browse?key={client.External.State.InnertubeApiKey}"),
- Content = new StringContent(payload.ToJsonString(), Encoding.UTF8, MediaTypeNames.Application.Json)
- };
- requestMessage.Headers.UserAgent.ParseAdd(client.UserAgent);
- requestMessage.Headers.Add("Origin", Origin);
-
- if (client.SapisidCookie != null)
- {
- requestMessage.Headers.Authorization = AuthenticationUtilities.GetSapisidHashHeader(client.External.GetDatasyncId(), client.SapisidCookie.Value, Origin);
- }
-
- var response = await httpClient.SendAsync(requestMessage);
- if (!response.IsSuccessStatusCode)
- {
- var responseResult = await response.Content.ReadAsStringAsync();
- return ResultError.Fail(responseResult);
- }
-
- 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/Json/ChannelJsonParser.cs b/Manager.YouTube/Parsers/Json/ChannelJsonParser.cs
index 6a4172a..7c528dd 100644
--- a/Manager.YouTube/Parsers/Json/ChannelJsonParser.cs
+++ b/Manager.YouTube/Parsers/Json/ChannelJsonParser.cs
@@ -9,25 +9,26 @@ namespace Manager.YouTube.Parsers.Json;
///
public static class ChannelJsonParser
{
- public static Result ParseJsonToChannelData(string json)
+ public static Result ParseJsonToChannelData(string json)
{
try
{
+ var channel = new Channel();
var doc = JsonDocument.Parse(json);
var rootDoc = doc.RootElement;
var microformat = rootDoc.GetProperty("microformat").GetProperty("microformatDataRenderer");
- var availableCountries = microformat
+ channel.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();
+ .OfType().ToList();
+ channel.Description = microformat.GetProperty("description").GetString();
+ channel.NoIndex = microformat.GetProperty("noindex").GetBoolean();
+ channel.Unlisted = microformat.GetProperty("unlisted").GetBoolean();
+ channel.FamilySafe = microformat.GetProperty("familySafe").GetBoolean();
+ channel.ChannelName = microformat.GetProperty("title").GetString();
var avatarThumbnails = rootDoc
.GetProperty("metadata")
@@ -35,15 +36,14 @@ public static class ChannelJsonParser
.GetProperty("avatar")
.GetProperty("thumbnails")
.EnumerateArray();
-
- var avatars = JsonParser.ParseImages(avatarThumbnails);
+ channel.AvatarImages = JsonParser.ParseImages(avatarThumbnails);
var headerContent = rootDoc
.GetProperty("header")
.GetProperty("pageHeaderRenderer")
.GetProperty("content");
- var metadataPartHandle = headerContent
+ channel.Handle = headerContent
.GetProperty("pageHeaderViewModel")
.GetProperty("metadata")
.GetProperty("contentMetadataViewModel")
@@ -63,21 +63,9 @@ public static class ChannelJsonParser
.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;
+ channel.BannerImages = JsonParser.ParseImages(bannerImages);
+
+ return channel;
}
catch (Exception e)
{
diff --git a/Manager.YouTube/YouTubeClient.cs b/Manager.YouTube/YouTubeClient.cs
index 686d3b5..e402ea5 100644
--- a/Manager.YouTube/YouTubeClient.cs
+++ b/Manager.YouTube/YouTubeClient.cs
@@ -1,6 +1,13 @@
using System.Net;
+using System.Net.Mime;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Nodes;
using DotBased.Monads;
using Manager.YouTube.Models;
+using Manager.YouTube.Models.Innertube;
+using Manager.YouTube.Parsers;
+using Manager.YouTube.Parsers.Json;
namespace Manager.YouTube;
@@ -38,7 +45,7 @@ public sealed class YouTubeClient : IDisposable
{
if (External.State is not { LoggedIn: true })
{
- var state = await NetworkService.GetClientStateAsync(this);
+ var state = await GetClientStateAsync();
if (!state.IsSuccess)
{
return state;
@@ -48,7 +55,7 @@ public sealed class YouTubeClient : IDisposable
if (string.IsNullOrWhiteSpace(External.State.WebPlayerContextConfig?.WebPlayerContext?.DatasyncId))
{
- var datasyncResult = await NetworkService.GetDatasyncIds(this);
+ var datasyncResult = await GetDatasyncIds();
if (!datasyncResult.IsSuccess)
{
return datasyncResult;
@@ -64,7 +71,7 @@ public sealed class YouTubeClient : IDisposable
if (string.IsNullOrWhiteSpace(Id))
{
- var accountInfoResult = await NetworkService.GetCurrentAccountIdAsync(this);
+ var accountInfoResult = await GetCurrentAccountIdAsync();
if (!accountInfoResult.IsSuccess)
{
return accountInfoResult;
@@ -73,13 +80,143 @@ public sealed class YouTubeClient : IDisposable
Id = accountInfoResult.Value;
}
- var accountInfo = await NetworkService.GetChannelAsync(Id, this);
+ var channelResult = await GetChannelByIdAsync(Id);
+ if (!channelResult.IsSuccess)
+ {
+ return channelResult.Error ?? ResultError.Fail("Failed to get channel.");
+ }
+ External.Channel = channelResult.Value;
return Result.Success();
}
+
+ public async Task> GetClientStateAsync()
+ {
+ var httpRequest = new HttpRequestMessage
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri(NetworkService.Origin)
+ };
+
+ var result = await NetworkService.MakeRequestAsync(httpRequest, this);
+ if (!result.IsSuccess)
+ {
+ return result.Error ?? ResultError.Fail("Request failed!");
+ }
+
+ var clientStateResult = HtmlParser.GetStateJson(result.Value);
+ if (clientStateResult is { IsSuccess: false, Error: not null })
+ {
+ return clientStateResult.Error;
+ }
+
+ ClientState? clientState;
+ try
+ {
+ clientState = JsonSerializer.Deserialize(clientStateResult.Value.Item1);
+ }
+ catch (Exception e)
+ {
+ return ResultError.Error(e, "Error while parsing JSON!");
+ }
+
+
+ if (clientState == null)
+ {
+ return ResultError.Fail("Unable to parse client state!");
+ }
+
+ clientState.IsPremiumUser = clientStateResult.Value.Item2;
+
+ return clientState;
+ }
+
+ public async Task> GetChannelByIdAsync(string channelId)
+ {
+ if (External.State == null)
+ {
+ return ResultError.Fail("No client state!");
+ }
+
+ if (string.IsNullOrWhiteSpace(channelId))
+ {
+ return ResultError.Fail("Channel id is empty!");
+ }
+
+ var serializedContext = JsonSerializer.SerializeToNode(External.State.InnerTubeContext);
+ var payload = new JsonObject { { "context", serializedContext }, { "browseId", channelId } };
+ var requestMessage = new HttpRequestMessage
+ {
+ Method = HttpMethod.Post,
+ RequestUri = new Uri($"{NetworkService.Origin}/youtubei/v1/browse?key={External.State.InnertubeApiKey}"),
+ Content = new StringContent(payload.ToJsonString(), Encoding.UTF8, MediaTypeNames.Application.Json)
+ };
+ var responseResult = await NetworkService.MakeRequestAsync(requestMessage, this);
+ if (!responseResult.IsSuccess)
+ {
+ return responseResult.Error ?? ResultError.Fail("Request failed!");
+ }
+
+ return ChannelJsonParser.ParseJsonToChannelData(responseResult.Value);
+ }
public void Dispose()
{
_httpClient?.Dispose();
}
+
+ private async Task> GetCurrentAccountIdAsync()
+ {
+ if (External.State is not { LoggedIn: true })
+ {
+ return ResultError.Fail("Client not logged in!");
+ }
+
+ var httpRequest = new HttpRequestMessage
+ {
+ Method = HttpMethod.Post,
+ RequestUri = new Uri($"{NetworkService.Origin}/youtubei/v1/account/account_menu")
+ };
+ var serializedContext = JsonSerializer.SerializeToNode(External.State.InnerTubeContext);
+ var payload = new JsonObject { { "context", serializedContext } };
+ httpRequest.Content = new StringContent(payload.ToJsonString(), Encoding.UTF8, MediaTypeNames.Application.Json);
+ var responseResult = await NetworkService.MakeRequestAsync(httpRequest, this);
+
+ if (!responseResult.IsSuccess)
+ {
+ return responseResult.Error ?? ResultError.Fail("Request failed!");
+ }
+
+ return JsonAccountParser.ParseAccountId(responseResult.Value);
+ }
+
+ private async Task> GetDatasyncIds()
+ {
+ if (External.State is not { LoggedIn: true } || CookieContainer.Count == 0)
+ {
+ return ResultError.Fail("Client is not logged in, requires logged in client for this endpoint (/getDatasyncIdsEndpoint).");
+ }
+
+ var httpRequest = new HttpRequestMessage
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri($"{NetworkService.Origin}/getDatasyncIdsEndpoint")
+ };
+ var responseResult = await NetworkService.MakeRequestAsync(httpRequest, this);
+ if (!responseResult.IsSuccess)
+ {
+ return responseResult.Error ?? ResultError.Fail("Request failed!");
+ }
+
+ var datasyncIdsJson = JsonNode.Parse(responseResult.Value.Replace(")]}'", ""));
+
+ var isLoggedOut = datasyncIdsJson?["responseContext"]?["mainAppWebResponseContext"]?["loggedOut"]
+ .Deserialize() ?? true;
+ if (!isLoggedOut)
+ {
+ return datasyncIdsJson?["datasyncIds"].Deserialize() ?? [];
+ }
+
+ return ResultError.Fail("Failed to get datasyncIds! Client not logged in.");
+ }
}
\ No newline at end of file
diff --git a/Manager.YouTube/YouTubeService.cs b/Manager.YouTube/YouTubeService.cs
deleted file mode 100644
index 14c398c..0000000
--- a/Manager.YouTube/YouTubeService.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Manager.YouTube;
-
-public class YouTubeService()
-{
-
-}
\ No newline at end of file