Discord.py have a user choose between 2 reactions - python

I'm trying to make my discord bot have a higher or lower game where the player clicks an up or down arrow to guess, but I'm struggling to work out how check if the reaction is the first one or the second one.
Here is my code so far (its in a cog):
#commands.command()
async def highlow(self, ctx, limit:int = 100, *args):
num = random.randint(0, limit)
msg = await ctx.reply("The number is {num} \nWHould you like to go (h)igher or (l)ower")
await self.client.add_reaction(msg, emoji="<:arrow_up:>")
await self.client.add_reaction(msg, emoji="<:arrow_down:>")
def check(reaction, user):
return reaction.message == msg and str(reaction.emoji) == '<:arrow_down:>'
await self.client.wait_for('reaction_add', check=check)
(Also, I am aware that self.client.add_reaction will not work, its some old code I just quickly copied over to test out)
How do I make it so that I can check either reaction instead of just one?

To add a reaction we use msg.add_reaction("Your reaction") in your case.
If you want to check the reaction of a user we wait_for a reaction_add.
You can have a look at the following code:
try:
reaction, user = await client.wait_for('reaction_add')
while user == client.user:
reaction, user = await client.wait_for('reaction_add')
if str(reaction.emoji) == "YourEmoji": # First reaction
# Do what you want to do
if str(reaction.emoji) == "YourEmoji": # Second reaction
# Do what you want to do
except Exception: # Timeout can be added for example
return
Here we check that the bot reaction is not counting as a valid reaction but instead a user one. You can then add more if-statements in your code.

Related

Discord embed not assigning roles on user eaction

I have mostly rebuilt the code and made changes.
When I do !embed everything works as it should, the bot creates the embed, and reacts with the emojis set, it just doesn't give user the roles once they react with the emojis the bot reacts to the post with..
I decided to add in some random print statements to hit the console per step and found I can see up to while True as commented in my code but after that the print statements don't respond.
My error handler/trace back has been pretty good so far and picked up everything, so its like after while True nothing exists, I believe the problem lies within this line:
reaction = await bot.wait_for("reaction_add", check=lambda reaction, user: user == reaction.message.id == msg.id)
Any help would be greatly appreciated.
desc1 = ("React with the corresponding emoji to game specific channels you want\n"
"\nšŸ‘ - Counter Strike\n"
"šŸ˜€ - Warframe\n"
"šŸ¤“ - Lost Ark\n"
"šŸ˜† - Chiller\n"
"šŸ’Ŗ - Warhammer")
#bot.command()
async def embed(ctx: commands.Context):
embed=discord.Embed(title="Create Your Profile", description=(desc1))
embed.set_footer(text="Jammys Bot")
msg = await ctx.send(embed=embed)
await msg.add_reaction('šŸ‘')
await msg.add_reaction('šŸ˜€')
await msg.add_reaction('šŸ¤“')
await msg.add_reaction('šŸ˜†')
await msg.add_reaction('šŸ’Ŗ')
print('it works here')
while True:
print('it works here 1') ## makes it up to here. Error handle/trace back isn't picking anything below up so its like its non-existent.
reaction = await bot.wait_for("reaction_add", check=lambda reaction, user: user == reaction.message.id == msg.id) # stops working here
print('it works here 2')
if reaction.emoji == 'šŸ‘':
print('it works here 3')
CSGO = ctx.guild.get_role(780182144747569193)
print('it works here 4')
await bot.add_roles(CSGO)
print('it works here 5')
if reaction.emoji == 'šŸ˜€':
WF = ctx.guild.get_role(780244861130768395)
await ctx.add_roles(WF)
if reaction.emoji == 'šŸ¤“':
LA = ctx.guild.get_role(947231964753170473)
await bot.add_roles(LA)
if reaction.emoji == 'šŸ˜†':
CHILL = ctx.guild.get_role(896853904384458772)
await bot.add_roles(CHILL)
if reaction.emoji == 'šŸ’Ŗ':
WH = ctx.guild.get_role(896882687816630293)
await bot.add_roles(WH)
To send it to a specific channel you can try this code
channel =client.get_channel(your channelid)
msg = await channel.send(embed = embed)

