Skip to content

Commit b41c47e

Browse files
authored
feat: add entitlement/app subscription support (#1617)
1 parent 59a03f5 commit b41c47e

File tree

16 files changed

+299
-0
lines changed

16 files changed

+299
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
::: interactions.models.discord.entitlement

docs/src/API Reference/API Reference/models/Discord/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ search:
1414
- [Components](components)
1515
- [Embed](embed)
1616
- [Emoji](emoji)
17+
- [Entitlement](entitlement)
1718
- [Enums](enums)
1819
- [File](file)
1920
- [Guild](guild)

interactions/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@
129129
EmbedField,
130130
EmbedFooter,
131131
EmbedProvider,
132+
Entitlement,
132133
ExplicitContentFilterLevel,
133134
Extension,
134135
File,
@@ -455,6 +456,7 @@
455456
"EmbedField",
456457
"EmbedFooter",
457458
"EmbedProvider",
459+
"Entitlement",
458460
"errors",
459461
"events",
460462
"ExplicitContentFilterLevel",

interactions/api/events/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
ChannelDelete,
1313
ChannelPinsUpdate,
1414
ChannelUpdate,
15+
EntitlementCreate,
16+
EntitlementDelete,
17+
EntitlementUpdate,
1518
GuildAuditLogEntryCreate,
1619
GuildAvailable,
1720
GuildEmojisUpdate,
@@ -120,6 +123,9 @@
120123
"ComponentError",
121124
"Connect",
122125
"Disconnect",
126+
"EntitlementCreate",
127+
"EntitlementDelete",
128+
"EntitlementUpdate",
123129
"Error",
124130
"ExtensionCommandParse",
125131
"ExtensionLoad",

interactions/api/events/discord.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ async def an_event_handler(event: ChannelCreate):
4343
"ChannelDelete",
4444
"ChannelPinsUpdate",
4545
"ChannelUpdate",
46+
"EntitlementCreate",
47+
"EntitlementDelete",
48+
"EntitlementUpdate",
4649
"GuildAuditLogEntryCreate",
4750
"GuildEmojisUpdate",
4851
"GuildJoin",
@@ -108,6 +111,7 @@ async def an_event_handler(event: ChannelCreate):
108111
VoiceChannel,
109112
)
110113
from interactions.models.discord.emoji import CustomEmoji, PartialEmoji
114+
from interactions.models.discord.entitlement import Entitlement
111115
from interactions.models.discord.guild import Guild, GuildIntegration
112116
from interactions.models.discord.message import Message
113117
from interactions.models.discord.reaction import Reaction
@@ -821,3 +825,28 @@ def member(self) -> Optional["Member"]:
821825
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
822826
class GuildScheduledEventUserRemove(GuildScheduledEventUserAdd):
823827
"""Dispatched when scheduled event is removed"""
828+
829+
830+
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
831+
class BaseEntitlementEvent(BaseEvent):
832+
entitlement: "Entitlement" = attrs.field(repr=True)
833+
834+
835+
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
836+
class EntitlementCreate(BaseEntitlementEvent):
837+
"""Dispatched when a user subscribes to a SKU."""
838+
839+
840+
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
841+
class EntitlementUpdate(BaseEntitlementEvent):
842+
"""Dispatched when a user's subscription renews for the next billing period."""
843+
844+
845+
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
846+
class EntitlementDelete(BaseEntitlementEvent):
847+
"""
848+
Dispatched when a user's entitlement is deleted.
849+
850+
Notably, this event is not dispatched when a user's subscription is cancelled.
851+
Instead, you simply won't receive an EntitlementUpdate event for the next billing period.
852+
"""

interactions/api/events/processors/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .voice_events import VoiceEvents
1313
from ._template import Processor
1414
from .auto_mod import AutoModEvents
15+
from .entitlement_events import EntitlementEvents
1516

1617
__all__ = (
1718
"ChannelEvents",
@@ -28,4 +29,5 @@
2829
"VoiceEvents",
2930
"Processor",
3031
"AutoModEvents",
32+
"EntitlementEvents",
3133
)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from typing import TYPE_CHECKING
2+
3+
from interactions.models.discord.entitlement import Entitlement
4+
import interactions.api.events as events
5+
from ._template import EventMixinTemplate, Processor
6+
7+
if TYPE_CHECKING:
8+
from interactions.api.events import RawGatewayEvent
9+
10+
11+
class EntitlementEvents(EventMixinTemplate):
12+
@Processor.define()
13+
async def _on_raw_entitlement_create(self, event: "RawGatewayEvent") -> None:
14+
self.dispatch(events.EntitlementCreate(Entitlement.from_dict(event.data, self)))
15+
16+
@Processor.define()
17+
async def _on_raw_entitlement_update(self, event: "RawGatewayEvent") -> None:
18+
self.dispatch(events.EntitlementUpdate(Entitlement.from_dict(event.data, self)))
19+
20+
@Processor.define()
21+
async def _on_raw_entitlement_delete(self, event: "RawGatewayEvent") -> None:
22+
self.dispatch(events.EntitlementDelete(Entitlement.from_dict(event.data, self)))

