Skip to content

Commit

Permalink
Merge 7308d97 into bd682c3
Browse files Browse the repository at this point in the history
  • Loading branch information
ticosax committed Nov 19, 2016
2 parents bd682c3 + 7308d97 commit d1e8236
Show file tree
Hide file tree
Showing 18 changed files with 2,653 additions and 693 deletions.
1 change: 1 addition & 0 deletions mopidy_soundcloud/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def get_config_schema(self):
schema['auth_token'] = config.Secret()
schema['explore'] = config.Deprecated()
schema['explore_pages'] = config.Deprecated()
schema['http_max_retries'] = config.Integer()
return schema

def validate_config(self, config): # no_coverage
Expand Down
7 changes: 5 additions & 2 deletions mopidy_soundcloud/ext.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
enabled = True

# Your SoundCloud auth token, you can get yours at http://www.mopidy.com/authenticate
auth_token =
auth_token =

# Number of songs to fetch in explore section
explore_songs = 25
explore_songs = 25

# For slow devices like the raspberrypi you might want to retry http connections
http_max_retries = 3
40 changes: 0 additions & 40 deletions mopidy_soundcloud/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,7 @@ class SoundCloudLibraryProvider(backend.LibraryProvider):
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']))
Expand Down Expand Up @@ -92,28 +90,6 @@ def list_user_follows(self):
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:
Expand Down Expand Up @@ -145,22 +121,6 @@ def browse(self, uri):
)
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()
Expand Down
59 changes: 17 additions & 42 deletions mopidy_soundcloud/soundcloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from mopidy.models import Album, Artist, Track

import requests
from requests.adapters import HTTPAdapter


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -71,11 +72,15 @@ def __init__(self, config):
super(SoundCloudClient, self).__init__()
token = config['auth_token']
self.explore_songs = config.get('explore_songs', 10)
max_retries = config.get('http_max_retries',
requests.adapters.DEFAULT_RETRIES)
self.http_client = requests.Session()
self.http_client.mount('https://api.soundcloud.com',
HTTPAdapter(max_retries=max_retries))
self.http_client.headers.update({'Authorization': 'OAuth %s' % token})

