Discord buttons all triggering at once. Discord.py - discord.py

I am attempting to create moderation on my discord bot that flags words as inappropriate and then allows the moderation team to easily ban/timeout the user without having to be present during the event. All of the moderation works, but only if I run the code once. If there are multiple button sets, they all trigger at once when one of them is pressed. For example, if I have press the ban button on one message, it will ban all users that have spoken inappropriately, rather than just banning the one person.
How do I fix this?
EDIT: I managed to fix the problem that all my button are pressed at once, and have run into a new button. Now, when one button is pressed, the rest of them fail if pressed. It's clear the code is resolving all instances of button_cxt, but I'm not sure how to prevent that or work around it.
Updated Code:
#bot.event
async def on_profanity(message, word):
try:
guildID = message.guild.id
guild = bot.get_guild(guildID)
channel = discord.utils.get(guild.text_channels, name="moderation-logs")
channel_id = channel.id
except:
return None
channel = bot.get_channel(channel_id)
embed = discord.Embed(title="Profanity Alert!", description=f"{message.author.name} just said ||{message.content}||. The bad word was: ||{word}||", color=discord.Color.blurple()) # Let's make an embed!
await channel.send(embed=embed)
buttons = [
manage_components.create_button(
style=ButtonStyle.red,
label="Ban!",
custom_id="ban"
),
manage_components.create_button(
style=ButtonStyle.gray,
label="Give them a Warning",
custom_id="warning"
),
manage_components.create_button(
style=ButtonStyle.green,
label="Nope!",
custom_id="noBan"
),
]
ActionRow = manage_components.create_actionrow(*buttons)
if "warning" in [y.name.lower() for y in message.author.roles]:
botMessage = await channel.send("They have already been warned. Do you wish to ban them?", components=[ActionRow])
else:
botMessage = await channel.send("Do you wish to ban them?", components=[ActionRow])
button_ctx: ComponentContext = await manage_components.wait_for_component(bot, components=ActionRow)
origin_id = button_ctx.origin_message.id
if (botMessage.id == origin_id):
await botMessage.delete()
if button_ctx.custom_id == "ban":
await channel.send("The deed has been done.")
bot.dispatch('ban', message, guild)
elif button_ctx.custom_id == "warning":
await channel.send("The warning has been sent!")
bot.dispatch('warning', message, guild, channel)
elif button_ctx.custom_id == "noBan":
await channel.send("No action taken")

so it took me a long time, and some thinking, but I did finally solve the problem. The issue was with these two sections:
button_ctx: ComponentContext = await manage_components.wait_for_component(bot, components=ActionRow)
The button context is listening for any buttons with the components ActionRow, which is all of the components for all of my buttons. Therefore, all buttons trigger their instance of the function at once. My small check means that only the one pressed does it's action, but it didn't stop the rest of the buttons from triggering that event listener.
So, the fix I thought of is to recursively restart all of the listeners if they aren't pressed. Do the action of the button pressed, and restart the function for the rest. Therefore, my now functioning code looks like this:
#bot.event
async def on_profanity(message, word):
try:
guildID = message.guild.id
guild = bot.get_guild(guildID)
channel = discord.utils.get(guild.text_channels, name="moderation-logs")
channel_id = channel.id
except:
return None
channel = bot.get_channel(channel_id)
embed = discord.Embed(title="Profanity Alert!", description=f"{message.author.name} just said ||{message.content}||. The bad word was: ||{word}||", color=discord.Color.blurple())
await channel.send(embed=embed)
buttons = [
manage_components.create_button(
style=ButtonStyle.red,
label="Ban!",
custom_id="ban"
),
manage_components.create_button(
style=ButtonStyle.gray,
label="Give them a Warning",
custom_id="warning"
),
manage_components.create_button(
style=ButtonStyle.green,
label="Nope!",
custom_id="noBan"
),
]
ActionRow = manage_components.create_actionrow(*buttons)
if "warning" in [y.name.lower() for y in message.author.roles]:
botMessage = await channel.send("They have already been warned. Do you wish to ban them?", components=[ActionRow])
else:
botMessage = await channel.send("Do you wish to ban them?", components=[ActionRow])
await modButtons(guild, message, channel, botMessage, ActionRow)
async def modButtons(guild, message, channel, botMessage, ActionRow):
button_ctx: ComponentContext = await manage_components.wait_for_component(bot, components=ActionRow)
origin_id = button_ctx.origin_message.id
if (botMessage.id == origin_id):
await botMessage.delete()
if button_ctx.custom_id == "ban":
#await channel.send("The deed has been done.")
bot.dispatch('ban', message, guild)
elif button_ctx.custom_id == "warning":
#await channel.send("The warning has been sent!")
bot.dispatch('warning', message, guild, channel)
elif button_ctx.custom_id == "noBan":
await channel.send(f"No action taken for {message.author.name}")
else:
await modButtons(guild, message, channel, botMessage, ActionRow) #recursion HERE!

Related

How to make a verify ban command?

