Picklebot's First Steps

Origin Story

My friend Dylan has been curating a weekly playlist on Spotify for a few months now, and has been posting it on various social media platforms to grow the follower base.  You can find that playlist and the other work he does here at http://www.promotelocal.com/thepicklejar/.  We were talking a couple weeks ago about how he gets the majority of his followers from the Spotify subreddit, but how infrequently he posted there.  

So, I decided what a great opportunity to learn some Web APIs, Python, and all sorts of other web technology I had never seen before.  What follows is the first part of what I'm sure will be a few in the birth, refactoring, and creation of Picklebot: A tiny bot that takes the week's Pickle Jar playlist and slaps it all over various social media platforms.  First up, Reddit!

The Setup

I started this bot with ZERO experience in Python.  So little in fact, that I spent a good two hours getting the Python library installed on my computer, as well as figuring out the standard Pip/VirtualEnv/etc. workflow process.  Before I even made it to syntax errors and building the logic for the bot, I had to digest the Spotify and Reddit APIs.  For this project, I'm using spotipy and praw, two python modules that wrap the calls for each of the aforementioned APIs in neat little python functions so you can call them directly in the script and get info back. Neat!

Spotify has a fairly extensive list of API calls you can use to interact with their platform. For this first iteration of PIcklebot, I only call their service once to return a few pieces of information about this week's playlist.  On the reddit end, I also use just one call to make a pre-formatted post to the /r/Spotify subreddit.  With the preface out of the way, lets dig in to the code!

The Script Breakdown

Main()

from spotipy.oauth2 import SpotifyClientCredentials
import spotipy
import spotipy.util as util
import praw
import configparser as ConfigParser
from configparser import NoSectionError, NoOptionError

def main():
    bot = Picklebot()
    results = bot.request_playlist()
    bot.post_to_reddit(results)

The main() method for Picklebot is just three steps!  First, we instantiate a Picklebot object, necessary later for when we host the bot in the Google Cloud and need to have it extend their webhandler class.  Then, we call the two methods defined in the bot itself, one for getting the playlist information and the other for posting to reddit.  Later on, more methods can be added with the same JSON results from the Spotify call to post to other social media platforms.

As seen above, the modules I'm using for Picklebot are spotipy, praw, and configparser.

Request_Playlist()

def request_playlist(self):
        #Instantiate configparser
        config = ConfigParser.RawConfigParser()
        config.read('settings.cfg')

        #Gets Spotify API credentials from the settings.cfg
        SPOTIFY_USER = config.get('Spotify OAuth', 'SPOTIFY_USER')
        SPOTIFY_SCOPE = config.get('Spotify OAuth', 'SPOTIFY_SCOPE')
        SPOTIFY_CLIENT_ID = config.get('Spotify OAuth', 'SPOTIFY_CLIENT_ID')
        SPOTIFY_SECRET = config.get('Spotify OAuth', 'SPOTIFY_SECRET')
        SPOTIFY_URL = config.get('Spotify OAuth', 'SPOTIFY_URL')

        #Retrieve token from spotify api and then get an instance of the spotipy class wrapping the api.
        token = util.prompt_for_user_token(SPOTIFY_USER, SPOTIFY_SCOPE, SPOTIFY_CLIENT_ID, SPOTIFY_SECRET, SPOTIFY_URL)
        sp = spotipy.Spotify(auth=token)
        #Share link for thepicklejar playlist, extrapolate the id's to pass when retrieving the object from the api.
        uri = 'spotify:user:1257163432:playlist:0RvjVC3UO1nO75hA5yME9c'
        username = uri.split(':')[2]
        playlist_id = uri.split(':')[4]

        #results is the full object retrieved from the Spotify api, filtered by the "fields" parameter so we only get back information we care about.
        results = sp.user_playlist(username, playlist_id, fields="external_urls, tracks.items(added_at, track(name, artists, popularity))")
        return results

In both functions for Picklebot, I'm using a module called ConfigParser, which is included in most Python binaries.  It allows for sensitive credential information to be stored in a .cfg file so it is separate from your source code.  This is helpful for keeping all of your credentials in one spot and makes it easier to share your source with the Internet without exposing all of your passwords!  

