Search code examples
discorddiscord.pydiscord-buttons

Prevent users from clicking Discord.py button twice


I have a channel that has an embed with a button, when the button is clicked it brings up an ephemeral survey.

If a user clicks the initial button multiple times then the survey will come up as many times. Is there a way to prevent this? The first button isn't ephemeral, everyone can use it so I can't really disable it after its clicked.

Is there a way to deactivate the button for that particular user or timeout the user from clicking it again?


    class surveyView(discord.ui.View):
    def __init__(self):
        super().__init__(timeout=None)
        self.value = None


    @discord.ui.button(label="Let's get started!", 
                       style=discord.ButtonStyle.blurple)

    async def survey(self, interaction:discord.Interaction, button:discord.ui.Button):

        view = questions()
        await interaction.response.send_message(content=f"", embed=surveyEmbed, 
                                                view=view, ephemeral=True)

Solution

  • There's no way to disable buttons natively for a user and not for others if the view is visible to everyone in the channel (aka, not ephemerally). But you could easily prevent users from clicking it multiple times with a bit of code.

    class SurveyView(discord.ui.View):
        def __init__(self):
            super().__init__(timeout=None)
            self.value = None
            self.clicked_users = []
    
        @discord.ui.button(
            label="Let's get started!", 
            style=discord.ButtonStyle.blurple
        )
        async def survey(self, interaction:discord.Interaction, button:discord.ui.Button):
    
            user_id = interaction.user.id
            if user_id in self.clicked_users:
                # user has already pressed the button
                await interaction.response.send_message(content=f"You have already pressed this button/done this survey.", ephemeral=True)
                return
    
            self.clicked_users.append(user_id)
            view = questions()
            await interaction.response.send_message(
                content=f"", embed=surveyEmbed, view=view, ephemeral=True
            )
    

    We define a variable called clicked_users in the view which is an empty list. Every time a user presses the button, we check if their user ID is already in the list. If it is, we tell them as such and exit. Otherwise, we do what you want to do and add the user to the list.

    One possible downside, is that if the questions view expires and the user doesn't complete the survey, then they won't be able to press the button again. You could mitigate that by passing a reference to this view to the questions view and removing the user ID from clicked_users if the view expires or the user cancels, etc.

    Example passing reference to questions:

    class QuestionsView(discord.ui.View):
        def __init__(self, survey, user_id):
            # whatever your super and other __init__ stuff is
            self.survey = survey
            self.user_id = user_id
    
        async def on_timeout(self):
            self.survey.users_clicked.remove(self.user_id)
    
        # rest of your code
    

    Then, in async def survey when we create it:

    view = QuestionsView(self, user_id)

    Now, the view has a reference to the "survey view" and the user that clicked it. When it times out, it will remove the user ID.

    Alternatively, instead of using self.clicked_users = [], we can have a database that tracks which users have done the survey. And use that to check if they can press the button or not.