""" Session and lobby management for voicebot. This module handles session creation and lobby management functionality. """ import json import ssl import urllib.request import urllib.error import urllib.parse import sys import os from pydantic import ValidationError # Add the parent directory to sys.path to allow absolute imports sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # Import shared models from shared.models import SessionModel, LobbyCreateResponse from voicebot.utils import http_base_url def create_or_get_session( server_url: str, session_id: str | None = None, insecure: bool = False ) -> str: """Call GET /api/session to obtain a session_id (unless one was provided). Uses urllib so no extra runtime deps are required. """ if session_id: return session_id http_base = http_base_url(server_url) url = f"{http_base}/api/session" req = urllib.request.Request(url, method="GET") # Prepare SSL context if requested (accept self-signed certs) ssl_ctx = None if insecure: ssl_ctx = ssl.create_default_context() ssl_ctx.check_hostname = False ssl_ctx.verify_mode = ssl.CERT_NONE try: with urllib.request.urlopen(req, timeout=10, context=ssl_ctx) as resp: body = resp.read() data = json.loads(body) # Validate response shape using Pydantic try: session = SessionModel.model_validate(data) except ValidationError as e: raise RuntimeError(f"Invalid session response from {url}: {e}") sid = session.id if not sid: raise RuntimeError(f"No session id returned from {url}: {data}") return sid except urllib.error.HTTPError as e: raise RuntimeError(f"HTTP error getting session: {e}") except Exception as e: raise RuntimeError(f"Error getting session: {e}") def create_or_get_lobby( server_url: str, session_id: str, lobby_name: str, private: bool = False, insecure: bool = False, ) -> str: """Call POST /api/lobby/{session_id} to create or lookup a lobby by name. Returns the lobby id. """ http_base = http_base_url(server_url) url = f"{http_base}/api/lobby/{urllib.parse.quote(session_id)}" payload = json.dumps( { "type": "lobby_create", "data": {"name": lobby_name, "private": private}, } ).encode("utf-8") req = urllib.request.Request( url, data=payload, headers={"Content-Type": "application/json"}, method="POST" ) # Prepare SSL context if requested (accept self-signed certs) ssl_ctx = None if insecure: ssl_ctx = ssl.create_default_context() ssl_ctx.check_hostname = False ssl_ctx.verify_mode = ssl.CERT_NONE try: with urllib.request.urlopen(req, timeout=10, context=ssl_ctx) as resp: body = resp.read() data = json.loads(body) # Expect shape: { "type": "lobby_created", "data": {"id":..., ...}} try: lobby_resp = LobbyCreateResponse.model_validate(data) except ValidationError as e: raise RuntimeError(f"Invalid lobby response from {url}: {e}") lobby_id = lobby_resp.data.id if not lobby_id: raise RuntimeError(f"No lobby id returned from {url}: {data}") return lobby_id except urllib.error.HTTPError as e: # Try to include response body for debugging try: body = e.read() msg = body.decode("utf-8", errors="ignore") except Exception: msg = str(e) raise RuntimeError(f"HTTP error creating lobby: {msg}") except Exception as e: raise RuntimeError(f"Error creating lobby: {e}")