0
votes

EDIT:

The solution was to add an additional condition in the check function, I have posted the details in a follow-up answer.

Main Question:

I am new python and decided to learn it by creating a discord bot.

On user command, I was able to get the bot to send a message that can change pages on react following this design:

I want to make a multi-page help command using discord.py

I can change the pages of one message just fine, however, if the user calls the command multiple times, reacting to one of the bot sent messages change the pages for all messages that haven't timed out. What can I change in the code so that reacting to one message won't trigger page changes in the other ones?

Additional questions:

def check(reaction, user):
        return user == ctx.author and str(reaction.emoji) in ["◀️", "▶️"]
        # This makes sure nobody except the command sender can interact with the "menu"
  • I understand that wait_for will use this function as a check, but where and how are the check parameters being passed in? How could I add more parameters if possible?
reaction, user = await bot.wait_for("reaction_add", timeout=60, check=check)
  • In the documentation, I couldn't pin down the return value of the wait_for method, it looks like it changes under different circumstances.
  • We aren't passing in a message instance into the wait_for method, so how does the method know which message to wait for a reaction on?
  • What happens to the flow of the program if the check fails? It appears that statements under this line are executed only if check() returns true. But what happens when the check fails?
2

2 Answers

1
votes

A better alternative than the method one you provided is the use of ext.menus (It's in beta, so it doesn't have any docs yet, to install it python -m pip install -U git+https://github.com/Rapptz/discord-ext-menus)

Example

from discord.ext import menus

class MyMenu(menus.Menu):
    async def send_initial_message(self, ctx, channel):
        return await channel.send(f'Hello {ctx.author}')

    @menus.button('\N{THUMBS UP SIGN}')
    async def on_thumbs_up(self, payload):
        await self.message.edit(content=f'Thanks {self.ctx.author}!')

    @menus.button('\N{THUMBS DOWN SIGN}')
    async def on_thumbs_down(self, payload):
        await self.message.edit(content=f"That's not nice {self.ctx.author}...")

    @menus.button('\N{BLACK SQUARE FOR STOP}\ufe0f')
    async def on_stop(self, payload):
        self.stop()

# later
@bot.command()
async def menu_example(ctx):
    m = MyMenu()
    await m.start(ctx)

Unfornatelly I can't answer your first question, I'm not sure why's that happening, sorry.

Answering your additional questions:

  1. wait_for takes the same arguments as the event, on_message takes message so the check will take message as the single argument (the method will also only return message). Adding more arguments it's pretty similar to the basics of decorators, we wrap the check in another outer function
def check(func): # The ADDITIONAL arguments we want to pass, in this example another function
    def inner_check(reaction, user): # The actual check
        return user == ctx.author and func(reaction, user)
    return inner_check

# To use it                                                     Note how I'm calling the check an additional argument
reaction, user = await bot.wait_for('reaction_add', check=check(lambda r, u: return True), timeout=60.0)

Python decorator basics

  1. Same as the first question
  2. The bot waits for any reaction to be added, if the check function returns True it will return the values
  3. If the check returns other than True the bot will wait till the function returns True or the timeout is over
0
votes

Thanks to Łukasz Kwieciński for answering my additional questions. Because I learned that wait_for waits for any reaction to any message, I've added an additional condition to the check() function. Now each message is independent of one another.

message = await ctx.send(f"Page {cur_page}/{pages}:\n{contents[cur_page-1]}")
    # getting the message object for editing and reacting

def check(reaction, user):
        if reaction.message != message:
            return false
            # SOLUTION: Checks if the message reacted on is the same as the one the bot sent
        
        return user == ctx.author and str(reaction.emoji) in ["◀️", "▶️"]
        # This makes sure nobody except the command sender can interact with the "menu"

However, this fix creates a possible performance problem. For every bot message that hasn't timed out yet, reacting to any message on the server causes it to run check() that many times.