Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
::: interactions.models.discord.entitlement
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ search:
- [Components](components)
- [Embed](embed)
- [Emoji](emoji)
- [Entitlement](entitlement)
- [Enums](enums)
- [File](file)
- [Guild](guild)
Expand Down
2 changes: 2 additions & 0 deletions interactions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
EmbedField,
EmbedFooter,
EmbedProvider,
Entitlement,
ExplicitContentFilterLevel,
Extension,
File,
Expand Down Expand Up @@ -455,6 +456,7 @@
"EmbedField",
"EmbedFooter",
"EmbedProvider",
"Entitlement",
"errors",
"events",
"ExplicitContentFilterLevel",
Expand Down
6 changes: 6 additions & 0 deletions interactions/api/events/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
ChannelDelete,
ChannelPinsUpdate,
ChannelUpdate,
EntitlementCreate,
EntitlementDelete,
EntitlementUpdate,
GuildAuditLogEntryCreate,
GuildAvailable,
GuildEmojisUpdate,
Expand Down Expand Up @@ -120,6 +123,9 @@
"ComponentError",
"Connect",
"Disconnect",
"EntitlementCreate",
"EntitlementDelete",
"EntitlementUpdate",
"Error",
"ExtensionCommandParse",
"ExtensionLoad",
Expand Down
29 changes: 29 additions & 0 deletions interactions/api/events/discord.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ async def an_event_handler(event: ChannelCreate):
"ChannelDelete",
"ChannelPinsUpdate",
"ChannelUpdate",
"EntitlementCreate",
"EntitlementDelete",
"EntitlementUpdate",
"GuildAuditLogEntryCreate",
"GuildEmojisUpdate",
"GuildJoin",
Expand Down Expand Up @@ -108,6 +111,7 @@ async def an_event_handler(event: ChannelCreate):
VoiceChannel,
)
from interactions.models.discord.emoji import CustomEmoji, PartialEmoji
from interactions.models.discord.entitlement import Entitlement
from interactions.models.discord.guild import Guild, GuildIntegration
from interactions.models.discord.message import Message
from interactions.models.discord.reaction import Reaction
Expand Down Expand Up @@ -821,3 +825,28 @@ def member(self) -> Optional["Member"]:
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildScheduledEventUserRemove(GuildScheduledEventUserAdd):
"""Dispatched when scheduled event is removed"""


@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class BaseEntitlementEvent(BaseEvent):
entitlement: "Entitlement" = attrs.field(repr=True)


@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class EntitlementCreate(BaseEntitlementEvent):
"""Dispatched when a user subscribes to a SKU."""


@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class EntitlementUpdate(BaseEntitlementEvent):
"""Dispatched when a user's subscription renews for the next billing period."""


@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class EntitlementDelete(BaseEntitlementEvent):
"""
Dispatched when a user's entitlement is deleted.

Notably, this event is not dispatched when a user's subscription is cancelled.
Instead, you simply won't receive an EntitlementUpdate event for the next billing period.
"""
2 changes: 2 additions & 0 deletions interactions/api/events/processors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .voice_events import VoiceEvents
from ._template import Processor
from .auto_mod import AutoModEvents
from .entitlement_events import EntitlementEvents

__all__ = (
"ChannelEvents",
Expand All @@ -28,4 +29,5 @@
"VoiceEvents",
"Processor",
"AutoModEvents",
"EntitlementEvents",
)
22 changes: 22 additions & 0 deletions interactions/api/events/processors/entitlement_events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import TYPE_CHECKING

from interactions.models.discord.entitlement import Entitlement
import interactions.api.events as events
from ._template import EventMixinTemplate, Processor

if TYPE_CHECKING:
from interactions.api.events import RawGatewayEvent


class EntitlementEvents(EventMixinTemplate):
@Processor.define()
async def _on_raw_entitlement_create(self, event: "RawGatewayEvent") -> None:
self.dispatch(events.EntitlementCreate(Entitlement.from_dict(event.data, self)))

@Processor.define()
async def _on_raw_entitlement_update(self, event: "RawGatewayEvent") -> None:
self.dispatch(events.EntitlementUpdate(Entitlement.from_dict(event.data, self)))

