Waiting for reaction in background in Discord Python - python

I have this bot that sends a particular message to the channel whenever a particular user's name is mentioned (using on_message()). And if I get tired of it and want it to stop, I just react to whatever it sent with a thumbs down within 2 minutes, and it stops.
Now, I also have another command that can take in that person's name as an argument, like .command #person_name. My problem is that, when I call the command with that specific person's name, the bot first posts the reaction message (which is fine), and then waits for two full minutes for the reaction, before it times out and moves on to the command body. How can I make the waitfor() function run in the background?
Here's a generalized gist of my code:
async def on_message(message):
if "person_name" in message.content:
await message.channel.send("sample reaction message")
await fun() #check for reaction
... #other things
await bot.process_commands(message)
async def fun(message):
content = message.content
channel = message.channel
global VARIABLE #this variable decides whether the bot should react to the person's name
def check(reaction,user):
return user.id == bot.owner_id and str(reaction.emoji) == 'πŸ‘Ž'
try: reaction,user = await bot.wait_for('reaction_add',timeout = (60 * 2),check=check)
except asyncio.TimeoutError: pass
else:
if str(reaction.emoji) == 'πŸ‘Ž':
VARIABLE = False #disable the reaction message
await channel.send(" Okay, I'll stop.")

Using the on_reaction_add event will allow you to do this. I'd advise against using global variables though. The event will trigger whenever someone reacts to a cached message. You will want to use on_raw_reaction_add if you want this to work even after a bot restart.
Add this function to your bot:
async def on_reaction_add(reaction, user):
if bot.is_owner(user) and str(reaction.emoji) == "πŸ‘Ž":
VARIABLE = False

Since you are using the check function
def check(reaction,user):
return user.id == bot.owner_id and str(reaction.emoji) == 'πŸ‘Ž'
and confirming if this reaction 'πŸ‘Ž' was added you don't need this if statement later
if str(reaction.emoji) == 'πŸ‘Ž':
--------------------------------UPDATE------------------------------------
If you still need to verify an emoji
try:
reaction_check = await bot.wait_for('reaction_add',check=check,timeout=15)
emoji = f"{reaction_check}"
emoji = emoji[emoji.find("(<Reaction emoji=")+len("(<Reaction emoji="):emoji.find("me")]
except:
emoji = 'βœ…'
if emoji.find('πŸ‘Ž') != -1:
print("code")
**
UPDATE 2
**
try:
reaction_check = await bot.wait_for('reaction_add',check=check,timeout=15)
# since **`reaction_check`** yeilds a tuple
emoji = reaction_check[0] #yeilds the emoji that was used in reaction
except:
emoji = 'βœ…'
if str(emoji) == 'πŸ‘Ž':
print("code")

Related

Bot won't respond to Emoji Reaction

