Discord.py wait for message or reaction - python

I'm quite new to both Python and Discord.py, and I'm trying to find how to make the bot wait for either a message or reaction from user at the same time.
I tried separating each but just resulted in the bot needing a message response before a reaction.
Here's a similar code I'm trying to do:
#bot.event
async def on_message(ctx):
if ctx.author == bot.user:
return
if ctx.content == "$respond":
message = await ctx.send("Waiting for response...")
while True:
try:
response = #Will wait for a message(Yes or No) or reaction(Check or Cross) from ctx.author for 30 secs
except asyncio.TimeoutError:
await message.edit(content=f"{ctx.author} did not respond anymore!")
break
if response.content == "yes":
await message.edit(content=f"{ctx.author} said yes!")
continue
elif response.content == "no":
await message.edit(content=f"{ctx.author} said nyo!")
continue
#These are reactions
elif str(response.emoji) == "✅":
await message.edit(content=f"ctx.author reacted ✅!")
continue
elif str(response.emoji) == "❌":
await messave.edit(content=f"Stopped listening to responses.")
break

bot.wait_for is what you're looking for here.
I would recommend using bot.command() for command handling purposes but this is fine as well.
This is how you wait for specific events (provided as the first argument) with specific conditions (as provided in the check param)
#bot.event
async def on_message(msg):
if msg.author == bot.user:
# if the author is the bot itself we just return to prevent recursive behaviour
return
if msg.content == "$response":
sent_message = await msg.channel.send("Waiting for response...")
res = await bot.wait_for(
"message",
check=lambda x: x.channel.id == msg.channel.id
and msg.author.id == x.author.id
and x.content.lower() == "yes"
or x.content.lower() == "no",
timeout=None,
)
if res.content.lower() == "yes":
await sent_message.edit(content=f"{msg.author} said yes!")
else:
await sent_message.edit(content=f"{msg.author} said no!")
This would result in :
Listening for multiple events is a rather a bit interesting,
Replace the existing wait_for with this snippet :
done, pending = await asyncio.wait([
bot.loop.create_task(bot.wait_for('message')),
bot.loop.create_task(bot.wait_for('reaction_add'))
], return_when=asyncio.FIRST_COMPLETED)
and you can listen for two events simultaneously
Here's how you can handle this using #bot.command()
import discord
import os
from discord.ext import commands
bot = commands.Bot(command_prefix="$", case_insensitive=True)
#bot.event
async def on_ready():
print(f"Logged in as {bot.user}")
#bot.command()
async def response(ctx):
sent_message = await ctx.channel.send("Waiting for response...")
res = await bot.wait_for(
"message",
check=lambda x: x.channel.id == ctx.channel.id
and ctx.author.id == x.author.id
and x.content.lower() == "yes"
or x.content.lower() == "no",
timeout=None,
)
if res.content.lower() == "yes":
await sent_message.edit(content=f"{ctx.author} said yes!")
else:
await sent_message.edit(content=f"{ctx.author} said no!")
bot.run(os.getenv("DISCORD_TOKEN"))
which would get you the same result.

Related

How do I return to the first event after the second event gets executed?

#client.event
async def on_message(message):
if message.author == client.user:
return
if message.content == 'Hey Sloime':
if message.author.id == xxx:
await message.channel.send('Yes master?')
elif message.author.id == xxx:
await message.channel.send('Tell me brother')
else:
await message.channel.send('TF u want?')
else:
return
#client.event
async def on_message(message):
if message.author == client.user:
return
if message.content == 'Can I have my Dis ID?':
await message.channel.send(message.author.id)
else:
await message.channel.send('Do I look like I can answer that?')
return
client.run(os.environ['TOKEN'])
It's my first time ever coding in Python, I have experience coding in c, c++, java and sql. My problem is that after I enter the second event on this bot I want to wait for a message corresponding to the first event, but it keeps looping on the second event, I apologize in advance if this is very simple.
Something like this?
#client.event
async def on_message(message):
if message.content == 'i want cheese':
await message.channel.send('how much?')
msg = await client.wait_for('message')
await message.channel.send(f'ok, you want {msg.content} cheese')
# you can do a check like this also
msg = await client.wait_for('message', check=lambda msg: msg.author.id == xxx)
wait_for('message') waits for a message to happen

Bot is not responding to the input given (Discord.py)

