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