使用 tweepy 和 discord.py 将推文发布到特定频道

Posted

技术标签:

【中文标题】使用 tweepy 和 discord.py 将推文发布到特定频道【英文标题】:Using tweepy with discord.py to post tweets to a specific channel 【发布时间】:2020-08-15 12:43:01 【问题描述】:

因此,我正在尝试结合使用教程、文档和在线示例来自学一点 Python,以构建一个 Discord 机器人,该机器人实现了一个或多个现有机器人的某些功能。其中一个功能是将一组 Twitter 帐户中的(新)推文发布到我的 Discord 服务器上的特定频道。我发现了几段代码,我拼凑在一起从 Twitter 流中读取这些代码,并在这里和那里“调整”了一些东西来尝试实现这一点。

但是,我一直遇到一个问题,实际上是让on_status 代码正确执行。我尝试了多种方法来使其正常工作,但我尝试的所有方法都会导致某种错误。以下是我一直在测试的最新迭代中的相关代码(已编辑):

import discord
import tweepy

from discord.ext import commands
from tweepy import Stream
from tweepy.streaming import StreamListener

class TweetListener(StreamListener):
    def on_status(self, status):
        if status.in_reply_to_status_id is None:
            TweetText = status.text

            for DGuild in MyBot.guilds:
                for DChannel in DGuild.text_channels:
                    if DChannel.name == 'testing':
                        TwitterEmbed = discord.Embed(title='New Tweet', description='New Tweet from my timeline.', color=0xFF0000)
                        TwitterEmbed.set_author(name='@TwitterHandle', icon_url=bot.user.default_avatar_url)
                        DChannel.send(TweetText, embed = TwitterEmbed)

DISCORD_TOKEN = 'dtoken'
TWITTER_CONSUMER_KEY = 'ckey'
TWITTER_CONSUMER_SECRET = 'csecret'
TWITTER_ACCESS_TOKEN = 'atoken'
TWITTER_ACCESS_SECRET = 'asecret'

MyBot = commands.Bot(command_prefix='!', description='This is a testing bot')
TwitterAuth = tweepy.OAuthHandler(TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET)
TwitterAuth.set_access_token(TWITTER_ACCESS_TOKEN, TWITTER_ACCESS_SECRET)
TweetAPI = tweepy.API(TwitterAuth)
NewListener = TweetListener()
NewStream = tweepy.Stream(auth=TweetAPI.auth, listener=NewListener)
NewStream.filter(follow=['USER IDS HERE'], is_async=True)

@bot.event
async def on_ready():
    print(MyBot.user.name,'has successfully logged in ('+str(MyBot.user.id)+')')
    print('Ready to respond')

MyBot.run(DISCORD_TOKEN)

当我将推文发布到我的测试帐户的时间线时,channel.send() 方法会生成以下消息:

RuntimeWarning: coroutine 'Messageable.send' was never awaited

我理解消息 - channel.send() 方法是异步方法,但 on_status() 事件处理程序是同步方法,我不能在 on_status() 中使用 await channel.send() - 但我不知道如何让它工作。我尝试将on_status() 方法设为异步方法:

    async def on_status(self, status):
        <same code for checking the tweet and finding the channel>
        await DChannel.send(TweetText, embed = TwitterEmbed)

但这总是会导致类似的警告:

RuntimeWarning: coroutine 'TweetListener.on_status' was never awaited
if self.on_status(status) is False:

我找到了问题,How do I async on_status from tweepy? 并点击了那里的链接,但我没有看到任何让我知道我的编码错误的东西。

通过进一步的研究,我还尝试使用来自asyncio 库的一些调用来进行调用:

    #channel.send(message, embed = TwitterEmbed)
    #---ASYNCIO TEST---
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.gather(DChannel.send(embed=TwitterEmbed)))
    loop.close()

不幸的是,当它尝试执行 loop=asyncio.get_event_loop() 指示 There is no current event loop in thread 'Thread-6'. 时,这会导致未处理的异常(因此它甚至无法使用 run_until_complete() 方法来查看 that 是否有效,虽然我在这一点上并不完全乐观)。

我意识到现有的 Discord 机器人(如 MEE6)已经可以完成我在这里尝试做的事情,但我希望能够让这个机器人成为“我自己的”机器人,同时自学一些 Python 知识。我可能在这里忽略了一些简单的事情,但我无法在 API 文档中找到任何似乎指向正确方向的 tweepydiscord.py 以及我的 Google- fu 显然没那么强。到目前为止,我能够找到的所有示例似乎都已过时,因为它们似乎引用了已弃用的方法和其中一个或两个库的旧版本。还有其他事情我仍然需要弄清楚如何去做(例如,获取 @TwitterHandle 以正确填充嵌入),但我必须让它能够阅读和在我担心到达那个点之前推送推文。


环境信息

Visual Studio 2017 CE

Python 3.6

discord.py 1.3.3

tweepy 3.8.0


更新:附加测试

