206 lines
11 KiB
Java
206 lines
11 KiB
Java
package eric.Roullette.service;
|
|
|
|
import com.fasterxml.jackson.core.type.TypeReference;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.neovisionaries.i18n.CountryCode;
|
|
import okhttp3.OkHttpClient;
|
|
import okhttp3.Request;
|
|
import okhttp3.Response;
|
|
import org.apache.hc.core5.http.ParseException;
|
|
import se.michaelthelin.spotify.SpotifyApi;
|
|
import se.michaelthelin.spotify.SpotifyHttpManager;
|
|
import se.michaelthelin.spotify.exceptions.SpotifyWebApiException;
|
|
import se.michaelthelin.spotify.model_objects.credentials.AuthorizationCodeCredentials;
|
|
import se.michaelthelin.spotify.model_objects.specification.*;
|
|
import se.michaelthelin.spotify.requests.data.player.GetCurrentUsersRecentlyPlayedTracksRequest;
|
|
import se.michaelthelin.spotify.requests.data.library.GetUsersSavedTracksRequest;
|
|
|
|
import java.io.IOException;
|
|
import java.net.URI;
|
|
import java.util.*;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
import static com.neovisionaries.i18n.CountryCode.DE;
|
|
|
|
public class SpotifyAuthService {
|
|
private final String clientId;
|
|
private final String clientSecret;
|
|
private final URI redirectUri;
|
|
// Speichert für jeden Benutzer eine eigene, authentifizierte SpotifyApi-Instanz
|
|
private final Map<String, SpotifyApi> userApis = new ConcurrentHashMap<>();
|
|
|
|
public SpotifyAuthService(String clientId, String clientSecret, String redirectUri) {
|
|
this.clientId = clientId;
|
|
this.clientSecret = clientSecret;
|
|
this.redirectUri = SpotifyHttpManager.makeUri(redirectUri);
|
|
}
|
|
|
|
public URI getAuthorizationUri(String user) {
|
|
// Temporäre API-Instanz nur für die Erstellung der Auth-URL
|
|
SpotifyApi tempApi = new SpotifyApi.Builder()
|
|
.setClientId(clientId)
|
|
.setClientSecret(clientSecret)
|
|
.setRedirectUri(redirectUri)
|
|
.build();
|
|
|
|
return tempApi.authorizationCodeUri()
|
|
.scope("user-read-recently-played user-library-read user-read-playback-state user-modify-playback-state streaming")
|
|
.state(user) // Der Benutzername wird im State mitgegeben
|
|
.build()
|
|
.execute();
|
|
}
|
|
|
|
public void exchangeCode(String code, String user) throws IOException, ParseException, SpotifyWebApiException {
|
|
// Erstellt eine neue, dedizierte API-Instanz für diesen Benutzer
|
|
SpotifyApi userApi = new SpotifyApi.Builder()
|
|
.setClientId(clientId)
|
|
.setClientSecret(clientSecret)
|
|
.setRedirectUri(redirectUri)
|
|
.build();
|
|
|
|
// Tauscht den Code gegen Tokens und konfiguriert die Instanz
|
|
AuthorizationCodeCredentials creds = userApi.authorizationCode(code).build().execute();
|
|
userApi.setAccessToken(creds.getAccessToken());
|
|
userApi.setRefreshToken(creds.getRefreshToken());
|
|
|
|
// Speichert die fertig konfigurierte API-Instanz für den Benutzer
|
|
userApis.put(user, userApi);
|
|
}
|
|
|
|
public List<String> getRecentTracks(String user) {
|
|
int limit = 50;
|
|
SpotifyApi userApi = userApis.get(user);
|
|
|
|
if (userApi == null) {
|
|
System.err.println("Kein SpotifyApi-Client für Benutzer gefunden: " + user);
|
|
return Collections.emptyList();
|
|
}
|
|
|
|
try {
|
|
GetCurrentUsersRecentlyPlayedTracksRequest request = userApi.getCurrentUsersRecentlyPlayedTracks()
|
|
.limit(limit)
|
|
.build();
|
|
PagingCursorbased<PlayHistory> history = request.execute();
|
|
if (history == null || history.getItems() == null) {
|
|
return Collections.emptyList();
|
|
}
|
|
List<String> recentTracks = Arrays.stream(history.getItems())
|
|
.map(item -> item.getTrack().getUri())
|
|
.distinct()
|
|
.toList();
|
|
|
|
if (recentTracks.size() < limit) {
|
|
int newLimit = limit - recentTracks.size();
|
|
// restliche songs mit kürzlich gespeicherten Tracks auffüllen
|
|
List<String> savedTracks = getSavedTracks(user, newLimit, 0);
|
|
// Nur Tracks hinzufügen, die noch nicht in recentTracks sind
|
|
savedTracks.removeAll(recentTracks);
|
|
recentTracks = new java.util.ArrayList<>(recentTracks);
|
|
recentTracks.addAll(savedTracks.subList(0, Math.min(newLimit, savedTracks.size())));
|
|
if(recentTracks.size() < limit){
|
|
newLimit = limit - recentTracks.size();
|
|
List<String> savedTracks2 = getSavedTracks(user, newLimit, 50);
|
|
savedTracks2.removeAll(recentTracks);
|
|
recentTracks.addAll(savedTracks2.subList(0, Math.min(newLimit, savedTracks2.size())));
|
|
}
|
|
}
|
|
return recentTracks.subList(0, Math.min(limit, recentTracks.size()));
|
|
} catch (IOException | SpotifyWebApiException | ParseException e) {
|
|
e.printStackTrace();
|
|
return Collections.emptyList();
|
|
}
|
|
}
|
|
|
|
private List<String> getSavedTracks(String user, int limit, int offset) {
|
|
SpotifyApi userApi = userApis.get(user);
|
|
|
|
if (userApi == null) {
|
|
System.err.println("Kein SpotifyApi-Client für Benutzer gefunden: " + user);
|
|
return Collections.emptyList();
|
|
}
|
|
try {
|
|
List<String> saved = new ArrayList<>();
|
|
while (saved.size() < limit) {
|
|
GetUsersSavedTracksRequest req = userApi.getUsersSavedTracks()
|
|
.limit(limit)
|
|
.offset(offset)
|
|
.market(CountryCode.DE)
|
|
.build();
|
|
Paging<SavedTrack> page = req.execute();
|
|
if (page == null || page.getItems().length == 0){
|
|
System.out.println("Keine weiteren gespeicherten Tracks gefunden.");
|
|
break;
|
|
}
|
|
for (SavedTrack st : page.getItems()) {
|
|
saved.add(st.getTrack().getUri());
|
|
if (saved.size() == limit) break;
|
|
}
|
|
offset += limit;
|
|
}
|
|
return saved;
|
|
} catch (IOException | SpotifyWebApiException | ParseException e) {
|
|
e.printStackTrace();
|
|
return Collections.emptyList();
|
|
}
|
|
}
|
|
public List<String> getTrackInfos(List<String> allTracks) {
|
|
//für jede URI den titel holen
|
|
List<String> trackInfos = new ArrayList<>();
|
|
for (String uri : allTracks) {
|
|
SpotifyApi userApi = userApis.values().stream().findFirst().orElse(null);
|
|
if (userApi == null) {
|
|
System.err.println("Kein SpotifyApi-Client gefunden.");
|
|
return Collections.emptyList();
|
|
}
|
|
try {
|
|
String trackId = uri.startsWith("spotify:track:") ? uri.substring("spotify:track:".length()) : uri;
|
|
var track = userApi.getTrack(trackId)
|
|
.build()
|
|
.execute();
|
|
if (track != null) {
|
|
String info = track.getName() + " - " + Arrays.stream(track.getArtists())
|
|
.map(ArtistSimplified::getName)
|
|
.reduce((a, b) -> a + ", " + b)
|
|
.orElse("Unbekannt");
|
|
trackInfos.add(info);
|
|
|
|
} else {
|
|
System.err.println("Track nicht gefunden: " + uri);
|
|
}
|
|
} catch (IOException | SpotifyWebApiException | ParseException e) {
|
|
System.err.println("Fehler beim Abrufen des Tracks: " + uri);
|
|
e.printStackTrace();
|
|
}
|
|
} return trackInfos;
|
|
}
|
|
|
|
|
|
public String getAccessTokenForUser(String username) {
|
|
SpotifyApi userApi = userApis.get(username);
|
|
if (userApi == null) {
|
|
System.err.println("Kein SpotifyApi-Client für Benutzer gefunden: " + username);
|
|
return null;
|
|
}
|
|
return userApi.getAccessToken();
|
|
}
|
|
public List<Map<String, Object>> getDevices(String username) {
|
|
String accessToken = getAccessTokenForUser(username);
|
|
OkHttpClient client = new OkHttpClient();
|
|
Request req = new Request.Builder()
|
|
.url("https://api.spotify.com/v1/me/player/devices")
|
|
.addHeader("Authorization", "Bearer " + accessToken)
|
|
.build();
|
|
try (Response resp = client.newCall(req).execute()) {
|
|
if (!resp.isSuccessful()) return List.of();
|
|
String body = resp.body().string();
|
|
// Parsen, z.B. mit Jackson
|
|
var node = new ObjectMapper().readTree(body);
|
|
var devices = node.get("devices");
|
|
return new ObjectMapper().convertValue(devices, new TypeReference<List<Map<String, Object>>>(){});
|
|
} catch (Exception e) {
|
|
return List.of();
|
|
}
|
|
}
|
|
}
|
|
|