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)];
}
/**
* Return the inner HTML of a balanced
whose opening tag starts at $lt.
* Correctly handles nested
elements. Shared by the Genius and dumb
* lyrics scrapers (both extract a lyrics container out of a raw HTML page).
*/
function inner_balanced_div($html, $lt) {
$open = strpos($html, '>', $lt);
if ($open === false) return '';
$i = $open + 1;
$start = $i;
$len = strlen($html);
$depth = 1;
while ($i < $len && $depth > 0) {
$next = strpos($html, '<', $i);
if ($next === false) break;
if (strncasecmp(substr($html, $next, 5), '
newlines before stripping tags.
$fragment = preg_replace('#
#i', "\n", $fragment);
// Drop any stray script/style.
$fragment = preg_replace('#<(script|style)\b[^>]*>.*?\1>#is', '', $fragment);
// Block-level tags imply a line break.
$fragment = preg_replace('#(p|div)>#i', "\n", $fragment);
$text = strip_tags($fragment);
$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
return $text;
}
/**
* dumb (https://github.com/rramiachraf/dumb): a self-hosted/public Genius
* frontend that server-renders search + song pages. When USE_DUMB is on,
* these replace the official Genius API as the lyrics/artwork source (no
* GENIUS_TOKEN needed) — same idea as the Genius HTML scrape elsewhere in
* this codebase, just against dumb's own pages instead of genius.com.
* Fragile by nature (scrapes another project's HTML): if dumb changes its
* templates, these regexes may need updating.
*/
/** Search dumb for a track; returns the first song result's page path (e.g. "/Artist-title-lyrics"), or '' if none. */
function dumb_search($artist, $title, $timeout) {
$base = cfg('dumb.url');
if ($base === '') return '';
$query = rawurlencode(trim($artist . ' - ' . $title, ' -'));
$html = http_get($base . '/search?q=' . $query, $timeout);
if ($html === null) return '';
if (!preg_match('/