Skip to content
1 change: 1 addition & 0 deletions changelog/1282.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add :attr:`Embed.flags`, ``Embed.image.flags``, ``Embed.thumbnail.flags``, :class:`EmbedFlags`, :class:`EmbedMediaFlags` and update the :class:`AttachmentFlags`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this is missing the unfurled media item flags, which were added later in the API docs PR.

30 changes: 30 additions & 0 deletions disnake/embeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from . import utils
from .colour import Colour
from .file import File
from .flags import EmbedFlags, EmbedMediaFlags
from .utils import MISSING

__all__ = ("Embed",)
Expand Down Expand Up @@ -85,6 +86,7 @@ class _EmbedMediaProxy(Sized, Protocol):
proxy_url: str | None
height: int | None
width: int | None
flags: EmbedMediaFlags | None

class _EmbedVideoProxy(Sized, Protocol):
url: str | None
Expand Down Expand Up @@ -178,6 +180,7 @@ class Embed:
"_fields",
"description",
"_files",
"_flags",
)

_default_colour: ClassVar[Colour | None] = None
Expand Down Expand Up @@ -216,6 +219,7 @@ def __init__(
self._image: EmbedImagePayload | None = None
self._footer: EmbedFooterPayload | None = None
self._fields: list[EmbedFieldPayload] | None = None
self._flags: int = 0

self._files: dict[_FileKey, File] = {}

Expand Down Expand Up @@ -259,12 +263,20 @@ def from_dict(cls, data: EmbedData) -> Self:
self.timestamp = utils.parse_time(data.get("timestamp"))

self._thumbnail = data.get("thumbnail")
if self._thumbnail and (thumbnail_flags := self._thumbnail.get("flags")):
self._thumbnail["flags"] = EmbedMediaFlags._from_value(thumbnail_flags) # pyright: ignore[reportGeneralTypeIssues]

self._video = data.get("video")
self._provider = data.get("provider")
self._author = data.get("author")

self._image = data.get("image")
if self._image and (image_flags := self._image.get("flags")):
self._image["flags"] = EmbedMediaFlags._from_value(image_flags) # pyright: ignore[reportGeneralTypeIssues]

Comment on lines +266 to +276

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like how this is done but I don't see much alternatives

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than save it here, we could make the flags when we access the image or thumbnail, no?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also no longer seems to be respected in serialisation.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also no longer seems to be respected in serialisation.

What do you mean? It's not consistent with the lib?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than save it here, we could make the flags when we access the image or thumbnail, no?

that was my idea as well. It's still a bit awkward, but a separate EmbedProxy subclass that special-cases flags should do, and doesn't result in weird consistency issues with from_dict and to_dict.

self._footer = data.get("footer")
self._fields = data.get("fields")
self._flags = data.get("flags", 0)

return self

Expand Down Expand Up @@ -361,6 +373,14 @@ def timestamp(self, value: datetime.datetime | None) -> None:
msg = f"Expected datetime.datetime or None received {type(value).__name__} instead"
raise TypeError(msg)

@property
def flags(self) -> EmbedFlags:
""":class:`EmbedFlags`: Returns the embed's flags.

.. versionadded:: |vnext|
"""
return EmbedFlags._from_value(self._flags)

@property
def footer(self) -> _EmbedFooterProxy:
"""Returns an ``EmbedProxy`` denoting the footer contents.
Expand Down Expand Up @@ -445,6 +465,11 @@ def image(self) -> _EmbedMediaProxy:
- ``proxy_url``
- ``width``
- ``height``
- ``flags``

.. versionchanged:: |vnext|

Added the ``flags`` attribute.

If an attribute is not set, it will be :data:`None`.
Comment on lines +470 to 474

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.. versionchanged:: |vnext|
Added the ``flags`` attribute.
If an attribute is not set, it will be :data:`None`.
If an attribute is not set, it will be :data:`None`.
.. versionchanged:: |vnext|
Added the ``flags`` attribute.

"""
Expand Down Expand Up @@ -498,6 +523,11 @@ def thumbnail(self) -> _EmbedMediaProxy:
- ``proxy_url``
- ``width``
- ``height``
- ``flags``

.. versionchanged:: |vnext|

Added the ``flags`` attribute.

If an attribute is not set, it will be :data:`None`.
Comment on lines +528 to 532

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.. versionchanged:: |vnext|
Added the ``flags`` attribute.
If an attribute is not set, it will be :data:`None`.
If an attribute is not set, it will be :data:`None`.
.. versionchanged:: |vnext|
Added the ``flags`` attribute.

"""
Expand Down
214 changes: 213 additions & 1 deletion disnake/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
"SKUFlags",
"ApplicationInstallTypes",
"InteractionContextTypes",
"EmbedFlags",
"EmbedMediaFlags",
)

BF = TypeVar("BF", bound="BaseFlags")
Expand Down Expand Up @@ -2622,13 +2624,62 @@ class AttachmentFlags(BaseFlags):
if TYPE_CHECKING:

@_generated
def __init__(self, *, is_remix: bool = ...) -> None: ...
def __init__(
self,
*,
contains_explicit_media: bool = ...,
is_animated: bool = ...,
is_clip: bool = ...,
is_remix: bool = ...,
is_spoiler: bool = ...,
is_thumbnail: bool = ...,
) -> None: ...

@flag_value
def is_clip(self):
""":class:`bool`: Returns ``True`` if the attachment is a clip.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
""":class:`bool`: Returns ``True`` if the attachment is a clip.
""":class:`bool`: Returns ``True`` if the attachment is a clip from a stream.


.. versionadded:: |vnext|
"""
return 1 << 0

@flag_value
def is_thumbnail(self):
""":class:`bool`: Returns ``True`` if the attachment is the thumbnail of a thread in a media channel.

.. versionadded:: |vnext|
"""
return 1 << 1

@flag_value
def is_remix(self) -> int:
""":class:`bool`: Returns ``True`` if the attachment has been edited using the Remix feature."""
return 1 << 2

@flag_value
def is_spoiler(self):
""":class:`bool`: Returns ``True`` if the attachment was marked as a spoiler.

.. versionadded:: |vnext|
"""
return 1 << 3

@flag_value
def contains_explicit_media(self):
Comment thread
Snipy7374 marked this conversation as resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This flag seems to have been removed from the docs again

""":class:`bool`: Returns ``True`` if the attachment was flagged as sensitive content.

.. versionadded:: |vnext|
"""
return 1 << 4

@flag_value
def is_animated(self):
""":class:`bool`: Returns ``True`` if the attachment is an animated image.

.. versionadded:: |vnext|
"""
return 1 << 5


class SKUFlags(BaseFlags):
"""Wraps up Discord SKU flags.
Expand Down Expand Up @@ -2922,3 +2973,164 @@ def bot_dm(self) -> int:
def private_channel(self) -> int:
""":class:`bool`: Returns ``True`` if the command is usable in DMs and group DMs with other users."""
return 1 << 2


class EmbedFlags(BaseFlags):
"""Wraps up Discord Embed flags.

.. collapse:: operations

.. describe:: x == y

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

Checks if two EmbedFlags instances are not equal.
.. describe:: x <= y

Checks if an EmbedFlags instance is a subset of another EmbedFlags instance.
.. describe:: x >= y

Checks if an EmbedFlags instance is a superset of another EmbedFlags instance.
.. describe:: x < y

Checks if an EmbedFlags instance is a strict subset of another EmbedFlags instance.
.. describe:: x > y

Checks if an EmbedFlags instance is a strict superset of another EmbedFlags instance.
.. describe:: x | y, x |= y

Returns a new EmbedFlags instance with all enabled flags from both x and y.
(Using ``|=`` will update in place).
.. describe:: x & y, x &= y

Returns a new EmbedFlags instance with only flags enabled on both x and y.
(Using ``&=`` will update in place).
.. describe:: x ^ y, x ^= y

Returns a new EmbedFlags instance with only flags enabled on one of x or y, but not both.
(Using ``^=`` will update in place).
.. describe:: ~x

Returns a new EmbedFlags instance with all flags from x inverted.
.. describe:: hash(x)

Returns 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.

Additionally supported are a few operations on class attributes.

.. describe:: EmbedFlags.y | EmbedFlags.z, EmbedFlags(y=True) | EmbedFlags.z

Returns an EmbedFlags instance with all provided flags enabled.

.. describe:: ~EmbedFlags.y

Returns an EmbedFlags instance with all flags except ``y`` inverted from their default value.

.. versionadded:: |vnext|

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

if TYPE_CHECKING:

@_generated
def __init__(
self, *, contains_explicit_media: bool = ..., is_content_inventory_entry: bool = ...
) -> None: ...

@flag_value
def contains_explicit_media(self):
Comment thread
Snipy7374 marked this conversation as resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, was removed from the docs

""":class:`bool`: Returns ``True`` if the embed was flagged as sensitive content."""
return 1 << 4

@flag_value
def is_content_inventory_entry(self):
""":class:`bool`: Returns ``True`` if the embed is a reply to an activity card."""
return 1 << 5


class EmbedMediaFlags(BaseFlags):
"""Wraps up Discord Embed media flags.

.. collapse:: operations

.. describe:: x == y

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

Checks if two EmbedMediaFlags instances are not equal.
.. describe:: x <= y

Checks if an EmbedMediaFlags instance is a subset of another EmbedMediaFlags instance.
.. describe:: x >= y

Checks if an EmbedMediaFlags instance is a superset of another EmbedMediaFlags instance.
.. describe:: x < y

Checks if an EmbedMediaFlags instance is a strict subset of another EmbedMediaFlags instance.
.. describe:: x > y

Checks if an EmbedMediaFlags instance is a strict superset of another EmbedMediaFlags instance.
.. describe:: x | y, x |= y

Returns a new EmbedMediaFlags instance with all enabled flags from both x and y.
(Using ``|=`` will update in place).
.. describe:: x & y, x &= y

Returns a new EmbedMediaFlags instance with only flags enabled on both x and y.
(Using ``&=`` will update in place).
.. describe:: x ^ y, x ^= y

Returns a new EmbedMediaFlags instance with only flags enabled on one of x or y, but not both.
(Using ``^=`` will update in place).
.. describe:: ~x

Returns a new EmbedMediaFlags instance with all flags from x inverted.
.. describe:: hash(x)

Returns 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.

Additionally supported are a few operations on class attributes.

.. describe:: EmbedMediaFlags.y | EmbedMediaFlags.z, EmbedMediaFlags(y=True) | EmbedMediaFlags.z

Returns an EmbedMediaFlags instance with all provided flags enabled.

.. describe:: ~EmbedMediaFlags.y

Returns an EmbedMediaFlags instance with all flags except ``y`` inverted from their default value.

.. versionadded:: |vnext|

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

if TYPE_CHECKING:

@_generated
def __init__(self, *, is_animated: bool = ...) -> None: ...

@flag_value
def is_animated(self):
""":class:`bool`: Returns ``True`` if the embed media is animated."""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
""":class:`bool`: Returns ``True`` if the embed media is animated."""
""":class:`bool`: Returns ``True`` if the embed image is animated."""

"media" is plural, at least in this context (apparently it can be singular when referring to mass media?). "embed medium" also sounds a bit weird, so "embed image" seems like a good compromise to me

return 1 << 5
24 changes: 23 additions & 1 deletion disnake/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,16 @@ class Attachment(Hashable):
(see :attr:`MessageFlags.is_voice_message`).

.. versionadded:: 2.9

clip_participants: List[:class:`User`]
If this attachment is a clip returns a list of users who were in the stream.

.. versionadded:: |vnext|

clip_created_at: Optional[:class:`datetime.datetime`]
If this attachment is a clip returns the creation timestamp.

.. versionadded:: |vnext|
Comment on lines +324 to +332

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
clip_participants: List[:class:`User`]
If this attachment is a clip returns a list of users who were in the stream.
.. versionadded:: |vnext|
clip_created_at: Optional[:class:`datetime.datetime`]
If this attachment is a clip returns the creation timestamp.
.. versionadded:: |vnext|
clip_participants: :class:`list`\[:class:`User`]
The list of users who were in the stream, if this attachment is a clip.
.. versionadded:: |vnext|
clip_created_at: :class:`datetime.datetime` | :data:`None`
The creation timestamp, if this attachment is a clip.
.. versionadded:: |vnext|

"""

__slots__ = (
Expand All @@ -338,6 +348,8 @@ class Attachment(Hashable):
"duration",
"waveform",
"_flags",
"clip_participants",
"clip_created_at",
)

def __init__(self, *, data: AttachmentPayload, state: ConnectionState) -> None:
Expand All @@ -358,13 +370,23 @@ def __init__(self, *, data: AttachmentPayload, state: ConnectionState) -> None:
b64decode(waveform_data) if (waveform_data := data.get("waveform")) else None
)
self._flags: int = data.get("flags", 0)
self.clip_participants: list[User] = [
User(state=state, data=d) for d in data.get("clip_participants", [])
]
self.clip_created_at: datetime.datetime | None = utils.parse_time(
data.get("clip_created_at")
)
Comment on lines +373 to +378

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few more non-flags-related additions in the API docs PR - I'd say this PR should either only add the new flags, or everything


def is_spoiler(self) -> bool:
"""Whether this attachment contains a spoiler.

:return type: :class:`bool`

.. versionchanged: |vnext|

Now considers the attachment flags as well as the filename.
"""
return self.filename.startswith("SPOILER_")
return self.filename.startswith("SPOILER_") or self.flags.is_spoiler

def __repr__(self) -> str:
return f"<Attachment id={self.id} filename={self.filename!r} url={self.url!r} ephemeral={self.ephemeral!r}>"
Expand Down
Loading
Loading