I'm planning to make a bot that got Name and Birthday from User input, and before storing the user's answer to database (in my case, Google Spreadsheet via gspread), Ask the user that input is correct.
So I decided to show an react emojis bottom the Checking embed, and If user reacts with β­•, It will append the User's input to Google Spreadsheet. in the other hand, if user reacts with ❌ one, It discard the user input and Showing Error Message.
The bot works.... only the Checking Embed. how nice.
and yes, bot deployed with no error, and If I use slash command and input the Data, the Bot prints Checking Embed and React Emojis well. (Look at the Picture for result.)
But It didn't react when I responded emoji. both of all not work.
here's my code about that command.
#slash.slash(
name="ν…ŒμŠ€νŠΈμ“°κΈ°",
description="μƒ˜ν”Œ 데이터λ₯Ό ꡬ글 μ‹œνŠΈμ— μž‘μ„±ν•©λ‹ˆλ‹€.",
guild_ids=[865433__________],
options=[
create_option(
name="이름",
description="μŠ€ν”„λ ˆλ“œμ‹œνŠΈμ— ν‘œμ‹œλ  이름",
option_type=3,
required=True
),
create_option(
name="생일",
description="MM-DD ν˜•μ‹μœΌλ‘œ μž…λ ₯",
option_type=3,
required=True
)
],
connector={
'이름': 'uname',
'생일': 'btday'
}
)
async def ap_sheet(ctx:SlashContext, uname:str, btday:str):
checker = await ctx.send(embed=check_emb(ctx, uname=uname, btday=btday))
await checker.add_reaction("β­•")
await checker.add_reaction("❌")
async def check(reaction, user):
return user == ctx.user and reaction.message.id == checker.id and str(reaction.emoji) in ['β­•','❌']
while True :
try:
reaction, user = await bot.wait_for("reaction_add", check=check, wait_for=20.0) #Wait 20sec for user input
if str(reaction.emoji) == 'β­•': #User Accepts and wants to append data to sheet
await checker.delete()
await ctx.send(discord.Embed(title="μ™„λ£Œ", description="μž…λ ₯ν•œ 정보λ₯Ό λ‚΄λ³΄λƒˆμŠ΅λ‹ˆλ‹€.", color=discord.Color.blue()), delete_after=5)
gc1.append_row([uname, ctx.author.id, btday])
elif str(reaction.emoji) == '❌': #User Cancels command
await checker.delete()
await ctx.send(discord.Embed(title="μ·¨μ†Œλ¨!", description="μ‚¬μš©μžμ˜ μ‘°μž‘μ— μ˜ν•΄ μ·¨μ†Œλ˜μ—ˆμ–΄μš”.", color=discord.Color.red()), delete_after=5)
pass
except asyncio.TimeoutError: #Time is Over
await ctx.send("μ‹œκ°„ μ΄ˆκ³Όλ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
This is the proper way to call a check, it doesnt look like yours is ever called
answers = ["β­•","❌"]
for reaction in answers:
await ctx.message.add_reaction(emoji=reaction)
def check(reaction, user):
if str(reaction.emoji) in answers and user.id == ctx.author.id:
return user == ctx.author and str(reaction.emoji) == str(reaction.emoji)
try:
reaction, user = await bot.wait_for('reaction_add', timeout=20.0, check=check)
if(str(reaction)=="❌"):
#Deny code here, I would recommend deleting the prompt
if(str(reaction)=="β­•"):
#Confirm code here
except:
#Code will go here if there is a timeout. I would recommend deleting the prompt
Oh, you have an indentation mistake in your while loop. Your while loop is inside your reaction check function.

Discord.py wait_for message... How to take the message content and the person who wrote the message?

I'm searching to write a shutdown-room command, that mute all the users in a specific room.
So, I have to get the user that wrote the message and the message content.
For now I have this, but in this case every message is monitored:
def shut_check(msg):
return msg.content
#client.command()
#commands.has_permissions(manage_messages=True)
async def shut_room(ctx):
await ctx.send("Please send me the id of the room that you want to shut down.")
content = await client.wait_for("message", check=check)
Now, I have the content of the message sent, but how can I verify if the author of the message is the ctx.author?
I have another request, can you explain to me what's the purpose of the pass_context=True in:
#client.command(pass_context=True)
It's simple logic, define the check function inside the command. Also your current check doesn't make any sense at all, it will always return a true-like value
#client.command()
async def shut_room(ctx):
def check(msg):
return msg.author == ctx.author
await ctx.send("Please send me the id of the room that you want to shut down.")
message = await client.wait_for("message", check=check)
content = message.content
Also when waiting for a message it doesn't return the content itself, but a discord.Message instance, I think you got confused with the check function.
Explaining the check parameter: the bot will wait for the desired event until the check function returns a truthy value.
EDIT:
Defining the check func outside the command
def check(ctx):
def inner(msg): # This is the actual check function, the one that holds the logic
return msg.author == ctx.author
return inner
# inside the command
message = await client.wait_for("message", check=check(ctx)) # Note that I'm calling it
I've wrapped the check function with another function so I can pass the Context argument.

Discord Bots: Detecting a message within another command

On my discord bot (python) I am trying to detect or rather to check a sent message within a command: if someone types the command $start the command should check in a while loop which messages are being sent after the $start command. If the sent message is "join", the user who sent the message should be added to a list. I was trying to detect and save messages in a variable via the on_message() function but it doesnt seem to work and I dont think that my code makes that much sense, but I dont know how to properly implement it.
import discord
from discord.ext import commands
client = commands.Bot(command_prefix = "$")
#client.event
async def on_ready():
print("started")
sentMsg = ""
users = []
#client.event
async def on_message(msg):
if msg.author == client.user:
return
else:
sentMsg = msg.content
print(sentMsg)
#client.command()
async def start(ctx):
print(sentMsg)
await ctx.send("starting ...")
users.append(ctx.author)
while True:
if sentMsg == "join":
ctx.send("Joined!")
sentMsg = ""
client.run(*token*)
I put the sentMsg varibale to the Watch-section in VS-Code and it always says "not available" although it prints the right values. Also when hovering over it in on_message() it says: "sentMsg" is not accessed Pylance.
Can anyone improve my code or does anyone have a better idea to implement this?
I'd appreciate anyone's help
instead of using an on_message event, you can make the bot detect messages using client.wait_for!
for example:
#client.command()
async def start(ctx):
await ctx.send("starting ...")
try:
message = await client.wait_for('message', timeout=10, check=lambda m: m.author == ctx.author and m.channel == ctx.channel and ctx.content == "join") # You can set the timeout to 'None'
# if you don't want it to timeout (then you can also remove the 'asyncio.TimeoutError' except case
# (remember you have to have at least 1 except after a 'try'))
# if you want the bot to cancel this if the message content is not 'join',
# take the last statement from the check and put it here with an if
# what you want to do after "join" message
except asyncio.TimeoutError:
await ctx.send('You failed to respond in time!')
return

How can I work around a possible "wait_for" event error with my discord bot?

I have an event waiting for a reaction under a message. But if the bot should fail once this event is dropped and if you then click on the reaction there is no reaction from the bot, because he can not capture this reaction. Is there an alternative how to get the bot to continue anyway?
I don't like to work with a timeout event or asyncio.sleep, because the input should come from the user. Is this possible?
My code so far without workaround:
try:
reaction, user = await self.bot.wait_for('reaction_add', timeout=43200)
while user == self.bot.user:
reaction, user = await self.bot.wait_for('reaction_add', timeout=43200)
if str(reaction.emoji) == "⏯":
await ctx.send("**Your next question will appear in `6` seconds.**", delete_after=5)
await intense.delete()
except asyncio.TimeoutError:
await ctx.send("**Really?! I though `12 hours` would be enough..."
"Your next question will come in about `6` seconds.**", delete_after=5)
await asyncio.sleep(6)
Below I've added two examples and tweaked up existing code to make it a little more cleaner. It cannot fail unless the message that the bot was adding to was deleted, otherwise it would work. I also added a check function just to help, but you can change that to whatever you want. Included a true loop, so it would keep waiting for an input.
message = await ctx.send("Test reactions")
back = '⬅️'
forward= '⏯'
await message.add_reaction(back)
await message.add_reaction(forward)
def check(reaction, user):
return user == ctx.author and str(reaction.emoji) in [back, forward]
member = ctx.author
while True:
try:
reaction, user = await client.wait_for("reaction_add", check=check)
if str(reaction.emoji) == back:
await ctx.send('Going back')
if str(reaction.emoji) == forward:
await ctx.send('Going forward')
Your other solution is just to use on_reaction_add event, but I wouldn't recommend, unless your bot is small and no one minds if a message always gets send if they sent an emoji you put in your code.
It's by far a much easier approach but may not expect the best results compared to the wait_for But hope this helps too.
#client.event
async def on_reaction_add(reaction, user):
if reaction.emoji == '⬅️':
await ctx.send('Going back')
if reaction.emoji == '⏯':
await ctx.send('Going back')
The problem with this, is it's always active and waiting for those reactions to be added, it can cause intermittent slowdown of the bot, or people could react to the emojis by accident and mess up the command because they shouldn't be able to use these commands unless the original command has been invoked.
I believe raw_reaction_add should work in this context. Eveytime a user will interact with the message reaction it causes an event

Discord.py Commands within Commands?

I've been thinking of making a Blackjack game in my discord bot, but I've hit a roadblock.
I obviously have the game which is summoned with the command .blackjack, and it works fine in generating the random values and sending the messages. However, I don't know how to make it so the player is able to say hit or stand after the message with the cards dealt is sent, for example.
#client.command()
async def blackjack(ctx):
# (insert all random number gens, etc. here)
await ctx.send(f"{dface1}{dsuit1} ({dvalue1}), {dface2}{dsuit2} ({dvalue2})")
await ctx.send(f"(Dealer Total: {dtotal})")
await ctx.send(f"{pface1}{psuit1} ({pvalue1}), {pface2}{psuit2} ({pvalue2})")
await ctx.send(f"(Total: {ptotal})")
Now what? What do I do to run my next part of the code, which is whether or not the player hit or stands, the dealer hitting and standing, etc.
I don't really know how to play Blackjack, so I'm afraid I won't be able to give you a full answer to your question. However I will say how you can achieve what you want. There are two ways you can go about doing this in my opinion.
Method 1
Waiting for the user to react to your bot's message
For this, you have to use:
reaction, user = await client.wait_for('reaction_add', timeout=60.0, check=check)
For example, say you are waiting for πŸ‡¦ or πŸ…±οΈ from the user (This can mean hit and stand respectively). The code would look something like this:
#client.command()
async def start(ctx):
def check(reaction, user):
return (user == ctx.author) and (str(reaction.emoji) == 'πŸ‡¦' or str(reaction.emoji) == 'πŸ…±οΈ')
async def sendMessage(msg):
message = await ctx.send(msg)
await message.add_reaction('πŸ‡¦')
await message.add_reaction('πŸ…±οΈ')
try:
reaction, user = await client.wait_for('reaction_add', timeout = 60.0, check = check)
except:
await message.clear_reactions()
await ctx.send('No reaction received.')
else:
await message.clear_reactions()
return reaction
return 0
reaction = str(await sendMessage('This is my message'))
This is a simple code to check if the user reacts with πŸ‡¦ or πŸ…±οΈ. You'll have to add more conditions and loops to get what you desire.
Method 2
Waiting for the user to send a message
For this, you have to use:
msg = await client.wait_for('message', check = check, timeout = 60.0)
You'll have to then check if msg equals hit or stand or some short form like h or s. Also be sure to write a check(author) function that is called inside the client.wait_for() function (check = check) to check if the author is that same as the one that ran the command.
I hope you'll be able to come up with the code you are looking for after reading this answer.
discord.py has built-in subcommand support, here's an example:
#commands.group(invoke_without_subcommand=True)
async def your_command_name(ctx):
# Do something if there's not a subcommand invoked
#your_command_name.command()
async def subcommand_name(ctx, *args):
# Do something
# To invoke
# {prefix}your_command_name subcommand_name some arguments here
Or you can simply wait for a message
#client.command()
async def blackjack(ctx):
# ...
def check(message):
"""Checks if the message author is the same as the one that invoked the
command, and if the user chose a valid option"""
return message.author == ctx.author and message.content.lower() in ['stand', 'hit']
await ctx.send('Would you like to hit or stand?')
message = await client.wait_for('message', check=check)
await ctx.send(f"You chose to `{message.content}`")
# To invoke
# {prefix}blackjack
# Would you like to hit or stand?
# stand
# You chose to `stand`

Categories

Resources