Skip to content

Commit

Permalink
Implement Yandex Music LavaSearch and few fixes (#200)
Browse files Browse the repository at this point in the history
  • Loading branch information
Krispeckt authored Jun 9, 2024
1 parent cbc40b9 commit 6af620a
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 27 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A collection of additional [Lavaplayer v2](https://github.com/sedmelluq/lavaplay
* [Spotify](https://www.spotify.com) playlists/albums/songs/artists(top tracks)/search results/[LavaSearch](https://github.com/topi314/LavaSearch)/[LavaLyrics](https://github.com/topi314/LavaLyrics)
* [Apple Music](https://www.apple.com/apple-music/) playlists/albums/songs/artists/search results/[LavaSearch](https://github.com/topi314/LavaSearch)(Big thx to [ryan5453](https://github.com/ryan5453) for helping me)
* [Deezer](https://www.deezer.com) playlists/albums/songs/artists/search results/[LavaSearch](https://github.com/topi314/LavaSearch)/[LavaLyrics](https://github.com/topi314/LavaLyrics)(Big thx to [ryan5453](https://github.com/ryan5453) and [melike2d](https://github.com/melike2d) for helping me)
* [Yandex Music](https://music.yandex.ru) playlists/albums/songs/artists/podcasts/search results/[LavaLyrics](https://github.com/topi314/LavaLyrics)(Thx to [AgutinVBoy](https://github.com/agutinvboy) for implementing it)
* [Yandex Music](https://music.yandex.ru) playlists/albums/songs/artists/podcasts/search results/[LavaLyrics](https://github.com/topi314/LavaLyrics)/[LavaSearch](https://github.com/topi314/LavaSearch)(Thx to [AgutinVBoy](https://github.com/agutinvboy) for implementing it)
* [Flowery TTS](https://flowery.pw/docs/flowery/synthesize-v-1-tts-get) (Thx to [bachtran02](https://github.com/bachtran02) for implementing it)
* [YouTube](https://youtube.com) & [YouTubeMusic](https://music.youtube.com/) [LavaSearch](https://github.com/topi314/LavaSearch)/[LavaLyrics](https://github.com/topi314/LavaLyrics) (Thx to [DRSchlaubi](https://github.com/DRSchlaubi) for helping me)

Expand Down Expand Up @@ -235,6 +235,7 @@ plugins:
spotify: false # Enable Spotify lyrics source
deezer: false # Enable Deezer lyrics source
youtube: false # Enable YouTube lyrics source
yandexmusic: false # Enable Yandex Music lyrics source
spotify:
clientId: "your client id"
clientSecret: "your client secret"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,6 @@ private AudioTrack parseTrack(JsonBrowser json, boolean preview) {
}

private AudioSearchResult getAutocomplete(String query, Set<AudioSearchResult.Type> types) throws IOException {
if (types.contains(AudioSearchResult.Type.TEXT)) {
throw new IllegalArgumentException("text is not a valid search type for Deezer");
}
if (types.isEmpty()) {
types = SEARCH_TYPES;
}
Expand Down Expand Up @@ -360,7 +357,10 @@ private AudioSearchResult getAutocomplete(String query, Set<AudioSearchResult.Ty
}
}

var tracks = this.parseTracks(json.get("tracks"), false);
var tracks = new ArrayList<AudioTrack>();
if (types.contains(AudioSearchResult.Type.TRACK)) {
tracks.addAll(this.parseTracks(json.get("tracks"), false));
}

return new BasicAudioSearchResult(tracks, albums, artists, playlists, new ArrayList<>());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,6 @@ public JsonBrowser getJson(String uri) throws IOException {
}

private AudioSearchResult getAutocomplete(String query, Set<AudioSearchResult.Type> types) throws IOException {
if (types.contains(AudioSearchResult.Type.TEXT)) {
throw new IllegalArgumentException("text is not a valid search type for Spotify");
}
if (types.isEmpty()) {
types = SEARCH_TYPES;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import com.github.topi314.lavalyrics.AudioLyricsManager;
import com.github.topi314.lavalyrics.lyrics.AudioLyrics;
import com.github.topi314.lavalyrics.lyrics.BasicAudioLyrics;
import com.github.topi314.lavasearch.AudioSearchManager;
import com.github.topi314.lavasearch.result.AudioSearchResult;
import com.github.topi314.lavasearch.result.BasicAudioSearchResult;
import com.github.topi314.lavasrc.ExtendedAudioPlaylist;
import com.github.topi314.lavasrc.ExtendedAudioSourceManager;
import com.github.topi314.lavasrc.LavaSrcTools;
Expand All @@ -27,13 +30,15 @@
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class YandexMusicSourceManager extends ExtendedAudioSourceManager implements HttpConfigurable, AudioLyricsManager {
public class YandexMusicSourceManager extends ExtendedAudioSourceManager implements HttpConfigurable, AudioLyricsManager, AudioSearchManager {
public static final Pattern URL_PATTERN = Pattern.compile("(https?://)?music\\.yandex\\.(?<domain>ru|com|kz|by)/(?<type1>artist|album|track)/(?<identifier>[0-9]+)(/(?<type2>track)/(?<identifier2>[0-9]+))?/?");
public static final Pattern URL_PLAYLIST_PATTERN = Pattern.compile("(https?://)?music\\.yandex\\.(?<domain>ru|com|kz|by)/users/(?<identifier>[0-9A-Za-z@.-]+)/playlists/(?<identifier2>[0-9]+)/?");
public static final Pattern EXTRACT_LYRICS_STROKE = Pattern.compile("\\[(?<min>\\d{2}):(?<sec>\\d{2})\\.(?<mil>\\d{2})] ?(?<text>.+)?");
Expand All @@ -43,6 +48,7 @@ public class YandexMusicSourceManager extends ExtendedAudioSourceManager impleme
public static final int ARTIST_MAX_PAGE_ITEMS = 10;
public static final int PLAYLIST_MAX_PAGE_ITEMS = 100;
public static final int ALBUM_MAX_PAGE_ITEMS = 50;
public static final Set<AudioSearchResult.Type> SEARCH_TYPES = Set.of(AudioSearchResult.Type.TRACK, AudioSearchResult.Type.ALBUM, AudioSearchResult.Type.PLAYLIST, AudioSearchResult.Type.ARTIST);

private static final Logger log = LoggerFactory.getLogger(YandexMusicSourceManager.class);

Expand Down Expand Up @@ -79,6 +85,114 @@ public String getSourceName() {
return "yandexmusic";
}

private AudioSearchResult getSearchResult(String query, Set<AudioSearchResult.Type> setOfTypes) throws IOException {
var json = this.getJson(
PUBLIC_API_BASE + "/search"
+ "?text=" + URLEncoder.encode(query, StandardCharsets.UTF_8)
+ "&type=all"
+ "&page=0"
);
if (setOfTypes.isEmpty()) {
setOfTypes = SEARCH_TYPES;
}

var resultJson = json.get("result");
if (json.isNull() || resultJson.isNull()) {
return AudioSearchResult.EMPTY;
}

var albums = new ArrayList<AudioPlaylist>();
var artists = new ArrayList<AudioPlaylist>();
var playlists = new ArrayList<AudioPlaylist>();
var tracks = new ArrayList<AudioTrack>();

if (setOfTypes.contains(AudioSearchResult.Type.ALBUM)) {
for (var albumsJson : resultJson.get("albums").get("results").values()) {
if (!albumsJson.get("available").asBoolean(false)) {
continue;
}

albums.add(new YandexMusicAudioPlaylist(
albumsJson.get("title").text(),
tracks,
ExtendedAudioPlaylist.Type.ALBUM,
"https://music.yandex.com/album/" + albumsJson.get("id").text(),
this.parseCoverUri(albumsJson),
this.parseArtist(albumsJson),
(int) albumsJson.get("trackCount").asLong(0)
));
}
}

if (setOfTypes.contains(AudioSearchResult.Type.ARTIST)) {
for (var artistJson : resultJson.get("artists").get("results").values()) {
if (!artistJson.get("available").asBoolean(false)) {
continue;
}

var authorName = artistJson.get("name").text();
artists.add(new YandexMusicAudioPlaylist(
authorName + "'s Top Tracks",
Collections.emptyList(),
YandexMusicAudioPlaylist.Type.ARTIST,
"https://music.yandex.com/artist/" + artistJson.get("id").text(),
this.parseCoverUri(artistJson),
authorName,
artistJson.get("counts").isNull() ? null : ((int) artistJson.get("counts").get("tracks").asLong(0))
));
}
}

if (setOfTypes.contains(AudioSearchResult.Type.PLAYLIST) && !resultJson.get("playlists").get("results").isNull()) {
for (var playlistJson : resultJson.get("playlists").get("results").values()) {
if (!playlistJson.get("available").asBoolean(false)) {
continue;
}

var name = "";
if (!playlistJson.get("owner").isNull()) {
if (!playlistJson.get("owner").get("name").isNull()) {
name = playlistJson.get("owner").get("name").text();
} else {
name = playlistJson.get("owner").get("login").text();
}
}

playlists.add(new YandexMusicAudioPlaylist(
name,
Collections.emptyList(),
YandexMusicAudioPlaylist.Type.PLAYLIST,
"https://music.yandex.com/users/" + playlistJson.get("owner").get("login").text() + "/playlists/" + playlistJson.get("kind").text(),
this.parseCoverUri(playlistJson),
playlistJson.get("owner").get("name").text(),
(int) playlistJson.get("trackCount").asLong(0)
));
}
}

if (setOfTypes.contains(AudioSearchResult.Type.TRACK) && !resultJson.get("tracks").get("results").isNull()) {
tracks.addAll(this.parseTracks(resultJson.get("tracks").get("results"), "com"));
}

return new BasicAudioSearchResult(tracks, albums, artists, playlists, new ArrayList<>());
}

@Override
public @Nullable AudioSearchResult loadSearch(@NotNull String query, @NotNull Set<AudioSearchResult.Type> setOfTypes) {
if (accessToken == null || accessToken.isEmpty()) {
throw new IllegalArgumentException("Yandex Music accessToken must be set");
}

try {
if (query.startsWith(SEARCH_PREFIX)) {
return this.getSearchResult(query.substring(SEARCH_PREFIX.length()), setOfTypes);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return null;
}

private JsonBrowser findLyrics(String identifier) throws IOException {
var sign = YandexMusicSign.create(identifier);
return this.getJson(
Expand All @@ -95,25 +209,9 @@ private JsonBrowser findLyrics(String identifier) throws IOException {
throw new IllegalArgumentException("Yandex Music accessToken must be set");
}

String yandexIdentifier = null;
if (track.getSourceManager() instanceof YandexMusicSourceManager) {
yandexIdentifier = track.getIdentifier();
} else {
try {
AudioItem item = this.getSearch(track.getInfo().title + " " + track.getInfo().author);
if (item != AudioReference.NO_TRACK) {
var playlist = (BasicAudioPlaylist) item;
if (!playlist.getTracks().isEmpty()) {
yandexIdentifier = playlist.getTracks().get(0).getIdentifier();
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (yandexIdentifier != null) {
try {
var lyricsJson = findLyrics(yandexIdentifier);
var lyricsJson = findLyrics(track.getIdentifier());
if (lyricsJson != null && !lyricsJson.isNull() && !lyricsJson.get("result").isNull()) {
return this.parseLyrics(
lyricsJson.get("result").get("downloadUrl").text(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public LavaSrcPlugin(LavaSrcConfig pluginConfig, SourcesConfig sourcesConfig, Ly
if (sourcesConfig.isDeezer() || lyricsSourcesConfig.isDeezer()) {
this.deezer = new DeezerAudioSourceManager(deezerConfig.getMasterDecryptionKey());
}
if (sourcesConfig.isYandexMusic()) {
if (sourcesConfig.isYandexMusic() || lyricsSourcesConfig.isYandexMusic()) {
this.yandexMusic = new YandexMusicSourceManager(yandexMusicConfig.getAccessToken());
if (yandexMusicConfig.getPlaylistLoadLimit() > 0) {
yandexMusic.setPlaylistLoadLimit(yandexMusicConfig.getPlaylistLoadLimit());
Expand Down Expand Up @@ -151,6 +151,10 @@ public SearchManager configure(@NotNull SearchManager manager) {
log.info("Registering Youtube search manager...");
manager.registerSearchManager(this.youtube);
}
if (this.yandexMusic != null && this.sourcesConfig.isYandexMusic()) {
log.info("Registering Yandex Music search manager...");
manager.registerSearchManager(this.yandexMusic);
}
return manager;
}

Expand Down

0 comments on commit 6af620a

Please sign in to comment.