shellwhale
shellwhale

Reputation: 902

Asynchronous REST API inside Discord.py

I am looking for a way to integrate a REST API inside my Discord.py, using the rewrite branch. I want to use aiohttp to handle requests but I am unsure of which approach I should take. The objective is to make for example a GET request to the API that would return a list of guilds the bot is in. Or as another example, a POST request that would ask the bot to write a given message into a specific channel. Overall it's about giving instruction to the bot from a webpage.

I tried putting the aiohttp app router and runner inside my Discord.py client class. The web server is indeed running, I made an async function to return guilds the bot is in, but it looks like the function won't take the request argument I pass to it when going to http://127.0.0.1/guilds. Thus leading to a missing 1 required positional argument error.

import discord
import asyncio
from aiohttp import web

class MyClient(discord.Client):
    async def on_ready(self):
        print('Logged on as {0}!'.format(self.user))

    async def get_guilds(self, request):
        response_obj = self.guilds
        return web.json_response(response_obj,status=200,content_type='application/json')

    app = web.Application()
    app.router.add_get('/guilds', get_guilds)
    web.run_app(app, port=80)

client = MyClient()
client.run(TOKEN)

Besides, the aiohttp server does not run asynchronously. I expect the on_ready(self) to run, but it never does. What am I doing wrong ?

Upvotes: 5

Views: 5258

Answers (2)

shellwhale
shellwhale

Reputation: 902

Well I found a way.

bot.py

from asyncio import gather, get_event_loop
from logging import basicConfig, INFO
from discord.ext.commands import Bot
from aiohttp.web import AppRunner, Application, TCPSite
from sys import argv

from api import routes

basicConfig(level=INFO)

async def run_bot():

    app = Application()
    app.add_routes(routes)

    runner = AppRunner(app)
    await runner.setup()
    site = TCPSite(runner, '0.0.0.0', 8080)
    await site.start()

    bot = Bot(command_prefix="$")
    app['bot'] = bot

    try:
        await bot.start(TOKEN)

    except:
        bot.close(),
        raise

    finally:
        await runner.cleanup()

if __name__ == '__main__':
    loop = get_event_loop()
    loop.run_until_complete(run_bot())

api.py

routes = RouteTableDef()

@routes.get('/guilds')
async def get_guilds(request):
    client = request.app['bot']
    guilds = []
    for guild in client.guilds:
        guilds.append(guild.id)

    response = Handler.success(guilds)
    return json_response(response, status=200, content_type='application/json')

Upvotes: 3

Patrick Haugh
Patrick Haugh

Reputation: 60984

I think the below should work to run the two tasks together. This isn't great design though, and will only get more complicated. You may be better served by separating your bot and your server and using a more structured way of communicating between them than sharing memory.

import discord
import asyncio
from aiohttp import web

client = discord.Client()

async def on_ready():
    print('Logged on as {0}!'.format(client.user))

async def get_guilds(request):
    response_obj = client.guilds
    return web.json_response(response_obj,status=200,content_type='application/json')


try:
    bot_task = client.loop.create_task(client.start("token"))
    app = web.Application()
    app.router.add_get('/guilds', get_guilds)
    web.run_app(app, port=80)
except:
    client.loop.run_until_complete(client.logout())

Upvotes: 2

Related Questions