Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
c68ede8
introduce replace_item and some additional patches
NeloBlivion Dec 23, 2025
f757ac0
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 23, 2025
56b5b46
cls
NeloBlivion Dec 23, 2025
ec0ddfa
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 23, 2025
deeed74
rework underlying
NeloBlivion Dec 23, 2025
c085789
Merge branch 'master' into cv2_fixes
NeloBlivion Dec 23, 2025
130fab8
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 23, 2025
45da8fe
maybe fixed
NeloBlivion Dec 23, 2025
aadf0ed
,
NeloBlivion Dec 23, 2025
3289505
or
NeloBlivion Dec 23, 2025
d984274
spacing
NeloBlivion Dec 23, 2025
97db5d4
replace and remove on gallery
NeloBlivion Dec 23, 2025
3775262
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 23, 2025
a5d076c
index
NeloBlivion Dec 23, 2025
22aaeb1
select_type
NeloBlivion Dec 23, 2025
d06f364
row
NeloBlivion Dec 23, 2025
6906ae5
type
NeloBlivion Dec 23, 2025
d993efe
cl
NeloBlivion Dec 24, 2025
3fc8b2a
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 24, 2025
cb403d2
fix(actions): rework release workflow (#3034)
Lulalaby Dec 24, 2025
7d65cf4
Merge branch 'master' into cv2_fixes
Paillat-dev Dec 24, 2025
5bfee91
Merge branch 'master' into cv2_fixes
Lulalaby Dec 24, 2025
d16f857
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 24, 2025
8f048e1
Merge branch 'master' into cv2_fixes
Lulalaby Dec 24, 2025
1074d51
Merge branch 'master' into cv2_fixes
Lulalaby Dec 24, 2025
91390cb
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 24, 2025
2f48c7b
revert cl
NeloBlivion Dec 24, 2025
ea33a62
files
NeloBlivion Dec 24, 2025
2ba67c3
file again
NeloBlivion Dec 24, 2025
3c49f09
one more
NeloBlivion Dec 24, 2025
738b6ee
Merge branch 'master' into cv2_fixes
NeloBlivion Dec 24, 2025
1ae6835
cl
NeloBlivion Dec 24, 2025
eb9fa92
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 24, 2025
2488e63
buildout for new features & items aliases
NeloBlivion Dec 25, 2025
5ebdeaa
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 25, 2025
050f639
fix
NeloBlivion Dec 25, 2025
d831318
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 25, 2025
6eb6086
NeloBlivion Dec 25, 2025
6496c4c
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 25, 2025
de61d8e
fix modal typing
NeloBlivion Dec 29, 2025
718fe73
Merge branch 'master' into cv2_fixes
NeloBlivion Dec 29, 2025
bc785f4
Merge branch 'master' into cv2_fixes
NeloBlivion Dec 29, 2025
66a4db6
correct return types
NeloBlivion Dec 29, 2025
de42bb6
fix modal error docs
NeloBlivion Dec 29, 2025
c4000cb
remove incorrect release script
NeloBlivion Dec 29, 2025
2f3da73
Merge branch 'master' into cv2_fixes
NeloBlivion Jan 4, 2026
c7a3983
fix paginator
NeloBlivion Jan 4, 2026
5e02950
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 4, 2026
572fe5a
doc fix
NeloBlivion Jan 8, 2026
d9b9c1f
add convenience methods to DesignerView
NeloBlivion Jan 8, 2026
a054102
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 8, 2026
9adf32d
Merge branch 'master' into cv2_fixes
Dorukyum Jan 12, 2026
2728982
adjust underlying order
NeloBlivion Jan 17, 2026
534f743
fix fileupload
NeloBlivion Jan 17, 2026
7cf34bd
Merge branch 'master' into cv2_fixes
NeloBlivion Jan 17, 2026
9db2632
misc
NeloBlivion Jan 17, 2026
d6e287d
view.add_row
NeloBlivion Jan 17, 2026
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ These changes are available on the `master` branch, but have not yet been releas

### Added

- Added `replace_item` to `DesignerView`, `Section`, `Container`, `ActionRow`, &
`MediaGallery` ([#3032](https://github.com/Pycord-Development/pycord/pull/3032))
- Added `.extension` attribute to emojis to get their file extension.
([#3055](https://github.com/Pycord-Development/pycord/pull/3055))

Expand All @@ -22,6 +24,8 @@ These changes are available on the `master` branch, but have not yet been releas

### Fixed

- Fixed core issues with modifying items in `Container` and `Section`
([#3032](https://github.com/Pycord-Development/pycord/pull/3032))
- Fixed `RawMessageUpdateEvent.cached_message` being always `None` even when the message
was cached. ([#3038](https://github.com/Pycord-Development/pycord/pull/3038))
- Fixed downloading animated emojis which were originally uploaded as WebP files by
Expand Down
2 changes: 1 addition & 1 deletion discord/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ async def on_modal_error(self, error: Exception, interaction: Interaction) -> No
The default modal error handler provided by the client.
The default implementation prints the traceback to stderr.

This only fires for a modal if you did not define its :func:`~discord.ui.Modal.on_error`.
This only fires for a modal if you did not define its :func:`~discord.ui.BaseModal.on_error`.

Parameters
----------
Expand Down
12 changes: 12 additions & 0 deletions discord/colour.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,18 @@ def to_rgb(self) -> tuple[int, int, int]:
"""Returns an (r, g, b) tuple representing the colour."""
return self.r, self.g, self.b

@classmethod
def resolve_value(cls: type[CT], value: int | Colour | None) -> CT:
if value is None or isinstance(value, Colour):
return value
elif isinstance(value, int):
return cls(value=value)
else:
raise TypeError(
"Expected discord.Colour, int, or None but received"
f" {value.__class__.__name__} instead."
)

@classmethod
def from_rgb(cls: type[CT], r: int, g: int, b: int) -> CT:
"""Constructs a :class:`Colour` from an RGB tuple."""
Expand Down
2 changes: 1 addition & 1 deletion discord/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def _raw_construct(cls: type[C], **kwargs) -> C:
try:
value = kwargs[slot]
except KeyError:
pass
setattr(self, slot, None)
else:
setattr(self, slot, value)
return self
Expand Down
10 changes: 1 addition & 9 deletions discord/embeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,15 +522,7 @@ def colour(self) -> Colour | None:

@colour.setter
def colour(self, value: int | Colour | None): # type: ignore
if value is None or isinstance(value, Colour):
self._colour = value
elif isinstance(value, int):
self._colour = Colour(value=value)
else:
raise TypeError(
"Expected discord.Colour, int, or None but received"
f" {value.__class__.__name__} instead."
)
self._colour = Colour.resolve_value(value)

color = colour

Expand Down
8 changes: 8 additions & 0 deletions discord/ext/pages/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

from typing import List

from typing_extensions import Self

import discord
from discord.errors import DiscordException
from discord.ext.bridge import BridgeContext
Expand Down Expand Up @@ -911,6 +913,12 @@ def update_custom_view(self, custom_view: discord.ui.View):
for item in custom_view.children:
self.add_item(item)

def clear_items(self) -> Self:
# Necessary override due to behavior of Item.parent, see #3057
self.children.clear()
self._View__weights.clear()
return self

def get_page_group_content(self, page_group: PageGroup) -> list[Page]:
"""Returns a converted list of `Page` objects for the given page group based on the content of its pages."""
return [self.get_page_content(page) for page in page_group.pages]
Expand Down
10 changes: 5 additions & 5 deletions discord/interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
from .types.interactions import InteractionCallbackResponse, InteractionData
from .types.interactions import InteractionMetadata as InteractionMetadataPayload
from .types.interactions import MessageInteraction as MessageInteractionPayload
from .ui.modal import Modal
from .ui.modal import BaseModal
from .ui.view import BaseView

InteractionChannel = Union[
Expand Down Expand Up @@ -168,7 +168,7 @@ class Interaction:
The view that this interaction belongs to.

.. versionadded:: 2.7
modal: Optional[:class:`Modal`]
modal: Optional[:class:`BaseModal`]
The modal that this interaction belongs to.

.. versionadded:: 2.7
Expand Down Expand Up @@ -258,7 +258,7 @@ def _from_data(self, data: InteractionPayload):

self.command: ApplicationCommand | None = None
self.view: BaseView | None = None
self.modal: Modal | None = None
self.modal: BaseModal | None = None
self.attachment_size_limit: int = data.get("attachment_size_limit")

self.message: Message | None = None
Expand Down Expand Up @@ -1343,14 +1343,14 @@ async def send_autocomplete_result(
self._responded = True
await self._process_callback_response(callback_response)

async def send_modal(self, modal: Modal) -> Interaction:
async def send_modal(self, modal: BaseModal) -> Interaction:
"""|coro|
Responds to this interaction by sending a modal dialog.
This cannot be used to respond to another modal dialog submission.

Parameters
----------
modal: :class:`discord.ui.Modal`
modal: :class:`discord.ui.BaseModal`
The modal dialog to display to the user.

Raises
Expand Down
4 changes: 2 additions & 2 deletions discord/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
from .stage_instance import StageInstance
from .sticker import GuildSticker
from .threads import Thread, ThreadMember
from .ui.modal import Modal, ModalStore
from .ui.modal import BaseModal, ModalStore
from .ui.view import BaseView, ViewStore
from .user import ClientUser, User

Expand Down Expand Up @@ -413,7 +413,7 @@ def store_view(self, view: BaseView, message_id: int | None = None) -> None:
def purge_message_view(self, message_id: int) -> None:
self._view_store.remove_message_view(message_id)

def store_modal(self, modal: Modal, message_id: int) -> None:
def store_modal(self, modal: BaseModal, message_id: int) -> None:
self._modal_store.add_modal(modal, message_id)

def prevent_view_updates_for(self, message_id: int) -> BaseView | None:
Expand Down
67 changes: 56 additions & 11 deletions discord/ui/action_row.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,7 @@ def __init__(

self.children: list[ViewItem] = []

self._underlying = ActionRowComponent._raw_construct(
type=ComponentType.action_row,
id=id,
children=[],
)
self._underlying = self._generate_underlying(id=id)

for func in self.__row_children_items__:
item: ViewItem = func.__discord_ui_model_type__(
Expand All @@ -111,14 +107,33 @@ def __init__(
for i in items:
self.add_item(i)

@property
def items(self) -> list[ViewItem]:
return self.children

@items.setter
def items(self, value: list[ViewItem]) -> None:
self.children = value

def _add_component_from_item(self, item: ViewItem):
self._underlying.children.append(item._underlying)
self.underlying.children.append(item._generate_underlying())

def _set_components(self, items: list[ViewItem]):
self._underlying.children.clear()
self.underlying.children.clear()
for item in items:
self._add_component_from_item(item)

def _generate_underlying(self, id: int | None = None) -> ActionRowComponent:
super()._generate_underlying(ActionRowComponent)
row = ActionRowComponent._raw_construct(
type=ComponentType.action_row,
id=id or self.id,
children=[],
)
for i in self.children:
row.children.append(i._generate_underlying())
return row

def add_item(self, item: ViewItem) -> Self:
"""Adds an item to the action row.

Expand Down Expand Up @@ -164,6 +179,36 @@ def remove_item(self, item: ViewItem | str | int) -> Self:
item.parent = None
return self

def replace_item(
self, original_item: ViewItem | str | int, new_item: ViewItem
) -> Self:
"""Directly replace an item in this row.
If an :class:`int` is provided, the item will be replaced by ``id``, otherwise by ``custom_id``.

Parameters
----------
original_item: Union[:class:`ViewItem`, :class:`int`, :class:`str`]
The item, item ``id``, or item ``custom_id`` to replace in the row.
new_item: :class:`ViewItem`
The new item to insert into the row.
"""

if not isinstance(new_item, (Select, Button)):
raise TypeError(f"expected Select or Button, not {new_item.__class__!r}")

if isinstance(original_item, (str, int)):
original_item = self.get_item(original_item)
if not original_item:
raise ValueError(f"Could not find original_item in row.")
try:
i = self.children.index(original_item)
new_item.parent = self
self.children[i] = new_item
original_item.parent = None
except ValueError:
raise ValueError(f"Could not find original_item in row.")
return self

def get_item(self, id: str | int) -> ViewItem | None:
"""Get an item from this action row. Roughly equivalent to `utils.get(row.children, ...)`.
If an ``int`` is provided, the item will be retrieved by ``id``, otherwise by ``custom_id``.
Expand Down Expand Up @@ -296,7 +341,7 @@ def add_select(
id: int | None = None,
default_values: Sequence[SelectDefaultValue] | None = None,
) -> Self:
"""Adds a :class:`Select` to the container.
"""Adds a :class:`Select` to the action row.

To append a pre-existing :class:`Select`, use the
:meth:`add_item` method instead.
Expand Down Expand Up @@ -358,7 +403,7 @@ def is_persistent(self) -> bool:
return all(item.is_persistent() for item in self.children)

def refresh_component(self, component: ActionRowComponent) -> None:
self._underlying = component
self.underlying = component
for i, y in enumerate(component.components):
x = self.children[i]
x.refresh_component(y)
Expand Down Expand Up @@ -396,14 +441,14 @@ def width(self):
"""Return the sum of the items' widths."""
t = 0
for item in self.children:
t += 1 if item._underlying.type is ComponentType.button else 5
t += 1 if item.underlying.type is ComponentType.button else 5
return t

def walk_items(self) -> Iterator[ViewItem]:
yield from self.children

def to_component_dict(self) -> ActionRowPayload:
self._set_components(self.children)
self._underlying = self._generate_underlying()
return super().to_component_dict()

@classmethod
Expand Down
Loading