fetch-browser-icons.sh raw

   1  #!/usr/bin/env bash
   2  # Fetch and convert browser launcher icons into assets/browser-icons/<id>.png
   3  # at 96x96. Uses Wikimedia's Special:FilePath redirector for canonical lookup.
   4  # Anything we can't fetch is left unwritten — the app falls back to the
   5  # in-app colored swatch for that browser.
   6  set -uo pipefail
   7  
   8  cd "$(dirname "$0")/.."
   9  out=assets/browser-icons
  10  mkdir -p "$out"
  11  
  12  UA="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
  13  
  14  # id|filename-on-commons-or-en-wikipedia|origin-host
  15  entries=(
  16    "firefox|Firefox_logo,_2019.svg|commons"
  17    "firefox-nightly|Firefox_Nightly_logo,_2017.svg|commons"
  18    "chrome|Google_Chrome_icon_(February_2022).svg|commons"
  19    "brave|Brave_lion_icon.svg|commons"
  20    "tor|Tor-logo-2011-flat.svg|commons"
  21    "ddg|The_DuckDuckGo_Duck.png|en"
  22  )
  23  
  24  # Direct-URL sources for niche browsers Wikimedia doesn't host.
  25  direct=(
  26    "ironfox|https://ironfoxoss.org/ironfox.png"
  27    "vanadium|https://grapheneos.org/apple-touch-icon.png"
  28  )
  29  
  30  for entry in "${entries[@]}"; do
  31    IFS='|' read -r id file host <<<"$entry"
  32    case $host in
  33      commons) url="https://commons.wikimedia.org/wiki/Special:FilePath/$file" ;;
  34      en)      url="https://en.wikipedia.org/wiki/Special:FilePath/$file" ;;
  35    esac
  36    tmp=$(mktemp -t bf-icon).${file##*.}
  37    if curl -fsSL -A "$UA" -o "$tmp" "$url"; then
  38      ok=0
  39      if [[ ${file##*.} == svg ]] && command -v rsvg-convert >/dev/null; then
  40        rsvg-convert -w 96 -h 96 "$tmp" -o "$out/$id.png" 2>/dev/null && ok=1
  41      fi
  42      if [ $ok -eq 0 ]; then
  43        magick "$tmp" -resize 96x96 -background none -gravity center -extent 96x96 "$out/$id.png" 2>/dev/null && ok=1
  44      fi
  45      [ $ok -eq 1 ] && echo "ok($id)" || echo "convert-fail($id)"
  46    else
  47      echo "fetch-fail($id)"
  48    fi
  49    rm -f "$tmp"
  50    sleep 1
  51  done
  52  
  53  
  54  # ─── Direct-URL sources for niche browsers ──────────────────────────────
  55  for entry in "${direct[@]}"; do
  56    IFS='|' read -r id url <<<"$entry"
  57    if [ -f "$out/$id.png" ]; then echo "skip($id) — already have"; continue; fi
  58    tmp=$(mktemp -t bf-icon).png
  59    if curl -fsSL -A "$UA" -o "$tmp" "$url"; then
  60      magick "$tmp" -resize 96x96 -background none -gravity center -extent 96x96 "$out/$id.png" 2>/dev/null \
  61        && echo "direct($id) ← $url" || echo "convert-fail($id)"
  62    else
  63      echo "fetch-fail($id) ← $url"
  64    fi
  65    rm -f "$tmp"
  66    sleep 1
  67  done
  68  
  69  # Firefox Beta has no canonical public icon distinct from Firefox release.
  70  # Copy the Firefox icon so the row still gets a brand-correct logo.
  71  if [ -f "$out/firefox.png" ] && [ ! -f "$out/firefox-beta.png" ]; then
  72    cp "$out/firefox.png" "$out/firefox-beta.png" && echo "alias(firefox-beta ← firefox)"
  73  fi
  74  
  75  # ─── Fallback: scrape privacytests.org/android ───────────────────────────
  76  # Their browser-logo-image <img> tags carry base64 PNG data URIs that map
  77  # cleanly to several of our ids — and crucially include Firefox Focus, which
  78  # Wikimedia doesn't host. We only fill in ids we don't already have.
  79  echo
  80  echo "Filling gaps from privacytests.org/android…"
  81  pt=$(mktemp -t pt-android).html
  82  if curl -fsSL -A "$UA" -o "$pt" "https://privacytests.org/android"; then
  83    python3 - "$pt" "$out" <<'PY'
  84  import re, base64, pathlib, sys
  85  html_path, out_dir = sys.argv[1], pathlib.Path(sys.argv[2])
  86  html = pathlib.Path(html_path).read_text()
  87  # privacytests name -> our browser id
  88  m = {'brave': 'brave', 'chrome': 'chrome', 'duckduckgo': 'ddg',
  89       'firefox': 'firefox', 'focus': 'focus', 'tor': 'tor'}
  90  hits = re.findall(
  91      r'<img class="browser-logo-image" alt="(\w+) logo" '
  92      r'src="data:image/png;base64,([^"]+)"', html)
  93  for name, b64 in hits:
  94      if name not in m: continue
  95      our = m[name]
  96      dst = out_dir / f"{our}.png"
  97      if dst.exists():
  98          print(f"skip({our}) — already have")
  99          continue
 100      dst.write_bytes(base64.b64decode(b64))
 101      print(f"pt({our}) ← privacytests.org")
 102  PY
 103  fi
 104  rm -f "$pt"
 105  
 106  echo
 107  echo "Wrote $(ls "$out" 2>/dev/null | wc -l | tr -d ' ') icons to $out/"
 108