3

Recently i decided to rewrite my discord bot and add buttons also. the main problem i encountered about this so far, i can't a disable a button just after being pressed people told be about button.disabled=True and in deed, it will disabling the button, but it's just sending it disabled, so it can't never be pressed. What i want is to be able to click it and do it's thing and then disable it.

As a reference i'll put some of the code

I use disnake, a discord.py fork, it does have the same syntaxes as dpy but we have buttons and slash commands, dropdown menus, etc

class BlurpleButton(Button):
    def __init__(self, label, emoji=None):
        super().__init__(label=label, style=discord.ButtonStyle.blurple, emoji=emoji)

this is to use easier the buttons, i created a template and i can use it on any command

class CustomView(View):
    def __init__(self, member: disnake.Member):
        self.member = member
        super().__init__(timeout=180)

    async def interaction_check(self, inter: disnake.MessageInteraction) -> bool:
        if inter.author != self.member:
            await inter.response.send_message(content="You don't have permission to press this button.", ephemeral=True)
            return False
        return True

and this is for buttons being able to be pressed just by a mentioned member for example if i do /test @member (i migrated to slash commands due to discord new privileged intent) just the member will be able to press it and no one else.

So far so good all working fine, now after we "assemble" this in a command

@commands.slash_command(description='test')
    async def test(self, inter):

         (do stuff in there)
         . . .
        button1 = BlurpleButton("Button name")
        view=CustomView(member)
        view.add_item(button1)

        async def button_callback(inter):
            await inter.send(embed=embedname2)

        button1.callback = button_callback
        await inter.send(embed=embed1, view=view)

