Reaction Handling in Discord.py Rewrite Commands - python-3.x

Is there a way to capture a reaction from a command. I have made a command that deletes a channel but I would like to ask the user if they are sure using reactions. I would like to prevent others from reacting to this message (Only the Context Author should React).
So far what I have found is just to use the on_reaction_add() but this can not detect the user who sent the command. I would like to only update the command if the message author is the one who reacted to the message, anyone else, ignore it.
Update: I found that wait_for() does exactly what I want but the issue now is how do I check for if the wrong reaction is set? (i.e if I press the second reaction, delete the message)
if is_admin:
msg = await ctx.send('Clear: Are you sure you would like to purge this entire channel?')
emoji1 = u"\u2705"
emoji2 = u"\u274E"
await msg.add_reaction(emoji=emoji1)
await msg.add_reaction(emoji=emoji2)
def check(reaction, user):
return user == ctx.message.author and reaction.emoji == u"\u2705"
try:
reaction, user = await self.client.wait_for('reaction_add', timeout=10.0, check=check)
except asyncio.TimeoutError:
return await msg.delete()
else:
channel = ctx.message.channel
new_channel = await channel.clone(name=channel.name, reason=f'Clear Channel ran by {ctx.message.author.name}')
await new_channel.edit(position=channel.position)
await channel.delete(reason=f'Clear Channel ran by {ctx.message.author.name}')
await new_channel.send('Clear: Channel has now been cleared.', delete_after=7)
else:
await ctx.send(f"Sorry, you do not have access to this command.", delete_after=5)

Here's a function I use for generating check functions for wait_for:
from collections.abc import Sequence
def make_sequence(seq):
if seq is None:
return ()
if isinstance(seq, Sequence) and not isinstance(seq, str):
return seq
else:
return (seq,)
def reaction_check(message=None, emoji=None, author=None, ignore_bot=True):
message = make_sequence(message)
message = tuple(m.id for m in message)
emoji = make_sequence(emoji)
author = make_sequence(author)
def check(reaction, user):
if ignore_bot and user.bot:
return False
if message and reaction.message.id not in message:
return False
if emoji and reaction.emoji not in emoji:
return False
if author and user not in author:
return False
return True
return check
We can pass the message(s), user(s), and emoji(s) that we want to wait for, and it will automatically ignore everything else.
check = reaction_check(message=msg, author=ctx.author, emoji=(emoji1, emoji2))
try:
reaction, user = await self.client.wait_for('reaction_add', timeout=10.0, check=check)
if reaction.emoji == emoji1:
# emoji1 logic
elif reaction.emoji == emoji2:
# emoji2 logic
except TimeoutError:
# timeout logic

Related

.verify command not giving role (discord.py 1.7.3)

