Discord.py Bot run function at specific time every day
Asked Answered
T

6

8

I'm using discord.py to create a discord bot, and I need to execute certain actions every day at a specific time. I saw this answer: How to make a loop in discord.py rewrite? and I've been using it so far.

The problem started when I hosted my bot on the heroku free plan. The server on Heroku resets at least once a day, which messes up the timer as shown in that post.

I also saw the schedule library. The problem with this is that it seems like it uses an infinite loop. Wouldn't that prevent me from running anything else during the 24 hours? The bot needs to be able to respond to commands at all times, in addition to sending out the message every 24 hours.

How can I execute an action every day at a specific time even if the server resets? Thank you in advance!

Tankard answered 27/8, 2020 at 23:2 Comment(3)
One solution is to set a specific time hardcoded in your code in a simple if and check if it matches with the server time and if it does then send your message. You can get current time using datetime object.Condon
How would I check though? Wouldn't I need an infinite loop to continuously check the time?Tankard
No , check my answer below. You check the time etc. on a different thread.Condon
C
8

You can write a function to run periodically on a different thread and check if it's the right time to send your message like this example:

from datetime import datetime
import threading


def checkTime():
    # This function runs periodically every 1 second
    threading.Timer(1, checkTime).start()

    now = datetime.now()

    current_time = now.strftime("%H:%M:%S")
    print("Current Time =", current_time)

    if(current_time == '02:11:00'):  # check if matches with the desired time
        print('sending message')


checkTime()
Condon answered 27/8, 2020 at 23:17 Comment(0)
S
4

I know I am late, but this may be helpful for future users.
There is a library called APScheduler which can be used to run functions by setting a cron job (there are other ways too instead of cron. Read more).

A small example will be like:

import discord
from discord.ext import commands

from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger

class Scheduler(commands.Cog):
    """Schedule commands."""
    def __init__(self, bot):
        self.bot = bot

        # Initialize session
        self.session = aiohttp.ClientSession()
    
    # Scheduled events
    async def schedule_func(self):

    def schedule(self):
        # Initialize scheduler
        schedule_log = logging.getLogger("apscheduler")
        schedule_log.setLevel(logging.WARNING)

        job_defaults = {
            "coalesce": True,
            "max_instances": 5,
            "misfire_grace_time": 15,
            "replace_existing": True,
        }

        scheduler = AsyncIOScheduler(job_defaults = job_defaults, 
                          logger = schedule_log)

        # Add jobs to scheduler
        scheduler.add_job(self.schedule_func, CronTrigger.from_crontab("0 * * * *")) 
        # Every hour

And in our main.py file add this (after importing the schedule_jobs.py file obviously):

# Start scheduled commands
scheduler = schedule_jobs.Scheduler(bot).schedule()
scheduler.start()
Secunderabad answered 30/5, 2021 at 2:16 Comment(0)
L
2

The heroku free plan is Linux. Therefore cron will let you run things at a specific time and /etc/init.d/ will let you run things at start up. It’s a good thing to know your OS.

Lexicography answered 28/8, 2020 at 3:56 Comment(2)
I didn't even think about that! I'm pretty experienced with linux, but I don't know much about heroku. How do I interact with the linux environment on heroku?Tankard
@MarekPinto through the shell, in a console windowLexicography
M
1

Have you considered using multithreading to run your program? You could have one thread waiting for the time of day you want, and another that is running the rest of your program. Here's some documentation to help you get started: Intro to Python Threading, Threading Documentation

Madelaine answered 27/8, 2020 at 23:16 Comment(0)
T
0

You can use discord.ext.tasks:

import datetime

import pytz
from discord.ext import commands, tasks

time = datetime.time(hour=9, minute=40, tzinfo=pytz.timezone("Europe/Prague"))


class DailyAction(commands.Cog):
    def __init__(self, bot) -> None:
        self.bot = bot
        self.my_task.start()

    @tasks.loop(time=time)
    async def my_task(self) -> None:
        print("This should trigger everyday at 9:40 AM (CET)")


async def setup(bot) -> None:
    await bot.add_cog(DailyAction(bot))

Check the docs for more details.

Heroku also has its own scheduler, more info in this answer.

Trollope answered 11/2 at 15:22 Comment(0)
S
-1

You can have an infinite loop and use the asyncio.sleep() function. This allows you to process commands from anywhere in the script and still wait the time. Here's a little example:

import asyncio

while True:
    await asyncio.sleep(60)
    print(1)

@client.command()
async def command(ctx):
    pass

Every minute the script will print 1 and anytime somebody does the command it will execute.

Shaughn answered 28/8, 2020 at 1:23 Comment(2)
This is what I'm using now. I had it running as await asyncio.sleep(86400) Wouldn't this not work with the server restarting every day? When the server restarts, I assume that asyncio.sleep will start counting from 0 again, right?Tankard
Yes, it will start back from 0 again. The thing is if you can predict when the server restarts, you can do it. Let's say the server restarts every day exactly. I would do this: await asyncio.sleep(86300)Shaughn

© 2022 - 2024 — McMap. All rights reserved.