Dominik
Dominik

Reputation: 3602

How do I check and wait for a reaction under a message?

I have an event where an embed gets multiple reactions. Here the user has x-seconds to select a reaction. If this is "wrong" an error should be spit out. If the reaction is the desired one, defined by me, another message should appear. I have this code, but I can't get any further:

        await embedX.add_reaction("1️⃣")
        await embedX.add_reaction("2️⃣")
        await embedX.add_reaction("3️⃣")
        try:
            reaction = await self.bot.wait_for('reaction_add', timeout=10)
            if str(reaction.emoji) == "1️⃣":
                await ctx.send("Congrats, right answer.")
            else:
                await ctx.send("Incorrect reaction.")
        except asyncio.TimeoutError:
            await ctx.send("Took too long.")

Error:

AttributeError: 'tuple' object has no attribute 'emoji'.

I'm still realtively new to Python, so I hope to find help here.

After I got help my code now looks like the following:

        e = discord.Embed(color=discord.Color.gold())
        e.title = "Woah, look!"
        e.description = "**Does this work?**"
        e.add_field(name="1️⃣", value="Yes", inline=False)
        e.add_field(name="2️⃣", value="No", inline=False)
        e.add_field(name="3️⃣", value="Maybe", inline=False)
        e.set_footer(text="You have 10 seconds to answer the question", icon_url=self.bot.user.avatar_url)
        e.timestamp = datetime.datetime.utcnow()
        embedX = await ctx.send(embed=e)
        await embedX.add_reaction("1️⃣")
        await embedX.add_reaction("2️⃣")
        await embedX.add_reaction("3️⃣")
        try:
            reaction, user = await self.bot.wait_for('reaction_add', timeout=10)
            if user == self.bot.user:
                return
            if str(reaction.emoji) == "1️⃣":
                await embedX.edit(embed=er)
            else:
                await ctx.send(embed=ew)
        except asyncio.TimeoutError:
            await embedX.edit(embed=etl)

Sometimes the bot counts the reaction, just 1 - to the others he does not react, but what he is not doing is: Giving out/Editing the embed if the reaction is wrong. (er and ew are actually other embeds I defined before.)

Upvotes: 4

Views: 385

Answers (1)

ZeroKnight
ZeroKnight

Reputation: 528

Your error message should give you a hint: you're receiving a tuple from self.bot.wait_for and not a discord.Reaction.

When using Client.wait_for, keep in mind what it returns (emphasis mine):

Returns no arguments, a single argument, or a tuple of multiple arguments that mirrors the parameters passed in the event reference.

Looking at the on_reaction_add event, we can see that this event's payload is a discord.Reaction and a discord.User object. When using wait_for(<event>), you get the same payload as if you were to extend on_<event> and handle it that way.

So putting it all together, this line:

await self.bot.wait_for('reaction_add', timeout=10)

Returns a tuple with a Reaction and a User, e.g. (<Reaction ...>, <User ...>). So you just need to make a slight modification:

reaction, user = await self.bot.wait_for('reaction_add', timeout=10)

Here we use tuple unpacking (or multiple assignment, if you prefer) to extract the desired values from the returned tuple.

Avoiding Feedback Loops

Since you'll receive all events, including those caused by your bot, you'll need to make sure that you ignore the event when it's caused by your bot to avoid a feedback loop. You can trivially guard against this in your event handlers with the following:

# Assuming `self` is an instance of `discord.Client`
if user == self.user:  # `user` here comes from the event payload
    return

As a concrete example for your situation:

reaction, user = await self.bot.wait_for('reaction_add', timeout=10)
if user == self.bot.user:
    return

Upvotes: 2

Related Questions