Skip to content

Commit 6b45130

Browse files
committed
Migrating some APIs to their HTTP equivalent
1 parent 8a6d9a6 commit 6b45130

13 files changed

Lines changed: 271 additions & 208 deletions

File tree

api/src/main/java/xyz/gianlu/librespot/api/handlers/MetadataHandler.java

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 devgianlu
2+
* Copyright 2022 devgianlu
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,7 +28,6 @@
2828
import xyz.gianlu.librespot.core.Session;
2929
import xyz.gianlu.librespot.dealer.ApiClient;
3030
import xyz.gianlu.librespot.mercury.MercuryClient;
31-
import xyz.gianlu.librespot.mercury.MercuryRequests;
3231
import xyz.gianlu.librespot.metadata.*;
3332

3433
import java.io.IOException;
@@ -122,15 +121,7 @@ private JsonObject handle(@NotNull Session session, @NotNull MetadataType type,
122121

123122
@NotNull
124123
private JsonObject handlePlaylist(@NotNull Session session, @NotNull String uri) throws IOException, MercuryClient.MercuryException {
125-
JsonObject obj = new JsonObject();
126-
obj.add("tracks", session.mercury().sendSync(MercuryRequests.getPlaylist(PlaylistId.fromUri(uri))).json());
127-
128-
try {
129-
obj.add("annotations", session.mercury().sendSync(MercuryRequests.getPlaylistAnnotation(PlaylistId.fromUri(uri))).json());
130-
} catch (MercuryClient.MercuryException ignored) {
131-
}
132-
133-
return obj;
124+
return ProtobufToJson.convert(session.api().getPlaylist(PlaylistId.fromUri(uri)));
134125
}
135126

136127
private enum MetadataType {

api/src/main/java/xyz/gianlu/librespot/api/handlers/ProfileHandler.java

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 devgianlu
2+
* Copyright 2022 devgianlu
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package xyz.gianlu.librespot.api.handlers;
1818

19+
import com.google.gson.JsonObject;
1920
import io.undertow.server.HttpServerExchange;
2021
import io.undertow.util.Headers;
2122
import org.apache.logging.log4j.LogManager;
@@ -24,10 +25,7 @@
2425
import xyz.gianlu.librespot.api.SessionWrapper;
2526
import xyz.gianlu.librespot.api.Utils;
2627
import xyz.gianlu.librespot.core.Session;
27-
import xyz.gianlu.librespot.mercury.MercuryClient;
28-
import xyz.gianlu.librespot.mercury.MercuryRequests;
2928

30-
import java.io.IOException;
3129
import java.util.Deque;
3230
import java.util.Map;
3331

@@ -38,19 +36,6 @@ public ProfileHandler(@NotNull SessionWrapper wrapper) {
3836
super(wrapper);
3937
}
4038

41-
private static void profileAction(@NotNull HttpServerExchange exchange, @NotNull Session session, @NotNull String userId, @NotNull String action) throws IOException {
42-
String uri = String.format("hm://user-profile-view/v2/desktop/profile/%s/%s", userId, action);
43-
44-
try {
45-
MercuryRequests.GenericJson resp = session.mercury().sendSync(MercuryRequests.getGenericJson(uri));
46-
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");
47-
exchange.getResponseSender().send(resp.toString());
48-
} catch (MercuryClient.MercuryException ex) {
49-
Utils.internalError(exchange, ex);
50-
LOGGER.error("Failed handling api request. {uri: {}}", uri, ex);
51-
}
52-
}
53-
5439
@Override
5540
protected void handleRequest(@NotNull HttpServerExchange exchange, @NotNull Session session) throws Exception {
5641
exchange.startBlocking();
@@ -72,10 +57,20 @@ protected void handleRequest(@NotNull HttpServerExchange exchange, @NotNull Sess
7257
return;
7358
}
7459

60+
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");
61+
7562
switch (action) {
7663
case "followers":
64+
JsonObject followers = session.api().getUserFollowers(userId);
65+
exchange.getResponseSender().send(followers.toString());
66+
break;
7767
case "following":
78-
profileAction(exchange, session, userId, action);
68+
JsonObject following = session.api().getUserFollowing(userId);
69+
exchange.getResponseSender().send(following.toString());
70+
break;
71+
case "profile":
72+
JsonObject profile = session.api().getUserProfile(userId, null, null);
73+
exchange.getResponseSender().send(profile.toString());
7974
break;
8075
default:
8176
Utils.invalidParameter(exchange, "action");

lib/src/main/java/xyz/gianlu/librespot/core/TokenProvider.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 devgianlu
2+
* Copyright 2022 devgianlu
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import org.slf4j.Logger;
2424
import org.slf4j.LoggerFactory;
2525
import xyz.gianlu.librespot.common.Utils;
26+
import xyz.gianlu.librespot.json.GenericJson;
2627
import xyz.gianlu.librespot.mercury.MercuryClient;
2728
import xyz.gianlu.librespot.mercury.MercuryRequests;
2829

@@ -65,7 +66,7 @@ public synchronized StoredToken getToken(@NotNull String... scopes) throws IOExc
6566
}
6667

6768
LOGGER.debug("Token expired or not suitable, requesting again. {scopes: {}, oldToken: {}}", Arrays.asList(scopes), token);
68-
MercuryRequests.GenericJson resp = session.mercury().sendSync(MercuryRequests.requestToken(session.deviceId(), String.join(",", scopes)));
69+
GenericJson resp = session.mercury().sendSync(MercuryRequests.requestToken(session.deviceId(), String.join(",", scopes)));
6970
token = new StoredToken(resp.obj);
7071

7172
LOGGER.debug("Updated token successfully! {scopes: {}, newToken: {}}", Arrays.asList(scopes), token);

lib/src/main/java/xyz/gianlu/librespot/dealer/ApiClient.java

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616

1717
package xyz.gianlu.librespot.dealer;
1818

19+
import com.google.gson.JsonObject;
20+
import com.google.gson.JsonParser;
1921
import com.google.protobuf.Message;
2022
import com.spotify.clienttoken.data.v0.Connectivity;
2123
import com.spotify.clienttoken.http.v0.ClientToken;
2224
import com.spotify.connectstate.Connect;
2325
import com.spotify.extendedmetadata.ExtendedMetadata;
2426
import com.spotify.metadata.Metadata;
27+
import com.spotify.playlist4.Playlist4ApiProto;
2528
import okhttp3.*;
2629
import okio.BufferedSink;
2730
import org.jetbrains.annotations.NotNull;
@@ -30,11 +33,13 @@
3033
import org.slf4j.LoggerFactory;
3134
import xyz.gianlu.librespot.Version;
3235
import xyz.gianlu.librespot.core.Session;
36+
import xyz.gianlu.librespot.json.StationsWrapper;
3337
import xyz.gianlu.librespot.mercury.MercuryClient;
3438
import xyz.gianlu.librespot.mercury.MercuryRequests;
3539
import xyz.gianlu.librespot.metadata.*;
3640

3741
import java.io.IOException;
42+
import java.util.List;
3843

3944
import static com.spotify.canvaz.CanvazOuterClass.EntityCanvazRequest;
4045
import static com.spotify.canvaz.CanvazOuterClass.EntityCanvazResponse;
@@ -202,6 +207,7 @@ public EntityCanvazResponse getCanvases(@NotNull EntityCanvazRequest req) throws
202207
}
203208
}
204209

210+
@NotNull
205211
public ExtendedMetadata.BatchedExtensionResponse getExtendedMetadata(@NotNull ExtendedMetadata.BatchedEntityRequest req) throws IOException, MercuryClient.MercuryException {
206212
try (Response resp = send("POST", "/extended-metadata/v0/extended-metadata", null, protoBody(req))) {
207213
StatusCodeException.checkStatus(resp);
@@ -212,6 +218,99 @@ public ExtendedMetadata.BatchedExtensionResponse getExtendedMetadata(@NotNull Ex
212218
}
213219
}
214220

221+
@NotNull
222+
public Playlist4ApiProto.SelectedListContent getPlaylist(@NotNull PlaylistId id) throws IOException, MercuryClient.MercuryException {
223+
try (Response resp = send("GET", "/playlist/v2/playlist/" + id.id(), null, null)) {
224+
StatusCodeException.checkStatus(resp);
225+
226+
227+
ResponseBody body;
228+
if ((body = resp.body()) == null) throw new IOException();
229+
return Playlist4ApiProto.SelectedListContent.parseFrom(body.byteStream());
230+
}
231+
}
232+
233+
@NotNull
234+
public JsonObject getUserProfile(@NotNull String id, @Nullable Integer playlistLimit, @Nullable Integer artistLimit) throws IOException, MercuryClient.MercuryException {
235+
StringBuilder url = new StringBuilder();
236+
url.append("/user-profile-view/v3/profile/");
237+
url.append(id);
238+
239+
if (playlistLimit != null || artistLimit != null) {
240+
url.append("?");
241+
242+
if (playlistLimit != null) {
243+
url.append("playlist_limit=");
244+
url.append(playlistLimit);
245+
if (artistLimit != null)
246+
url.append("&");
247+
}
248+
249+
if (artistLimit != null) {
250+
url.append("artist_limit=");
251+
url.append(artistLimit);
252+
}
253+
}
254+
255+
try (Response resp = send("GET", url.toString(), null, null)) {
256+
StatusCodeException.checkStatus(resp);
257+
258+
ResponseBody body;
259+
if ((body = resp.body()) == null) throw new IOException();
260+
return JsonParser.parseReader(body.charStream()).getAsJsonObject();
261+
}
262+
}
263+
264+
@NotNull
265+
public JsonObject getUserFollowers(@NotNull String id) throws IOException, MercuryClient.MercuryException {
266+
try (Response resp = send("GET", "/user-profile-view/v3/profile/" + id + "/followers", null, null)) {
267+
StatusCodeException.checkStatus(resp);
268+
269+
ResponseBody body;
270+
if ((body = resp.body()) == null) throw new IOException();
271+
return JsonParser.parseReader(body.charStream()).getAsJsonObject();
272+
}
273+
}
274+
275+
@NotNull
276+
public JsonObject getUserFollowing(@NotNull String id) throws IOException, MercuryClient.MercuryException {
277+
try (Response resp = send("GET", "/user-profile-view/v3/profile/" + id + "/following", null, null)) {
278+
StatusCodeException.checkStatus(resp);
279+
280+
ResponseBody body;
281+
if ((body = resp.body()) == null) throw new IOException();
282+
return JsonParser.parseReader(body.charStream()).getAsJsonObject();
283+
}
284+
}
285+
286+
@NotNull
287+
public JsonObject getRadioForTrack(@NotNull PlayableId id) throws IOException, MercuryClient.MercuryException {
288+
try (Response resp = send("GET", "/inspiredby-mix/v2/seed_to_playlist/" + id.toSpotifyUri() + "?response-format=json", null, null)) {
289+
StatusCodeException.checkStatus(resp);
290+
291+
ResponseBody body;
292+
if ((body = resp.body()) == null) throw new IOException();
293+
return JsonParser.parseReader(body.charStream()).getAsJsonObject();
294+
}
295+
}
296+
297+
@NotNull
298+
public StationsWrapper getApolloStation(@NotNull String context, @NotNull List<String> prevTracks, int count, boolean autoplay) throws IOException, MercuryClient.MercuryException {
299+
StringBuilder prevTracksStr = new StringBuilder();
300+
for (int i = 0; i < prevTracks.size(); i++) {
301+
if (i != 0) prevTracksStr.append(",");
302+
prevTracksStr.append(prevTracks.get(i));
303+
}
304+
305+
try (Response resp = send("GET", String.format("/radio-apollo/v3/stations/%s?count=%d&prev_tracks=%s&autoplay=%b", context, count, prevTracksStr, autoplay), null, null)) {
306+
StatusCodeException.checkStatus(resp);
307+
308+
ResponseBody body;
309+
if ((body = resp.body()) == null) throw new IOException();
310+
return new StationsWrapper(JsonParser.parseReader(body.charStream()).getAsJsonObject());
311+
}
312+
}
313+
215314
@NotNull
216315
private ClientToken.ClientTokenResponse clientToken() throws IOException {
217316
ClientToken.ClientTokenRequest protoReq = ClientToken.ClientTokenRequest.newBuilder()
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2022 devgianlu
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package xyz.gianlu.librespot.json;
18+
19+
import com.google.gson.JsonObject;
20+
import org.jetbrains.annotations.NotNull;
21+
22+
/**
23+
* @author devgianlu
24+
*/
25+
public final class GenericJson extends JsonWrapper {
26+
27+
public GenericJson(@NotNull JsonObject obj) {
28+
super(obj);
29+
}
30+
}

lib/src/main/java/xyz/gianlu/librespot/mercury/JsonWrapper.java renamed to lib/src/main/java/xyz/gianlu/librespot/json/JsonWrapper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 devgianlu
2+
* Copyright 2022 devgianlu
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package xyz.gianlu.librespot.mercury;
17+
package xyz.gianlu.librespot.json;
1818

1919
import com.google.gson.JsonObject;
2020
import org.jetbrains.annotations.NotNull;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2022 devgianlu
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package xyz.gianlu.librespot.json;
18+
19+
import com.google.gson.JsonObject;
20+
import com.spotify.context.ContextPageOuterClass;
21+
import org.jetbrains.annotations.NotNull;
22+
import org.jetbrains.annotations.Nullable;
23+
import xyz.gianlu.librespot.common.ProtoUtils;
24+
25+
import java.util.List;
26+
27+
/**
28+
* @author devgianlu
29+
*/
30+
public final class ResolvedContextWrapper extends JsonWrapper {
31+
32+
public ResolvedContextWrapper(@NotNull JsonObject obj) {
33+
super(obj);
34+
}
35+
36+
@NotNull
37+
public List<ContextPageOuterClass.ContextPage> pages() {
38+
return ProtoUtils.jsonToContextPages(obj.getAsJsonArray("pages"));
39+
}
40+
41+
@Nullable
42+
public JsonObject metadata() {
43+
return obj.getAsJsonObject("metadata");
44+
}
45+
46+
@NotNull
47+
public String uri() {
48+
return obj.get("uri").getAsString();
49+
}
50+
51+
@NotNull
52+
public String url() {
53+
return obj.get("url").getAsString();
54+
}
55+
}

0 commit comments

Comments
 (0)