Screensaver added
[vuplus_dvbapp-plugin] / lastfm / src / LastFM.py
1 import httpclient\r
2 \r
3 import os\r
4 import md5 # to encode password\r
5 import string\r
6 import time\r
7 import urllib\r
8 import xml.dom.minidom\r
9 \r
10 \r
11 \r
12 class LastFMEventRegister:\r
13     def __init__(self):\r
14         self.onMetadataChangedList = []\r
15     \r
16     def addOnMetadataChanged(self,callback):\r
17         self.onMetadataChangedList.append(callback)\r
18 \r
19     def removeOnMetadataChanged(self,callback):\r
20         self.onMetadataChangedList.remove(callback)\r
21     \r
22     def onMetadataChanged(self,metad):\r
23         for i in self.onMetadataChangedList:\r
24             i(metadata=metad)\r
25 \r
26 lastfm_event_register = LastFMEventRegister()\r
27             \r
28 class LastFMHandler:\r
29     def __init__(self):\r
30         pass\r
31     def onConnectSuccessful(self,reason):\r
32         pass\r
33     def onConnectFailed(self,reason):\r
34         pass\r
35     def onCommandFailed(self,reason):\r
36         pass\r
37     def onTrackSkiped(self,reason):\r
38         pass\r
39     def onTrackLoved(self,reason):\r
40         pass\r
41     def onTrackBaned(self,reason):\r
42         pass\r
43     def onGlobalTagsLoaded(self,tags):\r
44         pass\r
45     def onTopTracksLoaded(self,tracks):\r
46         pass\r
47     def onRecentTracksLoaded(self,tracks):\r
48         pass\r
49     def onRecentBannedTracksLoaded(self,tracks):\r
50         pass\r
51     def onRecentLovedTracksLoaded(self,tracks):\r
52         pass\r
53     def onNeighboursLoaded(self,user):\r
54         pass\r
55     def onFriendsLoaded(self,user):\r
56         pass\r
57     def onStationChanged(self,reason):\r
58         pass    \r
59     def onMetadataLoaded(self,metadata):\r
60         pass\r
61 \r
62 class LastFM(LastFMHandler):\r
63     DEFAULT_NAMESPACES = (\r
64         None, # RSS 0.91, 0.92, 0.93, 0.94, 2.0\r
65         'http://purl.org/rss/1.0/', # RSS 1.0\r
66         'http://my.netscape.com/rdf/simple/0.9/' # RSS 0.90\r
67     )\r
68     DUBLIN_CORE = ('http://purl.org/dc/elements/1.1/',)\r
69     \r
70     version = "1.0.1"\r
71     platform = "linux"\r
72     host = "ws.audioscrobbler.com"\r
73     port = 80\r
74     metadata = {}\r
75     info={}\r
76     cache_toptags= "/tmp/toptags"\r
77     \r
78     def __init__(self):\r
79         LastFMHandler.__init__(self)\r
80         self.state = False # if logged in\r
81                     \r
82     def connect(self,username,password):\r
83         httpclient.getPage(self.host,self.port\r
84                             ,"/radio/handshake.php?version=" + self.version + "&platform=" + self.platform + "&username=" + username + "&passwordmd5=" + self.hexify(md5.md5(password).digest())\r
85                             ,callback=self.connectCB,errorback=self.onConnectFailed)\r
86     \r
87     def connectCB(self,data):\r
88         self.info = self._parselines(data)\r
89         if self.info.has_key("session"):\r
90             self.lastfmsession = self.info["session"]\r
91             if self.lastfmsession.startswith("FAILED"):\r
92                 self.onConnectFailed(self.info["msg"])\r
93             else:\r
94                 self.streamurl = self.info["stream_url"]\r
95                 self.baseurl = self.info["base_url"]\r
96                 self.basepath = self.info["base_path"]\r
97                 self.subscriber = self.info["subscriber"]\r
98                 self.framehack = self.info["base_path"]\r
99                 self.state = True\r
100                 self.onConnectSuccessful("loggedin")\r
101         else:\r
102             self.onConnectFailed("login failed")\r
103         \r
104     def _parselines(self, str):\r
105         res = {}\r
106         vars = string.split(str, "\n")\r
107         for v in vars:\r
108             x = string.split(string.rstrip(v), "=", 1)\r
109             if len(x) == 2:\r
110                 res[x[0]] = x[1]\r
111             elif x != [""]:\r
112                 print "(urk?", x, ")"\r
113         return res\r
114     \r
115     def getPersonalURL(self,username,level=50):\r
116         return "lastfm://user/%s/recommended/32"%username\r
117     \r
118     def getNeighboursURL(self,username):\r
119         return "lastfm://user/%s/neighbours"%username\r
120 \r
121     def getLovedURL(self,username):\r
122         return "lastfm://user/%s/loved"%username\r
123     \r
124     def getSimilarArtistsURL(self,artist=None):\r
125         if artist is None and self.metadata.has_key('artist'):\r
126             return "lastfm://artist/%s/similarartists"%self.metadata['artist'].replace(" ","%20")\r
127         else:\r
128             return "lastfm://artist/%s/similarartists"%artist.replace(" ","%20")\r
129 \r
130     def getArtistsLikedByFans(self,artist=None):\r
131         if artist is None and self.metadata.has_key('artist'):\r
132             return "lastfm://artist/%s/fans"%self.metadata['artist'].replace(" ","%20")\r
133         else:\r
134             return "lastfm://artist/%s/fans"%artist.replace(" ","%20")\r
135     \r
136     def getArtistGroup(self,artist=None):\r
137         if artist is None and self.metadata.has_key('artist'):\r
138             return "lastfm://group/%s"%self.metadata['artist'].replace(" ","%20")\r
139         else:\r
140             return "lastfm://group/%s"%artist.replace(" ","%20")\r
141     \r
142     def getMetadata(self):\r
143         if self.state is not True:\r
144             self.onCommandFailed("not logged in")\r
145         else:\r
146             httpclient.getPage(self.info["base_url"],80\r
147                             ,self.info["base_path"] + "/np.php?session=" + self.info["session"]\r
148                             ,callback=self.getMetadataCB,errorback=self.onCommandFailed)\r
149 \r
150     def getMetadataCB(self,result):\r
151         tmp = self._parselines(result)\r
152         if tmp.has_key('\xef\xbb\xbfstreaming'):\r
153             tmp["streaming"] = tmp['\xef\xbb\xbfstreaming']\r
154 \r
155         if tmp.has_key("streaming"):\r
156             if tmp["streaming"] == "false" or (tmp["streaming"] == "true" and tmp.has_key("artist") and tmp.has_key("track") and tmp.has_key("trackduration")):\r
157                 if not tmp.has_key("album"):\r
158                     tmp["album"] = ""\r
159                     tmp["album_url"] = ""\r
160                 self.metadata = tmp\r
161                 self.metadatatime = time.time()\r
162                 self.metadataage = str(int(time.time() - self.metadatatime))\r
163                 self.onMetadataLoaded(self.metadata)\r
164                 lastfm_event_register.onMetadataChanged(self.metadata)\r
165         else:\r
166             self.onCommandFailed("Error while parsing Metadata")\r
167 \r
168     def command(self, cmd,callback):\r
169         # commands = skip, love, ban, rtp, nortp\r
170         if self.state is not True:\r
171             self.onCommandFailed("not logged in")\r
172         else:\r
173             httpclient.getPage(self.info["base_url"],80\r
174                             ,self.info["base_path"] + "/control.php?command=" + cmd + "&session=" + self.info["session"]\r
175                             ,callback=callback,errorback=self.onCommandFailed)\r
176 \r
177     def onTrackLovedCB(self,response):\r
178         res = self._parselines(response)\r
179         if res["response"] == "OK":\r
180             self.onTrackLoved("Track loved")\r
181         else:\r
182             self.onCommandFailed("Server returned FALSE")\r
183 \r
184     def onTrackBanedCB(self,response):\r
185         res = self._parselines(response)\r
186         if res["response"] == "OK":\r
187             self.onTrackBanned("Track baned")\r
188         else:\r
189             self.onCommandFailed("Server returned FALSE")\r
190 \r
191     def onTrackSkipedCB(self,response):\r
192         res = self._parselines(response)\r
193         if res["response"] == "OK":\r
194             self.onTrackSkiped("Track skiped")\r
195         else:\r
196             self.onCommandFailed("Server returned FALSE")\r
197                         \r
198     def love(self):\r
199         return self.command("love",self.onTrackLovedCB)\r
200 \r
201     def ban(self):\r
202         return self.command("ban",self.onTrackBanedCB)\r
203 \r
204     def skip(self):\r
205         return self.command("skip",self.onTrackSkipedCB)\r
206     \r
207     def hexify(self,s):\r
208         result = ""\r
209         for c in s:\r
210             result = result + ("%02x" % ord(c))\r
211         return result\r
212     \r
213 \r
214     def XMLgetElementsByTagName( self, node, tagName, possibleNamespaces=DEFAULT_NAMESPACES ):\r
215         for namespace in possibleNamespaces:\r
216             children = node.getElementsByTagNameNS(namespace, tagName)\r
217             if len(children): return children\r
218         return []\r
219 \r
220     def XMLnode_data( self, node, tagName, possibleNamespaces=DEFAULT_NAMESPACES):\r
221         children = self.XMLgetElementsByTagName(node, tagName, possibleNamespaces)\r
222         node = len(children) and children[0] or None\r
223         return node and "".join([child.data.encode("utf-8") for child in node.childNodes]) or None\r
224 \r
225     def XMLget_txt( self, node, tagName, default_txt="" ):\r
226         return self.XMLnode_data( node, tagName ) or self.XMLnode_data( node, tagName, self.DUBLIN_CORE ) or default_txt\r
227 \r
228     def getGlobalTags( self ,force_reload=False):\r
229         if self.state is not True:\r
230             self.onGlobalTagsFailed("not logged in")\r
231         else:\r
232             httpclient.getPage(self.info["base_url"],80\r
233                             ,"/1.0/tag/toptags.xml"\r
234                             ,callback=self.getGlobalTagsCB,errorback=self.onCommandFailed)\r
235 \r
236     def getGlobalTagsCB(self,result):\r
237         try:\r
238             rssDocument = xml.dom.minidom.parseString(result)\r
239             data =[]\r
240             for node in self.XMLgetElementsByTagName(rssDocument, 'tag'):\r
241                 nodex={}\r
242                 nodex['_display'] = nodex['name'] = node.getAttribute("name").encode("utf-8")\r
243                 nodex['count'] =  node.getAttribute("count").encode("utf-8")\r
244                 nodex['stationurl'] = "lastfm://globaltags/"+node.getAttribute("name").encode("utf-8").replace(" ","%20")\r
245                 nodex['url'] =  node.getAttribute("url").encode("utf-8")\r
246                 data.append(nodex)\r
247             self.onGlobalTagsLoaded(data)\r
248         except xml.parsers.expat.ExpatError,e:\r
249             self.onCommandFailed(e)\r
250 \r
251     def getTopTracks(self,username):\r
252         if self.state is not True:\r
253             self.onCommandFailed("not logged in")\r
254         else:\r
255             httpclient.getPage(self.info["base_url"],80\r
256                             ,"/1.0/user/%s/toptracks.xml"%username\r
257                             ,callback=self.getTopTracksCB,errorback=self.onCommandFailed)\r
258            \r
259     def getTopTracksCB(self,result):\r
260         re,rdata = self._parseTracks(result)\r
261         if re:\r
262             self.onTopTracksLoaded(rdata)\r
263         else:\r
264             self.onCommandFailed(rdata)\r
265             \r
266     def getRecentTracks(self,username):\r
267         if self.state is not True:\r
268             self.onCommandFailed("not logged in")\r
269         else:\r
270             httpclient.getPage(self.info["base_url"],80\r
271                             ,"/1.0/user/%s/recenttracks.xml"%username\r
272                             ,callback=self.getRecentTracksCB,errorback=self.onCommandFailed)\r
273            \r
274     def getRecentTracksCB(self,result):\r
275         re,rdata = self._parseTracks(result)\r
276         if re:\r
277             self.onRecentTracksLoaded(rdata)\r
278         else:\r
279             self.onCommandFailed(rdata)\r
280     \r
281     def getRecentLovedTracks(self,username):\r
282         if self.state is not True:\r
283             self.onCommandFailed("not logged in")\r
284         else:\r
285             httpclient.getPage(self.info["base_url"],80\r
286                             ,"/1.0/user/%s/recentlovedtracks.xml"%username\r
287                             ,callback=self.getRecentLovedTracksCB,errorback=self.onCommandFailed)\r
288            \r
289     def getRecentLovedTracksCB(self,result):\r
290         re,rdata = self._parseTracks(result)\r
291         if re:\r
292             self.onRecentLovedTracksLoaded(rdata)\r
293         else:\r
294             self.onCommandFailed(rdata)\r
295 \r
296     def getRecentBannedTracks(self,username):\r
297         if self.state is not True:\r
298             self.onCommandFailed("not logged in")\r
299         else:\r
300             httpclient.getPage(self.info["base_url"],80\r
301                             ,"/1.0/user/%s/recentbannedtracks.xml"%username\r
302                             ,callback=self.getRecentBannedTracksCB,errorback=self.onCommandFailed)\r
303            \r
304     def getRecentBannedTracksCB(self,result):\r
305         re,rdata = self._parseTracks(result)\r
306         if re:\r
307             self.onRecentBannedTracksLoaded(rdata)\r
308         else:\r
309             self.onCommandFailed(rdata)\r
310 \r
311     def _parseTracks(self,xmlrawdata):\r
312         #print xmlrawdata\r
313         try:\r
314             rssDocument = xml.dom.minidom.parseString(xmlrawdata)\r
315             data =[]\r
316             for node in self.XMLgetElementsByTagName(rssDocument, 'track'):\r
317                 nodex={}\r
318                 nodex['name'] = self.XMLget_txt(node, "name", "N/A" )\r
319                 nodex['artist'] =  self.XMLget_txt(node, "artist", "N/A" )\r
320                 nodex['playcount'] = self.XMLget_txt(node, "playcount", "N/A" )\r
321                 nodex['stationurl'] =  "lastfm://artist/"+nodex['artist'].replace(" ","%20")+"/"+nodex['name'].replace(" ","%20")\r
322                 nodex['url'] =  self.XMLget_txt(node, "url", "N/A" )\r
323                 nodex['_display'] = nodex['artist']+" - "+nodex['name']\r
324                 data.append(nodex)\r
325             return True,data\r
326         except xml.parsers.expat.ExpatError,e:\r
327             print e\r
328             return False,e\r
329 \r
330     def getNeighbours(self,username):\r
331         if self.state is not True:\r
332             self.onCommandFailed("not logged in")\r
333         else:\r
334             httpclient.getPage(self.info["base_url"],80\r
335                             ,"/1.0/user/%s/neighbours.xml"%username\r
336                             ,callback=self.getNeighboursCB,errorback=self.onCommandFailed)\r
337            \r
338     def getNeighboursCB(self,result):\r
339         re,rdata = self._parseUser(result)\r
340         if re:\r
341             self.onNeighboursLoaded(rdata)\r
342         else:\r
343             self.onCommandFailed(rdata)\r
344 \r
345     def getFriends(self,username):\r
346         if self.state is not True:\r
347             self.onCommandFailed("not logged in")\r
348         else:\r
349             httpclient.getPage(self.info["base_url"],80\r
350                             ,"/1.0/user/%s/friends.xml"%username\r
351                             ,callback=self.getFriendsCB,errorback=self.onCommandFailed)\r
352            \r
353     def getFriendsCB(self,result):\r
354         re,rdata = self._parseUser(result)\r
355         if re:\r
356             self.onFriendsLoaded(rdata)\r
357         else:\r
358             self.onCommandFailed(rdata)\r
359 \r
360 \r
361     def _parseUser(self,xmlrawdata):\r
362         print xmlrawdata\r
363         try:\r
364             rssDocument = xml.dom.minidom.parseString(xmlrawdata)\r
365             data =[]\r
366             for node in self.XMLgetElementsByTagName(rssDocument, 'user'):\r
367                 nodex={}\r
368                 nodex['name'] = node.getAttribute("username").encode("utf-8")\r
369                 nodex['url'] =  self.XMLget_txt(node, "url", "N/A" )\r
370                 nodex['stationurl'] =  "lastfm://user/"+nodex['name']+"/personal"\r
371                 nodex['_display'] = nodex['name']\r
372                 data.append(nodex)\r
373             return True,data\r
374         except xml.parsers.expat.ExpatError,e:\r
375             print e\r
376             return False,e\r
377 \r
378     def changeStation(self,url):\r
379         if self.state is not True:\r
380             self.onCommandFailed("not logged in")\r
381         else:\r
382             httpclient.getPage(self.info["base_url"],80\r
383                             ,self.info["base_path"] + "/adjust.php?session=" + self.info["session"] + "&url=" + url\r
384                             ,callback=self.changeStationCB,errorback=self.onCommandFailed)\r
385            \r
386     def changeStationCB(self,result):\r
387         res = self._parselines(result)\r
388         if res["response"] == "OK":\r
389             self.onStationChanged("Station changed")\r
390         else:\r
391             self.onCommandFailed("Server returned "+res["response"])\r
392 \r