0c09e2513d0e024bb2e835def83a1e1201a27484
[clinton/xbmc-groove.git] / resources / lib / GroovesharkAPI.py
1 import socket, hmac, urllib, urllib2, pprint, md5, os, pickle, tempfile, time, re, simplejson
2
3 SESSION_EXPIRY = 1209600 # 2 weeks
4
5 # Web app
6 WEB_APP_URL = "http://xbmc-groove.appspot.com/"
7
8 # GrooveAPI constants
9 THUMB_URL = 'http://beta.grooveshark.com/static/amazonart/m'
10 SONG_LIMIT = 25
11 ALBUM_LIMIT = 15
12 ARTIST_LIMIT = 15
13 SONG_SUFFIX = '.mp3'
14
15 # Main API
16 class GrooveAPI:
17
18 _ip = '0.0.0.0'
19 _country = ''
20 _sessionID = ''
21 _userID = 0
22 _lastSessionTime = 0
23 _lastStreamKey = ''
24 _lastStreamServerID = ''
25
26 # Constructor
27 def __init__(self):
28
29 self.simplejson = simplejson
30 socket.setdefaulttimeout(40)
31 self.cacheDir = os.path.join(tempfile.gettempdir(), 'groovesharkapi')
32 if os.path.isdir(self.cacheDir) == False:
33 os.makedirs(self.cacheDir)
34 print "Made " + self.cacheDir
35 self._getSavedSession()
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 == '':
42 raise StandardError('Failed to get session id')
43 else:
44 print "New GrooveAPI session id: " + self._sessionID
45 self._setSavedSession()
46
47 # Call to API
48 def _callRemote(self, method, params):
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
81 # Get a session id
82 def _getSessionID(self):
83 params = {}
84 result = self._callRemote('startSession', params)
85 self._lastSessionTime = time.time()
86 if 'result' in result:
87 return result['result']['sessionID']
88 else:
89 return ''
90
91 def _getSavedSession(self):
92 path = os.path.join(self.cacheDir, 'session.dmp')
93 try:
94 f = open(path, 'rb')
95 session = pickle.load(f)
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']
101 f.close()
102 except:
103 self._sessionID = ''
104 self._lastSessionTime = 0
105 self._userID = 0
106 self._ip = '0.0.0.0'
107 self._country = ''
108 pass
109
110 def _setSavedSession(self):
111 try:
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')
116 f = open(path, 'wb')
117 session = { 'sessionID' : self._sessionID, 'lastSessionTime' : self._lastSessionTime, 'userID': self._userID, 'ip' : self._ip, 'country' : self._country }
118 pickle.dump(session, f, protocol=pickle.HIGHEST_PROTOCOL)
119 f.close()
120 except:
121 print "An error occurred during save session"
122 pass
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
136
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'
146
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
161 # Authenticates the user for current API session
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)
167 try:
168 uid = result['result']['UserID']
169 except:
170 uid = 0
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
183
184 # Login
185 def login(self, username, password):
186 if self._userID <= 0:
187 # Check cache
188 self._getSavedSession()
189 if self._userID <= 0:
190 self._userID = self._authenticate(username, password)
191 if self._userID > 0:
192 self._setSavedSession()
193 return self._userID
194
195 # Logs the user out
196 def logout(self):
197 result = self._callRemote('logout', {'sessionID' : self._sessionID})
198 if 'result' in result and result['result']['success'] == True:
199 self._userID = 0
200 self._setSavedSession()
201 return True
202 return False
203
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
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):
234 result = self._callRemote('getSongSearchResults', {'query' : query, 'country' : self._country, 'limit' : limit})
235 if 'result' in result:
236 return self._parseSongs(result)
237 else:
238 return []
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):
250 result = self._callRemote('getAlbumSongs', {'albumID' : albumID, 'limit' : limit})
251 if 'result' in result:
252 return self._parseSongs(result)
253 else:
254 return []
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
264 # Gets the popular songs
265 def getPopularSongsToday(self, limit=SONG_LIMIT):
266 result = self._callRemote('getPopularSongsToday', {'limit' : limit})
267 if 'result' in result:
268 # Note limit is broken in the Grooveshark getPopularSongsToday method
269 return self._parseSongs(result, limit)
270 else:
271 return []
272
273 # Gets the favorite songs of the logged-in user
274 def getUserFavoriteSongs(self):
275 if (self._userID == 0):
276 return [];
277 result = self._callRemote('getUserFavoriteSongs', {})
278 if 'result' in result:
279 return self._parseSongs(result)
280 else:
281 return []
282
283 # Get song info
284 def getSongsInfo(self, songIDs):
285 result = self._callRemote('getSongsInfo', {'songIDs' : songIDs})
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:
291 info['CoverArtFilename'] = 'None'
292 return info
293 else:
294 return 'None'
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']
309
310 # Gets the playlists of the logged-in user
311 def getUserPlaylists(self):
312 if (self._userID == 0):
313 return [];
314 result = self._callRemote('getUserPlaylists', {})
315 if 'result' in result:
316 return self._parsePlaylists(result)
317 else:
318 return []
319
320 # Gets the playlists of the logged-in user
321 def getUserPlaylistsByUsername(self, username):
322 userID = self._getUserIDFromUsername(username)
323 if (userID > 0):
324 result = self._callRemote('getUserPlaylistsByUserID', {'userID' : userID})
325 if 'result' in result and result['result']['playlists'] != None:
326 playlists = result['result']['playlists']
327 return self._parsePlaylists(playlists)
328 else:
329 return []
330
331 # Creates a playlist with songs
332 def createPlaylist(self, name, songIDs):
333 result = self._callRemote('createPlaylist', {'name' : name, 'songIDs' : songIDs})
334 if 'result' in result and result['result']['success'] == True:
335 return result['result']['playlistID']
336 elif 'errors' in result:
337 return 0
338
339 # Sets the songs for a playlist
340 def setPlaylistSongs(self, playlistID, songIDs):
341 result = self._callRemote('setPlaylistSongs', {'playlistID' : playlistID, 'songIDs' : songIDs})
342 if 'result' in result and result['result']['success'] == True:
343 return True
344 else:
345 return False
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 []
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
362
363 def playlistRename(self, playlistId, name):
364 result = self._callRemote("renamePlaylist", {"playlistID": playlistId, "name": name})
365 if 'fault' in result:
366 return 0
367 else:
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 []
384
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
395 # Extract song data
396 def _parseSongs(self, items, limit=0):
397 if 'result' in items:
398 i = 0
399 list = []
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
422 if limit > 0 and l > limit:
423 l = limit
424 while(i < l):
425 if index == 'songs[]':
426 s = items['result'][0]['songs'][i]
427 elif index == 'songs':
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:
434 info = self.getSongsInfo(s['SongID'])
435 coverart = info['CoverArtFilename']
436 elif s['CoverArtFilename'] != None:
437 coverart = THUMB_URL+s['CoverArtFilename'].encode('ascii', 'ignore')
438 else:
439 coverart = 'None'
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
468 def _parseAlbums(self, items, limit=0):
469 if 'result' in items:
470 i = 0
471 list = []
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):
481 s = albums[i]
482 if 'CoverArtFilename' in s and s['CoverArtFilename'] != None:
483 coverart = THUMB_URL+s['CoverArtFilename'].encode('ascii', 'ignore')
484 else:
485 coverart = 'None'
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):
497 i = 0
498 list = []
499 if 'result' in items:
500 playlists = items['result']['playlists']
501 elif len(items) > 0:
502 playlists = items
503 else:
504 return []
505
506 while (i < len(playlists)):
507 s = playlists[i]
508 list.append([str(s['PlaylistName']).encode('ascii', 'ignore'), s['PlaylistID']])
509 i = i + 1
510 return list