← back to Costa Rica
scripts/ingest/region-images.js
98 lines
'use strict';
// Fetches a hero image for every region from Wikipedia's REST summary API.
// Returns {originalimage, thumbnail} + an attributable page URL. Public/CC
// licensed via Wikimedia Commons by definition. We never inline-host images
// from the Wikimedia upload origin in production (browser hot-links the
// commons URL directly, which is permitted under their TOU when paired
// with attribution + source link).
const { fetchJson, sleep, pool } = require('./_lib');
const TITLE_FALLBACKS = {
'san-jose': ['San_José,_Costa_Rica', 'San_José_(canton)', 'San_José_Province'],
'la-fortuna': ['La_Fortuna,_San_Carlos', 'Arenal_Volcano'],
'manuel-antonio': ['Manuel_Antonio_National_Park', 'Manuel_Antonio_(canton)'],
'monteverde': ['Monteverde', 'Monteverde_Cloud_Forest_Reserve'],
'santa-elena': ['Santa_Elena,_Puntarenas', 'Monteverde'],
'puerto-viejo': ['Puerto_Viejo_de_Talamanca', 'Puerto_Viejo,_Limón'],
'cahuita': ['Cahuita_National_Park', 'Cahuita'],
'tamarindo': ['Tamarindo,_Costa_Rica'],
'nosara': ['Nosara'],
'santa-teresa': ['Santa_Teresa,_Costa_Rica'],
'montezuma': ['Montezuma,_Costa_Rica'],
'jaco': ['Jacó,_Costa_Rica'],
'quepos': ['Quepos'],
'uvita': ['Uvita'],
'dominical': ['Dominical,_Costa_Rica'],
'limon': ['Limón'],
'puntarenas': ['Puntarenas'],
'papagayo': ['Papagayo,_Costa_Rica', 'Gulf_of_Papagayo'],
'playas-coco': ['Playas_del_Coco'],
'flamingo': ['Playa_Flamingo'],
'samara': ['Sámara,_Costa_Rica'],
'nicoya': ['Nicoya', 'Nicoya_Peninsula'],
'liberia': ['Liberia,_Costa_Rica'],
'heredia': ['Heredia,_Costa_Rica'],
'cartago': ['Cartago,_Costa_Rica'],
'alajuela': ['Alajuela'],
'atenas': ['Atenas,_Costa_Rica'],
'grecia': ['Grecia,_Costa_Rica'],
'escazu': ['Escazú_(canton)'],
'santa-ana': ['Santa_Ana_(canton)'],
};
// Generic fallback: try "<Name>,_Costa_Rica" then "<Name>" alone.
function candidateTitles(slug, name) {
if (TITLE_FALLBACKS[slug]) return TITLE_FALLBACKS[slug];
const safeName = name.replace(/\s+/g, '_');
return [`${safeName},_Costa_Rica`, safeName];
}
async function fetchWikiSummary(title) {
const url = `https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(title)}`;
try {
const j = await fetchJson(url, { headers: { 'Api-User-Agent': 'CR-Directory/0.1 (info@agentabrams.com)' } });
if (j.type === 'disambiguation') return null;
return j;
} catch (e) {
if (/HTTP 404/.test(e.message)) return null;
throw e;
}
}
(async () => {
const { rows: regions } = await pool.query(
`SELECT id, slug, name, image_url FROM regions WHERE region_type IN ('city','town','canton') ORDER BY id`
);
console.log(`[region-images] fetching for ${regions.length} regions`);
let hits = 0, miss = 0, skipped = 0;
for (const r of regions) {
if (r.image_url) { skipped++; continue; }
const titles = candidateTitles(r.slug, r.name);
let summary = null, usedTitle = null;
for (const t of titles) {
const s = await fetchWikiSummary(t);
if (s && (s.thumbnail || s.originalimage)) { summary = s; usedTitle = t; break; }
await sleep(300);
}
if (!summary) { miss++; console.log(`[region-images] miss ${r.slug.padEnd(20)} (${r.name})`); continue; }
const img = (summary.originalimage || summary.thumbnail).source;
const sourceUrl = summary.content_urls?.desktop?.page || `https://en.wikipedia.org/wiki/${encodeURIComponent(usedTitle)}`;
const credit = `Wikipedia: ${summary.title}`;
const license = 'See Wikimedia Commons file page';
await pool.query(
`UPDATE regions SET image_url=$1, image_credit=$2, image_license=$3, image_source_url=$4, image_fetched_at=NOW() WHERE id=$5`,
[img, credit, license, sourceUrl, r.id]
);
hits++;
console.log(`[region-images] HIT ${r.slug.padEnd(20)} → ${usedTitle}`);
await sleep(400);
}
console.log(`[region-images] DONE hits=${hits} miss=${miss} skipped=${skipped}`);
await pool.end();
})();