Browse Source

Add support for role subscription reading

pull/10109/head
Rapptz 2 years ago
committed by dolfies
parent
commit
2dd8f92309
  1. 1
      discord/enums.py
  2. 18
      discord/flags.py
  3. 56
      discord/message.py
  4. 16
      discord/role.py
  5. 2
      discord/types/integration.py
  6. 10
      discord/types/message.py
  7. 2
      discord/types/role.py
  8. 16
      docs/api.rst

1
discord/enums.py

@ -284,6 +284,7 @@ class MessageType(Enum):
guild_invite_reminder = 22 guild_invite_reminder = 22
context_menu_command = 23 context_menu_command = 23
auto_moderation_action = 24 auto_moderation_action = 24
role_subscription_purchase = 25
class SpeakingState(Enum): class SpeakingState(Enum):

18
discord/flags.py

@ -438,6 +438,24 @@ class SystemChannelFlags(BaseFlags):
""" """
return 8 return 8
@flag_value
def role_subscription_purchase_notifications(self):
""":class:`bool`: Returns ``True`` if role subscription purchase and renewal
notifications are enabled.
.. versionadded:: 2.0
"""
return 16
@flag_value
def role_subscription_purchase_notification_replies(self):
""":class:`bool`: Returns ``True`` if the role subscription notifications
have a sticker reply button.
.. versionadded:: 2.0
"""
return 32
@fill_with_flags() @fill_with_flags()
class MessageFlags(BaseFlags): class MessageFlags(BaseFlags):

56
discord/message.py

@ -79,6 +79,7 @@ if TYPE_CHECKING:
MessageReference as MessageReferencePayload, MessageReference as MessageReferencePayload,
BaseApplication as MessageApplicationPayload, BaseApplication as MessageApplicationPayload,
MessageActivity as MessageActivityPayload, MessageActivity as MessageActivityPayload,
RoleSubscriptionData as RoleSubscriptionDataPayload,
) )
from .types.interactions import MessageInteraction as MessageInteractionPayload from .types.interactions import MessageInteraction as MessageInteractionPayload
@ -111,6 +112,7 @@ __all__ = (
'PartialMessage', 'PartialMessage',
'MessageReference', 'MessageReference',
'DeletedReferencedMessage', 'DeletedReferencedMessage',
'RoleSubscriptionInfo',
) )
@ -554,6 +556,39 @@ def flatten_handlers(cls: Type[Message]) -> Type[Message]:
return cls return cls
class RoleSubscriptionInfo:
"""Represents a message's role subscription information.
This is currently only attached to messages of type :attr:`MessageType.role_subscription_purchase`.
.. versionadded:: 2.0
Attributes
-----------
role_subscription_listing_id: :class:`int`
The ID of the SKU and listing that the user is subscribed to.
tier_name: :class:`str`
The name of the tier that the user is subscribed to.
total_months_subscribed: :class:`int`
The cumulative number of months that the user has been subscribed for.
is_renewal: :class:`bool`
Whether this notification is for a renewal rather than a new purchase.
"""
__slots__ = (
'role_subscription_listing_id',
'tier_name',
'total_months_subscribed',
'is_renewal',
)
def __init__(self, data: RoleSubscriptionDataPayload) -> None:
self.role_subscription_listing_id: int = int(data['role_subscription_listing_id'])
self.tier_name: str = data['tier_name']
self.total_months_subscribed: int = data['total_months_subscribed']
self.is_renewal: bool = data['is_renewal']
class PartialMessage(Hashable): class PartialMessage(Hashable):
"""Represents a partial message to aid with working messages when only """Represents a partial message to aid with working messages when only
a message and channel ID are present. a message and channel ID are present.
@ -1257,6 +1292,11 @@ class Message(PartialMessage, Hashable):
components: List[Union[:class:`ActionRow`, :class:`Button`, :class:`SelectMenu`]] components: List[Union[:class:`ActionRow`, :class:`Button`, :class:`SelectMenu`]]
A list of components in the message. A list of components in the message.
.. versionadded:: 2.0
role_subscription: Optional[:class:`RoleSubscriptionInfo`]
The data of the role subscription purchase or renewal that prompted this
:attr:`MessageType.role_subscription_purchase` message.
.. versionadded:: 2.0 .. versionadded:: 2.0
guild: Optional[:class:`Guild`] guild: Optional[:class:`Guild`]
The guild that the message belongs to, if applicable. The guild that the message belongs to, if applicable.
@ -1300,6 +1340,7 @@ class Message(PartialMessage, Hashable):
'components', 'components',
'call', 'call',
'interaction', 'interaction',
'role_subscription',
) )
if TYPE_CHECKING: if TYPE_CHECKING:
@ -1385,6 +1426,14 @@ class Message(PartialMessage, Hashable):
# The channel will be the correct type here # The channel will be the correct type here
ref.resolved = self.__class__(channel=chan, data=resolved, state=state) # type: ignore ref.resolved = self.__class__(channel=chan, data=resolved, state=state) # type: ignore
self.role_subscription: Optional[RoleSubscriptionInfo] = None
try:
role_subscription = data['role_subscription_data']
except KeyError:
pass
else:
self.role_subscription = RoleSubscriptionInfo(role_subscription)
for handler in ('author', 'member', 'mentions', 'mention_roles', 'call', 'interaction', 'components'): for handler in ('author', 'member', 'mentions', 'mention_roles', 'call', 'interaction', 'components'):
try: try:
getattr(self, f'_handle_{handler}')(data[handler]) getattr(self, f'_handle_{handler}')(data[handler])
@ -1827,6 +1876,13 @@ class Message(PartialMessage, Hashable):
if self.type is MessageType.guild_invite_reminder: if self.type is MessageType.guild_invite_reminder:
return 'Wondering who to invite?\nStart by inviting anyone who can help you build the server!' return 'Wondering who to invite?\nStart by inviting anyone who can help you build the server!'
if self.type is MessageType.role_subscription_purchase and self.role_subscription is not None:
# TODO: figure out how the message looks like for is_renewal: true
total_months = self.role_subscription.total_months_subscribed
months = '1 month' if total_months == 1 else f'{total_months} months'
return f'{self.author.name} joined {self.role_subscription.tier_name} and has been a subscriber of {self.guild} for {months}!'
# Fallback for unknown message types
return self.content return self.content
@overload @overload