Now again, this piece of code it's doing what it's intended to do, sends an embed (let's just say where i put the . . . are few embeds) and attached to that embed we have button1 when it's clicked it sends embedname2 and there is where things are not working anymore, i keep trying in any ways after the embedname2 it's being sent, the button to disable itself by being clicked one time if i add button1.disabled=True in the callback, the button it will just be sent disabled without any possibility of being clicked. The main reason i put the callback inside the command is to be able to use embeds when the button triggers, if i put it in the subclassed button or view i can't do that anymore.

So this is my whole problem, if you know a better resolving to thing that includes the embed using and just members can press the button, please tell me, i have over a week trying to solve this and i can't get it right

Ares
  • 157
  • 1
  • 17
  • I did exactly this but with a different components module called `discord-ui`. The logic should be very similar and it should flow something like this: Send embed, button is clicked, message is then edited to have the same embed and button however the button is set to disabled now, embed2 is sent – Roopesh-J Feb 03 '22 at 21:43
  • To be more specific, once the button is pressed it should set itself to disabled and then edit the message to send the exact same components. Because you are sending the same components and the button properties are updated, the button should send as disabled. If you are having issues, making sure you are passing the correct components for the edited message since it is easy to make the mistake of sending a copy of the original button and not the updated one. – Roopesh-J Feb 03 '22 at 21:46
  • If a simpler version to test/practice is to have the message be edited, after the button is clicked, to not have the button at all. So it would end up just being the original embed. Good luck! – Roopesh-J Feb 03 '22 at 21:47
  • if i try to edit it i get ``This interaction has already been responded to before`` – Ares Feb 04 '22 at 00:13
  • Hm, that is odd. Again I don't know how `disnake` works, but there could be a simple workaround. You don't have to respond to the interaction twice. Is it possible to do edit the message and send another embed within the same response? If not it may be possible to do atleast one of those things and then do the other outside the interaction. The one outside the interaction would have to be placed in a conditional though. – Roopesh-J Feb 04 '22 at 02:14
  • If you try the above and doesn't work or are stuck on how to get it to work, edit your post to include the code you tried. Also if you haven't yet I would ask the `disnake` discord as well, there is most likely someone who has done what you are trying to do. – Roopesh-J Feb 04 '22 at 02:16

3 Answers3

6

Explanation

You can accomplish this by setting button.disabled to True in your button's callback. Then, you also would need to edit your original message to reflect this change.

Code

    @commands.slash_command(description='test')
    async def test(self, slash_inter: disnake.ApplicationCommandInteraction, member: disnake.Member):

        view = CustomView(member)
        button1 = BlurpleButton("TEST")
        view.add_item(button1)

        async def button_callback(button_inter: disnake.MessageInteraction):
            button1.disabled = True
            await button_inter.send(embed=embedname2)
            await slash_inter.edit_original_message(view=view)

        button1.callback = button_callback

        await slash_inter.send(embed=embed1, view=view)

Note: For /test @member to work, you need to add a disnake.Member parameter to your slash command.

Reference

disnake.MessageInteraction

disnake.ui.Button.callback

disnake slash commands

TheFungusAmongUs
  • 1,423
  • 3
  • 11
  • 28
  • Finally, a good answer, with this chance you helped me to see the main problem in my code ``inter`` was used both in the callback and the slash command arg, so they probably overwritten and it was not working, thank you – Ares Feb 07 '22 at 17:57
0

I use Pycord, not Disnake, but this should work. You might have to change the exact method call here or in your button callback.

async def interaction_check(self, inter: disnake.MessageInteraction) -> bool:
    if inter.author != self.member:
        await inter.response.send_message(content="You don't have permission to press this button.", ephemeral=True)
        return False

    # The interaction is allowed, so let's disable the button.
    # Interactions have a data field that stores the custom ID of the
    # component sent the interaction. We will use this to find our button
    # in the view.

    button_id = iter.data["custom_id"]
    [child for child in self.children if child.custom_id == button_id][0].disabled = True
    await inter.edit_original_message(view=self)

    # From this point on, you will be dealing with inter.followup, since
    # an interaction can only be responded to once.
    
    return True
tiltowait
  • 100
  • 1
  • 7
  • i tried your version and got hit by multiple errors such ``Unresolved reference 'child'`` , ``Cannot find reference 'data' in 'function | function | function'`` , ``Expected type 'collections.Iterable', got 'bool' instead`` , ``'in' expected`` (for loop error) Instead i tried to rearrange it https://pastebin.com/wuRSYFvd but then i got ``This interaction has already been responded to before`` which i don't understand why – Ares Feb 04 '22 at 22:18
  • @Ares I had a typo in my code, sorry. It should be `[child for child in self.children if child.custom_id == button_id]`. – tiltowait Feb 04 '22 at 22:43
  • As for the interaction being responded to before, you need to use `interaction.followup.send()`. – tiltowait Feb 04 '22 at 22:44
  • uh, now there's ``button_id = iter.data["b1"] AttributeError: 'builtin_function_or_method' object has no attribute 'data'`` i try to resolve a thing and another appears, also ``Unresolved attribute reference 'disabled' for class 'Item'`` – Ares Feb 04 '22 at 23:09
  • It would seem that, though they are both forks from discord.py, Disnake and Pycord differ enough in interactions as to be incompatible. Sorry! – tiltowait Feb 05 '22 at 03:21
-1

I tend to, and would recommend, creating new classes for each view callback. This way I can deal with multiple buttons easier and in a way that is easier to read. Feel free to edit my code accordingly, I'll leave comments as directions

To start, give your BlurpleButton a custom ID, or let the user define one inside the class

(label=label, style=discord.ButtonStyle.blurple, emoji=emoji, custom_id="bttn1") # bttn1 is your custom ID, you'll use it to select this button from the view when disabling it or modifying its contents

In you callback, try this code:

b = [x for x in self.children if x.custom_id == "bttn1"][0] # Selects the blurpleButton from the view's buttons based on the custom_id
b.disabled = True # Disable

Then finish it off by editing your message, with this new view

await inter.response.edit_message(view=self) # Edit that the buttons are attached to, but replacing the view with the new one containing our edits (disabling the button)
TheFungusAmongUs
  • 1,423
  • 3
  • 11
  • 28
Trent112232
  • 169
  • 8
  • "Cannot assign to list comprehension" i don't quite understand that for loop, i would appreciate if you could show an example on my code above or how exactly it should be applied, thank you – Ares Feb 01 '22 at 21:49
  • There is a typo in the code, it should be `x.custom_id == "bttn1"`. Then, this should be the correct answer. – TheFungusAmongUs Feb 04 '22 at 23:39
  • @Ares You should try it again. The code above is working for me. – TheFungusAmongUs Feb 06 '22 at 01:28
  • @TheFungusAmongUs ``Unresolved reference children`` that's why i asked for a clear example applied on what i provided above, because to me it doesn't makes sense – Ares Feb 06 '22 at 13:26