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)
|