In this method, first the spotipy wrapper grabs a token from Spotify's API to authenticate the bot.  That token is generated with the five pieces of information set at the top.  Then, it calls their GET request for retrieving a specific user's public playlist data.  More documentation on that call can be found here.  For the purposes of Picklebot, I only need a few pieces of info out of the massive block of JSON it returns, so the last parameter in the spotipy function call is a filter so I only get the JSON I care about.  Once I have that info, it's onto formatting and posting to reddit!

Post_to_Reddit()

def post_to_reddit(self, results):
        config = ConfigParser.RawConfigParser()
        config.read('settings.cfg')

        #Gets Reddit API credentials from the settings.cfg
        REDDIT_USERNAME = config.get('Reddit OAuth', 'REDDIT_USERNAME')
        REDDIT_PW = config.get('Reddit OAuth', 'REDDIT_PW')
        REDDIT_USER_AGENT = config.get('Reddit OAuth', 'REDDIT_USER_AGENT')
        REDDIT_CLIENT_ID = config.get('Reddit OAuth', 'REDDIT_CLIENT_ID')
        REDDIT_CLIENT_SECRET = config.get('Reddit OAuth', 'REDDIT_CLIENT_SECRET')

        #Grab an instance of the reddit class from praw
        reddit = praw.Reddit(client_id=REDDIT_CLIENT_ID,
                             client_secret=REDDIT_CLIENT_SECRET,
                             password=REDDIT_PW,
                             user_agent=REDDIT_USER_AGENT,
                             username=REDDIT_USERNAME)
        #Get the /r/spotify subreddit from praw
        pickleInstance = reddit.subreddit('spotify')


        #Trim the response into more digestible data structures, could probably just be done from the results object if I was better at python :)
        tracks = results['tracks']
        trackNames = []
        trackPop = []
        trackDates = []
        trackDict = {}

        #Honestly, this was a lot of guess and check to access the spotify api object, can only assume there's a better way.
        for i, trackHolder in enumerate(tracks.items()):
            for j, track in enumerate(trackHolder[1]):
                trackDates.append(track['added_at'])
                trackNames.append(track['track']['name'])
                trackPop.append(track['track']['popularity'])
                trackDict[track['track']['name']] = track['track']['artists'][0]['name']

        #Sorts the data by most popular song and grabs the top 3 songs in the playlist for the week.
        top3 = sorted(zip(trackPop,trackNames), reverse=True)[:3]
        top3Artists = []

        #Gets the artist responsible for the top 3 songs and adds their name to a dict.
        for i, name in top3:
            top3Artists.append(trackDict[name])

        #Builds the week's title using the most popular artists and submits it to reddit!
        postTitle = "This Week on The Pickle Jar: %s, %s, %s, and more!" %(top3Artists[0], top3Artists[1], top3Artists[2])
        pickleInstance.submit(postTitle, url=results['external_urls']['spotify'])

The first section of this function is basically identical to the Spotify call.  First, I grab the Reddit credentials from my config file and pass them through the Reddit API wrapper praw to get a Python object that can interface with Reddit.  Then I grab an instance of the praw object that is directed at the /r/spotify subreddit.

Since I'm completely new to Python, I was a little stumped at what the easiest way to get through the messy JSON object Spotify returns so I could have the relevant pieces of info at my disposal.  What follows the credentials portion is my attempt to slice open the JSON and sort it into relevant data objects.

Once I have those sorted pieces, I grab the top 3 artists in the playlist for the week and use them in the title of the post to Reddit.  The thought process behind this was to attract more followers based on familiar artists being name dropped in the post.  Finally, I pass the title and link to the playlist into the praw object's call to Reddit that posts it and we're done!

Conclusion and Next Steps

From here, all I have to do is run the script from the command line once to get a post on Reddit with everything working as intended!  Obviously, the end goal of this project is not to run the script manually every time I need to make a new post.  The next blog post will cover the transfer of the Picklebot script from my computer to the Google Cloud Platform, and how to build the yaml files, routes, and cron set ups so that it automatically posts to Reddit weekly. This will also include an additional check on the songs in the playlist for their "added by" timestamp, so that it only posts to Reddit when all of the songs are new for the week.

If you think this is neat, have any suggestions, or want the whole source for the project, I'll be putting the files as they grow on my Github account, and you can get started on your own bots!  This first one is just the .py file and you'll have to build your own .cfg file with your own credentials for the services. Reach out to me on any of my social media platforms if you have questions, happy building!