Skip to content
This repository has been archived by the owner on Feb 13, 2018. It is now read-only.

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jnewland committed Jun 28, 2012
0 parents commit d6512c8
Show file tree
Hide file tree
Showing 8 changed files with 419 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
@@ -0,0 +1,5 @@
bin/
log
.bundle
vendor/gems
.env
8 changes: 8 additions & 0 deletions Gemfile
@@ -0,0 +1,8 @@
source :rubygems

gem 'sinatra'

group :development do
gem 'heroku'
gem 'foreman'
end
39 changes: 39 additions & 0 deletions Gemfile.lock
@@ -0,0 +1,39 @@
GEM
remote: http://rubygems.org/
specs:
addressable (2.2.8)
excon (0.14.2)
foreman (0.47.0)
thor (>= 0.13.6)
heroku (2.28.7)
heroku-api (~> 0.2.6)
launchy (>= 0.3.2)
netrc (~> 0.7.5)
rest-client (~> 1.6.1)
rubyzip
heroku-api (0.2.6)
excon (~> 0.14.0)
launchy (2.1.0)
addressable (~> 2.2.6)
mime-types (1.19)
netrc (0.7.5)
rack (1.4.1)
rack-protection (1.2.0)
rack
rest-client (1.6.7)
mime-types (>= 1.16)
rubyzip (0.9.9)
sinatra (1.3.2)
rack (~> 1.3, >= 1.3.6)
rack-protection (~> 1.2)
tilt (~> 1.3, >= 1.3.3)
thor (0.15.3)
tilt (1.3.3)

PLATFORMS
ruby

DEPENDENCIES
foreman
heroku
sinatra
1 change: 1 addition & 0 deletions Procfile
@@ -0,0 +1 @@
web: bundle exec ruby lib/nowplaying.rb
23 changes: 23 additions & 0 deletions README.md
@@ -0,0 +1,23 @@
## Running on Heroku

Clone this repo.

In the clone:

heroku create --stack cedar jnewland-rdio-nowplaying

Set the following config at heroku:

heroku config:add RDIO_CONSUMER_KEY=foo
heroku config:add RDIO_CONSUMER_SECRET=foo@foo.com
heroku config:add POLL_INTERVAL=10

Ship it:

git push heroku master

Fire up a web process:

heroku scale web=1

