Skip to content

Commit 868ad16

Browse files
committed
Request Album and Artist
1 parent f0c9e0e commit 868ad16

7 files changed

Lines changed: 192 additions & 10 deletions

File tree

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

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
package xyz.gianlu.librespot.api;
22

33
import com.google.gson.JsonElement;
4+
import com.google.gson.JsonObject;
45
import com.google.protobuf.AbstractMessageLite;
56
import org.jetbrains.annotations.NotNull;
7+
import org.jetbrains.annotations.Nullable;
68
import xyz.gianlu.librespot.api.server.AbsApiHandler;
79
import xyz.gianlu.librespot.api.server.ApiServer;
810
import xyz.gianlu.librespot.core.Session;
911
import xyz.gianlu.librespot.mercury.MercuryClient;
1012
import xyz.gianlu.librespot.mercury.MercuryRequests;
1113
import xyz.gianlu.librespot.mercury.ProtoJsonMercuryRequest;
12-
import xyz.gianlu.librespot.mercury.model.PlaylistId;
13-
import xyz.gianlu.librespot.mercury.model.TrackId;
14+
import xyz.gianlu.librespot.mercury.model.*;
1415

1516
import java.io.IOException;
1617

@@ -27,15 +28,40 @@ public MetadataHandler(@NotNull Session session) {
2728
this.client = session.mercury();
2829
}
2930

