1 import socket
, hmac
, urllib
, urllib2
, pprint
, md5
, uuid
, re
, hashlib
, time
, random
, os
, pickle
3 SESSION_EXPIRY
= 518400 # 6 days in seconds
6 THUMB_URL
= 'http://beta.grooveshark.com/static/amazonart/'
7 THUMB_URL_DEFAULT
= 'http://grooveshark.com/webincludes/logo/Grooveshark_Logo_No-Text.png'
12 # GrooveSong constants
13 DOMAIN
= "grooveshark.com"
14 HOME_URL
= "http://listen." + DOMAIN
15 API_URL
= "http://cowbell." + DOMAIN
+ "/more.php"
16 SERVICE_URL
= "http://cowbell." + DOMAIN
+ "/service.php"
17 TOKEN_URL
= "http://cowbell." + DOMAIN
+ "/more.php"
20 CLIENT_NAME
= "gslite"
21 CLIENT_VERSION
= "20101012.37"
22 HEADERS
= {"Content-Type": "application/json",
23 "User-Agent": "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 (.NET CLR 3.5.30729)",
24 "Referer": "http://listen.grooveshark.com/main.swf?cowbell=fe87233106a6cef919a1294fb2c3c05f"}
26 RE_SESSION
= re
.compile('"sessionID":"\s*?([A-z0-9]+)"')
27 RANDOM_CHARS
= "1234567890abcdef"
32 def __init__(self
, cacheDir
):
35 self
.simplejson
= simplejson
37 self
.cacheDir
= cacheDir
38 self
._lastTokenTime
= 0
39 self
._lastSessionTime
= 0
40 self
.uuid
= self
._getUUID
()
42 self
._getSavedSession
()
43 # session ids last 1 week
44 if self
.sessionID
== '' or time
.time() - self
._lastSessionTime
>= SESSION_EXPIRY
:
45 self
.sessionID
= self
._getSession
(HOME_URL
)
46 if self
.sessionID
== '':
47 raise StandardError('Failed to get session id')
49 print "New GrooveSong session id: " + self
.sessionID
50 self
._setSavedSession
()
52 # The actual call to the API
53 def _callRemote(self
, method
, params
, type="default"):
56 "client": CLIENT_NAME
,
57 "clientRevision": CLIENT_VERSION
,
59 "session": self
.sessionID
},
60 "country": {"IPR":"1021", "ID":"223", "CC1":"0", "CC2":"0", "CC3":"0", "CC4":"2147483648"},
65 token
= self
._getMethodToken
(method
)
67 raise StandardError("Cannot get token")
69 postData
["header"]["token"] = token
71 url
= SERVICE_URL
+ "?" + method
73 url
= API_URL
+ "?" + method
75 postData
= self
.simplejson
.dumps(postData
)
76 print "GrooveSong URL: " + url
77 request
= urllib2
.Request(url
, postData
, HEADERS
)
79 response
= urllib2
.urlopen(request
).read()
81 response
= self
.simplejson
.loads(response
)
82 print "GrooveSong Response..."
83 pprint
.pprint(response
)
85 raise StandardError("API error: " + response
)
91 raise StandardError("API error: " + response
["fault"]["message"])
93 # Generate a random uuid
95 return str(uuid
.uuid4())
97 # Make a token ready for a request header
98 def _getMethodToken(self
, method
):
99 if (time
.time() - self
._lastTokenTime
) >= TOKEN_EXPIRY
:
100 self
._token
= self
._getCommunicationToken
()
101 if self
._token
== None:
103 self
._lastTokenTime
= time
.time()
104 self
._setSavedSession
()
107 while 6 > len(randomChars
):
108 randomChars
= randomChars
+ random
.choice(RANDOM_CHARS
)
110 token
= hashlib
.sha1(method
+ ":" + self
._token
+ ":quitStealinMahShit:" + randomChars
).hexdigest()
111 return randomChars
+ token
113 # Generate a communication token
114 def _getCommunicationToken(self
):
115 params
= {"secretKey": self
._getSecretKey
(self
.sessionID
)}
118 "client": CLIENT_NAME
,
119 "clientRevision": CLIENT_VERSION
,
121 "session": self
.sessionID
},
122 "country": {"IPR":"1021", "ID":"223", "CC1":"0", "CC2":"0", "CC3":"0", "CC4":"2147483648"},
124 "parameters": params
,
125 "method": "getCommunicationToken"}
127 postData
= self
.simplejson
.dumps(postData
)
128 request
= urllib2
.Request(TOKEN_URL
, postData
, HEADERS
)
129 response
= urllib2
.urlopen(request
).read()
131 response
= self
.simplejson
.loads(response
)
133 raise StandardError("API error: " + response
)
137 return response
["result"]
141 # Generate a secret key from a sessionID
142 def _getSecretKey(self
, sessionID
):
143 return hashlib
.md5(sessionID
).hexdigest()
145 # Get a session id from some HTML
146 def _getSession(self
, html
):
147 html
= urllib2
.urlopen(HOME_URL
).read()
148 session
= RE_SESSION
.search(html
)
150 self
._lastSessionTime
= time
.time()
151 return session
.group(1)
155 def _getSavedSession(self
):
156 path
= os
.path
.join(self
.cacheDir
, 'session.dmp')
159 session
= pickle
.load(f
)
160 self
.sessionID
= session
['sessionID']
161 self
._lastSessionTime
= session
['lastSessionTime']
162 self
._token
= session
['token']
163 self
._lastTokenTime
= session
['lastTokenTime']
167 self
._lastSessionTime
= 0
169 self
._lastTokenTime
= 0
172 def _setSavedSession(self
):
174 # Create the 'data' directory if it doesn't exist.
175 if not os
.path
.exists(self
.cacheDir
):
176 os
.makedirs(self
.cacheDir
)
177 path
= os
.path
.join(self
.cacheDir
, 'session.dmp')
179 session
= {'sessionID' : self
.sessionID
, 'lastSessionTime' : self
._lastSessionTime
, 'token' : self
._token
, 'lastTokenTime' : self
._lastTokenTime
}
180 pickle
.dump(session
, f
, protocol
=pickle
.HIGHEST_PROTOCOL
)
183 print "An error occurred during save session"
186 # Gets a stream key and host to get song content
187 def _getStreamDetails(self
, songID
):
192 "country": {"IPR":"1021","ID":"223", "CC1":"0", "CC2":"0", "CC3":"0", "CC4":"2147483648"}
194 response
= self
._callRemote
("getStreamKeyFromSongIDEx", params
)
195 self
._lastStreamKey
= response
["result"]["streamKey"]
196 self
._lastStreamServer
= response
["result"]["ip"]
197 self
._lastStreamServerID
= response
["result"]["streamServerID"]
199 # Tells Grooveshark you have downloaded a song
200 def _markSongDownloaded(self
, songID
):
202 "streamKey": self
._lastStreamKey
,
203 "streamServerID": self
._lastStreamServerID
,
205 self
._callRemote
("markSongDownloaded", params
)
207 # Download a song to a temporary file
208 def getSongURL(self
, songID
):
209 filename
= os
.path
.join(self
.cacheDir
, songID
+ '.mp3')
210 if os
.path
.isfile(filename
) == False:
211 self
._getStreamDetails
(songID
)
212 postData
= {"streamKey": self
._lastStreamKey
}
213 postData
= urllib
.urlencode(postData
)
214 urllib
.FancyURLopener().retrieve( "http://" + self
._lastStreamServer
+ "/stream.php", filename
, data
=postData
)
215 self
._markSongDownloaded
(songID
)
224 host
= 'api.grooveshark.com'
228 def __init__(self
, cacheDir
):
230 self
.simplejson
= simplejson
231 socket
.setdefaulttimeout(40)
232 self
.cacheDir
= cacheDir
233 self
._getSavedSession
()
234 # session ids last 1 week
235 if self
.sessionID
== '' or time
.time()- self
.lastSessionTime
>= SESSION_EXPIRY
:
236 self
.sessionID
= self
._getSessionID
()
237 if self
.sessionID
== '':
238 raise StandardError('Failed to get session id')
240 print "New GrooveAPI session id: " + self
.sessionID
241 self
._setSavedSession
()
244 def _keySort(self
, d
):
245 return [(k
,d
[k
]) for k
in sorted(d
.keys())]
248 def _createMessageSig(self
, method
, params
, secret
):
249 args
= self
._keySort
(params
);
256 h
= hmac
.new(secret
, data
)
259 # The actual call to the API
260 def _callRemote(self
, method
, params
= {}):
261 url
= 'http://%s/ws/2.1/?method=%s&%s&wsKey=wordpress&sig=%s&format=json' % (self
.host
, method
, urllib
.urlencode(params
), self
._createMessageSig
(method
, params
, 'd6c59291620c6eaa5bf94da08fae0ecc'))
263 req
= urllib2
.Request(url
)
264 response
= urllib2
.urlopen(req
)
265 result
= response
.read()
266 pprint
.pprint(result
)
269 result
= self
.simplejson
.loads(result
)
275 def _getSessionID(self
):
277 result
= self
._callRemote
('startSession', params
)
278 self
.lastSessionTime
= time
.time()
279 return result
['result']['sessionID']
281 def _getSavedSession(self
):
282 path
= os
.path
.join(self
.cacheDir
, 'grooveapi', 'session.dmp')
285 session
= pickle
.load(f
)
286 self
.sessionID
= session
['sessionID']
287 self
.lastSessionTime
= session
['lastSessionTime']
288 self
.userID
= session
['userID']
292 self
.lastSessionTime
= 0
296 def _setSavedSession(self
):
298 dir = os
.path
.join(self
.cacheDir
, 'grooveapi')
299 # Create the 'data' directory if it doesn't exist.
300 if not os
.path
.exists(dir):
302 path
= os
.path
.join(dir, 'session.dmp')
304 session
= { 'sessionID' : self
.sessionID
, 'lastSessionTime' : self
.lastSessionTime
, 'userID': self
.userID
}
305 pickle
.dump(session
, f
, protocol
=pickle
.HIGHEST_PROTOCOL
)
308 print "An error occurred during save session"
311 # Make user authentication token
312 def _getUserToken(self
, username
, password
):
313 return md5
.new(username
.lower() + md5
.new(password
).hexdigest()).hexdigest()
315 # Authenticates the user for current API session
316 def _authenticateUser(self
, username
, token
):
317 params
= {'sessionID': self
.sessionID
, 'username': username
, 'token': token
}
318 result
= self
._callRemote
('authenticateUser', params
)
319 return result
['result']['UserID']
322 def login(self
, username
, password
):
325 self
._getSavedSession
()
327 token
= self
._getUserToken
(username
, password
)
328 self
.userID
= self
._authenticateUser
(username
, token
)
330 self
._setSavedSession
()
335 result
= self
._callRemote
('logout', {'sessionID' : self
.sessionID
})
336 if 'result' in result
and result
['result']['success'] == True:
338 self
._setSavedSession
()
343 def getArtistSearchResults(self
, query
, limit
=ARTIST_LIMIT
):
344 result
= self
._callRemote
('getArtistSearchResults', {'query' : query
,'limit' : limit
})
345 if 'result' in result
:
346 return self
._parseArtists
(result
)
351 def getAlbumSearchResults(self
, query
, limit
=ALBUM_LIMIT
):
352 result
= self
._callRemote
('getAlbumSearchResults', {'query' : query
,'limit' : limit
})
353 if 'result' in result
:
354 return self
._parseAlbums
(result
)
359 def getSongSearchResults(self
, query
, limit
=SONG_LIMIT
):
360 result
= self
._callRemote
('getSongSearchResultsEx', {'query' : query
, 'limit' : limit
})
361 if 'result' in result
:
362 return self
._parseSongs
(result
)
366 # Gets the popular songs
367 def getPopularSongsToday(self
, limit
=SONG_LIMIT
):
368 result
= self
._callRemote
('getPopularSongsToday', {'limit' : limit
})
369 if 'result' in result
:
370 return self
._parseSongs
(result
)
374 # Gets the favorite songs of the logged-in user
375 def getUserFavoriteSongs(self
):
376 if (self
.userID
== 0):
378 result
= self
._callRemote
('getUserFavoriteSongs', {'sessionID' : self
.sessionID
})
379 if 'result' in result
:
380 return self
._parseSongs
(result
)
384 # Add song to user favorites
385 def addUserFavoriteSong(self
, songID
):
386 if (self
.userID
== 0):
388 result
= self
._callRemote
('addUserFavoriteSong', {'sessionID' : self
.sessionID
, 'songID' : songID
})
389 return result
['result']['success']
391 # Get the url to link to a song on Grooveshark
392 def getSongURLFromSongID(self
, songID
):
393 cacheDir
= os
.path
.join(self
.cacheDir
, 'groovesong')
394 song
= GrooveSong(cacheDir
)
395 return song
.getSongURL(songID
)
397 # Get the url to link to a song on Grooveshark
398 def getSongInfo(self
, songID
):
399 result
= self
._callRemote
('getSongInfoEx', {'songID' : songID
})
400 if 'result' in result
and 'SongID' in result
['result']:
401 info
= result
['result']
402 if 'CoverArtFilename' in info
and info
['CoverArtFilename'] != None:
403 info
['CoverArtFilename'] = THUMB_URL
+info
['CoverArtFilename'].encode('ascii', 'ignore')
405 info
['CoverArtFilename'] = THUMB_URL_DEFAULT
410 # Gets the playlists of the logged-in user
411 def getUserPlaylists(self
):
412 if (self
.userID
== 0):
414 result
= self
._callRemote
('getUserPlaylists', {'sessionID' : self
.sessionID
})
415 if 'result' in result
:
416 return self
._parsePlaylists
(result
)
420 # Creates a playlist with songs
421 def createPlaylist(self
, name
, songIDs
):
422 result
= self
._callRemote
('createPlaylist', {'name' : name
, 'songIDs' : songIDs
, 'sessionID' : self
.sessionID
})
423 if 'result' in result
and result
['result']['success'] == True:
424 return result
['result']['PlaylistID']
425 elif 'errors' in result
:
428 # Sets the songs for a playlist
429 def setPlaylistSongs(self
, playlistID
, songIDs
):
430 result
= self
._callRemote
('setPlaylistSongs', {'playlistID' : playlistID
, 'songIDs' : songIDs
, 'sessionID' : self
.sessionID
})
431 if 'result' in result
and result
['result']['success'] == True:
436 # Gets the songs of a playlist
437 def getPlaylistSongs(self
, playlistID
):
438 result
= self
._callRemote
('getPlaylistSongs', {'playlistID' : playlistID
});
439 if 'result' in result
:
440 return self
._parseSongs
(result
)
445 def _parseSongs(self
, items
):
446 if 'result' in items
:
449 if 'songs' in items
['result']:
450 l
= len(items
['result']['songs'])
452 elif 'song' in items
['result']:
456 l
= len(items
['result'])
460 s
= items
['result'][index
][i
]
461 elif index
== 'song':
462 s
= items
['result'][index
]
464 s
= items
['result'][i
]
465 if 'CoverArtFilename' not in s
:
466 info
= self
.getSongInfo(s
['SongID'])
467 coverart
= info
['CoverArtFilename']
468 elif s
['CoverArtFilename'] != None:
469 coverart
= THUMB_URL
+s
['CoverArtFilename'].encode('ascii', 'ignore')
471 coverart
= THUMB_URL_DEFAULT
472 list.append([s
['SongName'].encode('ascii', 'ignore'),\
474 s
['AlbumName'].encode('ascii', 'ignore'),\
476 s
['ArtistName'].encode('ascii', 'ignore'),\
484 # Extract artist data
485 def _parseArtists(self
, items
):
486 if 'result' in items
:
489 artists
= items
['result']['artists']
490 while(i
< len(artists
)):
492 list.append([s
['ArtistName'].encode('ascii', 'ignore'),\
500 def _parseAlbums(self
, items
):
501 if 'result' in items
:
504 albums
= items
['result']['albums']
505 while(i
< len(albums
)):
507 if 'CoverArtFilename' in s
and s
['CoverArtFilename'] != None:
508 coverart
= THUMB_URL
+s
['CoverArtFilename'].encode('ascii', 'ignore')
510 coverart
= THUMB_URL_DEFAULT
511 list.append([s
['ArtistName'].encode('ascii', 'ignore'),\
513 s
['AlbumName'].encode('ascii', 'ignore'),\
521 def _parsePlaylists(self
, items
):
522 if 'result' in items
:
525 playlists
= items
['result']
526 while(i
< len(playlists
)):
528 list.append([s
['PlaylistID'],\
529 s
['Name'].encode('ascii', 'ignore')])
540 groovesharkApi
= GrooveAPI('/tmp')
541 #res = groovesharkApi.login(sys.argv[1], sys.argv[2])
542 #res = groovesharkApi.getSongSearchResults('jimmy jazz', 3)
543 #res = groovesharkApi.getPopularSongsToday()
544 res
= groovesharkApi
.getSongURLFromSongID('27425375')
545 #res = groovesharkApi.getAlbumSearchResults('london calling', 3)
546 #res = groovesharkApi.getArtistSearchResults('the clash', 3)
547 #res = groovesharkApi.getUserFavoriteSongs()
548 #res = groovesharkApi.getUserPlaylists()
549 #res = groovesharkApi.getSongInfo('27425375')
550 #res = groovesharkApi.getPlaylistSongs(40902662)
551 #res = groovesharkApi.addUserFavoriteSong('27425375')
552 #res = groovesharkApi.logout()