Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add a function to discord.py event loop?

I am using Python with discord.py. Documentation here

I've got a bot that is running on a Discord server that links the server with a subreddit. Users have various commands that do things like getting the top submissions, getting the latest submissions, and so on.

I want to add some features to the bot, with one of them being a keyword notifier. The bot should search the subreddit for keywords in the title, and then notify users if they are on the list for that keyword. I know how to do this, I've done it plenty of times, but I don't know how to do it with a Discord bot. I have no experience with asynchio or any kind of asynchronous programming.

The way I've tried to do it works, but it is very janky and definitely not good. At the top of the on message() function, I just add a call to the search_submissions() function, so that whenever someone puts sends a new message on the server, the bot will scan the Reddit submissions. The server is busy enough that this would work relatively okay, but I really want to do it the "proper" way.

I don't know how to call the search_submissions() function without putting it inside of on_message().


Edit for extra code:

import discord

TOKEN = "redacted"
client = discord.Client()

@client.event
async def reddit_search():
    print("Searching")

@client.event
async def on_message(message):
    if message.content.startswith("reddit!hot"):
        # Get hot
    # Do other things.

@client.event
async def on_ready():
    print("Connected to Discord as {}.".format(client.user.name))

client.run(TOKEN)
like image 576
John Yuki Avatar asked Aug 04 '18 20:08

John Yuki


3 Answers

You can add a function to the bot event loop with Client.loop.create_task(search_submissions()) like this:

async def search_submissions():
    pass

client = discord.Client()

client.loop.create_task(search_submissions())
client.run(TOKEN)


Update:

If you want your function to continue working you can put it in a while loop with some sleeping in between:

async def search_submissions():
    while(true):
        # do your stuff
        await asyncio.sleep(1)
like image 64
Tom Gringauz Avatar answered Oct 17 '22 00:10

Tom Gringauz


You want your search_submissions() function to be async so other functions of your bot can still be invoked and your bot stays responsive. Define it to be def async and use aiohttp to send async HTTP requests to reddit -- what this does is send off the request, relinquish control to the event loop, and then take back control once the results have been transmitted back. If you use a standard HTTP library here instead then your whole bot will be blocked until the result comes back. This of course only makes sense if the task is mainly I/O-bound and less CPU-bound.

Then call search_submissions() in on_message(message) -- but call it asynchronously using result = await search_submissions(). This will resume execution of on_message once the result of search_submissions is ready.

If you truly want to do something else in the same context while waiting on search_submissions (which I think is unlikely), dispatch it as task = asyncio.create_task(search_submissions()). This will start the task immediately and allow you to do something else within the same function. Once you need the result you will have to result = await task.

async def search_submissions():
    async with aiohttp.ClientSession() as session:
        async with session.get(some_reddit_url) as response:
            return await response.read()

@client.event
async def on_message(message):
    if message.content.startswith("reddit!hot"):
        result = await search_submissions()
        await message.channel.send(result)
like image 40
xjcl Avatar answered Oct 17 '22 00:10

xjcl


The other answers here don't take into account discord.py's helpful tasks.loop decorator.

To make an event occur every 5 seconds, you would use

from discord.ext import tasks, commands

class MyCog(commands.Cog):
    def __init__(self):
        self.foo.start()

    def cog_unload(self):
        self.printer.cancel()

    @tasks.loop(seconds=5.0)
    async def foo(self):
        print('bar')

More can be found here: https://discordpy.readthedocs.io/en/latest/ext/tasks/

like image 28
Daniel O'Brien Avatar answered Oct 17 '22 01:10

Daniel O'Brien