componentsv2 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- componentsv2/__init__.py +11 -0
- componentsv2/component_holder.py +212 -0
- componentsv2/components/__init__.py +0 -0
- componentsv2/components/action_row.py +44 -0
- componentsv2/components/base.py +19 -0
- componentsv2/components/button.py +79 -0
- componentsv2/components/checkbox.py +62 -0
- componentsv2/components/file_upload.py +59 -0
- componentsv2/components/gallery.py +24 -0
- componentsv2/components/holders/__init__.py +0 -0
- componentsv2/components/holders/container.py +55 -0
- componentsv2/components/holders/label.py +19 -0
- componentsv2/components/holders/nestable.py +22 -0
- componentsv2/components/holders/section.py +20 -0
- componentsv2/components/media.py +44 -0
- componentsv2/components/radio_group.py +35 -0
- componentsv2/components/select.py +302 -0
- componentsv2/components/separator.py +21 -0
- componentsv2/components/text_display.py +16 -0
- componentsv2/components/text_input.py +31 -0
- componentsv2/components.py +20 -0
- componentsv2/utils/__init__.py +0 -0
- componentsv2/utils/serialize.py +8 -0
- componentsv2/wrapper.py +103 -0
- componentsv2-0.2.2.dist-info/METADATA +6 -0
- componentsv2-0.2.2.dist-info/RECORD +29 -0
- componentsv2-0.2.2.dist-info/WHEEL +5 -0
- componentsv2-0.2.2.dist-info/licenses/LICENSE.txt +21 -0
- componentsv2-0.2.2.dist-info/top_level.txt +1 -0
componentsv2/__init__.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
from .components.base import ComponentV2
|
|
2
|
+
from .components.button import ButtonV2
|
|
3
|
+
from .components.action_row import ActionRow
|
|
4
|
+
from .components.select import SelectObject
|
|
5
|
+
|
|
6
|
+
import copy
|
|
7
|
+
|
|
8
|
+
class ComponentHolder():
|
|
9
|
+
def __init_subclass__(cls):
|
|
10
|
+
children: list[ComponentV2] = []
|
|
11
|
+
|
|
12
|
+
for base in reversed(cls.__mro__):
|
|
13
|
+
children.extend(
|
|
14
|
+
member
|
|
15
|
+
for member in base.__dict__.values()
|
|
16
|
+
if hasattr(member, "__discord_ui_model_type__")
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
if len(children) > 25:
|
|
20
|
+
raise TypeError("Component Holders can only contain 25 components!")
|
|
21
|
+
|
|
22
|
+
cls.children = children
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.children = copy.deepcopy(self.children)
|
|
26
|
+
rows: list[ActionRow] = []
|
|
27
|
+
|
|
28
|
+
for child in self.children:
|
|
29
|
+
if hasattr(child, "parent_holder") == False:
|
|
30
|
+
child.parent_holder = self
|
|
31
|
+
|
|
32
|
+
if isinstance(child, ActionRow):
|
|
33
|
+
rows.append(child)
|
|
34
|
+
|
|
35
|
+
self.rows = rows
|
|
36
|
+
|
|
37
|
+
for child in self.children:
|
|
38
|
+
if isinstance(child, ButtonV2) or isinstance(child, SelectObject): # switch StringSelect to general Select object
|
|
39
|
+
self.append_to_row(child.row, child)
|
|
40
|
+
|
|
41
|
+
def __rec_add_components(self, component: ComponentV2, initial: bool):
|
|
42
|
+
if hasattr(component, "row"):
|
|
43
|
+
self.append_to_row(component.row, component)
|
|
44
|
+
|
|
45
|
+
if hasattr(component, "parent_holder") == False:
|
|
46
|
+
component.parent_holder = self
|
|
47
|
+
elif isinstance(component.parent_holder, ComponentHolder):
|
|
48
|
+
raise TypeError("Cannot re-assign components!")
|
|
49
|
+
|
|
50
|
+
if hasattr(component, "accessory") == True:
|
|
51
|
+
component.accessory.__row_child__ = True
|
|
52
|
+
self.children.append(component.accessory)
|
|
53
|
+
|
|
54
|
+
if initial == False:
|
|
55
|
+
component.__row_child__ = True
|
|
56
|
+
|
|
57
|
+
self.children.append(component)
|
|
58
|
+
|
|
59
|
+
if hasattr(component, "components") == True:
|
|
60
|
+
for child in component.components:
|
|
61
|
+
self.__rec_add_components(child, False)
|
|
62
|
+
|
|
63
|
+
def add_component(self, component: ComponentV2):
|
|
64
|
+
self.__rec_add_components(component, True)
|
|
65
|
+
return self
|
|
66
|
+
|
|
67
|
+
def add_components(self, *args: ComponentV2):
|
|
68
|
+
components = list(args)
|
|
69
|
+
|
|
70
|
+
for component in components:
|
|
71
|
+
self.__rec_add_components(component, True)
|
|
72
|
+
|
|
73
|
+
def append_to_row(self, row: int, component: ComponentV2):
|
|
74
|
+
if row < len(self.rows):
|
|
75
|
+
action_row = self.rows[row]
|
|
76
|
+
elif row == len(self.rows):
|
|
77
|
+
action_row = ActionRow()
|
|
78
|
+
self.rows.append(action_row)
|
|
79
|
+
self.children.append(action_row)
|
|
80
|
+
else:
|
|
81
|
+
raise IndexError("New rows must be created sequentially (cannot skip rows).")
|
|
82
|
+
|
|
83
|
+
component.__row_child__ = True
|
|
84
|
+
action_row.append_component(component)
|
|
85
|
+
|
|
86
|
+
def serialize(self):
|
|
87
|
+
components = []
|
|
88
|
+
|
|
89
|
+
for child in self.children:
|
|
90
|
+
if hasattr(child, "__row_child__") == True:
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
components.append(child.serialize())
|
|
94
|
+
|
|
95
|
+
return components
|
|
96
|
+
|
|
97
|
+
def get_component(self, custom_id: str):
|
|
98
|
+
for child in self.children:
|
|
99
|
+
if getattr(child, "custom_id", None) == custom_id:
|
|
100
|
+
return child
|
|
101
|
+
|
|
102
|
+
class ModalV2():
|
|
103
|
+
def __init__(self, custom_id: str, title: str):
|
|
104
|
+
if custom_id == None:
|
|
105
|
+
raise ValueError("With this wrapper, custom_ids for modals are required!")
|
|
106
|
+
|
|
107
|
+
self.custom_id = custom_id
|
|
108
|
+
self.title = title
|
|
109
|
+
|
|
110
|
+
self.children = []
|
|
111
|
+
|
|
112
|
+
async def __handle_component_submitted(self, sc: dict[str]):
|
|
113
|
+
if custom_id := sc.get("custom_id"):
|
|
114
|
+
component = self.get_component(custom_id)
|
|
115
|
+
|
|
116
|
+
if component is not None:
|
|
117
|
+
if values := sc.get("values"):
|
|
118
|
+
component.values = values
|
|
119
|
+
else:
|
|
120
|
+
values = None
|
|
121
|
+
|
|
122
|
+
if (value := sc.get("value")) is not None:
|
|
123
|
+
component.value = value
|
|
124
|
+
elif values:
|
|
125
|
+
component.value = values[0]
|
|
126
|
+
|
|
127
|
+
if component := sc.get("component"):
|
|
128
|
+
await self.__handle_component_submitted(component)
|
|
129
|
+
|
|
130
|
+
if components := sc.get("components"):
|
|
131
|
+
for child in components:
|
|
132
|
+
await self.__handle_component_submitted(child)
|
|
133
|
+
|
|
134
|
+
async def submitted(self, interaction, ser_components: list[dict[str]], initial: bool = True):
|
|
135
|
+
for sc in ser_components:
|
|
136
|
+
await self.__handle_component_submitted(sc)
|
|
137
|
+
|
|
138
|
+
if initial == True:
|
|
139
|
+
await self.on_form_submit(interaction)
|
|
140
|
+
|
|
141
|
+
async def on_form_submit(self, interaction):
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
def __rec_add_components(self, component: ComponentV2, initial: bool):
|
|
145
|
+
if hasattr(component, "parent_holder") == False:
|
|
146
|
+
component.parent_holder = self
|
|
147
|
+
elif isinstance(component.parent_holder, ModalV2):
|
|
148
|
+
raise TypeError("Cannot re-assign components!")
|
|
149
|
+
|
|
150
|
+
if hasattr(component, "accessory") == True:
|
|
151
|
+
component.accessory.__row_child__ = True
|
|
152
|
+
self.children.append(component.accessory)
|
|
153
|
+
|
|
154
|
+
if initial == False:
|
|
155
|
+
component.__row_child__ = True
|
|
156
|
+
|
|
157
|
+
self.children.append(component)
|
|
158
|
+
|
|
159
|
+
if hasattr(component, "components") == True:
|
|
160
|
+
for child in component.components:
|
|
161
|
+
self.__rec_add_components(child, False)
|
|
162
|
+
|
|
163
|
+
if hasattr(component, "component") == True:
|
|
164
|
+
self.__rec_add_components(component.component, False)
|
|
165
|
+
|
|
166
|
+
def add_component(self, component: ComponentV2):
|
|
167
|
+
self.__rec_add_components(component, True)
|
|
168
|
+
return self
|
|
169
|
+
|
|
170
|
+
def add_components(self, *args: ComponentV2):
|
|
171
|
+
components = list(args)
|
|
172
|
+
|
|
173
|
+
for component in components:
|
|
174
|
+
self.__rec_add_components(component, True)
|
|
175
|
+
|
|
176
|
+
def serialize(self):
|
|
177
|
+
components = []
|
|
178
|
+
|
|
179
|
+
for child in self.children:
|
|
180
|
+
if hasattr(child, "__row_child__") == True:
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
components.append(child.serialize())
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
"type": 9,
|
|
187
|
+
"data": {
|
|
188
|
+
"custom_id": self.custom_id,
|
|
189
|
+
"title": self.title,
|
|
190
|
+
"components": components
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
def get_component(self, custom_id: str):
|
|
195
|
+
if custom_id == None:
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
for child in self.children:
|
|
199
|
+
if getattr(child, "custom_id", None) == custom_id:
|
|
200
|
+
return child
|
|
201
|
+
|
|
202
|
+
def get_components(self) -> list[ComponentV2]:
|
|
203
|
+
components = []
|
|
204
|
+
|
|
205
|
+
for child in self.children:
|
|
206
|
+
if hasattr(child, "__row_child__") == True:
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
components.append(child)
|
|
210
|
+
|
|
211
|
+
return components
|
|
212
|
+
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from .base import ComponentV2
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
class ActionRow(ComponentV2):
|
|
5
|
+
types_to_max = {
|
|
6
|
+
2: 5,
|
|
7
|
+
|
|
8
|
+
3: 1,
|
|
9
|
+
5: 1,
|
|
10
|
+
6: 1,
|
|
11
|
+
7: 1,
|
|
12
|
+
8: 1,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
def __init__(self, id: Optional[int] = None):
|
|
16
|
+
super().__init__(1, id)
|
|
17
|
+
|
|
18
|
+
self.component_type: Optional[int] = None
|
|
19
|
+
self.components: list[ComponentV2] = []
|
|
20
|
+
|
|
21
|
+
self.__container_compatible__ = True
|
|
22
|
+
|
|
23
|
+
def serialize(self) -> dict:
|
|
24
|
+
base_dict: dict = super(ActionRow, self).serialize()
|
|
25
|
+
component_array = []
|
|
26
|
+
|
|
27
|
+
for component in self.components:
|
|
28
|
+
component_array.append(component.serialize())
|
|
29
|
+
|
|
30
|
+
base_dict["components"] = component_array
|
|
31
|
+
return base_dict
|
|
32
|
+
|
|
33
|
+
def append_component(self, component: ComponentV2):
|
|
34
|
+
if self.component_type != None and component.type != self.component_type:
|
|
35
|
+
raise TypeError("Cannot assign multiple different types to one action row.")
|
|
36
|
+
|
|
37
|
+
max = self.types_to_max[component.type]
|
|
38
|
+
if max == None:
|
|
39
|
+
raise TypeError(f"Component type {component.type} not accepted in an action row!")
|
|
40
|
+
|
|
41
|
+
if len(self.components) >= max:
|
|
42
|
+
raise ValueError(f"Cannot add more components to this action row! Max: {max}")
|
|
43
|
+
|
|
44
|
+
self.components.append(component)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
class ComponentV2():
|
|
4
|
+
def __init__(self, type: int, id: Optional[int] = None):
|
|
5
|
+
self.type = type
|
|
6
|
+
self.id = id
|
|
7
|
+
|
|
8
|
+
self.value = None
|
|
9
|
+
self.values = None
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def __discord_ui_model_type__(self):
|
|
13
|
+
return True
|
|
14
|
+
|
|
15
|
+
def serialize(self) -> dict:
|
|
16
|
+
return {
|
|
17
|
+
"type": self.type,
|
|
18
|
+
"id": self.id,
|
|
19
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import nextcord
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from .base import ComponentV2
|
|
5
|
+
|
|
6
|
+
from ..utils.serialize import serialize_emoji
|
|
7
|
+
|
|
8
|
+
class ButtonV2(ComponentV2):
|
|
9
|
+
def __init__(self, style: nextcord.ButtonStyle, label: Optional[str] = None, custom_id: Optional[str] = None, url: Optional[str] = None, disabled: Optional[bool] = False, emoji: Optional[nextcord.PartialEmoji] = None, row: Optional[int] = 1, id: Optional[int] = None):
|
|
10
|
+
if style != nextcord.ButtonStyle.link and custom_id == None:
|
|
11
|
+
raise TypeError("Non-link buttons must have a custom_id attribute!")
|
|
12
|
+
|
|
13
|
+
if style == nextcord.ButtonStyle.link and (custom_id != None or url == None):
|
|
14
|
+
raise TypeError("Link buttons cannot have a custom_id and must have a url attribute!")
|
|
15
|
+
|
|
16
|
+
super().__init__(2, id)
|
|
17
|
+
|
|
18
|
+
self.row = row
|
|
19
|
+
|
|
20
|
+
self.label = label
|
|
21
|
+
self.emoji = emoji
|
|
22
|
+
|
|
23
|
+
self.custom_id = custom_id
|
|
24
|
+
self.url = url
|
|
25
|
+
|
|
26
|
+
self.style = style.value
|
|
27
|
+
|
|
28
|
+
self.disabled = disabled
|
|
29
|
+
|
|
30
|
+
self.callback = None
|
|
31
|
+
self.registered = False
|
|
32
|
+
|
|
33
|
+
def __call__(self, func):
|
|
34
|
+
if self.registered == True:
|
|
35
|
+
raise ValueError("Buttons cannot be registered to multiple callbacks!")
|
|
36
|
+
|
|
37
|
+
self.callback = func
|
|
38
|
+
self.registered = True
|
|
39
|
+
|
|
40
|
+
return self
|
|
41
|
+
|
|
42
|
+
def serialize(self):
|
|
43
|
+
base_dict: dict = super(ButtonV2, self).serialize()
|
|
44
|
+
|
|
45
|
+
base_dict["label"] = self.label
|
|
46
|
+
|
|
47
|
+
base_dict["custom_id"] = self.custom_id
|
|
48
|
+
base_dict["url"] = self.url
|
|
49
|
+
|
|
50
|
+
base_dict["style"] = self.style
|
|
51
|
+
|
|
52
|
+
base_dict["disabled"] = self.disabled
|
|
53
|
+
|
|
54
|
+
if self.emoji != None:
|
|
55
|
+
base_dict["emoji"] = serialize_emoji(self.emoji)
|
|
56
|
+
|
|
57
|
+
return base_dict
|
|
58
|
+
|
|
59
|
+
async def activated(self, interaction):
|
|
60
|
+
if self.disabled == True:
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
if self.registered == False:
|
|
64
|
+
raise ValueError("Attempted to activate button, but it is not registered to a callback!")
|
|
65
|
+
|
|
66
|
+
await self.callback(self.parent_holder, self, interaction)
|
|
67
|
+
|
|
68
|
+
class UIButtonV2(ButtonV2):
|
|
69
|
+
def __init__(self, style, label = None, custom_id = None, url = None, disabled = False, emoji = None, row = 1, id = None):
|
|
70
|
+
super().__init__(style, label, custom_id, url, disabled, emoji, row, id)
|
|
71
|
+
|
|
72
|
+
async def activated(self, interaction):
|
|
73
|
+
if self.disabled == True:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
await self.ui_callback(interaction)
|
|
77
|
+
|
|
78
|
+
async def ui_callback(self, interaction):
|
|
79
|
+
pass
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from .base import ComponentV2
|
|
2
|
+
|
|
3
|
+
class Checkbox(ComponentV2):
|
|
4
|
+
def __init__(self, custom_id: str, default: bool = None, id = None):
|
|
5
|
+
super().__init__(23, id)
|
|
6
|
+
|
|
7
|
+
self.custom_id = custom_id
|
|
8
|
+
self.default = default
|
|
9
|
+
|
|
10
|
+
def serialize(self):
|
|
11
|
+
base_dict = super().serialize()
|
|
12
|
+
|
|
13
|
+
base_dict["custom_id"] = self.custom_id
|
|
14
|
+
base_dict["default"] = self.default
|
|
15
|
+
|
|
16
|
+
return base_dict
|
|
17
|
+
|
|
18
|
+
class CheckboxGroup(ComponentV2):
|
|
19
|
+
class Option():
|
|
20
|
+
def __init__(self, value: str, label: str, description: str = None, default: bool = None):
|
|
21
|
+
self.value = value
|
|
22
|
+
self.label = label
|
|
23
|
+
|
|
24
|
+
self.description = description
|
|
25
|
+
self.default = default
|
|
26
|
+
|
|
27
|
+
def serialize(self):
|
|
28
|
+
return {
|
|
29
|
+
"value": self.value,
|
|
30
|
+
"label": self.label,
|
|
31
|
+
|
|
32
|
+
"description": self.description,
|
|
33
|
+
"default": self.default,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
def __init__(self, custom_id: str, options: list[Option], min_values: int = None, max_values: int = None, required: bool = None, id = None):
|
|
37
|
+
super().__init__(22, id)
|
|
38
|
+
|
|
39
|
+
if required == True and min_values and min_values < 1:
|
|
40
|
+
raise ValueError("min_values must be ommited or >= 1 when required is omitted or True!")
|
|
41
|
+
|
|
42
|
+
self.custom_id = custom_id
|
|
43
|
+
self.options = options
|
|
44
|
+
self.min_values = min_values
|
|
45
|
+
self.max_values = max_values
|
|
46
|
+
self.required = required
|
|
47
|
+
|
|
48
|
+
def serialize(self):
|
|
49
|
+
base_dict = super().serialize()
|
|
50
|
+
|
|
51
|
+
checkbox_options = []
|
|
52
|
+
|
|
53
|
+
for option in self.options:
|
|
54
|
+
checkbox_options.append(option.serialize())
|
|
55
|
+
|
|
56
|
+
base_dict["custom_id"] = self.custom_id
|
|
57
|
+
base_dict["options"] = checkbox_options
|
|
58
|
+
base_dict["min_values"] = self.min_values
|
|
59
|
+
base_dict["max_values"] = self.max_values
|
|
60
|
+
base_dict["required"] = self.required
|
|
61
|
+
|
|
62
|
+
return base_dict
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import nextcord
|
|
2
|
+
from nextcord import Interaction
|
|
3
|
+
|
|
4
|
+
from .base import ComponentV2
|
|
5
|
+
from .gallery import MediaGallery as Gallery
|
|
6
|
+
from .media import MediaItem
|
|
7
|
+
|
|
8
|
+
def get_files(self, interaction, files: list[str]) -> list[MediaItem]: ## works for all files
|
|
9
|
+
resolved = interaction.data.get("resolved")
|
|
10
|
+
attachments: dict[str] = resolved.get("attachments")
|
|
11
|
+
|
|
12
|
+
links = []
|
|
13
|
+
for id in files:
|
|
14
|
+
data = attachments.get(id)
|
|
15
|
+
if data:
|
|
16
|
+
links.append(data.get("url"))
|
|
17
|
+
|
|
18
|
+
media = []
|
|
19
|
+
|
|
20
|
+
for link in links:
|
|
21
|
+
item = MediaItem(
|
|
22
|
+
nextcord.UnfurledMedia(link)
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
media.append(item)
|
|
26
|
+
|
|
27
|
+
return media
|
|
28
|
+
|
|
29
|
+
def get_gallery(self, interaction: Interaction, files: list[str]) -> Gallery: ## this only works for images
|
|
30
|
+
media = get_files(self, interaction, files)
|
|
31
|
+
return Gallery(media)
|
|
32
|
+
|
|
33
|
+
class FileUpload(ComponentV2):
|
|
34
|
+
def __init__(self, custom_id: str, min_values: int = None, max_values: int = None, required: bool = True, id = None):
|
|
35
|
+
if min_values and (min_values < 0 or min_values > 10):
|
|
36
|
+
raise ValueError("min_values must be between 0-10.")
|
|
37
|
+
|
|
38
|
+
if max_values and (max_values < 1 or max_values > 10):
|
|
39
|
+
raise ValueError("max_values must be between 1-10.")
|
|
40
|
+
|
|
41
|
+
if required == True and min_values and min_values < 1:
|
|
42
|
+
raise ValueError("min_values must be ommited or >= 1 when required is omitted or True!")
|
|
43
|
+
|
|
44
|
+
super().__init__(19, id)
|
|
45
|
+
|
|
46
|
+
self.custom_id = custom_id
|
|
47
|
+
self.min_values = min_values
|
|
48
|
+
self.max_values = max_values
|
|
49
|
+
self.required = required
|
|
50
|
+
|
|
51
|
+
def serialize(self):
|
|
52
|
+
base_dict = super().serialize()
|
|
53
|
+
|
|
54
|
+
base_dict["min_values"] = self.min_values
|
|
55
|
+
base_dict["max_values"] = self.max_values
|
|
56
|
+
base_dict["custom_id"] = self.custom_id
|
|
57
|
+
base_dict["required"] = self.required
|
|
58
|
+
|
|
59
|
+
return base_dict
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from nextcord import UnfurledMedia
|
|
2
|
+
from .base import ComponentV2
|
|
3
|
+
from.media import MediaItem
|
|
4
|
+
|
|
5
|
+
class MediaGallery(ComponentV2):
|
|
6
|
+
def __init__(self, items: list[MediaItem], id = None):
|
|
7
|
+
if len(items) < 1 or len(items) > 10:
|
|
8
|
+
raise ValueError("List of Media Items must be within the range 1-10.")
|
|
9
|
+
|
|
10
|
+
super().__init__(12, id)
|
|
11
|
+
self.items = items
|
|
12
|
+
|
|
13
|
+
self.__container_compatible__ = True
|
|
14
|
+
|
|
15
|
+
def serialize(self):
|
|
16
|
+
base_dict = super().serialize()
|
|
17
|
+
|
|
18
|
+
items = []
|
|
19
|
+
for media in self.items:
|
|
20
|
+
items.append(media.serialize())
|
|
21
|
+
|
|
22
|
+
base_dict["items"] = items
|
|
23
|
+
|
|
24
|
+
return base_dict
|
|
File without changes
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from .nestable import Nestable
|
|
2
|
+
from ..base import ComponentV2
|
|
3
|
+
|
|
4
|
+
from ..action_row import ActionRow
|
|
5
|
+
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
class Container(Nestable):
|
|
9
|
+
def __init__(self, components: list[ComponentV2] = [], accent_color: int = None, spoiler: bool = None, id: Optional[int] = None):
|
|
10
|
+
for component in components:
|
|
11
|
+
if hasattr(component, "__container_compatible__") == False:
|
|
12
|
+
raise TypeError(f"Component {component.__class__.__name__} not accepted in Containers!")
|
|
13
|
+
|
|
14
|
+
super().__init__(17, id, components)
|
|
15
|
+
|
|
16
|
+
self.accent_color = accent_color
|
|
17
|
+
self.spoiler = spoiler
|
|
18
|
+
|
|
19
|
+
def get_row(self, row: int) -> tuple[int, ActionRow]:
|
|
20
|
+
total_rows = -1
|
|
21
|
+
|
|
22
|
+
for component in self.components:
|
|
23
|
+
if isinstance(component, ActionRow) and total_rows == row:
|
|
24
|
+
return total_rows, component
|
|
25
|
+
elif isinstance(component, ActionRow):
|
|
26
|
+
total_rows += 1
|
|
27
|
+
|
|
28
|
+
return total_rows, None
|
|
29
|
+
|
|
30
|
+
def append_component(self, component: ComponentV2): # chained
|
|
31
|
+
if hasattr(component, "row"):
|
|
32
|
+
row, action_row = self.get_row(component.row)
|
|
33
|
+
|
|
34
|
+
if action_row != None:
|
|
35
|
+
action_row.append_component(component)
|
|
36
|
+
return self
|
|
37
|
+
elif row + 1 == component.row:
|
|
38
|
+
action_row = ActionRow()
|
|
39
|
+
action_row.append_component(component)
|
|
40
|
+
|
|
41
|
+
return self.append_component(action_row)
|
|
42
|
+
else:
|
|
43
|
+
IndexError("Action rows must be created sequentially!")
|
|
44
|
+
|
|
45
|
+
self.components.append(component)
|
|
46
|
+
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
def serialize(self):
|
|
50
|
+
base_dict = super().serialize()
|
|
51
|
+
|
|
52
|
+
base_dict["accent_color"] = self.accent_color
|
|
53
|
+
base_dict["spoiler"] = self.spoiler
|
|
54
|
+
|
|
55
|
+
return base_dict
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ..base import ComponentV2
|
|
2
|
+
|
|
3
|
+
class Label(ComponentV2):
|
|
4
|
+
def __init__(self, label: str, component: ComponentV2, description: str = None, id: int = None):
|
|
5
|
+
super().__init__(18, id)
|
|
6
|
+
|
|
7
|
+
self.label = label
|
|
8
|
+
|
|
9
|
+
self.component = component
|
|
10
|
+
self.description = description
|
|
11
|
+
|
|
12
|
+
def serialize(self):
|
|
13
|
+
base_dict = super().serialize()
|
|
14
|
+
|
|
15
|
+
base_dict["label"] = self.label
|
|
16
|
+
base_dict["component"] = self.component.serialize()
|
|
17
|
+
base_dict["description"] = self.description
|
|
18
|
+
|
|
19
|
+
return base_dict
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from ..base import ComponentV2
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
class Nestable(ComponentV2): ## a 'Nestable' base class is able to nest other components inside of itself. Action rows are intentionally left out of this class, as it exclusive for ComponentsV2.
|
|
5
|
+
def __init__(self, type: int, id: Optional[int] = None, components: list[ComponentV2] = []):
|
|
6
|
+
super().__init__(type, id)
|
|
7
|
+
|
|
8
|
+
self.components = components
|
|
9
|
+
|
|
10
|
+
def to_component_list(self):
|
|
11
|
+
components = []
|
|
12
|
+
|
|
13
|
+
for component in self.components:
|
|
14
|
+
components.append(component.serialize())
|
|
15
|
+
|
|
16
|
+
return components
|
|
17
|
+
|
|
18
|
+
def serialize(self):
|
|
19
|
+
base_dict = super().serialize()
|
|
20
|
+
base_dict["components"] = self.to_component_list()
|
|
21
|
+
|
|
22
|
+
return base_dict
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .nestable import Nestable
|
|
2
|
+
from ..base import ComponentV2
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
class Section(Nestable):
|
|
7
|
+
def __init__(self, components: list[ComponentV2], accessory: ComponentV2, id: Optional[int] = None):
|
|
8
|
+
if len(components) < 1 or len(components) > 3:
|
|
9
|
+
raise ValueError("Section can only take 1-3 components and cannot have an empty components table!")
|
|
10
|
+
|
|
11
|
+
super().__init__(9, id, components)
|
|
12
|
+
self.accessory = accessory
|
|
13
|
+
|
|
14
|
+
self.__container_compatible__ = True
|
|
15
|
+
|
|
16
|
+
def serialize(self):
|
|
17
|
+
base_dict = super().serialize()
|
|
18
|
+
base_dict["accessory"] = self.accessory.serialize()
|
|
19
|
+
|
|
20
|
+
return base_dict
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from nextcord import UnfurledMedia
|
|
2
|
+
|
|
3
|
+
from .base import ComponentV2
|
|
4
|
+
|
|
5
|
+
class MediaItem():
|
|
6
|
+
def __init__(self, media: UnfurledMedia, description: str = None, spoiler: bool = None):
|
|
7
|
+
self.media = media
|
|
8
|
+
self.description = description
|
|
9
|
+
self.spoiler = spoiler
|
|
10
|
+
|
|
11
|
+
def serialize(self):
|
|
12
|
+
return {
|
|
13
|
+
"media": self.media.to_dict(),
|
|
14
|
+
"description": self.description,
|
|
15
|
+
"spoiler": self.spoiler,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class Thumbnail(ComponentV2, MediaItem):
|
|
19
|
+
def __init__(self, media: UnfurledMedia, description: str = None, spoiler: bool = None, id: int = None):
|
|
20
|
+
ComponentV2.__init__(self, 11, id)
|
|
21
|
+
MediaItem.__init__(self, media, description, spoiler)
|
|
22
|
+
|
|
23
|
+
def serialize(self):
|
|
24
|
+
base_dict = super().serialize()
|
|
25
|
+
|
|
26
|
+
base_dict["media"] = self.media.to_dict()
|
|
27
|
+
base_dict["description"] = self.description
|
|
28
|
+
base_dict["spoiler"] = self.spoiler
|
|
29
|
+
|
|
30
|
+
return base_dict
|
|
31
|
+
|
|
32
|
+
class File(ComponentV2, MediaItem):
|
|
33
|
+
def __init__(self, file: UnfurledMedia, id = None):
|
|
34
|
+
ComponentV2.__init__(13, id)
|
|
35
|
+
MediaItem.__init__(self, file)
|
|
36
|
+
|
|
37
|
+
self.__container_compatible__ = True
|
|
38
|
+
|
|
39
|
+
def serialize(self):
|
|
40
|
+
return {
|
|
41
|
+
"file": self.media.to_dict(),
|
|
42
|
+
"description": self.description,
|
|
43
|
+
"spoiler": self.spoiler,
|
|
44
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from .base import ComponentV2
|
|
2
|
+
|
|
3
|
+
class RadioGroup(ComponentV2):
|
|
4
|
+
class Option():
|
|
5
|
+
def __init__(self, value: str, label: str):
|
|
6
|
+
self.value = value
|
|
7
|
+
self.label = label
|
|
8
|
+
|
|
9
|
+
def serialize(self):
|
|
10
|
+
return {
|
|
11
|
+
"value": self.value,
|
|
12
|
+
"label": self.label
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
def __init__(self, custom_id: str, options: list[Option], required: bool = None, id: int = None):
|
|
16
|
+
super().__init__(21, id)
|
|
17
|
+
|
|
18
|
+
self.custom_id = custom_id
|
|
19
|
+
self.options = options
|
|
20
|
+
|
|
21
|
+
self.required = required
|
|
22
|
+
|
|
23
|
+
def serialize(self):
|
|
24
|
+
base_dict = super().serialize()
|
|
25
|
+
|
|
26
|
+
radio_options = []
|
|
27
|
+
|
|
28
|
+
for option in self.options:
|
|
29
|
+
radio_options.append(option.serialize())
|
|
30
|
+
|
|
31
|
+
base_dict["custom_id"] = self.custom_id
|
|
32
|
+
base_dict["options"] = radio_options
|
|
33
|
+
base_dict["required"] = self.required
|
|
34
|
+
|
|
35
|
+
return base_dict
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import nextcord
|
|
2
|
+
from nextcord import Interaction
|
|
3
|
+
|
|
4
|
+
from .base import ComponentV2
|
|
5
|
+
from ..utils.serialize import serialize_emoji
|
|
6
|
+
|
|
7
|
+
class DefaultValue():
|
|
8
|
+
def __init__(self, id: str, type: str):
|
|
9
|
+
self.id = id
|
|
10
|
+
self.type = type
|
|
11
|
+
|
|
12
|
+
def serialize(self):
|
|
13
|
+
return {
|
|
14
|
+
"id": self.id,
|
|
15
|
+
"type": self.type
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class SelectObject(ComponentV2):
|
|
19
|
+
def __init__(self, type: int, custom_id: str, placeholder: str = None, min_values: int = None, max_values: int = None, required: bool = True, disabled: bool = False, row: int = 0):
|
|
20
|
+
if min_values != None and required == True and min_values < 1:
|
|
21
|
+
raise ValueError("Required cannot be true if min_values is less than 1!")
|
|
22
|
+
|
|
23
|
+
super().__init__(type)
|
|
24
|
+
|
|
25
|
+
self.custom_id = custom_id
|
|
26
|
+
self.placeholder = placeholder
|
|
27
|
+
self.min_values = min_values
|
|
28
|
+
self.max_values = max_values
|
|
29
|
+
self.required = required
|
|
30
|
+
self.disabled = disabled
|
|
31
|
+
self.row = row
|
|
32
|
+
|
|
33
|
+
self.callback = None
|
|
34
|
+
self.registered = False
|
|
35
|
+
|
|
36
|
+
def serialize(self):
|
|
37
|
+
return {
|
|
38
|
+
"type": self.type,
|
|
39
|
+
"id": self.id,
|
|
40
|
+
"custom_id": self.custom_id,
|
|
41
|
+
"placeholder": self.placeholder,
|
|
42
|
+
"min_values": self.min_values,
|
|
43
|
+
"max_values": self.max_values,
|
|
44
|
+
"required": self.required,
|
|
45
|
+
"disabled": self.disabled
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
def __call__(self, func):
|
|
49
|
+
if self.registered == True:
|
|
50
|
+
raise ValueError("Cannot register a Select component to multiple callbacks!")
|
|
51
|
+
|
|
52
|
+
self.registered = True
|
|
53
|
+
self.callback = func
|
|
54
|
+
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
class StringSelect(SelectObject):
|
|
58
|
+
class SelectOption():
|
|
59
|
+
def __init__(self, label: str, value: str, description: str = None, emoji: nextcord.PartialEmoji = None, default: bool = False):
|
|
60
|
+
self.label = label
|
|
61
|
+
self.value = value
|
|
62
|
+
self.description = description
|
|
63
|
+
self.emoji = emoji
|
|
64
|
+
self.default = default
|
|
65
|
+
|
|
66
|
+
def serialize(self):
|
|
67
|
+
emoji = None if self.emoji == None else serialize_emoji(self.emoji)
|
|
68
|
+
return {
|
|
69
|
+
"label": self.label,
|
|
70
|
+
"value": self.value,
|
|
71
|
+
"description": self.description,
|
|
72
|
+
"default": self.default,
|
|
73
|
+
"emoji": emoji,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def __init__(self, custom_id: str, options: list[SelectOption], placeholder: str = None, min_values: int = None, max_values: int = None, required: bool = True, disabled: bool = False, row: int = 0):
|
|
78
|
+
super().__init__(3, custom_id, placeholder, min_values, max_values, required, disabled, row)
|
|
79
|
+
|
|
80
|
+
self.options = options
|
|
81
|
+
self.placeholder = placeholder
|
|
82
|
+
|
|
83
|
+
def serialize(self):
|
|
84
|
+
str_options: list[dict[str]] = []
|
|
85
|
+
for option in self.options:
|
|
86
|
+
str_options.append(option.serialize())
|
|
87
|
+
|
|
88
|
+
base_dict = super().serialize()
|
|
89
|
+
base_dict["options"] = str_options
|
|
90
|
+
|
|
91
|
+
return base_dict
|
|
92
|
+
|
|
93
|
+
async def activated(self, interaction: Interaction):
|
|
94
|
+
if self.disabled == True:
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
if self.registered == False:
|
|
98
|
+
raise ValueError("Attempted to activate Select component, but it is not registered to a callback!")
|
|
99
|
+
|
|
100
|
+
values = interaction.data.get("values")
|
|
101
|
+
|
|
102
|
+
await self.callback(self.parent_holder, self, interaction, values)
|
|
103
|
+
|
|
104
|
+
## DEFAULT VALUE OBJECTS (might create ANOTHER subclass to handle these fellas)
|
|
105
|
+
class UserSelect(SelectObject):
|
|
106
|
+
def __init__(self, custom_id: str, default_values: list[DefaultValue] = None, placeholder: str = None, min_values: int = None, max_values: int = None, required: bool = True, disabled: bool = False, row: int = 0):
|
|
107
|
+
if default_values != None:
|
|
108
|
+
values = len(default_values)
|
|
109
|
+
|
|
110
|
+
min = min_values or 1
|
|
111
|
+
max = max_values or 1
|
|
112
|
+
|
|
113
|
+
if min > values or max < values:
|
|
114
|
+
raise ValueError("The length of 'default_values' MUST be within the range of min_values and max_values (default 1-1).")
|
|
115
|
+
|
|
116
|
+
super().__init__(5, custom_id, placeholder, min_values, max_values, required, disabled, row)
|
|
117
|
+
|
|
118
|
+
self.default_values = default_values
|
|
119
|
+
self.placeholder = placeholder
|
|
120
|
+
|
|
121
|
+
def serialize(self):
|
|
122
|
+
if self.default_values != None:
|
|
123
|
+
str_values: list[dict[str]] = []
|
|
124
|
+
for value in self.default_values:
|
|
125
|
+
str_values.append(value.serialize())
|
|
126
|
+
else:
|
|
127
|
+
str_values = None
|
|
128
|
+
|
|
129
|
+
base_dict = super().serialize()
|
|
130
|
+
base_dict["default_values"] = str_values
|
|
131
|
+
|
|
132
|
+
return base_dict
|
|
133
|
+
|
|
134
|
+
async def activated(self, interaction: Interaction):
|
|
135
|
+
if self.disabled == True:
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
if self.registered == False:
|
|
139
|
+
raise ValueError("Attempted to activate Select component, but it is not registered to a callback!")
|
|
140
|
+
|
|
141
|
+
values = interaction.data.get("values")
|
|
142
|
+
await self.callback(self.parent_holder, self, interaction, values) ## due to a nextcord limitation, i am only able to send the list of ids, not user objects.
|
|
143
|
+
|
|
144
|
+
class RoleSelect(SelectObject):
|
|
145
|
+
def __init__(self, custom_id: str, default_values: list[DefaultValue] = None, placeholder: str = None, min_values: int = None, max_values: int = None, required: bool = True, disabled: bool = False, row: int = 0):
|
|
146
|
+
if default_values != None:
|
|
147
|
+
values = len(default_values)
|
|
148
|
+
|
|
149
|
+
min = min_values or 1
|
|
150
|
+
max = max_values or 1
|
|
151
|
+
|
|
152
|
+
if min > values or max < values:
|
|
153
|
+
raise ValueError("The length of 'default_values' MUST be within the range of min_values and max_values (default 1-1).")
|
|
154
|
+
|
|
155
|
+
super().__init__(6, custom_id, placeholder, min_values, max_values, required, disabled, row)
|
|
156
|
+
|
|
157
|
+
self.default_values = default_values
|
|
158
|
+
self.placeholder = placeholder
|
|
159
|
+
|
|
160
|
+
def serialize(self):
|
|
161
|
+
if self.default_values != None:
|
|
162
|
+
str_values: list[dict[str]] = []
|
|
163
|
+
for value in self.default_values:
|
|
164
|
+
str_values.append(value.serialize())
|
|
165
|
+
else:
|
|
166
|
+
str_values = None
|
|
167
|
+
|
|
168
|
+
base_dict = super().serialize()
|
|
169
|
+
base_dict["default_values"] = str_values
|
|
170
|
+
|
|
171
|
+
return base_dict
|
|
172
|
+
|
|
173
|
+
async def activated(self, interaction: Interaction):
|
|
174
|
+
if self.disabled == True:
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
if self.registered == False:
|
|
178
|
+
raise ValueError("Attempted to activate Select component, but it is not registered to a callback!")
|
|
179
|
+
|
|
180
|
+
values = interaction.data.get("values")
|
|
181
|
+
await self.callback(self.parent_holder, self, interaction, values) ## due to a nextcord limitation, i am only able to send the list of values, not user objects.
|
|
182
|
+
|
|
183
|
+
class MentionableSelect(SelectObject):
|
|
184
|
+
def __init__(self, custom_id: str, default_values: list[DefaultValue] = None, placeholder: str = None, min_values: int = None, max_values: int = None, required: bool = True, disabled: bool = False, row: int = 0):
|
|
185
|
+
if default_values != None:
|
|
186
|
+
values = len(default_values)
|
|
187
|
+
|
|
188
|
+
min = min_values or 1
|
|
189
|
+
max = max_values or 1
|
|
190
|
+
|
|
191
|
+
if min > values or max < values:
|
|
192
|
+
raise ValueError("The length of 'default_values' MUST be within the range of min_values and max_values (default 1-1).")
|
|
193
|
+
|
|
194
|
+
super().__init__(7, custom_id, placeholder, min_values, max_values, required, disabled, row)
|
|
195
|
+
|
|
196
|
+
self.default_values = default_values
|
|
197
|
+
self.placeholder = placeholder
|
|
198
|
+
|
|
199
|
+
def serialize(self):
|
|
200
|
+
if self.default_values != None:
|
|
201
|
+
str_values: list[dict[str]] = []
|
|
202
|
+
for value in self.default_values:
|
|
203
|
+
str_values.append(value.serialize())
|
|
204
|
+
else:
|
|
205
|
+
str_values = None
|
|
206
|
+
|
|
207
|
+
base_dict = super().serialize()
|
|
208
|
+
base_dict["default_values"] = str_values
|
|
209
|
+
|
|
210
|
+
return base_dict
|
|
211
|
+
|
|
212
|
+
async def activated(self, interaction: Interaction):
|
|
213
|
+
if self.disabled == True:
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
if self.registered == False:
|
|
217
|
+
raise ValueError("Attempted to activate Select component, but it is not registered to a callback!")
|
|
218
|
+
|
|
219
|
+
values = interaction.data.get("values")
|
|
220
|
+
await self.callback(self.parent_holder, self, interaction, values) ## due to a nextcord limitation, i am only able to send the list of values, not user objects.
|
|
221
|
+
|
|
222
|
+
class ChannelSelect(SelectObject):
|
|
223
|
+
def __init__(self, custom_id: str, default_values: list[DefaultValue] = None, channel_types: list[nextcord.ChannelType] = None, placeholder: str = None, min_values: int = None, max_values: int = None, required: bool = True, disabled: bool = False, row: int = 0):
|
|
224
|
+
if default_values != None:
|
|
225
|
+
values = len(default_values)
|
|
226
|
+
|
|
227
|
+
min = min_values or 1
|
|
228
|
+
max = max_values or 1
|
|
229
|
+
|
|
230
|
+
if min > values or max < values:
|
|
231
|
+
raise ValueError("The length of 'default_values' MUST be within the range of min_values and max_values (default 1-1).")
|
|
232
|
+
|
|
233
|
+
if channel_types != None:
|
|
234
|
+
channel_values = []
|
|
235
|
+
for type in channel_types:
|
|
236
|
+
channel_values.append(type.value)
|
|
237
|
+
else:
|
|
238
|
+
channel_values = None
|
|
239
|
+
|
|
240
|
+
self.channel_types = channel_values
|
|
241
|
+
super().__init__(8, custom_id, placeholder, min_values, max_values, required, disabled, row)
|
|
242
|
+
|
|
243
|
+
self.default_values = default_values
|
|
244
|
+
self.placeholder = placeholder
|
|
245
|
+
|
|
246
|
+
def serialize(self):
|
|
247
|
+
if self.default_values != None:
|
|
248
|
+
str_values: list[dict[str]] = []
|
|
249
|
+
for value in self.default_values:
|
|
250
|
+
str_values.append(value.serialize())
|
|
251
|
+
else:
|
|
252
|
+
str_values = None
|
|
253
|
+
|
|
254
|
+
base_dict = super().serialize()
|
|
255
|
+
base_dict["default_values"] = str_values
|
|
256
|
+
base_dict["channel_types"] = self.channel_types
|
|
257
|
+
|
|
258
|
+
return base_dict
|
|
259
|
+
|
|
260
|
+
async def activated(self, interaction: Interaction):
|
|
261
|
+
if self.disabled == True:
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
if self.registered == False:
|
|
265
|
+
raise ValueError("Attempted to activate Select component, but it is not registered to a callback!")
|
|
266
|
+
|
|
267
|
+
values = interaction.data.get("values")
|
|
268
|
+
await self.callback(self.parent_holder, self, interaction, values) ## due to a nextcord limitation, i am only able to send the list of values, not user objects.
|
|
269
|
+
|
|
270
|
+
## UI SELECTS
|
|
271
|
+
class UISelectObject(SelectObject):
|
|
272
|
+
def __init__(self, type: int, custom_id: str, placeholder: str = None, min_values: int = None, max_values: int = None, required: int = True, disabled: int = False):
|
|
273
|
+
super().__init__(type, custom_id, placeholder, min_values, max_values, required, disabled)
|
|
274
|
+
|
|
275
|
+
async def activated(self, interaction):
|
|
276
|
+
if self.disabled == True:
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
await self.ui_callback(interaction)
|
|
280
|
+
|
|
281
|
+
async def ui_callback(self, interaction: Interaction):
|
|
282
|
+
pass
|
|
283
|
+
|
|
284
|
+
class UIStringSelect(UISelectObject, StringSelect):
|
|
285
|
+
def __init__(self, custom_id: str, options: list[StringSelect.SelectOption], placeholder: str = None, min_values: int = None, max_values: int = None, required: bool = True, disabled: bool = False):
|
|
286
|
+
StringSelect.__init__(self, custom_id, options, placeholder, min_values, max_values, required, disabled)
|
|
287
|
+
|
|
288
|
+
class UIUserSelect(UISelectObject, UserSelect):
|
|
289
|
+
def __init__(self, custom_id: str, default_values: list[DefaultValue] = None, placeholder: str = None, min_values: int = None, max_values: int = None, required: bool = True, disabled: bool = False):
|
|
290
|
+
UserSelect.__init__(self, custom_id, default_values, placeholder, min_values, max_values, required, disabled)
|
|
291
|
+
|
|
292
|
+
class UIRoleSelect(UISelectObject, RoleSelect):
|
|
293
|
+
def __init__(self, custom_id: str, default_values: list[DefaultValue] = None, placeholder: str = None, min_values: int = None, max_values: int = None, required: bool = True, disabled: bool = False):
|
|
294
|
+
RoleSelect.__init__(self, custom_id, default_values, placeholder, min_values, max_values, required, disabled)
|
|
295
|
+
|
|
296
|
+
class UIChannelSelect(UISelectObject, ChannelSelect):
|
|
297
|
+
def __init__(self, custom_id: str, default_values: list[DefaultValue] = None, placeholder: str = None, min_values: int = None, max_values: int = None, required: bool = True, disabled: bool = False):
|
|
298
|
+
ChannelSelect.__init__(self, custom_id, default_values, placeholder, min_values, max_values, required, disabled)
|
|
299
|
+
|
|
300
|
+
class UIMentionableSelect(UISelectObject, MentionableSelect):
|
|
301
|
+
def __init__(self, custom_id: str, default_values: list[DefaultValue] = None, placeholder: str = None, min_values: int = None, max_values: int = None, required: bool = True, disabled: bool = False):
|
|
302
|
+
MentionableSelect.__init__(self, custom_id, default_values, placeholder, min_values, max_values, required, disabled)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .base import ComponentV2
|
|
2
|
+
|
|
3
|
+
class Separator(ComponentV2):
|
|
4
|
+
def __init__(self, divider: bool = None, spacing: int = None, id = None):
|
|
5
|
+
if spacing != None and (spacing > 2 or spacing < 1):
|
|
6
|
+
raise ValueError("Spacing can only be 1-2! 1: small padding, 2: large padding.")
|
|
7
|
+
|
|
8
|
+
super().__init__(14, id)
|
|
9
|
+
|
|
10
|
+
self.divider = divider
|
|
11
|
+
self.spacing = spacing
|
|
12
|
+
|
|
13
|
+
self.__container_compatible__ = True
|
|
14
|
+
|
|
15
|
+
def serialize(self):
|
|
16
|
+
base_dict = super().serialize()
|
|
17
|
+
|
|
18
|
+
base_dict["divider"] = self.divider
|
|
19
|
+
base_dict["spacing"] = self.spacing
|
|
20
|
+
|
|
21
|
+
return base_dict
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .base import ComponentV2
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
class TextDisplay(ComponentV2):
|
|
5
|
+
def __init__(self, content: str, id: Optional[int] = None):
|
|
6
|
+
super().__init__(10, id)
|
|
7
|
+
|
|
8
|
+
self.content = content
|
|
9
|
+
|
|
10
|
+
self.__container_compatible__ = True
|
|
11
|
+
|
|
12
|
+
def serialize(self):
|
|
13
|
+
base_dict = super().serialize()
|
|
14
|
+
base_dict["content"] = self.content
|
|
15
|
+
|
|
16
|
+
return base_dict
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import nextcord
|
|
2
|
+
from .base import ComponentV2
|
|
3
|
+
|
|
4
|
+
class TextInput(ComponentV2):
|
|
5
|
+
def __init__(self, custom_id: str, style: nextcord.TextInputStyle, min_length: int = None, max_length: int = None, required: bool = None, set_value: str = None, placeholder: str = None, id: int = None):
|
|
6
|
+
super().__init__(4, id)
|
|
7
|
+
|
|
8
|
+
self.custom_id = custom_id
|
|
9
|
+
self.style = style
|
|
10
|
+
self.min_length = min_length
|
|
11
|
+
self.max_length = max_length
|
|
12
|
+
|
|
13
|
+
self.required = required
|
|
14
|
+
self.set_value = set_value
|
|
15
|
+
self.placeholder = placeholder
|
|
16
|
+
|
|
17
|
+
def serialize(self):
|
|
18
|
+
base_dict = super().serialize()
|
|
19
|
+
|
|
20
|
+
base_dict["custom_id"] = self.custom_id
|
|
21
|
+
base_dict["style"] = self.style.value
|
|
22
|
+
|
|
23
|
+
base_dict["min_length"] = self.min_length
|
|
24
|
+
base_dict["max_length"] = self.max_length
|
|
25
|
+
|
|
26
|
+
base_dict["required"] = self.required
|
|
27
|
+
base_dict["value"] = self.value
|
|
28
|
+
|
|
29
|
+
base_dict["placeholder"] = self.placeholder
|
|
30
|
+
|
|
31
|
+
return base_dict
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .components import (
|
|
2
|
+
action_row,
|
|
3
|
+
base,
|
|
4
|
+
button,
|
|
5
|
+
checkbox,
|
|
6
|
+
file_upload,
|
|
7
|
+
gallery,
|
|
8
|
+
media,
|
|
9
|
+
radio_group,
|
|
10
|
+
select,
|
|
11
|
+
separator,
|
|
12
|
+
text_display,
|
|
13
|
+
text_input,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from.components.holders import (
|
|
17
|
+
container,
|
|
18
|
+
label,
|
|
19
|
+
section
|
|
20
|
+
)
|
|
File without changes
|
componentsv2/wrapper.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import nextcord
|
|
2
|
+
import json
|
|
3
|
+
import copy
|
|
4
|
+
|
|
5
|
+
from nextcord.ext import commands
|
|
6
|
+
from nextcord import Interaction
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from nextcord import MessageFlags
|
|
10
|
+
|
|
11
|
+
## CLASSES
|
|
12
|
+
from .components.base import ComponentV2
|
|
13
|
+
from .components.action_row import ActionRow
|
|
14
|
+
from .components.button import ButtonV2
|
|
15
|
+
|
|
16
|
+
from .components.select import (
|
|
17
|
+
StringSelect,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from .component_holder import (
|
|
21
|
+
ComponentHolder,
|
|
22
|
+
ModalV2,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from .components.holders.label import Label
|
|
26
|
+
|
|
27
|
+
class NextcordAPIWrapperV2():
|
|
28
|
+
def __init__(self, bot: commands.Bot):
|
|
29
|
+
self.bot = bot
|
|
30
|
+
|
|
31
|
+
self.active_holders = {}
|
|
32
|
+
self.active_modals = {}
|
|
33
|
+
|
|
34
|
+
bot.add_listener(self.on_interaction, "on_interaction")
|
|
35
|
+
|
|
36
|
+
async def on_interaction(self, interaction: Interaction):
|
|
37
|
+
if interaction.type == nextcord.InteractionType.component:
|
|
38
|
+
holder: ComponentHolder = self.active_holders.get(str(interaction.message.id))
|
|
39
|
+
if holder != None:
|
|
40
|
+
component = holder.get_component(interaction.data.get("custom_id"))
|
|
41
|
+
if component != None:
|
|
42
|
+
await component.activated(interaction)
|
|
43
|
+
elif interaction.type == nextcord.InteractionType.modal_submit:
|
|
44
|
+
modal: ModalV2 = self.active_modals.get(interaction.user.id)
|
|
45
|
+
if modal != None:
|
|
46
|
+
await modal.submitted(interaction, interaction.data.get("components"))
|
|
47
|
+
|
|
48
|
+
def __message_interaction_payload(self, components: list[ComponentV2], content: str = None, flags: int = None) -> dict[str]:
|
|
49
|
+
return {
|
|
50
|
+
"type": 4, # update your FUCKING docs discord
|
|
51
|
+
"data": {
|
|
52
|
+
"flags": flags,
|
|
53
|
+
"content": content,
|
|
54
|
+
"components": components
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
def __edit_interaction_payload(self, components: list[ComponentV2], content: str = None) -> dict[str]:
|
|
59
|
+
return {
|
|
60
|
+
"type": 7, # update your FUCKING docs discord
|
|
61
|
+
"data": {
|
|
62
|
+
"content": content,
|
|
63
|
+
"components": components
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async def send_modal(self, interaction: Interaction, modal: ModalV2):
|
|
68
|
+
self.active_modals[interaction.user.id] = modal
|
|
69
|
+
|
|
70
|
+
await self.bot.http.request(
|
|
71
|
+
nextcord.http.Route("POST", f"/interactions/{interaction.id}/{interaction.token}/callback"),
|
|
72
|
+
json = modal.serialize()
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
async def send_message(self, interaction: Interaction, component_holder: ComponentHolder, content: str = None, *, ephemeral: bool = False, is_components_v2: bool = False):
|
|
76
|
+
if is_components_v2 == True and content != None:
|
|
77
|
+
raise TypeError("ComponentsV2 messages cannot contain content!")
|
|
78
|
+
|
|
79
|
+
flags = MessageFlags(ephemeral=ephemeral, is_components_v2=is_components_v2).value
|
|
80
|
+
response = await self.bot.http.request(
|
|
81
|
+
nextcord.http.Route("POST", f"/interactions/{interaction.id}/{interaction.token}/callback?with_response=true"),
|
|
82
|
+
json=self.__message_interaction_payload(components=component_holder.serialize(), content=content, flags=flags)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
self.active_holders[response["interaction"]["response_message_id"]] = component_holder
|
|
86
|
+
|
|
87
|
+
async def send_followup(self, interaction: Interaction, component_holder: ComponentHolder, content: str = None, *, ephemeral: bool = False, is_components_v2: bool = False):
|
|
88
|
+
if is_components_v2 == True and content != None:
|
|
89
|
+
raise TypeError("ComponentsV2 messages cannot contain content!")
|
|
90
|
+
|
|
91
|
+
flags = MessageFlags(ephemeral=ephemeral, is_components_v2=is_components_v2).value
|
|
92
|
+
response = await self.bot.http.request(
|
|
93
|
+
nextcord.http.Route("POST", f"/webhooks/{interaction.application_id}/{interaction.token}"),
|
|
94
|
+
json={"components": component_holder.serialize(), "content": content, "flags": flags}
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
self.active_holders[response["id"]] = component_holder
|
|
98
|
+
|
|
99
|
+
async def edit_message(self, interaction: Interaction, component_holder: ComponentHolder, content: str = None):
|
|
100
|
+
await self.bot.http.request(
|
|
101
|
+
nextcord.http.Route("POST", f"/interactions/{interaction.id}/{interaction.token}/callback"),
|
|
102
|
+
json=self.__edit_interaction_payload(components=component_holder.serialize(), content=content)
|
|
103
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
componentsv2/__init__.py,sha256=yXlmXdZMb0JLI7Z8A3Qo_mNt8h0gVtGKM9s8tAxtUgg,241
|
|
2
|
+
componentsv2/component_holder.py,sha256=x7O5EH3lHefwXeeAVnchDzH6fisaBw6eku-0Ulrok8U,7003
|
|
3
|
+
componentsv2/components.py,sha256=DlyZaQ3E3L_YMiyz6tB5Pe33mc_K4-g3q_meCr8hFpg,291
|
|
4
|
+
componentsv2/wrapper.py,sha256=U-3yyfrpoxF2Unri8hBg-FsabHwG6SQQaz4V3l8RNYY,4412
|
|
5
|
+
componentsv2/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
componentsv2/components/action_row.py,sha256=bhErre3kMk_l9kshdynazFqWZkTnRjPk0KzWF6WScs4,1367
|
|
7
|
+
componentsv2/components/base.py,sha256=Oq6QZynkMoQaM8tPzj9IR373puhlrmKxV4u_gTDUxk0,423
|
|
8
|
+
componentsv2/components/button.py,sha256=_a8snX-qX1N_vv8NX2tSe3c8EGSxKO1KuX5MJvmyYBQ,2589
|
|
9
|
+
componentsv2/components/checkbox.py,sha256=1RRSwqTLTSIv4pxG-LsEORzF6QicFBugvu56khteScw,1992
|
|
10
|
+
componentsv2/components/file_upload.py,sha256=bh-RA-pb5AJAuLMDXiW3hy8mArIHgJQfLsMrIui9VU4,1948
|
|
11
|
+
componentsv2/components/gallery.py,sha256=HIcMLwgjJk00AEjGKgC0-zXkgZYBr7KRJlzUCBE7suk,677
|
|
12
|
+
componentsv2/components/media.py,sha256=gElgj_R4nojhJMqkf8Bh6MI7eRq6B2_vkvCtWQTkfCE,1387
|
|
13
|
+
componentsv2/components/radio_group.py,sha256=-tcP4THx5e5iuaNfK8Gxl8CyjUibaqFcVRO_QMgVoKQ,966
|
|
14
|
+
componentsv2/components/select.py,sha256=vNhEDDXPBifTk8tVPBFQ3zbQjpbsYgDFTRXpEBCgtp4,13274
|
|
15
|
+
componentsv2/components/separator.py,sha256=I-MvlXXrkL2Bsf9EmoMgLjdHqE7ZuDZXj1MM6VGm48U,644
|
|
16
|
+
componentsv2/components/text_display.py,sha256=HRPrvdOMGHcmL5dEkB4p4vqcRVj6hixbt38_LrwDsCc,424
|
|
17
|
+
componentsv2/components/text_input.py,sha256=QijHphwNt58c3C6RfdXnb2yCQAXlPGS73PGLxRgOKdo,1018
|
|
18
|
+
componentsv2/components/holders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
componentsv2/components/holders/container.py,sha256=dpbObx1I7IGgbdrBM0ljISeNTkI5JmwjPClL8V14hhw,1882
|
|
20
|
+
componentsv2/components/holders/label.py,sha256=Ji-98ZNxnEf5zNffU7qNN_MSLT3-4QJvSfgrwRTTpkc,557
|
|
21
|
+
componentsv2/components/holders/nestable.py,sha256=b-NHokuXTgsN_raUNcBliBVlT_OfznHlCfvMtd_rMZc,789
|
|
22
|
+
componentsv2/components/holders/section.py,sha256=WsbiDO2Sqz1uw1csXvwMOnnkguVtXLZCyFz2U_JJrRE,693
|
|
23
|
+
componentsv2/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
componentsv2/utils/serialize.py,sha256=KQIGiUfU58jbrHe_A8MoB6n2wO6wttLk0F_Mcd5hIRk,199
|
|
25
|
+
componentsv2-0.2.2.dist-info/licenses/LICENSE.txt,sha256=OXcweB55IB6gyLzI3-m9fezKOmbjn1-_0Rbmh3glT2A,1084
|
|
26
|
+
componentsv2-0.2.2.dist-info/METADATA,sha256=vqlaDjCD2ytwHm3tuXb0uc_Hyc2wAszefnufyLol0Vw,134
|
|
27
|
+
componentsv2-0.2.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
28
|
+
componentsv2-0.2.2.dist-info/top_level.txt,sha256=ip99yM9pVbDNhDGl9lW60dQlAQDZ_XOmOjqN8HJAFG4,13
|
|
29
|
+
componentsv2-0.2.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 finnagen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
componentsv2
|