Async timer function blocks itself when called twice - python

I'm currently trying to implement a function to my Discord Bot where I can easily delete the sent message after a set amount of seconds.
Here is my function:
async def messageCountdown(context, message, counter):
response = await context.send(f"**{'—' * counter}** \n {message}")
for i in range(counter, 0, -1):
await response.edit(content=f"**{'—' * i}** \n {message}")
await asyncio.sleep(1)
await context.message.delete()
await response.delete()
Function call:
#client.command()
async def test(context, *message):
await messageCountdown(context, "Test", 10)
The function itself runs totally fine if only called once:
https://gyazo.com/3b1eef9ecf8ecbe6473e8b20dfcd19d1
As soon as I call it twice or more often, the countdown goes down inconsistently in a weird way: https://gyazo.com/af4b23c5831ae90d5bc5a8461a22b0d7
I tried the same again but replaced await asyncio.sleep(1) with time.sleep(1), same result.
This is where I don't know how to continue, as all I found was that asyncio should solve the problem, which it obviously doesn't. Also, I don't understand why one function blocks the opposite function since neither asyncio nor time should do so as the function is asynchronous (which should exactly prevent what happens right now, shouldn't it?).

There is no problem with the async function here. The problem is Discord API Rate Limits
You call the function twice so it does edit message twice every second instead of once.
Once you hit the rate limit the bot does the thing but it isn't yet updated due to rate limit, as soon as the rate limit is removed it instantly updates the message which causes it to jump from step 3 to step 1 directly, resulting in inconsistent updation of message.
Read More about Discord API Rate Limits: here

Related

Carry bots tasks out over restarts and shutdowns

