~andrewsomething/exaile/karmic

« back to all changes in this revision

Viewing changes to plugins/contextinfo/pylast.py

  • Committer: Aren Olson
  • Date: 2009-09-12 00:36:59 UTC
  • Revision ID: reacocard@gmail.com-20090912003659-w373sg0n04uoa8op
remove useless files, add soem of the fixes from lp bug 420019

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
#
3
 
# pylast - A Python interface to Last.fm
4
 
# Copyright (C) 2008-2009  Amr Hassan
5
 
#
6
 
# This program is free software; you can redistribute it and/or modify
7
 
# it under the terms of the GNU General Public License as published by
8
 
# the Free Software Foundation; either version 2 of the License, or
9
 
# (at your option) any later version.
10
 
#
11
 
# This program is distributed in the hope that it will be useful,
12
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 
# GNU General Public License for more details.
15
 
#
16
 
# You should have received a copy of the GNU General Public License
17
 
# along with this program; if not, write to the Free Software
18
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19
 
# USA
20
 
#
21
 
# http://code.google.com/p/pylast/
22
 
 
23
 
__name__ = 'pylast'
24
 
__version__ = '0.3'
25
 
__revision__ = "$Revision$"
26
 
__doc__ = 'A Python interface to Last.fm'
27
 
__author__ = 'Amr Hassan'
28
 
__copyright__ = "Copyright (C) 2008-2009  Amr Hassan"
29
 
__license__ = "gpl"
30
 
__email__ = 'amr.hassan@gmail.com'
31
 
 
32
 
# Parse revision and add it to __version__
33
 
r = __revision__
34
 
__version__ = __version__ + "." + r[r.find(" ")+1:r.rfind("$")-1]
35
 
 
36
 
# Default values for Last.fm.
37
 
WS_SERVER = ('ws.audioscrobbler.com', '/2.0/')
38
 
SUBMISSION_SERVER = "http://post.audioscrobbler.com:80/"
39
 
 
40
 
__proxy = None
41
 
__proxy_enabled = False
42
 
__cache_shelf = None
43
 
__cache_filename = None
44
 
__last_call_time = 0
45
 
 
46
 
import hashlib
47
 
import httplib
48
 
import urllib
49
 
import threading
50
 
from xml.dom import minidom
51
 
import os
52
 
import time
53
 
from logging import info, warn, debug
54
 
import shelve
55
 
import tempfile
56
 
 
57
 
STATUS_INVALID_SERVICE = 2
58
 
STATUS_INVALID_METHOD = 3
59
 
STATUS_AUTH_FAILED = 4
60
 
STATUS_INVALID_FORMAT = 5
61
 
STATUS_INVALID_PARAMS = 6
62
 
STATUS_INVALID_RESOURCE = 7
63
 
STATUS_TOKEN_ERROR = 8
64
 
STATUS_INVALID_SK = 9
65
 
STATUS_INVALID_API_KEY = 10
66
 
STATUS_OFFLINE = 11
67
 
STATUS_SUBSCRIBERS_ONLY = 12
68
 
STATUS_INVALID_SIGNATURE = 13
69
 
STATUS_TOKEN_UNAUTHORIZED = 14
70
 
STATUS_TOKEN_EXPIRED = 15
71
 
 
72
 
EVENT_ATTENDING = '0'
73
 
EVENT_MAYBE_ATTENDING = '1'
74
 
EVENT_NOT_ATTENDING = '2'
75
 
 
76
 
PERIOD_OVERALL = 'overall'
77
 
PERIOD_3MONTHS = '3month'
78
 
PERIOD_6MONTHS = '6month'
79
 
PERIOD_12MONTHS = '12month'
80
 
 
81
 
IMAGE_SMALL = 0
82
 
IMAGE_MEDIUM = 1
83
 
IMAGE_LARGE = 2
84
 
IMAGE_EXTRA_LARGE = 3
85
 
 
86
 
DOMAIN_ENGLISH = 'www.last.fm'
87
 
DOMAIN_GERMAN = 'www.lastfm.de'
88
 
DOMAIN_SPANISH = 'www.lastfm.es'
89
 
DOMAIN_FRENCH = 'www.lastfm.fr'
90
 
DOMAIN_ITALIAN = 'www.lastfm.it'
91
 
DOMAIN_POLISH = 'www.lastfm.pl'
92
 
DOMAIN_PORTUGUESE = 'www.lastfm.com.br'
93
 
DOMAIN_SWEDISH = 'www.lastfm.se'
94
 
DOMAIN_TURKISH = 'www.lastfm.com.tr'
95
 
DOMAIN_RUSSIAN = 'www.lastfm.ru'
96
 
DOMAIN_JAPANESE = 'www.lastfm.jp'
97
 
DOMAIN_CHINESE = 'cn.last.fm'
98
 
 
99
 
USER_MALE = 'Male'
100
 
USER_FEMALE = 'Female'
101
 
 
102
 
SCROBBLE_SOURCE_USER = "P"
103
 
SCROBBLE_SOURCE_NON_PERSONALIZED_BROADCAST = "R"
104
 
SCROBBLE_SOURCE_PERSONALIZED_BROADCAST = "E"
105
 
SCROBBLE_SOURCE_LASTFM = "L"
106
 
SCROBBLE_SOURCE_UNKNOWN = "U"
107
 
 
108
 
SCROBBLE_MODE_PLAYED = "L"
109
 
SCROBBLE_MODE_BANNED = "B"
110
 
SCROBBLE_MODE_SKIPPED = "S"
111
 
 
112
 
class _ThreadedCall(threading.Thread):
113
 
        """Facilitates calling a function on another thread."""
114
 
        
115
 
        def __init__(self, sender, funct, funct_args, callback, callback_args):
116
 
                
117
 
                threading.Thread.__init__(self)
118
 
                
119
 
                self.funct = funct
120
 
                self.funct_args = funct_args
121
 
                self.callback = callback
122
 
                self.callback_args = callback_args
123
 
                
124
 
                self.sender = sender
125
 
        
126
 
        def run(self):
127
 
                
128
 
                output = []
129
 
                
130
 
                if self.funct:
131
 
                        if self.funct_args:
132
 
                                output = self.funct(*self.funct_args)
133
 
                        else:
134
 
                                output = self.funct()
135
 
                                
136
 
                if self.callback:
137
 
                        if self.callback_args:
138
 
                                self.callback(self.sender, output, *self.callback_args)
139
 
                        else:
140
 
                                self.callback(self.sender, output)
141
 
        
142
 
class _Request(object):
143
 
        """Representing an abstract web service operation."""
144
 
        
145
 
        global WS_SERVER
146
 
        (HOST_NAME, HOST_SUBDIR) = WS_SERVER
147
 
        
148
 
        def __init__(self, method_name, params, api_key, api_secret, session_key = None):
149
 
 
150
 
                self.params = params
151
 
                self.api_secret = api_secret
152
 
                
153
 
                self.params["api_key"] = api_key
154
 
                self.params["method"] = method_name
155
 
                
156
 
                if is_caching_enabled():
157
 
                        self.shelf = get_cache_shelf()
158
 
                
159
 
                if session_key:
160
 
                        self.params["sk"] = session_key
161
 
                        self.sign_it()
162
 
        
163
 
        def sign_it(self):
164
 
                """Sign this request."""
165
 
                
166
 
                if not "api_sig" in self.params.keys():
167
 
                        self.params['api_sig'] = self._get_signature()
168
 
        
169
 
        def _get_signature(self):
170
 
                """Returns a 32-character hexadecimal md5 hash of the signature string."""
171
 
                
172
 
                keys = self.params.keys()[:]
173
 
                
174
 
                keys.sort()
175
 
                
176
 
                string = ""
177
 
                
178
 
                for name in keys:
179
 
                        string += name
180
 
                        string += self.params[name]
181
 
                
182
 
                string += self.api_secret
183
 
                
184
 
                return md5(string)
185
 
        
186
 
        def _get_cache_key(self):
187
 
                """The cache key is a string of concatenated sorted names and values."""
188
 
                
189
 
                keys = self.params.keys()
190
 
                keys.sort()
191
 
                
192
 
                cache_key = str()
193
 
                
194
 
                for key in keys:
195
 
                        if key != "api_sig" and key != "api_key" and key != "sk":
196
 
                                cache_key += key + self.params[key].encode("utf-8")
197
 
                
198
 
                return hashlib.sha1(cache_key).hexdigest()
199
 
        
200
 
        def _get_cached_response(self):
201
 
                """Returns a file object of the cached response."""
202
 
                
203
 
                if not self._is_cached():
204
 
                        response = self._download_response()
205
 
                        self.shelf[self._get_cache_key()] = response
206
 
                
207
 
                return self.shelf[self._get_cache_key()]
208
 
        
209
 
        def _is_cached(self):
210
 
                """Returns True if the request is already in cache."""
211
 
                
212
 
                return self.shelf.has_key(self._get_cache_key())
213
 
                
214
 
        def _download_response(self):
215
 
                """Returns a response body string from the server."""
216
 
                
217
 
                # Delay the call if necessary
218
 
                _delay_call()
219
 
                
220
 
                data = []
221
 
                for name in self.params.keys():
222
 
                        data.append('='.join((name, urllib.quote_plus(self.params[name].encode("utf-8")))))
223
 
                data = '&'.join(data)
224
 
                
225
 
                headers = {
226
 
                        "Content-type": "application/x-www-form-urlencoded",
227
 
                        'Accept-Charset': 'utf-8',
228
 
                        'User-Agent': __name__ + '/' + __version__
229
 
                        }               
230
 
 
231
 
                if is_proxy_enabled():
232
 
                        conn = httplib.HTTPConnection(host = _get_proxy()[0], port = _get_proxy()[1])
233
 
                        conn.request(method='POST', url="http://" + self.HOST_NAME + self.HOST_SUBDIR, 
234
 
                                body=data, headers=headers)
235
 
                else:
236
 
                        conn = httplib.HTTPConnection(host=self.HOST_NAME)
237
 
                        conn.request(method='POST', url=self.HOST_SUBDIR, body=data, headers=headers)
238
 
                
239
 
                response = conn.getresponse().read()
240
 
                self._check_response_for_errors(response)
241
 
                return response
242
 
                
243
 
        def execute(self, cacheable = False):
244
 
                """Returns the XML DOM response of the POST Request from the server"""
245
 
                
246
 
                if is_caching_enabled() and cacheable:
247
 
                        response = self._get_cached_response()
248
 
                else:
249
 
                        response = self._download_response()
250
 
                
251
 
                return minidom.parseString(response)
252
 
        
253
 
        def _check_response_for_errors(self, response):
254
 
                """Checks the response for errors and raises one if any exists."""
255
 
                
256
 
                doc = minidom.parseString(response)
257
 
                e = doc.getElementsByTagName('lfm')[0]
258
 
                
259
 
                if e.getAttribute('status') != "ok":
260
 
                        e = doc.getElementsByTagName('error')[0]
261
 
                        status = e.getAttribute('code')
262
 
                        details = e.firstChild.data.strip()
263
 
                        raise ServiceException(status, details)
264
 
 
265
 
class SessionKeyGenerator(object):
266
 
        """Methods of generating a session key:
267
 
        1) Web Authentication:
268
 
                a. sg = SessionKeyGenerator(API_KEY, API_SECRET)
269
 
                b. url = sg.get_web_auth_url()
270
 
                c. Ask the user to open the url and authorize you, and wait for it.
271
 
                d. session_key = sg.get_web_auth_session_key(url)
272
 
        2) Username and Password Authentication:
273
 
                a. username = raw_input("Please enter your username: ")
274
 
                b. md5_password = pylast.md5(raw_input("Please enter your password: ")
275
 
                c. session_key = SessionKeyGenerator(API_KEY, API_SECRET).get_session_key(username, md5_password)
276
 
        
277
 
        A session key's lifetime is infinie, unless the user provokes the rights of the given API Key.
278
 
        """
279
 
        
280
 
        def __init__(self, api_key, api_secret):                
281
 
                self.api_key = api_key
282
 
                self.api_secret = api_secret
283
 
                self.web_auth_tokens = {}
284
 
        
285
 
        def _get_web_auth_token(self):
286
 
                """Retrieves a token from Last.fm for web authentication.
287
 
                The token then has to be authorized from getAuthURL before creating session.
288
 
                """
289
 
                
290
 
                request = _Request('auth.getToken', dict(), self.api_key, self.api_secret)
291
 
                
292
 
                # default action is that a request is signed only when
293
 
                # a session key is provided.
294
 
                request.sign_it()
295
 
                
296
 
                doc = request.execute()
297
 
                
298
 
                e = doc.getElementsByTagName('token')[0]
299
 
                return e.firstChild.data
300
 
        
301
 
        def get_web_auth_url(self):
302
 
                """The user must open this page, and you first, then call get_web_auth_session_key(url) after that."""
303
 
                
304
 
                token = self._get_web_auth_token()
305
 
                
306
 
                url = 'http://www.last.fm/api/auth/?api_key=%(api)s&token=%(token)s' % \
307
 
                        {'api': self.api_key, 'token': token}
308
 
                
309
 
                self.web_auth_tokens[url] = token
310
 
                
311
 
                return url
312
 
 
313
 
        def get_web_auth_session_key(self, url):
314
 
                """Retrieves the session key of a web authorization process by its url."""
315
 
                
316
 
                if url in self.web_auth_tokens.keys():
317
 
                        token = self.web_auth_tokens[url]
318
 
                else:
319
 
                        token = ""      #that's gonna raise a ServiceException of an unauthorized token when the request is executed.
320
 
                
321
 
                request = _Request('auth.getSession', {'token': token}, self.api_key, self.api_secret)
322
 
                
323
 
                # default action is that a request is signed only when
324
 
                # a session key is provided.
325
 
                request.sign_it()
326
 
                
327
 
                doc = request.execute()
328
 
                
329
 
                return doc.getElementsByTagName('key')[0].firstChild.data
330
 
        
331
 
        def get_session_key(self, username, md5_password):
332
 
                """Retrieve a session key with a username and a md5 hash of the user's password."""
333
 
                
334
 
                params = {"username": username, "authToken": md5(username + md5_password)}
335
 
                request = _Request("auth.getMobileSession", params, self.api_key, self.api_secret)
336
 
                
337
 
                # default action is that a request is signed only when
338
 
                # a session key is provided.
339
 
                request.sign_it()
340
 
                
341
 
                doc = request.execute()
342
 
                
343
 
                return _extract(doc, "key")
344
 
 
345
 
class _BaseObject(object):
346
 
        """An abstract webservices object."""
347
 
                
348
 
        def __init__(self, api_key, api_secret, session_key):
349
 
                                
350
 
                self.api_key = api_key
351
 
                self.api_secret = api_secret
352
 
                self.session_key = session_key
353
 
                
354
 
                self.auth_data = (self.api_key, self.api_secret, self.session_key)
355
 
        
356
 
        def _request(self, method_name, cacheable = False, params = None):
357
 
                if not params:
358
 
                        params = self._get_params()
359
 
                        
360
 
                return _Request(method_name, params, *self.auth_data).execute(cacheable)
361
 
        
362
 
        def _get_params():
363
 
                """Returns the most common set of parameters between all objects."""
364
 
                
365
 
                return dict()
366
 
 
367
 
class _Taggable(object):
368
 
        """Common functions for classes with tags."""
369
 
        
370
 
        def __init__(self, ws_prefix):
371
 
                self.ws_prefix = ws_prefix
372
 
        
373
 
        def add_tags(self, *tags):
374
 
                """Adds one or several tags.
375
 
                * *tags: Any number of tag names or Tag objects. 
376
 
                """
377
 
                
378
 
                for tag in tags:
379
 
                        self._add_tag(tag)      
380
 
        
381
 
        def _add_tag(self, tag):
382
 
                """Adds one or several tags.
383
 
                * tag: one tag name or a Tag object.
384
 
                """
385
 
                
386
 
                if isinstance(tag, Tag):
387
 
                        tag = tag.get_name()
388
 
                
389
 
                params = self._get_params()
390
 
                params['tags'] = unicode(tag)
391
 
                
392
 
                self._request(self.ws_prefix + '.addTags', False, params)
393
 
                info("Tagged " + repr(self) + " as (" + repr(tag) + ")")
394
 
        
395
 
        def _remove_tag(self, single_tag):
396
 
                """Remove a user's tag from this object."""
397
 
                
398
 
                if isinstance(single_tag, Tag):
399
 
                        single_tag = single_tag.get_name()
400
 
                
401
 
                params = self._get_params()
