Tutorial: reddit SMS parser with Python

I made a reddit parser a while back just for fun. It got attention from Twilio and reddit, which was awesome!
It's a pretty small program (<100 lines), and looking back on it there are things I could've done better. I'm going to explain the barebones version of it, which won't include my messy old code.

Goal

Text the number a subreddit, and get back the top post from it.

A lot of the program is error-catching, so a simple goal like this will lessen room for problems.

Requirements

  • Python 2.7.8
  • These are in the requirements.txt file, might not all be necessary:
    Flask0.10.1
    Jinja2
    2.7.3
    MarkupSafe0.23
    SocksiPy-branch
    1.01
    Werkzeug0.9.6
    httplib2
    0.9
    itsdangerous0.24
    praw
    2.1.18
    requests2.3.0
    six
    1.7.3
    twilio3.6.6
    update-checker
    0.10
    coverage3.7.1
    coveralls
    0.4.2
  • Twilio account, helps to have paid version
  • Heroku account or some other app hosting
  • Git

Terminology

Python is a programming language with English-like syntax. The entire program will be made out of Python.
Flask is a lightweight web framework for Python. Allows for easy, fast web app creation. This app simply parses data from reddit, so there shouldn't need to be a huge, heavy framework for this.
PRAW is the Python reddit API Wrapper. It makes it easy to fetch data from reddit using Python.
Twilio is a great service for making voice and SMS applications.
Heroku lets you host your web apps online for free.
Coveralls and Travis are testing services. Travis runs tests on code every time it's pushed to a watched repo, and Coveralls checks how much code is run by the tests. Plus, these both give you cool badges to put on the repo :)
Last, but not least, Git is a version-control system. This works with Heroku to put the latest version of the project online.

Let's get started!

First, we need to make a file and add the imports. Make a file, run.py, and add these lines:

from flask import Flask, request  
import os  
app = Flask(__name__)  
if __name__ == "__main__":  
    port = int(os.environ.get('PORT', 5000))
    app.run(host='0.0.0.0', port=port)

This sets up the app with Flask. First, the proper modules are loaded in. Then, app is created as a Flask app.
Finally, the app runs on port 5000 if the file is called directly from the command line (if __name__== "__main__", so another server isn't run if the file is referenced from somewhere else)
Make sure you have these modules installed, then call python run.py. Go to localhost:5000 in your browser and you should see an example page for Flask!

Before we go any further, PRAW and Twilio need a little setup. At the top, put in

import twilio.twiml, praw  
r=praw.Reddit('reddit sms parser!')  

This gets PRAW and Twilio ready to use for later.

Now let's implement the Twilio part. We need to configure the app to receive SMS messages. After app = Flask(__name__), add

@app.route("/", methods=['POST'])
def receive_message():  
    resp = twilio.twiml.Response()
    return str(resp)

The first line tells Flask that when a POST request (What's POST?) is made to <flask app's url>/ , it should be forwarded to receive_message. Within receive_message, a Twilio response is created. At the end of the method, the response is returned. This makes the app return a message to the user.
Now we need to make a method to parse the incoming message. Before returning resp, write

    body = request.values.get('Body').lower()
    parsed = parse_sms(body)
    resp.message(parsed)

body is set to the body of the request. Remember, the incoming request to the server will be an SMS message, so we want to retrieve the body of the message.
Then, we parse the body and set that to a parsed variable.
Lastly, we set the message of the Twilio response to the parsed data that will be sent back.

Now to create the parse_sms method. Let's put this above the @app.route.

def parse_sms(body):

What should the method do?

  • Return the top (hot) post from the subreddit
  • If the subreddit doesn't exist, return a 'not found' message.
def parse_sms(body):  
    subreddit = r.get_subreddit(body)
    posts = list(subreddit.get_hot(limit=1))
    if len(list)==0:
        return "Couldn't find that subreddit"
    else:
        #format the subreddit
        post = list[0]

This code gets the subreddit from PRAW, and gets the top 1 post(s). If the list of posts is empty, the subreddit is not found, as every active subreddit has hot posts (I think?). If there is a post in the list, then we want to format that post to send back, so post is assigned to be the first (only) item in the list. Also, # is a comment in Python. Everything after the # is ignored.

So how should the post be formatted? That's up to you. In this example, we'll just return the title and the author.

        #format the subreddit
        post = list[0]
        return "%s by %s" % (p.title, p.author)

The %s replaces itself with the input string respectively.

Now that the program is done, it needs to be set up with Heroku and Twilio. You can find a tutorial on setting up a Python app with Heroku here, this also means you should put the requirements from the beginning into a requirements.txt file.

This part assumes you have the app hosted online. Go to your Twilio acccount, and get a number if you don't have one already. Go to Numbers and find the phone number you want to use.
Now change the incoming messaging URL to your app's URL, and change the dropdown on the right to HTTP POST. This is because of @app.route("/", methods=['POST']); the app is ready to receive a POST request rather than a GET.

Try texting the Twilio number a subreddit name, and you should get back the top post! More features only involve more advanced formatting, and checking for possible errors.

Have fun!