Skip to content

Instantly share code, notes, and snippets.

@Soheab
Last active October 9, 2025 21:55
Show Gist options
  • Select an option

  • Save Soheab/cfd769870b7b6eaf00a7aebf5293a622 to your computer and use it in GitHub Desktop.

Select an option

Save Soheab/cfd769870b7b6eaf00a7aebf5293a622 to your computer and use it in GitHub Desktop.
from typing import Any, Self, overload
import asyncio
import discord
HasChildren = (
discord.ui.Container
| discord.ui.ActionRow
| discord.ui.Section
| discord.ui.LayoutView
)
class GlobalView(discord.ui.LayoutView):
message: discord.Message | None = None
def __init__(
self,
*items: discord.ui.Item[Any],
author_id: int | None = None,
absolute_timeout: bool = False,
timeout: float = 180.0,
disable_items_on_timeout: bool = False,
) -> None:
self.author_id: int | None = author_id
self.disable_items_on_timeout = disable_items_on_timeout
# call the original __init__
# original handles the items and timeout
# so we don't need to do that ourselves.
# we are setting the timeout to None if absolute_timeout is True
# since we handle the timeout in that case.
super().__init__(timeout=timeout if not absolute_timeout else None)
# we allow passing items directly to the constructor,
# so we should add them to the view here.
for item in items:
self.add_item(item)
# author_id and disable_items_on_timeout are custom things.
# so we need to handle those, which we did above by setting the attributes.
# author_id is checked in interaction_check.
# disable_items_on_timeout is handled in on_timeout.
# absolute_timeout is also a custom thing.
# it's for stopping the view exactly at the timeout you set
# since the timeout resets on interaction.
# we handle that here.
# first check if there is even a timeout
# if not, we do nothing.
if timeout is not None and absolute_timeout:
loop = asyncio.get_event_loop()
# here we tell asyncio call to call View.stop and on_timeout after <timeout>
def call_later_callback():
# stop the view
self.stop()
# call on_timeout
loop.create_task(self.on_timeout())
loop.call_later(timeout, call_later_callback)
# property to get all textdisplay's content in the view
# similar to the "Copy Text" button in the client
@property
def content(self) -> str | None:
return "\n".join(
t.content
for t in self.walk_children()
if isinstance(t, discord.ui.TextDisplay)
)
# method to attach a message or interaction response
# to the view, so we can edit it later.
def attach_message(
self, message: discord.Message | discord.InteractionCallbackResponse
) -> None:
if isinstance(message, discord.InteractionCallbackResponse):
if not isinstance(message.resource, discord.Message):
raise TypeError(
"The provided InteractionCallbackResponse does not have a message."
)
self.message = message.resource
elif isinstance(message, discord.Message):
self.message = message
else:
raise TypeError("Expected Message or InteractionCallbackResponse")
# method to insert an item at a specific index in the view
def insert_item_at(self, index: int, /, item: discord.ui.Item) -> None:
self.insert_item_into(self, item, index=index)
@overload
def insert_item_into(
self,
target: discord.ui.MediaGallery,
item: discord.MediaGalleryItem,
*,
index: int | None = ...,
) -> Self: ...
@overload
def insert_item_into(
self,
target: HasChildren | discord.ui.LayoutView,
item: discord.ui.Item,
*,
index: int | None = ...,
) -> Self: ...
# method to insert an item at a specific index (optional) in a
# Container, ActionRow, Section, MediaGallery or LayoutView
def insert_item_into(
self,
target: HasChildren | discord.ui.MediaGallery | discord.ui.LayoutView,
item: discord.ui.Item | discord.MediaGalleryItem,
*,
index: int | None = None,
) -> Self:
is_media_gallery = isinstance(target, discord.ui.MediaGallery)
children = list(target.items if is_media_gallery else target.children)
index = (
max(0, min(index, len(children))) if index is not None else len(children)
)
children.insert(index, item)
target.clear_items()
for child in children:
if is_media_gallery:
target.add_item(media=child) # type: ignore
else:
target.add_item(item=child) # type: ignore
return self
# method that disables all childrens,
# including all nested.
# you still need to edit the message.
def disable_all_children(self):
for child in self.walk_children():
if hasattr(child, "disabled"):
child.disabled = True # type: ignore
# global view timeout handler
async def on_timeout(self):
# defining a list of Item: custom_id
# so we can know which items timed out
formatted_items: list[str] = [
f"{item}: {getattr(item, 'custom_id', None)}"
for item in self.walk_children()
]
print(
f"A timeout occurred in the following view: {self.__class__!r}\n",
f"Timed out with items: {', '.join(formatted_items)}",
)
# disable all items if the kwarg is set to True
if self.disable_items_on_timeout:
self.disable_all_children()
if self.message is not None:
await self.message.edit(view=self)
# global view error handler
async def on_error(
self, interaction: discord.Interaction, error: Exception, item: discord.ui.Item
) -> Any:
print(
f"An error occurred in the following view: {self.__class__!r}\n",
f"With item: {item!r}",
)
# sending a message to the user
await interaction.response.send_message(
f"An error occurred: {error}", ephemeral=True
)
# do original implementation (optional)
await super().on_error(interaction, error, item)
# global interaction (view) check
async def interaction_check(self, interaction: discord.Interaction) -> bool:
# send a message if the id of the interaction user
# and view owner don't match
if self.author_id and interaction.user.id != self.author_id:
await interaction.response.send_message("This is not your view!")
# return False so that the lib doesn't invoke the item callback.
return False
# else return True, we handled it above.
return True

