1
0
Fork 0
mirror of synced 2024-05-06 13:42:52 +12:00
This commit is contained in:
loathingKernel 2024-02-12 18:29:39 +02:00
parent 2a2458bacb
commit 9ec349e2d1
No known key found for this signature in database
GPG key ID: CE0C72D0B53821FD
14 changed files with 270 additions and 283 deletions

View file

@ -28,21 +28,15 @@ class StoreTab(SideTabWidget):
self.landing = LandingPage(self.api, parent=self)
self.landing_index = self.addTab(self.landing, self.tr("Store"))
self.search = SearchPage(self.core, self.api, parent=self)
self.search = SearchPage(self.api, parent=self)
self.search_index = self.addTab(self.search, self.tr("Search"))
self.wishlist = WishlistPage(self.api, parent=self)
self.wishlist_index = self.addTab(self.wishlist, self.tr("Wishlist"))
self.api.update_wishlist.connect(self.update_wishlist)
self.previous_index = self.landing_index
def showEvent(self, a0: QShowEvent) -> None:
if a0.spontaneous() or self.init:
return super().showEvent(a0)
# self.landing.load()
# self.wishlist.update_wishlist()
self.init = True
return super().showEvent(a0)
@ -51,6 +45,3 @@ class StoreTab(SideTabWidget):
return super().hideEvent(a0)
# TODO: Implement store unloading
return super().hideEvent(a0)
def update_wishlist(self):
self.landing.update_wishlist()

View file

@ -25,14 +25,14 @@ type DiscountSetting {
discountType: String
}
type AppliedRuled {
type AppliedRules {
id: ID
endDate: Date
discountSetting: DiscountSetting
}
type LineOfferRes {
appliedRules: [AppliedRuled]
appliedRules: [AppliedRules]
}
type GetPriceRes {

View file

@ -33,7 +33,7 @@ class DieselSystemDetailItem:
class DieselSystemDetail:
p_type: Optional[str] = None
details: Optional[List[DieselSystemDetailItem]] = None
system_type: Optional[str] = None
systemType: Optional[str] = None
unmapped: Dict[str, Any] = field(default_factory=dict)
@classmethod
@ -47,7 +47,7 @@ class DieselSystemDetail:
tmp = cls(
p_type=d.pop("_type", ""),
details=details,
system_type=d.pop("systemType", ""),
systemType=d.pop("systemType", ""),
)
tmp.unmapped = d
return tmp
@ -107,7 +107,7 @@ class DieselProductDetail:
p_type: Optional[str] = None
about: Optional[DieselProductAbout] = None
requirements: Optional[DieselSystemDetails] = None
social_links: Optional[DieselSocialLinks] = None
socialLinks: Optional[DieselSocialLinks] = None
unmapped: Dict[str, Any] = field(default_factory=dict)
@classmethod
@ -119,7 +119,7 @@ class DieselProductDetail:
p_type=d.pop("_type", ""),
about=about,
requirements=requirements,
social_links=d.pop("socialLinks", {}),
socialLinks=d.pop("socialLinks", {}),
)
tmp.unmapped = d
return tmp
@ -136,7 +136,7 @@ class DieselProduct:
namespace: Optional[str] = None
pages: Optional[List["DieselProduct"]] = None
data: Optional[DieselProductDetail] = None
product_name: Optional[str] = None
productName: Optional[str] = None
unmapped: Dict[str, Any] = field(default_factory=dict)
@classmethod
@ -158,7 +158,7 @@ class DieselProduct:
namespace=d.pop("namespace", ""),
pages=pages,
data=data,
product_name=d.pop("productName", ""),
productName=d.pop("productName", ""),
)
tmp.unmapped = d
return tmp

View file

