diff --git a/README.md b/README.md index 66986b6c..b7ead2ca 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ | YouTube([yt-dlp](https://github.com/yt-dlp/yt-dlp)) | πŸ“πŸ’ΏπŸŽ΅πŸ§‘πŸ” | Direct | [@topi314](https://github.com/topi314) | | [LRCLIB](https://lrclib.net)) | πŸ“œ | N/A | [@topi314](https://github.com/topi314) | | JioSaavn | πŸ“πŸ’ΏπŸŽ΅πŸ§‘πŸ”πŸ“»πŸ”¬ | Direct | [@WeeeeeeeeeeS](https://github.com/WeeeeeeeeeeS), [@freyacodes](https://github.com/freyacodes) | +| [Musixmatch](https://www.musixmatch.com) | πŸ“œ | N/A | [@southctrl](https://github.com/southctrl) | ### Features @@ -124,6 +125,7 @@ plugins: yandexmusic: false # Enable Yandex Music lyrics source vkmusic: false # Enable Vk Music lyrics source lrcLib: false # Enable LRC Library lyrics source (https://lrclib.net) + musixmatch: false # Enable Musixmatch lyrics source (https://musixmatch.com) spotify: # clientId & clientSecret are required for using spsearch # clientId: "your client id" diff --git a/main/src/main/java/com/github/topi314/lavasrc/musixmatch/MusixmatchLyricsManager.java b/main/src/main/java/com/github/topi314/lavasrc/musixmatch/MusixmatchLyricsManager.java new file mode 100644 index 00000000..681c46e8 --- /dev/null +++ b/main/src/main/java/com/github/topi314/lavasrc/musixmatch/MusixmatchLyricsManager.java @@ -0,0 +1,359 @@ +package com.github.topi314.lavasrc.musixmatch; + +import com.github.topi314.lavalyrics.AudioLyricsManager; +import com.github.topi314.lavalyrics.lyrics.AudioLyrics; +import com.github.topi314.lavalyrics.lyrics.BasicAudioLyrics; +import com.github.topi314.lavasrc.LavaSrcTools; +import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser; +import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools; +import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterfaceManager; +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.utils.URIBuilder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class MusixmatchLyricsManager implements AudioLyricsManager { + + private static final long CACHE_TTL = 300000; + private static final int MAX_CACHE_ENTRIES = 100; + private static final int REQUEST_TIMEOUT_MS = 8000; + + private static final String SEARCH_ENDPOINT = "https://apic-desktop.musixmatch.com/ws/1.1/track.search"; + private static final String LYRICS_ENDPOINT = "https://apic-desktop.musixmatch.com/ws/1.1/track.subtitle.get"; + private static final String ALT_LYRICS_ENDPOINT = "https://apic-desktop.musixmatch.com/ws/1.1/macro.subtitles.get"; + + private static final Pattern TIMESTAMP_REGEX = Pattern.compile("\\[\\d{1,2}:\\d{2}(?:\\.\\d{1,3})?\\]"); + private static final Pattern BRACKET_JUNK = Pattern.compile("\\s*\\[([^\\]]*(?:official|lyrics?|video|audio|mv|visualizer|color\\s*coded|hd|4k)[^\\]]*)\\]", Pattern.CASE_INSENSITIVE); + private static final String[] SEPARATORS = {" - ", " – ", " β€” ", " ~ ", "-"}; + + private final HttpInterfaceManager httpInterfaceManager; + private final Map cache; + private final RequestConfig requestConfig; + private final MusixmatchTokenTracker tokenTracker; + + public MusixmatchLyricsManager() { + this.httpInterfaceManager = HttpClientTools.createCookielessThreadLocalManager(); + this.cache = new ConcurrentHashMap<>(); + this.requestConfig = RequestConfig.custom() + .setConnectTimeout(REQUEST_TIMEOUT_MS) + .setSocketTimeout(REQUEST_TIMEOUT_MS) + .setConnectionRequestTimeout(REQUEST_TIMEOUT_MS) + .build(); + this.tokenTracker = new MusixmatchTokenTracker(); + } + + @NotNull + @Override + public String getSourceName() { + return "Musixmatch"; + } + + @Override + public @Nullable AudioLyrics loadLyrics(@NotNull AudioTrack audioTrack) { + String query = audioTrack.getInfo().author + " - " + audioTrack.getInfo().title; + try { + return findLyrics(query); + } catch (Exception e) { + return null; + } + } + + private AudioLyrics findLyrics(String query) throws IOException { + ParsedQuery parsed = parseQuery(query); + String key = cacheKey(parsed.artist, parsed.title); + + CacheEntry cached = cache.get(key); + if (cached != null && cached.expires > System.currentTimeMillis()) { + return cached.value; + } + + AudioLyrics result = null; + + try { + if (parsed.artist != null && !parsed.artist.isEmpty()) { + result = tryMacroEndpoint(parsed.artist, parsed.title); + if (result == null) { + result = trySearchAndLyrics(parsed.artist, parsed.title); + } + } else { + result = trySearchAndLyrics(null, parsed.title); + } + + if (result == null) { + result = tryMacroEndpoint(null, parsed.title); + } + } catch (Exception e) { + result = null; + } + + setCached(key, result); + return result; + } + + private AudioLyrics tryMacroEndpoint(String artist, String title) throws IOException { + try { + URIBuilder builder = new URIBuilder(ALT_LYRICS_ENDPOINT) + .addParameter("format", "json") + .addParameter("namespace", "lyrics_richsynched") + .addParameter("subtitle_format", "mxm") + .addParameter("q_track", title); + + if (artist != null && !artist.isEmpty()) { + builder.addParameter("q_artist", artist); + } + + JsonBrowser body = callMxm(builder.build()); + JsonBrowser macroCalls = body.get("macro_calls"); + + if (macroCalls.isNull()) { + return null; + } + + JsonBrowser lyricsData = macroCalls.get("track.lyrics.get").get("message").get("body").get("lyrics"); + JsonBrowser trackData = macroCalls.get("matcher.track.get").get("message").get("body").get("track"); + JsonBrowser subtitlesData = macroCalls.get("track.subtitles.get").get("message").get("body").get("subtitle_list"); + + String lyrics = lyricsData.get("lyrics_body").text(); + String subtitles = null; + + if (!subtitlesData.isNull() && !subtitlesData.values().isEmpty()) { + subtitles = subtitlesData.index(0).get("subtitle").get("subtitle_body").text(); + } + + if (lyrics != null || subtitles != null) { + return formatResult(subtitles, lyrics, trackData); + } + } catch (Exception e) { + return null; + } + return null; + } + + private AudioLyrics trySearchAndLyrics(String artist, String title) throws IOException { + try { + URIBuilder builder = new URIBuilder(SEARCH_ENDPOINT) + .addParameter("page_size", "1") + .addParameter("page", "1") + .addParameter("s_track_rating", "desc") + .addParameter("q_track", title); + + if (artist != null && !artist.isEmpty()) { + builder.addParameter("q_artist", artist); + } + + JsonBrowser searchBody = callMxm(builder.build()); + JsonBrowser trackList = searchBody.get("track_list"); + + if (trackList.isNull() || trackList.values().isEmpty()) { + return null; + } + + JsonBrowser track = trackList.index(0).get("track"); + String trackId = track.get("track_id").text(); + + if (trackId == null) { + return null; + } + + URIBuilder lyricsBuilder = new URIBuilder(LYRICS_ENDPOINT) + .addParameter("subtitle_format", "mxm") + .addParameter("track_id", trackId); + + JsonBrowser lyricsBody = callMxm(lyricsBuilder.build()); + String subtitles = lyricsBody.get("subtitle").get("subtitle_body").text(); + + if (subtitles != null) { + return formatResult(subtitles, null, track); + } + } catch (Exception e) { + return null; + } + return null; + } + + private AudioLyrics formatResult(String subtitles, String lyrics, JsonBrowser track) { + List lines = subtitles != null ? parseSubtitles(subtitles) : null; + String text = lyrics != null ? cleanLyrics(lyrics) : (lines != null ? linesToText(lines) : null); + + String trackName = track.get("track_name").text(); + String artistName = track.get("artist_name").text(); + String albumArt = track.get("album_coverart_800x800").text(); + + if (albumArt == null) { + albumArt = track.get("album_coverart_350x350").text(); + } + if (albumArt == null) { + albumArt = track.get("album_coverart_100x100").text(); + } + + return new BasicAudioLyrics("Musixmatch", "Musixmatch", text, lines != null ? lines : new ArrayList<>()); + } + + private List parseSubtitles(String subtitleBody) { + try { + JsonBrowser parsed = JsonBrowser.parse(subtitleBody); + List arr = parsed.isList() ? parsed.values() : + (parsed.get("subtitle").isList() ? parsed.get("subtitle").values() : null); + + if (arr == null || arr.isEmpty()) { + return null; + } + + List lines = new ArrayList<>(); + for (JsonBrowser item : arr) { + JsonBrowser timeTotal = item.get("time").get("total"); + double total = timeTotal.isNull() ? 0.0 : Double.parseDouble(timeTotal.text()); + String text = item.get("text").text(); + if (text != null) { + lines.add(new BasicAudioLyrics.BasicLine( + Duration.ofMillis(Math.round(total * 1000)), + null, + text + )); + } + } + return lines; + } catch (Exception e) { + return null; + } + } + + private String cleanLyrics(String lyrics) { + String cleaned = TIMESTAMP_REGEX.matcher(lyrics).replaceAll(""); + String[] lines = cleaned.split("\n"); + StringBuilder result = new StringBuilder(); + for (String line : lines) { + String trimmed = line.trim(); + if (!trimmed.isEmpty()) { + if (result.length() > 0) { + result.append("\n"); + } + result.append(trimmed); + } + } + return result.toString(); + } + + private String linesToText(List lines) { + StringBuilder sb = new StringBuilder(); + for (AudioLyrics.Line line : lines) { + if (sb.length() > 0) { + sb.append("\n"); + } + sb.append(line.getLine()); + } + return sb.toString(); + } + + private ParsedQuery parseQuery(String query) { + String cleaned = BRACKET_JUNK.matcher(query).replaceAll("").trim(); + for (String separator : SEPARATORS) { + int index = cleaned.indexOf(separator); + if (index > 0 && index < cleaned.length() - separator.length()) { + String artist = cleaned.substring(0, index).trim(); + String title = cleaned.substring(index + separator.length()).trim(); + if (!artist.isEmpty() && !title.isEmpty()) { + return new ParsedQuery(artist, title); + } + } + } + return new ParsedQuery(null, cleaned); + } + + private String cacheKey(String artist, String title) { + String normalizedArtist = artist != null ? artist.toLowerCase().trim() : ""; + String normalizedTitle = title.toLowerCase().trim(); + return normalizedArtist + "|" + normalizedTitle; + } + + private void setCached(String key, AudioLyrics value) { + if (cache.size() >= MAX_CACHE_ENTRIES) { + String firstKey = cache.keySet().iterator().next(); + cache.remove(firstKey); + } + cache.put(key, new CacheEntry(value, System.currentTimeMillis() + CACHE_TTL)); + } + + private JsonBrowser callMxm(URI uri) throws IOException { + String token = tokenTracker.getUserToken(); + String appId = tokenTracker.getAppId(); + + try { + URIBuilder builder = new URIBuilder(uri) + .addParameter("app_id", appId) + .addParameter("usertoken", token); + return apiGet(builder.build()); + } catch (URISyntaxException e) { + throw new IOException(e); + } catch (IOException e) { + tokenTracker.invalidateToken(); + throw e; + } + } + + private JsonBrowser apiGet(URI uri) throws IOException { + HttpGet request = new HttpGet(uri); + request.setConfig(requestConfig); + request.setHeader("Accept", "application/json"); + request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); + + try (var httpInterface = httpInterfaceManager.getInterface()) { + JsonBrowser response = LavaSrcTools.fetchResponseAsJson(httpInterface, request); + JsonBrowser header = response.get("message").get("header"); + JsonBrowser statusCodeBrowser = header.get("status_code"); + int statusCode = statusCodeBrowser.isNull() ? 0 : Integer.parseInt(statusCodeBrowser.text()); + + if (statusCode == 401 || statusCode == 403) { + tokenTracker.invalidateToken(); + throw new IOException("Musixmatch API authentication error: " + statusCode); + } + + if (statusCode != 200) { + throw new IOException("Musixmatch API error: " + statusCode); + } + + return response.get("message").get("body"); + } + } + + @Override + public void shutdown() { + try { + httpInterfaceManager.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static class CacheEntry { + AudioLyrics value; + long expires; + + CacheEntry(AudioLyrics value, long expires) { + this.value = value; + this.expires = expires; + } + } + + private static class ParsedQuery { + String artist; + String title; + + ParsedQuery(String artist, String title) { + this.artist = artist; + this.title = title; + } + } +} diff --git a/main/src/main/java/com/github/topi314/lavasrc/musixmatch/MusixmatchTokenTracker.java b/main/src/main/java/com/github/topi314/lavasrc/musixmatch/MusixmatchTokenTracker.java new file mode 100644 index 00000000..393cc58c --- /dev/null +++ b/main/src/main/java/com/github/topi314/lavasrc/musixmatch/MusixmatchTokenTracker.java @@ -0,0 +1,212 @@ +package com.github.topi314.lavasrc.musixmatch; + +import com.github.topi314.lavasrc.LavaSrcTools; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class MusixmatchTokenTracker { + private static final Logger log = LoggerFactory.getLogger(MusixmatchTokenTracker.class); + + private static final Pattern TOKEN_PATTERN = Pattern.compile("usertoken[\"']?\\s*[:=]\\s*[\"']([a-f0-9]{40,})[\"']", Pattern.CASE_INSENSITIVE); + private static final Pattern APP_ID_PATTERN = Pattern.compile("app_id[\"']?\\s*[:=]\\s*[\"']([^\"']+)[\"']", Pattern.CASE_INSENSITIVE); + private static final Pattern CONFIG_TOKEN_PATTERN = Pattern.compile("token[\"']?\\s*[:=]\\s*[\"']([a-f0-9]{40,})[\"']", Pattern.CASE_INSENSITIVE); + private static final Pattern SECRET_PATTERN = Pattern.compile("\"secret\":\\[(\\d+(?:,\\d+)+)]"); + + private static final String MUSIXMATCH_HOME = "https://www.musixmatch.com/"; + private static final String TOKEN_ENDPOINT = "https://apic-desktop.musixmatch.com/ws/1.1/token.get"; + private static final String APP_ID = "web-desktop-app-v1.0"; + private static final long TOKEN_TTL = 55000; + private static final int REQUEST_TIMEOUT_MS = 8000; + + private final Object tokenLock = new Object(); + private final RequestConfig requestConfig; + + private String userToken; + private Instant tokenExpires; + private String extractedAppId; + + public MusixmatchTokenTracker() { + this.requestConfig = RequestConfig.custom() + .setConnectTimeout(REQUEST_TIMEOUT_MS) + .setSocketTimeout(REQUEST_TIMEOUT_MS) + .setConnectionRequestTimeout(REQUEST_TIMEOUT_MS) + .build(); + } + + public String getUserToken() throws IOException { + if (this.userToken == null || this.tokenExpires == null || this.tokenExpires.isBefore(Instant.now())) { + synchronized (tokenLock) { + if (this.userToken == null || this.tokenExpires == null || this.tokenExpires.isBefore(Instant.now())) { + log.debug("User token is invalid or expired, refreshing token..."); + this.refreshUserToken(); + } + } + } + return this.userToken; + } + + private void refreshUserToken() throws IOException { + String token = null; + + try { + token = extractTokenFromWebsite(); + } catch (Exception e) { + log.debug("Failed to extract token from website, falling back to API", e); + } + + if (token == null) { + token = fetchTokenFromAPI(); + } + + this.userToken = token; + this.tokenExpires = Instant.now().plusMillis(TOKEN_TTL); + } + + private String extractTokenFromWebsite() throws IOException { + log.debug("Attempting to extract token from Musixmatch website"); + + try (CloseableHttpClient client = HttpClients.createDefault()) { + HttpGet request = new HttpGet(MUSIXMATCH_HOME); + request.setConfig(requestConfig); + + try (CloseableHttpResponse response = client.execute(request)) { + String html = EntityUtils.toString(response.getEntity()); + Document doc = Jsoup.parse(html); + Elements scriptElements = doc.select("script[src]"); + + List scriptUrls = new ArrayList<>(); + for (Element script : scriptElements) { + String scriptUrl = script.attr("src"); + if (scriptUrl.contains("main") || scriptUrl.contains("app") || scriptUrl.contains("bundle")) { + if (!scriptUrl.startsWith("http")) { + scriptUrl = MUSIXMATCH_HOME + scriptUrl.replaceFirst("^/", ""); + } + scriptUrls.add(scriptUrl); + log.debug("Found script URL: {}", scriptUrl); + } + } + + Elements inlineScripts = doc.select("script:not([src])"); + for (Element inlineScript : inlineScripts) { + String scriptContent = inlineScript.html(); + String token = extractTokenFromScript(scriptContent); + if (token != null) { + log.debug("Extracted token from inline script"); + return token; + } + } + + for (String scriptUrl : scriptUrls) { + String token = extractTokenFromScriptUrl(client, scriptUrl); + if (token != null) { + log.debug("Extracted token from script: {}", scriptUrl); + return token; + } + } + } + } + + log.debug("No token found in website scripts"); + return null; + } + + private String extractTokenFromScriptUrl(CloseableHttpClient client, String scriptUrl) throws IOException { + HttpGet scriptRequest = new HttpGet(scriptUrl); + scriptRequest.setConfig(requestConfig); + + try (CloseableHttpResponse scriptResponse = client.execute(scriptRequest)) { + String scriptContent = EntityUtils.toString(scriptResponse.getEntity()); + return extractTokenFromScript(scriptContent); + } + } + + private String extractTokenFromScript(String scriptContent) { + Matcher tokenMatcher = TOKEN_PATTERN.matcher(scriptContent); + if (tokenMatcher.find()) { + return tokenMatcher.group(1); + } + + Matcher configMatcher = CONFIG_TOKEN_PATTERN.matcher(scriptContent); + if (configMatcher.find()) { + return configMatcher.group(1); + } + + Matcher appIdMatcher = APP_ID_PATTERN.matcher(scriptContent); + if (appIdMatcher.find()) { + this.extractedAppId = appIdMatcher.group(1); + log.debug("Extracted app_id: {}", this.extractedAppId); + } + + return null; + } + + private String fetchTokenFromAPI() throws IOException { + log.debug("Fetching token from Musixmatch API"); + + try { + String appId = this.extractedAppId != null ? this.extractedAppId : APP_ID; + + URIBuilder builder = new URIBuilder(TOKEN_ENDPOINT) + .addParameter("app_id", appId); + + try (CloseableHttpClient client = HttpClients.createDefault()) { + HttpGet request = new HttpGet(builder.build()); + request.setConfig(requestConfig); + request.setHeader("Accept", "application/json"); + request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); + + try (CloseableHttpResponse response = client.execute(request)) { + String responseBody = EntityUtils.toString(response.getEntity()); + String token = parseTokenFromResponse(responseBody); + + if (token == null) { + throw new IOException("Failed to extract token from API response"); + } + + log.debug("Successfully fetched token from API"); + return token; + } + } + } catch (URISyntaxException e) { + throw new IOException("Failed to build token request URL", e); + } + } + + private String parseTokenFromResponse(String responseBody) { + Matcher matcher = Pattern.compile("\"user_token\"\\s*:\\s*\"([^\"]+)\"").matcher(responseBody); + if (matcher.find()) { + return matcher.group(1); + } + return null; + } + + public String getAppId() { + return this.extractedAppId != null ? this.extractedAppId : APP_ID; + } + + public void invalidateToken() { + synchronized (tokenLock) { + this.userToken = null; + this.tokenExpires = null; + } + } +} 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 817bf5b3..3b184465 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 @@ -5,11 +5,12 @@ import com.github.topi314.lavasearch.SearchManager; import com.github.topi314.lavasearch.api.SearchManagerConfiguration; import com.github.topi314.lavasrc.applemusic.AppleMusicSourceManager; +import com.github.topi314.lavasrc.lrclib.LrcLibLyricsManager; +import com.github.topi314.lavasrc.musixmatch.MusixmatchLyricsManager; import com.github.topi314.lavasrc.deezer.DeezerAudioSourceManager; import com.github.topi314.lavasrc.deezer.DeezerAudioTrack; import com.github.topi314.lavasrc.flowerytts.FloweryTTSSourceManager; import com.github.topi314.lavasrc.jiosaavn.JioSaavnAudioSourceManager; -import com.github.topi314.lavasrc.lrclib.LrcLibLyricsManager; import com.github.topi314.lavasrc.mirror.DefaultMirroringAudioTrackResolver; import com.github.topi314.lavasrc.plugin.config.*; import com.github.topi314.lavasrc.plugin.service.ProxyConfigurationService; @@ -21,8 +22,8 @@ import com.github.topi314.lavasrc.yandexmusic.YandexMusicSourceManager; import com.github.topi314.lavasrc.youtube.YoutubeSearchManager; import com.github.topi314.lavasrc.ytdlp.YtdlpAudioSourceManager; -import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import dev.arbjerg.lavalink.api.AudioPlayerManagerConfiguration; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,10 +49,11 @@ public class LavaSrcPlugin implements AudioPlayerManagerConfiguration, SearchMan private YoutubeSearchManager youtube; private VkMusicSourceManager vkMusic; private TidalSourceManager tidal; - private JioSaavnAudioSourceManager jioSaavn; private QobuzAudioSourceManager qobuz; private YtdlpAudioSourceManager ytdlp; private LrcLibLyricsManager lrcLib; + private MusixmatchLyricsManager musixmatch; + private JioSaavnAudioSourceManager jioSaavn; public LavaSrcPlugin( LavaSrcConfig pluginConfig, @@ -89,6 +91,7 @@ public LavaSrcPlugin( this.spotify.setLocalFiles(spotifyConfig.isLocalFiles()); } } + if (sourcesConfig.isAppleMusic()) { this.appleMusic = new AppleMusicSourceManager(pluginConfig.getProviders(), appleMusicConfig.getMediaAPIToken(), appleMusicConfig.getCountryCode(), unused -> manager); if (appleMusicConfig.getPlaylistLoadLimit() > 0) { @@ -98,29 +101,27 @@ public LavaSrcPlugin( appleMusic.setAlbumPageLimit(appleMusicConfig.getAlbumLoadLimit()); } } + if (sourcesConfig.isDeezer() || lyricsSourcesConfig.isDeezer()) { this.deezer = new DeezerAudioSourceManager(deezerConfig.getMasterDecryptionKey(), deezerConfig.getArl(), deezerConfig.getFormats()); } - + if (sourcesConfig.isYandexMusic() || lyricsSourcesConfig.isYandexMusic()) { this.yandexMusic = new YandexMusicSourceManager(yandexMusicConfig.getAccessToken()); - + proxyConfigurationService.configure(this.yandexMusic, yandexMusicConfig.getProxy()); - + if (yandexMusicConfig.getPlaylistLoadLimit() > 0) { yandexMusic.setPlaylistLoadLimit(yandexMusicConfig.getPlaylistLoadLimit()); } - if (yandexMusicConfig.getAlbumLoadLimit() > 0) { yandexMusic.setAlbumLoadLimit(yandexMusicConfig.getAlbumLoadLimit()); } - if (yandexMusicConfig.getArtistLoadLimit() > 0) { yandexMusic.setArtistLoadLimit(yandexMusicConfig.getArtistLoadLimit()); } - } - + if (sourcesConfig.isFloweryTTS()) { this.flowerytts = new FloweryTTSSourceManager(floweryTTSConfig.getVoice()); if (floweryTTSConfig.getTranslate()) { @@ -136,6 +137,7 @@ public LavaSrcPlugin( this.flowerytts.setAudioFormat(floweryTTSConfig.getAudioFormat()); } } + if (sourcesConfig.isYoutube() || lyricsSourcesConfig.isYoutube()) { if (hasNewYoutubeSource()) { log.info("Registering Youtube Source audio source manager..."); @@ -144,10 +146,12 @@ public LavaSrcPlugin( throw new IllegalStateException("Youtube LavaSearch requires the new Youtube Source plugin to be enabled."); } } + if (sourcesConfig.isVkMusic() || lyricsSourcesConfig.isVkMusic()) { this.vkMusic = new VkMusicSourceManager(vkMusicConfig.getUserToken()); + proxyConfigurationService.configure(this.vkMusic, vkMusicConfig.getProxy()); - + if (vkMusicConfig.getPlaylistLoadLimit() > 0) { vkMusic.setPlaylistLoadLimit(vkMusicConfig.getPlaylistLoadLimit()); } @@ -158,15 +162,18 @@ public LavaSrcPlugin( vkMusic.setRecommendationsLoadLimit(vkMusicConfig.getRecommendationLoadLimit()); } } + if (sourcesConfig.isTidal()) { this.tidal = new TidalSourceManager(pluginConfig.getProviders(), tidalConfig.getCountryCode(), unused -> this.manager, tidalConfig.getToken()); if (tidalConfig.getSearchLimit() > 0) { this.tidal.setSearchLimit(tidalConfig.getSearchLimit()); } } + if (sourcesConfig.isQobuz()) { this.qobuz = new QobuzAudioSourceManager(qobuzConfig.getUserOauthToken(), qobuzConfig.getAppId(), qobuzConfig.getAppSecret()); } + if (sourcesConfig.isYtdlp()) { this.ytdlp = new YtdlpAudioSourceManager(ytdlpConfig.getPath(), ytdlpConfig.getSearchLimit(), ytdlpConfig.getCustomLoadArgs(), ytdlpConfig.getCustomPlaybackArgs()); } @@ -175,9 +182,13 @@ public LavaSrcPlugin( this.lrcLib = new LrcLibLyricsManager(); } + if (lyricsSourcesConfig.isMusixmatch()) { + this.musixmatch = new MusixmatchLyricsManager(); + } + if (sourcesConfig.isJiosaavn()) { this.jioSaavn = new JioSaavnAudioSourceManager(jioSaavnConfig.buildConfig()); - + proxyConfigurationService.configure(this.jioSaavn, jioSaavnConfig.getProxy()); } } @@ -195,46 +206,57 @@ private boolean hasNewYoutubeSource() { @Override public AudioPlayerManager configure(@NotNull AudioPlayerManager manager) { this.manager = manager; + if (this.spotify != null && this.sourcesConfig.isSpotify()) { log.info("Registering Spotify audio source manager..."); manager.registerSourceManager(this.spotify); } + if (this.appleMusic != null) { log.info("Registering Apple Music audio source manager..."); manager.registerSourceManager(this.appleMusic); } + if (this.deezer != null && this.sourcesConfig.isDeezer()) { log.info("Registering Deezer audio source manager..."); manager.registerSourceManager(this.deezer); } - if (this.yandexMusic != null) { + + if (this.yandexMusic != null && this.sourcesConfig.isYandexMusic()) { log.info("Registering Yandex Music audio source manager..."); manager.registerSourceManager(this.yandexMusic); } + if (this.flowerytts != null) { log.info("Registering Flowery TTS audio source manager..."); manager.registerSourceManager(this.flowerytts); } - if (this.vkMusic != null) { - log.info("Registering Vk Music audio source manager..."); + + if (this.vkMusic != null && this.sourcesConfig.isVkMusic()) { + log.info("Registering VK Music audio source manager..."); manager.registerSourceManager(this.vkMusic); } + if (this.tidal != null) { log.info("Registering Tidal audio source manager..."); manager.registerSourceManager(this.tidal); } + if (this.qobuz != null) { log.info("Registering Qobuz audio source manager..."); manager.registerSourceManager(this.qobuz); } + if (this.ytdlp != null) { log.info("Registering YTDLP audio source manager..."); manager.registerSourceManager(this.ytdlp); } - if (this.jioSaavn != null) { + + if (this.jioSaavn != null && this.sourcesConfig.isJiosaavn()) { log.info("Registering JioSaavn audio source manager..."); manager.registerSourceManager(this.jioSaavn); } + return manager; } @@ -245,30 +267,37 @@ public SearchManager configure(@NotNull SearchManager manager) { log.info("Registering Spotify search manager..."); manager.registerSearchManager(this.spotify); } + if (this.appleMusic != null && this.sourcesConfig.isAppleMusic()) { log.info("Registering Apple Music search manager..."); manager.registerSearchManager(this.appleMusic); } + if (this.deezer != null && this.sourcesConfig.isDeezer()) { log.info("Registering Deezer search manager..."); manager.registerSearchManager(this.deezer); } + if (this.youtube != null && this.sourcesConfig.isYoutube()) { 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); } + if (this.vkMusic != null && this.sourcesConfig.isVkMusic()) { log.info("Registering VK Music search manager..."); manager.registerSearchManager(this.vkMusic); } + if (this.jioSaavn != null && this.sourcesConfig.isJiosaavn()) { log.info("Registering JioSaavn search manager..."); manager.registerSearchManager(this.jioSaavn); } + return manager; } @@ -279,26 +308,37 @@ public LyricsManager configure(@NotNull LyricsManager manager) { log.info("Registering Spotify lyrics manager..."); manager.registerLyricsManager(this.spotify); } + if (this.deezer != null && this.lyricsSourcesConfig.isDeezer()) { log.info("Registering Deezer lyrics manager..."); manager.registerLyricsManager(this.deezer); } + if (this.youtube != null && this.lyricsSourcesConfig.isYoutube()) { log.info("Registering YouTube lyrics manager..."); manager.registerLyricsManager(this.youtube); } + if (this.yandexMusic != null && this.lyricsSourcesConfig.isYandexMusic()) { log.info("Registering Yandex Music lyrics manager"); manager.registerLyricsManager(this.yandexMusic); } + if (this.vkMusic != null && this.lyricsSourcesConfig.isVkMusic()) { log.info("Registering VK Music lyrics manager..."); manager.registerLyricsManager(this.vkMusic); } + if (this.lrcLib != null && this.lyricsSourcesConfig.isLrcLib()) { log.info("Registering LRCLIB lyrics manager..."); manager.registerLyricsManager(this.lrcLib); } + + if (this.musixmatch != null && this.lyricsSourcesConfig.isMusixmatch()) { + log.info("Registering Musixmatch lyrics manager..."); + manager.registerLyricsManager(this.musixmatch); + } + return manager; } @@ -380,4 +420,4 @@ public void updateConfig(@RequestBody Config config) { } } } -} \ No newline at end of file +} diff --git a/plugin/src/main/java/com/github/topi314/lavasrc/plugin/config/LyricsSourcesConfig.java b/plugin/src/main/java/com/github/topi314/lavasrc/plugin/config/LyricsSourcesConfig.java index 71ad22d0..d69f34e7 100644 --- a/plugin/src/main/java/com/github/topi314/lavasrc/plugin/config/LyricsSourcesConfig.java +++ b/plugin/src/main/java/com/github/topi314/lavasrc/plugin/config/LyricsSourcesConfig.java @@ -14,6 +14,15 @@ public class LyricsSourcesConfig { private boolean yandexMusic = false; private boolean vkMusic = false; private boolean lrcLib = false; + private boolean musixmatch = false; + + public boolean isMusixmatch() { + return this.musixmatch; + } + + public void setMusixmatch(boolean musixmatch) { + this.musixmatch = musixmatch; + } public boolean isSpotify() { return this.spotify; @@ -54,11 +63,9 @@ public boolean isVkMusic() { public void setVkMusic(boolean vkMusic) { this.vkMusic = vkMusic; } - public boolean isLrcLib() { return this.lrcLib; } - public void setLrcLib(boolean lrcLib) { this.lrcLib = lrcLib; }