[CHANGE] Added old cipher implementation
This commit is contained in:
@@ -35,6 +35,9 @@ public class ClientState : AdditionalJsonData
|
|||||||
[JsonPropertyName("SERVER_VERSION")]
|
[JsonPropertyName("SERVER_VERSION")]
|
||||||
public string? ServerVersion { get; set; }
|
public string? ServerVersion { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("PLAYER_JS_URL")]
|
||||||
|
public string? PlayerJsUrl { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("INNERTUBE_CONTEXT")]
|
[JsonPropertyName("INNERTUBE_CONTEXT")]
|
||||||
public InnerTubeContext? InnerTubeContext { get; set; }
|
public InnerTubeContext? InnerTubeContext { get; set; }
|
||||||
|
|
||||||
|
|||||||
90
Manager.YouTube/Util/Cipher/CipherDecoder.cs
Normal file
90
Manager.YouTube/Util/Cipher/CipherDecoder.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
11
Manager.YouTube/Util/Cipher/CipherDecoderCollection.cs
Normal file
11
Manager.YouTube/Util/Cipher/CipherDecoderCollection.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace Manager.YouTube.Util.Cipher;
|
||||||
|
|
||||||
|
public class CipherDecoderCollection : KeyedCollection<string, CipherDecoder>
|
||||||
|
{
|
||||||
|
protected override string GetKeyForItem(CipherDecoder item)
|
||||||
|
{
|
||||||
|
return item.Version;
|
||||||
|
}
|
||||||
|
}
|
||||||
46
Manager.YouTube/Util/Cipher/CipherManager.cs
Normal file
46
Manager.YouTube/Util/Cipher/CipherManager.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
using DotBased.Logging;
|
||||||
|
using DotBased.Monads;
|
||||||
|
|
||||||
|
namespace Manager.YouTube.Util.Cipher;
|
||||||
|
|
||||||
|
public static class CipherManager
|
||||||
|
{
|
||||||
|
private static readonly CipherDecoderCollection LoadedCiphers = [];
|
||||||
|
private static readonly ILogger Logger = LogService.RegisterLogger(typeof(CipherManager));
|
||||||
|
|
||||||
|
public static async Task<Result<CipherDecoder>> GetDecoder(YouTubeClient client)
|
||||||
|
{
|
||||||
|
var relativePlayerJsUrl = client.State?.PlayerJsUrl;
|
||||||
|
if (string.IsNullOrEmpty(relativePlayerJsUrl))
|
||||||
|
{
|
||||||
|
return ResultError.Fail("Could not get player js url.");
|
||||||
|
}
|
||||||
|
var version = GetCipherVersion(relativePlayerJsUrl);
|
||||||
|
|
||||||
|
Logger.Debug($"Getting cipher decoder for version: {version}");
|
||||||
|
if (LoadedCiphers.TryGetValue(version, out var cipher))
|
||||||
|
{
|
||||||
|
return cipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var decoder = await CipherDecoder.CreateAsync(relativePlayerJsUrl, version);
|
||||||
|
LoadedCiphers.Add(decoder);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e, "Could not create cipher decoder. Version: {DecoderVersion}", version);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultError.Fail($"Could not create cipher decoder for {relativePlayerJsUrl} (v: {version})");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetCipherVersion(string relativePlayerUrl)
|
||||||
|
{
|
||||||
|
var split = relativePlayerUrl.Split('/');
|
||||||
|
var v = split[2];
|
||||||
|
var lang = split[4];
|
||||||
|
return $"{v}_{lang}";
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Manager.YouTube/Util/Cipher/Operations/CipherReverse.cs
Normal file
18
Manager.YouTube/Util/Cipher/Operations/CipherReverse.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Manager.YouTube.Util.Cipher.Operations;
|
||||||
|
|
||||||
|
public class CipherReverse : ICipherOperation
|
||||||
|
{
|
||||||
|
public string Decipher(string cipherSignature)
|
||||||
|
{
|
||||||
|
var buffer = new StringBuilder(cipherSignature.Length);
|
||||||
|
|
||||||
|
for (var i = cipherSignature.Length - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
buffer.Append(cipherSignature[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
6
Manager.YouTube/Util/Cipher/Operations/CipherSlice.cs
Normal file
6
Manager.YouTube/Util/Cipher/Operations/CipherSlice.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Manager.YouTube.Util.Cipher.Operations;
|
||||||
|
|
||||||
|
public class CipherSlice(int indexToSlice) : ICipherOperation
|
||||||
|
{
|
||||||
|
public string Decipher(string cipherSignature) => cipherSignature[indexToSlice..];
|
||||||
|
}
|
||||||
12
Manager.YouTube/Util/Cipher/Operations/CipherSwap.cs
Normal file
12
Manager.YouTube/Util/Cipher/Operations/CipherSwap.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Manager.YouTube.Util.Cipher.Operations;
|
||||||
|
|
||||||
|
public class CipherSwap(int indexToSwap) : ICipherOperation
|
||||||
|
{
|
||||||
|
public string Decipher(string cipherSignature) => new StringBuilder(cipherSignature)
|
||||||
|
{
|
||||||
|
[0] = cipherSignature[indexToSwap],
|
||||||
|
[indexToSwap] = cipherSignature[0]
|
||||||
|
}.ToString();
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Manager.YouTube.Util.Cipher.Operations;
|
||||||
|
|
||||||
|
public interface ICipherOperation
|
||||||
|
{
|
||||||
|
string Decipher(string cipherSignature);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user