Repeating a function when someone reacts to it - python

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)

Related

Discord.py wait for message or reaction

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.

Discord.py kick command not working (missing perms error)

I am currently working on a kick command that does a double check with the user before carrying out action for my discord bot, and came up with this:
#bot.command()
#commands.has_permissions(manage_guild=True)
async def kick(ctx,
member: discord.Member = None,
*,
reason="No reason provided"):
server_name = ctx.guild.name
user = member
if member == None:
await ctx.send(
f'{x_mark} **{ctx.message.author.name},** please mention somebody to kick.')
return
if member == ctx.message.author:
await ctx.send(
f'{x_mark} **{ctx.message.author.name},** you can\'t kick yourself, silly.')
return
embedcheck = discord.Embed(
title="Kick",
colour=0xFFD166,
description=f'Are you sure you want to kick **{user}?**')
embeddone = discord.Embed(
title="Kicked",
colour=0x06D6A0,
description=f'**{user}** has been kicked from the server.')
embedfail = discord.Embed(
title="Not Kicked",
colour=0xEF476F,
description=f'The kick did not carry out.')
msg = await ctx.send(embed=embedcheck)
await msg.add_reaction(check_mark)
await msg.add_reaction(x_mark)
def check(rctn, user):
return user.id == ctx.author.id and str(rctn) in [check_mark, x_mark]
while True:
try:
reaction, user = await bot.wait_for(
'reaction_add', timeout=60.0, check=check)
if str(reaction.emoji) == check_mark:
await msg.edit(embed=embeddone)
await user.kick(reason=reason)
if reason == None:
await user.send(
f'**{user.name}**, you were kicked from {server_name}. No reason was provided.'
)
else:
await user.send(
f'**{user.name}**, you were kicked from {server_name} for {reason}.'
)
return
elif str(reaction.emoji) == x_mark:
await msg.edit(embed=embedfail)
return
except asyncio.TimeoutError:
await msg.edit(embed=embedfail)
return
However when I do this, I get the error:
raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: Forbidden: 403 Forbidden (error code: 50013): Missing Permissions
I have no clue why this is happening, as the bot has every permission checked, as do I, and I am the server owner. Any help would be appriciated, thank you.
Update: I found the error, when running the command it would try to kick the user of the command instead of the specified member. Here is my updated code:
#commands.command()
#commands.has_permissions(kick_members = True)
#commands.bot_has_permissions(kick_members = True)
async def kick(self, ctx, member: discord.Member, *, reason="No reason provided"):
server_name = ctx.guild.name
if member == ctx.message.author:
await ctx.send(
f'{x_mark} **{ctx.message.author.name},** you can\'t kick yourself, silly.')
return
embedcheck = discord.Embed(
title="Kick",
colour=0xFFD166,
description=f'Are you sure you want to kick **{member}?**')
embeddone = discord.Embed(
title="Kicked",
colour=0x06D6A0,
description=f'**{member}** has been kicked from the server.')
embedfail = discord.Embed(
title="Not Kicked",
colour=0xEF476F,
description=f'The kick did not carry out.')
msg = await ctx.send(embed=embedcheck)
await msg.add_reaction(check_mark)
await msg.add_reaction(x_mark)
def check(rctn, user):
return user.id == ctx.author.id and str(rctn) in [check_mark, x_mark]
while True:
try:
reaction, user = await self.bot.wait_for(
'reaction_add', timeout=60.0, check=check)
if str(reaction.emoji) == check_mark:
await msg.edit(embed=embeddone)
await member.kick(reason=reason)
await member.send(f'**{member.name}**, you were kicked from {server_name} for {reason}.')
return
elif str(reaction.emoji) == x_mark:
await msg.edit(embed=embedfail)
return
except asyncio.TimeoutError:
await msg.edit(embed=embedfail)
return
#kick.error
async def kick_error(self, ctx, error):
if isinstance(error, commands.MissingRequiredArgument):
await ctx.send(f'{x_mark} **{ctx.message.author.name}**, you need to mention someone to kick.')
elif isinstance(error, commands.BadArgument):
await ctx.send(f'{x_mark} **{ctx.message.author.name}**, I could not find a user with that name.')
else:
raise error
This issue is that you are checking for manage_guild which is not the correct permission. Also keep in mind you are mixing up bot and commands
#bot.command()
#bot.has_permissions(kick_user = True) # to check the user itself
#bot.bot_has_permissions(kick_user = True) # to check the bot
async def kick(ctx):
Remember to allow all intents like this
intents = discord.Intents().all()
bot = commands.Bot(command_prefix="$", intents=intents)

