1
0
Fork 0
mirror of synced 2024-05-18 19:42:54 +12:00

SteamGrades: Return the Steam game ID with the grade and store it.

This allows compatibility tools that use the SteamAppId environment
variable to make decisions or apply fixes do their job more accurately.

Also use the stored variable to provide a link to protondb through
the grade label in GameInfo.

The steam grades now use the orjson library to load Steam's ~6MB
database faster.
This commit is contained in:
loathingKernel 2023-09-15 14:22:27 +03:00
parent 07b5d381f0
commit 36ad33b8f3
6 changed files with 70 additions and 76 deletions

View file

@ -20,7 +20,7 @@ from rare.models.game import RareGame
from rare.shared import RareCore
from rare.shared.workers import VerifyWorker, MoveWorker
from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo
from rare.utils.misc import format_size, icon
from rare.utils.misc import format_size, icon, style_hyperlink
from rare.widgets.image_widget import ImageWidget, ImageSize
from rare.widgets.side_tab import SideTabContents
from rare.components.dialogs.move_dialog import MoveDialog, is_game_dir
@ -302,7 +302,12 @@ class GameInfo(QWidget, SideTabContents):
self.ui.grade.setDisabled(
self.rgame.is_unreal or platform.system() == "Windows"
)
self.ui.grade.setText(self.steam_grade_ratings[self.rgame.steam_grade()])
self.ui.grade.setText(
style_hyperlink(
f"https://www.protondb.com/app/{self.rgame.steam_appid}",
self.steam_grade_ratings[self.rgame.steam_grade()]
)
)
self.ui.install_button.setEnabled(
(not self.rgame.is_installed or self.rgame.is_non_asset) and self.rgame.is_idle

View file

@ -19,6 +19,7 @@ from rare.shared.game_process import GameProcess
from rare.shared.image_manager import ImageManager
from rare.utils.paths import data_dir, get_rare_executable
from rare.utils.steam_grades import get_rating
from rare.utils.config_helper import add_envvar, remove_envvar
logger = getLogger("RareGame")
@ -31,6 +32,7 @@ class RareGame(RareGameSlim):
queue_pos: Optional[int] = None
last_played: datetime = datetime.min
grant_date: Optional[datetime] = None
steam_appid: int = 0
steam_grade: Optional[str] = None
steam_date: datetime = datetime.min
tags: List[str] = field(default_factory=list)
@ -43,6 +45,7 @@ class RareGame(RareGameSlim):
queue_pos=data.get("queue_pos", None),
last_played=datetime.fromisoformat(data["last_played"]) if data.get("last_played", None) else datetime.min,
grant_date=datetime.fromisoformat(data["grant_date"]) if data.get("grant_date", None) else None,
steam_appid=data.get("steam_appid", 0),
steam_grade=data.get("steam_grade", None),
steam_date=datetime.fromisoformat(data["steam_date"]) if data.get("steam_date", None) else datetime.min,
tags=data.get("tags", []),
@ -55,6 +58,7 @@ class RareGame(RareGameSlim):
queue_pos=self.queue_pos,
last_played=self.last_played.isoformat() if self.last_played else datetime.min,
grant_date=self.grant_date.isoformat() if self.grant_date else None,
steam_appid=self.steam_appid,
steam_grade=self.steam_grade,
steam_date=self.steam_date.isoformat() if self.steam_date else datetime.min,
tags=self.tags,
@ -77,6 +81,8 @@ class RareGame(RareGameSlim):
self.pixmap: QPixmap = QPixmap()
self.metadata: RareGame.Metadata = RareGame.Metadata()
self.__load_metadata()
if self.metadata.grant_date is None:
self.grant_date()
self.owned_dlcs: Set[RareGame] = set()
@ -427,18 +433,29 @@ class RareGame(RareGameSlim):
if platform.system() == "Windows" or self.is_unreal:
return "na"
elapsed_time = abs(datetime.utcnow() - self.metadata.steam_date)
if self.metadata.steam_grade is not None and elapsed_time.days < 3:
if (
self.metadata.steam_grade is not None
and self.metadata.steam_appid != 0
and elapsed_time.days < 3
):
return self.metadata.steam_grade
def _set_steam_grade():
rating = get_rating(self.core, self.app_name)
self.set_steam_grade(rating)
appid, rating = get_rating(self.core, self.app_name)
self.set_steam_grade(appid, rating)
worker = QRunnable.create(_set_steam_grade)
QThreadPool.globalInstance().start(worker)
return "pending"
def set_steam_grade(self, grade: str) -> None:
@property
def steam_appid(self) -> Optional[int]:
return self.metadata.steam_appid
def set_steam_grade(self, appid: int, grade: str) -> None:
if appid and not self.steam_appid:
add_envvar(self.app_name, "SteamAppId", str(appid))
self.metadata.steam_appid = appid
self.metadata.steam_grade = grade
self.metadata.steam_date = datetime.utcnow()
self.__save_metadata()

View file

@ -133,6 +133,7 @@ class Ui_GameInfo(object):
self.info_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.lbl_grade)
self.grade = QtWidgets.QLabel(GameInfo)
self.grade.setText("error")
self.grade.setOpenExternalLinks(True)
self.grade.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse)
self.grade.setObjectName("grade")
self.info_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.grade)

