diff --git a/rare/components/tabs/games/game_info/game_info.py b/rare/components/tabs/games/game_info/game_info.py index 05dc382f..c7f353e8 100644 --- a/rare/components/tabs/games/game_info/game_info.py +++ b/rare/components/tabs/games/game_info/game_info.py @@ -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 diff --git a/rare/models/game.py b/rare/models/game.py index 2bcdccbb..721dd075 100644 --- a/rare/models/game.py +++ b/rare/models/game.py @@ -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() diff --git a/rare/ui/components/tabs/games/game_info/game_info.py b/rare/ui/components/tabs/games/game_info/game_info.py index 3457e942..712f657a 100644 --- a/rare/ui/components/tabs/games/game_info/game_info.py +++ b/rare/ui/components/tabs/games/game_info/game_info.py @@ -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) diff --git a/rare/ui/components/tabs/games/game_info/game_info.ui b/rare/ui/components/tabs/games/game_info/game_info.ui index 89115667..261ba5d0 100644 --- a/rare/ui/components/tabs/games/game_info/game_info.ui +++ b/rare/ui/components/tabs/games/game_info/game_info.ui @@ -232,6 +232,9 @@ error + + true + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse diff --git a/rare/utils/misc.py b/rare/utils/misc.py index 3d0267b4..aa27a36a 100644 --- a/rare/utils/misc.py +++ b/rare/utils/misc.py @@ -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 "{}".format(link, title) diff --git a/rare/utils/steam_grades.py b/rare/utils/steam_grades.py index 06a7a2b8..54e5e599 100644 --- a/rare/utils/steam_grades.py +++ b/rare/utils/steam_grades.py @@ -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