Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancements in Track Handling and Artist Recognition for Yandex Music #134

Merged
merged 15 commits into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ LavaSrc adds the following fields to tracks & playlists in Lavalink
* `ymsearch:animals architects`
* https://music.yandex.ru/album/13886032/track/71663565
* https://music.yandex.ru/album/13886032
* https://music.yandex.ru/track/71663565
* https://music.yandex.ru/users/yamusic-bestsongs/playlists/701626
* https://music.yandex.ru/artist/701626

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,31 @@
import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface;
import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterfaceManager;
import com.sedmelluq.discord.lavaplayer.track.*;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class YandexMusicSourceManager implements AudioSourceManager, HttpConfigurable {
public static final Pattern URL_PATTERN = Pattern.compile("(https?://)?music\\.yandex\\.(ru|com)/(?<type1>artist|album)/(?<identifier>[0-9]+)/?((?<type2>track/)(?<identifier2>[0-9]+)/?)?");
public static final Pattern URL_PATTERN = Pattern.compile("(https?://)?music\\.yandex\\.(ru|com)/(?<type1>artist|album|track)/(?<identifier>[0-9]+)(/(?<type2>track)/(?<identifier2>[0-9]+))?/?");
public static final Pattern URL_PLAYLIST_PATTERN = Pattern.compile("(https?://)?music\\.yandex\\.(ru|com)/users/(?<identifier>[0-9A-Za-z@.-]+)/playlists/(?<identifier2>[0-9]+)/?");
public static final String SEARCH_PREFIX = "ymsearch:";
public static final String PUBLIC_API_BASE = "https://api.music.yandex.net";
Expand Down Expand Up @@ -72,6 +78,9 @@ public AudioItem loadItem(AudioPlayerManager manager, AudioReference reference)
case "artist":
var artistId = matcher.group("identifier");
return this.getArtist(artistId);
case "track":
var trackId = matcher.group("identifier");
return this.getTrack(trackId);
}
return null;
}
Expand All @@ -81,10 +90,10 @@ public AudioItem loadItem(AudioPlayerManager manager, AudioReference reference)
var playlistId = matcher.group("identifier2");
return this.getPlaylist(userId, playlistId);
}
} catch (IOException e) {
} catch (IOException | URISyntaxException e) {
BrokenFG marked this conversation as resolved.
Show resolved Hide resolved
throw new RuntimeException(e);
}
return null;
return null;
BrokenFG marked this conversation as resolved.
Show resolved Hide resolved
}

