fix: KöPi - klickbar, Bilder via Proxy, Datum angezeigt
This commit is contained in:
@@ -98,14 +98,18 @@ async function scrapeOffersWithPuppeteer() {
|
|||||||
const productImg = imgs[0]?.src || null;
|
const productImg = imgs[0]?.src || null;
|
||||||
const publisherImg = imgs[1]?.src || null;
|
const publisherImg = imgs[1]?.src || null;
|
||||||
|
|
||||||
// Text parsen: "neuKönig Pilsener bei Trinkgut"Aktuelle Angebote", Seite 6Mo. 6.7. - Sa. 11.7.2026..."
|
// Text parsen
|
||||||
const storeMatch = text.match(/bei\s+(.+?)\s*[""„]/);
|
const storeMatch = text.match(/bei\s+([^""„]+?)\s*[""„]/);
|
||||||
const titleMatch = text.match(/[""„](.+?)[""„]/);
|
const titleMatch = text.match(/[""„]([^""„]+?)[""„]/);
|
||||||
const pageMatch = text.match(/Seite\s+(\d+)/);
|
const pageMatch = text.match(/Seite\s+(\d+)/);
|
||||||
const dateMatch = text.match(/(\w+\.\s+\d+\.\d+\.)\s*-\s*(\w+\.\s+\d+\.\d+\.\d+)/);
|
const validMatch = text.match(/(Noch \d+ Tag[e]? g.ltig|G.ltig bis [\d\.]+)/);
|
||||||
const validMatch = text.match(/(Noch \d+ Tage? gültig|Gültig bis \S+)/);
|
const dateMatch = text.match(/(\d{1,2}\.\d{1,2}\.(?:\d{4})?)\s*-\s*(\w+\.?\s+\d{1,2}\.\d{1,2}\.\d{4})/);
|
||||||
const isNew = text.startsWith('neu');
|
const isNew = text.startsWith('neu');
|
||||||
|
|
||||||
|
// URL aus Link holen
|
||||||
|
const link = b.querySelector('a');
|
||||||
|
const url = link?.href || null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'brochure',
|
type: 'brochure',
|
||||||
store: storeMatch?.[1]?.trim() || null,
|
store: storeMatch?.[1]?.trim() || null,
|
||||||
@@ -114,6 +118,7 @@ async function scrapeOffersWithPuppeteer() {
|
|||||||
dateRange: dateMatch ? `${dateMatch[1]} - ${dateMatch[2]}` : null,
|
dateRange: dateMatch ? `${dateMatch[1]} - ${dateMatch[2]}` : null,
|
||||||
validity: validMatch?.[1] || null,
|
validity: validMatch?.[1] || null,
|
||||||
isNew,
|
isNew,
|
||||||
|
url,
|
||||||
productImage: productImg,
|
productImage: productImg,
|
||||||
publisherLogo: publisherImg,
|
publisherLogo: publisherImg,
|
||||||
};
|
};
|
||||||
@@ -256,4 +261,19 @@ router.post('/clear-cache', authenticate, (req, res) => {
|
|||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Bild-Proxy (umgeht CSP) ──────────────────────────────────────────────────
|
||||||
|
router.get('/img', authenticate, (req, res) => {
|
||||||
|
const url = req.query.url;
|
||||||
|
if (!url || !url.startsWith('https://')) return res.status(400).end();
|
||||||
|
const parsed = new URL(url);
|
||||||
|
const allowed = ['content-media.bonial.biz','publisher-media.bonial.biz','web-assets.kaufda.de'];
|
||||||
|
if (!allowed.some(h => parsed.hostname === h)) return res.status(403).end();
|
||||||
|
const reqImg = https.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, (imgRes) => {
|
||||||
|
res.setHeader('Content-Type', imgRes.headers['content-type'] || 'image/jpeg');
|
||||||
|
res.setHeader('Cache-Control', 'public, max-age=3600');
|
||||||
|
imgRes.pipe(res);
|
||||||
|
});
|
||||||
|
reqImg.on('error', () => res.status(502).end());
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -33,16 +33,21 @@ function publisherColor(name) {
|
|||||||
// ── Prospekt-Angebot Karte ────────────────────────────────────────────────────
|
// ── Prospekt-Angebot Karte ────────────────────────────────────────────────────
|
||||||
function BrochureOfferCard({ offer }) {
|
function BrochureOfferCard({ offer }) {
|
||||||
const color = publisherColor(offer.store);
|
const color = publisherColor(offer.store);
|
||||||
|
const Wrapper = offer.url
|
||||||
|
? ({children}) => <a href={offer.url} target="_blank" rel="noopener noreferrer" style={{textDecoration:'none'}}>{children}</a>
|
||||||
|
: ({children}) => <div>{children}</div>;
|
||||||
return (
|
return (
|
||||||
|
<Wrapper>
|
||||||
<div style={{
|
<div style={{
|
||||||
...S.card, padding:0, overflow:'hidden',
|
...S.card, padding:0, overflow:'hidden', cursor: offer.url ? 'pointer' : 'default',
|
||||||
border:'1px solid rgba(255,255,255,0.08)',
|
border:'1px solid rgba(255,255,255,0.08)',
|
||||||
display:'flex', flexDirection:'column',
|
display:'flex', flexDirection:'column',
|
||||||
}}>
|
}}>
|
||||||
{/* Prospektseite als Bild */}
|
{/* Prospektseite als Bild */}
|
||||||
{offer.productImage && (
|
{offer.productImage && (
|
||||||
<div style={{ position:'relative', height:160, overflow:'hidden', flexShrink:0 }}>
|
<div style={{ position:'relative', height:160, overflow:'hidden', flexShrink:0 }}>
|
||||||
<img src={offer.productImage} alt={`König Pilsener bei ${offer.store}`}
|
<img src={`/api/tools/koepi/img?url=${encodeURIComponent(offer.productImage)}`}
|
||||||
|
alt={`König Pilsener bei ${offer.store}`}
|
||||||
style={{ width:'100%', height:'100%', objectFit:'cover', display:'block' }}
|
style={{ width:'100%', height:'100%', objectFit:'cover', display:'block' }}
|
||||||
onError={e => { e.target.style.display='none'; }}
|
onError={e => { e.target.style.display='none'; }}
|
||||||
/>
|
/>
|
||||||
@@ -80,19 +85,20 @@ function BrochureOfferCard({ offer }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Gültigkeit */}
|
{/* Gültigkeit */}
|
||||||
{offer.validity && (
|
|
||||||
<div style={{ color: offer.validity.includes('Noch') ? '#f59e0b' : 'rgba(255,255,255,0.4)',
|
|
||||||
fontFamily:'monospace', fontSize:10, marginTop:4 }}>
|
|
||||||
🕐 {offer.validity}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{offer.dateRange && (
|
{offer.dateRange && (
|
||||||
<div style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:10, marginTop:2 }}>
|
<div style={{ color:'rgba(255,255,255,0.5)', fontFamily:'monospace', fontSize:10, marginTop:4 }}>
|
||||||
{offer.dateRange}
|
📅 {offer.dateRange}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{offer.validity && (
|
||||||
|
<div style={{ color: offer.validity.includes('Noch') ? '#f59e0b' : '#4ecdc4',
|
||||||
|
fontFamily:'monospace', fontSize:10, marginTop:2 }}>
|
||||||
|
⏰ {offer.validity}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +114,8 @@ function GridOfferCard({ offer }) {
|
|||||||
}}>
|
}}>
|
||||||
{offer.image && (
|
{offer.image && (
|
||||||
<div style={{ height:130, overflow:'hidden', flexShrink:0, background:'#fff', display:'flex', alignItems:'center', justifyContent:'center' }}>
|
<div style={{ height:130, overflow:'hidden', flexShrink:0, background:'#fff', display:'flex', alignItems:'center', justifyContent:'center' }}>
|
||||||
<img src={offer.image} alt={offer.name}
|
<img src={`/api/tools/koepi/img?url=${encodeURIComponent(offer.image)}`}
|
||||||
|
alt={offer.name}
|
||||||
style={{ width:'auto', height:'100%', maxWidth:'100%', objectFit:'contain', display:'block' }}
|
style={{ width:'auto', height:'100%', maxWidth:'100%', objectFit:'contain', display:'block' }}
|
||||||
onError={e => { e.target.parentElement.style.display='none'; }}
|
onError={e => { e.target.parentElement.style.display='none'; }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user