Discord.py bot + Quart:尝试连接到语音通道总是会出现 "task got future attached to a different loop"
Discord.py bot + Quart: Trying to connect to a voice channel always gives a "task got future attached to a different loop"
我一直在尝试创建一个可以通过网络界面接收命令的 discord 机器人。我使用 discord.py 作为 Discord API 包装器,使用 Quart 作为 REST 框架,因为我需要处理异步任务,而 Flask 不支持它们。
现在我有两个文件:
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"}
当我在端点 /online_list 上发出 GET 请求时一切正常,但是当我在 /goodnight 上发出请求时,代码成功运行直到到达指令 await discord.play_audio("goodnight.mp3, members[user])
,它接收正确的参数,但它总是引发以下异常:
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
我想我没有正确理解 asyncio 库的工作原理,因为在我看来,无论我尝试什么,app.py 中的 vc = await voiceChannel.connect()
行总是以 运行 在与主循环不同的循环中。有什么我想念的吗?
这是因为您在导入时启动了 discord 客户端(app.py 的第 3 行)。这样做意味着它将使用导入时可用的事件循环。然而,Quart(和 Hypercorn,除非被告知不要)将关闭现有循环并在它开始时创建一个新循环。出于这个原因,我建议使用 startup 功能进行初始化。
为了解决这个问题,我会将您的 discord 命令包装在 class 中,并在启动函数中对其进行初始化。请注意,我喜欢将实例存储在应用程序本身上(因此可以通过 current_app
代理访问它们),但这不是必需的。例如,
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()
...
我一直在尝试创建一个可以通过网络界面接收命令的 discord 机器人。我使用 discord.py 作为 Discord API 包装器,使用 Quart 作为 REST 框架,因为我需要处理异步任务,而 Flask 不支持它们。
现在我有两个文件:
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"}
当我在端点 /online_list 上发出 GET 请求时一切正常,但是当我在 /goodnight 上发出请求时,代码成功运行直到到达指令 await discord.play_audio("goodnight.mp3, members[user])
,它接收正确的参数,但它总是引发以下异常:
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
我想我没有正确理解 asyncio 库的工作原理,因为在我看来,无论我尝试什么,app.py 中的 vc = await voiceChannel.connect()
行总是以 运行 在与主循环不同的循环中。有什么我想念的吗?
这是因为您在导入时启动了 discord 客户端(app.py 的第 3 行)。这样做意味着它将使用导入时可用的事件循环。然而,Quart(和 Hypercorn,除非被告知不要)将关闭现有循环并在它开始时创建一个新循环。出于这个原因,我建议使用 startup 功能进行初始化。
为了解决这个问题,我会将您的 discord 命令包装在 class 中,并在启动函数中对其进行初始化。请注意,我喜欢将实例存储在应用程序本身上(因此可以通过 current_app
代理访问它们),但这不是必需的。例如,
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()
...