From 1413d3577010bf704112da29456be81a9427c237 Mon Sep 17 00:00:00 2001 From: stephendenham Date: Thu, 13 Jan 2011 00:19:02 +0000 Subject: [PATCH] New API. git-svn-id: svn://svn.code.sf.net/p/xbmc-groove/code@28 2dec19e3-eb1d-4749-8193-008c8bba0994 --- resources/lib/GroovesharkAPI.py | 488 ++++++++++++++++++++++++++++++++ 1 file changed, 488 insertions(+) create mode 100644 resources/lib/GroovesharkAPI.py diff --git a/resources/lib/GroovesharkAPI.py b/resources/lib/GroovesharkAPI.py new file mode 100644 index 0000000..18dd631 --- /dev/null +++ b/resources/lib/GroovesharkAPI.py @@ -0,0 +1,488 @@ +import socket, hmac, urllib, urllib2, pprint, md5, uuid, re, hashlib, time, random, os, pickle + +# GrooveAPI constants +THUMB_URL = 'http://beta.grooveshark.com/static/amazonart/' +THUMB_URL_DEFAULT = 'http://grooveshark.com/webincludes/logo/Grooveshark_Logo_No-Text.png' +SONG_LIMIT = 25 +ALBUM_LIMIT = 15 +ARTIST_LIMIT = 15 + +# GrooveSong constants +DOMAIN = "grooveshark.com" +HOME_URL = "http://listen." + DOMAIN +TOKEN_URL = "http://cowbell." + DOMAIN + "/more.php" +API_URL = "http://cowbell." + DOMAIN + "/more.php" +SERVICE_URL = "http://cowbell." + DOMAIN + "/service.php" + +CLIENT_NAME = "gslite" #htmlshark #jsqueue +CLIENT_VERSION = "20101012.37" #"20100831.25" +HEADERS = {"Content-Type": "application/json", + "User-Agent": "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 (.NET CLR 3.5.30729)", + "Referer": "http://listen.grooveshark.com/main.swf?cowbell=fe87233106a6cef919a1294fb2c3c05f"} + +RE_SESSION = re.compile('"sessionID":"\s*?([A-z0-9]+)"') #re.compile('sessionID:\s*?\'([A-z0-9]+)\',') +RANDOM_CHARS = "1234567890abcdef" + +# Get a song +class GrooveSong: + + def __init__(self, cacheDir): + + import simplejson + self.simplejson = simplejson + + self.cacheDir = cacheDir + self._lastTokenTime = 0 + self.uuid = self._getUUID() + self.sessionID = self._getSession(HOME_URL) + if self.sessionID == None: + raise StandardError("Cannot get session id") + + # The actual call to the API + def _callRemote(self, method, params, type="default"): + postData = { + "header": { + "client": CLIENT_NAME, + "clientRevision": CLIENT_VERSION, + "uuid": self.uuid, + "session": self.sessionID}, + "country": {"IPR":"1021", "ID":"223", "CC1":"0", "CC2":"0", "CC3":"0", "CC4":"2147483648"}, + "privacy": 1, + "parameters": params, + "method": method} + + token = self._getMethodToken(method) + if token == None: + raise StandardError("Cannot get token") + + postData["header"]["token"] = token + if type == "service": + url = SERVICE_URL + "?" + method + else: + url = API_URL + "?" + method + + postData = self.simplejson.dumps(postData) + print "GrooveSong URL: " + url + request = urllib2.Request(url, postData, HEADERS) + + response = urllib2.urlopen(request).read() + try: + response = self.simplejson.loads(response) + print "GrooveSong Response..." + pprint.pprint(response) + except: + raise StandardError("API error: " + response) + try: + response["fault"] + except KeyError: + return response + else: + raise StandardError("API error: " + response["fault"]["message"]) + + # Generate a random uuid + def _getUUID(self): + return str(uuid.uuid4()) + + # Make a token ready for a request header + def _getMethodToken(self, method): + if (time.time() - self._lastTokenTime) >= 1000: + self._token = self._getCommunicationToken() + if self._token == None: + return None + self._lastTokenTime = time.time() + + randomChars = "" + while 6 > len(randomChars): + randomChars = randomChars + random.choice(RANDOM_CHARS) + + token = hashlib.sha1(method + ":" + self._token + ":quitStealinMahShit:" + randomChars).hexdigest() + return randomChars + token + + # Generate a communication token + def _getCommunicationToken(self): + params = {"secretKey": self._getSecretKey(self.sessionID)} + postData = { + "header": { + "client": CLIENT_NAME, + "clientRevision": CLIENT_VERSION, + "uuid": self.uuid, + "session": self.sessionID}, + "country": {"IPR":"1021", "ID":"223", "CC1":"0", "CC2":"0", "CC3":"0", "CC4":"2147483648"}, + "privacy": 1, + "parameters": params, + "method": "getCommunicationToken"} + + postData = self.simplejson.dumps(postData) + request = urllib2.Request(TOKEN_URL, postData, HEADERS) + response = urllib2.urlopen(request).read() + try: + response = self.simplejson.loads(response) + except: + raise StandardError("API error: " + response) + try: + response["fault"] + except KeyError: + return response["result"] + else: + return None + + # Generate a secret key from a sessionID + def _getSecretKey(self, sessionID): + return hashlib.md5(sessionID).hexdigest() + + # Get a session id from some HTML + def _getSession(self, html): + html = urllib2.urlopen(HOME_URL).read() + session = RE_SESSION.search(html) + if session: + return session.group(1) + else: + return None + + # Gets a stream key and host to get song content + def _getStreamDetails(self, songID): + params = { + "songID": songID, + "prefetch": False, + "mobile": False, + "country": {"IPR":"1021","ID":"223", "CC1":"0", "CC2":"0", "CC3":"0", "CC4":"2147483648"} + } + response = self._callRemote("getStreamKeyFromSongIDEx", params) + self._lastStreamKey = response["result"]["streamKey"] + self._lastStreamServer = response["result"]["ip"] + self._lastStreamServerID = response["result"]["streamServerID"] + + # Tells Grooveshark you have downloaded a song + def _markSongDownloaded(self, songID): + params = { + "streamKey": self._lastStreamKey, + "streamServerID": self._lastStreamServerID, + "songID": songID} + self._callRemote("markSongDownloaded", params) + + # Download a song to a temporary file + def getSongURL(self, songID): + filename = os.path.join(self.cacheDir, songID + '.mp3') + if os.path.isfile(filename) == False: + self._getStreamDetails(songID) + postData = {"streamKey": self._lastStreamKey} + postData = urllib.urlencode(postData) + urllib.FancyURLopener().retrieve( "http://" + self._lastStreamServer + "/stream.php", filename, data=postData) + self._markSongDownloaded(songID) + return filename + + +# Main API +class GrooveAPI: + + sessionID = '' + userID = 0 + host = 'api.grooveshark.com' + + # Constructor + def __init__(self, cacheDir): + import simplejson + self.simplejson = simplejson + socket.setdefaulttimeout(40) + self.cacheDir = cacheDir + self.sessionID = self._getSavedSessionID() + if self.sessionID == '': + self.sessionID = self._getSessionID() + if self.sessionID == '': + raise StandardError('Failed to get session id') + else: + self._setSavedSessionID() + # Sort keys + def _keySort(self, d): + return [(k,d[k]) for k in sorted(d.keys())] + + # Make a message sig + def _createMessageSig(self, method, params, secret): + args = self._keySort(params); + data = ''; + for arg in args: + data += str(arg[0]) + data += str(arg[1]) + data = method + data + + h = hmac.new(secret, data) + return h.hexdigest() + + # The actual call to the API + def _callRemote(self, method, params = {}): + url = 'http://%s/ws/2.1/?method=%s&%s&wsKey=wordpress&sig=%s&format=json' % (self.host, method, urllib.urlencode(params), self._createMessageSig(method, params, 'd6c59291620c6eaa5bf94da08fae0ecc')) + print url + req = urllib2.Request(url) + response = urllib2.urlopen(req) + result = response.read() + pprint.pprint(result) + response.close() + try: + result = self.simplejson.loads(result) + return result + except: + return [] + + # Get a session id + def _getSessionID(self): + params = {} + result = self._callRemote('startSession', params) + return result['result']['sessionID'] + + def _getSavedSessionID(self): + sessionID = '' + path = os.path.join(self.cacheDir, 'session', 'session.txt') + try: + f = open(path, 'rb') + sessionID = pickle.load(f) + f.close() + except: + sessionID = '' + pass + + return sessionID + + def _setSavedSessionID(self): + try: + dir = os.path.join(self.cacheDir, 'session') + # Create the 'data' directory if it doesn't exist. + if not os.path.exists(dir): + os.makedirs(dir) + path = os.path.join(dir, 'session.txt') + f = open(path, 'wb') + pickle.dump(self.sessionID, f, protocol=pickle.HIGHEST_PROTOCOL) + f.close() + except: + print "An error occured during save session" + pass + + # Make user authentication token + def _getUserToken(self, username, password): + return md5.new(username.lower() + md5.new(password).hexdigest()).hexdigest() + + # Authenticates the user for current API session + def _authenticateUser(self, username, token): + params = {'sessionID': self.sessionID, 'username': username, 'token': token} + result = self._callRemote('authenticateUser', params) + return result['result']['UserID'] + + # Login + def login(self, username, password): + token = self._getUserToken(username, password) + self.userID = self._authenticateUser(username, token) + return self.userID + + # Logs the user out + def logout(self): + self._callRemote('logout', {'sessionID' : self.sessionID}) + + # Return user id + def getUserID(self): + return self.userID + + # Search for albums + def getArtistSearchResults(self, query, limit=ARTIST_LIMIT): + result = self._callRemote('getArtistSearchResults', {'query' : query,'limit' : limit}) + if 'result' in result: + return self._parseArtists(result) + else: + return [] + + # Search for albums + def getAlbumSearchResults(self, query, limit=ALBUM_LIMIT): + result = self._callRemote('getAlbumSearchResults', {'query' : query,'limit' : limit}) + if 'result' in result: + return self._parseAlbums(result) + else: + return [] + + # Search for songs + def getSongSearchResults(self, query, limit=SONG_LIMIT): + result = self._callRemote('getSongSearchResultsEx', {'query' : query, 'limit' : limit}) + if 'result' in result: + return self._parseSongs(result) + else: + return [] + + # Gets the popular songs + def getPopularSongsToday(self, limit=SONG_LIMIT): + result = self._callRemote('getPopularSongsToday', {'limit' : limit}) + if 'result' in result: + return self._parseSongs(result) + else: + return [] + + # Gets the favorite songs of the logged-in user + def getUserFavoriteSongs(self): + if (self.userID == 0): + return []; + result = self._callRemote('getUserFavoriteSongs', {'sessionID' : self.sessionID}) + if 'result' in result: + return self._parseSongs(result) + else: + return [] + + # Get the url to link to a song on Grooveshark + def getSongURLFromSongID(self, songID): + song = GrooveSong(self.cacheDir) + return song.getSongURL(songID) + + # Get the url to link to a song on Grooveshark + def getSongInfo(self, songID): + result = self._callRemote('getSongInfoEx', {'songID' : songID}) + if 'result' in result and 'SongID' in result['result']: + info = result['result'] + if 'CoverArtFilename' in info and info['CoverArtFilename'] != None: + info['CoverArtFilename'] = THUMB_URL+info['CoverArtFilename'].encode('ascii', 'ignore') + else: + info['CoverArtFilename'] = THUMB_URL_DEFAULT + return info + else: + return '' + + # Gets the playlists of the logged-in user + def getUserPlaylists(self): + if (self.userID == 0): + return []; + result = self._callRemote('getUserPlaylists', {'sessionID' : self.sessionID}) + if 'result' in result: + return self._parsePlaylists(result) + else: + return [] + + # Creates a playlist with songs + def createPlaylist(self, name, songIDs): + result = self._callRemote('createPlaylist', {'name' : name, 'songIDs' : songIDs, 'sessionID' : self.sessionID}) + if 'result' in result and result['result']['success'] == 'true': + return result['result']['PlaylistID'] + elif 'errors' in result: + return 0 + + # Sets the songs for a playlist + def setPlaylistSongs(self, playlistID, songIDs): + result = self._callRemote('setPlaylistSongs', {'playlistID' : playlistID, 'songIDs' : songIDs, 'sessionID' : self.sessionID}) + if 'result' in result and result['result']['success'] == 'true': + return True; + else: + return False; + + # Gets the songs of a playlist + def getPlaylistSongs(self, playlistID): + result = self._callRemote('getPlaylistSongs', {'playlistID' : playlistID}); + if 'result' in result: + return self._parseSongs(result) + else: + return [] + + # Extract song data + def _parseSongs(self, items): + if 'result' in items: + i = 0 + list = [] + if 'songs' in items['result']: + l = len(items['result']['songs']) + index = 'songs' + elif 'song' in items['result']: + l = 1 + index = 'song' + else: + l = len(items['result']) + index = '' + while(i < l): + if index == 'songs': + s = items['result'][index][i] + elif index == 'song': + s = items['result'][index] + else: + s = items['result'][i] + if 'CoverArtFilename' not in s: + info = self.getSongInfo(s['SongID']) + coverart = info['CoverArtFilename'] + elif s['CoverArtFilename'] != None: + coverart = THUMB_URL+s['CoverArtFilename'].encode('ascii', 'ignore') + else: + coverart = THUMB_URL_DEFAULT + list.append([s['SongName'].encode('ascii', 'ignore'),\ + s['SongID'],\ + s['AlbumName'].encode('ascii', 'ignore'),\ + s['AlbumID'],\ + s['ArtistName'].encode('ascii', 'ignore'),\ + s['ArtistID'],\ + coverart]) + i = i + 1 + return list + else: + return [] + + # Extract artist data + def _parseArtists(self, items): + if 'result' in items: + i = 0 + list = [] + artists = items['result']['artists'] + while(i < len(artists)): + s = artists[i] + list.append([s['ArtistName'].encode('ascii', 'ignore'),\ + s['ArtistID']]) + i = i + 1 + return list + else: + return [] + + # Extract album data + def _parseAlbums(self, items): + if 'result' in items: + i = 0 + list = [] + albums = items['result']['albums'] + while(i < len(albums)): + s = albums[i] + if 'CoverArtFilename' in s and s['CoverArtFilename'] != None: + coverart = THUMB_URL+s['CoverArtFilename'].encode('ascii', 'ignore') + else: + coverart = THUMB_URL_DEFAULT + list.append([s['ArtistName'].encode('ascii', 'ignore'),\ + s['ArtistID'],\ + s['AlbumName'].encode('ascii', 'ignore'),\ + s['AlbumID'],\ + coverart]) + i = i + 1 + return list + else: + return [] + + def _parsePlaylists(self, items): + if 'result' in items: + i = 0 + list = [] + playlists = items['result'] + while(i < len(playlists)): + s = playlists[i] + list.append([s['PlaylistID'],\ + s['Name'].encode('ascii', 'ignore')]) + i = i + 1 + return list + else: + return [] + + + +# Test + +#res = [] +#groovesharkApi = GrooveAPI('/tmp') +#res = groovesharkApi.login('stephendenham', '*******') +#res = groovesharkApi.getSongSearchResults('jimmy jazz', 3) +#res = groovesharkApi.getPopularSongsToday() +#res = groovesharkApi.getSongURLFromSongID('27425375') +#res = groovesharkApi.getAlbumSearchResults('london calling', 3) +#res = groovesharkApi.getArtistSearchResults('the clash', 3) +#res = groovesharkApi.getUserFavoriteSongs() +#res = groovesharkApi.getUserPlaylists() +#res = groovesharkApi.getSongInfo('27425375') +#res = groovesharkApi.getPlaylistSongs(40902662) + +#pprint.pprint(res) + + + -- 2.20.1