Browse Source
* Initial implementation * Expose various rich methods to fetch the new models * Add localize parameters and remove useless payment_source parameters from front-facing fetch methods * Implement fetching and (partially) redeeming gifts * Slot remaining models * Correctly document Gift.redeem() channel parameter * Implement some stuffs, fix more stuffs, add creating/editing skus/store listings * Various context properties fixes * Fix various types, expose SubscriptionPlan * (Partially) implement purchasing SKUs and gift flags * Finish and clean-up store/applications API implementations * Implement build uls, missing sub plan params, purchase sku ret * Fix upload_files() warning * Formatter pass * Normalize include_x to with_x, add various small missing things * Update sub on manual invoice payment instead of returning new object * Black pass * Implement missing integrations/applications API shit * Implement Application.store_listing_sku_id * Expose richer subscription metadata guild info * Implement SKU.system_requirements localization and modification * Black pass * Implement premium usage * Implement application whitelist * Implement active developer program enrollment * Readd new team members to cache * Polishing * Implement leaving active developer program * Type everything * Expose everything * Implement relationship activity statistics, improve model * Black pass * Document everything * Add crunchyroll connection type (#426) * Fix type-checking error in PrivateChannel ABC (#427) * Update required property fetching to new domain * Pin black to v22.6 * Get pyright to shut up * Black pass * Get pyright to shut uppull/10109/head
committed by
GitHub
53 changed files with 16670 additions and 1376 deletions
File diff suppressed because it is too large
@ -0,0 +1,381 @@ |
|||
""" |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2021-present Dolfies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a |
|||
copy of this software and associated documentation files (the "Software"), |
|||
to deal in the Software without restriction, including without limitation |
|||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|||
and/or sell copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|||
DEALINGS IN THE SOFTWARE. |
|||
""" |
|||
|
|||
from __future__ import annotations |
|||
|
|||
from datetime import datetime |
|||
from typing import TYPE_CHECKING, Optional, Union |
|||
|
|||
from .enums import ( |
|||
PaymentGateway, |
|||
PaymentSourceType, |
|||
try_enum, |
|||
) |
|||
from .flags import PaymentSourceFlags |
|||
from .mixins import Hashable |
|||
from .utils import MISSING |
|||
|
|||
if TYPE_CHECKING: |
|||
from datetime import date |
|||
from typing_extensions import Self |
|||
|
|||
from .state import ConnectionState |
|||
from .types.billing import ( |
|||
BillingAddress as BillingAddressPayload, |
|||
PartialPaymentSource as PartialPaymentSourcePayload, |
|||
PaymentSource as PaymentSourcePayload, |
|||
PremiumUsage as PremiumUsagePayload, |
|||
) |
|||
|
|||
__all__ = ( |
|||
'BillingAddress', |
|||
'PaymentSource', |
|||
'PremiumUsage', |
|||
) |
|||
|
|||
|
|||
class BillingAddress: |
|||
"""Represents a billing address. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two billing addresses are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two billing addresses are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the address' hash. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
name: :class:`str` |
|||
The payment source's name. |
|||
address: :class:`str` |
|||
The location's address. |
|||
postal_code: Optional[:class:`str`] |
|||
The location's postal code. |
|||
city: :class:`str` |
|||
The location's city. |
|||
state: Optional[:class:`str`] |
|||
The location's state or province. |
|||
country: :class:`str` |
|||
The location's country. |
|||
email: Optional[:class:`str`] |
|||
The email address associated with the payment source, if any. |
|||
""" |
|||
|
|||
__slots__ = ('_state', 'name', 'address', 'postal_code', 'city', 'state', 'country', 'email') |
|||
|
|||
def __init__( |
|||
self, |
|||
*, |
|||
name: str, |
|||
address: str, |
|||
city: str, |
|||
country: str, |
|||
state: Optional[str] = None, |
|||
postal_code: Optional[str] = None, |
|||
email: Optional[str] = None, |
|||
_state: Optional[ConnectionState] = None, |
|||
) -> None: |
|||
self._state = _state |
|||
|
|||
self.name = name |
|||
self.address = address |
|||
self.postal_code = postal_code |
|||
self.city = city |
|||
self.state = state |
|||
self.country = country |
|||
self.email = email |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<BillingAddress name={self.name!r} address={self.address!r} city={self.city!r} country={self.country!r}>' |
|||
|
|||
def __eq__(self, other: object) -> bool: |
|||
return isinstance(other, BillingAddress) and self.to_dict() == other.to_dict() |
|||
|
|||
def __ne__(self, other: object) -> bool: |
|||
if not isinstance(other, BillingAddress): |
|||
return True |
|||
return self.to_dict() != other.to_dict() |
|||
|
|||
def __hash__(self) -> int: |
|||
return hash(self.to_dict()) |
|||
|
|||
@classmethod |
|||
def from_dict(cls, data: BillingAddressPayload, state: ConnectionState) -> Self: |
|||
address = '\n'.join(filter(None, (data['line_1'], data.get('line_2')))) |
|||
return cls( |
|||
_state=state, |
|||
name=data['name'], |
|||
address=address, |
|||
postal_code=data.get('postal_code'), |
|||
city=data['city'], |
|||
state=data.get('state'), |
|||
country=data['country'], |
|||
email=data.get('email'), |
|||
) |
|||
|
|||
def to_dict(self) -> dict: |
|||
line1, _, line2 = self.address.partition('\n') |
|||
data = { |
|||
'name': self.name, |
|||
'line_1': line1, |
|||
'line_2': line2 or '', |
|||
'city': self.city, |
|||
'country': self.country, |
|||
} |
|||
if self.postal_code: |
|||
data['postal_code'] = self.postal_code |
|||
if self.state: |
|||
data['state'] = self.state |
|||
if self.email: |
|||
data['email'] = self.email |
|||
return data |
|||
|
|||
async def validate(self) -> str: |
|||
"""|coro| |
|||
|
|||
Validates the billing address. |
|||
|
|||
Raises |
|||
------ |
|||
TypeError |
|||
The billing address does not have state attached. |
|||
HTTPException |
|||
The billing address is invalid. |
|||
|
|||
Returns |
|||
------- |
|||
:class:`str` |
|||
The billing address token. |
|||
""" |
|||
if self._state is None: |
|||
raise TypeError('BillingAddress does not have state available') |
|||
|
|||
data = await self._state.http.validate_billing_address(self.to_dict()) |
|||
return data['token'] |
|||
|
|||
|
|||
class PaymentSource(Hashable): |
|||
"""Represents a payment source. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two payment sources are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two payment sources are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the source's hash. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
id: :class:`int` |
|||
The ID of the payment source. |
|||
brand: Optional[:class:`str`] |
|||
The brand of the payment source. This is only available for cards. |
|||
country: Optional[:class:`str`] |
|||
The country of the payment source. Not available in all contexts. |
|||
partial_card_number: Optional[:class:`str`] |
|||
The last four digits of the payment source. This is only available for cards. |
|||
billing_address: Optional[:class:`BillingAddress`] |
|||
The billing address of the payment source. Not available in all contexts. |
|||
type: :class:`PaymentSourceType` |
|||
The type of the payment source. |
|||
payment_gateway: :class:`PaymentGateway` |
|||
The payment gateway of the payment source. |
|||
default: :class:`bool` |
|||
Whether the payment source is the default payment source. |
|||
invalid: :class:`bool` |
|||
Whether the payment source is invalid. |
|||
expires_at: Optional[:class:`datetime.date`] |
|||
When the payment source expires. This is only available for cards. |
|||
email: Optional[:class:`str`] |
|||
The email address associated with the payment source, if any. |
|||
This is only available for PayPal. |
|||
bank: Optional[:class:`str`] |
|||
The bank associated with the payment source, if any. |
|||
This is only available for certain payment sources. |
|||
username: Optional[:class:`str`] |
|||
The username associated with the payment source, if any. |
|||
This is only available for Venmo. |
|||
""" |
|||
|
|||
__slots__ = ( |
|||
'_state', |
|||
'id', |
|||
'brand', |
|||
'country', |
|||
'partial_card_number', |
|||
'billing_address', |
|||
'type', |
|||
'payment_gateway', |
|||
'default', |
|||
'invalid', |
|||
'expires_at', |
|||
'email', |
|||
'bank', |
|||
'username', |
|||
'_flags', |
|||
) |
|||
|
|||
def __init__(self, *, data: Union[PaymentSourcePayload, PartialPaymentSourcePayload], state: ConnectionState) -> None: |
|||
self._state = state |
|||
self._update(data) |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<PaymentSource id={self.id} type={self.type!r} country={self.country!r}>' |
|||
|
|||
def _update(self, data: Union[PaymentSourcePayload, PartialPaymentSourcePayload]) -> None: |
|||
self.id: int = int(data['id']) |
|||
self.brand: Optional[str] = data.get('brand') |
|||
self.country: Optional[str] = data.get('country') |
|||
self.partial_card_number: Optional[str] = data.get('last_4') |
|||
self.billing_address: Optional[BillingAddress] = ( |
|||
BillingAddress.from_dict(data['billing_address'], state=self._state) if 'billing_address' in data else None # type: ignore # ??? |
|||
) |
|||
|
|||
self.type: PaymentSourceType = try_enum(PaymentSourceType, data['type']) |
|||
self.payment_gateway: PaymentGateway = try_enum(PaymentGateway, data['payment_gateway']) |
|||
self.default: bool = data.get('default', False) |
|||
self.invalid: bool = data['invalid'] |
|||
self._flags: int = data.get('flags', 0) |
|||
|
|||
month = data.get('expires_month') |
|||
year = data.get('expires_year') |
|||
self.expires_at: Optional[date] = datetime(year=year, month=month or 1, day=1).date() if year else None |
|||
|
|||
self.email: Optional[str] = data.get('email') |
|||
self.bank: Optional[str] = data.get('bank') |
|||
self.username: Optional[str] = data.get('username') |
|||
|
|||
if not self.country and self.billing_address: |
|||
self.country = self.billing_address.country |
|||
|
|||
if not self.email and self.billing_address: |
|||
self.email = self.billing_address.email |
|||
|
|||
@property |
|||
def flags(self) -> PaymentSourceFlags: |
|||
""":class:`PaymentSourceFlags`: Returns the payment source's flags.""" |
|||
return PaymentSourceFlags._from_value(self._flags) |
|||
|
|||
async def edit( |
|||
self, *, billing_address: BillingAddress = MISSING, default: bool = MISSING, expires_at: date = MISSING |
|||
) -> None: |
|||
"""|coro| |
|||
|
|||
Edits the payment source. |
|||
|
|||
Parameters |
|||
---------- |
|||
billing_address: :class:`BillingAddress` |
|||
The billing address of the payment source. |
|||
default: :class:`bool` |
|||
Whether the payment source is the default payment source. |
|||
expires_at: :class:`datetime.date` |
|||
When the payment source expires. This is only applicable to cards. |
|||
|
|||
Raises |
|||
------ |
|||
HTTPException |
|||
Editing the payment source failed. |
|||
""" |
|||
payload = {} |
|||
if billing_address is not MISSING: |
|||
payload['billing_address'] = billing_address.to_dict() |
|||
if default is not MISSING: |
|||
payload['default'] = default |
|||
if expires_at is not MISSING: |
|||
payload['expires_month'] = expires_at.month |
|||
payload['expires_year'] = expires_at.year |
|||
|
|||
data = await self._state.http.edit_payment_source(self.id, payload) |
|||
self._update(data) |
|||
|
|||
async def delete(self) -> None: |
|||
"""|coro| |
|||
|
|||
Deletes the payment source. |
|||
|
|||
Raises |
|||
------ |
|||
HTTPException |
|||
Deleting the payment source failed. |
|||
""" |
|||
await self._state.http.delete_payment_source(self.id) |
|||
|
|||
|
|||
class PremiumUsage: |
|||
"""Represents the usage of a user's premium perks. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
sticker_sends: :class:`int` |
|||
The number of premium sticker sends. |
|||
animated_emojis: :class:`int` |
|||
The number of animated emojis used. |
|||
global_emojis: :class:`int` |
|||
The number of global emojis used. |
|||
large_uploads: :class:`int` |
|||
The number of large uploads made. |
|||
hd_streams: :class:`int` |
|||
The number of HD streams. |
|||
hd_hours_streamed: :class:`int` |
|||
The number of hours streamed in HD. |
|||
""" |
|||
|
|||
__slots__ = ( |
|||
'sticker_sends', |
|||
'animated_emojis', |
|||
'global_emojis', |
|||
'large_uploads', |
|||
'hd_streams', |
|||
'hd_hours_streamed', |
|||
) |
|||
|
|||
def __init__(self, *, data: PremiumUsagePayload) -> None: |
|||
self.sticker_sends: int = data['nitro_sticker_sends']['value'] |
|||
self.animated_emojis: int = data['total_animated_emojis']['value'] |
|||
self.global_emojis: int = data['total_global_emojis']['value'] |
|||
self.large_uploads: int = data['total_large_uploads']['value'] |
|||
self.hd_streams: int = data['total_hd_streams']['value'] |
|||
self.hd_hours_streamed: int = data['hd_hours_streamed']['value'] |
File diff suppressed because it is too large
@ -0,0 +1,632 @@ |
|||
""" |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2021-present Dolfies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a |
|||
copy of this software and associated documentation files (the "Software"), |
|||
to deal in the Software without restriction, including without limitation |
|||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|||
and/or sell copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|||
DEALINGS IN THE SOFTWARE. |
|||
""" |
|||
|
|||
from __future__ import annotations |
|||
|
|||
from typing import TYPE_CHECKING, Any, List, Optional |
|||
|
|||
from .enums import EntitlementType, GiftStyle, PremiumType, try_enum |
|||
from .flags import GiftFlags |
|||
from .mixins import Hashable |
|||
from .payments import EntitlementPayment |
|||
from .promotions import Promotion |
|||
from .store import SKU, StoreListing, SubscriptionPlan |
|||
from .subscriptions import Subscription, SubscriptionTrial |
|||
from .utils import _get_as_snowflake, parse_time, utcnow |
|||
|
|||
if TYPE_CHECKING: |
|||
from datetime import datetime |
|||
|
|||
from .abc import Snowflake |
|||
from .guild import Guild |
|||
from .state import ConnectionState |
|||
from .types.entitlements import ( |
|||
Entitlement as EntitlementPayload, |
|||
Gift as GiftPayload, |
|||
GiftBatch as GiftBatchPayload, |
|||
) |
|||
from .user import User |
|||
|
|||
__all__ = ( |
|||
'Entitlement', |
|||
'Gift', |
|||
'GiftBatch', |
|||
) |
|||
|
|||
|
|||
class Entitlement(Hashable): |
|||
"""Represents a Discord entitlement. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two entitlements are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two entitlements are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the entitlement's hash. |
|||
|
|||
.. describe:: bool(x) |
|||
|
|||
Checks if the entitlement is active. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
id: :class:`int` |
|||
The ID of the entitlement. |
|||
type: :class:`EntitlementType` |
|||
The type of entitlement. |
|||
user_id: :class:`int` |
|||
The ID of the user the entitlement is for. |
|||
sku_id: :class:`int` |
|||
The ID of the SKU the entitlement grants. |
|||
application_id: :class:`int` |
|||
The ID of the application that owns the SKU the entitlement grants. |
|||
promotion_id: Optional[:class:`int`] |
|||
The ID of the promotion the entitlement is from. |
|||
parent_id: Optional[:class:`int`] |
|||
The ID of the entitlement's parent. |
|||
guild_id: Optional[:class:`int`] |
|||
The ID of the guild the entitlement is for. |
|||
branches: List[:class:`int`] |
|||
The IDs of the branches the entitlement grants. |
|||
gifter_id: Optional[:class:`int`] |
|||
The ID of the user that gifted the entitlement. |
|||
gift_style: Optional[:class:`GiftStyle`] |
|||
The style of the gift attached to this entitlement. |
|||
gift_batch_id: Optional[:class:`int`] |
|||
The ID of the batch the gift attached to this entitlement is from. |
|||
deleted: :class:`bool` |
|||
Whether the entitlement is deleted. |
|||
consumed: :class:`bool` |
|||
Whether the entitlement is consumed. |
|||
starts_at: Optional[:class:`datetime.datetime`] |
|||
When the entitlement period starts. |
|||
ends_at: Optional[:class:`datetime.datetime`] |
|||
When the entitlement period ends. |
|||
subscription_id: Optional[:class:`int`] |
|||
The ID of the subscription the entitlement is from. |
|||
subscription_plan: Optional[:class:`SubscriptionPlan`] |
|||
The subscription plan the entitlement is for. |
|||
|
|||
.. note:: |
|||
|
|||
This is a partial object without price information. |
|||
sku: Optional[:class:`SKU`] |
|||
The SKU the entitlement grants. |
|||
payment: Optional[:class:`EntitlementPayment`] |
|||
The payment made for the entitlement. |
|||
Not available in some contexts. |
|||
""" |
|||
|
|||
__slots__ = ( |
|||
'id', |
|||
'type', |
|||
'user_id', |
|||
'sku_id', |
|||
'application_id', |
|||
'promotion_id', |
|||
'parent_id', |
|||
'guild_id', |
|||
'branches', |
|||
'gifter_id', |
|||
'gift_style', |
|||
'gift_batch_id', |
|||
'_gift_flags', |
|||
'deleted', |
|||
'consumed', |
|||
'starts_at', |
|||
'ends_at', |
|||
'subscription_id', |
|||
'subscription_plan', |
|||
'sku', |
|||
'payment', |
|||
'_state', |
|||
) |
|||
|
|||
def __init__(self, *, data: EntitlementPayload, state: ConnectionState): |
|||
self._state = state |
|||
self._update(data) |
|||
|
|||
def _update(self, data: EntitlementPayload): |
|||
state = self._state |
|||
|
|||
self.id: int = int(data['id']) |
|||
self.type: EntitlementType = try_enum(EntitlementType, data['type']) |
|||
self.user_id: int = int(data.get('user_id') or state.self_id) # type: ignore |
|||
self.sku_id: int = int(data['sku_id']) |
|||
self.application_id: int = int(data['application_id']) |
|||
self.promotion_id: Optional[int] = _get_as_snowflake(data, 'promotion_id') |
|||
self.parent_id: Optional[int] = _get_as_snowflake(data, 'parent_id') |
|||
self.guild_id: Optional[int] = _get_as_snowflake(data, 'guild_id') |
|||
self.branches: List[int] = [int(branch) for branch in data.get('branches', [])] |
|||
self.gifter_id: Optional[int] = _get_as_snowflake(data, 'gifter_user_id') |
|||
self.gift_style: Optional[GiftStyle] = try_enum(GiftStyle, data.get('gift_style')) |
|||
self.gift_batch_id: Optional[int] = _get_as_snowflake(data, 'gift_code_batch_id') |
|||
self._gift_flags: int = data.get('gift_code_flags', 0) |
|||
|
|||
self.deleted: bool = data.get('deleted', False) |
|||
self.consumed: bool = data.get('consumed', False) |
|||
self.starts_at: Optional[datetime] = parse_time(data.get('starts_at')) |
|||
self.ends_at: Optional[datetime] = parse_time(data.get('ends_at')) |
|||
|
|||
self.subscription_id: Optional[int] = _get_as_snowflake(data, 'subscription_id') |
|||
self.subscription_plan: Optional[SubscriptionPlan] = ( |
|||
SubscriptionPlan(data=data['subscription_plan'], state=state) if 'subscription_plan' in data else None |
|||
) |
|||
self.sku: Optional[SKU] = SKU(data=data['sku'], state=state) if 'sku' in data else None |
|||
self.payment: Optional[EntitlementPayment] = ( |
|||
EntitlementPayment(data=data['payment'], entitlement=self) if 'payment' in data else None |
|||
) |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<Entitlement id={self.id} type={self.type!r} sku_id={self.sku_id} application_id={self.application_id}>' |
|||
|
|||
def __bool__(self) -> bool: |
|||
return self.is_active() |
|||
|
|||
@property |
|||
def guild(self) -> Optional[Guild]: |
|||
""":class:`Guild`: Returns the guild the entitlement is for, if accessible.""" |
|||
return self._state._get_guild(self.guild_id) |
|||
|
|||
@property |
|||
def premium_type(self) -> Optional[PremiumType]: |
|||
"""Optional[:class:`PremiumType`]: The premium type this entitlement grants, if it is for a premium subscription.""" |
|||
return PremiumType.from_sku_id(self.sku_id) |
|||
|
|||
@property |
|||
def gift_flags(self) -> GiftFlags: |
|||
""":class:`GiftFlags`: Returns the flags for the gift this entitlement is attached to.""" |
|||
return GiftFlags._from_value(self._gift_flags) |
|||
|
|||
def is_giftable(self) -> bool: |
|||
""":class:`bool`: Whether the entitlement is giftable.""" |
|||
return self.type == EntitlementType.user_gift and not self.gifter_id |
|||
|
|||
def is_active(self) -> bool: |
|||
""":class:`bool`: Whether the entitlement is active and offering perks.""" |
|||
# This is a copy of the logic used in the client |
|||
|
|||
if self.is_giftable() or self.deleted: |
|||
return False # Giftable entitlements have not yet been gifted therefore are not active |
|||
if self.starts_at and self.starts_at > utcnow(): |
|||
return False # Entitlement has not started yet |
|||
if self.ends_at and self.ends_at < utcnow(): |
|||
return False # Entitlement has ended |
|||
|
|||
if self.type == EntitlementType.premium_subscription: |
|||
# Premium subscription entitlements are only active |
|||
# if the SKU is offered for free to premium subscribers |
|||
# and the user is a premium subscriber |
|||
sku = self.sku |
|||
if sku and not sku.premium: |
|||
return False |
|||
if self._state.user and not self._state.user.premium_type == PremiumType.nitro: |
|||
return False |
|||
|
|||
return True |
|||
|
|||
async def subscription(self) -> Optional[Subscription]: |
|||
"""|coro| |
|||
|
|||
Retrieves the subscription this entitlement is attached to, if applicable. |
|||
|
|||
Raises |
|||
------ |
|||
NotFound |
|||
You cannot access this subscription. |
|||
HTTPException |
|||
Fetching the subscription failed. |
|||
|
|||
Returns |
|||
------- |
|||
Optional[:class:`Subscription`] |
|||
The retrieved subscription, if applicable. |
|||
""" |
|||
if not self.subscription_id: |
|||
return |
|||
|
|||
data = await self._state.http.get_subscription(self.subscription_id) |
|||
return Subscription(data=data, state=self._state) |
|||
|
|||
async def consume(self) -> None: |
|||
"""|coro| |
|||
|
|||
Consumes the entitlement. This marks a given user entitlement as expended, |
|||
and removes the entitlement from the user's active entitlements. |
|||
|
|||
This should be called after the user has received the relevant item, |
|||
and only works on entitlements for SKUs of type :attr:`SKUType.consumable`. |
|||
|
|||
Raises |
|||
------ |
|||
Forbidden |
|||
You do not have permissions to access this application. |
|||
HTTPException |
|||
Consuming the entitlement failed. |
|||
""" |
|||
await self._state.http.consume_app_entitlement(self.application_id, self.id) |
|||
|
|||
async def delete(self) -> None: |
|||
"""|coro| |
|||
|
|||
Deletes the entitlement. This removes the entitlement from the user's |
|||
entitlements, and is irreversible. |
|||
|
|||
This is only useable on entitlements of type :attr:`EntitlementType.test_mode_purchase`. |
|||
|
|||
Raises |
|||
------ |
|||
Forbidden |
|||
You do not have permissions to access this application. |
|||
HTTPException |
|||
Deleting the entitlement failed. |
|||
""" |
|||
await self._state.http.delete_app_entitlement(self.application_id, self.id) |
|||
|
|||
|
|||
class Gift: |
|||
"""Represents a Discord gift. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two gifts are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two gifts are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the gift's hash. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
code: :class:`str` |
|||
The gift's code. |
|||
expires_at: Optional[:class:`datetime.datetime`] |
|||
When the gift expires. |
|||
application_id: Optional[:class:`int`] |
|||
The ID of the application that owns the SKU the gift is for. |
|||
Not available in all contexts. |
|||
batch_id: Optional[:class:`int`] |
|||
The ID of the batch the gift is from. |
|||
sku_id: :class:`int` |
|||
The ID of the SKU the gift is for. |
|||
entitlement_branches: List[:class:`int`] |
|||
A list of entitlements the gift is for. |
|||
gift_style: Optional[:class:`GiftStyle`] |
|||
The style of the gift. |
|||
max_uses: :class:`int` |
|||
The maximum number of times the gift can be used. |
|||
uses: :class:`int` |
|||
The number of times the gift has been used. |
|||
redeemed: :class:`bool` |
|||
Whether the user has redeemed the gift. |
|||
revoked: :class:`bool` |
|||
Whether the gift has been revoked. |
|||
guild_id: Optional[:class:`int`] |
|||
The ID of the guild the gift was redeemed in. |
|||
Not available in all contexts. |
|||
channel_id: Optional[:class:`int`] |
|||
The ID of the channel the gift was redeemed in. |
|||
Not available in all contexts. |
|||
store_listing: Optional[:class:`StoreListing`] |
|||
The store listing for the SKU the gift is for. |
|||
Not available in all contexts. |
|||
promotion: Optional[:class:`Promotion`] |
|||
The promotion the gift is a part of, if any. |
|||
subscription_trial: Optional[:class:`SubscriptionTrial`] |
|||
The subscription trial the gift is a part of, if any. |
|||
subscription_plan_id: Optional[:class:`int`] |
|||
The ID of the subscription plan the gift is for, if any. |
|||
subscription_plan: Optional[:class:`SubscriptionPlan`] |
|||
The subscription plan the gift is for, if any. |
|||
user: Optional[:class:`User`] |
|||
The user who created the gift, if applicable. |
|||
""" |
|||
|
|||
__slots__ = ( |
|||
'code', |
|||
'expires_at', |
|||
'application_id', |
|||
'batch_id', |
|||
'sku_id', |
|||
'entitlement_branches', |
|||
'gift_style', |
|||
'_flags', |
|||
'max_uses', |
|||
'uses', |
|||
'redeemed', |
|||
'revoked', |
|||
'guild_id', |
|||
'channel_id', |
|||
'store_listing', |
|||
'promotion', |
|||
'subscription_trial', |
|||
'subscription_plan_id', |
|||
'subscription_plan', |
|||
'user', |
|||
'_state', |
|||
) |
|||
|
|||
def __init__(self, *, data: GiftPayload, state: ConnectionState) -> None: |
|||
self._state = state |
|||
self._update(data) |
|||
|
|||
def _update(self, data: GiftPayload) -> None: |
|||
state = self._state |
|||
|
|||
self.code: str = data['code'] |
|||
self.expires_at: Optional[datetime] = parse_time(data.get('expires_at')) |
|||
self.application_id: Optional[int] = _get_as_snowflake(data, 'application_id') |
|||
self.batch_id: Optional[int] = _get_as_snowflake(data, 'batch_id') |
|||
self.subscription_plan_id: Optional[int] = _get_as_snowflake(data, 'subscription_plan_id') |
|||
self.sku_id: int = int(data['sku_id']) |
|||
self.entitlement_branches: List[int] = [int(x) for x in data.get('entitlement_branches', [])] |
|||
self.gift_style: Optional[GiftStyle] = try_enum(GiftStyle, data['gift_style']) if data.get('gift_style') else None # type: ignore |
|||
self._flags: int = data.get('flags', 0) |
|||
|
|||
self.max_uses: int = data.get('max_uses', 0) |
|||
self.uses: int = data.get('uses', 0) |
|||
self.redeemed: bool = data.get('redeemed', False) |
|||
self.revoked: bool = data.get('revoked', False) |
|||
|
|||
self.guild_id: Optional[int] = _get_as_snowflake(data, 'guild_id') |
|||
self.channel_id: Optional[int] = _get_as_snowflake(data, 'channel_id') |
|||
|
|||
self.store_listing: Optional[StoreListing] = ( |
|||
StoreListing(data=data['store_listing'], state=state) if 'store_listing' in data else None |
|||
) |
|||
self.promotion: Optional[Promotion] = Promotion(data=data['promotion'], state=state) if 'promotion' in data else None |
|||
self.subscription_trial: Optional[SubscriptionTrial] = ( |
|||
SubscriptionTrial(data['subscription_trial']) if 'subscription_trial' in data else None |
|||
) |
|||
self.subscription_plan: Optional[SubscriptionPlan] = ( |
|||
SubscriptionPlan(data=data['subscription_plan'], state=state) if 'subscription_plan' in data else None |
|||
) |
|||
self.user: Optional[User] = self._state.create_user(data['user']) if 'user' in data else None |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<Gift code={self.code!r} sku_id={self.sku_id} uses={self.uses} max_uses={self.max_uses} redeemed={self.redeemed}>' |
|||
|
|||
def __eq__(self, other: Any) -> bool: |
|||
return isinstance(other, Gift) and other.code == self.code |
|||
|
|||
def __ne__(self, other: Any) -> bool: |
|||
if isinstance(other, Gift): |
|||
return other.code != self.code |
|||
return True |
|||
|
|||
def __hash__(self) -> int: |
|||
return hash(self.code) |
|||
|
|||
@property |
|||
def id(self) -> str: |
|||
""":class:`str`: Returns the code portion of the gift.""" |
|||
return self.code |
|||
|
|||
@property |
|||
def url(self) -> str: |
|||
""":class:`str`: Returns the gift's URL.""" |
|||
return f'https://discord.gift/{self.code}' |
|||
|
|||
@property |
|||
def remaining_uses(self) -> int: |
|||
""":class:`int`: Returns the number of remaining uses for the gift.""" |
|||
return self.max_uses - self.uses |
|||
|
|||
@property |
|||
def flags(self) -> GiftFlags: |
|||
""":class:`GiftFlags`: Returns the gift's flags.""" |
|||
return GiftFlags._from_value(self._flags) |
|||
|
|||
@property |
|||
def premium_type(self) -> Optional[PremiumType]: |
|||
"""Optional[:class:`PremiumType`]: The premium type this gift grants, if it is for a premium subscription.""" |
|||
return PremiumType.from_sku_id(self.sku_id) if self.is_subscription() else None |
|||
|
|||
def is_claimed(self) -> bool: |
|||
""":class:`bool`: Checks if the gift has been used up.""" |
|||
return self.uses >= self.max_uses if self.max_uses else False |
|||
|
|||
def is_expired(self) -> bool: |
|||
""":class:`bool`: Checks if the gift has expired.""" |
|||
return self.expires_at < utcnow() if self.expires_at else False |
|||
|
|||
def is_subscription(self) -> bool: |
|||
""":class:`bool`: Checks if the gift is for a subscription.""" |
|||
return self.subscription_plan_id is not None |
|||
|
|||
def is_premium_subscription(self) -> bool: |
|||
""":class:`bool`: Checks if the gift is for a premium subscription.""" |
|||
return self.is_subscription() and self.application_id == self._state.premium_subscriptions_application.id |
|||
|
|||
async def redeem( |
|||
self, |
|||
payment_source: Optional[Snowflake] = None, |
|||
*, |
|||
channel: Optional[Snowflake] = None, |
|||
gateway_checkout_context: Optional[str] = None, |
|||
) -> Entitlement: |
|||
"""|coro| |
|||
|
|||
Redeems the gift. |
|||
|
|||
Parameters |
|||
---------- |
|||
payment_source: Optional[:class:`PaymentSource`] |
|||
The payment source to use for the redemption. |
|||
Only required if the gift's :attr:`flags` have :attr:`GiftFlags.payment_source_required` set to ``True``. |
|||
channel: Optional[Union[:class:`TextChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`]] |
|||
The channel to redeem the gift in. This is usually the channel the gift was sent in. |
|||
While this is optional, it is recommended to pass this in. |
|||
gateway_checkout_context: Optional[:class:`str`] |
|||
The current checkout context. |
|||
|
|||
Raises |
|||
------ |
|||
HTTPException |
|||
The gift failed to redeem. |
|||
|
|||
Returns |
|||
------- |
|||
:class:`Entitlement` |
|||
The entitlement that was created from redeeming the gift. |
|||
""" |
|||
data = await self._state.http.redeem_gift( |
|||
self.code, |
|||
payment_source.id if payment_source else None, |
|||
channel.id if channel else None, |
|||
gateway_checkout_context, |
|||
) |
|||
return Entitlement(data=data, state=self._state) |
|||
|
|||
async def delete(self) -> None: |
|||
"""|coro| |
|||
|
|||
Revokes the gift. |
|||
|
|||
This is only possible for gifts the current account has created. |
|||
|
|||
Raises |
|||
------ |
|||
NotFound |
|||
The owned gift was not found. |
|||
HTTPException |
|||
The gift failed to delete. |
|||
""" |
|||
await self._state.http.delete_gift(self.code) |
|||
|
|||
|
|||
class GiftBatch(Hashable): |
|||
"""Represents a batch of gifts for an SKU. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two gift batches are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two gift batches are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the gift batch's hash. |
|||
|
|||
.. describe:: str(x) |
|||
|
|||
Returns the gift batch's description. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
----------- |
|||
id: :class:`int` |
|||
The ID of the gift batch. |
|||
application_id: :class:`int` |
|||
The ID of the application the gift batch is for. |
|||
sku_id: :class:`int` |
|||
The ID of the SKU the gift batch is for. |
|||
amount: :class:`int` |
|||
The amount of gifts in the batch. |
|||
description: :class:`str` |
|||
The description of the gift batch. |
|||
entitlement_branches: List[:class:`int`] |
|||
The entitlement branches the gift batch is for. |
|||
entitlement_starts_at: Optional[:class:`datetime.datetime`] |
|||
When the entitlement is valid from. |
|||
entitlement_ends_at: Optional[:class:`datetime.datetime`] |
|||
When the entitlement is valid until. |
|||
""" |
|||
|
|||
__slots__ = ( |
|||
'id', |
|||
'application_id', |
|||
'sku_id', |
|||
'amount', |
|||
'description', |
|||
'entitlement_branches', |
|||
'entitlement_starts_at', |
|||
'entitlement_ends_at', |
|||
'_state', |
|||
) |
|||
|
|||
def __init__(self, *, data: GiftBatchPayload, state: ConnectionState, application_id: int) -> None: |
|||
self._state: ConnectionState = state |
|||
self.id: int = int(data['id']) |
|||
self.application_id = application_id |
|||
self.sku_id: int = int(data['sku_id']) |
|||
self.amount: int = data['amount'] |
|||
self.description: str = data.get('description', '') |
|||
self.entitlement_branches: List[int] = [int(branch) for branch in data.get('entitlement_branches', [])] |
|||
self.entitlement_starts_at: Optional[datetime] = parse_time(data.get('entitlement_starts_at')) |
|||
self.entitlement_ends_at: Optional[datetime] = parse_time(data.get('entitlement_ends_at')) |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<GiftBatch id={self.id} sku_id={self.sku_id} amount={self.amount} description={self.description!r}>' |
|||
|
|||
def __str__(self) -> str: |
|||
return self.description |
|||
|
|||
def is_valid(self) -> bool: |
|||
""":class:`bool`: Checks if the gift batch is valid.""" |
|||
if self.entitlement_starts_at and self.entitlement_starts_at > utcnow(): |
|||
return False |
|||
if self.entitlement_ends_at and self.entitlement_ends_at < utcnow(): |
|||
return False |
|||
return True |
|||
|
|||
async def download(self) -> bytes: |
|||
"""|coro| |
|||
|
|||
Returns the gifts in the gift batch in CSV format. |
|||
|
|||
Raises |
|||
------- |
|||
Forbidden |
|||
You do not have permissions to download the batch. |
|||
HTTPException |
|||
Downloading the batch failed. |
|||
|
|||
Returns |
|||
------- |
|||
:class:`bytes` |
|||
The report content. |
|||
""" |
|||
return await self._state.http.get_gift_batch_csv(self.application_id, self.id) |
@ -0,0 +1,320 @@ |
|||
""" |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2021-present Dolfies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a |
|||
copy of this software and associated documentation files (the "Software"), |
|||
to deal in the Software without restriction, including without limitation |
|||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|||
and/or sell copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|||
DEALINGS IN THE SOFTWARE. |
|||
""" |
|||
|
|||
from __future__ import annotations |
|||
|
|||
from datetime import datetime, timedelta |
|||
from typing import TYPE_CHECKING, Optional |
|||
|
|||
from .mixins import Hashable |
|||
from .subscriptions import Subscription |
|||
from .utils import parse_time, utcnow |
|||
|
|||
if TYPE_CHECKING: |
|||
from .abc import Snowflake |
|||
from .guild import Guild |
|||
from .state import ConnectionState |
|||
from .types.subscriptions import ( |
|||
PremiumGuildSubscription as PremiumGuildSubscriptionPayload, |
|||
PremiumGuildSubscriptionSlot as PremiumGuildSubscriptionSlotPayload, |
|||
PremiumGuildSubscriptionCooldown as PremiumGuildSubscriptionCooldownPayload, |
|||
) |
|||
|
|||
__all__ = ( |
|||
'PremiumGuildSubscription', |
|||
'PremiumGuildSubscriptionSlot', |
|||
'PremiumGuildSubscriptionCooldown', |
|||
) |
|||
|
|||
|
|||
class PremiumGuildSubscription(Hashable): |
|||
"""Represents a premium guild subscription (boost). |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two premium guild subscriptions are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two premium guild subscriptions are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the premium guild subscription's hash. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
------------ |
|||
id: :class:`int` |
|||
The ID of the guild premium subscription. |
|||
guild_id: :class:`int` |
|||
The ID of the guild this guild premium subscription belongs to. |
|||
user_id: :class:`int` |
|||
The ID of the user this guild premium subscription belongs to. |
|||
user: :class:`User` |
|||
The user this guild premium subscription belongs to. |
|||
ended: :class:`bool` |
|||
Whether the guild premium subscription has ended. |
|||
ends_at: Optional[:class:`datetime.datetime`] |
|||
When the guild premium subscription ends. |
|||
""" |
|||
|
|||
def __init__(self, *, state: ConnectionState, data: PremiumGuildSubscriptionPayload): |
|||
self._state = state |
|||
self._update(data) |
|||
|
|||
def _update(self, data: PremiumGuildSubscriptionPayload): |
|||
state = self._state |
|||
|
|||
self.id = int(data['id']) |
|||
self.guild_id = int(data['guild_id']) |
|||
self.user_id = int(data['user_id']) |
|||
self.user = state.store_user(data['user']) if 'user' in data else state.user |
|||
self.ended = data.get('ended', False) |
|||
self.ends_at: Optional[datetime] = parse_time(data.get('ends_at')) |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<PremiumGuildSubscription id={self.id} guild_id={self.guild_id} user_id={self.user_id} ended={self.ended}>' |
|||
|
|||
@property |
|||
def guild(self) -> Optional[Guild]: |
|||
"""Optional[:class:`Guild`]: The guild this guild premium subscription belongs to, if available.""" |
|||
return self._state._get_guild(self.guild_id) |
|||
|
|||
@property |
|||
def remaining(self) -> Optional[timedelta]: |
|||
"""Optional[:class:`datetime.timedelta`]: The remaining time for this guild premium subscription. |
|||
|
|||
This is ``None`` if the subscription is not ending. |
|||
""" |
|||
if self.ends_at is None or self.ends_at <= utcnow(): |
|||
return None |
|||
|
|||
return self.ends_at - utcnow() |
|||
|
|||
async def delete(self) -> None: |
|||
"""|coro| |
|||
|
|||
Deletes this guild premium subscription. |
|||
|
|||
Raises |
|||
------- |
|||
Forbidden |
|||
You do not have permissions to delete this guild premium subscription. |
|||
HTTPException |
|||
Deleting the guild premium subscription failed. |
|||
""" |
|||
await self._state.http.delete_guild_subscription(self.guild_id, self.id) |
|||
|
|||
|
|||
class PremiumGuildSubscriptionSlot(Hashable): |
|||
"""Represents a premium guild subscription (boost) slot. |
|||
|
|||
This is a slot that can be used on a guild (to boost it). |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two subscription slots are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two subscription slots are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the subscription slot's hash. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
------------ |
|||
id: :class:`int` |
|||
The ID of the guild subscription slot. |
|||
subscription_id: :class:`int` |
|||
The ID of the guild subscription this slot belongs to. |
|||
canceled: :class:`bool` |
|||
Whether the slot is canceled. |
|||
cooldown_ends_at: Optional[:class:`datetime.datetime`] |
|||
When the cooldown for this guild subscription slot ends. |
|||
premium_guild_subscription: Optional[:class:`PremiumGuildSubscription`] |
|||
The subscription this slot belongs to. |
|||
""" |
|||
|
|||
__slots__ = ( |
|||
'id', |
|||
'subscription_id', |
|||
'canceled', |
|||
'cooldown_ends_at', |
|||
'premium_guild_subscription', |
|||
'_state', |
|||
) |
|||
|
|||
def __init__(self, *, state: ConnectionState, data: PremiumGuildSubscriptionSlotPayload): |
|||
self._state = state |
|||
self._update(data) |
|||
|
|||
def _update(self, data: PremiumGuildSubscriptionSlotPayload): |
|||
self.id = int(data['id']) |
|||
self.subscription_id = int(data['subscription_id']) |
|||
self.canceled = data.get('canceled', False) |
|||
self.cooldown_ends_at: Optional[datetime] = parse_time(data.get('cooldown_ends_at')) |
|||
|
|||
premium_guild_subscription = data.get('premium_guild_subscription') |
|||
self.premium_guild_subscription: Optional[PremiumGuildSubscription] = ( |
|||
PremiumGuildSubscription(state=self._state, data=premium_guild_subscription) |
|||
if premium_guild_subscription is not None |
|||
else None |
|||
) |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<PremiumGuildSubscriptionSlot id={self.id} subscription_id={self.subscription_id} canceled={self.canceled}>' |
|||
|
|||
def is_available(self) -> bool: |
|||
""":class:`bool`: Indicates if the slot is available for use.""" |
|||
return not self.premium_guild_subscription and not self.is_on_cooldown() |
|||
|
|||
def is_on_cooldown(self) -> bool: |
|||
""":class:`bool`: Indicates if the slot is on cooldown.""" |
|||
return self.cooldown_ends_at is not None and self.cooldown_ends_at > utcnow() |
|||
|
|||
@property |
|||
def cancelled(self) -> bool: |
|||
""":class:`bool`: Whether the slot is cancelled. |
|||
|
|||
This is an alias of :attr:`canceled`. |
|||
""" |
|||
return self.canceled |
|||
|
|||
@property |
|||
def cooldown_remaining(self) -> Optional[timedelta]: |
|||
"""Optional[:class:`datetime.timedelta`]: The cooldown remaining for this boost slot. |
|||
|
|||
This is ``None`` if the cooldown has ended. |
|||
""" |
|||
if self.cooldown_ends_at is None or self.cooldown_ends_at <= utcnow(): |
|||
return None |
|||
|
|||
return self.cooldown_ends_at - utcnow() |
|||
|
|||
async def subscription(self) -> Subscription: |
|||
"""|coro| |
|||
|
|||
Retrieves the subscription this guild subscription slot is attached to. |
|||
|
|||
Raises |
|||
------ |
|||
NotFound |
|||
You cannot access this subscription. |
|||
HTTPException |
|||
Fetching the subscription failed. |
|||
|
|||
Returns |
|||
------- |
|||
:class:`Subscription` |
|||
The retrieved subscription, if applicable. |
|||
""" |
|||
data = await self._state.http.get_subscription(self.subscription_id) |
|||
return Subscription(data=data, state=self._state) |
|||
|
|||
async def apply(self, guild: Snowflake) -> PremiumGuildSubscription: |
|||
"""|coro| |
|||
|
|||
Applies the premium guild subscription slot to a guild. |
|||
|
|||
Parameters |
|||
----------- |
|||
guild: :class:`Guild` |
|||
The guild to apply the slot to. |
|||
|
|||
Raises |
|||
------- |
|||
HTTPException |
|||
Applying the slot failed. |
|||
|
|||
Returns |
|||
-------- |
|||
:class:`PremiumGuildSubscription` |
|||
The premium guild subscription that was created. |
|||
""" |
|||
state = self._state |
|||
data = await state.http.apply_guild_subscription_slots(guild.id, (self.id,)) |
|||
return PremiumGuildSubscription(state=state, data=data[0]) |
|||
|
|||
async def cancel(self) -> None: |
|||
"""|coro| |
|||
|
|||
Cancels the guild subscription slot. |
|||
|
|||
Raises |
|||
------- |
|||
HTTPException |
|||
Cancelling the slot failed. |
|||
""" |
|||
data = await self._state.http.cancel_guild_subscription_slot(self.id) |
|||
self._update(data) |
|||
|
|||
async def uncancel(self) -> None: |
|||
"""|coro| |
|||
|
|||
Uncancels the guild subscription slot. |
|||
|
|||
Raises |
|||
------- |
|||
HTTPException |
|||
Uncancelling the slot failed. |
|||
""" |
|||
data = await self._state.http.uncancel_guild_subscription_slot(self.id) |
|||
self._update(data) |
|||
|
|||
|
|||
class PremiumGuildSubscriptionCooldown: |
|||
"""Represents a premium guild subscription cooldown. |
|||
|
|||
This is a cooldown that is applied to your guild subscription slot changes (boosting and unboosting). |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
------------ |
|||
ends_at: :class:`datetime.datetime` |
|||
When the cooldown resets. |
|||
limit: :class:`int` |
|||
The maximum number of changes that can be made before the cooldown is applied. |
|||
remaining: :class:`int` |
|||
The number of changes remaining before the cooldown is applied. |
|||
""" |
|||
|
|||
def __init__(self, *, state: ConnectionState, data: PremiumGuildSubscriptionCooldownPayload): |
|||
self._state = state |
|||
self._update(data) |
|||
|
|||
def _update(self, data: PremiumGuildSubscriptionCooldownPayload): |
|||
self.ends_at: datetime = parse_time(data['ends_at']) |
|||
self.limit = data['limit'] |
|||
self.remaining = data.get('remaining', 0) |
File diff suppressed because it is too large
@ -0,0 +1,280 @@ |
|||
""" |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2021-present Dolfies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a |
|||
copy of this software and associated documentation files (the "Software"), |
|||
to deal in the Software without restriction, including without limitation |
|||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|||
and/or sell copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|||
DEALINGS IN THE SOFTWARE. |
|||
""" |
|||
|
|||
from __future__ import annotations |
|||
|
|||
from typing import TYPE_CHECKING, Any, List, Optional |
|||
|
|||
from .appinfo import ApplicationActivityStatistics, ApplicationBranch, PartialApplication |
|||
from .entitlements import Entitlement |
|||
from .enums import SKUType, try_enum |
|||
from .flags import LibraryApplicationFlags |
|||
from .mixins import Hashable |
|||
from .utils import MISSING, _get_as_snowflake, find, parse_date, parse_time |
|||
|
|||
if TYPE_CHECKING: |
|||
from datetime import date, datetime |
|||
|
|||
from .asset import Asset |
|||
from .state import ConnectionState |
|||
from .types.appinfo import Branch as BranchPayload |
|||
from .types.library import LibraryApplication as LibraryApplicationPayload |
|||
from .types.store import PartialSKU as PartialSKUPayload |
|||
|
|||
__all__ = ( |
|||
'LibrarySKU', |
|||
'LibraryApplication', |
|||
) |
|||
|
|||
|
|||
class LibrarySKU(Hashable): |
|||
"""Represents a partial store SKU for a library entry. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two library SKUs are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two library SKUs are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the library SKU's hash. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
----------- |
|||
id: :class:`int` |
|||
The SKU's ID. |
|||
type: :class:`SKUType` |
|||
The type of the SKU. |
|||
preorder_release_date: Optional[:class:`datetime.date`] |
|||
The approximate date that the SKU will released for pre-order, if any. |
|||
preorder_released_at: Optional[:class:`datetime.datetime`] |
|||
The date that the SKU was released for pre-order, if any. |
|||
premium: :class:`bool` |
|||
Whether this SKU is provided for free to premium users. |
|||
""" |
|||
|
|||
__slots__ = ( |
|||
'id', |
|||
'type', |
|||
'preorder_release_date', |
|||
'preorder_released_at', |
|||
'premium', |
|||
) |
|||
|
|||
def __init__(self, data: PartialSKUPayload): |
|||
self.id: int = int(data['id']) |
|||
self.type: SKUType = try_enum(SKUType, data['type']) |
|||
self.preorder_release_date: Optional[date] = parse_date(data.get('preorder_approximate_release_date')) |
|||
self.preorder_released_at: Optional[datetime] = parse_time(data.get('preorder_release_at')) |
|||
self.premium: bool = data.get('premium', False) |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<LibrarySKU id={self.id} type={self.type!r} preorder_release_date={self.preorder_release_date!r} preorder_released_at={self.preorder_released_at!r} premium={self.premium!r}>' |
|||
|
|||
|
|||
class LibraryApplication: |
|||
"""Represents a library entry. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two library entries are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two library entries are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the library entry's hash. |
|||
|
|||
.. describe:: str(x) |
|||
|
|||
Returns the library entry's name. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
----------- |
|||
created_at: :class:`datetime.datetime` |
|||
When this library entry was created. |
|||
application: :class:`PartialApplication` |
|||
The application that this library entry is for. |
|||
sku_id: :class:`int` |
|||
The ID of the SKU that this library entry is for. |
|||
sku: :class:`LibrarySKU` |
|||
The SKU that this library entry is for. |
|||
entitlements: List[:class:`Entitlement`] |
|||
The entitlements that this library entry has. |
|||
branch_id: :class:`int` |
|||
The ID of the branch that this library entry installs. |
|||
branch: :class:`ApplicationBranch` |
|||
The branch that this library entry installs. |
|||
""" |
|||
|
|||
__slots__ = ( |
|||
'created_at', |
|||
'application', |
|||
'sku_id', |
|||
'sku', |
|||
'entitlements', |
|||
'branch_id', |
|||
'branch', |
|||
'_flags', |
|||
'_state', |
|||
) |
|||
|
|||
def __init__(self, *, state: ConnectionState, data: LibraryApplicationPayload): |
|||
self._state = state |
|||
self._update(data) |
|||
|
|||
def _update(self, data: LibraryApplicationPayload): |
|||
state = self._state |
|||
|
|||
self.created_at: datetime = parse_time(data['created_at']) |
|||
self.application: PartialApplication = PartialApplication(state=state, data=data['application']) |
|||
self.sku_id: int = int(data['sku_id']) |
|||
self.sku: LibrarySKU = LibrarySKU(data=data['sku']) |
|||
self.entitlements: List[Entitlement] = [Entitlement(state=state, data=e) for e in data.get('entitlements', [])] |
|||
self._flags = data.get('flags', 0) |
|||
|
|||
self.branch_id: int = int(data['branch_id']) |
|||
branch: Optional[BranchPayload] = data.get('branch') |
|||
if not branch: |
|||
branch = {'id': self.branch_id, 'name': 'master'} |
|||
self.branch: ApplicationBranch = ApplicationBranch(state=state, data=branch, application_id=self.application.id) |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<LibraryApplication created_at={self.created_at!r} application={self.application!r} sku={self.sku!r} branch={self.branch!r}>' |
|||
|
|||
def __eq__(self, other: Any) -> bool: |
|||
if isinstance(other, LibraryApplication): |
|||
return self.application.id == other.application.id and self.branch_id == other.branch_id |
|||
return False |
|||
|
|||
def __ne__(self, other: Any) -> bool: |
|||
if isinstance(other, LibraryApplication): |
|||
return self.application.id != other.application.id or self.branch_id != other.branch_id |
|||
return True |
|||
|
|||
def __hash__(self) -> int: |
|||
return hash((self.application.id, self.branch_id)) |
|||
|
|||
def __str__(self) -> str: |
|||
return self.application.name |
|||
|
|||
@property |
|||
def name(self) -> str: |
|||
""":class:`str`: The library entry's name.""" |
|||
return self.application.name |
|||
|
|||
@property |
|||
def icon(self) -> Optional[Asset]: |
|||
""":class:`Asset`: The library entry's icon asset, if any.""" |
|||
return self.application.icon |
|||
|
|||
@property |
|||
def flags(self) -> LibraryApplicationFlags: |
|||
""":class:`LibraryApplicationFlags`: The library entry's flags.""" |
|||
return LibraryApplicationFlags._from_value(self._flags) |
|||
|
|||
async def activity_statistics(self) -> ApplicationActivityStatistics: |
|||
"""|coro| |
|||
|
|||
Gets the activity statistics for this library entry. |
|||
|
|||
Raises |
|||
------- |
|||
HTTPException |
|||
Getting the activity statistics failed. |
|||
|
|||
Returns |
|||
-------- |
|||
:class:`ApplicationActivityStatistics` |
|||
The activity statistics for this library entry. |
|||
""" |
|||
state = self._state |
|||
data = await state.http.get_activity_statistics() |
|||
app = find(lambda a: _get_as_snowflake(a, 'application_id') == self.application.id, data) |
|||
return ApplicationActivityStatistics( |
|||
data=app |
|||
or {'application_id': self.application.id, 'total_duration': 0, 'last_played_at': '1970-01-01T00:00:00+00:00'}, |
|||
state=state, |
|||
) |
|||
|
|||
async def mark_installed(self) -> None: |
|||
"""|coro| |
|||
|
|||
Marks the library entry as installed. |
|||
|
|||
Raises |
|||
------- |
|||
HTTPException |
|||
Marking the library entry as installed failed. |
|||
""" |
|||
await self._state.http.mark_library_entry_installed(self.application.id, self.branch_id) |
|||
|
|||
async def edit(self, *, flags: LibraryApplicationFlags = MISSING) -> None: |
|||
"""|coro| |
|||
|
|||
Edits the library entry. |
|||
|
|||
All parameters are optional. |
|||
|
|||
Parameters |
|||
----------- |
|||
flags: :class:`LibraryApplicationFlags` |
|||
The new flags to set for the library entry. |
|||
|
|||
Raises |
|||
------- |
|||
HTTPException |
|||
Editing the library entry failed. |
|||
""" |
|||
payload = {} |
|||
if flags is not MISSING: |
|||
payload['flags'] = flags.value |
|||
|
|||
data = await self._state.http.edit_library_entry(self.application.id, self.branch_id, payload) |
|||
self._update(data) |
|||
|
|||
async def delete(self) -> None: |
|||
"""|coro| |
|||
|
|||
Deletes the library entry. |
|||
|
|||
Raises |
|||
------- |
|||
HTTPException |
|||
Deleting the library entry failed. |
|||
""" |
|||
await self._state.http.delete_library_entry(self.application.id, self.branch_id) |
@ -0,0 +1,134 @@ |
|||
""" |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2021-present Dolfies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a |
|||
copy of this software and associated documentation files (the "Software"), |
|||
to deal in the Software without restriction, including without limitation |
|||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|||
and/or sell copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|||
DEALINGS IN THE SOFTWARE. |
|||
""" |
|||
|
|||
from __future__ import annotations |
|||
|
|||
from typing import TYPE_CHECKING, Any, Dict, Iterator, Optional, Tuple, Union |
|||
|
|||
from .utils import parse_time |
|||
|
|||
|
|||
class Metadata: |
|||
"""Represents a raw model from Discord. |
|||
|
|||
Because of how unstable and wildly varying some metadata in Discord can be, this is a simple class |
|||
that just provides access to the raw data using dot notation. This means that ``None`` is returned |
|||
for unknown attributes instead of raising an exception. This class can be used similarly to a dictionary. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two metadata objects are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two metadata objects are not equal. |
|||
|
|||
.. describe:: x[key] |
|||
|
|||
Returns a metadata value if it is found, otherwise raises a :exc:`KeyError`. |
|||
|
|||
.. describe:: key in x |
|||
|
|||
Checks if a metadata value is present. |
|||
|
|||
.. describe:: len(x) |
|||
|
|||
Returns the number of metadata values present. |
|||
|
|||
.. describe:: iter(x) |
|||
Returns an iterator of ``(field, value)`` pairs. This allows this class |
|||
to be used as an iterable in list/dict/etc constructions. |
|||
""" |
|||
|
|||
def __init__(self, data: Optional[MetadataObject] = None) -> None: |
|||
if not data: |
|||
return |
|||
|
|||
for key, value in data.items(): |
|||
if isinstance(value, dict): |
|||
value = Metadata(value) |
|||
elif key.endswith('_id') and isinstance(value, str) and value.isdigit(): |
|||
value = int(value) |
|||
elif (key.endswith('_at') or key.endswith('_date')) and isinstance(value, str): |
|||
try: |
|||
value = parse_time(value) |
|||
except ValueError: |
|||
pass |
|||
elif isinstance(value, list): |
|||
value = [Metadata(x) if isinstance(x, dict) else x for x in value] |
|||
|
|||
self.__dict__[key] = value |
|||
|
|||
def __repr__(self) -> str: |
|||
if not self.__dict__: |
|||
return '<Metadata>' |
|||
return f'<Metadata {" ".join(f"{k}={v!r}" for k, v in self.__dict__.items())}>' |
|||
|
|||
def __eq__(self, other: object) -> bool: |
|||
if not isinstance(other, Metadata): |
|||
return False |
|||
return self.__dict__ == other.__dict__ |
|||
|
|||
def __ne__(self, other: object) -> bool: |
|||
if not isinstance(other, Metadata): |
|||
return True |
|||
return self.__dict__ != other.__dict__ |
|||
|
|||
def __iter__(self) -> Iterator[Tuple[str, Any]]: |
|||
yield from self.__dict__.items() |
|||
|
|||
def __getitem__(self, key: str) -> Any: |
|||
return self.__dict__[key] |
|||
|
|||
def __setitem__(self, key: str, value: Any) -> None: |
|||
self.__dict__[key] = value |
|||
|
|||
def __getattr__(self, _) -> Any: |
|||
return None |
|||
|
|||
def __contains__(self, key: str) -> bool: |
|||
return key in self.__dict__ |
|||
|
|||
def __len__(self) -> int: |
|||
return len(self.__dict__) |
|||
|
|||
def keys(self): |
|||
"""A set-like object providing a view on the metadata's keys.""" |
|||
return self.__dict__.keys() |
|||
|
|||
def values(self): |
|||
"""A set-like object providing a view on the metadata's values.""" |
|||
return self.__dict__.values() |
|||
|
|||
def items(self): |
|||
"""A set-like object providing a view on the metadata's items.""" |
|||
return self.__dict__.items() |
|||
|
|||
|
|||
if TYPE_CHECKING: |
|||
MetadataObject = Union[Metadata, Dict[str, Any]] |
@ -0,0 +1,290 @@ |
|||
""" |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2021-present Dolfies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a |
|||
copy of this software and associated documentation files (the "Software"), |
|||
to deal in the Software without restriction, including without limitation |
|||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|||
and/or sell copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|||
DEALINGS IN THE SOFTWARE. |
|||
""" |
|||
|
|||
from __future__ import annotations |
|||
|
|||
from datetime import datetime |
|||
from typing import TYPE_CHECKING, List, Optional |
|||
|
|||
from .billing import PaymentSource |
|||
from .enums import ( |
|||
PaymentGateway, |
|||
PaymentStatus, |
|||
SubscriptionType, |
|||
try_enum, |
|||
) |
|||
from .flags import PaymentFlags |
|||
from .mixins import Hashable |
|||
from .store import SKU |
|||
from .subscriptions import Subscription |
|||
from .utils import _get_as_snowflake, parse_time |
|||
|
|||
if TYPE_CHECKING: |
|||
from .entitlements import Entitlement |
|||
from .state import ConnectionState |
|||
from .types.payments import ( |
|||
PartialPayment as PartialPaymentPayload, |
|||
Payment as PaymentPayload, |
|||
) |
|||
|
|||
__all__ = ( |
|||
'Payment', |
|||
'EntitlementPayment', |
|||
) |
|||
|
|||
|
|||
class Payment(Hashable): |
|||
"""Represents a payment to Discord. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two payments are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two payments are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the payment's hash. |
|||
|
|||
.. describe:: str(x) |
|||
|
|||
Returns the payment's description. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
id: :class:`int` |
|||
The ID of the payment. |
|||
amount: :class:`int` |
|||
The amount of the payment. |
|||
amount_refunded: :class:`int` |
|||
The amount refunded from the payment, if any. |
|||
tax: :class:`int` |
|||
The amount of tax paid. |
|||
tax_inclusive: :class:`bool` |
|||
Whether the amount is inclusive of all taxes. |
|||
currency: :class:`str` |
|||
The currency the payment was made in. |
|||
description: :class:`str` |
|||
What the payment was for. |
|||
status: :class:`PaymentStatus` |
|||
The status of the payment. |
|||
created_at: :class:`datetime.datetime` |
|||
The time the payment was made. |
|||
sku: Optional[:class:`SKU`] |
|||
The SKU the payment was for, if applicable. |
|||
sku_id: Optional[:class:`int`] |
|||
The ID of the SKU the payment was for, if applicable. |
|||
sku_price: Optional[:class:`int`] |
|||
The price of the SKU the payment was for, if applicable. |
|||
subscription_plan_id: Optional[:class:`int`] |
|||
The ID of the subscription plan the payment was for, if applicable. |
|||
subscription: Optional[:class:`Subscription`] |
|||
The subscription the payment was for, if applicable. |
|||
payment_source: Optional[:class:`PaymentSource`] |
|||
The payment source the payment was made with. |
|||
payment_gateway: Optional[:class:`PaymentGateway`] |
|||
The payment gateway the payment was made with, if applicable. |
|||
payment_gateway_payment_id: Optional[:class:`str`] |
|||
The ID of the payment on the payment gateway, if any. |
|||
invoice_url: Optional[:class:`str`] |
|||
The URL to download the VAT invoice for this payment, if available. |
|||
refund_invoices_urls: List[:class:`str`] |
|||
A list of URLs to download VAT credit notices for refunds on this payment, if available. |
|||
refund_disqualification_reasons: List[:class:`str`] |
|||
A list of reasons why the payment cannot be refunded, if any. |
|||
""" |
|||
|
|||
__slots__ = ( |
|||
'id', |
|||
'amount', |
|||
'amount_refunded', |
|||
'tax', |
|||
'tax_inclusive', |
|||
'currency', |
|||
'description', |
|||
'status', |
|||
'created_at', |
|||
'sku', |
|||
'sku_id', |
|||
'sku_price', |
|||
'subscription_plan_id', |
|||
'subscription', |
|||
'payment_source', |
|||
'payment_gateway', |
|||
'payment_gateway_payment_id', |
|||
'invoice_url', |
|||
'refund_invoices_urls', |
|||
'refund_disqualification_reasons', |
|||
'_flags', |
|||
'_state', |
|||
) |
|||
|
|||
def __init__(self, *, data: PaymentPayload, state: ConnectionState): |
|||
self._state: ConnectionState = state |
|||
self._update(data) |
|||
|
|||
def _update(self, data: PaymentPayload) -> None: |
|||
state = self._state |
|||
|
|||
self.id: int = int(data['id']) |
|||
self.amount: int = data['amount'] |
|||
self.amount_refunded: int = data.get('amount_refunded') or 0 |
|||
self.tax: int = data.get('tax') or 0 |
|||
self.tax_inclusive: bool = data.get('tax_inclusive', True) |
|||
self.currency: str = data.get('currency', 'usd') |
|||
self.description: str = data['description'] |
|||
self.status: PaymentStatus = try_enum(PaymentStatus, data['status']) |
|||
self.created_at: datetime = parse_time(data['created_at']) |
|||
self.sku: Optional[SKU] = SKU(data=data['sku'], state=state) if 'sku' in data else None |
|||
self.sku_id: Optional[int] = _get_as_snowflake(data, 'sku_id') |
|||
self.sku_price: Optional[int] = data.get('sku_price') |
|||
self.subscription_plan_id: Optional[int] = _get_as_snowflake(data, 'sku_subscription_plan_id') |
|||
self.payment_gateway: Optional[PaymentGateway] = ( |
|||
try_enum(PaymentGateway, data['payment_gateway']) if 'payment_gateway' in data else None |
|||
) |
|||
self.payment_gateway_payment_id: Optional[str] = data.get('payment_gateway_payment_id') |
|||
self.invoice_url: Optional[str] = data.get('downloadable_invoice') |
|||
self.refund_invoices_urls: List[str] = data.get('downloadable_refund_invoices', []) |
|||
self.refund_disqualification_reasons: List[str] = data.get('premium_refund_disqualification_reasons', []) |
|||
self._flags: int = data.get('flags', 0) |
|||
|
|||
# The subscription object does not include the payment source ID |
|||
self.payment_source: Optional[PaymentSource] = ( |
|||
PaymentSource(data=data['payment_source'], state=state) if 'payment_source' in data else None |
|||
) |
|||
if 'subscription' in data and self.payment_source: |
|||
data['subscription']['payment_source_id'] = self.payment_source.id # type: ignore |
|||
self.subscription: Optional[Subscription] = ( |
|||
Subscription(data=data['subscription'], state=state) if 'subscription' in data else None |
|||
) |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<Payment id={self.id} amount={self.amount} currency={self.currency} status={self.status}>' |
|||
|
|||
def __str__(self) -> str: |
|||
return self.description |
|||
|
|||
def is_subscription(self) -> bool: |
|||
""":class:`bool`: Whether the payment was for a subscription.""" |
|||
return self.subscription is not None |
|||
|
|||
def is_premium_subscription(self) -> bool: |
|||
""":class:`bool`: Whether the payment was for a Discord premium subscription.""" |
|||
return self.subscription is not None and self.subscription.type == SubscriptionType.premium |
|||
|
|||
def is_premium_subscription_gift(self) -> bool: |
|||
""":class:`bool`: Whether the payment was for a Discord premium subscription gift.""" |
|||
return self.flags.gift and self.sku_id in self._state.premium_subscriptions_sku_ids.values() |
|||
|
|||
def is_purchased_externally(self) -> bool: |
|||
""":class:`bool`: Whether the payment was made externally.""" |
|||
return self.payment_gateway in (PaymentGateway.apple, PaymentGateway.google) |
|||
|
|||
@property |
|||
def flags(self) -> PaymentFlags: |
|||
""":class:`PaymentFlags`: Returns the payment's flags.""" |
|||
return PaymentFlags._from_value(self._flags) |
|||
|
|||
async def void(self) -> None: |
|||
"""|coro| |
|||
|
|||
Void the payment. Only applicable for payments of status :attr:`PaymentStatus.pending`. |
|||
|
|||
Raises |
|||
------ |
|||
HTTPException |
|||
Voiding the payment failed. |
|||
""" |
|||
await self._state.http.void_payment(self.id) |
|||
self.status = PaymentStatus.failed |
|||
|
|||
async def refund(self, reason: Optional[int] = None) -> None: |
|||
"""|coro| |
|||
|
|||
Refund the payment. |
|||
|
|||
Raises |
|||
------ |
|||
HTTPException |
|||
Refunding the payment failed. |
|||
""" |
|||
# reason here is an enum (0-8), but I was unable to find the enum values |
|||
# Either way, it's optional and this endpoint isn't really used anyway |
|||
await self._state.http.refund_payment(self.id, reason) |
|||
self.status = PaymentStatus.refunded |
|||
|
|||
|
|||
class EntitlementPayment(Hashable): |
|||
"""Represents a partial payment for an entitlement. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two payments are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two payments are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the payment's hash. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
entitlement: :class:`Entitlement` |
|||
The entitlement the payment is for. |
|||
id: :class:`int` |
|||
The ID of the payment. |
|||
amount: :class:`int` |
|||
The amount of the payment. |
|||
tax: :class:`int` |
|||
The amount of tax paid. |
|||
tax_inclusive: :class:`bool` |
|||
Whether the amount is inclusive of all taxes. |
|||
currency: :class:`str` |
|||
The currency the payment was made in. |
|||
""" |
|||
|
|||
__slots__ = ('entitlement', 'id', 'amount', 'tax', 'tax_inclusive', 'currency') |
|||
|
|||
def __init__(self, *, data: PartialPaymentPayload, entitlement: Entitlement): |
|||
self.entitlement = entitlement |
|||
self.id: int = int(data['id']) |
|||
self.amount: int = data['amount'] |
|||
self.tax: int = data.get('tax') or 0 |
|||
self.tax_inclusive: bool = data.get('tax_inclusive', True) |
|||
self.currency: str = data.get('currency', 'usd') |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<EntitlementPayment id={self.id} amount={self.amount} currency={self.currency}>' |
@ -0,0 +1,306 @@ |
|||
""" |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2021-present Dolfies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a |
|||
copy of this software and associated documentation files (the "Software"), |
|||
to deal in the Software without restriction, including without limitation |
|||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|||
and/or sell copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|||
DEALINGS IN THE SOFTWARE. |
|||
""" |
|||
|
|||
from __future__ import annotations |
|||
|
|||
from datetime import datetime |
|||
from typing import TYPE_CHECKING, List, Optional, Union |
|||
|
|||
from .enums import PaymentSourceType, try_enum |
|||
from .flags import PromotionFlags |
|||
from .mixins import Hashable |
|||
from .subscriptions import SubscriptionTrial |
|||
from .utils import _get_as_snowflake, parse_time, utcnow |
|||
|
|||
if TYPE_CHECKING: |
|||
from .state import ConnectionState |
|||
from .types.promotions import ( |
|||
ClaimedPromotion as ClaimedPromotionPayload, |
|||
Promotion as PromotionPayload, |
|||
TrialOffer as TrialOfferPayload, |
|||
PricingPromotion as PricingPromotionPayload, |
|||
) |
|||
|
|||
__all__ = ( |
|||
'Promotion', |
|||
'TrialOffer', |
|||
'PricingPromotion', |
|||
) |
|||
|
|||
|
|||
class Promotion(Hashable): |
|||
"""Represents a Discord promotion. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two promotions are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two promotions are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the promotion's hash. |
|||
|
|||
.. describe:: str(x) |
|||
|
|||
Returns the outbound promotion's name. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
id: :class:`int` |
|||
The promotion ID. |
|||
trial_id: Optional[:class:`int`] |
|||
The trial ID of the inbound promotion, if applicable. |
|||
starts_at: :class:`datetime.datetime` |
|||
When the promotion starts. |
|||
ends_at: :class:`datetime.datetime` |
|||
When the promotion ends. |
|||
claimed_at: Optional[:class:`datetime.datetime`] |
|||
When the promotion was claimed. |
|||
Only available for claimed promotions. |
|||
code: Optional[:class:`str`] |
|||
The promotion's claim code. Only available for claimed promotions. |
|||
outbound_title: :class:`str` |
|||
The title of the outbound promotion. |
|||
outbound_description: :class:`str` |
|||
The description of the outbound promotion. |
|||
outbound_link: :class:`str` |
|||
The redemption page of the outbound promotion, used to claim it. |
|||
outbound_restricted_countries: List[:class:`str`] |
|||
The countries that the outbound promotion is not available in. |
|||
inbound_title: Optional[:class:`str`] |
|||
The title of the inbound promotion. This is usually Discord Nitro. |
|||
inbound_description: Optional[:class:`str`] |
|||
The description of the inbound promotion. |
|||
inbound_link: Optional[:class:`str`] |
|||
The Discord help center link of the inbound promotion. |
|||
inbound_restricted_countries: List[:class:`str`] |
|||
The countries that the inbound promotion is not available in. |
|||
terms_and_conditions: :class:`str` |
|||
The terms and conditions of the promotion. |
|||
""" |
|||
|
|||
__slots__ = ( |
|||
'id', |
|||
'trial_id', |
|||
'starts_at', |
|||
'ends_at', |
|||
'claimed_at', |
|||
'code', |
|||
'outbound_title', |
|||
'outbound_description', |
|||
'outbound_link', |
|||
'outbound_restricted_countries', |
|||
'inbound_title', |
|||
'inbound_description', |
|||
'inbound_link', |
|||
'inbound_restricted_countries', |
|||
'terms_and_conditions', |
|||
'_flags', |
|||
'_state', |
|||
) |
|||
|
|||
def __init__(self, *, data: Union[PromotionPayload, ClaimedPromotionPayload], state: ConnectionState) -> None: |
|||
self._state = state |
|||
self._update(data) |
|||
|
|||
def __str__(self) -> str: |
|||
return self.outbound_title |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<Promotion id={self.id} title={self.outbound_title!r}>' |
|||
|
|||
def _update(self, data: Union[PromotionPayload, ClaimedPromotionPayload]) -> None: |
|||
promotion: PromotionPayload = data.get('promotion', data) # type: ignore |
|||
|
|||
self.id: int = int(promotion['id']) |
|||
self.trial_id: Optional[int] = _get_as_snowflake(promotion, 'trial_id') |
|||
self.starts_at: datetime = parse_time(promotion['start_date']) |
|||
self.ends_at: datetime = parse_time(promotion['end_date']) |
|||
self.claimed_at: Optional[datetime] = parse_time(data.get('claimed_at')) |
|||
self.code: Optional[str] = data.get('code') |
|||
self._flags: int = promotion.get('flags', 0) |
|||
|
|||
self.outbound_title: str = promotion['outbound_title'] |
|||
self.outbound_description: str = promotion['outbound_redemption_modal_body'] |
|||
self.outbound_link: str = promotion.get( |
|||
'outbound_redemption_page_link', |
|||
promotion.get('outbound_redemption_url_format', '').replace('{code}', self.code or '{code}'), |
|||
) |
|||
self.outbound_restricted_countries: List[str] = promotion.get('outbound_restricted_countries', []) |
|||
self.inbound_title: Optional[str] = promotion.get('inbound_header_text') |
|||
self.inbound_description: Optional[str] = promotion.get('inbound_body_text') |
|||
self.inbound_link: Optional[str] = promotion.get('inbound_help_center_link') |
|||
self.inbound_restricted_countries: List[str] = promotion.get('inbound_restricted_countries', []) |
|||
self.terms_and_conditions: str = promotion['outbound_terms_and_conditions'] |
|||
|
|||
@property |
|||
def flags(self) -> PromotionFlags: |
|||
""":class:`PromotionFlags`: Returns the promotion's flags.""" |
|||
return PromotionFlags._from_value(self._flags) |
|||
|
|||
def is_claimed(self) -> bool: |
|||
""":class:`bool`: Checks if the promotion has been claimed. |
|||
|
|||
Only accurate if the promotion was fetched from :meth:`Client.promotions` with ``claimed`` set to ``True`` or :meth:`claim` was just called. |
|||
""" |
|||
return self.claimed_at is not None |
|||
|
|||
def is_active(self) -> bool: |
|||
""":class:`bool`: Checks if the promotion is active.""" |
|||
return self.starts_at <= utcnow() <= self.ends_at |
|||
|
|||
async def claim(self) -> str: |
|||
"""|coro| |
|||
|
|||
Claims the promotion. |
|||
|
|||
Sets :attr:`claimed_at` and :attr:`code`. |
|||
|
|||
Raises |
|||
------ |
|||
Forbidden |
|||
You are not allowed to claim the promotion. |
|||
HTTPException |
|||
Claiming the promotion failed. |
|||
|
|||
Returns |
|||
------- |
|||
:class:`str` |
|||
The claim code for the outbound promotion. |
|||
""" |
|||
data = await self._state.http.claim_promotion(self.id) |
|||
self._update(data) |
|||
return data['code'] |
|||
|
|||
|
|||
class TrialOffer(Hashable): |
|||
"""Represents a Discord user trial offer. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two trial offers are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two trial offers are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the trial offer's hash. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
id: :class:`int` |
|||
The ID of the trial offer. |
|||
expires_at: :class:`datetime.datetime` |
|||
When the trial offer expires. |
|||
trial_id: :class:`int` |
|||
The ID of the trial. |
|||
trial: :class:`SubscriptionTrial` |
|||
The trial offered. |
|||
""" |
|||
|
|||
__slots__ = ( |
|||
'id', |
|||
'expires_at', |
|||
'trial_id', |
|||
'trial', |
|||
'_state', |
|||
) |
|||
|
|||
def __init__(self, *, data: TrialOfferPayload, state: ConnectionState) -> None: |
|||
self._state = state |
|||
|
|||
self.id: int = int(data['id']) |
|||
self.expires_at: datetime = parse_time(data['expires_at']) |
|||
self.trial_id: int = int(data['trial_id']) |
|||
self.trial: SubscriptionTrial = SubscriptionTrial(data['subscription_trial']) |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<TrialOffer id={self.id} trial={self.trial!r}>' |
|||
|
|||
async def ack(self) -> None: |
|||
"""|coro| |
|||
|
|||
Acknowledges the trial offer. |
|||
|
|||
Raises |
|||
------ |
|||
HTTPException |
|||
Acknowledging the trial offer failed. |
|||
""" |
|||
await self._state.http.ack_trial_offer(self.id) |
|||
|
|||
|
|||
class PricingPromotion: |
|||
"""Represents a Discord localized pricing promotion. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
subscription_plan_id: :class:`int` |
|||
The ID of the subscription plan the promotion is for. |
|||
country_code: :class:`str` |
|||
The country code the promotion applies to. |
|||
payment_source_types: List[:class:`PaymentSourceType`] |
|||
The payment source types the promotion is restricted to. |
|||
amount: :class:`int` |
|||
The discounted price of the subscription plan. |
|||
currency: :class:`str` |
|||
The currency of the discounted price. |
|||
""" |
|||
|
|||
__slots__ = ( |
|||
'subscription_plan_id', |
|||
'country_code', |
|||
'payment_source_types', |
|||
'amount', |
|||
'currency', |
|||
) |
|||
|
|||
def __init__(self, *, data: PricingPromotionPayload) -> None: |
|||
self.subscription_plan_id: int = int(data['plan_id']) |
|||
self.country_code: str = data['country_code'] |
|||
self.payment_source_types: List[PaymentSourceType] = [ |
|||
try_enum(PaymentSourceType, t) for t in data['payment_source_types'] |
|||
] |
|||
|
|||
price = data['price'] |
|||
self.amount: int = price['amount'] |
|||
self.currency: str = price['currency'] |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<PricingPromotion plan_id={self.subscription_plan_id} country_code={self.country_code!r} amount={self.amount} currency={self.currency!r}>' |
File diff suppressed because it is too large
@ -0,0 +1,859 @@ |
|||
""" |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2021-present Dolfies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a |
|||
copy of this software and associated documentation files (the "Software"), |
|||
to deal in the Software without restriction, including without limitation |
|||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|||
and/or sell copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|||
DEALINGS IN THE SOFTWARE. |
|||
""" |
|||
|
|||
from __future__ import annotations |
|||
|
|||
from datetime import datetime, timedelta |
|||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union |
|||
|
|||
from .billing import PaymentSource |
|||
from .enums import ( |
|||
PaymentGateway, |
|||
SubscriptionDiscountType, |
|||
SubscriptionInterval, |
|||
SubscriptionInvoiceStatus, |
|||
SubscriptionStatus, |
|||
SubscriptionType, |
|||
try_enum, |
|||
) |
|||
from .metadata import Metadata |
|||
from .mixins import Hashable |
|||
from .utils import MISSING, _get_as_snowflake, parse_time, snowflake_time, utcnow |
|||
|
|||
if TYPE_CHECKING: |
|||
from typing_extensions import Self |
|||
|
|||
from .abc import Snowflake |
|||
from .guild import Guild |
|||
from .state import ConnectionState |
|||
from .types.subscriptions import ( |
|||
SubscriptionDiscount as SubscriptionDiscountPayload, |
|||
SubscriptionInvoice as SubscriptionInvoicePayload, |
|||
SubscriptionInvoiceItem as SubscriptionInvoiceItemPayload, |
|||
SubscriptionItem as SubscriptionItemPayload, |
|||
SubscriptionRenewalMutations as SubscriptionRenewalMutationsPayload, |
|||
PartialSubscription as PartialSubscriptionPayload, |
|||
Subscription as SubscriptionPayload, |
|||
SubscriptionTrial as SubscriptionTrialPayload, |
|||
) |
|||
|
|||
__all__ = ( |
|||
'SubscriptionItem', |
|||
'SubscriptionDiscount', |
|||
'SubscriptionInvoiceItem', |
|||
'SubscriptionInvoice', |
|||
'SubscriptionRenewalMutations', |
|||
'Subscription', |
|||
'SubscriptionTrial', |
|||
) |
|||
|
|||
|
|||
class SubscriptionItem(Hashable): |
|||
"""Represents a Discord subscription item. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two subscription items are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two subscription items are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the item's hash. |
|||
|
|||
.. describe:: len(x) |
|||
|
|||
Returns the quantity of the subscription item. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
id: Optional[:class:`int`] |
|||
The ID of the subscription item. Always available when received from the API. |
|||
quantity: :class:`int` |
|||
How many of the item have been/are being purchased. |
|||
plan_id: :class:`int` |
|||
The ID of the plan the item is for. |
|||
""" |
|||
|
|||
__slots__ = ('id', 'quantity', 'plan_id') |
|||
|
|||
def __init__(self, *, id: Optional[int] = None, plan_id: int, quantity: int = 1) -> None: |
|||
self.id: Optional[int] = id |
|||
self.quantity: int = quantity |
|||
self.plan_id: int = plan_id |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<SubscriptionItem {f"id={self.id} " if self.id else ""}plan_id={self.plan_id} quantity={self.quantity}>' |
|||
|
|||
def __len__(self) -> int: |
|||
return self.quantity |
|||
|
|||
@classmethod |
|||
def from_dict(cls, data: SubscriptionItemPayload) -> Self: |
|||
return cls(id=int(data['id']), plan_id=int(data['plan_id']), quantity=int(data.get('quantity', 1))) |
|||
|
|||
def to_dict(self, with_id: bool = True) -> dict: |
|||
data = { |
|||
'quantity': self.quantity, |
|||
'plan_id': self.plan_id, |
|||
} |
|||
if self.id and with_id: |
|||
data['id'] = self.id |
|||
|
|||
return data |
|||
|
|||
|
|||
class SubscriptionDiscount: |
|||
"""Represents a discount on a Discord subscription item. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: int(x) |
|||
|
|||
Returns the discount's amount. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
type: :class:`SubscriptionDiscountType` |
|||
The type of the discount. |
|||
amount: :class:`int` |
|||
How much the discount is. |
|||
""" |
|||
|
|||
__slots__ = ('type', 'amount') |
|||
|
|||
def __init__(self, data: SubscriptionDiscountPayload) -> None: |
|||
self.type: SubscriptionDiscountType = try_enum(SubscriptionDiscountType, data['type']) |
|||
self.amount: int = data['amount'] |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<SubscriptionDiscount type={self.type!r} amount={self.amount}>' |
|||
|
|||
def __int__(self) -> int: |
|||
return self.amount |
|||
|
|||
|
|||
class SubscriptionInvoiceItem(Hashable): |
|||
"""Represents an invoice item. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two invoice items are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two invoice items are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the invoice's hash. |
|||
|
|||
.. describe:: len(x) |
|||
|
|||
Returns the quantity of the invoice item. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
id: :class:`int` |
|||
The ID of the invoice item. |
|||
quantity: :class:`int` |
|||
How many of the item have been/are being purchased. |
|||
amount: :class:`int` |
|||
The price of the item. This includes discounts. |
|||
proration: :class:`bool` |
|||
Whether the item is prorated. |
|||
plan_id: :class:`int` |
|||
The ID of the subscription plan the item represents. |
|||
plan_price: :class:`int` |
|||
The price of the subscription plan the item represents. This does not include discounts. |
|||
discounts: List[:class:`SubscriptionDiscount`] |
|||
A list of discounts applied to the item. |
|||
""" |
|||
|
|||
__slots__ = ('id', 'quantity', 'amount', 'proration', 'plan_id', 'plan_price', 'discounts') |
|||
|
|||
def __init__(self, data: SubscriptionInvoiceItemPayload) -> None: |
|||
self.id: int = int(data['id']) |
|||
self.quantity: int = data['quantity'] |
|||
self.amount: int = data['amount'] |
|||
self.proration: bool = data.get('proration', False) |
|||
self.plan_id: int = int(data['subscription_plan_id']) |
|||
self.plan_price: int = data['subscription_plan_price'] |
|||
self.discounts: List[SubscriptionDiscount] = [SubscriptionDiscount(d) for d in data['discounts']] |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<SubscriptionInvoiceItem id={self.id} quantity={self.quantity} amount={self.amount}>' |
|||
|
|||
def __len__(self) -> int: |
|||
return self.quantity |
|||
|
|||
@property |
|||
def savings(self) -> int: |
|||
""":class:`int`: The total amount of discounts on the invoice item.""" |
|||
return self.plan_price - self.amount |
|||
|
|||
def is_discounted(self) -> bool: |
|||
""":class:`bool`: Indicates if the invoice item has a discount.""" |
|||
return bool(self.discounts) |
|||
|
|||
def is_trial(self) -> bool: |
|||
""":class:`bool`: Indicates if the invoice item is a trial.""" |
|||
return not self.amount or any(discount.type is SubscriptionDiscountType.premium_trial for discount in self.discounts) |
|||
|
|||
|
|||
class SubscriptionInvoice(Hashable): |
|||
"""Represents an invoice for a Discord subscription. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two invoices are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two invoices are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the invoice's hash. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
subscription: Optional[:class:`Subscription`] |
|||
The subscription the invoice is for. Not available for new subscription previews. |
|||
id: :class:`int` |
|||
The ID of the invoice. |
|||
status: Optional[:class:`SubscriptionInvoiceStatus`] |
|||
The status of the invoice. Not available for subscription previews. |
|||
currency: :class:`str` |
|||
The currency the invoice is in. |
|||
subtotal: :class:`int` |
|||
The subtotal of the invoice. |
|||
tax: :class:`int` |
|||
The tax applied to the invoice. |
|||
total: :class:`int` |
|||
The total of the invoice. |
|||
tax_inclusive: :class:`bool` |
|||
Whether the subtotal is inclusive of all taxes. |
|||
items: List[:class:`SubscriptionInvoiceItem`] |
|||
The items in the invoice. |
|||
current_period_start: :class:`datetime.datetime` |
|||
When the current billing period started. |
|||
current_period_end: :class:`datetime.datetime` |
|||
When the current billing period ends. |
|||
""" |
|||
|
|||
__slots__ = ( |
|||
'_state', |
|||
'subscription', |
|||
'id', |
|||
'status', |
|||
'currency', |
|||
'subtotal', |
|||
'tax', |
|||
'total', |
|||
'tax_inclusive', |
|||
'items', |
|||
'current_period_start', |
|||
'current_period_end', |
|||
) |
|||
|
|||
def __init__( |
|||
self, subscription: Optional[Subscription], *, data: SubscriptionInvoicePayload, state: ConnectionState |
|||
) -> None: |
|||
self._state = state |
|||
self.subscription = subscription |
|||
self._update(data) |
|||
|
|||
def _update(self, data: SubscriptionInvoicePayload) -> None: |
|||
self.id: int = int(data['id']) |
|||
self.status: Optional[SubscriptionInvoiceStatus] = ( |
|||
try_enum(SubscriptionInvoiceStatus, data['status']) if 'status' in data else None |
|||
) |
|||
self.currency: str = data['currency'] |
|||
self.subtotal: int = data['subtotal'] |
|||
self.tax: int = data.get('tax', 0) |
|||
self.total: int = data['total'] |
|||
self.tax_inclusive: bool = data['tax_inclusive'] |
|||
self.items: List[SubscriptionInvoiceItem] = [SubscriptionInvoiceItem(d) for d in data.get('invoice_items', [])] |
|||
|
|||
self.current_period_start: datetime = parse_time(data['subscription_period_start']) # type: ignore # Should always be a datetime |
|||
self.current_period_end: datetime = parse_time(data['subscription_period_end']) # type: ignore # Should always be a datetime |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<SubscriptionInvoice id={self.id} status={self.status!r} total={self.total}>' |
|||
|
|||
def is_discounted(self) -> bool: |
|||
""":class:`bool`: Indicates if the invoice has a discount.""" |
|||
return any(item.discounts for item in self.items) |
|||
|
|||
def is_preview(self) -> bool: |
|||
""":class:`bool`: Indicates if the invoice is a preview and not real.""" |
|||
return self.subscription is None or self.status is None |
|||
|
|||
async def pay( |
|||
self, |
|||
payment_source: Optional[Snowflake] = None, |
|||
currency: str = 'usd', |
|||
*, |
|||
payment_source_token: Optional[str] = None, |
|||
return_url: Optional[str] = None, |
|||
) -> None: |
|||
"""|coro| |
|||
|
|||
Pays the invoice. |
|||
|
|||
Parameters |
|||
---------- |
|||
payment_source: Optional[:class:`PaymentSource`] |
|||
The payment source the invoice should be paid with. |
|||
currency: :class:`str` |
|||
The currency to pay with. |
|||
payment_source_token: Optional[:class:`str`] |
|||
The token used to authorize with the payment source. |
|||
return_url: Optional[:class:`str`] |
|||
The URL to return to after the payment is complete. |
|||
|
|||
Raises |
|||
------ |
|||
TypeError |
|||
The invoice is a preview and not real. |
|||
NotFound |
|||
The invoice is not open or found. |
|||
HTTPException |
|||
Paying the invoice failed. |
|||
""" |
|||
if self.is_preview() or not self.subscription: |
|||
raise TypeError('Cannot pay a nonexistant invoice') |
|||
|
|||
data = await self._state.http.pay_invoice( |
|||
self.subscription.id, |
|||
self.id, |
|||
payment_source.id if payment_source else None, |
|||
payment_source_token, |
|||
currency, |
|||
return_url, |
|||
) |
|||
self.subscription._update(data) |
|||
|
|||
|
|||
class SubscriptionRenewalMutations: |
|||
"""Represents a subscription renewal mutation. |
|||
|
|||
This represents changes to a subscription that will occur after renewal. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: len(x) |
|||
|
|||
Returns the number of items in the changed subscription, including quantity. |
|||
|
|||
.. describe:: bool(x) |
|||
|
|||
Returns whether any mutations are present. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
payment_gateway_plan_id: Optional[:class:`str`] |
|||
The payment gateway's new plan ID for the subscription. |
|||
This signifies an external plan change. |
|||
items: Optional[List[:class:`SubscriptionItem`]] |
|||
The new items of the subscription. |
|||
""" |
|||
|
|||
__slots__ = ('payment_gateway_plan_id', 'items') |
|||
|
|||
def __init__(self, data: SubscriptionRenewalMutationsPayload) -> None: |
|||
self.payment_gateway_plan_id: Optional[str] = data.get('payment_gateway_plan_id') |
|||
self.items: Optional[List[SubscriptionItem]] = ( |
|||
[SubscriptionItem.from_dict(item) for item in data['items']] if 'items' in data else None |
|||
) |
|||
|
|||
def __repr__(self) -> str: |
|||
return ( |
|||
f'<SubscriptionRenewalMutations payment_gateway_plan_id={self.payment_gateway_plan_id!r} items={self.items!r}>' |
|||
) |
|||
|
|||
def __len__(self) -> int: |
|||
return sum(item.quantity for item in self.items) if self.items else 0 |
|||
|
|||
def __bool__(self) -> bool: |
|||
return self.is_mutated() |
|||
|
|||
def is_mutated(self) -> bool: |
|||
""":class:`bool`: Checks if any renewal mutations exist.""" |
|||
return self.payment_gateway_plan_id is not None or self.items is not None |
|||
|
|||
|
|||
class Subscription(Hashable): |
|||
"""Represents a Discord subscription. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two premium subscriptions are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two premium subscriptions are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the subscription's hash. |
|||
|
|||
.. describe:: len(x) |
|||
|
|||
Returns the number of items in the subscription, including quantity. |
|||
|
|||
.. describe:: bool(x) |
|||
|
|||
Checks if the subscription is currently active and offering perks. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
id: :class:`int` |
|||
The ID of the subscription. |
|||
type: :class:`SubscriptionType` |
|||
The type of the subscription. |
|||
status: Optional[:class:`SubscriptionStatus`] |
|||
The status of the subscription. This is ``None`` for fake subscriptions. |
|||
payment_gateway: Optional[:class:`PaymentGateway`] |
|||
The payment gateway used to bill the subscription. |
|||
currency: :class:`str` |
|||
The currency the subscription is billed in. |
|||
items: List[:class:`SubscriptionItem`] |
|||
The items in the subscription. |
|||
renewal_mutations: :class:`SubscriptionRenewalMutations` |
|||
The mutations to the subscription that will occur after renewal. |
|||
trial_id: Optional[:class:`int`] |
|||
The ID of the trial the subscription is from, if applicable. |
|||
payment_source_id: Optional[:class:`int`] |
|||
The ID of the payment source the subscription is paid with, if applicable. |
|||
payment_gateway_plan_id: Optional[:class:`str`] |
|||
The payment gateway's plan ID for the subscription, if applicable. |
|||
payment_gateway_subscription_id: Optional[:class:`str`] |
|||
The payment gateway's subscription ID for the subscription, if applicable. |
|||
created_at: :class:`datetime.datetime` |
|||
When the subscription was created. |
|||
canceled_at: Optional[:class:`datetime.datetime`] |
|||
When the subscription was canceled. |
|||
This is only available for subscriptions with a :attr:`status` of :attr:`SubscriptionStatus.canceled`. |
|||
current_period_start: :class:`datetime.datetime` |
|||
When the current billing period started. |
|||
current_period_end: :class:`datetime.datetime` |
|||
When the current billing period ends. |
|||
trial_ends_at: Optional[:class:`datetime.datetime`] |
|||
When the trial ends, if applicable. |
|||
streak_started_at: Optional[:class:`datetime.datetime`] |
|||
When the current subscription streak started. |
|||
ended_at: Optional[:class:`datetime.datetime`] |
|||
When the subscription finally ended. |
|||
metadata: :class:`Metadata` |
|||
Extra metadata about the subscription. |
|||
latest_invoice: Optional[:class:`SubscriptionInvoice`] |
|||
The latest invoice for the subscription, if applicable. |
|||
""" |
|||
|
|||
__slots__ = ( |
|||
'_state', |
|||
'id', |
|||
'type', |
|||
'status', |
|||
'payment_gateway', |
|||
'currency', |
|||
'items', |
|||
'renewal_mutations', |
|||
'trial_id', |
|||
'payment_source_id', |
|||
'payment_gateway_plan_id', |
|||
'payment_gateway_subscription_id', |
|||
'created_at', |
|||
'canceled_at', |
|||
'current_period_start', |
|||
'current_period_end', |
|||
'trial_ends_at', |
|||
'streak_started_at', |
|||
'ended_at', |
|||
'metadata', |
|||
'latest_invoice', |
|||
) |
|||
|
|||
def __init__(self, *, data: Union[PartialSubscriptionPayload, SubscriptionPayload], state: ConnectionState) -> None: |
|||
self._state = state |
|||
self._update(data) |
|||
|
|||
def __repr__(self) -> str: |
|||
return f'<Subscription id={self.id} currency={self.currency!r} items={self.items!r}>' |
|||
|
|||
def __len__(self) -> int: |
|||
return sum(item.quantity for item in self.items) |
|||
|
|||
def __bool__(self) -> bool: |
|||
return self.is_active() |
|||
|
|||
def _update(self, data: PartialSubscriptionPayload) -> None: |
|||
self.id: int = int(data['id']) |
|||
self.type: SubscriptionType = try_enum(SubscriptionType, data['type']) |
|||
self.status: Optional[SubscriptionStatus] = ( |
|||
try_enum(SubscriptionStatus, data['status']) if 'status' in data else None # type: ignore # ??? |
|||
) |
|||
self.payment_gateway: Optional[PaymentGateway] = ( |
|||
try_enum(PaymentGateway, data['payment_gateway']) if 'payment_gateway' in data else None |
|||
) |
|||
self.currency: str = data.get('currency', 'usd') |
|||
self.items: List[SubscriptionItem] = [SubscriptionItem.from_dict(item) for item in data.get('items', [])] |
|||
self.renewal_mutations: SubscriptionRenewalMutations = SubscriptionRenewalMutations( |
|||
data.get('renewal_mutations') or {} |
|||
) |
|||
|
|||
self.trial_id: Optional[int] = _get_as_snowflake(data, 'trial_id') |
|||
self.payment_source_id: Optional[int] = _get_as_snowflake(data, 'payment_source_id') |
|||
self.payment_gateway_plan_id: Optional[str] = data.get('payment_gateway_plan_id') |
|||
self.payment_gateway_subscription_id: Optional[str] = data.get('payment_gateway_subscription_id') |
|||
|
|||
self.created_at: datetime = parse_time(data.get('created_at')) or snowflake_time(self.id) |
|||
self.canceled_at: Optional[datetime] = parse_time(data.get('canceled_at')) |
|||
|
|||
self.current_period_start: datetime = parse_time(data['current_period_start']) |
|||
self.current_period_end: datetime = parse_time(data['current_period_end']) |
|||
self.trial_ends_at: Optional[datetime] = parse_time(data.get('trial_ends_at')) |
|||
self.streak_started_at: Optional[datetime] = parse_time(data.get('streak_started_at')) |
|||
|
|||
metadata = data.get('metadata') or {} |
|||
self.ended_at: Optional[datetime] = parse_time(metadata.get('ended_at', None)) |
|||
self.metadata: Metadata = Metadata(metadata) |
|||
|
|||
self.latest_invoice: Optional[SubscriptionInvoice] = ( |
|||
SubscriptionInvoice(self, data=data['latest_invoice'], state=self._state) if 'latest_invoice' in data else None # type: ignore # ??? |
|||
) |
|||
|
|||
@property |
|||
def cancelled_at(self) -> Optional[datetime]: |
|||
"""Optional[:class:`datetime.datetime`]: When the subscription was canceled. |
|||
This is only available for subscriptions with a :attr:`status` of :attr:`SubscriptionStatus.canceled`. |
|||
|
|||
This is an alias of :attr:`canceled_at`. |
|||
""" |
|||
return self.canceled_at |
|||
|
|||
@property |
|||
def guild(self) -> Optional[Guild]: |
|||
""":class:`Guild`: The guild the subscription's entitlements apply to, if applicable.""" |
|||
return self._state._get_guild(self.metadata.guild_id) |
|||
|
|||
@property |
|||
def grace_period(self) -> int: |
|||
""":class:`int`: How many days past the renewal date the user has available to pay outstanding invoices. |
|||
|
|||
.. note:: |
|||
|
|||
This is a static value and does not change based on the subscription's status. |
|||
For that, see :attr:`remaining`. |
|||
""" |
|||
return 7 if self.payment_source_id else 3 |
|||
|
|||
@property |
|||
def remaining(self) -> timedelta: |
|||
""":class:`datetime.timedelta`: The remaining time until the subscription ends.""" |
|||
if self.status in (SubscriptionStatus.active, SubscriptionStatus.cancelled): |
|||
return self.current_period_end - utcnow() |
|||
elif self.status == SubscriptionStatus.past_due: |
|||
if self.payment_gateway == PaymentGateway.google and self.metadata.google_grace_period_expires_date: |
|||
return self.metadata.google_grace_period_expires_date - utcnow() |
|||
return (self.current_period_start + timedelta(days=self.grace_period)) - utcnow() |
|||
elif self.status == SubscriptionStatus.account_hold: |
|||
# Max hold time is 30 days |
|||
return (self.current_period_start + timedelta(days=30)) - utcnow() |
|||
return timedelta() |
|||
|
|||
@property |
|||
def trial_remaining(self) -> timedelta: |
|||
""":class:`datetime.timedelta`: The remaining time until the trial applied to the subscription ends.""" |
|||
if not self.trial_id: |
|||
return timedelta() |
|||
if not self.trial_ends_at: |
|||
# Infinite trial? |
|||
return self.remaining |
|||
return self.trial_ends_at - utcnow() |
|||
|
|||
def is_active(self) -> bool: |
|||
""":class:`bool`: Indicates if the subscription is currently active and providing perks.""" |
|||
return self.remaining > timedelta() |
|||
|
|||
def is_trial(self) -> bool: |
|||
""":class:`bool`: Indicates if the subscription is a trial.""" |
|||
return self.trial_id is not None |
|||
|
|||
async def edit( |
|||
self, |
|||
items: List[SubscriptionItem] = MISSING, |
|||
payment_source: Snowflake = MISSING, |
|||
currency: str = MISSING, |
|||
*, |
|||
status: SubscriptionStatus = MISSING, |
|||
payment_source_token: Optional[str] = None, |
|||
) -> None: |
|||
"""|coro| |
|||
|
|||
Edits the subscription. |
|||
|
|||
All parameters are optional. |
|||
|
|||
Parameters |
|||
---------- |
|||
items: List[:class:`SubscriptionItem`] |
|||
The new subscription items to use. |
|||
payment_source: :class:`int` |
|||
The new payment source for payment. |
|||
currency: :class:`str` |
|||
The new currency to use for payment. |
|||
status: :class:`SubscriptionStatus` |
|||
The new status of the subscription. |
|||
payment_source_token: Optional[:class:`str`] |
|||
The token used to authorize with the payment source. |
|||
|
|||
Raises |
|||
------ |
|||
Forbidden |
|||
You do not have permissions to edit the subscription. |
|||
HTTPException |
|||
Editing the subscription failed. |
|||
""" |
|||
payload = {} |
|||
if items is not MISSING: |
|||
payload['items'] = [item.to_dict() for item in items] if items else [] |
|||
if payment_source is not MISSING: |
|||
payload['payment_source_id'] = payment_source.id |
|||
payload['payment_source_token'] = payment_source_token |
|||
if currency is not MISSING: |
|||
payload['currency'] = currency |
|||
if status is not MISSING: |
|||
payload['status'] = int(status) |
|||
|
|||
data = await self._state.http.edit_subscription(self.id, **payload) |
|||
self._update(data) |
|||
|
|||
async def delete(self) -> None: |
|||
"""|coro| |
|||
|
|||
Deletes the subscription. |
|||
|
|||
There is an alias of this called :meth:`cancel`. |
|||
|
|||
Raises |
|||
------ |
|||
HTTPException |
|||
Deleting the subscription failed. |
|||
""" |
|||
await self._state.http.delete_subscription(self.id) |
|||
|
|||
async def cancel(self) -> None: |
|||
"""|coro| |
|||
|
|||
Deletes the subscription. |
|||
|
|||
Alias of :meth:`delete`. |
|||
|
|||
Raises |
|||
------ |
|||
HTTPException |
|||
Deleting the subscription failed. |
|||
""" |
|||
await self.delete() |
|||
|
|||
async def preview_invoice( |
|||
self, |
|||
*, |
|||
items: List[SubscriptionItem] = MISSING, |
|||
payment_source: Snowflake = MISSING, |
|||
currency: str = MISSING, |
|||
apply_entitlements: bool = MISSING, |
|||
renewal: bool = MISSING, |
|||
) -> SubscriptionInvoice: |
|||
"""|coro| |
|||
|
|||
Preview an invoice for the subscription with the given parameters. |
|||
|
|||
All parameters are optional and default to the current subscription values. |
|||
|
|||
Parameters |
|||
---------- |
|||
items: List[:class:`SubscriptionItem`] |
|||
The items the previewed invoice should have. |
|||
payment_source: :class:`.PaymentSource` |
|||
The payment source the previewed invoice should be paid with. |
|||
currency: :class:`str` |
|||
The currency the previewed invoice should be paid in. |
|||
apply_entitlements: :class:`bool` |
|||
Whether to apply entitlements (credits) to the previewed invoice. |
|||
renewal: :class:`bool` |
|||
Whether the previewed invoice should be a renewal. |
|||
|
|||
Raises |
|||
------ |
|||
HTTPException |
|||
Failed to preview the invoice. |
|||
|
|||
Returns |
|||
------- |
|||
:class:`SubscriptionInvoice` |
|||
The previewed invoice. |
|||
""" |
|||
payload: Dict[str, Any] = {} |
|||
if items is not MISSING: |
|||
payload['items'] = [item.to_dict() for item in items] if items else [] |
|||
if payment_source: |
|||
payload['payment_source_id'] = payment_source.id |
|||
if currency: |
|||
payload['currency'] = currency |
|||
if apply_entitlements is not MISSING: |
|||
payload['apply_entitlements'] = apply_entitlements |
|||
if renewal is not MISSING: |
|||
payload['renewal'] = renewal |
|||
|
|||
if payload: |
|||
data = await self._state.http.preview_subscription_update(self.id, **payload) |
|||
else: |
|||
data = await self._state.http.get_subscription_preview(self.id) |
|||
|
|||
return SubscriptionInvoice(self, data=data, state=self._state) |
|||
|
|||
async def payment_source(self) -> Optional[PaymentSource]: |
|||
"""|coro| |
|||
|
|||
Retrieves the payment source the subscription is paid with, if applicable. |
|||
|
|||
Raises |
|||
------ |
|||
NotFound |
|||
The payment source could not be found. |
|||
HTTPException |
|||
Retrieving the payment source failed. |
|||
|
|||
Returns |
|||
------- |
|||
Optional[:class:`PaymentSource`] |
|||
The payment source the subscription is paid with, if applicable. |
|||
""" |
|||
if not self.payment_source_id: |
|||
return |
|||
|
|||
data = await self._state.http.get_payment_source(self.payment_source_id) |
|||
return PaymentSource(data=data, state=self._state) |
|||
|
|||
async def invoices(self): |
|||
"""|coro| |
|||
|
|||
Retrieves all invoices for the subscription. |
|||
|
|||
Raises |
|||
------ |
|||
NotFound |
|||
The payment source or invoices could not be found. |
|||
HTTPException |
|||
Retrieving the invoices failed. |
|||
|
|||
Returns |
|||
------- |
|||
List[:class:`SubscriptionInvoice`] |
|||
The invoices. |
|||
""" |
|||
state = self._state |
|||
data = await state.http.get_subscription_invoices(self.id) |
|||
return [SubscriptionInvoice(self, data=d, state=state) for d in data] |
|||
|
|||
|
|||
class SubscriptionTrial(Hashable): |
|||
"""Represents a subscription trial. |
|||
|
|||
.. container:: operations |
|||
|
|||
.. describe:: x == y |
|||
|
|||
Checks if two trials are equal. |
|||
|
|||
.. describe:: x != y |
|||
|
|||
Checks if two trials are not equal. |
|||
|
|||
.. describe:: hash(x) |
|||
|
|||
Returns the trial's hash. |
|||
|
|||
.. versionadded:: 2.0 |
|||
|
|||
Attributes |
|||
---------- |
|||
id: :class:`int` |
|||
The ID of the trial. |
|||
interval: :class:`SubscriptionInterval` |
|||
The interval of the trial. |
|||
interval_count: :class:`int` |
|||
How many counts of the interval the trial provides. |
|||
""" |
|||
|
|||
__slots__ = ('id', 'interval', 'interval_count', 'sku_id') |
|||
|
|||
_INTERVAL_TABLE = { |
|||
SubscriptionInterval.day: 1, |
|||
SubscriptionInterval.month: 30, |
|||
SubscriptionInterval.year: 365, |
|||
} |
|||
|
|||
def __init__(self, data: SubscriptionTrialPayload): |
|||
self.id: int = int(data['id']) |
|||
self.interval: SubscriptionInterval = try_enum(SubscriptionInterval, data['interval']) |
|||
self.interval_count: int = data['interval_count'] |
|||
self.sku_id: int = int(data['sku_id']) |
|||
|
|||
def __repr__(self) -> str: |
|||
return ( |
|||
f'<SubscriptionTrial id={self.id} interval={self.interval} ' |
|||
f'interval_count={self.interval_count} sku_id={self.sku_id}>' |
|||
) |
|||
|
|||
@property |
|||
def duration(self) -> timedelta: |
|||
""":class:`datetime.timedelta`: How long the trial lasts.""" |
|||
return timedelta(days=self.interval_count * self._INTERVAL_TABLE[self.interval]) |
@ -0,0 +1,78 @@ |
|||
""" |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2021-present Dolfies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a |
|||
copy of this software and associated documentation files (the "Software"), |
|||
to deal in the Software without restriction, including without limitation |
|||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|||
and/or sell copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|||
DEALINGS IN THE SOFTWARE. |
|||
""" |
|||
|
|||
from __future__ import annotations |
|||
|
|||
from typing import Literal, TypedDict |
|||
from typing_extensions import NotRequired |
|||
|
|||
|
|||
class BillingAddress(TypedDict): |
|||
line_1: str |
|||
line_2: NotRequired[str] |
|||
name: str |
|||
postal_code: NotRequired[str] |
|||
city: str |
|||
state: NotRequired[str] |
|||
country: str |
|||
email: NotRequired[str] |
|||
|
|||
|
|||
class BillingAddressToken(TypedDict): |
|||
token: str |
|||
|
|||
|
|||
class PartialPaymentSource(TypedDict): |
|||
id: str |
|||
brand: NotRequired[str] |
|||
country: NotRequired[str] |
|||
last_4: NotRequired[str] |
|||
type: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] |
|||
payment_gateway: Literal[1, 2, 3, 4, 5, 6] |
|||
invalid: bool |
|||
flags: int |
|||
expires_month: NotRequired[int] |
|||
expires_year: NotRequired[int] |
|||
email: NotRequired[str] |
|||
bank: NotRequired[str] |
|||
username: NotRequired[str] |
|||
screen_status: int # TODO: Figure this out |
|||
|
|||
|
|||
class PaymentSource(PartialPaymentSource): |
|||
billing_address: BillingAddress |
|||
default: bool |
|||
|
|||
|
|||
class PremiumUsageValue(TypedDict): |
|||
value: int |
|||
|
|||
|
|||
class PremiumUsage(TypedDict): |
|||
nitro_sticker_sends: PremiumUsageValue |
|||
total_animated_emojis: PremiumUsageValue |
|||
total_global_emojis: PremiumUsageValue |
|||
total_large_uploads: PremiumUsageValue |
|||
total_hd_streams: PremiumUsageValue |
|||
hd_hours_streamed: PremiumUsageValue |
@ -0,0 +1,95 @@ |
|||
""" |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2021-present Dolfies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a |
|||
copy of this software and associated documentation files (the "Software"), |
|||
to deal in the Software without restriction, including without limitation |
|||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|||
and/or sell copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|||
DEALINGS IN THE SOFTWARE. |
|||
""" |
|||
|
|||
from __future__ import annotations |
|||
|
|||
from typing import List, Literal, Optional, TypedDict |
|||
from typing_extensions import NotRequired |
|||
|
|||
from .payments import PartialPayment |
|||
from .promotions import Promotion |
|||
from .snowflake import Snowflake |
|||
from .store import SKU, StoreListing |
|||
from .subscriptions import PartialSubscriptionPlan, SubscriptionPlan, SubscriptionTrial |
|||
from .user import PartialUser |
|||
|
|||
|
|||
class Entitlement(TypedDict): |
|||
id: Snowflake |
|||
type: Literal[1, 2, 3, 4, 5, 6, 7] |
|||
user_id: Snowflake |
|||
sku_id: Snowflake |
|||
application_id: Snowflake |
|||
promotion_id: Optional[Snowflake] |
|||
parent_id: NotRequired[Snowflake] |
|||
guild_id: NotRequired[Snowflake] |
|||
branches: NotRequired[List[Snowflake]] |
|||
gifter_user_id: NotRequired[Snowflake] |
|||
gift_style: NotRequired[Literal[1, 2, 3]] |
|||
gift_batch_id: NotRequired[Snowflake] |
|||
gift_code_flags: NotRequired[int] |
|||
deleted: bool |
|||
consumed: NotRequired[bool] |
|||
starts_at: NotRequired[str] |
|||
ends_at: NotRequired[str] |
|||
subscription_id: NotRequired[Snowflake] |
|||
subscription_plan: NotRequired[PartialSubscriptionPlan] |
|||
sku: NotRequired[SKU] |
|||
payment: NotRequired[PartialPayment] |
|||
|
|||
|
|||
class GatewayGift(TypedDict): |
|||
code: str |
|||
uses: int |
|||
sku_id: Snowflake |
|||
channel_id: NotRequired[Snowflake] |
|||
guild_id: NotRequired[Snowflake] |
|||
|
|||
|
|||
class Gift(GatewayGift): |
|||
expires_at: Optional[str] |
|||
application_id: Snowflake |
|||
batch_id: NotRequired[Snowflake] |
|||
entitlement_branches: NotRequired[List[Snowflake]] |
|||
gift_style: NotRequired[Optional[Literal[1, 2, 3]]] |
|||
flags: int |
|||
max_uses: int |
|||
uses: int |
|||
redeemed: bool |
|||
revoked: NotRequired[bool] |
|||
store_listing: NotRequired[StoreListing] |
|||
promotion: NotRequired[Promotion] |
|||
subscription_trial: NotRequired[SubscriptionTrial] |
|||
subscription_plan: NotRequired[SubscriptionPlan] |
|||
user: NotRequired[PartialUser] |
|||
|
|||
|
|||
class GiftBatch(TypedDict): |
|||
id: Snowflake |
|||
sku_id: Snowflake |
|||
amount: int |
|||
description: NotRequired[str] |
|||
entitlement_branches: NotRequired[List[Snowflake]] |
|||
entitlement_starts_at: NotRequired[str] |
|||
entitlement_ends_at: NotRequired[str] |
@ -0,0 +1,44 @@ |
|||
""" |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2021-present Dolfies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a |
|||
copy of this software and associated documentation files (the "Software"), |
|||
to deal in the Software without restriction, including without limitation |
|||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|||
and/or sell copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|||
DEALINGS IN THE SOFTWARE. |
|||
""" |
|||
|
|||
from __future__ import annotations |
|||
|
|||
from typing import List, TypedDict |
|||
from typing_extensions import NotRequired |
|||
|
|||
from .appinfo import Branch, PartialApplication |
|||
from .entitlements import Entitlement |
|||
from .snowflake import Snowflake |
|||
from .store import PartialSKU |
|||
|
|||
|
|||
class LibraryApplication(TypedDict): |
|||
created_at: str |
|||
application: PartialApplication |
|||
sku_id: Snowflake |
|||
sku: PartialSKU |
|||
entitlements: List[Entitlement] |
|||
flags: int |
|||
branch_id: Snowflake |
|||
branch: NotRequired[Branch] |
@ -0,0 +1,61 @@ |
|||
""" |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2021-present Dolfies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a |
|||
copy of this software and associated documentation files (the "Software"), |
|||
to deal in the Software without restriction, including without limitation |
|||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|||
and/or sell copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|||
DEALINGS IN THE SOFTWARE. |
|||
""" |
|||
|
|||
from __future__ import annotations |
|||
|
|||
from typing import List, Literal, TypedDict |
|||
from typing_extensions import NotRequired |
|||
|
|||
from .billing import PartialPaymentSource |
|||
from .snowflake import Snowflake |
|||
from .store import SKU |
|||
from .subscriptions import PartialSubscription |
|||
|
|||
|
|||
class PartialPayment(TypedDict): |
|||
# TODO: There might be more, but I don't have an example payload |
|||
id: Snowflake |
|||
amount: int |
|||
tax: int |
|||
tax_inclusive: bool |
|||
currency: str |
|||
|
|||
|
|||
class Payment(PartialPayment): |
|||
amount_refunded: int |
|||
description: str |
|||
status: Literal[0, 1, 2, 3, 4, 5] |
|||
created_at: str |
|||
sku_id: NotRequired[Snowflake] |
|||
sku_price: NotRequired[int] |
|||
sku_subscription_plan_id: NotRequired[Snowflake] |
|||
payment_gateway: NotRequired[Literal[1, 2, 3, 4, 5, 6]] |
|||
payment_gateway_payment_id: NotRequired[str] |
|||
downloadable_invoice: NotRequired[str] |
|||
downloadable_refund_invoices: NotRequired[List[str]] |
|||
refund_disqualification_reasons: NotRequired[List[str]] |
|||
flags: int |
|||
sku: NotRequired[SKU] |
|||
payment_source: NotRequired[PartialPaymentSource] |
|||
subscription: NotRequired[PartialSubscription] |
@ -0,0 +1,79 @@ |
|||
""" |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2021-present Dolfies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a |
|||
copy of this software and associated documentation files (the "Software"), |
|||
to deal in the Software without restriction, including without limitation |
|||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|||
and/or sell copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|||
DEALINGS IN THE SOFTWARE. |
|||
""" |
|||
|
|||
from __future__ import annotations |
|||
|
|||
from typing import List, Optional, TypedDict |
|||
from typing_extensions import NotRequired |
|||
|
|||
from .snowflake import Snowflake |
|||
from .subscriptions import SubscriptionTrial |
|||
|
|||
|
|||
class Promotion(TypedDict): |
|||
id: Snowflake |
|||
trial_id: NotRequired[Snowflake] |
|||
start_date: str |
|||
end_date: str |
|||
flags: int |
|||
outbound_title: str |
|||
outbound_redemption_modal_body: str |
|||
outbound_redemption_page_link: NotRequired[str] |
|||
outbound_redemption_url_format: NotRequired[str] |
|||
outbound_restricted_countries: NotRequired[List[str]] |
|||
outbound_terms_and_conditions: str |
|||
inbound_title: NotRequired[str] |
|||
inbound_body_text: NotRequired[str] |
|||
inbound_help_center_link: NotRequired[str] |
|||
inbound_restricted_countries: NotRequired[List[str]] |
|||
|
|||
|
|||
class ClaimedPromotion(TypedDict): |
|||
promotion: Promotion |
|||
code: str |
|||
claimed_at: str |
|||
|
|||
|
|||
class TrialOffer(TypedDict): |
|||
id: Snowflake |
|||
expires_at: str |
|||
trial_id: Snowflake |
|||
subscription_trial: SubscriptionTrial |
|||
|
|||
|
|||
class PromotionalPrice(TypedDict): |
|||
amount: int |
|||
currency: str |
|||
|
|||
|
|||
class PricingPromotion(TypedDict): |
|||
plan_id: Snowflake |
|||
country_code: str |
|||
payment_source_types: List[str] |
|||
price: PromotionalPrice |
|||
|
|||
|
|||
class WrappedPricingPromotion(TypedDict): |
|||
country_code: str |
|||
localized_pricing_promo: Optional[PricingPromotion] |
@ -0,0 +1,152 @@ |
|||
""" |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2021-present Dolfies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a |
|||
copy of this software and associated documentation files (the "Software"), |
|||
to deal in the Software without restriction, including without limitation |
|||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|||
and/or sell copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|||
DEALINGS IN THE SOFTWARE. |
|||
""" |
|||
|
|||
from __future__ import annotations |
|||
|
|||
from typing import Dict, List, Literal, Optional, TypedDict, Union |
|||
from typing_extensions import NotRequired |
|||
|
|||
from .appinfo import PartialApplication, StoreAsset |
|||
from .entitlements import Entitlement |
|||
from .guild import PartialGuild |
|||
from .library import LibraryApplication |
|||
from .snowflake import Snowflake |
|||
from .user import PartialUser |
|||
|
|||
LOCALIZED_STR = Union[str, Dict[str, str]] |
|||
|
|||
|
|||
class StoreNote(TypedDict): |
|||
content: str |
|||
user: Optional[PartialUser] |
|||
|
|||
|
|||
class SystemRequirement(TypedDict, total=False): |
|||
ram: int |
|||
disk: int |
|||
operating_system_version: LOCALIZED_STR |
|||
cpu: LOCALIZED_STR |
|||
gpu: LOCALIZED_STR |
|||
sound_card: LOCALIZED_STR |
|||
directx: LOCALIZED_STR |
|||
network: LOCALIZED_STR |
|||
notes: LOCALIZED_STR |
|||
|
|||
|
|||
class SystemRequirements(TypedDict, total=False): |
|||
minimum: SystemRequirement |
|||
recommended: SystemRequirement |
|||
|
|||
|
|||
class CarouselItem(TypedDict, total=False): |
|||
asset_id: Snowflake |
|||
youtube_video_id: str |
|||
|
|||
|
|||
class StoreListing(TypedDict): |
|||
id: Snowflake |
|||
summary: NotRequired[LOCALIZED_STR] |
|||
description: NotRequired[LOCALIZED_STR] |
|||
tagline: NotRequired[LOCALIZED_STR] |
|||
flavor_text: NotRequired[str] |
|||
published: NotRequired[bool] |
|||
entitlement_branch_id: NotRequired[Snowflake] |
|||
staff_notes: NotRequired[StoreNote] |
|||
guild: NotRequired[PartialGuild] |
|||
assets: NotRequired[List[StoreAsset]] |
|||
carousel_items: NotRequired[List[CarouselItem]] |
|||
preview_video: NotRequired[StoreAsset] |
|||
header_background: NotRequired[StoreAsset] |
|||
hero_background: NotRequired[StoreAsset] |
|||
hero_video: NotRequired[StoreAsset] |
|||
box_art: NotRequired[StoreAsset] |
|||
thumbnail: NotRequired[StoreAsset] |
|||
header_logo_light_theme: NotRequired[StoreAsset] |
|||
header_logo_dark_theme: NotRequired[StoreAsset] |
|||
sku: SKU |
|||
child_skus: NotRequired[List[SKU]] |
|||
alternative_skus: NotRequired[List[SKU]] |
|||
|
|||
|
|||
class SKUPrice(TypedDict): |
|||
currency: str |
|||
amount: int |
|||
sale_amount: NotRequired[Optional[int]] |
|||
sale_percentage: NotRequired[int] |
|||
premium: NotRequired[bool] |
|||
|
|||
|
|||
class ContentRating(TypedDict): |
|||
rating: int |
|||
descriptors: List[int] |
|||
|
|||
|
|||
class PartialSKU(TypedDict): |
|||
id: Snowflake |
|||
type: Literal[1, 2, 3, 4, 5, 6] |
|||
premium: bool |
|||
preorder_release_date: Optional[str] |
|||
preorder_released_at: Optional[str] |
|||
|
|||
|
|||
class SKU(PartialSKU): |
|||
id: Snowflake |
|||
type: Literal[1, 2, 3, 4, 5, 6] |
|||
name: LOCALIZED_STR |
|||
summary: NotRequired[LOCALIZED_STR] |
|||
legal_notice: NotRequired[LOCALIZED_STR] |
|||
slug: str |
|||
dependent_sku_id: Optional[Snowflake] |
|||
application_id: Snowflake |
|||
application: NotRequired[PartialApplication] |
|||
flags: int |
|||
price_tier: NotRequired[int] |
|||
price: NotRequired[Union[SKUPrice, Dict[str, int]]] |
|||
sale_price_tier: NotRequired[int] |
|||
sale_price: NotRequired[Dict[str, int]] |
|||
access_level: Literal[1, 2, 3] |
|||
features: List[int] |
|||
locales: NotRequired[List[str]] |
|||
genres: NotRequired[List[int]] |
|||
available_regions: NotRequired[List[str]] |
|||
content_rating_agency: NotRequired[Literal[1, 2]] |
|||
content_rating: NotRequired[ContentRating] |
|||
content_ratings: NotRequired[Dict[Literal[1, 2], ContentRating]] |
|||
system_requirements: NotRequired[Dict[Literal[1, 2, 3], SystemRequirements]] |
|||
release_date: Optional[str] |
|||
preorder_release_date: NotRequired[Optional[str]] |
|||
preorder_released_at: NotRequired[Optional[str]] |
|||
external_purchase_url: NotRequired[str] |
|||
premium: NotRequired[bool] |
|||
restricted: NotRequired[bool] |
|||
exclusive: NotRequired[bool] |
|||
show_age_gate: bool |
|||
bundled_skus: NotRequired[List[SKU]] |
|||
manifest_labels: Optional[List[Snowflake]] |
|||
|
|||
|
|||
class SKUPurchase(TypedDict): |
|||
entitlements: List[Entitlement] |
|||
library_applications: NotRequired[List[LibraryApplication]] |
|||
gift_code: NotRequired[str] |
@ -0,0 +1,161 @@ |
|||
""" |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2021-present Dolfies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a |
|||
copy of this software and associated documentation files (the "Software"), |
|||
to deal in the Software without restriction, including without limitation |
|||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|||
and/or sell copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|||
DEALINGS IN THE SOFTWARE. |
|||
""" |
|||
|
|||
from __future__ import annotations |
|||
|
|||
from typing import Any, Dict, List, Literal, Optional, TypedDict |
|||
from typing_extensions import NotRequired |
|||
|
|||
from .snowflake import Snowflake |
|||
from .user import PartialUser |
|||
|
|||
|
|||
class PremiumGuildSubscription(TypedDict): |
|||
id: Snowflake |
|||
guild_id: Snowflake |
|||
user_id: Snowflake |
|||
user: NotRequired[PartialUser] |
|||
ended: bool |
|||
ends_at: NotRequired[str] |
|||
|
|||
|
|||
class PremiumGuildSubscriptionSlot(TypedDict): |
|||
id: Snowflake |
|||
subscription_id: Snowflake |
|||
canceled: bool |
|||
cooldown_ends_at: Optional[str] |
|||
premium_guild_subscription: Optional[PremiumGuildSubscription] |
|||
|
|||
|
|||
class PremiumGuildSubscriptionCooldown(TypedDict): |
|||
ends_at: str |
|||
limit: int |
|||
remaining: int |
|||
|
|||
|
|||
class SubscriptionItem(TypedDict): |
|||
id: Snowflake |
|||
quantity: int |
|||
plan_id: Snowflake |
|||
|
|||
|
|||
class SubscriptionDiscount(TypedDict): |
|||
type: Literal[1, 2, 3, 4] |
|||
amount: int |
|||
|
|||
|
|||
class SubscriptionInvoiceItem(TypedDict): |
|||
id: Snowflake |
|||
quantity: int |
|||
amount: int |
|||
proration: bool |
|||
subscription_plan_id: Snowflake |
|||
subscription_plan_price: int |
|||
discounts: List[SubscriptionDiscount] |
|||
|
|||
|
|||
class SubscriptionInvoice(TypedDict): |
|||
id: Snowflake |
|||
status: NotRequired[Literal[1, 2, 3, 4]] |
|||
currency: str |
|||
subtotal: int |
|||
tax: int |
|||
total: int |
|||
tax_inclusive: bool |
|||
items: List[SubscriptionInvoiceItem] |
|||
current_period_start: str |
|||
current_period_end: str |
|||
|
|||
|
|||
class SubscriptionRenewalMutations(TypedDict, total=False): |
|||
payment_gateway_plan_id: Optional[str] |
|||
items: List[SubscriptionItem] |
|||
|
|||
|
|||
class PartialSubscription(TypedDict): |
|||
id: Snowflake |
|||
type: Literal[1, 2, 3] |
|||
payment_gateway: Optional[Literal[1, 2, 3, 4, 5, 6]] |
|||
currency: str |
|||
items: List[SubscriptionItem] |
|||
payment_gateway_plan_id: Optional[str] |
|||
payment_gateway_subscription_id: NotRequired[Optional[str]] |
|||
current_period_start: str |
|||
current_period_end: str |
|||
streak_started_at: NotRequired[str] |
|||
|
|||
|
|||
class Subscription(PartialSubscription): |
|||
status: Literal[0, 1, 2, 3, 4, 5, 6] |
|||
renewal_mutations: NotRequired[SubscriptionRenewalMutations] |
|||
trial_id: NotRequired[Snowflake] |
|||
payment_source_id: Optional[Snowflake] |
|||
created_at: str |
|||
canceled_at: NotRequired[str] |
|||
trial_ends_at: NotRequired[str] |
|||
metadata: NotRequired[Dict[str, Any]] |
|||
latest_invoice: NotRequired[SubscriptionInvoice] |
|||
|
|||
|
|||
class SubscriptionTrial(TypedDict): |
|||
id: Snowflake |
|||
interval: Literal[1, 2, 3] |
|||
interval_count: int |
|||
sku_id: Snowflake |
|||
|
|||
|
|||
class SubscriptionPrice(TypedDict): |
|||
currency: str |
|||
amount: int |
|||
exponent: int |
|||
|
|||
|
|||
class SubscriptionCountryPrice(TypedDict): |
|||
country_code: str |
|||
prices: List[SubscriptionPrice] |
|||
|
|||
|
|||
class SubscriptionPrices(TypedDict): |
|||
country_prices: SubscriptionCountryPrice |
|||
payment_source_prices: Dict[Snowflake, List[SubscriptionPrice]] |
|||
|
|||
|
|||
class PartialSubscriptionPlan(TypedDict): |
|||
id: Snowflake |
|||
name: str |
|||
sku_id: Snowflake |
|||
interval: Literal[1, 2, 3] |
|||
interval_count: int |
|||
tax_inclusive: bool |
|||
|
|||
|
|||
class SubscriptionPlan(PartialSubscriptionPlan): |
|||
prices: Dict[Literal[0, 1, 2, 3, 4], SubscriptionPrices] |
|||
price_tier: Literal[None] |
|||
currency: str |
|||
price: int |
|||
discount_price: NotRequired[int] |
|||
fallback_currency: NotRequired[str] |
|||
fallback_price: NotRequired[int] |
|||
fallback_discount_price: NotRequired[int] |
File diff suppressed because it is too large
Loading…
Reference in new issue