【问题标题】:How to send a message with discord.py from outside the event loop (i.e. from python-telegram-bot thread)?如何从事件循环外部(即从 python-telegram-bot 线程)发送带有 discord.py 的消息?
【发布时间】:2018-12-11 10:44:50
【问题描述】:

我想使用库 python-telegram-bot 和 discord.py(版本 1.0.0)制作一个在 discord 和 telegram 之间进行通信的机器人。然而问题是 discord.py 使用异步函数和 python-telegram-bot 线程。使用下面的代码,在不和谐中发布的消息一切正常(机器人将它们正确地发送到电报),但是反过来不起作用(机器人从电报中获取消息并将其发送到不和谐)。我以前在尝试在同步函数中运行不和谐channel.send 函数时遇到语法/运行时错误问题(因此要么只返回一个生成器对象,要么抱怨我不能在同步函数中使用await)。然而,与此同时,python-telegram-bot 的MessageHandler 需要一个同步函数,所以当给他一个异步函数时,Python 抱怨说,异步函数从未调用过“await”。 我现在尝试使用 asgiref 库中的 async_to_sync 方法从 MessageHandler 运行我的异步 broadcastMsg,但是代码仍然没有将消息发送到不和谐!它似乎正确调用了该函数,但仅在行 print('I get to here') 之前。没有显示错误,也没有不和谐的消息弹出。我想这与我必须将函数注册为 discord.py 事件循环中的任务这一事实有关,但是注册仅在它发生在 botDiscord.run(TOKENDISCORD) 执行之前才有效,这当然必须在之前发生. 所以把我的问题归结为一个问题: 我如何能够从另一个线程(来自电报MessageHandler)与 discord.py 事件循环进行交互。或者如果这是不可能的:如何在不进入 discord.py 事件循环的情况下使用 discord.py 发送消息?

感谢您的帮助

import asyncio
from asgiref.sync import async_to_sync
from telegram import Message as TMessage
from telegram.ext import (Updater,Filters,MessageHandler)
from discord.ext import commands
import discord

TChannelID = 'someTelegramChannelID'
DChannel = 'someDiscordChannelObject'

#%% define functions / commands
prefix = "?"
botDiscord = commands.Bot(command_prefix=prefix)
discordChannels = {}


async def broadcastMsg(medium,channel,message):
    ''' 
    Function to broadcast a message to all linked channels.
    '''
    if isinstance(message,TMessage):
        fromMedium = 'Telegram'
        author = message.from_user.username
        channel = message.chat.title
        content = message.text
    elif isinstance(message,discord.Message):
        fromMedium = 'Discord'
        author = message.author
        channel = message.channel.name
        content = message.content
    # check where message comes from        
    textToSend = '%s wrote on %s %s:\n%s'%(author,fromMedium,channel,content)
    # go through channels and send the message
    if 'telegram' in medium:
        # transform channel to telegram chatID and send
        updaterTelegram.bot.send_message(channel,textToSend)

    elif 'discord' in medium:
        print('I get to here')
        await channel.send(textToSend)

    print("I do not get there")


@botDiscord.event
async def on_message(message):
    await broadcastMsg('telegram',TChannelID,message)

def on_TMessage(bot,update):
    # check if this chat is already known, else save it
    # get channels to send to and send message
    async_to_sync(broadcastMsg)('discord',DChannel,update.message)


#%% initialize telegram and discord bot and run them
messageHandler = MessageHandler(Filters.text, on_TMessage)
updaterTelegram = Updater(token = TOKENTELEGRAM, request_kwargs={'read_timeout': 10, 'connect_timeout': 10})
updaterTelegram.dispatcher.add_handler(messageHandler)
updaterTelegram.start_polling()
botDiscord.run(TOKENDISCORD)  

【问题讨论】:

    标签: python-3.x python-asyncio discord.py python-telegram-bot


    【解决方案1】:

    如何在不进入 discord.py 事件循环的情况下使用 discord.py 发送消息?

    要从事件循环线程外部安全地调度协程,请使用asyncio.run_coroutine_threadsafe

    _loop = asyncio.get_event_loop()
    
    def on_TMessage(bot, update):
        asyncio.run_coroutine_threadsafe(
            broadcastMsg('discord', DChannel, update.message), _loop)
    

    【讨论】:

    • 所以这行得通,但问题是,如果你在一个不断运行的脚本中有这个。在您“结束”当前脚本之前,这些消息永远不会被发送。然后发送所有消息。就像他们都被扔进一个队列,只有在你完成脚本后才会执行。我怎样才能解决这个问题,因为我有一个永远运行的常量脚本,看着东西。
    • @Frank 我不确定你的脚本做了什么,但这个答案假设 asyncio 事件循环实际上 running 在不同的线程中。 (在问题的代码中,事件循环在主线程中运行,on_TMessage 在某个后台线程中被 Telegram API 调用)。如果不是这样,asyncio.run_coroutine_threadsafe() 将无用。您的问题可能是一个单独问题的材料,该问题应包括脚本架构等详细信息,如果可能的话,还应包括一个重现问题的最小/可运行示例。
    • 感谢解释(疯狂的用户名)哈哈!请您帮我看一下这个问题,如果没有意义,我可以编辑问题stackoverflow.com/questions/64368729/…
    【解决方案2】:

    您可以尝试将它们拆分为 2 个单独的 *.py 文件。

    t2d.py #telegram 不和谐

    import subprocess
    from telethon import TelegramClient, events, sync
    
    api_id = '...'
    api_hash = '...'
    
    with TelegramClient('name', api_id, api_hash) as client:     
        @client.on(events.NewMessage()) #inside .NewMessage() you can put specific channel like: chats="test_channel"
        async def handler(event):        
            print('test_channel raw text: ', event.raw_text) #this row is not necessary
    
            msg = event.raw_text
    
            subprocess.call(["python", "s2d.py", msg])
    
        client.run_until_disconnected()
    

    s2d.py #发送到不和谐

    import discord, sys
    
    my_secret = '...'
    
    clientdiscord = discord.Client()
    
    @clientdiscord.event
    async def on_ready():
        #print('We have logged in as {0.user}'.format(clientdiscord)) #this row is not necessary
    
        channel = clientdiscord.get_channel(123456789) # num of channel where you want to write message
    
        msg = sys.argv[1] #grab message
        
        msg = 's2d: ' + msg #only for test, you can delete this row 
        
        await channel.send(msg)
    
        quit() # very important quit this bot 
    
        
    clientdiscord.run(my_secret)
    

    会慢一点(子进程会造成延迟),但很容易解决

    【讨论】:

      猜你喜欢
      • 2018-09-21
      • 1970-01-01
      • 2018-04-20
      • 1970-01-01
      • 2015-05-14
      • 2021-04-01
      • 1970-01-01
      • 2023-03-14
      • 1970-01-01
      相关资源
      最近更新 更多