How to properly get user list from reactions? - python

I'm currently trying to create a Discord Bot that randomizes people into 2 teams.
Right now I was able to send the messages with the reactions, and then returning a list of users that reacted to the bot's message. But I'm facing the following problems:
The list includes the bot;
The list only includes the bot and the first person that reacted to the message (I would like to randomize as soon as I hit 10 players not including the bot)
I need to separate the list into two teams
if str(reaction.emoji) == '✅': ## right now this is just selecting the game
def check(reaction, user): ## this check is here but I was unable to properly use it without breaking the code, or even defining the limit of 10 reactions
return user != bot.user and (str(reaction.emoji) == '🎲')
msg_players1 = await ctx.send('Summoners, react below: ')
await msg_players1.add_reaction('🎲')
msg_players1 = await ctx.channel.fetch_message(msg_players1.id)
reactions = msg_players1.reactions
users = set()
for reactions in user, reaction:
async for user in reaction.users():
users.add(user)
await ctx.send(f"time 1: {', '.join(user.name for user in users)}")
I'm very new to coding, even more to Discord bots, any help is very much appreciated!
Thanks!
EDIT: I just tested and it's not returning the name of the user that is reacting to the 🎲 emoji, it's returning the name of the user that called the command

You're fetching the message as soon as you add a reaction to it, there's not enough time for users to react to it. You could sleep x amount of seconds and fetch the message, but it's a really bad idea, you'd have to guess how much time to give for the 10 users to react.
Instead you can use a while loop, wait for the exact 10 reactions (excluding the bots) and continue.
users = set()
def check(reaction, user):
return str(reaction) == "✅" and user != bot.user # or `client.user`, however you named it
while len(users) < 10:
reaction, user = await bot.wait_for("reaction_add", check=check)
users.add(user)
# once here, divide the `users` set into teams
References:
Bot.wait_for

Related

Get the user who triggered the on_raw_reaction_remove(payload)

