Skip to content

Commit

Permalink
added URL's "n" parameter scrambler
Browse files Browse the repository at this point in the history
  • Loading branch information
omansak committed Nov 4, 2021
1 parent 703a835 commit 3d7374d
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 4 deletions.
2 changes: 2 additions & 0 deletions YoutubeExplode/Bridge/Extractors/DashStreamInfoExtractor.cs
Expand Up @@ -23,6 +23,8 @@ internal class DashStreamInfoExtractor : IStreamInfoExtractor

// DASH streams don't have signatures
public string? TryGetSignature() => null;
public string? TryGetNSignature() => _memo.Wrap(() => TryGetUrl()?
.Pipe(s => Regex.Match(s, @"[\?&]n=(.*?)&").Groups[1].Value));

// DASH streams don't have signatures
public string? TryGetSignatureParameter() => null;
Expand Down
3 changes: 3 additions & 0 deletions YoutubeExplode/Bridge/Extractors/IStreamInfoExtractor.cs
Expand Up @@ -8,6 +8,8 @@ internal interface IStreamInfoExtractor

string? TryGetSignature();

string? TryGetNSignature();

string? TryGetSignatureParameter();

long? TryGetContentLength();
Expand All @@ -27,5 +29,6 @@ internal interface IStreamInfoExtractor
int? TryGetVideoHeight();

int? TryGetFramerate();

}
}
19 changes: 19 additions & 0 deletions YoutubeExplode/Bridge/Extractors/PlayerSourceExtractor.cs
Expand Up @@ -88,6 +88,25 @@ internal partial class PlayerSourceExtractor
return new SignatureScrambler(operations);
});

public string? TryNScramblerFunction() => _memo.Wrap(() =>
{
var functionNameRegex = new Regex(@"\.get\(\""n\""\)\)&&\(\w=([a-zA-Z0-9$]{3})\([a-zA-Z0-9]\)");
var functionNameRegexMatch = functionNameRegex.Match(_content);
if (functionNameRegexMatch.Success)
{
var unscrambleFunctionName = functionNameRegexMatch.Groups[1].Value;
var functionRegexMatchStart = Regex.Match(_content, unscrambleFunctionName + @"=function\((\w)\){var\s+\w=\1.split\(\x22{2}\),\w=");
var functionRegexMatchEnd = Regex.Match(_content, @"\+a}return\s\w.join\(\x22{2}\)};");
if (functionRegexMatchStart.Success && functionRegexMatchEnd.Success)
{
return "var " + _content.Substring(functionRegexMatchStart.Index, (functionRegexMatchEnd.Index + functionRegexMatchEnd.Length) - functionRegexMatchStart.Index);
}
}
return null;
});
}

internal partial class PlayerSourceExtractor
Expand Down
4 changes: 4 additions & 0 deletions YoutubeExplode/Bridge/Extractors/PlayerStreamInfoExtractor.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.Json;
using System.Text.RegularExpressions;
using YoutubeExplode.Utils;
Expand Down Expand Up @@ -44,6 +45,9 @@ internal class PlayerStreamInfoExtractor : IStreamInfoExtractor
TryGetCipherData()?.GetValueOrDefault("s")
);

public string? TryGetNSignature() => _memo.Wrap(() => TryGetUrl()?
.Pipe(s => Regex.Match(s, @"[\?&]n=(.*?)&").Groups[1].Value));

public string? TryGetSignatureParameter() => _memo.Wrap(() =>
TryGetCipherData()?.GetValueOrDefault("sp")
);
Expand Down
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using YoutubeExplode.Utils;
using YoutubeExplode.Utils.Extensions;
Expand All @@ -26,6 +27,9 @@ internal class UrlEncodedStreamInfoExtractor : IStreamInfoExtractor
_content.GetValueOrDefault("s")
);

public string? TryGetNSignature() => _memo.Wrap(() => TryGetUrl()?
.Pipe(s => Regex.Match(s, @"[\?&]n=(.*?)&").Groups[1].Value));

public string? TryGetSignatureParameter() => _memo.Wrap(() =>
_content.GetValueOrDefault("sp")
);
Expand Down
24 changes: 24 additions & 0 deletions YoutubeExplode/Bridge/SignatureScrambling/NSignatureScrambler.cs
@@ -0,0 +1,24 @@
using NiL.JS.BaseLibrary;
using NiL.JS.Core;
using NiL.JS.Extensions;
using System.Text.RegularExpressions;

