8817bb2e |
1 | import urllib2, md5, unicodedata, re, os, traceback, sys, pickle, socket |
2 | from operator import itemgetter, attrgetter |
3 | |
4 | class LoginTokensExceededError(Exception): |
5 | def __init__(self): |
6 | self.value = 'You have created to many tokens. Only 12 are allowed' |
7 | def __str__(self): |
8 | return repr(self.value) |
9 | |
10 | class LoginUnknownError(Exception): |
11 | def __init__(self): |
12 | self.value = 'Unable to get a new session ID. Wait a few minutes and try again' |
13 | def __str__(self): |
14 | return repr(self.value) |
15 | |
16 | class SessionIDTryAgainError(Exception): |
17 | def __init__(self): |
18 | self.value = 'Unable to get a new session ID. Wait a few minutes and try again' |
19 | def __str__(self): |
20 | return repr(self.value) |
21 | |
22 | class GrooveAPI: |
23 | def __init__(self, enableDebug = False, isXbox = False): |
24 | if isXbox == True: |
25 | import simplejson_xbox |
26 | self.simplejson = simplejson_xbox |
27 | print 'GrooveShark: Initialized as XBOX script' |
28 | else: |
29 | import simplejson |
30 | self.simplejson = simplejson |
31 | timeout = 40 |
32 | socket.setdefaulttimeout(timeout) |
33 | self.enableDebug = enableDebug |
34 | self.loggedIn = 0 |
35 | self.radioEnabled = 0 |
36 | self.userId = 0 |
37 | self.seedArtists = [] |
38 | self.frowns = [] |
39 | self.songIDsAlreadySeen = [] |
40 | self.recentArtists = [] |
41 | self.rootDir = os.getcwd() |
42 | self.sessionID = self.getSavedSession() |
43 | self.debug('Saved sessionID: ' + self.sessionID) |
44 | self.sessionID = self.getSessionFromAPI() |
45 | self.debug('API sessionID: ' + self.sessionID) |
46 | if self.sessionID == '': |
47 | self.sessionID = self.startSession() |
48 | self.debug('Start() sessionID: ' + self.sessionID) |
49 | if self.sessionID == '': |
50 | self.debug('Could not get a sessionID. Try again in a few minutes') |
51 | raise SessionIDTryAgainError() |
52 | else: |
53 | self.saveSession() |
54 | |
55 | self.debug('sessionID: ' + self.sessionID) |
56 | |
57 | def __del__(self): |
58 | try: |
59 | if self.loggedIn == 1: |
60 | self.logout() |
61 | except: |
62 | pass |
63 | |
64 | def debug(self, msg): |
65 | if self.enableDebug == True: |
66 | print msg |
67 | |
68 | def getSavedSession(self): |
69 | sessionID = '' |
70 | path = os.path.join(self.rootDir, 'data', 'session.txt') |
71 | |
72 | try: |
73 | f = open(path, 'rb') |
74 | sessionID = pickle.load(f) |
75 | f.close() |
76 | except: |
77 | sessionID = '' |
78 | pass |
79 | |
80 | return sessionID |
81 | |
82 | def saveSession(self): |
83 | try: |
84 | dir = os.path.join(self.rootDir, 'data') |
85 | # Create the 'data' directory if it doesn't exist. |
86 | if not os.path.exists(dir): |
87 | os.mkdir(dir) |
88 | path = os.path.join(dir, 'session.txt') |
89 | f = open(path, 'wb') |
90 | pickle.dump(self.sessionID, f, protocol=pickle.HIGHEST_PROTOCOL) |
91 | f.close() |
92 | except IOError, e: |
93 | print 'There was an error while saving the session pickle (%s)' % e |
94 | pass |
95 | except: |
96 | print "An unknown error occured during save session: " + str(sys.exc_info()[0]) |
97 | pass |
98 | |
99 | def saveSettings(self): |
100 | try: |
101 | dir = os.path.join(self.rootDir, 'data') |
102 | # Create the 'data' directory if it doesn't exist. |
103 | if not os.path.exists(dir): |
104 | os.mkdir(dir) |
105 | path = os.path.join(dir, 'settings1.txt') |
106 | f = open(path, 'wb') |
107 | pickle.dump(self.settings, f, protocol=pickle.HIGHEST_PROTOCOL) |
108 | f.close() |
109 | except IOError, e: |
110 | print 'There was an error while saving the settings pickle (%s)' % e |
111 | pass |
112 | except: |
113 | print "An unknown error occured during save settings\n" |
114 | pass |
115 | |
116 | def callRemote(self, method, params={}): |
117 | data = {'header': {'sessionID': self.sessionID}, 'method': method, 'parameters': params} |
118 | #data = {'header': {'sessionID': None}, 'method': method, 'parameters': params} |
119 | data = self.simplejson.dumps(data) |
120 | #proxy_support = urllib2.ProxyHandler({"http" : "http://wwwproxy.kom.aau.dk:3128"}) |
121 | ## build a new opener with proxy details |
122 | #opener = urllib2.build_opener(proxy_support, urllib2.HTTPHandler) |
123 | ## install it |
124 | #urllib2.install_opener(opener) |
125 | #print data |
126 | req = urllib2.Request("http://api.grooveshark.com/ws/1.0/?json") |
127 | req.add_header('Host', 'api.grooveshark.com') |
128 | req.add_header('Content-type', 'text/json') |
129 | req.add_header('Content-length', str(len(data))) |
130 | req.add_data(data) |
131 | response = urllib2.urlopen(req) |
132 | result = response.read() |
133 | response.close() |
134 | try: |
135 | result = self.simplejson.loads(result) |
136 | if 'fault' in result: |
137 | self.debug(result) |
138 | return result |
139 | except: |
140 | return [] |
141 | |
142 | def startSession(self): |
143 | response = urllib2.urlopen("http://www.moovida.com/services/grooveshark/session_start") |
144 | result = response.read() |
145 | result = self.simplejson.loads(result) |
146 | response.close() |
147 | if 'fault' in result: |
148 | return '' |
149 | else: |
150 | return result['header']['sessionID'] |
151 | |
152 | def sessionDestroy(self): |
153 | return self.callRemote("session.destroy") |
154 | |
155 | def getSessionFromAPI(self): |
156 | result = self.callRemote("session.get") |
157 | if 'fault' in result: |
158 | return '' |
159 | else: |
160 | return result['header']['sessionID'] |
161 | |
162 | def getStreamURL(self, songID): |
163 | result = self.callRemote("song.getStreamUrlEx", {"songID": songID}) |
164 | if 'result' in result: |
165 | return result['result']['url'] |
166 | else: |
167 | return '' |
168 | |
169 | def createUserAuthToken(self, username, password): |
170 | hashpass = md5.new(password).hexdigest() |
171 | hashpass = username + hashpass |
172 | hashpass = md5.new(hashpass).hexdigest() |
173 | result = self.callRemote("session.createUserAuthToken", {"username": username, "hashpass": hashpass}) |
174 | if 'result' in result: |
175 | return result['result']['token'], result['result']['userID'] |
176 | elif 'fault' in result: |
177 | if result['fault']['code'] == 256: |
178 | return -1 # Exceeded the number of allowed tokens. Should not happen |
179 | else: |
180 | return -2 # Unknown error |
181 | else: |
182 | return -2 # Unknown error |
183 | |
184 | def destroyUserAuthToken(self, token): |
185 | self.callRemote("session.destroyAuthToken", {"token": token}) |
186 | |
187 | def loginViaAuthToken(self, token): |
188 | result = self.callRemote("session.loginViaAuthToken", {"token": token}) |
189 | self.destroyUserAuthToken(token) |
190 | if 'result' in result: |
191 | self.userID = result['result']['userID'] |
192 | return result['result']['userID'] |
193 | else: |
194 | return 0 |
195 | |
196 | def login(self, username, password): |
197 | if self.loggedIn == 1: |
198 | return self.userId |
199 | result = self.createUserAuthToken(username, password) |
200 | if result == -1: |
201 | raise LoginTokensExceededError() |
202 | elif result == -2: |
203 | raise LoginUnknownError() |
204 | else: |
205 | self.token = result[0] |
206 | self.debug('Token:' + self.token) |
207 | self.userId = self.loginViaAuthToken(self.token) |
208 | if self.userId == 0: |
209 | raise LoginUnknownError() |
210 | else: |
211 | self.loggedIn = 1 |
212 | return self.userId |
213 | |
214 | def loginExt(self, username, password): |
215 | if self.loggedIn == 1: |
216 | return self.userId |
217 | token = md5.new(username.lower() + md5.new(password).hexdigest()).hexdigest() |
218 | result = self.callRemote("session.loginExt", {"username": username, "token": token}) |
219 | if 'result' in result: |
220 | if 'userID' in result['result']: |
221 | self.loggedIn = 1 |
222 | self.userId = result['result']['userID'] |
223 | return result['result']['userID'] |
224 | else: |
225 | return 0 |
226 | |
227 | |
228 | def loginBasic(self, username, password): |
229 | if self.loggedIn == 1: |
230 | return self.userId |
231 | result = self.callRemote("session.login", {"username": username, "password": password}) |
232 | if 'result' in result: |
233 | if 'userID' in result['result']: |
234 | self.loggedIn = 1 |
235 | self.userId = result['result']['userID'] |
236 | return result['result']['userID'] |
237 | else: |
238 | return 0 |
239 | |
240 | def loggedInStatus(self): |
241 | return self.loggedIn |
242 | |
243 | def logout(self): |
244 | self.callRemote("session.logout", {}) |
245 | self.loggedIn = 0 |
246 | |
247 | def getSongInfo(self, songID): |
248 | return self.callRemote("song.about", {"songID": songID})['result']['song'] |
249 | |
250 | def userGetFavoriteSongs(self, userID): |
251 | result = self.callRemote("user.getFavoriteSongs", {"userID": userID}) |
252 | list = self.parseSongs(result) |
253 | return list |
254 | |
255 | def userGetPlaylists(self, limit=25): |
256 | if self.loggedIn == 1: |
257 | result = self.callRemote("user.getPlaylists", {"userID": self.userId, "limit": limit}) |
258 | if 'result' in result: |
259 | playlists = result['result']['playlists'] |
260 | else: |
261 | return [] |
262 | i = 0 |
263 | list = [] |
264 | while(i < len(playlists)): |
265 | p = playlists[i] |
266 | list.append([p['playlistName'].encode('ascii', 'ignore'), p['playlistID']]) |
267 | i = i + 1 |
268 | return sorted(list, key=itemgetter(0)) |
269 | else: |
270 | return [] |
271 | |
272 | def playlistCreate(self, name, about): |
273 | if self.loggedIn == 1: |
274 | result = self.callRemote("playlist.create", {"name": name, "about": about}) |
275 | if 'result' in result: |
276 | return result['result']['playlistID'] |
277 | else: |
278 | return 0 |
279 | else: |
280 | return 0 |
281 | |
282 | def playlistGetSongs(self, playlistId, limit=25): |
283 | result = self.callRemote("playlist.getSongs", {"playlistID": playlistId}) |
284 | list = self.parseSongs(result) |
285 | return list |
286 | |
287 | def playlistDelete(self, playlistId): |
288 | if self.loggedIn == 1: |
289 | return self.callRemote("playlist.delete", {"playlistID": playlistId}) |
290 | |
291 | def playlistRename(self, playlistId, name): |
292 | if self.loggedIn == 1: |
293 | result = self.callRemote("playlist.rename", {"playlistID": playlistId, "name": name}) |
294 | if 'fault' in result: |
295 | return 0 |
296 | else: |
297 | return 1 |
298 | else: |
299 | return 0 |
300 | |
301 | def playlistClearSongs(self, playlistId): |
302 | if self.loggedIn == 1: |
303 | return self.callRemote("playlist.clearSongs", {"playlistID": playlistId}) |
304 | |
305 | def playlistAddSong(self, playlistId, songId, position): |
306 | if self.loggedIn == 1: |
307 | result = self.callRemote("playlist.addSong", {"playlistID": playlistId, "songID": songId, "position": position}) |
308 | if 'fault' in result: |
309 | return 0 |
310 | else: |
311 | return 1 |
312 | else: |
313 | return 0 |
314 | |
315 | def playlistReplace(self, playlistId, songIds): |
316 | if self.loggedIn == 1: |
317 | result = self.callRemote("playlist.replace", {"playlistID": playlistId, "songIDs": songIds}) |
318 | if 'fault' in result: |
319 | return 0 |
320 | else: |
321 | return 1 |
322 | else: |
323 | return 0 |
324 | |
325 | def autoplayStartWithArtistIDs(self, artistIds): |
326 | result = self.callRemote("autoplay.startWithArtistIDs", {"artistIDs": artistIds}) |
327 | if 'fault' in result: |
328 | self.radioEnabled = 0 |
329 | return 0 |
330 | else: |
331 | self.radioEnabled = 1 |
332 | return 1 |
333 | |
334 | def autoplayStart(self, songIds): |
335 | result = self.callRemote("autoplay.start", {"songIDs": songIds}) |
336 | if 'fault' in result: |
337 | self.radioEnabled = 0 |
338 | return 0 |
339 | else: |
340 | self.radioEnabled = 1 |
341 | return 1 |
342 | |
343 | def autoplayStop(self): |
344 | result = self.callRemote("autoplay.stop", {}) |
345 | if 'fault' in result: |
346 | self.radioEnabled = 1 |
347 | return 0 |
348 | else: |
349 | self.radioEnabled = 0 |
350 | return 1 |
351 | |
352 | def autoplayGetNextSongEx(self, seedArtists = [], frowns = [], songIDsAlreadySeen = [], recentArtists = []): |
353 | result = self.callRemote("autoplay.getNextSongEx", {"seedArtists": seedArtists, "frowns": frowns, "songIDsAlreadySeen": songIDsAlreadySeen, "recentArtists": recentArtists}) |
354 | if 'fault' in result: |
355 | return [] |
356 | else: |
357 | return result |
358 | |
359 | def radioGetNextSong(self): |
360 | if self.seedArtists == []: |
361 | return [] |
362 | else: |
363 | result = self.autoplayGetNextSongEx(self.seedArtists, self.frowns, self.songIDsAlreadySeen, self.recentArtists) |
364 | # print result |
365 | if 'fault' in result: |
366 | return [] |
367 | else: |
368 | song = self.parseSongs(result) |
369 | self.radioAlreadySeen(song[0][1]) |
370 | return song |
371 | |
372 | def radioFrown(self, songId): |
373 | self.frown.append(songId) |
374 | |
375 | def radioAlreadySeen(self, songId): |
376 | self.songIDsAlreadySeen.append(songId) |
377 | |
378 | def radioAddArtist(self, artistId): |
379 | self.seedArtists.append(artistId) |
380 | |
381 | def radioStart(self, artists = [], frowns = []): |
382 | for artist in artists: |
383 | self.seedArtists.append(artist) |
384 | for artist in frowns: |
385 | self.frowns.append(artist) |
386 | if self.autoplayStartWithArtistIDs(self.seedArtists) == 1: |
387 | self.radioEnabled = 1 |
388 | return 1 |
389 | else: |
390 | self.radioEnabled = 0 |
391 | return 0 |
392 | |
393 | def radioStop(self): |
394 | self.seedArtists = [] |
395 | self.frowns = [] |
396 | self.songIDsAlreadySeen = [] |
397 | self.recentArtists = [] |
398 | self.radioEnabled = 0 |
399 | |
400 | def radioTurnedOn(self): |
401 | return self.radioEnabled |
402 | |
403 | def favoriteSong(self, songID): |
404 | return self.callRemote("song.favorite", {"songID": songID}) |
405 | |
406 | def unfavoriteSong(self, songID): |
407 | return self.callRemote("song.unfavorite", {"songID": songID}) |
408 | |
409 | def getMethods(self): |
410 | return self.callRemote("service.getMethods") |
411 | |
412 | def searchSongsExactMatch(self, songName, artistName, albumName): |
413 | result = self.callRemote("search.songExactMatch", {"songName": songName, "artistName": artistName, "albumName": albumName}) |
414 | list = self.parseSongs(result) |
415 | return list |
416 | |
417 | def searchSongs(self, query, limit, page=0, sortKey=6): |
418 | result = self.callRemote("search.songs", {"query": query, "limit": limit, "page:": page, "streamableOnly": 1}) |
419 | list = self.parseSongs(result) |
420 | return list |
421 | #return sorted(list, key=itemgetter(sortKey)) |
422 | |
423 | def searchArtists(self, query, limit, sortKey=0): |
424 | result = self.callRemote("search.artists", {"query": query, "limit": limit, "streamableOnly": 1}) |
425 | list = self.parseArtists(result) |
426 | return list |
427 | #return sorted(list, key=itemgetter(sortKey)) |
428 | |
429 | def searchAlbums(self, query, limit, sortKey=2): |
430 | result = self.callRemote("search.albums", {"query": query, "limit": limit, "streamableOnly": 1}) |
431 | list = self.parseAlbums(result) |
432 | return list |
433 | #return sorted(list, key=itemgetter(sortKey)) |
434 | |
435 | def searchPlaylists(self, query, limit): |
436 | result = self.callRemote("search.playlists", {"query": query, "limit": limit, "streamableOnly": 1}) |
437 | list = self.parsePlaylists(result) |
438 | return list |
439 | |
440 | def popularGetSongs(self, limit): |
441 | result = self.callRemote("popular.getSongs", {"limit": limit}) |
442 | list = self.parseSongs(result) |
443 | return list |
444 | |
445 | def popularGetArtists(self, limit): |
446 | result = self.callRemote("popular.getArtists", {"limit": limit}) |
447 | list = self.parseArtists(result) |
448 | return list |
449 | |
450 | def popularGetAlbums(self, limit): |
451 | result = self.callRemote("popular.getAlbums", {"limit": limit}) |
452 | list = self.parseAlbums(result) |
453 | return list |
454 | |
455 | def artistGetAlbums(self, artistId, limit, sortKey=2): |
456 | result = self.callRemote("artist.getAlbums", {"artistID": artistId, "limit": limit}) |
457 | list = self.parseAlbums(result) |
458 | return list |
459 | #return sorted(list, key=itemgetter(sortKey)) |
460 | |
461 | def artistGetVerifiedAlbums(self, artistId, limit): |
462 | result = self.callRemote("artist.getVerifiedAlbums", {"artistID": artistId, "limit": limit}) |
463 | list = self.parseSongs(result) |
464 | return list |
465 | |
466 | def albumGetSongs(self, albumId, limit): |
467 | result = self.callRemote("album.getSongs", {"albumID": albumId, "limit": limit}) |
468 | list = self.parseSongs(result) |
469 | return list |
470 | |
471 | def songGetSimilar(self, songId, limit): |
472 | result = self.callRemote("song.getSimilar", {"songID": songId, "limit": limit}) |
473 | list = self.parseSongs(result) |
474 | return list |
475 | |
476 | def parseSongs(self, items): |
477 | try: |
478 | if 'result' in items: |
479 | i = 0 |
480 | list = [] |
481 | if 'songs' in items['result']: |
482 | l = len(items['result']['songs']) |
483 | index = 'songs' |
484 | elif 'song' in items['result']: |
485 | l = 1 |
486 | index = 'song' |
487 | else: |
488 | l = 0 |
489 | index = '' |
490 | while(i < l): |
491 | if index == 'songs': |
492 | s = items['result'][index][i] |
493 | else: |
494 | s = items['result'][index] |
495 | if 'estDurationSecs' in s: |
496 | dur = s['estDurationSecs'] |
497 | else: |
498 | dur = 0 |
499 | try: |
500 | notIn = True |
501 | for entry in list: |
502 | songName = s['songName'].encode('ascii', 'ignore') |
503 | albumName = s['albumName'].encode('ascii', 'ignore') |
504 | artistName = s['artistName'].encode('ascii', 'ignore') |
505 | if (entry[0].lower() == songName.lower()) and (entry[3].lower() == albumName.lower()) and (entry[6].lower() == artistName.lower()): |
506 | notIn = False |
507 | if notIn == True: |
508 | list.append([s['songName'].encode('ascii', 'ignore'),\ |
509 | s['songID'],\ |
510 | dur,\ |
511 | s['albumName'].encode('ascii', 'ignore'),\ |
512 | s['albumID'],\ |
513 | s['image']['tiny'].encode('ascii', 'ignore'),\ |
514 | s['artistName'].encode('ascii', 'ignore'),\ |
515 | s['artistID'],\ |
516 | s['image']['small'].encode('ascii', 'ignore'),\ |
517 | s['image']['medium'].encode('ascii', 'ignore')]) |
518 | except: |
519 | print 'GrooveShark: Could not parse song number: ' + str(i) |
520 | traceback.print_exc() |
521 | i = i + 1 |
522 | return list |
523 | else: |
524 | return [] |
525 | pass |
526 | except: |
527 | print 'GrooveShark: Could not parse songs. Got this:' |
528 | traceback.print_exc() |
529 | return [] |
530 | |
531 | def parseArtists(self, items): |
532 | if 'result' in items: |
533 | i = 0 |
534 | list = [] |
535 | artists = items['result']['artists'] |
536 | while(i < len(artists)): |
537 | s = artists[i] |
538 | list.append([s['artistName'].encode('ascii', 'ignore'),\ |
539 | s['artistID']]) |
540 | i = i + 1 |
541 | return list |
542 | else: |
543 | return [] |
544 | |
545 | def parseAlbums(self, items): |
546 | if 'result' in items: |
547 | i = 0 |
548 | list = [] |
549 | albums = items['result']['albums'] |
550 | while(i < len(albums)): |
551 | s = albums[i] |
552 | list.append([s['artistName'].encode('ascii', 'ignore'),\ |
553 | s['artistID'],\ |
554 | s['albumName'].encode('ascii', 'ignore'),\ |
555 | s['albumID'],\ |
556 | s['image']['tiny'].encode('ascii', 'ignore')]) |
557 | i = i + 1 |
558 | return list |
559 | else: |
560 | return [] |
561 | |
562 | def parsePlaylists(self, items): |
563 | if 'result' in items: |
564 | i = 0 |
565 | list = [] |
566 | playlists = items['result']['playlists'] |
567 | while(i < len(playlists)): |
568 | s = playlists[i] |
569 | list.append([s['playlistID'],\ |
570 | s['playlistName'].encode('ascii', 'ignore'),\ |
571 | s['username'].encode('ascii', 'ignore')]) |
572 | i = i + 1 |
573 | return list |
574 | else: |
575 | return [] |