Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Need to await a function inside a sync function

I tried solving this problem for 2 days already, read like 50 questions in StackOverflow and a lot of documentation about Python. I don't know what else to try.

My Code

import discord
import re
from datetime import datetime
import mysql.connector
from discord.ext import commands, tasks
from discord.utils import get
import atexit
from pynput import keyboard
import asyncio
from asgiref.sync import async_to_sync, sync_to_async

# The currently active modifiers
current = set()

# The key combination to check
COMBINATIONS_MUTE = [
    {keyboard.Key.shift, keyboard.KeyCode(char='a')},
    {keyboard.Key.shift, keyboard.KeyCode(char='A')}
]


COMBINATIONS_UNMUTE = [
    {keyboard.Key.shift, keyboard.KeyCode(char='b')},
    {keyboard.Key.shift, keyboard.KeyCode(char='B')}
]



GUILD_ID=guild_id
CHANNELS = ""
GUILD = ""
VOICE_CHANNEL = ""

client = commands.Bot(command_prefix = ".")

def get_channel(name):
    global CHANNELS
    for channel in CHANNELS:
        if (channel.name == name) and (channel.type.name == "voice"):
            return channel
    return False

def get_voice_channels():
    global CHANNELS
    channels = []
    for channel in CHANNELS:
        if (channel.type.name == "voice"):
            channels.append(channel)
    return channels

async def mute_all():
    global VOICE_CHANNELS
    print("mute all")
    for channel in VOICE_CHANNELS:
        for member in channel.members:
            await member.edit(mute=True)

async def unmute_all():
    global VOICE_CHANNELS
    print("unmute all")
    for channel in VOICE_CHANNELS:
        for member in channel.members:
            await member.edit(mute=False)


def on_press(key):
    if any([key in COMBO for COMBO in COMBINATIONS_MUTE]) and not key in current:
        current.add(key)
        if any(all(k in current for k in COMBO) for COMBO in COMBINATIONS_MUTE):
            asyncio.run(mute_all())
    elif any([key in COMBO for COMBO in COMBINATIONS_UNMUTE]):
        current.add(key)
        if any(all(k in current for k in COMBO) for COMBO in COMBINATIONS_UNMUTE):
            asyncio.run(unmute_all())

def on_release(key):
    if any([key in COMBO for COMBO in COMBINATIONS_MUTE]):
        current.remove(key)
    elif any([key in COMBO for COMBO in COMBINATIONS_UNMUTE]):
        current.remove(key)


@client.event
async def on_ready():
    global GUILD
    global CHANNELS
    global VOICE_CHANNELS
    global GUILD_ID

    GUILD = client.get_guild(GUILD_ID)

    CHANNELS = GUILD.channels

    VOICE_CHANNELS = get_voice_channels()
    print("bot ready")
    loop = asyncio.get_event_loop()
    with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
        listener.join()

client.run('token')

Basically what it should do is:

  1. When I press Shift+A mute everyone in every voice channel.
  2. When I press Shitf+B unmute everyone in every voice channel.

The problem is that I need to await the member.edit() but I can't because I can't await mute_all() nor unmute_all() because on_press(key) is not async and I can't make it async because the keyboard_listener won't let me.

What I tried (none worked)

  1. Using asyncio.run() on member.edit().
  2. Using asyncio.run() on mute_all().
  3. Making on_press(key) async.
  4. Using async_to_sync() on member.edit().
  5. Using async_to_sync() on mute_all().

I don't know what else to try.

like image 390
Carlos86 Avatar asked Nov 07 '22 05:11

Carlos86


1 Answers

keyboard.Listener is not async so it will block the event loop in on_ready, you need to init the listener in an executor, in this case we can just use the default one.

Here is all I changed to get this bit working:

def keyboard_listener():
    with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
        listener.join()

@client.event
async def on_ready():
    global GUILD
    global CHANNELS
    global VOICE_CHANNELS
    global GUILD_ID

    GUILD = client.get_guild(GUILD_ID)

    CHANNELS = GUILD.channels

    VOICE_CHANNELS = get_voice_channels()
    print("bot ready")

    loop.run_in_executor(None, keyboard_listener)


loop = asyncio.get_event_loop()

I then replaced all your asyncio.run with loop.create_task

I have a functioning version of this if my instructions are unclear feel free to ask questions.

Going from async to sync back to async is pretty gross but hey it works so it's not dumb I guess

I also changed your mute_all and unmute_all slightly, as your current implementation will only affect voice users that are in the channels when the bot initially connects. Here is an example of my change:

async def mute_all():
    print("mute all")
    for channel in client.get_guild(GUILD_ID).channels:
        if not channel.type.name == "voice":
            continue
        for member in channel.members:
            await member.edit(mute=True)
like image 150
unex Avatar answered Nov 14 '22 22:11

unex