[object Object]

← back to Animals

auto-save: 2026-07-22T20:16:30 (1 files) — scripts/localize_breed_heroes.cjs

3fc5e1ec1163fdba0bdec91db2b1921eb15585b8 · 2026-07-22 20:16:35 -0700 · Steve Abrams

Files touched

Diff

commit 3fc5e1ec1163fdba0bdec91db2b1921eb15585b8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jul 22 20:16:35 2026 -0700

    auto-save: 2026-07-22T20:16:30 (1 files) — scripts/localize_breed_heroes.cjs
---
 scripts/localize_breed_heroes.cjs | 79 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 79 insertions(+)

diff --git a/scripts/localize_breed_heroes.cjs b/scripts/localize_breed_heroes.cjs
new file mode 100644
index 0000000..a6ccf62
--- /dev/null
+++ b/scripts/localize_breed_heroes.cjs
@@ -0,0 +1,79 @@
+'use strict';
+// Localize breed hero images: the /breeds page hotlinked 140 full-res
+// Wikimedia originals, and Wikimedia 429s burst-loads — most cards rendered
+// broken. Download each hero ONCE as a ~900px thumb via Special:FilePath,
+// store under public/img/breeds/, keep the original URL in
+// breeds.hero_image_remote_url (attribution/credit column untouched).
+//
+// Idempotent: skips breeds whose hero_image_url is already local.
+// Polite: 1.1s between requests, UA identifies us, 429 → 20s backoff x3.
+
+const fs = require('fs');
+const path = require('path');
+const { Pool } = require('pg');
+
+const pool = new Pool({ connectionString: process.env.DATABASE_URL || 'postgresql:///animals_directory?host=/tmp' });
+const OUT_DIR = path.join(__dirname, '..', 'public', 'img', 'breeds');
+const UA = 'AnimalsDirect/1.0 (https://animals.agentabrams.com; info@agentabrams.com) breed-hero-localizer';
+const WIDTH = 900;
+
+const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
+
+function commonsFilename(remoteUrl) {
+  // https://upload.wikimedia.org/wikipedia/commons/2/29/2_Beagles....jpg?utm_...
+  const clean = remoteUrl.split('?')[0];
+  const seg = clean.split('/').pop();
+  return decodeURIComponent(seg);
+}
+
+async function fetchWithRetry(url) {
+  for (let attempt = 1; attempt <= 4; attempt++) {
+    const res = await fetch(url, { headers: { 'User-Agent': UA }, redirect: 'follow' });
+    if (res.status === 429) {
+      console.log(`  429 — backing off 20s (attempt ${attempt})`);
+      await sleep(20_000);
+      continue;
+    }
+    return res;
+  }
+  return null;
+}
+
+(async () => {
+  fs.mkdirSync(OUT_DIR, { recursive: true });
+  const { rows } = await pool.query(`
+    SELECT id, slug, hero_image_url FROM breeds
+    WHERE hero_image_url LIKE 'http%' ORDER BY slug`);
+  console.log(`${rows.length} breeds with remote heroes`);
+
+  await pool.query(`ALTER TABLE breeds ADD COLUMN IF NOT EXISTS hero_image_remote_url TEXT`);
+
+  let ok = 0, fail = 0;
+  for (const b of rows) {
+    const file = commonsFilename(b.hero_image_url);
+    const thumbUrl = `https://commons.wikimedia.org/wiki/Special:FilePath/${encodeURIComponent(file)}?width=${WIDTH}`;
+    try {
+      const res = await fetchWithRetry(thumbUrl);
+      if (!res || !res.ok) { console.log(`FAIL ${b.slug}: HTTP ${res && res.status}`); fail++; continue; }
+      const ct = res.headers.get('content-type') || '';
+      const ext = ct.includes('png') ? 'png' : ct.includes('gif') ? 'gif' : 'jpg';
+      const buf = Buffer.from(await res.arrayBuffer());
+      if (buf.length < 4096) { console.log(`FAIL ${b.slug}: tiny body (${buf.length}B)`); fail++; continue; }
+      const localFile = `${b.slug}.${ext}`;
+      fs.writeFileSync(path.join(OUT_DIR, localFile), buf);
+      await pool.query(
+        `UPDATE breeds SET hero_image_remote_url = hero_image_url,
+                           hero_image_url = $1
+         WHERE id = $2`,
+        [`/static/img/breeds/${localFile}`, b.id]);
+      ok++;
+      console.log(`ok ${b.slug} (${Math.round(buf.length / 1024)}KB)`);
+    } catch (e) {
+      console.log(`FAIL ${b.slug}: ${e.message}`);
+      fail++;
+    }
+    await sleep(1_100);
+  }
+  console.log(`DONE ok=${ok} fail=${fail}`);
+  await pool.end();
+})();

← a4bf2fe animals: basic-auth creds admin/DW2024!; deployed on mac3:97  ·  back to Animals  ·  Fix flag panel popping open on every lightbox view (CSS disp 1ed0c8d →