91add0859ebfbef3b92e53d693d4b17251e678be
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
, os
, pickle
, time
, re
, simplejson
, base64
, sys
, socket
, hashlib
19 from blowfish
import Blowfish
21 SESSION_EXPIRY
= 120 #1209600 # 2 weeks
24 WEB_APP_URL
= "http://xbmc-groove.appspot.com/"
27 THUMB_URL
= 'http://beta.grooveshark.com/static/amazonart/m'
41 _key
= hashlib
.md5(os
.path
.basename("GroovesharkAPI.py")).hexdigest()
45 def __init__(self
, debug
, tempDir
):
47 self
._debugging
= debug
48 self
.simplejson
= simplejson
49 if "linux" in sys
.platform
.lower():
50 socket
.setdefaulttimeout(30)
52 self
.cacheDir
= tempDir
53 if os
.path
.isdir(self
.cacheDir
) == False:
54 os
.makedirs(self
.cacheDir
)
56 print "Made " + self
.cacheDir
57 self
._getSavedSession
()
58 # session ids last 2 weeks
59 if self
._sessionID
== '' or time
.time()- self
._lastSessionTime
>= SESSION_EXPIRY
:
60 self
._sessionID
= self
._getSessionID
()
61 if self
._sessionID
== '':
62 raise StandardError('Failed to get session id')
65 print "New GrooveAPI session id: " + self
._sessionID
66 self
._ip
= self
._getIP
()
67 self
._country
= self
._getCountry
()
68 self
._setSavedSession
()
72 def _callRemote(self
, method
, params
):
74 res
= self
._getRemote
(method
, params
)
76 postData
= res
['postData']
78 print "Failed to get request URL and post data"
81 req
= urllib2
.Request(url
, postData
)
82 response
= urllib2
.urlopen(req
)
83 result
= response
.read()
88 result
= simplejson
.loads(result
)
90 except urllib2
.HTTPError
, e
:
91 print "HTTP error " + e
.code
92 except urllib2
.URLError
, e
:
93 print "URL error " + e
.reason
95 print "Request to Grooveshark API failed"
100 def _getRemote(self
, method
, params
= {}):
101 postData
= { "method": method
, "sessionid": self
._sessionID
, "parameters": params
}
102 postData
= simplejson
.dumps(postData
)
104 cipher
= Blowfish(self
._key
)
106 encryptedPostData
= cipher
.encryptCTR(postData
)
107 encryptedPostData
= base64
.urlsafe_b64encode(encryptedPostData
)
108 url
= WEB_APP_URL
+ "?postData=" + encryptedPostData
109 req
= urllib2
.Request(url
)
110 response
= urllib2
.urlopen(req
)
111 result
= response
.read()
114 pprint
.pprint(result
)
117 result
= simplejson
.loads(result
)
123 def _getSessionID(self
):
125 result
= self
._callRemote
('startSession', params
)
126 if 'result' in result
:
127 self
._lastSessionTime
= time
.time()
128 return result
['result']['sessionID']
132 def _getSavedSession(self
):
133 path
= os
.path
.join(self
.cacheDir
, 'groovesharksession.dmp')
136 session
= pickle
.load(f
)
137 self
._sessionID
= session
['sessionID']
138 self
._lastSessionTime
= session
['lastSessionTime']
139 self
._userID
= session
['userID']
140 self
._ip
= session
['ip']
141 self
._country
= session
['country']
145 self
._lastSessionTime
= 0
151 def _setSavedSession(self
):
153 # Create the directory if it doesn't exist.
154 if not os
.path
.exists(self
.cacheDir
):
155 os
.makedirs(self
.cacheDir
)
156 path
= os
.path
.join(self
.cacheDir
, 'groovesharksession.dmp')
158 session
= { 'sessionID' : self
._sessionID
, 'lastSessionTime' : self
._lastSessionTime
, 'userID': self
._userID
, 'ip' : self
._ip
, 'country' : self
._country
}
159 pickle
.dump(session
, f
, protocol
=pickle
.HIGHEST_PROTOCOL
)
162 print "An error occurred during save session"
168 myip
= urllib2
.urlopen('http://ipecho.net/plain').read()
169 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
):
171 print "IP is " + myip
177 def _getCountry(self
):
178 params
= { 'ip' : self
._ip
}
179 response
= self
._callRemote
("getCountry", params
)
180 return response
['result']
182 # Get userid from name
183 def _getUserIDFromUsername(self
, username
):
184 result
= self
._callRemote
('getUserIDFromUsername', {'username' : username
})
185 if 'result' in result
and result
['result']['UserID'] > 0:
186 return result
['result']['UserID']
190 # Authenticates the user for current API session
191 def _authenticate(self
, login
, password
):
192 md5pwd
= hashlib
.md5(password
).hexdigest()
193 params
= {'login': login
, 'password': md5pwd
}
195 result
= self
._callRemote
('authenticate', params
)
197 uid
= result
['result']['UserID']
206 def pingService(self
,):
207 result
= self
._callRemote
('pingService', {});
208 if 'result' in result
and result
['result'] != '':
214 def login(self
, username
, password
):
215 if self
._userID
<= 0:
217 self
._getSavedSession
()
218 if self
._userID
<= 0:
219 self
._userID
= self
._authenticate
(username
, password
)
221 self
._setSavedSession
()
226 result
= self
._callRemote
('logout', {'sessionID' : self
._sessionID
})
227 if 'result' in result
and result
['result']['success'] == True:
229 self
._setSavedSession
()
233 # Gets a stream key and host to get song content
234 def getSubscriberStreamKey(self
, songID
):
235 params
= { "songID": songID
, "country": self
._country
}
236 response
= self
._callRemote
("getSubscriberStreamKey", params
)
238 res
= response
["result"]
244 def getArtistSearchResults(self
, query
, limit
=ARTIST_LIMIT
):
245 result
= self
._callRemote
('getArtistSearchResults', {'query' : query
,'limit' : limit
})
246 if 'result' in result
:
247 return self
._parseArtists
(result
)
252 def getAlbumSearchResults(self
, query
, limit
=ALBUM_LIMIT
):
253 result
= self
._callRemote
('getAlbumSearchResults', {'query' : query
,'limit' : limit
})
254 if 'result' in result
:
255 return self
._parseAlbums
(result
)
260 def getSongSearchResults(self
, query
, limit
=SONG_LIMIT
):
261 result
= self
._callRemote
('getSongSearchResults', {'query' : query
, 'country' : self
._country
, 'limit' : limit
})
262 if 'result' in result
:
263 return self
._parseSongs
(result
)
268 def getArtistAlbums(self
, artistID
, limit
=ALBUM_LIMIT
):
269 result
= self
._callRemote
('getArtistVerifiedAlbums', {'artistID' : artistID
})
270 if 'result' in result
:
271 return self
._parseAlbums
(result
, limit
)
276 def getAlbumSongs(self
, albumID
, limit
=SONG_LIMIT
):
277 result
= self
._callRemote
('getAlbumSongs', {'albumID' : albumID
, 'limit' : limit
})
278 if 'result' in result
:
279 return self
._parseSongs
(result
)
283 # Get artist's popular songs
284 def getArtistPopularSongs(self
, artistID
, limit
= SONG_LIMIT
):
285 result
= self
._callRemote
('getArtistPopularSongs', {'artistID' : artistID
})
286 if 'result' in result
:
287 return self
._parseSongs
(result
, limit
)
291 # Gets the popular songs
292 def getPopularSongsToday(self
, limit
=SONG_LIMIT
):
293 result
= self
._callRemote
('getPopularSongsToday', {'limit' : limit
})
294 if 'result' in result
:
295 # Note limit is broken in the Grooveshark getPopularSongsToday method
296 return self
._parseSongs
(result
, limit
)
300 # Gets the favorite songs of the logged-in user
301 def getUserFavoriteSongs(self
):
302 if (self
._userID
== 0):
304 result
= self
._callRemote
('getUserFavoriteSongs', {})
305 if 'result' in result
:
306 return self
._parseSongs
(result
)
311 def getSongsInfo(self
, songIDs
):
312 result
= self
._callRemote
('getSongsInfo', {'songIDs' : songIDs
})
313 if 'result' in result
and 'SongID' in result
['result']:
314 info
= result
['result']
315 if 'CoverArtFilename' in info
and info
['CoverArtFilename'] != None:
316 info
['CoverArtFilename'] = THUMB_URL
+info
['CoverArtFilename'].encode('utf8', 'ignore')
318 info
['CoverArtFilename'] = 'None'
323 # Add song to user favorites
324 def addUserFavoriteSong(self
, songID
):
325 if (self
._userID
== 0):
327 result
= self
._callRemote
('addUserFavoriteSong', {'songID' : songID
})
328 return result
['result']['success']
330 # Remove songs from user favorites
331 def removeUserFavoriteSongs(self
, songIDs
):
332 if (self
._userID
== 0):
334 result
= self
._callRemote
('removeUserFavoriteSongs', {'songIDs' : songIDs
})
335 return result
['result']['success']
337 # Gets the playlists of the logged-in user
338 def getUserPlaylists(self
):
339 if (self
._userID
== 0):
341 result
= self
._callRemote
('getUserPlaylists', {})
342 if 'result' in result
:
343 return self
._parsePlaylists
(result
)
347 # Gets the playlists of the logged-in user
348 def getUserPlaylistsByUsername(self
, username
):
349 userID
= self
._getUserIDFromUsername
(username
)
351 result
= self
._callRemote
('getUserPlaylistsByUserID', {'userID' : userID
})
352 if 'result' in result
and result
['result']['playlists'] != None:
353 playlists
= result
['result']['playlists']
354 return self
._parsePlaylists
(playlists
)
358 # Creates a playlist with songs
359 def createPlaylist(self
, name
, songIDs
):
360 result
= self
._callRemote
('createPlaylist', {'name' : name
, 'songIDs' : songIDs
})
361 if 'result' in result
and result
['result']['success'] == True:
362 return result
['result']['playlistID']
363 elif 'errors' in result
:
366 # Sets the songs for a playlist
367 def setPlaylistSongs(self
, playlistID
, songIDs
):
368 result
= self
._callRemote
('setPlaylistSongs', {'playlistID' : playlistID
, 'songIDs' : songIDs
})
369 if 'result' in result
and result
['result']['success'] == True:
374 # Gets the songs of a playlist
375 def getPlaylistSongs(self
, playlistID
):
376 result
= self
._callRemote
('getPlaylistSongs', {'playlistID' : playlistID
});
377 if 'result' in result
:
378 return self
._parseSongs
(result
)
383 def playlistDelete(self
, playlistId
):
384 result
= self
._callRemote
("deletePlaylist", {"playlistID": playlistId
})
385 if 'fault' in result
:
390 def playlistRename(self
, playlistId
, name
):
391 result
= self
._callRemote
("renamePlaylist", {"playlistID": playlistId
, "name": name
})
392 if 'fault' in result
:
397 def getSimilarArtists(self
, artistId
, limit
):
398 items
= self
._callRemote
("getSimilarArtists", {"artistID": artistId
, "limit": limit
})
399 if 'result' in items
:
402 artists
= items
['result']['artists']
403 while(i
< len(artists
)):
405 itemList
.append([s
['artistName'].encode('utf8', 'ignore'),\
412 def getDoesArtistExist(self
, artistId
):
413 response
= self
._callRemote
("getDoesArtistExist", {"artistID": artistId
})
414 if 'result' in response
and response
['result'] == True:
419 def getDoesAlbumExist(self
, albumId
):
420 response
= self
._callRemote
("getDoesAlbumExist", {"albumID": albumId
})
421 if 'result' in response
and response
['result'] == True:
426 def getDoesSongExist(self
, songId
):
427 response
= self
._callRemote
("getDoesSongExist", {"songID": songId
})
428 if 'result' in response
and response
['result'] == True:
433 # After 30s play time
434 def markStreamKeyOver30Secs(self
, streamKey
, streamServerID
):
435 params
= { "streamKey" : streamKey
, "streamServerID" : streamServerID
}
436 self
._callRemote
("markStreamKeyOver30Secs", params
)
439 def markSongComplete(self
, songid
, streamKey
, streamServerID
):
440 params
= { "songID" : songid
, "streamKey" : streamKey
, "streamServerID" : streamServerID
}
441 self
._callRemote
("markSongComplete", params
)
444 def _parseSongs(self
, items
, limit
=0):
445 if 'result' in items
:
451 if 'songs' in items
['result'][0]:
452 l
= len(items
['result'][0]['songs'])
456 if l
< 0 and 'songs' in items
['result']:
457 l
= len(items
['result']['songs'])
461 if l
< 0 and 'song' in items
['result']:
467 l
= len(items
['result'])
470 if limit
> 0 and l
> limit
:
473 if index
== 'songs[]':
474 s
= items
['result'][0]['songs'][i
]
475 elif index
== 'songs':
476 s
= items
['result'][index
][i
]
477 elif index
== 'song':
478 s
= items
['result'][index
]
480 s
= items
['result'][i
]
481 if 'CoverArtFilename' not in s
:
482 info
= self
.getSongsInfo(s
['SongID'])
483 coverart
= info
['CoverArtFilename']
484 elif s
['CoverArtFilename'] != None:
485 coverart
= THUMB_URL
+s
['CoverArtFilename'].encode('utf8', 'ignore')
493 albumName
= s
['AlbumName']
496 itemList
.append([name
.encode('utf8', 'ignore'),\
498 albumName
.encode('utf8', 'ignore'),\
500 s
['ArtistName'].encode('utf8', 'ignore'),\
508 # Extract artist data
509 def _parseArtists(self
, items
):
510 if 'result' in items
:
513 artists
= items
['result']['artists']
514 while(i
< len(artists
)):
516 itemList
.append([s
['ArtistName'].encode('utf8', 'ignore'),\
524 def _parseAlbums(self
, items
, limit
=0):
525 if 'result' in items
:
529 albums
= items
['result']['albums']
531 res
= items
['result'][0]
532 albums
= res
['albums']
534 if limit
> 0 and l
> limit
:
539 name
= s
['Name'].encode('utf8', 'ignore')
541 name
= s
['AlbumName'].encode('utf8', 'ignore')
542 if 'CoverArtFilename' in s
and s
['CoverArtFilename'] != None:
543 coverart
= THUMB_URL
+s
['CoverArtFilename'].encode('utf8', 'ignore')
546 itemList
.append([s
['ArtistName'].encode('utf8', 'ignore'),\
556 def _parsePlaylists(self
, items
):
559 if 'result' in items
:
560 playlists
= items
['result']['playlists']
566 while (i
< len(playlists
)):
568 itemList
.append([unicode(s
['PlaylistName']).encode('utf8', 'ignore'), s
['PlaylistID']])