This gist shows you how to use message components in discord.py 2.0
This assumes that you know how Object Orientated Programming in Python works, if you don't know what this is then I recommend that you read this guide.
You'll need to install discord.py from git to use this (if you don't have git then google how to install it for your OS, I can't be bothered to put it in here...), if you already have this then you can skip to the next section. However if you don't please read the below
# on windows
py -3 -m pip install git+https://github.com/rapptz/discord.py.git
# on linux/osx
python3 -m pip install git+https://github.com/rapptz/discord.py.git
look at the below table for what you want the values to be
If you want have buttons in messages that your bot sends you have to pass the view
kwarg to any .send
function. This view
kwarg has to be of type discord.ui.View
or inherit from it. This way you can subclass discord.ui.View
and add buttons from that e.g.
class MyView(discord.ui.View):
@discord.ui.button(label='A button', style=discord.ButtonStyle.primary)
async def button_callback(self, interaction, button):
await interaction.response.send_message('Button was pressed', ephemeral=True)
# somewhere else...
view = MyView()
await something.send('Press the button!', view=view)
Note: if you want to override
MyView
's__init__
method you will need to add
super().__init__()
to the code in the
__init__
method otherwise the View will not work!!
Button styles can be one of the following:
Buttons can also have emojis at the start, you can use the emoji
keyword argument in the discord.ui.button
decorator to set this. As well as this buttons can be disabled with the enabled
kwarg and you can also disable a button when pressed like this:
class MyView(discord.ui.View):
@discord.ui.button(label='A button', style=discord.ButtonStyle.primary)
async def button_callback(self, interaction, button):
button.disabled = True
button.label = 'No more pressing!'
await interaction.response.edit_message(view=self)
# somewhere else...
view = MyView()
await something.send('Press the button!', view=view)
This looks like this:
If you want a URL button then you can simply do this (note that you MUST have the style set to discord.ButtonStyle.url
and URL buttons cannot have a callback since they are handled at the client - this also means that URL buttons don't have a custom id as well) e.g.
view = discord.ui.View()
view.add_item(discord.ui.Button(label='Go to website', url='https://example.com/', style=discord.ButtonStyle.url))
await something.send('Press the button!', view=view)
URL buttons look like this:
You can also just create an instance of discord.ui.View
and call add_item
passing a discord.ui.Button
object to it (this can also be a subclass of discord.ui.Button
)e.g.
class MyButton(discord.ui.Button):
def __init__(self):
super().__init__(label='A button', style=discord.ButtonStyle.primary)
async def callback(self, interaction):
await interaction.response.send_message('Button was pressed', ephemeral=True)
# somewhere else...
view = discord.ui.View()
view.add_item(MyButton())
await something.send('Press the button!', view=view)
You can have select menus in views as well, for this you can use the discord.ui.select
decorator (but the recommended way is subclassing it because it looks easier on the eye). You access the selected values through select.values
e.g.
class MyView(discord.ui.View):
@discord.ui.select(placeholder='Pick your colour', min_values=1, max_values=1, options=[
discord.SelectOption(label='Red', description='Your favourite colour is red', emoji='🟥'),
discord.SelectOption(label='Green', description='Your favourite colour is green', emoji='🟩'),
discord.SelectOption(label='Blue', description='Your favourite colour is blue', emoji='🟦')
])
async def select_callback(self, intersction, select):
await interaction.response.send_message(f'Your favourite colour is {select.values[0]}', ephemeral=True)
# Somewhere else...
view = MyView()
await something.send('What is your favourite colour?', view=view)
Select menus look like this:
Like discord.ui.Button
you can also subclass discord.ui.Select
e.g.
class MySelect(discord.ui.Select):
def __init__(self):
options = [
discord.SelectOption(label='Red', description='Your favourite colour is red', emoji='🟥'),
discord.SelectOption(label='Green', description='Your favourite colour is green', emoji='🟩'),
discord.SelectOption(label='Blue', description='Your favourite colour is blue', emoji='🟦')
]
super().__init__(placeholder='Pick your colour', min_values=1, max_values=1, options=options)
async def callback(self, interaction):
await interaction.response.send_message(f'Your favourite colour is {self.values[0]}', ephemeral=True)
# somewhere else...
view = discord.ui.View()
view.add_item(MySelect())
await something.send('What is your favourite colour?', view=view)
Another common thing to do is to pass a Context
object to the view e.g.
class MyView(discord.ui.View):
def __init__(self, ctx):
self.context = ctx
super().__init__()
...
# somewhere else...
view = MyView(ctx)
then you can override the interaction_check
method of MyView
like so
async def interaction_check(self, interaction):
if interaction.user != self.context.author:
return False
return True
to only allow the invoker of the command to use the view.
You can add timeouts to views meaning that they will expire and you will not be able to interact with them after x seconds. When the timeout expires the on_timeout
method is called e.g.
class MyView(discord.ui.View):
def __init__(self):
super().__init__(timeout=60)
async def on_timeout(self):
for child in self.children:
child.disabled = True
await self.message.edit(view=self)
...
# somewhere else...
view = MyView()
view.message = await something.send(..., view=view)
If an error happens in a button/select callback then by default, it will not be handled and an "interaction failed" message will appear to the user. If you want to customize this then you can use the on_error
method that gets called whenever something fails within the view e.g.
import traceback
class MyView(discord.ui.View):
@discord.ui.button(label='A button', style=discord.ButtonStyle.primary)
async def button_callback(self, interaction, button):
raise RuntimeError('Exception was raised')
async def on_error(self, error, item, interaction):
await interaction.response.defer()
exception = '\n'.join(traceback.format_exception(type(error), error, error.__traceback__))
exception = f'```py\n{exception}```'
await interaction.channel.send(f'An error occured in the button `{item.custom_id}`:\n{exception}')
# somewhere else...
view = MyView()
await ctx.send('Press the button!', view=view)
This looks like this which I personally think looks better than the normal "interaction has failed" message since it is more verbose and tells the user what exactly has gone wrong (not that they really need to know but it is still nice to know how to do this):
A message can have up to five "action rows" and each of these "action" rows have five slots where you can put message components. A button takes up one of these slots but a select menus takes up all five slots of a "action row". Keep this in mine when creating your views since you don't want to run out of space!
You must respond to an interaction within 3 seconds or the interaction token will be invalidated (this can be simply deferring it). You then have 15 minutes to send any follow-up messages (using the interaction.followup
webhook) since that is how long the interaction token lasts for once you have responded the first time. Also, pycord automatically defers the interaction for you if you have not responded yourself in the component callback so make sure not to spend too long sleeping/doing stuff in it!
Amazing work! Thank you!