namespace YoutubeExplode.Bridge.SignatureScrambling
{
internal class NSignatureScrambler
{
public string? Unscramble(string jsFunction, string input) => UnscrambleNSignature(jsFunction, input);

private string? UnscrambleNSignature(string jsFunction, string signature)
{
var functionNameRegex = Regex.Match(jsFunction, @"var\s(\w*)=");
if (!string.IsNullOrWhiteSpace(jsFunction) && functionNameRegex.Success)
{
var context = new Context();
context.Eval(jsFunction);
return context.GetVariable(functionNameRegex.Groups[1].Value).As<Function>().Call(new Arguments { signature }).Value.ToString();
}
return signature;
}
}
}
5 changes: 3 additions & 2 deletions YoutubeExplode/Utils/Url.cs
Expand Up @@ -8,9 +8,10 @@ namespace YoutubeExplode.Utils
{
internal static class Url
{
public static string SetQueryParameter(string url, string key, string value)
public static string SetQueryParameter(string url, string key, string value, bool forceEqual = false)
{
var existingMatch = Regex.Match(url, $"[?&]({Regex.Escape(key)}=?.*?)(?:&|/|$)");
// bug if key is "n", returns "ns" not "n"
var existingMatch = Regex.Match(url, $"[?&]({Regex.Escape(key)}={(forceEqual ? "?" : string.Empty)}.*?)(?:&|/|$)");

// Parameter has already been set to something
if (existingMatch.Success)
Expand Down
27 changes: 25 additions & 2 deletions YoutubeExplode/Videos/Streams/StreamClient.cs
Expand Up @@ -46,7 +46,23 @@ public StreamClient(HttpClient httpClient)
return Url.SetQueryParameter(
streamUrl,
signatureParameter ?? "signature",
signatureScrambler.Unscramble(signature)
signatureScrambler.Unscramble(signature),
true
);
}

private string UnscrambleNSignatureStreamUrl(
string streamUrl,
string? signature,
string? jsFunction)
{
if (string.IsNullOrWhiteSpace(signature) || string.IsNullOrWhiteSpace(jsFunction))
return streamUrl;

return Url.SetQueryParameter(
streamUrl,
"n",
new NSignatureScrambler().Unscramble(jsFunction, signature) ?? signature
);
}

Expand All @@ -69,6 +85,7 @@ public StreamClient(HttpClient httpClient)
ICollection<IStreamInfo> streamInfos,
IEnumerable<IStreamInfoExtractor> streamInfoExtractors,
SignatureScrambler signatureScrambler,
string? nSignatureScramblerFunction,
CancellationToken cancellationToken = default)
{
foreach (var streamInfoExtractor in streamInfoExtractors)
Expand All @@ -85,6 +102,8 @@ public StreamClient(HttpClient httpClient)
var signature = streamInfoExtractor.TryGetSignature();
var signatureParameter = streamInfoExtractor.TryGetSignatureParameter();
var url = UnscrambleStreamUrl(signatureScrambler, urlRaw, signature, signatureParameter);
// Unscramble N Signature URL
url = UnscrambleNSignatureStreamUrl(url, streamInfoExtractor.TryGetNSignature(), nSignatureScramblerFunction);

// Get content length
var contentLength =
Expand Down Expand Up @@ -192,6 +211,7 @@ public StreamClient(HttpClient httpClient)
: null;

var signatureScrambler = playerSource?.TryGetSignatureScrambler() ?? SignatureScrambler.Null;
var nSignatureScrambler = playerSource?.TryNScramblerFunction();

var playerResponseFromWatchPage = watchPage.TryGetPlayerResponse();
if (playerResponseFromWatchPage is not null)
Expand All @@ -212,6 +232,7 @@ public StreamClient(HttpClient httpClient)
streamInfos,
watchPage.GetStreams(),
signatureScrambler,
nSignatureScrambler,
cancellationToken
);

Expand All @@ -220,6 +241,7 @@ public StreamClient(HttpClient httpClient)
streamInfos,
playerResponseFromWatchPage.GetStreams(),
signatureScrambler,
nSignatureScrambler,
cancellationToken
);

Expand All @@ -234,6 +256,7 @@ public StreamClient(HttpClient httpClient)
streamInfos,
dashManifest.GetStreams(),
signatureScrambler,
nSignatureScrambler,
cancellationToken
);
}
Expand Down Expand Up @@ -311,7 +334,7 @@ public StreamClient(HttpClient httpClient)

var segmentSize = isThrottled
? 9_898_989 // breakpoint after which the throttling kicks in
: (long?) null; // no segmentation for non-throttled streams
: (long?)null; // no segmentation for non-throttled streams

var stream = new SegmentedHttpStream(
_httpClient,
Expand Down
1 change: 1 addition & 0 deletions YoutubeExplode/YoutubeExplode.csproj
Expand Up @@ -13,6 +13,7 @@
<ItemGroup>
<PackageReference Include="AngleSharp" Version="0.14.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="all" />
<PackageReference Include="NiL.JS" Version="2.5.1514" />
<PackageReference Include="Nullable" Version="1.3.0" PrivateAssets="all" />
</ItemGroup>

Expand Down

0 comments on commit 3d7374d

Please sign in to comment.