Skip to content

Commit 427d995

Browse files
markuspidevgianlu
andauthored
Add Profile (following/followers) Endpoint to API (#241)
* Add Profile Endpoint * Add ability to query followers of profiles * Add GeneralJson wrapper for generic json responses * Add statusCode field to MercuryException * Update README.md * Fixed request method + using GenericJson for tokens + refactored Co-authored-by: Gianlu <[email protected]>
1 parent 7e2a77c commit 427d995

6 files changed

Lines changed: 95 additions & 9 deletions

File tree

api/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ All the endpoints will respond with `200` if successful or:
3434
### Tokens
3535
- `POST /token/{scope}` Request an access token for a specific scope (or a comma separated list of scopes).
3636

37+
### Profile
38+
- `GET /profile/{user_id}/followers` Retrieve a list of profiles that are followers of the specified user
39+
- `GET /profile/{user_id}/following` Retrieve a list of profiles that the specified user is following
40+
3741
### Events
3842
You can subscribe for players events by creating a WebSocket connection to `/events`.
3943
The currently available events are:

api/src/main/java/xyz/gianlu/librespot/api/ApiServer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public ApiServer(@NotNull ApiConfiguration conf, @NotNull SessionWrapper wrapper
2828
.post("/metadata/{uri}", new MetadataHandler(wrapper, false))
2929
.post("/search/{query}", new SearchHandler(wrapper))
3030
.post("/token/{scope}", new TokensHandler(wrapper))
31+
.post("/profile/{user_id}/{action}", new ProfileHandler(wrapper))
3132
.get("/events", events));
3233
}
3334

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package xyz.gianlu.librespot.api.handlers;
2+
3+
import io.undertow.server.HttpServerExchange;
4+
import io.undertow.util.Headers;
5+
import org.apache.logging.log4j.LogManager;
6+
import org.apache.logging.log4j.Logger;
7+
import org.jetbrains.annotations.NotNull;
8+
import xyz.gianlu.librespot.api.SessionWrapper;
9+
import xyz.gianlu.librespot.api.Utils;
10+
import xyz.gianlu.librespot.core.Session;
11+
import xyz.gianlu.librespot.mercury.MercuryClient;
12+
import xyz.gianlu.librespot.mercury.MercuryRequests;
13+
14+
import java.io.IOException;
15+
import java.util.Deque;
16+
import java.util.Map;
17+
18+
public final class ProfileHandler extends AbsSessionHandler {
19+
private static final Logger LOGGER = LogManager.getLogger(ProfileHandler.class);
20+
21+
public ProfileHandler(@NotNull SessionWrapper wrapper) {
22+
super(wrapper);
23+
}
24+
25+
private static void profileAction(@NotNull HttpServerExchange exchange, @NotNull Session session, @NotNull String userId, @NotNull String action) throws IOException {
26+
String uri = String.format("hm://user-profile-view/v2/desktop/profile/%s/%s", userId, action);
27+
28+
try {
29+
MercuryRequests.GenericJson resp = session.mercury().sendSync(MercuryRequests.getGenericJson(uri));
30+
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");
31+
exchange.getResponseSender().send(resp.toString());
32+
} catch (MercuryClient.MercuryException ex) {
33+
Utils.internalError(exchange, ex);
34+
LOGGER.error("Failed handling api request. {uri: {}}", uri, ex);
35+
}
36+
}
37+
38+
@Override
39+
protected void handleRequest(@NotNull HttpServerExchange exchange, @NotNull Session session) throws Exception {
40+
exchange.startBlocking();
41+
if (exchange.isInIoThread()) {
42+
exchange.dispatch(this);
43+
return;
44+
}
45+
46+
Map<String, Deque<String>> params = Utils.readParameters(exchange);
47+
String userId = Utils.getFirstString(params, "user_id");
48+
if (userId == null) {
49+
Utils.invalidParameter(exchange, "user_id");
50+
return;
51+
}
52+
53+
String action = Utils.getFirstString(params, "action");
54+
if (action == null) {
55+
Utils.invalidParameter(exchange, "action");
56+
return;
57+
}
58+
59+
switch (action) {
60+
case "followers":
61+
case "following":
62+
profileAction(exchange, session, userId, action);
63+
break;
64+
default:
65+
Utils.invalidParameter(exchange, "action");
66+
}
67+
}
68+
}

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package xyz.gianlu.librespot.core;
22

