Skip to content

Instantly share code, notes, and snippets.

@EricRabil
Last active April 6, 2024 17:30
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save EricRabil/2f8cf09068274dd6c84500b1181db361 to your computer and use it in GitHub Desktop.
Save EricRabil/2f8cf09068274dd6c84500b1181db361 to your computer and use it in GitHub Desktop.

You'll need to get an access token first. The easiest way to do this is:

  1. Open Chrome
  2. Open devtools
  3. Go to the network inspector, and in the filter type "get_access_token"
  4. Navigate to https://open.spotify.com in that tab
  5. Click on the network request that shows up, then copy the entirety of the cookie header in the Request headers.

These access tokens expire, but the cookies appear to expire less frequently or not at all, so we will hold onto the cookies.

Whenever you need to get an access token, make a request to https://open.spotify.com/get_access_token?reason=transport&productType=web_player with the cookie header we extracted previously. The access token is in the accessToken property, and the expiration is in the accessTokenExpirationTimestampMs property.

To connect to the WebSocket API, open a connection to wss://guc-dealer.spotify.com/?access_token=${accessToken}. Please make sure to send a {"type":"ping"} message every thirty seconds (there's a bit of wiggle room, but please do your very best to stick to every thirty seconds). Below is an example of a player state update message:

{
   "headers":{
      "content-type":"application/json"
   },
   "payloads":[
      {
         "cluster":{
            "timestamp":"1586467152048",
            "active_device_id":"Playing Device ID",
            "player_state":{
               "timestamp":"1586467151009",
               "context_uri":"Spotify Context URI",
               "context_url":"",
               "context_restrictions":{
                  "disallow_toggling_repeat_context_reasons":[
                     "disallow-radio"
                  ],
                  "disallow_toggling_repeat_track_reasons":[
                     "disallow-radio"
                  ],
                  "disallow_toggling_shuffle_reasons":[
                     "disallow-radio"
                  ]
               },
               "play_origin":{
                  "feature_identifier":"radio",
                  "feature_version":"1.1.30",
                  "view_uri":"Spotify View URI",
                  "referrer_identifier":"spotify:app:station"
               },
               "index":{
                  "page":0,
                  "track":23
               },
               "track":{
                  "uri":"Spotify Track URI",
                  "uid":"Track UID",
                  "metadata":{
                     "context_uri":"Spotify Context URI",
                     "player":"audio",
                     "album_title":"Free Time",
                     "album_uri":"spotify:album:6b5iXFsc488so2TO4kUHfr",
                     "album_track_count":"7",
                     "image_small_url":"spotify:image:ab67616d00004851f7406e9cbd7f974e7ddf7b0e",
                     "image_url":"spotify:image:ab67616d0000b273f7406e9cbd7f974e7ddf7b0e",
                     "entity_uri":"Spotify Entity URI",
                     "artist_uri":"spotify:artist:5xkAtLTf309LAGZTbvULBn",
                     "is_explicit":"false",
                     "is_local":"false",
                     "album_disc_number":"1",
                     "title":"Painkiller",
                     "album_disc_count":"1",
                     "album_artist_name":"Ruel",
                     "radio.thumb":"",
                     "duration":"214000",
                     "collection.in_collection":"false",
                     "album_track_number":"3",
                     "image_xlarge_url":"spotify:image:ab67616d0000b273f7406e9cbd7f974e7ddf7b0e",
                     "popularity":"76",
                     "iteration":"0",
                     "collection.can_add":"true",
                     "has_lyrics":"false",
                     "is_promotional":"false",
                     "artist_name":"Ruel",
                     "image_large_url":"spotify:image:ab67616d0000b273f7406e9cbd7f974e7ddf7b0e",
                     "available_file_formats":"[\"OGG_VORBIS_320\",\"OGG_VORBIS_160\",\"OGG_VORBIS_96\",\"AAC_24\"]"
                  },
                  "provider":"context"
               },
               "playback_id":"Playback ID",
               "playback_speed":1.0,
               "position_as_of_timestamp":"0",
               "duration":"213506",
               "is_playing":true,
               "is_paused":false,
               "is_system_initiated":false,
               "options":{
                  "shuffling_context":false,
                  "repeating_context":false,
                  "repeating_track":false
               },
               "restrictions":{
                  "disallow_resuming_reasons":[
                     "not_paused"
                  ],
                  "disallow_toggling_repeat_context_reasons":[
                     "disallow-radio",
                     "endless_context"
                  ],
                  "disallow_toggling_repeat_track_reasons":[
                     "disallow-radio"
                  ],
                  "disallow_toggling_shuffle_reasons":[
                     "disallow-radio",
                     "endless_context"
                  ]
               },
               "suppressions":{

               },
               "prev_tracks":[
                 // array of previous tracks
               ],
               "next_tracks":[
                 // array of upcoming tracks
               ],
               "context_metadata":{
                  "context_description":"20205"
               },
               "page_metadata":{

               },
               "session_id":"session ID",
               "queue_revision":"revision ID"
            },
            "devices":{
               "Device ID":{
                  "can_play":true,
                  "volume":20971,
                  "name":"Device Name",
                  "capabilities":{
                     "can_be_player":true,
                     "gaia_eq_connect_id":true,
                     "supports_logout":true,
                     "is_observable":true,
                     "volume_steps":64,
                     "supported_types":[
                        "audio/ad",
                        "audio/episode",
                        "audio/interruption",
                        "audio/local",
                        "audio/track",
                        "video/ad",
                        "video/episode"
                     ],
                     "command_acks":true,
                     "supports_rename":true,
                     "supports_playlist_v2":true,
                     "is_controllable":true,
                     "supports_external_episodes":true,
                     "supports_transfer_command":true,
                     "supports_command_request":true,
                     "supports_gzip_pushes":true
                  },
                  "device_software_version":"Software Version",
                  "device_type":"Device Type",
                  "spirc_version":"3.2.6",
                  "device_id":"Device ID",
                  "metadata_map":{
                     "device_address_mask":"10.9.142.81/8",
                     "tier1_port":"50483"
                  }
               }
            },
            "transfer_data_timestamp":"1586467151015"
         },
         "update_reason":"DEVICE_STATE_CHANGED",
         "devices_that_changed":[
            // Array of Device IDs with changed states
         ]
      }
   ],
   "type":"message",
   "uri":"hm://connect-state/v1/cluster"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment