因此,我试图通过教程、文档和在线示例的组合来自学一些Python,以构建一个Discord机器人,实现一个或多个现有机器人的一些功能。其中一个功能是从某组推特账户向我的Discord服务器上的特定频道发布(新)推文。我发现了一些零碎的代码,这些代码是我拼凑在一起从推特流中读取的;调整的";这里和那里的一些事情来尝试实现这一点。
然而,在实际使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:
我发现了一个问题,如何从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()
不幸的是,当它试图执行指示There is no current event loop in thread 'Thread-6'.
的loop=asyncio.get_event_loop()
时,这会导致一个未处理的异常(因此它甚至不会访问run_until_complete()
方法来查看是否有效,尽管我在这一点上并不乐观)。
我意识到有像MEE6这样的现有Discord机器人已经可以做我在这里尝试做的事情,但我希望能够让这个机器人";我自己的";同时教自己一些关于Python的知识。我可能忽略了这里的一些简单内容,但我在API文档中找不到任何tweepy
或discord.py
的内容,这些内容似乎为我指明了正确的方向,而且我的Google-fu显然没有那么强。到目前为止,我能找到的所有示例似乎都已经过时了,因为它们似乎引用了不推荐使用的方法和其中一个或两个库的旧版本。还有其他事情我还需要弄清楚如何做(例如,让@TwitterHandle
正确填充嵌入),但我必须让它能够阅读和推送推文,然后才能担心达到这一点。
环境信息
Visual Studio 2017 CE
Python 3.6
不一致.py 1.3.3
花呢3.8.0
更新:附加测试
因此,为了向自己证明我的TweetListener
类确实在工作,我继续评论了on_status
方法中与Discord的所有交互(从for Guild in MyBot.guilds
到方法末尾的所有内容),然后添加了一个简单的print(TweetText)
。我再次运行了代码,登录到我为这个项目拥有的测试推特帐户,并发布了更新。检查控制台,它正确地输出了我推特的status.text
,正如我所期望的那样。正如我所想,我在这里遇到的唯一问题是试图将该文本send
发送到Discord。
我还尝试在同步模式下初始化tweepy.Stream.filter
,而不是异步NewStream.filter(follow=['USER IDS'])
。这只导致我的申请基本上是";悬挂;启动时。我意识到这是由于它在代码中的位置,所以我将TweetListener
初始化的所有三行都移到了on_ready
事件方法中。我把Discord代码注释掉并测试了一遍;工作";因为它将另一条测试推文的CCD_ 25打印到控制台。然而,机器人不会对其他任何事情做出响应(我想它仍然在流中"等待")。
即便如此,只是想看看它是否会有什么不同,我还是取消了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_async
和async_to_sync
方法的信息,所以我想我应该试一试。
asgiref.sync.async_to_sync(DChannel.send)(embed=TwitterEmbed)
不幸的是,我得到了与asyncio
版本相同的未处理异常。我觉得我正处于弄清楚这一点的边缘,但它根本没有"点击";然而
另一轮
好吧,在查找了关于我上面得到的Timeout context manager
异常的信息后,我偶然发现了另一个so问题,这给了我一线希望。这个关于RuntimeError的回答:应该在任务中使用超时上下文管理器,这让我再次回到使用asyncio
,并对OP问题背后的原因进行了简短但描述性的解释,然后使用asyncio.run_coroutine_threadsafe
方法提供了有用的建议。查看推荐的代码,这可以帮助我启动同步->异步方式通信有效。我实现了建议的更改(为运行机器人的Thread
对象创建了一个全局变量,添加了一个在该循环中生成机器人的"启动"方法,然后更改了on_status
中的Discord交互,将其整合在一起。
";好消息";当我发布推特时,我不再收到任何错误。此外,测试bot命令似乎也很好。坏消息是,它仍然没有将消息发送到该频道。由于它没有产生任何错误,因此不知道消息的最终位置。
我遇到了和你一样的问题,从谷歌上得到了确切的链接,并尝试了所有链接,但正如你所提到的,没有任何效果。
因此,经过大量的尝试和修补,我意识到,我可以将主循环传递给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)
希望这能有所帮助。