CI / test (push) Successful in 53s
- 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
101 lines
3.2 KiB
Python
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))
|