Hit up [papertrail](https://papertrailapp.com/events) and check on the logs.
101 changes: 101 additions & 0 deletions lib/nowplaying.rb
@@ -0,0 +1,101 @@
#!/usr/bin/env ruby

# (c) 2011 Rdio Inc
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require 'sinatra'
require 'uri'
$LOAD_PATH << './lib'
require 'rdio'

RDIO_CONSUMER_KEY = ENV['RDIO_CONSUMER_KEY']
RDIO_CONSUMER_SECRET = ENV['RDIO_CONSUMER_SECRET']

enable :sessions

get '/' do
access_token = session[:at]
access_token_secret = session[:ats]
if access_token and access_token_secret
rdio = Rdio.new([RDIO_CONSUMER_KEY, RDIO_CONSUMER_SECRET],
[access_token, access_token_secret])

user_key = rdio.call('currentUser')['result']['key']
play_data = rdio.call('get', { :keys => user_key, :extras => 'lastSongPlayed,lastSongPlayTime'})['result'][user_key]

play_time = play_data['lastSongPlayTime']
song_key = play_data['lastSongPlayed']['key']

song = rdio.call('get', { :keys => song_key, :extras => 'bigIcon'})['result'][song_key]

response = "
<html><head><title>Now Playing</title></head><body>
<img src='%s' />
<h1>%s</h1>
<h2>%s</h2>
<h3>%s</h3>
" % [song['bigIcon'], song['name'], song['album'], song['artist']]
response += '</body></html>'
return response
else
redirect to('/login')
end
end

get '/login' do
session.clear
# begin the authentication process
rdio = Rdio.new([RDIO_CONSUMER_KEY, RDIO_CONSUMER_SECRET])
callback_url = (URI.join request.url, '/callback').to_s
url = rdio.begin_authentication(callback_url)
# save our request token in the session
session[:rt] = rdio.token[0]
session[:rts] = rdio.token[1]
# go to Rdio to authenticate the app
redirect url
end

get '/callback' do
# get the state from cookies and the query string
request_token = session[:rt]
request_token_secret = session[:rts]
verifier = params[:oauth_verifier]
# make sure we have everything we need
if request_token and request_token_secret and verifier
# exchange the verifier and request token for an access token
rdio = Rdio.new([RDIO_CONSUMER_KEY, RDIO_CONSUMER_SECRET],
[request_token, request_token_secret])
rdio.complete_authentication(verifier)
# save the access token in cookies (and discard the request token)
session[:at] = rdio.token[0]
session[:ats] = rdio.token[1]
session.delete(:rt)
session.delete(:rts)
# go to the home page
redirect to('/')
else
# we're missing something important
redirect to('/logout')
end
end

get '/logout' do
session.clear
redirect to('/')
end
160 changes: 160 additions & 0 deletions lib/om.rb
@@ -0,0 +1,160 @@
# om is oauth-mini - a simple implementation of a useful subset of OAuth.
# It's designed to be useful and reusable but not general purpose.
#
# (c) 2011 Rdio Inc
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

# A simple OAuth client implementation. Do less better.
# Here are the restrictions:
# - only HMAC-SHA1 is supported
# - only WWW-Authentiate form signatures are generated
#
# To sign a request:
# auth = om([consumer_key,consumer_secret], url, params)
# send Authorization: <auth>
# when POSTing <params> to <url>
# Optional additional arguments are:
# token = [oauth_token, oauth_token_secret]
# method = "POST"
# realm = "Realm-for-authorization-header"

require 'uri'
require 'cgi'
require 'digest'
require 'digest/sha1'

if not "".respond_to?(:encoding)
# ruby 1.8 doesn't know about unicode :(
require 'iconv'
# we will just check that bytes are valid UTF-8
$__om_utf8_checker = Iconv.new("UTF-8", "UTF-8")
end

def om(consumer, url, post_params, token=nil, method='POST', realm=nil, timestamp=nil, nonce=nil)
# A one-shot simple OAuth signature generator

# the method must be upper-case
method = method.upcase

# we want params as an Array of name / value pairs
if post_params.is_a?(Array)
params = post_params
else
params = post_params.collect { |x| x }
end

# we want those pairs to be strings
params = params.collect { |k,v| [k.to_s, v.to_s]}

# normalize the URL
url = URI.parse(url)
# scheme is lower-case
url.scheme = url.scheme.downcase
# remove username & password
url.user = url.password = nil
# host is lowercase
url.host = url.host.downcase

# add URL params to the params
if url.query
CGI.parse(url.query).each { |k,vs| vs.each { |v| params.push([k,v]) } }
end

# remove the params and fragment
url.query = nil
url.fragment = nil

# add OAuth params
params = params + [
['oauth_version', '1.0'],
['oauth_timestamp', timestamp || Time.now.to_i.to_s],
['oauth_nonce', nonce || rand(1000000).to_s],
['oauth_signature_method', 'HMAC-SHA1'],
['oauth_consumer_key', consumer[0]],
]

# the consumer secret is the first half of the HMAC-SHA1 key
hmac_key = consumer[1] + '&'

if token != nil
# include a token in params
params.push ['oauth_token', token[0]]
# and the token secret in the HMAC-SHA1 key
hmac_key += token[1]
end

def percent_encode(s)
if s.respond_to?(:encoding)
# Ruby 1.9 knows about encodings, convert the string to UTF-8
s = s.encode(Encoding::UTF_8)
else
# Ruby 1.8 does not, just check that it's valid UTF-8
begin
$__om_utf8_checker.iconv(s)
rescue Iconv::IllegalSequence => exception
throw ArgumentError.new("Non-UTF-8 string: "+s.inspect)
end
end
chars = s.bytes.map do |b|
c = b.chr
if ((c >= '0' and c <= '9') or
(c >= 'A' and c <= 'Z') or
(c >= 'a' and c <= 'z') or
c == '-' or c == '.' or c == '_' or c == '~')
c
else
'%%%02X' % b
end
end
chars.join
end

# Sort lexicographically, first after key, then after value.
params.sort!
# escape the key/value pairs and combine them into a string
normalized_params = (params.collect {|p| percent_encode(p[0])+'='+percent_encode(p[1])}).join '&'

# build the signature base string
signature_base_string = (percent_encode(method) +
'&' + percent_encode(url.to_s) +
'&' + percent_encode(normalized_params))

# HMAC-SHA1
hmac = Digest::HMAC.new(hmac_key, Digest::SHA1)
hmac.update(signature_base_string)

# Calculate the digest base 64. Drop the trailing \n
oauth_signature = [hmac.digest].pack('m0').strip

# Build the Authorization header
if realm
authorization_params = [['realm', realm]]
else
authorization_params = []
end
authorization_params.push(['oauth_signature', oauth_signature])

# we only want certain params in the auth header
oauth_params = ['oauth_version', 'oauth_timestamp', 'oauth_nonce',
'oauth_signature_method', 'oauth_signature',
'oauth_consumer_key', 'oauth_token']
authorization_params.concat(params.select { |param| nil != oauth_params.index(param[0]) })

return 'OAuth ' + (authorization_params.collect {|param| '%s="%s"' % param}).join(', ')
end

0 comments on commit d6512c8

Please sign in to comment.