Skip to content

feat: add entitlement/app subscription support #1617

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 11, 2024
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
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy