#!/usr/bin/env bash # # spotify-prime-speaker — Prime a Bose SoundTouch speaker for Spotify playback # # Activates Spotify on a SoundTouch speaker by sending an access token # via the Spotify Connect ZeroConf endpoint (port 8200). This is the # same mechanism the Spotify desktop app uses internally. # # Works standalone — no soundtouch-service, ueberboese, or other server required. # # Requirements: curl, jq # # Usage: # ./spotify-prime-speaker SPEAKER_IP ACCESS_TOKEN # # Example: # ./spotify-prime-speaker 192.0.2.143 BQDj...your_token... # # How to get an access token: # - Spotify Developer Console: https://developer.spotify.com # (create an app, use the "Get Token" button) # - Via soundtouch-service management API: POST /mgmt/spotify/auth/init # - Via ueberboese management API: POST /mgmt/spotify/init # - Any Spotify OAuth Authorization Code flow with user-read-email scope # # Notes: # - Access tokens expire after 1 hour (3600 seconds) # - The speaker must be on the same network and reachable on port 8200 # - After priming, Spotify presets on the speaker should work immediately # - Re-run after each speaker reboot (or use a server like soundtouch-service # to automate this) # # How it works: # The Bose SoundTouch speaker exposes a Spotify Connect ZeroConf API # on port 8200. By sending an addUser request with a valid Spotify # access token, the speaker activates its built-in Spotify Connect # client. No encryption is needed — the token is sent as plain text, # exactly like the Spotify desktop app does it. # # Related: # - https://github.com/gesellix/Bose-SoundTouch (comprehensive toolkit) set -euo pipefail # --- Argument parsing --- if [ $# -lt 2 ]; then echo "Usage: $0 SPEAKER_IP ACCESS_TOKEN" echo "" echo "Prime a Bose SoundTouch speaker for Spotify playback." echo "" echo "Arguments:" echo " SPEAKER_IP IP address of the SoundTouch speaker" echo " ACCESS_TOKEN Spotify access token (starts with BQ...)" echo "" echo "Get a token at https://developer.spotify.com or via a server's OAuth flow." exit 1 fi SPEAKER_IP="$1" TOKEN="$2" ZC_URL="http://${SPEAKER_IP}:8200/zc" # --- Dependency check --- for cmd in curl jq; do if ! command -v "$cmd" &>/dev/null; then echo "Error: $cmd is required but not installed." >&2 exit 1 fi done # --- Step 1: Discover Spotify username from token --- echo "Discovering Spotify user from token..." ME_RESPONSE=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \ https://api.spotify.com/v1/me 2>&1) || { echo "Error: Failed to call Spotify /me API. Is the token valid?" >&2 echo " (tokens expire after 1 hour)" >&2 exit 1 } USER=$(echo "$ME_RESPONSE" | jq -r '.id // empty') if [ -z "$USER" ]; then echo "Error: Could not extract user ID from Spotify response." >&2 echo "$ME_RESPONSE" >&2 exit 1 fi echo " Spotify user: $USER" # --- Step 2: Check current speaker status --- echo "Checking speaker at ${SPEAKER_IP}:8200..." INFO=$(curl -sf "${ZC_URL}?action=getInfo" 2>&1) || { echo "Error: Could not reach speaker at ${SPEAKER_IP}:8200." >&2 echo " Is the speaker on and on the same network?" >&2 exit 1 } ACTIVE=$(echo "$INFO" | jq -r '.activeUser // empty') DEVICE_NAME=$(echo "$INFO" | jq -r '.remoteName // empty') if [ -n "$DEVICE_NAME" ]; then echo " Speaker: $DEVICE_NAME" fi if [ -n "$ACTIVE" ]; then echo " Already primed (activeUser=$ACTIVE)" echo "Done — speaker is ready for Spotify playback." exit 0 fi echo " No active Spotify user — priming now..." # --- Step 3: Send addUser --- RESULT=$(curl -sf -X POST "${ZC_URL}" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "action=addUser&userName=${USER}&blob=${TOKEN}&clientKey=&tokenType=accesstoken" \ 2>&1) || { echo "Error: addUser request failed." >&2 exit 1 } STATUS=$(echo "$RESULT" | jq -r '.status // -1') STATUS_STR=$(echo "$RESULT" | jq -r '.statusString // empty') if [ "$STATUS" != "101" ]; then echo "Error: Speaker returned status $STATUS ($STATUS_STR)" >&2 echo "$RESULT" | jq . >&2 exit 1 fi echo " Speaker accepted the token (status 101)." # --- Step 4: Verify --- echo " Verifying (waiting 2 seconds)..." sleep 2 ACTIVE=$(curl -sf "${ZC_URL}?action=getInfo" | jq -r '.activeUser // empty') if [ -n "$ACTIVE" ]; then echo "Done — speaker primed for Spotify (activeUser=$ACTIVE)" else echo "Warning: Speaker returned 101 but activeUser is still empty." echo " The speaker may need more time. Try pressing a Spotify preset." exit 1 fi