Add dumb: a privacy focused genius frontend alternative
This commit is contained in:
1 parent
4b7f1c9c8d
commit
12bc10449e
5 files changed
+155
-46
No files matched your search
@@ -101,3 +101,109 @@ function request_track() {
|
||||
$title = $_POST['title'] ?? $_GET['title'] ?? '';
|
||||
return [trim((string) $artist), trim((string) $title)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the inner HTML of a balanced <div> whose opening tag starts at $lt.
|
||||
* Correctly handles nested <div> 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), '</div', 5) === 0) {
|
||||
$depth--;
|
||||
if ($depth === 0) return substr($html, $start, $next - $start);
|
||||
$i = $next + 5;
|
||||
} elseif (strncasecmp(substr($html, $next, 4), '<div', 4) === 0) {
|
||||
$depth++;
|
||||
$i = $next + 4;
|
||||
} else {
|
||||
$i = $next + 1;
|
||||
}
|
||||
}
|
||||
return substr($html, $start, max(0, $i - $start));
|
||||
}
|
||||
|
||||
/** Turn one container's HTML into plain text with newlines. */
|
||||
function container_to_text($fragment) {
|
||||
// Line breaks -> newlines before stripping tags.
|
||||
$fragment = preg_replace('#<br\s*/?>#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('/<h2>\s*Songs\s*<\/h2>.*?<a[^>]+id="search-item"[^>]+href="([^"]+)"/is', $html, $m)) return '';
|
||||
return html_entity_decode($m[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch + parse a dumb song page ($path from dumb_search()). Returns
|
||||
* ['lyrics' => plain text, 'album' => name, 'art' => absolute album art url,
|
||||
* 'song_art' => absolute song thumbnail url] (any field may be ''), or null
|
||||
* if the page couldn't be fetched.
|
||||
*/
|
||||
function dumb_song($path, $timeout) {
|
||||
$base = cfg('dumb.url');
|
||||
if ($base === '' || $path === '') return null;
|
||||
$html = http_get($base . $path, $timeout);
|
||||
if ($html === null) return null;
|
||||
|
||||
$out = ['lyrics' => '', 'album' => '', 'art' => '', 'song_art' => ''];
|
||||
|
||||
// <div id="lyrics"> wraps the raw Genius lyrics HTML that dumb already
|
||||
// extracted server-side (no data-lyrics-container markers left in it).
|
||||
$idPos = strpos($html, 'id="lyrics"');
|
||||
if ($idPos !== false) {
|
||||
$lt = strrpos(substr($html, 0, $idPos), '<div');
|
||||
if ($lt !== false) $out['lyrics'] = trim(container_to_text(inner_balanced_div($html, $lt)));
|
||||
}
|
||||
|
||||
// Two <img id="album-artwork"> tags can appear: the song thumbnail
|
||||
// (#metadata, always present) and the album cover (#lyrics-album-
|
||||
// container, only when the song belongs to an album — marked with a
|
||||
// title="Album: …" attribute so the two are distinguishable).
|
||||
if (preg_match_all('/<img\b[^>]*>/i', $html, $imgs)) {
|
||||
foreach ($imgs[0] as $img) {
|
||||
if (strpos($img, 'id="album-artwork"') === false) continue;
|
||||
if (!preg_match('/\bsrc="([^"]+)"/i', $img, $src)) continue;
|
||||
$url = $base . html_entity_decode($src[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
if (preg_match('/\btitle="Album:\s*([^"]*)"/i', $img, $t)) {
|
||||
$out['album'] = html_entity_decode($t[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$out['art'] = $url;
|
||||
} elseif ($out['song_art'] === '') {
|
||||
$out['song_art'] = $url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
Reference in new issue
Block a user