Initial checkin.
[clinton/xbmc-groove.git] / resources / lib / GrooveAPI.py
1 import urllib2, md5, unicodedata, re, os, traceback, sys, pickle, socket
2 from operator import itemgetter, attrgetter
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 if isXbox == True:
25 import simplejson_xbox
26 self.simplejson = simplejson_xbox
27 print 'GrooveShark: Initialized as XBOX script'
28 else:
29 import simplejson
30 self.simplejson = simplejson
31 timeout = 40
32 socket.setdefaulttimeout(timeout)
33 self.enableDebug = enableDebug
34 self.loggedIn = 0
35 self.radioEnabled = 0
36 self.userId = 0
37 self.seedArtists = []
38 self.frowns = []
39 self.songIDsAlreadySeen = []
40 self.recentArtists = []
41 self.rootDir = os.getcwd()
42 self.sessionID = self.getSavedSession()
43 self.debug('Saved sessionID: ' + self.sessionID)
44 self.sessionID = self.getSessionFromAPI()
45 self.debug('API sessionID: ' + self.sessionID)
46 if self.sessionID == '':
47 self.sessionID = self.startSession()
48 self.debug('Start() sessionID: ' + self.sessionID)
49 if self.sessionID == '':
50 self.debug('Could not get a sessionID. Try again in a few minutes')
51 raise SessionIDTryAgainError()
52 else:
53 self.saveSession()
54
55 self.debug('sessionID: ' + self.sessionID)
56
57 def __del__(self):
58 try:
59 if self.loggedIn == 1:
60 self.logout()
61 except:
62 pass
63
64 def debug(self, msg):
65 if self.enableDebug == True:
66 print msg
67
68 def getSavedSession(self):
69 sessionID = ''
70 path = os.path.join(self.rootDir, 'data', 'session.txt')
71
72 try:
73 f = open(path, 'rb')
74 sessionID = pickle.load(f)
75 f.close()
76 except:
77 sessionID = ''
78 pass
79
80 return sessionID
81
82 def saveSession(self):
83 try:
84 dir = os.path.join(self.rootDir, 'data')
85 # Create the 'data' directory if it doesn't exist.
86 if not os.path.exists(dir):
87 os.mkdir(dir)
88 path = os.path.join(dir, 'session.txt')
89 f = open(path, 'wb')
90 pickle.dump(self.sessionID, f, protocol=pickle.HIGHEST_PROTOCOL)
91 f.close()
92 except IOError, e:
93 print 'There was an error while saving the session pickle (%s)' % e
94 pass
95 except:
96 print "An unknown error occured during save session: " + str(sys.exc_info()[0])
97 pass
98
99 def saveSettings(self):
100 try:
101 dir = os.path.join(self.rootDir, 'data')
102 # Create the 'data' directory if it doesn't exist.
103 if not os.path.exists(dir):
104 os.mkdir(dir)
105 path = os.path.join(dir, 'settings1.txt')
106 f = open(path, 'wb')
107 pickle.dump(self.settings, f, protocol=pickle.HIGHEST_PROTOCOL)
108 f.close()
109 except IOError, e:
110 print 'There was an error while saving the settings pickle (%s)' % e
111 pass
112 except:
113 print "An unknown error occured during save settings\n"
114 pass
115
116 def callRemote(self, method, params={}):
117 data = {'header': {'sessionID': self.sessionID}, 'method': method, 'parameters': params}
118 #data = {'header': {'sessionID': None}, 'method': method, 'parameters': params}
119 data = self.simplejson.dumps(data)
120 #proxy_support = urllib2.ProxyHandler({"http" : "http://wwwproxy.kom.aau.dk:3128"})
121 ## build a new opener with proxy details
122 #opener = urllib2.build_opener(proxy_support, urllib2.HTTPHandler)
123 ## install it
124 #urllib2.install_opener(opener)
125 #print data
126 req = urllib2.Request("http://api.grooveshark.com/ws/1.0/?json")
127 req.add_header('Host', 'api.grooveshark.com')
128 req.add_header('Content-type', 'text/json')
129 req.add_header('Content-length', str(len(data)))
130 req.add_data(data)
131 response = urllib2.urlopen(req)
132 result = response.read()
133 response.close()
134 try:
135 result = self.simplejson.loads(result)
136 if 'fault' in result:
137 self.debug(result)
138 return result
139 except:
140 return []
141
142 def startSession(self):
143 response = urllib2.urlopen("http://www.moovida.com/services/grooveshark/session_start")
144 result = response.read()
145 result = self.simplejson.loads(result)
146 response.close()
147 if 'fault' in result:
148 return ''
149 else:
150 return result['header']['sessionID']
151
152 def sessionDestroy(self):
153 return self.callRemote("session.destroy")
154
155 def getSessionFromAPI(self):
156 result = self.callRemote("session.get")
157 if 'fault' in result:
158 return ''
159 else:
160 return result['header']['sessionID']
161
162 def getStreamURL(self, songID):
163 result = self.callRemote("song.getStreamUrlEx", {"songID": songID})
164 if 'result' in result:
165 return result['result']['url']
166 else:
167 return ''
168
169 def createUserAuthToken(self, username, password):
170 hashpass = md5.new(password).hexdigest()
171 hashpass = username + hashpass
172 hashpass = md5.new(hashpass).hexdigest()
173 result = self.callRemote("session.createUserAuthToken", {"username": username, "hashpass": hashpass})
174 if 'result' in result:
175 return result['result']['token'], result['result']['userID']
176 elif 'fault' in result:
177 if result['fault']['code'] == 256:
178 return -1 # Exceeded the number of allowed tokens. Should not happen
179 else:
180 return -2 # Unknown error
181 else:
182 return -2 # Unknown error
183
184 def destroyUserAuthToken(self, token):
185 self.callRemote("session.destroyAuthToken", {"token": token})
186
187 def loginViaAuthToken(self, token):
188 result = self.callRemote("session.loginViaAuthToken", {"token": token})
189 self.destroyUserAuthToken(token)
190 if 'result' in result:
191 self.userID = result['result']['userID']
192 return result['result']['userID']
193 else:
194 return 0
195
196 def login(self, username, password):
197 if self.loggedIn == 1:
198 return self.userId
199 result = self.createUserAuthToken(username, password)
200 if result == -1:
201 raise LoginTokensExceededError()
202 elif result == -2:
203 raise LoginUnknownError()
204 else:
205 self.token = result[0]
206 self.debug('Token:' + self.token)
207 self.userId = self.loginViaAuthToken(self.token)
208 if self.userId == 0:
209 raise LoginUnknownError()
210 else:
211 self.loggedIn = 1
212 return self.userId
213
214 def loginExt(self, username, password):
215 if self.loggedIn == 1:
216 return self.userId
217 token = md5.new(username.lower() + md5.new(password).hexdigest()).hexdigest()
218 result = self.callRemote("session.loginExt", {"username": username, "token": token})
219 if 'result' in result:
220 if 'userID' in result['result']:
221 self.loggedIn = 1
222 self.userId = result['result']['userID']
223 return result['result']['userID']
224 else:
225 return 0
226
227
228 def loginBasic(self, username, password):
229 if self.loggedIn == 1:
230 return self.userId
231 result = self.callRemote("session.login", {"username": username, "password": password})
232 if 'result' in result:
233 if 'userID' in result['result']:
234 self.loggedIn = 1
235 self.userId = result['result']['userID']
236 return result['result']['userID']
237 else:
238 return 0
239
240 def loggedInStatus(self):
241 return self.loggedIn
242
243 def logout(self):
244 self.callRemote("session.logout", {})
245 self.loggedIn = 0
246
247 def getSongInfo(self, songID):
248 return self.callRemote("song.about", {"songID": songID})['result']['song']
249
250 def userGetFavoriteSongs(self, userID):
251 result = self.callRemote("user.getFavoriteSongs", {"userID": userID})
252 list = self.parseSongs(result)
253 return list
254
255 def userGetPlaylists(self, limit=25):
256 if self.loggedIn == 1:
257 result = self.callRemote("user.getPlaylists", {"userID": self.userId, "limit": limit})
258 if 'result' in result:
259 playlists = result['result']['playlists']
260 else:
261 return []
262 i = 0
263 list = []
264 while(i < len(playlists)):
265 p = playlists[i]
266 list.append([p['playlistName'].encode('ascii', 'ignore'), p['playlistID']])
267 i = i + 1
268 return sorted(list, key=itemgetter(0))
269 else:
270 return []
271
272 def playlistCreate(self, name, about):
273 if self.loggedIn == 1:
274 result = self.callRemote("playlist.create", {"name": name, "about": about})
275 if 'result' in result:
276 return result['result']['playlistID']
277 else:
278 return 0
279 else:
280 return 0
281
282 def playlistGetSongs(self, playlistId, limit=25):
283 result = self.callRemote("playlist.getSongs", {"playlistID": playlistId})
284 list = self.parseSongs(result)
285 return list
286
287 def playlistDelete(self, playlistId):
288 if self.loggedIn == 1:
289 return self.callRemote("playlist.delete", {"playlistID": playlistId})
290
291 def playlistRename(self, playlistId, name):
292 if self.loggedIn == 1:
293 result = self.callRemote("playlist.rename", {"playlistID": playlistId, "name": name})
294 if 'fault' in result:
295 return 0
296 else:
297 return 1
298 else:
299 return 0
300
301 def playlistClearSongs(self, playlistId):
302 if self.loggedIn == 1:
303 return self.callRemote("playlist.clearSongs", {"playlistID": playlistId})
304
305 def playlistAddSong(self, playlistId, songId, position):
306 if self.loggedIn == 1:
307 result = self.callRemote("playlist.addSong", {"playlistID": playlistId, "songID": songId, "position": position})
308 if 'fault' in result:
309 return 0
310 else:
311 return 1
312 else:
313 return 0
314
315 def playlistReplace(self, playlistId, songIds):
316 if self.loggedIn == 1:
317 result = self.callRemote("playlist.replace", {"playlistID": playlistId, "songIDs": songIds})
318 if 'fault' in result:
319 return 0
320 else:
321 return 1
322 else:
323 return 0
324
325 def autoplayStartWithArtistIDs(self, artistIds):
326 result = self.callRemote("autoplay.startWithArtistIDs", {"artistIDs": artistIds})
327 if 'fault' in result:
328 self.radioEnabled = 0
329 return 0
330 else:
331 self.radioEnabled = 1
332 return 1
333
334 def autoplayStart(self, songIds):
335 result = self.callRemote("autoplay.start", {"songIDs": songIds})
336 if 'fault' in result:
337 self.radioEnabled = 0
338 return 0
339 else:
340 self.radioEnabled = 1
341 return 1
342
343 def autoplayStop(self):
344 result = self.callRemote("autoplay.stop", {})
345 if 'fault' in result:
346 self.radioEnabled = 1
347 return 0
348 else:
349 self.radioEnabled = 0
350 return 1
351
352 def autoplayGetNextSongEx(self, seedArtists = [], frowns = [], songIDsAlreadySeen = [], recentArtists = []):
353 result = self.callRemote("autoplay.getNextSongEx", {"seedArtists": seedArtists, "frowns": frowns, "songIDsAlreadySeen": songIDsAlreadySeen, "recentArtists": recentArtists})
354 if 'fault' in result:
355 return []
356 else:
357 return result
358
359 def radioGetNextSong(self):
360 if self.seedArtists == []:
361 return []
362 else:
363 result = self.autoplayGetNextSongEx(self.seedArtists, self.frowns, self.songIDsAlreadySeen, self.recentArtists)
364 # print result
365 if 'fault' in result:
366 return []
367 else:
368 song = self.parseSongs(result)
369 self.radioAlreadySeen(song[0][1])
370 return song
371
372 def radioFrown(self, songId):
373 self.frown.append(songId)
374
375 def radioAlreadySeen(self, songId):
376 self.songIDsAlreadySeen.append(songId)
377
378 def radioAddArtist(self, artistId):
379 self.seedArtists.append(artistId)
380
381 def radioStart(self, artists = [], frowns = []):
382 for artist in artists:
383 self.seedArtists.append(artist)
384 for artist in frowns:
385 self.frowns.append(artist)
386 if self.autoplayStartWithArtistIDs(self.seedArtists) == 1:
387 self.radioEnabled = 1
388 return 1
389 else:
390 self.radioEnabled = 0
391 return 0
392
393 def radioStop(self):
394 self.seedArtists = []
395 self.frowns = []
396 self.songIDsAlreadySeen = []
397 self.recentArtists = []
398 self.radioEnabled = 0
399
400 def radioTurnedOn(self):
401 return self.radioEnabled
402
403 def favoriteSong(self, songID):
404 return self.callRemote("song.favorite", {"songID": songID})
405
406 def unfavoriteSong(self, songID):
407 return self.callRemote("song.unfavorite", {"songID": songID})
408
409 def getMethods(self):
410 return self.callRemote("service.getMethods")
411
412 def searchSongsExactMatch(self, songName, artistName, albumName):
413 result = self.callRemote("search.songExactMatch", {"songName": songName, "artistName": artistName, "albumName": albumName})
414 list = self.parseSongs(result)
415 return list
416
417 def searchSongs(self, query, limit, page=0, sortKey=6):
418 result = self.callRemote("search.songs", {"query": query, "limit": limit, "page:": page, "streamableOnly": 1})
419 list = self.parseSongs(result)
420 return list
421 #return sorted(list, key=itemgetter(sortKey))
422
423 def searchArtists(self, query, limit, sortKey=0):
424 result = self.callRemote("search.artists", {"query": query, "limit": limit, "streamableOnly": 1})
425 list = self.parseArtists(result)
426 return list
427 #return sorted(list, key=itemgetter(sortKey))
428
429 def searchAlbums(self, query, limit, sortKey=2):
430 result = self.callRemote("search.albums", {"query": query, "limit": limit, "streamableOnly": 1})
431 list = self.parseAlbums(result)
432 return list
433 #return sorted(list, key=itemgetter(sortKey))
434
435 def searchPlaylists(self, query, limit):
436 result = self.callRemote("search.playlists", {"query": query, "limit": limit, "streamableOnly": 1})
437 list = self.parsePlaylists(result)
438 return list
439
440 def popularGetSongs(self, limit):
441 result = self.callRemote("popular.getSongs", {"limit": limit})
442 list = self.parseSongs(result)
443 return list
444
445 def popularGetArtists(self, limit):
446 result = self.callRemote("popular.getArtists", {"limit": limit})
447 list = self.parseArtists(result)
448 return list
449
450 def popularGetAlbums(self, limit):
451 result = self.callRemote("popular.getAlbums", {"limit": limit})
452 list = self.parseAlbums(result)
453 return list
454
455 def artistGetAlbums(self, artistId, limit, sortKey=2):
456 result = self.callRemote("artist.getAlbums", {"artistID": artistId, "limit": limit})
457 list = self.parseAlbums(result)
458 return list
459 #return sorted(list, key=itemgetter(sortKey))
460
461 def artistGetVerifiedAlbums(self, artistId, limit):
462 result = self.callRemote("artist.getVerifiedAlbums", {"artistID": artistId, "limit": limit})
463 list = self.parseSongs(result)
464 return list
465
466 def albumGetSongs(self, albumId, limit):
467 result = self.callRemote("album.getSongs", {"albumID": albumId, "limit": limit})
468 list = self.parseSongs(result)
469 return list
470
471 def songGetSimilar(self, songId, limit):
472 result = self.callRemote("song.getSimilar", {"songID": songId, "limit": limit})
473 list = self.parseSongs(result)
474 return list
475
476 def parseSongs(self, items):
477 try:
478 if 'result' in items:
479 i = 0
480 list = []
481 if 'songs' in items['result']:
482 l = len(items['result']['songs'])
483 index = 'songs'
484 elif 'song' in items['result']:
485 l = 1
486 index = 'song'
487 else:
488 l = 0
489 index = ''
490 while(i < l):
491 if index == 'songs':
492 s = items['result'][index][i]
493 else:
494 s = items['result'][index]
495 if 'estDurationSecs' in s:
496 dur = s['estDurationSecs']
497 else:
498 dur = 0
499 try:
500 notIn = True
501 for entry in list:
502 songName = s['songName'].encode('ascii', 'ignore')
503 albumName = s['albumName'].encode('ascii', 'ignore')
504 artistName = s['artistName'].encode('ascii', 'ignore')
505 if (entry[0].lower() == songName.lower()) and (entry[3].lower() == albumName.lower()) and (entry[6].lower() == artistName.lower()):
506 notIn = False
507 if notIn == True:
508 list.append([s['songName'].encode('ascii', 'ignore'),\
509 s['songID'],\
510 dur,\
511 s['albumName'].encode('ascii', 'ignore'),\
512 s['albumID'],\
513 s['image']['tiny'].encode('ascii', 'ignore'),\
514 s['artistName'].encode('ascii', 'ignore'),\
515 s['artistID'],\
516 s['image']['small'].encode('ascii', 'ignore'),\
517 s['image']['medium'].encode('ascii', 'ignore')])
518 except:
519 print 'GrooveShark: Could not parse song number: ' + str(i)
520 traceback.print_exc()
521 i = i + 1
522 return list
523 else:
524 return []
525 pass
526 except:
527 print 'GrooveShark: Could not parse songs. Got this:'
528 traceback.print_exc()
529 return []
530
531 def parseArtists(self, items):
532 if 'result' in items:
533 i = 0
534 list = []
535 artists = items['result']['artists']
536 while(i < len(artists)):
537 s = artists[i]
538 list.append([s['artistName'].encode('ascii', 'ignore'),\
539 s['artistID']])
540 i = i + 1
541 return list
542 else:
543 return []
544
545 def parseAlbums(self, items):
546 if 'result' in items:
547 i = 0
548 list = []
549 albums = items['result']['albums']
550 while(i < len(albums)):
551 s = albums[i]
552 list.append([s['artistName'].encode('ascii', 'ignore'),\
553 s['artistID'],\
554 s['albumName'].encode('ascii', 'ignore'),\
555 s['albumID'],\
556 s['image']['tiny'].encode('ascii', 'ignore')])
557 i = i + 1
558 return list
559 else:
560 return []
561
562 def parsePlaylists(self, items):
563 if 'result' in items:
564 i = 0
565 list = []
566 playlists = items['result']['playlists']
567 while(i < len(playlists)):
568 s = playlists[i]
569 list.append([s['playlistID'],\
570 s['playlistName'].encode('ascii', 'ignore'),\
571 s['username'].encode('ascii', 'ignore')])
572 i = i + 1
573 return list
574 else:
575 return []