16
discord/role.py

@ -66,22 +66,31 @@ class RoleTags:
The bot's user ID that manages this role. The bot's user ID that manages this role.
integration_id: Optional[:class:`int`] integration_id: Optional[:class:`int`]
The integration ID that manages the role. The integration ID that manages the role.
subscription_listing_id: Optional[:class:`int`]
The ID of this role's subscription SKU and listing.
.. versionadded:: 2.0
""" """
__slots__ = ( __slots__ = (
'bot_id', 'bot_id',
'integration_id', 'integration_id',
'_premium_subscriber', '_premium_subscriber',
'_available_for_purchase',
'subscription_listing_id',
) )
def __init__(self, data: RoleTagPayload): def __init__(self, data: RoleTagPayload):
self.bot_id: Optional[int] = _get_as_snowflake(data, 'bot_id') self.bot_id: Optional[int] = _get_as_snowflake(data, 'bot_id')
self.integration_id: Optional[int] = _get_as_snowflake(data, 'integration_id') self.integration_id: Optional[int] = _get_as_snowflake(data, 'integration_id')
self.subscription_listing_id: Optional[int] = _get_as_snowflake(data, 'subscription_listing_id')
# NOTE: The API returns "null" for this if it's valid, which corresponds to None. # NOTE: The API returns "null" for this if it's valid, which corresponds to None.
# This is different from other fields where "null" means "not there". # This is different from other fields where "null" means "not there".
# So in this case, a value of None is the same as True. # So in this case, a value of None is the same as True.
# Which means we would need a different sentinel. # Which means we would need a different sentinel.
self._premium_subscriber: Optional[Any] = data.get('premium_subscriber', MISSING) self._premium_subscriber: Optional[Any] = data.get('premium_subscriber', MISSING)
self._available_for_purchase: Optional[Any] = data.get('available_for_purchase', MISSING)
def is_bot_managed(self) -> bool: def is_bot_managed(self) -> bool:
""":class:`bool`: Whether the role is associated with a bot.""" """:class:`bool`: Whether the role is associated with a bot."""
@ -95,6 +104,13 @@ class RoleTags:
""":class:`bool`: Whether the role is managed by an integration.""" """:class:`bool`: Whether the role is managed by an integration."""
return self.integration_id is not None return self.integration_id is not None
def is_available_for_purchase(self) -> bool:
""":class:`bool`: Whether the role is available for purchase.
.. versionadded:: 2.0
"""
return self._available_for_purchase is None
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
f'<RoleTags bot_id={self.bot_id} integration_id={self.integration_id} ' f'<RoleTags bot_id={self.bot_id} integration_id={self.integration_id} '

