From 545522b80fc68bc12366aa0c48f91bd095ff5089 Mon Sep 17 00:00:00 2001
From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com>
Date: Tue, 1 Feb 2022 19:52:41 +0100
Subject: [PATCH] [Youtube] Make throttling decryption more resilient to api
change
---
.../youtube/YoutubeThrottlingDecrypter.java | 97 ++++++++++++-------
.../extractors/YoutubeStreamExtractor.java | 25 +++--
.../YoutubeThrottlingDecrypterTest.java | 6 +-
3 files changed, 83 insertions(+), 45 deletions(-)
diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeThrottlingDecrypter.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeThrottlingDecrypter.java
index 21925392a..02566c4b3 100644
--- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeThrottlingDecrypter.java
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeThrottlingDecrypter.java
@@ -38,7 +38,9 @@ public class YoutubeThrottlingDecrypter {
private static final Pattern FUNCTION_NAME_PATTERN = Pattern.compile(
"b=a\\.get\\(\"n\"\\)\\)&&\\(b=(\\S+)\\(b\\),a\\.set\\(\"n\",b\\)");
- private static final Map
+ * The videoId is only used to fetch the decryption function.
+ * It can be a constant value of any existing video.
+ * A constant value is discouraged, because it could allow tracking.
+ */
+ public static String apply(final String url, final String videoId) throws ParsingException {
+ if (containsNParam(url)) {
+ if (FUNCTION == null) {
+ final String playerJsCode = YoutubeJavaScriptExtractor.extractJavaScriptCode(videoId);
+
+ FUNCTION_NAME = parseDecodeFunctionName(playerJsCode);
+ FUNCTION = parseDecodeFunction(playerJsCode, FUNCTION_NAME);
}
- return functionName;
+
+ final String oldNParam = parseNParam(url);
+ final String newNParam = decryptNParam(FUNCTION, FUNCTION_NAME, oldNParam);
+ return replaceNParam(url, oldNParam, newNParam);
+ } else {
+ return url;
+ }
+ }
+
+ private static String parseDecodeFunctionName(final String playerJsCode)
+ throws Parser.RegexException {
+ String functionName = Parser.matchGroup1(FUNCTION_NAME_PATTERN, playerJsCode);
+ final int arrayStartBrace = functionName.indexOf("[");
+
+ if (arrayStartBrace > 0) {
+ final String arrayVarName = functionName.substring(0, arrayStartBrace);
+ final String order = functionName.substring(
+ arrayStartBrace + 1, functionName.indexOf("]"));
+ final int arrayNum = Integer.parseInt(order);
+ final Pattern arrayPattern = Pattern.compile(
+ String.format("var %s=\\[(.+?)\\];", arrayVarName));
+ final String arrayStr = Parser.matchGroup1(arrayPattern, playerJsCode);
+ final String[] names = arrayStr.split(",");
+ functionName = names[arrayNum];
+ }
+ return functionName;
}
@Nonnull
- private String parseDecodeFunction(final String playerJsCode, final String functionName)
+ private static String parseDecodeFunction(final String playerJsCode, final String functionName)
throws Parser.RegexException {
try {
return parseWithParenthesisMatching(playerJsCode, functionName);
@@ -94,49 +124,50 @@ public class YoutubeThrottlingDecrypter {
}
@Nonnull
- private String parseWithParenthesisMatching(final String playerJsCode, final String functionName) {
+ private static String parseWithParenthesisMatching(final String playerJsCode, final String functionName) {
final String functionBase = functionName + "=function";
return functionBase + StringUtils.matchToClosingParenthesis(playerJsCode, functionBase) + ";";
}
@Nonnull
- private String parseWithRegex(final String playerJsCode, final String functionName) throws Parser.RegexException {
+ private static String parseWithRegex(final String playerJsCode, final String functionName) throws Parser.RegexException {
final Pattern functionPattern = Pattern.compile(functionName + "=function(.*?}};)\n",
Pattern.DOTALL);
return "function " + functionName + Parser.matchGroup1(functionPattern, playerJsCode);
}
- public String apply(final String url) throws Parser.RegexException {
+ @Deprecated
+ public String apply(final String url) throws ParsingException {
if (containsNParam(url)) {
final String oldNParam = parseNParam(url);
- final String newNParam = decryptNParam(oldNParam);
+ final String newNParam = decryptNParam(function, functionName, oldNParam);
return replaceNParam(url, oldNParam, newNParam);
} else {
return url;
}
}
- private boolean containsNParam(final String url) {
+ private static boolean containsNParam(final String url) {
return Parser.isMatch(N_PARAM_PATTERN, url);
}
- private String parseNParam(final String url) throws Parser.RegexException {
+ private static String parseNParam(final String url) throws Parser.RegexException {
return Parser.matchGroup1(N_PARAM_PATTERN, url);
}
- private String decryptNParam(final String nParam) {
- if (nParams.containsKey(nParam)) {
- return nParams.get(nParam);
+ private static String decryptNParam(final String function, final String functionName, final String nParam) {
+ if (N_PARAMS_CACHE.containsKey(nParam)) {
+ return N_PARAMS_CACHE.get(nParam);
}
final String decryptedNParam = JavaScript.run(function, functionName, nParam);
- nParams.put(nParam, decryptedNParam);
+ N_PARAMS_CACHE.put(nParam, decryptedNParam);
return decryptedNParam;
}
@Nonnull
- private String replaceNParam(@Nonnull final String url,
- final String oldValue,
- final String newValue) {
+ private static String replaceNParam(@Nonnull final String url,
+ final String oldValue,
+ final String newValue) {
return url.replace(oldValue, newValue);
}
@@ -144,13 +175,13 @@ public class YoutubeThrottlingDecrypter {
* @return the number of the cached "n" query parameters.
*/
public static int getCacheSize() {
- return nParams.size();
+ return N_PARAMS_CACHE.size();
}
/**
* Clears all stored "n" query parameters.
*/
public static void clearCache() {
- nParams.clear();
+ N_PARAMS_CACHE.clear();
}
}
diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java
index 5a04a772b..e83eeb257 100644
--- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java
@@ -469,14 +469,12 @@ public class YoutubeStreamExtractor extends StreamExtractor {
public List