Mopidy-SoundCloud-2.0.2/0000775000175000017500000000000012642323173015242 5ustar jodaljodal00000000000000Mopidy-SoundCloud-2.0.2/PKG-INFO0000664000175000017500000001530312642323173016341 0ustar jodaljodal00000000000000Metadata-Version: 1.1 Name: Mopidy-SoundCloud Version: 2.0.2 Summary: SoundCloud extension for Mopidy Home-page: https://github.com/mopidy/mopidy-soundcloud Author: dz0ny Author-email: dz0ny@ubuntu.si License: MIT Description: ***************** Mopidy-SoundCloud ***************** .. image:: https://img.shields.io/pypi/v/Mopidy-SoundCloud.svg?style=flat :target: https://pypi.python.org/pypi/Mopidy-SoundCloud/ :alt: Latest PyPI version .. image:: https://img.shields.io/pypi/dm/Mopidy-SoundCloud.svg?style=flat :target: https://pypi.python.org/pypi/Mopidy-SoundCloud/ :alt: Number of PyPI downloads .. image:: https://img.shields.io/travis/mopidy/mopidy-soundcloud/master.svg?style=flat :target: https://travis-ci.org/mopidy/mopidy-soundcloud :alt: Travis CI build status .. image:: https://img.shields.io/coveralls/mopidy/mopidy-soundcloud/master.svg?style=flat :target: https://coveralls.io/r/mopidy/mopidy-soundcloud?branch=master :alt: Test coverage `Mopidy `_ extension for playing music from `SoundCloud `_. Installation ============ Debian/Ubuntu/Raspbian: Install the ``mopidy-soundcloud`` package from `apt.mopidy.com `_:: sudo apt-get install mopidy-soundcloud Arch Linux: Install the ``mopidy-soundcloud`` package from `AUR `_:: sudo yaourt -S mopidy-soundcloud OS X: Install the ``mopidy-soundcloud`` package from the `mopidy/mopidy `_ Homebrew tap:: brew install mopidy-soundcloud Else: Install the dependencies listed above yourself, and then install the package from PyPI:: pip install Mopidy-SoundCloud If you're having trouble with audio playback from SoundCloud, make sure you have the "ugly" plugin set from GStreamer installed for MP3 support. The package is typically named ``gstreamer0.10-plugins-ugly`` or similar, depending on OS and distribution. The package isn't a strict requirement for Mopidy's core, so you may be missing it. Configuration ============= #. You must register for a user account at http://www.soundcloud.com/ #. You need a SoundCloud authentication token for Mopidy from http://www.mopidy.com/authenticate #. Add the authentication token to the ``mopidy.conf`` config file:: [soundcloud] auth_token = 1-1111-1111111 explore_songs = 25 Project resources ================= - `Source code `_ - `Issue tracker `_ Changelog ========= v2.0.2 (2016-01-03) ------------------- - Handle HTTP connection errors without a response. (PR #61) - Ignore tracks without an URI. (Related to mopidy#1340, PR #62) v2.0.1 (2015-10-06) ------------------- - Fix Unicode escape sequences in SoundCloud search queries by encoding as UTF-8. (Fixes #42, PR #55) v2.0.0 (2015-03-25) ------------------- - Require Mopidy >= 1.0. - Update to work with new playback API in Mopidy 1.0. - Update to work with new backend search API in Mopidy 1.0. v1.2.5 (2014-06-24) ------------------- - Add support for new explore api v1.2.4 (2014-05-15) ------------------- - Add support for adding track by url - Fix search parsing - Support for adding playlists from liked section - Fix for track parsing and empty artists field v1.2.3 (2014-04-02) ------------------- - Add support for playing music from groups v1.2.2 (2014-03-26) ------------------- - Update Soundcloud API endpoint v1.2.1 (2014-02-21) ------------------- - Properly escape unsafe chars in URIs. v1.2.0 (2014-02-16) ------------------- - Deprecated ``explore`` and ``explore_pages`` config values. - Extension is now using Mopidy's virtual filesystem to expose music from your SoundCloud account instead of fake playlists. See the "Browse" or "Files" option in your MPD client. In the virtual file system you can browse: - The "Stream" with tracks from the users you follow. - All "Explore" sections. - Your followers and their shared tracks. - Your liked tracks. - Your sets. - Add search support. - Add support for looking up music by SoundCloud URLs through searching for the URL as a file name. v1.1.0 (2014-01-20) ------------------- - Updated extension and backend APIs to match Mopidy 0.18. v1.0.18 (2014-01-11) -------------------- - Use proper logger namespaced to ``mopidy_soundcloud`` instead of ``mopidy``. - Fix wrong use of ``raise`` when the SoundCloud API doesn't respond as expected. v1.0.17 (2013-12-21) -------------------- - Don't cache the user request. - Require Requests >= 2.0. (Fixes #3) v1.0.16 (2013-10-22) -------------------- - Require Mopidy >= 0.14. - Fix crash when SoundCloud returns 404 on track lookup. (Fixes #7) - Add some tests. v1.0.15 (2013-07-31) -------------------- - Import code from old repo. - Handle authentication errors without crashing. (Fixes #3 and #4) Platform: UNKNOWN Classifier: Environment :: No Input/Output (Daemon) Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Topic :: Multimedia :: Sound/Audio :: Players Mopidy-SoundCloud-2.0.2/setup.py0000664000175000017500000000240612505250302016745 0ustar jodaljodal00000000000000from __future__ import unicode_literals import re from setuptools import find_packages, setup def get_version(filename): with open(filename) as fh: metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", fh.read())) return metadata['version'] setup( name='Mopidy-SoundCloud', version=get_version('mopidy_soundcloud/__init__.py'), url='https://github.com/mopidy/mopidy-soundcloud', license='MIT', author='dz0ny', author_email='dz0ny@ubuntu.si', description='SoundCloud extension for Mopidy', long_description=open('README.rst').read(), packages=find_packages(exclude=['tests', 'tests.*']), zip_safe=False, include_package_data=True, install_requires=[ 'setuptools', 'Mopidy >= 1.0', 'Pykka >= 1.1', 'requests >= 2.0.0', ], entry_points={ 'mopidy.ext': [ 'soundcloud = mopidy_soundcloud:SoundCloudExtension', ], }, classifiers=[ 'Environment :: No Input/Output (Daemon)', 'Intended Audience :: End Users/Desktop', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', 'Topic :: Multimedia :: Sound/Audio :: Players', ], ) Mopidy-SoundCloud-2.0.2/.travis.yml0000664000175000017500000000071712623103152017350 0ustar jodaljodal00000000000000sudo: false language: python python: - "2.7_with_system_site_packages" env: - TOX_ENV=py27 - TOX_ENV=flake8 install: - "pip install tox" script: - "tox -e $TOX_ENV" after_success: - "if [ $TOX_ENV == 'py27' ]; then pip install coveralls; coveralls; fi" branches: except: - debian notifications: irc: channels: - "irc.freenode.org#mopidy" on_success: change on_failure: change use_notice: true skip_join: true Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/0000775000175000017500000000000012642323173022334 5ustar jodaljodal00000000000000Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/PKG-INFO0000664000175000017500000001530312642323173023433 0ustar jodaljodal00000000000000Metadata-Version: 1.1 Name: Mopidy-SoundCloud Version: 2.0.2 Summary: SoundCloud extension for Mopidy Home-page: https://github.com/mopidy/mopidy-soundcloud Author: dz0ny Author-email: dz0ny@ubuntu.si License: MIT Description: ***************** Mopidy-SoundCloud ***************** .. image:: https://img.shields.io/pypi/v/Mopidy-SoundCloud.svg?style=flat :target: https://pypi.python.org/pypi/Mopidy-SoundCloud/ :alt: Latest PyPI version .. image:: https://img.shields.io/pypi/dm/Mopidy-SoundCloud.svg?style=flat :target: https://pypi.python.org/pypi/Mopidy-SoundCloud/ :alt: Number of PyPI downloads .. image:: https://img.shields.io/travis/mopidy/mopidy-soundcloud/master.svg?style=flat :target: https://travis-ci.org/mopidy/mopidy-soundcloud :alt: Travis CI build status .. image:: https://img.shields.io/coveralls/mopidy/mopidy-soundcloud/master.svg?style=flat :target: https://coveralls.io/r/mopidy/mopidy-soundcloud?branch=master :alt: Test coverage `Mopidy `_ extension for playing music from `SoundCloud `_. Installation ============ Debian/Ubuntu/Raspbian: Install the ``mopidy-soundcloud`` package from `apt.mopidy.com `_:: sudo apt-get install mopidy-soundcloud Arch Linux: Install the ``mopidy-soundcloud`` package from `AUR `_:: sudo yaourt -S mopidy-soundcloud OS X: Install the ``mopidy-soundcloud`` package from the `mopidy/mopidy `_ Homebrew tap:: brew install mopidy-soundcloud Else: Install the dependencies listed above yourself, and then install the package from PyPI:: pip install Mopidy-SoundCloud If you're having trouble with audio playback from SoundCloud, make sure you have the "ugly" plugin set from GStreamer installed for MP3 support. The package is typically named ``gstreamer0.10-plugins-ugly`` or similar, depending on OS and distribution. The package isn't a strict requirement for Mopidy's core, so you may be missing it. Configuration ============= #. You must register for a user account at http://www.soundcloud.com/ #. You need a SoundCloud authentication token for Mopidy from http://www.mopidy.com/authenticate #. Add the authentication token to the ``mopidy.conf`` config file:: [soundcloud] auth_token = 1-1111-1111111 explore_songs = 25 Project resources ================= - `Source code `_ - `Issue tracker `_ Changelog ========= v2.0.2 (2016-01-03) ------------------- - Handle HTTP connection errors without a response. (PR #61) - Ignore tracks without an URI. (Related to mopidy#1340, PR #62) v2.0.1 (2015-10-06) ------------------- - Fix Unicode escape sequences in SoundCloud search queries by encoding as UTF-8. (Fixes #42, PR #55) v2.0.0 (2015-03-25) ------------------- - Require Mopidy >= 1.0. - Update to work with new playback API in Mopidy 1.0. - Update to work with new backend search API in Mopidy 1.0. v1.2.5 (2014-06-24) ------------------- - Add support for new explore api v1.2.4 (2014-05-15) ------------------- - Add support for adding track by url - Fix search parsing - Support for adding playlists from liked section - Fix for track parsing and empty artists field v1.2.3 (2014-04-02) ------------------- - Add support for playing music from groups v1.2.2 (2014-03-26) ------------------- - Update Soundcloud API endpoint v1.2.1 (2014-02-21) ------------------- - Properly escape unsafe chars in URIs. v1.2.0 (2014-02-16) ------------------- - Deprecated ``explore`` and ``explore_pages`` config values. - Extension is now using Mopidy's virtual filesystem to expose music from your SoundCloud account instead of fake playlists. See the "Browse" or "Files" option in your MPD client. In the virtual file system you can browse: - The "Stream" with tracks from the users you follow. - All "Explore" sections. - Your followers and their shared tracks. - Your liked tracks. - Your sets. - Add search support. - Add support for looking up music by SoundCloud URLs through searching for the URL as a file name. v1.1.0 (2014-01-20) ------------------- - Updated extension and backend APIs to match Mopidy 0.18. v1.0.18 (2014-01-11) -------------------- - Use proper logger namespaced to ``mopidy_soundcloud`` instead of ``mopidy``. - Fix wrong use of ``raise`` when the SoundCloud API doesn't respond as expected. v1.0.17 (2013-12-21) -------------------- - Don't cache the user request. - Require Requests >= 2.0. (Fixes #3) v1.0.16 (2013-10-22) -------------------- - Require Mopidy >= 0.14. - Fix crash when SoundCloud returns 404 on track lookup. (Fixes #7) - Add some tests. v1.0.15 (2013-07-31) -------------------- - Import code from old repo. - Handle authentication errors without crashing. (Fixes #3 and #4) Platform: UNKNOWN Classifier: Environment :: No Input/Output (Daemon) Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Topic :: Multimedia :: Sound/Audio :: Players Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/entry_points.txt0000664000175000017500000000010112642323173025622 0ustar jodaljodal00000000000000[mopidy.ext] soundcloud = mopidy_soundcloud:SoundCloudExtension Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/not-zip-safe0000664000175000017500000000000112624040417024557 0ustar jodaljodal00000000000000 Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/dependency_links.txt0000664000175000017500000000000112642323173026402 0ustar jodaljodal00000000000000 Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/top_level.txt0000664000175000017500000000002212642323173025060 0ustar jodaljodal00000000000000mopidy_soundcloud Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/requires.txt0000664000175000017500000000007012642323173024731 0ustar jodaljodal00000000000000setuptools Mopidy >= 1.0 Pykka >= 1.1 requests >= 2.0.0 Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/SOURCES.txt0000664000175000017500000000212012642323173024213 0ustar jodaljodal00000000000000.coveragerc .mailmap .travis.yml LICENSE MANIFEST.in README.rst setup.cfg setup.py tox.ini Mopidy_SoundCloud.egg-info/PKG-INFO Mopidy_SoundCloud.egg-info/SOURCES.txt Mopidy_SoundCloud.egg-info/dependency_links.txt Mopidy_SoundCloud.egg-info/entry_points.txt Mopidy_SoundCloud.egg-info/not-zip-safe Mopidy_SoundCloud.egg-info/requires.txt Mopidy_SoundCloud.egg-info/top_level.txt mopidy_soundcloud/__init__.py mopidy_soundcloud/actor.py mopidy_soundcloud/ext.conf mopidy_soundcloud/library.py mopidy_soundcloud/soundcloud.py tests/__init__.py tests/__main__.py tests/test_api.py tests/test_cache.py tests/test_extension.py tests/test_library.py tests/fixtures/sc-explore.yaml tests/fixtures/sc-following.yaml tests/fixtures/sc-groups.yaml tests/fixtures/sc-liked.yaml tests/fixtures/sc-login-error.yaml tests/fixtures/sc-login.yaml tests/fixtures/sc-popular.yaml tests/fixtures/sc-resolve-http.yaml tests/fixtures/sc-resolve-track-id.yaml tests/fixtures/sc-resolve-track-none.yaml tests/fixtures/sc-resolve-track.yaml tests/fixtures/sc-sets.yaml tests/fixtures/sc-stream.yaml tests/fixtures/sc-tracks.yamlMopidy-SoundCloud-2.0.2/README.rst0000664000175000017500000001125212642322330016724 0ustar jodaljodal00000000000000***************** Mopidy-SoundCloud ***************** .. image:: https://img.shields.io/pypi/v/Mopidy-SoundCloud.svg?style=flat :target: https://pypi.python.org/pypi/Mopidy-SoundCloud/ :alt: Latest PyPI version .. image:: https://img.shields.io/pypi/dm/Mopidy-SoundCloud.svg?style=flat :target: https://pypi.python.org/pypi/Mopidy-SoundCloud/ :alt: Number of PyPI downloads .. image:: https://img.shields.io/travis/mopidy/mopidy-soundcloud/master.svg?style=flat :target: https://travis-ci.org/mopidy/mopidy-soundcloud :alt: Travis CI build status .. image:: https://img.shields.io/coveralls/mopidy/mopidy-soundcloud/master.svg?style=flat :target: https://coveralls.io/r/mopidy/mopidy-soundcloud?branch=master :alt: Test coverage `Mopidy `_ extension for playing music from `SoundCloud `_. Installation ============ Debian/Ubuntu/Raspbian: Install the ``mopidy-soundcloud`` package from `apt.mopidy.com `_:: sudo apt-get install mopidy-soundcloud Arch Linux: Install the ``mopidy-soundcloud`` package from `AUR `_:: sudo yaourt -S mopidy-soundcloud OS X: Install the ``mopidy-soundcloud`` package from the `mopidy/mopidy `_ Homebrew tap:: brew install mopidy-soundcloud Else: Install the dependencies listed above yourself, and then install the package from PyPI:: pip install Mopidy-SoundCloud If you're having trouble with audio playback from SoundCloud, make sure you have the "ugly" plugin set from GStreamer installed for MP3 support. The package is typically named ``gstreamer0.10-plugins-ugly`` or similar, depending on OS and distribution. The package isn't a strict requirement for Mopidy's core, so you may be missing it. Configuration ============= #. You must register for a user account at http://www.soundcloud.com/ #. You need a SoundCloud authentication token for Mopidy from http://www.mopidy.com/authenticate #. Add the authentication token to the ``mopidy.conf`` config file:: [soundcloud] auth_token = 1-1111-1111111 explore_songs = 25 Project resources ================= - `Source code `_ - `Issue tracker `_ Changelog ========= v2.0.2 (2016-01-03) ------------------- - Handle HTTP connection errors without a response. (PR #61) - Ignore tracks without an URI. (Related to mopidy#1340, PR #62) v2.0.1 (2015-10-06) ------------------- - Fix Unicode escape sequences in SoundCloud search queries by encoding as UTF-8. (Fixes #42, PR #55) v2.0.0 (2015-03-25) ------------------- - Require Mopidy >= 1.0. - Update to work with new playback API in Mopidy 1.0. - Update to work with new backend search API in Mopidy 1.0. v1.2.5 (2014-06-24) ------------------- - Add support for new explore api v1.2.4 (2014-05-15) ------------------- - Add support for adding track by url - Fix search parsing - Support for adding playlists from liked section - Fix for track parsing and empty artists field v1.2.3 (2014-04-02) ------------------- - Add support for playing music from groups v1.2.2 (2014-03-26) ------------------- - Update Soundcloud API endpoint v1.2.1 (2014-02-21) ------------------- - Properly escape unsafe chars in URIs. v1.2.0 (2014-02-16) ------------------- - Deprecated ``explore`` and ``explore_pages`` config values. - Extension is now using Mopidy's virtual filesystem to expose music from your SoundCloud account instead of fake playlists. See the "Browse" or "Files" option in your MPD client. In the virtual file system you can browse: - The "Stream" with tracks from the users you follow. - All "Explore" sections. - Your followers and their shared tracks. - Your liked tracks. - Your sets. - Add search support. - Add support for looking up music by SoundCloud URLs through searching for the URL as a file name. v1.1.0 (2014-01-20) ------------------- - Updated extension and backend APIs to match Mopidy 0.18. v1.0.18 (2014-01-11) -------------------- - Use proper logger namespaced to ``mopidy_soundcloud`` instead of ``mopidy``. - Fix wrong use of ``raise`` when the SoundCloud API doesn't respond as expected. v1.0.17 (2013-12-21) -------------------- - Don't cache the user request. - Require Requests >= 2.0. (Fixes #3) v1.0.16 (2013-10-22) -------------------- - Require Mopidy >= 0.14. - Fix crash when SoundCloud returns 404 on track lookup. (Fixes #7) - Add some tests. v1.0.15 (2013-07-31) -------------------- - Import code from old repo. - Handle authentication errors without crashing. (Fixes #3 and #4) Mopidy-SoundCloud-2.0.2/setup.cfg0000664000175000017500000000024312642323173017062 0ustar jodaljodal00000000000000[flake8] application-import-names = mopidy_soundcloud,tests exclude = .git,.tox [wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 Mopidy-SoundCloud-2.0.2/.coveragerc0000664000175000017500000000015212505250302017350 0ustar jodaljodal00000000000000[report] omit = */pyshared/* */python?.?/* */site-packages/nose/* */site-packages/mopidy/*Mopidy-SoundCloud-2.0.2/.mailmap0000664000175000017500000000035412623103152016655 0ustar jodaljodal00000000000000Janez Troha Janez Troha Janez Troha Morten Minde Neergaard Tom Roth Mopidy-SoundCloud-2.0.2/tox.ini0000664000175000017500000000074112623103152016547 0ustar jodaljodal00000000000000[tox] envlist = py27, flake8 [testenv] deps = mock mopidy==dev pytest pytest-cov pytest-xdist vcrpy install_command = pip install --allow-unverified=mopidy --pre {opts} {packages} commands = py.test \ --basetemp={envtmpdir} \ --junit-xml=xunit-{envname}.xml \ --cov=mopidy_soundcloud --cov-report=term-missing \ {posargs} [testenv:flake8] deps = flake8 flake8-import-order skip_install = true commands = flake8 Mopidy-SoundCloud-2.0.2/MANIFEST.in0000664000175000017500000000034012505250302016764 0ustar jodaljodal00000000000000include .coveragerc include .mailmap include .travis.yml include LICENSE include MANIFEST.in include README.rst include mopidy_soundcloud/ext.conf include tox.ini recursive-include tests *.py recursive-include tests *.yaml Mopidy-SoundCloud-2.0.2/LICENSE0000664000175000017500000000205712224244453016252 0ustar jodaljodal00000000000000Copyright (C) 2013 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.Mopidy-SoundCloud-2.0.2/tests/0000775000175000017500000000000012642323173016404 5ustar jodaljodal00000000000000Mopidy-SoundCloud-2.0.2/tests/test_cache.py0000664000175000017500000000147012505250302021051 0ustar jodaljodal00000000000000from __future__ import unicode_literals import unittest from mock import Mock from mopidy_soundcloud.soundcloud import cache class CacheTest(unittest.TestCase): def test_decorator(self): func = Mock() decorated_func = cache() decorated_func(func) func() self.assertEquals(func.called, True) self.assertEquals(decorated_func._call_count, 1) def test_set_default_cache(self): @cache() def returnstring(): return 'ok' self.assertEquals(returnstring(), 'ok') def test_set_ttl_cache(self): func = Mock() decorated_func = cache(func, ttl=5) func() self.assertEquals(func.called, True) self.assertEquals(decorated_func._call_count, 1) self.assertEquals(decorated_func.ttl, 5) Mopidy-SoundCloud-2.0.2/tests/fixtures/0000775000175000017500000000000012642323173020255 5ustar jodaljodal00000000000000Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-liked.yaml0000664000175000017500000000212512505250302022623 0ustar jodaljodal00000000000000interactions: - request: body: null headers: Accept: ['*/*'] 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] method: GET uri: https://api.soundcloud.com:443/e1/me/likes.json?limit=1000&client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA4uOBQApu0wNAgAAAA== 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, must-revalidate'] content-encoding: [gzip] content-length: ['22'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:03:54 GMT'] etag: ['"d751713988987e9331980363e24189ce"'] server: [am/2] status: {code: 200, message: OK} version: 1 Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-resolve-track-none.yaml0000664000175000017500000000176312505250302025260 0ustar jodaljodal00000000000000interactions: - request: body: null headers: Accept: ['*/*'] 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] method: GET uri: https://api.soundcloud.com:443/tracks/s38720262.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: {string: !!python/unicode '{"errors":[{"error_message":"404 - Not Found"}]}'} 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: [no-cache] content-length: ['48'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:00:39 GMT'] server: [am/2] status: {code: 404, message: Not Found} version: 1 Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-stream.yaml0000664000175000017500000000510412505250302023026 0ustar jodaljodal00000000000000interactions: - request: body: null headers: Accept: ['*/*'] 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] 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 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, must-revalidate'] content-encoding: [gzip] content-length: ['106'] content-type: [application/json] date: ['Fri, 02 Jan 2015 17:03:54 GMT'] server: [am/2] status: {code: 200, message: OK} version: 1 Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-resolve-track.yaml0000664000175000017500000000422412505250302024316 0ustar jodaljodal00000000000000interactions: - request: body: null headers: Accept: ['*/*'] 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] method: GET uri: https://api.soundcloud.com:443/tracks/38720262.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA5VUTY+bMBD9K8jXhYCBkMBtK7WnVXup1CMawCHeGJvaJlF2tf+9Yz5SNtVq1SgX DzPjN2/e8ys5cdmQglgN9Yn4hOMh2e/iKM5in9SagWVNCRZT4ojGYZSE0daLoiKlRZx6DxH+sGww TJeuNk2i7Xbvk2bQYLmS2G2bZinFXqrrmLRQCUYKqwfmE2OxO3Y+cMnNkTXYSGnecgmirJW0mF4a /oIpNKVZmma5TwQYW3aq4QeOBSOqbRhR/DtUSVbQ/IbKHEFz2WJWP1SC19jfQlsKbtw8eOqZ7kBw ecJjNWgOIjioQQeW2UCqM2CKschBt0bNuoo1jYuU1RULQQjMa9RFCgVjnBQHEAYH7AddH8GwctCC FHIQwg1QMTFyNZ1vOZZbR80UbZnUjhoHeYqTLyNA78H7hhC9n8x6gfd9AtkwU2veT4S7mukSCd3c QzPBEMfc0O26tNd+Pp/YFWluJdhhuZMbXU/JZ94wtcZf9d0Ccu5aXhno+1iH+zveBxtAwqYBb4s+ KNyB20fXJw45r5kckSKvAarhaE2gGerrPAoEScDco7W9KcIQer4xapBNLdTQbFBj4ahkEy4ixpZO nKR4HbW96HOW/fjpvQ4WAcyFM4cL5yO3/ygwDSkqMPNoVMR5QaObAj9F6wCYcEKFvW+CnBgf58Qx 70ZcQYQzWNCrbMcKpxuDlDRyJGRKMYGzKk33eZJnSZDTtt/GgQDdss1z35K32cS9gGvl9FEjrbgW dO5o7gOccWXOrou2F++sLv8YaviBvUDbi9KntcAucGZOFKu+bqjLu6F+v5ziYff8+DXh57Lb9Ojy xat3dZ9LJJw87ui/Hz7fZrtd9tfdCysJTX0yU4IvjFniaZTn+GV+65YozZLIJ2At1Ef3BhqE+H8q DlfFDqdCm7i35/Hp6ccv8vYHpzK+98cFAAA= 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, must-revalidate'] content-encoding: [gzip] content-length: ['710'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:00:39 GMT'] etag: ['"c886ffe06e6e5b3f18e77d4ea5a539dc"'] server: [am/2] set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="] status: {code: 200, message: OK} version: 1 Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-following.yaml0000664000175000017500000000212512505250302023533 0ustar jodaljodal00000000000000interactions: - request: body: null headers: Accept: ['*/*'] 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] method: GET uri: https://api.soundcloud.com:443/me/followings.json?limit=60&client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA4uOBQApu0wNAgAAAA== 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, must-revalidate'] content-encoding: [gzip] content-length: ['22'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:03:52 GMT'] etag: ['"d751713988987e9331980363e24189ce"'] server: [am/2] status: {code: 200, message: OK} version: 1 Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-explore.yaml0000664000175000017500000000254112505250302023213 0ustar jodaljodal00000000000000interactions: - request: body: null headers: Accept: ['*/*'] 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] method: GET uri: https://api-v2.soundcloud.com:443/explore/categories?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAAAExRwU7DMAz9l0qcOi4cOHBjK2gghqp1Ege0Q5ZaxVpqV07CtCH+nTjdOk5+fkme n19+ChNb5OLhs6h5iM5I+ZiJWZHrjnnvUzOPHgm8wgX30B4TeKIAEgxSDxRS/wZGCKlL8B0Ovry5 uy9rdhjQ6rs1OOyQKfPNgIIhmnSqUo1FIAuKBpag15vAcgzg3Ki4AftF7Lg7FttZ0Sc79r/pVSaS aZc8kQn4DeWa7V6pfoejwYUzPl0zLm8RKYjOrsw4uQIYyiVHnxv0VkOoJPbZ8Nzk5au48wEG3d6B DcJXRNnAMzsdujTSWhYoR9/KoKrry8uMF2pxcvlqTifNMFmnVFcQss0VEvbGXWVqNGPNUrVwJ+lb dN2LbB1JBdfJ9VyrGXL4XWf09DyuSamC3DZM3SF9BIhyHN056UlsmruRc0wJDLlM+3ywuLbY/v4B AAD//wMAP61+EEwCAAA= headers: cache-control: ['private, max-age=0'] content-encoding: [gzip] content-length: ['356'] content-type: [application/json] date: ['Fri, 02 Jan 2015 17:03:44 GMT'] server: [am/2] set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="] vary: [Origin] status: {code: 200, message: OK} version: 1 Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-resolve-http.yaml0000664000175000017500000000721112505250302024170 0ustar jodaljodal00000000000000interactions: - request: body: null headers: Accept: ['*/*'] 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] method: GET uri: https://api.soundcloud.com:443/resolve.json?url=https://soundcloud.com/bbc-radio-4/m-w-cloud&client_id=93e33e327fd8a9b77becd179652272e2 response: body: {string: !!python/unicode '{"status":"302 - Found","location":"https://api.soundcloud.com/tracks/122889665.json?client_id=93e33e327fd8a9b77becd179652272e2"}'} 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: [no-cache] content-length: ['129'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:02:10 GMT'] location: ['https://api.soundcloud.com/tracks/122889665.json?client_id=93e33e327fd8a9b77becd179652272e2'] server: [am/2] status: {code: 302, message: Found} - request: body: null headers: Accept: ['*/*'] 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] method: GET uri: https://api.soundcloud.com:443/tracks/122889665.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA5VVyY7bRhD9lQYD+GJx1cbhzR44yWGCII4NH4kmWSLbanYzvYjmGAb8Dznl9/wl qeYicxTDSXQSS6+Kr17VK330zkxUXuYZRcuzt/EYPsRJkqZ3h8N+45UKqIEqpwYxSRRvwzgJo4TE h2yXZPuIPI/wg3lWg8pdcro/pNtjsvEqq6hhUnjZfn+4S7GWbFsQhhYcvMwoCxtPG6yOlU9MMN1A hYWkYjUTlOelFAbhuWaPCLlL410cY1lOtclbWbETQ/y3SMVXUrqhiokaUZ0tOCuxvKF1zpl27TBJ FK0I0oJqIC0zZQOcEyoq0kNREC0tfkUIkzvM7EC1lDNxxtTW7/2SS+sIa4MateuuoC2gqlwkLwZE U84RV8lecEnHuJedKNcoQGdV2VANuVXcy4Tl3HVYAB+1nJ6vGMOMk26K1iCUk871NMW9Nw015Jd1 H+9cH7+PfXz5/Cd50wC5n2lXoEvFumlCmMo0KZgB38l9FcUFlbR1Y4iRZJCWFAN5+fKevHaqkF1A fpSKtFIB4RRhmnB2BmIawOYIEyW3FZAf7q1i0urd/SQ1EyiyRJAipgcws+RfPv+F8mtA0SkR0JOS s869VJGeDsEDTg2E4zG+76Rkiw19p2OsDxlpjOmyMOz7PiiKMihlYM9hp2StKK6jDosoOvI/VIM6 TsIL2s66KuCAs5lFdgbJzdDNz2cYcDVrQY1d5sC0KifwhVUg1zMtunYZ3Fw1H4Cq21iLO9/cBiuK SzQN/WqOk8RldEvc04tjzkoQI1PcNR8d1BjtKxyCuoymQv0R66TQqAXtWDAu97jCKEkbjvbXaO7Z +VjTOdrLPo4X4Wrq+VqMvz21BIrrj17xnVkcYBZytS7k2bI45NUHMyr+DzPvwugYRjsS77Jkm+0P VzP/aw/unTqcqWLxq2GnQYztY/c3nT/lTS/UULVKcHqxONCiKisxSjVBtO8O32EbR9tjlPo2bUE9 +pyqGoL3Xe19mk9ix+lQuM0p0YU4sHiOn+gFh+mO33IJlvOyevl32YbrI0SV6aU6r3K/QXzCzMx3 yf4Q7VP/WG63A1sx37idArdgN9X6JzJ0v721H35++OntJX3M26DDM7vcwpu8/7Bu4XRE3dBu9ErQ nl9v56JitPFmAfG86yWaYHj+k1lCGKHG0LJx/zwaif1PH4SrbMdOotPcSX/x8PDrO+/T35IvxQU/ BwAA 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, must-revalidate'] content-encoding: [gzip] content-length: ['915'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:02:10 GMT'] etag: ['"68d7df5086119d220bcd3a5e10b2d195"'] server: [am/2] set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="] status: {code: 200, message: OK} version: 1 Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-groups.yaml0000664000175000017500000000211012505250302023044 0ustar jodaljodal00000000000000interactions: - request: body: null headers: Accept: ['*/*'] 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] method: GET uri: https://api.soundcloud.com:443/me/groups.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA4uOBQApu0wNAgAAAA== 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, must-revalidate'] content-encoding: [gzip] content-length: ['22'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:03:54 GMT'] etag: ['"d751713988987e9331980363e24189ce"'] server: [am/2] status: {code: 200, message: OK} version: 1 Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-tracks.yaml0000664000175000017500000007767212505250302023045 0ustar jodaljodal00000000000000interactions: - request: body: null headers: Accept: ['*/*'] 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] method: GET uri: https://api.soundcloud.com:443/groups/136/tracks.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | 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 YgGWU+bADWUBAA== 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, must-revalidate'] content-encoding: [gzip] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:03:53 GMT'] etag: ['"83d7b92b9c74c43dde8ff4178609b297"'] server: [am/2] set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="] transfer-encoding: [chunked] status: {code: 200, message: OK} version: 1 Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-sets.yaml0000664000175000017500000000212612505250302022512 0ustar jodaljodal00000000000000interactions: - request: body: null headers: Accept: ['*/*'] 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] method: GET uri: https://api.soundcloud.com:443/me/playlists.json?limit=1000&client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA4uOBQApu0wNAgAAAA== 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, must-revalidate'] content-encoding: [gzip] content-length: ['22'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:03:54 GMT'] etag: ['"d751713988987e9331980363e24189ce"'] server: [am/2] status: {code: 200, message: OK} version: 1 Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-login.yaml0000664000175000017500000000322112505250302022641 0ustar jodaljodal00000000000000interactions: - request: body: null headers: Accept: ['*/*'] 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] method: GET uri: https://api.soundcloud.com:443/me.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA21SW27jMAy8SqHfDWKr2W63PkAvUSwERqKzRPRwRamFUfTuSztO1knrH0NDcmb4 +FDkVPdLP93rp4fHjTpSlLeqjFlt1IA5gKd4FCikgdxYkMsSnH4RAn4X88DFhOSoJ5z47lu9a3Tb 6N2d1t3PXbd7uPvRyiciNZNk/C1l4K5pYKAtpxqd9am6rU2hmYS4OXtc2zI1+6VWSm/KvhiGNyiQ VzWznt6yiLk4S1GAA3LjsIfqi1kqPOQDbod4EG0r3koeVRer9xvVU5ZOT2M4IXPra6CXxCVDCYFD tpmGQimeSSyVC6EjtunAV5Rh5AEsXmHvuGcqMvyT6vI0hYq/gCnK7uTVg2fcqJLBHs3cgOpa2a6H 0ZPYv4LElXrOiNOc696TNT28pSxavErsk/fpXfbyBaMo7v8zct1f+mXVvfyRhQ8+gTOMNkXHxmNf VKfb3614eq2pgOo+VBXvQUSdWdKXyNLLDYeciJyZ1N/gK+5P6SeTnACaeRBrk+fAeSI3sQB5NBiA vDQWZeVhUiu54uc/Eg4T/0ADAAA= 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, must-revalidate'] content-encoding: [gzip] content-length: ['419'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 16:59:22 GMT'] etag: ['"60fdab535961251305a18c8122b7f406"'] server: [am/2] status: {code: 200, message: OK} version: 1 Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-resolve-track-id.yaml0000664000175000017500000000740112505250302024710 0ustar jodaljodal00000000000000interactions: - request: body: null headers: Accept: ['*/*'] 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] method: GET uri: https://api.soundcloud.com:443/tracks/38720262.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA5VUTW+cMBD9K8jXLAsGlgRuqdSeovZSqUc0gBecNTa1DatNlP/eMR9bslUUdbUX DzPPb9688Ss5cVmTnFgN1YnsCMdD/HAfhVEa7UilGVhWF2AxJQppFIRxEB68MMwTmkeJdxfiD8sG w3ThapM4PBwedqQeNFiuJKIdkjShiKW6jkkLpWAkt3pgO2IsoiPykUtuWlYjkNK84RJEUSlpMb0w /AVTaELTJEmzHRFgbNGpmh85FkysDkFI8e9YxWlOsysr04LmssGsfigFrxDfQlMIblw/eOqZ7kBw ecJjOWgOwj+qQfuWWV+qETDFWNSg27JmXcnq2kWK8oKFIATm1eoshYIpTvIjCIMN9oOuWjCsGLQg uRyEcA2UTExazedrjuXWSTNHGya1k8ZRnuPky0TQu/O+IUXvJ7Oe732fSdbMVJr3s+CuZr5EQrdg aCYY8lgA3awLe+mX84ldUOZGgh3WO7nR1Zw88pqpLf+y71aSC2pxYaBvYx3Or70N1oCCzQ1eB31U OAM3j66PHXNeMTkxRV19dENrja8Z+mucDIIiYG5rbW/yIICe740aZF0JNdR79FgwOdkEq4kR0pmT 5K+Tt1d/LrafPr33wWqApXDRcNV80vYfByYBRQemHg3zKMtpeHXgp2wdARPMrBD7ashZ8alPbPOm xQ1FGMGC3mQ7VTjdG5SklpMgc4rx3arS5CGLszT2M9r0h8gXoBu2f+4b8rYscS/gUjp/VCgrjgU3 d1ruI4w4Mreuq7fX3dlc/jHV4IP1Am3PSp+2BjvDyJwpNriuqfO7pn6/nKLh/vnxa8zHotv3uOXr rt7UfW6RYN5xJ/9t89khvc/Cv9u9qhLTZEcWSfCFMWs8CbMMvyxv3RqlaYwYYC1UrXsDDVL8PxcH m2LHU+GauLfn8enpxy/y9gcvlQQkxwUAAA== 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, must-revalidate'] content-encoding: [gzip] content-length: ['709'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:03:55 GMT'] etag: ['"6152060d44f2e1ca16175b22acea932d"'] 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: HEAD uri: https://api.soundcloud.com:443/tracks/38720262/stream?client_id=93e33e327fd8a9b77becd179652272e2 response: body: {string: !!python/unicode ''} 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: [no-cache] content-length: ['0'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:03:55 GMT'] location: ['https://cf-media.sndcdn.com/qzk2u7jAE3iv.128.mp3?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiKjovL2NmLW1lZGlhLnNuZGNkbi5jb20vcXprMnU3akFFM2l2LjEyOC5tcDMiLCJDb25kaXRpb24iOnsiRGF0ZUxlc3NUaGFuIjp7IkFXUzpFcG9jaFRpbWUiOjE0MjAyMTg3MTB9fX1dfQ__&Signature=EKZT2x0hOy6g3Nn~fWvm-WOK0YJ27lFpWeU0PLvd5NEmEb8WbbSd5wyNbDN6XTYGN8TRdHUwj3jU8eQaZesNnrqE6f1HWNBA-e9-tM5bx9lrHTmJ3dQEHHRLulHt0emspkGQBRm25IhH7RMMLBM3UhB-McS~oH9rEizlpHDDAXIrNcwxGY0KG4R35auaJD-trqA3TKK3pm9ayQ-lSdgXrVjmum5TxL-15F72njAZ5WcmHKv7zSrM8-yD5HowS3n3nAQgc89mUSB834GxcIAciXg415ZF9gaHw8vgqmLvZHQVtIwVJRO73hvlh0hd4zrwh10INYnTROi3jbSkYjHw3A__&Key-Pair-Id=APKAJAGZ7VMH2PFPW6UQ'] server: [am/2] status: {code: 302, message: Found} version: 1 Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-login-error.yaml0000664000175000017500000000206012505250302023770 0ustar jodaljodal00000000000000interactions: - request: body: null headers: Accept: ['*/*'] Accept-Encoding: ['gzip, deflate'] Authorization: [!!python/unicode 'OAuth 1-fake-token'] Connection: [keep-alive] 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/me.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAAAFIAAAAA//8DAEXPbOkBAAAA 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: [no-cache] content-encoding: [gzip] content-length: ['27'] content-type: [text/html; charset=utf-8] date: ['Fri, 02 Jan 2015 16:59:28 GMT'] server: [am/2] www-authenticate: ['OAuth realm="SoundCloud", error="invalid_token"'] status: {code: 401, message: Unauthorized} version: 1 Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-popular.yaml0000664000175000017500000010336112505250302023221 0ustar jodaljodal00000000000000interactions: - request: body: null headers: Accept: ['*/*'] 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] method: GET uri: https://api-v2.soundcloud.com:443/explore/categories?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAAAExRwU7DMAz9l0qcOi4cOHBjK2gghqp1Ege0Q5ZaxVpqV07CtCH+nTjdOk5+fkme n19+ChNb5OLhs6h5iM5I+ZiJWZHrjnnvUzOPHgm8wgX30B4TeKIAEgxSDxRS/wZGCKlL8B0Ovry5 uy9rdhjQ6rs1OOyQKfPNgIIhmnSqUo1FIAuKBpag15vAcgzg3Ki4AftF7Lg7FttZ0Sc79r/pVSaS aZc8kQn4DeWa7V6pfoejwYUzPl0zLm8RKYjOrsw4uQIYyiVHnxv0VkOoJPbZ8Nzk5au48wEG3d6B DcJXRNnAMzsdujTSWhYoR9/KoKrry8uMF2pxcvlqTifNMFmnVFcQss0VEvbGXWVqNGPNUrVwJ+lb dN2LbB1JBdfJ9VyrGXL4XWf09DyuSamC3DZM3SF9BIhyHN056UlsmruRc0wJDLlM+3ywuLbY/v4B AAD//wMAP61+EEwCAAA= headers: cache-control: ['private, max-age=0'] content-encoding: [gzip] content-length: ['356'] content-type: [application/json] date: ['Fri, 02 Jan 2015 17:19:37 GMT'] server: [am/2] set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="] vary: [Origin] 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-v2.soundcloud.com:443/explore/Alternative+Rock?limit=10&offset=0&linked_partitioning=1&client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | 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 AP//AwC7AEBWjlwAAA== headers: cache-control: ['public, max-age=60'] content-encoding: [gzip] content-length: ['5599'] content-type: [application/json] date: ['Fri, 02 Jan 2015 17:19:38 GMT'] server: [am/2] vary: [Origin] 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] 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/tracks/170703457.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA51V247bNhD9FYJPLWpZki+SV28G0ssC202QbtGXAMJYGllcU6RKUnadIP/eoW5w jBZtsvDa1nBmPOfMnOEnfhKq5Bl3BooTX3BBD3EapdF6s00XvDAIDsscHPmsongTxlEYbVm0y6Ik 2yTsh4j+KK6zaHIfvNmk8Xa9W/CyM+CEVjxbJclDGlEy3TSoHBwk8syZDhfcOkpPqSuhhK2xpEza iKNQIPNCK0fuuRUfySWOkjTZrakmCdbljS5FJShgKmsVrlYsfqDis006l2VrMEIdyavtDlIUlN/B MZfCekD01KJpQAp1okf/IVTQgjkFVBDIoAH7Z4cGSgzgLMBpYwODjfiLIq0jbppbMNgcsCy9JT9c KR9ISX6lviipobdPsNvOFDVYzDsjeaY6KT2sA8qewuF59nHCecIG6xGV8YTtpUOjiOAzsve6b93o x596GOwdwWAB+8kDYb/OQNh3+xEJe++RfO9LRFsY0Q7d4i+1sIxerdEHAnNlzZVVcKa+OGRP75jV 6rigd/bIftm/YU6znhIm3JJlbz6oD+qlxslmGTAiRJVMV8y6q0TLLsLVTBMDSBxScUS4swv2LBSy R1XU7BmEtAF67hmNZ0dugvys7lRJjkDZ+n/pAkPYWYOSxgHtkj06dgH/m1WnPIBXLByrtGG2M7gk qAPLCpqZUYMSqRUTwb0QcndtZ8sJrzSDRyKbckxuwppi+n4WJerbVh7aZjobs+dXBHNva2jA63tj CTQ6Q69nJRCApldg0649BlGg8hX7CQtILrXzY0kKPPcK6oygs9q51mZhCK1Y9swVUnflkkQY9hht OOuccnr98uxTr/9ZwuNu6M++lMqkBsrWaiv85Ngxy8At/803621FozDNW8/+P0s32viNsk6z7WaW 7n+i8GXZcCyWks9SHlrRE0D477D/S+VwBgfmJtJTJ+KlVWVRqp61wcUGfuOtd6vkgbZkkH48R7tD IMEccfnaHvnncRe2Eq4H2qm0xjpFyyYe7ZOSeFaBtLQCv73s8OsWFqnsos3pdlAvcEY/XHe4L1/g fv39cV9UL+mP9fPPebNsaZ1O2+8u7n+MWjisTcow7cVvyDGF+qbf0byJknjnb59x6070p9tkRS0Y 2acrwU4nD9vIX3TD3TQZV/FqwcE5KGp/ZVkq8is1Fd5E+zo1qdZfCvunp7d/8M9/A1D87Dh5BwAA 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, must-revalidate'] content-encoding: [gzip] content-length: ['912'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:19:38 GMT'] etag: ['"7c52b4eaedea7f96c5ad520d99716a45"'] 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] 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/tracks/170437996.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA5VVXa+bOBD9K8ivGwImEBLebnelqlKrrlaVrrq6EhrAATfGdm2TKK3633cMIeVm +xndh9zx8fHMmTOTz+TIZUMK4gzUR7IiHP+heZxu8v1+uyK1YeBYU4JDTBLTNKJxFG+COC+ybRHn wR8xfvDeYJkp/eV0s8G/Dd5tBgOOK0mKJE2yNEE21fdMOqgEI4UzA1sR65AfuQ9cctuxBqmU4S2X IMpaSYfw0vJPCEl3WRJv6YoIsK7sVcMPHPFjWlkU0yhOApoVWVpkyS0t24HhskWUHirBa6R30JaC W1/QE3kQjhmJaZ5Y8I+qj08keCEGZgMNAmoWPBEenEG64NyBCy5qCFrlEFShLJIfg4o7JAhOjEuL 4Dk8yoacTwQf1Mz0ILg84os89GyhZwuRLUS2kCLGOhS6XwrD+oo1jY+U1QVvghCIa9RZCgVjnBQH EBY11IOpO7CsHIxAZOectkUUcTdIZtegtWBrlD5qqwhENfSRUNaFXIauY6HkbedCpiPe7PZ5QndJ 7hMSUDExNlQOQizecNz57pG/rpkgtmXS+NBCTIzOwFfBoxfw0Qv4HgV8qZwvhNnacD35w6s0PSih 90wvrvL+6e2HOuK5YYJhjaSY8hn9WrqLvkWO7IJOabGZg89mgnFr6vn7iTdMTRpNh5Xu57Mre3lh YO5jPdqwuw82gE2ZaG5+PShstPdVrze+Il4z6TP2vQvR1J2zoWE4KKfR54PheDZ3CzRfWzXIphZq aMZ+jTXa6DaOyOnHjBSfxzH9OmnXGR4Pn/ttsnEFOOPT5avAf4/2LsvS5/n/ecIxTyK6DWhabJIi 2d3m6ac5+xxsNKeG7Df3L9yJ5rwr9VmecAIHZoEf3UzXFsVp5CjNBLEhLh8aZ9uM0j0NWx33DEIB pmXrD7olX657SQu4VLjfcKMMEhuES2TcVwc4YfP8/pknaR7VxeM/Sjb63jyDcWdljguab9QwYcYi 4v0mjXOa5KGozEfDFkWsyBlOzHvrju38TJF3h3/7ff32w54+vCn7tcalN6+Vu3u/4LRo2ke+fffS xVm+i78uollSDF3VxHVr52iy3WW3tT8HM5QfnIO68z8GFrP7zTmIFrd9igonza/Ih9ev3z6SL/8B eQBtr9QGAAA= 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, must-revalidate'] content-encoding: [gzip] content-length: ['863'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:19:38 GMT'] etag: ['"1da67a79619ce66a4b2cb813b646db5c"'] 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] 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/tracks/172570926.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA5VV247TMBD9lcivtM1l02STt2pBCIn7cpHQSpGTTBNTxw6206is+HfGuW23gIA+ tePj8ZkzZ6b35MBESVJiFC0OZEUY/vDjYBt7SRCtSKGAGigzahATeH7o+p7rx44fpl6SbrfOEw8/ eK/ToDJ7OQi9rRfFK1J2ihomBYaCMNjaZLJpQBiacyCpUR2siDaYHlPvmWC6hhIzScUqJijPCikM wjPNviPkKomSOIqCFeFUm6yRJdszvDDTCtwgcPwk9a9TP1lo6ZoqJipEtV3OWYH5Da0yzrQt6I7s uAElkOcRnPeyONwRRLSgGsqZOCCkQJp8XTBzWgd4pA0K0pxXAE0OZWkjWX7CC5RzxJWyF1zSIU7S PeUai207VdRUQ9YpTlLRcW6LyYEPwo2/F4xhxso0RisQysp0RtdWMkLIjeXo3CBH+zLoQrF2VJ68 USUox9Tg3Dx1PzFx4u5TlNdQ7jg1KEid2pg2dd1WbrRxd13VacME6FsjFdwJe6rxuO/7zZ4WkEt5 2GAf3R4oKrHAH5CmZwY1HUCfYafgIecIwmxMYN8rRZs/p7JvPn5io6koc1rV34ZbWOqonaDNopMC DijwLNtg6syc2iVygBP6qcKOd1bQUV2mVTF/P7IS5HmD8raZz6bs2QmJXcYaNGt9GSwpOmJ8Y3H1 XqK5rPl6erQ1sAKEZWyNs0br10avFeA0HYdp6BTDs7kNtGUbLTtRFlx25aDDUKPGmZxmFnPaWSTp /TDLyzhOcz6cPbb4hc5TglFWsvv4/OPthxevn90Ogv9u8jzXu7aT512ngbdM3l+JWybanfhh8mXq RvWHmtEFF+X+SpYeqaHq7JL1K/M3GlUqxaDRCNFru6uiILyOkjBZQ3K84uGaU1XB5mtbkR/TFms5 PeW4DXEBdQI75U/xPT1iF+26mud53hNnj/+VsXuxUagyvVSHsxS/4T9ixgKSMIi9MI7XQWuotz0r YGU9BdZgF9n6R2rcvuP1t1fNl9cf3n7Nmk2L63FebBf3/sFu7rgRbf8uZIu3V/72YRPOanorMgmJ e1nPUT+KUObpD2IOhlcrQo2hRW3/NjSS+89ZcM9uW4YSp83u6N3Ll28+kx8/AShGE5b9BgAA 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, must-revalidate'] content-encoding: [gzip] content-length: ['852'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:19:38 GMT'] etag: ['"afae57b7f16e6013976ff05ce8d23fa4"'] 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] 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/tracks/173263597.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA5VU2W7bMBD8FYGvta3bh95SIE2LHi6StEWBAAIlrWVWFCmQlF0nyL93qcNxnKKH 4Qeb3F3Ozs7sA6mYKEhCjKJ5RSaE4R9/EQbzMF4tJiRXQA0UKTUYE3h+5PqeGwSOFyTxPAmXzisP P5jXalBpn+zFK0yekKJV1DApSBIEkRd4WE3WNQhDMw4kMaqFCdEG62PtDRNMb6HAUlKxkgnK01wK g+GpZvcYEkVLf+7PJ4RTbdJaFmzDML6DFbuej18nQFhxEgdHWHpLFRMlRjVtxlmO5Q0tU860beiO 3L69dD6vb97dvlt/urkjzh254AaUQOA7cK5lXt0RzGlA1ZQzUWFSBgJ2koPIYaqYNBrvtUGe6tO+ oM6gKOxJmh0wi3KOcYXcCy5pd06SDeUaKWhalW+phrRVHCO3xjQ6cV1mWgF6RpuGwwyZc2nrUp61 tfsCwVRjjxxcVqzCcB5HkR/hY5xmwLuZiJbzk3cMM3YA/WkJQtkBXK2/Y85wRV4/NelcD00WoHPF mn6klpX+AUFrm/9R6oOjIJeqQDCWFAUcsK3xoU5hqTk0x5MKDjjbEsluLYQeD9MqH3/vWAGyp6W/ zJp6vBuqpwegChWG0jy+iOIQZos69p+OCopTQEEfxbWROFIrgroJbS8sB2HB2knhXMut0VMFqOpd J8pWMbwbZ0MbNtOyFUXOZVt00+na0+7RO1jTeoIkD4OnRlsMhusunyurpKKUm4NszdbyZyMGcq8u Pl056zfO9/WX27c3Fu5LD6A10QOhtWa4SoLo6IG/QrcPWeQ9Qqx+lPuJJFGRZx2fwaU7aqg6yehE 7M80slSIXsFdiJ7iyvB9bxl584U3XWhRqfspp6qE2Y+mJI/DNmk4PWS4lXAPtAInhdO0SNMN3eEU 7dYYDTS68+TxP8N9aSHsmiqzl6o6KfKbDvqYrgVvhRsp9oL59FDt5b46aWFC9nQHVmJn1fbP+Hi/ WlyKLz+X3levTutZg4tq3CVnef8gOLdfQnZ858TFcRCHT9tnJBQ38sCldex4ilbCJTus6vEwXiI/ xtB8axe4RnT/aQf3JNtClGg4uxcvPnxYfyOPvwAWJ2mziAYAAA== 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, must-revalidate'] content-encoding: [gzip] content-length: ['835'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:19:38 GMT'] etag: ['"f9f43353692c61f586759a81c789f79f"'] 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] 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/tracks/157945262.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA5VV247bNhD9FZYveahlXSxZa71t0wBpsWgX3aB9CSDQ0thiTZEqL1a9Qf69Q9Hy ep0gbRcwsBzODM+cmTP6RA9ctrSiVrPmQBeU4yEtyk1eZOtsQRsNzEJbM4s+WZLmcVLGyYakabVK q/yOfJ/gH8Y5A7qegpPV3SrPVumCtk4zy5XEjGV+lySYTvU9SMu2AmhltYMFNRYfwOQ7LrnpoMVc SvM9l0zUjZIW3WvDn9ElKzeYebOgghlb96rlO47+E64iTtI4yUiaVWlSJekFl+mY5nKPXoPbCt5g esv2teDGV4SnAXTPBJcHPIJsBRgTGQGA9ijFe2ORgv4aMfRbaFtvqbcnjGJCoF+rRikUm+y02jFh sLjB6aZjBmqnBXp21g5VHO9B8qUz8SOwjt/37Bkp8lVtQUwUSifEVazl1tNFf3AnItVIlCSXIEyl /d29sKAlsn0E8puaOjmHvQtFkadQlIcKptF8CK2hH/VH6X8/ngsgtuOGTPNAdkqTnQYgzFZkHMel 7UCzIygJ1oJZYj996C8wEia2rieP797cv/+JKGc91O/m3PyDk2Aq8qryYPQuoZyb62D010+Dsnx3 qojpmbZI5ZLbQN7TgLPQne8vHErWe07wrEEA0h8OU0m1PQ3+PM8YOh3ghBO2R/bcxCWauNFNCDry FlRoX2jLduhpFf49Z69PwDSOJ6rj8iKOp7QdrcoXS8twWDLU1Px0jez2k7D6YeXB8wbkBBYnKkIN dNZEGlBYx0kWTnPE5GfI4BCxgS+NcrJthHKt70Q8FWjii3wxp5clrT4FWb8o8yz66fa1BLDB0VWH zynOjH7ocLpe2u8xfynFPE6TOF2TtKhWSZVtLlL8V/weDsKfYWL6izhfC+i27i8wsyOzTF8FecZ4 ujRIVysnsoKLifz+2uT5Zl0UeSQOTVkeIsH0HpZ/Dnv6+bzZcM5OW9yQuJKcxM2B223aeDt2xHb6 BTYrfl4nV4/j299GHH9t8eCoj0ofrvJ8pYjgE6q4y9clbu4iGtblX8P+qooFHbFrftxuso2vKHl8 OP7+8+757du/2/d1vxxwbc777ybuPwxfHBanb+INd4iyQBnMC3OmFD8PZzZxX5vZmhZ5cfluzEb/ EWDWsqbzXxOD4P6nMuKraI9Qofb8Kr9/ePj1D/r5H0foVmgWBwAA 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, must-revalidate'] content-encoding: [gzip] content-length: ['894'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:19:38 GMT'] etag: ['"3a2806d130e1fcdc7ba3f5f3559b2102"'] 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] 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/tracks/180099816.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA5VUS2+bQBD+K2gvOdQ2LME2cIsUtYdGqtpLL5HQAmPYetml+7DjRvnvneXhOlaq phYHM8zjm5lvvmey57ImObGaVXuyIBxfaBpFWZbSzYJUGpiFumAWfeKIJiGNw2gd0CSPkzyJgg8R /jDOGdCFD463lKZpvCC108xyJTFftk3pFpOprgNpWSmA5FY7WBBjMT2m3nHJTQs1ZlKaN1wyUVRK WnQvDP+FLhndxHGMkAQztuhUzXcc/QdU6zCi+AQxzdfbfJ2eUZmWaS4b9OpdKXiF6S1rCsGN7+eR 7DRAUKujFIrVjyR4JBKOQecMr/CtYo1yAgJgOJwWHUEiFBlIZS1mbVmH+XrQHRNc7jFh40yLJmNx at1lm9CVUNfeUpQndGRCoN9cePTcMWFwIr3TVcsMFE4L9Gyt7fMwLLldiVO4HxEtayW489UFK0EM c5dOiItoy62fMvnoO7yfOsSABqT29jthQUtc0AGCb2pY/RzyaeyiBlNp3o8rJHOK4OZ+qH0T7JQO hvm1oCF/lO9CKlmH1UesGgRgo/PrwMDCnvqzZQ8nXH6DKJ3HPEZxo6v5/4HXoMZBjR/Lvpu/TdmL E27v2tYhs9prY81wM2OaMwWxx26gftff+mnzCqRH7Be4RJ621iw1IPUPA3Wd5tPKDO6M9XxllJN1 JZSrV8j+cOjRhOcDw5z+cEj+PBze+Xamoxy+vabYxIApcBwn+TzSwiP8+3FE6zze5DQ5H8c/0fry JpxAYfIz1V9z86rHPwjZgVmmL5z9VDhdGRxJLYeBjC5miSJC4yjeRGm6We5dlGRPS8F0A6sffUNe Jn3pBTuVqFMoDU7iBdPJvmMHXJkXkvmI5qO8KI6130YaTmfLtD0qvb8IeQPv6DMARo2MM5rE6VKo LP5pLwAvyJEdwLPnKtvxVffia5zyEp5spm6LbtWjUs3qcRX3Di6Fo+z4PV2NKUmiNcrvLDfz9KIF mQaHYmZma0bXZ6WebbfoyqxFEfT6bRDbf/I8vIj2AFFCKq+Ddw8PX76Tl9+iD6WChgYAAA== 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, must-revalidate'] content-encoding: [gzip] content-length: ['793'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:19:38 GMT'] etag: ['"38831204ed9ab3ebf49b380963c41d63"'] 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] 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/tracks/172322693.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA5VVy67bRgz9FUGbLmpZb/tau/SRLhq0aG6AooABgdJQ9tSjGWEedtUg/x6OZPn6 ug2aemVxSA7PIQ/nY3jikoVVaDW0p3AVcvpIt1meZZtdvgpbjWCR1WDJJ0vSIk6TOC2DLKnKrCrT 4NuEfhTnDOp6Cs7zfFekFMucBsuVpIRPxWa7oWyq71FaaASGldUOV6GxlJ9yd1xyc0RGqZTmBy5B 1K2Sltxrw/8mlzwrN2VaJKtQgLF1rxjvOAVMdZVxksZJFqRFVWRV8nSryxxBc3kgr8E1greU38Kh Ftx4RD8aqyQHGezDZ8GHAfUYPAs14D4kR/rsQXB5Is8WNHZOkNVY4qS/x4B9g4x5S92M5AvC+zF1 kULBZA+rDoQhuIPT7REM1k4L8jxaO5gqjrl1Es0ahkHgmliKEWMQjetjpqJRuahDFJE9YmSgx5iz XZ6XRbIrc7pIQINi4l46Ie7usNx6osPv3BgoGfAP/hIKOKDU3v5GWNSSenTG4L2a2r+EfH+Dy9C0 mg9zI8NMsmAalaDTqieu7kn7xgQMG2eDqXTi9AcV/KFc8JaKDz4cMXim4vfhei/3ckF+uVzWHbTY KHWakJtrSuPboLqOtxw8nTNKSRmo8rcg27FRfwXvsVWaeVAaBRKxYTWTMBVZ23G4WU440iQdCK/z 6Gc3bnS7/D9zhmpuzHzYDP1yds1ejwj60dbTmB4fjQxoEuY0t3nuFM2TH7sLnD0i3qL0FfuBiWjo j9ZEGklI50kHTnM6W4iCga+NcpK1Qjk2UTVhNPFNrpTTyzCsPs4yvinxqvHp8PVYf4lt73rl+nWP p078Q360Fkh+myApq7yoshf5/ScGfxFBuJZK2W+iu5MIKeQB+pfqhjNY0Hehk7rStSHemJxYm11M RHsrTcrtJt2VZdQ3I8M0EqAPuP5zOISfrittEDA2tBppGTlJvUuv9g7O1Fe/uhZlL8vi7vKvrDt+ WS6g7UXp012OfwEw+0wIElq1yfZpm0bZBWy+uUOw8mOGfuYesl1e0fHLzz/9VoodT9kz1P16oF25 7LiHuK+YwHhejr6Nj7yVRVYQectWXPikdX6lkra0Waz5tnx5Lhbjlt4UsBbao39EDFX3P/UR30X7 EhUp0O/rN+/e/fp7+OkzkzBI/AwHAAA= 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, must-revalidate'] content-encoding: [gzip] content-length: ['878'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:19:38 GMT'] etag: ['"17967c5ed84d3b941bef99c44c95e9a3"'] 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] 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/tracks/166785347.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA5VVyY7bOBD9FYbXsUwtlhfdMgmCBAgSYNDBXAwIlEjLjClSISl71EH+fYraWu2k ZzF8sIuviq+2x+/4IhTDGXaGlhe8wgL+RNvtbp8mm90Kl4ZTx1lOHWDiMNqQ8EDCPYrSLNlk4Rb9 FsIH/FrLTe6dE/CN9skKs9ZQJ7TCWZxE8T6GYLquuXK0kBxnzrR8ha2D8BD6JJSwZ84gkjaiEorK vNTKATy34hEgaZpCWKAkqXV5rZk4CcD3rFISRiSMUbTN0jALdzMre6ZGqApQTVtIUUJ4R6tcCuvz OWIqHTcKaF45Mrq8HDFiJYsB1nBTUynUxZOjUga6dUGhu6AESq0R3ALIOihPvcyH1wVnzFvyogNX 8AQc0zclNe3tOINwFlJvWlOeqeV5ayQgz841GSG2pgbiy7VwZHmVpAWXfYFVK+XC2wnnyzlYK66M L+frp7x8ygMEv1lQZ9yWRjRDg/AnfkMWCiU5Oj7BoBqQNVL6tkJTCkgrJB5axS36J8bIR6SyaGsE XYfIyGrwhO/bN29j8sFKqhj6g5faMPsKHdVRfWlGpNOtQQzmwmbTHb4DQAXqvwYQ6RHeZ6Tw/Hhh Lnmh9aX3ecLM5+4mHPT/peMObmwL/tKxUDC8laH1SwDbaCfWJ0Eeku5d9eGooBVDGxWt55YZLjlM wdTBfg9z1zWz5cI7WIEKprT1vR0aLawpp99Xwbgepmg4LJp6Ohuj5x2n5t5Ww36d742MwtgOYeZF PGnYBb8wdZP4HAQMpmfspzuAbT07GxgOAnDtFxi2Y5xnCwNNG7G2ulWshA6yvlZ9jpbMMgMxvXzg 7HsvP7OCjNLUn/28kcM8jL5DRfE7YPS5db/rrq/1T0KxIVFM4gTFCYhctglnofhXzp6EJSM1CD7r w936Ps/0aejAhV6po2aB9+UR0dpCbZjqKzNAbACaGsVRnOzSQxIwuf/LhoGkpuLrr02Ff4xy20ja FSDboJStgv5Eo/1Er9A7r6twfy81k5gtLvda8xJZ4nn/UvFAnG7aXBZxfpHEgOmzCA/hJtrF2zBo vj0Wu2+LLFb4Rq/cz9ZdtNuzkmwfuvfho7ymgn3J63UDaj4J753ff5g0Mii2799d7fbbw3aT+mdr 1OqpqOEK9L+vJ6iYnazR/pBGm/lJm8xJ6F8o6hwtz/6ps0Dxfy4DWXh7nhrWzb8krz9+/Pwn/vE3 DcQHabEHAAA= 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, must-revalidate'] content-encoding: [gzip] content-length: ['920'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:19:38 GMT'] etag: ['"32eed85c2543f08cb8eef0618e2399e7"'] 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] 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/tracks/172120478.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA5VUa4sTMRT9KyUf/GLbedZ2BhZREQQFUUFhEYbMJG1jM8mQR5cq/ndP5lFnlxW1 LLR7cx/nnnvu/UFOQjFSEmdocyJLIvBPsk2TNM63uyVpDKeOs4o6+KRxkkdJHCX5InlWZmmZbxZP Y3wQ5y03VR+cptmuKIolYd5QJ7RCwk2WxTGy6bblytFaclI64/mSWIf8yL0XStgjZ0iljTgIRWXV aOXgXlnxHS7pNk6LZJcviaTWVa1mYi8Q0OPaRHGCv0WalJttudldcdkjNUId4NX5WooG+R09VFLY 0NFX8kI6bhRwnvnio25OXwk8Om5aKoU6weWsRcMtjNaBi3aOnbc1ZyxYqvoCVyol/Ji+U1LT3k7K PZUWbXbeNEdqeeWNhOfRua6MIuG84nZNu07yNciJvI2orH0bKb1qPVvhS2oHq2BFWmS77S5Onnt9 kz+h7iZJJH31GhUlrbnsyVdeylkxJ1xgerAeuDKB6dAlgsY38nnqj3HbGNENEwssDGkVba8pDJcc TUwZe81U7tJdLSd+wbQO4NOHWkNhYU0z/T4LxvVAwvBYd+30NmavLpwajBtiW5LJ1kIKRwgp+W1i FJynM7nsNaYWpkrFfh/wY3AqoA2DWUFUR2dXhkOo515n3ohxFBazoJ1YW+0Va6T2rJ9G35+NruuA nEHmpPwxrMlV6eMO9Y/31XOiDmI1egwdyCRvf1sf0TJ2LI3SfJFmZQ4551ct/xVwAAC8Iy7UvAr5 vu4e9DkDSc+AZmbegRqRrC14YapnZXCxK+x9EmMh0ywtVl4f+ea0ktQc+PpbdyA/x5PQSXqpcVqw zF5hOBhgfyr29Iw9D6s/bci0c7PiqP0HqNF1Lalxd9qcZlGPQB58esxxkRWbeFvEq/qCa9TMMC/J HT3zIKMH2e7uEVC82b+8/bC7ZZ/c66pdd7gu03V4EPcPmoqGsxJm9YCpZ7t4A3lP52QiEGd05A5n zU7WZJuD2fG+TsYMvtQ52hzD1bUA95+Cj2bRAaHGSoVD9+Ldu/dfyM9fmFC20j0GAAA= 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, must-revalidate'] content-encoding: [gzip] content-length: ['791'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:19:38 GMT'] etag: ['"b972a18ac1d4f79b31cdad19c3bc5e8e"'] 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] 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/tracks/164767462.json?client_id=93e33e327fd8a9b77becd179652272e2 response: body: string: !!binary | H4sIAAAAAAAEA1xTTW/bMAz9K4IuOSyNv9Kk823YUBQYCgy97BLAkCUmJiJLBiWn8PrnR9ltltWA AZuiyPceH9/kGZ2RtYyk9FmuJfJPsdvud/vtrlxLTaAimEZFzinzYpvlD1l5L8qqru7rci++5Pzw vTEANelyVT7sqnK/XUszkoronaz5Yl7lXM33PbioWguyjjTCWobI9bn2ER2GDgyX8oQndMo22rvI 6U3AP5xS3W+3+yLnMlaF2PTe4BH5wjuuosyqQhS7uvha5w9XXKFThO7EWcPYWtRcP6pTYzEkRgf5 zUYgxzgvIF68Ph8kZwxAvbLozjMw4tQElKXob6FD34IxKdK0E2cqaznP+FdnvZrjsj4qG5jlMJLu VIBmJCtrN1qbWLRgZ8mW/2tOxJj0WaIncJT0ucGZKCwp8hEXcAaCJhwWteUj+V6snrw14nkST76H 9Uqoi0JuaUEUeVYWwjvxg6FGfsULaE8mbA7uF8EdfwKJ2IFQth17Eb0g0JAUWs0NVwIdz81FO9Wi i3Gosyz0iiKz22DMvv/+mZo/T6n1wTHehatT/ZUXgQUW5IPmbL8mTsM1coaJB3/i0YxJgEUNDKQ/ vi9owN8K2g79x9l79WYCRZ9jPbuq+xw0iie49Lja7+jZBcklr+qSOKAGlxCnQd+xR7sY7gjY95fZ tiMhnyU5AuuhBtwEPzqjrR/Nhp2fzRxDdt0urpm2RtZv89b9W5z3lZwP/zejZllfFZ3RBPkXAAD/ /5WVy5KCMBBFf8ViLa8QIbijZqlVLl1OZUZx8AGY4Dgs/PfppokGyprHNqSTm9t9D1RNnjov2Oy1 VJMFfXoaEYhuABFhcz6b8/gekV+FoxDtG31w8T0fZH/3aJyB4XuHYmH+GqmsAnSpCD0NFm3KziDa ol1EShSkLIxY6hZncdqX7lGq3dbb1zvn1sOmPsr2DaAFmLiU0KawX8/lJ7QQoWLCZ9JsXf6jWj/v YwUjfa3Uwap7Ipr2kGqRslSIJHCVrOK8tVRPcYq2OFKj065DC7Ii23yFB6GS9vXk1UAug55R3R8G zCdmYcPGXnEeB1w8aGVMBLr2/gE0tVllfNb9DAjfZjVKoqkjm0a+fyDVNQj8ZwJ8qxpVVpAxJGm2 XK7Wzu0b9S5Rp50GAAA= 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, must-revalidate'] content-encoding: [gzip] content-length: ['869'] content-type: [application/json; charset=utf-8] date: ['Fri, 02 Jan 2015 17:19:38 GMT'] etag: ['"66dbb4584916e51f1a18d35581065139"'] server: [am/2] set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="] status: {code: 200, message: OK} version: 1 Mopidy-SoundCloud-2.0.2/tests/__init__.py0000644000175000017500000000000012231551106020472 0ustar jodaljodal00000000000000Mopidy-SoundCloud-2.0.2/tests/__main__.py0000644000175000017500000000010212231551106020456 0ustar jodaljodal00000000000000from __future__ import unicode_literals import nose nose.main() Mopidy-SoundCloud-2.0.2/tests/test_library.py0000664000175000017500000000654612505250302021463 0ustar jodaljodal00000000000000# coding=utf-8 from __future__ import unicode_literals import unittest import pykka from mopidy_soundcloud import SoundCloudExtension, actor from mopidy_soundcloud.library import ( SoundCloudLibraryProvider, new_folder, simplify_search_query) from mopidy_soundcloud.soundcloud import safe_url class ApiTest(unittest.TestCase): def setUp(self): config = SoundCloudExtension().get_config_schema() config['auth_token'] = '1-35204-61921957-55796ebef403996' # using this user http://maildrop.cc/inbox/mopidytestuser self.backend = actor.SoundCloudBackend.start( config={'soundcloud': config}, audio=None ).proxy() self.library = SoundCloudLibraryProvider(backend=self.backend) def tearDown(self): pykka.ActorRegistry.stop_all() def test_add_folder(self): try: from mopidy.models import Ref except ImportError as e: self.skipTest(e.message) self.assertEquals( new_folder('Test', ['test']), Ref(name='Test', type='directory', uri='soundcloud:directory:test') ) def test_mpc_search(self): self.assertEquals( simplify_search_query({u'any': [u'explosions in the sky']}), 'explosions in the sky' ) def test_moped_search(self): self.assertEquals( simplify_search_query( { u'track_name': [u'explosions in the sky'], u'any': [u'explosions in the sky'] } ), 'explosions in the sky explosions in the sky' ) def test_simple_search(self): self.assertEquals( simplify_search_query('explosions in the sky'), 'explosions in the sky' ) def test_aria_search(self): self.assertEquals( simplify_search_query(['explosions', 'in the sky']), 'explosions in the sky' ) def test_only_resolves_soundcloud_uris(self): self.assertIsNone(self.library.search( {'uri': 'http://www.youtube.com/watch?v=wD6H6Yhluo8'})) def test_returns_url_safe_string(self): self.assertEquals( safe_url('Alternative/Indie/rock/pop '), 'Alternative%2FIndie%2Frock%2Fpop+') self.assertEquals( safe_url('D∃∃P Hau⑀ iNDiE DᴬNCE | №➊ ²⁰¹⁴'), 'DP+Hau+iNDiE+DANCE+%7C+No+2014') def test_default_folders(self): try: from mopidy.models import Ref except ImportError as e: self.skipTest(e.message) self.assertEquals( self.library.browse('soundcloud:directory'), [ Ref(name='Explore', type='directory', uri='soundcloud:directory:explore'), Ref(name='Following', type='directory', uri='soundcloud:directory:following'), Ref(name='Groups', type='directory', uri='soundcloud:directory:groups'), Ref(name='Liked', type='directory', uri='soundcloud:directory:liked'), Ref(name='Sets', type='directory', uri='soundcloud:directory:sets'), Ref(name='Stream', type='directory', uri='soundcloud:directory:stream') ] ) Mopidy-SoundCloud-2.0.2/tests/test_extension.py0000644000175000017500000000107512300231306022015 0ustar jodaljodal00000000000000from __future__ import unicode_literals import unittest from mopidy_soundcloud import SoundCloudExtension class ExtensionTest(unittest.TestCase): def test_get_default_config(self): ext = SoundCloudExtension() config = ext.get_default_config() self.assertIn('[soundcloud]', config) self.assertIn('enabled = True', config) def test_get_config_schema(self): ext = SoundCloudExtension() schema = ext.get_config_schema() self.assertIn('auth_token', schema) self.assertIn('explore_songs', schema) Mopidy-SoundCloud-2.0.2/tests/test_api.py0000664000175000017500000001075312632302045020566 0ustar jodaljodal00000000000000from __future__ import unicode_literals import unittest import mock from mopidy.models import Track import vcr from mopidy_soundcloud import SoundCloudExtension from mopidy_soundcloud.soundcloud import SoundCloudClient, readable_url class ApiTest(unittest.TestCase): @vcr.use_cassette('tests/fixtures/sc-login.yaml') def setUp(self): config = SoundCloudExtension().get_config_schema() config['auth_token'] = '1-35204-61921957-55796ebef403996' config['explore_songs'] = 10 # using this user http://maildrop.cc/inbox/mopidytestuser self.api = SoundCloudClient(config) def test_resolves_string(self): _id = self.api.parse_track_uri('soundcloud:song.38720262') self.assertEquals(_id, '38720262') @vcr.use_cassette('tests/fixtures/sc-login-error.yaml') def test_responds_with_error(self): with mock.patch('mopidy_soundcloud.soundcloud.logger.error') as d: config = SoundCloudExtension().get_config_schema() config['auth_token'] = '1-fake-token' SoundCloudClient(config) d.assert_called_once_with('Invalid "auth_token" used for ' 'SoundCloud authentication!') @vcr.use_cassette('tests/fixtures/sc-resolve-track.yaml') def test_resolves_object(self): trackc = {} trackc[b'uri'] = 'soundcloud:song.38720262' track = Track(**trackc) id = self.api.parse_track_uri(track) self.assertEquals(id, '38720262') @vcr.use_cassette('tests/fixtures/sc-resolve-track-none.yaml') def test_resolves_unknown_track_to_none(self): track = self.api.get_track('s38720262') self.assertIsNone(track) @vcr.use_cassette('tests/fixtures/sc-resolve-track.yaml') def test_resolves_Track(self): track = self.api.get_track('38720262') self.assertIsInstance(track, Track) self.assertEquals( track.uri, 'soundcloud:song/Burial Four Tet - Nova.38720262' ) @vcr.use_cassette('tests/fixtures/sc-resolve-http.yaml') def test_resolves_http_url(self): track = self.api.resolve_url( 'https://soundcloud.com/bbc-radio-4/m-w-cloud' )[0] self.assertIsInstance(track, Track) self.assertEquals( track.uri, 'soundcloud:song/That Mitchell and Webb Sound The Cloud.122889665' ) @vcr.use_cassette('tests/fixtures/sc-liked.yaml') def test_get_user_liked(self): tracks = self.api.get_user_liked() self.assertIsInstance(tracks, list) @vcr.use_cassette('tests/fixtures/sc-stream.yaml') def test_get_user_stream(self): tracks = self.api.get_user_stream() self.assertIsInstance(tracks, list) @vcr.use_cassette('tests/fixtures/sc-explore.yaml') def test_get_explore(self): tracks = self.api.get_explore() self.assertIsInstance(tracks, list) self.assertEquals(tracks[0], 'Popular+Music') @vcr.use_cassette('tests/fixtures/sc-popular.yaml') def test_get_explore_popular_music(self): tracks = self.api.get_explore('1') self.assertIsInstance(tracks, list) self.assertIsInstance(tracks[0], Track) @vcr.use_cassette('tests/fixtures/sc-following.yaml') def test_get_followings(self): tracks = self.api.get_followings() self.assertIsInstance(tracks, list) @vcr.use_cassette('tests/fixtures/sc-sets.yaml') def test_get_sets(self): tracks = self.api.get_sets() self.assertIsInstance(tracks, list) @vcr.use_cassette('tests/fixtures/sc-groups.yaml') def test_get_groups(self): tracks = self.api.get_groups() self.assertIsInstance(tracks, list) @vcr.use_cassette('tests/fixtures/sc-tracks.yaml') def test_get_group_tracks(self): tracks = self.api.get_groups(136) self.assertIsInstance(tracks[0], Track) def test_readeble_url(self): self.assertEquals('Barsuk Records', readable_url('"@"Barsuk Records')) self.assertEquals('_Barsuk Records', readable_url('_Barsuk \'Records\'')) @vcr.use_cassette('tests/fixtures/sc-resolve-track-id.yaml') def test_resolves_stream_track(self): track = self.api.get_track('38720262', True) self.assertIsInstance(track, Track) self.assertEquals( track.uri, 'https://api.soundcloud.com/tracks/' '38720262/stream?client_id=93e33e327fd8a9b77becd179652272e2' ) Mopidy-SoundCloud-2.0.2/mopidy_soundcloud/0000775000175000017500000000000012642323173021002 5ustar jodaljodal00000000000000Mopidy-SoundCloud-2.0.2/mopidy_soundcloud/__init__.py0000664000175000017500000000243512642322340023112 0ustar jodaljodal00000000000000from __future__ import unicode_literals import os from mopidy import config, ext from mopidy.exceptions import ExtensionError __version__ = '2.0.2' class SoundCloudExtension(ext.Extension): dist_name = 'Mopidy-SoundCloud' ext_name = 'soundcloud' version = __version__ def get_default_config(self): conf_file = os.path.join(os.path.dirname(__file__), 'ext.conf') return config.read(conf_file) def get_config_schema(self): schema = super(SoundCloudExtension, self).get_config_schema() schema['explore_songs'] = config.Integer() schema['auth_token'] = config.Secret() schema['explore'] = config.Deprecated() schema['explore_pages'] = config.Deprecated() return schema def validate_config(self, config): # no_coverage if not config.getboolean('soundcloud', 'enabled'): return if not config.get('soundcloud', 'auth_token'): raise ExtensionError( 'In order to use SoundCloud extension you must provide an ' 'auth token. For more information refer to ' 'https://github.com/mopidy/mopidy-soundcloud/') def setup(self, registry): from .actor import SoundCloudBackend registry.add('backend', SoundCloudBackend) Mopidy-SoundCloud-2.0.2/mopidy_soundcloud/library.py0000664000175000017500000001634412632301132023016 0ustar jodaljodal00000000000000from __future__ import unicode_literals import collections import logging import re import urllib from urlparse import urlparse from mopidy import backend, models from mopidy.models import SearchResult, Track from mopidy_soundcloud.soundcloud import safe_url logger = logging.getLogger(__name__) def generate_uri(path): return 'soundcloud:directory:%s' % urllib.quote('/'.join(path)) def new_folder(name, path): return models.Ref.directory( uri=generate_uri(path), name=safe_url(name) ) def simplify_search_query(query): if isinstance(query, dict): r = [] for v in query.values(): if isinstance(v, list): r.extend(v) else: r.append(v) return ' '.join(r) if isinstance(query, list): return ' '.join(query) else: return query class SoundCloudLibraryProvider(backend.LibraryProvider): root_directory = models.Ref.directory( uri='soundcloud:directory', name='SoundCloud' ) def __init__(self, *args, **kwargs): super(SoundCloudLibraryProvider, self).__init__(*args, **kwargs) self.vfs = {'soundcloud:directory': collections.OrderedDict()} self.add_to_vfs(new_folder('Explore', ['explore'])) self.add_to_vfs(new_folder('Following', ['following'])) self.add_to_vfs(new_folder('Groups', ['groups'])) self.add_to_vfs(new_folder('Liked', ['liked'])) self.add_to_vfs(new_folder('Sets', ['sets'])) self.add_to_vfs(new_folder('Stream', ['stream'])) def add_to_vfs(self, _model): self.vfs['soundcloud:directory'][_model.uri] = _model def list_sets(self): sets_vfs = collections.OrderedDict() for (name, set_id, tracks) in self.backend.remote.get_sets(): sets_list = new_folder(name, ['sets', set_id]) logger.debug('Adding set %s to vfs' % sets_list.name) sets_vfs[set_id] = sets_list return sets_vfs.values() def list_liked(self): vfs_list = collections.OrderedDict() for data in self.backend.remote.get_user_liked(): try: name, set_id = data except (TypeError, ValueError): logger.debug('Adding liked track %s to vfs' % data.name) vfs_list[data.name] = models.Ref.track( uri=data.uri, name=data.name ) else: logger.debug('Adding liked playlist %s 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(): sets_list = new_folder(name, ['following', user_id]) logger.debug('Adding set %s to vfs' % sets_list.name) sets_vfs[user_id] = sets_list return sets_vfs.values() def list_explore(self): sets_vfs = collections.OrderedDict() for eid, name in enumerate(self.backend.remote.get_explore()): sets_list = new_folder( name, ['explore', str(eid)] ) logger.debug('Adding explore category %s to vfs' % sets_list.name) sets_vfs[str(eid)] = sets_list return sets_vfs.values() def list_groups(self): groups_vfs = collections.OrderedDict() for group in self.backend.remote.get_groups(): g_list = new_folder( group.get('name'), ['groups', str(group.get('id'))] ) logger.debug('Adding group %s to vfs' % g_list.name) groups_vfs[str(group.get('id'))] = g_list return groups_vfs.values() def tracklist_to_vfs(self, track_list): vfs_list = collections.OrderedDict() for temp_track in track_list: if not isinstance(temp_track, Track): temp_track = self.backend.remote.parse_track(temp_track) if hasattr(temp_track, 'uri'): vfs_list[temp_track.name] = models.Ref.track( uri=temp_track.uri, name=temp_track.name ) return vfs_list.values() def browse(self, uri): if not self.vfs.get(uri): (req_type, res_id) = re.match(r'.*:(\w*)(?:/(\d*))?', uri).groups() # Sets if 'sets' == req_type: if res_id: return self.tracklist_to_vfs( self.backend.remote.get_set(res_id) ) else: return self.list_sets() # Following if 'following' == req_type: if res_id: return self.tracklist_to_vfs( self.backend.remote.get_followings(res_id) ) else: return self.list_user_follows() # Explore if 'explore' == req_type: if res_id: return self.tracklist_to_vfs( self.backend.remote.get_explore(res_id) ) else: return self.list_explore() # Groups if 'groups' == req_type: if res_id: return self.tracklist_to_vfs( self.backend.remote.get_groups(res_id) ) else: return self.list_groups() # Liked if 'liked' == req_type: return self.list_liked() # User stream if 'stream' == req_type: return self.tracklist_to_vfs( self.backend.remote.get_user_stream() ) # root directory return self.vfs.get(uri, {}).values() def search(self, query=None, uris=None, exact=False): # TODO Support exact search if not query: return if 'uri' in query: search_query = ''.join(query['uri']) url = urlparse(search_query) if 'soundcloud.com' in url.netloc: logger.info('Resolving SoundCloud for: %s', search_query) return SearchResult( uri='soundcloud:search', tracks=self.backend.remote.resolve_url(search_query) ) else: search_query = simplify_search_query(query) logger.info('Searching SoundCloud for: %s', search_query) return SearchResult( uri='soundcloud:search', tracks=self.backend.remote.search(search_query) ) def lookup(self, uri): if 'sc:' in uri: uri = uri.replace('sc:', '') return self.backend.remote.resolve_url(uri) try: track_id = self.backend.remote.parse_track_uri(uri) track = self.backend.remote.get_track(track_id) if track is None: logger.info( 'Failed to lookup %s: SoundCloud track not found' % uri) return [] return [track] except Exception as error: logger.error('Failed to lookup %s: %s', uri, error) return [] Mopidy-SoundCloud-2.0.2/mopidy_soundcloud/ext.conf0000644000175000017500000000030212300231306022425 0ustar jodaljodal00000000000000[soundcloud] enabled = True # Your SoundCloud auth token, you can get yours at http://www.mopidy.com/authenticate auth_token = # Number of songs to fetch in explore section explore_songs = 25Mopidy-SoundCloud-2.0.2/mopidy_soundcloud/actor.py0000664000175000017500000000167412632301743022472 0ustar jodaljodal00000000000000from __future__ import unicode_literals import logging from mopidy import backend import pykka from .library import SoundCloudLibraryProvider from .soundcloud import SoundCloudClient logger = logging.getLogger(__name__) class SoundCloudBackend(pykka.ThreadingActor, backend.Backend): def __init__(self, config, audio): super(SoundCloudBackend, self).__init__() self.config = config self.remote = SoundCloudClient(config['soundcloud']) self.library = SoundCloudLibraryProvider(backend=self) self.playback = SoundCloudPlaybackProvider(audio=audio, backend=self) self.uri_schemes = ['soundcloud', 'sc'] class SoundCloudPlaybackProvider(backend.PlaybackProvider): def translate_uri(self, uri): track_id = self.backend.remote.parse_track_uri(uri) track = self.backend.remote.get_track(track_id, True) if track is None: return None return track.uri Mopidy-SoundCloud-2.0.2/mopidy_soundcloud/soundcloud.py0000664000175000017500000002454712632301774023551 0ustar jodaljodal00000000000000from __future__ import unicode_literals import collections import logging import re import string import time import unicodedata from multiprocessing.pool import ThreadPool from urllib import quote_plus from mopidy.models import Album, Artist, Track import requests logger = logging.getLogger(__name__) def safe_url(uri): return quote_plus( unicodedata.normalize('NFKD', unicode(uri)).encode('ASCII', 'ignore')) def readable_url(uri): valid_chars = "-_.() %s%s" % (string.ascii_letters, string.digits) safe_uri = unicodedata.normalize('NFKD', unicode(uri)).encode('ASCII', 'ignore') return re.sub('\s+', ' ', ''.join(c for c in safe_uri if c in valid_chars)).strip() class cache(object): # TODO: merge this to util library def __init__(self, ctl=8, ttl=3600): self.cache = {} self.ctl = ctl self.ttl = ttl self._call_count = 1 def __call__(self, func): def _memoized(*args): self.func = func now = time.time() try: value, last_update = self.cache[args] age = now - last_update if self._call_count >= self.ctl or age > self.ttl: self._call_count = 1 raise AttributeError self._call_count += 1 return value except (KeyError, AttributeError): value = self.func(*args) self.cache[args] = (value, now) return value except TypeError: return self.func(*args) return _memoized class SoundCloudClient(object): CLIENT_ID = '93e33e327fd8a9b77becd179652272e2' def __init__(self, config): super(SoundCloudClient, self).__init__() token = config['auth_token'] self.explore_songs = config.get('explore_songs', 10) self.http_client = requests.Session() self.http_client.headers.update({'Authorization': 'OAuth %s' % token}) try: self._get('me.json') except Exception as err: if err.response is not None and err.response.status_code == 401: logger.error('Invalid "auth_token" used for SoundCloud ' 'authentication!') else: raise @property @cache() def user(self): return self._get('me.json') @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) @cache() def get_explore_categories(self): return self._get('explore/categories', 'api-v2').get('music') 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( urn=explore[int(query_explore_id)], limit=self.explore_songs ) web_tracks = self._get(url, 'api-v2') track_ids = map(lambda x: x.get('id'), web_tracks.get('tracks')) return self.resolve_tracks(track_ids) return explore def get_groups(self, query_group_id=None): if query_group_id: web_tracks = self._get( 'groups/%d/tracks.json' % int(query_group_id)) tracks = [] for track in web_tracks: if 'track' in track.get('kind'): tracks.append(self.parse_track(track)) return tracks else: return self._get('me/groups.json') def get_followings(self, query_user_id=None): if query_user_id: return self._get('users/%s/tracks.json' % query_user_id) users = [] for playlist in self._get('me/followings.json?limit=60'): name = playlist.get('username') user_id = str(playlist.get('id')) logger.debug('Fetched user %s with id %s' % ( name, user_id )) users.append((name, user_id)) return users @cache() def get_set(self, set_id): playlist = self._get('playlists/%s.json' % set_id) return playlist.get('tracks', []) def get_sets(self): playable_sets = [] for playlist in self._get('me/playlists.json?limit=1000'): name = playlist.get('title') set_id = str(playlist.get('id')) tracks = playlist.get('tracks') logger.debug('Fetched set %s with id %s (%d tracks)' % ( name, set_id, len(tracks) )) playable_sets.append((name, set_id, tracks)) return playable_sets def get_user_liked(self): # Note: As with get_user_stream, this API call is undocumented. likes = [] liked = self._get('e1/me/likes.json?limit=1000') for data in liked: track = data['track'] if track: likes.append(self.parse_track(track)) pl = data['playlist'] if pl: likes.append((pl['title'], str(pl['id']))) return likes # Public @cache() def get_track(self, track_id, streamable=False): logger.debug('Getting info for track with id %s' % track_id) try: return self.parse_track(self._get('tracks/%s.json' % track_id), streamable) except Exception: return None def parse_track_uri(self, track): logger.debug('Parsing track %s' % (track)) if hasattr(track, "uri"): track = track.uri return track.split('.')[-1] def search(self, query): search_results = self._get( 'tracks.json?q=%s&filter=streamable&order=hotness&limit=%d' % ( quote_plus(query.encode('utf-8')), self.explore_songs)) tracks = [] for track in search_results: tracks.append(self.parse_track(track, False)) return self.sanitize_tracks(tracks) def parse_results(self, res): tracks = [] for track in res: tracks.append(self.parse_track(track)) return self.sanitize_tracks(tracks) def resolve_url(self, uri): return self.parse_results([self._get('resolve.json?url=%s' % uri)]) def _get(self, url, endpoint='api'): if '?' in url: url = '%s&client_id=%s' % (url, self.CLIENT_ID) else: url = '%s?client_id=%s' % (url, self.CLIENT_ID) url = 'https://%s.soundcloud.com/%s' % (endpoint, url) logger.debug('Requesting %s' % url) res = self.http_client.get(url) res.raise_for_status() return res.json() def sanitize_tracks(self, tracks): return filter(None, tracks) @cache() def parse_track(self, data, remote_url=False): if not data: return [] if not data['streamable']: logger.info( "'%s' can't be streamed from SoundCloud" % data.get('title')) return [] if not data['kind'] == 'track': logger.debug('%s is not track' % data.get('title')) return [] # NOTE kwargs dict keys must be bytestrings to work on Python < 2.6.5 # See https://github.com/mopidy/mopidy/issues/302 for details. track_kwargs = {} artist_kwargs = {} album_kwargs = {} if 'title' in data: name = data['title'] label_name = data.get('label_name') if bool(label_name): track_kwargs[b'name'] = name artist_kwargs[b'name'] = label_name else: track_kwargs[b'name'] = name artist_kwargs[b'name'] = data.get('user').get('username') album_kwargs[b'name'] = 'SoundCloud' if 'date' in data: track_kwargs[b'date'] = data['date'] if remote_url: if not self.can_be_streamed(data['stream_url']): logger.info( "'%s' can't be streamed from SoundCloud" % data.get( 'title')) return [] track_kwargs[b'uri'] = self.get_streamble_url(data['stream_url']) else: track_kwargs[b'uri'] = 'soundcloud:song/%s.%s' % ( readable_url(data.get('title')), data.get('id') ) track_kwargs[b'length'] = int(data.get('duration', 0)) track_kwargs[b'comment'] = data.get('permalink_url', '') if artist_kwargs: artist = Artist(**artist_kwargs) track_kwargs[b'artists'] = [artist] if album_kwargs: if 'artwork_url' in data and data['artwork_url']: album_kwargs[b'images'] = [data['artwork_url']] else: image = data.get('user').get('avatar_url') album_kwargs[b'images'] = [image] album = Album(**album_kwargs) track_kwargs[b'album'] = album track = Track(**track_kwargs) return track @cache() def can_be_streamed(self, url): req = self.http_client.head(self.get_streamble_url(url)) return req.status_code == 302 def get_streamble_url(self, url): return '%s?client_id=%s' % (url, self.CLIENT_ID) def resolve_tracks(self, track_ids): """Resolve tracks concurrently emulating browser :param track_ids:list of track ids :return:list `Track` """ pool = ThreadPool(processes=16) tracks = pool.map(self.get_track, track_ids) pool.close() tracks = [t for t in tracks if t is not None] return tracks