所以,为了向自己证明我的 TweetListener 课程确实有效,我继续在 on_status 方法中注释掉与 Discord 的所有交互(for Guild in MyBot.guilds 到结尾的所有内容的方法),然后添加一个简单的print(TweetText)。我再次运行代码,登录到我为这个项目拥有的测试 Twitter 帐户,并发布了更新。检查控制台,它正确地输出了我的推文的status.text,正如我所期望的那样。正如我所想,我在这里遇到的唯一问题是尝试将 send 该文本发送到 Discord。

我还尝试在同步模式而不是异步模式下初始化tweepy.Stream.filter - NewStream.filter(follow=['USER IDS'])。这只会导致我的应用程序在启动时基本上“挂起”。我意识到这是由于它在代码中的位置,所以我将TweetListener 初始化的所有三行移至on_ready 事件方法。我将 Discord 代码注释掉并进行了测试,它再次“起作用”,因为它将另一条测试推文的status.text 打印到控制台。但是,机器人不会响应任何其他内容(我假设仍在“等待”流)。

即便如此,只是为了看看它是否会有所不同,我继续取消了 Discord 交互代码的注释并再次尝试。结果与我最初的结果相同,只是这次 Discord 机器人不会响应频道中的活动(在代码的其他地方有几个超级简单的 @bot.command 声明),这又一次基本上让我回到原来的代码,遇到同样的问题。

我确信有更好的方法来编写整个代码。正如我上面所说,我现在才开始玩 Python 并且还在学习语法规则等。即便如此,我仍然不确定自己做错了什么和/或忽略了这使我无法完成我认为相当简单的任务。


更多测试

我回去又找到了一些asyncio 的示例,并在on_status 的 Discord 交互中尝试了另一个迭代:

    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    result=loop.run_until_complete(DChannel.send(embed=TwitterEmbed))

这一次我得到了一个 不同 未处理的异常,而不是我以前得到的。 run_until_complete 行在获取新推文时出现错误:

Timeout context manager should be used inside a task

我考虑了这一进展,但显然我还没有完全做到这一点,所以回到谷歌,我找到了关于 asgiref 库及其 sync_to_asyncasync_to_sync 方法的信息,所以我想我会给出试试看。

    asgiref.sync.async_to_sync(DChannel.send)(embed=TwitterEmbed)

不幸的是,我得到了与asyncio 版本相同的未处理异常。我觉得我正处于解决这个问题的边缘,但它还没有“点击”。


又一轮

好的,所以在查找了我上面提到的 Timeout context manager 异常的信息后,我偶然发现了另一个 SO 问题,这给了我一线希望。 RuntimeError: Timeout context manager should be used inside a task 上的这个答案再次让我回到使用 asyncio 并在使用 asyncio.run_coroutine_threadsafe 方法提供有用建议之前对 OP 问题背后的原因进行了简要但描述性的解释。查看推荐的代码,这可以帮助我有效地启动 sync->async 方法通信是有道理的。我实现了建议的更改(为运行机器人的Thread对象创建了一个全局变量,添加了一个在该循环中生成机器人的“启动”方法,然后更改了on_status中的Discord交互以实现所有功能在一起。

“好消息”是我在发布推文时不再收到任何错误。此外,测试 bot 命令似乎也可以正常工作。坏消息是它仍然没有将消息发送到频道。因为它没有产生任何错误,所以不知道消息在哪里结束。

【问题讨论】:

【参考方案1】:

我遇到了与您相同的问题,并从谷歌获得了确切的链接并尝试了所有这些链接,但正如您所提到的,没有任何效果。

因此,经过大量尝试和修改后,我意识到,我可以将主循环传递给 tweepy 侦听器并使用 run_coroutine_threadsafe 执行异步函数。

这是我的代码的要点:

听者:

class EpicListener(tweepy.StreamListener):
    def __init__(self, discord, loop, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.discord = discord # this is just a function which sends a message to a channel
        self.loop = loop # this is the loop of discord client

    def on_status(self, status):
        self.send_message(status._json)

    def send_message(self, msg):
        # Submit the coroutine to a given loop
        future = asyncio.run_coroutine_threadsafe(self.discord(msg), self.loop)
        # Wait for the result with an optional timeout argument
        future.result()

不和谐的客户:

class MyClient(discord.Client):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    async def on_ready(self):
        myStream = tweepy.Stream(
                    auth=api.auth, listener=EpicListener(discord=self.sendtwitter, loop=asyncio.get_event_loop())
                )
        myStream.filter(follow=['mohitwr'], is_async=True)
        print(myStream)

希望这会有所帮助。

【讨论】:

谢谢!有时间我会看看这个。看起来这只是重载 StreamListener 类的初始化程序的问题。我想如果我知道更多关于如何在 Python 中做到这一点,我可能会考虑到这一点,但现在我看起来很愚蠢。 ;)

以上是关于使用 tweepy 和 discord.py 将推文发布到特定频道的主要内容,如果未能解决你的问题,请参考以下文章

discord.py,同时使用斜杠命令和前缀

调整图像大小 discord.py

如何使用 discord.py 创建和分配角色?

我正在尝试使用 PIL 和 discord.py 更新排名

Discord.py - 使用命令更改前缀

如何使用 discord.py 获取所有文本频道?