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