From 829c2d4a1a925fd61067da3508c1b759af5a2283 Mon Sep 17 00:00:00 2001 From: Nadir Chowdhury Date: Sat, 24 Apr 2021 05:27:47 +0100 Subject: [PATCH] Add Activity.buttons --- discord/activity.py | 86 +++++++++++++++++++++++++++++++-------- discord/types/activity.py | 6 +++ 2 files changed, 75 insertions(+), 17 deletions(-) diff --git a/discord/activity.py b/discord/activity.py index f91288172..d40fdccb6 100644 --- a/discord/activity.py +++ b/discord/activity.py @@ -22,7 +22,10 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +from __future__ import annotations + import datetime +from typing import List, TYPE_CHECKING from .asset import Asset from .enums import ActivityType, try_enum @@ -71,6 +74,9 @@ type: int sync_id: str session_id: str flags: int +buttons: list[dict] + label: str (max: 32) + url: str (max: 512) There are also activity flags which are mostly uninteresting for the library atm. @@ -84,6 +90,15 @@ t.ActivityFlags = { } """ +if TYPE_CHECKING: + from .types.activity import ( + ActivityTimestamps, + ActivityParty, + ActivityAssets, + ActivityButton, + ) + + class BaseActivity: """The base activity that all user-settable activities inherit from. A user-settable activity is one that can be used in :meth:`Client.change_presence`. @@ -102,6 +117,7 @@ class BaseActivity: .. versionadded:: 1.3 """ + __slots__ = ('_created_at',) def __init__(self, **kwargs): @@ -116,6 +132,7 @@ class BaseActivity: if self._created_at is not None: return datetime.datetime.utcfromtimestamp(self._created_at / 1000) + class Activity(BaseActivity): """Represents an activity in Discord. @@ -164,27 +181,51 @@ class Activity(BaseActivity): - ``id``: A string representing the party ID. - ``size``: A list of up to two integer elements denoting (current_size, maximum_size). + buttons: List[:class:`dict`] + An list of dictionaries representing custom buttons shown in a rich presence. + Each dictionary contains the following keys: + + - ``label``: A string representing the text shown on the button. + - ``url``: A string representing the URL opened upon clicking the button. + + .. versionadded:: 2.0 + emoji: Optional[:class:`PartialEmoji`] The emoji that belongs to this activity. """ - __slots__ = ('state', 'details', '_created_at', 'timestamps', 'assets', 'party', - 'flags', 'sync_id', 'session_id', 'type', 'name', 'url', - 'application_id', 'emoji') + __slots__ = ( + 'state', + 'details', + '_created_at', + 'timestamps', + 'assets', + 'party', + 'flags', + 'sync_id', + 'session_id', + 'type', + 'name', + 'url', + 'application_id', + 'emoji', + 'buttons', + ) def __init__(self, **kwargs): super().__init__(**kwargs) self.state = kwargs.pop('state', None) self.details = kwargs.pop('details', None) - self.timestamps = kwargs.pop('timestamps', {}) - self.assets = kwargs.pop('assets', {}) - self.party = kwargs.pop('party', {}) + self.timestamps: ActivityTimestamps = kwargs.pop('timestamps', {}) + self.assets: ActivityAssets = kwargs.pop('assets', {}) + self.party: ActivityParty = kwargs.pop('party', {}) self.application_id = _get_as_snowflake(kwargs, 'application_id') self.name = kwargs.pop('name', None) self.url = kwargs.pop('url', None) self.flags = kwargs.pop('flags', 0) self.sync_id = kwargs.pop('sync_id', None) self.session_id = kwargs.pop('session_id', None) + self.buttons: List[ActivityButton] = kwargs.pop('buttons', []) activity_type = kwargs.pop('type', -1) self.type = activity_type if isinstance(activity_type, ActivityType) else try_enum(ActivityType, activity_type) @@ -269,6 +310,7 @@ class Activity(BaseActivity): return None else: return Asset.BASE + f'/app-assets/{self.application_id}/{small_image}.png' + @property def large_image_text(self): """Optional[:class:`str`]: Returns the large image asset hover text of this activity if applicable.""" @@ -321,7 +363,7 @@ class Game(BaseActivity): self.name = name try: - timestamps = extra['timestamps'] + timestamps: ActivityTimestamps = extra['timestamps'] except KeyError: self._start = 0 self._end = 0 @@ -365,11 +407,13 @@ class Game(BaseActivity): if self._end: timestamps['end'] = self._end + # fmt: off return { 'type': ActivityType.playing.value, 'name': str(self.name), 'timestamps': timestamps } + # fmt: on def __eq__(self, other): return isinstance(other, Game) and other.name == self.name @@ -380,6 +424,7 @@ class Game(BaseActivity): def __hash__(self): return hash(self.name) + class Streaming(BaseActivity): """A slimmed down version of :class:`Activity` that represents a Discord streaming status. @@ -433,8 +478,8 @@ class Streaming(BaseActivity): self.name = extra.pop('details', name) self.game = extra.pop('state', None) self.url = url - self.details = extra.pop('details', self.name) # compatibility - self.assets = extra.pop('assets', {}) + self.details = extra.pop('details', self.name) # compatibility + self.assets: ActivityAssets = extra.pop('assets', {}) @property def type(self): @@ -466,12 +511,14 @@ class Streaming(BaseActivity): return name[7:] if name[:7] == 'twitch:' else None def to_dict(self): + # fmt: off ret = { 'type': ActivityType.streaming.value, 'name': str(self.name), 'url': str(self.url), 'assets': self.assets } + # fmt: on if self.details: ret['details'] = self.details return ret @@ -485,6 +532,7 @@ class Streaming(BaseActivity): def __hash__(self): return hash(self.name) + class Spotify: """Represents a Spotify listening activity from Discord. This is a special case of :class:`Activity` that makes it easier to work with the Spotify integration. @@ -508,8 +556,7 @@ class Spotify: Returns the string 'Spotify'. """ - __slots__ = ('_state', '_details', '_timestamps', '_assets', '_party', '_sync_id', '_session_id', - '_created_at') + __slots__ = ('_state', '_details', '_timestamps', '_assets', '_party', '_sync_id', '_session_id', '_created_at') def __init__(self, **data): self._state = data.pop('state', None) @@ -543,7 +590,7 @@ class Spotify: """:class:`Colour`: Returns the Spotify integration colour, as a :class:`Colour`. There is an alias for this named :attr:`color`""" - return Colour(0x1db954) + return Colour(0x1DB954) @property def color(self): @@ -554,7 +601,7 @@ class Spotify: def to_dict(self): return { - 'flags': 48, # SYNC | PLAY + 'flags': 48, # SYNC | PLAY 'name': 'Spotify', 'assets': self._assets, 'party': self._party, @@ -562,7 +609,7 @@ class Spotify: 'session_id': self._session_id, 'timestamps': self._timestamps, 'details': self._details, - 'state': self._state + 'state': self._state, } @property @@ -571,8 +618,12 @@ class Spotify: return 'Spotify' def __eq__(self, other): - return (isinstance(other, Spotify) and other._session_id == self._session_id - and other._sync_id == self._sync_id and other.start == self.start) + return ( + isinstance(other, Spotify) + and other._session_id == self._session_id + and other._sync_id == self._sync_id + and other.start == self.start + ) def __ne__(self, other): return not self.__eq__(other) @@ -644,6 +695,7 @@ class Spotify: """:class:`str`: The party ID of the listening party.""" return self._party.get('id', '') + class CustomActivity(BaseActivity): """Represents a Custom activity from Discord. @@ -721,7 +773,7 @@ class CustomActivity(BaseActivity): return o def __eq__(self, other): - return (isinstance(other, CustomActivity) and other.name == self.name and other.emoji == self.emoji) + return isinstance(other, CustomActivity) and other.name == self.name and other.emoji == self.emoji def __ne__(self, other): return not self.__eq__(other) diff --git a/discord/types/activity.py b/discord/types/activity.py index 1e3ce7460..9c583d3a8 100644 --- a/discord/types/activity.py +++ b/discord/types/activity.py @@ -78,6 +78,11 @@ class ActivityEmoji(_ActivityEmojiOptional): name: str +class ActivityButton(TypedDict): + label: str + url: str + + class _SendableActivityOptional(TypedDict, total=False): url: Optional[str] @@ -106,3 +111,4 @@ class Activity(_BaseActivity, total=False): secrets: ActivitySecrets session_id: Optional[str] instance: bool + buttons: List[ActivityButton]