402
 
                params['tag'] = unicode(single_tag)
403
 
                
404
 
                self._request(self.ws_prefix + '.removeTag', False, params)
405
 
                info("Removed tag (" + repr(tag) + ") from " + repr(self))
406
 
 
407
 
        def get_tags(self):
408
 
                """Returns a list of the tags set by the user to this object."""
409
 
                
410
 
                # Uncacheable because it can be dynamically changed by the user.
411
 
                params = self._get_params()
412
 
                doc = _Request(self.ws_prefix + '.getTags', params, *self.auth_data).execute(cacheable = False)
413
 
                
414
 
                tag_names = _extract_all(doc, 'name')
415
 
                tags = []
416
 
                for tag in tag_names:
417
 
                        tags.append(Tag(tag, *self.auth_data))
418
 
                
419
 
                return tags
420
 
        
421
 
        def remove_tags(self, *tags):
422
 
                """Removes one or several tags from this object.
423
 
                * *tags: Any number of tag names or Tag objects. 
424
 
                """
425
 
                
426
 
                for tag in tags:
427
 
                        self._remove_tag(tag)
428
 
        
429
 
        def clear_tags(self):
430
 
                """Clears all the user-set tags. """
431
 
                
432
 
                self.remove_tags(*(self.get_tags()))
433
 
        
434
 
        def set_tags(self, *tags):
435
 
                """Sets this object's tags to only those tags.
436
 
                * *tags: any number of tag names.
437
 
                """
438
 
                
439
 
                c_old_tags = []
440
 
                old_tags = []
441
 
                c_new_tags = []
442
 
                new_tags = []
443
 
                
444
 
                to_remove = []
445
 
                to_add = []
446
 
                
447
 
                tags_on_server = self.get_tags()
448
 
                
449
 
                for tag in tags_on_server:
450
 
                        c_old_tags.append(tag.get_name().lower())
451
 
                        old_tags.append(tag.get_name())
452
 
                
453
 
                for tag in tags:
454
 
                        c_new_tags.append(tag.lower())
455
 
                        new_tags.append(tag)
456
 
                
457
 
                for i in range(0, len(old_tags)):
458
 
                        if not c_old_tags[i] in c_new_tags:
459
 
                                to_remove.append(old_tags[i])
460
 
                
461
 
                for i in range(0, len(new_tags)):
462
 
                        if not c_new_tags[i] in c_old_tags:
463
 
                                to_add.append(new_tags[i])
464
 
                
465
 
                self.remove_tags(*to_remove)
466
 
                self.add_tags(*to_add)
467
 
                
468
 
        def get_top_tags(self, limit = None):
469
 
                """Returns a list of the most frequently used Tags on this object."""
470
 
                
471
 
                doc = self._request(self.ws_prefix + '.getTopTags', True)
472
 
                
473
 
                elements = doc.getElementsByTagName('tag')
474
 
                list = []
475
 
                
476
 
                for element in elements:
477
 
                        if limit and len(list) >= limit:
478
 
                                break
479
 
                        tag_name = _extract(element, 'name')
480
 
                        tagcount = _extract(element, 'count')
481
 
                        
482
 
                        list.append(TopItem(Tag(tag_name, *self.auth_data), tagcount))
483
 
                
484
 
                return list
485
 
                
486
 
class ServiceException(Exception):
487
 
        """Exception related to the Last.fm web service"""
488
 
        
489
 
        def __init__(self, lastfm_status, details):
490
 
                self._lastfm_status = lastfm_status
491
 
                self._details = details
492
 
        
493
 
        def __str__(self):
494
 
                return self._details
495
 
        
496
 
        def get_id(self):
497
 
                """Returns the exception ID, from one of the following:
498
 
                        STATUS_INVALID_SERVICE = 2
499
 
                        STATUS_INVALID_METHOD = 3
500
 
                        STATUS_AUTH_FAILED = 4
501
 
                        STATUS_INVALID_FORMAT = 5
502
 
                        STATUS_INVALID_PARAMS = 6
503
 
                        STATUS_INVALID_RESOURCE = 7
504
 
                        STATUS_TOKEN_ERROR = 8
505
 
                        STATUS_INVALID_SK = 9
506
 
                        STATUS_INVALID_API_KEY = 10
507
 
                        STATUS_OFFLINE = 11
508
 
                        STATUS_SUBSCRIBERS_ONLY = 12
509
 
                        STATUS_TOKEN_UNAUTHORIZED = 14
510
 
                        STATUS_TOKEN_EXPIRED = 15
511
 
                """
512
 
                
513
 
                return self._lastfm_status
514
 
 
515
 
class TopItem (object):
516
 
        """A top item in a list that has a weight. Returned from functions like get_top_tracks() and get_top_artists()."""
517
 
        
518
 
        def __init__(self, item, weight):
519
 
                object.__init__(self)
520
 
                
521
 
                self.item = item
522
 
                self.weight = _number(weight)
523
 
        
524
 
        def __repr__(self):
525
 
                return "Item: " + self.get_item().__repr__() + ", Weight: " + str(self.get_weight())
526
 
        
527
 
        def get_item(self):
528
 
                """Returns the item."""
529
 
                
530
 
                return self.item
531
 
        
532
 
        def get_weight(self):
533
 
                """Returns the weight of the itme in the list."""
534
 
                
535
 
                return self.weight
536
 
 
537
 
 
538
 
class LibraryItem (object):
539
 
        """An item in a User's Library. It could be an artist, an album or a track."""
540
 
        
541
 
        def __init__(self, item, playcount, tagcount):
542
 
                object.__init__(self)
543
 
                
544
 
                self.item = item
545
 
                self.playcount = _number(playcount)
546
 
                self.tagcount = _number(tagcount)
547
 
        
548
 
        def __repr__(self):
549
 
                return "Item: " + self.get_item().__repr__() + ", Playcount: " + str(self.get_playcount()) + ", Tagcount: " + str(self.get_tagcount())
550
 
        
551
 
        def get_item(self):
552
 
                """Returns the itme."""
553
 
                
554
 
                return self.item
555
 
        
556
 
        def get_playcount(self):
557
 
                """Returns the item's playcount in the Library."""
558
 
                
559
 
                return self.playcount
560
 
                
561
 
        def get_tagcount(self):
562
 
                """Returns the item's tagcount in the Library."""
563
 
                
564
 
                return self.tagcount
565
 
 
566
 
class PlayedTrack (object):
567
 
        """A track with a playback date."""
568
 
        
569
 
        def __init__(self, track, date, timestamp):
570
 
                object.__init__(self)
571
 
                
572
 
                self.track = track
573
 
                self.date = date
574
 
                self.timestamp = timestamp
575
 
        
576
 
        def __repr__(self):
577
 
                return repr(self.track) + " played at " + self.date
578
 
        
579
 
        def get_track(self):
580
 
                """Return the track."""
581
 
                
582
 
                return self.track
583
 
        
584
 
        def get_item(self):
585
 
                """Returns the played track. An alias to get_track()."""
586
 
                
587
 
                return self.get_track();
588
 
        
589
 
        def get_date(self):
590
 
                """Returns the playback date."""
591
 
                
592
 
                return self.date
593
 
        
594
 
        def get_timestamp(self):
595
 
                """Returns the unix timestamp of the playback date."""
596
 
                
597
 
                return self.timestamp
598
 
        
599
 
 
600
 
class Album(_BaseObject, _Taggable):
601
 
        """A Last.fm album."""
602
 
        
603
 
        def __init__(self, artist, title, api_key, api_secret, session_key):
604
 
                """
605
 
                Create an album instance.
606
 
                # Parameters:
607
 
                        * artist str|Artist: An artist name or an Artist object.
608
 
                        * title str: The album title.
609
 
                """
610
 
                
611
 
                _BaseObject.__init__(self, api_key, api_secret, session_key)
612
 
                _Taggable.__init__(self, 'album')
613
 
                
614
 
                if isinstance(artist, Artist):
615
 
                        self.artist = artist
616
 
                else:
617
 
                        self.artist = Artist(artist, *self.auth_data)
618
 
                
619
 
                self.title = title
620
 
 
621
 
        def __repr__(self):
622
 
                return self.get_artist().get_name() + ' - ' + self.get_title()
623
 
        
624
 
        def __eq__(self, other):
625
 
                return (self.get_title().lower() == other.get_title().lower()) and (self.get_artist().get_name().lower() == other.get_artist().get_name().lower())
626
 
        
627
 
        def __ne__(self, other):
628
 
                return (self.get_title().lower() != other.get_title().lower()) or (self.get_artist().get_name().lower() != other.get_artist().get_name().lower())
629
 
        
630
 
        def _get_params(self):
631
 
                return {'artist': self.get_artist().get_name(), 'album': self.get_title(), }
632
 
        
633
 
        def get_artist(self):
634
 
                """Returns the associated Artist object."""
635
 
                
636
 
                return self.artist
637
 
        
638
 
        def get_title(self):
639
 
                """Returns the album title."""
640
 
                
641
 
                return self.title
642
 
        
643
 
        def get_name(self):
644
 
                """Returns the album title (alias to Album.get_title)."""
645
 
                
646
 
                return self.get_title()
647
 
        
648
 
        def get_release_date(self):
649
 
                """Retruns the release date of the album."""
650
 
                
651
 
                return _extract(self._request("album.getInfo", cacheable = True), "releasedate")
652
 
        
653
 
        def get_image_url(self, size = IMAGE_EXTRA_LARGE):
654
 
                """Returns the associated image URL.
655
 
                # Parameters:
656
 
                * size int: The image size. Possible values:
657
 
                        o IMAGE_EXTRA_LARGE
658
 
                        o IMAGE_LARGE
659
 
                        o IMAGE_MEDIUM
660
 
                        o IMAGE_SMALL
661
 
                """
662
 
                
663
 
                return _extract_all(self._request("album.getInfo", cacheable = True), 'image')[size]
664
 
        
665
 
        def get_id(self):
666
 
                """Returns the Last.fm ID."""
667
 
                
668
 
                return _extract(self._request("album.getInfo", cacheable = True), "id")
669
 
        
670
 
        def get_playcount(self):
671
 
                """Returns the number of plays on Last.fm."""
672
 
                
673
 
                return _number(_extract(self._request("album.getInfo", cacheable = True), "playcount"))
674
 
        
675
 
        def get_listener_count(self):
676
 
                """Returns the number of liteners on Last.fm."""
677
 
                
678
 
                return _number(_extract(self._request("album.getInfo", cacheable = True), "listeners"))
679
 
        
680
 
        def get_top_tags(self, limit = None):
681
 
                """Returns a list of the most-applied tags to this album."""
682
 
                
683
 
                # BROKEN: Web service is currently broken.
684
 
                
685
 
                return None
686
 
 
687
 
        def get_tracks(self):
688
 
                """Returns the list of Tracks on this album."""
689
 
                
690
 
                uri = 'lastfm://playlist/album/%s' %self.get_id()
691
 
                
692
 
                return XSPF(uri, *self.auth_data).get_tracks()
693
 
        
694
 
        def get_mbid(self):
695
 
                """Returns the MusicBrainz id of the album."""
696
 
                
697
 
                return _extract(self._request("album.getInfo", cacheable = True), "mbid")
698
 
                
699
 
        def get_url(self, domain_name = DOMAIN_ENGLISH):
700
 
                """Returns the url of the album page on Last.fm. 
701
 
                # Parameters:
702
 
                * domain_name str: Last.fm's language domain. Possible values:
703
 
                        o DOMAIN_ENGLISH
704
 
                        o DOMAIN_GERMAN
705
 
                        o DOMAIN_SPANISH
706
 
                        o DOMAIN_FRENCH
707
 
                        o DOMAIN_ITALIAN
708
 
                        o DOMAIN_POLISH
709
 
                        o DOMAIN_PORTUGUESE
710
 
                        o DOMAIN_SWEDISH
711
 
                        o DOMAIN_TURKISH
712
 
                        o DOMAIN_RUSSIAN
713
 
                        o DOMAIN_JAPANESE
714
 
                        o DOMAIN_CHINESE
715
 
                """
716
 
                
717
 
                url = 'http://%(domain)s/music/%(artist)s/%(album)s'
718
 
                
719
 
                artist = _get_url_safe(self.get_artist().get_name())
720
 
                album = _get_url_safe(self.get_title())
721
 
                
722
 
                return url %{'domain': domain_name, 'artist': artist, 'album': album}
723
 
 
724
 
class Artist(_BaseObject, _Taggable):
725
 
        """A Last.fm artist."""
726
 
        
727
 
        def __init__(self, name, api_key, api_secret, session_key):
728
 
                """Create an artist object.
729
 
                # Parameters:
730
 
                        * name str: The artist's name.
731
 
                """
732
 
                
733
 
                _BaseObject.__init__(self, api_key, api_secret, session_key)
734
 
                _Taggable.__init__(self, 'artist')
735
 
                
736
 
                self.name = name
737
 
 
738
 
        def __repr__(self):
739
 
                return unicode(self.get_name())
740
 
        
741
 
        def __eq__(self, other):
742
 
                return self.get_name().lower() == other.get_name().lower()
743
 
        
744
 
        def __ne__(self, other):
745
 
                return self.get_name().lower() != other.get_name().lower()
746
 
        
747
 
        def _get_params(self):
748
 
                return {'artist': self.get_name()}
749
 
        
750
 
        def get_name(self):
751
 
                """Returns the name of the artist."""
752
 
                
753
 
                return self.name
754
 
        
755
 
        def get_image_url(self, size = IMAGE_LARGE):
756
 
                """Returns the associated image URL. 
757
 
                # Parameters:
758
 
                        * size int: The image size. Possible values:
759
 
                          o IMAGE_LARGE
760
 
                          o IMAGE_MEDIUM
761
 
                          o IMAGE_SMALL
762
 
                """
763
 
                
764
 
                return _extract_all(self._request("artist.getInfo", True), "image")[size]
765
 
        
766
 
        def get_square_image(self):
767
 
                return _extract_all(self._request("artist.getImages", True), "size")[2]
768
 
        
769
 
        def get_playcount(self):
770
 
                """Returns the number of plays on Last.fm."""
771
 
                
772
 
                return _number(_extract(self._request("artist.getInfo", True), "playcount"))
773
 
 
774
 
        def get_mbid(self):
775
 
                """Returns the MusicBrainz ID of this artist."""
776
 
                
777
 
                doc = self._request("artist.getInfo", True)
778
 
                
779
 
                return _extract(doc, "mbid")
780
 
                
781
 
        def get_listener_count(self):
782
 
                """Returns the number of liteners on Last.fm."""
783
 
                
784
 
                return _number(_extract(self._request("artist.getInfo", True), "listeners"))
785
 
        
786
 
        def is_streamable(self):
787
 
                """Returns True if the artist is streamable."""
788
 
                
789
 
                return bool(_number(_extract(self._request("artist.getInfo", True), "streamable")))
790
 
        
791
 
        def get_bio_published_date(self):
792
 
                """Returns the date on which the artist's biography was published."""
793
 
                
794
 
                return _extract(self._request("artist.getInfo", True), "published")
795
 
        
796
 
        def get_bio_summary(self):
797
 
                """Returns the summary of the artist's biography."""
798
 
                
799
 
                return _extract(self._request("artist.getInfo", True), "summary")
800
 
        
801
 
        def get_bio_content(self):
802
 
                """Returns the content of the artist's biography."""
803
 
                
804
 
                return _extract(self._request("artist.getInfo", True), "content")
805
 
        
806
 
        def get_upcoming_events(self):
807
 
                """Returns a list of the upcoming Events for this artist."""
808
 
                
809
 
                doc = self._request('artist.getEvents', True)
810
 
                
811
 
                ids = _extract_all(doc, 'id')
812
 
                
813
 
                events = []
814
 
                for id in ids:
815
 
                        events.append(Event(id, *self.auth_data))
816
 
                
817
 
                return events
818
 
        
819
 
        def get_similar(self, limit = None):
820
 
                """Returns the similar artists on Last.fm."""
821
 
                
822
 
                params = self._get_params()
823
 
                if limit:
824
 
                        params['limit'] = unicode(limit)
825
 
                
826
 
                doc = self._request('artist.getSimilar', True, params)
827
 
                
828
 
                names = _extract_all(doc, 'name')
829
 
                
830
 
                artists = []
831
 
                for name in names:
832
 
                        artists.append(Artist(name, *self.auth_data))
833
 
                
834
 
                return artists
835
 
        
836
 
#       def get_top_tags(self):
837
 
#               """Returns the top tags."""
838
 
#               
839
 
