Bild-Proxy (same-origin, defeats hotlink protection) + klareres Reset-Feedback
This commit is contained in:
+6
-5
@@ -1,4 +1,4 @@
|
|||||||
const C24 = u => u; // check24 proxy urls already absolute
|
const IMG = u => '/img?u=' + encodeURIComponent(u); // route images through same-origin proxy
|
||||||
const REG = {
|
const REG = {
|
||||||
kroatien:{flag:'🇭🇷',name:'Kroatien',color:'var(--kro)',mk:'#e8643c'},
|
kroatien:{flag:'🇭🇷',name:'Kroatien',color:'var(--kro)',mk:'#e8643c'},
|
||||||
kanaren:{flag:'🇮🇨',name:'Kanaren',color:'var(--kan)',mk:'#f1a23b'},
|
kanaren:{flag:'🇮🇨',name:'Kanaren',color:'var(--kan)',mk:'#f1a23b'},
|
||||||
@@ -119,7 +119,7 @@ let STATE = {votes:{}};
|
|||||||
|
|
||||||
/* ---------- hero ---------- */
|
/* ---------- hero ---------- */
|
||||||
document.getElementById('herobg').style.backgroundImage =
|
document.getElementById('herobg').style.backgroundImage =
|
||||||
"url('"+byId('bluesun').imgs[0]+"')";
|
"url('"+IMG(byId('bluesun').imgs[0])+"')";
|
||||||
|
|
||||||
/* ---------- voter selector ---------- */
|
/* ---------- voter selector ---------- */
|
||||||
const whoEl = document.getElementById('who');
|
const whoEl = document.getElementById('who');
|
||||||
@@ -166,7 +166,7 @@ function avgFor(id){
|
|||||||
return {avg:n?sum/n:0,n,per};
|
return {avg:n?sum/n:0,n,per};
|
||||||
}
|
}
|
||||||
function cardHTML(o){
|
function cardHTML(o){
|
||||||
const imgs=o.imgs.map((s,i)=>`<img src="${s}" class="${i===0?'on':''}" loading="lazy" alt="${o.name}" onerror="this.remove()">`).join('');
|
const imgs=o.imgs.map((s,i)=>`<img src="${IMG(s)}" class="${i===0?'on':''}" loading="lazy" alt="${o.name}" onerror="this.closest('.ph')&&this.remove()">`).join('');
|
||||||
const dots=o.imgs.length>1?`<div class="dots">${o.imgs.map((_,i)=>`<span class="dot ${i===0?'on':''}"></span>`).join('')}</div>`:'';
|
const dots=o.imgs.length>1?`<div class="dots">${o.imgs.map((_,i)=>`<span class="dot ${i===0?'on':''}"></span>`).join('')}</div>`:'';
|
||||||
const nav=o.imgs.length>1?'<div class="nav l"></div><div class="nav r"></div>':'';
|
const nav=o.imgs.length>1?'<div class="nav l"></div><div class="nav r"></div>':'';
|
||||||
const rate=o.rate?`<span class="crate"><b>${o.rate}</b> ${o.rlabel}${o.bew?(' · '+o.bew+' Bew.'):''}</span>`:(o.bew?`<span class="crate">${o.bew} Bewertungen</span>`:'');
|
const rate=o.rate?`<span class="crate"><b>${o.rate}</b> ${o.rlabel}${o.bew?(' · '+o.bew+' Bew.'):''}</span>`:(o.bew?`<span class="crate">${o.bew} Bewertungen</span>`:'');
|
||||||
@@ -256,10 +256,11 @@ async function sendVote(option,stars){
|
|||||||
try{const r=await fetch('/api/vote',{method:'POST',headers:{'Content-Type':'application/json'},
|
try{const r=await fetch('/api/vote',{method:'POST',headers:{'Content-Type':'application/json'},
|
||||||
body:JSON.stringify({voter:me_,option,stars})});STATE=await r.json();renderVotes();}catch(e){}
|
body:JSON.stringify({voter:me_,option,stars})});STATE=await r.json();renderVotes();}catch(e){}
|
||||||
}
|
}
|
||||||
function flashSaved(){const s=document.getElementById('saved');s.classList.add('on');setTimeout(()=>s.classList.remove('on'),1400);}
|
function flashSaved(msg){const s=document.getElementById('saved');s.textContent=msg||'✓ gespeichert';s.classList.add('on');setTimeout(()=>s.classList.remove('on'),1800);}
|
||||||
document.getElementById('resetBtn').onclick=async()=>{
|
document.getElementById('resetBtn').onclick=async()=>{
|
||||||
if(!confirm('Wirklich ALLE Bewertungen von Till, Lea und Astrid zurücksetzen?'))return;
|
if(!confirm('Wirklich ALLE Bewertungen von Till, Lea und Astrid zurücksetzen?'))return;
|
||||||
try{const r=await fetch('/api/reset',{method:'POST'});STATE=await r.json();renderVotes();flashSaved();}catch(e){}
|
try{const r=await fetch('/api/reset',{method:'POST'});STATE=await r.json();renderVotes();flashSaved('✓ Alle Bewertungen gelöscht');}
|
||||||
|
catch(e){alert('Zurücksetzen fehlgeschlagen – bitte nochmal versuchen.');}
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderAll(){renderWho();renderRecos();renderRegions();}
|
function renderAll(){renderWho();renderRecos();renderRegions();}
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ function saveState(state) {
|
|||||||
}
|
}
|
||||||
let state = loadState();
|
let state = loadState();
|
||||||
|
|
||||||
|
// In-memory image cache for the proxy
|
||||||
|
const imgCache = new Map();
|
||||||
|
function hostAllowed(host) {
|
||||||
|
return /(^|\.)urlaub\.check24\.de$/.test(host) || host === 'files.ahoi-schiff.de';
|
||||||
|
}
|
||||||
|
|
||||||
const MIME = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8',
|
const MIME = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8',
|
||||||
'.css': 'text/css; charset=utf-8', '.json': 'application/json', '.svg': 'image/svg+xml',
|
'.css': 'text/css; charset=utf-8', '.json': 'application/json', '.svg': 'image/svg+xml',
|
||||||
'.png': 'image/png', '.jpg': 'image/jpeg', '.ico': 'image/x-icon', '.webmanifest': 'application/manifest+json' };
|
'.png': 'image/png', '.jpg': 'image/jpeg', '.ico': 'image/x-icon', '.webmanifest': 'application/manifest+json' };
|
||||||
@@ -72,6 +78,34 @@ const server = http.createServer(async (req, res) => {
|
|||||||
}
|
}
|
||||||
if (p === '/health') { return sendJSON(res, 200, { ok: true }); }
|
if (p === '/health') { return sendJSON(res, 200, { ok: true }); }
|
||||||
|
|
||||||
|
// ---- Image proxy (defeats hotlink/referrer protection, same-origin = always loads) ----
|
||||||
|
if (p === '/img' && req.method === 'GET') {
|
||||||
|
const u = url.searchParams.get('u');
|
||||||
|
let host;
|
||||||
|
try { host = new URL(u).host; } catch (e) { res.writeHead(400); return res.end('bad url'); }
|
||||||
|
if (!hostAllowed(host)) { res.writeHead(403); return res.end('host not allowed'); }
|
||||||
|
if (imgCache.has(u)) {
|
||||||
|
const c = imgCache.get(u);
|
||||||
|
res.writeHead(200, { 'Content-Type': c.type, 'Cache-Control': 'public, max-age=604800' });
|
||||||
|
return res.end(c.buf);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const referer = host.endsWith('ahoi-schiff.de') ? 'https://www.ahoi-schiff.de/' : 'https://urlaub.check24.de/';
|
||||||
|
const ac = new AbortController();
|
||||||
|
const t = setTimeout(() => ac.abort(), 8000);
|
||||||
|
const r = await fetch(u, { signal: ac.signal, headers: {
|
||||||
|
'Referer': referer, 'User-Agent': 'Mozilla/5.0 (compatible; HeidrichReise/1.0)', 'Accept': 'image/*'
|
||||||
|
}});
|
||||||
|
clearTimeout(t);
|
||||||
|
if (!r.ok) { res.writeHead(502); return res.end('upstream ' + r.status); }
|
||||||
|
const type = r.headers.get('content-type') || 'image/jpeg';
|
||||||
|
const buf = Buffer.from(await r.arrayBuffer());
|
||||||
|
if (imgCache.size < 300) imgCache.set(u, { buf, type });
|
||||||
|
res.writeHead(200, { 'Content-Type': type, 'Cache-Control': 'public, max-age=604800' });
|
||||||
|
return res.end(buf);
|
||||||
|
} catch (e) { res.writeHead(502); return res.end('proxy error'); }
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Static ----
|
// ---- Static ----
|
||||||
let file = p === '/' ? '/index.html' : decodeURIComponent(p);
|
let file = p === '/' ? '/index.html' : decodeURIComponent(p);
|
||||||
const full = path.join(PUBLIC, path.normalize(file).replace(/^(\.\.[\/\\])+/, ''));
|
const full = path.join(PUBLIC, path.normalize(file).replace(/^(\.\.[\/\\])+/, ''));
|
||||||
|
|||||||
Reference in New Issue
Block a user