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
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
import random
|
||||
from difflib import SequenceMatcher
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = os.getenv("SECRET_KEY")
|
||||
@@ -23,6 +24,9 @@ def get_spotify_client():
|
||||
cache_path=".cache"
|
||||
))
|
||||
|
||||
def similarity(a, b):
|
||||
return SequenceMatcher(None, a.lower(), b.lower()).ratio()
|
||||
|
||||
@app.route("/")
|
||||
def home():
|
||||
return render_template("login.html")
|
||||
@@ -60,6 +64,8 @@ def playlists():
|
||||
|
||||
@app.route("/quiz/<playlist_id>")
|
||||
def quiz(playlist_id):
|
||||
game_mode = request.args.get('mode', 'artist')
|
||||
|
||||
sp = get_spotify_client()
|
||||
items = sp.playlist_items(playlist_id, additional_types=["track"])["items"]
|
||||
|
||||
@@ -77,7 +83,75 @@ def quiz(playlist_id):
|
||||
# Zugriff auf 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"])
|
||||
def play_track():
|
||||
|
||||
@@ -4,7 +4,100 @@
|
||||
<title>Musik Quiz</title>
|
||||
<!-- Spotify Web Playback SDK -->
|
||||
<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>
|
||||
// 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 = () => {
|
||||
const token = '{{ access_token }}';
|
||||
const player = new Spotify.Player({
|
||||
@@ -22,7 +115,7 @@
|
||||
// Playback status updates
|
||||
player.addListener('player_state_changed', state => {
|
||||
console.log(state);
|
||||
// Hier könntest du UI-Updates basierend auf dem Playback-Status hinzufügen
|
||||
updatePlayButton(state);
|
||||
});
|
||||
|
||||
// Ready
|
||||
@@ -41,6 +134,9 @@
|
||||
}).catch(error => {
|
||||
console.error('Error starting playback:', error);
|
||||
});
|
||||
|
||||
// Korrekte Antwort basierend auf dem Spielmodus setzen
|
||||
setCorrectAnswer();
|
||||
});
|
||||
|
||||
// Not Ready
|
||||
@@ -50,54 +146,177 @@
|
||||
|
||||
// Connect to the player!
|
||||
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>
|
||||
<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>
|
||||
<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 -->
|
||||
<input type="hidden" id="device_id" value="">
|
||||
|
||||
<!-- Player Controls -->
|
||||
<div class="controls">
|
||||
<button class="btn" onclick="togglePlay()">Play/Pause</button>
|
||||
<!-- Spielmodi -->
|
||||
<div class="game-modes">
|
||||
<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>
|
||||
|
||||
<p><strong>Antwort:</strong> {{ track.artists[0].name }} (für Demo-Zwecke)</p>
|
||||
<a href="/quiz/{{ playlist_id }}" class="btn">Neue Frage</a>
|
||||
|
||||
<script>
|
||||
let isPlaying = true;
|
||||
|
||||
<!-- Player Controls -->
|
||||
<div class="controls" style="text-align: center;">
|
||||
<button id="playPauseBtn" class="btn" onclick="togglePlay()">⏸️ Pause</button>
|
||||
</div>
|
||||
|
||||
<!-- Antwort-Eingabe -->
|
||||
<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() {
|
||||
fetch('/toggle_playback', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
device_id: document.getElementById('device_id').value
|
||||
})
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<!-- Suchergebnisse -->
|
||||
<div id="searchResults" class="search-results"></div>
|
||||
|
||||
<!-- Ergebnis-Anzeige -->
|
||||
<div id="resultContainer" class="result-container"></div>
|
||||
|
||||
<!-- Nächste Frage Button, wird nach Antwort angezeigt -->
|
||||
<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 -->
|
||||
<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>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user