From 3eb1c710e300556efdc8741e929b3095c8010279 Mon Sep 17 00:00:00 2001 From: kriim Date: Sat, 20 Feb 2016 20:17:19 +0100 Subject: [PATCH 1/5] #48 stream shows a configurable amount of tracks and playlists now --- README.rst | 1 + mopidy_soundcloud/__init__.py | 1 + mopidy_soundcloud/ext.conf | 5 ++++- mopidy_soundcloud/library.py | 21 +++++++++++++++----- mopidy_soundcloud/soundcloud.py | 31 ++++++++++++++--------------- tests/fixtures/sc-stream.yaml | 35 +++------------------------------ tests/test_api.py | 5 +++-- tests/test_extension.py | 1 + 8 files changed, 44 insertions(+), 56 deletions(-) diff --git a/README.rst b/README.rst index fa66e62..ddc6ff8 100644 --- a/README.rst +++ b/README.rst @@ -86,6 +86,7 @@ Configuration [soundcloud] auth_token = 1-1111-1111111 explore_songs = 25 + stream_entries = 100 Project resources diff --git a/mopidy_soundcloud/__init__.py b/mopidy_soundcloud/__init__.py index 5ffa58b..aa92999 100644 --- a/mopidy_soundcloud/__init__.py +++ b/mopidy_soundcloud/__init__.py @@ -25,6 +25,7 @@ def get_config_schema(self): schema['auth_token'] = config.Secret() schema['explore'] = config.Deprecated() schema['explore_pages'] = config.Deprecated() + schema['stream_entries'] = config.Integer() return schema def validate_config(self, config): # no_coverage diff --git a/mopidy_soundcloud/ext.conf b/mopidy_soundcloud/ext.conf index 9f0a4e8..efa494b 100644 --- a/mopidy_soundcloud/ext.conf +++ b/mopidy_soundcloud/ext.conf @@ -5,4 +5,7 @@ enabled = True auth_token = # Number of songs to fetch in explore section -explore_songs = 25 \ No newline at end of file +explore_songs = 25 + +# Number of tracks and playlists to fetch from user stream +stream_entries = 100 \ No newline at end of file diff --git a/mopidy_soundcloud/library.py b/mopidy_soundcloud/library.py index f9f3ecc..73296e4 100644 --- a/mopidy_soundcloud/library.py +++ b/mopidy_soundcloud/library.py @@ -11,7 +11,6 @@ from mopidy_soundcloud.soundcloud import safe_url - logger = logging.getLogger(__name__) @@ -27,7 +26,6 @@ def new_folder(name, path): def simplify_search_query(query): - if isinstance(query, dict): r = [] for v in query.values(): @@ -84,6 +82,21 @@ def list_liked(self): vfs_list[set_id] = new_folder(name, ['sets', set_id]) return vfs_list.values() + def list_stream(self): + vfs_list = collections.OrderedDict() + for data in self.backend.remote.get_user_stream(): + try: + name, set_id = data + except (TypeError, ValueError): + logger.debug('Adding track "%s" from stream to vfs' % data.name) + vfs_list[data.name] = models.Ref.track( + uri=data.uri, name=data.name + ) + else: + logger.debug('Adding playlist "%s" from stream to vfs' % name) + vfs_list[set_id] = new_folder(name, ['sets', set_id]) + return vfs_list.values() + def list_user_follows(self): sets_vfs = collections.OrderedDict() for (name, user_id) in self.backend.remote.get_followings(): @@ -166,9 +179,7 @@ def browse(self, uri): return self.list_liked() # User stream if 'stream' == req_type: - return self.tracklist_to_vfs( - self.backend.remote.get_user_stream() - ) + return self.list_stream() # root directory return self.vfs.get(uri, {}).values() diff --git a/mopidy_soundcloud/soundcloud.py b/mopidy_soundcloud/soundcloud.py index d7c4b9d..c67f1e4 100644 --- a/mopidy_soundcloud/soundcloud.py +++ b/mopidy_soundcloud/soundcloud.py @@ -71,6 +71,7 @@ def __init__(self, config): super(SoundCloudClient, self).__init__() token = config['auth_token'] self.explore_songs = config.get('explore_songs', 10) + self.stream_entries = config.get('stream_entries', 100) self.http_client = requests.Session() self.http_client.headers.update({'Authorization': 'OAuth %s' % token}) @@ -91,22 +92,20 @@ def user(self): @cache() def get_user_stream(self): # User timeline like playlist which uses undocumented api - # https://api.soundcloud.com/e1/me/stream.json?offset=0 - # returns five elements per request - tracks = [] - for sid in xrange(0, 2): - stream = self._get('e1/me/stream.json?offset=%s' % sid * 5) - for data in stream.get('collection'): - kind = data.get('type') - # multiple types of track with same data - if 'track' in kind: - tracks.append(self.parse_track(data.get('track'))) - if kind == 'playlist': - playlist = data.get('playlist').get('tracks') - if isinstance(playlist, collections.Iterable): - tracks.extend(self.parse_results(playlist)) - - return self.sanitize_tracks(tracks) + # https://api.soundcloud.com/e1/me/stream.json?limit=0 + # additional parameters are 'promoted_playlist=true' or 'offset=00000152-cc97-84a0-0000-00002a293ab5' + tracks_and_playlists = [] + stream = self._get('e1/me/stream.json?limit=%s' % self.stream_entries) + for data in stream.get('collection'): + kind = data.get('type') + # multiple types of track with same data + if 'track' in kind: + tracks_and_playlists.append(self.parse_track(data.get('track'))) + if 'playlist' in kind: + playlist = data.get('playlist') + tracks_and_playlists.append((playlist.get('title'), str(playlist.get('id')))) + + return self.sanitize_tracks(tracks_and_playlists) @cache() def get_explore_categories(self): diff --git a/tests/fixtures/sc-stream.yaml b/tests/fixtures/sc-stream.yaml index 37d8503..38030a0 100644 --- a/tests/fixtures/sc-stream.yaml +++ b/tests/fixtures/sc-stream.yaml @@ -6,38 +6,9 @@ interactions: Accept-Encoding: ['gzip, deflate'] Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996'] Connection: [keep-alive] - User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic] + User-Agent: [python-requests/2.9.1] method: GET - uri: https://api.soundcloud.com:443/e1/me/stream.json?offset=0e1/me/stream.json?offset=0e1/me/stream.json?offset=0e1/me/stream.json?offset=0e1/me/stream.json?offset=0&client_id=93e33e327fd8a9b77becd179652272e2 - response: - body: - string: !!binary | - H4sIAAAAAAAAAATBwQqAIAwA0H/ZOVxehehDIkJsoqFOdDtF/957L0QVHXSlQREcJJE+HaLv2UzW - dofCepvAFcliJZwyyFfzTG57yTXLZldYIHApFCRzA3ec3w8AAP//AwCP9XBDVwAAAA== - headers: - access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin'] - access-control-allow-methods: ['GET, PUT, POST, DELETE'] - access-control-allow-origin: ['*'] - access-control-expose-headers: [Date] - cache-control: ['private, max-age=0'] - content-encoding: [gzip] - content-length: ['106'] - content-type: [application/json] - date: ['Fri, 02 Jan 2015 17:03:54 GMT'] - server: [am/2] - set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="] - status: {code: 200, message: OK} -- request: - body: null - headers: - Accept: ['*/*'] - Accept-Encoding: ['gzip, deflate'] - Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996'] - Connection: [keep-alive] - Cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="] - User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic] - method: GET - uri: https://api.soundcloud.com:443/e1/me/stream.json?offset=1e1/me/stream.json?offset=1e1/me/stream.json?offset=1e1/me/stream.json?offset=1e1/me/stream.json?offset=1&client_id=93e33e327fd8a9b77becd179652272e2 + uri: https://api.soundcloud.com/e1/me/stream.json?limit=10&client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | @@ -52,7 +23,7 @@ interactions: content-encoding: [gzip] content-length: ['106'] content-type: [application/json] - date: ['Fri, 02 Jan 2015 17:03:54 GMT'] + date: ['Sat, 20 Feb 2016 19:14:11 GMT'] server: [am/2] status: {code: 200, message: OK} version: 1 diff --git a/tests/test_api.py b/tests/test_api.py index cc220b3..c095c6e 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -18,6 +18,7 @@ def setUp(self): config = SoundCloudExtension().get_config_schema() config['auth_token'] = '1-35204-61921957-55796ebef403996' config['explore_songs'] = 10 + config['stream_entries'] = 10 # using this user http://maildrop.cc/inbox/mopidytestuser self.api = SoundCloudClient(config) @@ -75,8 +76,8 @@ def test_get_user_liked(self): @vcr.use_cassette('tests/fixtures/sc-stream.yaml') def test_get_user_stream(self): - tracks = self.api.get_user_stream() - self.assertIsInstance(tracks, list) + tracks_and_playlists = self.api.get_user_stream() + self.assertIsInstance(tracks_and_playlists, list) @vcr.use_cassette('tests/fixtures/sc-explore.yaml') def test_get_explore(self): diff --git a/tests/test_extension.py b/tests/test_extension.py index 225c51c..5ffd2c6 100644 --- a/tests/test_extension.py +++ b/tests/test_extension.py @@ -22,3 +22,4 @@ def test_get_config_schema(self): self.assertIn('auth_token', schema) self.assertIn('explore_songs', schema) + self.assertIn('stream_entries', schema) From efe205b9af2ef075efffbb6f386a199584906a48 Mon Sep 17 00:00:00 2001 From: kriim Date: Sat, 20 Feb 2016 20:34:13 +0100 Subject: [PATCH 2/5] empty commit to trigger travis build From 251839ac61e24ce03ad16516d5f1afc56a7eeb8c Mon Sep 17 00:00:00 2001 From: kriim Date: Sat, 20 Feb 2016 20:56:48 +0100 Subject: [PATCH 3/5] #48 removed unused imports and formatted code to meet conventions --- mopidy_soundcloud/library.py | 3 ++- mopidy_soundcloud/soundcloud.py | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/mopidy_soundcloud/library.py b/mopidy_soundcloud/library.py index 73296e4..646f8e3 100644 --- a/mopidy_soundcloud/library.py +++ b/mopidy_soundcloud/library.py @@ -88,7 +88,8 @@ def list_stream(self): try: name, set_id = data except (TypeError, ValueError): - logger.debug('Adding track "%s" from stream to vfs' % data.name) + logger.debug( + 'Adding track "%s" from stream to vfs' % data.name) vfs_list[data.name] = models.Ref.track( uri=data.uri, name=data.name ) diff --git a/mopidy_soundcloud/soundcloud.py b/mopidy_soundcloud/soundcloud.py index c67f1e4..6ffe9eb 100644 --- a/mopidy_soundcloud/soundcloud.py +++ b/mopidy_soundcloud/soundcloud.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals -import collections import logging import re import string @@ -9,10 +8,8 @@ from multiprocessing.pool import ThreadPool from urllib import quote_plus -from mopidy.models import Album, Artist, Track - import requests - +from mopidy.models import Album, Artist, Track logger = logging.getLogger(__name__) @@ -91,19 +88,22 @@ def user(self): @cache() def get_user_stream(self): - # User timeline like playlist which uses undocumented api + # User timeline like playlist which uses undocumented api: # https://api.soundcloud.com/e1/me/stream.json?limit=0 - # additional parameters are 'promoted_playlist=true' or 'offset=00000152-cc97-84a0-0000-00002a293ab5' + # Additional parameters are: 'promoted_playlist=true' + # or 'offset=00000152-cc97-84a0-0000-00002a293ab5' tracks_and_playlists = [] stream = self._get('e1/me/stream.json?limit=%s' % self.stream_entries) for data in stream.get('collection'): kind = data.get('type') # multiple types of track with same data if 'track' in kind: - tracks_and_playlists.append(self.parse_track(data.get('track'))) + tracks_and_playlists.append( + self.parse_track(data.get('track'))) if 'playlist' in kind: playlist = data.get('playlist') - tracks_and_playlists.append((playlist.get('title'), str(playlist.get('id')))) + tracks_and_playlists.append( + (playlist.get('title'), str(playlist.get('id')))) return self.sanitize_tracks(tracks_and_playlists) @@ -114,8 +114,9 @@ def get_explore_categories(self): def get_explore(self, query_explore_id=None): explore = self.get_explore_categories() if query_explore_id: - url = 'explore/{urn}?limit={limit}&offset=0&linked_partitioning=1'\ - .format( + url = \ + 'explore/{urn}?limit={limit}&offset=0&linked_partitioning=1' \ + .format( urn=explore[int(query_explore_id)], limit=self.explore_songs ) From 5d800c821db8ce80113a06423c4ad3d546f6ab2f Mon Sep 17 00:00:00 2001 From: kriim Date: Sat, 20 Feb 2016 21:37:31 +0100 Subject: [PATCH 4/5] #48 fixed flake8 errors --- mopidy_soundcloud/soundcloud.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mopidy_soundcloud/soundcloud.py b/mopidy_soundcloud/soundcloud.py index 6ffe9eb..394a6aa 100644 --- a/mopidy_soundcloud/soundcloud.py +++ b/mopidy_soundcloud/soundcloud.py @@ -114,9 +114,8 @@ def get_explore_categories(self): def get_explore(self, query_explore_id=None): explore = self.get_explore_categories() if query_explore_id: - url = \ - 'explore/{urn}?limit={limit}&offset=0&linked_partitioning=1' \ - .format( + url = 'explore/{urn}?limit={limit}&offset=0&linked_partitioning=1'\ + .format( urn=explore[int(query_explore_id)], limit=self.explore_songs ) From bcd546e615af4c758b54cad8e54687c6a55dc630 Mon Sep 17 00:00:00 2001 From: kriim Date: Sat, 20 Feb 2016 21:55:20 +0100 Subject: [PATCH 5/5] #48 fixed importorder --- mopidy_soundcloud/soundcloud.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mopidy_soundcloud/soundcloud.py b/mopidy_soundcloud/soundcloud.py index 394a6aa..77cb3bd 100644 --- a/mopidy_soundcloud/soundcloud.py +++ b/mopidy_soundcloud/soundcloud.py @@ -8,9 +8,10 @@ from multiprocessing.pool import ThreadPool from urllib import quote_plus -import requests from mopidy.models import Album, Artist, Track +import requests + logger = logging.getLogger(__name__)