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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ These changes are available on the `master` branch, but have not yet been releas

- Support for **Python 3.14**.
([#2948](https://github.com/Pycord-Development/pycord/pull/2948))
- Added `ComponentLimits` and `EmbedLimits` enums for Discord API constraints.
([#3217](https://github.com/Pycord-Development/pycord/pull/3217))

### Changed

Expand Down
77 changes: 57 additions & 20 deletions discord/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from .enums import (
ButtonStyle,
ChannelType,
ComponentLimits,
ComponentType,
InputTextStyle,
SelectDefaultValueType,
Expand Down Expand Up @@ -724,14 +725,26 @@ def __init__(
emoji: str | GuildEmoji | AppEmoji | PartialEmoji | None = None,
default: bool = False,
) -> None:
if len(label) > 100:
raise ValueError("label must be 100 characters or fewer")
if len(label) > ComponentLimits.select_option_label_max.value:
raise ValueError(
f"label must be {ComponentLimits.select_option_label_max.value} characters or fewer"
)

if value is not MISSING and len(value) > 100:
raise ValueError("value must be 100 characters or fewer")
if (
value is not MISSING
and len(value) > ComponentLimits.select_option_value_max.value
):
raise ValueError(
f"value must be {ComponentLimits.select_option_value_max.value} characters or fewer"
)

if description is not None and len(description) > 100:
raise ValueError("description must be 100 characters or fewer")
if (
description is not None
and len(description) > ComponentLimits.select_option_description_max.value
):
raise ValueError(
f"description must be {ComponentLimits.select_option_description_max.value} characters or fewer"
)

self.label = label
self.value = label if value is MISSING else value
Expand Down Expand Up @@ -990,7 +1003,7 @@ class Thumbnail(Component):
media: :class:`UnfurledMediaItem`
The component's underlying media object.
description: Optional[:class:`str`]
The thumbnail's description, up to 1024 characters.
The thumbnail's description, up to 256 characters.
spoiler: Optional[:class:`bool`]
Whether the thumbnail has the spoiler overlay.
"""
Expand Down Expand Up @@ -1039,7 +1052,7 @@ class MediaGalleryItem:
url: :class:`str`
The URL of this gallery item. This can either be an arbitrary URL or an ``attachment://`` URL to work with local files.
description: Optional[:class:`str`]
The gallery item's description, up to 1024 characters.
The gallery item's description, up to 256 characters.
spoiler: Optional[:class:`bool`]
Whether the gallery item is a spoiler.
"""
Expand Down Expand Up @@ -1527,14 +1540,26 @@ def __init__(
description: str | None = None,
default: bool = False,
) -> None:
if len(label) > 100:
raise ValueError("label must be 100 characters or fewer")
if len(label) > ComponentLimits.select_option_label_max.value:
raise ValueError(
f"label must be {ComponentLimits.select_option_label_max.value} characters or fewer"
)

if value is not MISSING and len(value) > 100:
raise ValueError("value must be 100 characters or fewer")
if (
value is not MISSING
and len(value) > ComponentLimits.select_option_value_max.value
):
raise ValueError(
f"value must be {ComponentLimits.select_option_value_max.value} characters or fewer"
)

if description is not None and len(description) > 100:
raise ValueError("description must be 100 characters or fewer")
if (
description is not None
and len(description) > ComponentLimits.select_option_description_max.value
):
raise ValueError(
f"description must be {ComponentLimits.select_option_description_max.value} characters or fewer"
)

self.label = label
self.value = label if value is MISSING else value
Expand Down Expand Up @@ -1687,14 +1712,26 @@ def __init__(
description: str | None = None,
default: bool = False,
) -> None:
if len(label) > 100:
raise ValueError("label must be 100 characters or fewer")
if len(label) > ComponentLimits.select_option_label_max.value:
raise ValueError(
f"label must be {ComponentLimits.select_option_label_max.value} characters or fewer"
)

if value is not MISSING and len(value) > 100:
raise ValueError("value must be 100 characters or fewer")
if (
value is not MISSING
and len(value) > ComponentLimits.select_option_value_max.value
):
raise ValueError(
f"value must be {ComponentLimits.select_option_value_max.value} characters or fewer"
)

if description is not None and len(description) > 100:
raise ValueError("description must be 100 characters or fewer")
if (
description is not None
and len(description) > ComponentLimits.select_option_description_max.value
):
raise ValueError(
f"description must be {ComponentLimits.select_option_description_max.value} characters or fewer"
)

self.label = label
self.value = label if value is MISSING else value
Expand Down
123 changes: 111 additions & 12 deletions discord/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@
"SelectDefaultValueType",
"ApplicationEventWebhookStatus",
"InviteTargetUsersJobStatusCode",
"ComponentLimits",
"EmbedLimits",
)


Expand All @@ -96,21 +98,17 @@ def _create_value_cls(name, comparable):
cls.__repr__ = lambda self: f"<{name}.{self.name}: {self.value!r}>"
cls.__str__ = lambda self: f"{name}.{self.name}"
if comparable:
cls.__le__ = (
lambda self, other: isinstance(other, self.__class__)
and self.value <= other.value
cls.__le__ = lambda self, other: (
isinstance(other, self.__class__) and self.value <= other.value
)
cls.__ge__ = (
lambda self, other: isinstance(other, self.__class__)
and self.value >= other.value
cls.__ge__ = lambda self, other: (
isinstance(other, self.__class__) and self.value >= other.value
)
cls.__lt__ = (
lambda self, other: isinstance(other, self.__class__)
and self.value < other.value
cls.__lt__ = lambda self, other: (
isinstance(other, self.__class__) and self.value < other.value
)
cls.__gt__ = (
lambda self, other: isinstance(other, self.__class__)
and self.value > other.value
cls.__gt__ = lambda self, other: (
isinstance(other, self.__class__) and self.value > other.value
)
return cls

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


class ComponentLimits(Enum):
Comment thread
Lumabots marked this conversation as resolved.
Comment thread
Lumabots marked this conversation as resolved.
# View constraints
view_children_max = 40

# ActionRow constraints
action_row_children_max = 5

# Button constraints
button_label_max = 80

# Container constraints
container_children_max = -1 # No limit

# MediaGallery constraints
media_gallery_items_min = 1
media_gallery_items_max = 10

# MediaGalleryItem constraints
media_gallery_item_description_max = 256
Comment thread
Lumabots marked this conversation as resolved.

# Select constraints
select_placeholder_max = 150
select_min_value_min = 0
select_min_value_max = 25
select_max_value_min = 1
select_max_value_max = 25
Comment on lines +1235 to +1238
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

These are not yet documented in docs/enums.rst

select_options_max = 25
select_default_values_max = 25

# Select option constraints
select_option_label_max = 100
select_option_value_max = 100
select_option_description_max = 100

# Section constraints
section_accessory_max = 1
section_children_min = 1
section_children_max = 3

# TextInput constraints
text_input_max_count = 5
text_input_label_max = 45
text_input_placeholder_max = 100
text_input_min_length_min = 0
text_input_min_length_max = 4000
text_input_max_length_min = 1
text_input_max_length_max = 4000
text_input_value_max = 4000

# TextDisplay constraints
text_display_content_max = 4000

# Thumbnail constraints
thumbnail_description_max = 256

# Custom ID constraints
custom_id_min = 1
custom_id_max = 100

# RadioGroup constraints
radio_options_max = 10

# CheckboxGroup constraints
checkbox_options_max = 10
checkbox_min_values_min = 0
checkbox_min_values_max = 10
checkbox_max_values_min = 1
checkbox_max_values_max = 10

# FileUpload constraints
file_upload_min_files = 0
file_upload_max_files_max = 10
file_upload_max_values_min = 1

# Modal constraints
modal_title_max = 45
modal_rows_max = 5


class EmbedLimits(Enum):
# Embed field constraints
fields_max = 25

# Field title/name constraints
field_name_max = 256

# Field value constraints
field_value_max = 1024

# Embed description constraints
description_max = 4096

# Embed footer constraints
footer_text_max = 2048

# Embed author constraints
author_name_max = 256
title_max = 256
total_max = 6000


T = TypeVar("T")


Expand Down
29 changes: 20 additions & 9 deletions discord/ui/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from typing import TYPE_CHECKING, Callable, TypeVar

from ..components import Button as ButtonComponent
from ..enums import ButtonStyle, ComponentType
from ..enums import ButtonStyle, ComponentLimits, ComponentType
from ..partial_emoji import PartialEmoji, _EmojiTag
from .item import ItemCallbackType, ViewItem

Expand Down Expand Up @@ -113,10 +113,17 @@ def __init__(
self._row: int | None = None
self._rendered_row: int | None = None
super().__init__()
if label and len(str(label)) > 80:
raise ValueError("label must be 80 characters or fewer")
if custom_id is not None and len(str(custom_id)) > 100:
raise ValueError("custom_id must be 100 characters or fewer")
if label and len(str(label)) > ComponentLimits.button_label_max.value:
raise ValueError(
f"label must be {ComponentLimits.button_label_max.value} characters or fewer"
)
if (
custom_id is not None
and len(str(custom_id)) > ComponentLimits.custom_id_max.value
):
raise ValueError(
f"custom_id must be {ComponentLimits.custom_id_max.value} characters or fewer"
)
if custom_id is not None and url is not None:
raise TypeError("cannot mix both url and custom_id with Button")
if sku_id is not None and url is not None:
Expand Down Expand Up @@ -206,8 +213,10 @@ def custom_id(self) -> str | None:
def custom_id(self, value: str | None):
if value is not None and not isinstance(value, str):
raise TypeError("custom_id must be None or str")
if value and len(value) > 100:
raise ValueError("custom_id must be 100 characters or fewer")
if value and len(value) > ComponentLimits.custom_id_max.value:
raise ValueError(
f"custom_id must be {ComponentLimits.custom_id_max.value} characters or fewer"
)
self.underlying.custom_id = value
self._provided_custom_id = value is not None

Expand Down Expand Up @@ -238,8 +247,10 @@ def label(self) -> str | None:

@label.setter
def label(self, value: str | None):
if value and len(str(value)) > 80:
raise ValueError("label must be 80 characters or fewer")
if value and len(str(value)) > ComponentLimits.button_label_max.value:
raise ValueError(
f"label must be {ComponentLimits.button_label_max.value} characters or fewer"
)
self.underlying.label = str(value) if value is not None else value

@property
Expand Down
8 changes: 5 additions & 3 deletions discord/ui/checkbox.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 Checkbox as CheckboxComponent
from ..enums import ComponentType
from ..enums import ComponentLimits, ComponentType
from .item import ModalItem

__all__ = ("Checkbox",)
Expand Down Expand Up @@ -105,8 +105,10 @@ def custom_id(self) -> str:
def custom_id(self, value: str):
if not isinstance(value, str):
raise TypeError(f"custom_id must be str not {value.__class__.__name__}")
if len(value) > 100:
raise ValueError("custom_id must be 100 characters or fewer")
if len(value) > ComponentLimits.custom_id_max.value:
raise ValueError(
f"custom_id must be {ComponentLimits.custom_id_max.value} characters or fewer"
)
self.underlying.custom_id = value

@property
Expand Down
Loading
Loading