The Basics#
This section explains how to use disnake-ext-components to create and manage your components, and how to send them to Discord so that your users can interact with them.
Components#
Component classes are all attrs
classes. If you’re unfamiliar with attrs, know that they work very similarly to dataclasses.
A component class has two types of fields:
Internal fields
These are reserved for parameters that directly influence the component, such as the
label
field for a button. For the most part, these match the attributes on the standard disnake.ui component classes.Custom id fields
These are entirely user-defined, and their values are stored inside the components’ custom ids. Custom ids are limited by the discord api to have a maximum length of 100 characters, so the information you can store here is limited!
Important
Both types of fields are created in the same way as normal attrs/dataclass fields. In particular, keep in mind that typehinting fields is required.
The examples in the tabs below show all available custom id parameters for each component type, and provide a very minimal example of how one such component could look.
The disnake-ext-components-equivalent of a disnake.ui.Button
.
1class MyButton(components.RichButton):
2 # All valid internal fields:
3 label: str = "My button"
4 style: disnake.ButtonStyle = disnake.ButtonStyle.primary
5 emoji: str = "\N{WHITE HEAVY CHECK MARK}"
6 disabled: bool = False
7
8 # Custom id fields:
9 foo: int # field without default
10 bar: int = 3 # field with default
11
12 # A callback is required:
13 async def callback(self, inter: disnake.MessageInteraction):
14 await inter.response.send_message("Click!")
Important
Since callbacks are required and URL-buttons cannot have a callback, disnake-ext-components does not support the url
attribute.
The disnake-ext-components-equivalent of a disnake.ui.StringSelect
.
1class MyButton(components.RichStringSelect):
2 # All valid internal fields:
3 placeholder: str = "My string select"
4 min_values: int = 1
5 max_values: int = 2
6 disabled: bool = False
7 options: List[disnake.SelectOption] = [
8 disnake.SelectOption(label="foo", value=1),
9 disnake.SelectOption(label="bar", value=2),
10 ]
11
12 # Custom id fields:
13 foo: int # field without default
14 bar: int = 3 # field with default
15
16 # A callback is required:
17 async def callback(self, inter: disnake.MessageInteraction):
18 await inter.response.send_message("Click!")
The disnake-ext-components-equivalent of a disnake.ui.UserSelect
.
1class MyButton(components.RichUserSelect):
2 # All valid internal fields:
3 placeholder: str = "My user select"
4 min_values: int = 1
5 max_values: int = 2
6 disabled: bool = False
7
8 # Custom id fields:
9 foo: int # field without default
10 bar: int = 3 # field with default
11
12 # A callback is required:
13 async def callback(self, inter: disnake.MessageInteraction):
14 await inter.response.send_message("Click!")
The disnake-ext-components-equivalent of a disnake.ui.RoleSelect
.
1class MyButton(components.RichRoleSelect):
2 # All valid internal fields:
3 placeholder: str = "My role select"
4 min_values: int = 1
5 max_values: int = 2
6 disabled: bool = False
7
8 # Custom id fields:
9 foo: int # field without default
10 bar: int = 3 # field with default
11
12 # A callback is required:
13 async def callback(self, inter: disnake.MessageInteraction):
14 await inter.response.send_message("Click!")
The disnake-ext-components-equivalent of a disnake.ui.ChannelSelect
.
1class MyButton(components.RichChannelSelect):
2 # All valid internal fields:
3 placeholder: str = "My channel select"
4 min_values: int = 1
5 max_values: int = 2
6 disabled: bool = False
7
8 # Custom id fields:
9 foo: int # field without default
10 bar: int = 3 # field with default
11
12 # A callback is required:
13 async def callback(self, inter: disnake.MessageInteraction):
14 await inter.response.send_message("Click!")
The disnake-ext-components-equivalent of a disnake.ui.MentionableSelect
.
1class MyButton(components.RichMentionableSelect):
2 # All valid internal fields:
3 placeholder: str = "My mentionable select"
4 min_values: int = 1
5 max_values: int = 2
6 disabled: bool = False
7
8 # Custom id fields:
9 foo: int # field without default
10 bar: int = 3 # field with default
11
12 # A callback is required:
13 async def callback(self, inter: disnake.MessageInteraction):
14 await inter.response.send_message("Click!")
Since these classes are created using attrs, the __init__
methods for your component classes are automatically generated. If you need further control, you can use attrs features like __attrs_post_init__
to process each instance before they are handled by disnake-ext-components.
Component Managers#
Now that we know how to create components, we need to learn how to hook these components into your bot. Luckily, this is pretty simple.
All we need is a ComponentManager
, which we get using get_manager()
. For basic usage, we just call get_manager()
without arguments and assign it to a variable. We then use add_to_bot()
and pass the bot to it to allow the manager to communicate with the bot.
Finally, we register components to the bot using register()
. This can be done in a few different ways, but for now the easiest way is to just use it as a basic decorator.
1import disnake
2from disnake.ext import commands, components
3
4bot = disnake.Bot(...)
5manager = components.get_manager()
6manager.add_to_bot(bot)
7
8@manager.register
9class MyButton(components.RichButton):
10 label: str = "My Button"
11
12 foo: int # field without default
13 bar: int = 3 # field with default
14
15 async def callback(self, inter: disnake.MessageInteraction):
16 await inter.response.send_message("Click!")
Important
The use of disnake.Client
with disnake-ext-components requires disnake version 2.10.0 or above. On lower versions of disnake, you need to use any of disnake’s bot classes.
Sending Components#
Last but not least, we need to send our components to discord. To do so, we first need to create an instance of our button class. This is simply done by instantiating the class as with any other class. Any custom id fields without a default value must be provided. Since the class is made using attrs, it is fully typehinted, and your typechecker will let you know if you are missing anything.
Tip
We definitely recommend using a type checker! pyright is particularly compatible as disnake-ext-components was developed with it.
Actually sending the component works slightly differently from normal disnake.ui components. We have two options:
Explicit conversion
We can explicitly convert our disnake-ext-components-style component into a disnake.ui-style component using
as_ui_component()
.Interaction wrapping
Alternatively, we can wrap an interaction into a new disnake-ext-components-style interaction, which can automatically deal with disnake-ext-components-style components. This is done using
wrap_interaction()
.Important
Interactions provided to component callbacks will automatically be wrapped. If you plan to use text commands, you must use explicit conversion, as
wrap_interaction()
does not supportcommands.Context
.
The examples in the tabs below show you how either of these options would look. You are free to pick whichever syntax you are more comfortable with.
1class MyButton(components.RichButton):
2 label = "My button"
3
4 foo: int # field without default
5 bar: int = 3 # field with default
6
7 async def callback(self, inter: disnake.MessageInteraction):
8 new_button = await self.as_ui_component()
9 await inter.response.send_message("Click!", components=new_button)
10
11
12@commands.slash_command()
13async def my_command(inter: disnake.MessageInteraction):
14 button = MyButton(foo=1)
15 ui_button = await button.as_ui_component()
16 await inter.response.send_message(components=ui_button)
1class MyButton(components.RichButton):
2 label = "My button"
3
4 foo: int # field without default
5 bar: int = 3 # field with default
6
7 async def callback(self, inter: components.MessageInteraction):
8 await inter.response.send_message("Click!", components=self)
9
10
11@commands.slash_command()
12async def my_command(inter: disnake.MessageInteraction):
13 wrapped = components.wrap_interaction(inter)
14 button = MyButton(foo=1)
15 await inter.response.send_message(components=button)
Note
The interaction in the button callback is now typehinted as a components.MessageInteraction
as opposed to a disnake.MessageInteraction
. This is only relevant for type-checking purposes.
Example#
You know know enough to make a fully functional component with disnake-ext-components! Combining the examples of the above sections nets you the following bot main file:
1import disnake
2from disnake.ext import commands, components
3
4bot = disnake.Bot(...)
5manager = components.get_manager()
6manager.add_to_bot(bot)
7
8@manager.register
9class MyButton(components.RichButton):
10 label: str = "My button"
11
12 foo: int # field without default
13 bar: int = 3 # field with default
14
15 async def callback(self, inter: disnake.MessageInteraction):
16 await inter.response.send_message("Click!", components=self)
17
18
19@commands.slash_command()
20async def my_command(inter: disnake.MessageInteraction):
21 wrapped = components.wrap_interaction(inter)
22 button = MyButton(foo=1)
23 await inter.response.send_message(components=button)
24
25bot.run(...)