Gathering reactions from a private DM but ignoring bot reactions - python-3.x

I'm making a command for my bot where it sends you this message:
The two reactions (tick and cross) become pre-reacted by the bot to show the user what to do. I wan't my bot to wait a set period of time and check for what the user has reacted with, at the moment my bot counts its own reactions so the count is false.
How would I go about doing this, everything I have tried has failed and I can no longer Ctrl+Z to the point where I have a vaguely working code.
Thanks.
EDIT: I actually fixed my problem about half an hour ago after many attempts, here's what I used:
res1 = await client.wait_for_reaction(emoji="✅", message=message_1, timeout=10)
res2 = await client.wait_for_reaction(emoji="❌", message=message_1, timeout=10)
if not res1.user == None or not res2.user == None:
if not res1.user.id == client.user or res2.user.id == client.user:

The best way to do this is to add a check to you wait_for_reaction that just checks the reacting user against client.user. This has the benefit of also working in scenarios where you want to accept exactly one reaction, and can't do another loop if you accidentally see a bot reaction
def not_bot_reaction(reaction, user):
return user != client.user
...
res1 = await client.wait_for_reaction(emoji="✅", message=message_1, timeout=10, check=not_bot_reaction)
res2 = await client.wait_for_reaction(emoji="❌", message=message_1, timeout=10, check=not_bot_reaction)
if res1 or res2: # If the wait_for times out, it will return None instead of the namedtuple
...

Related

How to have my Discord Bot (Python API) leave a voice channel in an event?

I need help having my discord bot leave a voice channel through an event with the Python API.
I've created an event that makes my bot join a voice channel if there is only 1 person present in said channel after 5 seconds, this part works. I then need my bot to leave the channel if another person joins or the one person leaves. I have not been able to figure out how to get it to leave. Any help would be much appreciated! Here is what my method looks like right now, need help at the "#Leave voice channel" comment:
#client.event
async def on_voice_state_update(member, before, after):
if member == client.user: #CATCH
return
if after.channel is None: #User has left a voice channel
print("User left voice channel")
return
else:
if before.channel is not after.channel:
memids = []
VC = member.voice.channel
for mem in VC.members:
memids.append(mem.id)
if len(memids) == 1:
await asyncio.sleep(5) #to be 10
print(len(memids))
if len(memids) == 1:
await VC.connect()
print("Joined Channel")
else:
print("Not Alone Anymore...")
return
else:
print("!=1")
#Leave voice channel
else:
return
return
return
You can use await ctx.voice_client.disconnect() to disconnect.

How do I make Discord.py bot remove roles sooner?

