Reactions not working when used to confirm an action - discord.py

In the following code i have set up the reactions as a confirmation for the next task. That is, if the reaction is no then the function does not proceed to append certain data into a dict. Same goes with the timeout. When tested, only the timeout works and sends the message that it is supposed to. However, when reacted on one of the reactions nothing happens. First time i am dealing with reactions so not sure what i've messed up.
yes = '✔'
no = '👎'
for y in yes:
await message.add_reaction(y)
for n in no:
await message.add_reaction(n)
def check(reaction, user):
return (reaction.message.id == message.id) and (user.id == ctx.author.id) and (str(reaction) in y) and (str(reaction) in n)
try:
reaction, user = await self.bot.wait_for('reaction_add', check=check, timeout=60)
except asyncio.TimeoutError:
await ctx.send("Timed out")
await message.delete()
return
if str(reaction) == '✔':
await ctx.send('some stuff')
if str(reaction) == '👎':
await ctx.send('canceled')
return

Your check will always return false,
(reaction.message.id == message.id) and (user.id == ctx.author.id) and (str(reaction) in y) and (str(reaction) in n) means that str(reaction) should be in both n and y.
Your check should be
def check(reaction, user):
return (reaction.message.id == message.id) and (user.id == ctx.author.id) and (str(reaction) in y) or (str(reaction) in n)

Related

discord.py problems with the checking

im trying to do a trivial discord bot, but with a "helper" it reveals you letter by letter the answer, each "x" senconds, but the problem is i can't to send the answer till the help is fully displayed
in the image the problem is cleary visible
this is the whole code
await ctx.send(a) #question
respuestas = b #answer
#reveal
string = respuestas[0]
blanked_string = [x if x.isspace() else "-" for x in string]
nonspace_indexes = [i for i, _ in enumerate(string)]
random.shuffle(nonspace_indexes)
for i in nonspace_indexes:
blanked_string[i] = string[i]
await ctx.send(''.join(blanked_string))
await asyncio.sleep(2)
#checking
def check(msg=discord.Message) -> True:
return msg.content.lower() in respuestas and msg.channel == ctx.message.channel
try:
guess = await bot.wait_for('message', timeout=6, check=check)
return await ctx.send(f"{guess.author.mention} first")
except asyncio.TimeoutError:
await ctx.send("time over")
#problem
https://i.stack.imgur.com/jlatG.png
note: im not trying to do a hangman
async def letters(response: str, channel: discord.TextChannel):
blanked_string = [x if x.isspace() else "-" for x in response]
nonspace_indexes = [i for i, _ in enumerate(response)]
random.shuffle(nonspace_indexes)
for i in nonspace_indexes:
blanked_string[i] = response[i]
await channel.send(''.join(blanked_string))
await asyncio.sleep(1)
#client.command()
async def game(ctx: commands.Context):
await ctx.send("Author of Python programming language...")
response = "Guido van Rossum"
letters_func = asyncio.create_task(letters(response, ctx.channel))
try:
player_response = await client.wait_for('message', check=lambda m: m.author == ctx.author and m.channel == ctx.channel, timeout=30)
except asyncio.TimeoutError:
letters_func.cancel()
await ctx.send("Time's up!")
else:
letters_func.cancel()
if player_response.content == response:
await ctx.send("Correct!")
else:
await ctx.send("Incorrect!")
Here is a possible resolution path for your problem. I took advantage of asyncio's presence to do this. You can test it, it works very well.
Be careful to adapt your response time to the length of your answer

Manually picking users from reaction roles

