diff --git a/discord/flags.py b/discord/flags.py index 9f687ff9bc..2238a1492d 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -32,6 +32,7 @@ __all__ = ( "SystemChannelFlags", "MessageFlags", + "AttachmentFlags", "PublicUserFlags", "Intents", "MemberCacheFlags", @@ -1485,3 +1486,68 @@ def require_tag(self): .. versionadded:: 2.2 """ return 1 << 4 + + +@fill_with_flags() +class AttachmentFlags(BaseFlags): + r"""Wraps up the Discord Attachment flags. + + See :class:`SystemChannelFlags`. + + .. container:: operations + + .. describe:: x == y + + Checks if two flags are equal. + .. describe:: x != y + + Checks if two flags 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. + + .. 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. + """ + + __slots__ = () + + @flag_value + def is_clip(self): + """:class:`bool`: Returns ``True`` if the attachment is a clip.""" + return 1 << 0 + + @flag_value + def is_thumbnail(self): + """:class:`bool`: Returns ``True`` if the attachment is a thumbnail.""" + return 1 << 1 + + @flag_value + def is_remix(self): + """:class:`bool`: Returns ``True`` if the attachment has been remixed.""" + return 1 << 2 diff --git a/discord/message.py b/discord/message.py index c74f40c288..b225a75be5 100644 --- a/discord/message.py +++ b/discord/message.py @@ -39,6 +39,7 @@ Union, overload, ) +from urllib.parse import parse_qs, urlparse from . import utils from .components import _component_factory @@ -47,7 +48,7 @@ from .enums import ChannelType, MessageType, try_enum from .errors import InvalidArgument from .file import File -from .flags import MessageFlags +from .flags import AttachmentFlags, MessageFlags from .guild import Guild from .member import Member from .mixins import Hashable @@ -178,6 +179,16 @@ class Attachment(Hashable): waveform: Optional[:class:`str`] The base64 encoded bytearray representing a sampled waveform (currently for voice messages). + .. versionadded:: 2.5 + + flags: :class:`AttachmentFlags` + Extra attributes of the attachment. + + .. versionadded:: 2.5 + + hm: :class:`str` + The unique signature of this attachment's instance. + .. versionadded:: 2.5 """ @@ -195,6 +206,10 @@ class Attachment(Hashable): "description", "duration_secs", "waveform", + "flags", + "_ex", + "_is", + "hm", ) def __init__(self, *, data: AttachmentPayload, state: ConnectionState): @@ -211,6 +226,32 @@ def __init__(self, *, data: AttachmentPayload, state: ConnectionState): self.description: str | None = data.get("description") self.duration_secs: float | None = data.get("duration_secs") self.waveform: str | None = data.get("waveform") + self.flags: AttachmentFlags = AttachmentFlags._from_value(data.get("flags", 0)) + self._ex: str | None = None + self._is: str | None = None + self.hm: str | None = None + + query = urlparse(self.url).query + extras = ["_ex", "_is", "hm"] + if query_params := parse_qs(query): + for attr in extras: + value = "".join(query_params.get(attr.replace("_", ""), [])) + if value: + setattr(self, attr, value) + + @property + def expires_at(self) -> datetime.datetime: + """This attachment URL's expiry time in UTC.""" + if not self._ex: + return None + return datetime.datetime.utcfromtimestamp(int(self._ex, 16)) + + @property + def issued_at(self) -> datetime.datetime: + """The attachment URL's issue time in UTC.""" + if not self._is: + return None + return datetime.datetime.utcfromtimestamp(int(self._is, 16)) def is_spoiler(self) -> bool: """Whether this attachment contains a spoiler.""" diff --git a/discord/types/message.py b/discord/types/message.py index 93e99ba7ab..10d819ebd4 100644 --- a/discord/types/message.py +++ b/discord/types/message.py @@ -74,6 +74,7 @@ class Attachment(TypedDict): proxy_url: str duration_secs: NotRequired[float] waveform: NotRequired[str] + flags: NotRequired[int] MessageActivityType = Literal[1, 2, 3, 5] diff --git a/docs/api/data_classes.rst b/docs/api/data_classes.rst index ab9e8d678d..735f20d68b 100644 --- a/docs/api/data_classes.rst +++ b/docs/api/data_classes.rst @@ -118,6 +118,11 @@ Flags .. autoclass:: MessageFlags() :members: +.. attributetable:: AttachmentFlags + +.. autoclass:: AttachmentFlags() + :members: + .. attributetable:: PublicUserFlags .. autoclass:: PublicUserFlags()