4

I want the bot to run a defined function everyday. I did some research and then I was able to write this:

def job():
    print("task")
    
schedule.every().day.at("11:58").do(job)

while True:
    schedule.run_pending()
    time.sleep(1)

but this code is blocking all other functions so I did some more research from other stack overflow answers and then I was able to write this:

def job():
    print('hi task')

def threaded(func):
    job_thread = threading.Thread(target=func)
    job_thread.start()

if __name__=="__main__":

    schedule.every().day.at("12:06").do(threaded,job)      
    while True:
        schedule.run_pending()

unfortunately this too , blocks my entire code. I need something that doesn't block other parts of my code. How to do that?

3 Answers3

3

You can use Tasks.

So let's say you want to upload a JSON file to a specific channel every day (24 hours) you will do something like this

    @tasks.loop(hours=24)
    async def upload_json_to_discord():
        channel_to_upload_to = client.get_channel(JSON_UPLOAD_CHANNEL_ID)
        try:
            await channel_to_upload_to.send(file=discord.File("id-list.json"))
            print("JSON file upload success")
        except Exception as e:
            print(f"Upload JSON failed: {e}")

and then you need to start the loop by using loop_func_name.start() so in this case:

upload_json_to_discord.start()

and so this will make the function run once every 24 hours

In case you are in a cog, you will have to use this in the __init__ function of the Cog's class

You can find detailed information on this in the documentation: https://discordpy.readthedocs.io/en/latest/ext/tasks/

Zacky
  • 69
  • 5
1

I am late to the party but since this question doesn't have an answer i'll throw mine in.

I'll assume you need this for a discord py bot since this is in your tags. As @Zacky said, you should use Tasks.

A function with @tasks.loop(hours=24) decorator won't start exactly every 24 hours, but will be scheduled in 24 hours when the function ends. This means you can delay the execution until the time you want, and then let the flow of the function continue until the end. Once there, the function will wait 24 hours to be executed again and it will correspond to the time of the day you want (plus the execution time of the function, so be careful if you want very precise function execution)

Here's how I'd do it

def seconds_until(hours, minutes):
    given_time = datetime.time(hours, minutes)
    now = datetime.datetime.now()
    future_exec = datetime.datetime.combine(now, given_time)
    if (future_exec - now).days < 0:  # If we are past the execution, it will take place tomorrow
    future_exec = datetime.datetime.combine(now + datetime.timedelta(days=1), given_time) # days always >= 0

    return (future_exec - now).total_seconds()
    

@tasks.loop(hours=24)
async def job(self):
    await asyncio.sleep(seconds_until(11,58))  # Will stay here until your clock says 11:58
    print("See you in 24 hours from exactly now")

as @dan pointed out, this might fail if the part of the code after the wait is not instantaneous, therefore here is a better solution

def seconds_until(hours, minutes):
    given_time = datetime.time(hours, minutes)
    now = datetime.datetime.now()
    future_exec = datetime.datetime.combine(now, given_time)
    if (future_exec - now).days < 0:  # If we are past the execution, it will take place tomorrow
    future_exec = datetime.datetime.combine(now + datetime.timedelta(days=1), given_time) # days always >= 0

    return (future_exec - now).total_seconds()
    

async def my_job_forever(self):
    while True:  # Or change to self.is_running or some variable to control the task
        await asyncio.sleep(seconds_until(11,58))  # Will stay here until your clock says 11:58
        print("See you in 24 hours from exactly now")
        await asyncio.sleep(60)  # Practical solution to ensure that the print isn't spammed as long as it is 11:58
Nathan Marotte
  • 771
  • 1
  • 5
  • 15
  • Does this really work though? Let's say the loop starts at 11, it waits until 11:58 and do something. Then the loop ends and is launched 24hr from now which will probably be at 11:59 the day after. It's gonna wait another 24hr before actually executing any code, unless I'm mistaken? – dan May 01 '21 at 18:02
  • Mmmh yes you're right this might be a problem, the practical fix would be to loop every 23 hours, and the theoretical fix would be to loop every second, or to use a while true and wait, i'll edit the answer – Nathan Marotte May 02 '21 at 09:53
0

For future people facing the same problem, the above answers using Tasks are correct, but depending on the hosting service you are using there might be an easier solution.

A popular choice is Heroku, you can find multiple tutorials to setup your bot like this one.

Then you can use the Heroku Scheduler add-on: enter image description here

to run your bot at a specified time :
enter image description here

and that‘s it, if you want to run a specific function in your bot you can specify arguments in the command and catch them in your code instead of just python bot.py like me.

Reza Rahemtola
  • 1,182
  • 7
  • 16
  • 30