I have a simple python bot within discord.
Lets say my code is as follows:
#client.command(pass_context=True)
async def test(ctx):
await asyncio.sleep(500)
await ctx.send("Hello!")
This code will have the bot wait 500 seconds before sending "Hello! in chat. If I shutdown my bot and restart it, this timer will obviously be lost and unsaved.
How can I carry this function out across restarts and such?
The way I implement something like this is with a try-finally loop, the finally part executes even if you stop/restart your bot (not with every method of killing the process)
try:
client.run(TOKEN)
finally:
save('data.json', data)
To make this work you would expand your command that requires restart with a part that saves to a global dictionary/list. Something like (hellosave is the global list here):
hellosave.append((time.time() + 500, ctx.channel.id))
await asyncio.sleep(500)
await ctx.send("Hello!")
hellosave.pop(0)
As save() you would take that global dict/list and save it in some format (you can do it any way you'd like, from pickling to printing it in a .txt file [I chose json here cuz I needed it to be readable, that might not be the case in your case])
Next time you boot up your bot, in the on_ready() event you look for the contents of that file and restart the function based on the data saved in it, like for example your hello case you'd check if the time in the saved event has already passed, if it has you send it immediately (or not at all) if it hasnt you create a task with asyncio.create_task that waits for the amount it needs (time.time() - hellotime) [to make this work you have to save the channel id, like I did in my example]

"on_message" of Discord.py can't work after I implemented "schedule" into it

I'm not familiar with Python so I'm afraid that this could be a silly question for all of you but I have already tried my best to solve it.
I worked on a self-project of making a bot to do some silly task just for fun.
First, I have done a function to let it receive a message with "on_message" event, which works well. here's the code
import discord
import os
discordClient = discord.Client()
#discordClient.event #event register
async def on_ready():
print('Logged in as {0.user}'.format(discordClient))
await routineAnnouncer.createDisocrdRoutine(discordClient)
#discordClient.event
async def on_message(message):
checked = False
if message.author == discordClient.user:
checked = True
return
else:
print("Get Message {0.content} from {0.author.name} ({0.author.id}) #{0.channel.name} ({0.channel.id})".
format(message))
#-------------------------- RUN ----------------------
print('Registering EVENTS COMPLETE')
discordClient.run(os.environ['discordToken'])
I code it on Repl.it and "os.environ" is its way to retrieve .env.
here are the code in "routineAnnouncer" that is used in the "on_ready" event
import os #ใช้ดึง .env (secret)
import schedule
import time
def scheduleAnnouncer(discordClient,announceType,announceDetail):
print("Announcing {} {}".format(announceType,announceDetail))
lzChannel = discordClient.get_channel(int(os.environ['lz_token_test']))
discordClient.loop.create_task(lzChannel.send("Announcing {} {}".format(announceType,announceDetail)))
async def createDisocrdRoutine(discordClient):
print("Registering CRONJOBS...")
scheduleAnnouncer(discordClient,"headerTest","descTest")
schedule.every(5).seconds.do(scheduleAnnouncer,discordClient,"header1","desc1")
schedule.every(10).seconds.do(scheduleAnnouncer,discordClient,"header2","desc2")
# while True:
# schedule.run_pending()
# time.sleep(5)
So, it supposed to do createDisocrdRoutine() after the connection is ready and set the scheduling text.
As you can see the last section of the code is commented. It is supposed to be a loop that triggers the scheduled text to be sent via discord to a designated channel.id.
To this point. The code works fine. the "on_message(message)" section able to print out whatever is sent to a channel.
The function "scheduleAnnouncer" also works fine. It can send message to the channel.
Discord Screen Repl.it Screen
After a loop "while: True" is uncommented in order to let the schedule work.
The loop works fine. It prints out the text as shown in the loop. but discord can't detect any text that is sent to the same channel before. Even the function "scheduleAnnouncer" that supposed to send a message is broken. It feels like anything involved with discord is broken as soon as the "while: True" is uncommented.
Discord Screen Repl.it Screen
I tried to separate the scheduling into another thread but it didn't work. I tried to use other cronjob managements like cronjob, sched or something else. Most of them let me face other problem.
I need to send argument into the task (discordClient, announceType, announceDetail).
I need to use the specific date/time to send. Not the interval like every 5 seconds. (The text that needs to be sent differ from time and day. Monday's text is not that same as Friday's text)
From both criteria. The "schedule" fits well and suppose to works fine.
Thank you in advance. I don't know what to do or check next. Every time I try to solve it. I always loop back to "schedule" and try to works with it over and over.
I hope that these pieces of information are enough. Thank you so much for your time.
time.sleep() is not async so it will freeze your program. Use await asyncio.sleep(). This is because it is pausing the whole program, not just that function.
The reason you’d want to use wait() here is because wait() is non-blocking, whereas time.sleep() is blocking. What this means is that when you use time.sleep(), you’ll block the main thread from continuing to run while it waits for the sleep() call to end. wait() solves this problem.
Quote From RealPython

Is there kind of a limit for asyncio in discord.py?

So I‘ve got a python discord bot. The idea is, there is a channel for funny quotes. In order to keep the channel organized, people can as well discuss in it, but after 24h their message should be deleted, if there‘s no %q before it. I‘ve got the following code:
#bot.event
async def on_message(ctx):
await bot.process_commands(ctx)
channel = bot.get_channel(ID_OF_CHANNEL)
if ctx.channel == channel:
if not ctx.content.lower().startswith("%q"):
await asyncio.sleep(86400)
await ctx.delete()
The problem is, it just doesn‘t work. The bot responds to other commands, but doesn‘t delete every single message after 24h. In fact, it doesn‘t delete any messages. I‘ve tried it with only 3h as well, but it didn‘t work too. Passing in only 30 seconds works…
I‘m sorry if my question may be stupid, but I‘m just clueless and I hope anybody can help me :)
May there be a limit of messages the bot can view or a limit of time it can do so?
Btw: I‘m pretty sure the bot was running all the time, so it was not interrupted.
EDIT: Just to make that clear: The idea is to start something like a timer on every single message, which works for 30 seconds, but strangely not for 3h.
Try using tasks instead of asyncio. It is made for such repetetive operations and it is easier and nicer because it is made by discord and is included in discord.ext. The way I would do it is something like this:
from discord.ext import tasks
#bot.command()
async def start(ctx):
deletingLoop.start() #here you start the loop
#tasks.loop(hours=24)
async def deletingLoop():
guild = self.bot.get_guild(guildID) #get the guild
channel = guild.get_channel(channelID) #and the channel
channel.purge(limit=1000) #clear the channel
and if you are afraid that this channel may have more than 1000 messages you can add on_message() event that would count the messages on this channel.
You have to take ctx.message as you want to delete the message of the context. This can take the parameter delay which deletes the selected message after that amount of time.
Code:
#bot.event
async def on_message(ctx):
await bot.process_commands(ctx)
channel = bot.get_channel(ID_OF_CHANNEL)
if ctx.channel == channel:
if not ctx.content.lower().startswith("%q"):
await ctx.message.delete(delay=24h_in_seconds)

