90 lines
2.7 KiB
Python
90 lines
2.7 KiB
Python
"""Normalization helpers for provisional server collection flows."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
from typing import Mapping
|
|
|
|
if TYPE_CHECKING:
|
|
from .a2s_client import A2SServerInfo
|
|
|
|
|
|
def normalize_server_record(
|
|
raw_record: Mapping[str, object],
|
|
*,
|
|
source_name: str,
|
|
) -> dict[str, object]:
|
|
"""Normalize a raw server record into the collector's internal shape."""
|
|
external_server_id = _string_or_none(raw_record.get("external_server_id"))
|
|
return {
|
|
"external_server_id": external_server_id,
|
|
"server_name": _string_or_default(raw_record.get("server_name"), "Unknown server"),
|
|
"status": _normalize_status(raw_record.get("status")),
|
|
"players": _coerce_int(raw_record.get("players")),
|
|
"max_players": _coerce_int(raw_record.get("max_players")),
|
|
"current_map": _string_or_none(raw_record.get("current_map")),
|
|
"region": _string_or_none(raw_record.get("region")),
|
|
"source_name": source_name,
|
|
"snapshot_origin": "controlled-fallback",
|
|
"source_ref": external_server_id or source_name,
|
|
}
|
|
|
|
|
|
def normalize_a2s_server_info(
|
|
server_info: "A2SServerInfo",
|
|
*,
|
|
source_name: str,
|
|
external_server_id: str | None = None,
|
|
region: str | None = None,
|
|
) -> dict[str, object]:
|
|
"""Normalize a probed A2S payload into the collector's internal shape."""
|
|
resolved_external_id = external_server_id or (
|
|
f"a2s:{server_info.host}:{server_info.query_port}"
|
|
)
|
|
return {
|
|
"external_server_id": resolved_external_id,
|
|
"server_name": server_info.server_name or "Unknown server",
|
|
"status": "online",
|
|
"players": server_info.players,
|
|
"max_players": server_info.max_players,
|
|
"current_map": server_info.map_name,
|
|
"region": region,
|
|
"source_name": source_name,
|
|
"snapshot_origin": "real-a2s",
|
|
"source_ref": f"a2s://{server_info.host}:{server_info.query_port}",
|
|
}
|
|
|
|
|
|
def _normalize_status(value: object) -> str:
|
|
if not isinstance(value, str):
|
|
return "unknown"
|
|
|
|
normalized = value.strip().lower()
|
|
if normalized in {"online", "offline", "unknown"}:
|
|
return normalized
|
|
|
|
return "unknown"
|
|
|
|
|
|
def _coerce_int(value: object) -> int | None:
|
|
if value is None:
|
|
return None
|
|
|
|
try:
|
|
return int(value)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def _string_or_none(value: object) -> str | None:
|
|
if not isinstance(value, str):
|
|
return None
|
|
|
|
stripped = value.strip()
|
|
return stripped or None
|
|
|
|
|
|
def _string_or_default(value: object, default: str) -> str:
|
|
normalized = _string_or_none(value)
|
|
return normalized or default
|