In discord.py, persistent views allow a bot to handle interactions from message components (i.e. buttons and select menus) after the bot has restarted.
For a view to be persistent, all its components must have a custom ID and the view must have its timeout set to None. This can look something like:
class CreateTicketView(discord.ui.View):
@discord.ui.button(label="Submit a Ticket", custom_id="create-ticket")
async def on_create(self, interaction, button):
...
view = CreateTicketView(timeout=None)
await ctx.send("Need support from staff?", view=view)
Once your bot restarts, you must add back your persistent view so discord.py knows which methods should be called when the same components are interacted with. This can be done with the Client.add_view() method:
class MyClient(discord.Client): # or commands.Bot
async def setup_hook(self):
view = CreateTicketView(timeout=None)
self.add_view(view)
Once you start your bot, this will handle interactions from all messages that
had CreateTicketView
sent with them.
This works well for views that are stateless, meaning the view doesn't have any attributes that need to be different between two messages with the same view. However, sometimes you will need to implement views that require state. The following example shows a view with a button that can only be used by a particular user:
class GreetingView(discord.ui.View):
def __init__(self, user_id):
super().__init__(timeout=None)
self.user_id = user_id
@discord.ui.button(label="Greet", custom_id="greet")
async def greet(self, interaction, button):
mention = interaction.user.mention
await interaction.response.send_message(f"Greetings {mention}!", ephemeral=True)
async def interaction_check(self, interaction):
if interaction.user.id != self.user_id:
await interaction.response.send_message(
f"This button is only for greeting <@{self.user_id}>!",
ephemeral=True,
)
return False
return True
To handle a view like this after startup, you will need to pass the original
arguments to __init__()
and give discord.py the same message ID that
you sent with your view.
For a few messages, you can manually hardcode those values like so:
class MyClient(discord.Client):
async def setup_hook(self):
greet_erica = GreetingView(user_id=297463612870492160)
self.add_view(greet_erica, message_id=1165036547050057789)
greet_jack = GreetingView(user_id=581280216836734988)
self.add_view(greet_jack, message_id=1165037150992085143)
Of course if your bot sends new views often, you probably don't want to edit your code each time to persist them, so it's a good idea to use a database for this.
Below this readme are two examples, one implementing a stateless view and another implementing a stateful view, using sqlite3 to store the view's state on disk.