4 import md5 # to encode password
14 DEFAULT_NAMESPACES = (
15 None, # RSS 0.91, 0.92, 0.93, 0.94, 2.0
16 'http://purl.org/rss/1.0/', # RSS 1.0
17 'http://my.netscape.com/rdf/simple/0.9/' # RSS 0.90
19 DUBLIN_CORE = ('http://purl.org/dc/elements/1.1/',)
23 host = "ws.audioscrobbler.com"
27 cache_toptags= "/tmp/toptags"
30 self.state = False # if logged in
32 def _loadURL(self,url):
33 s = httpclient.httpclient(self.host, self.port)
37 def connect(self,username,password):
38 self.info = self.parselines(self._loadURL("/radio/handshake.php?version=" + self.version + "&platform=" + self.platform + "&username=" + username + "&passwordmd5=" + self.hexify(md5.md5(password).digest())))
39 if self.info.has_key("session"):
40 self.session = self.info["session"]
41 if self.session.startswith("FAILED"):
42 return False,self.info["msg"]
44 self.streamurl = self.info["stream_url"]
45 self.baseurl = self.info["base_url"]
46 self.basepath = self.info["base_path"]
47 self.subscriber = self.info["subscriber"]
48 self.framehack = self.info["base_path"]
50 return True,"loggedin"
52 return False,"Could not login, wrong username or password?"
54 def parselines(self, str):
56 vars = string.split(str, "\n")
58 x = string.split(string.rstrip(v), "=", 1)
65 def getPersonalURL(self,username,level=50):
66 return "lastfm://user/%s/recommended/32"%username
68 def getNeighboursURL(self,username):
69 return "lastfm://user/%s/neighbours"%username
71 def getLovedURL(self,username):
72 return "lastfm://user/%s/loved"%username
74 def getSimilarArtistsURL(self,artist=None):
75 if artist is None and self.metadata.has_key('artist'):
76 return "lastfm://artist/%s/similarartists"%self.metadata['artist'].replace(" ","%20")
78 return "lastfm://artist/%s/similarartists"%artist.replace(" ","%20")
80 def getArtistsLikedByFans(self,artist=None):
81 if artist is None and self.metadata.has_key('artist'):
82 return "lastfm://artist/%s/fans"%self.metadata['artist'].replace(" ","%20")
84 return "lastfm://artist/%s/fans"%artist.replace(" ","%20")
86 def getArtistGroup(self,artist=None):
87 if artist is None and self.metadata.has_key('artist'):
88 return "lastfm://group/%s"%self.metadata['artist'].replace(" ","%20")
90 return "lastfm://group/%s"%artist.replace(" ","%20")
92 def getmetadata(self):
93 if self.state is not True:
96 s = httpclient.httpclient(self.info["base_url"], 80)
97 s.req(self.info["base_path"] + "/np.php?session=" + self.info["session"])
98 tmp = self.parselines(s.response)
99 if tmp.has_key('\xef\xbb\xbfstreaming'):
100 tmp["streaming"] = tmp['\xef\xbb\xbfstreaming']
102 if tmp.has_key("streaming"):
103 if tmp["streaming"] == "false" or (tmp["streaming"] == "true" and tmp.has_key("artist") and tmp.has_key("track") and tmp.has_key("trackduration")):
104 if not tmp.has_key("album"):
106 tmp["album_url"] = ""
108 self.metadatatime = time.time()
109 self.metadataage = str(int(time.time() - self.metadatatime))
111 #print self.metadatatime
112 #print "age",self.metadataage
116 def command(self, cmd):
117 if self.state is not True:
121 # commands = skip, love, ban, rtp, nortp
122 s = httpclient.httpclient(self.info["base_url"], 80)
123 s.req(self.info["base_path"] + "/control.php?command=" + cmd + "&session=" + self.info["session"])
124 res = self.parselines(s.response)
125 if res["response"] != "OK":
136 result = result + ("%02x" % ord(c))
140 return self.command("love")
143 return self.command("ban")
146 return self.command("skip")
149 def XMLgetElementsByTagName( self, node, tagName, possibleNamespaces=DEFAULT_NAMESPACES ):
150 for namespace in possibleNamespaces:
151 children = node.getElementsByTagNameNS(namespace, tagName)
152 if len(children): return children
155 def XMLnode_data( self, node, tagName, possibleNamespaces=DEFAULT_NAMESPACES):
156 children = self.XMLgetElementsByTagName(node, tagName, possibleNamespaces)
157 node = len(children) and children[0] or None
158 return node and "".join([child.data.encode("utf-8") for child in node.childNodes]) or None
160 def XMLget_txt( self, node, tagName, default_txt="" ):
161 return self.XMLnode_data( node, tagName ) or self.XMLnode_data( node, tagName, self.DUBLIN_CORE ) or default_txt
163 def getGlobalTags( self ,force_reload=False):
164 if self.state is not True:
169 if os.path.isfile(self.cache_toptags) is False or force_reload is True :
170 s = httpclient.httpclient(self.info["base_url"], 80)
171 s.req("/1.0/tag/toptags.xml")
173 fp = open(self.cache_toptags,"w")
177 fp = open(self.cache_toptags)
180 rssDocument = xml.dom.minidom.parseString(xmlsrc)
182 for node in self.XMLgetElementsByTagName(rssDocument, 'tag'):
184 nodex['_display'] = nodex['name'] = node.getAttribute("name").encode("utf-8")
185 nodex['count'] = node.getAttribute("count").encode("utf-8")
186 nodex['stationurl'] = "lastfm://globaltags/"+node.getAttribute("name").encode("utf-8").replace(" ","%20")
187 nodex['url'] = node.getAttribute("url").encode("utf-8")
190 except xml.parsers.expat.ExpatError,e:
194 def getTopTracks(self,username):
195 if self.state is not True:
198 s = httpclient.httpclient(self.info["base_url"], 80)
199 s.req("/1.0/user/%s/toptracks.xml"%username)
200 return self._parseTracks(s.response)
202 def getRecentTracks(self,username):
203 if self.state is not True:
206 s = httpclient.httpclient(self.info["base_url"], 80)
207 s.req("/1.0/user/%s/recenttracks.xml"%username)
208 return self._parseTracks(s.response)
209 def getRecentLovedTracks(self,username):
210 if self.state is not True:
213 s = httpclient.httpclient(self.info["base_url"], 80)
214 s.req("/1.0/user/%s/recentlovedtracks.xml"%username)
215 return self._parseTracks(s.response)
217 def getRecentBannedTracks(self,username):
218 if self.state is not True:
221 s = httpclient.httpclient(self.info["base_url"], 80)
222 s.req("/1.0/user/%s/recentbannedtracks.xml"%username)
223 return self._parseTracks(s.response)
225 def _parseTracks(self,xmlrawdata):
228 rssDocument = xml.dom.minidom.parseString(xmlrawdata)
230 for node in self.XMLgetElementsByTagName(rssDocument, 'track'):
232 nodex['name'] = self.XMLget_txt(node, "name", "N/A" )
233 nodex['artist'] = self.XMLget_txt(node, "artist", "N/A" )
234 nodex['playcount'] = self.XMLget_txt(node, "playcount", "N/A" )
235 nodex['stationurl'] = "lastfm://artist/"+nodex['artist'].replace(" ","%20")+"/"+nodex['name'].replace(" ","%20")
236 nodex['url'] = self.XMLget_txt(node, "url", "N/A" )
237 nodex['_display'] = nodex['artist']+" - "+nodex['name']
240 except xml.parsers.expat.ExpatError,e:
244 def getNeighbours(self,username):
245 if self.state is not True:
248 s = httpclient.httpclient(self.info["base_url"], 80)
249 s.req("/1.0/user/%s/neighbours.xml"%username)
250 return self._parseUser(s.response)
252 def getFriends(self,username):
253 if self.state is not True:
256 s = httpclient.httpclient(self.info["base_url"], 80)
257 s.req("/1.0/user/%s/friends.xml"%username)
258 return self._parseUser(s.response)
260 def _parseUser(self,xmlrawdata):
263 rssDocument = xml.dom.minidom.parseString(xmlrawdata)
265 for node in self.XMLgetElementsByTagName(rssDocument, 'user'):
267 nodex['name'] = node.getAttribute("username").encode("utf-8")
268 nodex['url'] = self.XMLget_txt(node, "url", "N/A" )
269 nodex['stationurl'] = "lastfm://user/"+nodex['name']+"/personal"
270 nodex['_display'] = nodex['name']
273 except xml.parsers.expat.ExpatError,e:
277 def changestation(self, url):
278 print "#"*20,self.state
279 if self.state is not True:
282 s = httpclient.httpclient(self.info["base_url"], 80)
283 s.req(self.info["base_path"] + "/adjust.php?session=" + self.info["session"] + "&url=" + url)
284 res = self.parselines(s.response)
285 if res["response"] != "OK":
286 print "station " + url + " returned:", res