diff --git a/Manager.App/Components/Application/Dev/DevelopmentVideo.razor b/Manager.App/Components/Application/Dev/DevelopmentVideo.razor
new file mode 100644
index 0000000..bd8c941
--- /dev/null
+++ b/Manager.App/Components/Application/Dev/DevelopmentVideo.razor
@@ -0,0 +1,16 @@
+@using Manager.App.Models.System
+@using Manager.App.Services.System
+
+@inject ISnackbar Snackbar
+@inject ClientService ClientService
+
+Video data
+
+
+
+
+
+
+ Get data
+
\ No newline at end of file
diff --git a/Manager.App/Components/Application/Dev/DevelopmentVideo.razor.cs b/Manager.App/Components/Application/Dev/DevelopmentVideo.razor.cs
new file mode 100644
index 0000000..0fa7ca1
--- /dev/null
+++ b/Manager.App/Components/Application/Dev/DevelopmentVideo.razor.cs
@@ -0,0 +1,56 @@
+using Manager.App.Models.System;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Web;
+using MudBlazor;
+
+namespace Manager.App.Components.Application.Dev;
+
+public partial class DevelopmentVideo : ComponentBase
+{
+ private YouTubeClientItem? _selectedClient;
+ private string _videoId = "";
+
+ private async Task> SearchClientsAsync(string? search, CancellationToken cancellationToken)
+ {
+ var searchResults = await ClientService.GetClientsAsync(search, cancellationToken: cancellationToken);
+ return !searchResults.IsSuccess ? [] : searchResults.Value;
+ }
+
+ private async Task GetDataAsync(MouseEventArgs obj)
+ {
+ if (_selectedClient == null)
+ {
+ Snackbar.Add("No client selected!", Severity.Warning);
+ return;
+ }
+
+ if (string.IsNullOrWhiteSpace(_videoId))
+ {
+ Snackbar.Add("No video ID set!", Severity.Warning);
+ return;
+ }
+
+ if (_videoId.Length != 11)
+ {
+ Snackbar.Add("Video ID needs to have an length of 11 chars!", Severity.Warning);
+ }
+
+ var clientResult = await ClientService.LoadClientByIdAsync(_selectedClient.Id);
+ if (!clientResult.IsSuccess)
+ {
+ Snackbar.Add(clientResult.Error?.Description ?? $"Failed to get client with id: {_selectedClient.Id}", Severity.Error);
+ return;
+ }
+
+ var ytClient = clientResult.Value;
+ var videoResult = await ytClient.GetVideoByIdAsync(_videoId);
+ if (!videoResult.IsSuccess)
+ {
+ Snackbar.Add(videoResult.Error?.Description ?? $"Failed to load video: {_videoId}", Severity.Error);
+ return;
+ }
+
+ var ytVideo = videoResult.Value;
+ Snackbar.Add($"Loaded video {ytVideo.Title}", Severity.Success);
+ }
+}
\ No newline at end of file
diff --git a/Manager.App/Components/Pages/Development.razor b/Manager.App/Components/Pages/Development.razor
index 338665e..2a3a512 100644
--- a/Manager.App/Components/Pages/Development.razor
+++ b/Manager.App/Components/Pages/Development.razor
@@ -6,4 +6,7 @@
+
+
+
\ No newline at end of file
diff --git a/Manager.App/Services/LibraryService.cs b/Manager.App/Services/LibraryService.cs
index 4e57e67..0e3ef22 100644
--- a/Manager.App/Services/LibraryService.cs
+++ b/Manager.App/Services/LibraryService.cs
@@ -101,6 +101,7 @@ public class LibraryService : ILibraryService
}
else
{
+ context.HttpCookies.RemoveRange(context.HttpCookies.Where(x => x.ClientId == client.Id));
context.ClientAccounts.Add(dbClient);
}
diff --git a/Manager.App/Services/System/ClientService.cs b/Manager.App/Services/System/ClientService.cs
index 4b3525d..aa6330e 100644
--- a/Manager.App/Services/System/ClientService.cs
+++ b/Manager.App/Services/System/ClientService.cs
@@ -40,7 +40,7 @@ public class ClientService(IServiceScopeFactory scopeFactory, ILogger> GetClientsAsync(string search, int offset = 0, int limit = 10, CancellationToken cancellationToken = default)
+ public async Task> GetClientsAsync(string? search, int offset = 0, int limit = 10, CancellationToken cancellationToken = default)
{
if (_libraryService == null)
{
diff --git a/Manager.YouTube/Models/Innertube/ColorInfo.cs b/Manager.YouTube/Models/Innertube/ColorInfo.cs
index afbd20a..642fa7b 100644
--- a/Manager.YouTube/Models/Innertube/ColorInfo.cs
+++ b/Manager.YouTube/Models/Innertube/ColorInfo.cs
@@ -1,8 +1,13 @@
+using System.Text.Json.Serialization;
+
namespace Manager.YouTube.Models.Innertube;
public class ColorInfo
{
+ [JsonPropertyName("primaries")]
public string Primaries { get; set; } = "";
+ [JsonPropertyName("transferCharacteristics")]
public string TransferCharacteristics { get; set; } = "";
+ [JsonPropertyName("matrixCoefficients")]
public string MatrixCoefficients { get; set; } = "";
}
\ No newline at end of file
diff --git a/Manager.YouTube/Models/Innertube/Range.cs b/Manager.YouTube/Models/Innertube/Range.cs
index 38d6fdc..2f6c384 100644
--- a/Manager.YouTube/Models/Innertube/Range.cs
+++ b/Manager.YouTube/Models/Innertube/Range.cs
@@ -1,7 +1,11 @@
+using System.Text.Json.Serialization;
+
namespace Manager.YouTube.Models.Innertube;
public class Range
{
+ [JsonPropertyName("start")]
public uint Start { get; set; }
+ [JsonPropertyName("end")]
public uint End { get; set; }
}
\ No newline at end of file
diff --git a/Manager.YouTube/Models/Innertube/StreamingData.cs b/Manager.YouTube/Models/Innertube/StreamingData.cs
index a1135f6..56947d0 100644
--- a/Manager.YouTube/Models/Innertube/StreamingData.cs
+++ b/Manager.YouTube/Models/Innertube/StreamingData.cs
@@ -1,10 +1,16 @@
+using System.Text.Json.Serialization;
+
namespace Manager.YouTube.Models.Innertube;
public class StreamingData
{
public DateTime FetchedUtc { get; set; } = DateTime.UtcNow;
+ [JsonPropertyName("expiresInSeconds")]
public int ExpiresInSeconds { get; set; }
+ [JsonPropertyName("serverAbrStreamingUrl")]
public string ServerAbrStreamingUrl { get; set; } = "";
+ [JsonPropertyName("formats")]
public List Formats { get; set; } = [];
+ [JsonPropertyName("adaptiveFormats")]
public List AdaptiveFormats { get; set; } = [];
}
\ No newline at end of file
diff --git a/Manager.YouTube/Models/Innertube/StreamingFormat.cs b/Manager.YouTube/Models/Innertube/StreamingFormat.cs
index 8d84adc..592738d 100644
--- a/Manager.YouTube/Models/Innertube/StreamingFormat.cs
+++ b/Manager.YouTube/Models/Innertube/StreamingFormat.cs
@@ -1,31 +1,59 @@
+using System.Text.Json.Serialization;
+
namespace Manager.YouTube.Models.Innertube;
public class StreamingFormat
{
+ [JsonPropertyName("itag")]
public int Itag { get; set; }
+ [JsonPropertyName("url")]
public string? Url { get; set; }
+ [JsonPropertyName("mimeType")]
public string MimeType { get; set; } = "";
+ [JsonPropertyName("bitrate")]
public uint Bitrate { get; set; }
+ [JsonPropertyName("width")]
public uint? Width { get; set; }
+ [JsonPropertyName("height")]
public uint? Height { get; set; }
+ [JsonPropertyName("initRange")]
public Range? InitRange { get; set; }
+ [JsonPropertyName("indexRange")]
public Range? IndexRange { get; set; }
+ [JsonPropertyName("lastModified")]
public long LastModified { get; set; }
+ [JsonPropertyName("contentLength")]
public long ContentLength { get; set; }
+ [JsonPropertyName("quality")]
public string Quality { get; set; } = "";
+ [JsonPropertyName("xtags")]
public string? Xtags { get; set; }
+ [JsonPropertyName("fps")]
public uint Fps { get; set; }
+ [JsonPropertyName("qualityLabel")]
public string QualityLabel { get; set; } = "";
+ [JsonPropertyName("projectionType")]
public string ProjectionType { get; set; } = "";
+ [JsonPropertyName("averagebitrate")]
public uint? AverageBitrate { get; set; }
+ [JsonPropertyName("highReplication")]
public bool? HighReplication { get; set; }
+ [JsonPropertyName("colorInfo")]
public ColorInfo? ColorInfo { get; set; }
+ [JsonPropertyName("audioQuality")]
public string? AudioQuality { get; set; } = "";
+ [JsonPropertyName("approxDurationMs")]
public long ApproxDurationMs { get; set; }
+ [JsonPropertyName("audioSampleRate")]
public int? AudioSampleRate { get; set; }
+ [JsonPropertyName("audioChannels")]
public int? AudioChannels { get; set; }
+ [JsonPropertyName("loudnessDb")]
public double? LoudnessDb { get; set; }
+ [JsonPropertyName("isDrc")]
public bool? IsDrc { get; set; }
+ [JsonPropertyName("signatureCipher")]
public string? SignatureCipher { get; set; }
+ [JsonPropertyName("qualityOrdinal")]
public string QualityOrdinal { get; set; } = "";
}
\ No newline at end of file
diff --git a/Manager.YouTube/Models/Innertube/WebImage.cs b/Manager.YouTube/Models/Innertube/WebImage.cs
index 37e282b..8cd8101 100644
--- a/Manager.YouTube/Models/Innertube/WebImage.cs
+++ b/Manager.YouTube/Models/Innertube/WebImage.cs
@@ -1,8 +1,13 @@
+using System.Text.Json.Serialization;
+
namespace Manager.YouTube.Models.Innertube;
public class WebImage
{
+ [JsonPropertyName("width")]
public int Width { get; set; }
+ [JsonPropertyName("height")]
public int Height { get; set; }
+ [JsonPropertyName("url")]
public string Url { get; set; } = "";
}
\ No newline at end of file
diff --git a/Manager.YouTube/Models/Parser/YouTubeVideoData.cs b/Manager.YouTube/Models/Parser/YouTubeVideoData.cs
index 190cea2..0aae6ce 100644
--- a/Manager.YouTube/Models/Parser/YouTubeVideoData.cs
+++ b/Manager.YouTube/Models/Parser/YouTubeVideoData.cs
@@ -4,6 +4,6 @@ namespace Manager.YouTube.Models.Parser;
public class YouTubeVideoData
{
- public JsonObject? YouTubePlayerData { get; set; }
- public JsonObject? YouTubeInitialData { get; set; }
+ public JsonNode? YouTubePlayerData { get; set; }
+ public JsonNode? YouTubeInitialData { get; set; }
}
\ No newline at end of file
diff --git a/Manager.YouTube/Parsers/HtmlParser.cs b/Manager.YouTube/Parsers/HtmlParser.cs
index cf22929..7200aba 100644
--- a/Manager.YouTube/Parsers/HtmlParser.cs
+++ b/Manager.YouTube/Parsers/HtmlParser.cs
@@ -74,8 +74,8 @@ public static class HtmlParser
{
return new YouTubeVideoData
{
- YouTubePlayerData = parsedPlayerInitialData?.AsObject(),
- YouTubeInitialData = parsedInitialData?.AsObject()
+ YouTubePlayerData = parsedPlayerInitialData,
+ YouTubeInitialData = parsedInitialData
};
}
catch (Exception e)
diff --git a/Manager.YouTube/Parsers/Json/JsonParser.cs b/Manager.YouTube/Parsers/Json/JsonParser.cs
index def4fa8..8b4130e 100644
--- a/Manager.YouTube/Parsers/Json/JsonParser.cs
+++ b/Manager.YouTube/Parsers/Json/JsonParser.cs
@@ -12,45 +12,58 @@ public static class JsonParser
.Select(image => new WebImage { Width = image.GetProperty("width").GetInt32(), Height = image.GetProperty("height").GetInt32(), Url = image.GetProperty("url").GetString() ?? "" })
.ToList();
- public static string ExtractTextOrHtml(JsonElement element)
+ public static string ExtractTextOrHtml(JsonNode? node)
{
+ if (node is not JsonObject nodeObj)
+ {
+ return "";
+ }
+
// Case 1: Simple text (no formatting)
- if (element.TryGetProperty("simpleText", out var simpleText))
- return simpleText.GetString() ?? string.Empty;
+ if (nodeObj.TryGetPropertyValue("simpleText", out var simpleText))
+ return simpleText?.GetValue() ?? string.Empty;
// Case 2: Runs (formatted text segments)
- if (element.TryGetProperty("runs", out var runs) && runs.ValueKind == JsonValueKind.Array)
+ if (nodeObj.TryGetPropertyValue("runs", out var runs) && runs != null && runs.GetValueKind() == JsonValueKind.Array)
{
var sb = new StringBuilder();
- foreach (var run in runs.EnumerateArray())
+ foreach (var runNode in runs.AsArray())
{
- var text = run.GetProperty("text").GetString() ?? string.Empty;
+ if (runNode is not JsonObject run)
+ {
+ continue;
+ }
+
+ var text = runNode["text"]?.GetValue() ?? string.Empty;
var formatted = System.Net.WebUtility.HtmlEncode(text);
- var bold = run.TryGetProperty("bold", out var boldProp) && boldProp.GetBoolean();
- var italic = run.TryGetProperty("italic", out var italicProp) && italicProp.GetBoolean();
- var underline = run.TryGetProperty("underline", out var underlineProp) && underlineProp.GetBoolean();
- var strikethrough = run.TryGetProperty("strikethrough", out var strikeProp) && strikeProp.GetBoolean();
+ var bold = run.TryGetPropertyValue("bold", out var boldNode) && boldNode is JsonValue bv && bv.GetValue();
+
+ var italic = run.TryGetPropertyValue("italic", out var italicNode) && italicNode is JsonValue iv && iv.GetValue();
+
+ var underline = run.TryGetPropertyValue("underline", out var underlineNode) && underlineNode is JsonValue uv && uv.GetValue();
+
+ var strikethrough = run.TryGetPropertyValue("strikethrough", out var strikeNode) && strikeNode is JsonValue sv && sv.GetValue();
if (bold) formatted = $"{formatted}";
if (italic) formatted = $"{formatted}";
if (underline) formatted = $"{formatted}";
if (strikethrough) formatted = $"{formatted}";
- if (run.TryGetProperty("navigationEndpoint", out var nav) &&
- nav.TryGetProperty("url", out var urlProp))
+ if (run.TryGetPropertyValue("navigationEndpoint", out var nav) && nav is JsonObject navObj &&
+ navObj.TryGetPropertyValue("url", out var urlProp))
{
- var url = urlProp.GetString();
+ var url = urlProp?.GetValue();
if (!string.IsNullOrEmpty(url))
formatted = $"{formatted}";
}
- if (run.TryGetProperty("emoji", out var emoji) && emoji.ValueKind == JsonValueKind.Object)
+ if (run.TryGetPropertyValue("emoji", out var emoji) && emoji is JsonObject emojiObj)
{
- if (emoji.TryGetProperty("url", out var emojiUrl))
+ if (emojiObj.TryGetPropertyValue("url", out var emojiUrl))
{
- var src = emojiUrl.GetString();
+ var src = emojiUrl?.GetValue();
if (!string.IsNullOrEmpty(src))
formatted = $"
";
}
@@ -64,10 +77,15 @@ public static class JsonParser
return string.Empty;
}
-
- public static List ExtractWebImages(JsonElement element)
+
+ public static List ExtractWebImages(JsonNode? node)
{
- var thumbnailsArray = element.GetProperty("thumbnail").GetProperty("thumbnails");
- return thumbnailsArray.Deserialize>() ?? [];
+ if (node == null)
+ {
+ return [];
+ }
+
+ var thumbnailsArray = node["thumbnails"];
+ return thumbnailsArray?.Deserialize>() ?? [];
}
}
\ No newline at end of file
diff --git a/Manager.YouTube/Parsers/Json/VideoJsonParser.cs b/Manager.YouTube/Parsers/Json/VideoJsonParser.cs
index eadcecb..2f17fd9 100644
--- a/Manager.YouTube/Parsers/Json/VideoJsonParser.cs
+++ b/Manager.YouTube/Parsers/Json/VideoJsonParser.cs
@@ -14,7 +14,7 @@ public static class VideoJsonParser
public static Result ParseVideoData(YouTubeVideoData videoData)
{
- if (videoData.YouTubeInitialData == null || videoData.YouTubeInitialData.Count == 0)
+ if (videoData.YouTubePlayerData == null)
{
return ResultError.Fail("No initial video data found!");
}
@@ -22,7 +22,7 @@ public static class VideoJsonParser
YouTubeVideo? video;
try
{
- video = videoData.YouTubeInitialData.Deserialize(VideoParserOptions);
+ video = videoData.YouTubePlayerData.Deserialize(VideoParserOptions);
}
catch (Exception e)
{
diff --git a/Manager.YouTube/Util/Cipher/CipherManager.cs b/Manager.YouTube/Util/Cipher/CipherManager.cs
index 5db1300..3a76d07 100644
--- a/Manager.YouTube/Util/Cipher/CipherManager.cs
+++ b/Manager.YouTube/Util/Cipher/CipherManager.cs
@@ -39,7 +39,7 @@ public static class CipherManager
private static string GetCipherVersion(string relativePlayerUrl)
{
- var split = relativePlayerUrl.Split('/');
+ var split = relativePlayerUrl.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var v = split[2];
var lang = split[4];
return $"{v}_{lang}";
diff --git a/Manager.YouTube/Util/Converters/NumericJsonConverter.cs b/Manager.YouTube/Util/Converters/NumericJsonConverter.cs
new file mode 100644
index 0000000..18e672e
--- /dev/null
+++ b/Manager.YouTube/Util/Converters/NumericJsonConverter.cs
@@ -0,0 +1,39 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Manager.YouTube.Util.Converters;
+
+public class NumericJsonConverter : JsonConverter where T : struct, IConvertible
+{
+ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ try
+ {
+ if (reader.TokenType == JsonTokenType.Number)
+ {
+ // Direct numeric value
+ return (T)Convert.ChangeType(reader.GetDouble(), typeof(T));
+ }
+
+ if (reader.TokenType == JsonTokenType.String)
+ {
+ var str = reader.GetString();
+ if (string.IsNullOrWhiteSpace(str))
+ throw new JsonException("Empty string cannot be converted to a number.");
+
+ return (T)Convert.ChangeType(str, typeof(T));
+ }
+
+ throw new JsonException($"Unexpected token {reader.TokenType} for type {typeof(T)}.");
+ }
+ catch (Exception ex)
+ {
+ throw new JsonException($"Error converting value to {typeof(T)}.", ex);
+ }
+ }
+
+ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
+ {
+ writer.WriteNumberValue(Convert.ToDouble(value));
+ }
+}
\ No newline at end of file
diff --git a/Manager.YouTube/Util/Converters/YouTubeVideoJsonConverter.cs b/Manager.YouTube/Util/Converters/YouTubeVideoJsonConverter.cs
index d7f73eb..f6eea63 100644
--- a/Manager.YouTube/Util/Converters/YouTubeVideoJsonConverter.cs
+++ b/Manager.YouTube/Util/Converters/YouTubeVideoJsonConverter.cs
@@ -1,5 +1,6 @@
using System.Runtime.Serialization;
using System.Text.Json;
+using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using DotBased.Logging;
using Manager.YouTube.Models;
@@ -11,56 +12,74 @@ namespace Manager.YouTube.Util.Converters;
public class YouTubeVideoJsonConverter : JsonConverter
{
private readonly ILogger _logger = LogService.RegisterLogger();
+ private readonly JsonSerializerOptions _serializerOptions = new()
+ {
+ Converters = {
+ new NumericJsonConverter(),
+ new NumericJsonConverter(),
+ new NumericJsonConverter(),
+ new NumericJsonConverter(),
+ new NumericJsonConverter() },
+ PropertyNameCaseInsensitive = true
+ };
public override YouTubeVideo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
- using var document = JsonDocument.ParseValue(ref reader);
- var root = document.RootElement;
+ var node = JsonNode.Parse(ref reader);
+ if (node == null)
+ {
+ throw new SerializationException("Failed to parse JSON reader.");
+ }
- var playabilityStatus = root.GetProperty("playabilityStatus");
- var streamingData = root.GetProperty("streamingData");
- var videoDetails = root.GetProperty("videoDetails");
- var playerConfigJson = root.GetProperty("playerConfig");
- var microformat = root.GetProperty("microformat").GetProperty("playerMicroformatRenderer");
+ var rootObject = node.AsObject();
- var videoId = videoDetails.GetProperty("videoId").GetString() ?? microformat.GetProperty("externalVideoId").GetString();
+ var playabilityStatus = rootObject["playabilityStatus"];
+ var streamingDataJson = rootObject["streamingData"];
+ var videoDetails = rootObject["videoDetails"];
+ var playerConfigJson = rootObject["playerConfig"];
+ var microformat = rootObject["microformat"]?["playerMicroformatRenderer"];
+
+ var videoId = videoDetails?["videoId"]?.GetValue() ?? microformat?["externalVideoId"]?.GetValue();
if (string.IsNullOrEmpty(videoId))
{
throw new SerializationException("Failed to get videoId");
}
+
+ var thumbnails = JsonParser.ExtractWebImages(videoDetails?["thumbnail"]);
+ thumbnails.AddRange(JsonParser.ExtractWebImages(microformat?["thumbnail"]));
- var thumbnails = JsonParser.ExtractWebImages(videoDetails.GetProperty("thumbnail"));
- thumbnails.AddRange(JsonParser.ExtractWebImages(microformat.GetProperty("thumbnail")));
+ var streamingData = streamingDataJson.Deserialize(_serializerOptions);
+ var playerConfig = ExtractPlayerConfig(playerConfigJson);
var video = new YouTubeVideo
{
VideoId = videoId,
- Title = JsonParser.ExtractTextOrHtml(microformat.GetProperty("title")),
- Description = JsonParser.ExtractTextOrHtml(microformat.GetProperty("description")),
- ViewCount = videoDetails.GetProperty("viewCount").GetInt32(),
- LikeCount = videoDetails.GetProperty("likeCount").GetInt32(),
- ChannelId = videoDetails.GetProperty("channelId").GetString() ?? "",
- Author = JsonParser.ExtractTextOrHtml(videoDetails.GetProperty("author")),
- PlayabilityStatus = playabilityStatus.GetProperty("status").GetString() ?? "",
- LengthSeconds = videoDetails.GetProperty("lengthSeconds").GetInt32(),
- Keywords = videoDetails.GetProperty("keywords").EnumerateArray().Select(v => v.GetString()).Cast().ToArray(),
- IsOwnerViewing = videoDetails.GetProperty("isOwnerViewing").GetBoolean(),
- AllowRating = videoDetails.GetProperty("allowRating").GetBoolean(),
- IsCrawlable = videoDetails.GetProperty("isCrawlable").GetBoolean(),
- IsPrivate = videoDetails.GetProperty("isPrivate").GetBoolean(),
- IsUnpluggedCorpus = videoDetails.GetProperty("isUnpluggedCorpus").GetBoolean(),
- IsLive = videoDetails.GetProperty("isLiveContent").GetBoolean(),
- IsFamilySave = microformat.GetProperty("isFamilySave").GetBoolean(),
- AvailableCountries = microformat.GetProperty("availableCountries").EnumerateArray().Select(v => v.GetString()).Cast().ToArray(),
- IsUnlisted = microformat.GetProperty("isUnlisted").GetBoolean(),
- HasYpcMetadata = microformat.GetProperty("hasYpcMetadata").GetBoolean(),
- PublishDate = microformat.GetProperty("publishDate").GetDateTime(),
- UploadDate = microformat.GetProperty("uploadDate").GetDateTime(),
- IsShortsEligible = microformat.GetProperty("isShortsEligible").GetBoolean(),
- Category = microformat.GetProperty("category").GetString() ?? "",
- StreamingData = streamingData.Deserialize(),
+ Title = JsonParser.ExtractTextOrHtml(microformat?["title"]),
+ Description = JsonParser.ExtractTextOrHtml(microformat?["description"]),
+ ViewCount = long.TryParse(microformat?["viewCount"]?.GetValue(), out var viewCountParsed) ? viewCountParsed : -1,
+ LikeCount = long.TryParse(microformat?["likeCount"]?.GetValue(), out var likeCountParsed) ? likeCountParsed : -1,
+ ChannelId = videoDetails?["channelId"]?.GetValue() ?? "",
+ Author = videoDetails?["author"]?.GetValue() ?? "",
+ PlayabilityStatus = playabilityStatus?["status"]?.GetValue() ?? "",
+ LengthSeconds = long.TryParse(videoDetails?["lengthSeconds"]?.GetValue(), out var lengthSecondsParsed) ? lengthSecondsParsed : -1,
+ Keywords = videoDetails?["keywords"]?.AsArray().Select(v => v?.GetValue() ?? "").ToArray() ?? [],
+ IsOwnerViewing = videoDetails?["isOwnerViewing"]?.GetValue() ?? false,
+ AllowRating = videoDetails?["allowRating"]?.GetValue() ?? false,
+ IsCrawlable = videoDetails?["isCrawlable"]?.GetValue() ?? false,
+ IsPrivate = videoDetails?["isPrivate"]?.GetValue() ?? false,
+ IsUnpluggedCorpus = videoDetails?["isUnpluggedCorpus"]?.GetValue() ?? false,
+ IsLive = videoDetails?["isLiveContent"]?.GetValue() ?? false,
+ IsFamilySave = microformat?["isFamilySave"]?.GetValue() ?? false,
+ AvailableCountries = microformat?["availableCountries"]?.AsArray().Select(v => v?.GetValue() ?? "").ToArray() ?? [],
+ IsUnlisted = microformat?["isUnlisted"]?.GetValue() ?? false,
+ HasYpcMetadata = microformat?["hasYpcMetadata"]?.GetValue() ?? false,
+ PublishDate = DateTime.TryParse(microformat?["publishDate"]?.GetValue(), out var parsedPublishDate) ? parsedPublishDate : DateTime.MinValue,
+ UploadDate = DateTime.TryParse(microformat?["uploadDate"]?.GetValue(), out var parsedUploadDate) ? parsedUploadDate : DateTime.MinValue,
+ IsShortsEligible = microformat?["isShortsEligible"]?.GetValue() ?? false,
+ Category = microformat?["category"]?.GetValue() ?? "",
+ StreamingData = streamingData,
Thumbnails = thumbnails,
- PlayerConfig = ExtractPlayerConfig(playerConfigJson)
+ PlayerConfig = playerConfig
};
return video;
@@ -71,23 +90,25 @@ public class YouTubeVideoJsonConverter : JsonConverter
throw new NotImplementedException("Converter only supports reading.");
}
- private PlayerConfig? ExtractPlayerConfig(JsonElement element)
+ private PlayerConfig? ExtractPlayerConfig(JsonNode? playerConfigNode)
{
+ if (playerConfigNode == null)
+ {
+ return null;
+ }
+
try
{
+ var playerConfigObj = playerConfigNode.AsObject();
var playerConfig = new PlayerConfig
{
- AudioLoudnessDb = element.GetProperty("audioConfig").GetProperty("loudnessDb").GetDouble(),
- AudioPerceptualLoudnessDb = element.GetProperty("audioConfig").GetProperty("perceptualLoudnessDb").GetDouble(),
- AudioEnablePerFormatLoudness = element.GetProperty("audioConfig").GetProperty("enablePerFormatLoudness")
- .GetBoolean(),
- MaxBitrate = element.GetProperty("streamSelectionConfig").GetProperty("maxBitrate").GetUInt32(),
- MaxReadAheadMediaTimeMs = element.GetProperty("mediaCommonConfig").GetProperty("dynamicReadaheadConfig")
- .GetProperty("maxReadAheadMediaTimeMs").GetUInt32(),
- MinReadAheadMediaTimeMs = element.GetProperty("mediaCommonConfig").GetProperty("dynamicReadaheadConfig")
- .GetProperty("minReadAheadMediaTimeMs").GetUInt32(),
- ReadAheadGrowthRateMs = element.GetProperty("mediaCommonConfig").GetProperty("dynamicReadaheadConfig")
- .GetProperty("readAheadGrowthRateMs").GetUInt32(),
+ AudioLoudnessDb = playerConfigObj["audioConfig"]?["loudnessDb"]?.GetValue() ?? 0,
+ AudioPerceptualLoudnessDb = playerConfigObj["audioConfig"]?["perceptualLoudnessDb"]?.GetValue() ?? 0,
+ AudioEnablePerFormatLoudness = playerConfigObj["audioConfig"]?["enablePerFormatLoudness"]?.GetValue() ?? false,
+ MaxBitrate = uint.TryParse(playerConfigObj["streamSelectionConfig"]?["maxBitrate"]?.GetValue(), out var parsedMaxBitrate) ? parsedMaxBitrate : 0,
+ MaxReadAheadMediaTimeMs = playerConfigObj["mediaCommonConfig"]?["dynamicReadaheadConfig"]?["maxReadAheadMediaTimeMs"]?.GetValue() ?? 0,
+ MinReadAheadMediaTimeMs = playerConfigObj["mediaCommonConfig"]?["dynamicReadaheadConfig"]?["minReadAheadMediaTimeMs"]?.GetValue() ?? 0,
+ ReadAheadGrowthRateMs = playerConfigObj["mediaCommonConfig"]?["dynamicReadaheadConfig"]?["readAheadGrowthRateMs"]?.GetValue() ?? 0,
};
return playerConfig;
}
diff --git a/Manager.YouTube/YouTubeClient.cs b/Manager.YouTube/YouTubeClient.cs
index f4c27d2..a5aacf2 100644
--- a/Manager.YouTube/YouTubeClient.cs
+++ b/Manager.YouTube/YouTubeClient.cs
@@ -175,10 +175,10 @@ public sealed class YouTubeClient : IDisposable
public async Task RotateCookiesPageAsync(string origin = NetworkService.Origin, int ytPid = 1)
{
- if (IsAnonymous)
+ /*if (IsAnonymous)
{
return ResultError.Fail("Anonymous clients cannot rotate cookies!");
- }
+ }*/
if (string.IsNullOrWhiteSpace(origin))
{
@@ -277,6 +277,12 @@ public sealed class YouTubeClient : IDisposable
return stateResult;
}
+ if (State is { LoggedIn: false })
+ {
+ _logger.Warning("Client is not logged in!");
+ return ResultError.Fail("Client login failed!");
+ }
+
var cookieRotationResult = await RotateCookiesPageAsync();
return !cookieRotationResult.IsSuccess ? cookieRotationResult : Result.Success();
}