@ -1,7 +1,7 @@
import logging
from dataclasses import dataclass, field
from datetime import datetime
from typing import List, Dict, Any, Type, Optional
from typing import List, Dict, Any, Type, Optional, Tuple
from .utils import parse_date
@ -17,7 +17,6 @@ ItemModel = Dict
SellerModel = Dict
PageSandboxModel = Dict
TagModel = Dict
PromotionsModel = Dict
@dataclass
@ -39,16 +38,15 @@ class ImageUrlModel:
d = src.copy()
type = d.pop("type", None)
url = d.pop("url", None)
tmp = cls(
type=type,
url=url,
)
tmp = cls(type=type, url=url)
return tmp
@dataclass
class KeyImagesModel:
key_images: Optional[List[ImageUrlModel]] = None
tall_types = ("DieselStoreFrontTall", "OfferImageTall", "Thumbnail", "ProductLogo", "DieselGameBoxLogo")
wide_types = ("DieselStoreFrontWide", "OfferImageWide", "VaultClosed", "ProductLogo")
def __getitem__(self, item):
return self.key_images[item]
@ -76,21 +74,13 @@ class KeyImagesModel:
return tmp
def available_tall(self) -> List[ImageUrlModel]:
tall_types = [
"DieselStoreFrontTall",
"OfferImageTall",
"Thumbnail",
"ProductLogo",
"DieselGameBoxLogo",
]
tall_images = filter(lambda img: img.type in tall_types, self.key_images)
tall_images = sorted(tall_images, key=lambda x: tall_types.index(x.type))
tall_images = filter(lambda img: img.type in KeyImagesModel.tall_types, self.key_images)
tall_images = sorted(tall_images, key=lambda x: KeyImagesModel.tall_types.index(x.type))
return tall_images
def available_wide(self) -> List[ImageUrlModel]:
wide_types = ["DieselStoreFrontWide", "OfferImageWide", "VaultClosed", "ProductLogo"]
wide_images = filter(lambda img: img.type in wide_types, self.key_images)
wide_images = sorted(wide_images, key=lambda x: wide_types.index(x.type))
wide_images = filter(lambda img: img.type in KeyImagesModel.wide_types, self.key_images)
wide_images = sorted(wide_images, key=lambda x: KeyImagesModel.wide_types.index(x.type))
return wide_images
def for_dimensions(self, w: int, h: int) -> ImageUrlModel:
@ -107,55 +97,133 @@ class KeyImagesModel:
return model
TotalPriceModel = Dict
FmtPriceModel = Dict
CurrencyModel = Dict
FormattedPriceModel = Dict
LineOffersModel = Dict
@dataclass
class GetPriceResModel:
total_price: Optional[TotalPriceModel] = None
fmt_price: Optional[FmtPriceModel] = None
line_offers: Optional[LineOffersModel] = None
class TotalPriceModel:
discountPrice: Optional[int] = None
originalPrice: Optional[int] = None
voucherDiscount: Optional[int] = None
discount: Optional[int] = None
currencyCode: Optional[str] = None
currencyInfo: Optional[CurrencyModel] = None
fmtPrice: Optional[FormattedPriceModel] = None
unmapped: Dict[str, Any] = field(default_factory=dict)
@classmethod
def from_dict(cls: Type["GetPriceResModel"], src: Dict[str, Any]) -> "GetPriceResModel":
def from_dict(cls: Type["TotalPriceModel"], src: Dict[str, Any]) -> "TotalPriceModel":
d = src.copy()
tmp = cls(
total_price=d.pop("totalPrice", {}),
fmt_price=d.pop("fmtPrice", {}),
line_offers=d.pop("lineOffers", {}),
discountPrice=d.pop("discountPrice", None),
originalPrice=d.pop("originalPrice", None),
voucherDiscount=d.pop("voucherDiscount", None),
discount=d.pop("discount", None),
currencyCode=d.pop("currencyCode", None),
currencyInfo=d.pop("currrencyInfo", {}),
fmtPrice=d.pop("fmtPrice", {}),
)
tmp.unmapped = d
return tmp
@dataclass
class GetPriceResModel:
totalPrice: Optional[TotalPriceModel] = None
lineOffers: Optional[LineOffersModel] = None
unmapped: Dict[str, Any] = field(default_factory=dict)
@classmethod
def from_dict(cls: Type["GetPriceResModel"], src: Dict[str, Any]) -> "GetPriceResModel":
d = src.copy()
total_price = TotalPriceModel.from_dict(x) if (x := d.pop("totalPrice", {})) else None
tmp = cls(totalPrice=total_price, lineOffers=d.pop("lineOffers", {}))
tmp.unmapped = d
return tmp
DiscountSettingModel = Dict
@dataclass
class PromotionalOfferModel:
startDate: Optional[datetime] = None
endDate: Optional[datetime] = None
discountSetting: Optional[DiscountSettingModel] = None
@classmethod
def from_dict(cls: Type["PromotionalOfferModel"], src: Dict[str, Any]) -> "PromotionalOfferModel":
d = src.copy()
start_date = parse_date(x) if (x := d.pop("startDate", "")) else None
end_date = parse_date(x) if (x := d.pop("endDate", "")) else None
tmp = cls(startDate=start_date, endDate=end_date, discountSetting=d.pop("discountSetting", {}))
tmp.unmapped = d
return tmp
@dataclass
class PromotionalOffersModel:
promotionalOffers: Optional[Tuple[PromotionalOfferModel]] = None
@classmethod
def from_list(cls: Type["PromotionalOffersModel"], src: Dict[str, List]) -> "PromotionalOffersModel":
d = src.copy()
promotional_offers = (
tuple([PromotionalOfferModel.from_dict(y) for y in x]) if (x := d.pop("promotionalOffers", [])) else None
)
tmp = cls(promotionalOffers=promotional_offers)
tmp.unmapped = d
return tmp
@dataclass
class PromotionsModel:
promotionalOffers: Optional[Tuple[PromotionalOffersModel]] = None
upcomingPromotionalOffers: Optional[Tuple[PromotionalOffersModel]] = None
@classmethod
def from_dict(cls: Type["PromotionsModel"], src: Dict[str, Any]) -> "PromotionsModel":
d = src.copy()
promotional_offers = (
tuple([PromotionalOffersModel.from_list(y) for y in x]) if (x := d.pop("promotionalOffers", [])) else None
)
upcoming_promotional_offers = (
tuple([PromotionalOffersModel.from_list(y) for y in x])
if (x := d.pop("upcomingPromotionalOffers", []))
else None
)
tmp = cls(promotionalOffers=promotional_offers, upcomingPromotionalOffers=upcoming_promotional_offers)
tmp.unmapped = d
return tmp
@dataclass
class CatalogOfferModel:
catalog_ns: Optional[CatalogNamespaceModel] = None
catalogNs: Optional[CatalogNamespaceModel] = None
categories: Optional[List[CategoryModel]] = None
custom_attributes: Optional[List[CustomAttributeModel]] = None
customAttributes: Optional[List[CustomAttributeModel]] = None
description: Optional[str] = None
effective_date: Optional[datetime] = None
expiry_date: Optional[datetime] = None
effectiveDate: Optional[datetime] = None
expiryDate: Optional[datetime] = None
id: Optional[str] = None
is_code_redemption_only: Optional[bool] = None
isCodeRedemptionOnly: Optional[bool] = None
items: Optional[List[ItemModel]] = None
key_images: Optional[KeyImagesModel] = None
keyImages: Optional[KeyImagesModel] = None
namespace: Optional[str] = None
offer_mappings: Optional[List[PageSandboxModel]] = None
offer_type: Optional[str] = None
offerMappings: Optional[List[PageSandboxModel]] = None
offerType: Optional[str] = None
price: Optional[GetPriceResModel] = None
product_slug: Optional[str] = None
productSlug: Optional[str] = None
promotions: Optional[PromotionsModel] = None
seller: Optional[SellerModel] = None
status: Optional[str] = None
tags: Optional[List[TagModel]] = None
title: Optional[str] = None
url: Optional[str] = None
url_slug: Optional[str] = None
viewable_date: Optional[datetime] = None
urlSlug: Optional[str] = None
viewableDate: Optional[datetime] = None
unmapped: Dict[str, Any] = field(default_factory=dict)
@classmethod
@ -165,31 +233,32 @@ class CatalogOfferModel:
expiry_date = parse_date(x) if (x := d.pop("expiryDate", "")) else None
key_images = KeyImagesModel.from_list(d.pop("keyImages", []))
price = GetPriceResModel.from_dict(x) if (x := d.pop("price", {})) else None
promotions = PromotionsModel.from_dict(x) if (x := d.pop("promotions", {})) else None
viewable_date = parse_date(x) if (x := d.pop("viewableDate", "")) else None
tmp = cls(
catalog_ns=d.pop("catalogNs", {}),
catalogNs=d.pop("catalogNs", {}),
categories=d.pop("categories", []),
custom_attributes=d.pop("customAttributes", []),
customAttributes=d.pop("customAttributes", []),
description=d.pop("description", ""),
effective_date=effective_date,
expiry_date=expiry_date,
effectiveDate=effective_date,
expiryDate=expiry_date,
id=d.pop("id", ""),
is_code_redemption_only=d.pop("isCodeRedemptionOnly", None),
isCodeRedemptionOnly=d.pop("isCodeRedemptionOnly", None),
items=d.pop("items", []),
key_images=key_images,
keyImages=key_images,
namespace=d.pop("namespace", ""),
offer_mappings=d.pop("offerMappings", []),
offer_type=d.pop("offerType", ""),
offerMappings=d.pop("offerMappings", []),
offerType=d.pop("offerType", ""),
price=price,
product_slug=d.pop("productSlug", ""),
promotions=d.pop("promotions", {}),
productSlug=d.pop("productSlug", ""),
promotions=promotions,
seller=d.pop("seller", {}),
status=d.pop("status", ""),
tags=d.pop("tags", []),
title=d.pop("title", ""),
url=d.pop("url", ""),
url_slug=d.pop("urlSlug", ""),
viewable_date=viewable_date,
urlSlug=d.pop("urlSlug", ""),
viewableDate=viewable_date,
)
tmp.unmapped = d
return tmp
@ -200,8 +269,8 @@ class WishlistItemModel:
created: Optional[datetime] = None
id: Optional[str] = None
namespace: Optional[str] = None
is_first_time: Optional[bool] = None
offer_id: Optional[str] = None
isFirstTime: Optional[bool] = None
offerId: Optional[str] = None
order: Optional[Any] = None
updated: Optional[datetime] = None
offer: Optional[CatalogOfferModel] = None
@ -217,8 +286,8 @@ class WishlistItemModel:
created=created,
id=d.pop("id", ""),
namespace=d.pop("namespace", ""),
is_first_time=d.pop("isFirstTime", None),
offer_id=d.pop("offerId", ""),
isFirstTime=d.pop("isFirstTime", None),
offerId=d.pop("offerId", ""),
order=d.pop("order", ""),
updated=updated,
offer=offer,
@ -238,10 +307,7 @@ class PagingModel:
d = src.copy()
count = d.pop("count", None)
total = d.pop("total", None)
tmp = cls(
count=count,
total=total,
)
tmp = cls(count=count, total=total)
tmp.unmapped = d
return tmp
@ -261,26 +327,21 @@ class SearchStoreModel:
elem = CatalogOfferModel.from_dict(item)
elements.append(elem)
paging = PagingModel.from_dict(x) if (x := d.pop("paging", {})) else None
tmp = cls(
elements=elements,
paging=paging,
)
tmp = cls(elements=elements, paging=paging)
tmp.unmapped = d
return tmp
@dataclass
class CatalogModel:
search_store: Optional[SearchStoreModel] = None
searchStore: Optional[SearchStoreModel] = None
unmapped: Dict[str, Any] = field(default_factory=dict)
@classmethod
def from_dict(cls: Type["CatalogModel"], src: Dict[str, Any]) -> "CatalogModel":
d = src.copy()
search_store = SearchStoreModel.from_dict(x) if (x := d.pop("searchStore", {})) else None
tmp = cls(
search_store=search_store,
)
tmp = cls(searchStore=search_store)
tmp.unmapped = d
return tmp
@ -300,10 +361,7 @@ class WishlistItemsModel:
elem = WishlistItemModel.from_dict(item)
elements.append(elem)
paging = PagingModel.from_dict(x) if (x := d.pop("paging", {})) else None
tmp = cls(
elements=elements,
paging=paging,
)
tmp = cls(elements=elements, paging=paging)
tmp.unmapped = d
return tmp
@ -316,16 +374,14 @@ class RemoveFromWishlistModel:
@classmethod
def from_dict(cls: Type["RemoveFromWishlistModel"], src: Dict[str, Any]) -> "RemoveFromWishlistModel":
d = src.copy()
tmp = cls(
success=d.pop("success", None),
)
tmp = cls(success=d.pop("success", None))
tmp.unmapped = d
return tmp
@dataclass
class AddToWishlistModel:
wishlist_item: Optional[WishlistItemModel] = None
wishlistItem: Optional[WishlistItemModel] = None
success: Optional[bool] = None
unmapped: Dict[str, Any] = field(default_factory=dict)
@ -333,33 +389,26 @@ class AddToWishlistModel:
def from_dict(cls: Type["AddToWishlistModel"], src: Dict[str, Any]) -> "AddToWishlistModel":
d = src.copy()
wishlist_item = WishlistItemModel.from_dict(x) if (x := d.pop("wishlistItem", {})) else None
tmp = cls(
wishlist_item=wishlist_item,
success=d.pop("success", None),
)
tmp = cls(wishlistItem=wishlist_item, success=d.pop("success", None))
tmp.unmapped = d
return tmp
@dataclass
class WishlistModel:
wishlist_items: Optional[WishlistItemsModel] = None
remove_from_wishlist: Optional[RemoveFromWishlistModel] = None
add_to_wishlist: Optional[AddToWishlistModel] = None
wishlistItems: Optional[WishlistItemsModel] = None
removeFromWishlist: Optional[RemoveFromWishlistModel] = None
addToWishlist: Optional[AddToWishlistModel] = None
unmapped: Dict[str, Any] = field(default_factory=dict)
@classmethod
def from_dict(cls: Type["WishlistModel"], src: Dict[str, Any]) -> "WishlistModel":
d = src.copy()
wishlist_items = WishlistItemsModel.from_dict(x) if (x := d.pop("wishlistItems", {})) else None
remove_from_wishlist = (
RemoveFromWishlistModel.from_dict(x) if (x := d.pop("removeFromWishlist", {})) else None
)
remove_from_wishlist = RemoveFromWishlistModel.from_dict(x) if (x := d.pop("removeFromWishlist", {})) else None
add_to_wishlist = AddToWishlistModel.from_dict(x) if (x := d.pop("addToWishlist", {})) else None
tmp = cls(
wishlist_items=wishlist_items,
remove_from_wishlist=remove_from_wishlist,
add_to_wishlist=add_to_wishlist,
wishlistItems=wishlist_items, removeFromWishlist=remove_from_wishlist, addToWishlist=add_to_wishlist
)
tmp.unmapped = d
return tmp

