initial version
[enigma2-plugins.git] / lastfm / src / LastFM.py
1 import httpclient
2
3 import os
4 import md5 # to encode password
5 import string
6 import time
7 import urllib
8 import Image
9 import xml.dom.minidom
10
11
12
13 class LastFM:
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
18     )
19     DUBLIN_CORE = ('http://purl.org/dc/elements/1.1/',)
20     
21     version = "1.0.1"
22     platform = "linux"
23     host = "ws.audioscrobbler.com"
24     port = 80
25     metadata = {}
26     info={}
27     cache_toptags= "/tmp/toptags"
28     
29     def __init__(self):
30         self.state = False # if logged in
31     
32     def _loadURL(self,url):
33         s = httpclient.httpclient(self.host, self.port)
34         s.req(url)
35         return s.response
36     
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"]
43             else:
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"]
49                 self.state = True
50                 return True,"loggedin"
51         else:
52             return False,"Could not login, wrong username or password?"
53         
54     def parselines(self, str):
55         res = {}
56         vars = string.split(str, "\n")
57         for v in vars:
58             x = string.split(string.rstrip(v), "=", 1)
59             if len(x) == 2:
60                 res[x[0]] = x[1]
61             elif x != [""]:
62                 print "(urk?", x, ")"
63         return res
64     
65     def getPersonalURL(self,username,level=50):
66         return "lastfm://user/%s/recommended/32"%username
67     
68     def getNeighboursURL(self,username):
69         return "lastfm://user/%s/neighbours"%username
70
71     def getLovedURL(self,username):
72         return "lastfm://user/%s/loved"%username
73     
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")
77         else:
78             return "lastfm://artist/%s/similarartists"%artist.replace(" ","%20")
79
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")
83         else:
84             return "lastfm://artist/%s/fans"%artist.replace(" ","%20")
85     
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")
89         else:
90             return "lastfm://group/%s"%artist.replace(" ","%20")
91     
92     def getmetadata(self):
93         if self.state is not True:
94             return False
95         else:
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']
101     
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"):
105                         tmp["album"] = ""
106                         tmp["album_url"] = ""
107                     self.metadata = tmp
108                     self.metadatatime = time.time()
109                     self.metadataage = str(int(time.time() - self.metadatatime))
110                     #print self.metadata
111                     #print self.metadatatime
112                     #print "age",self.metadataage
113                     return True
114             return False
115
116     def command(self, cmd):
117         if self.state is not True:
118             return False
119         else:
120             try:
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":
126                     return True
127                 else:
128                     return False
129             except Exception,e:
130                 print "Error",e
131                 return False
132     
133     def hexify(self,s):
134         result = ""
135         for c in s:
136             result = result + ("%02x" % ord(c))
137         return result
138     
139     def love(self):
140         return self.command("love")
141
142     def ban(self):
143         return self.command("ban")
144
145     def skip(self):
146         return self.command("skip")
147     
148
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
153         return []
154
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
159
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
162
163     def getGlobalTags( self ,force_reload=False):
164         if self.state is not True:
165             return []
166         else:
167             #TODO IOError
168             try: 
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")
172                     xmlsrc = s.response
173                     fp = open(self.cache_toptags,"w")
174                     fp.write(xmlsrc)
175                     fp.close()
176                 else:
177                     fp = open(self.cache_toptags)
178                     xmlsrc = fp.read()
179                     fp.close()
180                 rssDocument = xml.dom.minidom.parseString(xmlsrc)
181                 data =[]
182                 for node in self.XMLgetElementsByTagName(rssDocument, 'tag'):
183                     nodex={}
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")
188                     data.append(nodex)
189                 return data
190             except xml.parsers.expat.ExpatError,e:
191                 print e
192                 return []
193
194     def getTopTracks(self,username):
195         if self.state is not True:
196             return []
197         else:
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)
201
202     def getRecentTracks(self,username):
203         if self.state is not True:
204             return []
205         else:
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:
211             return []
212         else:
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)
216
217     def getRecentBannedTracks(self,username):
218         if self.state is not True:
219             return []
220         else:
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)
224
225     def _parseTracks(self,xmlrawdata):
226         #print xmlrawdata
227         try:
228             rssDocument = xml.dom.minidom.parseString(xmlrawdata)
229             data =[]
230             for node in self.XMLgetElementsByTagName(rssDocument, 'track'):
231                 nodex={}
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']
238                 data.append(nodex)
239             return data
240         except xml.parsers.expat.ExpatError,e:
241             print e
242             return []
243
244     def getNeighbours(self,username):
245         if self.state is not True:
246             return []
247         else:
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)
251
252     def getFriends(self,username):
253         if self.state is not True:
254             return []
255         else:
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)
259
260     def _parseUser(self,xmlrawdata):
261         print xmlrawdata
262         try:
263             rssDocument = xml.dom.minidom.parseString(xmlrawdata)
264             data =[]
265             for node in self.XMLgetElementsByTagName(rssDocument, 'user'):
266                 nodex={}
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']
271                 data.append(nodex)
272             return data
273         except xml.parsers.expat.ExpatError,e:
274             print e
275             return []
276
277     def changestation(self, url):
278         print "#"*20,self.state
279         if self.state is not True:
280             return False
281         else:
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
287             return res