From e87e1c57f929708d78d2f9a171d837765f757149 Mon Sep 17 00:00:00 2001 From: max Date: Thu, 23 Oct 2025 19:45:36 +0200 Subject: [PATCH] [CHANGE] Updated cipher implementation and added decipher function --- Manager.YouTube/Util/Cipher/CipherDecoder.cs | 40 ++++++++++++++++++-- Manager.YouTube/Util/Cipher/CipherManager.cs | 2 +- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/Manager.YouTube/Util/Cipher/CipherDecoder.cs b/Manager.YouTube/Util/Cipher/CipherDecoder.cs index a61a932..c917354 100644 --- a/Manager.YouTube/Util/Cipher/CipherDecoder.cs +++ b/Manager.YouTube/Util/Cipher/CipherDecoder.cs @@ -1,3 +1,4 @@ +using System.Collections.Frozen; using System.Text; using System.Text.RegularExpressions; using Manager.YouTube.Util.Cipher.Operations; @@ -8,11 +9,11 @@ public partial class CipherDecoder { public required string Version { get; init; } public required string OriginalRelativeUrl { get; init; } - public readonly IReadOnlyCollection Operations; + public readonly IReadOnlySet Operations; private CipherDecoder(IEnumerable operations) { - Operations = operations.ToList(); + Operations = operations.ToFrozenSet(); if (Operations.Count == 0) { throw new ArgumentNullException(nameof(operations), "No decipher operations given."); @@ -30,6 +31,37 @@ public partial class CipherDecoder return decoder; } + public string Decipher(string signatureCipher) + { + var urlBuilder = new StringBuilder(); + + var indexStart = signatureCipher.IndexOf("s=", StringComparison.Ordinal); + var indexEnd = signatureCipher.IndexOf("&", StringComparison.Ordinal); + var signature = signatureCipher.Substring(indexStart, indexEnd); + + indexStart = signatureCipher.IndexOf("&sp", StringComparison.Ordinal); + indexEnd = signatureCipher.IndexOf("&url", StringComparison.Ordinal); + var spParam = signatureCipher.Substring(indexStart, indexEnd - indexStart); + + indexStart = signatureCipher.IndexOf("&url", StringComparison.Ordinal); + var videoUrl = signatureCipher[indexStart..]; + + + signature = signature[(signature.IndexOf('=') + 1)..]; + spParam = spParam[(spParam.IndexOf('=') + 1)..]; + videoUrl = videoUrl[(videoUrl.IndexOf('=') + 1)..]; + if (string.IsNullOrWhiteSpace(signature)) + { + throw new InvalidOperationException("Invalid signature."); + } + var signatureDeciphered = Operations.Aggregate(signature, (acc, op) => op.Decipher(acc)); + + urlBuilder.Append(videoUrl); + urlBuilder.Append($"&{spParam}="); + urlBuilder.Append(signatureDeciphered); + return urlBuilder.ToString(); + } + private static async Task> GetCipherOperations(string relativeUrl, YouTubeClient? client = null) { var downloadRequest = new HttpRequestMessage(HttpMethod.Get, new Uri($"{NetworkService.Origin}/{relativeUrl}")); @@ -49,7 +81,7 @@ public partial class CipherDecoder 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 = []; + SortedSet operations = []; foreach (var statement in functionBody.Split(';')) { // Get the name of the function called in this statement @@ -73,7 +105,7 @@ public partial class CipherDecoder } } - return []; + return operations; } diff --git a/Manager.YouTube/Util/Cipher/CipherManager.cs b/Manager.YouTube/Util/Cipher/CipherManager.cs index fb06919..9900788 100644 --- a/Manager.YouTube/Util/Cipher/CipherManager.cs +++ b/Manager.YouTube/Util/Cipher/CipherManager.cs @@ -8,7 +8,7 @@ public static class CipherManager private static readonly CipherDecoderCollection LoadedCiphers = []; private static readonly ILogger Logger = LogService.RegisterLogger(typeof(CipherManager)); - public static async Task> GetDecoder(YouTubeClient client) + public static async Task> GetDecoderAsync(YouTubeClient client) { var relativePlayerJsUrl = client.State?.PlayerJsUrl; if (string.IsNullOrEmpty(relativePlayerJsUrl))