0

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.

Aharon K
  • 312
  • 1
  • 10

1 Answers1

1

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...
furas
  • 134,197
  • 12
  • 106
  • 148
  • small correction: it should rather be without `await` because with `await` it will wait forever for end of all `send()` and it will not run other code inside `on_ready()` – furas Sep 08 '22 at 22:22
  • I added example with `loop(time=datetime.time)` – furas Sep 09 '22 at 00:46
  • I just put it at the end of `on_ready()` to avoid the `await` problem. – Aharon K Sep 09 '22 at 18:54