private AudioItem getSearch(String query) throws IOException {
Expand Down Expand Up @@ -146,14 +155,18 @@ private AudioItem getArtist(String id) throws IOException {
return new YandexMusicAudioPlaylist(author + "'s Top Tracks", tracks, ExtendedAudioPlaylist.Type.ARTIST, json.get("result").get("url").text(), this.formatCoverUri(coverUri), author);
}

private AudioItem getPlaylist(String userString, String id) throws IOException {
private AudioItem getPlaylist(String userString, String id) throws IOException, URISyntaxException {
var json = this.getJson(PUBLIC_API_BASE + "/users/" + userString + "/playlists/" + id);
if (json.isNull() || json.get("result").isNull() || json.get("result").get("tracks").values().isEmpty()) {
return AudioReference.NO_TRACK;
}
var tracks = new ArrayList<AudioTrack>();
for (var track : json.get("result").get("tracks").values()) {
var parsedTrack = this.parseTrack(track.get("track"));
var tracksToParse = json.get("result").get("tracks").values();
if (tracksToParse.get(0).get("track").isNull()) {
tracksToParse = getTracks(getTrackIds(tracksToParse));
}
for (var track : tracksToParse) {
var parsedTrack = track.get("track").isNull() ? this.parseTrack(track) : this.parseTrack(track.get("track"));
if (parsedTrack != null) {
tracks.add(parsedTrack);
}
Expand All @@ -167,13 +180,35 @@ private AudioItem getPlaylist(String userString, String id) throws IOException {
return new YandexMusicAudioPlaylist(playlistTitle, tracks, ExtendedAudioPlaylist.Type.PLAYLIST, json.get("result").get("url").text(), this.formatCoverUri(coverUri), author);
}

private List<JsonBrowser> getTracks(String trackIds) throws IOException {
return getJson(PUBLIC_API_BASE + "/tracks?track-ids=" + URLEncoder.encode(trackIds, StandardCharsets.UTF_8)).get("result").values();
}

private String getTrackIds(List<JsonBrowser> tracksToParse) {
return tracksToParse.stream()
.map(node -> node.get("id").text())
.collect(Collectors.joining(","));
}

public JsonBrowser getJson(String uri) throws IOException {
return getJson(URI.create(uri));
}

public JsonBrowser getJson(String uri, List<NameValuePair> params) throws URISyntaxException, IOException {
return getJson(buildUriWithParams(uri, params));
}

BrokenFG marked this conversation as resolved.
Show resolved Hide resolved
public JsonBrowser getJson(URI uri) throws IOException {
BrokenFG marked this conversation as resolved.
Show resolved Hide resolved
var request = new HttpGet(uri);
request.setHeader("Accept", "application/json");
request.setHeader("Authorization", "OAuth " + this.accessToken);
return LavaSrcTools.fetchResponseAsJson(this.httpInterfaceManager.getInterface(), request);
}

private URI buildUriWithParams(String uri, List<NameValuePair> params) throws URISyntaxException {
return new URIBuilder(uri).addParameters(params).build();
}

BrokenFG marked this conversation as resolved.
Show resolved Hide resolved
public String getDownloadStrings(String uri) throws IOException {
var request = new HttpGet(uri);
request.setHeader("Accept", "application/json");
Expand All @@ -193,27 +228,49 @@ private List<AudioTrack> parseTracks(JsonBrowser json) {
}

private AudioTrack parseTrack(JsonBrowser json) {
if (!json.get("available").asBoolean(false) || json.get("albums").values().isEmpty()) {
topi314 marked this conversation as resolved.
Show resolved Hide resolved
if (!json.get("available").asBoolean(false)) {
return null;
}
var id = json.get("id").text();
var artist = json.get("major").get("name").text().equals("PODCASTS") ? json.get("albums").values().get(0).get("title").text() : json.get("artists").values().get(0).get("name").text();
var coverUri = json.get("albums").values().get(0).get("coverUri").text();
var artist = parseArtist(json);
var coverUri = json.get("coverUri").text();
return new YandexMusicAudioTrack(
new AudioTrackInfo(
json.get("title").text(),
artist,
json.get("durationMs").as(Long.class),
id,
false,
"https://music.yandex.ru/album/" + json.get("albums").values().get(0).get("id").text() + "/track/" + id,
"https://music.yandex.ru/track/" + id,
this.formatCoverUri(coverUri),
null
),
this
);
}

private String parseArtist(JsonBrowser json) {
if (!json.get("major").isNull() && json.get("major").get("name").text().equals("PODCASTS")) {
return json.get("albums").values().get(0).get("title").text();
}

if (!json.get("artists").values().isEmpty()) {
return extractArtists(json.get("artists"));
}

if (!json.get("matchedTrack").isNull()) {
return extractArtists(json.get("matchedTrack").get("artists"));
}

return "Unknown";
}

private String extractArtists(JsonBrowser artistNode) {
return artistNode.values().stream()
.map(node -> node.get("name").text())
.collect(Collectors.joining(", "));
}

private String formatCoverUri(String coverUri) {
return coverUri != null ? "https://" + coverUri.replace("%%", "400x400") : null;
}
Expand Down Expand Up @@ -254,4 +311,4 @@ public void shutdown() {
public HttpInterface getHttpInterface() {
return this.httpInterfaceManager.getInterface();
}
}
}