Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion discord/commands/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
from ..commands import ApplicationContext, AutocompleteContext
from ..enums import ChannelType
from ..enums import Enum as DiscordEnum
from ..enums import SlashCommandOptionType
from ..enums import FileType, SlashCommandOptionType
from ..utils import MISSING, basic_autocomplete

if TYPE_CHECKING:
Expand Down Expand Up @@ -195,6 +195,15 @@ class Option:
description_localizations: Dict[:class:`str`, :class:`str`]
The description localizations for this option. The values of this should be ``"locale": "description"``.
See `here <https://docs.discord.com/developers/reference#locales>`_ for a list of valid locales.
file_types: List[:class:`str`, :class:`FileType`]
The file types allowed for this option. Supports a mix of :class:`FileType` and dot-prefixed extensions (e.g. ``".pdf"``).
A maximum of 10 file types can be provided, up to 16 characters each.
Only applies to Options with an :attr:`input_type` of :class:`Attachment`.

.. warning::
This only checks the file extension - you will still need to validate the file contents yourself.

.. versionadded:: 2.9

Examples
--------
Expand Down Expand Up @@ -393,6 +402,23 @@ def __init__(
self.description_localizations = kwargs.pop(
"description_localizations", MISSING
)
self.file_types: list[FileType | str] = kwargs.pop("file_types", []) or []
if self.file_types:
if not isinstance(self.file_types, list):
raise TypeError(
f"file_types must be a list, not {self.file_types.__class__.__name__}"
)
if len(self.file_types) > 10:
raise ValueError("file_types must be between 0 and 10 in length")
for f in self.file_types:
if not isinstance(f, (str, FileType)):
raise TypeError(
"items in file_types must be of type str or FileType"
)
if len(str(f)) > 16:
raise ValueError(
"items in file_types must be a maximum of 16 characters in length"
)

if input_type is None:
raise TypeError("input_type cannot be NoneType.")
Expand Down Expand Up @@ -453,6 +479,8 @@ def to_dict(self) -> dict:
as_dict["min_length"] = self.min_length
if self.max_length is not None:
as_dict["max_length"] = self.max_length
if self.file_types:
as_dict["file_types"] = [str(f) for f in self.file_types]

return as_dict

Expand Down
20 changes: 20 additions & 0 deletions discord/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
ButtonStyle,
ChannelType,
ComponentType,
FileType,
InputTextStyle,
SelectDefaultValueType,
SeparatorSpacingSize,
Expand Down Expand Up @@ -1386,6 +1387,14 @@ class FileUpload(Component):
The maximum number of files that can be uploaded.
required: Optional[:class:`bool`]
Whether the file upload field is required or not. Defaults to `True`.
file_types: List[:class:`str`, :class:`FileType`]
The file types allowed in this upload. Supports a mix of :class:`FileType` and dot-prefixed extensions (e.g. ``".pdf"``).
A maximum of 10 file types can be provided, up to 16 characters each.

.. warning::
This only checks the file extension - you will still need to validate the file contents yourself.

.. versionadded:: 2.9
id: Optional[:class:`int`]
The file upload's ID.
"""
Expand All @@ -1396,6 +1405,7 @@ class FileUpload(Component):
"min_values",
"max_values",
"required",
"file_types",
"id",
)

Expand All @@ -1409,6 +1419,13 @@ def __init__(self, data: FileUploadComponentPayload):
self.min_values: int | None = data.get("min_values", None)
self.max_values: int | None = data.get("max_values", None)
self.required: bool = data.get("required", True)
self.file_types: list[str | FileType] = []
for f in data.get("file_types", []):
(
self.file_types.append(f)
if f not in FileType.__members__
else try_enum(FileType, f)
)

def to_dict(self) -> FileUploadComponentPayload:
payload = {
Expand All @@ -1427,6 +1444,9 @@ def to_dict(self) -> FileUploadComponentPayload:
if not self.required:
payload["required"] = self.required

if self.file_types:
payload["file_types"] = [str(f) for f in self.file_types]

return payload # type: ignore


Expand Down
21 changes: 21 additions & 0 deletions discord/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"SelectDefaultValueType",
"ApplicationEventWebhookStatus",
"InviteTargetUsersJobStatusCode",
"FileType",
)


Expand Down Expand Up @@ -1212,6 +1213,26 @@ class InviteTargetUsersJobStatusCode(Enum):
failed = 3


class FileType(Enum):
"""Represents the allowed file types in an attachment option or component. The types are subject to change based on what is supported on Discord clients.

.. versionadded:: 2.9

Attributes
----------
image: :class:`str`
Allow image files. This includes ``.png``, ``.gif``, ``.jpg``, ``.jpeg``, ``.jfif``, ``.webp``, and ``.avif``.
video: :class:`str`
Allow video files. This includes ``.mp4``, ``.mov``, ``.qt``, and ``.webm``.
audio: :class:`str`
Allow audio files. This includes ``.mp3``, ``.m4a``, ``.wav``, ``.ogg``, ``.opus``, and ``.flac``.
"""

image = "image"
video = "video"
audio = "audio"


T = TypeVar("T")


Expand Down
1 change: 1 addition & 0 deletions discord/types/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ class FileUploadComponent(BaseComponent):
max_values: NotRequired[int]
min_values: NotRequired[int]
required: NotRequired[bool]
file_types: NotRequired[list[str]]


class RadioGroupOption(TypedDict):
Expand Down
64 changes: 63 additions & 1 deletion discord/ui/file_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from typing import TYPE_CHECKING

from ..components import FileUpload as FileUploadComponent
from ..enums import ComponentType
from ..enums import ComponentType, FileType
from ..message import Attachment
from .item import ModalItem

Expand Down Expand Up @@ -58,13 +58,22 @@ class FileUpload(ModalItem):
Whether the file upload field is required or not. Defaults to ``True``.
id: Optional[:class:`int`]
The file upload field's ID.
file_types: List[:class:`str`, :class:`FileType`]
The file types allowed in this file upload. Supports a mix of :class:`FileType` and dot-prefixed extensions (e.g. ``".pdf"``).
A maximum of 10 file types can be provided, up to 16 characters each.

.. warning::
This only checks the file extension - you will still need to validate the file contents yourself.

.. versionadded:: 2.9
"""

__item_repr_attributes__: tuple[str, ...] = (
"required",
"min_values",
"max_values",
"custom_id",
"file_types",
"id",
)

Expand All @@ -75,6 +84,7 @@ def __init__(
min_values: int | None = None,
max_values: int | None = None,
required: bool = True,
file_types: list[str | FileType] | None = None,
id: int | None = None,
):
super().__init__()
Expand All @@ -88,6 +98,22 @@ def __init__(
)
if not isinstance(required, bool):
raise TypeError(f"required must be bool not {required.__class__.__name__}") # type: ignore
if file_types is not None:
if not isinstance(file_types, list):
raise TypeError(
f"file_types must be a list, not {file_types.__class__.__name__}"
)
if len(file_types) > 10:
raise ValueError("file_types must be between 0 and 10 in length")
for f in file_types:
if not isinstance(f, (str, FileType)):
raise TypeError(
"items in file_types must be of type str or FileType"
)
if len(str(f)) > 16:
raise ValueError(
"items in file_types must be a maximum of 16 characters in length"
)
custom_id = os.urandom(16).hex() if custom_id is None else custom_id
self._attachments: list[Attachment] | None = None

Expand All @@ -96,6 +122,7 @@ def __init__(
min_values=min_values,
max_values=max_values,
required=required,
file_types=file_types,
id=id,
)

Expand All @@ -105,6 +132,7 @@ def _generate_underlying(
min_values: int | None = None,
max_values: int | None = None,
required: bool | None = None,
file_types: list[str | FileType] | None = None,
id: int | None = None,
) -> FileUploadComponent:
super()._generate_underlying(FileUploadComponent)
Expand All @@ -114,6 +142,7 @@ def _generate_underlying(
min_values=min_values if min_values is not None else self.min_values,
max_values=max_values if max_values is not None else self.max_values,
required=required if required is not None else self.required,
file_types=file_types if file_types else [],
id=id or self.id,
)

Expand Down Expand Up @@ -167,6 +196,38 @@ def required(self, value: bool):
raise TypeError(f"required must be bool not {value.__class__.__name__}") # type: ignore
self.underlying.required = bool(value)

@property
def file_types(self) -> list[str | FileType]:
"""The file types allowed in this file upload. Supports a mix of :class:`FileType` and dot-prefixed extensions (e.g. ``".pdf"``).
A maximum of 10 file types can be provided, up to 16 characters each.

.. warning::
This only checks the file extension - you will still need to validate the file contents yourself.

.. versionadded:: 2.9
"""
return self.underlying.file_types

@file_types.setter
def file_types(self, value: list[str | FileType] | None):
if value:
if not isinstance(value, list):
raise TypeError(
f"file_types must be a list, not {value.__class__.__name__}"
)
if len(value) > 10:
raise ValueError("file_types must be between 0 and 10 in length")
for f in value:
if not isinstance(f, (str, FileType)):
raise TypeError(
"items in file_types must be of type str or FileType"
)
if len(str(f)) > 16:
raise ValueError(
"items in file_types must be a maximum of 16 characters in length"
)
self.underlying.file_types = value or []

@property
def values(self) -> list[Attachment] | None:
"""The files that were uploaded to the field. This will be ``None`` if the file upload has not been submitted via a modal yet."""
Expand Down Expand Up @@ -195,5 +256,6 @@ def from_component(
min_values=component.min_values,
max_values=component.max_values,
required=component.required,
file_types=component.file_types,
id=component.id,
)
17 changes: 17 additions & 0 deletions docs/api/enums.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2702,3 +2702,20 @@ of :class:`enum.Enum`.
.. attribute:: read_only

Represents the team read only role.

.. class:: FileType
Represents the allowed file types in an attachment option or component. The types are subject to change based on what is supported on Discord clients.

.. versionadded:: 2.9

.. attribute:: image

Allow image files. This includes ``.png``, ``.gif``, ``.jpg``, ``.jpeg``, ``.jfif``, ``.webp``, and ``.avif``.

.. attribute:: video

Allow video files. This includes ``.mp4``, ``.mov``, ``.qt``, and ``.webm``.

.. attribute:: audio

Allow audio files. This includes ``.mp3``, ``.m4a``, ``.wav``, ``.ogg``, ``.opus``, and ``.flac``.
Loading