From 629854c10403da18ddea913d2dd1afe4de46376b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AE=D1=80=D0=B8=D0=B9=20=D0=A7=D0=B5=D1=80=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE?= Date: Fri, 24 Oct 2025 17:35:12 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BE=D0=BD=D0=BB=D0=B0=D0=B9=D0=BD/=D0=BE=D1=84?= =?UTF-8?q?=D1=84=D0=BB=D0=B0=D0=B9=D0=BD=20=D1=84=D0=BE=D0=BB=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=B8=20=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8E=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D1=81=D0=BA=D0=BB=D0=B5=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BE=D0=BD=D0=BB=D0=B0=D0=B9=D0=BD=20=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D1=85=20(=D0=BD=D1=83=D0=B6=D0=BD=D0=BE=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B5=D1=80=D0=B8=D1=82=D1=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- get_data.py | 1037 +++++++++++++++++++++++++++------------------------ 1 file changed, 546 insertions(+), 491 deletions(-) diff --git a/get_data.py b/get_data.py index 0df7729..d26226e 100644 --- a/get_data.py +++ b/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}" # Быстрые запросы, когда матч онлайн (каждую секунду) @@ -83,9 +80,9 @@ URL_PLAY_BY_PLAY = "https://{host}/api/abc/games/play-by-play?Id={game_id}&lang= URL_LIVE_STATUS = "https://{host}/api/abc/games/live-status?Id={game_id}&lang={lang}" # Интервалы опроса -STATUS_CHECK_INTERVAL_SEC = 60 # проверять "онлайн?" раз в минуту -ONLINE_FETCH_INTERVAL_SEC = 1 # когда матч онлайн, дергать три запроса каждую секунду -POLL_INTERVAL_OFFLINE_SEC = 300 # резервный интервал сна при ошибках/до старта +STATUS_CHECK_INTERVAL_SEC = 60 # проверять "онлайн?" раз в минуту +ONLINE_FETCH_INTERVAL_SEC = 1 # когда матч онлайн, дергать три запроса каждую секунду +POLL_INTERVAL_OFFLINE_SEC = 300 # резервный интервал сна при ошибках/до старта TELEGRAM_BOT_TOKEN = "7639240596:AAH0YtdQoWZSC-_R_EW4wKAHHNLIA0F_ARY" # TELEGRAM_CHAT_ID = 228977654 @@ -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. @@ -490,24 +516,20 @@ def Json_Team_Generation(merged: dict, *, out_dir: str = "static", who: str | No # ) online = False role_list = [ - ("Center", "C"), - ("Guard", "G"), - ("Forward", "F"), - ("Power Forward", "PF"), - ("Small Forward", "SF"), - ("Shooting Guard", "SG"), - ("Point Guard", "PG"), - ("Forward-Center", "FC"), -] + ("Center", "C"), + ("Guard", "G"), + ("Forward", "F"), + ("Power Forward", "PF"), + ("Small Forward", "SF"), + ("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,346 +635,344 @@ 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 - ), - # "AvgPoints": ( - # row_player_season_avg["points"] - # if row_player_season_avg - # and row_player_season_avg["points"] != "" - # else "0.0" - # ), - # "AvgAssist": ( - # row_player_season_avg["assist"] - # if row_player_season_avg - # and row_player_season_avg["assist"] != "" - # else "0.0" - # ), - # "AvgBlocks": ( - # row_player_season_avg["blockShot"] - # if row_player_season_avg - # and row_player_season_avg["blockShot"] != "" - # else "0.0" - # ), - # "AvgDefRebound": ( - # row_player_season_avg["defRebound"] - # if row_player_season_avg - # and row_player_season_avg["defRebound"] != "" - # else "0.0" - # ), - # "AvgOffRebound": ( - # row_player_season_avg["offRebound"] - # if row_player_season_avg - # and row_player_season_avg["offRebound"] != "" - # else "0.0" - # ), - # "AvgRebound": ( - # row_player_season_avg["rebound"] - # if row_player_season_avg - # and row_player_season_avg["rebound"] != "" - # else "0.0" - # ), - # "AvgSteal": ( - # row_player_season_avg["steal"] - # if row_player_season_avg - # and row_player_season_avg["steal"] != "" - # else "0.0" - # ), - # "AvgTurnover": ( - # row_player_season_avg["turnover"] - # if row_player_season_avg - # and row_player_season_avg["turnover"] != "" - # else "0.0" - # ), - # "AvgFoul": ( - # row_player_season_avg["foul"] - # if row_player_season_avg - # and row_player_season_avg["foul"] != "" - # else "0.0" - # ), - # "AvgOpponentFoul": ( - # row_player_season_avg["foulsOnPlayer"] - # if row_player_season_avg - # and row_player_season_avg["foulsOnPlayer"] != "" - # else "0.0" - # ), - # "AvgPlusMinus": ( - # row_player_season_avg["plusMinus"] - # if row_player_season_avg - # and row_player_season_avg["plusMinus"] != "" - # else "0.0" - # ), - # "AvgDunk": ( - # row_player_season_avg["dunk"] - # if row_player_season_avg - # and row_player_season_avg["dunk"] != "" - # else "0.0" - # ), - # "AvgKPI": "0.0", - # "AvgPlayedTime": ( - # row_player_season_avg["playedTime"] - # if row_player_season_avg - # and row_player_season_avg["playedTime"] != "" - # else "0:00" - # ), - # "Shot1Percent": calc_shot_percent_by_type( - # sum_stat, item["stats"], online, shot_types=1 - # ), - # "Shot2Percent": calc_shot_percent_by_type( - # sum_stat, item["stats"], online, shot_types=2 - # ), - # "Shot3Percent": calc_shot_percent_by_type( - # sum_stat, item["stats"], online, shot_types=3 - # ), - # "Shot23Percent": calc_shot_percent_by_type( - # sum_stat, item["stats"], online, shot_types=[2, 3] - # ), - # "TPoints": sum_stat_with_online( - # "points", sum_stat, item["stats"], online - # ), - # "TShots1": calc_total_shots_str( - # sum_stat, item["stats"], online, 1 - # ), - # "TShots2": calc_total_shots_str( - # sum_stat, item["stats"], online, 2 - # ), - # "TShots3": calc_total_shots_str( - # sum_stat, item["stats"], online, 3 - # ), - # "TShots23": calc_total_shots_str( - # sum_stat, item["stats"], online, [2, 3] - # ), - # "TShot1Percent": calc_shot_percent_by_type( - # sum_stat, item["stats"], online, shot_types=1 - # ), - # "TShot2Percent": calc_shot_percent_by_type( - # sum_stat, item["stats"], online, shot_types=2 - # ), - # "TShot3Percent": calc_shot_percent_by_type( - # sum_stat, item["stats"], online, shot_types=3 - # ), - # "TShot23Percent": calc_shot_percent_by_type( - # sum_stat, item["stats"], online, shot_types=[2, 3] - # ), - # "TAssist": sum_stat_with_online( - # "assist", sum_stat, item["stats"], online - # ), - # "TBlocks": sum_stat_with_online( - # "blockShot", sum_stat, item["stats"], online - # ), - # "TDefRebound": sum_stat_with_online( - # "defRebound", sum_stat, item["stats"], online - # ), - # "TOffRebound": sum_stat_with_online( - # "offRebound", sum_stat, item["stats"], online - # ), - # "TRebound": ( - # sum_stat_with_online( - # "defRebound", sum_stat, item["stats"], online - # ) - # + sum_stat_with_online( - # "offRebound", sum_stat, item["stats"], online - # ) - # ), - # "TSteal": sum_stat_with_online( - # "steal", sum_stat, item["stats"], online - # ), - # "TTurnover": sum_stat_with_online( - # "turnover", sum_stat, item["stats"], online - # ), - # "TFoul": sum_stat_with_online( - # "foul", sum_stat, item["stats"], online - # ), - # "TOpponentFoul": sum_stat_with_online( - # "foulsOnPlayer", sum_stat, item["stats"], online - # ), - # "TPlusMinus": 0, - # "TDunk": sum_stat_with_online( - # "dunk", sum_stat, item["stats"], online - # ), - # "TKPI": 0, - # "TPlayedTime": sum_stat["playedTime"] if sum_stat else "0:00", - # "TGameCount": ( - # safe_int(sum_stat["games"]) - # if sum_stat and sum_stat.get("games") != "" - # else 0 - # ) - # + (1 if online else 0), - # "TStartCount": ( - # safe_int(sum_stat["isStarts"]) - # if sum_stat and sum_stat.get("isStarts", 0) != "" - # else 0 - # ), - # "CareerTShots1": calc_total_shots_str( - # row_player_career_sum, item["stats"], online, 1 - # ), - # "CareerTShots2": calc_total_shots_str( - # row_player_career_sum, item["stats"], online, 2 - # ), - # "CareerTShots3": calc_total_shots_str( - # row_player_career_sum, item["stats"], online, 3 - # ), - # "CareerTShots23": calc_total_shots_str( - # row_player_career_sum, item["stats"], online, [2, 3] - # ), - # "CareerTShot1Percent": calc_shot_percent_by_type( - # row_player_career_sum, item["stats"], online, 1 - # ), - # "CareerTShot2Percent": calc_shot_percent_by_type( - # row_player_career_sum, item["stats"], online, 2 - # ), - # "CareerTShot3Percent": calc_shot_percent_by_type( - # row_player_career_sum, item["stats"], online, 3 - # ), - # "CareerTShot23Percent": calc_shot_percent_by_type( - # row_player_career_sum, item["stats"], online, [2, 3] - # ), - # "CareerTPoints": sum_stat_with_online( - # "points", row_player_career_sum, item["stats"], online - # ), - # "CareerTAssist": sum_stat_with_online( - # "assist", row_player_career_sum, item["stats"], online - # ), - # "CareerTBlocks": sum_stat_with_online( - # "blockShot", row_player_career_sum, item["stats"], online - # ), - # "CareerTDefRebound": sum_stat_with_online( - # "defRebound", row_player_career_sum, item["stats"], online - # ), - # "CareerTOffRebound": sum_stat_with_online( - # "offRebound", row_player_career_sum, item["stats"], online - # ), - # "CareerTRebound": ( - # sum_stat_with_online( - # "defRebound", - # row_player_career_sum, - # item["stats"], - # online, - # ) - # + sum_stat_with_online( - # "offRebound", - # row_player_career_sum, - # item["stats"], - # online, - # ) - # ), - # "CareerTSteal": sum_stat_with_online( - # "steal", row_player_career_sum, item["stats"], online - # ), - # "CareerTTurnover": sum_stat_with_online( - # "turnover", row_player_career_sum, item["stats"], online - # ), - # "CareerTFoul": sum_stat_with_online( - # "foul", row_player_career_sum, item["stats"], online - # ), - # "CareerTOpponentFoul": sum_stat_with_online( - # "foulsOnPlayer", - # row_player_career_sum, - # item["stats"], - # online, - # ), - # "CareerTPlusMinus": 0, # оставить как есть - # "CareerTDunk": sum_stat_with_online( - # "dunk", row_player_career_sum, item["stats"], online - # ), - # "CareerTPlayedTime": ( - # row_player_career_sum["playedTime"] - # if row_player_career_sum - # else "0:00" - # ), - # "CareerTGameCount": sum_stat_with_online( - # "games", row_player_career_sum, item["stats"], online - # ) - # + (1 if online else 0), - # "CareerTStartCount": sum_stat_with_online( - # "isStarts", row_player_career_sum, item["stats"], online - # ), # если нужно, можно +1 при старте - # "AvgCarPoints": ( - # row_player_career_avg["points"] - # if row_player_career_avg - # and row_player_career_avg["points"] != "" - # else "0.0" - # ), - # "AvgCarAssist": ( - # row_player_career_avg["assist"] - # if row_player_career_avg - # and row_player_career_avg["assist"] != "" - # else "0.0" - # ), - # "AvgCarBlocks": ( - # row_player_career_avg["blockShot"] - # if row_player_career_avg - # and row_player_career_avg["blockShot"] != "" - # else "0.0" - # ), - # "AvgCarDefRebound": ( - # row_player_career_avg["defRebound"] - # if row_player_career_avg - # and row_player_career_avg["defRebound"] != "" - # else "0.0" - # ), - # "AvgCarOffRebound": ( - # row_player_career_avg["offRebound"] - # if row_player_career_avg - # and row_player_career_avg["offRebound"] != "" - # else "0.0" - # ), - # "AvgCarRebound": ( - # row_player_career_avg["rebound"] - # if row_player_career_avg - # and row_player_career_avg["rebound"] != "" - # else "0.0" - # ), - # "AvgCarSteal": ( - # row_player_career_avg["steal"] - # if row_player_career_avg - # and row_player_career_avg["steal"] != "" - # else "0.0" - # ), - # "AvgCarTurnover": ( - # row_player_career_avg["turnover"] - # if row_player_career_avg - # and row_player_career_avg["turnover"] != "" - # else "0.0" - # ), - # "AvgCarFoul": ( - # row_player_career_avg["foul"] - # if row_player_career_avg - # and row_player_career_avg["foul"] != "" - # else "0.0" - # ), - # "AvgCarOpponentFoul": ( - # row_player_career_avg["foulsOnPlayer"] - # if row_player_career_avg - # and row_player_career_avg["foulsOnPlayer"] != "" - # else "0.0" - # ), - # "AvgCarPlusMinus": ( - # row_player_career_avg["plusMinus"] - # if row_player_career_avg - # and row_player_career_avg["plusMinus"] != "" - # else "0.0" - # ), - # "AvgCarDunk": ( - # row_player_career_avg["dunk"] - # if row_player_career_avg - # and row_player_career_avg["dunk"] != "" - # else "0.0" - # ), - # "AvgCarKPI": "0.0", - # "AvgCarPlayedTime": ( - # row_player_career_avg["playedTime"] - # if row_player_career_avg - # and row_player_career_avg["playedTime"] != "" - # else "0:00" - # ), - # "HeadCoachStatsCareer": HeadCoachStatsCareer, - # "HeadCoachStatsTeam": HeadCoachStatsTeam, - # # "PTS_Career_High": get_carrer_high(item["personId"], "points"), - # # "AST_Career_High": get_carrer_high(item["personId"], "assist"), - # # "REB_Career_High": get_carrer_high(item["personId"], "rebound"), - # # "STL_Career_High": get_carrer_high(item["personId"], "steal"), - # # "BLK_Career_High": get_carrer_high(item["personId"], "blockShot"), + "isOnCourt": (item["stats"]["isOnCourt"] if item["stats"] else False), + # "AvgPoints": ( + # row_player_season_avg["points"] + # if row_player_season_avg + # and row_player_season_avg["points"] != "" + # else "0.0" + # ), + # "AvgAssist": ( + # row_player_season_avg["assist"] + # if row_player_season_avg + # and row_player_season_avg["assist"] != "" + # else "0.0" + # ), + # "AvgBlocks": ( + # row_player_season_avg["blockShot"] + # if row_player_season_avg + # and row_player_season_avg["blockShot"] != "" + # else "0.0" + # ), + # "AvgDefRebound": ( + # row_player_season_avg["defRebound"] + # if row_player_season_avg + # and row_player_season_avg["defRebound"] != "" + # else "0.0" + # ), + # "AvgOffRebound": ( + # row_player_season_avg["offRebound"] + # if row_player_season_avg + # and row_player_season_avg["offRebound"] != "" + # else "0.0" + # ), + # "AvgRebound": ( + # row_player_season_avg["rebound"] + # if row_player_season_avg + # and row_player_season_avg["rebound"] != "" + # else "0.0" + # ), + # "AvgSteal": ( + # row_player_season_avg["steal"] + # if row_player_season_avg + # and row_player_season_avg["steal"] != "" + # else "0.0" + # ), + # "AvgTurnover": ( + # row_player_season_avg["turnover"] + # if row_player_season_avg + # and row_player_season_avg["turnover"] != "" + # else "0.0" + # ), + # "AvgFoul": ( + # row_player_season_avg["foul"] + # if row_player_season_avg + # and row_player_season_avg["foul"] != "" + # else "0.0" + # ), + # "AvgOpponentFoul": ( + # row_player_season_avg["foulsOnPlayer"] + # if row_player_season_avg + # and row_player_season_avg["foulsOnPlayer"] != "" + # else "0.0" + # ), + # "AvgPlusMinus": ( + # row_player_season_avg["plusMinus"] + # if row_player_season_avg + # and row_player_season_avg["plusMinus"] != "" + # else "0.0" + # ), + # "AvgDunk": ( + # row_player_season_avg["dunk"] + # if row_player_season_avg + # and row_player_season_avg["dunk"] != "" + # else "0.0" + # ), + # "AvgKPI": "0.0", + # "AvgPlayedTime": ( + # row_player_season_avg["playedTime"] + # if row_player_season_avg + # and row_player_season_avg["playedTime"] != "" + # else "0:00" + # ), + # "Shot1Percent": calc_shot_percent_by_type( + # sum_stat, item["stats"], online, shot_types=1 + # ), + # "Shot2Percent": calc_shot_percent_by_type( + # sum_stat, item["stats"], online, shot_types=2 + # ), + # "Shot3Percent": calc_shot_percent_by_type( + # sum_stat, item["stats"], online, shot_types=3 + # ), + # "Shot23Percent": calc_shot_percent_by_type( + # sum_stat, item["stats"], online, shot_types=[2, 3] + # ), + # "TPoints": sum_stat_with_online( + # "points", sum_stat, item["stats"], online + # ), + # "TShots1": calc_total_shots_str( + # sum_stat, item["stats"], online, 1 + # ), + # "TShots2": calc_total_shots_str( + # sum_stat, item["stats"], online, 2 + # ), + # "TShots3": calc_total_shots_str( + # sum_stat, item["stats"], online, 3 + # ), + # "TShots23": calc_total_shots_str( + # sum_stat, item["stats"], online, [2, 3] + # ), + # "TShot1Percent": calc_shot_percent_by_type( + # sum_stat, item["stats"], online, shot_types=1 + # ), + # "TShot2Percent": calc_shot_percent_by_type( + # sum_stat, item["stats"], online, shot_types=2 + # ), + # "TShot3Percent": calc_shot_percent_by_type( + # sum_stat, item["stats"], online, shot_types=3 + # ), + # "TShot23Percent": calc_shot_percent_by_type( + # sum_stat, item["stats"], online, shot_types=[2, 3] + # ), + # "TAssist": sum_stat_with_online( + # "assist", sum_stat, item["stats"], online + # ), + # "TBlocks": sum_stat_with_online( + # "blockShot", sum_stat, item["stats"], online + # ), + # "TDefRebound": sum_stat_with_online( + # "defRebound", sum_stat, item["stats"], online + # ), + # "TOffRebound": sum_stat_with_online( + # "offRebound", sum_stat, item["stats"], online + # ), + # "TRebound": ( + # sum_stat_with_online( + # "defRebound", sum_stat, item["stats"], online + # ) + # + sum_stat_with_online( + # "offRebound", sum_stat, item["stats"], online + # ) + # ), + # "TSteal": sum_stat_with_online( + # "steal", sum_stat, item["stats"], online + # ), + # "TTurnover": sum_stat_with_online( + # "turnover", sum_stat, item["stats"], online + # ), + # "TFoul": sum_stat_with_online( + # "foul", sum_stat, item["stats"], online + # ), + # "TOpponentFoul": sum_stat_with_online( + # "foulsOnPlayer", sum_stat, item["stats"], online + # ), + # "TPlusMinus": 0, + # "TDunk": sum_stat_with_online( + # "dunk", sum_stat, item["stats"], online + # ), + # "TKPI": 0, + # "TPlayedTime": sum_stat["playedTime"] if sum_stat else "0:00", + # "TGameCount": ( + # safe_int(sum_stat["games"]) + # if sum_stat and sum_stat.get("games") != "" + # else 0 + # ) + # + (1 if online else 0), + # "TStartCount": ( + # safe_int(sum_stat["isStarts"]) + # if sum_stat and sum_stat.get("isStarts", 0) != "" + # else 0 + # ), + # "CareerTShots1": calc_total_shots_str( + # row_player_career_sum, item["stats"], online, 1 + # ), + # "CareerTShots2": calc_total_shots_str( + # row_player_career_sum, item["stats"], online, 2 + # ), + # "CareerTShots3": calc_total_shots_str( + # row_player_career_sum, item["stats"], online, 3 + # ), + # "CareerTShots23": calc_total_shots_str( + # row_player_career_sum, item["stats"], online, [2, 3] + # ), + # "CareerTShot1Percent": calc_shot_percent_by_type( + # row_player_career_sum, item["stats"], online, 1 + # ), + # "CareerTShot2Percent": calc_shot_percent_by_type( + # row_player_career_sum, item["stats"], online, 2 + # ), + # "CareerTShot3Percent": calc_shot_percent_by_type( + # row_player_career_sum, item["stats"], online, 3 + # ), + # "CareerTShot23Percent": calc_shot_percent_by_type( + # row_player_career_sum, item["stats"], online, [2, 3] + # ), + # "CareerTPoints": sum_stat_with_online( + # "points", row_player_career_sum, item["stats"], online + # ), + # "CareerTAssist": sum_stat_with_online( + # "assist", row_player_career_sum, item["stats"], online + # ), + # "CareerTBlocks": sum_stat_with_online( + # "blockShot", row_player_career_sum, item["stats"], online + # ), + # "CareerTDefRebound": sum_stat_with_online( + # "defRebound", row_player_career_sum, item["stats"], online + # ), + # "CareerTOffRebound": sum_stat_with_online( + # "offRebound", row_player_career_sum, item["stats"], online + # ), + # "CareerTRebound": ( + # sum_stat_with_online( + # "defRebound", + # row_player_career_sum, + # item["stats"], + # online, + # ) + # + sum_stat_with_online( + # "offRebound", + # row_player_career_sum, + # item["stats"], + # online, + # ) + # ), + # "CareerTSteal": sum_stat_with_online( + # "steal", row_player_career_sum, item["stats"], online + # ), + # "CareerTTurnover": sum_stat_with_online( + # "turnover", row_player_career_sum, item["stats"], online + # ), + # "CareerTFoul": sum_stat_with_online( + # "foul", row_player_career_sum, item["stats"], online + # ), + # "CareerTOpponentFoul": sum_stat_with_online( + # "foulsOnPlayer", + # row_player_career_sum, + # item["stats"], + # online, + # ), + # "CareerTPlusMinus": 0, # оставить как есть + # "CareerTDunk": sum_stat_with_online( + # "dunk", row_player_career_sum, item["stats"], online + # ), + # "CareerTPlayedTime": ( + # row_player_career_sum["playedTime"] + # if row_player_career_sum + # else "0:00" + # ), + # "CareerTGameCount": sum_stat_with_online( + # "games", row_player_career_sum, item["stats"], online + # ) + # + (1 if online else 0), + # "CareerTStartCount": sum_stat_with_online( + # "isStarts", row_player_career_sum, item["stats"], online + # ), # если нужно, можно +1 при старте + # "AvgCarPoints": ( + # row_player_career_avg["points"] + # if row_player_career_avg + # and row_player_career_avg["points"] != "" + # else "0.0" + # ), + # "AvgCarAssist": ( + # row_player_career_avg["assist"] + # if row_player_career_avg + # and row_player_career_avg["assist"] != "" + # else "0.0" + # ), + # "AvgCarBlocks": ( + # row_player_career_avg["blockShot"] + # if row_player_career_avg + # and row_player_career_avg["blockShot"] != "" + # else "0.0" + # ), + # "AvgCarDefRebound": ( + # row_player_career_avg["defRebound"] + # if row_player_career_avg + # and row_player_career_avg["defRebound"] != "" + # else "0.0" + # ), + # "AvgCarOffRebound": ( + # row_player_career_avg["offRebound"] + # if row_player_career_avg + # and row_player_career_avg["offRebound"] != "" + # else "0.0" + # ), + # "AvgCarRebound": ( + # row_player_career_avg["rebound"] + # if row_player_career_avg + # and row_player_career_avg["rebound"] != "" + # else "0.0" + # ), + # "AvgCarSteal": ( + # row_player_career_avg["steal"] + # if row_player_career_avg + # and row_player_career_avg["steal"] != "" + # else "0.0" + # ), + # "AvgCarTurnover": ( + # row_player_career_avg["turnover"] + # if row_player_career_avg + # and row_player_career_avg["turnover"] != "" + # else "0.0" + # ), + # "AvgCarFoul": ( + # row_player_career_avg["foul"] + # if row_player_career_avg + # and row_player_career_avg["foul"] != "" + # else "0.0" + # ), + # "AvgCarOpponentFoul": ( + # row_player_career_avg["foulsOnPlayer"] + # if row_player_career_avg + # and row_player_career_avg["foulsOnPlayer"] != "" + # else "0.0" + # ), + # "AvgCarPlusMinus": ( + # row_player_career_avg["plusMinus"] + # if row_player_career_avg + # and row_player_career_avg["plusMinus"] != "" + # else "0.0" + # ), + # "AvgCarDunk": ( + # row_player_career_avg["dunk"] + # if row_player_career_avg + # and row_player_career_avg["dunk"] != "" + # else "0.0" + # ), + # "AvgCarKPI": "0.0", + # "AvgCarPlayedTime": ( + # row_player_career_avg["playedTime"] + # if row_player_career_avg + # and row_player_career_avg["playedTime"] != "" + # else "0:00" + # ), + # "HeadCoachStatsCareer": HeadCoachStatsCareer, + # "HeadCoachStatsTeam": HeadCoachStatsTeam, + # # "PTS_Career_High": get_carrer_high(item["personId"], "points"), + # # "AST_Career_High": get_carrer_high(item["personId"], "assist"), + # # "REB_Career_High": get_carrer_high(item["personId"], "rebound"), + # # "STL_Career_High": get_carrer_high(item["personId"], "steal"), + # # "BLK_Career_High": get_carrer_high(item["personId"], "blockShot"), } team.append(player) count_player = sum(1 for x in team if x["startRole"] == "Player") @@ -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 = { @@ -1032,10 +1034,10 @@ def Json_Team_Generation(merged: dict, *, out_dir: str = "static", who: str | No x.get("startRole", 99), 99 ), # 99 — по умолчанию ) - out_path = Path(out_dir) / f"{who}.json" + out_path = Path(out_dir) / f"{who}.json" atomic_write_json(out_path, sorted_team) logging.info("Сохранил payload: {out_path}") - + top_sorted_team = sorted( filter(lambda x: x["startRole"] in ["Player", ""], sorted_team), key=lambda x: ( @@ -1051,8 +1053,8 @@ def Json_Team_Generation(merged: dict, *, out_dir: str = "static", who: str | No for item in top_sorted_team: item["pts"] = "" if item["num"] == "" else item["pts"] item["foul"] = "" if item["num"] == "" else item["foul"] - - out_path = Path(out_dir) / f"top{who.replace('t','T')}.json" + + out_path = Path(out_dir) / f"top{who.replace('t','T')}.json" atomic_write_json(out_path, top_sorted_team) logging.info("Сохранил payload: {out_path}") @@ -1064,11 +1066,12 @@ def Json_Team_Generation(merged: dict, *, out_dir: str = "static", who: str | No key=lambda x: int(x["num"]), reverse=False, ) - - out_path = Path(out_dir) / f"started_{who}.json" + + out_path = Path(out_dir) / f"started_{who}.json" 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: @@ -1369,16 +1362,14 @@ def Team_Both_Stat(merged: dict, *, out_dir: str = "static") -> None: "val2": val2, } ) - - out_path = Path(out_dir) / "team_stats.json" + + out_path = Path(out_dir) / "team_stats.json" atomic_write_json(out_path, result_json) logging.info("Сохранил payload: {out_path}") 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", ""), @@ -1435,10 +1424,10 @@ def Referee(merged: dict, *, out_dir: str = "static") -> None: else len(desired_order) ), ) - out_path = Path(out_dir) / "referee.json" + out_path = Path(out_dir) / "referee.json" atomic_write_json(out_path, referees) logging.info("Сохранил payload: {out_path}") - + except Exception as e: logger.error(f"Ошибка в Referee потоке: {e}", exc_info=True) @@ -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)]): @@ -1475,8 +1462,8 @@ def Scores_Quarter(merged: dict, *, out_dir: str = "static") -> None: logger.info("Счёт по четвертям получен из scoreByPeriods.") else: logger.debug("Нет данных по счёту, сохраняем пустые значения.") - - out_path = Path(out_dir) / "scores.json" + + out_path = Path(out_dir) / "scores.json" atomic_write_json(out_path, score_by_quarter) logging.info("Сохранил payload: {out_path}") @@ -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({ - "Connection": "keep-alive", - "Accept": "application/json, */*", - "Accept-Encoding": "gzip, deflate, br", - "User-Agent": "game-watcher/1.0" - }) + self._session.headers.update( + { + "Connection": "keep-alive", + "Accept": "application/json, */*", + "Accept-Encoding": "gzip, deflate, br", + "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( @@ -1678,7 +1721,7 @@ class OnlinePoller: if rest > 0: # спим только остаток self._stop_event.wait(rest) - + def start(self): if self._thread and self._thread.is_alive(): return @@ -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Мониторинг не запускаю."