try:
self._get('me.json')
self._get('me')
except Exception as err:
if err.response is not None and err.response.status_code == 401:
logger.error('Invalid "auth_token" used for SoundCloud '
Expand All @@ -86,16 +91,16 @@ def __init__(self, config):
@property
@cache()
def user(self):
return self._get('me.json')
return self._get('me')

@cache()
def get_user_stream(self):
# User timeline like playlist which uses undocumented api
# https://api.soundcloud.com/e1/me/stream.json?offset=0
# https://api.soundcloud.com/e1/me/stream?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)
stream = self._get('e1/me/stream?offset=%s' % sid * 5)
for data in stream.get('collection'):
kind = data.get('type')
# multiple types of track with same data
Expand All @@ -108,43 +113,13 @@ def get_user_stream(self):

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 self.sanitize_tracks(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)
return self._get('users/%s/tracks' % query_user_id)

users = []
for playlist in self._get('me/followings.json?limit=60'):
for playlist in self._get('me/followings.json?limit=60')['collection']:
name = playlist.get('username')
user_id = str(playlist.get('id'))
logger.debug('Fetched user %s with id %s' % (
Expand All @@ -156,12 +131,12 @@ def get_followings(self, query_user_id=None):

@cache()
def get_set(self, set_id):
playlist = self._get('playlists/%s.json' % set_id)
playlist = self._get('playlists/%s' % set_id)
return playlist.get('tracks', [])

def get_sets(self):
playable_sets = []
for playlist in self._get('me/playlists.json?limit=1000'):
for playlist in self._get('me/playlists?limit=1000'):
name = playlist.get('title')
set_id = str(playlist.get('id'))
tracks = playlist.get('tracks')
Expand All @@ -174,7 +149,7 @@ def get_sets(self):
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')
liked = self._get('e1/me/likes?limit=1000')
for data in liked:

track = data['track']
Expand All @@ -192,7 +167,7 @@ def get_user_liked(self):
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),
return self.parse_track(self._get('tracks/%s' % track_id),
streamable)
except Exception:
return None
Expand All @@ -206,7 +181,7 @@ def parse_track_uri(self, track):
def search(self, query):

search_results = self._get(
'tracks.json?q=%s&filter=streamable&order=hotness&limit=%d' % (
'tracks?q=%s&filter=streamable&order=hotness&limit=%d' % (
quote_plus(query.encode('utf-8')), self.explore_songs))
tracks = []
for track in search_results:
Expand All @@ -220,7 +195,7 @@ def parse_results(self, res):
return self.sanitize_tracks(tracks)

def resolve_url(self, uri):
return self.parse_results([self._get('resolve.json?url=%s' % uri)])
return self.parse_results([self._get('resolve?url=%s' % uri)])

def _get(self, url, endpoint='api'):
if '?' in url:
Expand Down
32 changes: 0 additions & 32 deletions tests/fixtures/sc-explore.yaml

This file was deleted.

61 changes: 53 additions & 8 deletions tests/fixtures/sc-following.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,71 @@ interactions:
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
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.11.1]
method: GET
uri: https://api.soundcloud.com:443/me/followings.json?limit=60&client_id=93e33e327fd8a9b77becd179652272e2
uri: https://api.soundcloud.com/me/followings.json?limit=60&client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA4uOBQApu0wNAgAAAA==
H4sIAAAAAAAAAJyTTY/aMBCG/0rka4HE+QCSU6uWa1Wp6qlbRcYxMMWxI9uBRSv+e8dLCAF2pbLi
kryZmXc+Hl4I11IK7kArUvx+IWzHHDNlayQpyMa5xhZhCHRiVcUrNeG6Dk8hdhxFURyneZ5mSTbm
m2SauLFkZi0mf5s1GRGoSEHnSZLQOJqOyBYUCqS1wuDHRpiaSVDbgRU6Wd2ikdRt9WrlNmDBqtZn
tAYGLbEGJjfBvrINe0efgopitcC8U43eFZVhbcmsK2tdwQqEbzKO6DSkUUhnAc2LjBbZNPiE80YY
uwKDwV3Z7/qS30mLmtU+rJVyGBV0Ogd3QOkrA6PxtRKWG2hO+yc/jK5aLsyTWjxjr1AL5ZgMfvpB
gy/GgXVPyv9e07tHjTHcFYFCl4lAl1n8eV0zkH4r3hGznfGmi/Whcag4w/i2fNXxRDPcS7uUwMsV
22kDTtjztwjnQD70Hjd71pI0P6ug1r2cxVhGsm4KNKkPtmFcdDtQuA6cFizXmDPU9mJp0bN04OSt
eH41otHW9V7UD1X75VykdES0wtNizopJK/AosL2aBLs7SNxgn3IcPcw7ndEsRcTGqx3VS7jjfZbM
8zj7GOyVUHqHYf/P+sntGvRvlypD2gfF34M9jgI6L2hSZOnbsN+AfgP5he07rIcQng56RWCaPIhg
SnOavQVhNGQw+IU81Fir+jiN/d66/1LP5fmG+/1+chNzA2t6D2v0OKvz458RUeLZlRsjVqduj/8A
AAD//wMAEhk/kr4FAAA=
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"']
content-length: ['584']
content-type: [application/json]
date: ['Thu, 27 Oct 2016 20:26:05 GMT']
server: [am/2]
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.11.1]
method: GET
uri: https://api.soundcloud.com/me/followings?limit=60&client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAAAJyTTY/aMBCG/0rka4HE+QCSU6uWa1Wp6qlbRcYxMMWxI9uBRSv+e8dLCAF2pbLi
kryZmXc+Hl4I11IK7kArUvx+IWzHHDNlayQpyMa5xhZhCHRiVcUrNeG6Dk8hdhxFURyneZ5mSTbm
m2SauLFkZi0mf5s1GRGoSEHnSZLQOJqOyBYUCqS1wuDHRpiaSVDbgRU6Wd2ikdRt9WrlNmDBqtZn
tAYGLbEGJjfBvrINe0efgopitcC8U43eFZVhbcmsK2tdwQqEbzKO6DSkUUhnAc2LjBbZNPiE80YY
uwKDwV3Z7/qS30mLmtU+rJVyGBV0Ogd3QOkrA6PxtRKWG2hO+yc/jK5aLsyTWjxjr1AL5ZgMfvpB
gy/GgXVPyv9e07tHjTHcFYFCl4lAl1n8eV0zkH4r3hGznfGmi/Whcag4w/i2fNXxRDPcS7uUwMsV
22kDTtjztwjnQD70Hjd71pI0P6ug1r2cxVhGsm4KNKkPtmFcdDtQuA6cFizXmDPU9mJp0bN04OSt
eH41otHW9V7UD1X75VykdES0wtNizopJK/AosL2aBLs7SNxgn3IcPcw7ndEsRcTGqx3VS7jjfZbM
8zj7GOyVUHqHYf/P+sntGvRvlypD2gfF34M9jgI6L2hSZOnbsN+AfgP5he07rIcQng56RWCaPIhg
SnOavQVhNGQw+IU81Fir+jiN/d66/1LP5fmG+/1+chNzA2t6D2v0OKvz458RUeLZlRsjVqduj/8A
AAD//wMAEhk/kr4FAAA=
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: ['584']
content-type: [application/json]
date: ['Thu, 27 Oct 2016 20:34:06 GMT']
server: [am/2]
status: {code: 200, message: OK}
version: 1
Loading

0 comments on commit d1e8236

Please sign in to comment.