отдача пустых данных для vMix, в момент когда треды переключаются из офлайн в онлайн
This commit is contained in:
340
get_data.py
340
get_data.py
@@ -120,6 +120,8 @@ threads_offline = []
|
|||||||
|
|
||||||
# какой режим сейчас запущен: "live" / "offline" / None
|
# какой режим сейчас запущен: "live" / "offline" / None
|
||||||
CURRENT_THREADS_MODE = None
|
CURRENT_THREADS_MODE = None
|
||||||
|
CLEAR_OUTPUT_FOR_VMIX = False
|
||||||
|
EMPTY_PHOTO_PATH = r"D:\Графика\БАСКЕТБОЛ\VTB League\ASSETS\EMPTY.png"
|
||||||
|
|
||||||
|
|
||||||
URLS = {
|
URLS = {
|
||||||
@@ -134,6 +136,17 @@ URLS = {
|
|||||||
"play-by-play": "{host}/api/abc/games/play-by-play?id={game_id}",
|
"play-by-play": "{host}/api/abc/games/play-by-play?id={game_id}",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def maybe_clear_for_vmix(payload):
|
||||||
|
"""
|
||||||
|
Если включён режим очистки — возвращаем payload,
|
||||||
|
где все значения заменены на "".
|
||||||
|
Иначе — возвращаем как есть.
|
||||||
|
"""
|
||||||
|
print(f"maybe_clear_for_vmix(): {CLEAR_OUTPUT_FOR_VMIX}")
|
||||||
|
if CLEAR_OUTPUT_FOR_VMIX:
|
||||||
|
return wipe_json_values(payload)
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
def start_offline_threads(season, game_id):
|
def start_offline_threads(season, game_id):
|
||||||
"""Запускаем редкие запросы, когда матча нет или он уже сыгран."""
|
"""Запускаем редкие запросы, когда матча нет или он уже сыгран."""
|
||||||
@@ -146,17 +159,20 @@ def start_offline_threads(season, game_id):
|
|||||||
stop_live_threads()
|
stop_live_threads()
|
||||||
stop_offline_threads()
|
stop_offline_threads()
|
||||||
logger.info("[threads] switching to OFFLINE mode ...")
|
logger.info("[threads] switching to OFFLINE mode ...")
|
||||||
|
# for key in latest_data:
|
||||||
|
# latest_data[key] = wipe_json_values(latest_data[key])
|
||||||
# 🔹 очищаем latest_data безопасно, чтобы не ломать структуру
|
# 🔹 очищаем latest_data безопасно, чтобы не ломать структуру
|
||||||
keep_keys = {
|
# keep_keys = {
|
||||||
"game",
|
# "game",
|
||||||
"pregame",
|
# "pregame",
|
||||||
"pregame-full-stats",
|
# "pregame-full-stats",
|
||||||
"actual-standings",
|
# "actual-standings",
|
||||||
"calendar",
|
# "calendar",
|
||||||
}
|
# }
|
||||||
for key in list(latest_data.keys()):
|
# for key in list(latest_data.keys()):
|
||||||
if key not in keep_keys:
|
# if key not in keep_keys:
|
||||||
del latest_data[key]
|
# del latest_data[key]
|
||||||
|
|
||||||
|
|
||||||
stop_event_offline.clear()
|
stop_event_offline.clear()
|
||||||
|
|
||||||
@@ -364,6 +380,7 @@ def has_full_game_ready() -> bool:
|
|||||||
and "teams" in payload["data"]["result"]
|
and "teams" in payload["data"]["result"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Функция запускаемая в потоках
|
# Функция запускаемая в потоках
|
||||||
def get_data_from_API(
|
def get_data_from_API(
|
||||||
name: str,
|
name: str,
|
||||||
@@ -376,8 +393,14 @@ def get_data_from_API(
|
|||||||
did_first_fetch = False
|
did_first_fetch = False
|
||||||
while not stop_event.is_set():
|
while not stop_event.is_set():
|
||||||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||||
if stop_when_live and globals().get("STATUS") == "live" and has_full_game_ready():
|
if (
|
||||||
logger.info(f"{[{current_time}]} [{name}] stopping because STATUS='live' and full game is ready")
|
stop_when_live
|
||||||
|
and globals().get("STATUS") == "live"
|
||||||
|
and has_full_game_ready()
|
||||||
|
):
|
||||||
|
logger.info(
|
||||||
|
f"{[{current_time}]} [{name}] stopping because STATUS='live' and full game is ready"
|
||||||
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -417,7 +440,9 @@ def get_data_from_API(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if stop_after_success and ok_status:
|
if stop_after_success and ok_status:
|
||||||
logger.info(f"[{name}] got successful response → stopping thread (stop_after_success)")
|
logger.info(
|
||||||
|
f"[{name}] got successful response → stopping thread (stop_after_success)"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# сколько уже заняло
|
# сколько уже заняло
|
||||||
@@ -431,8 +456,14 @@ def get_data_from_API(
|
|||||||
while slept < sleep_time:
|
while slept < sleep_time:
|
||||||
if stop_event.is_set():
|
if stop_event.is_set():
|
||||||
break
|
break
|
||||||
if stop_when_live and globals().get("STATUS") == "live" and has_full_game_ready():
|
if (
|
||||||
logger.info(f"[{name}] stopping during sleep because STATUS='live' and full game is ready")
|
stop_when_live
|
||||||
|
and globals().get("STATUS") == "live"
|
||||||
|
and has_full_game_ready()
|
||||||
|
):
|
||||||
|
logger.info(
|
||||||
|
f"[{name}] stopping during sleep because STATUS='live' and full game is ready"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
@@ -471,7 +502,9 @@ def results_consumer():
|
|||||||
try:
|
try:
|
||||||
if PRELOAD_LOCK:
|
if PRELOAD_LOCK:
|
||||||
incoming_gid = extract_game_id_from_payload(payload)
|
incoming_gid = extract_game_id_from_payload(payload)
|
||||||
if not incoming_gid or str(incoming_gid) != str(PRELOADED_GAME_ID):
|
if not incoming_gid or str(incoming_gid) != str(
|
||||||
|
PRELOADED_GAME_ID
|
||||||
|
):
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"results_consumer: skip game (gid={incoming_gid}) due to PRELOAD_LOCK; keep {PRELOADED_GAME_ID}"
|
f"results_consumer: skip game (gid={incoming_gid}) due to PRELOAD_LOCK; keep {PRELOADED_GAME_ID}"
|
||||||
)
|
)
|
||||||
@@ -587,8 +620,10 @@ def results_consumer():
|
|||||||
and GAME_START_DT.date() == datetime.now().date()
|
and GAME_START_DT.date() == datetime.now().date()
|
||||||
):
|
):
|
||||||
globals()["STATUS"] = "finished_wait"
|
globals()["STATUS"] = "finished_wait"
|
||||||
|
globals()["CLEAR_OUTPUT_FOR_VMIX"] = True # 👈 включаем режим "пустых" данных
|
||||||
else:
|
else:
|
||||||
globals()["STATUS"] = "finished_wait"
|
globals()["STATUS"] = "finished_wait"
|
||||||
|
globals()["CLEAR_OUTPUT_FOR_VMIX"] = True # 👈 включаем режим "пустых" данных
|
||||||
|
|
||||||
human_time = datetime.fromtimestamp(switch_at).strftime(
|
human_time = datetime.fromtimestamp(switch_at).strftime(
|
||||||
"%H:%M:%S"
|
"%H:%M:%S"
|
||||||
@@ -608,6 +643,7 @@ def results_consumer():
|
|||||||
"online" in raw_ls_status_low or "live" in raw_ls_status_low
|
"online" in raw_ls_status_low or "live" in raw_ls_status_low
|
||||||
):
|
):
|
||||||
# если до этого стояла отложка — уберём
|
# если до этого стояла отложка — уберём
|
||||||
|
globals()["CLEAR_OUTPUT_FOR_VMIX"] = False # 👈 выключаем очистку
|
||||||
if globals().get("OFFLINE_SWITCH_AT") is not None:
|
if globals().get("OFFLINE_SWITCH_AT") is not None:
|
||||||
logger.info(
|
logger.info(
|
||||||
"[status] match back to LIVE → cancel scheduled OFFLINE"
|
"[status] match back to LIVE → cancel scheduled OFFLINE"
|
||||||
@@ -628,7 +664,9 @@ def results_consumer():
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
if source == "game":
|
if source == "game":
|
||||||
has_game_already = "game" in latest_data and isinstance(latest_data.get("game"), dict)
|
has_game_already = "game" in latest_data and isinstance(
|
||||||
|
latest_data.get("game"), dict
|
||||||
|
)
|
||||||
|
|
||||||
# Полная структура?
|
# Полная структура?
|
||||||
is_full = (
|
is_full = (
|
||||||
@@ -644,15 +682,24 @@ def results_consumer():
|
|||||||
# чтобы /status видел "живость" раз в 5 минут независимо от полноты JSON.
|
# чтобы /status видел "живость" раз в 5 минут независимо от полноты JSON.
|
||||||
if globals().get("STATUS") != "live":
|
if globals().get("STATUS") != "live":
|
||||||
latest_data["game"] = {"ts": msg["ts"], "data": payload}
|
latest_data["game"] = {"ts": msg["ts"], "data": payload}
|
||||||
logger.debug("results_consumer: pre-live game → updated (full=%s)", is_full)
|
logger.debug(
|
||||||
|
"results_consumer: pre-live game → updated (full=%s)",
|
||||||
|
is_full,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# ✅ если игры ещё НЕТ в кэше — примем ПЕРВЫЙ game даже неполный,
|
# ✅ если игры ещё НЕТ в кэше — примем ПЕРВЫЙ game даже неполный,
|
||||||
# чтобы box-score/play-by-play могли его дорастить
|
# чтобы box-score/play-by-play могли его дорастить
|
||||||
if is_full or not has_game_already:
|
if is_full or not has_game_already:
|
||||||
latest_data["game"] = {"ts": msg["ts"], "data": payload}
|
latest_data["game"] = {"ts": msg["ts"], "data": payload}
|
||||||
logger.debug("results_consumer: LIVE → stored (full=%s, had=%s)", is_full, has_game_already)
|
logger.debug(
|
||||||
|
"results_consumer: LIVE → stored (full=%s, had=%s)",
|
||||||
|
is_full,
|
||||||
|
has_game_already,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.debug("results_consumer: LIVE & partial game → keep previous one")
|
logger.debug(
|
||||||
|
"results_consumer: LIVE & partial game → keep previous one"
|
||||||
|
)
|
||||||
|
|
||||||
# 2) Когда матч УЖЕ online (STATUS == 'live'):
|
# 2) Когда матч УЖЕ online (STATUS == 'live'):
|
||||||
# - поток 'game' в live-режиме погаснет сам (stop_when_live=True),
|
# - поток 'game' в live-режиме погаснет сам (stop_when_live=True),
|
||||||
@@ -927,7 +974,8 @@ def get_cached_game_id() -> str | None:
|
|||||||
# структура может быть {"data":{"result":{...}}} или {"result":{...}}
|
# структура может быть {"data":{"result":{...}}} или {"result":{...}}
|
||||||
result = (
|
result = (
|
||||||
payload.get("data", {}).get("result")
|
payload.get("data", {}).get("result")
|
||||||
if "data" in payload else payload.get("result")
|
if "data" in payload
|
||||||
|
else payload.get("result")
|
||||||
)
|
)
|
||||||
if not isinstance(result, dict):
|
if not isinstance(result, dict):
|
||||||
return None
|
return None
|
||||||
@@ -949,6 +997,7 @@ def extract_game_id_from_payload(payload: dict) -> str | None:
|
|||||||
return g.get("id")
|
return g.get("id")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def start_offline_prevgame(season, prev_game_id: str):
|
def start_offline_prevgame(season, prev_game_id: str):
|
||||||
"""
|
"""
|
||||||
Специальный оффлайн для ПРЕДЫДУЩЕЙ игры:
|
Специальный оффлайн для ПРЕДЫДУЩЕЙ игры:
|
||||||
@@ -965,10 +1014,18 @@ def start_offline_prevgame(season, prev_game_id: str):
|
|||||||
logger.info("[threads] switching to OFFLINE mode (previous game) ...")
|
logger.info("[threads] switching to OFFLINE mode (previous game) ...")
|
||||||
|
|
||||||
# оставим только полезные ключи
|
# оставим только полезные ключи
|
||||||
keep_keys = {"game", "pregame", "pregame-full-stats", "actual-standings", "calendar"}
|
keep_keys = {
|
||||||
for key in list(latest_data.keys()):
|
"game",
|
||||||
if key not in keep_keys:
|
"pregame",
|
||||||
del latest_data[key]
|
"pregame-full-stats",
|
||||||
|
"actual-standings",
|
||||||
|
"calendar",
|
||||||
|
}
|
||||||
|
# for key in list(latest_data.keys()):
|
||||||
|
# if key not in keep_keys:
|
||||||
|
# del latest_data[key]
|
||||||
|
# for key in latest_data:
|
||||||
|
# latest_data[key] = wipe_json_values(latest_data[key])
|
||||||
|
|
||||||
stop_event_offline.clear()
|
stop_event_offline.clear()
|
||||||
threads_offline = [
|
threads_offline = [
|
||||||
@@ -989,7 +1046,11 @@ def start_offline_prevgame(season, prev_game_id: str):
|
|||||||
args=(
|
args=(
|
||||||
"pregame-full-stats",
|
"pregame-full-stats",
|
||||||
URLS["pregame-full-stats"].format(
|
URLS["pregame-full-stats"].format(
|
||||||
host=HOST, league=LEAGUE, season=season, game_id=prev_game_id, lang=LANG
|
host=HOST,
|
||||||
|
league=LEAGUE,
|
||||||
|
season=season,
|
||||||
|
game_id=prev_game_id,
|
||||||
|
lang=LANG,
|
||||||
),
|
),
|
||||||
600,
|
600,
|
||||||
stop_event_offline,
|
stop_event_offline,
|
||||||
@@ -1043,6 +1104,7 @@ def start_prestart_watcher(game_dt: datetime | None):
|
|||||||
|
|
||||||
def _runner():
|
def _runner():
|
||||||
from datetime import time as dtime # для резервного парсинга времени
|
from datetime import time as dtime # для резервного парсинга времени
|
||||||
|
|
||||||
global STATUS
|
global STATUS
|
||||||
|
|
||||||
PRELOAD_LEAD = timedelta(hours=1, minutes=15) # T-1:15 → сброс
|
PRELOAD_LEAD = timedelta(hours=1, minutes=15) # T-1:15 → сброс
|
||||||
@@ -1056,7 +1118,9 @@ def start_prestart_watcher(game_dt: datetime | None):
|
|||||||
did_live = False
|
did_live = False
|
||||||
|
|
||||||
# --- вспомогательное: поиск предыдущей игры команды ДО сегодняшнего матча ---
|
# --- вспомогательное: поиск предыдущей игры команды ДО сегодняшнего матча ---
|
||||||
def _find_prev_game_id(calendar_json: dict, cutoff_dt: datetime) -> tuple[str | None, datetime | None]:
|
def _find_prev_game_id(
|
||||||
|
calendar_json: dict, cutoff_dt: datetime
|
||||||
|
) -> tuple[str | None, datetime | None]:
|
||||||
items = get_items(calendar_json) or []
|
items = get_items(calendar_json) or []
|
||||||
prev_id, prev_dt = None, None
|
prev_id, prev_dt = None, None
|
||||||
team_norm = (TEAM or "").strip().casefold()
|
team_norm = (TEAM or "").strip().casefold()
|
||||||
@@ -1071,7 +1135,9 @@ def start_prestart_watcher(game_dt: datetime | None):
|
|||||||
gdt = extract_game_datetime(g)
|
gdt = extract_game_datetime(g)
|
||||||
if not gdt:
|
if not gdt:
|
||||||
try:
|
try:
|
||||||
gd = datetime.strptime(g["game"]["localDate"], "%d.%m.%Y").date()
|
gd = datetime.strptime(
|
||||||
|
g["game"]["localDate"], "%d.%m.%Y"
|
||||||
|
).date()
|
||||||
gdt = datetime.combine(gd, dtime(0, 0))
|
gdt = datetime.combine(gd, dtime(0, 0))
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
@@ -1085,12 +1151,16 @@ def start_prestart_watcher(game_dt: datetime | None):
|
|||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
if now < RESET_AT:
|
if now < RESET_AT:
|
||||||
calendar_resp = requests.get(
|
calendar_resp = requests.get(
|
||||||
URLS["calendar"].format(host=HOST, league=LEAGUE, season=SEASON, lang=LANG),
|
URLS["calendar"].format(
|
||||||
timeout=6
|
host=HOST, league=LEAGUE, season=SEASON, lang=LANG
|
||||||
|
),
|
||||||
|
timeout=6,
|
||||||
).json()
|
).json()
|
||||||
prev_game_id, prev_game_dt = _find_prev_game_id(calendar_resp, game_dt)
|
prev_game_id, prev_game_dt = _find_prev_game_id(calendar_resp, game_dt)
|
||||||
if prev_game_id and str(prev_game_id) != str(GAME_ID):
|
if prev_game_id and str(prev_game_id) != str(GAME_ID):
|
||||||
logger.info(f"[preload] старт оффлайна по предыдущей игре {prev_game_id} ({prev_game_dt})")
|
logger.info(
|
||||||
|
f"[preload] старт оффлайна по предыдущей игре {prev_game_id} ({prev_game_dt})"
|
||||||
|
)
|
||||||
|
|
||||||
# включаем «замок», чтобы consumer принимал только старую игру
|
# включаем «замок», чтобы consumer принимал только старую игру
|
||||||
globals()["PRELOAD_LOCK"] = True
|
globals()["PRELOAD_LOCK"] = True
|
||||||
@@ -1103,11 +1173,12 @@ def start_prestart_watcher(game_dt: datetime | None):
|
|||||||
else:
|
else:
|
||||||
logger.warning("[preload] предыдущая игра не найдена — пропускаем")
|
logger.warning("[preload] предыдущая игра не найдена — пропускаем")
|
||||||
else:
|
else:
|
||||||
logger.info("[preload] уже поздно для предзагрузки (прошло T-1:15) — пропуск")
|
logger.info(
|
||||||
|
"[preload] уже поздно для предзагрузки (прошло T-1:15) — пропуск"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"[preload] ошибка предзагрузки прошлой игры: {e}")
|
logger.warning(f"[preload] ошибка предзагрузки прошлой игры: {e}")
|
||||||
|
|
||||||
|
|
||||||
# --- Основной цикл ожидания контрольных моментов ---
|
# --- Основной цикл ожидания контрольных моментов ---
|
||||||
while not stop_event.is_set():
|
while not stop_event.is_set():
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
@@ -1118,18 +1189,25 @@ def start_prestart_watcher(game_dt: datetime | None):
|
|||||||
|
|
||||||
# Шаг 2: ровно T-1:15 — сбрасываем предзагруженные данные
|
# Шаг 2: ровно T-1:15 — сбрасываем предзагруженные данные
|
||||||
if not did_reset and now >= RESET_AT:
|
if not did_reset and now >= RESET_AT:
|
||||||
logger.info(f"[reset] {now:%H:%M:%S} → T-1:15: сбрасываем предзагруженные данные")
|
logger.info(
|
||||||
|
f"[reset] {now:%H:%M:%S} → T-1:15: сбрасываем предзагруженные данные"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
stop_offline_threads() # на всякий
|
stop_offline_threads() # на всякий
|
||||||
latest_data.clear() # полный сброс кэша
|
# for key in latest_data:
|
||||||
|
# latest_data[key] = wipe_json_values(latest_data[key])
|
||||||
|
# latest_data.clear() # полный сброс кэша
|
||||||
# снять замок предзагрузки
|
# снять замок предзагрузки
|
||||||
globals()["PRELOAD_LOCK"] = False
|
globals()["PRELOAD_LOCK"] = False
|
||||||
globals()["PRELOADED_GAME_ID"] = None
|
globals()["PRELOADED_GAME_ID"] = None
|
||||||
globals()["PRELOAD_HOLD_UNTIL"] = None
|
globals()["PRELOAD_HOLD_UNTIL"] = None
|
||||||
|
|
||||||
logger.info("[reset] latest_data очищен; ждём T-1:10 для запуска live")
|
logger.info(
|
||||||
|
"[reset] latest_data очищен; ждём T-1:10 для запуска live"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"[reset] ошибка при очистке: {e}")
|
logger.warning(f"[reset] ошибка при очистке: {e}")
|
||||||
|
globals()["CLEAR_OUTPUT_FOR_VMIX"] = True
|
||||||
did_reset = True
|
did_reset = True
|
||||||
|
|
||||||
# Шаг 3: T-1:10 — включаем live-треды
|
# Шаг 3: T-1:10 — включаем live-треды
|
||||||
@@ -1138,6 +1216,7 @@ def start_prestart_watcher(game_dt: datetime | None):
|
|||||||
f"[prestart] {now:%H:%M:%S}, игра в {game_dt:%H:%M}, включаем LIVE threads по правилу T-1:10"
|
f"[prestart] {now:%H:%M:%S}, игра в {game_dt:%H:%M}, включаем LIVE threads по правилу T-1:10"
|
||||||
)
|
)
|
||||||
STATUS = "live_soon"
|
STATUS = "live_soon"
|
||||||
|
globals()["CLEAR_OUTPUT_FOR_VMIX"] = False # можно оставить пустоту до первых живых данных
|
||||||
stop_offline_threads() # на всякий случай
|
stop_offline_threads() # на всякий случай
|
||||||
start_live_threads(SEASON, GAME_ID)
|
start_live_threads(SEASON, GAME_ID)
|
||||||
did_live = True
|
did_live = True
|
||||||
@@ -1186,9 +1265,7 @@ async def lifespan(app: FastAPI):
|
|||||||
GAME_START_DT = game_dt
|
GAME_START_DT = game_dt
|
||||||
GAME_TODAY = is_today
|
GAME_TODAY = is_today
|
||||||
|
|
||||||
logger.info(
|
logger.info(f"Лига: {LEAGUE}\nСезон: {season}\nКоманда: {TEAM}\nGame ID: {game_id}")
|
||||||
f"Лига: {LEAGUE}\nСезон: {season}\nКоманда: {TEAM}\nGame ID: {game_id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 4. запускаем "длинные" потоки (они у тебя и так всегда)
|
# 4. запускаем "длинные" потоки (они у тебя и так всегда)
|
||||||
thread_result_consumer = threading.Thread(
|
thread_result_consumer = threading.Thread(
|
||||||
@@ -1254,7 +1331,7 @@ app = FastAPI(
|
|||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
docs_url=None, # ❌ отключает /docs
|
docs_url=None, # ❌ отключает /docs
|
||||||
redoc_url=None, # ❌ отключает /redoc
|
redoc_url=None, # ❌ отключает /redoc
|
||||||
openapi_url=None # ❌ отключает /openapi.json
|
openapi_url=None, # ❌ отключает /openapi.json
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1278,8 +1355,10 @@ def format_time(seconds: float | int) -> str:
|
|||||||
async def team1():
|
async def team1():
|
||||||
game = get_latest_game_safe("game")
|
game = get_latest_game_safe("game")
|
||||||
if not game:
|
if not game:
|
||||||
|
# если данных вообще нет (ещё ни одной игры) — тут реально нечего отдавать
|
||||||
raise HTTPException(status_code=503, detail="game data not ready")
|
raise HTTPException(status_code=503, detail="game data not ready")
|
||||||
return await team("team1")
|
data = await team("team1")
|
||||||
|
return maybe_clear_for_vmix(data)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/team2")
|
@app.get("/team2")
|
||||||
@@ -1287,29 +1366,71 @@ async def team2():
|
|||||||
game = get_latest_game_safe("game")
|
game = get_latest_game_safe("game")
|
||||||
if not game:
|
if not game:
|
||||||
raise HTTPException(status_code=503, detail="game data not ready")
|
raise HTTPException(status_code=503, detail="game data not ready")
|
||||||
return await team("team2")
|
data = await team("team2")
|
||||||
|
return maybe_clear_for_vmix(data)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/top_team1")
|
@app.get("/top_team1")
|
||||||
async def top_team1():
|
async def top_team1():
|
||||||
data = await team("team1")
|
data = await team("team1")
|
||||||
return await top_sorted_team(data)
|
top = await top_sorted_team(data)
|
||||||
|
return maybe_clear_for_vmix(top)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/top_team2")
|
@app.get("/top_team2")
|
||||||
async def top_team2():
|
async def top_team2():
|
||||||
data = await team("team2")
|
data = await team("team2")
|
||||||
return await top_sorted_team(data)
|
top = await top_sorted_team(data)
|
||||||
|
return maybe_clear_for_vmix(top)
|
||||||
|
|
||||||
|
|
||||||
def _b(v) -> bool:
|
def _b(v) -> bool:
|
||||||
if isinstance(v, bool): return v
|
if isinstance(v, bool):
|
||||||
if isinstance(v, (int, float)): return v != 0
|
return v
|
||||||
if isinstance(v, str): return v.strip().lower() in ("1","true","yes","on")
|
if isinstance(v, (int, float)):
|
||||||
|
return v != 0
|
||||||
|
if isinstance(v, str):
|
||||||
|
return v.strip().lower() in ("1", "true", "yes", "on")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _placeholders(n=5):
|
def _placeholders(n=5):
|
||||||
return [{"NameGFX":"", "Name1GFX":"", "Name2GFX":"", "isOnCourt":False, "num":"", } for _ in range(n)]
|
return [
|
||||||
|
{
|
||||||
|
"NameGFX": "",
|
||||||
|
"Name1GFX": "",
|
||||||
|
"Name2GFX": "",
|
||||||
|
"isOnCourt": False,
|
||||||
|
"num": "",
|
||||||
|
"photoGFX": EMPTY_PHOTO_PATH,
|
||||||
|
}
|
||||||
|
for _ in range(n)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def wipe_json_values(obj):
|
||||||
|
"""
|
||||||
|
Рекурсивно заменяет все значения JSON на пустые строки.
|
||||||
|
Если ключ содержит "photo", заменяет значение на EMPTY_PHOTO_PATH.
|
||||||
|
"""
|
||||||
|
# если словарь — обрабатываем ключи
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
new_dict = {}
|
||||||
|
for k, v in obj.items():
|
||||||
|
if "photo" in str(k).lower():
|
||||||
|
# ключ содержит photo → отдаём пустую картинку
|
||||||
|
new_dict[k] = EMPTY_PHOTO_PATH
|
||||||
|
else:
|
||||||
|
new_dict[k] = wipe_json_values(v)
|
||||||
|
return new_dict
|
||||||
|
|
||||||
|
# если список — рекурсивно обработать элементы
|
||||||
|
elif isinstance(obj, list):
|
||||||
|
return [wipe_json_values(v) for v in obj]
|
||||||
|
|
||||||
|
# любое конечное значение → ""
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
@app.get("/started_team1")
|
@app.get("/started_team1")
|
||||||
async def started_team1(sort_by: str = None):
|
async def started_team1(sort_by: str = None):
|
||||||
@@ -1323,14 +1444,15 @@ async def started_team1(sort_by: str = None):
|
|||||||
|
|
||||||
if sort_by and sort_by.strip().lower() == "isstart":
|
if sort_by and sort_by.strip().lower() == "isstart":
|
||||||
starters = [p for p in players if p["isStart"]]
|
starters = [p for p in players if p["isStart"]]
|
||||||
return starters[:5] if starters else _placeholders(5)
|
return maybe_clear_for_vmix(starters[:5] if starters else _placeholders(5))
|
||||||
|
|
||||||
if sort_by and sort_by.strip().lower() == "isoncourt":
|
if sort_by and sort_by.strip().lower() == "isoncourt":
|
||||||
on_court = [p for p in players if p["isOnCourt"]]
|
on_court = [p for p in players if p["isOnCourt"]]
|
||||||
return on_court[:5] if on_court else _placeholders(5)
|
return maybe_clear_for_vmix(on_court[:5] if on_court else _placeholders(5))
|
||||||
|
|
||||||
# дефолт — без фильтра, как раньше
|
# дефолт — без фильтра, как раньше
|
||||||
return players
|
return maybe_clear_for_vmix(players)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/started_team2")
|
@app.get("/started_team2")
|
||||||
async def started_team2(sort_by: str = None):
|
async def started_team2(sort_by: str = None):
|
||||||
@@ -1343,13 +1465,13 @@ async def started_team2(sort_by: str = None):
|
|||||||
|
|
||||||
if sort_by and sort_by.strip().lower() == "isstart":
|
if sort_by and sort_by.strip().lower() == "isstart":
|
||||||
starters = [p for p in players if p["isStart"]]
|
starters = [p for p in players if p["isStart"]]
|
||||||
return starters[:5] if starters else _placeholders(5)
|
return maybe_clear_for_vmix(starters[:5] if starters else _placeholders(5))
|
||||||
|
|
||||||
if sort_by and sort_by.strip().lower() == "isoncourt":
|
if sort_by and sort_by.strip().lower() == "isoncourt":
|
||||||
on_court = [p for p in players if p["isOnCourt"]]
|
on_court = [p for p in players if p["isOnCourt"]]
|
||||||
return on_court[:5] if on_court else _placeholders(5)
|
return maybe_clear_for_vmix(on_court[:5] if on_court else _placeholders(5))
|
||||||
|
|
||||||
return players
|
return maybe_clear_for_vmix(players)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/game")
|
@app.get("/game")
|
||||||
@@ -1388,7 +1510,9 @@ async def status(request: Request):
|
|||||||
cached_game_id = get_cached_game_id() or GAME_ID
|
cached_game_id = get_cached_game_id() or GAME_ID
|
||||||
note = ""
|
note = ""
|
||||||
if cached_game_id and GAME_ID and str(cached_game_id) != str(GAME_ID):
|
if cached_game_id and GAME_ID and str(cached_game_id) != str(GAME_ID):
|
||||||
note = f' <span style="color:#ffb84d;">(предзагружены данные прошлой игры)</span>'
|
note = (
|
||||||
|
f' <span style="color:#ffb84d;">(предзагружены данные прошлой игры)</span>'
|
||||||
|
)
|
||||||
data = {
|
data = {
|
||||||
"league": LEAGUE,
|
"league": LEAGUE,
|
||||||
"team": TEAM,
|
"team": TEAM,
|
||||||
@@ -1550,7 +1674,7 @@ async def status(request: Request):
|
|||||||
formatted = json.dumps(data, indent=4, ensure_ascii=False)
|
formatted = json.dumps(data, indent=4, ensure_ascii=False)
|
||||||
response = Response(content=formatted, media_type="application/json")
|
response = Response(content=formatted, media_type="application/json")
|
||||||
response.headers["Refresh"] = "1"
|
response.headers["Refresh"] = "1"
|
||||||
return response
|
return maybe_clear_for_vmix(response)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/scores")
|
@app.get("/scores")
|
||||||
@@ -1591,7 +1715,7 @@ async def scores():
|
|||||||
score_by_quarter[i]["score1"] = parts[0]
|
score_by_quarter[i]["score1"] = parts[0]
|
||||||
score_by_quarter[i]["score2"] = parts[1]
|
score_by_quarter[i]["score2"] = parts[1]
|
||||||
|
|
||||||
return score_by_quarter
|
return maybe_clear_for_vmix(score_by_quarter)
|
||||||
|
|
||||||
|
|
||||||
async def top_sorted_team(data):
|
async def top_sorted_team(data):
|
||||||
@@ -1617,7 +1741,7 @@ async def top_sorted_team(data):
|
|||||||
return top_sorted_team
|
return top_sorted_team
|
||||||
|
|
||||||
|
|
||||||
def get_latest_game_safe(name:str):
|
def get_latest_game_safe(name: str):
|
||||||
"""
|
"""
|
||||||
Безопасно достаём актуальный game из latest_data.
|
Безопасно достаём актуальный game из latest_data.
|
||||||
Возвращаем None, если структура ещё не готова или прилетел "плохой" game
|
Возвращаем None, если структура ещё не готова или прилетел "плохой" game
|
||||||
@@ -1673,15 +1797,31 @@ def _pick_last_avg_and_sum(stats_list: list) -> tuple[dict, dict]:
|
|||||||
last = stats_list[-1] if stats_list else None
|
last = stats_list[-1] if stats_list else None
|
||||||
prev = stats_list[-2] if len(stats_list) >= 2 else None
|
prev = stats_list[-2] if len(stats_list) >= 2 else None
|
||||||
|
|
||||||
season_avg = last.get("stats", {}) if isinstance(last, dict) and str(last.get("class")).lower() == "avg" else {}
|
season_avg = (
|
||||||
season_sum = prev.get("stats", {}) if isinstance(prev, dict) and str(prev.get("class")).lower() == "sum" else {}
|
last.get("stats", {})
|
||||||
|
if isinstance(last, dict) and str(last.get("class")).lower() == "avg"
|
||||||
|
else {}
|
||||||
|
)
|
||||||
|
season_sum = (
|
||||||
|
prev.get("stats", {})
|
||||||
|
if isinstance(prev, dict) and str(prev.get("class")).lower() == "sum"
|
||||||
|
else {}
|
||||||
|
)
|
||||||
|
|
||||||
# Бывают инверсии порядка (на всякий случай): попробуем найти явно
|
# Бывают инверсии порядка (на всякий случай): попробуем найти явно
|
||||||
if not season_avg or not season_sum:
|
if not season_avg or not season_sum:
|
||||||
for x in reversed(stats_list):
|
for x in reversed(stats_list):
|
||||||
if isinstance(x, dict) and str(x.get("class")).lower() == "avg" and not season_avg:
|
if (
|
||||||
|
isinstance(x, dict)
|
||||||
|
and str(x.get("class")).lower() == "avg"
|
||||||
|
and not season_avg
|
||||||
|
):
|
||||||
season_avg = x.get("stats", {}) or {}
|
season_avg = x.get("stats", {}) or {}
|
||||||
if isinstance(x, dict) and str(x.get("class")).lower() == "sum" and not season_sum:
|
if (
|
||||||
|
isinstance(x, dict)
|
||||||
|
and str(x.get("class")).lower() == "sum"
|
||||||
|
and not season_sum
|
||||||
|
):
|
||||||
season_sum = x.get("stats", {}) or {}
|
season_sum = x.get("stats", {}) or {}
|
||||||
if season_avg and season_sum:
|
if season_avg and season_sum:
|
||||||
break
|
break
|
||||||
@@ -1736,7 +1876,9 @@ async def team(who: str):
|
|||||||
full_stat = get_latest_game_safe("pregame-full-stats")
|
full_stat = get_latest_game_safe("pregame-full-stats")
|
||||||
if not full_stat:
|
if not full_stat:
|
||||||
# ⚠️ full_stat_data отсутствует — работаем только с game_data
|
# ⚠️ full_stat_data отсутствует — работаем только с game_data
|
||||||
logger.debug(f"[{who}] full_stat_data not found → continuing with game_data only")
|
logger.debug(
|
||||||
|
f"[{who}] full_stat_data not found → continuing with game_data only"
|
||||||
|
)
|
||||||
full_stat_data = {}
|
full_stat_data = {}
|
||||||
else:
|
else:
|
||||||
full_stat_data = full_stat["data"] if "data" in full_stat else full_stat
|
full_stat_data = full_stat["data"] if "data" in full_stat else full_stat
|
||||||
@@ -1784,14 +1926,20 @@ async def team(who: str):
|
|||||||
for item in starts:
|
for item in starts:
|
||||||
stats = item.get("stats") or {}
|
stats = item.get("stats") or {}
|
||||||
pid = str(item.get("personId"))
|
pid = str(item.get("personId"))
|
||||||
full_obj = next((p for p in (payload_full or []) if str(p.get("personId")) == pid), None)
|
full_obj = next(
|
||||||
|
(p for p in (payload_full or []) if str(p.get("personId")) == pid), None
|
||||||
|
)
|
||||||
|
|
||||||
season_sum = season_avg = career_sum = career_avg = {}
|
season_sum = season_avg = career_sum = career_avg = {}
|
||||||
if full_obj:
|
if full_obj:
|
||||||
# сезон
|
# сезон
|
||||||
season_sum, season_avg = _pick_last_avg_and_sum(full_obj.get("seasonStats") or [])
|
season_sum, season_avg = _pick_last_avg_and_sum(
|
||||||
|
full_obj.get("seasonStats") or []
|
||||||
|
)
|
||||||
# карьера
|
# карьера
|
||||||
career_sum, career_avg = _pick_career_sum_and_avg(full_obj.get("carrier") or [])
|
career_sum, career_avg = _pick_career_sum_and_avg(
|
||||||
|
full_obj.get("carrier") or []
|
||||||
|
)
|
||||||
|
|
||||||
season_sum = _safe(season_sum)
|
season_sum = _safe(season_sum)
|
||||||
season_avg = _safe(season_avg)
|
season_avg = _safe(season_avg)
|
||||||
@@ -1874,7 +2022,9 @@ async def team(who: str):
|
|||||||
car_T_block = car_ss_blk + _as_int(stats.get("block"))
|
car_T_block = car_ss_blk + _as_int(stats.get("block"))
|
||||||
car_T_dreb = car_ss_dreb + _as_int(stats.get("defReb"))
|
car_T_dreb = car_ss_dreb + _as_int(stats.get("defReb"))
|
||||||
car_T_oreb = car_ss_oreb + _as_int(stats.get("offReb"))
|
car_T_oreb = car_ss_oreb + _as_int(stats.get("offReb"))
|
||||||
car_T_reb = car_ss_reb + (_as_int(stats.get("defReb")) + _as_int(stats.get("offReb")))
|
car_T_reb = car_ss_reb + (
|
||||||
|
_as_int(stats.get("defReb")) + _as_int(stats.get("offReb"))
|
||||||
|
)
|
||||||
car_T_steal = car_ss_stl + _as_int(stats.get("steal"))
|
car_T_steal = car_ss_stl + _as_int(stats.get("steal"))
|
||||||
car_T_turn = car_ss_to + _as_int(stats.get("turnover"))
|
car_T_turn = car_ss_to + _as_int(stats.get("turnover"))
|
||||||
car_T_foul = car_ss_foul + _as_int(stats.get("foul"))
|
car_T_foul = car_ss_foul + _as_int(stats.get("foul"))
|
||||||
@@ -1922,8 +2072,8 @@ async def team(who: str):
|
|||||||
and item.get("lastName") is not None
|
and item.get("lastName") is not None
|
||||||
else "Команда"
|
else "Команда"
|
||||||
),
|
),
|
||||||
"Name1GFX": (item.get('firstName') or '').strip(),
|
"Name1GFX": (item.get("firstName") or "").strip(),
|
||||||
"Name2GFX": (item.get('lastName') or '').strip(),
|
"Name2GFX": (item.get("lastName") or "").strip(),
|
||||||
"captain": item.get("isCapitan", False),
|
"captain": item.get("isCapitan", False),
|
||||||
"age": item.get("age") or 0,
|
"age": item.get("age") or 0,
|
||||||
"height": f"{item.get('height')} cm" if item.get("height") else 0,
|
"height": f"{item.get('height')} cm" if item.get("height") else 0,
|
||||||
@@ -1983,14 +2133,19 @@ async def team(who: str):
|
|||||||
"dunk": _as_int(stats.get("dunk")),
|
"dunk": _as_int(stats.get("dunk")),
|
||||||
"kpi": (
|
"kpi": (
|
||||||
_as_int(stats.get("points"))
|
_as_int(stats.get("points"))
|
||||||
+ _as_int(stats.get("defReb")) + _as_int(stats.get("offReb"))
|
+ _as_int(stats.get("defReb"))
|
||||||
+ _as_int(stats.get("assist")) + _as_int(stats.get("steal")) + _as_int(stats.get("block"))
|
+ _as_int(stats.get("offReb"))
|
||||||
|
+ _as_int(stats.get("assist"))
|
||||||
|
+ _as_int(stats.get("steal"))
|
||||||
|
+ _as_int(stats.get("block"))
|
||||||
+ _as_int(stats.get("foulsOn"))
|
+ _as_int(stats.get("foulsOn"))
|
||||||
+ (g1 - s1) + (g2 - s2) + (g3 - s3)
|
+ (g1 - s1)
|
||||||
- _as_int(stats.get("turnover")) - _as_int(stats.get("foul"))
|
+ (g2 - s2)
|
||||||
|
+ (g3 - s3)
|
||||||
|
- _as_int(stats.get("turnover"))
|
||||||
|
- _as_int(stats.get("foul"))
|
||||||
),
|
),
|
||||||
"time": format_time(_as_int(stats.get("second"))),
|
"time": format_time(_as_int(stats.get("second"))),
|
||||||
|
|
||||||
# сезон — средние (из последнего Avg)
|
# сезон — средние (из последнего Avg)
|
||||||
"AvgPoints": season_avg.get("points") or "0.0",
|
"AvgPoints": season_avg.get("points") or "0.0",
|
||||||
"AvgAssist": season_avg.get("assist") or "0.0",
|
"AvgAssist": season_avg.get("assist") or "0.0",
|
||||||
@@ -2008,7 +2163,6 @@ async def team(who: str):
|
|||||||
"Shot2Percent": season_avg.get("shot2Percent") or "0.0%",
|
"Shot2Percent": season_avg.get("shot2Percent") or "0.0%",
|
||||||
"Shot3Percent": season_avg.get("shot3Percent") or "0.0%",
|
"Shot3Percent": season_avg.get("shot3Percent") or "0.0%",
|
||||||
"Shot23Percent": season_avg.get("shot23Percent") or "0.0%",
|
"Shot23Percent": season_avg.get("shot23Percent") or "0.0%",
|
||||||
|
|
||||||
# сезон — Totals (суммы из Sum + live)
|
# сезон — Totals (суммы из Sum + live)
|
||||||
"TPoints": T_points,
|
"TPoints": T_points,
|
||||||
"TShots1": f"{T_g1}/{T_s1}",
|
"TShots1": f"{T_g1}/{T_s1}",
|
||||||
@@ -2030,7 +2184,6 @@ async def team(who: str):
|
|||||||
"TPlayedTime": format_time(T_sec),
|
"TPlayedTime": format_time(T_sec),
|
||||||
"TGameCount": T_gms,
|
"TGameCount": T_gms,
|
||||||
"TStartCount": T_starts,
|
"TStartCount": T_starts,
|
||||||
|
|
||||||
# карьера — средние (из последнего Avg)
|
# карьера — средние (из последнего Avg)
|
||||||
"Career_AvgPoints": career_avg.get("points") or "0.0",
|
"Career_AvgPoints": career_avg.get("points") or "0.0",
|
||||||
"Career_AvgAssist": career_avg.get("assist") or "0.0",
|
"Career_AvgAssist": career_avg.get("assist") or "0.0",
|
||||||
@@ -2048,7 +2201,6 @@ async def team(who: str):
|
|||||||
"Career_Shot2Percent": career_avg.get("shot2Percent") or "0.0%",
|
"Career_Shot2Percent": career_avg.get("shot2Percent") or "0.0%",
|
||||||
"Career_Shot3Percent": career_avg.get("shot3Percent") or "0.0%",
|
"Career_Shot3Percent": career_avg.get("shot3Percent") or "0.0%",
|
||||||
"Career_Shot23Percent": career_avg.get("shot23Percent") or "0.0%",
|
"Career_Shot23Percent": career_avg.get("shot23Percent") or "0.0%",
|
||||||
|
|
||||||
# карьера — Totals (суммы из Sum + live)
|
# карьера — Totals (суммы из Sum + live)
|
||||||
"Career_TPoints": car_T_points,
|
"Career_TPoints": car_T_points,
|
||||||
"Career_TShots1": f"{car_T_g1}/{car_T_s1}",
|
"Career_TShots1": f"{car_T_g1}/{car_T_s1}",
|
||||||
@@ -2070,7 +2222,6 @@ async def team(who: str):
|
|||||||
"Career_TPlayedTime": format_time(car_T_sec),
|
"Career_TPlayedTime": format_time(car_T_sec),
|
||||||
"Career_TGameCount": car_T_gms,
|
"Career_TGameCount": car_T_gms,
|
||||||
"Career_TStartCount": car_T_starts,
|
"Career_TStartCount": car_T_starts,
|
||||||
|
|
||||||
}
|
}
|
||||||
team_rows.append(row)
|
team_rows.append(row)
|
||||||
|
|
||||||
@@ -2132,14 +2283,6 @@ async def team(who: str):
|
|||||||
|
|
||||||
|
|
||||||
async def started_team(data):
|
async def started_team(data):
|
||||||
# started_team = sorted(
|
|
||||||
# (
|
|
||||||
# p
|
|
||||||
# for p in data
|
|
||||||
# if p.get("startRole") == "Player" and p.get("isStart") is True
|
|
||||||
# ),
|
|
||||||
# key=lambda x: int(x.get("num") or 0),
|
|
||||||
# )
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@@ -2397,7 +2540,7 @@ async def team_stats():
|
|||||||
"val2": val2,
|
"val2": val2,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return result_json
|
return maybe_clear_for_vmix(result_json)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/referee")
|
@app.get("/referee")
|
||||||
@@ -2449,7 +2592,7 @@ async def referee():
|
|||||||
else len(desired_order)
|
else len(desired_order)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return referees
|
return maybe_clear_for_vmix(referees)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/team_comparison")
|
@app.get("/team_comparison")
|
||||||
@@ -2523,7 +2666,7 @@ async def team_comparison():
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
teams.append(temp_team)
|
teams.append(temp_team)
|
||||||
return teams
|
return maybe_clear_for_vmix(teams)
|
||||||
else:
|
else:
|
||||||
return [{"Данных о сравнении команд нет!"}]
|
return [{"Данных о сравнении команд нет!"}]
|
||||||
|
|
||||||
@@ -2594,7 +2737,7 @@ async def regular_standings():
|
|||||||
df["plus_minus"] = tg_plus - tg_minus
|
df["plus_minus"] = tg_plus - tg_minus
|
||||||
|
|
||||||
standings_payload = df.to_dict(orient="records")
|
standings_payload = df.to_dict(orient="records")
|
||||||
return standings_payload
|
return maybe_clear_for_vmix(standings_payload)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/live_status")
|
@app.get("/live_status")
|
||||||
@@ -2605,7 +2748,7 @@ async def live_status():
|
|||||||
|
|
||||||
if not ls:
|
if not ls:
|
||||||
# live-status ещё не прилетел
|
# live-status ещё не прилетел
|
||||||
return [{"foulsA": 0, "foulsB": 0}]
|
return maybe_clear_for_vmix([{"foulsA": 0, "foulsB": 0, "scoreA": 0, "scoreB": 0}])
|
||||||
|
|
||||||
raw = ls.get("data")
|
raw = ls.get("data")
|
||||||
|
|
||||||
@@ -2613,20 +2756,20 @@ async def live_status():
|
|||||||
if isinstance(raw, dict):
|
if isinstance(raw, dict):
|
||||||
# иногда API кладёт всё прямо в root, иногда внутрь result
|
# иногда API кладёт всё прямо в root, иногда внутрь result
|
||||||
if "result" in raw and isinstance(raw["result"], dict):
|
if "result" in raw and isinstance(raw["result"], dict):
|
||||||
return [raw["result"]]
|
return maybe_clear_for_vmix([raw["result"]])
|
||||||
else:
|
else:
|
||||||
# отдадим как есть, но в списке, чтобы фронт не сломать
|
# отдадим как есть, но в списке, чтобы фронт не сломать
|
||||||
return [raw]
|
return maybe_clear_for_vmix([raw])
|
||||||
|
|
||||||
# 2) если это просто строка статуса ("ok" / "no-status" / "error")
|
# 2) если это просто строка статуса ("ok" / "no-status" / "error")
|
||||||
if isinstance(raw, str):
|
if isinstance(raw, str):
|
||||||
return [{"status": raw}]
|
return maybe_clear_for_vmix([{"status": raw}])
|
||||||
|
|
||||||
# fallback
|
# fallback
|
||||||
return [{"foulsA": 0, "foulsB": 0}]
|
return maybe_clear_for_vmix([{"foulsA": 0, "foulsB": 0, "scoreA": 0, "scoreB": 0}])
|
||||||
else:
|
else:
|
||||||
# матч не идёт — как у тебя было
|
# матч не идёт — как у тебя было
|
||||||
return [{"foulsA": 0, "foulsB": 0}]
|
return maybe_clear_for_vmix([{"foulsA": 0, "foulsB": 0, "scoreA": 0, "scoreB": 0}])
|
||||||
|
|
||||||
|
|
||||||
@app.get("/info")
|
@app.get("/info")
|
||||||
@@ -2653,8 +2796,7 @@ async def info():
|
|||||||
full_format = date_obj.strftime("%A, %#d %B %Y")
|
full_format = date_obj.strftime("%A, %#d %B %Y")
|
||||||
short_format = date_obj.strftime("%A, %#d %b")
|
short_format = date_obj.strftime("%A, %#d %b")
|
||||||
|
|
||||||
|
return maybe_clear_for_vmix([
|
||||||
return [
|
|
||||||
{
|
{
|
||||||
"team1": team1_name,
|
"team1": team1_name,
|
||||||
"team2": team2_name,
|
"team2": team2_name,
|
||||||
@@ -2672,7 +2814,7 @@ async def info():
|
|||||||
"date1": str(full_format),
|
"date1": str(full_format),
|
||||||
"date2": str(short_format),
|
"date2": str(short_format),
|
||||||
}
|
}
|
||||||
]
|
])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user