dc3a63b69b0036f563590212ec3c5aeb19419149
[clinton/xbmc-groove.git] / resources / lib / GrooveAPI.py
1 import urllib2, md5, os, traceback, sys, pickle, socket, xbmc
2 from operator import itemgetter
3
4 class LoginTokensExceededError(Exception):
5 def __init__(self):
6 self.value = 'You have created to many tokens. Only 12 are allowed'
7 def __str__(self):
8 return repr(self.value)
9
10 class LoginUnknownError(Exception):
11 def __init__(self):
12 self.value = 'Unable to get a new session ID. Wait a few minutes and try again'
13 def __str__(self):
14 return repr(self.value)
15
16 class SessionIDTryAgainError(Exception):
17 def __init__(self):
18 self.value = 'Unable to get a new session ID. Wait a few minutes and try again'
19 def __str__(self):
20 return repr(self.value)
21
22 class GrooveAPI:
23 def __init__(self, enableDebug = False, isXbox = False):
24 import simplejson
25 self.simplejson = simplejson
26 timeout = 40
27 socket.setdefaulttimeout(timeout)
28 self.enableDebug = enableDebug
29 self.loggedIn = 0
30 self.userId = 0
31 self.removeDuplicates = False
32
33 self.radioRecentSongs = []
34 self.radioRecentArtists = []
35 self.radioEnabled = False
36
37 self.dataDir = 'addon_data'
38 self.confDir = xbmc.translatePath(os.path.join('special://masterprofile/' + self.dataDir, os.path.basename(os.getcwd())))
39 self.sessionID = self.getSavedSession()
40 self.debug('Saved sessionID: ' + self.sessionID)
41 #self.sessionID = self.getSessionFromAPI()
42 #self.debug('API sessionID: ' + self.sessionID)
43 if self.sessionID == '':
44 self.sessionID = self.startSession()
45 self.debug('Start() sessionID: ' + self.sessionID)
46 if self.sessionID == '':
47 self.debug('Could not get a sessionID. Try again in a few minutes')
48 raise SessionIDTryAgainError()
49 else:
50 self.saveSession()
51
52 self.debug('sessionID: ' + self.sessionID)
53
54 def __del__(self):
55 try:
56 if self.loggedIn == 1:
57 self.logout()
58 except:
59 pass
60
61 def debug(self, msg):
62 if self.enableDebug == True:
63 print msg
64
65 def setRemoveDuplicates(self, enable):
66 if enable == True or enable == 'true' or enable == 'True':
67 self.removeDuplicates = True
68 else:
69 self.removeDuplicates = False
70
71 def getSavedSession(self):
72 sessionID = ''
73 path = os.path.join(self.confDir, 'session', 'session.txt')
74
75 try:
76 f = open(path, 'rb')
77 sessionID = pickle.load(f)
78 f.close()
79 except:
80 sessionID = ''
81 pass
82
83 return sessionID
84
85 def saveSession(self):
86 try:
87 dir = os.path.join(self.confDir, 'session')
88 # Create the 'data' directory if it doesn't exist.
89 if not os.path.exists(dir):
90 os.makedirs(dir)
91 path = os.path.join(dir, 'session.txt')
92 f = open(path, 'wb')
93 pickle.dump(self.sessionID, f, protocol=pickle.HIGHEST_PROTOCOL)
94 f.close()
95 except IOError, e:
96 print 'There was an error while saving the session pickle (%s)' % e
97 pass
98 except:
99 print "An unknown error occured during save session: " + str(sys.exc_info()[0])
100 pass
101
102 def saveSettings(self):
103 try:
104 dir = os.path.join(self.confDir, 'data')
105 # Create the 'data' directory if it doesn't exist.
106 if not os.path.exists(dir):
107 os.makedirs(dir)
108 path = os.path.join(dir, 'settings1.txt')
109 f = open(path, 'wb')
110 pickle.dump(self.settings, f, protocol=pickle.HIGHEST_PROTOCOL)
111 f.close()
112 except IOError, e:
113 print 'There was an error while saving the settings pickle (%s)' % e
114 pass
115 except:
116 print "An unknown error occured during save settings\n"
117 pass
118
119 def callRemote(self, method, params={}):
120 data = {'header': {'sessionID': self.sessionID}, 'method': method, 'parameters': params}
121 data = self.simplejson.dumps(data)
122 req = urllib2.Request("http://api.grooveshark.com/ws/1.0/?json")
123 req.add_header('Host', 'api.grooveshark.com')
124 req.add_header('Content-type', 'text/json')
125 req.add_header('Content-length', str(len(data)))
126 req.add_data(data)
127 response = urllib2.urlopen(req)
128 result = response.read()
129 response.close()
130 try:
131 result = self.simplejson.loads(result)
132 if 'fault' in result:
133 self.debug(result)
134 if result['fault']['code'] == 8: #Session ID has expired. Get a new and try again if possible.
135 self.debug(result['fault']['message'])
136 self.sessionID = self.startSession()
137 if self.sessionID != '':
138 self.saveSession()
139 return self.callRemote(method, params)
140 else:
141 self.debug('GrooveShark: SessionID expired, but unable to get new')
142 return []
143 return result
144 except:
145 return []
146
147 def startSession(self):
148 response = urllib2.urlopen("http://www.moovida.com/services/grooveshark/session_start")
149 result = response.read()
150 result = self.simplejson.loads(result)
151 response.close()
152 if 'fault' in result:
153 return ''
154 else:
155 return result['result']['sessionID']
156
157 def sessionDestroy(self):
158 return self.callRemote("session.destroy")
159
160 def getSessionFromAPI(self):
161 result = self.callRemote("session.get")
162 if 'fault' in result:
163 return ''
164 else:
165 return result['header']['sessionID']
166
167 def getStreamURL(self, songID):
168 result = self.callRemote("song.getStreamUrlEx", {"songID": songID})
169 if 'result' in result:
170 return result['result']['url']
171 else:
172 return ''
173
174 def createUserAuthToken(self, username, password):
175 hashpass = md5.new(password).hexdigest()
176 hashpass = username + hashpass
177 hashpass = md5.new(hashpass).hexdigest()
178 result = self.callRemote("session.createUserAuthToken", {"username": username, "hashpass": hashpass})
179 if 'result' in result:
180 return result['result']['token'], result['result']['userID']
181 elif 'fault' in result:
182 if result['fault']['code'] == 256:
183 return -1 # Exceeded the number of allowed tokens. Should not happen
184 else:
185 return -2 # Unknown error
186 else:
187 return -2 # Unknown error
188
189 def destroyUserAuthToken(self, token):
190 self.callRemote("session.destroyAuthToken", {"token": token})
191
192 def loginViaAuthToken(self, token):
193 result = self.callRemote("session.loginViaAuthToken", {"token": token})
194 self.destroyUserAuthToken(token)
195 if 'result' in result:
196 self.userID = result['result']['userID']
197 return result['result']['userID']
198 else:
199 return 0
200
201 def login(self, username, password):
202 if self.loggedIn == 1:
203 return self.userId
204 result = self.createUserAuthToken(username, password)
205 if result == -1:
206 raise LoginTokensExceededError()
207 elif result == -2:
208 raise LoginUnknownError()
209 else:
210 self.token = result[0]
211 self.debug('Token:' + self.token)
212 self.userId = self.loginViaAuthToken(self.token)
213 if self.userId == 0:
214 raise LoginUnknownError()
215 else:
216 self.loggedIn = 1
217 return self.userId
218
219
220 def loginExt(self, username, password):
221 if self.loggedIn == 1:
222 return self.userId
223 token = md5.new(username.lower() + md5.new(password).hexdigest()).hexdigest()
224 result = self.callRemote("session.loginExt", {"username": username, "token": token})
225 if 'result' in result:
226 if 'userID' in result['result']:
227 self.loggedIn = 1
228 self.userId = result['result']['userID']
229 return result['result']['userID']
230 else:
231 return 0
232
233
234 def loginBasic(self, username, password):
235 if self.loggedIn == 1:
236 return self.userId
237 result = self.callRemote("session.login", {"username": username, "password": password})
238 if 'result' in result:
239 if 'userID' in result['result']:
240 self.loggedIn = 1
241 self.userId = result['result']['userID']
242 return result['result']['userID']
243 else:
244 return 0
245
246 def loggedInStatus(self):
247 return self.loggedIn
248
249 def logout(self):
250 self.callRemote("session.logout", {})
251 self.loggedIn = 0
252
253 def getSongInfo(self, songID):
254 return self.callRemote("song.about", {"songID": songID})['result']['song']
255
256 def userGetFavoriteSongs(self, userID):
257 result = self.callRemote("user.getFavoriteSongs", {"userID": userID})
258 list = self.parseSongs(result)
259 return list
260
261 def userGetPlaylists(self, limit=25):
262 if self.loggedIn == 1:
263 result = self.callRemote("user.getPlaylists", {"userID": self.userId, "limit": limit})
264 if 'result' in result:
265 playlists = result['result']['playlists']
266 else:
267 return []
268 i = 0
269 list = []
270 while(i < len(playlists)):
271 p = playlists[i]
272 list.append([p['playlistName'].encode('ascii', 'ignore'), p['playlistID']])
273 i = i + 1
274 return sorted(list, key=itemgetter(0))
275 else:
276 return []
277
278 def playlistCreate(self, name, about):
279 if self.loggedIn == 1:
280 result = self.callRemote("playlist.create", {"name": name, "about": about})
281 if 'result' in result:
282 return result['result']['playlistID']
283 else:
284 return 0
285 else:
286 return 0
287
288 def playlistCreateUnique(self, name, songIds):
289 if self.loggedIn == 1:
290 result = self.callRemote("playlist.createunique", {"name": name, "songIDs": songIds})
291 if 'result' in result:
292 return result['result']['playlistID']
293 else:
294 return 0
295 else:
296 return 0
297
298 def playlistGetSongs(self, playlistId, limit=25):
299 result = self.callRemote("playlist.getSongs", {"playlistID": playlistId})
300 list = self.parseSongs(result)
301 return list
302
303 def playlistDelete(self, playlistId):
304 if self.loggedIn == 1:
305 result = self.callRemote("playlist.delete", {"playlistID": playlistId})
306 if 'fault' in result:
307 return 0
308 else:
309 return 1
310 else:
311 return 0
312
313 def playlistRename(self, playlistId, name):
314 if self.loggedIn == 1:
315 result = self.callRemote("playlist.rename", {"playlistID": playlistId, "name": name})
316 if 'fault' in result:
317 return 0
318 else:
319 return 1
320 else:
321 return 0
322
323 def playlistClearSongs(self, playlistId):
324 if self.loggedIn == 1:
325 return self.callRemote("playlist.clearSongs", {"playlistID": playlistId})
326
327 def playlistAddSong(self, playlistId, songId, position):
328 if self.loggedIn == 1:
329 result = self.callRemote("playlist.addSong", {"playlistID": playlistId, "songID": songId, "position": position})
330 if 'fault' in result:
331 return 0
332 else:
333 return 1
334 else:
335 return 0
336
337 def playlistDeleteSong(self, playlistId, position):
338 if self.loggedIn == 1:
339 result = self.callRemote("playlist.removeSong", {"playlistID": playlistId, "position": position})
340 if 'fault' in result:
341 return 0
342 else:
343 return 1
344 else:
345 return 0
346
347 def playlistReplace(self, playlistId, songIds):
348 if self.loggedIn == 1:
349 result = self.callRemote("playlist.replace", {"playlistID": playlistId, "songIDs": songIds})
350 if 'fault' in result:
351 return 0
352 else:
353 return 1
354 else:
355 return 0
356
357 def radioStartArtists(self):
358 radio = self.getSavedRadio()
359 if radio == None:
360 return False
361 result = self.callRemote("autoplay.startWithArtistIDs", {"artistIDs": radio['seedArtists']})
362 if 'fault' in result:
363 print "Cannot autoplay artists"
364 self.radioEnabled = False
365 else:
366 self.radioEnabled = True
367 return self.radioEnabled
368
369 def radioStartSongs(self):
370 radio = self.getSavedRadio()
371 if radio == None:
372 return False
373 result = self.callRemote("autoplay.start", {"songIDs": radio['seedSongs']})
374 if 'fault' in result:
375 print "Cannot autoplay songs"
376 self.radioEnabled = False
377 else:
378 self.radioEnabled = True
379 return self.radioEnabled
380
381 def radioNextSong(self):
382 radio = self.getSavedRadio()
383 if radio == None:
384 return None
385 else:
386 result = self.callRemote("autoplay.getNextSongEx", {"seedArtists": radio['seedArtists'], "frowns": radio['frowns'], "songIDsAlreadySeen": self.radioRecentSongs, "recentArtists": self.radioRecentArtists})
387 if 'fault' in result:
388 return []
389 else:
390 song = self.parseSongs(result)
391 self.radioRecentSongs.append(song[0][1])
392 self.radioRecentArtists.append(song[0][7])
393 return song
394
395 def radioFrown(self, songId = None):
396 radio = self.getSavedRadio()
397 if radio != None and songId != None:
398 try:
399 radio['frowns'].remove(songId)
400 except: pass
401 radio['frowns'].append(songId)
402 return self.saveRadio(radio = radio)
403 else:
404 return False
405
406 def radioArtist(self, artistId = None):
407 radio = self.getSavedRadio()
408 if radio != None and artistId != None:
409 try:
410 radio['seedArtists'].remove(artistId)
411 except: pass
412 radio['seedArtists'].append(artistId)
413 print "Saved radio"
414 return self.saveRadio(radio = radio)
415 else:
416 print "Failed to get radio"
417 return False
418
419 def radioSong(self, songId = None):
420 radio = self.getSavedRadio()
421 if radio != None and songId != None:
422 try:
423 radio['seedSongs'].remove(songId)
424 except: pass
425 radio['seedSongs'].append(songId)
426 print "Saved radio"
427 return self.saveRadio(radio = radio)
428 else:
429 print "Failed to get radio"
430 return False
431
432 def radioTurnedOn(self):
433 return self.radioEnabled
434
435 def getSavedRadio(self):
436 path = os.path.join(self.confDir, 'radio', 'radio.dmp')
437 try:
438 f = open(path, 'rb')
439 radio = pickle.load(f)
440 f.close()
441 except:
442 print "Failed to open " + path
443 radio = {}
444 radio['seedSongs'] = []
445 radio['seedArtists'] = []
446 radio['frowns'] = []
447 if self.saveRadio(radio) == False:
448 return None
449 return radio
450
451 def saveRadio(self, radio): #blaher
452 if radio == {}:
453 print 'Invalid radio'
454 return False
455 try:
456 dir = os.path.join(self.confDir, 'radio')
457 # Create the 'data' directory if it doesn't exist.
458 if not os.path.exists(dir):
459 os.mkdir(dir)
460 path = os.path.join(dir, 'radio.dmp')
461 f = open(path, 'wb')
462 pickle.dump(radio, f, protocol=pickle.HIGHEST_PROTOCOL)
463 f.close()
464 return True
465 except IOError, e:
466 print 'There was an error while saving the radio pickle (%s)' % e
467 return False
468 except:
469 print "An unknown error occurred during save radio: " + str(sys.exc_info()[0])
470 return False
471
472 def favoriteSong(self, songID):
473 return self.callRemote("song.favorite", {"songID": songID})
474
475 def unfavoriteSong(self, songID):
476 return self.callRemote("song.unfavorite", {"songID": songID})
477
478 def getMethods(self):
479 return self.callRemote("service.getMethods")
480
481 def searchSongsExactMatch(self, songName, artistName, albumName):
482 result = self.callRemote("search.songExactMatch", {"songName": songName, "artistName": artistName, "albumName": albumName})
483 list = self.parseSongs(result)
484 return list
485
486 def searchSongs(self, query, limit, page=0, sortKey=6):
487 result = self.callRemote("search.songs", {"query": query, "limit": limit, "page:": page, "streamableOnly": 1})
488 list = self.parseSongs(result)
489 return list
490 #return sorted(list, key=itemgetter(sortKey))
491
492 def searchArtists(self, query, limit, sortKey=0):
493 result = self.callRemote("search.artists", {"query": query, "limit": limit, "streamableOnly": 1})
494 list = self.parseArtists(result)
495 return list
496 #return sorted(list, key=itemgetter(sortKey))
497
498 def searchAlbums(self, query, limit, sortKey=2):
499 result = self.callRemote("search.albums", {"query": query, "limit": limit, "streamableOnly": 1})
500 list = self.parseAlbums(result)
501 return list
502 #return sorted(list, key=itemgetter(sortKey))
503
504 def searchPlaylists(self, query, limit):
505 result = self.callRemote("search.playlists", {"query": query, "limit": limit, "streamableOnly": 1})
506 list = self.parsePlaylists(result)
507 return list
508
509 def popularGetSongs(self, limit):
510 result = self.callRemote("popular.getSongs", {"limit": limit})
511 list = self.parseSongs(result)
512 return list
513
514 def popularGetArtists(self, limit):
515 result = self.callRemote("popular.getArtists", {"limit": limit})
516 list = self.parseArtists(result)
517 return list
518
519 def popularGetAlbums(self, limit):
520 result = self.callRemote("popular.getAlbums", {"limit": limit})
521 list = self.parseAlbums(result)
522 return list
523
524 def artistAbout(self, artistId):
525 result = self.callRemote("artist.about", {"artistID": artistId})
526 return result
527
528 def artistGetAlbums(self, artistId, limit, sortKey=2):
529 result = self.callRemote("artist.getAlbums", {"artistID": artistId, "limit": limit})
530 list = self.parseAlbums(result)
531 return list
532 #return sorted(list, key=itemgetter(sortKey))
533
534 def artistGetVerifiedAlbums(self, artistId, limit):
535 result = self.callRemote("artist.getVerifiedAlbums", {"artistID": artistId, "limit": limit})
536 list = self.parseSongs(result)
537 return list
538
539 def albumGetSongs(self, albumId, limit):
540 result = self.callRemote("album.getSongs", {"albumID": albumId, "limit": limit})
541 list = self.parseSongs(result)
542 return list
543
544 def songGetSimilar(self, songId, limit):
545 result = self.callRemote("song.getSimilar", {"songID": songId, "limit": limit})
546 list = self.parseSongs(result)
547 return list
548
549 def artistGetSimilar(self, artistId, limit):
550 result = self.callRemote("artist.getSimilar", {"artistID": artistId, "limit": limit})
551 list = self.parseArtists(result)
552 return list
553
554 def songAbout(self, songId):
555 result = self.callRemote("song.about", {"songID": songId})
556 return result['result']['song']
557
558 def getVersion(self):
559 result = self.callRemote("service.getVersion", {})
560 return result
561
562 def parseSongs(self, items):
563 try:
564 if 'result' in items:
565 i = 0
566 list = []
567 if 'songs' in items['result']:
568 l = len(items['result']['songs'])
569 index = 'songs'
570 elif 'song' in items['result']:
571 l = 1
572 index = 'song'
573 else:
574 l = 0
575 index = ''
576 while(i < l):
577 if index == 'songs':
578 s = items['result'][index][i]
579 else:
580 s = items['result'][index]
581 if 'estDurationSecs' in s:
582 dur = s['estDurationSecs']
583 else:
584 dur = 0
585 try:
586 notIn = True
587 for entry in list:
588 songName = s['songName'].encode('ascii', 'ignore')
589 albumName = s['albumName'].encode('ascii', 'ignore')
590 artistName = s['artistName'].encode('ascii', 'ignore')
591 if self.removeDuplicates == True:
592 if (entry[0].lower() == songName.lower()) and (entry[3].lower() == albumName.lower()) and (entry[6].lower() == artistName.lower()):
593 notIn = False
594 if notIn == True:
595 list.append([s['songName'].encode('ascii', 'ignore'),\
596 s['songID'],\
597 dur,\
598 s['albumName'].encode('ascii', 'ignore'),\
599 s['albumID'],\
600 s['image']['tiny'].encode('ascii', 'ignore'),\
601 s['artistName'].encode('ascii', 'ignore'),\
602 s['artistID'],\
603 s['image']['small'].encode('ascii', 'ignore'),\
604 s['image']['medium'].encode('ascii', 'ignore')])
605 except:
606 print 'GrooveShark: Could not parse song number: ' + str(i)
607 traceback.print_exc()
608 i = i + 1
609 return list
610 else:
611 return []
612 pass
613 except:
614 print 'GrooveShark: Could not parse songs. Got this:'
615 traceback.print_exc()
616 return []
617
618 def parseArtists(self, items):
619 try:
620 if 'result' in items:
621 i = 0
622 list = []
623 artists = items['result']['artists']
624 while(i < len(artists)):
625 s = artists[i]
626 try:
627 list.append([s['artistName'].encode('ascii', 'ignore'),\
628 s['artistID']])
629 except:
630 print 'GrooveShark: Could not parse album number: ' + str(i)
631 traceback.print_exc()
632 i = i + 1
633 return list
634 else:
635 return []
636 except:
637 print 'GrooveShark: Could not parse artists. Got this:'
638 traceback.print_exc()
639 return []
640
641 def parseAlbums(self, items):
642 try:
643 if 'result' in items:
644 i = 0
645 list = []
646 albums = items['result']['albums']
647 while(i < len(albums)):
648 s = albums[i]
649 try: # Avoid ascii ancoding errors
650 list.append([s['artistName'].encode('ascii', 'ignore'),\
651 s['artistID'],\
652 s['albumName'].encode('ascii', 'ignore'),\
653 s['albumID'],\
654 s['image']['medium'].encode('ascii', 'ignore')])
655 except:
656 print 'GrooveShark: Could not parse album number: ' + str(i)
657 traceback.print_exc()
658 i = i + 1
659 return list
660 else:
661 return []
662 except:
663 print 'GrooveShark: Could not parse albums. Got this'
664 traceback.print_exc()
665 return []
666
667 def parsePlaylists(self, items):
668 try:
669 if 'result' in items:
670 i = 0
671 list = []
672 playlists = items['result']['playlists']
673 while(i < len(playlists)):
674 s = playlists[i]
675 try: # Avoid ascii ancoding errors
676 list.append([s['playlistID'],\
677 s['playlistName'].encode('ascii', 'ignore'),\
678 s['username'].encode('ascii', 'ignore')])
679 except:
680 print 'GrooveShark: Could not parse playlist number: ' + str(i)
681 traceback.print_exc()
682 i = i + 1
683 return list
684 else:
685 return []
686 except:
687 print 'GrooveShark: Could not parse playlists. Got this:'
688 print items
689 return []