1 # -*- coding: utf-8 -*-
3 # Copyright (c) 2009-2014, 2021 kaliko <kaliko@azylum.org>
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 Consume Last.fm web service
25 __author__ = 'Jack Kaliko'
29 from sima.lib.meta import Artist
30 from sima.lib.track import Track
32 from sima.lib.http import HttpClient
33 from sima.utils.utils import WSError, WSNotFound
34 from sima.utils.utils import getws
35 if len(LFM.get('apikey')) == 43: # simple hack allowing imp.reload
40 """Last.fm http client
42 root_url = 'http://{host}/{version}/'.format(**LFM)
45 """HTTP cache to use, in memory or persitent.
47 :param BaseCache cache: Set a cache, defaults to `False`.
54 self.http = HttpClient(cache=self.cache, stats=self.stats)
57 def _controls_answer(self, ans):
61 code = ans.get('error')
62 mess = ans.get('message')
64 raise WSNotFound(f'{mess}: "{self.artist}"')
68 def _forge_payload(self, artist, method='similar', track=None):
71 payloads = dict({'similar': {'method': 'artist.getsimilar',},
72 'top': {'method': 'artist.gettoptracks',},
73 'track': {'method': 'track.getsimilar',},
74 'info': {'method': 'artist.getinfo',},
76 payload = payloads.get(method)
77 payload.update(api_key=LFM.get('apikey'), format='json')
78 if not isinstance(artist, Artist):
79 raise TypeError(f'"{artist!r}" not an Artist object')
82 payload.update(mbid=f'{artist.mbid}')
84 payload.update(artist=artist.name,
86 payload.update(results=100)
88 payload.update(track=track)
89 # > hashing the URL into a cache key
90 # return a sorted list of 2-tuple to have consistent cache
91 return sorted(payload.items(), key=lambda param: param[0])
93 def get_similar(self, artist):
94 """Fetch similar artists
96 :param sima.lib.meta.Artist artist: `Artist` to fetch similar artists from
97 :returns: generator of :class:`sima.lib.meta.Artist`
99 payload = self._forge_payload(artist)
101 ans = self.http(self.root_url, payload)
104 except ValueError as err:
105 # Corrupted/malformed cache? cf. gitlab issue #35
106 raise WSError('Malformed json, try purging the cache: %s') from err
107 self._controls_answer(ans.json()) # pylint: disable=no-member
108 # Artist might be found but return no 'artist' list…
109 # cf. "Mulatu Astatqe" vs. "Mulatu Astatqé" with autocorrect=0
110 # json format is broken IMHO, xml is more consistent IIRC
112 # >>> {"similarartists":{"#text":"\n","artist":"Mulatu Astatqe"}}
113 # autocorrect=1 should fix it, checking anyway.
114 simarts = ans.json().get('similarartists').get('artist') # pylint: disable=no-member
115 if not isinstance(simarts, list):
116 raise WSError('Artist found but no similarities returned')
117 for art in ans.json().get('similarartists').get('artist'): # pylint: disable=no-member
118 yield Artist(name=art.get('name'), mbid=art.get('mbid', None))
120 def get_toptrack(self, artist):
121 """Fetch artist top tracks
123 :param sima.lib.meta.Artist artist: `Artist` to fetch top tracks from
124 :returns: generator of :class:`sima.lib.track.Track`
126 payload = self._forge_payload(artist, method='top')
127 ans = self.http(self.root_url, payload)
128 self._controls_answer(ans.json()) # pylint: disable=no-member
129 tops = ans.json().get('toptracks').get('track') # pylint: disable=no-member
130 art = {'artist': artist.name,
131 'musicbrainz_artistid': artist.mbid,}
133 for key in ['artist', 'streamable', 'listeners',
134 'url', 'image', '@attr']:
138 song.update(title=song.pop('name'))
139 song.update(time=song.pop('duration', 0))
143 # vim: ai ts=4 sw=4 sts=4 expandtab