-
-
Notifications
You must be signed in to change notification settings - Fork 494
feat: File type filtering for Attachment options and FileUpload component #3284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 8 commits
7cf9b67
c86a40e
f35b0a2
00b0211
edac61c
6dc69b4
868fdf3
0815760
1c75396
99dcede
e517dec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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: | ||
|
|
@@ -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 | ||
| -------- | ||
|
|
@@ -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: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This also checks the length of our enum values, which is a bit useless? |
||
| 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.") | ||
|
|
@@ -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] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| return as_dict | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -33,6 +33,7 @@ | |||||
| ButtonStyle, | ||||||
| ChannelType, | ||||||
| ComponentType, | ||||||
| FileType, | ||||||
| InputTextStyle, | ||||||
| SelectDefaultValueType, | ||||||
| SeparatorSpacingSize, | ||||||
|
|
@@ -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`] | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 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. | ||||||
| """ | ||||||
|
|
@@ -1396,6 +1405,7 @@ class FileUpload(Component): | |||||
| "min_values", | ||||||
| "max_values", | ||||||
| "required", | ||||||
| "file_types", | ||||||
| "id", | ||||||
| ) | ||||||
|
|
||||||
|
|
@@ -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) | ||||||
| ) | ||||||
|
Comment on lines
+1424
to
+1429
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure the logic here matches what you were planning, unless I missed something. Currently, when |
||||||
|
|
||||||
| def to_dict(self) -> FileUploadComponentPayload: | ||||||
| payload = { | ||||||
|
|
@@ -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] | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. make it a string enum |
||||||
|
|
||||||
| return payload # type: ignore | ||||||
|
|
||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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 | ||||||
|
|
||||||
|
|
@@ -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`] | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 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", | ||||||
| ) | ||||||
|
|
||||||
|
|
@@ -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__() | ||||||
|
|
@@ -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 | ||||||
|
|
||||||
|
|
@@ -96,6 +122,7 @@ def __init__( | |||||
| min_values=min_values, | ||||||
| max_values=max_values, | ||||||
| required=required, | ||||||
| file_types=file_types, | ||||||
| id=id, | ||||||
| ) | ||||||
|
|
||||||
|
|
@@ -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) | ||||||
|
|
@@ -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, | ||||||
| ) | ||||||
|
|
||||||
|
|
@@ -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.""" | ||||||
|
|
@@ -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, | ||||||
| ) | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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``. | ||
|
Comment on lines
+2711
to
+2721
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Documented twice? Docstring in the class too. Not sure if that's even considered. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
List[X, Y]is not valid, because it only takes one type parameter.