1
# -*- coding: utf-8 -*-
3
# pylast - A Python interface to Last.fm
4
# Copyright (C) 2008-2009 Amr Hassan
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.
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.
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
21
# http://code.google.com/p/pylast/
25
__revision__ = "$Revision$"
26
__doc__ = 'A Python interface to Last.fm'
27
__author__ = 'Amr Hassan'
28
__copyright__ = "Copyright (C) 2008-2009 Amr Hassan"
30
__email__ = 'amr.hassan@gmail.com'
32
# Parse revision and add it to __version__
34
__version__ = __version__ + "." + r[r.find(" ")+1:r.rfind("$")-1]
36
# Default values for Last.fm.
37
WS_SERVER = ('ws.audioscrobbler.com', '/2.0/')
38
SUBMISSION_SERVER = "http://post.audioscrobbler.com:80/"
41
__proxy_enabled = False
43
__cache_filename = None
50
from xml.dom import minidom
53
from logging import info, warn, debug
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
65
STATUS_INVALID_API_KEY = 10
67
STATUS_SUBSCRIBERS_ONLY = 12
68
STATUS_INVALID_SIGNATURE = 13
69
STATUS_TOKEN_UNAUTHORIZED = 14
70
STATUS_TOKEN_EXPIRED = 15
73
EVENT_MAYBE_ATTENDING = '1'
74
EVENT_NOT_ATTENDING = '2'
76
PERIOD_OVERALL = 'overall'
77
PERIOD_3MONTHS = '3month'
78
PERIOD_6MONTHS = '6month'
79
PERIOD_12MONTHS = '12month'
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'
100
USER_FEMALE = 'Female'
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"
108
SCROBBLE_MODE_PLAYED = "L"
109
SCROBBLE_MODE_BANNED = "B"
110
SCROBBLE_MODE_SKIPPED = "S"
112
class _ThreadedCall(threading.Thread):
113
"""Facilitates calling a function on another thread."""
115
def __init__(self, sender, funct, funct_args, callback, callback_args):
117
threading.Thread.__init__(self)
120
self.funct_args = funct_args
121
self.callback = callback
122
self.callback_args = callback_args
132
output = self.funct(*self.funct_args)
134
output = self.funct()
137
if self.callback_args:
138
self.callback(self.sender, output, *self.callback_args)
140
self.callback(self.sender, output)
142
class _Request(object):
143
"""Representing an abstract web service operation."""
146
(HOST_NAME, HOST_SUBDIR) = WS_SERVER
148
def __init__(self, method_name, params, api_key, api_secret, session_key = None):
151
self.api_secret = api_secret
153
self.params["api_key"] = api_key
154
self.params["method"] = method_name
156
if is_caching_enabled():
157
self.shelf = get_cache_shelf()
160
self.params["sk"] = session_key
164
"""Sign this request."""
166
if not "api_sig" in self.params.keys():
167
self.params['api_sig'] = self._get_signature()
169
def _get_signature(self):
170
"""Returns a 32-character hexadecimal md5 hash of the signature string."""
172
keys = self.params.keys()[:]
180
string += self.params[name]
182
string += self.api_secret
186
def _get_cache_key(self):
187
"""The cache key is a string of concatenated sorted names and values."""
189
keys = self.params.keys()
195
if key != "api_sig" and key != "api_key" and key != "sk":
196
cache_key += key + self.params[key].encode("utf-8")
198
return hashlib.sha1(cache_key).hexdigest()
200
def _get_cached_response(self):
201
"""Returns a file object of the cached response."""
203
if not self._is_cached():
204
response = self._download_response()
205
self.shelf[self._get_cache_key()] = response
207
return self.shelf[self._get_cache_key()]
209
def _is_cached(self):
210
"""Returns True if the request is already in cache."""
212
return self.shelf.has_key(self._get_cache_key())
214
def _download_response(self):
215
"""Returns a response body string from the server."""
217
# Delay the call if necessary
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)
226
"Content-type": "application/x-www-form-urlencoded",
227
'Accept-Charset': 'utf-8',
228
'User-Agent': __name__ + '/' + __version__
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)
236
conn = httplib.HTTPConnection(host=self.HOST_NAME)
237
conn.request(method='POST', url=self.HOST_SUBDIR, body=data, headers=headers)
239
response = conn.getresponse().read()
240
self._check_response_for_errors(response)
243
def execute(self, cacheable = False):
244
"""Returns the XML DOM response of the POST Request from the server"""
246
if is_caching_enabled() and cacheable:
247
response = self._get_cached_response()
249
response = self._download_response()
251
return minidom.parseString(response)
253
def _check_response_for_errors(self, response):
254
"""Checks the response for errors and raises one if any exists."""
256
doc = minidom.parseString(response)
257
e = doc.getElementsByTagName('lfm')[0]
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)
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)
277
A session key's lifetime is infinie, unless the user provokes the rights of the given API Key.
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 = {}
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.
290
request = _Request('auth.getToken', dict(), self.api_key, self.api_secret)
292
# default action is that a request is signed only when
293
# a session key is provided.
296
doc = request.execute()
298
e = doc.getElementsByTagName('token')[0]
299
return e.firstChild.data
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."""
304
token = self._get_web_auth_token()
306
url = 'http://www.last.fm/api/auth/?api_key=%(api)s&token=%(token)s' % \
307
{'api': self.api_key, 'token': token}
309
self.web_auth_tokens[url] = token
313
def get_web_auth_session_key(self, url):
314
"""Retrieves the session key of a web authorization process by its url."""
316
if url in self.web_auth_tokens.keys():
317
token = self.web_auth_tokens[url]
319
token = "" #that's gonna raise a ServiceException of an unauthorized token when the request is executed.
321
request = _Request('auth.getSession', {'token': token}, self.api_key, self.api_secret)
323
# default action is that a request is signed only when
324
# a session key is provided.
327
doc = request.execute()
329
return doc.getElementsByTagName('key')[0].firstChild.data
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."""
334
params = {"username": username, "authToken": md5(username + md5_password)}
335
request = _Request("auth.getMobileSession", params, self.api_key, self.api_secret)
337
# default action is that a request is signed only when
338
# a session key is provided.
341
doc = request.execute()
343
return _extract(doc, "key")
345
class _BaseObject(object):
346
"""An abstract webservices object."""
348
def __init__(self, api_key, api_secret, session_key):
350
self.api_key = api_key
351
self.api_secret = api_secret
352
self.session_key = session_key
354
self.auth_data = (self.api_key, self.api_secret, self.session_key)
356
def _request(self, method_name, cacheable = False, params = None):
358
params = self._get_params()
360
return _Request(method_name, params, *self.auth_data).execute(cacheable)
363
"""Returns the most common set of parameters between all objects."""
367
class _Taggable(object):
368
"""Common functions for classes with tags."""
370
def __init__(self, ws_prefix):
371
self.ws_prefix = ws_prefix
373
def add_tags(self, *tags):
374
"""Adds one or several tags.
375
* *tags: Any number of tag names or Tag objects.
381
def _add_tag(self, tag):
382
"""Adds one or several tags.
383
* tag: one tag name or a Tag object.
386
if isinstance(tag, Tag):
389
params = self._get_params()
390
params['tags'] = unicode(tag)
392
self._request(self.ws_prefix + '.addTags', False, params)
393
info("Tagged " + repr(self) + " as (" + repr(tag) + ")")
395
def _remove_tag(self, single_tag):
396
"""Remove a user's tag from this object."""
398
if isinstance(single_tag, Tag):
399
single_tag = single_tag.get_name()
401
params = self._get_params()
402
params['tag'] = unicode(single_tag)
404
self._request(self.ws_prefix + '.removeTag', False, params)
405
info("Removed tag (" + repr(tag) + ") from " + repr(self))
408
"""Returns a list of the tags set by the user to this object."""
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)
414
tag_names = _extract_all(doc, 'name')
416
for tag in tag_names:
417
tags.append(Tag(tag, *self.auth_data))
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.
427
self._remove_tag(tag)
429
def clear_tags(self):
430
"""Clears all the user-set tags. """
432
self.remove_tags(*(self.get_tags()))
434
def set_tags(self, *tags):
435
"""Sets this object's tags to only those tags.
436
* *tags: any number of tag names.
447
tags_on_server = self.get_tags()
449
for tag in tags_on_server:
450
c_old_tags.append(tag.get_name().lower())
451
old_tags.append(tag.get_name())
454
c_new_tags.append(tag.lower())
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])
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])
465
self.remove_tags(*to_remove)
466
self.add_tags(*to_add)
468
def get_top_tags(self, limit = None):
469
"""Returns a list of the most frequently used Tags on this object."""
471
doc = self._request(self.ws_prefix + '.getTopTags', True)
473
elements = doc.getElementsByTagName('tag')
476
for element in elements:
477
if limit and len(list) >= limit:
479
tag_name = _extract(element, 'name')
480
tagcount = _extract(element, 'count')
482
list.append(TopItem(Tag(tag_name, *self.auth_data), tagcount))
486
class ServiceException(Exception):
487
"""Exception related to the Last.fm web service"""
489
def __init__(self, lastfm_status, details):
490
self._lastfm_status = lastfm_status
491
self._details = details
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
508
STATUS_SUBSCRIBERS_ONLY = 12
509
STATUS_TOKEN_UNAUTHORIZED = 14
510
STATUS_TOKEN_EXPIRED = 15
513
return self._lastfm_status
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()."""
518
def __init__(self, item, weight):
519
object.__init__(self)
522
self.weight = _number(weight)
525
return "Item: " + self.get_item().__repr__() + ", Weight: " + str(self.get_weight())
528
"""Returns the item."""
532
def get_weight(self):
533
"""Returns the weight of the itme in the list."""
538
class LibraryItem (object):
539
"""An item in a User's Library. It could be an artist, an album or a track."""
541
def __init__(self, item, playcount, tagcount):
542
object.__init__(self)
545
self.playcount = _number(playcount)
546
self.tagcount = _number(tagcount)
549
return "Item: " + self.get_item().__repr__() + ", Playcount: " + str(self.get_playcount()) + ", Tagcount: " + str(self.get_tagcount())
552
"""Returns the itme."""
556
def get_playcount(self):
557
"""Returns the item's playcount in the Library."""
559
return self.playcount
561
def get_tagcount(self):
562
"""Returns the item's tagcount in the Library."""
566
class PlayedTrack (object):
567
"""A track with a playback date."""
569
def __init__(self, track, date, timestamp):
570
object.__init__(self)
574
self.timestamp = timestamp
577
return repr(self.track) + " played at " + self.date
580
"""Return the track."""
585
"""Returns the played track. An alias to get_track()."""
587
return self.get_track();
590
"""Returns the playback date."""
594
def get_timestamp(self):
595
"""Returns the unix timestamp of the playback date."""
597
return self.timestamp
600
class Album(_BaseObject, _Taggable):
601
"""A Last.fm album."""
603
def __init__(self, artist, title, api_key, api_secret, session_key):
605
Create an album instance.
607
* artist str|Artist: An artist name or an Artist object.
608
* title str: The album title.
611
_BaseObject.__init__(self, api_key, api_secret, session_key)
612
_Taggable.__init__(self, 'album')
614
if isinstance(artist, Artist):
617
self.artist = Artist(artist, *self.auth_data)
622
return self.get_artist().get_name() + ' - ' + self.get_title()
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())
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())
630
def _get_params(self):
631
return {'artist': self.get_artist().get_name(), 'album': self.get_title(), }
633
def get_artist(self):
634
"""Returns the associated Artist object."""
639
"""Returns the album title."""
644
"""Returns the album title (alias to Album.get_title)."""
646
return self.get_title()
648
def get_release_date(self):
649
"""Retruns the release date of the album."""
651
return _extract(self._request("album.getInfo", cacheable = True), "releasedate")
653
def get_image_url(self, size = IMAGE_EXTRA_LARGE):
654
"""Returns the associated image URL.
656
* size int: The image size. Possible values:
663
return _extract_all(self._request("album.getInfo", cacheable = True), 'image')[size]
666
"""Returns the Last.fm ID."""
668
return _extract(self._request("album.getInfo", cacheable = True), "id")
670
def get_playcount(self):
671
"""Returns the number of plays on Last.fm."""
673
return _number(_extract(self._request("album.getInfo", cacheable = True), "playcount"))
675
def get_listener_count(self):
676
"""Returns the number of liteners on Last.fm."""
678
return _number(_extract(self._request("album.getInfo", cacheable = True), "listeners"))
680
def get_top_tags(self, limit = None):
681
"""Returns a list of the most-applied tags to this album."""
683
# BROKEN: Web service is currently broken.
687
def get_tracks(self):
688
"""Returns the list of Tracks on this album."""
690
uri = 'lastfm://playlist/album/%s' %self.get_id()
692
return XSPF(uri, *self.auth_data).get_tracks()
695
"""Returns the MusicBrainz id of the album."""
697
return _extract(self._request("album.getInfo", cacheable = True), "mbid")
699
def get_url(self, domain_name = DOMAIN_ENGLISH):
700
"""Returns the url of the album page on Last.fm.
702
* domain_name str: Last.fm's language domain. Possible values:
717
url = 'http://%(domain)s/music/%(artist)s/%(album)s'
719
artist = _get_url_safe(self.get_artist().get_name())
720
album = _get_url_safe(self.get_title())
722
return url %{'domain': domain_name, 'artist': artist, 'album': album}
724
class Artist(_BaseObject, _Taggable):
725
"""A Last.fm artist."""
727
def __init__(self, name, api_key, api_secret, session_key):
728
"""Create an artist object.
730
* name str: The artist's name.
733
_BaseObject.__init__(self, api_key, api_secret, session_key)
734
_Taggable.__init__(self, 'artist')
739
return unicode(self.get_name())
741
def __eq__(self, other):
742
return self.get_name().lower() == other.get_name().lower()
744
def __ne__(self, other):
745
return self.get_name().lower() != other.get_name().lower()
747
def _get_params(self):
748
return {'artist': self.get_name()}
751
"""Returns the name of the artist."""
755
def get_image_url(self, size = IMAGE_LARGE):
756
"""Returns the associated image URL.
758
* size int: The image size. Possible values:
764
return _extract_all(self._request("artist.getInfo", True), "image")[size]
766
def get_square_image(self):
767
return _extract_all(self._request("artist.getImages", True), "size")[2]
769
def get_playcount(self):
770
"""Returns the number of plays on Last.fm."""
772
return _number(_extract(self._request("artist.getInfo", True), "playcount"))
775
"""Returns the MusicBrainz ID of this artist."""
777
doc = self._request("artist.getInfo", True)
779
return _extract(doc, "mbid")
781
def get_listener_count(self):
782
"""Returns the number of liteners on Last.fm."""
784
return _number(_extract(self._request("artist.getInfo", True), "listeners"))
786
def is_streamable(self):
787
"""Returns True if the artist is streamable."""
789
return bool(_number(_extract(self._request("artist.getInfo", True), "streamable")))
791
def get_bio_published_date(self):
792
"""Returns the date on which the artist's biography was published."""
794
return _extract(self._request("artist.getInfo", True), "published")
796
def get_bio_summary(self):
797
"""Returns the summary of the artist's biography."""
799
return _extract(self._request("artist.getInfo", True), "summary")
801
def get_bio_content(self):
802
"""Returns the content of the artist's biography."""
804
return _extract(self._request("artist.getInfo", True), "content")
806
def get_upcoming_events(self):
807
"""Returns a list of the upcoming Events for this artist."""
809
doc = self._request('artist.getEvents', True)
811
ids = _extract_all(doc, 'id')
815
events.append(Event(id, *self.auth_data))
819
def get_similar(self, limit = None):
820
"""Returns the similar artists on Last.fm."""
822
params = self._get_params()
824
params['limit'] = unicode(limit)
826
doc = self._request('artist.getSimilar', True, params)
828
names = _extract_all(doc, 'name')
832
artists.append(Artist(name, *self.auth_data))
836
# def get_top_tags(self):
837
# """Returns the top tags."""
839
# doc = self._request("artist.getInfo", True)
843
# elements = doc.getElementsByTagName('tag')
845
# for element in elements:
847
# name = _extract(element, 'name')
853
def get_top_tags(self):
854
"""Returns the top tags."""
856
doc = self._request("artist.getTopTags", True)
860
elements = doc.getElementsByTagName('tag')
862
for element in elements:
864
name = _extract(element, 'name')
870
def get_top_albums(self):
871
"""Retuns a list of the top albums."""
873
doc = self._request('artist.getTopAlbums', True)
877
for node in doc.getElementsByTagName("album"):
878
name = _extract(node, "name")
879
artist = _extract(node, "name", 1)
880
playcount = _extract(node, "playcount")
882
list.append(TopItem(Album(artist, name, *self.auth_data), playcount))
886
def get_top_tracks(self):
887
"""Returns a list of the most played Tracks by this artist."""
889
doc = self._request("artist.getTopTracks", True)
892
for track in doc.getElementsByTagName('track'):
894
title = _extract(track, "name")
895
artist = _extract(track, "name", 1)
896
playcount = _number(_extract(track, "playcount"))
898
list.append( TopItem(Track(artist, title, *self.auth_data), playcount) )
902
def get_top_fans(self, limit = None):
903
"""Returns a list of the Users who played this artist the most.
905
* limit int: Max elements.
908
params = self._get_params()
909
doc = self._request('artist.getTopFans', True)
913
elements = doc.getElementsByTagName('user')
915
for element in elements:
916
if limit and len(list) >= limit:
919
name = _extract(element, 'name')
920
weight = _number(_extract(element, 'weight'))
922
list.append(TopItem(User(name, *self.auth_data), weight))
926
def share(self, users, message = None):
927
"""Shares this artist (sends out recommendations).
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.
933
#last.fm currently accepts a max of 10 recipient at a time
934
while(len(users) > 10):
937
self.share(section, message)
941
if isinstance(user, User):
942
nusers.append(user.get_name())
946
params = self._get_params()
947
recipients = ','.join(nusers)
948
params['recipient'] = recipients
949
if message: params['message'] = unicode(message)
951
self._request('artist.share', False, params)
952
info(repr(self) + " was shared with " + repr(users))
954
def get_url(self, domain_name = DOMAIN_ENGLISH):
955
"""Returns the url of the artist page on Last.fm.
957
* domain_name: Last.fm's language domain. Possible values:
972
url = 'http://%(domain)s/music/%(artist)s'
974
artist = _get_url_safe(self.get_name())
976
return url %{'domain': domain_name, 'artist': artist}
979
class Event(_BaseObject):
980
"""A Last.fm event."""
982
def __init__(self, event_id, api_key, api_secret, session_key):
983
_BaseObject.__init__(self, api_key, api_secret, session_key)
985
self.id = unicode(event_id)
988
return "Event #" + self.get_id()
990
def __eq__(self, other):
991
return self.get_id() == other.get_id()
993
def __ne__(self, other):
994
return self.get_id() != other.get_id()
996
def _get_params(self):
997
return {'event': self.get_id()}
999
def attend(self, attending_status):
1000
"""Sets the attending status.
1001
* attending_status: The attending status. Possible values:
1003
o EVENT_MAYBE_ATTENDING
1004
o EVENT_NOT_ATTENDING
1007
params = self._get_params()
1008
params['status'] = unicode(attending_status)
1010
doc = self._request('event.attend', False, params)
1011
info("Attendance to " + repr(self) + " was set to " + repr(attending_status))
1014
"""Returns the id of the event on Last.fm. """
1017
def get_title(self):
1018
"""Returns the title of the event. """
1020
doc = self._request("event.getInfo", True)
1022
return _extract(doc, "title")
1024
def get_headliner(self):
1025
"""Returns the headliner of the event. """
1027
doc = self._request("event.getInfo", True)
1029
return Artist(_extract(doc, "headliner"), *self.auth_data)
1031
def get_artists(self):
1032
"""Returns a list of the participating Artists. """
1034
doc = self._request("event.getInfo", True)
1035
names = _extract_all(doc, "artist")
1039
artists.append(Artist(name, *self.auth_data))
1043
def get_venue(self):
1044
"""Returns the venue where the event is held."""
1046
doc = self._request("event.getInfo", True)
1048
venue_url = _extract(doc, "url")
1049
venue_id = _number(venue_url[venue_url.rfind("/") + 1:])
1051
return Venue(venue_id, *self.auth_data)
1053
def get_start_date(self):
1054
"""Returns the date when the event starts."""
1056
doc = self._request("event.getInfo", True)
1058
return _extract(doc, "startDate")
1060
def get_description(self):
1061
"""Returns the description of the event. """
1063
doc = self._request("event.getInfo", True)
1065
return _extract(doc, "description")
1067
def get_image_url(self, size = IMAGE_LARGE):
1068
"""Returns the associated image URL.
1069
* size: The image size. Possible values:
1075
doc = self._request("event.getInfo", True)
1077
return _extract_all(doc, "image")[size]
1079
def get_attendance_count(self):
1080
"""Returns the number of attending people. """
1082
doc = self._request("event.getInfo", True)
1084
return _number(_extract(doc, "attendance"))
1086
def get_review_count(self):
1087
"""Returns the number of available reviews for this event. """
1089
doc = self._request("event.getInfo", True)
1091
return _number(_extract(doc, "reviews"))
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:
1110
url = 'http://%(domain)s/event/%(id)s'
1112
return url %{'domain': domain_name, 'id': self.get_id()}
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.
1120
#last.fm currently accepts a max of 10 recipient at a time
1121
while(len(users) > 10):
1122
section = users[0:9]
1124
self.share(section, message)
1128
if isinstance(user, User):
1129
nusers.append(user.get_name())
1133
params = self._get_params()
1134
recipients = ','.join(nusers)
1135
params['recipient'] = recipients
1136
if message: params['message'] = unicode(message)
1138
self._request('event.share', False, params)
1139
info(repr(self) + " was shared with " + repr(users))
1141
class Country(_BaseObject):
1142
"""A country at Last.fm."""
1144
def __init__(self, name, api_key, api_secret, session_key):
1145
_BaseObject.__init__(self, api_key, api_secret, session_key)
1150
return self.get_name()
1152
def __eq__(self, other):
1153
self.get_name().lower() == other.get_name().lower()
1155
def __ne__(self, other):
1156
self.get_name() != other.get_name()
1158
def _get_params(self):
1159
return {'country': self.get_name()}
1161
def _get_name_from_code(self, alpha2code):
1162
# TODO: Have this function lookup the alpha-2 code and return the country name.
1167
"""Returns the country name. """
1171
def get_top_artists(self):
1172
"""Returns a sequence of the most played artists."""
1174
doc = self._request('geo.getTopArtists', True)
1177
for node in doc.getElementsByTagName("artist"):
1178
name = _extract(node, 'name')
1179
playcount = _extract(node, "playcount")
1181
list.append(TopItem(Artist(name, *self.auth_data), playcount))
1185
def get_top_tracks(self):
1186
"""Returns a sequence of the most played tracks"""
1188
doc = self._request("geo.getTopTracks", True)
1192
for n in doc.getElementsByTagName('track'):
1194
title = _extract(n, 'name')
1195
artist = _extract(n, 'name', 1)
1196
playcount = _number(_extract(n, "playcount"))
1198
list.append( TopItem(Track(artist, title, *self.auth_data), playcount))
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:
1219
url = 'http://%(domain)s/place/%(country_name)s'
1221
country_name = _get_url_safe(self.get_name())
1223
return url %{'domain': domain_name, 'country_name': country_name}
1226
class Library(_BaseObject):
1227
"""A user's Last.fm library."""
1229
def __init__(self, user, api_key, api_secret, session_key):
1230
_BaseObject.__init__(self, api_key, api_secret, session_key)
1232
if isinstance(user, User):
1235
self.user = User(user, *self.auth_data)
1237
self._albums_index = 0
1238
self._artists_index = 0
1239
self._tracks_index = 0
1242
return self.get_user().__repr__() + "'s Library"
1244
def _get_params(self):
1245
return {'user': self.user.get_name()}
1248
"""Returns the user who owns this library."""
1252
def add_album(self, album):
1253
"""Add an album to this library."""
1255
params = self._get_params()
1256
params["artist"] = album.get_artist.get_name()
1257
params["album"] = album.get_name()
1259
self._request("library.addAlbum", False, params)
1260
info(repr(album) + " was added to " + repr(self))
1262
def add_artist(self, artist):
1263
"""Add an artist to this library."""
1265
params = self._get_params()
1266
params["artist"] = artist.get_name()
1268
self._request("library.addArtist", False, params)
1269
info(repr(artist) + " was added to " + repr(self))
1271
def add_track(self, track):
1272
"""Add a track to this library."""
1274
params = self._get_prams()
1275
params["track"] = track.get_title()
1277
self._request("library.addTrack", False, params)
1278
info(repr(track) + " was added to " + repr(self))
1280
def _get_albums_pagecount(self):
1281
"""Returns the number of album pages in this library."""
1283
doc = self._request("library.getAlbums", True)
1285
return _number(doc.getElementsByTagName("albums")[0].getAttribute("totalPages"))
1287
def is_end_of_albums(self):
1288
"""Returns True when the last page of albums has ben retrieved."""
1290
if self._albums_index >= self._get_albums_pagecount():
1295
def _get_artists_pagecount(self):
1296
"""Returns the number of artist pages in this library."""
1298
doc = self._request("library.getArtists", True)
1300
return _number(doc.getElementsByTagName("artists")[0].getAttribute("totalPages"))
1302
def is_end_of_artists(self):
1303
"""Returns True when the last page of artists has ben retrieved."""
1305
if self._artists_index >= self._get_artists_pagecount():
1310
def _get_tracks_pagecount(self):
1311
"""Returns the number of track pages in this library."""
1313
doc = self._request("library.getTracks", True)
1315
return _number(doc.getElementsByTagName("tracks")[0].getAttribute("totalPages"))
1317
def is_end_of_tracks(self):
1318
"""Returns True when the last page of tracks has ben retrieved."""
1320
if self._tracks_index >= self._get_tracks_pagecount():
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.
1332
library = Library("rj", API_KEY, API_SECRET, SESSION_KEY)
1334
while not library.is_end_of_albums():
1335
print library.get_albums_page()
1338
self._albums_index += 1
1340
params = self._get_params()
1341
params["page"] = str(self._albums_index)
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"))
1351
list.append(LibraryItem(Album(artist, name, *self.auth_data), playcount, tagcount))
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.
1362
library = Library("rj", API_KEY, API_SECRET, SESSION_KEY)
1364
while not library.is_end_of_artists():
1365
print library.get_artists_page()
1368
self._artists_index += 1
1370
params = self._get_params()
1371
params["page"] = str(self._artists_index)
1374
doc = self._request("library.getArtists", True, params)
1375
for node in doc.getElementsByTagName("artist"):
1376
name = _extract(node, "name")
1378
playcount = _number(_extract(node, "playcount"))
1379
tagcount = _number(_extract(node, "tagcount"))
1381
list.append(LibraryItem(Artist(name, *self.auth_data), playcount, tagcount))
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.
1392
library = Library("rj", API_KEY, API_SECRET, SESSION_KEY)
1394
while not library.is_end_of_tracks():
1395
print library.get_tracks_page()
1398
self._tracks_index += 1
1400
params = self._get_params()
1401
params["page"] = str(self._tracks_index)
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"))
1411
list.append(LibraryItem(Track(artist, name, *self.auth_data), playcount, tagcount))
1416
class Playlist(_BaseObject):
1417
"""A Last.fm user playlist."""
1419
def __init__(self, user, id, api_key, api_secret, session_key):
1420
_BaseObject.__init__(self, api_key, api_secret, session_key)
1422
if isinstance(user, User):
1425
self.user = User(user, *self.auth_data)
1427
self.id = unicode(id)
1430
return repr(self.user) + "'s playlist # " + repr(self.id)
1432
def _get_info_node(self):
1433
"""Returns the node from user.getPlaylists where this playlist's info is."""
1435
doc = self._request("user.getPlaylists", True)
1437
for node in doc.getElementsByTagName("playlist"):
1438
if _extract(node, "id") == str(self.get_id()):
1441
def _get_params(self):
1442
return {'user': self.user.get_name(), 'playlistID': self.get_id()}
1445
"""Returns the playlist id."""
1450
"""Returns the owner user of this playlist."""
1454
def get_tracks(self):
1455
"""Returns a list of the tracks on this user playlist."""
1457
uri = u'lastfm://playlist/%s' %self.get_id()
1459
return XSPF(uri, *self.auth_data).get_tracks()
1461
def add_track(self, track):
1462
"""Adds a Track to this Playlist."""
1464
params = self._get_params()
1465
params['artist'] = track.get_artist().get_name()
1466
params['track'] = track.get_title()
1468
self._request('playlist.addTrack', False, params)
1469
info(repr(track) + " was added to " + repr(self))
1471
def get_title(self):
1472
"""Returns the title of this playlist."""
1474
return _extract(self._get_info_node(), "title")
1476
def get_creation_date(self):
1477
"""Returns the creation date of this playlist."""
1479
return _extract(self._get_info_node(), "date")
1482
"""Returns the number of tracks in this playlist."""
1484
return _number(_extract(self._get_info_node(), "size"))
1486
def get_description(self):
1487
"""Returns the description of this playlist."""
1489
return _extract(self._get_info_node(), "description")
1491
def get_duration(self):
1492
"""Returns the duration of this playlist in milliseconds."""
1494
return _number(_extract(self._get_info_node(), "duration"))
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."""
1500
if _extract(self._get_info_node(), "streamable") == '1':
1505
def has_track(self, track):
1506
"""Checks to see if track is already in the playlist.
1507
* track: Any Track object.
1510
return track in self.get_tracks()
1512
def get_image_url(self, size = IMAGE_LARGE):
1513
"""Returns the associated image URL.
1514
* size: The image size. Possible values:
1520
return _extract(self._get_info_node(), "image")[size]
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:
1538
url = "http://%(domain)s/user/%(user)s/library/playlists/%(appendix)s"
1540
english_url = _extract(self._get_info_node(), "url")
1541
appendix = english_url[english_url.rfind("/") + 1:]
1543
return url %{'domain': domain_name, 'appendix': appendix, "user": self.get_user().get_name()}
1546
class Tag(_BaseObject):
1547
"""A Last.fm object tag."""
1549
# TODO: getWeeklyArtistChart (too lazy, i'll wait for when someone requests it)
1551
def __init__(self, name, api_key, api_secret, session_key):
1552
_BaseObject.__init__(self, api_key, api_secret, session_key)
1556
def _get_params(self):
1557
return {'tag': self.get_name()}
1560
return self.get_name()
1563
return self.get_name().lower() == other.get_name().lower()
1566
return self.get_name().lower() != other.get_name().lower()
1569
"""Returns the name of the tag. """
1573
def get_similar(self):
1574
"""Returns the tags similar to this one, ordered by similarity. """
1576
doc = self._request('tag.getSimilar', True)
1579
names = _extract_all(doc, 'name')
1581
list.append(Tag(name, *self.auth_data))
1585
def get_top_albums(self):
1586
"""Retuns a list of the top albums."""
1588
doc = self._request('tag.getTopAlbums', True)
1592
for node in doc.getElementsByTagName("album"):
1593
name = _extract(node, "name")
1594
artist = _extract(node, "name", 1)
1595
playcount = _extract(node, "playcount")
1597
list.append(TopItem(Album(artist, name, *self.auth_data), playcount))
1601
def get_top_tracks(self):
1602
"""Returns a list of the most played Tracks by this artist."""
1604
doc = self._request("tag.getTopTracks", True)
1607
for track in doc.getElementsByTagName('track'):
1609
title = _extract(track, "name")
1610
artist = _extract(track, "name", 1)
1611
playcount = _number(_extract(track, "playcount"))
1613
list.append( TopItem(Track(artist, title, *self.auth_data), playcount) )
1617
def get_top_artists(self):
1618
"""Returns a sequence of the most played artists."""
1620
doc = self._request('tag.getTopArtists', True)
1623
for node in doc.getElementsByTagName("artist"):
1624
name = _extract(node, 'name')
1625
playcount = _extract(node, "playcount")
1627
list.append(TopItem(Artist(name, *self.auth_data), playcount))
1631
def get_weekly_chart_dates(self):
1632
"""Returns a list of From and To tuples for the available charts."""
1634
doc = self._request("tag.getWeeklyChartList", True)
1637
for node in doc.getElementsByTagName("chart"):
1638
list.append( (node.getAttribute("from"), node.getAttribute("to")) )
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."""
1645
params = self._get_params()
1646
if from_date and to_date:
1647
params["from"] = from_date
1648
params["to"] = to_date
1650
doc = self._request("tag.getWeeklyArtistChart", True, params)
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))
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:
1677
url = 'http://%(domain)s/tag/%(name)s'
1679
name = _get_url_safe(self.get_name())
1681
return url %{'domain': domain_name, 'name': name}
1683
class Track(_BaseObject, _Taggable):
1684
"""A Last.fm track."""
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')
1690
if isinstance(artist, Artist):
1691
self.artist = artist
1693
self.artist = Artist(artist, *self.auth_data)
1698
return self.get_artist().get_name() + ' - ' + self.get_title()
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())
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())
1706
def _get_params(self):
1707
return {'artist': self.get_artist().get_name(), 'track': self.get_title()}
1709
def get_artist(self):
1710
"""Returns the associated Artist object."""
1714
def get_title(self):
1715
"""Returns the track title."""
1720
"""Returns the track title (alias to Track.get_title)."""
1722
return self.get_title()
1725
"""Returns the track id on Last.fm."""
1727
doc = self._request("track.getInfo", True)
1729
return _extract(doc, "id")
1731
def get_duration(self):
1732
"""Returns the track duration."""
1734
doc = self._request("track.getInfo", True)
1736
return _number(_extract(doc, "duration"))
1739
"""Returns the MusicBrainz ID of this track."""
1741
doc = self._request("track.getInfo", True)
1743
return _extract(doc, "mbid")
1745
def get_listener_count(self):
1746
"""Returns the listener count."""
1748
doc = self._request("track.getInfo", True)
1750
return _number(_extract(doc, "listeners"))
1752
def get_playcount(self):
1753
"""Returns the play count."""
1755
doc = self._request("track.getInfo", True)
1756
return _number(_extract(doc, "playcount"))
1758
def get_top_tags(self):
1759
"""Returns the play count."""
1761
doc = self._request("track.getInfo", True)
1765
elements = doc.getElementsByTagName('tag')
1767
for element in elements:
1769
name = _extract(element, 'name')
1775
def is_streamable(self):
1776
"""Returns True if the track is available at Last.fm."""
1778
doc = self._request("track.getInfo", True)
1779
return _extract(doc, "streamable") == "1"
1781
def is_fulltrack_available(self):
1782
"""Returns True if the fulltrack is available for streaming."""
1784
doc = self._request("track.getInfo", True)
1785
return doc.getElementsByTagName("streamable")[0].getAttribute("fulltrack") == "1"
1787
def get_album(self):
1788
"""Returns the album object of this track."""
1790
doc = self._request("track.getInfo", True)
1792
albums = doc.getElementsByTagName("album")
1794
if len(albums) == 0:
1797
node = doc.getElementsByTagName("album")[0]
1798
return Album(_extract(node, "artist"), _extract(node, "title"), *self.auth_data)
1800
def get_wiki_published_date(self):
1801
"""Returns the date of publishing this version of the wiki."""
1803
doc = self._request("track.getInfo", True)
1805
if len(doc.getElementsByTagName("wiki")) == 0:
1808
node = doc.getElementsByTagName("wiki")[0]
1810
return _extract(node, "published")
1812
def get_wiki_summary(self):
1813
"""Returns the summary of the wiki."""
1815
doc = self._request("track.getInfo", True)
1817
if len(doc.getElementsByTagName("wiki")) == 0:
1820
node = doc.getElementsByTagName("wiki")[0]
1822
return _extract(node, "summary")
1824
def get_wiki_content(self):
1825
"""Returns the content of the wiki."""
1827
doc = self._request("track.getInfo", True)
1829
if len(doc.getElementsByTagName("wiki")) == 0:
1832
node = doc.getElementsByTagName("wiki")[0]
1834
return _extract(node, "content")
1837
"""Adds the track to the user's loved tracks. """
1839
self._request('track.love')
1842
"""Ban this track from ever playing on the radio. """
1844
self._request('track.ban')
1846
def get_similar(self):
1847
"""Returns similar tracks for this track on Last.fm, based on listening data. """
1849
doc = self._request('track.getSimilar', True)
1852
for node in doc.getElementsByTagName("track"):
1853
title = _extract(node, 'name')
1854
artist = _extract(node, 'name', 1)
1856
list.append(Track(artist, title, *self.auth_data))
1860
def get_top_fans(self, limit = None):
1861
"""Returns a list of the Users who played this track."""
1863
doc = self._request('track.getTopFans', True)
1867
elements = doc.getElementsByTagName('user')
1869
for element in elements:
1870
if limit and len(list) >= limit:
1873
name = _extract(element, 'name')
1874
weight = _number(_extract(element, 'weight'))
1876
list.append(TopItem(User(name, *self.auth_data), weight))
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.
1886
#last.fm currently accepts a max of 10 recipient at a time
1887
while(len(users) > 10):
1888
section = users[0:9]
1890
self.share(section, message)
1894
if isinstance(user, User):
1895
nusers.append(user.get_name())
1899
params = self._get_params()
1900
recipients = ','.join(nusers)
1901
params['recipient'] = recipients
1902
if message: params['message'] = unicode(message)
1904
self._request('track.share', False, params)
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:
1922
url = 'http://%(domain)s/music/%(artist)s/_/%(title)s'
1924
artist = _get_url_safe(self.get_artist().get_name())
1925
title = _get_url_safe(self.get_title())
1927
return url %{'domain': domain_name, 'artist': artist, 'title': title}
1929
class Group(_BaseObject):
1930
"""A Last.fm group."""
1932
def __init__(self, group_name, api_key, api_secret, session_key):
1933
_BaseObject.__init__(self, api_key, api_secret, session_key)
1935
self.name = group_name
1938
return self.get_name()
1940
def __eq__(self, other):
1941
return self.get_name().lower() == other.get_name().lower()
1943
def __ne__(self, other):
1944
return self.get_name() != other.get_name()
1946
def _get_params(self):
1947
return {'group': self.get_name()}
1950
"""Returns the group name. """
1953
def get_weekly_chart_dates(self):
1954
"""Returns a list of From and To tuples for the available charts."""
1956
doc = self._request("group.getWeeklyChartList", True)
1959
for node in doc.getElementsByTagName("chart"):
1960
list.append( (node.getAttribute("from"), node.getAttribute("to")) )
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."""
1967
params = self._get_params()
1968
if from_date and to_date:
1969
params["from"] = from_date
1970
params["to"] = to_date
1972
doc = self._request("group.getWeeklyArtistChart", True, params)
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))
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."""
1985
params = self._get_params()
1986
if from_date and to_date:
1987
params["from"] = from_date
1988
params["to"] = to_date
1990
doc = self._request("group.getWeeklyAlbumChart", True, params)
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))
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."""
2003
params = self._get_params()
2004
if from_date and to_date:
2005
params["from"] = from_date
2006
params["to"] = to_date
2008
doc = self._request("group.getWeeklyTrackChart", True, params)
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))
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:
2035
url = 'http://%(domain)s/group/%(name)s'
2037
name = _get_url_safe(self.get_name())
2039
return url %{'domain': domain_name, 'name': name}
2041
class XSPF(_BaseObject):
2042
"A Last.fm XSPF playlist."""
2044
def __init__(self, uri, api_key, api_secret, session_key):
2045
_BaseObject.__init__(self, api_key, api_secret, session_key)
2049
def _get_params(self):
2050
return {'playlistURL': self.get_uri()}
2053
return self.get_uri()
2055
def __eq__(self, other):
2056
return self.get_uri() == other.get_uri()
2058
def __ne__(self, other):
2059
return self.get_uri() != other.get_uri()
2062
"""Returns the Last.fm playlist URI. """
2066
def get_tracks(self):
2067
"""Returns the tracks on this playlist."""
2069
doc = self._request('playlist.fetch', True)
2072
for n in doc.getElementsByTagName('track'):
2073
title = _extract(n, 'title')
2074
artist = _extract(n, 'creator')
2076
list.append(Track(artist, title, *self.auth_data))
2080
class User(_BaseObject):
2081
"""A Last.fm user."""
2083
def __init__(self, user_name, api_key, api_secret, session_key):
2084
_BaseObject.__init__(self, api_key, api_secret, session_key)
2086
self.name = user_name
2088
self._past_events_index = 0
2089
self._recommended_events_index = 0
2090
self._recommended_artists_index = 0
2093
return self.get_name()
2095
def __eq__(self, another):
2096
return self.get_name() == another.get_name()
2098
def __ne__(self, another):
2099
return self.get_name() != another.get_name()
2101
def _get_params(self):
2102
return {"user": self.get_name()}
2105
"""Returns the nuser name."""
2109
def get_upcoming_events(self):
2110
"""Returns all the upcoming events for this user. """
2112
doc = self._request('user.getEvents', True)
2114
ids = _extract_all(doc, 'id')
2118
events.append(Event(id, *self.auth_data))
2122
def get_friends(self, limit = None):
2123
"""Returns a list of the user's friends. """
2125
params = self._get_params()
2127
params['limit'] = unicode(limit)
2129
doc = self._request('user.getFriends', True, params)
2131
names = _extract_all(doc, 'name')
2135
list.append(User(name, *self.auth_data))
2139
def get_loved_tracks(self):
2140
"""Returns the last 50 tracks loved by this user. """
2142
doc = self._request('user.getLovedTracks', True)
2145
for track in doc.getElementsByTagName('track'):
2146
title = _extract(track, 'name', 0)
2147
artist = _extract(track, 'name', 1)
2149
list.append(Track(artist, title, *self.auth_data))
2153
def get_neighbours(self, limit = None):
2154
"""Returns a list of the user's friends."""
2156
params = self._get_params()
2158
params['limit'] = unicode(limit)
2160
doc = self._request('user.getNeighbours', True, params)
2163
names = _extract_all(doc, 'name')
2166
list.append(User(name, *self.auth_data))
2170
def _get_past_events_pagecount(self):
2171
"""Returns the number of pages in the past events."""
2173
params = self._get_params()
2174
params["page"] = str(self._past_events_index)
2175
doc = self._request("user.getPastEvents", True, params)
2177
return _number(doc.getElementsByTagName("events")[0].getAttribute("totalPages"))
2179
def is_end_of_past_events(self):
2180
"""Returns True if the end of Past Events was reached."""
2182
return self._past_events_index >= self._get_past_events_pagecount()
2184
def get_past_events_page(self, ):
2185
"""Retruns a paginated list of all events a user has attended in the past.
2190
while not user.is_end_of_past_events():
2191
print user.get_past_events_page()
2195
self._past_events_index += 1
2196
params = self._get_params()
2197
params["page"] = str(self._past_events_index)
2199
doc = self._request('user.getPastEvents', True, params)
2202
for id in _extract_all(doc, 'id'):
2203
list.append(Event(id, *self.auth_data))
2207
def get_playlists(self):
2208
"""Returns a list of Playlists that this user owns."""
2210
doc = self._request("user.getPlaylists", True)
2213
for id in _extract_all(doc, "id"):
2214
playlists.append(Playlist(self.get_name(), id, *self.auth_data))
2218
def get_now_playing(self):
2219
"""Returns the currently playing track, or None if nothing is playing. """
2221
params = self._get_params()
2222
params['limit'] = '1'
2226
doc = self._request('user.getRecentTracks', False, params)
2228
e = doc.getElementsByTagName('track')[0]
2230
if not e.hasAttribute('nowplaying'):
2233
artist = _extract(e, 'artist')
2234
title = _extract(e, 'name')
2236
return Track(artist, title, *self.auth_data)
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. """
2245
params = self._get_params()
2247
params['limit'] = unicode(limit)
2249
doc = self._request('user.getRecentTracks', False, params)
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")
2258
if track.hasAttribute('nowplaying'):
2259
continue #to prevent the now playing track from sneaking in here
2261
list.append(PlayedTrack(Track(artist, title, *self.auth_data), date, timestamp))
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:
2274
params = self._get_params()
2275
params['period'] = period
2277
doc = self._request('user.getTopAlbums', True, params)
2280
for album in doc.getElementsByTagName('album'):
2281
name = _extract(album, 'name')
2282
artist = _extract(album, 'name', 1)
2283
playcount = _extract(album, "playcount")
2285
list.append(TopItem(Album(artist, name, *self.auth_data), playcount))
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:
2298
params = self._get_params()
2299
params['period'] = period
2301
doc = self._request('user.getTopArtists', True, params)
2304
for node in doc.getElementsByTagName('artist'):
2305
name = _extract(node, 'name')
2306
playcount = _extract(node, "playcount")
2308
list.append(TopItem(Artist(name, *self.auth_data), playcount))
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.
2317
doc = self._request("user.getTopTags", True)
2320
for node in doc.getElementsByTagName("tag"):
2321
list.append(TopItem(Tag(_extract(node, "name"), *self.auth_data), _extract(node, "count")))
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:
2334
params = self._get_params()
2335
params['period'] = period
2337
doc = self._request('user.getTopTracks', True, params)
2340
for track in doc.getElementsByTagName('track'):
2341
name = _extract(track, 'name')
2342
artist = _extract(track, 'name', 1)
2343
playcount = _extract(track, "playcount")
2345
list.append(TopItem(Track(artist, name, *self.auth_data), playcount))
2349
def get_weekly_chart_dates(self):
2350
"""Returns a list of From and To tuples for the available charts."""
2352
doc = self._request("user.getWeeklyChartList", True)
2355
for node in doc.getElementsByTagName("chart"):
2356
list.append( (node.getAttribute("from"), node.getAttribute("to")) )
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."""
2363
params = self._get_params()
2364
if from_date and to_date:
2365
params["from"] = from_date
2366
params["to"] = to_date
2368
doc = self._request("user.getWeeklyArtistChart", True, params)
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))
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."""
2381
params = self._get_params()
2382
if from_date and to_date:
2383
params["from"] = from_date
2384
params["to"] = to_date
2386
doc = self._request("user.getWeeklyAlbumChart", True, params)
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))
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."""
2399
params = self._get_params()
2400
if from_date and to_date:
2401
params["from"] = from_date
2402
params["to"] = to_date
2404
doc = self._request("user.getWeeklyTrackChart", True, params)
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))
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.
2420
if isinstance(user, User):
2421
user = user.get_name()
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
2431
doc = _Request('tasteometer.compare', params, *self.auth_data).execute()
2433
score = _extract(doc, 'score')
2435
artists = doc.getElementsByTagName('artists')[0]
2436
shared_artists_names = _extract_all(artists, 'name')
2438
shared_artists_list = []
2440
for name in shared_artists_names:
2441
shared_artists_list.append(Artist(name, *self.auth_data))
2443
return (score, shared_artists_list)
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.
2451
params = self._get_params()
2453
params['page'] = unicode(page)
2455
params['limit'] = unicode(limit)
2457
doc = _Request('user.getRecommendedEvents', params, *self.auth_data).execute()
2459
ids = _extract_all(doc, 'id')
2462
list.append(Event(id, *self.auth_data))
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:
2482
url = 'http://%(domain)s/user/%(name)s'
2484
name = _get_url_safe(self.get_name())
2486
return url %{'domain': domain_name, 'name': name}
2488
def get_library(self):
2489
"""Returns the associated Library object. """
2491
return Library(self, *self.auth_data)
2493
class AuthenticatedUser(User):
2494
def __init__(self, api_key, api_secret, session_key):
2495
User.__init__(self, "", api_key, api_secret, session_key);
2497
def _get_params(self):
2501
"""Returns the name of the authenticated user."""
2503
doc = self._request("user.getInfo", True)
2505
self.name = _extract(doc, "name")
2509
"""Returns the user id."""
2511
doc = self._request("user.getInfo", True)
2513
return _extract(doc, "id")
2515
def get_image_url(self):
2516
"""Returns the user's avatar."""
2518
doc = self._request("user.getInfo", True)
2520
return _extract(doc, "image")
2522
def get_language(self):
2523
"""Returns the language code of the language used by the user."""
2525
doc = self._request("user.getInfo", True)
2527
return _extract(doc, "lang")
2529
def get_country(self):
2530
"""Returns the name of the country of the user."""
2532
doc = self._request("user.getInfo", True)
2534
return Country(_extract(doc, "country"), *self.auth_data)
2537
"""Returns the user's age."""
2539
doc = self._request("user.getInfo", True)
2541
return _number(_extract(doc, "age"))
2543
def get_gender(self):
2544
"""Returns the user's gender. Either USER_MALE or USER_FEMALE."""
2546
doc = self._request("user.getInfo", True)
2548
return _extract(doc, "gender")
2557
def is_subscriber(self):
2558
"""Returns whether the user is a subscriber or not. True or False."""
2560
doc = self._request("user.getInfo", True)
2562
return _extract(doc, "subscriber") == "1"
2564
def get_playcount(self):
2565
"""Returns the user's playcount so far."""
2567
doc = self._request("user.getInfo", True)
2569
return _number(_extract(doc, "playcount"))
2571
def _get_recommended_events_pagecount(self):
2572
"""Returns the number of pages in the past events."""
2574
params = self._get_params()
2575
params["page"] = str(self._recommended_events_index)
2576
doc = self._request("user.getRecommendedEvents", True, params)
2578
return _number(doc.getElementsByTagName("events")[0].getAttribute("totalPages"))
2580
def is_end_of_recommended_events(self):
2581
"""Returns True if the end of Past Events was reached."""
2583
return self._recommended_events_index >= self._get_recommended_events_pagecount()
2585
def get_recommended_events_page(self, ):
2586
"""Retruns a paginated list of all events a user has attended in the past.
2591
while not user.is_end_of_recommended_events():
2592
print user.get_recommended_events_page()
2596
self._recommended_events_index += 1
2597
params = self._get_params()
2598
params["page"] = str(self._recommended_events_index)
2600
doc = self._request('user.getRecommendedEvents', True, params)
2603
for id in _extract_all(doc, 'id'):
2604
list.append(Event(id, *self.auth_data))
2608
def _get_recommended_artists_pagecount(self):
2609
"""Returns the number of pages in the past artists."""
2611
params = self._get_params()
2612
params["page"] = str(self._recommended_artists_index)
2613
doc = self._request("user.getRecommendedArtists", True, params)
2615
return _number(doc.getElementsByTagName("recommendations")[0].getAttribute("totalPages"))
2617
def is_end_of_recommended_artists(self):
2618
"""Returns True if the end of Past Artists was reached."""
2620
return self._recommended_artists_index >= self._get_recommended_artists_pagecount()
2622
def get_recommended_artists_page(self, ):
2623
"""Retruns a paginated list of all artists a user has attended in the past.
2628
while not user.is_end_of_recommended_artists():
2629
print user.get_recommended_artists_page()
2633
self._recommended_artists_index += 1
2634
params = self._get_params()
2635
params["page"] = str(self._recommended_artists_index)
2637
doc = self._request('user.getRecommendedArtists', True, params)
2640
for name in _extract_all(doc, 'name'):
2641
list.append(Artist(name, *self.auth_data))
2645
class _Search(_BaseObject):
2646
"""An abstract class. Use one of its derivatives."""
2648
def __init__(self, ws_prefix, search_terms, api_key, api_secret, session_key):
2649
_BaseObject.__init__(self, api_key, api_secret, session_key)
2651
self._ws_prefix = ws_prefix
2652
self.search_terms = search_terms
2654
self._last_page_index = 0
2656
def _get_params(self):
2659
for key in self.search_terms.keys():
2660
params[key] = self.search_terms[key]
2664
def get_total_result_count(self):
2665
"""Returns the total count of all the results."""
2667
doc = self._request(self._ws_prefix + ".search", True)
2669
return _extract(doc, "opensearch:totalResults")
2671
def _retreive_page(self, page_index):
2672
"""Returns the node of matches to be processed"""
2674
params = self._get_params()
2675
params["page"] = str(page_index)
2676
doc = self._request(self._ws_prefix + ".search", True, params)
2678
return doc.getElementsByTagName(self._ws_prefix + "matches")[0]
2680
def _retrieve_next_page(self):
2681
self._last_page_index += 1
2682
return self._retreive_page(self._last_page_index)
2684
class AlbumSearch(_Search):
2685
"""Search for an album by name."""
2687
def __init__(self, album_name, api_key, api_secret, session_key):
2689
_Search.__init__(self, "album", {"album": album_name}, api_key, api_secret, session_key)
2691
def get_next_page(self):
2692
"""Returns the next page of results as a sequence of Album objects."""
2694
master_node = self._retrieve_next_page()
2697
for node in master_node.getElementsByTagName("album"):
2698
list.append(Album(_extract(node, "artist"), _extract(node, "name"), *self.auth_data))
2702
class ArtistSearch(_Search):
2703
"""Search for an artist by artist name."""
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)
2708
def get_next_page(self):
2709
"""Returns the next page of results as a sequence of Artist objects."""
2711
master_node = self._retrieve_next_page()
2714
for node in master_node.getElementsByTagName("artist"):
2715
list.append(Artist(_extract(node, "name"), *self.auth_data))
2719
class TagSearch(_Search):
2720
"""Search for a tag by tag name."""
2722
def __init__(self, tag_name, api_key, api_secret, session_key):
2724
_Search.__init__(self, "tag", {"tag": tag_name}, api_key, api_secret, session_key)
2726
def get_next_page(self):
2727
"""Returns the next page of results as a sequence of Tag objects."""
2729
master_node = self._retrieve_next_page()
2732
for node in master_node.getElementsByTagName("tag"):
2733
list.append(Tag(_extract(node, "name"), *self.auth_data))
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."""
2741
def __init__(self, artist_name, track_title, api_key, api_secret, session_key):
2743
_Search.__init__(self, "track", {"track": track_title, "artist": artist_name}, api_key, api_secret, session_key)
2745
def get_next_page(self):
2746
"""Returns the next page of results as a sequence of Track objects."""
2748
master_node = self._retrieve_next_page()
2751
for node in master_node.getElementsByTagName("track"):
2752
list.append(Track(_extract(node, "artist"), _extract(node, "name"), *self.auth_data))
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."""
2760
def __init__(self, venue_name, country_name, api_key, api_secret, session_key):
2762
_Search.__init__(self, "venue", {"venue": venue_name, "country": country_name}, api_key, api_secret, session_key)
2764
def get_next_page(self):
2765
"""Returns the next page of results as a sequence of Track objects."""
2767
master_node = self._retrieve_next_page()
2770
for node in master_node.getElementsByTagName("venue"):
2771
list.append(Venue(_extract(node, "id"), *self.auth_data))
2775
class Venue(_BaseObject):
2776
"""A venue where events are held."""
2778
# TODO: waiting for a venue.getInfo web service to use.
2780
def __init__(self, id, api_key, api_secret, session_key):
2781
_BaseObject.__init__(self, api_key, api_secret, session_key)
2783
self.id = _number(id)
2786
return "Venue #" + str(self.id)
2788
def __eq__(self, other):
2789
return self.get_id() == other.get_id()
2791
def _get_params(self):
2792
return {"venue": self.get_id()}
2795
"""Returns the id of the venue."""
2799
def get_upcoming_events(self):
2800
"""Returns the upcoming events in this venue."""
2802
doc = self._request("venue.getEvents", True)
2805
for node in doc.getElementsByTagName("event"):
2806
list.append(Event(_extract(node, "id"), *self.auth_data))
2810
def get_past_events(self):
2811
"""Returns the past events held in this venue."""
2813
doc = self._request("venue.getEvents", True)
2816
for node in doc.getElementsByTagName("event"):
2817
list.append(Event(_extract(node, "id"), *self.auth_data))
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.
2828
params['title'] = unicode(title)
2829
params['description'] = unicode(description)
2831
doc = _Request('playlist.create', params, api_key, api_secret, session_key).execute()
2833
id = doc.getElementsByTagName("id")[0].firstChild.data
2834
user = doc.getElementsByTagName('playlists')[0].getAttribute('user')
2836
return Playlist(user, id, api_key, api_secret, session_key)
2838
def get_authenticated_user(api_key, api_secret, session_key):
2839
"""Returns the authenticated user."""
2841
return AuthenticatedUser(api_key, api_secret, session_key)
2844
"""Returns the md5 hash of a string."""
2846
hash = hashlib.md5()
2847
hash.update(text.encode("utf-8"))
2849
return hash.hexdigest()
2851
def enable_proxy(host, port):
2852
"""Enable a default web proxy."""
2855
global __proxy_enabled
2857
__proxy = [host, _number(port)]
2858
__proxy_enabled = True
2860
def disable_proxy():
2861
"""Disable using the web proxy."""
2863
global __proxy_enabled
2865
__proxy_enabled = False
2867
def is_proxy_enabled():
2868
"""Returns True if a web proxy is enabled."""
2870
global __proxy_enabled
2872
return __proxy_enabled
2875
"""Returns proxy details."""
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.
2890
thread = _ThreadedCall(sender, call, call_args, callback, callback_args)
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.
2898
global __cache_shelf
2899
global __cache_filename
2901
if not cache_filename:
2902
cache_filename = tempfile.mktemp(prefix="pylast_tmp_")
2904
__cache_filename = cache_filename
2905
__cache_shelf = shelve.open(__cache_filename)
2907
def disable_caching():
2908
"""Disables all caching features."""
2910
global __cache_shelf
2911
__cache_shelf = None
2914
global __cache_shelf
2916
__cache_shelf.close()
2917
__cache_shelf = None
2919
def is_caching_enabled():
2920
"""Returns True if caching is enabled."""
2922
global __cache_shelf
2923
return not (__cache_shelf == None)
2925
def get_cache_filename():
2926
"""Returns filename of the cache db in use."""
2928
global __cache_filename
2929
return __cache_filename
2931
def get_cache_shelf():
2932
"""Returns the Shelf object used for caching."""
2934
global __cache_shelf
2935
return __cache_shelf
2937
def _extract(node, name, index = 0):
2938
"""Extracts a value from the xml string"""
2940
nodes = node.getElementsByTagName(name)
2943
if nodes[index].firstChild:
2944
return nodes[index].firstChild.data.strip()
2948
def _extract_all(node, name, limit_count = None):
2949
"""Extracts all the values from the xml string. returning a list."""
2953
for i in range(0, len(node.getElementsByTagName(name))):
2954
if len(list) == limit_count:
2957
list.append(_extract(node, name, i))
2961
def _get_url_safe(text):
2962
"""Does all kinds of tricks on a text to make it safe to use in a url."""
2964
if type(text) == type(unicode()):
2967
return urllib.quote_plus(urllib.quote_plus(text)).lower()
2969
def _number(string):
2970
"""Extracts an int from a string. Returns a 0 if None or an empty string was passed."""
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."""
2983
return AlbumSearch(album_name, api_key, api_secret, session_key)
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."""
2989
return ArtistSearch(artist_name, api_key, api_secret, session_key)
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."""
2995
return TagSearch(tag_name, api_key, api_secret, session_key)
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."""
3002
return TrackSearch(artist_name, track_name, api_key, api_secret, session_key)
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."""
3009
return VenueSearch(venue_name, country_name, api_key, api_secret, session_key)
3011
def extract_items(topitems_or_libraryitems):
3012
"""Extracts a sequence of items from a sequence of TopItem or LibraryItem objects."""
3015
for i in topitems_or_libraryitems:
3016
list.append(i.get_item())
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."""
3023
doc = _Request("tag.getTopTags", dict(), api_key, api_secret, session_key).execute(True)
3025
for node in doc.getElementsByTagName("tag"):
3026
tag = Tag(_extract(node, "name"), api_key, api_secret, session_key)
3027
weight = _extract(node, "count")
3029
list.append(TopItem(tag, weight))
3033
def get_track_by_mbid(mbid, api_key, api_secret, session_key):
3034
"""Looks up a track by its MusicBrainz ID."""
3036
params = {"mbid": unicode(mbid)}
3038
doc = _Request("track.getInfo", params, api_key, api_secret, session_key).execute(True)
3040
return Track(_extract(doc, "name", 1), _extract(doc, "name"), api_key, api_secret, session_key)
3042
def get_artist_by_mbid(mbid, api_key, api_secret, session_key):
3043
"""Loooks up an artist by its MusicBrainz ID."""
3045
params = {"mbid": unicode(mbid)}
3047
doc = _Request("artist.getInfo", params, api_key, api_secret, session_key).execute(True)
3049
return Artist(_extract(doc, "name"), api_key, api_secret, session_key)
3051
def get_album_by_mbid(mbid, api_key, api_secret, session_key):
3052
"""Looks up an album by its MusicBrainz ID."""
3054
params = {"mbid": unicode(mbid)}
3056
doc = _Request("album.getInfo", params, api_key, api_secret, session_key).execute(True)
3058
return Album(_extract(doc, "artist"), _extract(doc, "name"), api_key, api_secret, session_key)
3061
"""Makes sure that web service calls are at least a second apart."""
3063
global __last_call_time
3065
# delay time in seconds
3069
if (now - __last_call_time) < DELAY_TIME:
3072
__last_call_time = now
3075
"""Clears the cache data and starts fresh."""
3080
if not os.path.exists(__cache_dir):
3083
for file in os.listdir(__cache_dir):
3084
os.remove(os.path.join(__cache_dir, file))
3088
# ------------------------------------------------------------
3090
class ScrobblingException(Exception):
3091
def __inint__(self, message):
3092
Exception.__init__(self)
3093
self.message = message
3098
class BannedClient(ScrobblingException):
3100
ScrobblingException.__init__(self, "This version of the client has been banned")
3102
class BadAuthentication(ScrobblingException):
3104
ScrobblingException.__init__(self, "Bad authentication token")
3106
class BadTime(ScrobblingException):
3108
ScrobblingException.__init__(self, "Time provided is not close enough to current time")
3110
class BadSession(ScrobblingException):
3112
ScrobblingException.__init__(self, "Bad session id, consider re-handshaking")
3114
class _ScrobblerRequest(object):
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("/"):]
3122
"""Returns a string response of this request."""
3124
connection = httplib.HTTPConnection(self.hostname)
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)
3133
"Content-type": "application/x-www-form-urlencoded",
3134
"Accept-Charset": "utf-8",
3135
"User-Agent": __name__ + "/" + __version__,
3136
"HOST": self.hostname
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()
3144
self._check_response_for_errors(response)
3145
debug("Scrobbler Response:\n\t" + response)
3149
def _check_response_for_errors(self, response):
3150
"""When passed a string response it checks for erros, raising
3151
any exceptions as necessary."""
3153
lines = response.split("\n")
3154
status_line = lines[0]
3156
if status_line == "OK":
3158
elif status_line == "BANNED":
3159
raise BannedClient()
3160
elif status_line == "BADAUTH":
3161
raise BadAuthenticationException()
3162
elif status_line == "BADTIME":
3164
elif status_line == "BADSESSION":
3166
elif status_line.startswith("FAILED "):
3167
reason = status_line[status_line.find("FAILED ")+len("FAILED "):]
3168
raise ScrobblingException(reason)
3170
class Scrobbler(object):
3171
"""A class for scrobbling tracks to Last.fm"""
3174
nowplaying_url = None
3175
submissions_url = None
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
3183
def _do_handshake(self):
3184
"""Handshakes with the server"""
3186
timestamp = str(int(time.time()))
3187
token = md5(self.password + timestamp)
3189
params = {"hs": "true", "p": "1.2.1", "c": self.client_id,
3190
"v": self.client_version, "u": self.username, "t": timestamp,
3193
global SUBMISSION_SERVER
3194
response = _ScrobblerRequest(SUBMISSION_SERVER, params).execute().split("\n")
3196
self.session_id = response[1]
3197
self.nowplaying_url = response[2]
3198
self.submissions_url = response[3]
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."""
3204
if not self.session_id or new:
3205
debug("Doing a scrobbling handshake")
3206
self._do_handshake()
3208
return self.session_id
3210
def report_now_playing(self, artist, title, album = "", duration = "", track_number = "", mbid = ""):
3212
params = {"s": self._get_session_id(), "a": artist, "t": title,
3213
"b": album, "l": duration, "n": track_number, "m": mbid}
3215
response = _ScrobblerRequest(self.nowplaying_url, params).execute()
3218
_ScrobblerRequest(self.nowplaying_url, params).execute()
3220
self._do_handshake()
3221
self.report_now_playing(artist, title, album, duration, track_number, mbid)
3223
info(artist + " - " + title + " was reported as now-playing")
3225
def scrobble(self, artist, title, time_started, source, mode, duration, album="", track_number="", mbid=""):
3226
"""Scrobble a track. parameters:
3227
artist: Artist name.
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.
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}
3250
response = _ScrobblerRequest(self.submissions_url, params).execute()
3251
info(artist + " - " + title + " was scrobbled")
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'