Search code examples
pythonasync-awaitapscheduler

How do I execute an async def in a Discord Hikari Bot every day?


I need help with a Discord Hikari bot that writes a message every day. I can't get the loop to work. I tried to do it with apscheduler (AsyncIOScheduler) but when I run it it doesn't do anything and when I run the async loop, it executes, but everything after it doesn't run, so the bot doesn't start.

import hikari
import lightbulb
import asyncio
import bot

from datetime import datetime
from lightbulb import commands, context
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger

daily_plugin = lightbulb.Plugin("Daily")
sched = AsyncIOScheduler()
sched.start()

@sched.scheduled_job(CronTrigger(hour=2, minute=0))
async def msg1():
    await bot.bot.rest.create_message(783996883080708096, "Morning Message")
    print("Message")

#asyncio.get_event_loop().run_forever()

def load(bot: lightbulb.BotApp) -> None:
    bot.add_plugin(daily_plugin)

(This is inside a Lightbulb plugin)


Solution

  • The problem here is strictly the asyncio.get_event_loop().run_forever() call. This is fully blocking and prevents the lightbulb BotApp from starting.

    The remaining code you have works, I have tested it with a 1 minute interval as follows:

    # main.py
    import os
    
    import hikari
    import lightbulb
    from dotenv import load_dotenv
    
    
    load_dotenv()
    
    
    bot = lightbulb.BotApp(
        token=os.environ["TOKEN"],
        prefix="$",
        intents=hikari.Intents.ALL,
        ignore_bots=True,
    )
    
    
    bot.load_extensions("daily")
    
    
    if __name__ == "__main__":
        bot.run()
    
    # daily.py
    import lightbulb
    from apscheduler.schedulers.asyncio import AsyncIOScheduler
    from apscheduler.triggers.cron import CronTrigger
    
    
    daily_plugin = lightbulb.Plugin("Daily")
    sched = AsyncIOScheduler()
    sched.start()
    
    
    @sched.scheduled_job(CronTrigger(minute="*/1"))
    async def msg1() -> None:
        await daily_plugin.app.rest.create_message(783996883080708096, "Morning Message")
    
    
    def load(bot: lightbulb.BotApp) -> None:
        bot.add_plugin(daily_plugin)
    

    Alternative

    You could also consider using the hikari lifetime events and a lightbulb Plugin listener decorator to start the scheduler to prevent cluttering your namespace. If you have other places you may need to use the scheduler outside this extension, you could even store it inside the BotApp's datastore for easy access anywhere you have the BotApp.

    # main.py
    import os
    
    import hikari
    import lightbulb
    from apscheduler.schedulers.asyncio import AsyncIOScheduler
    from dotenv import load_dotenv
    
    
    load_dotenv()
    
    
    bot = lightbulb.BotApp(
        token=os.environ["TOKEN"],
        prefix="$",
        intents=hikari.Intents.ALL,
        ignore_bots=True,
    )
    
    
    @bot.listen(hikari.StartingEvent)
    async def on_starting(_: hikari.StartingEvent) -> None:
        # This event fires once, while the BotApp is starting.
        bot.d.sched = AsyncIOScheduler()
        bot.d.sched.start()
        bot.load_extensions("daily")
    
    
    if __name__ == "__main__":
        bot.run()
    
    
    
    # daily.py
    import hikari
    import lightbulb
    from apscheduler.triggers.cron import CronTrigger
    
    
    daily_plugin = lightbulb.Plugin("Daily")
    
    
    async def msg1() -> None:
        await daily_plugin.app.rest.create_message(783996883080708096, "Morning Message")
    
    
    @daily_plugin.listener(hikari.StartedEvent)
    async def on_started(_: hikari.StartedEvent) -> None:
        # This event fires once, when the BotApp is fully started.
        daily_plugin.app.d.sched.add_job(msg1, CronTrigger(minute="*/1"))
    
    
    def load(bot: lightbulb.BotApp) -> None:
        bot.add_plugin(daily_plugin)