Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
369 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,5 @@ | |
*.pyo | ||
*.pyc | ||
php/examples/rdio-consumer-credentials.php | ||
python/examples/rdio_consumer_credentials.py | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,30 @@ | ||
The Python library isn't available yet. | ||
rdio-simple for Python | ||
|
||
An Rdio client including a built-in OAuth implementation. | ||
|
||
This has library only depends on libraries included by default in recent | ||
versions of Python. It has been tested with Python 2.6. | ||
|
||
To install the library simple add the om.py and rdio.py files to your source | ||
directory. | ||
|
||
Usage: | ||
To use the library just load the Rdio class from the rdio module: | ||
from rdio import Rdio | ||
Create an Rdio instance passing in a tuple with your consumer key and secret: | ||
rdio = Rdio(array("consumerkey", "consumersecret")) | ||
Make API calls with the call(methodname, params) method: | ||
rdio.call('get', keys='a254895,a104386') | ||
Authenticate and authorize with the begin_authentication and | ||
complete_authentication methods. | ||
|
||
The current token (either request or access) is stored in rdio.token as a | ||
tuple with the token and token secret. | ||
|
||
Examples: | ||
Both examples authenticate and then list the user's playlists. They use | ||
credentials stored in rdio-consumer-credentials.php. | ||
examples/command-line.py | ||
examples/web-based.py | ||
NOTE: web-based.py depends on web.py (http://www.webpy.org/) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
#!/usr/bin/env python | ||
|
||
# (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. | ||
|
||
# include the parent directory in the Python path | ||
import sys,os.path | ||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||
|
||
from rdio import Rdio | ||
from rdio_consumer_credentials import * | ||
from urllib2 import HTTPError | ||
|
||
# create an instance of the Rdio object with our consumer credentials | ||
rdio = Rdio((RDIO_CONSUMER_KEY, RDIO_CONSUMER_SECRET)) | ||
|
||
try: | ||
# authenticate against the Rdio service | ||
url = rdio.begin_authentication('oob') | ||
print 'Go to: ' + url | ||
verifier = raw_input('Then enter the code: ').strip() | ||
rdio.complete_authentication(verifier) | ||
|
||
# find out what playlists you created | ||
myPlaylists = rdio.call('getPlaylists')['result']['owned'] | ||
|
||
# list them | ||
for playlist in myPlaylists: | ||
print '%(shortUrl)s\t%(name)s' % playlist | ||
except HTTPError, e: | ||
# if we have a protocol error, print it | ||
print e.read() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# you can get these by signing up for a developer account at: | ||
# http://developer.rdio.com/ | ||
RDIO_CONSUMER_KEY = '' | ||
RDIO_CONSUMER_SECRET = '' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
#!/usr/bin/env python | ||
|
||
# (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. | ||
|
||
# include the parent directory in the Python path | ||
import sys,os.path | ||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||
|
||
# import the rdio-simple library | ||
from rdio import Rdio | ||
# and our example credentials | ||
from rdio_consumer_credentials import * | ||
|
||
# import web.py | ||
import web | ||
|
||
import urllib2 | ||
|
||
urls = ( | ||
'/', 'root', | ||
'/login', 'login', | ||
'/callback', 'callback', | ||
'/logout', 'logout', | ||
) | ||
app = web.application(urls, globals()) | ||
|
||
class root: | ||
def GET(self): | ||
access_token = web.cookies().get('at') | ||
access_token_secret = web.cookies().get('ats') | ||
if access_token and access_token_secret: | ||
rdio = Rdio((RDIO_CONSUMER_KEY, RDIO_CONSUMER_SECRET), | ||
(access_token, access_token_secret)) | ||
# make sure that we can make an authenticated call | ||
|
||
try: | ||
currentUser = rdio.call('currentUser')['result'] | ||
except urllib2.HTTPError: | ||
# this almost certainly means that authentication has been revoked for the app. log out. | ||
raise web.seeother('/logout') | ||
|
||
myPlaylists = rdio.call('getPlaylists')['result']['owned'] | ||
|
||
response = ''' | ||
<html><head><title>Rdio-Simple Example</title></head><body> | ||
<p>%s's playlists:</p> | ||
<ul> | ||
''' % currentUser['firstName'] | ||
for playlist in myPlaylists: | ||
response += '''<li><a href="%(shortUrl)s">%(name)s</a></li>''' % playlist | ||
response += '''</ul><a href="/logout">Log out of Rdio</a></body></html>''' | ||
return response | ||
else: | ||
return ''' | ||
<html><head><title>Rdio-Simple Example</title></head><body> | ||
<a href="/login">Log into Rdio</a> | ||
</body></html> | ||
''' | ||
|
||
class login: | ||
def GET(self): | ||
# clear all of our auth cookies | ||
web.setcookie('at', '', expires=-1) | ||
web.setcookie('ats', '', expires=-1) | ||
web.setcookie('rt', '', expires=-1) | ||
web.setcookie('rts', '', expires=-1) | ||
# begin the authentication process | ||
rdio = Rdio((RDIO_CONSUMER_KEY, RDIO_CONSUMER_SECRET)) | ||
url = rdio.begin_authentication(callback_url = web.ctx.homedomain+'/callback') | ||
# save our request token in cookies | ||
web.setcookie('rt', rdio.token[0], expires=60*60*24) # expires in one day | ||
web.setcookie('rts', rdio.token[1], expires=60*60*24) # expires in one day | ||
# go to Rdio to authenticate the app | ||
raise web.seeother(url) | ||
|
||
class callback: | ||
def GET(self): | ||
# get the state from cookies and the query string | ||
request_token = web.cookies().get('rt') | ||
request_token_secret = web.cookies().get('rts') | ||
verifier = web.input()['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((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) | ||
web.setcookie('at', rdio.token[0], expires=60*60*24*14) # expires in two weeks | ||
web.setcookie('ats', rdio.token[1], expires=60*60*24*14) # expires in two weeks | ||
web.setcookie('rt', '', expires=-1) | ||
web.setcookie('rts', '', expires=-1) | ||
# go to the home page | ||
raise web.seeother('/') | ||
else: | ||
# we're missing something important | ||
raise web.seeother('/logout') | ||
|
||
class logout: | ||
def GET(self): | ||
# clear all of our auth cookies | ||
web.setcookie('at', '', expires=-1) | ||
web.setcookie('ats', '', expires=-1) | ||
web.setcookie('rt', '', expires=-1) | ||
web.setcookie('rts', '', expires=-1) | ||
# and go to the homepage | ||
raise web.seeother('/') | ||
|
||
|
||
if __name__ == "__main__": | ||
app.run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
#!/usr/bin/env python | ||
|
||
"""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" | ||
""" | ||
|
||
import time, random, hmac, hashlib, urllib, binascii, urlparse | ||
|
||
def om(consumer, url, post_params, token=None, method='POST', realm=None): | ||
"""A one-shot simple OAuth signature generator""" | ||
|
||
# the method must be upper-case | ||
method = method.upper() | ||
|
||
# turn the POST params into a list of tuples if it's not already | ||
if isinstance(post_params, list): | ||
params = list(post_params) # copy the params list since we'll be messing with it | ||
else: | ||
params = post_params.items() | ||
|
||
# normalize the URL | ||
parts = urlparse.urlparse(url) | ||
scheme, netloc, path, query = parts[:4] | ||
# Exclude default port numbers. | ||
if scheme == 'http' and netloc[-3:] == ':80': | ||
netloc = netloc[:-3] | ||
elif scheme == 'https' and netloc[-4:] == ':443': | ||
netloc = netloc[:-4] | ||
normalized_url = '%s://%s%s' % (scheme, netloc, path) | ||
|
||
# add query-string params (if any) to the params list | ||
params.extend(urlparse.parse_qsl(query)) | ||
|
||
# add OAuth params | ||
params.extend([ | ||
('oauth_version', '1.0'), | ||
('oauth_timestamp', str(int(time.time()))), | ||
('oauth_nonce', str(random.randint(0, 1000000))), | ||
('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 is not None: | ||
# include a token in params | ||
params.append(('oauth_token', token[0])) | ||
# and the token secret in the HMAC-SHA1 key | ||
hmac_key += token[1] | ||
|
||
# Sort lexicographically, first after key, then after value. | ||
params.sort() | ||
# UTF-8 and escape the key/value pairs | ||
def escape(s): return urllib.quote(unicode(s).encode('utf-8'), safe='~') | ||
params = [(escape(k), escape(v)) for k,v in params] | ||
# Combine key value pairs into a string. | ||
normalized_params = '&'.join(['%s=%s' % (k, v) for k, v in params]) | ||
|
||
# build the signature base string | ||
signature_base_string = (escape(method) + | ||
'&' + escape(normalized_url) + | ||
'&' + escape(normalized_params)) | ||
|
||
# HMAC-SHA1 | ||
hashed = hmac.new(hmac_key, signature_base_string, hashlib.sha1) | ||
|
||
# Calculate the digest base 64. | ||
oauth_signature = binascii.b2a_base64(hashed.digest())[:-1] | ||
|
||
# Build the Authorization header | ||
authorization_params = [('oauth_signature', oauth_signature)] | ||
if realm is not None: | ||
authorization_params.insert(0, ('realm', escape(realm))) | ||
oauth_params = frozenset(('oauth_version', 'oauth_timestamp', 'oauth_nonce', | ||
'oauth_signature_method', 'oauth_signature', | ||
'oauth_consumer_key', 'oauth_token')) | ||
authorization_params.extend([p for p in params if p[0] in oauth_params]) | ||
|
||
return 'OAuth ' + (', '.join(['%s="%s"'%p for p in authorization_params])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# (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. | ||
|
||
from om import om | ||
import urllib2, urllib | ||
from urlparse import parse_qsl | ||
try: | ||
import json | ||
except ImportError: | ||
import simplejson as json | ||
|
||
class Rdio: | ||
def __init__(self, consumer, token=None): | ||
self.__consumer = consumer | ||
self.token = token | ||
|
||
def __signed_post(self, url, params): | ||
auth = om(self.__consumer, url, params, self.token) | ||
req = urllib2.Request(url, urllib.urlencode(params), {'Authorization': auth}) | ||
res = urllib2.urlopen(req) | ||
return res.read() | ||
|
||
def begin_authentication(self, callback_url): | ||
# request a request token from the server | ||
response = self.__signed_post('http://api.rdio.com/oauth/request_token', | ||
{'oauth_callback': callback_url}) | ||
# parse the response | ||
parsed = dict(parse_qsl(response)) | ||
# save the token | ||
self.token = (parsed['oauth_token'], parsed['oauth_token_secret']) | ||
# return an URL that the user can use to authorize this application | ||
return parsed['login_url'] + '?oauth_token=' + parsed['oauth_token'] | ||
|
||
def complete_authentication(self, verifier): | ||
# request an access token | ||
response = self.__signed_post('http://api.rdio.com/oauth/access_token', | ||
{'oauth_verifier': verifier}) | ||
# parse the response | ||
parsed = dict(parse_qsl(response)) | ||
# save the token | ||
self.token = (parsed['oauth_token'], parsed['oauth_token_secret']) | ||
|
||
def call(self, method, params=dict()): | ||
# make a copy of the dict | ||
params = dict(params) | ||
# put the method in the dict | ||
params['method'] = method | ||
# call to the server and parse the response | ||
return json.loads(self.__signed_post('http://api.rdio.com/1/', params)) | ||
|