GUI #41

Merged
3014947 merged 2 commits from GUI into main 2025-08-11 19:07:55 +02:00
5 changed files with 91 additions and 29 deletions

View File

@ -23,6 +23,7 @@ package eric.Roullette.controller;
import java.util.Map;
// import java.util.Objects;
// import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
// import java.util.concurrent.TimeUnit;
@ -32,6 +33,8 @@ public class GameController {
private final GameWebSocketHandler webSocketHandler;
private final OkHttpClient httpClient = new OkHttpClient();
private String accessToken = "";
private Map<String, String> userAccessTokens = new ConcurrentHashMap<>();
// Map<username, accessToken>
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GameController.class);
public GameController(Javalin app, GameService gs, SpotifyAuthService sas, GameWebSocketHandler wsHandler) {
@ -50,21 +53,28 @@ public class GameController {
ctx.status(400).result("username fehlt");
return;
}
var accessToken = authService.getAccessTokenForUser(username);
// spieler in map hinzufügen
String playerAccessToken = null;
if(!userAccessTokens.containsKey(username)) {
playerAccessToken = authService.getAccessTokenForUser(username);
if (playerAccessToken != null) {
userAccessTokens.put(username, playerAccessToken);
}
}
if (accessToken == null) {
ctx.status(401).result("Zugriffstoken fehlt oder ist ungültig");
return;
}
setToken(accessToken);
var devices = authService.getDevices(accessToken);
ctx.json(devices);
});
}
private void setToken(String accessToken) {
this.accessToken = accessToken;
}
@SuppressWarnings("unchecked")
private void createGame(Context ctx) throws InterruptedException {
Map<String, Object> body = (Map<String, Object>) ctx.bodyAsClass(Map.class);
@ -144,6 +154,7 @@ public class GameController {
try {
//String accessToken = authService.getAccessTokenForUser(username);
String accessToken = userAccessTokens.get(username);
OkHttpClient client = httpClient;
String trackId = trackUri.split(":")[2];
Request getTrack = new Request.Builder()

View File

@ -20,7 +20,7 @@ import java.net.URI;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import static com.neovisionaries.i18n.CountryCode.DE;
//import static com.neovisionaries.i18n.CountryCode.DE;
public class SpotifyAuthService {
private final String clientId;
@ -38,18 +38,27 @@ public class SpotifyAuthService {
public URI getAuthorizationUri(String user) {
System.out.println("Erstelle Auth-URL für Benutzer: " + user);
// Temporäre API-Instanz nur für die Erstellung der Auth-URL
String scope = "user-read-recently-played user-library-read user-modify-playback-state user-read-playback-state streaming user-read-private";
//
SpotifyApi tempApi = new SpotifyApi.Builder()
.setClientId(clientId)
.setClientSecret(clientSecret)
.setRedirectUri(redirectUri)
.build();
//überprüfe, ob der nutzer ein Premium-User ist
return tempApi.authorizationCodeUri()
.scope("user-read-recently-played user-library-read user-read-playback-state user-modify-playback-state streaming")
.scope(scope)
.state(user) // Der Benutzername wird im State mitgegeben
.show_dialog(true)
.build()
.execute();
}
// public boolean isPremiumUser(SpotifyApi api) throws IOException, SpotifyWebApiException, ParseException {
// var user = api.getCurrentUsersProfile().build().execute();
// return "premium".equalsIgnoreCase(String.valueOf(user.getProduct()));
// }
public void exchangeCode(String code, String user) throws IOException, ParseException, SpotifyWebApiException {
// Erstellt eine neue, dedizierte API-Instanz für diesen Benutzer
@ -63,6 +72,8 @@ public class SpotifyAuthService {
AuthorizationCodeCredentials creds = userApi.authorizationCode(code).build().execute();
userApi.setAccessToken(creds.getAccessToken());
userApi.setRefreshToken(creds.getRefreshToken());
System.out.println("Granted scopes: " + creds.getScope()); // wichtig fürs Debugging
// Speichert die fertig konfigurierte API-Instanz für den Benutzer
userApis.put(user, userApi);
@ -70,26 +81,57 @@ public class SpotifyAuthService {
public List<String> getRecentTracks(String user) {
System.out.println("Hole kürzlich gespielte Tracks für Benutzer: " + user);
int limit = 2;
int limit = 50;
SpotifyApi userApi = userApis.get(user);
if (userApi == null) {
System.err.println("Kein SpotifyApi-Client für Benutzer gefunden: " + user);
System.out.println("Kein SpotifyApi-Client für Benutzer gefunden: " + user);
return Collections.emptyList();
}
System.out.println("SpotifyApi-Client für Benutzer gefunden: " + user);
System.out.println("AccessToken: " + userApi.getAccessToken());
System.out.println("RefreshToken: " + userApi.getRefreshToken());
try {
System.out.println("Hole Profil für Benutzer: " + user);
// Hole das Profil des Benutzers, um den Account-Typ zu überprüfen
var profile = userApi.getCurrentUsersProfile().build().execute();
System.out.println("Account-Typ: " + profile.getProduct());
System.out.println("Erstelle Anfrage für kürzlich gespielte Tracks...");
if (userApi.getRefreshToken() == null) {
System.out.println("Refresh Token für Benutzer " + user + " ist nicht gesetzt.");
}
System.out.println("Refresh Token für Benutzer " + user + " ist gesetzt.");
if( userApi.getAccessToken() == null) {
System.out.println("Access Token für Benutzer " + user + " ist nicht gesetzt.");
return Collections.emptyList();
}
try {
System.out.println("Access Token für Benutzer " + user + " ist gesetzt.");
GetCurrentUsersRecentlyPlayedTracksRequest request = userApi.getCurrentUsersRecentlyPlayedTracks()
.limit(limit)
.build();
// Führe die Anfrage aus und erhalte die Ergebnisse
System.out.println("Führe paging Anfrage aus...");
PagingCursorbased<PlayHistory> history = request.execute();
System.out.println("Paging Anfrage erfolgreich ausgeführt.");
// Überprüfe, ob die Ergebnisse leer sind
System.out.println("Überprüfe, ob Ergebnisse vorhanden sind...");
if (history == null || history.getItems() == null) {
return Collections.emptyList();
}
System.out.println("Verarbeite kürzlich gespielte Tracks...");
// Extrahiere die URIs der kürzlich gespielten Tracks
List<String> recentTracks = Arrays.stream(history.getItems())
.map(item -> item.getTrack().getUri())
.distinct()
.toList();
System.out.println("Gefundene kürzlich gespielte Tracks: " + recentTracks.size());
if (recentTracks.size() < limit) {
int newLimit = limit - recentTracks.size();
@ -106,7 +148,14 @@ public class SpotifyAuthService {
recentTracks.addAll(savedTracks2.subList(0, Math.min(newLimit, savedTracks2.size())));
}
}
System.out.println("Endgültige Anzahl kürzlich gespielter Tracks: " + recentTracks.size());
return recentTracks.subList(0, Math.min(limit, recentTracks.size()));
// } catch (se.michaelthelin.spotify.exceptions.detailed.ForbiddenException e) {
// System.out.println("ForbiddenException: " + e.getMessage());
// System.out.println("Möglicherweise hat der Benutzer keine Berechtigung, kürzlich gespielte Tracks abzurufen.");
//
//
// return Collections.emptyList();
} catch (IOException | SpotifyWebApiException | ParseException e) {
e.printStackTrace();
return Collections.emptyList();
@ -197,6 +246,7 @@ public class SpotifyAuthService {
.build();
try (Response resp = client.newCall(req).execute()) {
if (!resp.isSuccessful()) return List.of();
assert resp.body() != null;
String body = resp.body().string();
// Parsen, z.B. mit Jackson
var node = new ObjectMapper().readTree(body);

View File

@ -22,10 +22,10 @@ public class GameWebSocketHandler {
// Spiel-ID → (Username → deren Guess)
private final Map<String, Map<String, String>> currentGuesses = new ConcurrentHashMap<>();
private final Map<String, List<String>> trackInfoCache = new ConcurrentHashMap<>();
private final Map<String, List<String>> allTracksCache = new ConcurrentHashMap<>();
// private final Map<String, List<String>> trackInfoCache = new ConcurrentHashMap<>();
// private final Map<String, List<String>> allTracksCache = new ConcurrentHashMap<>();
//Map<gameId, Map<player, List<String>>
private Map<String, Map<String, List<String>>> playerTracksCache = new ConcurrentHashMap<>();
//private Map<String, Map<String, List<String>>> playerTracksCache = new ConcurrentHashMap<>();
private Map<String, Map<String, List<String>>> playerTrackInfoCache = new ConcurrentHashMap<>();
public GameWebSocketHandler(GameService gameService) {
@ -77,9 +77,7 @@ public class GameWebSocketHandler {
}
case "requestPlayers" -> service.broadcastPlayers(gameId);
case "next-round" -> {
nextround(gameId);
}
case "next-round" -> nextround(gameId);
case "start-round" -> {
var currentGame = service.getOrCreateGame(gameId);
if (currentGame.players().isEmpty()) return;
@ -94,10 +92,10 @@ public class GameWebSocketHandler {
service.startRound(gameId, allTracks);
}
// Trackinfos für alle Spieler sammeln
Map<String, List<String>> allTrackInfos = service.getTrackInfos(allPlayerTracks);
//Map<String, List<String>> allTrackInfos = service.getTrackInfos(allPlayerTracks);
// Cache für Trackinfos pro Spiel-ID
playerTrackInfoCache.put(gameId, allTrackInfos);
System.out.println("TrackInfosCache für Spiel " + gameId + " hat " + allTrackInfos.size() + " Spieler (rundenstart)");
//playerTrackInfoCache.put(gameId, allTrackInfos);
//System.out.println("TrackInfosCache für Spiel " + gameId + " hat " + allTrackInfos.size() + " Spieler (rundenstart)");
broadcastRoundStart(gameId);
}
}
@ -168,7 +166,7 @@ private void broadcastRoundResult(String gameId) {
// Prüfe auf Gewinner
String winner = scores.entrySet().stream()
.filter(e -> e.getValue() >= 30)
.filter(e -> e.getValue() >= 6)
.map(Map.Entry::getKey)
.findFirst()
.orElse(null);
@ -182,14 +180,15 @@ private void broadcastRoundResult(String gameId) {
broadcastToAll(gameId, winMsg);
game.scores().replaceAll((user , pts) -> 0); // Reset Scores für alle Spieler
}else{
// nächste Runde starten
// ...
// new Thread(() -> {
// try { Thread.sleep(2000); } catch (InterruptedException ignored) {}
// nextround(gameId);
// }).start();
}
// else{
// // nächste Runde starten
// // ...
//// new Thread(() -> {
//// try { Thread.sleep(2000); } catch (InterruptedException ignored) {}
//// nextround(gameId);
//// }).start();
// }
}
/** Hilfsmethode: Sendet eine Nachricht an alle WebSocket-Sessions eines Spiels. */

View File

@ -189,7 +189,6 @@
<section class="card">
<div class="card-hd">
<h2 style="margin:0">Teilnehmer</h2>
<button id="refreshPlayers" class="btn btn-ghost">Aktualisieren</button>
</div>
<div class="card-bd">
<ul id="playersList"></ul>

View File

@ -301,7 +301,10 @@ function handleRoundResult({ scores, guesses, owner }) {
// 8) Spielende -> Start-Button zurück
function handleGameEnd({ winner }) {
const nextBtn = document.getElementById("nextRound");
resultP.textContent = `🎉 ${winner} hat gewonnen!`;
nextBtn.hidden = true;
nextBtn.disabled = true;
setTimeout(() => {
startBtn.hidden = false;
startBtn.disabled = false;