From 4854c56d587dce13f793536581696385805a39a8 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Mon, 18 Aug 2025 06:11:59 -0400 Subject: [PATCH] Allow discord.File in places where UnfurledMediaItem or str are allowed --- discord/components.py | 24 +++++++++++++++++------- discord/file.py | 8 ++++++++ discord/ui/file.py | 33 +++++++++++++++++++++++---------- discord/ui/media_gallery.py | 18 ++++++++++-------- discord/ui/thumbnail.py | 26 +++++++++++++++++++------- 5 files changed, 77 insertions(+), 32 deletions(-) diff --git a/discord/components.py b/discord/components.py index 41c0d3788..0986680fc 100644 --- a/discord/components.py +++ b/discord/components.py @@ -47,6 +47,7 @@ from .enums import ( ) from .flags import AttachmentFlags from .colour import Colour +from .file import File from .utils import get_slots, MISSING, _get_as_snowflake from .partial_emoji import PartialEmoji, _EmojiTag @@ -1009,7 +1010,7 @@ class MediaGalleryItem: Parameters ---------- - media: Union[:class:`str`, :class:`UnfurledMediaItem`] + media: Union[:class:`str`, :class:`discord.File`, :class:`UnfurledMediaItem`] The media item data. This can be a string representing a local file uploaded as an attachment in the message, which can be accessed using the ``attachment://`` format, or an arbitrary url. @@ -1029,14 +1030,21 @@ class MediaGalleryItem: def __init__( self, - media: Union[str, UnfurledMediaItem], + media: Union[str, File, UnfurledMediaItem], *, - description: Optional[str] = None, - spoiler: bool = False, + description: Optional[str] = MISSING, + spoiler: bool = MISSING, ) -> None: self.media = media - self.description: Optional[str] = description - self.spoiler: bool = spoiler + + if isinstance(media, File): + if description is MISSING: + description = media.description + if spoiler is MISSING: + spoiler = media.spoiler + + self.description: Optional[str] = None if description is MISSING else description + self.spoiler: bool = bool(spoiler) self._state: Optional[ConnectionState] = None def __repr__(self) -> str: @@ -1048,11 +1056,13 @@ class MediaGalleryItem: return self._media @media.setter - def media(self, value: Union[str, UnfurledMediaItem]) -> None: + def media(self, value: Union[str, File, UnfurledMediaItem]) -> None: if isinstance(value, str): self._media = UnfurledMediaItem(value) elif isinstance(value, UnfurledMediaItem): self._media = value + elif isinstance(value, File): + self._media = UnfurledMediaItem(value.uri) else: raise TypeError(f'Expected a str or UnfurledMediaItem, not {value.__class__.__name__}') diff --git a/discord/file.py b/discord/file.py index 7e4df415b..c0649d539 100644 --- a/discord/file.py +++ b/discord/file.py @@ -130,6 +130,14 @@ class File: def filename(self, value: str) -> None: self._filename, self.spoiler = _strip_spoiler(value) + @property + def uri(self) -> str: + """:class:`str`: Returns the ``attachment://`` URI for this file. + + .. versionadded:: 2.6 + """ + return f'attachment://{self.filename}' + def reset(self, *, seek: Union[int, bool] = True) -> None: # The `seek` parameter is needed because # the retry-loop is iterated over multiple times diff --git a/discord/ui/file.py b/discord/ui/file.py index 746f18fe0..92b927ac0 100644 --- a/discord/ui/file.py +++ b/discord/ui/file.py @@ -21,13 +21,17 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + from __future__ import annotations from typing import TYPE_CHECKING, Literal, Optional, TypeVar, Union + from .item import Item from ..components import FileComponent, UnfurledMediaItem from ..enums import ComponentType +from ..utils import MISSING +from ..file import File as SendableFile if TYPE_CHECKING: from typing_extensions import Self @@ -60,7 +64,7 @@ class File(Item[V]): Parameters ---------- - media: Union[:class:`str`, :class:`.UnfurledMediaItem`] + media: Union[:class:`str`, :class:`.UnfurledMediaItem`, :class:`discord.File`] This file's media. If this is a string it must point to a local file uploaded within the parent view of this item, and must meet the ``attachment://`` format. @@ -78,17 +82,24 @@ class File(Item[V]): def __init__( self, - media: Union[str, UnfurledMediaItem], + media: Union[str, UnfurledMediaItem, SendableFile], *, - spoiler: bool = False, + spoiler: bool = MISSING, id: Optional[int] = None, ) -> None: super().__init__() - self._underlying = FileComponent._raw_construct( - media=UnfurledMediaItem(media) if isinstance(media, str) else media, - spoiler=spoiler, - id=id, - ) + if isinstance(media, SendableFile): + self._underlying = FileComponent._raw_construct( + media=UnfurledMediaItem(media.uri), + spoiler=media.spoiler if spoiler is MISSING else spoiler, + id=id, + ) + else: + self._underlying = FileComponent._raw_construct( + media=UnfurledMediaItem(media) if isinstance(media, str) else media, + spoiler=bool(spoiler), + id=id, + ) self.id = id def _is_v2(self): @@ -108,13 +119,15 @@ class File(Item[V]): return self._underlying.media @media.setter - def media(self, value: Union[str, UnfurledMediaItem]) -> None: + def media(self, value: Union[str, SendableFile, UnfurledMediaItem]) -> None: if isinstance(value, str): self._underlying.media = UnfurledMediaItem(value) elif isinstance(value, UnfurledMediaItem): self._underlying.media = value + elif isinstance(value, SendableFile): + self._underlying.media = UnfurledMediaItem(value.uri) else: - raise TypeError(f'expected a str or UnfurledMediaItem, not {value.__class__.__name__!r}') + raise TypeError(f'expected a str or UnfurledMediaItem or File, not {value.__class__.__name__!r}') @property def url(self) -> str: diff --git a/discord/ui/media_gallery.py b/discord/ui/media_gallery.py index 8d9a1c9e1..ddcf581fa 100644 --- a/discord/ui/media_gallery.py +++ b/discord/ui/media_gallery.py @@ -27,6 +27,8 @@ from typing import TYPE_CHECKING, List, Literal, Optional, TypeVar, Union from .item import Item from ..enums import ComponentType +from ..utils import MISSING +from ..file import File from ..components import ( MediaGalleryItem, MediaGalleryComponent, @@ -110,9 +112,9 @@ class MediaGallery(Item[V]): def add_item( self, *, - media: Union[str, UnfurledMediaItem], - description: Optional[str] = None, - spoiler: bool = False, + media: Union[str, File, UnfurledMediaItem], + description: Optional[str] = MISSING, + spoiler: bool = MISSING, ) -> Self: """Adds an item to this gallery. @@ -121,7 +123,7 @@ class MediaGallery(Item[V]): Parameters ---------- - media: Union[:class:`str`, :class:`.UnfurledMediaItem`] + media: Union[:class:`str`, :class:`discord.File`, :class:`.UnfurledMediaItem`] The media item data. This can be a string representing a local file uploaded as an attachment in the message, which can be accessed using the ``attachment://`` format, or an arbitrary url. @@ -176,9 +178,9 @@ class MediaGallery(Item[V]): self, index: int, *, - media: Union[str, UnfurledMediaItem], - description: Optional[str] = None, - spoiler: bool = False, + media: Union[str, File, UnfurledMediaItem], + description: Optional[str] = MISSING, + spoiler: bool = MISSING, ) -> Self: """Inserts an item before a specified index to the media gallery. @@ -189,7 +191,7 @@ class MediaGallery(Item[V]): ---------- index: :class:`int` The index of where to insert the field. - media: Union[:class:`str`, :class:`.UnfurledMediaItem`] + media: Union[:class:`str`, :class:`discord.File`, :class:`.UnfurledMediaItem`] The media item data. This can be a string representing a local file uploaded as an attachment in the message, which can be accessed using the ``attachment://`` format, or an arbitrary url. diff --git a/discord/ui/thumbnail.py b/discord/ui/thumbnail.py index 855b27a27..b921ecee7 100644 --- a/discord/ui/thumbnail.py +++ b/discord/ui/thumbnail.py @@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, TypeVar, Union @@ -28,6 +29,8 @@ from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, TypeVar, Union from .item import Item from ..enums import ComponentType from ..components import UnfurledMediaItem +from ..file import File +from ..utils import MISSING if TYPE_CHECKING: from typing_extensions import Self @@ -47,7 +50,7 @@ class Thumbnail(Item[V]): Parameters ---------- - media: Union[:class:`str`, :class:`discord.UnfurledMediaItem`] + media: Union[:class:`str`, :class:`discord.File`, :class:`discord.UnfurledMediaItem`] The media of the thumbnail. This can be a URL or a reference to an attachment that matches the ``attachment://filename.extension`` structure. @@ -74,16 +77,23 @@ class Thumbnail(Item[V]): def __init__( self, - media: Union[str, UnfurledMediaItem], + media: Union[str, File, UnfurledMediaItem], *, - description: Optional[str] = None, - spoiler: bool = False, + description: Optional[str] = MISSING, + spoiler: bool = MISSING, id: Optional[int] = None, ) -> None: super().__init__() + + if isinstance(media, File): + description = description if description is not MISSING else media.description + spoiler = spoiler if spoiler is not MISSING else media.spoiler + media = media.uri + self._media: UnfurledMediaItem = UnfurledMediaItem(media) if isinstance(media, str) else media - self.description: Optional[str] = description - self.spoiler: bool = spoiler + self.description: Optional[str] = None if description is MISSING else description + self.spoiler: bool = bool(spoiler) + self.id = id @property @@ -96,11 +106,13 @@ class Thumbnail(Item[V]): return self._media @media.setter - def media(self, value: Union[str, UnfurledMediaItem]) -> None: + def media(self, value: Union[str, File, UnfurledMediaItem]) -> None: if isinstance(value, str): self._media = UnfurledMediaItem(value) elif isinstance(value, UnfurledMediaItem): self._media = value + elif isinstance(value, File): + self._media = UnfurledMediaItem(value.uri) else: raise TypeError(f'expected a str or UnfurledMediaItem, not {value.__class__.__name__!r}')