From c063e9a92a00b198dc70a6e491dd220c37b99a8e Mon Sep 17 00:00:00 2001 From: Dummerle Date: Mon, 14 Jun 2021 22:30:57 +0200 Subject: [PATCH] Add browse games function --- rare/components/tab_widget.py | 2 +- rare/components/tabs/shop/__init__.py | 29 ++- rare/components/tabs/shop/browse_games.py | 147 +++++++++++ rare/components/tabs/shop/game_widgets.py | 150 +++++++++++ rare/components/tabs/shop/search_results.py | 2 +- rare/components/tabs/shop/shop_info.py | 17 +- rare/components/tabs/shop/shop_models.py | 5 +- rare/components/tabs/shop/shop_widget.py | 239 +++--------------- rare/ui/components/tabs/store/browse_games.py | 145 +++++++++++ rare/ui/components/tabs/store/browse_games.ui | 225 +++++++++++++++++ .../components/tabs/store/shop_game_info.py | 4 + .../components/tabs/store/shop_game_info.ui | 7 + 12 files changed, 753 insertions(+), 219 deletions(-) create mode 100644 rare/components/tabs/shop/browse_games.py create mode 100644 rare/components/tabs/shop/game_widgets.py create mode 100644 rare/ui/components/tabs/store/browse_games.py create mode 100644 rare/ui/components/tabs/store/browse_games.ui diff --git a/rare/components/tab_widget.py b/rare/components/tab_widget.py index 238e39f1..995868cb 100644 --- a/rare/components/tab_widget.py +++ b/rare/components/tab_widget.py @@ -33,7 +33,7 @@ class TabWidget(QTabWidget): self.addTab(self.downloadTab, "Downloads" + (" (" + str(len(updates)) + ")" if len(updates) != 0 else "")) self.cloud_saves = SyncSaves(core, self) self.addTab(self.cloud_saves, "Cloud Saves") - self.store = Shop() + self.store = Shop(self.core) self.addTab(self.store, self.tr("Store (Beta)")) self.settings = SettingsTab(core, self) diff --git a/rare/components/tabs/shop/__init__.py b/rare/components/tabs/shop/__init__.py index 64269786..64067c9d 100644 --- a/rare/components/tabs/shop/__init__.py +++ b/rare/components/tabs/shop/__init__.py @@ -1,35 +1,54 @@ -from PyQt5.QtWidgets import QStackedWidget +import os + +from PyQt5.QtWidgets import QStackedWidget, QTabWidget +from custom_legendary.core import LegendaryCore from rare.components.tabs.shop.search_results import SearchResults from rare.components.tabs.shop.shop_info import ShopGameInfo from rare.components.tabs.shop.shop_widget import ShopWidget +from rare.components.tabs.shop.browse_games import BrowseGames class Shop(QStackedWidget): init = False - def __init__(self): + def __init__(self, core: LegendaryCore): super(Shop, self).__init__() + self.core = core + if p := os.getenv("XDG_CACHE_HOME"): + self.path = p + else: + self.path = os.path.expanduser("~/.cache/rare/cache/") + if not os.path.exists(self.path): + os.makedirs(self.path) - self.shop = ShopWidget() - self.addWidget(self.shop) + self.shop = ShopWidget(self.path) + self.browse_games = BrowseGames(self.path) + + self.store_tabs = QTabWidget() + self.store_tabs.addTab(self.shop, self.tr("Games")) + self.store_tabs.addTab(self.browse_games, self.tr("Browse")) + + self.addWidget(self.store_tabs) self.search_results = SearchResults() self.addWidget(self.search_results) self.search_results.show_info.connect(self.show_game_info) - self.info = ShopGameInfo() + self.info = ShopGameInfo([i.asset_info.namespace for i in self.core.get_game_list(True)]) self.addWidget(self.info) self.info.back_button.clicked.connect(lambda: self.setCurrentIndex(0)) self.search_results.back_button.clicked.connect(lambda: self.setCurrentIndex(0)) self.shop.show_info.connect(self.show_info) self.shop.show_game.connect(self.show_game_info) + self.browse_games.show_game.connect(self.show_game_info) def load(self): if not self.init: self.init = True self.shop.load() + self.browse_games.prepare_request() def show_game_info(self, data): self.info.update_game(data) diff --git a/rare/components/tabs/shop/browse_games.py b/rare/components/tabs/shop/browse_games.py new file mode 100644 index 00000000..e862f1bf --- /dev/null +++ b/rare/components/tabs/shop/browse_games.py @@ -0,0 +1,147 @@ +import datetime +import json +import logging +import random + +from PyQt5.QtCore import QUrl, pyqtSignal, QJsonParseError, QJsonDocument +from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager +from PyQt5.QtWidgets import QWidget + +from rare.components.tabs.shop.game_widgets import GameWidget +from rare.ui.components.tabs.store.browse_games import Ui_browse_games +from rare.utils.extra_widgets import FlowLayout, WaitingSpinner +from rare.utils.utils import get_lang + +logger = logging.getLogger("BrowseGames") + + +class BrowseGames(QWidget, Ui_browse_games): + show_game = pyqtSignal(dict) + price = "" + platform = (False, False) + + def __init__(self, path): + super(BrowseGames, self).__init__() + self.setupUi(self) + self.path = path + self.games_widget = QWidget() + self.games_widget.setLayout(FlowLayout()) + self.games.setWidget(self.games_widget) + self.manager = QNetworkAccessManager() + + self.stack.addWidget(WaitingSpinner()) + + self.clear_price.toggled.connect(lambda: self.prepare_request("") if self.clear_price.isChecked() else None) + self.free_button.toggled.connect(lambda: self.prepare_request("free") if self.free_button.isChecked() else None) + self.under10.toggled.connect( + lambda: self.prepare_request("[0, 1000)") if self.under10.isChecked() else None) + self.under20.toggled.connect( + lambda: self.prepare_request("[0, 2000)") if self.under20.isChecked() else None) + self.under30.toggled.connect( + lambda: self.prepare_request("[0, 3000)") if self.under30.isChecked() else None) + self.above.toggled.connect(lambda: self.prepare_request("[1499,]") if self.above.isChecked() else None) + self.on_discount.toggled.connect(lambda: self.prepare_request("sale") if self.on_discount.isChecked() else None) + + self.win_cb.toggled.connect( + lambda: self.prepare_request(platform=(self.win_cb.isChecked(), self.mac_cb.isChecked()))) + self.mac_cb.toggled.connect( + lambda: self.prepare_request(platform=(self.win_cb.isChecked(), self.mac_cb.isChecked()))) + + def prepare_request(self, price: str = None, platform: tuple = None): + if price is not None: + self.price = price + if platform is not None: + self.platform = platform + + locale = get_lang() + self.stack.setCurrentIndex(2) + date = f"[,{datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%dT%X')}.{str(random.randint(0, 999)).zfill(3)}Z]" + payload = {"variables": {"category": "games/edition/base|bundles/games|editors|software/edition/base", + "count": 30, "country": locale.upper(), "keywords": "", "locale": locale, + "sortDir": "DESC", "allowCountries": locale.upper(), "start": 0, "tag": "", + "withMapping": True, "withPrice": True, + "releaseDate": date, "effectiveDate": date + }, + "query": game_query} + + if self.price == "free": + payload["variables"]["freeGame"] = True + elif self.price.startswith(""): + payload["variables"]["priceRange"] = price.replace("", "") + elif self.price == "sale": + payload["variables"]["onSale"] = True + + if self.platform[0]: + payload["variables"]["tag"] = "9547" + if self.platform[1]: + payload["variables"]["tag"] += "|10719" + elif self.platform[1]: + payload["variables"]["tag"] = "10719" + + request = QNetworkRequest(QUrl("https://www.epicgames.com/graphql")) + request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") + self.game_request = self.manager.post(request, json.dumps(payload).encode("utf-8")) + self.game_request.finished.connect(self.show_games) + + def show_games(self): + if self.game_request: + if self.game_request.error() == QNetworkReply.NoError: + error = QJsonParseError() + json_data = QJsonDocument.fromJson(self.game_request.readAll().data(), error) + + if error.error == error.NoError: + try: + games = json.loads(json_data.toJson().data().decode())["data"]["Catalog"]["searchStore"][ + "elements"] + except TypeError as e: + logger.error("Type Error: " + str(e)) + self.stack.setCurrentIndex(1) + else: + QWidget().setLayout(self.games_widget.layout()) + self.games_widget.setLayout(FlowLayout()) + + for game in games: + w = GameWidget(self.path, game, 275) + self.games_widget.layout().addWidget(w) + w.show_info.connect(self.show_game.emit) + self.stack.setCurrentIndex(0) + return + + else: + logger.info(self.slug, error.errorString()) + else: + print(self.game_request.errorString()) + self.stack.setCurrentIndex(1) + + +game_query = "query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, " \ + "$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, " \ + "$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean " \ + "= false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, " \ + "$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n " \ + "category: $category\n count: $count\n country: $country\n keywords: $keywords\n " \ + "locale: $locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n " \ + "sortDir: $sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n " \ + "priceRange: $priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: " \ + "$effectiveDate\n ) {\n elements {\n title\n id\n namespace\n " \ + "description\n effectiveDate\n keyImages {\n type\n url\n }\n " \ + " currentPrice\n seller {\n id\n name\n }\n productSlug\n " \ + " urlSlug\n url\n tags {\n id\n }\n items {\n id\n " \ + " namespace\n }\n customAttributes {\n key\n value\n }\n " \ + "categories {\n path\n }\n catalogNs @include(if: $withMapping) {\n " \ + "mappings(pageType: \"productHome\") {\n pageSlug\n pageType\n }\n " \ + "}\n offerMappings @include(if: $withMapping) {\n pageSlug\n pageType\n " \ + "}\n price(country: $country) @include(if: $withPrice) {\n totalPrice {\n " \ + "discountPrice\n originalPrice\n voucherDiscount\n discount\n " \ + " currencyCode\n currencyInfo {\n decimals\n }\n fmtPrice(" \ + "locale: $locale) {\n originalPrice\n discountPrice\n " \ + "intermediatePrice\n }\n }\n lineOffers {\n appliedRules {\n " \ + " id\n endDate\n discountSetting {\n discountType\n " \ + " }\n }\n }\n }\n promotions(category: $category) @include(if: " \ + "$withPromotions) {\n promotionalOffers {\n promotionalOffers {\n " \ + "startDate\n endDate\n discountSetting {\n discountType\n " \ + " discountPercentage\n }\n }\n }\n " \ + "upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n " \ + "endDate\n discountSetting {\n discountType\n " \ + "discountPercentage\n }\n }\n }\n }\n }\n paging {\n " \ + " count\n total\n }\n }\n }\n}\n " diff --git a/rare/components/tabs/shop/game_widgets.py b/rare/components/tabs/shop/game_widgets.py new file mode 100644 index 00000000..865d0b4d --- /dev/null +++ b/rare/components/tabs/shop/game_widgets.py @@ -0,0 +1,150 @@ +import json +import logging +import os + +from PyQt5 import QtGui +from PyQt5.QtCore import pyqtSignal, QSettings, Qt, QUrl, QJsonParseError, QJsonDocument +from PyQt5.QtGui import QPixmap +from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel + +from rare.utils.extra_widgets import ImageLabel +from rare.utils.utils import get_lang + + +class GameWidget(QWidget): + show_info = pyqtSignal(dict) + + def __init__(self, path, json_info=None, width=300): + super(GameWidget, self).__init__() + self.manager = QNetworkAccessManager() + self.width = width + if json_info: + self.init_ui(json_info, path) + self.path = path + + def init_ui(self, json_info, path): + self.path = path + self.layout = QVBoxLayout() + self.image = ImageLabel() + self.json_info = json_info + self.slug = json_info["productSlug"] + + self.title = json_info["title"] + for img in json_info["keyImages"]: + if img["type"] in ["DieselStoreFrontWide", "OfferImageWide", "VaultClosed"]: + if img["type"] == "VaultClosed" and self.title != "Mystery Game": + continue + self.image.update_image(img["url"], json_info["title"], (self.width, int(self.width * 9 / 16))) + break + else: + print(json_info["keyImages"]) + + save = QSettings().value("cache_images", True, bool) + if os.path.exists(p := os.path.join(self.path, f"{json_info['title']}_wide.png")) and save: + self.image.setPixmap(QPixmap(p) + .scaled(self.width, int(self.width * 9 / 16), transformMode=Qt.SmoothTransformation)) + else: + for img in json_info["keyImages"]: + if img["type"] in ["DieselStoreFrontWide", "VaultClosed"]: + if img["type"] == "VaultClosed" and self.title != "Mystery Game": + continue + self.image_request = self.manager.get(QNetworkRequest(QUrl(img["url"]))) + self.image_request.finished.connect(lambda: self.image_ready(save)) + break + else: + # No image found + logging.error(f"No image found for {self.title}") + + self.layout.addWidget(self.image) + + self.title_label = QLabel(json_info["title"]) + self.title_label.setWordWrap(True) + self.layout.addWidget(self.title_label) + self.setLayout(self.layout) + + def image_ready(self, save: bool): + if self.image_request: + if self.image_request.error() == QNetworkReply.NoError: + data = self.image_request.readAll().data() + if save: + with open(os.path.join(self.path, f"{self.title}_wide.png"), "wb") as file: + file.write(data) + file.close() + pixmap = QPixmap() + pixmap.loadFromData(data) + self.image.setPixmap(pixmap.scaled(self.width, int(self.width * 9 / 16), + transformMode=Qt.SmoothTransformation)) + + def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: + self.show_info.emit(self.json_info) + + @classmethod + def from_request(cls, name, path): + c = cls(path) + c.manager = QNetworkAccessManager() + c.request = c.manager.get(QNetworkRequest()) + + locale = get_lang() + payload = json.dumps({ + "query": query, + "variables": {"category": "games/edition/base|bundles/games|editors|software/edition/base", "count": 1, + "country": "DE", "keywords": name, "locale": locale, "sortDir": "DESC", + "allowCountries": locale.upper(), + "start": 0, "tag": "", "withMapping": False, "withPrice": True} + }).encode() + request = QNetworkRequest(QUrl("https://www.epicgames.com/graphql")) + request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") + c.search_request = c.manager.post(request, payload) + c.search_request.finished.connect(lambda: c.handle_response(path)) + return c + + def handle_response(self, path): + if self.search_request: + if self.search_request.error() == QNetworkReply.NoError: + error = QJsonParseError() + json_data = QJsonDocument.fromJson(self.search_request.readAll().data(), error) + if QJsonParseError.NoError == error.error: + data = json.loads(json_data.toJson().data().decode())["data"]["Catalog"]["searchStore"][ + "elements"][0] + self.init_ui(data, path) + else: + logging.error(error.errorString()) + return + + else: + return + else: + return + + +query = "query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, " \ + "$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, " \ + "$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean = " \ + "false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, " \ + "$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n " \ + "category: $category\n count: $count\n country: $country\n keywords: $keywords\n locale: " \ + "$locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n sortDir: " \ + "$sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n priceRange: " \ + "$priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: $effectiveDate\n ) {" \ + "\n elements {\n title\n id\n namespace\n description\n " \ + "effectiveDate\n keyImages {\n type\n url\n }\n currentPrice\n " \ + "seller {\n id\n name\n }\n productSlug\n urlSlug\n url\n " \ + " tags {\n id\n }\n items {\n id\n namespace\n }\n " \ + "customAttributes {\n key\n value\n }\n categories {\n path\n " \ + "}\n catalogNs @include(if: $withMapping) {\n mappings(pageType: \"productHome\") {\n " \ + " pageSlug\n pageType\n }\n }\n offerMappings @include(if: $withMapping) " \ + "{\n pageSlug\n pageType\n }\n price(country: $country) @include(if: " \ + "$withPrice) {\n totalPrice {\n discountPrice\n originalPrice\n " \ + "voucherDiscount\n discount\n currencyCode\n currencyInfo {\n " \ + "decimals\n }\n fmtPrice(locale: $locale) {\n originalPrice\n " \ + "discountPrice\n intermediatePrice\n }\n }\n lineOffers {\n " \ + " appliedRules {\n id\n endDate\n discountSetting {\n " \ + "discountType\n }\n }\n }\n }\n promotions(category: " \ + "$category) @include(if: $withPromotions) {\n promotionalOffers {\n promotionalOffers {\n " \ + " startDate\n endDate\n discountSetting {\n " \ + "discountType\n discountPercentage\n }\n }\n }\n " \ + "upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n " \ + "endDate\n discountSetting {\n discountType\n discountPercentage\n " \ + " }\n }\n }\n }\n }\n paging {\n count\n " \ + "total\n }\n }\n }\n}\n " diff --git a/rare/components/tabs/shop/search_results.py b/rare/components/tabs/shop/search_results.py index 87ea7f85..613de082 100644 --- a/rare/components/tabs/shop/search_results.py +++ b/rare/components/tabs/shop/search_results.py @@ -67,7 +67,7 @@ class _SearchResultItem(QGroupBox): price = result['price']['totalPrice']['fmtPrice']['originalPrice'] discount_price = result['price']['totalPrice']['fmtPrice']['discountPrice'] price_layout = QHBoxLayout() - price_label = QLabel(price) + price_label = QLabel(price if price != "0" else self.tr("Free")) price_layout.addWidget(price_label) if price != discount_price: diff --git a/rare/components/tabs/shop/shop_info.py b/rare/components/tabs/shop/shop_info.py index 140571b3..9c142d9f 100644 --- a/rare/components/tabs/shop/shop_info.py +++ b/rare/components/tabs/shop/shop_info.py @@ -20,9 +20,10 @@ class ShopGameInfo(QWidget, Ui_shop_info): data: dict # TODO Design - def __init__(self): + def __init__(self, installed_titles: list): super(ShopGameInfo, self).__init__() self.setupUi(self) + self.installed = installed_titles self.open_store_button.clicked.connect(self.button_clicked) self.image = ImageLabel() self.image_stack.addWidget(self.image) @@ -38,6 +39,12 @@ class ShopGameInfo(QWidget, Ui_shop_info): slug = slug.replace("/home", "") self.slug = slug self.title.setText(data["title"]) + if data["namespace"] in self.installed: + self.open_store_button.setText(self.tr("Show Game on Epic Page")) + self.owned_label.setVisible(True) + else: + self.open_store_button.setText(self.tr("Buy Game in Epic Games Store")) + self.owned_label.setVisible(False) self.dev.setText(self.tr("Loading")) self.image.setPixmap(QPixmap()) @@ -73,8 +80,10 @@ class ShopGameInfo(QWidget, Ui_shop_info): return self.game = ShopGame.from_json(game, self.data) self.title.setText(self.game.title) - - self.price.setText(self.game.price) + if self.game.price != "0": + self.price.setText(self.game.price) + else: + self.price.setText(self.tr("Free")) if self.game.price != self.game.discount_price: self.discount_price.setText(self.game.discount_price) self.discount_price.setVisible(True) @@ -90,7 +99,7 @@ class ShopGameInfo(QWidget, Ui_shop_info): self.req_group_box.layout().addWidget(min_label, 0, 1) self.req_group_box.layout().addWidget(rec_label, 0, 2) - for i, (key, value) in enumerate(self.game.reqs["Windows"].items()): + for i, (key, value) in enumerate(self.game.reqs.get("Windows", {}).items()): self.req_group_box.layout().addWidget(QLabel(key), i + 1, 0) min_label = QLabel(value[0]) min_label.setWordWrap(True) diff --git a/rare/components/tabs/shop/shop_models.py b/rare/components/tabs/shop/shop_models.py index d029c27d..d5eaf664 100644 --- a/rare/components/tabs/shop/shop_models.py +++ b/rare/components/tabs/shop/shop_models.py @@ -63,7 +63,10 @@ class ShopGame: tmp.available_voice_langs = api_data["data"]["requirements"].get("languages", "Failed") tmp.reqs = {} for i, system in enumerate(api_data["data"]["requirements"]["systems"]): - tmp.reqs[system["systemType"]] = {} + try: + tmp.reqs[system["systemType"]] = {} + except KeyError: + continue for req in system["details"]: try: tmp.reqs[system["systemType"]][req["title"]] = (req["minimum"], req["recommended"]) diff --git a/rare/components/tabs/shop/shop_widget.py b/rare/components/tabs/shop/shop_widget.py index e0628b2d..80e84ce6 100644 --- a/rare/components/tabs/shop/shop_widget.py +++ b/rare/components/tabs/shop/shop_widget.py @@ -1,18 +1,16 @@ import datetime import json import logging -import os from json import JSONDecodeError -from PyQt5 import QtGui from PyQt5.QtCore import Qt, pyqtSignal, QUrl, QJsonDocument, QJsonParseError, \ - QStringListModel, QSettings -from PyQt5.QtGui import QPixmap + QStringListModel from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QCompleter, QGroupBox, QHBoxLayout, QScrollArea +from PyQt5.QtWidgets import QWidget, QCompleter, QGroupBox, QHBoxLayout, QScrollArea +from rare.components.tabs.shop.game_widgets import GameWidget from rare.ui.components.tabs.store.store import Ui_ShopWidget -from rare.utils.extra_widgets import WaitingSpinner, ImageLabel, FlowLayout +from rare.utils.extra_widgets import WaitingSpinner, FlowLayout from rare.utils.utils import get_lang logger = logging.getLogger("Shop") @@ -26,10 +24,11 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): active_search_request = False next_search = "" - def __init__(self): + def __init__(self, path): super(ShopWidget, self).__init__() self.setWidgetResizable(True) self.setupUi(self) + self.path = path self.manager = QNetworkAccessManager() self.free_games_widget = QWidget() self.free_games_widget.setLayout(FlowLayout()) @@ -49,54 +48,13 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): self.data = [] self.games_groupbox.setLayout(FlowLayout()) + self.games_groupbox.setVisible(False) def load(self): - if p := os.getenv("XDG_CACHE_HOME"): - self.path = p - else: - self.path = os.path.expanduser("~/.cache/rare/cache/") - if not os.path.exists(self.path): - os.makedirs(self.path) url = "https://store-site-backend-static.ak.epicgames.com/freeGamesPromotions" self.free_game_request = self.manager.get(QNetworkRequest(QUrl(url))) self.free_game_request.finished.connect(self.add_free_games) - game_list = ["Satisfactory", "Among Us", "Star Wars Jedi Fallen Order", "Watch Dogs", "Subnautica Below Zero"] - # TODO read from api - locale = get_lang() - payload = json.dumps( - {"variables": {"category": "games/edition/base|bundles/games|editors|software/edition/base", - "count": 15, "country": locale.upper(), "keywords": "", "locale": locale, - "sortDir": "DESC", "allowCountries": locale.upper(), "start": 0, "tag": "", - "withMapping": True, "withPrice": True - }, - "query": game_query}).encode() - - request = QNetworkRequest(QUrl("https://www.epicgames.com/graphql")) - request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") - self.game_request = self.manager.post(request, payload) - self.game_request.finished.connect(self.show_recommended_games) - - def show_recommended_games(self): - print("lol") - if self.game_request: - if self.game_request.error() == QNetworkReply.NoError: - try: - games = json.loads(self.game_request.readAll().data().decode())["data"]["Catalog"]["searchStore"][ - "elements"] - print(games) - except JSONDecodeError: - return - else: - return - else: - return - - for game in games: - w = GameWidget(self.path, game) - self.games_groupbox.layout().addWidget(w) - w.show_info.connect(self.show_game.emit) - def add_free_games(self): if self.free_game_request: if self.free_game_request.error() == QNetworkReply.NoError: @@ -190,27 +148,31 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): def show_search_results(self, show_direct=False): if self.search_request: - if self.search_request.error() == QNetworkReply.NoError: - error = QJsonParseError() - json_data = QJsonDocument.fromJson(self.search_request.readAll().data(), error) - if QJsonParseError.NoError == error.error: - data = json.loads(json_data.toJson().data().decode())["data"]["Catalog"]["searchStore"]["elements"] - self.data = data - if show_direct: - self.show_search_result(True) - return - titles = [i.get("title") for i in data] - model = QStringListModel() - model.setStringList(titles) - self.completer.setModel(model) - # self.completer.popup() - if self.search_request: - self.search_request.deleteLater() - else: - logging.error(error.errorString()) - # response = .decode(encoding="utf-8") - # print(response) - # results = json.loads(response) + try: + if self.search_request.error() == QNetworkReply.NoError: + error = QJsonParseError() + json_data = QJsonDocument.fromJson(self.search_request.readAll().data(), error) + if QJsonParseError.NoError == error.error: + data = json.loads(json_data.toJson().data().decode())["data"]["Catalog"]["searchStore"][ + "elements"] + self.data = data + if show_direct: + self.show_search_result(True) + return + titles = [i.get("title") for i in data] + model = QStringListModel() + model.setStringList(titles) + self.completer.setModel(model) + # self.completer.popup() + if self.search_request: + self.search_request.deleteLater() + else: + logging.error(error.errorString()) + # response = .decode(encoding="utf-8") + # print(response) + # results = json.loads(response) + except RuntimeError: + return self.search_games(self.next_search) @@ -227,111 +189,6 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): self.show_game.emit(result) -class GameWidget(QWidget): - show_info = pyqtSignal(dict) - - def __init__(self, path, json_info=None): - super(GameWidget, self).__init__() - self.manager = QNetworkAccessManager() - if json_info: - self.init_ui(json_info, path) - self.path = path - - def init_ui(self, json_info, path): - self.path = path - self.layout = QVBoxLayout() - self.image = ImageLabel() - self.json_info = json_info - self.slug = json_info["productSlug"] - self.width = 300 - self.title = json_info["title"] - for img in json_info["keyImages"]: - if img["type"] in ["DieselStoreFrontWide", "VaultClosed"]: - if img["type"] == "VaultClosed" and self.title != "Mystery Game": - continue - self.image.update_image(img["url"], json_info["title"], (self.width, int(self.width * 9 / 16))) - break - else: - print("No image found") - - save = QSettings().value("cache_images", True, bool) - if os.path.exists(p := os.path.join(self.path, f"{json_info['title']}_wide.png")) and save: - self.image.setPixmap(QPixmap(p) - .scaled(self.width, int(self.width * 9 / 16), transformMode=Qt.SmoothTransformation)) - else: - for img in json_info["keyImages"]: - if img["type"] in ["DieselStoreFrontWide", "VaultClosed"]: - if img["type"] == "VaultClosed" and self.title != "Mystery Game": - continue - self.image_request = self.manager.get(QNetworkRequest(QUrl(img["url"]))) - self.image_request.finished.connect(lambda: self.image_ready(save)) - break - else: - # No image found - logger.error(f"No image found for {self.title}") - - self.layout.addWidget(self.image) - - self.title_label = QLabel(json_info["title"]) - self.title_label.setWordWrap(True) - self.layout.addWidget(self.title_label) - self.setLayout(self.layout) - - def image_ready(self, save: bool): - if self.image_request: - if self.image_request.error() == QNetworkReply.NoError: - data = self.image_request.readAll().data() - if save: - with open(os.path.join(self.path, f"{self.title}_wide.png"), "wb") as file: - file.write(data) - file.close() - pixmap = QPixmap() - pixmap.loadFromData(data) - self.image.setPixmap(pixmap.scaled(self.width, int(self.width * 9 / 16), - transformMode=Qt.SmoothTransformation)) - - def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: - self.show_info.emit(self.json_info) - - @classmethod - def from_request(cls, name, path): - c = cls(path) - c.manager = QNetworkAccessManager() - c.request = c.manager.get(QNetworkRequest()) - - locale = get_lang() - payload = json.dumps({ - "query": query, - "variables": {"category": "games/edition/base|bundles/games|editors|software/edition/base", "count": 1, - "country": "DE", "keywords": name, "locale": locale, "sortDir": "DESC", - "allowCountries": locale.upper(), - "start": 0, "tag": "", "withMapping": False, "withPrice": True} - }).encode() - request = QNetworkRequest(QUrl("https://www.epicgames.com/graphql")) - request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") - c.search_request = c.manager.post(request, payload) - c.search_request.finished.connect(lambda: c.handle_response(path)) - return c - - def handle_response(self, path): - if self.search_request: - if self.search_request.error() == QNetworkReply.NoError: - error = QJsonParseError() - json_data = QJsonDocument.fromJson(self.search_request.readAll().data(), error) - if QJsonParseError.NoError == error.error: - data = json.loads(json_data.toJson().data().decode())["data"]["Catalog"]["searchStore"][ - "elements"][0] - self.init_ui(data, path) - else: - logging.error(error.errorString()) - return - - else: - return - else: - return - - query = "query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, " \ "$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, " \ "$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean = " \ @@ -362,35 +219,3 @@ query = "query searchStoreQuery($allowCountries: String, $category: String, $cou "endDate\n discountSetting {\n discountType\n discountPercentage\n " \ " }\n }\n }\n }\n }\n paging {\n count\n " \ "total\n }\n }\n }\n}\n " - -game_query = "query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, " \ - "$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, " \ - "$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean " \ - "= false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, " \ - "$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n " \ - "category: $category\n count: $count\n country: $country\n keywords: $keywords\n " \ - "locale: $locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n " \ - "sortDir: $sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n " \ - "priceRange: $priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: " \ - "$effectiveDate\n ) {\n elements {\n title\n id\n namespace\n " \ - "description\n effectiveDate\n keyImages {\n type\n url\n }\n " \ - " currentPrice\n seller {\n id\n name\n }\n productSlug\n " \ - " urlSlug\n url\n tags {\n id\n }\n items {\n id\n " \ - " namespace\n }\n customAttributes {\n key\n value\n }\n " \ - "categories {\n path\n }\n catalogNs @include(if: $withMapping) {\n " \ - "mappings(pageType: \"productHome\") {\n pageSlug\n pageType\n }\n " \ - "}\n offerMappings @include(if: $withMapping) {\n pageSlug\n pageType\n " \ - "}\n price(country: $country) @include(if: $withPrice) {\n totalPrice {\n " \ - "discountPrice\n originalPrice\n voucherDiscount\n discount\n " \ - " currencyCode\n currencyInfo {\n decimals\n }\n fmtPrice(" \ - "locale: $locale) {\n originalPrice\n discountPrice\n " \ - "intermediatePrice\n }\n }\n lineOffers {\n appliedRules {\n " \ - " id\n endDate\n discountSetting {\n discountType\n " \ - " }\n }\n }\n }\n promotions(category: $category) @include(if: " \ - "$withPromotions) {\n promotionalOffers {\n promotionalOffers {\n " \ - "startDate\n endDate\n discountSetting {\n discountType\n " \ - " discountPercentage\n }\n }\n }\n " \ - "upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n " \ - "endDate\n discountSetting {\n discountType\n " \ - "discountPercentage\n }\n }\n }\n }\n }\n paging {\n " \ - " count\n total\n }\n }\n }\n}\n " diff --git a/rare/ui/components/tabs/store/browse_games.py b/rare/ui/components/tabs/store/browse_games.py new file mode 100644 index 00000000..fba96450 --- /dev/null +++ b/rare/ui/components/tabs/store/browse_games.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'browse_games.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_browse_games(object): + def setupUi(self, browse_games): + browse_games.setObjectName("browse_games") + browse_games.resize(706, 541) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(browse_games) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.games_gb = QtWidgets.QGroupBox(browse_games) + self.games_gb.setObjectName("games_gb") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.games_gb) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.stack = QtWidgets.QStackedWidget(self.games_gb) + self.stack.setObjectName("stack") + self.games_page = QtWidgets.QWidget() + self.games_page.setObjectName("games_page") + self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.games_page) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.games = QtWidgets.QScrollArea(self.games_page) + self.games.setWidgetResizable(True) + self.games.setObjectName("games") + self.scrollAreaWidgetContents = QtWidgets.QWidget() + self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 394, 306)) + self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") + self.games.setWidget(self.scrollAreaWidgetContents) + self.verticalLayout_5.addWidget(self.games) + self.stack.addWidget(self.games_page) + self.error = QtWidgets.QWidget() + self.error.setObjectName("error") + self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.error) + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.error_label = QtWidgets.QLabel(self.error) + self.error_label.setObjectName("error_label") + self.verticalLayout_6.addWidget(self.error_label) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_6.addItem(spacerItem) + self.stack.addWidget(self.error) + self.verticalLayout_4.addWidget(self.stack) + self.horizontalLayout_2.addWidget(self.games_gb) + self.filter_gb = QtWidgets.QGroupBox(browse_games) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.filter_gb.sizePolicy().hasHeightForWidth()) + self.filter_gb.setSizePolicy(sizePolicy) + self.filter_gb.setMaximumSize(QtCore.QSize(200, 16777215)) + self.filter_gb.setFlat(False) + self.filter_gb.setCheckable(False) + self.filter_gb.setObjectName("filter_gb") + self.verticalLayout = QtWidgets.QVBoxLayout(self.filter_gb) + self.verticalLayout.setObjectName("verticalLayout") + self.filter_scroll = QtWidgets.QScrollArea(self.filter_gb) + self.filter_scroll.setWidgetResizable(True) + self.filter_scroll.setObjectName("filter_scroll") + self.scroll_widget = QtWidgets.QWidget() + self.scroll_widget.setGeometry(QtCore.QRect(0, 0, 174, 479)) + self.scroll_widget.setObjectName("scroll_widget") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.scroll_widget) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.price_gb = QtWidgets.QGroupBox(self.scroll_widget) + self.price_gb.setObjectName("price_gb") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.price_gb) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.clear_price = QtWidgets.QRadioButton(self.price_gb) + self.clear_price.setChecked(True) + self.clear_price.setObjectName("clear_price") + self.verticalLayout_2.addWidget(self.clear_price) + self.free_button = QtWidgets.QRadioButton(self.price_gb) + self.free_button.setObjectName("free_button") + self.verticalLayout_2.addWidget(self.free_button) + self.under10 = QtWidgets.QRadioButton(self.price_gb) + self.under10.setObjectName("under10") + self.verticalLayout_2.addWidget(self.under10) + self.under20 = QtWidgets.QRadioButton(self.price_gb) + self.under20.setObjectName("under20") + self.verticalLayout_2.addWidget(self.under20) + self.under30 = QtWidgets.QRadioButton(self.price_gb) + self.under30.setObjectName("under30") + self.verticalLayout_2.addWidget(self.under30) + self.above = QtWidgets.QRadioButton(self.price_gb) + self.above.setObjectName("above") + self.verticalLayout_2.addWidget(self.above) + self.on_discount = QtWidgets.QRadioButton(self.price_gb) + self.on_discount.setObjectName("on_discount") + self.verticalLayout_2.addWidget(self.on_discount) + self.verticalLayout_3.addWidget(self.price_gb) + self.platform_gb = QtWidgets.QGroupBox(self.scroll_widget) + self.platform_gb.setObjectName("platform_gb") + self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.platform_gb) + self.verticalLayout_7.setObjectName("verticalLayout_7") + self.win_cb = QtWidgets.QCheckBox(self.platform_gb) + self.win_cb.setObjectName("win_cb") + self.verticalLayout_7.addWidget(self.win_cb) + self.mac_cb = QtWidgets.QCheckBox(self.platform_gb) + self.mac_cb.setObjectName("mac_cb") + self.verticalLayout_7.addWidget(self.mac_cb) + self.verticalLayout_3.addWidget(self.platform_gb) + spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_3.addItem(spacerItem1) + self.filter_scroll.setWidget(self.scroll_widget) + self.verticalLayout.addWidget(self.filter_scroll) + self.horizontalLayout_2.addWidget(self.filter_gb) + + self.retranslateUi(browse_games) + self.stack.setCurrentIndex(1) + QtCore.QMetaObject.connectSlotsByName(browse_games) + + def retranslateUi(self, browse_games): + _translate = QtCore.QCoreApplication.translate + browse_games.setWindowTitle(_translate("browse_games", "Form")) + self.games_gb.setTitle(_translate("browse_games", "Games")) + self.error_label.setText(_translate("browse_games", "An error occured")) + self.filter_gb.setTitle(_translate("browse_games", "Filter")) + self.price_gb.setTitle(_translate("browse_games", "Price")) + self.clear_price.setText(_translate("browse_games", "Clear price filter")) + self.free_button.setText(_translate("browse_games", "Free")) + self.under10.setText(_translate("browse_games", "Under 10")) + self.under20.setText(_translate("browse_games", "Under 20")) + self.under30.setText(_translate("browse_games", "Under 30")) + self.above.setText(_translate("browse_games", "14.99 and above")) + self.on_discount.setText(_translate("browse_games", "Discount")) + self.platform_gb.setTitle(_translate("browse_games", "Platform")) + self.win_cb.setText(_translate("browse_games", "Windows")) + self.mac_cb.setText(_translate("browse_games", "Mac")) + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + browse_games = QtWidgets.QWidget() + ui = Ui_browse_games() + ui.setupUi(browse_games) + browse_games.show() + sys.exit(app.exec_()) diff --git a/rare/ui/components/tabs/store/browse_games.ui b/rare/ui/components/tabs/store/browse_games.ui new file mode 100644 index 00000000..8845f141 --- /dev/null +++ b/rare/ui/components/tabs/store/browse_games.ui @@ -0,0 +1,225 @@ + + + browse_games + + + + 0 + 0 + 706 + 541 + + + + Form + + + + + + Games + + + + + + 1 + + + + + + + true + + + + + 0 + 0 + 394 + 306 + + + + + + + + + + + + + An error occured + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + Filter + + + false + + + false + + + + + + true + + + + + 0 + 0 + 174 + 479 + + + + + + + Price + + + + + + Clear price filter + + + true + + + + + + + Free + + + + + + + Under 10 + + + + + + + Under 20 + + + + + + + Under 30 + + + + + + + 14.99 and above + + + + + + + Discount + + + + + + + + + + Platform + + + + + + Windows + + + + + + + Mac + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + diff --git a/rare/ui/components/tabs/store/shop_game_info.py b/rare/ui/components/tabs/store/shop_game_info.py index f259a955..ba095bc8 100644 --- a/rare/ui/components/tabs/store/shop_game_info.py +++ b/rare/ui/components/tabs/store/shop_game_info.py @@ -35,6 +35,9 @@ class Ui_shop_info(object): self.dev.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) self.dev.setObjectName("dev") self.verticalLayout_2.addWidget(self.dev) + self.owned_label = QtWidgets.QLabel(shop_info) + self.owned_label.setObjectName("owned_label") + self.verticalLayout_2.addWidget(self.owned_label) self.price = QtWidgets.QLabel(shop_info) self.price.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) self.price.setObjectName("price") @@ -69,6 +72,7 @@ class Ui_shop_info(object): self.back_button.setText(_translate("shop_info", "Back")) self.title.setText(_translate("shop_info", "Error")) self.dev.setText(_translate("shop_info", "TextLabel")) + self.owned_label.setText(_translate("shop_info", "You already own this game")) self.price.setText(_translate("shop_info", "TextLabel")) self.discount_price.setText(_translate("shop_info", "TextLabel")) self.open_store_button.setText(_translate("shop_info", "Buy Game in Epic Games Store")) diff --git a/rare/ui/components/tabs/store/shop_game_info.ui b/rare/ui/components/tabs/store/shop_game_info.ui index 17ebd5a8..25e500c1 100644 --- a/rare/ui/components/tabs/store/shop_game_info.ui +++ b/rare/ui/components/tabs/store/shop_game_info.ui @@ -52,6 +52,13 @@ + + + + You already own this game + + +