Right, I want to make it so when I do "/ban #user reason" the bot responds with an embed saying are you sure you want to ban this user and reacts to its message with a tick, and awaits for the reaction off of the person who made the ban.
Try this:
#bot.command(name="ban")
async def ban(ctx, member, reason=""):
if not ctx.message.mentions:
await ctx.channel.send("You must mention a user to use this command")
embed = discord.Embed(
title="Confirm ban",
description=f"Are you sure you want to ban {member.mention}",
color=0xff0000
)
message = await ctx.channel.send(embed=embed)
await message.add_reaction(u"\U0001F44D")
def check(pay):
pay.member == member and pay.message_id == ctx.message.id
await bot.wait_for("raw_reaction_add", check=check)
await member.ban(reason=reason)

Using CTX in on_reaction_add event -- Discord.py

async def on_reaction_add(reaction, user):
emoji = reaction.emoji
if emoji == "💌":
await user.channel.send("HI")
I got problem here with user.
I want to use here with ctx like ctx.channel.send.
but also it occured error how to use ctx in here?
Instead of using the on_reaction_add event, it's better in this case to use a wait_for command event. This would mean the event can only be triggered once and only when the command was invoked. However with your current event, this allows anyone to react to a message with that emoji and the bot would respond.
By using client.wait_for("reaction_add"), this would allow you to control when a user can react to the emoji. You can also add checks, this means only the user would be able to use the reactions on the message the bot sends. Other parameters can be passed, but it's up to you how you want to style it.
In the example below shows, the user can invoke the command, then is asked to react. The bot already adds these reactions, so the user would only need to react. The wait_for attribute would wait for the user to either react with the specified emojis and your command would send a message.
#client.command()
async def react(ctx):
message = await ctx.send('React to this message!')
mail = '💌'
post = '📮'
await message.add_reaction(mail)
await message.add_reaction(post)
def check(reaction, user):
return user == ctx.author and str(
reaction.emoji) in [mail, post]
member = ctx.author
while True:
try:
reaction, user = await client.wait_for("reaction_add", timeout=10.0, check=check)
if str(reaction.emoji) == mail:
await ctx.send('Hi you just received')
if str(reaction.emoji) == post:
await ctx.send('Hi you just posted...')
You need to use reaction.message.channel.send
async def on_reaction_add(reaction, user):
emoji = reaction.emoji
if str(emoji) == "💌": await reaction.message.channel.send("HI")

How to add the channel in which the message was edited?

I just want to add to the embed the channel in which the message was edited. So far no luck in finding a solution.
#Cog.listener()
async def on_message_edit(self, before, after):
if not after.author.bot:
if before.content != after.content:
embed = Embed(title="Message edit",
description=f"Edit by {after.author.display_name}.",
colour=after.author.colour,
timestamp=datetime.utcnow())
fields = [("Before", before.content, False),
("After", after.content, False)]
for name, value, inline in fields:
embed.add_field(name=name, value=value, inline=inline)
await self.log_channel.send(embed=embed)
If a message was edited, the channel is going to be the same as where the message was originally sent, use the Message.channel attribute to get the channel.
#Cog.listener()
async def on_message_edit(self, before, after):
channel = before.channel # or after.channel
await channel.sent('whatever')
Here is the new code for anyone that might need it.
#Cog.listener()
async def on_message_edit(self, before, after):
if not after.author.bot:
if before.content != after.content:
channel = before.channel
embed = Embed(title="Message edit",
description=f"Edit by {after.author.display_name}.",
colour=after.author.colour,
timestamp=datetime.utcnow())
fields = [("Before", before.content, False),
("After", after.content, False),
("Channel", channel.mention, False),]
for name, value, inline in fields:
embed.add_field(name=name, value=value, inline=inline)
await self.log_channel.send(embed=embed)

Is there a way to get a custom status message from discord.py?

I have what I need to create a user's profile card
I would like to know how to get the user's specified message (text) from discord.py.
Use member.activities to get the activities and check if any is CustomActivity
#bot.command()
async def status(ctx, m: discord.Member):
acts = m.activities
act = [i for i in acts if isinstance(i, discord.CustomActivity)]
if act:
act = act[0]
else:
return await ctx.send("No Custom Activity")
text = act.name
if text:
await ctx.send(f'Custom Status of {m.name}: **{text}**')
else:
return await ctx.send("No text in custom status")

How to make a discord.py reaction role code

I'm new to discord bot making, and I'd like to have the bot add a reaction to my message that, once pressed, allows the user reacting to pick up a role. What would be the code to allow me to do this? Thank you.
This is a very well written and formatted question! In order for the bot to detect that the reaction is on a specific message, you can do many ways. The easiest way would be to be by ID, so I'm just going to do this with a simple command.
messageIDs = []
#client.event
async def on_raw_reaction_add(payload):
global messageIDs
for messageID in messageIDs:
if messageID == payload.message_id:
user = payload.member
role = "roleName"
await user.add_roles(discord.utils.get(user.guild.roles, name = role))
#client.command()
async def addMessage(ctx, messageID):
global messageIDs
emoji = "👍"
channel = ctx.message.channel
try:
msg = await channel.fetch_message(messageID)
except:
await ctx.send("Invalid Message ID!")
return
await msg.add_reaction(emoji)
messageIDs.append(messageID)

Resources