Search avatars: proxy with a UA + graceful initials fallback

Screenshot showed channel results with empty circles, not the initials chip —
i.e. an avatar URL was present but the image was failing to load. Two causes
addressed:
- The image proxy fetched googleusercontent/yt3 with NO User-Agent, which the
  CDN 403s for some avatars → blank. Now sends a browser UA + Accept header.
- A failed avatar now swaps to the initials chip (onerror → outerHTML) instead
  of hiding the img and leaving an empty circle. Also proxy protocol-relative +
  deeper-subdomain YouTube image hosts. img-proxy tests green.
This commit is contained in:
BoulderBadgeDad 2026-06-17 09:57:42 -07:00
parent 0ff0d07c22
commit 3d67f51c29
2 changed files with 14 additions and 5 deletions

View file

@ -133,7 +133,13 @@ def register_routes(bp):
abort(404)
try:
import requests
upstream = requests.get(url, timeout=15, stream=True)
# A browser UA — Google's image CDN (yt3/googleusercontent) 403s some
# avatars when fetched with no User-Agent, which blanked search results.
upstream = requests.get(url, timeout=15, stream=True, headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"Accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",
})
if upstream.status_code != 200:
abort(404)
resp = Response(upstream.iter_content(8192),

View file

@ -37,18 +37,21 @@
// policy can't blank them out. Non-YouTube / already-proxied urls pass through.
function img(url) {
if (!url) return url;
if (/^https:\/\/([\w-]+\.)?(ytimg\.com|ggpht\.com|googleusercontent\.com)\//i.test(url))
if (url.indexOf('//') === 0) url = 'https:' + url; // protocol-relative
if (/^https:\/\/([\w-]+\.)*(ytimg\.com|ggpht\.com|googleusercontent\.com)\//i.test(url))
return '/api/video/img?u=' + encodeURIComponent(url);
return url;
}
function avatar(ch, cls) {
var url = ch && (ch.poster_url || ch.avatar_url);
var ini = esc(initials(ch && ch.title));
if (url) {
return '<img class="' + cls + '" src="' + esc(img(url)) +
'" alt="" loading="lazy" onerror="this.style.display=\'none\'">';
// If the image can't load, swap to the initials chip (never an empty circle).
return '<img class="' + cls + '" src="' + esc(img(url)) + '" alt="" loading="lazy" ' +
'onerror="this.outerHTML=\'<span class=&quot;' + cls + ' vyt-avatar--ph&quot;>' + ini + '</span>\'">';
}
return '<span class="' + cls + ' vyt-avatar--ph">' + esc(initials(ch && ch.title)) + '</span>';
return '<span class="' + cls + ' vyt-avatar--ph">' + ini + '</span>';
}
// A single wished video tile (thumbnail, title, date, remove).