=== modified file 'plugins/contextinfo/__init__.py'
--- plugins/contextinfo/__init__.py 2009-12-30 19:50:09 +0000
+++ plugins/contextinfo/__init__.py 2010-01-19 08:01:22 +0000
@@ -2,19 +2,25 @@
from xl.nls import gettext as _
from xlgui import panel, guiutil, playlist, menu
import HTMLParser
+from StringIO import StringIO
import Image
import base64
import gobject
import gtk
import os
import pylast
+import contextprefs
import re
import urllib
import webkit
import xlgui
from inspector import Inspector
-LFM_API_KEY = '3b954460f7b207e5414ffdf8c5710592'
+# Last.fm API Key for Exaile
+# if you reuse this code in a different application, please
+# register your own key with last.fm
+LFM_API_KEY = "3599c79a97fd61ce518b75922688bc38"
+
PANEL = None
CURPATH = os.path.realpath(__file__)
BASEDIR = os.path.dirname(CURPATH)+os.path.sep
@@ -32,7 +38,7 @@
selected = self.widget.get_selected_tracks()
pl = xlgui.controller().main.get_selected_playlist()
if pl:
- pl.playlist.add_tracks(selected, add_duplicates=False)
+ pl.playlist.add_tracks(selected)
def on_queue(self, selected=None):
"""
@@ -40,9 +46,9 @@
"""
selected = self.widget.get_selected_tracks()
pl = xlgui.controller().main.get_selected_playlist()
- ex.exaile().queue.add_tracks(selected, add_duplicates=False)
+ ex.exaile().queue.add_tracks(selected)
if pl:
- pl.playlist.add_tracks(selected, add_duplicates=False)
+ pl.playlist.add_tracks(selected)
pl.list.queue_draw()
class BrowserPage(webkit.WebView, providers.ProviderHandler):
@@ -291,7 +297,7 @@
selected = self.get_selected_tracks()
pl = xlgui.controller().main.get_selected_playlist()
if pl:
- pl.playlist.add_tracks(selected, add_duplicates=False)
+ pl.playlist.add_tracks(selected)
def _navigation_requested_cb(self, view, frame, networkRequest):
link = self.un_url(networkRequest.get_uri()).split('://', 1)
@@ -334,19 +340,6 @@
ex.exaile().gui.panels['collection'].filter.set_text('artist=%s' % self.hover.split('://')[1])
ex.exaile().gui.panel_notebook.set_current_page(0)
-class ImageBuffer(object):
- def __init__(self):
- self.buffer = ""
-
- def write(self, str):
- self.buffer+=str
-
- def read(self):
- return self.buffer
-
- def get_base64(self):
- return base64.b64encode(self.buffer)
-
class ContextTheme(object):
LOCAL_COLORS = None
@@ -403,12 +396,23 @@
else:
return None
+class ImageBuffer(object):
+ def __init__(self):
+ self.buffer = ""
+
+ def write(self, str):
+ self.buffer+=str
+
+ def get_base64(self):
+ return base64.b64encode(self.buffer)
+
def get_image_data(data, size):
imbuff = ImageBuffer()
- imbuff.write(data)
- im = Image.open(imbuff)
+ try:
+ im = Image.open(StringIO(data))
+ except Exception:
+ im = Image.open(xdg.get_data_path('images/nocover.png'))
im = im.resize(size, Image.ANTIALIAS)
- imbuff = ImageBuffer()
im.save(imbuff, "PNG")
return 'data:image/png;base64,%s' % imbuff.get_base64()
@@ -588,6 +592,7 @@
track_nbr = len(ex.exaile().collection.search('album=="%s"'% cd, tracks=tracks))
cover = get_track_cover(tr)
cover_data = get_image_data(cover, (60, 60))
+
html+='''
\
| \
%s %s | \
@@ -658,10 +663,14 @@
class DefaultPage(ContextPage):
def __init__(self, theme, base='default://', template='default.html', async=[]):
+ self.user = None
try:
- self.username = settings.get_option('plugin/ascrobbler/user')
+ self.username = settings.get_option('plugin/lastfm/user')
+ self.password_hash = pylast.md5(settings.get_option('plugin/lastfm/password'))
except:
self.username = None
+ self.password_hash = None
+
ContextPage.__init__(self, theme, base, template, async+['last-played-tracks', 'last-played-artists', 'last-added-tracks', 'last-added-artists', 'most-played-tracks', 'most-played-artists', 'lfm-last-played', 'lfm-top-tracks', 'lfm-top-albums', 'lfm-top-artists'])
def _last_played_tracks_title(self):
@@ -733,39 +742,44 @@
artists = get_top_artists('__playcount', int(limit))
return ', '.join(self.get_artist_anchor(artist) for artist in artists)
+ def get_lfm_user(self):
+ if not self.user:
+ self.user = pylast.User(self.username, self.password_hash, pylast.network)
+ return self.user
+
def _lfm_last_played_title(self):
return "Last Scrobbled Tracks"
def _lfm_last_played(self, limit=10):
- if self.username:
- tracks = pylast.User(self.username, LFM_API_KEY, None, None).get_recent_tracks(int(limit))
- return '
'.join(self.get_track_anchor_from_artist_title(tr.get_track().get_artist(), tr.get_track().get_title()) for tr in tracks)
+ if get_lfm_user():
+ tracks = self.get_lfm_user().get_recent_tracks(int(limit))
+ return '
'.join(self.get_track_anchor_from_artist_title(tr.track.get_artist(), tr.track.get_title()) for tr in tracks)
return "Enter your username in the settings"
def _lfm_top_tracks_title(self):
return "Your Top Tracks on Last.fm"
def _lfm_top_tracks(self, period='overall', limit=15):
- if self.username:
- tracks = pylast.User(self.username, LFM_API_KEY, None, None).get_top_tracks(period)[:int(limit)]
- return '
'.join(self.get_track_anchor_from_artist_title(tr.get_item().get_artist(), tr.get_item().get_title()) for tr in tracks)
+ if self.get_lfm_user():
+ tracks = self.get_lfm_user().get_top_tracks(period, int(limit))
+ return '
'.join(self.get_track_anchor_from_artist_title(tr.track.get_artist(), tr.track.get_title()) for tr in tracks)
return "Enter your username in the settings"
def _lfm_top_artists_title(self):
return "Your Top Artists on Last.fm"
def _lfm_top_artists(self, period='overall', limit=20):
- if self.username:
- artists = pylast.User(self.username, LFM_API_KEY, None, None).get_top_artists(period)[:int(limit)]
- return ', '.join(self.get_artist_anchor(artist.get_item().get_name()) for artist in artists)
+ if self.get_lfm_user():
+ artists = self.get_lfm_user().get_top_artists(period,int(limit))
+ return ', '.join(self.get_artist_anchor(artist.item.get_name()) for artist in artists)
return "Enter your username in the settings"
def _lfm_top_albums_title(self):
return "Your Top Albums on Last.fm"
def _lfm_top_albums(self, period='overall', limit=10):
- if self.username:
- cds = [album.get_item().get_title() for album in pylast.User(self.username, LFM_API_KEY, None, None).get_top_albums(period)[:int(limit)]]
+ if self.get_lfm_user():
+ cds = [album.get_item().get_title() for album in self.get_lfm_user().get_top_albums(period,int(limit))]
tracks = []
if len(cds)>0:
for cd in cds:
@@ -777,9 +791,15 @@
class ArtistPage(DefaultPage):
def __init__(self, theme, artist, base = 'artist://', template ='artist.html', async=[]):
self.artist = artist
- self.artist_tracks = get_artist_tracks(artist)
+ self.lfm_artist = None
+
DefaultPage.__init__(self, theme, base, template, async+['compils', 'albums', 'artist-info', 'artist-img', 'artist-tags', 'similar-artists', 'top-tracks'])
+ def get_lfm_artist(self):
+ if not self.lfm_artist:
+ self.lfm_artist = pylast.Artist(self.artist, pylast.network)
+ return self.lfm_artist
+
def get_template_fields(self):
fields = ContextPage.get_template_fields(self)
for field in fields:
@@ -810,30 +830,36 @@
def _artist(self):
return self.artist
- def _artist_img(self, size=pylast.IMAGE_LARGE):
+ def _artist_img(self, size=pylast.COVER_LARGE):
try:
- url = pylast.search_for_artist(self.artist, LFM_API_KEY, None, None).get_next_page()[0].get_image_url(size)
+ url = self.get_lfm_artist().get_cover_image(size)
return '' % url
except:
return ''
def _artist_info(self):
- bio = pylast.search_for_artist(self.artist, LFM_API_KEY, None, None).get_next_page()[0].get_bio_summary()
- return self.LFMInfoParser(self, str(bio), self['artist']).data
+ if self.get_lfm_artist():
+ bio = self.get_lfm_artist().get_bio_summary()
+ return self.LFMInfoParser(self, str(bio), self['artist']).data
+ return ''
def _top_tracks_title(self):
return 'Top tracks by %s' % self['artist']
def _top_tracks(self, limit=10):
- doc = pylast.search_for_artist(self['artist'],LFM_API_KEY, None, None).get_next_page()[0].get_top_tracks()[:int(limit)]
- return '
'.join(self.get_track_anchor_from_artist_title(self['artist'], tr.get_item().get_title(), img=True) for tr in doc)
+ if self.get_lfm_artist():
+ doc = self.get_lfm_artist().get_top_tracks(int(limit))
+ return '
'.join(self.get_track_anchor_from_artist_title(self['artist'], tr.get_item().get_title(), img=True) for tr in doc)
+ return ''
def _artist_tags_title(self):
return "Tags for %s" % self['artist']
def _artist_tags(self, limit=10):
- doc = pylast.search_for_artist(self['artist'],LFM_API_KEY, None, None).get_next_page()[0].get_top_tags()[:int(limit)]
- return ', '.join(self.get_tag_anchor(tag) for tag in doc)
+ if self.get_lfm_artist():
+ doc = self.get_lfm_artist().get_top_tags(int(limit))
+ return ', '.join(self.get_tag_anchor(tag.item) for tag in doc)
+ return ''
def _compils_title(self):
return "Compilations with %s"%self['artist']
@@ -847,17 +873,11 @@
def _similar_artists_title(self):
return "Artists related to %s" % self['artist']
- def _similar_artists_images(self):
- doc = pylast.search_for_artist(self['artist'], LFM_API_KEY, None, None)
- temp = doc.get_next_page()[0].get_similar(10)
- data = []
- for art in temp:
- data.append('' % (art, pylast.Artist.get_square_image(art)))
- return ''.join(data)
-
def _similar_artists(self, limit=10):
- sim_artists = pylast.search_for_artist(self['artist'], LFM_API_KEY, None, None).get_next_page()[0].get_similar(int(limit))
- return ', '.join(self.get_artist_anchor(sim_artist) for sim_artist in sim_artists)
+ if self.get_lfm_artist():
+ sim_artists = self.get_lfm_artist().get_similar(int(limit))
+ return ', '.join(self.get_artist_anchor(sim_artist.item) for sim_artist in sim_artists)
+ return ''
def _albums_title(self):
return "Albums by %s" % self['artist']
@@ -913,7 +933,14 @@
def __init__(self, theme, tag, base='tag://', template='tag.html', async=[]):
self.tag = tag
+ self.lfm_tag = None
+
DefaultPage.__init__(self, theme, base, template, async+['similar-tags', 'top-artists', 'tag-top-tracks', 'tag-top-albums'])
+
+ def get_lfm_tag(self):
+ if not self.lfm_tag:
+ self.lfm_tag = pylast.Tag(self.tag, pylast.network)
+ return self.lfm_tag
def _tag(self):
return self.tag
@@ -922,37 +949,51 @@
return "Tags similar to %s" % self['tag']
def _similar_tags(self):
- tags = pylast.search_for_tag(self.tag, LFM_API_KEY, None, None).get_next_page()[0].get_similar()
- return ', '.join(self.get_tag_anchor(tag) for tag in tags[:20])
+ if self.get_lfm_tag():
+ tags = self.get_lfm_tag().get_similar()
+ return ', '.join(self.get_tag_anchor(tag) for tag in tags[:20])
+ return ''
def _top_artists_title(self):
return "Top %s artists" % self.tag
def _top_artists(self):
- artists = pylast.search_for_tag(self.tag, LFM_API_KEY, None, None).get_next_page()[0].get_top_artists()
- return ', '.join(self.get_artist_anchor(artist.get_item().get_name()) for artist in artists[:20])
+ if self.get_lfm_tag():
+ artists = self.get_lfm_tag().get_top_artists()
+ return ', '.join(self.get_artist_anchor(artist.item) for artist in artists[:20])
+ return ''
def _tag_top_tracks_title(self):
return "Top %s tracks" % self.tag
def _tag_top_tracks(self):
- tracks = pylast.search_for_tag(self.tag, LFM_API_KEY, None, None).get_next_page()[0].get_top_tracks()
- return '
'.join(self.get_track_anchor_from_artist_title(track.get_item().get_artist(), track.get_item().get_title()) for track in tracks[:15])
+ if self.get_lfm_tag():
+ tracks = self.get_lfm_tag().get_top_tracks()
+ return '
'.join(self.get_track_anchor_from_artist_title(track.item.get_artist(), track.item.get_title()) for track in tracks[:15])
+ return ''
def _tag_top_albums_title(self):
return "Top %s albums" % self.tag
def _tag_top_albums(self):
- albums = pylast.search_for_tag(self.tag, LFM_API_KEY, None, None).get_next_page()[0].get_top_albums()
- return '
'.join("%s %s"%(self['album-ico'],self.get_album_anchor_from_artist_title(album.get_item().get_artist(), album.get_item().get_title())) for album in albums[:15])
+ if self.get_lfm_tag():
+ albums = self.get_lfm_tag().get_top_albums()
+ return '
'.join("%s %s"%(self['album-ico'],self.get_album_anchor_from_artist_title(album.item.get_artist(), album.item.get_name())) for album in albums[:15])
+ return ''
class PlayingPage(ArtistPage):
def __init__(self, theme, track, base='playing://', template='playing.html', async=[]):
self.track = track
+ self.lfm_track = None
ArtistPage.__init__(self, theme, get_track_tag(self.track, 'artist', 'unknown'),base, template, async+['track-tags', 'suggested-tracks', 'similar-tracks', 'lyrics'])
event.add_callback(self.refresh_rating, 'rating_changed')
+ def get_lfm_track(self):
+ if not self.lfm_track:
+ self.lfm_track = pylast.Track(get_track_tag(self.track, 'artist', 'unknown'), get_track_tag(self.track, 'title', 'unknown'), pylast.network)
+ return self.lfm_track
+
def link_clicked(self, link):
if link[0] == 'rate':
self.track.set_rating(int(link[1]))
@@ -969,9 +1010,9 @@
return get_track_tag(self.track, 'album', 'unknown')
def _track_cover(self):
- coverpath = get_track_cover(self.track)
- cover = get_image_data(coverpath, (100, 100))
- return "" % (self.get_album_href(self['album']), cover)
+ cover = get_track_cover(self.track)
+ cover_data = get_image_data(cover, (100, 100))
+ return "" % (self.get_album_href(self['album']), cover_data)
def _playcount(self):
try:
@@ -1008,9 +1049,11 @@
def _track_tags_title(self):
return "Tags for %s" % self['title']
- def _track_tags(self):
- tags = pylast.search_for_track(self['artist'], self['title'], LFM_API_KEY, None, None).get_next_page()[0].get_top_tags()
- return ', '.join(self.get_tag_anchor(tag) for tag in tags)
+ def _track_tags(self, limit=10):
+ if self.get_lfm_track():
+ tags = self.get_lfm_track().get_top_tags(limit)
+ return ', '.join(self.get_tag_anchor(tag.item) for tag in tags)
+ return ''
def _suggested_tracks_title(self):
return "Suggested tracks for %s" % self['title']
@@ -1023,8 +1066,10 @@
return 'Tracks similar to %s' % self['title']
def _similar_tracks(self, limit=10):
- doc = pylast.search_for_track(self['artist'], self['title'],LFM_API_KEY, None, None).get_next_page()[0].get_similar()[:int(limit)]
- return '
'.join(self.get_track_anchor_from_artist_title(str(tr.get_artist()), str(tr.get_title()), img=True) for tr in doc)
+ if self.get_lfm_track():
+ doc = self.get_lfm_track().get_similar(int(limit))
+ return '
'.join(self.get_track_anchor_from_artist_title(str(tr.get_artist()), str(tr.get_title()), img=True) for tr in doc)
+ return ''
def _lyrics_title(self):
return "Lyrics of %s by %s" % (self['title'],self['artist'])
@@ -1056,7 +1101,8 @@
os.mkdir(cachedir)
#TODO last.fm class
- pylast.enable_caching(os.path.join(cachedir, 'lastfm.cache'))
+ pylast.network = pylast.get_lastfm_network(LFM_API_KEY)
+ pylast.network.enable_caching(os.path.join(cachedir, 'lastfm.cache'))
self.controller = parent
@@ -1099,6 +1145,9 @@
window.destroy()
return (self._child, self.name)
+
+def get_prefs_pane():
+ return contextprefs
def exaile_ready(object=None, a=None, b=None):
global PANEL
=== added file 'plugins/contextinfo/context_pane.ui'
--- plugins/contextinfo/context_pane.ui 1970-01-01 00:00:00 +0000
+++ plugins/contextinfo/context_pane.ui 2010-01-14 16:33:34 +0000
@@ -0,0 +1,117 @@
+
+
+
+
+
+
=== modified file 'plugins/contextinfo/pylast.py'
--- plugins/contextinfo/pylast.py 2009-10-27 02:30:20 +0000
+++ plugins/contextinfo/pylast.py 2010-01-18 21:44:24 +0000
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# pylast - A Python interface to Last.fm
+# pylast - A Python interface to Last.fm (and other API compatible social networks)
# Copyright (C) 2008-2009 Amr Hassan
#
# This program is free software; you can redistribute it and/or modify
@@ -19,40 +19,29 @@
# USA
#
# http://code.google.com/p/pylast/
-
-__name__ = 'pylast'
-__version__ = '0.3'
-__revision__ = "$Revision$"
-__doc__ = 'A Python interface to Last.fm'
+
+__version__ = '0.4'
__author__ = 'Amr Hassan'
__copyright__ = "Copyright (C) 2008-2009 Amr Hassan"
__license__ = "gpl"
__email__ = 'amr.hassan@gmail.com'
-# Parse revision and add it to __version__
-r = __revision__
-__version__ = __version__ + "." + r[r.find(" ")+1:r.rfind("$")-1]
-
-# Default values for Last.fm.
-WS_SERVER = ('ws.audioscrobbler.com', '/2.0/')
-SUBMISSION_SERVER = "http://post.audioscrobbler.com:80/"
-
-__proxy = None
-__proxy_enabled = False
-__cache_shelf = None
-__cache_filename = None
-__last_call_time = 0
-
import hashlib
import httplib
import urllib
import threading
from xml.dom import minidom
-import os
+import xml.dom
import time
-from logging import info, warn, debug
import shelve
import tempfile
+import sys
+import htmlentitydefs
+
+try:
+ import collections
+except ImportError:
+ pass
STATUS_INVALID_SERVICE = 2
STATUS_INVALID_METHOD = 3
@@ -78,23 +67,28 @@
PERIOD_6MONTHS = '6month'
PERIOD_12MONTHS = '12month'
-IMAGE_SMALL = 0
-IMAGE_MEDIUM = 1
-IMAGE_LARGE = 2
-IMAGE_EXTRA_LARGE = 3
-
-DOMAIN_ENGLISH = 'www.last.fm'
-DOMAIN_GERMAN = 'www.lastfm.de'
-DOMAIN_SPANISH = 'www.lastfm.es'
-DOMAIN_FRENCH = 'www.lastfm.fr'
-DOMAIN_ITALIAN = 'www.lastfm.it'
-DOMAIN_POLISH = 'www.lastfm.pl'
-DOMAIN_PORTUGUESE = 'www.lastfm.com.br'
-DOMAIN_SWEDISH = 'www.lastfm.se'
-DOMAIN_TURKISH = 'www.lastfm.com.tr'
-DOMAIN_RUSSIAN = 'www.lastfm.ru'
-DOMAIN_JAPANESE = 'www.lastfm.jp'
-DOMAIN_CHINESE = 'cn.last.fm'
+DOMAIN_ENGLISH = 0
+DOMAIN_GERMAN = 1
+DOMAIN_SPANISH = 2
+DOMAIN_FRENCH = 3
+DOMAIN_ITALIAN = 4
+DOMAIN_POLISH = 5
+DOMAIN_PORTUGUESE = 6
+DOMAIN_SWEDISH = 7
+DOMAIN_TURKISH = 8
+DOMAIN_RUSSIAN = 9
+DOMAIN_JAPANESE = 10
+DOMAIN_CHINESE = 11
+
+COVER_SMALL = 0
+COVER_MEDIUM = 1
+COVER_LARGE = 2
+COVER_EXTRA_LARGE = 3
+COVER_MEGA = 4
+
+IMAGES_ORDER_POPULARITY = "popularity"
+IMAGES_ORDER_DATE = "dateadded"
+
USER_MALE = 'Male'
USER_FEMALE = 'Female'
@@ -105,3151 +99,3524 @@
SCROBBLE_SOURCE_LASTFM = "L"
SCROBBLE_SOURCE_UNKNOWN = "U"
-SCROBBLE_MODE_PLAYED = "L"
+SCROBBLE_MODE_PLAYED = ""
+SCROBBLE_MODE_LOVED = "L"
SCROBBLE_MODE_BANNED = "B"
SCROBBLE_MODE_SKIPPED = "S"
+"""
+A list of the implemented webservices (from http://www.last.fm/api/intro)
+=====================================
+# Album
+
+ * album.addTags DONE
+ * album.getInfo DONE
+ * album.getTags DONE
+ * album.removeTag DONE
+ * album.search DONE
+
+# Artist
+
+ * artist.addTags DONE
+ * artist.getEvents DONE
+ * artist.getImages DONE
+ * artist.getInfo DONE
+ * artist.getPodcast TODO
+ * artist.getShouts DONE
+ * artist.getSimilar DONE
+ * artist.getTags DONE
+ * artist.getTopAlbums DONE
+ * artist.getTopFans DONE
+ * artist.getTopTags DONE
+ * artist.getTopTracks DONE
+ * artist.removeTag DONE
+ * artist.search DONE
+ * artist.share DONE
+ * artist.shout DONE
+
+# Auth
+
+ * auth.getMobileSession DONE
+ * auth.getSession DONE
+ * auth.getToken DONE
+
+# Event
+
+ * event.attend DONE
+ * event.getAttendees DONE
+ * event.getInfo DONE
+ * event.getShouts DONE
+ * event.share DONE
+ * event.shout DONE
+
+# Geo
+
+ * geo.getEvents
+ * geo.getTopArtists
+ * geo.getTopTracks
+
+# Group
+
+ * group.getMembers DONE
+ * group.getWeeklyAlbumChart DONE
+ * group.getWeeklyArtistChart DONE
+ * group.getWeeklyChartList DONE
+ * group.getWeeklyTrackChart DONE
+
+# Library
+
+ * library.addAlbum DONE
+ * library.addArtist DONE
+ * library.addTrack DONE
+ * library.getAlbums DONE
+ * library.getArtists DONE
+ * library.getTracks DONE
+
+# Playlist
+
+ * playlist.addTrack DONE
+ * playlist.create DONE
+ * playlist.fetch DONE
+
+# Radio
+
+ * radio.getPlaylist
+ * radio.tune
+
+# Tag
+
+ * tag.getSimilar DONE
+ * tag.getTopAlbums DONE
+ * tag.getTopArtists DONE
+ * tag.getTopTags DONE
+ * tag.getTopTracks DONE
+ * tag.getWeeklyArtistChart DONE
+ * tag.getWeeklyChartList DONE
+ * tag.search DONE
+
+# Tasteometer
+
+ * tasteometer.compare DONE
+
+# Track
+
+ * track.addTags DONE
+ * track.ban DONE
+ * track.getInfo DONE
+ * track.getSimilar DONE
+ * track.getTags DONE
+ * track.getTopFans DONE
+ * track.getTopTags DONE
+ * track.love DONE
+ * track.removeTag DONE
+ * track.search DONE
+ * track.share DONE
+
+# User
+
+ * user.getEvents DONE
+ * user.getFriends DONE
+ * user.getInfo DONE
+ * user.getLovedTracks DONE
+ * user.getNeighbours DONE
+ * user.getPastEvents DONE
+ * user.getPlaylists DONE
+ * user.getRecentStations TODO
+ * user.getRecentTracks DONE
+ * user.getRecommendedArtists DONE
+ * user.getRecommendedEvents DONE
+ * user.getShouts DONE
+ * user.getTopAlbums DONE
+ * user.getTopArtists DONE
+ * user.getTopTags DONE
+ * user.getTopTracks DONE
+ * user.getWeeklyAlbumChart DONE
+ * user.getWeeklyArtistChart DONE
+ * user.getWeeklyChartList DONE
+ * user.getWeeklyTrackChart DONE
+ * user.shout DONE
+
+# Venue
+
+ * venue.getEvents DONE
+ * venue.getPastEvents DONE
+ * venue.search DONE
+"""
+
+class Network(object):
+ """
+ A music social network website that is Last.fm or one exposing a Last.fm compatible API
+ """
+
+ def __init__(self, name, homepage, ws_server, api_key, api_secret, session_key, submission_server, username, password_hash,
+ domain_names, urls):
+ """
+ name: the name of the network
+ homepage: the homepage url
+ ws_server: the url of the webservices server
+ api_key: a provided API_KEY
+ api_secret: a provided API_SECRET
+ session_key: a generated session_key or None
+ submission_server: the url of the server to which tracks are submitted (scrobbled)
+ username: a username of a valid user
+ password_hash: the output of pylast.md5(password) where password is the user's password thingy
+ domain_names: a dict mapping each DOMAIN_* value to a string domain name
+ urls: a dict mapping types to urls
+
+ if username and password_hash were provided and not session_key, session_key will be
+ generated automatically when needed.
+
+ Either a valid session_key or a combination of username and password_hash must be present for scrobbling.
+
+ You should use a preconfigured network object through a get_*_network(...) method instead of creating an object
+ of this class, unless you know what you're doing.
+ """
+
+ self.ws_server = ws_server
+ self.submission_server = submission_server
+ self.name = name
+ self.homepage = homepage
+ self.api_key = api_key
+ self.api_secret = api_secret
+ self.session_key = session_key
+ self.username = username
+ self.password_hash = password_hash
+ self.domain_names = domain_names
+ self.urls = urls
+
+ self.cache_backend = None
+ self.proxy_enabled = False
+ self.proxy = None
+ self.last_call_time = 0
+
+ #generate a session_key if necessary
+ if (self.api_key and self.api_secret) and not self.session_key and (self.username and self.password_hash):
+ sk_gen = SessionKeyGenerator(self)
+ self.session_key = sk_gen.get_session_key(self.username, self.password_hash)
+
+ def get_artist(self, artist_name):
+ """
+ Return an Artist object
+ """
+
+ return Artist(artist_name, self)
+
+ def get_track(self, artist, title):
+ """
+ Return a Track object
+ """
+
+ return Track(artist, title, self)
+
+ def get_album(self, artist, title):
+ """
+ Return an Album object
+ """
+
+ return Album(artist, title, self)
+
+ def get_authenticated_user(self):
+ """
+ Returns the authenticated user
+ """
+
+ return AuthenticatedUser(self)
+
+ def get_country(self, country_name):
+ """
+ Returns a country object
+ """
+
+ return Country(country_name, self)
+
+ def get_group(self, name):
+ """
+ Returns a Group object
+ """
+
+ return Group(name, self)
+
+ def get_user(self, username):
+ """
+ Returns a user object
+ """
+
+ return User(username, self)
+
+ def get_tag(self, name):
+ """
+ Returns a tag object
+ """
+
+ return Tag(name, self)
+
+ def get_scrobbler(self, client_id, client_version):
+ """
+ Returns a Scrobbler object used for submitting tracks to the server
+
+ Quote from http://www.last.fm/api/submissions:
+ ========
+ Client identifiers are used to provide a centrally managed database of
+ the client versions, allowing clients to be banned if they are found to
+ be behaving undesirably. The client ID is associated with a version
+ number on the server, however these are only incremented if a client is
+ banned and do not have to reflect the version of the actual client application.
+
+ During development, clients which have not been allocated an identifier should
+ use the identifier tst, with a version number of 1.0. Do not distribute code or
+ client implementations which use this test identifier. Do not use the identifiers
+ used by other clients.
+ =========
+
+ To obtain a new client identifier please contact:
+ * Last.fm: submissions@last.fm
+ * # TODO: list others
+
+ ...and provide us with the name of your client and its homepage address.
+ """
+
+ return Scrobbler(self, client_id, client_version)
+
+ def _get_language_domain(self, domain_language):
+ """
+ Returns the mapped domain name of the network to a DOMAIN_* value
+ """
+
+ if domain_language in self.domain_names:
+ return self.domain_names[domain_language]
+
+ def _get_url(self, domain, type):
+ return "http://%s/%s" %(self._get_language_domain(domain), self.urls[type])
+
+ def _get_ws_auth(self):
+ """
+ Returns a (API_KEY, API_SECRET, SESSION_KEY) tuple.
+ """
+ return (self.api_key, self.api_secret, self.session_key)
+
+ def _delay_call(self):
+ """
+ Makes sure that web service calls are at least a second apart
+ """
+
+ # delay time in seconds
+ DELAY_TIME = 1.0
+ now = time.time()
+
+ if (now - self.last_call_time) < DELAY_TIME:
+ time.sleep(1)
+
+ self.last_call_time = now
+
+ def create_new_playlist(self, title, description):
+ """
+ Creates a playlist for the authenticated user and returns it
+ title: The title of the new playlist.
+ description: The description of the new playlist.
+ """
+
+ params = {}
+ params['title'] = _unicode(title)
+ params['description'] = _unicode(description)
+
+ doc = _Request(self, 'playlist.create', params).execute(False)
+
+ e_id = doc.getElementsByTagName("id")[0].firstChild.data
+ user = doc.getElementsByTagName('playlists')[0].getAttribute('user')
+
+ return Playlist(user, e_id, self)
+
+ def get_top_tags(self, limit=None):
+ """Returns a sequence of the most used tags as a sequence of TopItem objects."""
+
+ doc = _Request(self, "tag.getTopTags").execute(True)
+ seq = []
+ for node in doc.getElementsByTagName("tag"):
+ tag = Tag(_extract(node, "name"), self)
+ weight = _number(_extract(node, "count"))
+
+ if len(seq) < limit:
+ seq.append(TopItem(tag, weight))
+
+ return seq
+
+ def enable_proxy(self, host, port):
+ """Enable a default web proxy"""
+
+ self.proxy = [host, _number(port)]
+ self.proxy_enabled = True
+
+ def disable_proxy(self):
+ """Disable using the web proxy"""
+
+ self.proxy_enabled = False
+
+ def is_proxy_enabled(self):
+ """Returns True if a web proxy is enabled."""
+
+ return self.proxy_enabled
+
+ def _get_proxy(self):
+ """Returns proxy details."""
+
+ return self.proxy
+
+ def enable_caching(self, file_path = None):
+ """Enables caching request-wide for all cachable calls.
+ In choosing the backend used for caching, it will try _SqliteCacheBackend first if
+ the module sqlite3 is present. If not, it will fallback to _ShelfCacheBackend which uses shelve.Shelf objects.
+
+ * file_path: A file path for the backend storage file. If
+ None set, a temp file would probably be created, according the backend.
+ """
+
+ if not file_path:
+ file_path = tempfile.mktemp(prefix="pylast_tmp_")
+
+ self.cache_backend = _ShelfCacheBackend(file_path)
+
+ def disable_caching(self):
+ """Disables all caching features."""
+
+ self.cache_backend = None
+
+ def is_caching_enabled(self):
+ """Returns True if caching is enabled."""
+
+ return not (self.cache_backend == None)
+
+ def _get_cache_backend(self):
+
+ return self.cache_backend
+
+ def search_for_album(self, album_name):
+ """Searches for an album by its name. Returns a AlbumSearch object.
+ Use get_next_page() to retreive sequences of results."""
+
+ return AlbumSearch(album_name, self)
+
+ def search_for_artist(self, artist_name):
+ """Searches of an artist by its name. Returns a ArtistSearch object.
+ Use get_next_page() to retreive sequences of results."""
+
+ return ArtistSearch(artist_name, self)
+
+ def search_for_tag(self, tag_name):
+ """Searches of a tag by its name. Returns a TagSearch object.
+ Use get_next_page() to retreive sequences of results."""
+
+ return TagSearch(tag_name, self)
+
+ def search_for_track(self, artist_name, track_name):
+ """Searches of a track by its name and its artist. Set artist to an empty string if not available.
+ Returns a TrackSearch object.
+ Use get_next_page() to retreive sequences of results."""
+
+ return TrackSearch(artist_name, track_name, self)
+
+ def search_for_venue(self, venue_name, country_name):
+ """Searches of a venue by its name and its country. Set country_name to an empty string if not available.
+ Returns a VenueSearch object.
+ Use get_next_page() to retreive sequences of results."""
+
+ return VenueSearch(venue_name, country_name, self)
+
+ def get_track_by_mbid(self, mbid):
+ """Looks up a track by its MusicBrainz ID"""
+
+ params = {"mbid": _unicode(mbid)}
+
+ doc = _Request(self, "track.getInfo", params).execute(True)
+
+ return Track(_extract(doc, "name", 1), _extract(doc, "name"), self)
+
+ def get_artist_by_mbid(self, mbid):
+ """Loooks up an artist by its MusicBrainz ID"""
+
+ params = {"mbid": _unicode(mbid)}
+
+ doc = _Request(self, "artist.getInfo", params).execute(True)
+
+ return Artist(_extract(doc, "name"), self)
+
+ def get_album_by_mbid(self, mbid):
+ """Looks up an album by its MusicBrainz ID"""
+
+ params = {"mbid": _unicode(mbid)}
+
+ doc = _Request(self, "album.getInfo", params).execute(True)
+
+ return Album(_extract(doc, "artist"), _extract(doc, "name"), self)
+
+def get_lastfm_network(api_key="", api_secret="", session_key = "", username = "", password_hash = ""):
+ """
+ Returns a preconfigured Network object for Last.fm
+
+ api_key: a provided API_KEY
+ api_secret: a provided API_SECRET
+ session_key: a generated session_key or None
+ username: a username of a valid user
+ password_hash: the output of pylast.md5(password) where password is the user's password
+
+ if username and password_hash were provided and not session_key, session_key will be
+ generated automatically when needed.
+
+ Either a valid session_key or a combination of username and password_hash must be present for scrobbling.
+
+ Most read-only webservices only require an api_key and an api_secret, see about obtaining them from:
+ http://www.last.fm/api/account
+ """
+
+ return Network (
+ name = "Last.fm",
+ homepage = "http://last.fm",
+ ws_server = ("ws.audioscrobbler.com", "/2.0/"),
+ api_key = api_key,
+ api_secret = api_secret,
+ session_key = session_key,
+ submission_server = "http://post.audioscrobbler.com:80/",
+ username = username,
+ password_hash = password_hash,
+ domain_names = {
+ DOMAIN_ENGLISH: 'www.last.fm',
+ DOMAIN_GERMAN: 'www.lastfm.de',
+ DOMAIN_SPANISH: 'www.lastfm.es',
+ DOMAIN_FRENCH: 'www.lastfm.fr',
+ DOMAIN_ITALIAN: 'www.lastfm.it',
+ DOMAIN_POLISH: 'www.lastfm.pl',
+ DOMAIN_PORTUGUESE: 'www.lastfm.com.br',
+ DOMAIN_SWEDISH: 'www.lastfm.se',
+ DOMAIN_TURKISH: 'www.lastfm.com.tr',
+ DOMAIN_RUSSIAN: 'www.lastfm.ru',
+ DOMAIN_JAPANESE: 'www.lastfm.jp',
+ DOMAIN_CHINESE: 'cn.last.fm',
+ },
+ urls = {
+ "album": "music/%(artist)s/%(album)s",
+ "artist": "music/%(artist)s",
+ "event": "event/%(id)s",
+ "country": "place/%(country_name)s",
+ "playlist": "user/%(user)s/library/playlists/%(appendix)s",
+ "tag": "tag/%(name)s",
+ "track": "music/%(artist)s/_/%(title)s",
+ "group": "group/%(name)s",
+ "user": "user/%(name)s",
+ }
+ )
+
+def get_librefm_network(api_key="", api_secret="", session_key = "", username = "", password_hash = ""):
+ """
+ Returns a preconfigured Network object for Libre.fm
+
+ api_key: a provided API_KEY
+ api_secret: a provided API_SECRET
+ session_key: a generated session_key or None
+ username: a username of a valid user
+ password_hash: the output of pylast.md5(password) where password is the user's password
+
+ if username and password_hash were provided and not session_key, session_key will be
+ generated automatically when needed.
+ """
+
+ return Network (
+ name = "Libre.fm",
+ homepage = "http://alpha.dev.libre.fm",
+ ws_server = ("alpha.dev.libre.fm", "/2.0/"),
+ api_key = api_key,
+ api_secret = api_secret,
+ session_key = session_key,
+ submission_server = "http://turtle.libre.fm:80/",
+ username = username,
+ password_hash = password_hash,
+ domain_names = {
+ DOMAIN_ENGLISH: "alpha.dev.libre.fm",
+ DOMAIN_GERMAN: "alpha.dev.libre.fm",
+ DOMAIN_SPANISH: "alpha.dev.libre.fm",
+ DOMAIN_FRENCH: "alpha.dev.libre.fm",
+ DOMAIN_ITALIAN: "alpha.dev.libre.fm",
+ DOMAIN_POLISH: "alpha.dev.libre.fm",
+ DOMAIN_PORTUGUESE: "alpha.dev.libre.fm",
+ DOMAIN_SWEDISH: "alpha.dev.libre.fm",
+ DOMAIN_TURKISH: "alpha.dev.libre.fm",
+ DOMAIN_RUSSIAN: "alpha.dev.libre.fm",
+ DOMAIN_JAPANESE: "alpha.dev.libre.fm",
+ DOMAIN_CHINESE: "alpha.dev.libre.fm",
+ },
+ urls = {
+ "album": "artist/%(artist)s/album/%(album)s",
+ "artist": "artist/%(artist)s",
+ "event": "event/%(id)s",
+ "country": "place/%(country_name)s",
+ "playlist": "user/%(user)s/library/playlists/%(appendix)s",
+ "tag": "tag/%(name)s",
+ "track": "music/%(artist)s/_/%(title)s",
+ "group": "group/%(name)s",
+ "user": "user/%(name)s",
+ }
+ )
+
+class _ShelfCacheBackend(object):
+ """Used as a backend for caching cacheable requests."""
+ def __init__(self, file_path = None):
+ self.shelf = shelve.open(file_path)
+
+ def get_xml(self, key):
+ return self.shelf[key]
+
+ def set_xml(self, key, xml_string):
+ self.shelf[key] = xml_string
+
+ def has_key(self, key):
+ return key in self.shelf.keys()
+
class _ThreadedCall(threading.Thread):
- """Facilitates calling a function on another thread."""
-
- def __init__(self, sender, funct, funct_args, callback, callback_args):
-
- threading.Thread.__init__(self)
-
- self.funct = funct
- self.funct_args = funct_args
- self.callback = callback
- self.callback_args = callback_args
-
- self.sender = sender
-
- def run(self):
-
- output = []
-
- if self.funct:
- if self.funct_args:
- output = self.funct(*self.funct_args)
- else:
- output = self.funct()
-
- if self.callback:
- if self.callback_args:
- self.callback(self.sender, output, *self.callback_args)
- else:
- self.callback(self.sender, output)
-
+ """Facilitates calling a function on another thread."""
+
+ def __init__(self, sender, funct, funct_args, callback, callback_args):
+
+ threading.Thread.__init__(self)
+
+ self.funct = funct
+ self.funct_args = funct_args
+ self.callback = callback
+ self.callback_args = callback_args
+
+ self.sender = sender
+
+ def run(self):
+
+ output = []
+
+ if self.funct:
+ if self.funct_args:
+ output = self.funct(*self.funct_args)
+ else:
+ output = self.funct()
+
+ if self.callback:
+ if self.callback_args:
+ self.callback(self.sender, output, *self.callback_args)
+ else:
+ self.callback(self.sender, output)
+
class _Request(object):
- """Representing an abstract web service operation."""
-
- global WS_SERVER
- (HOST_NAME, HOST_SUBDIR) = WS_SERVER
-
- def __init__(self, method_name, params, api_key, api_secret, session_key = None):
-
- self.params = params
- self.api_secret = api_secret
-
- self.params["api_key"] = api_key
- self.params["method"] = method_name
-
- if is_caching_enabled():
- self.shelf = get_cache_shelf()
-
- if session_key:
- self.params["sk"] = session_key
- self.sign_it()
-
- def sign_it(self):
- """Sign this request."""
-
- if not "api_sig" in self.params.keys():
- self.params['api_sig'] = self._get_signature()
-
- def _get_signature(self):
- """Returns a 32-character hexadecimal md5 hash of the signature string."""
-
- keys = self.params.keys()[:]
-
- keys.sort()
-
- string = ""
-
- for name in keys:
- string += name
- string += self.params[name]
-
- string += self.api_secret
-
- return md5(string)
-
- def _get_cache_key(self):
- """The cache key is a string of concatenated sorted names and values."""
-
- keys = self.params.keys()
- keys.sort()
-
- cache_key = str()
-
- for key in keys:
- if key != "api_sig" and key != "api_key" and key != "sk":
- cache_key += key + self.params[key].encode("utf-8")
-
- return hashlib.sha1(cache_key).hexdigest()
-
- def _get_cached_response(self):
- """Returns a file object of the cached response."""
-
- if not self._is_cached():
- response = self._download_response()
- self.shelf[self._get_cache_key()] = response
-
- return self.shelf[self._get_cache_key()]
-
- def _is_cached(self):
- """Returns True if the request is already in cache."""
-
- return self.shelf.has_key(self._get_cache_key())
-
- def _download_response(self):
- """Returns a response body string from the server."""
-
- # Delay the call if necessary
- _delay_call()
-
- data = []
- for name in self.params.keys():
- data.append('='.join((name, urllib.quote_plus(self.params[name].encode("utf-8")))))
- data = '&'.join(data)
-
- headers = {
- "Content-type": "application/x-www-form-urlencoded",
- 'Accept-Charset': 'utf-8',
- 'User-Agent': __name__ + '/' + __version__
- }
-
- if is_proxy_enabled():
- conn = httplib.HTTPConnection(host = _get_proxy()[0], port = _get_proxy()[1])
- conn.request(method='POST', url="http://" + self.HOST_NAME + self.HOST_SUBDIR,
- body=data, headers=headers)
- else:
- conn = httplib.HTTPConnection(host=self.HOST_NAME)
- conn.request(method='POST', url=self.HOST_SUBDIR, body=data, headers=headers)
-
- response = conn.getresponse().read()
- self._check_response_for_errors(response)
- return response
-
- def execute(self, cacheable = False):
- """Returns the XML DOM response of the POST Request from the server"""
-
- if is_caching_enabled() and cacheable:
- response = self._get_cached_response()
- else:
- response = self._download_response()
-
- return minidom.parseString(response)
-
- def _check_response_for_errors(self, response):
- """Checks the response for errors and raises one if any exists."""
-
- doc = minidom.parseString(response)
- e = doc.getElementsByTagName('lfm')[0]
-
- if e.getAttribute('status') != "ok":
- e = doc.getElementsByTagName('error')[0]
- status = e.getAttribute('code')
- details = e.firstChild.data.strip()
- raise ServiceException(status, details)
+ """Representing an abstract web service operation."""
+
+ def __init__(self, network, method_name, params = {}):
+
+ self.params = params
+ self.network = network
+
+ (self.api_key, self.api_secret, self.session_key) = network._get_ws_auth()
+
+ self.params["api_key"] = self.api_key
+ self.params["method"] = method_name
+
+ if network.is_caching_enabled():
+ self.cache = network._get_cache_backend()
+
+ if self.session_key:
+ self.params["sk"] = self.session_key
+ self.sign_it()
+
+ def sign_it(self):
+ """Sign this request."""
+
+ if not "api_sig" in self.params.keys():
+ self.params['api_sig'] = self._get_signature()
+
+ def _get_signature(self):
+ """Returns a 32-character hexadecimal md5 hash of the signature string."""
+
+ keys = self.params.keys()[:]
+
+ keys.sort()
+
+ string = ""
+
+ for name in keys:
+ string += name
+ string += self.params[name]
+
+ string += self.api_secret
+
+ return md5(string)
+
+ def _get_cache_key(self):
+ """The cache key is a string of concatenated sorted names and values."""
+
+ keys = self.params.keys()
+ keys.sort()
+
+ cache_key = str()
+
+ for key in keys:
+ if key != "api_sig" and key != "api_key" and key != "sk":
+ cache_key += key + _string(self.params[key])
+
+ return hashlib.sha1(cache_key).hexdigest()
+
+ def _get_cached_response(self):
+ """Returns a file object of the cached response."""
+
+ if not self._is_cached():
+ response = self._download_response()
+ self.cache.set_xml(self._get_cache_key(), response)
+
+ return self.cache.get_xml(self._get_cache_key())
+
+ def _is_cached(self):
+ """Returns True if the request is already in cache."""
+
+ return self.cache.has_key(self._get_cache_key())
+
+ def _download_response(self):
+ """Returns a response body string from the server."""
+
+ # Delay the call if necessary
+ #self.network._delay_call() # enable it if you want.
+
+ data = []
+ for name in self.params.keys():
+ data.append('='.join((name, urllib.quote_plus(_string(self.params[name])))))
+ data = '&'.join(data)
+
+ headers = {
+ "Content-type": "application/x-www-form-urlencoded",
+ 'Accept-Charset': 'utf-8',
+ 'User-Agent': "pylast" + '/' + __version__
+ }
+
+ (HOST_NAME, HOST_SUBDIR) = self.network.ws_server
+
+ if self.network.is_proxy_enabled():
+ conn = httplib.HTTPConnection(host = self._get_proxy()[0], port = self._get_proxy()[1])
+ conn.request(method='POST', url="http://" + HOST_NAME + HOST_SUBDIR,
+ body=data, headers=headers)
+ else:
+ conn = httplib.HTTPConnection(host=HOST_NAME)
+ conn.request(method='POST', url=HOST_SUBDIR, body=data, headers=headers)
+
+ response = conn.getresponse()
+ response_text = _unicode(response.read())
+ self._check_response_for_errors(response_text)
+ return response_text
+
+ def execute(self, cacheable = False):
+ """Returns the XML DOM response of the POST Request from the server"""
+
+ if self.network.is_caching_enabled() and cacheable:
+ response = self._get_cached_response()
+ else:
+ response = self._download_response()
+
+ return minidom.parseString(_string(response))
+
+ def _check_response_for_errors(self, response):
+ """Checks the response for errors and raises one if any exists."""
+
+ doc = minidom.parseString(_string(response))
+ e = doc.getElementsByTagName('lfm')[0]
+
+ if e.getAttribute('status') != "ok":
+ e = doc.getElementsByTagName('error')[0]
+ status = e.getAttribute('code')
+ details = e.firstChild.data.strip()
+ raise WSError(self.network, status, details)
class SessionKeyGenerator(object):
- """Methods of generating a session key:
- 1) Web Authentication:
- a. sg = SessionKeyGenerator(API_KEY, API_SECRET)
- b. url = sg.get_web_auth_url()
- c. Ask the user to open the url and authorize you, and wait for it.
- d. session_key = sg.get_web_auth_session_key(url)
- 2) Username and Password Authentication:
- a. username = raw_input("Please enter your username: ")
- b. md5_password = pylast.md5(raw_input("Please enter your password: ")
- c. session_key = SessionKeyGenerator(API_KEY, API_SECRET).get_session_key(username, md5_password)
-
- A session key's lifetime is infinie, unless the user provokes the rights of the given API Key.
- """
-
- def __init__(self, api_key, api_secret):
- self.api_key = api_key
- self.api_secret = api_secret
- self.web_auth_tokens = {}
-
- def _get_web_auth_token(self):
- """Retrieves a token from Last.fm for web authentication.
- The token then has to be authorized from getAuthURL before creating session.
- """
-
- request = _Request('auth.getToken', dict(), self.api_key, self.api_secret)
-
- # default action is that a request is signed only when
- # a session key is provided.
- request.sign_it()
-
- doc = request.execute()
-
- e = doc.getElementsByTagName('token')[0]
- return e.firstChild.data
-
- def get_web_auth_url(self):
- """The user must open this page, and you first, then call get_web_auth_session_key(url) after that."""
-
- token = self._get_web_auth_token()
-
- url = 'http://www.last.fm/api/auth/?api_key=%(api)s&token=%(token)s' % \
- {'api': self.api_key, 'token': token}
-
- self.web_auth_tokens[url] = token
-
- return url
-
- def get_web_auth_session_key(self, url):
- """Retrieves the session key of a web authorization process by its url."""
-
- if url in self.web_auth_tokens.keys():
- token = self.web_auth_tokens[url]
- else:
- token = "" #that's gonna raise a ServiceException of an unauthorized token when the request is executed.
-
- request = _Request('auth.getSession', {'token': token}, self.api_key, self.api_secret)
-
- # default action is that a request is signed only when
- # a session key is provided.
- request.sign_it()
-
- doc = request.execute()
-
- return doc.getElementsByTagName('key')[0].firstChild.data
-
- def get_session_key(self, username, md5_password):
- """Retrieve a session key with a username and a md5 hash of the user's password."""
-
- params = {"username": username, "authToken": md5(username + md5_password)}
- request = _Request("auth.getMobileSession", params, self.api_key, self.api_secret)
-
- # default action is that a request is signed only when
- # a session key is provided.
- request.sign_it()
-
- doc = request.execute()
-
- return _extract(doc, "key")
+ """Methods of generating a session key:
+ 1) Web Authentication:
+ a. network = get_*_network(API_KEY, API_SECRET)
+ b. sg = SessionKeyGenerator(network)
+ c. url = sg.get_web_auth_url()
+ d. Ask the user to open the url and authorize you, and wait for it.
+ e. session_key = sg.get_web_auth_session_key(url)
+ 2) Username and Password Authentication:
+ a. network = get_*_network(API_KEY, API_SECRET)
+ b. username = raw_input("Please enter your username: ")
+ c. password_hash = pylast.md5(raw_input("Please enter your password: ")
+ d. session_key = SessionKeyGenerator(network).get_session_key(username, password_hash)
+
+ A session key's lifetime is infinie, unless the user provokes the rights of the given API Key.
+
+ If you create a Network object with just a API_KEY and API_SECRET and a username and a password_hash, a
+ SESSION_KEY will be automatically generated for that network and stored in it so you don't have to do this
+ manually, unless you want to.
+ """
+
+ def __init__(self, network):
+ self.network = network
+ self.web_auth_tokens = {}
+
+ def _get_web_auth_token(self):
+ """Retrieves a token from the network for web authentication.
+ The token then has to be authorized from getAuthURL before creating session.
+ """
+
+ request = _Request(self.network, 'auth.getToken')
+
+ # default action is that a request is signed only when
+ # a session key is provided.
+ request.sign_it()
+
+ doc = request.execute()
+
+ e = doc.getElementsByTagName('token')[0]
+ return e.firstChild.data
+
+ def get_web_auth_url(self):
+ """The user must open this page, and you first, then call get_web_auth_session_key(url) after that."""
+
+ token = self._get_web_auth_token()
+
+ url = '%(homepage)s/api/auth/?api_key=%(api)s&token=%(token)s' % \
+ {"homepage": self.network.homepage, "api": self.network.api_key, "token": token}
+
+ self.web_auth_tokens[url] = token
+
+ return url
+
+ def get_web_auth_session_key(self, url):
+ """Retrieves the session key of a web authorization process by its url."""
+
+ if url in self.web_auth_tokens.keys():
+ token = self.web_auth_tokens[url]
+ else:
+ token = "" #that's gonna raise a WSError of an unauthorized token when the request is executed.
+
+ request = _Request(self.network, 'auth.getSession', {'token': token})
+
+ # default action is that a request is signed only when
+ # a session key is provided.
+ request.sign_it()
+
+ doc = request.execute()
+
+ return doc.getElementsByTagName('key')[0].firstChild.data
+
+ def get_session_key(self, username, password_hash):
+ """Retrieve a session key with a username and a md5 hash of the user's password."""
+
+ params = {"username": username, "authToken": md5(username + password_hash)}
+ request = _Request(self.network, "auth.getMobileSession", params)
+
+ # default action is that a request is signed only when
+ # a session key is provided.
+ request.sign_it()
+
+ doc = request.execute()
+
+ return _extract(doc, "key")
+
+def _namedtuple(name, children):
+ """
+ collections.namedtuple is available in (python >= 2.6)
+ """
+
+ v = sys.version_info
+ if v[1] >= 6 and v[0] < 3:
+ return collections.namedtuple(name, children)
+ else:
+ def fancydict(*args):
+ d = {}
+ i = 0
+ for child in children:
+ d[child.strip()] = args[i]
+ i += 1
+ return d
+
+ return fancydict
+
+TopItem = _namedtuple("TopItem", ["item", "weight"])
+SimilarItem = _namedtuple("SimilarItem", ["item", "match"])
+LibraryItem = _namedtuple("LibraryItem", ["item", "playcount", "tagcount"])
+PlayedTrack = _namedtuple("PlayedTrack", ["track", "playback_date", "timestamp"])
+ImageSizes = _namedtuple("ImageSizes", ["original", "large", "largesquare", "medium", "small", "extralarge"])
+Image = _namedtuple("Image", ["title", "url", "dateadded", "format", "owner", "sizes", "votes"])
+Shout = _namedtuple("Shout", ["body", "author", "date"])
+
+def _string_output(funct):
+ def r(*args):
+ return _string(funct(*args))
+
+ return r
class _BaseObject(object):
- """An abstract webservices object."""
-
- def __init__(self, api_key, api_secret, session_key):
-
- self.api_key = api_key
- self.api_secret = api_secret
- self.session_key = session_key
-
- self.auth_data = (self.api_key, self.api_secret, self.session_key)
-
- def _request(self, method_name, cacheable = False, params = None):
- if not params:
- params = self._get_params()
-
- return _Request(method_name, params, *self.auth_data).execute(cacheable)
-
- def _get_params():
- """Returns the most common set of parameters between all objects."""
-
- return dict()
+ """An abstract webservices object."""
+
+ network = None
+
+ def __init__(self, network):
+ self.network = network
+
+ def _request(self, method_name, cacheable = False, params = None):
+ if not params:
+ params = self._get_params()
+
+ return _Request(self.network, method_name, params).execute(cacheable)
+
+ def _get_params(self):
+ """Returns the most common set of parameters between all objects."""
+
+ return {}
+
+ def __hash__(self):
+ return hash(self.network) + \
+ hash(str(type(self)) + "".join(self._get_params().keys() + self._get_params().values()).lower())
class _Taggable(object):
- """Common functions for classes with tags."""
-
- def __init__(self, ws_prefix):
- self.ws_prefix = ws_prefix
-
- def add_tags(self, *tags):
- """Adds one or several tags.
- * *tags: Any number of tag names or Tag objects.
- """
-
- for tag in tags:
- self._add_tag(tag)
-
- def _add_tag(self, tag):
- """Adds one or several tags.
- * tag: one tag name or a Tag object.
- """
-
- if isinstance(tag, Tag):
- tag = tag.get_name()
-
- params = self._get_params()
- params['tags'] = unicode(tag)
-
- self._request(self.ws_prefix + '.addTags', False, params)
- info("Tagged " + repr(self) + " as (" + repr(tag) + ")")
-
- def _remove_tag(self, single_tag):
- """Remove a user's tag from this object."""
-
- if isinstance(single_tag, Tag):
- single_tag = single_tag.get_name()
-
- params = self._get_params()
- params['tag'] = unicode(single_tag)
-
- self._request(self.ws_prefix + '.removeTag', False, params)
- info("Removed tag (" + repr(tag) + ") from " + repr(self))
-
- def get_tags(self):
- """Returns a list of the tags set by the user to this object."""
-
- # Uncacheable because it can be dynamically changed by the user.
- params = self._get_params()
- doc = _Request(self.ws_prefix + '.getTags', params, *self.auth_data).execute(cacheable = False)
-
- tag_names = _extract_all(doc, 'name')
- tags = []
- for tag in tag_names:
- tags.append(Tag(tag, *self.auth_data))
-
- return tags
-
- def remove_tags(self, *tags):
- """Removes one or several tags from this object.
- * *tags: Any number of tag names or Tag objects.
- """
-
- for tag in tags:
- self._remove_tag(tag)
-
- def clear_tags(self):
- """Clears all the user-set tags. """
-
- self.remove_tags(*(self.get_tags()))
-
- def set_tags(self, *tags):
- """Sets this object's tags to only those tags.
- * *tags: any number of tag names.
- """
-
- c_old_tags = []
- old_tags = []
- c_new_tags = []
- new_tags = []
-
- to_remove = []
- to_add = []
-
- tags_on_server = self.get_tags()
-
- for tag in tags_on_server:
- c_old_tags.append(tag.get_name().lower())
- old_tags.append(tag.get_name())
-
- for tag in tags:
- c_new_tags.append(tag.lower())
- new_tags.append(tag)
-
- for i in range(0, len(old_tags)):
- if not c_old_tags[i] in c_new_tags:
- to_remove.append(old_tags[i])
-
- for i in range(0, len(new_tags)):
- if not c_new_tags[i] in c_old_tags:
- to_add.append(new_tags[i])
-
- self.remove_tags(*to_remove)
- self.add_tags(*to_add)
-
- def get_top_tags(self, limit = None):
- """Returns a list of the most frequently used Tags on this object."""
-
- doc = self._request(self.ws_prefix + '.getTopTags', True)
-
- elements = doc.getElementsByTagName('tag')
- list = []
-
- for element in elements:
- if limit and len(list) >= limit:
- break
- tag_name = _extract(element, 'name')
- tagcount = _extract(element, 'count')
-
- list.append(TopItem(Tag(tag_name, *self.auth_data), tagcount))
-
- return list
-
-class ServiceException(Exception):
- """Exception related to the Last.fm web service"""
-
- def __init__(self, lastfm_status, details):
- self._lastfm_status = lastfm_status
- self._details = details
-
- def __str__(self):
- return self._details
-
- def get_id(self):
- """Returns the exception ID, from one of the following:
- STATUS_INVALID_SERVICE = 2
- STATUS_INVALID_METHOD = 3
- STATUS_AUTH_FAILED = 4
- STATUS_INVALID_FORMAT = 5
- STATUS_INVALID_PARAMS = 6
- STATUS_INVALID_RESOURCE = 7
- STATUS_TOKEN_ERROR = 8
- STATUS_INVALID_SK = 9
- STATUS_INVALID_API_KEY = 10
- STATUS_OFFLINE = 11
- STATUS_SUBSCRIBERS_ONLY = 12
- STATUS_TOKEN_UNAUTHORIZED = 14
- STATUS_TOKEN_EXPIRED = 15
- """
-
- return self._lastfm_status
-
-class TopItem (object):
- """A top item in a list that has a weight. Returned from functions like get_top_tracks() and get_top_artists()."""
-
- def __init__(self, item, weight):
- object.__init__(self)
-
- self.item = item
- self.weight = _number(weight)
-
- def __repr__(self):
- return "Item: " + self.get_item().__repr__() + ", Weight: " + str(self.get_weight())
-
- def get_item(self):
- """Returns the item."""
-
- return self.item
-
- def get_weight(self):
- """Returns the weight of the itme in the list."""
-
- return self.weight
-
-
-class LibraryItem (object):
- """An item in a User's Library. It could be an artist, an album or a track."""
-
- def __init__(self, item, playcount, tagcount):
- object.__init__(self)
-
- self.item = item
- self.playcount = _number(playcount)
- self.tagcount = _number(tagcount)
-
- def __repr__(self):
- return "Item: " + self.get_item().__repr__() + ", Playcount: " + str(self.get_playcount()) + ", Tagcount: " + str(self.get_tagcount())
-
- def get_item(self):
- """Returns the itme."""
-
- return self.item
-
- def get_playcount(self):
- """Returns the item's playcount in the Library."""
-
- return self.playcount
-
- def get_tagcount(self):
- """Returns the item's tagcount in the Library."""
-
- return self.tagcount
-
-class PlayedTrack (object):
- """A track with a playback date."""
-
- def __init__(self, track, date, timestamp):
- object.__init__(self)
-
- self.track = track
- self.date = date
- self.timestamp = timestamp
-
- def __repr__(self):
- return repr(self.track) + " played at " + self.date
-
- def get_track(self):
- """Return the track."""
-
- return self.track
-
- def get_item(self):
- """Returns the played track. An alias to get_track()."""
-
- return self.get_track();
-
- def get_date(self):
- """Returns the playback date."""
-
- return self.date
-
- def get_timestamp(self):
- """Returns the unix timestamp of the playback date."""
-
- return self.timestamp
-
+ """Common functions for classes with tags."""
+
+ def __init__(self, ws_prefix):
+ self.ws_prefix = ws_prefix
+
+ def add_tags(self, *tags):
+ """Adds one or several tags.
+ * *tags: Any number of tag names or Tag objects.
+ """
+
+ for tag in tags:
+ self._add_tag(tag)
+
+ def _add_tag(self, tag):
+ """Adds one or several tags.
+ * tag: one tag name or a Tag object.
+ """
+
+ if isinstance(tag, Tag):
+ tag = tag.get_name()
+
+ params = self._get_params()
+ params['tags'] = _unicode(tag)
+
+ self._request(self.ws_prefix + '.addTags', False, params)
+
+ def _remove_tag(self, single_tag):
+ """Remove a user's tag from this object."""
+
+ if isinstance(single_tag, Tag):
+ single_tag = single_tag.get_name()
+
+ params = self._get_params()
+ params['tag'] = _unicode(single_tag)
+
+ self._request(self.ws_prefix + '.removeTag', False, params)
+
+ def get_tags(self):
+ """Returns a list of the tags set by the user to this object."""
+
+ # Uncacheable because it can be dynamically changed by the user.
+ params = self._get_params()
+
+ doc = self._request(self.ws_prefix + '.getTags', False, params)
+ tag_names = _extract_all(doc, 'name')
+ tags = []
+ for tag in tag_names:
+ tags.append(Tag(tag, self.network))
+
+ return tags
+
+ def remove_tags(self, *tags):
+ """Removes one or several tags from this object.
+ * *tags: Any number of tag names or Tag objects.
+ """
+
+ for tag in tags:
+ self._remove_tag(tag)
+
+ def clear_tags(self):
+ """Clears all the user-set tags. """
+
+ self.remove_tags(*(self.get_tags()))
+
+ def set_tags(self, *tags):
+ """Sets this object's tags to only those tags.
+ * *tags: any number of tag names.
+ """
+
+ c_old_tags = []
+ old_tags = []
+ c_new_tags = []
+ new_tags = []
+
+ to_remove = []
+ to_add = []
+
+ tags_on_server = self.get_tags()
+
+ for tag in tags_on_server:
+ c_old_tags.append(tag.get_name().lower())
+ old_tags.append(tag.get_name())
+
+ for tag in tags:
+ c_new_tags.append(tag.lower())
+ new_tags.append(tag)
+
+ for i in range(0, len(old_tags)):
+ if not c_old_tags[i] in c_new_tags:
+ to_remove.append(old_tags[i])
+
+ for i in range(0, len(new_tags)):
+ if not c_new_tags[i] in c_old_tags:
+ to_add.append(new_tags[i])
+
+ self.remove_tags(*to_remove)
+ self.add_tags(*to_add)
+
+ def get_top_tags(self, limit = None):
+ """Returns a list of the most frequently used Tags on this object."""
+
+ doc = self._request(self.ws_prefix + '.getTopTags', True)
+
+ elements = doc.getElementsByTagName('tag')
+ seq = []
+
+ for element in elements:
+ if limit and len(seq) >= limit:
+ break
+ tag_name = _extract(element, 'name')
+ tagcount = _extract(element, 'count')
+
+ seq.append(TopItem(Tag(tag_name, self.network), tagcount))
+
+ return seq
+
+class WSError(Exception):
+ """Exception related to the Network web service"""
+
+ def __init__(self, network, status, details):
+ self.status = status
+ self.details = details
+ self.network = network
+
+ @_string_output
+ def __str__(self):
+ return self.details
+
+ def get_id(self):
+ """Returns the exception ID, from one of the following:
+ STATUS_INVALID_SERVICE = 2
+ STATUS_INVALID_METHOD = 3
+ STATUS_AUTH_FAILED = 4
+ STATUS_INVALID_FORMAT = 5
+ STATUS_INVALID_PARAMS = 6
+ STATUS_INVALID_RESOURCE = 7
+ STATUS_TOKEN_ERROR = 8
+ STATUS_INVALID_SK = 9
+ STATUS_INVALID_API_KEY = 10
+ STATUS_OFFLINE = 11
+ STATUS_SUBSCRIBERS_ONLY = 12
+ STATUS_TOKEN_UNAUTHORIZED = 14
+ STATUS_TOKEN_EXPIRED = 15
+ """
+
+ return self.status
class Album(_BaseObject, _Taggable):
- """A Last.fm album."""
-
- def __init__(self, artist, title, api_key, api_secret, session_key):
- """
- Create an album instance.
- # Parameters:
- * artist str|Artist: An artist name or an Artist object.
- * title str: The album title.
- """
-
- _BaseObject.__init__(self, api_key, api_secret, session_key)
- _Taggable.__init__(self, 'album')
-
- if isinstance(artist, Artist):
- self.artist = artist
- else:
- self.artist = Artist(artist, *self.auth_data)
-
- self.title = title
-
- def __repr__(self):
- return self.get_artist().get_name() + ' - ' + self.get_title()
-
- def __eq__(self, other):
- return (self.get_title().lower() == other.get_title().lower()) and (self.get_artist().get_name().lower() == other.get_artist().get_name().lower())
-
- def __ne__(self, other):
- return (self.get_title().lower() != other.get_title().lower()) or (self.get_artist().get_name().lower() != other.get_artist().get_name().lower())
-
- def _get_params(self):
- return {'artist': self.get_artist().get_name(), 'album': self.get_title(), }
-
- def get_artist(self):
- """Returns the associated Artist object."""
-
- return self.artist
-
- def get_title(self):
- """Returns the album title."""
-
- return self.title
-
- def get_name(self):
- """Returns the album title (alias to Album.get_title)."""
-
- return self.get_title()
-
- def get_release_date(self):
- """Retruns the release date of the album."""
-
- return _extract(self._request("album.getInfo", cacheable = True), "releasedate")
-
- def get_image_url(self, size = IMAGE_EXTRA_LARGE):
- """Returns the associated image URL.
- # Parameters:
- * size int: The image size. Possible values:
- o IMAGE_EXTRA_LARGE
- o IMAGE_LARGE
- o IMAGE_MEDIUM
- o IMAGE_SMALL
- """
-
- return _extract_all(self._request("album.getInfo", cacheable = True), 'image')[size]
-
- def get_id(self):
- """Returns the Last.fm ID."""
-
- return _extract(self._request("album.getInfo", cacheable = True), "id")
-
- def get_playcount(self):
- """Returns the number of plays on Last.fm."""
-
- return _number(_extract(self._request("album.getInfo", cacheable = True), "playcount"))
-
- def get_listener_count(self):
- """Returns the number of liteners on Last.fm."""
-
- return _number(_extract(self._request("album.getInfo", cacheable = True), "listeners"))
-
- def get_top_tags(self, limit = None):
- """Returns a list of the most-applied tags to this album."""
-
- # BROKEN: Web service is currently broken.
-
- return None
-
- def get_tracks(self):
- """Returns the list of Tracks on this album."""
-
- uri = 'lastfm://playlist/album/%s' %self.get_id()
-
- return XSPF(uri, *self.auth_data).get_tracks()
-
- def get_mbid(self):
- """Returns the MusicBrainz id of the album."""
-
- return _extract(self._request("album.getInfo", cacheable = True), "mbid")
-
- def get_url(self, domain_name = DOMAIN_ENGLISH):
- """Returns the url of the album page on Last.fm.
- # Parameters:
- * domain_name str: Last.fm's language domain. Possible values:
- o DOMAIN_ENGLISH
- o DOMAIN_GERMAN
- o DOMAIN_SPANISH
- o DOMAIN_FRENCH
- o DOMAIN_ITALIAN
- o DOMAIN_POLISH
- o DOMAIN_PORTUGUESE
- o DOMAIN_SWEDISH
- o DOMAIN_TURKISH
- o DOMAIN_RUSSIAN
- o DOMAIN_JAPANESE
- o DOMAIN_CHINESE
- """
-
- url = 'http://%(domain)s/music/%(artist)s/%(album)s'
-
- artist = _get_url_safe(self.get_artist().get_name())
- album = _get_url_safe(self.get_title())
-
- return url %{'domain': domain_name, 'artist': artist, 'album': album}
+ """An album."""
+
+ title = None
+ artist = None
+
+ def __init__(self, artist, title, network):
+ """
+ Create an album instance.
+ # Parameters:
+ * artist: An artist name or an Artist object.
+ * title: The album title.
+ """
+
+ _BaseObject.__init__(self, network)
+ _Taggable.__init__(self, 'album')
+
+ if isinstance(artist, Artist):
+ self.artist = artist
+ else:
+ self.artist = Artist(artist, self.network)
+
+ self.title = title
+
+ @_string_output
+ def __repr__(self):
+ return u"%s - %s" %(self.get_artist().get_name(), self.get_title())
+
+ def __eq__(self, other):
+ return (self.get_title().lower() == other.get_title().lower()) and (self.get_artist().get_name().lower() == other.get_artist().get_name().lower())
+
+ def __ne__(self, other):
+ return (self.get_title().lower() != other.get_title().lower()) or (self.get_artist().get_name().lower() != other.get_artist().get_name().lower())
+
+ def _get_params(self):
+ return {'artist': self.get_artist().get_name(), 'album': self.get_title(), }
+
+ def get_artist(self):
+ """Returns the associated Artist object."""
+
+ return self.artist
+
+ def get_title(self):
+ """Returns the album title."""
+
+ return self.title
+
+ def get_name(self):
+ """Returns the album title (alias to Album.get_title)."""
+
+ return self.get_title()
+
+ def get_release_date(self):
+ """Retruns the release date of the album."""
+
+ return _extract(self._request("album.getInfo", cacheable = True), "releasedate")
+
+ def get_cover_image(self, size = COVER_EXTRA_LARGE):
+ """
+ Returns a uri to the cover image
+ size can be one of:
+ COVER_MEGA
+ COVER_EXTRA_LARGE
+ COVER_LARGE
+ COVER_MEDIUM
+ COVER_SMALL
+ """
+
+ return _extract_all(self._request("album.getInfo", cacheable = True), 'image')[size]
+
+ def get_id(self):
+ """Returns the ID"""
+
+ return _extract(self._request("album.getInfo", cacheable = True), "id")
+
+ def get_playcount(self):
+ """Returns the number of plays on the network"""
+
+ return _number(_extract(self._request("album.getInfo", cacheable = True), "playcount"))
+
+ def get_listener_count(self):
+ """Returns the number of liteners on the network"""
+
+ return _number(_extract(self._request("album.getInfo", cacheable = True), "listeners"))
+
+ def get_top_tags(self, limit=None):
+ """Returns a list of the most-applied tags to this album."""
+
+ doc = self._request("album.getInfo", True)
+ e = doc.getElementsByTagName("toptags")[0]
+
+ seq = []
+ for name in _extract_all(e, "name"):
+ if len(seq) < limit:
+ seq.append(Tag(name, self.network))
+
+ return seq
+
+ def get_tracks(self):
+ """Returns the list of Tracks on this album."""
+
+ uri = 'lastfm://playlist/album/%s' %self.get_id()
+
+ return XSPF(uri, self.network).get_tracks()
+
+ def get_mbid(self):
+ """Returns the MusicBrainz id of the album."""
+
+ return _extract(self._request("album.getInfo", cacheable = True), "mbid")
+
+ def get_url(self, domain_name = DOMAIN_ENGLISH):
+ """Returns the url of the album page on the network.
+ # Parameters:
+ * domain_name str: The network's language domain. Possible values:
+ o DOMAIN_ENGLISH
+ o DOMAIN_GERMAN
+ o DOMAIN_SPANISH
+ o DOMAIN_FRENCH
+ o DOMAIN_ITALIAN
+ o DOMAIN_POLISH
+ o DOMAIN_PORTUGUESE
+ o DOMAIN_SWEDISH
+ o DOMAIN_TURKISH
+ o DOMAIN_RUSSIAN
+ o DOMAIN_JAPANESE
+ o DOMAIN_CHINESE
+ """
+
+ artist = _url_safe(self.get_artist().get_name())
+ album = _url_safe(self.get_title())
+
+ return self.network._get_url(domain_name, "album") %{'artist': artist, 'album': album}
+
+ def get_wiki_published_date(self):
+ """Returns the date of publishing this version of the wiki."""
+
+ doc = self._request("album.getInfo", True)
+
+ if len(doc.getElementsByTagName("wiki")) == 0:
+ return
+
+ node = doc.getElementsByTagName("wiki")[0]
+
+ return _extract(node, "published")
+
+ def get_wiki_summary(self):
+ """Returns the summary of the wiki."""
+
+ doc = self._request("album.getInfo", True)
+
+ if len(doc.getElementsByTagName("wiki")) == 0:
+ return
+
+ node = doc.getElementsByTagName("wiki")[0]
+
+ return _extract(node, "summary")
+
+ def get_wiki_content(self):
+ """Returns the content of the wiki."""
+
+ doc = self._request("album.getInfo", True)
+
+ if len(doc.getElementsByTagName("wiki")) == 0:
+ return
+
+ node = doc.getElementsByTagName("wiki")[0]
+
+ return _extract(node, "content")
class Artist(_BaseObject, _Taggable):
- """A Last.fm artist."""
-
- def __init__(self, name, api_key, api_secret, session_key):
- """Create an artist object.
- # Parameters:
- * name str: The artist's name.
- """
-
- _BaseObject.__init__(self, api_key, api_secret, session_key)
- _Taggable.__init__(self, 'artist')
-
- self.name = name
-
- def __repr__(self):
- return unicode(self.get_name())
-
- def __eq__(self, other):
- return self.get_name().lower() == other.get_name().lower()
-
- def __ne__(self, other):
- return self.get_name().lower() != other.get_name().lower()
-
- def _get_params(self):
- return {'artist': self.get_name()}
-
- def get_name(self):
- """Returns the name of the artist."""
-
- return self.name
-
- def get_image_url(self, size = IMAGE_LARGE):
- """Returns the associated image URL.
- # Parameters:
- * size int: The image size. Possible values:
- o IMAGE_LARGE
- o IMAGE_MEDIUM
- o IMAGE_SMALL
- """
-
- return _extract_all(self._request("artist.getInfo", True), "image")[size]
-
- def get_square_image(self):
- return _extract_all(self._request("artist.getImages", True), "size")[2]
-
- def get_playcount(self):
- """Returns the number of plays on Last.fm."""
-
- return _number(_extract(self._request("artist.getInfo", True), "playcount"))
-
- def get_mbid(self):
- """Returns the MusicBrainz ID of this artist."""
-
- doc = self._request("artist.getInfo", True)
-
- return _extract(doc, "mbid")
-
- def get_listener_count(self):
- """Returns the number of liteners on Last.fm."""
-
- return _number(_extract(self._request("artist.getInfo", True), "listeners"))
-
- def is_streamable(self):
- """Returns True if the artist is streamable."""
-
- return bool(_number(_extract(self._request("artist.getInfo", True), "streamable")))
-
- def get_bio_published_date(self):
- """Returns the date on which the artist's biography was published."""
-
- return _extract(self._request("artist.getInfo", True), "published")
-
- def get_bio_summary(self):
- """Returns the summary of the artist's biography."""
-
- return _extract(self._request("artist.getInfo", True), "summary")
-
- def get_bio_content(self):
- """Returns the content of the artist's biography."""
-
- return _extract(self._request("artist.getInfo", True), "content")
-
- def get_upcoming_events(self):
- """Returns a list of the upcoming Events for this artist."""
-
- doc = self._request('artist.getEvents', True)
-
- ids = _extract_all(doc, 'id')
-
- events = []
- for id in ids:
- events.append(Event(id, *self.auth_data))
-
- return events
-
- def get_similar(self, limit = None):
- """Returns the similar artists on Last.fm."""
-
- params = self._get_params()
- if limit:
- params['limit'] = unicode(limit)
-
- doc = self._request('artist.getSimilar', True, params)
-
- names = _extract_all(doc, 'name')
-
- artists = []
- for name in names:
- artists.append(Artist(name, *self.auth_data))
-
- return artists
-
-# def get_top_tags(self):
-# """Returns the top tags."""
-#
-# doc = self._request("artist.getInfo", True)
-#
-# list = []
-#
-# elements = doc.getElementsByTagName('tag')
-#
-# for element in elements:
-#
-# name = _extract(element, 'name')
-#
-# list.append(name)
-#
-# return list
-
- def get_top_tags(self):
- """Returns the top tags."""
-
- doc = self._request("artist.getTopTags", True)
-
- list = []
-
- elements = doc.getElementsByTagName('tag')
-
- for element in elements:
-
- name = _extract(element, 'name')
-
- list.append(name)
-
- return list
-
- def get_top_albums(self):
- """Retuns a list of the top albums."""
-
- doc = self._request('artist.getTopAlbums', True)
-
- list = []
-
- for node in doc.getElementsByTagName("album"):
- name = _extract(node, "name")
- artist = _extract(node, "name", 1)
- playcount = _extract(node, "playcount")
-
- list.append(TopItem(Album(artist, name, *self.auth_data), playcount))
-
- return list
-
- def get_top_tracks(self):
- """Returns a list of the most played Tracks by this artist."""
-
- doc = self._request("artist.getTopTracks", True)
-
- list = []
- for track in doc.getElementsByTagName('track'):
-
- title = _extract(track, "name")
- artist = _extract(track, "name", 1)
- playcount = _number(_extract(track, "playcount"))
-
- list.append( TopItem(Track(artist, title, *self.auth_data), playcount) )
-
- return list
-
- def get_top_fans(self, limit = None):
- """Returns a list of the Users who played this artist the most.
- # Parameters:
- * limit int: Max elements.
- """
-
- params = self._get_params()
- doc = self._request('artist.getTopFans', True)
-
- list = []
-
- elements = doc.getElementsByTagName('user')
-
- for element in elements:
- if limit and len(list) >= limit:
- break
-
- name = _extract(element, 'name')
- weight = _number(_extract(element, 'weight'))
-
- list.append(TopItem(User(name, *self.auth_data), weight))
-
- return list
-
- def share(self, users, message = None):
- """Shares this artist (sends out recommendations).
- # Parameters:
- * users [User|str,]: A list that can contain usernames, emails, User objects, or all of them.
- * message str: A message to include in the recommendation message.
- """
-
- #last.fm currently accepts a max of 10 recipient at a time
- while(len(users) > 10):
- section = users[0:9]
- users = users[9:]
- self.share(section, message)
-
- nusers = []
- for user in users:
- if isinstance(user, User):
- nusers.append(user.get_name())
- else:
- nusers.append(user)
-
- params = self._get_params()
- recipients = ','.join(nusers)
- params['recipient'] = recipients
- if message: params['message'] = unicode(message)
-
- self._request('artist.share', False, params)
- info(repr(self) + " was shared with " + repr(users))
-
- def get_url(self, domain_name = DOMAIN_ENGLISH):
- """Returns the url of the artist page on Last.fm.
- # Parameters:
- * domain_name: Last.fm's language domain. Possible values:
- o DOMAIN_ENGLISH
- o DOMAIN_GERMAN
- o DOMAIN_SPANISH
- o DOMAIN_FRENCH
- o DOMAIN_ITALIAN
- o DOMAIN_POLISH
- o DOMAIN_PORTUGUESE
- o DOMAIN_SWEDISH
- o DOMAIN_TURKISH
- o DOMAIN_RUSSIAN
- o DOMAIN_JAPANESE
- o DOMAIN_CHINESE
- """
-
- url = 'http://%(domain)s/music/%(artist)s'
-
- artist = _get_url_safe(self.get_name())
-
- return url %{'domain': domain_name, 'artist': artist}
-
+ """An artist."""
+
+ name = None
+
+ def __init__(self, name, network):
+ """Create an artist object.
+ # Parameters:
+ * name str: The artist's name.
+ """
+
+ _BaseObject.__init__(self, network)
+ _Taggable.__init__(self, 'artist')
+
+ self.name = name
+
+ @_string_output
+ def __repr__(self):
+ return self.get_name()
+
+ def __eq__(self, other):
+ return self.get_name().lower() == other.get_name().lower()
+
+ def __ne__(self, other):
+ return self.get_name().lower() != other.get_name().lower()
+
+ def _get_params(self):
+ return {'artist': self.get_name()}
+
+ def get_name(self):
+ """Returns the name of the artist."""
+
+ return self.name
+
+ def get_cover_image(self, size = COVER_LARGE):
+ """
+ Returns a uri to the cover image
+ size can be one of:
+ COVER_MEGA
+ COVER_EXTRA_LARGE
+ COVER_LARGE
+ COVER_MEDIUM
+ COVER_SMALL
+ """
+
+ return _extract_all(self._request("artist.getInfo", True), "image")[size]
+
+ def get_playcount(self):
+ """Returns the number of plays on the network."""
+
+ return _number(_extract(self._request("artist.getInfo", True), "playcount"))
+
+ def get_mbid(self):
+ """Returns the MusicBrainz ID of this artist."""
+
+ doc = self._request("artist.getInfo", True)
+
+ return _extract(doc, "mbid")
+
+ def get_listener_count(self):
+ """Returns the number of liteners on the network."""
+
+ return _number(_extract(self._request("artist.getInfo", True), "listeners"))
+
+ def is_streamable(self):
+ """Returns True if the artist is streamable."""
+
+ return bool(_number(_extract(self._request("artist.getInfo", True), "streamable")))
+
+ def get_bio_published_date(self):
+ """Returns the date on which the artist's biography was published."""
+
+ return _extract(self._request("artist.getInfo", True), "published")
+
+ def get_bio_summary(self):
+ """Returns the summary of the artist's biography."""
+
+ return _extract(self._request("artist.getInfo", True), "summary")
+
+ def get_bio_content(self):
+ """Returns the content of the artist's biography."""
+
+ return _extract(self._request("artist.getInfo", True), "content")
+
+ def get_upcoming_events(self):
+ """Returns a list of the upcoming Events for this artist."""
+
+ doc = self._request('artist.getEvents', True)
+
+ ids = _extract_all(doc, 'id')
+
+ events = []
+ for e_id in ids:
+ events.append(Event(e_id, self.network))
+
+ return events
+
+ def get_similar(self, limit = None):
+ """Returns the similar artists on the network."""
+
+ params = self._get_params()
+ if limit:
+ params['limit'] = _unicode(limit)
+
+ doc = self._request('artist.getSimilar', True, params)
+
+ names = _extract_all(doc, "name")
+ matches = _extract_all(doc, "match")
+
+ artists = []
+ for i in range(0, len(names)):
+ artists.append(SimilarItem(Artist(names[i], self.network), _number(matches[i])))
+
+ return artists
+
+ def get_top_albums(self):
+ """Retuns a list of the top albums."""
+
+ doc = self._request('artist.getTopAlbums', True)
+
+ seq = []
+
+ for node in doc.getElementsByTagName("album"):
+ name = _extract(node, "name")
+ artist = _extract(node, "name", 1)
+ playcount = _extract(node, "playcount")
+
+ seq.append(TopItem(Album(artist, name, self.network), playcount))
+
+ return seq
+
+ def get_top_tracks(self):
+ """Returns a list of the most played Tracks by this artist."""
+
+ doc = self._request("artist.getTopTracks", True)
+
+ seq = []
+ for track in doc.getElementsByTagName('track'):
+
+ title = _extract(track, "name")
+ artist = _extract(track, "name", 1)
+ playcount = _number(_extract(track, "playcount"))
+
+ seq.append( TopItem(Track(artist, title, self.network), playcount) )
+
+ return seq
+
+ def get_top_fans(self, limit = None):
+ """Returns a list of the Users who played this artist the most.
+ # Parameters:
+ * limit int: Max elements.
+ """
+
+ doc = self._request('artist.getTopFans', True)
+
+ seq = []
+
+ elements = doc.getElementsByTagName('user')
+
+ for element in elements:
+ if limit and len(seq) >= limit:
+ break
+
+ name = _extract(element, 'name')
+ weight = _number(_extract(element, 'weight'))
+
+ seq.append(TopItem(User(name, self.network), weight))
+
+ return seq
+
+ def share(self, users, message = None):
+ """Shares this artist (sends out recommendations).
+ # Parameters:
+ * users [User|str,]: A list that can contain usernames, emails, User objects, or all of them.
+ * message str: A message to include in the recommendation message.
+ """
+
+ #last.fm currently accepts a max of 10 recipient at a time
+ while(len(users) > 10):
+ section = users[0:9]
+ users = users[9:]
+ self.share(section, message)
+
+ nusers = []
+ for user in users:
+ if isinstance(user, User):
+ nusers.append(user.get_name())
+ else:
+ nusers.append(user)
+
+ params = self._get_params()
+ recipients = ','.join(nusers)
+ params['recipient'] = recipients
+ if message: params['message'] = _unicode(message)
+
+ self._request('artist.share', False, params)
+
+ def get_url(self, domain_name = DOMAIN_ENGLISH):
+ """Returns the url of the artist page on the network.
+ # Parameters:
+ * domain_name: The network's language domain. Possible values:
+ o DOMAIN_ENGLISH
+ o DOMAIN_GERMAN
+ o DOMAIN_SPANISH
+ o DOMAIN_FRENCH
+ o DOMAIN_ITALIAN
+ o DOMAIN_POLISH
+ o DOMAIN_PORTUGUESE
+ o DOMAIN_SWEDISH
+ o DOMAIN_TURKISH
+ o DOMAIN_RUSSIAN
+ o DOMAIN_JAPANESE
+ o DOMAIN_CHINESE
+ """
+
+ artist = _url_safe(self.get_name())
+
+ return self.network._get_url(domain_name, "artist") %{'artist': artist}
+
+ def get_images(self, order=IMAGES_ORDER_POPULARITY, limit=None):
+ """
+ Returns a sequence of Image objects
+ if limit is None it will return all
+ order can be IMAGES_ORDER_POPULARITY or IMAGES_ORDER_DATE
+ """
+
+ images = []
+
+ params = self._get_params()
+ params["order"] = order
+ nodes = _collect_nodes(limit, self, "artist.getImages", True, params)
+ for e in nodes:
+ if _extract(e, "name"):
+ user = User(_extract(e, "name"), self.network)
+ else:
+ user = None
+
+ images.append(Image(
+ _extract(e, "title"),
+ _extract(e, "url"),
+ _extract(e, "dateadded"),
+ _extract(e, "format"),
+ user,
+ ImageSizes(*_extract_all(e, "size")),
+ (_extract(e, "thumbsup"), _extract(e, "thumbsdown"))
+ )
+ )
+ return images
+
+ def get_shouts(self, limit=50):
+ """
+ Returns a sequqence of Shout objects
+ """
+
+ shouts = []
+ for node in _collect_nodes(limit, self, "artist.getShouts", False):
+ shouts.append(Shout(
+ _extract(node, "body"),
+ User(_extract(node, "author"), self.network),
+ _extract(node, "date")
+ )
+ )
+ return shouts
+
+ def shout(self, message):
+ """
+ Post a shout
+ """
+
+ params = self._get_params()
+ params["message"] = message
+
+ self._request("artist.Shout", False, params)
+
class Event(_BaseObject):
- """A Last.fm event."""
-
- def __init__(self, event_id, api_key, api_secret, session_key):
- _BaseObject.__init__(self, api_key, api_secret, session_key)
-
- self.id = unicode(event_id)
-
- def __repr__(self):
- return "Event #" + self.get_id()
-
- def __eq__(self, other):
- return self.get_id() == other.get_id()
-
- def __ne__(self, other):
- return self.get_id() != other.get_id()
-
- def _get_params(self):
- return {'event': self.get_id()}
-
- def attend(self, attending_status):
- """Sets the attending status.
- * attending_status: The attending status. Possible values:
- o EVENT_ATTENDING
- o EVENT_MAYBE_ATTENDING
- o EVENT_NOT_ATTENDING
- """
-
- params = self._get_params()
- params['status'] = unicode(attending_status)
-
- doc = self._request('event.attend', False, params)
- info("Attendance to " + repr(self) + " was set to " + repr(attending_status))
-
- def get_id(self):
- """Returns the id of the event on Last.fm. """
- return self.id
-
- def get_title(self):
- """Returns the title of the event. """
-
- doc = self._request("event.getInfo", True)
-
- return _extract(doc, "title")
-
- def get_headliner(self):
- """Returns the headliner of the event. """
-
- doc = self._request("event.getInfo", True)
-
- return Artist(_extract(doc, "headliner"), *self.auth_data)
-
- def get_artists(self):
- """Returns a list of the participating Artists. """
-
- doc = self._request("event.getInfo", True)
- names = _extract_all(doc, "artist")
-
- artists = []
- for name in names:
- artists.append(Artist(name, *self.auth_data))
-
- return artists
-
- def get_venue(self):
- """Returns the venue where the event is held."""
-
- doc = self._request("event.getInfo", True)
-
- venue_url = _extract(doc, "url")
- venue_id = _number(venue_url[venue_url.rfind("/") + 1:])
-
- return Venue(venue_id, *self.auth_data)
-
- def get_start_date(self):
- """Returns the date when the event starts."""
-
- doc = self._request("event.getInfo", True)
-
- return _extract(doc, "startDate")
-
- def get_description(self):
- """Returns the description of the event. """
-
- doc = self._request("event.getInfo", True)
-
- return _extract(doc, "description")
-
- def get_image_url(self, size = IMAGE_LARGE):
- """Returns the associated image URL.
- * size: The image size. Possible values:
- o IMAGE_LARGE
- o IMAGE_MEDIUM
- o IMAGE_SMALL
- """
-
- doc = self._request("event.getInfo", True)
-
- return _extract_all(doc, "image")[size]
-
- def get_attendance_count(self):
- """Returns the number of attending people. """
-
- doc = self._request("event.getInfo", True)
-
- return _number(_extract(doc, "attendance"))
-
- def get_review_count(self):
- """Returns the number of available reviews for this event. """
-
- doc = self._request("event.getInfo", True)
-
- return _number(_extract(doc, "reviews"))
-
- def get_url(self, domain_name = DOMAIN_ENGLISH):
- """Returns the url of the event page on Last.fm.
- * domain_name: Last.fm's language domain. Possible values:
- o DOMAIN_ENGLISH
- o DOMAIN_GERMAN
- o DOMAIN_SPANISH
- o DOMAIN_FRENCH
- o DOMAIN_ITALIAN
- o DOMAIN_POLISH
- o DOMAIN_PORTUGUESE
- o DOMAIN_SWEDISH
- o DOMAIN_TURKISH
- o DOMAIN_RUSSIAN
- o DOMAIN_JAPANESE
- o DOMAIN_CHINESE
- """
-
- url = 'http://%(domain)s/event/%(id)s'
-
- return url %{'domain': domain_name, 'id': self.get_id()}
-
- def share(self, users, message = None):
- """Shares this event (sends out recommendations).
- * users: A list that can contain usernames, emails, User objects, or all of them.
- * message: A message to include in the recommendation message.
- """
-
- #last.fm currently accepts a max of 10 recipient at a time
- while(len(users) > 10):
- section = users[0:9]
- users = users[9:]
- self.share(section, message)
-
- nusers = []
- for user in users:
- if isinstance(user, User):
- nusers.append(user.get_name())
- else:
- nusers.append(user)
-
- params = self._get_params()
- recipients = ','.join(nusers)
- params['recipient'] = recipients
- if message: params['message'] = unicode(message)
-
- self._request('event.share', False, params)
- info(repr(self) + " was shared with " + repr(users))
-
+ """An event."""
+
+ id = None
+
+ def __init__(self, event_id, network):
+ _BaseObject.__init__(self, network)
+
+ self.id = _unicode(event_id)
+
+ @_string_output
+ def __repr__(self):
+ return "Event #" + self.get_id()
+
+ def __eq__(self, other):
+ return self.get_id() == other.get_id()
+
+ def __ne__(self, other):
+ return self.get_id() != other.get_id()
+
+ def _get_params(self):
+ return {'event': self.get_id()}
+
+ def attend(self, attending_status):
+ """Sets the attending status.
+ * attending_status: The attending status. Possible values:
+ o EVENT_ATTENDING
+ o EVENT_MAYBE_ATTENDING
+ o EVENT_NOT_ATTENDING
+ """
+
+ params = self._get_params()
+ params['status'] = _unicode(attending_status)
+
+ self._request('event.attend', False, params)
+
+ def get_attendees(self):
+ """
+ Get a list of attendees for an event
+ """
+
+ doc = self._request("event.getAttendees", False)
+
+ users = []
+ for name in _extract_all(doc, "name"):
+ users.append(User(name, self.network))
+
+ return users
+
+ def get_id(self):
+ """Returns the id of the event on the network. """
+
+ return self.id
+
+ def get_title(self):
+ """Returns the title of the event. """
+
+ doc = self._request("event.getInfo", True)
+
+ return _extract(doc, "title")
+
+ def get_headliner(self):
+ """Returns the headliner of the event. """
+
+ doc = self._request("event.getInfo", True)
+
+ return Artist(_extract(doc, "headliner"), self.network)
+
+ def get_artists(self):
+ """Returns a list of the participating Artists. """
+
+ doc = self._request("event.getInfo", True)
+ names = _extract_all(doc, "artist")
+
+ artists = []
+ for name in names:
+ artists.append(Artist(name, self.network))
+
+ return artists
+
+ def get_venue(self):
+ """Returns the venue where the event is held."""
+
+ doc = self._request("event.getInfo", True)
+
+ v = doc.getElementsByTagName("venue")[0]
+ venue_id = _number(_extract(v, "id"))
+
+ return Venue(venue_id, self.network)
+
+ def get_start_date(self):
+ """Returns the date when the event starts."""
+
+ doc = self._request("event.getInfo", True)
+
+ return _extract(doc, "startDate")
+
+ def get_description(self):
+ """Returns the description of the event. """
+
+ doc = self._request("event.getInfo", True)
+
+ return _extract(doc, "description")
+
+ def get_cover_image(self, size = COVER_LARGE):
+ """
+ Returns a uri to the cover image
+ size can be one of:
+ COVER_MEGA
+ COVER_EXTRA_LARGE
+ COVER_LARGE
+ COVER_MEDIUM
+ COVER_SMALL
+ """
+
+ doc = self._request("event.getInfo", True)
+
+ return _extract_all(doc, "image")[size]
+
+ def get_attendance_count(self):
+ """Returns the number of attending people. """
+
+ doc = self._request("event.getInfo", True)
+
+ return _number(_extract(doc, "attendance"))
+
+ def get_review_count(self):
+ """Returns the number of available reviews for this event. """
+
+ doc = self._request("event.getInfo", True)
+
+ return _number(_extract(doc, "reviews"))
+
+ def get_url(self, domain_name = DOMAIN_ENGLISH):
+ """Returns the url of the event page on the network.
+ * domain_name: The network's language domain. Possible values:
+ o DOMAIN_ENGLISH
+ o DOMAIN_GERMAN
+ o DOMAIN_SPANISH
+ o DOMAIN_FRENCH
+ o DOMAIN_ITALIAN
+ o DOMAIN_POLISH
+ o DOMAIN_PORTUGUESE
+ o DOMAIN_SWEDISH
+ o DOMAIN_TURKISH
+ o DOMAIN_RUSSIAN
+ o DOMAIN_JAPANESE
+ o DOMAIN_CHINESE
+ """
+
+ return self.network._get_url(domain_name, "event") %{'id': self.get_id()}
+
+ def share(self, users, message = None):
+ """Shares this event (sends out recommendations).
+ * users: A list that can contain usernames, emails, User objects, or all of them.
+ * message: A message to include in the recommendation message.
+ """
+
+ #last.fm currently accepts a max of 10 recipient at a time
+ while(len(users) > 10):
+ section = users[0:9]
+ users = users[9:]
+ self.share(section, message)
+
+ nusers = []
+ for user in users:
+ if isinstance(user, User):
+ nusers.append(user.get_name())
+ else:
+ nusers.append(user)
+
+ params = self._get_params()
+ recipients = ','.join(nusers)
+ params['recipient'] = recipients
+ if message: params['message'] = _unicode(message)
+
+ self._request('event.share', False, params)
+
+ def get_shouts(self, limit=50):
+ """
+ Returns a sequqence of Shout objects
+ """
+
+ shouts = []
+ for node in _collect_nodes(limit, self, "event.getShouts", False):
+ shouts.append(Shout(
+ _extract(node, "body"),
+ User(_extract(node, "author"), self.network),
+ _extract(node, "date")
+ )
+ )
+ return shouts
+
+ def shout(self, message):
+ """
+ Post a shout
+ """
+
+ params = self._get_params()
+ params["message"] = message
+
+ self._request("event.Shout", False, params)
+
class Country(_BaseObject):
- """A country at Last.fm."""
-
- def __init__(self, name, api_key, api_secret, session_key):
- _BaseObject.__init__(self, api_key, api_secret, session_key)
-
- self.name = name
-
- def __repr__(self):
- return self.get_name()
-
- def __eq__(self, other):
- self.get_name().lower() == other.get_name().lower()
-
- def __ne__(self, other):
- self.get_name() != other.get_name()
-
- def _get_params(self):
- return {'country': self.get_name()}
-
- def _get_name_from_code(self, alpha2code):
- # TODO: Have this function lookup the alpha-2 code and return the country name.
-
- return alpha2code
-
- def get_name(self):
- """Returns the country name. """
-
- return self.name
-
- def get_top_artists(self):
- """Returns a sequence of the most played artists."""
-
- doc = self._request('geo.getTopArtists', True)
-
- list = []
- for node in doc.getElementsByTagName("artist"):
- name = _extract(node, 'name')
- playcount = _extract(node, "playcount")
-
- list.append(TopItem(Artist(name, *self.auth_data), playcount))
-
- return list
-
- def get_top_tracks(self):
- """Returns a sequence of the most played tracks"""
-
- doc = self._request("geo.getTopTracks", True)
-
- list = []
-
- for n in doc.getElementsByTagName('track'):
-
- title = _extract(n, 'name')
- artist = _extract(n, 'name', 1)
- playcount = _number(_extract(n, "playcount"))
-
- list.append( TopItem(Track(artist, title, *self.auth_data), playcount))
-
- return list
-
- def get_url(self, domain_name = DOMAIN_ENGLISH):
- """Returns the url of the event page on Last.fm.
- * domain_name: Last.fm's language domain. Possible values:
- o DOMAIN_ENGLISH
- o DOMAIN_GERMAN
- o DOMAIN_SPANISH
- o DOMAIN_FRENCH
- o DOMAIN_ITALIAN
- o DOMAIN_POLISH
- o DOMAIN_PORTUGUESE
- o DOMAIN_SWEDISH
- o DOMAIN_TURKISH
- o DOMAIN_RUSSIAN
- o DOMAIN_JAPANESE
- o DOMAIN_CHINESE
- """
-
- url = 'http://%(domain)s/place/%(country_name)s'
-
- country_name = _get_url_safe(self.get_name())
-
- return url %{'domain': domain_name, 'country_name': country_name}
+ """A country at Last.fm."""
+
+ name = None
+
+ def __init__(self, name, network):
+ _BaseObject.__init__(self, network)
+
+ self.name = name
+
+ @_string_output
+ def __repr__(self):
+ return self.get_name()
+
+ def __eq__(self, other):
+ return self.get_name().lower() == other.get_name().lower()
+
+ def __ne__(self, other):
+ return self.get_name() != other.get_name()
+
+ def _get_params(self):
+ return {'country': self.get_name()}
+
+ def _get_name_from_code(self, alpha2code):
+ # TODO: Have this function lookup the alpha-2 code and return the country name.
+
+ return alpha2code
+
+ def get_name(self):
+ """Returns the country name. """
+
+ return self.name
+
+ def get_top_artists(self):
+ """Returns a sequence of the most played artists."""
+
+ doc = self._request('geo.getTopArtists', True)
+
+ seq = []
+ for node in doc.getElementsByTagName("artist"):
+ name = _extract(node, 'name')
+ playcount = _extract(node, "playcount")
+
+ seq.append(TopItem(Artist(name, self.network), playcount))
+
+ return seq
+
+ def get_top_tracks(self):
+ """Returns a sequence of the most played tracks"""
+
+ doc = self._request("geo.getTopTracks", True)
+
+ seq = []
+
+ for n in doc.getElementsByTagName('track'):
+
+ title = _extract(n, 'name')
+ artist = _extract(n, 'name', 1)
+ playcount = _number(_extract(n, "playcount"))
+
+ seq.append( TopItem(Track(artist, title, self.network), playcount))
+
+ return seq
+
+ def get_url(self, domain_name = DOMAIN_ENGLISH):
+ """Returns the url of the event page on the network.
+ * domain_name: The network's language domain. Possible values:
+ o DOMAIN_ENGLISH
+ o DOMAIN_GERMAN
+ o DOMAIN_SPANISH
+ o DOMAIN_FRENCH
+ o DOMAIN_ITALIAN
+ o DOMAIN_POLISH
+ o DOMAIN_PORTUGUESE
+ o DOMAIN_SWEDISH
+ o DOMAIN_TURKISH
+ o DOMAIN_RUSSIAN
+ o DOMAIN_JAPANESE
+ o DOMAIN_CHINESE
+ """
+
+ country_name = _url_safe(self.get_name())
+
+ return self.network._get_url(domain_name, "country") %{'country_name': country_name}
class Library(_BaseObject):
- """A user's Last.fm library."""
-
- def __init__(self, user, api_key, api_secret, session_key):
- _BaseObject.__init__(self, api_key, api_secret, session_key)
-
- if isinstance(user, User):
- self.user = user
- else:
- self.user = User(user, *self.auth_data)
-
- self._albums_index = 0
- self._artists_index = 0
- self._tracks_index = 0
-
- def __repr__(self):
- return self.get_user().__repr__() + "'s Library"
-
- def _get_params(self):
- return {'user': self.user.get_name()}
-
- def get_user(self):
- """Returns the user who owns this library."""
-
- return self.user
-
- def add_album(self, album):
- """Add an album to this library."""
-
- params = self._get_params()
- params["artist"] = album.get_artist.get_name()
- params["album"] = album.get_name()
-
- self._request("library.addAlbum", False, params)
- info(repr(album) + " was added to " + repr(self))
-
- def add_artist(self, artist):
- """Add an artist to this library."""
-
- params = self._get_params()
- params["artist"] = artist.get_name()
-
- self._request("library.addArtist", False, params)
- info(repr(artist) + " was added to " + repr(self))
-
- def add_track(self, track):
- """Add a track to this library."""
-
- params = self._get_prams()
- params["track"] = track.get_title()
-
- self._request("library.addTrack", False, params)
- info(repr(track) + " was added to " + repr(self))
-
- def _get_albums_pagecount(self):
- """Returns the number of album pages in this library."""
-
- doc = self._request("library.getAlbums", True)
-
- return _number(doc.getElementsByTagName("albums")[0].getAttribute("totalPages"))
-
- def is_end_of_albums(self):
- """Returns True when the last page of albums has ben retrieved."""
-
- if self._albums_index >= self._get_albums_pagecount():
- return True
- else:
- return False
-
- def _get_artists_pagecount(self):
- """Returns the number of artist pages in this library."""
-
- doc = self._request("library.getArtists", True)
-
- return _number(doc.getElementsByTagName("artists")[0].getAttribute("totalPages"))
-
- def is_end_of_artists(self):
- """Returns True when the last page of artists has ben retrieved."""
-
- if self._artists_index >= self._get_artists_pagecount():
- return True
- else:
- return False
-
- def _get_tracks_pagecount(self):
- """Returns the number of track pages in this library."""
-
- doc = self._request("library.getTracks", True)
-
- return _number(doc.getElementsByTagName("tracks")[0].getAttribute("totalPages"))
-
- def is_end_of_tracks(self):
- """Returns True when the last page of tracks has ben retrieved."""
-
- if self._tracks_index >= self._get_tracks_pagecount():
- return True
- else:
- return False
-
- def get_albums_page(self):
- """Retreives the next page of albums in the Library. Returns a sequence of TopItem objects.
- Use the function extract_items like extract_items(Library.get_albums_page()) to return only a sequence of
- Album objects with no extra data.
-
- Example:
- -------
- library = Library("rj", API_KEY, API_SECRET, SESSION_KEY)
-
- while not library.is_end_of_albums():
- print library.get_albums_page()
- """
-
- self._albums_index += 1
-
- params = self._get_params()
- params["page"] = str(self._albums_index)
-
- list = []
- doc = self._request("library.getAlbums", True, params)
- for node in doc.getElementsByTagName("album"):
- name = _extract(node, "name")
- artist = _extract(node, "name", 1)
- playcount = _number(_extract(node, "playcount"))
- tagcount = _number(_extract(node, "tagcount"))
-
- list.append(LibraryItem(Album(artist, name, *self.auth_data), playcount, tagcount))
-
- return list
-
- def get_artists_page(self):
- """Retreives the next page of artists in the Library. Returns a sequence of TopItem objects.
- Use the function extract_items like extract_items(Library.get_artists_page()) to return only a sequence of
- Artist objects with no extra data.
-
- Example:
- -------
- library = Library("rj", API_KEY, API_SECRET, SESSION_KEY)
-
- while not library.is_end_of_artists():
- print library.get_artists_page()
- """
-
- self._artists_index += 1
-
- params = self._get_params()
- params["page"] = str(self._artists_index)
-
- list = []
- doc = self._request("library.getArtists", True, params)
- for node in doc.getElementsByTagName("artist"):
- name = _extract(node, "name")
-
- playcount = _number(_extract(node, "playcount"))
- tagcount = _number(_extract(node, "tagcount"))
-
- list.append(LibraryItem(Artist(name, *self.auth_data), playcount, tagcount))
-
- return list
-
- def get_tracks_page(self):
- """Retreives the next page of tracks in the Library. Returns a sequence of TopItem objects.
- Use the function extract_items like extract_items(Library.get_tracks_page()) to return only a sequence of
- Track objects with no extra data.
-
- Example:
- -------
- library = Library("rj", API_KEY, API_SECRET, SESSION_KEY)
-
- while not library.is_end_of_tracks():
- print library.get_tracks_page()
- """
-
- self._tracks_index += 1
-
- params = self._get_params()
- params["page"] = str(self._tracks_index)
-
- list = []
- doc = self._request("library.getTracks", True, params)
- for node in doc.getElementsByTagName("track"):
- name = _extract(node, "name")
- artist = _extract(node, "name", 1)
- playcount = _number(_extract(node, "playcount"))
- tagcount = _number(_extract(node, "tagcount"))
-
- list.append(LibraryItem(Track(artist, name, *self.auth_data), playcount, tagcount))
-
- return list
-
+ """A user's Last.fm library."""
+
+ user = None
+
+ def __init__(self, user, network):
+ _BaseObject.__init__(self, network)
+
+ if isinstance(user, User):
+ self.user = user
+ else:
+ self.user = User(user, self.network)
+
+ self._albums_index = 0
+ self._artists_index = 0
+ self._tracks_index = 0
+
+ @_string_output
+ def __repr__(self):
+ return repr(self.get_user()) + "'s Library"
+
+ def _get_params(self):
+ return {'user': self.user.get_name()}
+
+ def get_user(self):
+ """Returns the user who owns this library."""
+
+ return self.user
+
+ def add_album(self, album):
+ """Add an album to this library."""
+
+ params = self._get_params()
+ params["artist"] = album.get_artist.get_name()
+ params["album"] = album.get_name()
+
+ self._request("library.addAlbum", False, params)
+
+ def add_artist(self, artist):
+ """Add an artist to this library."""
+
+ params = self._get_params()
+ params["artist"] = artist.get_name()
+
+ self._request("library.addArtist", False, params)
+
+ def add_track(self, track):
+ """Add a track to this library."""
+
+ params = self._get_params()
+ params["track"] = track.get_title()
+
+ self._request("library.addTrack", False, params)
+
+ def get_albums(self, limit=50):
+ """
+ Returns a sequence of Album objects
+ if limit==None it will return all (may take a while)
+ """
+
+ seq = []
+ for node in _collect_nodes(limit, self, "library.getAlbums", True):
+ name = _extract(node, "name")
+ artist = _extract(node, "name", 1)
+ playcount = _number(_extract(node, "playcount"))
+ tagcount = _number(_extract(node, "tagcount"))
+
+ seq.append(LibraryItem(Album(artist, name, self.network), playcount, tagcount))
+
+ return seq
+
+ def get_artists(self, limit=50):
+ """
+ Returns a sequence of Album objects
+ if limit==None it will return all (may take a while)
+ """
+
+ seq = []
+ for node in _collect_nodes(limit, self, "library.getArtists", True):
+ name = _extract(node, "name")
+
+ playcount = _number(_extract(node, "playcount"))
+ tagcount = _number(_extract(node, "tagcount"))
+
+ seq.append(LibraryItem(Artist(name, self.network), playcount, tagcount))
+
+ return seq
+
+ def get_tracks(self, limit=50):
+ """
+ Returns a sequence of Album objects
+ if limit==None it will return all (may take a while)
+ """
+
+ seq = []
+ for node in _collect_nodes(limit, self, "library.getTracks", True):
+ name = _extract(node, "name")
+ artist = _extract(node, "name", 1)
+ playcount = _number(_extract(node, "playcount"))
+ tagcount = _number(_extract(node, "tagcount"))
+
+ seq.append(LibraryItem(Track(artist, name, self.network), playcount, tagcount))
+
+ return seq
+
class Playlist(_BaseObject):
- """A Last.fm user playlist."""
-
- def __init__(self, user, id, api_key, api_secret, session_key):
- _BaseObject.__init__(self, api_key, api_secret, session_key)
-
- if isinstance(user, User):
- self.user = user
- else:
- self.user = User(user, *self.auth_data)
-
- self.id = unicode(id)
-
- def __repr__(self):
- return repr(self.user) + "'s playlist # " + repr(self.id)
-
- def _get_info_node(self):
- """Returns the node from user.getPlaylists where this playlist's info is."""
-
- doc = self._request("user.getPlaylists", True)
-
- for node in doc.getElementsByTagName("playlist"):
- if _extract(node, "id") == str(self.get_id()):
- return node
-
- def _get_params(self):
- return {'user': self.user.get_name(), 'playlistID': self.get_id()}
-
- def get_id(self):
- """Returns the playlist id."""
-
- return self.id
-
- def get_user(self):
- """Returns the owner user of this playlist."""
-
- return self.user
-
- def get_tracks(self):
- """Returns a list of the tracks on this user playlist."""
-
- uri = u'lastfm://playlist/%s' %self.get_id()
-
- return XSPF(uri, *self.auth_data).get_tracks()
-
- def add_track(self, track):
- """Adds a Track to this Playlist."""
-
- params = self._get_params()
- params['artist'] = track.get_artist().get_name()
- params['track'] = track.get_title()
-
- self._request('playlist.addTrack', False, params)
- info(repr(track) + " was added to " + repr(self))
-
- def get_title(self):
- """Returns the title of this playlist."""
-
- return _extract(self._get_info_node(), "title")
-
- def get_creation_date(self):
- """Returns the creation date of this playlist."""
-
- return _extract(self._get_info_node(), "date")
-
- def get_size(self):
- """Returns the number of tracks in this playlist."""
-
- return _number(_extract(self._get_info_node(), "size"))
-
- def get_description(self):
- """Returns the description of this playlist."""
-
- return _extract(self._get_info_node(), "description")
-
- def get_duration(self):
- """Returns the duration of this playlist in milliseconds."""
-
- return _number(_extract(self._get_info_node(), "duration"))
-
- def is_streamable(self):
- """Returns True if the playlist is streamable.
- For a playlist to be streamable, it needs at least 45 tracks by 15 different artists."""
-
- if _extract(self._get_info_node(), "streamable") == '1':
- return True
- else:
- return False
-
- def has_track(self, track):
- """Checks to see if track is already in the playlist.
- * track: Any Track object.
- """
-
- return track in self.get_tracks()
-
- def get_image_url(self, size = IMAGE_LARGE):
- """Returns the associated image URL.
- * size: The image size. Possible values:
- o IMAGE_LARGE
- o IMAGE_MEDIUM
- o IMAGE_SMALL
- """
-
- return _extract(self._get_info_node(), "image")[size]
-
- def get_url(self, domain_name = DOMAIN_ENGLISH):
- """Returns the url of the playlist on Last.fm.
- * domain_name: Last.fm's language domain. Possible values:
- o DOMAIN_ENGLISH
- o DOMAIN_GERMAN
- o DOMAIN_SPANISH
- o DOMAIN_FRENCH
- o DOMAIN_ITALIAN
- o DOMAIN_POLISH
- o DOMAIN_PORTUGUESE
- o DOMAIN_SWEDISH
- o DOMAIN_TURKISH
- o DOMAIN_RUSSIAN
- o DOMAIN_JAPANESE
- o DOMAIN_CHINESE
- """
- url = "http://%(domain)s/user/%(user)s/library/playlists/%(appendix)s"
-
- english_url = _extract(self._get_info_node(), "url")
- appendix = english_url[english_url.rfind("/") + 1:]
-
- return url %{'domain': domain_name, 'appendix': appendix, "user": self.get_user().get_name()}
-
+ """A Last.fm user playlist."""
+
+ id = None
+ user = None
+
+ def __init__(self, user, id, network):
+ _BaseObject.__init__(self, network)
+
+ if isinstance(user, User):
+ self.user = user
+ else:
+ self.user = User(user, self.network)
+
+ self.id = _unicode(id)
+
+ @_string_output
+ def __repr__(self):
+ return repr(self.user) + "'s playlist # " + repr(self.id)
+
+ def _get_info_node(self):
+ """Returns the node from user.getPlaylists where this playlist's info is."""
+
+ doc = self._request("user.getPlaylists", True)
+
+ for node in doc.getElementsByTagName("playlist"):
+ if _extract(node, "id") == str(self.get_id()):
+ return node
+
+ def _get_params(self):
+ return {'user': self.user.get_name(), 'playlistID': self.get_id()}
+
+ def get_id(self):
+ """Returns the playlist id."""
+
+ return self.id
+
+ def get_user(self):
+ """Returns the owner user of this playlist."""
+
+ return self.user
+
+ def get_tracks(self):
+ """Returns a list of the tracks on this user playlist."""
+
+ uri = u'lastfm://playlist/%s' %self.get_id()
+
+ return XSPF(uri, self.network).get_tracks()
+
+ def add_track(self, track):
+ """Adds a Track to this Playlist."""
+
+ params = self._get_params()
+ params['artist'] = track.get_artist().get_name()
+ params['track'] = track.get_title()
+
+ self._request('playlist.addTrack', False, params)
+
+ def get_title(self):
+ """Returns the title of this playlist."""
+
+ return _extract(self._get_info_node(), "title")
+
+ def get_creation_date(self):
+ """Returns the creation date of this playlist."""
+
+ return _extract(self._get_info_node(), "date")
+
+ def get_size(self):
+ """Returns the number of tracks in this playlist."""
+
+ return _number(_extract(self._get_info_node(), "size"))
+
+ def get_description(self):
+ """Returns the description of this playlist."""
+
+ return _extract(self._get_info_node(), "description")
+
+ def get_duration(self):
+ """Returns the duration of this playlist in milliseconds."""
+
+ return _number(_extract(self._get_info_node(), "duration"))
+
+ def is_streamable(self):
+ """Returns True if the playlist is streamable.
+ For a playlist to be streamable, it needs at least 45 tracks by 15 different artists."""
+
+ if _extract(self._get_info_node(), "streamable") == '1':
+ return True
+ else:
+ return False
+
+ def has_track(self, track):
+ """Checks to see if track is already in the playlist.
+ * track: Any Track object.
+ """
+
+ return track in self.get_tracks()
+
+ def get_cover_image(self, size = COVER_LARGE):
+ """
+ Returns a uri to the cover image
+ size can be one of:
+ COVER_MEGA
+ COVER_EXTRA_LARGE
+ COVER_LARGE
+ COVER_MEDIUM
+ COVER_SMALL
+ """
+
+ return _extract(self._get_info_node(), "image")[size]
+
+ def get_url(self, domain_name = DOMAIN_ENGLISH):
+ """Returns the url of the playlist on the network.
+ * domain_name: The network's language domain. Possible values:
+ o DOMAIN_ENGLISH
+ o DOMAIN_GERMAN
+ o DOMAIN_SPANISH
+ o DOMAIN_FRENCH
+ o DOMAIN_ITALIAN
+ o DOMAIN_POLISH
+ o DOMAIN_PORTUGUESE
+ o DOMAIN_SWEDISH
+ o DOMAIN_TURKISH
+ o DOMAIN_RUSSIAN
+ o DOMAIN_JAPANESE
+ o DOMAIN_CHINESE
+ """
+
+ english_url = _extract(self._get_info_node(), "url")
+ appendix = english_url[english_url.rfind("/") + 1:]
+
+ return self.network._get_url(domain_name, "playlist") %{'appendix': appendix, "user": self.get_user().get_name()}
+
class Tag(_BaseObject):
- """A Last.fm object tag."""
-
- # TODO: getWeeklyArtistChart (too lazy, i'll wait for when someone requests it)
-
- def __init__(self, name, api_key, api_secret, session_key):
- _BaseObject.__init__(self, api_key, api_secret, session_key)
-
- self.name = name
-
- def _get_params(self):
- return {'tag': self.get_name()}
-
- def __repr__(self):
- return self.get_name()
-
- def __eq__(self):
- return self.get_name().lower() == other.get_name().lower()
-
- def __ne__(self):
- return self.get_name().lower() != other.get_name().lower()
-
- def get_name(self):
- """Returns the name of the tag. """
-
- return self.name
-
- def get_similar(self):
- """Returns the tags similar to this one, ordered by similarity. """
-
- doc = self._request('tag.getSimilar', True)
-
- list = []
- names = _extract_all(doc, 'name')
- for name in names:
- list.append(Tag(name, *self.auth_data))
-
- return list
-
- def get_top_albums(self):
- """Retuns a list of the top albums."""
-
- doc = self._request('tag.getTopAlbums', True)
-
- list = []
-
- for node in doc.getElementsByTagName("album"):
- name = _extract(node, "name")
- artist = _extract(node, "name", 1)
- playcount = _extract(node, "playcount")
-
- list.append(TopItem(Album(artist, name, *self.auth_data), playcount))
-
- return list
-
- def get_top_tracks(self):
- """Returns a list of the most played Tracks by this artist."""
-
- doc = self._request("tag.getTopTracks", True)
-
- list = []
- for track in doc.getElementsByTagName('track'):
-
- title = _extract(track, "name")
- artist = _extract(track, "name", 1)
- playcount = _number(_extract(track, "playcount"))
-
- list.append( TopItem(Track(artist, title, *self.auth_data), playcount) )
-
- return list
-
- def get_top_artists(self):
- """Returns a sequence of the most played artists."""
-
- doc = self._request('tag.getTopArtists', True)
-
- list = []
- for node in doc.getElementsByTagName("artist"):
- name = _extract(node, 'name')
- playcount = _extract(node, "playcount")
-
- list.append(TopItem(Artist(name, *self.auth_data), playcount))
-
- return list
-
- def get_weekly_chart_dates(self):
- """Returns a list of From and To tuples for the available charts."""
-
- doc = self._request("tag.getWeeklyChartList", True)
-
- list = []
- for node in doc.getElementsByTagName("chart"):
- list.append( (node.getAttribute("from"), node.getAttribute("to")) )
-
- return list
-
- def get_weekly_artist_charts(self, from_date = None, to_date = None):
- """Returns the weekly artist charts for the week starting from the from_date value to the to_date value."""
-
- params = self._get_params()
- if from_date and to_date:
- params["from"] = from_date
- params["to"] = to_date
-
- doc = self._request("tag.getWeeklyArtistChart", True, params)
-
- list = []
- for node in doc.getElementsByTagName("artist"):
- item = Artist(_extract(node, "name"), *self.auth_data)
- weight = _extract(node, "weight")
- list.append(TopItem(item, weight))
-
- return list
-
- def get_url(self, domain_name = DOMAIN_ENGLISH):
- """Returns the url of the tag page on Last.fm.
- * domain_name: Last.fm's language domain. Possible values:
- o DOMAIN_ENGLISH
- o DOMAIN_GERMAN
- o DOMAIN_SPANISH
- o DOMAIN_FRENCH
- o DOMAIN_ITALIAN
- o DOMAIN_POLISH
- o DOMAIN_PORTUGUESE
- o DOMAIN_SWEDISH
- o DOMAIN_TURKISH
- o DOMAIN_RUSSIAN
- o DOMAIN_JAPANESE
- o DOMAIN_CHINESE
- """
-
- url = 'http://%(domain)s/tag/%(name)s'
-
- name = _get_url_safe(self.get_name())
-
- return url %{'domain': domain_name, 'name': name}
+ """A Last.fm object tag."""
+
+ # TODO: getWeeklyArtistChart (too lazy, i'll wait for when someone requests it)
+
+ name = None
+
+ def __init__(self, name, network):
+ _BaseObject.__init__(self, network)
+
+ self.name = name
+
+ def _get_params(self):
+ return {'tag': self.get_name()}
+
+ @_string_output
+ def __repr__(self):
+ return self.get_name()
+
+ def __eq__(self, other):
+ return self.get_name().lower() == other.get_name().lower()
+
+ def __ne__(self, other):
+ return self.get_name().lower() != other.get_name().lower()
+
+ def get_name(self):
+ """Returns the name of the tag. """
+
+ return self.name
+
+ def get_similar(self):
+ """Returns the tags similar to this one, ordered by similarity. """
+
+ doc = self._request('tag.getSimilar', True)
+
+ seq = []
+ names = _extract_all(doc, 'name')
+ for name in names:
+ seq.append(Tag(name, self.network))
+
+ return seq
+
+ def get_top_albums(self):
+ """Retuns a list of the top albums."""
+
+ doc = self._request('tag.getTopAlbums', True)
+
+ seq = []
+
+ for node in doc.getElementsByTagName("album"):
+ name = _extract(node, "name")
+ artist = _extract(node, "name", 1)
+ playcount = _extract(node, "playcount")
+
+ seq.append(TopItem(Album(artist, name, self.network), playcount))
+
+ return seq
+
+ def get_top_tracks(self):
+ """Returns a list of the most played Tracks by this artist."""
+
+ doc = self._request("tag.getTopTracks", True)
+
+ seq = []
+ for track in doc.getElementsByTagName('track'):
+
+ title = _extract(track, "name")
+ artist = _extract(track, "name", 1)
+ playcount = _number(_extract(track, "playcount"))
+
+ seq.append( TopItem(Track(artist, title, self.network), playcount) )
+
+ return seq
+
+ def get_top_artists(self):
+ """Returns a sequence of the most played artists."""
+
+ doc = self._request('tag.getTopArtists', True)
+
+ seq = []
+ for node in doc.getElementsByTagName("artist"):
+ name = _extract(node, 'name')
+ playcount = _extract(node, "playcount")
+
+ seq.append(TopItem(Artist(name, self.network), playcount))
+
+ return seq
+
+ def get_weekly_chart_dates(self):
+ """Returns a list of From and To tuples for the available charts."""
+
+ doc = self._request("tag.getWeeklyChartList", True)
+
+ seq = []
+ for node in doc.getElementsByTagName("chart"):
+ seq.append( (node.getAttribute("from"), node.getAttribute("to")) )
+
+ return seq
+
+ def get_weekly_artist_charts(self, from_date = None, to_date = None):
+ """Returns the weekly artist charts for the week starting from the from_date value to the to_date value."""
+
+ params = self._get_params()
+ if from_date and to_date:
+ params["from"] = from_date
+ params["to"] = to_date
+
+ doc = self._request("tag.getWeeklyArtistChart", True, params)
+
+ seq = []
+ for node in doc.getElementsByTagName("artist"):
+ item = Artist(_extract(node, "name"), self.network)
+ weight = _number(_extract(node, "weight"))
+ seq.append(TopItem(item, weight))
+
+ return seq
+
+ def get_url(self, domain_name = DOMAIN_ENGLISH):
+ """Returns the url of the tag page on the network.
+ * domain_name: The network's language domain. Possible values:
+ o DOMAIN_ENGLISH
+ o DOMAIN_GERMAN
+ o DOMAIN_SPANISH
+ o DOMAIN_FRENCH
+ o DOMAIN_ITALIAN
+ o DOMAIN_POLISH
+ o DOMAIN_PORTUGUESE
+ o DOMAIN_SWEDISH
+ o DOMAIN_TURKISH
+ o DOMAIN_RUSSIAN
+ o DOMAIN_JAPANESE
+ o DOMAIN_CHINESE
+ """
+
+ name = _url_safe(self.get_name())
+
+ return self.network._get_url(domain_name, "tag") %{'name': name}
class Track(_BaseObject, _Taggable):
- """A Last.fm track."""
-
- def __init__(self, artist, title, api_key, api_secret, session_key):
- _BaseObject.__init__(self, api_key, api_secret, session_key)
- _Taggable.__init__(self, 'track')
-
- if isinstance(artist, Artist):
- self.artist = artist
- else:
- self.artist = Artist(artist, *self.auth_data)
-
- self.title = title
-
- def __repr__(self):
- return self.get_artist().get_name() + ' - ' + self.get_title()
-
- def __eq__(self, other):
- return (self.get_title().lower() == other.get_title().lower()) and (self.get_artist().get_name().lower() == other.get_artist().get_name().lower())
-
- def __ne__(self, other):
- return (self.get_title().lower() != other.get_title().lower()) or (self.get_artist().get_name().lower() != other.get_artist().get_name().lower())
-
- def _get_params(self):
- return {'artist': self.get_artist().get_name(), 'track': self.get_title()}
-
- def get_artist(self):
- """Returns the associated Artist object."""
-
- return self.artist
-
- def get_title(self):
- """Returns the track title."""
-
- return self.title
-
- def get_name(self):
- """Returns the track title (alias to Track.get_title)."""
-
- return self.get_title()
-
- def get_id(self):
- """Returns the track id on Last.fm."""
-
- doc = self._request("track.getInfo", True)
-
- return _extract(doc, "id")
-
- def get_duration(self):
- """Returns the track duration."""
-
- doc = self._request("track.getInfo", True)
-
- return _number(_extract(doc, "duration"))
-
- def get_mbid(self):
- """Returns the MusicBrainz ID of this track."""
-
- doc = self._request("track.getInfo", True)
-
- return _extract(doc, "mbid")
-
- def get_listener_count(self):
- """Returns the listener count."""
-
- doc = self._request("track.getInfo", True)
-
- return _number(_extract(doc, "listeners"))
-
- def get_playcount(self):
- """Returns the play count."""
-
- doc = self._request("track.getInfo", True)
- return _number(_extract(doc, "playcount"))
-
- def get_top_tags(self):
- """Returns the play count."""
-
- doc = self._request("track.getInfo", True)
-
- list = []
-
- elements = doc.getElementsByTagName('tag')
-
- for element in elements:
-
- name = _extract(element, 'name')
-
- list.append(name)
-
- return list
-
- def is_streamable(self):
- """Returns True if the track is available at Last.fm."""
-
- doc = self._request("track.getInfo", True)
- return _extract(doc, "streamable") == "1"
-
- def is_fulltrack_available(self):
- """Returns True if the fulltrack is available for streaming."""
-
- doc = self._request("track.getInfo", True)
- return doc.getElementsByTagName("streamable")[0].getAttribute("fulltrack") == "1"
-
- def get_album(self):
- """Returns the album object of this track."""
-
- doc = self._request("track.getInfo", True)
-
- albums = doc.getElementsByTagName("album")
-
- if len(albums) == 0:
- return
-
- node = doc.getElementsByTagName("album")[0]
- return Album(_extract(node, "artist"), _extract(node, "title"), *self.auth_data)
-
- def get_wiki_published_date(self):
- """Returns the date of publishing this version of the wiki."""
-
- doc = self._request("track.getInfo", True)
-
- if len(doc.getElementsByTagName("wiki")) == 0:
- return
-
- node = doc.getElementsByTagName("wiki")[0]
-
- return _extract(node, "published")
-
- def get_wiki_summary(self):
- """Returns the summary of the wiki."""
-
- doc = self._request("track.getInfo", True)
-
- if len(doc.getElementsByTagName("wiki")) == 0:
- return
-
- node = doc.getElementsByTagName("wiki")[0]
-
- return _extract(node, "summary")
-
- def get_wiki_content(self):
- """Returns the content of the wiki."""
-
- doc = self._request("track.getInfo", True)
-
- if len(doc.getElementsByTagName("wiki")) == 0:
- return
-
- node = doc.getElementsByTagName("wiki")[0]
-
- return _extract(node, "content")
-
- def love(self):
- """Adds the track to the user's loved tracks. """
-
- self._request('track.love')
-
- def ban(self):
- """Ban this track from ever playing on the radio. """
-
- self._request('track.ban')
-
- def get_similar(self):
- """Returns similar tracks for this track on Last.fm, based on listening data. """
-
- doc = self._request('track.getSimilar', True)
-
- list = []
- for node in doc.getElementsByTagName("track"):
- title = _extract(node, 'name')
- artist = _extract(node, 'name', 1)
-
- list.append(Track(artist, title, *self.auth_data))
-
- return list
-
- def get_top_fans(self, limit = None):
- """Returns a list of the Users who played this track."""
-
- doc = self._request('track.getTopFans', True)
-
- list = []
-
- elements = doc.getElementsByTagName('user')
-
- for element in elements:
- if limit and len(list) >= limit:
- break
-
- name = _extract(element, 'name')
- weight = _number(_extract(element, 'weight'))
-
- list.append(TopItem(User(name, *self.auth_data), weight))
-
- return list
-
- def share(self, users, message = None):
- """Shares this track (sends out recommendations).
- * users: A list that can contain usernames, emails, User objects, or all of them.
- * message: A message to include in the recommendation message.
- """
-
- #last.fm currently accepts a max of 10 recipient at a time
- while(len(users) > 10):
- section = users[0:9]
- users = users[9:]
- self.share(section, message)
-
- nusers = []
- for user in users:
- if isinstance(user, User):
- nusers.append(user.get_name())
- else:
- nusers.append(user)
-
- params = self._get_params()
- recipients = ','.join(nusers)
- params['recipient'] = recipients
- if message: params['message'] = unicode(message)
-
- self._request('track.share', False, params)
-
- def get_url(self, domain_name = DOMAIN_ENGLISH):
- """Returns the url of the track page on Last.fm.
- * domain_name: Last.fm's language domain. Possible values:
- o DOMAIN_ENGLISH
- o DOMAIN_GERMAN
- o DOMAIN_SPANISH
- o DOMAIN_FRENCH
- o DOMAIN_ITALIAN
- o DOMAIN_POLISH
- o DOMAIN_PORTUGUESE
- o DOMAIN_SWEDISH
- o DOMAIN_TURKISH
- o DOMAIN_RUSSIAN
- o DOMAIN_JAPANESE
- o DOMAIN_CHINESE
- """
- url = 'http://%(domain)s/music/%(artist)s/_/%(title)s'
-
- artist = _get_url_safe(self.get_artist().get_name())
- title = _get_url_safe(self.get_title())
-
- return url %{'domain': domain_name, 'artist': artist, 'title': title}
-
+ """A Last.fm track."""
+
+ artist = None
+ title = None
+
+ def __init__(self, artist, title, network):
+ _BaseObject.__init__(self, network)
+ _Taggable.__init__(self, 'track')
+
+ if isinstance(artist, Artist):
+ self.artist = artist
+ else:
+ self.artist = Artist(artist, self.network)
+
+ self.title = title
+
+ @_string_output
+ def __repr__(self):
+ return self.get_artist().get_name() + ' - ' + self.get_title()
+
+ def __eq__(self, other):
+ return (self.get_title().lower() == other.get_title().lower()) and (self.get_artist().get_name().lower() == other.get_artist().get_name().lower())
+
+ def __ne__(self, other):
+ return (self.get_title().lower() != other.get_title().lower()) or (self.get_artist().get_name().lower() != other.get_artist().get_name().lower())
+
+ def _get_params(self):
+ return {'artist': self.get_artist().get_name(), 'track': self.get_title()}
+
+ def get_artist(self):
+ """Returns the associated Artist object."""
+
+ return self.artist
+
+ def get_title(self):
+ """Returns the track title."""
+
+ return self.title
+
+ def get_name(self):
+ """Returns the track title (alias to Track.get_title)."""
+
+ return self.get_title()
+
+ def get_id(self):
+ """Returns the track id on the network."""
+
+ doc = self._request("track.getInfo", True)
+
+ return _extract(doc, "id")
+
+ def get_duration(self):
+ """Returns the track duration."""
+
+ doc = self._request("track.getInfo", True)
+
+ return _number(_extract(doc, "duration"))
+
+ def get_mbid(self):
+ """Returns the MusicBrainz ID of this track."""
+
+ doc = self._request("track.getInfo", True)
+
+ return _extract(doc, "mbid")
+
+ def get_listener_count(self):
+ """Returns the listener count."""
+
+ doc = self._request("track.getInfo", True)
+
+ return _number(_extract(doc, "listeners"))
+
+ def get_playcount(self):
+ """Returns the play count."""
+
+ doc = self._request("track.getInfo", True)
+ return _number(_extract(doc, "playcount"))
+
+ def is_streamable(self):
+ """Returns True if the track is available at Last.fm."""
+
+ doc = self._request("track.getInfo", True)
+ return _extract(doc, "streamable") == "1"
+
+ def is_fulltrack_available(self):
+ """Returns True if the fulltrack is available for streaming."""
+
+ doc = self._request("track.getInfo", True)
+ return doc.getElementsByTagName("streamable")[0].getAttribute("fulltrack") == "1"
+
+ def get_album(self):
+ """Returns the album object of this track."""
+
+ doc = self._request("track.getInfo", True)
+
+ albums = doc.getElementsByTagName("album")
+
+ if len(albums) == 0:
+ return
+
+ node = doc.getElementsByTagName("album")[0]
+ return Album(_extract(node, "artist"), _extract(node, "title"), self.network)
+
+ def get_wiki_published_date(self):
+ """Returns the date of publishing this version of the wiki."""
+
+ doc = self._request("track.getInfo", True)
+
+ if len(doc.getElementsByTagName("wiki")) == 0:
+ return
+
+ node = doc.getElementsByTagName("wiki")[0]
+
+ return _extract(node, "published")
+
+ def get_wiki_summary(self):
+ """Returns the summary of the wiki."""
+
+ doc = self._request("track.getInfo", True)
+
+ if len(doc.getElementsByTagName("wiki")) == 0:
+ return
+
+ node = doc.getElementsByTagName("wiki")[0]
+
+ return _extract(node, "summary")
+
+ def get_wiki_content(self):
+ """Returns the content of the wiki."""
+
+ doc = self._request("track.getInfo", True)
+
+ if len(doc.getElementsByTagName("wiki")) == 0:
+ return
+
+ node = doc.getElementsByTagName("wiki")[0]
+
+ return _extract(node, "content")
+
+ def love(self):
+ """Adds the track to the user's loved tracks. """
+
+ self._request('track.love')
+
+ def ban(self):
+ """Ban this track from ever playing on the radio. """
+
+ self._request('track.ban')
+
+ def get_similar(self):
+ """Returns similar tracks for this track on the network, based on listening data. """
+
+ doc = self._request('track.getSimilar', True)
+
+ seq = []
+ for node in doc.getElementsByTagName("track"):
+ title = _extract(node, 'name')
+ artist = _extract(node, 'name', 1)
+ match = _number(_extract(node, "match"))
+
+ seq.append(SimilarItem(Track(artist, title, self.network), match))
+
+ return seq
+
+ def get_top_fans(self, limit = None):
+ """Returns a list of the Users who played this track."""
+
+ doc = self._request('track.getTopFans', True)
+
+ seq = []
+
+ elements = doc.getElementsByTagName('user')
+
+ for element in elements:
+ if limit and len(seq) >= limit:
+ break
+
+ name = _extract(element, 'name')
+ weight = _number(_extract(element, 'weight'))
+
+ seq.append(TopItem(User(name, self.network), weight))
+
+ return seq
+
+ def share(self, users, message = None):
+ """Shares this track (sends out recommendations).
+ * users: A list that can contain usernames, emails, User objects, or all of them.
+ * message: A message to include in the recommendation message.
+ """
+
+ #last.fm currently accepts a max of 10 recipient at a time
+ while(len(users) > 10):
+ section = users[0:9]
+ users = users[9:]
+ self.share(section, message)
+
+ nusers = []
+ for user in users:
+ if isinstance(user, User):
+ nusers.append(user.get_name())
+ else:
+ nusers.append(user)
+
+ params = self._get_params()
+ recipients = ','.join(nusers)
+ params['recipient'] = recipients
+ if message: params['message'] = _unicode(message)
+
+ self._request('track.share', False, params)
+
+ def get_url(self, domain_name = DOMAIN_ENGLISH):
+ """Returns the url of the track page on the network.
+ * domain_name: The network's language domain. Possible values:
+ o DOMAIN_ENGLISH
+ o DOMAIN_GERMAN
+ o DOMAIN_SPANISH
+ o DOMAIN_FRENCH
+ o DOMAIN_ITALIAN
+ o DOMAIN_POLISH
+ o DOMAIN_PORTUGUESE
+ o DOMAIN_SWEDISH
+ o DOMAIN_TURKISH
+ o DOMAIN_RUSSIAN
+ o DOMAIN_JAPANESE
+ o DOMAIN_CHINESE
+ """
+
+ artist = _url_safe(self.get_artist().get_name())
+ title = _url_safe(self.get_title())
+
+ return self.network._get_url(domain_name, "track") %{'domain': self.network._get_language_domain(domain_name), 'artist': artist, 'title': title}
+
+ def get_shouts(self, limit=50):
+ """
+ Returns a sequqence of Shout objects
+ """
+
+ shouts = []
+ for node in _collect_nodes(limit, self, "track.getShouts", False):
+ shouts.append(Shout(
+ _extract(node, "body"),
+ User(_extract(node, "author"), self.network),
+ _extract(node, "date")
+ )
+ )
+ return shouts
+
+ def shout(self, message):
+ """
+ Post a shout
+ """
+
+ params = self._get_params()
+ params["message"] = message
+
+ self._request("track.Shout", False, params)
+
class Group(_BaseObject):
- """A Last.fm group."""
-
- def __init__(self, group_name, api_key, api_secret, session_key):
- _BaseObject.__init__(self, api_key, api_secret, session_key)
-
- self.name = group_name
-
- def __repr__(self):
- return self.get_name()
-
- def __eq__(self, other):
- return self.get_name().lower() == other.get_name().lower()
-
- def __ne__(self, other):
- return self.get_name() != other.get_name()
-
- def _get_params(self):
- return {'group': self.get_name()}
-
- def get_name(self):
- """Returns the group name. """
- return self.name
-
- def get_weekly_chart_dates(self):
- """Returns a list of From and To tuples for the available charts."""
-
- doc = self._request("group.getWeeklyChartList", True)
-
- list = []
- for node in doc.getElementsByTagName("chart"):
- list.append( (node.getAttribute("from"), node.getAttribute("to")) )
-
- return list
-
- def get_weekly_artist_charts(self, from_date = None, to_date = None):
- """Returns the weekly artist charts for the week starting from the from_date value to the to_date value."""
-
- params = self._get_params()
- if from_date and to_date:
- params["from"] = from_date
- params["to"] = to_date
-
- doc = self._request("group.getWeeklyArtistChart", True, params)
-
- list = []
- for node in doc.getElementsByTagName("artist"):
- item = Artist(_extract(node, "name"), *self.auth_data)
- weight = _extract(node, "playcount")
- list.append(TopItem(item, weight))
-
- return list
-
- def get_weekly_album_charts(self, from_date = None, to_date = None):
- """Returns the weekly album charts for the week starting from the from_date value to the to_date value."""
-
- params = self._get_params()
- if from_date and to_date:
- params["from"] = from_date
- params["to"] = to_date
-
- doc = self._request("group.getWeeklyAlbumChart", True, params)
-
- list = []
- for node in doc.getElementsByTagName("album"):
- item = Album(_extract(node, "artist"), _extract(node, "name"), *self.auth_data)
- weight = _extract(node, "playcount")
- list.append(TopItem(item, weight))
-
- return list
-
- def get_weekly_track_charts(self, from_date = None, to_date = None):
- """Returns the weekly track charts for the week starting from the from_date value to the to_date value."""
-
- params = self._get_params()
- if from_date and to_date:
- params["from"] = from_date
- params["to"] = to_date
-
- doc = self._request("group.getWeeklyTrackChart", True, params)
-
- list = []
- for node in doc.getElementsByTagName("track"):
- item = Track(_extract(node, "artist"), _extract(node, "name"), *self.auth_data)
- weight = _extract(node, "playcount")
- list.append(TopItem(item, weight))
-
- return list
-
- def get_url(self, domain_name = DOMAIN_ENGLISH):
- """Returns the url of the group page on Last.fm.
- * domain_name: Last.fm's language domain. Possible values:
- o DOMAIN_ENGLISH
- o DOMAIN_GERMAN
- o DOMAIN_SPANISH
- o DOMAIN_FRENCH
- o DOMAIN_ITALIAN
- o DOMAIN_POLISH
- o DOMAIN_PORTUGUESE
- o DOMAIN_SWEDISH
- o DOMAIN_TURKISH
- o DOMAIN_RUSSIAN
- o DOMAIN_JAPANESE
- o DOMAIN_CHINESE
- """
-
- url = 'http://%(domain)s/group/%(name)s'
-
- name = _get_url_safe(self.get_name())
-
- return url %{'domain': domain_name, 'name': name}
+ """A Last.fm group."""
+
+ name = None
+
+ def __init__(self, group_name, network):
+ _BaseObject.__init__(self, network)
+
+ self.name = group_name
+
+ @_string_output
+ def __repr__(self):
+ return self.get_name()
+
+ def __eq__(self, other):
+ return self.get_name().lower() == other.get_name().lower()
+
+ def __ne__(self, other):
+ return self.get_name() != other.get_name()
+
+ def _get_params(self):
+ return {'group': self.get_name()}
+
+ def get_name(self):
+ """Returns the group name. """
+ return self.name
+
+ def get_weekly_chart_dates(self):
+ """Returns a list of From and To tuples for the available charts."""
+
+ doc = self._request("group.getWeeklyChartList", True)
+
+ seq = []
+ for node in doc.getElementsByTagName("chart"):
+ seq.append( (node.getAttribute("from"), node.getAttribute("to")) )
+
+ return seq
+
+ def get_weekly_artist_charts(self, from_date = None, to_date = None):
+ """Returns the weekly artist charts for the week starting from the from_date value to the to_date value."""
+
+ params = self._get_params()
+ if from_date and to_date:
+ params["from"] = from_date
+ params["to"] = to_date
+
+ doc = self._request("group.getWeeklyArtistChart", True, params)
+
+ seq = []
+ for node in doc.getElementsByTagName("artist"):
+ item = Artist(_extract(node, "name"), self.network)
+ weight = _number(_extract(node, "playcount"))
+ seq.append(TopItem(item, weight))
+
+ return seq
+
+ def get_weekly_album_charts(self, from_date = None, to_date = None):
+ """Returns the weekly album charts for the week starting from the from_date value to the to_date value."""
+
+ params = self._get_params()
+ if from_date and to_date:
+ params["from"] = from_date
+ params["to"] = to_date
+
+ doc = self._request("group.getWeeklyAlbumChart", True, params)
+
+ seq = []
+ for node in doc.getElementsByTagName("album"):
+ item = Album(_extract(node, "artist"), _extract(node, "name"), self.network)
+ weight = _number(_extract(node, "playcount"))
+ seq.append(TopItem(item, weight))
+
+ return seq
+
+ def get_weekly_track_charts(self, from_date = None, to_date = None):
+ """Returns the weekly track charts for the week starting from the from_date value to the to_date value."""
+
+ params = self._get_params()
+ if from_date and to_date:
+ params["from"] = from_date
+ params["to"] = to_date
+
+ doc = self._request("group.getWeeklyTrackChart", True, params)
+
+ seq = []
+ for node in doc.getElementsByTagName("track"):
+ item = Track(_extract(node, "artist"), _extract(node, "name"), self.network)
+ weight = _number(_extract(node, "playcount"))
+ seq.append(TopItem(item, weight))
+
+ return seq
+
+ def get_url(self, domain_name = DOMAIN_ENGLISH):
+ """Returns the url of the group page on the network.
+ * domain_name: The network's language domain. Possible values:
+ o DOMAIN_ENGLISH
+ o DOMAIN_GERMAN
+ o DOMAIN_SPANISH
+ o DOMAIN_FRENCH
+ o DOMAIN_ITALIAN
+ o DOMAIN_POLISH
+ o DOMAIN_PORTUGUESE
+ o DOMAIN_SWEDISH
+ o DOMAIN_TURKISH
+ o DOMAIN_RUSSIAN
+ o DOMAIN_JAPANESE
+ o DOMAIN_CHINESE
+ """
+
+ name = _url_safe(self.get_name())
+
+ return self.network._get_url(domain_name, "group") %{'name': name}
+
+ def get_members(self, limit=50):
+ """
+ Returns a sequence of User objects
+ if limit==None it will return all
+ """
+
+ nodes = _collect_nodes(limit, self, "group.getMembers", False)
+
+ users = []
+
+ for node in nodes:
+ users.append(User(_extract(node, "name"), self.network))
+
+ return users
class XSPF(_BaseObject):
- "A Last.fm XSPF playlist."""
-
- def __init__(self, uri, api_key, api_secret, session_key):
- _BaseObject.__init__(self, api_key, api_secret, session_key)
-
- self.uri = uri
-
- def _get_params(self):
- return {'playlistURL': self.get_uri()}
-
- def __repr__(self):
- return self.get_uri()
-
- def __eq__(self, other):
- return self.get_uri() == other.get_uri()
-
- def __ne__(self, other):
- return self.get_uri() != other.get_uri()
-
- def get_uri(self):
- """Returns the Last.fm playlist URI. """
-
- return self.uri
-
- def get_tracks(self):
- """Returns the tracks on this playlist."""
-
- doc = self._request('playlist.fetch', True)
-
- list = []
- for n in doc.getElementsByTagName('track'):
- title = _extract(n, 'title')
- artist = _extract(n, 'creator')
-
- list.append(Track(artist, title, *self.auth_data))
-
- return list
+ "A Last.fm XSPF playlist."""
+
+ uri = None
+
+ def __init__(self, uri, network):
+ _BaseObject.__init__(self, network)
+
+ self.uri = uri
+
+ def _get_params(self):
+ return {'playlistURL': self.get_uri()}
+
+ @_string_output
+ def __repr__(self):
+ return self.get_uri()
+
+ def __eq__(self, other):
+ return self.get_uri() == other.get_uri()
+
+ def __ne__(self, other):
+ return self.get_uri() != other.get_uri()
+
+ def get_uri(self):
+ """Returns the Last.fm playlist URI. """
+
+ return self.uri
+
+ def get_tracks(self):
+ """Returns the tracks on this playlist."""
+
+ doc = self._request('playlist.fetch', True)
+
+ seq = []
+ for n in doc.getElementsByTagName('track'):
+ title = _extract(n, 'title')
+ artist = _extract(n, 'creator')
+
+ seq.append(Track(artist, title, self.network))
+
+ return seq
class User(_BaseObject):
- """A Last.fm user."""
-
- def __init__(self, user_name, api_key, api_secret, session_key):
- _BaseObject.__init__(self, api_key, api_secret, session_key)
-
- self.name = user_name
-
- self._past_events_index = 0
- self._recommended_events_index = 0
- self._recommended_artists_index = 0
-
- def __repr__(self):
- return self.get_name()
-
- def __eq__(self, another):
- return self.get_name() == another.get_name()
-
- def __ne__(self, another):
- return self.get_name() != another.get_name()
-
- def _get_params(self):
- return {"user": self.get_name()}
-
- def get_name(self):
- """Returns the nuser name."""
-
- return self.name
-
- def get_upcoming_events(self):
- """Returns all the upcoming events for this user. """
-
- doc = self._request('user.getEvents', True)
-
- ids = _extract_all(doc, 'id')
- events = []
-
- for id in ids:
- events.append(Event(id, *self.auth_data))
-
- return events
-
- def get_friends(self, limit = None):
- """Returns a list of the user's friends. """
-
- params = self._get_params()
- if limit:
- params['limit'] = unicode(limit)
-
- doc = self._request('user.getFriends', True, params)
-
- names = _extract_all(doc, 'name')
-
- list = []
- for name in names:
- list.append(User(name, *self.auth_data))
-
- return list
-
- def get_loved_tracks(self):
- """Returns the last 50 tracks loved by this user. """
-
- doc = self._request('user.getLovedTracks', True)
-
- list = []
- for track in doc.getElementsByTagName('track'):
- title = _extract(track, 'name', 0)
- artist = _extract(track, 'name', 1)
-
- list.append(Track(artist, title, *self.auth_data))
-
- return list
-
- def get_neighbours(self, limit = None):
- """Returns a list of the user's friends."""
-
- params = self._get_params()
- if limit:
- params['limit'] = unicode(limit)
-
- doc = self._request('user.getNeighbours', True, params)
-
- list = []
- names = _extract_all(doc, 'name')
-
- for name in names:
- list.append(User(name, *self.auth_data))
-
- return list
-
- def _get_past_events_pagecount(self):
- """Returns the number of pages in the past events."""
-
- params = self._get_params()
- params["page"] = str(self._past_events_index)
- doc = self._request("user.getPastEvents", True, params)
-
- return _number(doc.getElementsByTagName("events")[0].getAttribute("totalPages"))
-
- def is_end_of_past_events(self):
- """Returns True if the end of Past Events was reached."""
-
- return self._past_events_index >= self._get_past_events_pagecount()
-
- def get_past_events_page(self, ):
- """Retruns a paginated list of all events a user has attended in the past.
-
- Example:
- --------
-
- while not user.is_end_of_past_events():
- print user.get_past_events_page()
-
- """
-
- self._past_events_index += 1
- params = self._get_params()
- params["page"] = str(self._past_events_index)
-
- doc = self._request('user.getPastEvents', True, params)
-
- list = []
- for id in _extract_all(doc, 'id'):
- list.append(Event(id, *self.auth_data))
-
- return list
-
- def get_playlists(self):
- """Returns a list of Playlists that this user owns."""
-
- doc = self._request("user.getPlaylists", True)
-
- playlists = []
- for id in _extract_all(doc, "id"):
- playlists.append(Playlist(self.get_name(), id, *self.auth_data))
-
- return playlists
-
- def get_now_playing(self):
- """Returns the currently playing track, or None if nothing is playing. """
-
- params = self._get_params()
- params['limit'] = '1'
-
- list = []
-
- doc = self._request('user.getRecentTracks', False, params)
-
- e = doc.getElementsByTagName('track')[0]
-
- if not e.hasAttribute('nowplaying'):
- return None
-
- artist = _extract(e, 'artist')
- title = _extract(e, 'name')
-
- return Track(artist, title, *self.auth_data)
-
-
- def get_recent_tracks(self, limit = None):
- """Returns this user's recent listened-to tracks as
- a sequence of PlayedTrack objects.
- Use extract_items() with the return of this function to
- get only a sequence of Track objects with no playback dates. """
-
- params = self._get_params()
- if limit:
- params['limit'] = unicode(limit)
-
- doc = self._request('user.getRecentTracks', False, params)
-
- list = []
- for track in doc.getElementsByTagName('track'):
- title = _extract(track, "name")
- artist = _extract(track, "artist")
- date = _extract(track, "date")
- timestamp = track.getElementsByTagName("date")[0].getAttribute("uts")
-
- if track.hasAttribute('nowplaying'):
- continue #to prevent the now playing track from sneaking in here
-
- list.append(PlayedTrack(Track(artist, title, *self.auth_data), date, timestamp))
-
- return list
-
- def get_top_albums(self, period = PERIOD_OVERALL):
- """Returns the top albums played by a user.
- * period: The period of time. Possible values:
- o PERIOD_OVERALL
- o PERIOD_3MONTHS
- o PERIOD_6MONTHS
- o PERIOD_12MONTHS
- """
-
- params = self._get_params()
- params['period'] = period
-
- doc = self._request('user.getTopAlbums', True, params)
-
- list = []
- for album in doc.getElementsByTagName('album'):
- name = _extract(album, 'name')
- artist = _extract(album, 'name', 1)
- playcount = _extract(album, "playcount")
-
- list.append(TopItem(Album(artist, name, *self.auth_data), playcount))
-
- return list
-
- def get_top_artists(self, period = PERIOD_OVERALL):
- """Returns the top artists played by a user.
- * period: The period of time. Possible values:
- o PERIOD_OVERALL
- o PERIOD_3MONTHS
- o PERIOD_6MONTHS
- o PERIOD_12MONTHS
- """
-
- params = self._get_params()
- params['period'] = period
-
- doc = self._request('user.getTopArtists', True, params)
-
- list = []
- for node in doc.getElementsByTagName('artist'):
- name = _extract(node, 'name')
- playcount = _extract(node, "playcount")
-
- list.append(TopItem(Artist(name, *self.auth_data), playcount))
-
- return list
-
- def get_top_tags(self, limit = None):
- """Returns a sequence of the top tags used by this user with their counts as (Tag, tagcount).
- * limit: The limit of how many tags to return.
- """
-
- doc = self._request("user.getTopTags", True)
-
- list = []
- for node in doc.getElementsByTagName("tag"):
- list.append(TopItem(Tag(_extract(node, "name"), *self.auth_data), _extract(node, "count")))
-
- return list
-
- def get_top_tracks(self, period = PERIOD_OVERALL):
- """Returns the top tracks played by a user.
- * period: The period of time. Possible values:
- o PERIOD_OVERALL
- o PERIOD_3MONTHS
- o PERIOD_6MONTHS
- o PERIOD_12MONTHS
- """
-
- params = self._get_params()
- params['period'] = period
-
- doc = self._request('user.getTopTracks', True, params)
-
- list = []
- for track in doc.getElementsByTagName('track'):
- name = _extract(track, 'name')
- artist = _extract(track, 'name', 1)
- playcount = _extract(track, "playcount")
-
- list.append(TopItem(Track(artist, name, *self.auth_data), playcount))
-
- return list
-
- def get_weekly_chart_dates(self):
- """Returns a list of From and To tuples for the available charts."""
-
- doc = self._request("user.getWeeklyChartList", True)
-
- list = []
- for node in doc.getElementsByTagName("chart"):
- list.append( (node.getAttribute("from"), node.getAttribute("to")) )
-
- return list
-
- def get_weekly_artist_charts(self, from_date = None, to_date = None):
- """Returns the weekly artist charts for the week starting from the from_date value to the to_date value."""
-
- params = self._get_params()
- if from_date and to_date:
- params["from"] = from_date
- params["to"] = to_date
-
- doc = self._request("user.getWeeklyArtistChart", True, params)
-
- list = []
- for node in doc.getElementsByTagName("artist"):
- item = Artist(_extract(node, "name"), *self.auth_data)
- weight = _extract(node, "playcount")
- list.append(TopItem(item, weight))
-
- return list
-
- def get_weekly_album_charts(self, from_date = None, to_date = None):
- """Returns the weekly album charts for the week starting from the from_date value to the to_date value."""
-
- params = self._get_params()
- if from_date and to_date:
- params["from"] = from_date
- params["to"] = to_date
-
- doc = self._request("user.getWeeklyAlbumChart", True, params)
-
- list = []
- for node in doc.getElementsByTagName("album"):
- item = Album(_extract(node, "artist"), _extract(node, "name"), *self.auth_data)
- weight = _extract(node, "playcount")
- list.append(TopItem(item, weight))
-
- return list
-
- def get_weekly_track_charts(self, from_date = None, to_date = None):
- """Returns the weekly track charts for the week starting from the from_date value to the to_date value."""
-
- params = self._get_params()
- if from_date and to_date:
- params["from"] = from_date
- params["to"] = to_date
-
- doc = self._request("user.getWeeklyTrackChart", True, params)
-
- list = []
- for node in doc.getElementsByTagName("track"):
- item = Track(_extract(node, "artist"), _extract(node, "name"), *self.auth_data)
- weight = _extract(node, "playcount")
- list.append(TopItem(item, weight))
-
- return list
-
- def compare_with_user(self, user, shared_artists_limit = None):
- """Compare this user with another Last.fm user.
- Returns a sequence (tasteometer_score, (shared_artist1, shared_artist2, ...))
- user: A User object or a username string/unicode object.
- """
-
- if isinstance(user, User):
- user = user.get_name()
-
- params = self._get_params()
- if shared_artists_limit:
- params['limit'] = unicode(shared_artists_limit)
- params['type1'] = 'user'
- params['type2'] = 'user'
- params['value1'] = self.get_name()
- params['value2'] = user
-
- doc = _Request('tasteometer.compare', params, *self.auth_data).execute()
-
- score = _extract(doc, 'score')
-
- artists = doc.getElementsByTagName('artists')[0]
- shared_artists_names = _extract_all(artists, 'name')
-
- shared_artists_list = []
-
- for name in shared_artists_names:
- shared_artists_list.append(Artist(name, *self.auth_data))
-
- return (score, shared_artists_list)
-
- def getRecommendedEvents(self, page = None, limit = None):
- """Returns a paginated list of all events recommended to a user by Last.fm, based on their listening profile.
- * page: The page number of results to return.
- * limit: The limit of events to return.
- """
-
- params = self._get_params()
- if page:
- params['page'] = unicode(page)
- if limit:
- params['limit'] = unicode(limit)
-
- doc = _Request('user.getRecommendedEvents', params, *self.auth_data).execute()
-
- ids = _extract_all(doc, 'id')
- list = []
- for id in ids:
- list.append(Event(id, *self.auth_data))
-
- return list
-
- def get_url(self, domain_name = DOMAIN_ENGLISH):
- """Returns the url of the user page on Last.fm.
- * domain_name: Last.fm's language domain. Possible values:
- o DOMAIN_ENGLISH
- o DOMAIN_GERMAN
- o DOMAIN_SPANISH
- o DOMAIN_FRENCH
- o DOMAIN_ITALIAN
- o DOMAIN_POLISH
- o DOMAIN_PORTUGUESE
- o DOMAIN_SWEDISH
- o DOMAIN_TURKISH
- o DOMAIN_RUSSIAN
- o DOMAIN_JAPANESE
- o DOMAIN_CHINESE
- """
- url = 'http://%(domain)s/user/%(name)s'
-
- name = _get_url_safe(self.get_name())
-
- return url %{'domain': domain_name, 'name': name}
-
- def get_library(self):
- """Returns the associated Library object. """
-
- return Library(self, *self.auth_data)
+ """A Last.fm user."""
+
+ name = None
+
+ def __init__(self, user_name, network):
+ _BaseObject.__init__(self, network)
+
+ self.name = user_name
+
+ self._past_events_index = 0
+ self._recommended_events_index = 0
+ self._recommended_artists_index = 0
+
+ @_string_output
+ def __repr__(self):
+ return self.get_name()
+
+ def __eq__(self, another):
+ return self.get_name() == another.get_name()
+
+ def __ne__(self, another):
+ return self.get_name() != another.get_name()
+
+ def _get_params(self):
+ return {"user": self.get_name()}
+
+ def get_name(self):
+ """Returns the nuser name."""
+
+ return self.name
+
+ def get_upcoming_events(self):
+ """Returns all the upcoming events for this user. """
+
+ doc = self._request('user.getEvents', True)
+
+ ids = _extract_all(doc, 'id')
+ events = []
+
+ for e_id in ids:
+ events.append(Event(e_id, self.network))
+
+ return events
+
+ def get_friends(self, limit = 50):
+ """Returns a list of the user's friends. """
+
+ seq = []
+ for node in _collect_nodes(limit, self, "user.getFriends", False):
+ seq.append(User(_extract(node, "name"), self.network))
+
+ return seq
+
+ def get_loved_tracks(self, limit=50):
+ """Returns the loved tracks by this user
+ if limit is None, it will return all of them
+ """
+
+ tracks = []
+ for track in _collect_nodes(limit, self, "user.getLovedTracks", False):
+ title = _extract(track, 'name', 0)
+ artist = _extract(track, 'name', 1)
+
+ tracks.append(Track(artist, title, self.network))
+
+ return tracks
+
+ def get_neighbours(self, limit = 50):
+ """Returns a list of the user's friends."""
+
+ params = self._get_params()
+ if limit:
+ params['limit'] = _unicode(limit)
+
+ doc = self._request('user.getNeighbours', True, params)
+
+ seq = []
+ names = _extract_all(doc, 'name')
+
+ for name in names:
+ seq.append(User(name, self.network))
+
+ return seq
+
+ def get_past_events(self, limit=50):
+ """
+ Returns a sequence of Event objects
+ if limit==None it will return all
+ """
+
+ seq = []
+ for n in _collect_nodes(limit, self, "user.getPastEvents", False):
+ seq.append(Event(_extract(n, "id"), self.network))
+
+ return seq
+
+ def get_playlists(self):
+ """Returns a list of Playlists that this user owns."""
+
+ doc = self._request("user.getPlaylists", True)
+
+ playlists = []
+ for playlist_id in _extract_all(doc, "id"):
+ playlists.append(Playlist(self.get_name(), playlist_id, self.network))
+
+ return playlists
+
+ def get_now_playing(self):
+ """Returns the currently playing track, or None if nothing is playing. """
+
+ params = self._get_params()
+ params['limit'] = '1'
+
+ doc = self._request('user.getRecentTracks', False, params)
+
+ e = doc.getElementsByTagName('track')[0]
+
+ if not e.hasAttribute('nowplaying'):
+ return None
+
+ artist = _extract(e, 'artist')
+ title = _extract(e, 'name')
+
+ return Track(artist, title, self.network)
+
+
+ def get_recent_tracks(self, limit = None):
+ """Returns this user's recent listened-to tracks as
+ a sequence of PlayedTrack objects.
+ Use extract_items() with the return of this function to
+ get only a sequence of Track objects with no playback dates. """
+
+ params = self._get_params()
+ if limit:
+ params['limit'] = _unicode(limit)
+
+ doc = self._request('user.getRecentTracks', False, params)
+
+ seq = []
+ for track in doc.getElementsByTagName('track'):
+ title = _extract(track, "name")
+ artist = _extract(track, "artist")
+ date = _extract(track, "date")
+ timestamp = track.getElementsByTagName("date")[0].getAttribute("uts")
+
+ if track.hasAttribute('nowplaying'):
+ continue #to prevent the now playing track from sneaking in here
+
+ seq.append(PlayedTrack(Track(artist, title, self.network), date, timestamp))
+
+ return seq
+
+ def get_top_albums(self, period = PERIOD_OVERALL):
+ """Returns the top albums played by a user.
+ * period: The period of time. Possible values:
+ o PERIOD_OVERALL
+ o PERIOD_3MONTHS
+ o PERIOD_6MONTHS
+ o PERIOD_12MONTHS
+ """
+
+ params = self._get_params()
+ params['period'] = period
+
+ doc = self._request('user.getTopAlbums', True, params)
+
+ seq = []
+ for album in doc.getElementsByTagName('album'):
+ name = _extract(album, 'name')
+ artist = _extract(album, 'name', 1)
+ playcount = _extract(album, "playcount")
+
+ seq.append(TopItem(Album(artist, name, self.network), playcount))
+
+ return seq
+
+ def get_top_artists(self, period = PERIOD_OVERALL):
+ """Returns the top artists played by a user.
+ * period: The period of time. Possible values:
+ o PERIOD_OVERALL
+ o PERIOD_3MONTHS
+ o PERIOD_6MONTHS
+ o PERIOD_12MONTHS
+ """
+
+ params = self._get_params()
+ params['period'] = period
+
+ doc = self._request('user.getTopArtists', True, params)
+
+ seq = []
+ for node in doc.getElementsByTagName('artist'):
+ name = _extract(node, 'name')
+ playcount = _extract(node, "playcount")
+
+ seq.append(TopItem(Artist(name, self.network), playcount))
+
+ return seq
+
+ def get_top_tags(self, limit = None):
+ """Returns a sequence of the top tags used by this user with their counts as (Tag, tagcount).
+ * limit: The limit of how many tags to return.
+ """
+
+ doc = self._request("user.getTopTags", True)
+
+ seq = []
+ for node in doc.getElementsByTagName("tag"):
+ if len(seq) < limit:
+ seq.append(TopItem(Tag(_extract(node, "name"), self.network), _extract(node, "count")))
+
+ return seq
+
+ def get_top_tracks(self, period = PERIOD_OVERALL):
+ """Returns the top tracks played by a user.
+ * period: The period of time. Possible values:
+ o PERIOD_OVERALL
+ o PERIOD_3MONTHS
+ o PERIOD_6MONTHS
+ o PERIOD_12MONTHS
+ """
+
+ params = self._get_params()
+ params['period'] = period
+
+ doc = self._request('user.getTopTracks', True, params)
+
+ seq = []
+ for track in doc.getElementsByTagName('track'):
+ name = _extract(track, 'name')
+ artist = _extract(track, 'name', 1)
+ playcount = _extract(track, "playcount")
+
+ seq.append(TopItem(Track(artist, name, self.network), playcount))
+
+ return seq
+
+ def get_weekly_chart_dates(self):
+ """Returns a list of From and To tuples for the available charts."""
+
+ doc = self._request("user.getWeeklyChartList", True)
+
+ seq = []
+ for node in doc.getElementsByTagName("chart"):
+ seq.append( (node.getAttribute("from"), node.getAttribute("to")) )
+
+ return seq
+
+ def get_weekly_artist_charts(self, from_date = None, to_date = None):
+ """Returns the weekly artist charts for the week starting from the from_date value to the to_date value."""
+
+ params = self._get_params()
+ if from_date and to_date:
+ params["from"] = from_date
+ params["to"] = to_date
+
+ doc = self._request("user.getWeeklyArtistChart", True, params)
+
+ seq = []
+ for node in doc.getElementsByTagName("artist"):
+ item = Artist(_extract(node, "name"), self.network)
+ weight = _number(_extract(node, "playcount"))
+ seq.append(TopItem(item, weight))
+
+ return seq
+
+ def get_weekly_album_charts(self, from_date = None, to_date = None):
+ """Returns the weekly album charts for the week starting from the from_date value to the to_date value."""
+
+ params = self._get_params()
+ if from_date and to_date:
+ params["from"] = from_date
+ params["to"] = to_date
+
+ doc = self._request("user.getWeeklyAlbumChart", True, params)
+
+ seq = []
+ for node in doc.getElementsByTagName("album"):
+ item = Album(_extract(node, "artist"), _extract(node, "name"), self.network)
+ weight = _number(_extract(node, "playcount"))
+ seq.append(TopItem(item, weight))
+
+ return seq
+
+ def get_weekly_track_charts(self, from_date = None, to_date = None):
+ """Returns the weekly track charts for the week starting from the from_date value to the to_date value."""
+
+ params = self._get_params()
+ if from_date and to_date:
+ params["from"] = from_date
+ params["to"] = to_date
+
+ doc = self._request("user.getWeeklyTrackChart", True, params)
+
+ seq = []
+ for node in doc.getElementsByTagName("track"):
+ item = Track(_extract(node, "artist"), _extract(node, "name"), self.network)
+ weight = _number(_extract(node, "playcount"))
+ seq.append(TopItem(item, weight))
+
+ return seq
+
+ def compare_with_user(self, user, shared_artists_limit = None):
+ """Compare this user with another Last.fm user.
+ Returns a sequence (tasteometer_score, (shared_artist1, shared_artist2, ...))
+ user: A User object or a username string/unicode object.
+ """
+
+ if isinstance(user, User):
+ user = user.get_name()
+
+ params = self._get_params()
+ if shared_artists_limit:
+ params['limit'] = _unicode(shared_artists_limit)
+ params['type1'] = 'user'
+ params['type2'] = 'user'
+ params['value1'] = self.get_name()
+ params['value2'] = user
+
+ doc = self._request('tasteometer.compare', False, params)
+
+ score = _extract(doc, 'score')
+
+ artists = doc.getElementsByTagName('artists')[0]
+ shared_artists_names = _extract_all(artists, 'name')
+
+ shared_artists_seq = []
+
+ for name in shared_artists_names:
+ shared_artists_seq.append(Artist(name, self.network))
+
+ return (score, shared_artists_seq)
+
+ def get_url(self, domain_name = DOMAIN_ENGLISH):
+ """Returns the url of the user page on the network.
+ * domain_name: The network's language domain. Possible values:
+ o DOMAIN_ENGLISH
+ o DOMAIN_GERMAN
+ o DOMAIN_SPANISH
+ o DOMAIN_FRENCH
+ o DOMAIN_ITALIAN
+ o DOMAIN_POLISH
+ o DOMAIN_PORTUGUESE
+ o DOMAIN_SWEDISH
+ o DOMAIN_TURKISH
+ o DOMAIN_RUSSIAN
+ o DOMAIN_JAPANESE
+ o DOMAIN_CHINESE
+ """
+
+ name = _url_safe(self.get_name())
+
+ return self.network._get_url(domain_name, "user") %{'name': name}
+
+ def get_library(self):
+ """Returns the associated Library object. """
+
+ return Library(self, self.network)
+
+ def get_shouts(self, limit=50):
+ """
+ Returns a sequqence of Shout objects
+ """
+
+ shouts = []
+ for node in _collect_nodes(limit, self, "user.getShouts", False):
+ shouts.append(Shout(
+ _extract(node, "body"),
+ User(_extract(node, "author"), self.network),
+ _extract(node, "date")
+ )
+ )
+ return shouts
+
+ def shout(self, message):
+ """
+ Post a shout
+ """
+
+ params = self._get_params()
+ params["message"] = message
+
+ self._request("user.Shout", False, params)
class AuthenticatedUser(User):
- def __init__(self, api_key, api_secret, session_key):
- User.__init__(self, "", api_key, api_secret, session_key);
-
- def _get_params(self):
- return {}
-
- def get_name(self):
- """Returns the name of the authenticated user."""
-
- doc = self._request("user.getInfo", True)
-
- self.name = _extract(doc, "name")
- return self.name
-
- def get_id(self):
- """Returns the user id."""
-
- doc = self._request("user.getInfo", True)
-
- return _extract(doc, "id")
-
- def get_image_url(self):
- """Returns the user's avatar."""
-
- doc = self._request("user.getInfo", True)
-
- return _extract(doc, "image")
-
- def get_language(self):
- """Returns the language code of the language used by the user."""
-
- doc = self._request("user.getInfo", True)
-
- return _extract(doc, "lang")
-
- def get_country(self):
- """Returns the name of the country of the user."""
-
- doc = self._request("user.getInfo", True)
-
- return Country(_extract(doc, "country"), *self.auth_data)
-
- def get_age(self):
- """Returns the user's age."""
-
- doc = self._request("user.getInfo", True)
-
- return _number(_extract(doc, "age"))
-
- def get_gender(self):
- """Returns the user's gender. Either USER_MALE or USER_FEMALE."""
-
- doc = self._request("user.getInfo", True)
-
- return _extract(doc, "gender")
-
- if value == 'm':
- return USER_MALE
- elif value == 'f':
- return USER_FEMALE
-
- return None
-
- def is_subscriber(self):
- """Returns whether the user is a subscriber or not. True or False."""
-
- doc = self._request("user.getInfo", True)
-
- return _extract(doc, "subscriber") == "1"
-
- def get_playcount(self):
- """Returns the user's playcount so far."""
-
- doc = self._request("user.getInfo", True)
-
- return _number(_extract(doc, "playcount"))
-
- def _get_recommended_events_pagecount(self):
- """Returns the number of pages in the past events."""
-
- params = self._get_params()
- params["page"] = str(self._recommended_events_index)
- doc = self._request("user.getRecommendedEvents", True, params)
-
- return _number(doc.getElementsByTagName("events")[0].getAttribute("totalPages"))
-
- def is_end_of_recommended_events(self):
- """Returns True if the end of Past Events was reached."""
-
- return self._recommended_events_index >= self._get_recommended_events_pagecount()
-
- def get_recommended_events_page(self, ):
- """Retruns a paginated list of all events a user has attended in the past.
-
- Example:
- --------
-
- while not user.is_end_of_recommended_events():
- print user.get_recommended_events_page()
-
- """
-
- self._recommended_events_index += 1
- params = self._get_params()
- params["page"] = str(self._recommended_events_index)
-
- doc = self._request('user.getRecommendedEvents', True, params)
-
- list = []
- for id in _extract_all(doc, 'id'):
- list.append(Event(id, *self.auth_data))
-
- return list
-
- def _get_recommended_artists_pagecount(self):
- """Returns the number of pages in the past artists."""
-
- params = self._get_params()
- params["page"] = str(self._recommended_artists_index)
- doc = self._request("user.getRecommendedArtists", True, params)
-
- return _number(doc.getElementsByTagName("recommendations")[0].getAttribute("totalPages"))
-
- def is_end_of_recommended_artists(self):
- """Returns True if the end of Past Artists was reached."""
-
- return self._recommended_artists_index >= self._get_recommended_artists_pagecount()
-
- def get_recommended_artists_page(self, ):
- """Retruns a paginated list of all artists a user has attended in the past.
-
- Example:
- --------
-
- while not user.is_end_of_recommended_artists():
- print user.get_recommended_artists_page()
-
- """
-
- self._recommended_artists_index += 1
- params = self._get_params()
- params["page"] = str(self._recommended_artists_index)
-
- doc = self._request('user.getRecommendedArtists', True, params)
-
- list = []
- for name in _extract_all(doc, 'name'):
- list.append(Artist(name, *self.auth_data))
-
- return list
-
+ def __init__(self, network):
+ User.__init__(self, "", network);
+
+ def _get_params(self):
+ return {"user": self.get_name()}
+
+ def get_name(self):
+ """Returns the name of the authenticated user."""
+
+ doc = self._request("user.getInfo", True, {"user": ""}) # hack
+
+ self.name = _extract(doc, "name")
+ return self.name
+
+ def get_id(self):
+ """Returns the user id."""
+
+ doc = self._request("user.getInfo", True)
+
+ return _extract(doc, "id")
+
+ def get_cover_image(self):
+ """Returns the user's avatar."""
+
+ doc = self._request("user.getInfo", True)
+
+ return _extract(doc, "image")
+
+ def get_language(self):
+ """Returns the language code of the language used by the user."""
+
+ doc = self._request("user.getInfo", True)
+
+ return _extract(doc, "lang")
+
+ def get_country(self):
+ """Returns the name of the country of the user."""
+
+ doc = self._request("user.getInfo", True)
+
+ return Country(_extract(doc, "country"), self.network)
+
+ def get_age(self):
+ """Returns the user's age."""
+
+ doc = self._request("user.getInfo", True)
+
+ return _number(_extract(doc, "age"))
+
+ def get_gender(self):
+ """Returns the user's gender. Either USER_MALE or USER_FEMALE."""
+
+ doc = self._request("user.getInfo", True)
+
+ value = _extract(doc, "gender")
+
+ if value == 'm':
+ return USER_MALE
+ elif value == 'f':
+ return USER_FEMALE
+
+ return None
+
+ def is_subscriber(self):
+ """Returns whether the user is a subscriber or not. True or False."""
+
+ doc = self._request("user.getInfo", True)
+
+ return _extract(doc, "subscriber") == "1"
+
+ def get_playcount(self):
+ """Returns the user's playcount so far."""
+
+ doc = self._request("user.getInfo", True)
+
+ return _number(_extract(doc, "playcount"))
+
+ def get_recommended_events(self, limit=50):
+ """
+ Returns a sequence of Event objects
+ if limit==None it will return all
+ """
+
+ seq = []
+ for node in _collect_nodes(limit, self, "user.getRecommendedEvents", False):
+ seq.append(Event(_extract(node, "id"), self.network))
+
+ return seq
+
+ def get_recommended_artists(self, limit=50):
+ """
+ Returns a sequence of Event objects
+ if limit==None it will return all
+ """
+
+ seq = []
+ for node in _collect_nodes(limit, self, "user.getRecommendedArtists", False):
+ seq.append(Artist(_extract(node, "name"), self.network))
+
+ return seq
+
class _Search(_BaseObject):
- """An abstract class. Use one of its derivatives."""
-
- def __init__(self, ws_prefix, search_terms, api_key, api_secret, session_key):
- _BaseObject.__init__(self, api_key, api_secret, session_key)
-
- self._ws_prefix = ws_prefix
- self.search_terms = search_terms
-
- self._last_page_index = 0
-
- def _get_params(self):
- params = {}
-
- for key in self.search_terms.keys():
- params[key] = self.search_terms[key]
-
- return params
-
- def get_total_result_count(self):
- """Returns the total count of all the results."""
-
- doc = self._request(self._ws_prefix + ".search", True)
-
- return _extract(doc, "opensearch:totalResults")
-
- def _retreive_page(self, page_index):
- """Returns the node of matches to be processed"""
-
- params = self._get_params()
- params["page"] = str(page_index)
- doc = self._request(self._ws_prefix + ".search", True, params)
-
- return doc.getElementsByTagName(self._ws_prefix + "matches")[0]
-
- def _retrieve_next_page(self):
- self._last_page_index += 1
- return self._retreive_page(self._last_page_index)
+ """An abstract class. Use one of its derivatives."""
+
+ def __init__(self, ws_prefix, search_terms, network):
+ _BaseObject.__init__(self, network)
+
+ self._ws_prefix = ws_prefix
+ self.search_terms = search_terms
+
+ self._last_page_index = 0
+
+ def _get_params(self):
+ params = {}
+
+ for key in self.search_terms.keys():
+ params[key] = self.search_terms[key]
+
+ return params
+
+ def get_total_result_count(self):
+ """Returns the total count of all the results."""
+
+ doc = self._request(self._ws_prefix + ".search", True)
+
+ return _extract(doc, "opensearch:totalResults")
+
+ def _retreive_page(self, page_index):
+ """Returns the node of matches to be processed"""
+
+ params = self._get_params()
+ params["page"] = str(page_index)
+ doc = self._request(self._ws_prefix + ".search", True, params)
+
+ return doc.getElementsByTagName(self._ws_prefix + "matches")[0]
+
+ def _retrieve_next_page(self):
+ self._last_page_index += 1
+ return self._retreive_page(self._last_page_index)
class AlbumSearch(_Search):
- """Search for an album by name."""
-
- def __init__(self, album_name, api_key, api_secret, session_key):
-
- _Search.__init__(self, "album", {"album": album_name}, api_key, api_secret, session_key)
-
- def get_next_page(self):
- """Returns the next page of results as a sequence of Album objects."""
-
- master_node = self._retrieve_next_page()
-
- list = []
- for node in master_node.getElementsByTagName("album"):
- list.append(Album(_extract(node, "artist"), _extract(node, "name"), *self.auth_data))
-
- return list
+ """Search for an album by name."""
+
+ def __init__(self, album_name, network):
+
+ _Search.__init__(self, "album", {"album": album_name}, network)
+
+ def get_next_page(self):
+ """Returns the next page of results as a sequence of Album objects."""
+
+ master_node = self._retrieve_next_page()
+
+ seq = []
+ for node in master_node.getElementsByTagName("album"):
+ seq.append(Album(_extract(node, "artist"), _extract(node, "name"), self.network))
+
+ return seq
class ArtistSearch(_Search):
- """Search for an artist by artist name."""
-
- def __init__(self, artist_name, api_key, api_secret, session_key):
- _Search.__init__(self, "artist", {"artist": artist_name}, api_key, api_secret, session_key)
-
- def get_next_page(self):
- """Returns the next page of results as a sequence of Artist objects."""
-
- master_node = self._retrieve_next_page()
-
- list = []
- for node in master_node.getElementsByTagName("artist"):
- list.append(Artist(_extract(node, "name"), *self.auth_data))
-
- return list
+ """Search for an artist by artist name."""
+
+ def __init__(self, artist_name, network):
+ _Search.__init__(self, "artist", {"artist": artist_name}, network)
+
+ def get_next_page(self):
+ """Returns the next page of results as a sequence of Artist objects."""
+
+ master_node = self._retrieve_next_page()
+
+ seq = []
+ for node in master_node.getElementsByTagName("artist"):
+ seq.append(Artist(_extract(node, "name"), self.network))
+
+ return seq
class TagSearch(_Search):
- """Search for a tag by tag name."""
-
- def __init__(self, tag_name, api_key, api_secret, session_key):
-
- _Search.__init__(self, "tag", {"tag": tag_name}, api_key, api_secret, session_key)
-
- def get_next_page(self):
- """Returns the next page of results as a sequence of Tag objects."""
-
- master_node = self._retrieve_next_page()
-
- list = []
- for node in master_node.getElementsByTagName("tag"):
- list.append(Tag(_extract(node, "name"), *self.auth_data))
-
- return list
+ """Search for a tag by tag name."""
+
+ def __init__(self, tag_name, network):
+
+ _Search.__init__(self, "tag", {"tag": tag_name}, network)
+
+ def get_next_page(self):
+ """Returns the next page of results as a sequence of Tag objects."""
+
+ master_node = self._retrieve_next_page()
+
+ seq = []
+ for node in master_node.getElementsByTagName("tag"):
+ seq.append(Tag(_extract(node, "name"), self.network))
+
+ return seq
class TrackSearch(_Search):
- """Search for a track by track title. If you don't wanna narrow the results down
- by specifying the artist name, set it to empty string."""
-
- def __init__(self, artist_name, track_title, api_key, api_secret, session_key):
-
- _Search.__init__(self, "track", {"track": track_title, "artist": artist_name}, api_key, api_secret, session_key)
-
- def get_next_page(self):
- """Returns the next page of results as a sequence of Track objects."""
-
- master_node = self._retrieve_next_page()
-
- list = []
- for node in master_node.getElementsByTagName("track"):
- list.append(Track(_extract(node, "artist"), _extract(node, "name"), *self.auth_data))
-
- return list
+ """Search for a track by track title. If you don't wanna narrow the results down
+ by specifying the artist name, set it to empty string."""
+
+ def __init__(self, artist_name, track_title, network):
+
+ _Search.__init__(self, "track", {"track": track_title, "artist": artist_name}, network)
+
+ def get_next_page(self):
+ """Returns the next page of results as a sequence of Track objects."""
+
+ master_node = self._retrieve_next_page()
+
+ seq = []
+ for node in master_node.getElementsByTagName("track"):
+ seq.append(Track(_extract(node, "artist"), _extract(node, "name"), self.network))
+
+ return seq
class VenueSearch(_Search):
- """Search for a venue by its name. If you don't wanna narrow the results down
- by specifying a country, set it to empty string."""
-
- def __init__(self, venue_name, country_name, api_key, api_secret, session_key):
-
- _Search.__init__(self, "venue", {"venue": venue_name, "country": country_name}, api_key, api_secret, session_key)
-
- def get_next_page(self):
- """Returns the next page of results as a sequence of Track objects."""
-
- master_node = self._retrieve_next_page()
-
- list = []
- for node in master_node.getElementsByTagName("venue"):
- list.append(Venue(_extract(node, "id"), *self.auth_data))
-
- return list
+ """Search for a venue by its name. If you don't wanna narrow the results down
+ by specifying a country, set it to empty string."""
+
+ def __init__(self, venue_name, country_name, network):
+
+ _Search.__init__(self, "venue", {"venue": venue_name, "country": country_name}, network)
+
+ def get_next_page(self):
+ """Returns the next page of results as a sequence of Track objects."""
+
+ master_node = self._retrieve_next_page()
+
+ seq = []
+ for node in master_node.getElementsByTagName("venue"):
+ seq.append(Venue(_extract(node, "id"), self.network))
+
+ return seq
class Venue(_BaseObject):
- """A venue where events are held."""
-
- # TODO: waiting for a venue.getInfo web service to use.
-
- def __init__(self, id, api_key, api_secret, session_key):
- _BaseObject.__init__(self, api_key, api_secret, session_key)
-
- self.id = _number(id)
-
- def __repr__(self):
- return "Venue #" + str(self.id)
-
- def __eq__(self, other):
- return self.get_id() == other.get_id()
-
- def _get_params(self):
- return {"venue": self.get_id()}
-
- def get_id(self):
- """Returns the id of the venue."""
-
- return self.id
-
- def get_upcoming_events(self):
- """Returns the upcoming events in this venue."""
-
- doc = self._request("venue.getEvents", True)
-
- list = []
- for node in doc.getElementsByTagName("event"):
- list.append(Event(_extract(node, "id"), *self.auth_data))
-
- return list
-
- def get_past_events(self):
- """Returns the past events held in this venue."""
-
- doc = self._request("venue.getEvents", True)
-
- list = []
- for node in doc.getElementsByTagName("event"):
- list.append(Event(_extract(node, "id"), *self.auth_data))
-
- return list
-
-def create_new_playlist(title, description, api_key, api_secret, session_key):
- """Creates a playlist for the authenticated user and returns it.
- * title: The title of the new playlist.
- * description: The description of the new playlist.
- """
-
- params = dict()
- params['title'] = unicode(title)
- params['description'] = unicode(description)
-
- doc = _Request('playlist.create', params, api_key, api_secret, session_key).execute()
-
- id = doc.getElementsByTagName("id")[0].firstChild.data
- user = doc.getElementsByTagName('playlists')[0].getAttribute('user')
-
- return Playlist(user, id, api_key, api_secret, session_key)
-
-def get_authenticated_user(api_key, api_secret, session_key):
- """Returns the authenticated user."""
-
- return AuthenticatedUser(api_key, api_secret, session_key)
+ """A venue where events are held."""
+
+ # TODO: waiting for a venue.getInfo web service to use.
+
+ id = None
+
+ def __init__(self, id, network):
+ _BaseObject.__init__(self, network)
+
+ self.id = _number(id)
+
+ @_string_output
+ def __repr__(self):
+ return "Venue #" + str(self.id)
+
+ def __eq__(self, other):
+ return self.get_id() == other.get_id()
+
+ def _get_params(self):
+ return {"venue": self.get_id()}
+
+ def get_id(self):
+ """Returns the id of the venue."""
+
+ return self.id
+
+ def get_upcoming_events(self):
+ """Returns the upcoming events in this venue."""
+
+ doc = self._request("venue.getEvents", True)
+
+ seq = []
+ for node in doc.getElementsByTagName("event"):
+ seq.append(Event(_extract(node, "id"), self.network))
+
+ return seq
+
+ def get_past_events(self):
+ """Returns the past events held in this venue."""
+
+ doc = self._request("venue.getEvents", True)
+
+ seq = []
+ for node in doc.getElementsByTagName("event"):
+ seq.append(Event(_extract(node, "id"), self.network))
+
+ return seq
def md5(text):
- """Returns the md5 hash of a string."""
-
- hash = hashlib.md5()
- hash.update(text.encode("utf-8"))
-
- return hash.hexdigest()
-
-def enable_proxy(host, port):
- """Enable a default web proxy."""
-
- global __proxy
- global __proxy_enabled
-
- __proxy = [host, _number(port)]
- __proxy_enabled = True
-
-def disable_proxy():
- """Disable using the web proxy."""
-
- global __proxy_enabled
-
- __proxy_enabled = False
-
-def is_proxy_enabled():
- """Returns True if a web proxy is enabled."""
-
- global __proxy_enabled
-
- return __proxy_enabled
-
-def _get_proxy():
- """Returns proxy details."""
-
- global __proxy
-
- return __proxy
+ """Returns the md5 hash of a string."""
+
+ h = hashlib.md5()
+ h.update(_string(text))
+
+ return h.hexdigest()
def async_call(sender, call, callback = None, call_args = None, callback_args = None):
- """This is the function for setting up an asynchronous operation.
- * call: The function to call asynchronously.
- * callback: The function to call after the operation is complete, Its prototype has to be like:
- callback(sender, output[, param1, param3, ... ])
- * call_args: A sequence of args to be passed to call.
- * callback_args: A sequence of args to be passed to callback.
- """
-
- thread = _ThreadedCall(sender, call, call_args, callback, callback_args)
- thread.start()
-
-def enable_caching(cache_filename = None):
- """Enables caching request-wide for all cachable calls. Uses a shelve.DbfilenameShelf object.
- * cache_filename: A filename for the db. Defaults to a temporary filename in the tmp directory.
- """
-
- global __cache_shelf
- global __cache_filename
-
- if not cache_filename:
- cache_filename = tempfile.mktemp(prefix="pylast_tmp_")
-
- __cache_filename = cache_filename
- __cache_shelf = shelve.open(__cache_filename)
-
-def disable_caching():
- """Disables all caching features."""
-
- global __cache_shelf
- __cache_shelf = None
-
-def close_shelf():
- global __cache_shelf
- if __cache_shelf:
- __cache_shelf.close()
- __cache_shelf = None
-
-def is_caching_enabled():
- """Returns True if caching is enabled."""
-
- global __cache_shelf
- return not (__cache_shelf == None)
-
-def get_cache_filename():
- """Returns filename of the cache db in use."""
-
- global __cache_filename
- return __cache_filename
-
-def get_cache_shelf():
- """Returns the Shelf object used for caching."""
-
- global __cache_shelf
- return __cache_shelf
-
+ """This is the function for setting up an asynchronous operation.
+ * call: The function to call asynchronously.
+ * callback: The function to call after the operation is complete, Its prototype has to be like:
+ callback(sender, output[, param1, param3, ... ])
+ * call_args: A sequence of args to be passed to call.
+ * callback_args: A sequence of args to be passed to callback.
+ """
+
+ thread = _ThreadedCall(sender, call, call_args, callback, callback_args)
+ thread.start()
+
+def _unicode(text):
+ if type(text) == unicode:
+ return text
+
+ if type(text) == int:
+ return unicode(text)
+
+ return unicode(text, "utf-8")
+
+def _string(text):
+ if type(text) == str:
+ return text
+
+ if type(text) == int:
+ return str(text)
+
+ return text.encode("utf-8")
+
+def _collect_nodes(limit, sender, method_name, cacheable, params=None):
+ """
+ Returns a sequqnce of dom.Node objects about as close to
+ limit as possible
+ """
+
+ if not limit: limit = sys.maxint
+ if not params: params = sender._get_params()
+
+ nodes = []
+ page = 1
+ end_of_pages = False
+
+ while len(nodes) < limit and not end_of_pages:
+ params["page"] = str(page)
+ doc = sender._request(method_name, cacheable, params)
+
+ main = doc.documentElement.childNodes[1]
+
+ if main.hasAttribute("totalPages"):
+ total_pages = _number(main.getAttribute("totalPages"))
+ elif main.hasAttribute("totalpages"):
+ total_pages = _number(main.getAttribute("totalpages"))
+ else:
+ raise Exception("No total pages attribute")
+
+ for node in main.childNodes:
+ if not node.nodeType == xml.dom.Node.TEXT_NODE and len(nodes) < limit:
+ nodes.append(node)
+
+ if page >= total_pages:
+ end_of_pages = True
+
+ page += 1
+
+ return nodes
+
def _extract(node, name, index = 0):
- """Extracts a value from the xml string"""
-
- nodes = node.getElementsByTagName(name)
-
- if len(nodes):
- if nodes[index].firstChild:
- return nodes[index].firstChild.data.strip()
- else:
- return None
+ """Extracts a value from the xml string"""
+
+ nodes = node.getElementsByTagName(name)
+
+ if len(nodes):
+ if nodes[index].firstChild:
+ return _unescape_htmlentity(nodes[index].firstChild.data.strip())
+ else:
+ return None
def _extract_all(node, name, limit_count = None):
- """Extracts all the values from the xml string. returning a list."""
-
- list = []
-
- for i in range(0, len(node.getElementsByTagName(name))):
- if len(list) == limit_count:
- break
-
- list.append(_extract(node, name, i))
-
- return list
-
-def _get_url_safe(text):
- """Does all kinds of tricks on a text to make it safe to use in a url."""
-
- if type(text) == type(unicode()):
- text = text
-
- return urllib.quote_plus(urllib.quote_plus(text)).lower()
+ """Extracts all the values from the xml string. returning a list."""
+
+ seq = []
+
+ for i in range(0, len(node.getElementsByTagName(name))):
+ if len(seq) == limit_count:
+ break
+
+ seq.append(_extract(node, name, i))
+
+ return seq
+
+def _url_safe(text):
+ """Does all kinds of tricks on a text to make it safe to use in a url."""
+
+ if type(text) == unicode:
+ text = text.encode('utf-8')
+
+ return urllib.quote_plus(urllib.quote_plus(text)).lower()
def _number(string):
- """Extracts an int from a string. Returns a 0 if None or an empty string was passed."""
-
- if not string:
- return 0
- elif string == "":
- return 0
- else:
- return int(string)
-
-def search_for_album(album_name, api_key, api_secret, session_key):
- """Searches for an album by its name. Returns a AlbumSearch object.
- Use get_next_page() to retreive sequences of results."""
-
- return AlbumSearch(album_name, api_key, api_secret, session_key)
-
-def search_for_artist(artist_name, api_key, api_secret, session_key):
- """Searches of an artist by its name. Returns a ArtistSearch object.
- Use get_next_page() to retreive sequences of results."""
-
- return ArtistSearch(artist_name, api_key, api_secret, session_key)
-
-def search_for_tag(tag_name, api_key, api_secret, session_key):
- """Searches of a tag by its name. Returns a TagSearch object.
- Use get_next_page() to retreive sequences of results."""
-
- return TagSearch(tag_name, api_key, api_secret, session_key)
-
-def search_for_track(artist_name, track_name, api_key, api_secret, session_key):
- """Searches of a track by its name and its artist. Set artist to an empty string if not available.
- Returns a TrackSearch object.
- Use get_next_page() to retreive sequences of results."""
-
- return TrackSearch(artist_name, track_name, api_key, api_secret, session_key)
-
-def search_for_venue(venue_name, country_name, api_key, api_secret, session_key):
- """Searches of a venue by its name and its country. Set country_name to an empty string if not available.
- Returns a VenueSearch object.
- Use get_next_page() to retreive sequences of results."""
-
- return VenueSearch(venue_name, country_name, api_key, api_secret, session_key)
-
+ """
+ Extracts an int from a string. Returns a 0 if None or an empty string was passed
+ """
+
+ if not string:
+ return 0
+ elif string == "":
+ return 0
+ else:
+ try:
+ return int(string)
+ except ValueError:
+ return float(string)
+
+def _unescape_htmlentity(string):
+
+ string = _unicode(string)
+
+ mapping = htmlentitydefs.name2codepoint
+ for key in mapping:
+ string = string.replace("&%s;" %key, unichr(mapping[key]))
+
+ return string
+
def extract_items(topitems_or_libraryitems):
- """Extracts a sequence of items from a sequence of TopItem or LibraryItem objects."""
-
- list = []
- for i in topitems_or_libraryitems:
- list.append(i.get_item())
-
- return list
-
-def get_top_tags(api_key, api_secret, session_key):
- """Returns a sequence of the most used Last.fm tags as a sequence of TopItem objects."""
-
- doc = _Request("tag.getTopTags", dict(), api_key, api_secret, session_key).execute(True)
- list = []
- for node in doc.getElementsByTagName("tag"):
- tag = Tag(_extract(node, "name"), api_key, api_secret, session_key)
- weight = _extract(node, "count")
-
- list.append(TopItem(tag, weight))
-
- return list
-
-def get_track_by_mbid(mbid, api_key, api_secret, session_key):
- """Looks up a track by its MusicBrainz ID."""
-
- params = {"mbid": unicode(mbid)}
-
- doc = _Request("track.getInfo", params, api_key, api_secret, session_key).execute(True)
-
- return Track(_extract(doc, "name", 1), _extract(doc, "name"), api_key, api_secret, session_key)
-
-def get_artist_by_mbid(mbid, api_key, api_secret, session_key):
- """Loooks up an artist by its MusicBrainz ID."""
-
- params = {"mbid": unicode(mbid)}
-
- doc = _Request("artist.getInfo", params, api_key, api_secret, session_key).execute(True)
-
- return Artist(_extract(doc, "name"), api_key, api_secret, session_key)
-
-def get_album_by_mbid(mbid, api_key, api_secret, session_key):
- """Looks up an album by its MusicBrainz ID."""
-
- params = {"mbid": unicode(mbid)}
-
- doc = _Request("album.getInfo", params, api_key, api_secret, session_key).execute(True)
-
- return Album(_extract(doc, "artist"), _extract(doc, "name"), api_key, api_secret, session_key)
-
-def _delay_call():
- """Makes sure that web service calls are at least a second apart."""
-
- global __last_call_time
-
- # delay time in seconds
- DELAY_TIME = 1.0
- now = time.time()
-
- if (now - __last_call_time) < DELAY_TIME:
- time.sleep(1)
-
- __last_call_time = now
-
-def clear_cache():
- """Clears the cache data and starts fresh."""
-
- global __cache_dir
-
-
- if not os.path.exists(__cache_dir):
- return
-
- for file in os.listdir(__cache_dir):
- os.remove(os.path.join(__cache_dir, file))
-
-
-
-# ------------------------------------------------------------
-
-class ScrobblingException(Exception):
- def __inint__(self, message):
- Exception.__init__(self)
- self.message = message
-
- def __repr__(self):
- return self.message
-
-class BannedClient(ScrobblingException):
- def __init__(self):
- ScrobblingException.__init__(self, "This version of the client has been banned")
-
-class BadAuthentication(ScrobblingException):
- def __init__(self):
- ScrobblingException.__init__(self, "Bad authentication token")
-
-class BadTime(ScrobblingException):
- def __init__(self):
- ScrobblingException.__init__(self, "Time provided is not close enough to current time")
-
-class BadSession(ScrobblingException):
- def __init__(self):
- ScrobblingException.__init__(self, "Bad session id, consider re-handshaking")
+ """Extracts a sequence of items from a sequence of TopItem or LibraryItem objects."""
+
+ seq = []
+ for i in topitems_or_libraryitems:
+ seq.append(i.get_item())
+
+ return seq
+
+class ScrobblingError(Exception):
+ def __init__(self, message):
+ Exception.__init__(self)
+ self.message = message
+
+ @_string_output
+ def __str__(self):
+ return self.message
+
+class BannedClientError(ScrobblingError):
+ def __init__(self):
+ ScrobblingError.__init__(self, "This version of the client has been banned")
+
+class BadAuthenticationError(ScrobblingError):
+ def __init__(self):
+ ScrobblingError.__init__(self, "Bad authentication token")
+
+class BadTimeError(ScrobblingError):
+ def __init__(self):
+ ScrobblingError.__init__(self, "Time provided is not close enough to current time")
+
+class BadSessionError(ScrobblingError):
+ def __init__(self):
+ ScrobblingError.__init__(self, "Bad session id, consider re-handshaking")
class _ScrobblerRequest(object):
-
- def __init__(self, url, params):
- self.params = params
- self.hostname = url[url.find("//") + 2:url.rfind("/")]
- self.subdir = url[url.rfind("/"):]
-
- def execute(self):
- """Returns a string response of this request."""
-
- connection = httplib.HTTPConnection(self.hostname)
-
- data = []
- for name in self.params.keys():
- value = urllib.quote_plus(self.params[name])
- data.append('='.join((name, value)))
- data = "&".join(data)
-
- headers = {
- "Content-type": "application/x-www-form-urlencoded",
- "Accept-Charset": "utf-8",
- "User-Agent": __name__ + "/" + __version__,
- "HOST": self.hostname
- }
-
- debug("Scrobbler Request:\n\tHOST :" + self.hostname + "\n\tSUBDIR: " + self.subdir +
- "\n\tDATA:" + repr(data) + "\n\tHEADERS: " + repr(headers))
- connection.request("POST", self.subdir, data, headers)
- response = connection.getresponse().read()
-
- self._check_response_for_errors(response)
- debug("Scrobbler Response:\n\t" + response)
-
- return response
-
- def _check_response_for_errors(self, response):
- """When passed a string response it checks for erros, raising
- any exceptions as necessary."""
-
- lines = response.split("\n")
- status_line = lines[0]
-
- if status_line == "OK":
- return
- elif status_line == "BANNED":
- raise BannedClient()
- elif status_line == "BADAUTH":
- raise BadAuthenticationException()
- elif status_line == "BADTIME":
- raise BadTime()
- elif status_line == "BADSESSION":
- raise BadSession()
- elif status_line.startswith("FAILED "):
- reason = status_line[status_line.find("FAILED ")+len("FAILED "):]
- raise ScrobblingException(reason)
-
+
+ def __init__(self, url, params, network, type="POST"):
+ self.params = params
+ self.type = type
+ (self.hostname, self.subdir) = urllib.splithost(url[len("http:"):])
+ self.network = network
+
+ def execute(self):
+ """Returns a string response of this request."""
+
+ connection = httplib.HTTPConnection(self.hostname)
+
+ data = []
+ for name in self.params.keys():
+ value = urllib.quote_plus(self.params[name])
+ data.append('='.join((name, value)))
+ data = "&".join(data)
+
+ headers = {
+ "Content-type": "application/x-www-form-urlencoded",
+ "Accept-Charset": "utf-8",
+ "User-Agent": "pylast" + "/" + __version__,
+ "HOST": self.hostname
+ }
+
+ if self.type == "GET":
+ connection.request("GET", self.subdir + "?" + data, headers = headers)
+ else:
+ connection.request("POST", self.subdir, data, headers)
+ response = connection.getresponse().read()
+
+ self._check_response_for_errors(response)
+
+ return response
+
+ def _check_response_for_errors(self, response):
+ """When passed a string response it checks for erros, raising
+ any exceptions as necessary."""
+
+ lines = response.split("\n")
+ status_line = lines[0]
+
+ if status_line == "OK":
+ return
+ elif status_line == "BANNED":
+ raise BannedClientError()
+ elif status_line == "BADAUTH":
+ raise BadAuthenticationError()
+ elif status_line == "BadTimeError":
+ raise BadTimeError()
+ elif status_line == "BadSessionError":
+ raise BadSessionError()
+ elif status_line.startswith("FAILED "):
+ reason = status_line[status_line.find("FAILED ")+len("FAILED "):]
+ raise ScrobblingError(reason)
+
class Scrobbler(object):
- """A class for scrobbling tracks to Last.fm"""
-
- session_id = None
- nowplaying_url = None
- submissions_url = None
-
- def __init__(self, client_id, client_version, username, md5_password):
- self.client_id = client_id
- self.client_version = client_version
- self.username = username
- self.password = md5_password
-
- def _do_handshake(self):
- """Handshakes with the server"""
-
- timestamp = str(int(time.time()))
- token = md5(self.password + timestamp)
-
- params = {"hs": "true", "p": "1.2.1", "c": self.client_id,
- "v": self.client_version, "u": self.username, "t": timestamp,
- "a": token}
-
- global SUBMISSION_SERVER
- response = _ScrobblerRequest(SUBMISSION_SERVER, params).execute().split("\n")
-
- self.session_id = response[1]
- self.nowplaying_url = response[2]
- self.submissions_url = response[3]
-
- def _get_session_id(self, new = False):
- """Returns a handshake. If new is true, then it will be requested from the server
- even if one was cached."""
-
- if not self.session_id or new:
- debug("Doing a scrobbling handshake")
- self._do_handshake()
-
- return self.session_id
-
- def report_now_playing(self, artist, title, album = "", duration = "", track_number = "", mbid = ""):
-
- params = {"s": self._get_session_id(), "a": artist, "t": title,
- "b": album, "l": duration, "n": track_number, "m": mbid}
-
- response = _ScrobblerRequest(self.nowplaying_url, params).execute()
-
- try:
- _ScrobblerRequest(self.nowplaying_url, params).execute()
- except BadSession:
- self._do_handshake()
- self.report_now_playing(artist, title, album, duration, track_number, mbid)
-
- info(artist + " - " + title + " was reported as now-playing")
-
- def scrobble(self, artist, title, time_started, source, mode, duration, album="", track_number="", mbid=""):
- """Scrobble a track. parameters:
- artist: Artist name.
- title: Track title.
- time_started: UTC timestamp of when the track started playing.
- source: The source of the track
- SCROBBLE_SOURCE_USER: Chosen by the user (the most common value, unless you have a reason for choosing otherwise, use this).
- SCROBBLE_SOURCE_NON_PERSONALIZED_BROADCAST: Non-personalised broadcast (e.g. Shoutcast, BBC Radio 1).
- SCROBBLE_SOURCE_PERSONALIZED_BROADCAST: Personalised recommendation except Last.fm (e.g. Pandora, Launchcast).
- SCROBBLE_SOURCE_LASTFM: ast.fm (any mode). In this case, the 5-digit recommendation_key value must be set.
- SCROBBLE_SOURCE_UNKNOWN: Source unknown.
- mode: The submission mode
- SCROBBLE_MODE_PLAYED: The track was played.
- SCROBBLE_MODE_SKIPPED: The track was skipped (Only if source was Last.fm)
- SCROBBLE_MODE_BANNED: The track was banned (Only if source was Last.fm)
- duration: Track duration in seconds.
- album: The album name.
- track_number: The track number on the album.
- mbid: MusicBrainz ID.
- """
-
- params = {"s": self._get_session_id(), "a[0]": artist, "t[0]": title,
- "i[0]": str(time_started), "o[0]": source, "r[0]": mode, "l[0]": str(duration),
- "b[0]": album, "n[0]": track_number, "m[0]": mbid}
-
- response = _ScrobblerRequest(self.submissions_url, params).execute()
- info(artist + " - " + title + " was scrobbled")
-
-
-#LFM_API_KEY = '3b954460f7b207e5414ffdf8c5710592'
-#print search_for_track('Air', 'Universal Traveller', LFM_API_KEY, None, None).get_next_page()[0].get_top_tags()
\ No newline at end of file
+ """A class for scrobbling tracks to Last.fm"""
+
+ session_id = None
+ nowplaying_url = None
+ submissions_url = None
+
+ def __init__(self, network, client_id, client_version):
+ self.client_id = client_id
+ self.client_version = client_version
+ self.username = network.username
+ self.password = network.password_hash
+ self.network = network
+
+ def _do_handshake(self):
+ """Handshakes with the server"""
+
+ timestamp = str(int(time.time()))
+ token = md5(self.password + timestamp)
+
+ params = {"hs": "true", "p": "1.2.1", "c": self.client_id,
+ "v": self.client_version, "u": self.username, "t": timestamp,
+ "a": token}
+
+ server = self.network.submission_server
+ response = _ScrobblerRequest(server, params, self.network, "GET").execute().split("\n")
+
+ self.session_id = response[1]
+ self.nowplaying_url = response[2]
+ self.submissions_url = response[3]
+
+ def _get_session_id(self, new = False):
+ """Returns a handshake. If new is true, then it will be requested from the server
+ even if one was cached."""
+
+ if not self.session_id or new:
+ self._do_handshake()
+
+ return self.session_id
+
+ def report_now_playing(self, artist, title, album = "", duration = "", track_number = "", mbid = ""):
+
+ params = {"s": self._get_session_id(), "a": artist, "t": title,
+ "b": album, "l": duration, "n": track_number, "m": mbid}
+
+ try:
+ _ScrobblerRequest(self.nowplaying_url, params, self.network).execute()
+ except BadSessionError:
+ self._do_handshake()
+ self.report_now_playing(artist, title, album, duration, track_number, mbid)
+
+ def scrobble(self, artist, title, time_started, source, mode, duration, album="", track_number="", mbid=""):
+ """Scrobble a track. parameters:
+ artist: Artist name.
+ title: Track title.
+ time_started: UTC timestamp of when the track started playing.
+ source: The source of the track
+ SCROBBLE_SOURCE_USER: Chosen by the user (the most common value, unless you have a reason for choosing otherwise, use this).
+ SCROBBLE_SOURCE_NON_PERSONALIZED_BROADCAST: Non-personalised broadcast (e.g. Shoutcast, BBC Radio 1).
+ SCROBBLE_SOURCE_PERSONALIZED_BROADCAST: Personalised recommendation except Last.fm (e.g. Pandora, Launchcast).
+ SCROBBLE_SOURCE_LASTFM: ast.fm (any mode). In this case, the 5-digit recommendation_key value must be set.
+ SCROBBLE_SOURCE_UNKNOWN: Source unknown.
+ mode: The submission mode
+ SCROBBLE_MODE_PLAYED: The track was played.
+ SCROBBLE_MODE_LOVED: The user manually loved the track (implies a listen)
+ SCROBBLE_MODE_SKIPPED: The track was skipped (Only if source was Last.fm)
+ SCROBBLE_MODE_BANNED: The track was banned (Only if source was Last.fm)
+ duration: Track duration in seconds.
+ album: The album name.
+ track_number: The track number on the album.
+ mbid: MusicBrainz ID.
+ """
+
+ params = {"s": self._get_session_id(), "a[0]": _string(artist), "t[0]": _string(title),
+ "i[0]": str(time_started), "o[0]": source, "r[0]": mode, "l[0]": str(duration),
+ "b[0]": _string(album), "n[0]": track_number, "m[0]": mbid}
+
+ _ScrobblerRequest(self.submissions_url, params, self.network).execute()