1
+ """
2
+ The MIT License (MIT)
3
+
4
+ Copyright (c) 2021-present Pycord Development
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a
7
+ copy of this software and associated documentation files (the "Software"),
8
+ to deal in the Software without restriction, including without limitation
9
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
10
+ and/or sell copies of the Software, and to permit persons to whom the
11
+ Software is furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22
+ DEALINGS IN THE SOFTWARE.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import asyncio
28
+ import os
29
+ from itertools import groupby
30
+ from typing import TYPE_CHECKING , Any , Callable , Iterator , TypeVar
31
+
32
+ from ..components import ActionRow as ActionRowComponent
33
+ from ..components import Button as ButtonComponent
34
+ from ..components import Component
35
+ from ..components import Container as ContainerComponent
36
+ from ..components import FileComponent
37
+ from ..components import Label as LabelComponent
38
+ from ..components import MediaGallery as MediaGalleryComponent
39
+ from ..components import Section as SectionComponent
40
+ from ..components import SelectMenu as SelectComponent
41
+ from ..components import Separator as SeparatorComponent
42
+ from ..components import TextDisplay as TextDisplayComponent
43
+ from ..components import Thumbnail as ThumbnailComponent
44
+ from ..components import _component_factory
45
+ from ..utils import find , get
46
+ from .action_row import ActionRow
47
+ from .item import Item , ItemCallbackType
48
+ from .view import View
49
+
50
+ __all__ = ("ComponentUI" )
51
+
52
+
53
+ if TYPE_CHECKING :
54
+ from typing_extensions import Self
55
+
56
+ from ..interactions import Interaction , InteractionMessage
57
+ from ..message import Message
58
+ from ..state import ConnectionState
59
+ from ..types .components import Component as ComponentPayload
60
+
61
+ class ComponentUI :
62
+ """The base structure for classes that contain :class:`~discord.ui.Item`.
63
+
64
+ .. versionadded:: 2.7
65
+
66
+ Parameters
67
+ ----------
68
+ *items: :class:`Item`
69
+ The initial items contained in this structure.
70
+ timeout: Optional[:class:`float`]
71
+ Timeout in seconds from last interaction with the UI before no longer accepting input. Defaults to 180.0.
72
+ If ``None`` then there is no timeout.
73
+
74
+ Attributes
75
+ ----------
76
+ timeout: Optional[:class:`float`]
77
+ Timeout from last interaction with the UI before no longer accepting input.
78
+ If ``None`` then there is no timeout.
79
+ children: List[:class:`Item`]
80
+ The list of children attached to this structure.
81
+ """
82
+
83
+ def __init__ (
84
+ self ,
85
+ * items : Item ,
86
+ timeout : float | None = 180.0 ,
87
+ ):
88
+ self .timeout = timeout
89
+ self .children : list [Item ] = []
90
+ for item in items :
91
+ self .add_item (item )
92
+
93
+ loop = asyncio .get_running_loop ()
94
+ self .__cancel_callback : Callable [[View ], None ] | None = None
95
+ self .__timeout_expiry : float | None = None
96
+ self .__timeout_task : asyncio .Task [None ] | None = None
97
+ self .__stopped : asyncio .Future [bool ] = loop .create_future ()
98
+
99
+ def __repr__ (self ) -> str :
100
+ return f"<{ self .__class__ .__name__ } timeout={ self .timeout } children={ len (self .children )} >"
101
+
102
+ async def __timeout_task_impl (self ) -> None :
103
+ while True :
104
+ # Guard just in case someone changes the value of the timeout at runtime
105
+ if self .timeout is None :
106
+ return
107
+
108
+ if self .__timeout_expiry is None :
109
+ return self ._dispatch_timeout ()
110
+
111
+ # Check if we've elapsed our currently set timeout
112
+ now = time .monotonic ()
113
+ if now >= self .__timeout_expiry :
114
+ return self ._dispatch_timeout ()
115
+
116
+ # Wait N seconds to see if timeout data has been refreshed
117
+ await asyncio .sleep (self .__timeout_expiry - now )
118
+
119
+ @property
120
+ def _expires_at (self ) -> float | None :
121
+ if self .timeout :
122
+ return time .monotonic () + self .timeout
123
+ return None
124
+
125
+ def _dispatch_timeout (self ):
126
+ raise NotImplementedError
127
+
128
+ def to_components (self ) -> list [dict [str , Any ]]:
129
+ return [item .to_component_dict () for item in self .children ]
130
+
131
+ def get_item (self , custom_id : str | int ) -> Item | None :
132
+ """Gets an item from this structure. Roughly equal to `utils.get(self.children, ...)`.
133
+ If an :class:`int` is provided, the item will be retrieved by ``id``, otherwise by ``custom_id``.
134
+ This method will also search nested items.
135
+
136
+ Parameters
137
+ ----------
138
+ custom_id: Union[:class:`str`, :class:`int`]
139
+ The id of the item to get
140
+
141
+ Returns
142
+ -------
143
+ Optional[:class:`Item`]
144
+ The item with the matching ``custom_id`` or ``id`` if it exists.
145
+ """
146
+ if not custom_id :
147
+ return None
148
+ attr = "id" if isinstance (custom_id , int ) else "custom_id"
149
+ child = find (lambda i : getattr (i , attr , None ) == custom_id , self .children )
150
+ if not child :
151
+ for i in self .children :
152
+ if hasattr (i , "get_item" ):
153
+ if child := i .get_item (custom_id ):
154
+ return child
155
+ return child
156
+
157
+ def add_item (self , item : Item ) -> Self :
158
+ raise NotImplementedError
159
+
160
+ def remove_item (self , item : Item ) -> Self :
161
+ raise NotImplementedError
162
+
163
+ def clear_items (self ) -> None :
164
+ raise NotImplementedError
165
+
166
+ async def on_timeout (self ) -> None :
167
+ """|coro|
168
+
169
+ A callback that is called when this structure's timeout elapses without being explicitly stopped.
170
+ """
0 commit comments