Skip to content

Commit

Permalink
Add new store fields and separate types
Browse files Browse the repository at this point in the history
  • Loading branch information
dolfies committed Jan 2, 2024
1 parent 297fcb0 commit c507bc2
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 41 deletions.
15 changes: 15 additions & 0 deletions discord/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
'ApplicationAssetType',
'SKUType',
'SKUAccessLevel',
'SKUProductLine',
'SKUFeature',
'SKUGenre',
'OperatingSystem',
Expand Down Expand Up @@ -1188,6 +1189,7 @@ class PaymentSourceType(Enum):
bancontact = 14
eps = 15
ideal = 16
cash_app = 17
payment_request = 99


Expand Down Expand Up @@ -1290,6 +1292,19 @@ def __int__(self) -> int:
return self.value


class SKUProductLine(Enum):
premium = 1
guild_premium = 2
iap = 3
guild_role = 4
guild_product = 5
application = 6
collectible = 7

def __int__(self) -> int:
return self.value


class SKUFeature(Enum):
single_player = 1
online_multiplayer = 2
Expand Down
17 changes: 17 additions & 0 deletions discord/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,23 @@ def application_user_subscription(self):
""":class:`bool`: Returns ``True`` if the SKU is a application subscription. These are subscriptions made to applications for premium perks bound to a user."""
return 1 << 8

@flag_value
def creator_monetization(self):
""":class:`bool`: Returns ``True`` if the SKU is a creator monetization product (e.g. guild role subscription, guild product).
.. versionadded:: 2.1
"""
# For some reason this is only actually present on products...
return 1 << 9

@flag_value
def guild_product(self):
""":class:`bool`: Returns ``True`` if the SKU is a guild product. These are one-time purchases made by guilds for premium perks.
.. versionadded:: 2.1
"""
return 1 << 10


@fill_with_flags()
class PaymentFlags(BaseFlags):
Expand Down
24 changes: 12 additions & 12 deletions discord/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -3090,7 +3090,7 @@ def get_app_skus(
payment_source_id: Optional[Snowflake] = None,
localize: bool = True,
with_bundled_skus: bool = True,
) -> Response[List[store.SKU]]:
) -> Response[List[store.PrivateSKU]]:
params = {}
if country_code:
params['country_code'] = country_code
Expand All @@ -3105,7 +3105,7 @@ def get_app_skus(
Route('GET', '/applications/{app_id}/skus', app_id=app_id), params=params, super_properties_to_track=True
)

def create_sku(self, payload: dict) -> Response[store.SKU]:
def create_sku(self, payload: dict) -> Response[store.PrivateSKU]:
return self.request(Route('POST', '/store/skus'), json=payload, super_properties_to_track=True)

def get_app_discoverability(self, app_id: Snowflake) -> Response[application.ApplicationDiscoverability]:
Expand Down Expand Up @@ -3507,7 +3507,7 @@ def get_store_listing(
country_code: Optional[str] = None,
payment_source_id: Optional[Snowflake] = None,
localize: bool = True,
) -> Response[store.StoreListing]:
) -> Response[store.PrivateStoreListing]:
params = {}
if country_code:
params['country_code'] = country_code
Expand All @@ -3525,7 +3525,7 @@ def get_store_listing_by_sku(
country_code: Optional[str] = None,
payment_source_id: Optional[Snowflake] = None,
localize: bool = True,
) -> Response[store.StoreListing]:
) -> Response[store.PublicStoreListing]:
params = {}
if country_code:
params['country_code'] = country_code
Expand All @@ -3543,7 +3543,7 @@ def get_sku_store_listings(
country_code: Optional[str] = None,
payment_source_id: Optional[int] = None,
localize: bool = True,
) -> Response[List[store.StoreListing]]:
) -> Response[List[store.PrivateStoreListing]]:
params = {}
if country_code:
params['country_code'] = country_code
Expand Down Expand Up @@ -3603,7 +3603,7 @@ def get_app_store_listings(
country_code: Optional[str] = None,
payment_source_id: Optional[int] = None,
localize: bool = True,
) -> Response[List[store.StoreListing]]:
) -> Response[List[store.PublicStoreListing]]:
params = {'application_id': app_id}
if country_code:
params['country_code'] = country_code
Expand All @@ -3621,7 +3621,7 @@ def get_app_store_listing(
country_code: Optional[str] = None,
payment_source_id: Optional[int] = None,
localize: bool = True,
) -> Response[store.StoreListing]:
) -> Response[store.PublicStoreListing]:
params = {}
if country_code:
params['country_code'] = country_code
Expand All @@ -3641,7 +3641,7 @@ def get_apps_store_listing(
country_code: Optional[str] = None,
payment_source_id: Optional[Snowflake] = None,
localize: bool = True,
) -> Response[List[store.StoreListing]]:
) -> Response[List[store.PublicStoreListing]]:
params: Dict[str, Any] = {'application_ids': app_ids}
if country_code:
params['country_code'] = country_code
Expand All @@ -3654,14 +3654,14 @@ def get_apps_store_listing(

def create_store_listing(
self, application_id: Snowflake, sku_id: Snowflake, payload: dict
) -> Response[store.StoreListing]:
) -> Response[store.PrivateStoreListing]:
return self.request(
Route('POST', '/store/listings'),
json={**payload, 'application_id': application_id, 'sku_id': sku_id},
super_properties_to_track=True,
)

