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'
41 _key
= md5
.new(os
.path
.basename("GroovesharkAPI.py")).hexdigest()
47 self
.simplejson
= simplejson
48 self
.cacheDir
= os
.path
.join(tempfile
.gettempdir(), 'groovesharkapi')
49 if os
.path
.isdir(self
.cacheDir
) == False:
50 os
.makedirs(self
.cacheDir
)
52 print "Made " + self
.cacheDir
53 self
._getSavedSession
()
54 # session ids last 2 weeks
55 if self
._sessionID
== '' or time
.time()- self
._lastSessionTime
>= SESSION_EXPIRY
:
56 self
._sessionID
= self
._getSessionID
()
57 if self
._sessionID
== '':
58 raise StandardError('Failed to get session id')
61 print "New GrooveAPI session id: " + self
._sessionID
62 self
._ip
= self
._getIP
()
63 self
._country
= self
._getCountry
()
64 self
._setSavedSession
()
67 def _callRemote(self
, method
, params
):
69 res
= self
._getRemote
(method
, params
)
71 postData
= res
['postData']
73 print "Failed to get request URL and post data"
76 req
= urllib2
.Request(url
, postData
)
77 response
= urllib2
.urlopen(req
)
78 result
= response
.read()
83 result
= simplejson
.loads(result
)
85 except urllib2
.HTTPError
, e
:
86 print "HTTP error " + e
.code
87 except urllib2
.URLError
, e
:
88 print "URL error " + e
.reason
90 print "Request to Grooveshark API failed"
95 def _getRemote(self
, method
, params
= {}):
96 postData
= { "method": method
, "sessionid": self
._sessionID
, "parameters": params
}
97 postData
= simplejson
.dumps(postData
)
99 cipher
= Blowfish(self
._key
)
101 encryptedPostData
= cipher
.encryptCTR(postData
)
102 encryptedPostData
= base64
.urlsafe_b64encode(encryptedPostData
)
103 url
= WEB_APP_URL
+ "?postData=" + encryptedPostData
104 req
= urllib2
.Request(url
)
105 response
= urllib2
.urlopen(req
)
106 result
= response
.read()
109 pprint
.pprint(result
)
112 result
= simplejson
.loads(result
)
118 def _getSessionID(self
):
120 result
= self
._callRemote
('startSession', params
)
121 if 'result' in result
:
122 self
._lastSessionTime
= time
.time()
123 return result
['result']['sessionID']
127 def _getSavedSession(self
):
128 path
= os
.path
.join(self
.cacheDir
, 'session.dmp')
131 session
= pickle
.load(f
)
132 self
._sessionID
= session
['sessionID']
133 self
._lastSessionTime
= session
['lastSessionTime']
134 self
._userID
= session
['userID']
135 self
._ip
= session
['ip']
136 self
._country
= session
['country']
140 self
._lastSessionTime
= 0
146 def _setSavedSession(self
):
148 # Create the directory if it doesn't exist.
149 if not os
.path
.exists(self
.cacheDir
):
150 os
.makedirs(self
.cacheDir
)
151 path
= os
.path
.join(self
.cacheDir
, 'session.dmp')
153 session
= { 'sessionID' : self
._sessionID
, 'lastSessionTime' : self
._lastSessionTime
, 'userID': self
._userID
, 'ip' : self
._ip
, 'country' : self
._country
}
154 pickle
.dump(session
, f
, protocol
=pickle
.HIGHEST_PROTOCOL
)
157 print "An error occurred during save session"
163 myip
= urllib2
.urlopen('http://whatismyip.org').read()
164 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
):
166 print "IP is " + myip
172 def _getCountry(self
):
173 params
= { 'ip' : self
._ip
}
174 response
= self
._callRemote
("getCountry", params
)
175 return response
['result']
177 # Get userid from name
178 def _getUserIDFromUsername(self
, username
):
179 result
= self
._callRemote
('getUserIDFromUsername', {'username' : username
})
180 if 'result' in result
and result
['result']['UserID'] > 0:
181 return result
['result']['UserID']
185 # Authenticates the user for current API session
186 def _authenticate(self
, login
, password
):
187 md5pwd
= md5
.new(password
).hexdigest()
188 params
= {'login': login
, 'password': md5pwd
}
190 result
= self
._callRemote
('authenticate', params
)
192 uid
= result
['result']['UserID']
201 def pingService(self
,):
202 result
= self
._callRemote
('pingService', {});
203 if 'result' in result
and result
['result'] != '':
209 def login(self
, username
, password
):
210 if self
._userID
<= 0:
212 self
._getSavedSession
()
213 if self
._userID
<= 0:
214 self
._userID
= self
._authenticate
(username
, password
)
216 self
._setSavedSession
()
221 result
= self
._callRemote
('logout', {'sessionID' : self
._sessionID
})
222 if 'result' in result
and result
['result']['success'] == True:
224 self
._setSavedSession
()
228 # Gets a stream key and host to get song content
229 def getSubscriberStreamKey(self
, songID
):
230 params
= { "songID": songID
, "country": self
._country
}
231 response
= self
._callRemote
("getSubscriberStreamKey", params
)
233 res
= response
["result"]
239 def getArtistSearchResults(self
, query
, limit
=ARTIST_LIMIT
):
240 result
= self
._callRemote
('getArtistSearchResults', {'query' : query
,'limit' : limit
})
241 if 'result' in result
:
242 return self
._parseArtists
(result
)
247 def getAlbumSearchResults(self
, query
, limit
=ALBUM_LIMIT
):
248 result
= self
._callRemote
('getAlbumSearchResults', {'query' : query
,'limit' : limit
})
249 if 'result' in result
:
250 return self
._parseAlbums
(result
)
255 def getSongSearchResults(self
, query
, limit
=SONG_LIMIT
):
256 result
= self
._callRemote
('getSongSearchResults', {'query' : query
, 'country' : self
._country
, 'limit' : limit
})
257 if 'result' in result
:
258 return self
._parseSongs
(result
)
263 def getArtistAlbums(self
, artistID
, limit
=ALBUM_LIMIT
):
264 result
= self
._callRemote
('getArtistVerifiedAlbums', {'artistID' : artistID
})
265 if 'result' in result
:
266 return self
._parseAlbums
(result
, limit
)
271 def getAlbumSongs(self
, albumID
, limit
=SONG_LIMIT
):
272 result
= self
._callRemote
('getAlbumSongs', {'albumID' : albumID
, 'limit' : limit
})
273 if 'result' in result
:
274 return self
._parseSongs
(result
)
278 # Get artist's popular songs
279 def getArtistPopularSongs(self
, artistID
, limit
= SONG_LIMIT
):
280 result
= self
._callRemote
('getArtistPopularSongs', {'artistID' : artistID
})
281 if 'result' in result
:
282 return self
._parseSongs
(result
, limit
)
286 # Gets the popular songs
287 def getPopularSongsToday(self
, limit
=SONG_LIMIT
):
288 result
= self
._callRemote
('getPopularSongsToday', {'limit' : limit
})
289 if 'result' in result
:
290 # Note limit is broken in the Grooveshark getPopularSongsToday method
291 return self
._parseSongs
(result
, limit
)
295 # Gets the favorite songs of the logged-in user
296 def getUserFavoriteSongs(self
):
297 if (self
._userID
== 0):
299 result
= self
._callRemote
('getUserFavoriteSongs', {})
300 if 'result' in result
:
301 return self
._parseSongs
(result
)
306 def getSongsInfo(self
, songIDs
):
307 result
= self
._callRemote
('getSongsInfo', {'songIDs' : songIDs
})
308 if 'result' in result
and 'SongID' in result
['result']:
309 info
= result
['result']
310 if 'CoverArtFilename' in info
and info
['CoverArtFilename'] != None:
311 info
['CoverArtFilename'] = THUMB_URL
+info
['CoverArtFilename'].encode('ascii', 'ignore')
313 info
['CoverArtFilename'] = 'None'
318 # Add song to user favorites
319 def addUserFavoriteSong(self
, songID
):
320 if (self
._userID
== 0):
322 result
= self
._callRemote
('addUserFavoriteSong', {'songID' : songID
})
323 return result
['result']['success']
325 # Remove songs from user favorites
326 def removeUserFavoriteSongs(self
, songIDs
):
327 if (self
._userID
== 0):
329 result
= self
._callRemote
('removeUserFavoriteSongs', {'songIDs' : songIDs
})
330 return result
['result']['success']
332 # Gets the playlists of the logged-in user
333 def getUserPlaylists(self
):
334 if (self
._userID
== 0):
336 result
= self
._callRemote
('getUserPlaylists', {})
337 if 'result' in result
:
338 return self
._parsePlaylists
(result
)
342 # Gets the playlists of the logged-in user
343 def getUserPlaylistsByUsername(self
, username
):
344 userID
= self
._getUserIDFromUsername
(username
)
346 result
= self
._callRemote
('getUserPlaylistsByUserID', {'userID' : userID
})
347 if 'result' in result
and result
['result']['playlists'] != None:
348 playlists
= result
['result']['playlists']
349 return self
._parsePlaylists
(playlists
)
353 # Creates a playlist with songs
354 def createPlaylist(self
, name
, songIDs
):
355 result
= self
._callRemote
('createPlaylist', {'name' : name
, 'songIDs' : songIDs
})
356 if 'result' in result
and result
['result']['success'] == True:
357 return result
['result']['playlistID']
358 elif 'errors' in result
:
361 # Sets the songs for a playlist
362 def setPlaylistSongs(self
, playlistID
, songIDs
):
363 result
= self
._callRemote
('setPlaylistSongs', {'playlistID' : playlistID
, 'songIDs' : songIDs
})
364 if 'result' in result
and result
['result']['success'] == True:
369 # Gets the songs of a playlist
370 def getPlaylistSongs(self
, playlistID
):
371 result
= self
._callRemote
('getPlaylistSongs', {'playlistID' : playlistID
});
372 if 'result' in result
:
373 return self
._parseSongs
(result
)
378 def playlistDelete(self
, playlistId
):
379 result
= self
._callRemote
("deletePlaylist", {"playlistID": playlistId
})
380 if 'fault' in result
:
385 def playlistRename(self
, playlistId
, name
):
386 result
= self
._callRemote
("renamePlaylist", {"playlistID": playlistId
, "name": name
})
387 if 'fault' in result
:
392 def getSimilarArtists(self
, artistId
, limit
):
393 items
= self
._callRemote
("getSimilarArtists", {"artistID": artistId
, "limit": limit
})
394 if 'result' in items
:
397 artists
= items
['result']['artists']
398 while(i
< len(artists
)):
400 list.append([s
['artistName'].encode('ascii', 'ignore'),\
407 def getDoesArtistExist(self
, artistId
):
408 response
= self
._callRemote
("getDoesArtistExist", {"artistID": artistId
})
409 if 'result' in response
and response
['result'] == True:
414 def getDoesAlbumExist(self
, albumId
):
415 response
= self
._callRemote
("getDoesAlbumExist", {"albumID": albumId
})
416 if 'result' in response
and response
['result'] == True:
421 def getDoesSongExist(self
, songId
):
422 response
= self
._callRemote
("getDoesSongExist", {"songID": songId
})
423 if 'result' in response
and response
['result'] == True:
428 # After 30s play time
429 def markStreamKeyOver30Secs(self
, lastStreamKey
, lastStreamServerID
):
430 params
= { "streamKey" : lastStreamKey
, "streamServerID" : lastStreamServerID
}
431 self
._callRemote
("markStreamKeyOver30Secs", params
)
434 def markSongComplete(self
, songid
, lastStreamKey
, lastStreamServerID
):
435 params
= { "songID" : songid
, "streamKey" : lastStreamKey
, "streamServerID" : lastStreamServerID
}
436 self
._callRemote
("markSongComplete", params
)
439 def setDebug(self
, state
):
441 self
._apiDebug
= True
443 self
._apiDebug
= False
444 if self
._apiDebug
== True:
445 print "API debug is on"
448 def _parseSongs(self
, items
, limit
=0):
449 if 'result' in items
:
455 if 'songs' in items
['result'][0]:
456 l
= len(items
['result'][0]['songs'])
460 if l
< 0 and 'songs' in items
['result']:
461 l
= len(items
['result']['songs'])
465 if l
< 0 and 'song' in items
['result']:
471 l
= len(items
['result'])
474 if limit
> 0 and l
> limit
:
477 if index
== 'songs[]':
478 s
= items
['result'][0]['songs'][i
]
479 elif index
== 'songs':
480 s
= items
['result'][index
][i
]
481 elif index
== 'song':
482 s
= items
['result'][index
]
484 s
= items
['result'][i
]
485 if 'CoverArtFilename' not in s
:
486 info
= self
.getSongsInfo(s
['SongID'])
487 coverart
= info
['CoverArtFilename']
488 elif s
['CoverArtFilename'] != None:
489 coverart
= THUMB_URL
+s
['CoverArtFilename'].encode('ascii', 'ignore')
492 list.append([s
['SongName'].encode('ascii', 'ignore'),\
494 s
['AlbumName'].encode('ascii', 'ignore'),\
496 s
['ArtistName'].encode('ascii', 'ignore'),\
504 # Extract artist data
505 def _parseArtists(self
, items
):
506 if 'result' in items
:
509 artists
= items
['result']['artists']
510 while(i
< len(artists
)):
512 list.append([s
['ArtistName'].encode('ascii', 'ignore'),\
520 def _parseAlbums(self
, items
, limit
=0):
521 if 'result' in items
:
525 albums
= items
['result']['albums']
527 res
= items
['result'][0]
528 albums
= res
['albums']
530 if limit
> 0 and l
> limit
:
534 if 'CoverArtFilename' in s
and s
['CoverArtFilename'] != None:
535 coverart
= THUMB_URL
+s
['CoverArtFilename'].encode('ascii', 'ignore')
538 list.append([s
['ArtistName'].encode('ascii', 'ignore'),\
540 s
['AlbumName'].encode('ascii', 'ignore'),\
548 def _parsePlaylists(self
, items
):
551 if 'result' in items
:
552 playlists
= items
['result']['playlists']
558 while (i
< len(playlists
)):
560 list.append([str(s
['PlaylistName']).encode('ascii', 'ignore'), s
['PlaylistID']])