4 import md5 # to encode password
\r
8 import xml.dom.minidom
\r
10 class LastFMHandler:
\r
13 def onConnectSuccessful(self,reason):
\r
15 def onConnectFailed(self,reason):
\r
17 def onCommandFailed(self,reason):
\r
19 def onTrackSkiped(self,reason):
\r
21 def onTrackLoved(self,reason):
\r
23 def onTrackBaned(self,reason):
\r
25 def onGlobalTagsLoaded(self,tags):
\r
27 def onTopTracksLoaded(self,tracks):
\r
29 def onRecentTracksLoaded(self,tracks):
\r
31 def onRecentBannedTracksLoaded(self,tracks):
\r
33 def onRecentLovedTracksLoaded(self,tracks):
\r
35 def onNeighboursLoaded(self,user):
\r
37 def onFriendsLoaded(self,user):
\r
39 def onStationChanged(self,reason):
\r
41 def onMetadataLoaded(self,metadata):
\r
44 class LastFM(LastFMHandler):
\r
45 DEFAULT_NAMESPACES = (
\r
46 None, # RSS 0.91, 0.92, 0.93, 0.94, 2.0
\r
47 'http://purl.org/rss/1.0/', # RSS 1.0
\r
48 'http://my.netscape.com/rdf/simple/0.9/' # RSS 0.90
\r
50 DUBLIN_CORE = ('http://purl.org/dc/elements/1.1/',)
\r
54 host = "ws.audioscrobbler.com"
\r
58 cache_toptags= "/tmp/toptags"
\r
61 LastFMHandler.__init__(self)
\r
62 self.state = False # if logged in
\r
64 def connect(self,username,password):
\r
65 httpclient.getPage(self.host,self.port
\r
66 ,"/radio/handshake.php?version=" + self.version + "&platform=" + self.platform + "&username=" + username + "&passwordmd5=" + self.hexify(md5.md5(password).digest())
\r
67 ,callback=self.connectCB,errorback=self.onConnectFailed)
\r
69 def connectCB(self,data):
\r
70 self.info = self._parselines(data)
\r
71 if self.info.has_key("session"):
\r
72 self.lastfmsession = self.info["session"]
\r
73 if self.lastfmsession.startswith("FAILED"):
\r
74 self.onConnectFailed(self.info["msg"])
\r
76 self.streamurl = self.info["stream_url"]
\r
77 self.baseurl = self.info["base_url"]
\r
78 self.basepath = self.info["base_path"]
\r
79 self.subscriber = self.info["subscriber"]
\r
80 self.framehack = self.info["base_path"]
\r
82 self.onConnectSuccessful("loggedin")
\r
84 self.onConnectFailed("login failed")
\r
86 def _parselines(self, str):
\r
88 vars = string.split(str, "\n")
\r
90 x = string.split(string.rstrip(v), "=", 1)
\r
94 print "(urk?", x, ")"
\r
97 def getPersonalURL(self,username,level=50):
\r
98 return "lastfm://user/%s/recommended/32"%username
\r
100 def getNeighboursURL(self,username):
\r
101 return "lastfm://user/%s/neighbours"%username
\r
103 def getLovedURL(self,username):
\r
104 return "lastfm://user/%s/loved"%username
\r
106 def getSimilarArtistsURL(self,artist=None):
\r
107 if artist is None and self.metadata.has_key('artist'):
\r
108 return "lastfm://artist/%s/similarartists"%self.metadata['artist'].replace(" ","%20")
\r
110 return "lastfm://artist/%s/similarartists"%artist.replace(" ","%20")
\r
112 def getArtistsLikedByFans(self,artist=None):
\r
113 if artist is None and self.metadata.has_key('artist'):
\r
114 return "lastfm://artist/%s/fans"%self.metadata['artist'].replace(" ","%20")
\r
116 return "lastfm://artist/%s/fans"%artist.replace(" ","%20")
\r
118 def getArtistGroup(self,artist=None):
\r
119 if artist is None and self.metadata.has_key('artist'):
\r
120 return "lastfm://group/%s"%self.metadata['artist'].replace(" ","%20")
\r
122 return "lastfm://group/%s"%artist.replace(" ","%20")
\r
124 def getMetadata(self):
\r
125 if self.state is not True:
\r
126 self.onCommandFailed("not logged in")
\r
128 httpclient.getPage(self.info["base_url"],80
\r
129 ,self.info["base_path"] + "/np.php?session=" + self.info["session"]
\r
130 ,callback=self.getMetadataCB,errorback=self.onCommandFailed)
\r
132 def getMetadataCB(self,result):
\r
133 tmp = self._parselines(result)
\r
134 if tmp.has_key('\xef\xbb\xbfstreaming'):
\r
135 tmp["streaming"] = tmp['\xef\xbb\xbfstreaming']
\r
137 if tmp.has_key("streaming"):
\r
138 if tmp["streaming"] == "false" or (tmp["streaming"] == "true" and tmp.has_key("artist") and tmp.has_key("track") and tmp.has_key("trackduration")):
\r
139 if not tmp.has_key("album"):
\r
141 tmp["album_url"] = ""
\r
142 self.metadata = tmp
\r
143 self.metadatatime = time.time()
\r
144 self.metadataage = str(int(time.time() - self.metadatatime))
\r
145 self.onMetadataLoaded(self.metadata)
\r
147 self.onCommandFailed("Error while parsing Metadata")
\r
149 def command(self, cmd,callback):
\r
150 # commands = skip, love, ban, rtp, nortp
\r
151 if self.state is not True:
\r
152 self.onCommandFailed("not logged in")
\r
154 httpclient.getPage(self.info["base_url"],80
\r
155 ,self.info["base_path"] + "/control.php?command=" + cmd + "&session=" + self.info["session"]
\r
156 ,callback=callback,errorback=self.onCommandFailed)
\r
158 def onTrackLovedCB(self,response):
\r
159 res = self._parselines(response)
\r
160 if res["response"] == "OK":
\r
161 self.onTrackLoved("Track loved")
\r
163 self.onCommandFailed("Server returned FALSE")
\r
165 def onTrackBanedCB(self,response):
\r
166 res = self._parselines(response)
\r
167 if res["response"] == "OK":
\r
168 self.onTrackLoved("Track baned")
\r
170 self.onCommandFailed("Server returned FALSE")
\r
172 def onTrackSkipedCB(self,response):
\r
173 res = self._parselines(response)
\r
174 if res["response"] == "OK":
\r
175 self.onTrackSkiped("Track skiped")
\r
177 self.onCommandFailed("Server returned FALSE")
\r
180 return self.command("love",self.onTrackLovedCB)
\r
183 return self.command("ban",self.onTrackBanedCB)
\r
186 return self.command("skip",self.onTrackSkipedCB)
\r
188 def hexify(self,s):
\r
191 result = result + ("%02x" % ord(c))
\r
195 def XMLgetElementsByTagName( self, node, tagName, possibleNamespaces=DEFAULT_NAMESPACES ):
\r
196 for namespace in possibleNamespaces:
\r
197 children = node.getElementsByTagNameNS(namespace, tagName)
\r
198 if len(children): return children
\r
201 def XMLnode_data( self, node, tagName, possibleNamespaces=DEFAULT_NAMESPACES):
\r
202 children = self.XMLgetElementsByTagName(node, tagName, possibleNamespaces)
\r
203 node = len(children) and children[0] or None
\r
204 return node and "".join([child.data.encode("utf-8") for child in node.childNodes]) or None
\r
206 def XMLget_txt( self, node, tagName, default_txt="" ):
\r
207 return self.XMLnode_data( node, tagName ) or self.XMLnode_data( node, tagName, self.DUBLIN_CORE ) or default_txt
\r
209 def getGlobalTags( self ,force_reload=False):
\r
210 if self.state is not True:
\r
211 self.onGlobalTagsFailed("not logged in")
\r
213 httpclient.getPage(self.info["base_url"],80
\r
214 ,"/1.0/tag/toptags.xml"
\r
215 ,callback=self.getGlobalTagsCB,errorback=self.onCommandFailed)
\r
217 def getGlobalTagsCB(self,result):
\r
219 rssDocument = xml.dom.minidom.parseString(result)
\r
221 for node in self.XMLgetElementsByTagName(rssDocument, 'tag'):
\r
223 nodex['_display'] = nodex['name'] = node.getAttribute("name").encode("utf-8")
\r
224 nodex['count'] = node.getAttribute("count").encode("utf-8")
\r
225 nodex['stationurl'] = "lastfm://globaltags/"+node.getAttribute("name").encode("utf-8").replace(" ","%20")
\r
226 nodex['url'] = node.getAttribute("url").encode("utf-8")
\r
228 self.onGlobalTagsLoaded(data)
\r
229 except xml.parsers.expat.ExpatError,e:
\r
230 self.onCommandFailed(e)
\r
232 def getTopTracks(self,username):
\r
233 if self.state is not True:
\r
234 self.onCommandFailed("not logged in")
\r
236 httpclient.getPage(self.info["base_url"],80
\r
237 ,"/1.0/user/%s/toptracks.xml"%username
\r
238 ,callback=self.getTopTracksCB,errorback=self.onCommandFailed)
\r
240 def getTopTracksCB(self,result):
\r
241 re,rdata = self._parseTracks(result)
\r
243 self.onTopTracksLoaded(rdata)
\r
245 self.onCommandFailed(rdata)
\r
247 def getRecentTracks(self,username):
\r
248 if self.state is not True:
\r
249 self.onCommandFailed("not logged in")
\r
251 httpclient.getPage(self.info["base_url"],80
\r
252 ,"/1.0/user/%s/recenttracks.xml"%username
\r
253 ,callback=self.getRecentTracksCB,errorback=self.onCommandFailed)
\r
255 def getRecentTracksCB(self,result):
\r
256 re,rdata = self._parseTracks(result)
\r
258 self.onRecentTracksLoaded(rdata)
\r
260 self.onCommandFailed(rdata)
\r
262 def getRecentLovedTracks(self,username):
\r
263 if self.state is not True:
\r
264 self.onCommandFailed("not logged in")
\r
266 httpclient.getPage(self.info["base_url"],80
\r
267 ,"/1.0/user/%s/recentlovedtracks.xml"%username
\r
268 ,callback=self.getRecentLovedTracksCB,errorback=self.onCommandFailed)
\r
270 def getRecentLovedTracksCB(self,result):
\r
271 re,rdata = self._parseTracks(result)
\r
273 self.onRecentLovedTracksLoaded(rdata)
\r
275 self.onCommandFailed(rdata)
\r
277 def getRecentBannedTracks(self,username):
\r
278 if self.state is not True:
\r
279 self.onCommandFailed("not logged in")
\r
281 httpclient.getPage(self.info["base_url"],80
\r
282 ,"/1.0/user/%s/recentbannedtracks.xml"%username
\r
283 ,callback=self.getRecentBannedTracksCB,errorback=self.onCommandFailed)
\r
285 def getRecentBannedTracksCB(self,result):
\r
286 re,rdata = self._parseTracks(result)
\r
288 self.onRecentBannedTracksLoaded(rdata)
\r
290 self.onCommandFailed(rdata)
\r
292 def _parseTracks(self,xmlrawdata):
\r
295 rssDocument = xml.dom.minidom.parseString(xmlrawdata)
\r
297 for node in self.XMLgetElementsByTagName(rssDocument, 'track'):
\r
299 nodex['name'] = self.XMLget_txt(node, "name", "N/A" )
\r
300 nodex['artist'] = self.XMLget_txt(node, "artist", "N/A" )
\r
301 nodex['playcount'] = self.XMLget_txt(node, "playcount", "N/A" )
\r
302 nodex['stationurl'] = "lastfm://artist/"+nodex['artist'].replace(" ","%20")+"/"+nodex['name'].replace(" ","%20")
\r
303 nodex['url'] = self.XMLget_txt(node, "url", "N/A" )
\r
304 nodex['_display'] = nodex['artist']+" - "+nodex['name']
\r
307 except xml.parsers.expat.ExpatError,e:
\r
311 def getNeighbours(self,username):
\r
312 if self.state is not True:
\r
313 self.onCommandFailed("not logged in")
\r
315 httpclient.getPage(self.info["base_url"],80
\r
316 ,"/1.0/user/%s/neighbours.xml"%username
\r
317 ,callback=self.getNeighboursCB,errorback=self.onCommandFailed)
\r
319 def getNeighboursCB(self,result):
\r
320 re,rdata = self._parseUser(result)
\r
322 self.onNeighboursLoaded(rdata)
\r
324 self.onCommandFailed(rdata)
\r
326 def getFriends(self,username):
\r
327 if self.state is not True:
\r
328 self.onCommandFailed("not logged in")
\r
330 httpclient.getPage(self.info["base_url"],80
\r
331 ,"/1.0/user/%s/friends.xml"%username
\r
332 ,callback=self.getFriendsCB,errorback=self.onCommandFailed)
\r
334 def getFriendsCB(self,result):
\r
335 re,rdata = self._parseUser(result)
\r
337 self.onFriendsLoaded(rdata)
\r
339 self.onCommandFailed(rdata)
\r
342 def _parseUser(self,xmlrawdata):
\r
345 rssDocument = xml.dom.minidom.parseString(xmlrawdata)
\r
347 for node in self.XMLgetElementsByTagName(rssDocument, 'user'):
\r
349 nodex['name'] = node.getAttribute("username").encode("utf-8")
\r
350 nodex['url'] = self.XMLget_txt(node, "url", "N/A" )
\r
351 nodex['stationurl'] = "lastfm://user/"+nodex['name']+"/personal"
\r
352 nodex['_display'] = nodex['name']
\r
355 except xml.parsers.expat.ExpatError,e:
\r
359 def changeStation(self,url):
\r
360 if self.state is not True:
\r
361 self.onCommandFailed("not logged in")
\r
363 httpclient.getPage(self.info["base_url"],80
\r
364 ,self.info["base_path"] + "/adjust.php?session=" + self.info["session"] + "&url=" + url
\r
365 ,callback=self.changeStationCB,errorback=self.onCommandFailed)
\r
367 def changeStationCB(self,result):
\r
368 res = self._parselines(result)
\r
369 if res["response"] == "OK":
\r
370 self.onStationChanged("Station changed")
\r
372 self.onCommandFailed("Server returned "+res["response"])
\r