767c0fb4c042ded9629aa04151c3c816eaf4d370
1 # Copyright 2011 Stephen Denham
3 # This file is part of xbmc-groove.
5 # xbmc-groove is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # xbmc-groove is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with xbmc-groove. If not, see <http://www.gnu.org/licenses/>.
18 import urllib2
, pprint
, md5
, os
, pickle
, tempfile
, time
, re
, simplejson
, base64
19 from blowfish
import Blowfish
21 SESSION_EXPIRY
= 1209600 # 2 weeks
24 WEB_APP_URL
= "http://xbmc-groove.appspot.com/"
27 THUMB_URL
= 'http://beta.grooveshark.com/static/amazonart/m'
42 _lastStreamServerID
= ''
43 _key
= md5
.new(os
.path
.basename("GroovesharkAPI.py")).hexdigest()
49 self
.simplejson
= simplejson
50 self
.cacheDir
= os
.path
.join(tempfile
.gettempdir(), 'groovesharkapi')
51 if os
.path
.isdir(self
.cacheDir
) == False:
52 os
.makedirs(self
.cacheDir
)
54 print "Made " + self
.cacheDir
55 self
._getSavedSession
()
56 # session ids last 2 weeks
57 if self
._sessionID
== '' or time
.time()- self
._lastSessionTime
>= SESSION_EXPIRY
:
58 self
._sessionID
= self
._getSessionID
()
59 if self
._sessionID
== '':
60 raise StandardError('Failed to get session id')
63 print "New GrooveAPI session id: " + self
._sessionID
64 self
._ip
= self
._getIP
()
65 self
._country
= self
._getCountry
()
66 self
._setSavedSession
()
69 def _callRemote(self
, method
, params
):
71 res
= self
._getRemote
(method
, params
)
73 postData
= res
['postData']
75 print "Failed to get request URL and post data"
78 req
= urllib2
.Request(url
, postData
)
79 response
= urllib2
.urlopen(req
)
80 result
= response
.read()
85 result
= simplejson
.loads(result
)
87 except urllib2
.HTTPError
, e
:
88 print "HTTP error " + e
.code
89 except urllib2
.URLError
, e
:
90 print "URL error " + e
.reason
92 print "Request to Grooveshark API failed"
97 def _getRemote(self
, method
, params
= {}):
98 postData
= { "method": method
, "sessionid": self
._sessionID
, "parameters": params
}
99 postData
= simplejson
.dumps(postData
)
101 cipher
= Blowfish(self
._key
)
103 encryptedPostData
= cipher
.encryptCTR(postData
)
104 encryptedPostData
= base64
.urlsafe_b64encode(encryptedPostData
)
105 url
= WEB_APP_URL
+ "?postData=" + encryptedPostData
106 req
= urllib2
.Request(url
)
107 response
= urllib2
.urlopen(req
)
108 result
= response
.read()
111 pprint
.pprint(result
)
114 result
= simplejson
.loads(result
)
120 def _getSessionID(self
):
122 result
= self
._callRemote
('startSession', params
)
123 if 'result' in result
:
124 self
._lastSessionTime
= time
.time()
125 return result
['result']['sessionID']
129 def _getSavedSession(self
):
130 path
= os
.path
.join(self
.cacheDir
, 'session.dmp')
133 session
= pickle
.load(f
)
134 self
._sessionID
= session
['sessionID']
135 self
._lastSessionTime
= session
['lastSessionTime']
136 self
._userID
= session
['userID']
137 self
._ip
= session
['ip']
138 self
._country
= session
['country']
142 self
._lastSessionTime
= 0
148 def _setSavedSession(self
):
150 # Create the directory if it doesn't exist.
151 if not os
.path
.exists(self
.cacheDir
):
152 os
.makedirs(self
.cacheDir
)
153 path
= os
.path
.join(self
.cacheDir
, 'session.dmp')
155 session
= { 'sessionID' : self
._sessionID
, 'lastSessionTime' : self
._lastSessionTime
, 'userID': self
._userID
, 'ip' : self
._ip
, 'country' : self
._country
}
156 pickle
.dump(session
, f
, protocol
=pickle
.HIGHEST_PROTOCOL
)
159 print "An error occurred during save session"
162 def _setParams(self
, params
):
164 # Create the directory if it doesn't exist.
165 if not os
.path
.exists(self
.cacheDir
):
166 os
.makedirs(self
.cacheDir
)
167 path
= os
.path
.join(self
.cacheDir
, 'params.dmp')
169 pickle
.dump(params
, f
, protocol
=pickle
.HIGHEST_PROTOCOL
)
172 print "An error occurred during save params"
178 myip
= urllib2
.urlopen('http://whatismyip.org').read()
179 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
):
181 print "IP is " + myip
187 def _getCountry(self
):
188 params
= { 'ip' : self
._ip
}
189 response
= self
._callRemote
("getCountry", params
)
190 return response
['result']
192 # Get userid from name
193 def _getUserIDFromUsername(self
, username
):
194 result
= self
._callRemote
('getUserIDFromUsername', {'username' : username
})
195 if 'result' in result
and result
['result']['UserID'] > 0:
196 return result
['result']['UserID']
200 # Authenticates the user for current API session
201 def _authenticate(self
, login
, password
):
202 md5pwd
= md5
.new(password
).hexdigest()
203 params
= {'login': login
, 'password': md5pwd
}
205 result
= self
._callRemote
('authenticate', params
)
207 uid
= result
['result']['UserID']
216 def pingService(self
,):
217 result
= self
._callRemote
('pingService', {});
218 if 'result' in result
and result
['result'] != '':
224 def login(self
, username
, password
):
225 if self
._userID
<= 0:
227 self
._getSavedSession
()
228 if self
._userID
<= 0:
229 self
._userID
= self
._authenticate
(username
, password
)
231 self
._setSavedSession
()
236 result
= self
._callRemote
('logout', {'sessionID' : self
._sessionID
})
237 if 'result' in result
and result
['result']['success'] == True:
239 self
._setSavedSession
()
243 # Gets a stream key and host to get song content
244 def getSubscriberStreamKey(self
, songID
):
245 params
= { "songID": songID
, "country": self
._country
}
246 response
= self
._callRemote
("getSubscriberStreamKey", params
)
248 self
._lastStreamKey
= response
["result"]["StreamKey"]
249 self
._lastStreamServerID
= response
["result"]["StreamServerID"]
250 res
= response
["result"]
256 def getArtistSearchResults(self
, query
, limit
=ARTIST_LIMIT
):
257 result
= self
._callRemote
('getArtistSearchResults', {'query' : query
,'limit' : limit
})
258 if 'result' in result
:
259 return self
._parseArtists
(result
)
264 def getAlbumSearchResults(self
, query
, limit
=ALBUM_LIMIT
):
265 result
= self
._callRemote
('getAlbumSearchResults', {'query' : query
,'limit' : limit
})
266 if 'result' in result
:
267 return self
._parseAlbums
(result
)
272 def getSongSearchResults(self
, query
, limit
=SONG_LIMIT
):
273 result
= self
._callRemote
('getSongSearchResults', {'query' : query
, 'country' : self
._country
, 'limit' : limit
})
274 if 'result' in result
:
275 return self
._parseSongs
(result
)
280 def getArtistAlbums(self
, artistID
, limit
=ALBUM_LIMIT
):
281 result
= self
._callRemote
('getArtistVerifiedAlbums', {'artistID' : artistID
})
282 if 'result' in result
:
283 return self
._parseAlbums
(result
, limit
)
288 def getAlbumSongs(self
, albumID
, limit
=SONG_LIMIT
):
289 result
= self
._callRemote
('getAlbumSongs', {'albumID' : albumID
, 'limit' : limit
})
290 if 'result' in result
:
291 return self
._parseSongs
(result
)
295 # Get artist's popular songs
296 def getArtistPopularSongs(self
, artistID
, limit
= SONG_LIMIT
):
297 result
= self
._callRemote
('getArtistPopularSongs', {'artistID' : artistID
})
298 if 'result' in result
:
299 return self
._parseSongs
(result
, limit
)
303 # Gets the popular songs
304 def getPopularSongsToday(self
, limit
=SONG_LIMIT
):
305 result
= self
._callRemote
('getPopularSongsToday', {'limit' : limit
})
306 if 'result' in result
:
307 # Note limit is broken in the Grooveshark getPopularSongsToday method
308 return self
._parseSongs
(result
, limit
)
312 # Gets the favorite songs of the logged-in user
313 def getUserFavoriteSongs(self
):
314 if (self
._userID
== 0):
316 result
= self
._callRemote
('getUserFavoriteSongs', {})
317 if 'result' in result
:
318 return self
._parseSongs
(result
)
323 def getSongsInfo(self
, songIDs
):
324 result
= self
._callRemote
('getSongsInfo', {'songIDs' : songIDs
})
325 if 'result' in result
and 'SongID' in result
['result']:
326 info
= result
['result']
327 if 'CoverArtFilename' in info
and info
['CoverArtFilename'] != None:
328 info
['CoverArtFilename'] = THUMB_URL
+info
['CoverArtFilename'].encode('ascii', 'ignore')
330 info
['CoverArtFilename'] = 'None'
335 # Add song to user favorites
336 def addUserFavoriteSong(self
, songID
):
337 if (self
._userID
== 0):
339 result
= self
._callRemote
('addUserFavoriteSong', {'songID' : songID
})
340 return result
['result']['success']
342 # Remove songs from user favorites
343 def removeUserFavoriteSongs(self
, songIDs
):
344 if (self
._userID
== 0):
346 result
= self
._callRemote
('removeUserFavoriteSongs', {'songIDs' : songIDs
})
347 return result
['result']['success']
349 # Gets the playlists of the logged-in user
350 def getUserPlaylists(self
):
351 if (self
._userID
== 0):
353 result
= self
._callRemote
('getUserPlaylists', {})
354 if 'result' in result
:
355 return self
._parsePlaylists
(result
)
359 # Gets the playlists of the logged-in user
360 def getUserPlaylistsByUsername(self
, username
):
361 userID
= self
._getUserIDFromUsername
(username
)
363 result
= self
._callRemote
('getUserPlaylistsByUserID', {'userID' : userID
})
364 if 'result' in result
and result
['result']['playlists'] != None:
365 playlists
= result
['result']['playlists']
366 return self
._parsePlaylists
(playlists
)
370 # Creates a playlist with songs
371 def createPlaylist(self
, name
, songIDs
):
372 result
= self
._callRemote
('createPlaylist', {'name' : name
, 'songIDs' : songIDs
})
373 if 'result' in result
and result
['result']['success'] == True:
374 return result
['result']['playlistID']
375 elif 'errors' in result
:
378 # Sets the songs for a playlist
379 def setPlaylistSongs(self
, playlistID
, songIDs
):
380 result
= self
._callRemote
('setPlaylistSongs', {'playlistID' : playlistID
, 'songIDs' : songIDs
})
381 if 'result' in result
and result
['result']['success'] == True:
386 # Gets the songs of a playlist
387 def getPlaylistSongs(self
, playlistID
):
388 result
= self
._callRemote
('getPlaylistSongs', {'playlistID' : playlistID
});
389 if 'result' in result
:
390 return self
._parseSongs
(result
)
395 def playlistDelete(self
, playlistId
):
396 result
= self
._callRemote
("deletePlaylist", {"playlistID": playlistId
})
397 if 'fault' in result
:
402 def playlistRename(self
, playlistId
, name
):
403 result
= self
._callRemote
("renamePlaylist", {"playlistID": playlistId
, "name": name
})
404 if 'fault' in result
:
409 def getSimilarArtists(self
, artistId
, limit
):
410 items
= self
._callRemote
("getSimilarArtists", {"artistID": artistId
, "limit": limit
})
411 if 'result' in items
:
414 artists
= items
['result']['artists']
415 while(i
< len(artists
)):
417 list.append([s
['artistName'].encode('ascii', 'ignore'),\
424 def getDoesArtistExist(self
, artistId
):
425 response
= self
._callRemote
("getDoesArtistExist", {"artistID": artistId
})
426 if 'result' in response
and response
['result'] == True:
431 def getDoesAlbumExist(self
, albumId
):
432 response
= self
._callRemote
("getDoesAlbumExist", {"albumID": albumId
})
433 if 'result' in response
and response
['result'] == True:
438 def getDoesSongExist(self
, songId
):
439 response
= self
._callRemote
("getDoesSongExist", {"songID": songId
})
440 if 'result' in response
and response
['result'] == True:
445 # After 30s play time
446 def markStreamKeyOver30Secs(self
):
447 params
= { "streamKey" : self
._lastStreamKey
, "streamServerID" : self
._lastStreamServerID
}
448 self
._callRemote
("markStreamKeyOver30Secs", params
)
451 def markSongComplete(self
, songid
):
452 params
= { "songID" : songid
, "streamKey" : self
._lastStreamKey
, "streamServerID" : self
._lastStreamServerID
}
453 self
._callRemote
("markSongComplete", params
)
456 def setDebug(self
, state
):
457 self
._apiDebug
= state
459 print "API debug is on"
463 def _parseSongs(self
, items
, limit
=0):
464 if 'result' in items
:
470 if 'songs' in items
['result'][0]:
471 l
= len(items
['result'][0]['songs'])
475 if l
< 0 and 'songs' in items
['result']:
476 l
= len(items
['result']['songs'])
480 if l
< 0 and 'song' in items
['result']:
486 l
= len(items
['result'])
489 if limit
> 0 and l
> limit
:
492 if index
== 'songs[]':
493 s
= items
['result'][0]['songs'][i
]
494 elif index
== 'songs':
495 s
= items
['result'][index
][i
]
496 elif index
== 'song':
497 s
= items
['result'][index
]
499 s
= items
['result'][i
]
500 if 'CoverArtFilename' not in s
:
501 info
= self
.getSongsInfo(s
['SongID'])
502 coverart
= info
['CoverArtFilename']
503 elif s
['CoverArtFilename'] != None:
504 coverart
= THUMB_URL
+s
['CoverArtFilename'].encode('ascii', 'ignore')
507 list.append([s
['SongName'].encode('ascii', 'ignore'),\
509 s
['AlbumName'].encode('ascii', 'ignore'),\
511 s
['ArtistName'].encode('ascii', 'ignore'),\
519 # Extract artist data
520 def _parseArtists(self
, items
):
521 if 'result' in items
:
524 artists
= items
['result']['artists']
525 while(i
< len(artists
)):
527 list.append([s
['ArtistName'].encode('ascii', 'ignore'),\
535 def _parseAlbums(self
, items
, limit
=0):
536 if 'result' in items
:
540 albums
= items
['result']['albums']
542 res
= items
['result'][0]
543 albums
= res
['albums']
545 if limit
> 0 and l
> limit
:
549 if 'CoverArtFilename' in s
and s
['CoverArtFilename'] != None:
550 coverart
= THUMB_URL
+s
['CoverArtFilename'].encode('ascii', 'ignore')
553 list.append([s
['ArtistName'].encode('ascii', 'ignore'),\
555 s
['AlbumName'].encode('ascii', 'ignore'),\
563 def _parsePlaylists(self
, items
):
566 if 'result' in items
:
567 playlists
= items
['result']['playlists']
573 while (i
< len(playlists
)):
575 list.append([str(s
['PlaylistName']).encode('ascii', 'ignore'), s
['PlaylistID']])