#               doc = self._request("artist.getInfo", True)
840
 
#               
841
 
#               list = []
842
 
#               
843
 
#               elements = doc.getElementsByTagName('tag')
844
 
#               
845
 
#               for element in elements:
846
 
#                               
847
 
#                       name = _extract(element, 'name')
848
 
#                       
849
 
#                       list.append(name)
850
 
#               
851
 
#               return list
852
 
        
853
 
        def get_top_tags(self):
854
 
                """Returns the top tags."""
855
 
                
856
 
                doc = self._request("artist.getTopTags", True)
857
 
                
858
 
                list = []
859
 
                
860
 
                elements = doc.getElementsByTagName('tag')
861
 
                
862
 
                for element in elements:
863
 
                                
864
 
                        name = _extract(element, 'name')
865
 
                        
866
 
                        list.append(name)
867
 
                
868
 
                return list
869
 
 
870
 
        def get_top_albums(self):
871
 
                """Retuns a list of the top albums."""
872
 
                
873
 
                doc = self._request('artist.getTopAlbums', True)
874
 
                
875
 
                list = []
876
 
                
877
 
                for node in doc.getElementsByTagName("album"):
878
 
                        name = _extract(node, "name")
879
 
                        artist = _extract(node, "name", 1)
880
 
                        playcount = _extract(node, "playcount")
881
 
                        
882
 
                        list.append(TopItem(Album(artist, name, *self.auth_data), playcount))
883
 
                
884
 
                return list
885
 
                
886
 
        def get_top_tracks(self):
887
 
                """Returns a list of the most played Tracks by this artist."""
888
 
                
889
 
                doc = self._request("artist.getTopTracks", True)
890
 
                
891
 
                list = []
892
 
                for track in doc.getElementsByTagName('track'):
893
 
                        
894
 
                        title = _extract(track, "name")
895
 
                        artist = _extract(track, "name", 1)
896
 
                        playcount = _number(_extract(track, "playcount"))
897
 
                        
898
 
                        list.append( TopItem(Track(artist, title, *self.auth_data), playcount) )
899
 
                
900
 
                return list
901
 
        
902
 
        def get_top_fans(self, limit = None):
903
 
                """Returns a list of the Users who played this artist the most.
904
 
                # Parameters:
905
 
                        * limit int: Max elements.
906
 
                """
907
 
                
908
 
                params = self._get_params()
909
 
                doc = self._request('artist.getTopFans', True)
910
 
                
911
 
                list = []
912
 
                
913
 
                elements = doc.getElementsByTagName('user')
914
 
                
915
 
                for element in elements:
916
 
                        if limit and len(list) >= limit:
917
 
                                break
918
 
                                
919
 
                        name = _extract(element, 'name')
920
 
                        weight = _number(_extract(element, 'weight'))
921
 
                        
922
 
                        list.append(TopItem(User(name, *self.auth_data), weight))
923
 
                
924
 
                return list
925
 
 
926
 
        def share(self, users, message = None):
927
 
                """Shares this artist (sends out recommendations). 
928
 
                # Parameters:
929
 
                        * users [User|str,]: A list that can contain usernames, emails, User objects, or all of them.
930
 
                        * message str: A message to include in the recommendation message. 
931
 
                """
932
 
                
933
 
                #last.fm currently accepts a max of 10 recipient at a time
934
 
                while(len(users) > 10):
935
 
                        section = users[0:9]
936
 
                        users = users[9:]
937
 
                        self.share(section, message)
938
 
                
939
 
                nusers = []
940
 
                for user in users:
941
 
                        if isinstance(user, User):
942
 
                                nusers.append(user.get_name())
943
 
                        else:
944
 
                                nusers.append(user)
945
 
                
946
 
                params = self._get_params()
947
 
                recipients = ','.join(nusers)
948
 
                params['recipient'] = recipients
949
 
                if message: params['message'] = unicode(message)
950
 
                
951
 
                self._request('artist.share', False, params)
952
 
                info(repr(self) + " was shared with " + repr(users))
953
 
        
954
 
        def get_url(self, domain_name = DOMAIN_ENGLISH):
955
 
                """Returns the url of the artist page on Last.fm. 
956
 
                # Parameters:
957
 
                * domain_name: Last.fm's language domain. Possible values:
958
 
                  o DOMAIN_ENGLISH
959
 
                  o DOMAIN_GERMAN
960
 
                  o DOMAIN_SPANISH
961
 
                  o DOMAIN_FRENCH
962
 
                  o DOMAIN_ITALIAN
963
 
                  o DOMAIN_POLISH
964
 
                  o DOMAIN_PORTUGUESE
965
 
                  o DOMAIN_SWEDISH
966
 
                  o DOMAIN_TURKISH
967
 
                  o DOMAIN_RUSSIAN
968
 
                  o DOMAIN_JAPANESE
969
 
                  o DOMAIN_CHINESE 
970
 
                """
971
 
                
972
 
                url = 'http://%(domain)s/music/%(artist)s'
973
 
                
974
 
                artist = _get_url_safe(self.get_name())
975
 
                
976
 
                return url %{'domain': domain_name, 'artist': artist}
977
 
 
978
 
 
979
 
class Event(_BaseObject):
980
 
        """A Last.fm event."""
981
 
        
982
 
        def __init__(self, event_id, api_key, api_secret, session_key):
983
 
                _BaseObject.__init__(self, api_key, api_secret, session_key)
984
 
                
985
 
                self.id = unicode(event_id)
986
 
        
987
 
        def __repr__(self):
988
 
                return "Event #" + self.get_id()
989
 
        
990
 
        def __eq__(self, other):
991
 
                return self.get_id() == other.get_id()
992
 
        
993
 
        def __ne__(self, other):
994
 
                return self.get_id() != other.get_id()
995
 
        
996
 
        def _get_params(self):
997
 
                return {'event': self.get_id()}
998
 
        
999
 
        def attend(self, attending_status):
1000
 
                """Sets the attending status.
1001
 
                * attending_status: The attending status. Possible values:
1002
 
                  o EVENT_ATTENDING
1003
 
                  o EVENT_MAYBE_ATTENDING
1004
 
                  o EVENT_NOT_ATTENDING 
1005
 
                """
1006
 
                
1007
 
                params = self._get_params()
1008
 
                params['status'] = unicode(attending_status)
1009
 
                
1010
 
                doc = self._request('event.attend', False, params)
1011
 
                info("Attendance to " + repr(self) + " was set to " + repr(attending_status))
1012
 
        
1013
 
        def get_id(self):
1014
 
                """Returns the id of the event on Last.fm. """
1015
 
                return self.id
1016
 
        
1017
 
        def get_title(self):
1018
 
                """Returns the title of the event. """
1019
 
                
1020
 
                doc = self._request("event.getInfo", True)
1021
 
                
1022
 
                return _extract(doc, "title")
1023
 
        
1024
 
        def get_headliner(self):
1025
 
                """Returns the headliner of the event. """
1026
 
                
1027
 
                doc = self._request("event.getInfo", True)
1028
 
                
1029
 
                return Artist(_extract(doc, "headliner"), *self.auth_data)
1030
 
        
1031
 
        def get_artists(self):
1032
 
                """Returns a list of the participating Artists. """
1033
 
                
1034
 
                doc = self._request("event.getInfo", True)
1035
 
                names = _extract_all(doc, "artist")
1036
 
                
1037
 
                artists = []
1038
 
                for name in names:
1039
 
                        artists.append(Artist(name, *self.auth_data))
1040
 
                
1041
 
                return artists
1042
 
        
1043
 
        def get_venue(self):
1044
 
                """Returns the venue where the event is held."""
1045
 
                
1046
 
                doc = self._request("event.getInfo", True)
1047
 
                
1048
 
                venue_url = _extract(doc, "url")
1049
 
                venue_id = _number(venue_url[venue_url.rfind("/") + 1:])
1050
 
                
1051
 
                return Venue(venue_id, *self.auth_data)
1052
 
        
1053
 
        def get_start_date(self):
1054
 
                """Returns the date when the event starts."""
1055
 
                
1056
 
                doc = self._request("event.getInfo", True)
1057
 
                
1058
 
                return _extract(doc, "startDate")
1059
 
                
1060
 
        def get_description(self):
1061
 
                """Returns the description of the event. """
1062
 
                
1063
 
                doc = self._request("event.getInfo", True)
1064
 
                
1065
 
                return _extract(doc, "description")
1066
 
        
1067
 
        def get_image_url(self, size = IMAGE_LARGE):
1068
 
                """Returns the associated image URL. 
1069
 
                * size: The image size. Possible values:
1070
 
                  o IMAGE_LARGE
1071
 
                  o IMAGE_MEDIUM
1072
 
                  o IMAGE_SMALL 
1073
 
                """
1074
 
                
1075
 
                doc = self._request("event.getInfo", True)
1076
 
                
1077
 
                return _extract_all(doc, "image")[size]
1078
 
        
1079
 
        def get_attendance_count(self):
1080
 
                """Returns the number of attending people. """
1081
 
                
1082
 
                doc = self._request("event.getInfo", True)
1083
 
                
1084
 
                return _number(_extract(doc, "attendance"))
1085
 
        
1086
 
        def get_review_count(self):
1087
 
                """Returns the number of available reviews for this event. """
1088
 
                
1089
 
                doc = self._request("event.getInfo", True)
1090
 
                
1091
 
                return _number(_extract(doc, "reviews"))
1092
 
        
1093
 
        def get_url(self, domain_name = DOMAIN_ENGLISH):
1094
 
                """Returns the url of the event page on Last.fm. 
1095
 
                * domain_name: Last.fm's language domain. Possible values:
1096
 
                  o DOMAIN_ENGLISH
1097
 
                  o DOMAIN_GERMAN
1098
 
                  o DOMAIN_SPANISH
1099
 
                  o DOMAIN_FRENCH
1100
 
                  o DOMAIN_ITALIAN
1101
 
                  o DOMAIN_POLISH
1102
 
                  o DOMAIN_PORTUGUESE
1103
 
                  o DOMAIN_SWEDISH
1104
 
                  o DOMAIN_TURKISH
1105
 
                  o DOMAIN_RUSSIAN
1106
 
                  o DOMAIN_JAPANESE
1107
 
                  o DOMAIN_CHINESE 
1108
 
                """
1109
 
                
1110
 
                url = 'http://%(domain)s/event/%(id)s'
1111
 
                
1112
 
                return url %{'domain': domain_name, 'id': self.get_id()}
1113
 
 
1114
 
        def share(self, users, message = None):
1115
 
                """Shares this event (sends out recommendations). 
1116
 
                * users: A list that can contain usernames, emails, User objects, or all of them.
1117
 
                * message: A message to include in the recommendation message. 
1118
 
                """
1119
 
                
1120
 
                #last.fm currently accepts a max of 10 recipient at a time
1121
 
                while(len(users) > 10):
1122
 
                        section = users[0:9]
1123
 
                        users = users[9:]
1124
 
                        self.share(section, message)
1125
 
                
1126
 
                nusers = []
1127
 
                for user in users:
1128
 
                        if isinstance(user, User):
1129
 
                                nusers.append(user.get_name())
1130
 
                        else:
1131
 
                                nusers.append(user)
1132
 
                
1133
 
                params = self._get_params()
1134
 
                recipients = ','.join(nusers)
1135
 
                params['recipient'] = recipients
1136
 
                if message: params['message'] = unicode(message)
1137
 
                
1138
 
                self._request('event.share', False, params)
1139
 
                info(repr(self) + " was shared with " + repr(users))
1140
 
 
1141
 
class Country(_BaseObject):
1142
 
        """A country at Last.fm."""
1143
 
        
1144
 
        def __init__(self, name, api_key, api_secret, session_key):
1145
 
                _BaseObject.__init__(self, api_key, api_secret, session_key)
1146
 
                
1147
 
                self.name = name
1148
 
        
1149
 
        def __repr__(self):
1150
 
                return self.get_name()
1151
 
        
1152
 
        def __eq__(self, other):
1153
 
                self.get_name().lower() == other.get_name().lower()
1154
 
        
1155
 
        def __ne__(self, other):
1156
 
                self.get_name() != other.get_name()
1157
 
        
1158
 
        def _get_params(self):
1159
 
                return {'country': self.get_name()}
1160
 
        
1161
 
        def _get_name_from_code(self, alpha2code):
1162
 
                # TODO: Have this function lookup the alpha-2 code and return the country name.
1163
 
                
1164
 
                return alpha2code
1165
 
        
1166
 
        def get_name(self):
1167
 
                """Returns the country name. """
1168
 
                
1169
 
                return self.name
1170
 
        
1171
 
        def get_top_artists(self):
1172
 
                """Returns a sequence of the most played artists."""
1173
 
                
1174
 
                doc = self._request('geo.getTopArtists', True)
1175
 
                
1176
 
                list = []
1177
 
                for node in doc.getElementsByTagName("artist"):
1178
 
                        name = _extract(node, 'name')
1179
 
                        playcount = _extract(node, "playcount")
1180
 
                
1181
 
                        list.append(TopItem(Artist(name, *self.auth_data), playcount))
1182
 
                
1183
 
                return list
1184
 
        
1185
 
        def get_top_tracks(self):
1186
 
                """Returns a sequence of the most played tracks"""
1187
 
                
1188
 
                doc = self._request("geo.getTopTracks", True)
1189
 
                
1190
 
                list = []
1191
 
                
1192
 
                for n in doc.getElementsByTagName('track'):
1193
 
                        
1194
 
                        title = _extract(n, 'name')
1195
 
                        artist = _extract(n, 'name', 1)
1196
 
                        playcount = _number(_extract(n, "playcount"))
1197
 
                        
1198
 
                        list.append( TopItem(Track(artist, title, *self.auth_data), playcount))
1199
 
                
1200
 
                return list
1201
 
        
1202
 
        def get_url(self, domain_name = DOMAIN_ENGLISH):
1203
 
                """Returns the url of the event page on Last.fm. 
1204
 
                * domain_name: Last.fm's language domain. Possible values:
1205
 
                  o DOMAIN_ENGLISH
1206
 
                  o DOMAIN_GERMAN
1207
 
                  o DOMAIN_SPANISH
1208
 
                  o DOMAIN_FRENCH
1209
 
                  o DOMAIN_ITALIAN
1210
 
                  o DOMAIN_POLISH
1211
 
                  o DOMAIN_PORTUGUESE
1212
 
                  o DOMAIN_SWEDISH
1213
 
                  o DOMAIN_TURKISH
1214
 
                  o DOMAIN_RUSSIAN
1215
 
                  o DOMAIN_JAPANESE
1216
 
                  o DOMAIN_CHINESE 
1217
 
                """
1218
 
                
1219
 
                url = 'http://%(domain)s/place/%(country_name)s'
1220
 
                
1221
 
                country_name = _get_url_safe(self.get_name())
1222
 
                
1223
 
                return url %{'domain': domain_name, 'country_name': country_name}
1224
 
 
1225
 
 
1226
 
class Library(_BaseObject):
1227
 
        """A user's Last.fm library."""
1228
 
        
1229
 
        def __init__(self, user, api_key, api_secret, session_key):
1230
 
                _BaseObject.__init__(self, api_key, api_secret, session_key)
1231
 
                
1232
 
                if isinstance(user, User):
1233
 
                        self.user = user
1234
 
                else:
1235
 
                        self.user = User(user, *self.auth_data)
1236
 
                
1237
 
                self._albums_index = 0
1238
 
                self._artists_index = 0
1239
 
                self._tracks_index = 0
1240
 
        
1241
 
        def __repr__(self):
1242
 
                return self.get_user().__repr__() + "'s Library"
1243
 
        
1244
 
        def _get_params(self):
1245
 
                return {'user': self.user.get_name()}
1246
 
        
1247
 
        def get_user(self):
1248
 
                """Returns the user who owns this library."""
1249
 
                
1250
 
                return self.user
1251
 
        
1252
 
        def add_album(self, album):
1253
 
                """Add an album to this library."""
1254
 
                
1255
 
                params = self._get_params()
1256
 
                params["artist"] = album.get_artist.get_name()
1257
 
                params["album"] = album.get_name()
1258
 
                
1259
 
                self._request("library.addAlbum", False, params)
1260
 
                info(repr(album) + " was added to " + repr(self))
1261
 
        
1262
 
        def add_artist(self, artist):
1263
 
                """Add an artist to this library."""
1264
 
                
1265
 
                params = self._get_params()
1266
 
                params["artist"] = artist.get_name()
1267
 
                
1268
 
                self._request("library.addArtist", False, params)