interactions/api/http/http_client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
BotRequests,
2020
ChannelRequests,
2121
EmojiRequests,
22+
EntitlementRequests,
2223
GuildRequests,
2324
InteractionRequests,
2425
MemberRequests,
@@ -204,6 +205,7 @@ class HTTPClient(
204205
BotRequests,
205206
ChannelRequests,
206207
EmojiRequests,
208+
EntitlementRequests,
207209
GuildRequests,
208210
InteractionRequests,
209211
MemberRequests,

interactions/api/http/http_requests/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .bot import BotRequests
22
from .channels import ChannelRequests
33
from .emojis import EmojiRequests
4+
from .entitlements import EntitlementRequests
45
from .guild import GuildRequests
56
from .interactions import InteractionRequests
67
from .members import MemberRequests
@@ -16,6 +17,7 @@
1617
"BotRequests",
1718
"ChannelRequests",
1819
"EmojiRequests",
20+
"EntitlementRequests",
1921
"GuildRequests",
2022
"InteractionRequests",
2123
"MemberRequests",
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from typing import TYPE_CHECKING, Optional
2+
3+
from ..route import Route, PAYLOAD_TYPE
4+
from interactions.models.internal.protocols import CanRequest
5+
from interactions.models.discord.snowflake import to_optional_snowflake, to_snowflake
6+
from interactions.client.utils.serializer import dict_filter_none
7+
8+
if TYPE_CHECKING:
9+
from interactions.models.discord.snowflake import Snowflake_Type
10+
11+
__all__ = ("EntitlementRequests",)
12+
13+
14+
class EntitlementRequests(CanRequest):
15+
async def get_entitlements(
16+
self,
17+
application_id: "Snowflake_Type",
18+
*,
19+
user_id: "Optional[Snowflake_Type]" = None,
20+
sku_ids: "Optional[list[Snowflake_Type]]" = None,
21+
before: "Optional[Snowflake_Type]" = None,
22+
after: "Optional[Snowflake_Type]" = None,
23+
limit: Optional[int] = 100,
24+
guild_id: "Optional[Snowflake_Type]" = None,
25+
exclude_ended: Optional[bool] = None,
26+
) -> list[dict]:
27+
"""
28+
Get an application's entitlements.
29+
30+
Args:
31+
application_id: The ID of the application.
32+
user_id: The ID of the user to filter entitlements by.
33+
sku_ids: The IDs of the SKUs to filter entitlements by.
34+
before: Get entitlements before this ID.
35+
after: Get entitlements after this ID.
36+
limit: The maximum number of entitlements to return. Maximum is 100.
37+
guild_id: The ID of the guild to filter entitlements by.
38+
exclude_ended: Whether to exclude ended entitlements.
39+
40+
Returns:
41+
A dictionary containing the application's entitlements.
42+
"""
43+
params: PAYLOAD_TYPE = {
44+
"user_id": to_optional_snowflake(user_id),
45+
"sku_ids": [to_snowflake(sku_id) for sku_id in sku_ids] if sku_ids else None,
46+
"before": to_optional_snowflake(before),
47+
"after": to_optional_snowflake(after),
48+
"limit": limit,
49+
"guild_id": to_optional_snowflake(guild_id),
50+
"exclude_ended": exclude_ended,
51+
}
52+
params = dict_filter_none(params)
53+
54+
return await self.request(
55+
Route("GET", "/applications/{application_id}/entitlements", application_id=application_id), params=params
56+
)
57+
58+
async def create_test_entitlement(self, payload: dict, application_id: "Snowflake_Type") -> dict:
59+
"""
60+
Create a test entitlement for an application.
61+
62+
Args:
63+
payload: The entitlement's data.
64+
application_id: The ID of the application.
65+
66+
Returns:
67+
A dictionary containing the test entitlement.
68+
"""
69+
return await self.request(
70+
Route("POST", "/applications/{application_id}/entitlements", application_id=application_id), payload=payload
71+
)
72+
73+
async def delete_test_entitlement(self, application_id: "Snowflake_Type", entitlement_id: "Snowflake_Type") -> None:
74+
"""
75+
Delete a test entitlement for an application.
76+
77+
Args:
78+
application_id: The ID of the application.
79+
entitlement_id: The ID of the entitlement.
80+
"""
81+
await self.request(
82+
Route(
83+
"DELETE",
84+
"/applications/{application_id}/entitlements/{entitlement_id}",
85+
application_id=application_id,
86+
entitlement_id=entitlement_id,
87+
)
88+
)

0 commit comments

Comments
 (0)