X-Git-Url: https://git.hcoop.net/clinton/xbmc-groove.git/blobdiff_plain/6842a53b348daf8d14f73df6849e76466ddc1012..c50484564d64db190f22875d73750e824951dcdf:/resources/lib/GroovesharkAPI.py diff --git a/resources/lib/GroovesharkAPI.py b/resources/lib/GroovesharkAPI.py index b14c3d5..9a91245 100644 --- a/resources/lib/GroovesharkAPI.py +++ b/resources/lib/GroovesharkAPI.py @@ -1,6 +1,10 @@ -import socket, hmac, urllib, urllib2, pprint, md5, re, sha, time, random, os, pickle, uuid, tempfile +import socket, urllib, urllib2, pprint, md5, os, pickle, tempfile, time, re, simplejson, base64 +from blowfish import Blowfish -SESSION_EXPIRY = 518400 # 6 days in seconds +SESSION_EXPIRY = 1209600 # 2 weeks + +# Web app +WEB_APP_URL = "http://xbmc-groove.appspot.com/" # GrooveAPI constants THUMB_URL = 'http://beta.grooveshark.com/static/amazonart/m' @@ -9,288 +13,114 @@ ALBUM_LIMIT = 15 ARTIST_LIMIT = 15 SONG_SUFFIX = '.mp3' -# GrooveSong constants -DOMAIN = "grooveshark.com" -HOME_URL = "http://listen." + DOMAIN -API_URL = "http://cowbell." + DOMAIN + "/more.php" -SERVICE_URL = "http://cowbell." + DOMAIN + "/service.php" -TOKEN_URL = "http://cowbell." + DOMAIN + "/more.php" - -CLIENT_NAME = "gslite" -CLIENT_VERSION = "20101012.37" -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]+)"') -RANDOM_CHARS = "1234567890abcdef" - -# Get a song -class GrooveSong: - - def __init__(self): - - import simplejson - self.simplejson = simplejson - - self.cacheDir = os.path.join(tempfile.gettempdir(), 'groovesong') - self._lastSessionTime = 0 - self.uuid = self._getUUID() - - self._getSavedSession() - # session ids last 1 week - if self.sessionID == '' or time.time() - self._lastSessionTime >= SESSION_EXPIRY: - self.sessionID = self._getSession(HOME_URL) - if self.sessionID == '': - raise StandardError('Failed to get session id') - else: - print "New GrooveSong session id: " + self.sessionID - self._setSavedSession() - - # 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): - self._token = self._getCommunicationToken() - if self._token == None: - return None - - randomChars = "" - while 6 > len(randomChars): - randomChars = randomChars + random.choice(RANDOM_CHARS) - - token = sha.new(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 md5.new(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: - self._lastSessionTime = time.time() - return session.group(1) - else: - return None - - def _getSavedSession(self): - path = os.path.join(self.cacheDir, 'session.dmp') - try: - f = open(path, 'rb') - session = pickle.load(f) - self.sessionID = session['sessionID'] - self._lastSessionTime = session['lastSessionTime'] - f.close() - except: - self.sessionID = '' - self._lastSessionTime = 0 - pass - - def _setSavedSession(self): - try: - # Create the 'data' directory if it doesn't exist. - if not os.path.exists(self.cacheDir): - os.makedirs(self.cacheDir) - path = os.path.join(self.cacheDir, 'session.dmp') - f = open(path, 'wb') - session = {'sessionID' : self.sessionID, 'lastSessionTime' : self._lastSessionTime} - pickle.dump(session, f, protocol=pickle.HIGHEST_PROTOCOL) - f.close() - except: - print "An error occurred during save session" - pass - - # 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) - try: - self._lastStreamKey = response["result"]["streamKey"] - self._lastStreamServer = response["result"]["ip"] - self._lastStreamServerID = response["result"]["streamServerID"] - return True - except: - return False - - # Tells Grooveshark you have downloaded a song - def _markSongDownloaded(self, songID): - params = { - "streamKey": self._lastStreamKey, - "streamServerID": self._lastStreamServerID, - "songID": songID} - self._callRemote("markSongDownloaded", params) - - # Get the song URL - def getSongURL(self, songID): - if self._getStreamDetails(songID) == True: - postData = {"streamKey": self._lastStreamKey} - postData = urllib.urlencode(postData) - return "http://" + self._lastStreamServer + "/stream.php?" + str(postData) - else: - return '' - # Main API class GrooveAPI: - sessionID = '' - userID = 0 - host = 'api.grooveshark.com' - lastSessionTime = 0 + _ip = '0.0.0.0' + _country = '' + _sessionID = '' + _userID = 0 + _lastSessionTime = 0 + _lastStreamKey = '' + _lastStreamServerID = '' + _key = md5.new(os.path.basename("GroovesharkAPI.py")).hexdigest() # Constructor def __init__(self): - import simplejson self.simplejson = simplejson - socket.setdefaulttimeout(40) - - self.cacheDir = os.path.join(tempfile.gettempdir(), 'grooveapi') + self.cacheDir = os.path.join(tempfile.gettempdir(), 'groovesharkapi') if os.path.isdir(self.cacheDir) == False: os.makedirs(self.cacheDir) print "Made " + self.cacheDir - self._getSavedSession() - # session ids last 1 week - if self.sessionID == '' or time.time()- self.lastSessionTime >= SESSION_EXPIRY: - self.sessionID = self._getSessionID() - if self.sessionID == '': + # session ids last 2 weeks + if self._sessionID == '' or time.time()- self._lastSessionTime >= SESSION_EXPIRY: + self._sessionID = self._getSessionID() + if self._sessionID == '': raise StandardError('Failed to get session id') else: - print "New GrooveAPI session id: " + self.sessionID + print "New GrooveAPI session id: " + self._sessionID + self._ip = self._getIP() + self._country = self._getCountry() self._setSavedSession() - # 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 + # Call to API + def _callRemote(self, method, params): + try: + res = self._getRemote(method, params) + url = res['url'] + postData = res['postData'] + except: + print "Failed to get request URL and post data" + return [] + try: + req = urllib2.Request(url, postData) + response = urllib2.urlopen(req) + result = response.read() + print "Response..." + pprint.pprint(result) + response.close() + result = simplejson.loads(result) + return result + except urllib2.HTTPError, e: + print "HTTP error " + e.code + except urllib2.URLError, e: + print "URL error " + e.reason + except: + print "Request to Grooveshark API failed" + return [] + + + # Get the API call + def _getRemote(self, method, params = {}): + postData = { "method": method, "sessionid": self._sessionID, "parameters": params } + postData = simplejson.dumps(postData) - 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 + cipher = Blowfish(self._key) + cipher.initCTR() + encryptedPostData = cipher.encryptCTR(postData) + encryptedPostData = base64.urlsafe_b64encode(encryptedPostData) + url = WEB_APP_URL + "?postData=" + encryptedPostData req = urllib2.Request(url) response = urllib2.urlopen(req) result = response.read() - print "Response..." + print "Request..." pprint.pprint(result) response.close() try: - result = self.simplejson.loads(result) + result = simplejson.loads(result) return result except: - return [] + return [] # Get a session id def _getSessionID(self): params = {} result = self._callRemote('startSession', params) - self.lastSessionTime = time.time() - return result['result']['sessionID'] + if 'result' in result: + self._lastSessionTime = time.time() + return result['result']['sessionID'] + else: + return '' def _getSavedSession(self): path = os.path.join(self.cacheDir, 'session.dmp') try: f = open(path, 'rb') session = pickle.load(f) - self.sessionID = session['sessionID'] - self.lastSessionTime = session['lastSessionTime'] - self.userID = session['userID'] + self._sessionID = session['sessionID'] + self._lastSessionTime = session['lastSessionTime'] + self._userID = session['userID'] + self._ip = session['ip'] + self._country = session['country'] f.close() except: - self.sessionID = '' - self.lastSessionTime = 0 - self.userID = 0 + self._sessionID = '' + self._lastSessionTime = 0 + self._userID = 0 + self._ip = '0.0.0.0' + self._country = '' pass def _setSavedSession(self): @@ -300,44 +130,105 @@ class GrooveAPI: os.makedirs(self.cacheDir) path = os.path.join(self.cacheDir, 'session.dmp') f = open(path, 'wb') - session = { 'sessionID' : self.sessionID, 'lastSessionTime' : self.lastSessionTime, 'userID': self.userID} + session = { 'sessionID' : self._sessionID, 'lastSessionTime' : self._lastSessionTime, 'userID': self._userID, 'ip' : self._ip, 'country' : self._country } pickle.dump(session, f, protocol=pickle.HIGHEST_PROTOCOL) f.close() except: print "An error occurred during save session" pass + + def _setParams(self, params): + try: + # Create the directory if it doesn't exist. + if not os.path.exists(self.cacheDir): + os.makedirs(self.cacheDir) + path = os.path.join(self.cacheDir, 'params.dmp') + f = open(path, 'wb') + pickle.dump(params, f, protocol=pickle.HIGHEST_PROTOCOL) + f.close() + except: + print "An error occurred during save params" + pass - # Make user authentication token - def _getUserToken(self, username, password): - return md5.new(username.lower() + md5.new(password).hexdigest()).hexdigest() + # Get IP + def _getIP(self): + try: + myip = urllib2.urlopen('http://whatismyip.org').read() + if re.match("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", myip): + print "IP is " + myip + return myip + except: + return '0.0.0.0' + # Get country + def _getCountry(self): + params = { 'ip' : self._ip } + response = self._callRemote("getCountry", params) + return response['result'] + + # Get userid from name + def _getUserIDFromUsername(self, username): + result = self._callRemote('getUserIDFromUsername', {'username' : username}) + if 'result' in result and result['result']['UserID'] > 0: + return result['result']['UserID'] + else: + return 0 + # 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'] + def _authenticate(self, login, password): + md5pwd = md5.new(password).hexdigest() + params = {'login': login, 'password': md5pwd} + + result = self._callRemote('authenticate', params) + try: + uid = result['result']['UserID'] + except: + uid = 0 + if (uid > 0): + return uid + else: + return 0 + + # Check the service + def pingService(self,): + result = self._callRemote('pingService', {}); + if 'result' in result and result['result'] != '': + return True + else: + return False # Login def login(self, username, password): - if self.userID <= 0: + if self._userID <= 0: # Check cache self._getSavedSession() - if self.userID <= 0: - token = self._getUserToken(username, password) - self.userID = self._authenticateUser(username, token) - if self.userID > 0: + if self._userID <= 0: + self._userID = self._authenticate(username, password) + if self._userID > 0: self._setSavedSession() - return self.userID + return self._userID # Logs the user out def logout(self): - result = self._callRemote('logout', {'sessionID' : self.sessionID}) + result = self._callRemote('logout', {'sessionID' : self._sessionID}) if 'result' in result and result['result']['success'] == True: - self.userID = 0 + self._userID = 0 self._setSavedSession() return True return False + # Gets a stream key and host to get song content + def getSubscriberStreamKey(self, songID): + params = { "songID": songID, "country": self._country } + response = self._callRemote("getSubscriberStreamKey", params) + try: + self._lastStreamKey = response["result"]["StreamKey"] + self._lastStreamServerID = response["result"]["StreamServerID"] + res = response["result"] + return res + except: + return False + # Search for albums def getArtistSearchResults(self, query, limit=ARTIST_LIMIT): result = self._callRemote('getArtistSearchResults', {'query' : query,'limit' : limit}) @@ -356,7 +247,7 @@ class GrooveAPI: # Search for songs def getSongSearchResults(self, query, limit=SONG_LIMIT): - result = self._callRemote('getSongSearchResultsEx', {'query' : query, 'limit' : limit}) + result = self._callRemote('getSongSearchResults', {'query' : query, 'country' : self._country, 'limit' : limit}) if 'result' in result: return self._parseSongs(result) else: @@ -372,7 +263,7 @@ class GrooveAPI: # Get album songs def getAlbumSongs(self, albumID, limit=SONG_LIMIT): - result = self._callRemote('getAlbumSongsEx', {'albumID' : albumID, 'limit' : limit}) + result = self._callRemote('getAlbumSongs', {'albumID' : albumID, 'limit' : limit}) if 'result' in result: return self._parseSongs(result) else: @@ -397,31 +288,17 @@ class GrooveAPI: # Gets the favorite songs of the logged-in user def getUserFavoriteSongs(self): - if (self.userID == 0): + if (self._userID == 0): return []; - result = self._callRemote('getUserFavoriteSongs', {'sessionID' : self.sessionID}) + result = self._callRemote('getUserFavoriteSongs', {}) if 'result' in result: return self._parseSongs(result) else: return [] - - # Add song to user favorites - def addUserFavoriteSong(self, songID): - if (self.userID == 0): - return False; - result = self._callRemote('addUserFavoriteSong', {'sessionID' : self.sessionID, 'songID' : songID}) - return result['result']['success'] - # Get the url to link to a song on Grooveshark - def getSongURLFromSongID(self, songID): - song = GrooveSong() - url = song.getSongURL(songID) - print "Got song URL " + url - return url - - # Get the url to link to a song on Grooveshark - def getSongInfo(self, songID): - result = self._callRemote('getSongInfoEx', {'songID' : songID}) + # Get song info + def getSongsInfo(self, songIDs): + result = self._callRemote('getSongsInfo', {'songIDs' : songIDs}) if 'result' in result and 'SongID' in result['result']: info = result['result'] if 'CoverArtFilename' in info and info['CoverArtFilename'] != None: @@ -431,30 +308,36 @@ class GrooveAPI: return info else: return 'None' + + # Add song to user favorites + def addUserFavoriteSong(self, songID): + if (self._userID == 0): + return False; + result = self._callRemote('addUserFavoriteSong', {'songID' : songID}) + return result['result']['success'] + + # Remove songs from user favorites + def removeUserFavoriteSongs(self, songIDs): + if (self._userID == 0): + return False; + result = self._callRemote('removeUserFavoriteSongs', {'songIDs' : songIDs}) + return result['result']['success'] # Gets the playlists of the logged-in user def getUserPlaylists(self): - if (self.userID == 0): + if (self._userID == 0): return []; - result = self._callRemote('getUserPlaylists', {'sessionID' : self.sessionID}) + result = self._callRemote('getUserPlaylists', {}) if 'result' in result: return self._parsePlaylists(result) else: return [] - # Get userid from name - def _getUserIDFromUsername(self, username): - result = self._callRemote('getUserIDFromUsername', {'username' : username}) - if 'result' in result and result['result']['UserID'] > 0: - return result['result']['UserID'] - else: - return 0 - # Gets the playlists of the logged-in user - def getUserPlaylistsEx(self, username): + def getUserPlaylistsByUsername(self, username): userID = self._getUserIDFromUsername(username) if (userID > 0): - result = self._callRemote('getUserPlaylistsEx', {'userID' : userID}) + result = self._callRemote('getUserPlaylistsByUserID', {'userID' : userID}) if 'result' in result and result['result']['playlists'] != None: playlists = result['result']['playlists'] return self._parsePlaylists(playlists) @@ -463,7 +346,7 @@ class GrooveAPI: # Creates a playlist with songs def createPlaylist(self, name, songIDs): - result = self._callRemote('createPlaylist', {'name' : name, 'songIDs' : songIDs, 'sessionID' : self.sessionID}) + result = self._callRemote('createPlaylist', {'name' : name, 'songIDs' : songIDs}) if 'result' in result and result['result']['success'] == True: return result['result']['playlistID'] elif 'errors' in result: @@ -471,7 +354,7 @@ class GrooveAPI: # Sets the songs for a playlist def setPlaylistSongs(self, playlistID, songIDs): - result = self._callRemote('setPlaylistSongs', {'playlistID' : playlistID, 'songIDs' : songIDs, 'sessionID' : self.sessionID}) + result = self._callRemote('setPlaylistSongs', {'playlistID' : playlistID, 'songIDs' : songIDs}) if 'result' in result and result['result']['success'] == True: return True else: @@ -484,15 +367,47 @@ class GrooveAPI: return self._parseSongs(result) else: return [] + + + def playlistDelete(self, playlistId): + result = self._callRemote("deletePlaylist", {"playlistID": playlistId}) + if 'fault' in result: + return 0 + else: + return 1 - # Check the service - def pingService(self,): - result = self._callRemote('pingService', {}); - if 'result' in result and result['result'] != '': - return True + def playlistRename(self, playlistId, name): + result = self._callRemote("renamePlaylist", {"playlistID": playlistId, "name": name}) + if 'fault' in result: + return 0 else: - return False + return 1 + + def getSimilarArtists(self, artistId, limit): + items = self._callRemote("getSimilarArtists", {"artistID": artistId, "limit": limit}) + 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 [] + # After 30s play time + def markStreamKeyOver30Secs(self): + params = { "streamKey" : self._lastStreamKey, "streamServerID" : self._lastStreamServerID } + self._callRemote("markStreamKeyOver30Secs", params) + + # Song complete + def markSongComplete(self, songid): + params = { "songID" : songid, "streamKey" : self._lastStreamKey, "streamServerID" : self._lastStreamServerID } + self._callRemote("markSongComplete", params) + # Extract song data def _parseSongs(self, items, limit=0): if 'result' in items: @@ -532,7 +447,7 @@ class GrooveAPI: else: s = items['result'][i] if 'CoverArtFilename' not in s: - info = self.getSongInfo(s['SongID']) + info = self.getSongsInfo(s['SongID']) coverart = info['CoverArtFilename'] elif s['CoverArtFilename'] != None: coverart = THUMB_URL+s['CoverArtFilename'].encode('ascii', 'ignore') @@ -598,41 +513,14 @@ class GrooveAPI: i = 0 list = [] if 'result' in items: - playlists = items['result'] + playlists = items['result']['playlists'] elif len(items) > 0: playlists = items else: return [] - + while (i < len(playlists)): s = playlists[i] - list.append([s['Name'].encode('ascii', 'ignore'), s['PlaylistID']]) + list.append([str(s['PlaylistName']).encode('ascii', 'ignore'), s['PlaylistID']]) i = i + 1 return list - -# Test -#import sys -#res = [] -#groovesharkApi = GrooveAPI() -#res = groovesharkApi.pingService() -#res = groovesharkApi.login(sys.argv[1], sys.argv[2]) -#songIDs = ['23404546','23401810','23401157'] -#res = groovesharkApi.createPlaylist("Test") -#res = groovesharkApi.setPlaylistSongs(res, songIDs) -#res = groovesharkApi.getPlaylistSongs(42251632) -#res = groovesharkApi.getSongSearchResults('jimmy jazz', 3) -#res = groovesharkApi.getPopularSongsToday(3) -#res = groovesharkApi.getSongURLFromSongID('26579347') -#res = groovesharkApi.getAlbumSearchResults('london calling', 3) -#res = groovesharkApi.getArtistAlbums('52283') -#res = groovesharkApi.getArtistSearchResults('the clash', 3) -#res = groovesharkApi.getUserFavoriteSongs() -#res = groovesharkApi.getUserPlaylists() -#res = groovesharkApi.getSongInfo('27425375') -#res = groovesharkApi.getPlaylistSongs(40902662) -#res = groovesharkApi.addUserFavoriteSong('27425375') -#res = groovesharkApi.logout() -#res = groovesharkApi.getUserPlaylistsEx('stephendenham') -#res = groovesharkApi.getArtistPopularSongs('3707') -# -#pprint.pprint(res)