feat: use SGDB for cover too

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
2025-10-20 13:03:52 +05:00
parent b59ee5ae8e
commit 3736bb279e
2 changed files with 85 additions and 1 deletions

View File

@@ -83,6 +83,43 @@ def load_pixmap_async(cover: str, width: int, height: int, callback: Callable[[Q
except Exception as e: except Exception as e:
logger.error(f"Ошибка обработки URL {cover}: {e}") logger.error(f"Ошибка обработки URL {cover}: {e}")
# SteamGridDB (SGDB)
if cover and cover.startswith("https://cdn2.steamgriddb.com"):
try:
parts = cover.split("/")
filename = parts[-1] if parts else "sgdb_cover.png"
# SGDB ссылки содержат уникальный хеш в названии — используем как имя
local_path = os.path.join(image_folder, filename)
if os.path.exists(local_path):
pixmap = QPixmap(local_path)
finish_with(pixmap)
return
def on_downloaded(result: str | None):
pixmap = QPixmap()
if result and os.path.exists(result):
pixmap.load(result)
if pixmap.isNull():
placeholder_path = theme_manager.get_theme_image("placeholder", current_theme_name)
if placeholder_path and QFile.exists(placeholder_path):
pixmap.load(placeholder_path)
else:
pixmap = QPixmap(width, height)
pixmap.fill(QColor("#333333"))
painter = QPainter(pixmap)
painter.setPen(QPen(QColor("white")))
painter.drawText(pixmap.rect(), Qt.AlignmentFlag.AlignCenter, "No Image")
painter.end()
finish_with(pixmap)
logger.info("Downloading SGDB cover for %s -> %s", app_name or "unknown", filename)
downloader.download_async(cover, local_path, timeout=5, callback=on_downloaded)
return
except Exception as e:
logger.error(f"Ошибка обработки SGDB URL {cover}: {e}")
if cover and cover.startswith(("http://", "https://")): if cover and cover.startswith(("http://", "https://")):
try: try:
local_path = os.path.join(image_folder, f"{app_name}.jpg") local_path = os.path.join(image_folder, f"{app_name}.jpg")

View File

@@ -23,6 +23,7 @@ import requests
import random import random
import base64 import base64
import glob import glob
import urllib.parse
downloader = Downloader() downloader = Downloader()
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -411,6 +412,39 @@ def save_app_details(app_id, data):
with open(cache_file, "wb") as f: with open(cache_file, "wb") as f:
f.write(orjson.dumps(data)) f.write(orjson.dumps(data))
def fetch_sgdb_cover(game_name: str) -> str:
"""
Fetch a cover image URL from steamgrid.usebottles.com for the given game.
The API returns a single string (quoted URL).
"""
try:
encoded = urllib.parse.quote(game_name)
url = f"https://steamgrid.usebottles.com/api/search/{encoded}"
resp = requests.get(url, timeout=5)
if resp.status_code != 200:
logger.warning("SGDB request failed for %s: %s", game_name, resp.status_code)
return ""
text = resp.text.strip()
# Убираем возможные кавычки вокруг строки
if text.startswith('"') and text.endswith('"'):
text = text[1:-1]
if text:
logger.info("Fetched SGDB cover for %s: %s", game_name, text)
return text
except Exception as e:
logger.warning("Failed to fetch SGDB cover for %s: %s", game_name, e)
return ""
def check_url_exists(url: str) -> bool:
"""Check whether a URL returns HTTP 200."""
try:
r = requests.head(url, timeout=3)
return r.status_code == 200
except Exception:
return False
def fetch_app_info_async(app_id: int, callback: Callable[[dict | None], None]): def fetch_app_info_async(app_id: int, callback: Callable[[dict | None], None]):
""" """
Asynchronously fetches detailed app info from Steam API. Asynchronously fetches detailed app info from Steam API.
@@ -629,6 +663,11 @@ def get_full_steam_game_info_async(appid: int, callback: Callable[[dict], None])
title = decode_text(app_info.get("name", "")) title = decode_text(app_info.get("name", ""))
description = decode_text(app_info.get("short_description", "")) description = decode_text(app_info.get("short_description", ""))
cover = f"https://steamcdn-a.akamaihd.net/steam/apps/{appid}/library_600x900_2x.jpg" cover = f"https://steamcdn-a.akamaihd.net/steam/apps/{appid}/library_600x900_2x.jpg"
if not check_url_exists(cover):
logger.info("Steam cover not found for %s, trying SGDB", title)
alt_cover = fetch_sgdb_cover(title)
if alt_cover:
cover = alt_cover
def on_protondb_tier(tier: str): def on_protondb_tier(tier: str):
def on_anticheat_status(anticheat_status: str): def on_anticheat_status(anticheat_status: str):
@@ -722,12 +761,15 @@ def get_steam_game_info_async(desktop_name: str, exec_line: str, callback: Calla
game_name = desktop_name or exe_name.capitalize() game_name = desktop_name or exe_name.capitalize()
if not matching_app: if not matching_app:
cover = fetch_sgdb_cover(game_name) or ""
logger.info("Using SGDB cover for non-Steam game '%s': %s", game_name, cover)
def on_anticheat_status(anticheat_status: str): def on_anticheat_status(anticheat_status: str):
callback({ callback({
"appid": "", "appid": "",
"name": decode_text(game_name), "name": decode_text(game_name),
"description": "", "description": "",
"cover": "", "cover": cover,
"controller_support": "", "controller_support": "",
"protondb_tier": "", "protondb_tier": "",
"steam_game": "false", "steam_game": "false",
@@ -758,6 +800,11 @@ def get_steam_game_info_async(desktop_name: str, exec_line: str, callback: Calla
title = decode_text(app_info.get("name", game_name)) title = decode_text(app_info.get("name", game_name))
description = decode_text(app_info.get("short_description", "")) description = decode_text(app_info.get("short_description", ""))
cover = f"https://steamcdn-a.akamaihd.net/steam/apps/{appid}/library_600x900_2x.jpg" cover = f"https://steamcdn-a.akamaihd.net/steam/apps/{appid}/library_600x900_2x.jpg"
if not check_url_exists(cover):
logger.info("Steam cover not found for %s, trying SGDB", title)
alt_cover = fetch_sgdb_cover(title)
if alt_cover:
cover = alt_cover
controller_support = app_info.get("controller_support", "") controller_support = app_info.get("controller_support", "")
def on_protondb_tier(tier: str): def on_protondb_tier(tier: str):