в процессе логирования
This commit is contained in:
452
get_data.py
452
get_data.py
@@ -9,13 +9,14 @@ import time
|
||||
import queue
|
||||
import argparse
|
||||
import uvicorn
|
||||
from pprint import pprint
|
||||
import os
|
||||
import pandas as pd
|
||||
import json
|
||||
from datetime import datetime, time as dtime, timedelta
|
||||
from fastapi.responses import Response
|
||||
|
||||
import logging
|
||||
import logging.config
|
||||
import platform
|
||||
|
||||
# передадим параметры через аргументы или глобальные переменные
|
||||
|
||||
@@ -26,16 +27,72 @@ parser.add_argument("--team", required=True)
|
||||
parser.add_argument("--lang", default="en")
|
||||
args = parser.parse_args()
|
||||
|
||||
MYHOST = platform.node()
|
||||
|
||||
if not os.path.exists("logs"):
|
||||
os.makedirs("logs")
|
||||
|
||||
telegram_bot_token = "7639240596:AAH0YtdQoWZSC-_R_EW4wKAHHNLIA0F_ARY"
|
||||
# TELEGRAM_CHAT_ID = 228977654
|
||||
telegram_chat_id = -4803699526
|
||||
log_config = {
|
||||
"version": 1,
|
||||
"handlers": {
|
||||
"telegram": {
|
||||
"class": "telegram_handler.TelegramHandler",
|
||||
"level": "INFO",
|
||||
"token": telegram_bot_token,
|
||||
"chat_id": telegram_chat_id,
|
||||
"formatter": "telegram",
|
||||
},
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"level": "INFO",
|
||||
"formatter": "simple",
|
||||
"stream": "ext://sys.stdout",
|
||||
},
|
||||
"file": {
|
||||
"class": "logging.FileHandler",
|
||||
"level": "DEBUG",
|
||||
"formatter": "simple",
|
||||
"filename": f"logs/GFX_{MYHOST}.log",
|
||||
"encoding": "utf-8",
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
__name__: {"handlers": ["console", "file", "telegram"], "level": "DEBUG"},
|
||||
},
|
||||
"formatters": {
|
||||
"telegram": {
|
||||
"class": "telegram_handler.HtmlFormatter",
|
||||
"format": f"%(levelname)s [{MYHOST.upper()}] %(message)s",
|
||||
"use_emoji": "True",
|
||||
},
|
||||
"simple": {
|
||||
"class": "logging.Formatter",
|
||||
"format": "%(asctime)s %(levelname)-8s %(funcName)s() - %(message)s",
|
||||
"datefmt": "%d.%m.%Y %H:%M:%S",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
logging.config.dictConfig(log_config)
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.handlers[2].formatter.use_emoji = True
|
||||
|
||||
|
||||
|
||||
|
||||
LEAGUE = args.league
|
||||
TEAM = args.team
|
||||
LANG = args.lang
|
||||
HOST = "https://pro.russiabasket.org"
|
||||
HOST = "https://deti.russiabasket.org"
|
||||
STATUS = False
|
||||
GAME_ID = None
|
||||
SEASON = None
|
||||
GAME_START_DT = None # datetime начала матча (локальная из календаря)
|
||||
GAME_TODAY = False # флаг: игра сегодня
|
||||
GAME_SOON = False # флаг: игра сегодня и скоро (<1 часа)
|
||||
GAME_START_DT = None # datetime начала матча (локальная из календаря)
|
||||
GAME_TODAY = False # флаг: игра сегодня
|
||||
GAME_SOON = False # флаг: игра сегодня и скоро (<1 часа)
|
||||
|
||||
URLS = {
|
||||
"seasons": "{host}/api/abc/comps/seasons?Tag={league}",
|
||||
@@ -50,6 +107,158 @@ URLS = {
|
||||
}
|
||||
|
||||
|
||||
def start_offline_threads(season, game_id):
|
||||
"""Запускаем редкие запросы, когда матча нет или он уже сыгран."""
|
||||
global threads_offline, CURRENT_THREADS_MODE, stop_event_offline
|
||||
|
||||
# если уже работаем в офлайне — не дублируем
|
||||
if CURRENT_THREADS_MODE == "offline":
|
||||
return
|
||||
|
||||
# на всякий случай гасим лайв
|
||||
stop_live_threads()
|
||||
|
||||
stop_event_offline.clear()
|
||||
threads_offline = [
|
||||
threading.Thread(
|
||||
target=get_data_from_API,
|
||||
args=(
|
||||
"game",
|
||||
URLS["game"].format(host=HOST, game_id=game_id, lang=LANG),
|
||||
1, # раз в секунду/реже
|
||||
stop_event_offline,
|
||||
),
|
||||
daemon=True,
|
||||
)
|
||||
]
|
||||
for t in threads_offline:
|
||||
t.start()
|
||||
|
||||
CURRENT_THREADS_MODE = "offline"
|
||||
print("[threads] OFFLINE threads started")
|
||||
|
||||
|
||||
def start_live_threads(season, game_id):
|
||||
"""Запускаем частые онлайн-запросы, когда матч идёт/вот-вот."""
|
||||
global threads_live, CURRENT_THREADS_MODE, stop_event_live
|
||||
|
||||
# если уже в лайве — не дублируем
|
||||
if CURRENT_THREADS_MODE == "live":
|
||||
return
|
||||
|
||||
# на всякий случай гасим офлайн
|
||||
stop_offline_threads()
|
||||
|
||||
stop_event_live.clear()
|
||||
threads_live = [
|
||||
threading.Thread(
|
||||
target=get_data_from_API,
|
||||
args=(
|
||||
"pregame",
|
||||
URLS["pregame"].format(
|
||||
host=HOST, league=LEAGUE, season=season, game_id=game_id, lang=LANG
|
||||
),
|
||||
0.0016667,
|
||||
stop_event_live,
|
||||
),
|
||||
daemon=True,
|
||||
),
|
||||
threading.Thread(
|
||||
target=get_data_from_API,
|
||||
args=(
|
||||
"pregame-full-stats",
|
||||
URLS["pregame-full-stats"].format(
|
||||
host=HOST, league=LEAGUE, season=season, game_id=game_id, lang=LANG
|
||||
),
|
||||
0.0016667,
|
||||
stop_event_live,
|
||||
),
|
||||
daemon=True,
|
||||
),
|
||||
threading.Thread(
|
||||
target=get_data_from_API,
|
||||
args=(
|
||||
"actual-standings",
|
||||
URLS["actual-standings"].format(
|
||||
host=HOST, league=LEAGUE, season=season, lang=LANG
|
||||
),
|
||||
0.0016667,
|
||||
stop_event_live,
|
||||
),
|
||||
daemon=True,
|
||||
),
|
||||
threading.Thread(
|
||||
target=get_data_from_API,
|
||||
args=(
|
||||
"game",
|
||||
URLS["game"].format(host=HOST, game_id=game_id, lang=LANG),
|
||||
0.00016, # часто
|
||||
stop_event_live,
|
||||
),
|
||||
daemon=True,
|
||||
),
|
||||
threading.Thread(
|
||||
target=get_data_from_API,
|
||||
args=(
|
||||
"live-status",
|
||||
URLS["live-status"].format(host=HOST, game_id=game_id),
|
||||
1,
|
||||
stop_event_live,
|
||||
),
|
||||
daemon=True,
|
||||
),
|
||||
threading.Thread(
|
||||
target=get_data_from_API,
|
||||
args=(
|
||||
"box-score",
|
||||
URLS["box-score"].format(host=HOST, game_id=game_id),
|
||||
1,
|
||||
stop_event_live,
|
||||
),
|
||||
daemon=True,
|
||||
),
|
||||
threading.Thread(
|
||||
target=get_data_from_API,
|
||||
args=(
|
||||
"play-by-play",
|
||||
URLS["play-by-play"].format(host=HOST, game_id=game_id),
|
||||
1,
|
||||
stop_event_live,
|
||||
),
|
||||
daemon=True,
|
||||
),
|
||||
]
|
||||
for t in threads_live:
|
||||
t.start()
|
||||
|
||||
CURRENT_THREADS_MODE = "live"
|
||||
print("[threads] LIVE threads started")
|
||||
|
||||
|
||||
def stop_live_threads():
|
||||
"""Гасим только live-треды."""
|
||||
global threads_live
|
||||
if not threads_live:
|
||||
return
|
||||
stop_event_live.set()
|
||||
for t in threads_live:
|
||||
t.join(timeout=1)
|
||||
threads_live = []
|
||||
print("[threads] LIVE threads stopped")
|
||||
|
||||
|
||||
def stop_offline_threads():
|
||||
"""Гасим только offline-треды."""
|
||||
global threads_offline
|
||||
if not threads_offline:
|
||||
return
|
||||
stop_event_offline.set()
|
||||
for t in threads_offline:
|
||||
t.join(timeout=1)
|
||||
threads_offline = []
|
||||
print("[threads] OFFLINE threads stopped")
|
||||
|
||||
|
||||
# общая очередь
|
||||
results_q = queue.Queue()
|
||||
# тут будем хранить последние данные
|
||||
@@ -57,6 +266,17 @@ latest_data = {}
|
||||
# событие для остановки потоков
|
||||
stop_event = threading.Event()
|
||||
|
||||
# отдельные события для разных наборов потоков
|
||||
stop_event_live = threading.Event()
|
||||
stop_event_offline = threading.Event()
|
||||
|
||||
# чтобы из consumer можно было их гасить
|
||||
threads_live = []
|
||||
threads_offline = []
|
||||
|
||||
# какой режим сейчас запущен: "live" / "offline" / None
|
||||
CURRENT_THREADS_MODE = None
|
||||
|
||||
|
||||
# Функция запускаемая в потоках
|
||||
def get_data_from_API(
|
||||
@@ -133,7 +353,9 @@ def results_consumer():
|
||||
and "teams" in payload["result"]
|
||||
):
|
||||
# обновляем команды
|
||||
game["data"]["result"]["game"]["fullScore"] = payload["result"]["fullScore"]
|
||||
game["data"]["result"]["game"]["fullScore"] = payload["result"][
|
||||
"fullScore"
|
||||
]
|
||||
for team in game["data"]["result"]["teams"]:
|
||||
if team["teamNumber"] != 0:
|
||||
box_team = [
|
||||
@@ -169,17 +391,13 @@ def results_consumer():
|
||||
}
|
||||
|
||||
elif "live-status" in source:
|
||||
# просто сохраним, как и остальные
|
||||
latest_data[source] = {
|
||||
"ts": msg["ts"],
|
||||
"data": payload,
|
||||
}
|
||||
|
||||
# попытка ДОПОЛНИТЕЛЬНО обновить глобальный STATUS по live-status
|
||||
try:
|
||||
ls_data = payload.get("result") or payload # иногда сразу result
|
||||
# тут нужно посмотреть, какое именно поле у тебя в live-status
|
||||
# допустим, там есть что-то вроде "status" или "gameStatus"
|
||||
ls_data = payload.get("result") or payload
|
||||
raw_ls_status = (
|
||||
ls_data.get("status")
|
||||
or ls_data.get("gameStatus")
|
||||
@@ -187,27 +405,42 @@ def results_consumer():
|
||||
)
|
||||
|
||||
if raw_ls_status:
|
||||
raw_ls_status = str(raw_ls_status).lower()
|
||||
raw_ls_status_low = str(raw_ls_status).lower()
|
||||
|
||||
# варианты, которые считаем "матч окончен"
|
||||
finished_markers = [
|
||||
"finished",
|
||||
"result",
|
||||
"resultconfirmed",
|
||||
"ended",
|
||||
"game over",
|
||||
"final",
|
||||
"game over",
|
||||
]
|
||||
|
||||
if any(m in raw_ls_status for m in finished_markers):
|
||||
# перезатираем глобальный статус — он более актуальный
|
||||
# если матч сегодня — делаем finished_today, иначе просто finished
|
||||
from datetime import datetime
|
||||
|
||||
if GAME_START_DT and GAME_START_DT.date() == datetime.now().date():
|
||||
# матч ЗАКОНЧЕН → гасим live и включаем offline
|
||||
if any(m in raw_ls_status_low for m in finished_markers):
|
||||
print("[status] match finished → switch to OFFLINE")
|
||||
if (
|
||||
GAME_START_DT
|
||||
and GAME_START_DT.date() == datetime.now().date()
|
||||
):
|
||||
globals()["STATUS"] = "finished_today"
|
||||
else:
|
||||
globals()["STATUS"] = "finished"
|
||||
|
||||
stop_live_threads()
|
||||
start_offline_threads(SEASON, GAME_ID)
|
||||
|
||||
# матч СТАЛ онлайном (напр., из Scheduled → Online)
|
||||
elif (
|
||||
"online" in raw_ls_status_low or "live" in raw_ls_status_low
|
||||
):
|
||||
if globals().get("STATUS") not in ["live", "live_soon"]:
|
||||
print(
|
||||
"[status] match became LIVE → switch to LIVE threads"
|
||||
)
|
||||
globals()["STATUS"] = "live"
|
||||
start_live_threads(SEASON, GAME_ID)
|
||||
|
||||
except Exception as e:
|
||||
print("results_consumer: live-status postprocess error:", e)
|
||||
|
||||
@@ -238,7 +471,9 @@ def results_consumer():
|
||||
}
|
||||
else:
|
||||
# 👉 уже есть какой-то game — неполным НЕ затираем
|
||||
print("results_consumer: got partial game, keeping previous one")
|
||||
print(
|
||||
"results_consumer: got partial game, keeping previous one"
|
||||
)
|
||||
|
||||
# и обязательно continue/return из этого elif/if
|
||||
else:
|
||||
@@ -253,6 +488,7 @@ def results_consumer():
|
||||
print("results_consumer error:", repr(e))
|
||||
continue
|
||||
|
||||
|
||||
def get_items(data: dict) -> list:
|
||||
"""
|
||||
Мелкий хелпер: берём первый список в ответе API.
|
||||
@@ -268,6 +504,7 @@ def get_items(data: dict) -> list:
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def pick_game_for_team(calendar_json):
|
||||
"""
|
||||
Возвращает:
|
||||
@@ -292,7 +529,11 @@ def pick_game_for_team(calendar_json):
|
||||
continue
|
||||
|
||||
gdt = extract_game_datetime(game)
|
||||
gdate = gdt.date() if gdt else datetime.strptime(game["game"]["localDate"], "%d.%m.%Y").date()
|
||||
gdate = (
|
||||
gdt.date()
|
||||
if gdt
|
||||
else datetime.strptime(game["game"]["localDate"], "%d.%m.%Y").date()
|
||||
)
|
||||
|
||||
if gdate == today:
|
||||
cal_status = game["game"].get("gameStatus")
|
||||
@@ -308,7 +549,11 @@ def pick_game_for_team(calendar_json):
|
||||
continue
|
||||
|
||||
gdt = extract_game_datetime(game)
|
||||
gdate = gdt.date() if gdt else datetime.strptime(game["game"]["localDate"], "%d.%m.%Y").date()
|
||||
gdate = (
|
||||
gdt.date()
|
||||
if gdt
|
||||
else datetime.strptime(game["game"]["localDate"], "%d.%m.%Y").date()
|
||||
)
|
||||
|
||||
if gdate <= today:
|
||||
last_id = game["game"]["id"]
|
||||
@@ -319,7 +564,6 @@ def pick_game_for_team(calendar_json):
|
||||
return last_id, last_dt, False, last_status
|
||||
|
||||
|
||||
|
||||
def extract_game_datetime(game_item: dict) -> datetime | None:
|
||||
"""
|
||||
Из элемента календаря достаём datetime матча.
|
||||
@@ -336,7 +580,7 @@ def extract_game_datetime(game_item: dict) -> datetime | None:
|
||||
dt_time = dtime(hour=0, minute=0)
|
||||
return datetime.combine(dt_date, dt_time)
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -345,7 +589,9 @@ async def lifespan(app: FastAPI):
|
||||
|
||||
# 1. проверяем API: seasons
|
||||
try:
|
||||
seasons_resp = requests.get(URLS["seasons"].format(host=HOST, league=LEAGUE)).json()
|
||||
seasons_resp = requests.get(
|
||||
URLS["seasons"].format(host=HOST, league=LEAGUE)
|
||||
).json()
|
||||
season = seasons_resp["items"][0]["season"]
|
||||
except Exception:
|
||||
now = datetime.now()
|
||||
@@ -367,7 +613,9 @@ async def lifespan(app: FastAPI):
|
||||
calendar = None
|
||||
|
||||
# 3. определяем игру
|
||||
game_id, game_dt, is_today, cal_status = pick_game_for_team(calendar) if calendar else (None, None, False, None)
|
||||
game_id, game_dt, is_today, cal_status = (
|
||||
pick_game_for_team(calendar) if calendar else (None, None, False, None)
|
||||
)
|
||||
GAME_ID = game_id
|
||||
GAME_START_DT = game_dt
|
||||
GAME_TODAY = is_today
|
||||
@@ -381,7 +629,7 @@ async def lifespan(app: FastAPI):
|
||||
|
||||
# 5. Подготовим онлайн и офлайн наборы (как у тебя)
|
||||
threads_live = [
|
||||
threading.Thread(
|
||||
threading.Thread(
|
||||
target=get_data_from_API,
|
||||
args=(
|
||||
"pregame",
|
||||
@@ -417,7 +665,6 @@ async def lifespan(app: FastAPI):
|
||||
),
|
||||
daemon=True,
|
||||
),
|
||||
|
||||
threading.Thread(
|
||||
target=get_data_from_API,
|
||||
args=(
|
||||
@@ -472,66 +719,43 @@ async def lifespan(app: FastAPI):
|
||||
)
|
||||
]
|
||||
|
||||
# 6. Решение: сегодня / не сегодня
|
||||
# 5. решаем, что запускать
|
||||
if not is_today:
|
||||
# ИГРЫ СЕГОДНЯ НЕТ → крутим только офлайн
|
||||
STATUS = "no_game_today"
|
||||
for t in threads_offline:
|
||||
t.start()
|
||||
start_offline_threads(SEASON, GAME_ID)
|
||||
else:
|
||||
# игра сегодня
|
||||
if cal_status is None:
|
||||
# нет статуса в календаре — считаем, что ещё не началась
|
||||
STATUS = "today_not_started"
|
||||
for t in threads_offline:
|
||||
t.start()
|
||||
start_offline_threads(SEASON, GAME_ID)
|
||||
elif cal_status == "Scheduled":
|
||||
# ещё не началась
|
||||
# проверим, не меньше ли часа до начала
|
||||
if game_dt:
|
||||
delta = game_dt - datetime.now()
|
||||
if delta <= timedelta(hours=1):
|
||||
# уже скоро → можно запускать онлайн
|
||||
STATUS = "live_soon"
|
||||
GAME_SOON = True
|
||||
for t in threads_live:
|
||||
t.start()
|
||||
start_live_threads(SEASON, GAME_ID)
|
||||
else:
|
||||
# ещё далеко → офлайн, но говорим что сегодня
|
||||
STATUS = "today_not_started"
|
||||
for t in threads_offline:
|
||||
t.start()
|
||||
# и можно повесить будильник, как раньше
|
||||
start_offline_threads(SEASON, GAME_ID)
|
||||
else:
|
||||
# нет времени — просто офлайн, но сегодня
|
||||
STATUS = "today_not_started"
|
||||
for t in threads_offline:
|
||||
t.start()
|
||||
start_offline_threads(SEASON, GAME_ID)
|
||||
elif cal_status == "Online":
|
||||
# матч идёт → сразу онлайн
|
||||
STATUS = "live"
|
||||
GAME_SOON = False
|
||||
for t in threads_live:
|
||||
t.start()
|
||||
start_live_threads(SEASON, GAME_ID)
|
||||
elif cal_status in ["Result", "ResultConfirmed"]:
|
||||
# матч уже сыгран, но дата всё ещё сегодня
|
||||
STATUS = "finished_today"
|
||||
for t in threads_offline:
|
||||
t.start()
|
||||
start_offline_threads(SEASON, GAME_ID)
|
||||
else:
|
||||
# неизвестный статус — безопасный вариант
|
||||
STATUS = "today_not_started"
|
||||
for t in threads_offline:
|
||||
t.start()
|
||||
start_offline_threads(SEASON, GAME_ID)
|
||||
|
||||
yield
|
||||
|
||||
|
||||
# -------- shutdown --------
|
||||
stop_event.set()
|
||||
# офлайн/онлайн ты можешь не делить тут, но оставлю
|
||||
stop_event.set()
|
||||
for t in threads_live + threads_offline:
|
||||
t.join(timeout=1)
|
||||
stop_event.set() # завершить consumer
|
||||
stop_live_threads()
|
||||
stop_offline_threads()
|
||||
thread_result_consumer.join(timeout=1)
|
||||
|
||||
|
||||
@@ -554,7 +778,7 @@ def format_time(seconds: float | int) -> str:
|
||||
return "0:00"
|
||||
|
||||
|
||||
@app.get("/team1.json")
|
||||
@app.get("/team1")
|
||||
async def team1():
|
||||
game = get_latest_game_safe()
|
||||
if not game:
|
||||
@@ -562,7 +786,7 @@ async def team1():
|
||||
return await team("team1")
|
||||
|
||||
|
||||
@app.get("/team2.json")
|
||||
@app.get("/team2")
|
||||
async def team2():
|
||||
game = get_latest_game_safe()
|
||||
if not game:
|
||||
@@ -570,36 +794,36 @@ async def team2():
|
||||
return await team("team2")
|
||||
|
||||
|
||||
@app.get("/top_team1.json")
|
||||
@app.get("/top_team1")
|
||||
async def top_team1():
|
||||
data = await team("team1")
|
||||
return await top_sorted_team(data)
|
||||
|
||||
|
||||
@app.get("/top_team2.json")
|
||||
@app.get("/top_team2")
|
||||
async def top_team2():
|
||||
data = await team("team2")
|
||||
return await top_sorted_team(data)
|
||||
|
||||
|
||||
@app.get("/started_team1.json")
|
||||
@app.get("/started_team1")
|
||||
async def started_team1():
|
||||
data = await team("team1")
|
||||
return await started_team(data)
|
||||
|
||||
|
||||
@app.get("/started_team2.json")
|
||||
@app.get("/started_team2")
|
||||
async def started_team2():
|
||||
data = await team("team2")
|
||||
return await started_team(data)
|
||||
|
||||
|
||||
@app.get("/game.json")
|
||||
@app.get("/game")
|
||||
async def game():
|
||||
return latest_data["game"]
|
||||
|
||||
|
||||
@app.get("/status.json")
|
||||
@app.get("/status")
|
||||
async def status(request: Request):
|
||||
def color_for_status(status_value: str) -> str:
|
||||
"""Подбор цвета статуса в HEX"""
|
||||
@@ -608,13 +832,25 @@ async def status(request: Request):
|
||||
return "#00FF00" # зелёный
|
||||
elif status_value in ["scheduled", "today_not_started", "upcoming"]:
|
||||
return "#FFFF00" # жёлтый
|
||||
elif status_value in ["result", "resultconfirmed", "finished", "finished_today"]:
|
||||
elif status_value in [
|
||||
"result",
|
||||
"resultconfirmed",
|
||||
"finished",
|
||||
"finished_today",
|
||||
]:
|
||||
return "#FF0000" # красный
|
||||
elif status_value in ["no_game_today", "unknown", "none"]:
|
||||
return "#FFFFFF" # белый
|
||||
else:
|
||||
return "#808080" # серый (неизвестный статус)
|
||||
|
||||
|
||||
# ✳️ сортируем latest_data в нужном порядке
|
||||
sort_order = ["game", "live-status", "box-score", "play-by-play"]
|
||||
sorted_keys = (
|
||||
[k for k in sort_order if k in latest_data] +
|
||||
sorted([k for k in latest_data if k not in sort_order])
|
||||
)
|
||||
|
||||
data = {
|
||||
"league": LEAGUE,
|
||||
"team": TEAM,
|
||||
@@ -624,16 +860,20 @@ async def status(request: Request):
|
||||
{
|
||||
"name": TEAM,
|
||||
"status": STATUS,
|
||||
"ts": GAME_START_DT.strftime("%Y-%m-%d %H:%M") if GAME_START_DT else "N/A",
|
||||
"ts": (
|
||||
GAME_START_DT.strftime("%Y-%m-%d %H:%M") if GAME_START_DT else "N/A"
|
||||
),
|
||||
"link": LEAGUE,
|
||||
"color": color_for_status(STATUS) # ← добавлено
|
||||
"color": color_for_status(STATUS),
|
||||
}
|
||||
] + [
|
||||
]
|
||||
+ [
|
||||
{
|
||||
"name": item,
|
||||
"status": (
|
||||
latest_data[item]["data"]["status"]
|
||||
if isinstance(latest_data[item]["data"], dict) and "status" in latest_data[item]["data"]
|
||||
if isinstance(latest_data[item]["data"], dict)
|
||||
and "status" in latest_data[item]["data"]
|
||||
else latest_data[item]["data"]
|
||||
),
|
||||
"ts": latest_data[item]["ts"],
|
||||
@@ -646,14 +886,15 @@ async def status(request: Request):
|
||||
),
|
||||
"color": color_for_status(
|
||||
latest_data[item]["data"]["status"]
|
||||
if isinstance(latest_data[item]["data"], dict) and "status" in latest_data[item]["data"]
|
||||
if isinstance(latest_data[item]["data"], dict)
|
||||
and "status" in latest_data[item]["data"]
|
||||
else latest_data[item]["data"]
|
||||
) # ← добавлено
|
||||
),
|
||||
}
|
||||
for item in latest_data
|
||||
for item in sorted_keys # ← используем отсортированный порядок
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
accept = request.headers.get("accept", "")
|
||||
if "text/html" in accept:
|
||||
status_raw = str(STATUS).lower()
|
||||
@@ -675,7 +916,7 @@ async def status(request: Request):
|
||||
else:
|
||||
gs_class = "unknown"
|
||||
gs_text = "⚪ UNKNOWN"
|
||||
|
||||
|
||||
html = f"""
|
||||
<html>
|
||||
<head>
|
||||
@@ -732,11 +973,19 @@ async def status(request: Request):
|
||||
for s in data["statuses"]:
|
||||
status_text = str(s["status"]).strip().lower()
|
||||
|
||||
if any(x in status_text for x in ["ok", "success", "live", "live_soon", "online"]):
|
||||
if any(
|
||||
x in status_text
|
||||
for x in ["ok", "success", "live", "live_soon", "online"]
|
||||
):
|
||||
color_class = "ok"
|
||||
elif any(x in status_text for x in ["scheduled", "today_not_started", "upcoming"]):
|
||||
elif any(
|
||||
x in status_text for x in ["scheduled", "today_not_started", "upcoming"]
|
||||
):
|
||||
color_class = "warn"
|
||||
elif any(x in status_text for x in ["result", "resultconfirmed", "finished", "finished_today"]):
|
||||
elif any(
|
||||
x in status_text
|
||||
for x in ["result", "resultconfirmed", "finished", "finished_today"]
|
||||
):
|
||||
color_class = "fail"
|
||||
else:
|
||||
color_class = "unknown"
|
||||
@@ -762,7 +1011,8 @@ async def status(request: Request):
|
||||
response.headers["Refresh"] = "1"
|
||||
return response
|
||||
|
||||
@app.get("/scores.json")
|
||||
|
||||
@app.get("/scores")
|
||||
async def scores():
|
||||
game = get_latest_game_safe()
|
||||
if not game:
|
||||
@@ -803,7 +1053,6 @@ async def scores():
|
||||
return score_by_quarter
|
||||
|
||||
|
||||
|
||||
async def top_sorted_team(data):
|
||||
top_sorted_team = sorted(
|
||||
(p for p in data if p.get("startRole") in ["Player", ""]),
|
||||
@@ -869,7 +1118,9 @@ async def team(who: str):
|
||||
|
||||
# нормализуем доступ к данным
|
||||
game_data = game["data"] if "data" in game else game
|
||||
result = game_data["result"] # здесь уже безопасно, мы проверили в get_latest_game_safe
|
||||
result = game_data[
|
||||
"result"
|
||||
] # здесь уже безопасно, мы проверили в get_latest_game_safe
|
||||
|
||||
# в result ожидаем "teams"
|
||||
teams = result.get("teams")
|
||||
@@ -1069,6 +1320,7 @@ async def team(who: str):
|
||||
|
||||
return sorted_team
|
||||
|
||||
|
||||
async def started_team(data):
|
||||
started_team = sorted(
|
||||
(
|
||||
@@ -1285,7 +1537,7 @@ stat_name_list = [
|
||||
]
|
||||
|
||||
|
||||
@app.get("/team_stats.json")
|
||||
@app.get("/team_stats")
|
||||
async def team_stats():
|
||||
teams = latest_data["game"]["data"]["result"]["teams"]
|
||||
plays = latest_data["game"]["data"]["result"]["plays"]
|
||||
@@ -1339,7 +1591,7 @@ async def team_stats():
|
||||
return result_json
|
||||
|
||||
|
||||
@app.get("/referee.json")
|
||||
@app.get("/referee")
|
||||
async def referee():
|
||||
desired_order = [
|
||||
"Crew chief",
|
||||
@@ -1391,7 +1643,7 @@ async def referee():
|
||||
return referees
|
||||
|
||||
|
||||
@app.get("/team_comparison.json")
|
||||
@app.get("/team_comparison")
|
||||
async def team_comparison():
|
||||
if STATUS not in ["no_game_today", "finished_today"]:
|
||||
data = latest_data["pregame"]["data"]["result"]
|
||||
@@ -1467,9 +1719,7 @@ async def team_comparison():
|
||||
return [{"Данных о сравнении команд нет!"}]
|
||||
|
||||
|
||||
|
||||
|
||||
@app.get("/standings.json")
|
||||
@app.get("/standings")
|
||||
async def regular_standings():
|
||||
data = latest_data["actual-standings"]["data"]["items"]
|
||||
for item in data:
|
||||
@@ -1540,7 +1790,7 @@ async def regular_standings():
|
||||
return standings_payload
|
||||
|
||||
|
||||
@app.get("/live_status.json")
|
||||
@app.get("/live_status")
|
||||
async def live_status():
|
||||
# если матч реально идёт/вот-вот — пытаемся отдать то, что есть
|
||||
if STATUS in ["live", "live_soon"]:
|
||||
@@ -1563,9 +1813,7 @@ async def live_status():
|
||||
|
||||
# 2) если это просто строка статуса ("ok" / "no-status" / "error")
|
||||
if isinstance(raw, str):
|
||||
return [{
|
||||
"status": raw
|
||||
}]
|
||||
return [{"status": raw}]
|
||||
|
||||
# fallback
|
||||
return [{"foulsA": 0, "foulsB": 0}]
|
||||
@@ -1575,4 +1823,4 @@ async def live_status():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run("get_data:app", host="0.0.0.0", port=8000, reload=True)
|
||||
uvicorn.run("get_data:app", host="0.0.0.0", port=8000, reload=True, log_level="critical")
|
||||
|
||||
Reference in New Issue
Block a user