View file

@ -2,4 +2,4 @@ from datetime import datetime, timezone
def parse_date(date: str):
return datetime.fromisoformat(date[:-1]).replace(tzinfo=timezone.utc)
return datetime.fromisoformat(date[:-1]).replace(tzinfo=timezone.utc)

View file

@ -14,26 +14,25 @@ from PyQt5.QtWidgets import (
QFrame,
)
from rare.components.tabs.store.api.models.response import CatalogOfferModel, WishlistItemModel
from rare.widgets.flow_layout import FlowLayout
from rare.widgets.side_tab import SideTabContents
from rare.widgets.sliding_stack import SlidingStackedWidget
from rare.components.tabs.store.api.models.response import CatalogOfferModel, WishlistItemModel
from .api.models.utils import parse_date
from .store_api import StoreAPI
from .widgets.details import DetailsWidget
from .widgets.items import StoreItemWidget
from .widgets.groups import StoreGroup
from .widgets.items import StoreItemWidget
logger = logging.getLogger("StoreLanding")
class LandingPage(SlidingStackedWidget, SideTabContents):
def __init__(self, api: StoreAPI, parent=None):
def __init__(self, store_api: StoreAPI, parent=None):
super(LandingPage, self).__init__(parent=parent)
self.implements_scrollarea = True
self.landing_widget = LandingWidget(api, parent=self)
self.landing_widget = LandingWidget(store_api, parent=self)
self.landing_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.landing_widget.set_title.connect(self.set_title)
self.landing_widget.show_details.connect(self.show_details)
@ -43,7 +42,7 @@ class LandingPage(SlidingStackedWidget, SideTabContents):
self.landing_scroll.setFrameStyle(QFrame.NoFrame | QFrame.Plain)
self.landing_scroll.setWidget(self.landing_widget)
self.details_widget = DetailsWidget([], api, parent=self)
self.details_widget = DetailsWidget([], store_api, parent=self)
self.details_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.details_widget.set_title.connect(self.set_title)
self.details_widget.back_clicked.connect(self.show_main)
@ -83,7 +82,7 @@ class LandingWidget(QWidget, SideTabContents):
self.discounts_group = StoreGroup(self.tr("Wishlist discounts"), layout=FlowLayout, parent=self)
self.discounts_group.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.games_group = StoreGroup(self.tr("Games"), FlowLayout, self)
self.games_group = StoreGroup(self.tr("Free to play"), FlowLayout, self)
self.games_group.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.games_group.loading(False)
self.games_group.setVisible(True)
@ -97,8 +96,8 @@ class LandingWidget(QWidget, SideTabContents):
def showEvent(self, a0: QShowEvent) -> None:
if a0.spontaneous():
return super().showEvent(a0)
self.api.get_free_games(self.__add_free)
self.api.get_wishlist(self.__add_discounts)
self.api.get_free(self.__update_free_games)
self.api.get_wishlist(self.__update_wishlist_discounts)
return super().showEvent(a0)
def hideEvent(self, a0: QHideEvent) -> None:
@ -107,28 +106,20 @@ class LandingWidget(QWidget, SideTabContents):
# TODO: Implement tab unloading
return super().hideEvent(a0)
def __add_discounts(self, wishlist: List[WishlistItemModel]):
def __update_wishlist_discounts(self, wishlist: List[WishlistItemModel]):
for w in self.discounts_group.findChildren(StoreItemWidget, options=Qt.FindDirectChildrenOnly):
self.discounts_group.layout().removeWidget(w)
w.deleteLater()
discounts = 0
for game in wishlist:
if not game:
continue
try:
if game.offer.price.total_price["discount"] > 0:
w = StoreItemWidget(self.api.cached_manager, game.offer)
w.show_details.connect(self.show_details)
self.discounts_group.layout().addWidget(w)
discounts += 1
except Exception as e:
logger.warning(f"{game} {e}")
continue
# self.discounts_group.setVisible(discounts > 0)
for item in wishlist:
if item.offer.price.totalPrice.discount > 0:
w = StoreItemWidget(self.api.cached_manager, item.offer)
w.show_details.connect(self.show_details)
self.discounts_group.layout().addWidget(w)
self.discounts_group.setVisible(bool(wishlist))
self.discounts_group.loading(False)
def __add_free(self, free_games: List[CatalogOfferModel]):
def __update_free_games(self, free_games: List[CatalogOfferModel]):
for w in self.free_games_now.findChildren(StoreItemWidget, options=Qt.FindDirectChildrenOnly):
self.free_games_now.layout().removeWidget(w)
w.deleteLater()
@ -140,56 +131,38 @@ class LandingWidget(QWidget, SideTabContents):
date = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
free_now = []
free_next = []
for game in free_games:
for item in free_games:
try:
if (
game.price.total_price["fmtPrice"]["discountPrice"] == "0"
and game.price.total_price["fmtPrice"]["originalPrice"]
!= game.price.total_price["fmtPrice"]["discountPrice"]
):
free_now.append(game)
if item.price.totalPrice.discountPrice == 0:
free_now.append(item)
continue
if game.title == "Mystery Game":
free_next.append(game)
if item.title == "Mystery Game":
free_next.append(item)
continue
except KeyError as e:
logger.warning(str(e))
try:
# parse datetime to check if game is next week or now
try:
start_date = parse_date(
game.promotions["upcomingPromotionalOffers"][0]["promotionalOffers"][0]["startDate"]
)
except Exception:
try:
start_date = parse_date(
game.promotions["promotionalOffers"][0]["promotionalOffers"][0]["startDate"]
)
except Exception as e:
continue
except TypeError:
print("type error")
continue
if not item.promotions.promotionalOffers:
start_date = item.promotions.upcomingPromotionalOffers[0].promotionalOffers[0].startDate
else:
start_date = item.promotions.promotionalOffers[0].promotionalOffers[0].startDate
if start_date > date:
free_next.append(game)
free_next.append(item)
# free games now
self.free_games_now.setVisible(bool(len(free_now)))
for game in free_now:
w = StoreItemWidget(self.api.cached_manager, game)
self.free_games_now.setVisible(bool(free_now))
for item in free_now:
w = StoreItemWidget(self.api.cached_manager, item)
w.show_details.connect(self.show_details)
self.free_games_now.layout().addWidget(w)
self.free_games_now.loading(False)
# free games next week
self.free_games_next.setVisible(bool(len(free_next)))
for game in free_next:
w = StoreItemWidget(self.api.cached_manager, game)
if game.title != "Mystery Game":
self.free_games_next.setVisible(bool(free_next))
for item in free_next:
w = StoreItemWidget(self.api.cached_manager, item)
if item.title != "Mystery Game":
w.show_details.connect(self.show_details)
self.free_games_next.layout().addWidget(w)
self.free_games_next.loading(False)

