This question already has an answer here:
SyntaxError (Python): 'await' outside async function
(1 answer)
Closed 1 year ago.
I know - there are a lot of questions with this title, but most are for Dark Sky or are different to my problem.
class Counter(discord.ui.View):
#discord.ui.button(label='0', style=discord.ButtonStyle.red)
async def counter(self, button: discord.ui.Button, interaction:
discord.Interaction):
number = int(button.label)
button.label = str(number + 1)
if number + 1 >= 5:
button.style = discord.ButtonStyle.green
await interaction.message.edit(view=self)
view = Counter()
await ctx.send('Press to increment', view=view)
Error message:
File "main.py", line 27
await ctx.send('Press to increment', view=view)
^
SyntaxError: 'await' outside function
I'm very new to python and am not sure why this is at all. Any help would be greatly appreciated!
you need to put the
view = Counter()
await ctx.send('Press to increment', view=view)
code inside a asynchron function, because the await keyword can only be used inside of asynchron functions.
example functions
async def my_function(): # this is an async function
...
def my_other_function(): # this is a normal function
...
Async functions have to be called with the await keyword, or else you'll get an exception
await my_function() # calling an async function
my_other_function() # calling a normal function
as you can see in this example, in the async function, you can use await
async def something():
print("hello async world")
async def my_function():
await something()
but if you try to await an async function inside a normal function
asnyc def something():
print("hello async world")
def my_other_function():
await something()
You will get the SyntaxError: 'await' outside function error
The best way to do this together with a discord bot is by adding the code to a listener. For example with the on_message event which will be fired whenever a message is incoming
Example for on_message event
import discord
client = discord.Client()
#client.event
async def on_message(message): # The event
if message.content == "your_text" # Insert a text here that is needed to use the "command"
# your code will be executed whenever the bot receives a message with the content "your_text"
view = Counter()
await message.channel.send('Press to increment', view=view)
client.run("your unique token here")
I would recommend to take a look at https://discordpy.readthedocs.io/en/stable/quickstart.html before starting
According to the ctx, your code is made for the client's #commands.command function.
(this should fix your code)
from discord.ext import commands
bot = commands.Bot(command_prefix="!")
class Counter(discord.ui.View):
#discord.ui.button(label='0', style=discord.ButtonStyle.red)
async def counter(self, button: discord.ui.Button, interaction:
discord.Interaction):
number = int(button.label)
button.label = str(number + 1)
if number + 1 >= 5:
button.style = discord.ButtonStyle.green
await interaction.message.edit(view=self)
#bot.command("test")
async def command(ctx):
view = Counter()
await ctx.send('Press to increment', view=view)
bot.run("your unique token here")
In order to use your code, you need to send a message in a channel where your bot can read messages with the content !test to invoke the command
The typical structure of your code will be (using sample code from the docs)
#bot.command()
async def foo(ctx, arg):
await ctx.send(arg)
That's also where the ctx you're using comes from--if you removed the await you'd get an error that ctx wasn't defined. Your code will specify functions that correspond to commands or other events on discord, and discord.py will handle calling the functions you specify when users perform those actions.
You can only use the await keyword in async functions. The idea behind these functions is that if your code has to do something that takes a long time, but mostly just involves waiting, python can go do other stuff while it waits. For discord.py, since a lot of your operations involve waiting for slow network operations like sending messages, that's very important.
here's one intro to the ideas behind async/await, I haven't read it too much, so if it's bad or anybody has better suggestions drop a link in the comments.
Related
async def get_guild_members(guild_id: int):
guild = bot.fetch_guild(guild_id)
return guild.member_count
get_guild_members(789226204980707409)
I'm using this code to try to find the memeber count of a guild on discord. WHen I run it it returns
<coroutine object get_guild_members at 0x7ffd518ef4c0>
What does this mean? How do I do it correctly?
This problem stems from your lack of knowledge of asyncio. You don't call asynchronous functions like regular functions but rather use asyncio.run(coro()) or the await coro() syntax inside another async function.
REGULAR FUNCTION
def main():
pass
main()
ASYNCIO FUNCTION
import asyncio
async def get_guild_members(guild_id: int):
guild = await bot.fetch_guild(guild_id)
return guild.member_count
member_count = asyncio.run(get_guild_members(78922624980707409))
so im trying to get a command to run every 5 minutes on my dsicrod.py bot and need ctx to get guild members and certain details like that so I need it in a bot.command, but I cant have that properly do that without tasks.loop(minutes=5) so I tried getting it to send the command with tasks.loop but it wouldn't work so I went to the pythin discord and got help they got me to this point
#bot.command(pass_context=True)
async def update_member_count(ctx):
await ctx.send(ctx.guild.member_count)
channel = discord.utils.get(ctx.guild.channels, id=829355122122424330)
await channel.edit(name = f'Member Count: {ctx.guild.member_count}')
#tasks.loop(minutes=5)
async def update_member_count2(ctx):
await update_member_count(ctx)
and It still gives errors saying that ctx arg is missing in update_member_count2. pls help
You can create your loop-function in another way. Your approach is a bit unclear to me.
Try out the following:
async def update_member_count(ctx):
while True:
await ctx.send(ctx.guild.member_count)
channel = discord.utils.get(ctx.guild.channels, id=YourID)
await channel.edit(name=f'Member Count: {ctx.guild.member_count}')
await asyncio.sleep(TimeInSeconds)
#bot.command()
async def updatem(ctx):
bot.loop.create_task(update_member_count(ctx)) # Create loop/task
await ctx.send("Loop started, changed member count.") # Optional
(Or you simply create the task in your on_ready event, depends on you)
What did we do?
Created a loop out of a function (async def update_member_count(ctx):)
Exectued updatem one time to activate the loop
Changed your actual command to a "function"
You could go with #Dominik's answer but its near impossible to stop a running While loop. Going ahead with discord.py tasks.
If you want to start the task with a command:
async def start(ctx):
update_member_count2.start(ctx)
If you want to start it when the bot starts, you have to slightly modify the code
#tasks.loop(minutes=5)
async def update_members():
guild = bot.get_guild(guildID)
channel = guild.get_channel(channelID)
#update channel, don't sleep
# inside init or on_ready
if not update_members.is_running():
update_members.start()
Use this in a on_member_join event
#bot.event
async def on_member_join(member):
channel = discord.utils.get(member.guild.channels, id=962732335386746910)
await channel.edit(name = f'⚫┃・Members: {member.guild.member_count}')
I would like to create a simple smoke test for my application using pytest. It would be really simple for me, that's why I tried. The problem is that bots does not reacts for other bot's message as I noticed. I looked at the code (bot.py) and modified the process_commands just for the smoke test. Unfortunately it's still works only for human messages during testing.
Edited code, a complete test (only HASH and channel id are imported)
from discord.ext import commands
import pytest
import threading
import time
from build_config import HASH, channel_id
bot = commands.Bot(command_prefix = '.', description="This is a test bot for stackoverflow!")
class Test_Message(commands.Cog):
#commands.command(pass_context = True)
async def message(self, ctx):
await ctx.send("text_message")
def run_bot():
bot.add_cog(Test_Message())
bot.run(HASH.DEBUG)
#pytest.fixture
def create_bot():
t1 = threading.Thread(target=run_bot, args=[])
t1.start()
class Test_Events:
successful = False
#staticmethod
#bot.event
async def on_ready():
channel = bot.get_channel(channel_id)
await channel.send('.message')
#staticmethod
#bot.event
async def on_message(message):
await bot.process_commands(message)
if (message.channel.name == "general" and message.clean_content == "text_message"):
Test_Events.successful = True
#staticmethod
#bot.event
async def process_commands(message):
ctx = await bot.get_context(message)
await bot.invoke(ctx)
#pytest.mark.asyncio
async def test_Smoke(create_bot):
must_end = time.time() + 60
while time.time() < must_end:
if Test_Events.successful:
break
time.sleep(1)
assert Test_Events.successful
Basically someone marked this one as a solution in Allow Discord Rewrite bot to respond to other bots, but it doesn’t work for me.
Edit: So I debug the discord.py, unfortunately there is at least another check in get_context self._skip_check(message.author.id, self.user.id).
So it turned out that the creators of discord.py created an overwritable process_commands function. That's why you can remove the
if message.author.bot:
return
part. Unfortunately this is not enough for anything, there is such a part deep in the code which reject the bot commands:
if self._skip_check(message.author.id, self.user.id):
return ctx
The solution is to take this check out:
bot._skip_check = lambda x, y: False
I only use this workaround for testing, it is forbidden to use anything like this one on a normal server.
My Goal
Hello, I want my bot to cycle through 3 prefixes, The prefix for the bot, The amount of servers its in, and another message of my choosing. I use the #bot.command/#bot thing, so the example in the FaQ in the readthedocs website doesn't help me. What i tried didn't work, and i also do not know how to get the number of servers the bot is in.
What I tried
async def my_background_task(self):
await bot.change_presence(activity=discord.Game(name="message"))
await asyncio.sleep(7)
await bot.change_presence(activity=discord.Game(name="PREFIX: CF>"))
await asyncio.sleep(7)
#bot.event
async def on_ready(self):
print('Bot is online and ready.')
self.bg_task = self.loop.create_task(self.my_background_task())
This solution has been tested:
prefixes = [lambda: 'prefix1', lambda: 'prefix2', lambda: str(len(bot.guilds))]
bot = commands.Bot(command_prefix=prefixes[0]())
async def my_background_task():
prefix_num = 0
while True:
prefix = prefixes[prefix_num]()
await bot.change_presence(activity=discord.Game(name=prefix))
bot.command_prefix = prefix
# increase the current prefix - if it's reached the length of the prefix list, set it back to 0
prefix_num = (prefix_num + 1) % len(prefixes)
await asyncio.sleep(7)
#bot.event
async def on_ready():
bot.loop.create_task(my_background_task())
First of all, your background task and on_ready function shouldn't have any self in them. Also, your background task has to have a while loop inside of it, otherwise, it will only run once.
This code utilizes anonymous lambda functions because it allows the bot to be added to a different server and adjust the prefix when that happens. When it comes time to change the prefix, one of the functions is called, returning a string.
I would also recommend looking at the API reference because it is very helpful.
I am using Python with discord.py. Documentation here
I've got a bot that is running on a Discord server that links the server with a subreddit. Users have various commands that do things like getting the top submissions, getting the latest submissions, and so on.
I want to add some features to the bot, with one of them being a keyword notifier. The bot should search the subreddit for keywords in the title, and then notify users if they are on the list for that keyword. I know how to do this, I've done it plenty of times, but I don't know how to do it with a Discord bot. I have no experience with asynchio or any kind of asynchronous programming.
The way I've tried to do it works, but it is very janky and definitely not good. At the top of the on message() function, I just add a call to the search_submissions() function, so that whenever someone puts sends a new message on the server, the bot will scan the Reddit submissions. The server is busy enough that this would work relatively okay, but I really want to do it the "proper" way.
I don't know how to call the search_submissions() function without putting it inside of on_message().
Edit for extra code:
import discord
TOKEN = "redacted"
client = discord.Client()
#client.event
async def reddit_search():
print("Searching")
#client.event
async def on_message(message):
if message.content.startswith("reddit!hot"):
# Get hot
# Do other things.
#client.event
async def on_ready():
print("Connected to Discord as {}.".format(client.user.name))
client.run(TOKEN)
You can add a function to the bot event loop with Client.loop.create_task(search_submissions()) like this:
async def search_submissions():
pass
client = discord.Client()
client.loop.create_task(search_submissions())
client.run(TOKEN)
Update:
If you want your function to continue working you can put it in a while loop with some sleeping in between:
async def search_submissions():
while(true):
# do your stuff
await asyncio.sleep(1)
The other answers here don't take into account discord.py's helpful tasks.loop decorator.
To make an event occur every 5 seconds, you would use
from discord.ext import tasks, commands
class MyCog(commands.Cog):
def __init__(self):
self.foo.start()
def cog_unload(self):
self.printer.cancel()
#tasks.loop(seconds=5.0)
async def foo(self):
print('bar')
More can be found here: https://discordpy.readthedocs.io/en/latest/ext/tasks/
You want your search_submissions() function to be async so other functions of your bot can still be invoked and your bot stays responsive. Define it to be def async and use aiohttp to send async HTTP requests to reddit -- what this does is send off the request, relinquish control to the event loop, and then take back control once the results have been transmitted back. If you use a standard HTTP library here instead then your whole bot will be blocked until the result comes back. This of course only makes sense if the task is mainly I/O-bound and less CPU-bound.
Then call search_submissions() in on_message(message) -- but call it asynchronously using result = await search_submissions(). This will resume execution of on_message once the result of search_submissions is ready.
If you truly want to do something else in the same context while waiting on search_submissions (which I think is unlikely), dispatch it as task = asyncio.create_task(search_submissions()). This will start the task immediately and allow you to do something else within the same function. Once you need the result you will have to result = await task.
async def search_submissions():
async with aiohttp.ClientSession() as session:
async with session.get(some_reddit_url) as response:
return await response.read()
#client.event
async def on_message(message):
if message.content.startswith("reddit!hot"):
result = await search_submissions()
await message.channel.send(result)