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