← back to Trending Dw
scripts/localize-images.js
67 lines
#!/usr/bin/env node
// localize-images.js — download any REMOTE card images into public/assets/wpb/ and rewrite
// bestsellers.json to serve them SAME-ORIGIN. So the trending site never hotlinks (even our
// own wallpapersback.com) — faster, and resilient if the source host is down.
// $0 (just fetches our own public images). Idempotent: skips ids already downloaded.
const fs = require('fs');
const path = require('path');
const https = require('https');
const ROOT = path.join(__dirname, '..');
const BEST = path.join(ROOT, 'data', 'bestsellers.json');
const DEST = path.join(ROOT, 'public', 'assets', 'wpb');
fs.mkdirSync(DEST, { recursive: true });
const EXT = { 'image/png':'png', 'image/jpeg':'jpg', 'image/webp':'webp', 'image/gif':'gif', 'image/avif':'avif' };
function fetchBuf(url){
return new Promise((resolve, reject) => {
https.get(url, res => {
if (res.statusCode !== 200) { res.resume(); return reject(new Error(url + ' → HTTP ' + res.statusCode)); }
const type = (res.headers['content-type'] || '').split(';')[0].trim();
const chunks = []; res.on('data', c => chunks.push(c));
res.on('end', () => resolve({ buf: Buffer.concat(chunks), type }));
}).on('error', reject);
});
}
(async () => {
const data = JSON.parse(fs.readFileSync(BEST, 'utf8'));
// collect distinct remote images (skip already-local /assets paths)
const remotes = new Map(); // url -> [items]
for (const it of data.items){
if (it.image && /^https?:\/\//.test(it.image)) {
if (!remotes.has(it.image)) remotes.set(it.image, []);
remotes.get(it.image).push(it);
}
}
if (!remotes.size) { console.log('no remote images — already fully local.'); return; }
let ok = 0, fail = 0;
for (const [url, items] of remotes){
// derive a stable local name: wpb/<by-id-id>.<ext>, else a hash of the url
const m = url.match(/\/by-id\/(\d+)/);
const stem = m ? m[1] : 'img-' + Buffer.from(url).toString('base64url').slice(0, 12);
try {
// find an already-downloaded file for this stem (idempotent)
const existing = fs.readdirSync(DEST).find(f => f.startsWith(stem + '.'));
let fname;
if (existing) { fname = existing; }
else {
const { buf, type } = await fetchBuf(url);
if (!buf.length) throw new Error('empty body');
fname = stem + '.' + (EXT[type] || 'png');
fs.writeFileSync(path.join(DEST, fname), buf);
}
const localPath = '/assets/wpb/' + fname;
for (const it of items) it.image = localPath;
ok++; console.log(` ${url} → ${localPath} (${items.length} card${items.length>1?'s':''})`);
} catch (e) {
fail++; console.log(` SKIP ${url} — ${e.message} (left as remote)`);
}
}
data.imagesLocalizedAt = new Date().toLocaleDateString('en-CA');
fs.writeFileSync(BEST, JSON.stringify(data, null, 2));
console.log(`localized ${ok} remote image${ok!==1?'s':''}${fail?`, ${fail} skipped`:''}. All served same-origin from /assets/wpb/.`);
})();