From b9c4f1dcf1333c14e2d1b67561ec7a5f955baa6e Mon Sep 17 00:00:00 2001 From: "David T. Sadler" Date: Sun, 9 Aug 2026 20:04:39 +0100 Subject: Initial commit --- src/TokenManager.php | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 src/TokenManager.php (limited to 'src/TokenManager.php') diff --git a/src/TokenManager.php b/src/TokenManager.php new file mode 100644 index 0000000..96b3969 --- /dev/null +++ b/src/TokenManager.php @@ -0,0 +1,179 @@ +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(); + } +} -- cgit v1.2.3-13-gbd6f