I am using async def on_raw_reaction_remove(payload) to track when a user removes a reaction..
How do I get the user who triggered the event? In my code i have my bot trigger this event by removing reactions in certain scenarios - but when this happens user = await client.fetch_user(user_id) always equals to the user who had reacted with the removed reaction and not the bot that removed it (i need to get the bot as user when the bot removes someone's reaction, so i can say if user == bot: return
I also tried
message = await channel.fetch_message(payload.message_id)
guild = message.guild
member = await guild.fetch_member(payload.user_id)
but it's the same.. the member is always pointing to the owner of the reaction that was removed, not the bot that's removing the reaction...
How can i get the one who is removing the reaction when using on_raw_reaction_remove?
(if it's relevant - i enabled intents in discord's developer portal and updated my code with
intents = discord.Intents.all()
client = discord.Client(intents=intents)
Thanks for your help, i wasted so much time on this already :frowning: (new to the discord api)
The answer is - there is nothing in the discord python API that can tell you who removed the reaction.
For my case, I solved it with a workaround - I just had to track if my bot removed a reaction with a variable - I am using a global variable to track when my bot removes a reaction and then just check later if the variable is True or False and respond accordingly and reset the variable back.
Thanks
What I did in this case was this:
guild = discord.utils.find(lambda g: g.id == payload.guild_id, bot.guilds)
member = discord.utils.find(lambda m: m.id == payload.user_id, guild.members)
You now just have to build this into your code

Programming a Discord bot in Python- Is there a way to limit the number of reactions each user can add in a certain channel?

I saw something that would limit a certain number of reactions to a message, but that's not quite what I want. I'm trying to limit the number of reactions each user can add in a certain channel (for example, each person would only be allowed three reactions at a time), - not quite sure how to do that. I'm new to programming, so any insight would be greatly appreciated.
If I'm wrong, you can correct me in comments, but as I understand, you want to limit reaction count for each message in a specific channel for each person. You can do this with different ways.
For the first way, you can get the all reaction users with a nested for loop, then you can check in on_reaction_add event that if the person that added new reaction has already for instance 3 reactions on this message.
#client.event
async def on_reaction_add(reaction, user):
channel = user.guild.get_channel(<channel id>)
if reaction.message.channel == channel:
users = [user for reaction in reaction.message.reactions for user in await reaction.users().flatten()]
if users.count(user) >= 3:
await reaction.message.remove_reaction(reaction, user)
Or you can do this with another way, without nested loops.
#client.event
async def on_reaction_add(reaction, user):
channel = user.guild.get_channel(<channel id>)
users = []
if reaction.message.channel == channel:
for i in reaction.message.reactions:
users += await i.users().flatten()
if users.count(user) >= 3:
await reaction.message.remove_reaction(reaction, user)
As far as I know, the second code is more optimized.
Both of these options will block a user from using more than 3 reactions to a single message in a specific channel.
EDIT
If you want to limit reaction amount for each member for all the messages in a specific channel:
At first, you have to get all the messages of this channel, You can do this by using discord.TextChannel.history(). Later, you have to get all the reactions of these messages. After that, you can get the reaction users in a list, then you can check if a user has already used 3 reactions.
#client.event
async def on_reaction_add(reaction, user):
channel = user.guild.get_channel(<channel id>)
users = []
if reaction.message.channel == channel:
for mess in await channel.history(limit=100).flatten():
for react in mess.reactions:
users += await react.users().flatten()
if users.count(user) >= 3:
await reaction.message.remove_reaction(reaction, user)
This code will only check the last 100 messages of the channel. You can change this by changing the limit parameter. However, it is not very optimized as it loops through all messages and all reactions. So if this channel is a chat channel, this code may cause a delay in your bot.
You can use the client.event or Cog.listener decorators to listen for specific events. In this case, we'd listen for on_reaction_add. On reaction, if the amount of reactions is greater than a given amount (perhaps stored in a db), it will remove the reaction.
#client.event
async def on_reaction_add(reaction, user):
if len(reaction.message.reactions) > reaction_limit:
await reaction.remove()
This will prevent any more reactions from being added once the unique emoji limit, reaction_limit, has been reached. This means even reactions on previous reactions that came before the limit was reached will be removed.
In a cog the code will not be too different.
#commands.Cog.listener()
async def on_reaction_add(self, reaction, user):
if len(reaction.message.reactions) > reaction_limit:
await reaction.remove()
Now, the issue with on_reaction_add is that it will not be called if the message is not stored in the cache. on_raw_reaction_add, however, will be called regardless of cache state. This will be the better, more viable solution, as it will not be affected by the many resets, shutdowns, and reloads your application will undoubtedly go through during development. Here is how I would implement this:
# this will be done outside of a cog, for brevity.
#client.event
async def on_raw_reaction_add(payload):
channel = client.get_channel(payload.channel_id)
if channel.guild is None:
return # this will only happen in the case of DMs.
msg = await channel.fetch_message(payload.message_id)
if len(msg.reactions) > reaction_limit:
await msg.remove_reaction(payload.emoji.id, payload.user_id)

How to get ID of a message that user is reacting to [discord.py]?

So I'm trying to write a attendance bot for my wow guild that upon using a command would:
send message based on that command [raid_type, role, date]
react to that message with emojis representing each role [dps, healer, tank]
Based on reactions, it would also add the name and the role of someone who reacts to that message to google spreadsheet.
While I think I have nailed down the adding reaction to the bot's message and actually storing the data on the spreadsheet, I have no idea how to make it that only the reaction to that particular message that bot sent would count. Right now if you react with, let's say emoji_1 which is responsible for 'dps', to any message on the whole server, bot will also add you to the spreadsheet.
Hence my question, how to refer to the message that user is reacting to ?
Here's part of the code:
#client.command(aliases=["nraid", "newboost"])
async def newraid(ctx, boost_type, date, *, message):
mes = f"sign up for {boost_type} on {date}. Additional info: \n{message}"
await ctx.send(mes)
channel = ctx.channel
embed = discord.Embed(title="A wild raid has appeared!",
description=f"sign up for {boost_type} on {date}. Additional info: \n{message}",
color=discord.Color.purple())
message_ = await channel.send(embed=embed)
global message_id
message_id = message_.id
await message_.add_reaction("🧙") # dps
await message_.add_reaction("🛡️") # tank
await message_.add_reaction("🏥") # healer
num = 0
rownum = 1
#client.event
async def on_reaction_add(reaction, user):
if not user.bot:
That's the part I have no clue about - how to get the ID of the message that user is reacting to and compare it with ID of the message that bot sent.
if message_id == user.message.id:
if reaction.emoji == ['🧙']:
global num, rownum
discord_name = user.display_name
role = 'dps'
num += 1
rownum += 1
can_attend = 'yes'
newrow = [num, discord_name, role, can_attend]
sheet.insert_row(newrow, rownum)
I would not be surprised if the answer is really simple, but I must admit I have just picked up python and a lot of the stuff, like reading API's documentation, analysing someone's code, can be hard at times and some of the guides on Youtube don't go into details. Any help is much appreciated :)
You can just type reaction.message.id

Is it possible to make a command !messages #user

I'm trying to make a command where when someone types for example '!messages #mee6' it shows how many messages that person has said in the server. So say if I typed "a" "b" "c" then did "!messages"
the bot would reply with "#user has sent 3 messages in this server. Does anyone know if this would be possible and if so how would I go about doing it in discord.py?
You could do this by iterating over each text channel in your server and then iterating over every message in the channel. Then count the amount of messages that are sent by the user.
#bot.command()
async def message(ctx, user: discord.User):
count = 0
for channel in ctx.message.guild.text_channels:
async for message in channel.history(limit=None):
if message.author.id == user.id:
count += 1
await ctx.channel.send(('{} has sent {} messages').format(user.mention, count))
This is my method but i believe it will take some time to do so especially if the server is big because you are checking every channel.
#bot.command()
async def messages(ctx, user: discord.Member = None):
if user is None:
# no user selected author is the user now
user = ctx.author
counter = 0
for channel in ctx.guild.text_channels:
async for message in channel.history(limit=None):
if message.author == user:
counter += 1
await ctx.send(f'{user.mention} has sent {counter} messages')
The principle is the following:
You'd have to take advantage of the Message Received event. Once a message is received, update the counts for the person, and when the !messages command is ran, display the amount.
Looping through every channel in the server and filtering all messages from the user is extremely inefficient and time taking, as well as it can get your bot ratelimited.
Use the event on_message() so you can count messages from every user from now on.
Create a dictionary containing the users as keys and the amount of messages as values
users_msg = {}
#client.event
async def on_message(message):
#If this is the first user messsage
if message.author not in users_msg:
users_msg[message.author] = 1
else:
users_msg[message.author] += 1
if message.content.startswith("!message"):
#Return a list containing all user mentions on the message
user = message.mentions
#Send the first user mention amount of messages
await message.channel.send(f'{user[0].mention} has sent {users_msg[user[0]]}'
#If you want so you can loop through every user mention on the command !message
#for mentions in user:
#await message.channel.send(f'{mentions.mention} has sent {users_msg[mentions]}')

Discord.py | Avoid getting more than one reaction by a same user to a message sent by a bot

Im trying to make a command that will send in an embed and that has two reactions, a tick and a cross, i want to make the user just react to one of the reaction, rather than reacting to both of them. I also need some help in making a system to ensure that the person reacting has a specific role. Any help would be highly appreciated!
This is possible using the on_raw_reaction_add() event.
#bot.event
async def on_raw_reaction_add(payload): # checks whenever a reaction is added to a message
# whether the message is in the cache or not
# check which channel the reaction was added in
if payload.channel_id == 112233445566778899:
channel = await bot.fetch_channel(payload.channel_id)
message = await channel.fetch_message(payload.message_id)
# iterating through each reaction in the message
for r in message.reactions:
# checks the reactant isn't a bot and the emoji isn't the one they just reacted with
if payload.member in await r.users().flatten() and not payload.member.bot and str(r) != str(payload.emoji):
# removes the reaction
await message.remove_reaction(r.emoji, payload.member)
References:
on_raw_reaction_add()
Message.remove_reaction()
Reaction.users()
User.bot
RawReactionActionEvent - (the payload)
Message.reactions
Client.fetch_channel()
TextChannel.fetch_message()
I think you can store the userId or something that is unique for all the user.
And then create a function check this ID appeared more than once or other logic.
https://discordpy.readthedocs.io/en/latest/api.html#userUser API
Here, you can get user ID from this object.
Further more, you can get author id from message your client receive.
def on_message(message):
print(message.author.id)

Categories

Resources