So i'm writing discord bot on python, and i want to make background task which would check subreddit for new submissions and post it if there's certain flair. But when i'm trying to start my bot, which worked perfect before, it just wait for something and doesn't start. What should i do?
async def reddit_task():
await bot.wait_until_ready()
start_time = time.time()
reddit = praw.Reddit(different keys)
subreddit = reddit.subreddit('certain subreddit')
for submission in subreddit.stream.submissions():
if submission.created_utc > start_time:
if submission.link_flair_text == 'certain flair':
em = discord.Embed(title=submission.title+'\n'+submission.shortlink)
if len(submission.preview) > 1:
em.set_image(url=submission.preview['images'][0]['source']['url'])
await bot.send_message(discord.Object(id='my channel id'), embed=em)
else:
pass
if __name__ == "__main__":
for extension in startup_extensions:
try:
bot.load_extension(extension)
except Exception as e:
exc = '{}: {}'.format(type(e).__name__, e)
print('Failed to load extension {}\n{}'.format(extension, exc))
bot.loop.create_task(reddit_task())
bot.run(config.bot_beta_token)
I ran into this problem as well since subreddit.stream.submissions() is blocking the for loop. So I solved it by using an infinite loop and subreddit.stream.submissions(pause_after=0) so it returns None if there are no new posts then waits 60 seconds before checking again.
async def reddit_task():
await client.wait_until_ready()
start_time = time.time()
reddit = praw.Reddit("<client_stuff>")
subreddit = reddit.subreddit("<some_subreddit>")
submissions = subreddit.stream.submissions(pause_after=0)
while not client.is_closed:
submission = next(submissions)
if submission is None:
# Wait 60 seconds for a new submission
await asyncio.sleep(60)
elif submission.created_utc > start_time:
<do_stuff_with_submission>
Related
I am trying to create a application, where i start the game with a countdown. For that I have a loop counting down. The problem is when i am awaiting my send loop, all messsages are sent after the loop is finished. When i am putting the function in a task it is sending instant but thats not what i want because I need to wait for the function to call another after.
Thats the the countdown function
async def init_starting(self):
if not self.lobby.status == "waiting":
print("Already Started")
return False
else:
print("Starting Countdown")
self.lobby.status = 'starting'
await self.save_lobby()
await self.send_status_message_with_status(status='starting', data={'has_started': self.lobby.has_started,
'host': self.host.username,
'players': self.players,
'lobby_code': self.lobby_code,
'countdown': 3})
countdown = 3
while countdown >= 0: # here are the messages not sent instant but after the loop is finished (with my current approach they are sent instant but i cant await it
countdown_data = {
'countdown': countdown
}
await self.send_status_message_with_status(status='starting', data=countdown_data)
countdown -= 1
await asyncio.sleep(1)
self.lobby.status = 'started'
await self.save_lobby()
await self.send_status_message_with_status(status='started', data={
'message': "Test"
})
return True
Here i am currently starting the task
if "command" in text_data_json:
command = text_data_json['command']
match command:
case 'start':
loop = asyncio.get_running_loop()
start_task1 = loop.create_task(self.init_starting()) # here i would like to await the task but when i do it they are not sent instant
case _:
print("Code not found")
Thanks for any help in advance!
I'm making a bot for a discord server and have a function that takes a bit of time to run. I want to add a spinning loading icon next to the status message like this Doing something: <spinning icon>. It edits the original message to loop through these messages:
Doing something: \
Doing something: |
Doing something: /
Doing something: -
I tried using a separate thread to update the message like this:
async def loadingBar(ctx, message : discord.Message):
loadingMessage0 = "{0}: \\".format(message)
loadingMessage1 = "{0}: |".format(message)
loadingMessage2 = "{0}: /".format(message)
loadingMessage3 = "{0}: -".format(message)
index = 0
while True:
if(index == 0):
await message.edit(contents = loadingMessage0)
index = 1
elif(index == 1):
await message.edit(contents = loadingMessage1)
index = 2
elif(index == 2):
await message.edit(contents = loadingMessage2)
index = 3
elif(index == 1):
await message.edit(contents = loadingMessage1)
index = 0
farther down, the bot command that starts the process...
#bot.command()
async def downloadSong(ctx, url : str, songname : str):
#Other code that doesn't matter
message = await ctx.send("Downloading audio")
_thread = threading.Thread(target=asyncio.run, args=(loadingBar(ctx, message),))
_thread.start()
#Function that takes a while
#Some way to kill thread, never got this far
However, I get the error Task <Task pending coro=<loadingBar() running at bot.py:20> cb=[_run_until_complete_cb() at /Users/user/.pyenv/versions/3.7.3/lib/python3.7/asyncio/base_events.py:158]> got Future <Future pending> attached to a different loop. I'm new to async programming and the discord libraries; Is there a better way to do this and if not what am I doing wrong?
Firstly, you should add a delay between iterations inside the while loop, use asyncio.sleep for this.
Secondly - asyncio and threading doesn't really work together, there's also no point in using threading here since it defeats the whole purpose of asyncio, use asyncio.create_task to run the coroutine "in the background", you can asign it to a variable and then call the cancel method to stop the task.
import asyncio
async def loadingBar(ctx, message : discord.Message):
loadingMessage0 = "{0}: \\".format(message)
loadingMessage1 = "{0}: |".format(message)
loadingMessage2 = "{0}: /".format(message)
loadingMessage3 = "{0}: -".format(message)
index = 0
while True:
if(index == 0):
await message.edit(contents = loadingMessage0)
index = 1
elif(index == 1):
await message.edit(contents = loadingMessage1)
index = 2
elif(index == 2):
await message.edit(contents = loadingMessage2)
index = 3
elif(index == 1):
await message.edit(contents = loadingMessage1)
index = 0
await asyncio.sleep(1) # you can edit the message 5 times per 5 seconds
#bot.command()
async def downloadSong(ctx, url : str, songname : str):
message = await ctx.send("Downloading audio")
task = asyncio.create_task(loadingBar(ctx, message)) # starting the coroutine "in the background"
# Function that takes a while
task.cancel() # stopping the background task
I'm trying to make a command that deletes a number of messages I want, but it doesn't work.
I got this error:
'coroutine' object has no attribute 'delete'.
if message.content.startswith("!נקה"):
delete_count = 0
try:
value = message.content.split("!נקה ",1)[1] #gets the value of the messages I want to delete
value = int(value)
flag = 1
except:
flag = 0
msg = await message.channel.send("שכחת לכתוב כמה הודעות למחוק.")
await asyncio.sleep(5)
await msg.delete()
if flag == 1:
for i in range(value-1):
if True:
with open("messagesList.txt", "r") as json_file:
messagesList = json.load(json_file) #loads a list of the id of messages
message_id = messagesList[0]
msg = message.channel.fetch_message(message_id)
await msg.delete()
delete_count += 1
with open("messagesList.txt", "w") as json_file:
json.dump(messagesList, json_file)
else:
print("", end = "")
if delete_count != 0:
message = await message.channel.send(f"{delete_count}הודעות נמחקו בהצלחה.") #prints the messages successfully delete
await asyncio.sleep(3) #wait 3 seconds
await message.delete()
Are you trying to create something like a purge command? If you want to simply delete some amount of messages from the channel (without other operations), then try this:
#client.command()
async def clear(ctx, amount = 5): # you can set the default amount of messages that will be deleted (if you didn't specify the amount while running the command)
deleted = await ctx.channel.purge(limit=amount + 1) # adding one to remove "clear" command too
await ctx.send(f"Deleted {len(deleted)} messages") # sends how many messages were deleted
Commands in docs
The error should be here:
except:
flag = 0
msg = await message.channel.send("שכחת לכתוב כמה הודעות למחוק.")
await asyncio.sleep(5)
await msg.delete() #The problematic line.
msg is a coroutine, not a Message object. It doesn't have a delete attribute.
You can simply use the delete_after=time parameter:
except:
flag = 0
msg = await message.channel.send("שכחת לכתוב כמה הודעות למחוק.", delete_after=5)
Another way is to make the bot delete the message, as you've tried:
except:
flag = 0
msg = await message.channel.send("שכחת לכתוב כמה הודעות למחוק.")
await asyncio.sleep(5)
await bot.delete_message(msg)
It's explained in this post.
I'm here with a question. I made this command that pings someone but I don't know how to make it stop. Originally it would listen for "~stop" and it would break the message sending loop. It wouldn't work though. Please help!
#client.command()
async def ping(ctx, member:discord.User=None):
pingEvent = 0
if (member == None):
await ctx.send("who do you want me to ping stupid")
if "~stop" in ctx.channel
pingEvent = 0
else:
pingEvent = 1
while pingEvent <= 1:
await ctx.send(
f"{member.mention}"
)
if pingEvent == 0:
break
First of all, your bot will get rate limited and eventually banned for doing this, but for educational purposes. Here is the solution.
Checking for stop inside loop
for _ in range(20): #restrict pings to avoid being banned
await ctx.send(member.mention)
try:
msg = await client.wait_for("message", check= lambda x: x.author == ctx.author and x.content.contains('~stop'), timeout=2)
except asyncio.TimeoutError:
continue
else:
break
This will send a message once per two seconds since we set the timeout to 2. which is well below the rate limit but still breaks the ToS
References:
asyncio.TimeoutError - you have to import it with import asyncio
wait_for
Read the Terms of Services
This should do the trick and might look more simple than the answer above/below
def check(m):
return ctx.channel == m.channel and m.author == ctx.author
try:
message = await self.bot.wait_for('message', check=check, timeout=300)
except asyncio.TimeoutError:
await ctx.send("You've used too long to answer!")
else:
return
So I'm making some minigame using discord.py, and this is what I got:
asyncio.create_task(self.stream_message_timer(ctx, correct, total), name=f"stream message timer {ctx.author.id}")
while bad_count > 0:
done, pending = await asyncio.wait([
self.client.wait_for('reaction_add', check=lambda reaction, user: str(reaction.emoji) in number_emojis and reaction.message.id == discord_message.id and user == ctx.author),
self.client.wait_for('message', check=lambda m: m.author == self.client.user and m.content == f"Times up!\n{correct}/{total} tasks successful.")
], return_when=asyncio.FIRST_COMPLETED)
try:
result = done.pop().result()
except Exception as e:
raise e
for future in done:
future.exception()
for future in pending:
future.cancel()
if type(result) == discord.Message:
return False
else:
reaction = result
# process the reaction, edit a message
await ctx.send(f"You deleted all the bad messages!\n{correct+1}/{total} tasks successful.")
for task in asyncio.all_tasks():
if task.get_name() == f"stream message timer {ctx.author.id}":
task.cancel()
break
return True
async def stream_message_timer(self, ctx, correct, total):
await asyncio.sleep(5)
await ctx.send(f"Times up!\n{correct}/{total} tasks successful.") # message linked to delete_chat, change there if this is changed
return False
Basically, I'm trying to make some kind of 5 second background timer as I wait for reactions at the same time.
No, I am not looking for timeout=5
The code that I have here works, but its very hacky. I'm either waiting for a reaction from the user, or just waiting for the bot to message "Times up".
Is there a cleaner way to do this? I would like to have the timer start right before the while loop, and forcefully stop anything inside the loop and make the function return False right when the timer finishes
Also, if the function does stop, I still want some way to cancel the timer, and that timer only.
I've been using this method for quite some time and it's making my code very unorganized. Thanks.
Here’s some sort of example that is independent from discord.py:
import asyncio
import random
async def main():
asyncio.create_task(timer(), name="some task name")
# simulates waiting for user input
await asyncio.sleep(random.uniform(2, 5))
return True
async def timer():
await asyncio.sleep(5)
# somehow make this return statement stop the main() function
return False
asyncio.run(main())
wait(return_when=FIRST_COMPLETED) is the correct way to do it, but you can simplify the invocation (and subsequent cancellation) by using the return value of create_task:
async def main():
timer_task = asyncio.create_task(timer())
user_input_task = asyncio.create_task(asyncio.sleep(random.uniform(2, 5)))
await asyncio.wait([timer_task, user_input_task], return_when=asyncio.FIRST_COMPLETED)
if not timer_task.done():
timer_task.cancel()
if user_input_task.done():
# we've finished the user input
result = await user_input_task
...
else:
# we've timed out
await timer_task # to propagate exceptions, if any
...
If this pattern repeats a lot in your code base, you can easily abstract it into a utility function.