diff --git a/README.md b/README.md index 37eaceb2..8137278d 100644 --- a/README.md +++ b/README.md @@ -83,10 +83,20 @@ playerManager.registerSourceManager(new SpotifySourceManager(null, clientId, cli ```java AudioPlayerManager playerManager = new DefaultAudioPlayerManager(); -// create a new AppleMusicSourceManager with the standard providers, token(pass null for automatic extraction), countrycode and AudioPlayerManager and register it +// create a new AppleMusicSourceManager with the standard providers, apple music api token, countrycode and AudioPlayerManager and register it playerManager.registerSourceManager(new AppleMusicSourceManager(null, mediaAPIToken , "us", playerManager)); ``` +
+How to get media api token with Apple developer account + +1. Go to https://music.apple.com +2. Open DevTools and go to the Debugger tab +3. Search with this regex `const \w{2}="(?(ey[\w-]+)\.([\w-]+)\.([\w-]+))"` in all `index-*.js` files +4. Copy the token from the source code + +
+ #### Deezer ```java AudioPlayerManager playerManager = new DefaultAudioPlayerManager(); @@ -100,7 +110,6 @@ playerManager.registerSourceManager(new DeezerSourceManager("...");
How to get access token -## How to get access token 1. (Optional) Open DevTools in your browser and on the Network tab enable trotlining. 2. Go to https://oauth.yandex.ru/authorize?response_type=token&client_id=23cabbbdc6cd418abb4b39c32c41195d 3. Authorize and grant access @@ -163,6 +172,8 @@ For all supported urls and queries see [here](#supported-urls-and-queries) To get your Spotify clientId & clientSecret go [here](https://developer.spotify.com/dashboard/applications) & then copy them into your `application.yml` like the following. +To get your Apple Music api token go [here](#apple-music) + To get your Yandex Music access token go [here](#yandex-music) (YES `plugins` IS AT ROOT IN THE YAML) @@ -188,7 +199,7 @@ plugins: albumLoadLimit: 6 # The number of pages at 50 tracks each applemusic: countryCode: "US" # the country code you want to use for filtering the artists top tracks and language. See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 - # mediaAPIToken: "..." # Can be used to bypass the auto token fetching which is likely to break again in the future + mediaAPIToken: "your apple music api token" # apple music api token playlistLoadLimit: 6 # The number of pages at 300 tracks each albumLoadLimit: 6 # The number of pages at 300 tracks each deezer: diff --git a/main/src/main/java/com/github/topisenpai/lavasrc/applemusic/AppleMusicSourceManager.java b/main/src/main/java/com/github/topisenpai/lavasrc/applemusic/AppleMusicSourceManager.java index d20e6f7a..ee74f2d8 100644 --- a/main/src/main/java/com/github/topisenpai/lavasrc/applemusic/AppleMusicSourceManager.java +++ b/main/src/main/java/com/github/topisenpai/lavasrc/applemusic/AppleMusicSourceManager.java @@ -5,20 +5,15 @@ import com.github.topisenpai.lavasrc.mirror.MirroringAudioTrackResolver; import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import com.sedmelluq.discord.lavaplayer.tools.DataFormatTools; +import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser; import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools; import com.sedmelluq.discord.lavaplayer.tools.io.HttpConfigurable; import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterfaceManager; -import com.sedmelluq.discord.lavaplayer.track.AudioItem; -import com.sedmelluq.discord.lavaplayer.track.AudioReference; -import com.sedmelluq.discord.lavaplayer.track.AudioTrack; -import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo; -import com.sedmelluq.discord.lavaplayer.track.BasicAudioPlaylist; -import org.apache.commons.io.IOUtils; +import com.sedmelluq.discord.lavaplayer.track.*; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.HttpClientBuilder; -import org.jsoup.Jsoup; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,7 +32,6 @@ public class AppleMusicSourceManager extends MirroringAudioSourceManager implements HttpConfigurable { public static final Pattern URL_PATTERN = Pattern.compile("(https?://)?(www\\.)?music\\.apple\\.com/(?[a-zA-Z]{2}/)?(?album|playlist|artist|song)(/[a-zA-Z\\d\\-]+)?/(?[a-zA-Z\\d\\-.]+)(\\?i=(?\\d+))?"); - public static final Pattern TOKEN_SCRIPT_PATTERN = Pattern.compile("const \\w{2}=\"(?(ey[\\w-]+)\\.([\\w-]+)\\.([\\w-]+))\""); public static final String SEARCH_PREFIX = "amsearch:"; public static final int MAX_PAGE_ITEMS = 300; public static final String API_BASE = "https://api.music.apple.com/v1/"; @@ -46,7 +40,7 @@ public class AppleMusicSourceManager extends MirroringAudioSourceManager impleme private final String countryCode; private int playlistPageLimit; private int albumPageLimit; - private String token; + private final String token; private String origin; private Instant tokenExpire; @@ -56,12 +50,17 @@ public AppleMusicSourceManager(String[] providers, String mediaAPIToken, String public AppleMusicSourceManager(String mediaAPIToken, String countryCode, AudioPlayerManager audioPlayerManager, MirroringAudioTrackResolver mirroringAudioTrackResolver) { super(audioPlayerManager, mirroringAudioTrackResolver); + if (mediaAPIToken == null || mediaAPIToken.isEmpty()) { + throw new RuntimeException("Apple Music API token is empty or null"); + } this.token = mediaAPIToken; + try { this.parseTokenData(); } catch (IOException e) { - throw new IllegalArgumentException("Cannot parse token for expire date and origin", e); + throw new RuntimeException("Failed to parse Apple Music API token", e); } + if (countryCode == null || countryCode.isEmpty()) { this.countryCode = "us"; } else { @@ -85,9 +84,9 @@ public String getSourceName() { @Override public AudioTrack decodeTrack(AudioTrackInfo trackInfo, DataInput input) throws IOException { return new AppleMusicAudioTrack(trackInfo, - DataFormatTools.readNullableText(input), - DataFormatTools.readNullableText(input), - this + DataFormatTools.readNullableText(input), + DataFormatTools.readNullableText(input), + this ); } @@ -129,44 +128,14 @@ public AudioItem loadItem(AudioPlayerManager manager, AudioReference reference) } public void parseTokenData() throws IOException { - if (this.token == null || this.token.isEmpty()) { - return; - } var json = JsonBrowser.parse(new String(Base64.getDecoder().decode(this.token.split("\\.")[1]))); this.tokenExpire = Instant.ofEpochSecond(json.get("exp").asLong(0)); this.origin = json.get("root_https_origin").index(0).text(); } - public void requestToken() throws IOException { - var request = new HttpGet("https://music.apple.com"); - try (var response = this.httpInterfaceManager.getInterface().execute(request)) { - var document = Jsoup.parse(response.getEntity().getContent(), null, ""); - var elements = document.select("script[type=module][src~=/assets/index.*.js]"); - if (elements.isEmpty()) { - throw new IllegalStateException("Cannot find token script element"); - } - - for (var element : elements) { - var tokenScriptURL = element.attr("src"); - request = new HttpGet("https://music.apple.com" + tokenScriptURL); - try (var indexResponse = this.httpInterfaceManager.getInterface().execute(request)) { - var tokenScript = IOUtils.toString(indexResponse.getEntity().getContent(), StandardCharsets.UTF_8); - var tokenMatcher = TOKEN_SCRIPT_PATTERN.matcher(tokenScript); - if (tokenMatcher.find()) { - this.token = tokenMatcher.group("token"); - this.parseTokenData(); - return; - } - } - - } - } - throw new IllegalStateException("Cannot find token script url"); - } - public String getToken() throws IOException { - if (this.token == null || this.tokenExpire == null || this.tokenExpire.isBefore(Instant.now())) { - this.requestToken(); + if (this.tokenExpire.isBefore(Instant.now())) { + throw new FriendlyException("Apple Music API token is expired", FriendlyException.Severity.SUSPICIOUS, null); } return this.token; } @@ -266,17 +235,17 @@ private AudioTrack parseTrack(JsonBrowser json) { var attributes = json.get("attributes"); var artwork = attributes.get("artwork"); return new AppleMusicAudioTrack( - new AudioTrackInfo( - attributes.get("name").text(), - attributes.get("artistName").text(), - attributes.get("durationInMillis").asLong(0), - json.get("id").text(), - false, - attributes.get("url").text() - ), - attributes.get("isrc").text(), - artwork.get("url").text().replace("{w}", artwork.get("width").text()).replace("{h}", artwork.get("height").text()), - this + new AudioTrackInfo( + attributes.get("name").text(), + attributes.get("artistName").text(), + attributes.get("durationInMillis").asLong(0), + json.get("id").text(), + false, + attributes.get("url").text() + ), + attributes.get("isrc").text(), + artwork.get("url").text().replace("{w}", artwork.get("width").text()).replace("{h}", artwork.get("height").text()), + this ); }