From a667e4b8ad84aebb59c73a926fa97ee1e971038c Mon Sep 17 00:00:00 2001 From: tringler Date: Fri, 19 Jan 2018 20:00:00 +0100 Subject: [PATCH 01/15] Changed requirement to original spotipy repo --- homeassistant/components/media_player/spotify.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index 734285d918a334..8df90ac7d4f57c 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -20,9 +20,8 @@ CONF_NAME, STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv -COMMIT = '544614f4b1d508201d363e84e871f86c90aa26b2' -REQUIREMENTS = ['https://github.com/happyleavesaoc/spotipy/' - 'archive/%s.zip#spotipy==2.4.4' % COMMIT] +REQUIREMENTS = ['https://github.com/plamere/spotipy/' + 'archive/master.zip#spotipy==2.4.4'] DEPENDENCIES = ['http'] From 618e7510302ac218877cdfc489532f3fc7b19421 Mon Sep 17 00:00:00 2001 From: tringler Date: Wed, 24 Jan 2018 01:51:44 +0100 Subject: [PATCH 02/15] Updated requirements --- requirements_all.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_all.txt b/requirements_all.txt index 840ed5a834ac7c..87da6ff41bdc96 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -352,7 +352,7 @@ httplib2==0.10.3 https://github.com/aparraga/braviarc/archive/0.3.7.zip#braviarc==0.3.7 # homeassistant.components.media_player.spotify -https://github.com/happyleavesaoc/spotipy/archive/544614f4b1d508201d363e84e871f86c90aa26b2.zip#spotipy==2.4.4 +https://github.com/plamere/spotipy/archive/master.zip#spotipy==2.4.4 # homeassistant.components.netatmo https://github.com/jabesq/netatmo-api-python/archive/v0.9.2.1.zip#lnetatmo==0.9.2.1 From aec8885d5bcb4460d734fd30463030ef67da4192 Mon Sep 17 00:00:00 2001 From: tringler Date: Wed, 24 Jan 2018 01:55:18 +0100 Subject: [PATCH 03/15] Spotify: Play owned/starred playlist by name/uri --- .../components/media_player/spotify.py | 89 ++++++++++++++++++- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index 8df90ac7d4f57c..0123a2d5f371a1 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -6,6 +6,8 @@ """ import logging from datetime import timedelta +import json +import os import voluptuous as vol @@ -31,7 +33,8 @@ SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK | SUPPORT_SELECT_SOURCE |\ SUPPORT_PLAY_MEDIA | SUPPORT_SHUFFLE_SET -SCOPE = 'user-read-playback-state user-modify-playback-state user-read-private' +SCOPE = 'user-read-playback-state user-modify-playback-state \ + user-read-private playlist-read-private' DEFAULT_CACHE_PATH = '.spotify-token-cache' AUTH_CALLBACK_PATH = '/api/spotify' AUTH_CALLBACK_NAME = 'api:spotify' @@ -56,6 +59,7 @@ }) SCAN_INTERVAL = timedelta(seconds=30) +PERSISTENCE = '.spotify_playlists.json' def request_configuration(hass, config, add_devices, oauth): @@ -89,8 +93,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): configurator = hass.components.configurator configurator.request_done(hass.data.get(DOMAIN)) del hass.data[DOMAIN] + playlistdata = hass.data[DOMAIN] = PlaylistData(hass) + hass.http.register_view(SpotifyPlaylistView) player = SpotifyMediaPlayer(oauth, config.get(CONF_NAME, DEFAULT_NAME), - config[CONF_ALIASES]) + config[CONF_ALIASES], playlistdata) add_devices([player], True) @@ -118,7 +124,7 @@ def get(self, request): class SpotifyMediaPlayer(MediaPlayerDevice): """Representation of a Spotify controller.""" - def __init__(self, oauth, name, aliases): + def __init__(self, oauth, name, aliases, playlistdata): """Initialize.""" self._name = name self._oauth = oauth @@ -136,6 +142,7 @@ def __init__(self, oauth, name, aliases): self._user = None self._aliases = aliases self._token_info = self._oauth.get_cached_token() + self._playlistdata = playlistdata def refresh_spotify_instance(self): """Fetch a new spotify instance.""" @@ -209,6 +216,39 @@ def update(self): self._volume = device.get('volume_percent') / 100 if device.get('name'): self._current_device = device.get('name') + # Discover playlists + discovered_playlists = self._player.current_user_playlists() + if discovered_playlists is not None: + new_playlists = {} + for discovered_playlist in discovered_playlists['items']: + playlist = { + 'name': discovered_playlist['name'], + 'id': discovered_playlist['id'], + 'collaborative': discovered_playlist['collaborative'], + 'public': discovered_playlist['public'], + 'owner': discovered_playlist['owner']['id'], + 'uri': discovered_playlist['uri'], + 'snapshot_id': discovered_playlist['snapshot_id'], + 'images': discovered_playlist['images'] + } + new_playlists[discovered_playlist['id']] = playlist + if not (discovered_playlist['id'] + in self._playlistdata.playlists): + self._playlistdata.update(playlist) + _LOGGER.info("New playlist discovered: {name} ({uri})" + .format(name=discovered_playlist['name'], + uri=discovered_playlist['uri'])) + playlist_diff = set(self._playlistdata.playlists.keys()) - \ + set(new_playlists.keys()) + if playlist_diff is not None: + for removed_playlist in playlist_diff: + _LOGGER.info("Playlist removed: {name} ({uri})" + .format(name=self._playlistdata.playlists + [removed_playlist]['name'], + uri=self._playlistdata.playlists + [removed_playlist]['uri'])) + del self._playlistdata.playlists[removed_playlist] + self._playlistdata.update() def set_volume_level(self, volume): """Set the volume level.""" @@ -246,6 +286,9 @@ def play_media(self, media_type, media_id, **kwargs): if media_type == MEDIA_TYPE_MUSIC: kwargs['uris'] = [media_id] elif media_type == MEDIA_TYPE_PLAYLIST: + for key, val in self._playlistdata.playlists.items(): + if media_id in str(val): + media_id = val["uri"] kwargs['context_uri'] = media_id else: _LOGGER.error("media type %s is not supported", media_type) @@ -327,3 +370,43 @@ def supported_features(self): def media_content_type(self): """Return the media type.""" return MEDIA_TYPE_MUSIC + + +class SpotifyPlaylistView(HomeAssistantView): + """View to retrieve Spotify Playlists.""" + + url = '/api/spotify/playlists' + name = "api:spotify/playlists" + + @callback + def get(self, request): + """Retrieve playlists.""" + return self.json(request.app['hass'].data[DOMAIN].playlists) + + +class PlaylistData: + """Class to hold Spotify playlist data.""" + def __init__(self, hass): + """Initialize the Spotify playlist store list.""" + self.hass = hass + self.playlists = {} + + def update(self, playlist=None): + """Add a playlist.""" + if playlist is not None: + self.playlists[playlist['id']] = playlist + self.save() + self.playlists = self.load() + + def load(self): + """Load the playlists.""" + path = self.hass.config.path(PERSISTENCE) + if not os.path.isfile(path): + return {} + with open(path) as file: + return json.loads(file.read()) + + def save(self): + """Save playlists.""" + with open(self.hass.config.path(PERSISTENCE), 'wt') as file: + file.write(json.dumps(self.playlists)) From d8467531f04e591623aa752b3eeaa677a1501049 Mon Sep 17 00:00:00 2001 From: tringler Date: Wed, 24 Jan 2018 02:01:56 +0100 Subject: [PATCH 04/15] Logging improvements (PEP 3101) --- homeassistant/components/media_player/spotify.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index 0123a2d5f371a1..8aee34511703b1 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -187,7 +187,8 @@ def update(self): device_diff = {name: id for name, id in self._devices.items() if old_devices.get(name, None) is None} if device_diff: - _LOGGER.info("New Devices: %s", str(device_diff)) + _LOGGER.info("New Devices: {device}" + .format(device=str(device_diff))) # Current playback state current = self._player.current_playback() if current is None: @@ -291,10 +292,12 @@ def play_media(self, media_type, media_id, **kwargs): media_id = val["uri"] kwargs['context_uri'] = media_id else: - _LOGGER.error("media type %s is not supported", media_type) + _LOGGER.error("media type {media_type} is not supported" + .format(media_type=media_type)) return if not media_id.startswith('spotify:'): - _LOGGER.error("media id must be spotify uri") + _LOGGER.error("media id {media_id} is not a valid spotify uri" + .format(media_id=media_id)) return self._player.start_playback(**kwargs) From b31bfb5f51a36e3aa29190cf7c4acbbfd856fc90 Mon Sep 17 00:00:00 2001 From: tringler Date: Wed, 24 Jan 2018 09:41:15 +0100 Subject: [PATCH 05/15] Removed unused key --- homeassistant/components/media_player/spotify.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index 8aee34511703b1..1abf34292eea4d 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -287,9 +287,9 @@ def play_media(self, media_type, media_id, **kwargs): if media_type == MEDIA_TYPE_MUSIC: kwargs['uris'] = [media_id] elif media_type == MEDIA_TYPE_PLAYLIST: - for key, val in self._playlistdata.playlists.items(): - if media_id in str(val): - media_id = val["uri"] + for playlist in self._playlistdata.playlists.values(): + if media_id in str(playlist): + media_id = playlist["uri"] kwargs['context_uri'] = media_id else: _LOGGER.error("media type {media_type} is not supported" From 9160610415cf2e419bc69a4cc4d0f976831ca314 Mon Sep 17 00:00:00 2001 From: tringler Date: Wed, 24 Jan 2018 16:02:32 +0100 Subject: [PATCH 06/15] Revoked PEP 3101 annotation --- .../components/media_player/spotify.py | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index 1abf34292eea4d..576e1bb4cf1a25 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -187,8 +187,7 @@ def update(self): device_diff = {name: id for name, id in self._devices.items() if old_devices.get(name, None) is None} if device_diff: - _LOGGER.info("New Devices: {device}" - .format(device=str(device_diff))) + _LOGGER.info("New Devices: %s", str(device_diff)) # Current playback state current = self._player.current_playback() if current is None: @@ -236,18 +235,16 @@ def update(self): if not (discovered_playlist['id'] in self._playlistdata.playlists): self._playlistdata.update(playlist) - _LOGGER.info("New playlist discovered: {name} ({uri})" - .format(name=discovered_playlist['name'], - uri=discovered_playlist['uri'])) + _LOGGER.info("New playlist discovered: %s (%s)" + % (discovered_playlist['name'], + discovered_playlist['uri'])) playlist_diff = set(self._playlistdata.playlists.keys()) - \ set(new_playlists.keys()) if playlist_diff is not None: for removed_playlist in playlist_diff: - _LOGGER.info("Playlist removed: {name} ({uri})" - .format(name=self._playlistdata.playlists - [removed_playlist]['name'], - uri=self._playlistdata.playlists - [removed_playlist]['uri'])) + _LOGGER.info("Playlist removed: %s (%s)" + % (discovered_playlist['name'], + discovered_playlist['uri'])) del self._playlistdata.playlists[removed_playlist] self._playlistdata.update() @@ -292,12 +289,10 @@ def play_media(self, media_type, media_id, **kwargs): media_id = playlist["uri"] kwargs['context_uri'] = media_id else: - _LOGGER.error("media type {media_type} is not supported" - .format(media_type=media_type)) + _LOGGER.error("media type %s is not supported", media_type) return if not media_id.startswith('spotify:'): - _LOGGER.error("media id {media_id} is not a valid spotify uri" - .format(media_id=media_id)) + _LOGGER.error("media id %s is not a valid spotify uri", media_id) return self._player.start_playback(**kwargs) From caabeb96e749a72e640ee37ab20f9c7803b8852e Mon Sep 17 00:00:00 2001 From: tringler Date: Tue, 6 Feb 2018 20:14:25 +0100 Subject: [PATCH 07/15] Updated requirements by script --- requirements_all.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements_all.txt b/requirements_all.txt index 87da6ff41bdc96..925a4a8aede861 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -351,9 +351,6 @@ httplib2==0.10.3 # homeassistant.components.media_player.braviatv https://github.com/aparraga/braviarc/archive/0.3.7.zip#braviarc==0.3.7 -# homeassistant.components.media_player.spotify -https://github.com/plamere/spotipy/archive/master.zip#spotipy==2.4.4 - # homeassistant.components.netatmo https://github.com/jabesq/netatmo-api-python/archive/v0.9.2.1.zip#lnetatmo==0.9.2.1 @@ -366,6 +363,9 @@ https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad591540925 # homeassistant.components.switch.anel_pwrctrl https://github.com/mweinelt/anel-pwrctrl/archive/ed26e8830e28a2bfa4260a9002db23ce3e7e63d7.zip#anel_pwrctrl==0.0.1 +# homeassistant.components.media_player.spotify +https://github.com/plamere/spotipy/archive/master.zip#spotipy==2.4.4 + # homeassistant.components.switch.edimax https://github.com/rkabadi/pyedimax/archive/365301ce3ff26129a7910c501ead09ea625f3700.zip#pyedimax==0.1 From cb156ed1ba28af855e2c96ab24e33576352af1c7 Mon Sep 17 00:00:00 2001 From: tringler Date: Tue, 6 Feb 2018 21:05:34 +0100 Subject: [PATCH 08/15] Logging bugfix --- homeassistant/components/media_player/spotify.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index 576e1bb4cf1a25..4ead69bb1f4805 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -243,8 +243,8 @@ def update(self): if playlist_diff is not None: for removed_playlist in playlist_diff: _LOGGER.info("Playlist removed: %s (%s)" - % (discovered_playlist['name'], - discovered_playlist['uri'])) + % (removed_playlist['name'], + removed_playlist['uri'])) del self._playlistdata.playlists[removed_playlist] self._playlistdata.update() From 56bf57882a622936b1029e38b89f373596bbef28 Mon Sep 17 00:00:00 2001 From: tringler Date: Tue, 6 Feb 2018 21:11:13 +0100 Subject: [PATCH 09/15] Fixed pylint error: logging-not-lazy --- homeassistant/components/media_player/spotify.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index 4ead69bb1f4805..da8cbf73c8c49b 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -235,16 +235,16 @@ def update(self): if not (discovered_playlist['id'] in self._playlistdata.playlists): self._playlistdata.update(playlist) - _LOGGER.info("New playlist discovered: %s (%s)" - % (discovered_playlist['name'], - discovered_playlist['uri'])) + _LOGGER.info("New playlist discovered: %s (%s)", + (discovered_playlist['name'], + discovered_playlist['uri'])) playlist_diff = set(self._playlistdata.playlists.keys()) - \ set(new_playlists.keys()) if playlist_diff is not None: for removed_playlist in playlist_diff: - _LOGGER.info("Playlist removed: %s (%s)" - % (removed_playlist['name'], - removed_playlist['uri'])) + _LOGGER.info("Playlist removed: %s (%s)", + (removed_playlist['name'], + removed_playlist['uri'])) del self._playlistdata.playlists[removed_playlist] self._playlistdata.update() From 50d061d6bce466bfc97e24d770db7431322a1ab0 Mon Sep 17 00:00:00 2001 From: tringler Date: Tue, 6 Feb 2018 21:16:45 +0100 Subject: [PATCH 10/15] Fixed Flake8 error D204 --- homeassistant/components/media_player/spotify.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index da8cbf73c8c49b..38a54e04b76366 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -384,6 +384,7 @@ def get(self, request): class PlaylistData: """Class to hold Spotify playlist data.""" + def __init__(self, hass): """Initialize the Spotify playlist store list.""" self.hass = hass From 287ad24bc929aa1fd6c6ca4d068fa376bff4fd69 Mon Sep 17 00:00:00 2001 From: tringler Date: Tue, 6 Feb 2018 22:32:30 +0100 Subject: [PATCH 11/15] Fixed logging syntax error --- homeassistant/components/media_player/spotify.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index 38a54e04b76366..15f022769ba931 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -236,15 +236,15 @@ def update(self): in self._playlistdata.playlists): self._playlistdata.update(playlist) _LOGGER.info("New playlist discovered: %s (%s)", - (discovered_playlist['name'], - discovered_playlist['uri'])) + discovered_playlist['name'], + discovered_playlist['uri']) playlist_diff = set(self._playlistdata.playlists.keys()) - \ set(new_playlists.keys()) if playlist_diff is not None: for removed_playlist in playlist_diff: _LOGGER.info("Playlist removed: %s (%s)", - (removed_playlist['name'], - removed_playlist['uri'])) + removed_playlist['name'], + removed_playlist['uri']) del self._playlistdata.playlists[removed_playlist] self._playlistdata.update() From 5061856316ce58b26f6ec048da004f77f44d918e Mon Sep 17 00:00:00 2001 From: tringler Date: Mon, 19 Feb 2018 20:16:46 +0100 Subject: [PATCH 12/15] Simplified file handling --- homeassistant/components/media_player/spotify.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index 15f022769ba931..1fd835b3997da2 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -402,10 +402,10 @@ def load(self): path = self.hass.config.path(PERSISTENCE) if not os.path.isfile(path): return {} - with open(path) as file: - return json.loads(file.read()) + with open(path) as p_file: + return json.loads(p_file) def save(self): """Save playlists.""" - with open(self.hass.config.path(PERSISTENCE), 'wt') as file: - file.write(json.dumps(self.playlists)) + with open(self.hass.config.path(PERSISTENCE), 'wt') as p_file: + json.dump(self.playlist, yp_file) From c78009023fce8fd715d6d157a2223e566a2088b9 Mon Sep 17 00:00:00 2001 From: tringler Date: Mon, 19 Feb 2018 20:20:56 +0100 Subject: [PATCH 13/15] Removed useless load operations --- homeassistant/components/media_player/spotify.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index 1fd835b3997da2..d740f796261a67 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -388,14 +388,13 @@ class PlaylistData: def __init__(self, hass): """Initialize the Spotify playlist store list.""" self.hass = hass - self.playlists = {} + self.playlists = self.load() def update(self, playlist=None): """Add a playlist.""" if playlist is not None: self.playlists[playlist['id']] = playlist self.save() - self.playlists = self.load() def load(self): """Load the playlists.""" From f7b6eb01342101c009dc4d7295d4cc581fb39ca2 Mon Sep 17 00:00:00 2001 From: tringler Date: Mon, 19 Feb 2018 20:24:34 +0100 Subject: [PATCH 14/15] Fixed typo on def save() --- homeassistant/components/media_player/spotify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index d740f796261a67..74f0172e25d197 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -407,4 +407,4 @@ def load(self): def save(self): """Save playlists.""" with open(self.hass.config.path(PERSISTENCE), 'wt') as p_file: - json.dump(self.playlist, yp_file) + json.dump(self.playlist, p_file) From f10364d551d31c4ceec26b69a3753409804bbd10 Mon Sep 17 00:00:00 2001 From: tringler Date: Mon, 19 Feb 2018 22:10:37 +0100 Subject: [PATCH 15/15] Fixed another typo on def save() --- homeassistant/components/media_player/spotify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index 74f0172e25d197..a6e60d055d3f79 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -407,4 +407,4 @@ def load(self): def save(self): """Save playlists.""" with open(self.hass.config.path(PERSISTENCE), 'wt') as p_file: - json.dump(self.playlist, p_file) + json.dump(self.playlists, p_file)