title: Using tweepy with discord.py to post tweets to a specific channel
body: <p>So, I'm trying to teach myself a bit of Python using a combination of tutorials, documentation, and online examples to build a Discord bot that implements some features of one or more existing bots. One of those features is posting (new) tweets from a certain set of Twitter accounts to a specific channel on my Discord server. I've found several bits and pieces of code that I've cobbled together to read from a Twitter stream and &quot;tweaked&quot; a few things here and there to try to accomplish this.</p>
  <p>However, I keep running into an issue with actually getting the <code>on_status</code> code to execute properly. I've tried a variety of ways to get it working, but everything I've tried results in some sort of error. Below is the pertinent code (redacted) from the most recent iteration that I've been testing:</p>
  <pre class="lang-py prettyprint-override"><code>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)
  </code></pre>
  <p>The <code>channel.send()</code> method generates the following message when I post a tweet to my testing account's timeline:</p>
  <pre><code>RuntimeWarning: coroutine 'Messageable.send' was never awaited
  </code></pre>
  <p>I understand the message - the <code>channel.send()</code> method is an asynchronous method, but the <code>on_status()</code> event handler is a synchronous method and I can't <code>await channel.send()</code>  inside <code>on_status()</code> - but I can't figure out how to make it work. I tried to make the <code>on_status()</code> method an asynchronous method:</p>
  <pre class="lang-py prettyprint-override"><code>    async def on_status(self, status):
          &lt;same code for checking the tweet and finding the channel&gt;
          await DChannel.send(TweetText, embed = TwitterEmbed)
  </code></pre>
  <p>but this always resulted in a similar warning:</p>
  <pre><code>RuntimeWarning: coroutine 'TweetListener.on_status' was never awaited
  if self.on_status(status) is False:
  </code></pre>
  <p>I found the question, <strong><a href="https://stackoverflow.com/q/59125640/2569697">How do I async on_status from tweepy?</a></strong> and followed the links there, but I didn't <em>see</em> anything that clued me in to whatever my coding error is.</p>
  <p>Through additional research, I also tried using some calls from the <code>asyncio</code> library to make the call:</p>
  <pre class="lang-py prettyprint-override"><code>    #channel.send(message, embed = TwitterEmbed)
      #---ASYNCIO TEST---
      loop = asyncio.get_event_loop()
      loop.run_until_complete(asyncio.gather(DChannel.send(embed=TwitterEmbed)))
      loop.close()
  </code></pre>
  <p>Unfortunately, this results in an unhandled exception when it attempts to execute <code>loop=asyncio.get_event_loop()</code> indicating <code>There is no current event loop in thread 'Thread-6'.</code> (so it won't even get to the <code>run_until_complete()</code> method to see if <em>that</em> will work, although I'm not exactly optimistic at this point).</p>
  <p>I realize there are existing Discord bots like MEE6 that can already do what I'm trying to do here, but I'd like to be able to make this bot &quot;my own&quot; while teaching myself a little about Python. I'm probably overlooking something simple here, but I've not been able to find anything in the API documentation for either <code>tweepy</code> or <code>discord.py</code> that seems to be pointing me in the correct direction, and my <em>Google-fu</em> is apparently not that strong. All of the examples I've been able to find so far appear to be out-of-date as  they seem to refer to deprecated methods and older versions of either or both libraries. There are other things that I still have to figure out how to do as well (<em>e.g., get the <code>@TwitterHandle</code> to correctly populate the embed</em>), but I've got to get it able to read and push the tweet before I can worry about getting to that point.</p>
  <hr />
  <h2>ENVIRONMENT INFO</h2>
  <p>Visual Studio 2017 CE</p>
  <p>Python 3.6</p>
  <p>discord.py 1.3.3</p>
  <p>tweepy 3.8.0</p>
  <hr />
  <h2>UPDATE: ADDITIONAL TESTING</h2>
  <p>So, just to prove to myself that my <code>TweetListener</code> class is actually working, I went ahead and commented out all of the interaction with Discord in the <code>on_status</code> method (<em>everything from <code>for Guild in MyBot.guilds</code> to the end of the method</em>), then added a simple <code>print(TweetText)</code>. I ran the code again, logged in to the testing Twitter account I have for this project, and posted an update. Checking the console, it correctly output the <code>status.text</code> of my tweet, as I expected it to. As I thought, the only issue I'm having here is trying to <code>send</code> that text to Discord.</p>
  <p>I also tried initializing the <code>tweepy.Stream.filter</code> in synchronous mode instead of asynchronous - <code>NewStream.filter(follow=['USER IDS'])</code>. This only resulted in my application basically &quot;hanging&quot; on startup. I realized this was due to its position in the code, so I moved all three lines of the <code>TweetListener</code> initialization to the <code>on_ready</code> event method. I left the Discord code commented out and tested, and it again &quot;worked&quot; in that it printed the <code>status.text</code> of another test tweet to the console. However, the bot won't respond to anything else (still &quot;waiting&quot; on the stream, I assume).</p>
  <p>Even so, just to see if it would make any difference, I went ahead and uncommented the Discord interaction code and tried again. The result was the same as I had initially, only this time the Discord bot won't respond to activity in the channel (<em>there are a couple of super simple <code>@bot.command</code> declarations elsewhere in the code</em>), which once again basically leads me back to my original code with the same issue.</p>
  <p>I'm certain there are better ways to code this entire thing. As I said above, I'm just now starting to play around with Python and still learning syntax rules and such. Even so, I'm still not sure what I'm doing wrong and/or overlooking that's preventing me from achieving what I had thought would be a fairly simple task.</p>
  <hr />
  <h2>MORE TESTING</h2>
  <p>I went back and found some more <code>asyncio</code> examples and tried another iteration in the Discord interaction in <code>on_status</code>:</p>
  <pre class="lang-py prettyprint-override"><code>    loop = asyncio.new_event_loop()
      asyncio.set_event_loop(loop)
      result=loop.run_until_complete(DChannel.send(embed=TwitterEmbed))
  </code></pre>
  <p>This time I got a <em>different</em> unhandled exception than I've gotten before. The error occurs on the <code>run_until_complete</code> line when it picks up a new tweet:</p>
  <pre><code>Timeout context manager should be used inside a task
  </code></pre>
  <p>I consider this progress, but I'm obviously still not quite there, so back to Google and I found information on the <code>asgiref</code> library with its <code>sync_to_async</code> and <code>async_to_sync</code> methods, so I figured I'd give that a try.</p>
  <pre class="lang-py prettyprint-override"><code>    asgiref.sync.async_to_sync(DChannel.send)(embed=TwitterEmbed)
  </code></pre>
  <p>Unfortunately, I get the same unhandled exception as with the <code>asyncio</code> version. I feel like I'm right on the edge of figuring this out, but it simply hasn't &quot;clicked&quot; yet.</p>
  <hr />
  <h2>ANOTHER ROUND</h2>
  <p>Okay, so after looking for information about the <code>Timeout context manager</code> exception I was getting above, I stumbled across another SO question that has given me a tiny glimmer of hope. This answer on <a href="https://stackoverflow.com/a/52240169/2569697">RuntimeError: Timeout context manager should be used inside a task</a> once again takes me back to using <code>asyncio</code> and gives a brief but descriptive explanation of the reason behind the OP's issue before providing a useful suggestion using the <code>asyncio.run_coroutine_threadsafe</code> method. Looking at the recommended code, it made sense that this could help me initiate the sync-&gt;async method communication effectively. I implemented the suggested changes (created a global variable for a <code>Thread</code> object on which to run the bot, added a &quot;startup&quot; method that generated the bot in that loop, then changed the Discord interaction in <code>on_status</code> to bring it all together.</p>
  <p>The &quot;good news&quot; is that I'm no longer getting <em>any</em> errors when I post a tweet. Also, testing the bot command seems to work just fine as well. The bad news is that it still didn't send the message to the channel. Since it didn't produce any errors, there's no telling where the message ended up.</p>