false, 'error'=>$code], $extra)); } function spotify_refresh_access_token(string $refreshToken): array { if (!defined('SPOTIFY_CLIENT_ID') || !defined('SPOTIFY_CLIENT_SECRET')) { return ['ok'=>false, 'why'=>'spotify_constants_missing']; } if (SPOTIFY_CLIENT_ID === '' || SPOTIFY_CLIENT_SECRET === '') { return ['ok'=>false, 'why'=>'spotify_client_empty']; } $ch = curl_init('https://accounts.spotify.com/api/token'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query([ 'grant_type' => 'refresh_token', 'refresh_token' => $refreshToken, ]), CURLOPT_HTTPHEADER => [ 'Authorization: Basic ' . base64_encode(SPOTIFY_CLIENT_ID . ':' . SPOTIFY_CLIENT_SECRET), 'Content-Type: application/x-www-form-urlencoded', ], CURLOPT_TIMEOUT => 10, ]); $raw = curl_exec($ch); $curlErr = curl_error($ch); $http = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $data = is_string($raw) ? json_decode($raw, true) : null; if (!is_string($raw)) { return ['ok'=>false,'why'=>'curl_failed','curl_error'=>$curlErr ?: 'unknown']; } if ($http < 200 || $http >= 300) { return ['ok'=>false,'why'=>'spotify_token_http','status'=>$http,'spotify'=>$data,'raw'=>$raw]; } if (!is_array($data) || empty($data['access_token'])) { return ['ok'=>false,'why'=>'spotify_token_bad_json','status'=>$http,'spotify'=>$data,'raw'=>$raw]; } return ['ok'=>true,'data'=>$data]; } function spotify_api_get(string $url, string $accessToken): array { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $accessToken, 'Accept: application/json', ], CURLOPT_TIMEOUT => 10, ]); $raw = curl_exec($ch); $curlErr = curl_error($ch); $http = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $data = is_string($raw) ? json_decode($raw, true) : null; return [$http, $data, $raw, $curlErr]; } try { // sanity: does pdo() exist? if (!function_exists('pdo')) safe_err('pdo_function_missing'); // what DB are we using? $dbName = pdo()->query("SELECT DATABASE()")->fetchColumn(); if (!$dbName) safe_err('no_database_selected'); // does table exist? $stmt = pdo()->query("SHOW TABLES LIKE 'spotify_tokens'"); $tbl = $stmt ? $stmt->fetchColumn() : null; if (!$tbl) safe_err('table_missing', ['db'=>$dbName]); // fetch row $q = pdo()->query("SELECT refresh_token, access_token, access_expires FROM spotify_tokens WHERE id=1"); if (!$q) safe_err('query_failed'); $row = $q->fetch(PDO::FETCH_ASSOC); if (!$row) safe_err('row_missing_id_1', ['db'=>$dbName]); $refresh = trim((string)($row['refresh_token'] ?? '')); $access = trim((string)($row['access_token'] ?? '')); $exp = (int)($row['access_expires'] ?? 0); if ($refresh === '') safe_err('refresh_token_empty_in_db', ['db'=>$dbName]); // refresh if needed if ($access === '' || $exp <= (time() + 30)) { $new = spotify_refresh_access_token($refresh); if (empty($new['ok'])) { safe_err('refresh_failed', ['detail'=>$new, 'db'=>$dbName]); } $access = (string)$new['data']['access_token']; $exp = time() + (int)($new['data']['expires_in'] ?? 3600); $ok = pdo()->prepare("UPDATE spotify_tokens SET access_token=?, access_expires=? WHERE id=1") ->execute([$access, $exp]); if (!$ok) safe_err('db_update_failed'); } // now playing [$http, $data, $raw, $curlErr] = spotify_api_get('https://api.spotify.com/v1/me/player/currently-playing', $access); if ($http === 200 && is_array($data) && !empty($data['item'])) { $t = $data['item']; $artists = []; foreach (($t['artists'] ?? []) as $a) $artists[] = (string)($a['name'] ?? ''); $img = (string)($t['album']['images'][0]['url'] ?? ''); json_out([ 'ok' => true, 'mode' => !empty($data['is_playing']) ? 'playing' : 'recent', 'track' => [ 'title' => (string)($t['name'] ?? ''), 'artist' => trim(implode(', ', array_filter($artists))), 'art' => $img, 'url' => (string)($t['external_urls']['spotify'] ?? ''), ], ]); } // fallback: recently played [$http2, $data2, $raw2, $curlErr2] = spotify_api_get('https://api.spotify.com/v1/me/player/recently-played?limit=1', $access); $item = (is_array($data2) && !empty($data2['items'][0]['track'])) ? $data2['items'][0]['track'] : null; if ($http2 === 200 && is_array($item)) { $artists = []; foreach (($item['artists'] ?? []) as $a) $artists[] = (string)($a['name'] ?? ''); $img = (string)($item['album']['images'][0]['url'] ?? ''); json_out([ 'ok' => true, 'mode' => 'recent', 'track' => [ 'title' => (string)($item['name'] ?? ''), 'artist' => trim(implode(', ', array_filter($artists))), 'art' => $img, 'url' => (string)($item['external_urls']['spotify'] ?? ''), ], ]); } safe_err('no_track', [ 'currently_playing_http' => $http, 'recent_http' => $http2 ]); } catch (Throwable $e) { safe_err('exception', [ 'type' => get_class($e), 'msg' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), ]); }