From 9b8b92f2316bdb2eaf62bf54c0becdeda3f0b58d Mon Sep 17 00:00:00 2001
From: SimolZimol <70102430+SimolZimol@users.noreply.github.com>
Date: Sun, 1 Jun 2025 18:51:12 +0200
Subject: [PATCH 01/55] modified: app.py
---
app.py | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/app.py b/app.py
index e0d0daa..94e9c97 100644
--- a/app.py
+++ b/app.py
@@ -10,6 +10,7 @@ import random
from difflib import SequenceMatcher
import re
import json
+import unicodedata
app = Flask(__name__)
app.secret_key = os.getenv("SECRET_KEY")
@@ -53,13 +54,20 @@ def similarity(a, b):
return SequenceMatcher(None, a.lower(), b.lower()).ratio()
def clean_title(title):
+ # Unicode-Normalisierung (z.B. é -> e)
+ title = unicodedata.normalize('NFKD', title)
+ title = "".join([c for c in title if not unicodedata.combining(c)])
# Entfernt alles in () oder []
title = re.sub(r"(\s*[\(\[][^)\]]*[\)\]])", "", title)
# Vereinheitliche Apostrophen und Anführungszeichen
title = title.replace("’", "'").replace("‘", "'").replace("`", "'")
title = title.replace('"', '').replace("„", '').replace("“", '').replace("”", '')
- title = title.replace("'", "") # Optional: alle Apostrophen entfernen
- return title.strip()
+ title = title.replace("'", "")
+ # Entferne alle nicht-alphanumerischen Zeichen (außer Leerzeichen)
+ title = re.sub(r"[^a-zA-Z0-9äöüÄÖÜß ]", "", title)
+ # Mehrfache Leerzeichen zu einem
+ title = re.sub(r"\s+", " ", title)
+ return title.strip().lower()
def get_all_playlist_tracks(sp, playlist_id):
tracks = []
@@ -215,7 +223,8 @@ def check_answer():
game_mode = data.get('game_mode', 'artist')
playlist_id = data.get('playlist_id')
- if game_mode == 'title':
+ # Immer clean_title für title und artist
+ if game_mode in ['title', 'artist']:
guess = clean_title(guess)
correct_answer = clean_title(correct_answer)
From 9b894d768733f0e5f848df2bf28160a764027b48 Mon Sep 17 00:00:00 2001
From: SimolZimol <70102430+SimolZimol@users.noreply.github.com>
Date: Sun, 1 Jun 2025 18:58:28 +0200
Subject: [PATCH 02/55] modified: templates/quiz.html
---
templates/quiz.html | 69 ++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 62 insertions(+), 7 deletions(-)
diff --git a/templates/quiz.html b/templates/quiz.html
index 6ee08d9..71c8a32 100644
--- a/templates/quiz.html
+++ b/templates/quiz.html
@@ -133,8 +133,7 @@
console.log('Ready with Device ID', device_id);
document.getElementById('device_id').value = device_id;
- // Hole Optionen
- const playDuration = parseInt(getOption('playDuration', '0'), 10);
+ const playDuration = getPlayDuration();
const startPosition = getOption('startPosition', 'start');
let position_ms = 0;
if (startPosition === 'random') {
@@ -156,7 +155,7 @@
// Stoppe nach playDuration Sekunden (wenn nicht unendlich)
if (playDuration > 0) {
- setTimeout(() => {
+ window.quizifyTimeout = setTimeout(() => {
player.pause();
}, playDuration * 1000);
}
@@ -189,11 +188,11 @@
correctAnswer = "{{ track.artists[0].name }}";
document.getElementById('question-text').innerText = i18n.question_artist;
document.getElementById('answerInput').placeholder = i18n.input_artist;
- } else if (currentGameMode === 'title') {
+ } else if (currentGameMode === 'title' ) {
correctAnswer = "{{ track.name }}";
document.getElementById('question-text').innerText = i18n.question_title;
document.getElementById('answerInput').placeholder = i18n.input_title;
- } else if (currentGameMode === 'year') {
+ } else if (currentGameMode === 'year' ) {
correctAnswer = "{{ track.album.release_date[:4] }}";
document.getElementById('question-text').innerText = i18n.question_year;
document.getElementById('answerInput').placeholder = i18n.input_year;
@@ -318,9 +317,57 @@ function getOption(key, defaultValue) {
return localStorage.getItem(key) || defaultValue;
}
window.onload = function() {
- document.getElementById('playDuration').value = getOption('playDuration', '0');
+ const playDuration = getOption('playDuration', '0');
+ const sel = document.getElementById('playDuration');
+ const custom = document.getElementById('customDuration');
+ const label = document.getElementById('customDurationLabel');
+ if (['10','15','30','0'].includes(playDuration)) {
+ sel.value = playDuration;
+ custom.style.display = 'none';
+ label.style.display = 'none';
+ } else {
+ sel.value = 'custom';
+ custom.value = playDuration;
+ custom.style.display = '';
+ label.style.display = '';
+ }
document.getElementById('startPosition').value = getOption('startPosition', 'start');
};
+
+function onPlayDurationChange() {
+ const sel = document.getElementById('playDuration');
+ const custom = document.getElementById('customDuration');
+ const label = document.getElementById('customDurationLabel');
+ if (sel.value === 'custom') {
+ custom.style.display = '';
+ label.style.display = '';
+ setOption('playDuration', custom.value || '10');
+ } else {
+ custom.style.display = 'none';
+ label.style.display = 'none';
+ setOption('playDuration', sel.value);
+ }
+}
+
+function getPlayDuration() {
+ const sel = document.getElementById('playDuration');
+ if (sel.value === 'custom') {
+ return parseInt(document.getElementById('customDuration').value) || 10;
+ }
+ return parseInt(sel.value);
+}
+
+// "Nochmal X Sekunden"-Button Funktion
+function replayDuration() {
+ const playDuration = getPlayDuration();
+ if (window.spotifyPlayer) {
+ window.spotifyPlayer.resume();
+ if (window.quizifyTimeout) clearTimeout(window.quizifyTimeout);
+ window.quizifyTimeout = setTimeout(() => {
+ window.spotifyPlayer.pause();
+ }, playDuration * 1000);
+ }
+}
@@ -350,12 +397,15 @@ window.onload = function() {
{{ translations['play_duration'] }}:
-
+
10s
15s
30s
{{ translations['unlimited'] }}
+ Custom
+
+ s
{{ translations['start_position'] }}:
@@ -365,6 +415,11 @@ window.onload = function() {
+
+
+ {{ translations['play_duration'] }} +
+
+
{{ translations['pause'] }}
From 3dd0d56ed40b369ba2048cd559e802d4ba7d64a2 Mon Sep 17 00:00:00 2001
From: SimolZimol <70102430+SimolZimol@users.noreply.github.com>
Date: Sun, 1 Jun 2025 19:01:29 +0200
Subject: [PATCH 03/55] modified: templates/quiz.html
---
templates/quiz.html | 15 ---------------
1 file changed, 15 deletions(-)
diff --git a/templates/quiz.html b/templates/quiz.html
index 71c8a32..9606719 100644
--- a/templates/quiz.html
+++ b/templates/quiz.html
@@ -122,12 +122,6 @@
player.addListener('account_error', ({ message }) => { console.error(message); });
player.addListener('playback_error', ({ message }) => { console.error(message); });
- // Playback status updates
- player.addListener('player_state_changed', state => {
- console.log(state);
- updatePlayButton(state);
- });
-
// Ready
player.addListener('ready', ({ device_id }) => {
console.log('Ready with Device ID', device_id);
@@ -174,15 +168,6 @@
window.spotifyPlayer = player;
};
- function updatePlayButton(state) {
- let playButton = document.getElementById('playPauseBtn');
- if (state && !state.paused) {
- playButton.innerHTML = i18n.pause;
- } else {
- playButton.innerHTML = '▶️ Play';
- }
- }
-
function setCorrectAnswer() {
if (currentGameMode === 'artist') {
correctAnswer = "{{ track.artists[0].name }}";
From b2c9d818ece4236992c35d74f23e1e26e7f640f1 Mon Sep 17 00:00:00 2001
From: SimolZimol <70102430+SimolZimol@users.noreply.github.com>
Date: Sun, 1 Jun 2025 19:03:13 +0200
Subject: [PATCH 04/55] modified: templates/quiz.html
---
templates/quiz.html | 5 -----
1 file changed, 5 deletions(-)
diff --git a/templates/quiz.html b/templates/quiz.html
index 9606719..90398de 100644
--- a/templates/quiz.html
+++ b/templates/quiz.html
@@ -405,11 +405,6 @@ function replayDuration() {
{{ translations['play_duration'] }} +
-
-
- {{ translations['pause'] }}
-
-
From 54adafe5b471de5058623d3fbae17d223b76db3b Mon Sep 17 00:00:00 2001
From: SimolZimol <70102430+SimolZimol@users.noreply.github.com>
Date: Sun, 1 Jun 2025 19:05:02 +0200
Subject: [PATCH 05/55] modified: templates/quiz.html
---
templates/quiz.html | 138 +++++++++++++++++++++-----------------------
1 file changed, 65 insertions(+), 73 deletions(-)
diff --git a/templates/quiz.html b/templates/quiz.html
index 90398de..6f66a60 100644
--- a/templates/quiz.html
+++ b/templates/quiz.html
@@ -356,79 +356,71 @@ function replayDuration() {
-
-
- {{ translations['songs_in_playlist'] }} {{ total_questions }}
-
- {{ translations['score'] }}: {{ score }} / {{ answered if answered > 0 else 1 }}
- ({{ ((score / (answered if answered > 0 else 1)) * 100) | round(0) if answered > 0 else 0 }}{{ translations['percent'] }})
-
-
-
-
{{ translations['question_artist'] }}
-
-
-
-
-
-
- {{ translations['guess_artist'] }}
- {{ translations['guess_title'] }}
- {{ translations['guess_year'] }}
-
-
-
-
- {{ translations['play_duration'] }}:
-
- 10s
- 15s
- 30s
- {{ translations['unlimited'] }}
- Custom
-
-
- s
-
- {{ translations['start_position'] }}:
-
- {{ translations['start'] }}
- {{ translations['random'] }}
-
-
-
-
-
-
- {{ translations['play_duration'] }} +
-
-
-
-
-
-
-
- {% if game_mode == 'artist' %}
-
{{ translations['tip_artist'] }}
- {% elif game_mode == 'title' %}
-
{{ translations['tip_title'] }}
- {% elif game_mode == 'year' %}
-
{{ translations['tip_year'] }}
- {% endif %}
+
+
+
+ {{ translations['songs_in_playlist'] }} {{ total_questions }}
+
+ {{ translations['score'] }}: {{ score }} / {{ answered if answered > 0 else 1 }}
+ ({{ ((score / (answered if answered > 0 else 1)) * 100) | round(0) if answered > 0 else 0 }}{{ translations['percent'] }})
+
+
+
+
{{ translations['question_artist'] }}
+
+
+ {{ translations['guess_artist'] }}
+ {{ translations['guess_title'] }}
+ {{ translations['guess_year'] }}
+
+
+ {{ translations['play_duration'] }}:
+
+ 10s
+ 15s
+ 30s
+ {{ translations['unlimited'] }}
+ Custom
+
+
+ s
+
+ {{ translations['start_position'] }}:
+
+ {{ translations['start'] }}
+ {{ translations['random'] }}
+
+
+
+
+ {{ translations['play_duration'] }} +
+
+
+
+ {% if game_mode == 'artist' %}
+
{{ translations['tip_artist'] }}
+ {% elif game_mode == 'title' %}
+
{{ translations['tip_title'] }}
+ {% elif game_mode == 'year' %}
+
{{ translations['tip_year'] }}
+ {% endif %}
+
From 075df4dd588daea4b352a7e7e681ac1d1dd3b832 Mon Sep 17 00:00:00 2001
From: SimolZimol <70102430+SimolZimol@users.noreply.github.com>
Date: Sun, 1 Jun 2025 19:07:42 +0200
Subject: [PATCH 06/55] modified: templates/quiz.html
---
templates/quiz.html | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/templates/quiz.html b/templates/quiz.html
index 6f66a60..4df11c1 100644
--- a/templates/quiz.html
+++ b/templates/quiz.html
@@ -356,15 +356,16 @@ function replayDuration() {
-
+
{{ translations['songs_in_playlist'] }} {{ total_questions }}
@@ -409,7 +410,7 @@ function replayDuration() {
{{ translations['answer_button'] }}
-
+
{{ translations['next_question'] }}
From 6fc16f6733ff1451c83de191c43edf2dc19e6732 Mon Sep 17 00:00:00 2001
From: SimolZimol <70102430+SimolZimol@users.noreply.github.com>
Date: Sun, 1 Jun 2025 19:10:51 +0200
Subject: [PATCH 07/55] modified: locales/de-DE.json modified:
locales/en-EN.json modified: templates/quiz.html
---
locales/de-DE.json | 3 ++-
locales/en-EN.json | 3 ++-
templates/quiz.html | 6 ++++--
3 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/locales/de-DE.json b/locales/de-DE.json
index 70e82d1..77e95fe 100644
--- a/locales/de-DE.json
+++ b/locales/de-DE.json
@@ -37,5 +37,6 @@
"album": "Album",
"year": "Jahr",
"open_on_spotify": "Auf Spotify öffnen",
- "logout": "Abmelden"
+ "logout": "Abmelden",
+ "custom": "Anpassbar"
}
\ No newline at end of file
diff --git a/locales/en-EN.json b/locales/en-EN.json
index 3bbc3bd..99a007b 100644
--- a/locales/en-EN.json
+++ b/locales/en-EN.json
@@ -37,5 +37,6 @@
"album": "Album",
"year": "Year",
"open_on_spotify": "Open on Spotify",
- "logout": "Logout"
+ "logout": "Logout",
+ "custom": "custom"
}
\ No newline at end of file
diff --git a/templates/quiz.html b/templates/quiz.html
index 4df11c1..7c27e54 100644
--- a/templates/quiz.html
+++ b/templates/quiz.html
@@ -365,7 +365,9 @@ function replayDuration() {
max-width: 500px;
width: 100%;
color: #fff;
- margin: 40px 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
">
{{ translations['songs_in_playlist'] }} {{ total_questions }}
@@ -391,7 +393,7 @@ function replayDuration() {
15s
30s
{{ translations['unlimited'] }}
-
Custom
+
{{ translations['custom'] }}
s
From 29819a192d09ca8a50163d1589dc5a66a983f7b0 Mon Sep 17 00:00:00 2001
From: SimolZimol <70102430+SimolZimol@users.noreply.github.com>
Date: Sun, 1 Jun 2025 19:13:26 +0200
Subject: [PATCH 08/55] modified: templates/quiz.html
---
templates/quiz.html | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/templates/quiz.html b/templates/quiz.html
index 7c27e54..742cf53 100644
--- a/templates/quiz.html
+++ b/templates/quiz.html
@@ -5,6 +5,10 @@
+
+
+
+
{{ translations['quiz_title'] }} – Game Modes
+
+
Battle Mode
+
Coming soon!
+
Party Mode
+
Coming soon!
+
+
+
+
\ No newline at end of file
diff --git a/templates/playlists.html b/templates/playlists.html
index 5609b73..3ab8bfe 100644
--- a/templates/playlists.html
+++ b/templates/playlists.html
@@ -61,7 +61,7 @@
{{ translations['choose_playlist'] }}
From 7f357cd4da1f308b0a66e73d32c2c88bb8eb0ab1 Mon Sep 17 00:00:00 2001
From: SimolZimol <70102430+SimolZimol@users.noreply.github.com>
Date: Wed, 4 Jun 2025 16:51:53 +0200
Subject: [PATCH 15/55] modified: app.py
---
app.py | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/app.py b/app.py
index 9e52c1b..dea329f 100644
--- a/app.py
+++ b/app.py
@@ -14,7 +14,6 @@ import unicodedata
app = Flask(__name__)
app.secret_key = os.getenv("SECRET_KEY")
-app.config['SESSION_PERMANENT'] = False
# Erweiterte Berechtigungen für Web Playback SDK
SCOPE = "user-library-read playlist-read-private streaming user-read-email user-read-private"
@@ -36,15 +35,13 @@ def get_translations():
def get_spotify_client():
token_info = session.get("token_info", None)
if not token_info:
- # Kein Token, redirect handled elsewhere
return None
- # Prüfen, ob Token abgelaufen ist
sp_oauth = SpotifyOAuth(
client_id=os.getenv("SPOTIPY_CLIENT_ID"),
client_secret=os.getenv("SPOTIPY_CLIENT_SECRET"),
redirect_uri=os.getenv("SPOTIPY_REDIRECT_URI"),
scope=SCOPE,
- cache_path=".cache"
+ cache_path=None # <--- wichtig!
)
if sp_oauth.is_token_expired(token_info):
token_info = sp_oauth.refresh_access_token(token_info['refresh_token'])
From 7f6f52c7832371c85221eaab55ff9092aa6ec6aa Mon Sep 17 00:00:00 2001
From: SimolZimol <70102430+SimolZimol@users.noreply.github.com>
Date: Wed, 4 Jun 2025 17:22:23 +0200
Subject: [PATCH 16/55] modified: app.py modified:
templates/playlists.html
---
app.py | 23 +++++++++++++++++++++++
templates/playlists.html | 17 +++++++++++++++++
2 files changed, 40 insertions(+)
diff --git a/app.py b/app.py
index dea329f..370dd27 100644
--- a/app.py
+++ b/app.py
@@ -11,6 +11,8 @@ from difflib import SequenceMatcher
import re
import json
import unicodedata
+import secrets
+from datetime import datetime, timedelta
app = Flask(__name__)
app.secret_key = os.getenv("SECRET_KEY")
@@ -295,5 +297,26 @@ def reset_quiz(playlist_id):
def gamemodes(playlist_id):
return render_template("gamemodes.html", playlist_id=playlist_id, translations=get_translations())
+invites = {} # {token: expiry_datetime}
+
+@app.route("/invite")
+def invite():
+ duration = int(request.args.get("duration", 60)) # Minuten
+ token = secrets.token_urlsafe(16)
+ expires = datetime.utcnow() + timedelta(minutes=duration)
+ invites[token] = expires
+ invite_link = url_for('guest_join', token=token, _external=True)
+ return render_template("invite.html", invite_link=invite_link, expires=expires)
+
+@app.route("/invite/
")
+def guest_join(token):
+ expires = invites.get(token)
+ if not expires or expires < datetime.utcnow():
+ return "Invite link expired or invalid.", 403
+ # Setze ein Cookie, damit der Gast als eingeladener User erkannt wird (optional)
+ resp = redirect(url_for('login'))
+ resp.set_cookie("guest_token", token, max_age=60*60) # 1 Stunde gültig
+ return resp
+
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
diff --git a/templates/playlists.html b/templates/playlists.html
index 3ab8bfe..05fddd2 100644
--- a/templates/playlists.html
+++ b/templates/playlists.html
@@ -51,6 +51,22 @@
background-color: #1ed760;
transform: scale(1.04);
}
+ .btn {
+ display: inline-block;
+ padding: 10px 20px;
+ background-color: #007bff;
+ color: #fff;
+ border-radius: 5px;
+ text-decoration: none;
+ font-size: 1em;
+ font-weight: bold;
+ transition: background 0.2s, transform 0.2s;
+ margin-top: 20px;
+ }
+ .btn:hover {
+ background-color: #0056b3;
+ transform: scale(1.05);
+ }
@@ -64,6 +80,7 @@
{{ pl.name }}
{% endfor %}
+ Gast einladen (1h-Link)