[CHANGE] Added old cipher implementation
This commit is contained in:
@@ -35,6 +35,9 @@ public class ClientState : AdditionalJsonData
|
||||
[JsonPropertyName("SERVER_VERSION")]
|
||||
public string? ServerVersion { get; set; }
|
||||
|
||||
[JsonPropertyName("PLAYER_JS_URL")]
|
||||
public string? PlayerJsUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("INNERTUBE_CONTEXT")]
|
||||
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