33
import com.google.gson.JsonArray;
4+
import com.google.gson.JsonObject;
45
import org.apache.logging.log4j.LogManager;
56
import org.apache.logging.log4j.Logger;
67
import org.jetbrains.annotations.NotNull;
@@ -48,8 +49,8 @@ public synchronized StoredToken getToken(@NotNull String... scopes) throws IOExc
4849
}
4950

5051
LOGGER.debug("Token expired or not suitable, requesting again. {scopes: {}, oldToken: {}}", Arrays.asList(scopes), token);
51-
MercuryRequests.KeymasterToken resp = session.mercury().sendSync(MercuryRequests.requestToken(session.deviceId(), String.join(",", scopes)));
52-
token = new StoredToken(resp);
52+
MercuryRequests.GenericJson resp = session.mercury().sendSync(MercuryRequests.requestToken(session.deviceId(), String.join(",", scopes)));
53+
token = new StoredToken(resp.obj);
5354

5455
LOGGER.debug("Updated token successfully! {scopes: {}, newToken: {}}", Arrays.asList(scopes), token);
5556
tokens.add(token);
@@ -68,12 +69,12 @@ public static class StoredToken {
6869
public final String[] scopes;
6970
public final long timestamp;
7071

71-
private StoredToken(@NotNull MercuryRequests.KeymasterToken token) {
72+
private StoredToken(@NotNull JsonObject obj) {
7273
timestamp = TimeProvider.currentTimeMillis();
73-
expiresIn = token.obj.get("expiresIn").getAsInt();
74-
accessToken = token.obj.get("accessToken").getAsString();
74+
expiresIn = obj.get("expiresIn").getAsInt();
75+
accessToken = obj.get("accessToken").getAsString();
7576

76-
JsonArray scopesArray = token.obj.getAsJsonArray("scope");
77+
JsonArray scopesArray = obj.getAsJsonArray("scope");
7778
scopes = new String[scopesArray.size()];
7879
for (int i = 0; i < scopesArray.size(); i++)
7980
scopes[i] = scopesArray.get(i).getAsString();

core/src/main/java/xyz/gianlu/librespot/mercury/MercuryClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ void dispatch(@NotNull Response resp) {
352352
}
353353

354354
public static class MercuryException extends Exception {
355-
private MercuryException(Response response) {
355+
private MercuryException(@NotNull Response response) {
356356
super(String.format("status: %d", response.statusCode));
357357
}
358358
}

core/src/main/java/xyz/gianlu/librespot/mercury/MercuryRequests.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,13 @@ public static JsonMercuryRequest<ResolvedContextWrapper> resolveContext(@NotNull
9898
}
9999

100100
@NotNull
101-
public static JsonMercuryRequest<KeymasterToken> requestToken(@NotNull String deviceId, @NotNull String scope) {
102-
return new JsonMercuryRequest<>(RawMercuryRequest.get(String.format("hm://keymaster/token/authenticated?scope=%s&client_id=%s&device_id=%s", scope, KEYMASTER_CLIENT_ID, deviceId)), KeymasterToken.class);
101+
public static JsonMercuryRequest<GenericJson> requestToken(@NotNull String deviceId, @NotNull String scope) {
102+
return new JsonMercuryRequest<>(RawMercuryRequest.get(String.format("hm://keymaster/token/authenticated?scope=%s&client_id=%s&device_id=%s", scope, KEYMASTER_CLIENT_ID, deviceId)), GenericJson.class);
103+
}
104+
105+
@NotNull
106+
public static JsonMercuryRequest<GenericJson> getGenericJson(@NotNull String uri) {
107+
return new JsonMercuryRequest<>(RawMercuryRequest.get(uri), GenericJson.class);
103108
}
104109

105110
@NotNull
@@ -159,4 +164,11 @@ public KeymasterToken(@NotNull JsonObject obj) {
159164
super(obj);
160165
}
161166
}
167+
168+
public static final class GenericJson extends JsonWrapper {
169+
170+
public GenericJson(@NotNull JsonObject obj) {
171+
super(obj);
172+
}
173+
}
162174
}

0 commit comments

Comments
 (0)