Files
YouTube-Manager/Manager.YouTube/Util/Cipher/CipherDecoder.cs
2025-10-23 19:27:07 +02:00

90 lines
3.6 KiB
C#

using System.Text;
using System.Text.RegularExpressions;
using Manager.YouTube.Util.Cipher.Operations;
namespace Manager.YouTube.Util.Cipher;
public partial class CipherDecoder
{
public required string Version { get; init; }
public required string OriginalRelativeUrl { get; init; }
public readonly IReadOnlyCollection<ICipherOperation> Operations;
private CipherDecoder(IEnumerable<ICipherOperation> operations)
{
Operations = operations.ToList();
if (Operations.Count == 0)
{
throw new ArgumentNullException(nameof(operations), "No decipher operations given.");
}
}
public static async Task<CipherDecoder> CreateAsync(string relativeUrl, string version, YouTubeClient? client = null)
{
var operations = await GetCipherOperations(relativeUrl, client);
var decoder = new CipherDecoder(operations)
{
OriginalRelativeUrl = relativeUrl,
Version = version
};
return decoder;
}
private static async Task<IEnumerable<ICipherOperation>> GetCipherOperations(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 [];
}
var playerJs = Encoding.UTF8.GetString(downloadResponse.Value.Data);
if (string.IsNullOrWhiteSpace(playerJs))
{
return [];
}
var functionBody = FunctionBodyRegex().Match(playerJs).Groups[0].ToString();
var definitionBody = DefinitionBodyRegex().Match(functionBody).Groups[1].Value;
var decipherDefinition = Regex.Match(playerJs, $@"var\s+{definitionBody}=\{{(\w+:function\(\w+(,\w+)?\)\{{(.*?)\}}),?\}};", RegexOptions.Singleline).Groups[0].ToString();
List<ICipherOperation> operations = [];
foreach (var statement in functionBody.Split(';'))
{
// Get the name of the function called in this statement
var calledFuncName = StatementFunctionNameRegex().Match(statement).Groups[1].Value;
if (string.IsNullOrWhiteSpace(calledFuncName))
continue;
if (Regex.IsMatch(decipherDefinition, $@"{Regex.Escape(calledFuncName)}:\bfunction\b\([a],b\).(\breturn\b)?.?\w+\."))
{
var index = int.Parse(OperationIndexRegex().Match(statement).Groups[1].Value);
operations.Add(new CipherSlice(index));
}
else if (Regex.IsMatch(decipherDefinition, $@"{Regex.Escape(calledFuncName)}:\bfunction\b\(\w+\,\w\).\bvar\b.\bc=a\b"))
{
var index = int.Parse(OperationIndexRegex().Match(statement).Groups[1].Value);
operations.Add(new CipherSwap(index));
}
else if (Regex.IsMatch(decipherDefinition, $@"{Regex.Escape(calledFuncName)}:\bfunction\b\(\w+\)"))
{
operations.Add(new CipherReverse());
}
}
return [];
}
[GeneratedRegex(@"(\w+)=function\(\w+\){(\w+)=\2\.split\(\x22{2}\);.*?return\s+\2\.join\(\x22{2}\)}")]
private static partial Regex FunctionBodyRegex();
[GeneratedRegex("([\\$_\\w]+).\\w+\\(\\w+,\\d+\\);")]
private static partial Regex DefinitionBodyRegex();
[GeneratedRegex(@"\(\w+,(\d+)\)")]
private static partial Regex OperationIndexRegex();
[GeneratedRegex(@"\w+(?:.|\[)(\""?\w+(?:\"")?)\]?\(")]
private static partial Regex StatementFunctionNameRegex();
}