Fix Windows https issue.
[clinton/xbmc-groove.git] / resources / lib / GroovesharkAPI.py
CommitLineData
5b941088 1import socket, urllib, urllib2, pprint, md5, os, pickle, tempfile, time, re, simplejson, base64
2from blowfish import Blowfish
1413d357 3
f95afae7 4SESSION_EXPIRY = 1209600 # 2 weeks
44dcc6f4 5
c52e3923 6# Web app
7WEB_APP_URL = "http://xbmc-groove.appspot.com/"
8
1413d357 9# GrooveAPI constants
6842a53b 10THUMB_URL = 'http://beta.grooveshark.com/static/amazonart/m'
1413d357 11SONG_LIMIT = 25
12ALBUM_LIMIT = 15
13ARTIST_LIMIT = 15
7ce01be6 14SONG_SUFFIX = '.mp3'
1413d357 15
1413d357 16# Main API
17class GrooveAPI:
18
f95afae7 19 _ip = '0.0.0.0'
20 _country = ''
21 _sessionID = ''
22 _userID = 0
23 _lastSessionTime = 0
24 _lastStreamKey = ''
25 _lastStreamServerID = ''
5b941088 26 _key = md5.new(os.path.basename("GroovesharkAPI.py")).hexdigest()
1413d357 27
28 # Constructor
7ce01be6 29 def __init__(self):
30
1413d357 31 self.simplejson = simplejson
f95afae7 32 self.cacheDir = os.path.join(tempfile.gettempdir(), 'groovesharkapi')
7ce01be6 33 if os.path.isdir(self.cacheDir) == False:
34 os.makedirs(self.cacheDir)
35 print "Made " + self.cacheDir
44dcc6f4 36 self._getSavedSession()
f95afae7 37 # session ids last 2 weeks
38 if self._sessionID == '' or time.time()- self._lastSessionTime >= SESSION_EXPIRY:
39 self._sessionID = self._getSessionID()
f95afae7 40 if self._sessionID == '':
1413d357 41 raise StandardError('Failed to get session id')
42 else:
f95afae7 43 print "New GrooveAPI session id: " + self._sessionID
5b941088 44 self._ip = self._getIP()
45 self._country = self._getCountry()
44dcc6f4 46 self._setSavedSession()
0e7dbdf7 47
f95afae7 48 # Call to API
49 def _callRemote(self, method, params):
c52e3923 50 try:
51 res = self._getRemote(method, params)
52 url = res['url']
53 postData = res['postData']
5b941088 54 except:
55 print "Failed to get request URL and post data"
56 return []
57 try:
c52e3923 58 req = urllib2.Request(url, postData)
59 response = urllib2.urlopen(req)
60 result = response.read()
61 print "Response..."
62 pprint.pprint(result)
63 response.close()
64 result = simplejson.loads(result)
65 return result
5b941088 66 except urllib2.HTTPError, e:
67 print "HTTP error " + e.code
68 except urllib2.URLError, e:
69 print "URL error " + e.reason
c52e3923 70 except:
5b941088 71 print "Request to Grooveshark API failed"
c52e3923 72 return []
73
5b941088 74
c52e3923 75 # Get the API call
76 def _getRemote(self, method, params = {}):
77 postData = { "method": method, "sessionid": self._sessionID, "parameters": params }
78 postData = simplejson.dumps(postData)
5b941088 79
80 cipher = Blowfish(self._key)
81 cipher.initCTR()
82 encryptedPostData = cipher.encryptCTR(postData)
83 encryptedPostData = base64.urlsafe_b64encode(encryptedPostData)
84 url = WEB_APP_URL + "?postData=" + encryptedPostData
c52e3923 85 req = urllib2.Request(url)
86 response = urllib2.urlopen(req)
87 result = response.read()
88 print "Request..."
89 pprint.pprint(result)
90 response.close()
91 try:
92 result = simplejson.loads(result)
93 return result
94 except:
95 return []
96
1413d357 97 # Get a session id
98 def _getSessionID(self):
99 params = {}
100 result = self._callRemote('startSession', params)
f95afae7 101 if 'result' in result:
5b941088 102 self._lastSessionTime = time.time()
f95afae7 103 return result['result']['sessionID']
104 else:
105 return ''
1413d357 106
44dcc6f4 107 def _getSavedSession(self):
7ce01be6 108 path = os.path.join(self.cacheDir, 'session.dmp')
1413d357 109 try:
110 f = open(path, 'rb')
0e7dbdf7 111 session = pickle.load(f)
f95afae7 112 self._sessionID = session['sessionID']
113 self._lastSessionTime = session['lastSessionTime']
114 self._userID = session['userID']
115 self._ip = session['ip']
116 self._country = session['country']
1413d357 117 f.close()
118 except:
f95afae7 119 self._sessionID = ''
120 self._lastSessionTime = 0
121 self._userID = 0
122 self._ip = '0.0.0.0'
123 self._country = ''
1413d357 124 pass
1413d357 125
44dcc6f4 126 def _setSavedSession(self):
1413d357 127 try:
7ce01be6 128 # Create the directory if it doesn't exist.
129 if not os.path.exists(self.cacheDir):
130 os.makedirs(self.cacheDir)
131 path = os.path.join(self.cacheDir, 'session.dmp')
1413d357 132 f = open(path, 'wb')
f95afae7 133 session = { 'sessionID' : self._sessionID, 'lastSessionTime' : self._lastSessionTime, 'userID': self._userID, 'ip' : self._ip, 'country' : self._country }
0e7dbdf7 134 pickle.dump(session, f, protocol=pickle.HIGHEST_PROTOCOL)
1413d357 135 f.close()
136 except:
44dcc6f4 137 print "An error occurred during save session"
1413d357 138 pass
f95afae7 139
140 def _setParams(self, params):
141 try:
142 # Create the directory if it doesn't exist.
143 if not os.path.exists(self.cacheDir):
144 os.makedirs(self.cacheDir)
145 path = os.path.join(self.cacheDir, 'params.dmp')
146 f = open(path, 'wb')
147 pickle.dump(params, f, protocol=pickle.HIGHEST_PROTOCOL)
148 f.close()
149 except:
150 print "An error occurred during save params"
151 pass
1413d357 152
f95afae7 153 # Get IP
154 def _getIP(self):
155 try:
156 myip = urllib2.urlopen('http://whatismyip.org').read()
157 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):
158 print "IP is " + myip
159 return myip
160 except:
161 return '0.0.0.0'
1413d357 162
f95afae7 163 # Get country
164 def _getCountry(self):
165 params = { 'ip' : self._ip }
166 response = self._callRemote("getCountry", params)
167 return response['result']
168
169 # Get userid from name
170 def _getUserIDFromUsername(self, username):
171 result = self._callRemote('getUserIDFromUsername', {'username' : username})
172 if 'result' in result and result['result']['UserID'] > 0:
173 return result['result']['UserID']
174 else:
175 return 0
176
1413d357 177 # Authenticates the user for current API session
f95afae7 178 def _authenticate(self, login, password):
179 md5pwd = md5.new(password).hexdigest()
180 params = {'login': login, 'password': md5pwd}
181
182 result = self._callRemote('authenticate', params)
c52e3923 183 try:
184 uid = result['result']['UserID']
185 except:
186 uid = 0
f95afae7 187 if (uid > 0):
188 return uid
189 else:
190 return 0
191
192 # Check the service
193 def pingService(self,):
194 result = self._callRemote('pingService', {});
195 if 'result' in result and result['result'] != '':
196 return True
197 else:
198 return False
1413d357 199
200 # Login
201 def login(self, username, password):
f95afae7 202 if self._userID <= 0:
44dcc6f4 203 # Check cache
204 self._getSavedSession()
f95afae7 205 if self._userID <= 0:
206 self._userID = self._authenticate(username, password)
207 if self._userID > 0:
44dcc6f4 208 self._setSavedSession()
f95afae7 209 return self._userID
1413d357 210
211 # Logs the user out
212 def logout(self):
f95afae7 213 result = self._callRemote('logout', {'sessionID' : self._sessionID})
44dcc6f4 214 if 'result' in result and result['result']['success'] == True:
f95afae7 215 self._userID = 0
44dcc6f4 216 self._setSavedSession()
217 return True
218 return False
1413d357 219
f95afae7 220 # Gets a stream key and host to get song content
221 def getSubscriberStreamKey(self, songID):
222 params = { "songID": songID, "country": self._country }
223 response = self._callRemote("getSubscriberStreamKey", params)
224 try:
225 self._lastStreamKey = response["result"]["StreamKey"]
226 self._lastStreamServerID = response["result"]["StreamServerID"]
227 res = response["result"]
228 return res
229 except:
230 return False
231
1413d357 232 # Search for albums
233 def getArtistSearchResults(self, query, limit=ARTIST_LIMIT):
234 result = self._callRemote('getArtistSearchResults', {'query' : query,'limit' : limit})
235 if 'result' in result:
236 return self._parseArtists(result)
237 else:
238 return []
239
240 # Search for albums
241 def getAlbumSearchResults(self, query, limit=ALBUM_LIMIT):
242 result = self._callRemote('getAlbumSearchResults', {'query' : query,'limit' : limit})
243 if 'result' in result:
244 return self._parseAlbums(result)
245 else:
246 return []
247
248 # Search for songs
249 def getSongSearchResults(self, query, limit=SONG_LIMIT):
f95afae7 250 result = self._callRemote('getSongSearchResults', {'query' : query, 'country' : self._country, 'limit' : limit})
1413d357 251 if 'result' in result:
252 return self._parseSongs(result)
253 else:
254 return []
7ce01be6 255
256 # Get artists albums
257 def getArtistAlbums(self, artistID, limit=ALBUM_LIMIT):
258 result = self._callRemote('getArtistAlbums', {'artistID' : artistID})
259 if 'result' in result:
260 return self._parseAlbums(result, limit)
261 else:
262 return []
263
264 # Get album songs
265 def getAlbumSongs(self, albumID, limit=SONG_LIMIT):
f95afae7 266 result = self._callRemote('getAlbumSongs', {'albumID' : albumID, 'limit' : limit})
7ce01be6 267 if 'result' in result:
268 return self._parseSongs(result)
269 else:
270 return []
97289139 271
272 # Get artist's popular songs
273 def getArtistPopularSongs(self, artistID, limit = SONG_LIMIT):
274 result = self._callRemote('getArtistPopularSongs', {'artistID' : artistID})
275 if 'result' in result:
276 return self._parseSongs(result, limit)
277 else:
278 return []
279
1413d357 280 # Gets the popular songs
281 def getPopularSongsToday(self, limit=SONG_LIMIT):
282 result = self._callRemote('getPopularSongsToday', {'limit' : limit})
283 if 'result' in result:
7ce01be6 284 # Note limit is broken in the Grooveshark getPopularSongsToday method
285 return self._parseSongs(result, limit)
1413d357 286 else:
287 return []
288
289 # Gets the favorite songs of the logged-in user
290 def getUserFavoriteSongs(self):
f95afae7 291 if (self._userID == 0):
1413d357 292 return [];
f95afae7 293 result = self._callRemote('getUserFavoriteSongs', {})
1413d357 294 if 'result' in result:
295 return self._parseSongs(result)
296 else:
297 return []
7ce01be6 298
f95afae7 299 # Get song info
300 def getSongsInfo(self, songIDs):
301 result = self._callRemote('getSongsInfo', {'songIDs' : songIDs})
1413d357 302 if 'result' in result and 'SongID' in result['result']:
303 info = result['result']
304 if 'CoverArtFilename' in info and info['CoverArtFilename'] != None:
305 info['CoverArtFilename'] = THUMB_URL+info['CoverArtFilename'].encode('ascii', 'ignore')
306 else:
7ce01be6 307 info['CoverArtFilename'] = 'None'
1413d357 308 return info
309 else:
7ce01be6 310 return 'None'
f95afae7 311
312 # Add song to user favorites
313 def addUserFavoriteSong(self, songID):
314 if (self._userID == 0):
315 return False;
316 result = self._callRemote('addUserFavoriteSong', {'songID' : songID})
317 return result['result']['success']
318
319 # Remove songs from user favorites
320 def removeUserFavoriteSongs(self, songIDs):
321 if (self._userID == 0):
322 return False;
323 result = self._callRemote('removeUserFavoriteSongs', {'songIDs' : songIDs})
324 return result['result']['success']
1413d357 325
326 # Gets the playlists of the logged-in user
327 def getUserPlaylists(self):
f95afae7 328 if (self._userID == 0):
1413d357 329 return [];
f95afae7 330 result = self._callRemote('getUserPlaylists', {})
1413d357 331 if 'result' in result:
332 return self._parsePlaylists(result)
333 else:
334 return []
86f629ea 335
86f629ea 336 # Gets the playlists of the logged-in user
f95afae7 337 def getUserPlaylistsByUsername(self, username):
86f629ea 338 userID = self._getUserIDFromUsername(username)
339 if (userID > 0):
f95afae7 340 result = self._callRemote('getUserPlaylistsByUserID', {'userID' : userID})
86f629ea 341 if 'result' in result and result['result']['playlists'] != None:
342 playlists = result['result']['playlists']
343 return self._parsePlaylists(playlists)
344 else:
345 return []
1413d357 346
347 # Creates a playlist with songs
348 def createPlaylist(self, name, songIDs):
f95afae7 349 result = self._callRemote('createPlaylist', {'name' : name, 'songIDs' : songIDs})
40ac5d22 350 if 'result' in result and result['result']['success'] == True:
7ce01be6 351 return result['result']['playlistID']
1413d357 352 elif 'errors' in result:
353 return 0
354
355 # Sets the songs for a playlist
356 def setPlaylistSongs(self, playlistID, songIDs):
f95afae7 357 result = self._callRemote('setPlaylistSongs', {'playlistID' : playlistID, 'songIDs' : songIDs})
40ac5d22 358 if 'result' in result and result['result']['success'] == True:
7ce01be6 359 return True
1413d357 360 else:
7ce01be6 361 return False
1413d357 362
363 # Gets the songs of a playlist
364 def getPlaylistSongs(self, playlistID):
365 result = self._callRemote('getPlaylistSongs', {'playlistID' : playlistID});
366 if 'result' in result:
367 return self._parseSongs(result)
368 else:
369 return []
f95afae7 370
371
372 def playlistDelete(self, playlistId):
373 result = self._callRemote("deletePlaylist", {"playlistID": playlistId})
374 if 'fault' in result:
375 return 0
376 else:
377 return 1
7ce01be6 378
f95afae7 379 def playlistRename(self, playlistId, name):
380 result = self._callRemote("renamePlaylist", {"playlistID": playlistId, "name": name})
381 if 'fault' in result:
382 return 0
7ce01be6 383 else:
f95afae7 384 return 1
385
386 def getSimilarArtists(self, artistId, limit):
387 items = self._callRemote("getSimilarArtists", {"artistID": artistId, "limit": limit})
388 if 'result' in items:
389 i = 0
390 list = []
391 artists = items['result']['artists']
392 while(i < len(artists)):
393 s = artists[i]
394 list.append([s['artistName'].encode('ascii', 'ignore'),\
395 s['artistID']])
396 i = i + 1
397 return list
398 else:
399 return []
1413d357 400
f95afae7 401 # After 30s play time
402 def markStreamKeyOver30Secs(self):
403 params = { "streamKey" : self._lastStreamKey, "streamServerID" : self._lastStreamServerID }
404 self._callRemote("markStreamKeyOver30Secs", params)
405
406 # Song complete
407 def markSongComplete(self, songid):
408 params = { "songID" : songid, "streamKey" : self._lastStreamKey, "streamServerID" : self._lastStreamServerID }
409 self._callRemote("markSongComplete", params)
410
1413d357 411 # Extract song data
7ce01be6 412 def _parseSongs(self, items, limit=0):
1413d357 413 if 'result' in items:
414 i = 0
415 list = []
97289139 416 index = ''
417 l = -1
418 try:
419 if 'songs' in items['result'][0]:
420 l = len(items['result'][0]['songs'])
421 index = 'songs[]'
422 except: pass
423 try:
424 if l < 0 and 'songs' in items['result']:
425 l = len(items['result']['songs'])
426 index = 'songs'
427 except: pass
428 try:
429 if l < 0 and 'song' in items['result']:
430 l = 1
431 index = 'song'
432 except: pass
433 try:
434 if l < 0:
435 l = len(items['result'])
436 except: pass
437
7ce01be6 438 if limit > 0 and l > limit:
439 l = limit
1413d357 440 while(i < l):
97289139 441 if index == 'songs[]':
442 s = items['result'][0]['songs'][i]
443 elif index == 'songs':
1413d357 444 s = items['result'][index][i]
445 elif index == 'song':
446 s = items['result'][index]
447 else:
448 s = items['result'][i]
449 if 'CoverArtFilename' not in s:
f95afae7 450 info = self.getSongsInfo(s['SongID'])
1413d357 451 coverart = info['CoverArtFilename']
452 elif s['CoverArtFilename'] != None:
453 coverart = THUMB_URL+s['CoverArtFilename'].encode('ascii', 'ignore')
454 else:
7ce01be6 455 coverart = 'None'
1413d357 456 list.append([s['SongName'].encode('ascii', 'ignore'),\
457 s['SongID'],\
458 s['AlbumName'].encode('ascii', 'ignore'),\
459 s['AlbumID'],\
460 s['ArtistName'].encode('ascii', 'ignore'),\
461 s['ArtistID'],\
462 coverart])
463 i = i + 1
464 return list
465 else:
466 return []
467
468 # Extract artist data
469 def _parseArtists(self, items):
470 if 'result' in items:
471 i = 0
472 list = []
473 artists = items['result']['artists']
474 while(i < len(artists)):
475 s = artists[i]
476 list.append([s['ArtistName'].encode('ascii', 'ignore'),\
477 s['ArtistID']])
478 i = i + 1
479 return list
480 else:
481 return []
482
483 # Extract album data
7ce01be6 484 def _parseAlbums(self, items, limit=0):
1413d357 485 if 'result' in items:
486 i = 0
487 list = []
7ce01be6 488 try:
489 albums = items['result']['albums']
490 except:
491 res = items['result'][0]
492 albums = res['albums']
493 l = len(albums)
494 if limit > 0 and l > limit:
495 l = limit
496 while(i < l):
1413d357 497 s = albums[i]
498 if 'CoverArtFilename' in s and s['CoverArtFilename'] != None:
499 coverart = THUMB_URL+s['CoverArtFilename'].encode('ascii', 'ignore')
500 else:
7ce01be6 501 coverart = 'None'
1413d357 502 list.append([s['ArtistName'].encode('ascii', 'ignore'),\
503 s['ArtistID'],\
504 s['AlbumName'].encode('ascii', 'ignore'),\
505 s['AlbumID'],\
506 coverart])
507 i = i + 1
508 return list
509 else:
510 return []
511
512 def _parsePlaylists(self, items):
86f629ea 513 i = 0
514 list = []
1413d357 515 if 'result' in items:
f95afae7 516 playlists = items['result']['playlists']
86f629ea 517 elif len(items) > 0:
518 playlists = items
1413d357 519 else:
520 return []
f95afae7 521
86f629ea 522 while (i < len(playlists)):
523 s = playlists[i]
f95afae7 524 list.append([str(s['PlaylistName']).encode('ascii', 'ignore'), s['PlaylistID']])
86f629ea 525 i = i + 1
526 return list