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 Operations; private CipherDecoder(IEnumerable operations) { Operations = operations.ToList(); if (Operations.Count == 0) { throw new ArgumentNullException(nameof(operations), "No decipher operations given."); } } public static async Task 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> 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 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(); }