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
);
}