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.
Related
I have an issue and I can't understand how to do it.
Basically I have a bot that send a message if he detect a word.
Now I wanted to add the possibility to modify that message based on who add a reaction (the bot add 3 reactions at each message he send).
So far, it's working, my bot respond and add the 3 reactions, but now I don't understant how to make my bot edit his message (I don't even manage to make the bot say who click on a reaction) and also remove the reaction.
I've read the docs, read a lot of forum and I can't find a way to make it work...
Please help =)
blue = '<:blue:1033370324135333898>'
red = '<:red:1033370379663704148>'
yellow = '<:yellow:1033370423078952970>'
#client.event
async def on_message(message):
if 'ping' in message.content.lower():
if message.channel.id == 1032275628470308895:
channel = client.get_channel(1032275628470308895)
react = await channel.send('pong!')
await react.add_reaction(blue)
await react.add_reaction(red)
await react.add_reaction(yellow)
#client.event
async def on_raw_reaction_add(payload):
# and then I don't know what to do, everything I've tried doesn't work
# I wanted to modify the message to (for example "{user} said blue pong!" and remove the reaction the user just use, and just keep the 3 from the bot)
# And then if another user use the yellow reaction, the bot modify his message to for example "{user] said yellow pong!"
# That could be great if the message was edited but keeping what was said before (in that case "pong! \n{user} said blue pong! \n{user} said yellow pong!")
# And last thing, if possible I want the bot to remove all reactions including his after 30mins
https://discordpy.readthedocs.io/en/stable/api.html?highlight=on_raw_reaction_add#discord.RawReactionActionEvent
The event on_raw_reaction_add returns a payload which is a RawReactionActionEvent class.
From this payload you can get the message_id which can be used to fetch the message (using for example discord.utils) and then you can edit the message using the class method edit
https://discordpy.readthedocs.io/en/latest/api.html#discord.Message.remove_reaction
The discord message object has a method called remove reaction that can remove reactions when you pass it an emoji and member object.
https://discordpy.readthedocs.io/en/latest/api.html#discord.Reaction.remove
You can also use the class method remove from the reaction object itself
These resources should be enough for you to code a solution to your problem :)
Alright I think I'm done!
Now my bot answer to a specific word, and add reactions to his message.
If someone react to it, my bot remove the user reaction, and edit his own message, keep his previous text and add something else at the end (in that case):
"Previous message"
"user" said it's blue pong
And then after some times, the bot remove all the reactions
Here the code if someone want to do that someday..
blue = '<:blue:1033370324135333898>'
red = '<:red:1033370379663704148>'
yellow = '<:yellow:1033370423078952970>'
#client.event
async def on_message(message):
if 'ping' in message.content.lower():
if message.channel.id == 1032275628470308895:
channel = client.get_channel(1032275628470308895)
react = await channel.send('pong!')
await react.add_reaction(blue)
await react.add_reaction(red)
await react.add_reaction(yellow)
await asyncio.sleep(10)
await react.clear_reactions()
#client.event
async def on_raw_reaction_add(payload):
channel = client.get_channel(payload.channel_id)
message = await channel.fetch_message(payload.message_id)
user = client.get_user(payload.user_id)
if str(payload.emoji) == blue:
if str(payload.user_id) == "bot_ID": # bot_ID here, don't want the bot trigered by his own reaction
return
await message.remove_reaction(payload.emoji, user)
await message.edit(content=f"{message.content} \n{user.name} said it's a blue pong !")
Thanks #hexxx for your help, it's was my first time programming something ^^
I'm designing a Discord bot that can basically be used throughout your server for different useful tasks, like getting server statistics, DMing a user, and (the topic of this question) making a poll. The code I have is pretty long, so I'm only going to include the imports, important variables, actual code where I'm having a problem, and the end where I run my bot:
from profanity import profanity
import os, random, discord, asyncio, praw
from discord.utils import get
from discord.ext import commands
from dotenv import load_dotenv
from discord.ext.commands import Bot
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
intents = discord.Intents().all()
intents.members = True
bot = commands.Bot(command_prefix="!!", case_insensitive=True, intents=intents)
devmode = False
#bot.command(name='makePoll', help="Makes a new poll that other users can vote for. Bob Bot may be erroring out because it does not have the highest permissions in the server, so you will need to give it roles to kick users.")
async def makeapoll(ctx,*, message : str):
embed=discord.Embed(title=random.choice(pollNames), description=message, color=0xfcd602)
embed.set_author(name=ctx.author.display_name + " (you can end this poll by reacting with this: 🔴)", url=random.choice(aintsupposedtobeheresunnyboy), icon_url=ctx.author.avatar_url)
mymsg = await ctx.send(embed=embed)
await mymsg.add_reaction('✅')
await mymsg.add_reaction('❌')
def check(reaction, user):
return user == ctx.message.author and str(reaction.emoji) == '🔴'
try:
reaction, user = await bot.wait_for('reaction_add', timeout=60.0, check=check)
except asyncio.TimeoutError:
await ctx.send("Some kind of error occured. Sowwy.")
deletionEmbed = discord.Embed(title="This ends now!", value="It's over, Anakin... I have the high ground", color=0xfcd602)
deletionEmbed.add_field(name="The poll ended!", value=f"The poll's author {ctx.author.display_name} just declared that the poll ended. RIP poll :(\n\nFor those of you who missed the question, here it is: *{message}*\n\n{ctx.author.display_name}, the results of the poll should have been DMed to you right about now. Happy statistical advancement!", inline=False)
await mymsg.edit(embed=deletionEmbed)
bot.run(TOKEN)
Basically, what I want to do here is that when the author of a poll reacts with a red dot 🔴 (which is used to officially stop the poll), my bot direct-messages the author of the poll with the most recent information about who reacted with any reaction. For example, if two people reacted with a checkmark and three people with a cross, I want the bot to message the poll's author with those stats: 2 checks and 3 crosses. If another user responded with a check after the poll, I don't want to reflect those changes. I tried using the add_raw_reaction function, but that didn't seem to work either. How could I fix this issue?
You can get the poll stats and send a DM with those numbers after your wait_for completes (when the author reacts or it times out). To do this, you first need to re-fetch the message so that you can obtain the updated reaction data since the original Message object does not update by itself.
mymsg = await mymsg.channel.fetch_message(mymsg.id)
Afterwards, you can get the Reaction objects from the message and compile a dictionary mapping the emojis to their count. Instead of just iterating through Message.reactions since it could include extraneous emojis, it's a good idea to have a list of just the emoji that's part of the poll, ✅ and ❌, and then only get the reactions for those using discord.utils.get.
choices = ['✅', '❌']
counts = {}
for emoji in choices:
reaction = discord.utils.get(mymsg.reactions, emoji=emoji)
counts[emoji] = reaction.count - 1 # minus 1 to exclude the bot
Now with the numbers stored in count, you can choose however you want to format your stats and then call .send() on the author to send the DM.
results = ...
await ctx.author.send(results)
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)