我可以使用外部方式控制 discord.py 机器人吗?

Posted

技术标签:

【中文标题】我可以使用外部方式控制 discord.py 机器人吗?【英文标题】:Can I control a discord.py bot using external means? 【发布时间】:2021-05-25 21:25:26 【问题描述】:

我想在 python 中创建一个外部 GUI 来控制我的不和谐机器人。到目前为止,我只看到 discord.py 与侦听器和命令前缀一起使用。 (我对协程不是很有经验)

我正在尝试做的一个例子:

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

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

这样的事情可以实现吗?怎么样?

【问题讨论】:

这能回答你的问题吗? Discord.py get user object from id/tag 【参考方案1】:

这是可以实现的,但是命令 bot.run(my_token) 正在阻塞,这意味着线程的执行在该命令处停止,因此在机器人关闭之前不会达到您的 while True。

我会写一个例子,但使用命令say而不是ban,因为它更容易测试和使用

首先要做的是在单独的线程中运行机器人以避免阻塞。为此,只需使用这段代码

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

这将在另一个线程中启动机器人以避免阻塞。然后我们可以将主线程用于 while True 循环。但是由于不和谐命令是协程,我们不能简单地从主线程调用该方法,我们需要通过asyincio.run_coroutine_threadsafe运行协程。可以这样做:

在 Main.py 中,在不同线程中启动机器人后

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

其中 cog 是您通过 bot.add_cog() 添加到机器人的 Cog 对象,其中包含命令 say

在你的 cog 中,创建命令

@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)

使用此代码,如果您将the_guild_id_to_say 替换为您希望发送消息的公会ID,并将the_channel_to_say 替换为您希望发送消息的公会中的频道ID

从终端或外部方式调用命令的唯一问题是您没有Context,因此您必须找到不同的方法来获取命令的公会/频道。您可以在终端中将其写入要禁止的用户 ID 之前。我将很快编辑我的答案以使其更完整,但这应该足以让您开始

编辑/更新: 这是一个完全正常工作的机器人(假设您有所需的库),它可以通过//say Hello 或在终端上键入810277254592200737 Hello 发送消息,其中810277254592200737 是您希望机器人所在频道的ID发送消息

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)

参考资料: 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

【讨论】:

以上是关于我可以使用外部方式控制 discord.py 机器人吗?的主要内容,如果未能解决你的问题,请参考以下文章

Discord.py - 机器人不响应命令

是否可以使用我的机器人在 discord.py 中附加大于 150Mb 的文件

如何在 discord.py 机器人中正确使用任务/事件循环?

Discord.py - 我应该选择哪种方式? [关闭]

权限检查 Discord.py 机器人

我无法使用 discord.py 向我的 discord 机器人添加命令