View file

@ -26,16 +26,16 @@ logger = logging.getLogger("Shop")
class SearchPage(SlidingStackedWidget, SideTabContents):
def __init__(self, core, api: StoreAPI, parent=None):
def __init__(self, store_api: StoreAPI, parent=None):
super(SearchPage, self).__init__(parent=parent)
self.implements_scrollarea = True
self.search_widget = SearchWidget(core, api, parent=self)
self.search_widget = SearchWidget(store_api, parent=self)
self.search_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.search_widget.set_title.connect(self.set_title)
self.search_widget.show_details.connect(self.show_details)
self.details_widget = DetailsWidget([], api, parent=self)
self.details_widget = DetailsWidget([], store_api, parent=self)
self.details_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.details_widget.set_title.connect(self.set_title)
self.details_widget.back_clicked.connect(self.show_main)
@ -58,27 +58,25 @@ class SearchPage(SlidingStackedWidget, SideTabContents):
class SearchWidget(QWidget, SideTabContents):
show_details = pyqtSignal(CatalogOfferModel)
def __init__(self, core: LegendaryCore, api: StoreAPI, parent=None):
def __init__(self, store_api: StoreAPI, parent=None):
super(SearchWidget, self).__init__(parent=parent)
self.implements_scrollarea = True
self.ui = Ui_SearchWidget()
self.ui.setupUi(self)
self.ui.main_layout.setContentsMargins(0, 0, 3, 0)
self.core = core
self.api_core = api
self.store_api = store_api
self.price = ""
self.tags = []
self.types = []
self.update_games_allowed = True
self.free_game_widgets = []
self.active_search_request = False
self.next_search = ""
self.wishlist: List = []
self.search_bar = ButtonLineEdit("fa.search", placeholder_text=self.tr("Search Games"))
self.results_scrollarea = ResultsWidget(self.api_core, self)
self.results_scrollarea = ResultsWidget(self.store_api, self)
self.results_scrollarea.show_details.connect(self.show_details)
self.ui.left_layout.addWidget(self.search_bar)
@ -188,8 +186,8 @@ class SearchWidget(QWidget, SideTabContents):
self.games_group.loading(True)
browse_model = SearchStoreQuery(
language=self.core.language_code,
country=self.core.country_code,
language=self.store_api.language_code,
country=self.store_api.country_code,
count=20,
price_range=self.price,
on_sale=self.ui.on_discount.isChecked(),
@ -198,7 +196,7 @@ class SearchWidget(QWidget, SideTabContents):
if self.types:
browse_model.category = "|".join(self.types)
self.api_core.browse_games(browse_model, self.show_games)
self.store_api.browse_games(browse_model, self.show_games)
class CheckBox(QCheckBox):

View file

@ -20,7 +20,7 @@ from .api.models.response import (
CatalogOfferModel,
)
logger = getLogger("ShopAPICore")
logger = getLogger("StoreAPI")
graphql_url = "https://graphql.epicgames.com/graphql"
@ -46,7 +46,7 @@ class StoreAPI(QObject):
self.browse_active = False
self.next_browse_request = tuple(())
def get_free_games(self, handle_func: callable):
def get_free(self, handle_func: callable):
url = "https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions"
params = {
"locale": self.locale,
@ -59,7 +59,7 @@ class StoreAPI(QObject):
def __handle_free_games(data, handle_func):
try:
response = ResponseModel.from_dict(data)
results: List[CatalogOfferModel] = response.data.catalog.search_store.elements
results: List[CatalogOfferModel] = response.data.catalog.searchStore.elements
handle_func(results)
except KeyError as e:
if DEBUG():
@ -94,7 +94,7 @@ class StoreAPI(QObject):
response = ResponseModel.from_dict(data)
if response.errors:
logger.error(response.errors)
handle_func(response.data.wishlist.wishlist_items.elements)
handle_func(response.data.wishlist.wishlistItems.elements)
except KeyError as e:
if DEBUG():
raise e
@ -132,7 +132,7 @@ class StoreAPI(QObject):
def __handle_search(data, handler):
try:
response = ResponseModel.from_dict(data)
handler(response.data.catalog.search_store.elements)
handler(response.data.catalog.searchStore.elements)
except KeyError as e:
logger.error(str(e))
if DEBUG():
@ -167,7 +167,7 @@ class StoreAPI(QObject):
if not self.next_browse_request:
try:
response = ResponseModel.from_dict(data)
handle_func(response.data.catalog.search_store.elements)
handle_func(response.data.catalog.searchStore.elements)
except KeyError as e:
if DEBUG():
raise e
@ -233,7 +233,7 @@ class StoreAPI(QObject):
# debug.exec()
try:
response = ResponseModel.from_dict(data)
data = response.data.wishlist.add_to_wishlist
data = response.data.wishlist.addToWishlist
handle_func(data.success)
except Exception as e:
if DEBUG():
@ -259,7 +259,7 @@ class StoreAPI(QObject):
# debug.exec()
try:
response = ResponseModel.from_dict(data)
data = response.data.wishlist.remove_from_wishlist
data = response.data.wishlist.removeFromWishlist
handle_func(data.success)
except Exception as e:
if DEBUG():

View file

@ -1,5 +1,5 @@
import logging
from typing import List
from typing import List, Dict
from PyQt5.QtCore import Qt, QUrl, pyqtSignal
from PyQt5.QtGui import QFont, QDesktopServices, QKeyEvent
@ -14,6 +14,7 @@ from PyQt5.QtWidgets import (
from rare.components.tabs.store.api.debug import DebugDialog
from rare.components.tabs.store.api.models.diesel import DieselProduct, DieselProductDetail, DieselSystemDetail
from rare.components.tabs.store.api.models.response import CatalogOfferModel
from rare.components.tabs.store.store_api import StoreAPI
from rare.models.image import ImageSize
from rare.ui.components.tabs.store.details import Ui_DetailsWidget
from rare.utils.misc import icon
@ -28,7 +29,7 @@ class DetailsWidget(QWidget, SideTabContents):
back_clicked: pyqtSignal = pyqtSignal()
# TODO Design
def __init__(self, installed_titles: list, api_core, parent=None):
def __init__(self, installed: List, store_api: StoreAPI, parent=None):
super(DetailsWidget, self).__init__(parent=parent)
self.implements_scrollarea = True
@ -36,13 +37,11 @@ class DetailsWidget(QWidget, SideTabContents):
self.ui.setupUi(self)
self.ui.main_layout.setContentsMargins(0, 0, 3, 0)
# self.core = LegendaryCoreSingleton()
self.api_core = api_core
self.installed = installed_titles
self.offer: CatalogOfferModel = None
self.data: dict = {}
self.store_api = store_api
self.installed = installed
self.catalog_offer: CatalogOfferModel = None
self.image = LoadingImageWidget(api_core.cached_manager, self)
self.image = LoadingImageWidget(store_api.cached_manager, self)
self.image.setFixedSize(ImageSize.Display)
self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignTop)
self.ui.left_layout.setAlignment(Qt.AlignTop)
@ -53,7 +52,7 @@ class DetailsWidget(QWidget, SideTabContents):
self.in_wishlist = False
self.wishlist = []
self.requirements_tabs: SideTabWidget = SideTabWidget(parent=self.ui.requirements_frame)
self.requirements_tabs = SideTabWidget(parent=self.ui.requirements_frame)
self.requirements_tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.ui.requirements_layout.addWidget(self.requirements_tabs)
@ -78,15 +77,17 @@ class DetailsWidget(QWidget, SideTabContents):
self.ui.title.setText(offer.title)
self.title_str = offer.title
self.id_str = offer.id
self.api_core.get_wishlist(self.handle_wishlist_update)
self.store_api.get_wishlist(self.handle_wishlist_update)
# lk: delete tabs in reverse order because indices are updated on deletion
while self.requirements_tabs.count():
self.requirements_tabs.widget(0).deleteLater()
self.requirements_tabs.removeTab(0)
self.requirements_tabs.clear()
slug = offer.product_slug
slug = offer.productSlug
if not slug:
for mapping in offer.offer_mappings:
for mapping in offer.offerMappings:
if mapping["pageType"] == "productHome":
slug = mapping["pageSlug"]
break
@ -114,24 +115,24 @@ class DetailsWidget(QWidget, SideTabContents):
# init API request
if slug:
self.api_core.get_game_config_cms(offer.product_slug, is_bundle, self.data_received)
self.store_api.get_game_config_cms(offer.productSlug, is_bundle, self.data_received)
# else:
# self.data_received({})
self.offer = offer
self.catalog_offer = offer
def add_to_wishlist(self):
if not self.in_wishlist:
self.api_core.add_to_wishlist(
self.offer.namespace,
self.offer.id,
self.store_api.add_to_wishlist(
self.catalog_offer.namespace,
self.catalog_offer.id,
lambda success: self.ui.wishlist_button.setText(self.tr("Remove from wishlist"))
if success
else self.ui.wishlist_button.setText("Something went wrong")
)
else:
self.api_core.remove_from_wishlist(
self.offer.namespace,
self.offer.id,
self.store_api.remove_from_wishlist(
self.catalog_offer.namespace,
self.catalog_offer.id,
lambda success: self.ui.wishlist_button.setText(self.tr("Add to wishlist"))
if success
else self.ui.wishlist_button.setText("Something went wrong"),
@ -146,34 +147,16 @@ class DetailsWidget(QWidget, SideTabContents):
except Exception as e:
raise e
logger.error(str(e))
self.price.setText("Error")
self.requirements_tabs.setEnabled(False)
for img in self.data.get("keyImages"):
if img["type"] in [
"DieselStoreFrontWide",
"OfferImageTall",
"VaultClosed",
"ProductLogo",
]:
self.image.fetchPixmap(img["url"])
break
self.price.setText("")
self.discount_price.setText("")
self.social_group.setEnabled(False)
self.tags.setText("")
self.dev.setText(self.data.get("seller", {}).get("name", ""))
return
# self.title.setText(self.game.title)
self.ui.price.setFont(QFont())
price = self.offer.price.total_price["fmtPrice"]["originalPrice"]
discount_price = self.offer.price.total_price["fmtPrice"]["discountPrice"]
self.ui.price.setFont(self.font())
price = self.catalog_offer.price.totalPrice.fmtPrice["originalPrice"]
discount_price = self.catalog_offer.price.totalPrice.fmtPrice["discountPrice"]
if price == "0" or price == 0:
self.ui.price.setText(self.tr("Free"))
else:
self.ui.price.setText(price)
if price != discount_price:
font = QFont()
font = self.font()
font.setStrikeOut(True)
self.ui.price.setFont(font)
self.ui.discount_price.setText(
@ -189,18 +172,11 @@ class DetailsWidget(QWidget, SideTabContents):
if requirements and requirements.systems:
for system in requirements.systems:
req_widget = RequirementsWidget(system, self.requirements_tabs)
self.requirements_tabs.addTab(req_widget, system.system_type)
# self.req_group_box.layout().addWidget(req_tabs)
# self.req_group_box.layout().setAlignment(Qt.AlignTop)
# else:
# self.req_group_box.layout().addWidget(
# QLabel(self.tr("Could not get requirements"))
# )
self.ui.requirements_frame.setVisible(True)
self.requirements_tabs.addTab(req_widget, system.systemType)
else:
self.ui.requirements_frame.setVisible(False)
key_images = self.offer.key_images
key_images = self.catalog_offer.keyImages
img_url = key_images.for_dimensions(self.image.size().width(), self.image.size().height())
self.image.fetchPixmap(img_url.url)
@ -223,7 +199,7 @@ class DetailsWidget(QWidget, SideTabContents):
self.ui.social_layout.removeWidget(b)
b.deleteLater()
links = product_data.social_links
links = product_data.socialLinks
link_count = 0
for name, url in links.items():
if name == "_type":
@ -252,8 +228,7 @@ class DetailsWidget(QWidget, SideTabContents):
# self.wishlist.append(game["offer"]["title"])
def button_clicked(self):
return
QDesktopServices.openUrl(QUrl(f"https://www.epicgames.com/store/{self.core.language_code}/p/{self.slug}"))
QDesktopServices.openUrl(QUrl(f"https://www.epicgames.com/store/{self.store_api.language_code}/p/{self.slug}"))
def keyPressEvent(self, a0: QKeyEvent):
if a0.key() == Qt.Key_Escape:

View file

@ -11,7 +11,7 @@ from rare.utils.misc import qta_icon
from rare.utils.qt_requests import QtRequests
from .image import LoadingImageWidget
logger = logging.getLogger("GameWidgets")
logger = logging.getLogger("StoreWidgets")
class ItemWidget(LoadingImageWidget):
@ -46,15 +46,15 @@ class StoreItemWidget(ItemWidget):
return
self.ui.title_label.setText(game.title)
for attr in game.custom_attributes:
for attr in game.customAttributes:
if attr["key"] == "developerName":
developer = attr["value"]
break
else:
developer = game.seller["name"]
self.ui.developer_label.setText(developer)
price = game.price.total_price["fmtPrice"]["originalPrice"]
discount_price = game.price.total_price["fmtPrice"]["discountPrice"]
price = game.price.totalPrice.fmtPrice["originalPrice"]
discount_price = game.price.totalPrice.fmtPrice["discountPrice"]
self.ui.price_label.setText(f'{price if price != "0" else self.tr("Free")}')
if price != discount_price:
font = self.ui.price_label.font()
@ -64,7 +64,7 @@ class StoreItemWidget(ItemWidget):
else:
self.ui.discount_label.setVisible(False)
key_images = game.key_images
key_images = game.keyImages
self.fetchPixmap(key_images.for_dimensions(self.width(), self.height()).url)
# for img in json_info["keyImages"]:
@ -83,13 +83,13 @@ class ResultsItemWidget(ItemWidget):
self.setFixedSize(ImageSize.Display)
self.ui.setupUi(self)
key_images = catalog_game.key_images
key_images = catalog_game.keyImages
self.fetchPixmap(key_images.for_dimensions(self.width(), self.height()).url)
self.ui.title_label.setText(catalog_game.title)
price = catalog_game.price.total_price["fmtPrice"]["originalPrice"]
discount_price = catalog_game.price.total_price["fmtPrice"]["discountPrice"]
price = catalog_game.price.totalPrice.fmtPrice["originalPrice"]
discount_price = catalog_game.price.totalPrice.fmtPrice["discountPrice"]
self.ui.price_label.setText(f'{price if price != "0" else self.tr("Free")}')
if price != discount_price:
font = self.ui.price_label.font()
@ -108,14 +108,14 @@ class WishlistItemWidget(ItemWidget):
super(WishlistItemWidget, self).__init__(manager, catalog_game, parent=parent)
self.setFixedSize(ImageSize.DisplayWide)
self.ui.setupUi(self)
for attr in catalog_game.custom_attributes:
for attr in catalog_game.customAttributes:
if attr["key"] == "developerName":
developer = attr["value"]
break
else:
developer = catalog_game.seller["name"]
original_price = catalog_game.price.total_price["fmtPrice"]["originalPrice"]
discount_price = catalog_game.price.total_price["fmtPrice"]["discountPrice"]
original_price = catalog_game.price.totalPrice.fmtPrice["originalPrice"]
discount_price = catalog_game.price.totalPrice.fmtPrice["discountPrice"]
self.ui.title_label.setText(catalog_game.title)
self.ui.developer_label.setText(developer)
@ -127,7 +127,7 @@ class WishlistItemWidget(ItemWidget):
self.ui.discount_label.setText(f'{discount_price if discount_price != "0" else self.tr("Free")}')
else:
self.ui.discount_label.setVisible(False)
key_images = catalog_game.key_images
key_images = catalog_game.keyImages
self.fetchPixmap(
key_images.for_dimensions(self.width(), self.height()).url
)

View file

@ -116,13 +116,13 @@ class WishlistWidget(QWidget, SideTabContents):
func = lambda x: x.catalog_game.title
reverse = self.ui.reverse.isChecked()
elif sort == 1:
func = lambda x: x.catalog_game.price.total_price["fmtPrice"]["discountPrice"]
func = lambda x: x.catalog_game.price.totalPrice["fmtPrice"]["discountPrice"]
reverse = self.ui.reverse.isChecked()
elif sort == 2:
func = lambda x: x.catalog_game.seller["name"]
reverse = self.ui.reverse.isChecked()
elif sort == 3:
func = lambda x: 1 - (x.catalog_game.price.total_price["discountPrice"] / x.catalog_game.price.total_price["originalPrice"])
func = lambda x: 1 - (x.catalog_game.price.totalPrice["discountPrice"] / x.catalog_game.price.totalPrice["originalPrice"])
reverse = not self.ui.reverse.isChecked()
else:
func = lambda x: x.catalog_game.title

View file

@ -18,6 +18,8 @@ class Ui_DetailsWidget(object):
DetailsWidget.setWindowTitle("DetailsWidget")
self.main_layout = QtWidgets.QHBoxLayout(DetailsWidget)
self.main_layout.setObjectName("main_layout")
self.left_layout = QtWidgets.QVBoxLayout()
self.left_layout.setObjectName("left_layout")
self.back_button = QtWidgets.QPushButton(DetailsWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
@ -28,9 +30,7 @@ class Ui_DetailsWidget(object):
self.back_button.setIconSize(QtCore.QSize(32, 32))
self.back_button.setFlat(True)
self.back_button.setObjectName("back_button")
self.main_layout.addWidget(self.back_button)
self.left_layout = QtWidgets.QVBoxLayout()
self.left_layout.setObjectName("left_layout")
self.left_layout.addWidget(self.back_button)
self.main_layout.addLayout(self.left_layout)
self.right_layout = QtWidgets.QVBoxLayout()
self.right_layout.setObjectName("right_layout")
@ -176,7 +176,7 @@ class Ui_DetailsWidget(object):
self.description_label.setObjectName("description_label")
self.right_layout.addWidget(self.description_label)
self.main_layout.addLayout(self.right_layout)
self.main_layout.setStretch(2, 1)
self.main_layout.setStretch(1, 1)
self.retranslateUi(DetailsWidget)

View file

@ -13,31 +13,32 @@
<property name="windowTitle">
<string notr="true">DetailsWidget</string>
</property>
<layout class="QHBoxLayout" name="main_layout" stretch="0,0,1">
<layout class="QHBoxLayout" name="main_layout" stretch="0,1">
<item>
<widget class="QPushButton" name="back_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="left_layout"/>
<layout class="QVBoxLayout" name="left_layout">
<item>
<widget class="QPushButton" name="back_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="right_layout">

View file

@ -5,7 +5,7 @@ from typing import Callable, Dict, TypeVar, List, Tuple
from typing import Union
import orjson
from PyQt5.QtCore import QObject, pyqtSignal, QUrl, QUrlQuery, pyqtSlot
from PyQt5.QtCore import QObject, pyqtSignal, QUrl, QUrlQuery, pyqtSlot, QJsonDocument
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply, QNetworkDiskCache
USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36"