#!/usr/bin/env python3 """ Сверяет кэш размеров (steam_app_sizes.json) с api.steamcmd.net. Находит игры, у которых размер в кэше сильно меньше, чем по steamcmd — скорее всего размер был взят из fallback (магазин Steam), а не из steamcmd. Запуск: из homelab/scripts/ python3 check_size_sources.py — проверить все игры (~10–15 мин) python3 check_size_sources.py --sample 50 — только 50 для быстрой оценки """ import argparse import json import random import time from pathlib import Path # используем ту же логику, что и steam_library_size from steam_library_size import get_app_size_from_steamcmd SCRIPT_DIR = Path(__file__).parent CACHE_FILE = SCRIPT_DIR / "steam_app_sizes.json" LIBRARY_FILE = SCRIPT_DIR / "steam_library.json" REQUEST_DELAY = 0.5 # если в кэше меньше чем (steamcmd * MIN_RATIO), считаем источник сомнительным MIN_RATIO = 0.6 # или если steamcmd даёт больше MIN_GB, а разница больше DIFF_GB MIN_GB_STEAMCMD = 15.0 DIFF_GB = 10.0 def main(sample: int | None = None) -> None: with open(CACHE_FILE, encoding="utf-8") as f: cache = json.load(f) cache_sizes = {int(k): v for k, v in cache.items()} if LIBRARY_FILE.exists(): with open(LIBRARY_FILE, encoding="utf-8") as f: library = json.load(f) name_by_appid = {g["appid"]: g.get("name", "?") for g in library} else: name_by_appid = {} items = list(cache_sizes.items()) if sample is not None and len(items) > sample: random.seed(42) items = random.sample(items, sample) print(f"Режим выборки: проверяем {sample} из {len(cache_sizes)} игр") total = len(items) suspicious = [] for i, (appid, cached_gb) in enumerate(items): if cached_gb is None: continue if not isinstance(cached_gb, (int, float)): continue cached_gb = float(cached_gb) steamcmd_gb = get_app_size_from_steamcmd(appid) time.sleep(REQUEST_DELAY) name = name_by_appid.get(appid, "?") if i % 20 == 0 or i == total - 1: print(f"\rПроверено {i + 1}/{total} {name[:40]}", end="", flush=True) if steamcmd_gb is None: continue if cached_gb >= steamcmd_gb * MIN_RATIO: continue if steamcmd_gb >= MIN_GB_STEAMCMD and (steamcmd_gb - cached_gb) >= DIFF_GB: suspicious.append({ "appid": appid, "name": name, "cached_gb": round(cached_gb, 2), "steamcmd_gb": round(steamcmd_gb, 2), }) print() print(f"Проверено: {total}") print(f"Подозрительных (кэш заметно меньше steamcmd): {len(suspicious)}") if sample and total < len(cache_sizes): print(f"(оценка на всю библиотеку: ~{len(suspicious) * len(cache_sizes) // max(1, total)} таких игр)") print() if suspicious: print("Список (кэш ГБ → steamcmd ГБ):") for s in sorted(suspicious, key=lambda x: -x["steamcmd_gb"]): print(f" {s['appid']:>8} {s['cached_gb']:>7.1f} → {s['steamcmd_gb']:>7.1f} {s['name']}") return suspicious def heuristic_round_numbers() -> None: """Без API: считает записи с «круглым» размером в ГБ (часто из требований магазина).""" with open(CACHE_FILE, encoding="utf-8") as f: cache = json.load(f) if LIBRARY_FILE.exists(): with open(LIBRARY_FILE, encoding="utf-8") as f: library = json.load(f) name_by_appid = {g["appid"]: g.get("name", "?") for g in library} else: name_by_appid = {} # круглые числа: целые или X.0, и типичные для магазина (1, 2, 4, 6, 8, 12, 16, 20, 70...) round_entries = [] for k, v in cache.items(): if v is None: continue gb = float(v) if gb <= 0: continue if gb == int(gb) or abs(gb - round(gb)) < 0.01: round_entries.append((int(k), round(gb, 1), name_by_appid.get(int(k), "?"))) round_entries.sort(key=lambda x: -x[1]) print(f"Записей с «круглым» размером (целое число ГБ): {len(round_entries)} из {len(cache)}") print("Часто так указывают в системных требованиях магазина, а не реальный размер депо.") print() print("Топ по размеру (могут быть занижены):") for appid, gb, name in round_entries[:30]: print(f" {gb:>6.1f} ГБ {appid:>8} {name}") if len(round_entries) > 30: print(f" ... и ещё {len(round_entries) - 30}") if __name__ == "__main__": p = argparse.ArgumentParser(description="Сверка кэша размеров с steamcmd API") p.add_argument("--sample", type=int, default=None, help="Проверить только N игр (быстрая оценка)") p.add_argument("--heuristic", action="store_true", help="Только эвристика по круглым числам (без API)") args = p.parse_args() if args.heuristic: heuristic_round_numbers() else: main(sample=args.sample)