Skip to content

Commit 7f9ae30

Browse files
committed
Refactor context filter handling
1 parent 0c4f1b4 commit 7f9ae30

File tree

8 files changed

+93
-87
lines changed

8 files changed

+93
-87
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package dev.lavalink.youtube;
2+
3+
public class ExceptionWithResponseBody extends RuntimeException {
4+
private final String responseBody;
5+
6+
public ExceptionWithResponseBody(String message, String responseBody) {
7+
super(message);
8+
this.responseBody = responseBody;
9+
}
10+
11+
public String getResponseBody() {
12+
return responseBody;
13+
}
14+
}

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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
package dev.lavalink.youtube.cipher;
22

3+
import com.sedmelluq.discord.lavaplayer.tools.DataFormatTools;
4+
import com.sedmelluq.discord.lavaplayer.tools.ExceptionTools;
5+
import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools;
36
import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface;
7+
import dev.lavalink.youtube.ExceptionWithResponseBody;
48
import dev.lavalink.youtube.track.format.StreamFormat;
9+
import org.apache.http.client.methods.CloseableHttpResponse;
10+
import org.apache.http.client.methods.HttpGet;
11+
import org.apache.http.util.EntityUtils;
512
import org.jetbrains.annotations.NotNull;
613

714
import java.io.IOException;
@@ -12,7 +19,6 @@
1219
* Handles parsing and caching of signature ciphers
1320
*/
1421
public interface CipherManager {
15-
1622
/**
1723
* Produces a valid playback URL for the specified track
1824
*
@@ -30,6 +36,25 @@ public interface CipherManager {
3036

3137
String getTimestamp(HttpInterface httpInterface, String sourceUrl) throws IOException;
3238

39+
default CachedPlayerScript getPlayerScript(@NotNull HttpInterface httpInterface) {
40+
synchronized (this) {
41+
try (CloseableHttpResponse response = httpInterface.execute(new HttpGet("https://www.youtube.com/embed/"))) {
42+
HttpClientTools.assertSuccessWithContent(response, "fetch player script (embed)");
43+
44+
String responseText = EntityUtils.toString(response.getEntity());
45+
String scriptUrl = DataFormatTools.extractBetween(responseText, "\"jsUrl\":\"", "\"");
46+
47+
if (scriptUrl == null) {
48+
throw new ExceptionWithResponseBody("no jsUrl found", responseText);
49+
}
50+
51+
return new CachedPlayerScript(scriptUrl);
52+
} catch (IOException e) {
53+
throw ExceptionTools.toRuntimeException(e);
54+
}
55+
}
56+
}
57+
3358
class CachedPlayerScript {
3459
public final String url;
3560
public final long expireTimestampMs;
@@ -39,6 +64,4 @@ protected CachedPlayerScript(@NotNull String url) {
3964
this.expireTimestampMs = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
4065
}
4166
}
42-
43-
4467
}

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

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.sedmelluq.discord.lavaplayer.tools.ExceptionTools;
55
import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools;
66
import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface;
7+
import dev.lavalink.youtube.ExceptionWithResponseBody;
78
import dev.lavalink.youtube.YoutubeSource;
89
import dev.lavalink.youtube.cipher.ScriptExtractionException.ExtractionFailureType;
910
import dev.lavalink.youtube.track.format.StreamFormat;
@@ -91,7 +92,6 @@ public class LocalSignatureCipherManager implements CipherManager {
9192
private final ConcurrentMap<String, SignatureCipher> cipherCache;
9293
private final Set<String> dumpedScriptUrls;
9394
private final ScriptEngine scriptEngine;
94-
private final Object cipherLoadLock;
9595

9696
protected volatile CachedPlayerScript cachedPlayerScript;
9797

@@ -102,7 +102,6 @@ public LocalSignatureCipherManager() {
102102
this.cipherCache = new ConcurrentHashMap<>();
103103
this.dumpedScriptUrls = new HashSet<>();
104104
this.scriptEngine = new RhinoScriptEngineFactory().getScriptEngine();
105-
this.cipherLoadLock = new Object();
106105
}
107106

108107
/**
@@ -168,30 +167,19 @@ public URI resolveFormatUrl(@NotNull HttpInterface httpInterface,
168167
}
169168
}
170169

171-
private CachedPlayerScript getPlayerScript(@NotNull HttpInterface httpInterface) {
172-
synchronized (cipherLoadLock) {
173-
try (CloseableHttpResponse response = httpInterface.execute(new HttpGet("https://www.youtube.com/embed/"))) {
174-
HttpClientTools.assertSuccessWithContent(response, "fetch player script (embed)");
175-
176-
String responseText = EntityUtils.toString(response.getEntity());
177-
String scriptUrl = DataFormatTools.extractBetween(responseText, "\"jsUrl\":\"", "\"");
178-
179-
if (scriptUrl == null) {
180-
throw throwWithDebugInfo(log, null, "no jsUrl found", "html", responseText);
181-
}
182-
183-
return (cachedPlayerScript = new CachedPlayerScript(scriptUrl));
184-
} catch (IOException e) {
185-
throw ExceptionTools.toRuntimeException(e);
186-
}
187-
}
188-
}
189-
190170
public CachedPlayerScript getCachedPlayerScript(@NotNull HttpInterface httpInterface) {
191171
if (cachedPlayerScript == null || System.currentTimeMillis() >= cachedPlayerScript.expireTimestampMs) {
192-
synchronized (cipherLoadLock) {
172+
synchronized (this) {
193173
if (cachedPlayerScript == null || System.currentTimeMillis() >= cachedPlayerScript.expireTimestampMs) {
194-
return getPlayerScript(httpInterface);
174+
try {
175+
return (cachedPlayerScript = getPlayerScript(httpInterface));
176+
} catch (RuntimeException e) {
177+
if (e instanceof ExceptionWithResponseBody) {
178+
throw throwWithDebugInfo(log, null, e.getMessage(), "html", ((ExceptionWithResponseBody) e).getResponseBody());
179+
}
180+
181+
throw e;
182+
}
195183
}
196184
}
197185
}
@@ -204,7 +192,7 @@ private SignatureCipher getCipherScript(@NotNull HttpInterface httpInterface,
204192
SignatureCipher cipherKey = cipherCache.get(cipherScriptUrl);
205193

206194
if (cipherKey == null) {
207-
synchronized (cipherLoadLock) {
195+
synchronized (this) {
208196
log.debug("Parsing player script {}", cipherScriptUrl);
209197

210198
try (CloseableHttpResponse response = httpInterface.execute(new HttpGet(CipherUtils.parseTokenScriptUrl(cipherScriptUrl)))) {
@@ -226,7 +214,7 @@ private SignatureCipher getCipherScript(@NotNull HttpInterface httpInterface,
226214

227215
public String getRawScript(@NotNull HttpInterface httpInterface,
228216
@NotNull String cipherScriptUrl) throws IOException {
229-
synchronized (cipherLoadLock) {
217+
synchronized (this) {
230218
log.debug("getting raw player script {}", cipherScriptUrl);
231219

232220
try (CloseableHttpResponse response = httpInterface.execute(new HttpGet(CipherUtils.parseTokenScriptUrl(cipherScriptUrl)))) {
@@ -267,7 +255,7 @@ private void dumpProblematicScript(@NotNull String script, @NotNull String sourc
267255
}
268256

269257
public String getTimestamp(HttpInterface httpInterface, String sourceUrl) throws IOException {
270-
synchronized (cipherLoadLock) {
258+
synchronized (this) {
271259
log.debug("Timestamp from script {}", sourceUrl);
272260

273261
try (CloseableHttpResponse response = httpInterface.execute(new HttpGet(CipherUtils.parseTokenScriptUrl(sourceUrl)))) {

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

Lines changed: 22 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,19 @@
22

33
import com.grack.nanojson.JsonWriter;
44
import com.sedmelluq.discord.lavaplayer.tools.DataFormatTools;
5-
import com.sedmelluq.discord.lavaplayer.tools.ExceptionTools;
65
import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser;
7-
import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools;
86
import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface;
7+
import dev.lavalink.youtube.ExceptionWithResponseBody;
98
import dev.lavalink.youtube.http.YoutubeHttpContextFilter;
109
import dev.lavalink.youtube.track.format.StreamFormat;
1110
import org.apache.http.HttpEntity;
12-
import org.apache.http.HttpRequest;
1311
import org.apache.http.client.methods.CloseableHttpResponse;
14-
import org.apache.http.client.methods.HttpGet;
1512
import org.apache.http.client.methods.HttpPost;
1613
import org.apache.http.client.utils.URIBuilder;
1714
import org.apache.http.entity.ContentType;
1815
import org.apache.http.entity.StringEntity;
1916
import org.apache.http.util.EntityUtils;
2017
import org.jetbrains.annotations.NotNull;
21-
import org.jetbrains.annotations.Nullable;
2218
import org.slf4j.Logger;
2319
import org.slf4j.LoggerFactory;
2420

@@ -35,7 +31,6 @@
3531
public class RemoteCipherManager implements CipherManager {
3632
private static final Logger log = LoggerFactory.getLogger(RemoteCipherManager.class);
3733

38-
private final Object cipherLoadLock;
3934
private final @NotNull String remoteUrl;
4035

4136
protected volatile CachedPlayerScript cachedPlayerScript;
@@ -44,7 +39,6 @@ public class RemoteCipherManager implements CipherManager {
4439
* Create a new remote cipher manager
4540
*/
4641
public RemoteCipherManager(@NotNull String remoteUrl) {
47-
this.cipherLoadLock = new Object();
4842
this.remoteUrl = remoteUrl;
4943
}
5044

@@ -65,50 +59,39 @@ public String getRemoteUrl() {
6559
*/
6660
@NotNull
6761
public URI resolveFormatUrl(@NotNull HttpInterface httpInterface,
68-
@NotNull String playerScript,
69-
@NotNull StreamFormat format) throws IOException {
62+
@NotNull String playerScript,
63+
@NotNull StreamFormat format) throws IOException {
7064
String signature = format.getSignature();
7165
String nParameter = format.getNParameter();
7266
URI initialUrl = format.getUrl();
7367

7468
URIBuilder uri = new URIBuilder(initialUrl);
7569

7670
if (!DataFormatTools.isNullOrEmpty(signature)) {
77-
return getUri(httpInterface, format.getSignature(), format.getSignatureKey(), nParameter, initialUrl, playerScript);
71+
return getUri(configureHttpInterface(httpInterface), format.getSignature(), format.getSignatureKey(), nParameter, initialUrl, playerScript);
7872
}
7973

80-
uri.setParameter("n", decipherN(httpInterface, nParameter, playerScript));
74+
uri.setParameter("n", decipherN(configureHttpInterface(httpInterface), nParameter, playerScript));
8175
try {
8276
return uri.build();
8377
} catch (URISyntaxException f) {
8478
throw new RuntimeException(f);
8579
}
8680
}
8781

88-
private CachedPlayerScript getPlayerScript(@NotNull HttpInterface httpInterface) {
89-
synchronized (cipherLoadLock) {
90-
try (CloseableHttpResponse response = httpInterface.execute(new HttpGet("https://www.youtube.com/embed/"))) {
91-
HttpClientTools.assertSuccessWithContent(response, "fetch player script (embed)");
92-
93-
String responseText = EntityUtils.toString(response.getEntity());
94-
String scriptUrl = DataFormatTools.extractBetween(responseText, "\"jsUrl\":\"", "\"");
95-
96-
if (scriptUrl == null) {
97-
throw throwWithDebugInfo(log, null, "no jsUrl found", "html", responseText);
98-
}
99-
100-
return (cachedPlayerScript = new CachedPlayerScript(scriptUrl));
101-
} catch (IOException e) {
102-
throw ExceptionTools.toRuntimeException(e);
103-
}
104-
}
105-
}
106-
10782
public CachedPlayerScript getCachedPlayerScript(@NotNull HttpInterface httpInterface) {
10883
if (cachedPlayerScript == null || System.currentTimeMillis() >= cachedPlayerScript.expireTimestampMs) {
109-
synchronized (cipherLoadLock) {
84+
synchronized (this) {
11085
if (cachedPlayerScript == null || System.currentTimeMillis() >= cachedPlayerScript.expireTimestampMs) {
111-
return getPlayerScript(httpInterface);
86+
try {
87+
return (cachedPlayerScript = getPlayerScript(httpInterface));
88+
} catch (RuntimeException e) {
89+
if (e instanceof ExceptionWithResponseBody) {
90+
throw throwWithDebugInfo(log, null, e.getMessage(), "html", ((ExceptionWithResponseBody) e).getResponseBody());
91+
}
92+
93+
throw e;
94+
}
11295
}
11396
}
11497
}
@@ -117,20 +100,17 @@ public CachedPlayerScript getCachedPlayerScript(@NotNull HttpInterface httpInter
117100
}
118101

119102
public String getTimestamp(HttpInterface httpInterface, String sourceUrl) throws IOException {
120-
synchronized (cipherLoadLock) {
103+
synchronized (this) {
121104
log.debug("Timestamp from script {}", sourceUrl);
122-
123-
return getTimestampFromScript(httpInterface, sourceUrl);
105+
return getTimestampFromScript(configureHttpInterface(httpInterface), sourceUrl);
124106
}
125107
}
126108

127109
private String getRemoteEndpoint(String path) {
128110
return remoteUrl.endsWith("/") ? remoteUrl + path : remoteUrl + "/" + path;
129111
}
130112

131-
132113
private String decipherN(HttpInterface httpInterface, String n, String playerScript) throws IOException {
133-
httpInterface.getContext().setAttribute(YoutubeHttpContextFilter.CIPHER_REQUEST_ATTRIBUTE, true);
134114
HttpPost request = new HttpPost(getRemoteEndpoint("decrypt_signature"));
135115

136116
log.debug("Deciphering N param: {} with script: {}", n, playerScript);
@@ -154,14 +134,14 @@ private String decipherN(HttpInterface httpInterface, String n, String playerScr
154134
}
155135

156136
JsonBrowser json = JsonBrowser.parse(responseBody);
157-
158137
String returnedN = json.get("decrypted_n_sig").text();
159138

160139
log.debug("Received decrypted N: {}", returnedN);
161140

162141
if (returnedN != null && !returnedN.isEmpty()) {
163142
return returnedN;
164143
}
144+
165145
return "";
166146
} else {
167147
throw new IOException("Decryption proxy request failed with status code: " + statusCode + ". Response: " + responseBody);
@@ -170,7 +150,6 @@ private String decipherN(HttpInterface httpInterface, String n, String playerScr
170150
}
171151

172152
private URI getUri(HttpInterface httpInterface, String sig, String sigKey, String nParam, URI initial, String playerScript) throws IOException {
173-
httpInterface.getContext().setAttribute(YoutubeHttpContextFilter.CIPHER_REQUEST_ATTRIBUTE, true);
174153
HttpPost request = new HttpPost(getRemoteEndpoint("decrypt_signature"));
175154

176155
log.debug("Deciphering N param: {} and Signature: {} with script: {}", nParam, sig, playerScript);
@@ -231,7 +210,6 @@ private URI getUri(HttpInterface httpInterface, String sig, String sigKey, Strin
231210
}
232211

233212
private String getTimestampFromScript(HttpInterface httpInterface, String playerScript) throws IOException {
234-
httpInterface.getContext().setAttribute(YoutubeHttpContextFilter.CIPHER_REQUEST_ATTRIBUTE, true);
235213
HttpPost request = new HttpPost(getRemoteEndpoint("get_sts"));
236214

237215
log.debug("Getting timestamp for script: {}", playerScript);
@@ -263,5 +241,9 @@ private String getTimestampFromScript(HttpInterface httpInterface, String player
263241
}
264242
}
265243

244+
public HttpInterface configureHttpInterface(HttpInterface httpInterface) {
245+
httpInterface.getContext().setAttribute(YoutubeHttpContextFilter.REMOTE_CIPHER_REQUEST_ATTRIBUTE, true);
246+
return httpInterface;
247+
}
266248
}
267249

common/src/main/java/dev/lavalink/youtube/http/YoutubeAccessTokenTracker.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public String getVisitorId() {
6363
}
6464

6565
public boolean isTokenFetchContext(@NotNull HttpClientContext context) {
66-
return context.getAttribute(TOKEN_FETCH_CONTEXT_ATTRIBUTE) == Boolean.TRUE;
66+
return context.removeAttribute(TOKEN_FETCH_CONTEXT_ATTRIBUTE) == Boolean.TRUE;
6767
}
6868

6969
private String fetchVisitorId() throws IOException {

0 commit comments

Comments
 (0)