def edit_store_listing(self, listing_id: Snowflake, payload: dict) -> Response[store.StoreListing]:
def edit_store_listing(self, listing_id: Snowflake, payload: dict) -> Response[store.PrivateStoreListing]:
return self.request(
Route('PATCH', '/store/listings/{listing_id}', listing_id=listing_id),
json=payload,
Expand All @@ -3675,7 +3675,7 @@ def get_sku(
country_code: Optional[str] = None,
payment_source_id: Optional[Snowflake] = None,
localize: bool = True,
) -> Response[store.SKU]:
) -> Response[store.PrivateSKU]:
params = {}
if country_code:
params['country_code'] = country_code
Expand All @@ -3686,7 +3686,7 @@ def get_sku(

return self.request(Route('GET', '/store/skus/{sku_id}', sku_id=sku_id), params=params)

def edit_sku(self, sku_id: Snowflake, payload: dict) -> Response[store.SKU]:
def edit_sku(self, sku_id: Snowflake, payload: dict) -> Response[store.PrivateSKU]:
return self.request(
Route('PATCH', '/store/skus/{sku_id}', sku_id=sku_id), json=payload, super_properties_to_track=True
)
Expand Down
74 changes: 69 additions & 5 deletions discord/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
SKUAccessLevel,
SKUFeature,
SKUGenre,
SKUProductLine,
SKUType,
SubscriptionInterval,
SubscriptionPlanPurchaseType,
Expand Down Expand Up @@ -76,6 +77,7 @@
SKU as SKUPayload,
CarouselItem as CarouselItemPayload,
ContentRating as ContentRatingPayload,
PremiumPrice as PremiumPricePayload,
SKUPrice as SKUPricePayload,
StoreListing as StoreListingPayload,
StoreNote as StoreNotePayload,
Expand Down Expand Up @@ -330,6 +332,8 @@ class SystemRequirements:
Any extra notes on recommended requirements.
"""

# I hate this class so much

if TYPE_CHECKING:
os: OperatingSystem
minimum_ram: Optional[int]
Expand Down Expand Up @@ -607,6 +611,19 @@ class StoreListing(Hashable):
The guild tied to this listing, if any.
published: :class:`bool`
Whether the listing is published and publicly visible.
published_at: Optional[:class:`datetime.datetime`]
When the listing was published, if available.
.. note::
This data is not available for all listings.
.. versionadded:: 2.1
unpublished_at: Optional[:class:`datetime.datetime`]
When the listing was last unpublished, if available.
If this is a future date, the listing will be unpublished at that time.
.. versionadded:: 2.1
staff_note: Optional[:class:`StoreNote`]
The staff note attached to this listing.
assets: List[:class:`StoreAsset`]
Expand Down Expand Up @@ -645,6 +662,8 @@ class StoreListing(Hashable):
'entitlement_branch_id',
'guild',
'published',
'published_at',
'unpublished_at',
'staff_note',
'assets',
'carousel_items',
Expand Down Expand Up @@ -693,6 +712,8 @@ def _update(self, data: StoreListingPayload, application: Optional[PartialApplic
self.entitlement_branch_id: Optional[int] = _get_as_snowflake(data, 'entitlement_branch_id')
self.guild: Optional[Guild] = state.create_guild(data['guild']) if 'guild' in data else None
self.published: bool = data.get('published', True)
self.published_at: Optional[datetime] = parse_time(data['published_at']) if 'published_at' in data else None
self.unpublished_at: Optional[datetime] = parse_time(data['unpublished_at']) if 'unpublished_at' in data else None
self.staff_note: Optional[StoreNote] = (
StoreNote(data=data['staff_notes'], state=state) if 'staff_notes' in data else None
)
Expand Down Expand Up @@ -902,29 +923,53 @@ class SKUPrice:
The price of the SKU with discounts applied, if any.
sale_percentage: :class:`int`
The percentage of the price discounted, if any.
exponent: :class:`int`
The offset of the currency's decimal point.
For example, if the price is 1000 and the exponent is 2, the price is $10.00.
.. versionadded:: 2.1
premium: Dict[:class:`PremiumType`, :class:`SKUPrice`]
Special SKU prices for premium (Nitro) users.
.. versionadded:: 2.1
"""

__slots__ = ('currency', 'amount', 'sale_amount', 'sale_percentage', 'premium', 'exponent')
__slots__ = ('currency', 'amount', 'sale_amount', 'sale_percentage', 'exponent', 'premium')

def __init__(self, data: Union[SKUPricePayload, SubscriptionPricePayload]) -> None:
self.currency: str = data.get('currency', 'usd')
self.amount: int = data.get('amount', 0)
self.sale_amount: Optional[int] = data.get('sale_amount')
self.sale_percentage: int = data.get('sale_percentage', 0)
self.premium = data.get('premium')
self.exponent: Optional[int] = data.get('exponent')
self.exponent: int = data.get('exponent', data.get('currency_exponent', 0))
self.premium: Dict[PremiumType, SKUPrice] = {
try_enum(PremiumType, premium_type): SKUPrice.from_premium(self, premium_data)
for premium_type, premium_data in data.get('premium', {}).items()
}

@classmethod
def from_private(cls, data: SKUPayload) -> SKUPrice:
payload: SKUPricePayload = {
'currency': 'usd',
'currency_exponent': 2,
'amount': data.get('price_tier') or 0,
'sale_amount': data.get('sale_price_tier'),
}
if payload['sale_amount'] is not None:
payload['sale_percentage'] = int((1 - (payload['sale_amount'] / payload['amount'])) * 100)
return cls(payload)

@classmethod
def from_premium(cls, parent: SKUPrice, data: PremiumPricePayload) -> SKUPrice:
payload: SKUPricePayload = {
'currency': parent.currency,
'currency_exponent': parent.exponent,
'amount': parent.amount,
'sale_amount': data.get('amount'),
'sale_percentage': data.get('percentage'),
}
return cls(payload)

def __repr__(self) -> str:
return f'<SKUPrice amount={self.amount} currency={self.currency!r}>'

Expand All @@ -939,8 +984,13 @@ def is_discounted(self) -> bool:
return self.sale_percentage > 0

def is_free(self) -> bool:
""":class:`bool`: Checks whether the SKU is free."""
return self.amount == 0
""":class:`bool`: Checks whether the SKU is free.
.. versionchanged:: 2.1
This now also checks the :attr:`sale_amount` to see if the SKU is free with discounts applied.
"""
return self.sale_amount == 0 or self.amount == 0

@property
def discounts(self) -> int:
Expand Down Expand Up @@ -1047,6 +1097,10 @@ class SKU(Hashable):
The legal notice of the SKU localized to different languages.
type: :class:`SKUType`
The type of the SKU.
product_line: Optional[:class:`SKUProductLine`]
The product line of the SKU, if any.
.. versionadded:: 2.1
slug: :class:`str`
The URL slug of the SKU.
dependent_sku_id: Optional[:class:`int`]
Expand Down Expand Up @@ -1098,6 +1152,10 @@ class SKU(Hashable):
Whether this SKU is restricted.
exclusive: :class:`bool`
Whether this SKU is exclusive to Discord.
deleted: :class:`bool`
Whether this SKU has been soft-deleted.
.. versionadded:: 2.1
show_age_gate: :class:`bool`
Whether the client should prompt the user to verify their age.
bundled_skus: List[:class:`SKU`]
Expand All @@ -1116,6 +1174,7 @@ class SKU(Hashable):
'legal_notice',
'legal_notice_localizations',
'type',
'product_line',
'slug',
'price_tier',
'price_overrides',
Expand All @@ -1139,6 +1198,7 @@ class SKU(Hashable):
'premium',
'restricted',
'exclusive',
'deleted',
'show_age_gate',
'bundled_skus',
'manifests',
Expand Down Expand Up @@ -1179,6 +1239,9 @@ def _update(self, data: SKUPayload) -> None:

self.id: int = int(data['id'])
self.type: SKUType = try_enum(SKUType, data['type'])
self.product_line: Optional[SKUProductLine] = (
try_enum(SKUProductLine, data['product_line']) if data.get('product_line') else None
)
self.slug: str = data['slug']
self.dependent_sku_id: Optional[int] = _get_as_snowflake(data, 'dependent_sku_id')
self.application_id: int = int(data['application_id'])
Expand Down Expand Up @@ -1233,6 +1296,7 @@ def _update(self, data: SKUPayload) -> None:
self.premium: bool = data.get('premium', False)
self.restricted: bool = data.get('restricted', False)
self.exclusive: bool = data.get('exclusive', False)
self.deleted: bool = data.get('deleted', False)
self.show_age_gate: bool = data.get('show_age_gate', False)
self.bundled_skus: List[SKU] = [
SKU(data=sku, state=state, application=self.application) for sku in data.get('bundled_skus', [])
Expand Down
Loading

0 comments on commit c507bc2

Please sign in to comment.