I watched a tutorial on how to get Input from users in Discord.py. The bot however, does not respond to the input given to it.
The bot sends the "This is a bot test. Type (YES/NO)" message. But when I type "yes" or "no" it does not respond.
#client.command()
async def bot(ctx):
await ctx.channel.send("This is a bot test. Type (YES/NO)")
try:
message = await bot.wait_for("message", check=lambda m: m.author == ctx.author and m.channel == ctx.channel, timeout=30.0)
except asyncio.TimeoutError:
await ctx.channel.send("I have been ignored")
else:
if message.content.lower() == "yes":
await ctx.channel.send("The test was succesfull!")
elif message.content.lower() == "no":
await ctx.channel.send("Thank you for your response")
else:
await ctx.channel.send("I cannot understand you")
You're decorating with client but calling bot in your function.
Change
#client.command()
async def bot(ctx):
await ctx.channel.send("This is a bot test. Type (YES/NO)")
try:
message = await bot.wait_for("message", check=lambda m: m.author == ctx.author and m.channel == ctx.channel, timeout=30.0)
To
#client.command()
async def bot(ctx):
await ctx.channel.send("This is a bot test. Type (YES/NO)")
try:
message = await client.wait_for("message", check=lambda m: m.author == ctx.author and m.channel == ctx.channel, timeout=30.0)

Repeating a function when someone reacts to it

Trying to make it so when someone reacts to the message with the emoji it repeats the function - trying to find something that works similar to goto in batch files
Eg: I type $randomcar
Bot sends: Your chosen car is the bmw! with the 🔄 reaction. When I press on it it repeats the function again:
Bot sends Your chosen car is the audi! with the 🔄 reaction which I can press on to once again to repeat the function
#bot.command()
async def randomcar(ctx):
while True:
msg = await ctx.send("Your chosen car is the {}!".format(random.choice(cars)))
await msg.add_reaction("🔄")
async def on_raw_reaction_add(payload: discord.RawReactionActionEvent):
if msg.reaction=="🔄" and msg.id:
continue
I am getting the error:
Syntax error: continue not properly in loop
Thanks
This can be done using recursion:
#bot.command()
async def randomcar(ctx):
msg = await ctx.send("Your chosen car is the {}!".format(random.choice(cars)))
# you may need to check if your bot has the "add reactions" permission
await msg.add_reaction("🔄")
await bot.wait_for(
"reaction_add",
check=lambda reaction, user: user == ctx.author and reaction.emoji == "🔄" and reaction.message == msg,
)
await randomcar(ctx)
You could also add a timeout after which it will stop repeating:
#bot.command()
async def randomcar(ctx):
msg = await ctx.send("Your chosen car is the {}!".format(random.choice(cars)))
await msg.add_reaction("🔄")
try:
await bot.wait_for(
"reaction_add",
timeout=10.0, # 10 second timeout
check=lambda reaction, user: user == ctx.author and reaction.emoji == "🔄" and reaction.message == msg,
)
except asyncio.TimeoutError:
try:
await msg.remove_reaction("🔄", bot.user)
except discord.NotFound:
pass
else:
await randomcar(ctx)

How to stop a command during execution discord.py

#client.command()
async def spam(ctx):
while True:
time.sleep(2)
await ctx.send("spam")
#client.event
async def on_message(message):
if message.content == "!stop":
# stop spamming
I want the code to stop spamming but I'm not sure how.
Try this example using wait_for:
#client.command()
async def spam(ctx):
def check(m):
return m.author == ctx.author and m.channel == ctx.channel
stopped = False
await ctx.send("spam")
while not stopped:
try:
message = await client.wait_for("message", check=check, timeout=2)
stopped = True if message.content.lower() == "!stop" else False
except asyncio.TimeoutError: # make sure you've imported asyncio
await ctx.send("spam")
await ctx.send("spam finished!")
References:
Client.wait_for()
Message.author
Message.channel

How to cancel a pending wait_for

Assuming a command similar to this:
#bot.command()
async def test(ctx):
def check(r, u):
return u == ctx.message.author and r.message.channel == ctx.message.channel and str(r.emoji) == '✅'
await ctx.send("React to this with ✅")
try:
reaction, user = await bot.wait_for('reaction_add', timeout=300.0, check=check)
except asyncio.TimeoutError:
await ctx.send('Timeout')
else:
await ctx.send('Cool, thanks!')
Is there any way to cancel that wait_for if the user sends the same command multiple times before actually reacting to the message? So the bot stop waiting for reactions on previously sent messages and only waits for the last one.
Would something like this work for you?
pending_tasks = dict()
async def test(ctx):
def check(r, u):
return u == ctx.message.author and r.message.channel == ctx.message.channel and str(r.emoji) == '✅'
await ctx.send("React to this with ✅")
try:
if ctx.message.author in pending_tasks:
pending_tasks[ctx.message.author].close()
pending_tasks[ctx.message.author] = bot.wait_for('reaction_add', timeout=300.0, check=check)
reaction, user = await pending_tasks[ctx.message.author]
except asyncio.TimeoutError:
await ctx.send('Timeout')
else:
await ctx.send('Cool, thanks!')
You store all of the pending requests in a dict, and before creating another request you check if you are already have an existing task for this user, if you do you cancel it and create a new one

Categories