diff --git a/Manager.App/Components/Application/Dev/CipherDev.razor b/Manager.App/Components/Application/Dev/CipherDev.razor
new file mode 100644
index 0000000..42d9609
--- /dev/null
+++ b/Manager.App/Components/Application/Dev/CipherDev.razor
@@ -0,0 +1,13 @@
+@using Manager.App.Models.System
+@using Manager.App.Services.System
+
+@inject ISnackbar Snackbar
+@inject ClientService ClientService
+
+Cipher manager
+
+
+
+ Exec
+
\ No newline at end of file
diff --git a/Manager.App/Components/Application/Dev/CipherDev.razor.cs b/Manager.App/Components/Application/Dev/CipherDev.razor.cs
new file mode 100644
index 0000000..dd7d7a9
--- /dev/null
+++ b/Manager.App/Components/Application/Dev/CipherDev.razor.cs
@@ -0,0 +1,43 @@
+using Manager.App.Models.System;
+using Manager.YouTube.Util.Cipher;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Web;
+using MudBlazor;
+
+namespace Manager.App.Components.Application.Dev;
+
+public partial class CipherDev : ComponentBase
+{
+ private YouTubeClientItem? _selectedClient;
+
+ private async Task ExecCipher(MouseEventArgs obj)
+ {
+ if (_selectedClient == null)
+ {
+ Snackbar.Add("No client selected", Severity.Warning);
+ return;
+ }
+
+ var ytClientResult = await ClientService.LoadClientByIdAsync(_selectedClient.Id);
+ if (!ytClientResult.IsSuccess)
+ {
+ Snackbar.Add(ytClientResult.Error?.Description ?? "Failed to get the client!", Severity.Error);
+ return;
+ }
+
+ var ytClient = ytClientResult.Value;
+ if (ytClient.State == null)
+ {
+ Snackbar.Add("Client state is null!", Severity.Warning);
+ return;
+ }
+
+ var decoder = await CipherManager.GetDecoderAsync(ytClient.State, ytClient);
+ }
+
+ private async Task> SearchClientsAsync(string? search, CancellationToken cancellationToken)
+ {
+ var searchResults = await ClientService.GetClientsAsync(search, cancellationToken: cancellationToken);
+ return !searchResults.IsSuccess ? [] : searchResults.Value;
+ }
+}
\ No newline at end of file
diff --git a/Manager.App/Components/Application/Dev/DevelopmentVideo.razor b/Manager.App/Components/Application/Dev/DevelopmentVideo.razor
index bd8c941..1495d71 100644
--- a/Manager.App/Components/Application/Dev/DevelopmentVideo.razor
+++ b/Manager.App/Components/Application/Dev/DevelopmentVideo.razor
@@ -5,7 +5,7 @@
@inject ClientService ClientService
Video data
-
+
diff --git a/Manager.App/Components/Pages/Development.razor b/Manager.App/Components/Pages/Development.razor
index 2a3a512..bc2da8f 100644
--- a/Manager.App/Components/Pages/Development.razor
+++ b/Manager.App/Components/Pages/Development.razor
@@ -9,4 +9,7 @@
+
+
+
\ No newline at end of file
diff --git a/Manager.App/Services/LibraryService.cs b/Manager.App/Services/LibraryService.cs
index 0e3ef22..3f7fa02 100644
--- a/Manager.App/Services/LibraryService.cs
+++ b/Manager.App/Services/LibraryService.cs
@@ -144,9 +144,9 @@ public class LibraryService : ILibraryService
try
{
await using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
- var channel = await context.Channels
+ var channel = await context.Channels.AsSplitQuery()
.Include(c => c.ClientAccount)
- .ThenInclude(p => p!.HttpCookies)
+ .ThenInclude(p => p!.HttpCookies)
.Include(f => f.Files)
.FirstOrDefaultAsync(c => c.Id == id, cancellationToken);
diff --git a/Manager.YouTube/Models/Innertube/StreamingData.cs b/Manager.YouTube/Models/Innertube/StreamingData.cs
index 56947d0..4f39c6b 100644
--- a/Manager.YouTube/Models/Innertube/StreamingData.cs
+++ b/Manager.YouTube/Models/Innertube/StreamingData.cs
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
+using Manager.YouTube.Util.Converters;
namespace Manager.YouTube.Models.Innertube;
@@ -8,6 +9,7 @@ public class StreamingData
[JsonPropertyName("expiresInSeconds")]
public int ExpiresInSeconds { get; set; }
[JsonPropertyName("serverAbrStreamingUrl")]
+ [JsonConverter(typeof(JsonUrlEscapeConverter))]
public string ServerAbrStreamingUrl { get; set; } = "";
[JsonPropertyName("formats")]
public List Formats { get; set; } = [];
diff --git a/Manager.YouTube/Util/Cipher/CipherDecoder.cs b/Manager.YouTube/Util/Cipher/CipherDecoder.cs
index b9c0663..9f43ae2 100644
--- a/Manager.YouTube/Util/Cipher/CipherDecoder.cs
+++ b/Manager.YouTube/Util/Cipher/CipherDecoder.cs
@@ -112,7 +112,7 @@ public partial class CipherDecoder
}
- [GeneratedRegex(@"(\w+)=function\(\w+\){(\w+)=\2\.split\(\x22{2}\);.*?return\s+\2\.join\(\x22{2}\)}")]
+ [GeneratedRegex(@"([A-Za-z_$][A-Za-z0-9_$]*)=function\([A-Za-z_$][A-Za-z0-9_$]*\)\{\s*([A-Za-z_$][A-Za-z0-9_$]*)=\2\.split\(\x22\x22\);[\s\S]*?return\s+\2\.join\(\x22\x22\)\s*\}")]
private static partial Regex FunctionBodyRegex();
[GeneratedRegex("([\\$_\\w]+).\\w+\\(\\w+,\\d+\\);")]
private static partial Regex DefinitionBodyRegex();
diff --git a/Manager.YouTube/Util/Converters/JsonUrlEscapeConverter.cs b/Manager.YouTube/Util/Converters/JsonUrlEscapeConverter.cs
new file mode 100644
index 0000000..f5efd32
--- /dev/null
+++ b/Manager.YouTube/Util/Converters/JsonUrlEscapeConverter.cs
@@ -0,0 +1,27 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+
+namespace Manager.YouTube.Util.Converters;
+
+public partial class JsonUrlEscapeConverter : JsonConverter
+{
+ public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var url = reader.GetString();
+ if (string.IsNullOrWhiteSpace(url))
+ {
+ return url;
+ }
+
+ return UrlPatternRegex().IsMatch(url) ? Uri.UnescapeDataString(url) : url;
+ }
+
+ public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value);
+ }
+
+ [GeneratedRegex("^(https?|ftp)://", RegexOptions.IgnoreCase | RegexOptions.Compiled, "nl-NL")]
+ private static partial Regex UrlPatternRegex();
+}
\ No newline at end of file