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 API consumer_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+'?

' + 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:

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('

'+str(datetime.datetime.today())+"

Starting " + 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.