Skip to content

Commit e36d4c1

Browse files
drobotkNuckyzoSumAtrIX
authored
feat(Spotify - Spoof client): Fix issues like songs skipping by spoofing to iOS (#5388)
Co-authored-by: Nuckyz <[email protected]> Co-authored-by: oSumAtrIX <[email protected]>
1 parent 71db0a2 commit e36d4c1

File tree

15 files changed

+352
-936
lines changed

15 files changed

+352
-936
lines changed

extensions/spotify/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ dependencies {
77
compileOnly(project(":extensions:spotify:stub"))
88
compileOnly(libs.annotation)
99

10-
implementation(project(":extensions:spotify:utils"))
1110
implementation(libs.nanohttpd)
1211
implementation(libs.protobuf.javalite)
1312
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package app.revanced.extension.spotify.misc.fix;
2+
3+
import androidx.annotation.NonNull;
4+
import androidx.annotation.Nullable;
5+
import app.revanced.extension.shared.Logger;
6+
import app.revanced.extension.spotify.misc.fix.clienttoken.data.v0.ClienttokenHttp.*;
7+
8+
import java.io.IOException;
9+
import java.io.InputStream;
10+
import java.net.HttpURLConnection;
11+
import java.net.URL;
12+
13+
import static app.revanced.extension.spotify.misc.fix.Constants.*;
14+
15+
class ClientTokenService {
16+
private static final String IOS_CLIENT_ID = "58bd3c95768941ea9eb4350aaa033eb3";
17+
private static final String IOS_USER_AGENT;
18+
19+
static {
20+
String clientVersion = getClientVersion();
21+
int commitHashIndex = clientVersion.lastIndexOf(".");
22+
String version = clientVersion.substring(
23+
clientVersion.indexOf("-") + 1,
24+
clientVersion.lastIndexOf(".", commitHashIndex - 1)
25+
);
26+
27+
IOS_USER_AGENT = "Spotify/" + version + " iOS/" + getSystemVersion() + " (" + getHardwareMachine() + ")";
28+
}
29+
30+
private static final ConnectivitySdkData.Builder IOS_CONNECTIVITY_SDK_DATA =
31+
ConnectivitySdkData.newBuilder()
32+
.setPlatformSpecificData(PlatformSpecificData.newBuilder()
33+
.setIos(NativeIOSData.newBuilder()
34+
.setHwMachine(getHardwareMachine())
35+
.setSystemVersion(getSystemVersion())
36+
)
37+
);
38+
39+
private static final ClientDataRequest.Builder IOS_CLIENT_DATA_REQUEST =
40+
ClientDataRequest.newBuilder()
41+
.setClientVersion(getClientVersion())
42+
.setClientId(IOS_CLIENT_ID);
43+
44+
private static final ClientTokenRequest.Builder IOS_CLIENT_TOKEN_REQUEST =
45+
ClientTokenRequest.newBuilder()
46+
.setRequestType(ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST);
47+
48+
49+
@NonNull
50+
static ClientTokenRequest newIOSClientTokenRequest(String deviceId) {
51+
Logger.printInfo(() -> "Creating new iOS client token request with device ID: " + deviceId);
52+
53+
return IOS_CLIENT_TOKEN_REQUEST
54+
.setClientData(IOS_CLIENT_DATA_REQUEST
55+
.setConnectivitySdkData(IOS_CONNECTIVITY_SDK_DATA
56+
.setDeviceId(deviceId)
57+
)
58+
)
59+
.build();
60+
}
61+
62+
@Nullable
63+
static ClientTokenResponse getClientTokenResponse(@NonNull ClientTokenRequest request) {
64+
if (request.getRequestType() == ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST) {
65+
Logger.printInfo(() -> "Requesting iOS client token");
66+
String deviceId = request.getClientData().getConnectivitySdkData().getDeviceId();
67+
request = newIOSClientTokenRequest(deviceId);
68+
}
69+
70+
ClientTokenResponse response;
71+
try {
72+
response = requestClientToken(request);
73+
} catch (IOException ex) {
74+
Logger.printException(() -> "Failed to handle request", ex);
75+
return null;
76+
}
77+
78+
return response;
79+
}
80+
81+
@NonNull
82+
private static ClientTokenResponse requestClientToken(@NonNull ClientTokenRequest request) throws IOException {
83+
HttpURLConnection urlConnection = (HttpURLConnection) new URL(CLIENT_TOKEN_API_URL).openConnection();
84+
urlConnection.setRequestMethod("POST");
85+
urlConnection.setDoOutput(true);
86+
urlConnection.setRequestProperty("Content-Type", "application/x-protobuf");
87+
urlConnection.setRequestProperty("Accept", "application/x-protobuf");
88+
urlConnection.setRequestProperty("User-Agent", IOS_USER_AGENT);
89+
90+
byte[] requestArray = request.toByteArray();
91+
urlConnection.setFixedLengthStreamingMode(requestArray.length);
92+
urlConnection.getOutputStream().write(requestArray);
93+
94+
try (InputStream inputStream = urlConnection.getInputStream()) {
95+
return ClientTokenResponse.parseFrom(inputStream);
96+
}
97+
}
98+
99+
@Nullable
100+
static ClientTokenResponse serveClientTokenRequest(@NonNull InputStream inputStream) {
101+
ClientTokenRequest request;
102+
try {
103+
request = ClientTokenRequest.parseFrom(inputStream);
104+
} catch (IOException ex) {
105+
Logger.printException(() -> "Failed to parse request from input stream", ex);
106+
return null;
107+
}
108+
Logger.printInfo(() -> "Request of type: " + request.getRequestType());
109+
110+
ClientTokenResponse response = getClientTokenResponse(request);
111+
if (response != null) Logger.printInfo(() -> "Response of type: " + response.getResponseType());
112+
113+
return response;
114+
}
115+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package app.revanced.extension.spotify.misc.fix;
2+
3+
import androidx.annotation.NonNull;
4+
5+
class Constants {
6+
static final String CLIENT_TOKEN_API_PATH = "/v1/clienttoken";
7+
static final String CLIENT_TOKEN_API_URL = "https://clienttoken.spotify.com" + CLIENT_TOKEN_API_PATH;
8+
9+
// Modified by a patch. Do not touch.
10+
@NonNull
11+
static String getClientVersion() {
12+
return "";
13+
}
14+
15+
// Modified by a patch. Do not touch.
16+
@NonNull
17+
static String getSystemVersion() {
18+
return "";
19+
}
20+
21+
// Modified by a patch. Do not touch.
22+
@NonNull
23+
static String getHardwareMachine() {
24+
return "";
25+
}
26+
}

extensions/spotify/src/main/java/app/revanced/extension/spotify/misc/fix/LoginRequestListener.java

Lines changed: 0 additions & 158 deletions
This file was deleted.

0 commit comments

Comments
 (0)