Skip to content

Commit c8cb649

Browse files
fixed regex for player ids 22f02d3d & 6450230e
1 parent c9e52a4 commit c8cb649

File tree

3 files changed

+179
-33
lines changed

3 files changed

+179
-33
lines changed

common/src/main/java/dev/lavalink/youtube/cipher/SignatureCipher.java

Lines changed: 122 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,144 @@
11
package dev.lavalink.youtube.cipher;
22

33
import org.jetbrains.annotations.NotNull;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
46

57
import javax.script.Invocable;
68
import javax.script.ScriptEngine;
79
import javax.script.ScriptException;
810
import java.util.ArrayList;
911
import java.util.List;
12+
import java.util.regex.Matcher;
13+
import java.util.regex.Pattern;
1014

1115
/**
1216
* Describes one signature cipher
1317
*/
1418
public class SignatureCipher {
19+
private static final Logger log = LoggerFactory.getLogger(SignatureCipher.class);
20+
private static final Pattern nFunctionTcePattern = Pattern.compile(
21+
"function\\s*\\((\\w+)\\)\\s*\\{var\\s*\\w+\\s*=\\s*\\1\\[\\w+\\[\\d+\\]\\]\\(\\w+\\[\\d+\\]\\)\\s*,\\s*\\w+\\s*=\\s*\\[.*?\\]\\;.*?catch\\(\\s*(\\w+)\\s*\\s*\\)\\s*\\{return\\s*\\w+\\[\\d+\\](\\+\\1)?\\}\\s*return\\s*\\w+\\[\\w+\\[\\d+\\]\\]\\(\\w+\\[\\d+\\]\\)\\}\\;",
22+
Pattern.DOTALL);
23+
private static final Pattern sigFunctionTcePattern = Pattern.compile("function\\(\\s*([a-zA-Z0-9$])\\s*\\)\\s*\\{" +
24+
"\\s*\\1\\s*=\\s*\\1\\[(\\w+)\\[\\d+\\]\\]\\(\\2\\[\\d+\\]\\);" +
25+
"([a-zA-Z0-9$]+)\\[\\2\\[\\d+\\]\\]\\(\\s*\\1\\s*,\\s*\\d+\\s*\\);" +
26+
"\\s*\\3\\[\\2\\[\\d+\\]\\]\\(\\s*\\1\\s*,\\s*\\d+\\s*\\);" +
27+
".*?return\\s*\\1\\[\\2\\[\\d+\\]\\]\\(\\2\\[\\d+\\]\\)\\};");
28+
private static final Pattern tceGlobalVarsPattern = Pattern.compile(
29+
"('use\\s*strict';)?" +
30+
"(?<code>var\\s*" +
31+
"(?<varname>[a-zA-Z0-9_$]+)\\s*=\\s*" +
32+
"(?<value>" +
33+
"(?:\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*')" +
34+
"\\.split\\(" +
35+
"(?:\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*')" +
36+
"\\)" +
37+
"|" +
38+
"\\[" +
39+
"(?:(?:\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*')" +
40+
"\\s*,?\\s*)*" +
41+
"\\]" +
42+
"|" +
43+
"\"[^\"]*\"\\.split\\(\"[^\"]*\"\\)" +
44+
")" +
45+
")");
46+
private static final Pattern tceSigFunctionActionsPattern = Pattern.compile(
47+
"var\\s*[a-zA-Z0-9$_]+\\s*=\\s*\\{\\s*[a-zA-Z0-9$_]+\\s*:\\s*function\\((\\w+|\\s*\\w+\\s*,\\s*\\w+\\s*)\\)\\s*\\{\\s*(\\s*var\\s*\\w+=\\w+\\[\\d+\\];\\w+\\[\\d+\\]\\s*=\\s*\\w+\\[\\w+\\s*\\%\\s*\\w+\\[\\w+\\[\\d+\\]\\]\\];\\s*\\w+\\[\\w+\\s*%\\s*\\w+\\[\\w+\\[\\d+\\]\\]\\]\\s*=\\s*\\w+\\s*\\},|\\w+\\[\\w+\\[\\d+\\]\\]\\(\\)\\},)\\s*[a-zA-Z0-9$_]+\\s*:\\s*function\\((\\s*\\w+\\w*,\\s*\\w+\\s*|\\w+)\\)\\s*\\{(\\w+\\[\\w+\\[\\d+\\]\\]\\(\\)|\\s*var\\s*\\w+\\s*=\\s*\\w+\\[\\d+\\]\\s*;\\w+\\[\\d+\\]\\s*=\\w+\\[\\s*\\w+\\s*%\\s*\\w+\\[\\w+\\[\\d+\\]\\]\\]\\s*;\\w+\\[\\s*\\w+\\s*%\\s*\\w\\[\\w+\\[\\d+\\]\\]\\]\\s*=\\s*\\w+\\s*)\\},\\s*[a-zA-Z0-9$_]+\\s*:\\s*function\\s*\\(\\s*\\w+\\s*,\\s*\\w+\\s*\\)\\{\\w+\\[\\w+\\[\\d+\\]\\]\\(\\s*\\d+\\s*,\\s*\\w+\\s*\\)\\}\\};");
48+
1549
private final List<CipherOperation> operations = new ArrayList<>();
1650
public final String nFunction;
1751
public final String scriptTimestamp;
1852
public final String rawScript;
53+
public final String sigFunction;
54+
public final String sigFunctionActions;
1955

20-
public final boolean tceScript;
21-
public final String tceVars;
56+
public final TCEVariable tceVariable;
2257

23-
public SignatureCipher(@NotNull String nFunction,
24-
@NotNull String timestamp,
25-
@NotNull String rawScript,
26-
boolean tceScript,
27-
@NotNull String tceVars) {
58+
public SignatureCipher(@NotNull String nFunction, @NotNull String sigFunction, @NotNull String sigFunctionActions,
59+
@NotNull String timestamp, @NotNull String rawScript, @NotNull TCEVariable tceVariable) {
2860
this.nFunction = nFunction;
2961
this.scriptTimestamp = timestamp;
3062
this.rawScript = rawScript;
31-
this.tceScript = tceScript;
32-
this.tceVars = tceVars;
63+
this.sigFunction = sigFunction;
64+
this.sigFunctionActions = sigFunctionActions;
65+
this.tceVariable = tceVariable;
66+
}
67+
68+
public SignatureCipher(@NotNull String nFunction, @NotNull String timestamp, @NotNull String rawScript) {
69+
this.nFunction = nFunction;
70+
this.scriptTimestamp = timestamp;
71+
this.rawScript = rawScript;
72+
this.tceVariable = null;
73+
this.sigFunction = null;
74+
this.sigFunctionActions = null;
75+
}
76+
77+
public static SignatureCipher fromRawScript(@NotNull String jsCode, @NotNull String timestamp) {
78+
log.debug("Finding tce global variable from the script...");
79+
Matcher tceVariableMatcher = tceGlobalVarsPattern.matcher(jsCode);
80+
81+
if (!tceVariableMatcher.find()) {
82+
log.warn("Failed to find the tce global variable...");
83+
return null;
84+
}
85+
86+
87+
TCEVariable tce = new TCEVariable(tceVariableMatcher.group("varname"), tceVariableMatcher.group("code"),
88+
tceVariableMatcher.group("value"));
89+
90+
Matcher nFunctionMatcher = nFunctionTcePattern.matcher(jsCode);
91+
92+
if (!nFunctionMatcher.find()) {
93+
log.warn("Failed to find the tce variant n function...");
94+
return null;
95+
}
96+
97+
98+
99+
100+
Matcher sigFunctionMatcher = sigFunctionTcePattern.matcher(jsCode);
101+
if (!sigFunctionMatcher.find()) {
102+
log.warn("Failed to find the tce variant sig function....");
103+
return null;
104+
}
105+
106+
Matcher sigFunctionActionsMatcher = tceSigFunctionActionsPattern.matcher(jsCode);
107+
if (!sigFunctionActionsMatcher.find()) {
108+
log.warn("Failed to find the tce variant sig function actions...");
109+
return null;
110+
}
111+
112+
String nFunction = nFunctionMatcher.group(0);
113+
Pattern shortCircuitPattern = Pattern.compile(String.format(
114+
";\\s*if\\s*\\(\\s*typeof\\s+[a-zA-Z0-9_$]+\\s*===?\\s*(?:\"undefined\"|'undefined'|%s\\[\\d+\\])\\s*\\)\\s*return\\s+\\w+;",
115+
tce.getEscapedName()));
116+
Matcher tceShortCircuitMatcher = shortCircuitPattern.matcher(nFunction);
117+
if (tceShortCircuitMatcher.find()) {
118+
System.out.println("TCE global variable short circuit detected replacing nFunction...");
119+
nFunction = nFunction.replaceAll(shortCircuitPattern.toString(), ";");
120+
}
121+
122+
return new SignatureCipher(nFunction, sigFunctionMatcher.group(0), sigFunctionActionsMatcher.group(0), timestamp,
123+
jsCode, tce);
124+
}
125+
126+
public boolean isTceScript() {
127+
return this.tceVariable != null;
128+
129+
}
130+
131+
/**
132+
* @param text Text to apply the cipher on
133+
* @return The result of the cipher on the input text
134+
*/
135+
public String apply(@NotNull String text, @NotNull ScriptEngine scriptEngine)
136+
throws ScriptException, NoSuchMethodException {
137+
String transformed;
138+
139+
scriptEngine.eval("sig=" + sigFunction + sigFunctionActions + (isTceScript() ? tceVariable.getCode() : ""));
140+
transformed = (String) ((Invocable) scriptEngine).invokeFunction("sig", text);
141+
return transformed;
33142
}
34143

35144
/**
@@ -63,14 +172,15 @@ public String apply(@NotNull String text) {
63172
}
64173

65174
/**
66-
* @param text Text to transform
175+
* @param text Text to transform
67176
* @param scriptEngine JavaScript engine to execute function
68177
* @return The result of the n parameter transformation
69178
*/
70-
public String transform(@NotNull String text, @NotNull ScriptEngine scriptEngine) throws ScriptException, NoSuchMethodException {
179+
public String transform(@NotNull String text, @NotNull ScriptEngine scriptEngine)
180+
throws ScriptException, NoSuchMethodException {
71181
String transformed;
72182

73-
scriptEngine.eval("n=" + nFunction + (tceScript ? tceVars : ""));
183+
scriptEngine.eval("n=" + nFunction + (isTceScript() ? tceVariable.getCode() : ""));
74184
transformed = (String) ((Invocable) scriptEngine).invokeFunction("n", text);
75185

76186
return transformed;

common/src/main/java/dev/lavalink/youtube/cipher/SignatureCipherManager.java

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import java.util.regex.Pattern;
3636
import java.util.stream.Collectors;
3737
import java.util.stream.Stream;
38-
3938
import static com.sedmelluq.discord.lavaplayer.tools.ExceptionTools.throwWithDebugInfo;
4039

4140
/**
@@ -152,8 +151,15 @@ public URI resolveFormatUrl(@NotNull HttpInterface httpInterface, @NotNull Strin
152151
SignatureCipher cipher = getCipherScript(httpInterface, playerScript);
153152

154153
if (!DataFormatTools.isNullOrEmpty(signature)) {
155-
uri.setParameter(format.getSignatureKey(), cipher.apply(signature));
156-
}
154+
try {
155+
uri.setParameter(format.getSignatureKey(), !cipher.isTceScript() ? cipher.apply(signature) : cipher.apply(signature , scriptEngine));
156+
} catch (ScriptException | NoSuchMethodException e) {
157+
// URLs can still be played without a resolved n parameter. It just means they're
158+
// throttled. But we shouldn't throw an exception anyway as it's not really fatal.
159+
dumpProblematicScript(cipherCache.get(playerScript).rawScript, playerScript, "Can't transform s parameter " + signature + " with " + " sig function");
160+
}
161+
}
162+
157163

158164
if (!DataFormatTools.isNullOrEmpty(nParameter)) {
159165
try {
@@ -269,8 +275,19 @@ private void dumpProblematicScript(@NotNull String script, @NotNull String sourc
269275
}
270276

271277
private SignatureCipher extractFromScript(@NotNull String script, @NotNull String sourceUrl) {
272-
Matcher actions = actionsPattern.matcher(script);
273278
Matcher scriptTimestamp = timestampPattern.matcher(script);
279+
if (!scriptTimestamp.find()) {
280+
dumpProblematicScript(script, sourceUrl, "no timestamp match");
281+
throw new IllegalStateException("Must find timestamp from script: " + sourceUrl);
282+
}
283+
284+
SignatureCipher tceCypherKey = SignatureCipher.fromRawScript(script , scriptTimestamp.group(2));
285+
286+
if (tceCypherKey != null) {
287+
return tceCypherKey;
288+
}
289+
290+
Matcher actions = actionsPattern.matcher(script);
274291

275292
boolean matchedTce = false;
276293

@@ -306,10 +323,7 @@ private SignatureCipher extractFromScript(@NotNull String script, @NotNull Strin
306323

307324
Matcher matcher = extractor.matcher(functions.group(matchedTce ? 1 : 2));
308325

309-
if (!scriptTimestamp.find()) {
310-
dumpProblematicScript(script, sourceUrl, "no timestamp match");
311-
throw new IllegalStateException("Must find timestamp from script: " + sourceUrl);
312-
}
326+
313327

314328
// use matchedTce hint to determine which regex we should use to parse the script.
315329
Matcher nFunctionMatcher = matchedTce ? nFunctionTcePattern.matcher(script) : nFunctionPattern.matcher(script);
@@ -331,25 +345,15 @@ private SignatureCipher extractFromScript(@NotNull String script, @NotNull Strin
331345
matchedTce = true;
332346
}
333347

334-
Matcher tceVars = tceGlobalVarsPattern.matcher(script);
335-
String tceText = "";
336-
337-
if (!tceVars.find()) {
338-
if (matchedTce) {
339-
dumpProblematicScript(script, sourceUrl, "no tce variables match");
340-
log.warn("Got tce player script but could not find global variables: {}", sourceUrl);
341-
}
342-
} else {
343-
tceText = tceVars.group(1);
344-
}
348+
345349

346350
String nFunction = nFunctionMatcher.group(0);
347351
String nfParameterName = DataFormatTools.extractBetween(nFunction, "(", ")");
348352
// remove short-circuit that prevents n challenge transformation
349353
// nFunction = nFunction.replaceAll("if\\s*\\(\\s*typeof\\s*[\\w$]+\\s*===?.*?\\)\\s*return\\s+" + nfParameterName + "\\s*;?", "");
350354
nFunction = nFunction.replaceAll("if\\s*\\(typeof\\s*[^\\s()]+\\s*===?.*?\\)return " + nfParameterName + "\\s*;?", "");
351355

352-
SignatureCipher cipherKey = new SignatureCipher(nFunction, scriptTimestamp.group(2), script, matchedTce, tceText);
356+
SignatureCipher cipherKey = new SignatureCipher(nFunction, scriptTimestamp.group(2), script);
353357

354358
while (matcher.find()) {
355359
String type = matcher.group(1);
@@ -403,4 +407,4 @@ protected CachedPlayerScript(@NotNull String url) {
403407
this.expireTimestampMs = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
404408
}
405409
}
406-
}
410+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package dev.lavalink.youtube.cipher;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
5+
public class TCEVariable {
6+
private final String name;
7+
private final String code;
8+
private final String value;
9+
10+
public TCEVariable(@NotNull String name, @NotNull String code, @NotNull String value) {
11+
this.name = name;
12+
this.code = code;
13+
this.value = value;
14+
}
15+
16+
public String getEscapedName() {
17+
return this.name.replace("$", "\\$");
18+
}
19+
20+
public String getName() {
21+
return this.name;
22+
}
23+
24+
public String getCode() {
25+
return this.code;
26+
}
27+
28+
public String getValue() {
29+
return this.value;
30+
}
31+
32+
}

0 commit comments

Comments
 (0)