Discord.py бот Кварт: попытка подключиться к голосовому каналу всегда выдает сообщение «задача в будущем привязана к другому циклу».

#python #discord.py #python-asyncio #quart

#python #discord.py #python-asyncio #quart

Вопрос:

я пытался создать discord-бота, который может получать команды через веб-интерфейс. Я использую discord.py в качестве оболочки API Discord и 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"}
  

Когда я делаю запрос GET в endpoint / online_list, все работает нормально, но когда я делаю запрос в / goodnight, код успешно выполняется до тех пор, пока не будет достигнута инструкция await discord.play_audio("goodnight.mp3, members[user]) , которая получает правильные параметры, но всегда вызывает следующее исключение:

 Traceback (most recent call last):
  File "G:Gunthervenvlibsite-packagesquartapp.py", line 1814, in handle_request
    return await self.full_dispatch_request(request_context)
  File "G:Gunthervenvlibsite-packagesquartapp.py", line 1836, in full_dispatch_request
    result = await self.handle_user_exception(error)
  File "G:Gunthervenvlibsite-packagesquartapp.py", line 1076, in handle_user_exception
    raise error
  File "G:Gunthervenvlibsite-packagesquartapp.py", line 1834, in full_dispatch_request
    result = await self.dispatch_request(request_context)
  File "G:Gunthervenvlibsite-packagesquartapp.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:Guntherapp.py", line 55, in play_audio
    vc = await voiceChannel.connect()
  File "G:Gunthervenvlibsite-packagesdiscordabc.py", line 1122, in connect
    await voice.connect(timeout=timeout, reconnect=reconnect)
  File "G:Gunthervenvlibsite-packagesdiscordvoice_client.py", line 352, in connect
    self.ws = await self.connect_websocket()
  File "G:Gunthervenvlibsite-packagesdiscordvoice_client.py", line 323, in connect_websocket
    await ws.poll_event()
  File "G:Gunthervenvlibsite-packagesdiscordgateway.py", line 893, in poll_event
    await self.received_message(json.loads(msg.data))
  File "G:Gunthervenvlibsite-packagesdiscordgateway.py", line 825, in received_message
    await self.initial_connection(data)
  File "G:Gunthervenvlibsite-packagesdiscordgateway.py", line 849, in initial_connection
    recv = await self.loop.sock_recv(state.socket, 70)
  File "C:UsersKylesAppDataLocalProgramsPythonPython38libasyncioproactor_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:Gunthervenvlibsite-packagesquartasgi.py:70> cb=[_wait.<locals>._on_completion() at C:UsersKylesAppDataLocalProgramsPythonPython38libasynciotasks.py:507]> got Future <_OverlappedFuture pending overlapped=<pending, 0x199c31f0ca0>> attached to a different loop
  

Наверное, я неправильно понимаю, как работает библиотека asyncio, поскольку мне кажется, что независимо от того, что я пытаюсь, строка vc = await voiceChannel.connect() в app.py всегда заканчивается запуском в другом цикле, отличном от основного. Я чего-то не хватает?

Ответ №1:

Это потому, что вы инициируете клиент discord при импорте (строка 3 из app.py ). Это означает, что он будет использовать цикл событий, доступный во время импорта. Однако Quart (и Hypercorn, если не указано иное) закроют существующий цикл и создадут новый при его запуске. Именно по этой причине я рекомендую использовать функцию запуска для инициализации.

Чтобы решить эту проблему, я бы обернул ваши команды discord в класс и инициализировал его в функции запуска. Обратите внимание, что мне нравится хранить экземпляры в самом приложении (чтобы к ним можно было получить доступ через 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()
    ...