Compare commits

...

2 Commits

Author SHA1 Message Date
max
4c04378080 [CHANGE] Added jint. Reworking decipher functionality 2025-10-28 19:08:59 +01:00
max
b5c701b971 [CHANGE] Reworked cipher stuff 2025-10-24 21:16:08 +02:00
12 changed files with 204 additions and 4 deletions

View File

@@ -0,0 +1,13 @@
@using Manager.App.Models.System
@using Manager.App.Services.System
@inject ISnackbar Snackbar
@inject ClientService ClientService
<MudText>Cipher manager</MudText>
<MudStack Row Spacing="2">
<MudAutocomplete T="YouTubeClientItem" Label="Client" @bind-Value="@_selectedClient" SearchFunc="SearchClientsAsync" ToStringFunc="@(i => i == null ? "null?" : $"{i.Name} ({i.Handle})")"
Variant="Variant.Outlined" ShowProgressIndicator ProgressIndicatorColor="Color.Primary">
</MudAutocomplete>
<MudButton OnClick="ExecCipher">Exec</MudButton>
</MudStack>

View File

@@ -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<IEnumerable<YouTubeClientItem>> SearchClientsAsync(string? search, CancellationToken cancellationToken)
{
var searchResults = await ClientService.GetClientsAsync(search, cancellationToken: cancellationToken);
return !searchResults.IsSuccess ? [] : searchResults.Value;
}
}

View File

@@ -5,7 +5,7 @@
@inject ClientService ClientService @inject ClientService ClientService
<MudText>Video data</MudText> <MudText>Video data</MudText>
<MudStack Spacing="2"> <MudStack Row Spacing="2">
<MudAutocomplete T="YouTubeClientItem" Label="Client" @bind-Value="@_selectedClient" SearchFunc="SearchClientsAsync" ToStringFunc="@(i => i == null ? "null?" : $"{i.Name} ({i.Handle})")" <MudAutocomplete T="YouTubeClientItem" Label="Client" @bind-Value="@_selectedClient" SearchFunc="SearchClientsAsync" ToStringFunc="@(i => i == null ? "null?" : $"{i.Name} ({i.Handle})")"
Variant="Variant.Outlined" ShowProgressIndicator ProgressIndicatorColor="Color.Primary"> Variant="Variant.Outlined" ShowProgressIndicator ProgressIndicatorColor="Color.Primary">
</MudAutocomplete> </MudAutocomplete>

View File

@@ -9,4 +9,7 @@
<MudTabPanel Text="Video"> <MudTabPanel Text="Video">
<DevelopmentVideo /> <DevelopmentVideo />
</MudTabPanel> </MudTabPanel>
<MudTabPanel Text="Cipher">
<CipherDev />
</MudTabPanel>
</MudTabs> </MudTabs>

View File

