1 import urllib2
, pprint
, md5
, os
, pickle
, tempfile
, time
, re
, simplejson
, base64
2 from blowfish
import Blowfish
4 SESSION_EXPIRY
= 1209600 # 2 weeks
7 WEB_APP_URL
= "http://xbmc-groove.appspot.com/"
10 THUMB_URL
= 'http://beta.grooveshark.com/static/amazonart/m'
25 _lastStreamServerID
= ''
26 _key
= md5
.new(os
.path
.basename("GroovesharkAPI.py")).hexdigest()
31 self
.simplejson
= simplejson
32 self
.cacheDir
= os
.path
.join(tempfile
.gettempdir(), 'groovesharkapi')
33 if os
.path
.isdir(self
.cacheDir
) == False:
34 os
.makedirs(self
.cacheDir
)
35 print "Made " + self
.cacheDir
36 self
._getSavedSession
()
37 # session ids last 2 weeks
38 if self
._sessionID
== '' or time
.time()- self
._lastSessionTime
>= SESSION_EXPIRY
:
39 self
._sessionID
= self
._getSessionID
()
40 if self
._sessionID
== '':
41 raise StandardError('Failed to get session id')
43 print "New GrooveAPI session id: " + self
._sessionID
44 self
._ip
= self
._getIP
()
45 self
._country
= self
._getCountry
()
46 self
._setSavedSession
()
49 def _callRemote(self
, method
, params
):
51 res
= self
._getRemote
(method
, params
)
53 postData
= res
['postData']
55 print "Failed to get request URL and post data"
58 req
= urllib2
.Request(url
, postData
)
59 response
= urllib2
.urlopen(req
)
60 result
= response
.read()
64 result
= simplejson
.loads(result
)
66 except urllib2
.HTTPError
, e
:
67 print "HTTP error " + e
.code
68 except urllib2
.URLError
, e
:
69 print "URL error " + e
.reason
71 print "Request to Grooveshark API failed"
76 def _getRemote(self
, method
, params
= {}):
77 postData
= { "method": method
, "sessionid": self
._sessionID
, "parameters": params
}
78 postData
= simplejson
.dumps(postData
)
80 cipher
= Blowfish(self
._key
)
82 encryptedPostData
= cipher
.encryptCTR(postData
)
83 encryptedPostData
= base64
.urlsafe_b64encode(encryptedPostData
)
84 url
= WEB_APP_URL
+ "?postData=" + encryptedPostData
85 req
= urllib2
.Request(url
)
86 response
= urllib2
.urlopen(req
)
87 result
= response
.read()
92 result
= simplejson
.loads(result
)
98 def _getSessionID(self
):
100 result
= self
._callRemote
('startSession', params
)
101 if 'result' in result
:
102 self
._lastSessionTime
= time
.time()
103 return result
['result']['sessionID']
107 def _getSavedSession(self
):
108 path
= os
.path
.join(self
.cacheDir
, 'session.dmp')
111 session
= pickle
.load(f
)
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']
120 self
._lastSessionTime
= 0
126 def _setSavedSession(self
):
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')
133 session
= { 'sessionID' : self
._sessionID
, 'lastSessionTime' : self
._lastSessionTime
, 'userID': self
._userID
, 'ip' : self
._ip
, 'country' : self
._country
}
134 pickle
.dump(session
, f
, protocol
=pickle
.HIGHEST_PROTOCOL
)
137 print "An error occurred during save session"
140 def _setParams(self
, params
):
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')
147 pickle
.dump(params
, f
, protocol
=pickle
.HIGHEST_PROTOCOL
)
150 print "An error occurred during save params"
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
164 def _getCountry(self
):
165 params
= { 'ip' : self
._ip
}
166 response
= self
._callRemote
("getCountry", params
)
167 return response
['result']
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']
177 # Authenticates the user for current API session
178 def _authenticate(self
, login
, password
):
179 md5pwd
= md5
.new(password
).hexdigest()
180 params
= {'login': login
, 'password': md5pwd
}
182 result
= self
._callRemote
('authenticate', params
)
184 uid
= result
['result']['UserID']
193 def pingService(self
,):
194 result
= self
._callRemote
('pingService', {});
195 if 'result' in result
and result
['result'] != '':
201 def login(self
, username
, password
):
202 if self
._userID
<= 0:
204 self
._getSavedSession
()
205 if self
._userID
<= 0:
206 self
._userID
= self
._authenticate
(username
, password
)
208 self
._setSavedSession
()
213 result
= self
._callRemote
('logout', {'sessionID' : self
._sessionID
})
214 if 'result' in result
and result
['result']['success'] == True:
216 self
._setSavedSession
()
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
)
225 self
._lastStreamKey
= response
["result"]["StreamKey"]
226 self
._lastStreamServerID
= response
["result"]["StreamServerID"]
227 res
= response
["result"]
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
)
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
)
249 def getSongSearchResults(self
, query
, limit
=SONG_LIMIT
):
250 result
= self
._callRemote
('getSongSearchResults', {'query' : query
, 'country' : self
._country
, 'limit' : limit
})
251 if 'result' in result
:
252 return self
._parseSongs
(result
)
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
)
265 def getAlbumSongs(self
, albumID
, limit
=SONG_LIMIT
):
266 result
= self
._callRemote
('getAlbumSongs', {'albumID' : albumID
, 'limit' : limit
})
267 if 'result' in result
:
268 return self
._parseSongs
(result
)
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
)
280 # Gets the popular songs
281 def getPopularSongsToday(self
, limit
=SONG_LIMIT
):
282 result
= self
._callRemote
('getPopularSongsToday', {'limit' : limit
})
283 if 'result' in result
:
284 # Note limit is broken in the Grooveshark getPopularSongsToday method
285 return self
._parseSongs
(result
, limit
)
289 # Gets the favorite songs of the logged-in user
290 def getUserFavoriteSongs(self
):
291 if (self
._userID
== 0):
293 result
= self
._callRemote
('getUserFavoriteSongs', {})
294 if 'result' in result
:
295 return self
._parseSongs
(result
)
300 def getSongsInfo(self
, songIDs
):
301 result
= self
._callRemote
('getSongsInfo', {'songIDs' : songIDs
})
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')
307 info
['CoverArtFilename'] = 'None'
312 # Add song to user favorites
313 def addUserFavoriteSong(self
, songID
):
314 if (self
._userID
== 0):
316 result
= self
._callRemote
('addUserFavoriteSong', {'songID' : songID
})
317 return result
['result']['success']
319 # Remove songs from user favorites
320 def removeUserFavoriteSongs(self
, songIDs
):
321 if (self
._userID
== 0):
323 result
= self
._callRemote
('removeUserFavoriteSongs', {'songIDs' : songIDs
})
324 return result
['result']['success']
326 # Gets the playlists of the logged-in user
327 def getUserPlaylists(self
):
328 if (self
._userID
== 0):
330 result
= self
._callRemote
('getUserPlaylists', {})
331 if 'result' in result
:
332 return self
._parsePlaylists
(result
)
336 # Gets the playlists of the logged-in user
337 def getUserPlaylistsByUsername(self
, username
):
338 userID
= self
._getUserIDFromUsername
(username
)
340 result
= self
._callRemote
('getUserPlaylistsByUserID', {'userID' : userID
})
341 if 'result' in result
and result
['result']['playlists'] != None:
342 playlists
= result
['result']['playlists']
343 return self
._parsePlaylists
(playlists
)
347 # Creates a playlist with songs
348 def createPlaylist(self
, name
, songIDs
):
349 result
= self
._callRemote
('createPlaylist', {'name' : name
, 'songIDs' : songIDs
})
350 if 'result' in result
and result
['result']['success'] == True:
351 return result
['result']['playlistID']
352 elif 'errors' in result
:
355 # Sets the songs for a playlist
356 def setPlaylistSongs(self
, playlistID
, songIDs
):
357 result
= self
._callRemote
('setPlaylistSongs', {'playlistID' : playlistID
, 'songIDs' : songIDs
})
358 if 'result' in result
and result
['result']['success'] == True:
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
)
372 def playlistDelete(self
, playlistId
):
373 result
= self
._callRemote
("deletePlaylist", {"playlistID": playlistId
})
374 if 'fault' in result
:
379 def playlistRename(self
, playlistId
, name
):
380 result
= self
._callRemote
("renamePlaylist", {"playlistID": playlistId
, "name": name
})
381 if 'fault' in result
:
386 def getSimilarArtists(self
, artistId
, limit
):
387 items
= self
._callRemote
("getSimilarArtists", {"artistID": artistId
, "limit": limit
})
388 if 'result' in items
:
391 artists
= items
['result']['artists']
392 while(i
< len(artists
)):
394 list.append([s
['artistName'].encode('ascii', 'ignore'),\
401 # After 30s play time
402 def markStreamKeyOver30Secs(self
):
403 params
= { "streamKey" : self
._lastStreamKey
, "streamServerID" : self
._lastStreamServerID
}
404 self
._callRemote
("markStreamKeyOver30Secs", params
)
407 def markSongComplete(self
, songid
):
408 params
= { "songID" : songid
, "streamKey" : self
._lastStreamKey
, "streamServerID" : self
._lastStreamServerID
}
409 self
._callRemote
("markSongComplete", params
)
412 def _parseSongs(self
, items
, limit
=0):
413 if 'result' in items
:
419 if 'songs' in items
['result'][0]:
420 l
= len(items
['result'][0]['songs'])
424 if l
< 0 and 'songs' in items
['result']:
425 l
= len(items
['result']['songs'])
429 if l
< 0 and 'song' in items
['result']:
435 l
= len(items
['result'])
438 if limit
> 0 and l
> limit
:
441 if index
== 'songs[]':
442 s
= items
['result'][0]['songs'][i
]
443 elif index
== 'songs':
444 s
= items
['result'][index
][i
]
445 elif index
== 'song':
446 s
= items
['result'][index
]
448 s
= items
['result'][i
]
449 if 'CoverArtFilename' not in s
:
450 info
= self
.getSongsInfo(s
['SongID'])
451 coverart
= info
['CoverArtFilename']
452 elif s
['CoverArtFilename'] != None:
453 coverart
= THUMB_URL
+s
['CoverArtFilename'].encode('ascii', 'ignore')
456 list.append([s
['SongName'].encode('ascii', 'ignore'),\
458 s
['AlbumName'].encode('ascii', 'ignore'),\
460 s
['ArtistName'].encode('ascii', 'ignore'),\
468 # Extract artist data
469 def _parseArtists(self
, items
):
470 if 'result' in items
:
473 artists
= items
['result']['artists']
474 while(i
< len(artists
)):
476 list.append([s
['ArtistName'].encode('ascii', 'ignore'),\
484 def _parseAlbums(self
, items
, limit
=0):
485 if 'result' in items
:
489 albums
= items
['result']['albums']
491 res
= items
['result'][0]
492 albums
= res
['albums']
494 if limit
> 0 and l
> limit
:
498 if 'CoverArtFilename' in s
and s
['CoverArtFilename'] != None:
499 coverart
= THUMB_URL
+s
['CoverArtFilename'].encode('ascii', 'ignore')
502 list.append([s
['ArtistName'].encode('ascii', 'ignore'),\
504 s
['AlbumName'].encode('ascii', 'ignore'),\
512 def _parsePlaylists(self
, items
):
515 if 'result' in items
:
516 playlists
= items
['result']['playlists']
522 while (i
< len(playlists
)):
524 list.append([str(s
['PlaylistName']).encode('ascii', 'ignore'), s
['PlaylistID']])