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)
Related
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!
I'm a bit lost in what to do with this one, I have tried a couple of things but i can only get it to detect nickname changes within the server, which is not what i want
This is what I have:
#client.event
async def on_member_update(before, after):
if before.display_name != after.display_name:
embed = discord.Embed(colour=discord.Colour.from_rgb(162, 28, 29), title=f"Changed Username")
embed.add_field(name='Current Name', value=before.display_name)
embed.add_field(name='Old Name', value=after.display_name)
channel = client.get_channel(----------)
await channel.send(embed=embed)
yes, there is an event called on_user_update here is the example:
#client.event
async def on_user_update(before,after):
if before.name != after.name:
I am using code that I found here.
But, I'm getting an error that the client object doesn't have the attribute send_message. I have tried message.channel.send but that failed as well.
#client.event
async def on_ready():
Channel = client.get_channel('YOUR_CHANNEL_ID')
Text= "YOUR_MESSAGE_HERE"
Moji = await client.send_message(Channel, Text)
await client.add_reaction(Moji, emoji='🏃')
#client.event
async def on_reaction_add(reaction, user):
Channel = client.get_channel('YOUR_CHANNEL_ID')
if reaction.message.channel.id != Channel:
return
if reaction.emoji == "🏃":
Role = discord.utils.get(user.server.roles, name="YOUR_ROLE_NAME_HERE")
await client.add_roles(user, Role)
One of the reasons why your code might not be working is because you may have your channel id stored in a string. Your code shows:
Channel = client.get_channel('YOUR CHANNEL ID')
The code above shows that your channel id is stored in a string, which it should not be. With the code above, your actual code may look like this:
Channel = client.get_channel('1234567890')
Instead you should have:
Channel = client.get_channel(1234567890)
I tried to write a code that will remove a role from a member when a certain reaction is added. My code works fine until the end, where the role is removed from the member, where an "Attribute error" pops up; "AttributeError: 'NoneType' object has no attribute 'remove_roles'"
Here's my code:
#client.event
async def on_raw_reaction_remove(payload):
await client.wait_until_ready()
guild = await client.fetch_guild(payload.guild_id)
member = get(guild.members, id=payload.user_id)
if payload.message_id == id:
if payload.emoji.name == "a":
role = discord.utils.get(guild.roles, name = "a")
elif payload.emoji.name == "b":
role = discord.utils.get(guild.roles, name = "b")
await member.remove_roles(role)
My guess is that i'm defining member the wrong way, but I have no idea how to fix it
In this situation client.get_user() works better.
Here is the fixed code
#client.event
async def on_raw_reaction_remove(payload):
await client.wait_until_ready()
guild = await client.fetch_guild(payload.guild_id)
member = client.get_user(id=payload.user_id)
if payload.message_id == id:
if payload.emoji.name == "a":
role = discord.utils.get(guild.roles, name = "a")
elif payload.emoji.name == "b":
role = discord.utils.get(guild.roles, name = "b")
await member.remove_roles(role)
Hope this helped!
The main reason behind this is payload.member is None when using on_raw_reaction_remove.
This is actually because discord does not send this data, so discord.py has it as None.
Source-https://github.com/Rapptz/discord.py/issues/5871
To work around this, you get the guild object in question, then grab the member object from the guild.
The payload does not have the guild object either, so we have to get that as well.
What I did is I actually created the member object and tacked it onto the payload manually in the case of on_raw_reaction_remove.
This way both on_raw_reaction_add and on_raw_reaction_remove have the same payload for my final processing.
async def on_raw_reaction_remove(self, payload):
guild = self.get_guild(payload.guild_id) #Self here is Client/Bot
member = guild.get_member(payload.user_id)
payload.member = member
await reaction_role(payload, remove=True)
Furthermore, this requires the privileged members intent. To enable it, log back into the developer portal and look for the privileged intents > member intent, then tick the slider to on.
intent = discord.Intents.default()
intent.members = True
The intent needs to be passed to the client, or bot wrapper if using the bot extension.
commands.Bot.__init__(self, <...>, intents=intent)
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)