Files
weirsoe-party-protocol/party/views.py
T
agw 554c924097
CI / test (push) Successful in 53s
Nørdespørgsmål, spørgsmålspakker, flere lydvarianter og natlig forvarmning
- 87 nye spørgsmål med kilder: Star Wars (inkl. Legends), Harry Potter,
  Ringenes Herre, Lægevidenskab, E-faktura, IT-sikkerhed, Computerhistorie
- Spørgsmålspakker pr. kategori (familie, nørd, arbejde); værten vælger
  Alle/Familie/Nørd, og arbejdspakken kun under "Flere valg"
- Lydeffekter i flere udgaver, der vælges tilfældigt (aldrig samme to gange
  i træk); jingles og tænkemusik til de nye kategorier; ekstra tænke- og
  afsløringsmusik
- Musik hentes og afkodes først, når den skal spilles, og slippes igen
- tts_warm --budget: forvarmer spørgsmål, forklaringer og kategori-replikker
  (de næste spørgsmål først) inden for et tegnbudget

Claude-Session: https://claude.ai/code/session_01JmhJG6JnhXfzNiKv8f2vHu
2026-10-03 18:58:31 +02:00

101 lines
3.2 KiB
Python

import io
import re
import segno
from asgiref.sync import sync_to_async
from django.conf import settings
from django.http import FileResponse, Http404, HttpResponse, HttpResponseForbidden, JsonResponse
from django.shortcuts import redirect, render
from django.views.decorators.http import require_GET, require_POST
from fupogfakta.models import load_game_questions
from . import tts
from .assets import asset_map
from .rooms import create_room, get_room
CODE_RE = re.compile(r"^[A-Za-z]{4}$")
def _forbidden():
return HttpResponseForbidden("Du skal være logget ind som vært for at oprette et spil.")
@require_GET
def play(request):
code = request.GET.get("code", "")
return render(request, "party/play.html", {"prefill_code": code.upper() if CODE_RE.match(code) else "",
"assets": asset_map()})
@require_GET
def host_new_page(request):
if not request.host_identity:
return _forbidden()
return render(request, "party/host_new.html", {"username": request.host_identity.username,
"work_pack_label": settings.WORK_PACK_LABEL})
@require_POST
async def host_new(request):
ident = request.host_identity
if not ident:
return _forbidden()
questions = await sync_to_async(load_game_questions)(request.POST.get("pack", "alle"))
if not questions:
return HttpResponse("Der er ingen spørgsmål i banken. Kør 'manage.py load_questions'.", status=503)
room = create_room(questions, ident.username, settings.PUBLIC_URL)
return redirect("party:host", code=room.code)
@require_GET
def host(request, code):
if not request.host_identity:
return _forbidden()
if not get_room(code):
return redirect("party:host_new_page")
return render(request, "party/host.html", {"code": code.upper(), "assets": asset_map()})
@require_GET
def qr(request, code):
if not CODE_RE.match(code):
raise Http404
buf = io.BytesIO()
segno.make(f"{settings.PUBLIC_URL}/?code={code.upper()}", error="m").save(
buf, kind="svg", scale=10, border=2, dark="#1b1035", light="#ffffff")
return HttpResponse(buf.getvalue(), content_type="image/svg+xml")
@require_GET
def healthz(request):
return HttpResponse("ok", content_type="text/plain")
@require_GET
def tts_audio(request, digest):
path = tts.cache_path(digest)
if not re.fullmatch(r"[0-9a-f]{32}", digest) or not path.exists():
raise Http404
resp = FileResponse(path.open("rb"), content_type="audio/mpeg")
resp["Cache-Control"] = "public, max-age=31536000, immutable"
return resp
MIC_MAX_BYTES = 2_000_000
@require_POST
async def host_mic(request, code):
"""Lydstump fra storskærmens mikrofon (eksperimentel). Kun værten; lyden gemmes aldrig."""
if not request.host_identity:
return _forbidden()
room = get_room(code)
if not room:
raise Http404
body = request.body
if not body or len(body) > MIC_MAX_BYTES:
return JsonResponse({"error": "tom eller for stor lydstump"}, status=400)
ctype = (request.content_type or "audio/webm").split(";")[0]
return JsonResponse(await room.hear(body, ctype))