1269
 
                info(repr(artist) + " was added to " + repr(self))
1270
 
        
1271
 
        def add_track(self, track):
1272
 
                """Add a track to this library."""
1273
 
                
1274
 
                params = self._get_prams()
1275
 
                params["track"] = track.get_title()
1276
 
                
1277
 
                self._request("library.addTrack", False, params)
1278
 
                info(repr(track) + " was added to " + repr(self))
1279
 
        
1280
 
        def _get_albums_pagecount(self):
1281
 
                """Returns the number of album pages in this library."""
1282
 
                
1283
 
                doc = self._request("library.getAlbums", True)
1284
 
                
1285
 
                return _number(doc.getElementsByTagName("albums")[0].getAttribute("totalPages"))
1286
 
        
1287
 
        def is_end_of_albums(self):
1288
 
                """Returns True when the last page of albums has ben retrieved."""
1289
 
 
1290
 
                if self._albums_index >= self._get_albums_pagecount():
1291
 
                        return True
1292
 
                else:
1293
 
                        return False
1294
 
        
1295
 
        def _get_artists_pagecount(self):
1296
 
                """Returns the number of artist pages in this library."""
1297
 
                
1298
 
                doc = self._request("library.getArtists", True)
1299
 
                
1300
 
                return _number(doc.getElementsByTagName("artists")[0].getAttribute("totalPages"))
1301
 
        
1302
 
        def is_end_of_artists(self):
1303
 
                """Returns True when the last page of artists has ben retrieved."""
1304
 
                
1305
 
                if self._artists_index >= self._get_artists_pagecount():
1306
 
                        return True
1307
 
                else:
1308
 
                        return False
1309
 
 
1310
 
        def _get_tracks_pagecount(self):
1311
 
                """Returns the number of track pages in this library."""
1312
 
                
1313
 
                doc = self._request("library.getTracks", True)
1314
 
                
1315
 
                return _number(doc.getElementsByTagName("tracks")[0].getAttribute("totalPages"))
1316
 
        
1317
 
        def is_end_of_tracks(self):
1318
 
                """Returns True when the last page of tracks has ben retrieved."""
1319
 
                
1320
 
                if self._tracks_index >= self._get_tracks_pagecount():
1321
 
                        return True
1322
 
                else:
1323
 
                        return False
1324
 
        
1325
 
        def get_albums_page(self):
1326
 
                """Retreives the next page of albums in the Library. Returns a sequence of TopItem objects.
1327
 
                Use the function extract_items like extract_items(Library.get_albums_page()) to return only a sequence of
1328
 
                Album objects with no extra data.
1329
 
                
1330
 
                Example:
1331
 
                -------
1332
 
                library = Library("rj", API_KEY, API_SECRET, SESSION_KEY)
1333
 
                
1334
 
                while not library.is_end_of_albums():
1335
 
                        print library.get_albums_page()
1336
 
                """
1337
 
                
1338
 
                self._albums_index += 1
1339
 
                
1340
 
                params = self._get_params()
1341
 
                params["page"] = str(self._albums_index)
1342
 
                
1343
 
                list = []
1344
 
                doc = self._request("library.getAlbums", True, params)
1345
 
                for node in doc.getElementsByTagName("album"):
1346
 
                        name = _extract(node, "name")
1347
 
                        artist = _extract(node, "name", 1)
1348
 
                        playcount = _number(_extract(node, "playcount"))
1349
 
                        tagcount = _number(_extract(node, "tagcount"))
1350
 
                        
1351
 
                        list.append(LibraryItem(Album(artist, name, *self.auth_data), playcount, tagcount))
1352
 
                
1353
 
                return list
1354
 
        
1355
 
        def get_artists_page(self):
1356
 
                """Retreives the next page of artists in the Library. Returns a sequence of TopItem objects.
1357
 
                Use the function extract_items like extract_items(Library.get_artists_page()) to return only a sequence of
1358
 
                Artist objects with no extra data.
1359
 
                
1360
 
                Example:
1361
 
                -------
1362
 
                library = Library("rj", API_KEY, API_SECRET, SESSION_KEY)
1363
 
                
1364
 
                while not library.is_end_of_artists():
1365
 
                        print library.get_artists_page()
1366
 
                """
1367
 
 
1368
 
                self._artists_index += 1
1369
 
                
1370
 
                params = self._get_params()
1371
 
                params["page"] = str(self._artists_index)
1372
 
                
1373
 
                list = []
1374
 
                doc = self._request("library.getArtists", True, params)
1375
 
                for node in doc.getElementsByTagName("artist"):
1376
 
                        name = _extract(node, "name")
1377
 
                        
1378
 
                        playcount = _number(_extract(node, "playcount"))
1379
 
                        tagcount = _number(_extract(node, "tagcount"))
1380
 
                        
1381
 
                        list.append(LibraryItem(Artist(name, *self.auth_data), playcount, tagcount))
1382
 
                
1383
 
                return list
1384
 
 
1385
 
        def get_tracks_page(self):
1386
 
                """Retreives the next page of tracks in the Library. Returns a sequence of TopItem objects.
1387
 
                Use the function extract_items like extract_items(Library.get_tracks_page()) to return only a sequence of
1388
 
                Track objects with no extra data.
1389
 
                
1390
 
                Example:
1391
 
                -------
1392
 
                library = Library("rj", API_KEY, API_SECRET, SESSION_KEY)
1393
 
                
1394
 
                while not library.is_end_of_tracks():
1395
 
                        print library.get_tracks_page()
1396
 
                """
1397
 
                
1398
 
                self._tracks_index += 1
1399
 
                
1400
 
                params = self._get_params()
1401
 
                params["page"] = str(self._tracks_index)
1402
 
                
1403
 
                list = []
1404
 
                doc = self._request("library.getTracks", True, params)
1405
 
                for node in doc.getElementsByTagName("track"):
1406
 
                        name = _extract(node, "name")
1407
 
                        artist = _extract(node, "name", 1)
1408
 
                        playcount = _number(_extract(node, "playcount"))
1409
 
                        tagcount = _number(_extract(node, "tagcount"))
1410
 
                        
1411
 
                        list.append(LibraryItem(Track(artist, name, *self.auth_data), playcount, tagcount))
1412
 
                
1413
 
                return list
1414
 
                        
1415
 
 
1416
 
class Playlist(_BaseObject):
1417
 
        """A Last.fm user playlist."""
1418
 
        
1419
 
        def __init__(self, user, id, api_key, api_secret, session_key):
1420
 
                _BaseObject.__init__(self, api_key, api_secret, session_key)
1421
 
                
1422
 
                if isinstance(user, User):
1423
 
                        self.user = user
1424
 
                else:
1425
 
                        self.user = User(user, *self.auth_data)
1426
 
                
1427
 
                self.id = unicode(id)
1428
 
        
1429
 
        def __repr__(self):
1430
 
                return repr(self.user) + "'s playlist # " + repr(self.id)
1431
 
                
1432
 
        def _get_info_node(self):
1433
 
                """Returns the node from user.getPlaylists where this playlist's info is."""
1434
 
                
1435
 
                doc = self._request("user.getPlaylists", True)
1436
 
                
1437
 
                for node in doc.getElementsByTagName("playlist"):
1438
 
                        if _extract(node, "id") == str(self.get_id()):
1439
 
                                return node
1440
 
        
1441
 
        def _get_params(self):
1442
 
                return {'user': self.user.get_name(), 'playlistID': self.get_id()}
1443
 
        
1444
 
        def get_id(self):
1445
 
                """Returns the playlist id."""
1446
 
                
1447
 
                return self.id
1448
 
        
1449
 
        def get_user(self):
1450
 
                """Returns the owner user of this playlist."""
1451
 
                
1452
 
                return self.user
1453
 
        
1454
 
        def get_tracks(self):
1455
 
                """Returns a list of the tracks on this user playlist."""
1456
 
                
1457
 
                uri = u'lastfm://playlist/%s' %self.get_id()
1458
 
                
1459
 
                return XSPF(uri, *self.auth_data).get_tracks()
1460
 
        
1461
 
        def add_track(self, track):
1462
 
                """Adds a Track to this Playlist."""
1463
 
                
1464
 
                params = self._get_params()
1465
 
                params['artist'] = track.get_artist().get_name()
1466
 
                params['track'] = track.get_title()
1467
 
                
1468
 
                self._request('playlist.addTrack', False, params)
1469
 
                info(repr(track) + " was added to " + repr(self))
1470
 
        
1471
 
        def get_title(self):
1472
 
                """Returns the title of this playlist."""
1473
 
                
1474
 
                return _extract(self._get_info_node(), "title")
1475
 
        
1476
 
        def get_creation_date(self):
1477
 
                """Returns the creation date of this playlist."""
1478
 
                
1479
 
                return _extract(self._get_info_node(), "date")
1480
 
        
1481
 
        def get_size(self):
1482
 
                """Returns the number of tracks in this playlist."""
1483
 
                
1484
 
                return _number(_extract(self._get_info_node(), "size"))
1485
 
        
1486
 
        def get_description(self):
1487
 
                """Returns the description of this playlist."""
1488
 
                
1489
 
                return _extract(self._get_info_node(), "description")
1490
 
        
1491
 
        def get_duration(self):
1492
 
                """Returns the duration of this playlist in milliseconds."""
1493
 
                
1494
 
                return _number(_extract(self._get_info_node(), "duration"))
1495
 
        
1496
 
        def is_streamable(self):
1497
 
                """Returns True if the playlist is streamable.
1498
 
                For a playlist to be streamable, it needs at least 45 tracks by 15 different artists."""
1499
 
                
1500
 
                if _extract(self._get_info_node(), "streamable") == '1':
1501
 
                        return True
1502
 
                else:
1503
 
                        return False
1504
 
        
1505
 
        def has_track(self, track):
1506
 
                """Checks to see if track is already in the playlist.
1507
 
                * track: Any Track object.
1508
 
                """
1509
 
                
1510
 
                return track in self.get_tracks()
1511
 
 
1512
 
        def get_image_url(self, size = IMAGE_LARGE):
1513
 
                """Returns the associated image URL.
1514
 
                * size: The image size. Possible values:
1515
 
                  o IMAGE_LARGE
1516
 
                  o IMAGE_MEDIUM
1517
 
                  o IMAGE_SMALL
1518
 
                """
1519
 
                
1520
 
                return _extract(self._get_info_node(), "image")[size]
1521
 
        
1522
 
        def get_url(self, domain_name = DOMAIN_ENGLISH):
1523
 
                """Returns the url of the playlist on Last.fm. 
1524
 
                * domain_name: Last.fm's language domain. Possible values:
1525
 
                  o DOMAIN_ENGLISH
1526
 
                  o DOMAIN_GERMAN
1527
 
                  o DOMAIN_SPANISH
1528
 
                  o DOMAIN_FRENCH
1529
 
                  o DOMAIN_ITALIAN
1530
 
                  o DOMAIN_POLISH
1531
 
                  o DOMAIN_PORTUGUESE
1532
 
                  o DOMAIN_SWEDISH
1533
 
                  o DOMAIN_TURKISH
1534
 
                  o DOMAIN_RUSSIAN
1535
 
                  o DOMAIN_JAPANESE
1536
 
                  o DOMAIN_CHINESE 
1537
 
                """
1538
 
                url = "http://%(domain)s/user/%(user)s/library/playlists/%(appendix)s"
1539
 
                
1540
 
                english_url = _extract(self._get_info_node(), "url")
1541
 
                appendix = english_url[english_url.rfind("/") + 1:]
1542
 
                
1543
 
                return url %{'domain': domain_name, 'appendix': appendix, "user": self.get_user().get_name()}
1544
 
                
1545
 
 
1546
 
class Tag(_BaseObject):
1547
 
        """A Last.fm object tag."""
1548
 
        
1549
 
        # TODO: getWeeklyArtistChart (too lazy, i'll wait for when someone requests it)
1550
 
        
1551
 
        def __init__(self, name, api_key, api_secret, session_key):
1552
 
                _BaseObject.__init__(self, api_key, api_secret, session_key)
1553
 
                
1554
 
                self.name = name
1555
 
        
1556
 
        def _get_params(self):
1557
 
                return {'tag': self.get_name()}
1558
 
        
1559
 
        def __repr__(self):
1560
 
                return self.get_name()
1561
 
        
1562
 
        def __eq__(self):
1563
 
                return self.get_name().lower() == other.get_name().lower()
1564
 
        
1565
 
        def __ne__(self):
1566
 
                return self.get_name().lower() != other.get_name().lower()
1567
 
        
1568
 
        def get_name(self):
1569
 
                """Returns the name of the tag. """
1570
 
                
1571
 
                return self.name
1572
 
 
1573
 
        def get_similar(self):
1574
 
                """Returns the tags similar to this one, ordered by similarity. """
1575
 
                
1576
 
                doc = self._request('tag.getSimilar', True)
1577
 
                
1578
 
                list = []
1579
 
                names = _extract_all(doc, 'name')
1580
 
                for name in names:
1581
 
                        list.append(Tag(name, *self.auth_data))
1582
 
                
1583
 
                return list
1584
 
        
1585
 
        def get_top_albums(self):
1586
 
                """Retuns a list of the top albums."""
1587
 
                
1588
 
                doc = self._request('tag.getTopAlbums', True)
1589
 
                
1590
 
                list = []
1591
 
                
1592
 
                for node in doc.getElementsByTagName("album"):
1593
 
                        name = _extract(node, "name")
1594
 
                        artist = _extract(node, "name", 1)
1595
 
                        playcount = _extract(node, "playcount")
1596
 
                        
1597
 
                        list.append(TopItem(Album(artist, name, *self.auth_data), playcount))
1598
 
                
1599
 
                return list
1600
 
                
1601
 
        def get_top_tracks(self):
1602
 
                """Returns a list of the most played Tracks by this artist."""
1603
 
                
1604
 
                doc = self._request("tag.getTopTracks", True)
1605
 
                
1606
 
                list = []
1607
 
                for track in doc.getElementsByTagName('track'):
1608
 
                        
1609
 
                        title = _extract(track, "name")
1610
 
                        artist = _extract(track, "name", 1)
1611
 
                        playcount = _number(_extract(track, "playcount"))
1612
 
                        
1613
 
                        list.append( TopItem(Track(artist, title, *self.auth_data), playcount) )
1614
 
                
1615
 
                return list
1616
 
        
1617
 
        def get_top_artists(self):
1618
 
                """Returns a sequence of the most played artists."""
1619
 
                
1620
 
                doc = self._request('tag.getTopArtists', True)
1621
 
                
1622
 
                list = []
1623
 
                for node in doc.getElementsByTagName("artist"):
1624
 
                        name = _extract(node, 'name')
1625
 
                        playcount = _extract(node, "playcount")
1626
 
                
1627
 
                        list.append(TopItem(Artist(name, *self.auth_data), playcount))
1628
 
                
1629
 
                return list
1630
 
        
1631
 
        def get_weekly_chart_dates(self):
1632
 
                """Returns a list of From and To tuples for the available charts."""
1633
 
                
1634
 
                doc = self._request("tag.getWeeklyChartList", True)
1635
 
                
1636
 
                list = []
1637
 
                for node in doc.getElementsByTagName("chart"):
1638
 
                        list.append( (node.getAttribute("from"), node.getAttribute("to")) )
1639
 
                
1640
 
                return list
1641
 
        
1642
 
        def get_weekly_artist_charts(self, from_date = None, to_date = None):
1643
 
                """Returns the weekly artist charts for the week starting from the from_date value to the to_date value."""
1644
 
                
1645
 
                params = self._get_params()
1646
 
                if from_date and to_date:
1647
 
                        params["from"] = from_date
1648
 
                        params["to"] = to_date
1649
 
                
1650
 
                doc = self._request("tag.getWeeklyArtistChart", True, params)
1651
 
                
1652
 
                list = []
1653
 
                for node in doc.getElementsByTagName("artist"):
1654
 
                        item = Artist(_extract(node, "name"), *self.auth_data)
1655
 
                        weight = _extract(node, "weight")
1656
 
                        list.append(TopItem(item, weight))
1657
 
                
1658
 
                return list
1659
 
        
1660
 
        def get_url(self, domain_name = DOMAIN_ENGLISH):