I am creating a discord bot, and with this bot I want to create a verify command. I started working on it, and everything works to the point of giving the reaction (Sending message with the "yes and "no" reactions), when I run the code, it doesn't throw any errors. If you do help me, I would please also like an explanation of why my code doesn't work and why your's does if you can, so I can learn.
Thank you! -JJ
Code:
#client.command()
async def verify(ctx):
verifier = ctx.author
jj = await client.fetch_user(270397954773352469)
validReactions = ['✅', '🚫']
role = discord.utils.get(ctx.guild.roles, name="Verified")
await ctx.send(f'{verifier}... Awaiting Verification, you will recieve a dm when you are verified')
dm = await jj.send(f'{verifier.mention} is trying to be verified, do you know him/her?')
await dm.add_reaction("✅")
await dm.add_reaction("🚫")
def check(reaction, user):
return user == ctx.author and str(reaction.emoji) in validReactions
reaction, user = await client.wait_for('reaction_add', timeout=float('inf') , check=check) #float('inf') for no timeout on when I can add the reaction for yes or no
if str(reaction.emoji) == "✅":
await verifier.send("You have been verified")
await client.add_roles(verifier, role)
elif str(reaction.emoji) == "🚫":
await verifier.send("You have not been verified, please try again if you think this was a mistake, or contact the owner")
You send a message to person called jj and user == ctx.author in check function is always True. (Because ctx.author can not see that message). So, try to replace
def check(reaction, user):
return user == ctx.author and str(reaction.emoji) in validReactions
with
def check(reaction, user):
return user == jj and str(reaction.emoji) in validReactions

Translation with reactions not working on python

I'm trying to do a bot translation using reactions. Although something is not working.
#commands.Cog.listener()
async def on_reaction_add(self, reaction, user):
if user == self.client:
return
if reaction.emoji == ":flag_us:":
text = reaction.message.id
translate_text = google_translator.translate(text, lang_tgt='en')
await self.client.send_message(translate_text.channel)
elif reaction.emoji == ":flag_cn:":
text = reaction.message.id
translate_text = google_translator.translate(text, lang_tgt='zh')
await self.client.send_message(translate_text.channel)
else:
return
No error returned and no action made
This is because reaction.emoji isn't a string, but is an object itself. You're probably looking for reaction.emoji.name.
Also, there are a few issues within the if/elif clauses that would prevent your code from running, even if the original issue was fixed.
reaction.message.id is an integer, so passing it to google_translator.translate() will result in an error.
The name of an emoji tends not to be the name you would enter in Discord. The best practice would be to put the unicode of the emoji.
To send a message to channel, you should use TextChannel.send()
#commands.Cog.listener()
async def on_raw_reaction_add(self, payload):
if payload.member == self.client:
return
if payload.emoji.name == u"\U0001f1fa\U0001f1f8":
message = await self.client.fetch_message(payload.message_id)
translate_text = google_translator.translate(message.content, lang_tgt='en')
channel = await self.client.fetch_channel(payload.channel_id)
await channel.send(translate_text)
elif payload.emoji.name == u"\U0001F1E8\U0001F1F3":
message = await self.client.fetch_message(payload.message_id)
translate_text = google_translator.translate(message.content, lang_tgt='zh')
channel = await self.client.fetch_channel(payload.channel_id)
await channel.send(translate_text)
This would work, but I would recommend taking all of the various calls outside of the if/elif clauses:
#commands.Cog.listener()
async def on_raw_reaction_add(self, payload):
if payload.member == self.client:
return
emoji_to_language = {
u"\U0001f1fa\U0001f1f8": "en",
u"\U0001F1E8\U0001F1F3": "zh"
}
lang = emoji_to_language.get(payload.emoji.name)
if lang is None:
break
message = await self.client.fetch_message(payload.message_id)
translate_text = google_translator.translate(message.content, lang_tgt=lang')
channel = await self.client.fetch_channel(payload.channel_id)
await channel.send(translate_text)

'TextChannel' object is not iterable | channel check not working

Hi I'm trying to check to see if a channel already exists instead of creating a new one each time a member sends a modmail request. The error I get is
File "C:\Users\User\Desktop\Build 1.0.2\cogs\modmail.py", line 145, in on_message
if get(modmailchannel, name=f"{message.author.name.lower()}{message.author.discriminator}"):
File "C:\Users\User\AppData\Local\Programs\Python\Python37\lib\site-packages\discord\utils.py", line 271, in get
for elem in iterable:
TypeError: 'TextChannel' object is not iterable
enter code here
not sure why or the method is incorrect some how. The channel name that is created is the users name and discriminator. What I'm trying to achieve is if that channel already exists don't create another channel/post in the same channel.
Help appreicated
Here is the code I'm working with:
#commands.Cog.listener()
async def on_message(self, message):
if not isinstance(message.channel, discord.DMChannel) or message.author.id == self.bot.user.id:
# not a DM, or it's just the bot itself
return
bucket = self._cd.get_bucket(message)
retry_after = bucket.update_rate_limit()
seconds = bucket.update_rate_limit()
seconds = round(seconds, 2)
hours, remainder = divmod(int(seconds), 3600)
minutes, seconds = divmod(remainder, 60)
if retry_after:
pass
else:
channel = self.bot.get_channel(744311308509249610)
if not channel:
print("Mail channel not found! Reconfigure bot!")
time = datetime.utcnow()
guild = self.bot.get_guild(715288565877309521)
member_role = get(guild.roles, name='Members')
mod_role = get(guild.roles, name='Members')
muted_role = get(guild.roles, name='Modmail Muted')
user_id = message.author.id
author = guild.get_member(user_id)
content = message.clean_content
if muted_role in author.roles:
await message.channel.send("You're not allowed to use modmail.")
return
if len(message.content) < 0:
await message.channel.send("Your message should be atleast 50 characters in length.")
return
if member_role in author.roles:
emoji1 = '📨'
emoji2 = '🗑️'
message_confirm = "You're about to send a message to the mod team, react with :incoming_envelope: to confirm otherwise :wastebasket: to discard your message."
embed = discord.Embed(description=message_confirm, colour=0x5539cc)
embed.title = "Confirm Message"
confirm = await message.channel.send(embed=embed)
await confirm.add_reaction(emoji1)
await confirm.add_reaction(emoji2)
check = reaction_check(message=confirm, author=message.author, emoji=(emoji1, emoji2))
try:
reaction, user = await self.bot.wait_for('reaction_add', timeout=60.0, check=check)
if reaction.emoji == emoji1:
embed = discord.Embed(title=f"Message Sent", description=f"Hello {message.author.display_name}! We have received your message and we will get back to you as soon as possible. You can send another message in **{minutes}m {seconds}s**.", timestamp=time, colour=0x5539cc)
await message.channel.send(embed=embed)
elif reaction.emoji == emoji2:
embed = discord.Embed(title=f"Message Not Sent", description=f"Your message has not been sent. You can send another message in **{minutes}m {seconds}s**.", timestamp=time, colour=0x5539cc)
await message.channel.send(embed=embed)
return
except asyncio.TimeoutError:
await message.channel.send(f'Sorry, you took too long to confirm your message. Try again in **{minutes}m {seconds}s**.')
return
overwrites = {
guild.default_role: discord.PermissionOverwrite(read_messages=False),
guild.me: discord.PermissionOverwrite(read_messages=True),
member_role: discord.PermissionOverwrite(read_messages=False),
mod_role: discord.PermissionOverwrite(read_messages=True)}
embed = discord.Embed(title=f"Modmail message request from — {message.author.name}#{message.author.discriminator}", colour=0xff8100)
embed.add_field(name="Member:", value=f"{message.author.mention}" ,inline=True)
embed.add_field(name="Reply ID:", value=f"{message.author.id}" ,inline=True)
embed.add_field(name="Message:", value=content[:1000] or "blank", inline=False)
embed.set_footer(text=f"C&P Command: !reply {message.author.id}")
if message.attachments:
embed.add_field(name="Attachments", value=", ".join([i.url for i in message.attachments]))
if len(content[1000:]) > 0:
embed.add_field(name="Message (continued):", value=content[1000:])
modmailchannel = await guild.create_text_channel(f'{message.author.name}{message.author.discriminator}', overwrites=overwrites, category=self.bot.get_channel(744944688271720518))
await modmailchannel.send(embed=embed)
if get(modmailchannel, name=f"{message.author.name.lower()}{message.author.discriminator}"):
await modmailchannel.send(embed=embed)
else:
await message.channel.send("Only members can use modmail.")
Instead of if get(modmailchannel, name=f"{message.author.name.lower()}{message.author.discriminator}"): use:
if modmailchannel.name == f"{message.author.name.lower()}{message.author.discriminator}":

Why is discord.Client.wait_for is asking for event when it has one?

The problem i'm having is the code never getting through "0004" since it gets stuck on wait_for which wants additional positional argument: 'event', which should be the 'message' in bracket right next to it from what I've seen on examples from discord.py site & inside the code itself.
'''
class Test(commands.Cog):
def __init__(self, spreadsheeter):
self.spreadsheeter = spreadsheeter
#commands.command()
async def apply(self, ctx):
a_list = []
submit_channel = spreadsheeter.get_channel(718698172293316608)
channel = await ctx.author.create_dm()
def check(m):
return m.content is not None and m.channel == channel
for question in q_list:
print("0001")
sleep(.5)
print("0002")
await channel.send(question)
print("0003")
msg = await Client.wait_for('message', timeout=60, check=check)
print("0004")
a_list.append(msg.content)
print("0005")
submit_wait = True
print("0006")
while submit_wait:
print("0007")
await channel.send("End of questions 'submit' to finish.")
print("0008")
msg = await Client.wait_for("message", check=check)
print("0009")
if "submit" in msg.content.lower():
print("0010")
submit_wait =False
print("0011")
answers = "\n".join(f"{a}. {b}" for a, b in enumerate(a_list, 1))
print("0012")
submit_msg = f"Apllication from {msg.author} \nThe answers are:\n{answers}"
print("0013")
await submit_channel.send(submit_msg)
print("0014")
Error:
What I've unsuccessfully tried:
discord.client.wait_for('message', check=check)
Error: discord.client has no attribute 'wait_for'
Same for discord.member, ctx.member, ctx.client
Replacing "message" to 'message' (doesn't change anything)
Moving around the position of 'message'
Raises ton of other errors...
Giving the line bracket at start (self, "message", check=check)
Error: 'Test' object has no attribute 'loop'
#client.event instead of cog style commands.command
Error: Unresolved reference 'client'
Intent of the code:
Be called from DM as a command, then start a series of questions from q_list in that DM and then store them into a_list. Then after its done it should submit the a_list into as submit_msg into discord channel.
What it does so far?:
Asks first question from q_list
q_list = [
"Question one",
"Question two ha-ha-haaa",
"Question three ha-ha-haaa"
]
"""
After that it immediately after trying to await answer gives error.
2 Things at the beginning, you should only make a dm channel if there isn't any yet.
ctx.author.chreate_dm() doesn't return a channel, so instead of assigning the dm channel to channel you can check it's in the right channel by using ctx.author.dm_channel
#commands.command()
async def apply(self, ctx):
a_list = []
submit_channel = self.spreadsheeter.get_channel(718698172293316608)
if not ctx.author.dm_channel:
await ctx.author.create_dm()
def check(m):
return m.content is not None and m.channel == ctx.author.dm_channel
Instead of Client use spreadsheeter as that is your client
msg = await self.spreadsheeter.wait_for('message', timeout=60, check=check)
Figured it out, the thing being is it might work in cogs, but I think it won't or something is just missing...
The way i made it to work is to use it in
#Client.event (in my case #spreadsheeter.event)
Instead of ctx. I used payload.
"""
#spreadsheeter.event
async def on_raw_reaction_add(payload):
print("Hell-Yeah")
message_id = payload.message_id
if message_id == 739153263907176480:
guild_id = payload.guild_id
guild = discord.utils.find(lambda g : g.id == guild_id, spreadsheeter.guilds)
print (payload.emoji.name)
if payload.emoji.name == "No1":
role = discord.utils.find(lambda r: r.name == 'RAIDERS', guild.roles)
print(role)
if role in payload.member.roles:
print("You really want to leave?")
else:
print("Kick")
await payload.member.kick()
elif payload.emoji.name == "Yes1":
print("Yo, we got a potential raider over here..")
channel = await payload.member.create_dm()
await channel.send("This is still Work/Bot is under Development (Not actuall application yet).")
await channel.send("Whenever you are ready, please start replaying to the questions.")
print("0000")
submit_channel = spreadsheeter.get_channel(718698172293316608)
if not payload.member.dm_channel:
await payload.member.create_dm()
channel = payload.member.dm_channel
print(channel)
def check(m):
return m.content is not None and m.channel == channel
for question in q_list:
print("0001")
sleep(.5)
print("0002")
await channel.send(question)
print("0003")
msg = await spreadsheeter.wait_for(event='message', timeout=60, check=check)
print("0004")
a_list.append(msg.content)
print("0005")
submit_wait = True
print("0006")
while submit_wait:
print("0007")
await channel.send("End of questions 'submit' to finish.")
print("0008")
msg = await spreadsheeter.wait_for("message", timeout=60, check=check)
print("0009")
if "submit" in msg.content.lower():
print("0010")
submit_wait = False
print("0011")
answers = "\n".join(f"{a}. {b}" for a, b in enumerate(a_list, 1))
print("0012")
submit_msg = f"Apllication from {msg.author} \nThe answers are:\n{answers}"
print("0013")
await submit_channel.send(submit_msg)
print("0014")
elif payload.emoji.name == "Need_more_info":
print("Yo, we got a potential diplomat over here..")
channel = await payload.member.create_dm()
await channel.send("This is still Work/Bot is under Development (Not actuall application yet).")
await channel.send("Whenever you are ready, please start with \n_apply")
"""
It still doesn't have the handling for multiple users, but works fine with one at a time at this moment, which solves the original problem of wait_for not waiting for anything else then timeout to run out.

Adding an exception to tell the user in the channel they dont have permission

I don't get any errors with this code but bot still wont tell the non-admin user they dont have permission, it just stays in the terminal, what am i doing wrong in this that bot wont say that or what would i need to change to fix that, also using discord.py rewrite
bans a user with a reason
#client.command()
#commands.has_any_role("Keyblade Master","Foretellers")
async def ban (ctx, member:discord.User=None, reason =None):
adminroles = ("Keyblade Master","Foretellers")
try:
if member == None or member == ctx.message.author:
await ctx.channel.send("You cannot ban yourself")
return
elif reason == None:
reason = "being a jerk!"
message = f"You have been banned from {ctx.guild.name} for {reason}"
await member.send(message)
# await ctx.guild.ban(member)
await ctx.channel.send(f"{member} is banned!")
except commands.errors.MissingAnyRole(adminroles):
await ctx.channel.send("You do not have permission to do that!")
return
Errors raised during command invocation, before the callback is called (like CheckFailure, UserInputError, and their derivatives) need to be handled by a separate error handler, because the code that is failing is not actually in your try block.
#client.command(name="ban")
#commands.has_any_role("Keyblade Master","Foretellers")
async def ban_command(ctx, member:discord.User=None, reason =None):
adminroles = ("Keyblade Master","Foretellers")
if member == None or member == ctx.message.author:
await ctx.channel.send("You cannot ban yourself")
return
elif reason == None:
reason = "being a jerk!"
message = f"You have been banned from {ctx.guild.name} for {reason}"
await member.send(message)
# await ctx.guild.ban(member)
await ctx.send(f"{member} is banned!")
#ban_command.error
async def ban_error(ctx, error):
if isinstance(error, commands.MissingAnyRole):
await ctx.send("You do not have permission to do that!")
else:
raise error

Resources