Rdio / Spotify Conversion with Hubot and Slack

We love Slack at Viget. The #music channel is my favorite; it's great for finding new music or just discussing your favorite jam. Whenever we come across something we want to share, we grab the link from either Rdio or Spotify and paste into Slack. Because there's no clear preference between the two services, I developed some custom hubot scripts to convert the URLs from one service to another.

If you're just looking for the scripts, here you go:

Rdio to Spotify

hubot rdio to spotify conversion

The detailed explanation:

Rdio links can come in two flavors: shortened (http://rd.io/x/...;) or long and descriptive (http://www.rdio.com/artist/.../album/.../track/...).

<code>robot.hear /(https?:\/\/(www.)?(rd.?io).+)/i, (msg) -></code>

We first need to determine which type of link we're working with. When visiting a link of the shortened type, Rdio simply redirects to the longer full version (http://rd.io/x/QXvrziJ2kDE/ converts to https://www.rdio.com/artist/CHVRCHES/album/Recover_-_EP/). If the script determines it has a shortened URL, it makes an HTTP request to resolve the expanded URL.

<code>if (msg.match[1].indexOf('http://rd.io/x/') != -1)
 method: 'HEAD'
 url: msg.match[1]
 followAllRedirects: true
 , (error, response) ->
 expandedUrl = response.request.href</code>

From there, we can parse the longer full version of the URL which has a consistent structure to determine: Artist, Album, and optionally Track.

<code>artistFromUrl = (url) ->
 artist = ''
 artistMatch = url.match(/artist\/([^\/]*)/)
 artist = artistMatch[1].replace(/_/g,' ') if artistMatch != null
 return artist</code>

Using the Artist, Album, and Track information, we then use this great little Spotify search engine at: http://www.stewsnooze.com/searchify/. The included example will make a request like http://www.stewsnooze.com/searchify/?q=CHVRCHES+Recover%20-%20EP.

<code>url = "http://www.stewsnooze.com/searchify/?q=#{artist}+#{album}+#{track}"

request url, (error, response, html) ->
 unless error
 # next: parse the response</code>

Now that we have a page with results, we can use the cheerio node module which is a subset of jQuery in order to parse the HTML result content. It goes through one-by-one parsing the HTML to grab the link destination and text, and adding each to the results list. If there is an exact match, it will prefer that result and stop.

<code>$ = cheerio.load(html)

$('.results').filter ->
 data = $(this)
 results = []

 for result in data.children()
 result = $(result)
 if track != ''
 trackMatch = result.html().match(/(spotify:track([^"])*)/)
 resultText = "#{result.text()} -- #{convertSpotifyResultToLink(trackMatch[1])}"
 results.push resultText if trackMatch != null
 # check for an exact match
 if result.text() == "#{artist} - #{album} - #{track}"
 results = []
 results.push resultText

convertSpotifyResultToLink = (result) ->
 spotifyUrl = 'https://play.spotify.com/'
 resultToken = result.match(/.+:(.+)$/)

 if result.indexOf('spotify:track:') == 0
 spotifyUrl += "track/#{resultToken[1]}"
 spotifyUrl += "album/#{resultToken[1]}"

 return spotifyUrl</code>

Now that we have our results, we just have Hubot spit out the link!

<code>if results.length > 1
 result_count_line = "Found #{results.length} Spotify results"
 msg.send "#{result_count_line}:\n #{results.join('\n')}"
 msg.send results</code>

Spotify to Rdio

hubot spotify to rdio

This conversion is a bit more complicated and requires using both the Spotify and Rdio APIs. You will need to create API applications for both types of API and configure your Hubot with the according client_id and client_secret for both API types (e.g. SPOTIFY_CLIENT_ID). 

API documention can be found here: Spotify API Documentation and Rdio API Documentation

API Application links: Spotify Applications and Rdio Applications

The detailed explanation:

Spotify links can come in a variety of flavors http://play.spotify.com/album/TOKEN or http://open.spotify.com/track/TOKEN. For example: http://open.spotify.com/track/4HGrOVrcNuoM0wQv3APnT8

<code>robot.hear /(https?:\/\/(www.)?((play|open).spotify.com).+)/i, (msg) -></code>

The script first determines whether it's dealing with a Album or Track, and stores the token. 

<code>urlParseResults = searchTypeAndTokenFromUrl(url)
searchType = urlParseResults[0]
searchToken = urlParseResults[1]

searchTypeAndTokenFromUrl = (url) ->
 token = ''
 type = ''

 if (url.indexOf('/track/') != -1)
 type = 'track'
 tokenMatch = url.match(/track\/([^\/]*)/)
 token = tokenMatch[1] if tokenMatch != null
 else if (url.indexOf('/album/') != -1)
 type = 'album'
 tokenMatch = url.match(/album\/([^\/]*)/)
 token = tokenMatch[1] if tokenMatch != null

 return [type, token]</code>

Then, in order to work with the Spotify API it makes a request to get a fresh OAuth token. 

<code>refreshSpotifyAuthToken = (callback) ->
 client_id = process.env.SPOTIFY_CLIENT_ID
 client_secret = process.env.SPOTIFY_CLIENT_SECRET
 basic_auth_base64_encoded = new Buffer("#{client_id}:#{client_secret}").toString('base64')

 options = {
 url: 'https://accounts.spotify.com/api/token',
 method: 'POST',
 headers: {
 'Authorization': "Basic #{basic_auth_base64_encoded}"
 form: { 'grant_type': 'client_credentials' }

 request options, (error, response, body) ->

Using the returned auth token, it can then do a lookup using the Spotify API using either of two API endpoints: tracks/TOKEN and albums/TOKEN.

<code>spotifyApiLookup = (authToken, searchType, searchToken, callback) ->
 url = "https://api.spotify.com/v1/#{searchType}s/#{searchToken}"

 options = {
 url: url,
 headers: {
 'Authorization': "Bearer #{authToken}"

 request options, (error, response, body) ->

The API returns a JSON response including information about the Artist, Album, and potentially Track. This effectively converts that funky TOKEN value into what we need: 

Now that we've identified what the Spotify link means, we can use the Rdio API to find out if it has any matching songs. Using the provided rdio API javascript scripts, we can make a call to the Rdio API `search` method, with a `query` and `types` (Track or Album) parameters.

<code>rdioAlbumFromSpotifyResponse = (response, callback) ->
 # first get artist names and album from spotify API response
 responseJson = JSON.parse(response)
 artists = (artist.name for artist in responseJson.artists).join(" ")
 album = responseJson.name
 # search Rdio API
 rdio.call 'search', { query: "#{artists} #{album}", types: "Album"}, (err, body) ->

Once we've got the response from the Rdio API and we can have our friendly hubot respond!

parseRdioResponseAndRespond = (response, message) ->
 if parseInt(response.result.number_results) > 1
 result_count_line = "Found #{response.result.number_results} Rdio results"
 result_lines = ("#{result.name} - http://www.rdio.com#{result.url}" for result in response.result.results).join("\n")
 message.send "#{result_count_line}:\n #{result_lines}"
 result = response.result.results[0]
 message.send "http://www.rdio.com#{result.url}"</code>

Find this useful? Developed anything similar? Please join the conversation below!

Mike is a developer who uses his computer and electrical engineering degrees to craft complex but streamlined back-end systems for clients such as Shure and Privia Medical Group.

More posts by Mike