How to use

Copy the GlobalView implementation into a file such as /utils/views/base.py, then import and subclass it where needed.

Example:

/base.py (paste the implementation):

# (paste GlobalView implementation here)

/something.py:

from base import GlobalView

class Something(GlobalView):
  ...

# sending
v = Something(123)  # 123 = user id
await some_channel.send(view=v)

Features

  • Built-in user-id check (optional)
  • Accept components directly in the constructor (pass items to GlobalView(...)).
  • Error handler
  • Timeout handler
  • disable_items_on_timeout option (default: False) — when enabled, the view disables children on timeout.
  • attach_message method to attach a discord.Message or InteractionCallbackResponse so the view can edit it on timeout.
  • disable_all_children() to disable nested children recursively.
  • Insert items at a specific index using insert_item_at and insert_item_into helpers.
  • Absolute view timeout support (schedules stop + on_timeout at the specified time).
  • Ability to get combined text content from all TextDisplays, like the "Copy Text" button in the client.

Attaching a message

You can attach a message to the view so it can edit it on timeout. Use attach_message(...) or set the message attribute directly.

Accepts either a discord.Message or an discord.InteractionCallbackResponse (the latter must contain a Message in resource).

When disable_items_on_timeout is True, the view will disable children on timeout and will edit this message.

Inserting items at a specific index

You can use insert_item_at(...) to insert into the view's top-level children, or insert_item_into(...) to insert into a nested target (ActionRow, Section, Container, MediaGallery).

  • insert_item_at(index: int, /, item: discord.ui.Item) -> Self

    • Inserts an item at the specified index in the view's top-level children.
    view.insert_item_at(0, discord.ui.TextDisplay("hello world"))
  • insert_item_into(target, *, index: int | None = None) -> Self

    • Inserts item into target's children list. The optional index argument controls the insertion point.
    • target can be a discord.ui.View, discord.ui.Container, discord.ui.Section, discord.ui.ActionRow, or discord.ui.MediaGallery.
    view.insert_item_into(section, discord.ui.TextDisplay("hello world"), index=0)

Changelog

  • October 9, 2025 — Added features and reliability improvements

    • Accept components directly in the constructor (pass items to GlobalView(...)).
    • Added assign_message to attach a discord.Message or InteractionCallbackResponse so the view can edit the message on timeout.
    • Added insert_item_at and insert_item_into helpers (with overloads) to insert items into Views, Containers, Sections, ActionRows, and MediaGallerys at specific indices.
    • Added disable_items_on_timeout option. When enabled the view will disable all children on timeout and automatically edit the attached message to persist the disabled state.
    • Added a content helper to get all TextDisplay content in the view.
    • Improved on_timeout and disable_all_children to walk nested children and safely disable items.
    • Improved on_error to notify the interaction user with an ephemeral message and then call the base handler.
  • August 25, 2025

    • Switched out ui.View for ui.LayoutView
    • Made author_id optional
    • Renamed disable_all_items() to disable_all_children()
  • October 3, 2022

    • Initial release
@tookender
Copy link

thank you soheab

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment