storage->accessToken === '' || $this->storage->refreshToken === '') { $this->authorise(); $this->requestAccessToken(); } if ($this->storage->expiresOn < time()) { $this->refreshAccessToken(); } return $this->storage->accessToken; } private function authorise(): void { $this->authorisationCode = ''; $this->httpServer->get( '/callback', function (Request $request, HttpServer $server): Response { $server->closeAfterResponse(); if ( !array_key_exists('state', $request->params) || $request->params['state'] !== $this->credentials->state ) { $body = 'State mismatch.'; } elseif (array_key_exists('error', $request->params)) { $body = "Error from Spotify: {$request->params['error']}"; } elseif (!array_key_exists('code', $request->params)) { $body = "Missing authorisation code from Spotify."; } else { $this->authorisationCode = $request->params['code']; $body = 'Authorisation code received from Spotify. You can close this page and go back to the terminal.'; } return new Response( statusCode: 200, contentType: 'text/plain; charset=UTF-8', body: htmlspecialchars($body, ENT_QUOTES), ); }, ); $query = http_build_query([ 'response_type' => 'code', 'client_id' => $this->credentials->clientID, 'redirect_uri' => $this->credentials->redirectURI, 'state' => $this->credentials->state, 'scope' => 'playlist-read-private', 'show_dialog' => false, ]); echo "Visit the below URL in your browser to authorise access to Spotify.\n"; echo "https://accounts.spotify.com/authorize?$query\n"; echo "Waiting for authorisation from Spotify...\n"; $this->httpServer->run(); if ($this->authorisationCode === '') { echo "Failed to get authorisation code from Spotify.\n"; exit(1); } } private function requestAccessToken(): void { echo "Requesting an access token from Spotify.\n"; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://accounts.spotify.com/api/token', CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Basic ' . base64_encode($this->credentials->clientID . ':' . $this->credentials->clientSecret), 'Content-Type: application/x-www-form-urlencoded', ], CURLOPT_POSTFIELDS => http_build_query([ 'grant_type' => 'authorization_code', 'code' => $this->authorisationCode, 'redirect_uri' => $this->credentials->redirectURI, ]), CURLOPT_RETURNTRANSFER => true, ]); $response = curl_exec($ch); $statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); if ($statusCode !== 200) { echo "Non 200 response from Spotify: $statusCode $response\n"; exit(1); } $data = json_decode($response, true) ?? []; $accessToken = $data['access_token'] ?? ''; $refreshToken = $data['refresh_token'] ?? ''; $expiresIn = $data['expires_in'] ?? 0; if ($accessToken === '' || $refreshToken === '') { echo "Failed to get access token from Spotify.\n"; exit(1); } $this->storage->accessToken = $accessToken; $this->storage->refreshToken = $refreshToken; $this->storage->expiresOn = time() + $expiresIn; $this->storage->save(); } private function refreshAccessToken(): void { echo "Refreshing the Spotify access token.\n"; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://accounts.spotify.com/api/token', CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Basic ' . base64_encode($this->credentials->clientID . ':' . $this->credentials->clientSecret), 'Content-Type: application/x-www-form-urlencoded', ], CURLOPT_POSTFIELDS => http_build_query([ 'grant_type' => 'refresh_token', 'refresh_token' => $this->storage->refreshToken, ]), CURLOPT_RETURNTRANSFER => true, ]); $response = curl_exec($ch); $statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); if ($statusCode !== 200) { echo "Non 200 response from Spotify: $statusCode $response\n"; exit(1); } $data = json_decode($response, true) ?? []; $accessToken = $data['access_token'] ?? ''; $refreshToken = $data['refresh_token'] ?? $this->storage->refreshToken; $expiresIn = $data['expires_in'] ?? 0; if ($accessToken === '' || $refreshToken === '') { echo "Failed to refresh the Spotify access token.\n"; exit(1); } $this->storage->accessToken = $accessToken; $this->storage->refreshToken = $refreshToken; $this->storage->expiresOn = time() + $expiresIn; $this->storage->save(); } }