@Processor.define()
async def _on_raw_entitlement_delete(self, event: "RawGatewayEvent") -> None:
self.dispatch(events.EntitlementDelete(Entitlement.from_dict(event.data, self)))
2 changes: 2 additions & 0 deletions interactions/api/http/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
BotRequests,
ChannelRequests,
EmojiRequests,
EntitlementRequests,
GuildRequests,
InteractionRequests,
MemberRequests,
Expand Down Expand Up @@ -204,6 +205,7 @@ class HTTPClient(
BotRequests,
ChannelRequests,
EmojiRequests,
EntitlementRequests,
GuildRequests,
InteractionRequests,
MemberRequests,
Expand Down
2 changes: 2 additions & 0 deletions interactions/api/http/http_requests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .bot import BotRequests
from .channels import ChannelRequests
from .emojis import EmojiRequests
from .entitlements import EntitlementRequests
from .guild import GuildRequests
from .interactions import InteractionRequests
from .members import MemberRequests
Expand All @@ -16,6 +17,7 @@
"BotRequests",
"ChannelRequests",
"EmojiRequests",
"EntitlementRequests",
"GuildRequests",
"InteractionRequests",
"MemberRequests",
Expand Down
88 changes: 88 additions & 0 deletions interactions/api/http/http_requests/entitlements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from typing import TYPE_CHECKING, Optional

from ..route import Route, PAYLOAD_TYPE
from interactions.models.internal.protocols import CanRequest
from interactions.models.discord.snowflake import to_optional_snowflake, to_snowflake
from interactions.client.utils.serializer import dict_filter_none

if TYPE_CHECKING:
from interactions.models.discord.snowflake import Snowflake_Type

__all__ = ("EntitlementRequests",)


class EntitlementRequests(CanRequest):
async def get_entitlements(
self,
application_id: "Snowflake_Type",
*,
user_id: "Optional[Snowflake_Type]" = None,
sku_ids: "Optional[list[Snowflake_Type]]" = None,
before: "Optional[Snowflake_Type]" = None,
after: "Optional[Snowflake_Type]" = None,
limit: Optional[int] = 100,
guild_id: "Optional[Snowflake_Type]" = None,
exclude_ended: Optional[bool] = None,
) -> list[dict]:
"""
Get an application's entitlements.

Args:
application_id: The ID of the application.
user_id: The ID of the user to filter entitlements by.
sku_ids: The IDs of the SKUs to filter entitlements by.
before: Get entitlements before this ID.
after: Get entitlements after this ID.
limit: The maximum number of entitlements to return. Maximum is 100.
guild_id: The ID of the guild to filter entitlements by.
exclude_ended: Whether to exclude ended entitlements.

Returns:
A dictionary containing the application's entitlements.
"""
params: PAYLOAD_TYPE = {
"user_id": to_optional_snowflake(user_id),
"sku_ids": [to_snowflake(sku_id) for sku_id in sku_ids] if sku_ids else None,
"before": to_optional_snowflake(before),
"after": to_optional_snowflake(after),
"limit": limit,
"guild_id": to_optional_snowflake(guild_id),
"exclude_ended": exclude_ended,
}
params = dict_filter_none(params)

return await self.request(
Route("GET", "/applications/{application_id}/entitlements", application_id=application_id), params=params
)

async def create_test_entitlement(self, payload: dict, application_id: "Snowflake_Type") -> dict:
"""
Create a test entitlement for an application.

Args:
payload: The entitlement's data.
application_id: The ID of the application.

Returns:
A dictionary containing the test entitlement.
"""
return await self.request(
Route("POST", "/applications/{application_id}/entitlements", application_id=application_id), payload=payload
)

async def delete_test_entitlement(self, application_id: "Snowflake_Type", entitlement_id: "Snowflake_Type") -> None:
"""
Delete a test entitlement for an application.

Args:
application_id: The ID of the application.
entitlement_id: The ID of the entitlement.
"""
await self.request(
Route(
"DELETE",
"/applications/{application_id}/entitlements/{entitlement_id}",
application_id=application_id,
entitlement_id=entitlement_id,
)
)
68 changes: 68 additions & 0 deletions interactions/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
from interactions.models.discord.color import BrandColors
from interactions.models.discord.components import get_components_ids, BaseComponent
from interactions.models.discord.embed import Embed
from interactions.models.discord.entitlement import Entitlement
from interactions.models.discord.enums import (
ComponentType,
Intents,
Expand Down Expand Up @@ -213,6 +214,7 @@
class Client(
processors.AutoModEvents,
processors.ChannelEvents,
processors.EntitlementEvents,
processors.GuildEvents,
processors.IntegrationEvents,
processors.MemberEvents,
Expand Down Expand Up @@ -2464,6 +2466,72 @@ def get_bot_voice_state(self, guild_id: "Snowflake_Type") -> Optional[ActiveVoic
"""
return self._connection_state.get_voice_state(guild_id)

async def fetch_entitlements(
self,
*,
user_id: "Optional[Snowflake_Type]" = None,
sku_ids: "Optional[list[Snowflake_Type]]" = None,
before: "Optional[Snowflake_Type]" = None,
after: "Optional[Snowflake_Type]" = None,
limit: Optional[int] = 100,
guild_id: "Optional[Snowflake_Type]" = None,
exclude_ended: Optional[bool] = None,
) -> List[Entitlement]:
"""
Fetch the entitlements for the bot's application.

Args:
user_id: The ID of the user to filter entitlements by.
sku_ids: The IDs of the SKUs to filter entitlements by.
before: Get entitlements before this ID.
after: Get entitlements after this ID.
limit: The maximum number of entitlements to return. Maximum is 100.
guild_id: The ID of the guild to filter entitlements by.
exclude_ended: Whether to exclude ended entitlements.

Returns:
A list of entitlements.
"""
entitlements_data = await self.http.get_entitlements(
self.app.id,
user_id=user_id,
sku_ids=sku_ids,
before=before,
after=after,
limit=limit,
guild_id=guild_id,
exclude_ended=exclude_ended,
)
return Entitlement.from_list(entitlements_data, self)

async def create_test_entitlement(
self, sku_id: "Snowflake_Type", owner_id: "Snowflake_Type", owner_type: int
) -> Entitlement:
"""
Create a test entitlement for the bot's application.

Args:
sku_id: The ID of the SKU to create the entitlement for.
owner_id: The ID of the owner of the entitlement.
owner_type: The type of the owner of the entitlement. 1 for a guild subscription, 2 for a user subscription

Returns:
The created entitlement.
"""
payload = {"sku_id": to_snowflake(sku_id), "owner_id": to_snowflake(owner_id), "owner_type": owner_type}

entitlement_data = await self.http.create_test_entitlement(payload, self.app.id)
return Entitlement.from_dict(entitlement_data, self)

async def delete_test_entitlement(self, entitlement_id: "Snowflake_Type") -> None:
"""
Delete a test entitlement for the bot's application.

Args:
entitlement_id: The ID of the entitlement to delete.
"""
await self.http.delete_test_entitlement(self.app.id, to_snowflake(entitlement_id))

def mention_command(self, name: str, scope: int = 0) -> str:
"""
Returns a string that would mention the interaction specified.
Expand Down
2 changes: 2 additions & 0 deletions interactions/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
EmbedField,
EmbedFooter,
EmbedProvider,
Entitlement,
ExplicitContentFilterLevel,
File,
FlatUIColors,
Expand Down Expand Up @@ -396,6 +397,7 @@
"EmbedField",
"EmbedFooter",
"EmbedProvider",
"Entitlement",
"ExplicitContentFilterLevel",
"Extension",
"File",
Expand Down
2 changes: 2 additions & 0 deletions interactions/models/discord/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@

from .embed import Embed, EmbedAttachment, EmbedAuthor, EmbedField, EmbedFooter, EmbedProvider, process_embeds
from .emoji import CustomEmoji, PartialEmoji, process_emoji, process_emoji_req_format
from .entitlement import Entitlement
from .enums import (
ActivityFlag,
ActivityType,
Expand Down Expand Up @@ -223,6 +224,7 @@
"EmbedField",
"EmbedFooter",
"EmbedProvider",
"Entitlement",
"ExplicitContentFilterLevel",
"File",
"FlatUIColors",
Expand Down
Loading