@@ -144,7 +144,7 @@ public class LibraryService : ILibraryService
try try
{ {
await using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken); await using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
var channel = await context.Channels var channel = await context.Channels.AsSplitQuery()
.Include(c => c.ClientAccount) .Include(c => c.ClientAccount)
.ThenInclude(p => p!.HttpCookies) .ThenInclude(p => p!.HttpCookies)
.Include(f => f.Files) .Include(f => f.Files)

View File

@@ -0,0 +1,58 @@
using System.Text;
using DotBased.Monads;
namespace Manager.YouTube.Interpreter;
public class JavaScriptEngineManager
{
private readonly PlayerEngineCollection _engines = [];
public async Task<Result<PlayerEngine>> GetPlayerEngine(string playerUrl)
{
if (string.IsNullOrEmpty(playerUrl))
{
return ResultError.Fail("player url is empty or null!");
}
var version = GetScriptVersion(playerUrl);
if (_engines.TryGetValue(version, out var engine))
{
return engine;
}
var playerJsSourceResult = await DownloadPlayerScriptAsync(playerUrl);
if (!playerJsSourceResult.IsSuccess)
{
return playerJsSourceResult.Error ?? ResultError.Fail("Download player script failed!");
}
return new PlayerEngine(version, playerJsSourceResult.Value);
}
private static string GetScriptVersion(string relativePlayerUrl)
{
var split = relativePlayerUrl.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var v = split[2];
var lang = split[4];
return $"{v}-{lang}";
}
private static async Task<Result<string>> DownloadPlayerScriptAsync(string relativeUrl, YouTubeClient? client = null)
{
var downloadRequest = new HttpRequestMessage(HttpMethod.Get, new Uri($"{NetworkService.Origin}/{relativeUrl}"));
var downloadResponse = await NetworkService.DownloadBytesAsync(downloadRequest, client);
if (!downloadResponse.IsSuccess)
{
return downloadResponse.Error ?? ResultError.Fail($"Failed to download script from url: {relativeUrl}");
}
var playerJs = Encoding.UTF8.GetString(downloadResponse.Value.Data);
if (string.IsNullOrWhiteSpace(playerJs))
{
return ResultError.Fail("Script value is empty!");
}
return playerJs;
}
}

View File

@@ -0,0 +1,42 @@
using DotBased.Logging;
using Jint;
namespace Manager.YouTube.Interpreter;
public class PlayerEngine
{
public string Version { get; set; }
public Engine JsEngine { get; set; }
private ILogger Logger { get; set; }
public PlayerEngine(string version, string script)
{
if (string.IsNullOrEmpty(version))
{
throw new ArgumentNullException(nameof(version));
}
if (string.IsNullOrEmpty(script))
{
throw new ArgumentNullException(nameof(script));
}
Logger = LogService.RegisterLogger(typeof(PlayerEngine), version);
Version = version;
JsEngine = new Engine().Execute(script).SetValue("log", new Action<object>(obj =>
{
var logStr = obj.ToString();
if (string.IsNullOrEmpty(logStr))
{
return;
}
Logger.Information(logStr);
}));
}
public void InitializePlayer()
{
JsEngine.Execute("createPlayer");
}
}

View File

@@ -0,0 +1,11 @@
using System.Collections.ObjectModel;
namespace Manager.YouTube.Interpreter;
public class PlayerEngineCollection : KeyedCollection<string, PlayerEngine>
{
protected override string GetKeyForItem(PlayerEngine item)
{
return item.Version;
}
}

View File

@@ -9,6 +9,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DotBased" Version="1.0.0" /> <PackageReference Include="DotBased" Version="1.0.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.2" /> <PackageReference Include="HtmlAgilityPack" Version="1.12.2" />
<PackageReference Include="Jint" Version="4.4.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,4 +1,5 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Manager.YouTube.Util.Converters;
namespace Manager.YouTube.Models.Innertube; namespace Manager.YouTube.Models.Innertube;
@@ -8,6 +9,7 @@ public class StreamingData
[JsonPropertyName("expiresInSeconds")] [JsonPropertyName("expiresInSeconds")]
public int ExpiresInSeconds { get; set; } public int ExpiresInSeconds { get; set; }
[JsonPropertyName("serverAbrStreamingUrl")] [JsonPropertyName("serverAbrStreamingUrl")]
[JsonConverter(typeof(JsonUrlEscapeConverter))]
public string ServerAbrStreamingUrl { get; set; } = ""; public string ServerAbrStreamingUrl { get; set; } = "";
[JsonPropertyName("formats")] [JsonPropertyName("formats")]
public List<StreamingFormat> Formats { get; set; } = []; public List<StreamingFormat> Formats { get; set; } = [];

View File

@@ -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(); private static partial Regex FunctionBodyRegex();
[GeneratedRegex("([\\$_\\w]+).\\w+\\(\\w+,\\d+\\);")] [GeneratedRegex("([\\$_\\w]+).\\w+\\(\\w+,\\d+\\);")]
private static partial Regex DefinitionBodyRegex(); private static partial Regex DefinitionBodyRegex();

View File

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