Scheduling an async function in Discord.py

Question:

I’ve been trying to schedule a discord.py function to be run every day at midnight.

async def send():
    updays[0] += 1
    await client.get_channel(XXXXXXXXXXXXXXXXX).send(f"It has been {updays[0]} days.")

I first tried it with schedule

schedule.every().day.at('00:00').do(send)

And then learned it can’t handle async functions. I then ran into an SO answer that explained how to do it with just Discord.py iself. My code now looks like this:

import ...

load_dotenv()
client = discord.Client(intents=discord.Intents.all())

updays = [0]

...

@tasks.loop(hours=24)
async def send():
    updays[0] += 1
    await client.get_channel(XXXXXXXXXXXXXXXXX).send(f"It has been {updays[0]} days.")


@send.before_loop
async def before_send():
    hour, minute = 0, 0
    await client.wait_until_ready()
    now = datetime.now()
    future = datetime.datetime(now.year, now.month, now.day, hour, minute)
    if now.hour >= hour and now.minute >= minute:
        future += timedelta(days=1)
    await asyncio.sleep((future - now).seconds)


if __name__ == '__main__':
    client.run(os.getenv('TOKEN'))
    send.start()

But it doesn’t do anything. The rest of the bot works, but nothing happens at the specified time.

Asked By: Aharon K

||

Answers:

I think problem with starting tasks was already in some question.


client.run() runs loop which works all time and your code can’t execute
send.start() – but even if you run it before client.run() it will not work because it needs already working loop.

You should start task in on_ready()

@client.event
def on_ready()
    send.start()   # without `await`

if __name__ == '__main__':
    client.run(os.getenv('TOKEN'))

EDIT:

I think you could set it with loop(time=datetime.time) without @send.before_loop

Important is to use correct timezone. If you don’t use it then it will run it with UTC.

#tz = datetime.timezone.utc                           # Europe/London (UTC)
#tz = datetime.timezone(datetime.timedelta(hours=2))  # Europe/Warsaw (CEST)
tz = datetime.datetime.now().astimezone().tzinfo     # local timezone
print('timezone:', tz)

midnight = datetime.time(hour=0, minute=0, second=0, microsecond=0, tzinfo=tz)
print('midnight:', midnight, midnight.tzinfo)

@tasks.loop(time=midnight)
async def send():
    updays[0] += 1
    await client.get_channel(XXXXXXXXXXXXXXXXX).send(f"It has been {updays[0]} days.")

It should run also with list loop(time=[time1, time2, ...])

midnight = datetime.time(hour=0, minute=0, second=0, microsecond=0, tzinfo=tz)
noon = datetime.time(hour=12, minute=0, second=0, microsecond=0, tzinfo=tz)

@tasks.loop(time=[midnight, noon])
async def send():
     # ...code...
Answered By: furas
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.