Compare commits
7 Commits
101a5a09d3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| eff04e68f4 | |||
| aec3fccfea | |||
| bfa74b51c3 | |||
| a317dd9d6a | |||
| 9efb351522 | |||
|
|
04574f5621 | ||
|
|
9aa698dcfe |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,4 +3,5 @@
|
||||
/logs/*
|
||||
*.venv
|
||||
*.env
|
||||
/shotmaps/*
|
||||
/shotmaps/*
|
||||
get_data copy.py
|
||||
@@ -507,9 +507,6 @@ main() {
|
||||
# Загрузка кода
|
||||
download_code "$release"
|
||||
|
||||
# Проверка файлов
|
||||
check_required_files
|
||||
|
||||
# Настройка виртуального окружения
|
||||
setup_venv
|
||||
|
||||
|
||||
142
get_data.py
142
get_data.py
@@ -1,5 +1,5 @@
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
from fastapi.responses import Response, HTMLResponse, StreamingResponse
|
||||
from fastapi.responses import Response, HTMLResponse, StreamingResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from contextlib import asynccontextmanager
|
||||
import requests, uvicorn, json
|
||||
@@ -14,7 +14,7 @@ from dotenv import load_dotenv
|
||||
from pprint import pprint
|
||||
import nasio
|
||||
import io, os, platform, time
|
||||
import xml.etree.ElementTree as ETя
|
||||
import xml.etree.ElementTree as ET
|
||||
import re
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from io import BytesIO
|
||||
@@ -202,6 +202,8 @@ URLS = {
|
||||
"live-status": "{host}api/abc/games/live-status?id={game_id}",
|
||||
"box-score": "{host}api/abc/games/box-score?id={game_id}",
|
||||
"play-by-play": "{host}api/abc/games/play-by-play?id={game_id}",
|
||||
"players-stats-league": "{host}/api/abc/comps/players-stats?tag={league}&lang={lang}&maxResultCount=10000",
|
||||
"players-stats-season": "{host}/api/abc/comps/players-stats?tag={league}&season={season}&lang={lang}&maxResultCount=10000",
|
||||
}
|
||||
|
||||
|
||||
@@ -687,11 +689,11 @@ def results_consumer():
|
||||
"game over",
|
||||
]
|
||||
|
||||
# 1) матч ЗАКОНЧЕН → запускаем ОТСРОЧЕННЫЙ переход
|
||||
# 1) матч ЗАКОНЧЕН → запускаем ОТСРОЧЕННЫЙ переход
|
||||
# ##TODO - Усложненый код? Нужен, если статус бывает сложнее, чем "result",
|
||||
# а что-то в стиле "result 1:0", т.е. слова из finished_markers являются
|
||||
# а что-то в стиле "result 1:0", т.е. слова из finished_markers являются
|
||||
# состовной частью настоящего статуса
|
||||
# В противном случае вполне рабочий вариант:
|
||||
# В противном случае вполне рабочий вариант:
|
||||
# if raw_ls_status_low in finished_markers:
|
||||
if any(m in raw_ls_status_low for m in finished_markers):
|
||||
now_ts = time.time()
|
||||
@@ -1398,6 +1400,37 @@ async def lifespan(app: FastAPI):
|
||||
)
|
||||
thread_excel.start()
|
||||
|
||||
try:
|
||||
season_stats = requests.get(
|
||||
URLS["players-stats-season"].format(
|
||||
host=HOST, league=LEAGUE, season=season, lang=LANG
|
||||
)
|
||||
).json()
|
||||
except Exception as ex:
|
||||
season_stats = None
|
||||
try:
|
||||
league_stats = requests.get(
|
||||
URLS["players-stats-league"].format(host=HOST, league=LEAGUE, lang=LANG)
|
||||
).json()
|
||||
except Exception as ex:
|
||||
league_stats = None
|
||||
|
||||
# 👇 ДОБАВЬ ЭТО
|
||||
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
||||
|
||||
if season_stats is not None:
|
||||
latest_data["season_stats"] = {
|
||||
"ts": ts,
|
||||
"data": season_stats,
|
||||
}
|
||||
|
||||
if league_stats is not None:
|
||||
latest_data["league_stats"] = {
|
||||
"ts": ts,
|
||||
"data": league_stats,
|
||||
}
|
||||
# 👆 ДО СЮДА
|
||||
|
||||
# 4. запускаем "длинные" потоки (они у тебя и так всегда)
|
||||
thread_result_consumer = threading.Thread(
|
||||
target=results_consumer,
|
||||
@@ -1473,16 +1506,6 @@ app = FastAPI(
|
||||
redoc_url=None, # ❌ отключает /redoc
|
||||
openapi_url=None, # ❌ отключает /openapi.json
|
||||
)
|
||||
# раздаём /shotmaps как статику из SHOTMAP_DIR
|
||||
# app.mount("/shotmaps", StaticFiles(directory=SHOTMAP_DIR), name="shotmaps")
|
||||
|
||||
# @app.get("/shotmaps/{filename}")
|
||||
# async def get_shotmap(filename: str):
|
||||
# data = SHOTMAP_CACHE.get(filename)
|
||||
# if not data:
|
||||
# # если вдруг перезапустился процесс или такой карты нет
|
||||
# raise HTTPException(status_code=404, detail="Shotmap not found")
|
||||
# return Response(content=data, media_type="image/png")
|
||||
|
||||
|
||||
def format_time(seconds: float | int) -> str:
|
||||
@@ -1709,6 +1732,7 @@ async def status(request: Request):
|
||||
),
|
||||
}
|
||||
for item in sorted_keys
|
||||
if item not in ["league_stats", "season_stats"]
|
||||
],
|
||||
}
|
||||
|
||||
@@ -2246,7 +2270,12 @@ async def team(who: str):
|
||||
f"{item.get('displayNumber')}.png",
|
||||
)
|
||||
if item.get("startRole") == "Player"
|
||||
else ""
|
||||
else os.path.join(
|
||||
"D:\\Photos",
|
||||
result["league"]["abcName"],
|
||||
result[who]["name"],
|
||||
f'Head Coach_{(item.get("lastName") or "").strip()} {(item.get("firstName") or "").strip()}.png',
|
||||
)
|
||||
),
|
||||
# live-стата
|
||||
"pts": _as_int(stats.get("points")),
|
||||
@@ -3237,9 +3266,11 @@ def change_vmix_datasource_urls(xml_data, new_base_url: str) -> bytes:
|
||||
root = ET.fromstring(text)
|
||||
|
||||
# 4. Меняем URL
|
||||
for url_tag in root.findall(".//datasource[@friendlyName='JSON']//instance//state/xml/url"):
|
||||
for url_tag in root.findall(
|
||||
".//datasource[@friendlyName='JSON']//instance//state/xml/url"
|
||||
):
|
||||
old_url = url_tag.text.strip()
|
||||
pattern = r'https?:\/\/[^\/]+'
|
||||
pattern = r"https?:\/\/[^\/]+"
|
||||
new_url = re.sub(pattern, new_base_url, old_url, count=0, flags=0)
|
||||
url_tag.text = new_url
|
||||
|
||||
@@ -3633,6 +3664,7 @@ async def get_shotmap_image(player_id_shots: str):
|
||||
Отдаёт картинку карты бросков из оперативной памяти.
|
||||
player_id_shots должен совпадать с ключом в SHOTMAP_CACHE, например "23_shots".
|
||||
"""
|
||||
# async with SHOTMAPS_LOCK:
|
||||
data = SHOTMAP_CACHE.get(player_id_shots)
|
||||
if not data:
|
||||
raise HTTPException(status_code=404, detail="Shotmap not found in memory")
|
||||
@@ -3738,6 +3770,30 @@ async def last_5_games():
|
||||
if not CALENDAR or "items" not in CALENDAR:
|
||||
return {"opponent": "", "date": "", "place": "", "place_ru": ""}
|
||||
|
||||
WEEKDAYS_EN = [
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
"sunday",
|
||||
]
|
||||
MONTHS_EN = [
|
||||
"january",
|
||||
"february",
|
||||
"march",
|
||||
"april",
|
||||
"may",
|
||||
"june",
|
||||
"july",
|
||||
"august",
|
||||
"september",
|
||||
"october",
|
||||
"november",
|
||||
"december",
|
||||
]
|
||||
|
||||
now = datetime.now() # наивное "сейчас"
|
||||
best = None # {"dt": ..., "opp": ..., "place": "home"/"away"}
|
||||
|
||||
@@ -3779,11 +3835,23 @@ async def last_5_games():
|
||||
|
||||
place_ru = "дома" if best["place"] == "home" else "в гостях"
|
||||
|
||||
# 🆕 формируем английскую строку
|
||||
dt = best["dt"]
|
||||
weekday_en = WEEKDAYS_EN[dt.weekday()] # monday..sunday
|
||||
month_en = MONTHS_EN[dt.month - 1] # january..december
|
||||
day = dt.day # 1..31
|
||||
place_en = "home" if best["place"] == "home" else "away"
|
||||
|
||||
formatted = (
|
||||
f"{weekday_en}, {month_en} {day}, at {place_en} against {best['opp']}"
|
||||
)
|
||||
|
||||
return {
|
||||
"opponent": best["opp"],
|
||||
"date": best["dt"].strftime("%Y-%m-%d %H:%M"),
|
||||
"place": best["place"], # "home" / "away"
|
||||
"place_ru": place_ru, # "дома" / "в гостях"
|
||||
"formatted": formatted, # 🆕 "wednesday, march 26, at home against astana"
|
||||
}
|
||||
|
||||
# последние 5 игр и результаты
|
||||
@@ -3806,6 +3874,7 @@ async def last_5_games():
|
||||
"nextGameDate": next1["date"],
|
||||
"nextGamePlace": next1["place_ru"], # "дома" / "в гостях"
|
||||
"nextGameHomeAway": next1["place"], # "home" / "away" (если нужно в логике)
|
||||
"nextGameFormatted": next1["formatted"],
|
||||
},
|
||||
{
|
||||
"teamName": team2_name,
|
||||
@@ -3815,6 +3884,7 @@ async def last_5_games():
|
||||
"nextGameDate": next2["date"],
|
||||
"nextGamePlace": next2["place_ru"],
|
||||
"nextGameHomeAway": next2["place"],
|
||||
"nextGameFormatted": next2["formatted"],
|
||||
},
|
||||
]
|
||||
return data
|
||||
@@ -3916,7 +3986,8 @@ async def commentary():
|
||||
plus_minus = p.get("plusMinus", "")
|
||||
kpi = p.get("kpi", "")
|
||||
|
||||
rows.append(f"""
|
||||
rows.append(
|
||||
f"""
|
||||
<tr>
|
||||
<td>{num}</td>
|
||||
<td class="player-name"
|
||||
@@ -3939,7 +4010,8 @@ async def commentary():
|
||||
<td class="extra-col">{plus_minus}</td>
|
||||
<td class="extra-col">{kpi}</td>
|
||||
</tr>
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
return f"""
|
||||
<h3>{title}</h3>
|
||||
@@ -4458,7 +4530,7 @@ async def dashboard():
|
||||
|
||||
# порядок вывода метрик в центральном столбце (как на твоём дашборде)
|
||||
center_stats_order = [
|
||||
"pt-1", # штрафные
|
||||
"pt-1", # штрафные
|
||||
"pt-1_pro",
|
||||
"pt-2",
|
||||
"pt-2_pro",
|
||||
@@ -4901,6 +4973,34 @@ async def dashboard():
|
||||
return HTMLResponse(content=html)
|
||||
|
||||
|
||||
@app.get("/game_history")
|
||||
async def game_history():
|
||||
pregame = get_latest_game_safe("pregame")
|
||||
if not pregame:
|
||||
return [{"Данных об истории команд нет!"}]
|
||||
|
||||
pregame_data = pregame["data"] if "data" in pregame else pregame
|
||||
result = pregame_data.get("result", {}).get("gameHistory", {}) or {}
|
||||
history = []
|
||||
|
||||
for row in result:
|
||||
row_team1 = get_excel_row_for_team(row["team1"]["name"]) or {}
|
||||
row_team2 = get_excel_row_for_team(row["team2"]["name"]) or {}
|
||||
history.append(
|
||||
{
|
||||
"team1": row_team1.get("TeamTLA", ""),
|
||||
"team2": row_team2.get("TeamTLA", ""),
|
||||
"team1_logo": row_team1.get("TeamLogo", ""),
|
||||
"team2_logo": row_team2.get("TeamLogo", ""),
|
||||
"score1": row["game"]["score1"],
|
||||
"score2": row["game"]["score2"],
|
||||
"localDate": row["game"]["localDate"],
|
||||
"team1Win": pregame_data.get("result", {}).get("team1Win"),
|
||||
"team2Win": pregame_data.get("result", {}).get("team2Win"),
|
||||
}
|
||||
)
|
||||
return history
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(
|
||||
"get_data:app", host="0.0.0.0", port=8000, reload=True, log_level="debug"
|
||||
|
||||
Reference in New Issue
Block a user