following changes in httpclient
[enigma2-plugins.git] / 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 class LastFMHandler:\r
11     def __init__(self):\r
12         pass\r
13     def onConnectSuccessful(self,reason):\r
14         pass\r
15     def onConnectFailed(self,reason):\r
16         pass\r
17     def onCommandFailed(self,reason):\r
18         pass\r
19     def onTrackSkiped(self,reason):\r
20         pass\r
21     def onTrackLoved(self,reason):\r
22         pass\r
23     def onTrackBaned(self,reason):\r
24         pass\r
25     def onGlobalTagsLoaded(self,tags):\r
26         pass\r
27     def onTopTracksLoaded(self,tracks):\r
28         pass\r
29     def onRecentTracksLoaded(self,tracks):\r
30         pass\r
31     def onRecentBannedTracksLoaded(self,tracks):\r
32         pass\r
33     def onRecentLovedTracksLoaded(self,tracks):\r
34         pass\r
35     def onNeighboursLoaded(self,user):\r
36         pass\r
37     def onFriendsLoaded(self,user):\r
38         pass\r
39     def onStationChanged(self,reason):\r
40         pass    \r
41     def onMetadataLoaded(self,metadata):\r
42         pass\r
43 \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
49     )\r
50     DUBLIN_CORE = ('http://purl.org/dc/elements/1.1/',)\r
51     \r
52     version = "1.0.1"\r
53     platform = "linux"\r
54     host = "ws.audioscrobbler.com"\r
55     port = 80\r
56     metadata = {}\r
57     info={}\r
58     cache_toptags= "/tmp/toptags"\r
59     \r
60     def __init__(self):\r
61         LastFMHandler.__init__(self)\r
62         self.state = False # if logged in\r
63                     \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
68     \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
75             else:\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
81                 self.state = True\r
82                 self.onConnectSuccessful("loggedin")\r
83         else:\r
84             self.onConnectFailed("login failed")\r
85         \r
86     def _parselines(self, str):\r
87         res = {}\r
88         vars = string.split(str, "\n")\r
89         for v in vars:\r
90             x = string.split(string.rstrip(v), "=", 1)\r
91             if len(x) == 2:\r
92                 res[x[0]] = x[1]\r
93             elif x != [""]:\r
94                 print "(urk?", x, ")"\r
95         return res\r
96     \r
97     def getPersonalURL(self,username,level=50):\r
98         return "lastfm://user/%s/recommended/32"%username\r
99     \r
100     def getNeighboursURL(self,username):\r
101         return "lastfm://user/%s/neighbours"%username\r
102 \r
103     def getLovedURL(self,username):\r
104         return "lastfm://user/%s/loved"%username\r
105     \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
109         else:\r
110             return "lastfm://artist/%s/similarartists"%artist.replace(" ","%20")\r
111 \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
115         else:\r
116             return "lastfm://artist/%s/fans"%artist.replace(" ","%20")\r
117     \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
121         else:\r
122             return "lastfm://group/%s"%artist.replace(" ","%20")\r
123     \r
124     def getMetadata(self):\r
125         if self.state is not True:\r
126             self.onCommandFailed("not logged in")\r
127         else:\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
131 \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
136 \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
140                     tmp["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
146         else:\r
147             self.onCommandFailed("Error while parsing Metadata")\r
148 \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
153         else:\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
157 \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
162         else:\r
163             self.onCommandFailed("Server returned FALSE")\r
164 \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
169         else:\r
170             self.onCommandFailed("Server returned FALSE")\r
171 \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
176         else:\r
177             self.onCommandFailed("Server returned FALSE")\r
178                         \r
179     def love(self):\r
180         return self.command("love",self.onTrackLovedCB)\r
181 \r
182     def ban(self):\r
183         return self.command("ban",self.onTrackBanedCB)\r
184 \r
185     def skip(self):\r
186         return self.command("skip",self.onTrackSkipedCB)\r
187     \r
188     def hexify(self,s):\r
189         result = ""\r
190         for c in s:\r
191             result = result + ("%02x" % ord(c))\r
192         return result\r
193     \r
194 \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
199         return []\r
200 \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
205 \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
208 \r
209     def getGlobalTags( self ,force_reload=False):\r
210         if self.state is not True:\r
211             self.onGlobalTagsFailed("not logged in")\r
212         else:\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
216 \r
217     def getGlobalTagsCB(self,result):\r
218         try:\r
219             rssDocument = xml.dom.minidom.parseString(result)\r
220             data =[]\r
221             for node in self.XMLgetElementsByTagName(rssDocument, 'tag'):\r
222                 nodex={}\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
227                 data.append(nodex)\r
228             self.onGlobalTagsLoaded(data)\r
229         except xml.parsers.expat.ExpatError,e:\r
230             self.onCommandFailed(e)\r
231 \r
232     def getTopTracks(self,username):\r
233         if self.state is not True:\r
234             self.onCommandFailed("not logged in")\r
235         else:\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
239            \r
240     def getTopTracksCB(self,result):\r
241         re,rdata = self._parseTracks(result)\r
242         if re:\r
243             self.onTopTracksLoaded(rdata)\r
244         else:\r
245             self.onCommandFailed(rdata)\r
246             \r
247     def getRecentTracks(self,username):\r
248         if self.state is not True:\r
249             self.onCommandFailed("not logged in")\r
250         else:\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
254            \r
255     def getRecentTracksCB(self,result):\r
256         re,rdata = self._parseTracks(result)\r
257         if re:\r
258             self.onRecentTracksLoaded(rdata)\r
259         else:\r
260             self.onCommandFailed(rdata)\r
261     \r
262     def getRecentLovedTracks(self,username):\r
263         if self.state is not True:\r
264             self.onCommandFailed("not logged in")\r
265         else:\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
269            \r
270     def getRecentLovedTracksCB(self,result):\r
271         re,rdata = self._parseTracks(result)\r
272         if re:\r
273             self.onRecentLovedTracksLoaded(rdata)\r
274         else:\r
275             self.onCommandFailed(rdata)\r
276 \r
277     def getRecentBannedTracks(self,username):\r
278         if self.state is not True:\r
279             self.onCommandFailed("not logged in")\r
280         else:\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
284            \r
285     def getRecentBannedTracksCB(self,result):\r
286         re,rdata = self._parseTracks(result)\r
287         if re:\r
288             self.onRecentBannedTracksLoaded(rdata)\r
289         else:\r
290             self.onCommandFailed(rdata)\r
291 \r
292     def _parseTracks(self,xmlrawdata):\r
293         #print xmlrawdata\r
294         try:\r
295             rssDocument = xml.dom.minidom.parseString(xmlrawdata)\r
296             data =[]\r
297             for node in self.XMLgetElementsByTagName(rssDocument, 'track'):\r
298                 nodex={}\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
305                 data.append(nodex)\r
306             return True,data\r
307         except xml.parsers.expat.ExpatError,e:\r
308             print e\r
309             return False,e\r
310 \r
311     def getNeighbours(self,username):\r
312         if self.state is not True:\r
313             self.onCommandFailed("not logged in")\r
314         else:\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
318            \r
319     def getNeighboursCB(self,result):\r
320         re,rdata = self._parseUser(result)\r
321         if re:\r
322             self.onNeighboursLoaded(rdata)\r
323         else:\r
324             self.onCommandFailed(rdata)\r
325 \r
326     def getFriends(self,username):\r
327         if self.state is not True:\r
328             self.onCommandFailed("not logged in")\r
329         else:\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
333            \r
334     def getFriendsCB(self,result):\r
335         re,rdata = self._parseUser(result)\r
336         if re:\r
337             self.onFriendsLoaded(rdata)\r
338         else:\r
339             self.onCommandFailed(rdata)\r
340 \r
341 \r
342     def _parseUser(self,xmlrawdata):\r
343         print xmlrawdata\r
344         try:\r
345             rssDocument = xml.dom.minidom.parseString(xmlrawdata)\r
346             data =[]\r
347             for node in self.XMLgetElementsByTagName(rssDocument, 'user'):\r
348                 nodex={}\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
353                 data.append(nodex)\r
354             return True,data\r
355         except xml.parsers.expat.ExpatError,e:\r
356             print e\r
357             return False,e\r
358 \r
359     def changeStation(self,url):\r
360         if self.state is not True:\r
361             self.onCommandFailed("not logged in")\r
362         else:\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
366            \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
371         else:\r
372             self.onCommandFailed("Server returned "+res["response"])\r
373 \r