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