annotate toolboxes/_python_lib/pylast/pylast.py @ 0:e9a9cd732c1e tip

first hg version after svn
author wolffd
date Tue, 10 Feb 2015 15:05:51 +0000
parents
children
rev   line source
wolffd@0 1 # -*- coding: utf-8 -*-
wolffd@0 2 #
wolffd@0 3 # pylast - A Python interface to Last.fm (and other API compatible social networks)
wolffd@0 4 #
wolffd@0 5 # Copyright 2008-2010 Amr Hassan
wolffd@0 6 #
wolffd@0 7 # Licensed under the Apache License, Version 2.0 (the "License");
wolffd@0 8 # you may not use this file except in compliance with the License.
wolffd@0 9 # You may obtain a copy of the License at
wolffd@0 10 #
wolffd@0 11 # http://www.apache.org/licenses/LICENSE-2.0
wolffd@0 12 #
wolffd@0 13 # Unless required by applicable law or agreed to in writing, software
wolffd@0 14 # distributed under the License is distributed on an "AS IS" BASIS,
wolffd@0 15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
wolffd@0 16 # See the License for the specific language governing permissions and
wolffd@0 17 # limitations under the License.
wolffd@0 18 #
wolffd@0 19 # http://code.google.com/p/pylast/
wolffd@0 20
wolffd@0 21 __version__ = '0.5'
wolffd@0 22 __author__ = 'Amr Hassan'
wolffd@0 23 __copyright__ = "Copyright (C) 2008-2010 Amr Hassan"
wolffd@0 24 __license__ = "apache2"
wolffd@0 25 __email__ = 'amr.hassan@gmail.com'
wolffd@0 26
wolffd@0 27 import hashlib
wolffd@0 28 from xml.dom import minidom
wolffd@0 29 import xml.dom
wolffd@0 30 import time
wolffd@0 31 import shelve
wolffd@0 32 import tempfile
wolffd@0 33 import sys
wolffd@0 34 import collections
wolffd@0 35 import warnings
wolffd@0 36
wolffd@0 37 def _deprecation_warning(message):
wolffd@0 38 warnings.warn(message, DeprecationWarning)
wolffd@0 39
wolffd@0 40 if sys.version_info[0] == 3:
wolffd@0 41 from http.client import HTTPConnection
wolffd@0 42 import html.entities as htmlentitydefs
wolffd@0 43 from urllib.parse import splithost as url_split_host
wolffd@0 44 from urllib.parse import quote_plus as url_quote_plus
wolffd@0 45
wolffd@0 46 unichr = chr
wolffd@0 47
wolffd@0 48 elif sys.version_info[0] == 2:
wolffd@0 49 from httplib import HTTPConnection
wolffd@0 50 import htmlentitydefs
wolffd@0 51 from urllib import splithost as url_split_host
wolffd@0 52 from urllib import quote_plus as url_quote_plus
wolffd@0 53
wolffd@0 54 STATUS_INVALID_SERVICE = 2
wolffd@0 55 STATUS_INVALID_METHOD = 3
wolffd@0 56 STATUS_AUTH_FAILED = 4
wolffd@0 57 STATUS_INVALID_FORMAT = 5
wolffd@0 58 STATUS_INVALID_PARAMS = 6
wolffd@0 59 STATUS_INVALID_RESOURCE = 7
wolffd@0 60 STATUS_TOKEN_ERROR = 8
wolffd@0 61 STATUS_INVALID_SK = 9
wolffd@0 62 STATUS_INVALID_API_KEY = 10
wolffd@0 63 STATUS_OFFLINE = 11
wolffd@0 64 STATUS_SUBSCRIBERS_ONLY = 12
wolffd@0 65 STATUS_INVALID_SIGNATURE = 13
wolffd@0 66 STATUS_TOKEN_UNAUTHORIZED = 14
wolffd@0 67 STATUS_TOKEN_EXPIRED = 15
wolffd@0 68
wolffd@0 69 EVENT_ATTENDING = '0'
wolffd@0 70 EVENT_MAYBE_ATTENDING = '1'
wolffd@0 71 EVENT_NOT_ATTENDING = '2'
wolffd@0 72
wolffd@0 73 PERIOD_OVERALL = 'overall'
wolffd@0 74 PERIOD_7DAYS = "7day"
wolffd@0 75 PERIOD_3MONTHS = '3month'
wolffd@0 76 PERIOD_6MONTHS = '6month'
wolffd@0 77 PERIOD_12MONTHS = '12month'
wolffd@0 78
wolffd@0 79 DOMAIN_ENGLISH = 0
wolffd@0 80 DOMAIN_GERMAN = 1
wolffd@0 81 DOMAIN_SPANISH = 2
wolffd@0 82 DOMAIN_FRENCH = 3
wolffd@0 83 DOMAIN_ITALIAN = 4
wolffd@0 84 DOMAIN_POLISH = 5
wolffd@0 85 DOMAIN_PORTUGUESE = 6
wolffd@0 86 DOMAIN_SWEDISH = 7
wolffd@0 87 DOMAIN_TURKISH = 8
wolffd@0 88 DOMAIN_RUSSIAN = 9
wolffd@0 89 DOMAIN_JAPANESE = 10
wolffd@0 90 DOMAIN_CHINESE = 11
wolffd@0 91
wolffd@0 92 COVER_SMALL = 0
wolffd@0 93 COVER_MEDIUM = 1
wolffd@0 94 COVER_LARGE = 2
wolffd@0 95 COVER_EXTRA_LARGE = 3
wolffd@0 96 COVER_MEGA = 4
wolffd@0 97
wolffd@0 98 IMAGES_ORDER_POPULARITY = "popularity"
wolffd@0 99 IMAGES_ORDER_DATE = "dateadded"
wolffd@0 100
wolffd@0 101
wolffd@0 102 USER_MALE = 'Male'
wolffd@0 103 USER_FEMALE = 'Female'
wolffd@0 104
wolffd@0 105 SCROBBLE_SOURCE_USER = "P"
wolffd@0 106 SCROBBLE_SOURCE_NON_PERSONALIZED_BROADCAST = "R"
wolffd@0 107 SCROBBLE_SOURCE_PERSONALIZED_BROADCAST = "E"
wolffd@0 108 SCROBBLE_SOURCE_LASTFM = "L"
wolffd@0 109 SCROBBLE_SOURCE_UNKNOWN = "U"
wolffd@0 110
wolffd@0 111 SCROBBLE_MODE_PLAYED = ""
wolffd@0 112 SCROBBLE_MODE_LOVED = "L"
wolffd@0 113 SCROBBLE_MODE_BANNED = "B"
wolffd@0 114 SCROBBLE_MODE_SKIPPED = "S"
wolffd@0 115
wolffd@0 116 class _Network(object):
wolffd@0 117 """
wolffd@0 118 A music social network website that is Last.fm or one exposing a Last.fm compatible API
wolffd@0 119 """
wolffd@0 120
wolffd@0 121 def __init__(self, name, homepage, ws_server, api_key, api_secret, session_key, submission_server, username, password_hash,
wolffd@0 122 domain_names, urls):
wolffd@0 123 """
wolffd@0 124 name: the name of the network
wolffd@0 125 homepage: the homepage url
wolffd@0 126 ws_server: the url of the webservices server
wolffd@0 127 api_key: a provided API_KEY
wolffd@0 128 api_secret: a provided API_SECRET
wolffd@0 129 session_key: a generated session_key or None
wolffd@0 130 submission_server: the url of the server to which tracks are submitted (scrobbled)
wolffd@0 131 username: a username of a valid user
wolffd@0 132 password_hash: the output of pylast.md5(password) where password is the user's password
wolffd@0 133 domain_names: a dict mapping each DOMAIN_* value to a string domain name
wolffd@0 134 urls: a dict mapping types to urls
wolffd@0 135
wolffd@0 136 if username and password_hash were provided and not session_key, session_key will be
wolffd@0 137 generated automatically when needed.
wolffd@0 138
wolffd@0 139 Either a valid session_key or a combination of username and password_hash must be present for scrobbling.
wolffd@0 140
wolffd@0 141 You should use a preconfigured network object through a get_*_network(...) method instead of creating an object
wolffd@0 142 of this class, unless you know what you're doing.
wolffd@0 143 """
wolffd@0 144
wolffd@0 145 self.name = name
wolffd@0 146 self.homepage = homepage
wolffd@0 147 self.ws_server = ws_server
wolffd@0 148 self.api_key = api_key
wolffd@0 149 self.api_secret = api_secret
wolffd@0 150 self.session_key = session_key
wolffd@0 151 self.submission_server = submission_server
wolffd@0 152 self.username = username
wolffd@0 153 self.password_hash = password_hash
wolffd@0 154 self.domain_names = domain_names
wolffd@0 155 self.urls = urls
wolffd@0 156
wolffd@0 157 self.cache_backend = None
wolffd@0 158 self.proxy_enabled = False
wolffd@0 159 self.proxy = None
wolffd@0 160 self.last_call_time = 0
wolffd@0 161
wolffd@0 162 #generate a session_key if necessary
wolffd@0 163 if (self.api_key and self.api_secret) and not self.session_key and (self.username and self.password_hash):
wolffd@0 164 sk_gen = SessionKeyGenerator(self)
wolffd@0 165 self.session_key = sk_gen.get_session_key(self.username, self.password_hash)
wolffd@0 166
wolffd@0 167 """def __repr__(self):
wolffd@0 168 attributes = ("name", "homepage", "ws_server", "api_key", "api_secret", "session_key", "submission_server",
wolffd@0 169 "username", "password_hash", "domain_names", "urls")
wolffd@0 170
wolffd@0 171 text = "pylast._Network(%s)"
wolffd@0 172 args = []
wolffd@0 173 for attr in attributes:
wolffd@0 174 args.append("=".join((attr, repr(getattr(self, attr)))))
wolffd@0 175
wolffd@0 176 return text % ", ".join(args)
wolffd@0 177 """
wolffd@0 178
wolffd@0 179 def __str__(self):
wolffd@0 180 return "The %s Network" %self.name
wolffd@0 181
wolffd@0 182 def get_artist(self, artist_name):
wolffd@0 183 """
wolffd@0 184 Return an Artist object
wolffd@0 185 """
wolffd@0 186
wolffd@0 187 return Artist(artist_name, self)
wolffd@0 188
wolffd@0 189 def get_track(self, artist, title):
wolffd@0 190 """
wolffd@0 191 Return a Track object
wolffd@0 192 """
wolffd@0 193
wolffd@0 194 return Track(artist, title, self)
wolffd@0 195
wolffd@0 196 def get_album(self, artist, title):
wolffd@0 197 """
wolffd@0 198 Return an Album object
wolffd@0 199 """
wolffd@0 200
wolffd@0 201 return Album(artist, title, self)
wolffd@0 202
wolffd@0 203 def get_authenticated_user(self):
wolffd@0 204 """
wolffd@0 205 Returns the authenticated user
wolffd@0 206 """
wolffd@0 207
wolffd@0 208 return AuthenticatedUser(self)
wolffd@0 209
wolffd@0 210 def get_country(self, country_name):
wolffd@0 211 """
wolffd@0 212 Returns a country object
wolffd@0 213 """
wolffd@0 214
wolffd@0 215 return Country(country_name, self)
wolffd@0 216
wolffd@0 217 def get_group(self, name):
wolffd@0 218 """
wolffd@0 219 Returns a Group object
wolffd@0 220 """
wolffd@0 221
wolffd@0 222 return Group(name, self)
wolffd@0 223
wolffd@0 224 def get_user(self, username):
wolffd@0 225 """
wolffd@0 226 Returns a user object
wolffd@0 227 """
wolffd@0 228
wolffd@0 229 return User(username, self)
wolffd@0 230
wolffd@0 231 def get_tag(self, name):
wolffd@0 232 """
wolffd@0 233 Returns a tag object
wolffd@0 234 """
wolffd@0 235
wolffd@0 236 return Tag(name, self)
wolffd@0 237
wolffd@0 238 def get_scrobbler(self, client_id, client_version):
wolffd@0 239 """
wolffd@0 240 Returns a Scrobbler object used for submitting tracks to the server
wolffd@0 241
wolffd@0 242 Quote from http://www.last.fm/api/submissions:
wolffd@0 243 ========
wolffd@0 244 Client identifiers are used to provide a centrally managed database of
wolffd@0 245 the client versions, allowing clients to be banned if they are found to
wolffd@0 246 be behaving undesirably. The client ID is associated with a version
wolffd@0 247 number on the server, however these are only incremented if a client is
wolffd@0 248 banned and do not have to reflect the version of the actual client application.
wolffd@0 249
wolffd@0 250 During development, clients which have not been allocated an identifier should
wolffd@0 251 use the identifier tst, with a version number of 1.0. Do not distribute code or
wolffd@0 252 client implementations which use this test identifier. Do not use the identifiers
wolffd@0 253 used by other clients.
wolffd@0 254 =========
wolffd@0 255
wolffd@0 256 To obtain a new client identifier please contact:
wolffd@0 257 * Last.fm: submissions@last.fm
wolffd@0 258 * # TODO: list others
wolffd@0 259
wolffd@0 260 ...and provide us with the name of your client and its homepage address.
wolffd@0 261 """
wolffd@0 262
wolffd@0 263 _deprecation_warning("Use _Network.scrobble(...), _Network.scrobble_many(...), and Netowrk.update_now_playing(...) instead")
wolffd@0 264
wolffd@0 265 return Scrobbler(self, client_id, client_version)
wolffd@0 266
wolffd@0 267 def _get_language_domain(self, domain_language):
wolffd@0 268 """
wolffd@0 269 Returns the mapped domain name of the network to a DOMAIN_* value
wolffd@0 270 """
wolffd@0 271
wolffd@0 272 if domain_language in self.domain_names:
wolffd@0 273 return self.domain_names[domain_language]
wolffd@0 274
wolffd@0 275 def _get_url(self, domain, type):
wolffd@0 276 return "http://%s/%s" %(self._get_language_domain(domain), self.urls[type])
wolffd@0 277
wolffd@0 278 def _get_ws_auth(self):
wolffd@0 279 """
wolffd@0 280 Returns a (API_KEY, API_SECRET, SESSION_KEY) tuple.
wolffd@0 281 """
wolffd@0 282 return (self.api_key, self.api_secret, self.session_key)
wolffd@0 283
wolffd@0 284 def _delay_call(self):
wolffd@0 285 """
wolffd@0 286 Makes sure that web service calls are at least a second apart
wolffd@0 287 """
wolffd@0 288
wolffd@0 289 # delay time in seconds
wolffd@0 290 DELAY_TIME = 1.0
wolffd@0 291 now = time.time()
wolffd@0 292
wolffd@0 293 if (now - self.last_call_time) < DELAY_TIME:
wolffd@0 294 time.sleep(1)
wolffd@0 295
wolffd@0 296 self.last_call_time = now
wolffd@0 297
wolffd@0 298 def create_new_playlist(self, title, description):
wolffd@0 299 """
wolffd@0 300 Creates a playlist for the authenticated user and returns it
wolffd@0 301 title: The title of the new playlist.
wolffd@0 302 description: The description of the new playlist.
wolffd@0 303 """
wolffd@0 304
wolffd@0 305 params = {}
wolffd@0 306 params['title'] = title
wolffd@0 307 params['description'] = description
wolffd@0 308
wolffd@0 309 doc = _Request(self, 'playlist.create', params).execute(False)
wolffd@0 310
wolffd@0 311 e_id = doc.getElementsByTagName("id")[0].firstChild.data
wolffd@0 312 user = doc.getElementsByTagName('playlists')[0].getAttribute('user')
wolffd@0 313
wolffd@0 314 return Playlist(user, e_id, self)
wolffd@0 315
wolffd@0 316 def get_top_tags(self, limit=None):
wolffd@0 317 """Returns a sequence of the most used tags as a sequence of TopItem objects."""
wolffd@0 318
wolffd@0 319 doc = _Request(self, "tag.getTopTags").execute(True)
wolffd@0 320 seq = []
wolffd@0 321 for node in doc.getElementsByTagName("tag"):
wolffd@0 322 tag = Tag(_extract(node, "name"), self)
wolffd@0 323 weight = _number(_extract(node, "count"))
wolffd@0 324
wolffd@0 325 seq.append(TopItem(tag, weight))
wolffd@0 326
wolffd@0 327 if limit:
wolffd@0 328 seq = seq[:limit]
wolffd@0 329
wolffd@0 330 return seq
wolffd@0 331
wolffd@0 332 def enable_proxy(self, host, port):
wolffd@0 333 """Enable a default web proxy"""
wolffd@0 334
wolffd@0 335 self.proxy = [host, _number(port)]
wolffd@0 336 self.proxy_enabled = True
wolffd@0 337
wolffd@0 338 def disable_proxy(self):
wolffd@0 339 """Disable using the web proxy"""
wolffd@0 340
wolffd@0 341 self.proxy_enabled = False
wolffd@0 342
wolffd@0 343 def is_proxy_enabled(self):
wolffd@0 344 """Returns True if a web proxy is enabled."""
wolffd@0 345
wolffd@0 346 return self.proxy_enabled
wolffd@0 347
wolffd@0 348 def _get_proxy(self):
wolffd@0 349 """Returns proxy details."""
wolffd@0 350
wolffd@0 351 return self.proxy
wolffd@0 352
wolffd@0 353 def enable_caching(self, file_path = None):
wolffd@0 354 """Enables caching request-wide for all cachable calls.
wolffd@0 355 In choosing the backend used for caching, it will try _SqliteCacheBackend first if
wolffd@0 356 the module sqlite3 is present. If not, it will fallback to _ShelfCacheBackend which uses shelve.Shelf objects.
wolffd@0 357
wolffd@0 358 * file_path: A file path for the backend storage file. If
wolffd@0 359 None set, a temp file would probably be created, according the backend.
wolffd@0 360 """
wolffd@0 361
wolffd@0 362 if not file_path:
wolffd@0 363 file_path = tempfile.mktemp(prefix="pylast_tmp_")
wolffd@0 364
wolffd@0 365 self.cache_backend = _ShelfCacheBackend(file_path)
wolffd@0 366
wolffd@0 367 def disable_caching(self):
wolffd@0 368 """Disables all caching features."""
wolffd@0 369
wolffd@0 370 self.cache_backend = None
wolffd@0 371
wolffd@0 372 def is_caching_enabled(self):
wolffd@0 373 """Returns True if caching is enabled."""
wolffd@0 374
wolffd@0 375 return not (self.cache_backend == None)
wolffd@0 376
wolffd@0 377 def _get_cache_backend(self):
wolffd@0 378
wolffd@0 379 return self.cache_backend
wolffd@0 380
wolffd@0 381 def search_for_album(self, album_name):
wolffd@0 382 """Searches for an album by its name. Returns a AlbumSearch object.
wolffd@0 383 Use get_next_page() to retreive sequences of results."""
wolffd@0 384
wolffd@0 385 return AlbumSearch(album_name, self)
wolffd@0 386
wolffd@0 387 def search_for_artist(self, artist_name):
wolffd@0 388 """Searches of an artist by its name. Returns a ArtistSearch object.
wolffd@0 389 Use get_next_page() to retreive sequences of results."""
wolffd@0 390
wolffd@0 391 return ArtistSearch(artist_name, self)
wolffd@0 392
wolffd@0 393 def search_for_tag(self, tag_name):
wolffd@0 394 """Searches of a tag by its name. Returns a TagSearch object.
wolffd@0 395 Use get_next_page() to retreive sequences of results."""
wolffd@0 396
wolffd@0 397 return TagSearch(tag_name, self)
wolffd@0 398
wolffd@0 399 def search_for_track(self, artist_name, track_name):
wolffd@0 400 """Searches of a track by its name and its artist. Set artist to an empty string if not available.
wolffd@0 401 Returns a TrackSearch object.
wolffd@0 402 Use get_next_page() to retreive sequences of results."""
wolffd@0 403
wolffd@0 404 return TrackSearch(artist_name, track_name, self)
wolffd@0 405
wolffd@0 406 def search_for_venue(self, venue_name, country_name):
wolffd@0 407 """Searches of a venue by its name and its country. Set country_name to an empty string if not available.
wolffd@0 408 Returns a VenueSearch object.
wolffd@0 409 Use get_next_page() to retreive sequences of results."""
wolffd@0 410
wolffd@0 411 return VenueSearch(venue_name, country_name, self)
wolffd@0 412
wolffd@0 413 def get_track_by_mbid(self, mbid):
wolffd@0 414 """Looks up a track by its MusicBrainz ID"""
wolffd@0 415
wolffd@0 416 params = {"mbid": mbid}
wolffd@0 417
wolffd@0 418 doc = _Request(self, "track.getInfo", params).execute(True)
wolffd@0 419
wolffd@0 420 return Track(_extract(doc, "name", 1), _extract(doc, "name"), self)
wolffd@0 421
wolffd@0 422 def get_artist_by_mbid(self, mbid):
wolffd@0 423 """Loooks up an artist by its MusicBrainz ID"""
wolffd@0 424
wolffd@0 425 params = {"mbid": mbid}
wolffd@0 426
wolffd@0 427 doc = _Request(self, "artist.getInfo", params).execute(True)
wolffd@0 428
wolffd@0 429 return Artist(_extract(doc, "name"), self)
wolffd@0 430
wolffd@0 431 def get_album_by_mbid(self, mbid):
wolffd@0 432 """Looks up an album by its MusicBrainz ID"""
wolffd@0 433
wolffd@0 434 params = {"mbid": mbid}
wolffd@0 435
wolffd@0 436 doc = _Request(self, "album.getInfo", params).execute(True)
wolffd@0 437
wolffd@0 438 return Album(_extract(doc, "artist"), _extract(doc, "name"), self)
wolffd@0 439
wolffd@0 440 def update_now_playing(self, artist, title, album = None, album_artist = None,
wolffd@0 441 duration = None, track_number = None, mbid = None, context = None):
wolffd@0 442 """
wolffd@0 443 Used to notify Last.fm that a user has started listening to a track.
wolffd@0 444
wolffd@0 445 Parameters:
wolffd@0 446 artist (Required) : The artist name
wolffd@0 447 title (Required) : The track title
wolffd@0 448 album (Optional) : The album name.
wolffd@0 449 album_artist (Optional) : The album artist - if this differs from the track artist.
wolffd@0 450 duration (Optional) : The length of the track in seconds.
wolffd@0 451 track_number (Optional) : The track number of the track on the album.
wolffd@0 452 mbid (Optional) : The MusicBrainz Track ID.
wolffd@0 453 context (Optional) : Sub-client version (not public, only enabled for certain API keys)
wolffd@0 454 """
wolffd@0 455
wolffd@0 456 params = {"track": title, "artist": artist}
wolffd@0 457
wolffd@0 458 if album: params["album"] = album
wolffd@0 459 if album_artist: params["albumArtist"] = album_artist
wolffd@0 460 if context: params["context"] = context
wolffd@0 461 if track_number: params["trackNumber"] = track_number
wolffd@0 462 if mbid: params["mbid"] = mbid
wolffd@0 463 if duration: params["duration"] = duration
wolffd@0 464
wolffd@0 465 _Request(self, "track.updateNowPlaying", params).execute()
wolffd@0 466
wolffd@0 467 def scrobble(self, artist, title, timestamp, album = None, album_artist = None, track_number = None,
wolffd@0 468 duration = None, stream_id = None, context = None, mbid = None):
wolffd@0 469
wolffd@0 470 """Used to add a track-play to a user's profile.
wolffd@0 471
wolffd@0 472 Parameters:
wolffd@0 473 artist (Required) : The artist name.
wolffd@0 474 title (Required) : The track name.
wolffd@0 475 timestamp (Required) : The time the track started playing, in UNIX timestamp format (integer number of seconds since 00:00:00, January 1st 1970 UTC). This must be in the UTC time zone.
wolffd@0 476 album (Optional) : The album name.
wolffd@0 477 album_artist (Optional) : The album artist - if this differs from the track artist.
wolffd@0 478 context (Optional) : Sub-client version (not public, only enabled for certain API keys)
wolffd@0 479 stream_id (Optional) : The stream id for this track received from the radio.getPlaylist service.
wolffd@0 480 track_number (Optional) : The track number of the track on the album.
wolffd@0 481 mbid (Optional) : The MusicBrainz Track ID.
wolffd@0 482 duration (Optional) : The length of the track in seconds.
wolffd@0 483 """
wolffd@0 484
wolffd@0 485 return self.scrobble_many(({"artist": artist, "title": title, "timestamp": timestamp, "album": album, "album_artist": album_artist,
wolffd@0 486 "track_number": track_number, "duration": duration, "stream_id": stream_id, "context": context, "mbid": mbid},))
wolffd@0 487
wolffd@0 488 def scrobble_many(self, tracks):
wolffd@0 489 """
wolffd@0 490 Used to scrobble a batch of tracks at once. The parameter tracks is a sequence of dicts per
wolffd@0 491 track containing the keyword arguments as if passed to the scrobble() method.
wolffd@0 492 """
wolffd@0 493
wolffd@0 494 tracks_to_scrobble = tracks[:50]
wolffd@0 495 if len(tracks) > 50:
wolffd@0 496 remaining_tracks = tracks[50:]
wolffd@0 497 else:
wolffd@0 498 remaining_tracks = None
wolffd@0 499
wolffd@0 500 params = {}
wolffd@0 501 for i in range(len(tracks_to_scrobble)):
wolffd@0 502
wolffd@0 503 params["artist[%d]" % i] = tracks_to_scrobble[i]["artist"]
wolffd@0 504 params["track[%d]" % i] = tracks_to_scrobble[i]["title"]
wolffd@0 505
wolffd@0 506 additional_args = ("timestamp", "album", "album_artist", "context", "stream_id", "track_number", "mbid", "duration")
wolffd@0 507 args_map_to = {"album_artist": "albumArtist", "track_number": "trackNumber", "stream_id": "streamID"} # so friggin lazy
wolffd@0 508
wolffd@0 509 for arg in additional_args:
wolffd@0 510
wolffd@0 511 if arg in tracks_to_scrobble[i] and tracks_to_scrobble[i][arg]:
wolffd@0 512 if arg in args_map_to:
wolffd@0 513 maps_to = args_map_to[arg]
wolffd@0 514 else:
wolffd@0 515 maps_to = arg
wolffd@0 516
wolffd@0 517 params["%s[%d]" %(maps_to, i)] = tracks_to_scrobble[i][arg]
wolffd@0 518
wolffd@0 519
wolffd@0 520 _Request(self, "track.scrobble", params).execute()
wolffd@0 521
wolffd@0 522 if remaining_tracks:
wolffd@0 523 self.scrobble_many(remaining_tracks)
wolffd@0 524
wolffd@0 525 class LastFMNetwork(_Network):
wolffd@0 526
wolffd@0 527 """A Last.fm network object
wolffd@0 528
wolffd@0 529 api_key: a provided API_KEY
wolffd@0 530 api_secret: a provided API_SECRET
wolffd@0 531 session_key: a generated session_key or None
wolffd@0 532 username: a username of a valid user
wolffd@0 533 password_hash: the output of pylast.md5(password) where password is the user's password
wolffd@0 534
wolffd@0 535 if username and password_hash were provided and not session_key, session_key will be
wolffd@0 536 generated automatically when needed.
wolffd@0 537
wolffd@0 538 Either a valid session_key or a combination of username and password_hash must be present for scrobbling.
wolffd@0 539
wolffd@0 540 Most read-only webservices only require an api_key and an api_secret, see about obtaining them from:
wolffd@0 541 http://www.last.fm/api/account
wolffd@0 542 """
wolffd@0 543
wolffd@0 544 def __init__(self, api_key="", api_secret="", session_key="", username="", password_hash=""):
wolffd@0 545 _Network.__init__(self,
wolffd@0 546 name = "Last.fm",
wolffd@0 547 homepage = "http://last.fm",
wolffd@0 548 ws_server = ("ws.audioscrobbler.com", "/2.0/"),
wolffd@0 549 api_key = api_key,
wolffd@0 550 api_secret = api_secret,
wolffd@0 551 session_key = session_key,
wolffd@0 552 submission_server = "http://post.audioscrobbler.com:80/",
wolffd@0 553 username = username,
wolffd@0 554 password_hash = password_hash,
wolffd@0 555 domain_names = {
wolffd@0 556 DOMAIN_ENGLISH: 'www.last.fm',
wolffd@0 557 DOMAIN_GERMAN: 'www.lastfm.de',
wolffd@0 558 DOMAIN_SPANISH: 'www.lastfm.es',
wolffd@0 559 DOMAIN_FRENCH: 'www.lastfm.fr',
wolffd@0 560 DOMAIN_ITALIAN: 'www.lastfm.it',
wolffd@0 561 DOMAIN_POLISH: 'www.lastfm.pl',
wolffd@0 562 DOMAIN_PORTUGUESE: 'www.lastfm.com.br',
wolffd@0 563 DOMAIN_SWEDISH: 'www.lastfm.se',
wolffd@0 564 DOMAIN_TURKISH: 'www.lastfm.com.tr',
wolffd@0 565 DOMAIN_RUSSIAN: 'www.lastfm.ru',
wolffd@0 566 DOMAIN_JAPANESE: 'www.lastfm.jp',
wolffd@0 567 DOMAIN_CHINESE: 'cn.last.fm',
wolffd@0 568 },
wolffd@0 569 urls = {
wolffd@0 570 "album": "music/%(artist)s/%(album)s",
wolffd@0 571 "artist": "music/%(artist)s",
wolffd@0 572 "event": "event/%(id)s",
wolffd@0 573 "country": "place/%(country_name)s",
wolffd@0 574 "playlist": "user/%(user)s/library/playlists/%(appendix)s",
wolffd@0 575 "tag": "tag/%(name)s",
wolffd@0 576 "track": "music/%(artist)s/_/%(title)s",
wolffd@0 577 "group": "group/%(name)s",
wolffd@0 578 "user": "user/%(name)s",
wolffd@0 579 }
wolffd@0 580 )
wolffd@0 581
wolffd@0 582 def __repr__(self):
wolffd@0 583 return "pylast.LastFMNetwork(%s)" %(", ".join(("'%s'" %self.api_key, "'%s'" %self.api_secret, "'%s'" %self.session_key,
wolffd@0 584 "'%s'" %self.username, "'%s'" %self.password_hash)))
wolffd@0 585
wolffd@0 586 def __str__(self):
wolffd@0 587 return "LastFM Network"
wolffd@0 588
wolffd@0 589 def get_lastfm_network(api_key="", api_secret="", session_key = "", username = "", password_hash = ""):
wolffd@0 590 """
wolffd@0 591 Returns a preconfigured _Network object for Last.fm
wolffd@0 592
wolffd@0 593 api_key: a provided API_KEY
wolffd@0 594 api_secret: a provided API_SECRET
wolffd@0 595 session_key: a generated session_key or None
wolffd@0 596 username: a username of a valid user
wolffd@0 597 password_hash: the output of pylast.md5(password) where password is the user's password
wolffd@0 598
wolffd@0 599 if username and password_hash were provided and not session_key, session_key will be
wolffd@0 600 generated automatically when needed.
wolffd@0 601
wolffd@0 602 Either a valid session_key or a combination of username and password_hash must be present for scrobbling.
wolffd@0 603
wolffd@0 604 Most read-only webservices only require an api_key and an api_secret, see about obtaining them from:
wolffd@0 605 http://www.last.fm/api/account
wolffd@0 606 """
wolffd@0 607
wolffd@0 608 _deprecation_warning("Create a LastFMNetwork object instead")
wolffd@0 609
wolffd@0 610 return LastFMNetwork(api_key, api_secret, session_key, username, password_hash)
wolffd@0 611
wolffd@0 612 class LibreFMNetwork(_Network):
wolffd@0 613 """
wolffd@0 614 A preconfigured _Network object for Libre.fm
wolffd@0 615
wolffd@0 616 api_key: a provided API_KEY
wolffd@0 617 api_secret: a provided API_SECRET
wolffd@0 618 session_key: a generated session_key or None
wolffd@0 619 username: a username of a valid user
wolffd@0 620 password_hash: the output of pylast.md5(password) where password is the user's password
wolffd@0 621
wolffd@0 622 if username and password_hash were provided and not session_key, session_key will be
wolffd@0 623 generated automatically when needed.
wolffd@0 624 """
wolffd@0 625
wolffd@0 626 def __init__(self, api_key="", api_secret="", session_key = "", username = "", password_hash = ""):
wolffd@0 627
wolffd@0 628 _Network.__init__(self,
wolffd@0 629 name = "Libre.fm",
wolffd@0 630 homepage = "http://alpha.dev.libre.fm",
wolffd@0 631 ws_server = ("alpha.dev.libre.fm", "/2.0/"),
wolffd@0 632 api_key = api_key,
wolffd@0 633 api_secret = api_secret,
wolffd@0 634 session_key = session_key,
wolffd@0 635 submission_server = "http://turtle.libre.fm:80/",
wolffd@0 636 username = username,
wolffd@0 637 password_hash = password_hash,
wolffd@0 638 domain_names = {
wolffd@0 639 DOMAIN_ENGLISH: "alpha.dev.libre.fm",
wolffd@0 640 DOMAIN_GERMAN: "alpha.dev.libre.fm",
wolffd@0 641 DOMAIN_SPANISH: "alpha.dev.libre.fm",
wolffd@0 642 DOMAIN_FRENCH: "alpha.dev.libre.fm",
wolffd@0 643 DOMAIN_ITALIAN: "alpha.dev.libre.fm",
wolffd@0 644 DOMAIN_POLISH: "alpha.dev.libre.fm",
wolffd@0 645 DOMAIN_PORTUGUESE: "alpha.dev.libre.fm",
wolffd@0 646 DOMAIN_SWEDISH: "alpha.dev.libre.fm",
wolffd@0 647 DOMAIN_TURKISH: "alpha.dev.libre.fm",
wolffd@0 648 DOMAIN_RUSSIAN: "alpha.dev.libre.fm",
wolffd@0 649 DOMAIN_JAPANESE: "alpha.dev.libre.fm",
wolffd@0 650 DOMAIN_CHINESE: "alpha.dev.libre.fm",
wolffd@0 651 },
wolffd@0 652 urls = {
wolffd@0 653 "album": "artist/%(artist)s/album/%(album)s",
wolffd@0 654 "artist": "artist/%(artist)s",
wolffd@0 655 "event": "event/%(id)s",
wolffd@0 656 "country": "place/%(country_name)s",
wolffd@0 657 "playlist": "user/%(user)s/library/playlists/%(appendix)s",
wolffd@0 658 "tag": "tag/%(name)s",
wolffd@0 659 "track": "music/%(artist)s/_/%(title)s",
wolffd@0 660 "group": "group/%(name)s",
wolffd@0 661 "user": "user/%(name)s",
wolffd@0 662 }
wolffd@0 663 )
wolffd@0 664
wolffd@0 665 def __repr__(self):
wolffd@0 666 return "pylast.LibreFMNetwork(%s)" %(", ".join(("'%s'" %self.api_key, "'%s'" %self.api_secret, "'%s'" %self.session_key,
wolffd@0 667 "'%s'" %self.username, "'%s'" %self.password_hash)))
wolffd@0 668
wolffd@0 669 def __str__(self):
wolffd@0 670 return "Libre.fm Network"
wolffd@0 671
wolffd@0 672 def get_librefm_network(api_key="", api_secret="", session_key = "", username = "", password_hash = ""):
wolffd@0 673 """
wolffd@0 674 Returns a preconfigured _Network object for Libre.fm
wolffd@0 675
wolffd@0 676 api_key: a provided API_KEY
wolffd@0 677 api_secret: a provided API_SECRET
wolffd@0 678 session_key: a generated session_key or None
wolffd@0 679 username: a username of a valid user
wolffd@0 680 password_hash: the output of pylast.md5(password) where password is the user's password
wolffd@0 681
wolffd@0 682 if username and password_hash were provided and not session_key, session_key will be
wolffd@0 683 generated automatically when needed.
wolffd@0 684 """
wolffd@0 685
wolffd@0 686 _deprecation_warning("DeprecationWarning: Create a LibreFMNetwork object instead")
wolffd@0 687
wolffd@0 688 return LibreFMNetwork(api_key, api_secret, session_key, username, password_hash)
wolffd@0 689
wolffd@0 690 class _ShelfCacheBackend(object):
wolffd@0 691 """Used as a backend for caching cacheable requests."""
wolffd@0 692 def __init__(self, file_path = None):
wolffd@0 693 self.shelf = shelve.open(file_path)
wolffd@0 694
wolffd@0 695 def get_xml(self, key):
wolffd@0 696 return self.shelf[key]
wolffd@0 697
wolffd@0 698 def set_xml(self, key, xml_string):
wolffd@0 699 self.shelf[key] = xml_string
wolffd@0 700
wolffd@0 701 def has_key(self, key):
wolffd@0 702 return key in self.shelf.keys()
wolffd@0 703
wolffd@0 704 class _Request(object):
wolffd@0 705 """Representing an abstract web service operation."""
wolffd@0 706
wolffd@0 707 def __init__(self, network, method_name, params = {}):
wolffd@0 708
wolffd@0 709 self.network = network
wolffd@0 710 self.params = {}
wolffd@0 711
wolffd@0 712 for key in params:
wolffd@0 713 self.params[key] = _unicode(params[key])
wolffd@0 714
wolffd@0 715 (self.api_key, self.api_secret, self.session_key) = network._get_ws_auth()
wolffd@0 716
wolffd@0 717 self.params["api_key"] = self.api_key
wolffd@0 718 self.params["method"] = method_name
wolffd@0 719
wolffd@0 720 if network.is_caching_enabled():
wolffd@0 721 self.cache = network._get_cache_backend()
wolffd@0 722
wolffd@0 723 if self.session_key:
wolffd@0 724 self.params["sk"] = self.session_key
wolffd@0 725 self.sign_it()
wolffd@0 726
wolffd@0 727 def sign_it(self):
wolffd@0 728 """Sign this request."""
wolffd@0 729
wolffd@0 730 if not "api_sig" in self.params.keys():
wolffd@0 731 self.params['api_sig'] = self._get_signature()
wolffd@0 732
wolffd@0 733 def _get_signature(self):
wolffd@0 734 """Returns a 32-character hexadecimal md5 hash of the signature string."""
wolffd@0 735
wolffd@0 736 keys = list(self.params.keys())
wolffd@0 737
wolffd@0 738 keys.sort()
wolffd@0 739
wolffd@0 740 string = ""
wolffd@0 741
wolffd@0 742 for name in keys:
wolffd@0 743 string += name
wolffd@0 744 string += self.params[name]
wolffd@0 745
wolffd@0 746 string += self.api_secret
wolffd@0 747
wolffd@0 748 return md5(string)
wolffd@0 749
wolffd@0 750 def _get_cache_key(self):
wolffd@0 751 """The cache key is a string of concatenated sorted names and values."""
wolffd@0 752
wolffd@0 753 keys = list(self.params.keys())
wolffd@0 754 keys.sort()
wolffd@0 755
wolffd@0 756 cache_key = str()
wolffd@0 757
wolffd@0 758 for key in keys:
wolffd@0 759 if key != "api_sig" and key != "api_key" and key != "sk":
wolffd@0 760 cache_key += key + _string(self.params[key])
wolffd@0 761
wolffd@0 762 return hashlib.sha1(cache_key).hexdigest()
wolffd@0 763
wolffd@0 764 def _get_cached_response(self):
wolffd@0 765 """Returns a file object of the cached response."""
wolffd@0 766
wolffd@0 767 if not self._is_cached():
wolffd@0 768 response = self._download_response()
wolffd@0 769 self.cache.set_xml(self._get_cache_key(), response)
wolffd@0 770
wolffd@0 771 return self.cache.get_xml(self._get_cache_key())
wolffd@0 772
wolffd@0 773 def _is_cached(self):
wolffd@0 774 """Returns True if the request is already in cache."""
wolffd@0 775
wolffd@0 776 return self.cache.has_key(self._get_cache_key())
wolffd@0 777
wolffd@0 778 def _download_response(self):
wolffd@0 779 """Returns a response body string from the server."""
wolffd@0 780
wolffd@0 781 # Delay the call if necessary
wolffd@0 782 #self.network._delay_call() # enable it if you want.
wolffd@0 783
wolffd@0 784 data = []
wolffd@0 785 for name in self.params.keys():
wolffd@0 786 data.append('='.join((name, url_quote_plus(_string(self.params[name])))))
wolffd@0 787 data = '&'.join(data)
wolffd@0 788
wolffd@0 789 headers = {
wolffd@0 790 "Content-type": "application/x-www-form-urlencoded",
wolffd@0 791 'Accept-Charset': 'utf-8',
wolffd@0 792 'User-Agent': "pylast" + '/' + __version__
wolffd@0 793 }
wolffd@0 794
wolffd@0 795 (HOST_NAME, HOST_SUBDIR) = self.network.ws_server
wolffd@0 796
wolffd@0 797 if self.network.is_proxy_enabled():
wolffd@0 798 conn = HTTPConnection(host = self._get_proxy()[0], port = self._get_proxy()[1])
wolffd@0 799
wolffd@0 800 try:
wolffd@0 801 conn.request(method='POST', url="http://" + HOST_NAME + HOST_SUBDIR,
wolffd@0 802 body=data, headers=headers)
wolffd@0 803 except Exception as e:
wolffd@0 804 raise NetworkError(self.network, e)
wolffd@0 805
wolffd@0 806 else:
wolffd@0 807 conn = HTTPConnection(host=HOST_NAME)
wolffd@0 808
wolffd@0 809 try:
wolffd@0 810 conn.request(method='POST', url=HOST_SUBDIR, body=data, headers=headers)
wolffd@0 811 except Exception as e:
wolffd@0 812 raise NetworkError(self.network, e)
wolffd@0 813
wolffd@0 814 try:
wolffd@0 815 response_text = _unicode(conn.getresponse().read())
wolffd@0 816 except Exception as e:
wolffd@0 817 raise MalformedResponseError(self.network, e)
wolffd@0 818
wolffd@0 819 self._check_response_for_errors(response_text)
wolffd@0 820 return response_text
wolffd@0 821
wolffd@0 822 def execute(self, cacheable = False):
wolffd@0 823 """Returns the XML DOM response of the POST Request from the server"""
wolffd@0 824
wolffd@0 825 if self.network.is_caching_enabled() and cacheable:
wolffd@0 826 response = self._get_cached_response()
wolffd@0 827 else:
wolffd@0 828 response = self._download_response()
wolffd@0 829
wolffd@0 830 return minidom.parseString(_string(response))
wolffd@0 831
wolffd@0 832 def _check_response_for_errors(self, response):
wolffd@0 833 """Checks the response for errors and raises one if any exists."""
wolffd@0 834
wolffd@0 835 try:
wolffd@0 836 doc = minidom.parseString(_string(response))
wolffd@0 837 except Exception as e:
wolffd@0 838 raise MalformedResponseError(self.network, e)
wolffd@0 839
wolffd@0 840 e = doc.getElementsByTagName('lfm')[0]
wolffd@0 841
wolffd@0 842 if e.getAttribute('status') != "ok":
wolffd@0 843 e = doc.getElementsByTagName('error')[0]
wolffd@0 844 status = e.getAttribute('code')
wolffd@0 845 details = e.firstChild.data.strip()
wolffd@0 846 raise WSError(self.network, status, details)
wolffd@0 847
wolffd@0 848 class SessionKeyGenerator(object):
wolffd@0 849 """Methods of generating a session key:
wolffd@0 850 1) Web Authentication:
wolffd@0 851 a. network = get_*_network(API_KEY, API_SECRET)
wolffd@0 852 b. sg = SessionKeyGenerator(network)
wolffd@0 853 c. url = sg.get_web_auth_url()
wolffd@0 854 d. Ask the user to open the url and authorize you, and wait for it.
wolffd@0 855 e. session_key = sg.get_web_auth_session_key(url)
wolffd@0 856 2) Username and Password Authentication:
wolffd@0 857 a. network = get_*_network(API_KEY, API_SECRET)
wolffd@0 858 b. username = raw_input("Please enter your username: ")
wolffd@0 859 c. password_hash = pylast.md5(raw_input("Please enter your password: ")
wolffd@0 860 d. session_key = SessionKeyGenerator(network).get_session_key(username, password_hash)
wolffd@0 861
wolffd@0 862 A session key's lifetime is infinie, unless the user provokes the rights of the given API Key.
wolffd@0 863
wolffd@0 864 If you create a Network object with just a API_KEY and API_SECRET and a username and a password_hash, a
wolffd@0 865 SESSION_KEY will be automatically generated for that network and stored in it so you don't have to do this
wolffd@0 866 manually, unless you want to.
wolffd@0 867 """
wolffd@0 868
wolffd@0 869 def __init__(self, network):
wolffd@0 870 self.network = network
wolffd@0 871 self.web_auth_tokens = {}
wolffd@0 872
wolffd@0 873 def _get_web_auth_token(self):
wolffd@0 874 """Retrieves a token from the network for web authentication.
wolffd@0 875 The token then has to be authorized from getAuthURL before creating session.
wolffd@0 876 """
wolffd@0 877
wolffd@0 878 request = _Request(self.network, 'auth.getToken')
wolffd@0 879
wolffd@0 880 # default action is that a request is signed only when
wolffd@0 881 # a session key is provided.
wolffd@0 882 request.sign_it()
wolffd@0 883
wolffd@0 884 doc = request.execute()
wolffd@0 885
wolffd@0 886 e = doc.getElementsByTagName('token')[0]
wolffd@0 887 return e.firstChild.data
wolffd@0 888
wolffd@0 889 def get_web_auth_url(self):
wolffd@0 890 """The user must open this page, and you first, then call get_web_auth_session_key(url) after that."""
wolffd@0 891
wolffd@0 892 token = self._get_web_auth_token()
wolffd@0 893
wolffd@0 894 url = '%(homepage)s/api/auth/?api_key=%(api)s&token=%(token)s' % \
wolffd@0 895 {"homepage": self.network.homepage, "api": self.network.api_key, "token": token}
wolffd@0 896
wolffd@0 897 self.web_auth_tokens[url] = token
wolffd@0 898
wolffd@0 899 return url
wolffd@0 900
wolffd@0 901 def get_web_auth_session_key(self, url):
wolffd@0 902 """Retrieves the session key of a web authorization process by its url."""
wolffd@0 903
wolffd@0 904 if url in self.web_auth_tokens.keys():
wolffd@0 905 token = self.web_auth_tokens[url]
wolffd@0 906 else:
wolffd@0 907 token = "" #that's gonna raise a WSError of an unauthorized token when the request is executed.
wolffd@0 908
wolffd@0 909 request = _Request(self.network, 'auth.getSession', {'token': token})
wolffd@0 910
wolffd@0 911 # default action is that a request is signed only when
wolffd@0 912 # a session key is provided.
wolffd@0 913 request.sign_it()
wolffd@0 914
wolffd@0 915 doc = request.execute()
wolffd@0 916
wolffd@0 917 return doc.getElementsByTagName('key')[0].firstChild.data
wolffd@0 918
wolffd@0 919 def get_session_key(self, username, password_hash):
wolffd@0 920 """Retrieve a session key with a username and a md5 hash of the user's password."""
wolffd@0 921
wolffd@0 922 params = {"username": username, "authToken": md5(username + password_hash)}
wolffd@0 923 request = _Request(self.network, "auth.getMobileSession", params)
wolffd@0 924
wolffd@0 925 # default action is that a request is signed only when
wolffd@0 926 # a session key is provided.
wolffd@0 927 request.sign_it()
wolffd@0 928
wolffd@0 929 doc = request.execute()
wolffd@0 930
wolffd@0 931 return _extract(doc, "key")
wolffd@0 932
wolffd@0 933 TopItem = collections.namedtuple("TopItem", ["item", "weight"])
wolffd@0 934 SimilarItem = collections.namedtuple("SimilarItem", ["item", "match"])
wolffd@0 935 LibraryItem = collections.namedtuple("LibraryItem", ["item", "playcount", "tagcount"])
wolffd@0 936 PlayedTrack = collections.namedtuple("PlayedTrack", ["track", "playback_date", "timestamp"])
wolffd@0 937 LovedTrack = collections.namedtuple("LovedTrack", ["track", "date", "timestamp"])
wolffd@0 938 ImageSizes = collections.namedtuple("ImageSizes", ["original", "large", "largesquare", "medium", "small", "extralarge"])
wolffd@0 939 Image = collections.namedtuple("Image", ["title", "url", "dateadded", "format", "owner", "sizes", "votes"])
wolffd@0 940 Shout = collections.namedtuple("Shout", ["body", "author", "date"])
wolffd@0 941
wolffd@0 942 def _string_output(funct):
wolffd@0 943 def r(*args):
wolffd@0 944 return _string(funct(*args))
wolffd@0 945
wolffd@0 946 return r
wolffd@0 947
wolffd@0 948 def _pad_list(given_list, desired_length, padding = None):
wolffd@0 949 """
wolffd@0 950 Pads a list to be of the desired_length.
wolffd@0 951 """
wolffd@0 952
wolffd@0 953 while len(given_list) < desired_length:
wolffd@0 954 given_list.append(padding)
wolffd@0 955
wolffd@0 956 return given_list
wolffd@0 957
wolffd@0 958 class _BaseObject(object):
wolffd@0 959 """An abstract webservices object."""
wolffd@0 960
wolffd@0 961 network = None
wolffd@0 962
wolffd@0 963 def __init__(self, network):
wolffd@0 964 self.network = network
wolffd@0 965
wolffd@0 966 def _request(self, method_name, cacheable = False, params = None):
wolffd@0 967 if not params:
wolffd@0 968 params = self._get_params()
wolffd@0 969
wolffd@0 970 return _Request(self.network, method_name, params).execute(cacheable)
wolffd@0 971
wolffd@0 972 def _get_params(self):
wolffd@0 973 """Returns the most common set of parameters between all objects."""
wolffd@0 974
wolffd@0 975 return {}
wolffd@0 976
wolffd@0 977 def __hash__(self):
wolffd@0 978 return hash(self.network) + \
wolffd@0 979 hash(str(type(self)) + "".join(list(self._get_params().keys()) + list(self._get_params().values())).lower())
wolffd@0 980
wolffd@0 981 class _Taggable(object):
wolffd@0 982 """Common functions for classes with tags."""
wolffd@0 983
wolffd@0 984 def __init__(self, ws_prefix):
wolffd@0 985 self.ws_prefix = ws_prefix
wolffd@0 986
wolffd@0 987 def add_tags(self, tags):
wolffd@0 988 """Adds one or several tags.
wolffd@0 989 * tags: A sequence of tag names or Tag objects.
wolffd@0 990 """
wolffd@0 991
wolffd@0 992 for tag in tags:
wolffd@0 993 self.add_tag(tag)
wolffd@0 994
wolffd@0 995 def add_tag(self, tag):
wolffd@0 996 """Adds one tag.
wolffd@0 997 * tag: a tag name or a Tag object.
wolffd@0 998 """
wolffd@0 999
wolffd@0 1000 if isinstance(tag, Tag):
wolffd@0 1001 tag = tag.get_name()
wolffd@0 1002
wolffd@0 1003 params = self._get_params()
wolffd@0 1004 params['tags'] = tag
wolffd@0 1005
wolffd@0 1006 self._request(self.ws_prefix + '.addTags', False, params)
wolffd@0 1007
wolffd@0 1008 def remove_tag(self, tag):
wolffd@0 1009 """Remove a user's tag from this object."""
wolffd@0 1010
wolffd@0 1011 if isinstance(tag, Tag):
wolffd@0 1012 tag = tag.get_name()
wolffd@0 1013
wolffd@0 1014 params = self._get_params()
wolffd@0 1015 params['tag'] = tag
wolffd@0 1016
wolffd@0 1017 self._request(self.ws_prefix + '.removeTag', False, params)
wolffd@0 1018
wolffd@0 1019 def get_tags(self):
wolffd@0 1020 """Returns a list of the tags set by the user to this object."""
wolffd@0 1021
wolffd@0 1022 # Uncacheable because it can be dynamically changed by the user.
wolffd@0 1023 params = self._get_params()
wolffd@0 1024
wolffd@0 1025 doc = self._request(self.ws_prefix + '.getTags', False, params)
wolffd@0 1026 tag_names = _extract_all(doc, 'name')
wolffd@0 1027 tags = []
wolffd@0 1028 for tag in tag_names:
wolffd@0 1029 tags.append(Tag(tag, self.network))
wolffd@0 1030
wolffd@0 1031 return tags
wolffd@0 1032
wolffd@0 1033 def remove_tags(self, tags):
wolffd@0 1034 """Removes one or several tags from this object.
wolffd@0 1035 * tags: a sequence of tag names or Tag objects.
wolffd@0 1036 """
wolffd@0 1037
wolffd@0 1038 for tag in tags:
wolffd@0 1039 self.remove_tag(tag)
wolffd@0 1040
wolffd@0 1041 def clear_tags(self):
wolffd@0 1042 """Clears all the user-set tags. """
wolffd@0 1043
wolffd@0 1044 self.remove_tags(*(self.get_tags()))
wolffd@0 1045
wolffd@0 1046 def set_tags(self, tags):
wolffd@0 1047 """Sets this object's tags to only those tags.
wolffd@0 1048 * tags: a sequence of tag names or Tag objects.
wolffd@0 1049 """
wolffd@0 1050
wolffd@0 1051 c_old_tags = []
wolffd@0 1052 old_tags = []
wolffd@0 1053 c_new_tags = []
wolffd@0 1054 new_tags = []
wolffd@0 1055
wolffd@0 1056 to_remove = []
wolffd@0 1057 to_add = []
wolffd@0 1058
wolffd@0 1059 tags_on_server = self.get_tags()
wolffd@0 1060
wolffd@0 1061 for tag in tags_on_server:
wolffd@0 1062 c_old_tags.append(tag.get_name().lower())
wolffd@0 1063 old_tags.append(tag.get_name())
wolffd@0 1064
wolffd@0 1065 for tag in tags:
wolffd@0 1066 c_new_tags.append(tag.lower())
wolffd@0 1067 new_tags.append(tag)
wolffd@0 1068
wolffd@0 1069 for i in range(0, len(old_tags)):
wolffd@0 1070 if not c_old_tags[i] in c_new_tags:
wolffd@0 1071 to_remove.append(old_tags[i])
wolffd@0 1072
wolffd@0 1073 for i in range(0, len(new_tags)):
wolffd@0 1074 if not c_new_tags[i] in c_old_tags:
wolffd@0 1075 to_add.append(new_tags[i])
wolffd@0 1076
wolffd@0 1077 self.remove_tags(to_remove)
wolffd@0 1078 self.add_tags(to_add)
wolffd@0 1079
wolffd@0 1080 def get_top_tags(self, limit=None):
wolffd@0 1081 """Returns a list of the most frequently used Tags on this object."""
wolffd@0 1082
wolffd@0 1083 doc = self._request(self.ws_prefix + '.getTopTags', True)
wolffd@0 1084
wolffd@0 1085 elements = doc.getElementsByTagName('tag')
wolffd@0 1086 seq = []
wolffd@0 1087
wolffd@0 1088 for element in elements:
wolffd@0 1089 tag_name = _extract(element, 'name')
wolffd@0 1090 tagcount = _extract(element, 'count')
wolffd@0 1091
wolffd@0 1092 seq.append(TopItem(Tag(tag_name, self.network), tagcount))
wolffd@0 1093
wolffd@0 1094 if limit:
wolffd@0 1095 seq = seq[:limit]
wolffd@0 1096
wolffd@0 1097 return seq
wolffd@0 1098
wolffd@0 1099 class WSError(Exception):
wolffd@0 1100 """Exception related to the Network web service"""
wolffd@0 1101
wolffd@0 1102 def __init__(self, network, status, details):
wolffd@0 1103 self.status = status
wolffd@0 1104 self.details = details
wolffd@0 1105 self.network = network
wolffd@0 1106
wolffd@0 1107 @_string_output
wolffd@0 1108 def __str__(self):
wolffd@0 1109 return self.details
wolffd@0 1110
wolffd@0 1111 def get_id(self):
wolffd@0 1112 """Returns the exception ID, from one of the following:
wolffd@0 1113 STATUS_INVALID_SERVICE = 2
wolffd@0 1114 STATUS_INVALID_METHOD = 3
wolffd@0 1115 STATUS_AUTH_FAILED = 4
wolffd@0 1116 STATUS_INVALID_FORMAT = 5
wolffd@0 1117 STATUS_INVALID_PARAMS = 6
wolffd@0 1118 STATUS_INVALID_RESOURCE = 7
wolffd@0 1119 STATUS_TOKEN_ERROR = 8
wolffd@0 1120 STATUS_INVALID_SK = 9
wolffd@0 1121 STATUS_INVALID_API_KEY = 10
wolffd@0 1122 STATUS_OFFLINE = 11
wolffd@0 1123 STATUS_SUBSCRIBERS_ONLY = 12
wolffd@0 1124 STATUS_TOKEN_UNAUTHORIZED = 14
wolffd@0 1125 STATUS_TOKEN_EXPIRED = 15
wolffd@0 1126 """
wolffd@0 1127
wolffd@0 1128 return self.status
wolffd@0 1129
wolffd@0 1130 class MalformedResponseError(Exception):
wolffd@0 1131 """Exception conveying a malformed response from Last.fm."""
wolffd@0 1132
wolffd@0 1133 def __init__(self, network, underlying_error):
wolffd@0 1134 self.network = network
wolffd@0 1135 self.underlying_error = underlying_error
wolffd@0 1136
wolffd@0 1137 def __str__(self):
wolffd@0 1138 return "Malformed response from Last.fm. Underlying error: %s" %str(self.underlying_error)
wolffd@0 1139
wolffd@0 1140 class NetworkError(Exception):
wolffd@0 1141 """Exception conveying a problem in sending a request to Last.fm"""
wolffd@0 1142
wolffd@0 1143 def __init__(self, network, underlying_error):
wolffd@0 1144 self.network = network
wolffd@0 1145 self.underlying_error = underlying_error
wolffd@0 1146
wolffd@0 1147 def __str__(self):
wolffd@0 1148 return "NetworkError: %s" %str(self.underlying_error)
wolffd@0 1149
wolffd@0 1150 class Album(_BaseObject, _Taggable):
wolffd@0 1151 """An album."""
wolffd@0 1152
wolffd@0 1153 title = None
wolffd@0 1154 artist = None
wolffd@0 1155
wolffd@0 1156 def __init__(self, artist, title, network):
wolffd@0 1157 """
wolffd@0 1158 Create an album instance.
wolffd@0 1159 # Parameters:
wolffd@0 1160 * artist: An artist name or an Artist object.
wolffd@0 1161 * title: The album title.
wolffd@0 1162 """
wolffd@0 1163
wolffd@0 1164 _BaseObject.__init__(self, network)
wolffd@0 1165 _Taggable.__init__(self, 'album')
wolffd@0 1166
wolffd@0 1167 if isinstance(artist, Artist):
wolffd@0 1168 self.artist = artist
wolffd@0 1169 else:
wolffd@0 1170 self.artist = Artist(artist, self.network)
wolffd@0 1171
wolffd@0 1172 self.title = title
wolffd@0 1173
wolffd@0 1174 def __repr__(self):
wolffd@0 1175 return "pylast.Album(%s, %s, %s)" %(repr(self.artist.name), repr(self.title), repr(self.network))
wolffd@0 1176
wolffd@0 1177 @_string_output
wolffd@0 1178 def __str__(self):
wolffd@0 1179 return _unicode("%s - %s") %(self.get_artist().get_name(), self.get_title())
wolffd@0 1180
wolffd@0 1181 def __eq__(self, other):
wolffd@0 1182 return (self.get_title().lower() == other.get_title().lower()) and (self.get_artist().get_name().lower() == other.get_artist().get_name().lower())
wolffd@0 1183
wolffd@0 1184 def __ne__(self, other):
wolffd@0 1185 return (self.get_title().lower() != other.get_title().lower()) or (self.get_artist().get_name().lower() != other.get_artist().get_name().lower())
wolffd@0 1186
wolffd@0 1187 def _get_params(self):
wolffd@0 1188 return {'artist': self.get_artist().get_name(), 'album': self.get_title(), }
wolffd@0 1189
wolffd@0 1190 def get_artist(self):
wolffd@0 1191 """Returns the associated Artist object."""
wolffd@0 1192
wolffd@0 1193 return self.artist
wolffd@0 1194
wolffd@0 1195 def get_title(self):
wolffd@0 1196 """Returns the album title."""
wolffd@0 1197
wolffd@0 1198 return self.title
wolffd@0 1199
wolffd@0 1200 def get_name(self):
wolffd@0 1201 """Returns the album title (alias to Album.get_title)."""
wolffd@0 1202
wolffd@0 1203 return self.get_title()
wolffd@0 1204
wolffd@0 1205 def get_release_date(self):
wolffd@0 1206 """Retruns the release date of the album."""
wolffd@0 1207
wolffd@0 1208 return _extract(self._request("album.getInfo", cacheable = True), "releasedate")
wolffd@0 1209
wolffd@0 1210 def get_cover_image(self, size = COVER_EXTRA_LARGE):
wolffd@0 1211 """
wolffd@0 1212 Returns a uri to the cover image
wolffd@0 1213 size can be one of:
wolffd@0 1214 COVER_EXTRA_LARGE
wolffd@0 1215 COVER_LARGE
wolffd@0 1216 COVER_MEDIUM
wolffd@0 1217 COVER_SMALL
wolffd@0 1218 """
wolffd@0 1219
wolffd@0 1220 return _extract_all(self._request("album.getInfo", cacheable = True), 'image')[size]
wolffd@0 1221
wolffd@0 1222 def get_id(self):
wolffd@0 1223 """Returns the ID"""
wolffd@0 1224
wolffd@0 1225 return _extract(self._request("album.getInfo", cacheable = True), "id")
wolffd@0 1226
wolffd@0 1227 def get_playcount(self):
wolffd@0 1228 """Returns the number of plays on the network"""
wolffd@0 1229
wolffd@0 1230 return _number(_extract(self._request("album.getInfo", cacheable = True), "playcount"))
wolffd@0 1231
wolffd@0 1232 def get_listener_count(self):
wolffd@0 1233 """Returns the number of liteners on the network"""
wolffd@0 1234
wolffd@0 1235 return _number(_extract(self._request("album.getInfo", cacheable = True), "listeners"))
wolffd@0 1236
wolffd@0 1237 def get_top_tags(self, limit=None):
wolffd@0 1238 """Returns a list of the most-applied tags to this album."""
wolffd@0 1239
wolffd@0 1240 doc = self._request("album.getInfo", True)
wolffd@0 1241 e = doc.getElementsByTagName("toptags")[0]
wolffd@0 1242
wolffd@0 1243 seq = []
wolffd@0 1244 for name in _extract_all(e, "name"):
wolffd@0 1245 seq.append(Tag(name, self.network))
wolffd@0 1246
wolffd@0 1247 if limit:
wolffd@0 1248 seq = seq[:limit]
wolffd@0 1249
wolffd@0 1250 return seq
wolffd@0 1251
wolffd@0 1252 def get_tracks(self):
wolffd@0 1253 """Returns the list of Tracks on this album."""
wolffd@0 1254
wolffd@0 1255 uri = 'lastfm://playlist/album/%s' %self.get_id()
wolffd@0 1256
wolffd@0 1257 return XSPF(uri, self.network).get_tracks()
wolffd@0 1258
wolffd@0 1259 def get_mbid(self):
wolffd@0 1260 """Returns the MusicBrainz id of the album."""
wolffd@0 1261
wolffd@0 1262 return _extract(self._request("album.getInfo", cacheable = True), "mbid")
wolffd@0 1263
wolffd@0 1264 def get_url(self, domain_name = DOMAIN_ENGLISH):
wolffd@0 1265 """Returns the url of the album page on the network.
wolffd@0 1266 # Parameters:
wolffd@0 1267 * domain_name str: The network's language domain. Possible values:
wolffd@0 1268 o DOMAIN_ENGLISH
wolffd@0 1269 o DOMAIN_GERMAN
wolffd@0 1270 o DOMAIN_SPANISH
wolffd@0 1271 o DOMAIN_FRENCH
wolffd@0 1272 o DOMAIN_ITALIAN
wolffd@0 1273 o DOMAIN_POLISH
wolffd@0 1274 o DOMAIN_PORTUGUESE
wolffd@0 1275 o DOMAIN_SWEDISH
wolffd@0 1276 o DOMAIN_TURKISH
wolffd@0 1277 o DOMAIN_RUSSIAN
wolffd@0 1278 o DOMAIN_JAPANESE
wolffd@0 1279 o DOMAIN_CHINESE
wolffd@0 1280 """
wolffd@0 1281
wolffd@0 1282 artist = _url_safe(self.get_artist().get_name())
wolffd@0 1283 album = _url_safe(self.get_title())
wolffd@0 1284
wolffd@0 1285 return self.network._get_url(domain_name, "album") %{'artist': artist, 'album': album}
wolffd@0 1286
wolffd@0 1287 def get_wiki_published_date(self):
wolffd@0 1288 """Returns the date of publishing this version of the wiki."""
wolffd@0 1289
wolffd@0 1290 doc = self._request("album.getInfo", True)
wolffd@0 1291
wolffd@0 1292 if len(doc.getElementsByTagName("wiki")) == 0:
wolffd@0 1293 return
wolffd@0 1294
wolffd@0 1295 node = doc.getElementsByTagName("wiki")[0]
wolffd@0 1296
wolffd@0 1297 return _extract(node, "published")
wolffd@0 1298
wolffd@0 1299 def get_wiki_summary(self):
wolffd@0 1300 """Returns the summary of the wiki."""
wolffd@0 1301
wolffd@0 1302 doc = self._request("album.getInfo", True)
wolffd@0 1303
wolffd@0 1304 if len(doc.getElementsByTagName("wiki")) == 0:
wolffd@0 1305 return
wolffd@0 1306
wolffd@0 1307 node = doc.getElementsByTagName("wiki")[0]
wolffd@0 1308
wolffd@0 1309 return _extract(node, "summary")
wolffd@0 1310
wolffd@0 1311 def get_wiki_content(self):
wolffd@0 1312 """Returns the content of the wiki."""
wolffd@0 1313
wolffd@0 1314 doc = self._request("album.getInfo", True)
wolffd@0 1315
wolffd@0 1316 if len(doc.getElementsByTagName("wiki")) == 0:
wolffd@0 1317 return
wolffd@0 1318
wolffd@0 1319 node = doc.getElementsByTagName("wiki")[0]
wolffd@0 1320
wolffd@0 1321 return _extract(node, "content")
wolffd@0 1322
wolffd@0 1323 class Artist(_BaseObject, _Taggable):
wolffd@0 1324 """An artist."""
wolffd@0 1325
wolffd@0 1326 name = None
wolffd@0 1327
wolffd@0 1328 def __init__(self, name, network):
wolffd@0 1329 """Create an artist object.
wolffd@0 1330 # Parameters:
wolffd@0 1331 * name str: The artist's name.
wolffd@0 1332 """
wolffd@0 1333
wolffd@0 1334 _BaseObject.__init__(self, network)
wolffd@0 1335 _Taggable.__init__(self, 'artist')
wolffd@0 1336
wolffd@0 1337 self.name = name
wolffd@0 1338
wolffd@0 1339 def __repr__(self):
wolffd@0 1340 return "pylast.Artist(%s, %s)" %(repr(self.get_name()), repr(self.network))
wolffd@0 1341
wolffd@0 1342 @_string_output
wolffd@0 1343 def __str__(self):
wolffd@0 1344 return self.get_name()
wolffd@0 1345
wolffd@0 1346 def __eq__(self, other):
wolffd@0 1347 return self.get_name().lower() == other.get_name().lower()
wolffd@0 1348
wolffd@0 1349 def __ne__(self, other):
wolffd@0 1350 return self.get_name().lower() != other.get_name().lower()
wolffd@0 1351
wolffd@0 1352 def _get_params(self):
wolffd@0 1353 return {'artist': self.get_name()}
wolffd@0 1354
wolffd@0 1355 def get_name(self, properly_capitalized=False):
wolffd@0 1356 """Returns the name of the artist.
wolffd@0 1357 If properly_capitalized was asserted then the name would be downloaded
wolffd@0 1358 overwriting the given one."""
wolffd@0 1359
wolffd@0 1360 if properly_capitalized:
wolffd@0 1361 self.name = _extract(self._request("artist.getInfo", True), "name")
wolffd@0 1362
wolffd@0 1363 return self.name
wolffd@0 1364
wolffd@0 1365 def get_cover_image(self, size = COVER_MEGA):
wolffd@0 1366 """
wolffd@0 1367 Returns a uri to the cover image
wolffd@0 1368 size can be one of:
wolffd@0 1369 COVER_MEGA
wolffd@0 1370 COVER_EXTRA_LARGE
wolffd@0 1371 COVER_LARGE
wolffd@0 1372 COVER_MEDIUM
wolffd@0 1373 COVER_SMALL
wolffd@0 1374 """
wolffd@0 1375
wolffd@0 1376 return _extract_all(self._request("artist.getInfo", True), "image")[size]
wolffd@0 1377
wolffd@0 1378 def get_playcount(self):
wolffd@0 1379 """Returns the number of plays on the network."""
wolffd@0 1380
wolffd@0 1381 return _number(_extract(self._request("artist.getInfo", True), "playcount"))
wolffd@0 1382
wolffd@0 1383 def get_mbid(self):
wolffd@0 1384 """Returns the MusicBrainz ID of this artist."""
wolffd@0 1385
wolffd@0 1386 doc = self._request("artist.getInfo", True)
wolffd@0 1387
wolffd@0 1388 return _extract(doc, "mbid")
wolffd@0 1389
wolffd@0 1390 def get_listener_count(self):
wolffd@0 1391 """Returns the number of liteners on the network."""
wolffd@0 1392
wolffd@0 1393 if hasattr(self, "listener_count"):
wolffd@0 1394 return self.listener_count
wolffd@0 1395 else:
wolffd@0 1396 self.listener_count = _number(_extract(self._request("artist.getInfo", True), "listeners"))
wolffd@0 1397 return self.listener_count
wolffd@0 1398
wolffd@0 1399 def is_streamable(self):
wolffd@0 1400 """Returns True if the artist is streamable."""
wolffd@0 1401
wolffd@0 1402 return bool(_number(_extract(self._request("artist.getInfo", True), "streamable")))
wolffd@0 1403
wolffd@0 1404 def get_bio_published_date(self):
wolffd@0 1405 """Returns the date on which the artist's biography was published."""
wolffd@0 1406
wolffd@0 1407 return _extract(self._request("artist.getInfo", True), "published")
wolffd@0 1408
wolffd@0 1409 def get_bio_summary(self):
wolffd@0 1410 """Returns the summary of the artist's biography."""
wolffd@0 1411
wolffd@0 1412 return _extract(self._request("artist.getInfo", True), "summary")
wolffd@0 1413
wolffd@0 1414 def get_bio_content(self):
wolffd@0 1415 """Returns the content of the artist's biography."""
wolffd@0 1416
wolffd@0 1417 return _extract(self._request("artist.getInfo", True), "content")
wolffd@0 1418
wolffd@0 1419 def get_upcoming_events(self):
wolffd@0 1420 """Returns a list of the upcoming Events for this artist."""
wolffd@0 1421
wolffd@0 1422 doc = self._request('artist.getEvents', True)
wolffd@0 1423
wolffd@0 1424 ids = _extract_all(doc, 'id')
wolffd@0 1425
wolffd@0 1426 events = []
wolffd@0 1427 for e_id in ids:
wolffd@0 1428 events.append(Event(e_id, self.network))
wolffd@0 1429
wolffd@0 1430 return events
wolffd@0 1431
wolffd@0 1432 def get_similar(self, limit = None):
wolffd@0 1433 """Returns the similar artists on the network."""
wolffd@0 1434
wolffd@0 1435 params = self._get_params()
wolffd@0 1436 if limit:
wolffd@0 1437 params['limit'] = limit
wolffd@0 1438
wolffd@0 1439 doc = self._request('artist.getSimilar', True, params)
wolffd@0 1440
wolffd@0 1441 names = _extract_all(doc, "name")
wolffd@0 1442 matches = _extract_all(doc, "match")
wolffd@0 1443
wolffd@0 1444 artists = []
wolffd@0 1445 for i in range(0, len(names)):
wolffd@0 1446 artists.append(SimilarItem(Artist(names[i], self.network), _number(matches[i])))
wolffd@0 1447
wolffd@0 1448 return artists
wolffd@0 1449
wolffd@0 1450 def compare_with_artist(self, artist, shared_artists_limit = None):
wolffd@0 1451 """Compare this artist with another Last.fm artist.
wolffd@0 1452 Seems like the tasteometer only returns stats on subsets of artist sets...
wolffd@0 1453 Returns a sequence (tasteometer_score, (shared_artist1, shared_artist2, ...))
wolffd@0 1454 artist: A artist object or a artistname string/unicode object.
wolffd@0 1455 """
wolffd@0 1456
wolffd@0 1457 if isinstance(artist, Artist):
wolffd@0 1458 artist = artist.get_name()
wolffd@0 1459
wolffd@0 1460 params = self._get_params()
wolffd@0 1461 if shared_artists_limit:
wolffd@0 1462 params['limit'] = shared_artists_limit
wolffd@0 1463 params['type1'] = 'artists'
wolffd@0 1464 params['type2'] = 'artists'
wolffd@0 1465 params['value1'] = self.get_name()
wolffd@0 1466 params['value2'] = artist
wolffd@0 1467
wolffd@0 1468 doc = self._request('tasteometer.compare', False, params)
wolffd@0 1469
wolffd@0 1470 score = _extract(doc, 'score')
wolffd@0 1471
wolffd@0 1472 artists = doc.getElementsByTagName('artists')[0]
wolffd@0 1473 shared_artists_names = _extract_all(artists, 'name')
wolffd@0 1474
wolffd@0 1475 shared_artists_seq = []
wolffd@0 1476
wolffd@0 1477 for name in shared_artists_names:
wolffd@0 1478 shared_artists_seq.append(Artist(name, self.network))
wolffd@0 1479
wolffd@0 1480 return (score, shared_artists_seq)
wolffd@0 1481
wolffd@0 1482 def get_top_albums(self):
wolffd@0 1483 """Retuns a list of the top albums."""
wolffd@0 1484
wolffd@0 1485 doc = self._request('artist.getTopAlbums', True)
wolffd@0 1486
wolffd@0 1487 seq = []
wolffd@0 1488
wolffd@0 1489 for node in doc.getElementsByTagName("album"):
wolffd@0 1490 name = _extract(node, "name")
wolffd@0 1491 artist = _extract(node, "name", 1)
wolffd@0 1492 playcount = _extract(node, "playcount")
wolffd@0 1493
wolffd@0 1494 seq.append(TopItem(Album(artist, name, self.network), playcount))
wolffd@0 1495
wolffd@0 1496 return seq
wolffd@0 1497
wolffd@0 1498 def get_top_tracks(self):
wolffd@0 1499 """Returns a list of the most played Tracks by this artist."""
wolffd@0 1500
wolffd@0 1501 doc = self._request("artist.getTopTracks", True)
wolffd@0 1502
wolffd@0 1503 seq = []
wolffd@0 1504 for track in doc.getElementsByTagName('track'):
wolffd@0 1505
wolffd@0 1506 title = _extract(track, "name")
wolffd@0 1507 artist = _extract(track, "name", 1)
wolffd@0 1508 playcount = _number(_extract(track, "playcount"))
wolffd@0 1509
wolffd@0 1510 seq.append( TopItem(Track(artist, title, self.network), playcount) )
wolffd@0 1511
wolffd@0 1512 return seq
wolffd@0 1513
wolffd@0 1514 def get_top_fans(self, limit = None):
wolffd@0 1515 """Returns a list of the Users who played this artist the most.
wolffd@0 1516 # Parameters:
wolffd@0 1517 * limit int: Max elements.
wolffd@0 1518 """
wolffd@0 1519
wolffd@0 1520 doc = self._request('artist.getTopFans', True)
wolffd@0 1521
wolffd@0 1522 seq = []
wolffd@0 1523
wolffd@0 1524 elements = doc.getElementsByTagName('user')
wolffd@0 1525
wolffd@0 1526 for element in elements:
wolffd@0 1527 if limit and len(seq) >= limit:
wolffd@0 1528 break
wolffd@0 1529
wolffd@0 1530 name = _extract(element, 'name')
wolffd@0 1531 weight = _number(_extract(element, 'weight'))
wolffd@0 1532
wolffd@0 1533 seq.append(TopItem(User(name, self.network), weight))
wolffd@0 1534
wolffd@0 1535 return seq
wolffd@0 1536
wolffd@0 1537 def share(self, users, message = None):
wolffd@0 1538 """Shares this artist (sends out recommendations).
wolffd@0 1539 # Parameters:
wolffd@0 1540 * users [User|str,]: A list that can contain usernames, emails, User objects, or all of them.
wolffd@0 1541 * message str: A message to include in the recommendation message.
wolffd@0 1542 """
wolffd@0 1543
wolffd@0 1544 #last.fm currently accepts a max of 10 recipient at a time
wolffd@0 1545 while(len(users) > 10):
wolffd@0 1546 section = users[0:9]
wolffd@0 1547 users = users[9:]
wolffd@0 1548 self.share(section, message)
wolffd@0 1549
wolffd@0 1550 nusers = []
wolffd@0 1551 for user in users:
wolffd@0 1552 if isinstance(user, User):
wolffd@0 1553 nusers.append(user.get_name())
wolffd@0 1554 else:
wolffd@0 1555 nusers.append(user)
wolffd@0 1556
wolffd@0 1557 params = self._get_params()
wolffd@0 1558 recipients = ','.join(nusers)
wolffd@0 1559 params['recipient'] = recipients
wolffd@0 1560 if message:
wolffd@0 1561 params['message'] = message
wolffd@0 1562
wolffd@0 1563 self._request('artist.share', False, params)
wolffd@0 1564
wolffd@0 1565 def get_url(self, domain_name = DOMAIN_ENGLISH):
wolffd@0 1566 """Returns the url of the artist page on the network.
wolffd@0 1567 # Parameters:
wolffd@0 1568 * domain_name: The network's language domain. Possible values:
wolffd@0 1569 o DOMAIN_ENGLISH
wolffd@0 1570 o DOMAIN_GERMAN
wolffd@0 1571 o DOMAIN_SPANISH
wolffd@0 1572 o DOMAIN_FRENCH
wolffd@0 1573 o DOMAIN_ITALIAN
wolffd@0 1574 o DOMAIN_POLISH
wolffd@0 1575 o DOMAIN_PORTUGUESE
wolffd@0 1576 o DOMAIN_SWEDISH
wolffd@0 1577 o DOMAIN_TURKISH
wolffd@0 1578 o DOMAIN_RUSSIAN
wolffd@0 1579 o DOMAIN_JAPANESE
wolffd@0 1580 o DOMAIN_CHINESE
wolffd@0 1581 """
wolffd@0 1582
wolffd@0 1583 artist = _url_safe(self.get_name())
wolffd@0 1584
wolffd@0 1585 return self.network._get_url(domain_name, "artist") %{'artist': artist}
wolffd@0 1586
wolffd@0 1587 def get_images(self, order=IMAGES_ORDER_POPULARITY, limit=None):
wolffd@0 1588 """
wolffd@0 1589 Returns a sequence of Image objects
wolffd@0 1590 if limit is None it will return all
wolffd@0 1591 order can be IMAGES_ORDER_POPULARITY or IMAGES_ORDER_DATE.
wolffd@0 1592
wolffd@0 1593 If limit==None, it will try to pull all the available data.
wolffd@0 1594 """
wolffd@0 1595
wolffd@0 1596 images = []
wolffd@0 1597
wolffd@0 1598 params = self._get_params()
wolffd@0 1599 params["order"] = order
wolffd@0 1600 nodes = _collect_nodes(limit, self, "artist.getImages", True, params)
wolffd@0 1601 for e in nodes:
wolffd@0 1602 if _extract(e, "name"):
wolffd@0 1603 user = User(_extract(e, "name"), self.network)
wolffd@0 1604 else:
wolffd@0 1605 user = None
wolffd@0 1606
wolffd@0 1607 images.append(Image(
wolffd@0 1608 _extract(e, "title"),
wolffd@0 1609 _extract(e, "url"),
wolffd@0 1610 _extract(e, "dateadded"),
wolffd@0 1611 _extract(e, "format"),
wolffd@0 1612 user,
wolffd@0 1613 ImageSizes(*_extract_all(e, "size")),
wolffd@0 1614 (_extract(e, "thumbsup"), _extract(e, "thumbsdown"))
wolffd@0 1615 )
wolffd@0 1616 )
wolffd@0 1617 return images
wolffd@0 1618
wolffd@0 1619 def get_shouts(self, limit=50):
wolffd@0 1620 """
wolffd@0 1621 Returns a sequqence of Shout objects
wolffd@0 1622 """
wolffd@0 1623
wolffd@0 1624 shouts = []
wolffd@0 1625 for node in _collect_nodes(limit, self, "artist.getShouts", False):
wolffd@0 1626 shouts.append(Shout(
wolffd@0 1627 _extract(node, "body"),
wolffd@0 1628 User(_extract(node, "author"), self.network),
wolffd@0 1629 _extract(node, "date")
wolffd@0 1630 )
wolffd@0 1631 )
wolffd@0 1632 return shouts
wolffd@0 1633
wolffd@0 1634 def shout(self, message):
wolffd@0 1635 """
wolffd@0 1636 Post a shout
wolffd@0 1637 """
wolffd@0 1638
wolffd@0 1639 params = self._get_params()
wolffd@0 1640 params["message"] = message
wolffd@0 1641
wolffd@0 1642 self._request("artist.Shout", False, params)
wolffd@0 1643
wolffd@0 1644
wolffd@0 1645 class Event(_BaseObject):
wolffd@0 1646 """An event."""
wolffd@0 1647
wolffd@0 1648 id = None
wolffd@0 1649
wolffd@0 1650 def __init__(self, event_id, network):
wolffd@0 1651 _BaseObject.__init__(self, network)
wolffd@0 1652
wolffd@0 1653 self.id = event_id
wolffd@0 1654
wolffd@0 1655 def __repr__(self):
wolffd@0 1656 return "pylast.Event(%s, %s)" %(repr(self.id), repr(self.network))
wolffd@0 1657
wolffd@0 1658 @_string_output
wolffd@0 1659 def __str__(self):
wolffd@0 1660 return "Event #" + self.get_id()
wolffd@0 1661
wolffd@0 1662 def __eq__(self, other):
wolffd@0 1663 return self.get_id() == other.get_id()
wolffd@0 1664
wolffd@0 1665 def __ne__(self, other):
wolffd@0 1666 return self.get_id() != other.get_id()
wolffd@0 1667
wolffd@0 1668 def _get_params(self):
wolffd@0 1669 return {'event': self.get_id()}
wolffd@0 1670
wolffd@0 1671 def attend(self, attending_status):
wolffd@0 1672 """Sets the attending status.
wolffd@0 1673 * attending_status: The attending status. Possible values:
wolffd@0 1674 o EVENT_ATTENDING
wolffd@0 1675 o EVENT_MAYBE_ATTENDING
wolffd@0 1676 o EVENT_NOT_ATTENDING
wolffd@0 1677 """
wolffd@0 1678
wolffd@0 1679 params = self._get_params()
wolffd@0 1680 params['status'] = attending_status
wolffd@0 1681
wolffd@0 1682 self._request('event.attend', False, params)
wolffd@0 1683
wolffd@0 1684 def get_attendees(self):
wolffd@0 1685 """
wolffd@0 1686 Get a list of attendees for an event
wolffd@0 1687 """
wolffd@0 1688
wolffd@0 1689 doc = self._request("event.getAttendees", False)
wolffd@0 1690
wolffd@0 1691 users = []
wolffd@0 1692 for name in _extract_all(doc, "name"):
wolffd@0 1693 users.append(User(name, self.network))
wolffd@0 1694
wolffd@0 1695 return users
wolffd@0 1696
wolffd@0 1697 def get_id(self):
wolffd@0 1698 """Returns the id of the event on the network. """
wolffd@0 1699
wolffd@0 1700 return self.id
wolffd@0 1701
wolffd@0 1702 def get_title(self):
wolffd@0 1703 """Returns the title of the event. """
wolffd@0 1704
wolffd@0 1705 doc = self._request("event.getInfo", True)
wolffd@0 1706
wolffd@0 1707 return _extract(doc, "title")
wolffd@0 1708
wolffd@0 1709 def get_headliner(self):
wolffd@0 1710 """Returns the headliner of the event. """
wolffd@0 1711
wolffd@0 1712 doc = self._request("event.getInfo", True)
wolffd@0 1713
wolffd@0 1714 return Artist(_extract(doc, "headliner"), self.network)
wolffd@0 1715
wolffd@0 1716 def get_artists(self):
wolffd@0 1717 """Returns a list of the participating Artists. """
wolffd@0 1718
wolffd@0 1719 doc = self._request("event.getInfo", True)
wolffd@0 1720 names = _extract_all(doc, "artist")
wolffd@0 1721
wolffd@0 1722 artists = []
wolffd@0 1723 for name in names:
wolffd@0 1724 artists.append(Artist(name, self.network))
wolffd@0 1725
wolffd@0 1726 return artists
wolffd@0 1727
wolffd@0 1728 def get_venue(self):
wolffd@0 1729 """Returns the venue where the event is held."""
wolffd@0 1730
wolffd@0 1731 doc = self._request("event.getInfo", True)
wolffd@0 1732
wolffd@0 1733 v = doc.getElementsByTagName("venue")[0]
wolffd@0 1734 venue_id = _number(_extract(v, "id"))
wolffd@0 1735
wolffd@0 1736 return Venue(venue_id, self.network)
wolffd@0 1737
wolffd@0 1738 def get_start_date(self):
wolffd@0 1739 """Returns the date when the event starts."""
wolffd@0 1740
wolffd@0 1741 doc = self._request("event.getInfo", True)
wolffd@0 1742
wolffd@0 1743 return _extract(doc, "startDate")
wolffd@0 1744
wolffd@0 1745 def get_description(self):
wolffd@0 1746 """Returns the description of the event. """
wolffd@0 1747
wolffd@0 1748 doc = self._request("event.getInfo", True)
wolffd@0 1749
wolffd@0 1750 return _extract(doc, "description")
wolffd@0 1751
wolffd@0 1752 def get_cover_image(self, size = COVER_MEGA):
wolffd@0 1753 """
wolffd@0 1754 Returns a uri to the cover image
wolffd@0 1755 size can be one of:
wolffd@0 1756 COVER_MEGA
wolffd@0 1757 COVER_EXTRA_LARGE
wolffd@0 1758 COVER_LARGE
wolffd@0 1759 COVER_MEDIUM
wolffd@0 1760 COVER_SMALL
wolffd@0 1761 """
wolffd@0 1762
wolffd@0 1763 doc = self._request("event.getInfo", True)
wolffd@0 1764
wolffd@0 1765 return _extract_all(doc, "image")[size]
wolffd@0 1766
wolffd@0 1767 def get_attendance_count(self):
wolffd@0 1768 """Returns the number of attending people. """
wolffd@0 1769
wolffd@0 1770 doc = self._request("event.getInfo", True)
wolffd@0 1771
wolffd@0 1772 return _number(_extract(doc, "attendance"))
wolffd@0 1773
wolffd@0 1774 def get_review_count(self):
wolffd@0 1775 """Returns the number of available reviews for this event. """
wolffd@0 1776
wolffd@0 1777 doc = self._request("event.getInfo", True)
wolffd@0 1778
wolffd@0 1779 return _number(_extract(doc, "reviews"))
wolffd@0 1780
wolffd@0 1781 def get_url(self, domain_name = DOMAIN_ENGLISH):
wolffd@0 1782 """Returns the url of the event page on the network.
wolffd@0 1783 * domain_name: The network's language domain. Possible values:
wolffd@0 1784 o DOMAIN_ENGLISH
wolffd@0 1785 o DOMAIN_GERMAN
wolffd@0 1786 o DOMAIN_SPANISH
wolffd@0 1787 o DOMAIN_FRENCH
wolffd@0 1788 o DOMAIN_ITALIAN
wolffd@0 1789 o DOMAIN_POLISH
wolffd@0 1790 o DOMAIN_PORTUGUESE
wolffd@0 1791 o DOMAIN_SWEDISH
wolffd@0 1792 o DOMAIN_TURKISH
wolffd@0 1793 o DOMAIN_RUSSIAN
wolffd@0 1794 o DOMAIN_JAPANESE
wolffd@0 1795 o DOMAIN_CHINESE
wolffd@0 1796 """
wolffd@0 1797
wolffd@0 1798 return self.network._get_url(domain_name, "event") %{'id': self.get_id()}
wolffd@0 1799
wolffd@0 1800 def share(self, users, message = None):
wolffd@0 1801 """Shares this event (sends out recommendations).
wolffd@0 1802 * users: A list that can contain usernames, emails, User objects, or all of them.
wolffd@0 1803 * message: A message to include in the recommendation message.
wolffd@0 1804 """
wolffd@0 1805
wolffd@0 1806 #last.fm currently accepts a max of 10 recipient at a time
wolffd@0 1807 while(len(users) > 10):
wolffd@0 1808 section = users[0:9]
wolffd@0 1809 users = users[9:]
wolffd@0 1810 self.share(section, message)
wolffd@0 1811
wolffd@0 1812 nusers = []
wolffd@0 1813 for user in users:
wolffd@0 1814 if isinstance(user, User):
wolffd@0 1815 nusers.append(user.get_name())
wolffd@0 1816 else:
wolffd@0 1817 nusers.append(user)
wolffd@0 1818
wolffd@0 1819 params = self._get_params()
wolffd@0 1820 recipients = ','.join(nusers)
wolffd@0 1821 params['recipient'] = recipients
wolffd@0 1822 if message:
wolffd@0 1823 params['message'] = message
wolffd@0 1824
wolffd@0 1825 self._request('event.share', False, params)
wolffd@0 1826
wolffd@0 1827 def get_shouts(self, limit=50):
wolffd@0 1828 """
wolffd@0 1829 Returns a sequqence of Shout objects
wolffd@0 1830 """
wolffd@0 1831
wolffd@0 1832 shouts = []
wolffd@0 1833 for node in _collect_nodes(limit, self, "event.getShouts", False):
wolffd@0 1834 shouts.append(Shout(
wolffd@0 1835 _extract(node, "body"),
wolffd@0 1836 User(_extract(node, "author"), self.network),
wolffd@0 1837 _extract(node, "date")
wolffd@0 1838 )
wolffd@0 1839 )
wolffd@0 1840 return shouts
wolffd@0 1841
wolffd@0 1842 def shout(self, message):
wolffd@0 1843 """
wolffd@0 1844 Post a shout
wolffd@0 1845 """
wolffd@0 1846
wolffd@0 1847 params = self._get_params()
wolffd@0 1848 params["message"] = message
wolffd@0 1849
wolffd@0 1850 self._request("event.Shout", False, params)
wolffd@0 1851
wolffd@0 1852 class Country(_BaseObject):
wolffd@0 1853 """A country at Last.fm."""
wolffd@0 1854
wolffd@0 1855 name = None
wolffd@0 1856
wolffd@0 1857 def __init__(self, name, network):
wolffd@0 1858 _BaseObject.__init__(self, network)
wolffd@0 1859
wolffd@0 1860 self.name = name
wolffd@0 1861
wolffd@0 1862 def __repr__(self):
wolffd@0 1863 return "pylast.Country(%s, %s)" %(repr(self.name), repr(self.network))
wolffd@0 1864
wolffd@0 1865 @_string_output
wolffd@0 1866 def __str__(self):
wolffd@0 1867 return self.get_name()
wolffd@0 1868
wolffd@0 1869 def __eq__(self, other):
wolffd@0 1870 return self.get_name().lower() == other.get_name().lower()
wolffd@0 1871
wolffd@0 1872 def __ne__(self, other):
wolffd@0 1873 return self.get_name() != other.get_name()
wolffd@0 1874
wolffd@0 1875 def _get_params(self):
wolffd@0 1876 return {'country': self.get_name()}
wolffd@0 1877
wolffd@0 1878 def _get_name_from_code(self, alpha2code):
wolffd@0 1879 # TODO: Have this function lookup the alpha-2 code and return the country name.
wolffd@0 1880
wolffd@0 1881 return alpha2code
wolffd@0 1882
wolffd@0 1883 def get_name(self):
wolffd@0 1884 """Returns the country name. """
wolffd@0 1885
wolffd@0 1886 return self.name
wolffd@0 1887
wolffd@0 1888 def get_top_artists(self):
wolffd@0 1889 """Returns a sequence of the most played artists."""
wolffd@0 1890
wolffd@0 1891 doc = self._request('geo.getTopArtists', True)
wolffd@0 1892
wolffd@0 1893 seq = []
wolffd@0 1894 for node in doc.getElementsByTagName("artist"):
wolffd@0 1895 name = _extract(node, 'name')
wolffd@0 1896 playcount = _extract(node, "playcount")
wolffd@0 1897
wolffd@0 1898 seq.append(TopItem(Artist(name, self.network), playcount))
wolffd@0 1899
wolffd@0 1900 return seq
wolffd@0 1901
wolffd@0 1902 def get_top_tracks(self):
wolffd@0 1903 """Returns a sequence of the most played tracks"""
wolffd@0 1904
wolffd@0 1905 doc = self._request("geo.getTopTracks", True)
wolffd@0 1906
wolffd@0 1907 seq = []
wolffd@0 1908
wolffd@0 1909 for n in doc.getElementsByTagName('track'):
wolffd@0 1910
wolffd@0 1911 title = _extract(n, 'name')
wolffd@0 1912 artist = _extract(n, 'name', 1)
wolffd@0 1913 playcount = _number(_extract(n, "playcount"))
wolffd@0 1914
wolffd@0 1915 seq.append( TopItem(Track(artist, title, self.network), playcount))
wolffd@0 1916
wolffd@0 1917 return seq
wolffd@0 1918
wolffd@0 1919 def get_url(self, domain_name = DOMAIN_ENGLISH):
wolffd@0 1920 """Returns the url of the event page on the network.
wolffd@0 1921 * domain_name: The network's language domain. Possible values:
wolffd@0 1922 o DOMAIN_ENGLISH
wolffd@0 1923 o DOMAIN_GERMAN
wolffd@0 1924 o DOMAIN_SPANISH
wolffd@0 1925 o DOMAIN_FRENCH
wolffd@0 1926 o DOMAIN_ITALIAN
wolffd@0 1927 o DOMAIN_POLISH
wolffd@0 1928 o DOMAIN_PORTUGUESE
wolffd@0 1929 o DOMAIN_SWEDISH
wolffd@0 1930 o DOMAIN_TURKISH
wolffd@0 1931 o DOMAIN_RUSSIAN
wolffd@0 1932 o DOMAIN_JAPANESE
wolffd@0 1933 o DOMAIN_CHINESE
wolffd@0 1934 """
wolffd@0 1935
wolffd@0 1936 country_name = _url_safe(self.get_name())
wolffd@0 1937
wolffd@0 1938 return self.network._get_url(domain_name, "country") %{'country_name': country_name}
wolffd@0 1939
wolffd@0 1940
wolffd@0 1941 class Library(_BaseObject):
wolffd@0 1942 """A user's Last.fm library."""
wolffd@0 1943
wolffd@0 1944 user = None
wolffd@0 1945
wolffd@0 1946 def __init__(self, user, network):
wolffd@0 1947 _BaseObject.__init__(self, network)
wolffd@0 1948
wolffd@0 1949 if isinstance(user, User):
wolffd@0 1950 self.user = user
wolffd@0 1951 else:
wolffd@0 1952 self.user = User(user, self.network)
wolffd@0 1953
wolffd@0 1954 self._albums_index = 0
wolffd@0 1955 self._artists_index = 0
wolffd@0 1956 self._tracks_index = 0
wolffd@0 1957
wolffd@0 1958 def __repr__(self):
wolffd@0 1959 return "pylast.Library(%s, %s)" %(repr(self.user), repr(self.network))
wolffd@0 1960
wolffd@0 1961 @_string_output
wolffd@0 1962 def __str__(self):
wolffd@0 1963 return repr(self.get_user()) + "'s Library"
wolffd@0 1964
wolffd@0 1965 def _get_params(self):
wolffd@0 1966 return {'user': self.user.get_name()}
wolffd@0 1967
wolffd@0 1968 def get_user(self):
wolffd@0 1969 """Returns the user who owns this library."""
wolffd@0 1970
wolffd@0 1971 return self.user
wolffd@0 1972
wolffd@0 1973 def add_album(self, album):
wolffd@0 1974 """Add an album to this library."""
wolffd@0 1975
wolffd@0 1976 params = self._get_params()
wolffd@0 1977 params["artist"] = album.get_artist.get_name()
wolffd@0 1978 params["album"] = album.get_name()
wolffd@0 1979
wolffd@0 1980 self._request("library.addAlbum", False, params)
wolffd@0 1981
wolffd@0 1982 def add_artist(self, artist):
wolffd@0 1983 """Add an artist to this library."""
wolffd@0 1984
wolffd@0 1985 params = self._get_params()
wolffd@0 1986 params["artist"] = artist.get_name()
wolffd@0 1987
wolffd@0 1988 self._request("library.addArtist", False, params)
wolffd@0 1989
wolffd@0 1990 def add_track(self, track):
wolffd@0 1991 """Add a track to this library."""
wolffd@0 1992
wolffd@0 1993 params = self._get_params()
wolffd@0 1994 params["track"] = track.get_title()
wolffd@0 1995
wolffd@0 1996 self._request("library.addTrack", False, params)
wolffd@0 1997
wolffd@0 1998 def get_albums(self, artist=None, limit=50):
wolffd@0 1999 """
wolffd@0 2000 Returns a sequence of Album objects
wolffd@0 2001 If no artist is specified, it will return all, sorted by playcount descendingly.
wolffd@0 2002 If limit==None it will return all (may take a while)
wolffd@0 2003 """
wolffd@0 2004
wolffd@0 2005 params = self._get_params()
wolffd@0 2006 if artist:
wolffd@0 2007 params["artist"] = artist
wolffd@0 2008
wolffd@0 2009 seq = []
wolffd@0 2010 for node in _collect_nodes(limit, self, "library.getAlbums", True, params):
wolffd@0 2011 name = _extract(node, "name")
wolffd@0 2012 artist = _extract(node, "name", 1)
wolffd@0 2013 playcount = _number(_extract(node, "playcount"))
wolffd@0 2014 tagcount = _number(_extract(node, "tagcount"))
wolffd@0 2015
wolffd@0 2016 seq.append(LibraryItem(Album(artist, name, self.network), playcount, tagcount))
wolffd@0 2017
wolffd@0 2018 return seq
wolffd@0 2019
wolffd@0 2020 def get_artists(self, limit=50):
wolffd@0 2021 """
wolffd@0 2022 Returns a sequence of Album objects
wolffd@0 2023 if limit==None it will return all (may take a while)
wolffd@0 2024 """
wolffd@0 2025
wolffd@0 2026 seq = []
wolffd@0 2027 for node in _collect_nodes(limit, self, "library.getArtists", True):
wolffd@0 2028 name = _extract(node, "name")
wolffd@0 2029
wolffd@0 2030 playcount = _number(_extract(node, "playcount"))
wolffd@0 2031 tagcount = _number(_extract(node, "tagcount"))
wolffd@0 2032
wolffd@0 2033 seq.append(LibraryItem(Artist(name, self.network), playcount, tagcount))
wolffd@0 2034
wolffd@0 2035 return seq
wolffd@0 2036
wolffd@0 2037 def get_tracks(self, artist=None, album=None, limit=50):
wolffd@0 2038 """
wolffd@0 2039 Returns a sequence of Album objects
wolffd@0 2040 If limit==None it will return all (may take a while)
wolffd@0 2041 """
wolffd@0 2042
wolffd@0 2043 params = self._get_params()
wolffd@0 2044 if artist:
wolffd@0 2045 params["artist"] = artist
wolffd@0 2046 if album:
wolffd@0 2047 params["album"] = album
wolffd@0 2048
wolffd@0 2049 seq = []
wolffd@0 2050 for node in _collect_nodes(limit, self, "library.getTracks", True, params):
wolffd@0 2051 name = _extract(node, "name")
wolffd@0 2052 artist = _extract(node, "name", 1)
wolffd@0 2053 playcount = _number(_extract(node, "playcount"))
wolffd@0 2054 tagcount = _number(_extract(node, "tagcount"))
wolffd@0 2055
wolffd@0 2056 seq.append(LibraryItem(Track(artist, name, self.network), playcount, tagcount))
wolffd@0 2057
wolffd@0 2058 return seq
wolffd@0 2059
wolffd@0 2060
wolffd@0 2061 class Playlist(_BaseObject):
wolffd@0 2062 """A Last.fm user playlist."""
wolffd@0 2063
wolffd@0 2064 id = None
wolffd@0 2065 user = None
wolffd@0 2066
wolffd@0 2067 def __init__(self, user, id, network):
wolffd@0 2068 _BaseObject.__init__(self, network)
wolffd@0 2069
wolffd@0 2070 if isinstance(user, User):
wolffd@0 2071 self.user = user
wolffd@0 2072 else:
wolffd@0 2073 self.user = User(user, self.network)
wolffd@0 2074
wolffd@0 2075 self.id = id
wolffd@0 2076
wolffd@0 2077 @_string_output
wolffd@0 2078 def __str__(self):
wolffd@0 2079 return repr(self.user) + "'s playlist # " + repr(self.id)
wolffd@0 2080
wolffd@0 2081 def _get_info_node(self):
wolffd@0 2082 """Returns the node from user.getPlaylists where this playlist's info is."""
wolffd@0 2083
wolffd@0 2084 doc = self._request("user.getPlaylists", True)
wolffd@0 2085
wolffd@0 2086 for node in doc.getElementsByTagName("playlist"):
wolffd@0 2087 if _extract(node, "id") == str(self.get_id()):
wolffd@0 2088 return node
wolffd@0 2089
wolffd@0 2090 def _get_params(self):
wolffd@0 2091 return {'user': self.user.get_name(), 'playlistID': self.get_id()}
wolffd@0 2092
wolffd@0 2093 def get_id(self):
wolffd@0 2094 """Returns the playlist id."""
wolffd@0 2095
wolffd@0 2096 return self.id
wolffd@0 2097
wolffd@0 2098 def get_user(self):
wolffd@0 2099 """Returns the owner user of this playlist."""
wolffd@0 2100
wolffd@0 2101 return self.user
wolffd@0 2102
wolffd@0 2103 def get_tracks(self):
wolffd@0 2104 """Returns a list of the tracks on this user playlist."""
wolffd@0 2105
wolffd@0 2106 uri = _unicode('lastfm://playlist/%s') %self.get_id()
wolffd@0 2107
wolffd@0 2108 return XSPF(uri, self.network).get_tracks()
wolffd@0 2109
wolffd@0 2110 def add_track(self, track):
wolffd@0 2111 """Adds a Track to this Playlist."""
wolffd@0 2112
wolffd@0 2113 params = self._get_params()
wolffd@0 2114 params['artist'] = track.get_artist().get_name()
wolffd@0 2115 params['track'] = track.get_title()
wolffd@0 2116
wolffd@0 2117 self._request('playlist.addTrack', False, params)
wolffd@0 2118
wolffd@0 2119 def get_title(self):
wolffd@0 2120 """Returns the title of this playlist."""
wolffd@0 2121
wolffd@0 2122 return _extract(self._get_info_node(), "title")
wolffd@0 2123
wolffd@0 2124 def get_creation_date(self):
wolffd@0 2125 """Returns the creation date of this playlist."""
wolffd@0 2126
wolffd@0 2127 return _extract(self._get_info_node(), "date")
wolffd@0 2128
wolffd@0 2129 def get_size(self):
wolffd@0 2130 """Returns the number of tracks in this playlist."""
wolffd@0 2131
wolffd@0 2132 return _number(_extract(self._get_info_node(), "size"))
wolffd@0 2133
wolffd@0 2134 def get_description(self):
wolffd@0 2135 """Returns the description of this playlist."""
wolffd@0 2136
wolffd@0 2137 return _extract(self._get_info_node(), "description")
wolffd@0 2138
wolffd@0 2139 def get_duration(self):
wolffd@0 2140 """Returns the duration of this playlist in milliseconds."""
wolffd@0 2141
wolffd@0 2142 return _number(_extract(self._get_info_node(), "duration"))
wolffd@0 2143
wolffd@0 2144 def is_streamable(self):
wolffd@0 2145 """Returns True if the playlist is streamable.
wolffd@0 2146 For a playlist to be streamable, it needs at least 45 tracks by 15 different artists."""
wolffd@0 2147
wolffd@0 2148 if _extract(self._get_info_node(), "streamable") == '1':
wolffd@0 2149 return True
wolffd@0 2150 else:
wolffd@0 2151 return False
wolffd@0 2152
wolffd@0 2153 def has_track(self, track):
wolffd@0 2154 """Checks to see if track is already in the playlist.
wolffd@0 2155 * track: Any Track object.
wolffd@0 2156 """
wolffd@0 2157
wolffd@0 2158 return track in self.get_tracks()
wolffd@0 2159
wolffd@0 2160 def get_cover_image(self, size = COVER_EXTRA_LARGE):
wolffd@0 2161 """
wolffd@0 2162 Returns a uri to the cover image
wolffd@0 2163 size can be one of:
wolffd@0 2164 COVER_MEGA
wolffd@0 2165 COVER_EXTRA_LARGE
wolffd@0 2166 COVER_LARGE
wolffd@0 2167 COVER_MEDIUM
wolffd@0 2168 COVER_SMALL
wolffd@0 2169 """
wolffd@0 2170
wolffd@0 2171 return _extract(self._get_info_node(), "image")[size]
wolffd@0 2172
wolffd@0 2173 def get_url(self, domain_name = DOMAIN_ENGLISH):
wolffd@0 2174 """Returns the url of the playlist on the network.
wolffd@0 2175 * domain_name: The network's language domain. Possible values:
wolffd@0 2176 o DOMAIN_ENGLISH
wolffd@0 2177 o DOMAIN_GERMAN
wolffd@0 2178 o DOMAIN_SPANISH
wolffd@0 2179 o DOMAIN_FRENCH
wolffd@0 2180 o DOMAIN_ITALIAN
wolffd@0 2181 o DOMAIN_POLISH
wolffd@0 2182 o DOMAIN_PORTUGUESE
wolffd@0 2183 o DOMAIN_SWEDISH
wolffd@0 2184 o DOMAIN_TURKISH
wolffd@0 2185 o DOMAIN_RUSSIAN
wolffd@0 2186 o DOMAIN_JAPANESE
wolffd@0 2187 o DOMAIN_CHINESE
wolffd@0 2188 """
wolffd@0 2189
wolffd@0 2190 english_url = _extract(self._get_info_node(), "url")
wolffd@0 2191 appendix = english_url[english_url.rfind("/") + 1:]
wolffd@0 2192
wolffd@0 2193 return self.network._get_url(domain_name, "playlist") %{'appendix': appendix, "user": self.get_user().get_name()}
wolffd@0 2194
wolffd@0 2195
wolffd@0 2196 class Tag(_BaseObject):
wolffd@0 2197 """A Last.fm object tag."""
wolffd@0 2198
wolffd@0 2199 # TODO: getWeeklyArtistChart (too lazy, i'll wait for when someone requests it)
wolffd@0 2200
wolffd@0 2201 name = None
wolffd@0 2202
wolffd@0 2203 def __init__(self, name, network):
wolffd@0 2204 _BaseObject.__init__(self, network)
wolffd@0 2205
wolffd@0 2206 self.name = name
wolffd@0 2207
wolffd@0 2208 def __repr__(self):
wolffd@0 2209 return "pylast.Tag(%s, %s)" %(repr(self.name), repr(self.network))
wolffd@0 2210
wolffd@0 2211 @_string_output
wolffd@0 2212 def __str__(self):
wolffd@0 2213 return self.get_name()
wolffd@0 2214
wolffd@0 2215 def __eq__(self, other):
wolffd@0 2216 return self.get_name().lower() == other.get_name().lower()
wolffd@0 2217
wolffd@0 2218 def __ne__(self, other):
wolffd@0 2219 return self.get_name().lower() != other.get_name().lower()
wolffd@0 2220
wolffd@0 2221 def _get_params(self):
wolffd@0 2222 return {'tag': self.get_name()}
wolffd@0 2223
wolffd@0 2224 def get_name(self, properly_capitalized=False):
wolffd@0 2225 """Returns the name of the tag. """
wolffd@0 2226
wolffd@0 2227 if properly_capitalized:
wolffd@0 2228 self.name = _extract(self._request("tag.getInfo", True), "name")
wolffd@0 2229
wolffd@0 2230 return self.name
wolffd@0 2231
wolffd@0 2232 def get_similar(self):
wolffd@0 2233 """Returns the tags similar to this one, ordered by similarity. """
wolffd@0 2234
wolffd@0 2235 doc = self._request('tag.getSimilar', True)
wolffd@0 2236
wolffd@0 2237 seq = []
wolffd@0 2238 names = _extract_all(doc, 'name')
wolffd@0 2239 for name in names:
wolffd@0 2240 seq.append(Tag(name, self.network))
wolffd@0 2241
wolffd@0 2242 return seq
wolffd@0 2243
wolffd@0 2244 def get_top_albums(self):
wolffd@0 2245 """Retuns a list of the top albums."""
wolffd@0 2246
wolffd@0 2247 doc = self._request('tag.getTopAlbums', True)
wolffd@0 2248
wolffd@0 2249 seq = []
wolffd@0 2250
wolffd@0 2251 for node in doc.getElementsByTagName("album"):
wolffd@0 2252 name = _extract(node, "name")
wolffd@0 2253 artist = _extract(node, "name", 1)
wolffd@0 2254 playcount = _extract(node, "playcount")
wolffd@0 2255
wolffd@0 2256 seq.append(TopItem(Album(artist, name, self.network), playcount))
wolffd@0 2257
wolffd@0 2258 return seq
wolffd@0 2259
wolffd@0 2260 def get_top_tracks(self):
wolffd@0 2261 """Returns a list of the most played Tracks by this artist."""
wolffd@0 2262
wolffd@0 2263 doc = self._request("tag.getTopTracks", True)
wolffd@0 2264
wolffd@0 2265 seq = []
wolffd@0 2266 for track in doc.getElementsByTagName('track'):
wolffd@0 2267
wolffd@0 2268 title = _extract(track, "name")
wolffd@0 2269 artist = _extract(track, "name", 1)
wolffd@0 2270 playcount = _number(_extract(track, "playcount"))
wolffd@0 2271
wolffd@0 2272 seq.append( TopItem(Track(artist, title, self.network), playcount) )
wolffd@0 2273
wolffd@0 2274 return seq
wolffd@0 2275
wolffd@0 2276 def get_top_artists(self):
wolffd@0 2277 """Returns a sequence of the most played artists."""
wolffd@0 2278
wolffd@0 2279 doc = self._request('tag.getTopArtists', True)
wolffd@0 2280
wolffd@0 2281 seq = []
wolffd@0 2282 for node in doc.getElementsByTagName("artist"):
wolffd@0 2283 name = _extract(node, 'name')
wolffd@0 2284 playcount = _extract(node, "playcount")
wolffd@0 2285
wolffd@0 2286 seq.append(TopItem(Artist(name, self.network), playcount))
wolffd@0 2287
wolffd@0 2288 return seq
wolffd@0 2289
wolffd@0 2290 def get_weekly_chart_dates(self):
wolffd@0 2291 """Returns a list of From and To tuples for the available charts."""
wolffd@0 2292
wolffd@0 2293 doc = self._request("tag.getWeeklyChartList", True)
wolffd@0 2294
wolffd@0 2295 seq = []
wolffd@0 2296 for node in doc.getElementsByTagName("chart"):
wolffd@0 2297 seq.append( (node.getAttribute("from"), node.getAttribute("to")) )
wolffd@0 2298
wolffd@0 2299 return seq
wolffd@0 2300
wolffd@0 2301 def get_weekly_artist_charts(self, from_date = None, to_date = None):
wolffd@0 2302 """Returns the weekly artist charts for the week starting from the from_date value to the to_date value."""
wolffd@0 2303
wolffd@0 2304 params = self._get_params()
wolffd@0 2305 if from_date and to_date:
wolffd@0 2306 params["from"] = from_date
wolffd@0 2307 params["to"] = to_date
wolffd@0 2308
wolffd@0 2309 doc = self._request("tag.getWeeklyArtistChart", True, params)
wolffd@0 2310
wolffd@0 2311 seq = []
wolffd@0 2312 for node in doc.getElementsByTagName("artist"):
wolffd@0 2313 item = Artist(_extract(node, "name"), self.network)
wolffd@0 2314 weight = _number(_extract(node, "weight"))
wolffd@0 2315 seq.append(TopItem(item, weight))
wolffd@0 2316
wolffd@0 2317 return seq
wolffd@0 2318
wolffd@0 2319 def get_url(self, domain_name = DOMAIN_ENGLISH):
wolffd@0 2320 """Returns the url of the tag page on the network.
wolffd@0 2321 * domain_name: The network's language domain. Possible values:
wolffd@0 2322 o DOMAIN_ENGLISH
wolffd@0 2323 o DOMAIN_GERMAN
wolffd@0 2324 o DOMAIN_SPANISH
wolffd@0 2325 o DOMAIN_FRENCH
wolffd@0 2326 o DOMAIN_ITALIAN
wolffd@0 2327 o DOMAIN_POLISH
wolffd@0 2328 o DOMAIN_PORTUGUESE
wolffd@0 2329 o DOMAIN_SWEDISH
wolffd@0 2330 o DOMAIN_TURKISH
wolffd@0 2331 o DOMAIN_RUSSIAN
wolffd@0 2332 o DOMAIN_JAPANESE
wolffd@0 2333 o DOMAIN_CHINESE
wolffd@0 2334 """
wolffd@0 2335
wolffd@0 2336 name = _url_safe(self.get_name())
wolffd@0 2337
wolffd@0 2338 return self.network._get_url(domain_name, "tag") %{'name': name}
wolffd@0 2339
wolffd@0 2340 class Track(_BaseObject, _Taggable):
wolffd@0 2341 """A Last.fm track."""
wolffd@0 2342
wolffd@0 2343 artist = None
wolffd@0 2344 title = None
wolffd@0 2345
wolffd@0 2346 def __init__(self, artist, title, network):
wolffd@0 2347 _BaseObject.__init__(self, network)
wolffd@0 2348 _Taggable.__init__(self, 'track')
wolffd@0 2349
wolffd@0 2350 if isinstance(artist, Artist):
wolffd@0 2351 self.artist = artist
wolffd@0 2352 else:
wolffd@0 2353 self.artist = Artist(artist, self.network)
wolffd@0 2354
wolffd@0 2355 self.title = title
wolffd@0 2356
wolffd@0 2357 def __repr__(self):
wolffd@0 2358 return "pylast.Track(%s, %s, %s)" %(repr(self.artist.name), repr(self.title), repr(self.network))
wolffd@0 2359
wolffd@0 2360 @_string_output
wolffd@0 2361 def __str__(self):
wolffd@0 2362 return self.get_artist().get_name() + ' - ' + self.get_title()
wolffd@0 2363
wolffd@0 2364 def __eq__(self, other):
wolffd@0 2365 return (self.get_title().lower() == other.get_title().lower()) and (self.get_artist().get_name().lower() == other.get_artist().get_name().lower())
wolffd@0 2366
wolffd@0 2367 def __ne__(self, other):
wolffd@0 2368 return (self.get_title().lower() != other.get_title().lower()) or (self.get_artist().get_name().lower() != other.get_artist().get_name().lower())
wolffd@0 2369
wolffd@0 2370 def _get_params(self):
wolffd@0 2371 return {'artist': self.get_artist().get_name(), 'track': self.get_title()}
wolffd@0 2372
wolffd@0 2373 def get_artist(self):
wolffd@0 2374 """Returns the associated Artist object."""
wolffd@0 2375
wolffd@0 2376 return self.artist
wolffd@0 2377
wolffd@0 2378 def get_title(self, properly_capitalized=False):
wolffd@0 2379 """Returns the track title."""
wolffd@0 2380
wolffd@0 2381 if properly_capitalized:
wolffd@0 2382 self.title = _extract(self._request("track.getInfo", True), "name")
wolffd@0 2383
wolffd@0 2384 return self.title
wolffd@0 2385
wolffd@0 2386 def get_name(self, properly_capitalized=False):
wolffd@0 2387 """Returns the track title (alias to Track.get_title)."""
wolffd@0 2388
wolffd@0 2389 return self.get_title(properly_capitalized)
wolffd@0 2390
wolffd@0 2391 def get_id(self):
wolffd@0 2392 """Returns the track id on the network."""
wolffd@0 2393
wolffd@0 2394 doc = self._request("track.getInfo", True)
wolffd@0 2395
wolffd@0 2396 return _extract(doc, "id")
wolffd@0 2397
wolffd@0 2398 def get_duration(self):
wolffd@0 2399 """Returns the track duration."""
wolffd@0 2400
wolffd@0 2401 doc = self._request("track.getInfo", True)
wolffd@0 2402
wolffd@0 2403 return _number(_extract(doc, "duration"))
wolffd@0 2404
wolffd@0 2405 def get_mbid(self):
wolffd@0 2406 """Returns the MusicBrainz ID of this track."""
wolffd@0 2407
wolffd@0 2408 doc = self._request("track.getInfo", True)
wolffd@0 2409
wolffd@0 2410 return _extract(doc, "mbid")
wolffd@0 2411
wolffd@0 2412 def get_listener_count(self):
wolffd@0 2413 """Returns the listener count."""
wolffd@0 2414
wolffd@0 2415 if hasattr(self, "listener_count"):
wolffd@0 2416 return self.listener_count
wolffd@0 2417 else:
wolffd@0 2418 doc = self._request("track.getInfo", True)
wolffd@0 2419 self.listener_count = _number(_extract(doc, "listeners"))
wolffd@0 2420 return self.listener_count
wolffd@0 2421
wolffd@0 2422 def get_playcount(self):
wolffd@0 2423 """Returns the play count."""
wolffd@0 2424
wolffd@0 2425 doc = self._request("track.getInfo", True)
wolffd@0 2426 return _number(_extract(doc, "playcount"))
wolffd@0 2427
wolffd@0 2428 def is_streamable(self):
wolffd@0 2429 """Returns True if the track is available at Last.fm."""
wolffd@0 2430
wolffd@0 2431 doc = self._request("track.getInfo", True)
wolffd@0 2432 return _extract(doc, "streamable") == "1"
wolffd@0 2433
wolffd@0 2434 def is_fulltrack_available(self):
wolffd@0 2435 """Returns True if the fulltrack is available for streaming."""
wolffd@0 2436
wolffd@0 2437 doc = self._request("track.getInfo", True)
wolffd@0 2438 return doc.getElementsByTagName("streamable")[0].getAttribute("fulltrack") == "1"
wolffd@0 2439
wolffd@0 2440 def get_album(self):
wolffd@0 2441 """Returns the album object of this track."""
wolffd@0 2442
wolffd@0 2443 doc = self._request("track.getInfo", True)
wolffd@0 2444
wolffd@0 2445 albums = doc.getElementsByTagName("album")
wolffd@0 2446
wolffd@0 2447 if len(albums) == 0:
wolffd@0 2448 return
wolffd@0 2449
wolffd@0 2450 node = doc.getElementsByTagName("album")[0]
wolffd@0 2451 return Album(_extract(node, "artist"), _extract(node, "title"), self.network)
wolffd@0 2452
wolffd@0 2453 def get_wiki_published_date(self):
wolffd@0 2454 """Returns the date of publishing this version of the wiki."""
wolffd@0 2455
wolffd@0 2456 doc = self._request("track.getInfo", True)
wolffd@0 2457
wolffd@0 2458 if len(doc.getElementsByTagName("wiki")) == 0:
wolffd@0 2459 return
wolffd@0 2460
wolffd@0 2461 node = doc.getElementsByTagName("wiki")[0]
wolffd@0 2462
wolffd@0 2463 return _extract(node, "published")
wolffd@0 2464
wolffd@0 2465 def get_wiki_summary(self):
wolffd@0 2466 """Returns the summary of the wiki."""
wolffd@0 2467
wolffd@0 2468 doc = self._request("track.getInfo", True)
wolffd@0 2469
wolffd@0 2470 if len(doc.getElementsByTagName("wiki")) == 0:
wolffd@0 2471 return
wolffd@0 2472
wolffd@0 2473 node = doc.getElementsByTagName("wiki")[0]
wolffd@0 2474
wolffd@0 2475 return _extract(node, "summary")
wolffd@0 2476
wolffd@0 2477 def get_wiki_content(self):
wolffd@0 2478 """Returns the content of the wiki."""
wolffd@0 2479
wolffd@0 2480 doc = self._request("track.getInfo", True)
wolffd@0 2481
wolffd@0 2482 if len(doc.getElementsByTagName("wiki")) == 0:
wolffd@0 2483 return
wolffd@0 2484
wolffd@0 2485 node = doc.getElementsByTagName("wiki")[0]
wolffd@0 2486
wolffd@0 2487 return _extract(node, "content")
wolffd@0 2488
wolffd@0 2489 def love(self):
wolffd@0 2490 """Adds the track to the user's loved tracks. """
wolffd@0 2491
wolffd@0 2492 self._request('track.love')
wolffd@0 2493
wolffd@0 2494 def ban(self):
wolffd@0 2495 """Ban this track from ever playing on the radio. """
wolffd@0 2496
wolffd@0 2497 self._request('track.ban')
wolffd@0 2498
wolffd@0 2499 def get_similar(self):
wolffd@0 2500 """Returns similar tracks for this track on the network, based on listening data. """
wolffd@0 2501
wolffd@0 2502 doc = self._request('track.getSimilar', True)
wolffd@0 2503
wolffd@0 2504 seq = []
wolffd@0 2505 for node in doc.getElementsByTagName("track"):
wolffd@0 2506 title = _extract(node, 'name')
wolffd@0 2507 artist = _extract(node, 'name', 1)
wolffd@0 2508 match = _number(_extract(node, "match"))
wolffd@0 2509
wolffd@0 2510 seq.append(SimilarItem(Track(artist, title, self.network), match))
wolffd@0 2511
wolffd@0 2512 return seq
wolffd@0 2513
wolffd@0 2514 def get_top_fans(self, limit = None):
wolffd@0 2515 """Returns a list of the Users who played this track."""
wolffd@0 2516
wolffd@0 2517 doc = self._request('track.getTopFans', True)
wolffd@0 2518
wolffd@0 2519 seq = []
wolffd@0 2520
wolffd@0 2521 elements = doc.getElementsByTagName('user')
wolffd@0 2522
wolffd@0 2523 for element in elements:
wolffd@0 2524 if limit and len(seq) >= limit:
wolffd@0 2525 break
wolffd@0 2526
wolffd@0 2527 name = _extract(element, 'name')
wolffd@0 2528 weight = _number(_extract(element, 'weight'))
wolffd@0 2529
wolffd@0 2530 seq.append(TopItem(User(name, self.network), weight))
wolffd@0 2531
wolffd@0 2532 return seq
wolffd@0 2533
wolffd@0 2534 def share(self, users, message = None):
wolffd@0 2535 """Shares this track (sends out recommendations).
wolffd@0 2536 * users: A list that can contain usernames, emails, User objects, or all of them.
wolffd@0 2537 * message: A message to include in the recommendation message.
wolffd@0 2538 """
wolffd@0 2539
wolffd@0 2540 #last.fm currently accepts a max of 10 recipient at a time
wolffd@0 2541 while(len(users) > 10):
wolffd@0 2542 section = users[0:9]
wolffd@0 2543 users = users[9:]
wolffd@0 2544 self.share(section, message)
wolffd@0 2545
wolffd@0 2546 nusers = []
wolffd@0 2547 for user in users:
wolffd@0 2548 if isinstance(user, User):
wolffd@0 2549 nusers.append(user.get_name())
wolffd@0 2550 else:
wolffd@0 2551 nusers.append(user)
wolffd@0 2552
wolffd@0 2553 params = self._get_params()
wolffd@0 2554 recipients = ','.join(nusers)
wolffd@0 2555 params['recipient'] = recipients
wolffd@0 2556 if message:
wolffd@0 2557 params['message'] = message
wolffd@0 2558
wolffd@0 2559 self._request('track.share', False, params)
wolffd@0 2560
wolffd@0 2561 def get_url(self, domain_name = DOMAIN_ENGLISH):
wolffd@0 2562 """Returns the url of the track page on the network.
wolffd@0 2563 * domain_name: The network's language domain. Possible values:
wolffd@0 2564 o DOMAIN_ENGLISH
wolffd@0 2565 o DOMAIN_GERMAN
wolffd@0 2566 o DOMAIN_SPANISH
wolffd@0 2567 o DOMAIN_FRENCH
wolffd@0 2568 o DOMAIN_ITALIAN
wolffd@0 2569 o DOMAIN_POLISH
wolffd@0 2570 o DOMAIN_PORTUGUESE
wolffd@0 2571 o DOMAIN_SWEDISH
wolffd@0 2572 o DOMAIN_TURKISH
wolffd@0 2573 o DOMAIN_RUSSIAN
wolffd@0 2574 o DOMAIN_JAPANESE
wolffd@0 2575 o DOMAIN_CHINESE
wolffd@0 2576 """
wolffd@0 2577
wolffd@0 2578 artist = _url_safe(self.get_artist().get_name())
wolffd@0 2579 title = _url_safe(self.get_title())
wolffd@0 2580
wolffd@0 2581 return self.network._get_url(domain_name, "track") %{'domain': self.network._get_language_domain(domain_name), 'artist': artist, 'title': title}
wolffd@0 2582
wolffd@0 2583 def get_shouts(self, limit=50):
wolffd@0 2584 """
wolffd@0 2585 Returns a sequqence of Shout objects
wolffd@0 2586 """
wolffd@0 2587
wolffd@0 2588 shouts = []
wolffd@0 2589 for node in _collect_nodes(limit, self, "track.getShouts", False):
wolffd@0 2590 shouts.append(Shout(
wolffd@0 2591 _extract(node, "body"),
wolffd@0 2592 User(_extract(node, "author"), self.network),
wolffd@0 2593 _extract(node, "date")
wolffd@0 2594 )
wolffd@0 2595 )
wolffd@0 2596 return shouts
wolffd@0 2597
wolffd@0 2598 class Group(_BaseObject):
wolffd@0 2599 """A Last.fm group."""
wolffd@0 2600
wolffd@0 2601 name = None
wolffd@0 2602
wolffd@0 2603 def __init__(self, group_name, network):
wolffd@0 2604 _BaseObject.__init__(self, network)
wolffd@0 2605
wolffd@0 2606 self.name = group_name
wolffd@0 2607
wolffd@0 2608 def __repr__(self):
wolffd@0 2609 return "pylast.Group(%s, %s)" %(repr(self.name), repr(self.network))
wolffd@0 2610
wolffd@0 2611 @_string_output
wolffd@0 2612 def __str__(self):
wolffd@0 2613 return self.get_name()
wolffd@0 2614
wolffd@0 2615 def __eq__(self, other):
wolffd@0 2616 return self.get_name().lower() == other.get_name().lower()
wolffd@0 2617
wolffd@0 2618 def __ne__(self, other):
wolffd@0 2619 return self.get_name() != other.get_name()
wolffd@0 2620
wolffd@0 2621 def _get_params(self):
wolffd@0 2622 return {'group': self.get_name()}
wolffd@0 2623
wolffd@0 2624 def get_name(self):
wolffd@0 2625 """Returns the group name. """
wolffd@0 2626 return self.name
wolffd@0 2627
wolffd@0 2628 def get_weekly_chart_dates(self):
wolffd@0 2629 """Returns a list of From and To tuples for the available charts."""
wolffd@0 2630
wolffd@0 2631 doc = self._request("group.getWeeklyChartList", True)
wolffd@0 2632
wolffd@0 2633 seq = []
wolffd@0 2634 for node in doc.getElementsByTagName("chart"):
wolffd@0 2635 seq.append( (node.getAttribute("from"), node.getAttribute("to")) )
wolffd@0 2636
wolffd@0 2637 return seq
wolffd@0 2638
wolffd@0 2639 def get_weekly_artist_charts(self, from_date = None, to_date = None):
wolffd@0 2640 """Returns the weekly artist charts for the week starting from the from_date value to the to_date value."""
wolffd@0 2641
wolffd@0 2642 params = self._get_params()
wolffd@0 2643 if from_date and to_date:
wolffd@0 2644 params["from"] = from_date
wolffd@0 2645 params["to"] = to_date
wolffd@0 2646
wolffd@0 2647 doc = self._request("group.getWeeklyArtistChart", True, params)
wolffd@0 2648
wolffd@0 2649 seq = []
wolffd@0 2650 for node in doc.getElementsByTagName("artist"):
wolffd@0 2651 item = Artist(_extract(node, "name"), self.network)
wolffd@0 2652 weight = _number(_extract(node, "playcount"))
wolffd@0 2653 seq.append(TopItem(item, weight))
wolffd@0 2654
wolffd@0 2655 return seq
wolffd@0 2656
wolffd@0 2657 def get_weekly_album_charts(self, from_date = None, to_date = None):
wolffd@0 2658 """Returns the weekly album charts for the week starting from the from_date value to the to_date value."""
wolffd@0 2659
wolffd@0 2660 params = self._get_params()
wolffd@0 2661 if from_date and to_date:
wolffd@0 2662 params["from"] = from_date
wolffd@0 2663 params["to"] = to_date
wolffd@0 2664
wolffd@0 2665 doc = self._request("group.getWeeklyAlbumChart", True, params)
wolffd@0 2666
wolffd@0 2667 seq = []
wolffd@0 2668 for node in doc.getElementsByTagName("album"):
wolffd@0 2669 item = Album(_extract(node, "artist"), _extract(node, "name"), self.network)
wolffd@0 2670 weight = _number(_extract(node, "playcount"))
wolffd@0 2671 seq.append(TopItem(item, weight))
wolffd@0 2672
wolffd@0 2673 return seq
wolffd@0 2674
wolffd@0 2675 def get_weekly_track_charts(self, from_date = None, to_date = None):
wolffd@0 2676 """Returns the weekly track charts for the week starting from the from_date value to the to_date value."""
wolffd@0 2677
wolffd@0 2678 params = self._get_params()
wolffd@0 2679 if from_date and to_date:
wolffd@0 2680 params["from"] = from_date
wolffd@0 2681 params["to"] = to_date
wolffd@0 2682
wolffd@0 2683 doc = self._request("group.getWeeklyTrackChart", True, params)
wolffd@0 2684
wolffd@0 2685 seq = []
wolffd@0 2686 for node in doc.getElementsByTagName("track"):
wolffd@0 2687 item = Track(_extract(node, "artist"), _extract(node, "name"), self.network)
wolffd@0 2688 weight = _number(_extract(node, "playcount"))
wolffd@0 2689 seq.append(TopItem(item, weight))
wolffd@0 2690
wolffd@0 2691 return seq
wolffd@0 2692
wolffd@0 2693 def get_url(self, domain_name = DOMAIN_ENGLISH):
wolffd@0 2694 """Returns the url of the group page on the network.
wolffd@0 2695 * domain_name: The network's language domain. Possible values:
wolffd@0 2696 o DOMAIN_ENGLISH
wolffd@0 2697 o DOMAIN_GERMAN
wolffd@0 2698 o DOMAIN_SPANISH
wolffd@0 2699 o DOMAIN_FRENCH
wolffd@0 2700 o DOMAIN_ITALIAN
wolffd@0 2701 o DOMAIN_POLISH
wolffd@0 2702 o DOMAIN_PORTUGUESE
wolffd@0 2703 o DOMAIN_SWEDISH
wolffd@0 2704 o DOMAIN_TURKISH
wolffd@0 2705 o DOMAIN_RUSSIAN
wolffd@0 2706 o DOMAIN_JAPANESE
wolffd@0 2707 o DOMAIN_CHINESE
wolffd@0 2708 """
wolffd@0 2709
wolffd@0 2710 name = _url_safe(self.get_name())
wolffd@0 2711
wolffd@0 2712 return self.network._get_url(domain_name, "group") %{'name': name}
wolffd@0 2713
wolffd@0 2714 def get_members(self, limit=50):
wolffd@0 2715 """
wolffd@0 2716 Returns a sequence of User objects
wolffd@0 2717 if limit==None it will return all
wolffd@0 2718 """
wolffd@0 2719
wolffd@0 2720 nodes = _collect_nodes(limit, self, "group.getMembers", False)
wolffd@0 2721
wolffd@0 2722 users = []
wolffd@0 2723
wolffd@0 2724 for node in nodes:
wolffd@0 2725 users.append(User(_extract(node, "name"), self.network))
wolffd@0 2726
wolffd@0 2727 return users
wolffd@0 2728
wolffd@0 2729 class XSPF(_BaseObject):
wolffd@0 2730 "A Last.fm XSPF playlist."""
wolffd@0 2731
wolffd@0 2732 uri = None
wolffd@0 2733
wolffd@0 2734 def __init__(self, uri, network):
wolffd@0 2735 _BaseObject.__init__(self, network)
wolffd@0 2736
wolffd@0 2737 self.uri = uri
wolffd@0 2738
wolffd@0 2739 def _get_params(self):
wolffd@0 2740 return {'playlistURL': self.get_uri()}
wolffd@0 2741
wolffd@0 2742 @_string_output
wolffd@0 2743 def __str__(self):
wolffd@0 2744 return self.get_uri()
wolffd@0 2745
wolffd@0 2746 def __eq__(self, other):
wolffd@0 2747 return self.get_uri() == other.get_uri()
wolffd@0 2748
wolffd@0 2749 def __ne__(self, other):
wolffd@0 2750 return self.get_uri() != other.get_uri()
wolffd@0 2751
wolffd@0 2752 def get_uri(self):
wolffd@0 2753 """Returns the Last.fm playlist URI. """
wolffd@0 2754
wolffd@0 2755 return self.uri
wolffd@0 2756
wolffd@0 2757 def get_tracks(self):
wolffd@0 2758 """Returns the tracks on this playlist."""
wolffd@0 2759
wolffd@0 2760 doc = self._request('playlist.fetch', True)
wolffd@0 2761
wolffd@0 2762 seq = []
wolffd@0 2763 for n in doc.getElementsByTagName('track'):
wolffd@0 2764 title = _extract(n, 'title')
wolffd@0 2765 artist = _extract(n, 'creator')
wolffd@0 2766
wolffd@0 2767 seq.append(Track(artist, title, self.network))
wolffd@0 2768
wolffd@0 2769 return seq
wolffd@0 2770
wolffd@0 2771 class User(_BaseObject):
wolffd@0 2772 """A Last.fm user."""
wolffd@0 2773
wolffd@0 2774 name = None
wolffd@0 2775
wolffd@0 2776 def __init__(self, user_name, network):
wolffd@0 2777 _BaseObject.__init__(self, network)
wolffd@0 2778
wolffd@0 2779 self.name = user_name
wolffd@0 2780
wolffd@0 2781 self._past_events_index = 0
wolffd@0 2782 self._recommended_events_index = 0
wolffd@0 2783 self._recommended_artists_index = 0
wolffd@0 2784
wolffd@0 2785 def __repr__(self):
wolffd@0 2786 return "pylast.User(%s, %s)" %(repr(self.name), repr(self.network))
wolffd@0 2787
wolffd@0 2788 @_string_output
wolffd@0 2789 def __str__(self):
wolffd@0 2790 return self.get_name()
wolffd@0 2791
wolffd@0 2792 def __eq__(self, another):
wolffd@0 2793 return self.get_name() == another.get_name()
wolffd@0 2794
wolffd@0 2795 def __ne__(self, another):
wolffd@0 2796 return self.get_name() != another.get_name()
wolffd@0 2797
wolffd@0 2798 def _get_params(self):
wolffd@0 2799 return {"user": self.get_name()}
wolffd@0 2800
wolffd@0 2801 def get_name(self, properly_capitalized=False):
wolffd@0 2802 """Returns the nuser name."""
wolffd@0 2803
wolffd@0 2804 if properly_capitalized:
wolffd@0 2805 self.name = _extract(self._request("user.getInfo", True), "name")
wolffd@0 2806
wolffd@0 2807 return self.name
wolffd@0 2808
wolffd@0 2809 def get_upcoming_events(self):
wolffd@0 2810 """Returns all the upcoming events for this user. """
wolffd@0 2811
wolffd@0 2812 doc = self._request('user.getEvents', True)
wolffd@0 2813
wolffd@0 2814 ids = _extract_all(doc, 'id')
wolffd@0 2815 events = []
wolffd@0 2816
wolffd@0 2817 for e_id in ids:
wolffd@0 2818 events.append(Event(e_id, self.network))
wolffd@0 2819
wolffd@0 2820 return events
wolffd@0 2821
wolffd@0 2822 def get_friends(self, limit = 50):
wolffd@0 2823 """Returns a list of the user's friends. """
wolffd@0 2824
wolffd@0 2825 seq = []
wolffd@0 2826 for node in _collect_nodes(limit, self, "user.getFriends", False):
wolffd@0 2827 seq.append(User(_extract(node, "name"), self.network))
wolffd@0 2828
wolffd@0 2829 return seq
wolffd@0 2830
wolffd@0 2831 def get_loved_tracks(self, limit=50):
wolffd@0 2832 """Returns this user's loved track as a sequence of LovedTrack objects
wolffd@0 2833 in reverse order of their timestamp, all the way back to the first track.
wolffd@0 2834
wolffd@0 2835 If limit==None, it will try to pull all the available data.
wolffd@0 2836
wolffd@0 2837 This method uses caching. Enable caching only if you're pulling a
wolffd@0 2838 large amount of data.
wolffd@0 2839
wolffd@0 2840 Use extract_items() with the return of this function to
wolffd@0 2841 get only a sequence of Track objects with no playback dates. """
wolffd@0 2842
wolffd@0 2843 params = self._get_params()
wolffd@0 2844 if limit:
wolffd@0 2845 params['limit'] = limit
wolffd@0 2846
wolffd@0 2847 seq = []
wolffd@0 2848 for track in _collect_nodes(limit, self, "user.getLovedTracks", True, params):
wolffd@0 2849
wolffd@0 2850 title = _extract(track, "name")
wolffd@0 2851 artist = _extract(track, "name", 1)
wolffd@0 2852 date = _extract(track, "date")
wolffd@0 2853 timestamp = track.getElementsByTagName("date")[0].getAttribute("uts")
wolffd@0 2854
wolffd@0 2855 seq.append(LovedTrack(Track(artist, title, self.network), date, timestamp))
wolffd@0 2856
wolffd@0 2857 return seq
wolffd@0 2858
wolffd@0 2859 def get_neighbours(self, limit = 50):
wolffd@0 2860 """Returns a list of the user's friends."""
wolffd@0 2861
wolffd@0 2862 params = self._get_params()
wolffd@0 2863 if limit:
wolffd@0 2864 params['limit'] = limit
wolffd@0 2865
wolffd@0 2866 doc = self._request('user.getNeighbours', True, params)
wolffd@0 2867
wolffd@0 2868 seq = []
wolffd@0 2869 names = _extract_all(doc, 'name')
wolffd@0 2870
wolffd@0 2871 for name in names:
wolffd@0 2872 seq.append(User(name, self.network))
wolffd@0 2873
wolffd@0 2874 return seq
wolffd@0 2875
wolffd@0 2876 def get_past_events(self, limit=50):
wolffd@0 2877 """
wolffd@0 2878 Returns a sequence of Event objects
wolffd@0 2879 if limit==None it will return all
wolffd@0 2880 """
wolffd@0 2881
wolffd@0 2882 seq = []
wolffd@0 2883 for n in _collect_nodes(limit, self, "user.getPastEvents", False):
wolffd@0 2884 seq.append(Event(_extract(n, "id"), self.network))
wolffd@0 2885
wolffd@0 2886 return seq
wolffd@0 2887
wolffd@0 2888 def get_playlists(self):
wolffd@0 2889 """Returns a list of Playlists that this user owns."""
wolffd@0 2890
wolffd@0 2891 doc = self._request("user.getPlaylists", True)
wolffd@0 2892
wolffd@0 2893 playlists = []
wolffd@0 2894 for playlist_id in _extract_all(doc, "id"):
wolffd@0 2895 playlists.append(Playlist(self.get_name(), playlist_id, self.network))
wolffd@0 2896
wolffd@0 2897 return playlists
wolffd@0 2898
wolffd@0 2899 def get_now_playing(self):
wolffd@0 2900 """Returns the currently playing track, or None if nothing is playing. """
wolffd@0 2901
wolffd@0 2902 params = self._get_params()
wolffd@0 2903 params['limit'] = '1'
wolffd@0 2904
wolffd@0 2905 doc = self._request('user.getRecentTracks', False, params)
wolffd@0 2906
wolffd@0 2907 e = doc.getElementsByTagName('track')[0]
wolffd@0 2908
wolffd@0 2909 if not e.hasAttribute('nowplaying'):
wolffd@0 2910 return None
wolffd@0 2911
wolffd@0 2912 artist = _extract(e, 'artist')
wolffd@0 2913 title = _extract(e, 'name')
wolffd@0 2914
wolffd@0 2915 return Track(artist, title, self.network)
wolffd@0 2916
wolffd@0 2917
wolffd@0 2918 def get_recent_tracks(self, limit = 10):
wolffd@0 2919 """Returns this user's played track as a sequence of PlayedTrack objects
wolffd@0 2920 in reverse order of their playtime, all the way back to the first track.
wolffd@0 2921
wolffd@0 2922 If limit==None, it will try to pull all the available data.
wolffd@0 2923
wolffd@0 2924 This method uses caching. Enable caching only if you're pulling a
wolffd@0 2925 large amount of data.
wolffd@0 2926
wolffd@0 2927 Use extract_items() with the return of this function to
wolffd@0 2928 get only a sequence of Track objects with no playback dates. """
wolffd@0 2929
wolffd@0 2930 params = self._get_params()
wolffd@0 2931 if limit:
wolffd@0 2932 params['limit'] = limit
wolffd@0 2933
wolffd@0 2934 seq = []
wolffd@0 2935 for track in _collect_nodes(limit, self, "user.getRecentTracks", True, params):
wolffd@0 2936
wolffd@0 2937 if track.hasAttribute('nowplaying'):
wolffd@0 2938 continue #to prevent the now playing track from sneaking in here
wolffd@0 2939
wolffd@0 2940 title = _extract(track, "name")
wolffd@0 2941 artist = _extract(track, "artist")
wolffd@0 2942 date = _extract(track, "date")
wolffd@0 2943 timestamp = track.getElementsByTagName("date")[0].getAttribute("uts")
wolffd@0 2944
wolffd@0 2945 seq.append(PlayedTrack(Track(artist, title, self.network), date, timestamp))
wolffd@0 2946
wolffd@0 2947 return seq
wolffd@0 2948
wolffd@0 2949 def get_id(self):
wolffd@0 2950 """Returns the user id."""
wolffd@0 2951
wolffd@0 2952 doc = self._request("user.getInfo", True)
wolffd@0 2953
wolffd@0 2954 return _extract(doc, "id")
wolffd@0 2955
wolffd@0 2956 def get_language(self):
wolffd@0 2957 """Returns the language code of the language used by the user."""
wolffd@0 2958
wolffd@0 2959 doc = self._request("user.getInfo", True)
wolffd@0 2960
wolffd@0 2961 return _extract(doc, "lang")
wolffd@0 2962
wolffd@0 2963 def get_country(self):
wolffd@0 2964 """Returns the name of the country of the user."""
wolffd@0 2965
wolffd@0 2966 doc = self._request("user.getInfo", True)
wolffd@0 2967
wolffd@0 2968 return Country(_extract(doc, "country"), self.network)
wolffd@0 2969
wolffd@0 2970 def get_age(self):
wolffd@0 2971 """Returns the user's age."""
wolffd@0 2972
wolffd@0 2973 doc = self._request("user.getInfo", True)
wolffd@0 2974
wolffd@0 2975 return _number(_extract(doc, "age"))
wolffd@0 2976
wolffd@0 2977 def get_gender(self):
wolffd@0 2978 """Returns the user's gender. Either USER_MALE or USER_FEMALE."""
wolffd@0 2979
wolffd@0 2980 doc = self._request("user.getInfo", True)
wolffd@0 2981
wolffd@0 2982 value = _extract(doc, "gender")
wolffd@0 2983
wolffd@0 2984 if value == 'm':
wolffd@0 2985 return USER_MALE
wolffd@0 2986 elif value == 'f':
wolffd@0 2987 return USER_FEMALE
wolffd@0 2988
wolffd@0 2989 return None
wolffd@0 2990
wolffd@0 2991 def is_subscriber(self):
wolffd@0 2992 """Returns whether the user is a subscriber or not. True or False."""
wolffd@0 2993
wolffd@0 2994 doc = self._request("user.getInfo", True)
wolffd@0 2995
wolffd@0 2996 return _extract(doc, "subscriber") == "1"
wolffd@0 2997
wolffd@0 2998 def get_playcount(self):
wolffd@0 2999 """Returns the user's playcount so far."""
wolffd@0 3000
wolffd@0 3001 doc = self._request("user.getInfo", True)
wolffd@0 3002
wolffd@0 3003 return _number(_extract(doc, "playcount"))
wolffd@0 3004
wolffd@0 3005 def get_top_albums(self, period = PERIOD_OVERALL):
wolffd@0 3006 """Returns the top albums played by a user.
wolffd@0 3007 * period: The period of time. Possible values:
wolffd@0 3008 o PERIOD_OVERALL
wolffd@0 3009 o PERIOD_7DAYS
wolffd@0 3010 o PERIOD_3MONTHS
wolffd@0 3011 o PERIOD_6MONTHS
wolffd@0 3012 o PERIOD_12MONTHS
wolffd@0 3013 """
wolffd@0 3014
wolffd@0 3015 params = self._get_params()
wolffd@0 3016 params['period'] = period
wolffd@0 3017
wolffd@0 3018 doc = self._request('user.getTopAlbums', True, params)
wolffd@0 3019
wolffd@0 3020 seq = []
wolffd@0 3021 for album in doc.getElementsByTagName('album'):
wolffd@0 3022 name = _extract(album, 'name')
wolffd@0 3023 artist = _extract(album, 'name', 1)
wolffd@0 3024 playcount = _extract(album, "playcount")
wolffd@0 3025
wolffd@0 3026 seq.append(TopItem(Album(artist, name, self.network), playcount))
wolffd@0 3027
wolffd@0 3028 return seq
wolffd@0 3029
wolffd@0 3030 def get_top_artists(self, period = PERIOD_OVERALL):
wolffd@0 3031 """Returns the top artists played by a user.
wolffd@0 3032 * period: The period of time. Possible values:
wolffd@0 3033 o PERIOD_OVERALL
wolffd@0 3034 o PERIOD_7DAYS
wolffd@0 3035 o PERIOD_3MONTHS
wolffd@0 3036 o PERIOD_6MONTHS
wolffd@0 3037 o PERIOD_12MONTHS
wolffd@0 3038 """
wolffd@0 3039
wolffd@0 3040 params = self._get_params()
wolffd@0 3041 params['period'] = period
wolffd@0 3042
wolffd@0 3043 doc = self._request('user.getTopArtists', True, params)
wolffd@0 3044
wolffd@0 3045 seq = []
wolffd@0 3046 for node in doc.getElementsByTagName('artist'):
wolffd@0 3047 name = _extract(node, 'name')
wolffd@0 3048 playcount = _extract(node, "playcount")
wolffd@0 3049
wolffd@0 3050 seq.append(TopItem(Artist(name, self.network), playcount))
wolffd@0 3051
wolffd@0 3052 return seq
wolffd@0 3053
wolffd@0 3054 def get_top_tags(self, limit=None):
wolffd@0 3055 """Returns a sequence of the top tags used by this user with their counts as TopItem objects.
wolffd@0 3056 * limit: The limit of how many tags to return.
wolffd@0 3057 """
wolffd@0 3058
wolffd@0 3059 doc = self._request("user.getTopTags", True)
wolffd@0 3060
wolffd@0 3061 seq = []
wolffd@0 3062 for node in doc.getElementsByTagName("tag"):
wolffd@0 3063 seq.append(TopItem(Tag(_extract(node, "name"), self.network), _extract(node, "count")))
wolffd@0 3064
wolffd@0 3065 if limit:
wolffd@0 3066 seq = seq[:limit]
wolffd@0 3067
wolffd@0 3068 return seq
wolffd@0 3069
wolffd@0 3070 def get_top_tracks(self, period = PERIOD_OVERALL):
wolffd@0 3071 """Returns the top tracks played by a user.
wolffd@0 3072 * period: The period of time. Possible values:
wolffd@0 3073 o PERIOD_OVERALL
wolffd@0 3074 o PERIOD_7DAYS
wolffd@0 3075 o PERIOD_3MONTHS
wolffd@0 3076 o PERIOD_6MONTHS
wolffd@0 3077 o PERIOD_12MONTHS
wolffd@0 3078 """
wolffd@0 3079
wolffd@0 3080 params = self._get_params()
wolffd@0 3081 params['period'] = period
wolffd@0 3082
wolffd@0 3083 doc = self._request('user.getTopTracks', True, params)
wolffd@0 3084
wolffd@0 3085 seq = []
wolffd@0 3086 for track in doc.getElementsByTagName('track'):
wolffd@0 3087 name = _extract(track, 'name')
wolffd@0 3088 artist = _extract(track, 'name', 1)
wolffd@0 3089 playcount = _extract(track, "playcount")
wolffd@0 3090
wolffd@0 3091 seq.append(TopItem(Track(artist, name, self.network), playcount))
wolffd@0 3092
wolffd@0 3093 return seq
wolffd@0 3094
wolffd@0 3095 def get_weekly_chart_dates(self):
wolffd@0 3096 """Returns a list of From and To tuples for the available charts."""
wolffd@0 3097
wolffd@0 3098 doc = self._request("user.getWeeklyChartList", True)
wolffd@0 3099
wolffd@0 3100 seq = []
wolffd@0 3101 for node in doc.getElementsByTagName("chart"):
wolffd@0 3102 seq.append( (node.getAttribute("from"), node.getAttribute("to")) )
wolffd@0 3103
wolffd@0 3104 return seq
wolffd@0 3105
wolffd@0 3106 def get_weekly_artist_charts(self, from_date = None, to_date = None):
wolffd@0 3107 """Returns the weekly artist charts for the week starting from the from_date value to the to_date value."""
wolffd@0 3108
wolffd@0 3109 params = self._get_params()
wolffd@0 3110 if from_date and to_date:
wolffd@0 3111 params["from"] = from_date
wolffd@0 3112 params["to"] = to_date
wolffd@0 3113
wolffd@0 3114 doc = self._request("user.getWeeklyArtistChart", True, params)
wolffd@0 3115
wolffd@0 3116 seq = []
wolffd@0 3117 for node in doc.getElementsByTagName("artist"):
wolffd@0 3118 item = Artist(_extract(node, "name"), self.network)
wolffd@0 3119 weight = _number(_extract(node, "playcount"))
wolffd@0 3120 seq.append(TopItem(item, weight))
wolffd@0 3121
wolffd@0 3122 return seq
wolffd@0 3123
wolffd@0 3124 def get_weekly_album_charts(self, from_date = None, to_date = None):
wolffd@0 3125 """Returns the weekly album charts for the week starting from the from_date value to the to_date value."""
wolffd@0 3126
wolffd@0 3127 params = self._get_params()
wolffd@0 3128 if from_date and to_date:
wolffd@0 3129 params["from"] = from_date
wolffd@0 3130 params["to"] = to_date
wolffd@0 3131
wolffd@0 3132 doc = self._request("user.getWeeklyAlbumChart", True, params)
wolffd@0 3133
wolffd@0 3134 seq = []
wolffd@0 3135 for node in doc.getElementsByTagName("album"):
wolffd@0 3136 item = Album(_extract(node, "artist"), _extract(node, "name"), self.network)
wolffd@0 3137 weight = _number(_extract(node, "playcount"))
wolffd@0 3138 seq.append(TopItem(item, weight))
wolffd@0 3139
wolffd@0 3140 return seq
wolffd@0 3141
wolffd@0 3142 def get_weekly_track_charts(self, from_date = None, to_date = None):
wolffd@0 3143 """Returns the weekly track charts for the week starting from the from_date value to the to_date value."""
wolffd@0 3144
wolffd@0 3145 params = self._get_params()
wolffd@0 3146 if from_date and to_date:
wolffd@0 3147 params["from"] = from_date
wolffd@0 3148 params["to"] = to_date
wolffd@0 3149
wolffd@0 3150 doc = self._request("user.getWeeklyTrackChart", True, params)
wolffd@0 3151
wolffd@0 3152 seq = []
wolffd@0 3153 for node in doc.getElementsByTagName("track"):
wolffd@0 3154 item = Track(_extract(node, "artist"), _extract(node, "name"), self.network)
wolffd@0 3155 weight = _number(_extract(node, "playcount"))
wolffd@0 3156 seq.append(TopItem(item, weight))
wolffd@0 3157
wolffd@0 3158 return seq
wolffd@0 3159
wolffd@0 3160 def compare_with_user(self, user, shared_artists_limit = None):
wolffd@0 3161 """Compare this user with another Last.fm user.
wolffd@0 3162 Returns a sequence (tasteometer_score, (shared_artist1, shared_artist2, ...))
wolffd@0 3163 user: A User object or a username string/unicode object.
wolffd@0 3164 """
wolffd@0 3165
wolffd@0 3166 if isinstance(user, User):
wolffd@0 3167 user = user.get_name()
wolffd@0 3168
wolffd@0 3169 params = self._get_params()
wolffd@0 3170 if shared_artists_limit:
wolffd@0 3171 params['limit'] = shared_artists_limit
wolffd@0 3172 params['type1'] = 'user'
wolffd@0 3173 params['type2'] = 'user'
wolffd@0 3174 params['value1'] = self.get_name()
wolffd@0 3175 params['value2'] = user
wolffd@0 3176
wolffd@0 3177 doc = self._request('tasteometer.compare', False, params)
wolffd@0 3178
wolffd@0 3179 score = _extract(doc, 'score')
wolffd@0 3180
wolffd@0 3181 artists = doc.getElementsByTagName('artists')[0]
wolffd@0 3182 shared_artists_names = _extract_all(artists, 'name')
wolffd@0 3183
wolffd@0 3184 shared_artists_seq = []
wolffd@0 3185
wolffd@0 3186 for name in shared_artists_names:
wolffd@0 3187 shared_artists_seq.append(Artist(name, self.network))
wolffd@0 3188
wolffd@0 3189 return (score, shared_artists_seq)
wolffd@0 3190
wolffd@0 3191 def get_image(self):
wolffd@0 3192 """Returns the user's avatar."""
wolffd@0 3193
wolffd@0 3194 doc = self._request("user.getInfo", True)
wolffd@0 3195
wolffd@0 3196 return _extract(doc, "image")
wolffd@0 3197
wolffd@0 3198 def get_url(self, domain_name = DOMAIN_ENGLISH):
wolffd@0 3199 """Returns the url of the user page on the network.
wolffd@0 3200 * domain_name: The network's language domain. Possible values:
wolffd@0 3201 o DOMAIN_ENGLISH
wolffd@0 3202 o DOMAIN_GERMAN
wolffd@0 3203 o DOMAIN_SPANISH
wolffd@0 3204 o DOMAIN_FRENCH
wolffd@0 3205 o DOMAIN_ITALIAN
wolffd@0 3206 o DOMAIN_POLISH
wolffd@0 3207 o DOMAIN_PORTUGUESE
wolffd@0 3208 o DOMAIN_SWEDISH
wolffd@0 3209 o DOMAIN_TURKISH
wolffd@0 3210 o DOMAIN_RUSSIAN
wolffd@0 3211 o DOMAIN_JAPANESE
wolffd@0 3212 o DOMAIN_CHINESE
wolffd@0 3213 """
wolffd@0 3214
wolffd@0 3215 name = _url_safe(self.get_name())
wolffd@0 3216
wolffd@0 3217 return self.network._get_url(domain_name, "user") %{'name': name}
wolffd@0 3218
wolffd@0 3219 def get_library(self):
wolffd@0 3220 """Returns the associated Library object. """
wolffd@0 3221
wolffd@0 3222 return Library(self, self.network)
wolffd@0 3223
wolffd@0 3224 def get_shouts(self, limit=50):
wolffd@0 3225 """
wolffd@0 3226 Returns a sequqence of Shout objects
wolffd@0 3227 """
wolffd@0 3228
wolffd@0 3229 shouts = []
wolffd@0 3230 for node in _collect_nodes(limit, self, "user.getShouts", False):
wolffd@0 3231 shouts.append(Shout(
wolffd@0 3232 _extract(node, "body"),
wolffd@0 3233 User(_extract(node, "author"), self.network),
wolffd@0 3234 _extract(node, "date")
wolffd@0 3235 )
wolffd@0 3236 )
wolffd@0 3237 return shouts
wolffd@0 3238
wolffd@0 3239 def shout(self, message):
wolffd@0 3240 """
wolffd@0 3241 Post a shout
wolffd@0 3242 """
wolffd@0 3243
wolffd@0 3244 params = self._get_params()
wolffd@0 3245 params["message"] = message
wolffd@0 3246
wolffd@0 3247 self._request("user.Shout", False, params)
wolffd@0 3248
wolffd@0 3249 class AuthenticatedUser(User):
wolffd@0 3250 def __init__(self, network):
wolffd@0 3251 User.__init__(self, "", network);
wolffd@0 3252
wolffd@0 3253 def _get_params(self):
wolffd@0 3254 return {"user": self.get_name()}
wolffd@0 3255
wolffd@0 3256 def get_name(self):
wolffd@0 3257 """Returns the name of the authenticated user."""
wolffd@0 3258
wolffd@0 3259 doc = self._request("user.getInfo", True, {"user": ""}) # hack
wolffd@0 3260
wolffd@0 3261 self.name = _extract(doc, "name")
wolffd@0 3262 return self.name
wolffd@0 3263
wolffd@0 3264 def get_recommended_events(self, limit=50):
wolffd@0 3265 """
wolffd@0 3266 Returns a sequence of Event objects
wolffd@0 3267 if limit==None it will return all
wolffd@0 3268 """
wolffd@0 3269
wolffd@0 3270 seq = []
wolffd@0 3271 for node in _collect_nodes(limit, self, "user.getRecommendedEvents", False):
wolffd@0 3272 seq.append(Event(_extract(node, "id"), self.network))
wolffd@0 3273
wolffd@0 3274 return seq
wolffd@0 3275
wolffd@0 3276 def get_recommended_artists(self, limit=50):
wolffd@0 3277 """
wolffd@0 3278 Returns a sequence of Event objects
wolffd@0 3279 if limit==None it will return all
wolffd@0 3280 """
wolffd@0 3281
wolffd@0 3282 seq = []
wolffd@0 3283 for node in _collect_nodes(limit, self, "user.getRecommendedArtists", False):
wolffd@0 3284 seq.append(Artist(_extract(node, "name"), self.network))
wolffd@0 3285
wolffd@0 3286 return seq
wolffd@0 3287
wolffd@0 3288 class _Search(_BaseObject):
wolffd@0 3289 """An abstract class. Use one of its derivatives."""
wolffd@0 3290
wolffd@0 3291 def __init__(self, ws_prefix, search_terms, network):
wolffd@0 3292 _BaseObject.__init__(self, network)
wolffd@0 3293
wolffd@0 3294 self._ws_prefix = ws_prefix
wolffd@0 3295 self.search_terms = search_terms
wolffd@0 3296
wolffd@0 3297 self._last_page_index = 0
wolffd@0 3298
wolffd@0 3299 def _get_params(self):
wolffd@0 3300 params = {}
wolffd@0 3301
wolffd@0 3302 for key in self.search_terms.keys():
wolffd@0 3303 params[key] = self.search_terms[key]
wolffd@0 3304
wolffd@0 3305 return params
wolffd@0 3306
wolffd@0 3307 def get_total_result_count(self):
wolffd@0 3308 """Returns the total count of all the results."""
wolffd@0 3309
wolffd@0 3310 doc = self._request(self._ws_prefix + ".search", True)
wolffd@0 3311
wolffd@0 3312 return _extract(doc, "opensearch:totalResults")
wolffd@0 3313
wolffd@0 3314 def _retreive_page(self, page_index):
wolffd@0 3315 """Returns the node of matches to be processed"""
wolffd@0 3316
wolffd@0 3317 params = self._get_params()
wolffd@0 3318 params["page"] = str(page_index)
wolffd@0 3319 doc = self._request(self._ws_prefix + ".search", True, params)
wolffd@0 3320
wolffd@0 3321 return doc.getElementsByTagName(self._ws_prefix + "matches")[0]
wolffd@0 3322
wolffd@0 3323 def _retrieve_next_page(self):
wolffd@0 3324 self._last_page_index += 1
wolffd@0 3325 return self._retreive_page(self._last_page_index)
wolffd@0 3326
wolffd@0 3327 class AlbumSearch(_Search):
wolffd@0 3328 """Search for an album by name."""
wolffd@0 3329
wolffd@0 3330 def __init__(self, album_name, network):
wolffd@0 3331
wolffd@0 3332 _Search.__init__(self, "album", {"album": album_name}, network)
wolffd@0 3333
wolffd@0 3334 def get_next_page(self):
wolffd@0 3335 """Returns the next page of results as a sequence of Album objects."""
wolffd@0 3336
wolffd@0 3337 master_node = self._retrieve_next_page()
wolffd@0 3338
wolffd@0 3339 seq = []
wolffd@0 3340 for node in master_node.getElementsByTagName("album"):
wolffd@0 3341 seq.append(Album(_extract(node, "artist"), _extract(node, "name"), self.network))
wolffd@0 3342
wolffd@0 3343 return seq
wolffd@0 3344
wolffd@0 3345 class ArtistSearch(_Search):
wolffd@0 3346 """Search for an artist by artist name."""
wolffd@0 3347
wolffd@0 3348 def __init__(self, artist_name, network):
wolffd@0 3349 _Search.__init__(self, "artist", {"artist": artist_name}, network)
wolffd@0 3350
wolffd@0 3351 def get_next_page(self):
wolffd@0 3352 """Returns the next page of results as a sequence of Artist objects."""
wolffd@0 3353
wolffd@0 3354 master_node = self._retrieve_next_page()
wolffd@0 3355
wolffd@0 3356 seq = []
wolffd@0 3357 for node in master_node.getElementsByTagName("artist"):
wolffd@0 3358 artist = Artist(_extract(node, "name"), self.network)
wolffd@0 3359 artist.listener_count = _number(_extract(node, "listeners"))
wolffd@0 3360 seq.append(artist)
wolffd@0 3361
wolffd@0 3362 return seq
wolffd@0 3363
wolffd@0 3364 class TagSearch(_Search):
wolffd@0 3365 """Search for a tag by tag name."""
wolffd@0 3366
wolffd@0 3367 def __init__(self, tag_name, network):
wolffd@0 3368
wolffd@0 3369 _Search.__init__(self, "tag", {"tag": tag_name}, network)
wolffd@0 3370
wolffd@0 3371 def get_next_page(self):
wolffd@0 3372 """Returns the next page of results as a sequence of Tag objects."""
wolffd@0 3373
wolffd@0 3374 master_node = self._retrieve_next_page()
wolffd@0 3375
wolffd@0 3376 seq = []
wolffd@0 3377 for node in master_node.getElementsByTagName("tag"):
wolffd@0 3378 tag = Tag(_extract(node, "name"), self.network)
wolffd@0 3379 tag.tag_count = _number(_extract(node, "count"))
wolffd@0 3380 seq.append(tag)
wolffd@0 3381
wolffd@0 3382 return seq
wolffd@0 3383
wolffd@0 3384 class TrackSearch(_Search):
wolffd@0 3385 """Search for a track by track title. If you don't wanna narrow the results down
wolffd@0 3386 by specifying the artist name, set it to empty string."""
wolffd@0 3387
wolffd@0 3388 def __init__(self, artist_name, track_title, network):
wolffd@0 3389
wolffd@0 3390 _Search.__init__(self, "track", {"track": track_title, "artist": artist_name}, network)
wolffd@0 3391
wolffd@0 3392 def get_next_page(self):
wolffd@0 3393 """Returns the next page of results as a sequence of Track objects."""
wolffd@0 3394
wolffd@0 3395 master_node = self._retrieve_next_page()
wolffd@0 3396
wolffd@0 3397 seq = []
wolffd@0 3398 for node in master_node.getElementsByTagName("track"):
wolffd@0 3399 track = Track(_extract(node, "artist"), _extract(node, "name"), self.network)
wolffd@0 3400 track.listener_count = _number(_extract(node, "listeners"))
wolffd@0 3401 seq.append(track)
wolffd@0 3402
wolffd@0 3403 return seq
wolffd@0 3404
wolffd@0 3405 class VenueSearch(_Search):
wolffd@0 3406 """Search for a venue by its name. If you don't wanna narrow the results down
wolffd@0 3407 by specifying a country, set it to empty string."""
wolffd@0 3408
wolffd@0 3409 def __init__(self, venue_name, country_name, network):
wolffd@0 3410
wolffd@0 3411 _Search.__init__(self, "venue", {"venue": venue_name, "country": country_name}, network)
wolffd@0 3412
wolffd@0 3413 def get_next_page(self):
wolffd@0 3414 """Returns the next page of results as a sequence of Track objects."""
wolffd@0 3415
wolffd@0 3416 master_node = self._retrieve_next_page()
wolffd@0 3417
wolffd@0 3418 seq = []
wolffd@0 3419 for node in master_node.getElementsByTagName("venue"):
wolffd@0 3420 seq.append(Venue(_extract(node, "id"), self.network))
wolffd@0 3421
wolffd@0 3422 return seq
wolffd@0 3423
wolffd@0 3424 class Venue(_BaseObject):
wolffd@0 3425 """A venue where events are held."""
wolffd@0 3426
wolffd@0 3427 # TODO: waiting for a venue.getInfo web service to use.
wolffd@0 3428
wolffd@0 3429 id = None
wolffd@0 3430
wolffd@0 3431 def __init__(self, id, network):
wolffd@0 3432 _BaseObject.__init__(self, network)
wolffd@0 3433
wolffd@0 3434 self.id = _number(id)
wolffd@0 3435
wolffd@0 3436 def __repr__(self):
wolffd@0 3437 return "pylast.Venue(%s, %s)" %(repr(self.id), repr(self.network))
wolffd@0 3438
wolffd@0 3439 @_string_output
wolffd@0 3440 def __str__(self):
wolffd@0 3441 return "Venue #" + str(self.id)
wolffd@0 3442
wolffd@0 3443 def __eq__(self, other):
wolffd@0 3444 return self.get_id() == other.get_id()
wolffd@0 3445
wolffd@0 3446 def _get_params(self):
wolffd@0 3447 return {"venue": self.get_id()}
wolffd@0 3448
wolffd@0 3449 def get_id(self):
wolffd@0 3450 """Returns the id of the venue."""
wolffd@0 3451
wolffd@0 3452 return self.id
wolffd@0 3453
wolffd@0 3454 def get_upcoming_events(self):
wolffd@0 3455 """Returns the upcoming events in this venue."""
wolffd@0 3456
wolffd@0 3457 doc = self._request("venue.getEvents", True)
wolffd@0 3458
wolffd@0 3459 seq = []
wolffd@0 3460 for node in doc.getElementsByTagName("event"):
wolffd@0 3461 seq.append(Event(_extract(node, "id"), self.network))
wolffd@0 3462
wolffd@0 3463 return seq
wolffd@0 3464
wolffd@0 3465 def get_past_events(self):
wolffd@0 3466 """Returns the past events held in this venue."""
wolffd@0 3467
wolffd@0 3468 doc = self._request("venue.getEvents", True)
wolffd@0 3469
wolffd@0 3470 seq = []
wolffd@0 3471 for node in doc.getElementsByTagName("event"):
wolffd@0 3472 seq.append(Event(_extract(node, "id"), self.network))
wolffd@0 3473
wolffd@0 3474 return seq
wolffd@0 3475
wolffd@0 3476 def md5(text):
wolffd@0 3477 """Returns the md5 hash of a string."""
wolffd@0 3478
wolffd@0 3479 h = hashlib.md5()
wolffd@0 3480 h.update(_unicode(text).encode("utf-8"))
wolffd@0 3481
wolffd@0 3482 return h.hexdigest()
wolffd@0 3483
wolffd@0 3484 def _unicode(text):
wolffd@0 3485 if sys.version_info[0] == 3:
wolffd@0 3486 if type(text) in (bytes, bytearray):
wolffd@0 3487 return str(text, "utf-8")
wolffd@0 3488 elif type(text) == str:
wolffd@0 3489 return text
wolffd@0 3490 else:
wolffd@0 3491 return str(text)
wolffd@0 3492
wolffd@0 3493 elif sys.version_info[0] ==2:
wolffd@0 3494 if type(text) in (str,):
wolffd@0 3495 return unicode(text, "utf-8")
wolffd@0 3496 elif type(text) == unicode:
wolffd@0 3497 return text
wolffd@0 3498 else:
wolffd@0 3499 return unicode(text)
wolffd@0 3500
wolffd@0 3501 def _string(text):
wolffd@0 3502 """For Python2 routines that can only process str type."""
wolffd@0 3503
wolffd@0 3504 if sys.version_info[0] == 3:
wolffd@0 3505 if type(text) != str:
wolffd@0 3506 return str(text)
wolffd@0 3507 else:
wolffd@0 3508 return text
wolffd@0 3509
wolffd@0 3510 elif sys.version_info[0] == 2:
wolffd@0 3511 if type(text) == str:
wolffd@0 3512 return text
wolffd@0 3513
wolffd@0 3514 if type(text) == int:
wolffd@0 3515 return str(text)
wolffd@0 3516
wolffd@0 3517 return text.encode("utf-8")
wolffd@0 3518
wolffd@0 3519 def _collect_nodes(limit, sender, method_name, cacheable, params=None):
wolffd@0 3520 """
wolffd@0 3521 Returns a sequqnce of dom.Node objects about as close to
wolffd@0 3522 limit as possible
wolffd@0 3523 """
wolffd@0 3524
wolffd@0 3525 if not params:
wolffd@0 3526 params = sender._get_params()
wolffd@0 3527
wolffd@0 3528 nodes = []
wolffd@0 3529 page = 1
wolffd@0 3530 end_of_pages = False
wolffd@0 3531
wolffd@0 3532 while not end_of_pages and (not limit or (limit and len(nodes) < limit)):
wolffd@0 3533 params["page"] = str(page)
wolffd@0 3534 doc = sender._request(method_name, cacheable, params)
wolffd@0 3535
wolffd@0 3536 main = doc.documentElement.childNodes[1]
wolffd@0 3537
wolffd@0 3538 if main.hasAttribute("totalPages"):
wolffd@0 3539 total_pages = _number(main.getAttribute("totalPages"))
wolffd@0 3540 elif main.hasAttribute("totalpages"):
wolffd@0 3541 total_pages = _number(main.getAttribute("totalpages"))
wolffd@0 3542 else:
wolffd@0 3543 raise Exception("No total pages attribute")
wolffd@0 3544
wolffd@0 3545 for node in main.childNodes:
wolffd@0 3546 if not node.nodeType == xml.dom.Node.TEXT_NODE and len(nodes) < limit:
wolffd@0 3547 nodes.append(node)
wolffd@0 3548
wolffd@0 3549 if page >= total_pages:
wolffd@0 3550 end_of_pages = True
wolffd@0 3551
wolffd@0 3552 page += 1
wolffd@0 3553
wolffd@0 3554 return nodes
wolffd@0 3555
wolffd@0 3556 def _extract(node, name, index = 0):
wolffd@0 3557 """Extracts a value from the xml string"""
wolffd@0 3558
wolffd@0 3559 nodes = node.getElementsByTagName(name)
wolffd@0 3560
wolffd@0 3561 if len(nodes):
wolffd@0 3562 if nodes[index].firstChild:
wolffd@0 3563 return _unescape_htmlentity(nodes[index].firstChild.data.strip())
wolffd@0 3564 else:
wolffd@0 3565 return None
wolffd@0 3566
wolffd@0 3567 def _extract_all(node, name, limit_count = None):
wolffd@0 3568 """Extracts all the values from the xml string. returning a list."""
wolffd@0 3569
wolffd@0 3570 seq = []
wolffd@0 3571
wolffd@0 3572 for i in range(0, len(node.getElementsByTagName(name))):
wolffd@0 3573 if len(seq) == limit_count:
wolffd@0 3574 break
wolffd@0 3575
wolffd@0 3576 seq.append(_extract(node, name, i))
wolffd@0 3577
wolffd@0 3578 return seq
wolffd@0 3579
wolffd@0 3580 def _url_safe(text):
wolffd@0 3581 """Does all kinds of tricks on a text to make it safe to use in a url."""
wolffd@0 3582
wolffd@0 3583 return url_quote_plus(url_quote_plus(_string(text))).lower()
wolffd@0 3584
wolffd@0 3585 def _number(string):
wolffd@0 3586 """
wolffd@0 3587 Extracts an int from a string. Returns a 0 if None or an empty string was passed
wolffd@0 3588 """
wolffd@0 3589
wolffd@0 3590 if not string:
wolffd@0 3591 return 0
wolffd@0 3592 elif string == "":
wolffd@0 3593 return 0
wolffd@0 3594 else:
wolffd@0 3595 try:
wolffd@0 3596 return int(string)
wolffd@0 3597 except ValueError:
wolffd@0 3598 return float(string)
wolffd@0 3599
wolffd@0 3600 def _unescape_htmlentity(string):
wolffd@0 3601
wolffd@0 3602 #string = _unicode(string)
wolffd@0 3603
wolffd@0 3604 mapping = htmlentitydefs.name2codepoint
wolffd@0 3605 for key in mapping:
wolffd@0 3606 string = string.replace("&%s;" %key, unichr(mapping[key]))
wolffd@0 3607
wolffd@0 3608 return string
wolffd@0 3609
wolffd@0 3610 def extract_items(topitems_or_libraryitems):
wolffd@0 3611 """Extracts a sequence of items from a sequence of TopItem or LibraryItem objects."""
wolffd@0 3612
wolffd@0 3613 seq = []
wolffd@0 3614 for i in topitems_or_libraryitems:
wolffd@0 3615 seq.append(i.item)
wolffd@0 3616
wolffd@0 3617 return seq
wolffd@0 3618
wolffd@0 3619 class ScrobblingError(Exception):
wolffd@0 3620 def __init__(self, message):
wolffd@0 3621 Exception.__init__(self)
wolffd@0 3622 self.message = message
wolffd@0 3623
wolffd@0 3624 @_string_output
wolffd@0 3625 def __str__(self):
wolffd@0 3626 return self.message
wolffd@0 3627
wolffd@0 3628 class BannedClientError(ScrobblingError):
wolffd@0 3629 def __init__(self):
wolffd@0 3630 ScrobblingError.__init__(self, "This version of the client has been banned")
wolffd@0 3631
wolffd@0 3632 class BadAuthenticationError(ScrobblingError):
wolffd@0 3633 def __init__(self):
wolffd@0 3634 ScrobblingError.__init__(self, "Bad authentication token")
wolffd@0 3635
wolffd@0 3636 class BadTimeError(ScrobblingError):
wolffd@0 3637 def __init__(self):
wolffd@0 3638 ScrobblingError.__init__(self, "Time provided is not close enough to current time")
wolffd@0 3639
wolffd@0 3640 class BadSessionError(ScrobblingError):
wolffd@0 3641 def __init__(self):
wolffd@0 3642 ScrobblingError.__init__(self, "Bad session id, consider re-handshaking")
wolffd@0 3643
wolffd@0 3644 class _ScrobblerRequest(object):
wolffd@0 3645
wolffd@0 3646 def __init__(self, url, params, network, type="POST"):
wolffd@0 3647
wolffd@0 3648 for key in params:
wolffd@0 3649 params[key] = str(params[key])
wolffd@0 3650
wolffd@0 3651 self.params = params
wolffd@0 3652 self.type = type
wolffd@0 3653 (self.hostname, self.subdir) = url_split_host(url[len("http:"):])
wolffd@0 3654 self.network = network
wolffd@0 3655
wolffd@0 3656 def execute(self):
wolffd@0 3657 """Returns a string response of this request."""
wolffd@0 3658
wolffd@0 3659 connection = HTTPConnection(self.hostname)
wolffd@0 3660
wolffd@0 3661 data = []
wolffd@0 3662 for name in self.params.keys():
wolffd@0 3663 value = url_quote_plus(self.params[name])
wolffd@0 3664 data.append('='.join((name, value)))
wolffd@0 3665 data = "&".join(data)
wolffd@0 3666
wolffd@0 3667 headers = {
wolffd@0 3668 "Content-type": "application/x-www-form-urlencoded",
wolffd@0 3669 "Accept-Charset": "utf-8",
wolffd@0 3670 "User-Agent": "pylast" + "/" + __version__,
wolffd@0 3671 "HOST": self.hostname
wolffd@0 3672 }
wolffd@0 3673
wolffd@0 3674 if self.type == "GET":
wolffd@0 3675 connection.request("GET", self.subdir + "?" + data, headers = headers)
wolffd@0 3676 else:
wolffd@0 3677 connection.request("POST", self.subdir, data, headers)
wolffd@0 3678 response = _unicode(connection.getresponse().read())
wolffd@0 3679
wolffd@0 3680 self._check_response_for_errors(response)
wolffd@0 3681
wolffd@0 3682 return response
wolffd@0 3683
wolffd@0 3684 def _check_response_for_errors(self, response):
wolffd@0 3685 """When passed a string response it checks for erros, raising
wolffd@0 3686 any exceptions as necessary."""
wolffd@0 3687
wolffd@0 3688 lines = response.split("\n")
wolffd@0 3689 status_line = lines[0]
wolffd@0 3690
wolffd@0 3691 if status_line == "OK":
wolffd@0 3692 return
wolffd@0 3693 elif status_line == "BANNED":
wolffd@0 3694 raise BannedClientError()
wolffd@0 3695 elif status_line == "BADAUTH":
wolffd@0 3696 raise BadAuthenticationError()
wolffd@0 3697 elif status_line == "BADTIME":
wolffd@0 3698 raise BadTimeError()
wolffd@0 3699 elif status_line == "BADSESSION":
wolffd@0 3700 raise BadSessionError()
wolffd@0 3701 elif status_line.startswith("FAILED "):
wolffd@0 3702 reason = status_line[status_line.find("FAILED ")+len("FAILED "):]
wolffd@0 3703 raise ScrobblingError(reason)
wolffd@0 3704
wolffd@0 3705 class Scrobbler(object):
wolffd@0 3706 """A class for scrobbling tracks to Last.fm"""
wolffd@0 3707
wolffd@0 3708 session_id = None
wolffd@0 3709 nowplaying_url = None
wolffd@0 3710 submissions_url = None
wolffd@0 3711
wolffd@0 3712 def __init__(self, network, client_id, client_version):
wolffd@0 3713 self.client_id = client_id
wolffd@0 3714 self.client_version = client_version
wolffd@0 3715 self.username = network.username
wolffd@0 3716 self.password = network.password_hash
wolffd@0 3717 self.network = network
wolffd@0 3718
wolffd@0 3719 def _do_handshake(self):
wolffd@0 3720 """Handshakes with the server"""
wolffd@0 3721
wolffd@0 3722 timestamp = str(int(time.time()))
wolffd@0 3723
wolffd@0 3724 if self.password and self.username:
wolffd@0 3725 token = md5(self.password + timestamp)
wolffd@0 3726 elif self.network.api_key and self.network.api_secret and self.network.session_key:
wolffd@0 3727 if not self.username:
wolffd@0 3728 self.username = self.network.get_authenticated_user().get_name()
wolffd@0 3729 token = md5(self.network.api_secret + timestamp)
wolffd@0 3730
wolffd@0 3731 params = {"hs": "true", "p": "1.2.1", "c": self.client_id,
wolffd@0 3732 "v": self.client_version, "u": self.username, "t": timestamp,
wolffd@0 3733 "a": token}
wolffd@0 3734
wolffd@0 3735 if self.network.session_key and self.network.api_key:
wolffd@0 3736 params["sk"] = self.network.session_key
wolffd@0 3737 params["api_key"] = self.network.api_key
wolffd@0 3738
wolffd@0 3739 server = self.network.submission_server
wolffd@0 3740 response = _ScrobblerRequest(server, params, self.network, "GET").execute().split("\n")
wolffd@0 3741
wolffd@0 3742 self.session_id = response[1]
wolffd@0 3743 self.nowplaying_url = response[2]
wolffd@0 3744 self.submissions_url = response[3]
wolffd@0 3745
wolffd@0 3746 def _get_session_id(self, new = False):
wolffd@0 3747 """Returns a handshake. If new is true, then it will be requested from the server
wolffd@0 3748 even if one was cached."""
wolffd@0 3749
wolffd@0 3750 if not self.session_id or new:
wolffd@0 3751 self._do_handshake()
wolffd@0 3752
wolffd@0 3753 return self.session_id
wolffd@0 3754
wolffd@0 3755 def report_now_playing(self, artist, title, album = "", duration = "", track_number = "", mbid = ""):
wolffd@0 3756
wolffd@0 3757 _deprecation_warning("DeprecationWarning: Use Netowrk.update_now_playing(...) instead")
wolffd@0 3758
wolffd@0 3759 params = {"s": self._get_session_id(), "a": artist, "t": title,
wolffd@0 3760 "b": album, "l": duration, "n": track_number, "m": mbid}
wolffd@0 3761
wolffd@0 3762 try:
wolffd@0 3763 _ScrobblerRequest(self.nowplaying_url, params, self.network).execute()
wolffd@0 3764 except BadSessionError:
wolffd@0 3765 self._do_handshake()
wolffd@0 3766 self.report_now_playing(artist, title, album, duration, track_number, mbid)
wolffd@0 3767
wolffd@0 3768 def scrobble(self, artist, title, time_started, source, mode, duration, album="", track_number="", mbid=""):
wolffd@0 3769 """Scrobble a track. parameters:
wolffd@0 3770 artist: Artist name.
wolffd@0 3771 title: Track title.
wolffd@0 3772 time_started: UTC timestamp of when the track started playing.
wolffd@0 3773 source: The source of the track
wolffd@0 3774 SCROBBLE_SOURCE_USER: Chosen by the user (the most common value, unless you have a reason for choosing otherwise, use this).
wolffd@0 3775 SCROBBLE_SOURCE_NON_PERSONALIZED_BROADCAST: Non-personalised broadcast (e.g. Shoutcast, BBC Radio 1).
wolffd@0 3776 SCROBBLE_SOURCE_PERSONALIZED_BROADCAST: Personalised recommendation except Last.fm (e.g. Pandora, Launchcast).
wolffd@0 3777 SCROBBLE_SOURCE_LASTFM: ast.fm (any mode). In this case, the 5-digit recommendation_key value must be set.
wolffd@0 3778 SCROBBLE_SOURCE_UNKNOWN: Source unknown.
wolffd@0 3779 mode: The submission mode
wolffd@0 3780 SCROBBLE_MODE_PLAYED: The track was played.
wolffd@0 3781 SCROBBLE_MODE_LOVED: The user manually loved the track (implies a listen)
wolffd@0 3782 SCROBBLE_MODE_SKIPPED: The track was skipped (Only if source was Last.fm)
wolffd@0 3783 SCROBBLE_MODE_BANNED: The track was banned (Only if source was Last.fm)
wolffd@0 3784 duration: Track duration in seconds.
wolffd@0 3785 album: The album name.
wolffd@0 3786 track_number: The track number on the album.
wolffd@0 3787 mbid: MusicBrainz ID.
wolffd@0 3788 """
wolffd@0 3789
wolffd@0 3790 _deprecation_warning("DeprecationWarning: Use Network.scrobble(...) instead")
wolffd@0 3791
wolffd@0 3792 params = {"s": self._get_session_id(), "a[0]": _string(artist), "t[0]": _string(title),
wolffd@0 3793 "i[0]": str(time_started), "o[0]": source, "r[0]": mode, "l[0]": str(duration),
wolffd@0 3794 "b[0]": _string(album), "n[0]": track_number, "m[0]": mbid}
wolffd@0 3795
wolffd@0 3796 _ScrobblerRequest(self.submissions_url, params, self.network).execute()
wolffd@0 3797
wolffd@0 3798 def scrobble_many(self, tracks):
wolffd@0 3799 """
wolffd@0 3800 Scrobble several tracks at once.
wolffd@0 3801
wolffd@0 3802 tracks: A sequence of a sequence of parameters for each trach. The order of parameters
wolffd@0 3803 is the same as if passed to the scrobble() method.
wolffd@0 3804 """
wolffd@0 3805
wolffd@0 3806 _deprecation_warning("DeprecationWarning: Use Network.scrobble_many(...) instead")
wolffd@0 3807
wolffd@0 3808 remainder = []
wolffd@0 3809
wolffd@0 3810 if len(tracks) > 50:
wolffd@0 3811 remainder = tracks[50:]
wolffd@0 3812 tracks = tracks[:50]
wolffd@0 3813
wolffd@0 3814 params = {"s": self._get_session_id()}
wolffd@0 3815
wolffd@0 3816 i = 0
wolffd@0 3817 for t in tracks:
wolffd@0 3818 _pad_list(t, 9, "")
wolffd@0 3819 params["a[%s]" % str(i)] = _string(t[0])
wolffd@0 3820 params["t[%s]" % str(i)] = _string(t[1])
wolffd@0 3821 params["i[%s]" % str(i)] = str(t[2])
wolffd@0 3822 params["o[%s]" % str(i)] = t[3]
wolffd@0 3823 params["r[%s]" % str(i)] = t[4]
wolffd@0 3824 params["l[%s]" % str(i)] = str(t[5])
wolffd@0 3825 params["b[%s]" % str(i)] = _string(t[6])
wolffd@0 3826 params["n[%s]" % str(i)] = t[7]
wolffd@0 3827 params["m[%s]" % str(i)] = t[8]
wolffd@0 3828
wolffd@0 3829 i += 1
wolffd@0 3830
wolffd@0 3831 _ScrobblerRequest(self.submissions_url, params, self.network).execute()
wolffd@0 3832
wolffd@0 3833 if remainder:
wolffd@0 3834 self.scrobble_many(remainder)