Some playlist updates.
[clinton/xbmc-groove.git] / resources / lib / GrooveAPI.py
index f4eb865..ed22a24 100644 (file)
@@ -1,4 +1,4 @@
-import urllib2, md5, unicodedata, re, os, traceback, sys, pickle, socket
+import urllib2, md5, unicodedata, re, os, traceback, sys, pickle, socket, xbmc
 from operator import itemgetter, attrgetter
 
 class LoginTokensExceededError(Exception):
@@ -21,28 +21,25 @@ class SessionIDTryAgainError(Exception):
 
 class GrooveAPI:
        def __init__(self, enableDebug = False, isXbox = False):
-               if isXbox == True:
-                       import simplejson_xbox
-                       self.simplejson = simplejson_xbox
-                       print 'GrooveShark: Initialized as XBOX script'
-               else:
-                       import simplejson
-                       self.simplejson = simplejson
+               import simplejson
+               self.simplejson = simplejson
                timeout = 40
                socket.setdefaulttimeout(timeout)
                self.enableDebug = enableDebug
                self.loggedIn = 0
-               self.radioEnabled = 0
                self.userId = 0
-               self.seedArtists = []
-               self.frowns = []
-               self.songIDsAlreadySeen = []
-               self.recentArtists = []
-               self.rootDir = os.getcwd()
+               self.removeDuplicates = False
+
+               self.radioRecentSongs = []
+               self.radioRecentArtists = []
+               self.radioEnabled = False
+
+               self.dataDir = 'addon_data'
+               self.confDir = xbmc.translatePath(os.path.join('special://masterprofile/' + self.dataDir, os.path.basename(os.getcwd())))
                self.sessionID = self.getSavedSession()
                self.debug('Saved sessionID: ' + self.sessionID)
-               self.sessionID = self.getSessionFromAPI()
-               self.debug('API sessionID: ' + self.sessionID)
+               #self.sessionID = self.getSessionFromAPI()
+               #self.debug('API sessionID: ' + self.sessionID)
                if self.sessionID == '':
                        self.sessionID = self.startSession()
                        self.debug('Start() sessionID: ' + self.sessionID)
@@ -64,10 +61,16 @@ class GrooveAPI:
        def debug(self, msg):
                if self.enableDebug == True:
                        print msg
+
+       def setRemoveDuplicates(self, enable):
+               if enable == True or enable == 'true' or enable == 'True':
+                       self.removeDuplicates = True
+               else:
+                       self.removeDuplicates = False
                        
        def getSavedSession(self):
                sessionID = ''
-               path = os.path.join(self.rootDir, 'data', 'session.txt')
+               path = os.path.join(self.confDir, 'session', 'session.txt')
 
                try:
                        f = open(path, 'rb')
@@ -81,10 +84,10 @@ class GrooveAPI:
 
        def saveSession(self):                  
                try:
-                       dir = os.path.join(self.rootDir, 'data')
+                       dir = os.path.join(self.confDir, 'session')
                        # Create the 'data' directory if it doesn't exist.
                        if not os.path.exists(dir):
-                               os.mkdir(dir)
+                               os.makedirs(dir)
                        path = os.path.join(dir, 'session.txt')
                        f = open(path, 'wb')
                        pickle.dump(self.sessionID, f, protocol=pickle.HIGHEST_PROTOCOL)
@@ -98,10 +101,10 @@ class GrooveAPI:
                        
        def saveSettings(self):                 
                try:
-                       dir = os.path.join(self.rootDir, 'data')
+                       dir = os.path.join(self.confDir, 'data')
                        # Create the 'data' directory if it doesn't exist.
                        if not os.path.exists(dir):
-                               os.mkdir(dir)
+                               os.makedirs(dir)
                        path = os.path.join(dir, 'settings1.txt')
                        f = open(path, 'wb')
                        pickle.dump(self.settings, f, protocol=pickle.HIGHEST_PROTOCOL)
@@ -115,14 +118,7 @@ class GrooveAPI:
 
        def callRemote(self, method, params={}):
                data = {'header': {'sessionID': self.sessionID}, 'method': method, 'parameters': params}
-               #data = {'header': {'sessionID': None}, 'method': method, 'parameters': params}
                data = self.simplejson.dumps(data)
-               #proxy_support = urllib2.ProxyHandler({"http" : "http://wwwproxy.kom.aau.dk:3128"})
-               ## build a new opener with proxy details
-               #opener = urllib2.build_opener(proxy_support, urllib2.HTTPHandler)
-               ## install it
-               #urllib2.install_opener(opener)
-               #print data
                req = urllib2.Request("http://api.grooveshark.com/ws/1.0/?json")
                req.add_header('Host', 'api.grooveshark.com')
                req.add_header('Content-type', 'text/json')
