[CHORE] Better dumb search #6
No files matched your search
+65
-4
@@ -102,6 +102,46 @@ function request_track() {
|
||||
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 <div> whose opening tag starts at $lt.
|
||||
* Correctly handles nested <div> elements. Shared by the Genius and dumb
|
||||
@@ -154,15 +194,36 @@ function container_to_text($fragment) {
|
||||
* 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. */
|
||||
/**
|
||||
* Search dumb for a track; returns a song result's page path (e.g.
|
||||
* "/Artist-title-lyrics"), or '' if none. Scans the first 5 song hits for
|
||||
* one actually attributed to $artist (see artist_loosely_matches) and
|
||||
* returns that; falls back to the top hit if none match rather than
|
||||
* returning nothing, so an unusual artist-name spelling can't regress
|
||||
* availability entirely.
|
||||
*/
|
||||
function dumb_search($artist, $title, $timeout) {
|
||||
$base = cfg('dumb.url');
|
||||
if ($base === '') return '';
|
||||
$query = rawurlencode(trim($artist . ' - ' . $title, ' -'));
|
||||
$query = rawurlencode(trim($artist . ' - ' . strip_feature_credit($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');
|
||||
|
||||
if (!preg_match('/<h2>\s*Songs\s*<\/h2>(.*?)(?=<h2>|$)/is', $html, $section)) return '';
|
||||
if (!preg_match_all('/<a[^>]+id="search-item"[^>]+href="([^"]+)"[^>]*>.*?<span>([^<]*)<\/span>/is', $section[1], $hits, PREG_SET_ORDER)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$fallback = '';
|
||||
$checked = 0;
|
||||
foreach ($hits as $h) {
|
||||
$href = html_entity_decode($h[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
if ($fallback === '') $fallback = $href;
|
||||
if ($checked++ >= 5) break;
|
||||
$hitArtist = html_entity_decode($h[2], ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
if (artist_loosely_matches($hitArtist, $artist)) return $href;
|
||||
}
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+15
-2
@@ -85,11 +85,24 @@ function genius_artwork($artist, $title) {
|
||||
if ($token === '') return null;
|
||||
$headers = ['Authorization: Bearer ' . $token];
|
||||
|
||||
$query = rawurlencode(trim($artist . ' - ' . $title, ' -'));
|
||||
$query = rawurlencode(trim($artist . ' - ' . strip_feature_credit($title), ' -'));
|
||||
$search = http_get('https://api.genius.com/search?q=' . $query, 5, $headers);
|
||||
if ($search === null) return null;
|
||||
|
||||
$hit = dot_get(json_decode($search, true), 'response.hits.0.result', null);
|
||||
$hits = dot_get(json_decode($search, true), 'response.hits', []);
|
||||
if (!is_array($hits) || count($hits) === 0) return null;
|
||||
|
||||
// Same artist-attribution scan as api/lyrics.php — skip translation-page
|
||||
// hits filed under a pseudo-artist account, falling back to the raw top
|
||||
// hit if nothing in the first 5 matches.
|
||||
$hit = null;
|
||||
$checked = 0;
|
||||
foreach ($hits as $h) {
|
||||
if ($checked++ >= 5) break;
|
||||
$r = dot_get($h, 'result', null);
|
||||
if ($r && artist_loosely_matches((string) ($r['artist_names'] ?? ''), $artist)) { $hit = $r; break; }
|
||||
}
|
||||
if ($hit === null) $hit = dot_get($hits, '0.result', null);
|
||||
if (!$hit) return null;
|
||||
|
||||
$out = [
|
||||
|
||||
+25
-15
@@ -112,7 +112,7 @@ $token = cfg('genius.token');
|
||||
$headers = ['Authorization: Bearer ' . $token];
|
||||
|
||||
/* -- Find the song on Genius ----------------------------------------------- */
|
||||
$query = rawurlencode(trim($artist . ' - ' . $title, ' -'));
|
||||
$query = rawurlencode(trim($artist . ' - ' . strip_feature_credit($title), ' -'));
|
||||
$search = http_get('https://api.genius.com/search?q=' . $query, $timeout, $headers);
|
||||
if ($search === null) json_out($empty);
|
||||
|
||||
@@ -120,23 +120,33 @@ $json = json_decode($search, true);
|
||||
$hits = dot_get($json, 'response.hits', []);
|
||||
if (!is_array($hits) || count($hits) === 0) json_out($empty);
|
||||
|
||||
$pageUrl = (string) dot_get($hits, '0.result.url', '');
|
||||
// Scan the first 5 hits for one actually attributed to $artist — skips
|
||||
// past translation pages, which Genius files under pseudo-artist accounts
|
||||
// like "Genius Deutsche Übersetzungen" rather than the real artist. Falls
|
||||
// back to the raw top hit if nothing matches, so an unusual artist-name
|
||||
// spelling can't regress availability entirely. When prefer_english is on,
|
||||
// keep scanning matching hits until one is confirmed English.
|
||||
$pageUrl = '';
|
||||
$fallbackUrl = (string) dot_get($hits, '0.result.url', '');
|
||||
$checked = 0;
|
||||
foreach ($hits as $h) {
|
||||
if ($checked++ >= 5) break;
|
||||
if (!artist_loosely_matches((string) dot_get($h, 'result.artist_names', ''), $artist)) continue;
|
||||
|
||||
// Optionally prefer the first English-language match (a few extra API calls).
|
||||
if (cfg('lyrics.prefer_english')) {
|
||||
$checked = 0;
|
||||
foreach ($hits as $h) {
|
||||
if ($checked++ >= 5) break;
|
||||
$id = dot_get($h, 'result.id', null);
|
||||
if (!$id) continue;
|
||||
$songRaw = http_get('https://api.genius.com/songs/' . rawurlencode((string) $id), $timeout, $headers);
|
||||
if ($songRaw === null) continue;
|
||||
if (dot_get(json_decode($songRaw, true), 'response.song.language', '') === 'en') {
|
||||
$pageUrl = (string) dot_get($h, 'result.url', $pageUrl);
|
||||
break;
|
||||
}
|
||||
$hitUrl = (string) dot_get($h, 'result.url', '');
|
||||
if ($pageUrl === '') $pageUrl = $hitUrl;
|
||||
if (!cfg('lyrics.prefer_english')) break;
|
||||
|
||||
$id = dot_get($h, 'result.id', null);
|
||||
if (!$id) continue;
|
||||
$songRaw = http_get('https://api.genius.com/songs/' . rawurlencode((string) $id), $timeout, $headers);
|
||||
if ($songRaw === null) continue;
|
||||
if (dot_get(json_decode($songRaw, true), 'response.song.language', '') === 'en') {
|
||||
$pageUrl = $hitUrl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($pageUrl === '') $pageUrl = $fallbackUrl;
|
||||
if ($pageUrl === '') json_out($empty);
|
||||
|
||||
/* -- Scrape the lyric containers ------------------------------------------- */
|
||||
|
||||
Reference in new issue
Block a user