Skip to content
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

feat: implement monetization #2273

Merged
merged 23 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
513a21e
feat: implement monetization
plun1331 Dec 1, 2023
3b78bb5
docs: add documentation for monetization
plun1331 Dec 1, 2023
5d2a61d
chore: update changelog
plun1331 Dec 1, 2023
a1aa579
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 1, 2023
3308478
fix: add SKUFlags to __all__
plun1331 Dec 2, 2023
b00cd91
Merge branch 'monetization' of https://github.com/plun1331/pycord int…
plun1331 Dec 2, 2023
0b1f174
docs: document classes properly
plun1331 Dec 2, 2023
e6b28f7
Merge branch 'master' into monetization
plun1331 Dec 2, 2023
b50449e
Merge branch 'monetization' of https://github.com/plun1331/pycord int…
plun1331 Dec 4, 2023
838b05b
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 4, 2023
7f7146c
feat: add entitlements to interaction structure
plun1331 Dec 4, 2023
47a8dc3
Merge branch 'monetization' of https://github.com/plun1331/pycord int…
plun1331 Dec 4, 2023
e1d2b3e
feat: update interaction and response types
plun1331 Dec 4, 2023
d353229
Merge branch 'master' into monetization
plun1331 Feb 1, 2024
f4bb7b3
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 1, 2024
acc8e91
h
plun1331 Feb 1, 2024
75b97cf
Merge branch 'monetization' of https://github.com/plun1331/pycord int…
plun1331 Feb 1, 2024
f8b34c5
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 1, 2024
b12bdaa
add class doc
plun1331 Feb 1, 2024
2ecf2b1
Merge branch 'monetization' of https://github.com/plun1331/pycord int…
plun1331 Feb 1, 2024
f4c2e3e
e
plun1331 Feb 1, 2024
135e927
target 2.5
plun1331 Feb 1, 2024
d3c55d6
e
plun1331 Feb 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ These changes are available on the `master` branch, but have not yet been releas
([#2131](https://github.com/Pycord-Development/pycord/pull/2131))
- Added support for guild onboarding related features.
([#2127](https://github.com/Pycord-Development/pycord/pull/2127))
- Added support for monetization-related objects and events.
([#2273](https://github.com/Pycord-Development/pycord/pull/2273))

### Changed

Expand Down
1 change: 1 addition & 0 deletions discord/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
from .member import *
from .mentions import *
from .message import *
from .monetization import *
from .object import *
from .onboarding import *
from .partial_emoji import *
Expand Down
31 changes: 31 additions & 0 deletions discord/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
from .invite import Invite
from .iterators import GuildIterator
from .mentions import AllowedMentions
from .monetization import SKU, Entitlement
from .object import Object
from .stage_instance import StageInstance
from .state import ConnectionState
Expand Down Expand Up @@ -2002,3 +2003,33 @@ async def update_role_connection_metadata_records(
self.application_id, payload
)
return [ApplicationRoleConnectionMetadata.from_dict(r) for r in data]

async def fetch_skus(self) -> list[SKU]:
"""|coro|

Fetches the bot's SKUs.

.. versionadded:: 2.5

Returns
-------
List[:class:`.SKU`]
The bot's SKUs.
"""
data = await self._connection.http.list_skus(self.application_id)
return [SKU(data=s) for s in data]

async def fetch_entitlements(self) -> list[Entitlement]:
"""|coro|

Fetches the bot's entitlements.

.. versionadded:: 2.5

Returns
-------
List[:class:`.Entitlement`]
The bot's entitlements.
"""
data = await self._connection.http.list_entitlements(self.application_id)
return [Entitlement(data=e, state=self._connection) for e in data]
24 changes: 24 additions & 0 deletions discord/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@
"PromptType",
"OnboardingMode",
"ReactionType",
"SKUType",
"EntitlementType",
"EntitlementOwnerType",
)


Expand Down Expand Up @@ -674,6 +677,7 @@ class InteractionResponseType(Enum):
message_update = 7 # for components
auto_complete_result = 8 # for autocomplete interactions
modal = 9 # for modal dialogs
premium_required = 10


class VideoQualityMode(Enum):
Expand Down Expand Up @@ -985,6 +989,26 @@ class ReactionType(Enum):
burst = 1


class SKUType(Enum):
"""The SKU type"""

subscription = 5
subscription_group = 6


class EntitlementType(Enum):
"""The entitlement type"""

application_subscription = 8


plun1331 marked this conversation as resolved.
Show resolved Hide resolved
class EntitlementOwnerType(Enum):
"""The entitlement owner type"""

guild = 1
user = 2


T = TypeVar("T")


Expand Down
78 changes: 70 additions & 8 deletions discord/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"MemberCacheFlags",
"ApplicationFlags",
"ChannelFlags",
"SKUFlags",
)

FV = TypeVar("FV", bound="flag_value")
Expand Down Expand Up @@ -1492,8 +1493,6 @@ def require_tag(self):
class AttachmentFlags(BaseFlags):
r"""Wraps up the Discord Attachment flags.

See :class:`SystemChannelFlags`.

.. container:: operations

.. describe:: x == y
Expand All @@ -1519,20 +1518,20 @@ class AttachmentFlags(BaseFlags):
Returns the inverse of a flag.
.. describe:: hash(x)

Return the flag's hash.
Return the flag's hash.
.. describe:: iter(x)

Returns an iterator of ``(name, value)`` pairs. This allows it
to be, for example, constructed as a dict or a list of pairs.
Returns an iterator of ``(name, value)`` pairs. This allows it
to be, for example, constructed as a dict or a list of pairs.
Note that aliases are not shown.

.. versionadded:: 2.5

Attributes
-----------
value: :class:`int`
The raw value. This value is a bit array field of a 53-bit integer
representing the currently available flags. You should query
flags via the properties rather than using this raw value.
The raw value. You should query flags via the properties
rather than using this raw value.
"""

__slots__ = ()
Expand All @@ -1551,3 +1550,66 @@ def is_thumbnail(self):
def is_remix(self):
""":class:`bool`: Returns ``True`` if the attachment has been remixed."""
return 1 << 2


@fill_with_flags()
class SKUFlags(BaseFlags):
r"""Wraps up the Discord SKU flags.

.. container:: operations

.. describe:: x == y

Checks if two SKUFlags are equal.
.. describe:: x != y

Checks if two SKUFlags are not equal.
.. describe:: x + y

Adds two flags together. Equivalent to ``x | y``.
.. describe:: x - y

Subtracts two flags from each other.
.. describe:: x | y

Returns the union of two flags. Equivalent to ``x + y``.
.. describe:: x & y

Returns the intersection of two flags.
.. describe:: ~x

Returns the inverse of a flag.
.. describe:: hash(x)

Return the flag's hash.
.. describe:: iter(x)

Returns an iterator of ``(name, value)`` pairs. This allows it
to be, for example, constructed as a dict or a list of pairs.
Note that aliases are not shown.

.. versionadded:: 2.5

Attributes
-----------
value: :class:`int`
The raw value. You should query flags via the properties
rather than using this raw value.
"""

__slots__ = ()

@flag_value
def available(self):
""":class:`bool`: Returns ``True`` if the SKU is available for purchase."""
return 1 << 2

@flag_value
def guild_subscription(self):
""":class:`bool`: Returns ``True`` if the SKU is a guild subscription."""
return 1 << 7

@flag_value
def user_subscription(self):
""":class:`bool`: Returns ``True`` if the SKU is a user subscription."""
return 1 << 8
24 changes: 24 additions & 0 deletions discord/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
from .iterators import AuditLogIterator, BanIterator, MemberIterator
from .member import Member, VoiceState
from .mixins import Hashable
from .monetization import Entitlement, EntitlementOwnerType
from .onboarding import Onboarding
from .permissions import PermissionOverwrite
from .role import Role
Expand Down Expand Up @@ -3950,3 +3951,26 @@ async def delete_auto_moderation_rule(
"""

await self._state.http.delete_auto_moderation_rule(self.id, id, reason=reason)

async def create_test_entitlement(self, sku: Snowflake) -> Entitlement:
"""|coro|

Creates a test entitlement for the guild.

Parameters
----------
sku: :class:`Snowflake`
The SKU to create a test entitlement for.

Returns
-------
:class:`Entitlement`
The created entitlement.
"""
payload = {
"sku_id": sku.id,
"owner_id": self.id,
"owner_type": EntitlementOwnerType.guild.value,
}
data = await self._state.http.create_test_entitlement(self.id, payload)
return Entitlement(data=data, state=self._state)
50 changes: 50 additions & 0 deletions discord/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
invite,
member,
message,
monetization,
onboarding,
role,
scheduled_events,
Expand Down Expand Up @@ -2885,6 +2886,55 @@ def update_application_role_connection_metadata_records(
)
return self.request(r, json=payload)

# Monetization

def list_skus(
self,
application_id: Snowflake,
) -> Response[list[monetization.SKU]]:
r = Route(
"GET",
"/applications/{application_id}/skus",
application_id=application_id,
)
return self.request(r)

def list_entitlements(
self,
application_id: Snowflake,
) -> Response[list[monetization.Entitlement]]:
r = Route(
"GET",
"/applications/{application_id}/entitlements",
application_id=application_id,
)
return self.request(r)

def create_test_entitlement(
self,
application_id: Snowflake,
payload: monetization.CreateTestEntitlementPayload,
) -> Response[monetization.Entitlement]:
r = Route(
"POST",
"/applications/{application_id}/entitlements",
application_id=application_id,
)
return self.request(r, json=payload)

def delete_test_entitlement(
self,
application_id: Snowflake,
entitlement_id: Snowflake,
) -> Response[None]:
r = Route(
"DELETE",
"/applications/{application_id}/entitlements/{entitlement_id}",
application_id=application_id,
entitlement_id=entitlement_id,
)
return self.request(r)

# Onboarding

def get_onboarding(self, guild_id: Snowflake) -> Response[onboarding.Onboarding]:
Expand Down
35 changes: 35 additions & 0 deletions discord/interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from .flags import MessageFlags
from .member import Member
from .message import Attachment, Message
from .monetization import Entitlement
from .object import Object
from .permissions import Permissions
from .user import User
Expand Down Expand Up @@ -183,6 +184,9 @@ def _from_data(self, data: InteractionPayload):
self.data.get("custom_id") if self.data is not None else None
)
self._app_permissions: int = int(data.get("app_permissions", 0))
self.entitlements: list[Entitlement] = [
Entitlement(data=e, state=self._state) for e in data.get("entitlements", [])
]

self.message: Message | None = None
self.channel = None
Expand Down Expand Up @@ -1185,6 +1189,37 @@ async def send_modal(self, modal: Modal) -> Interaction:
self._parent._state.store_modal(modal, self._parent.user.id)
return self._parent

async def premium_required(self) -> Interaction:
"""|coro|
Responds to this interaction by sending a premium required message.

Raises
------
HTTPException
Sending the message failed.
InteractionResponded
This interaction has already been responded to before.
"""
if self._responded:
raise InteractionResponded(self._parent)

parent = self._parent

adapter = async_context.get()
http = parent._state.http
await self._locked_response(
adapter.create_interaction_response(
parent.id,
parent.token,
session=parent._session,
proxy=http.proxy,
proxy_auth=http.proxy_auth,
type=InteractionResponseType.premium_required.value,
)
)
self._responded = True
return self._parent

async def _locked_response(self, coro: Coroutine[Any]):
"""|coro|

Expand Down