I have made a bot which automates role adding and removal. It all works, but the adding works in 15/20 seconds after running the command. The removal takes up to 15 minutes.
What am I doing wrong? I have started a few months ago with programming and started with Python, so any hint is usefull.
The guildid, channelid and roleid are numbers now, I put placeholders in.
#tasks.loop(seconds=10)
async def autoderolergame():
tasks = []
guild = client.get_guild(guildid)
channel = guild.get_channel(channelid)
role = guild.get_role(roleid)
for member in guild.members:
if member not in channel.members:
try:
await member.remove_roles(role, reason='Leaving Voice channel')
except:
pass
if str(member.status) == 'offline':
try:
await member.remove_roles(role, reason='Going offline, so no more VIP rain')
except:
pass
I also try to subtitute 'for member in guild.members' with
'async for member in guild.fetch_members():'
Sadly this made no difference.
I am thinking about saving a list of users in the channel and updating that every 10 seconds and then look at the list and select all users which aren't in the channel anymore, remove the role and then remove them from the list, would that be a better solution?
I rather don't because I would like to use the built in functions of Discord.py.
Thanks for your help!
You can use an asynchronous approach to make it faster
import asyncio
remove_roles_semaphore = asyncio.Semaphore(20)
async def remove_role(member, role, reason):
async with remove_roles_semaphore:
try:
await member.remove_roles(role, reason=reason)
except:
pass
#tasks.loop(seconds=10)
async def autoderolergame():
guild = client.get_guild(guildid)
channel = guild.get_channel(channelid)
role = guild.get_role(roleid)
tasks = []
for member in guild.members:
if member not in channel.members:
tasks.append(remove_role(member, role, 'Leaving Voice channel')
if str(member.status) == 'offline':
tasks.append(remove_role(member, role, 'Going offline, so no more VIP rain')
await asyncio.gather(*tasks)
Though keep in mind that you can get easily ratelimited for this, if you hit the global ratelimit (50 requests per second) you can get banned. Use this method at your own risk (you might want to enable logging to see any ratelimit warnings)
I used asyncio.Semaphore to limit the amount of requests made concurrently, feel free to change the value, but remember -- the higher the value the higher the chances to get banned (do not exceed 50).
Sadly I cannot really test the code so I can't promise that it's going to be faster.
I found a way to solve the problem.
I was using the code to look at all the guild members.
Since I want roles to be removed upon leaving the channel, I only needed too look at the roles a user has and then check if they are still in the room.
So this is my new code:
#tasks.loop(seconds=10)
async def autoderolergame():
tasks = []
guild = client.get_guild(guildid)
channel = guild.get_channel(channelid)
role = guild.get_role(roleid)
for member in role.members: (<-- only changed this from guild to role)
if member not in channel.members:
try:
await member.remove_roles(role, reason='Leaving Voice channel')
except:
pass
if str(member.status) == 'offline':
try:
await member.remove_roles(role, reason='Going offline, so no more VIP rain')
except:
pass
I also changed to seconds to a longer interval because of the tip Łukasz Kwieciński gave me regarding the rate limit. It's not an issue now since it works like 1000x times faster.

What could cause discord.VoiceChannel.edit() to have major performance issues?

I've been working on a Discord bot using the discord.py library, and I want to add a feature where it updates a voice channel's name every time someone joins; for example, it starts as "zero noobs" with no one connected and changes to "one noob", "two noobs", etc. as people connect or disconnect.
I tried implementing this with Bot.on_voice_state_update():
#bot.event
async def on_voice_state_update(member: discord.Member, before: discord.VoiceState, after: discord.VoiceState):
# Change name of noobs channel upon change in number of connected members
try:
if (before.channel.id == CHANNEL_ID or after.channel.id == CHANNEL_ID)\
and before.channel != after.channel:
await _edit_noobs()
# Either before.channel or after.channel is None (user connected/disconnected)
except AttributeError:
await _edit_noobs()
where _edit_noobs() is a helper function:
async def _edit_noobs():
myserver = discord.utils.find(lambda g: g.id == GUILD_ID, bot.guilds)
channel = discord.utils.find(lambda c: c.id == CHANNEL_ID, myserver.voice_channels)
nums_strs = ("zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten")
num_connected = len(channel.members)
try:
num_str = nums_strs[num_connected]
# More than ten members connected (just in case)
except IndexError:
num_str = "a lot of"
# Handle singular form
if num_connected == 1:
noobs = "noob"
else:
noobs = "noobs"
await channel.edit(name=f"{num_str} {noobs}")
I've tested this code with print() statements numerous times, and there doesn't seem to be a problem in the logic (the functions run when they should, and the variables assume the values they should). The problem is the await channel.edit() line takes a very long time to execute. When I was testing the bot, it could successfully change the channel name 2-3 times in quick succession, but after that it lags - changing the name minutes after an event. I don't see any notes on potential performance issues in the documentation, nor have I seen questions regarding this. I'm not too familiar with how coroutines work in Python and discord.py, so is there something that could be causing my await statements to lag so much?
Also: discord.utils.find() and all other lines execute almost instantly (tested with print() statements); the only lines that didn't were the await statements.

How do I fix a counting program where the current number is incremented when a duplicate author is detected?

I'm having a bit of trouble: I made a counting channel and this part of my code:
recent_author = (await message.channel.history(limit=2).flatten())[1].author
if message.author == recent_author:
await message.author.send(f"You can't hog this channel, let someone else have a turn!")
await message.delete()
prevents users from sending messages more than once; but, when the message is deleted, the variable that holds the current number is incremented. That's not supposed to happen but it does: I've tried doing so many things at this point like moving this piece of code below other pieces of code in the event function or merging the code into an if else statement--nothing seems to solve my problem. I was thinking about subtracting 1 from the current number when a duplicate author is detected; however, that would mean subtracting 1 for anything other than a number.
Here's my full code:
import discord
from discord.ext import commands
import re
class Counting(commands.Cog):
""" Users take turns incrementing a number. """
def __init__(self, client):
self.client = client
self.count_channel = 736241527847911494
self.cum_num = 98
self.num_regex = re.compile(r'(^\d+)')
#commands.Cog.listener()
async def on_message(self, message):
if message.channel.id == self.count_channel:
try:
recent_author = (await message.channel.history(limit=2).flatten())[1].author
if message.author == recent_author:
await message.author.send(f"You can't hog this channel, let someone else have a turn!")
await message.delete()
match_obj = self.num_regex.search(message.content)
if int(match_obj.group(1)) != self.cum_num + 1:
await message.author.send(f'Your message must start with **{self.cum_num + 1}**!')
await message.delete()
else:
self.cum_num += 1
except AttributeError:
await message.delete()
This looks like the if/else logic needs tweaking.
Directly after your check for the duplicate author it initialises and sets match_obj then runs the if statement.
So despite the message being removed from the text channel if the message content was valid then it will still increment the counter because nothing is preventing it from running that logic.
It looks to me like you will want to structure it as follows:
recent_author = (await message.channel.history(limit=2).flatten())[1].author
if message.author == recent_author:
await message.author.send(f"You can't hog this channel, let someone else have a turn!")
await message.delete()
else:
match_obj = self.num_regex.search(message.content)
if int(match_obj.group(1)) != self.cum_num + 1:
await message.author.send(f'Your message must start with **{self.cum_num + 1}**!')
await message.delete()
else:
self.cum_num += 1
This way if the latest author is the duplicate author it won't call the code in the else block. However, if it is not a duplicate author it will run the next stage of the code.
You could try using wait_for and in the check compare user names to the previous message, if they are the same then you can do something if not you can ignore it. You could also try using a database or memory for this.

Python Compare lists

So I have this script,
good_users=[]
async def callposts(ctx):
g0=str(ctx.guild)
g=g0.replace(' ','_')
sqluse(g)
x="SELECT authorid, COUNT(*) FROM posts GROUP BY authorid"
mycursor.execute(x)
k=mycursor.fetchall()
for user in k:
if user[1] > 7:
good_users.append(user)
k.remove(user)
print(user)
return k
async def kick(ctx, uid):
await ctx.guild.kick(ctx.guild.get_member(int(uid)))
#client.command(pass_context = True)
async def post_check(ctx):
ausers=list(ctx.guild.members)
lowposters= await callposts(ctx)
for user in ausers:
if user == client.user:
print(user.name+" is a bot!")
ausers.remove(user)
elif user.id in exempt:
print(user.name+" is exempt!")
ausers.remove(user)
elif user.id in good_users:
print(user.name+" is a good user!")
ausers.remove(user)
else:
await kick(ctx,user.id)
print(ausers)
What I am trying to do here is remove inactive users. So I have 2 lists that I want to compare the memberlist to, exempt and good_users. I also am checking to make sure it isn't the bot. So this script removes the bot from the list but it doesn't remove the user that's in exempt or good users. So in turn it tries to kick everyone that's not a bot. They are trying to take over!
I'm looking this over but right now I'm sick so not 100%.
The prints are just for troubleshooting purposes however, all but the firt print in the callpost function print nothing and that one for some reason only prints the user, now it isn't printing the bot so the bot may not be in the list to get removed.
Any thoughts?
You're never appending anything to exempt_users and because of the scope of good_users it is only filled with users within callposts() because you're not returning it when calling it in post_check().
Changing the following should fix your problem:
Return good_users from callposts()
Add a new variable to where you call callposts() in post_check() like
lowposters, good_users = await callposts(ctx)
So I finally figured this out, the script was looking at user.id as an attribute of user object. To fix that I had to use
elif str(user.id) in exempt): and elif str(user.id) in good_users:

Resources