31+
@NotNull
32+
private static <I extends SpotifyId> I extractId(@NotNull Class<I> clazz, @NotNull ApiServer.Request request, @Nullable JsonElement params) throws ApiServer.PredefinedJsonRpcException {
33+
if (params == null || !params.isJsonObject())
34+
throw ApiServer.PredefinedJsonRpcException.from(request, ApiServer.PredefinedJsonRpcError.INVALID_PARAMS);
35+
36+
try {
37+
JsonObject obj = params.getAsJsonObject();
38+
if (obj.has("gid")) {
39+
return SpotifyId.fromHex(clazz, obj.get("gid").getAsString());
40+
} else if (obj.has("uri")) {
41+
return SpotifyId.fromUri(clazz, obj.get("uri").getAsString());
42+
} else if (obj.has("base62")) {
43+
return SpotifyId.fromBase62(clazz, obj.get("gid").getAsString());
44+
} else {
45+
throw ApiServer.PredefinedJsonRpcException.from(request, ApiServer.PredefinedJsonRpcError.INVALID_REQUEST);
46+
}
47+
} catch (SpotifyId.SpotifyIdParsingException ex) {
48+
throw ApiServer.PredefinedJsonRpcException.from(request, ApiServer.PredefinedJsonRpcError.INVALID_REQUEST);
49+
}
50+
}
51+
3052
@Override
3153
protected @NotNull JsonElement handleRequest(ApiServer.@NotNull Request request) throws ApiServer.PredefinedJsonRpcException, HandlingException {
3254
switch (request.getSuffix()) {
3355
case "rootlists":
3456
return handle(MercuryRequests.getRootPlaylists(session.apWelcome().getCanonicalUsername()));
3557
case "playlist":
36-
return handle(MercuryRequests.getPlaylist(PlaylistId.fromUri(request.params.getAsString())));
58+
return handle(MercuryRequests.getPlaylist(extractId(PlaylistId.class, request, request.params)));
3759
case "track":
38-
return handle(MercuryRequests.getTrack(TrackId.fromUri(request.params.getAsString())));
60+
return handle(MercuryRequests.getTrack(extractId(TrackId.class, request, request.params)));
61+
case "artist":
62+
return handle(MercuryRequests.getArtist(extractId(ArtistId.class, request, request.params)));
63+
case "album":
64+
return handle(MercuryRequests.getAlbum(extractId(AlbumId.class, request, request.params)));
3965
default:
4066
throw ApiServer.PredefinedJsonRpcException.from(request, ApiServer.PredefinedJsonRpcError.METHOD_NOT_FOUND);
4167
}

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import xyz.gianlu.librespot.common.proto.Metadata;
1212
import xyz.gianlu.librespot.common.proto.Playlist4Changes;
1313
import xyz.gianlu.librespot.common.proto.Playlist4Content;
14+
import xyz.gianlu.librespot.mercury.model.AlbumId;
15+
import xyz.gianlu.librespot.mercury.model.ArtistId;
1416
import xyz.gianlu.librespot.mercury.model.PlaylistId;
1517
import xyz.gianlu.librespot.mercury.model.TrackId;
1618

@@ -108,7 +110,7 @@ public final class MercuryRequests {
108110
@Override
109111
public @NotNull JsonElement convert(Metadata.@NotNull Artist proto) {
110112
JsonObject obj = new JsonObject();
111-
obj.addProperty("gid", Utils.toBase64(proto.getGid()));
113+
obj.addProperty("gid", Utils.bytesToHex(proto.getGid()));
112114
obj.addProperty("name", proto.getName());
113115
obj.addProperty("popularity", proto.getPopularity());
114116
obj.addProperty("isPortraitAlbumCover", proto.getIsPortraitAlbumCover());
@@ -132,7 +134,7 @@ public final class MercuryRequests {
132134
@Override
133135
public @NotNull JsonElement convert(Metadata.@NotNull Album proto) {
134136
JsonObject obj = new JsonObject();
135-
obj.addProperty("gid", Utils.toBase64(proto.getGid()));
137+
obj.addProperty("gid", Utils.bytesToHex(proto.getGid()));
136138
obj.addProperty("name", proto.getName());
137139
obj.addProperty("popularity", proto.getPopularity());
138140
obj.addProperty("label", proto.getLabel());
@@ -161,7 +163,7 @@ public final class MercuryRequests {
161163
@Override
162164
public @NotNull JsonElement convert(Metadata.@NotNull Track proto) {
163165
JsonObject obj = new JsonObject();
164-
obj.addProperty("gid", Utils.toBase64(proto.getGid()));
166+
obj.addProperty("gid", Utils.bytesToHex(proto.getGid()));
165167
obj.addProperty("name", proto.getName());
166168
obj.addProperty("number", proto.getNumber());
167169
obj.addProperty("discNumber", proto.getDiscNumber());
@@ -236,6 +238,16 @@ public static ProtoJsonMercuryRequest<Metadata.Track> getTrack(@NotNull TrackId
236238
return new ProtoJsonMercuryRequest<>(RawMercuryRequest.get(id.toMercuryUri()), Metadata.Track.parser(), TRACK_JSON_CONVERTER);
237239
}
238240

241+
@NotNull
242+
public static ProtoJsonMercuryRequest<Metadata.Artist> getArtist(@NotNull ArtistId id) {
243+
return new ProtoJsonMercuryRequest<>(RawMercuryRequest.get(id.toMercuryUri()), Metadata.Artist.parser(), ARTIST_JSON_CONVERTER);
244+
}
245+
246+
@NotNull
247+
public static ProtoJsonMercuryRequest<Metadata.Album> getAlbum(@NotNull AlbumId id) {
248+
return new ProtoJsonMercuryRequest<>(RawMercuryRequest.get(id.toMercuryUri()), Metadata.Album.parser(), ALBUM_JSON_CONVERTER);
249+
}
250+
239251
@NotNull
240252
public static ProtobufMercuryRequest<Mercury.MercuryMultiGetReply> multiGet(@NotNull String uri, Mercury.MercuryRequest... subs) {
241253
RawMercuryRequest.Builder request = RawMercuryRequest.newBuilder()
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package xyz.gianlu.librespot.mercury.model;
2+
3+
import io.seruco.encoding.base62.Base62;
4+
import org.jetbrains.annotations.NotNull;
5+
import xyz.gianlu.librespot.common.Utils;
6+
7+
import java.math.BigInteger;
8+
import java.util.regex.Matcher;
9+
import java.util.regex.Pattern;
10+
11+
/**
12+
* @author Gianlu
13+
*/
14+
public final class AlbumId implements SpotifyId {
15+
private static final Pattern PATTERN = Pattern.compile("spotify:album:(.{22})");
16+
private static final Base62 BASE62 = Base62.createInstanceWithInvertedCharacterSet();
17+
private final String hexId;
18+
19+
private AlbumId(@NotNull String hex) {
20+
this.hexId = hex;
21+
}
22+
23+
@NotNull
24+
public static AlbumId fromUri(@NotNull String uri) {
25+
Matcher matcher = PATTERN.matcher(uri);
26+
if (matcher.find()) {
27+
String id = matcher.group(1);
28+
return new AlbumId(Utils.bytesToHex(BASE62.decode(id.getBytes())));
29+
} else {
30+
throw new IllegalArgumentException("Not a Spotify album ID: " + uri);
31+
}
32+
}
33+
34+
@NotNull
35+
public static AlbumId fromBase62(@NotNull String base62) {
36+
return new AlbumId(Utils.bytesToHex(BASE62.decode(base62.getBytes())));
37+
}
38+
39+
@NotNull
40+
public static AlbumId fromHex(@NotNull String hex) {
41+
return new AlbumId(hex);
42+
}
43+
44+
@Override
45+
public @NotNull String toMercuryUri() {
46+
return "hm://metadata/4/album/" + hexId;
47+
}
48+
49+
@Override
50+
public @NotNull String toSpotifyUri() {
51+
return "spotify:album:" + new String(BASE62.encode(new BigInteger(hexId, 16).toByteArray()));
52+
}
53+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package xyz.gianlu.librespot.mercury.model;
2+
3+
import io.seruco.encoding.base62.Base62;
4+
import org.jetbrains.annotations.NotNull;
5+
import xyz.gianlu.librespot.common.Utils;
6+
7+
import java.math.BigInteger;
8+
import java.util.regex.Matcher;
9+
import java.util.regex.Pattern;
10+
11+
/**
12+
* @author Gianlu
13+
*/
14+
public final class ArtistId implements SpotifyId {
15+
private static final Pattern PATTERN = Pattern.compile("spotify:artist:(.{22})");
16+
private static final Base62 BASE62 = Base62.createInstanceWithInvertedCharacterSet();
17+
private final String hexId;
18+
19+
private ArtistId(@NotNull String hex) {
20+
this.hexId = hex;
21+
}
22+
23+
@NotNull
24+
public static ArtistId fromUri(@NotNull String uri) {
25+
Matcher matcher = PATTERN.matcher(uri);
26+
if (matcher.find()) {
27+
String id = matcher.group(1);
28+
return new ArtistId(Utils.bytesToHex(BASE62.decode(id.getBytes())));
29+
} else {
30+
throw new IllegalArgumentException("Not a Spotify artist ID: " + uri);
31+
}
32+
}
33+
34+
@NotNull
35+
public static ArtistId fromBase62(@NotNull String base62) {
36+
return new ArtistId(Utils.bytesToHex(BASE62.decode(base62.getBytes())));
37+
}
38+
39+
@NotNull
40+
public static ArtistId fromHex(@NotNull String hex) {
41+
return new ArtistId(hex);
42+
}
43+
44+
@Override
45+
public @NotNull String toMercuryUri() {
46+
return "hm://metadata/4/artist/" + hexId;
47+
}
48+
49+
@Override
50+
public @NotNull String toSpotifyUri() {
51+
return "spotify:artist:" + new String(BASE62.encode(new BigInteger(hexId, 16).toByteArray()));
52+
}
53+
}

core/src/main/java/xyz/gianlu/librespot/mercury/model/PlaylistId.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package xyz.gianlu.librespot.mercury.model;
22

33
import org.jetbrains.annotations.NotNull;
4-
import xyz.gianlu.librespot.common.proto.Playlist4Content;
54

65
import java.util.regex.Matcher;
76
import java.util.regex.Pattern;
87

98
/**
109
* @author Gianlu
1110
*/
12-
public class PlaylistId implements SpotifyId {
11+
public final class PlaylistId implements SpotifyId {
1312
private static final Pattern PATTERN = Pattern.compile("spotify:user:(.*):playlist:(.{22})");
1413
public final String username;
1514
public final String playlistId;

core/src/main/java/xyz/gianlu/librespot/mercury/model/SpotifyId.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,50 @@
22

33
import org.jetbrains.annotations.NotNull;
44

5+
import java.lang.reflect.InvocationTargetException;
6+
import java.lang.reflect.Method;
7+
58
/**
69
* @author Gianlu
710
*/
811
public interface SpotifyId {
12+
String STATIC_FROM_URI = "fromUri";
13+
String STATIC_FROM_BASE62 = "fromBase62";
14+
String STATIC_FROM_HEX = "fromHex";
15+
16+
@NotNull
17+
static <I extends SpotifyId> I fromBase62(Class<I> clazz, String base62) throws SpotifyIdParsingException {
18+
return callReflection(clazz, STATIC_FROM_BASE62, base62);
19+
}
20+
21+
@NotNull
22+
static <I extends SpotifyId> I fromHex(Class<I> clazz, String hex) throws SpotifyIdParsingException {
23+
return callReflection(clazz, STATIC_FROM_HEX, hex);
24+
}
25+
26+
@NotNull
27+
static <I extends SpotifyId> I fromUri(Class<I> clazz, String uri) throws SpotifyIdParsingException {
28+
return callReflection(clazz, STATIC_FROM_URI, uri);
29+
}
30+
31+
@SuppressWarnings("unchecked")
32+
@NotNull
33+
static <I extends SpotifyId> I callReflection(@NotNull Class<I> clazz, @NotNull String name, @NotNull String arg) throws SpotifyIdParsingException {
34+
try {
35+
Method method = clazz.getDeclaredMethod(name, String.class);
36+
return (I) method.invoke(null, arg);
37+
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
38+
throw new SpotifyIdParsingException(ex);
39+
}
40+
}
41+
942
@NotNull String toMercuryUri();
1043

1144
@NotNull String toSpotifyUri();
45+
46+
class SpotifyIdParsingException extends Exception {
47+
SpotifyIdParsingException(Throwable cause) {
48+
super(cause);
49+
}
50+
}
1251
}

core/src/main/java/xyz/gianlu/librespot/mercury/model/TrackId.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
/**
1313
* @author Gianlu
1414
*/
15-
public class TrackId implements SpotifyId {
15+
public final class TrackId implements SpotifyId {
1616
private static final Pattern PATTERN = Pattern.compile("spotify:track:(.{22})");
1717
private static final Base62 BASE62 = Base62.createInstanceWithInvertedCharacterSet();
1818
private final String hexId;

0 commit comments

Comments
 (0)