I want to know how to get a message content (specifically the embeds) from the message id? Just like you can get the member using a member id
on_raw_reaction_add() example:
#bot.event
async def on_raw_reaction_add(payload):
channel = bot.get_channel(payload.channel_id)
msg = await channel.fetch_message(payload.message_id)
embed = msg.embeds[0]
# do something you want to
Command example:
#bot.command()
async def getmsg(ctx, channel: discord.TextChannel, msgID: int):
msg = await channel.fetch_message(msgID)
await ctx.send(msg.embeds[0].description)
In the command I'm passing in channel and msgID, so a sample command execution would like !getmsg #general 112233445566778899 - the channel must be in the same server that you're executing the command in!
Then I'm getting the message object using the fetch_message() coroutine, which allows me to get a list of embeds in said message. I then choose the first, and only, embed by choosing position index 0.
After that, the bot then sends the description (or whatever attribute you'd like) of the embed.
References:
discord.TextChannel
TextChannel.fetch_message()
Message.embeds
discord.Embed - This is where you can find the different attributes of the embed
commands.command() - Command decorator
on_raw_reaction_add()
discord.RawReactionActionEvent - The payload returned from the reaction event
RawReactionActionEvent.message_id - The ID of the message which was reacted to
RawReactionActionEvent.channel_id - The text channel that the reaction was added in
Related
I'm trying to have a single embed that is going to be used as a list of some sort. I would like for an add_field to be added when a command is pushed to the bot. For example, I want to add to this current embed a new line with embed.add_field(name, value) that is given by a user when activated. How would I go about this? I've tried using message ID and message content but I don't think this is the best method. Thanks in advance.
#bot.command()
async def mangaEmbed(ctx):
embed = discord.Embed(
title = "Completed Self Prints",
#description ="test",
color = discord.Color.blue()
)
#embed.set_footer(text="Footer")
#embed.set_image(url="https://media.discordapp.net/attachments/138231921925816320/902586759773306900/unknown.png?width=810&height=375")
#embed.set_thumbnail(url="https://media.discordapp.net/attachments/138231921925816320/902586759773306900/unknown.png?width=810&height=375")
#embed.set_author(name= "Kyle G", icon_url="https://media.discordapp.net/attachments/138231921925816320/902586759773306900/unknown.png?width=810&height=375")
embed.add_field(name="Kokou no Hito by NggKGG", value="Volumes 1-17", inline=False)
embed.add_field(name="DrangonBall Z\nby NggKGG", value="Kokoue no 222o", inline=False)
embed.add_field(name="Homoculus", value="Volumes 1-24\nManmangaboy", inline=False)
await ctx.send(embed=embed)
The new problem is not being able to pass the discord.Message using the message id
#bot.command()
async def editembed(ctx, vols, *, title):
#just assuming you only want to get the first embed in the message
message = discord.Message(903060490035556383)
print(message)
embed = message.embeds[0]
embed.add_field(name=title, value=vols, inline=False)
await message.edit(embed=embed)
If I understood your question correctly, you want to edit an already sent embed from a different command right?
message has the attribute embeds, which will return a list of embeds the message has. You can use that to get the embed you want and edit it in a separate command.
Something like this should work:
#bot.command()
async def editembed(ctx, message: discord.Message):
#just assuming you only want to get the first embed in the message
embed = message.embeds[0]
embed.add_field(name="test", value="test")
await message.edit(embed=embed)
https://discordpy.readthedocs.io/en/master/api.html?highlight=message#discord.Message.embeds
Edit:
Regarding your second question:
You would have to get your message from somewhere, if it is always the same embed that you want to edit you can just define it inside your command using fetch_message. There are 2 ways to do this, either you would have to also get the channel the message you wanna edit is in, or just use ctx but then you can only use this command in the same channel as the embed that you want to edit.
Option 1 (recommended):
#bot.command()
async def editembed(ctx):
channel = bot.get_channel(1234567890)
message = await channel.fetch_message(1234567890)
Option 2:
#bot.command()
async def editembed(ctx):
message = await ctx.fetch_message(1234567890)
Please keep in mind that embeds can only have 25 fields in them though.
Outcome
To snipe messages sent in X channel instead of all the channels within the Discord guild. That is, it should only track message deletions in that one channel (identified by its ID), and only respond to the !snipe command in that same channel. The current code I have here snipes every message sent within the Discord guild.
Question
How can I snipe messages sent in X channel instead of the entire guild?
I mostly intend to run this bot in one guild. However, it would be nice if it could scale to multiple guilds if needed.
The code I have so far is below.
import discord
from discord.ext import commands
from tokens import token
client = commands.Bot(command_prefix="!", self_bot=False)
client.sniped_messages = {}
#client.event
async def on_ready():
print("Your bot is ready.")
#client.event
async def on_message_delete(message):
print(f'sniped message {message}')
client.sniped_messages[message.guild.id] = (
message.content, message.author, message.channel.name, message.created_at)
#client.command()
async def snipe(ctx):
try:
contents, author, channel_name, time = client.sniped_messages[ctx.guild.id]
except:
await ctx.channel.send("Couldn't find a message to snipe!")
return
embed = discord.Embed(description=contents,
color=discord.Color.purple(), timestamp=time)
embed.set_author(
name=f"{author.name}#{author.discriminator}", icon_url=author.avatar_url)
embed.set_footer(text=f"Deleted in : #{channel_name}")
await ctx.channel.send(embed=embed)
client.run(token, bot=True)
I'm going to suggest two slightly different solutions, because the code can be simpler if you're only running this bot on one guild. What's common to both is that you need to check in what channel messages are deleted, and in what channel the !snipe command is sent.
Single-Guild Version
If you're only monitoring/sniping one channel on one guild, then you can only ever have one deleted message to keep track of. Thus, you don't need a dictionary like in your posted code; you can just keep a single message or None.
You're already importing your token from a separate file, so you might as well put the channel ID (which is an int, unlike the bot token) there too for convenience. Note that, by convention, constants (variables you don't intend to change) are usually named in all caps in Python. tokens.py would look something like this:
TOKEN = 'string of characters here'
CHANNEL_ID = 123456789 # actually a 17- or 18-digit integer
And the bot itself:
import discord
from discord.ext import commands
from tokens import TOKEN, CHANNEL_ID
client = commands.Bot(command_prefix='!')
client.sniped_message = None
#client.event
async def on_ready():
print("Your bot is ready.")
#client.event
async def on_message_delete(message):
# Make sure it's in the watched channel, and not one of the bot's own
# messages.
if message.channel.id == CHANNEL_ID and message.author != client.user:
print(f'sniped message: {message}')
client.sniped_message = message
#client.command()
async def snipe(ctx):
# Only respond to the command in the watched channel.
if ctx.channel.id != CHANNEL_ID:
return
if client.sniped_message is None:
await ctx.channel.send("Couldn't find a message to snipe!")
return
message = client.sniped_message
embed = discord.Embed(
description=message.content,
color=discord.Color.purple(),
timestamp=message.created_at
)
embed.set_author(
name=f"{message.author.name}#{message.author.discriminator}",
icon_url=message.author.avatar_url
)
embed.set_footer(text=f"Deleted in: #{message.channel.name}")
await ctx.channel.send(embed=embed)
client.run(TOKEN)
Multi-Guild Version
If you're monitoring one channel each in multiple guilds, then you need to keep track of them separately. Handily, channel IDs are globally unique, not just within a single guild. So you can keep track of them by ID alone, without having to include the guild ID as well.
You could keep them in a list, but I recommend a set, because checking whether something is in a set or not is faster. Comments to help yourself remember which one is which are probably also a good idea.
TOKEN = 'string of characters here'
# Not a dictionary, even though it uses {}
CHANNEL_IDS = {
# That one guild
123456789,
# The other guild
987654322,
}
Then instead of checking against the single channel ID, you check if it's in the set of multiple IDs.
import discord
from discord.ext import commands
from tokens import TOKEN, CHANNEL_IDS
client = commands.Bot(command_prefix='!')
client.sniped_messages = {}
#client.event
async def on_ready():
print("Your bot is ready.")
#client.event
async def on_message_delete(message):
# Make sure it's in a watched channel, and not one of the bot's own
# messages.
if message.channel.id in CHANNEL_IDS and message.author != client.user:
print(f'sniped message: {message}')
client.sniped_messages[message.channel.id] = message
#client.command()
async def snipe(ctx):
# Only respond to the command in a watched channel.
if ctx.channel.id not in CHANNEL_IDS:
return
try:
message = client.sniped_messages[ctx.channel.id]
# See note below
except KeyError:
await ctx.channel.send("Couldn't find a message to snipe!")
return
embed = discord.Embed(
description=message.content,
color=discord.Color.purple(),
timestamp=message.created_at
)
embed.set_author(
name=f"{message.author.name}#{message.author.discriminator}",
icon_url=message.author.avatar_url
)
embed.set_footer(text=f"Deleted in: #{message.channel.name}")
await ctx.channel.send(embed=embed)
client.run(TOKEN)
Note: bare except clauses, like in your original code, are not generally a good idea. Here we only want to catch KeyError, which is what is raised if the requested key isn't in the dictionary.
You could, optionally, implement the same logic in a different way:
message = client.sniped_messages.get(ctx.channel.id)
if message is None:
await ctx.channel.send("Couldn't find a message to snipe!")
return
A dictionary's .get() method returns the corresponding item just like normal indexing. However, if there is no such key, instead of raising an exception, it returns a default value (which is None if you don't specify one in the call to get).
If you're using Python 3.8+, the first two lines could also be combined using an assignment expression (using the "walrus operator"), which assigns and checks all at once:
if (message := client.sniped_messages.get(ctx.channel.id)) is None:
await ctx.channel.send("Couldn't find a message to snipe!")
return
These alternative options are mentioned for completeness; all of these ways of doing it are perfectly fine.
I'm making a discord bot and I want it to send a message whenever it joins a new guild.
However, I only want it to send the message in the #general channel of the guild it joins:
#client.event
async def on_guild_join(guild):
chans = guild.text_channels
for channel in chans:
if channel.name == 'general':
await channel.send('hi')
break
The problem that I have noticed is that guild.text_channels only returns the name of the very first channel of the server. I want to iterate through all channels and finally send message only on the #general channel.
What's the workaround for it?
There are a couple ways you can do this.
Here's an example using utils.get():
import discord # To access utils.get
#client.event
async def on_guild_join(guild):
channel = discord.utils.get(guild.text_channels, name="general")
await channel.send("Hi!")
Or if the guild has a system_channel set up, you can send a message there:
#client.event
async def on_guild_join(guild):
await guild.system_channel.send("Hi!")
You can create checks for both of these, but bear in mind that some servers might not have a text channel called general or a system channel set up, so you may receive some attribute errors complaining about NoneType not having a .send() attribute.
These errors can be avoided with either an error handler or a try/except.
References:
Guild.system_channel
utils.get()
I'm wondering how to get a message by its message id. I have tried discord.fetch_message(id) and discord.get_message(id), but both raise:
Command raised an exception: AttributeError: module 'discord' has no attribute 'fetch_message'/'get_message'
When getting a message, you're going to need an abc.Messageable object - essentially an object where you can send a message in, for example a text channel, a DM etc.
Example:
#bot.command()
async def getmsg(ctx, msgID: int): # yes, you can do msg: discord.Message
# but for the purposes of this, i'm using an int
msg = await ctx.fetch_message(msgID) # you now have the message object from the id
# ctx.fetch_message gets it from the channel
# the command was executed in
###################################################
#bot.command()
async def getmsg(ctx, channel: discord.TextChannel, member: discord.Member):
msg = discord.utils.get(await channel.history(limit=100).flatten(), author=member)
# this gets the most recent message from a specified member in the past 100 messages
# in a certain text channel - just an idea of how to use its versatility
References:
abc.Messageable
TextChannel.history()
TextChannel.fetch_message()
Context.fetch_message()
i want send messages to all channel that bot has joined
def check(cnt):
while 1:
if hash_origin != str(subprocess.check_output(["md5sum",filename])[:-len(filename)-3])[2:-1] :
print("file destroyed alert!")
alert = 1
sleep(0.5)
i want send message to all discord channel that bot joined when specific file's hash result is different from original.
i know how to send respond message to channel
#client.event
async def on_message(message):
using this code, right?
but i want to send message to all of the channel that bot has joined when some event has happens.
In order to send a message to every channel that the bot is in, you must do:
#client.event
async def foo(bar): # change this to the event in which you wish to call it from
for guild in client.guilds:
for channel in guild.channels:
await channel.send(messagedata) # change messagedata to whatever it is you want to send.
#client.event
async def foo(bar): # change this to the event in which you wish to call it from
for guild in client.guilds:
for channel in guild.channels:
await channel.send(messagedata) # change messagedata to whatever it is you want to send.
when using this code directly, error has occurred
AttributeError: 'CategoryChannel' object has no attribute 'send'
because "guild.channels" object have list of "all channels"(including text,voice,etc..) that bot has joined.
so, if i want send text message to channel, using "guild.text_channels" instead.
anyway, thanks to Tylerr !!