0
# Interactions and UI Components
1
2
Interactive Discord UI elements including buttons, select menus, modals, and views for creating rich user interfaces. These components enable persistent interactions and complex user workflows within Discord.
3
4
## Capabilities
5
6
### Views
7
8
Container classes that manage collections of interactive UI components with persistent state and callback handling.
9
10
```python { .api }
11
class View:
12
def __init__(
13
self,
14
*,
15
timeout: Optional[float] = 180.0,
16
auto_defer: bool = True
17
):
18
"""
19
Create a view for managing UI components.
20
21
Parameters:
22
- timeout: View timeout in seconds (None for no timeout)
23
- auto_defer: Whether to auto-defer interactions
24
"""
25
26
timeout: Optional[float]
27
children: List[Item]
28
29
def add_item(self, item: Item) -> Self:
30
"""
31
Add a UI component to this view.
32
33
Parameters:
34
- item: Component to add
35
36
Returns:
37
Self for method chaining
38
"""
39
40
def remove_item(self, item: Item) -> Self:
41
"""
42
Remove a UI component from this view.
43
44
Parameters:
45
- item: Component to remove
46
47
Returns:
48
Self for method chaining
49
"""
50
51
def clear_items(self) -> Self:
52
"""
53
Remove all items from this view.
54
55
Returns:
56
Self for method chaining
57
"""
58
59
def get_item(self, custom_id: str) -> Optional[Item]:
60
"""
61
Get an item by custom_id.
62
63
Parameters:
64
- custom_id: Custom ID to search for
65
66
Returns:
67
Item if found
68
"""
69
70
async def interaction_check(self, interaction: Interaction) -> bool:
71
"""
72
Check if interaction should be processed by this view.
73
74
Parameters:
75
- interaction: Interaction to check
76
77
Returns:
78
True if interaction should be processed
79
"""
80
81
async def on_timeout(self) -> None:
82
"""Called when the view times out."""
83
84
async def on_error(self, interaction: Interaction, error: Exception, item: Item) -> None:
85
"""
86
Called when an error occurs in a component callback.
87
88
Parameters:
89
- interaction: Interaction that caused the error
90
- error: Exception that occurred
91
- item: Component that caused the error
92
"""
93
94
def stop(self) -> None:
95
"""Stop the view and prevent further interactions."""
96
97
def is_finished(self) -> bool:
98
"""Whether the view has finished (stopped or timed out)."""
99
100
def is_persistent(self) -> bool:
101
"""Whether the view is persistent (all items have custom_id)."""
102
103
@classmethod
104
def from_message(cls, message: Message, *, timeout: Optional[float] = 180.0) -> View:
105
"""
106
Create a view from existing message components.
107
108
Parameters:
109
- message: Message with components
110
- timeout: View timeout
111
112
Returns:
113
View instance with message components
114
"""
115
```
116
117
### Buttons
118
119
Clickable button components with various styles and callback handling.
120
121
```python { .api }
122
class Button(Item):
123
def __init__(
124
self,
125
*,
126
style: ButtonStyle = ButtonStyle.secondary,
127
label: Optional[str] = None,
128
disabled: bool = False,
129
custom_id: Optional[str] = None,
130
url: Optional[str] = None,
131
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
132
row: Optional[int] = None
133
):
134
"""
135
Create a button component.
136
137
Parameters:
138
- style: Button style (primary, secondary, success, danger, link)
139
- label: Button text label
140
- disabled: Whether button is disabled
141
- custom_id: Custom ID for persistent buttons
142
- url: URL for link buttons
143
- emoji: Button emoji
144
- row: Action row (0-4)
145
"""
146
147
style: ButtonStyle
148
label: Optional[str]
149
disabled: bool
150
custom_id: Optional[str]
151
url: Optional[str]
152
emoji: Optional[Union[str, Emoji, PartialEmoji]]
153
154
async def callback(self, interaction: MessageInteraction) -> None:
155
"""
156
Button click callback. Override this method.
157
158
Parameters:
159
- interaction: Button click interaction
160
"""
161
162
def button(
163
*,
164
label: Optional[str] = None,
165
custom_id: Optional[str] = None,
166
disabled: bool = False,
167
style: ButtonStyle = ButtonStyle.secondary,
168
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
169
row: Optional[int] = None
170
):
171
"""
172
Decorator for button callbacks in views.
173
174
Parameters:
175
- label: Button text label
176
- custom_id: Custom ID for persistent buttons
177
- disabled: Whether button is disabled
178
- style: Button style
179
- emoji: Button emoji
180
- row: Action row (0-4)
181
182
Returns:
183
Decorated method as a Button component
184
"""
185
```
186
187
### Select Menus
188
189
Dropdown selection components with multiple variations for different data types.
190
191
```python { .api }
192
class BaseSelect(Item):
193
def __init__(
194
self,
195
*,
196
custom_id: Optional[str] = None,
197
placeholder: Optional[str] = None,
198
min_values: int = 1,
199
max_values: int = 1,
200
disabled: bool = False,
201
row: Optional[int] = None
202
):
203
"""
204
Base class for select menu components.
205
206
Parameters:
207
- custom_id: Custom ID for persistent components
208
- placeholder: Placeholder text
209
- min_values: Minimum selections required
210
- max_values: Maximum selections allowed
211
- disabled: Whether component is disabled
212
- row: Action row (0-4)
213
"""
214
215
custom_id: Optional[str]
216
placeholder: Optional[str]
217
min_values: int
218
max_values: int
219
disabled: bool
220
values: List[str]
221
222
async def callback(self, interaction: MessageInteraction) -> None:
223
"""
224
Selection callback. Override this method.
225
226
Parameters:
227
- interaction: Selection interaction
228
"""
229
230
class StringSelect(BaseSelect):
231
def __init__(
232
self,
233
*,
234
options: List[SelectOption],
235
custom_id: Optional[str] = None,
236
placeholder: Optional[str] = None,
237
min_values: int = 1,
238
max_values: int = 1,
239
disabled: bool = False,
240
row: Optional[int] = None
241
):
242
"""
243
String selection menu with predefined options.
244
245
Parameters:
246
- options: List of SelectOption objects
247
- custom_id: Custom ID for persistent components
248
- placeholder: Placeholder text
249
- min_values: Minimum selections required
250
- max_values: Maximum selections allowed
251
- disabled: Whether component is disabled
252
- row: Action row (0-4)
253
"""
254
255
options: List[SelectOption]
256
257
class UserSelect(BaseSelect):
258
def __init__(
259
self,
260
*,
261
custom_id: Optional[str] = None,
262
placeholder: Optional[str] = None,
263
min_values: int = 1,
264
max_values: int = 1,
265
disabled: bool = False,
266
row: Optional[int] = None,
267
default_values: Optional[List[SelectDefaultValue]] = None
268
):
269
"""
270
User selection menu.
271
272
Parameters:
273
- custom_id: Custom ID for persistent components
274
- placeholder: Placeholder text
275
- min_values: Minimum selections required
276
- max_values: Maximum selections allowed
277
- disabled: Whether component is disabled
278
- row: Action row (0-4)
279
- default_values: Default selected users
280
"""
281
282
default_values: Optional[List[SelectDefaultValue]]
283
284
class RoleSelect(BaseSelect):
285
def __init__(
286
self,
287
*,
288
custom_id: Optional[str] = None,
289
placeholder: Optional[str] = None,
290
min_values: int = 1,
291
max_values: int = 1,
292
disabled: bool = False,
293
row: Optional[int] = None,
294
default_values: Optional[List[SelectDefaultValue]] = None
295
):
296
"""
297
Role selection menu.
298
299
Parameters:
300
- custom_id: Custom ID for persistent components
301
- placeholder: Placeholder text
302
- min_values: Minimum selections required
303
- max_values: Maximum selections allowed
304
- disabled: Whether component is disabled
305
- row: Action row (0-4)
306
- default_values: Default selected roles
307
"""
308
309
default_values: Optional[List[SelectDefaultValue]]
310
311
class ChannelSelect(BaseSelect):
312
def __init__(
313
self,
314
*,
315
channel_types: Optional[List[ChannelType]] = None,
316
custom_id: Optional[str] = None,
317
placeholder: Optional[str] = None,
318
min_values: int = 1,
319
max_values: int = 1,
320
disabled: bool = False,
321
row: Optional[int] = None,
322
default_values: Optional[List[SelectDefaultValue]] = None
323
):
324
"""
325
Channel selection menu.
326
327
Parameters:
328
- channel_types: Allowed channel types
329
- custom_id: Custom ID for persistent components
330
- placeholder: Placeholder text
331
- min_values: Minimum selections required
332
- max_values: Maximum selections allowed
333
- disabled: Whether component is disabled
334
- row: Action row (0-4)
335
- default_values: Default selected channels
336
"""
337
338
channel_types: Optional[List[ChannelType]]
339
default_values: Optional[List[SelectDefaultValue]]
340
341
class MentionableSelect(BaseSelect):
342
def __init__(
343
self,
344
*,
345
custom_id: Optional[str] = None,
346
placeholder: Optional[str] = None,
347
min_values: int = 1,
348
max_values: int = 1,
349
disabled: bool = False,
350
row: Optional[int] = None,
351
default_values: Optional[List[SelectDefaultValue]] = None
352
):
353
"""
354
Mentionable entity (users and roles) selection menu.
355
356
Parameters:
357
- custom_id: Custom ID for persistent components
358
- placeholder: Placeholder text
359
- min_values: Minimum selections required
360
- max_values: Maximum selections allowed
361
- disabled: Whether component is disabled
362
- row: Action row (0-4)
363
- default_values: Default selected entities
364
"""
365
366
default_values: Optional[List[SelectDefaultValue]]
367
368
class SelectOption:
369
def __init__(
370
self,
371
*,
372
label: str,
373
value: str,
374
description: Optional[str] = None,
375
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
376
default: bool = False
377
):
378
"""
379
Option for string select menus.
380
381
Parameters:
382
- label: Option display label
383
- value: Option value
384
- description: Option description
385
- emoji: Option emoji
386
- default: Whether option is selected by default
387
"""
388
389
label: str
390
value: str
391
description: Optional[str]
392
emoji: Optional[Union[str, Emoji, PartialEmoji]]
393
default: bool
394
395
class SelectDefaultValue:
396
def __init__(self, id: int, type: SelectDefaultValueType):
397
"""
398
Default value for entity select menus.
399
400
Parameters:
401
- id: Entity ID
402
- type: Entity type (user, role, channel)
403
"""
404
405
id: int
406
type: SelectDefaultValueType
407
```
408
409
### Modals
410
411
Dialog forms for collecting structured user input with multiple text fields.
412
413
```python { .api }
414
class Modal:
415
def __init__(
416
self,
417
*,
418
title: str,
419
custom_id: Optional[str] = None,
420
timeout: Optional[float] = None
421
):
422
"""
423
Create a modal dialog form.
424
425
Parameters:
426
- title: Modal title
427
- custom_id: Custom ID for persistent modals
428
- timeout: Modal timeout in seconds
429
"""
430
431
title: str
432
custom_id: Optional[str]
433
timeout: Optional[float]
434
children: List[TextInput]
435
436
def add_item(self, item: TextInput) -> Self:
437
"""
438
Add a text input to this modal.
439
440
Parameters:
441
- item: TextInput to add
442
443
Returns:
444
Self for method chaining
445
"""
446
447
def remove_item(self, item: TextInput) -> Self:
448
"""
449
Remove a text input from this modal.
450
451
Parameters:
452
- item: TextInput to remove
453
454
Returns:
455
Self for method chaining
456
"""
457
458
async def on_submit(self, interaction: ModalInteraction) -> None:
459
"""
460
Called when modal is submitted. Override this method.
461
462
Parameters:
463
- interaction: Modal submission interaction
464
"""
465
466
async def on_error(self, interaction: ModalInteraction, error: Exception) -> None:
467
"""
468
Called when an error occurs during modal submission.
469
470
Parameters:
471
- interaction: Modal interaction
472
- error: Exception that occurred
473
"""
474
475
async def on_timeout(self) -> None:
476
"""Called when the modal times out."""
477
478
def stop(self) -> None:
479
"""Stop the modal and prevent further interactions."""
480
481
class TextInput(Item):
482
def __init__(
483
self,
484
*,
485
label: str,
486
custom_id: Optional[str] = None,
487
style: TextInputStyle = TextInputStyle.short,
488
placeholder: Optional[str] = None,
489
value: Optional[str] = None,
490
required: bool = True,
491
min_length: Optional[int] = None,
492
max_length: Optional[int] = None,
493
row: Optional[int] = None
494
):
495
"""
496
Text input field for modals.
497
498
Parameters:
499
- label: Input field label
500
- custom_id: Custom ID for the input
501
- style: Input style (short or paragraph)
502
- placeholder: Placeholder text
503
- value: Pre-filled value
504
- required: Whether input is required
505
- min_length: Minimum text length
506
- max_length: Maximum text length
507
- row: Action row (0-4)
508
"""
509
510
label: str
511
custom_id: Optional[str]
512
style: TextInputStyle
513
placeholder: Optional[str]
514
value: Optional[str]
515
required: bool
516
min_length: Optional[int]
517
max_length: Optional[int]
518
519
def text_input(
520
*,
521
label: str,
522
custom_id: Optional[str] = None,
523
style: TextInputStyle = TextInputStyle.short,
524
placeholder: Optional[str] = None,
525
default: Optional[str] = None,
526
required: bool = True,
527
min_length: Optional[int] = None,
528
max_length: Optional[int] = None,
529
row: Optional[int] = None
530
):
531
"""
532
Decorator for text input fields in modals.
533
534
Parameters:
535
- label: Input field label
536
- custom_id: Custom ID for the input
537
- style: Input style (short or paragraph)
538
- placeholder: Placeholder text
539
- default: Pre-filled value
540
- required: Whether input is required
541
- min_length: Minimum text length
542
- max_length: Maximum text length
543
- row: Action row (0-4)
544
545
Returns:
546
Decorated attribute as a TextInput component
547
"""
548
```
549
550
### Message Interactions
551
552
Interaction objects for UI component interactions like button clicks and select menu selections.
553
554
```python { .api }
555
class MessageInteraction:
556
def __init__(self): ...
557
558
id: int
559
type: InteractionType
560
data: MessageInteractionData
561
guild: Optional[Guild]
562
guild_id: Optional[int]
563
channel: Optional[Union[GuildChannel, PartialMessageable]]
564
channel_id: Optional[int]
565
user: Union[User, Member]
566
token: str
567
version: int
568
message: Message
569
locale: Optional[Locale]
570
guild_locale: Optional[Locale]
571
created_at: datetime
572
expires_at: datetime
573
574
@property
575
def response(self) -> InteractionResponse:
576
"""Interaction response handler."""
577
578
@property
579
def followup(self) -> Webhook:
580
"""Webhook for followup messages."""
581
582
@property
583
def component(self) -> Component:
584
"""The component that triggered this interaction."""
585
586
class ModalInteraction:
587
def __init__(self): ...
588
589
id: int
590
type: InteractionType
591
data: ModalInteractionData
592
guild: Optional[Guild]
593
guild_id: Optional[int]
594
channel: Optional[Union[GuildChannel, PartialMessageable]]
595
channel_id: Optional[int]
596
user: Union[User, Member]
597
token: str
598
version: int
599
message: Optional[Message]
600
locale: Optional[Locale]
601
guild_locale: Optional[Locale]
602
created_at: datetime
603
expires_at: datetime
604
605
@property
606
def response(self) -> InteractionResponse:
607
"""Interaction response handler."""
608
609
@property
610
def followup(self) -> Webhook:
611
"""Webhook for followup messages."""
612
613
@property
614
def text_values(self) -> Dict[str, str]:
615
"""Dictionary mapping text input custom_ids to their values."""
616
```
617
618
## Usage Examples
619
620
### Basic Button View
621
622
```python
623
import disnake
624
625
class BasicView(disnake.ui.View):
626
def __init__(self):
627
super().__init__(timeout=60)
628
self.value = None
629
630
@disnake.ui.button(label="Yes", style=disnake.ButtonStyle.green)
631
async def yes_button(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
632
self.value = True
633
self.stop()
634
await interaction.response.send_message("You clicked Yes!", ephemeral=True)
635
636
@disnake.ui.button(label="No", style=disnake.ButtonStyle.red)
637
async def no_button(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
638
self.value = False
639
self.stop()
640
await interaction.response.send_message("You clicked No!", ephemeral=True)
641
642
# Usage
643
@bot.slash_command(description="Ask a yes/no question")
644
async def question(inter: disnake.ApplicationCommandInteraction):
645
view = BasicView()
646
await inter.response.send_message("Do you like pizza?", view=view)
647
648
await view.wait()
649
if view.value is None:
650
await inter.followup.send("You didn't respond in time!")
651
elif view.value:
652
await inter.followup.send("Great choice!")
653
else:
654
await inter.followup.send("That's okay!")
655
```
656
657
### Select Menu Example
658
659
```python
660
class GameSelect(disnake.ui.View):
661
@disnake.ui.string_select(
662
placeholder="Choose your favorite game...",
663
min_values=1,
664
max_values=3,
665
options=[
666
disnake.SelectOption(
667
label="Minecraft",
668
value="minecraft",
669
description="Block building game",
670
emoji="π«"
671
),
672
disnake.SelectOption(
673
label="Among Us",
674
value="among_us",
675
description="Social deduction game",
676
emoji="π΄"
677
),
678
disnake.SelectOption(
679
label="Discord",
680
value="discord",
681
description="Chat application",
682
emoji="π¬"
683
),
684
]
685
)
686
async def select_callback(self, select: disnake.ui.StringSelect, interaction: disnake.MessageInteraction):
687
games = ", ".join(select.values)
688
await interaction.response.send_message(f"You selected: {games}")
689
690
@bot.slash_command(description="Choose your favorite games")
691
async def games(inter: disnake.ApplicationCommandInteraction):
692
view = GameSelect()
693
await inter.response.send_message("Select your favorites:", view=view)
694
```
695
696
### User/Role Select Menus
697
698
```python
699
class ModeratorPanel(disnake.ui.View):
700
@disnake.ui.user_select(placeholder="Select users to moderate...")
701
async def select_users(self, select: disnake.ui.UserSelect, interaction: disnake.MessageInteraction):
702
users = [f"{user.mention}" for user in select.values]
703
await interaction.response.send_message(
704
f"Selected users: {', '.join(users)}",
705
ephemeral=True
706
)
707
708
@disnake.ui.role_select(placeholder="Select roles to manage...")
709
async def select_roles(self, select: disnake.ui.RoleSelect, interaction: disnake.MessageInteraction):
710
roles = [f"{role.mention}" for role in select.values]
711
await interaction.response.send_message(
712
f"Selected roles: {', '.join(roles)}",
713
ephemeral=True
714
)
715
716
@bot.slash_command(description="Moderator panel")
717
async def mod_panel(inter: disnake.ApplicationCommandInteraction):
718
view = ModeratorPanel()
719
await inter.response.send_message("Moderator Controls:", view=view)
720
```
721
722
### Modal Forms
723
724
```python
725
class FeedbackModal(disnake.ui.Modal):
726
def __init__(self):
727
super().__init__(title="Submit Feedback", timeout=300)
728
729
name = disnake.ui.TextInput(
730
label="Your Name",
731
placeholder="Enter your name here...",
732
required=True,
733
max_length=100
734
)
735
736
feedback = disnake.ui.TextInput(
737
label="Feedback",
738
style=disnake.TextInputStyle.paragraph,
739
placeholder="Tell us what you think...",
740
required=True,
741
min_length=10,
742
max_length=1000
743
)
744
745
async def on_submit(self, interaction: disnake.ModalInteraction):
746
embed = disnake.Embed(
747
title="New Feedback",
748
color=disnake.Color.blue()
749
)
750
embed.add_field(name="From", value=self.name.value, inline=True)
751
embed.add_field(name="User", value=interaction.author.mention, inline=True)
752
embed.add_field(name="Feedback", value=self.feedback.value, inline=False)
753
754
# Send to feedback channel
755
feedback_channel = bot.get_channel(FEEDBACK_CHANNEL_ID)
756
await feedback_channel.send(embed=embed)
757
758
await interaction.response.send_message(
759
"Thank you for your feedback!",
760
ephemeral=True
761
)
762
763
@bot.slash_command(description="Submit feedback")
764
async def feedback(inter: disnake.ApplicationCommandInteraction):
765
modal = FeedbackModal()
766
await inter.response.send_modal(modal)
767
```
768
769
### Persistent Views
770
771
```python
772
class PersistentView(disnake.ui.View):
773
def __init__(self):
774
super().__init__(timeout=None) # No timeout for persistent views
775
776
@disnake.ui.button(
777
label="Get Support",
778
style=disnake.ButtonStyle.primary,
779
custom_id="support_button" # Custom ID for persistence
780
)
781
async def support_button(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
782
# This button will work even after bot restarts
783
await interaction.response.send_message(
784
"Support ticket created! A moderator will be with you shortly.",
785
ephemeral=True
786
)
787
788
# Add persistent view on bot startup
789
@bot.event
790
async def on_ready():
791
bot.add_view(PersistentView())
792
print(f'{bot.user} is ready!')
793
794
@bot.slash_command(description="Send support panel")
795
async def support_panel(inter: disnake.ApplicationCommandInteraction):
796
embed = disnake.Embed(
797
title="Support Center",
798
description="Click the button below to get help."
799
)
800
view = PersistentView()
801
await inter.response.send_message(embed=embed, view=view)
802
```
803
804
### Complex Interactive Workflow
805
806
```python
807
class ConfigurationWizard(disnake.ui.View):
808
def __init__(self):
809
super().__init__(timeout=300)
810
self.config = {}
811
812
@disnake.ui.button(label="Step 1: Choose Channel", style=disnake.ButtonStyle.primary)
813
async def step1(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
814
view = ChannelSelectView(self)
815
await interaction.response.edit_message(
816
content="Step 1: Select a channel for notifications:",
817
view=view
818
)
819
820
@disnake.ui.button(label="Step 2: Set Permissions", style=disnake.ButtonStyle.secondary, disabled=True)
821
async def step2(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
822
view = RoleSelectView(self)
823
await interaction.response.edit_message(
824
content="Step 2: Select roles that can use this feature:",
825
view=view
826
)
827
828
@disnake.ui.button(label="Finish", style=disnake.ButtonStyle.green, disabled=True)
829
async def finish(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
830
embed = disnake.Embed(title="Configuration Complete!")
831
embed.add_field(name="Channel", value=self.config.get('channel', 'None'))
832
embed.add_field(name="Roles", value=', '.join(self.config.get('roles', [])))
833
834
await interaction.response.edit_message(
835
content="Configuration saved!",
836
embed=embed,
837
view=None
838
)
839
840
class ChannelSelectView(disnake.ui.View):
841
def __init__(self, parent):
842
super().__init__(timeout=300)
843
self.parent = parent
844
845
@disnake.ui.channel_select(
846
placeholder="Select notification channel...",
847
channel_types=[disnake.ChannelType.text]
848
)
849
async def select_channel(self, select: disnake.ui.ChannelSelect, interaction: disnake.MessageInteraction):
850
self.parent.config['channel'] = select.values[0].mention
851
852
# Enable step 2
853
self.parent.children[1].disabled = False
854
855
await interaction.response.edit_message(
856
content="β Channel selected! Now proceed to step 2:",
857
view=self.parent
858
)
859
860
class RoleSelectView(disnake.ui.View):
861
def __init__(self, parent):
862
super().__init__(timeout=300)
863
self.parent = parent
864
865
@disnake.ui.role_select(
866
placeholder="Select authorized roles...",
867
min_values=1,
868
max_values=5
869
)
870
async def select_roles(self, select: disnake.ui.RoleSelect, interaction: disnake.MessageInteraction):
871
self.parent.config['roles'] = [role.mention for role in select.values]
872
873
# Enable finish button
874
self.parent.children[2].disabled = False
875
876
await interaction.response.edit_message(
877
content="β Roles selected! Click Finish to complete setup:",
878
view=self.parent
879
)
880
881
@bot.slash_command(description="Configure bot settings")
882
async def configure(inter: disnake.ApplicationCommandInteraction):
883
view = ConfigurationWizard()
884
await inter.response.send_message("Welcome to the configuration wizard!", view=view)
885
```