1661
 
                """Returns the url of the tag page on Last.fm. 
1662
 
                * domain_name: Last.fm's language domain. Possible values:
1663
 
                  o DOMAIN_ENGLISH
1664
 
                  o DOMAIN_GERMAN
1665
 
                  o DOMAIN_SPANISH
1666
 
                  o DOMAIN_FRENCH
1667
 
                  o DOMAIN_ITALIAN
1668
 
                  o DOMAIN_POLISH
1669
 
                  o DOMAIN_PORTUGUESE
1670
 
                  o DOMAIN_SWEDISH
1671
 
                  o DOMAIN_TURKISH
1672
 
                  o DOMAIN_RUSSIAN
1673
 
                  o DOMAIN_JAPANESE
1674
 
                  o DOMAIN_CHINESE 
1675
 
                """
1676
 
                
1677
 
                url = 'http://%(domain)s/tag/%(name)s'
1678
 
                
1679
 
                name = _get_url_safe(self.get_name())
1680
 
                
1681
 
                return url %{'domain': domain_name, 'name': name}
1682
 
 
1683
 
class Track(_BaseObject, _Taggable):
1684
 
        """A Last.fm track."""
1685
 
        
1686
 
        def __init__(self, artist, title, api_key, api_secret, session_key):
1687
 
                _BaseObject.__init__(self, api_key, api_secret, session_key)
1688
 
                _Taggable.__init__(self, 'track')
1689
 
                
1690
 
                if isinstance(artist, Artist):
1691
 
                        self.artist = artist
1692
 
                else:
1693
 
                        self.artist = Artist(artist, *self.auth_data)
1694
 
                
1695
 
                self.title = title
1696
 
 
1697
 
        def __repr__(self):
1698
 
                return self.get_artist().get_name() + ' - ' + self.get_title()
1699
 
 
1700
 
        def __eq__(self, other):
1701
 
                return (self.get_title().lower() == other.get_title().lower()) and (self.get_artist().get_name().lower() == other.get_artist().get_name().lower())
1702
 
        
1703
 
        def __ne__(self, other):
1704
 
                return (self.get_title().lower() != other.get_title().lower()) or (self.get_artist().get_name().lower() != other.get_artist().get_name().lower())
1705
 
        
1706
 
        def _get_params(self):
1707
 
                return {'artist': self.get_artist().get_name(), 'track': self.get_title()}
1708
 
                        
1709
 
        def get_artist(self):
1710
 
                """Returns the associated Artist object."""
1711
 
                
1712
 
                return self.artist
1713
 
        
1714
 
        def get_title(self):
1715
 
                """Returns the track title."""
1716
 
                
1717
 
                return self.title
1718
 
        
1719
 
        def get_name(self):
1720
 
                """Returns the track title (alias to Track.get_title)."""
1721
 
                
1722
 
                return self.get_title()
1723
 
        
1724
 
        def get_id(self):
1725
 
                """Returns the track id on Last.fm."""
1726
 
                
1727
 
                doc = self._request("track.getInfo", True)
1728
 
                
1729
 
                return _extract(doc, "id")
1730
 
        
1731
 
        def get_duration(self):
1732
 
                """Returns the track duration."""
1733
 
                
1734
 
                doc = self._request("track.getInfo", True)
1735
 
                
1736
 
                return _number(_extract(doc, "duration"))
1737
 
        
1738
 
        def get_mbid(self):
1739
 
                """Returns the MusicBrainz ID of this track."""
1740
 
                
1741
 
                doc = self._request("track.getInfo", True)
1742
 
                
1743
 
                return _extract(doc, "mbid")
1744
 
                
1745
 
        def get_listener_count(self):
1746
 
                """Returns the listener count."""
1747
 
                
1748
 
                doc = self._request("track.getInfo", True)
1749
 
                
1750
 
                return _number(_extract(doc, "listeners"))
1751
 
        
1752
 
        def get_playcount(self):
1753
 
                """Returns the play count."""
1754
 
                
1755
 
                doc = self._request("track.getInfo", True)
1756
 
                return _number(_extract(doc, "playcount"))
1757
 
        
1758
 
        def get_top_tags(self):
1759
 
                """Returns the play count."""
1760
 
                
1761
 
                doc = self._request("track.getInfo", True)
1762
 
                
1763
 
                list = []
1764
 
                
1765
 
                elements = doc.getElementsByTagName('tag')
1766
 
                
1767
 
                for element in elements:
1768
 
                                
1769
 
                        name = _extract(element, 'name')
1770
 
                        
1771
 
                        list.append(name)
1772
 
                
1773
 
                return list
1774
 
        
1775
 
        def is_streamable(self):
1776
 
                """Returns True if the track is available at Last.fm."""
1777
 
                
1778
 
                doc = self._request("track.getInfo", True)
1779
 
                return _extract(doc, "streamable") == "1"
1780
 
        
1781
 
        def is_fulltrack_available(self):
1782
 
                """Returns True if the fulltrack is available for streaming."""
1783
 
                
1784
 
                doc = self._request("track.getInfo", True)
1785
 
                return doc.getElementsByTagName("streamable")[0].getAttribute("fulltrack") == "1"
1786
 
                
1787
 
        def get_album(self):
1788
 
                """Returns the album object of this track."""
1789
 
                
1790
 
                doc = self._request("track.getInfo", True)
1791
 
                
1792
 
                albums = doc.getElementsByTagName("album")
1793
 
                
1794
 
                if len(albums) == 0:
1795
 
                        return
1796
 
                
1797
 
                node = doc.getElementsByTagName("album")[0]
1798
 
                return Album(_extract(node, "artist"), _extract(node, "title"), *self.auth_data)
1799
 
        
1800
 
        def get_wiki_published_date(self):
1801
 
                """Returns the date of publishing this version of the wiki."""
1802
 
                
1803
 
                doc = self._request("track.getInfo", True)
1804
 
                
1805
 
                if len(doc.getElementsByTagName("wiki")) == 0:
1806
 
                        return
1807
 
                
1808
 
                node = doc.getElementsByTagName("wiki")[0]
1809
 
                
1810
 
                return _extract(node, "published")
1811
 
        
1812
 
        def get_wiki_summary(self):
1813
 
                """Returns the summary of the wiki."""
1814
 
                
1815
 
                doc = self._request("track.getInfo", True)
1816
 
                
1817
 
                if len(doc.getElementsByTagName("wiki")) == 0:
1818
 
                        return
1819
 
                
1820
 
                node = doc.getElementsByTagName("wiki")[0]
1821
 
                
1822
 
                return _extract(node, "summary")
1823
 
                
1824
 
        def get_wiki_content(self):
1825
 
                """Returns the content of the wiki."""
1826
 
                
1827
 
                doc = self._request("track.getInfo", True)
1828
 
                
1829
 
                if len(doc.getElementsByTagName("wiki")) == 0:
1830
 
                        return
1831
 
                
1832
 
                node = doc.getElementsByTagName("wiki")[0]
1833
 
                
1834
 
                return _extract(node, "content")
1835
 
        
1836
 
        def love(self):
1837
 
                """Adds the track to the user's loved tracks. """
1838
 
                
1839
 
                self._request('track.love')
1840
 
        
1841
 
        def ban(self):
1842
 
                """Ban this track from ever playing on the radio. """
1843
 
                
1844
 
                self._request('track.ban')
1845
 
        
1846
 
        def get_similar(self):
1847
 
                """Returns similar tracks for this track on Last.fm, based on listening data. """
1848
 
                
1849
 
                doc = self._request('track.getSimilar', True)
1850
 
                
1851
 
                list = []
1852
 
                for node in doc.getElementsByTagName("track"):
1853
 
                        title = _extract(node, 'name')
1854
 
                        artist = _extract(node, 'name', 1)
1855
 
                        
1856
 
                        list.append(Track(artist, title, *self.auth_data))
1857
 
                
1858
 
                return list
1859
 
 
1860
 
        def get_top_fans(self, limit = None):
1861
 
                """Returns a list of the Users who played this track."""
1862
 
                
1863
 
                doc = self._request('track.getTopFans', True)
1864
 
                
1865
 
                list = []
1866
 
                
1867
 
                elements = doc.getElementsByTagName('user')
1868
 
                
1869
 
                for element in elements:
1870
 
                        if limit and len(list) >= limit:
1871
 
                                break
1872
 
                                
1873
 
                        name = _extract(element, 'name')
1874
 
                        weight = _number(_extract(element, 'weight'))
1875
 
                        
1876
 
                        list.append(TopItem(User(name, *self.auth_data), weight))
1877
 
                
1878
 
                return list
1879
 
        
1880
 
        def share(self, users, message = None):
1881
 
                """Shares this track (sends out recommendations). 
1882
 
                * users: A list that can contain usernames, emails, User objects, or all of them.
1883
 
                * message: A message to include in the recommendation message. 
1884
 
                """
1885
 
                
1886
 
                #last.fm currently accepts a max of 10 recipient at a time
1887
 
                while(len(users) > 10):
1888
 
                        section = users[0:9]
1889
 
                        users = users[9:]
1890
 
                        self.share(section, message)
1891
 
                
1892
 
                nusers = []
1893
 
                for user in users:
1894
 
                        if isinstance(user, User):
1895
 
                                nusers.append(user.get_name())
1896
 
                        else:
1897
 
                                nusers.append(user)
1898
 
                
1899
 
                params = self._get_params()
1900
 
                recipients = ','.join(nusers)
1901
 
                params['recipient'] = recipients
1902
 
                if message: params['message'] = unicode(message)
1903
 
                
1904
 
                self._request('track.share', False, params)
1905
 
        
1906
 
        def get_url(self, domain_name = DOMAIN_ENGLISH):
1907
 
                """Returns the url of the track page on Last.fm. 
1908
 
                * domain_name: Last.fm's language domain. Possible values:
1909
 
                  o DOMAIN_ENGLISH
1910
 
                  o DOMAIN_GERMAN
1911
 
                  o DOMAIN_SPANISH
1912
 
                  o DOMAIN_FRENCH
1913
 
                  o DOMAIN_ITALIAN
1914
 
                  o DOMAIN_POLISH
1915
 
                  o DOMAIN_PORTUGUESE
1916
 
                  o DOMAIN_SWEDISH
1917
 
                  o DOMAIN_TURKISH
1918
 
                  o DOMAIN_RUSSIAN
1919
 
                  o DOMAIN_JAPANESE
1920
 
                  o DOMAIN_CHINESE 
1921
 
                """
1922
 
                url = 'http://%(domain)s/music/%(artist)s/_/%(title)s'
1923
 
                
1924
 
                artist = _get_url_safe(self.get_artist().get_name())
1925
 
                title = _get_url_safe(self.get_title())
1926
 
                
1927
 
                return url %{'domain': domain_name, 'artist': artist, 'title': title}
1928
 
        
1929
 
class Group(_BaseObject):
1930
 
        """A Last.fm group."""
1931
 
        
1932
 
        def __init__(self, group_name, api_key, api_secret, session_key):
1933
 
                _BaseObject.__init__(self, api_key, api_secret, session_key)
1934
 
                
1935
 
                self.name = group_name
1936
 
        
1937
 
        def __repr__(self):
1938
 
                return self.get_name()
1939
 
        
1940
 
        def __eq__(self, other):
1941
 
                return self.get_name().lower() == other.get_name().lower()
1942
 
        
1943
 
        def __ne__(self, other):
1944
 
                return self.get_name() != other.get_name()
1945
 
        
1946
 
        def _get_params(self):
1947
 
                return {'group': self.get_name()}
1948
 
        
1949
 
        def get_name(self):
1950
 
                """Returns the group name. """
1951
 
                return self.name
1952
 
        
1953
 
        def get_weekly_chart_dates(self):
1954
 
                """Returns a list of From and To tuples for the available charts."""
1955
 
                
1956
 
                doc = self._request("group.getWeeklyChartList", True)
1957
 
                
1958
 
                list = []
1959
 
                for node in doc.getElementsByTagName("chart"):
1960
 
                        list.append( (node.getAttribute("from"), node.getAttribute("to")) )
1961
 
                
1962
 
                return list
1963
 
        
1964
 
        def get_weekly_artist_charts(self, from_date = None, to_date = None):
1965
 
                """Returns the weekly artist charts for the week starting from the from_date value to the to_date value."""
1966
 
                
1967
 
                params = self._get_params()
1968
 
                if from_date and to_date:
1969
 
                        params["from"] = from_date
1970
 
                        params["to"] = to_date
1971
 
                
1972
 
                doc = self._request("group.getWeeklyArtistChart", True, params)
1973
 
                
1974
 
                list = []
1975
 
                for node in doc.getElementsByTagName("artist"):
1976
 
                        item = Artist(_extract(node, "name"), *self.auth_data)
1977
 
                        weight = _extract(node, "playcount")
1978
 
                        list.append(TopItem(item, weight))
1979
 
                
1980
 
                return list
1981
 
 
1982
 
        def get_weekly_album_charts(self, from_date = None, to_date = None):
1983
 
                """Returns the weekly album charts for the week starting from the from_date value to the to_date value."""
1984
 
                
1985
 
                params = self._get_params()
1986
 
                if from_date and to_date:
1987
 
                        params["from"] = from_date
1988
 
                        params["to"] = to_date
1989
 
                
1990
 
                doc = self._request("group.getWeeklyAlbumChart", True, params)
1991
 
                
1992
 
                list = []
1993
 
                for node in doc.getElementsByTagName("album"):
1994
 
                        item = Album(_extract(node, "artist"), _extract(node, "name"), *self.auth_data)
1995
 
                        weight = _extract(node, "playcount")
1996
 
                        list.append(TopItem(item, weight))
1997
 
                
1998
 
                return list
1999
 
 
2000
 
        def get_weekly_track_charts(self, from_date = None, to_date = None):
2001
 
                """Returns the weekly track charts for the week starting from the from_date value to the to_date value."""
2002
 
                
2003
 
                params = self._get_params()
2004
 
                if from_date and to_date:
2005
 
                        params["from"] = from_date
2006
 
                        params["to"] = to_date
2007
 
                
2008
 
                doc = self._request("group.getWeeklyTrackChart", True, params)
2009
 
                
2010
 
                list = []
2011
 
                for node in doc.getElementsByTagName("track"):
2012
 
                        item = Track(_extract(node, "artist"), _extract(node, "name"), *self.auth_data)
2013
 
                        weight = _extract(node, "playcount")
2014
 
                        list.append(TopItem(item, weight))
2015
 
                
2016
 
                return list
2017
 
                
2018
 
        def get_url(self, domain_name = DOMAIN_ENGLISH):
2019
 
                """Returns the url of the group page on Last.fm. 
2020
 
                * domain_name: Last.fm's language domain. Possible values:
2021
 
                  o DOMAIN_ENGLISH
2022
 
                  o DOMAIN_GERMAN
2023
 
                  o DOMAIN_SPANISH
2024
 
                  o DOMAIN_FRENCH
2025
 
                  o DOMAIN_ITALIAN
2026
 
                  o DOMAIN_POLISH
2027
 
                  o DOMAIN_PORTUGUESE
2028
 
                  o DOMAIN_SWEDISH
2029
 
                  o DOMAIN_TURKISH
2030
 
                  o DOMAIN_RUSSIAN
2031
 
                  o DOMAIN_JAPANESE
2032
 
                  o DOMAIN_CHINESE 
2033
 
                """
2034
 
                
2035
 
                url = 'http://%(domain)s/group/%(name)s'
2036
 
                
2037
 
                name = _get_url_safe(self.get_name())
2038
 
                
2039
 
                return url %{'domain': domain_name, 'name': name}
2040
 
 
2041
 
class XSPF(_BaseObject):
2042
 
        "A Last.fm XSPF playlist."""
2043
 
        
2044
 
        def __init__(self, uri, api_key, api_secret, session_key):
2045
 
                _BaseObject.__init__(self, api_key, api_secret, session_key)
2046
 
                
2047
 
                self.uri = uri
2048
 
        
2049
 
        def _get_params(self):
2050
 
                return {'playlistURL': self.get_uri()}
2051
 
        
2052
 
        def __repr__(self):
2053
 
                return self.get_uri()
2054
 
        
2055
 
        def __eq__(self, other):
2056
 
                return self.get_uri() == other.get_uri()
2057
 
        
2058
 
        def __ne__(self, other):
2059
 
                return self.get_uri() != other.get_uri()
2060
 
        
2061
 
        def get_uri(self):
2062
 
                """Returns the Last.fm playlist URI. """
2063
 
                
2064
 
                return self.uri
2065
 
        
2066
 
        def get_tracks(self):
2067
 
                """Returns the tracks on this playlist."""
2068
 
                
2069
 
                doc = self._request('playlist.fetch', True)
2070
 
                
2071
 
                list = []
2072
 
                for n in doc.getElementsByTagName('track'):
2073
 
                        title = _extract(n, 'title')
