使用 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 文档中找到任何似乎指向正确方向的 tweepy
或 discord.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_async
和 async_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 将推文发布到特定频道的主要内容,如果未能解决你的问题,请参考以下文章