1
0
Fork 0
mirror of synced 2024-06-21 12:00:25 +12:00

Add browse games function

This commit is contained in:
Dummerle 2021-06-14 22:30:57 +02:00
parent 0f6a4c5ae6
commit c063e9a92a
12 changed files with 753 additions and 219 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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("<price>[0, 1000)") if self.under10.isChecked() else None)
self.under20.toggled.connect(
lambda: self.prepare_request("<price>[0, 2000)") if self.under20.isChecked() else None)
self.under30.toggled.connect(
lambda: self.prepare_request("<price>[0, 3000)") if self.under30.isChecked() else None)
self.above.toggled.connect(lambda: self.prepare_request("<price>[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("<price>"):
payload["variables"]["priceRange"] = price.replace("<price>", "")
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 "

View file

@ -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 "

View file

@ -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:

View file

@ -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)

View file

@ -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"])

View file

@ -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 "

View file

@ -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_())

View file

@ -0,0 +1,225 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>browse_games</class>
<widget class="QWidget" name="browse_games">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>706</width>
<height>541</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QGroupBox" name="games_gb">
<property name="title">
<string>Games</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QStackedWidget" name="stack">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="games_page">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QScrollArea" name="games">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>394</width>
<height>306</height>
</rect>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="error">
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QLabel" name="error_label">
<property name="text">
<string>An error occured</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="filter_gb">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="title">
<string>Filter</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QScrollArea" name="filter_scroll">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scroll_widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>174</width>
<height>479</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="price_gb">
<property name="title">
<string>Price</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="clear_price">
<property name="text">
<string>Clear price filter</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="free_button">
<property name="text">
<string>Free</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="under10">
<property name="text">
<string>Under 10</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="under20">
<property name="text">
<string>Under 20</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="under30">
<property name="text">
<string>Under 30</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="above">
<property name="text">
<string>14.99 and above</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="on_discount">
<property name="text">
<string>Discount</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="platform_gb">
<property name="title">
<string>Platform</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QCheckBox" name="win_cb">
<property name="text">
<string>Windows</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mac_cb">
<property name="text">
<string>Mac</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -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"))

View file

@ -52,6 +52,13 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="owned_label">
<property name="text">
<string>You already own this game</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="price">
<property name="text">