2074
 
                        artist = _extract(n, 'creator')
2075
 
                        
2076
 
                        list.append(Track(artist, title, *self.auth_data))
2077
 
                
2078
 
                return list
2079
 
 
2080
 
class User(_BaseObject):
2081
 
        """A Last.fm user."""
2082
 
        
2083
 
        def __init__(self, user_name, api_key, api_secret, session_key):
2084
 
                _BaseObject.__init__(self, api_key, api_secret, session_key)
2085
 
                
2086
 
                self.name = user_name
2087
 
                
2088
 
                self._past_events_index = 0
2089
 
                self._recommended_events_index = 0
2090
 
                self._recommended_artists_index = 0
2091
 
        
2092
 
        def __repr__(self):
2093
 
                return self.get_name()
2094
 
        
2095
 
        def __eq__(self, another):
2096
 
                return self.get_name() == another.get_name()
2097
 
        
2098
 
        def __ne__(self, another):
2099
 
                return self.get_name() != another.get_name()
2100
 
        
2101
 
        def _get_params(self):
2102
 
                return {"user": self.get_name()}
2103
 
                
2104
 
        def get_name(self):
2105
 
                """Returns the nuser name."""
2106
 
                
2107
 
                return self.name
2108
 
        
2109
 
        def get_upcoming_events(self):
2110
 
                """Returns all the upcoming events for this user. """
2111
 
                
2112
 
                doc = self._request('user.getEvents', True)
2113
 
                
2114
 
                ids = _extract_all(doc, 'id')
2115
 
                events = []
2116
 
                
2117
 
                for id in ids:
2118
 
                        events.append(Event(id, *self.auth_data))
2119
 
                
2120
 
                return events
2121
 
        
2122
 
        def get_friends(self, limit = None):
2123
 
                """Returns a list of the user's friends. """
2124
 
                
2125
 
                params = self._get_params()
2126
 
                if limit:
2127
 
                        params['limit'] = unicode(limit)
2128
 
                
2129
 
                doc = self._request('user.getFriends', True, params)
2130
 
                
2131
 
                names = _extract_all(doc, 'name')
2132
 
                
2133
 
                list = []
2134
 
                for name in names:
2135
 
                        list.append(User(name, *self.auth_data))
2136
 
                
2137
 
                return list
2138
 
        
2139
 
        def get_loved_tracks(self):
2140
 
                """Returns the last 50 tracks loved by this user. """
2141
 
                
2142
 
                doc = self._request('user.getLovedTracks', True)
2143
 
                
2144
 
                list = []
2145
 
                for track in doc.getElementsByTagName('track'):
2146
 
                        title = _extract(track, 'name', 0)
2147
 
                        artist = _extract(track, 'name', 1)
2148
 
                        
2149
 
                        list.append(Track(artist, title, *self.auth_data))
2150
 
                
2151
 
                return list
2152
 
        
2153
 
        def get_neighbours(self, limit = None):
2154
 
                """Returns a list of the user's friends."""
2155
 
                
2156
 
                params = self._get_params()
2157
 
                if limit:
2158
 
                        params['limit'] = unicode(limit)
2159
 
                
2160
 
                doc = self._request('user.getNeighbours', True, params)
2161
 
                
2162
 
                list = []
2163
 
                names = _extract_all(doc, 'name')
2164
 
                
2165
 
                for name in names:
2166
 
                        list.append(User(name, *self.auth_data))
2167
 
                
2168
 
                return list
2169
 
        
2170
 
        def _get_past_events_pagecount(self):
2171
 
                """Returns the number of pages in the past events."""
2172
 
                
2173
 
                params = self._get_params()
2174
 
                params["page"] = str(self._past_events_index)
2175
 
                doc = self._request("user.getPastEvents", True, params)
2176
 
                
2177
 
                return _number(doc.getElementsByTagName("events")[0].getAttribute("totalPages"))
2178
 
        
2179
 
        def is_end_of_past_events(self):
2180
 
                """Returns True if the end of Past Events was reached."""
2181
 
                
2182
 
                return self._past_events_index >= self._get_past_events_pagecount()
2183
 
                
2184
 
        def get_past_events_page(self, ):
2185
 
                """Retruns a paginated list of all events a user has attended in the past.
2186
 
                
2187
 
                Example:
2188
 
                --------
2189
 
                
2190
 
                while not user.is_end_of_past_events():
2191
 
                        print user.get_past_events_page()
2192
 
                
2193
 
                """
2194
 
                
2195
 
                self._past_events_index += 1
2196
 
                params = self._get_params()
2197
 
                params["page"] = str(self._past_events_index)
2198
 
                
2199
 
                doc = self._request('user.getPastEvents', True, params)
2200
 
                
2201
 
                list = []
2202
 
                for id in _extract_all(doc, 'id'):
2203
 
                        list.append(Event(id, *self.auth_data))
2204
 
                
2205
 
                return list
2206
 
                                
2207
 
        def get_playlists(self):
2208
 
                """Returns a list of Playlists that this user owns."""
2209
 
                
2210
 
                doc = self._request("user.getPlaylists", True)
2211
 
                
2212
 
                playlists = []
2213
 
                for id in _extract_all(doc, "id"):
2214
 
                        playlists.append(Playlist(self.get_name(), id, *self.auth_data))
2215
 
                
2216
 
                return playlists
2217
 
        
2218
 
        def get_now_playing(self):
2219
 
                """Returns the currently playing track, or None if nothing is playing. """
2220
 
                
2221
 
                params = self._get_params()
2222
 
                params['limit'] = '1'
2223
 
                
2224
 
                list = []
2225
 
                
2226
 
                doc = self._request('user.getRecentTracks', False, params)
2227
 
                
2228
 
                e = doc.getElementsByTagName('track')[0]
2229
 
                
2230
 
                if not e.hasAttribute('nowplaying'):
2231
 
                        return None
2232
 
                
2233
 
                artist = _extract(e, 'artist')
2234
 
                title = _extract(e, 'name')
2235
 
                
2236
 
                return Track(artist, title, *self.auth_data)
2237
 
 
2238
 
 
2239
 
        def get_recent_tracks(self, limit = None):
2240
 
                """Returns this user's recent listened-to tracks as
2241
 
                a sequence of PlayedTrack objects.
2242
 
                Use extract_items() with the return of this function to
2243
 
                get only a sequence of Track objects with no playback dates. """
2244
 
                
2245
 
                params = self._get_params()
2246
 
                if limit:
2247
 
                        params['limit'] = unicode(limit)
2248
 
                
2249
 
                doc = self._request('user.getRecentTracks', False, params)
2250
 
                
2251
 
                list = []
2252
 
                for track in doc.getElementsByTagName('track'):
2253
 
                        title = _extract(track, "name")
2254
 
                        artist = _extract(track, "artist")
2255
 
                        date = _extract(track, "date")
2256
 
                        timestamp = track.getElementsByTagName("date")[0].getAttribute("uts")
2257
 
                        
2258
 
                        if track.hasAttribute('nowplaying'):
2259
 
                                continue        #to prevent the now playing track from sneaking in here
2260
 
                                
2261
 
                        list.append(PlayedTrack(Track(artist, title, *self.auth_data), date, timestamp))
2262
 
                
2263
 
                return list
2264
 
        
2265
 
        def get_top_albums(self, period = PERIOD_OVERALL):
2266
 
                """Returns the top albums played by a user. 
2267
 
                * period: The period of time. Possible values:
2268
 
                  o PERIOD_OVERALL
2269
 
                  o PERIOD_3MONTHS
2270
 
                  o PERIOD_6MONTHS
2271
 
                  o PERIOD_12MONTHS 
2272
 
                """
2273
 
                
2274
 
                params = self._get_params()
2275
 
                params['period'] = period
2276
 
                
2277
 
                doc = self._request('user.getTopAlbums', True, params)
2278
 
                
2279
 
                list = []
2280
 
                for album in doc.getElementsByTagName('album'):
2281
 
                        name = _extract(album, 'name')
2282
 
                        artist = _extract(album, 'name', 1)
2283
 
                        playcount = _extract(album, "playcount")
2284
 
                        
2285
 
                        list.append(TopItem(Album(artist, name, *self.auth_data), playcount))
2286
 
                
2287
 
                return list
2288
 
        
2289
 
        def get_top_artists(self, period = PERIOD_OVERALL):
2290
 
                """Returns the top artists played by a user. 
2291
 
                * period: The period of time. Possible values:
2292
 
                  o PERIOD_OVERALL
2293
 
                  o PERIOD_3MONTHS
2294
 
                  o PERIOD_6MONTHS
2295
 
                  o PERIOD_12MONTHS 
2296
 
                """
2297
 
                
2298
 
                params = self._get_params()
2299
 
                params['period'] = period
2300
 
                
2301
 
                doc = self._request('user.getTopArtists', True, params)
2302
 
                
2303
 
                list = []
2304
 
                for node in doc.getElementsByTagName('artist'):
2305
 
                        name = _extract(node, 'name')
2306
 
                        playcount = _extract(node, "playcount")
2307
 
                        
2308
 
                        list.append(TopItem(Artist(name, *self.auth_data), playcount))
2309
 
                
2310
 
                return list
2311
 
        
2312
 
        def get_top_tags(self, limit = None):
2313
 
                """Returns a sequence of the top tags used by this user with their counts as (Tag, tagcount). 
2314
 
                * limit: The limit of how many tags to return. 
2315
 
                """
2316
 
                
2317
 
                doc = self._request("user.getTopTags", True)
2318
 
                
2319
 
                list = []
2320
 
                for node in doc.getElementsByTagName("tag"):
2321
 
                        list.append(TopItem(Tag(_extract(node, "name"), *self.auth_data), _extract(node, "count")))
2322
 
                
2323
 
                return list
2324
 
        
2325
 
        def get_top_tracks(self, period = PERIOD_OVERALL):
2326
 
                """Returns the top tracks played by a user. 
2327
 
                * period: The period of time. Possible values:
2328
 
                  o PERIOD_OVERALL
2329
 
                  o PERIOD_3MONTHS
2330
 
                  o PERIOD_6MONTHS
2331
 
                  o PERIOD_12MONTHS 
2332
 
                """
2333
 
                
2334
 
                params = self._get_params()
2335
 
                params['period'] = period
2336
 
                
2337
 
                doc = self._request('user.getTopTracks', True, params)
2338
 
                
2339
 
                list = []
2340
 
                for track in doc.getElementsByTagName('track'):
2341
 
                        name = _extract(track, 'name')
2342
 
                        artist = _extract(track, 'name', 1)
2343
 
                        playcount = _extract(track, "playcount")
2344
 
                        
2345
 
                        list.append(TopItem(Track(artist, name, *self.auth_data), playcount))
2346
 
                
2347
 
                return list
2348
 
        
2349
 
        def get_weekly_chart_dates(self):
2350
 
                """Returns a list of From and To tuples for the available charts."""
2351
 
                
2352
 
                doc = self._request("user.getWeeklyChartList", True)
2353
 
                
2354
 
                list = []
2355
 
                for node in doc.getElementsByTagName("chart"):
2356
 
                        list.append( (node.getAttribute("from"), node.getAttribute("to")) )
2357
 
                
2358
 
                return list
2359
 
        
2360
 
        def get_weekly_artist_charts(self, from_date = None, to_date = None):
2361
 
                """Returns the weekly artist charts for the week starting from the from_date value to the to_date value."""
2362
 
                
2363
 
                params = self._get_params()
2364
 
                if from_date and to_date:
2365
 
                        params["from"] = from_date
2366
 
                        params["to"] = to_date
2367
 
                
2368
 
                doc = self._request("user.getWeeklyArtistChart", True, params)
2369
 
                
2370
 
                list = []
2371
 
                for node in doc.getElementsByTagName("artist"):
2372
 
                        item = Artist(_extract(node, "name"), *self.auth_data)
2373
 
                        weight = _extract(node, "playcount")
2374
 
                        list.append(TopItem(item, weight))
2375
 
                
2376
 
                return list
2377
 
 
2378
 
        def get_weekly_album_charts(self, from_date = None, to_date = None):
2379
 
                """Returns the weekly album charts for the week starting from the from_date value to the to_date value."""
2380
 
                
2381
 
                params = self._get_params()
2382
 
                if from_date and to_date:
2383
 
                        params["from"] = from_date
2384
 
                        params["to"] = to_date
2385
 
                
2386
 
                doc = self._request("user.getWeeklyAlbumChart", True, params)
2387
 
                
2388
 
                list = []
2389
 
                for node in doc.getElementsByTagName("album"):
2390
 
                        item = Album(_extract(node, "artist"), _extract(node, "name"), *self.auth_data)
2391
 
                        weight = _extract(node, "playcount")
2392
 
                        list.append(TopItem(item, weight))
2393
 
                
2394
 
                return list
2395
 
 
2396
 
        def get_weekly_track_charts(self, from_date = None, to_date = None):
2397
 
                """Returns the weekly track charts for the week starting from the from_date value to the to_date value."""
2398
 
                
2399
 
                params = self._get_params()
2400
 
                if from_date and to_date:
2401
 
                        params["from"] = from_date
2402
 
                        params["to"] = to_date
2403
 
                
2404
 
                doc = self._request("user.getWeeklyTrackChart", True, params)
2405
 
                
2406
 
                list = []
2407
 
                for node in doc.getElementsByTagName("track"):
2408
 
                        item = Track(_extract(node, "artist"), _extract(node, "name"), *self.auth_data)
2409
 
                        weight = _extract(node, "playcount")
2410
 
                        list.append(TopItem(item, weight))
2411
 
                
2412
 
                return list
2413
 
        
2414
 
        def compare_with_user(self, user, shared_artists_limit = None):
2415
 
                """Compare this user with another Last.fm user.
2416
 
                Returns a sequence (tasteometer_score, (shared_artist1, shared_artist2, ...))
2417
 
                user: A User object or a username string/unicode object.
2418
 
                """
2419
 
                
2420
 
                if isinstance(user, User):
2421
 
                        user = user.get_name()
2422
 
                
2423
 
                params = self._get_params()
2424
 
                if shared_artists_limit:
2425
 
                        params['limit'] = unicode(shared_artists_limit)
2426
 
                params['type1'] = 'user'
2427
 
                params['type2'] = 'user'
2428
 
                params['value1'] = self.get_name()
2429
 
                params['value2'] = user
2430
 
                
2431
 
                doc = _Request('tasteometer.compare', params, *self.auth_data).execute()
2432
 
                
2433
 
                score = _extract(doc, 'score')
2434
 
                
2435
 
                artists = doc.getElementsByTagName('artists')[0]
2436
 
                shared_artists_names = _extract_all(artists, 'name')
2437
 
                
2438
 
                shared_artists_list = []
2439
 
                
2440
 
                for name in shared_artists_names:
2441
 
                        shared_artists_list.append(Artist(name, *self.auth_data))
2442
 
                
2443
 
                return (score, shared_artists_list)
2444
 
        
2445
 
        def getRecommendedEvents(self, page = None, limit = None):
2446
 
                """Returns a paginated list of all events recommended to a user by Last.fm, based on their listening profile.
2447
 
                * page: The page number of results to return.
2448
 
                * limit: The limit of events to return.
2449
 
                """
2450
 
                
2451
 
                params = self._get_params()
2452
 
                if page:
2453
 
                        params['page'] = unicode(page)
2454
 
                if limit:
2455
 
                        params['limit'] = unicode(limit)
2456
 
                
2457
 
                doc = _Request('user.getRecommendedEvents', params, *self.auth_data).execute()
2458
 
                
2459
 
                ids = _extract_all(doc, 'id')
2460
 
                list = []
2461
 
                for id in ids:
2462
 
                        list.append(Event(id, *self.auth_data))
2463
 
                
2464
 
                return list
2465
 
        
2466
 
        def get_url(self, domain_name = DOMAIN_ENGLISH):
2467
 
                """Returns the url of the user page on Last.fm. 
2468
 
                * domain_name: Last.fm's language domain. Possible values:
2469
 
                  o DOMAIN_ENGLISH
2470
 
                  o DOMAIN_GERMAN
2471
 
                  o DOMAIN_SPANISH
2472
 
                  o DOMAIN_FRENCH
2473
 
                  o DOMAIN_ITALIAN
2474
 
                  o DOMAIN_POLISH
2475
 
                  o DOMAIN_PORTUGUESE
2476
 
                  o DOMAIN_SWEDISH
2477
 
                  o DOMAIN_TURKISH
2478
 
                  o DOMAIN_RUSSIAN
2479
 
                  o DOMAIN_JAPANESE
2480
 
                  o DOMAIN_CHINESE 
2481
 
                """