@@ -135,6 +131,15 @@ class GrooveAPI:
                        result = self.simplejson.loads(result)
                        if 'fault' in result:
                                self.debug(result)
+                               if result['fault']['code'] == 8: #Session ID has expired. Get a new and try again if possible.
+                                       self.debug(result['fault']['message'])
+                                       self.sessionID = self.startSession()
+                                       if self.sessionID != '':
+                                               self.saveSession()
+                                               return self.callRemote(method, params)
+                                       else:
+                                               self.debug('GrooveShark: SessionID expired, but unable to get new')
+                                               return []
                        return result
                except:
                        return []
@@ -147,7 +152,7 @@ class GrooveAPI:
                if 'fault' in result:
                        return ''
                else:
-                       return result['header']['sessionID']
+                       return result['result']['sessionID']
 
        def sessionDestroy(self):
                return self.callRemote("session.destroy")
@@ -210,6 +215,7 @@ class GrooveAPI:
                        else:
                                self.loggedIn = 1
                                return self.userId
+
                        
        def loginExt(self, username, password):
                if self.loggedIn == 1:
@@ -278,6 +284,16 @@ class GrooveAPI:
                                return 0
                else:
                        return 0
+
+       def playlistCreateUnique(self, name, songIds):
+               if self.loggedIn == 1:
+                       result = self.callRemote("playlist.createunique", {"name": name, "songIDs": songIds})
+                       if 'result' in result:
+                               return result['result']['playlistID']
+                       else:
+                               return 0
+               else:
+                       return 0
                        
        def playlistGetSongs(self, playlistId, limit=25):
                result = self.callRemote("playlist.getSongs", {"playlistID": playlistId})
@@ -311,6 +327,16 @@ class GrooveAPI:
                                return 1
                else:
                        return 0
+               
+       def playlistDeleteSong(self, playlistId, position):
+               if self.loggedIn == 1:
+                       result = self.callRemote("playlist.removeSong", {"playlistID": playlistId, "position": position})
+                       if 'fault' in result:
+                               return 0
+                       else:
+                               return 1
+               else:
+                       return 0
                        
        def playlistReplace(self, playlistId, songIds):
                if self.loggedIn == 1:
@@ -322,84 +348,122 @@ class GrooveAPI:
                else:
                        return 0
 
-       def autoplayStartWithArtistIDs(self, artistIds):
-               result = self.callRemote("autoplay.startWithArtistIDs", {"artistIDs": artistIds})
-               if 'fault' in result:
-                       self.radioEnabled = 0
-                       return 0
-               else:
-                       self.radioEnabled = 1
-                       return 1                
-
-       def autoplayStart(self, songIds):
-               result = self.callRemote("autoplay.start", {"songIDs": songIds})
-               if 'fault' in result:
-                       self.radioEnabled = 0
-                       return 0
-               else:
-                       self.radioEnabled = 1
-                       return 1
-
-       def autoplayStop(self):
-               result = self.callRemote("autoplay.stop", {})
+       def radioStartArtists(self):
+               radio = self.getSavedRadio()
+               if radio == None:
+                       return False
+               result = self.callRemote("autoplay.startWithArtistIDs", {"artistIDs": radio['seedArtists']})
                if 'fault' in result:
-                       self.radioEnabled = 1
-                       return 0
+                       print "Cannot autoplay artists"
+                       self.radioEnabled = False
                else:
-                       self.radioEnabled = 0
-                       return 1
+                       self.radioEnabled = True
+               return self.radioEnabled
 
-       def autoplayGetNextSongEx(self, seedArtists = [], frowns = [], songIDsAlreadySeen = [], recentArtists = []):
-               result = self.callRemote("autoplay.getNextSongEx", {"seedArtists": seedArtists, "frowns": frowns, "songIDsAlreadySeen": songIDsAlreadySeen, "recentArtists": recentArtists})
+       def radioStartSongs(self):
+               radio = self.getSavedRadio()
+               if radio == None:
+                       return False
+               result = self.callRemote("autoplay.start", {"songIDs": radio['seedSongs']})
                if 'fault' in result:
-                       return []
+                       print "Cannot autoplay songs"
+                       self.radioEnabled = False
                else:
-                       return result
+                       self.radioEnabled = True
+               return self.radioEnabled
        
-       def radioGetNextSong(self):
-               if self.seedArtists == []:
-                       return []
+       def radioNextSong(self):
+               radio = self.getSavedRadio()
+               if radio == None:
+                       return None
                else:
