I've been doing a lot of work creating discord bots with the discord api through javascript with discord.js.
I am trying to create my first discord bot with python using the discord api through discord.py and with requests through requests.py.
My goal is to check status code on a site, and when a message is sent that contains "status code," it will reply with the site's status code in an embed.
Here is my code to do so:
import discord
import requests
r = requests.get('redactedurl')
test = r.status_code
class MyClient(discord.Client):
async def on_ready(self):
print('Logged on as {0}!'.format(self.user))
async def on_message(self, message):
if (message.channel.id == redacted):
if "status code" in message.content:
print('Message from {0.author}: {0.content}'.format(message))
embed = discord.Embed(color=0x00ff00)
embed.title = "test"
embed.description = '**Status Code:** {r.status_code}'
await message.channel.send(embed=embed)
client = MyClient()
client.run('redacted')
The following is a list of questions I hope anybody can answer to help me out :)
As you see here: https://gyazo.com/f6ae7082486cade72389534a05655fec, this just sends "{r.status_code}" in the embed, instead of the actual status code, what am I doing wrong?
What does it mean when i see 0 in the curly brackets. For example, can somebody explain "('Logged on as {0}!'.format(self.user))" to me? Since I'm new to python and discord.py, I am confused about this whole line. I know what the outcome is, but forgive my ignorance, is it all necessary?
In "send(embed=embed)", why can't i just put send(embed)?
Finally, is there anything else that i can do to improve the code?
Thank you so much if you're able to help!
In the line you set the embed description, outputting r.status_code as a string instead of the value it contains. Try embed.description = '**Status Code:** {0}'.format(r.status_code)
The 0 resembles the index of the value that is supposed to be there. For example, '{1}'.format(10, 20) would print out the value at index 1 which in this case is 20.
When you use send(embed) then the bot will end up sending the embed in string form which will look very off, if you try sending it you will see what I mean. In this case, we have to specify which argument we are assigning the value to. This function accepts kwargs which are Keyworded arguments, in this case, embed is one of the kwargs in this send() function. This function accepts other kwargs as well such as content, tts, delete_after, etc. It is all documented.
You can simplify creating the embed by passing in the kwargs, for example: discord.Embed(title='whatever title', color='whatever color')
The discord.Embed() can support more arguments if you look at the documentation.
Here is a link to the documentation: https://discordpy.readthedocs.io/en/latest/index.html
If you search for TextChannel, and look for the send() function, you can find more arguments supported, as well as for discord.Embed().
Okay working down your list of questions:
You're not formatting {r.status_code} but just sending it as a string which is why it shows up as just that. To fix it, all you have to do is add 1 "f".
embed.description = f'**Status Code:** {r.status_code}'
or, if you want to use a consistent way of formatting your strings:
embed.description = '**Status Code:** {0}'.format(r.status_code)
In Python, the curly brackets '{}' in a string can be used for formatting. This can be done in multiple ways including how you're doing it in your code. str.format() can take multiple arguments if you want to format different values in your string. The 0 is just an index for which argument you want to use.
I'm not too familiar with the discord library but having taken a quick look at the documentation it would seem that if you did that it would take your embed variable and pass it into send as the content variable. Thereby sending it as a normal message instead of embedding it.
I personally like f-strings more for formatting because it makes your code more easily readable. I can't really comment on your use of the discord library but besides that your code looks fine! Purely for aesthetics/readability I would put an empty line between your imports and defining your variables, and between your variables and your class.
Here is an example!
import discord
client = commands.Bot(command_prefix = '~')
#client.command()
async def embed(ctx):
embed=discord.Embed(title="Hello!",description="Im a embed text!")
await ctx.send(embed=embed)
client.run("TOKEN")
import discord
from discord.ext import commands
client = commands.Bot(command_prefix='>')
#client.event
async def on_ready():
print("Log : "+str(client.user))
#client.command()
async def embed(ctx):
em = discord.Embed(color=discord.Color.from_rgb(0,0,255),title="Your title",description="Your description")
await ctx.send(embed=em)
client.run("token")
Related
I'm trying to run a very simple program: it should check for a string in a link, and in case of a true or false result it should send a different message to a discord channel. The part that verifies the link works, the problem is the part that sends the message, I tried to do it again in different ways but I can't get it to work, this is the code:
#client.command()
async def check_if_live():
global live
contents = requests.get('https://www.twitch.tv/im_marcotv').content.decode('utf-8')
channel = client.get_channel(1005529902528860222)
if 'isLiveBroadcast' in contents:
print('live')
live = True
await channel.send('live')
else:
print('not live')
live = False
await channel.send('not live')
In other parts of the code where I send a message in response to a command using await message.channel.send("message")everything works as expected.
I have done a lot of research but can't find anything useful.
*edit:
With the code above the error is 'NoneType' object has no attribute 'send' on the line await channel.send('not live'), if i try to do as suggested by Artie Vandelay inserting await client.fetch_channel(1005529902528860222) before channel = client.get_channel(1005529902528860222) on the inserted line I have an error 'NoneType' object has no attribute 'request'.
Since I think that at this point it may have a certain value, I also insert the code sections about the client variable.
client = discord.Client()
load_dotenv()
TOKEN = os.getenv('TOKEN')
#all bot code
client.run(TOKEN)
I also tried this:
bot = commands.Bot(command_prefix='!')
load_dotenv()
TOKEN = os.getenv('TOKEN')
#bot.command()
#all bot code
bot.run(TOKEN)
Both methods return errors
Double check if your channel id is correct.
Also try using
.text
instead of
.content.decode('utf-8')
if it still does not work, please send your error message, so we can see what is wrong.
I answer my question because I found a solution, instead of using #bot.command I used #bot.event async def on_ready(), and now it works, I hope this answer can be useful to someone else who has this same problem.
First of all I am sorry if I'm doing something wrong. This is my first question.
I am currently trying to create a Discord Bot using Python.
Edit: Though the answer of one person helped me a lot, my question remains, because I still have the "await coro" exception and another one was thrown after I corrected the old mistake. I've updated the code and the exceptions. Thanks for your help!
When I'm trying to send an embed when the bot joins the server, I get two exceptions. Since I don't have 50 servers, I replaced the on_member_join(self) with a simple function call when something is written in a channel:
File "...\Python\Python39\lib\site-packages\discord\client.py"
await coro(*args, **kwargs)
TypeError: on_message() missing 1 required positional argument: 'ctx'
Though I watched videos and searched on stackoverflow, I still don't understand ctx completely. That's probably the reason I'm making this mistake. If you could help me correct the code or even explain what ctx is (is it like "this" in java?), that'd be great!
Here's the code of the two functions trying to send an embed:
import discord
from discord.utils import get
from discord.ext import commands
Bot_prefix = "<" #Later used before every command
class MyClient(discord.Client):
async def Joining_Server(ctx, self):
#Get channel by name:
channel = get(ctx.guild.text_channels, name="Channel Name")
#Get channel by ID:
channels_Ids = get(ctx.guild.text_channels, id=discord.channel_id)
embed = discord.Embed(title="Thanks for adding me!", description="Try")
fields = [("Prefix", "My prefix is <. Just write it in front of every command!", True),
("Changing prefix", "Wanna change my prefix? Just write \"<change_prefix\" and the prefix you want, such as: \"<change_prefix !\"", True),
("Commands", "For a list of the most important commands type \"<help\"", False),
("Help", "For more help, type \"<help_All\" or visit:", True)]
for channels in self.channel_Ids:
if(commands.has_permissions(write=True)):
channel_verified = channels.id
await ctx.channel_verified.send(embed)
async def on_message(message, self, ctx):
if message.author == client.user:
return
if message.content == "test":
await MyClient.Joining_Server(ctx, self)
Thank you for helping me! Again: I'm sorry if I'm doing something wrong, it's my first question. Please ask if you need something. Feedback would also be very helpful.
I think you simply want to compare the content of the message to the "test" string
if message.author == client.user:
return
if message.content == "test":
await MyClient.Joining_Server()
So I recently created a discord bot with various meme commands and moderating commands. I am relatively new to python but I understand the gist of it. I have already created a command that lets me(only me) DM a user through the bot. I now want to try and make the bot be able to read the messages that are sent back to it and send them to me, whether its printed in shell or sent to a specific channel/me I don't care, I just want to be able to see what is sent to it.
I did some reading around and found this, and from that I collected this:
#bot.event
async def on_message(message):
channel = bot.get_channel('channel ID')
if message.server is None and message.author != bot.user:
await bot.send_message(channel, message.content)
await bot.process_commands(message)
This alone has not worked for me, I get an error saying that "AttributeError: 'Message' object has no attribute 'server'" when I DM'ed the bot. I was told that discord.py rewrite does not use 'server', but rather 'guild'. So I changed it to say message.guild. From there it gave me the error "AttributeError: 'Bot' object has no attribute 'send_message'", and that's about as far as I got there. I've tinkered with it and changed things here and there and made some slight progress...I think. My new code is:
#bot.event
async def on_message(ctx, message):
channel = bot.get_channel('channel ID')
if message.guild is None and message.author != bot.user:
await ctx.send(channel, message.content)
await bot.process_commands(message)
This gives me the error "TypeError: on_message() missing 1 required positional argument: 'message'".
That is as far as I've gotten now. Any help would be appreciated, as I said, I'm still somewhat new to python, I've only started using it about 2 months ago.
You're using the ctx parameter, which is unique to commands.
'ctx' is commands.Context, which provides information about commands that are executed.
Since this isn't a command, but rather an event, you shouldn't provide it.
Untested, but this should do the trick:
(I made it so it ignores messages from ALL bots, to avoid spam)
for printing messages to console
#bot.event
async def on_message(message: discord.Message):
if message.guild is None and not message.author.bot:
print(message.content)
await bot.process_commands(message)
for dumping message contents to a channel
msg_dump_channel = 1234
#bot.event
async def on_message(message: discord.Message):
channel = bot.get_channel(msg_dump_channel)
if message.guild is None and not message.author.bot:
# if the channel is public at all, make sure to sanitize this first
await channel.send(message.content)
await bot.process_commands(message)
Your first code is using ancient, outdated and unsupported dpy version.
You mostly updated it correctly in the second code except for few mistakes like the on_message event.
It is not working because it only takes one argument message, there is no ctx in that event.
So the event is passing one argument but your function takes 2 thus the error. The correct code would be:
async def on_message(message):
But now you'll be confused what to do with this line you wrote:
await ctx.send(channel, message.content)
Even if ctx existed this wouldn't work because send doesn't take destination anymore, instead you call send on destination destination.send("Hello") so translated to your code if the channel is where you want to send it would be await channel.send(message.content)
Also one final note I noticed that you used string here:
bot.get_channel('channel ID') this could be you just giving example but just remember that IDs are ints, if you pass string it will never find that channel.
For example this is invalid:
bot.get_channel('123456789111315178')
And this would be valid: bot.get_channel(123456789111315178)
Based on that error, I'd say you're likely calling on_message() at some point and not giving the message parameter. Look through your code and see if that's maybe the case.
Alternatively, if it's some other code you haven't written that's calling your function, then it might only be calling on_message() with one argument, in which case the ctx argument might be incompatible with that function.
I need to implement some of the feature and one of the feature is implementing polls type feature. Can't use public discord bots due to some policies so we have to implement something on my own. Did some research yesterday and was able to make basic bot using python3 and commands api from discord.ext. Now what i need to figure out is:
Read reactions added by a user to a message?
Create a message with reactions (like bots which create reaction polls?)
Pin a message?
I believe from ctx i can get user tags (admin etc). Is there a better way to do so?
Couldn't find anything helpful on Commands reference page or probably i am looking at wrong documentation. any help would be appreciated.
thanks
Updated: Thanks guys. now i am stuck at how to add emoji, here is my code
poll_emojis = {0: ':zero:', 1: ':one:', 2: ':two:', 3: ':three:', 4: ':four:'}
#client.event
async def on_message(message):
if message.author == client.user:
return
if message.content.startswith('$create_poll'):
poll_content = message.content.split('"')
poll_text = poll_content[1]
poll_options = []
poll_option_text = ''
count = 0
for poll_option in poll_content[2:]:
if poll_option.strip() != '':
poll_options.append(poll_option)
poll_option_text += '{0}: {1}\t'.format(poll_emojis[count], poll_option)
count += 1
posted_message = await message.channel.send('**{0}**\n{1}'.format(poll_text, poll_option_text))
count = 0
for poll_option in poll_options:
await posted_message.add_reaction(Emoji(poll_emojis[count]))
count += 1
As an aside, given you're starting this project, and are already using the rewrite documentation, make sure you're using the rewrite version. There are some questions on here about how to make sure, and how to get it if you're not, but it's better documented and easier to use. My answers below assume you are using discord.py-rewrite
Message.reactions is a list of Reactions. You could get a mapping of reactions to their counts with
{react.emoji: react.count for react in message.reactions}
You could react to the message immediately after posting it:
#bot.command()
async def poll(ctx, *, text):
message = await ctx.send(text)
for emoji in ('👍', '👎'):
await message.add_reaction(emoji)
You can use Message.pin: await message.pin()
I'm not sure what you mean by "user tags". Do you mean roles?
I would write your command as
#bot.command()
async def create_poll(ctx, text, *emojis: discord.Emoji):
msg = await ctx.send(text)
for emoji in emojis:
await msg.add_reaction(emoji)
Note that this will only work for custom emoji, the ones you have added to your own server (This is because discord.py treats unicode emoji and custom emoji differently.) This would accept commands like
!create_poll "Vote in the Primary!" :obamaemoji: :hillaryemoji:
assuming those two emoji were on the server you send the command in.
With the new commands.Greedy converter, I would rewrite the above command like so:
#bot.command()
async def create_poll(ctx, emojis: Greedy[Emoji], *, text):
msg = await ctx.send(text)
for emoji in emojis:
await msg.add_reaction(emoji)
So invocation would be a little more natural, without the quotes:
!create_poll :obamaemoji: :hillaryemoji: Vote in the Primary!
I have just seen what you are stuck and I was stuck at this too. The solution is that reactions are not actually things like ":one:" it is actual emojis, there are different ways to fix this, the simplest being adding them to the dictionary yourself. Or you can use python3 -m pip install discordhelp and use the functions for it listed here.
So I have my url of the image in a variable. It needs to be in the variable because it changes every time the command is run. Unfortunately, the documentation says that set_image requires a string url, and trying to use a variable throws the 400 error. I then tried doing a simple send_message with the link, but Discord does not download the image into the chat because it is not a string. Does anyone know how to get around this? Thank you!
embed.set_image(url = exampleVariable) #throws error
If you want the simplest way of doing things, just sending the image url will work. The only problem is that it will send both the url and the image.
If you want a nicer result, you must do one if the following:
If you are using the rewrite branch, you need to do
imageURL = "image url"
embed = discord.Embed()
embed.set_image(url=imageURL)
await ctx.send(embed = embed)
If you are using the async branch, you need to do
imageURL = "image url"
embed = discord.Embed()
embed.set_image(url=imageURL)
await bot.send_message(ctx.message.channel, embed = embed)
To figure out which branch you have, you need to do print(discord.__version__). If 1.0.0a is printed, then you have the rewrite branch. If 0.16.2 is printed, then you have the async branch
An exception (discord.Forbidden) will be thrown if your bot cannot send embeds to the channel.
Sending an embed to a channel is as simple as:
# rewrite
await ctx.send(embed=embed_object)
# async
await bot.send_message(CHANNEL_ID, embed=embed_object)
Otherwise your when setting the image of an embed, you must pass in a string URL
(see async doc and rewrite).
the URL must be a string and as stated in the docs "only HTTPS supported"
it doesn't matter that you're passing a variable because a variable is only a reference to an object, in this case, a string.
if I recall correctly discord will not display an invalid image URL. So you may want to double check the URL.
otherwise, code such as this should work.
(assuming rewrite)
#bot.command()
async def image(ctx):
return await ctx.send(embed=discord.Embed().set_image(url=ctx.author.avatar_url))
(assuming async)
#bot.command(pass_context=True)
async def image(ctx):
em = discord.Embed().set_image(url=ctx.message.author.avatar_url)
return await bot.send_message(ctx.message.channel.id, embed=em)