"""Site icons for Vitals, fetched from the site itself and cached on disk. No third-party icon service, no tracking pixel, no CDN in the middle: the icon comes from the origin you are already talking to, over the same network path the browser used. First request for a host hits the network once, then it is served from ~/.cache/vitals/icons for a month. If a site has no icon, we return None and the UI draws a letter tile in the site's own hash colour. A missing icon is never an error worth showing. """ import hashlib import os import socket import time import urllib.error import urllib.request from urllib.parse import urlparse CACHE_DIR = os.path.expanduser("~/.cache/vitals/icons") MAX_BYTES = 96 * 1024 MAX_AGE_S = 30 * 24 * 3600 TIMEOUT = 2.5 # what a browser would accept, most common first CANDIDATES = ("/favicon.ico", "/favicon.png", "/apple-touch-icon.png") def _host_of(url): try: p = urlparse(url) if p.scheme not in ("http", "https") or not p.hostname: return None return p.scheme, p.hostname, p.port except ValueError: return None def _path_for(host): return os.path.join(CACHE_DIR, hashlib.sha1(host.encode()).hexdigest() + ".img") def _content_type(data): if data[:4] == b"\x89PNG": return "image/png" if data[:6] in (b"GIF87a", b"GIF89a"): return "image/gif" if data[:4] == b"RIFF" and data[8:12] == b"WEBP": return "image/webp" if data[:2] == b"\xff\xd8": return "image/jpeg" if data[:4] == b" 40: return data except (urllib.error.URLError, socket.timeout, OSError, ValueError): continue return None def _refresh_in_background(scheme, host, port, path): import threading def work(): data = _fetch(scheme, host, port) if not data: return try: tmp = path + ".tmp" with open(tmp, "wb") as f: f.write(data) os.replace(tmp, path) except OSError: pass threading.Thread(target=work, daemon=True, name="vitals-icon").start()