A scheduled task to a discord bot in python fails to do its job

I am programming a discord bot in python. It needs to sweep the member list every 24hrs, check their roles and do some actions accordingly. I started to program this, but the scheduled task apparently doesn't have access to discord. I can't seem to get a member. When I do this in a command:
#bot.command(name='sweepercmd', help='')
async def sweepercmd(ctx):
member = get(bot.get_all_members(), name="Waldstein")
print(member)
It prints "Waldstein#4164" in the bash console as expected. However if I put the same code in a task like this:
#tasks.loop(hours=24.0)
async def sweeper():
member = get(bot.get_all_members(), name="Waldstein")
print(member)
It prints "None". Adding ctx like ...sweeper(ctx)... makes it hang.
How can I access discord within my task just like in the commands?
thanks in advance for the help,
Jo
As for why your tasks are failing, I'm not entirely sure, but I'll look into it for you and edit my answer when I get some info on it. In the mean time, this should suffice:
You can use loop.create_task() as an alternative for this.
async def sweep_task():
while True:
member = discord.utils.get(bot.get_all_members(), name="Waldstein")
print(member)
await asyncio.sleep(86400)
#bot.command()
async def sweep(ctx): # doesn't necessarily need to go into a command - you could put it into
# your on_ready event if you want to
bot.loop.create_task(sweep_task())
References:
Client.loop
Client.get_all_members()
utils.get()
loop.create_task()
asyncio.sleep()

Bug on a loop after few hours Discord.py rewrite (python)

Firstly I'm French, so sorry for my bad English
I have a problem. I did not succeed to make a normal Discord.py loop so I did one myself:
#client.event
async def on_ready():
game = discord.Game(f"{bot_version} | {len(client.guilds)} serveurs")
await client.change_presence(status=discord.Status.idle, activity=game)
print(f"Bot connecté en tant que : {client.user.name}")
print(f"Le temps de chargement a été de : {loading_time} secondes")
await client.wait_until_ready()
#Loop part
t = 0
while not client.is_closed():
for i in range(2):
server_id = cursor.execute(f"SELECT server_id FROM 'servers' WHERE id={i+1}")
server_id = server_id.fetchone()[0]
server_id = int(server_id)
#PV Démons
...
...
#Time test
print(t)
t = t+4
await asyncio.sleep(4)
I condensed the code but in reality it's more than 150 lines.
Ok now let's explain my problem:
So this code repeats every 4 seconds, but after 3/4 hours, it repeats twice every 4 seconds (video).
For my tests I made a variable to which I add 4 at the end of the loop. But after 3/4 hours, this variable is duplicated (video).
So that are my questions :
Why this variable is duplicated?
Why this code is repeated twice every 4 seconds?
How I can fix that?
How I can do normal loop on Discord.py Rewrite?
Video : Video of the bug
The documentation tells that the event: on_ready(). Can be runned multiple times because of reconnection logic. Normally the bot doesnt reconnect in a few hours. This could explain why your problem only happens every 3/4 hours.
Further more the on_ready() function isnt made with the idea of having a infinite loop (or any kind of long running loop). As it is normally used for setting some things up. When a on_ready() function is taking more than 4 hours and suddenly it reconnects causing a new instance of on_ready() to be ran. You will see double the numbers as you explained. If we do want to use these long while loops. I recommend using tasks instead.
This would also solve the potential problem on_ready() event gives you. By only allowing 1 of that task to be running at a time.
Tasks are used for running stuff in the background. You can activate these tasks in the on_ready() function.
Make sure your on_ready function isnt doing background stuff in the future. And wont take more than a hour to complete. It is used for setting things up when ready. Not for running tasks in background (as explained there are other ways of achieving that).
it probably repeats twice because you're doing this
for i in range(2):
you should be able to fix this by doing this instead
for i in range(1):

Categories

Resources