diff --git a/radio/urls.py b/radio/urls.py index 4e7b1d7..518bc9d 100644 --- a/radio/urls.py +++ b/radio/urls.py @@ -20,4 +20,5 @@ urlpatterns = [ path('radio/focus/stats/', views.focus_stats, name='focus_stats'), path('radio/stream-player/', views.stream_player, name='stream_player'), path('radio/creamfresh-stream/', views.creamfresh_stream, name='creamfresh_stream'), + path('radio/creamfresh-feedback/', views.creamfresh_feedback, name='creamfresh_feedback'), ] diff --git a/radio/views.py b/radio/views.py index dfad8fd..12b26c3 100644 --- a/radio/views.py +++ b/radio/views.py @@ -622,6 +622,33 @@ def creamfresh_stream(request): return response +@csrf_exempt +@require_http_methods(['POST']) +def creamfresh_feedback(request): + """Relays a thumbs up/down to the creamfresh DJ's own /dj/feedback -- + same server-side-credentials reasoning as creamfresh_stream above.""" + try: + body = json.loads(request.body) + except (json.JSONDecodeError, ValueError): + return JsonResponse({'error': 'invalid JSON'}, status=400) + + vote = body.get('vote') + if vote not in ('up', 'down'): + return JsonResponse({'error': "vote must be 'up' or 'down'"}, status=400) + + try: + upstream = requests.post( + 'https://radio.creamfresh.xyz/dj/feedback', + auth=CREAMFRESH_AUTH, + json={'vote': vote}, + timeout=15, + ) + except requests.RequestException: + return JsonResponse({'error': 'upstream unreachable'}, status=502) + + return JsonResponse(upstream.json(), status=upstream.status_code, safe=False) + + def stream_player(request): url = request.GET.get('url', '').strip() name = request.GET.get('name', '').strip() diff --git a/static/js/app.js b/static/js/app.js index aea7906..fd3cf0f 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -9,6 +9,10 @@ // State // --------------------------------------------------------------------------- +// Hardcoded for now -- only creamfresh radio gets the vote buttons, since +// only its backend (the DJ) actually does anything with them. +const CREAMFRESH_RADIO_URL = 'https://diora.creamfresh.xyz/radio/creamfresh-stream/'; + let currentStation = null; // { url, name, id } | null let currentTrack = ''; let sseSource = null; @@ -176,6 +180,12 @@ function playStation(url, name, stationId) { $('play-stop-btn').classList.add('playing'); $('save-station-btn').style.display = ''; + const isCreamfresh = url === CREAMFRESH_RADIO_URL; + $('creamfresh-vote-up-btn').style.display = isCreamfresh ? '' : 'none'; + $('creamfresh-vote-down-btn').style.display = isCreamfresh ? '' : 'none'; + $('creamfresh-vote-up-btn').classList.remove('active'); + $('creamfresh-vote-down-btn').classList.remove('active'); + startMetadataSSE(url); startPlaySession(name, url); maybeShowDonationHint(url, name); @@ -220,6 +230,8 @@ function stopPlayback(clearStation = true) { $('play-stop-btn').textContent = '▶ Play'; $('play-stop-btn').classList.remove('playing'); $('save-station-btn').style.display = 'none'; + $('creamfresh-vote-up-btn').style.display = 'none'; + $('creamfresh-vote-down-btn').style.display = 'none'; $('affiliate-section').style.display = 'none'; stopPlaySession(); @@ -604,6 +616,36 @@ async function saveCurrentStation() { await saveStation(data); } +// --------------------------------------------------------------------------- +// creamfresh radio: DJ feedback (hardcoded to this one station, see +// CREAMFRESH_RADIO_URL above) +// --------------------------------------------------------------------------- + +async function creamfreshVote(direction) { + const upBtn = $('creamfresh-vote-up-btn'); + const downBtn = $('creamfresh-vote-down-btn'); + upBtn.disabled = true; + downBtn.disabled = true; + try { + const res = await fetch('/radio/creamfresh-feedback/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCsrfToken(), + }, + body: JSON.stringify({ vote: direction }), + }); + if (!res.ok) throw new Error(`feedback returned ${res.status}`); + upBtn.classList.toggle('active', direction === 'up'); + downBtn.classList.toggle('active', direction === 'down'); + } catch (err) { + console.warn('creamfresh vote failed', err); + } finally { + upBtn.disabled = false; + downBtn.disabled = false; + } +} + async function saveStation(station) { try { const res = await fetch('/radio/save/', { diff --git a/templates/radio/player.html b/templates/radio/player.html index 727f695..9681785 100644 --- a/templates/radio/player.html +++ b/templates/radio/player.html @@ -17,6 +17,8 @@ + +