← 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/.`);
})();