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