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 @@
+
+