From 257708bf3ecc5dc4b75c9f531029253638d46332 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Wed, 27 May 2026 21:00:59 +0600 Subject: [PATCH] =?UTF-8?q?[2026-05-27]=20add:=20steam-grid-artwork.py=20?= =?UTF-8?q?=E2=80=94=20SteamGridDB=20artwork=20fetcher=20for=20non-Steam?= =?UTF-8?q?=20shortcuts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- steam-grid-artwork.py | 141 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 steam-grid-artwork.py diff --git a/steam-grid-artwork.py b/steam-grid-artwork.py new file mode 100644 index 00000000..94c67fd7 --- /dev/null +++ b/steam-grid-artwork.py @@ -0,0 +1,141 @@ +#!/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)