/
PandoraUnchained.py
175 lines (127 loc) · 6.18 KB
/
PandoraUnchained.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#!/usr/local/bin/python
#############################################################################################
# PANDORA UNCHAINED by jfktrey
# release 2, 2013-5-21
#
# Free your Pandora bookmarks and likes for use on other platforms.
#############################################################################################
import sys, os, json, csv, cStringIO
from collections import OrderedDict
from string import Template
import twill.commands as Browser # These twill libraries are modified. See commands.js for more details.
import twill.browser as _browser
from twill.BeautifulSoup3 import BeautifulSoup
DEBUG = False
LOGFILE = open('log.txt', 'w') if DEBUG else open(os.devnull, 'w')
Browser.OUT = LOGFILE
_browser.OUT = LOGFILE
PANDORA_LOGIN_URL = 'http://www.pandora.com/login.vm'
PANDORA_WEBNAME_URL = Template('http://feeds.pandora.com/services/ajax/?method=authenticate.emailToWebname&email=${email}')
PANDORA_BOOKMARKS_URL = Template('http://www.pandora.com/content/bookmarked_tracks?trackStartIndex=${trackstart}&webname=${webname}') #returns 5 bookmarks at a time
PANDORA_LIKES_COUNT_URL = Template('http://www.pandora.com/content/likes?webname=${webname}')
PANDORA_LIKES_URL = Template('http://www.pandora.com/content/tracklikes?likeStartIndex=${likestart}&thumbStartIndex=${thumbstart}&webname=${webname}') #returns 5 or 10 bookmarks at a time (...???)
PANDORA_STATIONS_URL = Template('http://www.pandora.com/content/stations?startIndex=${start}&webname=${webname}') #returns all stations
# The information that comes about a user's profile from v35, while useful (number of bookmarks, number of likes, number of stations), is encrypted.
# Even the request's POST data is encrypted with Blowfish. I can't just insert values into the request because it's encrypted. Would be possible through much more work or a full-blown headless browser like PhantomJS.
class PandoraUnchained:
def __init__ (self, email, password):
Browser.go(PANDORA_LOGIN_URL)
Browser.formvalue(1, 'login_username', email)
Browser.formvalue(1, 'login_password', password)
Browser.submit()
self.webname = Browser.info().split('/').pop()
def getNumberOfLikes (self):
url = PANDORA_LIKES_COUNT_URL.substitute(webname = self.webname)
Browser.go(url)
soup = BeautifulSoup(Browser.show())
songsDiv = soup.find('div', {'id': 'songs'})
numberSpan = songsDiv.find('span', {'class': 'section_count'})
return int(numberSpan.text.strip('()'))
def getBookmarks (self, currentTrackCallback):
lastRequestedTrackIndex = 0 # the number of the first track that was requested in the previous set of bookmarks
newBookmarksCount = 0 # the number of new bookmarks in this request
bookmarks = []
while self._returnedFullPageOfBookmarks(lastRequestedTrackIndex, newBookmarksCount):
newTrackIndex = lastRequestedTrackIndex + newBookmarksCount
newBookmarks = self._requestBookmarks(newTrackIndex)
lastRequestedTrackIndex = newTrackIndex
bookmarks += newBookmarks # adding in the actual song titles and artists
newBookmarksCount = len(newBookmarks) # the number of new bookmarks in this request
currentTrackCallback(lastRequestedTrackIndex)
return bookmarks
def _requestBookmarks (self, startingTrack):
url = PANDORA_BOOKMARKS_URL.substitute(trackstart = startingTrack, webname = self.webname)
Browser.go(url)
return self._songHtmlToList(Browser.show())
def _returnedFullPageOfBookmarks (self, startingTrack, numberOfBookmarks):
if (startingTrack == numberOfBookmarks == 0):
return True
elif (startingTrack <= 5) and (numberOfBookmarks == 5):
return True
elif (startingTrack > 5) and (numberOfBookmarks == 10):
return True
else:
return False
def getLikes (self, currentTrackCallback):
likeIndex = 0
thumbIndex = 0
newLikesCount = 0
likes = []
while (likeIndex != None) and (thumbIndex != None):
(newLikes, likeIndex, thumbIndex) = self._requestLikes(likeIndex, thumbIndex)
currentTrackCallback(likeIndex, thumbIndex)
likes += newLikes
newLikesCount = len(newLikes)
return likes
def _requestLikes (self, startingLike, startingThumb):
url = PANDORA_LIKES_URL.substitute(webname = self.webname, likestart = startingLike, thumbstart = startingThumb)
Browser.go(url)
likesList = self._songHtmlToList(Browser.show())
soup = BeautifulSoup(Browser.show())
nextInfoElement = soup.find('div', {'class': 'show_more tracklike'})
nextStartingLike = self._attributeNumberValueOrZero(nextInfoElement, 'data-nextlikestartindex')
nextStartingThumb = self._attributeNumberValueOrZero(nextInfoElement, 'data-nextthumbstartindex')
return (likesList, nextStartingLike, nextStartingThumb)
def _songHtmlToList (self, html):
songList = []
soup = BeautifulSoup(Browser.show())
songs = soup.findAll('div', {'class': 'infobox-body'})
for song in songs:
links = song.findAll('a')
title = links[0].text
artist = links[1].text
songList.append([title, artist])
return songList
def _attributeNumberValueOrZero (self, element, attribute):
try:
return int(element[attribute])
except KeyError:
return 0
except TypeError:
return None
def close (self):
LOGFILE.close()
#############################################################################################
def textFromSongList (songList):
songText = ''
for song in songList:
songText += '%s by %s\n' %(song[0], song[1])
return songText
def csvFromSongList (songList):
songCsv = cStringIO.StringIO()
songList = [['title', 'artist']] + songList
writer = csv.writer(songCsv, dialect='excel', quotechar='"', quoting=csv.QUOTE_ALL)
for song in songList:
writer.writerow(song)
return songCsv.getvalue().replace('\r\n', '\n')
def makeSongListNoDuplicates (songList):
cleanedSongList = []
for song in songList:
if song not in cleanedSongList:
cleanedSongList.append(song)
return cleanedSongList
def getWebnameFromEmail (self, email):
url = PANDORA_WEBNAME_URL.substitute(email = email)
Browser.go(url)
responseJson = json.loads(Browser.show())
return responseJson['result']['webname']