129 lines
5.5 KiB
Python
129 lines
5.5 KiB
Python
#!/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)
|