2
discord/types/integration.py

@ -49,7 +49,7 @@ class PartialIntegration(TypedDict):
application_id: NotRequired[Snowflake] application_id: NotRequired[Snowflake]
IntegrationType = Literal['twitch', 'youtube', 'discord'] IntegrationType = Literal['twitch', 'youtube', 'discord', 'guild_subscription']
class BaseIntegration(PartialIntegration): class BaseIntegration(PartialIntegration):

10
discord/types/message.py

@ -86,7 +86,14 @@ class MessageReference(TypedDict, total=False):
fail_if_not_exists: bool fail_if_not_exists: bool
MessageType = Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 18, 19, 20, 21] class RoleSubscriptionData(TypedDict):
role_subscription_listing_id: Snowflake
tier_name: str
total_months_subscribed: int
is_renewal: bool
MessageType = Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 18, 19, 20, 21, 22, 23, 24, 25]
class Message(PartialMessage): class Message(PartialMessage):
@ -117,6 +124,7 @@ class Message(PartialMessage):
referenced_message: NotRequired[Optional[Message]] referenced_message: NotRequired[Optional[Message]]
interaction: NotRequired[MessageInteraction] interaction: NotRequired[MessageInteraction]
components: NotRequired[List[Component]] components: NotRequired[List[Component]]
role_subscription_data: NotRequired[RoleSubscriptionData]
AllowedMentionType = Literal['roles', 'users', 'everyone'] AllowedMentionType = Literal['roles', 'users', 'everyone']

2
discord/types/role.py

@ -47,4 +47,6 @@ class Role(TypedDict):
class RoleTags(TypedDict, total=False): class RoleTags(TypedDict, total=False):
bot_id: Snowflake bot_id: Snowflake
integration_id: Snowflake integration_id: Snowflake
subscription_listing_id: Snowflake
premium_subscriber: None premium_subscriber: None
available_for_purchase: None

16
docs/api.rst

@ -1690,6 +1690,11 @@ of :class:`enum.Enum`.
The system message sent when an AutoMod rule is triggered. This is only The system message sent when an AutoMod rule is triggered. This is only
sent if the rule is configured to sent an alert when triggered. sent if the rule is configured to sent an alert when triggered.
.. versionadded:: 2.0
.. attribute:: role_subscription_purchase
The system message sent when a user purchases or renews a role subscription.
.. versionadded:: 2.0 .. versionadded:: 2.0
.. class:: InviteType .. class:: InviteType
@ -4801,7 +4806,7 @@ of :class:`enum.Enum`.
The ``id`` locale. The ``id`` locale.
.. versionadded:: 2.2 .. versionadded:: 2.0
.. attribute:: danish .. attribute:: danish
@ -5374,7 +5379,7 @@ of :class:`enum.Enum`.
Represents how a forum's posts are layed out in the client. Represents how a forum's posts are layed out in the client.
.. versionadded:: 2.2 .. versionadded:: 2.0
.. attribute:: not_set .. attribute:: not_set
@ -6973,7 +6978,7 @@ Message
.. attributetable:: PartialMessage .. attributetable:: PartialMessage
.. autoclass:: PartialMessage .. autoclass:: PartialMessage()
:members: :members:
.. attributetable:: Attachment .. attributetable:: Attachment
@ -6991,6 +6996,11 @@ Message
.. autoclass:: DeletedReferencedMessage() .. autoclass:: DeletedReferencedMessage()
:members: :members:
.. attributetable:: RoleSubscriptionInfo
.. autoclass:: RoleSubscriptionInfo()
:members:
Reaction Reaction
~~~~~~~~ ~~~~~~~~

Loading…
Cancel
Save