How to set up an obscenely easy, AI-API-using tweetbot in Python

Using Tweepy and some image recognition public APIs to help Twitter users with an all-important question — IsItNSFW?

Marek K. Cichy

--

I love learning on the go. The approach is particularly useful for me this year. 2019 in my case means a professional transition from humanities (focus on translation) to tech (focus on NLP/ML). Here’s one example of how quickly one can apply freshly-acquired skills. Also, this story comes in a series of threes.

A month ago, after a poke from Piotr Migdał, I described how to fool neural networks with seemingly pornographic optical illusions. As a spin-off and to learn a new skill, I created a simple tweetbot. @IsItNSFW, upon receiving an image, will respond with the scores a handful of AI image detection models give it.

To create it, I needed to put together three elements:

  • Twitter interaction via Tweepy,
  • connecting to AI detector APIs,
  • and somewhere to host my script.

Twitter interaction via Tweepy

That’s exactly what it is.

For the first module, Tweepy was my choice after Googling for accessible Twitter-wise Python libraries. Again, there are three Tweepy-related parts of the code:

  • linking a Twitter account to the script,
  • defining a function that will reply to tweets,
  • setting a stream

Prerequisite: Twitter account setup

I started by creating an account my bot will be using on Twitter. Then I headed to the developer dashboard in order to create a new app and obtain the API key, API secret key, Access token and Access token secret.

Space Invaders guard my passwords; hide yours as well!

I noted them down in a safe place. In fact, I stored them straight away in a separate file called secrets.py. That way I can share the rest of the code on GitHub without needing to worry about the secrecy of my API access keys:

#Twitter APIconsumer_key = '...'consumer_secret = '...'access_token = '...'access_secret = '...'

Connecting to Twitter

Only now do I start to write the main script (nsfw_bot.py in my case). I used the newly-created credentials to connect to Twitter using Tweepy’s OAuthHandler class:

auth = tweepy.OAuthHandler(consumer_key, consumer_secret)auth.set_access_token(access_token, access_secret)api = tweepy.API(auth)

The api variable will be the entry point for the operations I will perform with Twitter — in this case, posting replies with the image scores. Tweepy’s API class provides access to the entire Twitter RESTful API methods (see API Reference for more information).

Replying function

Then I set the tweet_image_ratings() function exactly for that. The function accepts — you guessed it — 3 arguments:

(pic_url: [the tweeted image source],
username: [the username of the user that tweeted the image],
status_id: [the id of the tweet])

My function consults the image with all the three image detectors and replies to the tweets with the corresponding scores and a retweet of the image. In order to retweet, I need to temporarily store the image as temp.jpg:

def tweet_image_ratings(pic_url, username, status_id):
# Take the pic url and tweet the various NSFW ratings
clarifai_score = check_clarifai(pic_url)
deepai_score = check_deepai(pic_url)
sightengine_score = check_sightengine(pic_url)
filename = 'temp.jpg'
request = requests.get(pic_url, stream=True)
if request.status_code == 200:
with open(filename, 'wb') as image:
for chunk in request:
image.write(chunk)
api.update_with_media(filename, status='Is it NSFW, @'+username+'?\n' + clarifai_score+deepai_score+sightengine_score, in_reply_to_status_id=status_id)

The last Tweepy-related bit to fill in — tweet_image_ratings() arguments, that is, the original tweet data. How to get those?

Streaming

by Micah Hallahan on Unsplash

In order to keep the connection open and be able to respond to all incoming and upcoming tweets, I need the streaming API. Tweepy makes it easier to use the Twitter streaming API by hiding authentication, session handling and reading incoming messages under the hood.

I used the streaming API in two steps.

  • I created a class inheriting from StreamListener, overriding the on_status method. BotStreamer captures the username and tweet id. If an image is accompanying the tweet, it passes the three arguments the tweet_image_ratings() function described above:
class BotStreamer(tweepy.StreamListener):
# Called when a new status arrives which is passed down from the on_data method of the StreamListener
def on_status(self, status):
username = status.user.screen_name
status_id = status.id
if 'media' in status.entities:
for image in status.entities['media']:
tweet_image_ratings(image['media_url'], username, status_id)
  • Using that class, I instantiated a Stream object and connected to the Twitter API using it:
myStreamListener=BotStreamer()
stream=tweepy.Stream(auth, myStreamListener)
stream.filter(track=['@IsItNSFW'])

Connecting to AI detector APIs

The last part of nsfw_bot.py I wrote concerns connecting to the nudity detectors and retrieving their scoring for a given picture. It may surprise you, but I chose three:

For each of these, I had to create an account and an API key, immediately stored in secrets.py. In all three cases, the pricing of the service includes a free tier that is absolutely sufficient for my light use.

As for the code itself, in the case of Clarifai and Sightengine we are presented with a dedicated library. DeepAI uses a simple requests query. In any case, there are pythonically few lines to write:

def check_sightengine(pic_url):
try:
client = SightengineClient(sightengine_user, sightengine_secret)
output = client.check('nudity').set_url(pic_url)
score = 'Sightengine: ' + \
str(round(100*output['nudity']['raw']))+'% NSFW'
except:
score = ''
return score

In all 3 cases, I included a try/except clause for any error handling. This way the bot will only tweet about the successfully retrieved scores.

And that’s it! We’re done with a simple bot. Time to test it…

Check out the complete code of nsfw_bot.py in this GitHub repo.

Where to host my script?

A server of the people (Gayatri Malhotra on Unsplash)

That was the last issue I had to solve. Running it from my computer was not a sustainable option. Literally the first service I stumbled upon was PythonAnywhere. Straightforward, easy to apply with a fairly generous free use tier and helpful staff — it won my heart from the start.

I simply uploaded both nsfw_bot.py and secrets.py to PythonAnywhere. The service comes with a full Python environment installed, so the only thing left to do was open a Bash console in PyAnywhere and run my script.

I was still concerned about the infallibility of the bot out in the wild — will it crash on some unavoidable or unpredictable connection error? Googling for a solution, I found the following trick:

from subprocess import Popen
import sys
import datetime
filename = sys.argv[1]
while True:
print('\n'+str(datetime.datetime.today())+"\nStarting " + filename)
p = Popen("python " + filename, shell=True)
p.wait()

The miniscript I called forever.py opens the [filename] as a new subprocess every time it produces an error. I added a print(datetime.datetime.today()) statement to have a log on the frequency the script crashes.

Final thoughts

A bot to set up on the couch in one sitting (Annie Spratt on Unsplash)

The main purpose of this story is to show how little fuss is needed to set up a minimal working tweetbot connecting to several APIs. I was writing it in a public cafe, and realized a limitation of the @IsItNSFW bot itself — after all, many people may be too embarrassed to upload a (verging on a) kinky photo on their Twitter and have it publicly retweeted.

That said, use and abuse the bot, the repo and please share the results here. Maybe set up a bot that writes image captions? Or creates images from text? The APIs are there for you to grab.

My thanks go to Piotr Migdał for the initial prod and invaluable feedback.

--

--

Marek K. Cichy

PL PT ES Linguist turned NLP/ML rookie. Striving to bridge various worlds.