27
27
28
28
from typing import TYPE_CHECKING
29
29
30
- from .enums import EntitlementType , SKUType , try_enum
30
+ from .abc import Snowflake , SnowflakeTime
31
+ from .enums import EntitlementType , SKUType , SubscriptionStatus , try_enum
31
32
from .flags import SKUFlags
33
+ from .iterators import SubscriptionIterator
32
34
from .mixins import Hashable
33
- from .utils import MISSING , _get_as_snowflake , parse_time
35
+ from .utils import cached_property , cached_slot_property , MISSING , _get_as_snowflake , parse_time
34
36
35
37
if TYPE_CHECKING :
36
38
from datetime import datetime
37
39
38
40
from .state import ConnectionState
39
41
from .types .monetization import SKU as SKUPayload
40
42
from .types .monetization import Entitlement as EntitlementPayload
43
+ from .types .monetization import Subscription as SubscriptionPayload
41
44
42
45
43
46
__all__ = (
@@ -68,6 +71,7 @@ class SKU(Hashable):
68
71
"""
69
72
70
73
__slots__ = (
74
+ "_state" ,
71
75
"id" ,
72
76
"type" ,
73
77
"application_id" ,
@@ -76,7 +80,8 @@ class SKU(Hashable):
76
80
"flags" ,
77
81
)
78
82
79
- def __init__ (self , * , data : SKUPayload ) -> None :
83
+ def __init__ (self , * , state : ConnectionState , data : SKUPayload ) -> None :
84
+ self ._state : ConnectionState = state
80
85
self .id : int = int (data ["id" ])
81
86
self .type : SKUType = try_enum (SKUType , data ["type" ])
82
87
self .application_id : int = int (data ["application_id" ])
@@ -101,12 +106,48 @@ def url(self) -> str:
101
106
""":class:`str`: Returns the URL for the SKU."""
102
107
return f"https://discord.com/application-directory/{ self .application_id } /store/{ self .id } "
103
108
109
+ def list_subscriptions (
110
+ self ,
111
+ user : Snowflake , # user is required because this is a bot, we are not using oauth2
112
+ * ,
113
+ before : SnowflakeTime | None = None ,
114
+ after : SnowflakeTime | None = None ,
115
+ limit : int | None = 100 ,
116
+ ) -> SubscriptionIterator :
117
+ """Returns an :class:`.AsyncIterator` that enables fetching the SKU's subscriptions.
118
+
119
+ .. versionadded:: 2.7
120
+
121
+ Parameters
122
+ ----------
123
+ user: :class:`.abc.Snowflake`
124
+ The user to retrieve subscriptions for.
125
+ before: :class:`.abc.Snowflake` | :class:`datetime.datetime` | None
126
+ Retrieves subscriptions before this date or object.
127
+ If a datetime is provided, it is recommended to use a UTC-aware datetime.
128
+ If the datetime is naive, it is assumed to be local time.
129
+ after: :class:`.abc.Snowflake` | :class:`datetime.datetime` | None
130
+ Retrieve subscriptions after this date or object.
131
+ If a datetime is provided, it is recommended to use a UTC-aware datetime.
132
+ If the datetime is naive, it is assumed to be local time.
133
+ limit: :class:`int` | None
134
+ The number of subscriptions to retrieve. If ``None``, retrieves all subscriptions.
135
+ """
136
+ return SubscriptionIterator (self ._state , self .id , user_id = user .id , before = before , after = after , limit = limit )
137
+
104
138
105
139
class Entitlement (Hashable ):
106
140
"""Represents a Discord entitlement.
107
141
108
142
.. versionadded:: 2.5
109
143
144
+ .. notice::
145
+
146
+ As of October 1, 2024, entitlements that have been purchased will have ``ends_at`` set to ``None``
147
+ unless the parent :class:`Subscription` has been cancelled.
148
+
149
+ `See the Discord changelog. <https://discord.com/developers/docs/change-log#premium-apps-entitlement-migration-and-new-subscription-api>`_
150
+
110
151
Attributes
111
152
----------
112
153
id: :class:`int`
@@ -130,7 +171,7 @@ class Entitlement(Hashable):
130
171
consumed: :class:`bool`
131
172
Whether or not this entitlement has been consumed.
132
173
This will always be ``False`` for entitlements that are not
133
- of type :attr:`EntitlementType .consumable`.
174
+ from an SKU of type :attr:`SKUType .consumable`.
134
175
"""
135
176
136
177
__slots__ = (
@@ -158,7 +199,7 @@ def __init__(self, *, data: EntitlementPayload, state: ConnectionState) -> None:
158
199
self .starts_at : datetime | MISSING = (
159
200
parse_time (data .get ("starts_at" )) or MISSING
160
201
)
161
- self .ends_at : datetime | MISSING = parse_time (data .get ("ends_at" )) or MISSING
202
+ self .ends_at : datetime | MISSING | None = parse_time (ea ) if ( ea := data .get ("ends_at" )) is not None else MISSING
162
203
self .guild_id : int | MISSING = _get_as_snowflake (data , "guild_id" ) or MISSING
163
204
self .consumed : bool = data .get ("consumed" , False )
164
205
@@ -177,18 +218,13 @@ async def consume(self) -> None:
177
218
178
219
Consumes this entitlement.
179
220
180
- This can only be done on entitlements of type :attr:`EntitlementType .consumable`.
221
+ This can only be done on entitlements from an SKU of type :attr:`SKUType .consumable`.
181
222
182
223
Raises
183
224
------
184
- TypeError
185
- The entitlement is not consumable.
186
225
HTTPException
187
226
Consuming the entitlement failed.
188
227
"""
189
- if self .type is not EntitlementType .consumable :
190
- raise TypeError ("Cannot consume non-consumable entitlement" )
191
-
192
228
await self ._state .http .consume_entitlement (self ._state .application_id , self .id )
193
229
self .consumed = True
194
230
@@ -205,3 +241,69 @@ async def delete(self) -> None:
205
241
Deleting the entitlement failed.
206
242
"""
207
243
await self ._state .http .delete_test_entitlement (self .application_id , self .id )
244
+
245
+
246
+ class Subscription (Hashable ):
247
+ """Represents a user making recurring payments for one or more SKUs.
248
+
249
+ Successful payments grant the user access to entitlements associated with the SKU.
250
+
251
+ .. versionadded:: 2.7
252
+
253
+ Attributes
254
+ ----------
255
+ id: :class:`int`
256
+ The subscription's ID.
257
+ user_id: :class:`int`
258
+ The ID of the user that owns this subscription.
259
+ sku_ids: List[:class:`int`]
260
+ The IDs of the SKUs this subscription is for.
261
+ entitlement_ids: List[:class:`int`]
262
+ The IDs of the entitlements this subscription is for.
263
+ current_period_start: :class:`datetime.datetime`
264
+ The start of the current subscription period.
265
+ current_period_end: :class:`datetime.datetime`
266
+ The end of the current subscription period.
267
+ status: :class:`SubscriptionStatus`
268
+ The status of the subscription.
269
+ canceled_at: :class:`datetime.datetime` | ``None``
270
+ When the subscription was canceled.
271
+ """
272
+ __slots__ = (
273
+ "_state" ,
274
+ "id" ,
275
+ "user_id" ,
276
+ "sku_ids" ,
277
+ "entitlement_ids" ,
278
+ "current_period_start" ,
279
+ "current_period_end" ,
280
+ "status" ,
281
+ "canceled_at" ,
282
+ "country" ,
283
+ )
284
+
285
+ def __init__ (self , * , state : ConnectionState , data : SubscriptionPayload ) -> None :
286
+ self ._state : ConnectionState = state
287
+ self .id : int = int (data ["id" ])
288
+ self .user_id : int = int (data ["user_id" ])
289
+ self .sku_ids : list [int ] = list (map (int , data ["sku_ids" ]))
290
+ self .entitlement_ids : list [int ] = list (map (int , data ["entitlement_ids" ]))
291
+ self .current_period_start : datetime = parse_time (data ["current_period_start" ])
292
+ self .current_period_end : datetime = parse_time (data ["current_period_end" ])
293
+ self .status : SubscriptionStatus = try_enum (SubscriptionStatus , data ["status" ])
294
+ self .canceled_at : datetime | None = parse_time (data .get ("canceled_at" ))
295
+ self .country : str | None = data .get ("country" ) # Not documented, it is only available with oauth2, not bots
296
+
297
+ def __repr__ (self ) -> str :
298
+ return (
299
+ f"<Subscription id={ self .id } user_id={ self .user_id } status={ self .status } >"
300
+ )
301
+
302
+ def __eq__ (self , other : object ) -> bool :
303
+ return isinstance (other , self .__class__ ) and other .id == self .id
304
+
305
+ @property
306
+ def user (self ):
307
+ """Optional[:class:`User`]: The user that owns this subscription."""
308
+ return self ._state .get_user (self .user_id )
309
+
0 commit comments