The all function creates a list of stocks who have no available shares. It then runs alert, a looping function, to alert me when the the stock becomes available. I need to run all these alert loops simultaneously but all waits for the first stock to become available before starting the next loop.
I've tried using threading to create a thread for each stock but I cannot await a Thread.start()
async def all(self, ctx):
stocks = requests.get(f'https://api.torn.com/torn/?
selections=stocks&key={api}').json()['stocks']
zero = []
acronymz = []
for items in stocks:
if stocks[items]['available_shares'] == 0:
zero.append(items)
acronymz.append(stocks[items]['acronym'])
await ctx.send(f'Zero: {zero}')
for acronyms in zero:
print(acronyms)
# Thread(target=alert, args=(ctx, acronyms)).start()
await alert(ctx, acronyms)
# await asyncio.sleep(0.5)
async def alert(ctx, items):
stocks = requests.get(f'https://api.torn.com/torn/?selections=stocks&key={api}').json()['stocks'][items]
if stocks['available_shares'] == 0:
await ctx.send(f'I am now watching {stocks["acronym"]}. I will let you know when there are shares available!')
while stocks['available_shares'] == 0:
stocks = requests.get(f'https://api.torn.com/torn/?selections=stocks&key={api}').json()['stocks'][items]
print(stocks)
await asyncio.sleep(5)
await ctx.send(f'There are {stocks["available_shares"]} in {stocks["acronym"]}')
stocks = https://pastebin.com/FhuR4d4R ["stocks"]
You can schedule tasks for the event loop without awaiting them immediately. Here's an example using asyncio.gather
await asyncio.gather(*(alert(ctx, acronyms) for acronyms in zero))
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
So I'm trying to make a command for my Discord bot, where it will check every channel in a server and check the last message in each channel, and then send all the channels that start with the key variable.
async def starthistory(self, ctx, key, msg, num):
for channel in ctx.guild.text_channels:
async for message in channel.history(limit=1):
message_content = message.content.lower()
if len(message.embeds) > 0:
if len(message.embeds[0].title) > 0:
message_content = message.embeds[0].title.lower()
elif len(message.embeds[0].author) > 0:
message_content = message.embeds[0].author.lower()
elif len(message.embeds[0].description) > 0:
message_content = message.embeds[0].description.lower()
if message_content.startswith(key.lower()):
num += 1
msg += f"\n**{num}.** {channel.mention} - **{channel.name}**"
#startswith
#_list.command(name="starts_with",
aliases=["startswith", "sw", "s"],
brief="Lists all channels with message starting with <key>.",
help="Lists all channels with last message starting with the word/phrase <key>.",
case_insensitive=True)
async def _starts_with(self, ctx, *, key):
msg = f"Channels with last message starting with `{key}`:"
num = 0
wait = await ctx.send(f"Looking for messages starting with `{key}`...")
asyncio.create_task(self.starthistory(ctx=ctx, key=key, msg=msg, num=num))
if num == 0:
msg += "\n**None**"
msg += f"\n\nTotal number of channels = **{num}**"
for para in textwrap.wrap(msg, 2000, expand_tabs=False, replace_whitespace=False, fix_sentence_endings=False, break_long_words=False, drop_whitespace=False, break_on_hyphens=False, max_lines=None):
await ctx.send(para)
await asyncio.sleep(0.5)
await wait.edit(content="✅ Done.")
I want it to concurrently look at each channel's history so it doesn't take as long. Currently, my code doesn't change the already defined variables: num is always 0 and msg is always None.
How to concurrently look at each channel's history instead of one at a time?
asyncio.create_task(coro) creates an asynchronous task and runs it in the background. To allow your for loop to run asynchronously, where all the text channels are being processed at the same time, you should use asyncio.gather(coros) instead.
Here is the working code (I trimmed down your code to only the relevant parts):
#staticmethod
async def check_history(msgs, channel, key, semaphore):
async with semaphore:
async for message in channel.history(limit=1):
message_content = message.content.lower()
# trimmed some code...
if message_content.startswith(key.lower()):
num = len(msgs)
msgs += [f"**{num}.** {channel.mention} - **{channel.name}**"]
#_list.command()
async def _starts_with(self, ctx, *, key):
msgs = [f"Channels with last message starting with `{key}`:"]
tasks = []
semaphore = asyncio.Semaphore(10)
for channel in ctx.guild.text_channels:
tasks += [self.check_history(msgs, channel, key, semaphore)]
await asyncio.gather(*tasks)
if len(msgs) == 1:
msgs += ["**None**"]
msgs += [f"\nTotal number of channels = **{len(msgs)}**"]
msg = "\n".join(msgs)
print(msg)
Main points of note/why this works:
I used asyncio.gather() to await all of the check_history coroutines.
I used a asyncio.Semaphore(10) which will restrict the max concurrency to 10. Discord API doesn't like it when you send too many requests at the same time. With too much channels, you might get temporary blocked.
Usually, it's not recommended pass immutable objects (strs and ints) to an external function and attempt to change its values. For your use case, I believe the best alternative is to use a msg list then str.join() it later. This gets rid of num as well.
I am designing a discord bot that loops certain text when a certain conditions are met, which then triggers this function to run.
async def inform(self,message):
while flag==1:
await message.channel.send("text")
await asyncio.sleep(5)
await message.channel.send("text2")
await asyncio.sleep(5)
await message.channel.send("text3")
await asyncio.sleep(5)
Now the problem is when the conditions are not met anymore the function completes the entire cycle before haulting. I want it to stop the moment the condition is not satisfied anymore.
I thought of adding an
if flag==0:
return
After every line but that is not the elegant solution I am looking for.
I don't want the entire code to stop running, but just this function.
I am a beginner to python and any insights are welcome :)
Thank You!
A while loop, before each iteration will look if the condition is True, if it is, it will continue, if not it will stop.
while flag == 1:
if 'some condition met':
flag = 0
# And the loop will stop
The loop will stop by itself, nothing more. Also it's better to use booleans.
Here's your code a bit improved
# Defining variables
flag = True
iteration = 0
while flag:
# Adding `1` each iteration
iteration += 1
# Sending the message
await message.channel.send(f'text{iteration}')
# Sleeping
await asyncio.sleep(5)
If I got it right, You want to check if a condition is met. Here is an infinite loop until some criteria is met. If this is not what you wanted please explain it again.
import random
msgs = ['msg1','msg2','msg3']
condition_met = False
while True:
if condition_met:
break
# also return works here
else:
await message.channel.send(random.choise(msgs))
await asyncio.sleep(5)
if something == 'Condition met here':
condition_met = True
Each iteration of the loop should only send one message, to keep track of the order of messages sent, I used the variable i which is incremented every time a message is sent.
async def inform(self,message):
i = 0
while flag==1:
if i == 0:
await message.channel.send("text")
await asyncio.sleep(5)
elif i == 1:
await message.channel.send("text2")
await asyncio.sleep(5)
elif i == 2:
await message.channel.send("text3")
await asyncio.sleep(5)
i = (i + 1) % 3
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.