Skip to content

Commit 0c4f1b4

Browse files
authored
Add some headers to remote cipher requests (#181)
* add optional user-agent header and plugin version header to remote cipher requests * dont log using remote cipher if null url * Cipher readme updates * pr feedback * Set yt-cipher headers in the context filter * remote cipher gets its own http interface * PR Comments, use yt context filter only * remove context, remove old 429 message
1 parent 9f70f2e commit 0c4f1b4

File tree

7 files changed

+63
-26
lines changed

7 files changed

+63
-26
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ plugins:
325325
remoteCipher:
326326
url: "http://localhost:8001" # The base URL of your remote cipher server.
327327
password: "your_secret_password" # The password to authenticate with your remote cipher server.
328+
userAgent: "your_service_name" # Optional user-agent header, used for metrics on the backend.
328329
```
329330
330331
## REST routes (`plugin` only)

common/src/main/java/dev/lavalink/youtube/YoutubeAudioSourceManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,11 @@ public YoutubeAudioSourceManager(@NotNull YoutubeSourceOptions options,
150150
contextFilter = new YoutubeHttpContextFilter();
151151
contextFilter.setTokenTracker(new YoutubeAccessTokenTracker(httpInterfaceManager));
152152
contextFilter.setOauth2Handler(oauth2Handler);
153-
154153
httpInterfaceManager.setHttpContextFilter(contextFilter);
155154

156155
if (!DataFormatTools.isNullOrEmpty(options.getRemoteCipherUrl())) {
157-
this.cipherManager = new RemoteCipherManager(options.getRemoteCipherUrl(), options.getRemoteCipherPassword());
156+
contextFilter.setCipherConfig(options.getRemoteCipherPassword(), options.getRemoteCipherUserAgent(), YoutubeSource.VERSION);
157+
this.cipherManager = new RemoteCipherManager(options.getRemoteCipherUrl());
158158
} else {
159159
this.cipherManager = new LocalSignatureCipherManager();
160160
}

common/src/main/java/dev/lavalink/youtube/YoutubeSourceOptions.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public class YoutubeSourceOptions {
88
private boolean allowDirectPlaylistIds = true;
99
private String remoteCipherUrl;
1010
private String remoteCipherPassword;
11+
private String remoteCipherUserAgent;
1112

1213
public boolean isAllowSearch() {
1314
return allowSearch;
@@ -40,14 +41,20 @@ public String getRemoteCipherUrl() {
4041
return remoteCipherUrl;
4142
}
4243

43-
public YoutubeSourceOptions setRemoteCipherUrl(String remoteCipherUrl, @Nullable String remoteCipherPassword) {
44+
public YoutubeSourceOptions setRemoteCipherUrl(String remoteCipherUrl, @Nullable String remoteCipherPassword, @Nullable String remoteCipherUserAgent) {
4445
this.remoteCipherUrl = remoteCipherUrl;
4546
this.remoteCipherPassword = remoteCipherPassword;
47+
this.remoteCipherUserAgent = remoteCipherUserAgent;
4648
return this;
4749
}
4850

4951
public String getRemoteCipherPassword() {
5052
return remoteCipherPassword;
5153
}
5254

55+
@Nullable
56+
public String getRemoteCipherUserAgent() {
57+
return remoteCipherUserAgent;
58+
}
59+
5360
}

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

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser;
77
import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools;
88
import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface;
9+
import dev.lavalink.youtube.http.YoutubeHttpContextFilter;
910
import dev.lavalink.youtube.track.format.StreamFormat;
1011
import org.apache.http.HttpEntity;
1112
import org.apache.http.HttpRequest;
@@ -36,28 +37,22 @@ public class RemoteCipherManager implements CipherManager {
3637

3738
private final Object cipherLoadLock;
3839
private final @NotNull String remoteUrl;
39-
private final @Nullable String remotePass;
4040

4141
protected volatile CachedPlayerScript cachedPlayerScript;
4242

4343
/**
4444
* Create a new remote cipher manager
4545
*/
46-
public RemoteCipherManager(@NotNull String remoteUrl, @Nullable String remotePass) {
46+
public RemoteCipherManager(@NotNull String remoteUrl) {
4747
this.cipherLoadLock = new Object();
4848
this.remoteUrl = remoteUrl;
49-
this.remotePass = remotePass;
5049
}
5150

5251
@NotNull
5352
public String getRemoteUrl() {
5453
return remoteUrl;
5554
}
5655

57-
@Nullable
58-
public String getRemotePass() {
59-
return remotePass;
60-
}
6156

6257
/**
6358
* Produces a valid playback URL for the specified track
@@ -70,8 +65,8 @@ public String getRemotePass() {
7065
*/
7166
@NotNull
7267
public URI resolveFormatUrl(@NotNull HttpInterface httpInterface,
73-
@NotNull String playerScript,
74-
@NotNull StreamFormat format) throws IOException {
68+
@NotNull String playerScript,
69+
@NotNull StreamFormat format) throws IOException {
7570
String signature = format.getSignature();
7671
String nParameter = format.getNParameter();
7772
URI initialUrl = format.getUrl();
@@ -133,13 +128,9 @@ private String getRemoteEndpoint(String path) {
133128
return remoteUrl.endsWith("/") ? remoteUrl + path : remoteUrl + "/" + path;
134129
}
135130

136-
private void applyPass(HttpRequest request) {
137-
if (remotePass != null && !remotePass.isEmpty()) {
138-
request.addHeader("Authorization", remotePass);
139-
}
140-
}
141131

142132
private String decipherN(HttpInterface httpInterface, String n, String playerScript) throws IOException {
133+
httpInterface.getContext().setAttribute(YoutubeHttpContextFilter.CIPHER_REQUEST_ATTRIBUTE, true);
143134
HttpPost request = new HttpPost(getRemoteEndpoint("decrypt_signature"));
144135

145136
log.debug("Deciphering N param: {} with script: {}", n, playerScript);
@@ -151,7 +142,6 @@ private String decipherN(HttpInterface httpInterface, String n, String playerScr
151142
.end()
152143
.done();
153144
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
154-
applyPass(request);
155145

156146
try (CloseableHttpResponse response = httpInterface.execute(request)) {
157147
int statusCode = response.getStatusLine().getStatusCode();
@@ -180,6 +170,7 @@ private String decipherN(HttpInterface httpInterface, String n, String playerScr
180170
}
181171

182172
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);
183174
HttpPost request = new HttpPost(getRemoteEndpoint("decrypt_signature"));
184175

185176
log.debug("Deciphering N param: {} and Signature: {} with script: {}", nParam, sig, playerScript);
@@ -193,7 +184,6 @@ private URI getUri(HttpInterface httpInterface, String sig, String sigKey, Strin
193184
.end()
194185
.done();
195186
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
196-
applyPass(request);
197187

198188
try (CloseableHttpResponse response = httpInterface.execute(request)) {
199189
int statusCode = response.getStatusLine().getStatusCode();
@@ -241,6 +231,7 @@ private URI getUri(HttpInterface httpInterface, String sig, String sigKey, Strin
241231
}
242232

243233
private String getTimestampFromScript(HttpInterface httpInterface, String playerScript) throws IOException {
234+
httpInterface.getContext().setAttribute(YoutubeHttpContextFilter.CIPHER_REQUEST_ATTRIBUTE, true);
244235
HttpPost request = new HttpPost(getRemoteEndpoint("get_sts"));
245236

246237
log.debug("Getting timestamp for script: {}", playerScript);
@@ -251,7 +242,6 @@ private String getTimestampFromScript(HttpInterface httpInterface, String player
251242
.end()
252243
.done();
253244
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
254-
applyPass(request);
255245

256246
try (CloseableHttpResponse response = httpInterface.execute(request)) {
257247
int statusCode = response.getStatusLine().getStatusCode();
@@ -274,3 +264,4 @@ private String getTimestampFromScript(HttpInterface httpInterface, String player
274264
}
275265

276266
}
267+

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

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
44
import com.sedmelluq.discord.lavaplayer.tools.http.HttpContextRetryCounter;
55
import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools;
6+
import com.sedmelluq.discord.lavaplayer.tools.DataFormatTools;
67
import dev.lavalink.youtube.clients.skeleton.Client;
78
import org.apache.http.HttpResponse;
89
import org.apache.http.client.CookieStore;
910
import org.apache.http.client.methods.HttpUriRequest;
1011
import org.apache.http.client.protocol.HttpClientContext;
1112
import org.apache.http.impl.client.BasicCookieStore;
1213
import org.jetbrains.annotations.NotNull;
14+
import org.jetbrains.annotations.Nullable;
1315
import org.slf4j.Logger;
1416
import org.slf4j.LoggerFactory;
1517

@@ -22,12 +24,17 @@ public class YoutubeHttpContextFilter extends BaseYoutubeHttpContextFilter {
2224
private static final String ATTRIBUTE_RESET_RETRY = "isResetRetry";
2325
public static final String ATTRIBUTE_USER_AGENT_SPECIFIED = "clientUserAgent";
2426
public static final String ATTRIBUTE_VISITOR_DATA_SPECIFIED = "clientVisitorData";
27+
public static final String CIPHER_REQUEST_ATTRIBUTE = "cipher-request";
2528

2629
private static final HttpContextRetryCounter retryCounter = new HttpContextRetryCounter("yt-token-retry");
2730

2831
private YoutubeAccessTokenTracker tokenTracker;
2932
private YoutubeOauth2Handler oauth2Handler;
3033

34+
private String remoteCipherPass;
35+
private String remoteCipherUserAgent;
36+
private String pluginVersion;
37+
3138
public void setTokenTracker(@NotNull YoutubeAccessTokenTracker tokenTracker) {
3239
this.tokenTracker = tokenTracker;
3340
}
@@ -36,6 +43,15 @@ public void setOauth2Handler(@NotNull YoutubeOauth2Handler oauth2Handler) {
3643
this.oauth2Handler = oauth2Handler;
3744
}
3845

46+
public void setCipherConfig(@Nullable String remotePass,
47+
@Nullable String userAgent,
48+
@NotNull String pluginVersion) {
49+
this.remoteCipherPass = remotePass;
50+
this.remoteCipherUserAgent = userAgent;
51+
this.pluginVersion = pluginVersion;
52+
}
53+
54+
3955
@Override
4056
public void onContextOpen(HttpClientContext context) {
4157
CookieStore cookieStore = context.getCookieStore();
@@ -70,7 +86,19 @@ public void onRequest(HttpClientContext context,
7086

7187
String userAgent = context.getAttribute(ATTRIBUTE_USER_AGENT_SPECIFIED, String.class);
7288

73-
if (!request.getURI().getHost().contains("googlevideo")) {
89+
if (isRemoteCipherRequest(context)) {
90+
context.removeAttribute(CIPHER_REQUEST_ATTRIBUTE);
91+
92+
if (!DataFormatTools.isNullOrEmpty(remoteCipherPass)) {
93+
request.addHeader("Authorization", remoteCipherPass);
94+
}
95+
96+
if (!DataFormatTools.isNullOrEmpty(remoteCipherUserAgent)) {
97+
request.addHeader("User-Agent", remoteCipherUserAgent);
98+
}
99+
100+
request.addHeader("Plugin-Version", pluginVersion);
101+
} else if (!request.getURI().getHost().contains("googlevideo")) {
74102
if (userAgent != null) {
75103
request.setHeader("User-Agent", userAgent);
76104

@@ -114,9 +142,6 @@ public void onRequest(HttpClientContext context,
114142
public boolean onRequestResponse(HttpClientContext context,
115143
HttpUriRequest request,
116144
HttpResponse response) {
117-
if (response.getStatusLine().getStatusCode() == 429) {
118-
throw new FriendlyException("This IP address has been blocked by YouTube (429).", COMMON, null);
119-
}
120145

121146
// if (tokenTracker.isTokenFetchContext(context) || retryCounter.getRetryCount(context) >= 1) {
122147
// return false;
@@ -138,4 +163,8 @@ public boolean onRequestException(HttpClientContext context,
138163

139164
return false;
140165
}
166+
167+
private boolean isRemoteCipherRequest(HttpClientContext context) {
168+
return context.getAttribute(CIPHER_REQUEST_ATTRIBUTE) == Boolean.TRUE;
169+
}
141170
}

plugin/src/main/java/dev/lavalink/youtube/plugin/YoutubePluginLoader.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,9 @@ public AudioPlayerManager configure(AudioPlayerManager audioPlayerManager) {
180180
}
181181
}
182182

183-
if (cipherConfig != null) {
183+
if (cipherConfig != null && cipherConfig.getUrl() != null) {
184184
log.info("Using remote cipher server with URL \"{}\"", cipherConfig.getUrl());
185-
sourceOptions.setRemoteCipherUrl(cipherConfig.getUrl(), cipherConfig.getPassword());
185+
sourceOptions.setRemoteCipherUrl(cipherConfig.getUrl(), cipherConfig.getPassword(), cipherConfig.getUserAgent());
186186
}
187187
}
188188

plugin/src/main/java/dev/lavalink/youtube/plugin/YoutubeRemoteCipherConfig.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
public class YoutubeRemoteCipherConfig {
44
private String url;
55
private String password;
6+
private String userAgent = "yt-source";
67

78
public String getUrl() {
89
return url;
@@ -12,6 +13,10 @@ public String getPassword() {
1213
return password;
1314
}
1415

16+
public String getUserAgent() {
17+
return userAgent;
18+
}
19+
1520
public void setUrl(String url) {
1621
this.url = url;
1722
}
@@ -20,4 +25,8 @@ public void setPassword(String password) {
2025
this.password = password;
2126
}
2227

28+
public void setUserAgent(String userAgent) {
29+
this.userAgent = userAgent;
30+
}
31+
2332
}

0 commit comments

Comments
 (0)