├── README.md └── http_remote.py /README.md: -------------------------------------------------------------------------------- 1 | A simple Python client that describes how to interact with the local, built-in HTTP server inside the Spotify player. 2 | 3 | For more information, see my [blog post](http://cgbystrom.com/articles/deconstructing-spotifys-builtin-http-server/) describing more details. 4 | -------------------------------------------------------------------------------- /http_remote.py: -------------------------------------------------------------------------------- 1 | import ssl 2 | from string import ascii_lowercase 3 | from random import choice 4 | import urllib 5 | import urllib2 6 | import json 7 | import time 8 | 9 | # Default port that Spotify Web Helper binds to. 10 | PORT = 4370 11 | DEFAULT_RETURN_ON = ['login', 'logout', 'play', 'pause', 'error', 'ap'] 12 | ORIGIN_HEADER = {'Origin': 'https://open.spotify.com'} 13 | 14 | # I had some troubles with the version of Spotify's SSL cert and Python 2.7 on Mac. 15 | # Did this monkey dirty patch to fix it. Your milage may vary. 16 | def new_wrap_socket(*args, **kwargs): 17 | kwargs['ssl_version'] = ssl.PROTOCOL_SSLv3 18 | return orig_wrap_socket(*args, **kwargs) 19 | 20 | orig_wrap_socket, ssl.wrap_socket = ssl.wrap_socket, new_wrap_socket 21 | 22 | def get_json(url, params={}, headers={}): 23 | if params: 24 | url += "?" + urllib.urlencode(params) 25 | request = urllib2.Request(url, headers=headers) 26 | return json.loads(urllib2.urlopen(request).read()) 27 | 28 | 29 | def generate_local_hostname(): 30 | """Generate a random hostname under the .spotilocal.com domain""" 31 | subdomain = ''.join(choice(ascii_lowercase) for x in range(10)) 32 | return subdomain + '.spotilocal.com' 33 | 34 | 35 | def get_url(url): 36 | return "https://%s:%d%s" % (generate_local_hostname(), PORT, url) 37 | 38 | 39 | def get_version(): 40 | return get_json(get_url('/service/version.json'), params={'service': 'remote'}, headers=ORIGIN_HEADER) 41 | 42 | 43 | def get_oauth_token(): 44 | return get_json('http://open.spotify.com/token')['t'] 45 | 46 | 47 | def get_csrf_token(): 48 | # Requires Origin header to be set to generate the CSRF token. 49 | return get_json(get_url('/simplecsrf/token.json'), headers=ORIGIN_HEADER)['token'] 50 | 51 | 52 | def get_status(oauth_token, csrf_token, return_after=59, return_on=DEFAULT_RETURN_ON): 53 | params = { 54 | 'oauth': oauth_token, 55 | 'csrf': csrf_token, 56 | 'returnafter': return_after, 57 | 'returnon': ','.join(return_on) 58 | } 59 | return get_json(get_url('/remote/status.json'), params=params, headers=ORIGIN_HEADER) 60 | 61 | 62 | def pause(oauth_token, csrf_token, pause=True): 63 | params = { 64 | 'oauth': oauth_token, 65 | 'csrf': csrf_token, 66 | 'pause': 'true' if pause else 'false' 67 | } 68 | get_json(get_url('/remote/pause.json'), params=params, headers=ORIGIN_HEADER) 69 | 70 | 71 | def unpause(oauth_token, csrf_token): 72 | pause(oauth_token, csrf_token, pause=False) 73 | 74 | 75 | def play(oauth_token, csrf_token, spotify_uri): 76 | params = { 77 | 'oauth': oauth_token, 78 | 'csrf': csrf_token, 79 | 'uri': spotify_uri, 80 | 'context': spotify_uri, 81 | } 82 | get_json(get_url('/remote/play.json'), params=params, headers=ORIGIN_HEADER) 83 | 84 | 85 | def open_spotify_client(): 86 | return get(get_url('/remote/open.json'), headers=ORIGIN_HEADER).text 87 | 88 | 89 | if __name__ == '__main__': 90 | oauth_token = get_oauth_token() 91 | csrf_token = get_csrf_token() 92 | 93 | print "Playing album.." 94 | play(oauth_token, csrf_token, 'spotify:album:6eWtdQm0hSlTgpkbw4LaBG') 95 | time.sleep(5) 96 | print "Pausing song.." 97 | pause(oauth_token, csrf_token) 98 | time.sleep(2) 99 | print "Unpausing song.." 100 | unpause(oauth_token, csrf_token) 101 | --------------------------------------------------------------------------------