Dynamic help command - Discord.py

I'm making a "dynamic" help menu command for my Discord bot. Now my commands work with "normal" messages. But I want to use Embed. So, let me explain.
Basically, I do re!help and the bot sends an Embed. Then I add two reactions ("ā—€" and "ā–¶"), and if I click on a reaction (eg "ā–¶") the Embed is updated with the commands (I want both the title and the description of the Embed to be updated). I hope I was clear and if you did not understand what I want tell me I will try to explain it again. Here is my code anyway:
#client.command()
async def help(ctx):
contents = [
"""
text 1
""",
"""
text 2
""",
"""
text 3
""",
"""
text 4
"""]
pages = 4
cur_page = 1
message = await ctx.send(f"{contents[cur_page-1]}")
# getting the message object for editing and reacting
await message.add_reaction("ā—€ļø")
await message.add_reaction("ā–¶ļø")
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"
while True:
try:
reaction, user = await client.wait_for("reaction_add", timeout=60, check=check)
# waiting for a reaction to be added - times out after x seconds, 60 in this
# example
if str(reaction.emoji) == "ā–¶ļø" and cur_page != pages:
cur_page += 1
await message.edit(content=f"Page {cur_page}/{pages}:\n{contents[cur_page-1]}")
await message.remove_reaction(reaction, user)
elif str(reaction.emoji) == "ā—€ļø" and cur_page > 1:
cur_page -= 1
await message.edit(content=f"Page {cur_page}/{pages}:\n{contents[cur_page-1]}")
await message.remove_reaction(reaction, user)
else:
await message.remove_reaction(reaction, user)
# removes reactions if the user tries to go forward on the last page or
# backwards on the first page
except asyncio.TimeoutError:
await message.delete()
break
Wish someone could help!
Instead of contents being a list of strings, make it a list of embeds, then edit the message appropiately
contents = [
discord.Embed(title="page 1"),
discord.Embed(title="page 2"),
discord.Embed(title="page 3"),
discord.Embed(title="page 4")
]
...
await message.edit(
content=f"Page {cur_page}/{pages}",
embed=contents[cur_page - 1]
)

How do you check if a specific user reacts to a specific message with many specific emojis

