Files
weirsoe-party-protocol/voice/services.py
Asger Geel Weirsøe a81bc1250c
Some checks failed
CI / test-and-quality (push) Failing after 4m4s
Big visual overhaul docker compsoe file etc
2026-03-23 14:11:30 +01:00

194 lines
6.5 KiB
Python

from __future__ import annotations
from typing import Any
from fupogfakta.models import GameSession, RoundQuestion
from lobby.i18n import i18n_locale_config
from .models import PhaseVoiceLine, QuestionVoiceLine
VOICE_GAME_KEY = "fupogfakta"
DEFAULT_PHASE_LINES: dict[str, dict[str, str]] = {
"en": {
"intro": (
"Welcome to Fup og Fakta. Invent a believable lie, spot the real answer, "
"and score points when other players believe your bluff."
),
"lobby": "Players are joining the session. Get ready to start the round.",
"lie": "The question is live. Players, write one believable lie before time runs out.",
"guess": "The answers are mixed. Pick the answer you believe is true.",
"reveal": "Time to reveal the lies, the guesses, and the correct answer.",
"scoreboard": "Here comes the scoreboard for this round.",
"finished": "The game is finished. Here is the final result.",
},
"da": {
"intro": (
"Velkommen til Fup og Fakta. Find på en troværdig løgn, gennemsku det rigtige svar, "
"og få point når andre hopper på dit bluff."
),
"lobby": "Spillerne er ved at joine sessionen. Gør klar til at starte runden.",
"lie": "Spørgsmålet er live. Spillere, skriv en troværdig løgn før tiden løber ud.",
"guess": "Svarene er blandet. Vælg det svar du tror er rigtigt.",
"reveal": "Nu afslører vi løgnene, gættene og det rigtige svar.",
"scoreboard": "Her kommer scoreboardet for denne runde.",
"finished": "Spillet er slut. Her er det endelige resultat.",
},
}
def _default_question_prompt(locale: str, prompt: str) -> str:
if locale == "da":
return f"Spørgsmålet lyder: {prompt}"
return f"The question is: {prompt}"
def _default_question_reveal(locale: str, correct_answer: str) -> str:
if locale == "da":
return f"Det rigtige svar er: {correct_answer}"
return f"The correct answer is: {correct_answer}"
def _supported_locales() -> tuple[str, tuple[str, ...]]:
return i18n_locale_config()
def _default_phase_line(cue_key: str, locale: str) -> str:
default_locale, _locales = _supported_locales()
localized_lines = DEFAULT_PHASE_LINES.get(locale) or DEFAULT_PHASE_LINES.get(default_locale) or {}
return localized_lines.get(cue_key, cue_key)
def _resolve_audio_url(audio_field: Any) -> str | None:
if not audio_field:
return None
try:
return str(audio_field.url)
except ValueError:
return None
def _resolve_phase_content(*, cue_key: str, locale: str) -> tuple[str, str | None, str]:
custom = (
PhaseVoiceLine.objects.filter(
game_key=VOICE_GAME_KEY,
cue_key=cue_key,
locale=locale,
is_active=True,
)
.first()
)
if custom:
return custom.text, _resolve_audio_url(custom.audio_file), "custom"
return _default_phase_line(cue_key, locale), None, "default"
def _resolve_question_content(
*,
cue_key: str,
locale: str,
round_question: RoundQuestion,
) -> tuple[str, str | None, str]:
custom = (
QuestionVoiceLine.objects.filter(
question=round_question.question,
cue_key=cue_key,
locale=locale,
is_active=True,
)
.first()
)
if custom:
return custom.text, _resolve_audio_url(custom.audio_file), "custom"
if cue_key == QuestionVoiceLine.CueKey.QUESTION_REVEAL:
return _default_question_reveal(locale, round_question.correct_answer), None, "default"
return _default_question_prompt(locale, round_question.question.prompt), None, "default"
def _build_cue_payload(
*,
cue_key: str,
text_by_locale: dict[str, str],
audio_urls: dict[str, str],
source: str,
) -> dict[str, Any]:
return {
"cue": cue_key,
"translations": text_by_locale,
"audio_urls": audio_urls,
"source": source,
}
def resolve_session_voice_cues(
session: GameSession,
*,
current_round_question: RoundQuestion | None,
) -> dict[str, Any]:
default_locale, supported_locales = _supported_locales()
def collect_phase(cue_key: str) -> dict[str, Any]:
translations: dict[str, str] = {}
audio_urls: dict[str, str] = {}
source = "default"
for locale in supported_locales:
text, audio_url, line_source = _resolve_phase_content(cue_key=cue_key, locale=locale)
translations[locale] = text
if audio_url:
audio_urls[locale] = audio_url
if line_source == "custom":
source = "custom"
return _build_cue_payload(
cue_key=cue_key,
text_by_locale=translations,
audio_urls=audio_urls,
source=source,
)
def collect_question(cue_key: str) -> dict[str, Any] | None:
if current_round_question is None:
return None
translations: dict[str, str] = {}
audio_urls: dict[str, str] = {}
source = "default"
for locale in supported_locales:
text, audio_url, line_source = _resolve_question_content(
cue_key=cue_key,
locale=locale,
round_question=current_round_question,
)
translations[locale] = text
if audio_url:
audio_urls[locale] = audio_url
if line_source == "custom":
source = "custom"
return _build_cue_payload(
cue_key=cue_key,
text_by_locale=translations,
audio_urls=audio_urls,
source=source,
)
phase_cue_key = session.status if session.status in {
GameSession.Status.LOBBY,
GameSession.Status.LIE,
GameSession.Status.GUESS,
GameSession.Status.REVEAL,
GameSession.Status.SCOREBOARD,
GameSession.Status.FINISHED,
} else GameSession.Status.LOBBY
return {
"default_locale": default_locale,
"intro": collect_phase(PhaseVoiceLine.CueKey.INTRO),
"phase": collect_phase(phase_cue_key),
"question_prompt": collect_question(QuestionVoiceLine.CueKey.QUESTION_PROMPT)
if session.status in {GameSession.Status.LIE, GameSession.Status.GUESS}
else None,
"question_reveal": collect_question(QuestionVoiceLine.CueKey.QUESTION_REVEAL)
if session.status in {GameSession.Status.REVEAL, GameSession.Status.SCOREBOARD, GameSession.Status.FINISHED}
else None,
}