142 lines
4.5 KiB
Python
142 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Universal SteamGridDB artwork fetcher for non-Steam shortcuts on Bazzite HTPC.
|
|
|
|
Usage:
|
|
python3 steam-grid-artwork.py --appid APPID --name "Game Name"
|
|
|
|
Will download grid (p.png), hero (_hero.png), and logo (_logo.png)
|
|
into ~/.steam/steam/userdata/147839491/config/grid/
|
|
|
|
Requires: SGDB_API_KEY env var or ~/Documents/steamgriddb_api_key.txt
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
# ── config ────────────────────────────────────────────
|
|
GRID_DIR = os.path.expanduser(
|
|
"~/.steam/steam/userdata/147839491/config/grid"
|
|
)
|
|
KEY_FILE = os.path.expanduser("~/Documents/steamgriddb_api_key.txt")
|
|
SGDB_KEY = os.environ.get("SGDB_API_KEY")
|
|
if not SGDB_KEY:
|
|
try:
|
|
SGDB_KEY = Path(KEY_FILE).read_text().strip()
|
|
except Exception:
|
|
print("ERROR: SGDB_API_KEY not set and ~/Documents/steamgriddb_api_key.txt not found")
|
|
sys.exit(1)
|
|
|
|
API = "https://www.steamgriddb.com/api/v2"
|
|
UA = "steam-grid-artwork/1.0"
|
|
|
|
|
|
def api_get(path):
|
|
"""curl wrapper returning parsed JSON."""
|
|
url = f"{API}/{path}"
|
|
r = subprocess.run(
|
|
["curl", "-s", "-H", f"Authorization: Bearer {SGDB_KEY}", "-A", UA, url],
|
|
capture_output=True, text=True, timeout=20
|
|
)
|
|
if r.returncode != 0:
|
|
print(f" curl error: {r.stderr}")
|
|
return None
|
|
try:
|
|
return json.loads(r.stdout)
|
|
except json.JSONDecodeError:
|
|
print(f" invalid JSON response: {r.stdout[:200]}")
|
|
return None
|
|
|
|
|
|
def download(url, dest):
|
|
"""Download a file via curl."""
|
|
print(f" → {dest.name} ({dest.parent})")
|
|
subprocess.run(
|
|
["curl", "-sL", "-H", "User-Agent: Mozilla/5.0", url, "-o", str(dest)],
|
|
capture_output=True, timeout=60, check=True
|
|
)
|
|
size = dest.stat().st_size
|
|
print(f" {size:,} bytes")
|
|
|
|
|
|
def pick_first(items, prefer_style=None, prefer_dim=None):
|
|
"""Pick first item, optionally preferring a style or dimension."""
|
|
if prefer_style:
|
|
for item in items:
|
|
if item.get("style") == prefer_style:
|
|
return item
|
|
if prefer_dim:
|
|
w, h = prefer_dim
|
|
for item in items:
|
|
if item.get("width") == w and item.get("height") == h:
|
|
return item
|
|
return items[0] if items else None
|
|
|
|
|
|
def fetch_artwork(appid, game_name):
|
|
"""Fetch grid, hero, logo for a given appid and game name."""
|
|
# ── search ──
|
|
safe_name = game_name.replace(" ", "%20")
|
|
result = api_get(f"search/autocomplete/{safe_name}")
|
|
if not result or not result.get("data"):
|
|
# try trimmed name (before colon, dash etc.)
|
|
short = game_name.split(":")[0].split("-")[0].strip().replace(" ", "%20")
|
|
if short != safe_name:
|
|
result = api_get(f"search/autocomplete/{short}")
|
|
if not result or not result.get("data"):
|
|
print(f" ✗ game '{game_name}' not found on SteamGridDB")
|
|
return False
|
|
|
|
game = result["data"][0]
|
|
game_id = game["id"]
|
|
sg_name = game["name"]
|
|
print(f" ✓ SGDB id={game_id} — \"{sg_name}\"")
|
|
|
|
# ── grid (p.png) ──
|
|
dest = Path(GRID_DIR) / f"{appid}p.png"
|
|
print(" Grid:")
|
|
result = api_get(f"grids/game/{game_id}")
|
|
if result and result.get("data"):
|
|
item = pick_first(result["data"], prefer_style="alternate")
|
|
if item:
|
|
download(item["url"], dest)
|
|
else:
|
|
print(" no grids found")
|
|
|
|
# ── hero (_hero.png) ──
|
|
dest = Path(GRID_DIR) / f"{appid}_hero.png"
|
|
print(" Hero:")
|
|
result = api_get(f"heroes/game/{game_id}")
|
|
if result and result.get("data"):
|
|
item = pick_first(result["data"])
|
|
if item:
|
|
download(item["url"], dest)
|
|
else:
|
|
print(" no heroes found")
|
|
|
|
# ── logo (_logo.png) ──
|
|
dest = Path(GRID_DIR) / f"{appid}_logo.png"
|
|
print(" Logo:")
|
|
result = api_get(f"logos/game/{game_id}")
|
|
if result and result.get("data"):
|
|
item = pick_first(result["data"])
|
|
if item:
|
|
download(item["url"], dest)
|
|
else:
|
|
print(" no logos found")
|
|
|
|
return True
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import argparse
|
|
parser = argparse.ArgumentParser(description="Fetch SteamGridDB artwork")
|
|
parser.add_argument("--appid", required=True, help="Steam app ID (unsigned)")
|
|
parser.add_argument("--name", required=True, help="Game name for search")
|
|
args = parser.parse_args()
|
|
|
|
fetch_artwork(args.appid, args.name)
|