I am currently making a car economy bot and I wanted to be able to interact with the bot just like how a game would. I wanted to use reactions for buttons but the problem was that when I used the code,
confirm_right = await client.wait_for("reaction_add", check=react_check_right)
confirm_left = await client.wait_for("reaction_add", check=react_check_left)
It didn't work as expected, first it waits for the confirm_right then the confirm_left.
I'm hoping to find a way to be able to make them both work at the same time.
This is my check functions,
def check(message):
return ctx.author.id == message.author.id
def react_check_right(reaction, user):
return user == ctx.author and str(reaction.emoji) in ["āž”ļø"]
Found the answer
reaction, user = await client.wait_for('reaction_add', check = lambda reaction, user: reaction.emoji in ["āž”ļø", "ā¬…ļø"])
if user == ctx.author:
if reaction.emoji == 'āž”ļø':
if car_shown < len(users[str(user.id)]['car']) - 1:
car_shown += 1
else:
car_shown = 0
```
this was basically it

How to get a discord wait_for to wait for either reaction or a message using discord.py

I have been using the following two methods to get a user input from a discord embed. The first I use to get a text based message input and the second I use to get a reaction input (used to paginate the embed). What I would like to know is if I can merge these two to create a method to wait for either a reaction or a message input?
#message input
try:
answer = await self.bot.wait_for(
"message",
timeout=60,
check=lambda message: message.author.id == ctx.author.id
and isinstance(message.channel, discord.channel.DMChannel)
)
#reaction input
try:
reaction, user = await self.bot.wait_for(
"reaction_add",
timeout=60,
check=lambda reaction, user: user.id == ctx.author.id
and reaction.emoji in buttons
#and isinstance(reaction.channel, discord.channel.DMChannel),
)
UPDATE:
So I have tried to implement the method duckboycool linked to (Many thanks man!).
The issue I am having now is that when I react to the paginated embed it works perfectly, the reaction is noted and the embed is updated accordingly. But if I input a message instead it get the following error:
"reaction, emoji = await task
TypeError: cannot unpack non-iterable Message object"
here is my code:
finished =0
while finished == 0:
done_tasks = None
check1=lambda reaction, user: user.id == ctx.author.id and reaction.emoji in buttons
check2=lambda message: message.author.id == ctx.author.id and isinstance(message.channel, discord.channel.DMChannel)
pending_tasks = [self.bot.wait_for('reaction_add',check=check1),self.bot.wait_for('message',check=check2)]
done_tasks, pending_tasks = await asyncio.wait(pending_tasks, return_when=asyncio.FIRST_COMPLETED)
#print(done_tasks)
for task in pending_tasks:
task.cancel()
for task in done_tasks:
reaction, emoji = await task
message = await task
if reaction:
print(reaction)
previous_page = current
if reaction.emoji == u"\u23EA":
current = 0
elif reaction.emoji == u"\u25C0":
if current > 0:
current -= 1
elif reaction.emoji == u"\u25B6":
if current < len(pages)-1:
current += 1
elif reaction.emoji == u"\u23E9":
current = len(pages)-1
if current != previous_page:
await msg.edit(embed = pages[current])
else:
print(message.content)
In the updated code, you need to check what kind of awaitable the event is so that you can unpack with the correct number of values and complete the intended behavior. With your examples of message and reaction_add, this would probably look something like this.
for task in done_tasks:
taskobj = await task
if isinstance(taskobj, discord.Message):
message = taskobj
#Message logic
else:
reaction, user = taskobj
#Reaction logic
You might have to do different things depending on which events you're using. (Here, it relies on a tuple being recognized as not an instance of discord.Message.)

Discord.py silence command

I have been asking lots of questions lately about discord.py and this is one of them.
Sometimes there are those times when some people spam your discord server but kicking or banning them seems too harsh. I had the idea for a silence command which would delete every new message on a channel for a given amount of time.
My code so far is:
#BSL.command(pass_context = True)
async def silence(ctx, lenghth = None):
if ctx.message.author.server_permissions.administrator or ctx.message.author.id == ownerID:
global silentMode
global silentChannel
silentChannel = ctx.message.channel
silentMode = True
lenghth = int(lenghth)
if lenghth != '':
await asyncio.sleep(lenghth)
silentMode = False
else:
await asyncio.sleep(10)
silentMode = False
else:
await BSL.send_message(ctx.message.channel, 'Sorry, you do not have the permissions to do that #{}!'.format(ctx.message.author))
The code in my on_message section is:
if silentMode == True:
await BSL.delete_message(message)
if message.content.startswith('bsl;'):
await BSL.process_commands(message)
All the variables used are pre-defined at the top of the bot.
My problem is that the bot deletes all new messages in all channels which it has access to. I tried putting if silentChannel == ctx.message.channel in the on_message section but this made the command stop working completely.
Any suggestions as to why this is happening are much appreciated.
Something like
silent_channels = set()
#BSL.event
async def on_message(message):
if message.channel in silent_channels:
if not message.author.server_permissions.administrator and message.author.id != ownerID:
await BSL.delete_message(message)
return
await BSL.process_commands(message)
#BSL.command(pass_context=True)
async def silent(ctx, length=0): # Corrected spelling of length
if ctx.message.author.server_permissions.administrator or ctx.message.author.id == ownerID:
silent_channels.add(ctx.message.channel)
await BSL.say('Going silent.')
if length:
length = int(length)
await asyncio.sleep(length)
if ctx.message.channel not in silent_channels: # Woken manually
return
silent_channels.discard(ctx.message.channel)
await BSL.say('Waking up.')
#BSL.command(pass_context=True)
async def wake(ctx):
silent_channels.discard(ctx.message.channel)
Should work (I haven't tested it, testing bots is a pain). Searching through sets is fast, so doing it for every message shouldn't be a real burden on your resources.

Categories