I wanted, to make giveaway command. I know how to do this, but there's one problem, i don't know how to make my bot count reactions. I maked a command, bot sends message, adds reactions, sleep x time, and then it must to count reactions and choose winner. How do i do this?
send message
wait x seconds
fetch the message again
count the total reactions
choose a random reaction
choose a random user
# 1
message = await ctx.send('Waiting x seconds')
# 2
await asyncio.sleep(x)
# 3
message = await ctx.channel.fetch_message(message.id)
# 4
total_count = 0
for r in message.reactions:
total_count += r.count
# 5
reaction = random.choice(message.reactions)
# 6
members = await reaction.users().flatten()
winner = random.choice(members)
Related
So basicly, I'm creating a command that let you vote for something and after 5 min it will check which is higher.
But the problem is I don't know how to do that.
Here is my code so far:
#client.command()
async def strongy_boi(ctx, boi):
if boi == "copper-golem-allay":
mess = await ctx.send(":heart: = copper golem :blue_heart: = allay")
await mess.add_reaction('❤️')
await mess.add_reaction('💙')
await asyncio.sleep(5)
#do code that check which is higher
One way you can do this is to access the message reactions through discord.Message.reactions and iterate through them, checking the discord.Reaction.count and comparing with the current highest. To check for reactions, however, a message needs to be cached, which can be done through await fetch_message(). Do view the revised code and further explanations below.
#client.command()
async def strongy_boi(ctx, boi):
if boi == "copper-golem-allay":
mess = await ctx.send("❤️ = copper golem 💙 = allay")
await mess.add_reaction('❤️')
await mess.add_reaction('💙')
await asyncio.sleep(5)
# new code starts here #
msg = await ctx.channel.fetch_message(mess.id) # 'Cache' the message
# create variables to save the highest reactions
highest_reaction = ""
highest_reaction_number = 0
# msg.reactions format:
# [<Reaction emoji='❤️' me=True count=2>, <Reaction emoji='💙' me=True count=1>]
for reaction in msg.reactions: # iterate through every reaction in the message
if (reaction.count-1) > highest_reaction_number:
# (reaction.count-1) discounts the bot's reaction
highest_reaction = reaction.emoji
highest_reaction_count = reaction.count-1
await ctx.send(f"{highest_reaction} wins with {highest_reaction_count} votes!")
Other Links:
Count reactions on a message - Stackoverflow
How do I count reactions on a message in discord py? - Stackoverflow
Reactions counting - Stackoverflow
Get a List of Reactions on a Message - Stackoverflow
I'm currently using this code to check the number of messages sent by a user but this approach is very slow, it is taking 1 - 2 min to calculate for each user
user = discord.utils.find(lambda m: m.id== j, channel.guild.members)
async for message in channel.history(limit = 100000):
if message.author == user:
userMessages.append(message.content)
print(len(userMessages))
is there any other fast approach to doing this?
Counting messages
You can use on_message event to count messages.
message_count = {}
#client.event
async def on_message(message):
global message_count
if message.guild.id not in message_count:
message_count[message.guild.id] = {}
try:
message_count[message.guild.id][message.author.id] += 1
except KeyError:
message_count[message.guild.id][message.author.id] = 1
client.process_commands(message)
And then use
member = something # specify member here
try:
count = message_count[member.guild.id][member.id]
except KeyError:
count = 0
# now `count` is count of messages from `member`
To get count of messages from member.
Note: Message count resets on your bot restart but this solution would work very quickly.
Database
Another way to do what you want is use any database to store message count from different members.
This is an anti spam code to prevent spams and stuff.
time_window_milliseconds = 5000
max_msg_per_window = 5
author_msg_times = {}
# Struct:
# {
# "<author_id>": ["<msg_time", "<msg_time>", ...],
# "<author_id>": ["<msg_time"],
# }
#client.event
async def on_message(message):
global author_msg_counts
author_id = message.author.id
# Get current epoch time in milliseconds
curr_time = datetime.now().timestamp() * 1000
# Make empty list for author id, if it does not exist
if not author_msg_times.get(author_id, False):
author_msg_times[author_id] = []
# Append the time of this message to the users list of message times
author_msg_times[author_id].append(curr_time)
# Find the beginning of our time window.
expr_time = curr_time - time_window_milliseconds
# Find message times which occurred before the start of our window
expired_msgs = [
msg_time for msg_time in author_msg_times[author_id]
if msg_time < expr_time
]
# Remove all the expired messages times from our list
for msg_time in expired_msgs:
author_msg_times[author_id].remove(msg_time)
# ^ note: we probably need to use a mutex here. Multiple threads
# might be trying to update this at the same time. Not sure though.
if len(author_msg_times[author_id]) > max_msg_per_window:
await message.channel.send(f"{message.author.mention} stop spamming or i will mute you 😡")
await asyncio.sleep(4)
await message.channel.send(f"{message.author.mention} stop spamming or i will mute you 😡")
muted = discord.utils.get(message.guild.roles, name="Muted")
if not muted:
muted = await message.guild.create_role(name="Muted")
await message.author.send(f"You have been muted in {message.guild.name} for spamming | You'll be unmuted in 10 minutes.")
await message.author.add_roles(muted)
await message.channel.send(f"{message.author.mention} have been muted for 10 minutes.")
await asyncio.sleep(600)
await message.channel.send(f"{message.author.mention} have been unmuted | Reason: Time is over. ")
await message.author.remove_roles(muted)
await message.author.send(f"You have been unmuted from {message.guild.name}")
Hey guys, I'm getting an error here. It works OK, but when someone keeps spamming, the bot keeps spamming, and it becomes a mess. After about a minute, the bot keeps spamming, so stop spamming or I'll mute you.
You need to exclude your own bots messages in the event. So just this should work:
#client.event
async def on_message(message):
if message.author == client.user:
return
#rest of your code
I currently have a function which is polling a message for reactions and adding users to a list based on that using Discord.py. Here is the code below:
#commands.Cog.listener()
async def on_message(self, message):
editMessage = message
tankBoosters = []
healBoosters = []
dpsBooster = []
boosters = []
# we do not want the bot to reply to itself
if message.author != self.bot.user:
return
if len(message.embeds) > 0:
for embed in message.embeds:
if "Boost is Ready" in embed.fields:
return
else:
pass
for x in message.embeds:
if '<:tank:801416324306829312>' and '<:healer:801416334243921971>' and '<:dps:801416343848615947>' in x.description:
await message.add_reaction('<:tank:801416324306829312>')
await message.add_reaction('<:healer:801416334243921971>')
await message.add_reaction('<:dps:801416343848615947>')
embedToEdit = x
def check(reaction, user):
return str(reaction.emoji) in ['<:tank:801416324306829312>', '<:healer:801416334243921971>', '<:dps:801416343848615947>'] and user != self.bot.user
boosters = tankBoosters + healBoosters + dpsBooster
while len(boosters) != 4:
if len(boosters) != 4:
reaction, user = await self.bot.wait_for('reaction_add', check=check)
print(message.reactions)
if reaction.emoji.name == 'tank' and len(tankBoosters) == 0:
tankBoosters.append(user)
if reaction.emoji.name == 'healer' and len(healBoosters) == 0:
healBoosters.append(user)
if reaction.emoji.name == 'dps' and len(dpsBooster) < 2:
dpsBooster.append(user)
if len(tankBoosters) == 1 and len(healBoosters) == 1 and len(dpsBooster) == 2:
message = f"<:tank:801416324306829312> {tankBoosters[0].mention} \n <:healer:801416334243921971> {healBoosters[0].mention} \n <:dps:801416343848615947> {dpsBooster[0].mention} \n <:dps:801416343848615947> {dpsBooster[1].mention}"
embedToEdit.add_field(name="Boost is Ready", value=message, inline=False)
await editMessage.edit(embed=embed)
This is working fine, but what I need to do be able to do is remove users from the respective lists (tank, heal and dps) when a reaction is removed from the message.
I.e. a message is posted and 3 tanks, 2 healers and 6 DPS "sign up" to the message by posting reactions to the message and they are appended to their respective lists. Then 1 tank and 2 DPS "unsign" by removing their reaction to the message. I need to remove those users from the list when they remove their reaction. I have looked into using message.reactions[0].users() but according to the VS Code debug terminal, message.reactions[0].users() is <discord.iterators.ReactionIterator object at 0x011B5DF0>
, which I unfortunately don't know enough about python or discord to understand!
I think you can try this:
#bot.event
async def on_raw_reaction_remove(payload):
reaction = str(payload.emoji)
msg_id = payload.message_id
user_id = payload.user_id
guild_id = payload.guild_id
exists = db.exists(msg_id)
When someone remove his reaction, you know what reaction, the user ID...
For those interested, or frustratedly searching for answers on how to do this, I ended up changing my approach slightly. Instead of doing all the logic inside the on_message function, I removed that and used on_raw_reaction_add and on_raw_reaction_remove instead, and I am tracking users by storing them in global arrays which are emptied once certain conditions are met.
This does mean that the code can only handle looking at one post at a time, but thats all I need it to do for now :)
I am writing a bot for discord and there is a command ($ bone) in which the bot writes the following: "select a number from 1 - 9" and after that the user should answer and here's how I can read the line in which he wrote the number?
As alluded to in the comments, we will use a coroutine called wait_for, that waits for events like message: https://discordpy.readthedocs.io/en/latest/api.html#discord.Client.wait_for
Because these are the same events we might hook into with bot.event, we will be able to process the returned message.
#bot.command()
async def bone(ctx):
await ctx.send("Select a number 1-9")
def my_check_func(message): #A checking function: When we return True, consider our message "passed".
if message.author == ctx.author: #Make sure we're only responding to the original invoker
return True
else:
return False
response = await bot.wait_for("message", check=my_check_func) #This waits for the first message event that passes the check function.
#This execution process now freezes until a message that passes that check is sent,
#but other bot interactions can still happen.
c = response.content #equal to message.content, because response IS a message
if c.isdigit() and 1 <= int(c) <= 9: #if our response is a number equal to 1, 9, or between those...
await ctx.send("You sent: {}".format(response.content))
else:
await ctx.send("That's not a number between 1 and 9!")
Real example interaction to prove it works:
Cook1
$bone
Tutbot
Select a number 1-9
Cook1
4
Tutbot
You sent: 4
Cook1
$bone
Tutbot
Select a number 1-9
Cook1
no
Tutbot
That's not a number between 1 and 9!