добавленны создание онлайн/оффлайн фолов и поправил функцию на склеивание онлайн данных (нужно проверить)
This commit is contained in:
307
get_data.py
307
get_data.py
@@ -1,4 +1,3 @@
|
||||
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
@@ -72,9 +71,7 @@ DEFAULT_LANG = "en"
|
||||
# URL-шаблоны (замени на реальные)
|
||||
HOST = "ref.russiabasket.org"
|
||||
URL_SEASON = "https://{host}/api/abc/comps/seasons?Tag={league}&Lang={lang}" # вернёт JSON со списком сезонов
|
||||
URL_SCHEDULE = (
|
||||
"https://{host}/api/abc/comps/calendar?Tag={league}&Season={season}&Lang={lang}&MaxResultCount=1000" # расписание лиги (или команды)
|
||||
)
|
||||
URL_SCHEDULE = "https://{host}/api/abc/comps/calendar?Tag={league}&Season={season}&Lang={lang}&MaxResultCount=1000" # расписание лиги (или команды)
|
||||
# Статус конкретной игры (используется для проверки "онлайн?" раз в минуту)
|
||||
URL_GAME = "https://{host}/api/abc/games/game?Id={game_id}&lang={lang}"
|
||||
# Быстрые запросы, когда матч онлайн (каждую секунду)
|
||||
@@ -146,7 +143,9 @@ logger.handlers[2].formatter.use_emoji = True
|
||||
# ==========================
|
||||
|
||||
|
||||
def fetch_json(url: str, params: dict | None = None, session: requests.Session | None = None) -> dict:
|
||||
def fetch_json(
|
||||
url: str, params: dict | None = None, session: requests.Session | None = None
|
||||
) -> dict:
|
||||
"""
|
||||
GET JSON с таймаутом и внятными ошибками.
|
||||
Использует переданный session для keep-alive.
|
||||
@@ -237,7 +236,9 @@ def parse_game_start_dt(item: dict) -> datetime:
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Ошибка парсинга localDate/localTime '{ld} {lt}': {e}")
|
||||
|
||||
raise RuntimeError("Не найдено ни одного подходящего поля времени (defaultZoneDateTime/scheduledTime/startTime/localDate+localTime).")
|
||||
raise RuntimeError(
|
||||
"Не найдено ни одного подходящего поля времени (defaultZoneDateTime/scheduledTime/startTime/localDate+localTime)."
|
||||
)
|
||||
|
||||
|
||||
def extract_game_status(data: dict) -> str:
|
||||
@@ -254,21 +255,32 @@ def extract_game_status(data: dict) -> str:
|
||||
# ---- ДОП. ЗАПРОСЫ ПРИ ОНЛАЙНЕ
|
||||
# ==========================
|
||||
|
||||
def fetch_box_score(league: str, game_id: str, lang: str, session: requests.Session | None = None) -> dict:
|
||||
|
||||
def fetch_box_score(
|
||||
league: str, game_id: str, lang: str, session: requests.Session | None = None
|
||||
) -> dict:
|
||||
url = URL_BOX_SCORE.format(host=HOST, league=league, game_id=game_id, lang=lang)
|
||||
return fetch_json(url, session=session)
|
||||
|
||||
def fetch_play_by_play(league: str, game_id: str, lang: str, session: requests.Session | None = None) -> dict:
|
||||
|
||||
def fetch_play_by_play(
|
||||
league: str, game_id: str, lang: str, session: requests.Session | None = None
|
||||
) -> dict:
|
||||
url = URL_PLAY_BY_PLAY.format(host=HOST, league=league, game_id=game_id, lang=lang)
|
||||
return fetch_json(url, session=session)
|
||||
|
||||
def fetch_live_status(league: str, game_id: str, lang: str, session: requests.Session | None = None) -> dict:
|
||||
|
||||
def fetch_live_status(
|
||||
league: str, game_id: str, lang: str, session: requests.Session | None = None
|
||||
) -> dict:
|
||||
url = URL_LIVE_STATUS.format(host=HOST, league=league, game_id=game_id, lang=lang)
|
||||
return fetch_json(url, session=session)
|
||||
|
||||
|
||||
def _now_iso() -> str:
|
||||
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
||||
|
||||
|
||||
def _get(d: dict | None, *path, default=None):
|
||||
"""Безопасно достаём вложенные ключи: _get(d, "result", "fullScore", default={})"""
|
||||
cur = d or {}
|
||||
@@ -278,6 +290,7 @@ def _get(d: dict | None, *path, default=None):
|
||||
cur = cur[p]
|
||||
return cur
|
||||
|
||||
|
||||
def _dedup_plays(plays: List[dict]) -> List[dict]:
|
||||
"""
|
||||
Удаляем дубли по стабильному идентификатору события.
|
||||
@@ -298,9 +311,16 @@ def _dedup_plays(plays: List[dict]) -> List[dict]:
|
||||
seen.add(key)
|
||||
out.append(ev)
|
||||
# если есть поле sequence/time — отсортируем, чтобы обработчик получал стабильный порядок
|
||||
out.sort(key=lambda e: (e.get("sequence") is None, e.get("sequence"), e.get("time") or e.get("clock")))
|
||||
out.sort(
|
||||
key=lambda e: (
|
||||
e.get("sequence") is None,
|
||||
e.get("sequence"),
|
||||
e.get("time") or e.get("clock"),
|
||||
)
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def merge_online_payloads(
|
||||
game: dict,
|
||||
box_score: dict | None,
|
||||
@@ -312,41 +332,38 @@ def merge_online_payloads(
|
||||
Ничего не знает о внутренней логике обработки — только нормализует.
|
||||
"""
|
||||
# исходные куски
|
||||
plays_raw: List[dict] = _get(play_by_play, "result", default=[]) or []
|
||||
score_by_periods = _get(box_score, "result", "scoreByPeriods", default=[]) or []
|
||||
full_score = _get(box_score, "result", "fullScore", default={}) or {}
|
||||
teams = _get(box_score, "result", "teams", default={}) or {} # если пригодится в обработчике
|
||||
players = _get(box_score, "result", "players", default=[]) or []
|
||||
# live
|
||||
period = _get(live_status, "result", "period")
|
||||
clock = _get(live_status, "result", "clock")
|
||||
status = _get(live_status, "result", "status") # e.g., "inprogress", "ended", "scheduled"
|
||||
# plays_raw: List[dict] = _get(play_by_play, "result", default=[]) or []
|
||||
# score_by_periods = _get(box_score, "result", "scoreByPeriods", default=[]) or []
|
||||
# full_score = _get(box_score, "result", "fullScore", default={}) or {}
|
||||
# teams = _get(box_score, "result", "teams", default={}) or {} # если пригодится в обработчике
|
||||
# players = _get(box_score, "result", "players", default=[]) or []
|
||||
|
||||
# box_score = _get(box_score, "result", "teams", default=[]) or []
|
||||
# fullScore = _get(box_score, "result", "fullScore", default="") or ""
|
||||
|
||||
# # live
|
||||
# live_status = _get(live_status, "result", "live_status")
|
||||
# period = _get(live_status, "result", "period")
|
||||
# clock = _get(live_status, "result", "clock")
|
||||
# status = _get(live_status, "result", "status") # e.g., "inprogress", "ended", "scheduled"
|
||||
|
||||
# нормализация/дедуп
|
||||
plays = _dedup_plays(plays_raw)
|
||||
# plays = _dedup_plays(plays_raw)
|
||||
|
||||
game["result"]["plays"] = play_by_play.get("result", [])
|
||||
game["result"]["scoreByPeriods"] = box_score["result"].get("scoreByPeriods", [])
|
||||
game["result"]["fullScore"] = box_score["result"].get("fullScore", {})
|
||||
game["result"]["live_status"] = live_status["result"]
|
||||
|
||||
merged: Dict[str, Any] = {
|
||||
"meta": {
|
||||
"generatedAt": _now_iso(),
|
||||
"sourceHints": {
|
||||
"boxScoreHas": list((_get(box_score, "result") or {}).keys()),
|
||||
"pbpLen": len(plays),
|
||||
"pbpLen": "",
|
||||
},
|
||||
},
|
||||
"result": {
|
||||
# то, что просил: три ключа (плюс ещё полезные поля)
|
||||
"plays": plays,
|
||||
"scoreByPeriods": score_by_periods,
|
||||
"fullScore": full_score,
|
||||
# добавим live — обработчику пригодится
|
||||
"period": period,
|
||||
"clock": clock,
|
||||
"status": status,
|
||||
# опционально: передадим команды/игроков, если есть в box score
|
||||
"teams": teams,
|
||||
"players": players,
|
||||
},
|
||||
"result": game,
|
||||
}
|
||||
return merged
|
||||
|
||||
@@ -381,6 +398,7 @@ def is_already_merged(obj: dict) -> bool:
|
||||
and isinstance(r.get("scoreByPeriods", []), list)
|
||||
)
|
||||
|
||||
|
||||
def ensure_merged_payload(
|
||||
game_or_merged: dict | None = None,
|
||||
*,
|
||||
@@ -423,20 +441,26 @@ def ensure_merged_payload(
|
||||
},
|
||||
"result": g, # положим сырой ответ целиком — чтобы файл гарантированно записался
|
||||
}
|
||||
raise ValueError("ensure_merged_payload: не передан ни уже-склеенный game, ни box/pbp/live.")
|
||||
raise ValueError(
|
||||
"ensure_merged_payload: не передан ни уже-склеенный game, ни box/pbp/live."
|
||||
)
|
||||
|
||||
|
||||
def atomic_write_json(path: str | Path, data: dict, ensure_dirs: bool = True) -> None:
|
||||
path = Path(path)
|
||||
if ensure_dirs:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
# атомарная запись: пишем во временный файл и переименовываем
|
||||
with tempfile.NamedTemporaryFile("w", delete=False, dir=str(path.parent), encoding="utf-8") as tmp:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
"w", delete=False, dir=str(path.parent), encoding="utf-8"
|
||||
) as tmp:
|
||||
json.dump(data, tmp, ensure_ascii=False, indent=2)
|
||||
tmp.flush()
|
||||
os.fsync(tmp.fileno())
|
||||
tmp_name = tmp.name
|
||||
os.replace(tmp_name, path)
|
||||
|
||||
|
||||
def format_time(seconds: float | int) -> str:
|
||||
"""
|
||||
Форматирует время в секундах в строку "M:SS".
|
||||
@@ -456,7 +480,9 @@ def format_time(seconds: float | int) -> str:
|
||||
return "0:00"
|
||||
|
||||
|
||||
def Json_Team_Generation(merged: dict, *, out_dir: str = "static", who: str | None = None) -> None:
|
||||
def Json_Team_Generation(
|
||||
merged: dict, *, out_dir: str = "static", who: str | None = None
|
||||
) -> None:
|
||||
"""
|
||||
Единая точка: принимает уже нормализованный merged, делает нужные вычисления (если надо)
|
||||
и сохраняет в JSON.
|
||||
@@ -498,16 +524,12 @@ def Json_Team_Generation(merged: dict, *, out_dir: str = "static", who: str | No
|
||||
("Shooting Guard", "SG"),
|
||||
("Point Guard", "PG"),
|
||||
("Forward-Center", "FC"),
|
||||
]
|
||||
]
|
||||
starts = payload["starts"]
|
||||
team = []
|
||||
for item in starts:
|
||||
player = {
|
||||
"id": (
|
||||
item["personId"]
|
||||
if item["personId"]
|
||||
else ""
|
||||
),
|
||||
"id": (item["personId"] if item["personId"] else ""),
|
||||
"num": item["displayNumber"],
|
||||
"startRole": item["startRole"],
|
||||
"role": item["positionName"],
|
||||
@@ -517,29 +539,21 @@ def Json_Team_Generation(merged: dict, *, out_dir: str = "static", who: str | No
|
||||
for r in role_list
|
||||
if r[0].lower() == item["positionName"].lower()
|
||||
][0]
|
||||
if any(
|
||||
r[0].lower() == item["positionName"].lower()
|
||||
for r in role_list
|
||||
)
|
||||
if any(r[0].lower() == item["positionName"].lower() for r in role_list)
|
||||
else ""
|
||||
),
|
||||
"NameGFX": (
|
||||
f"{item['firstName'].strip()} {item['lastName'].strip()}"
|
||||
if item["firstName"] is not None
|
||||
and item["lastName"] is not None
|
||||
if item["firstName"] is not None and item["lastName"] is not None
|
||||
else "Команда"
|
||||
),
|
||||
"captain": item["isCapitan"],
|
||||
"age": item["age"] if item["age"] is not None else 0,
|
||||
"height": f'{item["height"]} cm' if item["height"] else 0,
|
||||
"weight": f'{item["weight"]} kg' if item["weight"] else 0,
|
||||
"isStart": (
|
||||
item["stats"]["isStart"] if item["stats"] else False
|
||||
),
|
||||
"isStart": (item["stats"]["isStart"] if item["stats"] else False),
|
||||
"isOn": (
|
||||
"🏀"
|
||||
if item["stats"] and item["stats"]["isOnCourt"] is True
|
||||
else ""
|
||||
"🏀" if item["stats"] and item["stats"]["isOnCourt"] is True else ""
|
||||
),
|
||||
"flag": f"https://flagicons.lipis.dev/flags/4x3/{'ru' if item['countryId'] is None and item['countryName'] == 'Russia' else '' if item['countryId'] is None else item['countryId'].lower() if item['countryName'] is not None else ''}.svg",
|
||||
"pts": item["stats"]["points"] if item["stats"] else 0,
|
||||
@@ -599,23 +613,15 @@ def Json_Team_Generation(merged: dict, *, out_dir: str = "static", who: str | No
|
||||
if item["stats"]
|
||||
else 0
|
||||
),
|
||||
"time": (
|
||||
format_time(item["stats"]["second"])
|
||||
if item["stats"]
|
||||
else "0:00"
|
||||
),
|
||||
"time": (format_time(item["stats"]["second"]) if item["stats"] else "0:00"),
|
||||
"pts1q": 0,
|
||||
"pts2q": 0,
|
||||
"pts3q": 0,
|
||||
"pts4q": 0,
|
||||
"pts1h": 0,
|
||||
"pts2h": 0,
|
||||
"Name1GFX": (
|
||||
item["firstName"].strip() if item["firstName"] else ""
|
||||
),
|
||||
"Name2GFX": (
|
||||
item["lastName"].strip() if item["lastName"] else ""
|
||||
),
|
||||
"Name1GFX": (item["firstName"].strip() if item["firstName"] else ""),
|
||||
"Name2GFX": (item["lastName"].strip() if item["lastName"] else ""),
|
||||
"photoGFX": (
|
||||
os.path.join(
|
||||
"D:\\Photos",
|
||||
@@ -629,9 +635,7 @@ def Json_Team_Generation(merged: dict, *, out_dir: str = "static", who: str | No
|
||||
else ""
|
||||
),
|
||||
# "season": text,
|
||||
"isOnCourt": (
|
||||
item["stats"]["isOnCourt"] if item["stats"] else False
|
||||
),
|
||||
"isOnCourt": (item["stats"]["isOnCourt"] if item["stats"] else False),
|
||||
# "AvgPoints": (
|
||||
# row_player_season_avg["points"]
|
||||
# if row_player_season_avg
|
||||
@@ -1012,9 +1016,7 @@ def Json_Team_Generation(merged: dict, *, out_dir: str = "static", who: str | No
|
||||
)
|
||||
for key in team[0].keys()
|
||||
}
|
||||
for _ in range(
|
||||
(4 if count_player <= 4 else 12) - count_player
|
||||
)
|
||||
for _ in range((4 if count_player <= 4 else 12) - count_player)
|
||||
]
|
||||
team.extend(empty_rows)
|
||||
role_priority = {
|
||||
@@ -1069,6 +1071,7 @@ def Json_Team_Generation(merged: dict, *, out_dir: str = "static", who: str | No
|
||||
atomic_write_json(out_path, started_team)
|
||||
logging.info("Сохранил payload: {out_path}")
|
||||
|
||||
|
||||
def time_outs_func(data_pbp: list[dict]) -> tuple[str, int, str, int]:
|
||||
"""
|
||||
Вычисляет количество оставшихся таймаутов для обеих команд
|
||||
@@ -1124,6 +1127,7 @@ def time_outs_func(data_pbp: list[dict]) -> tuple[str, int, str, int]:
|
||||
|
||||
return t1_str, t1_left, t2_str, t2_left
|
||||
|
||||
|
||||
def add_data_for_teams(new_data: list[dict]) -> tuple[float, list, float]:
|
||||
"""
|
||||
Возвращает усреднённые статистики команды:
|
||||
@@ -1174,6 +1178,7 @@ def add_data_for_teams(new_data: list[dict]) -> tuple[float, list, float]:
|
||||
points = [points_start, points_start_pro, points_bench, points_bench_pro]
|
||||
return avg_age, points, avg_height
|
||||
|
||||
|
||||
def add_new_team_stat(
|
||||
data: dict,
|
||||
avg_age: float,
|
||||
@@ -1243,6 +1248,7 @@ def add_new_team_stat(
|
||||
|
||||
return data
|
||||
|
||||
|
||||
stat_name_list = [
|
||||
("points", "Очки", "points"),
|
||||
("pt-1", "Штрафные", "free throws"),
|
||||
@@ -1305,22 +1311,14 @@ def Team_Both_Stat(merged: dict, *, out_dir: str = "static") -> None:
|
||||
# time.sleep()
|
||||
|
||||
# Таймауты
|
||||
timeout_str1, timeout_left1, timeout_str2, timeout_left2 = time_outs_func(
|
||||
plays
|
||||
)
|
||||
timeout_str1, timeout_left1, timeout_str2, timeout_left2 = time_outs_func(plays)
|
||||
|
||||
# Возраст, очки, рост
|
||||
avg_age_1, points_1, avg_height_1 = add_data_for_teams(
|
||||
team_1.get("starts", [])
|
||||
)
|
||||
avg_age_2, points_2, avg_height_2 = add_data_for_teams(
|
||||
team_2.get("starts", [])
|
||||
)
|
||||
avg_age_1, points_1, avg_height_1 = add_data_for_teams(team_1.get("starts", []))
|
||||
avg_age_2, points_2, avg_height_2 = add_data_for_teams(team_2.get("starts", []))
|
||||
|
||||
if not team_1.get("total") or not team_2.get("total"):
|
||||
logger.debug(
|
||||
"Нет total у команд — пропускаю перезапись team_stats.json"
|
||||
)
|
||||
logger.debug("Нет total у команд — пропускаю перезапись team_stats.json")
|
||||
|
||||
# Форматирование общей статистики (как и было)
|
||||
total_1 = add_new_team_stat(
|
||||
@@ -1340,19 +1338,14 @@ def Team_Both_Stat(merged: dict, *, out_dir: str = "static") -> None:
|
||||
timeout_left2,
|
||||
)
|
||||
|
||||
|
||||
# Финальный JSON
|
||||
result_json = []
|
||||
for key in total_1:
|
||||
val1 = (
|
||||
int(total_1[key])
|
||||
if isinstance(total_1[key], float)
|
||||
else total_1[key]
|
||||
int(total_1[key]) if isinstance(total_1[key], float) else total_1[key]
|
||||
)
|
||||
val2 = (
|
||||
int(total_2[key])
|
||||
if isinstance(total_2[key], float)
|
||||
else total_2[key]
|
||||
int(total_2[key]) if isinstance(total_2[key], float) else total_2[key]
|
||||
)
|
||||
stat_rus, stat_eng = "", ""
|
||||
for s in stat_name_list:
|
||||
@@ -1376,9 +1369,7 @@ def Team_Both_Stat(merged: dict, *, out_dir: str = "static") -> None:
|
||||
|
||||
logger.debug("Успешно записаны данные в team_stats.json")
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при обработке командной статистики: {e}", exc_info=True
|
||||
)
|
||||
logger.error(f"Ошибка при обработке командной статистики: {e}", exc_info=True)
|
||||
|
||||
|
||||
def Referee(merged: dict, *, out_dir: str = "static") -> None:
|
||||
@@ -1411,9 +1402,7 @@ def Referee(merged: dict, *, out_dir: str = "static") -> None:
|
||||
referees = []
|
||||
|
||||
for r in referees_raw:
|
||||
flag_code = (
|
||||
r.get("countryId", "").lower() if r.get("countryName") else ""
|
||||
)
|
||||
flag_code = r.get("countryId", "").lower() if r.get("countryName") else ""
|
||||
referees.append(
|
||||
{
|
||||
"displayNumber": r.get("displayNumber", ""),
|
||||
@@ -1454,9 +1443,7 @@ def Scores_Quarter(merged: dict, *, out_dir: str = "static") -> None:
|
||||
score_by_quarter = [{"Q": q, "score1": "", "score2": ""} for q in quarters]
|
||||
try:
|
||||
# Сначала пробуем fullScore
|
||||
full_score_str = (
|
||||
merged.get("result", {}).get("game", {}).get("fullScore", "")
|
||||
)
|
||||
full_score_str = merged.get("result", {}).get("game", {}).get("fullScore", "")
|
||||
if full_score_str:
|
||||
full_score_list = full_score_str.split(",")
|
||||
for i, score_str in enumerate(full_score_list[: len(score_by_quarter)]):
|
||||
@@ -1484,6 +1471,34 @@ def Scores_Quarter(merged: dict, *, out_dir: str = "static") -> None:
|
||||
logger.error(f"Ошибка в Scores_Quarter: {e}", exc_info=True)
|
||||
|
||||
|
||||
def status_online_func(merged: dict, *, out_dir: str = "static") -> None:
|
||||
"""
|
||||
Получает онлайн-статус игры и возвращает данные + путь к PNG-фолам.
|
||||
"""
|
||||
try:
|
||||
out_path = Path(out_dir) / "live_status.json"
|
||||
|
||||
if "live_status" in merged["result"]:
|
||||
status_data = merged["result"]["live_status"]
|
||||
atomic_write_json(out_path, status_data)
|
||||
else:
|
||||
logger.warning("Матч не ОНЛАЙН!!!!")
|
||||
atomic_write_json(
|
||||
out_path,
|
||||
[
|
||||
{
|
||||
"foulsA": 0,
|
||||
"foulsB": 0,
|
||||
}
|
||||
],
|
||||
)
|
||||
logging.info("Сохранил payload: {out_path}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка в status_online_func: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
|
||||
# ==========================
|
||||
# ---- ДОМЕННАЯ ЛОГИКА
|
||||
# ==========================
|
||||
@@ -1492,7 +1507,9 @@ def Scores_Quarter(merged: dict, *, out_dir: str = "static") -> None:
|
||||
def validate_league_or_die(league: str) -> str:
|
||||
league = (league or DEFAULT_LEAGUE).lower().strip()
|
||||
if league not in ALLOWED_LEAGUES:
|
||||
logger.warning(f"Неверный тег лиги: '{league}'. Допустимо: {sorted(ALLOWED_LEAGUES)}")
|
||||
logger.warning(
|
||||
f"Неверный тег лиги: '{league}'. Допустимо: {sorted(ALLOWED_LEAGUES)}"
|
||||
)
|
||||
sys.exit(2)
|
||||
return league
|
||||
|
||||
@@ -1502,16 +1519,16 @@ def get_last_season_or_die(league: str, lang: str) -> str:
|
||||
try:
|
||||
data = fetch_json(url)
|
||||
season = extract_last_season(data)
|
||||
logging.info(
|
||||
f"Последний сезон для {league}: {season}"
|
||||
)
|
||||
logging.info(f"Последний сезон для {league}: {season}")
|
||||
return season
|
||||
except Exception as e:
|
||||
logger.warning(f"Не получилось получить последний сезон для {league}: {e}")
|
||||
sys.exit(3)
|
||||
|
||||
|
||||
def get_team_schedule_or_die(league: str, season: str, team: str, lang: str) -> list[dict]:
|
||||
def get_team_schedule_or_die(
|
||||
league: str, season: str, team: str, lang: str
|
||||
) -> list[dict]:
|
||||
url = URL_SCHEDULE.format(host=HOST, league=league, season=season, lang=lang)
|
||||
try:
|
||||
data = fetch_json(url)
|
||||
@@ -1545,7 +1562,7 @@ def pick_today_or_last_played(
|
||||
return today_game, last_played
|
||||
|
||||
|
||||
def is_game_online(league: str, game_id: str, lang:str) -> str:
|
||||
def is_game_online(league: str, game_id: str, lang: str) -> str:
|
||||
"""
|
||||
Возвращает статус: inprogress|scheduled|finished (или то, что твой API даёт).
|
||||
"""
|
||||
@@ -1584,6 +1601,7 @@ class PostProcessor:
|
||||
Team_Both_Stat(merged, out_dir="static")
|
||||
Referee(merged, out_dir="static")
|
||||
Scores_Quarter(merged, out_dir="static")
|
||||
status_online_func(merged, out_dir="static")
|
||||
except Exception as e:
|
||||
logging.exception(f"Postproc failed: {e}")
|
||||
|
||||
@@ -1592,7 +1610,9 @@ class PostProcessor:
|
||||
|
||||
|
||||
class OnlinePoller:
|
||||
def __init__(self, league: str, game_id: str, lang: str, on_update: callable | None = None):
|
||||
def __init__(
|
||||
self, league: str, game_id: str, lang: str, on_update: callable | None = None
|
||||
):
|
||||
self.league = league
|
||||
self.game_id = game_id
|
||||
self.lang = lang
|
||||
@@ -1605,19 +1625,24 @@ class OnlinePoller:
|
||||
# 1) Постоянная сессия и пул соединений
|
||||
self._session = requests.Session()
|
||||
retry = Retry(
|
||||
total=2, connect=2, read=2, backoff_factor=0.1,
|
||||
total=2,
|
||||
connect=2,
|
||||
read=2,
|
||||
backoff_factor=0.1,
|
||||
status_forcelist=(502, 503, 504),
|
||||
allowed_methods=frozenset(["GET"])
|
||||
allowed_methods=frozenset(["GET"]),
|
||||
)
|
||||
adapter = HTTPAdapter(pool_connections=1, pool_maxsize=10, max_retries=retry)
|
||||
self._session.mount("http://", adapter)
|
||||
self._session.mount("https://", adapter)
|
||||
self._session.headers.update({
|
||||
self._session.headers.update(
|
||||
{
|
||||
"Connection": "keep-alive",
|
||||
"Accept": "application/json, */*",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"User-Agent": "game-watcher/1.0"
|
||||
})
|
||||
"User-Agent": "game-watcher/1.0",
|
||||
}
|
||||
)
|
||||
|
||||
def stop(self):
|
||||
if self._thread and self._thread.is_alive():
|
||||
@@ -1641,9 +1666,27 @@ class OnlinePoller:
|
||||
started = time.perf_counter()
|
||||
try:
|
||||
futures = [
|
||||
pool.submit(fetch_box_score, self.league, self.game_id, self.lang, self._session),
|
||||
pool.submit(fetch_play_by_play, self.league, self.game_id, self.lang, self._session),
|
||||
pool.submit(fetch_live_status, self.league, self.game_id, self.lang, self._session),
|
||||
pool.submit(
|
||||
fetch_box_score,
|
||||
self.league,
|
||||
self.game_id,
|
||||
self.lang,
|
||||
self._session,
|
||||
),
|
||||
pool.submit(
|
||||
fetch_play_by_play,
|
||||
self.league,
|
||||
self.game_id,
|
||||
self.lang,
|
||||
self._session,
|
||||
),
|
||||
pool.submit(
|
||||
fetch_live_status,
|
||||
self.league,
|
||||
self.game_id,
|
||||
self.lang,
|
||||
self._session,
|
||||
),
|
||||
]
|
||||
bs, pbp, ls = (f.result() for f in futures)
|
||||
merged = ensure_merged_payload(
|
||||
@@ -1691,7 +1734,10 @@ class OnlinePoller:
|
||||
self._thread.start()
|
||||
self._log.info(f"Онлайн-поллер для игры {self.game_id} запущен.")
|
||||
|
||||
def monitor_game_loop(league: str, game_id: str, lang:str, stop_event: threading.Event) -> None:
|
||||
|
||||
def monitor_game_loop(
|
||||
league: str, game_id: str, lang: str, stop_event: threading.Event
|
||||
) -> None:
|
||||
logger.info(f"Старт мониторинга игры {game_id} ({league}).")
|
||||
poller = OnlinePoller(league, game_id, lang)
|
||||
was_online = False
|
||||
@@ -1707,10 +1753,14 @@ def monitor_game_loop(league: str, game_id: str, lang:str, stop_event: threading
|
||||
break
|
||||
|
||||
if is_online and not was_online:
|
||||
logger.info(f"Матч {game_id} перешёл в онлайн.\nЗапускаем быстрый опрос (1 сек).")
|
||||
logger.info(
|
||||
f"Матч {game_id} перешёл в онлайн.\nЗапускаем быстрый опрос (1 сек)."
|
||||
)
|
||||
poller.start()
|
||||
elif not is_online and was_online:
|
||||
logger.info(f"Матч {game_id} вышел из онлайна (или ещё не стартовал).\nОстанавливаем быстрый опрос.")
|
||||
logger.info(
|
||||
f"Матч {game_id} вышел из онлайна (или ещё не стартовал).\nОстанавливаем быстрый опрос."
|
||||
)
|
||||
poller.stop()
|
||||
|
||||
was_online = is_online
|
||||
@@ -1730,7 +1780,9 @@ def monitor_game_loop(league: str, game_id: str, lang:str, stop_event: threading
|
||||
|
||||
def next_midnight_local(now: datetime) -> datetime:
|
||||
tomorrow = (now + timedelta(days=1)).date()
|
||||
return datetime.combine(tomorrow, datetime.min.time(), tzinfo=APP_TZ) + timedelta(minutes=5)
|
||||
return datetime.combine(tomorrow, datetime.min.time(), tzinfo=APP_TZ) + timedelta(
|
||||
minutes=5
|
||||
)
|
||||
# return now + timedelta(seconds=30)
|
||||
|
||||
|
||||
@@ -1834,9 +1886,7 @@ def main():
|
||||
parser.add_argument(
|
||||
"--team", type=str, required=True, help="код/тег команды (например, BOS)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--lang", type=str, default="en", help="язык получения данных"
|
||||
)
|
||||
parser.add_argument("--lang", type=str, default="en", help="язык получения данных")
|
||||
parser.add_argument(
|
||||
"--log-level", type=str, default="INFO", help="DEBUG|INFO|WARNING|ERROR"
|
||||
)
|
||||
@@ -1844,7 +1894,9 @@ def main():
|
||||
print(args)
|
||||
|
||||
# logger.info(f"Запуск программы пользователем: {MYHOST}")
|
||||
logger.info(f"Запуск с параметрами:\nleague={args.league}\nteam={args.team}\nlang={args.lang}")
|
||||
logger.info(
|
||||
f"Запуск с параметрами:\nleague={args.league}\nteam={args.team}\nlang={args.lang}"
|
||||
)
|
||||
|
||||
league = validate_league_or_die(args.league)
|
||||
team = args.team.lower()
|
||||
@@ -1875,20 +1927,23 @@ def main():
|
||||
if last_played:
|
||||
game_id = last_played["game"]["id"]
|
||||
try:
|
||||
url = URL_GAME.format(host=HOST, league=league, game_id=game_id, lang=args.lang)
|
||||
url = URL_GAME.format(
|
||||
host=HOST, league=league, game_id=game_id, lang=args.lang
|
||||
)
|
||||
game_json = fetch_json(url)
|
||||
merged = ensure_merged_payload(
|
||||
game_json,
|
||||
game_meta={
|
||||
"id": game_json.get("result", {}).get("gameId"),
|
||||
"league": args.league
|
||||
}
|
||||
"league": args.league,
|
||||
},
|
||||
)
|
||||
Json_Team_Generation(merged, out_dir="static", who="team1")
|
||||
Json_Team_Generation(merged, out_dir="static", who="team2")
|
||||
Team_Both_Stat(merged, out_dir="static")
|
||||
Referee(merged, out_dir="static")
|
||||
Scores_Quarter(merged, out_dir="static")
|
||||
status_online_func(merged, out_dir="static")
|
||||
# print(merged)
|
||||
logger.info(
|
||||
f"Сегодня у {team} нет игры.\nПоследняя сыгранная: gameID={game_id}.\nМониторинг не запускаю."
|
||||
|
||||
Reference in New Issue
Block a user