Compare commits
2 Commits
a84195aefa
...
4c04378080
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c04378080 | ||
|
|
b5c701b971 |
13
Manager.App/Components/Application/Dev/CipherDev.razor
Normal file
13
Manager.App/Components/Application/Dev/CipherDev.razor
Normal 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>
|
||||||
43
Manager.App/Components/Application/Dev/CipherDev.razor.cs
Normal file
43
Manager.App/Components/Application/Dev/CipherDev.razor.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -9,4 +9,7 @@
|
|||||||
<MudTabPanel Text="Video">
|
<MudTabPanel Text="Video">
|
||||||
<DevelopmentVideo />
|
<DevelopmentVideo />
|
||||||
</MudTabPanel>
|
</MudTabPanel>
|
||||||
|
<MudTabPanel Text="Cipher">
|
||||||
|
<CipherDev />
|
||||||
|
</MudTabPanel>
|
||||||
</MudTabs>
|
</MudTabs>
|
||||||
@@ -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)
|
||||||
|
|||||||
58
Manager.YouTube/Interpreter/JavaScriptEngineManager.cs
Normal file
58
Manager.YouTube/Interpreter/JavaScriptEngineManager.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Manager.YouTube/Interpreter/PlayerEngine.cs
Normal file
42
Manager.YouTube/Interpreter/PlayerEngine.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Manager.YouTube/Interpreter/PlayerEngineCollection.cs
Normal file
11
Manager.YouTube/Interpreter/PlayerEngineCollection.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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; } = [];
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
27
Manager.YouTube/Util/Converters/JsonUrlEscapeConverter.cs
Normal file
27
Manager.YouTube/Util/Converters/JsonUrlEscapeConverter.cs
Normal 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();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user