Store: Exploratory changes for GraphQL API
This commit is contained in:
parent
f6396f488a
commit
91af16b76d
|
@ -6,6 +6,7 @@ from rare.widgets.side_tab import SideTabWidget
|
|||
from .game_info import ShopGameInfo
|
||||
from .search_results import SearchResults
|
||||
from .shop_api_core import ShopApiCore
|
||||
from .api.models.response import CatalogOfferModel
|
||||
from .shop_widget import ShopWidget
|
||||
from .wishlist import WishlistWidget, Wishlist
|
||||
|
||||
|
@ -69,7 +70,7 @@ class StoreTab(SideTabWidget):
|
|||
def update_wishlist(self):
|
||||
self.shop.update_wishlist()
|
||||
|
||||
def show_game(self, data):
|
||||
def show_game(self, data: CatalogOfferModel):
|
||||
self.previous_index = self.currentIndex()
|
||||
self.info.update_game(data)
|
||||
self.setCurrentIndex(self.info_index)
|
||||
|
|
0
rare/components/tabs/store/api/__init__.py
Normal file
0
rare/components/tabs/store/api/__init__.py
Normal file
568
rare/components/tabs/store/api/constants/queries.py
Normal file
568
rare/components/tabs/store/api/constants/queries.py
Normal file
|
@ -0,0 +1,568 @@
|
|||
|
||||
FEED_QUERY = '''
|
||||
query feedQuery(
|
||||
$locale: String!
|
||||
$countryCode: String
|
||||
$offset: Int
|
||||
$postsPerPage: Int
|
||||
$category: String
|
||||
) {
|
||||
TransientStream {
|
||||
myTransientFeed(countryCode: $countryCode, locale: $locale) {
|
||||
id
|
||||
activity {
|
||||
... on LinkAccountActivity {
|
||||
type
|
||||
created_at
|
||||
platforms
|
||||
}
|
||||
... on SuggestedFriendsActivity {
|
||||
type
|
||||
created_at
|
||||
platform
|
||||
suggestions {
|
||||
epicId
|
||||
epicDisplayName
|
||||
platformFullName
|
||||
platformAvatar
|
||||
}
|
||||
}
|
||||
... on IncomingInvitesActivity {
|
||||
type
|
||||
created_at
|
||||
invites {
|
||||
epicId
|
||||
epicDisplayName
|
||||
}
|
||||
}
|
||||
... on RecentPlayersActivity {
|
||||
type
|
||||
created_at
|
||||
players {
|
||||
epicId
|
||||
epicDisplayName
|
||||
playedGameName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Blog {
|
||||
dieselBlogPosts: getPosts(
|
||||
locale: $locale
|
||||
offset: $offset
|
||||
postsPerPage: $postsPerPage
|
||||
category: $category
|
||||
) {
|
||||
blogList {
|
||||
_id
|
||||
author
|
||||
category
|
||||
content
|
||||
urlPattern
|
||||
slug
|
||||
sticky
|
||||
title
|
||||
date
|
||||
image
|
||||
shareImage
|
||||
trendingImage
|
||||
url
|
||||
featured
|
||||
link
|
||||
externalLink
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
REVIEWS_QUERY = '''
|
||||
query productReviewsQuery($sku: String!) {
|
||||
OpenCritic {
|
||||
productReviews(sku: $sku) {
|
||||
id
|
||||
name
|
||||
openCriticScore
|
||||
reviewCount
|
||||
percentRecommended
|
||||
openCriticUrl
|
||||
award
|
||||
topReviews {
|
||||
publishedDate
|
||||
externalUrl
|
||||
snippet
|
||||
language
|
||||
score
|
||||
author
|
||||
ScoreFormat {
|
||||
id
|
||||
description
|
||||
}
|
||||
OutletId
|
||||
outletName
|
||||
displayScore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
MEDIA_QUERY = '''
|
||||
query fetchMediaRef($mediaRefId: String!) {
|
||||
Media {
|
||||
getMediaRef(mediaRefId: $mediaRefId) {
|
||||
accountId
|
||||
outputs {
|
||||
duration
|
||||
url
|
||||
width
|
||||
height
|
||||
key
|
||||
contentType
|
||||
}
|
||||
namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
ADDONS_QUERY = '''
|
||||
query getAddonsByNamespace(
|
||||
$categories: String!
|
||||
$count: Int!
|
||||
$country: String!
|
||||
$locale: String!
|
||||
$namespace: String!
|
||||
$sortBy: String!
|
||||
$sortDir: String!
|
||||
) {
|
||||
Catalog {
|
||||
catalogOffers(
|
||||
namespace: $namespace
|
||||
locale: $locale
|
||||
params: {
|
||||
category: $categories
|
||||
count: $count
|
||||
country: $country
|
||||
sortBy: $sortBy
|
||||
sortDir: $sortDir
|
||||
}
|
||||
) {
|
||||
elements {
|
||||
countriesBlacklist
|
||||
customAttributes {
|
||||
key
|
||||
value
|
||||
}
|
||||
description
|
||||
developer
|
||||
effectiveDate
|
||||
id
|
||||
isFeatured
|
||||
keyImages {
|
||||
type
|
||||
url
|
||||
}
|
||||
lastModifiedDate
|
||||
longDescription
|
||||
namespace
|
||||
offerType
|
||||
productSlug
|
||||
releaseDate
|
||||
status
|
||||
technicalDetails
|
||||
title
|
||||
urlSlug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
CATALOG_QUERY = '''
|
||||
query catalogQuery(
|
||||
$category: String
|
||||
$count: Int
|
||||
$country: String!
|
||||
$keywords: String
|
||||
$locale: String
|
||||
$namespace: String!
|
||||
$sortBy: String
|
||||
$sortDir: String
|
||||
$start: Int
|
||||
$tag: String
|
||||
) {
|
||||
Catalog {
|
||||
catalogOffers(
|
||||
namespace: $namespace
|
||||
locale: $locale
|
||||
params: {
|
||||
count: $count
|
||||
country: $country
|
||||
category: $category
|
||||
keywords: $keywords
|
||||
sortBy: $sortBy
|
||||
sortDir: $sortDir
|
||||
start: $start
|
||||
tag: $tag
|
||||
}
|
||||
) {
|
||||
elements {
|
||||
isFeatured
|
||||
collectionOfferIds
|
||||
title
|
||||
id
|
||||
namespace
|
||||
description
|
||||
keyImages {
|
||||
type
|
||||
url
|
||||
}
|
||||
seller {
|
||||
id
|
||||
name
|
||||
}
|
||||
productSlug
|
||||
urlSlug
|
||||
items {
|
||||
id
|
||||
namespace
|
||||
}
|
||||
customAttributes {
|
||||
key
|
||||
value
|
||||
}
|
||||
categories {
|
||||
path
|
||||
}
|
||||
price(country: $country) {
|
||||
totalPrice {
|
||||
discountPrice
|
||||
originalPrice
|
||||
voucherDiscount
|
||||
discount
|
||||
fmtPrice(locale: $locale) {
|
||||
originalPrice
|
||||
discountPrice
|
||||
intermediatePrice
|
||||
}
|
||||
}
|
||||
lineOffers {
|
||||
appliedRules {
|
||||
id
|
||||
endDate
|
||||
}
|
||||
}
|
||||
}
|
||||
linkedOfferId
|
||||
linkedOffer {
|
||||
effectiveDate
|
||||
customAttributes {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
paging {
|
||||
count
|
||||
total
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
CATALOG_TAGS_QUERY = '''
|
||||
query catalogTags($namespace: String!) {
|
||||
Catalog {
|
||||
tags(namespace: $namespace, start: 0, count: 999) {
|
||||
elements {
|
||||
aliases
|
||||
id
|
||||
name
|
||||
referenceCount
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
PREREQUISITES_QUERY = '''
|
||||
query fetchPrerequisites($offerParams: [OfferParams]) {
|
||||
Launcher {
|
||||
prerequisites(offerParams: $offerParams) {
|
||||
namespace
|
||||
offerId
|
||||
missingPrerequisiteItems
|
||||
satisfiesPrerequisites
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
PROMOTIONS_QUERY = '''
|
||||
query promotionsQuery(
|
||||
$namespace: String!
|
||||
$country: String!
|
||||
$locale: String!
|
||||
) {
|
||||
Catalog {
|
||||
catalogOffers(
|
||||
namespace: $namespace
|
||||
locale: $locale
|
||||
params: {
|
||||
category: "freegames"
|
||||
country: $country
|
||||
sortBy: "effectiveDate"
|
||||
sortDir: "asc"
|
||||
}
|
||||
) {
|
||||
elements {
|
||||
title
|
||||
description
|
||||
id
|
||||
namespace
|
||||
categories {
|
||||
path
|
||||
}
|
||||
linkedOfferNs
|
||||
linkedOfferId
|
||||
keyImages {
|
||||
type
|
||||
url
|
||||
}
|
||||
productSlug
|
||||
promotions {
|
||||
promotionalOffers {
|
||||
promotionalOffers {
|
||||
startDate
|
||||
endDate
|
||||
discountSetting {
|
||||
discountType
|
||||
discountPercentage
|
||||
}
|
||||
}
|
||||
}
|
||||
upcomingPromotionalOffers {
|
||||
promotionalOffers {
|
||||
startDate
|
||||
endDate
|
||||
discountSetting {
|
||||
discountType
|
||||
discountPercentage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
OFFERS_QUERY = '''
|
||||
query catalogQuery(
|
||||
$productNamespace: String!
|
||||
$offerId: String!
|
||||
$locale: String
|
||||
$country: String!
|
||||
$includeSubItems: Boolean!
|
||||
) {
|
||||
Catalog {
|
||||
catalogOffer(namespace: $productNamespace, id: $offerId, locale: $locale) {
|
||||
title
|
||||
id
|
||||
namespace
|
||||
description
|
||||
effectiveDate
|
||||
expiryDate
|
||||
isCodeRedemptionOnly
|
||||
keyImages {
|
||||
type
|
||||
url
|
||||
}
|
||||
seller {
|
||||
id
|
||||
name
|
||||
}
|
||||
productSlug
|
||||
urlSlug
|
||||
url
|
||||
tags {
|
||||
id
|
||||
}
|
||||
items {
|
||||
id
|
||||
namespace
|
||||
}
|
||||
customAttributes {
|
||||
key
|
||||
value
|
||||
}
|
||||
categories {
|
||||
path
|
||||
}
|
||||
price(country: $country) {
|
||||
totalPrice {
|
||||
discountPrice
|
||||
originalPrice
|
||||
voucherDiscount
|
||||
discount
|
||||
currencyCode
|
||||
currencyInfo {
|
||||
decimals
|
||||
}
|
||||
fmtPrice(locale: $locale) {
|
||||
originalPrice
|
||||
discountPrice
|
||||
intermediatePrice
|
||||
}
|
||||
}
|
||||
lineOffers {
|
||||
appliedRules {
|
||||
id
|
||||
endDate
|
||||
discountSetting {
|
||||
discountType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
offerSubItems(namespace: $productNamespace, id: $offerId)
|
||||
@include(if: $includeSubItems) {
|
||||
namespace
|
||||
id
|
||||
releaseInfo {
|
||||
appId
|
||||
platform
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
SEARCH_STORE_QUERY = '''
|
||||
query searchStoreQuery(
|
||||
$allowCountries: String
|
||||
$category: String
|
||||
$count: Int
|
||||
$country: String!
|
||||
$keywords: String
|
||||
$locale: String
|
||||
$namespace: String
|
||||
$itemNs: String
|
||||
$sortBy: String
|
||||
$sortDir: String
|
||||
$start: Int
|
||||
$tag: String
|
||||
$releaseDate: String
|
||||
$withPrice: Boolean = false
|
||||
$withPromotions: Boolean = false
|
||||
) {
|
||||
Catalog {
|
||||
searchStore(
|
||||
allowCountries: $allowCountries
|
||||
category: $category
|
||||
count: $count
|
||||
country: $country
|
||||
keywords: $keywords
|
||||
locale: $locale
|
||||
namespace: $namespace
|
||||
itemNs: $itemNs
|
||||
sortBy: $sortBy
|
||||
sortDir: $sortDir
|
||||
releaseDate: $releaseDate
|
||||
start: $start
|
||||
tag: $tag
|
||||
) {
|
||||
elements {
|
||||
title
|
||||
id
|
||||
namespace
|
||||
description
|
||||
effectiveDate
|
||||
keyImages {
|
||||
type
|
||||
url
|
||||
}
|
||||
seller {
|
||||
id
|
||||
name
|
||||
}
|
||||
productSlug
|
||||
urlSlug
|
||||
url
|
||||
tags {
|
||||
id
|
||||
}
|
||||
items {
|
||||
id
|
||||
namespace
|
||||
}
|
||||
customAttributes {
|
||||
key
|
||||
value
|
||||
}
|
||||
categories {
|
||||
path
|
||||
}
|
||||
price(country: $country) @include(if: $withPrice) {
|
||||
totalPrice {
|
||||
discountPrice
|
||||
originalPrice
|
||||
voucherDiscount
|
||||
discount
|
||||
currencyCode
|
||||
currencyInfo {
|
||||
decimals
|
||||
}
|
||||
fmtPrice(locale: $locale) {
|
||||
originalPrice
|
||||
discountPrice
|
||||
intermediatePrice
|
||||
}
|
||||
}
|
||||
lineOffers {
|
||||
appliedRules {
|
||||
id
|
||||
endDate
|
||||
discountSetting {
|
||||
discountType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
promotions(category: $category) @include(if: $withPromotions) {
|
||||
promotionalOffers {
|
||||
promotionalOffers {
|
||||
startDate
|
||||
endDate
|
||||
discountSetting {
|
||||
discountType
|
||||
discountPercentage
|
||||
}
|
||||
}
|
||||
}
|
||||
upcomingPromotionalOffers {
|
||||
promotionalOffers {
|
||||
startDate
|
||||
endDate
|
||||
discountSetting {
|
||||
discountType
|
||||
discountPercentage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
paging {
|
||||
count
|
||||
total
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
29
rare/components/tabs/store/api/debug.py
Normal file
29
rare/components/tabs/store/api/debug.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QTreeView, QDialog, QVBoxLayout
|
||||
|
||||
from utils.json_formatter import QJsonModel
|
||||
|
||||
|
||||
class DebugView(QTreeView):
|
||||
def __init__(self, data, parent=None):
|
||||
super(DebugView, self).__init__(parent=parent)
|
||||
self.setColumnWidth(0, 300)
|
||||
self.setWordWrap(True)
|
||||
self.model = QJsonModel(self)
|
||||
self.setModel(self.model)
|
||||
self.setContextMenuPolicy(Qt.ActionsContextMenu)
|
||||
try:
|
||||
self.model.load(data)
|
||||
except Exception as e:
|
||||
pass
|
||||
self.resizeColumnToContents(0)
|
||||
|
||||
|
||||
class DebugDialog(QDialog):
|
||||
def __init__(self, data, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self.resize(800, 600)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
view = DebugView(data, self)
|
||||
layout.addWidget(view)
|
15
rare/components/tabs/store/api/graphql/.graphqlconfig
Normal file
15
rare/components/tabs/store/api/graphql/.graphqlconfig
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "EGS GraphQL Schema",
|
||||
"schemaPath": "schema.graphql",
|
||||
"extensions": {
|
||||
"endpoints": {
|
||||
"Default GraphQL Endpoint": {
|
||||
"url": "http://localhost:8080/graphql",
|
||||
"headers": {
|
||||
"user-agent": "JS GraphQL"
|
||||
},
|
||||
"introspect": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
rare/components/tabs/store/api/graphql/schema.graphql
Normal file
41
rare/components/tabs/store/api/graphql/schema.graphql
Normal file
|
@ -0,0 +1,41 @@
|
|||
scalar Date
|
||||
|
||||
type Currency {
|
||||
decimals: Int
|
||||
symbol: String
|
||||
}
|
||||
|
||||
type FormattedPrice {
|
||||
originalPrice: String
|
||||
discountPrice: String
|
||||
intermediatePrice: String
|
||||
}
|
||||
|
||||
type TotalPrice {
|
||||
discountPrice: Int
|
||||
originalPrice: Int
|
||||
voucherDiscount: Int
|
||||
discount: Int
|
||||
currencyCode: String
|
||||
currencyInfo: Currency
|
||||
fmtPrice(locale: String): FormattedPrice
|
||||
}
|
||||
|
||||
type DiscountSetting {
|
||||
discountType: String
|
||||
}
|
||||
|
||||
type AppliedRuled {
|
||||
id: ID
|
||||
endDate: Date
|
||||
discountSetting: DiscountSetting
|
||||
}
|
||||
|
||||
type LineOfferRes {
|
||||
appliedRules: [AppliedRuled]
|
||||
}
|
||||
|
||||
type GetPriceRes {
|
||||
totalPrice: TotalPrice
|
||||
lineOffers: [LineOfferRes]
|
||||
}
|
0
rare/components/tabs/store/api/models/__init__.py
Normal file
0
rare/components/tabs/store/api/models/__init__.py
Normal file
164
rare/components/tabs/store/api/models/diesel.py
Normal file
164
rare/components/tabs/store/api/models/diesel.py
Normal file
|
@ -0,0 +1,164 @@
|
|||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Dict, Any, Type, Optional
|
||||
|
||||
logger = logging.getLogger("DieselModels")
|
||||
|
||||
# lk: Typing overloads for unimplemented types
|
||||
DieselSocialLinks = Dict
|
||||
|
||||
|
||||
@dataclass
|
||||
class DieselSystemDetailItem:
|
||||
p_type: Optional[str] = None
|
||||
minimum: Optional[str] = None
|
||||
recommended: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["DieselSystemDetailItem"], src: Dict[str, Any]) -> "DieselSystemDetailItem":
|
||||
d = src.copy()
|
||||
tmp = cls(
|
||||
p_type=d.pop("_type", ""),
|
||||
minimum=d.pop("minimum", ""),
|
||||
recommended=d.pop("recommended", ""),
|
||||
title=d.pop("title", ""),
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class DieselSystemDetail:
|
||||
p_type: Optional[str] = None
|
||||
details: Optional[List[DieselSystemDetailItem]] = None
|
||||
system_type: Optional[str] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["DieselSystemDetail"], src: Dict[str, Any]) -> "DieselSystemDetail":
|
||||
d = src.copy()
|
||||
_details = d.pop("details", [])
|
||||
details = [] if _details else None
|
||||
for item in _details:
|
||||
detail = DieselSystemDetailItem.from_dict(item)
|
||||
details.append(detail)
|
||||
tmp = cls(
|
||||
p_type=d.pop("_type", ""),
|
||||
details=details,
|
||||
system_type=d.pop("systemType", ""),
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class DieselSystemDetails:
|
||||
p_type: Optional[str] = None
|
||||
languages: Optional[List[str]] = None
|
||||
rating: Optional[Dict] = None
|
||||
systems: Optional[List[DieselSystemDetail]] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["DieselSystemDetails"], src: Dict[str, Any]) -> "DieselSystemDetails":
|
||||
d = src.copy()
|
||||
_systems = d.pop("systems", [])
|
||||
systems = [] if _systems else None
|
||||
for item in _systems:
|
||||
system = DieselSystemDetail.from_dict(item)
|
||||
systems.append(system)
|
||||
tmp = cls(
|
||||
p_type=d.pop("_type", ""),
|
||||
languages=d.pop("languages", []),
|
||||
rating=d.pop("rating", {}),
|
||||
systems=systems,
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class DieselProductAbout:
|
||||
p_type: Optional[str] = None
|
||||
desciption: Optional[str] = None
|
||||
developer_attribution: Optional[str] = None
|
||||
publisher_attribution: Optional[str] = None
|
||||
short_description: Optional[str] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["DieselProductAbout"], src: Dict[str, Any]) -> "DieselProductAbout":
|
||||
d = src.copy()
|
||||
tmp = cls(
|
||||
p_type=d.pop("_type", ""),
|
||||
desciption=d.pop("description", ""),
|
||||
developer_attribution=d.pop("developerAttribution", ""),
|
||||
publisher_attribution=d.pop("publisherAttribution", ""),
|
||||
short_description=d.pop("shortDescription", ""),
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class DieselProductDetail:
|
||||
p_type: Optional[str] = None
|
||||
about: Optional[DieselProductAbout] = None
|
||||
requirements: Optional[DieselSystemDetails] = None
|
||||
social_links: Optional[DieselSocialLinks] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["DieselProductDetail"], src: Dict[str, Any]) -> "DieselProductDetail":
|
||||
d = src.copy()
|
||||
about = DieselProductAbout.from_dict(x) if (x := d.pop("about"), {}) else None
|
||||
requirements = DieselSystemDetails.from_dict(x) if (x := d.pop("requirements", {})) else None
|
||||
tmp = cls(
|
||||
p_type=d.pop("_type", ""),
|
||||
about=about,
|
||||
requirements=requirements,
|
||||
social_links=d.pop("socialLinks", {}),
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class DieselProduct:
|
||||
p_id: Optional[str] = None
|
||||
p_images_: Optional[List[str]] = None
|
||||
p_locale: Optional[str] = None
|
||||
p_slug: Optional[str] = None
|
||||
p_title: Optional[str] = None
|
||||
p_url_pattern: Optional[str] = None
|
||||
namespace: Optional[str] = None
|
||||
pages: Optional[List["DieselProduct"]] = None
|
||||
data: Optional[DieselProductDetail] = None
|
||||
product_name: Optional[str] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["DieselProduct"], src: Dict[str, Any]) -> "DieselProduct":
|
||||
d = src.copy()
|
||||
_pages = d.pop("pages", [])
|
||||
pages = [] if _pages else None
|
||||
for item in _pages:
|
||||
page = DieselProduct.from_dict(item)
|
||||
pages.append(page)
|
||||
data = DieselProductDetail.from_dict(x) if (x := d.pop("data", {})) else None
|
||||
tmp = cls(
|
||||
p_id=d.pop("_id", ""),
|
||||
p_images_=d.pop("_images_", []),
|
||||
p_locale=d.pop("_locale", ""),
|
||||
p_slug=d.pop("_slug", ""),
|
||||
p_title=d.pop("_title", ""),
|
||||
p_url_pattern=d.pop("_urlPattern", ""),
|
||||
namespace=d.pop("namespace", ""),
|
||||
pages=pages,
|
||||
data=data,
|
||||
product_name=d.pop("productName", ""),
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
80
rare/components/tabs/store/api/models/query.py
Normal file
80
rare/components/tabs/store/api/models/query.py
Normal file
|
@ -0,0 +1,80 @@
|
|||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import List
|
||||
|
||||
|
||||
@dataclass
|
||||
class SearchDateRange:
|
||||
start_date: datetime = datetime(year=1990, month=1, day=1, tzinfo=timezone.utc)
|
||||
end_date: datetime = datetime.utcnow()
|
||||
|
||||
def __str__(self):
|
||||
def fmt_date(date: datetime) -> str:
|
||||
# lk: The formatting accepted by the GraphQL API is either '%Y-%m-%dT%H:%M:%S.000Z' or '%Y-%m-%dT'
|
||||
return datetime.strftime(date, '%Y-%m-%dT%H:%M:%S.000Z')
|
||||
return f"[{fmt_date(self.start_date)},{fmt_date(self.end_date)}]"
|
||||
|
||||
|
||||
@dataclass
|
||||
class SearchStoreQuery:
|
||||
country: str = "US"
|
||||
category: str = "games/edition/base|bundles/games|editors|software/edition/base"
|
||||
count: int = 30
|
||||
keywords: str = ""
|
||||
language: str = "en"
|
||||
namespace: str = ""
|
||||
with_mapping: bool = True
|
||||
item_ns: str = ""
|
||||
sort_by: str = "releaseDate"
|
||||
sort_dir: str = "DESC"
|
||||
start: int = 0
|
||||
tag: List[str] = ""
|
||||
release_date: SearchDateRange = field(default_factory=SearchDateRange)
|
||||
with_price: bool = True
|
||||
with_promotions: bool = True
|
||||
price_range: str = ""
|
||||
free_game: bool = None
|
||||
on_sale: bool = None
|
||||
effective_date: SearchDateRange = field(default_factory=SearchDateRange)
|
||||
|
||||
def __post_init__(self):
|
||||
self.locale = f"{self.language}-{self.country}"
|
||||
|
||||
def to_dict(self):
|
||||
payload = {
|
||||
"allowCountries": self.country,
|
||||
"category": self.category,
|
||||
"count": self.count,
|
||||
"country": self.country,
|
||||
"keywords": self.keywords,
|
||||
"locale": self.locale,
|
||||
"namespace": self.namespace,
|
||||
"withMapping": self.with_mapping,
|
||||
"itemNs": self.item_ns,
|
||||
"sortBy": self.sort_by,
|
||||
"sortDir": self.sort_dir,
|
||||
"start": self.start,
|
||||
"tag": self.tag,
|
||||
"releaseDate": str(self.release_date),
|
||||
"withPrice": self.with_price,
|
||||
"withPromotions": self.with_promotions,
|
||||
"priceRange": self.price_range,
|
||||
"freeGame": self.free_game,
|
||||
"onSale": self.on_sale,
|
||||
"effectiveDate": str(self.effective_date),
|
||||
}
|
||||
# payload.pop("withPromotions")
|
||||
payload.pop("onSale")
|
||||
if self.price_range == "free":
|
||||
payload["freeGame"] = True
|
||||
payload.pop("priceRange")
|
||||
elif self.price_range.startswith("<price>"):
|
||||
payload["priceRange"] = self.price_range.replace("<price>", "")
|
||||
if self.on_sale:
|
||||
payload["onSale"] = True
|
||||
|
||||
if self.price_range:
|
||||
payload["effectiveDate"] = self.effective_date
|
||||
else:
|
||||
payload.pop("priceRange")
|
||||
return payload
|
589
rare/components/tabs/store/api/models/response.py
Normal file
589
rare/components/tabs/store/api/models/response.py
Normal file
|
@ -0,0 +1,589 @@
|
|||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Dict, Any, Type, Optional
|
||||
|
||||
logger = logging.getLogger("StoreApiModels")
|
||||
|
||||
# lk: Typing overloads for unimplemented types
|
||||
DieselSocialLinks = Dict
|
||||
|
||||
CatalogNamespaceModel = Dict
|
||||
CategoryModel = Dict
|
||||
CustomAttributeModel = Dict
|
||||
ItemModel = Dict
|
||||
SellerModel = Dict
|
||||
OfferMappingModel = Dict
|
||||
TagModel = Dict
|
||||
PromotionsModel = Dict
|
||||
|
||||
|
||||
def parse_date(date: str):
|
||||
return datetime.fromisoformat(date[:-1]).replace(tzinfo=timezone.utc)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DieselSystemDetailItem:
|
||||
p_type: Optional[str] = None
|
||||
minimum: Optional[str] = None
|
||||
recommended: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["DieselSystemDetailItem"], src: Dict[str, Any]) -> "DieselSystemDetailItem":
|
||||
d = src.copy()
|
||||
tmp = cls(
|
||||
p_type=d.pop("_type", ""),
|
||||
minimum=d.pop("minimum", ""),
|
||||
recommended=d.pop("recommended", ""),
|
||||
title=d.pop("title", ""),
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class DieselSystemDetail:
|
||||
p_type: Optional[str] = None
|
||||
details: Optional[List[DieselSystemDetailItem]] = None
|
||||
system_type: Optional[str] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["DieselSystemDetail"], src: Dict[str, Any]) -> "DieselSystemDetail":
|
||||
d = src.copy()
|
||||
_details = d.pop("details", [])
|
||||
details = [] if _details else None
|
||||
for item in _details:
|
||||
detail = DieselSystemDetailItem.from_dict(item)
|
||||
details.append(detail)
|
||||
tmp = cls(
|
||||
p_type=d.pop("_type", ""),
|
||||
details=details,
|
||||
system_type=d.pop("systemType", ""),
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class DieselSystemDetails:
|
||||
p_type: Optional[str] = None
|
||||
languages: Optional[List[str]] = None
|
||||
rating: Optional[Dict] = None
|
||||
systems: Optional[List[DieselSystemDetail]] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["DieselSystemDetails"], src: Dict[str, Any]) -> "DieselSystemDetails":
|
||||
d = src.copy()
|
||||
_systems = d.pop("systems", [])
|
||||
systems = [] if _systems else None
|
||||
for item in _systems:
|
||||
system = DieselSystemDetail.from_dict(item)
|
||||
systems.append(system)
|
||||
tmp = cls(
|
||||
p_type=d.pop("_type", ""),
|
||||
languages=d.pop("languages", []),
|
||||
rating=d.pop("rating", {}),
|
||||
systems=systems,
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class DieselProductAbout:
|
||||
p_type: Optional[str] = None
|
||||
desciption: Optional[str] = None
|
||||
developer_attribution: Optional[str] = None
|
||||
publisher_attribution: Optional[str] = None
|
||||
short_description: Optional[str] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["DieselProductAbout"], src: Dict[str, Any]) -> "DieselProductAbout":
|
||||
d = src.copy()
|
||||
tmp = cls(
|
||||
p_type=d.pop("_type", ""),
|
||||
desciption=d.pop("description", ""),
|
||||
developer_attribution=d.pop("developerAttribution", ""),
|
||||
publisher_attribution=d.pop("publisherAttribution", ""),
|
||||
short_description=d.pop("shortDescription", ""),
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class DieselProductDetail:
|
||||
p_type: Optional[str] = None
|
||||
about: Optional[DieselProductAbout] = None
|
||||
requirements: Optional[DieselSystemDetails] = None
|
||||
social_links: Optional[DieselSocialLinks] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["DieselProductDetail"], src: Dict[str, Any]) -> "DieselProductDetail":
|
||||
d = src.copy()
|
||||
about = DieselProductAbout.from_dict(x) if (x := d.pop("about"), {}) else None
|
||||
requirements = DieselSystemDetails.from_dict(x) if (x := d.pop("requirements", {})) else None
|
||||
tmp = cls(
|
||||
p_type=d.pop("_type", ""),
|
||||
about=about,
|
||||
requirements=requirements,
|
||||
social_links=d.pop("socialLinks", {}),
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class DieselProduct:
|
||||
p_id: Optional[str] = None
|
||||
p_images_: Optional[List[str]] = None
|
||||
p_locale: Optional[str] = None
|
||||
p_slug: Optional[str] = None
|
||||
p_title: Optional[str] = None
|
||||
p_url_pattern: Optional[str] = None
|
||||
namespace: Optional[str] = None
|
||||
pages: Optional[List["DieselProduct"]] = None
|
||||
data: Optional[DieselProductDetail] = None
|
||||
product_name: Optional[str] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["DieselProduct"], src: Dict[str, Any]) -> "DieselProduct":
|
||||
d = src.copy()
|
||||
_pages = d.pop("pages", [])
|
||||
pages = [] if _pages else None
|
||||
for item in _pages:
|
||||
page = DieselProduct.from_dict(item)
|
||||
pages.append(page)
|
||||
data = DieselProductDetail.from_dict(x) if (x := d.pop("data", {})) else None
|
||||
tmp = cls(
|
||||
p_id=d.pop("_id", ""),
|
||||
p_images_=d.pop("_images_", []),
|
||||
p_locale=d.pop("_locale", ""),
|
||||
p_slug=d.pop("_slug", ""),
|
||||
p_title=d.pop("_title", ""),
|
||||
p_url_pattern=d.pop("_urlPattern", ""),
|
||||
namespace=d.pop("namespace", ""),
|
||||
pages=pages,
|
||||
data=data,
|
||||
product_name=d.pop("productName", ""),
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class ImageUrlModel:
|
||||
type: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
tmp: Dict[str, Any] = {}
|
||||
tmp.update({})
|
||||
if self.type is not None:
|
||||
tmp["type"] = self.type
|
||||
if self.url is not None:
|
||||
tmp["url"] = self.url
|
||||
return tmp
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["ImageUrlModel"], src: Dict[str, Any]) -> "ImageUrlModel":
|
||||
d = src.copy()
|
||||
type = d.pop("type", None)
|
||||
url = d.pop("url", None)
|
||||
tmp = cls(
|
||||
type=type,
|
||||
url=url,
|
||||
)
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class KeyImagesModel:
|
||||
key_images: Optional[List[ImageUrlModel]] = None
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.key_images[item]
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.key_images)
|
||||
|
||||
def to_list(self) -> List[Dict[str, Any]]:
|
||||
items: Optional[List[Dict[str, Any]]] = None
|
||||
if self.key_images is not None:
|
||||
items = []
|
||||
for image_url in self.key_images:
|
||||
item = image_url.to_dict()
|
||||
items.append(item)
|
||||
return items
|
||||
|
||||
@classmethod
|
||||
def from_list(cls: Type["KeyImagesModel"], src: List[Dict]):
|
||||
d = src.copy()
|
||||
key_images = []
|
||||
for item in d:
|
||||
image_url = ImageUrlModel.from_dict(item)
|
||||
key_images.append(image_url)
|
||||
tmp = cls(key_images)
|
||||
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))
|
||||
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))
|
||||
return wide_images
|
||||
|
||||
def for_dimensions(self, w: int, h: int) -> ImageUrlModel:
|
||||
try:
|
||||
if w > h:
|
||||
model = self.available_wide()[0]
|
||||
else:
|
||||
model = self.available_tall()[0]
|
||||
_ = model.url
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
logger.error(self.to_list())
|
||||
else:
|
||||
return model
|
||||
|
||||
|
||||
TotalPriceModel = Dict
|
||||
FmtPriceModel = Dict
|
||||
LineOffersModel = Dict
|
||||
|
||||
|
||||
@dataclass
|
||||
class PriceModel:
|
||||
total_price: Optional[TotalPriceModel] = None
|
||||
fmt_price: Optional[FmtPriceModel] = None
|
||||
line_offers: Optional[LineOffersModel] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["PriceModel"], src: Dict[str, Any]) -> "PriceModel":
|
||||
d = src.copy()
|
||||
tmp = cls(
|
||||
total_price=d.pop("totalPrice", {}),
|
||||
fmt_price=d.pop("fmtPrice", {}),
|
||||
line_offers=d.pop("lineOffers", {}),
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class CatalogOfferModel:
|
||||
catalog_ns: Optional[CatalogNamespaceModel] = None
|
||||
categories: Optional[List[CategoryModel]] = None
|
||||
custom_attributes: Optional[List[CustomAttributeModel]] = None
|
||||
description: Optional[str] = None
|
||||
effective_date: Optional[datetime] = None
|
||||
expiry_date: Optional[datetime] = None
|
||||
id: Optional[str] = None
|
||||
is_code_redemption_only: Optional[bool] = None
|
||||
items: Optional[List[ItemModel]] = None
|
||||
key_images: Optional[KeyImagesModel] = None
|
||||
namespace: Optional[str] = None
|
||||
offer_mappings: Optional[List[OfferMappingModel]] = None
|
||||
offer_type: Optional[str] = None
|
||||
price: Optional[PriceModel] = None
|
||||
product_slug: 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
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["CatalogOfferModel"], src: Dict[str, Any]) -> "CatalogOfferModel":
|
||||
d = src.copy()
|
||||
effective_date = parse_date(x) if (x := d.pop("effectiveDate", "")) else None
|
||||
expiry_date = parse_date(x) if (x := d.pop("expiryDate", "")) else None
|
||||
key_images = KeyImagesModel.from_list(d.pop("keyImages", []))
|
||||
price = PriceModel.from_dict(x) if (x := d.pop("price", {})) else None
|
||||
viewable_date = parse_date(x) if (x := d.pop("viewableDate", "")) else None
|
||||
tmp = cls(
|
||||
catalog_ns=d.pop("catalogNs", {}),
|
||||
categories=d.pop("categories", []),
|
||||
custom_attributes=d.pop("customAttributes", []),
|
||||
description=d.pop("description", ""),
|
||||
effective_date=effective_date,
|
||||
expiry_date=expiry_date,
|
||||
id=d.pop("id", ""),
|
||||
is_code_redemption_only=d.pop("isCodeRedemptionOnly", None),
|
||||
items=d.pop("items", []),
|
||||
key_images=key_images,
|
||||
namespace=d.pop("namespace", ""),
|
||||
offer_mappings=d.pop("offerMappings", []),
|
||||
offer_type=d.pop("offerType", ""),
|
||||
price=price,
|
||||
product_slug=d.pop("productSlug", ""),
|
||||
promotions=d.pop("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,
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
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
|
||||
order: Optional[Any] = None
|
||||
updated: Optional[datetime] = None
|
||||
offer: Optional[CatalogOfferModel] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["WishlistItemModel"], src: Dict[str, Any]) -> "WishlistItemModel":
|
||||
d = src.copy()
|
||||
created = parse_date(x) if (x := d.pop("created", "")) else None
|
||||
offer = CatalogOfferModel.from_dict(x) if (x := d.pop("offer", {})) else None
|
||||
updated = parse_date(x) if (x := d.pop("updated", "")) else None
|
||||
tmp = cls(
|
||||
created=created,
|
||||
id=d.pop("id", ""),
|
||||
namespace=d.pop("namespace", ""),
|
||||
is_first_time=d.pop("isFirstTime", None),
|
||||
offer_id=d.pop("offerId", ""),
|
||||
order=d.pop("order", ""),
|
||||
updated=updated,
|
||||
offer=offer,
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class PagingModel:
|
||||
count: Optional[int] = None
|
||||
total: Optional[int] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["PagingModel"], src: Dict[str, Any]) -> "PagingModel":
|
||||
d = src.copy()
|
||||
count = d.pop("count", None)
|
||||
total = d.pop("total", None)
|
||||
tmp = cls(
|
||||
count=count,
|
||||
total=total,
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class SearchStoreModel:
|
||||
elements: Optional[List[CatalogOfferModel]] = None
|
||||
paging: Optional[PagingModel] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["SearchStoreModel"], src: Dict[str, Any]) -> "SearchStoreModel":
|
||||
d = src.copy()
|
||||
_elements = d.pop("elements", [])
|
||||
elements = [] if _elements else None
|
||||
for item in _elements:
|
||||
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.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class CatalogModel:
|
||||
search_store: 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.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class WishlistItemsModel:
|
||||
elements: Optional[List[WishlistItemModel]] = None
|
||||
paging: Optional[PagingModel] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["WishlistItemsModel"], src: Dict[str, Any]) -> "WishlistItemsModel":
|
||||
d = src.copy()
|
||||
_elements = d.pop("elements", [])
|
||||
elements = [] if _elements else None
|
||||
for item in _elements:
|
||||
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.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class RemoveFromWishlistModel:
|
||||
success: Optional[bool] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["RemoveFromWishlistModel"], src: Dict[str, Any]) -> "RemoveFromWishlistModel":
|
||||
d = src.copy()
|
||||
tmp = cls(
|
||||
success=d.pop("success", None),
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class AddToWishlistModel:
|
||||
wishlist_item: Optional[WishlistItemModel] = None
|
||||
success: Optional[bool] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
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.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class WishlistModel:
|
||||
wishlist_items: Optional[WishlistItemsModel] = None
|
||||
remove_from_wishlist: Optional[RemoveFromWishlistModel] = None
|
||||
add_to_wishlist: 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
|
||||
)
|
||||
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,
|
||||
)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
ProductModel = Dict
|
||||
|
||||
|
||||
@dataclass
|
||||
class DataModel:
|
||||
product: Optional[ProductModel] = None
|
||||
catalog: Optional[CatalogModel] = None
|
||||
wishlist: Optional[WishlistModel] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["DataModel"], src: Dict[str, Any]) -> "DataModel":
|
||||
d = src.copy()
|
||||
catalog = CatalogModel.from_dict(x) if (x := d.pop("Catalog", {})) else None
|
||||
wishlist = WishlistModel.from_dict(x) if (x := d.pop("Wishlist", {})) else None
|
||||
tmp = cls(product=d.pop("Product", {}), catalog=catalog, wishlist=wishlist)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class ErrorModel:
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["ErrorModel"], src: Dict[str, Any]) -> "ErrorModel":
|
||||
d = src.copy()
|
||||
tmp = cls()
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExtensionsModel:
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["ExtensionsModel"], src: Dict[str, Any]) -> "ExtensionsModel":
|
||||
d = src.copy()
|
||||
tmp = cls()
|
||||
tmp.unmapped = d
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class ResponseModel:
|
||||
data: Optional[DataModel] = None
|
||||
errors: Optional[List[ErrorModel]] = None
|
||||
extensions: Optional[ExtensionsModel] = None
|
||||
unmapped: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type["ResponseModel"], src: Dict[str, Any]) -> "ResponseModel":
|
||||
d = src.copy()
|
||||
data = DataModel.from_dict(x) if (x := d.pop("data", {})) else None
|
||||
_errors = d.pop("errors", [])
|
||||
errors = [] if _errors else None
|
||||
for item in _errors:
|
||||
error = ErrorModel.from_dict(item)
|
||||
errors.append(error)
|
||||
extensions = ExtensionsModel.from_dict(x) if (x := d.pop("extensions", {})) else None
|
||||
tmp = cls(data=data, errors=errors, extensions=extensions)
|
||||
tmp.unmapped = d
|
||||
return tmp
|
5
rare/components/tabs/store/api/models/utils.py
Normal file
5
rare/components/tabs/store/api/models/utils.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
def parse_date(date: str):
|
||||
return datetime.fromisoformat(date[:-1]).replace(tzinfo=timezone.utc)
|
|
@ -44,8 +44,187 @@ class Constants(QObject):
|
|||
]
|
||||
|
||||
|
||||
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) {
|
||||
__Image = '''
|
||||
type
|
||||
url
|
||||
alt
|
||||
'''
|
||||
|
||||
__StorePageMapping = '''
|
||||
cmsSlug
|
||||
offerId
|
||||
prePurchaseOfferId
|
||||
'''
|
||||
|
||||
__PageSandboxModel = '''
|
||||
pageSlug
|
||||
pageType
|
||||
productId
|
||||
sandboxId
|
||||
createdDate
|
||||
updatedDate
|
||||
deletedDate
|
||||
mappings {
|
||||
%s
|
||||
}
|
||||
''' % (__StorePageMapping)
|
||||
|
||||
__CatalogNamespace = '''
|
||||
parent
|
||||
displayName
|
||||
store
|
||||
home: mappings(pageType: "productHome") {
|
||||
%s
|
||||
}
|
||||
addons: mappings(pageType: "addon--cms-hybrid") {
|
||||
%s
|
||||
}
|
||||
offers: mappings(pageType: "offer") {
|
||||
%s
|
||||
}
|
||||
''' % (__PageSandboxModel, __PageSandboxModel, __PageSandboxModel)
|
||||
|
||||
__CatalogItem = '''
|
||||
id
|
||||
namespace
|
||||
'''
|
||||
|
||||
__GetPriceRes = '''
|
||||
totalPrice {
|
||||
discountPrice
|
||||
originalPrice
|
||||
voucherDiscount
|
||||
discount
|
||||
currencyCode
|
||||
currencyInfo {
|
||||
decimals
|
||||
symbol
|
||||
}
|
||||
fmtPrice(locale: $locale) {
|
||||
originalPrice
|
||||
discountPrice
|
||||
intermediatePrice
|
||||
}
|
||||
}
|
||||
lineOffers {
|
||||
appliedRules {
|
||||
id
|
||||
endDate
|
||||
discountSetting {
|
||||
discountType
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
__Promotions = '''
|
||||
promotionalOffers {
|
||||
promotionalOffers {
|
||||
startDate
|
||||
endDate
|
||||
discountSetting {
|
||||
discountType
|
||||
discountPercentage
|
||||
}
|
||||
}
|
||||
}
|
||||
upcomingPromotionalOffers {
|
||||
promotionalOffers {
|
||||
startDate
|
||||
endDate
|
||||
discountSetting {
|
||||
discountType
|
||||
discountPercentage
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
__CatalogOffer = '''
|
||||
title
|
||||
id
|
||||
namespace
|
||||
offerType
|
||||
expiryDate
|
||||
status
|
||||
isCodeRedemptionOnly
|
||||
description
|
||||
effectiveDate
|
||||
keyImages {
|
||||
%(image)s
|
||||
}
|
||||
currentPrice
|
||||
seller {
|
||||
id
|
||||
name
|
||||
}
|
||||
productSlug
|
||||
urlSlug
|
||||
url
|
||||
tags {
|
||||
id
|
||||
name
|
||||
groupName
|
||||
}
|
||||
items {
|
||||
%(catalog_item)s
|
||||
}
|
||||
customAttributes {
|
||||
key
|
||||
value
|
||||
}
|
||||
categories {
|
||||
path
|
||||
}
|
||||
catalogNs @include(if: $withMapping) {
|
||||
%(catalog_namespace)s
|
||||
}
|
||||
offerMappings @include(if: $withMapping) {
|
||||
%(page_sandbox_model)s
|
||||
}
|
||||
price(country: $country) @include(if: $withPrice) {
|
||||
%(get_price_res)s
|
||||
}
|
||||
promotions(category: $category) @include(if: $withPromotions) {
|
||||
%(promotions)s
|
||||
}
|
||||
''' % {
|
||||
"image": __Image,
|
||||
"catalog_item": __CatalogItem,
|
||||
"catalog_namespace": __CatalogNamespace,
|
||||
"page_sandbox_model": __PageSandboxModel,
|
||||
"get_price_res": __GetPriceRes,
|
||||
"promotions": __Promotions,
|
||||
}
|
||||
|
||||
__Pagination = '''
|
||||
count
|
||||
total
|
||||
'''
|
||||
|
||||
SEARCH_STORE_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
|
||||
) {
|
||||
Catalog {
|
||||
searchStore(
|
||||
allowCountries: $allowCountries
|
||||
|
@ -67,452 +246,209 @@ query searchStoreQuery($allowCountries: String, $category: String, $count: Int,
|
|||
effectiveDate: $effectiveDate
|
||||
) {
|
||||
elements {
|
||||
title
|
||||
id
|
||||
namespace
|
||||
description
|
||||
effectiveDate
|
||||
keyImages {
|
||||
type
|
||||
url
|
||||
}
|
||||
currentPrice
|
||||
seller {
|
||||
id
|
||||
name
|
||||
}
|
||||
productSlug
|
||||
urlSlug
|
||||
url
|
||||
tags {
|
||||
id
|
||||
}
|
||||
items {
|
||||
id
|
||||
namespace
|
||||
}
|
||||
customAttributes {
|
||||
key
|
||||
value
|
||||
}
|
||||
categories {
|
||||
path
|
||||
}
|
||||
catalogNs @include(if: $withMapping) {
|
||||
mappings(pageType: "productHome") {
|
||||
pageSlug
|
||||
pageType
|
||||
}
|
||||
}
|
||||
offerMappings @include(if: $withMapping) {
|
||||
pageSlug
|
||||
pageType
|
||||
}
|
||||
price(country: $country) @include(if: $withPrice) {
|
||||
totalPrice {
|
||||
discountPrice
|
||||
originalPrice
|
||||
voucherDiscount
|
||||
discount
|
||||
currencyCode
|
||||
currencyInfo {
|
||||
decimals
|
||||
}
|
||||
fmtPrice(locale: $locale) {
|
||||
originalPrice
|
||||
discountPrice
|
||||
intermediatePrice
|
||||
}
|
||||
}
|
||||
lineOffers {
|
||||
appliedRules {
|
||||
id
|
||||
endDate
|
||||
discountSetting {
|
||||
discountType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
promotions(category: $category) @include(if: $withPromotions) {
|
||||
promotionalOffers {
|
||||
promotionalOffers {
|
||||
startDate
|
||||
endDate
|
||||
discountSetting {
|
||||
discountType
|
||||
discountPercentage
|
||||
}
|
||||
}
|
||||
}
|
||||
upcomingPromotionalOffers {
|
||||
promotionalOffers {
|
||||
startDate
|
||||
endDate
|
||||
discountSetting {
|
||||
discountType
|
||||
discountPercentage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
%s
|
||||
}
|
||||
paging {
|
||||
count
|
||||
total
|
||||
%s
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
''' % (__CatalogOffer, __Pagination)
|
||||
|
||||
search_query = """
|
||||
query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, $keywords: String, $locale: String, $namespace: String, $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) {
|
||||
Catalog {
|
||||
searchStore(
|
||||
allowCountries: $allowCountries
|
||||
category: $category
|
||||
count: $count
|
||||
country: $country
|
||||
keywords: $keywords
|
||||
locale: $locale
|
||||
namespace: $namespace
|
||||
itemNs: $itemNs
|
||||
sortBy: $sortBy
|
||||
sortDir: $sortDir
|
||||
releaseDate: $releaseDate
|
||||
start: $start
|
||||
tag: $tag
|
||||
priceRange: $priceRange
|
||||
freeGame: $freeGame
|
||||
onSale: $onSale
|
||||
effectiveDate: $effectiveDate
|
||||
) {
|
||||
elements {
|
||||
title
|
||||
id
|
||||
namespace
|
||||
description
|
||||
effectiveDate
|
||||
keyImages {
|
||||
type
|
||||
url
|
||||
}
|
||||
currentPrice
|
||||
seller {
|
||||
id
|
||||
name
|
||||
}
|
||||
productSlug
|
||||
urlSlug
|
||||
url
|
||||
tags {
|
||||
id
|
||||
}
|
||||
items {
|
||||
id
|
||||
namespace
|
||||
}
|
||||
customAttributes {
|
||||
key
|
||||
value
|
||||
}
|
||||
categories {
|
||||
path
|
||||
}
|
||||
catalogNs {
|
||||
mappings(pageType: "productHome") {
|
||||
pageSlug
|
||||
pageType
|
||||
}
|
||||
}
|
||||
offerMappings {
|
||||
pageSlug
|
||||
pageType
|
||||
}
|
||||
price(country: $country) @include(if: $withPrice) {
|
||||
totalPrice {
|
||||
discountPrice
|
||||
originalPrice
|
||||
voucherDiscount
|
||||
discount
|
||||
currencyCode
|
||||
currencyInfo {
|
||||
decimals
|
||||
}
|
||||
fmtPrice(locale: $locale) {
|
||||
originalPrice
|
||||
discountPrice
|
||||
intermediatePrice
|
||||
}
|
||||
}
|
||||
lineOffers {
|
||||
appliedRules {
|
||||
id
|
||||
endDate
|
||||
discountSetting {
|
||||
discountType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
promotions(category: $category) @include(if: $withPromotions) {
|
||||
promotionalOffers {
|
||||
promotionalOffers {
|
||||
startDate
|
||||
endDate
|
||||
discountSetting {
|
||||
discountType
|
||||
discountPercentage
|
||||
}
|
||||
}
|
||||
}
|
||||
upcomingPromotionalOffers {
|
||||
promotionalOffers {
|
||||
startDate
|
||||
endDate
|
||||
discountSetting {
|
||||
discountType
|
||||
discountPercentage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
paging {
|
||||
count
|
||||
total
|
||||
}
|
||||
}
|
||||
}
|
||||
__WISHLIST_ITEM = '''
|
||||
id
|
||||
order
|
||||
created
|
||||
offerId
|
||||
updated
|
||||
namespace
|
||||
isFirstTime
|
||||
offer(locale: $locale) {
|
||||
%s
|
||||
}
|
||||
"""
|
||||
''' % __CatalogOffer
|
||||
|
||||
wishlist_query = """
|
||||
query wishlistQuery($country:String!, $locale:String) {
|
||||
WISHLIST_QUERY = '''
|
||||
query wishlistQuery(
|
||||
$country: String!
|
||||
$locale: String
|
||||
$category: String
|
||||
$withMapping: Boolean = false
|
||||
$withPrice: Boolean = false
|
||||
$withPromotions: Boolean = false
|
||||
) {
|
||||
Wishlist {
|
||||
wishlistItems {
|
||||
elements {
|
||||
id
|
||||
order
|
||||
created
|
||||
offerId
|
||||
updated
|
||||
namespace
|
||||
offer(locale: $locale) {
|
||||
productSlug
|
||||
urlSlug
|
||||
title
|
||||
id
|
||||
namespace
|
||||
offerType
|
||||
expiryDate
|
||||
status
|
||||
isCodeRedemptionOnly
|
||||
description
|
||||
effectiveDate
|
||||
keyImages {
|
||||
type
|
||||
url
|
||||
}
|
||||
seller {
|
||||
id
|
||||
name
|
||||
}
|
||||
productSlug
|
||||
urlSlug
|
||||
items {
|
||||
id
|
||||
namespace
|
||||
}
|
||||
customAttributes {
|
||||
key
|
||||
value
|
||||
}
|
||||
catalogNs {
|
||||
mappings(pageType: "productHome") {
|
||||
pageSlug
|
||||
pageType
|
||||
%s
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''' % __WISHLIST_ITEM
|
||||
|
||||
WISHLIST_ADD_QUERY = '''
|
||||
mutation addWishlistMutation(
|
||||
$namespace: String!
|
||||
$offerId: String!
|
||||
$country: String!
|
||||
$locale: String
|
||||
$category: String
|
||||
$withMapping: Boolean = false
|
||||
$withPrice: Boolean = false
|
||||
$withPromotions: Boolean = false
|
||||
) {
|
||||
Wishlist {
|
||||
addToWishlist(
|
||||
namespace: $namespace
|
||||
offerId: $offerId
|
||||
) {
|
||||
wishlistItem {
|
||||
%s
|
||||
}
|
||||
success
|
||||
}
|
||||
}
|
||||
}
|
||||
''' % __WISHLIST_ITEM
|
||||
|
||||
WISHLIST_REMOVE_QUERY = '''
|
||||
mutation removeFromWishlistMutation(
|
||||
$namespace: String!
|
||||
$offerId: String!
|
||||
$operation: RemoveOperation!
|
||||
) {
|
||||
Wishlist {
|
||||
removeFromWishlist(
|
||||
namespace: $namespace
|
||||
offerId: $offerId
|
||||
operation: $operation
|
||||
) {
|
||||
success
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
COUPONS_QUERY = '''
|
||||
query getCoupons(
|
||||
$currencyCountry: String!
|
||||
$identityId: String!
|
||||
$locale: String
|
||||
) {
|
||||
CodeRedemption {
|
||||
coupons(
|
||||
currencyCountry: $currencyCountry
|
||||
identityId: $identityId
|
||||
includeSalesEventInfo: true
|
||||
) {
|
||||
code
|
||||
codeStatus
|
||||
codeType
|
||||
consumptionMetadata {
|
||||
amountDisplay {
|
||||
amount
|
||||
currency
|
||||
placement
|
||||
symbol
|
||||
}
|
||||
minSalesPriceDisplay {
|
||||
amount
|
||||
currency
|
||||
placement
|
||||
symbol
|
||||
}
|
||||
}
|
||||
endDate
|
||||
namespace
|
||||
salesEvent(locale: $locale) {
|
||||
eventName
|
||||
eventSlug
|
||||
voucherImages {
|
||||
type
|
||||
url
|
||||
}
|
||||
voucherLink
|
||||
}
|
||||
startDate
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
STORE_CONFIG_QUERY = '''
|
||||
query getStoreConfig(
|
||||
$includeCriticReviews: Boolean = false
|
||||
$locale: String!
|
||||
$sandboxId: String!
|
||||
$templateId: String
|
||||
) {
|
||||
Product {
|
||||
sandbox(sandboxId: $sandboxId) {
|
||||
configuration(locale: $locale, templateId: $templateId) {
|
||||
... on StoreConfiguration {
|
||||
configs {
|
||||
shortDescription
|
||||
criticReviews @include(if: $includeCriticReviews) {
|
||||
openCritic
|
||||
}
|
||||
}
|
||||
offerMappings {
|
||||
pageSlug
|
||||
pageType
|
||||
}
|
||||
categories {
|
||||
path
|
||||
}
|
||||
price(country: $country) {
|
||||
totalPrice {
|
||||
discountPrice
|
||||
originalPrice
|
||||
voucherDiscount
|
||||
discount
|
||||
fmtPrice(locale: $locale) {
|
||||
originalPrice
|
||||
discountPrice
|
||||
intermediatePrice
|
||||
socialLinks {
|
||||
platform
|
||||
url
|
||||
}
|
||||
supportedAudio
|
||||
supportedText
|
||||
tags(locale: $locale) {
|
||||
id
|
||||
name
|
||||
groupName
|
||||
}
|
||||
technicalRequirements {
|
||||
macos {
|
||||
minimum
|
||||
recommended
|
||||
title
|
||||
}
|
||||
currencyCode
|
||||
currencyInfo {
|
||||
decimals
|
||||
symbol
|
||||
windows {
|
||||
minimum
|
||||
recommended
|
||||
title
|
||||
}
|
||||
}
|
||||
lineOffers {
|
||||
appliedRules {
|
||||
id
|
||||
endDate
|
||||
}
|
||||
}
|
||||
... on HomeConfiguration {
|
||||
configs {
|
||||
keyImages {
|
||||
... on KeyImage {
|
||||
type
|
||||
url
|
||||
alt
|
||||
}
|
||||
}
|
||||
longDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
add_to_wishlist_query = """
|
||||
mutation addWishlistMutation($namespace: String!, $offerId: String!, $country:String!, $locale:String) {
|
||||
Wishlist {
|
||||
addToWishlist(namespace: $namespace, offerId: $offerId) {
|
||||
wishlistItem {
|
||||
id,
|
||||
order,
|
||||
created,
|
||||
offerId,
|
||||
updated,
|
||||
namespace,
|
||||
isFirstTime
|
||||
offer {
|
||||
productSlug
|
||||
urlSlug
|
||||
title
|
||||
id
|
||||
namespace
|
||||
offerType
|
||||
expiryDate
|
||||
status
|
||||
isCodeRedemptionOnly
|
||||
description
|
||||
effectiveDate
|
||||
keyImages {
|
||||
type
|
||||
url
|
||||
}
|
||||
seller {
|
||||
id
|
||||
name
|
||||
}
|
||||
productSlug
|
||||
urlSlug
|
||||
items {
|
||||
id
|
||||
namespace
|
||||
}
|
||||
customAttributes {
|
||||
key
|
||||
value
|
||||
}
|
||||
catalogNs {
|
||||
mappings(pageType: "productHome") {
|
||||
pageSlug
|
||||
pageType
|
||||
}
|
||||
}
|
||||
offerMappings {
|
||||
pageSlug
|
||||
pageType
|
||||
}
|
||||
categories {
|
||||
path
|
||||
}
|
||||
price(country: $country) {
|
||||
totalPrice {
|
||||
discountPrice
|
||||
originalPrice
|
||||
voucherDiscount
|
||||
discount
|
||||
fmtPrice(locale: $locale) {
|
||||
originalPrice
|
||||
discountPrice
|
||||
intermediatePrice
|
||||
}
|
||||
currencyCode
|
||||
currencyInfo {
|
||||
decimals
|
||||
symbol
|
||||
}
|
||||
}
|
||||
lineOffers {
|
||||
appliedRules {
|
||||
id
|
||||
endDate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
success
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
remove_from_wishlist_query = """
|
||||
mutation removeFromWishlistMutation($namespace: String!, $offerId: String!, $operation: RemoveOperation!) {
|
||||
Wishlist {
|
||||
removeFromWishlist(namespace: $namespace, offerId: $offerId, operation: $operation) {
|
||||
success
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
coupon_query = """
|
||||
query getCoupons($currencyCountry: String!, $identityId: String!, $locale: String) {
|
||||
CodeRedemption {
|
||||
coupons(currencyCountry: $currencyCountry, identityId: $identityId, includeSalesEventInfo: true) {
|
||||
code
|
||||
codeStatus
|
||||
codeType
|
||||
consumptionMetadata {
|
||||
amountDisplay {
|
||||
amount
|
||||
currency
|
||||
placement
|
||||
symbol
|
||||
}
|
||||
minSalesPriceDisplay {
|
||||
amount
|
||||
currency
|
||||
placement
|
||||
symbol
|
||||
}
|
||||
}
|
||||
endDate
|
||||
namespace
|
||||
salesEvent(locale: $locale) {
|
||||
eventName
|
||||
eventSlug
|
||||
voucherImages {
|
||||
type
|
||||
url
|
||||
}
|
||||
voucherLink
|
||||
}
|
||||
startDate
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
'''
|
||||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# from sgqlc import introspection, codegen
|
||||
#
|
||||
# coupon = codegen.operation.parse_graphql(coupon_query)
|
||||
# codegen.schema.
|
||||
# print(coupon.)
|
||||
def compress_query(query: str) -> str:
|
||||
return query.replace(" ", "").replace("\n", " ")
|
||||
|
||||
|
||||
game_query = compress_query(SEARCH_STORE_QUERY)
|
||||
search_query = compress_query(SEARCH_STORE_QUERY)
|
||||
wishlist_query = compress_query(WISHLIST_QUERY)
|
||||
wishlist_add_query = compress_query(WISHLIST_ADD_QUERY)
|
||||
wishlist_remove_query = compress_query(WISHLIST_REMOVE_QUERY)
|
||||
coupons_query = compress_query(COUPONS_QUERY)
|
||||
store_config_query = compress_query(STORE_CONFIG_QUERY)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(SEARCH_STORE_QUERY)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import logging
|
||||
from pprint import pprint
|
||||
from typing import List
|
||||
|
||||
from PyQt5.QtCore import Qt, QUrl
|
||||
from PyQt5.QtGui import QFont, QDesktopServices, QFontMetrics
|
||||
|
@ -10,13 +12,14 @@ from PyQt5.QtWidgets import (
|
|||
QSizePolicy,
|
||||
)
|
||||
|
||||
from rare.components.tabs.store.shop_models import ShopGame
|
||||
from rare.components.tabs.store.api.models.response import CatalogOfferModel, DieselProduct, DieselProductDetail
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from rare.shared.image_manager import ImageSize
|
||||
from rare.ui.components.tabs.store.shop_game_info import Ui_ShopInfo
|
||||
from rare.utils.misc import icon
|
||||
from rare.widgets.side_tab import SideTabWidget, SideTabContents
|
||||
from rare.widgets.elide_label import ElideLabel
|
||||
from .api.debug import DebugDialog
|
||||
from .image_widget import ShopImageWidget
|
||||
|
||||
logger = logging.getLogger("ShopInfo")
|
||||
|
@ -37,45 +40,46 @@ class ShopGameInfo(QWidget, SideTabContents):
|
|||
self.image.setFixedSize(ImageSize.Normal)
|
||||
self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignTop)
|
||||
|
||||
self.game: ShopGame = None
|
||||
self.offer: CatalogOfferModel = None
|
||||
self.data: dict = {}
|
||||
|
||||
self.ui.wishlist_button.clicked.connect(self.add_to_wishlist)
|
||||
self.ui.wishlist_button.setVisible(True)
|
||||
self.in_wishlist = False
|
||||
self.wishlist = []
|
||||
|
||||
self.requirements_tabs: SideTabWidget = SideTabWidget(parent=self.ui.requirements_group)
|
||||
self.requirements_tabs: SideTabWidget = SideTabWidget(parent=self.ui.requirements_frame)
|
||||
self.requirements_tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
self.ui.requirements_layout.addWidget(self.requirements_tabs)
|
||||
|
||||
self.setDisabled(True)
|
||||
|
||||
def handle_wishlist_update(self, data):
|
||||
if data and data[0] == "error":
|
||||
def handle_wishlist_update(self, wishlist: List[CatalogOfferModel]):
|
||||
if wishlist and wishlist[0] == "error":
|
||||
return
|
||||
self.wishlist = [i["offer"]["title"] for i in data]
|
||||
if self.title_str in self.wishlist:
|
||||
self.wishlist = [game.id for game in wishlist]
|
||||
if self.id_str in self.wishlist:
|
||||
self.in_wishlist = True
|
||||
self.ui.wishlist_button.setVisible(True)
|
||||
self.ui.wishlist_button.setText(self.tr("Remove from Wishlist"))
|
||||
else:
|
||||
self.in_wishlist = False
|
||||
self.ui.wishlist_button.setVisible(False)
|
||||
|
||||
def update_game(self, data: dict):
|
||||
self.set_title.emit(data["title"])
|
||||
self.ui.title.setText(data["title"])
|
||||
self.title_str = data["title"]
|
||||
self.id_str = data["id"]
|
||||
def update_game(self, offer: CatalogOfferModel):
|
||||
debug = DebugDialog(offer.__dict__, None)
|
||||
debug.exec()
|
||||
self.set_title.emit(offer.title)
|
||||
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)
|
||||
# lk: delete tabs in inverse order because indices are updated on deletion
|
||||
# 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 = data["productSlug"]
|
||||
slug = offer.product_slug
|
||||
if not slug:
|
||||
for mapping in data["offerMappings"]:
|
||||
for mapping in offer.offer_mappings:
|
||||
if mapping["pageType"] == "productHome":
|
||||
slug = mapping["pageSlug"]
|
||||
break
|
||||
|
@ -86,7 +90,7 @@ class ShopGameInfo(QWidget, SideTabContents):
|
|||
slug = slug.replace("/home", "")
|
||||
self.slug = slug
|
||||
|
||||
if data["namespace"] in self.installed:
|
||||
if offer.namespace in self.installed:
|
||||
self.ui.open_store_button.setText(self.tr("Show Game on Epic Page"))
|
||||
self.ui.owned_label.setVisible(True)
|
||||
else:
|
||||
|
@ -94,39 +98,44 @@ class ShopGameInfo(QWidget, SideTabContents):
|
|||
self.ui.owned_label.setVisible(False)
|
||||
|
||||
self.ui.price.setText(self.tr("Loading"))
|
||||
self.ui.wishlist_button.setVisible(False)
|
||||
# self.title.setText(self.tr("Loading"))
|
||||
# self.image.setPixmap(QPixmap())
|
||||
self.data = data
|
||||
is_bundle = False
|
||||
for i in data["categories"]:
|
||||
for i in offer.categories:
|
||||
if "bundles" in i.get("path", ""):
|
||||
is_bundle = True
|
||||
|
||||
# init API request
|
||||
if slug:
|
||||
self.api_core.get_game(slug, is_bundle, self.data_received)
|
||||
self.api_core.get_game(offer.product_slug, is_bundle, self.data_received)
|
||||
# else:
|
||||
# self.data_received({})
|
||||
self.offer = offer
|
||||
|
||||
def add_to_wishlist(self):
|
||||
if not self.in_wishlist:
|
||||
return
|
||||
# self.api_core.add_to_wishlist(self.game.namespace, self.game.offer_id,
|
||||
# lambda success: self.wishlist_button.setText(self.tr("Remove from wishlist"))
|
||||
# if success else self.wishlist_button.setText("Something goes wrong"))
|
||||
self.api_core.add_to_wishlist(
|
||||
self.offer.namespace,
|
||||
self.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.game.namespace,
|
||||
self.game.offer_id,
|
||||
lambda success: self.ui.wishlist_button.setVisible(False)
|
||||
self.offer.namespace,
|
||||
self.offer.id,
|
||||
lambda success: self.ui.wishlist_button.setText(self.tr("Add to wishlist"))
|
||||
if success
|
||||
else self.ui.wishlist_button.setText("Something goes wrong"),
|
||||
else self.ui.wishlist_button.setText("Something went wrong"),
|
||||
)
|
||||
|
||||
def data_received(self, game):
|
||||
def data_received(self, product: DieselProduct):
|
||||
try:
|
||||
self.game = ShopGame.from_json(game, self.data)
|
||||
if product.pages:
|
||||
product_data: DieselProductDetail = product.pages[0].data
|
||||
else:
|
||||
product_data: DieselProductDetail = product.data
|
||||
except Exception as e:
|
||||
raise e
|
||||
logger.error(str(e))
|
||||
|
@ -150,17 +159,19 @@ class ShopGameInfo(QWidget, SideTabContents):
|
|||
# self.title.setText(self.game.title)
|
||||
|
||||
self.ui.price.setFont(QFont())
|
||||
if self.game.price == "0" or self.game.price == 0:
|
||||
price = self.offer.price.total_price["fmtPrice"]["originalPrice"]
|
||||
discount_price = self.offer.price.total_price["fmtPrice"]["discountPrice"]
|
||||
if price == "0" or price == 0:
|
||||
self.ui.price.setText(self.tr("Free"))
|
||||
else:
|
||||
self.ui.price.setText(self.game.price)
|
||||
if self.game.price != self.game.discount_price:
|
||||
self.ui.price.setText(price)
|
||||
if price != discount_price:
|
||||
font = QFont()
|
||||
font.setStrikeOut(True)
|
||||
self.ui.price.setFont(font)
|
||||
self.ui.discount_price.setText(
|
||||
self.game.discount_price
|
||||
if self.game.discount_price != "0"
|
||||
discount_price
|
||||
if discount_price != "0"
|
||||
else self.tr("Free")
|
||||
)
|
||||
self.ui.discount_price.setVisible(True)
|
||||
|
@ -171,8 +182,9 @@ class ShopGameInfo(QWidget, SideTabContents):
|
|||
bold_font.setBold(True)
|
||||
|
||||
fm = QFontMetrics(self.font())
|
||||
if self.game.reqs:
|
||||
for system in self.game.reqs:
|
||||
requirements = product_data.requirements
|
||||
if requirements and requirements.systems:
|
||||
for system in requirements.systems:
|
||||
req_widget = QWidget(self.requirements_tabs)
|
||||
req_layout = QGridLayout(req_widget)
|
||||
req_widget.layout().setAlignment(Qt.AlignTop)
|
||||
|
@ -185,53 +197,57 @@ class ShopGameInfo(QWidget, SideTabContents):
|
|||
req_layout.addWidget(rec_label, 0, 2)
|
||||
req_layout.setColumnStretch(1, 2)
|
||||
req_layout.setColumnStretch(2, 2)
|
||||
for i, (key, value) in enumerate(self.game.reqs.get(system, {}).items()):
|
||||
req_layout.addWidget(QLabel(key, parent=req_widget), i + 1, 0)
|
||||
min_label = ElideLabel(value[0], parent=req_widget)
|
||||
for i, detail in enumerate(system.details):
|
||||
req_layout.addWidget(QLabel(detail.title, parent=req_widget), i + 1, 0)
|
||||
min_label = ElideLabel(detail.minimum, parent=req_widget)
|
||||
req_layout.addWidget(min_label, i + 1, 1)
|
||||
rec_label = ElideLabel(value[1], parent=req_widget)
|
||||
rec_label = ElideLabel(detail.recommended, parent=req_widget)
|
||||
req_layout.addWidget(rec_label, i + 1, 2)
|
||||
self.requirements_tabs.addTab(req_widget, system)
|
||||
# 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.requirements_tabs.setEnabled(True)
|
||||
if self.game.image_urls.front_tall:
|
||||
img_url = self.game.image_urls.front_tall
|
||||
elif self.game.image_urls.offer_image_tall:
|
||||
img_url = self.game.image_urls.offer_image_tall
|
||||
elif self.game.image_urls.product_logo:
|
||||
img_url = self.game.image_urls.product_logo
|
||||
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)
|
||||
else:
|
||||
img_url = ""
|
||||
self.image.fetchPixmap(img_url)
|
||||
self.ui.requirements_frame.setVisible(False)
|
||||
|
||||
key_images = self.offer.key_images
|
||||
img_url = key_images.for_dimensions(self.image.size().width(), self.image.size().height())
|
||||
self.image.fetchPixmap(img_url.url)
|
||||
|
||||
# self.image_stack.setCurrentIndex(0)
|
||||
try:
|
||||
if isinstance(self.game.developer, list):
|
||||
self.ui.dev.setText(", ".join(self.game.developer))
|
||||
else:
|
||||
self.ui.dev.setText(self.game.developer)
|
||||
except KeyError:
|
||||
pass
|
||||
self.ui.tags.setText(", ".join(self.game.tags))
|
||||
about = product_data.about
|
||||
self.ui.description_label.setText(about.desciption)
|
||||
self.ui.dev.setText(about.developer_attribution)
|
||||
# try:
|
||||
# if isinstance(aboudeveloper, list):
|
||||
# self.ui.dev.setText(", ".join(self.game.developer))
|
||||
# else:
|
||||
# self.ui.dev.setText(self.game.developer)
|
||||
# except KeyError:
|
||||
# pass
|
||||
tags = product_data.unmapped["meta"].get("tags", [])
|
||||
self.ui.tags.setText(", ".join(tags))
|
||||
|
||||
# clear Layout
|
||||
for b in self.ui.social_group.findChildren(SocialButton, options=Qt.FindDirectChildrenOnly):
|
||||
self.ui.social_layout.removeWidget(b)
|
||||
b.deleteLater()
|
||||
|
||||
links = product_data.social_links
|
||||
link_count = 0
|
||||
for name, url in self.game.links:
|
||||
|
||||
if name.lower() == "homepage":
|
||||
for name, url in links.items():
|
||||
if name == "_type":
|
||||
continue
|
||||
name = name.replace("link", "").lower()
|
||||
if name == "homepage":
|
||||
icn = icon("mdi.web", "fa.search", scale_factor=1.5)
|
||||
else:
|
||||
try:
|
||||
icn = icon(f"mdi.{name.lower()}", f"fa.{name.lower()}", scale_factor=1.5)
|
||||
icn = icon(f"mdi.{name}", f"fa.{name}", scale_factor=1.5)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
continue
|
||||
|
@ -244,10 +260,10 @@ class ShopGameInfo(QWidget, SideTabContents):
|
|||
|
||||
self.setEnabled(True)
|
||||
|
||||
def add_wishlist_items(self, wishlist):
|
||||
wishlist = wishlist["data"]["Wishlist"]["wishlistItems"]["elements"]
|
||||
for game in wishlist:
|
||||
self.wishlist.append(game["offer"]["title"])
|
||||
# def add_wishlist_items(self, wishlist: List[CatalogGameModel]):
|
||||
# wishlist = wishlist["data"]["Wishlist"]["wishlistItems"]["elements"]
|
||||
# for game in wishlist:
|
||||
# self.wishlist.append(game["offer"]["title"])
|
||||
|
||||
def button_clicked(self):
|
||||
return
|
||||
|
|
|
@ -3,42 +3,44 @@ import logging
|
|||
from PyQt5.QtCore import pyqtSignal, Qt
|
||||
from PyQt5.QtGui import QMouseEvent
|
||||
from PyQt5.QtWidgets import QPushButton
|
||||
from orjson import orjson
|
||||
|
||||
from rare.components.tabs.store.shop_models import ImageUrlModel
|
||||
from rare.components.tabs.store.api.models.response import CatalogOfferModel
|
||||
from rare.shared.image_manager import ImageSize
|
||||
from rare.utils.misc import qta_icon
|
||||
from rare.utils.qt_requests import QtRequestManager
|
||||
from .api.debug import DebugDialog
|
||||
from .image_widget import ShopImageWidget
|
||||
|
||||
logger = logging.getLogger("GameWidgets")
|
||||
|
||||
|
||||
class GameWidget(ShopImageWidget):
|
||||
show_info = pyqtSignal(dict)
|
||||
show_info = pyqtSignal(CatalogOfferModel)
|
||||
|
||||
def __init__(self, manager: QtRequestManager, json_info=None, parent=None):
|
||||
def __init__(self, manager: QtRequestManager, catalog_game: CatalogOfferModel = None, parent=None):
|
||||
super(GameWidget, self).__init__(manager, parent=parent)
|
||||
self.setFixedSize(ImageSize.Wide)
|
||||
self.ui.setupUi(self)
|
||||
self.json_info = json_info
|
||||
if json_info:
|
||||
self.init_ui(json_info)
|
||||
self.catalog_game = catalog_game
|
||||
if catalog_game:
|
||||
self.init_ui(catalog_game)
|
||||
|
||||
def init_ui(self, json_info):
|
||||
if not json_info:
|
||||
def init_ui(self, game: CatalogOfferModel):
|
||||
if not game:
|
||||
self.ui.title_label.setText(self.tr("An error occurred"))
|
||||
return
|
||||
|
||||
self.ui.title_label.setText(json_info.get("title"))
|
||||
for attr in json_info["customAttributes"]:
|
||||
self.ui.title_label.setText(game.title)
|
||||
for attr in game.custom_attributes:
|
||||
if attr["key"] == "developerName":
|
||||
developer = attr["value"]
|
||||
break
|
||||
else:
|
||||
developer = json_info["seller"]["name"]
|
||||
developer = game.seller["name"]
|
||||
self.ui.developer_label.setText(developer)
|
||||
price = json_info["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
|
||||
discount_price = json_info["price"]["totalPrice"]["fmtPrice"]["discountPrice"]
|
||||
price = game.price.total_price["fmtPrice"]["originalPrice"]
|
||||
discount_price = game.price.total_price["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()
|
||||
|
@ -48,43 +50,48 @@ class GameWidget(ShopImageWidget):
|
|||
else:
|
||||
self.ui.discount_label.setVisible(False)
|
||||
|
||||
for c in r'<>?":|\/*':
|
||||
json_info["title"] = json_info["title"].replace(c, "")
|
||||
key_images = game.key_images
|
||||
self.fetchPixmap(key_images.for_dimensions(self.width(), self.height()).url)
|
||||
|
||||
for img in json_info["keyImages"]:
|
||||
if img["type"] in ["DieselStoreFrontWide", "OfferImageWide", "VaultClosed", "ProductLogo",]:
|
||||
if img["type"] == "VaultClosed" and json_info["title"] != "Mystery Game":
|
||||
continue
|
||||
self.fetchPixmap(img["url"])
|
||||
break
|
||||
else:
|
||||
logger.info(", ".join([img["type"] for img in json_info["keyImages"]]))
|
||||
# for img in json_info["keyImages"]:
|
||||
# if img["type"] in ["DieselStoreFrontWide", "OfferImageWide", "VaultClosed", "ProductLogo"]:
|
||||
# if img["type"] == "VaultClosed" and json_info["title"] != "Mystery Game":
|
||||
# continue
|
||||
# self.fetchPixmap(img["url"])
|
||||
# break
|
||||
# else:
|
||||
# logger.info(", ".join([img["type"] for img in json_info["keyImages"]]))
|
||||
|
||||
def mousePressEvent(self, a0: QMouseEvent) -> None:
|
||||
if a0.button() == Qt.LeftButton:
|
||||
a0.accept()
|
||||
self.show_info.emit(self.json_info)
|
||||
self.show_info.emit(self.catalog_game)
|
||||
if a0.button() == Qt.RightButton:
|
||||
a0.accept()
|
||||
print(self.catalog_game.__dict__)
|
||||
dialog = DebugDialog(self.catalog_game.__dict__, self)
|
||||
dialog.show()
|
||||
|
||||
|
||||
class WishlistWidget(ShopImageWidget):
|
||||
open_game = pyqtSignal(dict)
|
||||
delete_from_wishlist = pyqtSignal(dict)
|
||||
open_game = pyqtSignal(CatalogOfferModel)
|
||||
delete_from_wishlist = pyqtSignal(CatalogOfferModel)
|
||||
|
||||
def __init__(self, manager: QtRequestManager, game: dict, parent=None):
|
||||
def __init__(self, manager: QtRequestManager, catalog_game: CatalogOfferModel, parent=None):
|
||||
super(WishlistWidget, self).__init__(manager, parent=parent)
|
||||
self.setFixedSize(ImageSize.Wide)
|
||||
self.ui.setupUi(self)
|
||||
self.game = game
|
||||
for attr in game["customAttributes"]:
|
||||
self.game = catalog_game
|
||||
for attr in catalog_game.custom_attributes:
|
||||
if attr["key"] == "developerName":
|
||||
developer = attr["value"]
|
||||
break
|
||||
else:
|
||||
developer = game["seller"]["name"]
|
||||
original_price = game["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
|
||||
discount_price = game["price"]["totalPrice"]["fmtPrice"]["discountPrice"]
|
||||
developer = catalog_game.seller["name"]
|
||||
original_price = catalog_game.price.total_price["fmtPrice"]["originalPrice"]
|
||||
discount_price = catalog_game.price.total_price["fmtPrice"]["discountPrice"]
|
||||
|
||||
self.ui.title_label.setText(game.get("title"))
|
||||
self.ui.title_label.setText(catalog_game.title)
|
||||
self.ui.developer_label.setText(developer)
|
||||
self.ui.price_label.setText(f'{original_price if original_price != "0" else self.tr("Free")}')
|
||||
if original_price != discount_price:
|
||||
|
@ -94,11 +101,10 @@ class WishlistWidget(ShopImageWidget):
|
|||
self.ui.discount_label.setText(f'{discount_price if discount_price != "0" else self.tr("Free")}')
|
||||
else:
|
||||
self.ui.discount_label.setVisible(False)
|
||||
image_model = ImageUrlModel.from_json(game["keyImages"])
|
||||
url = image_model.front_wide
|
||||
if not url:
|
||||
url = image_model.offer_image_wide
|
||||
self.fetchPixmap(url)
|
||||
key_images = catalog_game.key_images
|
||||
self.fetchPixmap(
|
||||
key_images.for_dimensions(self.width(), self.height()).url
|
||||
)
|
||||
|
||||
self.delete_button = QPushButton(self)
|
||||
self.delete_button.setIcon(icon("mdi.delete", color="white"))
|
||||
|
@ -113,5 +119,7 @@ class WishlistWidget(ShopImageWidget):
|
|||
a0.accept()
|
||||
self.open_game.emit(self.game)
|
||||
# right
|
||||
elif a0.button() == Qt.RightButton:
|
||||
pass # self.showMenu(e)
|
||||
if a0.button() == Qt.RightButton:
|
||||
a0.accept()
|
||||
dialog = DebugDialog(self.game.__dict__, self)
|
||||
dialog.show()
|
||||
|
|
|
@ -13,9 +13,12 @@ from rare.widgets.flow_layout import FlowLayout
|
|||
from rare.widgets.side_tab import SideTabContents
|
||||
from .image_widget import ShopImageWidget
|
||||
|
||||
from .api.debug import DebugDialog
|
||||
from .api.models.response import CatalogOfferModel
|
||||
|
||||
|
||||
class SearchResults(QScrollArea, SideTabContents):
|
||||
show_info = pyqtSignal(dict)
|
||||
show_info = pyqtSignal(CatalogOfferModel)
|
||||
|
||||
def __init__(self, api_core, parent=None):
|
||||
super(SearchResults, self).__init__(parent=parent)
|
||||
|
@ -59,22 +62,20 @@ class SearchResults(QScrollArea, SideTabContents):
|
|||
|
||||
|
||||
class SearchResultItem(ShopImageWidget):
|
||||
show_info = pyqtSignal(dict)
|
||||
show_info = pyqtSignal(CatalogOfferModel)
|
||||
|
||||
def __init__(self, manager: QtRequestManager, result: dict, parent=None):
|
||||
def __init__(self, manager: QtRequestManager, catalog_game: CatalogOfferModel, parent=None):
|
||||
super(SearchResultItem, self).__init__(manager, parent=parent)
|
||||
self.setFixedSize(ImageSize.Normal)
|
||||
self.ui.setupUi(self)
|
||||
for img in result["keyImages"]:
|
||||
if img["type"] in ["DieselStoreFrontTall", "OfferImageTall", "Thumbnail", "ProductLogo"]:
|
||||
self.fetchPixmap(img["url"])
|
||||
break
|
||||
else:
|
||||
print("No image found")
|
||||
|
||||
self.ui.title_label.setText(result["title"])
|
||||
price = result["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
|
||||
discount_price = result["price"]["totalPrice"]["fmtPrice"]["discountPrice"]
|
||||
key_images = catalog_game.key_images
|
||||
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"]
|
||||
self.ui.price_label.setText(f'{price if price != "0" else self.tr("Free")}')
|
||||
if price != discount_price:
|
||||
font = self.ui.price_label.font()
|
||||
|
@ -84,9 +85,14 @@ class SearchResultItem(ShopImageWidget):
|
|||
else:
|
||||
self.ui.discount_label.setVisible(False)
|
||||
|
||||
self.res = result
|
||||
self.catalog_game = catalog_game
|
||||
|
||||
def mousePressEvent(self, a0: QMouseEvent) -> None:
|
||||
if a0.button() == Qt.LeftButton:
|
||||
a0.accept()
|
||||
self.show_info.emit(self.res)
|
||||
self.show_info.emit(self.catalog_game)
|
||||
if a0.button() == Qt.RightButton:
|
||||
a0.accept()
|
||||
dialog = DebugDialog(self.catalog_game.__dict__, self)
|
||||
dialog.show()
|
||||
|
||||
|
|
|
@ -1,21 +1,33 @@
|
|||
from logging import getLogger
|
||||
from typing import List, Callable
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QObject
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from rare.components.tabs.store.api.debug import DebugDialog
|
||||
from rare.components.tabs.store.constants import (
|
||||
wishlist_query,
|
||||
search_query,
|
||||
add_to_wishlist_query,
|
||||
remove_from_wishlist_query,
|
||||
wishlist_add_query,
|
||||
wishlist_remove_query,
|
||||
)
|
||||
from rare.components.tabs.store.shop_models import BrowseModel
|
||||
from rare.utils.paths import cache_dir
|
||||
from rare.utils.qt_requests import QtRequests
|
||||
from .api.models.query import SearchStoreQuery
|
||||
from .api.models.response import (
|
||||
DieselProduct,
|
||||
ResponseModel,
|
||||
CatalogOfferModel,
|
||||
)
|
||||
|
||||
logger = getLogger("ShopAPICore")
|
||||
graphql_url = "https://graphql.epicgames.com/graphql"
|
||||
|
||||
|
||||
DEBUG: Callable[[], bool] = lambda: "--debug" in QApplication.arguments()
|
||||
|
||||
|
||||
class ShopApiCore(QObject):
|
||||
update_wishlist = pyqtSignal()
|
||||
|
||||
|
@ -25,6 +37,7 @@ class ShopApiCore(QObject):
|
|||
self.language_code: str = language
|
||||
self.country_code: str = country
|
||||
self.locale = f"{self.language_code}-{self.country_code}"
|
||||
self.locale = "en-US"
|
||||
self.manager = QtRequests(parent=self)
|
||||
self.authed_manager = QtRequests(token=token, parent=self)
|
||||
self.cached_manager = QtRequests(cache=str(cache_dir().joinpath("store")), parent=self)
|
||||
|
@ -39,54 +52,67 @@ class ShopApiCore(QObject):
|
|||
"country": self.country_code,
|
||||
"allowCountries": self.country_code,
|
||||
}
|
||||
self.manager.get(url, lambda data: self._handle_free_games(data, handle_func), params=params)
|
||||
self.manager.get(url, lambda data: self.__handle_free_games(data, handle_func), params=params)
|
||||
|
||||
def _handle_free_games(self, data, handle_func):
|
||||
@staticmethod
|
||||
def __handle_free_games(data, handle_func):
|
||||
try:
|
||||
results: dict = data["data"]["Catalog"]["searchStore"]["elements"]
|
||||
except KeyError:
|
||||
response = ResponseModel.from_dict(data)
|
||||
results: List[CatalogOfferModel] = response.data.catalog.search_store.elements
|
||||
handle_func(results)
|
||||
except KeyError as e:
|
||||
if DEBUG():
|
||||
raise e
|
||||
logger.error("Free games Api request failed")
|
||||
handle_func(["error", "Key error"])
|
||||
return
|
||||
except Exception as e:
|
||||
if DEBUG():
|
||||
raise e
|
||||
logger.error(f"Free games Api request failed: {e}")
|
||||
handle_func(["error", e])
|
||||
return
|
||||
handle_func(results)
|
||||
|
||||
def get_wishlist(self, handle_func):
|
||||
self.authed_manager.post(
|
||||
graphql_url,
|
||||
lambda data: self._handle_wishlist(data, handle_func),
|
||||
lambda data: self.__handle_wishlist(data, handle_func),
|
||||
{
|
||||
"query": wishlist_query,
|
||||
"variables": {
|
||||
"country": self.country_code,
|
||||
"locale": self.locale,
|
||||
"withPrice": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
def _handle_wishlist(self, data, handle_func):
|
||||
@staticmethod
|
||||
def __handle_wishlist(data, handle_func):
|
||||
try:
|
||||
results: list = data["data"]["Wishlist"]["wishlistItems"]["elements"]
|
||||
except KeyError:
|
||||
response = ResponseModel.from_dict(data)
|
||||
if response.errors:
|
||||
logger.error(response.errors)
|
||||
handle_func(response.data.wishlist.wishlist_items.elements)
|
||||
except KeyError as e:
|
||||
if DEBUG():
|
||||
raise e
|
||||
logger.error("Free games Api request failed")
|
||||
handle_func(["error", "Key error"])
|
||||
return
|
||||
except Exception as e:
|
||||
if DEBUG():
|
||||
raise e
|
||||
logger.error(f"Free games Api request failed: {e}")
|
||||
handle_func(["error", e])
|
||||
return
|
||||
|
||||
handle_func(results)
|
||||
|
||||
def search_game(self, name, handle_func):
|
||||
def search_game(self, name, handler):
|
||||
payload = {
|
||||
"query": search_query,
|
||||
"variables": {
|
||||
"category": "games/edition/base|bundles/games|editors|software/edition/base",
|
||||
"count": 10,
|
||||
"count": 20,
|
||||
"country": self.country_code,
|
||||
"keywords": name,
|
||||
"locale": self.locale,
|
||||
|
@ -99,42 +125,56 @@ class ShopApiCore(QObject):
|
|||
},
|
||||
}
|
||||
|
||||
self.manager.post(graphql_url, lambda data: self._handle_search(data, handle_func), payload)
|
||||
self.manager.post(graphql_url, lambda data: self.__handle_search(data, handler), payload)
|
||||
|
||||
def _handle_search(self, data, handle_func):
|
||||
@staticmethod
|
||||
def __handle_search(data, handler):
|
||||
try:
|
||||
handle_func(data["data"]["Catalog"]["searchStore"]["elements"])
|
||||
response = ResponseModel.from_dict(data)
|
||||
handler(response.data.catalog.search_store.elements)
|
||||
except KeyError as e:
|
||||
logger.error(str(e))
|
||||
handle_func([])
|
||||
if DEBUG():
|
||||
raise e
|
||||
handler([])
|
||||
except Exception as e:
|
||||
logger.error(f"Search Api request failed: {e}")
|
||||
handle_func([])
|
||||
if DEBUG():
|
||||
raise e
|
||||
handler([])
|
||||
return
|
||||
|
||||
def browse_games(self, browse_model: BrowseModel, handle_func):
|
||||
def browse_games(self, browse_model: SearchStoreQuery, handle_func):
|
||||
if self.browse_active:
|
||||
self.next_browse_request = (browse_model, handle_func)
|
||||
return
|
||||
self.browse_active = True
|
||||
payload = {
|
||||
"query": search_query,
|
||||
"variables": browse_model.__dict__
|
||||
"variables": browse_model.to_dict()
|
||||
}
|
||||
self.manager.post(graphql_url, lambda data: self._handle_browse_games(data, handle_func), payload)
|
||||
debug = DebugDialog(payload["variables"], None)
|
||||
debug.exec()
|
||||
self.manager.post(graphql_url, lambda data: self.__handle_browse_games(data, handle_func), payload)
|
||||
|
||||
def _handle_browse_games(self, data, handle_func):
|
||||
def __handle_browse_games(self, data, handle_func):
|
||||
debug = DebugDialog(data, None)
|
||||
debug.exec()
|
||||
self.browse_active = False
|
||||
if data is None:
|
||||
data = {}
|
||||
if not self.next_browse_request:
|
||||
|
||||
try:
|
||||
handle_func(data["data"]["Catalog"]["searchStore"]["elements"])
|
||||
response = ResponseModel.from_dict(data)
|
||||
handle_func(response.data.catalog.search_store.elements)
|
||||
except KeyError as e:
|
||||
if DEBUG():
|
||||
raise e
|
||||
logger.error(str(e))
|
||||
handle_func([])
|
||||
except Exception as e:
|
||||
if DEBUG():
|
||||
raise e
|
||||
logger.error(f"Browse games Api request failed: {e}")
|
||||
handle_func([])
|
||||
return
|
||||
|
@ -143,62 +183,72 @@ class ShopApiCore(QObject):
|
|||
self.next_browse_request = tuple(())
|
||||
|
||||
def get_game(self, slug: str, is_bundle: bool, handle_func):
|
||||
url = f"https://store-content.ak.epicgames.com/api/{self.locale}/content/{'products' if not is_bundle else 'bundles'}/{slug}"
|
||||
self.manager.get(url, lambda data: self._handle_get_game(data, handle_func))
|
||||
url = "https://store-content.ak.epicgames.com/api"
|
||||
url += f"/{self.locale}/content/{'products' if not is_bundle else 'bundles'}/{slug}"
|
||||
self.manager.get(url, lambda data: self.__handle_get_game(data, handle_func))
|
||||
|
||||
def _handle_get_game(self, data, handle_func):
|
||||
@staticmethod
|
||||
def __handle_get_game(data, handle_func):
|
||||
debug = DebugDialog(data, None)
|
||||
debug.exec()
|
||||
try:
|
||||
handle_func(data)
|
||||
product = DieselProduct.from_dict(data)
|
||||
handle_func(product)
|
||||
except Exception as e:
|
||||
raise e
|
||||
if DEBUG():
|
||||
raise e
|
||||
logger.error(str(e))
|
||||
# handle_func({})
|
||||
|
||||
# needs a captcha
|
||||
def add_to_wishlist(self, namespace, offer_id, handle_func: callable):
|
||||
payload = {
|
||||
"query": wishlist_add_query,
|
||||
"variables": {
|
||||
"offerId": offer_id,
|
||||
"namespace": namespace,
|
||||
"country": self.country_code,
|
||||
"locale": self.locale,
|
||||
},
|
||||
"query": add_to_wishlist_query,
|
||||
}
|
||||
self.authed_manager.post(graphql_url, lambda data: self._handle_add_to_wishlist(data, handle_func), payload)
|
||||
|
||||
def _handle_add_to_wishlist(self, data, handle_func):
|
||||
debug = DebugDialog(data, None)
|
||||
debug.exec()
|
||||
try:
|
||||
data = data["data"]["Wishlist"]["addToWishlist"]
|
||||
if data["success"]:
|
||||
handle_func(True)
|
||||
else:
|
||||
handle_func(False)
|
||||
response = ResponseModel.from_dict(data)
|
||||
data = response.data.wishlist.add_to_wishlist
|
||||
handle_func(data.success)
|
||||
except Exception as e:
|
||||
if DEBUG():
|
||||
raise e
|
||||
logger.error(str(e))
|
||||
handle_func(False)
|
||||
self.update_wishlist.emit()
|
||||
|
||||
def remove_from_wishlist(self, namespace, offer_id, handle_func: callable):
|
||||
payload = {
|
||||
"query": wishlist_remove_query,
|
||||
"variables": {
|
||||
"offerId": offer_id,
|
||||
"namespace": namespace,
|
||||
"operation": "REMOVE",
|
||||
},
|
||||
"query": remove_from_wishlist_query,
|
||||
}
|
||||
self.authed_manager.post(graphql_url, lambda data: self._handle_remove_from_wishlist(data, handle_func),
|
||||
payload)
|
||||
|
||||
def _handle_remove_from_wishlist(self, data, handle_func):
|
||||
debug = DebugDialog(data, None)
|
||||
debug.exec()
|
||||
try:
|
||||
data = data["data"]["Wishlist"]["removeFromWishlist"]
|
||||
if data["success"]:
|
||||
handle_func(True)
|
||||
else:
|
||||
handle_func(False)
|
||||
response = ResponseModel.from_dict(data)
|
||||
data = response.data.wishlist.remove_from_wishlist
|
||||
handle_func(data.success)
|
||||
except Exception as e:
|
||||
if DEBUG():
|
||||
raise e
|
||||
logger.error(str(e))
|
||||
handle_func(False)
|
||||
self.update_wishlist.emit()
|
||||
|
|
|
@ -1,195 +0,0 @@
|
|||
import datetime
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Dict
|
||||
import epicstore_api.queries as egs_query
|
||||
|
||||
|
||||
class ImageUrlModel:
|
||||
def __init__(
|
||||
self,
|
||||
front_tall: str = "",
|
||||
offer_image_tall: str = "",
|
||||
thumbnail: str = "",
|
||||
front_wide: str = "",
|
||||
offer_image_wide: str = "",
|
||||
product_logo: str = "",
|
||||
):
|
||||
self.front_tall = front_tall
|
||||
self.offer_image_tall = offer_image_tall
|
||||
self.thumbnail = thumbnail
|
||||
self.front_wide = front_wide
|
||||
self.offer_image_wide = offer_image_wide
|
||||
self.product_logo = product_logo
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json_data: list):
|
||||
tmp = cls()
|
||||
for item in json_data:
|
||||
if item["type"] == "Thumbnail":
|
||||
tmp.thumbnail = item["url"]
|
||||
elif item["type"] == "DieselStoreFrontTall":
|
||||
tmp.front_tall = item["url"]
|
||||
elif item["type"] == "DieselStoreFrontWide":
|
||||
tmp.front_wide = item["url"]
|
||||
elif item["type"] == "OfferImageTall":
|
||||
tmp.offer_image_tall = item["url"]
|
||||
elif item["type"] == "OfferImageWide":
|
||||
tmp.offer_image_wide = item["url"]
|
||||
elif item["type"] == "ProductLogo":
|
||||
tmp.product_logo = item["url"]
|
||||
return tmp
|
||||
|
||||
|
||||
class ShopGame:
|
||||
# TODO: Copyrights etc
|
||||
def __init__(
|
||||
self,
|
||||
title: str = "",
|
||||
id: str = "",
|
||||
image_urls: ImageUrlModel = None,
|
||||
social_links: Dict = None,
|
||||
langs: Dict = None,
|
||||
reqs: Dict = None,
|
||||
publisher: str = "",
|
||||
developer: str = "",
|
||||
original_price: str = "",
|
||||
discount_price: str = "",
|
||||
tags: List = None,
|
||||
namespace: str = "",
|
||||
offer_id: str = "",
|
||||
):
|
||||
self.title = title
|
||||
self.id = id
|
||||
self.image_urls = image_urls
|
||||
self.links = []
|
||||
if social_links:
|
||||
for item in social_links:
|
||||
if item.startswith("link"):
|
||||
self.links.append(
|
||||
tuple((item.replace("link", ""), social_links[item]))
|
||||
)
|
||||
else:
|
||||
self.links = []
|
||||
self.languages = langs if langs is not None else {}
|
||||
self.reqs = reqs if reqs is not None else {}
|
||||
self.publisher = publisher
|
||||
self.developer = developer
|
||||
self.price = original_price
|
||||
self.discount_price = discount_price
|
||||
self.tags = tags if tags is not None else []
|
||||
self.namespace = namespace
|
||||
self.offer_id = offer_id
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, api_data: dict, search_data: dict):
|
||||
if isinstance(api_data, list):
|
||||
for product in api_data:
|
||||
if product["_title"] == "home":
|
||||
api_data = product
|
||||
break
|
||||
if "pages" in api_data.keys():
|
||||
for page in api_data["pages"]:
|
||||
if page["_slug"] == "home":
|
||||
api_data = page
|
||||
break
|
||||
tmp = cls()
|
||||
if search_data:
|
||||
tmp.title = search_data.get("title", "Fail")
|
||||
tmp.id = search_data.get("id")
|
||||
tmp.image_urls = ImageUrlModel.from_json(search_data["keyImages"])
|
||||
if not tmp.developer:
|
||||
for i in search_data["customAttributes"]:
|
||||
if i["key"] == "developerName":
|
||||
tmp.developer = i["value"]
|
||||
tmp.price = search_data["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
|
||||
tmp.discount_price = search_data["price"]["totalPrice"]["fmtPrice"][
|
||||
"discountPrice"
|
||||
]
|
||||
tmp.namespace = search_data["namespace"]
|
||||
tmp.offer_id = search_data["id"]
|
||||
|
||||
if api_data:
|
||||
links = api_data["data"]["socialLinks"]
|
||||
tmp.links = []
|
||||
for item in links:
|
||||
if item.startswith("link"):
|
||||
tmp.links.append(tuple((item.replace("link", ""), links[item])))
|
||||
tmp.available_voice_langs = api_data["data"]["requirements"].get(
|
||||
"languages", "Failed"
|
||||
)
|
||||
tmp.reqs = {}
|
||||
for i, system in enumerate(api_data["data"]["requirements"].get("systems", [])):
|
||||
try:
|
||||
tmp.reqs[system["systemType"]] = {}
|
||||
except KeyError:
|
||||
continue
|
||||
for req in system["details"]:
|
||||
try:
|
||||
tmp.reqs[system["systemType"]][req["title"]] = (
|
||||
req["minimum"],
|
||||
req["recommended"],
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
tmp.publisher = api_data["data"]["meta"].get("publisher", "")
|
||||
tmp.developer = api_data["data"]["meta"].get("developer", "")
|
||||
tmp.tags = [
|
||||
i.replace("_", " ").capitalize()
|
||||
for i in api_data["data"]["meta"].get("tags", [])
|
||||
]
|
||||
|
||||
return tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class BrowseModel:
|
||||
category: str = "games/edition/base|bundles/games|editors|software/edition/base"
|
||||
count: int = 30
|
||||
language_code: str = "en"
|
||||
country_code: str = "US"
|
||||
keywords: str = ""
|
||||
sortDir: str = "DESC"
|
||||
start: int = 0
|
||||
tag: str = ""
|
||||
withMapping: bool = True
|
||||
withPrice: bool = True
|
||||
date: str = (
|
||||
f"[,{datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%dT%H:%M:%S')}.420Z]"
|
||||
)
|
||||
price: str = ""
|
||||
onSale: bool = False
|
||||
|
||||
def __post_init__(self):
|
||||
self.locale = f"{self.language_code}-{self.country_code}"
|
||||
|
||||
@property
|
||||
def __dict__(self):
|
||||
payload = {
|
||||
"count": self.count,
|
||||
"category": self.category,
|
||||
"allowCountries": self.country_code,
|
||||
"namespace": "",
|
||||
"sortBy": "releaseDate",
|
||||
"sortDir": self.sortDir,
|
||||
"start": self.start,
|
||||
"keywords": self.keywords,
|
||||
"tag": self.tag,
|
||||
"priceRange": self.price,
|
||||
"releaseDate": self.date,
|
||||
"withPrice": self.withPrice,
|
||||
"locale": self.locale,
|
||||
"country": self.country_code,
|
||||
}
|
||||
if self.price == "free":
|
||||
payload["freeGame"] = True
|
||||
payload.pop("priceRange")
|
||||
elif self.price.startswith("<price>"):
|
||||
payload["priceRange"] = self.price.replace("<price>", "")
|
||||
if self.onSale:
|
||||
payload["onSale"] = True
|
||||
|
||||
if self.price:
|
||||
payload["effectiveDate"] = self.date
|
||||
else:
|
||||
payload.pop("priceRange")
|
||||
return payload
|
|
@ -11,17 +11,18 @@ from PyQt5.QtWidgets import (
|
|||
QHBoxLayout,
|
||||
QWidget, QSizePolicy, QStackedLayout,
|
||||
)
|
||||
|
||||
from legendary.core import LegendaryCore
|
||||
|
||||
from rare.ui.components.tabs.store.store import Ui_ShopWidget
|
||||
from rare.utils.extra_widgets import ButtonLineEdit
|
||||
from rare.widgets.flow_layout import FlowLayout
|
||||
from rare.widgets.side_tab import SideTabContents
|
||||
from .api.models.query import SearchStoreQuery
|
||||
from .api.models.response import CatalogOfferModel, WishlistItemModel
|
||||
from .constants import Constants
|
||||
from .game_widgets import GameWidget
|
||||
from .image_widget import WaitingSpinner
|
||||
from .shop_api_core import ShopApiCore
|
||||
from .shop_models import BrowseModel
|
||||
|
||||
logger = logging.getLogger("Shop")
|
||||
|
||||
|
@ -102,8 +103,8 @@ class ShopWidget(QWidget, SideTabContents):
|
|||
def update_wishlist(self):
|
||||
self.api_core.get_wishlist(self.add_wishlist_items)
|
||||
|
||||
def add_wishlist_items(self, wishlist):
|
||||
for w in self.discounts_flow.findChildren(QGroupBox, options=Qt.FindDirectChildrenOnly):
|
||||
def add_wishlist_items(self, wishlist: List[WishlistItemModel]):
|
||||
for w in self.discounts_flow.findChildren(QWidget, options=Qt.FindDirectChildrenOnly):
|
||||
self.discounts_flow.layout().removeWidget(w)
|
||||
w.deleteLater()
|
||||
|
||||
|
@ -124,8 +125,8 @@ class ShopWidget(QWidget, SideTabContents):
|
|||
if not game:
|
||||
continue
|
||||
try:
|
||||
if game["offer"]["price"]["totalPrice"]["discount"] > 0:
|
||||
w = GameWidget(self.api_core.cached_manager, game["offer"])
|
||||
if game.offer.price.total_price["discount"] > 0:
|
||||
w = GameWidget(self.api_core.cached_manager, game.offer)
|
||||
w.show_info.connect(self.show_game)
|
||||
self.discounts_flow.layout().addWidget(w)
|
||||
discounts += 1
|
||||
|
@ -137,7 +138,7 @@ class ShopWidget(QWidget, SideTabContents):
|
|||
# FIXME: FlowLayout doesn't update on adding widget
|
||||
self.discounts_flow.layout().update()
|
||||
|
||||
def add_free_games(self, free_games: list):
|
||||
def add_free_games(self, free_games: List[CatalogOfferModel]):
|
||||
for w in self.ui.free_container.layout().findChildren(QGroupBox, options=Qt.FindDirectChildrenOnly):
|
||||
self.ui.free_container.layout().removeWidget(w)
|
||||
w.deleteLater()
|
||||
|
@ -174,14 +175,14 @@ class ShopWidget(QWidget, SideTabContents):
|
|||
for game in free_games:
|
||||
try:
|
||||
if (
|
||||
game["price"]["totalPrice"]["fmtPrice"]["discountPrice"] == "0"
|
||||
and game["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
|
||||
!= game["price"]["totalPrice"]["fmtPrice"]["discountPrice"]
|
||||
game.price.total_price["fmtPrice"]["discountPrice"] == "0"
|
||||
and game.price.total_price["fmtPrice"]["originalPrice"]
|
||||
!= game.price.total_price["fmtPrice"]["discountPrice"]
|
||||
):
|
||||
free_games_now.append(game)
|
||||
continue
|
||||
|
||||
if game["title"] == "Mystery Game":
|
||||
if game.title == "Mystery Game":
|
||||
coming_free_games.append(game)
|
||||
continue
|
||||
except KeyError as e:
|
||||
|
@ -191,7 +192,7 @@ class ShopWidget(QWidget, SideTabContents):
|
|||
# parse datetime to check if game is next week or now
|
||||
try:
|
||||
start_date = datetime.datetime.strptime(
|
||||
game["promotions"]["upcomingPromotionalOffers"][0][
|
||||
game.promotions["upcomingPromotionalOffers"][0][
|
||||
"promotionalOffers"
|
||||
][0]["startDate"],
|
||||
"%Y-%m-%dT%H:%M:%S.%fZ",
|
||||
|
@ -199,7 +200,7 @@ class ShopWidget(QWidget, SideTabContents):
|
|||
except Exception:
|
||||
try:
|
||||
start_date = datetime.datetime.strptime(
|
||||
game["promotions"]["promotionalOffers"][0][
|
||||
game.promotions["promotionalOffers"][0][
|
||||
"promotionalOffers"
|
||||
][0]["startDate"],
|
||||
"%Y-%m-%dT%H:%M:%S.%fZ",
|
||||
|
@ -230,7 +231,7 @@ class ShopWidget(QWidget, SideTabContents):
|
|||
# free games next week
|
||||
for free_game in coming_free_games:
|
||||
w = GameWidget(self.api_core.cached_manager, free_game)
|
||||
if free_game["title"] != "Mystery Game":
|
||||
if free_game.title != "Mystery Game":
|
||||
w.show_info.connect(self.show_game)
|
||||
self.free_games_next.layout().addWidget(w)
|
||||
# self.coming_free_games.setFixedWidth(int(40 + len(coming_free_games) * 300))
|
||||
|
@ -350,12 +351,12 @@ class ShopWidget(QWidget, SideTabContents):
|
|||
|
||||
self.games_layout.setCurrentWidget(self.games_spinner)
|
||||
|
||||
browse_model = BrowseModel(
|
||||
language_code=self.core.language_code,
|
||||
country_code=self.core.country_code,
|
||||
browse_model = SearchStoreQuery(
|
||||
language=self.core.language_code,
|
||||
country=self.core.country_code,
|
||||
count=20,
|
||||
price=self.price,
|
||||
onSale=self.ui.on_discount.isChecked(),
|
||||
price_range=self.price,
|
||||
on_sale=self.ui.on_discount.isChecked(),
|
||||
)
|
||||
browse_model.tag = "|".join(self.tags)
|
||||
|
||||
|
@ -364,14 +365,14 @@ class ShopWidget(QWidget, SideTabContents):
|
|||
self.api_core.browse_games(browse_model, self.show_games)
|
||||
|
||||
def show_games(self, data):
|
||||
for w in self.games_flow.layout().findChildren(GameWidget, options=Qt.FindDirectChildrenOnly):
|
||||
for w in self.games_flow.findChildren(QWidget, options=Qt.FindDirectChildrenOnly):
|
||||
self.games_flow.layout().removeWidget(w)
|
||||
w.deleteLater()
|
||||
|
||||
if data:
|
||||
for game in data:
|
||||
w = GameWidget(self.api_core.cached_manager, game)
|
||||
w.show_info.connect(self.show_game.emit)
|
||||
w.show_info.connect(self.show_game)
|
||||
self.games_flow.layout().addWidget(w)
|
||||
else:
|
||||
self.games_flow.layout().addWidget(
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from typing import List
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, Qt
|
||||
from PyQt5.QtWidgets import QMessageBox, QWidget
|
||||
|
||||
|
@ -7,10 +9,11 @@ from rare.widgets.side_tab import SideTabContents
|
|||
from rare.widgets.flow_layout import FlowLayout
|
||||
from .shop_api_core import ShopApiCore
|
||||
from .game_widgets import WishlistWidget
|
||||
from .api.models.response import WishlistItemModel, CatalogOfferModel
|
||||
|
||||
|
||||
class Wishlist(QWidget, SideTabContents):
|
||||
show_game_info = pyqtSignal(dict)
|
||||
show_game_info = pyqtSignal(CatalogOfferModel)
|
||||
update_wishlist_signal = pyqtSignal()
|
||||
|
||||
def __init__(self, api_core: ShopApiCore, parent=None):
|
||||
|
@ -38,10 +41,10 @@ class Wishlist(QWidget, SideTabContents):
|
|||
self.setEnabled(False)
|
||||
self.api_core.get_wishlist(self.set_wishlist)
|
||||
|
||||
def delete_from_wishlist(self, game):
|
||||
def delete_from_wishlist(self, game: CatalogOfferModel):
|
||||
self.api_core.remove_from_wishlist(
|
||||
game["namespace"],
|
||||
game["id"],
|
||||
game.namespace,
|
||||
game.id,
|
||||
lambda success: self.update_wishlist()
|
||||
if success
|
||||
else QMessageBox.warning(
|
||||
|
@ -73,27 +76,26 @@ class Wishlist(QWidget, SideTabContents):
|
|||
self.ui.list_container.layout().removeWidget(w)
|
||||
|
||||
if sort == 0:
|
||||
func = lambda x: x.game["title"]
|
||||
func = lambda x: x.game.title
|
||||
reverse = self.ui.reverse.isChecked()
|
||||
elif sort == 1:
|
||||
func = lambda x: x.game["price"]["totalPrice"]["fmtPrice"]["discountPrice"]
|
||||
func = lambda x: x.game.price.total_price["fmtPrice"]["discountPrice"]
|
||||
reverse = self.ui.reverse.isChecked()
|
||||
elif sort == 2:
|
||||
func = lambda x: x.game["seller"]["name"]
|
||||
func = lambda x: x.game.seller["name"]
|
||||
reverse = self.ui.reverse.isChecked()
|
||||
elif sort == 3:
|
||||
func = lambda x: 1 - (x.game["price"]["totalPrice"]["discountPrice"] / x.game["price"]["totalPrice"]["originalPrice"])
|
||||
func = lambda x: 1 - (x.game.price.total_price["discountPrice"] / x.game.price.total_price["originalPrice"])
|
||||
reverse = not self.ui.reverse.isChecked()
|
||||
else:
|
||||
func = lambda x: x.game["title"]
|
||||
func = lambda x: x.game.title
|
||||
reverse = self.ui.reverse.isChecked()
|
||||
|
||||
widgets = sorted(widgets, key=func, reverse=reverse)
|
||||
for w in widgets:
|
||||
self.ui.list_container.layout().addWidget(w)
|
||||
|
||||
|
||||
def set_wishlist(self, wishlist=None, sort=0):
|
||||
def set_wishlist(self, wishlist: List[WishlistItemModel] = None, sort=0):
|
||||
if wishlist and wishlist[0] == "error":
|
||||
return
|
||||
|
||||
|
@ -111,8 +113,8 @@ class Wishlist(QWidget, SideTabContents):
|
|||
self.ui.no_games_label.setVisible(False)
|
||||
|
||||
for game in wishlist:
|
||||
w = WishlistWidget(self.api_core.cached_manager, game["offer"], self.ui.list_container)
|
||||
w.open_game.connect(self.show_game_info.emit)
|
||||
w = WishlistWidget(self.api_core.cached_manager, game.offer, self.ui.list_container)
|
||||
w.open_game.connect(self.show_game_info)
|
||||
w.delete_from_wishlist.connect(self.delete_from_wishlist)
|
||||
self.widgets.append(w)
|
||||
self.list_layout.addWidget(w)
|
||||
|
|
|
@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
|||
class Ui_ShopInfo(object):
|
||||
def setupUi(self, ShopInfo):
|
||||
ShopInfo.setObjectName("ShopInfo")
|
||||
ShopInfo.resize(747, 442)
|
||||
ShopInfo.resize(443, 347)
|
||||
ShopInfo.setWindowTitle("ShopGameInfo")
|
||||
self.main_layout = QtWidgets.QHBoxLayout(ShopInfo)
|
||||
self.main_layout.setObjectName("main_layout")
|
||||
|
@ -146,19 +146,43 @@ class Ui_ShopInfo(object):
|
|||
self.button_layout.addWidget(self.wishlist_button)
|
||||
self.info_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.buttons_widget)
|
||||
self.right_layout.addLayout(self.info_layout)
|
||||
self.requirements_group = QtWidgets.QFrame(ShopInfo)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
self.requirements_frame = QtWidgets.QFrame(ShopInfo)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.requirements_group.sizePolicy().hasHeightForWidth())
|
||||
self.requirements_group.setSizePolicy(sizePolicy)
|
||||
self.requirements_group.setFrameShape(QtWidgets.QFrame.StyledPanel)
|
||||
self.requirements_group.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
self.requirements_group.setObjectName("requirements_group")
|
||||
self.requirements_layout = QtWidgets.QHBoxLayout(self.requirements_group)
|
||||
sizePolicy.setHeightForWidth(self.requirements_frame.sizePolicy().hasHeightForWidth())
|
||||
self.requirements_frame.setSizePolicy(sizePolicy)
|
||||
self.requirements_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
|
||||
self.requirements_frame.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
self.requirements_frame.setObjectName("requirements_frame")
|
||||
self.requirements_layout = QtWidgets.QHBoxLayout(self.requirements_frame)
|
||||
self.requirements_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.requirements_layout.setObjectName("requirements_layout")
|
||||
self.right_layout.addWidget(self.requirements_group)
|
||||
self.right_layout.addWidget(self.requirements_frame)
|
||||
self.description_group = QtWidgets.QGroupBox(ShopInfo)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.description_group.sizePolicy().hasHeightForWidth())
|
||||
self.description_group.setSizePolicy(sizePolicy)
|
||||
self.description_group.setFlat(False)
|
||||
self.description_group.setObjectName("description_group")
|
||||
self.description_layout = QtWidgets.QVBoxLayout(self.description_group)
|
||||
self.description_layout.setObjectName("description_layout")
|
||||
self.description_label = QtWidgets.QLabel(self.description_group)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.description_label.sizePolicy().hasHeightForWidth())
|
||||
self.description_label.setSizePolicy(sizePolicy)
|
||||
self.description_label.setText("error")
|
||||
self.description_label.setTextFormat(QtCore.Qt.MarkdownText)
|
||||
self.description_label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
|
||||
self.description_label.setWordWrap(True)
|
||||
self.description_label.setOpenExternalLinks(True)
|
||||
self.description_label.setObjectName("description_label")
|
||||
self.description_layout.addWidget(self.description_label)
|
||||
self.right_layout.addWidget(self.description_group)
|
||||
self.main_layout.addLayout(self.right_layout)
|
||||
self.main_layout.setStretch(1, 1)
|
||||
|
||||
|
@ -176,6 +200,7 @@ class Ui_ShopInfo(object):
|
|||
self.actions_label.setText(_translate("ShopInfo", "Actions"))
|
||||
self.open_store_button.setText(_translate("ShopInfo", "Buy in Epic Games Store"))
|
||||
self.wishlist_button.setText(_translate("ShopInfo", "Add to wishlist"))
|
||||
self.description_group.setTitle(_translate("ShopInfo", "Description"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>747</width>
|
||||
<height>442</height>
|
||||
<width>443</width>
|
||||
<height>347</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -309,9 +309,9 @@
|
|||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="requirements_group">
|
||||
<widget class="QFrame" name="requirements_frame">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
|
@ -338,6 +338,49 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="description_group">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Description</string>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="description_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="description_label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">error</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::MarkdownText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
|
|
@ -66,7 +66,7 @@ class QtRequests(QObject):
|
|||
|
||||
def __post(self, item: RequestQueueItem):
|
||||
request = self.__prepare_request(item)
|
||||
payload = orjson.dumps(item.payload) # pylint: disable=maybe-no-member
|
||||
payload = orjson.dumps(item.payload)
|
||||
reply = self.manager.post(request, payload)
|
||||
reply.errorOccurred.connect(self.__on_error)
|
||||
self.__active_requests[reply] = item
|
||||
|
|
|
@ -7,3 +7,4 @@ nuitka
|
|||
ordered-set
|
||||
PyQt5-stubs
|
||||
qstylizer
|
||||
|
||||
|
|
|
@ -13,3 +13,4 @@ pythonnet>=3.0.0rc4; platform_system == "Windows"
|
|||
cefpython3; platform_system == "Windows"
|
||||
pywebview[cef]; platform_system == "Windows"
|
||||
pypresence
|
||||
|
||||
|
|
|
@ -7,3 +7,4 @@ legendary-gl @ git+https://github.com/derrod/legendary@96e07ff ; platform_system
|
|||
orjson
|
||||
vdf; platform_system == "Linux" or platform_system == "FreeBSD"
|
||||
pywin32; platform_system == "Windows"
|
||||
|
||||
|
|
Loading…
Reference in a new issue