-                       result = self.autoplayGetNextSongEx(self.seedArtists, self.frowns, self.songIDsAlreadySeen, self.recentArtists)
-#                      print result
+                       result = self.callRemote("autoplay.getNextSongEx", {"seedArtists": radio['seedArtists'], "frowns": radio['frowns'], "songIDsAlreadySeen": self.radioRecentSongs, "recentArtists": self.radioRecentArtists})
                        if 'fault' in result:
                                return []
                        else:
                                song = self.parseSongs(result)
-                               self.radioAlreadySeen(song[0][1])
+                               self.radioRecentSongs.append(song[0][1])
+                               self.radioRecentArtists.append(song[0][7])
                                return song
 
-       def radioFrown(self, songId):
-               self.frown.append(songId)
-
-       def radioAlreadySeen(self, songId):
-               self.songIDsAlreadySeen.append(songId)
-
-       def radioAddArtist(self, artistId):
-               self.seedArtists.append(artistId)
-
-       def radioStart(self, artists = [], frowns = []):
-               for artist in artists:
-                       self.seedArtists.append(artist)
-               for artist in frowns:
-                       self.frowns.append(artist)
-               if self.autoplayStartWithArtistIDs(self.seedArtists) == 1:
-                       self.radioEnabled = 1
-                       return 1
+       def radioFrown(self, songId = None):
+               radio = self.getSavedRadio()
+               if radio != None and songId != None:
+                       try:
+                               radio['frowns'].remove(songId)
+                       except: pass
+                       radio['frowns'].append(songId)
+                       return self.saveRadio(radio = radio)
                else:
-                       self.radioEnabled = 0
-                       return 0
-
-       def radioStop(self):
-               self.seedArtists = []
-               self.frowns = []
-               self.songIDsAlreadySeen = []
-               self.recentArtists = []
-               self.radioEnabled = 0
+                       return False
+
+       def radioArtist(self, artistId = None):
+               radio = self.getSavedRadio()
+               if radio != None and artistId != None:
+                       try:
+                               radio['seedArtists'].remove(artistId)
+                       except: pass
+                       radio['seedArtists'].append(artistId)
+                       print "Saved radio"
+                       return self.saveRadio(radio = radio)
+               else:
+                       print "Failed to get radio"
+                       return False
+
+       def radioSong(self, songId = None):
+               radio = self.getSavedRadio()
+               if radio != None and songId != None:
+                       try:
+                               radio['seedSongs'].remove(songId)
+                       except: pass
+                       radio['seedSongs'].append(songId)
+                       print "Saved radio"
+                       return self.saveRadio(radio = radio)
+               else:
+                       print "Failed to get radio"
+                       return False
 
        def radioTurnedOn(self):
                return self.radioEnabled
 
+       def getSavedRadio(self):
+               path = os.path.join(self.confDir, 'radio', 'radio.dmp')
+               try:
+                       f = open(path, 'rb')
+                       radio = pickle.load(f)
+                       f.close()
+                       print radio
+               except:
+                       print "Failed to open " + path
+                       radio = {}
+                       radio['seedSongs'] = []
+                       radio['seedArtists'] = []
+                       radio['frowns'] = []
+                       if self.saveRadio(radio) == False:
+                               return None
+               return radio
+
+       def saveRadio(self, radio): #blaher
+               if radio == {}:
+                       print 'Invalid radio'
+                       return False
+               try:
+                       dir = os.path.join(self.confDir, 'radio')
+                       # Create the 'data' directory if it doesn't exist.
+                       if not os.path.exists(dir):
+                               os.mkdir(dir)
+                       path = os.path.join(dir, 'radio.dmp')
+                       f = open(path, 'wb')
+                       pickle.dump(radio, f, protocol=pickle.HIGHEST_PROTOCOL)
+                       f.close()
+                       return True
+               except IOError, e:
+                       print 'There was an error while saving the radio pickle (%s)' % e
+                       return False
+               except:
+                       print "An unknown error occurred during save radio: " + str(sys.exc_info()[0])
+                       return False
+
        def favoriteSong(self, songID):
                return self.callRemote("song.favorite", {"songID": songID})
 
@@ -452,6 +516,10 @@ class GrooveAPI:
                list = self.parseAlbums(result)
                return list
                
+       def artistAbout(self, artistId):
+               result = self.callRemote("artist.about", {"artistID": artistId})
+               return result
+               
        def artistGetAlbums(self, artistId, limit, sortKey=2):
                result = self.callRemote("artist.getAlbums", {"artistID": artistId, "limit": limit})
                list = self.parseAlbums(result)
@@ -473,6 +541,19 @@ class GrooveAPI:
                list = self.parseSongs(result)
                return list
 
