Issue
i've been trying to create a discord bot that can receive commands through a web interface. I'm using discord.py as a Discord API wrapper and Quart as a REST framework, because i need to handle asynchronous tasks and Flask doesn't support them.
Right now i have two files:
app.py
import discord
bot = discord.Client(intents=discord.Intents.all())
...
async def play_audio(audio_name, voiceChannel):
vc = await voiceChannel.connect()
print("test")
vc.play(discord.FFmpegPCMAudio(source="audio\{}".format(audio_name), executable=FFMPEG_PATH))
while vc.is_playing():
time.sleep(.1)
await vc.disconnect()
async def get_online_voice_members():
guild = bot.get_guild(NMC_GUILD_ID)
online_voice_users = {}
for voiceChannel in guild.voice_channels:
for user in voiceChannel.members:
online_voice_users[user] = voiceChannel
return online_voice_users
...
api.py
import asyncio
from quart import Quart
import app as discord
QUART_APP = Quart(__name__)
@QUART_APP.before_serving
async def before_serving():
loop = asyncio.get_event_loop()
await discord.bot.login("MY BOT TOKEN")
loop.create_task(discord.bot.connect())
...
@QUART_APP.route("/online_list", methods=["GET"])
async def get_online_members():
resp = {}
members = await discord.get_online_voice_members()
for user in members.keys():
resp[user.id] = {"nick" : user.nick, "channel" : members[user].id}
return resp
@QUART_APP.route("/goodnight", methods=["GET"])
async def send_goodnight():
members = await discord.get_online_voice_members()
for user in members.keys():
if user.id == 12345:
await discord.play_audio("goodnight.mp3", members[user])
break
return {"response":"OK"}
When i make a GET request on the endpoint /online_list everything works fine, but when i make a request on /goodnight, the code successfully runs until reaching the instruction await discord.play_audio("goodnight.mp3, members[user])
, which receives the correct parameters, but it always raises the following exception:
Traceback (most recent call last):
File "G:\Gunther\venv\lib\site-packages\quart\app.py", line 1814, in handle_request
return await self.full_dispatch_request(request_context)
File "G:\Gunther\venv\lib\site-packages\quart\app.py", line 1836, in full_dispatch_request
result = await self.handle_user_exception(error)
File "G:\Gunther\venv\lib\site-packages\quart\app.py", line 1076, in handle_user_exception
raise error
File "G:\Gunther\venv\lib\site-packages\quart\app.py", line 1834, in full_dispatch_request
result = await self.dispatch_request(request_context)
File "G:\Gunther\venv\lib\site-packages\quart\app.py", line 1882, in dispatch_request
return await handler(**request_.view_args)
File "G:/Dati HDD F/GitHub Projects/Gunther/api.py", line 59, in send_buonanotte
await discord.play_audio("goodnight.mp3", members[user])
File "G:\Gunther\app.py", line 55, in play_audio
vc = await voiceChannel.connect()
File "G:\Gunther\venv\lib\site-packages\discord\abc.py", line 1122, in connect
await voice.connect(timeout=timeout, reconnect=reconnect)
File "G:\Gunther\venv\lib\site-packages\discord\voice_client.py", line 352, in connect
self.ws = await self.connect_websocket()
File "G:\Gunther\venv\lib\site-packages\discord\voice_client.py", line 323, in connect_websocket
await ws.poll_event()
File "G:\Gunther\venv\lib\site-packages\discord\gateway.py", line 893, in poll_event
await self.received_message(json.loads(msg.data))
File "G:\Gunther\venv\lib\site-packages\discord\gateway.py", line 825, in received_message
await self.initial_connection(data)
File "G:\Gunther\venv\lib\site-packages\discord\gateway.py", line 849, in initial_connection
recv = await self.loop.sock_recv(state.socket, 70)
File "C:\Users\Kyles\AppData\Local\Programs\Python\Python38\lib\asyncio\proactor_events.py", line 693, in sock_recv
return await self._proactor.recv(sock, n)
RuntimeError: Task <Task pending name='Task-27' coro=<ASGIHTTPConnection.handle_request() running at G:\Gunther\venv\lib\site-packages\quart\asgi.py:70> cb=[_wait.<locals>._on_completion() at C:\Users\Kyles\AppData\Local\Programs\Python\Python38\lib\asyncio\tasks.py:507]> got Future <_OverlappedFuture pending overlapped=<pending, 0x199c31f0ca0>> attached to a different loop
I guess i'm not understanding correctly how the asyncio library works, as it seems to me that no matter what i try, the line vc = await voiceChannel.connect()
in app.py always ends up running on a different loop than the main one. Is there something i'm missing?
Solution
This is because you initiate the discord client on import (line 3 of app.py). Doing so means that it will use the event loop available at the time of import. However, Quart (and Hypercorn unless told not too) will close the existing loop and create a new one as it starts. It is for this reason that I recommend utilizing the startup functionality for initialization.
To solve this I would wrap your discord commands in a class and initialize it in a startup function. Note that I like storing instances on the app itself (so they can be accessed via the current_app
proxy), this is not necessary though. For example,
app.py
class DiscordClient:
def __init__(self):
self.bot = discord.Client(intents=discord.Intents.all())
...
async def get_online_voice_members(self):
guild = self.bot.get_guild(NMC_GUILD_ID)
...
api.py
@QUART_APP.before_serving
async def before_serving():
loop = asyncio.get_event_loop()
QUART_APP.discord_client = DiscordClient()
await QUART_APP.discord_client.bot.login("MY BOT TOKEN")
loop.create_task(QUART_APP.discord_client.bot.connect())
@QUART_APP.route("/online_list", methods=["GET"])
async def get_online_members():
resp = {}
members = await QUART_APP.discord_client.get_online_voice_members()
...
Answered By - pgjones
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.