Adding all files
This commit is contained in:
974
.local/lib/python3.14/site-packages/disnake/activity.py
Normal file
974
.local/lib/python3.14/site-packages/disnake/activity.py
Normal file
@@ -0,0 +1,974 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union, overload
|
||||
|
||||
from .asset import Asset
|
||||
from .colour import Colour
|
||||
from .enums import ActivityType, StatusDisplayType, try_enum
|
||||
from .partial_emoji import PartialEmoji
|
||||
|
||||
__all__ = (
|
||||
"BaseActivity",
|
||||
"Activity",
|
||||
"Streaming",
|
||||
"Game",
|
||||
"Spotify",
|
||||
"CustomActivity",
|
||||
)
|
||||
|
||||
"""If curious, this is the current schema for an activity.
|
||||
|
||||
It's fairly long so I will document it here:
|
||||
|
||||
All keys are optional.
|
||||
|
||||
state: str (max: 128),
|
||||
details: str (max: 128)
|
||||
timestamps: dict
|
||||
start: int (min: 1)
|
||||
end: int (min: 1)
|
||||
assets: dict
|
||||
large_image: str (max: 32)
|
||||
large_text: str (max: 128)
|
||||
small_image: str (max: 32)
|
||||
small_text: str (max: 128)
|
||||
party: dict
|
||||
id: str (max: 128),
|
||||
size: List[int] (max-length: 2)
|
||||
elem: int (min: 1)
|
||||
secrets: dict
|
||||
match: str (max: 128)
|
||||
join: str (max: 128)
|
||||
spectate: str (max: 128)
|
||||
instance: bool
|
||||
application_id: str
|
||||
name: str (max: 128)
|
||||
url: str
|
||||
type: int
|
||||
sync_id: str
|
||||
session_id: str
|
||||
flags: int
|
||||
buttons: list[str (max: 32)]
|
||||
|
||||
There are also activity flags which are mostly uninteresting for the library atm.
|
||||
|
||||
t.ActivityFlags = {
|
||||
INSTANCE: 1,
|
||||
JOIN: 2,
|
||||
SPECTATE: 4,
|
||||
JOIN_REQUEST: 8,
|
||||
SYNC: 16,
|
||||
PLAY: 32
|
||||
}
|
||||
"""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .state import ConnectionState
|
||||
from .types.activity import (
|
||||
Activity as ActivityPayload,
|
||||
ActivityAssets,
|
||||
ActivityEmoji as ActivityEmojiPayload,
|
||||
ActivityParty,
|
||||
ActivityTimestamps,
|
||||
)
|
||||
from .types.emoji import PartialEmoji as PartialEmojiPayload
|
||||
from .types.widget import WidgetActivity as WidgetActivityPayload
|
||||
|
||||
|
||||
class _BaseActivity:
|
||||
__slots__ = ("_created_at", "_timestamps", "assets")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
created_at: Optional[float] = None,
|
||||
timestamps: Optional[ActivityTimestamps] = None,
|
||||
assets: Optional[ActivityAssets] = None,
|
||||
**kwargs: Any, # discarded
|
||||
) -> None:
|
||||
self._created_at: Optional[float] = created_at
|
||||
self._timestamps: ActivityTimestamps = timestamps or {}
|
||||
self.assets: ActivityAssets = assets or {}
|
||||
|
||||
@property
|
||||
def created_at(self) -> Optional[datetime.datetime]:
|
||||
"""Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC.
|
||||
|
||||
.. versionadded:: 1.3
|
||||
"""
|
||||
if self._created_at is not None:
|
||||
return datetime.datetime.fromtimestamp(
|
||||
self._created_at / 1000, tz=datetime.timezone.utc
|
||||
)
|
||||
|
||||
@property
|
||||
def start(self) -> Optional[datetime.datetime]:
|
||||
"""Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC, if applicable.
|
||||
|
||||
.. versionchanged:: 2.6
|
||||
This attribute can now be ``None``.
|
||||
"""
|
||||
try:
|
||||
timestamp = self._timestamps["start"] / 1000
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
return datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc)
|
||||
|
||||
@property
|
||||
def end(self) -> Optional[datetime.datetime]:
|
||||
"""Optional[:class:`datetime.datetime`]: When the user will stop doing this activity in UTC, if applicable.
|
||||
|
||||
.. versionchanged:: 2.6
|
||||
This attribute can now be ``None``.
|
||||
"""
|
||||
try:
|
||||
timestamp = self._timestamps["end"] / 1000
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
return datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc)
|
||||
|
||||
def to_dict(self) -> ActivityPayload:
|
||||
raise NotImplementedError
|
||||
|
||||
def _create_image_url(self, asset: str) -> Optional[str]:
|
||||
# `asset` can be a simple ID (see `Activity._create_image_url`),
|
||||
# or a string of the format `<prefix>:<id>`
|
||||
prefix, _, asset_id = asset.partition(":")
|
||||
|
||||
if asset_id and (url_fmt := _ACTIVITY_URLS.get(prefix)):
|
||||
return url_fmt.format(asset_id)
|
||||
return None
|
||||
|
||||
@property
|
||||
def large_image_url(self) -> Optional[str]:
|
||||
"""Optional[:class:`str`]: Returns a URL pointing to the large image asset of this activity, if applicable.
|
||||
|
||||
.. versionchanged:: 2.10
|
||||
Moved from :class:`Activity` to base type, making this available to all activity types.
|
||||
Additionally, supports dynamic asset urls using the ``mp:`` prefix now.
|
||||
"""
|
||||
try:
|
||||
large_image = self.assets["large_image"]
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
return self._create_image_url(large_image)
|
||||
|
||||
@property
|
||||
def small_image_url(self) -> Optional[str]:
|
||||
"""Optional[:class:`str`]: Returns a URL pointing to the small image asset of this activity, if applicable.
|
||||
|
||||
.. versionchanged:: 2.10
|
||||
Moved from :class:`Activity` to base type, making this available to all activity types.
|
||||
Additionally, supports dynamic asset urls using the ``mp:`` prefix now.
|
||||
"""
|
||||
try:
|
||||
small_image = self.assets["small_image"]
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
return self._create_image_url(small_image)
|
||||
|
||||
@property
|
||||
def large_image_text(self) -> Optional[str]:
|
||||
"""Optional[:class:`str`]: Returns the large image asset hover text of this activity, if applicable.
|
||||
|
||||
.. versionchanged:: 2.10
|
||||
Moved from :class:`Activity` to base type, making this available to all activity types.
|
||||
"""
|
||||
return self.assets.get("large_text")
|
||||
|
||||
@property
|
||||
def small_image_text(self) -> Optional[str]:
|
||||
"""Optional[:class:`str`]: Returns the small image asset hover text of this activity, if applicable.
|
||||
|
||||
.. versionchanged:: 2.10
|
||||
Moved from :class:`Activity` to base type, making this available to all activity types.
|
||||
"""
|
||||
return self.assets.get("small_text")
|
||||
|
||||
@property
|
||||
def large_image_link(self) -> Optional[str]:
|
||||
"""Optional[:class:`str`]: Returns the large image asset URL of this activity, if applicable.
|
||||
|
||||
.. versionadded:: 2.11
|
||||
"""
|
||||
return self.assets.get("large_url")
|
||||
|
||||
@property
|
||||
def small_image_link(self) -> Optional[str]:
|
||||
"""Optional[:class:`str`]: Returns the small image asset URL of this activity, if applicable.
|
||||
|
||||
.. versionadded:: 2.11
|
||||
"""
|
||||
return self.assets.get("small_url")
|
||||
|
||||
|
||||
# tag type for user-settable activities
|
||||
class BaseActivity(_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`.
|
||||
|
||||
The following types currently count as user-settable:
|
||||
|
||||
- :class:`Activity`
|
||||
- :class:`Game`
|
||||
- :class:`Streaming`
|
||||
- :class:`CustomActivity`
|
||||
|
||||
Note that although these types are considered user-settable by the library,
|
||||
Discord typically ignores certain combinations of activity depending on
|
||||
what is currently set. This behaviour may change in the future so there are
|
||||
no guarantees on whether Discord will actually let you set these types.
|
||||
|
||||
.. versionadded:: 1.3
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
# There are additional urls for twitch/youtube/spotify, however
|
||||
# it appears that Discord does not want to document those:
|
||||
# https://github.com/discord/discord-api-docs/pull/4617
|
||||
# They are partially supported by different properties, e.g. `Spotify.album_cover_url`.
|
||||
_ACTIVITY_URLS = {
|
||||
"mp": "https://media.discordapp.net/{}",
|
||||
}
|
||||
|
||||
|
||||
class Activity(BaseActivity):
|
||||
"""Represents an activity in Discord.
|
||||
|
||||
This could be an activity such as streaming, playing, listening
|
||||
or watching.
|
||||
|
||||
For memory optimisation purposes, some activities are offered in slimmed
|
||||
down versions:
|
||||
|
||||
- :class:`Game`
|
||||
- :class:`Streaming`
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: Optional[:class:`str`]
|
||||
The name of the activity.
|
||||
url: Optional[:class:`str`]
|
||||
A stream URL that the activity could be doing.
|
||||
type: :class:`ActivityType`
|
||||
The type of activity currently being done.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
application_id: Optional[:class:`int`]
|
||||
The application ID of the game.
|
||||
name: Optional[:class:`str`]
|
||||
The name of the activity.
|
||||
url: Optional[:class:`str`]
|
||||
A stream URL that the activity could be doing.
|
||||
type: :class:`ActivityType`
|
||||
The type of activity currently being done.
|
||||
state: Optional[:class:`str`]
|
||||
The user's current state. For example, "In Game".
|
||||
details: Optional[:class:`str`]
|
||||
The detail of the user's current activity.
|
||||
assets: :class:`dict`
|
||||
A dictionary representing the images and their hover text of an activity.
|
||||
It contains the following optional keys:
|
||||
|
||||
- ``large_image``: A string representing the ID for the large image asset.
|
||||
- ``large_text``: A string representing the text when hovering over the large image asset.
|
||||
- ``large_url``: A string representing an URL that is opened when clicking on the large image.
|
||||
- ``small_image``: A string representing the ID for the small image asset.
|
||||
- ``small_text``: A string representing the text when hovering over the small image asset.
|
||||
- ``small_url``: A string representing a URL that is opened when clicking on the small image.
|
||||
party: :class:`dict`
|
||||
A dictionary representing the activity party. It contains the following optional keys:
|
||||
|
||||
- ``id``: A string representing the party ID.
|
||||
- ``size``: A list of two integers denoting (current_size, maximum_size).
|
||||
buttons: List[str]
|
||||
A list of strings representing the labels of custom buttons shown in a rich presence.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
.. versionchanged:: 2.6
|
||||
Changed type to ``List[str]`` to match API types.
|
||||
|
||||
emoji: Optional[:class:`PartialEmoji`]
|
||||
The emoji that belongs to this activity.
|
||||
details_url: Optional[:class:`str`]
|
||||
An URL that is linked when clicking on the details text of an activity.
|
||||
|
||||
.. versionadded:: 2.11
|
||||
state_url: Optional[:class:`str`]
|
||||
An URL that is linked when clicking on the state text of an activity.
|
||||
|
||||
.. versionadded:: 2.11
|
||||
status_display_type: Optional[:class:`StatusDisplayType`]
|
||||
Controls which field is displayed in the user's status activity text in the member list.
|
||||
|
||||
.. versionadded:: 2.11
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"state",
|
||||
"details",
|
||||
"party",
|
||||
"flags",
|
||||
"type",
|
||||
"name",
|
||||
"url",
|
||||
"application_id",
|
||||
"emoji",
|
||||
"buttons",
|
||||
"id",
|
||||
"platform",
|
||||
"sync_id",
|
||||
"session_id",
|
||||
"details_url",
|
||||
"state_url",
|
||||
"status_display_type",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
name: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
type: Optional[Union[ActivityType, int]] = None,
|
||||
state: Optional[str] = None,
|
||||
state_url: Optional[str] = None,
|
||||
details: Optional[str] = None,
|
||||
details_url: Optional[str] = None,
|
||||
party: Optional[ActivityParty] = None,
|
||||
application_id: Optional[Union[str, int]] = None,
|
||||
flags: Optional[int] = None,
|
||||
buttons: Optional[List[str]] = None,
|
||||
emoji: Optional[Union[PartialEmojiPayload, ActivityEmojiPayload]] = None,
|
||||
id: Optional[str] = None,
|
||||
platform: Optional[str] = None,
|
||||
sync_id: Optional[str] = None,
|
||||
session_id: Optional[str] = None,
|
||||
status_display_type: Optional[Union[StatusDisplayType, int]] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.state: Optional[str] = state
|
||||
self.state_url: Optional[str] = state_url
|
||||
self.details: Optional[str] = details
|
||||
self.details_url: Optional[str] = details_url
|
||||
self.party: ActivityParty = party or {}
|
||||
self.application_id: Optional[int] = (
|
||||
int(application_id) if application_id is not None else None
|
||||
)
|
||||
self.name: Optional[str] = name
|
||||
self.url: Optional[str] = url
|
||||
self.flags: int = flags or 0
|
||||
self.buttons: List[str] = buttons or []
|
||||
|
||||
# undocumented fields:
|
||||
self.id: Optional[str] = id
|
||||
self.platform: Optional[str] = platform
|
||||
self.sync_id: Optional[str] = sync_id
|
||||
self.session_id: Optional[str] = session_id
|
||||
|
||||
activity_type = type if type is not None else 0
|
||||
self.type: ActivityType = (
|
||||
activity_type
|
||||
if isinstance(activity_type, ActivityType)
|
||||
else try_enum(ActivityType, activity_type)
|
||||
)
|
||||
|
||||
self.status_display_type: Optional[StatusDisplayType] = (
|
||||
try_enum(StatusDisplayType, status_display_type)
|
||||
if isinstance(status_display_type, int)
|
||||
else status_display_type
|
||||
)
|
||||
|
||||
self.emoji: Optional[PartialEmoji] = (
|
||||
PartialEmoji.from_dict(emoji) if emoji is not None else None
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
attrs = (
|
||||
("type", self.type),
|
||||
("name", self.name),
|
||||
("url", self.url),
|
||||
("details", self.details),
|
||||
("application_id", self.application_id),
|
||||
("session_id", self.session_id),
|
||||
("emoji", self.emoji),
|
||||
)
|
||||
inner = " ".join(f"{k!s}={v!r}" for k, v in attrs)
|
||||
return f"<Activity {inner}>"
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
ret: Dict[str, Any] = {}
|
||||
for attr in self.__slots__:
|
||||
value = getattr(self, attr, None)
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
if isinstance(value, dict) and len(value) == 0:
|
||||
continue
|
||||
|
||||
ret[attr] = value
|
||||
|
||||
# fix type field
|
||||
ret["type"] = int(self.type)
|
||||
|
||||
if self.status_display_type:
|
||||
ret["status_display_type"] = int(self.status_display_type)
|
||||
|
||||
if self.emoji:
|
||||
ret["emoji"] = self.emoji.to_dict()
|
||||
# defined in base class slots
|
||||
if self._timestamps:
|
||||
ret["timestamps"] = self._timestamps
|
||||
return ret
|
||||
|
||||
def _create_image_url(self, asset: str) -> Optional[str]:
|
||||
# if parent method already returns valid url, use that
|
||||
if url := super()._create_image_url(asset):
|
||||
return url
|
||||
|
||||
# if it's not a `<prefix>:<id>` asset and we have an application ID, create url
|
||||
if ":" not in asset and self.application_id:
|
||||
return f"{Asset.BASE}/app-assets/{self.application_id}/{asset}.png"
|
||||
|
||||
# else, it's an unknown asset url
|
||||
return None
|
||||
|
||||
|
||||
class Game(BaseActivity):
|
||||
"""A slimmed down version of :class:`Activity` that represents a Discord game.
|
||||
|
||||
This is typically displayed via **Playing** on the official Discord client.
|
||||
|
||||
.. collapse:: operations
|
||||
|
||||
.. describe:: x == y
|
||||
|
||||
Checks if two games are equal.
|
||||
|
||||
.. describe:: x != y
|
||||
|
||||
Checks if two games are not equal.
|
||||
|
||||
.. describe:: hash(x)
|
||||
|
||||
Returns the game's hash.
|
||||
|
||||
.. describe:: str(x)
|
||||
|
||||
Returns the game's name.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: :class:`str`
|
||||
The game's name.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
name: :class:`str`
|
||||
The game's name.
|
||||
assets: :class:`dict`
|
||||
A dictionary with the same structure as :attr:`Activity.assets`.
|
||||
"""
|
||||
|
||||
__slots__ = ("name", "platform")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
*,
|
||||
platform: Optional[str] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.name: str = name
|
||||
|
||||
# undocumented
|
||||
self.platform: Optional[str] = platform
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ActivityType.playing]:
|
||||
""":class:`ActivityType`: Returns the game's type. This is for compatibility with :class:`Activity`.
|
||||
|
||||
It always returns :attr:`ActivityType.playing`.
|
||||
"""
|
||||
return ActivityType.playing
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.name)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Game name={self.name!r}>"
|
||||
|
||||
def to_dict(self) -> ActivityPayload:
|
||||
return {
|
||||
"type": ActivityType.playing.value,
|
||||
"name": str(self.name),
|
||||
"timestamps": self._timestamps,
|
||||
"assets": self.assets,
|
||||
}
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
return isinstance(other, Game) and other.name == self.name
|
||||
|
||||
def __ne__(self, other: Any) -> bool:
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.name)
|
||||
|
||||
|
||||
class Streaming(BaseActivity):
|
||||
"""A slimmed down version of :class:`Activity` that represents a Discord streaming status.
|
||||
|
||||
This is typically displayed via **Streaming** on the official Discord client.
|
||||
|
||||
.. collapse:: operations
|
||||
|
||||
.. describe:: x == y
|
||||
|
||||
Checks if two streams are equal.
|
||||
|
||||
.. describe:: x != y
|
||||
|
||||
Checks if two streams are not equal.
|
||||
|
||||
.. describe:: hash(x)
|
||||
|
||||
Returns the stream's hash.
|
||||
|
||||
.. describe:: str(x)
|
||||
|
||||
Returns the stream's name.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
platform: Optional[:class:`str`]
|
||||
Where the user is streaming from (ie. YouTube, Twitch).
|
||||
|
||||
.. versionadded:: 1.3
|
||||
|
||||
name: Optional[:class:`str`]
|
||||
The stream's name.
|
||||
details: Optional[:class:`str`]
|
||||
An alias for :attr:`name`
|
||||
game: Optional[:class:`str`]
|
||||
The game being streamed.
|
||||
|
||||
.. versionadded:: 1.3
|
||||
|
||||
url: :class:`str`
|
||||
The stream's URL.
|
||||
assets: :class:`dict`
|
||||
A dictionary with the same structure as :attr:`Activity.assets`.
|
||||
"""
|
||||
|
||||
__slots__ = ("platform", "name", "game", "url", "details")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
name: Optional[str],
|
||||
url: str,
|
||||
details: Optional[str] = None,
|
||||
state: Optional[str] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.platform: Optional[str] = name
|
||||
self.name: Optional[str] = details or name
|
||||
self.details: Optional[str] = self.name # compatibility
|
||||
self.url: str = url
|
||||
self.game: Optional[str] = state
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ActivityType.streaming]:
|
||||
""":class:`ActivityType`: Returns the game's type. This is for compatibility with :class:`Activity`.
|
||||
|
||||
It always returns :attr:`ActivityType.streaming`.
|
||||
"""
|
||||
return ActivityType.streaming
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.name)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Streaming name={self.name!r}>"
|
||||
|
||||
@property
|
||||
def twitch_name(self) -> Optional[str]:
|
||||
"""Optional[:class:`str`]: If provided, the twitch name of the user streaming.
|
||||
|
||||
This corresponds to the ``large_image`` key of the :attr:`Streaming.assets`
|
||||
dictionary if it starts with ``twitch:``. Typically set by the Discord client.
|
||||
"""
|
||||
try:
|
||||
name = self.assets["large_image"]
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
return name[7:] if name[:7] == "twitch:" else None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
ret: Dict[str, Any] = {
|
||||
"type": ActivityType.streaming.value,
|
||||
"name": str(self.name),
|
||||
"url": str(self.url),
|
||||
"assets": self.assets,
|
||||
}
|
||||
if self.details:
|
||||
ret["details"] = self.details
|
||||
return ret
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
return isinstance(other, Streaming) and other.name == self.name and other.url == self.url
|
||||
|
||||
def __ne__(self, other: Any) -> bool:
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.name)
|
||||
|
||||
|
||||
class Spotify(_BaseActivity):
|
||||
"""Represents a Spotify listening activity from Discord.
|
||||
|
||||
.. collapse:: operations
|
||||
|
||||
.. describe:: x == y
|
||||
|
||||
Checks if two activities are equal.
|
||||
|
||||
.. describe:: x != y
|
||||
|
||||
Checks if two activities are not equal.
|
||||
|
||||
.. describe:: hash(x)
|
||||
|
||||
Returns the activity's hash.
|
||||
|
||||
.. describe:: str(x)
|
||||
|
||||
Returns the string 'Spotify'.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"_state",
|
||||
"_details",
|
||||
"_party",
|
||||
"_sync_id",
|
||||
"_session_id",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
state: Optional[str] = None,
|
||||
details: Optional[str] = None,
|
||||
party: Optional[ActivityParty] = None,
|
||||
sync_id: Optional[str] = None,
|
||||
session_id: Optional[str] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self._state: str = state or ""
|
||||
self._details: str = details or ""
|
||||
self._party: ActivityParty = party or {}
|
||||
self._sync_id: str = sync_id or ""
|
||||
self._session_id: Optional[str] = session_id
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ActivityType.listening]:
|
||||
""":class:`ActivityType`: Returns the activity's type. This is for compatibility with :class:`Activity`.
|
||||
|
||||
It always returns :attr:`ActivityType.listening`.
|
||||
"""
|
||||
return ActivityType.listening
|
||||
|
||||
@property
|
||||
def colour(self) -> Colour:
|
||||
""":class:`Colour`: Returns the Spotify integration colour, as a :class:`Colour`.
|
||||
|
||||
There is an alias for this named :attr:`color`
|
||||
"""
|
||||
return Colour(0x1DB954)
|
||||
|
||||
@property
|
||||
def color(self) -> Colour:
|
||||
""":class:`Colour`: Returns the Spotify integration colour, as a :class:`Colour`.
|
||||
|
||||
There is an alias for this named :attr:`colour`
|
||||
"""
|
||||
return self.colour
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"flags": 48, # SYNC | PLAY
|
||||
"name": "Spotify",
|
||||
"assets": self.assets,
|
||||
"party": self._party,
|
||||
"sync_id": self._sync_id,
|
||||
"session_id": self._session_id,
|
||||
"timestamps": self._timestamps,
|
||||
"details": self._details,
|
||||
"state": self._state,
|
||||
}
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
""":class:`str`: The activity's name. This will always return "Spotify"."""
|
||||
return "Spotify"
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
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: Any) -> bool:
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._session_id)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "Spotify"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Spotify title={self.title!r} artist={self.artist!r} track_id={self.track_id!r}>"
|
||||
|
||||
@property
|
||||
def title(self) -> str:
|
||||
""":class:`str`: The title of the song being played."""
|
||||
return self._details
|
||||
|
||||
@property
|
||||
def artists(self) -> List[str]:
|
||||
"""List[:class:`str`]: The artists of the song being played."""
|
||||
return self._state.split("; ")
|
||||
|
||||
@property
|
||||
def artist(self) -> str:
|
||||
""":class:`str`: The artist of the song being played.
|
||||
|
||||
This does not attempt to split the artist information into
|
||||
multiple artists. Useful if there's only a single artist.
|
||||
"""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def album(self) -> str:
|
||||
""":class:`str`: The album that the song being played belongs to."""
|
||||
return self.assets.get("large_text", "")
|
||||
|
||||
@property
|
||||
def album_cover_url(self) -> str:
|
||||
""":class:`str`: The album cover image URL from Spotify's CDN."""
|
||||
large_image = self.assets.get("large_image", "")
|
||||
if large_image[:8] != "spotify:":
|
||||
return ""
|
||||
album_image_id = large_image[8:]
|
||||
return f"https://i.scdn.co/image/{album_image_id}"
|
||||
|
||||
@property
|
||||
def track_id(self) -> str:
|
||||
""":class:`str`: The track ID used by Spotify to identify this song."""
|
||||
return self._sync_id
|
||||
|
||||
@property
|
||||
def track_url(self) -> str:
|
||||
""":class:`str`: The track URL to listen on Spotify.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
return f"https://open.spotify.com/track/{self.track_id}"
|
||||
|
||||
@property
|
||||
def duration(self) -> Optional[datetime.timedelta]:
|
||||
"""Optional[:class:`datetime.timedelta`]: The duration of the song being played, if applicable.
|
||||
|
||||
.. versionchanged:: 2.6
|
||||
This attribute can now be ``None``.
|
||||
"""
|
||||
start, end = self.start, self.end
|
||||
if start and end:
|
||||
return end - start
|
||||
return None
|
||||
|
||||
@property
|
||||
def party_id(self) -> str:
|
||||
""":class:`str`: The party ID of the listening party."""
|
||||
return self._party.get("id", "")
|
||||
|
||||
|
||||
class CustomActivity(BaseActivity):
|
||||
"""Represents a Custom activity from Discord.
|
||||
|
||||
.. collapse:: operations
|
||||
|
||||
.. describe:: x == y
|
||||
|
||||
Checks if two activities are equal.
|
||||
|
||||
.. describe:: x != y
|
||||
|
||||
Checks if two activities are not equal.
|
||||
|
||||
.. describe:: hash(x)
|
||||
|
||||
Returns the activity's hash.
|
||||
|
||||
.. describe:: str(x)
|
||||
|
||||
Returns the custom status text.
|
||||
|
||||
.. versionadded:: 1.3
|
||||
|
||||
Attributes
|
||||
----------
|
||||
name: Optional[:class:`str`]
|
||||
The custom activity's name.
|
||||
emoji: Optional[:class:`PartialEmoji`]
|
||||
The emoji to pass to the activity, if any.
|
||||
|
||||
This currently cannot be set by bots.
|
||||
"""
|
||||
|
||||
__slots__ = ("name", "emoji", "state")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: Optional[str],
|
||||
*,
|
||||
emoji: Optional[Union[ActivityEmojiPayload, str, PartialEmoji]] = None,
|
||||
state: Optional[str] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.name: Optional[str] = name
|
||||
# Fall back to `name`, since `state` is the relevant field for custom status (`name` is not shown)
|
||||
self.state: Optional[str] = state or name
|
||||
|
||||
# The official client uses "Custom Status" as the name, the actual name is in `state`
|
||||
if self.name == "Custom Status":
|
||||
self.name = self.state
|
||||
|
||||
self.emoji: Optional[PartialEmoji]
|
||||
if emoji is None:
|
||||
self.emoji = emoji
|
||||
elif isinstance(emoji, dict):
|
||||
self.emoji = PartialEmoji.from_dict(emoji)
|
||||
elif isinstance(emoji, str):
|
||||
self.emoji = PartialEmoji(name=emoji)
|
||||
elif isinstance(emoji, PartialEmoji):
|
||||
self.emoji = emoji
|
||||
else:
|
||||
raise TypeError(
|
||||
f"Expected str, PartialEmoji, or None, received {type(emoji)!r} instead."
|
||||
)
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ActivityType.custom]:
|
||||
""":class:`ActivityType`: Returns the activity's type. This is for compatibility with :class:`Activity`.
|
||||
|
||||
It always returns :attr:`ActivityType.custom`.
|
||||
"""
|
||||
return ActivityType.custom
|
||||
|
||||
def to_dict(self) -> ActivityPayload:
|
||||
o: ActivityPayload
|
||||
if self.name == self.state:
|
||||
o = {
|
||||
"type": ActivityType.custom.value,
|
||||
"state": self.name,
|
||||
"name": "Custom Status",
|
||||
}
|
||||
else:
|
||||
o = {
|
||||
"type": ActivityType.custom.value,
|
||||
"name": self.name or "",
|
||||
}
|
||||
|
||||
if self.emoji:
|
||||
o["emoji"] = self.emoji.to_dict() # type: ignore
|
||||
return o
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
return (
|
||||
isinstance(other, CustomActivity)
|
||||
and other.name == self.name
|
||||
and other.emoji == self.emoji
|
||||
)
|
||||
|
||||
def __ne__(self, other: Any) -> bool:
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.name, str(self.emoji)))
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.emoji:
|
||||
if self.name:
|
||||
return f"{self.emoji} {self.name}"
|
||||
return str(self.emoji)
|
||||
else:
|
||||
return str(self.name)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<CustomActivity name={self.name!r} emoji={self.emoji!r}>"
|
||||
|
||||
|
||||
ActivityTypes = Union[Activity, Game, CustomActivity, Streaming, Spotify]
|
||||
|
||||
|
||||
@overload
|
||||
def create_activity(
|
||||
data: Union[ActivityPayload, WidgetActivityPayload], *, state: Optional[ConnectionState] = None
|
||||
) -> ActivityTypes: ...
|
||||
|
||||
|
||||
@overload
|
||||
def create_activity(data: None, *, state: Optional[ConnectionState] = None) -> None: ...
|
||||
|
||||
|
||||
def create_activity(
|
||||
data: Optional[Union[ActivityPayload, WidgetActivityPayload]],
|
||||
*,
|
||||
state: Optional[ConnectionState] = None,
|
||||
) -> Optional[ActivityTypes]:
|
||||
if not data:
|
||||
return None
|
||||
|
||||
activity: ActivityTypes
|
||||
game_type = try_enum(ActivityType, data.get("type", -1))
|
||||
if game_type is ActivityType.playing and not (
|
||||
"application_id" in data or "session_id" in data or "state" in data
|
||||
):
|
||||
activity = Game(**data) # type: ignore # pyright bug(?)
|
||||
elif game_type is ActivityType.custom and "name" in data:
|
||||
activity = CustomActivity(**data) # type: ignore
|
||||
elif game_type is ActivityType.streaming and "url" in data:
|
||||
# url won't be None here
|
||||
activity = Streaming(**data) # type: ignore
|
||||
elif game_type is ActivityType.listening and "sync_id" in data and "session_id" in data:
|
||||
activity = Spotify(**data)
|
||||
else:
|
||||
activity = Activity(**data) # type: ignore
|
||||
|
||||
if isinstance(activity, (Activity, CustomActivity)) and activity.emoji and state:
|
||||
activity.emoji._state = state
|
||||
|
||||
return activity
|
||||
Reference in New Issue
Block a user