forked from Boria138/PortProtonQt
feat: use SGDB for cover too
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
@@ -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")
|
||||||
|
@@ -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):
|
||||||
|
Reference in New Issue
Block a user