+       def artistGetSimilar(self, artistId, limit):
+               result = self.callRemote("artist.getSimilar", {"artistID": artistId, "limit": limit})
+               list = self.parseArtists(result)
+               return list
+
+       def songAbout(self, songId):
+               result = self.callRemote("song.about", {"songID": songId})
+               return result['result']['song']
+
+       def getVersion(self):
+               result = self.callRemote("service.getVersion", {})
+               return result
+
        def parseSongs(self, items):
                try:
                        if 'result' in items:
@@ -502,8 +583,9 @@ class GrooveAPI:
                                                        songName = s['songName'].encode('ascii', 'ignore')
                                                        albumName = s['albumName'].encode('ascii', 'ignore')
                                                        artistName = s['artistName'].encode('ascii', 'ignore')
-                                                       if (entry[0].lower() == songName.lower()) and (entry[3].lower() == albumName.lower()) and (entry[6].lower() == artistName.lower()):
-                                                               notIn = False
+                                                       if self.removeDuplicates == True:
+                                                               if (entry[0].lower() == songName.lower()) and (entry[3].lower() == albumName.lower()) and (entry[6].lower() == artistName.lower()):
+                                                                       notIn = False
                                                if notIn == True:
                                                        list.append([s['songName'].encode('ascii', 'ignore'),\
                                                        s['songID'],\
@@ -529,47 +611,74 @@ class GrooveAPI:
                        return []
 
        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:
+               try:
+                       if 'result' in items:
+                               i = 0
+                               list = []
+                               artists = items['result']['artists']
+                               while(i < len(artists)):
+                                       s = artists[i]
+                                       try:
+                                               list.append([s['artistName'].encode('ascii', 'ignore'),\
+                                               s['artistID']])
+                                       except:
+                                               print 'GrooveShark: Could not parse album number: ' + str(i)
+                                               traceback.print_exc()
+                                       i = i + 1
+                               return list
+                       else:
+                               return []
+               except:
+                       print 'GrooveShark: Could not parse artists. Got this:'
+                       traceback.print_exc()
                        return []
 
        def parseAlbums(self, items):
-               if 'result' in items:
-                       i = 0
-                       list = []
-                       albums = items['result']['albums']
-                       while(i < len(albums)):
-                               s = albums[i]
-                               list.append([s['artistName'].encode('ascii', 'ignore'),\
-                               s['artistID'],\
-                               s['albumName'].encode('ascii', 'ignore'),\
-                               s['albumID'],\
-                               s['image']['tiny'].encode('ascii', 'ignore')])
-                               i = i + 1
-                       return list
-               else:
+               try:
+                       if 'result' in items:
+                               i = 0
+                               list = []
+                               albums = items['result']['albums']
+                               while(i < len(albums)):
+                                       s = albums[i]
+                                       try: # Avoid ascii ancoding errors
+                                               list.append([s['artistName'].encode('ascii', 'ignore'),\
+                                               s['artistID'],\
+                                               s['albumName'].encode('ascii', 'ignore'),\
+                                               s['albumID'],\
+                                               s['image']['medium'].encode('ascii', 'ignore')])
+                                       except:
+                                               print 'GrooveShark: Could not parse album number: ' + str(i)
+                                               traceback.print_exc()
+                                       i = i + 1
+                               return list
+                       else:
+                               return []
+               except:
+                       print 'GrooveShark: Could not parse albums. Got this'
+                       traceback.print_exc()
                        return []
 
        def parsePlaylists(self, items):
-               if 'result' in items:
-                       i = 0
-                       list = []
-                       playlists = items['result']['playlists']
-                       while(i < len(playlists)):
-                               s = playlists[i]
-                               list.append([s['playlistID'],\
-                               s['playlistName'].encode('ascii', 'ignore'),\
-                               s['username'].encode('ascii', 'ignore')])
-                               i = i + 1
-                       return list
-               else:
+               try:
+                       if 'result' in items:
+                               i = 0
+                               list = []
+                               playlists = items['result']['playlists']
+                               while(i < len(playlists)):
+                                       s = playlists[i]
+                                       try: # Avoid ascii ancoding errors
+                                               list.append([s['playlistID'],\
+                                               s['playlistName'].encode('ascii', 'ignore'),\
+                                               s['username'].encode('ascii', 'ignore')])
+                                       except:
+                                               print 'GrooveShark: Could not parse playlist number: ' + str(i)
+                                               traceback.print_exc()
+                                       i = i + 1
+                               return list
+                       else:
+                               return []
+               except:
+                       print 'GrooveShark: Could not parse playlists. Got this:'
+                       print items
                        return []