From 6af620ace35f37ef938cc84e5f954d642373627f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D1=80=D0=B8=D1=81=D1=82=D0=B8=D0=BA?= <105949686+Krispeckt@users.noreply.github.com> Date: Mon, 10 Jun 2024 00:21:40 +0300 Subject: [PATCH] Implement Yandex Music LavaSearch and few fixes (#200) --- README.md | 3 +- .../deezer/DeezerAudioSourceManager.java | 8 +- .../lavasrc/spotify/SpotifySourceManager.java | 3 - .../yandexmusic/YandexMusicSourceManager.java | 134 +++++++++++++++--- .../topi314/lavasrc/plugin/LavaSrcPlugin.java | 6 +- 5 files changed, 127 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 1077529c..80c5406b 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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" diff --git a/main/src/main/java/com/github/topi314/lavasrc/deezer/DeezerAudioSourceManager.java b/main/src/main/java/com/github/topi314/lavasrc/deezer/DeezerAudioSourceManager.java index 01c05596..8696972b 100644 --- a/main/src/main/java/com/github/topi314/lavasrc/deezer/DeezerAudioSourceManager.java +++ b/main/src/main/java/com/github/topi314/lavasrc/deezer/DeezerAudioSourceManager.java @@ -304,9 +304,6 @@ private AudioTrack parseTrack(JsonBrowser json, boolean preview) { } private AudioSearchResult getAutocomplete(String query, Set 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; } @@ -360,7 +357,10 @@ private AudioSearchResult getAutocomplete(String query, Set(); + if (types.contains(AudioSearchResult.Type.TRACK)) { + tracks.addAll(this.parseTracks(json.get("tracks"), false)); + } return new BasicAudioSearchResult(tracks, albums, artists, playlists, new ArrayList<>()); } diff --git a/main/src/main/java/com/github/topi314/lavasrc/spotify/SpotifySourceManager.java b/main/src/main/java/com/github/topi314/lavasrc/spotify/SpotifySourceManager.java index 0fa84a8e..fdd0ca19 100644 --- a/main/src/main/java/com/github/topi314/lavasrc/spotify/SpotifySourceManager.java +++ b/main/src/main/java/com/github/topi314/lavasrc/spotify/SpotifySourceManager.java @@ -279,9 +279,6 @@ public JsonBrowser getJson(String uri) throws IOException { } private AudioSearchResult getAutocomplete(String query, Set 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; } diff --git a/main/src/main/java/com/github/topi314/lavasrc/yandexmusic/YandexMusicSourceManager.java b/main/src/main/java/com/github/topi314/lavasrc/yandexmusic/YandexMusicSourceManager.java index ec62f70d..16ac1cc0 100644 --- a/main/src/main/java/com/github/topi314/lavasrc/yandexmusic/YandexMusicSourceManager.java +++ b/main/src/main/java/com/github/topi314/lavasrc/yandexmusic/YandexMusicSourceManager.java @@ -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; @@ -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\\.(?ru|com|kz|by)/(?artist|album|track)/(?[0-9]+)(/(?track)/(?[0-9]+))?/?"); public static final Pattern URL_PLAYLIST_PATTERN = Pattern.compile("(https?://)?music\\.yandex\\.(?ru|com|kz|by)/users/(?[0-9A-Za-z@.-]+)/playlists/(?[0-9]+)/?"); public static final Pattern EXTRACT_LYRICS_STROKE = Pattern.compile("\\[(?\\d{2}):(?\\d{2})\\.(?\\d{2})] ?(?.+)?"); @@ -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 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); @@ -79,6 +85,114 @@ public String getSourceName() { return "yandexmusic"; } + private AudioSearchResult getSearchResult(String query, Set 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(); + var artists = new ArrayList(); + var playlists = new ArrayList(); + var tracks = new ArrayList(); + + 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 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( @@ -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(), diff --git a/plugin/src/main/java/com/github/topi314/lavasrc/plugin/LavaSrcPlugin.java b/plugin/src/main/java/com/github/topi314/lavasrc/plugin/LavaSrcPlugin.java index 5a343760..04c88809 100644 --- a/plugin/src/main/java/com/github/topi314/lavasrc/plugin/LavaSrcPlugin.java +++ b/plugin/src/main/java/com/github/topi314/lavasrc/plugin/LavaSrcPlugin.java @@ -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()); @@ -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; }