diff --git a/.gitignore b/.gitignore index 236c12b..61009a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .env /.venv +logs/ /logs/* __pycache__/ *.pyc diff --git a/README.md b/README.md index e69de29..e7111d5 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,4 @@ +## Проверка логов +```shell +sudo journalctl -t KHL +``` \ No newline at end of file diff --git a/deploy.sh b/deploy.sh index 0ac9095..e063b95 100755 --- a/deploy.sh +++ b/deploy.sh @@ -11,6 +11,7 @@ NC='\033[0m' # No Color REPO_URL="https://git.tvstart.ru/ychernenko/KHL.git" TARGET_DIR="/root/KHL" SERVICE_NAME="khl-data.service" +TARGET_ENV="/mnt/aescript/.env" show_help() { echo "Использование: $0 -r <релиз> [-h]" @@ -206,25 +207,38 @@ create_systemd_service() { fi # Формируем команду для data сервиса - local data_command="$TARGET_DIR/.venv/bin/python3 $TARGET_DIR/get_data.py" local data_service_file="/etc/systemd/system/$SERVICE_NAME" log_info "Создание файла сервиса: $data_service_file" cat > "$data_service_file" << EOF [Unit] Description=KHL Data Service +Documentation=https://git.tvstart.ru/ychernenko/KHL After=network.target +Wants=network.target [Service] Type=simple User=root WorkingDirectory=$TARGET_DIR Environment=PATH=$TARGET_DIR/.venv/bin -ExecStart=$data_command -Restart=always -RestartSec=30 +EnvironmentFile=$TARGET_ENV +ExecStart=python3 $TARGET_DIR/get_data.py + +# Лимиты ресурсов +MemoryMax=1G +CPUQuota=80% + +# Логирование StandardOutput=journal StandardError=journal +SyslogIdentifier=KHL + +# Поведение при перезапуске +Restart=always +RestartSec=10 +StartLimitInterval=300 +StartLimitBurst=5 [Install] WantedBy=multi-user.target diff --git a/get_data.py b/get_data.py index e0a0b0b..de804e3 100644 --- a/get_data.py +++ b/get_data.py @@ -1,4 +1,8 @@ +<<<<<<< HEAD from fastapi import FastAPI, HTTPException, Query +======= +from fastapi import FastAPI, HTTPException, Query, Request +>>>>>>> c60caaa8aaad763cf7605dcd2c4502b8dfc3be84 from fastapi.responses import Response, JSONResponse, HTMLResponse, StreamingResponse from fastapi.encoders import jsonable_encoder import pandas as pd @@ -15,6 +19,10 @@ import nasio import logging import logging.config import platform +<<<<<<< HEAD +======= +import json +>>>>>>> c60caaa8aaad763cf7605dcd2c4502b8dfc3be84 from pprint import pprint @@ -28,6 +36,7 @@ _latest_lock = Lock() _stop_event = Event() _worker_thread: Thread | None = None +<<<<<<< HEAD # Загружаем переменные из .env if load_dotenv(dotenv_path="/mnt/khl/.env", verbose=True): print("Добавить в лог что был найден файл окружения!!") @@ -36,19 +45,22 @@ else: load_dotenv() print("Добавить в лог что не был найден файл окружения!!") +======= +pprint(f"Локальный файл окружения ={load_dotenv(verbose=True)}") +>>>>>>> c60caaa8aaad763cf7605dcd2c4502b8dfc3be84 api_user = os.getenv("API_USER") api_pass = os.getenv("API_PASS") league = os.getenv("LEAGUE") +api_base_url = os.getenv("API_BASE_URL") POLL_SEC = int(os.getenv("GAME_POLL_SECONDS")) SERVER_NAME = os.getenv("SYNO_URL") USER = os.getenv("SYNO_USERNAME") PASSWORD = os.getenv("SYNO_PASSWORD") -PATH = "/team-folders/GFX/Hockey/KHL/Soft/MATCH.xlsm" - +PATH = f'{os.getenv("SYNO_PATH")}MATCH.xlsm' def load_today_schedule(): """Возвращает DataFrame матчей на сегодня с нужными колонками (или пустой DF).""" - url_tournaments = "http://stat2tv.khl.ru/tournaments.xml" + url_tournaments = f"{api_base_url}tournaments.xml" r = requests.get( url_tournaments, auth=HTTPBasicAuth(api_user, api_pass), verify=False ) @@ -72,7 +84,7 @@ def load_today_schedule(): global current_tournament_id, current_season current_tournament_id = tournament_id current_season = season - url_schedule = f"http://stat2tv.khl.ru/{tournament_id}/schedule-{tournament_id}.xml" + url_schedule = f"{api_base_url}{tournament_id}/schedule-{tournament_id}.xml" r = requests.get(url_schedule, auth=HTTPBasicAuth(api_user, api_pass), verify=False) schedule_df = pd.read_xml(io.StringIO(r.text)) @@ -130,7 +142,7 @@ def load_today_schedule(): def _build_game_url(tournament_id: int, game_id: int) -> str: # URL по аналогии с расписанием: .../{tournament_id}/json_en/{game_id}.json # Если у тебя другой шаблон — просто поменяй строку ниже. - return f"http://stat2tv.khl.ru/{tournament_id}/json_en/{game_id}.json" + return f"{api_base_url}{tournament_id}/json_en/{game_id}.json" def _fetch_game_once(tournament_id: int, game_id: int) -> dict: @@ -737,9 +749,22 @@ def _norm_name(s: str | None) -> str: return "" return str(s).strip().casefold() +<<<<<<< HEAD @app.get("/info") def info(): +======= +def _load_buf(): + buf = nasio.load_bio(user=USER, password=PASSWORD, + nas_ip=SERVER_NAME, nas_port="443", path=PATH) + if isinstance(buf, (bytes, bytearray, memoryview)): + buf = io.BytesIO(buf) + buf.seek(0) + return buf + +@app.get("/info") +async def info(format: str = "xlsx", sheet: str = "TEAMS"): +>>>>>>> c60caaa8aaad763cf7605dcd2c4502b8dfc3be84 # 1) Проверяем, выбран ли матч global current_season if not selected_game_id: @@ -768,6 +793,7 @@ def info(): away_name = str(row.get("visitorName_en", "")).strip() # 3) Подтягиваем справочник команд из Excel (лист TEAMS) +<<<<<<< HEAD try: binary_bytes: bytes = nasio.load_bio( user=USER, @@ -841,6 +867,109 @@ def info(): return JSONResponse(content=payload) except Exception as ex: pprint(ex) +======= + src = _load_buf() + + if format == "xlsx": + # читаем нужный лист из исходного XLSM + df = pd.read_excel(src, sheet_name=sheet, engine="openpyxl") + + # пишем НОВЫЙ XLSX (без макросов) — это то, что понимает vMix + out = io.BytesIO() + with pd.ExcelWriter(out, engine="openpyxl") as writer: + df.to_excel(writer, sheet_name=sheet, index=False) + out.seek(0) + + return StreamingResponse( + out, + media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + headers={ + # стабильное имя файла, чтобы vMix не путался + "Content-Disposition": "inline; filename=vmix.xlsx", + # отключаем кэш браузера/прокси, vMix сам опрашивает по интервалу + "Cache-Control": "no-cache, no-store, must-revalidate", + "Pragma": "no-cache", + }, + ) + + elif format == "csv": + df = pd.read_excel(src, sheet_name=sheet, engine="openpyxl") + csv_bytes = df.to_csv(index=False, encoding="utf-8-sig").encode("utf-8") + return StreamingResponse( + io.BytesIO(csv_bytes), + media_type="text/csv; charset=utf-8", + headers={ + "Content-Disposition": "inline; filename=vmix.csv", + "Cache-Control": "no-cache, no-store, must-revalidate", + "Pragma": "no-cache", + }, + ) + + elif format == "json": + df = pd.read_excel(src, sheet_name=sheet, engine="openpyxl") + payload = json.dumps(df.to_dict(orient="records"), ensure_ascii=False) + return Response( + content=payload, + media_type="application/json; charset=utf-8", + headers={ + "Cache-Control": "no-cache, no-store, must-revalidate", + "Pragma": "no-cache", + }, + ) + + return Response("Unsupported format", status_code=400) + + # # Оставляем только полезные поля (подгони под свой файл) + # keep = ["Team", "Logo", "Short", "HexPodl", "HexBase", "HexText"] + # keep = [c for c in keep if c in teams_df.columns] + # teams_df = teams_df.loc[:, keep].copy() + + # # 4) Нормализованные ключи для джоина по имени + # teams_df["__key"] = teams_df["Team"].apply(_norm_name) + + # def _pick_team_info(name: str) -> dict: + # key = _norm_name(name) + # hit = teams_df.loc[teams_df["__key"] == key] + # if hit.empty: + # # не нашли точное совпадение — вернём только название + # return {"Team": name} + # rec = hit.iloc[0].to_dict() + # rec.pop("__key", None) + # # заменим NaN/inf на None, чтобы JSON не падал + # for k, v in list(rec.items()): + # if pd.isna(v) or v in (np.inf, -np.inf): + # rec[k] = None + # return rec + + # home_info = _pick_team_info(home_name) + # away_info = _pick_team_info(away_name) + # date_obj = datetime.strptime(row.get("datetime_str", ""), "%d.%m.%Y %H:%M") + # try: + # full_format = date_obj.strftime("%B %-d, %Y") + # except ValueError: + # full_format = date_obj.strftime("%B %#d, %Y") + + # payload = [ + # { + # "selected_id": int(selected_game_id), + # "tournament_id": ( + # int(current_tournament_id) if current_tournament_id else None + # ), + # "datetime": str(full_format), + # "arena": str(row.get("arena_en", "")), + # "arena_city": str(row.get("arena_city_en", "")), + # "home": home_info, + # "home_city": str(row.get("homeCity_en", "")), + # "away": away_info, + # "away_city": str(row.get("visitorCity_en", "")), + # "season": current_season, + # } + # ] + + # return JSONResponse(content=payload) + # except Exception as ex: + # pprint(ex) +>>>>>>> c60caaa8aaad763cf7605dcd2c4502b8dfc3be84 if __name__ == "__main__":