true, CURLOPT_CONNECTTIMEOUT => (int) $timeout, CURLOPT_TIMEOUT => (int) $timeout, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 3, CURLOPT_USERAGENT => cfg('runtime.user_agent'), ]); if (!empty($headers)) { curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); } $body = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); curl_close($ch); if ($body === false || $code >= 400) return null; return $body; } // Fallback without cURL. $ctx = stream_context_create([ 'http' => [ 'timeout' => (int) $timeout, 'header' => implode("\r\n", array_merge(['User-Agent: ' . cfg('runtime.user_agent')], $headers)), 'ignore_errors' => true, ], 'ssl' => ['verify_peer' => true, 'verify_peer_name' => true], ]); $body = @file_get_contents($url, false, $ctx); return ($body === false) ? null : $body; } /** * Read a value out of a decoded-JSON structure using dot notation. * Supports array indexes: "a.b.0.c". */ function dot_get($data, $path, $default = null) { if ($path === '' || $path === null) return $default; $node = $data; foreach (explode('.', $path) as $key) { if (is_array($node) && array_key_exists($key, $node)) { $node = $node[$key]; } elseif (is_object($node) && isset($node->$key)) { $node = $node->$key; } else { return $default; } } return $node; } /** Read the artist/title the browser sends to artwork/lyrics endpoints. */ function request_track() { $artist = $_POST['artist'] ?? $_GET['artist'] ?? ''; $title = $_POST['title'] ?? $_GET['title'] ?? ''; return [trim((string) $artist), trim((string) $title)]; } /** * Strip a featuring-artist credit ("(feat. X)", "(ft. X)", "(featuring X)", * "(with X)", bracket variants, or an unparenthesized trailing "feat. X") * before it goes into a Genius/dumb search query. * * Genius's canonical song title never includes the featuring credit (it's * separate metadata), but translation/cover pages often spell it out * verbatim in their title — e.g. searching "Havana (feat. Young Thug)" * surfaces only German/French translation pages (titled "... ft. Young * Thug ... (Deutsche Übersetzung)" etc.), while "Havana" alone correctly * finds the original as the first hit. Leaves other qualifiers like * "(Remix)"/"(Live)" alone — those genuinely change which page you want. */ function strip_feature_credit($title) { $title = preg_replace('/[\(\[]\s*(?:feat\.?|ft\.?|featuring|with)\b[^\)\]]*[\)\]]/i', '', $title); $title = preg_replace('/\s+(?:feat\.?|ft\.?|featuring)\b.+$/i', '', $title); return trim(preg_replace('/\s{2,}/', ' ', $title)); } /** * Loose "is this search hit actually by the artist we want" check — * case-insensitive, ignores punctuation/spacing and anything in brackets * (so "Camila Cabello (Ft. Young Thug)" still matches "Camila Cabello"). * An empty side always matches (nothing to compare against). Used to skip * past hits attributed to someone else entirely — most notably Genius's * "Genius Deutsche Übersetzungen" / "Genius traductions françaises" / etc. * pseudo-artist accounts that translation pages get filed under. */ function artist_loosely_matches($hitArtist, $wantArtist) { $norm = function ($s) { $s = preg_replace('/[\(\[][^\)\]]*[\)\]]/', '', (string) $s); $s = preg_replace('/[^\p{L}\p{N}]+/u', '', $s); return strtolower($s); }; $a = $norm($hitArtist); $b = $norm($wantArtist); if ($a === '' || $b === '') return true; return strpos($a, $b) !== false || strpos($b, $a) !== false; } /** * Return the inner HTML of a balanced