добавил условие в тред game.
поправил results_consumer. поправил статусы на live. поправил выводы статусов в телеграмм. поправил вывод турнирной таблицы. удалил лишние повторящие треды
This commit is contained in:
231
get_data.py
231
get_data.py
@@ -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"]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user