2482
 
                url = 'http://%(domain)s/user/%(name)s'
2483
 
                
2484
 
                name = _get_url_safe(self.get_name())
2485
 
                
2486
 
                return url %{'domain': domain_name, 'name': name}
2487
 
 
2488
 
        def get_library(self):
2489
 
                """Returns the associated Library object. """
2490
 
                
2491
 
                return Library(self, *self.auth_data)
2492
 
 
2493
 
class AuthenticatedUser(User):
2494
 
        def __init__(self, api_key, api_secret, session_key):
2495
 
                User.__init__(self, "", api_key, api_secret, session_key);
2496
 
        
2497
 
        def _get_params(self):
2498
 
                return {}
2499
 
                
2500
 
        def get_name(self):
2501
 
                """Returns the name of the authenticated user."""
2502
 
                
2503
 
                doc = self._request("user.getInfo", True)
2504
 
                
2505
 
                self.name = _extract(doc, "name")
2506
 
                return self.name
2507
 
        
2508
 
        def get_id(self):
2509
 
                """Returns the user id."""
2510
 
                
2511
 
                doc = self._request("user.getInfo", True)
2512
 
                
2513
 
                return _extract(doc, "id")
2514
 
        
2515
 
        def get_image_url(self):
2516
 
                """Returns the user's avatar."""
2517
 
                        
2518
 
                doc = self._request("user.getInfo", True)
2519
 
                
2520
 
                return _extract(doc, "image")
2521
 
        
2522
 
        def get_language(self):
2523
 
                """Returns the language code of the language used by the user."""
2524
 
                
2525
 
                doc = self._request("user.getInfo", True)
2526
 
                
2527
 
                return _extract(doc, "lang")
2528
 
        
2529
 
        def get_country(self):
2530
 
                """Returns the name of the country of the user."""
2531
 
                
2532
 
                doc = self._request("user.getInfo", True)
2533
 
                
2534
 
                return Country(_extract(doc, "country"), *self.auth_data)
2535
 
        
2536
 
        def get_age(self):
2537
 
                """Returns the user's age."""
2538
 
                
2539
 
                doc = self._request("user.getInfo", True)
2540
 
                
2541
 
                return _number(_extract(doc, "age"))
2542
 
        
2543
 
        def get_gender(self):
2544
 
                """Returns the user's gender. Either USER_MALE or USER_FEMALE."""
2545
 
                
2546
 
                doc = self._request("user.getInfo", True)
2547
 
                
2548
 
                return _extract(doc, "gender")
2549
 
                
2550
 
                if value == 'm':
2551
 
                        return USER_MALE
2552
 
                elif value == 'f':
2553
 
                        return USER_FEMALE
2554
 
                
2555
 
                return None
2556
 
        
2557
 
        def is_subscriber(self):
2558
 
                """Returns whether the user is a subscriber or not. True or False."""
2559
 
                
2560
 
                doc = self._request("user.getInfo", True)
2561
 
                
2562
 
                return _extract(doc, "subscriber") == "1"
2563
 
        
2564
 
        def get_playcount(self):
2565
 
                """Returns the user's playcount so far."""
2566
 
                
2567
 
                doc = self._request("user.getInfo", True)
2568
 
                
2569
 
                return _number(_extract(doc, "playcount"))
2570
 
                
2571
 
        def _get_recommended_events_pagecount(self):
2572
 
                """Returns the number of pages in the past events."""
2573
 
                
2574
 
                params = self._get_params()
2575
 
                params["page"] = str(self._recommended_events_index)
2576
 
                doc = self._request("user.getRecommendedEvents", True, params)
2577
 
                
2578
 
                return _number(doc.getElementsByTagName("events")[0].getAttribute("totalPages"))
2579
 
        
2580
 
        def is_end_of_recommended_events(self):
2581
 
                """Returns True if the end of Past Events was reached."""
2582
 
                
2583
 
                return self._recommended_events_index >= self._get_recommended_events_pagecount()
2584
 
                
2585
 
        def get_recommended_events_page(self, ):
2586
 
                """Retruns a paginated list of all events a user has attended in the past.
2587
 
                
2588
 
                Example:
2589
 
                --------
2590
 
                
2591
 
                while not user.is_end_of_recommended_events():
2592
 
                        print user.get_recommended_events_page()
2593
 
                
2594
 
                """
2595
 
                
2596
 
                self._recommended_events_index += 1
2597
 
                params = self._get_params()
2598
 
                params["page"] = str(self._recommended_events_index)
2599
 
                
2600
 
                doc = self._request('user.getRecommendedEvents', True, params)
2601
 
                
2602
 
                list = []
2603
 
                for id in _extract_all(doc, 'id'):
2604
 
                        list.append(Event(id, *self.auth_data))
2605
 
                
2606
 
                return list
2607
 
 
2608
 
        def _get_recommended_artists_pagecount(self):
2609
 
                """Returns the number of pages in the past artists."""
2610
 
                
2611
 
                params = self._get_params()
2612
 
                params["page"] = str(self._recommended_artists_index)
2613
 
                doc = self._request("user.getRecommendedArtists", True, params)
2614
 
                
2615
 
                return _number(doc.getElementsByTagName("recommendations")[0].getAttribute("totalPages"))
2616
 
        
2617
 
        def is_end_of_recommended_artists(self):
2618
 
                """Returns True if the end of Past Artists was reached."""
2619
 
                
2620
 
                return self._recommended_artists_index >= self._get_recommended_artists_pagecount()
2621
 
                
2622
 
        def get_recommended_artists_page(self, ):
2623
 
                """Retruns a paginated list of all artists a user has attended in the past.
2624
 
                
2625
 
                Example:
2626
 
                --------
2627
 
                
2628
 
                while not user.is_end_of_recommended_artists():
2629
 
                        print user.get_recommended_artists_page()
2630
 
                
2631
 
                """
2632
 
                
2633
 
                self._recommended_artists_index += 1
2634
 
                params = self._get_params()
2635
 
                params["page"] = str(self._recommended_artists_index)
2636
 
                
2637
 
                doc = self._request('user.getRecommendedArtists', True, params)
2638
 
                
2639
 
                list = []
2640
 
                for name in _extract_all(doc, 'name'):
2641
 
                        list.append(Artist(name, *self.auth_data))
2642
 
                
2643
 
                return list
2644
 
        
2645
 
class _Search(_BaseObject):
2646
 
        """An abstract class. Use one of its derivatives."""
2647
 
        
2648
 
        def __init__(self, ws_prefix, search_terms, api_key, api_secret, session_key):
2649
 
                _BaseObject.__init__(self, api_key, api_secret, session_key)
2650
 
                
2651
 
                self._ws_prefix = ws_prefix
2652
 
                self.search_terms = search_terms
2653
 
                
2654
 
                self._last_page_index = 0
2655
 
        
2656
 
        def _get_params(self):
2657
 
                params = {}
2658
 
                
2659
 
                for key in self.search_terms.keys():
2660
 
                        params[key] = self.search_terms[key]
2661
 
                
2662
 
                return params
2663
 
        
2664
 
        def get_total_result_count(self):
2665
 
                """Returns the total count of all the results."""
2666
 
                
2667
 
                doc = self._request(self._ws_prefix + ".search", True)
2668
 
                
2669
 
                return _extract(doc, "opensearch:totalResults")
2670
 
        
2671
 
        def _retreive_page(self, page_index):
2672
 
                """Returns the node of matches to be processed"""
2673
 
                
2674
 
                params = self._get_params()
2675
 
                params["page"] = str(page_index)
2676
 
                doc = self._request(self._ws_prefix + ".search", True, params)
2677
 
                
2678
 
                return doc.getElementsByTagName(self._ws_prefix + "matches")[0]
2679
 
        
2680
 
        def _retrieve_next_page(self):
2681
 
                self._last_page_index += 1
2682
 
                return self._retreive_page(self._last_page_index)
2683
 
 
2684
 
class AlbumSearch(_Search):
2685
 
        """Search for an album by name."""
2686
 
        
2687
 
        def __init__(self, album_name, api_key, api_secret, session_key):
2688
 
                
2689
 
                _Search.__init__(self, "album", {"album": album_name}, api_key, api_secret, session_key)
2690
 
        
2691
 
        def get_next_page(self):
2692
 
                """Returns the next page of results as a sequence of Album objects."""
2693
 
                
2694
 
                master_node = self._retrieve_next_page()
2695
 
                
2696
 
                list = []
2697
 
                for node in master_node.getElementsByTagName("album"):
2698
 
                        list.append(Album(_extract(node, "artist"), _extract(node, "name"), *self.auth_data))
2699
 
                
2700
 
                return list
2701
 
 
2702
 
class ArtistSearch(_Search):
2703
 
        """Search for an artist by artist name."""
2704
 
        
2705
 
        def __init__(self, artist_name, api_key, api_secret, session_key):
2706
 
                _Search.__init__(self, "artist", {"artist": artist_name}, api_key, api_secret, session_key)
2707
 
 
2708
 
        def get_next_page(self):
2709
 
                """Returns the next page of results as a sequence of Artist objects."""
2710
 
                
2711
 
                master_node = self._retrieve_next_page()
2712
 
                
2713
 
                list = []
2714
 
                for node in master_node.getElementsByTagName("artist"):
2715
 
                        list.append(Artist(_extract(node, "name"), *self.auth_data))
2716
 
                
2717
 
                return list
2718
 
 
2719
 
class TagSearch(_Search):
2720
 
        """Search for a tag by tag name."""
2721
 
        
2722
 
        def __init__(self, tag_name, api_key, api_secret, session_key):
2723
 
                
2724
 
                _Search.__init__(self, "tag", {"tag": tag_name}, api_key, api_secret, session_key)
2725
 
                
2726
 
        def get_next_page(self):
2727
 
                """Returns the next page of results as a sequence of Tag objects."""
2728
 
                
2729
 
                master_node = self._retrieve_next_page()
2730
 
                
2731
 
                list = []
2732
 
                for node in master_node.getElementsByTagName("tag"):
2733
 
                        list.append(Tag(_extract(node, "name"), *self.auth_data))
2734
 
                
2735
 
                return list
2736
 
 
2737
 
class TrackSearch(_Search):
2738
 
        """Search for a track by track title. If you don't wanna narrow the results down
2739
 
        by specifying the artist name, set it to empty string."""
2740
 
        
2741
 
        def __init__(self, artist_name, track_title, api_key, api_secret, session_key):
2742
 
                
2743
 
                _Search.__init__(self, "track", {"track": track_title, "artist": artist_name}, api_key, api_secret, session_key)
2744
 
 
2745
 
        def get_next_page(self):
2746
 
                """Returns the next page of results as a sequence of Track objects."""
2747
 
                
2748
 
                master_node = self._retrieve_next_page()
2749
 
                
2750
 
                list = []
2751
 
                for node in master_node.getElementsByTagName("track"):
2752
 
                        list.append(Track(_extract(node, "artist"), _extract(node, "name"), *self.auth_data))
2753
 
                
2754
 
                return list
2755
 
 
2756
 
class VenueSearch(_Search):
2757
 
        """Search for a venue by its name. If you don't wanna narrow the results down
2758
 
        by specifying a country, set it to empty string."""
2759
 
        
2760
 
        def __init__(self, venue_name, country_name, api_key, api_secret, session_key):
2761
 
                
2762
 
                _Search.__init__(self, "venue", {"venue": venue_name, "country": country_name}, api_key, api_secret, session_key)
2763
 
 
2764
 
        def get_next_page(self):
2765
 
                """Returns the next page of results as a sequence of Track objects."""
2766
 
                
2767
 
                master_node = self._retrieve_next_page()
2768
 
                
2769
 
                list = []
2770
 
                for node in master_node.getElementsByTagName("venue"):
2771
 
                        list.append(Venue(_extract(node, "id"), *self.auth_data))
2772
 
                
2773
 
                return list
2774
 
 
2775
 
class Venue(_BaseObject):
2776
 
        """A venue where events are held."""
2777
 
        
2778
 
        # TODO: waiting for a venue.getInfo web service to use.
2779
 
        
2780
 
        def __init__(self, id, api_key, api_secret, session_key):
2781
 
                _BaseObject.__init__(self, api_key, api_secret, session_key)
2782
 
                
2783
 
                self.id = _number(id)
2784
 
        
2785
 
        def __repr__(self):
2786
 
                return "Venue #" + str(self.id)
2787
 
        
2788
 
        def __eq__(self, other):
2789
 
                return self.get_id() == other.get_id()
2790
 
        
2791
 
        def _get_params(self):
2792
 
                return {"venue": self.get_id()}
2793
 
                
2794
 
        def get_id(self):
2795
 
                """Returns the id of the venue."""
2796
 
                
2797
 
                return self.id
2798
 
        
2799
 
        def get_upcoming_events(self):
2800
 
                """Returns the upcoming events in this venue."""
2801
 
                
2802
 
                doc = self._request("venue.getEvents", True)
2803
 
                
2804
 
                list = []
2805
 
                for node in doc.getElementsByTagName("event"):
2806
 
                        list.append(Event(_extract(node, "id"), *self.auth_data))
2807
 
                
2808
 
                return list
2809
 
        
2810
 
        def get_past_events(self):
2811
 
                """Returns the past events held in this venue."""
2812
 
                
2813
 
                doc = self._request("venue.getEvents", True)
2814
 
                
2815
 
                list = []
2816
 
                for node in doc.getElementsByTagName("event"):
2817
 
                        list.append(Event(_extract(node, "id"), *self.auth_data))
2818
 
                
2819
 
                return list
2820
 
                
2821
 
def create_new_playlist(title, description, api_key, api_secret, session_key):
2822
 
        """Creates a playlist for the authenticated user and returns it.
2823
 
        * title: The title of the new playlist.
2824
 
        * description: The description of the new playlist.
2825
 
        """
2826
 
        
2827
 
        params = dict()
2828
 
        params['title'] = unicode(title)
2829
 
        params['description'] = unicode(description)
2830
 
        
2831
 
        doc = _Request('playlist.create', params, api_key, api_secret, session_key).execute()
2832
 
        
2833
 
        id = doc.getElementsByTagName("id")[0].firstChild.data
2834
 
        user = doc.getElementsByTagName('playlists')[0].getAttribute('user')
2835
 
        
2836
 
        return Playlist(user, id, api_key, api_secret, session_key)
2837
 
 
2838
 
def get_authenticated_user(api_key, api_secret, session_key):
2839
 
        """Returns the authenticated user."""
2840
 
        
2841
 
        return AuthenticatedUser(api_key, api_secret, session_key)
2842
 
 
2843
 
def md5(text):
2844
 
        """Returns the md5 hash of a string."""
2845
 
        
2846
 
        hash = hashlib.md5()
2847
 
        hash.update(text.encode("utf-8"))
2848
 
        
2849
 
        return hash.hexdigest()
2850
 
 
2851
 
def enable_proxy(host, port):
2852
 
        """Enable a default web proxy."""
2853
 
        
2854
 
        global __proxy
2855
 
        global __proxy_enabled
2856
 
        
2857
 
        __proxy = [host, _number(port)]
2858
 
        __proxy_enabled = True
2859
 
 
2860
 
def disable_proxy():
2861
 
        """Disable using the web proxy."""
2862
 
        
2863
 
        global __proxy_enabled
2864
 
        
2865
 
        __proxy_enabled = False
2866
 
 
2867
 
def is_proxy_enabled():
2868
 
        """Returns True if a web proxy is enabled."""
2869
 
        
2870
 
        global __proxy_enabled
2871
 
        
2872
 
        return __proxy_enabled
2873
 
 
2874
 
def _get_proxy():
2875
 
        """Returns proxy details."""
2876
 
        
2877
 
        global __proxy
2878
 
        
2879
 
        return __proxy
2880
 
 
2881
 
def async_call(sender, call, callback = None, call_args = None, callback_args = None):
2882
 
        """This is the function for setting up an asynchronous operation.
2883
 
        * call: The function to call asynchronously.
2884
 
        * callback: The function to call after the operation is complete, Its prototype has to be like:
2885
 
                callback(sender, output[, param1, param3, ... ])
2886
 
        * call_args: A sequence of args to be passed to call.
2887
 
        * callback_args: A sequence of args to be passed to callback.
2888
 
        """
2889
 
        
2890
 
        thread = _ThreadedCall(sender, call, call_args, callback, callback_args)
2891
 
        thread.start()
2892
 
 
2893
 
