From 978e0c88da99d2392642fd6f8699b985c966733c Mon Sep 17 00:00:00 2001 From: plun1331 Date: Wed, 28 Aug 2024 15:14:42 -0700 Subject: [PATCH 01/19] feat: subscriptions & related changes --- discord/client.py | 2 +- discord/enums.py | 8 +++ discord/http.py | 35 ++++++++++ discord/iterators.py | 104 ++++++++++++++++++++++++++++ discord/monetization.py | 124 +++++++++++++++++++++++++++++++--- discord/state.py | 14 +++- discord/types/monetization.py | 20 +++++- docs/api/enums.rst | 19 ++++++ docs/api/events.rst | 108 +++++++++++++++++++---------- 9 files changed, 382 insertions(+), 52 deletions(-) diff --git a/discord/client.py b/discord/client.py index 7f13696f6f..ed720895ac 100644 --- a/discord/client.py +++ b/discord/client.py @@ -2048,7 +2048,7 @@ async def fetch_skus(self) -> list[SKU]: The bot's SKUs. """ data = await self._connection.http.list_skus(self.application_id) - return [SKU(data=s) for s in data] + return [SKU(state=self._connection, data=s) for s in data] def entitlements( self, diff --git a/discord/enums.py b/discord/enums.py index 14aa54d460..6721938601 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -1053,6 +1053,14 @@ class PollLayoutType(Enum): default = 1 +class SubscriptionStatus(Enum): + """The status of a subscription.""" + + active = 1 + ending = 2 + inactive = 3 + + T = TypeVar("T") diff --git a/discord/http.py b/discord/http.py index 7813d20a23..0ef82125a6 100644 --- a/discord/http.py +++ b/discord/http.py @@ -3027,6 +3027,41 @@ def delete_test_entitlement( ) return self.request(r) + async def list_sku_subscriptions( + self, + sku_id: Snowflake, + *, + before: Snowflake | None = None, + after: Snowflake | None = None, + limit: int = 50, + user_id: Snowflake | None = None, + ) -> Response[list[monetization.Subscription]]: + params: dict[str, Any] = {} + if before is not None: + params["before"] = before + if after is not None: + params["after"] = after + if limit is not None: + params["limit"] = limit + if user_id is not None: + params["user_id"] = user_id + return self.request( + Route("GET", "/skus/{sku_id}/subscriptions", sku_id=sku_id), + params=params, + ) + + async def get_subscription( + self, + sku_id: Snowflake, + subscription_id: Snowflake, + ) -> Response[monetization.Subscription]: + return self.request( + Route( + "GET", "/skus/{sku_id}/subscriptions/{subscription_id}", + sku_id=sku_id, subscription_id=subscription_id + ) + ) + # Onboarding def get_onboarding(self, guild_id: Snowflake) -> Response[onboarding.Onboarding]: diff --git a/discord/iterators.py b/discord/iterators.py index 7404e790a4..032710962a 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -52,6 +52,7 @@ "MemberIterator", "ScheduledEventSubscribersIterator", "EntitlementIterator", + "SubscriptionIterator", ) if TYPE_CHECKING: @@ -67,6 +68,7 @@ from .types.threads import Thread as ThreadPayload from .types.user import PartialUser as PartialUserPayload from .user import User + from .types.monetization import Subscription as SubscriptionPayload T = TypeVar("T") OT = TypeVar("OT") @@ -1041,3 +1043,105 @@ async def fill_entitlements(self): for element in reversed(data): await self.entitlements.put(Entitlement(data=element, state=self.state)) + + +class SubscriptionIterator(_AsyncIterator["Subscription"]): + def __init__( + self, + state, + sku_id: int, + limit: int = None, + before: datetime.datetime | None = None, + after: datetime.datetime | None = None, + user_id: int | None = None + ): + if isinstance(before, datetime.datetime): + before = Object(id=time_snowflake(before, high=False)) + if isinstance(after, datetime.datetime): + after = Object(id=time_snowflake(after, high=True)) + + self.state = state + self.sku_id = sku_id + self.limit = limit + self.before = before + self.after = after + self.user_id = user_id + + self._filter = None + + self.get_subscriptions = state.http.list_sku_subscriptions + self.subscriptions = asyncio.Queue() + + if self.before and self.after: + self._retrieve_subscriptions = self._retrieve_subscriptions_before_strategy # type: ignore + self._filter = lambda m: int(m["id"]) > self.after.id + elif self.after: + self._retrieve_subscriptions = self._retrieve_subscriptions_after_strategy # type: ignore + else: + self._retrieve_subscriptions = self._retrieve_subscriptions_before_strategy # type: ignore + + async def next(self) -> Guild: + if self.subscriptions.empty(): + await self.fill_subscriptions() + + try: + return self.subscriptions.get_nowait() + except asyncio.QueueEmpty: + raise NoMoreItems() + + def _get_retrieve(self): + l = self.limit + if l is None or l > 100: + r = 100 + else: + r = l + self.retrieve = r + return r > 0 + + def create_subscription(self, data): + from .monetization import Subscription + + return Subscription(state=self.state, data=data) + + async def fill_subscriptions(self): + if self._get_retrieve(): + data = await self._retrieve_subscriptions(self.retrieve) + if self.limit is None or len(data) < 100: + self.limit = 0 + + if self._filter: + data = filter(self._filter, data) + + for element in data: + await self.subscriptions.put(self.create_subscription(element)) + + async def _retrieve_subscriptions(self, retrieve) -> list[SubscriptionPayload]: + raise NotImplementedError + + async def _retrieve_subscriptions_before_strategy(self, retrieve): + before = self.before.id if self.before else None + data: list[SubscriptionPayload] = await self.get_subscriptions( + self.sku_id, + limit=retrieve, + before=before, + user_id=self.user_id, + ) + if len(data): + if self.limit is not None: + self.limit -= retrieve + self.before = Object(id=int(data[-1]["id"])) + return data + + async def _retrieve_subscriptions_after_strategy(self, retrieve): + after = self.after.id if self.after else None + data: list[SubscriptionPayload] = await self.get_subscriptions( + self.sku_id, + limit=retrieve, + after=after, + user_id=self.user_id, + ) + if len(data): + if self.limit is not None: + self.limit -= retrieve + self.after = Object(id=int(data[0]["id"])) + return data diff --git a/discord/monetization.py b/discord/monetization.py index f18ce0593b..f42e33a8f6 100644 --- a/discord/monetization.py +++ b/discord/monetization.py @@ -27,10 +27,12 @@ from typing import TYPE_CHECKING -from .enums import EntitlementType, SKUType, try_enum +from .abc import Snowflake, SnowflakeTime +from .enums import EntitlementType, SKUType, SubscriptionStatus, try_enum from .flags import SKUFlags +from .iterators import SubscriptionIterator from .mixins import Hashable -from .utils import MISSING, _get_as_snowflake, parse_time +from .utils import cached_property, cached_slot_property, MISSING, _get_as_snowflake, parse_time if TYPE_CHECKING: from datetime import datetime @@ -38,6 +40,7 @@ from .state import ConnectionState from .types.monetization import SKU as SKUPayload from .types.monetization import Entitlement as EntitlementPayload + from .types.monetization import Subscription as SubscriptionPayload __all__ = ( @@ -68,6 +71,7 @@ class SKU(Hashable): """ __slots__ = ( + "_state", "id", "type", "application_id", @@ -76,7 +80,8 @@ class SKU(Hashable): "flags", ) - def __init__(self, *, data: SKUPayload) -> None: + def __init__(self, *, state: ConnectionState, data: SKUPayload) -> None: + self._state: ConnectionState = state self.id: int = int(data["id"]) self.type: SKUType = try_enum(SKUType, data["type"]) self.application_id: int = int(data["application_id"]) @@ -101,12 +106,48 @@ def url(self) -> str: """:class:`str`: Returns the URL for the SKU.""" return f"https://discord.com/application-directory/{self.application_id}/store/{self.id}" + def list_subscriptions( + self, + user: Snowflake, # user is required because this is a bot, we are not using oauth2 + *, + before: SnowflakeTime | None = None, + after: SnowflakeTime | None = None, + limit: int | None = 100, + ) -> SubscriptionIterator: + """Returns an :class:`.AsyncIterator` that enables fetching the SKU's subscriptions. + + .. versionadded:: 2.7 + + Parameters + ---------- + user: :class:`.abc.Snowflake` + The user to retrieve subscriptions for. + before: :class:`.abc.Snowflake` | :class:`datetime.datetime` | None + Retrieves subscriptions before this date or object. + If a datetime is provided, it is recommended to use a UTC-aware datetime. + If the datetime is naive, it is assumed to be local time. + after: :class:`.abc.Snowflake` | :class:`datetime.datetime` | None + Retrieve subscriptions after this date or object. + If a datetime is provided, it is recommended to use a UTC-aware datetime. + If the datetime is naive, it is assumed to be local time. + limit: :class:`int` | None + The number of subscriptions to retrieve. If ``None``, retrieves all subscriptions. + """ + return SubscriptionIterator(self._state, self.id, user_id=user.id, before=before, after=after, limit=limit) + class Entitlement(Hashable): """Represents a Discord entitlement. .. versionadded:: 2.5 + .. notice:: + + As of October 1, 2024, entitlements that have been purchased will have ``ends_at`` set to ``None`` + unless the parent :class:`Subscription` has been cancelled. + + `See the Discord changelog. `_ + Attributes ---------- id: :class:`int` @@ -130,7 +171,7 @@ class Entitlement(Hashable): consumed: :class:`bool` Whether or not this entitlement has been consumed. This will always be ``False`` for entitlements that are not - of type :attr:`EntitlementType.consumable`. + from an SKU of type :attr:`SKUType.consumable`. """ __slots__ = ( @@ -158,7 +199,7 @@ def __init__(self, *, data: EntitlementPayload, state: ConnectionState) -> None: self.starts_at: datetime | MISSING = ( parse_time(data.get("starts_at")) or MISSING ) - self.ends_at: datetime | MISSING = parse_time(data.get("ends_at")) or MISSING + self.ends_at: datetime | MISSING | None = parse_time(ea) if (ea := data.get("ends_at")) is not None else MISSING self.guild_id: int | MISSING = _get_as_snowflake(data, "guild_id") or MISSING self.consumed: bool = data.get("consumed", False) @@ -177,18 +218,13 @@ async def consume(self) -> None: Consumes this entitlement. - This can only be done on entitlements of type :attr:`EntitlementType.consumable`. + This can only be done on entitlements from an SKU of type :attr:`SKUType.consumable`. Raises ------ - TypeError - The entitlement is not consumable. HTTPException Consuming the entitlement failed. """ - if self.type is not EntitlementType.consumable: - raise TypeError("Cannot consume non-consumable entitlement") - await self._state.http.consume_entitlement(self._state.application_id, self.id) self.consumed = True @@ -205,3 +241,69 @@ async def delete(self) -> None: Deleting the entitlement failed. """ await self._state.http.delete_test_entitlement(self.application_id, self.id) + + +class Subscription(Hashable): + """Represents a user making recurring payments for one or more SKUs. + + Successful payments grant the user access to entitlements associated with the SKU. + + .. versionadded:: 2.7 + + Attributes + ---------- + id: :class:`int` + The subscription's ID. + user_id: :class:`int` + The ID of the user that owns this subscription. + sku_ids: List[:class:`int`] + The IDs of the SKUs this subscription is for. + entitlement_ids: List[:class:`int`] + The IDs of the entitlements this subscription is for. + current_period_start: :class:`datetime.datetime` + The start of the current subscription period. + current_period_end: :class:`datetime.datetime` + The end of the current subscription period. + status: :class:`SubscriptionStatus` + The status of the subscription. + canceled_at: :class:`datetime.datetime` | ``None`` + When the subscription was canceled. + """ + __slots__ = ( + "_state", + "id", + "user_id", + "sku_ids", + "entitlement_ids", + "current_period_start", + "current_period_end", + "status", + "canceled_at", + "country", + ) + + def __init__(self, *, state: ConnectionState, data: SubscriptionPayload) -> None: + self._state: ConnectionState = state + self.id: int = int(data["id"]) + self.user_id: int = int(data["user_id"]) + self.sku_ids: list[int] = list(map(int, data["sku_ids"])) + self.entitlement_ids: list[int] = list(map(int, data["entitlement_ids"])) + self.current_period_start: datetime = parse_time(data["current_period_start"]) + self.current_period_end: datetime = parse_time(data["current_period_end"]) + self.status: SubscriptionStatus = try_enum(SubscriptionStatus, data["status"]) + self.canceled_at: datetime | None = parse_time(data.get("canceled_at")) + self.country: str | None = data.get("country") # Not documented, it is only available with oauth2, not bots + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) and other.id == self.id + + @property + def user(self): + """Optional[:class:`User`]: The user that owns this subscription.""" + return self._state.get_user(self.user_id) + diff --git a/discord/state.py b/discord/state.py index c8d8d4dced..282ba50624 100644 --- a/discord/state.py +++ b/discord/state.py @@ -59,7 +59,7 @@ from .member import Member from .mentions import AllowedMentions from .message import Message -from .monetization import Entitlement +from .monetization import Entitlement, Subscription from .object import Object from .partial_emoji import PartialEmoji from .poll import Poll, PollAnswerCount @@ -698,6 +698,18 @@ def parse_entitlement_delete(self, data) -> None: event = Entitlement(data=data, state=self) self.dispatch("entitlement_delete", event) + def parse_subscription_create(self, data) -> None: + event = Subscription(data=data, state=self) + self.dispatch("subscription_create", event) + + def parse_subscription_update(self, data) -> None: + event = Subscription(data=data, state=self) + self.dispatch("subscription_update", event) + + def parse_subscription_delete(self, data) -> None: + event = Subscription(data=data, state=self) + self.dispatch("subscription_delete", event) + def parse_message_create(self, data) -> None: channel, _ = self._get_guild_channel(data) # channel would be the correct type here diff --git a/discord/types/monetization.py b/discord/types/monetization.py index 8b186c83e0..145750a15e 100644 --- a/discord/types/monetization.py +++ b/discord/types/monetization.py @@ -25,14 +25,15 @@ from __future__ import annotations -from typing import Literal +from typing import List, Literal from .._typed_dict import NotRequired, TypedDict from .snowflake import Snowflake -SKUType = Literal[5, 6] -EntitlementType = Literal[8] +SKUType = Literal[2, 3, 5, 6] +EntitlementType = Literal[1, 2, 3, 4, 5, 6, 7, 8] OwnerType = Literal[1, 2] +SubscriptionStatus = Literal[1, 2, 3] class SKU(TypedDict): @@ -54,9 +55,22 @@ class Entitlement(TypedDict): starts_at: NotRequired[str] ends_at: NotRequired[str] guild_id: NotRequired[Snowflake] + consumed: NotRequired[bool] class CreateTestEntitlementPayload(TypedDict): sku_id: Snowflake owner_id: Snowflake owner_type: OwnerType + + +class Subscription(TypedDict): + id: Snowflake + user_id: Snowflake + sku_ids: List[Snowflake] + entitlement_ids: List[Snowflake] + current_period_start: str + current_period_end: str + status: SubscriptionStatus + canceled_at: str | None + country: NotRequired[str] diff --git a/docs/api/enums.rst b/docs/api/enums.rst index c734e2fd1e..a9700a8fc4 100644 --- a/docs/api/enums.rst +++ b/docs/api/enums.rst @@ -2500,3 +2500,22 @@ of :class:`enum.Enum`. .. attribute:: private_channel The interaction is in a private DM or group DM channel. + + +.. class:: SubscriptionStatus + + Represents a subscription's status. + + .. versionadded:: 2.7 + + .. attribute:: active + + The subscription is active and is scheduled to renew. + + .. attribute:: ending + + The subscription is active but will not renew + + .. attribute:: inactive + + The subscription is inactive and the subscription owner is not being charged. diff --git a/docs/api/events.rst b/docs/api/events.rst index 1b011fa1f4..2ed87d394e 100644 --- a/docs/api/events.rst +++ b/docs/api/events.rst @@ -410,42 +410,6 @@ Connection WebSocket library. It can be :class:`bytes` to denote a binary message or :class:`str` to denote a regular text message. -Entitlements ------------- -.. function:: on_entitlement_create(entitlement) - - Called when a user subscribes to an SKU. - - .. versionadded:: 2.5 - - :param entitlement: The entitlement that was created as a result of the subscription. - :type entitlement: :class:`Entitlement` - -.. function:: on_entitlement_update(entitlement) - - Called when a user's subscription to an Entitlement is renewed for the next billing period. - - .. versionadded:: 2.5 - - :param entitlement: The entitlement that was updated. - :type entitlement: :class:`Entitlement` - -.. function:: on_entitlement_delete(entitlement) - - Called when a user's entitlement is deleted. - - Entitlements are usually only deleted when Discord issues a refund for a subscription, - or manually removes an entitlement from a user. - - .. note:: - - This is not called when a user's subscription is cancelled. - - .. versionadded:: 2.5 - - :param entitlement: The entitlement that was deleted. - :type entitlement: :class:`Entitlement` - Guilds ------ .. function:: on_guild_join(guild) @@ -910,6 +874,78 @@ Messages :param payload: The raw event payload data. :type payload: :class:`RawMessageUpdateEvent` +Monetization +------------ +.. function:: on_entitlement_create(entitlement) + + Called when a user subscribes to an SKU. + + .. versionadded:: 2.5 + + :param entitlement: The entitlement that was created as a result of the subscription. + :type entitlement: :class:`Entitlement` + +.. function:: on_entitlement_update(entitlement) + + Called when a user's subscription to an Entitlement is cancelled. + + .. versionadded:: 2.5 + + .. notice:: + + Before October 1, 2024, this event was called when a user's subscription was renewed. + + Entitlements that no longer follow this behavior will have a type of :attr:`EntitlementType.purchase`. + Those that follow the old behavior will have a type of :attr:`EntitlementType.application_subscription`. + + `See the Discord changelog. `_ + + :param entitlement: The entitlement that was updated. + :type entitlement: :class:`Entitlement` + +.. function:: on_entitlement_delete(entitlement) + + Called when a user's entitlement is deleted. + + Entitlements are usually only deleted when Discord issues a refund for a subscription, + or manually removes an entitlement from a user. + + .. note:: + + This is not called when a user's subscription is cancelled. + + .. versionadded:: 2.5 + + :param entitlement: The entitlement that was deleted. + :type entitlement: :class:`Entitlement` + +.. function:: on_subscription_create(subscription) + + Called when a subscription is created for the application. + + .. versionadded:: 2.7 + + :param subscription: The subscription that was created. + :type subscription: :class:`Subscription` + +.. function:: on_subscription_update(subscription) + + Called when a subscription has been updated. This could be a renewal, cancellation, or other payment related update. + + .. versionadded:: 2.7 + + :param subscription: The subscription that was updated. + :type subscription: :class:`Subscription` + +.. function:: on_subscription_delete(subscription) + + Called when a subscription has been deleted. + + .. versionadded:: 2.7 + + :param subscription: The subscription that was deleted. + :type subscription: :class:`Subscription` + Polls ~~~~~~~~~ .. function:: on_poll_vote_add(poll, user, answer) From e16c72a5ccc4a58538cec2f851e80693001d639f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2024 22:20:24 +0000 Subject: [PATCH 02/19] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/http.py | 6 ++++-- discord/iterators.py | 4 ++-- discord/monetization.py | 27 ++++++++++++++++++++++----- discord/types/monetization.py | 4 ++-- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/discord/http.py b/discord/http.py index 0ef82125a6..789faf9cf0 100644 --- a/discord/http.py +++ b/discord/http.py @@ -3057,8 +3057,10 @@ async def get_subscription( ) -> Response[monetization.Subscription]: return self.request( Route( - "GET", "/skus/{sku_id}/subscriptions/{subscription_id}", - sku_id=sku_id, subscription_id=subscription_id + "GET", + "/skus/{sku_id}/subscriptions/{subscription_id}", + sku_id=sku_id, + subscription_id=subscription_id, ) ) diff --git a/discord/iterators.py b/discord/iterators.py index 032710962a..e6e50b4275 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -65,10 +65,10 @@ from .types.audit_log import AuditLog as AuditLogPayload from .types.guild import Guild as GuildPayload from .types.message import Message as MessagePayload + from .types.monetization import Subscription as SubscriptionPayload from .types.threads import Thread as ThreadPayload from .types.user import PartialUser as PartialUserPayload from .user import User - from .types.monetization import Subscription as SubscriptionPayload T = TypeVar("T") OT = TypeVar("OT") @@ -1053,7 +1053,7 @@ def __init__( limit: int = None, before: datetime.datetime | None = None, after: datetime.datetime | None = None, - user_id: int | None = None + user_id: int | None = None, ): if isinstance(before, datetime.datetime): before = Object(id=time_snowflake(before, high=False)) diff --git a/discord/monetization.py b/discord/monetization.py index f42e33a8f6..e6c91373b2 100644 --- a/discord/monetization.py +++ b/discord/monetization.py @@ -32,7 +32,13 @@ from .flags import SKUFlags from .iterators import SubscriptionIterator from .mixins import Hashable -from .utils import cached_property, cached_slot_property, MISSING, _get_as_snowflake, parse_time +from .utils import ( + MISSING, + _get_as_snowflake, + cached_property, + cached_slot_property, + parse_time, +) if TYPE_CHECKING: from datetime import datetime @@ -133,7 +139,14 @@ def list_subscriptions( limit: :class:`int` | None The number of subscriptions to retrieve. If ``None``, retrieves all subscriptions. """ - return SubscriptionIterator(self._state, self.id, user_id=user.id, before=before, after=after, limit=limit) + return SubscriptionIterator( + self._state, + self.id, + user_id=user.id, + before=before, + after=after, + limit=limit, + ) class Entitlement(Hashable): @@ -199,7 +212,9 @@ def __init__(self, *, data: EntitlementPayload, state: ConnectionState) -> None: self.starts_at: datetime | MISSING = ( parse_time(data.get("starts_at")) or MISSING ) - self.ends_at: datetime | MISSING | None = parse_time(ea) if (ea := data.get("ends_at")) is not None else MISSING + self.ends_at: datetime | MISSING | None = ( + parse_time(ea) if (ea := data.get("ends_at")) is not None else MISSING + ) self.guild_id: int | MISSING = _get_as_snowflake(data, "guild_id") or MISSING self.consumed: bool = data.get("consumed", False) @@ -269,6 +284,7 @@ class Subscription(Hashable): canceled_at: :class:`datetime.datetime` | ``None`` When the subscription was canceled. """ + __slots__ = ( "_state", "id", @@ -292,7 +308,9 @@ def __init__(self, *, state: ConnectionState, data: SubscriptionPayload) -> None self.current_period_end: datetime = parse_time(data["current_period_end"]) self.status: SubscriptionStatus = try_enum(SubscriptionStatus, data["status"]) self.canceled_at: datetime | None = parse_time(data.get("canceled_at")) - self.country: str | None = data.get("country") # Not documented, it is only available with oauth2, not bots + self.country: str | None = data.get( + "country" + ) # Not documented, it is only available with oauth2, not bots def __repr__(self) -> str: return ( @@ -306,4 +324,3 @@ def __eq__(self, other: object) -> bool: def user(self): """Optional[:class:`User`]: The user that owns this subscription.""" return self._state.get_user(self.user_id) - diff --git a/discord/types/monetization.py b/discord/types/monetization.py index 145750a15e..2e7f5f2ff9 100644 --- a/discord/types/monetization.py +++ b/discord/types/monetization.py @@ -67,8 +67,8 @@ class CreateTestEntitlementPayload(TypedDict): class Subscription(TypedDict): id: Snowflake user_id: Snowflake - sku_ids: List[Snowflake] - entitlement_ids: List[Snowflake] + sku_ids: list[Snowflake] + entitlement_ids: list[Snowflake] current_period_start: str current_period_end: str status: SubscriptionStatus From af1b084fdcf40d8b6bceae25d63773f377829140 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Wed, 28 Aug 2024 15:20:56 -0700 Subject: [PATCH 03/19] feat: changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ceaa298b18..45f4753107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,11 +19,20 @@ These changes are available on the `master` branch, but have not yet been releas ([#2496](https://github.com/Pycord-Development/pycord/pull/2496)) - ⚠️ **This Version Removes Support For Python 3.8** ⚠️ ([#2521](https://github.com/Pycord-Development/pycord/pull/2521)) +- `Entitlement.ends_at` can now be `None`. + ([#2564](https://github.com/Pycord-Development/pycord/pull/2564)) ### Added - Added `Guild.fetch_role` method. ([#2528](https://github.com/Pycord-Development/pycord/pull/2528)) +- Added new `Subscription` object and related events. + ([#2564](https://github.com/Pycord-Development/pycord/pull/2564)) + +### Fixed + +- Fixed `AttributeError` when trying to consume a consumable entitlement. + ([#2564](https://github.com/Pycord-Development/pycord/pull/2564)) ## [2.6.0] - 2024-07-09 From 7d35a67eb93b271877bc16a7a5ca6005a7dea8c9 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Wed, 28 Aug 2024 15:21:31 -0700 Subject: [PATCH 04/19] feat: changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45f4753107..5f016c385e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ These changes are available on the `master` branch, but have not yet been releas - Added `Guild.fetch_role` method. ([#2528](https://github.com/Pycord-Development/pycord/pull/2528)) -- Added new `Subscription` object and related events. +- Added new `Subscription` object and related methods/events. ([#2564](https://github.com/Pycord-Development/pycord/pull/2564)) ### Fixed From 1e317a8aba81b900d07c61d49f04d3a80bc39fe3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2024 22:22:04 +0000 Subject: [PATCH 05/19] style(pre-commit): auto fixes from pre-commit.com hooks --- CHANGELOG.md | 4 ++-- discord/types/monetization.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f016c385e..22e8e2e414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,12 +26,12 @@ These changes are available on the `master` branch, but have not yet been releas - Added `Guild.fetch_role` method. ([#2528](https://github.com/Pycord-Development/pycord/pull/2528)) -- Added new `Subscription` object and related methods/events. +- Added new `Subscription` object and related methods/events. ([#2564](https://github.com/Pycord-Development/pycord/pull/2564)) ### Fixed -- Fixed `AttributeError` when trying to consume a consumable entitlement. +- Fixed `AttributeError` when trying to consume a consumable entitlement. ([#2564](https://github.com/Pycord-Development/pycord/pull/2564)) ## [2.6.0] - 2024-07-09 diff --git a/discord/types/monetization.py b/discord/types/monetization.py index 2e7f5f2ff9..bb6bc93374 100644 --- a/discord/types/monetization.py +++ b/discord/types/monetization.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import List, Literal +from typing import Literal from .._typed_dict import NotRequired, TypedDict from .snowflake import Snowflake From efdb9728063075381d2347a6b6097d205ee686e0 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Wed, 28 Aug 2024 15:25:14 -0700 Subject: [PATCH 06/19] fix: move abc import to TYPE_CHECKING --- discord/monetization.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/discord/monetization.py b/discord/monetization.py index e6c91373b2..bf60a5ecaf 100644 --- a/discord/monetization.py +++ b/discord/monetization.py @@ -27,7 +27,6 @@ from typing import TYPE_CHECKING -from .abc import Snowflake, SnowflakeTime from .enums import EntitlementType, SKUType, SubscriptionStatus, try_enum from .flags import SKUFlags from .iterators import SubscriptionIterator @@ -35,14 +34,13 @@ from .utils import ( MISSING, _get_as_snowflake, - cached_property, - cached_slot_property, parse_time, ) if TYPE_CHECKING: from datetime import datetime + from .abc import Snowflake, SnowflakeTime from .state import ConnectionState from .types.monetization import SKU as SKUPayload from .types.monetization import Entitlement as EntitlementPayload From 635c4b579705de12254779395bd64a0215496b9c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2024 22:26:05 +0000 Subject: [PATCH 07/19] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/monetization.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/discord/monetization.py b/discord/monetization.py index bf60a5ecaf..e25b3eb7ef 100644 --- a/discord/monetization.py +++ b/discord/monetization.py @@ -31,11 +31,7 @@ from .flags import SKUFlags from .iterators import SubscriptionIterator from .mixins import Hashable -from .utils import ( - MISSING, - _get_as_snowflake, - parse_time, -) +from .utils import MISSING, _get_as_snowflake, parse_time if TYPE_CHECKING: from datetime import datetime From 32ebc62752e65f5434f0b5b5a8b41c25846413a0 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Wed, 28 Aug 2024 15:28:11 -0700 Subject: [PATCH 08/19] fix: circular import from Entitlement --- discord/iterators.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/discord/iterators.py b/discord/iterators.py index e6e50b4275..735cb97b33 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -40,7 +40,6 @@ from .audit_logs import AuditLogEntry from .errors import NoMoreItems -from .monetization import Entitlement from .object import Object from .utils import maybe_coroutine, snowflake_time, time_snowflake @@ -1012,6 +1011,11 @@ def _get_retrieve(self): self.retrieve = r return r > 0 + async def create_entitlement(self, data): + from .monetization import Entitlement + + return Entitlement(data=data, state=self.state) + async def fill_entitlements(self): if not self._get_retrieve(): return @@ -1042,7 +1046,7 @@ async def fill_entitlements(self): self.after = Object(id=int(data[-1]["id"])) for element in reversed(data): - await self.entitlements.put(Entitlement(data=element, state=self.state)) + await self.entitlements.put(self.create_entitlement(element)) class SubscriptionIterator(_AsyncIterator["Subscription"]): From 9f00a5a76770cbc4dc9d95f2ade801745e5ceb9f Mon Sep 17 00:00:00 2001 From: plun1331 Date: Wed, 28 Aug 2024 15:29:59 -0700 Subject: [PATCH 09/19] docs: correct directives from notice to note --- discord/monetization.py | 2 +- docs/api/events.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/monetization.py b/discord/monetization.py index e25b3eb7ef..0f51161d8e 100644 --- a/discord/monetization.py +++ b/discord/monetization.py @@ -148,7 +148,7 @@ class Entitlement(Hashable): .. versionadded:: 2.5 - .. notice:: + .. note:: As of October 1, 2024, entitlements that have been purchased will have ``ends_at`` set to ``None`` unless the parent :class:`Subscription` has been cancelled. diff --git a/docs/api/events.rst b/docs/api/events.rst index 2ed87d394e..c2dba18751 100644 --- a/docs/api/events.rst +++ b/docs/api/events.rst @@ -891,7 +891,7 @@ Monetization .. versionadded:: 2.5 - .. notice:: + .. note:: Before October 1, 2024, this event was called when a user's subscription was renewed. From 3ad15d6efe1356bf8b894fd2ad171fbb924f4065 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Thu, 29 Aug 2024 00:33:25 +0200 Subject: [PATCH 10/19] Update discord/enums.py Signed-off-by: Lala Sabathil --- discord/enums.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/discord/enums.py b/discord/enums.py index 6721938601..57601a24eb 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -1056,9 +1056,9 @@ class PollLayoutType(Enum): class SubscriptionStatus(Enum): """The status of a subscription.""" - active = 1 - ending = 2 - inactive = 3 + active = 0 + ending = 1 + inactive = 2 T = TypeVar("T") From 3b1a695443850e7f230b554a0446f947f1ed8b4b Mon Sep 17 00:00:00 2001 From: plun1331 Date: Wed, 28 Aug 2024 15:44:38 -0700 Subject: [PATCH 11/19] despite what it looks like, this is a lazily written commit message --- discord/monetization.py | 28 +++++++++++++++++++++++++++- docs/api/events.rst | 1 + docs/api/models.rst | 5 +++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/discord/monetization.py b/discord/monetization.py index 0f51161d8e..08ba976075 100644 --- a/discord/monetization.py +++ b/discord/monetization.py @@ -46,6 +46,7 @@ __all__ = ( "SKU", "Entitlement", + "Subscription", ) @@ -106,7 +107,7 @@ def url(self) -> str: """:class:`str`: Returns the URL for the SKU.""" return f"https://discord.com/application-directory/{self.application_id}/store/{self.id}" - def list_subscriptions( + def fetch_subscriptions( self, user: Snowflake, # user is required because this is a bot, we are not using oauth2 *, @@ -132,6 +133,31 @@ def list_subscriptions( If the datetime is naive, it is assumed to be local time. limit: :class:`int` | None The number of subscriptions to retrieve. If ``None``, retrieves all subscriptions. + + Yields + ------ + :class:`Subscription` + A subscription that the user has for this SKU. + + Raises + ------ + :exc:`HTTPException` + Getting the subscriptions failed. + + Examples + -------- + + Usage :: + + async for subscription in sku.fetch_subscriptions(discord.Object(id=123456789)): + print(subscription.status) + + Flattening into a list :: + + subscriptions = await sku.fetch_subscriptions(discord.Object(id=123456789)).flatten() + # subscriptions is now a list of Subscription... + + All parameters except for ``user`` are optional. """ return SubscriptionIterator( self._state, diff --git a/docs/api/events.rst b/docs/api/events.rst index c2dba18751..eace44a6f7 100644 --- a/docs/api/events.rst +++ b/docs/api/events.rst @@ -946,6 +946,7 @@ Monetization :param subscription: The subscription that was deleted. :type subscription: :class:`Subscription` + Polls ~~~~~~~~~ .. function:: on_poll_vote_add(poll, user, answer) diff --git a/docs/api/models.rst b/docs/api/models.rst index bea6b3c0de..921a940d92 100644 --- a/docs/api/models.rst +++ b/docs/api/models.rst @@ -114,6 +114,11 @@ Monetization .. autoclass:: Entitlement() :members: +.. attributetable:: Subscription + +.. autoclass:: Subscription() + :members: + Guild ----- From f31487e97f620305a7c03c92a283080deef05184 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Wed, 28 Aug 2024 15:51:37 -0700 Subject: [PATCH 12/19] despite what it looks like, this is a lazily written commit message 2 --- docs/api/events.rst | 147 ++++++++++++++++++++++---------------------- 1 file changed, 73 insertions(+), 74 deletions(-) diff --git a/docs/api/events.rst b/docs/api/events.rst index eace44a6f7..4d5280cc72 100644 --- a/docs/api/events.rst +++ b/docs/api/events.rst @@ -874,81 +874,8 @@ Messages :param payload: The raw event payload data. :type payload: :class:`RawMessageUpdateEvent` -Monetization ------------- -.. function:: on_entitlement_create(entitlement) - - Called when a user subscribes to an SKU. - - .. versionadded:: 2.5 - - :param entitlement: The entitlement that was created as a result of the subscription. - :type entitlement: :class:`Entitlement` - -.. function:: on_entitlement_update(entitlement) - - Called when a user's subscription to an Entitlement is cancelled. - - .. versionadded:: 2.5 - - .. note:: - - Before October 1, 2024, this event was called when a user's subscription was renewed. - - Entitlements that no longer follow this behavior will have a type of :attr:`EntitlementType.purchase`. - Those that follow the old behavior will have a type of :attr:`EntitlementType.application_subscription`. - - `See the Discord changelog. `_ - - :param entitlement: The entitlement that was updated. - :type entitlement: :class:`Entitlement` - -.. function:: on_entitlement_delete(entitlement) - - Called when a user's entitlement is deleted. - - Entitlements are usually only deleted when Discord issues a refund for a subscription, - or manually removes an entitlement from a user. - - .. note:: - - This is not called when a user's subscription is cancelled. - - .. versionadded:: 2.5 - - :param entitlement: The entitlement that was deleted. - :type entitlement: :class:`Entitlement` - -.. function:: on_subscription_create(subscription) - - Called when a subscription is created for the application. - - .. versionadded:: 2.7 - - :param subscription: The subscription that was created. - :type subscription: :class:`Subscription` - -.. function:: on_subscription_update(subscription) - - Called when a subscription has been updated. This could be a renewal, cancellation, or other payment related update. - - .. versionadded:: 2.7 - - :param subscription: The subscription that was updated. - :type subscription: :class:`Subscription` - -.. function:: on_subscription_delete(subscription) - - Called when a subscription has been deleted. - - .. versionadded:: 2.7 - - :param subscription: The subscription that was deleted. - :type subscription: :class:`Subscription` - - Polls -~~~~~~~~~ +~~~~~~ .. function:: on_poll_vote_add(poll, user, answer) Called when a vote is cast on a poll. If multiple answers were selected, this fires multiple times. @@ -1116,6 +1043,78 @@ Reactions :param payload: The raw event payload data. :type payload: :class:`RawReactionClearEmojiEvent` +Monetization +------------ +.. function:: on_entitlement_create(entitlement) + + Called when a user subscribes to an SKU. + + .. versionadded:: 2.5 + + :param entitlement: The entitlement that was created as a result of the subscription. + :type entitlement: :class:`Entitlement` + +.. function:: on_entitlement_update(entitlement) + + Called when a user's subscription to an Entitlement is cancelled. + + .. versionadded:: 2.5 + + .. note:: + + Before October 1, 2024, this event was called when a user's subscription was renewed. + + Entitlements that no longer follow this behavior will have a type of :attr:`EntitlementType.purchase`. + Those that follow the old behavior will have a type of :attr:`EntitlementType.application_subscription`. + + `See the Discord changelog. `_ + + :param entitlement: The entitlement that was updated. + :type entitlement: :class:`Entitlement` + +.. function:: on_entitlement_delete(entitlement) + + Called when a user's entitlement is deleted. + + Entitlements are usually only deleted when Discord issues a refund for a subscription, + or manually removes an entitlement from a user. + + .. note:: + + This is not called when a user's subscription is cancelled. + + .. versionadded:: 2.5 + + :param entitlement: The entitlement that was deleted. + :type entitlement: :class:`Entitlement` + +.. function:: on_subscription_create(subscription) + + Called when a subscription is created for the application. + + .. versionadded:: 2.7 + + :param subscription: The subscription that was created. + :type subscription: :class:`Subscription` + +.. function:: on_subscription_update(subscription) + + Called when a subscription has been updated. This could be a renewal, cancellation, or other payment related update. + + .. versionadded:: 2.7 + + :param subscription: The subscription that was updated. + :type subscription: :class:`Subscription` + +.. function:: on_subscription_delete(subscription) + + Called when a subscription has been deleted. + + .. versionadded:: 2.7 + + :param subscription: The subscription that was deleted. + :type subscription: :class:`Subscription` + Scheduled Events ---------------- .. function:: on_scheduled_event_create(event) From 121268ba166e46a0a6288d1d3eaa3700cdcf48bf Mon Sep 17 00:00:00 2001 From: plun1331 Date: Thu, 19 Sep 2024 11:16:52 -0700 Subject: [PATCH 13/19] fix bugs --- discord/http.py | 4 ++-- discord/iterators.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/discord/http.py b/discord/http.py index 789faf9cf0..e85ac0efb1 100644 --- a/discord/http.py +++ b/discord/http.py @@ -3027,7 +3027,7 @@ def delete_test_entitlement( ) return self.request(r) - async def list_sku_subscriptions( + def list_sku_subscriptions( self, sku_id: Snowflake, *, @@ -3050,7 +3050,7 @@ async def list_sku_subscriptions( params=params, ) - async def get_subscription( + def get_subscription( self, sku_id: Snowflake, subscription_id: Snowflake, diff --git a/discord/iterators.py b/discord/iterators.py index 735cb97b33..c292fdc861 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -1011,7 +1011,7 @@ def _get_retrieve(self): self.retrieve = r return r > 0 - async def create_entitlement(self, data): + def create_entitlement(self, data): from .monetization import Entitlement return Entitlement(data=data, state=self.state) From 74a96c85a9468fee2b01133dd30d1e428e4d92ad Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 18:26:40 +0000 Subject: [PATCH 14/19] style(pre-commit): auto fixes from pre-commit.com hooks --- CHANGELOG.md | 2 -- discord/iterators.py | 5 +++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index beafe7a86b..5d116379b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,7 +52,6 @@ These changes are available on the `master` branch, but have not yet been releas - Fixed `AttributeError` when trying to consume a consumable entitlement. ([#2564](https://github.com/Pycord-Development/pycord/pull/2564)) - ## [2.6.1] - 2024-09-15 ### Fixed @@ -80,7 +79,6 @@ These changes are available on the `master` branch, but have not yet been releas - Fixed inverted type hints in `CheckAnyFailure`. ([#2502](https://github.com/Pycord-Development/pycord/pull/2502)) - ## [2.6.0] - 2024-07-09 ### Added diff --git a/discord/iterators.py b/discord/iterators.py index c95545e432..673ff8aaae 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -64,7 +64,8 @@ from .types.audit_log import AuditLog as AuditLogPayload from .types.guild import Guild as GuildPayload from .types.message import Message as MessagePayload - from .types.monetization import Entitlement as EntitlementPayload, Subscription as SubscriptionPayload + from .types.monetization import Entitlement as EntitlementPayload + from .types.monetization import Subscription as SubscriptionPayload from .types.threads import Thread as ThreadPayload from .types.user import PartialUser as PartialUserPayload from .user import User @@ -1064,7 +1065,7 @@ async def _retrieve_entitlements_before_strategy( self.limit -= retrieve self.before = Object(id=int(data[-1]["id"])) return data - + async def _retrieve_entitlements_after_strategy( self, retrieve: int ) -> list[EntitlementPayload]: From e4aaaec2ab5bc1409e5660329db4a615cb2bf76d Mon Sep 17 00:00:00 2001 From: plun1331 Date: Thu, 19 Sep 2024 11:27:10 -0700 Subject: [PATCH 15/19] Update CHANGELOG.md Signed-off-by: plun1331 --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d116379b2..2c61a0f3b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,8 +22,6 @@ These changes are available on the `master` branch, but have not yet been releas `tags`. ([#2520](https://github.com/Pycord-Development/pycord/pull/2520)) - Added `Member.guild_banner` and `Member.display_banner` properties. ([#2556](https://github.com/Pycord-Development/pycord/pull/2556)) -- Added `Guild.fetch_role` method. - ([#2528](https://github.com/Pycord-Development/pycord/pull/2528)) - Added new `Subscription` object and related methods/events. ([#2564](https://github.com/Pycord-Development/pycord/pull/2564)) From b8a6bb76433636bbc6ec40037c80d54e15517372 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Thu, 19 Sep 2024 11:30:57 -0700 Subject: [PATCH 16/19] Update iterators.py Signed-off-by: plun1331 --- discord/iterators.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/discord/iterators.py b/discord/iterators.py index 673ff8aaae..3ce4cad292 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -59,6 +59,7 @@ from .guild import BanEntry, Guild from .member import Member from .message import Message + from .monetization import Entitlement, Subscription from .scheduled_events import ScheduledEvent from .threads import Thread from .types.audit_log import AuditLog as AuditLogPayload @@ -1022,7 +1023,7 @@ def _get_retrieve(self): self.retrieve = r return r > 0 - def create_entitlement(self, data): + def create_entitlement(self, data) -> Entitlement: from .monetization import Entitlement return Entitlement(data=data, state=self.state) @@ -1042,7 +1043,7 @@ async def fill_entitlements(self): for element in data: await self.entitlements.put(self.create_entitlement(element)) - async def _retrieve_entitlements(self, retrieve) -> list[Entitlement]: + async def _retrieve_entitlements(self, retrieve) -> list[EntitlementPayload]: """Retrieve entitlements and update next parameters.""" raise NotImplementedError @@ -1140,7 +1141,7 @@ def _get_retrieve(self): self.retrieve = r return r > 0 - def create_subscription(self, data): + def create_subscription(self, data) -> Subscription: from .monetization import Subscription return Subscription(state=self.state, data=data) From 4477b802ea7a2f653705d9c1bd06c6fc8f3f733e Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 21 Sep 2024 18:20:46 -0700 Subject: [PATCH 17/19] apply review suggestions --- discord/iterators.py | 6 +++--- discord/monetization.py | 2 +- docs/api/enums.rst | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/discord/iterators.py b/discord/iterators.py index 3ce4cad292..73662574cb 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -1116,12 +1116,12 @@ def __init__( self.subscriptions = asyncio.Queue() if self.before and self.after: - self._retrieve_subscriptions = self._retrieve_subscriptions_before_strategy # type: ignore + self._retrieve_subscriptions = self._retrieve_subscriptions_before_strategy self._filter = lambda m: int(m["id"]) > self.after.id elif self.after: - self._retrieve_subscriptions = self._retrieve_subscriptions_after_strategy # type: ignore + self._retrieve_subscriptions = self._retrieve_subscriptions_after_strategy else: - self._retrieve_subscriptions = self._retrieve_subscriptions_before_strategy # type: ignore + self._retrieve_subscriptions = self._retrieve_subscriptions_before_strategy async def next(self) -> Guild: if self.subscriptions.empty(): diff --git a/discord/monetization.py b/discord/monetization.py index 08ba976075..196e7f445e 100644 --- a/discord/monetization.py +++ b/discord/monetization.py @@ -122,7 +122,7 @@ def fetch_subscriptions( Parameters ---------- user: :class:`.abc.Snowflake` - The user to retrieve subscriptions for. + The user for which to retrieve subscriptions. before: :class:`.abc.Snowflake` | :class:`datetime.datetime` | None Retrieves subscriptions before this date or object. If a datetime is provided, it is recommended to use a UTC-aware datetime. diff --git a/docs/api/enums.rst b/docs/api/enums.rst index a9700a8fc4..8494217b7a 100644 --- a/docs/api/enums.rst +++ b/docs/api/enums.rst @@ -2514,7 +2514,7 @@ of :class:`enum.Enum`. .. attribute:: ending - The subscription is active but will not renew + The subscription is active but will not renew. .. attribute:: inactive From 3028e56dd9269447441be33d1a5dd1b57707788c Mon Sep 17 00:00:00 2001 From: plun1331 Date: Fri, 31 Jan 2025 12:39:02 -0800 Subject: [PATCH 18/19] Update monetization.py Signed-off-by: plun1331 --- discord/types/monetization.py | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/types/monetization.py b/discord/types/monetization.py index 59c16a3d63..13ed22ccc3 100644 --- a/discord/types/monetization.py +++ b/discord/types/monetization.py @@ -70,6 +70,7 @@ class Subscription(TypedDict): user_id: Snowflake sku_ids: list[Snowflake] entitlement_ids: list[Snowflake] + renewal_sku_ids: list[Snowflake] current_period_start: str current_period_end: str status: SubscriptionStatus From 7d08b2c9ec8d997c5bacd1e028ae509747c8c929 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Fri, 31 Jan 2025 12:40:51 -0800 Subject: [PATCH 19/19] Update monetization.py Signed-off-by: plun1331 --- discord/monetization.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/discord/monetization.py b/discord/monetization.py index 196e7f445e..daa4bf0806 100644 --- a/discord/monetization.py +++ b/discord/monetization.py @@ -295,6 +295,8 @@ class Subscription(Hashable): The IDs of the SKUs this subscription is for. entitlement_ids: List[:class:`int`] The IDs of the entitlements this subscription is for. + renewal_sku_ids: List[:class:`int`] + The IDs of the SKUs that the buyer will be subscribed to at renewal. current_period_start: :class:`datetime.datetime` The start of the current subscription period. current_period_end: :class:`datetime.datetime` @@ -311,6 +313,7 @@ class Subscription(Hashable): "user_id", "sku_ids", "entitlement_ids", + "renewal_sku_ids", "current_period_start", "current_period_end", "status", @@ -324,6 +327,7 @@ def __init__(self, *, state: ConnectionState, data: SubscriptionPayload) -> None self.user_id: int = int(data["user_id"]) self.sku_ids: list[int] = list(map(int, data["sku_ids"])) self.entitlement_ids: list[int] = list(map(int, data["entitlement_ids"])) + self.renewal_sku_ids: list[int] = list(map(int, data["renewal_sku_ids"])) self.current_period_start: datetime = parse_time(data["current_period_start"]) self.current_period_end: datetime = parse_time(data["current_period_end"]) self.status: SubscriptionStatus = try_enum(SubscriptionStatus, data["status"])