Files
weirsoe-party-protocol/party/consumers.py
T
agw d149aa323e
CI / test (push) Failing after 39s
Udbyder-agnostisk spilhjerne, rummets mikrofon og rigtig musik
Hjernen (party/brain) skriver kommentarer til løgnene, dømmer løgne
(sandheden eller upassende), personlige drillerier og en slut-anmeldelse,
alt i baggrunden med faste replikker som fallback. Udbyderen vælges med
BRAIN_PROVIDER: anthropic (Claude), openai (OpenAI-kompatibel, fx Codex)
eller ollama (fx Gemma). Rummets mikrofon er eksperimentel: storskærmen
sender korte lydstumper til talegenkendelse (STT_PROVIDER), og kun et kort
resumé af latter og udsagn huskes til hjernen. Musikken er nu lavet med
en musikmodel.

Claude-Session: https://claude.ai/code/session_01JmhJG6JnhXfzNiKv8f2vHu
2026-10-01 21:59:50 +02:00

171 lines
6.0 KiB
Python

"""WebSocket-forbindelser for storskærm (vært) og telefoner (spillere/publikum)."""
import logging
import time
from channels.generic.websocket import AsyncJsonWebsocketConsumer
from fupogfakta.engine import GameError
from . import stt
from .auth import scope_identity
from .rooms import get_room
log = logging.getLogger(__name__)
class _RoomConsumer(AsyncJsonWebsocketConsumer):
room = None
async def connect(self):
self.room = get_room(self.scope["url_route"]["kwargs"]["code"])
if not self.authorized():
await self.close(code=4403)
return
await self.accept()
if not self.room:
await self.send_error("unknown_room", "Det spil findes ikke. Tjek koden.")
await self.close(code=4404)
return
self.room.connections.add(self)
self.room.touch()
await self.on_open()
def authorized(self) -> bool:
return True
async def on_open(self):
pass
async def disconnect(self, code):
if self.room:
self.room.connections.discard(self)
await self.on_close()
async def on_close(self):
pass
async def send_error(self, code, message):
await self.send_json({"type": "error", "code": code, "message": message})
async def receive_json(self, content, **kwargs):
if not self.room or not isinstance(content, dict):
return
action = content.get("action")
if action == "ping":
await self.send_json({"type": "pong"})
return
version = self.room.game.version
try:
await self.handle(action, content, time.time())
except GameError as e:
await self.send_error(e.code, e.message)
except (TypeError, ValueError, KeyError):
await self.send_error("bad_request", "Ugyldig besked.")
if self.room.game.version != version:
self.room.changed()
else:
self.room.handle_events() # fx "det var sandheden", der afvises men kommenteres
async def handle(self, action, content, now):
raise NotImplementedError
async def send_state(self):
raise NotImplementedError
class HostConsumer(_RoomConsumer):
def authorized(self):
return scope_identity(self.scope) is not None
async def on_open(self):
self.room.host_opened()
await self.send_state()
async def send_state(self):
state = self.room.game.snapshot(time.time(), host=True)
state["narration"] = self.room.narration
state["phase_key"] = self.room.game.phase_key()
state["mic"] = {"on": self.room.mic_on(), "laughs": self.room.mic_laughs, "available": stt.enabled()}
await self.send_json({"type": "state", "state": state})
async def handle(self, action, content, now):
g = self.room.game
if action in ("start", "skip", "play_again"):
self.room.cut_speech() # værtens knap vinder over speakeren
if action == "start":
g.start(None, now)
elif action == "skip":
g.skip(now)
elif action == "play_again":
g.play_again(None, now)
elif action == "kick":
g.kick(str(content["player_id"]), now)
else:
await self.send_error("bad_request", "Ukendt handling.")
class PlayConsumer(_RoomConsumer):
member_id = None
async def on_close(self):
if self.member_id:
# Er spilleren stadig forbundet fra en anden fane/telefon, er de ikke væk.
still = any(getattr(c, "member_id", None) == self.member_id for c in self.room.connections)
if not still:
version = self.room.game.version
self.room.game.set_connected(self.member_id, False, time.time())
if self.room.game.version != version:
self.room.changed()
def _member(self):
g = self.room.game
return g.players.get(self.member_id) or g.audience.get(self.member_id)
async def send_state(self):
if not self.member_id:
return
member = self._member()
if not member: # smidt ud af værten
self.member_id = None
await self.send_error("unknown_token", "Du er ikke længere med i spillet.")
return
await self.send_json({"type": "state", "state": self.room.game.snapshot(time.time(), viewer=member)})
async def _attach(self, member, now):
self.member_id = member.id
self.room.game.set_connected(member.id, True, now)
await self.send_json({"type": "joined", "token": member.token, "player_id": member.id, "role": member.role})
await self.send_state()
async def handle(self, action, content, now):
g = self.room.game
if action == "join":
await self._attach(g.join(str(content.get("name", ""))), now)
return
if action == "resume":
await self._attach(g.resume(str(content.get("token", ""))), now)
return
if not self.member_id or not self._member():
raise GameError("unknown_token", "Join spillet først.")
mid = self.member_id
if action in ("start", "play_again") and mid == g.vip_id():
self.room.cut_speech()
if action == "start":
g.start(mid, now)
elif action == "choose_category":
g.choose_category(mid, int(content["category_id"]), now)
elif action == "submit_lie":
self.room.prepare_judging()
g.submit_lie(mid, str(content.get("text", "")), now)
elif action == "lie_for_me":
await self.send_json({"type": "suggestion", "text": g.lie_for_me(mid)})
elif action == "guess":
g.guess(mid, str(content["answer_id"]), now)
elif action == "like":
g.like(mid, str(content["answer_id"]))
elif action == "play_again":
g.play_again(mid, now)
else:
await self.send_error("bad_request", "Ukendt handling.")