I have a command which allows people to react to a message to enter into a "battle". Then with another command, 2 people are chosen. This is my code:
#client.command(aliases=['start', 'g'])
async def startbattle(ctx):
msg = await ctx.send("React to enter battle")
await msg.add_reaction("🎉")
await asyncio.sleep(10000000000000000)
message = await ctx.fetch_message(msg.id)
for reaction in message.reactions:
if str(reaction.emoji) == "🎉":
users = await reaction.users().flatten()
if len(users) == 1:
return await msg.edit(embed=discord.Embed(title="Nobody has won the giveaway."))
try:
user1 = random.choice(users)
user2 = random.choice(users)
except ValueError:
return await ctx.send("not enough participants")
await ctx.send(f'Battle will be against {user1} and {user2}')
#client.command()
async def pick(ctx):
async for message in ctx.channel.history(limit=100, oldest_first=False):
if message.author.id == client.user.id and message.embeds:
reroll = await ctx.fetch_message(message.id)
users = await reroll.reactions[0].users().flatten()
users.pop(users.index(client.user))
winner1 = random.choice(users)
winner2 = random.choice(users)
await ctx.send(f"The battle will be against {winner1.mention} and {winner2.mention}")
break
else:
await ctx.send("No giveaways going on in this channel.")
This is the error which I get while using the "pick" command,
users = await reroll.reactions[0].users().flatten()
IndexError: list index out of range
the error is in the for loop as it gets the msg and the msg doesnt have any reaction so it there is no list so list is out of range to fix this u have to add try and except there so it skips the msg with no reactions.
code :
#client.command()
async def pick(ctx):
async for message in ctx.channel.history(limit=100, oldest_first=False):
if message.author.id == client.user.id and message.embeds:
reroll = await ctx.fetch_message(message.id)
try:
users = await reroll.reactions[0].users().flatten()
except:
break
users.pop(users.index(client.user))
winner1 = random.choice(users)
winner2 = random.choice(users)
await ctx.send(f"The battle will be against {winner1.mention} and {winner2.mention}")
return # <--- was the break
else:
await ctx.send("No giveaways going on in this channel.")
and a suggestion dont add break in the if statement as it will continuously check for msg if already it got one

Messages repeating infinitely -- Discord.py

So this is 100% a wip and I'm super confused on how to stop the discord bot from reading the same user message over and over again.
import discord
class MyClient(discord.Client):
async def on_ready(self):
print('Logged in as')
print(self.user.name)
print(self.user.id)
print('------')
async def on_message(self, message):
global choice
global r
# we do not want the bot to reply to itself
if message.author.id == self.user.id:
return
if message.content.startswith('!hello'):
await message.reply('Hello!', mention_author=True)
r=0
basic = True
#upgrade1 = False
choice = '!idle'
while(choice != '!end'):
if message.content.startswith('!idle'):
choice= await message.reply('!click / !bank / !upgrade / !minigame / !end')
if message.author.id == self.user.id:
return
if message.content.startswith('!click' or '!c'):
r = r + 1
await message.reply("You have " + str(r) + " Renown")
elif message.content.startswith ('!bank'):
await message.reply ("You have " + str(r) + " Renown")
#elif message.content.startswith ('!upgrade' and r >= 10):
#await message.reply('Upgrading..')
#r = r - 10
#basic = False
#elif message.content.startswith ('!upgrade' and r < 10):
#await message.reply ('Not Enough Renown')
elif message.content.startswith('!minigame' or '!mg'):
#random number choosing game
await message.reply ("Game")
elif message.content.startswith ('!end'):
basic = False
#while time.time() < future:
#r =r + .00001
client = MyClient()
client.run
Your issue comes from the while(choice != '!end') loop. Once the bot enters that loop, the messages he is sending within that loop of course will be repeated because you are not changing the value of choice unless the message is !idle.
Notice in your code how if the command is for example !click, you are sending the following message:
await message.reply("You have " + str(r) + " Renown")
But you are not changing the value of choice, so in the next iteration of the while loop it will repeat the same, once and again.
I'm not sure what you are trying to achieve, but having a while loop in the on_message event does not seem a good idea for me, as this is a blocking feature. In my opinion you should replace it with an if.

How to check if a response message is by the author while ignoring other messages not by the author in discord bot

I have this
async def deletechar(ctx):
em = discord.Embed(title = f"Are you sure you want to erase your character?",color = discord.Color.red())
em.add_field(name = "Yes, delete it please",value = "Y")
em.add_field(name = "Nevermind",value = "N")
await ctx.send(embed = em)
response = await client.wait_for_message('message')
if response.content == 'Y' and ctx.author == response.author:
await ctx.send("Successfully Deleted Character")
elif response.content == 'N' and ctx.author == response.author:
await ctx.send("Not Deleted")
this may be poorly written, though it works it's just that if a message is not by the author it'll kick out of the function. I want to await a response by the author within the context while ignoring other messages but not using too much CPU while doing it. Any suggestions?
I don't want it to hang in this function because I want it to be able to process messages from other users while it waits for this one
Using a check
response = await client.wait_for_message('message', check=lambda m: m.author.id == ctx.author.id)