How to make a Discord.Py random reaction game

How can I make the bot choose a right random answer (as a reaction)
And if the user gets it right send a winner message
if its wrong send a loser message.
redcrew = '<:redcrewmates:776867415514153031>'
bluecrew = '<:bluecrewmates:776867439085617153>'
limecrew = '<:limecrewmates:776867489866711041>'
whitecrew = '<:whitecrewmates:776867529900425217>'
await msg1.add_reaction(redcrew)
await msg1.add_reaction(bluecrew)
await msg1.add_reaction(limecrew)
await msg1.add_reaction(whitecrew)
def check(reaction, user):
return user == ctx.author and str(reaction.emoji) ==
try:
reaction, user = await client.wait_for('reaction_add', timeout=60.0, check=check)
except asyncio.TimeoutError:
await ctx.send('Sorry, you took too long to vote!')
else:
await ctx.send('hello')
def setup(bot):
bot.add_cog(games(bot))```
**DONT WORRY ABOUT THE INDENTS**
import random
#bot.command()
async def reaction_game(ctx):
reactions = [
'<:redcrewmates:776867415514153031>',
'<:bluecrewmates:776867439085617153>',
'<:limecrewmates:776867489866711041>',
'<:whitecrewmates:776867529900425217>'
]
winning_reaction = random.choice(reactions)
message = await ctx.send('some message')
for reaction in reactions:
await ctx.add_reaction(reaction)
def check(reaction, user):
return user == ctx.author and str(reaction) == winning_reaction
try:
reaction, user = await bot.wait_for('reaction_add', check=check, timeout=60.0)
await ctx.send('You reacted with the winning emoji')
except asyncio.TimeoutError:
await ctx.send('You took too long to react')
else:
await ctx.send('hello')

how to make the bot respond only to add_reaction on the same message?

def isAdmin(reaction, user):
for i in admins:
if user.id == i: return (str(reaction.emoji) == '👍' or str(reaction.emoji) == '👎')
try: reaction, user = await self.bot.wait_for('reaction_add', timeout=43200, check=isAdmin)
except asyncio.TimeoutError: await msg.edit(embed=embedEnd)
else:
resign_channel = discord.utils.get(ctx.guild.channels, name="resignings")
if str(reaction.emoji) == '👍':
print('{}: {} resigned {} {}.'.format(extCalls.timeNow('datetime'), ctx.channel.category.name, firstName, lastName))
await msg.edit(embed=embedFin)
await resign_channel.send(embed=embedConf)
else: await msg.edit(embed=embedEnd)
This is my code to have the bot wait for a reaction on msg. But, for some reason, when I have two msg (messages) waiting for a reaction at the same time, when I react to one message, both trigger success, even though I didn't react to the second message. This is hugely problematic for an admin-based approval system... does anyone know why this is happening or how to fix it?
This code is really messy, but if you're searching for a way to wait only for a reaction on one message you should add stuff to your check command, for example:
#bot.command(name="some_command")
async def sc(ctx):
msg = await ctx.send("Some interesting message!")
def check(reaction, user):
return reaction.message.id == msg.id
try:
reaction, user = await bot.wait_for("reaction_add", check=check,timeout=30.0)
except asyncio.TimeoutError:
await ctx.send("timeout")
return
await ctx.send(f"You reacted {str(reaction)}")

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