关于python:使用带有discord.py的tweepy将推文发布到特定频道

Using tweepy with discord.py to post tweets to a specific channel

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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()方法会生成以下消息:

1
RuntimeWarning: coroutine 'Messageable.send' was never awaited

我理解消息-channel.send()方法是一个异步方法,但是on_status()事件处理程序是一个同步方法,我不能在on_status()中使用await channel.send()-但我无法确定了解如何使其工作。我试图使on_status()方法成为异步方法:

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

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

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

我发现了一个问题,如何从tweepy同步on_status?并按照那里的链接进行操作,但是我看不到任何导致我出现编码错误的原因。

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

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

不幸的是,当它尝试执行指示There is no current event loop in thread 'Thread-6'.loop=asyncio.get_event_loop()时,这会导致未处理的异常(因此,即使我\\ n在这一点上还不完全乐观)。

我意识到已经有像MEE6这样的现有Discord机器人已经可以完成我在这里想要做的事情,但是我希望能够在自学一点的同时使它成为"我自己的"机器人关于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帐户,并发布了更新。检查控制台,它按我的预期正确输出了我的tweet的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交互中尝试了另一次迭代。

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

这次,我遇到了与以前不同的未处理异常。当它收到一条新的tweet时,该错误发生在run_until_complete行上:

1
Timeout context manager should be used inside a task

我考虑了这一进展,但是显然我还没有到位,因此回到Google时,我发现asgiref库中包含sync_to_asyncasync_to_sync方法的信息,所以我想出了" d试试看。

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

不幸的是,我得到了与asyncio版本相同的未处理异常。我觉得我马上就知道了,但这还没有"被点击"。

再来一轮

好吧,所以在查找有关我正在了解的Timeout context manager异常的信息之后,我偶然发现了另一个SO问题,给了我一线希望。有关RuntimeError的答案:任务中应再次使用超时上下文管理器,这使我重新回到使用asyncio,并在使用方法。查看推荐的代码,这可以帮助我有效地启动sync-> async方法通信。我实施了建议的更改(为运行bot的Thread对象创建了一个全局变量,添加了在循环中生成bot的" startup "方法,然后更改了on_status中的Discord交互以使全部在一起。

"好消息"是我发布推文时不再遇到任何错误。另外,测试bot命令似乎也可以正常工作。坏消息是它仍然没有将消息发送到频道。由于它不会产生任何错误,因此无法确定消息在何处结束。


我遇到了与您相同的问题,并从Google获得了确切的链接并尝试了所有链接,但正如您所提到的,没有任何作用。

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

这是我代码的要点:

收听者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()

不和谐的客户:

1
2
3
4
5
6
7
8
9
10
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)

希望这会有所帮助。