добавил условие в тред game.

поправил results_consumer.
поправил статусы на live.
поправил выводы статусов в телеграмм.
поправил вывод турнирной таблицы.
удалил лишние повторящие треды
This commit is contained in:
2025-11-03 21:26:09 +03:00
parent 7301e6871a
commit 7ebca93103

View File

@@ -17,6 +17,7 @@ from fastapi.responses import Response
import logging import logging
import logging.config import logging.config
import platform import platform
import socket
# передадим параметры через аргументы или глобальные переменные # передадим параметры через аргументы или глобальные переменные
@@ -28,6 +29,7 @@ parser.add_argument("--lang", default="en")
args = parser.parse_args() args = parser.parse_args()
MYHOST = platform.node() MYHOST = platform.node()
user_name = socket.gethostname()
if not os.path.exists("logs"): if not os.path.exists("logs"):
os.makedirs("logs") os.makedirs("logs")
@@ -65,7 +67,7 @@ log_config = {
"formatters": { "formatters": {
"telegram": { "telegram": {
"class": "telegram_handler.HtmlFormatter", "class": "telegram_handler.HtmlFormatter",
"format": f"%(levelname)s <b>[{MYHOST.upper()}]</b> %(message)s", "format": f"%(levelname)s <b>[{MYHOST.upper()}] [{user_name}]</b>\n%(message)s",
"use_emoji": "True", "use_emoji": "True",
}, },
"simple": { "simple": {
@@ -225,6 +227,7 @@ def start_live_threads(season, game_id):
URLS["game"].format(host=HOST, game_id=game_id, lang=LANG), URLS["game"].format(host=HOST, game_id=game_id, lang=LANG),
300, # часто 300, # часто
stop_event_live, stop_event_live,
True,
), ),
daemon=True, daemon=True,
), ),
@@ -304,13 +307,17 @@ def stop_offline_threads():
# Функция запускаемая в потоках # Функция запускаемая в потоках
def get_data_from_API( def get_data_from_API(
name: str, url: str, sleep_time: float, stop_event: threading.Event name: str, url: str, sleep_time: float, stop_event: threading.Event, stop_when_live=False
): ):
did_first_fetch = False
while not stop_event.is_set(): while not stop_event.is_set():
if stop_when_live and globals().get("STATUS") == "live" and did_first_fetch:
logger.info(f"[{name}] stopping because STATUS='live' and first fetch done")
break
start = time.time() start = time.time()
try: try:
value = requests.get(url, timeout=5).json() value = requests.get(url, timeout=5).json()
did_first_fetch = True # помечаем, что один заход сделали
except json.JSONDecodeError as json_err: except json.JSONDecodeError as json_err:
logger.warning(f"[{name}] Ошибка парсинга JSON: {json_err}") logger.warning(f"[{name}] Ошибка парсинга JSON: {json_err}")
value = {"error": f"JSON decode error: {json_err}"} value = {"error": f"JSON decode error: {json_err}"}
@@ -342,7 +349,16 @@ def get_data_from_API(
# to_sleep = sleep_time - elapsed # to_sleep = sleep_time - elapsed
# print(to_sleep) # print(to_sleep)
# if to_sleep > 0: # if to_sleep > 0:
time.sleep(sleep_time) # умное ожидание с быстрым выходом при live
slept = 0
while slept < sleep_time:
if stop_event.is_set():
break
if stop_when_live and globals().get("STATUS") == "live" and did_first_fetch:
logger.info(f"[{name}] stopping during sleep because STATUS='live' and first fetch done")
return
time.sleep(1)
slept += 1
# если запрос занял дольше — просто сразу следующую итерацию # если запрос занял дольше — просто сразу следующую итерацию
@@ -370,7 +386,15 @@ def results_consumer():
# универсальный статус (может не быть) # универсальный статус (может не быть)
incoming_status = payload.get("status") # может быть None incoming_status = payload.get("status") # может быть None
# print(source, incoming_status)
if source == "game":
# обработка ТОЛЬКО в спец-ветке ниже
pass
else:
latest_data[source] = {
"ts": msg["ts"],
"data": incoming_status if incoming_status is not None else payload,
}
# 1) play-by-play # 1) play-by-play
if "play-by-play" in source: if "play-by-play" in source:
game = latest_data.get("game") game = latest_data.get("game")
@@ -386,10 +410,6 @@ def results_consumer():
game["data"]["result"]["plays"] = payload["result"] game["data"]["result"]["plays"] = payload["result"]
# а вот статус у play-by-play иногда просто "no-status" # а вот статус у play-by-play иногда просто "no-status"
latest_data[source] = {
"ts": msg["ts"],
"data": incoming_status if incoming_status is not None else payload,
}
# 2) box-score # 2) box-score
elif "box-score" in source: elif "box-score" in source:
@@ -438,10 +458,10 @@ def results_consumer():
team["maxPointsInRow"] = box_team["maxPointsInRow"] team["maxPointsInRow"] = box_team["maxPointsInRow"]
# в любом случае сохраняем сам факт, что box-score пришёл # в любом случае сохраняем сам факт, что box-score пришёл
latest_data[source] = { # latest_data[source] = {
"ts": msg["ts"], # "ts": msg["ts"],
"data": incoming_status if incoming_status is not None else payload, # "data": incoming_status if incoming_status is not None else payload,
} # }
elif "live-status" in source: elif "live-status" in source:
latest_data[source] = { latest_data[source] = {
@@ -512,10 +532,8 @@ def results_consumer():
) )
globals()["OFFLINE_SWITCH_AT"] = None globals()["OFFLINE_SWITCH_AT"] = None
if globals().get("STATUS") not in ["live", "live_soon"]: if globals().get("STATUS") != "live":
logger.info( logger.info("[status] match became LIVE → switch to LIVE threads")
"[status] match became LIVE → switch to LIVE threads"
)
globals()["STATUS"] = "live" globals()["STATUS"] = "live"
start_live_threads(SEASON, GAME_ID) start_live_threads(SEASON, GAME_ID)
@@ -526,7 +544,8 @@ def results_consumer():
else: else:
if source == "game": if source == "game":
has_game_already = "game" in latest_data # has_game_already = "teams" in latest_data
has_game_already = "game" in latest_data and isinstance(latest_data.get("game"), dict)
# есть ли в ответе ПОЛНАЯ структура # есть ли в ответе ПОЛНАЯ структура
is_full = ( is_full = (
@@ -542,18 +561,26 @@ def results_consumer():
"data": payload, "data": payload,
} }
else: else:
# game неполный if globals().get("STATUS") in ("live", "live_soon") and has_game_already:
if not has_game_already: logger.debug("results_consumer: LIVE & partial game → keep previous one")
# 👉 раньше game вообще не было — лучше положить хоть что-то
latest_data["game"] = {
"ts": msg["ts"],
"data": payload,
}
else: else:
# 👉 уже есть какой-то game — неполным НЕ затираем if not has_game_already:
logger.debug( # раньше не было вообще — положим хоть что-то
"results_consumer: got partial game, keeping previous one" latest_data["game"] = {"ts": msg["ts"], "data": payload}
) else:
logger.debug("results_consumer: got partial game, keeping previous one")
# # game неполный
# if not has_game_already:
# # 👉 раньше game вообще не было — лучше положить хоть что-то
# latest_data["game"] = {
# "ts": msg["ts"],
# "data": payload,
# }
# else:
# # 👉 уже есть какой-то game — неполным НЕ затираем
# logger.debug(
# "results_consumer: got partial game, keeping previous one"
# )
# и обязательно continue/return из этого elif/if # и обязательно continue/return из этого elif/if
else: else:
@@ -666,7 +693,7 @@ def build_pretty_status_message():
Если game ещё нет — шлём хотя бы статусы источников. Если game ещё нет — шлём хотя бы статусы источников.
""" """
lines = [] lines = []
lines.append(f"\n🏀 <b>{LEAGUE.upper()}</b> • {TEAM}") lines.append(f"🏀 <b>{LEAGUE.upper()}</b> • {TEAM}")
lines.append(f"📌 Game ID: <code>{GAME_ID}</code>") lines.append(f"📌 Game ID: <code>{GAME_ID}</code>")
lines.append(f"🕒 {GAME_START_DT}") lines.append(f"🕒 {GAME_START_DT}")
@@ -674,17 +701,27 @@ def build_pretty_status_message():
game_wrap = latest_data.get("game") game_wrap = latest_data.get("game")
has_game = False has_game = False
if game_wrap: if game_wrap:
game_data = game_wrap.get("data") or game_wrap raw = game_wrap.get("data") if isinstance(game_wrap, dict) else game_wrap
result = game_data.get("result") or {}
# raw может быть: dict (полный payload) | dict (уже result) | str ("ok"/"no-status")
result = {}
if isinstance(raw, dict):
# ваш нормальный полный ответ по game имеет структуру: {"data": {"result": {...}}}
# но на всякий случай поддержим и вариант, где сразу {"result": {...}} или уже {"game": ...}
result = raw.get("data", {}).get("result", {}) if "data" in raw else (raw.get("result") or raw)
else:
result = {}
game_info = result.get("game") or {} game_info = result.get("game") or {}
team1_name = result["team1"]["name"] team1_name = (result.get("team1") or {}).get("name", "Team 1")
team2_name = result["team2"]["name"] team2_name = (result.get("team2") or {}).get("name", "Team 2")
lines.append(f"👥 {team1_name} vs {team2_name}")
score_now = game_info.get("score") or "" score_now = game_info.get("score") or ""
full_score = game_info.get("fullScore") or "" full_score = game_info.get("fullScore") or ""
lines.append(f"👥 {team1_name} vs {team2_name}")
if score_now: if score_now:
lines.append(f"🔢 Score: <b>{score_now}</b>") lines.append(f"🔢 Score: <b>{score_now}</b>")
@@ -694,14 +731,31 @@ def build_pretty_status_message():
if q_text: if q_text:
lines.append(f"🧱 By quarters: {q_text}") lines.append(f"🧱 By quarters: {q_text}")
has_game = True has_game = bool(result)
# live-status отдельно # live-status отдельно
ls = latest_data.get("live-status", {}) # ls = latest_data.get("live-status", {})
ls_raw = ls.get("data") or {} # ls_raw = ls.get("data") or {}
# ls_status = (
# ls_raw.get("status") or ls_raw.get("gameStatus") or ls_raw.get("state") or "—"
# )
# lines.append(f"🟢 LIVE status: <b>{ls_status}</b>")
ls_wrap = latest_data.get("live-status")
ls_status = ""
if ls_wrap:
raw = ls_wrap.get("data")
if isinstance(raw, dict):
ls_dict = raw.get("result") or raw
ls_status = ( ls_status = (
ls_raw.get("status") or ls_raw.get("gameStatus") or ls_raw.get("state") or "" ls_dict.get("status")
or ls_dict.get("gameStatus")
or ls_dict.get("state")
or ""
) )
elif isinstance(raw, str):
# API/consumer могли положить просто строку статуса: "ok", "no-status", "error"
ls_status = raw
lines.append(f"🟢 LIVE status: <b>{ls_status}</b>") lines.append(f"🟢 LIVE status: <b>{ls_status}</b>")
# добавим блок по источникам — это как раз “состояние запросов” # добавим блок по источникам — это как раз “состояние запросов”
@@ -864,98 +918,6 @@ async def lifespan(app: FastAPI):
) )
thread_status_broadcaster.start() thread_status_broadcaster.start()
# 5. Подготовим онлайн и офлайн наборы (как у тебя)
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
),
600,
stop_event,
),
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
),
600,
stop_event,
),
daemon=True,
),
threading.Thread(
target=get_data_from_API,
args=(
"actual-standings",
URLS["actual-standings"].format(
host=HOST, league=LEAGUE, season=season, lang=LANG
),
600,
stop_event,
),
daemon=True,
),
threading.Thread(
target=get_data_from_API,
args=(
"game",
URLS["game"].format(host=HOST, game_id=game_id, lang=LANG),
600,
stop_event,
),
daemon=True,
),
threading.Thread(
target=get_data_from_API,
args=(
"live-status",
URLS["live-status"].format(host=HOST, game_id=game_id),
0.5,
stop_event,
),
daemon=True,
),
threading.Thread(
target=get_data_from_API,
args=(
"box-score",
URLS["box-score"].format(host=HOST, game_id=game_id),
0.5,
stop_event,
),
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,
),
daemon=True,
),
]
threads_offline = [
threading.Thread(
target=get_data_from_API,
args=(
"game",
URLS["game"].format(host=HOST, game_id=game_id, lang=LANG),
600, # реже
stop_event,
),
daemon=True,
)
]
# 5. решаем, что запускать # 5. решаем, что запускать
if not is_today: if not is_today:
STATUS = "no_game_today" STATUS = "no_game_today"
@@ -1141,7 +1103,8 @@ async def status(request: Request):
accept = request.headers.get("accept", "") accept = request.headers.get("accept", "")
if "text/html" in accept: if "text/html" in accept:
status_raw = str(STATUS).lower() status_raw = str(STATUS).lower()
if status_raw in ["live"]: # print(status_raw)
if status_raw in ["live", "online"]:
gs_class = "live" gs_class = "live"
gs_text = "🟢 LIVE" gs_text = "🟢 LIVE"
elif status_raw in ["live_soon", "today_not_started"]: elif status_raw in ["live_soon", "today_not_started"]:
@@ -1968,7 +1931,7 @@ async def team_comparison():
async def regular_standings(): async def regular_standings():
data = latest_data["actual-standings"]["data"]["items"] data = latest_data["actual-standings"]["data"]["items"]
for item in data: for item in data:
if item["comp"]["name"] == "Regular Season": # if item["comp"]["name"] == "Regular Season":
if item.get("standings"): if item.get("standings"):
standings_rows = item["standings"] standings_rows = item["standings"]