Discordpy TempMute Command

Kind of new to Discordpy moderation commands and would like to know why this code doesn't work,
as I get no errors while trying to run it. it's supposed to temp mute a user for a specific amount of time depending on the unit and if no muted role was created it, creates one.
#commands.command()
async def mute(self , ctx, user : discord.Member, duration = 0,*, unit = None):
guild = ctx.guild
for role in guild.roles:
if role.name =="Muted":
await member.add_roles(role)
await ctx.send("{} Has been muted by {} for {duration}{unit} " .format(member.mention,ctx.author.mention))
return
overwrite = discord.PermissionsOverwrite(send_messages=False)
newRole = await guild.create_role(name="Muted")
for channel in guild.text_channels:
await channel.set_permissions(newRole,overwrite=overwrite)
await member.add_roles(newRole)
await ctx.send("{} Has been muted by {} for {duration}{unit} " .format(member.mention,ctx.author.mention))
if unit == "s":
wait = 1 * duration
await asyncio.sleep(wait)
elif unit == "m":
wait = 60 * duration
await asyncio.sleep(wait)
await user.remove_roles(roleobject)
await ctx.send(f":white_check_mark: {user} was unmuted")
```
All the code after your return statement is useless, as it will never be executed. Maybe you indented that part too far.
Make a list of the role names and check it through that instead of having a big loop
rolenames = {role.name: role for role in guild.roles}
if 'Muted' not in rolenames.keys():
# Make the new role
role = new_role_you_created
else:
role = rolenames['Muted']
# Add the role to the user
await member.add_roles(role)
All the code written after a return statement will NEVER be executed, so the last part of your code is unreachable.
You can use discord.utils.get to get a role based on the name, insted of looping over every role in the guild.
Then, if there isn't any role under that name, you create a new one (I'm guessing that's the behaviour you're looking for)
#commands.command()
async def mute(self , ctx, member : discord.Member, duration: int = 0, unit: str = None):
guild = ctx.guild
role = discord.utils.get(guild.roles, name="Muted")
if role is None:
overwrite = discord.PermissionsOverwrite(send_messages=False)
role = await guild.create_role(name="Muted")
for channel in guild.text_channels:
await channel.set_permissions(role, overwrite=overwrite)
await member.add_roles(role)
await ctx.send(f"{member.mention} Has been muted by {ctx.author.mention} for {duration}{unit} ")
if unit == "s":
wait = 1 * duration
await asyncio.sleep(wait)
elif unit == "m":
wait = 60 * duration
await asyncio.sleep(wait)
await member.remove_roles(role)
await ctx.send(f":white_check_mark: {member} was unmuted")
You should consider checking if the unit is valid before executing the command, because wait will not be defined if unit is not s or m, causing the command to raise an error, and the user will be muted forever.
Also, using asyncio.sleep in a tempmute command is not recommended. If the bot crashes or is turns off, the user will never be unmuted. Instead, store the tempmutes in a database and check them every X time.
As the others already stated, the code after the return statement won't be executed. So basically the whole command ends if there is a role called "Muted". Also you seem to create a new role called "Muted" if there is a role called Muted in which you disable sending messages. Also I am not sure, if you can't just set the global role permissions to send_messages=True
Do you maybe mean something like this?
#commands.command()
async def mute(self , ctx, user : discord.Member, duration = 0,*, unit = None):
guild = ctx.guild
muted_role = None
for role in guild.roles:
if role.name =="Muted":
muted_role = role
if muted_role == None:
overwrite = discord.PermissionsOverwrite(send_messages=False)
muted_role = await guild.create_role(name="Muted")
for channel in guild.text_channels:
await channel.set_permissions(muted_role,overwrite=overwrite)
await member.add_roles(muted_role)
await ctx.send("{} Has been muted by {} for {duration}{unit} " .format(member.mention,ctx.author.mention))
if unit == "s":
wait = 1 * duration
await asyncio.sleep(wait)
elif unit == "m":
wait = 60 * duration
await asyncio.sleep(wait)
await user.remove_roles(roleobject)
await ctx.send(f":white_check_mark: {user} was unmuted")
I hope I could help!

Resources