modified: app.py
modified: templates/quiz.html
This commit is contained in:
76
app.py
76
app.py
@@ -7,6 +7,7 @@ import os
|
|||||||
import spotipy
|
import spotipy
|
||||||
from spotipy.oauth2 import SpotifyOAuth
|
from spotipy.oauth2 import SpotifyOAuth
|
||||||
import random
|
import random
|
||||||
|
from difflib import SequenceMatcher
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = os.getenv("SECRET_KEY")
|
app.secret_key = os.getenv("SECRET_KEY")
|
||||||
@@ -23,6 +24,9 @@ def get_spotify_client():
|
|||||||
cache_path=".cache"
|
cache_path=".cache"
|
||||||
))
|
))
|
||||||
|
|
||||||
|
def similarity(a, b):
|
||||||
|
return SequenceMatcher(None, a.lower(), b.lower()).ratio()
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def home():
|
def home():
|
||||||
return render_template("login.html")
|
return render_template("login.html")
|
||||||
@@ -60,6 +64,8 @@ def playlists():
|
|||||||
|
|
||||||
@app.route("/quiz/<playlist_id>")
|
@app.route("/quiz/<playlist_id>")
|
||||||
def quiz(playlist_id):
|
def quiz(playlist_id):
|
||||||
|
game_mode = request.args.get('mode', 'artist')
|
||||||
|
|
||||||
sp = get_spotify_client()
|
sp = get_spotify_client()
|
||||||
items = sp.playlist_items(playlist_id, additional_types=["track"])["items"]
|
items = sp.playlist_items(playlist_id, additional_types=["track"])["items"]
|
||||||
|
|
||||||
@@ -77,7 +83,75 @@ def quiz(playlist_id):
|
|||||||
# Zugriff auf access_token
|
# Zugriff auf access_token
|
||||||
access_token = token_info['access_token']
|
access_token = token_info['access_token']
|
||||||
|
|
||||||
return render_template("quiz.html", track=track, access_token=access_token, playlist_id=playlist_id)
|
# Alle Tracks für die Suchfunktion (für title und artist mode)
|
||||||
|
all_tracks = []
|
||||||
|
for item in tracks:
|
||||||
|
track_info = {
|
||||||
|
"id": item["id"],
|
||||||
|
"name": item["name"],
|
||||||
|
"artist": item["artists"][0]["name"],
|
||||||
|
"uri": item["uri"],
|
||||||
|
"release_date": item.get("album", {}).get("release_date", "Unbekannt")[:4] # Nur das Jahr
|
||||||
|
}
|
||||||
|
all_tracks.append(track_info)
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"quiz.html",
|
||||||
|
track=track,
|
||||||
|
access_token=access_token,
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
game_mode=game_mode,
|
||||||
|
all_tracks=all_tracks
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.route("/search_track", methods=["POST"])
|
||||||
|
def search_track():
|
||||||
|
data = request.json
|
||||||
|
query = data.get('query', '').lower()
|
||||||
|
all_tracks = data.get('all_tracks', [])
|
||||||
|
|
||||||
|
if not query or not all_tracks:
|
||||||
|
return {"results": []}
|
||||||
|
|
||||||
|
# Tracks nach Ähnlichkeit filtern (80% Übereinstimmung)
|
||||||
|
results = []
|
||||||
|
for track in all_tracks:
|
||||||
|
name_similarity = similarity(query, track["name"])
|
||||||
|
artist_similarity = similarity(query, track["artist"])
|
||||||
|
|
||||||
|
# Wenn Name oder Künstler zu 80% übereinstimmt
|
||||||
|
if name_similarity >= 0.8 or artist_similarity >= 0.8:
|
||||||
|
results.append({
|
||||||
|
"id": track["id"],
|
||||||
|
"name": track["name"],
|
||||||
|
"artist": track["artist"],
|
||||||
|
"uri": track["uri"],
|
||||||
|
"similarity": max(name_similarity, artist_similarity)
|
||||||
|
})
|
||||||
|
|
||||||
|
# Nach Ähnlichkeit sortieren
|
||||||
|
results.sort(key=lambda x: x["similarity"], reverse=True)
|
||||||
|
|
||||||
|
return {"results": results}
|
||||||
|
|
||||||
|
@app.route("/check_answer", methods=["POST"])
|
||||||
|
def check_answer():
|
||||||
|
data = request.json
|
||||||
|
guess = data.get('guess', '').lower()
|
||||||
|
correct_answer = data.get('correct_answer', '').lower()
|
||||||
|
game_mode = data.get('game_mode', 'artist')
|
||||||
|
|
||||||
|
# Bei Jahr-Modus: Exakte Übereinstimmung prüfen
|
||||||
|
if game_mode == 'year':
|
||||||
|
is_correct = guess == correct_answer
|
||||||
|
else:
|
||||||
|
# Bei anderen Modi: Ähnlichkeitsprüfung (90% Übereinstimmung gilt als korrekt)
|
||||||
|
is_correct = similarity(guess, correct_answer) >= 0.9
|
||||||
|
|
||||||
|
return {
|
||||||
|
"correct": is_correct,
|
||||||
|
"correct_answer": correct_answer
|
||||||
|
}
|
||||||
|
|
||||||
@app.route("/play_track", methods=["POST"])
|
@app.route("/play_track", methods=["POST"])
|
||||||
def play_track():
|
def play_track():
|
||||||
|
|||||||
@@ -4,7 +4,100 @@
|
|||||||
<title>Musik Quiz</title>
|
<title>Musik Quiz</title>
|
||||||
<!-- Spotify Web Playback SDK -->
|
<!-- Spotify Web Playback SDK -->
|
||||||
<script src="https://sdk.scdn.co/spotify-player.js"></script>
|
<script src="https://sdk.scdn.co/spotify-player.js"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.controls {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
padding: 10px 15px;
|
||||||
|
margin: 5px;
|
||||||
|
background-color: #1DB954;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.btn:hover {
|
||||||
|
background-color: #1ed760;
|
||||||
|
}
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: #535353;
|
||||||
|
}
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background-color: #7b7b7b;
|
||||||
|
}
|
||||||
|
.btn-success {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
}
|
||||||
|
.btn-danger {
|
||||||
|
background-color: #f44336;
|
||||||
|
}
|
||||||
|
.game-modes {
|
||||||
|
margin: 20px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.search-results {
|
||||||
|
margin-top: 15px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.search-item {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.search-item:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.result-container {
|
||||||
|
margin: 20px 0;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.correct {
|
||||||
|
background-color: #e8f5e9;
|
||||||
|
border: 1px solid #4CAF50;
|
||||||
|
}
|
||||||
|
.incorrect {
|
||||||
|
background-color: #ffebee;
|
||||||
|
border: 1px solid #f44336;
|
||||||
|
}
|
||||||
|
input[type="text"], input[type="number"] {
|
||||||
|
padding: 10px;
|
||||||
|
width: 300px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.hint-container {
|
||||||
|
margin: 15px 0;
|
||||||
|
font-style: italic;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<script>
|
<script>
|
||||||
|
// Speichern aller Tracks für die Suche
|
||||||
|
let allTracks = {{ all_tracks|tojson }};
|
||||||
|
let currentGameMode = "{{ game_mode }}";
|
||||||
|
let correctAnswer = "";
|
||||||
|
|
||||||
|
// Wird aufgerufen, wenn Spotify Web Playback SDK geladen ist
|
||||||
window.onSpotifyWebPlaybackSDKReady = () => {
|
window.onSpotifyWebPlaybackSDKReady = () => {
|
||||||
const token = '{{ access_token }}';
|
const token = '{{ access_token }}';
|
||||||
const player = new Spotify.Player({
|
const player = new Spotify.Player({
|
||||||
@@ -22,7 +115,7 @@
|
|||||||
// Playback status updates
|
// Playback status updates
|
||||||
player.addListener('player_state_changed', state => {
|
player.addListener('player_state_changed', state => {
|
||||||
console.log(state);
|
console.log(state);
|
||||||
// Hier könntest du UI-Updates basierend auf dem Playback-Status hinzufügen
|
updatePlayButton(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ready
|
// Ready
|
||||||
@@ -41,6 +134,9 @@
|
|||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('Error starting playback:', error);
|
console.error('Error starting playback:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Korrekte Antwort basierend auf dem Spielmodus setzen
|
||||||
|
setCorrectAnswer();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Not Ready
|
// Not Ready
|
||||||
@@ -50,54 +146,177 @@
|
|||||||
|
|
||||||
// Connect to the player!
|
// Connect to the player!
|
||||||
player.connect();
|
player.connect();
|
||||||
|
|
||||||
|
// Globale Referenz zum Player für andere Funktionen
|
||||||
|
window.spotifyPlayer = player;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function updatePlayButton(state) {
|
||||||
|
let playButton = document.getElementById('playPauseBtn');
|
||||||
|
if (state && !state.paused) {
|
||||||
|
playButton.innerHTML = '⏸️ Pause';
|
||||||
|
} else {
|
||||||
|
playButton.innerHTML = '▶️ Play';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCorrectAnswer() {
|
||||||
|
if (currentGameMode === 'artist') {
|
||||||
|
correctAnswer = "{{ track.artists[0].name }}";
|
||||||
|
document.getElementById('question-text').innerText = "Wer ist der Künstler dieses Songs?";
|
||||||
|
document.getElementById('answerInput').placeholder = "Künstlername eingeben...";
|
||||||
|
} else if (currentGameMode === 'title') {
|
||||||
|
correctAnswer = "{{ track.name }}";
|
||||||
|
document.getElementById('question-text').innerText = "Wie heißt dieser Song?";
|
||||||
|
document.getElementById('answerInput').placeholder = "Songtitel eingeben...";
|
||||||
|
} else if (currentGameMode === 'year') {
|
||||||
|
correctAnswer = "{{ track.album.release_date[:4] }}";
|
||||||
|
document.getElementById('question-text').innerText = "In welchem Jahr wurde dieser Song veröffentlicht?";
|
||||||
|
document.getElementById('answerInput').placeholder = "Jahr eingeben...";
|
||||||
|
document.getElementById('answerInput').type = "number";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePlay() {
|
||||||
|
const deviceId = document.getElementById('device_id').value;
|
||||||
|
|
||||||
|
fetch('/toggle_playback', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ device_id: deviceId })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error toggling playback:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchTracks() {
|
||||||
|
const query = document.getElementById('answerInput').value;
|
||||||
|
if (query.length < 2) {
|
||||||
|
document.getElementById('searchResults').style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('/search_track', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
query: query,
|
||||||
|
all_tracks: allTracks
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const resultsContainer = document.getElementById('searchResults');
|
||||||
|
resultsContainer.innerHTML = '';
|
||||||
|
|
||||||
|
if (data.results.length === 0) {
|
||||||
|
resultsContainer.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.results.forEach(result => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'search-item';
|
||||||
|
item.innerHTML = `<strong>${result.name}</strong> - ${result.artist}`;
|
||||||
|
item.onclick = function() {
|
||||||
|
document.getElementById('answerInput').value =
|
||||||
|
currentGameMode === 'artist' ? result.artist : result.name;
|
||||||
|
resultsContainer.style.display = 'none';
|
||||||
|
};
|
||||||
|
resultsContainer.appendChild(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
resultsContainer.style.display = 'block';
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error searching tracks:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAnswer() {
|
||||||
|
const guess = document.getElementById('answerInput').value;
|
||||||
|
if (!guess) return;
|
||||||
|
|
||||||
|
fetch('/check_answer', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
guess: guess,
|
||||||
|
correct_answer: correctAnswer,
|
||||||
|
game_mode: currentGameMode
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const resultContainer = document.getElementById('resultContainer');
|
||||||
|
resultContainer.style.display = 'block';
|
||||||
|
|
||||||
|
if (data.correct) {
|
||||||
|
resultContainer.className = 'result-container correct';
|
||||||
|
resultContainer.innerHTML = `<h3>Richtig! 🎉</h3>`;
|
||||||
|
} else {
|
||||||
|
resultContainer.className = 'result-container incorrect';
|
||||||
|
resultContainer.innerHTML = `<h3>Falsch 😢</h3>
|
||||||
|
<p>Die richtige Antwort ist: <strong>${data.correct_answer}</strong></p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zeige den "Nächste Frage" Button
|
||||||
|
document.getElementById('nextQuestionBtn').style.display = 'inline-block';
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error checking answer:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchGameMode(mode) {
|
||||||
|
window.location.href = `/quiz/{{ playlist_id }}?mode=${mode}`;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
|
||||||
.controls {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
.btn {
|
|
||||||
padding: 10px 15px;
|
|
||||||
margin: 5px;
|
|
||||||
background-color: #1DB954;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.btn:hover {
|
|
||||||
background-color: #1ed760;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>Wer ist der Künstler dieses Songs?</h2>
|
<h2 id="question-text">Wer ist der Künstler dieses Songs?</h2>
|
||||||
|
|
||||||
<!-- Verstecktes Feld für device_id -->
|
<!-- Verstecktes Feld für device_id -->
|
||||||
<input type="hidden" id="device_id" value="">
|
<input type="hidden" id="device_id" value="">
|
||||||
|
|
||||||
<!-- Player Controls -->
|
<!-- Spielmodi -->
|
||||||
<div class="controls">
|
<div class="game-modes">
|
||||||
<button class="btn" onclick="togglePlay()">Play/Pause</button>
|
<button class="btn {{ 'btn-success' if game_mode == 'artist' else 'btn-secondary' }}" onclick="switchGameMode('artist')">Künstler erraten</button>
|
||||||
|
<button class="btn {{ 'btn-success' if game_mode == 'title' else 'btn-secondary' }}" onclick="switchGameMode('title')">Titel erraten</button>
|
||||||
|
<button class="btn {{ 'btn-success' if game_mode == 'year' else 'btn-secondary' }}" onclick="switchGameMode('year')">Jahr erraten</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p><strong>Antwort:</strong> {{ track.artists[0].name }} (für Demo-Zwecke)</p>
|
<!-- Player Controls -->
|
||||||
<a href="/quiz/{{ playlist_id }}" class="btn">Neue Frage</a>
|
<div class="controls" style="text-align: center;">
|
||||||
|
<button id="playPauseBtn" class="btn" onclick="togglePlay()">⏸️ Pause</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<!-- Antwort-Eingabe -->
|
||||||
let isPlaying = true;
|
<div style="text-align: center; margin-top: 30px;">
|
||||||
|
<input type="text" id="answerInput" placeholder="Gib deine Antwort ein..." oninput="searchTracks()">
|
||||||
|
<button class="btn" onclick="checkAnswer()">Antworten</button>
|
||||||
|
|
||||||
function togglePlay() {
|
<!-- Suchergebnisse -->
|
||||||
fetch('/toggle_playback', {
|
<div id="searchResults" class="search-results"></div>
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
<!-- Ergebnis-Anzeige -->
|
||||||
'Content-Type': 'application/json',
|
<div id="resultContainer" class="result-container"></div>
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
<!-- Nächste Frage Button, wird nach Antwort angezeigt -->
|
||||||
device_id: document.getElementById('device_id').value
|
<a id="nextQuestionBtn" href="/quiz/{{ playlist_id }}?mode={{ game_mode }}" class="btn" style="display: none;">Nächste Frage</a>
|
||||||
})
|
</div>
|
||||||
});
|
|
||||||
}
|
<!-- Hilfe-Text je nach Modus -->
|
||||||
</script>
|
<div class="hint-container">
|
||||||
|
{% if game_mode == 'artist' %}
|
||||||
|
<p>Tipp: Gib den Namen des Künstlers ein, der diesen Song performt.</p>
|
||||||
|
{% elif game_mode == 'title' %}
|
||||||
|
<p>Tipp: Gib den Titel des Songs ein, den du gerade hörst.</p>
|
||||||
|
{% elif game_mode == 'year' %}
|
||||||
|
<p>Tipp: Gib das Erscheinungsjahr des Songs ein.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user