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]}')
Related
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
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)
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
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)
A server owner on discord asked me to Add a custom currency system to my bot for him (the bot is only on this server). To encourage activity we did not go with the dailies system and instead my idea was to have a message pop up every 7min with an image and users have to add a "Reaction" to the image. To make it easy and control the inputs i want to add a reaction to my image so users can just click the reaction and it will add the amount.
With all that context out of the way, here is the problem as a background task. I have no clue how to pass context to the reaction!
async def my_background_task():
await bot.wait_until_ready()
counter = 0
channel = discord.Object(id='446782521070321664')
while not bot.is_closed:
counter += 1
with open('vbuck.png', 'rb') as f:
await bot.send_file(channel, f) #sends a png of a vbuck
await bot.add_reaction(discord.message,'<:vbuck:441740047897591809>') #This is the Reaction part
await asyncio.sleep(420) # task runs every 7 min seconds
bot.loop.create_task(my_background_task())
If you could give me advice that would be great and if you are feeling extra generous Code + explanation would be appreciated I am learning python from this project.
You need to save the Message object that send_file returns (the message that was sent). Then you can use that Message object in Client.wait_for_reaction to wait for a reaction.
async def task():
await bot.wait_until_ready()
channel = discord.Object(id='446782521070321664')
vbuck = discord.utils.get(bot.get_all_emojis(), name='vbuck')
check = lambda reaction, user: user != bot.user
while not bot.is_closed:
msg = await bot.send_file(channel, 'vbuck.png') #sends a png of a vbuck
await bot.add_reaction(msg, vbuck)
for _ in range(7*60):
res = await bot.wait_for_reaction(message=msg, emoji=vbuck, check=check, timeout=1)
if res:
# res.user reacted to the message
If I understood the question correctly, you want to actually wait for users to react to the file you posted and then award them on that reaction. I'm gonna assume you are using discord.py version 1.0 or higher.
The background task itself can not, as far as I know, be handed any context - because it isn't invoked like a comment in a specific context.
However, the API (https://discordpy.readthedocs.io/en/rewrite/api.html#discord.on_reaction_add) states that there is a listener to reactions on messages, which means you can simply use
#bot.event
async def on_reaction_add(reaction, user):
#here do some checks on reaction.message and to check that it is actually
#the correct message being reacted too and avoid multiple reactions of one user.
The API will also tell you what kind of checks you can do on the message. You could give the message a certain signature when you let the bot post it (a timestamp like time.time() seems good enough) and then access reaction.message.content and compare it to the current time. To do that, I would modify your background task to something like this:
async def my_background_task():
await bot.wait_until_ready()
counter = 0
channel = bot.get_channel(id='446782521070321664')
while not bot.is_closed:
counter += 1
mess = "maybe a timestamp"
e = discord.Embed()
e.set_image(url = "some cool image maybe randomly chosen, please no vbucks")
await channel.send(mess, embed = e) #sends a message with embed picture
await asyncio.sleep(420) # task runs every 7 min
the message content will then simply be message.
Hope this helps.