def enable_caching(cache_filename = None):
2894
 
        """Enables caching request-wide for all cachable calls. Uses a shelve.DbfilenameShelf object.
2895
 
        * cache_filename: A filename for the db. Defaults to a temporary filename in the tmp directory.
2896
 
        """
2897
 
        
2898
 
        global __cache_shelf
2899
 
        global __cache_filename
2900
 
        
2901
 
        if not cache_filename:
2902
 
                cache_filename = tempfile.mktemp(prefix="pylast_tmp_")
2903
 
                
2904
 
        __cache_filename = cache_filename
2905
 
        __cache_shelf = shelve.open(__cache_filename)
2906
 
                
2907
 
def disable_caching():
2908
 
        """Disables all caching features."""
2909
 
 
2910
 
        global __cache_shelf
2911
 
        __cache_shelf = None
2912
 
        
2913
 
def close_shelf():
2914
 
        global __cache_shelf
2915
 
        if __cache_shelf:
2916
 
                __cache_shelf.close()
2917
 
        __cache_shelf = None
2918
 
 
2919
 
def is_caching_enabled():
2920
 
        """Returns True if caching is enabled."""
2921
 
        
2922
 
        global __cache_shelf
2923
 
        return not (__cache_shelf == None)
2924
 
 
2925
 
def get_cache_filename():
2926
 
        """Returns filename of the cache db in use."""
2927
 
        
2928
 
        global __cache_filename
2929
 
        return __cache_filename
2930
 
 
2931
 
def get_cache_shelf():
2932
 
        """Returns the Shelf object used for caching."""
2933
 
        
2934
 
        global __cache_shelf
2935
 
        return __cache_shelf
2936
 
        
2937
 
def _extract(node, name, index = 0):
2938
 
        """Extracts a value from the xml string"""
2939
 
        
2940
 
        nodes = node.getElementsByTagName(name)
2941
 
        
2942
 
        if len(nodes):
2943
 
                if nodes[index].firstChild:
2944
 
                        return nodes[index].firstChild.data.strip()
2945
 
        else:
2946
 
                return None
2947
 
 
2948
 
def _extract_all(node, name, limit_count = None):
2949
 
        """Extracts all the values from the xml string. returning a list."""
2950
 
        
2951
 
        list = []
2952
 
        
2953
 
        for i in range(0, len(node.getElementsByTagName(name))):
2954
 
                if len(list) == limit_count:
2955
 
                        break
2956
 
                
2957
 
                list.append(_extract(node, name, i))
2958
 
        
2959
 
        return list
2960
 
 
2961
 
def _get_url_safe(text):
2962
 
        """Does all kinds of tricks on a text to make it safe to use in a url."""
2963
 
        
2964
 
        if type(text) == type(unicode()):
2965
 
                text = text
2966
 
        
2967
 
        return urllib.quote_plus(urllib.quote_plus(text)).lower()
2968
 
 
2969
 
def _number(string):
2970
 
        """Extracts an int from a string. Returns a 0 if None or an empty string was passed."""
2971
 
        
2972
 
        if not string:
2973
 
                return 0
2974
 
        elif string == "":
2975
 
                return 0
2976
 
        else:
2977
 
                return int(string)
2978
 
 
2979
 
def search_for_album(album_name, api_key, api_secret, session_key):
2980
 
        """Searches for an album by its name. Returns a AlbumSearch object.
2981
 
        Use get_next_page() to retreive sequences of results."""
2982
 
        
2983
 
        return AlbumSearch(album_name, api_key, api_secret, session_key)
2984
 
 
2985
 
def search_for_artist(artist_name, api_key, api_secret, session_key):
2986
 
        """Searches of an artist by its name. Returns a ArtistSearch object.
2987
 
        Use get_next_page() to retreive sequences of results."""
2988
 
        
2989
 
        return ArtistSearch(artist_name, api_key, api_secret, session_key)
2990
 
 
2991
 
def search_for_tag(tag_name, api_key, api_secret, session_key):
2992
 
        """Searches of a tag by its name. Returns a TagSearch object.
2993
 
        Use get_next_page() to retreive sequences of results."""
2994
 
        
2995
 
        return TagSearch(tag_name, api_key, api_secret, session_key)
2996
 
 
2997
 
def search_for_track(artist_name, track_name, api_key, api_secret, session_key):
2998
 
        """Searches of a track by its name and its artist. Set artist to an empty string if not available.
2999
 
        Returns a TrackSearch object.
3000
 
        Use get_next_page() to retreive sequences of results."""
3001
 
        
3002
 
        return TrackSearch(artist_name, track_name, api_key, api_secret, session_key)
3003
 
 
3004
 
def search_for_venue(venue_name, country_name, api_key, api_secret, session_key):
3005
 
        """Searches of a venue by its name and its country. Set country_name to an empty string if not available.
3006
 
        Returns a VenueSearch object.
3007
 
        Use get_next_page() to retreive sequences of results."""
3008
 
 
3009
 
        return VenueSearch(venue_name, country_name, api_key, api_secret, session_key)
3010
 
 
3011
 
def extract_items(topitems_or_libraryitems):
3012
 
        """Extracts a sequence of items from a sequence of TopItem or LibraryItem objects."""
3013
 
        
3014
 
        list = []
3015
 
        for i in topitems_or_libraryitems:
3016
 
                list.append(i.get_item())
3017
 
        
3018
 
        return list
3019
 
 
3020
 
def get_top_tags(api_key, api_secret, session_key):
3021
 
        """Returns a sequence of the most used Last.fm tags as a sequence of TopItem objects."""
3022
 
        
3023
 
        doc = _Request("tag.getTopTags", dict(), api_key, api_secret, session_key).execute(True)
3024
 
        list = []
3025
 
        for node in doc.getElementsByTagName("tag"):
3026
 
                tag = Tag(_extract(node, "name"), api_key, api_secret, session_key)
3027
 
                weight = _extract(node, "count")
3028
 
                
3029
 
                list.append(TopItem(tag, weight))
3030
 
        
3031
 
        return list
3032
 
 
3033
 
def get_track_by_mbid(mbid, api_key, api_secret, session_key):
3034
 
        """Looks up a track by its MusicBrainz ID."""
3035
 
        
3036
 
        params = {"mbid": unicode(mbid)}
3037
 
        
3038
 
        doc = _Request("track.getInfo", params, api_key, api_secret, session_key).execute(True)
3039
 
        
3040
 
        return Track(_extract(doc, "name", 1), _extract(doc, "name"), api_key, api_secret, session_key)
3041
 
 
3042
 
def get_artist_by_mbid(mbid, api_key, api_secret, session_key):
3043
 
        """Loooks up an artist by its MusicBrainz ID."""
3044
 
        
3045
 
        params = {"mbid": unicode(mbid)}
3046
 
        
3047
 
        doc = _Request("artist.getInfo", params, api_key, api_secret, session_key).execute(True)
3048
 
        
3049
 
        return Artist(_extract(doc, "name"), api_key, api_secret, session_key)
3050
 
 
3051
 
def get_album_by_mbid(mbid, api_key, api_secret, session_key):
3052
 
        """Looks up an album by its MusicBrainz ID."""
3053
 
        
3054
 
        params = {"mbid": unicode(mbid)}
3055
 
        
3056
 
        doc = _Request("album.getInfo", params, api_key, api_secret, session_key).execute(True)
3057
 
        
3058
 
        return Album(_extract(doc, "artist"), _extract(doc, "name"), api_key, api_secret, session_key)
3059
 
 
3060
 
def _delay_call():
3061
 
        """Makes sure that web service calls are at least a second apart."""
3062
 
        
3063
 
        global __last_call_time
3064
 
        
3065
 
        # delay time in seconds
3066
 
        DELAY_TIME = 1.0
3067
 
        now = time.time()
3068
 
                
3069
 
        if (now - __last_call_time) < DELAY_TIME:
3070
 
                time.sleep(1)
3071
 
        
3072
 
        __last_call_time = now
3073
 
 
3074
 
def clear_cache():
3075
 
        """Clears the cache data and starts fresh."""
3076
 
        
3077
 
        global __cache_dir
3078
 
        
3079
 
        
3080
 
        if not os.path.exists(__cache_dir):
3081
 
                return
3082
 
        
3083
 
        for file in os.listdir(__cache_dir):
3084
 
                os.remove(os.path.join(__cache_dir, file))
3085
 
 
3086
 
 
3087
 
 
3088
 
# ------------------------------------------------------------
3089
 
 
3090
 
class ScrobblingException(Exception):
3091
 
        def __inint__(self, message):
3092
 
                Exception.__init__(self)
3093
 
                self.message = message
3094
 
                
3095
 
        def __repr__(self):
3096
 
                return self.message
3097
 
 
3098
 
class BannedClient(ScrobblingException):
3099
 
        def __init__(self):
3100
 
                ScrobblingException.__init__(self, "This version of the client has been banned")
3101
 
 
3102
 
class BadAuthentication(ScrobblingException):
3103
 
        def __init__(self):
3104
 
                ScrobblingException.__init__(self, "Bad authentication token")
3105
 
 
3106
 
class BadTime(ScrobblingException):
3107
 
        def __init__(self):
3108
 
                ScrobblingException.__init__(self, "Time provided is not close enough to current time")
3109
 
 
3110
 
class BadSession(ScrobblingException):
3111
 
        def __init__(self):
3112
 
                ScrobblingException.__init__(self, "Bad session id, consider re-handshaking")
3113
 
 
3114
 
class _ScrobblerRequest(object):
3115
 
        
3116
 
        def __init__(self, url, params):
3117
 
                self.params = params
3118
 
                self.hostname = url[url.find("//") + 2:url.rfind("/")]
3119
 
                self.subdir = url[url.rfind("/"):]
3120
 
        
3121
 
        def execute(self):
3122
 
                """Returns a string response of this request."""
3123
 
                
3124
 
                connection = httplib.HTTPConnection(self.hostname)
3125
 
 
3126
 
                data = []
3127
 
                for name in self.params.keys():
3128
 
                        value = urllib.quote_plus(self.params[name])
3129
 
                        data.append('='.join((name, value)))
3130
 
                data = "&".join(data)
3131
 
                
3132
 
                headers = {
3133
 
                        "Content-type": "application/x-www-form-urlencoded",
3134
 
                        "Accept-Charset": "utf-8",
3135
 
                        "User-Agent": __name__ + "/" + __version__,
3136
 
                        "HOST": self.hostname
3137
 
                        }
3138
 
                
3139
 
                debug("Scrobbler Request:\n\tHOST :" + self.hostname + "\n\tSUBDIR: " + self.subdir +
3140
 
                        "\n\tDATA:" + repr(data) + "\n\tHEADERS: " + repr(headers))
3141
 
                connection.request("POST", self.subdir, data, headers)
3142
 
                response = connection.getresponse().read()
3143
 
                
3144
 
                self._check_response_for_errors(response)
3145
 
                debug("Scrobbler Response:\n\t" + response)
3146
 
                
3147
 
                return response
3148
 
                
3149
 
        def _check_response_for_errors(self, response):
3150
 
                """When passed a string response it checks for erros, raising
3151
 
                any exceptions as necessary."""
3152
 
                
3153
 
                lines = response.split("\n")
3154
 
                status_line = lines[0]
3155
 
                
3156
 
                if status_line == "OK":
3157
 
                        return
3158
 
                elif status_line == "BANNED":
3159
 
                        raise BannedClient()
3160
 
                elif status_line == "BADAUTH":
3161
 
                        raise BadAuthenticationException()
3162
 
                elif status_line == "BADTIME":
3163
 
                        raise BadTime()
3164
 
                elif status_line == "BADSESSION":
3165
 
                        raise BadSession()
3166
 
                elif status_line.startswith("FAILED "):
3167
 
                        reason = status_line[status_line.find("FAILED ")+len("FAILED "):]
3168
 
                        raise ScrobblingException(reason)
3169
 
        
3170
 
class Scrobbler(object):
3171
 
        """A class for scrobbling tracks to Last.fm"""
3172
 
        
3173
 
        session_id = None
3174
 
        nowplaying_url = None
3175
 
        submissions_url = None
3176
 
        
3177
 
        def __init__(self, client_id, client_version, username, md5_password):
3178
 
                self.client_id = client_id
3179
 
                self.client_version = client_version
3180
 
                self.username = username
3181
 
                self.password = md5_password
3182
 
        
3183
 
        def _do_handshake(self):
3184
 
                """Handshakes with the server"""
3185
 
                
3186
 
                timestamp = str(int(time.time()))
3187
 
                token = md5(self.password + timestamp)
3188
 
                
3189
 
                params = {"hs": "true", "p": "1.2.1", "c": self.client_id,
3190
 
                        "v": self.client_version, "u": self.username, "t": timestamp,
3191
 
                        "a": token}
3192
 
                
3193
 
                global SUBMISSION_SERVER
3194
 
                response = _ScrobblerRequest(SUBMISSION_SERVER, params).execute().split("\n")
3195
 
                
3196
 
                self.session_id = response[1]
3197
 
                self.nowplaying_url = response[2]
3198
 
                self.submissions_url = response[3]
3199
 
        
3200
 
        def _get_session_id(self, new = False):
3201
 
                """Returns a handshake. If new is true, then it will be requested from the server
3202
 
                even if one was cached."""
3203
 
                
3204
 
                if not self.session_id or new:
3205
 
                        debug("Doing a scrobbling handshake")
3206
 
                        self._do_handshake()
3207
 
                
3208
 
                return self.session_id
3209
 
        
3210
 
        def report_now_playing(self, artist, title, album = "", duration = "", track_number = "", mbid = ""):
3211
 
                
3212
 
                params = {"s": self._get_session_id(), "a": artist, "t": title,
3213
 
                        "b": album, "l": duration, "n": track_number, "m": mbid}
3214
 
                
3215
 
                response = _ScrobblerRequest(self.nowplaying_url, params).execute()
3216
 
                
3217
 
                try:
3218
 
                        _ScrobblerRequest(self.nowplaying_url, params).execute()
3219
 
                except BadSession:
3220
 
                        self._do_handshake()
3221
 
                        self.report_now_playing(artist, title, album, duration, track_number, mbid)
3222
 
                
3223
 
                info(artist + " - " + title + " was reported as now-playing")
3224
 
        
3225
 
        def scrobble(self, artist, title, time_started, source, mode, duration, album="", track_number="", mbid=""):
3226
 
                """Scrobble a track. parameters:
3227
 
                        artist: Artist name.
3228
 
                        title: Track title.
3229
 
                        time_started: UTC timestamp of when the track started playing.
3230
 
                        source: The source of the track
3231
 
                                SCROBBLE_SOURCE_USER: Chosen by the user (the most common value, unless you have a reason for choosing otherwise, use this).
3232
 
                                SCROBBLE_SOURCE_NON_PERSONALIZED_BROADCAST: Non-personalised broadcast (e.g. Shoutcast, BBC Radio 1).
3233
 
                                SCROBBLE_SOURCE_PERSONALIZED_BROADCAST: Personalised recommendation except Last.fm (e.g. Pandora, Launchcast).
3234
 
                                SCROBBLE_SOURCE_LASTFM: ast.fm (any mode). In this case, the 5-digit recommendation_key value must be set.
3235
 
                                SCROBBLE_SOURCE_UNKNOWN: Source unknown.
3236
 
                        mode: The submission mode
3237
 
                                SCROBBLE_MODE_PLAYED: The track was played.
3238
 
                                SCROBBLE_MODE_SKIPPED: The track was skipped (Only if source was Last.fm)
3239
 
                                SCROBBLE_MODE_BANNED: The track was banned (Only if source was Last.fm)
3240
 
                        duration: Track duration in seconds.
3241
 
                        album: The album name.
3242
 
                        track_number: The track number on the album.
3243
 
                        mbid: MusicBrainz ID.
3244
 
                """
3245
 
                
3246
 
                params = {"s": self._get_session_id(), "a[0]": artist, "t[0]": title,
3247
 
                        "i[0]": str(time_started), "o[0]": source, "r[0]": mode, "l[0]": str(duration),
3248
 
                        "b[0]": album, "n[0]": track_number, "m[0]": mbid}
3249
 
                
3250
 
                response = _ScrobblerRequest(self.submissions_url, params).execute()
3251
 
                info(artist + " - " + title + " was scrobbled")
3252
 
 
3253
 
 
3254
 
#LFM_API_KEY = '3b954460f7b207e5414ffdf8c5710592'
3255
 
#print search_for_track('Air', 'Universal Traveller', LFM_API_KEY, None, None).get_next_page()[0].get_top_tags()
 
 
b'\\ No newline at end of file'