Fix carried use-sound origin and centralize sound normalization
This commit is contained in:
44
server/app/items/sound_policy.py
Normal file
44
server/app/items/sound_policy.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Shared normalization helpers for item sound/media URL parameters."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def normalize_sound_reference(raw: object) -> str:
|
||||
"""Normalize sound value to empty/URL/or `sounds/`-relative path."""
|
||||
|
||||
token = str(raw or "").strip()
|
||||
if not token:
|
||||
return ""
|
||||
lowered = token.lower()
|
||||
if lowered in {"none", "off"}:
|
||||
return ""
|
||||
if lowered.startswith(("http://", "https://", "data:", "blob:")):
|
||||
return token
|
||||
if token.startswith("/sounds/"):
|
||||
return token[1:]
|
||||
if token.startswith("sounds/"):
|
||||
return token
|
||||
if "/" not in token:
|
||||
return f"sounds/{token}"
|
||||
return token
|
||||
|
||||
|
||||
def normalize_media_reference(raw: object) -> str:
|
||||
"""Normalize media URL-like value while preserving path/query format."""
|
||||
|
||||
token = str(raw or "").strip()
|
||||
if not token:
|
||||
return ""
|
||||
lowered = token.lower()
|
||||
if lowered in {"none", "off"}:
|
||||
return ""
|
||||
return token
|
||||
|
||||
|
||||
def enforce_max_length(value: str, *, max_length: int, field_name: str) -> str:
|
||||
"""Enforce max character length for normalized string fields."""
|
||||
|
||||
if len(value) > max_length:
|
||||
raise ValueError(f"{field_name} must be {max_length} characters or less.")
|
||||
return value
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ....models import WorldItem
|
||||
from ...sound_policy import enforce_max_length, normalize_media_reference
|
||||
from ...helpers import keep_only_known_params
|
||||
from .definition import CHANNEL_OPTIONS, EFFECT_OPTIONS, PARAM_KEYS
|
||||
|
||||
@@ -10,10 +11,11 @@ from .definition import CHANNEL_OPTIONS, EFFECT_OPTIONS, PARAM_KEYS
|
||||
def validate_update(item: WorldItem, next_params: dict) -> dict:
|
||||
"""Validate and normalize radio params."""
|
||||
|
||||
stream_url = str(next_params.get("streamUrl", "")).strip()
|
||||
if len(stream_url) > 2048:
|
||||
raise ValueError("streamUrl must be 2048 characters or less.")
|
||||
next_params["streamUrl"] = stream_url
|
||||
next_params["streamUrl"] = enforce_max_length(
|
||||
normalize_media_reference(next_params.get("streamUrl", "")),
|
||||
max_length=2048,
|
||||
field_name="streamUrl",
|
||||
)
|
||||
|
||||
enabled_value = next_params.get("enabled", True)
|
||||
if isinstance(enabled_value, bool):
|
||||
|
||||
@@ -3,30 +3,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ....models import WorldItem
|
||||
from ...sound_policy import enforce_max_length, normalize_sound_reference
|
||||
from ...helpers import keep_only_known_params, parse_bool_like
|
||||
from .definition import EFFECT_OPTIONS, PARAM_KEYS
|
||||
|
||||
|
||||
def _normalize_sound_value(raw: object) -> str:
|
||||
"""Normalize sound value to empty/URL/or sounds-relative path."""
|
||||
|
||||
token = str(raw or "").strip()
|
||||
if not token:
|
||||
return ""
|
||||
lowered = token.lower()
|
||||
if lowered in {"none", "off"}:
|
||||
return ""
|
||||
if lowered.startswith(("http://", "https://", "data:", "blob:")):
|
||||
return token
|
||||
if token.startswith("/sounds/"):
|
||||
return token[1:]
|
||||
if token.startswith("sounds/"):
|
||||
return token
|
||||
if "/" not in token:
|
||||
return f"sounds/{token}"
|
||||
return token
|
||||
|
||||
|
||||
def validate_update(item: WorldItem, next_params: dict) -> dict:
|
||||
"""Validate and normalize widget params."""
|
||||
|
||||
@@ -88,10 +69,14 @@ def validate_update(item: WorldItem, next_params: dict) -> dict:
|
||||
raise ValueError("emitEffectValue must be between 0 and 100.")
|
||||
next_params["emitEffectValue"] = round(emit_effect_value, 1)
|
||||
|
||||
next_params["useSound"] = _normalize_sound_value(next_params.get("useSound", item.params.get("useSound", "")))
|
||||
next_params["emitSound"] = _normalize_sound_value(next_params.get("emitSound", item.params.get("emitSound", "")))
|
||||
if len(next_params["useSound"]) > 2048:
|
||||
raise ValueError("useSound must be 2048 characters or less.")
|
||||
if len(next_params["emitSound"]) > 2048:
|
||||
raise ValueError("emitSound must be 2048 characters or less.")
|
||||
next_params["useSound"] = enforce_max_length(
|
||||
normalize_sound_reference(next_params.get("useSound", item.params.get("useSound", ""))),
|
||||
max_length=2048,
|
||||
field_name="useSound",
|
||||
)
|
||||
next_params["emitSound"] = enforce_max_length(
|
||||
normalize_sound_reference(next_params.get("emitSound", item.params.get("emitSound", ""))),
|
||||
max_length=2048,
|
||||
field_name="emitSound",
|
||||
)
|
||||
return keep_only_known_params(next_params, PARAM_KEYS)
|
||||
|
||||
@@ -209,6 +209,15 @@ class SignalingServer:
|
||||
return item.useSound.strip()
|
||||
return None
|
||||
|
||||
def _get_item_sound_source_position(self, item: WorldItem) -> tuple[int, int]:
|
||||
"""Resolve source position for item-emitted one-shot sounds."""
|
||||
|
||||
if item.carrierId:
|
||||
carrier = self._get_client_by_id(item.carrierId)
|
||||
if carrier is not None:
|
||||
return carrier.x, carrier.y
|
||||
return item.x, item.y
|
||||
|
||||
def _get_client_by_id(self, client_id: str) -> ClientConnection | None:
|
||||
"""Resolve one connected client by id."""
|
||||
|
||||
@@ -1141,13 +1150,14 @@ class SignalingServer:
|
||||
)
|
||||
use_sound = self._resolve_item_use_sound(item)
|
||||
if use_sound:
|
||||
sound_x, sound_y = self._get_item_sound_source_position(item)
|
||||
await self._broadcast(
|
||||
ItemUseSoundPacket(
|
||||
type="item_use_sound",
|
||||
itemId=item.id,
|
||||
sound=use_sound,
|
||||
x=item.x,
|
||||
y=item.y,
|
||||
x=sound_x,
|
||||
y=sound_y,
|
||||
)
|
||||
)
|
||||
if item.type == "piano":
|
||||
|
||||
Reference in New Issue
Block a user