Add thumbs up/down for creamfresh radio, hardcoded to that station
Relays to the DJ's own /dj/feedback via the same server-side-auth proxy pattern as the stream itself, so the browser never needs the station's credentials. Buttons only show when the currently playing station is creamfresh radio (matched by URL against a hardcoded constant) -- no other station's backend does anything with a vote, so no other station gets the buttons yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Drq8o4HfFmwkXz2AgvaQ4p
This commit is contained in:
parent
9228906db8
commit
171560c71e
4 changed files with 72 additions and 0 deletions
|
|
@ -20,4 +20,5 @@ urlpatterns = [
|
||||||
path('radio/focus/stats/', views.focus_stats, name='focus_stats'),
|
path('radio/focus/stats/', views.focus_stats, name='focus_stats'),
|
||||||
path('radio/stream-player/', views.stream_player, name='stream_player'),
|
path('radio/stream-player/', views.stream_player, name='stream_player'),
|
||||||
path('radio/creamfresh-stream/', views.creamfresh_stream, name='creamfresh_stream'),
|
path('radio/creamfresh-stream/', views.creamfresh_stream, name='creamfresh_stream'),
|
||||||
|
path('radio/creamfresh-feedback/', views.creamfresh_feedback, name='creamfresh_feedback'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -622,6 +622,33 @@ def creamfresh_stream(request):
|
||||||
return response
|
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):
|
def stream_player(request):
|
||||||
url = request.GET.get('url', '').strip()
|
url = request.GET.get('url', '').strip()
|
||||||
name = request.GET.get('name', '').strip()
|
name = request.GET.get('name', '').strip()
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@
|
||||||
// State
|
// 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 currentStation = null; // { url, name, id } | null
|
||||||
let currentTrack = '';
|
let currentTrack = '';
|
||||||
let sseSource = null;
|
let sseSource = null;
|
||||||
|
|
@ -176,6 +180,12 @@ function playStation(url, name, stationId) {
|
||||||
$('play-stop-btn').classList.add('playing');
|
$('play-stop-btn').classList.add('playing');
|
||||||
$('save-station-btn').style.display = '';
|
$('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);
|
startMetadataSSE(url);
|
||||||
startPlaySession(name, url);
|
startPlaySession(name, url);
|
||||||
maybeShowDonationHint(url, name);
|
maybeShowDonationHint(url, name);
|
||||||
|
|
@ -220,6 +230,8 @@ function stopPlayback(clearStation = true) {
|
||||||
$('play-stop-btn').textContent = '▶ Play';
|
$('play-stop-btn').textContent = '▶ Play';
|
||||||
$('play-stop-btn').classList.remove('playing');
|
$('play-stop-btn').classList.remove('playing');
|
||||||
$('save-station-btn').style.display = 'none';
|
$('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';
|
$('affiliate-section').style.display = 'none';
|
||||||
|
|
||||||
stopPlaySession();
|
stopPlaySession();
|
||||||
|
|
@ -604,6 +616,36 @@ async function saveCurrentStation() {
|
||||||
await saveStation(data);
|
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) {
|
async function saveStation(station) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/radio/save/', {
|
const res = await fetch('/radio/save/', {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
<input type="number" id="volume-num" min="0" max="255" value="204" class="volume-num">
|
<input type="number" id="volume-num" min="0" max="255" value="204" class="volume-num">
|
||||||
</label>
|
</label>
|
||||||
<button class="btn btn-save" id="save-station-btn" style="display:none;" onclick="saveCurrentStation()">★ Save</button>
|
<button class="btn btn-save" id="save-station-btn" style="display:none;" onclick="saveCurrentStation()">★ Save</button>
|
||||||
|
<button class="btn-icon" id="creamfresh-vote-up-btn" style="display:none;" onclick="creamfreshVote('up')" title="Gefällt mir">👍</button>
|
||||||
|
<button class="btn-icon" id="creamfresh-vote-down-btn" style="display:none;" onclick="creamfreshVote('down')" title="Gefällt mir nicht">👎</button>
|
||||||
<button class="btn-icon" id="dnd-btn" onclick="toggleDND()" title="Focus mode (hides UI, press Esc to exit)">⊙</button>
|
<button class="btn-icon" id="dnd-btn" onclick="toggleDND()" title="Focus mode (hides UI, press Esc to exit)">⊙</button>
|
||||||
<button class="btn-icon" id="focus-station-btn" onclick="openRadioSidebar()" title="Radio">◉</button>
|
<button class="btn-icon" id="focus-station-btn" onclick="openRadioSidebar()" title="Radio">◉</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue