Search code examples
pythondiscord.pynextcord

Wait for either a button press or a message response in nextcord


i need a function, let's call it button_or_text. this function returns a certain value if a button is pressed, most likely a str. it returns the Message object for a message has been sent.

what i tried doing:

import nextcord
import nextcord.ext.commands as cmds

class Coggers(cmds.Cog):
    def __init__(self, bot):
        self.bot = bot

    @cmds.command()
    async def test(self, ctx: cmds.Context):
        async def button_or_text():
            class Buttons(nextcord.ui.View):
                def __init__(self):
                    super().__init__()
                    self.value = None
                
                @nextcord.ui.button(label="very mysterious button", style=nextcord.ButtonStyle.green)
                async def very_mysterious_button(self, button: nextcord.ui.Button, interact: nextcord.Interaction):
                    self.stop()
                    # insert function that somehow cancels the wait_for()
                    # return "some value"
        
            view = Buttons()
            await ctx.send("press this very mysterious button or send a message?", view=view)

            check = lambda msg: ctx.author.id == msg.author.id
            message = await self.bot.wait_for("message", check=check)

            return message.content

        
        print(await button_or_text())
        await ctx.send("end")


def setup(bot: cmds.Bot):
    bot.add_cog(Coggers(bot))

here i did a wait_for for the function. if a button was pressed, somehow cancel that wait_for then return the set value for pressing a button. if a message was sent, somehow stop the Buttons object from listening then return the Message object.

i'm scratching my head on how to do both the wait_for cancel and the return, but i feel like this isn't the best way of doing it and i'm missing a more "elegant" way

any help?


Solution

  • Without all details as to what exactly you want to achieve it's hard to give a comprehensive solution, but here's the general idea of how to wait for the first of two events simultaneously:

    async def await_input(self):
        """waits for valid user input (message, button) and returns the corresponding payload
        """
    
        events = [
            self.bot.client.wait_for('message', check=self._check_msg),
            self.bot.client.wait_for('interaction', check=self._check_button)
        ]
    
        # with asyncio.FIRST_COMPLETED, this triggers as soon as one of the events is fired
        done, pending = await asyncio.wait(events, return_when=asyncio.FIRST_COMPLETED)
        event = done.pop().result()
    
        # cancel the other check
        for future in pending:
            future.cancel()
    
        # check if the event has the `application_id` attribute to determine if it was the button press or a message
        if hasattr(event, 'application_id'):
            return 'button pressed'  # or whatever you want to return there
        else:
            return event  # this will be the message
    

    What's left for you to do is to implement the _check_msg (this can most likely be the lambda you already have) and _check_button to make sure you're only getting input you are interested in. You might also want to set a timeout for each of the events unless you want to wait indefinitely