From 0bc40127748a13ecfe809a7c6a28b96637fb363c Mon Sep 17 00:00:00 2001 From: eric <3024947@stud.hs-mannheim.de> Date: Mon, 11 Aug 2025 20:32:16 +0200 Subject: [PATCH 01/10] trackinfos werden zu beginn von rundenstart aktualisiert und danach nur wenn ein spieler joint --- .../Roullette/controller/GameController.java | 8 ++--- .../Roullette/service/SpotifyAuthService.java | 2 +- .../websocket/GameWebSocketHandler.java | 35 +++++++++++++++---- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/main/java/eric/Roullette/controller/GameController.java b/src/main/java/eric/Roullette/controller/GameController.java index b752532..ca15429 100644 --- a/src/main/java/eric/Roullette/controller/GameController.java +++ b/src/main/java/eric/Roullette/controller/GameController.java @@ -62,13 +62,11 @@ public class GameController { userAccessTokens.put(username, playerAccessToken); } } - - if (accessToken == null) { - ctx.status(401).result("Zugriffstoken fehlt oder ist ungültig"); + if (!userAccessTokens.containsKey(username)) { + ctx.status(401).result("Zugriffstoken für Benutzer " + username + " nicht gefunden."); return; } - - var devices = authService.getDevices(accessToken); + var devices = authService.getDevices(userAccessTokens.get(username)); ctx.json(devices); }); diff --git a/src/main/java/eric/Roullette/service/SpotifyAuthService.java b/src/main/java/eric/Roullette/service/SpotifyAuthService.java index cb62c98..9b28fcc 100644 --- a/src/main/java/eric/Roullette/service/SpotifyAuthService.java +++ b/src/main/java/eric/Roullette/service/SpotifyAuthService.java @@ -51,7 +51,7 @@ public class SpotifyAuthService { return tempApi.authorizationCodeUri() .scope(scope) .state(user) // Der Benutzername wird im State mitgegeben - .show_dialog(true) + //.show_dialog(true) .build() .execute(); } diff --git a/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java b/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java index dff62d1..85dcc02 100644 --- a/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java +++ b/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java @@ -88,14 +88,36 @@ public class GameWebSocketHandler { .flatMap(List::stream) .toList(); System.out.println("AlltracksCache für Spiel " + gameId + " hat " + allTracks.size() + " Songs (rundenstart)"); + //Trackinfos für alle Spieler sammeln + + if(playerTrackInfoCache.containsKey(gameId)){ + // Wenn der Cache schon existiert, dann nur die Trackinfos nutzen + System.out.println("TrackInfosCache für Spiel " + gameId + " existiert bereits (rundenstart)"); + // prüfen ob ein neuer spieler dazugekommen ist + if( allPlayerTracks.size() > playerTrackInfoCache.get(gameId).size()) { + System.out.println("Neuer Spieler hinzugefügt, Trackinfos werden aktualisiert (rundenstart)"); + Map> allTrackInfos = service.getTrackInfos(allPlayerTracks); + // Cache für Trackinfos pro Spiel-ID aktualisieren + playerTrackInfoCache.put(gameId, allTrackInfos); + } else { + System.out.println("Keine neuen Spieler, Trackinfos bleiben unverändert (rundenstart)"); + } + + } else { + // Wenn der Cache nicht existiert, dann Trackinfos sammeln + System.out.println("TrackInfosCache für Spiel " + gameId + " wird erstellt (rundenstart)"); + Map> allTrackInfos = service.getTrackInfos(allPlayerTracks); +// Cache für Trackinfos pro Spiel-ID + playerTrackInfoCache.put(gameId, allTrackInfos); + } + + System.out.println("TrackInfosCache für Spiel " + gameId + " hat " + playerTrackInfoCache.get(gameId).size() + " Spieler (rundenstart)"); + + if (!allTracks.isEmpty()) { service.startRound(gameId, allTracks); } - // Trackinfos für alle Spieler sammeln - //Map> 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)"); +// broadcastRoundStart(gameId); } } @@ -165,8 +187,9 @@ private void broadcastRoundResult(String gameId) { broadcastToAll(gameId, msg); // Prüfe auf Gewinner + int score = 6; // Punktestand für den Gewinn String winner = scores.entrySet().stream() - .filter(e -> e.getValue() >= 6) + .filter(e -> e.getValue() >= score) .map(Map.Entry::getKey) .findFirst() .orElse(null); -- 2.43.0 From 04f374cfff3f39acd518fa1be536d4026052633e Mon Sep 17 00:00:00 2001 From: eric <3024947@stud.hs-mannheim.de> Date: Mon, 11 Aug 2025 22:35:29 +0200 Subject: [PATCH 02/10] songowner system war falsch implementiert --- src/main/java/eric/Roullette/service/GameService.java | 6 +++++- .../java/eric/Roullette/service/SpotifyAuthService.java | 2 +- .../eric/Roullette/websocket/GameWebSocketHandler.java | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/eric/Roullette/service/GameService.java b/src/main/java/eric/Roullette/service/GameService.java index 2161e7d..090541f 100644 --- a/src/main/java/eric/Roullette/service/GameService.java +++ b/src/main/java/eric/Roullette/service/GameService.java @@ -92,7 +92,11 @@ package eric.Roullette.service; Game g = getOrCreateGame(gameId); if (g.players().isEmpty()) throw new IllegalStateException("No players"); String owner = g.players().get(ThreadLocalRandom.current().nextInt(g.players().size())); - String song = uris.get(ThreadLocalRandom.current().nextInt(uris.size())); + List ownerTracks = g.playerTracks().getOrDefault(owner, List.of()); + if (ownerTracks.isEmpty()) throw new IllegalStateException("Owner hat keine Tracks"); + String song = ownerTracks.get(ThreadLocalRandom.current().nextInt(ownerTracks.size())); + + //String song = uris.get(ThreadLocalRandom.current().nextInt(uris.size())); Game updated = new Game(gameId, g.players(), g.scores(), owner, song, uris, g.playerTracks()); games.put(gameId, updated); return updated; diff --git a/src/main/java/eric/Roullette/service/SpotifyAuthService.java b/src/main/java/eric/Roullette/service/SpotifyAuthService.java index 9b28fcc..10797cf 100644 --- a/src/main/java/eric/Roullette/service/SpotifyAuthService.java +++ b/src/main/java/eric/Roullette/service/SpotifyAuthService.java @@ -81,7 +81,7 @@ public class SpotifyAuthService { public List getRecentTracks(String user) { System.out.println("Hole kürzlich gespielte Tracks für Benutzer: " + user); - int limit = 50; + int limit = 5; SpotifyApi userApi = userApis.get(user); if (userApi == null) { diff --git a/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java b/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java index 85dcc02..3bc9575 100644 --- a/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java +++ b/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java @@ -171,12 +171,19 @@ private void broadcastRoundResult(String gameId) { Map guesses = currentGuesses.remove(gameId); String owner = game.currentOwner(); +// System.out.println("Owner: " + owner); +// System.out.println("Guesses: " + guesses); +// System.out.println("Scores vor Auswertung: " + scores); + // Für jeden Tippenden Score anpassen for (Map.Entry entry : guesses.entrySet()) { String guesser = entry.getKey(); boolean correct = owner.equals(entry.getValue()); scores.merge(guesser, correct ? 3 : -1, Integer::sum); } +// System.out.println("Owner: " + owner); +// System.out.println("Guesses: " + guesses); +// System.out.println("Scores nach Auswertung: " + scores); String msg = JsonUtil.toJson(Map.of( "type", "round-result", -- 2.43.0 From 5f176bab0fad88014d9c12ed09ac338ec157794b Mon Sep 17 00:00:00 2001 From: eric <3024947@stud.hs-mannheim.de> Date: Tue, 12 Aug 2025 01:19:55 +0200 Subject: [PATCH 03/10] offset dynamisch gestalten --- .../Roullette/service/SpotifyAuthService.java | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/main/java/eric/Roullette/service/SpotifyAuthService.java b/src/main/java/eric/Roullette/service/SpotifyAuthService.java index 10797cf..19753d9 100644 --- a/src/main/java/eric/Roullette/service/SpotifyAuthService.java +++ b/src/main/java/eric/Roullette/service/SpotifyAuthService.java @@ -81,51 +81,51 @@ public class SpotifyAuthService { public List getRecentTracks(String user) { System.out.println("Hole kürzlich gespielte Tracks für Benutzer: " + user); - int limit = 5; + int limit = 50; SpotifyApi userApi = userApis.get(user); if (userApi == null) { - System.out.println("Kein SpotifyApi-Client für Benutzer gefunden: " + user); + System.err.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()); +// 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); + //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()); + //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("Erstelle Anfrage für kürzlich gespielte Tracks..."); +// if (userApi.getRefreshToken() == null) { +// System.err.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(); - } +// 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(); +// } - System.out.println("Access Token für Benutzer " + user + " ist gesetzt."); +// 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..."); +// System.out.println("Führe paging Anfrage aus..."); PagingCursorbased history = request.execute(); - System.out.println("Paging Anfrage erfolgreich ausgeführt."); +// System.out.println("Paging Anfrage erfolgreich ausgeführt."); // Überprüfe, ob die Ergebnisse leer sind - System.out.println("Überprüfe, ob Ergebnisse vorhanden 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..."); +// System.out.println("Verarbeite kürzlich gespielte Tracks..."); // Extrahiere die URIs der kürzlich gespielten Tracks List recentTracks = Arrays.stream(history.getItems()) .map(item -> item.getTrack().getUri()) @@ -134,6 +134,7 @@ public class SpotifyAuthService { System.out.println("Gefundene kürzlich gespielte Tracks: " + recentTracks.size()); if (recentTracks.size() < limit) { + int oldLimit = limit; int newLimit = limit - recentTracks.size(); // restliche songs mit kürzlich gespeicherten Tracks auffüllen List savedTracks = getSavedTracks(user, newLimit, 0); @@ -142,8 +143,9 @@ public class SpotifyAuthService { recentTracks = new java.util.ArrayList<>(recentTracks); recentTracks.addAll(savedTracks.subList(0, Math.min(newLimit, savedTracks.size()))); if(recentTracks.size() < limit){ + oldLimit = newLimit; newLimit = limit - recentTracks.size(); - List savedTracks2 = getSavedTracks(user, newLimit, 50); + List savedTracks2 = getSavedTracks(user, newLimit, oldLimit); savedTracks2.removeAll(recentTracks); recentTracks.addAll(savedTracks2.subList(0, Math.min(newLimit, savedTracks2.size()))); } -- 2.43.0 From b3eecfd10e9deb56a9a87da76dd74a17bd1d9fc9 Mon Sep 17 00:00:00 2001 From: eric <3024947@stud.hs-mannheim.de> Date: Tue, 12 Aug 2025 02:42:56 +0200 Subject: [PATCH 04/10] spiel endet nicht wenn zwei leute maximale punktzahl haben --- .../websocket/GameWebSocketHandler.java | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java b/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java index 3bc9575..e9a6d14 100644 --- a/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java +++ b/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java @@ -194,23 +194,24 @@ private void broadcastRoundResult(String gameId) { broadcastToAll(gameId, msg); // Prüfe auf Gewinner - int score = 6; // Punktestand für den Gewinn - String winner = scores.entrySet().stream() - .filter(e -> e.getValue() >= score) - .map(Map.Entry::getKey) - .findFirst() - .orElse(null); - if (winner != null) { - // Broadcast an alle, dass das Spiel vorbei ist - String winMsg = JsonUtil.toJson(Map.of( - "type", "game-end", - "winner", winner, - "scores", scores - )); - broadcastToAll(gameId, winMsg); - game.scores().replaceAll((user , pts) -> 0); // Reset Scores für alle Spieler + // Nur beenden, wenn EIN Spieler allein die höchste Punktzahl >= score hat + int score = 6; + int max = scores.values().stream().max(Integer::compareTo).orElse(0); + List topScorers = scores.entrySet().stream() + .filter(e -> e.getValue() == max && max >= score) + .map(Map.Entry::getKey) + .toList(); - } + if (topScorers.size() == 1) { + String winner = topScorers.get(0); + String winMsg = JsonUtil.toJson(Map.of( + "type", "game-end", + "winner", winner, + "scores", scores + )); + broadcastToAll(gameId, winMsg); + game.scores().replaceAll((user , pts) -> 0); // Reset Scores + } // else{ // // nächste Runde starten // // ... -- 2.43.0 From ed720301afa682b6475b1a8e3d7d14b7eb2dff2e Mon Sep 17 00:00:00 2001 From: eric <3024947@stud.hs-mannheim.de> Date: Tue, 12 Aug 2025 02:53:08 +0200 Subject: [PATCH 05/10] sync --- README.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e469917..57b9ebe 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,5 @@ -1. Limit manuell einstellbar ✔️ -2. Keine doppelten Songs ✔ ️ -3. songs die schon gespielt wurden nicht mehr spielen -4. songs die schon gespielt wurden nicht mehr in der Liste anzeigen -5. songs die schon gespielt wurden in einer extra Liste anzeigen -6. Um so frischer ein Song gehört worden ist desto eher soll er abgespielt werden -7. mehrere auswählbar, wenn man alle richtig hat einen extra punkt kriegen -8. verhindern dass 3 mal hinterander der gleicher owner bzw. das gleiche lied kommt -9. songs die schon gespielt wurden in extra liste anzeigen um doppelte songs zu vermeiden -(evtl. feste liste für spätere runden) -10. Schauen ob andere auch einen song in der liste haben für mehrere auswahlen \ No newline at end of file +Setup: + +Spotify developer app öffnen (free) +in user management jeden mitspieler mit email und username (anmeldename bei spotify) eintragen, geht seit mai nurnoch manuell da extended quota mode nurnoch für firmen ist +maven projekt compilieren, ClientID, redirectURL und Clientsecret auf server eintragen und Website als service ausführen \ No newline at end of file -- 2.43.0 From f53bafb918413dfe2f411991ab409d140b6608c3 Mon Sep 17 00:00:00 2001 From: eric <3024947@stud.hs-mannheim.de> Date: Tue, 12 Aug 2025 02:53:59 +0200 Subject: [PATCH 06/10] sync --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 57b9ebe..7366f92 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ Setup: -Spotify developer app öffnen (free) -in user management jeden mitspieler mit email und username (anmeldename bei spotify) eintragen, geht seit mai nurnoch manuell da extended quota mode nurnoch für firmen ist -maven projekt compilieren, ClientID, redirectURL und Clientsecret auf server eintragen und Website als service ausführen \ No newline at end of file +- Spotify developer app öffnen (free) +- in user management jeden mitspieler mit email und username (anmeldename bei spotify) eintragen, geht seit mai nurnoch manuell da extended quota mode nurnoch für firmen ist +- maven projekt compilieren, ClientID, redirectURL und Clientsecret auf server eintragen und Website als service ausführen \ No newline at end of file -- 2.43.0 From 257e10c3b3d32cd09a4fcf747253e43396ade60b Mon Sep 17 00:00:00 2001 From: eric <3024947@stud.hs-mannheim.de> Date: Tue, 12 Aug 2025 02:59:22 +0200 Subject: [PATCH 07/10] =?UTF-8?q?man=20kann=20"kein=20ger=C3=A4t"=20ausw?= =?UTF-8?q?=C3=A4hlen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/public/js/device-select.js | 9 +++++++++ src/main/resources/public/js/game.js | 5 +++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/resources/public/js/device-select.js b/src/main/resources/public/js/device-select.js index 487fe5d..5b1027a 100644 --- a/src/main/resources/public/js/device-select.js +++ b/src/main/resources/public/js/device-select.js @@ -18,6 +18,13 @@ area.appendChild(select); async function loadDevices() { select.innerHTML = ""; + + // Standardoption "Kein Gerät" + const noneOpt = document.createElement("option"); + noneOpt.value = ""; + noneOpt.textContent = "Kein Gerät"; + select.appendChild(noneOpt); + const devices = await fetchJson(`/api/spotify/devices?username=${encodeURIComponent(username)}`); if (!devices.length) { const opt = document.createElement("option"); @@ -34,6 +41,8 @@ async function loadDevices() { select.appendChild(opt); }); select.disabled = false; + select.value = ""; // "Kein Gerät" als Standard + } loadDevices(); diff --git a/src/main/resources/public/js/game.js b/src/main/resources/public/js/game.js index e8b1964..2f7ef9c 100644 --- a/src/main/resources/public/js/game.js +++ b/src/main/resources/public/js/game.js @@ -316,8 +316,9 @@ function handleGameEnd({ winner }) { // Spotify-Playback Funktion (unverändert) async function playOnSpotify(trackUri, username) { const deviceId = document.getElementById("deviceSelect")?.value; - if (!deviceId) { - alert("Bitte ein Wiedergabegerät auswählen!"); + if (deviceId === "") { + //alert("Bitte ein Wiedergabegerät auswählen!"); + //keine warnung i guess return; } try { -- 2.43.0 From 324345d9eb7231ff71626d61787259019ea55603 Mon Sep 17 00:00:00 2001 From: eric <3024947@stud.hs-mannheim.de> Date: Tue, 12 Aug 2025 02:59:28 +0200 Subject: [PATCH 08/10] =?UTF-8?q?Unn=C3=B6tig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index cb82fcd..59a12ba 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1 @@ -spring.application.name=Roullette - -spotify: -client-id: 70c36cd6e2d54ad0ba2e60ef6334bbc8 -client-secret: 116188574dd140eab1973e75c7e5ecfe -redirect-uri: https://www.davidmagkuchen.de/spotify/callback \ No newline at end of file +spring.application.name=Roullette \ No newline at end of file -- 2.43.0 From cb850804a73e3558f7229fc557e3055d37bf472b Mon Sep 17 00:00:00 2001 From: eric <3024947@stud.hs-mannheim.de> Date: Tue, 12 Aug 2025 03:04:37 +0200 Subject: [PATCH 09/10] =?UTF-8?q?entfernen=20aus=20der=20spielerliste=20we?= =?UTF-8?q?nn=20jemand=20verl=C3=A4sst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Roullette/websocket/GameWebSocketHandler.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java b/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java index e9a6d14..f8bcf1e 100644 --- a/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java +++ b/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java @@ -52,7 +52,19 @@ public class GameWebSocketHandler { // Connection geschlossen ws.onClose(ctx -> { String gameId = ctx.pathParam("gameId"); + String username = ctx.queryParam("username"); service.removeSession(gameId, ctx); + + // Spieler aus der Spielerliste entfernen + var game = service.getOrCreateGame(gameId); + if (username != null && game.players().contains(username)) { + game.players().remove(username); + game.scores().remove(username); + // Optional: auch die Tracks entfernen + game.playerTracks().remove(username); + } + service.broadcastPlayers(gameId); + }); // Eingehende Nachrichten (Guesses & Player-Requests) -- 2.43.0 From 4499f6b837d9cd14636d81d906dce03dcabbf324 Mon Sep 17 00:00:00 2001 From: Eric Paci <3014947@stud.hs-mannheim.de> Date: Tue, 12 Aug 2025 03:08:51 +0200 Subject: [PATCH 10/10] Delete README.md --- README.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 7366f92..0000000 --- a/README.md +++ /dev/null @@ -1,5 +0,0 @@ -Setup: - -- Spotify developer app öffnen (free) -- in user management jeden mitspieler mit email und username (anmeldename bei spotify) eintragen, geht seit mai nurnoch manuell da extended quota mode nurnoch für firmen ist -- maven projekt compilieren, ClientID, redirectURL und Clientsecret auf server eintragen und Website als service ausführen \ No newline at end of file -- 2.43.0