View file

@ -232,6 +232,9 @@
<property name="text">
<string notr="true">error</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>

View file

@ -204,3 +204,7 @@ def widget_object_name(widget: Union[QObject, wrappertype, Type], suffix: str) -
def elide_text(label: QLabel, text: str) -> str:
metrics = QFontMetrics(label.font())
return metrics.elidedText(text, Qt.ElideRight, label.sizeHint().width())
def style_hyperlink(link: str, title: str) -> str:
return "<a href='{}' style='color: #2980b9; text-decoration:none'>{}</a>".format(link, title)

View file

@ -1,46 +1,34 @@
import difflib
import orjson
import os
from datetime import date
from datetime import datetime
from typing import Tuple
import orjson
import requests
from rare.lgndr.core import LegendaryCore
from rare.utils.paths import data_dir, cache_dir
from rare.utils.paths import cache_dir
replace_chars = ",;.:-_ "
url = "https://api.steampowered.com/ISteamApps/GetAppList/v2/"
steamapi_url = "https://api.steampowered.com/ISteamApps/GetAppList/v2/"
protondb_url = "https://www.protondb.com/api/v1/reports/summaries/"
__steam_ids_json = None
__grades_json = None
__active_download = False
def get_rating(core: LegendaryCore, app_name: str):
global __grades_json
if __grades_json is None:
if os.path.exists(p := os.path.join(data_dir(), "steam_ids.json")):
grades = orjson.loads(open(p).read())
__grades_json = grades
else:
grades = {}
__grades_json = grades
def get_rating(core: LegendaryCore, app_name: str) -> Tuple[int, str]:
game = core.get_game(app_name)
try:
steam_id = get_steam_id(game.app_title)
if not steam_id:
raise Exception
grade = get_grade(steam_id)
except Exception as e:
return 0, "fail"
else:
grades = __grades_json
if not grades.get(app_name):
game = core.get_game(app_name)
try:
steam_id = get_steam_id(game.app_title)
grade = get_grade(steam_id)
except:
return "fail"
grades[app_name] = {"steam_id": steam_id, "grade": grade}
with open(os.path.join(data_dir(), "steam_ids.json"), "w") as f:
f.write(orjson.dumps(grades).decode("utf-8"))
return grade
else:
return grades[app_name].get("grade")
return steam_id, grade
# you should iniciate the module with the game's steam code
@ -48,8 +36,7 @@ def get_grade(steam_code):
if steam_code == 0:
return "fail"
steam_code = str(steam_code)
url = "https://www.protondb.com/api/v1/reports/summaries/"
res = requests.get(f"{url}{steam_code}.json")
res = requests.get(f"{protondb_url}{steam_code}.json")
try:
lista = orjson.loads(res.text)
except orjson.JSONDecodeError:
@ -60,13 +47,19 @@ def get_grade(steam_code):
def load_json() -> dict:
file = os.path.join(cache_dir(), "game_list.json")
if not os.path.exists(file):
response = requests.get(url)
mod_time = datetime.fromtimestamp(os.path.getmtime(file))
elapsed_time = abs(datetime.now() - mod_time)
global __active_download
if __active_download:
return {}
if not os.path.exists(file) or elapsed_time.days > 7:
__active_download = True
response = requests.get(steamapi_url)
__active_download = False
steam_ids = orjson.loads(response.text)["applist"]["apps"]
ids = {}
for game in steam_ids:
ids[game["name"]] = game["appid"]
with open(file, "w") as f:
f.write(orjson.dumps(ids).decode("utf-8"))
return ids
@ -74,51 +67,22 @@ def load_json() -> dict:
return orjson.loads(open(file, "r").read())
def get_steam_id(title: str):
file = os.path.join(cache_dir(), "game_list.json")
def get_steam_id(title: str) -> int:
# workarounds for satisfactory
# FIXME: This has to be made smarter.
title = title.replace("Early Access", "").replace("Experimental", "").strip()
# title = title.split(":")[0]
# title = title.split("-")[0]
global __steam_ids_json
if __steam_ids_json is None:
if not os.path.exists(file):
response = requests.get(url)
ids = {}
steam_ids = orjson.loads(response.text)["applist"]["apps"]
for game in steam_ids:
ids[game["name"]] = game["appid"]
__steam_ids_json = ids
with open(file, "w") as f:
f.write(orjson.dumps(ids).decode("utf-8"))
else:
ids = orjson.loads(open(file, "r").read())
__steam_ids_json = ids
else:
ids = __steam_ids_json
__steam_ids_json = load_json()
ids = __steam_ids_json
if title in ids.keys():
steam_name = [title]
else:
steam_name = difflib.get_close_matches(title, ids.keys(), n=1)
steam_name = difflib.get_close_matches(title, ids.keys(), n=1, cutoff=0.5)
if steam_name:
return ids[steam_name[0]]
else:
return 0
def check_time(): # this function check if it's time to update
file = os.path.join(cache_dir(), "game_list.json")
json_table = orjson.loads(open(file, "r").read())
today = date.today()
day = 0 # it controls how many days it's necessary for an update
for i in "ymd":
if i == "d":
day = 7
else:
day = 0
if int(today.strftime("%" + i)) > int(json_table["data"][i]) + day:
return 1
else:
return 0