Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 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
eba1d9e
Merge branch 'master' into cv2_fixes
NeloBlivion Jan 19, 2026
4dccef7
Merge branch 'Pycord-Development:master' into cv2_fixes
NeloBlivion Jan 21, 2026
6e5507a
misc fixes
NeloBlivion Jan 23, 2026
01e95f8
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 23, 2026
0678073
fix
NeloBlivion Jan 29, 2026
2a4e4d5
adjust legacy item attributes
NeloBlivion Jan 30, 2026
71a0e81
Merge branch 'master' into cv2_fixes
NeloBlivion Jan 30, 2026
99ecd2c
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 30, 2026
23b7743
width docs adjustment
NeloBlivion Jan 30, 2026
fb2e439
Merge branch 'master' into cv2_fixes
NeloBlivion Feb 7, 2026
23be55b
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 7, 2026
7c69fb0
Merge branch 'master' into view_additions
NeloBlivion Feb 8, 2026
12caf98
Update CHANGELOG.md
NeloBlivion Feb 20, 2026
4b71a77
Merge branch 'master' into view_additions
NeloBlivion Feb 20, 2026
5d8609c
Merge branch 'master' into view_additions
NeloBlivion Feb 23, 2026
f12147d
Merge branch 'master' into view_additions
NeloBlivion Feb 26, 2026
db9a63c
Message.get_view
NeloBlivion Feb 26, 2026
66054a0
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 26, 2026
a985986
Merge branch 'master' into view_additions
Lulalaby Feb 28, 2026
2fb1ac5
Merge branch 'master' into view_additions
Paillat-dev Mar 2, 2026
d46f6e8
Merge branch 'master' into view_additions
NeloBlivion Mar 4, 2026
6d30016
adjust imports
NeloBlivion Mar 4, 2026
065e1a1
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 4, 2026
68e8252
sure whatever i hate this
NeloBlivion Mar 4, 2026
4c89957
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 4, 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
2 changes: 2 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 support for community invites.
([#3044](https://github.com/Pycord-Development/pycord/pull/3044))
- Added `Member.colours` and `Member.colors` properties.
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 @@ -27,6 +27,8 @@
import contextlib
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 @@ -914,6 +916,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
27 changes: 26 additions & 1 deletion discord/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
from .types.snowflake import SnowflakeList
from .types.threads import ThreadArchiveDuration
from .types.user import User as UserPayload
from .ui.view import BaseView
from .ui.view import BaseView, DesignerView
from .user import User

MR = TypeVar("MR", bound="MessageReference")
Expand Down Expand Up @@ -2345,6 +2345,31 @@ def get_component(self, id: str | int) -> Component | None:
return component
return None

def get_view(self, cls: BaseView | None = None) -> DesignerView | BaseView | None:
"""Retrieve this message's view from the ViewStore. If there is no stored view, a new view will be returned if :attr:`components` is not empty.

Parameters
----------
cls
The class that will be used to generate the new view.
By default, this is :class:`discord.ui.DesignerView`. Should a custom
class be provided, it must inherit from :class:`discord.ui.BaseView`
and properly implement ``from_message``.

Returns
-------
Optional[Union[:class:`discord.ui.DesignerView`, :class:`discord.ui.BaseView`]]
The view belonging to this message, if it exists or there are components available to create a new view.
"""
v = self._state.get_message_view(self.id)
if not v and self.components:
if not cls:
from .ui.view import DesignerView

cls = DesignerView
v = cls.from_message(self)
return v


class PartialMessage(Hashable):
"""Represents a partial message to aid with working messages when only
Expand Down
3 changes: 3 additions & 0 deletions discord/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,9 @@ 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 get_message_view(self, message_id: int) -> BaseView | None:
self._view_store.get_message_view(message_id)

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

Expand Down
30 changes: 30 additions & 0 deletions discord/ui/action_row.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,36 @@ def remove_item(self, item: ViewItem | str | int) -> Self:
pass
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
30 changes: 30 additions & 0 deletions discord/ui/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,36 @@ def remove_item(self, item: ViewItem | str | int) -> Self:
pass
return self

def replace_item(
self, original_item: ViewItem | str | int, new_item: ViewItem
) -> Self:
"""Directly replace an item in this container.
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 container.
new_item: :class:`ViewItem`
The new item to insert into the container.
"""

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 container.")
try:
if original_item.parent is self:
i = self.items.index(original_item)
new_item.parent = self
self.items[i] = new_item
original_item.parent = None
else:
original_item.parent.replace_item(original_item, new_item)
except ValueError:
raise ValueError(f"Could not find original_item in container.")
return self

def get_item(self, id: str | int) -> ViewItem | None:
"""Get an item from this container. Roughly equivalent to `utils.get(container.items, ...)`.
If an ``int`` is provided, the item will be retrieved by ``id``, otherwise by ``custom_id``.
Expand Down
57 changes: 48 additions & 9 deletions discord/ui/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import asyncio
import time
from itertools import groupby
from operator import attrgetter
from typing import TYPE_CHECKING, Any, Callable

from ..utils import find, get
Expand Down Expand Up @@ -115,30 +116,68 @@ def _dispatch_timeout(self):
def to_components(self) -> list[dict[str, Any]]:
return [item.to_component_dict() for item in self.children]

def get_item(self, custom_id: str | int) -> Item | None:
"""Gets an item from this structure. Roughly equal to `utils.get(self.children, ...)`.
def get_item(self, custom_id: str | int | None = None, **attrs: Any) -> Item | None:
r"""Gets an item from this structure. Roughly equal to `utils.get(self.children, **attrs)`.
If an :class:`int` is provided, the item will be retrieved by ``id``, otherwise by ``custom_id``.
This method will also search nested items.
If ``attrs`` are provided, it will check them by logical AND as done in :func:`~utils.get`.
To have a nested attribute search (i.e. search by ``x.y``) then pass in ``x__y`` as the keyword argument.

Examples
---------

Basic usage:

.. code-block:: python3

container = my_view.get(1234)

Attribute matching:

.. code-block:: python3

button = my_view.get(label='Click me!', style=discord.ButtonStyle.danger)

Parameters
----------
custom_id: Union[:class:`str`, :class:`int`]
custom_id: Optional[Union[:class:`str`, :class:`int`]]
The id of the item to get
\*\*attrs
Keyword arguments that denote attributes to search with.

Returns
-------
Optional[:class:`Item`]
The item with the matching ``custom_id`` or ``id`` if it exists.
The item with the matching ``custom_id``, ``id``, or ``attrs`` if it exists.
"""
if not custom_id:
if not (custom_id or attrs):
Copy link
Member

Choose a reason for hiding this comment

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

This could just be if, elif, else : return None and would be clearer I think ?

return None
attr = "id" if isinstance(custom_id, int) else "custom_id"
child = find(lambda i: getattr(i, attr, None) == custom_id, self.children)
if not child:
child = None
if custom_id:
attr = "id" if isinstance(custom_id, int) else "custom_id"
child = find(lambda i: getattr(i, attr, None) == custom_id, self.children)
if not child:
for i in self.children:
if hasattr(i, "get_item"):
if child := i.get_item(custom_id):
return child
elif attrs:
_all = all
attrget = attrgetter
for i in self.children:
converted = [
(attrget(attr.replace("__", ".")), value)
for attr, value in attrs.items()
]
try:
if _all(pred(i) == value for pred, value in converted):
return i
except:
pass
if hasattr(i, "get_item"):
if child := i.get_item(custom_id):
if child := i.get_item(custom_id, **attrs):
return child

return child

def add_item(self, item: Item) -> Self:
Expand Down
16 changes: 16 additions & 0 deletions discord/ui/media_gallery.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,22 @@ def remove_item(self, index: int) -> Self:
pass
return self

def replace_item(self, index: int, new_item: MediaGalleryItem) -> Self:
"""Directly replace an item in this gallery by index.

Parameters
----------
original_item: :class:`int`
The index of the item to replace in this gallery.
new_item: :class:`MediaGalleryItem`
The new item to insert into the gallery.
"""

if not isinstance(new_item, MediaGalleryItem):
raise TypeError(f"expected MediaGalleryItem not {new_item.__class__!r}")
self._underlying.items[index] = new_item
return self

def to_component_dict(self) -> MediaGalleryComponentPayload:
self._underlying = self._generate_underlying()
return super().to_component_dict()
Expand Down
33 changes: 33 additions & 0 deletions discord/ui/section.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,39 @@ def remove_item(self, item: ViewItem | str | int) -> Self:
pass
return self

def replace_item(
self, original_item: ViewItem | str | int, new_item: ViewItem
) -> Self:
"""Directly replace an item in this section.
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 section.
new_item: :class:`ViewItem`
The new item to insert into the section.
"""

if not isinstance(new_item, ViewItem):
raise TypeError(f"expected ViewItem 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 section.")
Copy link
Member

Choose a reason for hiding this comment

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

Albeit a bit weird, this error would also raise if a user (incorrectly) passes None or another falsy value as original_item. Would not be an issue to care about normally, really, but since we already verify the types for new_item we should probably for original_item ? Idk...

try:
if original_item is self.accessory:
self.accessory = new_item
else:
i = self.items.index(original_item)
self.items[i] = new_item
original_item.parent = None
new_item.parent = self
except ValueError:
raise ValueError(f"Could not find original_item in section.")
return self

def get_item(self, id: int | str) -> ViewItem | None:
"""Get an item from this section. Alias for `utils.get(section.walk_items(), ...)`.
If an ``int`` is provided, it will be retrieved by ``id``, otherwise it will check the accessory's ``custom_id``.
Expand Down
Loading