-1

I would like to create an external GUI in python to control my discord bot. So far I've only seen discord.py being used with listeners and command prefixes. (I'm not very experienced with coroutines)

An example of what I'm trying to do:

async def mute(member):
    await member.edit(mute=True)

while True:
    member = client.fetch_member(int(input('member id: ')))
    mute(member)

Is something like this achievable? How?

Josh Correia
  • 3,807
  • 3
  • 33
  • 50
spaghetsie
  • 49
  • 4
  • Does this answer your question? [Discord.py get user object from id/tag](https://stackoverflow.com/questions/54499864/discord-py-get-user-object-from-id-tag) – Seth Feb 23 '21 at 16:18

1 Answers1

0

It would be achievable, but the command bot.run(my_token) is blocking, it means the execution of the thread stops at that command, so your while True will not be reached until the bot is shutdown.

I will write an example but using the command say instead of ban, since it is easier to test and work with

The first thing to do would be to run the bot in a separate thread to avoid blocking. To do that, simply use this bit of code

threading1 = threading.Thread(target=bot.run, args=[bot_token])
threading1.daemon = True
threading1.start()
loop = asyncio.get_event_loop() # this is needed for the While True

This will start the bot in another thread to avoid blocking. Then we can use the main thread for the while True loop. However since discord commands are coroutines, we cannot simply call the method from the main thread, we need to run the coroutine via asyincio.run_coroutine_threadsafe. This can be done like this :

In Main.py, after starting the bot in a different thread

while True:
    text_to_send = input()
    asyncio.run_coroutine_threadsafe(cog.say(text_to_send), loop)

where cog is the Cog object that you added to the bot via bot.add_cog() that contains the command say

In your cog, create the commmand

@command()
async def say(self, text: str = "hello"):
    guild: Guild = self.bot.get_guild(the_guild_id_to_say)
    channel: TextChannel = guild.get_channel(the_channel_to_say)
    await channel.send(content=text)

With this code, if you replace the_guild_id_to_say with the id of the guild in which you wish to send the message, and the_channel_to_say with the id of the channel in the guild in which you wish to send the message

The only problem with calling a command from the terminal or external means, is that you do not have the Context so you have to find a different way to get the guild/channel of your command. You could write it in the terminal, before the id of the user to ban. I will soon edit my answer to be more complete but this should be more than enough for you to get started

EDIT/UPDATE : Here is a fully working bot (given that you have the required libraries) that can send a message via either //say Hello or by typing 810277254592200737 Hello on the terminal where 810277254592200737 is the id of the channel on which you wish the bot to send the message

Bot.py

import asyncio
from discord.ext.commands import Bot
import threading
import sys

from View.OutsideCommunicationCog import OutsideCommunicationCog


class MyBot(Bot):
    def __init__(self, command_prefix, **options):
        super().__init__(command_prefix, **options)


def get_channel_id_and_text_from_input(text_to_parse):
    input_as_list = text_to_parse.split()
    channel_id = int(input_as_list[0])
    text_to_send = " ".join(input_as_list[1:])
    return channel_id, text_to_send


if __name__ == '__main__':
    bot_token = sys.argv[1]
    bot = MyBot(command_prefix="//")

    bot_run_thread = threading.Thread(target=bot.run, args=[bot_token])
    bot_run_thread.daemon = True
    bot_run_thread.start()

    cogs = [OutsideCommunicationCog(bot)]
    outside_communication_cog = cogs[0]
    for cog in cogs:
        bot.add_cog(cog)

    loop = asyncio.get_event_loop()
    while True:
        raw_input = input()
        c_id, text = get_channel_id_and_text_from_input(raw_input)
        print(c_id, text)

        asyncio.run_coroutine_threadsafe(outside_communication_cog.say(None, message=text, channel_id=c_id), loop)

OutsideCommunicationCog.py

from typing import Optional

from discord import TextChannel
from discord.ext.commands import command, Context, Bot, Cog


class OutsideCommunicationCog(Cog):
    bot: Bot

    def __init__(self, bot):
        self.bot = bot

    @command()
    async def say(self, ctx: Optional[Context], message: str, *, channel_id=None):
        channel: TextChannel = self.bot.get_channel(channel_id) if channel_id is not None else ctx.channel
        if ctx is None:
            if channel_id is None:
                print("No information to communicate")

        await channel.send(content=message)

References : How to use threading to get user input realtime while main still running in python

RuntimeError: Timeout context manager should be used inside a task

https://docs.python.org/3/library/asyncio-task.html#asyncio.run_coroutine_threadsafe

Nathan Marotte
  • 771
  • 1
  • 5
  • 15