diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2d37275c..5f7d9ae9 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,6 @@ repos:
entry: isort
language: python
types: [python]
-
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
diff --git a/docs/api/enums.rst b/docs/api/enums.rst
index b3b563e0..ef07a7d2 100644
--- a/docs/api/enums.rst
+++ b/docs/api/enums.rst
@@ -37,3 +37,5 @@ Enums
.. autoclass:: VerificationLevel
.. autoclass:: Status
.. autoclass:: ActivityType
+.. autoclass:: SKUType
+.. autoclass:: EntitlementType
diff --git a/docs/api/flags.rst b/docs/api/flags.rst
index 96e28ea8..2ce5691b 100644
--- a/docs/api/flags.rst
+++ b/docs/api/flags.rst
@@ -216,3 +216,9 @@ Flags
.. py:attribute:: auto_moderation_block_message
.. py:attribute:: auto_moderation_flag_to_channel
.. py:attribute:: auto_moderation_user_communication_disabled
+
+.. class:: SKUFlags
+
+ .. py:attribute:: available
+ .. py:attribute:: guild_subscription
+ .. py:attribute:: user_subscription
diff --git a/docs/api/models.rst b/docs/api/models.rst
index 41bf560b..f252eea0 100644
--- a/docs/api/models.rst
+++ b/docs/api/models.rst
@@ -293,3 +293,11 @@ Models that relate to interactions.
:inherited-members:
:no-special-members:
.. .. autoclass:: InteractionWebhook
+.. autoclass:: Entitlement
+ :members:
+ :inherited-members:
+ :no-special-members:
+.. autoclass:: SKU
+ :members:
+ :inherited-members:
+ :no-special-members:
diff --git a/novus/__init__.py b/novus/__init__.py
index e43a1630..b829d3f9 100644
--- a/novus/__init__.py
+++ b/novus/__init__.py
@@ -63,6 +63,8 @@
'ContextComandData',
'Embed',
'Emoji',
+ 'Entitlement',
+ 'EntitlementType',
'EventEntityType',
'EventPrivacyLevel',
'EventStatus',
@@ -132,6 +134,9 @@
'Reaction',
'Role',
'RoleSelectMenu',
+ 'SKU',
+ 'SKUFlags',
+ 'SKUType',
'ScheduledEvent',
'SelectOption',
'StageInstance',
diff --git a/novus/api/_cache.py b/novus/api/_cache.py
index 26cca597..e50d3d1a 100644
--- a/novus/api/_cache.py
+++ b/novus/api/_cache.py
@@ -83,6 +83,12 @@ def __repr__(self) -> str:
def do_nothing(instance: Any, *items: Any) -> None:
pass
+ @property
+ def application_id(self) -> int | None:
+ if self.application:
+ return self.application.id
+ return None
+
def add_guilds(self, *items: Guild) -> None:
for i in items:
self.guild_ids.add(i.id)
diff --git a/novus/api/_http.py b/novus/api/_http.py
index b41db348..4893269e 100644
--- a/novus/api/_http.py
+++ b/novus/api/_http.py
@@ -47,6 +47,7 @@
from .guild_template import GuildTemplateHTTPConnection
from .interaction import InteractionHTTPConnection
from .invite import InviteHTTPConnection
+from .monetization import MonetizationHTTPConnection
from .oauth2 import Oauth2HTTPConnection
from .stage_instance import StageHTTPConnection
from .sticker import StickerHTTPConnection
@@ -127,12 +128,15 @@ class HTTPConnection:
guild_template : GuildTemplateHTTPConnection
interaction : InteractionHTTPConnection
invite : InviteHTTPConnection
+ monetization : MonetizationHTTPConnection
oauth2 : Oauth2HTTPConnection
stage_instance : StageHTTPConnection
sticker : StickerHTTPConnection
user : UserHTTPConnection
voice : VoiceHTTPConnection
webhook : WebhookHTTPConnection
+ application_id : int
+ The ID of the associated application.
"""
AUTH_PREFIX: str = "Bot"
@@ -167,6 +171,7 @@ def __init__(
self.guild_template = GuildTemplateHTTPConnection(self)
self.interaction = InteractionHTTPConnection(self)
self.invite = InviteHTTPConnection(self)
+ self.monetization = MonetizationHTTPConnection(self)
self.oauth2 = Oauth2HTTPConnection(self)
self.stage_instance = StageHTTPConnection(self)
self.sticker = StickerHTTPConnection(self)
@@ -195,6 +200,10 @@ async def __aexit__(self, *_args: Any) -> None:
if self._session:
await self._session.close()
+ @property
+ def application_id(self) -> int:
+ return self.cache.application_id
+
def request_params(
self,
*,
diff --git a/novus/api/monetization.py b/novus/api/monetization.py
new file mode 100644
index 00000000..f8cc8f21
--- /dev/null
+++ b/novus/api/monetization.py
@@ -0,0 +1,92 @@
+"""
+Copyright (c) Kae Bartlett
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+any later version.
+
+This program is dis2tributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from ._route import Route
+from ..models import Entitlement, SKU
+
+if TYPE_CHECKING:
+ from ._http import HTTPConnection
+ from .. import payloads
+
+__all__ = (
+ 'MonetizationHTTPConnection',
+)
+
+
+class MonetizationHTTPConnection:
+
+ def __init__(self, parent: HTTPConnection):
+ self.parent = parent
+
+ async def list_skus(
+ self,
+ application_id: int) -> list[SKU]:
+ """List all SKUs associated with the given application ID."""
+
+ route = Route(
+ "GET",
+ "/applications/{application_id}/skus",
+ application_id=application_id,
+ )
+ data: list[payloads.SKU] = await self.parent.request(
+ route,
+ )
+ return [
+ SKU(state=self.parent, data=d)
+ for d in data
+ ]
+
+ async def list_entitlements(
+ self,
+ application_id: int) -> list[Entitlement]:
+ """List all entitlements associated with the given application ID."""
+
+ route = Route(
+ "GET",
+ "/applications/{application_id}/commands",
+ application_id=application_id,
+ )
+ data: list[payloads.ApplicationCommand] = await self.parent.request(
+ route,
+ )
+ return [
+ Entitlement(state=self.parent, data=d)
+ for d in data
+ ]
+
+ async def consume_entitlement(
+ self,
+ application_id: int,
+ entitlement_id: int) -> list[Entitlement]:
+ """List all entitlements associated with the given application ID."""
+
+ route = Route(
+ "GET",
+ "/applications/{application_id}/commands",
+ application_id=application_id,
+ )
+ data: list[payloads.ApplicationCommand] = await self.parent.request(
+ route,
+ )
+ return [
+ Entitlement(state=self.parent, data=d)
+ for d in data
+ ]
diff --git a/novus/enums/__init__.py b/novus/enums/__init__.py
index 7a67b782..62509520 100644
--- a/novus/enums/__init__.py
+++ b/novus/enums/__init__.py
@@ -24,6 +24,7 @@
from .guild import *
from .interaction import *
from .message import *
+from .monetization import *
from .presence import *
from .scheduled_event import *
from .sticker import *
@@ -43,6 +44,7 @@
'ChannelType',
'ComponentType',
'ContentFilterLevel',
+ 'EntitlementType',
'EventEntityType',
'EventPrivacyLevel',
'EventStatus',
@@ -57,6 +59,7 @@
'NotificationLevel',
'PermissionOverwriteType',
'PremiumTier',
+ 'SKUType',
'Status',
'StickerFormat',
'StickerType',
diff --git a/novus/enums/monetization.py b/novus/enums/monetization.py
new file mode 100644
index 00000000..3257f7ca
--- /dev/null
+++ b/novus/enums/monetization.py
@@ -0,0 +1,43 @@
+"""
+Copyright (c) Kae Bartlett
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+"""
+
+from __future__ import annotations
+
+from .utils import Enum
+
+__all__ = (
+ 'SKUType',
+ 'EntitlementType',
+)
+
+
+class SKUType(Enum):
+ DURABLE = 2
+ CONSUMABLE = 3
+ SUBSCRIPTION = 5
+ SUBSCRIPTION_GROUP = 6
+
+
+class EntitlementType(Enum):
+ PURCHASE = 1
+ PREMIUM_SUBSCRIPTION = 2
+ DEVELOPER_GIFT = 3
+ TEST_MODE_PURCHASE = 4
+ FREE_PURCHASE = 5
+ USER_GIFT = 6
+ PREMIUM_PURCHASE = 7
+ APPLICATION_SUBSCRIPTION = 8
diff --git a/novus/ext/client/client.py b/novus/ext/client/client.py
index fa60a8a0..59fca8e3 100644
--- a/novus/ext/client/client.py
+++ b/novus/ext/client/client.py
@@ -625,6 +625,29 @@ async def _handle_command_sync(
log.info("Editing app command %s %s in guild %s", id, comm, guild_id)
await edit_(id, **comm.application_command._to_data())
+ async def fetch_application_id(self) -> int:
+ """
+ Fetch and cache the application ID for the given token.
+
+ Returns
+ -------
+ int
+ The ID of the application
+ """
+
+ aid: int | None = self.state.application_id
+ if aid is None:
+ app = self.state.cache.application
+ if app is None:
+ app = await self.state.oauth2.get_current_bot_information()
+ self.state.cache.application = app
+ aid = app.id
+ return aid
+
+ @property
+ def application_id(self) -> int:
+ return self.state.application_id
+
async def sync_commands(
self,
*,
@@ -642,14 +665,7 @@ async def sync_commands(
log.info(f"Syncing {len(command_length)} commands")
# Get application ID
- aid: int | None = self.state.cache.application_id
- if aid is None:
- app = self.state.cache.application
- if app is None:
- app = await self.state.oauth2.get_current_bot_information()
- self.state.cache.application = app
- aid = app.id
- assert aid
+ aid = await self.fetch_application_id()
# Group our commands by guild ID
commands_by_guild: dict[int | None, dict[str, Command]]
diff --git a/novus/flags/__init__.py b/novus/flags/__init__.py
index 35cfb1e6..5f1b4c43 100644
--- a/novus/flags/__init__.py
+++ b/novus/flags/__init__.py
@@ -20,6 +20,7 @@
from .gateway import *
from .guild import *
from .message import *
+from .monetization import *
from .permissions import *
from .user import *
@@ -30,5 +31,6 @@
'MessageFlags',
'Permissions',
'SystemChannelFlags',
+ 'SKUFlags',
'UserFlags',
)
diff --git a/novus/flags/monetization.py b/novus/flags/monetization.py
new file mode 100644
index 00000000..0a6230f0
--- /dev/null
+++ b/novus/flags/monetization.py
@@ -0,0 +1,41 @@
+"""
+Copyright (c) Kae Bartlett
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from vfflags import Flags
+
+__all__ = (
+ 'SKUFlags',
+)
+
+
+class SKUFlags(Flags):
+ """Flags associated with SKUs."""
+
+ if TYPE_CHECKING:
+ available: bool
+ guild_subscription: bool
+ user_subscription: bool
+
+ CREATE_FLAGS = {
+ "available": 1 << 2,
+ "guild_subscription": 1 << 7,
+ "user_subscription": 1 << 8,
+ }
diff --git a/novus/models/__init__.py b/novus/models/__init__.py
index 6ce30f88..a8d914b4 100644
--- a/novus/models/__init__.py
+++ b/novus/models/__init__.py
@@ -26,6 +26,7 @@
from .file import *
from .guild import *
from .guild_member import *
+from .monetization import *
from .interaction import *
from .invite import *
from .message import *
@@ -68,6 +69,7 @@
'ContextComandData',
'Embed',
'Emoji',
+ 'Entitlement',
'File',
'ForumTag',
'Guild',
@@ -98,6 +100,7 @@
'Reaction',
'Role',
'RoleSelectMenu',
+ 'SKU',
'ScheduledEvent',
'SelectOption',
'StageInstance',
diff --git a/novus/models/interaction.py b/novus/models/interaction.py
index 17c577ea..789783ba 100644
--- a/novus/models/interaction.py
+++ b/novus/models/interaction.py
@@ -36,6 +36,7 @@
from .guild import BaseGuild, Guild
from .guild_member import GuildMember
from .message import Attachment, Message
+from .monetization import Entitlement
from .role import Role
from .ui.action_row import ActionRow
from .ui.select_menu import SelectOption
@@ -458,6 +459,9 @@ class Interaction(Generic[IData]):
The user's locale.
guild_locale: str | None
The locale of the guild where the interaction was run.
+ entitlements: list[Entitlement]
+ A list of the entitlements that the user associated with the
+ interaction has.
"""
__slots__ = (
@@ -475,6 +479,7 @@ class Interaction(Generic[IData]):
'app_permissions',
'locale',
'guild_locale',
+ 'entitlements',
'_responded',
'_stream',
'_stream_request',
@@ -492,14 +497,20 @@ class Interaction(Generic[IData]):
app_permissions: Permissions
locale: str
guild_locale: str | None
+ entitlements: list[Entitlement]
+
_stream: web.StreamResponse | None
_stream_request: web.Request | None
def __init__(self, *, state: HTTPConnection, data: payloads.Interaction):
self.state = state
self.id = try_snowflake(data["id"])
+
+ # For our responses
self._stream = None
self._stream_request = None
+
+ # Parse relevant meta
self.application_id = try_snowflake(data["application_id"])
self.type = data["type"]
self.guild = self.state.cache.get_guild(data.get("guild_id"))
@@ -508,8 +519,7 @@ def __init__(self, *, state: HTTPConnection, data: payloads.Interaction):
self.guild = Guild(state=state, data=data["guild"])
else:
self.guild = BaseGuild(state=state, data={"id": data["guild_id"]}) # pyright: ignore
- channel = self.state.cache.get_channel(data.get("channel_id"))
- if channel is None:
+ if (self.channel := self.state.cache.get_channel(data.get("channel_id"))) is None:
self.channel = Channel.partial(self.state, data["channel_id"])
else:
self.channel = channel
@@ -525,7 +535,7 @@ def __init__(self, *, state: HTTPConnection, data: payloads.Interaction):
guild_id=self.guild.id, # pyright: ignore
)
else:
- self.user = None # pyright: ignore # Ping interactions :(
+ self.user = None # pyright: ignore # Only ever happens for pings
self.token = data["token"]
self.version = data["version"]
if "message" in data:
@@ -538,6 +548,12 @@ def __init__(self, *, state: HTTPConnection, data: payloads.Interaction):
self.app_permissions = Permissions.all()
self.locale = data["locale"]
self.guild_locale = data.get("guild_locale")
+ self.entitlements = [
+ Entitlement(data=i, state=self.state)
+ for i in data.get("entitlements", [])
+ ]
+
+ # Parse data
data_object = None
if "data" in data:
data_dict = data["data"]
diff --git a/novus/models/monetization.py b/novus/models/monetization.py
new file mode 100644
index 00000000..52b05657
--- /dev/null
+++ b/novus/models/monetization.py
@@ -0,0 +1,243 @@
+"""
+Copyright (c) Kae Bartlett
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from ..utils import DiscordDatetime, parse_timestamp, try_snowflake, try_id
+from ..flags import SKUFlags
+from ..enums import SKUType
+
+if TYPE_CHECKING:
+ from ..api import HTTPConnection
+ from .. import payloads
+
+__all__ = (
+ 'SKU',
+ 'Entitlement',
+)
+
+
+class SKU:
+ """
+ A Discord stock-keeping unit - an item that can be purchased.
+
+ Attributes
+ ----------
+ id : int
+ The ID of the SKU.
+ type : int
+ The type of the SKU.
+
+ .. seealso: `novus.SKUType`
+ application_id : int
+ The ID of the application that the SKU is associated with.
+ name : str
+ The name of the SKU.
+ slug : str
+ A system-generated URL slug based on the SKU's name.
+ flags : `novus.SKUFlags`
+ Flags associated with the SKU.
+ """
+
+ def __init__(self, *, data: payloads.SKU, state: HTTPConnection):
+ self.id: int = try_snowflake(data["id"])
+ self.type: SKUType = SKUType(data["type"])
+ self.application_id: int = try_snowflake(data["application_id"])
+ self.name: str = data["name"]
+ self.slug: str = data["slug"]
+ self.flags: SKUFlags = SKUFlags(data["flags"])
+
+ # API methods
+
+ async def list_skus(cls, state: HTTPConnection) -> list[SKU]:
+ """
+ List all of the SKUs that the application has created.
+
+ Parameters
+ ----------
+ state : novus.api.HTTPConnection
+ The API connection.
+ """
+
+ return await state.monetization.list_skus(state.application_id)
+
+
+class Entitlement:
+ """
+ A purchase that a user has made associated with your application.
+
+ Attributes
+ ----------
+ id : int
+ The ID of the entitlement.
+ sku_id : int
+ The ID of the purchased SKU.
+ application_id : int
+ The ID of the application that the SKU belongs to.
+ user_id : int | None
+ The user that was granted access to the entitlement's SKU.
+ type : int
+ The type of entitlement.
+
+ .. seealso:: `novus.EntitlementType`
+ deleted : bool
+ Whether or not the entitlement was deleted.
+ starts_at : `novus.utils.DiscordDatetime` | None
+ The date from which the entitlement is valid. Not present on test
+ entitlements.
+ ends_at : `novus.utils.DiscordDatetime` | None
+ The date at which the entitlement is no longer valid. Not present on
+ test entitlements.
+ guild_id : int | None
+ The ID of the guild that was granted access to the entitlement's SKU.
+ consumed : bool | None
+ For consumable items, whether or not the entitlement has been consumed.
+ """
+
+ __slots__ = (
+ 'id',
+ 'sku_id',
+ 'application_id',
+ 'user_id',
+ 'type',
+ 'deleted',
+ 'starts_at',
+ 'ends_at',
+ 'guild_id',
+ 'consumed',
+ 'state',
+ )
+
+ def __init__(self, *, data: payloads.Entitlement, state: HTTPConnection):
+ self.id: int = try_snowflake(data["id"])
+ self.sku_id: int = try_snowflake(data["sku_id"])
+ self.application_id: int = try_snowflake(data["application_id"])
+ self.user_id: int | None = try_snowflake(data.get("user_id"))
+ self.type: int = data["type"]
+ self.deleted: bool = data["deleted"]
+ self.starts_at: DiscordDatetime | None = parse_timestamp(data.get("starts_at"))
+ self.ends_at: DiscordDatetime | None = parse_timestamp(data.get("ends_at"))
+ self.guild_id: int | None = data.get("guild_id")
+ self.consumed: bool | None = data.get("consumed")
+ self.state = state
+
+ # API methods
+
+ # @classmethod
+ # async def list_entitlements(
+ # cls,
+ # state: HTTPConnection,
+ # *,
+ # limit: int = 100,
+ # around: int | abc.Snowflake | Message = MISSING,
+ # before: int | abc.Snowflake | Message = MISSING,
+ # after: int | abc.Snowflake | Message = MISSING) -> list[Entitlement]:
+ # """
+ # Get a number of messages from the channel.
+
+ # Parameters
+ # ----------
+ # limit : int
+ # The number of messages that you want to get. Maximum 100.
+ # around : int | novus.abc.Snowflake
+ # Get messages around this ID.
+ # Only one of ``around``, ``before``, and ``after`` can be set.
+ # before : int | novus.abc.Snowflake
+ # Get messages before this ID.
+ # Only one of ``around``, ``before``, and ``after`` can be set.
+ # after : int | novus.abc.Snowflake
+ # Get messages after this ID.
+ # Only one of ``around``, ``before``, and ``after`` can be set.
+
+ # Returns
+ # -------
+ # list[novus.Message]
+ # The messages that were retrieved.
+ # """
+
+ # params: dict[str, int] = {}
+ # add_not_missing(params, "limit", limit)
+ # add_not_missing(params, "around", around, try_id)
+ # add_not_missing(params, "before", before, try_id)
+ # add_not_missing(params, "after", after, try_id)
+ # return await self.state.channel.get_channel_messages(
+ # self.id,
+ # **params,
+ # )
+
+ # @classmethod
+ # def entitlement(
+ # cls,
+ # state: HTTPConnection,
+ # *,
+ # limit: int | None = 100,
+ # before: int | abc.Snowflake | Message = MISSING,
+ # after: int | abc.Snowflake | Message = MISSING) -> APIIterator[Message]:
+ # """
+ # Get an iterator of messages from a channel.
+
+ # Examples
+ # --------
+
+ # .. code-block::
+
+ # async for message in channel.messages(limit=1_000):
+ # print(message.content)
+
+ # .. code-block::
+
+ # messages = await channel.messages(limit=200).flatten()
+
+ # Parameters
+ # ----------
+ # limit : int
+ # The number of messages that you want to get.
+ # before : int | novus.abc.Snowflake
+ # Get messages before this ID.
+ # Only one of ``around``, ``before``, and ``after`` can be set.
+ # after : int | novus.abc.Snowflake
+ # Get messages after this ID.
+ # Only one of ``around``, ``before``, and ``after`` can be set.
+
+ # Returns
+ # -------
+ # APIIterator[novus.Message]
+ # The messages that were retrieved, as a generator.
+ # """
+
+ # from ..api import APIIterator # circular import
+ # return APIIterator(
+ # method=self.fetch_messages,
+ # before=before,
+ # after=after,
+ # limit=limit,
+ # method_limit=100,
+ # )
+
+ async def consume(self) -> None:
+ """
+ Consume the entitlement.
+ """
+
+ await self.state.monetization.consume_entitlement(
+ self.application_id,
+ self.id,
+ )
+ self.consumed = True
+ return None
diff --git a/novus/payloads/__init__.py b/novus/payloads/__init__.py
index b048f1aa..33db82ca 100644
--- a/novus/payloads/__init__.py
+++ b/novus/payloads/__init__.py
@@ -31,6 +31,7 @@
from .interaction import *
from .invite import *
from .message import *
+from .monetization import *
from .oauth2 import *
from .stage_instance import *
from .sticker import *
@@ -72,6 +73,7 @@
'Embed',
'EmbedType',
'Emoji',
+ 'Entitlement',
'ForumDefaultReaction',
'ForumTag',
'GatewayGuild',
@@ -115,6 +117,7 @@
'Reaction',
'Role',
'RoleTags',
+ 'SKU',
'SelectMenu',
'SelectOption',
'Snowflake',
diff --git a/novus/payloads/interaction.py b/novus/payloads/interaction.py
index cada7f1b..d55139e8 100644
--- a/novus/payloads/interaction.py
+++ b/novus/payloads/interaction.py
@@ -17,7 +17,7 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Literal, TypedDict
+from typing import TYPE_CHECKING, Any, Literal, TypedDict, List
from typing_extensions import NotRequired
@@ -27,6 +27,7 @@
ApplicationCommandOptionType,
Attachment,
Channel,
+ Entitlement,
Guild,
GuildMember,
Message,
@@ -108,3 +109,6 @@ class Interaction(TypedDict):
app_permissions: NotRequired[str]
locale: str
guild_locale: NotRequired[str]
+ entitlements: List[Entitlement]
+ authorizing_integration_owners: dict[str, Any]
+ context: NotRequired[int]
diff --git a/novus/payloads/monetization.py b/novus/payloads/monetization.py
new file mode 100644
index 00000000..a046319c
--- /dev/null
+++ b/novus/payloads/monetization.py
@@ -0,0 +1,52 @@
+"""
+Copyright (c) Kae Bartlett
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, TypedDict
+
+from typing_extensions import NotRequired
+
+if TYPE_CHECKING:
+ from ._util import Snowflake, Timestamp
+
+__all__ = (
+ 'SKU',
+ 'Entitlement',
+)
+
+
+class SKU(TypedDict):
+ id: Snowflake
+ type: int
+ application_id: Snowflake
+ name: str
+ slug: str
+ flags: int
+
+
+class Entitlement(TypedDict):
+ id: Snowflake
+ sku_id: Snowflake
+ application_id: Snowflake
+ user_id: NotRequired[Snowflake]
+ type: int
+ deleted: bool
+ starts_at: NotRequired[Timestamp]
+ ends_at: NotRequired[Timestamp]
+ guild_id: NotRequired[Snowflake]
+ consumed: NotRequired[bool]